[
  {
    "path": ".github/ISSUE_TEMPLATE/-dokit-------------.md",
    "content": "---\nname: \"【DoKit生态场景】-描述出现的问题\"\nabout: DoKit生态场景比如Android、iOS、小程序、Flutter，后面跟你需要描述的内容\ntitle: \"【DoKit生态场景】-描述出现的问题\"\nlabels: ''\nassignees: jtsky\n\n---\n\nIt is recommended to refer to the following process before submitting issues：https://github.com/didi/DoraemonKit/issues/745\nIf you still cannot solve your problem, you can submit your issue according to the following template\n\nPlease complete the following informations.\n> Android、iOS? OS version? Brand? \n> Expected behavior and actual behavior.\n> Steps to reproduce the problem.\n> More informations such as error messages and stack traces are welcomed.\n>\n\n建议提issues之前可以参考一下DoKit社区答疑流程：https://github.com/didi/DoraemonKit/issues/745\n假如还是无法解决你的问题，你可以按照以下模板来提交你的issue\n\n请补充如下信息。\n> Android 还是 iOS？系统版本是多少？手机品牌是什么？（如有）\n> 期望的表现和实际的表现。（如有）\n> 问题重现的步骤。（如有）\n> 其他的错误信息和堆栈信息如果有也可以一并提供出来。（如有）\n"
  },
  {
    "path": ".github/issue_template.md",
    "content": "Please complete the following informations.\n> Expected behavior and actual behavior.\n> Steps to reproduce the problem.\n> Android or iOS? OS version? Brand? \n> More informations such as error messages and stack traces are welcomed.\n\n请补充如下信息。\n> 期望的表现和实际的表现。（如有）\n> 问题重现的步骤。（如有）\n> Android 还是 iOS？系统版本是多少？手机品牌是什么？（如有）\n> 其他的错误信息和堆栈信息如果有也可以一并提供出来。（如有）\n> 最好给我们提供可以复现问题的Demo\n"
  },
  {
    "path": ".gitignore",
    "content": "# macOS.gitignore\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n"
  },
  {
    "path": "Android/.editorconfig",
    "content": "# http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 4\nend_of_line = lf\ninsert_final_newline = true\n\n[*.java]\nmax_line_length = 140\nwildcard_import_limit = 99\n\n[*.kt]\nmax_line_length = 160\nwildcard_import_limit = 99\n"
  },
  {
    "path": "Android/.gitignore",
    "content": "*.iml\n.gradle\nlocal.properties\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n.DS_Store\n/build\n/*/build\n/captures\n.externalNativeBuild\n.idea/\n.project\n.classpath\n.settings/\n/*/*.iml\n/*/*/*.iml\n"
  },
  {
    "path": "Android/README.md",
    "content": "# Android接入指南\n\n## DoKit Android 最新版本\n**由于jcenter事件的影响，我们需要将DoKit For Android迁移到mavenCentral()，但是需要更改groupId.所以大家要注意一下，具体的更新信息如下：**\n\n**lastversion:3.5.0；kotlin编译插件为1.4.32 ；支持Gradle 6.8及以上**\n**lastversion:3.5.0.1； kotlin编译插件为1.3.72； 支持Gradle 6.8及以下**\n\n|DoKit|最新版本|描述|\n|-|-|-|\n|3.3.5及以后的Androidx|debugImplementation \"io.github.didi.dokit:${aarName}: ${lastversion}\"|(1)dokitx的library和plugin的groupId及版本号需要保持一致;(2)AGP最低版本要求3.3.0+|\n|3.3.5及以前的Androidx版本|debugImplementation \"com.didichuxing.doraemonkit:${aarName}:3.3.5\"|（1）dokitx的library和plugin的groupId及版本号需要保持一致； (2)AGP最低版本要求3.3.0+|\n|支持android support|debugImplementation \"com.didichuxing.doraemonkit:${aarName}:3.3.5\"|support放弃更新，请大家尽快升级和适配Androidx|\n\n**${aarName}需要改为指定的名称，参考如下:**\n```\n//核心模块\n\ndebugImplementation \"io.github.didi.dokit:dokitx:${lastversion}\"\n\n//文件同步模块\n\ndebugImplementation \"io.github.didi.dokit:dokitx-ft:${lastversion}\"\n\n//一机多控模块\n\ndebugImplementation \"io.github.didi.dokit:dokitx-mc:${lastversion}\"\n\n//weex模块\n\ndebugImplementation \"io.github.didi.dokit:dokitx-weex:${lastversion}\"\n\n//no-op 模块\n\nreleaseImplementation \"io.github.didi.dokit:dokitx-no-op:${lastversion}\"\n```\n\n**debugImplementation 需要根据自己的构建改成对应的productFlavor**\n\n\n**下面所有的例子均用dokitx举例。要使用support版本请将dokitx改为dokit即可。\nv3.3.5以后的版本需要添加mavenCentral()仓库**\n\n\n## 接入步骤\n#### 1. Gradle 依赖\n\n```groovy\ndependencies {\n    debugImplementation 'io.github.didi.dokit:dokitx:${lastversion}'\n    releaseImplementation 'io.github.didi.dokit:dokitx-no-op:${lastversion}'\n}\n```\n\n**滴滴内部业务:**\n\n滴滴内部业务线接入请添加模块\n\n\n```\n//数据mock内部网络库支持\ndebugImplementation 'io.github.didi.dokit:dokitx-rpc:${lastversion}'\n//一机多控内部网络库支持\ndebugImplementation 'io.github.didi.dokit:dokitx-rpc-mc:${lastversion}'\n```\n\n\n\n最新版本参见[这里](https://github.com/didi/DoraemonKit/blob/master/Doc/android-ReleaseNotes.md)。\n\n\n#### 2. 初始化\n\n在 App 启动的时候进行初始化。\n\n```kotlin\noveride fun onCreate() { \n   DoKit.Builder(this)\n            .productId(\"需要使用平台功能的话，需要到dokit.cn平台申请id\")\n            .build()\n} \n```\n\n\n#### 3. 流量监控以及其他AOP功能（可选）\nAOP包括以下几个功能:\n1)百度、腾讯、高德地图的经纬度模拟\n2)UrlConnection、Okhttp 抓包以及后续的接口hook功能\n3)App 启动耗时统计\n4)慢函数\n5)大图\n\n在项目的 `build.gradle` 中添加 classpath。\n\n```groovy\nbuildscript {\n    dependencies {\n        classpath 'io.github.didi.dokit:dokitx-plugin:${lastversion}'\n    }\n}\n```\n\n在 app 的 `build.gradle` 中添加 plugin。\n\n```groovy\napply plugin: 'com.didi.dokit'\n\n```\n\n**插件配置选项:**\n添加到app module 的build.gradle文件下 与android {}处于同一级\n```groovy\ndokitExt {\n    //通用设置\n    comm {\n        //地图经纬度开关\n        gpsSwitch true\n        //网络开关\n        networkSwitch true\n        //大图开关\n        bigImgSwitch true\n        //webView js 抓包\n        webViewSwitch true\n    }\n\n    slowMethod {\n        //调用栈模式配置 对应gradle.properties中DOKIT_METHOD_STRATEGY=0\n        stackMethod {\n            //默认值为 5ms 小于该值的函数在调用栈中不显示\n            thresholdTime 10\n            //调用栈函数入口 千万不要用我默认的配置 如果有特殊需求修改成项目中自己的入口 假如不需要可以去掉该字段\n            enterMethods = [\"com.didichuxing.doraemondemo.MainDebugActivity.test1\"]\n            //黑名单 粒度最小到类 暂不支持到方法  千万不要用我默认的配置 如果有特殊需求修改成项目中自己的入口 假如不需要可以去掉该字段\n            methodBlacklist = [\"com.facebook.drawee.backends.pipeline.Fresco\"]\n        }\n        //普通模式配置 对应gradle.properties中DOKIT_METHOD_STRATEGY=1\n        normalMethod {\n            //默认值为 500ms 小于该值的函数在运行时不会在控制台中被打印\n            thresholdTime 500\n            //需要针对函数插装的包名 千万不要用我默认的配置 如果有特殊需求修改成项目中自己的项目包名 假如不需要可以去掉该字段\n            packageNames = [\"com.didichuxing.doraemondemo\"]\n            //不需要针对函数插装的包名&类名 千万不要用我默认的配置 如果有特殊需求修改成项目中自己的项目包名 假如不需要可以去掉该字段\n            methodBlacklist = [\"com.didichuxing.doraemondemo.dokit\"]\n        }\n    }\n}\n```\n\n其中**strategy**和**methodSwitch**配置项已经弃用。新的配置开关位于项目根目录下的**gradle.properties**中。\n\n具体的配置如下所示：\n```\n// dokit全局配置\n// 插件开关\nDOKIT_PLUGIN_SWITCH=true\n// DOKIT读取三方库会和booster冲突 如果你的项目中也集成了booster 建议将开关改成false\nDOKIT_THIRD_LIB_SWITCH=true\n// 插件日志\nDOKIT_LOG_SWITCH=true\n// 自定义Webview的全限定名 主要是作用于h5 js抓包和数据mock\nDOKIT_WEBVIEW_CLASS_NAME=com/didichuxing/doraemonkit/widget/webview/MyWebView\n// dokit 慢函数开关\nDOKIT_METHOD_SWITCH=true\n// dokit 函数调用栈层级\nDOKIT_METHOD_STACK_LEVEL=4\n// 0:默认模式 打印函数调用栈 需添加指定入口  默认为application onCreate 和attachBaseContext\n// 1:普通模式 运行时打印某个函数的耗时 全局业务代码函数插入\nDOKIT_METHOD_STRATEGY=0\n```\n\n**理由**:\n为了减少项目的编译时间，所以慢函数的默认开关为false。再加上plugin的transform注册必须早于project.afterEvaluate。所以无法通过原先的配置项拿到配置信息，只能通过在全局的gradle.properties中的配置可以拿到。\n\n**tips：**\n当修改完DoKit插件的相关配置以后一定要clean一下重新编译才能生效。这是AS的缓存增量编译导致的，暂时没有其他好的解决方案。\n\n\n\n#### 4. 自定义功能组件（可选）\n\n自定义组件需要实现 IKit 接口，该接口对应哆啦A梦功能面板中的组件。\n\n以代驾乘客端为例，实现环境切换组件如下。\n\n```kotlin\nclass DemoKit : AbstractKit() {\n    override val category: Int\n        get() = Category.BIZ\n    override val name: Int\n        get() = R.string.dk_kit_demo\n    override val icon: Int\n        get() = R.mipmap.dk_sys_info\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        SimpleDoKitStarter.startFloating(DemoDokitView::class.java)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n    }\n\n}\n```\n\n在初始化的时候注册自定义组件。\n\n```kotlin\noverride fun onCreate() {\n    DoKit.Builder(this)\n            .productId(\"需要使用平台功能的话，需要到dokit.cn平台申请id\")\n\t    .customKits(mapKits)\n            .build()\n}\n```\n\n**DoKit入口api**\n```kotlin\npublic class DoKit private constructor() {\n    companion object {\n        \n\n\n        /**\n         * 主icon是否处于显示状态\n         */\n        @JvmStatic\n        val isMainIconShow: Boolean\n            get() = false\n\n\n        /**\n         * 显示主icon\n         */\n        @JvmStatic\n        fun show() {\n        }\n\n        /**\n         * 直接显示工具面板页面\n         */\n        @JvmStatic\n        fun showToolPanel() {\n        }\n\n        /**\n         * 直接隐藏工具面板\n         */\n        @JvmStatic\n        fun hideToolPanel() {\n        }\n\n        /**\n         * 隐藏主icon\n         */\n        @JvmStatic\n        fun hide() {\n        }\n\n        /**\n         * 启动悬浮窗\n         * @JvmStatic:允许使用java的静态方法的方式调用\n         * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n         */\n        @JvmStatic\n        @JvmOverloads\n        fun launchFloating(\n            targetClass: Class<out AbsDokitView>,\n            mode: DoKitViewLaunchMode = DoKitViewLaunchMode.SINGLE_INSTANCE,\n            bundle: Bundle? = null\n        ) {\n        }\n\n\n        /**\n         * 启动悬浮窗\n         * @JvmStatic:允许使用java的静态方法的方式调用\n         * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n         */\n        @JvmStatic\n        @JvmOverloads\n        fun launchFloating(\n            targetClass: KClass<out AbsDokitView>,\n            mode: DoKitViewLaunchMode = DoKitViewLaunchMode.SINGLE_INSTANCE,\n            bundle: Bundle? = null\n        ) {\n        }\n\n        /**\n         * 移除悬浮窗\n         * @JvmStatic:允许使用java的静态方法的方式调用\n         * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n         */\n        @JvmStatic\n        fun removeFloating(targetClass: Class<out AbsDokitView>) {\n        }\n\n        /**\n         * 移除悬浮窗\n         * @JvmStatic:允许使用java的静态方法的方式调用\n         * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n         */\n        @JvmStatic\n        fun removeFloating(targetClass: KClass<out AbsDokitView>) {\n        }\n\n        /**\n         * 移除悬浮窗\n         * @JvmStatic:允许使用java的静态方法的方式调用\n         * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n         */\n        @JvmStatic\n        fun removeFloating(dokitView: AbsDokitView) {\n        }\n\n\n        /**\n         * 启动全屏页面\n         * @JvmStatic:允许使用java的静态方法的方式调用\n         * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n         */\n        @JvmStatic\n        @JvmOverloads\n        fun launchFullScreen(\n            targetClass: Class<out BaseFragment>,\n            context: Context? = null,\n            bundle: Bundle? = null,\n            isSystemFragment: Boolean = false\n        ) {\n        }\n\n        /**\n         * 启动全屏页面\n         * @JvmStatic:允许使用java的静态方法的方式调用\n         * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n         */\n        @JvmStatic\n        @JvmOverloads\n        fun launchFullScreen(\n            targetClass: KClass<out BaseFragment>,\n            context: Context? = null,\n            bundle: Bundle? = null,\n            isSystemFragment: Boolean = false\n        ) {\n        }\n\n\n        @JvmStatic\n        fun <T : AbsDokitView> getDoKitView(\n            activity: Activity?,\n            clazz: Class<out T>\n        ): T? {\n            return null\n        }\n\n        @JvmStatic\n        fun <T : AbsDokitView> getDoKitView(\n            activity: Activity?,\n            clazz: KClass<out T>\n        ): T? {\n            return null\n        }\n\n        /**\n         * 发送自定义一机多控事件\n         */\n        @JvmStatic\n        fun sendCustomEvent(\n            eventType: String,\n            view: View? = null,\n            param: Map<String, String>? = null\n        ) {\n        }\n        /**\n         * 获取一机多控类型\n         */\n        @JvmStatic\n        fun mcMode(): WSMode {\n            return WSMode.UNKNOW\n        }\n    }\n\n\n    class Builder(private val app: Application) {\n        private var productId: String = \"\"\n        private var mapKits: LinkedHashMap<String, List<AbstractKit>> = linkedMapOf()\n        private var listKits: List<AbstractKit> = arrayListOf()\n\n        init {\n        }\n\n        fun productId(productId: String): Builder {\n            return this\n        }\n\n        /**\n         * mapKits & listKits 二选一\n         */\n        fun customKits(mapKits: LinkedHashMap<String, List<AbstractKit>>): Builder {\n            return this\n        }\n\n        /**\n         * mapKits & listKits 二选一\n         */\n        fun customKits(listKits: List<AbstractKit>): Builder {\n            return this\n        }\n\n        /**\n         * H5任意门全局回调\n         */\n        fun webDoorCallback(callback: WebDoorManager.WebDoorCallback): Builder {\n            return this\n        }\n\n        /**\n         * 禁用app信息上传开关，该上传信息只为做DoKit接入量的统计，如果用户需要保护app隐私，可调用该方法进行禁用\n         */\n        fun disableUpload(): Builder {\n            return this\n        }\n\n        fun debug(debug: Boolean): Builder {\n            return this\n        }\n\n        /**\n         * 是否显示主入口icon\n         */\n        fun alwaysShowMainIcon(alwaysShow: Boolean): Builder {\n            return this\n        }\n\n        /**\n         * 设置加密数据库密码\n         */\n        fun databasePass(map: Map<String, String>): Builder {\n            return this\n        }\n\n        /**\n         * 设置文件管理助手http端口号\n         */\n        fun fileManagerHttpPort(port: Int): Builder {\n            return this\n        }\n\n        /**\n         * 一机多控端口号\n         */\n        fun mcWSPort(port: Int): Builder {\n            return this\n        }\n\n        /**\n         * 一机多控自定义拦截器\n         */\n        fun mcClientProcess(interceptor: McClientProcessor): Builder {\n            return this\n        }\n\n        /**\n         *设置dokit的性能监控全局回调\n         */\n        fun callBack(callback: DoKitCallBack): Builder {\n            return this\n        }\n\n\n        /**\n         * 设置扩展网络拦截器的代理对象\n         */\n        fun netExtInterceptor(extInterceptorProxy: DokitExtInterceptor.DokitExtInterceptorProxy): Builder {\n            return this\n        }\n\n\n        fun build() {\n        }\n    }\n}\n```\n开启插件调试\n./gradlew :app:assembleDebug -Dorg.gradle.daemon=false -Dorg.gradle.debug=true\n\n\n#### 5. FAQ\n\n参考[这里](http://xingyun.xiaojukeji.com/docs/dokit/#/SDKProblems)\n"
  },
  {
    "path": "Android/app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "Android/app/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-kapt'\napply from: 'doraemonkit.gradle'\n//apply plugin: 'com.didiglobal.booster'\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n    defaultConfig {\n        applicationId rootProject.ext.android[\"applicationId\"]\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_21\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n        ndk {\n            abiFilters \"armeabi\", \"arm64-v8a\"\n        }\n        multiDexEnabled true\n    }\n    signingConfigs {\n        release {\n            storeFile file(\"keystore/test.keystore\")\n            storePassword \"test123456\"\n            keyAlias \"test\"\n            keyPassword \"test123456\"\n        }\n    }\n    buildTypes {\n        debug {\n            resValue(\"string\", \"dokit_db_Person.db\", \"a_password\")\n            debuggable true\n            minifyEnabled false\n            shrinkResources false\n            signingConfig signingConfigs.release\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n        release {\n//            debuggable true\n            minifyEnabled false\n            shrinkResources false\n            signingConfig signingConfigs.release\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n//    flavorDimensions \"default\"\n//    productFlavors {\n//        product0 {\n//            applicationId \"com.didichuxing.doraemondemo0\"\n////            resValue \"string\", \"app_name\", \"DoKitDemo0\"\n////            manifestPlaceholders = [CHANNEL_VALUE: \"product0\"\n////                                    , app_icon   : \"@mipmap/logo\"]\n//        }\n//        product1 {\n//            applicationId \"com.didichuxing.doraemondemo1\"\n////            resValue \"string\", \"app_name\", \"DoKitDemo1\"\n////            manifestPlaceholders = [CHANNEL_VALUE: \"product1\"\n////                                    , app_icon   : \"@mipmap/logo\"]\n//        }\n//    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n    //指定资源和代码目录\n    sourceSets {\n        debug {\n            manifest.srcFile 'src/debug/java/AndroidManifest.xml'\n            jniLibs.srcDir 'libs'\n        }\n\n        release {\n            manifest.srcFile 'src/release/java/AndroidManifest.xml'\n            jniLibs.srcDir 'libs'\n        }\n    }\n\n\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n    /**\n     * 支持ViewBinding\n     */\n    buildFeatures {\n        viewBinding = true\n    }\n\n\n    packagingOptions {\n        //dokit ktor pickFirst\n//        pickFirst 'META-INF/io.netty.versions.properties'\n//        pickFirst 'META-INF/INDEX.LIST'\n        pickFirst 'lib/x86_64/libc++_shared.so'\n        pickFirst 'lib/arm64-v8a/libc++_shared.so'\n        pickFirst 'lib/armeabi-v7a/libc++_shared.so'\n        pickFirst 'lib/x86/libc++_shared.so'\n        pickFirst 'lib/armeabi/libc++_shared.so'\n    }\n}\n\n\n//dokit 扩展\n\n\ndokit {\n    gpsEnable true\n    bigImageEnable true\n    webView {\n        network true\n        dokitWeb true\n        vConsole true\n    }\n    gps {\n        didi true\n        baidu true\n    }\n}\n\n//dokit {\n//\n//    slowMethod {\n//        //调用栈模式配置\n//        stackMethod {\n//            //默认值为 5ms 小于该值的函数在调用栈中不显示\n//            thresholdTime 5\n//            //调用栈函数入口\n//            enterMethods = [\"com.didichuxing.doraemondemo.MainDebugActivity.test1\"]\n//            //黑名单 粒度最小到类 暂不支持到方法\n//            methodBlacklist = [\"com.facebook.drawee.backends.pipeline.Fresco\"]\n//        }\n//        //普通模式配置\n//        normalMethod {\n//            //默认值为 500ms 小于该值的函数在运行时不会在控制台中被打印\n//            thresholdTime 100\n//            //需要针对函数插装的包名\n//            packageNames = [\"com.didichuxing.doraemondemo\"]\n//            //不需要针对函数插装的包名和类名\n//            methodBlacklist = [\"com.didichuxing.doraemondemo.dokit\"]\n//        }\n//    }\n//}\n\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n\n    implementation project(path: ':dokit-util')\n    implementation project(path: ':dokit-mc')\n    implementation project(path: ':dokit-autotest')\n    implementation project(path: ':dokit-test')\n\n    testImplementation rootProject.ext.dependencies[\"junit\"]\n    implementation rootProject.ext.dependencies[\"constraintLayout\"]\n    implementation rootProject.ext.dependencies[\"multidex\"]\n    implementation rootProject.ext.dependencies[\"flexbox\"]\n    implementation rootProject.ext.dependencies[\"lifecycle-viewmodel-savedstate\"]\n    implementation rootProject.ext.dependencies[\"appcompat\"]\n\n    implementation rootProject.ext.dependencies[\"material\"]\n    if (needKotlinV14()) {\n        implementation rootProject.ext.dependencies[\"kotlin_v14\"]\n        implementation rootProject.ext.dependencies[\"coroutines-core_v14\"]\n        implementation rootProject.ext.dependencies[\"coroutines-android_v14\"]\n        implementation rootProject.ext.dependencies[\"coil_v13\"]\n    } else {\n        implementation rootProject.ext.dependencies[\"kotlin_v13\"]\n        implementation rootProject.ext.dependencies[\"coroutines-core_v13\"]\n        implementation rootProject.ext.dependencies[\"coroutines-android_v13\"]\n        implementation rootProject.ext.dependencies[\"coil_v11\"]\n    }\n    implementation rootProject.ext.dependencies[\"fragment\"]\n    implementation rootProject.ext.dependencies[\"fragment-ktx\"]\n    implementation rootProject.ext.dependencies[\"retrofit2\"]\n    implementation rootProject.ext.dependencies[\"retrofit2_gson\"]\n    implementation rootProject.ext.dependencies[\"retrofit2_scalars\"]\n    implementation rootProject.ext.dependencies[\"retrofit2_rxjava2\"]\n    implementation rootProject.ext.dependencies[\"rxAndroid\"]\n    implementation rootProject.ext.dependencies[\"okhttp_v3\"]\n    implementation rootProject.ext.dependencies[\"okgo\"]\n    implementation rootProject.ext.dependencies[\"glide\"]\n\n//    implementation rootProject.ext.dependencies[\"glide_okhttp3\"]\n    kapt rootProject.ext.dependencies[\"glide_compiler\"]\n\n    implementation rootProject.ext.dependencies[\"picasso\"]\n    implementation rootProject.ext.dependencies[\"fresco\"]\n    implementation rootProject.ext.dependencies[\"fresco-processors\"]\n    implementation rootProject.ext.dependencies[\"image-loader\"]\n    implementation rootProject.ext.dependencies[\"room_runtime\"]\n    implementation rootProject.ext.dependencies[\"wcdb\"]\n    implementation rootProject.ext.dependencies[\"jsoup\"]\n    //weex相关\n    implementation rootProject.ext.dependencies[\"weex_inspector\"]\n    implementation rootProject.ext.dependencies[\"weex_sdk\"]\n    implementation rootProject.ext.dependencies[\"utilcode\"]\n    implementation rootProject.ext.dependencies[\"brvah\"]\n\n    implementation rootProject.ext.dependencies[\"easypermissions\"]\n    releaseImplementation rootProject.ext.dependencies[\"okgo\"]\n    //高德地图定位\n    implementation rootProject.ext.dependencies[\"amap_location\"]\n    //高德搜索\n    implementation rootProject.ext.dependencies[\"amap_search\"]\n    //高德地图\n    implementation rootProject.ext.dependencies[\"amap_navi\"]\n    //腾讯地图定位\n//    implementation rootProject.ext.dependencies[\"tencent_location\"]\n//    implementation rootProject.ext.dependencies[\"tencent_map\"]\n//    implementation rootProject.ext.dependencies[\"tencent_map_util\"]\n//    debugImplementation rootProject.ext.dependencies[\"leakcanary-android\"]\n    //百度地图定位\n//    implementation files('libs/BaiduLBS_Android.jar')\n    //腾讯x5\n    implementation rootProject.ext.dependencies[\"tbs\"]\n    debugImplementation rootProject.ext.dependencies[\"leakcanary_android\"]\n    implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'\n    implementation project(\":dokit-pthread-hook\")\n\n    implementation rootProject.ext.dependencies[\"epic\"]\n}\n\n\n\n//configurations.all {\n//    //循环每个依赖库\n//    resolutionStrategy.eachDependency { DependencyResolveDetails details ->\n//        //获取当前循环到的依赖库\n//        def requested = details.requested\n//        //如果这个依赖库群组的名字是com.android.support\n//        if (requested.group == 'com.squareup.okhttp3') {\n//            //且其名字不是以multidex开头的\n//            if (requested.name == \"okhttp\") {\n//                //这里指定需要统一的依赖版本 比如我的需要配置成27.1.1\n//                //details.useVersion '3.14.7'\n//                details.useVersion rootProject.ext.android[\"okhttp_v4\"]\n//            }\n//        }\n//    }\n//}\n"
  },
  {
    "path": "Android/app/doraemonkit.gradle",
    "content": "def runType = rootProject.ext.publish_config[\"run_type\"]\nif (runType == 0) {\n    // 引用插件\n    apply plugin: 'com.didi.dokit.debug'\n    // 这里引用正常库\n    dependencies {\n        //外部平台依赖\n        debugImplementation project(\":dokit\")\n        debugImplementation project(\":dokit-mc\")\n        debugImplementation project(\":dokit-ft\")\n//        debugImplementation project(\":dokit-weex\")\n//        debugImplementation project(\":dokit-rpc\")\n//        debugImplementation project(\":dokit-rpc-mc\")\n        debugImplementation project(\":dokit-gps-mock\")\n        releaseImplementation project(\":dokit-no-op\")\n    }\n} else if (runType == 1) {\n    apply plugin: 'com.didi.dokit'\n    // 引用no-op的库\n    dependencies {\n        //新版线上包\n        debugImplementation \"io.github.didi.dokit:dokitx:${rootProject.ext.publish_config[\"version\"]}\"\n        debugImplementation \"io.github.didi.dokit:dokitx-ft:${rootProject.ext.publish_config[\"version\"]}\"\n        debugImplementation \"io.github.didi.dokit:dokitx-mc:${rootProject.ext.publish_config[\"version\"]}\"\n        debugImplementation \"io.github.didi.dokit:dokitx-weex:${rootProject.ext.publish_config[\"version\"]}\"\n        debugImplementation \"io.github.didi.dokit:dokit-gps-mock:${rootProject.ext.publish_config[\"version\"]}\"\n        releaseImplementation \"io.github.didi.dokit:dokitx-no-op:${rootProject.ext.publish_config[\"version\"]}\"\n    }\n}\n"
  },
  {
    "path": "Android/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original from file name.\n#-renamesourcefileattribute SourceFile\n\n# start 腾讯地图 SDK\n-keep class com.tencent.tencentmap.**{*;}\n-keep class com.tencent.map.**{*;}\n-keep class com.tencent.beacontmap.**{*;}\n-keep class navsns.**{*;}\n-dontwarn com.qq.**\n-dontwarn com.tencent.**\n# end 腾讯地图 SDK\n\n"
  },
  {
    "path": "Android/app/src/androidTest/java/com/didichuxing/doraemondemo/ExampleInstrumentedTest.java",
    "content": "//package com.didichuxing.doraemondemo;\n//\n//import android.content.Context;\n//\n//import org.junit.Test;\n//import org.junit.runner.RunWith;\n//\n//import static org.junit.Assert.*;\n//\n///**\n// * Instrumented test, which will execute on an Android device.\n// *\n// * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n// */\n//@RunWith(AndroidJUnit4.class)\n//public class ExampleInstrumentedTest {\n//    @Test\n//    public void useAppContext() throws Exception {\n//        // Context of the app under test.\n//        Context appContext = InstrumentationRegistry.getTargetContext();\n//\n//        assertEquals(\"com.didichuxing.doraemondemo\", appContext.getPackageName());\n//    }\n//}\n"
  },
  {
    "path": "Android/app/src/debug/java/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.didichuxing.doraemondemo\">\n    <!-- 腾讯地图 SDK 要求的权限(开始) -->\n    <!-- 访问网络获取地图服务 -->\n    <uses-permission android:name=\"android.permission.INTERNET\" /> <!-- 检查网络可用性 -->\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" /> <!-- 访问WiFi状态 -->\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" /> <!-- 需要外部存储写权限用于保存地图缓存 -->\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" /> <!-- 获取 device id 辨别设备 -->\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />\n\n\n    <!--    tools:replace问题参考  https://developer.android.google.cn/studio/build/manifest-merge?hl=zh-cn tools:replace部分-->\n    <!--    android:requestLegacyExternalStorage=\"true\" 解决sd卡没有权限的问题-->\n    <application\n        android:name=\".App\"\n        android:allowBackup=\"false\"\n        android:icon=\"@mipmap/dk_app_icon\"\n        android:label=\"@string/app_name\"\n        android:requestLegacyExternalStorage=\"true\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\"\n        tools:replace=\"android:allowBackup\">\n        <activity android:name=\".old.MainDebugActivityOkhttpV3\">\n\n        </activity>\n\n        <!--        <activity-->\n        <!--            android:name=\".mc.MCActivity\"-->\n        <!--            android:configChanges=\"orientation|screenSize\">-->\n        <!--            <intent-filter>-->\n        <!--                <action android:name=\"android.intent.action.MAIN\" />-->\n\n        <!--                <category android:name=\"android.intent.category.LAUNCHER\" />-->\n        <!--            </intent-filter>-->\n        <!--        </activity>-->\n\n        <activity android:name=\".mc.WebViewActivity\" />\n        <activity android:name=\".comm.CommFragmentActivity\" />\n\n\n        <!--                <activity-->\n        <!--                    android:name=\".MainDebugActivityOkhttpV3\"-->\n        <!--                    android:configChanges=\"orientation|screenSize\">-->\n        <!--                    <intent-filter>-->\n        <!--                        <action android:name=\"android.intent.action.MAIN\" />-->\n\n        <!--                        <category android:name=\"android.intent.category.LAUNCHER\" />-->\n        <!--                    </intent-filter>-->\n        <!--                </activity>-->\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "Android/app/src/debug/java/com/didichuxing/doraemondemo/AopApp.java",
    "content": "package com.didichuxing.doraemondemo;\n\nimport android.app.Application;\nimport android.content.Context;\n\nimport androidx.multidex.MultiDex;\n\nimport com.didichuxing.doraemondemo.dokit.DemoKit;\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.DoKitCallBack;\nimport com.didichuxing.doraemonkit.kit.AbstractKit;\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord;\nimport com.facebook.drawee.backends.pipeline.Fresco;\nimport com.facebook.imagepipeline.core.ImagePipelineConfig;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/22-17:03\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class AopApp extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        List<AbstractKit> kits = new ArrayList<>();\n        kits.add(new DemoKit());\n        //测试环境:a49842eeebeb1989b3f9565eb12c276b\n        //线上环境:749a0600b5e48dd77cf8ee680be7b1b7\n        //new AopTest().test();\n        ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)\n                .setDiskCacheEnabled(false)\n                .build();\n        Fresco.initialize(this, config);\n\n        //是否显示入口icon\n        // DoraemonKit.setAwaysShowMainIcon(false);\n\n        new DoKit.Builder(this)\n                .productId(\"749a0600b5e48dd77cf8ee680be7b1b7\")\n                .disableUpload()\n                .fileManagerHttpPort(9001)\n                .mcWSPort(5555)\n                .alwaysShowMainIcon(true)\n                .callBack(new DoKitCallBack() {\n                    @Override\n                    public void onNetworkCallBack(@NotNull NetworkRecord record) {\n\n                    }\n\n                    @Override\n                    public void onCpuCallBack(float value, @NotNull String filePath) {\n\n                    }\n\n                    @Override\n                    public void onFpsCallBack(float value, @NotNull String filePath) {\n\n                    }\n\n                    @Override\n                    public void onMemoryCallBack(float value, @NotNull String filePath) {\n\n                    }\n\n\n                })\n                .build();\n\n        //DoraemonKit.install(this, kits, \"70e78c27f9174d68668d8a66a2b66483\")\n    }\n\n\n    @Override\n    protected void attachBaseContext(Context base) {\n        super.attachBaseContext(base);\n        MultiDex.install(base);\n    }\n}\n"
  },
  {
    "path": "Android/app/src/debug/java/com/didichuxing/doraemondemo/App.kt",
    "content": "package com.didichuxing.doraemondemo\n\nimport android.app.Activity\nimport android.app.Application\nimport android.content.Context\nimport android.view.View\nimport androidx.multidex.MultiDex\nimport com.baidu.mapapi.CoordType\nimport com.baidu.mapapi.SDKInitializer\nimport com.blankj.utilcode.util.PathUtils\nimport com.blankj.utilcode.util.ToastUtils\nimport com.didichuxing.doraemondemo.dokit.DemoKit\nimport com.didichuxing.doraemondemo.dokit.TestSimpleDokitFloatViewKit\nimport com.didichuxing.doraemondemo.dokit.TestSimpleDokitFragmentKit\nimport com.didichuxing.doraemondemo.mc.SlideBar\nimport com.didichuxing.doraemondemo.module.http.CustomInterceptor\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.DoKitCallBack\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.core.McClientProcessor\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitExtInterceptor\nimport com.didichuxing.doraemonkit.util.LogUtils\nimport com.facebook.drawee.backends.pipeline.Fresco\nimport com.facebook.imagepipeline.core.ImagePipelineConfig\nimport com.lzy.okgo.OkGo\nimport okhttp3.Cache\nimport okhttp3.Interceptor\nimport okhttp3.OkHttpClient\nimport okhttp3.Response\nimport java.io.File\n\n/**\n * @author jint\n * @mail 704167880@qq.com\n */\nclass App : Application() {\n\n    override fun onCreate() {\n        super.onCreate()\n        //百度地图初始化\n        SDKInitializer.initialize(this)\n        SDKInitializer.setCoordType(CoordType.BD09LL)\n        //测试环境:a49842eeebeb1989b3f9565eb12c276b\n        //线上环境:749a0600b5e48dd77cf8ee680be7b1b7\n        //DoraemonKit.disableUpload()\n        //是否显示入口icon\n        // DoraemonKit.setAwaysShowMainIcon(false);\n\n\n        val kits: MutableList<AbstractKit> = ArrayList()\n        kits.add(DemoKit())\n        kits.add(TestSimpleDokitFloatViewKit())\n        kits.add(TestSimpleDokitFragmentKit())\n\n        val mapKits: LinkedHashMap<String, List<AbstractKit>> = linkedMapOf()\n        mapKits[\"业务专区1\"] = mutableListOf<AbstractKit>().apply {\n            add(DemoKit())\n            add(TestSimpleDokitFloatViewKit())\n            add(TestSimpleDokitFragmentKit())\n        }\n\n        mapKits[\"业务专区2\"] = mutableListOf<AbstractKit>(DemoKit())\n\n\n\n        DoKit.Builder(this)\n            .productId(\"749a0600b5e48dd77cf8ee680be7b1b7\")\n                //测试环境pid\n//            .productId(\"277016abcc33bff1e6a4f1afdf14b8e1\")\n            .disableUpload()\n            .customKits(mapKits)\n            .fileManagerHttpPort(9001)\n            .databasePass(mapOf(\"Person.db\" to \"a_password\"))\n            .mcWSPort(5555)\n            .alwaysShowMainIcon(true)\n            .callBack(object : DoKitCallBack {\n                override fun onCpuCallBack(value: Float, filePath: String) {\n                    super.onCpuCallBack(value, filePath)\n                }\n\n                override fun onFpsCallBack(value: Float, filePath: String) {\n                    super.onFpsCallBack(value, filePath)\n                }\n\n                override fun onMemoryCallBack(value: Float, filePath: String) {\n                    super.onMemoryCallBack(value, filePath)\n                }\n\n                override fun onNetworkCallBack(record: NetworkRecord) {\n                    super.onNetworkCallBack(record)\n                }\n            })\n            .netExtInterceptor(object : DokitExtInterceptor.DokitExtInterceptorProxy {\n                override fun intercept(chain: Interceptor.Chain): Response {\n                    return chain.proceed(chain.request())\n                }\n\n            })\n            .mcClientProcess(object : McClientProcessor {\n                override fun process(\n                    activity: Activity?,\n                    view: View?,\n                    eventType: String,\n                    params: Map<String, String>\n                ) {\n                    when (eventType) {\n                        \"un_lock\" -> {\n                            ToastUtils.showShort(params[\"unlock\"])\n                        }\n                        \"lock_process\" -> {\n                            val leftMargin = params[\"progress\"]?.toInt()\n                            leftMargin?.let {\n                                if (view is SlideBar) {\n                                    view.setMarginLeftExtra(it)\n                                }\n                            }\n\n                        }\n                        else -> {\n\n                        }\n                    }\n\n                }\n\n            })\n            .build()\n\n\n        val client: OkHttpClient = OkHttpClient.Builder()\n            .addInterceptor(CustomInterceptor())\n            .cache(Cache(File(\"${PathUtils.getInternalAppCachePath()}/dokit\"), 1024 * 1024 * 100))\n            .build()\n        OkGo.getInstance().init(this).okHttpClient = client\n\n        val config = ImagePipelineConfig.newBuilder(this)\n            .setDiskCacheEnabled(false)\n            .build()\n        Fresco.initialize(this, config)\n\n//        PaymentConfiguration.init(\n//            this,\n//            \"pk_test_TYooMQauvdEDq54NiTphI7jx\"\n//        )\n\n        //严格检查模式\n        //StrictMode.enableDefaults();\n\n        com.didichuxing.doraemonkit.util.LogUtils.getConfig()\n            .setLogSwitch(true)\n            // 设置是否输出到控制台开关，默认开\n            .setConsoleSwitch(true)\n            // 设置 log 全局标签，默认为空，当全局标签不为空时，我们输出的 log 全部为该 tag， 为空时，如果传入的 tag 为空那就显示类名，否则显示 tag\n            .setGlobalTag(\"Dokit\")\n            // 设置 log 头信息开关，默认为开\n            .setLogHeadSwitch(true)\n            // 打印 log 时是否存到文件的开关，默认关\n            .setLog2FileSwitch(false)\n            // 当自定义路径为空时，写入应用的/cache/log/目录中\n            .setDir(\"\")\n            // 当文件前缀为空时，默认为\"util\"，即写入文件为\"util-MM-dd.txt\"\n            .setFilePrefix(\"djx-table-log\")\n            // 输出日志是否带边框开关，默认开\n            .setBorderSwitch(true)\n            // 一条日志仅输出一条，默认开，为美化 AS 3.1 的 Logcat\n            .setSingleTagSwitch(false)\n            // log 的控制台过滤器，和 logcat 过滤器同理，默认 Verbose\n            .setConsoleFilter(LogUtils.V)\n            // log 文件过滤器，和 logcat 过滤器同理，默认 Verbose\n            .setFileFilter(LogUtils.E)\n            // log 栈深度，默认为 1\n            .setStackDeep(1)\n            .stackOffset = 1\n    }\n\n    override fun attachBaseContext(base: Context) {\n        super.attachBaseContext(base)\n        MultiDex.install(this)\n    }\n\n    companion object {\n        private const val TAG = \"App\"\n        var leakActivity: Activity? = null\n    }\n}\n"
  },
  {
    "path": "Android/app/src/debug/java/com/didichuxing/doraemondemo/amap/AMapUtil.java",
    "content": "/**\n * \n */\npackage com.didichuxing.doraemondemo.amap;\n\nimport android.text.Html;\nimport android.text.Spanned;\nimport android.widget.EditText;\n\nimport com.amap.api.maps.model.LatLng;\nimport com.amap.api.services.core.LatLonPoint;\nimport com.amap.api.services.route.BusPath;\nimport com.amap.api.services.route.BusStep;\nimport com.amap.api.services.route.RouteBusLineItem;\nimport com.amap.api.services.route.RouteRailwayItem;\nimport com.didichuxing.doraemondemo.R;\n\nimport java.text.DecimalFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\n\npublic class AMapUtil {\n\t/**\n\t * 判断edittext是否null\n\t */\n\tpublic static String checkEditText(EditText editText) {\n\t\tif (editText != null && editText.getText() != null\n\t\t\t\t&& !(editText.getText().toString().trim().equals(\"\"))) {\n\t\t\treturn editText.getText().toString().trim();\n\t\t} else {\n\t\t\treturn \"\";\n\t\t}\n\t}\n\n\tpublic static Spanned stringToSpan(String src) {\n\t\treturn src == null ? null : Html.fromHtml(src.replace(\"\\n\", \"<br />\"));\n\t}\n\n\tpublic static String colorFont(String src, String color) {\n\t\tStringBuffer strBuf = new StringBuffer();\n\n\t\tstrBuf.append(\"<font color=\").append(color).append(\">\").append(src)\n\t\t\t\t.append(\"</font>\");\n\t\treturn strBuf.toString();\n\t}\n\n\tpublic static String makeHtmlNewLine() {\n\t\treturn \"<br />\";\n\t}\n\n\tpublic static String makeHtmlSpace(int number) {\n\t\tfinal String space = \"&nbsp;\";\n\t\tStringBuilder result = new StringBuilder();\n\t\tfor (int i = 0; i < number; i++) {\n\t\t\tresult.append(space);\n\t\t}\n\t\treturn result.toString();\n\t}\n\n\tpublic static String getFriendlyLength(int lenMeter) {\n\t\tif (lenMeter > 10000) // 10 km\n\t\t{\n\t\t\tint dis = lenMeter / 1000;\n\t\t\treturn dis + ChString.Kilometer;\n\t\t}\n\n\t\tif (lenMeter > 1000) {\n\t\t\tfloat dis = (float) lenMeter / 1000;\n\t\t\tDecimalFormat fnum = new DecimalFormat(\"##0.0\");\n\t\t\tString dstr = fnum.format(dis);\n\t\t\treturn dstr + ChString.Kilometer;\n\t\t}\n\n\t\tif (lenMeter > 100) {\n\t\t\tint dis = lenMeter / 50 * 50;\n\t\t\treturn dis + ChString.Meter;\n\t\t}\n\n\t\tint dis = lenMeter / 10 * 10;\n\t\tif (dis == 0) {\n\t\t\tdis = 10;\n\t\t}\n\n\t\treturn dis + ChString.Meter;\n\t}\n\n\tpublic static boolean IsEmptyOrNullString(String s) {\n\t\treturn (s == null) || (s.trim().length() == 0);\n\t}\n\n\t/**\n\t * 把LatLng对象转化为LatLonPoint对象\n\t */\n\tpublic static LatLonPoint convertToLatLonPoint(LatLng latlon) {\n\t\treturn new LatLonPoint(latlon.latitude, latlon.longitude);\n\t}\n\n\t/**\n\t * 把LatLonPoint对象转化为LatLon对象\n\t */\n\tpublic static LatLng convertToLatLng(LatLonPoint latLonPoint) {\n\t\treturn new LatLng(latLonPoint.getLatitude(), latLonPoint.getLongitude());\n\t}\n\n\t/**\n\t * 把集合体的LatLonPoint转化为集合体的LatLng\n\t */\n\tpublic static ArrayList<LatLng> convertArrList(List<LatLonPoint> shapes) {\n\t\tArrayList<LatLng> lineShapes = new ArrayList<LatLng>();\n\t\tfor (LatLonPoint point : shapes) {\n\t\t\tLatLng latLngTemp = convertToLatLng(point);\n\t\t\tlineShapes.add(latLngTemp);\n\t\t}\n\t\treturn lineShapes;\n\t}\n\n\t/**\n\t * long类型时间格式化\n\t */\n\tpublic static String convertToTime(long time) {\n\t\tSimpleDateFormat df = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n\t\tDate date = new Date(time);\n\t\treturn df.format(date);\n\t}\n\n\tpublic static final String HtmlBlack = \"#000000\";\n\tpublic static final String HtmlGray = \"#808080\";\n\t\n\tpublic static String getFriendlyTime(int second) {\n\t\tif (second > 3600) {\n\t\t\tint hour = second / 3600;\n\t\t\tint miniate = (second % 3600) / 60;\n\t\t\treturn hour + \"小时\" + miniate + \"分钟\";\n\t\t}\n\t\tif (second >= 60) {\n\t\t\tint miniate = second / 60;\n\t\t\treturn miniate + \"分钟\";\n\t\t}\n\t\treturn second + \"秒\";\n\t}\n\t\n\t//路径规划方向指示和图片对应\n\t\tpublic static int getDriveActionID(String actionName) {\n\t\t\tif (actionName == null || actionName.equals(\"\")) {\n\t\t\t\treturn R.mipmap.dir3;\n\t\t\t}\n\t\t\tif (\"左转\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir2;\n\t\t\t}\n\t\t\tif (\"右转\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir1;\n\t\t\t}\n\t\t\tif (\"向左前方行驶\".equals(actionName) || \"靠左\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir6;\n\t\t\t}\n\t\t\tif (\"向右前方行驶\".equals(actionName) || \"靠右\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir5;\n\t\t\t}\n\t\t\tif (\"向左后方行驶\".equals(actionName) || \"左转调头\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir7;\n\t\t\t}\n\t\t\tif (\"向右后方行驶\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir8;\n\t\t\t}\n\t\t\tif (\"直行\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir3;\n\t\t\t}\n\t\t\tif (\"减速行驶\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir4;\n\t\t\t}\n\t\t\treturn R.mipmap.dir3;\n\t\t}\n\t\t\n\t\tpublic static int getWalkActionID(String actionName) {\n\t\t\tif (actionName == null || actionName.equals(\"\")) {\n\t\t\t\treturn R.mipmap.dir13;\n\t\t\t}\n\t\t\tif (\"左转\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir2;\n\t\t\t}\n\t\t\tif (\"右转\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir1;\n\t\t\t}\n\t\t\tif (\"向左前方\".equals(actionName) || \"靠左\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir6;\n\t\t\t}\n\t\t\tif (\"向右前方\".equals(actionName) || \"靠右\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir5;\n\t\t\t}\n\t\t\tif (\"向左后方\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir7;\n\t\t\t}\n\t\t\tif (\"向右后方\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir8;\n\t\t\t}\n\t\t\tif (\"直行\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir3;\n\t\t\t}\n\t\t\tif (\"通过人行横道\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir9;\n\t\t\t}\n\t\t\tif (\"通过过街天桥\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir11;\n\t\t\t}\n\t\t\tif (\"通过地下通道\".equals(actionName)) {\n\t\t\t\treturn R.mipmap.dir10;\n\t\t\t}\n\n\t\t\treturn R.mipmap.dir13;\n\t\t}\n\t\t\n\t\tpublic static String getBusPathTitle(BusPath busPath) {\n\t\t\tif (busPath == null) {\n\t\t\t\treturn String.valueOf(\"\");\n\t\t\t}\n\t\t\tList<BusStep> busSetps = busPath.getSteps();\n\t\t\tif (busSetps == null) {\n\t\t\t\treturn String.valueOf(\"\");\n\t\t\t}\n\t\t\tStringBuffer sb = new StringBuffer();\n\t\t\tfor (BusStep busStep : busSetps) {\n\t\t\t\t StringBuffer title = new StringBuffer();\n\t\t\t   if (busStep.getBusLines().size() > 0) {\n\t\t\t\t   for (RouteBusLineItem busline : busStep.getBusLines()) {\n\t\t\t\t\t   if (busline == null) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t  \n\t\t\t\t\t   String buslineName = getSimpleBusLineName(busline.getBusLineName());\n\t\t\t\t\t   title.append(buslineName);\n\t\t\t\t\t   title.append(\" / \");\n\t\t\t\t}\n//\t\t\t\t\tRouteBusLineItem busline = busStep.getBusLines().get(0);\n\t\t\t\t   \n\t\t\t\t\tsb.append(title.substring(0, title.length() - 3));\n\t\t\t\t\tsb.append(\" > \");\n\t\t\t\t}\n\t\t\t\tif (busStep.getRailway() != null) {\n\t\t\t\t\tRouteRailwayItem railway = busStep.getRailway();\n\t\t\t\t\tsb.append(railway.getTrip()+\"(\"+railway.getDeparturestop().getName()\n\t\t\t\t\t\t\t+\" - \"+railway.getArrivalstop().getName()+\")\");\n\t\t\t\t\tsb.append(\" > \");\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn sb.substring(0, sb.length() - 3);\n\t\t}\n\n\t\tpublic static String getBusPathDes(BusPath busPath) {\n\t\t\tif (busPath == null) {\n\t\t\t\treturn String.valueOf(\"\");\n\t\t\t}\n\t\t\tlong second = busPath.getDuration();\n\t\t\tString time = getFriendlyTime((int) second);\n\t\t\tfloat subDistance = busPath.getDistance();\n\t\t\tString subDis = getFriendlyLength((int) subDistance);\n\t\t\tfloat walkDistance = busPath.getWalkDistance();\n\t\t\tString walkDis = getFriendlyLength((int) walkDistance);\n\t\t\treturn String.valueOf(time + \" | \" + subDis + \" | 步行\" + walkDis);\n\t\t}\n\t\t\n\t\tpublic static String getSimpleBusLineName(String busLineName) {\n\t\t\tif (busLineName == null) {\n\t\t\t\treturn String.valueOf(\"\");\n\t\t\t}\n\t\t\treturn busLineName.replaceAll(\"\\\\(.*?\\\\)\", \"\");\n\t\t}\n\t\n}\n"
  },
  {
    "path": "Android/app/src/debug/java/com/didichuxing/doraemondemo/amap/ChString.java",
    "content": "package com.didichuxing.doraemondemo.amap;\n\npublic class ChString {\n\tpublic static final String Kilometer = \"\\u516c\\u91cc\";// \"公里\";\n\tpublic static final String Meter = \"\\u7c73\";// \"米\";\n\tpublic static final String ByFoot = \"\\u6b65\\u884c\";// \"步行\";\n\tpublic static final String To = \"\\u53bb\\u5f80\";// \"去往\";\n\tpublic static final String Station = \"\\u8f66\\u7ad9\";// \"车站\";\n\tpublic static final String TargetPlace = \"\\u76ee\\u7684\\u5730\";// \"目的地\";\n\tpublic static final String StartPlace = \"\\u51fa\\u53d1\\u5730\";// \"出发地\";\n\tpublic static final String About = \"\\u5927\\u7ea6\";// \"大约\";\n\tpublic static final String Direction = \"\\u65b9\\u5411\";// \"方向\";\n\n\tpublic static final String GetOn = \"\\u4e0a\\u8f66\";// \"上车\";\n\tpublic static final String GetOff = \"\\u4e0b\\u8f66\";// \"下车\";\n\tpublic static final String Zhan = \"\\u7ad9\";// \"站\";\n\n\tpublic static final String cross = \"\\u4ea4\\u53c9\\u8def\\u53e3\"; // 交叉路口\n\tpublic static final String type = \"\\u7c7b\\u522b\"; // 类别\n\tpublic static final String address = \"\\u5730\\u5740\"; // 地址\n\tpublic static final String PrevStep = \"\\u4e0a\\u4e00\\u6b65\";\n\tpublic static final String NextStep = \"\\u4e0b\\u4e00\\u6b65\";\n\tpublic static final String Gong = \"\\u516c\\u4ea4\";\n\tpublic static final String ByBus = \"\\u4e58\\u8f66\";\n\tpublic static final String Arrive = \"\\u5230\\u8FBE\";// 到达\n}\n"
  },
  {
    "path": "Android/app/src/debug/java/com/didichuxing/doraemondemo/amap/DrivingRouteOverLay.java",
    "content": "package com.didichuxing.doraemondemo.amap;\n\nimport android.content.Context;\nimport android.graphics.Color;\n\nimport com.amap.api.maps.AMap;\nimport com.amap.api.maps.model.BitmapDescriptor;\nimport com.amap.api.maps.model.BitmapDescriptorFactory;\nimport com.amap.api.maps.model.LatLng;\nimport com.amap.api.maps.model.LatLngBounds;\nimport com.amap.api.maps.model.Marker;\nimport com.amap.api.maps.model.MarkerOptions;\nimport com.amap.api.maps.model.PolylineOptions;\nimport com.amap.api.services.core.LatLonPoint;\nimport com.amap.api.services.route.DrivePath;\nimport com.amap.api.services.route.DriveStep;\nimport com.amap.api.services.route.TMC;\nimport com.didichuxing.doraemondemo.R;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n/**\n * 导航路线图层类。\n */\npublic class DrivingRouteOverLay extends RouteOverlay{\n\n\tprivate DrivePath drivePath;\n    private List<LatLonPoint> throughPointList;\n    private List<Marker> throughPointMarkerList = new ArrayList<Marker>();\n    private boolean throughPointMarkerVisible = true;\n    private List<TMC> tmcs;\n    private PolylineOptions mPolylineOptions;\n    private PolylineOptions mPolylineOptionscolor = null;\n    private Context mContext;\n    private boolean isColorfulline = true;\n    private float mWidth = 25;\n    private List<LatLng> mLatLngsOfPath;\n\n\tpublic void setIsColorfulline(boolean iscolorfulline) {\n\t\tthis.isColorfulline = iscolorfulline;\n\t}\n\n\t/**\n     * 根据给定的参数，构造一个导航路线图层类对象。\n     *\n     * @param amap      地图对象。\n     * @param path 导航路线规划方案。\n     * @param context   当前的activity对象。\n     */\n    public DrivingRouteOverLay(Context context, AMap amap, DrivePath path,\n                               LatLonPoint start, LatLonPoint end, List<LatLonPoint> throughPointList) {\n    \tsuper(context);\n    \tmContext = context; \n        mAMap = amap; \n        this.drivePath = path;\n        startPoint = AMapUtil.convertToLatLng(start);\n        endPoint = AMapUtil.convertToLatLng(end);\n        this.throughPointList = throughPointList;\n    }\n\n    public float getRouteWidth() {\n        return mWidth;\n    }\n\n    /**\n     * 设置路线宽度\n     *\n     * @param mWidth 路线宽度，取值范围：大于0\n     */\n    public void setRouteWidth(float mWidth) {\n        this.mWidth = mWidth;\n    }\n\n    /**\n     * 添加驾车路线添加到地图上显示。\n     */\n\tpublic void addToMap() {\n\t\tinitPolylineOptions();\n        try {\n            if (mAMap == null) {\n                return;\n            }\n\n            if (mWidth == 0 || drivePath == null) {\n                return;\n            }\n            mLatLngsOfPath = new ArrayList<LatLng>();\n            tmcs = new ArrayList<TMC>();\n            List<DriveStep> drivePaths = drivePath.getSteps();\n            mPolylineOptions.add(startPoint);\n            for (DriveStep step : drivePaths) {\n                List<LatLonPoint> latlonPoints = step.getPolyline();\n                List<TMC> tmclist = step.getTMCs();\n                tmcs.addAll(tmclist);\n                addDrivingStationMarkers(step, convertToLatLng(latlonPoints.get(0)));\n                for (LatLonPoint latlonpoint : latlonPoints) {\n                \tmPolylineOptions.add(convertToLatLng(latlonpoint));\n                \tmLatLngsOfPath.add(convertToLatLng(latlonpoint));\n\t\t\t\t}\n            }\n            mPolylineOptions.add(endPoint);\n            if (startMarker != null) {\n                startMarker.remove();\n                startMarker = null;\n            }\n            if (endMarker != null) {\n                endMarker.remove();\n                endMarker = null;\n            }\n            addStartAndEndMarker();\n            addThroughPointMarker();\n            if (isColorfulline && tmcs.size()>0 ) {\n            \tcolorWayUpdate(tmcs);\n\t\t\t}else {\n\t\t\t\tshowPolyline();\n\t\t\t}            \n            \n        } catch (Throwable e) {\n        \te.printStackTrace();\n        }\n    }\n\n\t/**\n     * 初始化线段属性\n     */\n    private void initPolylineOptions() {\n\n        mPolylineOptions = null;\n\n        mPolylineOptions = new PolylineOptions();\n        mPolylineOptions.color(getDriveColor()).width(getRouteWidth());\n    }\n\n    private void showPolyline() {\n        addPolyLine(mPolylineOptions);\n    }\n    \n\n    /**\n     * 根据不同的路段拥堵情况展示不同的颜色\n     *\n     * @param tmcSection\n     */\n    private void colorWayUpdate(List<TMC> tmcSection) {\n        if (mAMap == null) {\n            return;\n        }\n        if (tmcSection == null || tmcSection.size() <= 0) {\n            return;\n        }\n        TMC segmentTrafficStatus;\n        addPolyLine(new PolylineOptions().add(startPoint,\n        \t\tAMapUtil.convertToLatLng(tmcSection.get(0).getPolyline().get(0)))\n\t\t\t\t.setDottedLine(true));\n        String status = \"\";\n        for (int i = 0; i < tmcSection.size(); i++) {\n        \tsegmentTrafficStatus = tmcSection.get(i);\n        \tList<LatLonPoint> mployline = segmentTrafficStatus.getPolyline();\n        \tif (status.equals(segmentTrafficStatus.getStatus())) {\n    \t\t\tfor (int j = 1; j < mployline.size(); j++) {//第一个点和上一段最后一个点重复，这个不重复添加\n    \t\t\t\tmPolylineOptionscolor.add(AMapUtil.convertToLatLng(mployline.get(j)));\n    \t\t\t}\n\t\t\t}else {\n\t\t\t\tif (mPolylineOptionscolor != null) {\n\t\t\t\t\taddPolyLine(mPolylineOptionscolor.color(getcolor(status)));\n\t\t\t\t} \n\t\t\t\tmPolylineOptionscolor = null;\n\t\t\t\tmPolylineOptionscolor = new PolylineOptions().width(getRouteWidth());\n\t\t\t\tstatus = segmentTrafficStatus.getStatus();\n    \t\t\tfor (int j = 0; j < mployline.size(); j++) {\n    \t\t\t\tmPolylineOptionscolor.add(AMapUtil.convertToLatLng(mployline.get(j)));\n    \t\t\t}\n\t\t\t}\n        \tif (i == tmcSection.size()-1 && mPolylineOptionscolor != null) {\n        \t\taddPolyLine(mPolylineOptionscolor.color(getcolor(status)));\n        \t\taddPolyLine(new PolylineOptions().add(\n        \t\t\t\tAMapUtil.convertToLatLng(mployline.get(mployline.size()-1)), endPoint)\n         \t\t\t   .setDottedLine(true));\n\t\t\t}\n\t\t}\n    }\n    \n    private int getcolor(String status) {\n\n    \tif (status.equals(\"畅通\")) {\n    \t\treturn Color.GREEN;\n\t\t} else if (status.equals(\"缓行\")) {\n\t\t\t return Color.YELLOW;\n\t\t} else if (status.equals(\"拥堵\")) {\n\t\t\treturn Color.RED;\n\t\t} else if (status.equals(\"严重拥堵\")) {\n\t\t\treturn Color.parseColor(\"#990033\");\n\t\t} else {\n\t\t\treturn Color.parseColor(\"#537edc\");\n\t\t}\t\n\t}\n\n\tpublic LatLng convertToLatLng(LatLonPoint point) {\n        return new LatLng(point.getLatitude(),point.getLongitude());\n  }\n    \n    /**\n     * @param driveStep\n     * @param latLng\n     */\n    private void addDrivingStationMarkers(DriveStep driveStep, LatLng latLng) {\n        addStationMarker(new MarkerOptions()\n                .position(latLng)\n                .title(\"\\u65B9\\u5411:\" + driveStep.getAction()\n                        + \"\\n\\u9053\\u8DEF:\" + driveStep.getRoad())\n                .snippet(driveStep.getInstruction()).visible(nodeIconVisible)\n                .anchor(0.5f, 0.5f).icon(getDriveBitmapDescriptor()));\n    }\n\n    @Override\n    protected LatLngBounds getLatLngBounds() {\n        LatLngBounds.Builder b = LatLngBounds.builder();\n        b.include(new LatLng(startPoint.latitude, startPoint.longitude));\n        b.include(new LatLng(endPoint.latitude, endPoint.longitude));\n        if (this.throughPointList != null && this.throughPointList.size() > 0) {\n            for (int i = 0; i < this.throughPointList.size(); i++) {\n                b.include(new LatLng(\n                        this.throughPointList.get(i).getLatitude(),\n                        this.throughPointList.get(i).getLongitude()));\n            }\n        }\n        return b.build();\n    }\n\n    public void setThroughPointIconVisibility(boolean visible) {\n        try {\n            throughPointMarkerVisible = visible;\n            if (this.throughPointMarkerList != null\n                    && this.throughPointMarkerList.size() > 0) {\n                for (int i = 0; i < this.throughPointMarkerList.size(); i++) {\n                    this.throughPointMarkerList.get(i).setVisible(visible);\n                }\n            }\n        } catch (Throwable e) {\n            e.printStackTrace();\n        }\n    }\n    \n    private void addThroughPointMarker() {\n        if (this.throughPointList != null && this.throughPointList.size() > 0) {\n            LatLonPoint latLonPoint = null;\n            for (int i = 0; i < this.throughPointList.size(); i++) {\n                latLonPoint = this.throughPointList.get(i);\n                if (latLonPoint != null) {\n                    throughPointMarkerList.add(mAMap\n                            .addMarker((new MarkerOptions())\n                                    .position(\n                                            new LatLng(latLonPoint\n                                                    .getLatitude(), latLonPoint\n                                                    .getLongitude()))\n                                    .visible(throughPointMarkerVisible)\n                                    .icon(getThroughPointBitDes())\n                                    .title(\"\\u9014\\u7ECF\\u70B9\")));\n                }\n            }\n        }\n    }\n    \n    private BitmapDescriptor getThroughPointBitDes() {\n    \treturn BitmapDescriptorFactory.fromResource(R.mipmap.amap_through);\n       \n    }\n\n    /**\n     * 获取两点间距离\n     *\n     * @param start\n     * @param end\n     * @return\n     */\n    public static int calculateDistance(LatLng start, LatLng end) {\n        double x1 = start.longitude;\n        double y1 = start.latitude;\n        double x2 = end.longitude;\n        double y2 = end.latitude;\n        return calculateDistance(x1, y1, x2, y2);\n    }\n\n    public static int calculateDistance(double x1, double y1, double x2, double y2) {\n        final double NF_pi = 0.01745329251994329; // 弧度 PI/180\n        x1 *= NF_pi;\n        y1 *= NF_pi;\n        x2 *= NF_pi;\n        y2 *= NF_pi;\n        double sinx1 = Math.sin(x1);\n        double siny1 = Math.sin(y1);\n        double cosx1 = Math.cos(x1);\n        double cosy1 = Math.cos(y1);\n        double sinx2 = Math.sin(x2);\n        double siny2 = Math.sin(y2);\n        double cosx2 = Math.cos(x2);\n        double cosy2 = Math.cos(y2);\n        double[] v1 = new double[3];\n        v1[0] = cosy1 * cosx1 - cosy2 * cosx2;\n        v1[1] = cosy1 * sinx1 - cosy2 * sinx2;\n        v1[2] = siny1 - siny2;\n        double dist = Math.sqrt(v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2]);\n\n        return (int) (Math.asin(dist / 2) * 12742001.5798544);\n    }\n\n\n    //获取指定两点之间固定距离点\n    public static LatLng getPointForDis(LatLng sPt, LatLng ePt, double dis) {\n        double lSegLength = calculateDistance(sPt, ePt);\n        double preResult = dis / lSegLength;\n        return new LatLng((ePt.latitude - sPt.latitude) * preResult + sPt.latitude, (ePt.longitude - sPt.longitude) * preResult + sPt.longitude);\n    }\n    /**\n     * 去掉DriveLineOverlay上的线段和标记。\n     */\n    @Override\n    public void removeFromMap() {\n        try {\n            super.removeFromMap();\n            if (this.throughPointMarkerList != null\n                    && this.throughPointMarkerList.size() > 0) {\n                for (int i = 0; i < this.throughPointMarkerList.size(); i++) {\n                    this.throughPointMarkerList.get(i).remove();\n                }\n                this.throughPointMarkerList.clear();\n            }\n        } catch (Throwable e) {\n            e.printStackTrace();\n        }\n    }\n}"
  },
  {
    "path": "Android/app/src/debug/java/com/didichuxing/doraemondemo/amap/FloatGpsMockRouteKitView.java",
    "content": "package com.didichuxing.doraemondemo.amap;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.FrameLayout;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemondemo.R;\nimport com.didichuxing.doraemondemo.dokit.SimpleDoKitView;\nimport com.didichuxing.doraemonkit.gps_mock.lbs.route.FloatGpsRouteMockCache;\n\npublic class FloatGpsMockRouteKitView extends SimpleDoKitView {\n    public static final String TAG = \"FloatGpsMockRoutKitView\";\n    private View mRootView;\n    private static int sMockSpeed = 10;\n    private TextView mMockSpeedTv;\n    private SeekBar mSpeedSeekBar;\n    private SeekBar routeSeekBar;\n    private TextView mockRouteTv;\n    private Handler handler = new Handler(Looper.getMainLooper());\n    private Runnable updateRouteUiRunnable = new Runnable() {\n        @Override\n        public void run() {\n            routeSeekBar.setMax(FloatGpsRouteMockCache.getRouteCount());\n            routeSeekBar.setProgress(FloatGpsRouteMockCache.getMockRouteProgress());\n            mockRouteTv.setText(\"模拟路线进度:\" + FloatGpsRouteMockCache.getMockRouteProgress() + \" / \" + FloatGpsRouteMockCache.getRouteCount());\n            if (FloatGpsRouteMockCache.getRouteCount() == 0) {\n                mockRouteTv.setText(\"使用 FloatGpsRouteMockCache.mockGpsRoute 设置模拟线路\");\n            }\n        }\n    };\n\n    @Override\n    protected int getLayoutId() {\n        return R.layout.layout_mock_route;\n    }\n\n\n    @Override\n    public void onViewCreated(FrameLayout rootView) {\n        super.onViewCreated(rootView);\n        mRootView = rootView;\n        setMockLocationConfig();\n    }\n\n    private void setMockLocationConfig() {\n\n        // 路线模拟工具\n        FloatGpsRouteMockCache.setRouteChangeListener(new FloatGpsRouteMockCache.IOnRouteChange() {\n            @Override\n            public void onRouteChange() {\n                updateRouteUI(0);\n            }\n\n            public void onIndexChange(int index) {\n                Log.d(TAG, \"⚠️ onIndexChange() called with: index = [\" + index + \"]\");\n                updateRouteUI(index);\n            }\n        });\n        mockRouteTv = findViewById(R.id.tv_mock_route);\n        routeSeekBar = findViewById(R.id.dk_sb_mock_route_seekBar);\n        routeSeekBar.setMax(FloatGpsRouteMockCache.getRouteCount());\n        routeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {\n            @Override\n            public void onProgressChanged(SeekBar routeSeekBar, int progress, boolean fromUser) {\n                if (fromUser) {\n                    updateRoute(getContext().getApplicationContext(), progress);\n                }\n            }\n\n            @Override\n            public void onStartTrackingTouch(SeekBar routeSeekBar) {\n            }\n\n            @Override\n            public void onStopTrackingTouch(SeekBar routeSeekBar) {\n            }\n        });\n        findViewById(R.id.btn_route_back).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                updateRoute(v.getContext().getApplicationContext(), FloatGpsRouteMockCache.getMockRouteProgress() - 1);\n            }\n        });\n\n        findViewById(R.id.btn_route_forward).setOnClickListener(new View.OnClickListener() {\n\n            @Override\n            public void onClick(View v) {\n                updateRoute(v.getContext().getApplicationContext(), FloatGpsRouteMockCache.getMockRouteProgress() + 1);\n            }\n        });\n\n        findViewById(R.id.btn_route_pause).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                FloatGpsRouteMockCache.pausePlayMockRoute();\n            }\n        });\n        findViewById(R.id.btn_route_resume).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                FloatGpsRouteMockCache.resumePlayMockRoute(getContext());\n            }\n        });\n    }\n\n    private void updateRoute(Context context, int index) {\n        index = FloatGpsRouteMockCache.setMockRouteProgress(context, index);\n        updateRouteUI(index);\n    }\n\n    private void updateRouteUI(int index) {\n        handler.post(updateRouteUiRunnable);\n    }\n\n}\n"
  },
  {
    "path": "Android/app/src/debug/java/com/didichuxing/doraemondemo/amap/FloatGpsPresetMockKit.java",
    "content": "package com.didichuxing.doraemondemo.amap;\n\nimport android.app.Activity;\nimport android.content.Context;\n\nimport com.blankj.utilcode.util.ToastUtils;\nimport com.didichuxing.doraemondemo.R;\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig;\nimport com.didichuxing.doraemonkit.kit.AbstractKit;\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Created by changzuozhen on 2021年1月22日\n */\npublic class FloatGpsPresetMockKit extends AbstractKit {\n\n\n    @Override\n    public int getName() {\n        return R.string.dk_kit_gps_mock_preset;\n    }\n\n    @Override\n    public int getIcon() {\n        return R.mipmap.dk_mock_location_preset;\n    }\n\n    @Override\n    public boolean onClickWithReturn(@NotNull Activity activity) {\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip));\n            return false;\n        }\n\n        if (!DokitPluginConfig.SWITCH_GPS) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_gps_close_tip));\n            return false;\n        }\n\n        DoKit.launchFloating(FloatGpsPresetMockKitView.class);\n        return true;\n    }\n\n    @Override\n    public void onAppInit(Context context) {\n\n    }\n\n    @Override\n    public boolean isInnerKit() {\n        return false;\n    }\n\n\n}"
  },
  {
    "path": "Android/app/src/debug/java/com/didichuxing/doraemondemo/amap/FloatGpsPresetMockKitView.java",
    "content": "package com.didichuxing.doraemondemo.amap;\n\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.Button;\nimport android.widget.FrameLayout;\nimport android.widget.Switch;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemondemo.R;\nimport com.didichuxing.doraemondemo.dokit.SimpleDoKitView;\nimport com.didichuxing.doraemonkit.gps_mock.lbs.common.LocInfo;\nimport com.didichuxing.doraemonkit.gps_mock.lbs.manual.FloatGpsMockCache;\nimport com.didichuxing.doraemonkit.gps_mock.lbs.preset.FloatGpsPresetMockCache;\nimport com.didichuxing.doraemonkit.gps_mock.lbs.preset.MockLocList;\nimport com.google.android.flexbox.FlexboxLayout;\n\nimport java.util.ArrayList;\n\npublic class FloatGpsPresetMockKitView extends SimpleDoKitView {\n    public static final String TAG = \"FloatGpsPresetMockKitView\";\n    private View mRootView;\n\n    @Override\n    protected int getLayoutId() {\n        return R.layout.layout_mock_location_preset;\n    }\n\n\n    @Override\n    public void onViewCreated(FrameLayout rootView) {\n        super.onViewCreated(rootView);\n        mRootView = rootView;\n        setMockLocationConfig();\n    }\n\n    private void setMockLocationConfig() {\n        // 模拟位置预 单点\n        final MockLocList locationList = FloatGpsPresetMockCache.sMockLocationList;\n        ArrayList<String> configString = new ArrayList<>();\n        for (LocInfo locInfo : locationList) {\n            configString.add(locInfo.locName);\n        }\n\n        LocInfo config = FloatGpsPresetMockCache.getMockLocConfig();\n        if (config != null) {\n            Log.i(getTAG(), \"⚠️setMockLocationConfig() setSelection called\" + config.locName);\n            updateCurrentLocConfig(config);\n        }\n\n        FlexboxLayout flexboxLayout = findViewById(R.id.cl_mock_gps_flexbox_container);\n        for (final LocInfo locInfo : FloatGpsPresetMockCache.sMockLocationList) {\n            Button button = new Button(getContext());\n            button.setText(locInfo.locName);\n            button.setMinWidth(0);\n            button.setMinHeight(0);\n            button.setMinimumWidth(0);//必须同时设置这个\n            button.setMinimumHeight(0);//必须同时设置这个\n            button.setTextSize(9);\n            button.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    try {\n                        Log.d(getTAG(), \"⚠️onNewItemSelected() called with: locInfo = [\" + locInfo + \"]\");\n                        FloatGpsMockCache.mockToLocation(locInfo.lat, locInfo.lng);\n                        ((Switch) findViewById(R.id.env_switch3)).setChecked(true);\n                        updateCurrentLocConfig(locInfo);\n                    } catch (Exception e) {\n                    }\n                }\n            });\n            flexboxLayout.addView(button);\n        }\n    }\n\n\n    private void updateCurrentLocConfig(LocInfo currentConfig) {\n        TextView envInfo = this.findViewById(R.id.env_info3);\n        updateGsonInfo(currentConfig.toString(), envInfo);\n    }\n\n    private void updateGsonInfo(String currentConfig, TextView envInfo) {\n        if (currentConfig == null) {\n            envInfo.setVisibility(View.GONE);\n        } else {\n            envInfo.setVisibility(View.VISIBLE);\n            envInfo.setText(currentConfig);\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/app/src/debug/java/com/didichuxing/doraemondemo/amap/RouteOverlay.java",
    "content": "package com.didichuxing.doraemondemo.amap;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\n\n\nimport com.amap.api.maps.AMap;\nimport com.amap.api.maps.CameraUpdateFactory;\nimport com.amap.api.maps.model.BitmapDescriptor;\nimport com.amap.api.maps.model.BitmapDescriptorFactory;\nimport com.amap.api.maps.model.LatLng;\nimport com.amap.api.maps.model.LatLngBounds;\nimport com.amap.api.maps.model.Marker;\nimport com.amap.api.maps.model.MarkerOptions;\nimport com.amap.api.maps.model.Polyline;\nimport com.amap.api.maps.model.PolylineOptions;\nimport com.didichuxing.doraemondemo.R;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class RouteOverlay {\n\tprotected List<Marker> stationMarkers = new ArrayList<Marker>();\n\tprotected List<Polyline> allPolyLines = new ArrayList<Polyline>();\n\tprotected Marker startMarker;\n\tprotected Marker endMarker;\n\tprotected LatLng startPoint;\n\tprotected LatLng endPoint;\n\tprotected AMap mAMap;\n\tprivate Context mContext;\n\tprivate Bitmap startBit, endBit, busBit, walkBit, driveBit;\n\tprotected boolean nodeIconVisible = true;\n\n\tpublic RouteOverlay(Context context) {\n\t\tmContext = context;\n\t}\n\n\t/**\n\t * 去掉BusRouteOverlay上所有的Marker。\n\t * @since V2.1.0\n\t */\n\tpublic void removeFromMap() {\n\t\tif (startMarker != null) {\n\t\t\tstartMarker.remove();\n\n\t\t}\n\t\tif (endMarker != null) {\n\t\t\tendMarker.remove();\n\t\t}\n\t\tfor (Marker marker : stationMarkers) {\n\t\t\tmarker.remove();\n\t\t}\n\t\tfor (Polyline line : allPolyLines) {\n\t\t\tline.remove();\n\t\t}\n\t\tdestroyBit();\n\t}\n\n\tprivate void destroyBit() {\n\t\tif (startBit != null) {\n\t\t\tstartBit.recycle();\n\t\t\tstartBit = null;\n\t\t}\n\t\tif (endBit != null) {\n\t\t\tendBit.recycle();\n\t\t\tendBit = null;\n\t\t}\n\t\tif (busBit != null) {\n\t\t\tbusBit.recycle();\n\t\t\tbusBit = null;\n\t\t}\n\t\tif (walkBit != null) {\n\t\t\twalkBit.recycle();\n\t\t\twalkBit = null;\n\t\t}\n\t\tif (driveBit != null) {\n\t\t\tdriveBit.recycle();\n\t\t\tdriveBit = null;\n\t\t}\n\t}\n\t/**\n\t * 给起点Marker设置图标，并返回更换图标的图片。如不用默认图片，需要重写此方法。\n\t * @return 更换的Marker图片。\n\t * @since V2.1.0\n\t */\n\tprotected BitmapDescriptor getStartBitmapDescriptor() {\n\t\treturn BitmapDescriptorFactory.fromResource(R.mipmap.amap_start);\n\t}\n\t/**\n\t * 给终点Marker设置图标，并返回更换图标的图片。如不用默认图片，需要重写此方法。\n\t * @return 更换的Marker图片。\n\t * @since V2.1.0\n\t */\n\tprotected BitmapDescriptor getEndBitmapDescriptor() {\n\t\treturn BitmapDescriptorFactory.fromResource(R.mipmap.amap_end);\n\t}\n\t/**\n\t * 给公交Marker设置图标，并返回更换图标的图片。如不用默认图片，需要重写此方法。\n\t * @return 更换的Marker图片。\n\t * @since V2.1.0\n\t */\n\tprotected BitmapDescriptor getBusBitmapDescriptor() {\n\t\treturn BitmapDescriptorFactory.fromResource(R.mipmap.dk_lbs_bus);\n\t}\n\t/**\n\t * 给步行Marker设置图标，并返回更换图标的图片。如不用默认图片，需要重写此方法。\n\t * @return 更换的Marker图片。\n\t * @since V2.1.0\n\t */\n\tprotected BitmapDescriptor getWalkBitmapDescriptor() {\n\t\treturn BitmapDescriptorFactory.fromResource(R.mipmap.dk_lbs_man);\n\t}\n\n\tprotected BitmapDescriptor getDriveBitmapDescriptor() {\n\t\treturn BitmapDescriptorFactory.fromResource(R.mipmap.dk_lbs_car);\n\t}\n\n\tprotected void addStartAndEndMarker() {\n\t\tstartMarker = mAMap.addMarker((new MarkerOptions())\n\t\t\t\t.position(startPoint).icon(getStartBitmapDescriptor())\n\t\t\t\t.title(\"\\u8D77\\u70B9\"));\n\t\t// startMarker.showInfoWindow();\n\n\t\tendMarker = mAMap.addMarker((new MarkerOptions()).position(endPoint)\n\t\t\t\t.icon(getEndBitmapDescriptor()).title(\"\\u7EC8\\u70B9\"));\n\t\t// mAMap.moveCamera(CameraUpdateFactory.newLatLngZoom(startPoint,\n\t\t// getShowRouteZoom()));\n\t}\n\t/**\n\t * 移动镜头到当前的视角。\n\t * @since V2.1.0\n\t */\n\tpublic void zoomToSpan() {\n\t\tif (startPoint != null) {\n\t\t\tif (mAMap == null)\n\t\t\t\treturn;\n\t\t\ttry {\n\t\t\t\tLatLngBounds bounds = getLatLngBounds();\n\t\t\t\tmAMap.animateCamera(CameraUpdateFactory\n\t\t\t\t\t\t.newLatLngBounds(bounds, 50));\n\t\t\t} catch (Throwable e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected LatLngBounds getLatLngBounds() {\n\t\tLatLngBounds.Builder b = LatLngBounds.builder();\n\t\tb.include(new LatLng(startPoint.latitude, startPoint.longitude));\n\t\tb.include(new LatLng(endPoint.latitude, endPoint.longitude));\n\t\treturn b.build();\n\t}\n\t/**\n\t * 路段节点图标控制显示接口。\n\t * @param visible true为显示节点图标，false为不显示。\n\t * @since V2.3.1\n\t */\n\tpublic void setNodeIconVisibility(boolean visible) {\n\t\ttry {\n\t\t\tnodeIconVisible = visible;\n\t\t\tif (this.stationMarkers != null && this.stationMarkers.size() > 0) {\n\t\t\t\tfor (int i = 0; i < this.stationMarkers.size(); i++) {\n\t\t\t\t\tthis.stationMarkers.get(i).setVisible(visible);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (Throwable e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n\t\n\tprotected void addStationMarker(MarkerOptions options) {\n\t\tif(options == null) {\n\t\t\treturn;\n\t\t}\n\t\tMarker marker = mAMap.addMarker(options);\n\t\tif(marker != null) {\n\t\t\tstationMarkers.add(marker);\n\t\t}\n\t\t\n\t}\n\n\tprotected void addPolyLine(PolylineOptions options) {\n\t\tif(options == null) {\n\t\t\treturn;\n\t\t}\n\t\tPolyline polyline = mAMap.addPolyline(options);\n\t\tif(polyline != null) {\n\t\t\tallPolyLines.add(polyline);\n\t\t}\n\t}\n\t\n\tprotected float getRouteWidth() {\n\t\treturn 18f;\n\t}\n\n\tprotected int getWalkColor() {\n\t\treturn Color.parseColor(\"#6db74d\");\n\t}\n\n\t/**\n\t * 自定义路线颜色。\n\t * return 自定义路线颜色。\n\t * @since V2.2.1\n\t */\n\tprotected int getBusColor() {\n\t\treturn Color.parseColor(\"#537edc\");\n\t}\n\n\tprotected int getDriveColor() {\n\t\treturn Color.parseColor(\"#537edc\");\n\t}\n\n\t// protected int getShowRouteZoom() {\n\t// return 15;\n\t// }\n}\n"
  },
  {
    "path": "Android/app/src/debug/java/com/didichuxing/doraemondemo/amap/mockroute/BearingUtils.java",
    "content": "package com.didichuxing.doraemondemo.amap.mockroute;\n\n/**\n * changzuozhen\n * 2021年 04月 20日\n */\npublic final class BearingUtils {\n    private BearingUtils() {\n    }\n\n    public static double bearing(double lat1, double lon1, double lat2, double lon2) {\n        double longitude1 = lon1;\n        double longitude2 = lon2;\n        double latitude1 = Math.toRadians(lat1);\n        double latitude2 = Math.toRadians(lat2);\n        double longDiff = Math.toRadians(longitude2 - longitude1);\n        double y = Math.sin(longDiff) * Math.cos(latitude2);\n        double x = Math.cos(latitude1) * Math.sin(latitude2) - Math.sin(latitude1) * Math.cos(latitude2) * Math.cos(longDiff);\n\n        return (Math.toDegrees(Math.atan2(y, x)) + 360) % 360;\n    }\n}\n"
  },
  {
    "path": "Android/app/src/debug/java/com/didichuxing/doraemondemo/amap/mockroute/LogUtils.java",
    "content": "package com.didichuxing.doraemondemo.amap.mockroute;\n\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.util.Log;\n\n/**\n * 日志控制\n */\npublic final class LogUtils {\n\n    public static boolean S_OPEN_LOG = true;\n    static volatile long sCurrentForSpendTimeLog = System.currentTimeMillis();\n    static volatile int sSpendTimeIndex = 0;\n    private static final String TAG = \"_AndyTest_\";\n\n    private static HandlerThread mBackThread;\n    private static Handler sHandler;\n\n    static HandlerThread getBackThread() {\n        if (mBackThread == null) {\n            synchronized (LogUtils.class) {\n                if (mBackThread == null) {\n                    mBackThread = new HandlerThread(\"LogUtils Thread\");\n                    mBackThread.start();\n                }\n            }\n        }\n        return mBackThread;\n    }\n\n    static Handler getHandler() {\n        if (sHandler == null) {\n            synchronized (LogUtils.class) {\n                if (sHandler == null) {\n                    sHandler = new Handler(getBackThread().getLooper());\n                }\n            }\n        }\n        return sHandler;\n    }\n\n    private LogUtils() {\n    }\n\n    public static void d(String logKey, CharSequence msg) {\n        d(logKey, msg, 2);\n    }\n\n    public static void d(String logKey, final CharSequence msg, final Object... args) {\n        d(logKey, msg.toString() + args, 2);\n    }\n\n    /**\n     * @param stackIndex 1:当前位置，2：上级栈位置，0：logcat 的位置（没有意义）\n     */\n    public static void d(String logKey, CharSequence msg, int stackIndex) {\n        if (S_OPEN_LOG) {\n            StackTraceElement ste = new Throwable().getStackTrace()[stackIndex];\n            String log = build(msg, ste);\n            Log.d(logKey, log);\n        }\n    }\n\n    public static void d(CharSequence msg) {\n        if (S_OPEN_LOG) {\n            StackTraceElement ste = new Throwable().getStackTrace()[1];\n            String log = build(msg, ste);\n            Log.d(TAG, log);\n\n        }\n    }\n\n    public static void i(String logKey, CharSequence msg, int stackIndex) {\n        if (S_OPEN_LOG) {\n            StackTraceElement ste = new Throwable().getStackTrace()[stackIndex];\n            String log = build(msg, ste);\n            Log.i(logKey, log);\n//            Log.i(TAG, \"[\" + logKey + \"]\" + log);\n        }\n    }\n\n    public static void i(String logKey, CharSequence msg) {\n        i(logKey, msg, 2);\n    }\n\n    public static void v(String logKey, CharSequence msg, int stackIndex) {\n        if (S_OPEN_LOG) {\n            StackTraceElement ste = new Throwable().getStackTrace()[stackIndex];\n            String log = build(msg, ste);\n            Log.v(logKey, log);\n//            Log.v(TAG, \"[\" + logKey + \"]\" + log);\n        }\n    }\n\n    public static void v(String logKey, CharSequence msg) {\n        v(logKey, msg, 2);\n    }\n\n    public static void w(String logKey, CharSequence msg) {\n        if (S_OPEN_LOG) {\n            StackTraceElement ste = new Throwable().getStackTrace()[1];\n            String log = build(logKey, msg, ste);\n            Log.w(logKey, log);\n//            Log.w(TAG, \"[\" + logKey + \"]\" + log);\n        }\n    }\n\n    /**\n     * 打印error级别的log\n     *\n     * @param tag tag标签\n     */\n    public static void e(String tag, Throwable tr) {\n        if (S_OPEN_LOG) {\n            StackTraceElement ste = new Throwable().getStackTrace()[1];\n            String log = build(tag, \"\", ste, tr);\n            Log.e(tag, log, tr);\n//            Log.e(tag, \"[\" + Thread.currentThread().getId() + \"]\" + tr.getMessage(), tr);\n        }\n    }\n\n    public static void e(String logKey, CharSequence msg) {\n        if (S_OPEN_LOG) {\n            StackTraceElement ste = new Throwable().getStackTrace()[1];\n            String log = build(logKey, msg, ste);\n            Log.e(logKey, log);\n\n        }\n    }\n\n    public static void e(String logKey, CharSequence msg, Throwable e) {\n        if (S_OPEN_LOG) {\n            StackTraceElement ste = new Throwable().getStackTrace()[1];\n            String log = build(logKey, msg, ste);\n            Log.d(logKey, log, e);\n//            Log.e(TAG, log, e);\n        }\n    }\n\n    /**\n     * 打印调用栈信息\n     */\n    public static void t(String logKey, CharSequence msg) {\n        if (S_OPEN_LOG) {\n            StackTraceElement[] stackTrace = new Throwable().getStackTrace();\n            StringBuilder sb = new StringBuilder();\n            if (stackTrace.length > 0) {\n                sb.append(build(msg, stackTrace[1]));\n                sb.append('\\n');\n            } else {\n                sb.append(msg);\n            }\n            for (int i = 2; i < stackTrace.length; i++) {\n                sb.append(stackTrace[i]);\n                sb.append('\\n');\n            }\n            Log.i(logKey, sb.toString());\n        }\n    }\n\n    public static void timeSinceLast() {\n        LogUtils.d(TAG, \"⚠️\" + (sSpendTimeIndex++) + \"spend:\" + (System.currentTimeMillis() - sCurrentForSpendTimeLog) + \" \", 2);\n        sCurrentForSpendTimeLog = System.currentTimeMillis();\n    }\n\n\n    public static String getLineText(String msg, int stackIndex) {\n        if (S_OPEN_LOG) {\n            StackTraceElement ste = new Throwable().getStackTrace()[stackIndex];\n            String log = build(msg, ste);\n            return log;\n        } else {\n            return msg;\n        }\n    }\n\n    public static String getLineText(String msg) {\n        if (S_OPEN_LOG) {\n            int stackIndex = 1;\n            StackTraceElement ste = new Throwable().getStackTrace()[stackIndex];\n            String log = build(msg, ste);\n            return log;\n        } else {\n            return msg;\n        }\n    }\n\n    /**\n     * 制作打log位置的文件名与文件行号详细信息\n     */\n    private static String build(CharSequence log, StackTraceElement ste) {\n        StringBuilder buf = new StringBuilder();\n\n        buf.append(\"[\").append(Thread.currentThread().getId()).append(\"]\");\n\n        if (ste.isNativeMethod()) {\n            buf.append(\"(Native Method)\");\n        } else {\n            CharSequence fileName = ste.getFileName();\n\n            if (fileName == null) {\n                buf.append(\"(Unknown Source)\");\n            } else {\n                int lineNum = ste.getLineNumber();\n                buf.append('(');\n                buf.append(fileName);\n                if (lineNum >= 0) {\n                    buf.append(':');\n                    buf.append(lineNum);\n                }\n                buf.append(\"):\");\n            }\n        }\n        buf.append(log);\n        return buf.toString();\n    }\n\n    private static String build(String logKey, CharSequence msg, StackTraceElement ste) {\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"[\").append(logKey).append(\"]\").append(build(msg, ste));\n        return sb.toString();\n    }\n\n    private static String build(String logKey, CharSequence msg, StackTraceElement ste, Throwable e) {\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"[\").append(logKey).append(\"]\").append(ste.toString()).append(\":\").append(msg).append(\"\\r\\n\").append(\"e:\").append(e.getMessage());\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "Android/app/src/debug/java/com/didichuxing/doraemondemo/amap/mockroute/MockGPSTaskData.java",
    "content": "package com.didichuxing.doraemondemo.amap.mockroute;\n\nimport java.util.ArrayList;\n\n/**\n * changzuozhen\n * 2021年 04月 20日\n */\npublic class MockGPSTaskData {\n    private static final String TAG = \"TaskInfoData\";\n    public double start_lng;\n    public double start_lat;\n    public double end_lng;\n    public double end_lat;\n    public ArrayList<MockGPSItem> mockGPSItems;\n\n    @Override\n    public String toString() {\n        return \"任务 轨迹点=\" + mockGPSItems.size() +\n                \", start_lat,lng= \" + start_lat + \",\" + start_lng +\n                \", end_lat,lng= \" + end_lat + \",\" + end_lng;\n    }\n\n    public static class MockGPSItem {\n        public Double lng;\n        public Double lat;\n\n        public Long time;\n\n        public Float accuracy;\n        public Double speed;\n        public Long bearing;\n\n        @Override\n        public String toString() {\n            return \"TraceItem{\" +\n                    \"lng=\" + lng +\n                    \", lat=\" + lat +\n                    \", time=\" + time +\n                    \", accuracy=\" + accuracy +\n                    \", speed=\" + speed +\n                    \", bearing=\" + bearing +\n                    '}';\n        }\n\n        public MockGPSItem setLng(Double lng) {\n            this.lng = lng;\n            return this;\n        }\n\n        public MockGPSItem setLat(Double lat) {\n            this.lat = lat;\n            return this;\n        }\n\n        public MockGPSItem setTime(Long time) {\n            this.time = time;\n            return this;\n        }\n\n        public MockGPSItem setAccuracy(Float accuracy) {\n            this.accuracy = accuracy;\n            return this;\n        }\n\n        public MockGPSItem setSpeed(Double speed) {\n            this.speed = speed;\n            return this;\n        }\n\n        public MockGPSItem setBearing(Long bearing) {\n            this.bearing = bearing;\n            return this;\n        }\n    }\n\n\n    public static void modifyBearing(ArrayList<MockGPSItem> mockGPSItems) {\n        int compairStep = 5;\n        if (mockGPSItems != null && mockGPSItems.size() > compairStep) {\n//            LogUtils.d(TAG, \"⚠️modifyBearing() called with: mockGPSItems = [\" + mockGPSItems.size() + \"]\");\n            for (int i = 0; i < mockGPSItems.size() - compairStep; i++) {\n                MockGPSItem a = mockGPSItems.get(i);\n                MockGPSItem b = mockGPSItems.get(i + compairStep);\n//                LogUtils.d(TAG, \"⚠️modifyBearing() called with:\" +\n//                        \"bearing = [\" + a.bearing + \"] \" +\n//                        \"bearing = [\" + (long) BearingUtils.bearing(a.lat, a.lng, b.lat, b.lng) + \"]\");\n                a.bearing = (long) BearingUtils.bearing(a.lat, a.lng, b.lat, b.lng);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/app/src/debug/java/com/didichuxing/doraemondemo/amap/mockroute/MockGPSTaskManager.java",
    "content": "package com.didichuxing.doraemondemo.amap.mockroute;\n\nimport android.location.Location;\nimport android.location.LocationManager;\n\nimport androidx.annotation.NonNull;\n\nimport com.amap.api.maps.AMapUtils;\nimport com.amap.api.maps.model.LatLng;\nimport com.amap.api.navi.model.AMapNaviPath;\nimport com.amap.api.navi.model.NaviLatLng;\n\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.concurrent.TimeUnit;\n\nimport io.reactivex.Observable;\nimport io.reactivex.android.schedulers.AndroidSchedulers;\nimport io.reactivex.functions.Consumer;\nimport io.reactivex.functions.Function;\nimport io.reactivex.functions.Predicate;\nimport io.reactivex.schedulers.Schedulers;\n\n\n/**\n * 使用Dokit位置mock\n * changzuozhen\n * 2021年 04月 20日\n */\npublic class MockGPSTaskManager {\n    private static final String TAG = \"TaskMockManager\";\n    public static int PERIOD_LEVEL_MIN = 1;\n    public static int PERIOD_LEVEL_MAX = 20;\n    public static int PERIOD_UNIT = 50;\n    private static int mPeriodLevel = PERIOD_LEVEL_MAX;\n\n    private Long mProgressIndex = 0L;\n    private Long mProgressIndexMax = 0L;\n\n    private boolean mPause = false;\n    private boolean mSkip = false;\n\n    MockGPSTaskData taskInfoData;\n\n    public static MockGPSTaskManager sMockGPSTaskManager;\n\n    public MockGPSTaskManager(MockGPSTaskData mockGPSTaskData) {\n        this.taskInfoData = mockGPSTaskData;\n        mProgressIndexMax = Long.valueOf(mockGPSTaskData.mockGPSItems.size() - 1);\n\n    }\n\n    public Observable<Location> startGpsMockTask() {\n        return Observable.interval(0, PERIOD_UNIT, TimeUnit.MILLISECONDS, Schedulers.io())\n//                Observable.interval(0, PERIOD_UNIT * getPeriodLevel(), TimeUnit.MILLISECONDS, Schedulers.io())\n//                Observable.timer(PERIOD_UNIT * getPeriodLevel(), TimeUnit.MICROSECONDS)\n//                .subscribeOn(Schedulers.io())\n                .filter(new Predicate<Long>() {\n                    @Override\n                    public boolean test(Long aLong) throws Exception {\n                        boolean periodMatched = (aLong % getPeriodLevel()) == 0;\n                        return checkMockLocationState() && periodMatched;\n//                        return checkMockLocationState();\n                    }\n                })\n                .map(new Function<Long, Long>() {\n                    @Override\n                    public Long apply(@NonNull Long aLong) throws Exception {\n                        return mProgressIndex++;\n                    }\n                })\n                .takeWhile(new Predicate<Long>() {\n                    @Override\n                    public boolean test(Long aLong) throws Exception {\n                        return !mSkip && taskInfoData != null && aLong < taskInfoData.mockGPSItems.size();\n                    }\n                })\n                .observeOn(AndroidSchedulers.mainThread())\n                .map(new Function<Long, Location>() {\n                    private Location mLastLocation;\n                    @Override\n                    public Location apply(Long aLong) throws Exception {\n                        MockGPSTaskData.MockGPSItem mockGPSItem = taskInfoData.mockGPSItems.get(aLong.intValue());\n                        // 轨迹模拟\n                        Location location = new Location(LocationManager.GPS_PROVIDER);\n                        location.setLatitude(mockGPSItem.lat);\n                        location.setLongitude(mockGPSItem.lng);\n                        location.setAccuracy(mockGPSItem.accuracy.floatValue());\n                        location.setSpeed(mockGPSItem.speed.floatValue());\n                        location.setBearing(mockGPSItem.bearing);\n                        LogUtils.v(TAG, \"⚠️模拟定位：\" + location + \" index: \" + aLong + \" TaskInfoData: \" + taskInfoData);\n                        float calculateLineDistance = (location == null || mLastLocation == null) ? -1 : AMapUtils.calculateLineDistance(new LatLng(mLastLocation.getLatitude(), mLastLocation.getLongitude()), new LatLng(location.getLatitude(), location.getLongitude()));\n                        if (calculateLineDistance < 0) {\n                            LogUtils.v(TAG, \"⚠️模拟定位：\" + location + \" index: \" + aLong + \" TaskInfoData: \" + taskInfoData);\n                        } else if (calculateLineDistance > 100) {\n                            LogUtils.w(TAG, \"⚠️模拟定位： 跳动距离：\" + calculateLineDistance + \" \" + location + \" index: \" + aLong + \" TaskInfoData: \" + taskInfoData);\n                        } else {\n                            LogUtils.v(TAG, \"⚠️模拟定位： 跳动距离：\" + calculateLineDistance + \" \" + location + \" index: \" + aLong + \" TaskInfoData: \" + taskInfoData);\n                        }\n                        mLastLocation = location;\n\n                        //GpsMockManager.getInstance().mockLocationWithNotify(location);\n                        return location;\n                    }\n                })\n                .doOnError(new Consumer<Throwable>() {\n                    @Override\n                    public void accept(Throwable throwable) throws Exception {\n\n                        LogUtils.d(TAG, \"mock 子任务异常 \" + taskInfoData + \" \" + throwable + \"]\");\n                    }\n                });\n    }\n\n    /**\n     * 根据高德导航的轨迹数据进行轨迹模拟\n     *\n     * @param naviRouteInfo\n     * @return\n     */\n    @Nullable\n    public static Observable<Location> startGpsMockTask(@Nullable AMapNaviPath naviRouteInfo) {\n        if (naviRouteInfo != null && naviRouteInfo.getCoordList() != null) {\n            MockGPSTaskData mockGPSTaskData = new MockGPSTaskData();\n            mockGPSTaskData.mockGPSItems = new ArrayList();\n            Iterator cordListIt = naviRouteInfo.getCoordList().iterator();\n            while (cordListIt.hasNext()) {\n                NaviLatLng latLng = (NaviLatLng) cordListIt.next();\n                MockGPSTaskData.MockGPSItem mockGPSItem = new MockGPSTaskData.MockGPSItem();\n                mockGPSTaskData.mockGPSItems.add(mockGPSItem.setLat(latLng.getLatitude()).setLng(latLng.getLongitude()).setTime(System.currentTimeMillis()).setAccuracy(10.0F).setBearing(0L).setSpeed(15.0D));\n            }\n            MockGPSTaskData.modifyBearing(mockGPSTaskData.mockGPSItems);\n            MockGPSTaskManager mockGPSTaskManager = new MockGPSTaskManager(mockGPSTaskData);\n            if (sMockGPSTaskManager != null) {\n                sMockGPSTaskManager.setSkip(true);\n            }\n            sMockGPSTaskManager = mockGPSTaskManager;\n            return mockGPSTaskManager.startGpsMockTask();\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * 暂停当前任务\n     */\n    public void pause() {\n        mPause = true;\n    }\n\n    /**\n     * 恢复执行当前任务\n     */\n    public void resume() {\n        mPause = false;\n    }\n\n    /**\n     * 跳过当前任务\n     *\n     * @param skip\n     * @return\n     */\n    public MockGPSTaskManager setSkip(boolean skip) {\n        this.mSkip = skip;\n        return this;\n    }\n\n    /**\n     * 获取时间间隔等级\n     *\n     * @return\n     */\n    public static int getPeriodLevel() {\n        return mPeriodLevel;\n    }\n\n    /**\n     * 时间间隔等级\n     * 1       --- PERIOD_UNIT * 1      = 50\n     * 10      --- PERIOD_UNIT * 10     = 500\n     * 20      --- PERIOD_UNIT * 20     = 1000\n     */\n    public static void setPeriodLevel(int periodLevel) {\n        if (periodLevel < 1) {\n            mPeriodLevel = 1;\n        } else {\n            mPeriodLevel = periodLevel;\n        }\n        LogUtils.v(TAG, \"setPeriodLevel() called with: periodLevel = [\" + mPeriodLevel + \"]\");\n    }\n\n    public boolean checkMockLocationState() {\n//        return !mPause && mState > 5;\n        return !mPause;\n    }\n\n    public Long getSeekProgress() {\n        return mProgressIndex;\n    }\n\n    public MockGPSTaskManager seekProgress(Long progress) {\n        if (progress < 0) {\n            mProgressIndex = 0L;\n        } else if (progress > mProgressIndexMax) {\n            this.mProgressIndex = mProgressIndexMax;\n        } else {\n            this.mProgressIndex = progress;\n        }\n        return this;\n    }\n\n    public boolean completed() {\n        return mProgressIndex >= mProgressIndexMax;\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:dist=\"http://schemas.android.com/apk/distribution\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.didichuxing.doraemondemo\">\n\n    <dist:module dist:instant=\"true\" />\n\n    <uses-permission\n        android:name=\"android.permission.READ_FRAME_BUFFER\"\n        tools:ignore=\"ProtectedPermissions\" />\n    <uses-permission android:name=\"android.permission.INTERNET\" /> <!-- tencent Android Q新增权限，允许应用在后台发起定位，如应用target为Q，请添加此权限 -->\n    <uses-permission android:name=\"android.permission.ACCESS_BACKGROUND_LOCATION\" /> <!-- tencent 如果设置了target >= 28 如果需要启动后台定位则必须声明这个权限 -->\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" /> <!-- 用于进行网络定位 -->\n    <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" /> <!-- 用于访问GPS定位 -->\n    <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" /> <!-- 用于获取运营商信息，用于支持提供运营商信息相关的接口 -->\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" /> <!-- 用于访问wifi网络信息，wifi信息会用于进行网络定位 -->\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" /> <!-- 用于获取wifi的获取权限，wifi信息会用来进行网络定位 -->\n    <uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\" /> <!-- 用于读取手机当前的状态 -->\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" /> <!-- 用于写入缓存数据到扩展存储卡 -->\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" /> <!-- 用于申请调用A-GPS模块 -->\n    <uses-permission android:name=\"android.permission.ACCESS_LOCATION_EXTRA_COMMANDS\" />\n\n    <application\n        android:name=\".App\"\n        android:allowBackup=\"false\"\n        android:icon=\"@mipmap/dk_app_icon\"\n        android:label=\"@string/app_name\"\n        android:networkSecurityConfig=\"@xml/network_config\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\"\n        tools:ignore=\"GoogleAppIndexingWarning\">\n\n        <activity\n            android:name=\".MainDoKitActivity\"\n            android:theme=\"@style/NoTitleTheme\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\".mc.NetMainActivity\"\n            android:exported=\"false\" />\n        <activity android:name=\".WebViewSystemActivity\" />\n        <activity android:name=\".WebViewX5Activity\" />\n        <activity android:name=\".EmptyActivity\" />\n        <activity android:name=\".module.bigbitmap.BigBitmapActivity\" />\n        <activity android:name=\".module.leak.LeakActivity\" />\n        <activity android:name=\".mc.MCActivity\" />\n        <activity android:name=\"com.amap.api.navi.AmapRouteActivity\" />\n        <activity\n            android:name=\".comm.CommFragmentActivity\"\n            android:theme=\"@style/Theme.AppCompat.Light.NoActionBar\" />\n        <!-- 高德定位服务 -->\n\n\n        <service android:name=\"com.amap.api.location.APSService\" />\n\n\n        <!-- 百度地图定位服务 -->\n        <service\n            android:name=\"com.baidu.location.f\"\n            android:enabled=\"true\"\n            android:process=\":remote\" />\n\n        <!-- Dokit targetApi为 29 Android Q时截屏的前台服务 -->\n        <service\n            android:name=\".test.ScreenRecordingService\"\n            android:enabled=\"true\"\n            android:foregroundServiceType=\"mediaProjection\" />\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/AopTest.java",
    "content": "package com.didichuxing.doraemondemo;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/22-11:38\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class AopTest {\n\n\n    public void getDoKit() {\n//        OkHttpHook.addDoKitIntercept(this);\n    }\n\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/BaseStatusBarActivity.java",
    "content": "package com.didichuxing.doraemondemo;\n\nimport android.app.Activity;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.view.WindowManager;\n\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\n\n/**\n * didi Create on 2022/5/25 .\n * <p>\n * Copyright (c) 2022/5/25 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/5/25 5:55 下午\n * @Description 用一句话说明文件功能\n */\n\npublic class BaseStatusBarActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        preUpdateStatusBar(this);\n    }\n\n\n    private void preUpdateStatusBar(Activity activity) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);\n            activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);\n            activity.getWindow().setStatusBarColor(getResources().getColor(R.color.colorPrimary));\n        }\n    }\n\n    private void setLightStatusBar() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            getWindow().getDecorView().setSystemUiVisibility(\n                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);\n        }\n    }\n\n    private void setNotLightStatusBar() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            getWindow().getDecorView().setSystemUiVisibility(\n                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN );\n        }\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/DataBaseActivity.kt",
    "content": "package com.didichuxing.doraemondemo\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.appcompat.app.AppCompatActivity\nimport com.blankj.utilcode.util.ToastUtils\nimport com.didichuxing.doraemondemo.db.PersonDBHelper\nimport kotlinx.coroutines.*\n\nclass DataBaseActivity : AppCompatActivity() {\n    companion object {\n        val TAG = \"SecondActivity\"\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_second)\n\n        findViewById<View>(R.id.tv).setOnClickListener {\n            // Create Person encrypted database\n            CoroutineScope(Dispatchers.Main).launch {\n                val job = async(Dispatchers.IO) { insertPersonDB() }\n                val success = job.await()\n                ToastUtils.showShort(\"插入数据成功\")\n\n            }\n            ToastUtils.showShort(\"开始插入数据\")\n        }\n    }\n\n\n    /**\n     * 只非ui编程中执行操作\n     */\n    private fun insertPersonDB(): Boolean {\n        val personDBHelper = PersonDBHelper(applicationContext)\n        if (personDBHelper.count() == 0) {\n            for (i in 0..99) {\n                val firstName = \"${PersonDBHelper.PERSON_COLUMN_FIRST_NAME}_$i\"\n                val lastName = \"${PersonDBHelper.PERSON_COLUMN_LAST_NAME}_$i\"\n                val address = \"${PersonDBHelper.PERSON_COLUMN_ADDRESS}_$i\"\n                personDBHelper.insertPerson(firstName, lastName, address)\n            }\n        }\n\n        return true\n    }\n\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/EmptyActivity.kt",
    "content": "package com.didichuxing.doraemondemo\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.appcompat.app.AppCompatActivity\nimport com.blankj.utilcode.util.ToastUtils\nimport com.didichuxing.doraemondemo.db.PersonDBHelper\nimport kotlinx.coroutines.*\n\nclass EmptyActivity : AppCompatActivity() {\n    companion object {\n        val TAG = \"EmptyActivity\"\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_empty)\n\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/MainDoKitActivity.kt",
    "content": "package com.didichuxing.doraemondemo\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.ViewGroup\nimport androidx.core.view.children\nimport com.didichuxing.doraemondemo.mc.MCActivity\nimport com.didichuxing.doraemondemo.module.CrashTest\nimport com.didichuxing.doraemondemo.module.DoKitItemView\nimport com.didichuxing.doraemondemo.module.MethodCostTest\nimport com.didichuxing.doraemondemo.module.bigbitmap.BigBitmapActivity\nimport com.didichuxing.doraemondemo.module.db.DataBaseTest\nimport com.didichuxing.doraemondemo.module.http.FileUploadTest\nimport com.didichuxing.doraemondemo.module.http.OkHttpMock\nimport com.didichuxing.doraemondemo.module.http.RetrofitMock\nimport com.didichuxing.doraemondemo.module.http.URLConnectionMock\nimport com.didichuxing.doraemondemo.module.leak.LeakActivity\nimport com.didichuxing.doraemondemo.old.MainDebugActivityOkhttpV3\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.util.ToastUtils\n\n\n/**\n * didi Create on 2022/5/25 .\n *\n * Copyright (c) 2022/5/25 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/5/25 6:05 下午\n * @Description 用一句话说明文件功能\n */\n\nclass MainDoKitActivity : BaseStatusBarActivity() {\n\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_dokit_main)\n        val all: ViewGroup = findViewById<ViewGroup>(R.id.all)\n\n        all.children.forEach {\n            if (it is DoKitItemView) {\n                val item = it as DoKitItemView\n                item.setOnClickListener {\n                    onItemClick(item, item.itemText)\n                }\n            }\n        }\n    }\n\n\n    private fun onItemClick(itemView: DoKitItemView, text: String) {\n        Log.i(\"TEST\", \"onItemClick :$text\")\n        when (text) {\n            //工具入口\n            \"显示/隐藏快捷入口\" -> {\n                showHideDoKit();\n            }\n            \"打开工具窗口\" -> {\n                DoKit.showToolPanel()\n            }\n            //平台工具\n            \"数据Mock测试\" -> {\n                startActivity(Intent(this, EmptyActivity::class.java))\n            }\n            \"OkHttp 模拟请求\" -> {\n                OkHttpMock.test()\n            }\n            \"UrlConnection 模拟请求\" -> {\n                URLConnectionMock.get(\"https://wanandroid.com/user_article/list/0/json\")\n            }\n            \"retrofit 模拟请求\" -> {\n                RetrofitMock.test()\n            }\n            \"一机多控测试\" -> {\n                startActivity(Intent(this, MCActivity::class.java))\n            }\n            \"自动化测试\" -> {\n                startActivity(Intent(this, MCActivity::class.java))\n            }\n\n            //常用工具\n            \"日志测试\" -> {\n                startActivity(Intent(this, EmptyActivity::class.java))\n            }\n            \"跳转其他Activity\" -> {\n                startActivity(Intent(this, EmptyActivity::class.java))\n            }\n\n            \"系统:WebView\" -> {\n                startActivity(Intent(this, WebViewSystemActivity::class.java))\n            }\n            \"X5:WebView\" -> {\n                startActivity(Intent(this, WebViewX5Activity::class.java))\n            }\n\n            //LBS\n            \"位置模拟\" -> {\n                startActivity(Intent(this, EmptyActivity::class.java))\n            }\n            \"路径模拟\" -> {\n                startActivity(Intent(this, EmptyActivity::class.java))\n            }\n\n            //性能工具\n            \"模拟内存泄漏\" -> {\n                startActivity(Intent(this, LeakActivity::class.java))\n            }\n\n            \"模拟耗时函数调用\" -> {\n                MethodCostTest.test()\n            }\n\n            \"崩溃模拟\" -> {\n                CrashTest.test()\n            }\n\n            \"创建数据库\" -> {\n                DataBaseTest.test()\n            }\n            \"文件上传模拟\" -> {\n                FileUploadTest.requestByFile(getActivity(), filesDir, true)\n            }\n            \"文件下载模拟\" -> {\n                FileUploadTest.requestByFile(getActivity(), filesDir, false)\n            }\n            \"大图检测模拟\" -> {\n                startActivity(Intent(this, BigBitmapActivity::class.java))\n            }\n            //视觉工具\n            \"取色器测试\" -> {\n                startActivity(Intent(this, EmptyActivity::class.java))\n            }\n            \"标尺对齐测试\" -> {\n                startActivity(Intent(this, EmptyActivity::class.java))\n            }\n\n            //其他工具\n            \"旧版页面入口\" -> {\n                startActivity(Intent(this, MainDebugActivityOkhttpV3::class.java))\n            }\n            else -> {\n                ToastUtils.showShort(\"$text\")\n            }\n\n        }\n    }\n\n\n    private fun getActivity(): Activity {\n        return this\n    }\n\n    private fun showHideDoKit() {\n        if (DoKit.isMainIconShow) {\n            DoKit.hide()\n        } else {\n            DoKit.show()\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/WebViewSystemActivity.kt",
    "content": "package com.didichuxing.doraemondemo\n\nimport android.annotation.SuppressLint\nimport android.os.Build\nimport android.os.Bundle\nimport android.util.Log\nimport android.webkit.*\nimport androidx.annotation.RequiresApi\nimport androidx.appcompat.app.AppCompatActivity\n\n/**\n * Created by jintai on 2018/11/13.\n */\nclass WebViewSystemActivity : AppCompatActivity() {\n\n    val TAG = \"WebViewActivity\"\n    lateinit var mWebView: WebView\n    val url = \"https://xingyun.xiaojukeji.com/docs/dokit\"\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_normal_webview)\n        mWebView = findViewById<WebView>(R.id.normal_web_view)\n        initWebView(mWebView)\n\n        mWebView.loadUrl(url)\n\n    }\n\n\n    @SuppressLint(\"JavascriptInterface\")\n    private fun initWebView(webView: WebView) {\n        val webSettings: WebSettings = webView.settings\n        webSettings.pluginState = WebSettings.PluginState.ON\n        webSettings.javaScriptEnabled = true\n        webSettings.allowFileAccess = false\n        webSettings.loadsImagesAutomatically = true\n        webSettings.useWideViewPort = true\n        webSettings.builtInZoomControls = false\n        webSettings.defaultTextEncodingName = \"UTF-8\"\n        webSettings.domStorageEnabled = true\n        webSettings.cacheMode = WebSettings.LOAD_DEFAULT\n        webSettings.javaScriptCanOpenWindowsAutomatically = false\n        webSettings.allowFileAccessFromFileURLs = true\n        webSettings.allowUniversalAccessFromFileURLs = true\n\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {\n            webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH)\n        }\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n            WebView.setWebContentsDebuggingEnabled(true)\n        }\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW\n        }\n        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            webView.removeJavascriptInterface(\"searchBoxJavaBridge_\")\n            webView.removeJavascriptInterface(\"accessibilityTraversal\")\n            webView.removeJavascriptInterface(\"accessibility\")\n        }\n\n\n        webView.webViewClient = object : WebViewClient() {\n            override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {\n                view.loadUrl(url)\n                return true\n            }\n\n            @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n            override fun shouldInterceptRequest(\n                view: WebView?,\n                request: WebResourceRequest?\n            ): WebResourceResponse? {\n                return super.shouldInterceptRequest(view, request)\n            }\n\n        }\n\n        webView.webChromeClient = object : WebChromeClient() {\n            override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {\n                val message = consoleMessage!!.message()\n                val lineNumber = consoleMessage.lineNumber()\n                val sourceID = consoleMessage.sourceId()\n                val messageLevel = consoleMessage.message()\n\n                Log.i(\n                    TAG, String.format(\n                        \"[%s] sourceID: %s lineNumber: %n message: %s\",\n                        messageLevel, sourceID, lineNumber, message\n                    )\n                )\n\n                //Log.i(TAG, \"consoleMessage===>${consoleMessage?.message()}\")\n                return super.onConsoleMessage(consoleMessage)\n            }\n        }\n    }\n\n\n    override fun onBackPressed() {\n        if (mWebView.canGoBack()) {\n            mWebView.goBack()\n        } else {\n            super.onBackPressed()\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/WebViewX5Activity.kt",
    "content": "package com.didichuxing.doraemondemo\n\nimport android.annotation.SuppressLint\nimport android.os.Build\nimport android.os.Bundle\nimport androidx.annotation.RequiresApi\nimport androidx.appcompat.app.AppCompatActivity\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport com.tencent.smtt.export.external.interfaces.ConsoleMessage\nimport com.tencent.smtt.export.external.interfaces.WebResourceRequest\nimport com.tencent.smtt.export.external.interfaces.WebResourceResponse\nimport com.tencent.smtt.sdk.WebChromeClient\nimport com.tencent.smtt.sdk.WebSettings\nimport com.tencent.smtt.sdk.WebView\nimport com.tencent.smtt.sdk.WebViewClient\n\n/**\n * Created by jintai on 2018/11/13.\n */\nclass WebViewX5Activity : AppCompatActivity() {\n    val TAG = \"WebViewActivity\"\n    lateinit var mWebView: WebView\n    val url = \"https://xingyun.xiaojukeji.com/docs/dokit\"\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_x5_webview)\n        mWebView = findViewById<WebView>(R.id.x5_web_view)\n        initWebView(mWebView)\n        mWebView.loadUrl(url)\n    }\n\n\n    @SuppressLint(\"JavascriptInterface\")\n    private fun initWebView(webView: WebView) {\n        val webSettings: WebSettings = webView.settings\n        webSettings.pluginState = WebSettings.PluginState.ON\n        webSettings.javaScriptEnabled = true\n        webSettings.allowFileAccess = false\n        webSettings.loadsImagesAutomatically = true\n        webSettings.useWideViewPort = true\n        webSettings.builtInZoomControls = false\n        webSettings.defaultTextEncodingName = \"UTF-8\"\n        webSettings.domStorageEnabled = true\n        webSettings.cacheMode = WebSettings.LOAD_DEFAULT\n        webSettings.javaScriptCanOpenWindowsAutomatically = false\n        webSettings.setAllowFileAccessFromFileURLs(true)\n        webSettings.setAllowUniversalAccessFromFileURLs(true)\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {\n            webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH)\n        }\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n            WebView.setWebContentsDebuggingEnabled(true)\n        }\n\n        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            webView.removeJavascriptInterface(\"searchBoxJavaBridge_\")\n            webView.removeJavascriptInterface(\"accessibilityTraversal\")\n            webView.removeJavascriptInterface(\"accessibility\")\n        }\n\n\n        webView.webViewClient = object : WebViewClient() {\n            override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {\n                view.loadUrl(url)\n                return true\n            }\n\n            @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n            override fun shouldInterceptRequest(\n                view: WebView?,\n                request: WebResourceRequest?\n            ): WebResourceResponse? {\n                return super.shouldInterceptRequest(view, request)\n            }\n\n        }\n\n        webView.webChromeClient = object : WebChromeClient() {\n            override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {\n                LogHelper.i(TAG, \"consoleMessage===>${consoleMessage?.message()}\")\n                return super.onConsoleMessage(consoleMessage)\n            }\n        }\n    }\n\n    override fun onBackPressed() {\n        if (mWebView.canGoBack()) {\n            mWebView.goBack()\n        } else {\n            super.onBackPressed()\n        }\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/amap/AMapRouterFragment.kt",
    "content": "package com.didichuxing.doraemondemo.amap\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.TextView\nimport com.amap.api.maps.AMap\nimport com.amap.api.maps.MapView\nimport com.amap.api.maps.model.LatLng\nimport com.amap.api.maps.model.MyLocationStyle\nimport com.amap.api.maps.model.Poi\nimport com.amap.api.navi.*\nimport com.amap.api.navi.enums.PathPlanningStrategy\nimport com.amap.api.navi.model.AMapNaviLocation\nimport com.amap.api.navi.model.NaviLatLng\nimport com.didichuxing.doraemondemo.R\nimport com.didichuxing.doraemondemo.comm.CommBaseFragment\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：3/11/21-14:51\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass AMapRouterFragment : CommBaseFragment() {\n    private var mDefaultNaviListener: DefaultNaviListener? = null\n    private lateinit var mAmap: AMap\n    private lateinit var mapView: MapView\n    private lateinit var mAMapNavi: AMapNavi\n    private val mStartPoint = NaviLatLng(30.29659, 120.081127)\n    private val mEndPoint = NaviLatLng(30.296793, 121.07527)\n    override fun initActivityTitle(): String {\n        return \"高德路径规划\"\n    }\n\n    override fun getLayoutId(): Int {\n        return R.layout.fragment_amap\n    }\n\n\n    override fun initView(savedInstanceState: Bundle?) {\n        mapView = findViewById(R.id.amap_view)\n        mapView.onCreate(savedInstanceState)\n        mAmap = mapView.map\n        val startNavi = findViewById<TextView>(R.id.tv_start)\n        startNavi.setOnClickListener {\n            val params =\n                AmapNaviParams(\n                    Poi(\"西溪谷\", LatLng(mStartPoint.latitude, mStartPoint.longitude), \"\"),\n                    null,\n                    Poi(\"管理学院创新大楼\", LatLng(mEndPoint.latitude, mEndPoint.longitude), \"\"),\n                    AmapNaviType.DRIVER\n                )\n            params.setUseInnerVoice(true)\n            AmapNaviPage.getInstance().showRouteActivity(activity, params, object :\n                INaviInfoCallback {\n                override fun onInitNaviFailure() {\n                }\n\n                override fun onGetNavigationText(p0: String?) {\n                }\n\n                override fun onLocationChange(p0: AMapNaviLocation?) {\n                }\n\n                override fun onArriveDestination(p0: Boolean) {\n                }\n\n                override fun onStartNavi(p0: Int) {\n                }\n\n                override fun onCalculateRouteSuccess(p0: IntArray?) {\n                }\n\n                override fun onCalculateRouteFailure(p0: Int) {\n                }\n\n                override fun onStopSpeaking() {\n                }\n\n                override fun onReCalculateRoute(p0: Int) {\n                }\n\n                override fun onExitPage(p0: Int) {\n                }\n\n                override fun onStrategyChanged(p0: Int) {\n                }\n\n                override fun onArrivedWayPoint(p0: Int) {\n                }\n\n                override fun onMapTypeChanged(p0: Int) {\n                }\n\n                override fun onNaviDirectionChanged(p0: Int) {\n                }\n\n                override fun onDayAndNightModeChanged(p0: Int) {\n                }\n\n                override fun onBroadcastModeChanged(p0: Int) {\n                }\n\n                override fun onScaleAutoChanged(p0: Boolean) {\n                }\n\n                override fun getCustomMiddleView(): View? {\n                    return null\n                }\n\n                override fun getCustomNaviView(): View? {\n                    return null\n                }\n\n                override fun getCustomNaviBottomView(): View? {\n                    return null\n                }\n            })\n        }\n        initAMapLocation()\n    }\n\n    /**\n     * 初始化高德地图的定位\n     */\n    private fun initAMapLocation() {\n        mAmap.minZoomLevel = 10.0f\n\n        //初始化定位蓝点样式类myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE);//连续定位、且将视角移动到地图中心点，定位点依照设备方向旋转，并且会跟随设备移动。（1秒1次定位）如果不设置myLocationType，默认也会执行此种模式。\n        val myLocationStyle = MyLocationStyle()\n        //设置连续定位模式下的定位间隔，只在连续定位模式下生效，单次定位模式下不会生效。单位为毫秒\n        myLocationStyle.interval(1000L)\n//        myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE_NO_CENTER)\n        myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_FOLLOW)\n        myLocationStyle.myLocationIcon(\n            com.amap.api.maps.model.BitmapDescriptorFactory.fromResource(\n                R.mipmap.ic_navi_map_gps_locked\n            )\n        )\n        ////设置是否显示定位小蓝点，用于满足只想使用定位，不想使用定位小蓝点的场景，设置false以后图面上不再有定位蓝点的概念，但是会持续回调位置信息。\n        myLocationStyle.showMyLocation(true)\n        mAmap.myLocationStyle = myLocationStyle //设置定位蓝点的Style\n\n        //aMap.getUiSettings().setMyLocationButtonEnabled(true);设置默认定位按钮是否显示，非必需设置。\n        // 设置为true表示启动显示定位蓝点，false表示隐藏定位蓝点并不进行定位，默认是false。\n        mAmap.isMyLocationEnabled = true\n        //规划路径\n        mAMapNavi = AMapNavi.getInstance(activity?.application)\n        val startList = mutableListOf<NaviLatLng>()\n        startList.add(mStartPoint)\n        val endList = mutableListOf<NaviLatLng>()\n        endList.add(mEndPoint)\n        mAMapNavi.calculateDriveRoute(\n            startList,\n            endList,\n            null,\n            PathPlanningStrategy.DRIVING_MULTIPLE_ROUTES_DEFAULT\n        )\n        mAMapNavi.addAMapNaviListener(activity?.application?.let {\n            mDefaultNaviListener = DefaultNaviListener(mAmap, mAMapNavi, it)\n            mDefaultNaviListener\n        })\n    }\n\n    override fun onResume() {\n        super.onResume()\n        mapView.onResume()\n    }\n\n    override fun onPause() {\n        super.onPause()\n        mapView.onPause()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        mDefaultNaviListener?.onDestroy()\n        mapView.onDestroy()\n    }\n\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        super.onSaveInstanceState(outState)\n        mapView.onSaveInstanceState(outState)\n    }\n}"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/amap/DefaultNaviListener.kt",
    "content": "package com.didichuxing.doraemondemo.amap\n\nimport android.content.Context\nimport android.util.Log\nimport com.amap.api.maps.AMap\nimport com.amap.api.maps.AMapUtils\nimport com.amap.api.maps.model.LatLng\nimport com.amap.api.navi.AMapNavi\nimport com.amap.api.navi.AMapNaviListener\nimport com.amap.api.navi.enums.NaviType\nimport com.amap.api.navi.model.*\nimport io.reactivex.disposables.Disposable\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2/25/21-20:00\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DefaultNaviListener(val mAMap: AMap, val mAMapNavi: AMapNavi, val context: Context) :\n    AMapNaviListener {\n//    private var mNaviRouteOverlay: NaviRouteOverlay? = null\n//\n//    init {\n//        mNaviRouteOverlay = NaviRouteOverlay(mAMap, null)\n//    }\n\n    private var disp: Disposable? = null\n    private var mNaviLocation: AMapNaviLocation? = null\n\n    companion object {\n        const val TAG = \"DefaultNaviListener\"\n    }\n\n    override fun onInitNaviFailure() {\n    }\n\n    override fun onInitNaviSuccess() {\n    }\n\n    override fun onStartNavi(p0: Int) {\n    }\n\n    override fun onTrafficStatusUpdate() {\n    }\n\n    /**\n     * 改变位置时自动回调\n     */\n    override fun onLocationChange(location: AMapNaviLocation?) {\n        val calculateLineDistance: Float = if (mNaviLocation == null || location == null) -1f else\n            AMapUtils.calculateLineDistance(\n                LatLng(mNaviLocation!!.coord.latitude, mNaviLocation!!.coord.longitude),\n                LatLng(location.coord.latitude, location.coord.longitude)\n            )\n        if (calculateLineDistance < 0) {\n            Log.v(TAG, \"⚠️高德定位：\" + location.getString())\n        } else if (calculateLineDistance > 50) {\n            Log.w(TAG, \"⚠️高德定位：跳动距离:\" + calculateLineDistance + \"  \" + location.getString())\n        } else {\n            Log.v(TAG, \"⚠️高德定位：跳动距离:\" + calculateLineDistance + \"  \" + location.getString())\n        }\n        mNaviLocation = location\n    }\n\n    private fun AMapNaviLocation?.getString(): String {\n        return if (this != null) \"lat,lng:${coord?.latitude},:${coord?.longitude}, 精度:$accuracy, 速度:$speed, 方向:$bearing, 海拔:$altitude, 时间:$time\"\n        else \"null\"\n    }\n\n    override fun onGetNavigationText(p0: Int, p1: String?) {\n    }\n\n    override fun onGetNavigationText(p0: String?) {\n    }\n\n    override fun onEndEmulatorNavi() {\n    }\n\n    override fun onArriveDestination() {\n    }\n\n    override fun onCalculateRouteFailure(p0: Int) {\n    }\n\n    override fun onCalculateRouteFailure(p0: AMapCalcRouteResult?) {\n    }\n\n    override fun onReCalculateRouteForYaw() {\n    }\n\n    override fun onReCalculateRouteForTrafficJam() {\n    }\n\n    override fun onArrivedWayPoint(p0: Int) {\n    }\n\n    override fun onGpsOpenStatus(p0: Boolean) {\n    }\n\n    override fun onNaviInfoUpdate(p0: NaviInfo?) {\n    }\n\n    override fun updateCameraInfo(p0: Array<out AMapNaviCameraInfo>?) {\n    }\n\n    override fun updateIntervalCameraInfo(\n        p0: AMapNaviCameraInfo?,\n        p1: AMapNaviCameraInfo?,\n        p2: Int\n    ) {\n    }\n\n    override fun onServiceAreaUpdate(p0: Array<out AMapServiceAreaInfo>?) {\n    }\n\n    override fun showCross(p0: AMapNaviCross?) {\n    }\n\n    override fun hideCross() {\n    }\n\n    override fun showModeCross(p0: AMapModelCross?) {\n    }\n\n    override fun hideModeCross() {\n    }\n\n    override fun showLaneInfo(p0: Array<out AMapLaneInfo>?, p1: ByteArray?, p2: ByteArray?) {\n    }\n\n    override fun showLaneInfo(p0: AMapLaneInfo?) {\n    }\n\n    override fun hideLaneInfo() {\n    }\n\n    override fun onCalculateRouteSuccess(p0: IntArray?) {\n\n    }\n\n    /**\n     * 线路规划成功\n     */\n    override fun onCalculateRouteSuccess(result: AMapCalcRouteResult?) {\n//        LogHelper.i(TAG, \"mAMapNavi.naviPath.coordList===>${mAMapNavi.naviPath.coordList.size}\")\n//        RouterManager.mCoordList = mAMapNavi.naviPath.coordList\n        val naviRouteOverlay = NaviRouteOverlay(mAMap, mAMapNavi.naviPath, context)\n        naviRouteOverlay.setShowDefaultLineArrow(true)\n        naviRouteOverlay.addToMap()\n//        naviRouteOverlay.removeFromMap()\n        mAMapNavi.setEmulatorNaviSpeed(10)\n        /**\n         * \tCRUISE\n        巡航模式（数值：3）\n        EMULATOR\n        模拟导航（数值：2）\n        GPS\n        实时导航（数值：1）\n        NONE\n        未开始导航（数值：-1）\n         */\n        mAMapNavi.startNavi(NaviType.GPS)\n\n//        disp = MockGPSTaskManager.startGpsMockTask(mAMapNavi.naviPath)?.subscribe()\n    }\n\n    override fun notifyParallelRoad(p0: Int) {\n    }\n\n    override fun OnUpdateTrafficFacility(p0: Array<out AMapNaviTrafficFacilityInfo>?) {\n    }\n\n    override fun OnUpdateTrafficFacility(p0: AMapNaviTrafficFacilityInfo?) {\n    }\n\n    override fun updateAimlessModeStatistics(p0: AimLessModeStat?) {\n    }\n\n    override fun updateAimlessModeCongestionInfo(p0: AimLessModeCongestionInfo?) {\n    }\n\n    override fun onPlayRing(p0: Int) {\n    }\n\n    override fun onNaviRouteNotify(p0: AMapNaviRouteNotifyData?) {\n    }\n\n    override fun onGpsSignalWeak(p0: Boolean) {\n    }\n\n    fun onDestroy() {\n        if (disp != null && !disp!!.isDisposed()) {\n            disp!!.dispose()\n        }\n    }\n}"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/amap/NaviRouteOverlay.kt",
    "content": "package com.didichuxing.doraemondemo.amap\n\nimport android.content.Context\nimport android.graphics.BitmapFactory\nimport com.amap.api.maps.AMap\nimport com.amap.api.maps.model.BitmapDescriptorFactory\nimport com.amap.api.maps.model.LatLng\nimport com.amap.api.maps.model.Polyline\nimport com.amap.api.maps.model.PolylineOptions\nimport com.amap.api.navi.model.AMapNaviPath\nimport com.amap.api.navi.model.RouteOverlayOptions\nimport com.amap.api.navi.view.RouteOverLay\nimport com.blankj.utilcode.util.ConvertUtils\nimport com.blankj.utilcode.util.ReflectUtils\n\n/**\n * 地图上的导航路径\n *\n * @author vinda\n * @since 2017/12/27\n */\nclass NaviRouteOverlay(\n    private val aMap: AMap,\n    aMapNaviPath: AMapNaviPath?,\n    val mContext: Context\n) : RouteOverLay(\n    aMap, aMapNaviPath, mContext\n) {\n    var routeId = 0\n\n    //显示默认路线箭头\n    private var showDefaultLineArrow = false\n\n    /**\n     * 非选中状态，使用比较浅的颜色，需要重新调用addToMap生效\n     *\n     * @param shadow\n     */\n    private fun setupOptions(shadow: Boolean) {\n        val options: RouteOverlayOptions\n        if (shadow) {\n            options = customShadowRouteTexture()\n            //setZindex(ROUTE_SHADOW_Z_INDEX)\n        } else {\n            options = customRouteTexture()\n            //setZindex(ROUTE_NORMAL_Z_INDEX)\n        }\n        options.setOnRouteCameShow(false)\n        routeOverlayOptions = options\n    }\n\n    /**\n     * 设置是否显示默认路线（非路况）上的箭头,在调用addToMap之前调用\n     */\n    fun setShowDefaultLineArrow(visible: Boolean) {\n        showDefaultLineArrow = visible\n    }\n\n    /**\n     * 复写父类方法\n     */\n    override fun addToMap() {\n        super.addToMap()\n        //支持默认路线时显示箭头\n        if (showDefaultLineArrow) {\n            //显示箭头\n            addDefaultArrowLine()\n        }\n    }\n\n    /**\n     * 默认路线加入箭头\n     */\n    private fun addDefaultArrowLine() {\n        //通过反射的方式向父类加入箭头路线\n        val path = ReflectUtils.reflect(this).field(\"mPathPoints\").get<List<Any>>()\n        val lines = ReflectUtils.reflect(this).field(\"mCustomPolyLines\").get<List<Any>>()\n        if (path != null && path is List<*> && lines != null && lines is List<*>) {\n            val mLatLngsOfPath = path as List<LatLng>\n            val mCustomPolylines = lines as MutableList<Polyline>\n            if (mLatLngsOfPath.isNotEmpty()) {\n                val arrowOnRoute = BitmapDescriptorFactory.fromBitmap(\n                    routeOverlayOptions.arrowOnTrafficRoute\n                )\n                val mDefaultArrowline = this.aMap.addPolyline(\n                    PolylineOptions().addAll(mLatLngsOfPath).setCustomTexture(arrowOnRoute).width(\n                        width / 1.5f\n                    )\n                )\n                mCustomPolylines.add(mDefaultArrowline)\n            }\n        }\n    }\n\n    private fun customShadowRouteTexture(): RouteOverlayOptions {\n        val routeOverlayOptions = RouteOverlayOptions()\n        var fis =\n            BitmapDescriptorFactory::class.java.getResourceAsStream(\"/assets/img/dk_custtexture_aolr.png\")\n        val custtexture_aolr = BitmapFactory.decodeStream(fis)\n        fis =\n            BitmapDescriptorFactory::class.java.getResourceAsStream(\"/assets/img/dk_custtexture_b.png\")\n        val custtexture = BitmapFactory.decodeStream(fis)\n        fis =\n            BitmapDescriptorFactory::class.java.getResourceAsStream(\"/assets/img/dk_custtexture_no_b.png\")\n        val custtexture_no = BitmapFactory.decodeStream(fis)\n        fis =\n            BitmapDescriptorFactory::class.java.getResourceAsStream(\"/assets/img/dk_custtexture_green_b.png\")\n        val custtexture_green = BitmapFactory.decodeStream(fis)\n        fis =\n            BitmapDescriptorFactory::class.java.getResourceAsStream(\"/assets/img/dk_custtexture_slow_b.png\")\n        val custtexture_slow = BitmapFactory.decodeStream(fis)\n        fis =\n            BitmapDescriptorFactory::class.java.getResourceAsStream(\"/assets/img/dk_custtexture_bad_b.png\")\n        val custtexture_bad = BitmapFactory.decodeStream(fis)\n        fis =\n            BitmapDescriptorFactory::class.java.getResourceAsStream(\"/assets/img/dk_custtexture_grayred_b.png\")\n        val custtexture_grayred = BitmapFactory.decodeStream(fis)\n        routeOverlayOptions.arrowOnTrafficRoute = custtexture_aolr\n        routeOverlayOptions.normalRoute = custtexture\n        routeOverlayOptions.unknownTraffic = custtexture_no\n        routeOverlayOptions.smoothTraffic = custtexture_green\n        routeOverlayOptions.slowTraffic = custtexture_slow\n        routeOverlayOptions.jamTraffic = custtexture_bad\n        routeOverlayOptions.veryJamTraffic = custtexture_grayred\n        //设置导航线路的宽度, 单位：像素\n        routeOverlayOptions.lineWidth =\n            ConvertUtils.dp2px(LINE_WIDTH_DP.toFloat()).toFloat()\n        return routeOverlayOptions\n    }\n\n    companion object {\n        private const val LINE_WIDTH_DP = 14\n        fun customRouteTexture(): RouteOverlayOptions {\n            val routeOverlayOptions = RouteOverlayOptions()\n            var fis =\n                BitmapDescriptorFactory::class.java.getResourceAsStream(\"/assets/img/dk_custtexture_aolr.png\")\n            val custtexture_aolr = BitmapFactory.decodeStream(fis)\n            fis =\n                BitmapDescriptorFactory::class.java.getResourceAsStream(\"/assets/img/dk_custtexture.png\")\n            val custtexture = BitmapFactory.decodeStream(fis)\n            fis =\n                BitmapDescriptorFactory::class.java.getResourceAsStream(\"/assets/img/dk_custtexture_no.png\")\n            val custtexture_no = BitmapFactory.decodeStream(fis)\n            fis =\n                BitmapDescriptorFactory::class.java.getResourceAsStream(\"/assets/img/dk_custtexture_green.png\")\n            val custtexture_green = BitmapFactory.decodeStream(fis)\n            fis =\n                BitmapDescriptorFactory::class.java.getResourceAsStream(\"/assets/img/dk_custtexture_slow.png\")\n            val custtexture_slow = BitmapFactory.decodeStream(fis)\n            fis =\n                BitmapDescriptorFactory::class.java.getResourceAsStream(\"/assets/img/dk_custtexture_bad.png\")\n            val custtexture_bad = BitmapFactory.decodeStream(fis)\n            fis =\n                BitmapDescriptorFactory::class.java.getResourceAsStream(\"/assets/img/dk_custtexture_grayred.png\")\n            val custtexture_grayred = BitmapFactory.decodeStream(fis)\n            routeOverlayOptions.arrowOnTrafficRoute = custtexture_aolr\n            routeOverlayOptions.normalRoute = custtexture\n            routeOverlayOptions.unknownTraffic = custtexture_no\n            routeOverlayOptions.smoothTraffic = custtexture_green\n            routeOverlayOptions.slowTraffic = custtexture_slow\n            routeOverlayOptions.jamTraffic = custtexture_bad\n            routeOverlayOptions.veryJamTraffic = custtexture_grayred\n            //设置导航线路的宽度, 单位：像素\n            routeOverlayOptions.lineWidth = ConvertUtils.dp2px(LINE_WIDTH_DP.toFloat()).toFloat()\n            return routeOverlayOptions\n        }\n    }\n\n    init {\n        setupOptions(false)\n        //不显示起点、终点\n//        setStartPointBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8))\n//        setEndPointBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8))\n    }\n}"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/comm/CommBaseFragment.kt",
    "content": "package com.didichuxing.doraemondemo.comm\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.IdRes\nimport androidx.annotation.LayoutRes\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.activityViewModels\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.ViewModelProvider\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：3/11/21-16:14\n * 描    述：\n * 修订历史：\n * ================================================\n */\nabstract class CommBaseFragment : Fragment() {\n    private val viewModel: CommViewModel by activityViewModels()\n\n    private lateinit var mRootView: ViewGroup\n    abstract fun initActivityTitle(): String\n\n    @LayoutRes\n    abstract fun getLayoutId(): Int\n\n\n    private fun bindViewModel() {\n        viewModel.title = initActivityTitle()\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        bindViewModel()\n    }\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        mRootView = inflater.inflate(getLayoutId(), container, false) as ViewGroup\n        return mRootView\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        initView(savedInstanceState)\n    }\n\n    abstract fun initView(savedInstanceState: Bundle?)\n\n    fun <T : View> findViewById(@IdRes id: Int): T {\n\n        return mRootView.findViewById(id)\n    }\n\n}"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/comm/CommFragmentActivity.kt",
    "content": "package com.didichuxing.doraemondemo.comm\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.Window\nimport android.widget.TextView\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Observer\nimport androidx.lifecycle.SavedStateViewModelFactory\nimport com.didichuxing.doraemondemo.R\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：3/11/21-15:14\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass CommFragmentActivity : AppCompatActivity() {\n    var fragmentClass: Class<out CommBaseFragment>? = null\n\n    private val viewModel: CommViewModel by viewModels()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_comm)\n        initTitleBar()\n        initFragment()\n    }\n\n    private fun initFragment() {\n        val bundle = intent.extras\n        bundle?.let {\n            if (bundle.get(CommLauncher.FRAGMENT_CLASS) != null) {\n                fragmentClass = bundle[CommLauncher.FRAGMENT_CLASS] as Class<out CommBaseFragment>?\n                fragmentClass?.let {\n                    supportFragmentManager\n                        .beginTransaction()\n                        .add(R.id.container_view, it.newInstance())\n                        .commit()\n                }\n            }\n        }\n\n\n    }\n\n    private fun initTitleBar() {\n        findViewById<View>(R.id.iv_back).setOnClickListener {\n            finish()\n        }\n\n        viewModel.getTitle().observe(this,\n            Observer<String> {\n                findViewById<TextView>(R.id.tv_title).text = it\n            })\n    }\n\n\n}"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/comm/CommLauncher.kt",
    "content": "package com.didichuxing.doraemondemo.comm\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Bundle\nimport com.didichuxing.doraemonkit.DoKit\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：3/11/21-17:40\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject CommLauncher {\n    const val FRAGMENT_CLASS: String = \"FRAGMENT_CLASS\"\n\n    fun startActivity(\n        targetClass: Class<out CommBaseFragment?>,\n        context: Context,\n        bundle: Bundle? = null\n    ) {\n        context?.startActivity(Intent(context, CommFragmentActivity::class.java).apply {\n            flags = Intent.FLAG_ACTIVITY_NEW_TASK\n            putExtra(FRAGMENT_CLASS, targetClass)\n            if (bundle != null) {\n                putExtras(bundle)\n            }\n        })\n    }\n}"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/comm/CommViewModel.kt",
    "content": "package com.didichuxing.doraemondemo.comm\n\nimport androidx.lifecycle.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：3/11/21-16:12\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass CommViewModel(private val state: SavedStateHandle) : ViewModel() {\n    companion object {\n        const val KEY_TITLE = \"title\"\n    }\n\n    var title: String?\n        get() {\n            return state.get(KEY_TITLE)\n        }\n        set(value) {\n            state.set(KEY_TITLE, value)\n        }\n\n    fun getTitle(): LiveData<String> {\n        return state.getLiveData<String>(KEY_TITLE, \"DoKit\")\n    }\n\n    fun saveTitle(title: String) {\n\n        state.set(KEY_TITLE, title)\n    }\n\n}"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/db/DatabaseHelper.kt",
    "content": "package com.didichuxing.doraemondemo.db\n\nimport android.content.Context\nimport android.database.sqlite.SQLiteDatabase\nimport android.database.sqlite.SQLiteDatabase.CursorFactory\nimport android.database.sqlite.SQLiteOpenHelper\nimport android.widget.Toast\n\n/**\n * Created by wanglikun on 2019/5/4\n */\nclass DatabaseHelper(private val mContext: Context, name: String?, factory: CursorFactory?, version: Int) : SQLiteOpenHelper(mContext, name, factory, version) {\n    override fun onCreate(db: SQLiteDatabase) {\n        db.execSQL(CREATE_BOOK)\n        db.execSQL(INSERT_BOOK)\n        Toast.makeText(mContext, \"创建成功\", Toast.LENGTH_SHORT).show()\n    }\n\n    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {}\n\n    companion object {\n        const val CREATE_BOOK = \"create table Book (\" +\n                \"id integer primary key autoincrement, \" +\n                \"author text, \" +\n                \"price real, \" +\n                \"page integer, \" +\n                \"name text)\"\n        const val INSERT_BOOK = \"insert into Book (\" +\n                \"author,\" +\n                \"price,\" +\n                \"page,\" +\n                \"name\" + \")\" +\n                \"values (\" +\n                \"'jint',\" +\n                \"100,\" +\n                \"1000,\" +\n                \"'从入门到放弃')\"\n    }\n\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/db/PersonDBHelper.java",
    "content": "/*\n *\n *  *    Copyright (C) 2019 Amit Shekhar\n *  *    Copyright (C) 2011 Android Open Source Project\n *  *\n *  *    Licensed under the Apache License, Version 2.0 (the \"License\");\n *  *    you may not use this file except in compliance with the License.\n *  *    You may obtain a copy of the License at\n *  *\n *  *        http://www.apache.org/licenses/LICENSE-2.0\n *  *\n *  *    Unless required by applicable law or agreed to in writing, software\n *  *    distributed under the License is distributed on an \"AS IS\" BASIS,\n *  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  *    See the License for the specific language governing permissions and\n *  *    limitations under the License.\n *\n */\n\npackage com.didichuxing.doraemondemo.db;\n\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.database.Cursor;\n\n\nimport com.tencent.wcdb.DatabaseUtils;\nimport com.tencent.wcdb.database.SQLiteDatabase;\nimport com.tencent.wcdb.database.SQLiteOpenHelper;\n\nimport java.util.ArrayList;\n\npublic class PersonDBHelper extends SQLiteOpenHelper {\n\n    public static final String DATABASE_NAME = \"Person.db\";\n    public static final String PERSON_TABLE_NAME = \"person\";\n    public static final String PERSON_COLUMN_ID = \"id\";\n    public static final String PERSON_COLUMN_FIRST_NAME = \"first_name\";\n    public static final String PERSON_COLUMN_LAST_NAME = \"last_name\";\n    public static final String PERSON_COLUMN_ADDRESS = \"address\";\n    private static final String DB_PASSWORD = \"a_password\";\n\n    public PersonDBHelper(Context context) {\n        super(context, DATABASE_NAME, DB_PASSWORD.getBytes(), null, 3, null);\n    }\n\n    @Override\n    public void onCreate(SQLiteDatabase db) {\n        db.execSQL(\n                \"create table person \" +\n                        \"(id integer primary key, first_name text, last_name text, address text)\"\n        );\n    }\n\n    @Override\n    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n        db.execSQL(\"DROP TABLE IF EXISTS person\");\n        onCreate(db);\n    }\n\n    public boolean insertPerson(String firstName, String lastName, String address) {\n        SQLiteDatabase db = this.getWritableDatabase();\n        ContentValues contentValues = new ContentValues();\n        contentValues.put(\"first_name\", firstName);\n        contentValues.put(\"last_name\", lastName);\n        contentValues.put(\"address\", address);\n        db.insert(\"person\", null, contentValues);\n        db.close();\n        return true;\n    }\n\n    public Cursor getData(int id) {\n        SQLiteDatabase db = this.getReadableDatabase();\n        Cursor res = db.rawQuery(\"select * from person where id=\" + id + \"\", null);\n        return res;\n    }\n\n    public int numberOfRows() {\n        SQLiteDatabase db = this.getReadableDatabase();\n        int numRows = (int) DatabaseUtils.queryNumEntries(db, PERSON_TABLE_NAME);\n        return numRows;\n    }\n\n    public boolean updatePerson(Integer id, String firstName, String lastName, String address, float mileage) {\n        SQLiteDatabase db = this.getWritableDatabase();\n        ContentValues contentValues = new ContentValues();\n        contentValues.put(\"first_name\", firstName);\n        contentValues.put(\"last_name\", lastName);\n        contentValues.put(\"address\", address);\n        db.update(\"person\", contentValues, \"id = ? \", new String[]{Integer.toString(id)});\n        db.close();\n        return true;\n    }\n\n    public Integer deletePerson(Integer id) {\n        SQLiteDatabase db = this.getWritableDatabase();\n        return db.delete(\"person\",\n                \"id = ? \",\n                new String[]{Integer.toString(id)});\n    }\n\n    public ArrayList<String> getAllPerson() {\n        ArrayList<String> arrayList = new ArrayList<>();\n\n        SQLiteDatabase db = this.getReadableDatabase();\n        Cursor res = db.rawQuery(\"select * from person\", null);\n        res.moveToFirst();\n\n        while (!res.isAfterLast()) {\n            arrayList.add(\n                    res.getString(res.getColumnIndex(PERSON_COLUMN_FIRST_NAME)) + \" \" +\n                            res.getString(res.getColumnIndex(PERSON_COLUMN_LAST_NAME)));\n            res.moveToNext();\n        }\n        res.close();\n        db.close();\n        return arrayList;\n    }\n\n    public int count() {\n        SQLiteDatabase db = getReadableDatabase();\n        Cursor cursor = db.rawQuery(\"select * from person\", null);\n        try {\n            if (cursor != null && cursor.getCount() > 0) {\n                cursor.moveToFirst();\n                return cursor.getInt(0);\n            } else {\n                return 0;\n            }\n        } finally {\n            cursor.close();\n            db.close();\n        }\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/dokit/BorderDoKitView.java",
    "content": "//package com.didichuxing.doraemondemo.dokit;\n//\n//import android.content.Context;\n//import android.view.LayoutInflater;\n//import android.view.View;\n//import android.widget.FrameLayout;\n//\n//import com.didichuxing.doraemonkit.R;\n//import com.didichuxing.doraemonkit.kit.core.AbsDokitView;\n//import com.didichuxing.doraemonkit.kit.core.DokitViewLayoutParams;\n//import com.didichuxing.doraemonkit.kit.viewcheck.LayoutBorderView;\n//import com.didichuxing.doraemonkit.model.ViewInfo;\n//\n///**\n// * Created by jintai on 2019/09/26.\n// * 在改布局上绘制相应的View\n// */\n//public class BorderDoKitView extends AbsDokitView {\n//    private LayoutBorderView mLayoutBorderView = null;\n//\n//    @Override\n//    public void onCreate(Context context) {\n//\n//    }\n//\n//    @Override\n//    public void onDestroy() {\n//        super.onDestroy();\n//\n//    }\n//\n//    @Override\n//    public View onCreateView(Context context, FrameLayout view) {\n//        return LayoutInflater.from(context).inflate(R.layout.dk_float_view_check_draw, null);\n//    }\n//\n//\n//    @Override\n//    public void initDokitViewLayoutParams(DokitViewLayoutParams params) {\n//        params.flags = DokitViewLayoutParams.FLAG_NOT_FOCUSABLE_AND_NOT_TOUCHABLE;\n//        params.width = DokitViewLayoutParams.MATCH_PARENT;\n//        params.height = DokitViewLayoutParams.MATCH_PARENT;\n//    }\n//\n//\n//    @Override\n//    public void onViewCreated(FrameLayout view) {\n//        mLayoutBorderView = findViewById(R.id.rect_view);\n//        setDoKitViewNotResponseTouchEvent(getDoKitView());\n//    }\n//\n//\n//    /**\n//     * 解决ViewCheckDrawDokitView的margin被改变的bug\n//     */\n//    @Override\n//    public void onResume() {\n//        super.onResume();\n//        if (getNormalLayoutParams() != null) {\n//            FrameLayout.LayoutParams params = getNormalLayoutParams();\n//            params.setMargins(0, 0, 0, 0);\n//            params.width = FrameLayout.LayoutParams.MATCH_PARENT;\n//            params.height = FrameLayout.LayoutParams.MATCH_PARENT;\n//            immInvalidate();\n//        }\n//    }\n//\n//    @Override\n//    public boolean canDrag() {\n//        return false;\n//    }\n//\n//    public void showBorder(View target) {\n//        if (target == null) {\n//            mLayoutBorderView.showViewLayoutBorder((ViewInfo) null);\n//        } else {\n//            mLayoutBorderView.showViewLayoutBorder(new ViewInfo(target));\n//        }\n//    }\n//\n//}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/dokit/CustomDokitFragment.kt",
    "content": "package com.didichuxing.doraemondemo.dokit\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.CompoundButton\nimport com.didichuxing.doraemondemo.R\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitFragment\n\n/**\n * @Author: changzuozhen\n * @Date: 2020-12-22\n * 切换全屏与否只需要调整继承关系即可\n * @see CustomDokitFragment\n *\n * @see TestSimpleDoKitFloatView\n *\n *\n * 悬浮窗，支持折叠\n *\n * 启动工具函数\n *\n *\n *\n * 全屏页面\n *\n * @see com.didichuxing.doraemonkit.kit.core.AbsDoKitFragment\n * 启动工具函数\n *\n */\nclass CustomDokitFragment : AbsDoKitFragment() {\n    override fun onViewCreated(rootView: View?) {\n        super.onViewCreated(view)\n        ViewSetupHelper.setupButton(rootView, R.id.test1, \"TestSimpleDokitFragment\") { v: View? ->\n            val bundle = Bundle()\n            bundle.putString(\"test\", \"test\")\n            DoKit.launchFullScreen(CustomDokitFragment::class.java, context, isSystemFragment = false)\n        }\n\n        // 隐藏\n        ViewSetupHelper.setupButton(rootView, R.id.test2, \"\") { v: View? -> }\n        ViewSetupHelper.setupToggleButton(\n            rootView,\n            R.id.tb_test1,\n            \"TB\",\n            false\n        ) { buttonView: CompoundButton?, isChecked: Boolean -> }\n\n        // 隐藏\n        ViewSetupHelper.setupToggleButton(\n            rootView,\n            R.id.tb_test2,\n            \"\",\n            false\n        ) { buttonView: CompoundButton?, isChecked: Boolean -> }\n    }\n\n\n    override fun layoutId(): Int {\n        return R.layout.layout_demo_custom\n    }\n\n    override fun initTitle(): String {\n        return \"我是自定义页面\"\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/dokit/DemoDoKitView.kt",
    "content": "package com.didichuxing.doraemondemo.dokit\n\nimport android.content.Context\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport android.widget.TextView\nimport com.didichuxing.doraemondemo.R\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-23-21:02\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DemoDoKitView : AbsDoKitView() {\n    override fun onCreate(context: Context) {}\n    override fun onCreateView(context: Context, rootView: FrameLayout): View {\n        return LayoutInflater.from(context).inflate(R.layout.dk_demo, rootView, false)\n    }\n\n    override fun onViewCreated(rootView: FrameLayout) {\n\n        val tvClose = findViewById<TextView>(R.id.tv_close)\n        tvClose?.setOnClickListener {\n            DoKit.removeFloating(DemoDoKitView::class)\n        }\n    }\n\n\n    override fun initDokitViewLayoutParams(params: DoKitViewLayoutParams) {\n        params.width = DoKitViewLayoutParams.WRAP_CONTENT\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT\n        params.gravity = Gravity.TOP or Gravity.LEFT\n        params.x = 200\n        params.y = 200\n    }\n\n\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/dokit/DemoKit.kt",
    "content": "package com.didichuxing.doraemondemo.dokit\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemondemo.R\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.Category\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-24-15:48\n * 描    述：kit demo\n * 修订历史：\n * ================================================\n */\nclass DemoKit : AbstractKit() {\n    override val category: Int\n        get() = Category.BIZ\n    override val name: Int\n        get() = R.string.dk_kit_demo\n    override val icon: Int\n        get() = R.mipmap.dk_sys_info\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        DoKit.launchFloating(DemoDoKitView::class.java)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n    }\n\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/dokit/SimpleDoKitView.java",
    "content": "package com.didichuxing.doraemondemo.dokit;\n\nimport android.content.Context;\nimport android.util.DisplayMetrics;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.widget.CompoundButton;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.Switch;\nimport android.widget.TextView;\n\nimport com.blankj.utilcode.util.ConvertUtils;\nimport com.didichuxing.doraemondemo.R;\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\n\n/**\n * @Author: changzuozhen\n * @Date: 2020-12-22\n * <p>\n * 悬浮窗，支持折叠\n * @see SimpleDoKitView\n * 启动工具函数\n */\npublic abstract class SimpleDoKitView extends AbsDoKitView {\n    private static final String TAG = \"SimpleBaseFloatPage\";\n    int mWidth;\n    int mHeight;\n    int mDp50InPx;\n    private WindowManager mWindowManager;\n    private FrameLayout mFloatContainer;\n    private Switch mShowSwitch;\n    private Context mContext;\n\n    @Override\n    public void onEnterForeground() {\n        super.onEnterForeground();\n        getParentView().setVisibility(View.VISIBLE);\n    }\n\n    @Override\n    public void onEnterBackground() {\n        super.onEnterBackground();\n        getParentView().setVisibility(View.GONE);\n    }\n\n    public void showContainer(boolean isChecked) {\n        mFloatContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE);\n        immInvalidate();\n    }\n\n    @Override\n    public void onCreate(Context context) {\n        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);\n        DisplayMetrics outMetrics = new DisplayMetrics();\n        mWindowManager.getDefaultDisplay().getMetrics(outMetrics);\n        mDp50InPx = ConvertUtils.dp2px(50);\n        mWidth = outMetrics.widthPixels - mDp50InPx;\n        mHeight = outMetrics.heightPixels - mDp50InPx;\n    }\n\n\n    @Override\n    public View onCreateView(Context context, FrameLayout rootView) {\n        mContext = context;\n        return LayoutInflater.from(context).inflate(R.layout.dk_layout_simple_dokit_float_view, rootView, false);\n    }\n\n    @Override\n    public void onViewCreated(FrameLayout rootView) {\n        mFloatContainer = findViewById(R.id.floatContainer);\n        LayoutInflater.from(mContext).inflate(getLayoutId(), mFloatContainer);\n        mShowSwitch = findViewById(R.id.showHideSwitch);\n        TextView title = findViewById(R.id.floatPageTitle);\n        ImageView close = findViewById(R.id.floatClose);\n        close.setOnClickListener(v -> DoKit.removeFloating(this));\n        title.setText(getTag());\n        mShowSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {\n            @Override\n            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {\n                showContainer(isChecked);\n            }\n        });\n        initView();\n    }\n\n    protected abstract int getLayoutId();\n\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.width = DoKitViewLayoutParams.WRAP_CONTENT;\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT;\n        params.gravity = Gravity.TOP | Gravity.LEFT;\n        params.x = 200;\n        params.y = 200;\n    }\n\n    @Override\n    public boolean onBackPressed() {\n        mShowSwitch.setChecked(false);\n        return false;\n    }\n\n    @Override\n    public boolean shouldDealBackKey() {\n        return true;\n    }\n\n    protected void initView() {\n    }\n\n\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/dokit/TestSimpleDoKitFloatView.kt",
    "content": "package com.didichuxing.doraemondemo.dokit\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.View\nimport android.widget.CompoundButton\nimport android.widget.FrameLayout\nimport com.didichuxing.doraemondemo.R\nimport com.didichuxing.doraemonkit.DoKit\n\n/**\n * @Author: changzuozhen\n * @Date: 2020-12-22\n * 切换全屏与否只需要调整继承关系即可\n * @see CustomDokitFragment\n * @see TestSimpleDoKitFloatView\n *\n * 悬浮窗，支持折叠\n * @see com.didichuxing.doraemonkit.kit.core.SimpleDokitView\n * 启动工具函数\n *\n * 全屏页面\n * @see com.didichuxing.doraemonkit.kit.core.AbsDoKitFragment\n * 启动工具函数\n *\n */\nclass TestSimpleDoKitFloatView : SimpleDoKitView() {\n    override fun getLayoutId(): Int {\n        return R.layout.layout_demo_custom\n    }\n\n    override fun onViewCreated(rootView: FrameLayout?) {\n        super.onViewCreated(rootView)\n\n        ViewSetupHelper.setupButton(rootView, R.id.test1, \"TestSimpleDokitFragment\", View.OnClickListener {\n            val bundle = Bundle()\n            bundle.putString(\"test\", \"test\")\n            DoKit.launchFullScreen(CustomDokitFragment::class.java, context)\n        })\n\n        // 隐藏\n        ViewSetupHelper.setupButton(rootView, R.id.test2, \"\", null)\n\n        ViewSetupHelper.setupToggleButton(rootView, R.id.tb_test1, \"TB\", false, CompoundButton.OnCheckedChangeListener { buttonView, isChecked -> Log.d(\"TEST\", \"TB $isChecked\") })\n        // 隐藏\n        ViewSetupHelper.setupToggleButton(rootView, R.id.tb_test2, \"\", false, null)\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/dokit/TestSimpleDokitFloatViewKit.kt",
    "content": "package com.didichuxing.doraemondemo.dokit\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemondemo.R\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.Category\n\n/**\n * @Author: changzuozhen\n * @Date: 2020-12-22\n */\nclass TestSimpleDokitFloatViewKit : AbstractKit() {\n    override val category: Int\n        get() = Category.BIZ\n    override val name: Int\n        get() = R.string.dk_kit_simple_float\n    override val icon: Int\n        get() = R.mipmap.dk_sys_info\n\n    override fun onClick(context: Context?) {\n    }\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        DoKit.launchFloating(TestSimpleDoKitFloatView::class.java)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/dokit/TestSimpleDokitFragmentKit.kt",
    "content": "package com.didichuxing.doraemondemo.dokit\n\nimport android.app.Activity\nimport android.content.Context\nimport android.os.Bundle\nimport com.didichuxing.doraemondemo.R\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.Category\n\n/**\n * @Author: changzuozhen\n * @Date: 2020-12-22\n */\nclass TestSimpleDokitFragmentKit : AbstractKit() {\n    override val category: Int\n        get() = Category.BIZ\n    override val name: Int\n        get() = R.string.dk_kit_fullscreen\n    override val icon: Int\n        get() = R.mipmap.dk_sys_info\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        val bundle = Bundle()\n        bundle.putString(\"test\", \"test\")\n        DoKit.launchFullScreen(CustomDokitFragment::class, activity, bundle)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n    }\n}"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/dokit/ViewSetupHelper.java",
    "content": "package com.didichuxing.doraemondemo.dokit;\n\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.widget.CompoundButton;\nimport android.widget.TextView;\nimport android.widget.ToggleButton;\n\npublic final class ViewSetupHelper {\n    private ViewSetupHelper() {\n    }\n\n    public static void setupButton(View container, int viewId, String title, View.OnClickListener onBtnClick) {\n        TextView button = (TextView) container.findViewById(viewId);\n        if (TextUtils.isEmpty(title)) {\n            button.setVisibility(View.GONE);\n        } else {\n            button.setText(title);\n            button.setVisibility(View.VISIBLE);\n            button.setOnClickListener(onBtnClick);\n        }\n    }\n\n    public static void setVisible(View container, int viewId, boolean visible) {\n        container.findViewById(viewId).setVisibility(visible ? View.VISIBLE : View.GONE);\n    }\n\n    public static void setupToggleButton(View container, int viewId, String title, boolean checked, CompoundButton.OnCheckedChangeListener onCheckedChangeListener) {\n        ToggleButton toggleButton = (ToggleButton) container.findViewById(viewId);\n        if (TextUtils.isEmpty(title)) {\n            toggleButton.setVisibility(View.GONE);\n        } else {\n            toggleButton.setVisibility(View.VISIBLE);\n            toggleButton.setTextOn(title + \" ON\");\n            toggleButton.setTextOff(title + \" OFF\");\n            toggleButton.setChecked(checked);\n            toggleButton.setOnCheckedChangeListener(onCheckedChangeListener);\n            onCheckedChangeListener.onCheckedChanged(toggleButton, checked);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/mc/DoKitButton.kt",
    "content": "package com.didichuxing.doraemondemo.mc\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport androidx.appcompat.widget.AppCompatButton\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/7-19:27\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitButton : AppCompatButton {\n    companion object {\n        const val TAG = \"DoKitButton\"\n    }\n\n    constructor(context: Context) : super(context)\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(\n        context,\n        attrs,\n        defStyleAttr\n    )\n\n\n//    private val POPULATING_ACCESSIBILITY_EVENT_TYPES = (AccessibilityEvent.TYPE_VIEW_CLICKED\n//            or AccessibilityEvent.TYPE_VIEW_LONG_CLICKED\n//            or AccessibilityEvent.TYPE_VIEW_SELECTED\n//            or AccessibilityEvent.TYPE_VIEW_FOCUSED\n//            or AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED\n//            or AccessibilityEvent.TYPE_VIEW_HOVER_ENTER\n//            or AccessibilityEvent.TYPE_VIEW_HOVER_EXIT\n//            or AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED\n//            or AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED\n//            or AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED\n//            or AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY)\n//\n//\n//    override fun sendAccessibilityEvent(eventType: Int) {\n//        super.sendAccessibilityEvent(eventType)\n//    }\n//\n//    override fun sendAccessibilityEventUnchecked(event: AccessibilityEvent?) {\n//\n//\n//        // Do not send scroll events since first they are not interesting for\n//        // accessibility and second such events a generated too frequently.\n//        // For details see the implementation of bringTextIntoView().\n//        if (event!!.eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {\n//            return\n//        }\n//\n//        if (!isShown) {\n//            return\n//        }\n//        onInitializeAccessibilityEvent(event)\n//        LogHelper.i(TAG, \"event1===>$event\")\n//        // Only a subset of accessibility events populates text content.\n//        // Only a subset of accessibility events populates text content.\n//        if (event!!.eventType and POPULATING_ACCESSIBILITY_EVENT_TYPES != 0) {\n//            dispatchPopulateAccessibilityEvent(event)\n//        }\n//        LogHelper.i(TAG, \"event2===>$event\")\n//\n//        // Android 9.0以下的系统会在requestSendAccessibilityEvent 方法中对于event做一定的处理 导致event 中的有些字段会被置空\n//        this.parent?.requestSendAccessibilityEvent(this, event)\n//        LogHelper.i(TAG, \"event3===>$event\")\n//    }\n\n    override fun onTouchEvent(event: MotionEvent?): Boolean {\n        return super.onTouchEvent(event)\n\n    }\n}"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/mc/DoKitRecycleView.kt",
    "content": "package com.didichuxing.doraemondemo.mc\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.recyclerview.widget.RecyclerView\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/7-19:17\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitRecycleView : RecyclerView {\n\n    constructor(context: Context) : super(context)\n\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(\n        context,\n        attrs,\n        defStyleAttr\n    )\n\n\n}"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/mc/DoKitWebView.kt",
    "content": "package com.didichuxing.doraemondemo.mc\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.webkit.WebView\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/7-19:27\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitWebView : WebView {\n    companion object {\n        const val TAG = \"DoKitWebView\"\n    }\n\n    constructor(context: Context) : super(context)\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(\n        context,\n        attrs,\n        defStyleAttr\n    )\n\n    override fun getAccessibilityClassName(): CharSequence {\n        return super.getAccessibilityClassName()\n    }\n\n    override fun getAccessibilityTraversalBefore(): Int {\n        return super.getAccessibilityTraversalBefore()\n    }\n\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/mc/FileUtils.java",
    "content": "package com.didichuxing.doraemondemo.mc;\n\nimport java.io.File;\nimport java.io.InputStream;\n\n/**\n * didi Create on 2022/3/17 .\n * <p>\n * Copyright (c) 2022/3/17 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/17 4:54 下午\n * @Description 用一句话说明文件功能\n */\n\npublic class FileUtils {\n\n    /**\n     * 获取assets目录下的单个文件\n     * 这种方式只能用于webview加载\n     * 读取文件夹，直接取路径是不行的\n     *\n     * @param fileName 文件夹名\n     * @return File\n     */\n    public static File getFileFromAssetsFile(String fileName) {\n        String path = \"file:///android_asset/\" + fileName;\n        File file = new File(path);\n        return file;\n    }\n\n    public static String readString(InputStream inputStream) throws Exception {\n        int size = inputStream.available();\n        byte[] buffer = new byte[size];\n        inputStream.read(buffer, 0, size);\n        return new String(buffer);\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/mc/MCActivity.kt",
    "content": "package com.didichuxing.doraemondemo.mc\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.*\nimport android.widget.AdapterView.OnItemSelectedListener\nimport androidx.appcompat.app.AlertDialog\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.fragment.app.Fragment\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewpager.widget.ViewPager\nimport com.blankj.utilcode.util.ToastUtils\nimport com.didichuxing.doraemondemo.R\nimport com.didichuxing.doraemondemo.test.ScreenRecordingTest\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.constant.BundleKey\nimport com.didichuxing.doraemonkit.kit.fileexplorer.ImageDetailFragment\nimport com.didichuxing.doraemonkit.kit.test.report.ScreenShotManager\nimport java.io.File\n\n/**\n * 一机多控Demo Activity\n */\nclass MCActivity : AppCompatActivity() {\n\n    companion object {\n        private const val TAG = \"MCActivity\"\n    }\n\n    lateinit var mAdapter: RVAdapter\n\n    private val screenShotManager = ScreenShotManager(\"test/kk\")\n\n    private val screenRecordingTest = ScreenRecordingTest()\n\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_mc)\n\n        findViewById<Button>(R.id.nextPage).setOnClickListener {\n            startActivity(Intent(this, NetMainActivity::class.java))\n        }\n\n        findViewById<Button>(R.id.webPage).setOnClickListener {\n            startActivity(Intent(this, WebViewActivity::class.java))\n        }\n\n        findViewById<Button>(R.id.testPage).setOnClickListener {\n            startScreenShot()\n        }\n\n        findViewById<Button>(R.id.screenPage).setOnClickListener {\n            screenRecordingTest.start(this)\n        }\n\n\n\n        findViewById<SlideBar>(R.id.unlock_bar).setOnUnlockListener(object :\n            SlideBar.OnUnlockListener {\n            override fun onUnlock(view: View?) {\n                DoKit.sendCustomEvent(\n                    \"un_lock\",\n                    view,\n                    mapOf(\n                        \"unlock\" to \"custom unlock\",\n                        \"testRecording\" to \"true\"\n                    )\n                )\n            }\n\n            override fun progress(view: View?, leftMargin: Int) {\n                DoKit.sendCustomEvent(\n                    \"lock_process\",\n                    view,\n                    mapOf(\n                        \"progress\" to \"$leftMargin\",\n                        \"testRecording\" to \"false\"\n                    )\n                )\n            }\n        })\n\n        val spinner = findViewById<Spinner>(R.id.spinner)\n\n        val citis = resources.getStringArray(R.array.city);\n        val adapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, citis)\n        //第三步：设置下拉列表下拉时的菜单样式\n        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)\n        spinner.setAdapter(adapter)\n        spinner.prompt = \"测试\"\n\n        spinner.setOnItemSelectedListener(object : OnItemSelectedListener {\n            override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {\n                ToastUtils.showShort(\"选择：\" + citis[position])\n            }\n\n            override fun onNothingSelected(parent: AdapterView<*>?) {\n                ToastUtils.showShort(\"没有选择\")\n            }\n        })\n\n\n        findViewById<View>(R.id.btn1).setOnClickListener {\n            ToastUtils.showShort(\"正常点击\")\n        }\n\n\n        findViewById<View>(R.id.btn2).setOnClickListener {\n            AlertDialog.Builder(this)\n                .setMessage(\"我是弹框\")\n                .setPositiveButton(\n                    \"确定\"\n                ) { dialog, _ ->\n                    run {\n                        ToastUtils.showShort(\"确定\")\n                        dialog.dismiss()\n                    }\n                }.setNegativeButton(\"取消\") { dialog, _ ->\n                    run {\n                        ToastUtils.showShort(\"取消\")\n                        dialog.dismiss()\n                    }\n                }.show()\n\n\n        }\n        findViewById<RadioGroup>(R.id.radio_group).setOnCheckedChangeListener { _, checkedId ->\n            ToastUtils.showLong(\"checkedId===>${checkedId}\")\n        }\n\n        initData()\n\n    }\n\n\n    private fun startScreenShot() {\n        val map = screenShotManager.screenshotBitmap()\n        val fileName = screenShotManager.createNextFileName()\n        screenShotManager.saveBitmap(map, fileName)\n        val bundle = Bundle()\n        bundle.putSerializable(BundleKey.FILE_KEY, File(screenShotManager.getScreenFile(fileName)))\n        DoKit.launchFullScreen(ImageDetailFragment::class.java, this, bundle, false)\n    }\n\n\n    private fun initData() {\n\n        val rvDatas = mutableListOf<String>()\n        val lvDatas = mutableListOf<String>()\n\n\n        for (index in 0..100) {\n            val item = layoutInflater.inflate(R.layout.item_sc, null)\n            val tv = item.findViewById<TextView>(R.id.tv)\n            tv.setOnClickListener {\n                ToastUtils.showShort(\"${(it as TextView).text}\")\n            }\n            tv.text = \"sc item $index\"\n            findViewById<LinearLayout>(R.id.ll).addView(item)\n            rvDatas.add(\"rv item $index\")\n            lvDatas.add(\"lv item $index\")\n        }\n\n        findViewById<ListView>(R.id.lv)\n            .apply {\n                adapter = ArrayAdapter<String>(\n                    this@MCActivity,\n                    android.R.layout.simple_list_item_1,\n                    lvDatas\n                )\n\n                onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->\n                    ToastUtils.showShort(lvDatas[position])\n                }\n            }\n\n\n\n        mAdapter = RVAdapter(R.layout.item_rv, rvDatas)\n        mAdapter.setOnItemClickListener { adapter, _, position ->\n            ToastUtils.showShort(\"rv item  click ==>$position\")\n        }\n        findViewById<RecyclerView>(R.id.rv).apply {\n            layoutManager = LinearLayoutManager(this@MCActivity)\n            adapter = mAdapter\n        }\n\n\n        val fragments = mutableListOf<Fragment>()\n        fragments.add(VpFragment(0))\n        fragments.add(VpFragment(1))\n        fragments.add(VpFragment(2))\n        fragments.add(VpFragment(3))\n\n        findViewById<ViewPager>(R.id.vp).adapter = VPAdapter(fragments, supportFragmentManager)\n\n    }\n\n\n    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {\n        super.onActivityResult(requestCode, resultCode, data)\n        screenRecordingTest.onActivityResult(requestCode, resultCode, data)\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/mc/MyProxyWebView.java",
    "content": "package com.didichuxing.doraemondemo.mc;\n\nimport android.webkit.WebView;\n\n/**\n * didi Create on 2022/3/22 .\n * <p>\n * Copyright (c) 2022/3/22 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/22 7:15 下午\n * @Description 用一句话说明文件功能\n */\n\npublic class MyProxyWebView {\n\n\n    private WebView webView;\n\n    public MyProxyWebView(WebView webView) {\n        this.webView = webView;\n    }\n\n    public WebView getWebView() {\n        return webView;\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/mc/MyTestWebView.java",
    "content": "package com.didichuxing.doraemondemo.mc;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.webkit.WebView;\n\nimport java.util.Map;\n\n/**\n * didi Create on 2022/3/22 .\n * <p>\n * Copyright (c) 2022/3/22 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/22 3:56 下午\n * @Description 用一句话说明文件功能\n */\n\npublic class MyTestWebView extends WebView {\n\n    public MyTestWebView(Context context) {\n        super(context);\n    }\n\n    public MyTestWebView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public MyTestWebView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n    public MyTestWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n    }\n\n    @Override\n    public void loadUrl(String url) {\n        super.loadUrl(url);\n    }\n\n    @Override\n    public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {\n        super.loadUrl(url, additionalHttpHeaders);\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/mc/MyTestWebViewBuilder.java",
    "content": "package com.didichuxing.doraemondemo.mc;\n\nimport android.text.TextUtils;\n\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * didi Create on 2022/3/22 .\n * <p>\n * Copyright (c) 2022/3/22 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/22 7:14 下午\n * @Description 用一句话说明文件功能\n */\n\npublic class MyTestWebViewBuilder {\n\n    public final String TAG = \"WebViewBuilder\";\n\n    private MyProxyWebView mKDWebView;\n    private String initUrl;\n    private final Map<String, String> additionalHttpHeaders = new HashMap<String, String>();\n\n\n    public MyTestWebViewBuilder(MyProxyWebView mKDWebView, String initUrl) {\n        this.mKDWebView = mKDWebView;\n        this.initUrl = initUrl;\n    }\n\n    public void load(String url, Map<String, String> headers) {\n        mKDWebView.getWebView().loadUrl(url, headers);\n    }\n\n\n    /**\n     * 返回WebView\n     */\n    public MyProxyWebView create() {\n        if (!TextUtils.isEmpty(initUrl)) {\n            LogHelper.d(TAG, \"load initurl:\" + initUrl);\n            mKDWebView.getWebView().loadUrl(initUrl, additionalHttpHeaders);\n        }\n        return mKDWebView;\n    }\n\n    /**\n     * 返回WebView\n     */\n    public MyProxyWebView create2(String a, String b, String c, String d, String e, String f) {\n        if (!TextUtils.isEmpty(initUrl)) {\n            LogHelper.d(TAG, \"load initurl:\" + initUrl);\n            String dx = initUrl;\n            mKDWebView.getWebView().loadUrl(f, additionalHttpHeaders);\n        }\n        return mKDWebView;\n    }\n\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/mc/NetMainActivity.kt",
    "content": "package com.didichuxing.doraemondemo.mc\n\nimport androidx.appcompat.app.AppCompatActivity\nimport android.os.Bundle\nimport android.widget.TextView\nimport com.didichuxing.doraemondemo.R\nimport kotlinx.coroutines.*\nimport okhttp3.*\nimport java.io.IOException\n\nclass NetMainActivity : AppCompatActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_net_main)\n\n    }\n\n    override fun onResume() {\n        super.onResume()\n        request()\n    }\n\n    fun request() {\n        val httpRequest = Request.Builder()\n            .header(\"User-Agent\", \"mc-test\")\n            .url(\"https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7\")\n            .build()\n\n        val client = OkHttpClient.Builder().build()\n        client.newCall(httpRequest).enqueue(object : Callback {\n            override fun onFailure(call: Call, e: IOException) {\n                GlobalScope.launch(Dispatchers.Main) {\n                    val view = findViewById<TextView>(R.id.text)\n                    view.text = e.message\n                }\n            }\n\n            override fun onResponse(call: Call, response: Response) {\n                GlobalScope.launch(Dispatchers.Main) {\n                    val text: String? = response.body()?.string()\n                    val view = findViewById<TextView>(R.id.text)\n                    view.text = text\n                }\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/mc/RVAdapter.kt",
    "content": "package com.didichuxing.doraemondemo.mc\n\nimport android.widget.Button\nimport android.widget.TextView\nimport com.blankj.utilcode.util.ToastUtils\nimport com.chad.library.adapter.base.BaseQuickAdapter\nimport com.chad.library.adapter.base.viewholder.BaseViewHolder\nimport com.didichuxing.doraemondemo.R\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/7-14:48\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass RVAdapter(layoutResId: Int, data: MutableList<String>? = null) :\n    BaseQuickAdapter<String, BaseViewHolder>(layoutResId, data) {\n    companion object {\n        const val TAG = \"DemoAdapter\"\n    }\n\n    override fun convert(holder: BaseViewHolder, item: String) {\n        holder.getView<TextView>(R.id.tv).text = item\n        holder.getView<Button>(R.id.btn).setOnClickListener {\n\n            val position = holder.adapterPosition\n            ToastUtils.showShort(\"rv item inner btn click==$position\")\n        }\n    }\n}"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/mc/SlideBar.java",
    "content": "package com.didichuxing.doraemondemo.mc;\n\nimport android.animation.ValueAnimator;\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.FrameLayout;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemondemo.R;\n\n\n/**\n * 拖动解锁按钮\n *\n * @author jint\n */\npublic class SlideBar extends FrameLayout {\n\n    private View backgroundView;\n    private View floatView;\n    private View floatBackground;\n    private TextView mText = null;\n\n    private int thumbWidth = 0;\n    private boolean sliding = false;\n    private int sliderPosition = 0;\n    private int initialSliderPosition = 0;\n    private float initialSlidingX = 0;\n\n    private static final int TEXT_ALIGN_CENTER = 0;\n    private static final int TEXT_ALIGN_CENTER_EXCEPT_THUMB = 1;\n\n    private int textAlign = TEXT_ALIGN_CENTER;\n\n    private OnUnlockListener listener = null;\n\n    public void setOnUnlockListener(OnUnlockListener listener) {\n        this.listener = listener;\n    }\n\n    public SlideBar(Context context) {\n        this(context, null);\n    }\n\n    public SlideBar(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public SlideBar(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        init(context, attrs);\n\n    }\n\n    private void init(Context context, AttributeSet attrs) {\n        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n        inflater.inflate(R.layout.layout_slidebar, this, true);\n\n        backgroundView = findViewById(R.id.slidebar_root_bg);\n        mText = findViewById(R.id.slidebar_text_label);\n        floatBackground = findViewById(R.id.slidebar_float_background);\n        floatView = findViewById(R.id.slidebar_float_view);\n        if (attrs != null) {\n            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UnlockBar);\n            String label = a.getString(R.styleable.UnlockBar_label);\n            mText.setText(label);\n            textAlign = a.getInt(R.styleable.UnlockBar_textAlign, TEXT_ALIGN_CENTER);\n            a.recycle();\n        } else {\n            textAlign = TEXT_ALIGN_CENTER_EXCEPT_THUMB;\n        }\n//        if (textAlign == TEXT_ALIGN_CENTER_EXCEPT_THUMB) {\n//            FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mText.getLayoutParams();\n//            params.setMargins(context.getResources().getDimensionPixelSize(R.dimen.slidebar_thumb_width) / 2, 0, 0, 0);\n//            mText.setLayoutParams(params);\n//        }\n\n        LayoutParams params = (LayoutParams) floatView.getLayoutParams();\n        ViewGroup p = (ViewGroup) floatBackground.getParent();\n        thumbWidth = p.getPaddingLeft() + p.getPaddingRight()\n                + params.leftMargin + params.rightMargin\n                + context.getResources().getDimensionPixelSize(R.dimen.slidebar_thumb_width);\n    }\n\n    public void reset() {\n        sliderPosition = 0;\n\n        final LayoutParams bgParams = (LayoutParams) floatBackground.getLayoutParams();\n        ValueAnimator animator = ValueAnimator.ofInt(bgParams.leftMargin, 0);\n        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator valueAnimator) {\n                int value = (Integer) valueAnimator.getAnimatedValue();\n                setMarginLeftExtra(value);\n                if (value <= 1 && !isPressed()) {\n                    mText.setAlpha(1f);\n                }\n                floatBackground.requestLayout();\n            }\n        });\n        animator.setDuration(300);\n        animator.start();\n    }\n\n    /**\n     * 设置按钮文字\n     */\n    public void setLabel(CharSequence s) {\n        mText.setText(s);\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        super.onTouchEvent(event);\n\n        if (!mDragAble) {\n            return true;\n        }\n\n        if (event.getAction() == MotionEvent.ACTION_DOWN) {\n            if (event.getX() > sliderPosition && event.getX() < (sliderPosition + thumbWidth)) {\n                sliding = true;\n                initialSlidingX = event.getX();\n                initialSliderPosition = sliderPosition;\n                mText.setAlpha(0);\n                setPressed(true);\n            }\n            event.setAction(MotionEvent.ACTION_DOWN);\n        } else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_OUTSIDE) {\n            if (sliderPosition >= (getMeasuredWidth() - thumbWidth) * 0.7) {\n                //到70%就行\n                if (listener != null) listener.onUnlock(this);\n            } else if (sliding) {\n                reset();\n            }\n            sliding = false;\n            setPressed(false);\n            event.setAction(MotionEvent.ACTION_UP);\n        } else if (event.getAction() == MotionEvent.ACTION_MOVE && sliding) {\n            sliderPosition = (int) (initialSliderPosition + (event.getX() - initialSlidingX));\n            if (sliderPosition <= 0) sliderPosition = 0;\n\n            if (sliderPosition >= (getMeasuredWidth() - thumbWidth)) {\n                sliderPosition = getMeasuredWidth() - thumbWidth;\n            } else {\n//                int max = getMeasuredWidth() - thumbWidth;\n//                int progress = (int) (sliderPosition * 100 / (max * 1.0f));\n                mText.setAlpha(0);\n            }\n            setMarginLeftExtra(sliderPosition);\n            event.setAction(MotionEvent.ACTION_MOVE);\n        }\n        return true;\n    }\n\n    public void setMarginLeftExtra(int leftMargin) {\n        if (floatBackground == null) return;\n        if (listener != null) listener.progress(this, leftMargin);\n        LayoutParams params = (LayoutParams) floatBackground.getLayoutParams();\n        params.setMargins(leftMargin, params.topMargin, params.rightMargin, params.bottomMargin);\n        floatBackground.setLayoutParams(params);\n    }\n\n    public interface OnUnlockListener {\n        void onUnlock(View view);\n\n        void progress(View view, int leftMargin);\n    }\n\n\n    /**\n     * 设置后台背景\n     *\n     * @param color\n     */\n    public void setBackgroundColor(int color) {\n        if (backgroundView != null) {\n            backgroundView.setBackgroundColor(color);\n        }\n    }\n\n    /**\n     * 设置前台背景\n     *\n     * @param color\n     */\n    public void setForegroundColor(int color) {\n        if (floatBackground != null) {\n            floatBackground.setBackgroundColor(color);\n        }\n    }\n\n    /**\n     * 设置圆角\n     *\n     * @param radius\n     */\n    public void setBorderRadius(float radius) {\n        // TODO: 在这里添加设置圆角代码\n    }\n\n    /**\n     * 设置是否可以拖动\n     */\n    private boolean mDragAble = true;\n\n    public void setDragAble(boolean able) {\n        if (able) {\n            floatView.setVisibility(View.VISIBLE);\n            mDragAble = true;\n        } else {\n            floatView.setVisibility(View.GONE);\n            mDragAble = false;\n        }\n    }\n\n    @Override\n    public boolean performClick() {\n        return super.performClick();\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/mc/VPAdapter.kt",
    "content": "package com.didichuxing.doraemondemo.mc\n\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentManager\nimport androidx.fragment.app.FragmentPagerAdapter\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/18-11:14\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass VPAdapter(private val fragments: List<Fragment>, fm: FragmentManager) : FragmentPagerAdapter(\n    fm,\n    BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT\n) {\n    override fun getCount(): Int {\n        return fragments.size\n    }\n\n    override fun getItem(position: Int): Fragment {\n        return fragments[position]\n    }\n}"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/mc/VpFragment.kt",
    "content": "package com.didichuxing.doraemondemo.mc\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Button\nimport androidx.fragment.app.Fragment\nimport com.blankj.utilcode.util.ToastUtils\nimport com.didichuxing.doraemondemo.R\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/29-11:24\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass VpFragment(index: Int) : Fragment() {\n    val mIndex: Int = index\n\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        val view = inflater.inflate(R.layout.fragment_vp, container, false)\n        val btn = view.findViewById<Button>(R.id.btn)\n        btn.text = \"Fragment_$mIndex\"\n        btn.setOnClickListener {\n            ToastUtils.showShort((it as Button).text.toString())\n        }\n        return view\n    }\n\n\n}"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/mc/WebViewActivity.kt",
    "content": "package com.didichuxing.doraemondemo.mc\n\nimport android.graphics.Bitmap\nimport android.os.Build\nimport android.os.Bundle\nimport android.webkit.*\nimport androidx.appcompat.app.AppCompatActivity\nimport com.didichuxing.doraemondemo.R\nimport com.didichuxing.doraemonkit.util.LogHelper\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/29-21:58\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass WebViewActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_webview)\n        val webview = findViewById<WebView>(R.id.webview)\n\n        webview.settings.allowFileAccess = true\n        webview.settings.allowContentAccess = true\n        webview.settings.allowFileAccessFromFileURLs = true\n        webview.settings.javaScriptEnabled = true\n        webview.settings.domStorageEnabled = true\n        webview.settings.databaseEnabled = true\n        webview.settings.setAppCacheEnabled(true)\n        webview.settings.mixedContentMode =WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE\n        webview.settings.allowUniversalAccessFromFileURLs = true\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            CookieManager.getInstance().setAcceptThirdPartyCookies(webview, true);\n        }\n\n        webview.webChromeClient = object : WebChromeClient() {\n            override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {\n                return super.onConsoleMessage(consoleMessage)\n            }\n\n            override fun onProgressChanged(view: WebView?, newProgress: Int) {\n                super.onProgressChanged(view, newProgress)\n            }\n        }\n\n        webview.webViewClient = object : WebViewClient() {\n\n            override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {\n                return super.shouldInterceptRequest(view, request)\n            }\n\n            override fun onLoadResource(view: WebView?, url: String?) {\n                super.onLoadResource(view, url)\n            }\n\n            override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {\n                super.onPageStarted(view, url, favicon)\n            }\n\n            override fun onPageFinished(view: WebView?, url: String?) {\n                super.onPageFinished(view, url)\n                LogHelper.e(\"WEB\", \"dokit onPageFinished()\")\n            }\n\n        }\n\n//        val url = \"https://www.dokit.cn/#/index/home\"\n        val url = \"https://page.xiaojukeji.com/m/ddPage_0sMxiuk7.html\"\n        val webViewBuilder = MyTestWebViewBuilder(MyProxyWebView(webview), url)\n        webViewBuilder.create()\n    }\n\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/CrashTest.kt",
    "content": "package com.didichuxing.doraemondemo.module\n\nobject CrashTest {\n\n    fun test() {\n        checkNotNull(testCrash())\n    }\n\n\n    private fun testCrash(): String? {\n        return null\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/DoKitItemView.java",
    "content": "package com.didichuxing.doraemondemo.module;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Color;\nimport android.util.AttributeSet;\nimport android.util.TypedValue;\nimport android.view.View;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemondemo.R;\n\n/**\n * didi Create on 2022/5/26 .\n * <p>\n * Copyright (c) 2022/5/26 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/5/26 11:18 上午\n * @Description 用一句话说明文件功能\n */\n\npublic class DoKitItemView extends FrameLayout {\n\n    private int itemLayoutId;\n    private int itemIcon;\n    private int itemTextSize;\n    private boolean itemIconShow;\n    private boolean itemTextShow;\n    private String itemText = \"\";\n\n    private TextView textView;\n    private ImageView imageView;\n\n\n    public DoKitItemView(@NonNull Context context) {\n        super(context);\n    }\n\n    public DoKitItemView(@NonNull Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        init(context, attrs);\n    }\n\n    public DoKitItemView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init(context, attrs);\n    }\n\n    private void init(Context context, @Nullable AttributeSet attrs) {\n        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DoKitItemView);\n        int layout = a.getInt(R.styleable.DoKitItemView_itemLayout, R.layout.view_dokit_item_view);\n        int icon = a.getResourceId(R.styleable.DoKitItemView_itemIcon, R.mipmap.dk_arrow_normal);\n        String text = a.getString(R.styleable.DoKitItemView_itemText);\n        int textSize = a.getDimensionPixelSize(R.styleable.DoKitItemView_itemTextSize, -1);\n        boolean iconShow = a.getBoolean(R.styleable.DoKitItemView_itemIconShow, true);\n        boolean textShow = a.getBoolean(R.styleable.DoKitItemView_itemTextShow, true);\n\n        itemLayoutId = layout;\n        itemIcon = icon;\n        itemText = text == null ? \"\" : text;\n        itemTextSize = textSize;\n        itemIconShow = iconShow;\n        itemTextShow = textShow;\n        a.recycle();\n\n    }\n\n    @Override\n    protected void onFinishInflate() {\n        super.onFinishInflate();\n        inflate(getContext(), itemLayoutId, this);\n        textView = findViewById(R.id.itemText);\n        imageView = findViewById(R.id.itemIcon);\n        View root = findViewById(R.id.rootView);\n\n        ShadowDrawable.setShadowDrawable(root, Color.parseColor(\"#FFFFFF\"), dpToPx(8),\n            Color.parseColor(\"#26777777\"), dpToPx(4), 0, 0);\n\n        textView.setText(itemText);\n        imageView.setBackgroundResource(itemIcon);\n        if (!itemIconShow) {\n            imageView.setVisibility(GONE);\n        }\n        if (!itemTextShow) {\n            textView.setVisibility(GONE);\n        }\n        if (itemTextSize >= 0) {\n            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, itemTextSize);\n        }\n    }\n\n    private int dpToPx(int dp) {\n        return (int) getResources().getDisplayMetrics().density * dp;\n    }\n\n    public String getItemText() {\n        return itemText;\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/MethodCostTest.kt",
    "content": "package com.didichuxing.doraemondemo.module\n\nobject MethodCostTest {\n\n\n    fun test() {\n        test1()\n    }\n\n    private fun test1() {\n        try {\n            Thread.sleep(1000)\n        } catch (e: InterruptedException) {\n            e.printStackTrace()\n        }\n        test2()\n    }\n\n    private fun test2() {\n        try {\n            Thread.sleep(200)\n        } catch (e: InterruptedException) {\n            e.printStackTrace()\n        }\n        test3()\n    }\n\n    private fun test3() {\n        try {\n            Thread.sleep(200)\n        } catch (e: InterruptedException) {\n            e.printStackTrace()\n        }\n        test4()\n    }\n\n    private fun test4() {\n        try {\n            Thread.sleep(200)\n        } catch (e: InterruptedException) {\n            e.printStackTrace()\n        }\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/ModuleActivity.java",
    "content": "package com.didichuxing.doraemondemo.module;\n\n\n/**\n * didi Create on 2022/5/25 .\n * <p>\n * Copyright (c) 2022/5/25 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/5/25 6:23 下午\n * @Description 用一句话说明文件功能\n */\n\npublic class ModuleActivity {\n\n\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/ShadowDrawable.java",
    "content": "package com.didichuxing.doraemondemo.module;\n\n/**\n * didi Create on 2022/5/27 .\n * <p>\n * Copyright (c) 2022/5/27 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/5/27 11:41 上午\n * @Description 用一句话说明文件功能\n */\n\n\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.ColorFilter;\nimport android.graphics.LinearGradient;\nimport android.graphics.Paint;\nimport android.graphics.PixelFormat;\nimport android.graphics.PorterDuffXfermode;\nimport android.graphics.RectF;\nimport android.graphics.PorterDuff.Mode;\nimport android.graphics.Shader.TileMode;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.view.ViewCompat;\n\npublic class ShadowDrawable extends Drawable {\n    private Paint mPaint;\n    private int mShadowRadius;\n    private int mShape;\n    private int mShapeRadius;\n    private int mOffsetX;\n    private int mOffsetY;\n    private int[] mBgColor;\n    private RectF mRect;\n    public static final int SHAPE_ROUND = 1;\n    public static final int SHAPE_CIRCLE = 2;\n\n    private ShadowDrawable(int shape, int[] bgColor, int shapeRadius, int shadowColor, int shadowRadius, int offsetX, int offsetY) {\n        this.mShape = shape;\n        this.mBgColor = bgColor;\n        this.mShapeRadius = shapeRadius;\n        this.mShadowRadius = shadowRadius;\n        this.mOffsetX = offsetX;\n        this.mOffsetY = offsetY;\n        this.mPaint = new Paint();\n        this.mPaint.setColor(0);\n        this.mPaint.setAntiAlias(true);\n        this.mPaint.setShadowLayer((float) shadowRadius, (float) offsetX, (float) offsetY, shadowColor);\n        this.mPaint.setXfermode(new PorterDuffXfermode(Mode.DST_ATOP));\n    }\n\n    public void setBounds(int left, int top, int right, int bottom) {\n        super.setBounds(left, top, right, bottom);\n        this.mRect = new RectF((float) (left + this.mShadowRadius - this.mOffsetX), (float) (top + this.mShadowRadius - this.mOffsetY), (float) (right - this.mShadowRadius - this.mOffsetX), (float) (bottom - this.mShadowRadius - this.mOffsetY));\n    }\n\n    public void draw(@NonNull Canvas canvas) {\n        Paint newPaint = new Paint();\n        if (this.mBgColor != null) {\n            if (this.mBgColor.length == 1) {\n                newPaint.setColor(this.mBgColor[0]);\n            } else {\n                newPaint.setShader(new LinearGradient(this.mRect.left, this.mRect.height() / 2.0F, this.mRect.right, this.mRect.height() / 2.0F, this.mBgColor, (float[]) null, TileMode.CLAMP));\n            }\n        }\n\n        newPaint.setAntiAlias(true);\n        if (this.mShape == 1) {\n            canvas.drawRoundRect(this.mRect, (float) this.mShapeRadius, (float) this.mShapeRadius, this.mPaint);\n            canvas.drawRoundRect(this.mRect, (float) this.mShapeRadius, (float) this.mShapeRadius, newPaint);\n        } else {\n            canvas.drawCircle(this.mRect.centerX(), this.mRect.centerY(), Math.min(this.mRect.width(), this.mRect.height()) / 2.0F, this.mPaint);\n            canvas.drawCircle(this.mRect.centerX(), this.mRect.centerY(), Math.min(this.mRect.width(), this.mRect.height()) / 2.0F, newPaint);\n        }\n\n    }\n\n    public void setAlpha(int alpha) {\n        this.mPaint.setAlpha(alpha);\n    }\n\n    public void setColorFilter(@Nullable ColorFilter colorFilter) {\n        this.mPaint.setColorFilter(colorFilter);\n    }\n\n    public int getOpacity() {\n        return PixelFormat.TRANSLUCENT;\n\n    }\n\n    public static void setShadowDrawable(View view, Drawable drawable) {\n        view.setLayerType(View.LAYER_TYPE_SOFTWARE, (Paint) null);\n        ViewCompat.setBackground(view, drawable);\n    }\n\n    public static void setShadowDrawable(View view, int shapeRadius, int shadowColor, int shadowRadius, int offsetX, int offsetY) {\n        ShadowDrawable drawable = (new ShadowDrawable.Builder()).setShapeRadius(shapeRadius).setShadowColor(shadowColor).setShadowRadius(shadowRadius).setOffsetX(offsetX).setOffsetY(offsetY).builder();\n        view.setLayerType(View.LAYER_TYPE_SOFTWARE, (Paint) null);\n        ViewCompat.setBackground(view, drawable);\n    }\n\n    public static void setShadowDrawable(View view, int bgColor, int shapeRadius, int shadowColor, int shadowRadius, int offsetX, int offsetY) {\n        ShadowDrawable drawable = (new ShadowDrawable.Builder()).setBgColor(bgColor).setShapeRadius(shapeRadius).setShadowColor(shadowColor).setShadowRadius(shadowRadius).setOffsetX(offsetX).setOffsetY(offsetY).builder();\n        view.setLayerType(View.LAYER_TYPE_SOFTWARE, (Paint) null);\n        ViewCompat.setBackground(view, drawable);\n    }\n\n    public static void setShadowDrawable(View view, int shape, int bgColor, int shapeRadius, int shadowColor, int shadowRadius, int offsetX, int offsetY) {\n        ShadowDrawable drawable = (new ShadowDrawable.Builder()).setShape(shape).setBgColor(bgColor).setShapeRadius(shapeRadius).setShadowColor(shadowColor).setShadowRadius(shadowRadius).setOffsetX(offsetX).setOffsetY(offsetY).builder();\n        view.setLayerType(View.LAYER_TYPE_SOFTWARE, (Paint) null);\n        ViewCompat.setBackground(view, drawable);\n    }\n\n    public static void setShadowDrawable(View view, int[] bgColor, int shapeRadius, int shadowColor, int shadowRadius, int offsetX, int offsetY) {\n        ShadowDrawable drawable = (new ShadowDrawable.Builder()).setBgColor(bgColor).setShapeRadius(shapeRadius).setShadowColor(shadowColor).setShadowRadius(shadowRadius).setOffsetX(offsetX).setOffsetY(offsetY).builder();\n        view.setLayerType(View.LAYER_TYPE_SOFTWARE, (Paint) null);\n        ViewCompat.setBackground(view, drawable);\n    }\n\n    public static class Builder {\n        private int mShape = 1;\n        private int mShapeRadius = 12;\n        private int mShadowColor = Color.parseColor(\"#4d000000\");\n        private int mShadowRadius = 18;\n        private int mOffsetX = 0;\n        private int mOffsetY = 0;\n        private int[] mBgColor;\n\n        public Builder() {\n            this.mOffsetX = 0;\n            this.mOffsetY = 0;\n            this.mBgColor = new int[1];\n            this.mBgColor[0] = 0;\n        }\n\n        public ShadowDrawable.Builder setShape(int mShape) {\n            this.mShape = mShape;\n            return this;\n        }\n\n        public ShadowDrawable.Builder setShapeRadius(int ShapeRadius) {\n            this.mShapeRadius = ShapeRadius;\n            return this;\n        }\n\n        public ShadowDrawable.Builder setShadowColor(int shadowColor) {\n            this.mShadowColor = shadowColor;\n            return this;\n        }\n\n        public ShadowDrawable.Builder setShadowRadius(int shadowRadius) {\n            this.mShadowRadius = shadowRadius;\n            return this;\n        }\n\n        public ShadowDrawable.Builder setOffsetX(int OffsetX) {\n            this.mOffsetX = OffsetX;\n            return this;\n        }\n\n        public ShadowDrawable.Builder setOffsetY(int OffsetY) {\n            this.mOffsetY = OffsetY;\n            return this;\n        }\n\n        public ShadowDrawable.Builder setBgColor(int BgColor) {\n            this.mBgColor[0] = BgColor;\n            return this;\n        }\n\n        public ShadowDrawable.Builder setBgColor(int[] BgColor) {\n            this.mBgColor = BgColor;\n            return this;\n        }\n\n        public ShadowDrawable builder() {\n            return new ShadowDrawable(this.mShape, this.mBgColor, this.mShapeRadius, this.mShadowColor, this.mShadowRadius, this.mOffsetX, this.mOffsetY);\n        }\n    }\n}\n\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/bigbitmap/BigBitmapActivity.kt",
    "content": "package com.didichuxing.doraemondemo.module.bigbitmap\n\nimport android.graphics.BitmapFactory\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.ImageView\nimport coil.imageLoader\nimport coil.request.CachePolicy\nimport coil.transform.CircleCropTransformation\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.RequestBuilder\nimport com.bumptech.glide.load.engine.DiskCacheStrategy\nimport com.bumptech.glide.load.resource.bitmap.CircleCrop\nimport com.didichuxing.doraemondemo.BaseStatusBarActivity\nimport com.didichuxing.doraemondemo.R\nimport com.didichuxing.doraemondemo.databinding.ActivityBigBitmapMockBinding\nimport com.facebook.drawee.backends.pipeline.Fresco\nimport com.nostra13.universalimageloader.core.ImageLoader\nimport com.nostra13.universalimageloader.core.ImageLoaderConfiguration\nimport com.squareup.picasso.MemoryPolicy\nimport com.squareup.picasso.Picasso\nimport com.squareup.picasso.RequestCreator\nimport java.io.IOException\nimport java.net.HttpURLConnection\nimport java.net.MalformedURLException\nimport java.net.URL\n\n\n/**\n * didi Create on 2022/5/27 .\n *\n * Copyright (c) 2022/5/27 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/5/27 6:00 下午\n * @Description 用一句话说明文件功能\n */\n\nclass BigBitmapActivity : BaseStatusBarActivity() {\n\n    //Glide 加载\n    val picassoImgUrl =\n        \"https://gimg2.baidu.com/image_search/src=http%3A%2F%2F2c.zol-img.com.cn%2Fproduct%2F124_500x2000%2F748%2FceZOdKgDAFsq2.jpg&refer=http%3A%2F%2F2c.zol-img.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621652979&t=850e537c70eaa7753e892bc8b4d05f57\"\n    val glideImageUrl =\n        \"https://gimg2.baidu.com/image_search/src=http%3A%2F%2F1812.img.pp.sohu.com.cn%2Fimages%2Fblog%2F2009%2F11%2F18%2F18%2F8%2F125b6560a6ag214.jpg&refer=http%3A%2F%2F1812.img.pp.sohu.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621652979&t=76d35d35d9c510f1c24a422e9e02fd46\"\n    val frescoImageUrl =\n        \"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fyouimg1.c-ctrip.com%2Ftarget%2Ftg%2F035%2F063%2F726%2F3ea4031f045945e1843ae5156749d64c.jpg&refer=http%3A%2F%2Fyouimg1.c-ctrip.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621652979&t=7150aaa2071d512cf2f6b556e126dd66\"\n    val imageLoaderImageUrl =\n        \"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fyouimg1.c-ctrip.com%2Ftarget%2Ftg%2F004%2F531%2F381%2F4339f96900344574a0c8ca272a7b8f27.jpg&refer=http%3A%2F%2Fyouimg1.c-ctrip.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621652979&t=b7e83ecc987c64cc31079469d292eb56\"\n    val coilImageUrl =\n        \"https://cdn.nlark.com/yuque/0/2020/png/252337/1587091196083-assets/web-upload/62122ab5-986b-4662-be88-d3007a5e31c5.png\"\n\n    private var _binding: ActivityBigBitmapMockBinding? = null\n\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        title = \"大图检测\"\n\n        //初始化\n        val config = ImageLoaderConfiguration.Builder(this)\n            .build()\n        ImageLoader.getInstance().init(config)\n\n        _binding = ActivityBigBitmapMockBinding.inflate(layoutInflater).also { it ->\n            setContentView(it.root)\n\n            it.btnLoadImg.setOnClickListener { view ->\n                onClick(view)\n            }\n        }\n\n    }\n\n\n     fun onClick(v: View) {\n        when (v.id) {\n\n            R.id.btn_load_img -> {\n\n                Picasso.get().load(picassoImgUrl)\n                    .memoryPolicy(MemoryPolicy.NO_CACHE)\n                    .placeholder(R.mipmap.cat)\n                    .error(R.mipmap.cat)\n                    .intoOrCancel(_binding?.ivPicasso)\n\n                Glide.with(this)\n                    .asBitmap()\n                    .load(glideImageUrl)\n                    .placeholder(R.mipmap.cat)\n                    .error(R.mipmap.cat)\n                    .diskCacheStrategy(DiskCacheStrategy.NONE)\n                    .skipMemoryCache(true)\n                    .transform(CircleCrop())\n                    .intoOrCancel(_binding?.ivGlide)\n\n                //coil\n                _binding?.ivCoil?.apply {\n                    val request = coil.request.ImageRequest.Builder(this.context)\n                        .memoryCachePolicy(CachePolicy.DISABLED)\n                        .transformations(CircleCropTransformation())\n                        .diskCachePolicy(CachePolicy.DISABLED)\n                        .data(coilImageUrl)\n                        .target(this)\n                        .build()\n                    imageLoader.enqueue(request)\n                }\n\n                //connect\n                run {\n                    requestImage(coilImageUrl)\n                }\n\n                //imageLoader\n                val imageLoader = ImageLoader.getInstance()\n                imageLoader.displayImageOrNot(imageLoaderImageUrl, _binding?.ivImageloader)\n                //fresco\n                _binding?.ivFresco?.setImageURI(Uri.parse(frescoImageUrl))\n                val imagePipeline = Fresco.getImagePipeline()\n                // combines above two lines\n                imagePipeline.clearCaches()\n            }\n\n            else -> {\n            }\n        }\n    }\n\n    private fun requestImage(urlStr: String) {\n        try {\n            //\n            val url = URL(urlStr)\n            // http    https\n            // ftp\n            val urlConnection = url.openConnection() as HttpURLConnection\n            //http get post\n            urlConnection.requestMethod = \"GET\"\n            urlConnection.connectTimeout = 5000\n            urlConnection.readTimeout = 5000\n            val responseCode = urlConnection.responseCode\n            if (responseCode == 200) {\n                val bitmap = BitmapFactory.decodeStream(urlConnection.inputStream)\n\n                runOnUiThread {\n                    _binding?.ivConnect?.setImageBitmap(bitmap)\n                }\n            }\n        } catch (e: MalformedURLException) {\n            e.printStackTrace()\n        } catch (e: IOException) {\n            e.printStackTrace()\n        }\n    }\n\n    private fun RequestCreator.intoOrCancel(target: ImageView?) {\n        target?.also { into(it) }\n    }\n\n    private fun RequestBuilder<*>.intoOrCancel(target: ImageView?) {\n        target?.also { into(it) }\n    }\n\n    private fun ImageLoader.displayImageOrNot(url: String, target: ImageView?) {\n        target?.also { displayImage(url, it) }\n    }\n\n    override fun onResume() {\n        super.onResume()\n    }\n\n\n    override fun onDestroy() {\n        super.onDestroy()\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/db/DataBaseTest.kt",
    "content": "package com.didichuxing.doraemondemo.module.db\n\nimport com.didichuxing.doraemondemo.db.DatabaseHelper\nimport com.didichuxing.doraemonkit.util.Utils\n\nobject DataBaseTest {\n\n    fun test() {\n        val dbHelper = DatabaseHelper(Utils.getApp(), \"BookStore.db\", null, 1)\n        dbHelper.writableDatabase\n        dbHelper.close()\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/http/CustomInterceptor.kt",
    "content": "package com.didichuxing.doraemondemo.module.http\n\nimport android.util.Log\nimport okhttp3.Interceptor\nimport okhttp3.Response\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：4/22/21-11:21\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass CustomInterceptor : Interceptor {\n    override fun intercept(chain: Interceptor.Chain): Response {\n        Log.i(\"CustomInterceptor\", \"===custom intercept===\")\n        return chain.proceed(chain.request())\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/http/FileUploadTest.kt",
    "content": "package com.didichuxing.doraemondemo.module.http\n\nimport android.app.ProgressDialog\nimport android.content.Context\nimport android.os.SystemClock\nimport android.text.format.Formatter\nimport android.util.Log\nimport android.widget.Toast\nimport com.didichuxing.doraemonkit.util.ThreadUtils.runOnUiThread\nimport okhttp3.*\nimport java.io.*\nimport java.net.SocketTimeoutException\nimport java.net.UnknownHostException\n\n\n/**\n * didi Create on 2022/5/27 .\n *\n * Copyright (c) 2022/5/27 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/5/27 3:40 下午\n * @Description 用一句话说明文件功能\n */\n\nobject FileUploadTest {\n\n    private var okHttpClient: OkHttpClient = OkHttpClient().newBuilder().build()\n\n    /**\n     * 模拟上传或下载文件\n     *\n     * @param upload true上传 false下载\n     */\n    fun requestByFile(context: Context, filesDir: File, upload: Boolean) {\n        val dialog = ProgressDialog.show(context, null, null)\n        dialog.setCancelable(true)\n        var request: Request? = null\n        if (upload) {\n            try {\n                //模拟一个1M的文件用来上传\n                val length = 1L * 1024 * 1024\n                val temp = File(filesDir, \"test.tmp\")\n                if (!temp.exists() || temp.length() != length) {\n                    val accessFile = RandomAccessFile(temp, \"rwd\")\n                    accessFile.setLength(length)\n                    temp.createNewFile()\n                }\n                request = Request.Builder()\n                    .post(RequestBody.create(MediaType.parse(temp.name), temp))\n                    .url(\"http://wallpaper.apc.360.cn/index.php?c=WallPaper&a=getAppsByOrder&order=create_time&start=0&count=1&from=360chrome\")\n                    .build()\n            } catch (e: IOException) {\n                e.printStackTrace()\n            }\n        } else {\n            //下载一个2M的文件\n            request = Request.Builder()\n                .get()\n                .url(\"http://cdn1.lbesec.com/products/history/20131220/privacyspace_rel_2.2.1617.apk\")\n                .build()\n        }\n        val call = okHttpClient!!.newCall(request!!)\n        val startTime = SystemClock.uptimeMillis()\n        call.enqueue(object : Callback {\n            override fun onFailure(call: Call, e: IOException) {\n                dialog.cancel()\n                onHttpFailure(context, e)\n            }\n\n            @Throws(IOException::class)\n            override fun onResponse(call: Call, response: okhttp3.Response) {\n                if (!response.isSuccessful) {\n                    onFailure(call, IOException(response.message()))\n                    return\n                }\n                val body = response.body()\n                if (!upload) {\n                    inputStream2File(body!!.byteStream(), File(filesDir, \"test.apk\"))\n                }\n                dialog.cancel()\n                val requestLength = if (upload) call.request().body()!!.contentLength() else 0\n                val responseLength = if (body!!.contentLength() < 0) 0 else body.contentLength()\n                val endTime = SystemClock.uptimeMillis() - startTime\n                val speed = (if (upload) requestLength else responseLength) / endTime * 1000\n                val message = String.format(\n                    \"请求大小：%s，响应大小：%s，耗时：%dms，均速：%s/s\",\n                    Formatter.formatFileSize(context, requestLength),\n                    Formatter.formatFileSize(context, responseLength),\n                    endTime,\n                    Formatter.formatFileSize(context, speed)\n                )\n                runOnUiThread {\n                    Log.d(\"onResponse\", message)\n                    Toast.makeText(context, message, Toast.LENGTH_LONG)\n                        .show()\n                }\n            }\n        })\n    }\n\n    private fun onHttpFailure(context: Context, e: IOException) {\n        e.printStackTrace()\n        runOnUiThread {\n            if (e is UnknownHostException) {\n                Toast.makeText(context, \"网络异常\", Toast.LENGTH_SHORT).show()\n            } else if (e is SocketTimeoutException) {\n                Toast.makeText(context, \"请求超时\", Toast.LENGTH_SHORT).show()\n            } else {\n                Toast.makeText(context, e.message, Toast.LENGTH_LONG)\n                    .show()\n            }\n        }\n    }\n\n    private fun inputStream2File(`is`: InputStream, saveFile: File) {\n        var len: Int\n        val buf = ByteArray(2048)\n        val fos = FileOutputStream(saveFile)\n        `is`.use { input ->\n            fos.use { output ->\n                while (input.read(buf).also { len = it } != -1) {\n                    output.write(buf, 0, len)\n                }\n                output.flush()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/http/OkHttpMock.kt",
    "content": "package com.didichuxing.doraemondemo.module.http\n\nimport android.util.Log\nimport com.didichuxing.doraemondemo.old.MainDebugActivityOkhttpV3\nimport com.lzy.okgo.OkGo\nimport com.lzy.okgo.callback.StringCallback\nimport com.lzy.okgo.model.Response\nimport org.json.JSONObject\n\n\n/**\n * didi Create on 2022/5/27 .\n *\n * Copyright (c) 2022/5/27 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/5/27 3:22 下午\n * @Description 用一句话说明文件功能\n */\n\nobject OkHttpMock {\n\n\n    fun test() {\n\n        val jsonObject = JSONObject()\n        jsonObject.put(\"c\", \"cc\")\n        jsonObject.put(\"d\", \"dd\")\n        OkGo.post<String>(\"https://wanandroid.com/user_article/list/0/json?b=bb&a=aa\")\n            .upJson(jsonObject)\n            .execute(object : StringCallback() {\n                override fun onSuccess(response: Response<String>?) {\n                    response?.let {\n                        Log.i(MainDebugActivityOkhttpV3.TAG, \"okhttp====onSuccess===>\" + it.body())\n                    }\n                }\n\n            })\n\n    }\n\n    fun test2() {\n        OkGo.post<String>(\"https://wanandroid.com/user_article/list/0/json?b=bb&a=aa\")\n            .params(\"c\", \"cc\")\n            .params(\"d\", \"dd\")\n            .execute()\n\n    }\n\n\n    fun test3() {\n        OkGo.get<String>(\"https://wanandroid.com/user_article/list/0/json?a=aa&b=bb\")\n            //.upJson(json.toString())\n            .execute(object : StringCallback() {\n                override fun onSuccess(response: Response<String>?) {\n                    response?.let {\n                        Log.i(\n                            MainDebugActivityOkhttpV3.TAG,\n                            \"okhttp====onSuccess===>\" + it.body()\n                        )\n                    }\n                }\n\n                override fun onError(response: Response<String>?) {\n                    response?.let {\n                        Log.i(\n                            MainDebugActivityOkhttpV3.TAG,\n                            \"okhttp====onError===>\" + it.message()\n                        )\n                    }\n                }\n\n            })\n    }\n\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/http/RetrofitMock.kt",
    "content": "package com.didichuxing.doraemondemo.module.http\n\nimport android.util.Log\nimport com.didichuxing.doraemondemo.old.MainDebugActivityOkhttpV3\nimport com.didichuxing.doraemondemo.module.retrofit.GithubService\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.MainScope\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.plus\nimport retrofit2.Retrofit\nimport retrofit2.converter.gson.GsonConverterFactory\nimport retrofit2.converter.scalars.ScalarsConverterFactory\n\nobject RetrofitMock {\n\n\n    private val lifecycleScope = MainScope() + CoroutineName(this.toString())\n\n\n    private val retrofit = Retrofit.Builder()\n        .baseUrl(\"https://api.github.com/\")\n        .addConverterFactory(ScalarsConverterFactory.create())\n        .addConverterFactory(GsonConverterFactory.create())\n        .build()\n\n    /**\n     * github 接口\n     */\n    private var githubService: GithubService? = null\n\n    fun test() {\n        githubService = retrofit.create(GithubService::class.java)\n\n        lifecycleScope.launch {\n            val result = githubService?.githubUserInfo(\"jtsky\")\n            Log.i(MainDebugActivityOkhttpV3.TAG, \"result===>${result}\")\n        }\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/http/URLConnectionMock.kt",
    "content": "package com.didichuxing.doraemondemo.module.http\n\nimport android.util.Log\nimport com.blankj.utilcode.util.ConvertUtils\nimport com.blankj.utilcode.util.ThreadUtils\nimport com.didichuxing.doraemondemo.old.MainDebugActivityOkhttpV3\nimport java.io.IOException\nimport java.net.HttpURLConnection\nimport java.net.URL\n\nobject URLConnectionMock {\n\n    fun get(url: String) {\n        ThreadUtils.executeByIo(object : ThreadUtils.SimpleTask<String?>() {\n            @Throws(Throwable::class)\n            override fun doInBackground(): String {\n                try {\n                    val url = URL(url.trim())\n                    //打开连接\n                    val urlConnection = url.openConnection() as HttpURLConnection\n                    //urlConnection.setRequestProperty(\"token\", \"10051:abc\");\n                    //urlConnection.setRequestProperty(\"Content-type\", \"application/json\");\n                    //int log = urlConnection.getResponseCode();\n                    //得到输入流\n                    val `is` = urlConnection.inputStream\n                    return ConvertUtils.inputStream2String(`is`, \"utf-8\")\n                } catch (e: IOException) {\n                    e.printStackTrace()\n                }\n                return \"error\"\n            }\n\n\n            override fun onSuccess(result: String?) {\n                Log.i(MainDebugActivityOkhttpV3.TAG, \"httpUrlConnection====response===>===>$result\")\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/leak/LeakActivity.kt",
    "content": "package com.didichuxing.doraemondemo.module.leak\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport com.didichuxing.doraemondemo.App\nimport com.didichuxing.doraemondemo.R\n\n/**\n * 模拟内存泄漏的activity\n */\nclass LeakActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_leak)\n        App.leakActivity = this\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/retrofit/GithubService.kt",
    "content": "package com.didichuxing.doraemondemo.module.retrofit\n\nimport retrofit2.http.GET\nimport retrofit2.http.Path\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/14-17:34\n * 描    述：\n * 修订历史：\n * ================================================\n */\ninterface GithubService {\n    @GET(\"users/{user}\")\n    suspend fun githubUserInfo(@Path(\"user\") user: String?): GithubUserInfo\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/module/retrofit/GithubUserInfo.kt",
    "content": "package com.didichuxing.doraemondemo.module.retrofit\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/14-17:40\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class GithubUserInfo(\n        var login: String?,\n        var id: Int?,\n        var node_id: String?,\n        var avatar_url: String?,\n        var gravatar_id: String?,\n        var url: String?,\n        var html_url: String?,\n        var followers_url: String?,\n        var following_url: String?,\n        var gists_url: String?,\n        var starred_url: String?,\n        var subscriptions_url: String?,\n        var organizations_url: String?,\n        var repos_url: String?,\n        var events_url: String?,\n        var received_events_url: String?,\n        var type: String?,\n        var isSite_admin: Boolean,\n        var name: String?,\n        var company: Any?,\n        var blog: String?,\n        var location: String?,\n        var email: Any?,\n        var hireable: Any?,\n        var bio: String?,\n        var public_repos: Int,\n        var public_gists: Int,\n        var followers: Int,\n        var following: Int,\n        var created_at: String?,\n        var updated_at: String?\n)\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/old/BaseActivity.kt",
    "content": "package com.didichuxing.doraemondemo.old\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/3-14:06\n * 描    述：\n * 修订历史：\n * ================================================\n */\nabstract class BaseActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/old/MainAdapter.kt",
    "content": "package com.didichuxing.doraemondemo.old\n\nimport android.widget.Button\nimport androidx.annotation.LayoutRes\nimport com.chad.library.adapter.base.BaseQuickAdapter\nimport com.chad.library.adapter.base.viewholder.BaseViewHolder\nimport com.didichuxing.doraemondemo.R\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/17-11:01\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass MainAdapter(@LayoutRes layoutId: Int, data: MutableList<String>) :\n    BaseQuickAdapter<String, BaseViewHolder>(layoutId, data) {\n    override fun convert(holder: BaseViewHolder, item: String) {\n        holder.getView<Button>(R.id.btn).text = item\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/old/MainDebugActivityOkhttpV3.kt",
    "content": "package com.didichuxing.doraemondemo.old\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.app.ProgressDialog\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.location.Location\nimport android.location.LocationListener\nimport android.location.LocationManager\nimport android.os.*\nimport android.text.format.Formatter\nimport android.util.Log\nimport android.widget.Toast\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.amap.api.location.AMapLocationClient\nimport com.amap.api.location.AMapLocationClientOption\nimport com.amap.api.location.AMapLocationListener\nimport com.amap.api.navi.model.*\nimport com.blankj.utilcode.util.*\nimport com.didichuxing.doraemondemo.*\nimport com.didichuxing.doraemondemo.amap.AMapRouterFragment\nimport com.didichuxing.doraemondemo.comm.CommLauncher\nimport com.didichuxing.doraemondemo.databinding.ActivityMainBinding\nimport com.didichuxing.doraemondemo.db.DatabaseHelper\nimport com.didichuxing.doraemondemo.mc.MCActivity\nimport com.didichuxing.doraemondemo.module.leak.LeakActivity\nimport com.didichuxing.doraemondemo.module.retrofit.GithubService\nimport com.didichuxing.doraemonkit.DoKit\nimport com.lzy.okgo.OkGo\nimport com.lzy.okgo.callback.StringCallback\nimport com.lzy.okgo.model.Response\nimport kotlinx.coroutines.*\nimport okhttp3.*\nimport org.json.JSONObject\nimport pub.devrel.easypermissions.EasyPermissions\nimport pub.devrel.easypermissions.PermissionRequest\nimport retrofit2.Retrofit\nimport retrofit2.converter.gson.GsonConverterFactory\nimport retrofit2.converter.scalars.ScalarsConverterFactory\nimport java.io.*\nimport java.net.*\nimport kotlin.coroutines.resume\n\n/**\n * @author jintai\n */\nclass MainDebugActivityOkhttpV3 : BaseActivity(), CoroutineScope by MainScope() {\n\n    private var okHttpClient: OkHttpClient? = null\n    private var mLocationManager: LocationManager? = null\n\n    private lateinit var mAdapter: MainAdapter\n\n    private val retrofit = Retrofit.Builder()\n        .baseUrl(\"https://api.github.com/\")\n        .addConverterFactory(ScalarsConverterFactory.create())\n        .addConverterFactory(GsonConverterFactory.create())\n        .build()\n\n    /**\n     * github 接口\n     */\n    private var githubService: GithubService? = null\n\n    private var _binding: ActivityMainBinding? = null\n\n\n\n    val datas = mutableListOf(\n        \"显示/隐藏Dokit入口\",\n        \"显示工具面板\",\n        \"弹框测试\",\n        \"系统反射测试\",\n        \"获取已安装的app\",\n        \"截屏\",\n        \"跳转其他Activity\",\n        \"一机多控\",\n        \"NormalWebView\",\n        \"X5WebView\",\n        \"模拟内存泄漏\",\n        \"函数调用耗时(TAG:MethodCostUtil)\",\n        \"获取位置信息(系统)\",\n        \"获取位置信息(高德)\",\n        \"高德路径规划\",\n        \"OkHttp Mock\",\n        \"HttpURLConnection Mock\",\n        \"Retrofit Mock\",\n        \"模拟Crash\",\n        \"创建数据库\",\n        \"上传文件\",\n        \"下载文件\"\n    )\n\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        _binding = ActivityMainBinding.inflate(layoutInflater).also {\n            setContentView(it.root)\n            mAdapter = MainAdapter(R.layout.item_main_rv, datas)\n            it.initView(this)\n        }\n        mAdapter.setOnItemClickListener { _, _, position ->\n            when (datas[position]) {\n                \"弹框测试\" -> {\n//                    val bundle = Bundle()\n//                    bundle.putString(\"text\", \"测试同步异常\")\n//                    DoKit.launchFloating<McDialogDoKitView>(bundle = bundle)\n\n//                    lifecycleScope.launch {\n//                        delay(15000)\n//                        Log.i(TAG, \"===inner===\")\n//                    }\n//                    Log.i(TAG, \"===out===\")\n\n                    startActivity(Intent(this, MainDoKitActivity::class.java))\n\n                }\n                \"系统反射测试\" -> {\n                    try {\n                        val activityClass = Class.forName(\"dalvik.system.VMRuntime\")\n                        val field = activityClass.getDeclaredMethod(\"setHiddenApiExemptions\", Array<String>::class.java)\n                        field.isAccessible = true\n                        Toast.makeText(this, \"call success!!\", Toast.LENGTH_SHORT).show()\n                    } catch (e: Throwable) {\n                        Log.e(TAG, \"error:\", e)\n                        Toast.makeText(this, \"error: $e\", Toast.LENGTH_SHORT).show()\n                    }\n                }\n                \"显示/隐藏Dokit入口\" -> {\n                    if (DoKit.isMainIconShow) {\n                        DoKit.hide()\n                    } else {\n                        DoKit.show()\n                    }\n\n                }\n                \"显示工具面板\" -> {\n                    DoKit.showToolPanel()\n                }\n                \"获取已安装的app\" -> {\n                    packageManager.getInstalledApplications(\n                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) PackageManager.MATCH_UNINSTALLED_PACKAGES\n                        else PackageManager.GET_UNINSTALLED_PACKAGES\n                    )\n                    packageManager.getInstalledApplications(PackageManager.MATCH_UNINSTALLED_PACKAGES)\n                }\n                \"截屏\" -> {\n//                    lifecycleScope.launch {\n//\n//                        DoKit.launchFloating(BorderDoKitView::class.java)\n//                        val borderDoKitView =\n//                            DoKit.getDoKitView(\n//                                this@MainDebugActivityOkhttpV3,\n//                                BorderDoKitView::class.java\n//                            ) as BorderDoKitView\n//                        borderDoKitView.showBorder(view)\n//                        withContext(Dispatchers.IO) {\n//                            delay(200)\n//                            val bitmap = ScreenUtils.screenShot(this@MainDebugActivityOkhttpV3)\n//                            val output = File(getCrashCacheDir(), \"test.png\")\n//                            DoKitImageUtil.bitmap2File(bitmap, 100, output)\n//                        }\n//\n//                    }\n\n                }\n                \"跳转其他Activity\" -> {\n                    startActivity(Intent(this, EmptyActivity::class.java))\n                }\n                \"一机多控\" -> {\n                    startActivity(Intent(this, MCActivity::class.java))\n                }\n                \"NormalWebView\" -> {\n                    startActivity(Intent(this, WebViewSystemActivity::class.java))\n                }\n                \"X5WebView\" -> {\n                    startActivity(Intent(this, WebViewX5Activity::class.java))\n                }\n\n                \"模拟内存泄漏\" -> {\n                    startActivity(Intent(this, LeakActivity::class.java))\n                }\n\n                \"函数调用耗时(TAG:MethodCostUtil)\" -> {\n                    test1()\n                }\n                \"获取位置信息(系统)\" -> {\n                    startNormaLocation()\n                }\n                \"获取位置信息(高德)\" -> {\n                    startAMapLocation()\n                }\n                \"高德路径规划\" -> {\n                    CommLauncher.startActivity(AMapRouterFragment::class.java, this)\n                }\n                \"OkHttp Mock\" -> {\n                    val jsonObject = JSONObject()\n                    jsonObject.put(\"c\", \"cc\")\n                    jsonObject.put(\"d\", \"dd\")\n                    OkGo.post<String>(\"https://wanandroid.com/user_article/list/0/json?b=bb&a=aa\")\n                        .upJson(jsonObject)\n                        .execute(object : StringCallback() {\n                            override fun onSuccess(response: Response<String>?) {\n                                response?.let {\n                                    Log.i(TAG, \"okhttp====onSuccess===>\" + it.body())\n                                }\n                            }\n\n                        })\n\n\n//                    OkGo.post<String>(\"https://wanandroid.com/user_article/list/0/json?b=bb&a=aa\")\n//                        .params(\"c\", \"cc\")\n//                        .params(\"d\", \"dd\")\n//                        .execute()\n//                    OkGo.get<String>(\"https://wanandroid.com/user_article/list/0/json?a=aa&b=bb\")\n//                        //.upJson(json.toString())\n//                        .execute(object : StringCallback() {\n//                            override fun onSuccess(response: Response<String>?) {\n//                                response?.let {\n//                                    Log.i(\n//                                        MainDebugActivityOkhttpV3.TAG,\n//                                        \"okhttp====onSuccess===>\" + it.body()\n//                                    )\n//                                }\n//                            }\n//\n//                            override fun onError(response: Response<String>?) {\n//                                response?.let {\n//                                    Log.i(\n//                                        MainDebugActivityOkhttpV3.TAG,\n//                                        \"okhttp====onError===>\" + it.message()\n//                                    )\n//                                }\n//                            }\n//\n//                        })\n                }\n                \"HttpURLConnection Mock\" -> {\n                    requestByGet(\"https://wanandroid.com/user_article/list/0/json\")\n                }\n                \"Retrofit Mock\" -> {\n                    lifecycleScope.launch {\n                        val result = githubService?.githubUserInfo(\"jtsky\")\n                        Log.i(TAG, \"result===>${result}\")\n                    }\n\n                }\n                \"模拟Crash\" -> {\n                    checkNotNull(testCrash())\n                }\n                \"创建数据库\" -> {\n                    val dbHelper = DatabaseHelper(this, \"BookStore.db\", null, 1)\n                    dbHelper.writableDatabase\n                    dbHelper.close()\n                }\n                \"上传文件\" -> {\n                    requestByFile(true)\n                }\n                \"下载文件\" -> {\n                    requestByFile(false)\n                }\n                else -> {\n                }\n            }\n        }\n\n        okHttpClient = OkHttpClient().newBuilder().build()\n        //获取定位服务\n        mLocationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager\n        //高德定位服务\n        EasyPermissions.requestPermissions(\n            PermissionRequest.Builder(\n                this, 200,\n                Manifest.permission.ACCESS_FINE_LOCATION,\n                Manifest.permission.ACCESS_COARSE_LOCATION,\n                Manifest.permission.READ_EXTERNAL_STORAGE,\n                Manifest.permission.WRITE_EXTERNAL_STORAGE\n            ).build()\n        )\n\n\n        githubService = retrofit.create(GithubService::class.java)\n    }\n\n    private fun ActivityMainBinding.initView(context: Context) {\n        tvEnv.text = \"${getString(R.string.app_build_types)}:Debug\"\n        rv.layoutManager = LinearLayoutManager(context)\n        rv.adapter = mAdapter\n    }\n\n    private fun test1() {\n        try {\n            Thread.sleep(1000)\n        } catch (e: InterruptedException) {\n            e.printStackTrace()\n        }\n        test2()\n    }\n\n    private fun test2() {\n        try {\n            Thread.sleep(200)\n        } catch (e: InterruptedException) {\n            e.printStackTrace()\n        }\n        test3()\n    }\n\n    private fun test3() {\n        try {\n            Thread.sleep(200)\n        } catch (e: InterruptedException) {\n            e.printStackTrace()\n        }\n        test4()\n    }\n\n    private fun test4() {\n        try {\n            Thread.sleep(200)\n        } catch (e: InterruptedException) {\n            e.printStackTrace()\n        }\n    }\n\n    private val mLocationListener: LocationListener = object : LocationListener {\n        override fun onLocationChanged(location: Location) {\n            val string = \"lat====>\" + location.latitude + \"  lng====>\" + location.longitude\n            Log.i(TAG, \"系统定位====>$string\")\n        }\n\n        override fun onProviderDisabled(arg0: String) {}\n        override fun onProviderEnabled(arg0: String) {}\n        override fun onStatusChanged(arg0: String, arg1: Int, arg2: Bundle) {}\n    }\n\n    /**\n     * 启动普通定位\n     */\n    @SuppressLint(\"MissingPermission\")\n    private fun startNormaLocation() {\n        mLocationManager!!.requestLocationUpdates(\n            LocationManager.NETWORK_PROVIDER,\n            0,\n            0f,\n            mLocationListener\n        )\n    }\n\n    /**\n     * 启动高德定位\n     */\n    private fun startAMapLocation() {\n        // 确保调用SDK任何接口前先调用更新隐私合规updatePrivacyShow、updatePrivacyAgree两个接口并且参数值都为true，若未正确设置有崩溃风险\n        AMapLocationClient.updatePrivacyShow(this, true, true)\n        AMapLocationClient.updatePrivacyAgree(this, true)\n\n        //声明mLocationOption对象\n        var mLocationOption: AMapLocationClientOption? = null\n        val mlocationClient = AMapLocationClient(this)\n\n        //初始化定位参数\n        mLocationOption = AMapLocationClientOption()\n        //设置定位监听\n        mlocationClient!!.setLocationListener(mapLocationListener)\n        //设置定位模式为高精度模式，Battery_Saving为低功耗模式，Device_Sensors是仅设备模式\n        mLocationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy\n        //设置定位间隔,单位毫秒,默认为2000ms\n        mLocationOption.interval = 2000\n        //设置定位参数\n        mlocationClient!!.setLocationOption(mLocationOption)\n        // 此方法为每隔固定时间会发起一次定位请求，为了减少电量消耗或网络流量消耗，\n        // 注意设置合适的定位时间的间隔（最小间隔支持为1000ms），并且在合适时间调用stopLocation()方法来取消定位请求\n        // 在定位结束后，在合适的生命周期调用onDestroy()方法\n        // 在单次定位情况下，定位无论成功与否，都无需调用stopLocation()方法移除请求，定位sdk内部会移除\n        //启动定位\n        mlocationClient!!.startLocation()\n    }\n\n    /**\n     * 启动高德定位服务\n     */\n    private var mapLocationListener = AMapLocationListener { aMapLocation ->\n        val errorCode = aMapLocation.errorCode\n        val errorInfo = aMapLocation.errorInfo\n        Log.i(\n            TAG,\n            \"高德定位===lat==>\" + aMapLocation.latitude + \"   lng==>\" + aMapLocation.longitude + \"  errorCode===>\" + errorCode + \"   errorInfo===>\" + errorInfo\n        )\n    }\n\n\n    private fun testCrash(): String? {\n        return null\n    }\n\n    private fun requestByGet(path: String) {\n        ThreadUtils.executeByIo(object : ThreadUtils.SimpleTask<String?>() {\n            @Throws(Throwable::class)\n            override fun doInBackground(): String {\n                try {\n                    val url = URL(path.trim())\n                    //打开连接\n                    val urlConnection = url.openConnection() as HttpURLConnection\n                    //urlConnection.setRequestProperty(\"token\", \"10051:abc\");\n                    //urlConnection.setRequestProperty(\"Content-type\", \"application/json\");\n                    //int log = urlConnection.getResponseCode();\n                    //得到输入流\n                    val `is` = urlConnection.inputStream\n                    return ConvertUtils.inputStream2String(`is`, \"utf-8\")\n                } catch (e: IOException) {\n                    e.printStackTrace()\n                }\n                return \"error\"\n            }\n\n\n            override fun onSuccess(result: String?) {\n                Log.i(TAG, \"httpUrlConnection====response===>===>$result\")\n            }\n        })\n    }\n\n\n    /**\n     * 模拟上传或下载文件\n     *\n     * @param upload true上传 false下载\n     */\n    private fun requestByFile(upload: Boolean) {\n        val dialog = ProgressDialog.show(this, null, null)\n        dialog.setCancelable(true)\n        var request: Request? = null\n        if (upload) {\n            try {\n                //模拟一个1M的文件用来上传\n                val length = 1L * 1024 * 1024\n                val temp = File(filesDir, \"test.tmp\")\n                if (!temp.exists() || temp.length() != length) {\n                    val accessFile = RandomAccessFile(temp, \"rwd\")\n                    accessFile.setLength(length)\n                    temp.createNewFile()\n                }\n                request = Request.Builder()\n                    .post(RequestBody.create(MediaType.parse(temp.name), temp))\n                    .url(\"http://wallpaper.apc.360.cn/index.php?c=WallPaper&a=getAppsByOrder&order=create_time&start=0&count=1&from=360chrome\")\n                    .build()\n            } catch (e: IOException) {\n                e.printStackTrace()\n            }\n        } else {\n            //下载一个2M的文件\n            request = Request.Builder()\n                .get()\n                .url(\"http://cdn1.lbesec.com/products/history/20131220/privacyspace_rel_2.2.1617.apk\")\n                .build()\n        }\n        val call = okHttpClient!!.newCall(request!!)\n        val startTime = SystemClock.uptimeMillis()\n        call.enqueue(object : Callback {\n            override fun onFailure(call: Call, e: IOException) {\n                dialog.cancel()\n                onHttpFailure(e)\n            }\n\n            @Throws(IOException::class)\n            override fun onResponse(call: Call, response: okhttp3.Response) {\n                if (!response.isSuccessful) {\n                    onFailure(call, IOException(response.message()))\n                    return\n                }\n                val body = response.body()\n                if (!upload) {\n                    inputStream2File(body!!.byteStream(), File(filesDir, \"test.apk\"))\n                }\n                dialog.cancel()\n                val requestLength = if (upload) call.request().body()!!.contentLength() else 0\n                val responseLength = if (body!!.contentLength() < 0) 0 else body.contentLength()\n                val endTime = SystemClock.uptimeMillis() - startTime\n                val speed = (if (upload) requestLength else responseLength) / endTime * 1000\n                val message = String.format(\n                    \"请求大小：%s，响应大小：%s，耗时：%dms，均速：%s/s\",\n                    Formatter.formatFileSize(applicationContext, requestLength),\n                    Formatter.formatFileSize(applicationContext, responseLength),\n                    endTime,\n                    Formatter.formatFileSize(applicationContext, speed)\n                )\n                runOnUiThread {\n                    Log.d(\"onResponse\", message)\n                    Toast.makeText(this@MainDebugActivityOkhttpV3, message, Toast.LENGTH_LONG)\n                        .show()\n                }\n            }\n        })\n    }\n\n    private fun onHttpFailure(e: IOException) {\n        e.printStackTrace()\n        runOnUiThread {\n            if (e is UnknownHostException) {\n                Toast.makeText(this@MainDebugActivityOkhttpV3, \"网络异常\", Toast.LENGTH_SHORT).show()\n            } else if (e is SocketTimeoutException) {\n                Toast.makeText(this@MainDebugActivityOkhttpV3, \"请求超时\", Toast.LENGTH_SHORT).show()\n            } else {\n                Toast.makeText(this@MainDebugActivityOkhttpV3, e.message, Toast.LENGTH_LONG)\n                    .show()\n            }\n        }\n    }\n\n    private fun inputStream2File(`is`: InputStream, saveFile: File) {\n        var len: Int\n        val buf = ByteArray(2048)\n        val fos = FileOutputStream(saveFile)\n        `is`.use { input ->\n            fos.use { output ->\n                while (input.read(buf).also { len = it } != -1) {\n                    output.write(buf, 0, len)\n                }\n                output.flush()\n            }\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        okHttpClient!!.dispatcher().cancelAll()\n        mLocationManager!!.removeUpdates(mLocationListener)\n    }\n\n    companion object {\n        const val TAG = \"MainDebugActivity\"\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/old/MainDebugActivityOkhttpV4.kt",
    "content": "package com.didichuxing.doraemondemo.old//package com.didichuxing.doraemondemo\n//\n//import android.Manifest\n//import android.annotation.SuppressLint\n//import android.app.ProgressDialog\n//import android.content.Context\n//import android.content.Intent\n//import android.graphics.Bitmap\n//import android.graphics.BitmapFactory\n//import android.location.Location\n//import android.location.LocationListener\n//import android.location.LocationManager\n//import android.net.Uri\n//import android.os.*\n//import android.text.format.Formatter\n//import android.util.Log\n//import android.view.View\n//import android.widget.ImageView\n//import android.widget.TextView\n//import android.widget.Toast\n//import com.amap.api.location.AMapLocationClient\n//import com.amap.api.location.AMapLocationClientOption\n//import com.amap.api.location.AMapLocationListener\n//import com.baidu.location.BDAbstractLocationListener\n//import com.baidu.location.BDLocation\n//import com.baidu.location.LocationClient\n//import com.baidu.location.LocationClientOption\n//import com.didichuxing.doraemonkit.util.ConvertUtils\n//import com.didichuxing.doraemonkit.util.ThreadUtils\n//import com.didichuxing.doraemonkit.util.ThreadUtils.SimpleTask\n//import com.bumptech.glide.Glide\n//import com.bumptech.glide.load.engine.DiskCacheStrategy\n//import com.bumptech.glide.load.resource.bitmap.CircleCrop\n//import com.didichuxing.doraemondemo.module.retrofit.GithubService\n//import com.didichuxing.doraemonkit.DoraemonKit\n//import com.facebook.drawee.backends.pipeline.Fresco\n//import com.facebook.drawee.view.SimpleDraweeView\n//import com.lzy.okgo.OkGo\n//import com.lzy.okgo.callback.StringCallback\n//import com.lzy.okgo.model.Response\n//import com.nostra13.universalimageloader.core.ImageLoader\n//import com.nostra13.universalimageloader.core.ImageLoaderConfiguration\n//import com.squareup.picasso.MemoryPolicy\n//import com.squareup.picasso.Picasso\n//import io.reactivex.schedulers.Schedulers\n//import kotlinx.android.synthetic.main.activity_main.*\n//import okhttp3.*\n//import okhttp3.MediaType.Companion.toMediaTypeOrNull\n//import org.json.JSONObject\n//import pub.devrel.easypermissions.EasyPermissions\n//import pub.devrel.easypermissions.PermissionRequest\n//import retrofit2.Retrofit\n//import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory\n//import retrofit2.converter.gson.GsonConverterFactory\n//import java.io.*\n//import java.net.*\n//\n///**\n// * @author jintai\n// */\n//class MainDebugActivityOkhttpV4 : BaseActivity(), View.OnClickListener {\n//    private var okHttpClient: OkHttpClient? = null\n//    private var mLocationManager: LocationManager? = null\n//    private val UPDATE_UI = 100\n//\n//    private val retrofit = Retrofit.Builder()\n//        .baseUrl(\"https://api.github.com/\")\n//        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())\n//        .addConverterFactory(GsonConverterFactory.create())\n//        .build()\n//\n//    /**\n//     * github 接口\n//     */\n//    private var githubService: GithubService? = null\n//\n//    @SuppressLint(\"HandlerLeak\")\n//    private val mHandler: Handler = object : Handler() {\n//        override fun handleMessage(msg: Message) {\n//            super.handleMessage(msg)\n//            when (msg.what) {\n//                100 -> (findViewById<View>(R.id.iv_picasso) as ImageView).setImageBitmap(msg.obj as Bitmap)\n//                else -> {\n//                }\n//            }\n//        }\n//    }\n//\n//    override fun onCreate(savedInstanceState: Bundle?) {\n//        super.onCreate(savedInstanceState)\n//        setContentView(R.layout.activity_main)\n//        val tvEnv = findViewById<TextView>(R.id.tv_env)\n//        tvEnv.text = \"${getString(R.string.app_build_types)}:Debug\"\n//        btn_jump.setOnClickListener(this)\n//        btn_webview.setOnClickListener(this)\n//        btn_x5_webview.setOnClickListener(this)\n//        findViewById<View>(R.id.btn_method_cost).setOnClickListener(this)\n//        findViewById<View>(R.id.btn_jump_leak).setOnClickListener(this)\n//        findViewById<View>(R.id.btn_app_launch_stack).setOnClickListener(this)\n//        findViewById<View>(R.id.btn_show_tool_panel).setOnClickListener(this)\n//        findViewById<View>(R.id.btn_location).setOnClickListener(this)\n//        findViewById<View>(R.id.btn_load_img).setOnClickListener(this)\n//        findViewById<View>(R.id.btn_okhttp_mock).setOnClickListener(this)\n//        findViewById<View>(R.id.btn_connection_mock).setOnClickListener(this)\n//        //        findViewById(R.id.btn_rpc_mock).setOnClickListener(this);\n//        btn_retrofit_mock.setOnClickListener(this)\n//        findViewById<View>(R.id.btn_test_crash).setOnClickListener(this)\n//        findViewById<View>(R.id.btn_show_hide_icon).setOnClickListener(this)\n//        findViewById<View>(R.id.btn_create_database).setOnClickListener(this)\n//        findViewById<View>(R.id.btn_upload_test).setOnClickListener(this)\n//        findViewById<View>(R.id.btn_download_test).setOnClickListener(this)\n//        okHttpClient = OkHttpClient().newBuilder().build()\n//        //获取定位服务\n//        mLocationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager\n//        //获取获取当前单次定位\n//        EasyPermissions.requestPermissions(\n//            PermissionRequest.Builder(\n//                this, 200,\n//                Manifest.permission.ACCESS_FINE_LOCATION,\n//                Manifest.permission.ACCESS_COARSE_LOCATION,\n//                Manifest.permission.READ_EXTERNAL_STORAGE,\n//                Manifest.permission.WRITE_EXTERNAL_STORAGE\n//            ).build()\n//        )\n//        //初始化\n//        val config = ImageLoaderConfiguration.Builder(this)\n//            .build()\n//        ImageLoader.getInstance().init(config)\n//\n//        githubService = retrofit.create(GithubService::class.java)\n//\n//    }\n//\n//    private fun test1() {\n//        try {\n//            Thread.sleep(1000)\n//        } catch (e: InterruptedException) {\n//            e.printStackTrace()\n//        }\n//        test2()\n//    }\n//\n//    private fun test2() {\n//        try {\n//            Thread.sleep(200)\n//        } catch (e: InterruptedException) {\n//            e.printStackTrace()\n//        }\n//        test3()\n//    }\n//\n//    private fun test3() {\n//        try {\n//            Thread.sleep(200)\n//        } catch (e: InterruptedException) {\n//            e.printStackTrace()\n//        }\n//        test4()\n//    }\n//\n//    private fun test4() {\n//        try {\n//            Thread.sleep(200)\n//        } catch (e: InterruptedException) {\n//            e.printStackTrace()\n//        }\n//    }\n//\n//    private val mLocationListener: LocationListener = object : LocationListener {\n//        override fun onLocationChanged(location: Location) {\n//            val string = \"lat====>\" + location.latitude + \"  lng====>\" + location.longitude\n//            Log.i(TAG, \"系统定位====>$string\")\n//        }\n//\n//        override fun onProviderDisabled(arg0: String) {}\n//        override fun onProviderEnabled(arg0: String) {}\n//        override fun onStatusChanged(arg0: String, arg1: Int, arg2: Bundle) {}\n//    }\n//\n//    /**\n//     * 启动普通定位\n//     */\n//    @SuppressLint(\"MissingPermission\")\n//    private fun startNormaLocation() {\n//        mLocationManager!!.requestLocationUpdates(\n//            LocationManager.NETWORK_PROVIDER,\n//            0,\n//            0f,\n//            mLocationListener\n//        )\n//    }\n//\n//\n//\n//\n//\n//\n//    override fun onClick(v: View) {\n//        when (v.id) {\n//            R.id.btn_method_cost -> test1()\n//            R.id.btn_show_tool_panel ->                 //直接调起工具面板\n//                DoraemonKit.showToolPanel()\n//            R.id.btn_jump -> startActivity(Intent(this, SecondActivity::class.java))\n//            R.id.btn_webview -> startActivity(Intent(this, WebViewNormalActivity::class.java))\n//            R.id.btn_x5_webview -> startActivity(Intent(this, WebViewX5Activity::class.java))\n//            R.id.btn_jump_leak -> startActivity(Intent(this, LeakActivity::class.java))\n//            R.id.btn_app_launch_stack -> {\n//                //MethodStackUtil.getInstance().toJson()\n//            }\n//            R.id.btn_location -> startNormaLocation()\n//            R.id.btn_load_img -> {\n//                //Glide 加载\n//                val picassoImgUrl =\n//                    \"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1585832555614&di=ea70ed1254b3242803d7dde56eedfe9f&imgtype=0&src=http%3A%2F%2Ft9.baidu.com%2Fit%2Fu%3D2268908537%2C2815455140%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D719\"\n//                val glideImageUrl =\n//                    \"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1584969662890&di=bc7b18d8b4efa73fb88ddef4f6f56acc&imgtype=0&src=http%3A%2F%2Ft9.baidu.com%2Fit%2Fu%3D583874135%2C70653437%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D3607%26h%3D2408\"\n//                val frescoImageUrl =\n//                    \"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1584969662890&di=09318a918fe9ea73a8e27c80291bf669&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D1484500186%2C1503043093%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853\"\n//                val imageLoaderImageUrl =\n//                    \"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1584969662891&di=acaf549645e58b6c67c231d495e18271&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D3571592872%2C3353494284%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1200%26h%3D1290\"\n//                Picasso.get().load(picassoImgUrl)\n//                    .memoryPolicy(MemoryPolicy.NO_CACHE)\n//                    .placeholder(R.mipmap.dk_health_bg)\n//                    .error(R.mipmap.dk_health_bg)\n//                    .into(findViewById<View>(R.id.iv_picasso) as ImageView)\n//                Glide.with(this@MainDebugActivityOkhttpV4)\n//                    .asBitmap()\n//                    .load(glideImageUrl)\n//                    .placeholder(R.mipmap.dk_health_bg)\n//                    .error(R.mipmap.dk_health_bg)\n//                    .diskCacheStrategy(DiskCacheStrategy.NONE)\n//                    .skipMemoryCache(true)\n//                    .transform(CircleCrop())\n//                    .into((findViewById<View>(R.id.iv_glide) as ImageView))\n//                val imageLoader = ImageLoader.getInstance()\n//                imageLoader.displayImage(\n//                    imageLoaderImageUrl,\n//                    findViewById<View>(R.id.iv_imageloader) as ImageView\n//                )\n//                val frescoImageView = findViewById<SimpleDraweeView>(R.id.iv_fresco)\n//                frescoImageView.setImageURI(Uri.parse(frescoImageUrl))\n//                val imagePipeline = Fresco.getImagePipeline()\n//                // combines above two lines\n//                imagePipeline.clearCaches()\n//            }\n//            R.id.btn_okhttp_mock -> {\n//                //DokitOkGo.<String>get(\"https://www.tianqiapi.com/api\")\n////                DokitOkGo.post<String>(\"http://www.v2ex.com/api/topics/hot.json?aaa=aaa&bbb=bbb\")\n////                        .params(\"ccc\", \"ccc\")\n////                        .params(\"ddd\", \"ddd\")\n////                        .execute(object : StringCallback() {\n////                            override fun onSuccess(response: Response<String>) {\n////                                Log.i(TAG, \"okhttp====onSuccess===>\" + response.body())\n////                            }\n////\n////                            override fun onError(response: Response<String>) {\n////                                Log.i(TAG, \"okhttp====onError===>\" + response.message())\n////                            }\n////                        })\n//\n//                val json = JSONObject()\n//                //json\n//                OkGo.get<String>(\"https://wanandroid.com/user_article/list/0/json\")\n//                    //.upJson(json.toString())\n//                    .execute(object : StringCallback() {\n//                        override fun onSuccess(response: Response<String>?) {\n//                            response?.let {\n//                                Log.i(TAG, \"okhttp====onSuccess===>\" + it.body())\n//                            }\n//                        }\n//\n//                        override fun onError(response: Response<String>?) {\n//                            response?.let {\n//                                Log.i(TAG, \"okhttp====onError===>\" + it.message())\n//                            }\n//                        }\n//\n//                    })\n//            }\n//\n//\n//            R.id.btn_connection_mock -> {\n//                //requestByGet(\"https://www.v2ex.com/api/topics/hot.json\");\n//                //requestByGet(\"https://gank.io/api/today?a=哈哈&b=bb\")\n//                requestByGet(\"https://wanandroid.com/user_article/list/0/json\")\n//                //requestByGet(\"https://ant.pingan.com.cn/c.gif?title=非车险产品dock&url=http://com.pingan.lifecircle/feiche_AllProductPage&eventName=pageview&refererUrlTime=400&referer=http://com.pingan.lifecircle/personal_MainPage&siteId=QRM79jeIRrN2B565&userProperty=%7B\\\"userIdType\\\":\\\"aopsId\\\"%7D&resolution=1080*2340&userAgent=Dalvik/2.1.0(Linux;U;Android9;RedmiNote8ProMIUI/V11.0.6.0.PGGCNXM)&language=zh&apiv=2&seStartTime=1592363736562&eventTime=1592363794558&userId=138068825&netType=4G&customerVar=%7B\\\"sdkVersion\\\":\\\"2.2.8.6\\\",\\\"macAddress\\\":\\\"A4:45:19:38:4A:5A\\\",\\\"gpsFlag\\\":\\\"0\\\",\\\"imei\\\":\\\"\\\"%7D&uaOs=Android&uaOsMajor=&uaOsMinor=&uaDevice=RedmiNote8Pro&downloadChannel=ch1&appVersion=1.13.1&deviceId=b69a044c294af87f&sessionId=8416a37db255daa4&projectId=OCUTeffvo02r7nmj\")\n//            }\n//            R.id.btn_retrofit_mock -> {\n//                githubService?.githubUserInfo(\"jtsky\")\n//                    ?.subscribeOn(Schedulers.io())\n//                    ?.subscribe(\n//                        { Log.i(TAG, \"githubUserInfo===>${it.login}\") },\n//                        { Log.e(TAG, \"Request failed by retrofit mock\", it) }\n//                    )\n//            }\n//            R.id.btn_test_crash -> testCrash()!!.length\n//            R.id.btn_show_hide_icon -> if (DoraemonKit.isShow) {\n//                DoraemonKit.hide()\n//            } else {\n//                DoraemonKit.show()\n//            }\n//            R.id.btn_create_database -> {\n//                val dbHelper = MyDatabaseHelper(this, \"BookStore.db\", null, 1)\n//                dbHelper.writableDatabase\n//                dbHelper.close()\n//            }\n//            R.id.btn_upload_test -> requestByFile(true)\n//            R.id.btn_download_test -> requestByFile(false)\n//            else -> {\n//            }\n//        }\n//    }\n//\n//    private fun testCrash(): String? {\n//        return null\n//    }\n//\n//    private fun requestByGet(path: String) {\n//        ThreadUtils.executeByIo(object : SimpleTask<String?>() {\n//            @Throws(Throwable::class)\n//            override fun doInBackground(): String {\n//                try {\n//                    val url = URL(path.trim())\n//                    //打开连接\n//                    val urlConnection = url.openConnection() as HttpURLConnection\n//                    //urlConnection.setRequestProperty(\"token\", \"10051:abc\");\n//                    //urlConnection.setRequestProperty(\"Content-type\", \"application/json\");\n//                    //int log = urlConnection.getResponseCode();\n//                    //得到输入流\n//                    val `is` = urlConnection.inputStream\n//                    return ConvertUtils.inputStream2String(`is`, \"utf-8\")\n//                } catch (e: IOException) {\n//                    e.printStackTrace()\n//                }\n//                return \"error\"\n//            }\n//\n//\n//            override fun onSuccess(result: String?) {\n//                Log.i(TAG, \"httpUrlConnection====response===>===>$result\")\n//            }\n//        })\n//    }\n//\n//\n//    /**\n//     * 模拟上传或下载文件\n//     *\n//     * @param upload true上传 false下载\n//     */\n//    private fun requestByFile(upload: Boolean) {\n//        val dialog = ProgressDialog.show(this, null, null)\n//        dialog.setCancelable(true)\n//        var request: Request? = null\n//        if (upload) {\n//            try {\n//                //模拟一个1M的文件用来上传\n//                val length = 1L * 1024 * 1024\n//                val temp = File(filesDir, \"test.tmp\")\n//                if (!temp.exists() || temp.length() != length) {\n//                    val accessFile = RandomAccessFile(temp, \"rwd\")\n//                    accessFile.setLength(length)\n//                    temp.createNewFile()\n//                }\n//                request = Request.Builder()\n//                    .post(RequestBody.create(temp.name.toMediaTypeOrNull(), temp))\n//                    .url(\"http://wallpaper.apc.360.cn/index.php?c=WallPaper&a=getAppsByOrder&order=create_time&start=0&count=1&from=360chrome\")\n//                    .build()\n//            } catch (e: IOException) {\n//                e.printStackTrace()\n//            }\n//        } else {\n//            //下载一个2M的文件\n//            request = Request.Builder()\n//                .get()\n//                .url(\"http://cdn1.lbesec.com/products/history/20131220/privacyspace_rel_2.2.1617.apk\")\n//                .build()\n//        }\n//        val call = okHttpClient!!.newCall(request!!)\n//        val startTime = SystemClock.uptimeMillis()\n//        call.enqueue(object : Callback {\n//            override fun onFailure(call: Call, e: IOException) {\n//                dialog.cancel()\n//                onHttpFailure(e)\n//            }\n//\n//            @Throws(IOException::class)\n//            override fun onResponse(call: Call, response: okhttp3.Response) {\n//                if (!response.isSuccessful) {\n//                    onFailure(call, IOException(response.message))\n//                    return\n//                }\n//                val body = response.body\n//                if (!upload) {\n//                    inputStream2File(body!!.byteStream(), File(filesDir, \"test.apk\"))\n//                }\n//                dialog.cancel()\n//                val requestLength = if (upload) call.request().body!!.contentLength() else 0\n//                val responseLength = if (body!!.contentLength() < 0) 0 else body.contentLength()\n//                val endTime = SystemClock.uptimeMillis() - startTime\n//                val speed = (if (upload) requestLength else responseLength) / endTime * 1000\n//                val message = String.format(\n//                    \"请求大小：%s，响应大小：%s，耗时：%dms，均速：%s/s\",\n//                    Formatter.formatFileSize(applicationContext, requestLength),\n//                    Formatter.formatFileSize(applicationContext, responseLength),\n//                    endTime,\n//                    Formatter.formatFileSize(applicationContext, speed)\n//                )\n//                runOnUiThread {\n//                    Log.d(\"onResponse\", message)\n//                    Toast.makeText(this@MainDebugActivityOkhttpV4, message, Toast.LENGTH_LONG)\n//                        .show()\n//                }\n//            }\n//        })\n//    }\n//\n//    private fun onHttpFailure(e: IOException) {\n//        e.printStackTrace()\n//        runOnUiThread {\n//            if (e is UnknownHostException) {\n//                Toast.makeText(this@MainDebugActivityOkhttpV4, \"网络异常\", Toast.LENGTH_SHORT).show()\n//            } else if (e is SocketTimeoutException) {\n//                Toast.makeText(this@MainDebugActivityOkhttpV4, \"请求超时\", Toast.LENGTH_SHORT).show()\n//            } else {\n//                Toast.makeText(this@MainDebugActivityOkhttpV4, e.message, Toast.LENGTH_LONG).show()\n//            }\n//        }\n//    }\n//\n//    private fun inputStream2File(`is`: InputStream, saveFile: File) {\n//        try {\n//            var len: Int\n//            val buf = ByteArray(2048)\n//            val fos = FileOutputStream(saveFile)\n//            while (`is`.read(buf).also { len = it } != -1) {\n//                fos.write(buf, 0, len)\n//            }\n//            fos.flush()\n//            fos.close()\n//            `is`.close()\n//        } catch (e: Exception) {\n//            e.printStackTrace()\n//        }\n//    }\n//\n//    override fun onDestroy() {\n//        super.onDestroy()\n//        okHttpClient!!.dispatcher.cancelAll()\n//        mLocationManager!!.removeUpdates(mLocationListener)\n//    }\n//\n//    private fun requestImage(urlStr: String) {\n//        try {\n//            //\n//            val url = URL(urlStr)\n//            // http    https\n//            // ftp\n//            val urlConnection = url.openConnection() as HttpURLConnection\n//            //http get post\n//            urlConnection.requestMethod = \"GET\"\n//            urlConnection.connectTimeout = 5000\n//            urlConnection.readTimeout = 5000\n//            val responseCode = urlConnection.responseCode\n//            if (responseCode == 200) {\n//                val bitmap = BitmapFactory.decodeStream(urlConnection.inputStream)\n//                //更新 ui\n//                mHandler.sendMessage(mHandler.obtainMessage(UPDATE_UI, bitmap))\n//            }\n//        } catch (e: MalformedURLException) {\n//            e.printStackTrace()\n//        } catch (e: IOException) {\n//            e.printStackTrace()\n//        }\n//    }\n//\n//    companion object {\n//        const val TAG = \"MainDebugActivity\"\n//\n//    }\n//}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/old/MapActivity.kt",
    "content": "//package com.didichuxing.doraemondemo\n//\n//import android.location.Location\n//import android.os.Bundle\n//import android.os.Looper\n//import android.widget.Toast\n//import androidx.appcompat.app.AppCompatActivity\n//import com.amap.api.location.AMapLocationClient\n//import com.amap.api.location.AMapLocationClientOption\n//import com.amap.api.maps.AMap\n//import com.amap.api.maps.CameraUpdateFactory\n//import com.amap.api.maps.model.LatLng\n//import com.amap.api.maps.model.MarkerOptions\n//import com.amap.api.maps.model.MyLocationStyle\n//import com.amap.api.services.route.*\n//import com.baidu.location.*\n//import com.baidu.mapapi.map.*\n//import com.tencent.map.geolocation.TencentLocation\n//import com.tencent.map.geolocation.TencentLocationListener\n//import com.tencent.map.geolocation.TencentLocationManager\n//import com.tencent.map.geolocation.TencentLocationRequest\n//import com.tencent.tencentmap.mapsdk.maps.LocationSource\n//import com.tencent.tencentmap.mapsdk.maps.TencentMap\n//import kotlinx.android.synthetic.main.activity_amap_path.*\n//import kotlinx.android.synthetic.main.activity_map.*\n//\n//\n///**\n// * 高德地图路径规划\n// */\n//class MapActivity : AppCompatActivity() {\n//\n//\n//    companion object {\n//        val TAG = \"MapActivity\"\n//        const val ZOOM_INDEX = 14.0\n//        const val BD_ZOOM_INDEX = 18.0\n//    }\n//\n//    private lateinit var aMap: AMap\n//\n//\n//    override fun onCreate(savedInstanceState: Bundle?) {\n//        super.onCreate(savedInstanceState)\n//        setContentView(R.layout.activity_map)\n//        amap_view.onCreate(savedInstanceState)\n//        aMap = amap_view.map\n//        aMap.minZoomLevel = ZOOM_INDEX.toFloat()\n//        initAMapLocation()\n//        initTencentMapLocation()\n//        initBDMapLocation()\n//    }\n//\n//\n////    lateinit var aMapLocationClient: AMapLocationClient\n////    lateinit var aMapLocationClientOption: AMapLocationClientOption\n//\n//    /**\n//     * 初始化高德地图的定位\n//     */\n//    private fun initAMapLocation() {\n////        aMapLocationClient = AMapLocationClient(this)\n////        aMapLocationClientOption = AMapLocationClientOption()\n////        aMapLocationClientOption.locationMode =\n////            AMapLocationClientOption.AMapLocationMode.Hight_Accuracy\n////        aMapLocationClientOption.interval = 2000\n////        aMapLocationClient.setLocationOption(aMapLocationClientOption)\n////        aMapLocationClient.setLocationListener {\n////            val options = MarkerOptions().position(LatLng(it.latitude, it.longitude))\n////            options.draggable(true).icon(\n////                com.amap.api.maps.model.BitmapDescriptorFactory.fromResource(\n////                    R.mipmap.ic_navi_map_gps_locked\n////                )\n////            )\n////\n////            aMap.addMarker(options)\n////            aMap.clear()\n////            aMap.animateCamera(CameraUpdateFactory.newLatLng(LatLng(it.latitude, it.longitude)))\n////        }\n////        aMapLocationClient.startLocation()\n//\n//        //初始化定位蓝点样式类myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE);//连续定位、且将视角移动到地图中心点，定位点依照设备方向旋转，并且会跟随设备移动。（1秒1次定位）如果不设置myLocationType，默认也会执行此种模式。\n//        val myLocationStyle = MyLocationStyle()\n//        //设置连续定位模式下的定位间隔，只在连续定位模式下生效，单次定位模式下不会生效。单位为毫秒\n//        myLocationStyle.interval(2000)\n//        myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATE)\n//        myLocationStyle.myLocationIcon(\n//            com.amap.api.maps.model.BitmapDescriptorFactory.fromResource(\n//                R.mipmap.ic_navi_map_gps_locked\n//            )\n//        )\n//        ////设置是否显示定位小蓝点，用于满足只想使用定位，不想使用定位小蓝点的场景，设置false以后图面上不再有定位蓝点的概念，但是会持续回调位置信息。\n//        myLocationStyle.showMyLocation(true)\n//        aMap.myLocationStyle = myLocationStyle //设置定位蓝点的Style\n//\n//        //aMap.getUiSettings().setMyLocationButtonEnabled(true);设置默认定位按钮是否显示，非必需设置。\n//        // 设置为true表示启动显示定位蓝点，false表示隐藏定位蓝点并不进行定位，默认是false。\n//        aMap.isMyLocationEnabled = true\n//\n//    }\n//\n//    /**\n//     * 百度地图回调\n//     */\n//    private lateinit var mBDLocationClient: LocationClient\n//\n//    private val mBDLocationListener: BDLocationListener =\n//        BDLocationListener { location ->\n//            location?.let {\n//                val locData: MyLocationData = MyLocationData.Builder()\n//                    .accuracy(location.radius) // 此处设置开发者获取到的方向信息，顺时针0-360\n//                    .direction(location.direction)\n//                    .latitude(location.latitude)\n//                    .longitude(location.longitude)\n//                    .build()\n//                bdmap_view.map.setMyLocationData(locData)\n//            }\n//        }\n//\n//\n//    lateinit var mBDMap: BaiduMap\n//\n//    /**\n//     * 初始化百度地图的定位\n//     */\n//    private fun initBDMapLocation() {\n//        mBDMap = bdmap_view.map\n//        mBDMap.isMyLocationEnabled = true\n//        val mapStatus: MapStatus = MapStatus.Builder()\n//            .zoom(BD_ZOOM_INDEX.toFloat())\n//            .build()\n//        mBDMap.setMapStatus(MapStatusUpdateFactory.newMapStatus(mapStatus))\n//        mBDMap.setMyLocationConfiguration(\n//            MyLocationConfiguration(\n//                MyLocationConfiguration.LocationMode.FOLLOWING,\n//                false,\n//                BitmapDescriptorFactory.fromResource(R.mipmap.ic_navi_map_gps_locked)\n//            )\n//        )\n//        //定位初始化\n//        mBDLocationClient = LocationClient(this)\n//\n//        //通过LocationClientOption设置LocationClient相关参数\n//        val option = LocationClientOption()\n//        option.isOpenGps = true // 打开gps\n//        option.setCoorType(\"bd09ll\") // 设置坐标类型\n////        option.setScanSpan(5000)\n//\n//        //设置locationClientOption\n//        mBDLocationClient.locOption = option\n//        //注册LocationListener监听器\n//        mBDLocationClient.registerLocationListener(mBDLocationListener)\n//        //开启地图定位图层\n//        mBDLocationClient.start()\n//\n//\n//    }\n//\n//\n//    /**\n//     * ========腾讯地图========\n//     */\n//\n//    //用于访问腾讯定位服务的类, 周期性向客户端提供位置更新\n//    var mTencentLocationManager: TencentLocationManager? = null\n//\n//    //创建定位请求\n//    var mTencentLocationRequest: TencentLocationRequest? = TencentLocationRequest.create()\n//    var mTencentOnLocationChangedListener: LocationSource.OnLocationChangedListener? = null\n//    val mTencentLocationListener: TencentLocationListener by lazy {\n//        object : TencentLocationListener {\n//            override fun onLocationChanged(\n//                tencentLocation: TencentLocation?,\n//                code: Int,\n//                s: String?\n//            ) {\n//                tencentLocation?.let {\n//                    if (code == TencentLocation.ERROR_OK) {\n//                        val location = Location(tencentLocation.provider)\n//                        //设置经纬度\n//                        //设置经纬度\n//                        location.latitude = tencentLocation.latitude\n//                        location.longitude = tencentLocation.longitude\n//                        //设置精度，这个值会被设置为定位点上表示精度的圆形半径\n//                        //设置精度，这个值会被设置为定位点上表示精度的圆形半径\n//                        location.accuracy = tencentLocation.accuracy\n//                        //设置定位标的旋转角度，注意 tencentLocation.getBearing() 只有在 gps 时才有可能获取\n//                        //设置定位标的旋转角度，注意 tencentLocation.getBearing() 只有在 gps 时才有可能获取\n//                        location.bearing = tencentLocation.bearing\n//                        //将位置信息返回给地图\n//                        //将位置信息返回给地图\n//                        mTencentOnLocationChangedListener?.onLocationChanged(location)\n//                    }\n//                }\n//            }\n//\n//            override fun onStatusUpdate(p0: String?, p1: Int, p2: String?) {\n//            }\n//\n//        }\n//    }\n//\n//    /**\n//     * ========腾讯地图========\n//     */\n//\n//\n//    lateinit var mTencentMap: TencentMap\n//\n//    /**\n//     * 初始化腾讯地图的定位\n//     */\n//    private fun initTencentMapLocation() {\n//        mTencentLocationManager = TencentLocationManager.getInstance(this)\n//        mTencentMap = tencentmap_view.map\n//        mTencentMap.setLocationSource(object : LocationSource {\n//            override fun activate(locationChangedListener: LocationSource.OnLocationChangedListener?) {\n//                locationChangedListener?.let {\n//                    mTencentOnLocationChangedListener = it\n//                    //开启定位\n//                    //开启定位\n//                    val err: Int? = mTencentLocationManager?.requestLocationUpdates(\n//                        mTencentLocationRequest, mTencentLocationListener, Looper.myLooper()\n//                    )\n//                    when (err) {\n//                        1 -> Toast.makeText(\n//                            this@MapActivity,\n//                            \"设备缺少使用腾讯定位服务需要的基本条件\",\n//                            Toast.LENGTH_SHORT\n//                        ).show()\n//                        2 -> Toast.makeText(\n//                            this@MapActivity,\n//                            \"manifest 中配置的 key 不正确\", Toast.LENGTH_SHORT\n//                        ).show()\n//                        3 -> Toast.makeText(\n//                            this@MapActivity,\n//                            \"自动加载libtencentloc.so失败\", Toast.LENGTH_SHORT\n//                        ).show()\n//                        else -> {\n//                        }\n//                    }\n//                }\n//            }\n//\n//            override fun deactivate() {\n//                //当不需要展示定位点时，需要停止定位并释放相关资源\n//                mTencentLocationManager?.removeUpdates(mTencentLocationListener);\n//                mTencentLocationManager = null;\n//                mTencentLocationRequest = null;\n//                mTencentLocationManager = null;\n//            }\n//\n//        })\n//        mTencentMap.isMyLocationEnabled = true\n//        mTencentMap.setMinZoomLevel(ZOOM_INDEX.toInt())\n//\n//        //设置定位周期（位置监听器回调周期）为3s\n////        mTencentLocationRequest?.interval = 3000\n//        mTencentLocationManager?.requestSingleFreshLocation(\n//            mTencentLocationRequest,\n//            mTencentLocationListener, Looper.myLooper()\n//        )\n//    }\n//\n//    override fun onStart() {\n//        tencentmap_view.onStart()\n//        super.onStart()\n//    }\n//\n//    override fun onResume() {\n//        tencentmap_view.onResume()\n//        amap_view.onResume()\n//        bdmap_view.onResume()\n//        super.onResume()\n//\n//    }\n//\n//    override fun onPause() {\n//        tencentmap_view.onPause()\n//        amap_view.onPause()\n//        bdmap_view.onPause()\n//        super.onPause()\n//    }\n//\n//    override fun onStop() {\n//        tencentmap_view.onStop()\n//        super.onStop()\n//    }\n//\n//    override fun onDestroy() {\n//        tencentmap_view.onDestroy()\n//        amap_view.onDestroy()\n//        mBDLocationClient.stop()\n//        bdmap_view.onDestroy()\n//        bdmap_view.map.isMyLocationEnabled = false\n//        super.onDestroy()\n//\n//    }\n//\n//    override fun onSaveInstanceState(outState: Bundle) {\n//        amap_view.onSaveInstanceState(outState)\n//        super.onSaveInstanceState(outState)\n//    }\n//}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/old/MapShowingLocationActivity.kt",
    "content": "//package com.didichuxing.doraemondemo\n//\n//import android.Manifest\n//import android.annotation.SuppressLint\n//import android.os.Bundle\n//import android.util.Log\n//import android.view.View\n//import androidx.appcompat.app.AppCompatActivity\n//import com.baidu.location.BDAbstractLocationListener\n//import com.baidu.location.BDLocation\n//import com.baidu.location.LocationClient\n//import com.baidu.location.LocationClientOption\n//import com.didichuxing.doraemonkit.kit.core.ViewSetupHelper\n//import com.tencent.tencentmap.mapsdk.maps.CameraUpdateFactory\n//import com.tencent.tencentmap.mapsdk.maps.SupportMapFragment\n//import com.tencent.tencentmap.mapsdk.maps.TencentMap\n//import com.tencent.tencentmap.mapsdk.maps.model.*\n//import pub.devrel.easypermissions.EasyPermissions\n//import pub.devrel.easypermissions.PermissionRequest\n//\n//\n//class MapShowingLocationActivity : AppCompatActivity() {\n//    private lateinit var mRootView: View\n//    private var mTencentMap: TencentMap? = null\n//    private var mBaiduLocationClient: LocationClient? = null\n//    private var mCustomMarker: Marker? = null\n//    private var mLocationMarker: Marker? = null\n//    private var mAccuracyCircle: Circle? = null\n//\n//    override fun onCreate(savedInstanceState: Bundle?) {\n//        super.onCreate(savedInstanceState)\n//        setContentView(R.layout.activity_map_showing_location)\n//        mRootView = findViewById<View>(R.id.map_showing_location)\n//        initMap()\n//        initButtons()\n//        EasyPermissions.requestPermissions(\n//            PermissionRequest.Builder(\n//                this, 200,\n//                Manifest.permission.ACCESS_FINE_LOCATION,\n//                Manifest.permission.ACCESS_COARSE_LOCATION,\n//                Manifest.permission.READ_EXTERNAL_STORAGE,\n//                Manifest.permission.WRITE_EXTERNAL_STORAGE\n//            ).build()\n//        )\n//    }\n//\n//    private fun initMap() {\n//        val manager = supportFragmentManager\n//        val fragment = manager.findFragmentById(R.id.fragment_map) as SupportMapFragment?\n//        if (fragment != null) {\n//            mTencentMap = fragment.map\n//        }\n//    }\n//\n//\n//    private fun initButtons() {\n//        ViewSetupHelper.setupButton(mRootView, R.id.map_test_btn_1, \"添加\") {\n//            setMarker(40.011313, 116.391907)\n//        }\n//        ViewSetupHelper.setupButton(mRootView, R.id.map_test_btn_2, \"移除\") {\n//            removeMarker()\n//        }\n//        ViewSetupHelper.setupButton(mRootView, R.id.map_test_btn_3, \"归位\") {\n//            if (mCustomMarker != null) mTencentMap?.animateCamera(CameraUpdateFactory.newLatLng(mCustomMarker?.position))\n//        }\n//        ViewSetupHelper.setupButton(mRootView, R.id.map_test_btn_4, \"启动定位\") {\n//            startLocation()\n//        }\n//        ViewSetupHelper.setupButton(mRootView, R.id.map_test_btn_5, \"停止定位\") {\n//            stopLocation()\n//        }\n//    }\n//\n//    private var mbdLocationListener: BDAbstractLocationListener =\n//        object : BDAbstractLocationListener() {\n//            override fun onReceiveLocation(bdLocation: BDLocation) {\n//                Log.i(TAG, \"百度定位===onReceiveLocation===lat==>\" + bdLocation.latitude + \"   lng==>\" + bdLocation.longitude)\n//                setLocationMarker(bdLocation.latitude, bdLocation.longitude, 100f.toDouble())\n//            }\n//        }\n//\n//    @SuppressLint(\"MissingPermission\")\n//    private fun startLocation() {\n//        //百度地图\n//        if (mBaiduLocationClient == null) {\n//            mBaiduLocationClient = LocationClient(this)\n//            //通过LocationClientOption设置LocationClient相关参数\n//            val option = LocationClientOption()\n//            // 打开gps\n//            option.isOpenGps = true\n//            // 设置坐标类型\n//            option.setCoorType(\"gcj02\")\n//            option.setScanSpan(5000)\n//            mBaiduLocationClient!!.locOption = option\n//            mBaiduLocationClient!!.registerLocationListener(mbdLocationListener)\n//        }\n//        mBaiduLocationClient?.start()\n//    }\n//\n//    private fun stopLocation() {\n//        if (mBaiduLocationClient == null) return\n//        mBaiduLocationClient?.stop()\n//    }\n//\n//    private fun setMarker(lat: Double, lng: Double) {\n//        val position = LatLng(lat, lng)\n//        mCustomMarker?.remove()\n//        mCustomMarker = mTencentMap?.addMarker(MarkerOptions(position))\n//        mTencentMap?.animateCamera(CameraUpdateFactory.newLatLng(position))\n//    }\n//\n//    private fun setLocationMarker(lat: Double, lng: Double) {\n//        val position = LatLng(lat, lng)\n//        if (mLocationMarker == null) {\n//            val markerOptions = MarkerOptions(position)\n//                .icon(BitmapDescriptorFactory.fromResource(R.mipmap.dk_location_marker))\n//            mLocationMarker = mTencentMap?.addMarker(markerOptions)\n//        } else {\n//            mLocationMarker?.position = position\n//        }\n//        mTencentMap?.animateCamera(CameraUpdateFactory.newLatLng(position))\n//    }\n//\n//    private fun setLocationMarker(lat: Double, lng: Double, radius: Double) {\n//        val position = LatLng(lat, lng)\n//        if (mLocationMarker == null) {\n//            val markerOptions = MarkerOptions(position)\n//                .icon(BitmapDescriptorFactory.fromResource(R.mipmap.dk_location_marker))\n//            mLocationMarker = mTencentMap?.addMarker(markerOptions)\n//            mAccuracyCircle = mTencentMap?.addCircle(\n//                CircleOptions().center(position)\n//                    .radius(radius)\n//                    .fillColor(resources.getColor(R.color.colorCircleMarkerFill))\n//                    .strokeColor(resources.getColor(R.color.colorCircleMarkerStroke))\n//            )\n//        } else {\n//            mLocationMarker?.position = position\n//            mAccuracyCircle?.center = position\n//            mAccuracyCircle?.radius = radius\n//        }\n//        mTencentMap?.animateCamera(CameraUpdateFactory.newLatLng(position))\n//    }\n//\n//\n//    private fun removeMarker() {\n//        mCustomMarker?.remove()\n//        mCustomMarker = null\n//    }\n//\n//    override fun onDestroy() {\n//        super.onDestroy()\n//        stopLocation()\n//    }\n//\n//    companion object {\n//        const val TAG = \"MapShowingLocationActivity\"\n//    }\n//}"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/test/ScreenRecordingService.java",
    "content": "package com.didichuxing.doraemondemo.test;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.app.Notification;\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.app.PendingIntent;\nimport android.app.Service;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.PixelFormat;\nimport android.hardware.HardwareBuffer;\nimport android.hardware.display.DisplayManager;\nimport android.hardware.display.VirtualDisplay;\nimport android.media.Image;\nimport android.media.ImageReader;\nimport android.media.projection.MediaProjection;\nimport android.media.projection.MediaProjectionManager;\nimport android.os.Build;\nimport android.os.IBinder;\nimport android.util.DisplayMetrics;\n\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport androidx.core.app.NotificationCompat;\n\nimport com.didichuxing.doraemondemo.R;\nimport com.didichuxing.doraemondemo.mc.MCActivity;\nimport com.didichuxing.doraemondemo.test.screen.ScreenRecordingDoKitView;\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.kit.test.report.ScreenShotManager;\nimport com.didichuxing.doraemonkit.util.DoKitExecutorUtil;\n\nimport java.nio.ByteBuffer;\n\n/**\n * didi Create on 2022/4/25 .\n * <p>\n * Copyright (c) 2022/4/25 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/25 11:47 上午\n * @Description 屏幕录制服务\n */\n\npublic class ScreenRecordingService extends Service {\n\n\n    public static MediaProjectionManager mMediaProjectionManager;\n    public static Activity activity;\n    public static MediaProjection mMediaProjection;\n\n    public static int mResultCode;\n    public static Intent mResultData;\n\n    private static ScreenRecordingService service;\n\n\n    private ImageReader mImageReader;\n    private ScreenShotManager screenShotManager = new ScreenShotManager(\"test/sc/\");\n    private boolean enable = true;\n\n    public static void stopService() {\n        if (service != null) {\n            service.stopSelf();\n        }\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        if (service != null) {\n            service.stopSelf();\n        }\n        service = this;\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        service = null;\n        enable = false;\n        DoKit.removeFloating(ScreenRecordingDoKitView.class);\n    }\n\n    @SuppressLint(\"WrongConstant\")\n    @RequiresApi(api = Build.VERSION_CODES.O)\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n\n        NotificationChannel channel = new NotificationChannel(\"NOTIFICATION_CHANNEL_ID\", \"NOTIFICATION_CHANNEL_NAME\", NotificationManager.IMPORTANCE_MIN);\n        channel.setDescription(\"NOTIFICATION_CHANNEL_DESC\");\n\n        if (activity == null || activity.getApplication() == null) {\n            return START_STICKY;\n        }\n\n        NotificationManager mNM = (NotificationManager) getApplication().getSystemService(Context.NOTIFICATION_SERVICE);\n\n        if (mNM != null) {\n            mNM.createNotificationChannel(channel);\n        }\n\n        NotificationCompat.Builder builder = new NotificationCompat.Builder(activity, channel.getId());\n        // 在API11之后构建Notification的方式\n        Intent nfIntent = new Intent(activity, MCActivity.class);\n\n        builder.setContentIntent(PendingIntent.getActivity(activity, 0, nfIntent, 0))\n            .setLargeIcon(BitmapFactory.decodeResource(activity.getResources(), R.mipmap.ic_launcher))\n            .setContentTitle(\"下拉列表中的Title\")\n            .setSmallIcon(R.mipmap.ic_launcher)\n            .setContentText(\"要显示的内容\")\n            .setWhen(System.currentTimeMillis());\n\n        Notification notification = builder.build(); // 获取构建好的Notification\n        notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音\n\n        startForeground(100, notification);\n\n\n        mMediaProjection = mMediaProjectionManager.getMediaProjection(mResultCode, mResultData);\n\n        DisplayMetrics metrics = activity.getResources().getDisplayMetrics();\n        int windowWidth = metrics.widthPixels;\n        int windowHeight = metrics.heightPixels;\n        float mScreenDensity = metrics.density;\n\n        ImageReader mImageReader = null; //ImageFormat.RGB_565\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {\n            mImageReader = ImageReader.newInstance(windowWidth, windowHeight, PixelFormat.RGBA_8888, 2, HardwareBuffer.USAGE_CPU_WRITE_OFTEN);\n        } else {\n            mImageReader = ImageReader.newInstance(windowWidth, windowHeight, PixelFormat.RGBA_8888, 2);\n        }\n        VirtualDisplay mVirtualDisplay = mMediaProjection.createVirtualDisplay(\"screen-mirror\",\n            windowWidth, windowHeight, (int) mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,\n            mImageReader.getSurface(), null, null);\n\n        this.mImageReader = mImageReader;\n\n        DoKitExecutorUtil.execute(mRunnable);\n\n        return super.onStartCommand(intent, flags, startId);\n    }\n\n    private Runnable mRunnable = new Runnable() {\n        @Override\n        public void run() {\n            while (enable) {\n                try {\n                    Thread.sleep(100);\n                    acquireLatestImage();\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    };\n\n\n    public void acquireLatestImage() {\n        Image image = mImageReader.acquireLatestImage();\n        int width = image.getWidth();\n        int height = image.getHeight();\n        final Image.Plane[] planes = image.getPlanes();\n        if (planes.length > 0) {\n            final ByteBuffer buffer = planes[0].getBuffer();\n            int pixelStride = planes[0].getPixelStride();\n            int rowStride = planes[0].getRowStride();\n            int rowPadding = rowStride - pixelStride * width;\n            Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);\n            bitmap.copyPixelsFromBuffer(buffer);\n            bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height);\n\n            ScreenRecordingDoKitView.Companion.updateScreen(bitmap);\n//            screenShotManager.saveBitmap(bitmap, screenShotManager.createNextFileName());\n        }\n\n        image.close();\n    }\n\n    @Nullable\n    @Override\n    public IBinder onBind(Intent intent) {\n        return null;\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/test/ScreenRecordingTest.java",
    "content": "package com.didichuxing.doraemondemo.test;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.media.projection.MediaProjectionManager;\n\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemondemo.test.screen.ScreenRecordingDoKitView;\nimport com.didichuxing.doraemonkit.DoKit;\n\n/**\n * didi Create on 2022/4/25 .\n * <p>\n * Copyright (c) 2022/4/25 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/25 11:24 上午\n * @Description 屏幕录制服务测试支持\n */\n\npublic class ScreenRecordingTest {\n    public static final int REQUEST_MEDIA_PROJECTION = 100;\n\n    private MediaProjectionManager mMediaProjectionManager;\n    private Activity activity;\n\n    public void start(Activity activity) {\n        this.activity = activity;\n        mMediaProjectionManager = (MediaProjectionManager) activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);\n\n        activity.startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);\n    }\n\n    public void startService() {\n        Intent nfIntent = new Intent(activity.getApplication(), ScreenRecordingService.class);\n        activity.startService(nfIntent);\n    }\n\n    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {\n        ScreenRecordingService.activity = activity;\n        ScreenRecordingService.mMediaProjectionManager = mMediaProjectionManager;\n\n        ScreenRecordingService.mResultCode = resultCode;\n        ScreenRecordingService.mResultData = data;\n\n        startService();\n        DoKit.launchFloating(ScreenRecordingDoKitView.class);\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/java/com/didichuxing/doraemondemo/test/screen/ScreenRecordingDoKitView.kt",
    "content": "package com.didichuxing.doraemondemo.test.screen\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport android.widget.ImageView\nimport com.didichuxing.doraemondemo.R\nimport com.didichuxing.doraemondemo.test.ScreenRecordingService\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams\nimport com.didichuxing.doraemonkit.kit.test.widget.FlashImageView\nimport com.didichuxing.doraemonkit.util.ConvertUtils\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.didichuxing.doraemonkit.util.UIUtils\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.MainScope\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.plus\n\n\n/**\n * didi Create on 2022/4/14 .\n *\n * Copyright (c) 2022/4/14 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/14 4:40 下午\n * @Description 用一句话说明文件功能\n */\n\nclass ScreenRecordingDoKitView : AbsDoKitView() {\n\n    companion object {\n        private val mainScope = MainScope() + CoroutineName(this.toString())\n        private val myDoKitViews: MutableSet<ScreenRecordingDoKitView> = mutableSetOf()\n\n        fun updateScreen(bitmap: Bitmap) {\n            mainScope.launch {\n                myDoKitViews.forEach {\n                    it.updateScreen(bitmap)\n                }\n            }\n        }\n    }\n\n    private var mRedDot: FlashImageView? = null\n    private var mScreenImageView: ImageView? = null\n\n\n    override fun onCreate(context: Context?) {\n\n    }\n\n    override fun onCreateView(context: Context?, rootView: FrameLayout?): View {\n        return LayoutInflater.from(context).inflate(R.layout.dk_screen_show_view, rootView, false)\n    }\n\n    override fun onViewCreated(rootView: FrameLayout?) {\n\n        myDoKitViews.add(this)\n\n        mRedDot = findViewById(R.id.dot)\n        mScreenImageView = findViewById(R.id.screenAll)\n\n        rootView?.setOnClickListener {\n\n        }\n        rootView?.findViewById<ImageView>(R.id.close)?.setOnClickListener {\n            ScreenRecordingService.stopService()\n            ToastUtils.showShort(\"已停止录屏\")\n        }\n\n        mRedDot?.startFlash()\n\n    }\n\n    private fun updateScreen(bitmap: Bitmap) {\n        mScreenImageView?.let {\n            it.setImageBitmap(bitmap)\n        }\n    }\n\n\n    override fun initDokitViewLayoutParams(params: DoKitViewLayoutParams) {\n        params.width = UIUtils.getWidthPixels() / 2\n        params.height = UIUtils.getRealHeightPixels() / 2\n        params.gravity = Gravity.TOP or Gravity.LEFT\n        params.x = ConvertUtils.dp2px(25f)\n        params.y = ConvertUtils.dp2px(25f)\n    }\n\n    override fun onDestroy() {\n        myDoKitViews.remove(this)\n        mRedDot?.cancelFlash()\n        super.onDestroy()\n    }\n}\n"
  },
  {
    "path": "Android/app/src/main/res/drawable/bg_unlock_bar_btn_normal_v5.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@color/color_00A39E\" />\n    <corners android:radius=\"6dp\" />\n</shape>"
  },
  {
    "path": "Android/app/src/main/res/drawable/bg_unlock_bar_normal_v5.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@color/color_414657\" />\n    <corners android:radius=\"6dp\" />\n</shape>"
  },
  {
    "path": "Android/app/src/main/res/drawable/dk_btn_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"#ff00ff\" />\n    <corners android:radius=\"4dp\" />\n    <padding\n        android:left=\"2dp\"\n        android:right=\"2dp\" />\n    <size android:width=\"20dp\" />\n</shape>"
  },
  {
    "path": "Android/app/src/main/res/drawable/dk_info_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"#ffffff\" />\n    <corners android:radius=\"4dp\" />\n    <stroke android:color=\"#cccccc\" android:width=\"1px\" />\n</shape>"
  },
  {
    "path": "Android/app/src/main/res/drawable/dk_line_divider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <size android:height=\"5dp\" />\n    <solid android:color=\"#FFFFFF\" />\n</shape>"
  },
  {
    "path": "Android/app/src/main/res/drawable/dk_shape_float_view_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"#ffffff\" />\n    <corners android:radius=\"5dp\" />\n    <padding\n        android:bottom=\"1px\"\n        android:left=\"1px\"\n        android:right=\"1px\"\n        android:top=\"1px\" />\n</shape>\n"
  },
  {
    "path": "Android/app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\">\n    <path\n        android:fillColor=\"#26A69A\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n</vector>\n"
  },
  {
    "path": "Android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\">\n    <path\n        android:fillType=\"evenOdd\"\n        android:pathData=\"M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z\"\n        android:strokeColor=\"#00000000\"\n        android:strokeWidth=\"1\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"78.5885\"\n                android:endY=\"90.9159\"\n                android:startX=\"48.7653\"\n                android:startY=\"61.0927\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z\"\n        android:strokeColor=\"#00000000\"\n        android:strokeWidth=\"1\" />\n</vector>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_amap_path.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\"com.didichuxing.doraemondemo.EmptyActivity\">\n\n\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_big_bitmap_mock.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\"com.didichuxing.doraemondemo.module.bigbitmap.BigBitmapActivity\"\n    tools:ignore=\"MissingDefaultResource\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <Button\n            android:id=\"@+id/btn_load_img\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"50dp\"\n            android:layout_marginLeft=\"40dp\"\n            android:layout_marginRight=\"40dp\"\n            android:text=\"@string/app_btn_load_img\"\n            android:textAllCaps=\"false\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"10dp\"\n            android:layout_marginBottom=\"10dp\"\n            android:background=\"#999999\"\n            android:orientation=\"vertical\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:gravity=\"center\"\n                    android:text=\"Coil\"\n                    android:textColor=\"#ffffff\"\n                    android:textSize=\"16sp\" />\n\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:gravity=\"center\"\n                    android:text=\"connect\"\n                    android:textColor=\"#ffffff\"\n                    android:textSize=\"16sp\" />\n\n            </LinearLayout>\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <ImageView\n                    android:id=\"@+id/iv_coil\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"150dp\"\n                    android:layout_weight=\"1\" />\n\n                <ImageView\n                    android:id=\"@+id/iv_connect\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"150dp\"\n                    android:layout_weight=\"1\" />\n\n\n            </LinearLayout>\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:gravity=\"center\"\n                    android:text=\"Picasso\"\n                    android:textColor=\"#ffffff\"\n                    android:textSize=\"16sp\" />\n\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:gravity=\"center\"\n                    android:text=\"Glide\"\n                    android:textColor=\"#ffffff\"\n                    android:textSize=\"16sp\" />\n\n            </LinearLayout>\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <ImageView\n                    android:id=\"@+id/iv_picasso\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"150dp\"\n                    android:layout_weight=\"1\" />\n\n                <ImageView\n                    android:id=\"@+id/iv_glide\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"150dp\"\n                    android:layout_weight=\"1\" />\n\n\n            </LinearLayout>\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:gravity=\"center\"\n                    android:text=\"ImageLoader\"\n                    android:textColor=\"#ffffff\"\n                    android:textSize=\"16sp\" />\n\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:gravity=\"center\"\n                    android:text=\"Fresco\"\n                    android:textColor=\"#ffffff\"\n                    android:textSize=\"16sp\" />\n            </LinearLayout>\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n\n                <ImageView\n                    android:id=\"@+id/iv_imageloader\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"150dp\"\n                    android:layout_weight=\"1\" />\n\n                <com.facebook.drawee.view.SimpleDraweeView\n                    android:id=\"@+id/iv_fresco\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"150dp\"\n                    android:layout_weight=\"1\" />\n            </LinearLayout>\n\n        </LinearLayout>\n    </LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_comm.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/comm_root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <RelativeLayout\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"48dp\">\n\n        <ImageView\n            android:id=\"@+id/iv_back\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerVertical=\"true\"\n            android:padding=\"16dp\"\n            android:src=\"@mipmap/title_back\" />\n\n        <TextView\n            android:id=\"@+id/tv_title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerInParent=\"true\"\n            android:text=\"DoKit\"\n            android:textColor=\"#333333\"\n            android:textSize=\"18sp\"\n            android:textStyle=\"bold\" />\n\n    </RelativeLayout>\n\n    <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/container_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_below=\"@id/title_bar\" />\n\n</RelativeLayout>\n\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_dokit_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/color_white\">\n\n    <ScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <LinearLayout\n            android:id=\"@+id/all\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:paddingBottom=\"40dp\">\n\n            <RelativeLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"160dp\">\n\n                <ImageView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"80dp\"\n                    android:background=\"@color/colorPrimary\" />\n\n                <androidx.cardview.widget.CardView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"140dp\"\n                    android:layout_marginLeft=\"18dp\"\n                    android:layout_marginTop=\"10dp\"\n                    android:layout_marginRight=\"18dp\"\n                    app:cardBackgroundColor=\"@color/color_white\"\n                    app:cardCornerRadius=\"8dp\"\n                    app:cardElevation=\"8dp\">\n\n                    <RelativeLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\">\n\n                        <com.google.android.material.circularreveal.cardview.CircularRevealCardView\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_centerHorizontal=\"true\"\n                            android:layout_marginTop=\"15dp\"\n                            app:cardCornerRadius=\"8dp\"\n                            app:cardElevation=\"0dp\">\n\n                            <ImageView\n                                android:layout_width=\"65dp\"\n                                android:layout_height=\"65dp\"\n                                android:src=\"@mipmap/dk_dokit_big\" />\n\n                        </com.google.android.material.circularreveal.cardview.CircularRevealCardView>\n\n\n                        <TextView\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_alignParentBottom=\"true\"\n                            android:layout_centerHorizontal=\"true\"\n                            android:layout_gravity=\"center\"\n                            android:layout_marginBottom=\"20dp\"\n                            android:text=\"一款面向泛前端产品研发全生命周期的效率平台\"\n                            android:textColor=\"@color/dk_color_333333\"\n                            android:textSize=\"12dp\" />\n\n                    </RelativeLayout>\n\n                </androidx.cardview.widget.CardView>\n\n            </RelativeLayout>\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"28dp\"\n                android:layout_marginTop=\"20dp\"\n                android:layout_marginBottom=\"10dp\"\n                android:text=\"工具入口\"\n                android:textColor=\"@color/dk_color_999999\"\n                android:textSize=\"14dp\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"显示/隐藏快捷入口\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"打开工具窗口\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"28dp\"\n                android:layout_marginTop=\"20dp\"\n                android:layout_marginBottom=\"10dp\"\n                android:text=\"平台工具\"\n                android:textColor=\"@color/dk_color_999999\"\n                android:textSize=\"14dp\" />\n\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"数据Mock测试\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"OkHttp 模拟请求\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"UrlConnection 模拟请求\" />\n\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"retrofit 模拟请求\" />\n\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"一机多控测试\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"自动化测试\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"28dp\"\n                android:layout_marginTop=\"20dp\"\n                android:layout_marginBottom=\"10dp\"\n                android:text=\"常用工具\"\n                android:textColor=\"@color/dk_color_999999\"\n                android:textSize=\"14dp\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"日志测试\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"系统:WebView\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"X5:Webview\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"28dp\"\n                android:layout_marginTop=\"20dp\"\n                android:layout_marginBottom=\"10dp\"\n                android:text=\"LBS\"\n                android:textColor=\"@color/dk_color_999999\"\n                android:textSize=\"14dp\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"位置模拟\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"路径模拟\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"28dp\"\n                android:layout_marginTop=\"20dp\"\n                android:layout_marginBottom=\"10dp\"\n                android:text=\"性能工具\"\n                android:textColor=\"@color/dk_color_999999\"\n                android:textSize=\"14dp\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"模拟内存泄漏\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"模拟耗时函数调用\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"崩溃模拟\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"数据库测试\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"文件上传模拟\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"文件下载模拟\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"大图检测模拟\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"28dp\"\n                android:layout_marginTop=\"20dp\"\n                android:layout_marginBottom=\"10dp\"\n                android:text=\"视觉工具\"\n                android:textColor=\"@color/dk_color_999999\"\n                android:textSize=\"14dp\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"取色器测试\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"标尺对齐测试\" />\n\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"28dp\"\n                android:layout_marginTop=\"20dp\"\n                android:layout_marginBottom=\"10dp\"\n                android:text=\"其他工具\"\n                android:textColor=\"@color/dk_color_999999\"\n                android:textSize=\"14dp\" />\n\n            <com.didichuxing.doraemondemo.module.DoKitItemView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginLeft=\"14dp\"\n                android:layout_marginRight=\"14dp\"\n                app:itemText=\"旧版页面入口\" />\n\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"26dp\"\n                android:text=\"v3.7.1\"\n                android:textColor=\"@color/dk_color_999999\"\n                android:textSize=\"14dp\" />\n\n        </LinearLayout>\n\n    </ScrollView>\n\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\"com.didichuxing.doraemondemo.EmptyActivity\">\n\n    <TextView\n        android:id=\"@+id/tv\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center\"\n        android:text=\"功能没有实现，期待你来完成！\" />\n\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_leak.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\"com.didichuxing.doraemondemo.module.leak.LeakActivity\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center\"\n        android:text=\"当前activity关闭时会有内存泄漏\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:scrollbars=\"vertical\"\n    tools:ignore=\"MissingDefaultResource\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/tv_env\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:padding=\"10dp\"\n            android:text=\"@string/app_build_types\"\n            android:textSize=\"18sp\" />\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/rv\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"800dp\" />\n\n    </LinearLayout>\n\n</androidx.core.widget.NestedScrollView>\n\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_map.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n\n    <Button\n        android:id=\"@+id/btn_refresh\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"刷新\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_below=\"@id/btn_refresh\"\n        android:divider=\"@drawable/dk_line_divider\"\n        android:orientation=\"vertical\"\n        android:showDividers=\"middle\">\n\n        <com.amap.api.maps.MapView\n            android:id=\"@+id/amap_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n\n        <com.baidu.mapapi.map.MapView\n            android:id=\"@+id/bdmap_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n\n\n    </LinearLayout>\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_mc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemondemo.mc.SlideBar\n        android:id=\"@+id/unlock_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"60dp\"\n        android:layout_margin=\"8dp\"\n        android:layout_marginTop=\"10dp\"\n        android:clickable=\"true\"\n        app:label=\"右滑\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_marginBottom=\"16dp\"\n        android:orientation=\"vertical\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\">\n\n            <Button\n                android:id=\"@+id/nextPage\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"网络\" />\n\n            <Button\n                android:id=\"@+id/webPage\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"5dp\"\n                android:text=\"网页\" />\n\n            <Button\n                android:id=\"@+id/testPage\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"5dp\"\n                android:text=\"截图\" />\n\n            <Button\n                android:id=\"@+id/screenPage\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"5dp\"\n                android:text=\"录屏\" />\n\n        </LinearLayout>\n\n\n        <Spinner\n            android:id=\"@+id/spinner\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:dropDownVerticalOffset=\"35dp\"\n            android:popupBackground=\"@color/dk_color_79DE79\"\n            android:spinnerMode=\"dropdown\" />\n\n    </LinearLayout>\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <com.didichuxing.doraemondemo.mc.DoKitButton\n            android:id=\"@+id/btn1\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"正常\"\n            android:textSize=\"18sp\" />\n\n\n        <Button\n            android:id=\"@+id/btn2\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/btn1\"\n            android:text=\"弹框\"\n            android:textSize=\"18sp\" />\n\n        <RadioGroup\n            android:id=\"@+id/radio_group\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"10dp\"\n            android:layout_toRightOf=\"@id/btn2\"\n            android:orientation=\"vertical\">\n\n            <RadioButton\n                android:id=\"@+id/r0\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"选项0\" />\n\n            <RadioButton\n                android:id=\"@+id/r1\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"选项1\" />\n\n            <RadioButton\n                android:id=\"@+id/r2\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"选项2\" />\n        </RadioGroup>\n\n\n        <!--        <Button-->\n        <!--            android:id=\"@+id/btn_webview\"-->\n        <!--            android:layout_width=\"wrap_content\"-->\n        <!--            android:layout_height=\"wrap_content\"-->\n        <!--            android:layout_toRightOf=\"@id/radio_group\"-->\n        <!--            android:text=\"打开WebView\" />-->\n\n        <EditText\n            android:id=\"@+id/ed0\"\n            android:layout_width=\"150dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:layout_toRightOf=\"@id/radio_group\"\n            android:hint=\"请输入keyword\" />\n\n        <EditText\n            android:id=\"@+id/ed1\"\n            android:layout_width=\"150dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/ed0\"\n            android:layout_marginTop=\"5dp\"\n            android:layout_toRightOf=\"@id/radio_group\"\n            android:hint=\"请输入keyword\" />\n    </RelativeLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"200dp\"\n        android:orientation=\"horizontal\">\n\n        <ScrollView\n            android:id=\"@+id/sc\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\">\n\n            <LinearLayout\n                android:id=\"@+id/ll\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:orientation=\"vertical\" />\n        </ScrollView>\n\n        <ListView\n            android:id=\"@+id/lv\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\" />\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/rv\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\" />\n    </LinearLayout>\n\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/vp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n\n</LinearLayout>\n\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_net_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".mc.NetMainActivity\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/text\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"30dp\"\n            android:text=\"test\"\n            android:textSize=\"12dp\" />\n\n    </LinearLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_normal_webview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\"com.didichuxing.doraemondemo.WebViewSystemActivity\">\n\n    <WebView\n        android:id=\"@+id/normal_web_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_second.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\"com.didichuxing.doraemondemo.EmptyActivity\">\n\n    <TextView\n        android:id=\"@+id/tv\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center\"\n        android:text=\"second activity\" />\n\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_webview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\"com.didichuxing.doraemondemo.mc.WebViewActivity\"\n    tools:ignore=\"MissingDefaultResource\">\n\n\n    <WebView\n        android:id=\"@+id/webview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_ws.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\"com.didichuxing.doraemondemo.WSActivity\"\n    android:padding=\"10dp\"\n    >\n\n    <Button\n        android:id=\"@+id/btn_server_start\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center\"\n        android:text=\"WS server start\" />\n\n    <Button\n        android:id=\"@+id/btn_server_stop\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center\"\n        android:text=\"WS server stop\" />\n\n    <Button\n        android:id=\"@+id/btn_server_send\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center\"\n        android:text=\"WS server send\" />\n\n\n    <Button\n        android:id=\"@+id/btn_client_connect\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center\"\n        android:text=\"WS client connect\" />\n\n\n    <Button\n        android:id=\"@+id/btn_client_close\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center\"\n        android:text=\"WS client close\" />\n\n    <Button\n        android:id=\"@+id/btn_client_send\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center\"\n        android:text=\"WS client send\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/activity_x5_webview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\"com.didichuxing.doraemondemo.WebViewX5Activity\">\n\n    <com.tencent.smtt.sdk.WebView\n        android:id=\"@+id/x5_web_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/app/src/main/res/layout/dk_demo.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center\"\n    android:orientation=\"vertical\"\n    android:paddingLeft=\"15dp\"\n    android:paddingRight=\"15dp\"\n    android:paddingBottom=\"15dp\">\n\n    <FrameLayout\n        android:layout_width=\"400dp\"\n        android:layout_height=\"100dp\"\n        android:background=\"@drawable/dk_info_background\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"我是demo浮标\" />\n\n        <TextView\n            android:id=\"@+id/tv_close\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top|right\"\n            android:text=\"X\"\n            android:textSize=\"24sp\"\n            android:layout_margin=\"10dp\"\n            />\n    </FrameLayout>\n\n\n</RelativeLayout>"
  },
  {
    "path": "Android/app/src/main/res/layout/dk_layout_simple_dokit_float_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/dk_shape_float_view_bg\"\n    android:clipChildren=\"false\"\n    android:minWidth=\"50dp\"\n    android:minHeight=\"50dp\">\n\n    <ImageButton\n        android:id=\"@+id/floatClose\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"5dp\"\n        android:layout_marginLeft=\"5dp\"\n        android:background=\"@mipmap/dk_close_25\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/showHideSwitch\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/showHideSwitch\" />\n\n    <Switch\n        android:id=\"@+id/showHideSwitch\"\n        android:layout_width=\"40dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"15dp\"\n        android:layout_marginLeft=\"15dp\"\n        android:layout_marginEnd=\"5dp\"\n        android:layout_marginRight=\"5dp\"\n        android:checked=\"true\"\n        android:minHeight=\"5dp\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintStart_toEndOf=\"@id/floatClose\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/floatPageTitle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"#ff000000\"\n        android:textSize=\"10sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintStart_toStartOf=\"@id/floatClose\"\n        app:layout_constraintTop_toBottomOf=\"@id/floatClose\"\n        tools:text=\"title\" />\n\n\n    <FrameLayout\n        android:id=\"@+id/floatContainer\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"#00FFFFFF\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/floatPageTitle\"\n        tools:layout_width=\"200dp\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "Android/app/src/main/res/layout/dk_screen_show_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"#60333333\">\n\n    <ImageView\n        android:id=\"@+id/screenAll\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scaleType=\"centerInside\">\n\n    </ImageView>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"10dp\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginRight=\"10dp\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\">\n\n        <com.didichuxing.doraemonkit.kit.test.widget.FlashImageView\n            android:id=\"@+id/dot\"\n            android:layout_width=\"14dp\"\n            android:layout_height=\"14dp\"\n            android:background=\"@drawable/dk_autotest_flash_red_bg\" />\n\n        <Space\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"100\" />\n\n        <ImageView\n            android:id=\"@+id/close\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"@dimen/dk_dp_10\"\n            android:src=\"@mipmap/dk_close_icon\" />\n\n    </LinearLayout>\n\n</FrameLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/fragment_amap.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/amap_wrap\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n\n    <TextView\n        android:id=\"@+id/tv_start\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"60dp\"\n        android:layout_alignParentBottom=\"true\"\n        android:background=\"#1E90FF\"\n        android:gravity=\"center\"\n        android:text=\"开始导航\"\n        android:textColor=\"#ffffff\"\n        android:textSize=\"24sp\" />\n\n    <com.amap.api.maps.MapView\n        android:id=\"@+id/amap_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_above=\"@id/tv_start\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/fragment_vp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"com.didichuxing.doraemondemo.mc.MCActivity\"\n    tools:ignore=\"MissingDefaultResource\">\n\n\n    <Button\n        android:id=\"@+id/btn\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:text=\"fragment_0\" />\n\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/item_lv.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:padding=\"10dp\">\n\n    <TextView\n        android:id=\"@+id/tv\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center\"\n        android:textSize=\"16sp\"\n        tools:text=\"lv item 0\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/item_main_rv.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/item_rl\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:paddingLeft=\"10dp\"\n    android:paddingRight=\"10dp\">\n\n    <Button\n        android:id=\"@+id/btn\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentRight=\"true\"\n        android:clickable=\"false\"\n        android:textSize=\"16sp\"\n        android:textStyle=\"bold\"\n        tools:text=\"点我\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/item_rv.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/item_rl\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:padding=\"10dp\">\n\n    <TextView\n        android:id=\"@+id/tv\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center\"\n        android:textSize=\"14sp\"\n        tools:text=\"rv item 0\" />\n\n    <Button\n        android:id=\"@+id/btn\"\n        android:layout_width=\"35dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentRight=\"true\"\n        android:text=\"点我\"\n        android:textSize=\"12sp\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/item_sc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:padding=\"10dp\">\n\n    <TextView\n        android:id=\"@+id/tv\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center\"\n        android:textSize=\"16sp\"\n        tools:text=\"sc item 0\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/layout_demo_custom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"20dp\">\n\n        <LinearLayout\n            android:id=\"@+id/linearLayout\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:orientation=\"vertical\">\n\n            <Button\n                android:id=\"@+id/test1\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:minWidth=\"1dp\"\n                android:minHeight=\"1dp\"\n                android:text=\"1\"\n                android:textAllCaps=\"false\" />\n\n            <Button\n                android:id=\"@+id/test2\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:minWidth=\"1dp\"\n                android:minHeight=\"1dp\"\n                android:text=\"2\"\n                android:textAllCaps=\"false\" />\n\n            <ToggleButton\n                android:id=\"@+id/tb_test1\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\" />\n            <ToggleButton\n                android:id=\"@+id/tb_test2\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\" />\n\n            <EditText\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"测试一下键盘焦点\" />\n\n        </LinearLayout>\n\n    </LinearLayout>\n</FrameLayout>"
  },
  {
    "path": "Android/app/src/main/res/layout/layout_mock_location_preset.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/mock_location_root_container\"\n    android:layout_width=\"220dp\"\n    android:layout_height=\"match_parent\">\n\n    <TextView\n        android:id=\"@+id/env_info3\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"10sp\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:text=\"env_info3\" />\n\n    <ScrollView\n        android:id=\"@+id/cl_mock_gps_scrollview\"\n        android:layout_width=\"220dp\"\n        android:layout_height=\"100dp\"\n        android:maxHeight=\"100dp\"\n        android:orientation=\"vertical\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/env_info3\">\n\n        <com.google.android.flexbox.FlexboxLayout\n            android:id=\"@+id/cl_mock_gps_flexbox_container\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:flexWrap=\"wrap\"\n            app:justifyContent=\"space_evenly\">\n\n        </com.google.android.flexbox.FlexboxLayout>\n\n    </ScrollView>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/layout_mock_route.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/mock_location_root_container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:maxWidth=\"220dp\"\n    >\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/cl_mock_route_container\"\n        android:layout_width=\"220dp\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:visibility=\"visible\">\n\n        <TextView\n            android:id=\"@+id/tv_mock_route\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"route:\"\n            android:textColor=\"@android:color/black\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <LinearLayout\n            android:id=\"@+id/ll_mock_route\"\n            android:layout_width=\"220dp\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:orientation=\"horizontal\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/tv_mock_route\">\n\n            <Button\n                android:id=\"@+id/btn_route_back\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:minWidth=\"1dp\"\n                android:minHeight=\"1dp\"\n                android:text=\"-\"\n                android:textSize=\"10dp\" />\n\n            <SeekBar\n                android:id=\"@+id/dk_sb_mock_route_seekBar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:max=\"20\"\n                android:min=\"0\"\n                android:progress=\"0\" />\n\n            <Button\n                android:id=\"@+id/btn_route_forward\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:minWidth=\"1dp\"\n                android:minHeight=\"1dp\"\n                android:text=\"+\"\n                android:textSize=\"10dp\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:id=\"@+id/ll_mock_route_control\"\n            android:layout_width=\"220dp\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:orientation=\"horizontal\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/ll_mock_route\">\n\n            <Button\n                android:id=\"@+id/btn_route_pause\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:minWidth=\"1dp\"\n                android:minHeight=\"1dp\"\n                android:text=\"pause\"\n                android:textSize=\"10dp\" />\n\n\n            <Button\n                android:id=\"@+id/btn_route_resume\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:minWidth=\"1dp\"\n                android:minHeight=\"1dp\"\n                android:text=\"resume\"\n                android:textSize=\"10dp\" />\n\n        </LinearLayout>\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/layout/layout_slidebar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/slidebar_root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"70dp\">\n\n    <!--background-->\n    <View\n        android:id=\"@+id/slidebar_root_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"center\"\n        android:background=\"@drawable/bg_unlock_bar_normal_v5\" />\n\n    <!--float background-->\n    <FrameLayout\n        android:id=\"@+id/slidebar_float_background\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@drawable/bg_unlock_bar_btn_normal_v5\">\n\n        <ImageView\n            android:id=\"@+id/slidebar_float_view\"\n            android:layout_width=\"60dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_gravity=\"center_vertical\"\n            android:scaleType=\"centerInside\"\n            android:src=\"@mipmap/btn_icon_unlock_arrow\" />\n    </FrameLayout>\n\n    <!--label-->\n    <TextView\n        android:id=\"@+id/slidebar_text_label\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:gravity=\"center\"\n        android:textColor=\"#ffffff\"\n        android:textSize=\"17sp\"\n        android:textStyle=\"bold\" />\n\n</merge>"
  },
  {
    "path": "Android/app/src/main/res/layout/view_dokit_item_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/rootView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\">\n\n\n        <TextView\n            android:id=\"@+id/itemText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerInParent=\"true\"\n            android:layout_marginLeft=\"16dp\"\n            android:gravity=\"center\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"16dp\"\n            tools:text=\"item 0\" />\n\n        <Space\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"100\" />\n\n        <ImageView\n            android:id=\"@+id/itemIcon\"\n            android:layout_width=\"8dp\"\n            android:layout_height=\"13.5dp\"\n            android:layout_marginRight=\"16dp\"\n            android:background=\"@mipmap/dk_arrow_normal\"\n            android:backgroundTint=\"@color/dk_color_999999\" />\n\n    </LinearLayout>\n\n\n</FrameLayout>\n"
  },
  {
    "path": "Android/app/src/main/res/values/atts.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <declare-styleable name=\"DoKitItemView\">\n        <attr name=\"itemText\" format=\"string\" />\n        <attr name=\"itemTextSize\" format=\"dimension\" />\n        <attr name=\"itemIcon\" format=\"reference\" />\n        <attr name=\"itemLayout\" format=\"reference\" />\n        <attr name=\"itemTextShow\" format=\"boolean\" />\n        <attr name=\"itemIconShow\" format=\"boolean\" />\n    </declare-styleable>\n\n</resources>\n"
  },
  {
    "path": "Android/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"color_00A39E\">#00A39E</color>\n    <color name=\"color_414657\">#414657</color>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n    <color name=\"colorCircleMarkerFill\">#750090FF</color>\n    <color name=\"colorCircleMarkerStroke\">#684C00FF</color>\n</resources>\n"
  },
  {
    "path": "Android/app/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <dimen name=\"slidebar_thumb_width\">60dp</dimen>\n\n</resources>"
  },
  {
    "path": "Android/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">DoKit</string>\n    <string name=\"dk_kit_demo\">demo</string>\n    <string name=\"dk_titlebar_back\">&#060;</string>\n    <string name=\"app_build_types\">当前编译环境</string>\n    <string name=\"app_btn_jump\">跳转其他Activity</string>\n    <string name=\"app_btn_mc\">一机多控</string>\n    <string name=\"app_btn_show_hide_icon\">显示/隐藏Dokit入口</string>\n    <string name=\"app_btn_leak\">模拟内存泄漏</string>\n    <string name=\"app_btn_app_launch_stack\">app启动函数调用栈</string>\n    <string name=\"app_btn_show_tool_panel\">显示工具面板</string>\n    <string name=\"app_btn_installed_app\">获取已安装的app</string>\n    <string name=\"app_btn_method_cost\">函数调用耗时(TAG:MethodCostUtil)</string>\n    <string name=\"app_btn_location\">获取位置信息(系统)</string>\n    <string name=\"app_btn_path_amap\">高德路径规划</string>\n    <string name=\"dk_kit_gps_mock_preset\">位置预设</string>\n    <string name=\"app_btn_location_map\">获取位置信息(三方地图)</string>\n    <string name=\"app_btn_location_map2\">地图展示定位</string>\n    <string name=\"app_btn_load_img\">大图检测</string>\n    <string name=\"app_btn_okhttp_mock\">OkHttp Mock</string>\n    <string name=\"app_btn_urlconnection_mock\">HttpURLConnection Mock</string>\n    <string name=\"app_btn_retrofit_mock\">Retrofit Mock</string>\n    <string name=\"app_btn_custom_mock\">其他网络库(自定义抓包)</string>\n    <string name=\"app_btn_crash_test\">模拟Crash</string>\n    <string name=\"app_btn_create_database\">创建数据库</string>\n    <string name=\"app_btn_upload_file\">上传文件</string>\n    <string name=\"app_btn_download_file\">下载文件</string>\n    <string name=\"dk_kit_simple_float\" translatable=\"false\">自定义悬浮</string>\n    <string name=\"dk_kit_fullscreen\" translatable=\"false\">自定义全屏</string>\n\n    <string-array name=\"city\">\n        <item> 北京</item>\n        <item>上海</item>\n        <item>广州</item>\n        <item>深圳</item>\n    </string-array>\n\n</resources>\n"
  },
  {
    "path": "Android/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.DayNight\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <style name=\"NoTitleTheme\" parent=\"Theme.MaterialComponents.DayNight.NoActionBar\">\n        <item name=\"android:windowNoTitle\">true</item>\n    </style>\n\n    <style name=\"DarkTitleTheme\" parent=\"Theme.MaterialComponents.DayNight.NoActionBar\">\n        <item name=\"android:windowNoTitle\">true</item>\n    </style>\n\n    <declare-styleable name=\"UnlockBar\">\n        <attr name=\"label\" format=\"string\" />\n        <attr name=\"textAlign\">\n            <enum name=\"center\" value=\"0\" />\n            <enum name=\"centerExceptThumb\" value=\"1\" />\n        </attr>\n    </declare-styleable>\n\n\n</resources>\n"
  },
  {
    "path": "Android/app/src/main/res/values-en-rUS/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">DoKit</string>\n    <string name=\"app_build_types\">BuildType</string>\n    <string name=\"app_btn_jump\">Jump Other Activity</string>\n    <string name=\"app_btn_mc\">MC</string>\n    <string name=\"app_btn_show_hide_icon\">Show/Hide Dokit icon</string>\n    <string name=\"app_btn_leak\">Memory Leaks</string>\n    <string name=\"app_btn_app_launch_stack\">app launch method stack</string>\n    <string name=\"app_btn_show_tool_panel\">Show Tool Panel</string>\n    <string name=\"app_btn_method_cost\">Method Cost Time(TAG:MethodCostUtil)</string>\n    <string name=\"app_btn_location\">Location(System)</string>\n    <string name=\"app_btn_location_map\">Location((amap、baidu、tencent))</string>\n    <string name=\"app_btn_location_map2\">Map Showing Location</string>\n    <string name=\"app_btn_load_img\">Big Img</string>\n    <string name=\"app_btn_okhttp_mock\">OkHttp Mock</string>\n    <string name=\"app_btn_urlconnection_mock\">HttpURLConnection Mock</string>\n    <string name=\"app_btn_custom_mock\">Custom Net Mock</string>\n    <string name=\"app_btn_retrofit_mock\">Retrofit Mock</string>\n    <string name=\"app_btn_crash_test\">Crash</string>\n    <string name=\"app_btn_create_database\">Create DB</string>\n    <string name=\"app_btn_upload_file\">Upload File</string>\n    <string name=\"app_btn_download_file\">Download File</string>\n</resources>\n"
  },
  {
    "path": "Android/app/src/main/res/values-zh-rCN/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">DoKit</string>\n    <string name=\"app_build_types\">当前编译环境</string>\n    <string name=\"app_btn_jump\">跳转其他Activity</string>\n    <string name=\"app_btn_mc\">一机多控</string>\n    <string name=\"app_btn_show_hide_icon\">显示/隐藏DoKit入口</string>\n    <string name=\"app_btn_leak\">模拟内存泄漏</string>\n    <string name=\"app_btn_app_launch_stack\">app启动函数调用栈</string>\n    <string name=\"app_btn_show_tool_panel\">显示工具面板</string>\n    <string name=\"app_btn_method_cost\">函数调用耗时(TAG:MethodCostUtil)</string>\n    <string name=\"app_btn_location\">获取位置信息(系统)</string>\n    <string name=\"app_btn_location_map\">获取位置信息(三方地图)</string>\n    <string name=\"app_btn_location_map2\">地图展示定位</string>\n    <string name=\"app_btn_load_img\">大图检测</string>\n    <string name=\"app_btn_okhttp_mock\">OkHttp Mock</string>\n    <string name=\"app_btn_urlconnection_mock\">HttpURLConnection Mock</string>\n    <string name=\"app_btn_custom_mock\">其他网络库(自定义抓包)</string>\n    <string name=\"app_btn_retrofit_mock\">Retrofit Mock</string>\n    <string name=\"app_btn_crash_test\">模拟Crash</string>\n    <string name=\"app_btn_create_database\">创建数据库</string>\n    <string name=\"app_btn_upload_file\">上传文件</string>\n    <string name=\"app_btn_download_file\">下载文件</string>\n</resources>\n"
  },
  {
    "path": "Android/app/src/main/res/xml/network_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config>\n    <base-config cleartextTrafficPermitted=\"true\">\n        <trust-anchors>\n            <certificates src=\"system\" />\n        </trust-anchors>\n    </base-config>\n</network-security-config>"
  },
  {
    "path": "Android/app/src/release/java/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.didichuxing.doraemondemo\">\n\n    <!--    tools:replace问题参考  https://developer.android.google.cn/studio/build/manifest-merge?hl=zh-cn tools:replace部分-->\n    <application\n        android:name=\".App\"\n        android:allowBackup=\"false\"\n        android:icon=\"@mipmap/dk_app_icon\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\"\n        tools:replace=\"android:allowBackup\">\n\n        <activity android:name=\".old.MainDebugActivityOkhttpV3\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n\n    </application>\n</manifest>\n"
  },
  {
    "path": "Android/app/src/release/java/com/didichuxing/doraemondemo/App.kt",
    "content": "package com.didichuxing.doraemondemo\n\nimport android.app.Activity\nimport android.app.Application\nimport android.content.Context\nimport android.view.View\nimport android.view.accessibility.AccessibilityEvent\nimport androidx.multidex.MultiDex\nimport com.baidu.mapapi.CoordType\nimport com.baidu.mapapi.SDKInitializer\nimport com.blankj.utilcode.util.FileUtils\nimport com.blankj.utilcode.util.PathUtils\nimport com.didichuxing.doraemondemo.dokit.DemoKit\nimport com.didichuxing.doraemondemo.dokit.TestSimpleDokitFloatViewKit\nimport com.didichuxing.doraemondemo.dokit.TestSimpleDokitFragmentKit\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.DoKitCallBack\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.core.McClientProcessor\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitExtInterceptor\nimport com.facebook.drawee.backends.pipeline.Fresco\nimport com.facebook.imagepipeline.core.ImagePipelineConfig\nimport com.lzy.okgo.OkGo\nimport okhttp3.Cache\nimport okhttp3.Interceptor\nimport okhttp3.OkHttpClient\nimport okhttp3.Response\nimport java.io.File\n\n/**\n * @author jint\n * @mail 704167880@qq.com\n */\nclass App : Application() {\n\n    override fun onCreate() {\n        super.onCreate()\n        //百度地图初始化\n        SDKInitializer.initialize(this)\n        SDKInitializer.setCoordType(CoordType.BD09LL)\n        //测试环境:a49842eeebeb1989b3f9565eb12c276b\n        //线上环境:749a0600b5e48dd77cf8ee680be7b1b7\n        //DoraemonKit.disableUpload()\n        //是否显示入口icon\n        // DoraemonKit.setAwaysShowMainIcon(false);\n\n\n        val kits: MutableList<AbstractKit> = ArrayList()\n        kits.add(DemoKit())\n        kits.add(TestSimpleDokitFloatViewKit())\n        kits.add(TestSimpleDokitFragmentKit())\n\n        val mapKits: LinkedHashMap<String, List<AbstractKit>> = linkedMapOf()\n        mapKits[\"业务专区1\"] = mutableListOf<AbstractKit>().apply {\n            add(DemoKit())\n            add(TestSimpleDokitFloatViewKit())\n            add(TestSimpleDokitFragmentKit())\n        }\n\n        mapKits[\"业务专区2\"] = mutableListOf<AbstractKit>(DemoKit())\n\n\n\n        DoKit.Builder(this)\n            .productId(\"749a0600b5e48dd77cf8ee680be7b1b7\")\n//            .productId(\"277016abcc33bff1e6a4f1afdf14b8e1\")\n            .disableUpload()\n            .customKits(mapKits)\n            .fileManagerHttpPort(9001)\n            .databasePass(mapOf(\"Person.db\" to \"a_password\"))\n            .mcWSPort(5555)\n            .alwaysShowMainIcon(true)\n            .callBack(object : DoKitCallBack {\n                override fun onCpuCallBack(value: Float, filePath: String) {\n                    super.onCpuCallBack(value, filePath)\n                }\n\n                override fun onFpsCallBack(value: Float, filePath: String) {\n                    super.onFpsCallBack(value, filePath)\n                }\n\n                override fun onMemoryCallBack(value: Float, filePath: String) {\n                    super.onMemoryCallBack(value, filePath)\n                }\n\n                override fun onNetworkCallBack(record: NetworkRecord) {\n                    super.onNetworkCallBack(record)\n                }\n            })\n            .netExtInterceptor(object : DokitExtInterceptor.DokitExtInterceptorProxy {\n                override fun intercept(chain: Interceptor.Chain): Response {\n                    return chain.proceed(chain.request())\n                }\n\n            })\n            .mcClientProcess(object : McClientProcessor {\n                override fun process(\n                    activity: Activity?,\n                    view: View?,\n                    eventType: String,\n                    params: Map<String, String>\n                ) {\n                }\n\n            })\n            .build()\n\n\n        val client: OkHttpClient = OkHttpClient.Builder()\n            .addInterceptor(CustomInterceptor())\n            .cache(Cache(File(\"${PathUtils.getInternalAppCachePath()}/dokit\"), 1024 * 1024 * 100))\n            .build()\n        OkGo.getInstance().init(this).okHttpClient = client\n\n        val config = ImagePipelineConfig.newBuilder(this)\n            .setDiskCacheEnabled(false)\n            .build()\n        Fresco.initialize(this, config)\n\n        //严格检查模式\n        //StrictMode.enableDefaults();\n    }\n\n    override fun attachBaseContext(base: Context) {\n        super.attachBaseContext(base)\n        MultiDex.install(this)\n    }\n\n    companion object {\n        private const val TAG = \"App\"\n        var leakActivity: Activity? = null\n    }\n}"
  },
  {
    "path": "Android/app/src/test/java/com/didichuxing/doraemondemo/ExampleUnitTest.kt",
    "content": "package com.didichuxing.doraemondemo\n\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.launch\nimport okio.ByteString.Companion.encodeUtf8\nimport org.junit.Test\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.lang.reflect.Proxy\nimport java.util.*\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see [Testing documentation](http://d.android.com/tools/testing)\n */\nclass ExampleUnitTest {\n\n\n    @Test\n    fun toDex() {\n        val DEX =\n            \"ZGV4CjAzNQCl4EprGS2pXI/v3OwlBrlfRnX5rmkKVdN0CwAAcAAAAHhWNBIAAAAAAAAAAMgKAABEAAAAcAAAABMAAACAAQAACwAAAMwBAAAMAAAAUAIAAA8AAACwAgAAAwAAACgDAADsBwAAiAMAABYGAAAYBgAAHQYAACcGAAAvBgAAPwYAAEsGAABbBgAAcAYAAIIGAACJBgAAkQYAAJQGAACYBgAAnAYAAKIGAAClBgAAqgYAAMUGAADrBgAABwcAABsHAAAuBwAARAcAAFgHAABsBwAAgAcAAJcHAACzBwAA2wcAAAIIAAAlCAAAMQgAAEIIAABLCAAAUAgAAFMIAABhCAAAbwgAAHMIAAB2CAAAeggAAI4IAACjCAAAuAgAAMEIAADaCAAA3QgAAOUIAADwCAAA+QgAAAoJAAAeCQAAMQkAAD0JAABFCQAAUgkAAGwJAAB0CQAAfQkAAJgJAAChCQAArQkAAMUJAADXCQAA3QkAAOUJAADzCQAACwAAABEAAAASAAAAEwAAABQAAAAVAAAAFwAAABgAAAAZAAAAGgAAABsAAAAcAAAAHQAAAB4AAAAjAAAAJwAAACkAAAAqAAAAKwAAAAwAAAAAAAAA3AUAAA0AAAAAAAAA5AUAAA4AAAAAAAAA7AUAAA8AAAACAAAAAAAAABAAAAAGAAAA+AUAABAAAAAKAAAAAAYAACMAAAAOAAAAAAAAACYAAAAOAAAACAYAACcAAAAPAAAAAAAAACgAAAAPAAAACAYAACgAAAAPAAAAEAYAAAIAAAA/AAAAAwAAACEAAAALAAcABAAAAAsABwAFAAAACwAPAAkAAAALAAcACgAAAAsAAAAkAAAACwAHACUAAAAMAAcAIgAAAAwABgA9AAAADAAKAD4AAAANAAcAIgAAAAEAAwAzAAAABAACAC4AAAAFAAUANAAAAAYABgADAAAACAAHADcAAAAKAAQANgAAAAsABgADAAAADAAGAAIAAAAMAAYAAwAAAAwACQAvAAAADAAKAC8AAAAMAAgAMAAAAA0ABgADAAAADQABAEEAAAANAAAAQgAAAAsAAAARAAAABgAAAAAAAAAIAAAAAAAAAHgKAABmCgAADAAAABEAAAAGAAAAAAAAAAcAAAAAAAAAjgoAAHIKAAANAAAAAQAAAAYAAAAAAAAAIAAAAAAAAACxCgAAdQoAAAEAAQABAAAAAwoAAAQAAABwEAMAAAAOAAoAAAADAAEACAoAAHsAAABgBQEAEwYcADRlbQAcBQUAGgYxABIXI3cQABIIHAkHAE0JBwhuMAIAZQcMARwFBQAaBjQAEicjdxAAEggcCQcATQkHCBIYHAkQAE0JBwhuMAIAZQcMAhIFEhYjZhEAEgcaCC0ATQgGB24wBQBRBgwEHwQFABIlI1URABIGGgc1AE0HBQYSFhIHTQcFBm4wBQBCBQwDHwMKABIlI1URABIGGgc+AE0HBQYSFhIXI3cQABIIHAkSAE0JBwhNBwUGbjAFAEIFDAUfBQoAaQUKABIFEgYjZhEAbjAFAFMGDAVpBQkADgANABoFBgAaBjsAcTABAGUAKPcAAAYAAABrAAEAAQEJcgEAAQABAAAANwoAAAQAAABwEAMAAAAOAAMAAQABAAAAPAoAAAsAAAASECMAEgASAU0CAAFxEAoAAAAKAA8AAAAIAAEAAwABAEIKAAAdAAAAEhESAmIDCQA4AwYAYgMKADkDBAABIQ8BYgMKAGIECQASFSNVEQASBk0HBQZuMAUAQwUo8g0AASEo7wAADAAAAA0AAQABAQkaAwAAAAEAAABSCgAADQAAABIQIwASABIBGgIPAE0CAAFxEAoAAAAKAA8AAAABAAEAAQAAAFcKAAAEAAAAcBADAAAADgAEAAEAAQAAAFwKAAAeAAAAEgBgAQEAEwIcADUhAwAPAHEACwAAAAoBOQH7/xoAMgBxEAQAAABuEAAAAwAMAFIAAABxEA4AAAAKACjqAQAAAAAAAAABAAAAAQAAAAMAAAAHAAcACQAAAAIAAAAGABEAAgAAAAcAEAABAAAABwAAAAEAAAASAAAAAzEuMAAIPGNsaW5pdD4ABjxpbml0PgAOQVBQTElDQVRJT05fSUQACkJVSUxEX1RZUEUADkJvb3RzdHJhcENsYXNzABNCb290c3RyYXBDbGFzcy5qYXZhABBCdWlsZENvbmZpZy5qYXZhAAVERUJVRwAGRkxBVk9SAAFJAAJJSQACSUwABElMTEwAAUwAA0xMTAAZTGFuZHJvaWQvY29udGVudC9Db250ZXh0OwAkTGFuZHJvaWQvY29udGVudC9wbS9BcHBsaWNhdGlvbkluZm87ABpMYW5kcm9pZC9vcy9CdWlsZCRWRVJTSU9OOwASTGFuZHJvaWQvdXRpbC9Mb2c7ABFMamF2YS9sYW5nL0NsYXNzOwAUTGphdmEvbGFuZy9DbGFzczwqPjsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5nL1N5c3RlbTsAFUxqYXZhL2xhbmcvVGhyb3dhYmxlOwAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsAJkxtZS93ZWlzaHUvZnJlZXJlZmxlY3Rpb24vQnVpbGRDb25maWc7ACVMbWUvd2Vpc2h1L3JlZmxlY3Rpb24vQm9vdHN0cmFwQ2xhc3M7ACFMbWUvd2Vpc2h1L3JlZmxlY3Rpb24vUmVmbGVjdGlvbjsAClJlZmxlY3Rpb24AD1JlZmxlY3Rpb24uamF2YQAHU0RLX0lOVAADVEFHAAFWAAxWRVJTSU9OX0NPREUADFZFUlNJT05fTkFNRQACVkwAAVoAAlpMABJbTGphdmEvbGFuZy9DbGFzczsAE1tMamF2YS9sYW5nL09iamVjdDsAE1tMamF2YS9sYW5nL1N0cmluZzsAB2NvbnRleHQAF2RhbHZpay5zeXN0ZW0uVk1SdW50aW1lAAFlAAZleGVtcHQACWV4ZW1wdEFsbAAHZm9yTmFtZQAPZnJlZS1yZWZsZWN0aW9uABJnZXRBcHBsaWNhdGlvbkluZm8AEWdldERlY2xhcmVkTWV0aG9kAApnZXRSdW50aW1lAAZpbnZva2UAC2xvYWRMaWJyYXJ5ABhtZS53ZWlzaHUuZnJlZXJlZmxlY3Rpb24ABm1ldGhvZAAHbWV0aG9kcwAZcmVmbGVjdCBib290c3RyYXAgZmFpbGVkOgAHcmVsZWFzZQAKc1ZtUnVudGltZQAWc2V0SGlkZGVuQXBpRXhlbXB0aW9ucwAQdGFyZ2V0U2RrVmVyc2lvbgAEdGhpcwAGdW5zZWFsAAx1bnNlYWxOYXRpdmUADnZtUnVudGltZUNsYXNzAAYABw4AFgAHDmr/AwEyCwEVEAMCNQvwBAREBhcBEg8DAzYLARsPqQUCBQMFBBkeAwAvCgAOAAcOACwBOgcOADYBOwcsnRriAQEDAC8KHgBIAAcOAA0ABw4AEwEtBx1yGWtaAAYXOBc8HxcABAEXAQEXBgEXHwYAAQACGQEZARkBGQEZARkGgYAEiAcDAAUACBoBCgEKB4iABKAHAYGABLQJAQnMCQGJAfQJAQnMCgEAAwALGgyBgAT4CgEJkAsBigIAAAAADgAAAAAAAAABAAAAAAAAAAEAAABEAAAAcAAAAAIAAAATAAAAgAEAAAMAAAALAAAAzAEAAAQAAAAMAAAAUAIAAAUAAAAPAAAAsAIAAAYAAAADAAAAKAMAAAEgAAAIAAAAiAMAAAEQAAAHAAAA3AUAAAIgAABEAAAAFgYAAAMgAAAIAAAAAwoAAAUgAAADAAAAZgoAAAAgAAADAAAAeAoAAAAQAAABAAAAyAoAAA==\"\n        val bytes = Base64.getDecoder().decode(DEX)\n        val code: File = File(\"/Users/didi\", \"test.dex\")\n\n        FileOutputStream(code).use { fos -> fos.write(bytes) }\n    }\n\n    @Test\n    @Throws(Exception::class)\n    fun hex() {\n//        val originKey =\n//            \"method=POST&path=/gateway&fragment=null&query={\\\"api\\\":\\\"lj.u.d.changeOnline\\\",\\\"appKey\\\":\\\"b4f945fe780140d8a0d19d1f2d021db7\\\"}&contentType=application/json; charset=utf-8&requestBody={\\\"type\\\":1.0}\"\n\n        val originKey =\n            \"method=GET&path=/test&fragment=null&query={}&contentType=jsona&requestBody={\\\"1\\\":\\\"111\\\",\\\"a\\\":\\\"aaa\\\",\\\"b\\\":\\\"bbb\\\",\\\"c\\\":\\\"ccc\\\",\\\"是\\\":\\\"是是是\\\"}\"\n        val hex = originKey.encodeUtf8().md5().hex()\n\n        println(\"hex===>${hex}\")\n\n\n    }\n\n\n    @Test\n    fun testMapSort() {\n        val maps = mapOf(\n            \"Nepal\" to \"Kathmandu\",\n            \"India\" to \"New Delhi\",\n            \"United States\" to \"Washington\",\n            \"England\" to \"London\",\n            \"Australia\" to \"Canberra\",\n            \"bb\" to \"bbb\",\n            \"aa\" to \"aaa\",\n            \"222\" to \"222\",\n            \"111\" to \"111\"\n        )\n\n        val result = maps.toList().sortedBy { (key, _) -> key }.toMap()\n\n        result.forEach { (key, value) ->\n            println(\"key===>$key  value===>$value\")\n        }\n\n    }\n\n    @Test\n    fun testJson() {\n        val maps = mapOf(\n            \"Nepal\" to \"Kathmandu\",\n            \"India\" to \"New Delhi\",\n            \"United States\" to \"Washington\",\n            \"England\" to \"London\",\n            \"Australia\" to \"Canberra\",\n            \"bb\" to \"bbb\",\n            \"aa\" to \"aaa\",\n            \"222\" to \"222\",\n            \"111\" to \"111\"\n        )\n\n        val result = maps.toList().sortedBy { (key, _) -> key }.toMap()\n\n        result.forEach { (key, value) ->\n            println(\"key===>$key  value===>$value\")\n        }\n\n    }\n\n    @Test\n    fun urlEncode() {\n        val json = \"{\\n\" +\n            \"    \\\"data\\\": {\\n\" +\n            \"        \\\"curPage\\\": 1,\\n\" +\n            \"        \\\"datas\\\": [\\n\" +\n            \"            {\\n\" +\n            \"                \\\"apkLink\\\": \\\"\\\",\\n\" +\n            \"                \\\"audit\\\": 1,\\n\" +\n            \"                \\\"author\\\": \\\"\\\",\\n\" +\n            \"                \\\"canEdit\\\": false,\\n\" +\n            \"                \\\"chapterId\\\": 494,\\n\" +\n            \"                \\\"chapterName\\\": \\\"广场\\\",\\n\" +\n            \"                \\\"collect\\\": false,\\n\" +\n            \"                \\\"courseId\\\": 13,\\n\" +\n            \"                \\\"desc\\\": \\\"\\\",\\n\" +\n            \"                \\\"descMd\\\": \\\"\\\",\\n\" +\n            \"                \\\"envelopePic\\\": \\\"\\\",\\n\" +\n            \"                \\\"fresh\\\": false,\\n\" +\n            \"                \\\"host\\\": \\\"\\\",\\n\" +\n            \"                \\\"id\\\": 18661,\\n\" +\n            \"                \\\"link\\\": \\\"https://juejin.cn/post/6973900070358319135\\\",\\n\" +\n            \"                \\\"niceDate\\\": \\\"2天前\\\",\\n\" +\n            \"                \\\"niceShareDate\\\": \\\"2天前\\\",\\n\" +\n            \"                \\\"origin\\\": \\\"\\\",\\n\" +\n            \"                \\\"prefix\\\": \\\"\\\",\\n\" +\n            \"                \\\"projectLink\\\": \\\"\\\",\\n\" +\n            \"                \\\"publishTime\\\": 1624099393000,\\n\" +\n            \"                \\\"realSuperChapterId\\\": 493,\\n\" +\n            \"                \\\"selfVisible\\\": 0,\\n\" +\n            \"                \\\"shareDate\\\": 1624099393000,\\n\" +\n            \"                \\\"shareUser\\\": \\\"鸿洋\\\",\\n\" +\n            \"                \\\"superChapterId\\\": 494,\\n\" +\n            \"                \\\"superChapterName\\\": \\\"广场Tab\\\",\\n\" +\n            \"                \\\"tags\\\": [],\\n\" +\n            \"                \\\"title\\\": \\\"源码篇：Flutter Bloc背后的思想，一篇纠结的文章\\\",\\n\" +\n            \"                \\\"type\\\": 0,\\n\" +\n            \"                \\\"userId\\\": 2,\\n\" +\n            \"                \\\"visible\\\": 0,\\n\" +\n            \"                \\\"zan\\\": 0\\n\" +\n            \"            },\\n\" +\n            \"            {\\n\" +\n            \"                \\\"apkLink\\\": \\\"\\\",\\n\" +\n            \"                \\\"audit\\\": 1,\\n\" +\n            \"                \\\"author\\\": \\\"\\\",\\n\" +\n            \"                \\\"canEdit\\\": false,\\n\" +\n            \"                \\\"chapterId\\\": 494,\\n\" +\n            \"                \\\"chapterName\\\": \\\"广场\\\",\\n\" +\n            \"                \\\"collect\\\": false,\\n\" +\n            \"                \\\"courseId\\\": 13,\\n\" +\n            \"                \\\"desc\\\": \\\"\\\",\\n\" +\n            \"                \\\"descMd\\\": \\\"\\\",\\n\" +\n            \"                \\\"envelopePic\\\": \\\"\\\",\\n\" +\n            \"                \\\"fresh\\\": false,\\n\" +\n            \"                \\\"host\\\": \\\"\\\",\\n\" +\n            \"                \\\"id\\\": 18660,\\n\" +\n            \"                \\\"link\\\": \\\"https://juejin.cn/post/6974734070999679013\\\",\\n\" +\n            \"                \\\"niceDate\\\": \\\"2天前\\\",\\n\" +\n            \"                \\\"niceShareDate\\\": \\\"2天前\\\",\\n\" +\n            \"                \\\"origin\\\": \\\"\\\",\\n\" +\n            \"                \\\"prefix\\\": \\\"\\\",\\n\" +\n            \"                \\\"projectLink\\\": \\\"\\\",\\n\" +\n            \"                \\\"publishTime\\\": 1624090904000,\\n\" +\n            \"                \\\"realSuperChapterId\\\": 493,\\n\" +\n            \"                \\\"selfVisible\\\": 0,\\n\" +\n            \"                \\\"shareDate\\\": 1624090904000,\\n\" +\n            \"                \\\"shareUser\\\": \\\"goweii\\\",\\n\" +\n            \"                \\\"superChapterId\\\": 494,\\n\" +\n            \"                \\\"superChapterName\\\": \\\"广场Tab\\\",\\n\" +\n            \"                \\\"tags\\\": [],\\n\" +\n            \"                \\\"title\\\": \\\"我又开发了一个非常好用的开源库，调试Android数据库有救了\\\",\\n\" +\n            \"                \\\"type\\\": 0,\\n\" +\n            \"                \\\"userId\\\": 20382,\\n\" +\n            \"                \\\"visible\\\": 0,\\n\" +\n            \"                \\\"zan\\\": 0\\n\" +\n            \"            }\\n\" +\n            \"        ],\\n\" +\n            \"        \\\"offset\\\": 0,\\n\" +\n            \"        \\\"over\\\": false,\\n\" +\n            \"        \\\"pageCount\\\": 154,\\n\" +\n            \"        \\\"size\\\": 20,\\n\" +\n            \"        \\\"total\\\": 3063\\n\" +\n            \"    },\\n\" +\n            \"    \\\"errorCode\\\": 0,\\n\" +\n            \"    \\\"errorMsg\\\": \\\"\\\"\\n\" +\n            \"}\"\n\n\n        val encode = Base64.getEncoder().encodeToString(json.toByteArray())\n\n        println(encode)\n        val base64 =\n            \"ewogICAgImRhdGEiOiB7CiAgICAgICAgImN1clBhZ2UiOiAxLAogICAgICAgICJkYXRhcyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImFwa0xpbmsiOiAiIiwKICAgICAgICAgICAgICAgICJhdWRpdCI6IDEsCiAgICAgICAgICAgICAgICAiYXV0aG9yIjogIiIsCiAgICAgICAgICAgICAgICAiY2FuRWRpdCI6IGZhbHNlLAogICAgICAgICAgICAgICAgImNoYXB0ZXJJZCI6IDQ5NCwKICAgICAgICAgICAgICAgICJjaGFwdGVyTmFtZSI6ICLlub/lnLoiLAogICAgICAgICAgICAgICAgImNvbGxlY3QiOiBmYWxzZSwKICAgICAgICAgICAgICAgICJjb3Vyc2VJZCI6IDEzLAogICAgICAgICAgICAgICAgImRlc2MiOiAiIiwKICAgICAgICAgICAgICAgICJkZXNjTWQiOiAiIiwKICAgICAgICAgICAgICAgICJlbnZlbG9wZVBpYyI6ICIiLAogICAgICAgICAgICAgICAgImZyZXNoIjogZmFsc2UsCiAgICAgICAgICAgICAgICAiaG9zdCI6ICIiLAogICAgICAgICAgICAgICAgImlkIjogMTg2NjEsCiAgICAgICAgICAgICAgICAibGluayI6ICJodHRwczovL2p1ZWppbi5jbi9wb3N0LzY5NzM5MDAwNzAzNTgzMTkxMzUiLAogICAgICAgICAgICAgICAgIm5pY2VEYXRlIjogIjLlpKnliY0iLAogICAgICAgICAgICAgICAgIm5pY2VTaGFyZURhdGUiOiAiMuWkqeWJjSIsCiAgICAgICAgICAgICAgICAib3JpZ2luIjogIiIsCiAgICAgICAgICAgICAgICAicHJlZml4IjogIiIsCiAgICAgICAgICAgICAgICAicHJvamVjdExpbmsiOiAiIiwKICAgICAgICAgICAgICAgICJwdWJsaXNoVGltZSI6IDE2MjQwOTkzOTMwMDAsCiAgICAgICAgICAgICAgICAicmVhbFN1cGVyQ2hhcHRlcklkIjogNDkzLAogICAgICAgICAgICAgICAgInNlbGZWaXNpYmxlIjogMCwKICAgICAgICAgICAgICAgICJzaGFyZURhdGUiOiAxNjI0MDk5MzkzMDAwLAogICAgICAgICAgICAgICAgInNoYXJlVXNlciI6ICLpuL/mtIsiLAogICAgICAgICAgICAgICAgInN1cGVyQ2hhcHRlcklkIjogNDk0LAogICAgICAgICAgICAgICAgInN1cGVyQ2hhcHRlck5hbWUiOiAi5bm/5Zy6VGFiIiwKICAgICAgICAgICAgICAgICJ0YWdzIjogW10sCiAgICAgICAgICAgICAgICAidGl0bGUiOiAi5rqQ56CB56+H77yaRmx1dHRlciBCbG9j6IOM5ZCO55qE5oCd5oOz77yM5LiA56+H57qg57uT55qE5paH56ugIiwKICAgICAgICAgICAgICAgICJ0eXBlIjogMCwKICAgICAgICAgICAgICAgICJ1c2VySWQiOiAyLAogICAgICAgICAgICAgICAgInZpc2libGUiOiAwLAogICAgICAgICAgICAgICAgInphbiI6IDAKICAgICAgICAgICAgfSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImFwa0xpbmsiOiAiIiwKICAgICAgICAgICAgICAgICJhdWRpdCI6IDEsCiAgICAgICAgICAgICAgICAiYXV0aG9yIjogIiIsCiAgICAgICAgICAgICAgICAiY2FuRWRpdCI6IGZhbHNlLAogICAgICAgICAgICAgICAgImNoYXB0ZXJJZCI6IDQ5NCwKICAgICAgICAgICAgICAgICJjaGFwdGVyTmFtZSI6ICLlub/lnLoiLAogICAgICAgICAgICAgICAgImNvbGxlY3QiOiBmYWxzZSwKICAgICAgICAgICAgICAgICJjb3Vyc2VJZCI6IDEzLAogICAgICAgICAgICAgICAgImRlc2MiOiAiIiwKICAgICAgICAgICAgICAgICJkZXNjTWQiOiAiIiwKICAgICAgICAgICAgICAgICJlbnZlbG9wZVBpYyI6ICIiLAogICAgICAgICAgICAgICAgImZyZXNoIjogZmFsc2UsCiAgICAgICAgICAgICAgICAiaG9zdCI6ICIiLAogICAgICAgICAgICAgICAgImlkIjogMTg2NjAsCiAgICAgICAgICAgICAgICAibGluayI6ICJodHRwczovL2p1ZWppbi5jbi9wb3N0LzY5NzQ3MzQwNzA5OTk2NzkwMTMiLAogICAgICAgICAgICAgICAgIm5pY2VEYXRlIjogIjLlpKnliY0iLAogICAgICAgICAgICAgICAgIm5pY2VTaGFyZURhdGUiOiAiMuWkqeWJjSIsCiAgICAgICAgICAgICAgICAib3JpZ2luIjogIiIsCiAgICAgICAgICAgICAgICAicHJlZml4IjogIiIsCiAgICAgICAgICAgICAgICAicHJvamVjdExpbmsiOiAiIiwKICAgICAgICAgICAgICAgICJwdWJsaXNoVGltZSI6IDE2MjQwOTA5MDQwMDAsCiAgICAgICAgICAgICAgICAicmVhbFN1cGVyQ2hhcHRlcklkIjogNDkzLAogICAgICAgICAgICAgICAgInNlbGZWaXNpYmxlIjogMCwKICAgICAgICAgICAgICAgICJzaGFyZURhdGUiOiAxNjI0MDkwOTA0MDAwLAogICAgICAgICAgICAgICAgInNoYXJlVXNlciI6ICJnb3dlaWkiLAogICAgICAgICAgICAgICAgInN1cGVyQ2hhcHRlcklkIjogNDk0LAogICAgICAgICAgICAgICAgInN1cGVyQ2hhcHRlck5hbWUiOiAi5bm/5Zy6VGFiIiwKICAgICAgICAgICAgICAgICJ0YWdzIjogW10sCiAgICAgICAgICAgICAgICAidGl0bGUiOiAi5oiR5Y+I5byA5Y+R5LqG5LiA5Liq6Z2e5bi45aW955So55qE5byA5rqQ5bqT77yM6LCD6K+VQW5kcm9pZOaVsOaNruW6k+acieaVkeS6hiIsCiAgICAgICAgICAgICAgICAidHlwZSI6IDAsCiAgICAgICAgICAgICAgICAidXNlcklkIjogMjAzODIsCiAgICAgICAgICAgICAgICAidmlzaWJsZSI6IDAsCiAgICAgICAgICAgICAgICAiemFuIjogMAogICAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAib2Zmc2V0IjogMCwKICAgICAgICAib3ZlciI6IGZhbHNlLAogICAgICAgICJwYWdlQ291bnQiOiAxNTQsCiAgICAgICAgInNpemUiOiAyMCwKICAgICAgICAidG90YWwiOiAzMDYzCiAgICB9LAogICAgImVycm9yQ29kZSI6IDAsCiAgICAiZXJyb3JNc2ciOiAiIgp9\"\n        val decode = String(Base64.getDecoder().decode(base64))\n        println(decode)\n\n//        val encode = EncodeUtils.binaryEncode(aa)\n//        val binary = \"1111011 100010 1100001 100010 111010 100010 1100001 1100001 100010 101100 100010 1100010 100010 111010 100010 1100010 1100010 100010 1111101\"\n//        val decode = EncodeUtils.binaryDecode(binary)\n//        println(encode)\n//        println(decode)\n\n    }\n\n\n}\n\n"
  },
  {
    "path": "Android/app/src/test/java/com/didichuxing/doraemondemo/KotlinBaseUnitTest.kt",
    "content": "package com.didichuxing.doraemondemo\n\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.launch\nimport okio.ByteString.Companion.encodeUtf8\nimport org.junit.Test\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.lang.reflect.Proxy\nimport java.util.*\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see [Testing documentation](http://d.android.com/tools/testing)\n */\nclass KotlinBaseUnitTest {\n\n    @Test\n    @Throws(Exception::class)\n    fun run() {\n        a {\n\n        }\n\n        b {\n            launch {\n\n            }\n        }\n\n        c {\n\n        }\n\n        d {\n            launch {\n\n            }\n        }\n\n    }\n\n    private fun a(block: suspend () -> Unit) {\n\n    }\n\n    private fun b(block: suspend CoroutineScope.() -> Unit) {\n\n    }\n\n    private fun c(block: () -> Unit) {\n\n    }\n\n    private fun d(block: CoroutineScope.() -> Unit) {\n\n    }\n\n\n}\n\n"
  },
  {
    "path": "Android/app/src/test/java/com/didichuxing/doraemondemo/KotlinCaseTest.kt",
    "content": "package com.didichuxing.doraemondemo\n\nimport org.junit.Test\n\nclass KotlinCaseTest {\n\n\n    @Test\n    @Throws(Exception::class)\n    fun testLet() {\n\n        println(\"----------------------\")\n        var target :String? = null\n\n        target?.let { name->\n            println(\"test:: $name\")\n        }?:run {\n            println(\"test:: $this\")\n        }\n        println(\"*********************\")\n    }\n}\n"
  },
  {
    "path": "Android/app/src/test/java/com/didichuxing/doraemondemo/TestJava.java",
    "content": "package com.didichuxing.doraemondemo;\n\nimport org.junit.Test;\n\n/**\n * didi Create on 2022/3/22 .\n * <p>\n * Copyright (c) 2022/3/22 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/22 7:43 下午\n * @Description 用一句话说明文件功能\n */\n\npublic class TestJava {\n\n    @Test\n    public void test() throws Exception{\n\n        System.out.println(\"test\");\n    }\n}\n"
  },
  {
    "path": "Android/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    apply from: \"config.gradle\"\n    def runType = rootProject.ext.publish_config[\"run_type\"]\n    def dokitPluginVersion = rootProject.ext.publish_config[\"version\"]\n\n    def kotlinVersion\n    if (needKotlinV14()) {\n        kotlinVersion = rootProject.ext.android[\"kotlin_version_v14\"]\n    } else {\n        kotlinVersion = rootProject.ext.android[\"kotlin_version_v13\"]\n    }\n\n    def gradlePluginVersion = rootProject.ext.android[\"agp_module_verson\"]\n\n\n    repositories {\n        google()\n        mavenCentral()\n        jcenter()\n        maven {\n            url \"https://oss.sonatype.org/content/groups/public\"\n        }\n\n        println(\"[dokit build] add buildscript private repositories\")\n        println(\"[dokit build] usePrivateEnv=\" + usePrivateEnv() +\",privateRepository=\"+ privateRepository())\n        println(\"[dokit build] useLocalRepository=\"+useLocalRepository()+\",localRepository=\"+localRepository() )\n\n        if (usePrivateEnv()){\n            maven {\n                url privateRepository()\n            }\n        }\n        if (useLocalRepository()){\n            maven {\n                url uri(localRepository())\n            }\n        }\n\n    }\n    dependencies {\n        classpath \"com.android.tools.build:gradle:$gradlePluginVersion\"\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion\"\n\n        // MavenCentral\n        classpath \"org.jetbrains.dokka:dokka-gradle-plugin:1.4.30\"\n        if (runType == 1) {\n            classpath \"io.github.didi.dokit:dokitx-plugin:$dokitPluginVersion\"\n        }\n\n//        classpath \"com.didiglobal.booster:booster-task-compression-pngquant:${rootProject.ext.android[\"booster_version\"]}\"\n\n//        classpath \"com.didiglobal.booster:booster-gradle-plugin:3.3.1\"\n//        classpath \"com.didiglobal.booster:booster-task-analyser:3.3.1\"\n    }\n\n}\n\napply from: \"./config.gradle\"\n\nallprojects {\n    def isRootProject = \"Dokit-Android\".equals(project.name)\n    repositories {\n        google()\n        mavenCentral()\n        jcenter()\n        maven {\n            url \"https://oss.sonatype.org/content/groups/public\"\n        }\n\n        println(\"[dokit build] add allprojects repositories config project=\" + project.name)\n        println(\"[dokit build] usePrivateEnv=\" + usePrivateEnv() + \",privateRepository=\" + privateRepository())\n        println(\"[dokit build] useLocalRepository=\" + useLocalRepository() + \",localRepository=\" + localRepository())\n\n        if (usePrivateEnv()) {\n            maven {\n                url privateRepository()\n            }\n        }\n\n        if (useLocalRepository()){\n            maven {\n                url uri(localRepository())\n            }\n        }\n    }\n}\n\ntask clean(type: Delete) {\n    doLast {\n        delete rootProject.buildDir\n    }\n}\n\n/**\n * 删除指定目录下的文件\n */\ntask deleteSource(type: Delete) {\n    doFirst {\n        println(\"path===>${project.rootDir}\")\n        println(\"===delete Plugin Source start===\")\n    }\n    followSymlinks = true\n    delete \"${project.rootDir}/dokit-plugin/src/main/kotlin\"\n    doLast {\n        println(\"===delete Plugin Source end===\")\n    }\n}\n\n/**\n * 将buildSrc中的代码同步到doraemonkit-plugin中\n */\ntask copyPluginSource(type: Copy, dependsOn: deleteSource) {\n    doFirst {\n        println(\"===copy Plugin Source start===\")\n    }\n\n    from \"${project.rootDir}/buildSrc/src/main/kotlin\"\n    into \"${project.rootDir}/dokit-plugin/src/main/kotlin\"\n    include('**/*')\n\n    doLast {\n        println(\"===copy Plugin Source end===\")\n    }\n}\n\n/**\n * 打包上传配置检查\n */\ntask checkUploadConfig4Maven() {\n    doLast {\n        if (rootProject.ext.publish_config[\"archives_type\"] != 2) {\n            throw new RuntimeException(\"执行当前打包上传任务必须修改config.gradle配置中的archives_type = 2。\")\n        }\n\n        //仓库检查\n        def repositories = new ArrayList<String>()\n\n        rootProject.repositories.forEach {\n            if (it instanceof MavenArtifactRepository) {\n                repositories.add((it as MavenArtifactRepository).url.toString())\n            }\n        }\n\n        //相关模块检查\n        def modules = new ArrayList<String>()\n\n        rootProject.allprojects.forEach {\n            modules.add(it.name)\n        }\n\n//        if (!modules.contains(\"dokit-rpc\")) {\n//            throw new RuntimeException(\"未找到dokit-rpc module。。。\")\n//        }\n//\n//        if (!modules.contains(\"dokit-rpc-mc\")) {\n//            throw new RuntimeException(\"未找到dokit-rpc-mc module。。。\")\n//        }\n\n        if (!modules.contains(\"dokit-plugin\")) {\n            throw new RuntimeException(\"未找到dokit-plugin module。。。\")\n        }\n    }\n}\n\ntask checkUploadConfig4Local() {\n    doLast {\n        //配置检查\n        if (rootProject.ext.publish_config[\"archives_type\"] != 0) {\n            throw new RuntimeException(\"执行当前打包上传任务必须修改config.gradle配置中的archives_type = 0。\")\n        }\n        //仓库检查\n        def repositories = new ArrayList<String>()\n\n        rootProject.repositories.forEach {\n            if (it instanceof MavenArtifactRepository) {\n                repositories.add((it as MavenArtifactRepository).url.toString())\n            }\n        }\n\n        //相关模块检查\n        def modules = new ArrayList<String>()\n\n        rootProject.allprojects.forEach {\n            modules.add(it.name)\n        }\n\n//        if (!modules.contains(\"dokit-rpc\")) {\n//            throw new RuntimeException(\"未找到dokit-rpc module。。。\")\n//        }\n//\n//        if (!modules.contains(\"dokit-rpc-mc\")) {\n//            throw new RuntimeException(\"未找到dokit-rpc-mc module。。。\")\n//        }\n\n        if (!modules.contains(\"dokit-plugin\")) {\n            throw new RuntimeException(\"未找到dokit-plugin module。。。\")\n        }\n    }\n}\n\ntask checkUploadConfig4Didi() {\n    doLast {\n        if (rootProject.ext.publish_config[\"archives_type\"] != 1) {\n            throw new RuntimeException(\"执行当前打包上传任务必须修改config.gradle配置中的archives_type = 1。\")\n        }\n        //仓库检查\n        def repositories = new ArrayList<String>()\n\n        rootProject.repositories.forEach {\n            if (it instanceof MavenArtifactRepository) {\n                repositories.add((it as MavenArtifactRepository).url.toString())\n            }\n        }\n\n        //相关模块检查\n        def modules = new ArrayList<String>()\n\n        rootProject.allprojects.forEach {\n            modules.add(it.name)\n        }\n\n//        if (!modules.contains(\"dokit-rpc\")) {\n//            throw new RuntimeException(\"未找到dokit-rpc module。。。\")\n//        }\n//\n//        if (!modules.contains(\"dokit-rpc-mc\")) {\n//            throw new RuntimeException(\"未找到dokit-rpc-mc module。。。\")\n//        }\n\n        if (!modules.contains(\"dokit-plugin\")) {\n            throw new RuntimeException(\"未找到dokit-plugin module。。。\")\n        }\n    }\n}\n\n\n\n"
  },
  {
    "path": "Android/buildSrc/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "Android/buildSrc/build.gradle",
    "content": "buildscript {\n    apply from: \"../config.gradle\"\n\n    repositories {\n        mavenLocal()\n        mavenCentral()\n        google()\n        jcenter()\n        maven { url 'https://oss.sonatype.org/content/repositories/public/' }\n        maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }\n    }\n    dependencies {\n        if (needKotlinV14()) {\n            classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:${rootProject.ext.android[\"kotlin_version_v14\"]}\"\n        } else {\n            classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:${rootProject.ext.android[\"kotlin_version_v13\"]}\"\n        }\n    }\n}\n//apply plugin: 'java-library'\napply plugin: 'kotlin'\napply plugin: 'kotlin-kapt'\n\n/**\n * 由于 buildSrc 的执行时机要早于任何一个 project，因此需要⾃⼰添加仓库\n */\nrepositories {\n    mavenLocal()\n    mavenCentral()\n    google()\n    jcenter()\n    maven { url 'https://oss.sonatype.org/content/repositories/public/' }\n    maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }\n}\n\nsourceSets {\n    main {\n        java {\n            srcDirs += []\n        }\n        kotlin {\n            srcDirs += ['src/main/kotlin', 'src/main/java']\n        }\n    }\n\n\n}\n\ncompileKotlin {\n    kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8\n}\n\n\ndependencies {\n    compileOnly localGroovy()\n    compileOnly gradleApi()\n\n    implementation \"com.android.tools.build:gradle:${rootProject.ext.android[\"agp_plugin_verson\"]}\"\n    /* 👇👇👇👇 引用这两个模块 👇👇👇👇 */\n    api \"com.didiglobal.booster:booster-api:${rootProject.ext.android[\"booster_version\"]}\"\n    api \"com.didiglobal.booster:booster-transform-asm:${rootProject.ext.android[\"booster_version\"]}\"\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitExt.kt",
    "content": "package com.didichuxing.doraemonkit.plugin\n\nimport com.android.build.gradle.api.BaseVariant\nimport com.android.dex.DexFormat\nimport com.android.dx.command.dexer.Main\nimport com.didiglobal.booster.kotlinx.NCPU\nimport com.didiglobal.booster.kotlinx.redirect\nimport com.didiglobal.booster.kotlinx.search\nimport com.didiglobal.booster.kotlinx.touch\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.util.transform\nimport org.apache.commons.compress.archivers.jar.JarArchiveEntry\nimport org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator\nimport org.apache.commons.compress.archivers.zip.ZipArchiveEntry\nimport org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream\nimport org.apache.commons.compress.parallel.InputStreamSupplier\nimport org.objectweb.asm.Opcodes.*\nimport org.objectweb.asm.tree.*\nimport java.io.File\nimport java.io.IOException\nimport java.io.OutputStream\nimport java.util.concurrent.*\nimport java.util.jar.JarFile\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipFile\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/19-18:00\n * 描    述：dokit 对象扩展\n * 修订历史：\n * ================================================\n */\n\nfun MethodNode.isGetSetMethod(): Boolean {\n    var ignoreCount = 0\n    val iterator = instructions.iterator()\n    while (iterator.hasNext()) {\n        val insnNode = iterator.next()\n        val opcode = insnNode.opcode\n        if (-1 == opcode) {\n            continue\n        }\n        if (opcode != GETFIELD && opcode != GETSTATIC && opcode != H_GETFIELD && opcode != H_GETSTATIC && opcode != RETURN && opcode != ARETURN && opcode != DRETURN && opcode != FRETURN && opcode != LRETURN && opcode != IRETURN && opcode != PUTFIELD && opcode != PUTSTATIC && opcode != H_PUTFIELD && opcode != H_PUTSTATIC && opcode > SALOAD) {\n            if (name.equals(\"<init>\") && opcode == INVOKESPECIAL) {\n                ignoreCount++\n                if (ignoreCount > 1) {\n                    return false\n                }\n                continue\n            }\n            return false\n        }\n    }\n    return true\n}\n\nfun MethodNode.isSingleMethod(): Boolean {\n    val iterator = instructions.iterator()\n    while (iterator.hasNext()) {\n        val insnNode = iterator.next()\n        val opcode = insnNode.opcode\n        if (-1 == opcode) {\n            continue\n        } else if (INVOKEVIRTUAL <= opcode && opcode <= INVOKEDYNAMIC) {\n            return false\n        }\n    }\n    return true\n}\n\nfun MethodNode.isEmptyMethod(): Boolean {\n    val iterator = instructions.iterator()\n    while (iterator.hasNext()) {\n        val insnNode = iterator.next()\n        val opcode = insnNode.opcode\n        return if (-1 == opcode) {\n            continue\n        } else {\n            false\n        }\n    }\n    return true\n}\n\nfun MethodNode.isMainMethod(className: String): Boolean {\n    if (this.name == \"main\" && this.desc == \"([Ljava/lang/String;)V\") {\n        \"====isMainMethod====$className  ${this.name}   ${this.desc}   ${this.access}\".println()\n        return true\n    }\n\n    return false\n}\n\n\nfun InsnList.getMethodExitInsnNodes(): Sequence<InsnNode>? {\n    return this.iterator()?.asSequence()?.filterIsInstance(InsnNode::class.java)?.filter {\n        it.opcode == RETURN ||\n                it.opcode == IRETURN ||\n                it.opcode == FRETURN ||\n                it.opcode == ARETURN ||\n                it.opcode == LRETURN ||\n                it.opcode == DRETURN ||\n                it.opcode == ATHROW\n    }\n}\n\nfun BaseVariant.isRelease(): Boolean {\n    if (this.name.contains(\"release\") || this.name.contains(\"Release\")) {\n        return true\n    }\n    return false\n}\n\n\nfun TransformContext.isRelease(): Boolean {\n    if (this.name.contains(\"release\") || this.name.contains(\"Release\")) {\n        return true\n    }\n    return false\n}\n\n\nfun String.println() {\n    if (DoKitExtUtil.dokitLogSwitchOpen()) {\n        println(\"[dokit plugin]===>$this\")\n    }\n}\n\nfun File.lastPath(): String {\n    return this.path.split(\"/\").last()\n}\n\nval MethodInsnNode.ownerClassName: String\n    get() = owner.replace('/', '.')\n\n\nval ClassNode.formatSuperName: String\n    get() = superName.replace('/', '.')\n\ninternal fun File.dex(output: File, api: Int = DexFormat.API_NO_EXTENDED_OPCODES): Int {\n    val args = Main.Arguments().apply {\n        numThreads = NCPU\n        debug = true\n        warnings = true\n        emptyOk = true\n        multiDex = true\n        jarOutput = true\n        optimize = false\n        minSdkVersion = api\n        fileNames = arrayOf(output.canonicalPath)\n        outName = canonicalPath\n    }\n    return try {\n        Main.run(args)\n    } catch (t: Throwable) {\n        t.printStackTrace()\n        -1\n    }\n}\n\n/**\n * Transform this file or directory to the output by the specified transformer\n *\n * @param output The output location\n * @param transformer The byte data transformer\n */\nfun File.dokitTransform(output: File, transformer: (ByteArray) -> ByteArray = { it -> it }) {\n    when {\n        isDirectory -> this.toURI().let { base ->\n            this.search().parallelStream().forEach {\n                it.transform(File(output, base.relativize(it.toURI()).path), transformer)\n            }\n        }\n        isFile -> when (extension.toLowerCase()) {\n            \"jar\" -> JarFile(this).use {\n                it.dokitTransform(output, ::JarArchiveEntry, transformer)\n            }\n            \"class\" -> this.inputStream().use {\n                it.transform(transformer).redirect(output)\n            }\n            else -> this.copyTo(output, true)\n        }\n        else -> throw IOException(\"Unexpected file: ${this.canonicalPath}\")\n    }\n}\n\nfun ZipFile.dokitTransform(\n    output: File,\n    entryFactory: (ZipEntry) -> ZipArchiveEntry = ::ZipArchiveEntry,\n    transformer: (ByteArray) -> ByteArray = { it -> it }\n) = output.touch().outputStream().buffered().use {\n    this.dokitTransform(it, entryFactory, transformer)\n}\n\n\nfun ZipFile.dokitTransform(\n    output: OutputStream,\n    entryFactory: (ZipEntry) -> ZipArchiveEntry = ::ZipArchiveEntry,\n    transformer: (ByteArray) -> ByteArray = { it -> it }\n) {\n    val entries = mutableSetOf<String>()\n    val creator = ParallelScatterZipCreator(\n        ThreadPoolExecutor(\n            NCPU,\n            NCPU,\n            0L,\n            TimeUnit.MILLISECONDS,\n            LinkedBlockingQueue<Runnable>(),\n            Executors.defaultThreadFactory(),\n            RejectedExecutionHandler { runnable, _ ->\n                runnable.run()\n            })\n    )\n    //将jar包里的文件序列化输出\n    entries().asSequence().forEach { entry ->\n        if (!entries.contains(entry.name)) {\n            val zae = entryFactory(entry)\n\n            val stream = InputStreamSupplier {\n                when (entry.name.substringAfterLast('.', \"\")) {\n                    \"class\" -> getInputStream(entry).use { src ->\n                        try {\n                            src.transform(transformer).inputStream()\n                        } catch (e: Throwable) {\n                            System.err.println(\"Broken class: ${this.name}!/${entry.name}\")\n                            getInputStream(entry)\n                        }\n                    }\n                    else -> getInputStream(entry)\n                }\n            }\n\n            creator.addArchiveEntry(zae, stream)\n            entries.add(entry.name)\n        } else {\n            System.err.println(\"Duplicated jar entry: ${this.name}!/${entry.name}\")\n        }\n    }\n    val zip = ZipArchiveOutputStream(output)\n    zip.use { zipStream ->\n        try {\n            creator.writeTo(zipStream)\n            zipStream.close()\n        } catch (e: Exception) {\n            zipStream.close()\n//            e.printStackTrace()\n//            \"e===>${e.message}\".println()\n            System.err.println(\"Duplicated jar entry: ${this.name}!\")\n        }\n    }\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitExtUtil.kt",
    "content": "package com.didichuxing.doraemonkit.plugin\n\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.extension.SlowMethodExtension\nimport com.didichuxing.doraemonkit.plugin.thirdlib.ThirdLibInfo\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/24-14:58\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject DoKitExtUtil {\n\n    var DOKIT_GPS_MOCK_INCLUDE = false\n\n    /**\n     * 三方库版本信息\n     */\n\n    val THIRD_LIB_INFOS = mutableListOf<ThirdLibInfo>()\n\n    /**\n     * dokit 插件开关 字段权限必须为public 否则无法进行赋值\n     */\n    var DOKIT_PLUGIN_SWITCH = true\n    var DOKIT_LOG_SWITCH = false\n\n    /**\n     * 默认函数调用为5级\n     */\n    var STACK_METHOD_LEVEL = 5\n\n    /**\n     * 自定义webview全限定名\n     */\n    var WEBVIEW_CLASS_NAME: String = \"\"\n\n\n    /**\n     * 慢函数默认关闭\n     */\n    var SLOW_METHOD_SWITCH = false\n\n    /**\n     * 三方库信息开关\n     */\n    var THIRD_LIBINFO_SWITCH = true\n\n\n    /**\n     * 慢函数策略 默认为函数调用栈策略\n     */\n    var SLOW_METHOD_STRATEGY = SlowMethodExtension.STRATEGY_STACK\n\n    private val applications: MutableSet<String> = mutableSetOf()\n\n    /**\n     * app的packageName\n     */\n    private var appPackageName: String = \"\"\n\n\n    val slowMethodExt = SlowMethodExtension()\n\n\n    fun dokitPluginSwitchOpen(): Boolean {\n        return DOKIT_PLUGIN_SWITCH\n    }\n\n\n    fun dokitLogSwitchOpen(): Boolean {\n        return DOKIT_LOG_SWITCH\n    }\n\n    fun dokitSlowMethodSwitchOpen(): Boolean {\n        return SLOW_METHOD_SWITCH\n    }\n\n    /**\n     * 初始化\n     *\n     * @param dokitEx dokitExtension\n     * @param appExtension   appExtension\n     */\n    fun init(dokitEx: DoKitExtension) {\n        //设置普通的配置\n        //slowMethodExt.strategy = dokitEx.slowMethod.strategy\n        //slowMethodExt.methodSwitch = dokitEx.slowMethod.methodSwitch\n        /**\n         * ============慢函数普通策略的配置 start==========\n         */\n        slowMethodExt.normalMethod.thresholdTime = dokitEx.slowMethod.normalMethod.thresholdTime\n        //设置慢函数普通策略插装包名\n        slowMethodExt.normalMethod.packageNames.clear()\n        for (packageName in dokitEx.slowMethod.normalMethod.packageNames) {\n            slowMethodExt.normalMethod.packageNames.add(packageName)\n        }\n        //添加默认的包名\n        if (appPackageName.isNotEmpty()) {\n            if (slowMethodExt.normalMethod.packageNames.isEmpty()) {\n                slowMethodExt.normalMethod.packageNames.add(appPackageName)\n            }\n        }\n\n\n        //设置慢函数普通策略插装包名黑名单\n        slowMethodExt.normalMethod.methodBlacklist.clear()\n        for (blackStr in dokitEx.slowMethod.normalMethod.methodBlacklist) {\n            slowMethodExt.normalMethod.methodBlacklist.add(blackStr)\n        }\n        /**\n         * ============慢函数普通策略的配置end==========\n         */\n        /**\n         * ============慢函数stack策略的配置 start==========\n         */\n        slowMethodExt.stackMethod.thresholdTime = dokitEx.slowMethod.stackMethod.thresholdTime\n        slowMethodExt.stackMethod.enterMethods.clear()\n        //添加默认的入口函数\n        for (application in applications) {\n            val attachBaseContextMethodName = \"$application.attachBaseContext\"\n            val onCreateMethodName = \"$application.onCreate\"\n            slowMethodExt.stackMethod.enterMethods.add(attachBaseContextMethodName)\n            slowMethodExt.stackMethod.enterMethods.add(onCreateMethodName)\n        }\n        for (methodName in dokitEx.slowMethod.stackMethod.enterMethods) {\n            slowMethodExt.stackMethod.enterMethods.add(methodName)\n        }\n\n        //设置慢函数调用栈策略插装包名黑名单\n        slowMethodExt.stackMethod.methodBlacklist.clear()\n        for (blackStr in dokitEx.slowMethod.stackMethod.methodBlacklist) {\n            slowMethodExt.stackMethod.methodBlacklist.add(blackStr)\n        }\n\n        /**\n         * ============慢函数stack策略的配置  end==========\n         */\n\n    }\n\n\n    fun setApplications(applications: MutableSet<String>) {\n        if (applications.isEmpty()) {\n            return\n        }\n        this.applications.clear()\n        for (application in applications) {\n            this.applications.add(application)\n        }\n    }\n\n    /**\n     * 设置packageName\n     */\n    fun setAppPackageName(packageName: String) {\n        appPackageName = packageName\n    }\n\n    fun ignorePackageNames(className: String): Boolean {\n        //命中白名单返回false\n        for (packageName in whitePackageNames) {\n            if (className.startsWith(packageName, true)) {\n                return false\n            }\n        }\n\n        //命中黑名单返回true\n        for (packageName in blackPackageNames) {\n            if (className.startsWith(packageName, true)) {\n                return true\n            }\n        }\n\n        return false\n    }\n\n\n    /**\n     * 白名单\n     */\n    private val whitePackageNames = arrayOf(\n        \"com.didichuxing.doraemonkit.DoraemonKit\",\n        \"com.didichuxing.doraemonkit.DoKit\",\n        \"com.didichuxing.doraemonkit.DoKitReal\"\n\n    )\n\n\n    /**\n     * 黑名单\n     */\n    private val blackPackageNames = arrayOf(\n        \"com.didichuxing.doraemonkit.\",\n        \"kotlin.\",\n        \"java.\",\n        \"android.\",\n        \"androidx.\"\n    )\n\n    fun log(\n        tag: String,\n        className: String,\n        methodName: String,\n        access: Int,\n        desc: String,\n        signature: String,\n        thresholdTime: Int\n    ) {\n        if (DOKIT_LOG_SWITCH) {\n            println(\"$tag===matched====>  className===$className   methodName===$methodName   access===$access   desc===$desc   signature===$signature    thresholdTime===$thresholdTime\")\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitPlugin.kt",
    "content": "package com.didichuxing.doraemonkit.plugin\n\nimport com.android.build.gradle.AppExtension\nimport com.android.build.gradle.LibraryExtension\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.extension.SlowMethodExtension\nimport com.didichuxing.doraemonkit.plugin.processor.DoKitPluginConfigProcessor\nimport com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNodeUtil\nimport com.didichuxing.doraemonkit.plugin.thirdlib.ThirdLibVariantProcessor\nimport com.didichuxing.doraemonkit.plugin.transform.*\nimport com.didiglobal.booster.gradle.GTE_V3_4\nimport com.didiglobal.booster.gradle.getAndroid\nimport com.didiglobal.booster.gradle.getProperty\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\n\n\n/**\n * when 也可以用来取代 if-else if链。\n * 如果不提供参数，所有的分支条件都是简单的布尔表达式，而当一个分支的条件为真时则执行该分支：\n */\n\n/**\n * 作用域函数:let、run、with、apply 以及 also\n * 它们的唯一目的是在对象的上下文中执行代码块\n * 由于作用域函数本质上都非常相似，因此了解它们之间的区别很重要。每个作用域函数之间有两个主要区别：\n * 引用上下文对象的方式:\n * 作为 lambda 表达式的接收者（this）或者作为 lambda 表达式的参数（it）\n * run、with 以及 apply 通过关键字 this 引用上下文对象\n * let 及 also 将上下文对象作为 lambda 表达式参数\n *\n * 返回值:\n * apply 及 also 返回上下文对象。\n * let、run 及 with 返回 lambda 表达式结果.\n */\n/**\n * 函数\t对象引用\t   返回值\t    是否是扩展函数\n * let\t it\t     Lambda 表达式结果\t是\n * run\t this\t Lambda 表达式结果\t是\n * run\t  -\t     Lambda 表达式结果\t不是：调用无需上下文对象\n * with\t this\t Lambda 表达式结果\t不是：把上下文对象当做参数\n * apply this\t 上下文对象\t        是\n * also\t it\t     上下文对象\t        是\n */\n\n/**\n *对一个非空（non-null）对象执行 lambda 表达式：let\n *将表达式作为变量引入为局部作用域中：let\n *对象配置：apply\n *对象配置并且计算结果：run\n *在需要表达式的地方运行语句：非扩展的 run\n *附加效果：also\n *一个对象的一组函数调用：with\n */\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/14-10:01\n * 描    述：\n * 修订历史：\n * ================================================\n *\n * DoKit 插件入口\n */\n\nclass DoKitPlugin : Plugin<Project> {\n    override fun apply(project: Project) {\n        //创建指定扩展 并将project 传入构造函数\n        val doKit = project.extensions.create(\"dokit\", DoKitExtension::class.java)\n        \"dokit plugin apply ${doKit}\".println()\n\n        project.gradle.addListener(DoKitTransformTaskExecutionListener(project))\n\n\n        when {\n            project.plugins.hasPlugin(\"com.android.application\") || project.plugins.hasPlugin(\"com.android.dynamic-feature\") -> {\n                if (!isReleaseTask(project)) {\n                    project.getAndroid<AppExtension>().let { androidExt ->\n                        val pluginSwitch = project.getProperty(\"DOKIT_PLUGIN_SWITCH\", true)\n                        val logSwitch = project.getProperty(\"DOKIT_LOG_SWITCH\", false)\n\n                        val slowMethodSwitch = project.getProperty(\"DOKIT_METHOD_SWITCH\", false)\n                        val slowMethodStrategy = project.getProperty(\"DOKIT_METHOD_STRATEGY\", 0)\n                        val methodStackLevel = project.getProperty(\"DOKIT_METHOD_STACK_LEVEL\", 5)\n                        val webViewClassName = project.getProperty(\"DOKIT_WEBVIEW_CLASS_NAME\", \"\")\n                        val thirdLibInfo = project.getProperty(\"DOKIT_THIRD_LIB_SWITCH\", true)\n                        DoKitExtUtil.DOKIT_PLUGIN_SWITCH = pluginSwitch\n                        DoKitExtUtil.DOKIT_LOG_SWITCH = logSwitch\n                        DoKitExtUtil.SLOW_METHOD_SWITCH = slowMethodSwitch\n                        DoKitExtUtil.SLOW_METHOD_STRATEGY = slowMethodStrategy\n                        DoKitExtUtil.STACK_METHOD_LEVEL = methodStackLevel\n                        DoKitExtUtil.WEBVIEW_CLASS_NAME = webViewClassName\n                        DoKitExtUtil.THIRD_LIBINFO_SWITCH = thirdLibInfo\n\n                        \"application module ${project.name} is executing...\".println()\n\n                        MethodStackNodeUtil.METHOD_STACK_KEYS.clear()\n                        if (DoKitExtUtil.DOKIT_PLUGIN_SWITCH) {\n                            //注册transform\n                            androidExt.registerTransform(commNewInstance(project))\n                            if (slowMethodSwitch && slowMethodStrategy == SlowMethodExtension.STRATEGY_STACK) {\n                                MethodStackNodeUtil.METHOD_STACK_KEYS.add(0, mutableSetOf<String>())\n                                val methodStackRange = 1 until methodStackLevel\n                                if (methodStackLevel > 1) {\n                                    for (index in methodStackRange) {\n                                        MethodStackNodeUtil.METHOD_STACK_KEYS.add(\n                                            index,\n                                            mutableSetOf<String>()\n                                        )\n                                        androidExt.registerTransform(\n                                            dependNewInstance(project, index)\n                                        )\n                                    }\n                                }\n                            }\n                        }\n\n                        /**\n                         * 所有项目的build.gradle执行完毕\n                         * wiki:https://juejin.im/post/6844903607679057934\n                         *\n                         * **/\n                        project.gradle.projectsEvaluated {\n                            \"===projectsEvaluated===\".println()\n                            androidExt.applicationVariants.forEach { variant ->\n                                ThirdLibVariantProcessor(project).process(variant)\n                                DoKitPluginConfigProcessor(project).process(variant)\n                            }\n\n                        }\n\n\n                        //task依赖关系图建立完毕\n                        project.gradle.taskGraph.whenReady {\n                            \"===taskGraph.whenReady===\".println()\n                            \"dokit config :: ${doKit}\".println()\n                        }\n\n                    }\n                }\n\n            }\n\n            project.plugins.hasPlugin(\"com.android.library\") -> {\n                if (!isReleaseTask(project)) {\n                    project.getAndroid<LibraryExtension>().let { libraryExt ->\n                        \"library module ${project.name} is executing...\".println()\n                        if (DoKitExtUtil.DOKIT_PLUGIN_SWITCH) {\n                            libraryExt.registerTransform(commNewInstance(project))\n                        }\n                        project.afterEvaluate {\n                            libraryExt.libraryVariants.forEach { variant ->\n                                ThirdLibVariantProcessor(project).process(variant)\n                                DoKitPluginConfigProcessor(project).process(variant)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    private fun isReleaseTask(project: Project): Boolean {\n        return project.gradle.startParameter.taskNames.any {\n            it.contains(\"release\") || it.contains(\"Release\")\n        }\n    }\n\n    private fun commNewInstance(project: Project): DoKitBaseTransform = when {\n        GTE_V3_4 -> DoKitCommonTransformV34(project)\n        else -> DoKitCommonTransform(project)\n    }\n\n    private fun dependNewInstance(project: Project, index: Int): DoKitBaseTransform = when {\n        GTE_V3_4 -> DoKitDependTransformV34(project, index)\n        else -> DoKitDependTransform(project, index)\n    }\n\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitPluginUtil.kt",
    "content": "package com.didichuxing.doraemonkit.plugin\n\nimport java.io.File\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/10/20-15:13\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject DoKitPluginUtil {\n    const val BYTE = 1\n    const val KB = 1024\n    const val MB = 1048576\n    const val GB = 1073741824\n\n    fun fileSize(file: File, precision: Int): String? {\n        if (!file.isFile) {\n            return \"0kb\"\n        }\n        if (file.isDirectory) {\n            return \"0kb\"\n        }\n        val fileLength = file.length()\n        return byte2FitMemorySize(fileLength, precision)\n    }\n\n    private fun byte2FitMemorySize(byteSize: Long, precision: Int): String? {\n        require(precision >= 0) { \"precision shouldn't be less than zero!\" }\n        return if (byteSize < 0) {\n            throw IllegalArgumentException(\"byteSize shouldn't be less than zero!\")\n        } else if (byteSize < KB) {\n            String.format(\"%.\" + precision + \"fB\", byteSize.toDouble())\n        } else if (byteSize < MB) {\n            String.format(\"%.\" + precision + \"fKB\", byteSize.toDouble() / KB)\n        } else if (byteSize < GB) {\n            String.format(\"%.\" + precision + \"fMB\", byteSize.toDouble() / MB)\n        } else {\n            String.format(\"%.\" + precision + \"fGB\", byteSize.toDouble() / GB)\n        }\n    }\n\n    private fun getNextChunk(version: String, n: Int, p: Int): Pair<Int, Int> {\n        // if pointer is set to the end of string\n        // return 0\n        if (p > n - 1) {\n            return Pair(0, p)\n        }\n        // find the end of chunk\n        var i = 0\n        var pEnd = p\n        while (pEnd < n && version[pEnd].equals(\".\")) {\n            ++pEnd\n        }\n        // retrieve the chunk\n        i = if (pEnd != n - 1) {\n            version.substring(p, pEnd).toInt()\n        } else {\n            version.substring(p, n).toInt()\n        }\n        // find the beginning of next chunk\n        val q = pEnd + 1\n\n        return Pair(i, q)\n\n    }\n\n    /**\n     * 比较version的大小\n     */\n    fun compareVersion(version1: String, version2: String): Int {\n        var p1 = 0\n        var p2 = 0\n        val n1 = version1.length\n        val n2 = version2.length\n        var i1: Int\n        var i2: Int\n        var pair: Pair<Int, Int>\n        while (p1 < n1 || p2 < n2) {\n            pair = getNextChunk(version1, n1, p1)\n            i1 = pair.first\n            p1 = pair.second\n\n            pair = getNextChunk(version2, n2, p2)\n            i2 = pair.first\n            p2 = pair.second\n            if (i1 != i2) {\n                return if (i1 > i2) {\n                    1\n                } else {\n                    -1\n                }\n            }\n        }\n        return 0\n    }\n}"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitTransformTaskExecutionListener.kt",
    "content": "package com.didichuxing.doraemonkit.plugin\n\nimport com.android.build.gradle.internal.pipeline.TransformTask\nimport com.didiglobal.booster.kotlinx.call\nimport com.didiglobal.booster.kotlinx.get\nimport org.gradle.api.Project\nimport org.gradle.api.Task\nimport org.gradle.api.execution.TaskExecutionAdapter\n\n/**\n * @author neighbWang\n */\nclass DoKitTransformTaskExecutionListener(private val project: Project) : TaskExecutionAdapter() {\n\n    override fun beforeExecute(task: Task) {\n        task.takeIf {\n            it.project == project && it is TransformTask && it.transform.scopes.isNotEmpty()\n        }?.run {\n            task[\"outputStream\"]?.call<Unit>(\"init\")\n        }\n    }\n\n}"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/BigImageExtension.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.extension\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 6:07 下午\n * @Description 用一句话说明文件功能\n */\n\nopen class BigImageExtension(\n    var glide: Boolean = true,\n    var picasso: Boolean = true,\n    var fresco: Boolean = true,\n    var imageLoader: Boolean = true,\n    var coil: Boolean = true\n) {\n\n\n    fun glide(boolean: Boolean) {\n        glide = boolean\n    }\n\n    fun picasso(boolean: Boolean) {\n        picasso = boolean\n    }\n\n    fun fresco(boolean: Boolean) {\n        fresco = boolean\n    }\n\n    fun imageLoader(boolean: Boolean) {\n        imageLoader = boolean\n    }\n\n    fun coil(boolean: Boolean) {\n        coil = boolean\n    }\n\n\n    override fun toString(): String {\n        return \"BigImageExtension(glide=$glide, picasso=$picasso, Fresco=$fresco, imageLoader=$imageLoader, coil=$coil)\"\n    }\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/DoKitExtension.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.extension\n\nimport org.gradle.api.Action\n\n/**\n * Created by jint on 07/10/2018.\n *\n * DoKit plugin 配置实例  @see DoKitExtension\n *\n * 支持DSL语法可以在插件中直接配置\n */\nopen class DoKitExtension(\n    var pluginEnable: Boolean = true,\n    var logEnable: Boolean = true,\n    var thirdLibEnable: Boolean = true,\n\n    var gpsEnable: Boolean = true,\n    var gps: GpsExtension = GpsExtension(),\n\n    var networkEnable: Boolean = true,\n    var network: NetworkExtension = NetworkExtension(),\n\n    var bigImageEnable: Boolean = true,\n    var bigImage: BigImageExtension = BigImageExtension(),\n\n    var webViewEnable: Boolean = true,\n    var webView: WebViewExtension = WebViewExtension(),\n\n    var slowMethodEnable: Boolean = true,\n    var slowMethod: SlowMethodExtension = SlowMethodExtension()\n) {\n\n\n    /**\n     * gps 支持 DSL 语法\n     *\n     * @param action\n     */\n    fun gps(action: Action<GpsExtension>) {\n        action.execute(gps)\n    }\n\n    /**\n     * network 支持 DSL 语法\n     *\n     * @param action\n     */\n    fun network(action: Action<NetworkExtension>) {\n        action.execute(network)\n    }\n\n    /**\n     * bigImage 支持 DSL 语法\n     *\n     * @param action\n     */\n    fun bigImage(action: Action<BigImageExtension>) {\n        action.execute(bigImage)\n    }\n    \n    fun webView(action: Action<WebViewExtension>) {\n        action.execute(webView)\n    }\n\n    /**\n     * 让slowMethod 支持 DSL 语法\n     *\n     * @param action\n     */\n    fun slowMethod(action: Action<SlowMethodExtension>) {\n        action.execute(slowMethod)\n    }\n\n\n\n\n    override fun toString(): String {\n        return \"DoKitExtension(pluginEnable=$pluginEnable, logEnable=$logEnable, thirdLibEnable=$thirdLibEnable, gpsEnable=$gpsEnable, gps=$gps, networkEnable=$networkEnable, network=$network, bigImageEnable=$bigImageEnable, bigImage=$bigImage, webViewEnable=$webViewEnable, webView=$webView, slowMethodEnable=$slowMethodEnable, slowMethod=$slowMethod)\"\n    }\n\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/GpsExtension.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.extension\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 5:36 下午\n * @Description 用一句话说明文件功能\n * @see DoKitExtension\n */\n\nopen class GpsExtension(\n    var local: Boolean = true,\n    var baidu: Boolean = true,\n    var tencent: Boolean = true,\n    var amap: Boolean = true,\n    var didi: Boolean = true\n) {\n\n\n    fun local(boolean: Boolean) {\n        local = boolean\n    }\n\n    fun baidu(boolean: Boolean) {\n        baidu = boolean\n    }\n\n    fun tencent(boolean: Boolean) {\n        tencent = boolean\n    }\n\n    fun amap(boolean: Boolean) {\n        amap = boolean\n    }\n\n    fun didi(boolean: Boolean) {\n        didi = boolean\n    }\n\n    override fun toString(): String {\n        return \"GpsExtension(local=$local, baidu=$baidu, tencent=$tencent, didi=$didi)\"\n    }\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/NetworkExtension.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.extension\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 5:36 下午\n * @Description 用一句话说明文件功能\n */\n\nopen class NetworkExtension(\n    var okHttp: Boolean = true,\n    var urlConnect: Boolean = true,\n    var didiHttp: Boolean = true,\n    var didiSocket: Boolean = true,\n    var didiDjSocket: Boolean = true\n) {\n\n\n    fun okHttp(boolean: Boolean) {\n        okHttp = boolean\n    }\n\n    fun urlConnect(boolean: Boolean) {\n        urlConnect = boolean\n    }\n\n    fun didiHttp(boolean: Boolean) {\n        didiHttp = boolean\n    }\n\n    fun didiSocket(boolean: Boolean) {\n        didiSocket = boolean\n    }\n\n    fun didiDjSocket(boolean: Boolean) {\n        didiDjSocket = boolean\n    }\n\n    override fun toString(): String {\n        return \"NetworkExtension(okHttp=$okHttp, urlConnect=$urlConnect, didiHttp=$didiHttp, didiSocket=$didiSocket, didiDjSocket=$didiDjSocket)\"\n    }\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/SlowMethodExtension.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.extension\n\nimport groovy.lang.Closure\nimport org.gradle.util.ConfigureUtil\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/28-14:56\n * 描    述：\n * 修订历史：\n * ================================================\n */\n\nopen class SlowMethodExtension(\n    //0:打印函数调用栈  1:普通模式 运行时打印某个函数的耗时 全局业务代码函数插入\n    @Deprecated(\"已弃用,请在项目根目录的gradle.properties中通过DOKIT_METHOD_STRATEGY=0|1 来控制\")\n    var strategy: Int = STRATEGY_STACK,\n    //函数功能开关\n    @Deprecated(\"已弃用,请在项目根目录的gradle.properties中通过DoKit_METHOD_SWITCH=true|false 来控制\")\n    var methodSwitch: Boolean = false,\n    //函数调用栈模式\n    var stackMethod: StackMethodExt = StackMethodExt(),\n    //普通模式\n    var normalMethod: NormalMethodExt = NormalMethodExt()\n) {\n\n\n    /**\n     * 函数功能开关\n     */\n    fun strategy(strategy: Int) {\n        this.strategy = strategy\n    }\n\n    fun methodSwitch(methodSwitch: Boolean) {\n        this.methodSwitch = methodSwitch\n    }\n\n    fun stackMethod(closure: Closure<StackMethodExt?>?) {\n        ConfigureUtil.configure(closure, stackMethod)\n    }\n\n    fun normalMethod(closure: Closure<NormalMethodExt?>?) {\n        ConfigureUtil.configure(closure, normalMethod)\n    }\n\n    class StackMethodExt(\n        //默认阈值为5ms\n        var thresholdTime: Int = 5,\n        //入口函集合\n        var enterMethods: MutableSet<String> = mutableSetOf(),\n        //插桩黑名单\n        var methodBlacklist: MutableSet<String> = mutableSetOf()\n    ) {\n\n        /**\n         * 默认值为5ms\n         */\n        fun thresholdTime(thresholdTime: Int) {\n            this.thresholdTime = thresholdTime\n        }\n\n\n        fun enterMethods(enterMethods: MutableSet<String>) {\n            this.enterMethods = enterMethods\n        }\n\n        fun methodBlacklist(methodBlacklist: MutableSet<String>) {\n            this.methodBlacklist = methodBlacklist\n        }\n\n        override fun toString(): String {\n            return \"StackMethodExt(thresholdTime=$thresholdTime, enterMethods=$enterMethods, methodBlacklist=$methodBlacklist)\"\n        }\n\n\n    }\n\n    class NormalMethodExt(\n        //默认阈值为500ms\n        var thresholdTime: Int = 500,\n        //普通函数的插装包名集合\n        var packageNames: MutableSet<String> = mutableSetOf(),\n        //插桩黑名单\n        var methodBlacklist: MutableSet<String> = mutableSetOf()\n    ) {\n        /**\n         * 默认值为500ms\n         */\n\n        fun thresholdTime(thresholdTime: Int) {\n            this.thresholdTime = thresholdTime\n        }\n\n        fun packageNames(packageNames: MutableSet<String>) {\n            this.packageNames = packageNames\n        }\n\n        fun methodBlacklist(methodBlacklist: MutableSet<String>) {\n            this.methodBlacklist = methodBlacklist\n        }\n\n        override fun toString(): String {\n            return \"NormalMethodExt{\" +\n                    \"thresholdTime=\" + thresholdTime +\n                    \", packageNames=\" + packageNames +\n                    \", methodBlacklist=\" + methodBlacklist +\n                    '}'\n        }\n    }\n\n    override fun toString(): String {\n        return \"SlowMethodExt{\" +\n                \"strategy=\" + strategy +\n                \", methodSwitch=\" + methodSwitch +\n                \", stackMethod=\" + stackMethod +\n                \", normalMethod=\" + normalMethod +\n                '}'\n    }\n\n    companion object {\n        const val STRATEGY_STACK = 0\n        const val STRATEGY_NORMAL = 1\n    }\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/WebViewExtension.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.extension\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 6:10 下午\n * @Description 用一句话说明文件功能\n */\n\nopen class WebViewExtension(\n    var network: Boolean = true,\n    var dokitWeb: Boolean = false,\n    var vConsole: Boolean = false\n) {\n\n    fun network(boolean: Boolean) {\n        network = boolean\n    }\n\n    fun dokitWeb(boolean: Boolean) {\n        dokitWeb = boolean\n    }\n\n    fun vConsole(boolean: Boolean) {\n        vConsole = boolean\n    }\n\n    override fun toString(): String {\n        return \"WebViewExtension(network=$network, dokitWeb=$dokitWeb, vConsole=$vConsole)\"\n    }\n\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/processor/DoKitComponentHandler.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.processor\n\nimport org.xml.sax.Attributes\nimport org.xml.sax.helpers.DefaultHandler\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：3/2/21-17:26\n * 描    述：\n * 修订历史：\n * ================================================\n */\nconst val ATTR_NAME = \"android:name\"\n\nconst val MANIFEST_ATTR_NAME = \"package\"\n\nclass DoKitComponentHandler : DefaultHandler() {\n    var appPackageName: String = \"\"\n    val applications = mutableSetOf<String>()\n    val activities = mutableSetOf<String>()\n    val services = mutableSetOf<String>()\n    val providers = mutableSetOf<String>()\n    val receivers = mutableSetOf<String>()\n\n    override fun startElement(\n        uri: String,\n        localName: String,\n        qName: String,\n        attributes: Attributes\n    ) {\n        val name: String = attributes.getValue(ATTR_NAME) ?: \"\"\n\n        val packageName: String = attributes.getValue(MANIFEST_ATTR_NAME) ?: \"\"\n\n        when (qName) {\n            \"manifest\" -> {\n                appPackageName = packageName\n            }\n            \"application\" -> {\n                applications.add(name)\n            }\n            \"activity\" -> {\n                activities.add(name)\n            }\n            \"service\" -> {\n                services.add(name)\n            }\n            \"provider\" -> {\n                providers.add(name)\n            }\n            \"receiver\" -> {\n                receivers.add(name)\n            }\n\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/processor/DoKitPluginConfigProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.processor\n\nimport com.android.build.gradle.AppExtension\nimport com.android.build.gradle.api.ApplicationVariant\nimport com.android.build.gradle.api.BaseVariant\nimport com.didichuxing.doraemonkit.plugin.*\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.thirdlib.ThirdLibInfo\nimport com.didiglobal.booster.gradle.dependencies\nimport com.didiglobal.booster.gradle.getAndroid\nimport com.didiglobal.booster.gradle.mergedManifests\nimport com.didiglobal.booster.gradle.project\nimport com.didiglobal.booster.task.spi.VariantProcessor\nimport org.gradle.api.Project\nimport org.gradle.api.artifacts.result.ResolvedArtifactResult\nimport javax.xml.parsers.SAXParserFactory\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/15-11:28\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitPluginConfigProcessor(val project: Project) : VariantProcessor {\n    override fun process(variant: BaseVariant) {\n        if (!DoKitExtUtil.DOKIT_PLUGIN_SWITCH) {\n            return\n        }\n\n        if (variant.isRelease()) {\n            return\n        }\n\n        //统计三方库信息\n        if (DoKitExtUtil.THIRD_LIBINFO_SWITCH) {\n            //遍历三方库\n            val dependencies = variant.dependencies\n            for (artifactResult: ResolvedArtifactResult in dependencies) {\n                val variants = artifactResult.variant.displayName.split(\" \")\n                var thirdLibInfo: ThirdLibInfo? = null\n                if (variants.size == 3) {\n                    thirdLibInfo = ThirdLibInfo(variants[0], artifactResult.file.length())\n                    checkConfig(thirdLibInfo.variant)\n                } else if (variants.size == 4) {\n                    thirdLibInfo = ThirdLibInfo(\"porject ${variants[1]}\", artifactResult.file.length())\n                    checkConfig(thirdLibInfo.variant)\n                }\n            }\n        }\n        //查找AndroidManifest.xml 文件 并处理\n        processApplicationVariant(variant)\n\n    }\n\n\n    private fun checkConfig(variant: String) {\n        if (variant.contains(\"dokitx-gps-mock\") || variant.contains(\"dokit-gps-mock\")) {\n            DoKitExtUtil.DOKIT_GPS_MOCK_INCLUDE = true;\n        }\n    }\n\n\n    private fun processApplicationVariant(variant: BaseVariant) {\n        //查找application module下的配置\n        if (variant is ApplicationVariant) {\n\n            project.tasks.find {\n                //\"===task Name is ${it.name}\".println()\n                it.name == \"processDebugManifest\"\n            }?.let { transformTask ->\n                transformTask.doLast {\n                    \"===processDebugManifest task has executed===\".println()\n                    //查找AndroidManifest.xml 文件路径\n                    variant.mergedManifests.forEach { manifest ->\n                        val parser = SAXParserFactory.newInstance().newSAXParser()\n                        val handler = DoKitComponentHandler()\n                        \"App Manifest path====>$manifest\".println()\n                        try {\n                            parser.parse(manifest, handler)\n                        } catch (e: Exception) {\n                            e.printStackTrace()\n                            \"===processDebugManifest task error. ${manifest.absoluteFile}\".println()\n                        }\n                        \"App PackageName is====>${handler.appPackageName}\".println()\n                        \"App Application path====>${handler.applications}\".println()\n                        DoKitExtUtil.setAppPackageName(handler.appPackageName)\n                        DoKitExtUtil.setApplications(handler.applications)\n                    }\n\n                    //读取插件配置\n                    variant.project.getAndroid<AppExtension>().let { appExt ->\n                        //查找Application路径\n                        val doKitExt = variant.project.extensions.getByType(DoKitExtension::class.java)\n                        DoKitExtUtil.init(doKitExt)\n                    }\n                }\n            }\n\n        } else {\n            \"${variant.project.name}-不建议在Library Module下引入dokit插件\".println()\n        }\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/stack_method/MethodStackNode.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.stack_method\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/20-16:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class MethodStackNode(var level: Int,\n                           var className: String,\n                           var methodName: String,\n                           var desc: String,\n                           var parentClassName: String,\n                           var parentMethodName: String,\n                           var parentDesc: String)"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/stack_method/MethodStackNodeUtil.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.stack_method\n\nimport java.util.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/20-16:58\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject MethodStackNodeUtil {\n\n\n    val METHOD_STACK_KEYS: MutableList<MutableSet<String>> by lazy {\n        Collections.synchronizedList(mutableListOf<MutableSet<String>>())\n    }\n\n\n    fun addMethodStackNode(level: Int, methodStackNode: MethodStackNode) {\n        val key = \"${methodStackNode.className}&${methodStackNode.methodName}&${methodStackNode.desc}\"\n        METHOD_STACK_KEYS[level].add(key)\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/thirdlib/ThirdLibInfo.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.thirdlib\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/10/20-14:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\n\ndata class ThirdLibInfo(val variant: String, val fileSize: Long)\n\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/thirdlib/ThirdLibVariantProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.thirdlib\n\nimport com.android.build.gradle.api.BaseVariant\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.isRelease\nimport com.didiglobal.booster.gradle.dependencies\nimport com.didiglobal.booster.task.spi.VariantProcessor\nimport org.gradle.api.Project\nimport org.gradle.api.artifacts.result.ResolvedArtifactResult\n\n\n/**\n * didi Create on 2023/3/22 .\n *\n * Copyright (c) 2023/3/22 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/22 2:57 下午\n * @Description 用一句话说明文件功能\n */\n\nclass ThirdLibVariantProcessor(val project: Project) : VariantProcessor {\n\n    override fun process(variant: BaseVariant) {\n        if (!DoKitExtUtil.DOKIT_PLUGIN_SWITCH) {\n            return\n        }\n\n        if (variant.isRelease()) {\n            return\n        }\n\n        val dokit = project.extensions.getByType(DoKitExtension::class.java)\n\n        //统计三方库信息\n        if (dokit.thirdLibEnable && DoKitExtUtil.THIRD_LIBINFO_SWITCH) {\n            //遍历三方库\n            val dependencies = variant.dependencies\n            DoKitExtUtil.THIRD_LIB_INFOS.clear()\n            for (artifactResult: ResolvedArtifactResult in dependencies) {\n                val variants = artifactResult.variant.displayName.split(\" \")\n                var thirdLibInfo: ThirdLibInfo? = null\n                if (variants.size == 3) {\n                    thirdLibInfo = ThirdLibInfo(variants[0], artifactResult.file.length())\n                    DoKitExtUtil.THIRD_LIB_INFOS.add(thirdLibInfo)\n                } else if (variants.size == 4) {\n                    thirdLibInfo = ThirdLibInfo(\"porject ${variants[1]}\", artifactResult.file.length())\n                    DoKitExtUtil.THIRD_LIB_INFOS.add(thirdLibInfo)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitBaseTransform.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform\n\nimport com.android.build.api.transform.QualifiedContent\nimport com.android.build.api.transform.Transform\nimport com.android.build.api.transform.TransformInvocation\nimport com.android.build.gradle.BaseExtension\nimport com.android.build.gradle.internal.pipeline.TransformManager\nimport com.didiglobal.booster.gradle.*\nimport com.didiglobal.booster.transform.AbstractKlassPool\nimport com.didiglobal.booster.transform.Transformer\nimport org.gradle.api.Project\n\n/**\n * Represents the transform base\n * DoKitCommTransform 作用于 CommTransformer、BigImgTransformer、UrlConnectionTransformer、GlobalSlowMethodTransformer\n * @author johnsonlee\n */\n\nopen class DoKitBaseTransform protected constructor(val project: Project) : Transform() {\n\n    /*transformers\n     * Preload transformers as List to fix NoSuchElementException caused by ServiceLoader in parallel mode\n     * booster 的默认出炉逻辑 DoKit已重写自处理\n     */\n    open val transformers = listOf<Transformer>()\n\n    internal val verifyEnabled = project.getProperty(OPT_TRANSFORM_VERIFY, false)\n\n    private val android: BaseExtension = project.getAndroid()\n\n    private lateinit var androidKlassPool: AbstractKlassPool\n\n    init {\n        project.afterEvaluate {\n            androidKlassPool = object : AbstractKlassPool(android.bootClasspath) {}\n        }\n    }\n\n    val bootKlassPool: AbstractKlassPool\n        get() = androidKlassPool\n\n    override fun getName() = this.javaClass.simpleName\n\n    override fun isIncremental() = !verifyEnabled\n\n    override fun isCacheable() = !verifyEnabled\n\n    override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> =\n        TransformManager.CONTENT_CLASS\n\n    override fun getScopes(): MutableSet<in QualifiedContent.Scope> = when {\n        transformers.isEmpty() -> mutableSetOf()\n        project.plugins.hasPlugin(\"com.android.library\") -> SCOPE_PROJECT\n        project.plugins.hasPlugin(\"com.android.application\") -> SCOPE_FULL_PROJECT\n        project.plugins.hasPlugin(\"com.android.dynamic-feature\") -> SCOPE_FULL_WITH_FEATURES\n        else -> TODO(\"Not an Android project\")\n    }\n\n    override fun getReferencedScopes(): MutableSet<in QualifiedContent.Scope> = when {\n        transformers.isEmpty() -> when {\n            project.plugins.hasPlugin(\"com.android.library\") -> SCOPE_PROJECT\n            project.plugins.hasPlugin(\"com.android.application\") -> SCOPE_FULL_PROJECT\n            project.plugins.hasPlugin(\"com.android.dynamic-feature\") -> SCOPE_FULL_WITH_FEATURES\n            else -> TODO(\"Not an Android project\")\n        }\n        else -> super.getReferencedScopes()\n    }\n\n    final override fun transform(invocation: TransformInvocation) {\n        DoKitTransformInvocation(invocation, this).apply {\n            if (isIncremental) {\n                doIncrementalTransform()\n            } else {\n                outputProvider?.deleteAll()\n                doFullTransform()\n            }\n        }\n    }\n\n\n}\n\n/**\n * The option for transform outputs verifying, default is false\n */\nprivate const val OPT_TRANSFORM_VERIFY = \"dokit.transform.verify\"\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitCommonTransform.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform\n\nimport com.didichuxing.doraemonkit.plugin.transform.asmtransform.DoKitAsmTransformer\nimport com.didichuxing.doraemonkit.plugin.transform.classtransform.*\nimport com.didiglobal.booster.transform.Transformer\nimport org.gradle.api.Project\n\n/**\n * Represents the transform base\n * DoKitCommTransform 作用于 CommTransformer、BigImgTransformer、UrlConnectionTransformer、GlobalSlowMethodTransformer、EnterMethodStackTransformer\n * @author johnsonlee\n */\nclass DoKitCommonTransform(androidProject: Project) : DoKitBaseTransform(androidProject) {\n\n    override val transformers = listOf<Transformer>(\n        DoKitAsmTransformer(\n            listOf(\n                CommClassTransformer(),\n                ThirdLibsClassTransformer(),\n                //网络\n                Okhttp3ClassTransformer(),\n                UrlConnectionTransformer(),\n                WebViewClassTransformer(),\n\n                //地图GPS\n                GPSClassTransformer(),\n                GPSAMapClassTransformer(),\n                GPSBDClassTransformer(),\n                GPSTencentClassTransformer(),\n                //大图检测\n                BigImgClassTransformer(),\n                //全局慢函数\n                GSMClassTransformer(),\n                //入口慢函数\n                EnterMSClassTransformer()\n            )\n        )\n    )\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitCommonTransformV34.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform\n\nimport com.android.build.api.variant.VariantInfo\nimport com.didichuxing.doraemonkit.plugin.transform.asmtransform.DoKitAsmTransformer\nimport com.didichuxing.doraemonkit.plugin.transform.classtransform.*\nimport com.didiglobal.booster.transform.Transformer\nimport org.gradle.api.Project\n\nclass DoKitCommonTransformV34(project: Project) : DoKitBaseTransform(project) {\n\n\n    override val transformers = listOf<Transformer>(\n        DoKitAsmTransformer(\n            listOf(\n                CommClassTransformer(),\n                ThirdLibsClassTransformer(),\n                //网络\n                Okhttp3ClassTransformer(),\n                UrlConnectionTransformer(),\n                WebViewClassTransformer(),\n\n                //地图GPS\n                GPSClassTransformer(),\n                GPSAMapClassTransformer(),\n                GPSBDClassTransformer(),\n                GPSTencentClassTransformer(),\n                //大图检测\n                BigImgClassTransformer(),\n                //全局慢函数\n                GSMClassTransformer(),\n                //入口慢函数\n                EnterMSClassTransformer()\n            )\n        )\n    )\n\n    @Suppress(\"UnstableApiUsage\")\n    override fun applyToVariant(variant: VariantInfo): Boolean {\n        return variant.buildTypeEnabled || (variant.flavorNames.isNotEmpty() && variant.fullVariantEnabled)\n    }\n\n    @Suppress(\"UnstableApiUsage\")\n    private val VariantInfo.fullVariantEnabled: Boolean\n        get() = project.findProperty(\"booster.transform.${fullVariantName}.enabled\")?.toString()?.toBoolean() ?: true\n\n    @Suppress(\"UnstableApiUsage\")\n    private val VariantInfo.buildTypeEnabled: Boolean\n        get() = project.findProperty(\"booster.transform.${buildTypeName}.enabled\")?.toString()?.toBoolean() ?: true\n\n}\n\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitDependTransform.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform\n\nimport com.didichuxing.doraemonkit.plugin.transform.asmtransform.DoKitAsmTransformer\nimport com.didichuxing.doraemonkit.plugin.transform.classtransform.MSDClassTransformer\nimport com.didiglobal.booster.transform.Transformer\nimport org.gradle.api.Project\n\n/**\n * Represents the transform base\n * DoKitCommTransform 作用于 CommTransformer、BigImgTransformer、UrlConnectionTransformer、GlobalSlowMethodTransformer\n * @author johnsonlee\n */\nopen class DoKitDependTransform(androidProject: Project, private val level: Int) :\n    DoKitBaseTransform(androidProject) {\n\n    override val transformers = listOf<Transformer>(DoKitAsmTransformer(listOf(MSDClassTransformer(level))))\n\n    override fun getName(): String {\n        return \"${this.javaClass.simpleName}_$level\"\n    }\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitDependTransformV34.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform\n\nimport com.android.build.api.variant.VariantInfo\nimport com.didichuxing.doraemonkit.plugin.transform.asmtransform.DoKitAsmTransformer\nimport com.didichuxing.doraemonkit.plugin.transform.classtransform.MSDClassTransformer\nimport com.didiglobal.booster.transform.Transformer\nimport org.gradle.api.Project\n\n/**\n * Represents the transform base\n * DoKitCommTransform 作用于 CommTransformer、BigImgTransformer、UrlConnectionTransformer、GlobalSlowMethodTransformer\n * @author johnsonlee\n */\nopen class DoKitDependTransformV34(androidProject: Project, private val level: Int) :\n    DoKitBaseTransform(androidProject) {\n\n    override val transformers = listOf<Transformer>(DoKitAsmTransformer(listOf(MSDClassTransformer(level))))\n\n    override fun getName(): String {\n        return \"${this.javaClass.simpleName}_$level\"\n    }\n\n    @Suppress(\"UnstableApiUsage\")\n    override fun applyToVariant(variant: VariantInfo): Boolean {\n        return variant.buildTypeEnabled || (variant.flavorNames.isNotEmpty() && variant.fullVariantEnabled)\n    }\n\n    @Suppress(\"UnstableApiUsage\")\n    private val VariantInfo.fullVariantEnabled: Boolean\n        get() = project.findProperty(\"booster.transform.${fullVariantName}.enabled\")?.toString()\n            ?.toBoolean() ?: true\n\n    @Suppress(\"UnstableApiUsage\")\n    private val VariantInfo.buildTypeEnabled: Boolean\n        get() = project.findProperty(\"booster.transform.${buildTypeName}.enabled\")?.toString()\n            ?.toBoolean() ?: true\n\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitTransformContext.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform\n\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport org.gradle.api.Project\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 6:31 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface DoKitTransformContext {\n\n    fun project(): Project\n\n    fun dokitExtension(): DoKitExtension\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitTransformInvocation.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform\n\nimport com.android.build.api.transform.*\nimport com.android.build.api.transform.Status.*\nimport com.android.dex.DexFormat\nimport com.didichuxing.doraemonkit.plugin.dex\nimport com.didichuxing.doraemonkit.plugin.dokitTransform\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.gradle.*\nimport com.didiglobal.booster.kotlinx.NCPU\nimport com.didiglobal.booster.kotlinx.file\nimport com.didiglobal.booster.kotlinx.green\nimport com.didiglobal.booster.kotlinx.red\nimport com.didiglobal.booster.transform.*\nimport com.didiglobal.booster.transform.util.transform\nimport org.gradle.api.Project\nimport java.io.File\nimport java.net.URI\nimport java.util.concurrent.*\n\n/**\n * Represents a delegate of TransformInvocation\n *\n * @author johnsonlee\n */\ninternal  class DoKitTransformInvocation(\n    private val delegate: TransformInvocation,\n    internal val transform: DoKitBaseTransform\n) : TransformInvocation by delegate, TransformContext, ArtifactManager,DoKitTransformContext {\n\n    private val project = transform.project\n\n    private val outputs = CopyOnWriteArrayList<File>()\n\n    override val name: String = delegate.context.variantName\n\n    override val projectDir: File = project.projectDir\n\n    override val buildDir: File = project.buildDir\n\n    override val temporaryDir: File = delegate.context.temporaryDir\n\n    override val reportsDir: File = File(buildDir, \"reports\").also { it.mkdirs() }\n\n    override val bootClasspath = delegate.bootClasspath\n\n    override val compileClasspath = delegate.compileClasspath\n\n    override val runtimeClasspath = delegate.runtimeClasspath\n\n    override val artifacts = this\n\n    override val dependencies: Collection<String> by lazy {\n        ResolvedArtifactResults(variant).map {\n            it.id.displayName\n        }\n    }\n\n    override val klassPool: AbstractKlassPool =\n        object : AbstractKlassPool(compileClasspath, transform.bootKlassPool) {}\n\n    override val applicationId = delegate.applicationId\n\n    override val originalApplicationId = delegate.originalApplicationId\n\n    override val isDebuggable = variant.buildType.isDebuggable\n\n    override val isDataBindingEnabled = delegate.isDataBindingEnabled\n\n    override fun hasProperty(name: String) = project.hasProperty(name)\n\n    override fun <T> getProperty(name: String, default: T): T = project.getProperty(name, default)\n\n    override fun get(type: String) = variant.artifacts.get(type)\n\n    internal fun doFullTransform() = doTransform(this::transformFully)\n\n    internal fun doIncrementalTransform() = doTransform(this::transformIncrementally)\n\n    private fun onPreTransform() {\n        transform.transformers.forEach {\n            it.onPreTransform(this)\n        }\n    }\n\n    private fun onPostTransform() {\n        transform.transformers.forEach {\n            it.onPostTransform(this)\n        }\n    }\n\n    private fun doTransform(block: (ExecutorService) -> Iterable<Future<*>>) {\n        this.outputs.clear()\n        this.onPreTransform()\n\n        val executor = Executors.newFixedThreadPool(NCPU)\n        try {\n            block(executor).forEach {\n                it.get()\n            }\n        } finally {\n            executor.shutdown()\n            executor.awaitTermination(1, TimeUnit.HOURS)\n        }\n\n        this.onPostTransform()\n\n        if (transform.verifyEnabled) {\n            this.doVerify()\n        }\n    }\n\n    private fun transformFully(executor: ExecutorService) = this.inputs.map {\n        it.jarInputs + it.directoryInputs\n    }.flatten().map { input ->\n        executor.submit {\n            val format = if (input is DirectoryInput) Format.DIRECTORY else Format.JAR\n            outputProvider?.let { provider ->\n                project.logger.info(\"Transforming ${input.file}\")\n                input.transform(\n                    provider.getContentLocation(\n                        input.name,\n                        input.contentTypes,\n                        input.scopes,\n                        format\n                    )\n                )\n            }\n        }\n    }\n\n    private fun transformIncrementally(executor: ExecutorService) = this.inputs.map { input ->\n        input.jarInputs.filter { it.status != NOTCHANGED }.map { jarInput ->\n            executor.submit {\n                doIncrementalTransform(jarInput)\n            }\n        } + input.directoryInputs.filter { it.changedFiles.isNotEmpty() }.map { dirInput ->\n            val base = dirInput.file.toURI()\n            executor.submit {\n                doIncrementalTransform(dirInput, base)\n            }\n        }\n    }.flatten()\n\n    @Suppress(\"NON_EXHAUSTIVE_WHEN\")\n    private fun doIncrementalTransform(jarInput: JarInput) {\n        when (jarInput.status) {\n            REMOVED -> jarInput.file.delete()\n            CHANGED, ADDED -> {\n                project.logger.info(\"Transforming ${jarInput.file}\")\n                outputProvider?.let { provider ->\n                    jarInput.transform(\n                        provider.getContentLocation(\n                            jarInput.name,\n                            jarInput.contentTypes,\n                            jarInput.scopes,\n                            Format.JAR\n                        )\n                    )\n                }\n            }\n        }\n    }\n\n    @Suppress(\"NON_EXHAUSTIVE_WHEN\")\n    private fun doIncrementalTransform(dirInput: DirectoryInput, base: URI) {\n        dirInput.changedFiles.forEach { (file, status) ->\n            when (status) {\n                REMOVED -> {\n                    project.logger.info(\"Deleting $file\")\n                    outputProvider?.let { provider ->\n                        provider.getContentLocation(\n                            dirInput.name,\n                            dirInput.contentTypes,\n                            dirInput.scopes,\n                            Format.DIRECTORY\n                        ).parentFile.listFiles()?.asSequence()\n                            ?.filter { it.isDirectory }\n                            ?.map { File(it, dirInput.file.toURI().relativize(file.toURI()).path) }\n                            ?.filter { it.exists() }\n                            ?.forEach { it.delete() }\n                    }\n                    file.delete()\n                }\n                ADDED, CHANGED -> {\n                    project.logger.info(\"Transforming $file\")\n                    outputProvider?.let { provider ->\n                        val root = provider.getContentLocation(\n                            dirInput.name,\n                            dirInput.contentTypes,\n                            dirInput.scopes,\n                            Format.DIRECTORY\n                        )\n                        val output = File(root, base.relativize(file.toURI()).path)\n                        outputs += output\n                        file.transform(output) { bytecode ->\n                            bytecode.transform()\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    private fun doVerify() {\n        outputs.sortedBy(File::nameWithoutExtension).forEach { output ->\n            val out = temporaryDir.file(output.name)\n            val rc = out.dex(\n                output,\n                variant.extension.defaultConfig.targetSdkVersion?.apiLevel\n                    ?: DexFormat.API_NO_EXTENDED_OPCODES\n            )\n            println(\"${if (rc != 0) red(\"✗\") else green(\"✓\")} $output\")\n            out.deleteRecursively()\n        }\n    }\n\n    private fun QualifiedContent.transform(output: File) {\n        outputs += output\n        try {\n            this.file.dokitTransform(output) { bytecode ->\n                bytecode.transform()\n            }\n        } catch (e: Exception) {\n            \"e===>${e.message}\".println()\n            e.printStackTrace()\n        }\n\n    }\n\n    private fun ByteArray.transform(): ByteArray {\n        return transform.transformers.fold(this) { bytes, transformer ->\n            transformer.transform(this@DoKitTransformInvocation, bytes)\n        }\n    }\n\n    override fun project(): Project {\n        return project\n    }\n\n    override fun dokitExtension(): DoKitExtension {\n        return project.extensions.getByType(DoKitExtension::class.java)\n    }\n\n    override fun <R> registerCollector(collector: Collector<R>) {\n        TODO(\"Not yet implemented\")\n    }\n\n    override fun <R> unregisterCollector(collector: Collector<R>) {\n        TODO(\"Not yet implemented\")\n    }\n\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/asmtransform/BaseDoKitAsmTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.asmtransform\n\nimport com.didiglobal.booster.annotations.Priority\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.Transformer\nimport com.didiglobal.booster.transform.asm.ClassTransformer\nimport org.objectweb.asm.ClassReader\nimport org.objectweb.asm.ClassWriter\nimport org.objectweb.asm.tree.ClassNode\nimport java.lang.management.ManagementFactory\nimport java.lang.management.ThreadMXBean\nimport java.util.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/21-16:44\n * 描    述：\n * 修订历史：\n * ================================================\n */\nopen class BaseDoKitAsmTransformer : Transformer {\n    private val threadMxBean = ManagementFactory.getThreadMXBean()\n\n    private val durations = mutableMapOf<ClassTransformer, Long>()\n\n    private val classLoader: ClassLoader\n\n    internal val transformers: Iterable<ClassTransformer>\n\n    constructor() : this(Thread.currentThread().contextClassLoader)\n\n    constructor(classLoader: ClassLoader = Thread.currentThread().contextClassLoader) : this(\n        ServiceLoader.load(ClassTransformer::class.java, classLoader).sortedBy {\n            it.javaClass.getAnnotation(Priority::class.java)?.value ?: 0\n        }, classLoader\n    )\n\n    constructor(\n        transformers: Iterable<ClassTransformer>,\n        classLoader: ClassLoader = Thread.currentThread().contextClassLoader\n    ) {\n        this.classLoader = classLoader\n        this.transformers = transformers\n    }\n\n\n    override fun onPreTransform(context: TransformContext) {\n        this.transformers.forEach { transformer ->\n            this.threadMxBean.sumCpuTime(transformer) {\n                transformer.onPreTransform(context)\n            }\n        }\n    }\n\n    override fun transform(context: TransformContext, bytecode: ByteArray): ByteArray {\n        return ClassWriter(ClassWriter.COMPUTE_MAXS).also { writer ->\n            this.transformers.fold(ClassNode().also { klass ->\n                ClassReader(bytecode).accept(klass, 0)\n            }) { klass, transformer ->\n                this.threadMxBean.sumCpuTime(transformer) {\n                    transformer.transform(context, klass)\n                }\n            }.accept(writer)\n        }.toByteArray()\n    }\n\n    override fun onPostTransform(context: TransformContext) {\n        this.transformers.forEach { transformer ->\n            this.threadMxBean.sumCpuTime(transformer) {\n                transformer.onPostTransform(context)\n            }\n        }\n\n        val w1 = this.durations.keys.map {\n            it.javaClass.name.length\n        }.max() ?: 20\n        this.durations.forEach { (transformer, ns) ->\n            println(\"${transformer.javaClass.name.padEnd(w1 + 1)}: ${ns / 1000000} ms\")\n        }\n    }\n\n    private fun <R> ThreadMXBean.sumCpuTime(transformer: ClassTransformer, action: () -> R): R {\n        val ct0 = this.currentThreadCpuTime\n        val result = action()\n        val ct1 = this.currentThreadCpuTime\n        durations[transformer] = durations.getOrDefault(transformer, 0) + (ct1 - ct0)\n        return result\n    }\n\n}\n\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/asmtransform/DoKitAsmTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.asmtransform\n\nimport com.didiglobal.booster.transform.asm.ClassTransformer\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/21-16:44\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitAsmTransformer(transformers: Iterable<ClassTransformer>) :\n    BaseDoKitAsmTransformer(transformers)\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/AbsClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.isRelease\nimport com.didichuxing.doraemonkit.plugin.transform.DoKitTransformContext\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.ClassTransformer\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.tree.ClassNode\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/5/12-18:06\n * 描    述：\n * 修订历史：\n * ================================================\n */\n\nopen class AbsClassTransformer : ClassTransformer {\n\n\n    open fun onDoKitClassInterceptor(context: TransformContext, klass: ClassNode): Boolean {\n        if (context.isRelease()) {\n            return true\n        }\n        if (!DoKitExtUtil.dokitPluginSwitchOpen()) {\n            return true\n        }\n\n        //过滤kotlin module-info\n        if (klass.className == \"module-info\") {\n            return true\n        }\n        return false\n    }\n\n    override fun transform(context: TransformContext, klass: ClassNode): ClassNode {\n        if (onDoKitClassInterceptor(context, klass)) {\n            return klass\n        }\n\n        if (DoKitExtUtil.ignorePackageNames(klass.className)) {\n            return klass\n        }\n\n        if (context is DoKitTransformContext) {\n            val project = context.project()\n            val dokit = context.dokitExtension()\n            transform(project, dokit, context, klass)\n        }\n        return klass\n    }\n\n    open fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode = klass\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/BigImgClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.getMethodExitInsnNodes\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.ClassNode\nimport org.objectweb.asm.tree.InsnList\nimport org.objectweb.asm.tree.MethodInsnNode\nimport org.objectweb.asm.tree.VarInsnNode\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/14-18:07\n * 描    述：wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b\n * 修订历史：\n * ================================================\n */\nclass BigImgClassTransformer : AbsClassTransformer() {\n\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        if (!dokit.bigImageEnable) {\n            return klass\n        }\n\n        val className = klass.className\n        //glide\n        if (dokit.bigImage.glide && className == \"com.bumptech.glide.request.SingleRequest\") {\n            klass.methods.find { methodNode ->\n                (methodNode.name == \"init\" || methodNode.name == \"<init>\") && methodNode.desc != null\n            }.let { methodNode ->\n                //函数结束的地方插入\n                methodNode?.instructions?.getMethodExitInsnNodes()?.forEach {\n                    \"${context.projectDir.lastPath()}->hook glide  succeed: ${className}_${methodNode.name}_${methodNode.desc}\".println()\n                    methodNode.instructions?.insertBefore(it, createGlideInsnList())\n                }\n            }\n        }\n\n        //picasso\n        if (dokit.bigImage.picasso && className == \"com.squareup.picasso.Request\") {\n            klass.methods.find { methodNode ->\n                methodNode.name == \"<init>\" && methodNode.desc != null\n            }.let { methodNode ->\n                //函数结束的地方插入\n                methodNode?.instructions?.getMethodExitInsnNodes()?.forEach {\n                    \"${context.projectDir.lastPath()}->hook picasso  succeed: ${className}_${methodNode.name}_${methodNode.desc}\".println()\n                    methodNode.instructions?.insertBefore(it, createPicassoInsnList())\n                }\n            }\n        }\n\n        //Fresco\n        if (dokit.bigImage.fresco && className == \"com.facebook.imagepipeline.request.ImageRequest\") {\n            klass.methods.find { methodNode ->\n                methodNode.name == \"<init>\" && methodNode.desc != null\n            }.let { methodNode ->\n                \"${context.projectDir.lastPath()}->hook Fresco succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                //函数开始的地方插入\n                methodNode?.instructions?.insert(createFrescoInsnList())\n            }\n        }\n\n        //ImageLoader\n        if (dokit.bigImage.imageLoader && className == \"com.nostra13.universalimageloader.core.ImageLoadingInfo\") {\n            klass.methods.find { methodNode ->\n                methodNode.name == \"<init>\" && methodNode.desc != null\n            }.let { methodNode ->\n                \"${context.projectDir.lastPath()}->hook ImageLoader  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                methodNode?.instructions?.insert(createImageLoaderInsnList())\n            }\n        }\n\n        //Coil\n        if (dokit.bigImage.coil && className == \"coil.request.ImageRequest\") {\n            \"hook Coil Start\".println()\n            klass.methods.find { methodNode ->\n                methodNode.name == \"<init>\" && methodNode.desc != null\n            }.let { methodNode ->\n                //函数结束的地方插入\n                methodNode?.instructions?.getMethodExitInsnNodes()?.forEach {\n                    \"${context.projectDir.lastPath()}->hook Coil  succeed: ${className}_${methodNode.name}_${methodNode.desc}\".println()\n                    methodNode.instructions?.insertBefore(it, createCoilInsnList())\n                }\n            }\n        }\n        return klass\n    }\n\n    /**\n     * 创建Glide Aop代码指令\n     */\n    private fun createGlideInsnList(): InsnList {\n        return with(InsnList()) {\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/bigimg/glide/GlideHook\",\n                    \"proxy\",\n                    \"(Ljava/lang/Object;)V\",\n                    false\n                )\n            )\n            this\n        }\n    }\n\n    /**\n     * 创建Picasso Aop代码指令\n     */\n    private fun createPicassoInsnList(): InsnList {\n        return with(InsnList()) {\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/bigimg/picasso/PicassoHook\",\n                    \"proxy\",\n                    \"(Ljava/lang/Object;)V\",\n                    false\n                )\n            )\n            this\n        }\n\n    }\n\n    /**\n     * 创建Fresco Aop代码指令\n     */\n    private fun createFrescoInsnList(): InsnList {\n        return with(InsnList()) {\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEVIRTUAL,\n                    \"com/facebook/imagepipeline/request/ImageRequestBuilder\",\n                    \"getSourceUri\",\n                    \"()Landroid/net/Uri;\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEVIRTUAL,\n                    \"com/facebook/imagepipeline/request/ImageRequestBuilder\",\n                    \"getPostprocessor\",\n                    \"()Lcom/facebook/imagepipeline/request/Postprocessor;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/bigimg/fresco/FrescoHook\",\n                    \"proxy\",\n                    \"(Landroid/net/Uri;Lcom/facebook/imagepipeline/request/Postprocessor;)Lcom/facebook/imagepipeline/request/Postprocessor;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEVIRTUAL,\n                    \"com/facebook/imagepipeline/request/ImageRequestBuilder\",\n                    \"setPostprocessor\",\n                    \"(Lcom/facebook/imagepipeline/request/Postprocessor;)Lcom/facebook/imagepipeline/request/ImageRequestBuilder;\",\n                    false\n                )\n            )\n            this\n        }\n\n    }\n\n    /**\n     * 创建ImageLoader Aop代码指令\n     */\n    private fun createImageLoaderInsnList(): InsnList {\n        return with(InsnList()) {\n            add(VarInsnNode(Opcodes.ALOAD, 6))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/bigimg/imageloader/ImageLoaderHook\",\n                    \"proxy\",\n                    \"(Lcom/nostra13/universalimageloader/core/listener/ImageLoadingListener;)Lcom/nostra13/universalimageloader/core/listener/ImageLoadingListener;\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ASTORE, 6))\n            this\n        }\n    }\n\n    private fun createCoilInsnList(): InsnList {\n        return with(InsnList()) {\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/bigimg/coil/CoilHook\",\n                    \"proxy\",\n                    \"(Ljava/lang/Object;)V\",\n                    false\n                )\n            )\n            this\n        }\n    }\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/CommClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.extension.SlowMethodExtension\nimport com.didichuxing.doraemonkit.plugin.formatSuperName\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/14-18:07\n * 描    述：wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b\n * 修订历史：\n * ================================================\n */\nclass CommClassTransformer : AbsClassTransformer() {\n\n    override fun transform(context: TransformContext, klass: ClassNode): ClassNode {\n\n        val className = klass.className\n        val superName = klass.formatSuperName\n\n        //hook Androidx的ComponentActivity\n        if (className != \"com.didichuxing.doraemonkit.aop.mc.DoKitProxyActivity\" && superName == \"android.app.Activity\") {\n            createComponentActivitySuperActivityImpl(klass)\n        }\n\n        return super.transform(context, klass)\n    }\n\n    /**\n     * 类处理转化实现\n     */\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        //查找DoraemonKitReal&pluginConfig方法并插入指定字节码\n        if (className == \"com.didichuxing.doraemonkit.DoKitReal\") {\n            //插件配置\n            klass.methods?.find {\n                it.name == \"pluginConfig\"\n            }.let { methodNode ->\n                \"${context.projectDir.lastPath()}->insert map to the DoKitReal pluginConfig succeed\".println()\n                methodNode?.instructions?.insert(createPluginConfigInsnList(dokit.gpsEnable, dokit.networkEnable, dokit.bigImageEnable))\n            }\n        }\n        return klass\n    }\n\n    /**\n     * 创建pluginConfig代码指令\n     */\n    private fun createPluginConfigInsnList(gpsSwitch: Boolean, networkSwitch: Boolean, bigImgSwitch: Boolean): InsnList {\n        return with(InsnList()) {\n            //new HashMap\n            add(TypeInsnNode(Opcodes.NEW, \"java/util/HashMap\"))\n            add(InsnNode(Opcodes.DUP))\n            add(MethodInsnNode(Opcodes.INVOKESPECIAL, \"java/util/HashMap\", \"<init>\", \"()V\", false))\n            //保存变量\n            add(VarInsnNode(Opcodes.ASTORE, 0))\n            //获取第一个变量\n            //put(\"dokitPluginSwitch\",true)\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(LdcInsnNode(\"dokitPluginSwitch\"))\n            add(InsnNode(if (DoKitExtUtil.dokitPluginSwitchOpen()) Opcodes.ICONST_1 else Opcodes.ICONST_0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"java/lang/Boolean\",\n                    \"valueOf\",\n                    \"(Z)Ljava/lang/Boolean;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEINTERFACE,\n                    \"java/util/Map\",\n                    \"put\",\n                    \"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\",\n                    true\n                )\n            )\n            add(InsnNode(Opcodes.POP))\n\n            //put(\"gpsSwitch\",true)\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(LdcInsnNode(\"gpsSwitch\"))\n            add(InsnNode(if (gpsSwitch) Opcodes.ICONST_1 else Opcodes.ICONST_0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"java/lang/Boolean\",\n                    \"valueOf\",\n                    \"(Z)Ljava/lang/Boolean;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEINTERFACE,\n                    \"java/util/Map\",\n                    \"put\",\n                    \"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\",\n                    true\n                )\n            )\n            add(InsnNode(Opcodes.POP))\n\n            //put(\"networkSwitch\",true)\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(LdcInsnNode(\"networkSwitch\"))\n            add(InsnNode(if (networkSwitch) Opcodes.ICONST_1 else Opcodes.ICONST_0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"java/lang/Boolean\",\n                    \"valueOf\",\n                    \"(Z)Ljava/lang/Boolean;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEINTERFACE,\n                    \"java/util/Map\",\n                    \"put\",\n                    \"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\",\n                    true\n                )\n            )\n            add(InsnNode(Opcodes.POP))\n\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(LdcInsnNode(\"bigImgSwitch\"))\n            add(InsnNode(if (bigImgSwitch) Opcodes.ICONST_1 else Opcodes.ICONST_0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"java/lang/Boolean\",\n                    \"valueOf\",\n                    \"(Z)Ljava/lang/Boolean;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEINTERFACE,\n                    \"java/util/Map\",\n                    \"put\",\n                    \"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\",\n                    true\n                )\n            )\n            add(InsnNode(Opcodes.POP))\n\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(LdcInsnNode(\"methodSwitch\"))\n            add(InsnNode(if (DoKitExtUtil.dokitSlowMethodSwitchOpen()) Opcodes.ICONST_1 else Opcodes.ICONST_0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"java/lang/Boolean\",\n                    \"valueOf\",\n                    \"(Z)Ljava/lang/Boolean;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEINTERFACE,\n                    \"java/util/Map\",\n                    \"put\",\n                    \"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\",\n                    true\n                )\n            )\n            add(InsnNode(Opcodes.POP))\n\n\n            //put(\"methodStrategy\",0)\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(LdcInsnNode(\"methodStrategy\"))\n            add(InsnNode(if (DoKitExtUtil.SLOW_METHOD_STRATEGY == SlowMethodExtension.STRATEGY_STACK) Opcodes.ICONST_0 else Opcodes.ICONST_1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"java/lang/Integer\",\n                    \"valueOf\",\n                    \"(I)Ljava/lang/Integer;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEINTERFACE,\n                    \"java/util/Map\",\n                    \"put\",\n                    \"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\",\n                    true\n                )\n            )\n            add(InsnNode(Opcodes.POP))\n\n            //将HashMap注入到DokitPluginConfig中\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/DokitPluginConfig\",\n                    \"inject\",\n                    \"(Ljava/util/Map;)V\",\n                    false\n                )\n            )\n            this\n        }\n    }\n\n    /**\n     * 重置ComponentActivity的父类\n     */\n    private fun createComponentActivitySuperActivityImpl(klass: ClassNode) {\n        /**\n         * 修改继承的父类\n         */\n        klass.superName = \"com/didichuxing/doraemonkit/aop/mc/DoKitProxyActivity\"\n    }\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/EnterMSClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.*\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.extension.SlowMethodExtension\nimport com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNode\nimport com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNodeUtil\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.asIterable\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/14-18:07\n * 描    述：入口函数 慢函数调用栈 wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b\n * 修订历史：不要指定自动注入 需要手动在DoKitAsmTransformer中通过配置创建\n *\n * 原理:transform()方法的调用是无序的\n * 原因:哪一个class会先被transformer执行是不确定的  但是每一个class被transformer执行顺序是遵循transformer的Priority规则的\n * ================================================\n *\n * 入口函数 慢函数调用栈\n *\n */\nclass EnterMSClassTransformer : AbsClassTransformer() {\n\n    private val thresholdTime = DoKitExtUtil.slowMethodExt.stackMethod.thresholdTime\n    private val level = 0\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        if (!DoKitExtUtil.dokitSlowMethodSwitchOpen()) {\n            return klass\n        }\n\n        if (DoKitExtUtil.SLOW_METHOD_STRATEGY == SlowMethodExtension.STRATEGY_NORMAL) {\n            return klass\n        }\n\n        //默认为Application onCreate 和attachBaseContext\n        val enterMethods = DoKitExtUtil.slowMethodExt.stackMethod.enterMethods\n\n        if (enterMethods.isNotEmpty()) {\n            enterMethods.forEach { enterMethodName ->\n                klass.methods.forEach { methodNode ->\n                    val allMethodName = \"${klass.className}.${methodNode.name}\"\n                    if (allMethodName == enterMethodName) {\n                        \"${context.projectDir.lastPath()}->level-->$level mathched enterMethod===>$allMethodName\".println()\n                        operateMethodInsn(klass, methodNode)\n                    }\n                }\n            }\n        }\n\n        return klass\n    }\n\n\n    private fun operateMethodInsn(klass: ClassNode, methodNode: MethodNode) {\n        //读取全是函数调用的指令\n        methodNode.instructions.asIterable().filterIsInstance(MethodInsnNode::class.java)\n            .filter { methodInsnNode ->\n                methodInsnNode.name != \"<init>\"\n            }.forEach { methodInsnNode ->\n                val methodStackNode = MethodStackNode(\n                    level,\n                    methodInsnNode.ownerClassName,\n                    methodInsnNode.name,\n                    methodInsnNode.desc,\n                    klass.className,\n                    methodNode.name,\n                    methodNode.desc\n                )\n                MethodStackNodeUtil.addMethodStackNode(level, methodStackNode)\n            }\n        //函数出入口插入耗时统计代码\n        //方法入口插入\n        methodNode.instructions.insert(\n            createMethodEnterInsnList(\n                level,\n                klass.className,\n                methodNode.name,\n                methodNode.desc,\n                methodNode.access\n            )\n        )\n        //方法出口插入\n        methodNode.instructions.getMethodExitInsnNodes()?.forEach { methodExitInsnNode ->\n            methodNode.instructions.insertBefore(\n                methodExitInsnNode,\n                createMethodExitInsnList(\n                    level,\n                    klass.className,\n                    methodNode.name,\n                    methodNode.desc,\n                    methodNode.access\n                )\n            )\n        }\n    }\n\n\n    /**\n     * 创建慢函数入口指令集\n     */\n    private fun createMethodEnterInsnList(\n        level: Int,\n        className: String,\n        methodName: String,\n        desc: String,\n        access: Int\n    ): InsnList {\n        val isStaticMethod = access and Opcodes.ACC_STATIC != 0\n        return with(InsnList()) {\n            if (isStaticMethod) {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, DoKitExtUtil.STACK_METHOD_LEVEL))\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeStaticMethodCostStart\",\n                        \"(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V\",\n                        false\n                    )\n                )\n            } else {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, DoKitExtUtil.STACK_METHOD_LEVEL))\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(VarInsnNode(Opcodes.ALOAD, 0))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeObjectMethodCostStart\",\n                        \"(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V\",\n                        false\n                    )\n                )\n            }\n            this\n        }\n\n\n    }\n\n\n    /**\n     * 创建慢函数退出时的指令集\n     */\n    private fun createMethodExitInsnList(\n        level: Int,\n        className: String,\n        methodName: String,\n        desc: String,\n        access: Int\n    ): InsnList {\n        val isStaticMethod = access and Opcodes.ACC_STATIC != 0\n\n        return with(InsnList()) {\n            if (isStaticMethod) {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeStaticMethodCostEnd\",\n                        \"(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V\",\n                        false\n                    )\n                )\n            } else {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(VarInsnNode(Opcodes.ALOAD, 0))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeObjectMethodCostEnd\",\n                        \"(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V\",\n                        false\n                    )\n                )\n            }\n            this\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/GPSAMapClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 3:06 下午\n * @Description 高德地图 hooks\n */\n\nclass GPSAMapClassTransformer : AbsClassTransformer() {\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        if (dokit.gpsEnable && dokit.gps.amap && DoKitExtUtil.DOKIT_GPS_MOCK_INCLUDE) {\n            //插入高德地图定位相关字节码\n            if (className == \"com.amap.api.location.AMapLocationClient\") {\n                //设置监听器\n                klass.methods?.find {\n                    it.name == \"setLocationListener\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook amap map  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(createAmapLocationInsnList())\n                }\n                //反注册监听器\n                klass.methods?.find {\n                    it.name == \"unRegisterLocationListener\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook amap map  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(\n                        createAmapLocationUnRegisterInsnList()\n                    )\n                }\n                //代理getLastKnownLocation\n                klass.methods?.find {\n                    it.name == \"getLastKnownLocation\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook AMapLocationClient getLastKnownLocation  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(createAMapClientLastKnownLocation())\n                }\n\n            }\n//            //插入高德 地图定位相关字节码\n//            if (className == \"com.amap.api.maps.AMap\") {\n//                //设置LocationSource代理\n//                klass.methods?.find {\n//                    it.name == \"setLocationSource\"\n//                }.let { methodNode ->\n//                    \"${context.projectDir.lastPath()}->hook amap map LocationSource  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n//                    methodNode?.instructions?.insert(createAmapLocationSourceInsnList())\n//                }\n//            }\n\n            //插入高德地图导航相关字节码\n            if (className == \"com.amap.api.navi.AMapNavi\") {\n                //设置监听器\n                klass.methods?.find {\n                    it.name == \"addAMapNaviListener\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook amap map navi  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(createAmapNaviInsnList())\n                }\n                //反注册监听器\n                klass.methods?.find {\n                    it.name == \"removeAMapNaviListener\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook amap map navi  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(\n                        createAmapNaviUnRegisterInsnList()\n                    )\n                }\n            }\n        }\n        return klass\n    }\n\n    /**\n     * 创建Amap地图代码指令\n     */\n    private fun createAmapLocationInsnList(): InsnList {\n        return with(InsnList()) {\n            //在AMapLocationClient的 setLocationListener 方法之中插入自定义代理回调类\n            add(\n                TypeInsnNode(\n                    Opcodes.NEW,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/AMapLocationListenerProxy\"\n                )\n            )\n            add(InsnNode(Opcodes.DUP))\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESPECIAL,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/AMapLocationListenerProxy\",\n                    \"<init>\",\n                    \"(Lcom/amap/api/location/AMapLocationListener;)V\",\n                    false\n                )\n            )\n            //对第一个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n\n    /**\n     * 创建Amap地图导航代码指令\n     */\n    private fun createAmapNaviInsnList(): InsnList {\n        return with(InsnList()) {\n            //在AMapNavi的addAMapNaviListener方法之中插入自定义代理回调类\n            add(\n                TypeInsnNode(\n                    Opcodes.NEW,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/AMapNaviListenerProxy\"\n                )\n            )\n            add(InsnNode(Opcodes.DUP))\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESPECIAL,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/AMapNaviListenerProxy\",\n                    \"<init>\",\n                    \"(Lcom/amap/api/navi/AMapNaviListener;)V\",\n                    false\n                )\n            )\n            //对第一个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n\n\n    /**\n     * 创建Amap LocationSource代码指令\n     */\n    private fun createAmapLocationSourceInsnList(): InsnList {\n        return with(InsnList()) {\n            //在AMapNavi的addAMapNaviListener方法之中插入自定义代理回调类\n            add(\n                TypeInsnNode(\n                    Opcodes.NEW,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/AMapLocationSourceProxy\"\n                )\n            )\n            add(InsnNode(Opcodes.DUP))\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESPECIAL,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/AMapLocationSourceProxy\",\n                    \"<init>\",\n                    \"(Lcom/amap/api/maps/LocationSource;)V\",\n                    false\n                )\n            )\n            //对第一个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n\n    /**\n     * 创建AMapLocationClient#LastKnownLocation 字节码替换\n     */\n    private fun createAMapClientLastKnownLocation(): InsnList {\n        return with(InsnList()) {\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/AMapLocationClientProxy\",\n                    \"getLastKnownLocation\",\n                    \"(Lcom/amap/api/location/AMapLocationClient;)Lcom/amap/api/location/AMapLocation;\",\n                    false\n                )\n            )\n            add(InsnNode(Opcodes.ARETURN))\n            this\n        }\n\n    }\n\n    /**\n     * 创建Amap地图UnRegister代码指令\n     */\n    private fun createAmapLocationUnRegisterInsnList(): InsnList {\n        return with(InsnList()) {\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/ThirdMapLocationListenerUtil\",\n                    \"unRegisterAmapLocationListener\",\n                    \"(Lcom/amap/api/location/AMapLocationListener;)Lcom/didichuxing/doraemonkit/gps_mock/map/AMapLocationListenerProxy;\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n    }\n\n    /**\n     * 创建Amap地图 Navi UnRegister代码指令\n     */\n    private fun createAmapNaviUnRegisterInsnList(): InsnList {\n        return with(InsnList()) {\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/ThirdMapLocationListenerUtil\",\n                    \"unRegisterAmapNaviListener\",\n                    \"(Lcom/amap/api/navi/AMapNaviListener;)Lcom/didichuxing/doraemonkit/gps_mock/map/AMapNaviListenerProxy;\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/GPSBDClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 3:06 下午\n * @Description 百度地图 hooks\n */\n\nclass GPSBDClassTransformer : AbsClassTransformer() {\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        if (dokit.gpsEnable && dokit.gps.baidu && DoKitExtUtil.DOKIT_GPS_MOCK_INCLUDE) {\n            //插入百度地图相关字节码\n            if (className == \"com.baidu.location.LocationClient\") {\n                //拦截注册监听器\n                klass.methods?.filter {\n                    it.name == \"registerLocationListener\"\n                        && (it.desc == \"(Lcom/baidu/location/BDLocationListener;)V\" || it.desc == \"(Lcom/baidu/location/BDAbstractLocationListener;)V\")\n                }?.forEach { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook baidu map  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    if (methodNode.desc == \"(Lcom/baidu/location/BDLocationListener;)V\") {\n                        methodNode?.instructions?.insert(createBDLocationListenerInsnList())\n                    } else if (methodNode.desc == \"(Lcom/baidu/location/BDAbstractLocationListener;)V\") {\n                        methodNode?.instructions?.insert(createBDLocationAbsListenerInsnList())\n                    }\n                }\n                //反注册监听器\n                klass.methods?.find {\n                    it.name == \"unRegisterLocationListener\" && it.desc == \"(Lcom/baidu/location/BDLocationListener;)V\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook baidu map  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(\n                        createBDLocationUnRegisterInsnList()\n                    )\n                }\n                //反注册监听器\n                klass.methods?.find {\n                    it.name == \"unRegisterLocationListener\" && it.desc == \"(Lcom/baidu/location/BDAbstractLocationListener;)V\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook baidu map  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(\n                        createBDAbsLocationUnRegisterInsnList()\n                    )\n                }\n            }\n        }\n        return klass\n    }\n\n    /**\n     * 创建百度地图代码指令\n     */\n    private fun createBDLocationListenerInsnList(): InsnList {\n        return with(InsnList()) {\n            //在LocationClient的registerLocationListener方法之中插入自定义代理回调类\n            add(\n                TypeInsnNode(\n                    Opcodes.NEW,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/BDLocationListenerProxy\"\n                )\n            )\n            add(InsnNode(Opcodes.DUP))\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESPECIAL,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/BDLocationListenerProxy\",\n                    \"<init>\",\n                    \"(Lcom/baidu/location/BDLocationListener;)V\",\n                    false\n                )\n            )\n            //对第一个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n\n            this\n        }\n\n    }\n\n    /**\n     * 创建百度地图代码指令\n     */\n    private fun createBDLocationAbsListenerInsnList(): InsnList {\n        return with(InsnList()) {\n            //在LocationClient的registerLocationListener方法之中插入自定义代理回调类\n            add(\n                TypeInsnNode(\n                    Opcodes.NEW,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/BDAbsLocationListenerProxy\"\n                )\n            )\n            add(InsnNode(Opcodes.DUP))\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESPECIAL,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/BDAbsLocationListenerProxy\",\n                    \"<init>\",\n                    \"(Lcom/baidu/location/BDAbstractLocationListener;)V\",\n                    false\n                )\n            )\n            //对第一个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n    }\n\n    /**\n     * 创建百度地图UnRegister代码指令\n     */\n    private fun createBDLocationUnRegisterInsnList(): InsnList {\n        return with(InsnList()) {\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/ThirdMapLocationListenerUtil\",\n                    \"unRegisterBDLocationListener\",\n                    \"(Lcom/baidu/location/BDLocationListener;)Lcom/didichuxing/doraemonkit/gps_mock/map/BDLocationListenerProxy;\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n\n    /**\n     * 创建百度地图UnRegister代码指令\n     */\n    private fun createBDAbsLocationUnRegisterInsnList(): InsnList {\n        return with(InsnList()) {\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/ThirdMapLocationListenerUtil\",\n                    \"unRegisterBDLocationListener\",\n                    \"(Lcom/baidu/location/BDAbstractLocationListener;)Lcom/didichuxing/doraemonkit/gps_mock/map/BDAbsLocationListenerProxy;\",\n                    false\n                )\n            )\n            //对第一个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n\n    /**\n     * 创建百度地图代码指令\n     */\n    private fun createBaiduLocationInsnList(): InsnList {\n        return with(InsnList()) {\n            //在AMapLocationClient的setLocationListener方法之中插入自定义代理回调类\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/BDLocationUtil\",\n                    \"proxy\",\n                    \"(Lcom/baidu/location/BDLocation;)Lcom/baidu/location/BDLocation;\",\n                    false\n                )\n            )\n            //对第一个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/GPSClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.kotlinx.asIterable\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.ClassNode\nimport org.objectweb.asm.tree.MethodInsnNode\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 3:06 下午\n * @Description 系统定位hooks\n */\n\nclass GPSClassTransformer : AbsClassTransformer() {\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        //gps字节码操作\n        if (dokit.gpsEnable && dokit.gps.local && DoKitExtUtil.DOKIT_GPS_MOCK_INCLUDE) {\n            //系统 gpsStatus hook\n            klass.methods.forEach { method ->\n                method.instructions?.iterator()?.asIterable()\n                    ?.filterIsInstance(MethodInsnNode::class.java)?.filter {\n                        it.opcode == Opcodes.INVOKEVIRTUAL &&\n                            it.owner == \"android/location/LocationManager\" &&\n                            it.name == \"getGpsStatus\" &&\n                            it.desc == \"(Landroid/location/GpsStatus;)Landroid/location/GpsStatus;\"\n                    }?.forEach {\n                        \"${context.projectDir.lastPath()}->hook LocationManager#getGpsStatus method  succeed in : ${className}_${method.name}_${method.desc}\".println()\n                        method.instructions.insert(\n                            it,\n                            MethodInsnNode(\n                                Opcodes.INVOKESTATIC,\n                                \"com/didichuxing/doraemonkit/gps_mock/location/GpsStatusUtil\",\n                                \"wrap\",\n                                \"(Landroid/location/GpsStatus;)Landroid/location/GpsStatus;\",\n                                false\n                            )\n                        )\n                    }\n            }\n        }\n        return klass\n    }\n\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/GPSTencentClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 3:06 下午\n * @Description 腾讯地图 hooks\n */\n\nclass GPSTencentClassTransformer : AbsClassTransformer() {\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        if (dokit.gpsEnable && dokit.gps.tencent && DoKitExtUtil.DOKIT_GPS_MOCK_INCLUDE) {\n            //插入腾讯地图相关字节码\n            if (className == \"com.tencent.map.geolocation.TencentLocationManager\") {\n                //持续定位和单次定位\n                klass.methods?.filter {\n                    it.name == \"requestSingleFreshLocation\" || it.name == \"requestLocationUpdates\"\n                }?.forEach { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook tencent map  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(createTencentLocationInsnList())\n                }\n\n                //反注册监听器\n                klass.methods?.find {\n                    it.name == \"removeUpdates\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook tencent map  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(\n                        createTencentLocationUnRegisterInsnList()\n                    )\n                }\n            }\n        }\n        return klass\n    }\n\n    /**\n     * 创建tencent地图代码指令\n     */\n    private fun createTencentLocationInsnList(): InsnList {\n        return with(InsnList()) {\n            //在AMapLocationClient的setLocationListener方法之中插入自定义代理回调类\n            add(\n                TypeInsnNode(\n                    Opcodes.NEW,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/TencentLocationListenerProxy\"\n                )\n            )\n            add(InsnNode(Opcodes.DUP))\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 2))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESPECIAL,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/TencentLocationListenerProxy\",\n                    \"<init>\",\n                    \"(Lcom/tencent/map/geolocation/TencentLocationListener;)V\",\n                    false\n                )\n            )\n            //对第二个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 2))\n\n            this\n        }\n\n    }\n\n    /**\n     * 创建Tencent地图UnRegister代码指令\n     */\n    private fun createTencentLocationUnRegisterInsnList(): InsnList {\n        return with(InsnList()) {\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/ThirdMapLocationListenerUtil\",\n                    \"unRegisterTencentLocationListener\",\n                    \"(Lcom/tencent/map/geolocation/TencentLocationListener;)Lcom/didichuxing/doraemonkit/gps_mock/map/TencentLocationListenerProxy;\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/GSMClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.*\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.extension.SlowMethodExtension\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.asIterable\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/14-18:07\n * 描    述：\n *           全局慢函数业务代码慢函数\n *           wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b\n *\n * 修订历史：\n * ================================================\n *\n *  全局慢函数\n *\n */\nclass GSMClassTransformer : AbsClassTransformer() {\n    val thresholdTime = DoKitExtUtil.slowMethodExt.normalMethod.thresholdTime\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n\n        if (!DoKitExtUtil.dokitSlowMethodSwitchOpen()) {\n            return klass\n        }\n\n        if (DoKitExtUtil.SLOW_METHOD_STRATEGY == SlowMethodExtension.STRATEGY_STACK) {\n            return klass\n        }\n\n        if (DoKitExtUtil.ignorePackageNames(klass.className)) {\n            return klass\n        }\n\n\n        val className = klass.className\n        //没有自定义设置插装包名 默认是以packageName为包名 即全局业务代码插桩\n        DoKitExtUtil.slowMethodExt.normalMethod.packageNames.forEach { packageName ->\n            //包含在白名单中且不在黑名单中\n            if (className.contains(packageName) && notMatchedBlackList(className)) {\n                klass.methods.filter { methodNode ->\n                    methodNode.name != \"<init>\" &&\n                        !methodNode.isEmptyMethod() &&\n                        !methodNode.isSingleMethod() &&\n                        !methodNode.isGetSetMethod() &&\n                        !methodNode.isMainMethod(className)\n                }.forEach { methodNode ->\n                    methodNode.instructions.asIterable()\n                        .filterIsInstance(MethodInsnNode::class.java).let { methodInsnNodes ->\n                            if (methodInsnNodes.isNotEmpty()) {\n                                //方法入口插入\n                                methodNode.instructions.insert(\n                                    createMethodEnterInsnList(\n                                        className,\n                                        methodNode.name,\n                                        methodNode.access\n                                    )\n                                )\n                                //方法出口插入\n                                methodNode.instructions.getMethodExitInsnNodes()\n                                    ?.forEach { methodExitInsnNode ->\n                                        methodNode.instructions.insertBefore(\n                                            methodExitInsnNode,\n                                            createMethodExitInsnList(\n                                                className,\n                                                methodNode.name,\n                                                methodNode.access\n                                            )\n                                        )\n                                    }\n                            }\n                        }\n                }\n            }\n        }\n        return klass\n    }\n\n\n    private fun notMatchedBlackList(className: String): Boolean {\n        for (strBlack in DoKitExtUtil.slowMethodExt.normalMethod.methodBlacklist) {\n            if (className.contains(strBlack)) {\n                return false\n            }\n        }\n\n        return true\n    }\n\n    /**\n     * 创建慢函数入口指令集\n     */\n    private fun createMethodEnterInsnList(\n        className: String,\n        methodName: String,\n        access: Int\n    ): InsnList {\n        val isStaticMethod = access and Opcodes.ACC_STATIC != 0\n        return with(InsnList()) {\n            if (isStaticMethod) {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/MethodCostUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.SIPUSH, thresholdTime))\n                add(LdcInsnNode(\"$className&$methodName\"))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"recodeStaticMethodCostStart\",\n                        \"(ILjava/lang/String;)V\",\n                        false\n                    )\n                )\n            } else {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/MethodCostUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.SIPUSH, thresholdTime))\n                add(LdcInsnNode(\"$className&$methodName\"))\n                add(VarInsnNode(Opcodes.ALOAD, 0))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"recodeObjectMethodCostStart\",\n                        \"(ILjava/lang/String;Ljava/lang/Object;)V\",\n                        false\n                    )\n                )\n            }\n\n            this\n        }\n\n    }\n\n\n    /**\n     * 创建慢函数退出时的指令集\n     */\n    private fun createMethodExitInsnList(\n        className: String,\n        methodName: String,\n        access: Int\n    ): InsnList {\n        val isStaticMethod = access and Opcodes.ACC_STATIC != 0\n        return with(InsnList()) {\n            if (isStaticMethod) {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/MethodCostUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.SIPUSH, thresholdTime))\n                add(LdcInsnNode(\"$className&$methodName\"))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"recodeStaticMethodCostEnd\",\n                        \"(ILjava/lang/String;)V\",\n                        false\n                    )\n                )\n            } else {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/MethodCostUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.SIPUSH, thresholdTime))\n                add(LdcInsnNode(\"$className&$methodName\"))\n                add(VarInsnNode(Opcodes.ALOAD, 0))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"recodeObjectMethodCostEnd\",\n                        \"(ILjava/lang/String;Ljava/lang/Object;)V\",\n                        false\n                    )\n                )\n            }\n            this\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/MSDClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.*\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.extension.SlowMethodExtension\nimport com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNode\nimport com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNodeUtil\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.asIterable\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/14-18:07\n * 描    述：函数调用栈依赖 慢函数调用栈 wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b\n * 修订历史：不要指定自动注入 需要手动在DoKitAsmTransformer中通过配置创建\n *\n * 原理:transform()方法的调用是无序的\n * 原因:哪一个class会先被transformer执行是不确定的  但是每一个class被transformer执行顺序是遵循transformer的Priority规则的\n * ================================================\n */\nclass MSDClassTransformer(private val level: Int = 1) : AbsClassTransformer() {\n\n    private val thresholdTime = DoKitExtUtil.slowMethodExt.stackMethod.thresholdTime\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n\n        if (!DoKitExtUtil.dokitSlowMethodSwitchOpen()) {\n            return klass\n        }\n\n        if (DoKitExtUtil.SLOW_METHOD_STRATEGY == SlowMethodExtension.STRATEGY_NORMAL) {\n            return klass\n        }\n\n        if (DoKitExtUtil.ignorePackageNames(klass.className)) {\n            return klass\n        }\n\n        //命中黑名单则不插桩\n        if (!notMatchedBlackList(klass.className)) {\n            return klass\n        }\n\n\n        val methodStackKeys: MutableSet<String> = MethodStackNodeUtil.METHOD_STACK_KEYS[level - 1]\n\n        klass.methods.filter { methodNode ->\n            methodNode.name != \"<init>\" &&\n                !methodNode.isEmptyMethod() &&\n                !methodNode.isSingleMethod() &&\n                !methodNode.isGetSetMethod() &&\n                !methodNode.isMainMethod(klass.className)\n        }.forEach { methodNode ->\n            val key = \"${klass.className}&${methodNode.name}&${methodNode.desc}\"\n            if (methodStackKeys.contains(key)) {\n                \"${context.projectDir.lastPath()}->level-->$level   mathched key===>$key\".println()\n                operateMethodInsn(klass, methodNode)\n            }\n\n        }\n\n        return klass\n    }\n\n\n    private fun operateMethodInsn(klass: ClassNode, methodNode: MethodNode) {\n        //读取全是函数调用的指令\n        methodNode.instructions.asIterable().filterIsInstance(MethodInsnNode::class.java)\n            .filter { methodInsnNode ->\n                methodInsnNode.name != \"<init>\"\n            }.forEach { methodInsnNode ->\n                val methodStackNode = MethodStackNode(\n                    level,\n                    methodInsnNode.ownerClassName,\n                    methodInsnNode.name,\n                    methodInsnNode.desc,\n                    klass.className,\n                    methodNode.name,\n                    methodNode.desc\n                )\n                MethodStackNodeUtil.addMethodStackNode(level, methodStackNode)\n            }\n        //函数出入口插入耗时统计代码\n        //方法入口插入\n        methodNode.instructions.insert(\n            createMethodEnterInsnList(\n                level,\n                klass.className,\n                methodNode.name,\n                methodNode.desc,\n                methodNode.access\n            )\n        )\n        //方法出口插入\n        methodNode.instructions.getMethodExitInsnNodes()?.forEach { methodExitInsnNode ->\n            methodNode.instructions.insertBefore(\n                methodExitInsnNode,\n                createMethodExitInsnList(\n                    level,\n                    klass.className,\n                    methodNode.name,\n                    methodNode.desc,\n                    methodNode.access\n                )\n            )\n        }\n    }\n\n\n    /**\n     * 创建慢函数入口指令集\n     */\n    private fun createMethodEnterInsnList(\n        level: Int,\n        className: String,\n        methodName: String,\n        desc: String,\n        access: Int\n    ): InsnList {\n        val isStaticMethod = access and Opcodes.ACC_STATIC != 0\n        return with(InsnList()) {\n            if (isStaticMethod) {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, DoKitExtUtil.STACK_METHOD_LEVEL))\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeStaticMethodCostStart\",\n                        \"(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V\",\n                        false\n                    )\n                )\n            } else {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, DoKitExtUtil.STACK_METHOD_LEVEL))\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(VarInsnNode(Opcodes.ALOAD, 0))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeObjectMethodCostStart\",\n                        \"(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V\",\n                        false\n                    )\n                )\n            }\n            this\n        }\n\n    }\n\n\n    /**\n     * 创建慢函数退出时的指令集\n     */\n    private fun createMethodExitInsnList(\n        level: Int,\n        className: String,\n        methodName: String,\n        desc: String,\n        access: Int\n    ): InsnList {\n        val isStaticMethod = access and Opcodes.ACC_STATIC != 0\n        return with(InsnList()) {\n            if (isStaticMethod) {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeStaticMethodCostEnd\",\n                        \"(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V\",\n                        false\n                    )\n                )\n            } else {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(VarInsnNode(Opcodes.ALOAD, 0))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeObjectMethodCostEnd\",\n                        \"(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V\",\n                        false\n                    )\n                )\n            }\n            this\n        }\n\n    }\n\n    private fun notMatchedBlackList(className: String): Boolean {\n        for (strBlack in DoKitExtUtil.slowMethodExt.stackMethod.methodBlacklist) {\n            if (className.contains(strBlack)) {\n                return false\n            }\n        }\n\n        return true\n    }\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/Okhttp3ClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.kotlinx.asIterable\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 3:06 下午\n * @Description 用一句话说明文件功能\n */\n\nclass Okhttp3ClassTransformer : AbsClassTransformer() {\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        if (dokit.networkEnable && dokit.network.okHttp) {\n            //hook OkhttpClient\n            if (className == \"okhttp3.OkHttpClient\") {\n                klass.methods?.find {\n                    it.name == \"<init>\" && it.desc != \"()V\"\n                }.let {\n                    \"${context.projectDir.lastPath()}->hook OkhttpClient  succeed: ${className}_${it?.name}_${it?.desc}\".println()\n                    it?.instructions\n                        ?.iterator()\n                        ?.asIterable()\n                        ?.filterIsInstance(FieldInsnNode::class.java)\n                        ?.filter { fieldInsnNode ->\n                            fieldInsnNode.opcode == Opcodes.PUTFIELD\n                                && fieldInsnNode.owner == \"okhttp3/OkHttpClient\"\n                                && fieldInsnNode.name == \"networkInterceptors\"\n                                && fieldInsnNode.desc == \"Ljava/util/List;\"\n                        }?.forEach { fieldInsnNode ->\n                            it.instructions.insert(fieldInsnNode, createOkHttpClientInsnList())\n                        }\n                }\n            }\n        }\n        return klass\n    }\n\n    /**\n     * 创建OkhttpClient一个数构造函数指令\n     */\n    private fun createOkHttpClientInsnList(): InsnList {\n        return with(InsnList()) {\n            //插入application 拦截器\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/OkHttpHook\",\n                    \"addDoKitIntercept\",\n                    \"(Lokhttp3/OkHttpClient;)V\",\n                    false\n                )\n            )\n            this\n        }\n\n    }\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/ThirdLibsClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 3:06 下午\n * @Description 用一句话说明文件功能\n */\n\nclass ThirdLibsClassTransformer : AbsClassTransformer() {\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        if (dokit.thirdLibEnable) {\n            //查找DoraemonKitReal&pluginConfig方法并插入指定字节码\n            if (className == \"com.didichuxing.doraemonkit.DoKitReal\") {\n                //三方库信息注入\n                klass.methods?.find {\n                    it.name == \"initThirdLibraryInfo\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->insert map to the DoKitReal initThirdLibraryInfo succeed\".println()\n                    methodNode?.instructions?.insert(createThirdLibInfoInsnList())\n                }\n            }\n        }\n\n        return klass\n    }\n\n    /**\n     * 创建pluginConfig代码指令\n     */\n    private fun createThirdLibInfoInsnList(): InsnList {\n        //val insnList = InsnList()\n        return with(InsnList()) {\n            //new HashMap\n            add(TypeInsnNode(Opcodes.NEW, \"java/util/HashMap\"))\n            add(InsnNode(Opcodes.DUP))\n            add(MethodInsnNode(Opcodes.INVOKESPECIAL, \"java/util/HashMap\", \"<init>\", \"()V\", false))\n            //保存变量\n            add(VarInsnNode(Opcodes.ASTORE, 0))\n            DoKitExtUtil.THIRD_LIB_INFOS.forEach { thirdLibInfo ->\n                add(VarInsnNode(Opcodes.ALOAD, 0))\n                add(LdcInsnNode(thirdLibInfo.variant))\n                add(LdcInsnNode(thirdLibInfo.fileSize.toString()))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEINTERFACE,\n                        \"java/util/Map\",\n                        \"put\",\n                        \"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\",\n                        false\n                    )\n                )\n                add(InsnNode(Opcodes.POP))\n            }\n            //将HashMap注入到DokitPluginConfig中\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/DokitThirdLibInfo\",\n                    \"inject\",\n                    \"(Ljava/util/Map;)V\",\n                    false\n                )\n            )\n            this\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/UrlConnectionTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.kotlinx.asIterable\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.ClassNode\nimport org.objectweb.asm.tree.MethodInsnNode\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/14-18:07\n * 描    述：wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b\n * 修订历史：\n * ================================================\n */\nclass UrlConnectionTransformer : AbsClassTransformer() {\n\n    private val SHADOW_URL = \"com/didichuxing/doraemonkit/aop/urlconnection/HttpUrlConnectionProxyUtil\"\n\n    private val DESC = \"(Ljava/net/URLConnection;)Ljava/net/URLConnection;\"\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        if (dokit.networkEnable && dokit.network.urlConnect) {\n            // url connection\n            klass.methods.forEach { method ->\n                method.instructions?.iterator()?.asIterable()\n                    ?.filterIsInstance(MethodInsnNode::class.java)?.filter {\n                        it.opcode == Opcodes.INVOKEVIRTUAL &&\n                            it.owner == \"java/net/URL\" &&\n                            it.name == \"openConnection\" &&\n                            it.desc == \"()Ljava/net/URLConnection;\"\n                    }?.forEach {\n                        \"${context.projectDir.lastPath()}->hook URL#openConnection method  succeed in : ${className}_${method.name}_${method.desc}\".println()\n                        method.instructions.insert(\n                            it,\n                            MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_URL, \"proxy\", DESC, false)\n                        )\n                    }\n            }\n        }\n        return klass\n    }\n\n\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/WebViewClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.formatSuperName\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.kotlinx.asIterable\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 3:06 下午\n * @Description WebView hooks\n */\n\nclass WebViewClassTransformer : AbsClassTransformer() {\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n\n        val className = klass.className\n        val superName = klass.formatSuperName\n\n        //网络 OkHttp&didi platform aop\n        if (dokit.webViewEnable) {\n            //webView 字节码操作\n            if (dokit.webView.network) {\n                //普通的webview\n                klass.methods.forEach { method ->\n                    method.instructions?.iterator()?.asIterable()\n                        ?.filterIsInstance(MethodInsnNode::class.java)?.filter {\n                            if (\"loadUrl\".equals(it.name)) {\n                                \"hook loadUrl() all ${className} ^${superName}^${it.owner} :: ${it.name} , ${it.desc} ,${it.opcode}\".println()\n                            }\n                            (it.opcode == Opcodes.INVOKEVIRTUAL || it.opcode == Opcodes.INVOKESPECIAL)\n                                && it.name == \"loadUrl\"\n                                && (it.desc == \"(Ljava/lang/String;)V\" || it.desc == \"(Ljava/lang/String;Ljava/util/Map;)V\")\n                                && isWebViewOwnerNameMatched(it.owner)\n                        }?.forEach {\n                            \"${context.projectDir.lastPath()}->hook WebView#loadurl method  succeed in :  ${className}_${method.name}_${method.desc} | ${it.owner}\".println()\n                            if (it.desc == \"(Ljava/lang/String;)V\") {\n                                method.instructions.insertBefore(it, createWebViewInsnList())\n                            } else {\n                                method.instructions.insertBefore(it, createWebViewInsnList(method))\n                            }\n                        }\n                }\n            }\n        }\n        return klass\n    }\n\n\n    private fun isWebViewOwnerNameMatched(ownerName: String): Boolean {\n        return ownerName == \"android/webkit/WebView\" ||\n            ownerName == \"com/tencent/smtt/sdk/WebView\" ||\n            ownerName.contentEquals(\"WebView\") ||\n            ownerName == DoKitExtUtil.WEBVIEW_CLASS_NAME\n    }\n\n\n    /**\n     * 创建webView函数指令集\n     * 参考:https://www.jianshu.com/p/7d623f441bed\n     */\n    private fun createWebViewInsnList(): InsnList {\n        return with(InsnList()) {\n            //复制栈顶的2个指令 指令集变为 比如 aload 2 aload0 / aload 2 aload0\n            add(InsnNode(Opcodes.DUP2))\n            //抛出最上面的指令 指令集变为 aload 2 aload0 / aload 2  其中 aload 2即为我们所需要的对象\n            add(InsnNode(Opcodes.POP))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/WebViewHook\",\n                    \"inject\",\n                    \"(Ljava/lang/Object;)V\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/WebViewHook\",\n                    \"getSafeUrl\",\n                    \"(Ljava/lang/String;)Ljava/lang/String;\",\n                    false\n                )\n            )\n            this\n        }\n    }\n\n    /**\n     * 创建webView函数指令集 (多参数，先存参数然后取出)\n     * 参考:https://www.jianshu.com/p/7d623f441bed\n     */\n    private fun createWebViewInsnList(method: MethodNode): InsnList {\n        val size = method.localVariables.size\n        return with(InsnList()) {\n            add(VarInsnNode(Opcodes.ASTORE, size + 1))\n            add(VarInsnNode(Opcodes.ASTORE, size))\n            add(InsnNode(Opcodes.DUP))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/WebViewHook\",\n                    \"inject\",\n                    \"(Ljava/lang/Object;)V\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ALOAD, size))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/WebViewHook\",\n                    \"getSafeUrl\",\n                    \"(Ljava/lang/String;)Ljava/lang/String;\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ALOAD, size + 1))\n            this\n        }\n    }\n}\n"
  },
  {
    "path": "Android/buildSrc/src/main/resources/META-INF/gradle-plugins/com.didi.dokit.debug.properties",
    "content": "#入口文件\n#文件名为插件名 即 apply plugin \"xxx\"\nimplementation-class=com.didichuxing.doraemonkit.plugin.DoKitPlugin"
  },
  {
    "path": "Android/config.gradle",
    "content": "ext {\n    publish_config = [\n        //0:发布到到本地localRepoURL仓库\n        //1:发布到滴滴内部仓库 一般不建议使用 如果需要发布到滴滴内网仓库需要将版本号改得比较大 假如版本号跟jcenter上的一致会由于缓存导致没法下载最新的jcenter的线上代码\n        //2:发布到maven_central远程仓库\n        archives_type: 0,\n        //0:依赖dokit本地module运行\n        //1:依赖dokit远程aar运行\n        run_type     : 0,\n        //0:外部公共环境\n        //1:内部私有环境，使用私有仓库构建，仓库地址在 local.properties 中添加 PRIVATE_REPOSITORY_URL\n        run_env      : 1,\n        //是否使用本地仓库，需要使用绝对路径，仓库地址在 local.properties 中添加 LOCAL_REPOSITORY_URL\n        use_local    : true,\n//        group_id     : 'io.github.didi.dokit',\n        group_id     : 'com.didichuxing.doraemonkit',\n        version      : '3.7.14.9-kotlin-13'\n    ]\n\n    android = [compileSdkVersion             : 31,\n               applicationId                 : \"com.didichuxing.doraemondemo\",\n               minSdkVersion_16              : 16,\n               minSdkVersion_21              : 21,\n               targetSdkVersion              : 31,\n               //app版本号\n               versionCode                   : 1,\n               versionName                   : \"1.0.0\",\n               glide_version                 : \"4.9.0\",\n               kotlin_version_v13            : \"1.3.72\",\n               kotlin_version_v14            : \"1.4.32\",\n               kotlin_version_v15            : \"1.5.31\",\n               kotlinx_coroutines_version_v13: \"1.3.7\",\n               kotlinx_coroutines_version_v14: \"1.4.3\",\n               booster_version               : \"4.13.0\",\n               agp_module_verson             : \"7.0.0\",\n               agp_plugin_verson             : \"4.2.2\",\n               ktor                          : \"1.5.4\",\n               okhttp_v2                     : \"2.7.5\",\n               okhttp_v3                     : \"3.14.7\",\n               okhttp_v4                     : \"4.7.0\",\n               coil_v11                      : \"1.1.0\",\n               coil_v13                      : \"1.3.2\",\n    ]\n\n    dependencies = [// ###### android library  start ######\n                    \"multidex\"                      : 'androidx.multidex:multidex:2.0.0',\n                    \"appcompat\"                     : 'androidx.appcompat:appcompat:1.1.0',\n                    \"cardview\"                      : 'androidx.cardview:cardview:1.0.0',\n                    \"recyclerview\"                  : 'androidx.recyclerview:recyclerview:1.1.0',\n                    \"material\"                      : 'com.google.android.material:material:1.1.0',\n                    \"annotation\"                    : \"androidx.annotation:annotation:1.1.0\",\n                    \"kotlin_v13\"                    : \"org.jetbrains.kotlin:kotlin-stdlib:${android[\"kotlin_version_v13\"]}\",\n                    \"kotlin_v14\"                    : \"org.jetbrains.kotlin:kotlin-stdlib:${android[\"kotlin_version_v14\"]}\",\n                    \"coroutines-core_v13\"           : \"org.jetbrains.kotlinx:kotlinx-coroutines-core:${android[\"kotlinx_coroutines_version_v13\"]}\",\n                    \"coroutines-core_v14\"           : \"org.jetbrains.kotlinx:kotlinx-coroutines-core:${android[\"kotlinx_coroutines_version_v14\"]}\",\n                    \"coroutines-android_v13\"        : \"org.jetbrains.kotlinx:kotlinx-coroutines-android:${android[\"kotlinx_coroutines_version_v13\"]}\",\n                    \"coroutines-android_v14\"        : \"org.jetbrains.kotlinx:kotlinx-coroutines-android:${android[\"kotlinx_coroutines_version_v14\"]}\",\n                    \"core-ktx\"                      : \"androidx.core:core-ktx:1.3.0\",\n                    \"activity-ktx\"                  : \"androidx.activity:activity-ktx:1.1.0\",\n                    \"webkit\"                        : \"androidx.webkit:webkit:1.3.0\",\n                    \"volley\"                        : \"com.android.volley:volley:1.1.1\",\n                    \"fragment\"                      : \"androidx.fragment:fragment:1.2.0\",\n                    \"fragment-ktx\"                  : \"androidx.fragment:fragment-ktx:1.2.0\",\n                    \"drawerlayout\"                  : \"androidx.drawerlayout:drawerlayout:1.1.1\",\n                    //constraintLayout\n                    \"constraintLayout\"              : 'androidx.constraintlayout:constraintlayout:1.1.3',\n\n                    //lifecycle\n                    \"lifecycle-viewmodel-savedstate\": \"androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0\",\n                    \"lifecycle-comm\"                : \"androidx.lifecycle:lifecycle-common-java8:2.2.0\",\n                    //test\n                    \"junit\"                         : \"junit:junit:4.12\",\n                    //第三方\n                    \"guava\"                         : \"com.google.guava:guava:27.1-android\",\n                    //表格\n                    //\"SmartTable\"                    : 'com.github.huangyanbin:SmartTable:2.2.0',\n                    \"retrofit2\"                     : \"com.squareup.retrofit2:retrofit:2.9.0\",\n                    \"retrofit2_scalars\"             : \"com.squareup.retrofit2:converter-scalars:2.9.0\",\n                    \"retrofit2_gson\"                : \"com.squareup.retrofit2:converter-gson:2.9.0\",\n                    \"retrofit2_rxjava2\"             : \"com.squareup.retrofit2:adapter-rxjava2:2.9.0\",\n                    \"rxAndroid\"                     : \"io.reactivex.rxjava2:rxandroid:2.1.1\",\n                    //暂时不支持3.14.0以上的okhttp版本\n                    \"okhttp_v2\"                     : \"com.squareup.okhttp:okhttp:${android[\"okhttp_v2\"]}\",\n                    \"okhttp_v3\"                     : \"com.squareup.okhttp3:okhttp:${android[\"okhttp_v3\"]}\",\n                    \"okhttp_v4\"                     : \"com.squareup.okhttp3:okhttp:${android[\"okhttp_v4\"]}\",\n                    //okSocket wiki:https://www.jianshu.com/p/8ee3ee766265\n                    \"oksocket\"                      : \"com.tonystark.android:socket:4.2.2\",\n                    \"oksocket-server\"               : \"com.tonystark.android:socket-server:4.2.2\",\n                    //ktor\n                    \"ktor_server_core\"              : \"io.ktor:ktor-server-core:${android[\"ktor\"]}\",\n                    \"ktor_server_cio\"               : \"io.ktor:ktor-server-cio:${android[\"ktor\"]}\",\n                    \"ktor_server_netty\"             : \"io.ktor:ktor-server-netty:${android[\"ktor\"]}\",\n                    \"ktor_server_websockets\"        : \"io.ktor:ktor-websockets:${android[\"ktor\"]}\",\n                    \"ktor_client_websockets\"        : \"io.ktor:ktor-client-websockets:${android[\"ktor\"]}\",\n                    \"ktor_client_cio\"               : \"io.ktor:ktor-client-cio:${android[\"ktor\"]}\",\n                    \"ktor_client_apache\"            : \"io.ktor:ktor-client-apache:${android[\"ktor\"]}\",\n                    \"ktor_client_android\"           : \"io.ktor:ktor-client-android:${android[\"ktor\"]}\",\n                    \"ktor_client_okhttp\"            : \"io.ktor:ktor-client-okhttp:${android[\"ktor\"]}\",\n                    \"ktor_jackson\"                  : \"io.ktor:ktor-jackson:${android[\"ktor\"]}\",\n                    //将urlconnection代理到okhttp\n                    //\"okhttp_urlconnection\"    : \"com.squareup.okhttp3:okhttp-urlconnection:3.12.1\",\n                    \"okio\"                          : \"com.squareup.okio:okio:1.17.2\",\n                    \"utilcode\"                      : 'com.blankj:utilcodex:1.29.0',\n                    \"glide\"                         : \"com.github.bumptech.glide:glide:${android[\"glide_version\"]}\",\n                    \"glide_v3\"                      : 'com.github.bumptech.glide:glide:3.8.0',\n                    \"glide_okhttp3\"                 : \"com.github.bumptech.glide:okhttp3-integration:${android[\"glide_version\"]}\",\n                    \"glide_compiler\"                : \"com.github.bumptech.glide:compiler:${android[\"glide_version\"]}\",\n                    \"coil_v11\"                      : \"io.coil-kt:coil:${android[\"coil_v11\"]}\",\n                    \"coil_v13\"                      : \"io.coil-kt:coil:${android[\"coil_v13\"]}\",\n                    \"picasso\"                       : 'com.squareup.picasso:picasso:2.71828',\n                    \"fresco\"                        : 'com.facebook.fresco:fresco:1.13.0',\n                    \"fresco-processors\"             : \"jp.wasabeef:fresco-processors:2.1.0\",\n                    \"image-loader\"                  : 'com.nostra13.universalimageloader:universal-image-loader:1.9.5',\n                    \"rootbeer-lib\"                  : 'com.scottyab:rootbeer-lib:0.0.8',\n                    \"gson\"                          : 'com.google.code.gson:gson:2.8.2',\n                    \"zxing\"                         : 'com.google.zxing:core:3.3.0',\n                    \"free_reflection\"               : 'me.weishu:free_reflection:2.1.0',\n                    \"leakcanary_android\"            : 'com.squareup.leakcanary:leakcanary-android:2.7',\n                    \"haha\"                          : 'com.squareup.haha:haha:2.0.4',\n                    \"debug-db\"                      : \"com.amitshekhar.android:debug-db:1.0.6\",\n                    \"debug-db-encrypt\"              : \"com.amitshekhar.android:debug-db-encrypt:1.0.6\",\n                    //https://github.com/Tencent/wcdb\n                    \"wcdb\"                          : \"com.tencent.wcdb:wcdb-android:1.0.8\",\n                    \"weex_inspector\"                : \"com.taobao.android:weex_inspector:0.24.2.11\",\n                    \"weex_sdk\"                      : \"org.apache.weex:sdk:0.28.0\",\n                    \"brvah\"                         : 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4',\n                    \"easy_refresh_layout\"           : 'com.github.anzaizai:EasyRefreshLayout:1.3.1',\n                    \"android_spinkit\"               : 'com.github.ybq:Android-SpinKit:1.4.0',\n                    \"jsonviewer\"                    : \"com.yuyh.json:jsonviewer:1.0.6\",\n                    \"room_runtime\"                  : 'androidx.room:room-runtime:2.0.0',\n                    \"room_compile\"                  : 'androidx.room:room-compiler:2.0.0',\n                    \"didi_http\"                     : 'com.didichuxing.foundation:http:2.1.0.86',\n                    \"didi_rpc\"                      : 'com.didichuxing.foundation:rpc:2.1.0.86',\n                    \"okgo\"                          : \"com.lzy.net:okgo:3.0.4\",\n                    //高德地图定位\n                    \"amap_location\"                 : 'com.amap.api:location:6.1.0',\n                    //高德地图\n                    \"amap_search\"                   : 'com.amap.api:search:9.2.0',\n                    \"amap_navi\"                     : 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0',\n                    //腾讯地图定位\n                    \"tencent_location\"              : 'com.tencent.map.geolocation:TencentLocationSdk-openplatform:7.2.5',\n                    \"tencent_map\"                   : 'com.tencent.map:tencent-map-vector-sdk:4.4.0',\n                    \"tencent_map_util\"              : 'com.tencent.map:sdk-utilities:1.0.6',\n                    \"easypermissions\"               : \"pub.devrel:easypermissions:2.0.1\",\n                    //跨进程通信框架\n                    \"abridge\"                       : \"com.sjtu.yifei:abridge:1.0.1\",\n                    \"jsoup\"                         : \"org.jsoup:jsoup:1.13.1\",\n                    \"mimecraft\"                     : \"com.squareup.mimecraft:mimecraft:1.1.1\",\n                    //tencent x5 浏览器 https://x5.tencent.com/\n                    \"tbs\"                           : \"com.tencent.tbs.tbssdk:sdk:43903\",\n//                    \"swipeback\"    : \"me.imid.swipebacklayout.lib:library:1.1.0\"\n                    \"flexbox\"                       : 'com.google.android:flexbox:1.0.0',\n                    \"auto_service\"                  : 'com.google.auto.service:auto-service:1.0',\n                    \"hummer\"                        : 'io.github.didi.hummer:hummer:0.3.23',\n                    \"sandhook_hooklib\"              : 'com.swift.sandhook:hooklib:4.2.1',\n                    \"sandhook_nativehook\"           : 'com.swift.sandhook:nativehook:4.2.1',\n                    \"sandhook_xposedcompat\"         : 'com.swift.sandhook:xposedcompat:4.2.1',\n                    \"epic\"                          : 'me.weishu:epic:0.11.1',\n//                    \"epic\"                          : 'com.github.tiann:epic:0.11.2',\n\n    ]\n    //定义全局方法\n    needKotlinV14 = {\n        String version = publish_config[\"version\"]\n        return !version.contains(\"kotlin-13\")\n    }\n\n    usePrivateEnv = {\n        def runEnv = publish_config[\"run_env\"]\n        return runEnv == 1\n    }\n\n    useLocalRepository = {\n        def useLocal = publish_config[\"use_local\"]\n        return useLocal\n    }\n\n    privateRepository = {\n        Properties properties = new Properties()\n        properties.load(project.rootProject.file('local.properties').newDataInputStream())\n        def url = properties.getProperty('PRIVATE_REPOSITORY_URL', \"\")\n        return url\n    }\n\n    localRepository = {\n        Properties properties = new Properties()\n        properties.load(project.rootProject.file('local.properties').newDataInputStream())\n        def url = properties.getProperty('LOCAL_REPOSITORY_URL', \"\")\n        return url\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/.gitignore",
    "content": "/build\n/.cxx\n"
  },
  {
    "path": "Android/dokit/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-kapt'\napply from: '../upload.gradle'\n\ndef dokitVersion = rootProject.ext.publish_config[\"version\"]\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_16\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n\n        lintOptions {\n            abortOnError false\n        }\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        externalNativeBuild {\n            cmake {\n                cppFlags \"-std=c++11\"\n                arguments '-DANDROID_STL=c++_static'\n            }\n        }\n        javaCompileOptions {\n            annotationProcessorOptions {\n                includeCompileClasspath true\n            }\n        }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n            buildConfigField(\"String\", \"DOKIT_VERSION\", \"\\\"\" + dokitVersion + \"\\\"\")\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n            buildConfigField(\"String\", \"DOKIT_VERSION\", \"\\\"\" + dokitVersion + \"\\\"\")\n        }\n    }\n//    externalNativeBuild {\n//        cmake {\n//            path \"src/main/cpp/CMakeLists.txt\"\n//        }\n//    }\n\n//    compileOptions {\n//        sourceCompatibility JavaVersion.VERSION_1_8\n//        targetCompatibility JavaVersion.VERSION_1_8\n//    }\n\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n\n\n    /**\n     * 支持ViewBinding\n     */\n    buildFeatures {\n//        viewBinding = true\n        dataBinding = true\n    }\n\n\n}\n\ndependencies {\n//    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    //noinspection GradleCompatible\n    testImplementation 'junit:junit:4.12'\n    androidTestImplementation 'junit:junit:4.12'\n    if (needKotlinV14()) {\n        implementation rootProject.ext.dependencies[\"kotlin_v14\"]\n    } else {\n        implementation rootProject.ext.dependencies[\"kotlin_v13\"]\n    }\n    implementation rootProject.ext.dependencies[\"lifecycle-comm\"]\n\n    implementation rootProject.ext.dependencies[\"constraintLayout\"]\n    implementation rootProject.ext.dependencies[\"appcompat\"]\n    implementation rootProject.ext.dependencies[\"cardview\"]\n    implementation rootProject.ext.dependencies[\"recyclerview\"]\n    implementation rootProject.ext.dependencies[\"core-ktx\"]\n    implementation rootProject.ext.dependencies[\"activity-ktx\"]\n    implementation rootProject.ext.dependencies[\"webkit\"]\n    implementation rootProject.ext.dependencies[\"volley\"]\n    api rootProject.ext.dependencies[\"gson\"]\n    implementation rootProject.ext.dependencies[\"zxing\"]\n\n    //远程调试db\n    //implementation rootProject.ext.dependencies[\"debug-db\"]\n    //implementation rootProject.ext.dependencies[\"debug-db-encrypt\"]\n    implementation rootProject.ext.dependencies[\"android_spinkit\"]\n    implementation rootProject.ext.dependencies[\"okhttp_v2\"]\n    implementation rootProject.ext.dependencies[\"okhttp_v3\"]\n    implementation rootProject.ext.dependencies[\"room_runtime\"]\n\n    implementation rootProject.ext.dependencies[\"jsoup\"]\n    //okhttp wrap\n    implementation project(':dokit-okhttp-api')\n    implementation project(':dokit-util')\n\n\n//    implementation rootProject.ext.dependencies[\"mimecraft\"]\n    //会合滴滴内部的spi-plugin冲突\n//    implementation rootProject.ext.dependencies[\"ktor-serialization\"]\n//    implementation rootProject.ext.dependencies[\"ktor-gson\"]\n\n    kapt rootProject.ext.dependencies[\"room_compile\"]\n    //auto-service\n    implementation rootProject.ext.dependencies[\"auto_service\"]\n    kapt rootProject.ext.dependencies[\"auto_service\"]\n\n\n    //三大图片框架\n    if (needKotlinV14()) {\n        compileOnly rootProject.ext.dependencies[\"coil_v13\"]\n    } else {\n        compileOnly rootProject.ext.dependencies[\"coil_v11\"]\n    }\n    compileOnly rootProject.ext.dependencies[\"glide\"]\n    compileOnly rootProject.ext.dependencies[\"picasso\"]\n    compileOnly rootProject.ext.dependencies[\"fresco\"]\n    compileOnly rootProject.ext.dependencies[\"image-loader\"]\n    //腾讯x5\n    compileOnly rootProject.ext.dependencies[\"tbs\"]\n\n    //高德地图\n//    implementation rootProject.ext.dependencies[\"amap_map3d\"]\n    //高德地图定位\n    compileOnly rootProject.ext.dependencies[\"amap_location\"]\n    //高德导航\n    compileOnly rootProject.ext.dependencies[\"amap_navi\"]\n    //腾讯地图定位\n    compileOnly rootProject.ext.dependencies[\"tencent_location\"]\n//    compileOnly rootProject.ext.dependencies[\"tencent_map\"]\n    //百度地图定位\n//    compileOnly files('libs/BaiduLBS_Android.jar')\n\n}\n\n\nconfigurations.all {\n    //循环每个依赖库\n    resolutionStrategy.eachDependency { DependencyResolveDetails details ->\n        //获取当前循环到的依赖库\n//        def requested = details.requested\n//        //如果这个依赖库群组的名字是com.android.support\n//        if (requested.group == 'com.squareup.okhttp3') {\n//            //且其名字不是以multidex开头的\n//            if (requested.name == \"okhttp\") {\n//                //这里指定需要统一的依赖版本 比如我的需要配置成27.1.1\n//                //details.useVersion rootProject.ext.android[\"okhttp_v4\"]\n//            }\n//        }\n//        else if (requested.group == 'androidx.core') {\n//            if (requested.name == \"core\") {\n//                //这里指定需要统一的依赖版本 比如我的需要配置成27.1.1\n//                details.useVersion '1.2.0'\n//            }\n//        } else if (requested.group == 'androidx.versionedparcelable') {\n//            if (requested.name == \"versionedparcelable\") {\n//                //这里指定需要统一的依赖版本 比如我的需要配置成27.1.1\n//                details.useVersion '1.1.0'\n//            }\n//        }\n    }\n}\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Android/dokit/gradle.properties",
    "content": "ARTIFACT_ID=dokitx"
  },
  {
    "path": "Android/dokit/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original from file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "Android/dokit/src/androidTest/java/com/didichuxing/doraemonkit/ExampleInstrumentedTest.java",
    "content": "//package com.didichuxing.doraemonkit;\n//\n//import android.content.Context;\n//\n//\n///**\n// * Instrumented test, which will execute on an Android device.\n// *\n// * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n// */\n//@RunWith(AndroidJUnit4.class)\n//public class ExampleInstrumentedTest {\n//    @Test\n//    public void useAppContext() throws Exception {\n//        // Context of the app under test.\n//        Context appContext = InstrumentationRegistry.getTargetContext();\n//\n//        assertEquals(\"com.didichuxing.doraemonkit.test\", appContext.getPackageName());\n//    }\n//}\n"
  },
  {
    "path": "Android/dokit/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.didichuxing.doraemonkit\">\n\n    <!-- Window -->\n    <uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\" />\n\n    <!-- Network -->\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n\n    <!-- IO -->\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n\n    <!-- Setting -->\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />\n\n    <!-- Zxing-->\n    <uses-permission android:name=\"android.permission.VIBRATE\" />\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n\n    <uses-feature android:name=\"android.hardware.camera.autofocus\" />\n    <!--leakCanary-->\n    <!-- To allow starting foreground services on Android P+ - https://developer.android.com/preview/behavior-changes#fg-svc -->\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />\n    <!--map -->\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n\n\n    <!--    networkSecurityConfig 兼容android P 的网络请求\n    android:networkSecurityConfig=\"@xml/dokit_network_config\"\n    -->\n    <!--    android:requestLegacyExternalStorage=\"true\" 解决sd卡没有权限的问题-->\n    <application>\n        <activity\n            android:name=\".kit.core.UniversalActivity\"\n            android:screenOrientation=\"portrait\"\n            android:theme=\"@style/Theme.AppCompat.Light.NoActionBar\"\n            android:windowSoftInputMode=\"adjustPan|stateHidden|stateUnchanged\" />\n        <activity\n            android:name=\".kit.core.TranslucentActivity\"\n            android:theme=\"@style/DK.Theme.Translucent\"\n            android:windowSoftInputMode=\"stateHidden|stateUnchanged\" />\n        <activity\n            android:name=\".zxing.activity.CaptureActivity\"\n            android:screenOrientation=\"portrait\"\n            android:windowSoftInputMode=\"stateHidden|stateUnchanged\" />\n        <activity\n            android:name=\".kit.connect.DoKitScanActivity\"\n            android:screenOrientation=\"portrait\"\n            android:windowSoftInputMode=\"stateHidden|stateUnchanged\" />\n\n        <provider\n            android:name=\".DebugFileProvider\"\n            android:authorities=\"${applicationId}.debugfileprovider\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/dokit_debug_provider_paths\" />\n        </provider>\n\n        <!-- Dokit targetApi为 29 Android Q时截屏的前台服务 -->\n        <service\n            android:name=\"com.didichuxing.doraemonkit.kit.colorpick.ScreenRecorderService\"\n            android:enabled=\"true\"\n            android:foregroundServiceType=\"mediaProjection\" />\n\n        <!--<service-->\n        <!--android:name=\".kit.viewcheck.DebugAccessibilityService\"-->\n        <!--android:permission=\"android.permission.BIND_ACCESSIBILITY_SERVICE\">-->\n        <!--<intent-filter>-->\n        <!--<action android:name=\"android.accessibilityservice.AccessibilityService\" />-->\n        <!--</intent-filter>-->\n        <!--<meta-data-->\n        <!--android:name=\"android.accessibilityservice\"-->\n        <!--android:resource=\"@xml/debug_accessibility_service_config\" />-->\n        <!--</service>-->\n\n        <!--        截图服务 适配Android Q-->\n\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "Android/dokit/src/main/assets/dokit_system_kits.json",
    "content": "[\n  {\n    \"groupId\": \"dk_category_platform\",\n      \"kits\": [\n          {\n              \"allClassName\": \"com.didichuxing.doraemonkit.kit.network.MockKit\",\n              \"checked\": true,\n              \"innerKitId\": \"dokit_sdk_platform_ck_mock\"\n          },\n          {\n              \"allClassName\": \"com.didichuxing.doraemonkit.kit.health.HealthKit\",\n              \"checked\": true,\n              \"innerKitId\": \"dokit_sdk_platform_ck_health\"\n          },\n          {\n              \"allClassName\": \"com.didichuxing.doraemonkit.kit.filemanager.FileTransferKit\",\n              \"checked\": true,\n              \"innerKitId\": \"dokit_sdk_platform_ck_filetransfer\"\n          },\n          {\n              \"allClassName\": \"com.didichuxing.doraemonkit.kit.mc.MultiControlKitTest\",\n              \"checked\": true,\n              \"innerKitId\": \"dokit_sdk_platform_ck_mc_test\"\n          },\n          {\n              \"allClassName\": \"com.didichuxing.doraemonkit.kit.connect.DoKitStudioConnectKit\",\n              \"checked\": true,\n              \"innerKitId\": \"dokit_sdk_platform_ck_dokit_connect\"\n          },\n          {\n              \"allClassName\": \"com.didichuxing.doraemonkit.kit.dokitforweb.AutoService\",\n              \"checked\": true,\n              \"innerKitId\": \"dokit_sdk_platform_ck_dokit_for_web\"\n          },\n          {\n              \"allClassName\": \"com.didichuxing.doraemonkit.kit.mc.MultiControlKit\",\n              \"checked\": true,\n              \"innerKitId\": \"dokit_sdk_platform_ck_mc\"\n          },\n          {\n              \"allClassName\": \"com.didichuxing.doraemonkit.kit.autotest.AutoTestControlKit\",\n              \"checked\": true,\n              \"innerKitId\": \"dokit_sdk_platform_ck_autotest\"\n          },\n          {\n              \"allClassName\": \"com.kronos.dokit.pthread.PThreadKit\",\n              \"checked\": true,\n              \"innerKitId\": \"dokit_sdk_platform_thread_hook\"\n          }\n      ]\n  },\n  {\n    \"groupId\": \"dk_category_comms\",\n    \"kits\": [\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.sysinfo.SysInfoKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_comm_ck_appinfo\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.sysinfo.ThirdLibInfoKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_comm_ck_thirdlibinfo\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.sysinfo.DevelopmentPageKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_comm_ck_devpage\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.sysinfo.LocalLangKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_comm_ck_local_lang\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.fileexplorer.FileExplorerKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_comm_ck_sandbox\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.webdoor.WebDoorKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_comm_ck_h5\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.dataclean.DataCleanKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_comm_ck_cache\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.loginfo.LogInfoKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_comm_ck_log\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.h5_help.H5Kit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_comm_ck_h5kit\"\n      }\n    ]\n  },\n  {\n    \"groupId\": \"dk_category_lbs\",\n    \"kits\": [\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.gpsmock.GpsMockKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_comm_ck_gps\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.lbs.manual.PosAdjustKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_lbs_ck_pos_adjust\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.lbs.route.RealNavMockKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_lbs_ck_nav\"\n      }\n    ]\n  },\n  {\n    \"groupId\": \"dk_category_weex\",\n    \"kits\": [\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.weex.log.WeexLogKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_weex_ck_log\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.weex.storage.WeexStorageKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_weex_ck_storage\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.weex.info.WeexInfoKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_weex_ck_info\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.weex.devtool.WeexDevToolKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_weex_ck_devtool\"\n      }\n    ]\n  },\n  {\n    \"groupId\": \"dk_category_performance\",\n    \"kits\": [\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.parameter.frameInfo.FrameInfoKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_performance_ck_fps\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.parameter.cpu.CpuKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_performance_ck_cpu\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.parameter.ram.RamKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_performance_ck_arm\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.network.NetworkKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_performance_ck_network\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.crash.CrashCaptureKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_comm_ck_crash\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.blockmonitor.BlockMonitorKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_performance_ck_block\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.largepicture.LargePictureKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_performance_ck_img\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.weaknetwork.WeakNetworkKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_comm_ck_weaknetwork\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.timecounter.TimeCounterKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_performance_ck_open_coast\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.uiperformance.UIPerformanceKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_performance_ck_hierarchy\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.methodtrace.MethodCostKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_performance_ck_method_coast\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.leakcanary.LeakCanaryKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_performance_ck_leak\"\n      }\n    ]\n  },\n  {\n    \"groupId\": \"dk_category_ui\",\n    \"kits\": [\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.colorpick.ColorPickerKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_ui_ck_color_pick\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.alignruler.AlignRulerKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_ui_ck_aligin_scaleplate\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.viewcheck.ViewCheckerKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_ui_ck_widget\"\n      },\n      {\n        \"allClassName\": \"com.didichuxing.doraemonkit.kit.layoutborder.LayoutBorderKit\",\n        \"checked\": true,\n        \"innerKitId\": \"dokit_sdk_ui_ck_border\"\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "Android/dokit/src/main/assets/h5help/dokit.js",
    "content": "(function(){\"use strict\";function makeMap(e,t){const o=Object.create(null),n=e.split(\",\");for(let e=0;e<n.length;e++)o[n[e]]=!0;return t?e=>!!o[e.toLowerCase()]:e=>!!o[e]}const specialBooleanAttrs=\"itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly\",isSpecialBooleanAttr=makeMap(specialBooleanAttrs);function includeBooleanAttr(e){return!!e||\"\"===e}function normalizeStyle(e){if(isArray(e)){const t={};for(let o=0;o<e.length;o++){const n=e[o],i=isString(n)?parseStringStyle(n):normalizeStyle(n);if(i)for(const e in i)t[e]=i[e]}return t}return isString(e)||isObject$1(e)?e:void 0}const listDelimiterRE=/;(?![^(]*\\))/g,propertyDelimiterRE=/:(.+)/;function parseStringStyle(e){const t={};return e.split(listDelimiterRE).forEach((e=>{if(e){const o=e.split(propertyDelimiterRE);o.length>1&&(t[o[0].trim()]=o[1].trim())}})),t}function normalizeClass(e){let t=\"\";if(isString(e))t=e;else if(isArray(e))for(let o=0;o<e.length;o++){const n=normalizeClass(e[o]);n&&(t+=n+\" \")}else if(isObject$1(e))for(const o in e)e[o]&&(t+=o+\" \");return t.trim()}function looseCompareArrays(e,t){if(e.length!==t.length)return!1;let o=!0;for(let n=0;o&&n<e.length;n++)o=looseEqual(e[n],t[n]);return o}function looseEqual(e,t){if(e===t)return!0;let o=isDate(e),n=isDate(t);if(o||n)return!(!o||!n)&&e.getTime()===t.getTime();if(o=isArray(e),n=isArray(t),o||n)return!(!o||!n)&&looseCompareArrays(e,t);if(o=isObject$1(e),n=isObject$1(t),o||n){if(!o||!n)return!1;if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const o in e){const n=e.hasOwnProperty(o),i=t.hasOwnProperty(o);if(n&&!i||!n&&i||!looseEqual(e[o],t[o]))return!1}}return String(e)===String(t)}function looseIndexOf(e,t){return e.findIndex((e=>looseEqual(e,t)))}const toDisplayString=e=>isString(e)?e:null==e?\"\":isArray(e)||isObject$1(e)&&(e.toString===objectToString||!isFunction(e.toString))?JSON.stringify(e,replacer,2):String(e),replacer=(e,t)=>t&&t.__v_isRef?replacer(e,t.value):isMap(t)?{[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,o])=>(e[`${t} =>`]=o,e)),{})}:isSet(t)?{[`Set(${t.size})`]:[...t.values()]}:!isObject$1(t)||isArray(t)||isPlainObject(t)?t:String(t),EMPTY_OBJ={},EMPTY_ARR=[],NOOP=()=>{},NO=()=>!1,onRE=/^on[^a-z]/,isOn=e=>onRE.test(e),isModelListener=e=>e.startsWith(\"onUpdate:\"),extend=Object.assign,remove=(e,t)=>{const o=e.indexOf(t);o>-1&&e.splice(o,1)},hasOwnProperty=Object.prototype.hasOwnProperty,hasOwn=(e,t)=>hasOwnProperty.call(e,t),isArray=Array.isArray,isMap=e=>\"[object Map]\"===toTypeString(e),isSet=e=>\"[object Set]\"===toTypeString(e),isDate=e=>e instanceof Date,isFunction=e=>\"function\"==typeof e,isString=e=>\"string\"==typeof e,isSymbol=e=>\"symbol\"==typeof e,isObject$1=e=>null!==e&&\"object\"==typeof e,isPromise=e=>isObject$1(e)&&isFunction(e.then)&&isFunction(e.catch),objectToString=Object.prototype.toString,toTypeString=e=>objectToString.call(e),toRawType=e=>toTypeString(e).slice(8,-1),isPlainObject=e=>\"[object Object]\"===toTypeString(e),isIntegerKey=e=>isString(e)&&\"NaN\"!==e&&\"-\"!==e[0]&&\"\"+parseInt(e,10)===e,isReservedProp=makeMap(\",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted\"),cacheStringFunction=e=>{const t=Object.create(null);return o=>t[o]||(t[o]=e(o))},camelizeRE=/-(\\w)/g,camelize=cacheStringFunction((e=>e.replace(camelizeRE,((e,t)=>t?t.toUpperCase():\"\")))),hyphenateRE=/\\B([A-Z])/g,hyphenate=cacheStringFunction((e=>e.replace(hyphenateRE,\"-$1\").toLowerCase())),capitalize=cacheStringFunction((e=>e.charAt(0).toUpperCase()+e.slice(1))),toHandlerKey=cacheStringFunction((e=>e?`on${capitalize(e)}`:\"\")),hasChanged=(e,t)=>!Object.is(e,t),invokeArrayFns=(e,t)=>{for(let o=0;o<e.length;o++)e[o](t)},def=(e,t,o)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:o})},toNumber=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let _globalThis;const getGlobalThis=()=>_globalThis||(_globalThis=\"undefined\"!=typeof globalThis?globalThis:\"undefined\"!=typeof self?self:\"undefined\"!=typeof window?window:\"undefined\"!=typeof global?global:{});let activeEffectScope;class EffectScope{constructor(e=!1){this.active=!0,this.effects=[],this.cleanups=[],!e&&activeEffectScope&&(this.parent=activeEffectScope,this.index=(activeEffectScope.scopes||(activeEffectScope.scopes=[])).push(this)-1)}run(e){if(this.active)try{return activeEffectScope=this,e()}finally{activeEffectScope=this.parent}}on(){activeEffectScope=this}off(){activeEffectScope=this.parent}stop(e){if(this.active){let t,o;for(t=0,o=this.effects.length;t<o;t++)this.effects[t].stop();for(t=0,o=this.cleanups.length;t<o;t++)this.cleanups[t]();if(this.scopes)for(t=0,o=this.scopes.length;t<o;t++)this.scopes[t].stop(!0);if(this.parent&&!e){const e=this.parent.scopes.pop();e&&e!==this&&(this.parent.scopes[this.index]=e,e.index=this.index)}this.active=!1}}}function recordEffectScope(e,t=activeEffectScope){t&&t.active&&t.effects.push(e)}const createDep=e=>{const t=new Set(e);return t.w=0,t.n=0,t},wasTracked=e=>(e.w&trackOpBit)>0,newTracked=e=>(e.n&trackOpBit)>0,initDepMarkers=({deps:e})=>{if(e.length)for(let t=0;t<e.length;t++)e[t].w|=trackOpBit},finalizeDepMarkers=e=>{const{deps:t}=e;if(t.length){let o=0;for(let n=0;n<t.length;n++){const i=t[n];wasTracked(i)&&!newTracked(i)?i.delete(e):t[o++]=i,i.w&=~trackOpBit,i.n&=~trackOpBit}t.length=o}},targetMap=new WeakMap;let effectTrackDepth=0,trackOpBit=1;const maxMarkerBits=30;let activeEffect;const ITERATE_KEY=Symbol(\"\"),MAP_KEY_ITERATE_KEY=Symbol(\"\");class ReactiveEffect{constructor(e,t=null,o){this.fn=e,this.scheduler=t,this.active=!0,this.deps=[],this.parent=void 0,recordEffectScope(this,o)}run(){if(!this.active)return this.fn();let e=activeEffect,t=shouldTrack;for(;e;){if(e===this)return;e=e.parent}try{return this.parent=activeEffect,activeEffect=this,shouldTrack=!0,trackOpBit=1<<++effectTrackDepth,effectTrackDepth<=maxMarkerBits?initDepMarkers(this):cleanupEffect(this),this.fn()}finally{effectTrackDepth<=maxMarkerBits&&finalizeDepMarkers(this),trackOpBit=1<<--effectTrackDepth,activeEffect=this.parent,shouldTrack=t,this.parent=void 0}}stop(){this.active&&(cleanupEffect(this),this.onStop&&this.onStop(),this.active=!1)}}function cleanupEffect(e){const{deps:t}=e;if(t.length){for(let o=0;o<t.length;o++)t[o].delete(e);t.length=0}}let shouldTrack=!0;const trackStack=[];function pauseTracking(){trackStack.push(shouldTrack),shouldTrack=!1}function resetTracking(){const e=trackStack.pop();shouldTrack=void 0===e||e}function track(e,t,o){if(shouldTrack&&activeEffect){let t=targetMap.get(e);t||targetMap.set(e,t=new Map);let n=t.get(o);n||t.set(o,n=createDep()),trackEffects(n)}}function trackEffects(e,t){let o=!1;effectTrackDepth<=maxMarkerBits?newTracked(e)||(e.n|=trackOpBit,o=!wasTracked(e)):o=!e.has(activeEffect),o&&(e.add(activeEffect),activeEffect.deps.push(e))}function trigger$1(e,t,o,n,i,r){const s=targetMap.get(e);if(!s)return;let a=[];if(\"clear\"===t)a=[...s.values()];else if(\"length\"===o&&isArray(e))s.forEach(((e,t)=>{(\"length\"===t||t>=n)&&a.push(e)}));else switch(void 0!==o&&a.push(s.get(o)),t){case\"add\":isArray(e)?isIntegerKey(o)&&a.push(s.get(\"length\")):(a.push(s.get(ITERATE_KEY)),isMap(e)&&a.push(s.get(MAP_KEY_ITERATE_KEY)));break;case\"delete\":isArray(e)||(a.push(s.get(ITERATE_KEY)),isMap(e)&&a.push(s.get(MAP_KEY_ITERATE_KEY)));break;case\"set\":isMap(e)&&a.push(s.get(ITERATE_KEY))}if(1===a.length)a[0]&&triggerEffects(a[0]);else{const e=[];for(const t of a)t&&e.push(...t);triggerEffects(createDep(e))}}function triggerEffects(e,t){for(const t of isArray(e)?e:[...e])(t!==activeEffect||t.allowRecurse)&&(t.scheduler?t.scheduler():t.run())}const isNonTrackableKeys=makeMap(\"__proto__,__v_isRef,__isVue\"),builtInSymbols=new Set(Object.getOwnPropertyNames(Symbol).map((e=>Symbol[e])).filter(isSymbol)),get=createGetter(),shallowGet=createGetter(!1,!0),readonlyGet=createGetter(!0),arrayInstrumentations=createArrayInstrumentations();function createArrayInstrumentations(){const e={};return[\"includes\",\"indexOf\",\"lastIndexOf\"].forEach((t=>{e[t]=function(...e){const o=toRaw(this);for(let e=0,t=this.length;e<t;e++)track(o,\"get\",e+\"\");const n=o[t](...e);return-1===n||!1===n?o[t](...e.map(toRaw)):n}})),[\"push\",\"pop\",\"shift\",\"unshift\",\"splice\"].forEach((t=>{e[t]=function(...e){pauseTracking();const o=toRaw(this)[t].apply(this,e);return resetTracking(),o}})),e}function createGetter(e=!1,t=!1){return function(o,n,i){if(\"__v_isReactive\"===n)return!e;if(\"__v_isReadonly\"===n)return e;if(\"__v_isShallow\"===n)return t;if(\"__v_raw\"===n&&i===(e?t?shallowReadonlyMap:readonlyMap:t?shallowReactiveMap:reactiveMap).get(o))return o;const r=isArray(o);if(!e&&r&&hasOwn(arrayInstrumentations,n))return Reflect.get(arrayInstrumentations,n,i);const s=Reflect.get(o,n,i);if(isSymbol(n)?builtInSymbols.has(n):isNonTrackableKeys(n))return s;if(e||track(o,\"get\",n),t)return s;if(isRef(s)){return!r||!isIntegerKey(n)?s.value:s}return isObject$1(s)?e?readonly(s):reactive(s):s}}const set=createSetter(),shallowSet=createSetter(!0);function createSetter(e=!1){return function(t,o,n,i){let r=t[o];if(isReadonly(r)&&isRef(r)&&!isRef(n))return!1;if(!e&&!isReadonly(n)&&(isShallow(n)||(n=toRaw(n),r=toRaw(r)),!isArray(t)&&isRef(r)&&!isRef(n)))return r.value=n,!0;const s=isArray(t)&&isIntegerKey(o)?Number(o)<t.length:hasOwn(t,o),a=Reflect.set(t,o,n,i);return t===toRaw(i)&&(s?hasChanged(n,r)&&trigger$1(t,\"set\",o,n):trigger$1(t,\"add\",o,n)),a}}function deleteProperty(e,t){const o=hasOwn(e,t);e[t];const n=Reflect.deleteProperty(e,t);return n&&o&&trigger$1(e,\"delete\",t,void 0),n}function has(e,t){const o=Reflect.has(e,t);return isSymbol(t)&&builtInSymbols.has(t)||track(e,\"has\",t),o}function ownKeys(e){return track(e,\"iterate\",isArray(e)?\"length\":ITERATE_KEY),Reflect.ownKeys(e)}const mutableHandlers={get:get,set:set,deleteProperty:deleteProperty,has:has,ownKeys:ownKeys},readonlyHandlers={get:readonlyGet,set:(e,t)=>!0,deleteProperty:(e,t)=>!0},shallowReactiveHandlers=extend({},mutableHandlers,{get:shallowGet,set:shallowSet}),toShallow=e=>e,getProto=e=>Reflect.getPrototypeOf(e);function get$1(e,t,o=!1,n=!1){const i=toRaw(e=e.__v_raw),r=toRaw(t);t!==r&&!o&&track(i,\"get\",t),!o&&track(i,\"get\",r);const{has:s}=getProto(i),a=n?toShallow:o?toReadonly:toReactive;return s.call(i,t)?a(e.get(t)):s.call(i,r)?a(e.get(r)):void(e!==i&&e.get(t))}function has$1(e,t=!1){const o=this.__v_raw,n=toRaw(o),i=toRaw(e);return e!==i&&!t&&track(n,\"has\",e),!t&&track(n,\"has\",i),e===i?o.has(e):o.has(e)||o.has(i)}function size(e,t=!1){return e=e.__v_raw,!t&&track(toRaw(e),\"iterate\",ITERATE_KEY),Reflect.get(e,\"size\",e)}function add(e){e=toRaw(e);const t=toRaw(this);return getProto(t).has.call(t,e)||(t.add(e),trigger$1(t,\"add\",e,e)),this}function set$1(e,t){t=toRaw(t);const o=toRaw(this),{has:n,get:i}=getProto(o);let r=n.call(o,e);r||(e=toRaw(e),r=n.call(o,e));const s=i.call(o,e);return o.set(e,t),r?hasChanged(t,s)&&trigger$1(o,\"set\",e,t):trigger$1(o,\"add\",e,t),this}function deleteEntry(e){const t=toRaw(this),{has:o,get:n}=getProto(t);let i=o.call(t,e);i||(e=toRaw(e),i=o.call(t,e)),n&&n.call(t,e);const r=t.delete(e);return i&&trigger$1(t,\"delete\",e,void 0),r}function clear(){const e=toRaw(this),t=0!==e.size,o=e.clear();return t&&trigger$1(e,\"clear\",void 0,void 0),o}function createForEach(e,t){return function(o,n){const i=this,r=i.__v_raw,s=toRaw(r),a=t?toShallow:e?toReadonly:toReactive;return!e&&track(s,\"iterate\",ITERATE_KEY),r.forEach(((e,t)=>o.call(n,a(e),a(t),i)))}}function createIterableMethod(e,t,o){return function(...n){const i=this.__v_raw,r=toRaw(i),s=isMap(r),a=\"entries\"===e||e===Symbol.iterator&&s,l=\"keys\"===e&&s,c=i[e](...n),d=o?toShallow:t?toReadonly:toReactive;return!t&&track(r,\"iterate\",l?MAP_KEY_ITERATE_KEY:ITERATE_KEY),{next(){const{value:e,done:t}=c.next();return t?{value:e,done:t}:{value:a?[d(e[0]),d(e[1])]:d(e),done:t}},[Symbol.iterator](){return this}}}}function createReadonlyMethod(e){return function(...t){return\"delete\"!==e&&this}}function createInstrumentations(){const e={get(e){return get$1(this,e)},get size(){return size(this)},has:has$1,add:add,set:set$1,delete:deleteEntry,clear:clear,forEach:createForEach(!1,!1)},t={get(e){return get$1(this,e,!1,!0)},get size(){return size(this)},has:has$1,add:add,set:set$1,delete:deleteEntry,clear:clear,forEach:createForEach(!1,!0)},o={get(e){return get$1(this,e,!0)},get size(){return size(this,!0)},has(e){return has$1.call(this,e,!0)},add:createReadonlyMethod(\"add\"),set:createReadonlyMethod(\"set\"),delete:createReadonlyMethod(\"delete\"),clear:createReadonlyMethod(\"clear\"),forEach:createForEach(!0,!1)},n={get(e){return get$1(this,e,!0,!0)},get size(){return size(this,!0)},has(e){return has$1.call(this,e,!0)},add:createReadonlyMethod(\"add\"),set:createReadonlyMethod(\"set\"),delete:createReadonlyMethod(\"delete\"),clear:createReadonlyMethod(\"clear\"),forEach:createForEach(!0,!0)};return[\"keys\",\"values\",\"entries\",Symbol.iterator].forEach((i=>{e[i]=createIterableMethod(i,!1,!1),o[i]=createIterableMethod(i,!0,!1),t[i]=createIterableMethod(i,!1,!0),n[i]=createIterableMethod(i,!0,!0)})),[e,o,t,n]}const[mutableInstrumentations,readonlyInstrumentations,shallowInstrumentations,shallowReadonlyInstrumentations]=createInstrumentations();function createInstrumentationGetter(e,t){const o=t?e?shallowReadonlyInstrumentations:shallowInstrumentations:e?readonlyInstrumentations:mutableInstrumentations;return(t,n,i)=>\"__v_isReactive\"===n?!e:\"__v_isReadonly\"===n?e:\"__v_raw\"===n?t:Reflect.get(hasOwn(o,n)&&n in t?o:t,n,i)}const mutableCollectionHandlers={get:createInstrumentationGetter(!1,!1)},shallowCollectionHandlers={get:createInstrumentationGetter(!1,!0)},readonlyCollectionHandlers={get:createInstrumentationGetter(!0,!1)},reactiveMap=new WeakMap,shallowReactiveMap=new WeakMap,readonlyMap=new WeakMap,shallowReadonlyMap=new WeakMap;function targetTypeMap(e){switch(e){case\"Object\":case\"Array\":return 1;case\"Map\":case\"Set\":case\"WeakMap\":case\"WeakSet\":return 2;default:return 0}}function getTargetType(e){return e.__v_skip||!Object.isExtensible(e)?0:targetTypeMap(toRawType(e))}function reactive(e){return isReadonly(e)?e:createReactiveObject(e,!1,mutableHandlers,mutableCollectionHandlers,reactiveMap)}function shallowReactive(e){return createReactiveObject(e,!1,shallowReactiveHandlers,shallowCollectionHandlers,shallowReactiveMap)}function readonly(e){return createReactiveObject(e,!0,readonlyHandlers,readonlyCollectionHandlers,readonlyMap)}function createReactiveObject(e,t,o,n,i){if(!isObject$1(e))return e;if(e.__v_raw&&(!t||!e.__v_isReactive))return e;const r=i.get(e);if(r)return r;const s=getTargetType(e);if(0===s)return e;const a=new Proxy(e,2===s?n:o);return i.set(e,a),a}function isReactive(e){return isReadonly(e)?isReactive(e.__v_raw):!(!e||!e.__v_isReactive)}function isReadonly(e){return!(!e||!e.__v_isReadonly)}function isShallow(e){return!(!e||!e.__v_isShallow)}function isProxy(e){return isReactive(e)||isReadonly(e)}function toRaw(e){const t=e&&e.__v_raw;return t?toRaw(t):e}function markRaw(e){return def(e,\"__v_skip\",!0),e}const toReactive=e=>isObject$1(e)?reactive(e):e,toReadonly=e=>isObject$1(e)?readonly(e):e;function trackRefValue(e){shouldTrack&&activeEffect&&trackEffects((e=toRaw(e)).dep||(e.dep=createDep()))}function triggerRefValue(e,t){(e=toRaw(e)).dep&&triggerEffects(e.dep)}function isRef(e){return!(!e||!0!==e.__v_isRef)}function shallowRef(e){return createRef(e,!0)}function createRef(e,t){return isRef(e)?e:new RefImpl(e,t)}class RefImpl{constructor(e,t){this.__v_isShallow=t,this.dep=void 0,this.__v_isRef=!0,this._rawValue=t?e:toRaw(e),this._value=t?e:toReactive(e)}get value(){return trackRefValue(this),this._value}set value(e){e=this.__v_isShallow?e:toRaw(e),hasChanged(e,this._rawValue)&&(this._rawValue=e,this._value=this.__v_isShallow?e:toReactive(e),triggerRefValue(this))}}function unref(e){return isRef(e)?e.value:e}const shallowUnwrapHandlers={get:(e,t,o)=>unref(Reflect.get(e,t,o)),set:(e,t,o,n)=>{const i=e[t];return isRef(i)&&!isRef(o)?(i.value=o,!0):Reflect.set(e,t,o,n)}};function proxyRefs(e){return isReactive(e)?e:new Proxy(e,shallowUnwrapHandlers)}class ComputedRefImpl{constructor(e,t,o,n){this._setter=t,this.dep=void 0,this.__v_isRef=!0,this._dirty=!0,this.effect=new ReactiveEffect(e,(()=>{this._dirty||(this._dirty=!0,triggerRefValue(this))})),this.effect.computed=this,this.effect.active=this._cacheable=!n,this.__v_isReadonly=o}get value(){const e=toRaw(this);return trackRefValue(e),!e._dirty&&e._cacheable||(e._dirty=!1,e._value=e.effect.run()),e._value}set value(e){this._setter(e)}}function computed$1(e,t,o=!1){let n,i;const r=isFunction(e);r?(n=e,i=NOOP):(n=e.get,i=e.set);return new ComputedRefImpl(n,i,r||!i,o)}function callWithErrorHandling(e,t,o,n){let i;try{i=n?e(...n):e()}catch(e){handleError(e,t,o)}return i}function callWithAsyncErrorHandling(e,t,o,n){if(isFunction(e)){const i=callWithErrorHandling(e,t,o,n);return i&&isPromise(i)&&i.catch((e=>{handleError(e,t,o)})),i}const i=[];for(let r=0;r<e.length;r++)i.push(callWithAsyncErrorHandling(e[r],t,o,n));return i}function handleError(e,t,o,n=!0){const i=t?t.vnode:null;if(t){let n=t.parent;const i=t.proxy,r=o;for(;n;){const t=n.ec;if(t)for(let o=0;o<t.length;o++)if(!1===t[o](e,i,r))return;n=n.parent}const s=t.appContext.config.errorHandler;if(s)return void callWithErrorHandling(s,null,10,[e,i,r])}logError(e,o,i,n)}function logError(e,t,o,n=!0){console.error(e)}Promise.resolve();let isFlushing=!1,isFlushPending=!1;const queue=[];let flushIndex=0;const pendingPreFlushCbs=[];let activePreFlushCbs=null,preFlushIndex=0;const pendingPostFlushCbs=[];let activePostFlushCbs=null,postFlushIndex=0;const resolvedPromise=Promise.resolve();let currentFlushPromise=null,currentPreFlushParentJob=null;function nextTick(e){const t=currentFlushPromise||resolvedPromise;return e?t.then(this?e.bind(this):e):t}function findInsertionIndex(e){let t=flushIndex+1,o=queue.length;for(;t<o;){const n=t+o>>>1;getId(queue[n])<e?t=n+1:o=n}return t}function queueJob(e){queue.length&&queue.includes(e,isFlushing&&e.allowRecurse?flushIndex+1:flushIndex)||e===currentPreFlushParentJob||(null==e.id?queue.push(e):queue.splice(findInsertionIndex(e.id),0,e),queueFlush())}function queueFlush(){isFlushing||isFlushPending||(isFlushPending=!0,currentFlushPromise=resolvedPromise.then(flushJobs))}function invalidateJob(e){const t=queue.indexOf(e);t>flushIndex&&queue.splice(t,1)}function queueCb(e,t,o,n){isArray(e)?o.push(...e):t&&t.includes(e,e.allowRecurse?n+1:n)||o.push(e),queueFlush()}function queuePreFlushCb(e){queueCb(e,activePreFlushCbs,pendingPreFlushCbs,preFlushIndex)}function queuePostFlushCb(e){queueCb(e,activePostFlushCbs,pendingPostFlushCbs,postFlushIndex)}function flushPreFlushCbs(e,t=null){if(pendingPreFlushCbs.length){for(currentPreFlushParentJob=t,activePreFlushCbs=[...new Set(pendingPreFlushCbs)],pendingPreFlushCbs.length=0,preFlushIndex=0;preFlushIndex<activePreFlushCbs.length;preFlushIndex++)activePreFlushCbs[preFlushIndex]();activePreFlushCbs=null,preFlushIndex=0,currentPreFlushParentJob=null,flushPreFlushCbs(e,t)}}function flushPostFlushCbs(e){if(pendingPostFlushCbs.length){const e=[...new Set(pendingPostFlushCbs)];if(pendingPostFlushCbs.length=0,activePostFlushCbs)return void activePostFlushCbs.push(...e);for(activePostFlushCbs=e,activePostFlushCbs.sort(((e,t)=>getId(e)-getId(t))),postFlushIndex=0;postFlushIndex<activePostFlushCbs.length;postFlushIndex++)activePostFlushCbs[postFlushIndex]();activePostFlushCbs=null,postFlushIndex=0}}const getId=e=>null==e.id?1/0:e.id;function flushJobs(e){isFlushPending=!1,isFlushing=!0,flushPreFlushCbs(e),queue.sort(((e,t)=>getId(e)-getId(t)));try{for(flushIndex=0;flushIndex<queue.length;flushIndex++){const e=queue[flushIndex];e&&!1!==e.active&&callWithErrorHandling(e,null,14)}}finally{flushIndex=0,queue.length=0,flushPostFlushCbs(),isFlushing=!1,currentFlushPromise=null,(queue.length||pendingPreFlushCbs.length||pendingPostFlushCbs.length)&&flushJobs(e)}}let devtools,buffer=[],devtoolsNotInstalled=!1;function emit(e,...t){devtools?devtools.emit(e,...t):devtoolsNotInstalled||buffer.push({event:e,args:t})}function setDevtoolsHook(e,t){var o,n;if(devtools=e,devtools)devtools.enabled=!0,buffer.forEach((({event:e,args:t})=>devtools.emit(e,...t))),buffer=[];else if(\"undefined\"!=typeof window&&window.HTMLElement&&!(null===(n=null===(o=window.navigator)||void 0===o?void 0:o.userAgent)||void 0===n?void 0:n.includes(\"jsdom\"))){(t.__VUE_DEVTOOLS_HOOK_REPLAY__=t.__VUE_DEVTOOLS_HOOK_REPLAY__||[]).push((e=>{setDevtoolsHook(e,t)})),setTimeout((()=>{devtools||(t.__VUE_DEVTOOLS_HOOK_REPLAY__=null,devtoolsNotInstalled=!0,buffer=[])}),3e3)}else devtoolsNotInstalled=!0,buffer=[]}function devtoolsInitApp(e,t){emit(\"app:init\",e,t,{Fragment:Fragment,Text:Text,Comment:Comment,Static:Static})}function devtoolsUnmountApp(e){emit(\"app:unmount\",e)}const devtoolsComponentAdded=createDevtoolsComponentHook(\"component:added\"),devtoolsComponentUpdated=createDevtoolsComponentHook(\"component:updated\"),devtoolsComponentRemoved=createDevtoolsComponentHook(\"component:removed\");function createDevtoolsComponentHook(e){return t=>{emit(e,t.appContext.app,t.uid,t.parent?t.parent.uid:void 0,t)}}function devtoolsComponentEmit(e,t,o){emit(\"component:emit\",e.appContext.app,e,t,o)}function emit$1(e,t,...o){const n=e.vnode.props||EMPTY_OBJ;let i=o;const r=t.startsWith(\"update:\"),s=r&&t.slice(7);if(s&&s in n){const e=`${\"modelValue\"===s?\"model\":s}Modifiers`,{number:t,trim:r}=n[e]||EMPTY_OBJ;r?i=o.map((e=>e.trim())):t&&(i=o.map(toNumber))}let a;devtoolsComponentEmit(e,t,i);let l=n[a=toHandlerKey(t)]||n[a=toHandlerKey(camelize(t))];!l&&r&&(l=n[a=toHandlerKey(hyphenate(t))]),l&&callWithAsyncErrorHandling(l,e,6,i);const c=n[a+\"Once\"];if(c){if(e.emitted){if(e.emitted[a])return}else e.emitted={};e.emitted[a]=!0,callWithAsyncErrorHandling(c,e,6,i)}}function normalizeEmitsOptions(e,t,o=!1){const n=t.emitsCache,i=n.get(e);if(void 0!==i)return i;const r=e.emits;let s={},a=!1;if(!isFunction(e)){const n=e=>{const o=normalizeEmitsOptions(e,t,!0);o&&(a=!0,extend(s,o))};!o&&t.mixins.length&&t.mixins.forEach(n),e.extends&&n(e.extends),e.mixins&&e.mixins.forEach(n)}return r||a?(isArray(r)?r.forEach((e=>s[e]=null)):extend(s,r),n.set(e,s),s):(n.set(e,null),null)}function isEmitListener(e,t){return!(!e||!isOn(t))&&(t=t.slice(2).replace(/Once$/,\"\"),hasOwn(e,t[0].toLowerCase()+t.slice(1))||hasOwn(e,hyphenate(t))||hasOwn(e,t))}let currentRenderingInstance=null,currentScopeId=null;function setCurrentRenderingInstance(e){const t=currentRenderingInstance;return currentRenderingInstance=e,currentScopeId=e&&e.type.__scopeId||null,t}function pushScopeId(e){currentScopeId=e}function popScopeId(){currentScopeId=null}function withCtx(e,t=currentRenderingInstance,o){if(!t)return e;if(e._n)return e;const n=(...o)=>{n._d&&setBlockTracking(-1);const i=setCurrentRenderingInstance(t),r=e(...o);return setCurrentRenderingInstance(i),n._d&&setBlockTracking(1),devtoolsComponentUpdated(t),r};return n._n=!0,n._c=!0,n._d=!0,n}function markAttrsAccessed(){}function renderComponentRoot(e){const{type:t,vnode:o,proxy:n,withProxy:i,props:r,propsOptions:[s],slots:a,attrs:l,emit:c,render:d,renderCache:u,data:h,setupState:p,ctx:f,inheritAttrs:m}=e;let g,v;const k=setCurrentRenderingInstance(e);try{if(4&o.shapeFlag){const e=i||n;g=normalizeVNode(d.call(e,e,u,r,p,h,f)),v=l}else{const e=t;0,g=normalizeVNode(e.length>1?e(r,{attrs:l,slots:a,emit:c}):e(r,null)),v=t.props?l:getFunctionalFallthrough(l)}}catch(t){blockStack.length=0,handleError(t,e,1),g=createVNode(Comment)}let y=g;if(v&&!1!==m){const e=Object.keys(v),{shapeFlag:t}=y;e.length&&7&t&&(s&&e.some(isModelListener)&&(v=filterModelListeners(v,s)),y=cloneVNode(y,v))}return o.dirs&&(y.dirs=y.dirs?y.dirs.concat(o.dirs):o.dirs),o.transition&&(y.transition=o.transition),g=y,setCurrentRenderingInstance(k),g}const getFunctionalFallthrough=e=>{let t;for(const o in e)(\"class\"===o||\"style\"===o||isOn(o))&&((t||(t={}))[o]=e[o]);return t},filterModelListeners=(e,t)=>{const o={};for(const n in e)isModelListener(n)&&n.slice(9)in t||(o[n]=e[n]);return o};function shouldUpdateComponent(e,t,o){const{props:n,children:i,component:r}=e,{props:s,children:a,patchFlag:l}=t,c=r.emitsOptions;if(t.dirs||t.transition)return!0;if(!(o&&l>=0))return!(!i&&!a||a&&a.$stable)||n!==s&&(n?!s||hasPropsChanged(n,s,c):!!s);if(1024&l)return!0;if(16&l)return n?hasPropsChanged(n,s,c):!!s;if(8&l){const e=t.dynamicProps;for(let t=0;t<e.length;t++){const o=e[t];if(s[o]!==n[o]&&!isEmitListener(c,o))return!0}}return!1}function hasPropsChanged(e,t,o){const n=Object.keys(t);if(n.length!==Object.keys(e).length)return!0;for(let i=0;i<n.length;i++){const r=n[i];if(t[r]!==e[r]&&!isEmitListener(o,r))return!0}return!1}function updateHOCHostEl({vnode:e,parent:t},o){for(;t&&t.subTree===e;)(e=t.vnode).el=o,t=t.parent}const isSuspense=e=>e.__isSuspense;function queueEffectWithSuspense(e,t){t&&t.pendingBranch?isArray(e)?t.effects.push(...e):t.effects.push(e):queuePostFlushCb(e)}function provide(e,t){if(currentInstance){let o=currentInstance.provides;const n=currentInstance.parent&&currentInstance.parent.provides;n===o&&(o=currentInstance.provides=Object.create(n)),o[e]=t}else;}function inject(e,t,o=!1){const n=currentInstance||currentRenderingInstance;if(n){const i=null==n.parent?n.vnode.appContext&&n.vnode.appContext.provides:n.parent.provides;if(i&&e in i)return i[e];if(arguments.length>1)return o&&isFunction(t)?t.call(n.proxy):t}}const INITIAL_WATCHER_VALUE={};function watch(e,t,o){return doWatch(e,t,o)}function doWatch(e,t,{immediate:o,deep:n,flush:i,onTrack:r,onTrigger:s}=EMPTY_OBJ){const a=currentInstance;let l,c,d=!1,u=!1;if(isRef(e)?(l=()=>e.value,d=isShallow(e)):isReactive(e)?(l=()=>e,n=!0):isArray(e)?(u=!0,d=e.some(isReactive),l=()=>e.map((e=>isRef(e)?e.value:isReactive(e)?traverse(e):isFunction(e)?callWithErrorHandling(e,a,2):void 0))):l=isFunction(e)?t?()=>callWithErrorHandling(e,a,2):()=>{if(!a||!a.isUnmounted)return c&&c(),callWithAsyncErrorHandling(e,a,3,[h])}:NOOP,t&&n){const e=l;l=()=>traverse(e())}let h=e=>{c=g.onStop=()=>{callWithErrorHandling(e,a,4)}};if(isInSSRComponentSetup)return h=NOOP,t?o&&callWithAsyncErrorHandling(t,a,3,[l(),u?[]:void 0,h]):l(),NOOP;let p=u?[]:INITIAL_WATCHER_VALUE;const f=()=>{if(g.active)if(t){const e=g.run();(n||d||(u?e.some(((e,t)=>hasChanged(e,p[t]))):hasChanged(e,p)))&&(c&&c(),callWithAsyncErrorHandling(t,a,3,[e,p===INITIAL_WATCHER_VALUE?void 0:p,h]),p=e)}else g.run()};let m;f.allowRecurse=!!t,m=\"sync\"===i?f:\"post\"===i?()=>queuePostRenderEffect(f,a&&a.suspense):()=>{!a||a.isMounted?queuePreFlushCb(f):f()};const g=new ReactiveEffect(l,m);return t?o?f():p=g.run():\"post\"===i?queuePostRenderEffect(g.run.bind(g),a&&a.suspense):g.run(),()=>{g.stop(),a&&a.scope&&remove(a.scope.effects,g)}}function instanceWatch(e,t,o){const n=this.proxy,i=isString(e)?e.includes(\".\")?createPathGetter(n,e):()=>n[e]:e.bind(n,n);let r;isFunction(t)?r=t:(r=t.handler,o=t);const s=currentInstance;setCurrentInstance(this);const a=doWatch(i,r.bind(n),o);return s?setCurrentInstance(s):unsetCurrentInstance(),a}function createPathGetter(e,t){const o=t.split(\".\");return()=>{let t=e;for(let e=0;e<o.length&&t;e++)t=t[o[e]];return t}}function traverse(e,t){if(!isObject$1(e)||e.__v_skip)return e;if((t=t||new Set).has(e))return e;if(t.add(e),isRef(e))traverse(e.value,t);else if(isArray(e))for(let o=0;o<e.length;o++)traverse(e[o],t);else if(isSet(e)||isMap(e))e.forEach((e=>{traverse(e,t)}));else if(isPlainObject(e))for(const o in e)traverse(e[o],t);return e}function useTransitionState(){const e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leavingVNodes:new Map};return onMounted((()=>{e.isMounted=!0})),onBeforeUnmount((()=>{e.isUnmounting=!0})),e}const TransitionHookValidator=[Function,Array],BaseTransitionImpl={name:\"BaseTransition\",props:{mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:TransitionHookValidator,onEnter:TransitionHookValidator,onAfterEnter:TransitionHookValidator,onEnterCancelled:TransitionHookValidator,onBeforeLeave:TransitionHookValidator,onLeave:TransitionHookValidator,onAfterLeave:TransitionHookValidator,onLeaveCancelled:TransitionHookValidator,onBeforeAppear:TransitionHookValidator,onAppear:TransitionHookValidator,onAfterAppear:TransitionHookValidator,onAppearCancelled:TransitionHookValidator},setup(e,{slots:t}){const o=getCurrentInstance(),n=useTransitionState();let i;return()=>{const r=t.default&&getTransitionRawChildren(t.default(),!0);if(!r||!r.length)return;const s=toRaw(e),{mode:a}=s,l=r[0];if(n.isLeaving)return emptyPlaceholder(l);const c=getKeepAliveChild(l);if(!c)return emptyPlaceholder(l);const d=resolveTransitionHooks(c,s,n,o);setTransitionHooks(c,d);const u=o.subTree,h=u&&getKeepAliveChild(u);let p=!1;const{getTransitionKey:f}=c.type;if(f){const e=f();void 0===i?i=e:e!==i&&(i=e,p=!0)}if(h&&h.type!==Comment&&(!isSameVNodeType(c,h)||p)){const e=resolveTransitionHooks(h,s,n,o);if(setTransitionHooks(h,e),\"out-in\"===a)return n.isLeaving=!0,e.afterLeave=()=>{n.isLeaving=!1,o.update()},emptyPlaceholder(l);\"in-out\"===a&&c.type!==Comment&&(e.delayLeave=(e,t,o)=>{getLeavingNodesForType(n,h)[String(h.key)]=h,e._leaveCb=()=>{t(),e._leaveCb=void 0,delete d.delayedLeave},d.delayedLeave=o})}return l}}},BaseTransition=BaseTransitionImpl;function getLeavingNodesForType(e,t){const{leavingVNodes:o}=e;let n=o.get(t.type);return n||(n=Object.create(null),o.set(t.type,n)),n}function resolveTransitionHooks(e,t,o,n){const{appear:i,mode:r,persisted:s=!1,onBeforeEnter:a,onEnter:l,onAfterEnter:c,onEnterCancelled:d,onBeforeLeave:u,onLeave:h,onAfterLeave:p,onLeaveCancelled:f,onBeforeAppear:m,onAppear:g,onAfterAppear:v,onAppearCancelled:k}=t,y=String(e.key),w=getLeavingNodesForType(o,e),A=(e,t)=>{e&&callWithAsyncErrorHandling(e,n,9,t)},C={mode:r,persisted:s,beforeEnter(t){let n=a;if(!o.isMounted){if(!i)return;n=m||a}t._leaveCb&&t._leaveCb(!0);const r=w[y];r&&isSameVNodeType(e,r)&&r.el._leaveCb&&r.el._leaveCb(),A(n,[t])},enter(e){let t=l,n=c,r=d;if(!o.isMounted){if(!i)return;t=g||l,n=v||c,r=k||d}let s=!1;const a=e._enterCb=t=>{s||(s=!0,A(t?r:n,[e]),C.delayedLeave&&C.delayedLeave(),e._enterCb=void 0)};t?(t(e,a),t.length<=1&&a()):a()},leave(t,n){const i=String(e.key);if(t._enterCb&&t._enterCb(!0),o.isUnmounting)return n();A(u,[t]);let r=!1;const s=t._leaveCb=o=>{r||(r=!0,n(),A(o?f:p,[t]),t._leaveCb=void 0,w[i]===e&&delete w[i])};w[i]=e,h?(h(t,s),h.length<=1&&s()):s()},clone:e=>resolveTransitionHooks(e,t,o,n)};return C}function emptyPlaceholder(e){if(isKeepAlive(e))return(e=cloneVNode(e)).children=null,e}function getKeepAliveChild(e){return isKeepAlive(e)?e.children?e.children[0]:void 0:e}function setTransitionHooks(e,t){6&e.shapeFlag&&e.component?setTransitionHooks(e.component.subTree,t):128&e.shapeFlag?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function getTransitionRawChildren(e,t=!1){let o=[],n=0;for(let i=0;i<e.length;i++){const r=e[i];r.type===Fragment?(128&r.patchFlag&&n++,o=o.concat(getTransitionRawChildren(r.children,t))):(t||r.type!==Comment)&&o.push(r)}if(n>1)for(let e=0;e<o.length;e++)o[e].patchFlag=-2;return o}function defineComponent(e){return isFunction(e)?{setup:e,name:e.name}:e}const isAsyncWrapper=e=>!!e.type.__asyncLoader,isKeepAlive=e=>e.type.__isKeepAlive,KeepAliveImpl={name:\"KeepAlive\",__isKeepAlive:!0,props:{include:[String,RegExp,Array],exclude:[String,RegExp,Array],max:[String,Number]},setup(e,{slots:t}){const o=getCurrentInstance(),n=o.ctx;if(!n.renderer)return t.default;const i=new Map,r=new Set;let s=null;o.__v_cache=i;const a=o.suspense,{renderer:{p:l,m:c,um:d,o:{createElement:u}}}=n,h=u(\"div\");function p(e){resetShapeFlag(e),d(e,o,a,!0)}function f(e){i.forEach(((t,o)=>{const n=getComponentName(t.type);!n||e&&e(n)||m(o)}))}function m(e){const t=i.get(e);s&&t.type===s.type?s&&resetShapeFlag(s):p(t),i.delete(e),r.delete(e)}n.activate=(e,t,o,n,i)=>{const r=e.component;c(e,t,o,0,a),l(r.vnode,e,t,o,r,a,n,e.slotScopeIds,i),queuePostRenderEffect((()=>{r.isDeactivated=!1,r.a&&invokeArrayFns(r.a);const t=e.props&&e.props.onVnodeMounted;t&&invokeVNodeHook(t,r.parent,e)}),a),devtoolsComponentAdded(r)},n.deactivate=e=>{const t=e.component;c(e,h,null,1,a),queuePostRenderEffect((()=>{t.da&&invokeArrayFns(t.da);const o=e.props&&e.props.onVnodeUnmounted;o&&invokeVNodeHook(o,t.parent,e),t.isDeactivated=!0}),a),devtoolsComponentAdded(t)},watch((()=>[e.include,e.exclude]),(([e,t])=>{e&&f((t=>matches(e,t))),t&&f((e=>!matches(t,e)))}),{flush:\"post\",deep:!0});let g=null;const v=()=>{null!=g&&i.set(g,getInnerChild(o.subTree))};return onMounted(v),onUpdated(v),onBeforeUnmount((()=>{i.forEach((e=>{const{subTree:t,suspense:n}=o,i=getInnerChild(t);if(e.type!==i.type)p(e);else{resetShapeFlag(i);const e=i.component.da;e&&queuePostRenderEffect(e,n)}}))})),()=>{if(g=null,!t.default)return null;const o=t.default(),n=o[0];if(o.length>1)return s=null,o;if(!(isVNode(n)&&(4&n.shapeFlag||128&n.shapeFlag)))return s=null,n;let a=getInnerChild(n);const l=a.type,c=getComponentName(isAsyncWrapper(a)?a.type.__asyncResolved||{}:l),{include:d,exclude:u,max:h}=e;if(d&&(!c||!matches(d,c))||u&&c&&matches(u,c))return s=a,n;const p=null==a.key?l:a.key,f=i.get(p);return a.el&&(a=cloneVNode(a),128&n.shapeFlag&&(n.ssContent=a)),g=p,f?(a.el=f.el,a.component=f.component,a.transition&&setTransitionHooks(a,a.transition),a.shapeFlag|=512,r.delete(p),r.add(p)):(r.add(p),h&&r.size>parseInt(h,10)&&m(r.values().next().value)),a.shapeFlag|=256,s=a,n}}},KeepAlive=KeepAliveImpl;function matches(e,t){return isArray(e)?e.some((e=>matches(e,t))):isString(e)?e.split(\",\").includes(t):!!e.test&&e.test(t)}function onActivated(e,t){registerKeepAliveHook(e,\"a\",t)}function onDeactivated(e,t){registerKeepAliveHook(e,\"da\",t)}function registerKeepAliveHook(e,t,o=currentInstance){const n=e.__wdc||(e.__wdc=()=>{let t=o;for(;t;){if(t.isDeactivated)return;t=t.parent}return e()});if(injectHook(t,n,o),o){let e=o.parent;for(;e&&e.parent;)isKeepAlive(e.parent.vnode)&&injectToKeepAliveRoot(n,t,o,e),e=e.parent}}function injectToKeepAliveRoot(e,t,o,n){const i=injectHook(t,e,n,!0);onUnmounted((()=>{remove(n[t],i)}),o)}function resetShapeFlag(e){let t=e.shapeFlag;256&t&&(t-=256),512&t&&(t-=512),e.shapeFlag=t}function getInnerChild(e){return 128&e.shapeFlag?e.ssContent:e}function injectHook(e,t,o=currentInstance,n=!1){if(o){const i=o[e]||(o[e]=[]),r=t.__weh||(t.__weh=(...n)=>{if(o.isUnmounted)return;pauseTracking(),setCurrentInstance(o);const i=callWithAsyncErrorHandling(t,o,e,n);return unsetCurrentInstance(),resetTracking(),i});return n?i.unshift(r):i.push(r),r}}const createHook=e=>(t,o=currentInstance)=>(!isInSSRComponentSetup||\"sp\"===e)&&injectHook(e,t,o),onBeforeMount=createHook(\"bm\"),onMounted=createHook(\"m\"),onBeforeUpdate=createHook(\"bu\"),onUpdated=createHook(\"u\"),onBeforeUnmount=createHook(\"bum\"),onUnmounted=createHook(\"um\"),onServerPrefetch=createHook(\"sp\"),onRenderTriggered=createHook(\"rtg\"),onRenderTracked=createHook(\"rtc\");function onErrorCaptured(e,t=currentInstance){injectHook(\"ec\",e,t)}let shouldCacheAccess=!0;function applyOptions(e){const t=resolveMergedOptions(e),o=e.proxy,n=e.ctx;shouldCacheAccess=!1,t.beforeCreate&&callHook$1(t.beforeCreate,e,\"bc\");const{data:i,computed:r,methods:s,watch:a,provide:l,inject:c,created:d,beforeMount:u,mounted:h,beforeUpdate:p,updated:f,activated:m,deactivated:g,beforeDestroy:v,beforeUnmount:k,destroyed:y,unmounted:w,render:A,renderTracked:C,renderTriggered:b,errorCaptured:_,serverPrefetch:x,expose:S,inheritAttrs:B,components:L,directives:T,filters:E}=t;if(c&&resolveInjections(c,n,null,e.appContext.config.unwrapInjectedRef),s)for(const e in s){const t=s[e];isFunction(t)&&(n[e]=t.bind(o))}if(i){const t=i.call(o,o);isObject$1(t)&&(e.data=reactive(t))}if(shouldCacheAccess=!0,r)for(const e in r){const t=r[e],i=isFunction(t)?t.bind(o,o):isFunction(t.get)?t.get.bind(o,o):NOOP,s=!isFunction(t)&&isFunction(t.set)?t.set.bind(o):NOOP,a=computed({get:i,set:s});Object.defineProperty(n,e,{enumerable:!0,configurable:!0,get:()=>a.value,set:e=>a.value=e})}if(a)for(const e in a)createWatcher(a[e],n,o,e);if(l){const e=isFunction(l)?l.call(o):l;Reflect.ownKeys(e).forEach((t=>{provide(t,e[t])}))}function N(e,t){isArray(t)?t.forEach((t=>e(t.bind(o)))):t&&e(t.bind(o))}if(d&&callHook$1(d,e,\"c\"),N(onBeforeMount,u),N(onMounted,h),N(onBeforeUpdate,p),N(onUpdated,f),N(onActivated,m),N(onDeactivated,g),N(onErrorCaptured,_),N(onRenderTracked,C),N(onRenderTriggered,b),N(onBeforeUnmount,k),N(onUnmounted,w),N(onServerPrefetch,x),isArray(S))if(S.length){const t=e.exposed||(e.exposed={});S.forEach((e=>{Object.defineProperty(t,e,{get:()=>o[e],set:t=>o[e]=t})}))}else e.exposed||(e.exposed={});A&&e.render===NOOP&&(e.render=A),null!=B&&(e.inheritAttrs=B),L&&(e.components=L),T&&(e.directives=T)}function resolveInjections(e,t,o=NOOP,n=!1){isArray(e)&&(e=normalizeInject(e));for(const o in e){const i=e[o];let r;r=isObject$1(i)?\"default\"in i?inject(i.from||o,i.default,!0):inject(i.from||o):inject(i),isRef(r)&&n?Object.defineProperty(t,o,{enumerable:!0,configurable:!0,get:()=>r.value,set:e=>r.value=e}):t[o]=r}}function callHook$1(e,t,o){callWithAsyncErrorHandling(isArray(e)?e.map((e=>e.bind(t.proxy))):e.bind(t.proxy),t,o)}function createWatcher(e,t,o,n){const i=n.includes(\".\")?createPathGetter(o,n):()=>o[n];if(isString(e)){const o=t[e];isFunction(o)&&watch(i,o)}else if(isFunction(e))watch(i,e.bind(o));else if(isObject$1(e))if(isArray(e))e.forEach((e=>createWatcher(e,t,o,n)));else{const n=isFunction(e.handler)?e.handler.bind(o):t[e.handler];isFunction(n)&&watch(i,n,e)}}function resolveMergedOptions(e){const t=e.type,{mixins:o,extends:n}=t,{mixins:i,optionsCache:r,config:{optionMergeStrategies:s}}=e.appContext,a=r.get(t);let l;return a?l=a:i.length||o||n?(l={},i.length&&i.forEach((e=>mergeOptions(l,e,s,!0))),mergeOptions(l,t,s)):l=t,r.set(t,l),l}function mergeOptions(e,t,o,n=!1){const{mixins:i,extends:r}=t;r&&mergeOptions(e,r,o,!0),i&&i.forEach((t=>mergeOptions(e,t,o,!0)));for(const i in t)if(n&&\"expose\"===i);else{const n=internalOptionMergeStrats[i]||o&&o[i];e[i]=n?n(e[i],t[i]):t[i]}return e}const internalOptionMergeStrats={data:mergeDataFn,props:mergeObjectOptions,emits:mergeObjectOptions,methods:mergeObjectOptions,computed:mergeObjectOptions,beforeCreate:mergeAsArray,created:mergeAsArray,beforeMount:mergeAsArray,mounted:mergeAsArray,beforeUpdate:mergeAsArray,updated:mergeAsArray,beforeDestroy:mergeAsArray,beforeUnmount:mergeAsArray,destroyed:mergeAsArray,unmounted:mergeAsArray,activated:mergeAsArray,deactivated:mergeAsArray,errorCaptured:mergeAsArray,serverPrefetch:mergeAsArray,components:mergeObjectOptions,directives:mergeObjectOptions,watch:mergeWatchOptions,provide:mergeDataFn,inject:mergeInject};function mergeDataFn(e,t){return t?e?function(){return extend(isFunction(e)?e.call(this,this):e,isFunction(t)?t.call(this,this):t)}:t:e}function mergeInject(e,t){return mergeObjectOptions(normalizeInject(e),normalizeInject(t))}function normalizeInject(e){if(isArray(e)){const t={};for(let o=0;o<e.length;o++)t[e[o]]=e[o];return t}return e}function mergeAsArray(e,t){return e?[...new Set([].concat(e,t))]:t}function mergeObjectOptions(e,t){return e?extend(extend(Object.create(null),e),t):t}function mergeWatchOptions(e,t){if(!e)return t;if(!t)return e;const o=extend(Object.create(null),e);for(const n in t)o[n]=mergeAsArray(e[n],t[n]);return o}function initProps(e,t,o,n=!1){const i={},r={};def(r,InternalObjectKey,1),e.propsDefaults=Object.create(null),setFullProps(e,t,i,r);for(const t in e.propsOptions[0])t in i||(i[t]=void 0);o?e.props=n?i:shallowReactive(i):e.type.props?e.props=i:e.props=r,e.attrs=r}function updateProps(e,t,o,n){const{props:i,attrs:r,vnode:{patchFlag:s}}=e,a=toRaw(i),[l]=e.propsOptions;let c=!1;if(!(n||s>0)||16&s){let n;setFullProps(e,t,i,r)&&(c=!0);for(const r in a)t&&(hasOwn(t,r)||(n=hyphenate(r))!==r&&hasOwn(t,n))||(l?!o||void 0===o[r]&&void 0===o[n]||(i[r]=resolvePropValue(l,a,r,void 0,e,!0)):delete i[r]);if(r!==a)for(const e in r)t&&hasOwn(t,e)||(delete r[e],c=!0)}else if(8&s){const o=e.vnode.dynamicProps;for(let n=0;n<o.length;n++){let s=o[n];const d=t[s];if(l)if(hasOwn(r,s))d!==r[s]&&(r[s]=d,c=!0);else{const t=camelize(s);i[t]=resolvePropValue(l,a,t,d,e,!1)}else d!==r[s]&&(r[s]=d,c=!0)}}c&&trigger$1(e,\"set\",\"$attrs\")}function setFullProps(e,t,o,n){const[i,r]=e.propsOptions;let s,a=!1;if(t)for(let l in t){if(isReservedProp(l))continue;const c=t[l];let d;i&&hasOwn(i,d=camelize(l))?r&&r.includes(d)?(s||(s={}))[d]=c:o[d]=c:isEmitListener(e.emitsOptions,l)||l in n&&c===n[l]||(n[l]=c,a=!0)}if(r){const t=toRaw(o),n=s||EMPTY_OBJ;for(let s=0;s<r.length;s++){const a=r[s];o[a]=resolvePropValue(i,t,a,n[a],e,!hasOwn(n,a))}}return a}function resolvePropValue(e,t,o,n,i,r){const s=e[o];if(null!=s){const e=hasOwn(s,\"default\");if(e&&void 0===n){const e=s.default;if(s.type!==Function&&isFunction(e)){const{propsDefaults:r}=i;o in r?n=r[o]:(setCurrentInstance(i),n=r[o]=e.call(null,t),unsetCurrentInstance())}else n=e}s[0]&&(r&&!e?n=!1:!s[1]||\"\"!==n&&n!==hyphenate(o)||(n=!0))}return n}function normalizePropsOptions(e,t,o=!1){const n=t.propsCache,i=n.get(e);if(i)return i;const r=e.props,s={},a=[];let l=!1;if(!isFunction(e)){const n=e=>{l=!0;const[o,n]=normalizePropsOptions(e,t,!0);extend(s,o),n&&a.push(...n)};!o&&t.mixins.length&&t.mixins.forEach(n),e.extends&&n(e.extends),e.mixins&&e.mixins.forEach(n)}if(!r&&!l)return n.set(e,EMPTY_ARR),EMPTY_ARR;if(isArray(r))for(let e=0;e<r.length;e++){const t=camelize(r[e]);validatePropName(t)&&(s[t]=EMPTY_OBJ)}else if(r)for(const e in r){const t=camelize(e);if(validatePropName(t)){const o=r[e],n=s[t]=isArray(o)||isFunction(o)?{type:o}:o;if(n){const e=getTypeIndex(Boolean,n.type),o=getTypeIndex(String,n.type);n[0]=e>-1,n[1]=o<0||e<o,(e>-1||hasOwn(n,\"default\"))&&a.push(t)}}}const c=[s,a];return n.set(e,c),c}function validatePropName(e){return\"$\"!==e[0]}function getType$1(e){const t=e&&e.toString().match(/^\\s*function (\\w+)/);return t?t[1]:null===e?\"null\":\"\"}function isSameType(e,t){return getType$1(e)===getType$1(t)}function getTypeIndex(e,t){return isArray(t)?t.findIndex((t=>isSameType(t,e))):isFunction(t)&&isSameType(t,e)?0:-1}const isInternalKey=e=>\"_\"===e[0]||\"$stable\"===e,normalizeSlotValue=e=>isArray(e)?e.map(normalizeVNode):[normalizeVNode(e)],normalizeSlot=(e,t,o)=>{const n=withCtx(((...e)=>normalizeSlotValue(t(...e))),o);return n._c=!1,n},normalizeObjectSlots=(e,t,o)=>{const n=e._ctx;for(const o in e){if(isInternalKey(o))continue;const i=e[o];if(isFunction(i))t[o]=normalizeSlot(o,i,n);else if(null!=i){const e=normalizeSlotValue(i);t[o]=()=>e}}},normalizeVNodeSlots=(e,t)=>{const o=normalizeSlotValue(t);e.slots.default=()=>o},initSlots=(e,t)=>{if(32&e.vnode.shapeFlag){const o=t._;o?(e.slots=toRaw(t),def(t,\"_\",o)):normalizeObjectSlots(t,e.slots={})}else e.slots={},t&&normalizeVNodeSlots(e,t);def(e.slots,InternalObjectKey,1)},updateSlots=(e,t,o)=>{const{vnode:n,slots:i}=e;let r=!0,s=EMPTY_OBJ;if(32&n.shapeFlag){const e=t._;e?o&&1===e?r=!1:(extend(i,t),o||1!==e||delete i._):(r=!t.$stable,normalizeObjectSlots(t,i)),s=t}else t&&(normalizeVNodeSlots(e,t),s={default:1});if(r)for(const e in i)isInternalKey(e)||e in s||delete i[e]};function withDirectives(e,t){if(null===currentRenderingInstance)return e;const o=currentRenderingInstance.proxy,n=e.dirs||(e.dirs=[]);for(let e=0;e<t.length;e++){let[i,r,s,a=EMPTY_OBJ]=t[e];isFunction(i)&&(i={mounted:i,updated:i}),i.deep&&traverse(r),n.push({dir:i,instance:o,value:r,oldValue:void 0,arg:s,modifiers:a})}return e}function invokeDirectiveHook(e,t,o,n){const i=e.dirs,r=t&&t.dirs;for(let s=0;s<i.length;s++){const a=i[s];r&&(a.oldValue=r[s].value);let l=a.dir[n];l&&(pauseTracking(),callWithAsyncErrorHandling(l,o,8,[e.el,a,e,t]),resetTracking())}}function createAppContext(){return{app:null,config:{isNativeTag:NO,performance:!1,globalProperties:{},optionMergeStrategies:{},errorHandler:void 0,warnHandler:void 0,compilerOptions:{}},mixins:[],components:{},directives:{},provides:Object.create(null),optionsCache:new WeakMap,propsCache:new WeakMap,emitsCache:new WeakMap}}let uid=0;function createAppAPI(e,t){return function(o,n=null){null==n||isObject$1(n)||(n=null);const i=createAppContext(),r=new Set;let s=!1;const a=i.app={_uid:uid++,_component:o,_props:n,_container:null,_context:i,_instance:null,version:version,get config(){return i.config},set config(e){},use:(e,...t)=>(r.has(e)||(e&&isFunction(e.install)?(r.add(e),e.install(a,...t)):isFunction(e)&&(r.add(e),e(a,...t))),a),mixin:e=>(i.mixins.includes(e)||i.mixins.push(e),a),component:(e,t)=>t?(i.components[e]=t,a):i.components[e],directive:(e,t)=>t?(i.directives[e]=t,a):i.directives[e],mount(r,l,c){if(!s){const d=createVNode(o,n);return d.appContext=i,l&&t?t(d,r):e(d,r,c),s=!0,a._container=r,r.__vue_app__=a,a._instance=d.component,devtoolsInitApp(a,version),getExposeProxy(d.component)||d.component.proxy}},unmount(){s&&(e(null,a._container),a._instance=null,devtoolsUnmountApp(a),delete a._container.__vue_app__)},provide:(e,t)=>(i.provides[e]=t,a)};return a}}function setRef(e,t,o,n,i=!1){if(isArray(e))return void e.forEach(((e,r)=>setRef(e,t&&(isArray(t)?t[r]:t),o,n,i)));if(isAsyncWrapper(n)&&!i)return;const r=4&n.shapeFlag?getExposeProxy(n.component)||n.component.proxy:n.el,s=i?null:r,{i:a,r:l}=e,c=t&&t.r,d=a.refs===EMPTY_OBJ?a.refs={}:a.refs,u=a.setupState;if(null!=c&&c!==l&&(isString(c)?(d[c]=null,hasOwn(u,c)&&(u[c]=null)):isRef(c)&&(c.value=null)),isFunction(l))callWithErrorHandling(l,a,12,[s,d]);else{const t=isString(l),n=isRef(l);if(t||n){const n=()=>{if(e.f){const o=t?d[l]:l.value;i?isArray(o)&&remove(o,r):isArray(o)?o.includes(r)||o.push(r):t?d[l]=[r]:(l.value=[r],e.k&&(d[e.k]=l.value))}else t?(d[l]=s,hasOwn(u,l)&&(u[l]=s)):isRef(l)&&(l.value=s,e.k&&(d[e.k]=s))};s?(n.id=-1,queuePostRenderEffect(n,o)):n()}}}const queuePostRenderEffect=queueEffectWithSuspense;function createRenderer(e){return baseCreateRenderer(e)}function baseCreateRenderer(e,t){const o=getGlobalThis();o.__VUE__=!0,setDevtoolsHook(o.__VUE_DEVTOOLS_GLOBAL_HOOK__,o);const{insert:n,remove:i,patchProp:r,createElement:s,createText:a,createComment:l,setText:c,setElementText:d,parentNode:u,nextSibling:h,setScopeId:p=NOOP,cloneNode:f,insertStaticContent:m}=e,g=(e,t,o,n=null,i=null,r=null,s=!1,a=null,l=!!t.dynamicChildren)=>{if(e===t)return;e&&!isSameVNodeType(e,t)&&(n=z(e),R(e,i,r,!0),e=null),-2===t.patchFlag&&(l=!1,t.dynamicChildren=null);const{type:c,ref:d,shapeFlag:u}=t;switch(c){case Text:v(e,t,o,n);break;case Comment:k(e,t,o,n);break;case Static:null==e&&y(t,o,n,s);break;case Fragment:B(e,t,o,n,i,r,s,a,l);break;default:1&u?w(e,t,o,n,i,r,s,a,l):6&u?L(e,t,o,n,i,r,s,a,l):(64&u||128&u)&&c.process(e,t,o,n,i,r,s,a,l,U)}null!=d&&i&&setRef(d,e&&e.ref,r,t||e,!t)},v=(e,t,o,i)=>{if(null==e)n(t.el=a(t.children),o,i);else{const o=t.el=e.el;t.children!==e.children&&c(o,t.children)}},k=(e,t,o,i)=>{null==e?n(t.el=l(t.children||\"\"),o,i):t.el=e.el},y=(e,t,o,n)=>{[e.el,e.anchor]=m(e.children,t,o,n,e.el,e.anchor)},w=(e,t,o,n,i,r,s,a,l)=>{s=s||\"svg\"===t.type,null==e?A(t,o,n,i,r,s,a,l):_(e,t,i,r,s,a,l)},A=(e,t,o,i,a,l,c,u)=>{let h,p;const{type:m,props:g,shapeFlag:v,transition:k,patchFlag:y,dirs:w}=e;if(e.el&&void 0!==f&&-1===y)h=e.el=f(e.el);else{if(h=e.el=s(e.type,l,g&&g.is,g),8&v?d(h,e.children):16&v&&b(e.children,h,null,i,a,l&&\"foreignObject\"!==m,c,u),w&&invokeDirectiveHook(e,null,i,\"created\"),g){for(const t in g)\"value\"===t||isReservedProp(t)||r(h,t,null,g[t],l,e.children,i,a,F);\"value\"in g&&r(h,\"value\",null,g.value),(p=g.onVnodeBeforeMount)&&invokeVNodeHook(p,i,e)}C(h,e,e.scopeId,c,i)}Object.defineProperty(h,\"__vnode\",{value:e,enumerable:!1}),Object.defineProperty(h,\"__vueParentComponent\",{value:i,enumerable:!1}),w&&invokeDirectiveHook(e,null,i,\"beforeMount\");const A=(!a||a&&!a.pendingBranch)&&k&&!k.persisted;A&&k.beforeEnter(h),n(h,t,o),((p=g&&g.onVnodeMounted)||A||w)&&queuePostRenderEffect((()=>{p&&invokeVNodeHook(p,i,e),A&&k.enter(h),w&&invokeDirectiveHook(e,null,i,\"mounted\")}),a)},C=(e,t,o,n,i)=>{if(o&&p(e,o),n)for(let t=0;t<n.length;t++)p(e,n[t]);if(i){if(t===i.subTree){const t=i.vnode;C(e,t,t.scopeId,t.slotScopeIds,i.parent)}}},b=(e,t,o,n,i,r,s,a,l=0)=>{for(let c=l;c<e.length;c++){const l=e[c]=a?cloneIfMounted(e[c]):normalizeVNode(e[c]);g(null,l,t,o,n,i,r,s,a)}},_=(e,t,o,n,i,s,a)=>{const l=t.el=e.el;let{patchFlag:c,dynamicChildren:u,dirs:h}=t;c|=16&e.patchFlag;const p=e.props||EMPTY_OBJ,f=t.props||EMPTY_OBJ;let m;o&&toggleRecurse(o,!1),(m=f.onVnodeBeforeUpdate)&&invokeVNodeHook(m,o,t,e),h&&invokeDirectiveHook(t,e,o,\"beforeUpdate\"),o&&toggleRecurse(o,!0);const g=i&&\"foreignObject\"!==t.type;if(u?x(e.dynamicChildren,u,l,o,n,g,s):a||I(e,t,l,null,o,n,g,s,!1),c>0){if(16&c)S(l,t,p,f,o,n,i);else if(2&c&&p.class!==f.class&&r(l,\"class\",null,f.class,i),4&c&&r(l,\"style\",p.style,f.style,i),8&c){const s=t.dynamicProps;for(let t=0;t<s.length;t++){const a=s[t],c=p[a],d=f[a];d===c&&\"value\"!==a||r(l,a,c,d,i,e.children,o,n,F)}}1&c&&e.children!==t.children&&d(l,t.children)}else a||null!=u||S(l,t,p,f,o,n,i);((m=f.onVnodeUpdated)||h)&&queuePostRenderEffect((()=>{m&&invokeVNodeHook(m,o,t,e),h&&invokeDirectiveHook(t,e,o,\"updated\")}),n)},x=(e,t,o,n,i,r,s)=>{for(let a=0;a<t.length;a++){const l=e[a],c=t[a],d=l.el&&(l.type===Fragment||!isSameVNodeType(l,c)||70&l.shapeFlag)?u(l.el):o;g(l,c,d,null,n,i,r,s,!0)}},S=(e,t,o,n,i,s,a)=>{if(o!==n){for(const l in n){if(isReservedProp(l))continue;const c=n[l],d=o[l];c!==d&&\"value\"!==l&&r(e,l,d,c,a,t.children,i,s,F)}if(o!==EMPTY_OBJ)for(const l in o)isReservedProp(l)||l in n||r(e,l,o[l],null,a,t.children,i,s,F);\"value\"in n&&r(e,\"value\",o.value,n.value)}},B=(e,t,o,i,r,s,l,c,d)=>{const u=t.el=e?e.el:a(\"\"),h=t.anchor=e?e.anchor:a(\"\");let{patchFlag:p,dynamicChildren:f,slotScopeIds:m}=t;m&&(c=c?c.concat(m):m),null==e?(n(u,o,i),n(h,o,i),b(t.children,o,h,r,s,l,c,d)):p>0&&64&p&&f&&e.dynamicChildren?(x(e.dynamicChildren,f,o,r,s,l,c),(null!=t.key||r&&t===r.subTree)&&traverseStaticChildren(e,t,!0)):I(e,t,o,h,r,s,l,c,d)},L=(e,t,o,n,i,r,s,a,l)=>{t.slotScopeIds=a,null==e?512&t.shapeFlag?i.ctx.activate(t,o,n,s,l):T(t,o,n,i,r,s,l):E(e,t,l)},T=(e,t,o,n,i,r,s)=>{const a=e.component=createComponentInstance(e,n,i);if(isKeepAlive(e)&&(a.ctx.renderer=U),setupComponent(a),a.asyncDep){if(i&&i.registerDep(a,N),!e.el){const e=a.subTree=createVNode(Comment);k(null,e,t,o)}}else N(a,e,t,o,i,r,s)},E=(e,t,o)=>{const n=t.component=e.component;if(shouldUpdateComponent(e,t,o)){if(n.asyncDep&&!n.asyncResolved)return void M(n,t,o);n.next=t,invalidateJob(n.update),n.update()}else t.component=e.component,t.el=e.el,n.vnode=t},N=(e,t,o,n,i,r,s)=>{const a=e.effect=new ReactiveEffect((()=>{if(e.isMounted){let t,{next:o,bu:n,u:a,parent:l,vnode:c}=e,d=o;toggleRecurse(e,!1),o?(o.el=c.el,M(e,o,s)):o=c,n&&invokeArrayFns(n),(t=o.props&&o.props.onVnodeBeforeUpdate)&&invokeVNodeHook(t,l,o,c),toggleRecurse(e,!0);const h=renderComponentRoot(e),p=e.subTree;e.subTree=h,g(p,h,u(p.el),z(p),e,i,r),o.el=h.el,null===d&&updateHOCHostEl(e,h.el),a&&queuePostRenderEffect(a,i),(t=o.props&&o.props.onVnodeUpdated)&&queuePostRenderEffect((()=>invokeVNodeHook(t,l,o,c)),i),devtoolsComponentUpdated(e)}else{let s;const{el:a,props:l}=t,{bm:c,m:d,parent:u}=e,h=isAsyncWrapper(t);if(toggleRecurse(e,!1),c&&invokeArrayFns(c),!h&&(s=l&&l.onVnodeBeforeMount)&&invokeVNodeHook(s,u,t),toggleRecurse(e,!0),a&&W){const o=()=>{e.subTree=renderComponentRoot(e),W(a,e.subTree,e,i,null)};h?t.type.__asyncLoader().then((()=>!e.isUnmounted&&o())):o()}else{const s=e.subTree=renderComponentRoot(e);g(null,s,o,n,e,i,r),t.el=s.el}if(d&&queuePostRenderEffect(d,i),!h&&(s=l&&l.onVnodeMounted)){const e=t;queuePostRenderEffect((()=>invokeVNodeHook(s,u,e)),i)}256&t.shapeFlag&&e.a&&queuePostRenderEffect(e.a,i),e.isMounted=!0,devtoolsComponentAdded(e),t=o=n=null}}),(()=>queueJob(e.update)),e.scope),l=e.update=a.run.bind(a);l.id=e.uid,toggleRecurse(e,!0),l()},M=(e,t,o)=>{t.component=e;const n=e.vnode.props;e.vnode=t,e.next=null,updateProps(e,t.props,n,o),updateSlots(e,t.children,o),pauseTracking(),flushPreFlushCbs(void 0,e.update),resetTracking()},I=(e,t,o,n,i,r,s,a,l=!1)=>{const c=e&&e.children,u=e?e.shapeFlag:0,h=t.children,{patchFlag:p,shapeFlag:f}=t;if(p>0){if(128&p)return void O(c,h,o,n,i,r,s,a,l);if(256&p)return void P(c,h,o,n,i,r,s,a,l)}8&f?(16&u&&F(c,i,r),h!==c&&d(o,h)):16&u?16&f?O(c,h,o,n,i,r,s,a,l):F(c,i,r,!0):(8&u&&d(o,\"\"),16&f&&b(h,o,n,i,r,s,a,l))},P=(e,t,o,n,i,r,s,a,l)=>{t=t||EMPTY_ARR;const c=(e=e||EMPTY_ARR).length,d=t.length,u=Math.min(c,d);let h;for(h=0;h<u;h++){const n=t[h]=l?cloneIfMounted(t[h]):normalizeVNode(t[h]);g(e[h],n,o,null,i,r,s,a,l)}c>d?F(e,i,r,!0,!1,u):b(t,o,n,i,r,s,a,l,u)},O=(e,t,o,n,i,r,s,a,l)=>{let c=0;const d=t.length;let u=e.length-1,h=d-1;for(;c<=u&&c<=h;){const n=e[c],d=t[c]=l?cloneIfMounted(t[c]):normalizeVNode(t[c]);if(!isSameVNodeType(n,d))break;g(n,d,o,null,i,r,s,a,l),c++}for(;c<=u&&c<=h;){const n=e[u],c=t[h]=l?cloneIfMounted(t[h]):normalizeVNode(t[h]);if(!isSameVNodeType(n,c))break;g(n,c,o,null,i,r,s,a,l),u--,h--}if(c>u){if(c<=h){const e=h+1,u=e<d?t[e].el:n;for(;c<=h;)g(null,t[c]=l?cloneIfMounted(t[c]):normalizeVNode(t[c]),o,u,i,r,s,a,l),c++}}else if(c>h)for(;c<=u;)R(e[c],i,r,!0),c++;else{const p=c,f=c,m=new Map;for(c=f;c<=h;c++){const e=t[c]=l?cloneIfMounted(t[c]):normalizeVNode(t[c]);null!=e.key&&m.set(e.key,c)}let v,k=0;const y=h-f+1;let w=!1,A=0;const C=new Array(y);for(c=0;c<y;c++)C[c]=0;for(c=p;c<=u;c++){const n=e[c];if(k>=y){R(n,i,r,!0);continue}let d;if(null!=n.key)d=m.get(n.key);else for(v=f;v<=h;v++)if(0===C[v-f]&&isSameVNodeType(n,t[v])){d=v;break}void 0===d?R(n,i,r,!0):(C[d-f]=c+1,d>=A?A=d:w=!0,g(n,t[d],o,null,i,r,s,a,l),k++)}const b=w?getSequence(C):EMPTY_ARR;for(v=b.length-1,c=y-1;c>=0;c--){const e=f+c,u=t[e],h=e+1<d?t[e+1].el:n;0===C[c]?g(null,u,o,h,i,r,s,a,l):w&&(v<0||c!==b[v]?D(u,o,h,2):v--)}}},D=(e,t,o,i,r=null)=>{const{el:s,type:a,transition:l,children:c,shapeFlag:d}=e;if(6&d)return void D(e.component.subTree,t,o,i);if(128&d)return void e.suspense.move(t,o,i);if(64&d)return void a.move(e,t,o,U);if(a===Fragment){n(s,t,o);for(let e=0;e<c.length;e++)D(c[e],t,o,i);return void n(e.anchor,t,o)}if(a===Static)return void(({el:e,anchor:t},o,i)=>{let r;for(;e&&e!==t;)r=h(e),n(e,o,i),e=r;n(t,o,i)})(e,t,o);if(2!==i&&1&d&&l)if(0===i)l.beforeEnter(s),n(s,t,o),queuePostRenderEffect((()=>l.enter(s)),r);else{const{leave:e,delayLeave:i,afterLeave:r}=l,a=()=>n(s,t,o),c=()=>{e(s,(()=>{a(),r&&r()}))};i?i(s,a,c):c()}else n(s,t,o)},R=(e,t,o,n=!1,i=!1)=>{const{type:r,props:s,ref:a,children:l,dynamicChildren:c,shapeFlag:d,patchFlag:u,dirs:h}=e;if(null!=a&&setRef(a,null,o,e,!0),256&d)return void t.ctx.deactivate(e);const p=1&d&&h,f=!isAsyncWrapper(e);let m;if(f&&(m=s&&s.onVnodeBeforeUnmount)&&invokeVNodeHook(m,t,e),6&d)Y(e.component,o,n);else{if(128&d)return void e.suspense.unmount(o,n);p&&invokeDirectiveHook(e,null,t,\"beforeUnmount\"),64&d?e.type.remove(e,t,o,i,U,n):c&&(r!==Fragment||u>0&&64&u)?F(c,t,o,!1,!0):(r===Fragment&&384&u||!i&&16&d)&&F(l,t,o),n&&V(e)}(f&&(m=s&&s.onVnodeUnmounted)||p)&&queuePostRenderEffect((()=>{m&&invokeVNodeHook(m,t,e),p&&invokeDirectiveHook(e,null,t,\"unmounted\")}),o)},V=e=>{const{type:t,el:o,anchor:n,transition:r}=e;if(t===Fragment)return void H(o,n);if(t===Static)return void(({el:e,anchor:t})=>{let o;for(;e&&e!==t;)o=h(e),i(e),e=o;i(t)})(e);const s=()=>{i(o),r&&!r.persisted&&r.afterLeave&&r.afterLeave()};if(1&e.shapeFlag&&r&&!r.persisted){const{leave:t,delayLeave:n}=r,i=()=>t(o,s);n?n(e.el,s,i):i()}else s()},H=(e,t)=>{let o;for(;e!==t;)o=h(e),i(e),e=o;i(t)},Y=(e,t,o)=>{const{bum:n,scope:i,update:r,subTree:s,um:a}=e;n&&invokeArrayFns(n),i.stop(),r&&(r.active=!1,R(s,e,t,o)),a&&queuePostRenderEffect(a,t),queuePostRenderEffect((()=>{e.isUnmounted=!0}),t),t&&t.pendingBranch&&!t.isUnmounted&&e.asyncDep&&!e.asyncResolved&&e.suspenseId===t.pendingId&&(t.deps--,0===t.deps&&t.resolve()),devtoolsComponentRemoved(e)},F=(e,t,o,n=!1,i=!1,r=0)=>{for(let s=r;s<e.length;s++)R(e[s],t,o,n,i)},z=e=>6&e.shapeFlag?z(e.component.subTree):128&e.shapeFlag?e.suspense.next():h(e.anchor||e.el),j=(e,t,o)=>{null==e?t._vnode&&R(t._vnode,null,null,!0):g(t._vnode||null,e,t,null,null,null,o),flushPostFlushCbs(),t._vnode=e},U={p:g,um:R,m:D,r:V,mt:T,mc:b,pc:I,pbc:x,n:z,o:e};let q,W;return t&&([q,W]=t(U)),{render:j,hydrate:q,createApp:createAppAPI(j,q)}}function toggleRecurse({effect:e,update:t},o){e.allowRecurse=t.allowRecurse=o}function traverseStaticChildren(e,t,o=!1){const n=e.children,i=t.children;if(isArray(n)&&isArray(i))for(let e=0;e<n.length;e++){const t=n[e];let r=i[e];1&r.shapeFlag&&!r.dynamicChildren&&((r.patchFlag<=0||32===r.patchFlag)&&(r=i[e]=cloneIfMounted(i[e]),r.el=t.el),o||traverseStaticChildren(t,r))}}function getSequence(e){const t=e.slice(),o=[0];let n,i,r,s,a;const l=e.length;for(n=0;n<l;n++){const l=e[n];if(0!==l){if(i=o[o.length-1],e[i]<l){t[n]=i,o.push(n);continue}for(r=0,s=o.length-1;r<s;)a=r+s>>1,e[o[a]]<l?r=a+1:s=a;l<e[o[r]]&&(r>0&&(t[n]=o[r-1]),o[r]=n)}}for(r=o.length,s=o[r-1];r-- >0;)o[r]=s,s=t[s];return o}const isTeleport=e=>e.__isTeleport,COMPONENTS=\"components\",DIRECTIVES=\"directives\";function resolveComponent(e,t){return resolveAsset(COMPONENTS,e,!0,t)||e}const NULL_DYNAMIC_COMPONENT=Symbol();function resolveDynamicComponent(e){return isString(e)?resolveAsset(COMPONENTS,e,!1)||e:e||NULL_DYNAMIC_COMPONENT}function resolveDirective(e){return resolveAsset(DIRECTIVES,e)}function resolveAsset(e,t,o=!0,n=!1){const i=currentRenderingInstance||currentInstance;if(i){const o=i.type;if(e===COMPONENTS){const e=getComponentName(o);if(e&&(e===t||e===camelize(t)||e===capitalize(camelize(t))))return o}const r=resolve(i[e]||o[e],t)||resolve(i.appContext[e],t);return!r&&n?o:r}}function resolve(e,t){return e&&(e[t]||e[camelize(t)]||e[capitalize(camelize(t))])}const Fragment=Symbol(void 0),Text=Symbol(void 0),Comment=Symbol(void 0),Static=Symbol(void 0),blockStack=[];let currentBlock=null;function openBlock(e=!1){blockStack.push(currentBlock=e?null:[])}function closeBlock(){blockStack.pop(),currentBlock=blockStack[blockStack.length-1]||null}let isBlockTreeEnabled=1;function setBlockTracking(e){isBlockTreeEnabled+=e}function setupBlock(e){return e.dynamicChildren=isBlockTreeEnabled>0?currentBlock||EMPTY_ARR:null,closeBlock(),isBlockTreeEnabled>0&&currentBlock&&currentBlock.push(e),e}function createElementBlock(e,t,o,n,i,r){return setupBlock(createBaseVNode(e,t,o,n,i,r,!0))}function createBlock(e,t,o,n,i){return setupBlock(createVNode(e,t,o,n,i,!0))}function isVNode(e){return!!e&&!0===e.__v_isVNode}function isSameVNodeType(e,t){return e.type===t.type&&e.key===t.key}const InternalObjectKey=\"__vInternal\",normalizeKey=({key:e})=>null!=e?e:null,normalizeRef=({ref:e,ref_key:t,ref_for:o})=>null!=e?isString(e)||isRef(e)||isFunction(e)?{i:currentRenderingInstance,r:e,k:t,f:!!o}:e:null;function createBaseVNode(e,t=null,o=null,n=0,i=null,r=(e===Fragment?0:1),s=!1,a=!1){const l={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&normalizeKey(t),ref:t&&normalizeRef(t),scopeId:currentScopeId,slotScopeIds:null,children:o,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:r,patchFlag:n,dynamicProps:i,dynamicChildren:null,appContext:null};return a?(normalizeChildren(l,o),128&r&&e.normalize(l)):o&&(l.shapeFlag|=isString(o)?8:16),isBlockTreeEnabled>0&&!s&&currentBlock&&(l.patchFlag>0||6&r)&&32!==l.patchFlag&&currentBlock.push(l),l}const createVNode=_createVNode;function _createVNode(e,t=null,o=null,n=0,i=null,r=!1){if(e&&e!==NULL_DYNAMIC_COMPONENT||(e=Comment),isVNode(e)){const n=cloneVNode(e,t,!0);return o&&normalizeChildren(n,o),n}if(isClassComponent(e)&&(e=e.__vccOpts),t){t=guardReactiveProps(t);let{class:e,style:o}=t;e&&!isString(e)&&(t.class=normalizeClass(e)),isObject$1(o)&&(isProxy(o)&&!isArray(o)&&(o=extend({},o)),t.style=normalizeStyle(o))}return createBaseVNode(e,t,o,n,i,isString(e)?1:isSuspense(e)?128:isTeleport(e)?64:isObject$1(e)?4:isFunction(e)?2:0,r,!0)}function guardReactiveProps(e){return e?isProxy(e)||InternalObjectKey in e?extend({},e):e:null}function cloneVNode(e,t,o=!1){const{props:n,ref:i,patchFlag:r,children:s}=e,a=t?mergeProps(n||{},t):n;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:a,key:a&&normalizeKey(a),ref:t&&t.ref?o&&i?isArray(i)?i.concat(normalizeRef(t)):[i,normalizeRef(t)]:normalizeRef(t):i,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:s,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Fragment?-1===r?16:16|r:r,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&cloneVNode(e.ssContent),ssFallback:e.ssFallback&&cloneVNode(e.ssFallback),el:e.el,anchor:e.anchor}}function createTextVNode(e=\" \",t=0){return createVNode(Text,null,e,t)}function createStaticVNode(e,t){const o=createVNode(Static,null,e);return o.staticCount=t,o}function createCommentVNode(e=\"\",t=!1){return t?(openBlock(),createBlock(Comment,null,e)):createVNode(Comment,null,e)}function normalizeVNode(e){return null==e||\"boolean\"==typeof e?createVNode(Comment):isArray(e)?createVNode(Fragment,null,e.slice()):\"object\"==typeof e?cloneIfMounted(e):createVNode(Text,null,String(e))}function cloneIfMounted(e){return null===e.el||e.memo?e:cloneVNode(e)}function normalizeChildren(e,t){let o=0;const{shapeFlag:n}=e;if(null==t)t=null;else if(isArray(t))o=16;else if(\"object\"==typeof t){if(65&n){const o=t.default;return void(o&&(o._c&&(o._d=!1),normalizeChildren(e,o()),o._c&&(o._d=!0)))}{o=32;const n=t._;n||InternalObjectKey in t?3===n&&currentRenderingInstance&&(1===currentRenderingInstance.slots._?t._=1:(t._=2,e.patchFlag|=1024)):t._ctx=currentRenderingInstance}}else isFunction(t)?(t={default:t,_ctx:currentRenderingInstance},o=32):(t=String(t),64&n?(o=16,t=[createTextVNode(t)]):o=8);e.children=t,e.shapeFlag|=o}function mergeProps(...e){const t={};for(let o=0;o<e.length;o++){const n=e[o];for(const e in n)if(\"class\"===e)t.class!==n.class&&(t.class=normalizeClass([t.class,n.class]));else if(\"style\"===e)t.style=normalizeStyle([t.style,n.style]);else if(isOn(e)){const o=t[e],i=n[e];!i||o===i||isArray(o)&&o.includes(i)||(t[e]=o?[].concat(o,i):i)}else\"\"!==e&&(t[e]=n[e])}return t}function invokeVNodeHook(e,t,o,n=null){callWithAsyncErrorHandling(e,t,7,[o,n])}function renderList(e,t,o,n){let i;const r=o&&o[n];if(isArray(e)||isString(e)){i=new Array(e.length);for(let o=0,n=e.length;o<n;o++)i[o]=t(e[o],o,void 0,r&&r[o])}else if(\"number\"==typeof e){i=new Array(e);for(let o=0;o<e;o++)i[o]=t(o+1,o,void 0,r&&r[o])}else if(isObject$1(e))if(e[Symbol.iterator])i=Array.from(e,((e,o)=>t(e,o,void 0,r&&r[o])));else{const o=Object.keys(e);i=new Array(o.length);for(let n=0,s=o.length;n<s;n++){const s=o[n];i[n]=t(e[s],s,n,r&&r[n])}}else i=[];return o&&(o[n]=i),i}function renderSlot(e,t,o={},n,i){if(currentRenderingInstance.isCE)return createVNode(\"slot\",\"default\"===t?null:{name:t},n&&n());let r=e[t];r&&r._c&&(r._d=!1),openBlock();const s=r&&ensureValidVNode(r(o)),a=createBlock(Fragment,{key:o.key||`_${t}`},s||(n?n():[]),s&&1===e._?64:-2);return!i&&a.scopeId&&(a.slotScopeIds=[a.scopeId+\"-s\"]),r&&r._c&&(r._d=!0),a}function ensureValidVNode(e){return e.some((e=>!isVNode(e)||e.type!==Comment&&!(e.type===Fragment&&!ensureValidVNode(e.children))))?e:null}const getPublicInstance=e=>e?isStatefulComponent(e)?getExposeProxy(e)||e.proxy:getPublicInstance(e.parent):null,publicPropertiesMap=extend(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>getPublicInstance(e.parent),$root:e=>getPublicInstance(e.root),$emit:e=>e.emit,$options:e=>resolveMergedOptions(e),$forceUpdate:e=>()=>queueJob(e.update),$nextTick:e=>nextTick.bind(e.proxy),$watch:e=>instanceWatch.bind(e)}),PublicInstanceProxyHandlers={get({_:e},t){const{ctx:o,setupState:n,data:i,props:r,accessCache:s,type:a,appContext:l}=e;let c;if(\"$\"!==t[0]){const a=s[t];if(void 0!==a)switch(a){case 1:return n[t];case 2:return i[t];case 4:return o[t];case 3:return r[t]}else{if(n!==EMPTY_OBJ&&hasOwn(n,t))return s[t]=1,n[t];if(i!==EMPTY_OBJ&&hasOwn(i,t))return s[t]=2,i[t];if((c=e.propsOptions[0])&&hasOwn(c,t))return s[t]=3,r[t];if(o!==EMPTY_OBJ&&hasOwn(o,t))return s[t]=4,o[t];shouldCacheAccess&&(s[t]=0)}}const d=publicPropertiesMap[t];let u,h;return d?(\"$attrs\"===t&&track(e,\"get\",t),d(e)):(u=a.__cssModules)&&(u=u[t])?u:o!==EMPTY_OBJ&&hasOwn(o,t)?(s[t]=4,o[t]):(h=l.config.globalProperties,hasOwn(h,t)?h[t]:void 0)},set({_:e},t,o){const{data:n,setupState:i,ctx:r}=e;return i!==EMPTY_OBJ&&hasOwn(i,t)?(i[t]=o,!0):n!==EMPTY_OBJ&&hasOwn(n,t)?(n[t]=o,!0):!hasOwn(e.props,t)&&((\"$\"!==t[0]||!(t.slice(1)in e))&&(r[t]=o,!0))},has({_:{data:e,setupState:t,accessCache:o,ctx:n,appContext:i,propsOptions:r}},s){let a;return!!o[s]||e!==EMPTY_OBJ&&hasOwn(e,s)||t!==EMPTY_OBJ&&hasOwn(t,s)||(a=r[0])&&hasOwn(a,s)||hasOwn(n,s)||hasOwn(publicPropertiesMap,s)||hasOwn(i.config.globalProperties,s)},defineProperty(e,t,o){return null!=o.get?this.set(e,t,o.get(),null):null!=o.value&&this.set(e,t,o.value,null),Reflect.defineProperty(e,t,o)}},emptyAppContext=createAppContext();let uid$1=0;function createComponentInstance(e,t,o){const n=e.type,i=(t?t.appContext:e.appContext)||emptyAppContext,r={uid:uid$1++,vnode:e,type:n,parent:t,appContext:i,root:null,next:null,subTree:null,effect:null,update:null,scope:new EffectScope(!0),render:null,proxy:null,exposed:null,exposeProxy:null,withProxy:null,provides:t?t.provides:Object.create(i.provides),accessCache:null,renderCache:[],components:null,directives:null,propsOptions:normalizePropsOptions(n,i),emitsOptions:normalizeEmitsOptions(n,i),emit:null,emitted:null,propsDefaults:EMPTY_OBJ,inheritAttrs:n.inheritAttrs,ctx:EMPTY_OBJ,data:EMPTY_OBJ,props:EMPTY_OBJ,attrs:EMPTY_OBJ,slots:EMPTY_OBJ,refs:EMPTY_OBJ,setupState:EMPTY_OBJ,setupContext:null,suspense:o,suspenseId:o?o.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null,sp:null};return r.ctx={_:r},r.root=t?t.root:r,r.emit=emit$1.bind(null,r),e.ce&&e.ce(r),r}let currentInstance=null;const getCurrentInstance=()=>currentInstance||currentRenderingInstance,setCurrentInstance=e=>{currentInstance=e,e.scope.on()},unsetCurrentInstance=()=>{currentInstance&&currentInstance.scope.off(),currentInstance=null};function isStatefulComponent(e){return 4&e.vnode.shapeFlag}let isInSSRComponentSetup=!1,compile;function setupComponent(e,t=!1){isInSSRComponentSetup=t;const{props:o,children:n}=e.vnode,i=isStatefulComponent(e);initProps(e,o,i,t),initSlots(e,n);const r=i?setupStatefulComponent(e,t):void 0;return isInSSRComponentSetup=!1,r}function setupStatefulComponent(e,t){const o=e.type;e.accessCache=Object.create(null),e.proxy=markRaw(new Proxy(e.ctx,PublicInstanceProxyHandlers));const{setup:n}=o;if(n){const o=e.setupContext=n.length>1?createSetupContext(e):null;setCurrentInstance(e),pauseTracking();const i=callWithErrorHandling(n,e,0,[e.props,o]);if(resetTracking(),unsetCurrentInstance(),isPromise(i)){if(i.then(unsetCurrentInstance,unsetCurrentInstance),t)return i.then((o=>{handleSetupResult(e,o,t)})).catch((t=>{handleError(t,e,0)}));e.asyncDep=i}else handleSetupResult(e,i,t)}else finishComponentSetup(e,t)}function handleSetupResult(e,t,o){isFunction(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:isObject$1(t)&&(e.devtoolsRawSetupState=t,e.setupState=proxyRefs(t)),finishComponentSetup(e,o)}function finishComponentSetup(e,t,o){const n=e.type;if(!e.render){if(!t&&compile&&!n.render){const t=n.template;if(t){const{isCustomElement:o,compilerOptions:i}=e.appContext.config,{delimiters:r,compilerOptions:s}=n,a=extend(extend({isCustomElement:o,delimiters:r},i),s);n.render=compile(t,a)}}e.render=n.render||NOOP}setCurrentInstance(e),pauseTracking(),applyOptions(e),resetTracking(),unsetCurrentInstance()}function createAttrsProxy(e){return new Proxy(e.attrs,{get:(t,o)=>(track(e,\"get\",\"$attrs\"),t[o])})}function createSetupContext(e){const t=t=>{e.exposed=t||{}};let o;return{get attrs(){return o||(o=createAttrsProxy(e))},slots:e.slots,emit:e.emit,expose:t}}function getExposeProxy(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(proxyRefs(markRaw(e.exposed)),{get:(t,o)=>o in t?t[o]:o in publicPropertiesMap?publicPropertiesMap[o](e):void 0}))}function getComponentName(e){return isFunction(e)&&e.displayName||e.name}function isClassComponent(e){return isFunction(e)&&\"__vccOpts\"in e}const computed=(e,t)=>computed$1(e,t,isInSSRComponentSetup);function h$1(e,t,o){const n=arguments.length;return 2===n?isObject$1(t)&&!isArray(t)?isVNode(t)?createVNode(e,null,[t]):createVNode(e,t):createVNode(e,null,t):(n>3?o=Array.prototype.slice.call(arguments,2):3===n&&isVNode(o)&&(o=[o]),createVNode(e,t,o))}const version=\"3.2.31\",svgNS=\"http://www.w3.org/2000/svg\",doc=\"undefined\"!=typeof document?document:null,templateContainer=doc&&doc.createElement(\"template\"),nodeOps={insert:(e,t,o)=>{t.insertBefore(e,o||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,o,n)=>{const i=t?doc.createElementNS(svgNS,e):doc.createElement(e,o?{is:o}:void 0);return\"select\"===e&&n&&null!=n.multiple&&i.setAttribute(\"multiple\",n.multiple),i},createText:e=>doc.createTextNode(e),createComment:e=>doc.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>doc.querySelector(e),setScopeId(e,t){e.setAttribute(t,\"\")},cloneNode(e){const t=e.cloneNode(!0);return\"_value\"in e&&(t._value=e._value),t},insertStaticContent(e,t,o,n,i,r){const s=o?o.previousSibling:t.lastChild;if(i&&(i===r||i.nextSibling))for(;t.insertBefore(i.cloneNode(!0),o),i!==r&&(i=i.nextSibling););else{templateContainer.innerHTML=n?`<svg>${e}</svg>`:e;const i=templateContainer.content;if(n){const e=i.firstChild;for(;e.firstChild;)i.appendChild(e.firstChild);i.removeChild(e)}t.insertBefore(i,o)}return[s?s.nextSibling:t.firstChild,o?o.previousSibling:t.lastChild]}};function patchClass(e,t,o){const n=e._vtc;n&&(t=(t?[t,...n]:[...n]).join(\" \")),null==t?e.removeAttribute(\"class\"):o?e.setAttribute(\"class\",t):e.className=t}function patchStyle(e,t,o){const n=e.style,i=isString(o);if(o&&!i){for(const e in o)setStyle(n,e,o[e]);if(t&&!isString(t))for(const e in t)null==o[e]&&setStyle(n,e,\"\")}else{const r=n.display;i?t!==o&&(n.cssText=o):t&&e.removeAttribute(\"style\"),\"_vod\"in e&&(n.display=r)}}const importantRE=/\\s*!important$/;function setStyle(e,t,o){if(isArray(o))o.forEach((o=>setStyle(e,t,o)));else if(t.startsWith(\"--\"))e.setProperty(t,o);else{const n=autoPrefix(e,t);importantRE.test(o)?e.setProperty(hyphenate(n),o.replace(importantRE,\"\"),\"important\"):e[n]=o}}const prefixes=[\"Webkit\",\"Moz\",\"ms\"],prefixCache={};function autoPrefix(e,t){const o=prefixCache[t];if(o)return o;let n=camelize(t);if(\"filter\"!==n&&n in e)return prefixCache[t]=n;n=capitalize(n);for(let o=0;o<prefixes.length;o++){const i=prefixes[o]+n;if(i in e)return prefixCache[t]=i}return t}const xlinkNS=\"http://www.w3.org/1999/xlink\";function patchAttr(e,t,o,n,i){if(n&&t.startsWith(\"xlink:\"))null==o?e.removeAttributeNS(xlinkNS,t.slice(6,t.length)):e.setAttributeNS(xlinkNS,t,o);else{const n=isSpecialBooleanAttr(t);null==o||n&&!includeBooleanAttr(o)?e.removeAttribute(t):e.setAttribute(t,n?\"\":o)}}function patchDOMProp(e,t,o,n,i,r,s){if(\"innerHTML\"===t||\"textContent\"===t)return n&&s(n,i,r),void(e[t]=null==o?\"\":o);if(\"value\"===t&&\"PROGRESS\"!==e.tagName&&!e.tagName.includes(\"-\")){e._value=o;const n=null==o?\"\":o;return e.value===n&&\"OPTION\"!==e.tagName||(e.value=n),void(null==o&&e.removeAttribute(t))}if(\"\"===o||null==o){const n=typeof e[t];if(\"boolean\"===n)return void(e[t]=includeBooleanAttr(o));if(null==o&&\"string\"===n)return e[t]=\"\",void e.removeAttribute(t);if(\"number\"===n){try{e[t]=0}catch(e){}return void e.removeAttribute(t)}}try{e[t]=o}catch(e){}}let _getNow=Date.now,skipTimestampCheck=!1;if(\"undefined\"!=typeof window){_getNow()>document.createEvent(\"Event\").timeStamp&&(_getNow=()=>performance.now());const e=navigator.userAgent.match(/firefox\\/(\\d+)/i);skipTimestampCheck=!!(e&&Number(e[1])<=53)}let cachedNow=0;const p$1=Promise.resolve(),reset=()=>{cachedNow=0},getNow=()=>cachedNow||(p$1.then(reset),cachedNow=_getNow());function addEventListener$1(e,t,o,n){e.addEventListener(t,o,n)}function removeEventListener$1(e,t,o,n){e.removeEventListener(t,o,n)}function patchEvent(e,t,o,n,i=null){const r=e._vei||(e._vei={}),s=r[t];if(n&&s)s.value=n;else{const[o,a]=parseName(t);if(n){addEventListener$1(e,o,r[t]=createInvoker(n,i),a)}else s&&(removeEventListener$1(e,o,s,a),r[t]=void 0)}}const optionsModifierRE=/(?:Once|Passive|Capture)$/;function parseName(e){let t;if(optionsModifierRE.test(e)){let o;for(t={};o=e.match(optionsModifierRE);)e=e.slice(0,e.length-o[0].length),t[o[0].toLowerCase()]=!0}return[hyphenate(e.slice(2)),t]}function createInvoker(e,t){const o=e=>{const n=e.timeStamp||_getNow();(skipTimestampCheck||n>=o.attached-1)&&callWithAsyncErrorHandling(patchStopImmediatePropagation(e,o.value),t,5,[e])};return o.value=e,o.attached=getNow(),o}function patchStopImmediatePropagation(e,t){if(isArray(t)){const o=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{o.call(e),e._stopped=!0},t.map((e=>t=>!t._stopped&&e&&e(t)))}return t}const nativeOnRE=/^on[a-z]/,patchProp=(e,t,o,n,i=!1,r,s,a,l)=>{\"class\"===t?patchClass(e,n,i):\"style\"===t?patchStyle(e,o,n):isOn(t)?isModelListener(t)||patchEvent(e,t,o,n,s):(\".\"===t[0]?(t=t.slice(1),1):\"^\"===t[0]?(t=t.slice(1),0):shouldSetAsProp(e,t,n,i))?patchDOMProp(e,t,n,r,s,a,l):(\"true-value\"===t?e._trueValue=n:\"false-value\"===t&&(e._falseValue=n),patchAttr(e,t,n,i))};function shouldSetAsProp(e,t,o,n){return n?\"innerHTML\"===t||\"textContent\"===t||!!(t in e&&nativeOnRE.test(t)&&isFunction(o)):\"spellcheck\"!==t&&\"draggable\"!==t&&(\"form\"!==t&&((\"list\"!==t||\"INPUT\"!==e.tagName)&&((\"type\"!==t||\"TEXTAREA\"!==e.tagName)&&((!nativeOnRE.test(t)||!isString(o))&&t in e))))}const TRANSITION=\"transition\",ANIMATION=\"animation\",Transition=(e,{slots:t})=>h$1(BaseTransition,resolveTransitionProps(e),t);Transition.displayName=\"Transition\";const DOMTransitionPropsValidators={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String};Transition.props=extend({},BaseTransition.props,DOMTransitionPropsValidators);const callHook=(e,t=[])=>{isArray(e)?e.forEach((e=>e(...t))):e&&e(...t)},hasExplicitCallback=e=>!!e&&(isArray(e)?e.some((e=>e.length>1)):e.length>1);function resolveTransitionProps(e){const t={};for(const o in e)o in DOMTransitionPropsValidators||(t[o]=e[o]);if(!1===e.css)return t;const{name:o=\"v\",type:n,duration:i,enterFromClass:r=`${o}-enter-from`,enterActiveClass:s=`${o}-enter-active`,enterToClass:a=`${o}-enter-to`,appearFromClass:l=r,appearActiveClass:c=s,appearToClass:d=a,leaveFromClass:u=`${o}-leave-from`,leaveActiveClass:h=`${o}-leave-active`,leaveToClass:p=`${o}-leave-to`}=e,f=normalizeDuration(i),m=f&&f[0],g=f&&f[1],{onBeforeEnter:v,onEnter:k,onEnterCancelled:y,onLeave:w,onLeaveCancelled:A,onBeforeAppear:C=v,onAppear:b=k,onAppearCancelled:_=y}=t,x=(e,t,o)=>{removeTransitionClass(e,t?d:a),removeTransitionClass(e,t?c:s),o&&o()},S=(e,t)=>{removeTransitionClass(e,p),removeTransitionClass(e,h),t&&t()},B=e=>(t,o)=>{const i=e?b:k,s=()=>x(t,e,o);callHook(i,[t,s]),nextFrame((()=>{removeTransitionClass(t,e?l:r),addTransitionClass(t,e?d:a),hasExplicitCallback(i)||whenTransitionEnds(t,n,m,s)}))};return extend(t,{onBeforeEnter(e){callHook(v,[e]),addTransitionClass(e,r),addTransitionClass(e,s)},onBeforeAppear(e){callHook(C,[e]),addTransitionClass(e,l),addTransitionClass(e,c)},onEnter:B(!1),onAppear:B(!0),onLeave(e,t){const o=()=>S(e,t);addTransitionClass(e,u),forceReflow(),addTransitionClass(e,h),nextFrame((()=>{removeTransitionClass(e,u),addTransitionClass(e,p),hasExplicitCallback(w)||whenTransitionEnds(e,n,g,o)})),callHook(w,[e,o])},onEnterCancelled(e){x(e,!1),callHook(y,[e])},onAppearCancelled(e){x(e,!0),callHook(_,[e])},onLeaveCancelled(e){S(e),callHook(A,[e])}})}function normalizeDuration(e){if(null==e)return null;if(isObject$1(e))return[NumberOf(e.enter),NumberOf(e.leave)];{const t=NumberOf(e);return[t,t]}}function NumberOf(e){return toNumber(e)}function addTransitionClass(e,t){t.split(/\\s+/).forEach((t=>t&&e.classList.add(t))),(e._vtc||(e._vtc=new Set)).add(t)}function removeTransitionClass(e,t){t.split(/\\s+/).forEach((t=>t&&e.classList.remove(t)));const{_vtc:o}=e;o&&(o.delete(t),o.size||(e._vtc=void 0))}function nextFrame(e){requestAnimationFrame((()=>{requestAnimationFrame(e)}))}let endId=0;function whenTransitionEnds(e,t,o,n){const i=e._endId=++endId,r=()=>{i===e._endId&&n()};if(o)return setTimeout(r,o);const{type:s,timeout:a,propCount:l}=getTransitionInfo(e,t);if(!s)return n();const c=s+\"end\";let d=0;const u=()=>{e.removeEventListener(c,h),r()},h=t=>{t.target===e&&++d>=l&&u()};setTimeout((()=>{d<l&&u()}),a+1),e.addEventListener(c,h)}function getTransitionInfo(e,t){const o=window.getComputedStyle(e),n=e=>(o[e]||\"\").split(\", \"),i=n(TRANSITION+\"Delay\"),r=n(TRANSITION+\"Duration\"),s=getTimeout(i,r),a=n(ANIMATION+\"Delay\"),l=n(ANIMATION+\"Duration\"),c=getTimeout(a,l);let d=null,u=0,h=0;t===TRANSITION?s>0&&(d=TRANSITION,u=s,h=r.length):t===ANIMATION?c>0&&(d=ANIMATION,u=c,h=l.length):(u=Math.max(s,c),d=u>0?s>c?TRANSITION:ANIMATION:null,h=d?d===TRANSITION?r.length:l.length:0);return{type:d,timeout:u,propCount:h,hasTransform:d===TRANSITION&&/\\b(transform|all)(,|$)/.test(o[TRANSITION+\"Property\"])}}function getTimeout(e,t){for(;e.length<t.length;)e=e.concat(e);return Math.max(...t.map(((t,o)=>toMs(t)+toMs(e[o]))))}function toMs(e){return 1e3*Number(e.slice(0,-1).replace(\",\",\".\"))}function forceReflow(){return document.body.offsetHeight}const getModelAssigner=e=>{const t=e.props[\"onUpdate:modelValue\"];return isArray(t)?e=>invokeArrayFns(t,e):t};function onCompositionStart(e){e.target.composing=!0}function onCompositionEnd(e){const t=e.target;t.composing&&(t.composing=!1,trigger(t,\"input\"))}function trigger(e,t){const o=document.createEvent(\"HTMLEvents\");o.initEvent(t,!0,!0),e.dispatchEvent(o)}const vModelText={created(e,{modifiers:{lazy:t,trim:o,number:n}},i){e._assign=getModelAssigner(i);const r=n||i.props&&\"number\"===i.props.type;addEventListener$1(e,t?\"change\":\"input\",(t=>{if(t.target.composing)return;let n=e.value;o?n=n.trim():r&&(n=toNumber(n)),e._assign(n)})),o&&addEventListener$1(e,\"change\",(()=>{e.value=e.value.trim()})),t||(addEventListener$1(e,\"compositionstart\",onCompositionStart),addEventListener$1(e,\"compositionend\",onCompositionEnd),addEventListener$1(e,\"change\",onCompositionEnd))},mounted(e,{value:t}){e.value=null==t?\"\":t},beforeUpdate(e,{value:t,modifiers:{lazy:o,trim:n,number:i}},r){if(e._assign=getModelAssigner(r),e.composing)return;if(document.activeElement===e){if(o)return;if(n&&e.value.trim()===t)return;if((i||\"number\"===e.type)&&toNumber(e.value)===t)return}const s=null==t?\"\":t;e.value!==s&&(e.value=s)}},vModelCheckbox={deep:!0,created(e,t,o){e._assign=getModelAssigner(o),addEventListener$1(e,\"change\",(()=>{const t=e._modelValue,o=getValue(e),n=e.checked,i=e._assign;if(isArray(t)){const e=looseIndexOf(t,o),r=-1!==e;if(n&&!r)i(t.concat(o));else if(!n&&r){const o=[...t];o.splice(e,1),i(o)}}else if(isSet(t)){const e=new Set(t);n?e.add(o):e.delete(o),i(e)}else i(getCheckboxValue(e,n))}))},mounted:setChecked,beforeUpdate(e,t,o){e._assign=getModelAssigner(o),setChecked(e,t,o)}};function setChecked(e,{value:t,oldValue:o},n){e._modelValue=t,isArray(t)?e.checked=looseIndexOf(t,n.props.value)>-1:isSet(t)?e.checked=t.has(n.props.value):t!==o&&(e.checked=looseEqual(t,getCheckboxValue(e,!0)))}function getValue(e){return\"_value\"in e?e._value:e.value}function getCheckboxValue(e,t){const o=t?\"_trueValue\":\"_falseValue\";return o in e?e[o]:t}const systemModifiers=[\"ctrl\",\"shift\",\"alt\",\"meta\"],modifierGuards={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>\"button\"in e&&0!==e.button,middle:e=>\"button\"in e&&1!==e.button,right:e=>\"button\"in e&&2!==e.button,exact:(e,t)=>systemModifiers.some((o=>e[`${o}Key`]&&!t.includes(o)))},withModifiers=(e,t)=>(o,...n)=>{for(let e=0;e<t.length;e++){const n=modifierGuards[t[e]];if(n&&n(o,t))return}return e(o,...n)},vShow={beforeMount(e,{value:t},{transition:o}){e._vod=\"none\"===e.style.display?\"\":e.style.display,o&&t?o.beforeEnter(e):setDisplay(e,t)},mounted(e,{value:t},{transition:o}){o&&t&&o.enter(e)},updated(e,{value:t,oldValue:o},{transition:n}){!t!=!o&&(n?t?(n.beforeEnter(e),setDisplay(e,!0),n.enter(e)):n.leave(e,(()=>{setDisplay(e,!1)})):setDisplay(e,t))},beforeUnmount(e,{value:t}){setDisplay(e,t)}};function setDisplay(e,t){e.style.display=t?e._vod:\"none\"}const rendererOptions=extend({patchProp:patchProp},nodeOps);let renderer;function ensureRenderer(){return renderer||(renderer=createRenderer(rendererOptions))}const createApp=(...e)=>{const t=ensureRenderer().createApp(...e),{mount:o}=t;return t.mount=e=>{const n=normalizeContainer(e);if(!n)return;const i=t._component;isFunction(i)||i.render||i.template||(i.template=n.innerHTML),n.innerHTML=\"\";const r=o(n,!1,n instanceof SVGElement);return n instanceof Element&&(n.removeAttribute(\"v-cloak\"),n.setAttribute(\"data-v-app\",\"\")),r},t};function normalizeContainer(e){if(isString(e)){return document.querySelector(e)}return e}class EventEmitter{constructor(){this._events={}}on(e,t){const o=this._events[e]||[];o.push(t),this._events[e]=o}emit(e,...t){(this._events[e]||[]).forEach((e=>e(...t)))}once(e,t){const o=(...n)=>{t(...n),this.off(e,o)};o.initialCallback=t,this.on(e,o)}off(e,t){const o=(this._events[e]||[]).filter((e=>e!=t&&e.initialCallback!=t));this._events[e]=o}}const getPartUrlByParam=(e,t)=>/^(?:([A-Za-z]+):)?(\\/{0,3})([0-9.\\-A-Za-z]+)(?::(\\d+))?(?:\\/([^?#]*))?(?:\\?([^#]*))?(?:#(.*))?$/.exec(e)[[\"url\",\"scheme\",\"slash\",\"host\",\"port\",\"path\",\"query\",\"hash\"].indexOf(t)],getQueryMap=e=>{if(!e)return null;let t={};return e.split(\"&\").forEach((e=>{e&&(t[e.split(\"=\")[0]]=e.split(\"=\")[1])})),t},$bus$1=new EventEmitter,guid$1=function(){function e(){return(65536*(1+Math.random())|0).toString(16).substring(1)}return e()+e()+\"-\"+e()+\"-\"+e()+\"-\"+e()+\"-\"+e()+e()+e()},HTTP_STATUS_CODES={100:\"Continue\",101:\"Switching Protocols\",200:\"OK\",201:\"Created\",202:\"Accepted\",203:\"Non-Authoritative Information\",204:\"No Content\",205:\"Reset Content\",206:\"Partial Content\",300:\"Multiple Choice\",301:\"Moved Permanently\",302:\"Found\",303:\"See Other\",304:\"Not Modified\",305:\"Use Proxy\",307:\"Temporary Redirect\",400:\"Bad Request\",401:\"Unauthorized\",402:\"Payment Required\",403:\"Forbidden\",404:\"Not Found\",405:\"Method Not Allowed\",406:\"Not Acceptable\",407:\"Proxy Authentication Required\",408:\"Request Timeout\",409:\"Conflict\",410:\"Gone\",411:\"Length Required\",412:\"Precondition Failed\",413:\"Request Entity Too Large\",414:\"Request-URI Too Long\",415:\"Unsupported Media Type\",416:\"Requested Range Not Satisfiable\",417:\"Expectation Failed\",422:\"Unprocessable Entity\",500:\"Internal Server Error\",501:\"Not Implemented\",502:\"Bad Gateway\",503:\"Service Unavailable\",504:\"Gateway Timeout\",505:\"HTTP Version Not Supported\"},getQueryVariable=(e,t)=>{if(t.indexOf(\"?\")>=0)for(var o=(t?t.split(\"?\")[1]:window.location.search.substring(1)).split(\"&\"),n=0;n<o.length;n++){var i=o[n].split(\"=\");if(i[0]==e)return i[1]}return!1},strMapToObj=e=>{let t=Object.create(null);for(let[o,n]of e)t[o]=n;return t},completionUrlProtocol=e=>\"http:\"==e.substr(0,5).toLowerCase()||\"https:\"==e.substr(0,6).toLowerCase()?e:`${location.protocol}`+e,INIT_VALUE=9999;let MOUSE_DOWN_FLAG=!1;const DEFAULT_EL_CONF={name:\"\",opacity:1,left:\"\",top:\"\",safeBottom:0},dragable={mounted(e,t){e.config={...DEFAULT_EL_CONF,...t.value},e.dokitEntryLastX=INIT_VALUE,e.dokitEntryLastY=INIT_VALUE,e.style.position=\"fixed\",e.style.opacity=e.config.opacity,e.dokitPositionLeft=getDefaultX(e),e.dokitPositionTop=getDefaultY(e),e.style.top=`${e.dokitPositionTop}px`,e.style.left=`${e.dokitPositionLeft}px`,adjustPosition(e),e.ontouchstart=()=>{moveStart(e)},e.ontouchmove=t=>{t.preventDefault(),moving(e,t)},e.ontouchend=t=>{moveEnd(e)},e.onmousedown=t=>{t.preventDefault(),moveStart(e),MOUSE_DOWN_FLAG=!0},window.addEventListener(\"mousemove\",(t=>{MOUSE_DOWN_FLAG&&moving(e,t)})),window.addEventListener(\"mouseup\",(t=>{MOUSE_DOWN_FLAG&&(moveEnd(e),MOUSE_DOWN_FLAG=!1)})),window.addEventListener(\"resize\",(()=>{adjustPosition(e)}))}};function moveStart(e){e.style.opacity=1}function moving(e,t){let o=t.touches?t.touches[0]:t;if(e.dokitEntryLastX===INIT_VALUE)return e.dokitEntryLastX=o.clientX,void(e.dokitEntryLastY=o.clientY);e.dokitPositionTop+=o.clientY-e.dokitEntryLastY,e.dokitPositionLeft+=o.clientX-e.dokitEntryLastX,e.dokitEntryLastX=o.clientX,e.dokitEntryLastY=o.clientY,e.style.top=`${e.dokitPositionTop}px`,e.style.left=`${e.dokitPositionLeft}px`}function moveEnd(e,t){setTimeout((()=>{adjustPosition(e),e.config.name&&localStorage.setItem(`dokitPositionTop_${e.config.name}`,e.dokitPositionTop),e.config.name&&localStorage.setItem(`dokitPositionLeft_${e.config.name}`,e.dokitPositionLeft)}),100),e.dokitEntryLastX=INIT_VALUE,e.dokitEntryLastY=INIT_VALUE,e.style.opacity=e.config.opacity}function getDefaultX(e){let t=e.config.left||Math.round(window.innerWidth/2);return localStorage.getItem(`dokitPositionLeft_${e.config.name}`)?parseInt(localStorage.getItem(`dokitPositionLeft_${e.config.name}`)):t}function getDefaultY(e){let t=e.config.top||Math.round(window.innerHeight/2);return localStorage.getItem(`dokitPositionTop_${e.config.name}`)?parseInt(localStorage.getItem(`dokitPositionTop_${e.config.name}`)):t}function adjustPosition(e){e.dokitPositionLeft<0?(e.dokitPositionLeft=0,e.style.left=`${e.dokitPositionLeft}px`):e.dokitPositionLeft+e.getBoundingClientRect().width>window.innerWidth&&(e.dokitPositionLeft=window.innerWidth-e.getBoundingClientRect().width,e.style.left=`${e.dokitPositionLeft}px`),e.dokitPositionTop<0?(e.dokitPositionTop=0,e.style.top=`${e.dokitPositionTop}px`):e.dokitPositionTop+e.getBoundingClientRect().height+e.config.safeBottom>window.innerHeight&&(e.dokitPositionTop=window.innerHeight-e.getBoundingClientRect().height-e.config.safeBottom,e.style.top=`${e.dokitPositionTop}px`)}const mapTag=\"[object Map]\",setTag=\"[object Set]\",arrayTag=\"[object Array]\",objectTag=\"[object Object]\",argsTag=\"[object Arguments]\",boolTag=\"[object Boolean]\",dateTag=\"[object Date]\",numberTag=\"[object Number]\",stringTag=\"[object String]\",symbolTag=\"[object Symbol]\",errorTag=\"[object Error]\",regexpTag=\"[object RegExp]\",funcTag=\"[object Function]\",deepTag=[mapTag,setTag,arrayTag,objectTag,argsTag];function forEach(e,t){let o=-1;const n=e.length;for(;++o<n;)t(e[o],o);return e}function isObject(e){const t=typeof e;return null!==e&&(\"object\"===t||\"function\"===t)}function getType(e){return Object.prototype.toString.call(e)}function cloneSymbol(e){return Object(Symbol.prototype.valueOf.call(e))}function cloneReg(e){const t=new e.constructor(e.source,/\\w*$/.exec(e));return t.lastIndex=e.lastIndex,t}function cloneFunction(func){const bodyReg=/(?<={)(.|\\n)+(?=})/m,paramReg=/(?<=\\().+(?=\\)\\s+{)/,funcString=func.toString();if(func.prototype){const e=paramReg.exec(funcString),t=bodyReg.exec(funcString);if(t){if(e){const o=e[0].split(\",\");return new Function(...o,t[0])}return new Function(t[0])}return null}return eval(funcString)}function cloneOtherType(e,t){const o=e.constructor;switch(t){case boolTag:case numberTag:case stringTag:case errorTag:case dateTag:return new o(e);case regexpTag:return cloneReg(e);case symbolTag:return cloneSymbol(e);case funcTag:return cloneFunction(e);default:return null}}function allKey(e,t){let o=[];for(const i in e)o.push(i),e.propertyIsEnumerable(i)||n(t,i,e[i]);function n(e,t,o){Object.defineProperty(e,t,{enumerable:!0,get:()=>o,set(e){o=e}})}return o}const clone=(e,t=new WeakMap)=>{if(!isObject(e))return e;const o=getType(e);let n;if(!(deepTag.includes(o)||Object.prototype.isPrototypeOf(e)||Array.prototype.isPrototypeOf(e)))return cloneOtherType(e,o);if(n=Object.create(e.constructor.prototype),t.get(e))return t.get(e);if(t.set(e,n),o===setTag)return e.forEach((e=>{n.add(clone(e,t))})),n;if(o===mapTag)return e.forEach(((e,o)=>{n.set(o,clone(e,t))})),n;const i=Array.prototype.isPrototypeOf(n)?void 0:allKey(e,n);return forEach(i||e,((o,r)=>{i&&(r=o),Array.prototype.isPrototypeOf(n)?n.splice(r,1,clone(e[r],t)):n[r]=clone(e[r],t)})),n};var chrsz=8;function hex_md5(e){return binl2hex(core_md5(str2binl(e),e.length*chrsz))}function core_md5(e,t){e[t>>5]|=128<<t%32,e[14+(t+64>>>9<<4)]=t;for(var o=1732584193,n=-271733879,i=-1732584194,r=271733878,s=0;s<e.length;s+=16){var a=o,l=n,c=i,d=r;o=md5_ff(o,n,i,r,e[s+0],7,-680876936),r=md5_ff(r,o,n,i,e[s+1],12,-389564586),i=md5_ff(i,r,o,n,e[s+2],17,606105819),n=md5_ff(n,i,r,o,e[s+3],22,-1044525330),o=md5_ff(o,n,i,r,e[s+4],7,-176418897),r=md5_ff(r,o,n,i,e[s+5],12,1200080426),i=md5_ff(i,r,o,n,e[s+6],17,-1473231341),n=md5_ff(n,i,r,o,e[s+7],22,-45705983),o=md5_ff(o,n,i,r,e[s+8],7,1770035416),r=md5_ff(r,o,n,i,e[s+9],12,-1958414417),i=md5_ff(i,r,o,n,e[s+10],17,-42063),n=md5_ff(n,i,r,o,e[s+11],22,-1990404162),o=md5_ff(o,n,i,r,e[s+12],7,1804603682),r=md5_ff(r,o,n,i,e[s+13],12,-40341101),i=md5_ff(i,r,o,n,e[s+14],17,-1502002290),o=md5_gg(o,n=md5_ff(n,i,r,o,e[s+15],22,1236535329),i,r,e[s+1],5,-165796510),r=md5_gg(r,o,n,i,e[s+6],9,-1069501632),i=md5_gg(i,r,o,n,e[s+11],14,643717713),n=md5_gg(n,i,r,o,e[s+0],20,-373897302),o=md5_gg(o,n,i,r,e[s+5],5,-701558691),r=md5_gg(r,o,n,i,e[s+10],9,38016083),i=md5_gg(i,r,o,n,e[s+15],14,-660478335),n=md5_gg(n,i,r,o,e[s+4],20,-405537848),o=md5_gg(o,n,i,r,e[s+9],5,568446438),r=md5_gg(r,o,n,i,e[s+14],9,-1019803690),i=md5_gg(i,r,o,n,e[s+3],14,-187363961),n=md5_gg(n,i,r,o,e[s+8],20,1163531501),o=md5_gg(o,n,i,r,e[s+13],5,-1444681467),r=md5_gg(r,o,n,i,e[s+2],9,-51403784),i=md5_gg(i,r,o,n,e[s+7],14,1735328473),o=md5_hh(o,n=md5_gg(n,i,r,o,e[s+12],20,-1926607734),i,r,e[s+5],4,-378558),r=md5_hh(r,o,n,i,e[s+8],11,-2022574463),i=md5_hh(i,r,o,n,e[s+11],16,1839030562),n=md5_hh(n,i,r,o,e[s+14],23,-35309556),o=md5_hh(o,n,i,r,e[s+1],4,-1530992060),r=md5_hh(r,o,n,i,e[s+4],11,1272893353),i=md5_hh(i,r,o,n,e[s+7],16,-155497632),n=md5_hh(n,i,r,o,e[s+10],23,-1094730640),o=md5_hh(o,n,i,r,e[s+13],4,681279174),r=md5_hh(r,o,n,i,e[s+0],11,-358537222),i=md5_hh(i,r,o,n,e[s+3],16,-722521979),n=md5_hh(n,i,r,o,e[s+6],23,76029189),o=md5_hh(o,n,i,r,e[s+9],4,-640364487),r=md5_hh(r,o,n,i,e[s+12],11,-421815835),i=md5_hh(i,r,o,n,e[s+15],16,530742520),o=md5_ii(o,n=md5_hh(n,i,r,o,e[s+2],23,-995338651),i,r,e[s+0],6,-198630844),r=md5_ii(r,o,n,i,e[s+7],10,1126891415),i=md5_ii(i,r,o,n,e[s+14],15,-1416354905),n=md5_ii(n,i,r,o,e[s+5],21,-57434055),o=md5_ii(o,n,i,r,e[s+12],6,1700485571),r=md5_ii(r,o,n,i,e[s+3],10,-1894986606),i=md5_ii(i,r,o,n,e[s+10],15,-1051523),n=md5_ii(n,i,r,o,e[s+1],21,-2054922799),o=md5_ii(o,n,i,r,e[s+8],6,1873313359),r=md5_ii(r,o,n,i,e[s+15],10,-30611744),i=md5_ii(i,r,o,n,e[s+6],15,-1560198380),n=md5_ii(n,i,r,o,e[s+13],21,1309151649),o=md5_ii(o,n,i,r,e[s+4],6,-145523070),r=md5_ii(r,o,n,i,e[s+11],10,-1120210379),i=md5_ii(i,r,o,n,e[s+2],15,718787259),n=md5_ii(n,i,r,o,e[s+9],21,-343485551),o=safe_add(o,a),n=safe_add(n,l),i=safe_add(i,c),r=safe_add(r,d)}return Array(o,n,i,r)}function md5_cmn(e,t,o,n,i,r){return safe_add(bit_rol(safe_add(safe_add(t,e),safe_add(n,r)),i),o)}function md5_ff(e,t,o,n,i,r,s){return md5_cmn(t&o|~t&n,e,t,i,r,s)}function md5_gg(e,t,o,n,i,r,s){return md5_cmn(t&n|o&~n,e,t,i,r,s)}function md5_hh(e,t,o,n,i,r,s){return md5_cmn(t^o^n,e,t,i,r,s)}function md5_ii(e,t,o,n,i,r,s){return md5_cmn(o^(t|~n),e,t,i,r,s)}function safe_add(e,t){var o=(65535&e)+(65535&t);return(e>>16)+(t>>16)+(o>>16)<<16|65535&o}function bit_rol(e,t){return e<<t|e>>>32-t}function str2binl(e){for(var t=Array(),o=(1<<chrsz)-1,n=0;n<e.length*chrsz;n+=chrsz)t[n>>5]|=(e.charCodeAt(n/chrsz)&o)<<n%32;return t}function binl2hex(e){for(var t=\"0123456789abcdef\",o=\"\",n=0;n<4*e.length;n++)o+=t.charAt(e[n>>2]>>n%4*8+4&15)+t.charAt(e[n>>2]>>n%4*8&15);return o}class Request extends EventEmitter{constructor(){super(),this.hookFetchConfig={},this.hookXhrConfig={},this.multiControlhookConfig={},this.initialize()}initialize(){let e=this;const t=window.XMLHttpRequest.prototype,o=t.send,n=t.open,i=t.setRequestHeader,r=t.getResponseHeader,s=t.getAllResponseHeaders;window.XMLHttpRequest.prototype.setRequestHeader=function(...t){this.reqConf.requestHeaders||(this.reqConf.requestHeaders={}),this.reqConf.requestHeaders[t[0]]=t[1],e.hookXhrConfig.onBeforeSetRequestHeader?(t=e.hookXhrConfig.onBeforeSetRequestHeader(t,this.reqConf))&&i.apply(this,t):i.apply(this,t)},window.addEventListener(\"xhrSendStart\",(function(e){console.log(\"xhrSendStart\",e)})),window.XMLHttpRequest.prototype.open=function(...t){console.log(\"open\",t);let o={...t};t=e.hookXhrConfig.onBeforeOpen&&e.hookXhrConfig.onBeforeOpen(t)||t;const i=this;this.reqConf={id:guid$1(),type:\"xhr\",requestInfo:{method:t[0].toUpperCase(),url:completionUrlProtocol(t[1])},originRequestInfo:{method:o[0].toUpperCase(),url:completionUrlProtocol(o[1])}},i.addEventListener(\"readystatechange\",(function(t){try{switch(i.readyState){case 2:e.multiControlhookConfig?.xhrHostRequest?.call(e,i);break;case 4:e.handleDone(i),e.multiControlhookConfig?.xhrHostResponse?.call(e,i)}}catch(e){console.log(e)}})),i.addEventListener(\"error\",(function(t){e.emit(\"REQUEST.ERROR\",{id:this.reqConf.id,responseInfo:{type:\"error\"}})})),i.getResponseHeader=t=>e.multiControlhookConfig?.getResponseHeader?e.multiControlhookConfig?.getResponseHeader.call(this,r,t):r.call(this,t),i.getAllResponseHeaders=()=>e.multiControlhookConfig?.getAllResponseHeaders?e.multiControlhookConfig?.getAllResponseHeaders.apply(this,[s,...arguments]):s.call(this,t),n.apply(this,t)},window.XMLHttpRequest.prototype.send=function(){try{arguments.length&&(this.reqConf.requestInfo.body=arguments[0]),e.multiControlhookConfig?.xhrClientQuery?.apply(this,[o,...arguments]),e.emit(\"REQUEST.SEND\",this.reqConf),e.multiControlhookConfig?.isOriginSend?e.multiControlhookConfig?.isOriginSend.apply(this,[o,...arguments]):o.apply(this,arguments)}catch(e){console.log(e)}};const a=window.fetch;window.fetch=async function(...t){try{let o=guid$1(),n=guid$1(),i=guid$1(),r=null;if(console.log(\"fetchHostRequest:\",e.multiControlhookConfig?.fetchHostRequest),e.multiControlhookConfig?.fetchHostRequest?.apply(e,[o,n,...arguments]),r=e.multiControlhookConfig?.fetchResult?.apply(e,[n,i,a,...arguments]),e.multiControlhookConfig?.fetchClientQuery?.apply(e,[n,...arguments]),t=e.hookFetchConfig.onBeforeFetch&&e.hookFetchConfig.onBeforeFetch(t)||t,e.emit(\"REQUEST.SEND\",{id:i,type:\"fetch\",requestInfo:{url:t[0],method:(t.length>1&&t[1].method||\"get\").toUpperCase(),headers:t.length>1&&t[1].headers||{},body:t.length>1&&t[1].body||\"\"}}),!r){r=a(...t);const n=await r,s=await n.clone().text();e.multiControlhookConfig?.fetchHostResponse?.apply(e,[o,n,s]),e.emit(\"REQUEST.DONE\",{id:i,responseInfo:{contentType:n.headers.get(\"Content-Type\"),status:n.status,resRaw:s}})}return r}catch(e){console.error(e)}}}hookXhr(e){this.hookXhrConfig=e}hookFetch(e){this.hookFetchConfig=e}getType(e){if(!e)return{type:\"unknown\",subType:\"unknown\"};const t=e.split(\";\")[0].split(\"/\");return{type:t[0],subType:t[t.length-1]}}readBlobAsText(e,t){const o=new FileReader;o.onload=()=>{t(null,o.result)},o.onerror=e=>{t(e)},o.readAsText(e)}handleDone(e){const t=e.responseType;let o=\"\";const n=()=>{this.emit(\"REQUEST.DONE\",{id:e.reqConf.id,responseInfo:{resRaw:o,contentType:e.getResponseHeader(\"Content-Type\"),status:e.status}})},i=this.getType(e.getResponseHeader(\"Content-Type\")||\"\");\"blob\"!==t||\"text\"!==i.type&&\"javascript\"!==i.subType&&\"json\"!==i.subType?(\"\"!==t&&\"text\"!==t||(o=e.responseText),\"json\"===t&&(o=JSON.stringify(e.response)),n()):this.readBlobAsText(e.response,((e,t)=>{t&&(o=t),n()}))}multiControlhook(e){this.multiControlhookConfig=e}}new EventEmitter;const R=()=>{var e=URL.createObjectURL(new Blob),t=e.toString();return URL.revokeObjectURL(e),t.substr(t.lastIndexOf(\"/\")+1)},L$1=(e,t,o)=>{var n=null,i=null;return function(...r){var s=+new Date,a=this;n||(n=s),s-n>o?(clearTimeout(i),e.apply(a,r),n=s):(clearTimeout(i),i=setTimeout((function(){e.apply(a,r)}),t))}};function P$1(e,t){let o=0;return(...n)=>{let i=+new Date;i-o>t&&(e.apply(this,n),o=i)}}const F$1={click:{actionType:\"ON_CLICK\",actionName:\"点击\"},dblclick:{actionType:\"ON_DBL_CLICK\",actionName:\"双击\"},touchstart:{actionType:\"ON_TOUCH_START\",actionName:\"触摸开始\"},touchmove:{actionType:\"ON_TOUCH_MOVE\",actionName:\"触摸移动\"},touchend:{actionType:\"ON_TOUCH_END\",actionName:\"触摸结束\"},input:{actionType:\"ON_INPUT_CHANGE\",actionName:\"输入\"},scroll:{actionType:\"ON_SCROLL\",actionName:\"滚动\"}};var W={props:{title:{default:\"DoKit\"},canBack:{default:!0}},data:()=>({icon:\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAzCAMAAADIDVqJAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAXFQTFRFAAAANX7GNHzFM3zFM3zFNn3ENYDHNHzFM3zENIDHNH7INH3ENX3GNn7GM33ENYDFNYDGM4DINH7HM3zENH3HNHzENYDGM37FM33FNH3FNn3FNX7HN4DINHzEM33HNH7FNIDGNn7GM33ENX3FNXzHNoDENH7FNH3EM3zEN33INHzFNH3FM4DHNX3HNH3GNX7ENHzENHzFNYDFNH3FM33ENn3JM33GN4DINX7EM3zEM33GNH7FM33FM33FNIDFNH3GNX3KM37HNHzFN4DINX7FNH3EOHzHNH3GM3zFM4DGM33EM3zENHzENH3EM33FOoTFgID/NHzFOIDHM3zEM3zFNoPJM33FM33ENH3FNHzENYDL////NHzENH3FM4PFM3zFOYDGNH3FM3zFN3zINHzEM33FNoDJM3zFM33FNH3ENHzFM37FNH3ENH3FM33ENH7GNX7FM33ENH3FNH7FM37ENH7FM3zFNH3EM4jMM3zE////TdHL6gAAAHl0Uk5TAEPy+vA9RPb0QEX3P0fzPkg8Sfg7SjpL+fE5TThON082Ue81UjRT++4zVO0yVjFX/OwwWOsvWi5b6i1d/eksXitf6Cph5yli/ihk5ieh3x8CoiCg4CGf4Z3iIgGc4yOaJJnkJZjlJpaVk5KQj42LioiHhYSCgHOyD3AmCqsAAAABYktHRFt0vJU0AAAACXBIWXMAAABIAAAASABGyWs+AAABPUlEQVQ4y43UxVoCUBQE4Ctgd3cXKAYoKqioINiBrdjdXby9C+6c3ZzPWf+rE2MMS4bDmY4rk5qs7FQ6ObnU5OXDFFBTCFNUTE2JmFJqysqtcVRQUymmiprqGmtq66ipF9NATSOMq4maZidMCzWtMG3t1HSI6aSmq9sat4eaHpheLzV9YvqpGRi0xuenZghmOEDNiJhRasZggiFqxiesmQxTMyVmmpoZmEiUmlmYWJyauXmYBWoWYZaWqTEr1qRW1zhaT4jaUNQm1Na2onagdjW1B7V/wFXyEOroWFEnok4VdQZ1fqGoS6ira0XdpP4x+uStKL5EY+6gYpq6h4rEFfUgKqqoRyjlkI15ggqGFfUsKqSoFyjlTY15hfIFFPUmyq+odyilhIz5gHJ7FfUpyqOoLyilYo35/rFJ/Jo/DZ3bT7fEcIgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDQtMjFUMTc6MzI6MjgrMDg6MDBBnT5hAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA0LTIxVDE3OjMyOjI4KzA4OjAwMMCG3QAAAABJRU5ErkJggg==\"}),methods:{handleBackRoute(){this.$emit(\"back\")||this.$router.back()}}};const U={class:\"dokit-bar\"},I=[\"src\"],z=(e=>(pushScopeId(\"data-v-d48b84c4\"),e=e(),popScopeId(),e))((()=>createBaseVNode(\"span\",{class:\"dokit-bar-back-btn\"},\"返回\",-1))),G={class:\"dokit-bar-title\"},j={class:\"dokit-bar-title-text\"};function V(e,t){void 0===t&&(t={});var o=t.insertAt;if(e&&\"undefined\"!=typeof document){var n=document.head||document.getElementsByTagName(\"head\")[0],i=document.createElement(\"style\");i.type=\"text/css\",\"top\"===o&&n.firstChild?n.insertBefore(i,n.firstChild):n.appendChild(i),i.styleSheet?i.styleSheet.cssText=e:i.appendChild(document.createTextNode(e))}}V(\".dokit-bar[data-v-d48b84c4] {\\n  background-color: white;\\n  height: 50px;\\n  width: 100%;\\n  display: flex;\\n  justify-content: center;\\n  align-items: center;\\n  padding: 0 10px;\\n  box-sizing: border-box;\\n  position: relative;\\n  border-radius: 10px 10px 0 0;\\n}\\n.dokit-bar-back[data-v-d48b84c4] {\\n  position: absolute;\\n  left: 10px;\\n  display: flex;\\n  flex-direction: row;\\n  align-items: center;\\n}\\n.dokit-bar-back-icon[data-v-d48b84c4] {\\n  display: inline-block;\\n  height: 18px;\\n}\\n.dokit-bar-back-btn[data-v-d48b84c4] {\\n  color: #337CC4;\\n  font-size: 16px;\\n  margin-left: 5px;\\n}\\n.dokit-bar-title-text[data-v-d48b84c4] {\\n  color: #333333;\\n  font-size: 20px;\\n  font-weight: bold;\\n}\\n.dokit-bar-other-text[data-v-d48b84c4] {\\n  color: #666666;\\n  font-size: 16px;\\n}\\n\"),W.render=function(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",U,[withDirectives(createBaseVNode(\"div\",{class:\"dokit-bar-back\",onClick:t[0]||(t[0]=(...e)=>r.handleBackRoute&&r.handleBackRoute(...e))},[createBaseVNode(\"img\",{class:\"dokit-bar-back-icon\",src:i.icon},null,8,I),z],512),[[vShow,o.canBack]]),createBaseVNode(\"div\",G,[createBaseVNode(\"span\",j,toDisplayString(o.title),1)])])},W.__scopeId=\"data-v-d48b84c4\";var $={components:{TopBar:W},data:()=>({}),computed:{component(){return this.$route.component},title(){return this.$route.meta&&this.$route.meta.title||\"DoKit\"},canBack(){return\"home\"!==this.$route.name}}};const X={class:\"dokit-container\"},B={class:\"dokit-router-container\"};V(\".dokit-container[data-v-6cd4753b] {\\n  position: fixed;\\n  left: 0;\\n  right: 0;\\n  top: 100px;\\n  bottom: 0;\\n  background-color: #f5f6f7;\\n  display: flex;\\n  flex-direction: column;\\n  z-index: 999;\\n  border-radius: 10px 10px 0 0;\\n}\\n.dokit-router-container[data-v-6cd4753b] {\\n  margin-top: 5px;\\n  background-color: white;\\n  flex: 1;\\n  overflow-y: scroll;\\n}\\n\"),$.render=function(e,t,o,n,i,r){const s=resolveComponent(\"top-bar\");return openBlock(),createElementBlock(\"div\",X,[createVNode(s,{title:r.title,canBack:r.canBack},null,8,[\"title\",\"canBack\"]),createBaseVNode(\"div\",B,[(openBlock(),createBlock(KeepAlive,null,[(openBlock(),createBlock(resolveDynamicComponent(r.component)))],1024))])])},$.__scopeId=\"data-v-6cd4753b\";var q={components:{},data:()=>({}),computed:{independPlugins(){return this.$store.state.independPlugins}},methods:{toRaw:toRaw}};const Z={class:\"dokit-container\",style:{\"z-index\":\"998\"}};V(\".dokit-container[data-v-6b9cd332] {\\n  position: absolute;\\n  left: 0;\\n  right: 0;\\n  bottom: 0;\\n  top: 0;\\n}\\n\"),q.render=function(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",Z,[(openBlock(!0),createElementBlock(Fragment,null,renderList(r.independPlugins,(e=>(openBlock(),createBlock(resolveDynamicComponent(r.toRaw(e.component)),{key:e.name})))),128))])},q.__scopeId=\"data-v-6b9cd332\";var J={props:{element:Object},data:()=>({}),computed:{highlightnNode(){return this.element?.__dokitForWeb_node},isOverfllow(){return this.element.getBoundingClientRect().top+this.highlightnNode.boxStyle.marginTop<25?\"\\n          top: 0px;\\n          left: 0px;\\n        \":`\\n          top: ${-25-this.highlightnNode.boxStyle.marginTop}px;\\n          left: ${-this.highlightnNode.boxStyle.marginLeft}px;\\n        `},contentLeft(){return`${parseInt(this.highlightnNode.boxStyle.paddingLeft)+parseInt(this.highlightnNode.boxStyle.borderLeftWidth)}px`},contentTop(){return`${parseInt(this.highlightnNode.boxStyle.paddingTop)+parseInt(this.highlightnNode.boxStyle.borderTopWidth)}px`}}};const K={class:\"dokit-elements-indicator\"},Q={class:\"nodeName\"},tt={key:0,class:\"nodeaId\"},et={key:1,class:\"nodeaClass\"};V(\".dokit-elements-highlight[data-v-c7fa2f80] {\\n  position: absolute;\\n  z-index: -100;\\n  pointer-events: none !important;\\n}\\n.dokit-elements-highlight .dokit-elements-indicator[data-v-c7fa2f80] {\\n  position: absolute;\\n  left: 0;\\n  right: 0;\\n  width: 100%;\\n  height: 100%;\\n}\\n.dokit-elements-highlight .dokit-elements-indicator > *[data-v-c7fa2f80] {\\n  pointer-events: none !important;\\n}\\n.dokit-elements-highlight .dokit-elements-indicator .dokit-margin[data-v-c7fa2f80],\\n.dokit-elements-highlight .dokit-elements-indicator .dokit-border[data-v-c7fa2f80],\\n.dokit-elements-highlight .dokit-elements-indicator .dokit-padding[data-v-c7fa2f80],\\n.dokit-elements-highlight .dokit-elements-indicator .dokit-content[data-v-c7fa2f80] {\\n  position: absolute;\\n}\\n.dokit-elements-highlight .dokit-elements-indicator .dokit-border[data-v-c7fa2f80] {\\n  box-sizing: border-box;\\n}\\n.dokit-elements-highlight .dokit-elements-size[data-v-c7fa2f80] {\\n  position: absolute;\\n  background: #fff;\\n  color: #222;\\n  font-size: 12px;\\n  height: 25px;\\n  line-height: 25px;\\n  text-align: center;\\n  padding: 0 5px;\\n  white-space: nowrap;\\n  overflow-x: hidden;\\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.05), 0 1px 4px 0 rgba(0, 0, 0, 0.08), 0 3px 1px -2px rgba(0, 0, 0, 0.2);\\n}\\n.dokit-elements-highlight .dokit-elements-size .nodeName[data-v-c7fa2f80] {\\n  color: #881280;\\n}\\n.dokit-elements-highlight .dokit-elements-size .nodeaId[data-v-c7fa2f80] {\\n  color: #1a1aa8;\\n}\\n.dokit-elements-highlight .dokit-elements-size .nodeaClass[data-v-c7fa2f80] {\\n  color: #8f4919;\\n}\\n\"),J.render=function(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",{class:\"dokit-elements-highlight\",style:normalizeStyle(`\\n          top: ${o.element.getBoundingClientRect().top}px;\\n          left: ${o.element.getBoundingClientRect().left}px;\\n        `)},[createBaseVNode(\"div\",K,[createBaseVNode(\"div\",{class:\"dokit-margin\",style:normalizeStyle(`\\n          left: -${r.highlightnNode.boxStyle.marginLeft}px;\\n          top: -${r.highlightnNode.boxStyle.marginTop}px;\\n          width: ${o.element.offsetWidth}px;\\n          height: ${o.element.offsetHeight}px;\\n          border-width: ${r.highlightnNode.boxStyle.marginTop}px ${r.highlightnNode.boxStyle.marginRight}px ${r.highlightnNode.boxStyle.marginBottom}px ${r.highlightnNode.boxStyle.marginLeft}px;\\n          border-style: solid;\\n          border-color: rgba(246, 178, 107, 0.66);\\n        `)},null,4),createBaseVNode(\"div\",{class:\"dokit-border\",style:normalizeStyle(`\\n          left: 0px;\\n          top:0px;\\n          width: ${o.element.offsetWidth}px;\\n          height: ${o.element.offsetHeight}px;\\n          border-width: ${r.highlightnNode.boxStyle.borderTopWidth}px ${r.highlightnNode.boxStyle.borderRightWidth}px ${r.highlightnNode.boxStyle.borderBottomWidth}px ${r.highlightnNode.boxStyle.borderLeftWidth}px;\\n          border-style: solid;\\n          border-color: rgba(255, 229, 153, 0.66);\\n        `)},null,4),createBaseVNode(\"div\",{class:\"dokit-padding\",style:normalizeStyle(`\\n          left: ${r.highlightnNode.boxStyle.borderLeftWidth}px;\\n          top: ${r.highlightnNode.boxStyle.borderTopWidth}px;\\n          width: ${r.highlightnNode.boxStyle.contentWidth}px;\\n          height: ${r.highlightnNode.boxStyle.contentHeight}px;\\n          border-width: ${r.highlightnNode.boxStyle.paddingTop}px ${r.highlightnNode.boxStyle.paddingRight}px ${r.highlightnNode.boxStyle.paddingBottom}px ${r.highlightnNode.boxStyle.paddingLeft}px;\\n          border-style: solid;\\n          border-color: rgba(147, 196, 125, 0.55);\\n        `)},null,4),createBaseVNode(\"div\",{class:\"dokit-content\",style:normalizeStyle(`\\n          left: ${r.contentLeft};\\n          top: ${r.contentTop};\\n          width: ${r.highlightnNode.boxStyle.contentWidth}px;\\n          height: ${r.highlightnNode.boxStyle.contentHeight}px;\\n          background: rgba(111, 168, 220, 0.66);\\n        `)},null,4)]),createBaseVNode(\"div\",{class:\"dokit-elements-size\",style:normalizeStyle(r.isOverfllow)},[createBaseVNode(\"span\",Q,toDisplayString(o.element.nodeName.toLowerCase()),1),\"\"!==o.element.id?(openBlock(),createElementBlock(\"span\",tt,toDisplayString(`#${o.element.id}`),1)):createCommentVNode(\"\",!0),\"\"!==o.element.className?(openBlock(),createElementBlock(\"span\",et,toDisplayString(`.${o.element.className}`),1)):createCommentVNode(\"\",!0),createBaseVNode(\"span\",null,\" | \"+toDisplayString(o.element.offsetWidth)+\" × \"+toDisplayString(o.element.offsetHeight),1)],4)],4)},J.__scopeId=\"data-v-c7fa2f80\";var ot={directives:{dragable:dragable},data:()=>({isHost:!1,btnConfig:{name:\"dokit_multi_control\",opacity:.5,left:window.innerWidth-50,top:.2*window.innerHeight,safeBottom:50,eventPlayback:null}}),computed:{isHost(){return this.$store.state.isHost}},methods:{change(){this.$store.state.isHost=!this.$store.state.isHost}}};V(\".dokit-masterSuspendedBall[data-v-697bd0cd] {\\n  z-index: 10000;\\n  font-size: 16px;\\n  width: 50px;\\n  height: 50px;\\n  border-radius: 50%;\\n  display: flex;\\n  align-items: center;\\n  justify-content: center;\\n  background-color: #ccc;\\n}\\n\"),ot.render=function(e,t,o,n,i,r){const s=resolveDirective(\"dragable\");return openBlock(),createElementBlock(\"div\",null,[withDirectives((openBlock(),createElementBlock(\"div\",{class:\"dokit-masterSuspendedBall\",onClick:t[0]||(t[0]=(...e)=>r.change&&r.change(...e))},[createTextVNode(toDisplayString(r.isHost?\"主机\":\"从机\"),1)])),[[s,i.btnConfig]])])},ot.__scopeId=\"data-v-697bd0cd\";const it=new class{constructor(e){let{state:t}=e;this.initData(t)}initData(e={}){this._state=reactive({data:e})}get state(){return this._state.data}install(e){e.provide(\"store\",this),e.config.globalProperties.$store=this}}({state:{showContainer:!1,showHighlightElement:!1,highlightElement:null,independPlugins:[],interfaceList:[],features:[],socketUrl:\"\",isHost:!1,socketConnect:!1,socketHistoryList:new Map,mcClientWaitRequestQueue:[],mcHostWaitRequestQueue:[],mcHostWaitResponseQueue:[],mcClientWaitFetchRequestQueue:[],mcHostWaitFetchRequestQueue:[],mcHostWaitFetchResponseQueue:[],isNative:!1}});function st(){return it.state}function lt(e){it.state.showContainer=e||!it.state.showContainer}function rt(e){it.state.showHighlightElement=e||!it.state.showHighlightElement}function at(e){it.state.highlightElement=e}function dt(e){it.state.independPlugins.findIndex((t=>t.name===e.name))>-1||it.state.independPlugins.push(e)}function ct(e){let t=it.state.independPlugins.findIndex((t=>t.name===e));-1!==t&&it.state.independPlugins.splice(t,1)}class ht{static _findRootDocument(e,t){return e.nodeType===Node.DOCUMENT_NODE?e:e===t?e.ownerDocument:e}static index(e){const t=e.parentNode;if(!t)return null;let o=t.firstChild;if(!o)return null;let n=0;for(;o&&(o.nodeType===Node.ELEMENT_NODE&&(n+=1),o!==e);)o=o.nextSibling;return n}static any(){return{name:\"*\",weight:1,type:\"any\"}}static nthChild(e,t){return{name:`${e.name}:nth-child(${t})`,penalty:e.weight+1,type:\"nth\"}}static filterNth(e){return\"html\"!==e.name&&!e.name.startsWith(\"#\")}constructor(e){const t={root:document.body,tagName:e=>!0};this._opts=Object.assign({},t,e),this._rootDoc=ht._findRootDocument(this._opts.root,t.root)}bubbleUp(e){const{root:t}=this._opts,o=[];let n=e,i=0;for(;n&&n!==t.parentElement;){let e=this.tagName(n)||ht.any();const t=ht.index(n);t&&\"html\"!==e.name&&(e=ht.nthChild(e,t)),e.level=i,o.push(e),n=n.parentElement,i+=1}return o}tagName(e){const t=e.tagName.toLowerCase(),{tagName:o}=this._opts;return o(t)?{name:t,weight:2,type:\"tag\"}:null}find(e){const t=this.bubbleUp(e).sort(((e,t)=>t.level-e.level)).reduce(((e,t)=>e+`${t.name}>`),\"\");return t.length?t.slice(0,t.length-1):t}}function ut(e){return new ht(e)}\n/*! https://mths.be/cssesc v3.0.0 by @mathias */var ft={}.hasOwnProperty,pt=/[ -,\\.\\/:-@\\[-\\^`\\{-~]/,mt=/[ -,\\.\\/:-@\\[\\]\\^`\\{-~]/,gt=/(^|\\\\+)?(\\\\[A-F0-9]{1,6})\\x20(?![a-fA-F0-9\\x20])/g,vt=function e(t,o){\"single\"!=(o=function(e,t){if(!e)return t;var o={};for(var n in t)o[n]=ft.call(e,n)?e[n]:t[n];return o}(o,e.options)).quotes&&\"double\"!=o.quotes&&(o.quotes=\"single\");for(var n=\"double\"==o.quotes?'\"':\"'\",i=o.isIdentifier,r=t.charAt(0),s=\"\",a=0,l=t.length;a<l;){var c=t.charAt(a++),d=c.charCodeAt(),u=void 0;if(d<32||d>126){if(d>=55296&&d<=56319&&a<l){var h=t.charCodeAt(a++);56320==(64512&h)?d=((1023&d)<<10)+(1023&h)+65536:a--}u=\"\\\\\"+d.toString(16).toUpperCase()+\" \"}else u=o.escapeEverything?pt.test(c)?\"\\\\\"+c:\"\\\\\"+d.toString(16).toUpperCase()+\" \":/[\\t\\n\\f\\r\\x0B]/.test(c)?\"\\\\\"+d.toString(16).toUpperCase()+\" \":\"\\\\\"==c||!i&&('\"'==c&&n==c||\"'\"==c&&n==c)||i&&mt.test(c)?\"\\\\\"+c:c;s+=u}return i&&(/^-[-\\d]/.test(s)?s=\"\\\\-\"+s.slice(1):/\\d/.test(r)&&(s=\"\\\\3\"+r+\" \"+s.slice(1))),s=s.replace(gt,(function(e,t,o){return t&&t.length%2?e:(t||\"\")+o})),!i&&o.wrap?n+s+n:s};vt.options={escapeEverything:!1,isIdentifier:!1,quotes:\"single\",wrap:!1},vt.version=\"3.0.0\";var yt=vt;let wt;!function(e){e[e.All=0]=\"All\",e[e.Two=1]=\"Two\",e[e.One=2]=\"One\"}(wt||(wt={}));var _t={CLICK:\"click\",DBLCLICK:\"dblclick\",TOUCHSTART:\"touchstart\",TOUCHMOVE:\"touchmove\",TOUCHEND:\"touchend\",INPUT:\"input\",SCROLL:\"scroll\"};const xt=(e,t=0,o=3)=>{if(t>o)return t;const n=Array.from(e.childNodes||[]);if(e.nodeType!==Node.ELEMENT_NODE)return t;if(0===n.length)return t;const i=n.map((e=>xt(e,t+1,o)));return Math.max(...i)},kt=(e,t=3)=>xt(e)>t?\"\":(e=>{switch(e.nodeType){case Node.ELEMENT_NODE:return e.innerHTML;case Node.TEXT_NODE:return e.nodeValue;default:return\"\"}})(e);function bt(){throw new Error(\"Dynamic requires are not currently supported by rollup-plugin-commonjs\")}var St,Tt,Mt,Nt=(St=function(e,t){e.exports=function(){var t,o;function n(){return t.apply(null,arguments)}function i(e){return e instanceof Array||\"[object Array]\"===Object.prototype.toString.call(e)}function r(e){return null!=e&&\"[object Object]\"===Object.prototype.toString.call(e)}function s(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function a(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;var t;for(t in e)if(s(e,t))return!1;return!0}function l(e){return void 0===e}function c(e){return\"number\"==typeof e||\"[object Number]\"===Object.prototype.toString.call(e)}function d(e){return e instanceof Date||\"[object Date]\"===Object.prototype.toString.call(e)}function u(e,t){var o,n=[];for(o=0;o<e.length;++o)n.push(t(e[o],o));return n}function h(e,t){for(var o in t)s(t,o)&&(e[o]=t[o]);return s(t,\"toString\")&&(e.toString=t.toString),s(t,\"valueOf\")&&(e.valueOf=t.valueOf),e}function p(e,t,o,n){return Bt(e,t,o,n,!0).utc()}function f(e){return null==e._pf&&(e._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidEra:null,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],era:null,meridiem:null,rfc2822:!1,weekdayMismatch:!1}),e._pf}function m(e){if(null==e._isValid){var t=f(e),n=o.call(t.parsedDateParts,(function(e){return null!=e})),i=!isNaN(e._d.getTime())&&t.overflow<0&&!t.empty&&!t.invalidEra&&!t.invalidMonth&&!t.invalidWeekday&&!t.weekdayMismatch&&!t.nullInput&&!t.invalidFormat&&!t.userInvalidated&&(!t.meridiem||t.meridiem&&n);if(e._strict&&(i=i&&0===t.charsLeftOver&&0===t.unusedTokens.length&&void 0===t.bigHour),null!=Object.isFrozen&&Object.isFrozen(e))return i;e._isValid=i}return e._isValid}function g(e){var t=p(NaN);return null!=e?h(f(t),e):f(t).userInvalidated=!0,t}o=Array.prototype.some?Array.prototype.some:function(e){var t,o=Object(this),n=o.length>>>0;for(t=0;t<n;t++)if(t in o&&e.call(this,o[t],t,o))return!0;return!1};var v=n.momentProperties=[],k=!1;function y(e,t){var o,n,i;if(l(t._isAMomentObject)||(e._isAMomentObject=t._isAMomentObject),l(t._i)||(e._i=t._i),l(t._f)||(e._f=t._f),l(t._l)||(e._l=t._l),l(t._strict)||(e._strict=t._strict),l(t._tzm)||(e._tzm=t._tzm),l(t._isUTC)||(e._isUTC=t._isUTC),l(t._offset)||(e._offset=t._offset),l(t._pf)||(e._pf=f(t)),l(t._locale)||(e._locale=t._locale),v.length>0)for(o=0;o<v.length;o++)l(i=t[n=v[o]])||(e[n]=i);return e}function w(e){y(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===k&&(k=!0,n.updateOffset(this),k=!1)}function A(e){return e instanceof w||null!=e&&null!=e._isAMomentObject}function C(e){!1===n.suppressDeprecationWarnings&&\"undefined\"!=typeof console&&console.warn&&console.warn(\"Deprecation warning: \"+e)}function b(e,t){var o=!0;return h((function(){if(null!=n.deprecationHandler&&n.deprecationHandler(null,e),o){var i,r,a,l=[];for(r=0;r<arguments.length;r++){if(i=\"\",\"object\"==typeof arguments[r]){for(a in i+=\"\\n[\"+r+\"] \",arguments[0])s(arguments[0],a)&&(i+=a+\": \"+arguments[0][a]+\", \");i=i.slice(0,-2)}else i=arguments[r];l.push(i)}C(e+\"\\nArguments: \"+Array.prototype.slice.call(l).join(\"\")+\"\\n\"+(new Error).stack),o=!1}return t.apply(this,arguments)}),t)}var _,x={};function S(e,t){null!=n.deprecationHandler&&n.deprecationHandler(e,t),x[e]||(C(t),x[e]=!0)}function B(e){return\"undefined\"!=typeof Function&&e instanceof Function||\"[object Function]\"===Object.prototype.toString.call(e)}function L(e,t){var o,n=h({},e);for(o in t)s(t,o)&&(r(e[o])&&r(t[o])?(n[o]={},h(n[o],e[o]),h(n[o],t[o])):null!=t[o]?n[o]=t[o]:delete n[o]);for(o in e)s(e,o)&&!s(t,o)&&r(e[o])&&(n[o]=h({},n[o]));return n}function T(e){null!=e&&this.set(e)}n.suppressDeprecationWarnings=!1,n.deprecationHandler=null,_=Object.keys?Object.keys:function(e){var t,o=[];for(t in e)s(e,t)&&o.push(t);return o};function E(e,t,o){var n=\"\"+Math.abs(e),i=t-n.length;return(e>=0?o?\"+\":\"\":\"-\")+Math.pow(10,Math.max(0,i)).toString().substr(1)+n}var N=/(\\[[^\\[]*\\])|(\\\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,M=/(\\[[^\\[]*\\])|(\\\\)?(LTS|LT|LL?L?L?|l{1,4})/g,I={},P={};function O(e,t,o,n){var i=n;\"string\"==typeof n&&(i=function(){return this[n]()}),e&&(P[e]=i),t&&(P[t[0]]=function(){return E(i.apply(this,arguments),t[1],t[2])}),o&&(P[o]=function(){return this.localeData().ordinal(i.apply(this,arguments),e)})}function D(e){return e.match(/\\[[\\s\\S]/)?e.replace(/^\\[|\\]$/g,\"\"):e.replace(/\\\\/g,\"\")}function R(e,t){return e.isValid()?(t=V(t,e.localeData()),I[t]=I[t]||function(e){var t,o,n=e.match(N);for(t=0,o=n.length;t<o;t++)P[n[t]]?n[t]=P[n[t]]:n[t]=D(n[t]);return function(t){var i,r=\"\";for(i=0;i<o;i++)r+=B(n[i])?n[i].call(t,e):n[i];return r}}(t),I[t](e)):e.localeData().invalidDate()}function V(e,t){var o=5;function n(e){return t.longDateFormat(e)||e}for(M.lastIndex=0;o>=0&&M.test(e);)e=e.replace(M,n),M.lastIndex=0,o-=1;return e}var H={};function Y(e,t){var o=e.toLowerCase();H[o]=H[o+\"s\"]=H[t]=e}function F(e){return\"string\"==typeof e?H[e]||H[e.toLowerCase()]:void 0}function z(e){var t,o,n={};for(o in e)s(e,o)&&(t=F(o))&&(n[t]=e[o]);return n}var j={};function U(e,t){j[e]=t}function q(e){return e%4==0&&e%100!=0||e%400==0}function W(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function K(e){var t=+e,o=0;return 0!==t&&isFinite(t)&&(o=W(t)),o}function J(e,t){return function(o){return null!=o?(X(this,e,o),n.updateOffset(this,t),this):$(this,e)}}function $(e,t){return e.isValid()?e._d[\"get\"+(e._isUTC?\"UTC\":\"\")+t]():NaN}function X(e,t,o){e.isValid()&&!isNaN(o)&&(\"FullYear\"===t&&q(e.year())&&1===e.month()&&29===e.date()?(o=K(o),e._d[\"set\"+(e._isUTC?\"UTC\":\"\")+t](o,e.month(),Ce(o,e.month()))):e._d[\"set\"+(e._isUTC?\"UTC\":\"\")+t](o))}var Q,G=/\\d/,Z=/\\d\\d/,ee=/\\d{3}/,te=/\\d{4}/,oe=/[+-]?\\d{6}/,ne=/\\d\\d?/,ie=/\\d\\d\\d\\d?/,re=/\\d\\d\\d\\d\\d\\d?/,se=/\\d{1,3}/,ae=/\\d{1,4}/,le=/[+-]?\\d{1,6}/,ce=/\\d+/,de=/[+-]?\\d+/,ue=/Z|[+-]\\d\\d:?\\d\\d/gi,he=/Z|[+-]\\d\\d(?::?\\d\\d)?/gi,pe=/[0-9]{0,256}['a-z\\u00A0-\\u05FF\\u0700-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFF07\\uFF10-\\uFFEF]{1,256}|[\\u0600-\\u06FF\\/]{1,256}(\\s*?[\\u0600-\\u06FF]{1,256}){1,2}/i;function fe(e,t,o){Q[e]=B(t)?t:function(e,n){return e&&o?o:t}}function me(e,t){return s(Q,e)?Q[e](t._strict,t._locale):new RegExp(function(e){return ge(e.replace(\"\\\\\",\"\").replace(/\\\\(\\[)|\\\\(\\])|\\[([^\\]\\[]*)\\]|\\\\(.)/g,(function(e,t,o,n,i){return t||o||n||i})))}(e))}function ge(e){return e.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g,\"\\\\$&\")}Q={};var ve={};function ke(e,t){var o,n=t;for(\"string\"==typeof e&&(e=[e]),c(t)&&(n=function(e,o){o[t]=K(e)}),o=0;o<e.length;o++)ve[e[o]]=n}function ye(e,t){ke(e,(function(e,o,n,i){n._w=n._w||{},t(e,n._w,n,i)}))}function we(e,t,o){null!=t&&s(ve,e)&&ve[e](t,o._a,o,e)}var Ae;function Ce(e,t){if(isNaN(e)||isNaN(t))return NaN;var o=function(e,t){return(e%t+t)%t}(t,12);return e+=(t-o)/12,1===o?q(e)?29:28:31-o%7%2}Ae=Array.prototype.indexOf?Array.prototype.indexOf:function(e){var t;for(t=0;t<this.length;++t)if(this[t]===e)return t;return-1},O(\"M\",[\"MM\",2],\"Mo\",(function(){return this.month()+1})),O(\"MMM\",0,0,(function(e){return this.localeData().monthsShort(this,e)})),O(\"MMMM\",0,0,(function(e){return this.localeData().months(this,e)})),Y(\"month\",\"M\"),U(\"month\",8),fe(\"M\",ne),fe(\"MM\",ne,Z),fe(\"MMM\",(function(e,t){return t.monthsShortRegex(e)})),fe(\"MMMM\",(function(e,t){return t.monthsRegex(e)})),ke([\"M\",\"MM\"],(function(e,t){t[1]=K(e)-1})),ke([\"MMM\",\"MMMM\"],(function(e,t,o,n){var i=o._locale.monthsParse(e,n,o._strict);null!=i?t[1]=i:f(o).invalidMonth=e}));var be=\"January_February_March_April_May_June_July_August_September_October_November_December\".split(\"_\"),_e=\"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec\".split(\"_\"),xe=/D[oD]?(\\[[^\\[\\]]*\\]|\\s)+MMMM?/,Se=pe,Be=pe;function Le(e,t,o){var n,i,r,s=e.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],n=0;n<12;++n)r=p([2e3,n]),this._shortMonthsParse[n]=this.monthsShort(r,\"\").toLocaleLowerCase(),this._longMonthsParse[n]=this.months(r,\"\").toLocaleLowerCase();return o?\"MMM\"===t?-1!==(i=Ae.call(this._shortMonthsParse,s))?i:null:-1!==(i=Ae.call(this._longMonthsParse,s))?i:null:\"MMM\"===t?-1!==(i=Ae.call(this._shortMonthsParse,s))||-1!==(i=Ae.call(this._longMonthsParse,s))?i:null:-1!==(i=Ae.call(this._longMonthsParse,s))||-1!==(i=Ae.call(this._shortMonthsParse,s))?i:null}function Te(e,t){var o;if(!e.isValid())return e;if(\"string\"==typeof t)if(/^\\d+$/.test(t))t=K(t);else if(!c(t=e.localeData().monthsParse(t)))return e;return o=Math.min(e.date(),Ce(e.year(),t)),e._d[\"set\"+(e._isUTC?\"UTC\":\"\")+\"Month\"](t,o),e}function Ee(e){return null!=e?(Te(this,e),n.updateOffset(this,!0),this):$(this,\"Month\")}function Ne(){function e(e,t){return t.length-e.length}var t,o,n=[],i=[],r=[];for(t=0;t<12;t++)o=p([2e3,t]),n.push(this.monthsShort(o,\"\")),i.push(this.months(o,\"\")),r.push(this.months(o,\"\")),r.push(this.monthsShort(o,\"\"));for(n.sort(e),i.sort(e),r.sort(e),t=0;t<12;t++)n[t]=ge(n[t]),i[t]=ge(i[t]);for(t=0;t<24;t++)r[t]=ge(r[t]);this._monthsRegex=new RegExp(\"^(\"+r.join(\"|\")+\")\",\"i\"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp(\"^(\"+i.join(\"|\")+\")\",\"i\"),this._monthsShortStrictRegex=new RegExp(\"^(\"+n.join(\"|\")+\")\",\"i\")}function Me(e){return q(e)?366:365}O(\"Y\",0,0,(function(){var e=this.year();return e<=9999?E(e,4):\"+\"+e})),O(0,[\"YY\",2],0,(function(){return this.year()%100})),O(0,[\"YYYY\",4],0,\"year\"),O(0,[\"YYYYY\",5],0,\"year\"),O(0,[\"YYYYYY\",6,!0],0,\"year\"),Y(\"year\",\"y\"),U(\"year\",1),fe(\"Y\",de),fe(\"YY\",ne,Z),fe(\"YYYY\",ae,te),fe(\"YYYYY\",le,oe),fe(\"YYYYYY\",le,oe),ke([\"YYYYY\",\"YYYYYY\"],0),ke(\"YYYY\",(function(e,t){t[0]=2===e.length?n.parseTwoDigitYear(e):K(e)})),ke(\"YY\",(function(e,t){t[0]=n.parseTwoDigitYear(e)})),ke(\"Y\",(function(e,t){t[0]=parseInt(e,10)})),n.parseTwoDigitYear=function(e){return K(e)+(K(e)>68?1900:2e3)};var Ie=J(\"FullYear\",!0);function Pe(e,t,o,n,i,r,s){var a;return e<100&&e>=0?(a=new Date(e+400,t,o,n,i,r,s),isFinite(a.getFullYear())&&a.setFullYear(e)):a=new Date(e,t,o,n,i,r,s),a}function Oe(e){var t,o;return e<100&&e>=0?((o=Array.prototype.slice.call(arguments))[0]=e+400,t=new Date(Date.UTC.apply(null,o)),isFinite(t.getUTCFullYear())&&t.setUTCFullYear(e)):t=new Date(Date.UTC.apply(null,arguments)),t}function De(e,t,o){var n=7+t-o;return-(7+Oe(e,0,n).getUTCDay()-t)%7+n-1}function Re(e,t,o,n,i){var r,s,a=1+7*(t-1)+(7+o-n)%7+De(e,n,i);return a<=0?s=Me(r=e-1)+a:a>Me(e)?(r=e+1,s=a-Me(e)):(r=e,s=a),{year:r,dayOfYear:s}}function Ve(e,t,o){var n,i,r=De(e.year(),t,o),s=Math.floor((e.dayOfYear()-r-1)/7)+1;return s<1?n=s+He(i=e.year()-1,t,o):s>He(e.year(),t,o)?(n=s-He(e.year(),t,o),i=e.year()+1):(i=e.year(),n=s),{week:n,year:i}}function He(e,t,o){var n=De(e,t,o),i=De(e+1,t,o);return(Me(e)-n+i)/7}O(\"w\",[\"ww\",2],\"wo\",\"week\"),O(\"W\",[\"WW\",2],\"Wo\",\"isoWeek\"),Y(\"week\",\"w\"),Y(\"isoWeek\",\"W\"),U(\"week\",5),U(\"isoWeek\",5),fe(\"w\",ne),fe(\"ww\",ne,Z),fe(\"W\",ne),fe(\"WW\",ne,Z),ye([\"w\",\"ww\",\"W\",\"WW\"],(function(e,t,o,n){t[n.substr(0,1)]=K(e)}));function Ye(e,t){return e.slice(t,7).concat(e.slice(0,t))}O(\"d\",0,\"do\",\"day\"),O(\"dd\",0,0,(function(e){return this.localeData().weekdaysMin(this,e)})),O(\"ddd\",0,0,(function(e){return this.localeData().weekdaysShort(this,e)})),O(\"dddd\",0,0,(function(e){return this.localeData().weekdays(this,e)})),O(\"e\",0,0,\"weekday\"),O(\"E\",0,0,\"isoWeekday\"),Y(\"day\",\"d\"),Y(\"weekday\",\"e\"),Y(\"isoWeekday\",\"E\"),U(\"day\",11),U(\"weekday\",11),U(\"isoWeekday\",11),fe(\"d\",ne),fe(\"e\",ne),fe(\"E\",ne),fe(\"dd\",(function(e,t){return t.weekdaysMinRegex(e)})),fe(\"ddd\",(function(e,t){return t.weekdaysShortRegex(e)})),fe(\"dddd\",(function(e,t){return t.weekdaysRegex(e)})),ye([\"dd\",\"ddd\",\"dddd\"],(function(e,t,o,n){var i=o._locale.weekdaysParse(e,n,o._strict);null!=i?t.d=i:f(o).invalidWeekday=e})),ye([\"d\",\"e\",\"E\"],(function(e,t,o,n){t[n]=K(e)}));var Fe=\"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday\".split(\"_\"),ze=\"Sun_Mon_Tue_Wed_Thu_Fri_Sat\".split(\"_\"),je=\"Su_Mo_Tu_We_Th_Fr_Sa\".split(\"_\"),Ue=pe,qe=pe,We=pe;function Ke(e,t,o){var n,i,r,s=e.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],n=0;n<7;++n)r=p([2e3,1]).day(n),this._minWeekdaysParse[n]=this.weekdaysMin(r,\"\").toLocaleLowerCase(),this._shortWeekdaysParse[n]=this.weekdaysShort(r,\"\").toLocaleLowerCase(),this._weekdaysParse[n]=this.weekdays(r,\"\").toLocaleLowerCase();return o?\"dddd\"===t?-1!==(i=Ae.call(this._weekdaysParse,s))?i:null:\"ddd\"===t?-1!==(i=Ae.call(this._shortWeekdaysParse,s))?i:null:-1!==(i=Ae.call(this._minWeekdaysParse,s))?i:null:\"dddd\"===t?-1!==(i=Ae.call(this._weekdaysParse,s))||-1!==(i=Ae.call(this._shortWeekdaysParse,s))||-1!==(i=Ae.call(this._minWeekdaysParse,s))?i:null:\"ddd\"===t?-1!==(i=Ae.call(this._shortWeekdaysParse,s))||-1!==(i=Ae.call(this._weekdaysParse,s))||-1!==(i=Ae.call(this._minWeekdaysParse,s))?i:null:-1!==(i=Ae.call(this._minWeekdaysParse,s))||-1!==(i=Ae.call(this._weekdaysParse,s))||-1!==(i=Ae.call(this._shortWeekdaysParse,s))?i:null}function Je(){function e(e,t){return t.length-e.length}var t,o,n,i,r,s=[],a=[],l=[],c=[];for(t=0;t<7;t++)o=p([2e3,1]).day(t),n=ge(this.weekdaysMin(o,\"\")),i=ge(this.weekdaysShort(o,\"\")),r=ge(this.weekdays(o,\"\")),s.push(n),a.push(i),l.push(r),c.push(n),c.push(i),c.push(r);s.sort(e),a.sort(e),l.sort(e),c.sort(e),this._weekdaysRegex=new RegExp(\"^(\"+c.join(\"|\")+\")\",\"i\"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp(\"^(\"+l.join(\"|\")+\")\",\"i\"),this._weekdaysShortStrictRegex=new RegExp(\"^(\"+a.join(\"|\")+\")\",\"i\"),this._weekdaysMinStrictRegex=new RegExp(\"^(\"+s.join(\"|\")+\")\",\"i\")}function $e(){return this.hours()%12||12}function Xe(e,t){O(e,0,0,(function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)}))}function Qe(e,t){return t._meridiemParse}O(\"H\",[\"HH\",2],0,\"hour\"),O(\"h\",[\"hh\",2],0,$e),O(\"k\",[\"kk\",2],0,(function(){return this.hours()||24})),O(\"hmm\",0,0,(function(){return\"\"+$e.apply(this)+E(this.minutes(),2)})),O(\"hmmss\",0,0,(function(){return\"\"+$e.apply(this)+E(this.minutes(),2)+E(this.seconds(),2)})),O(\"Hmm\",0,0,(function(){return\"\"+this.hours()+E(this.minutes(),2)})),O(\"Hmmss\",0,0,(function(){return\"\"+this.hours()+E(this.minutes(),2)+E(this.seconds(),2)})),Xe(\"a\",!0),Xe(\"A\",!1),Y(\"hour\",\"h\"),U(\"hour\",13),fe(\"a\",Qe),fe(\"A\",Qe),fe(\"H\",ne),fe(\"h\",ne),fe(\"k\",ne),fe(\"HH\",ne,Z),fe(\"hh\",ne,Z),fe(\"kk\",ne,Z),fe(\"hmm\",ie),fe(\"hmmss\",re),fe(\"Hmm\",ie),fe(\"Hmmss\",re),ke([\"H\",\"HH\"],3),ke([\"k\",\"kk\"],(function(e,t,o){var n=K(e);t[3]=24===n?0:n})),ke([\"a\",\"A\"],(function(e,t,o){o._isPm=o._locale.isPM(e),o._meridiem=e})),ke([\"h\",\"hh\"],(function(e,t,o){t[3]=K(e),f(o).bigHour=!0})),ke(\"hmm\",(function(e,t,o){var n=e.length-2;t[3]=K(e.substr(0,n)),t[4]=K(e.substr(n)),f(o).bigHour=!0})),ke(\"hmmss\",(function(e,t,o){var n=e.length-4,i=e.length-2;t[3]=K(e.substr(0,n)),t[4]=K(e.substr(n,2)),t[5]=K(e.substr(i)),f(o).bigHour=!0})),ke(\"Hmm\",(function(e,t,o){var n=e.length-2;t[3]=K(e.substr(0,n)),t[4]=K(e.substr(n))})),ke(\"Hmmss\",(function(e,t,o){var n=e.length-4,i=e.length-2;t[3]=K(e.substr(0,n)),t[4]=K(e.substr(n,2)),t[5]=K(e.substr(i))}));var Ge=J(\"Hours\",!0);var Ze,et={calendar:{sameDay:\"[Today at] LT\",nextDay:\"[Tomorrow at] LT\",nextWeek:\"dddd [at] LT\",lastDay:\"[Yesterday at] LT\",lastWeek:\"[Last] dddd [at] LT\",sameElse:\"L\"},longDateFormat:{LTS:\"h:mm:ss A\",LT:\"h:mm A\",L:\"MM/DD/YYYY\",LL:\"MMMM D, YYYY\",LLL:\"MMMM D, YYYY h:mm A\",LLLL:\"dddd, MMMM D, YYYY h:mm A\"},invalidDate:\"Invalid date\",ordinal:\"%d\",dayOfMonthOrdinalParse:/\\d{1,2}/,relativeTime:{future:\"in %s\",past:\"%s ago\",s:\"a few seconds\",ss:\"%d seconds\",m:\"a minute\",mm:\"%d minutes\",h:\"an hour\",hh:\"%d hours\",d:\"a day\",dd:\"%d days\",w:\"a week\",ww:\"%d weeks\",M:\"a month\",MM:\"%d months\",y:\"a year\",yy:\"%d years\"},months:be,monthsShort:_e,week:{dow:0,doy:6},weekdays:Fe,weekdaysMin:je,weekdaysShort:ze,meridiemParse:/[ap]\\.?m?\\.?/i},tt={},ot={};function nt(e,t){var o,n=Math.min(e.length,t.length);for(o=0;o<n;o+=1)if(e[o]!==t[o])return o;return n}function it(e){return e?e.toLowerCase().replace(\"_\",\"-\"):e}function rt(t){var o=null;if(void 0===tt[t]&&e&&e.exports)try{o=Ze._abbr,bt(),st(o)}catch(e){tt[t]=null}return tt[t]}function st(e,t){var o;return e&&((o=l(t)?lt(e):at(e,t))?Ze=o:\"undefined\"!=typeof console&&console.warn&&console.warn(\"Locale \"+e+\" not found. Did you forget to load it?\")),Ze._abbr}function at(e,t){if(null!==t){var o,n=et;if(t.abbr=e,null!=tt[e])S(\"defineLocaleOverride\",\"use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info.\"),n=tt[e]._config;else if(null!=t.parentLocale)if(null!=tt[t.parentLocale])n=tt[t.parentLocale]._config;else{if(null==(o=rt(t.parentLocale)))return ot[t.parentLocale]||(ot[t.parentLocale]=[]),ot[t.parentLocale].push({name:e,config:t}),null;n=o._config}return tt[e]=new T(L(n,t)),ot[e]&&ot[e].forEach((function(e){at(e.name,e.config)})),st(e),tt[e]}return delete tt[e],null}function lt(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return Ze;if(!i(e)){if(t=rt(e))return t;e=[e]}return function(e){for(var t,o,n,i,r=0;r<e.length;){for(t=(i=it(e[r]).split(\"-\")).length,o=(o=it(e[r+1]))?o.split(\"-\"):null;t>0;){if(n=rt(i.slice(0,t).join(\"-\")))return n;if(o&&o.length>=t&&nt(i,o)>=t-1)break;t--}r++}return Ze}(e)}function ct(e){var t,o=e._a;return o&&-2===f(e).overflow&&(t=o[1]<0||o[1]>11?1:o[2]<1||o[2]>Ce(o[0],o[1])?2:o[3]<0||o[3]>24||24===o[3]&&(0!==o[4]||0!==o[5]||0!==o[6])?3:o[4]<0||o[4]>59?4:o[5]<0||o[5]>59?5:o[6]<0||o[6]>999?6:-1,f(e)._overflowDayOfYear&&(t<0||t>2)&&(t=2),f(e)._overflowWeeks&&-1===t&&(t=7),f(e)._overflowWeekday&&-1===t&&(t=8),f(e).overflow=t),e}var dt=/^\\s*((?:[+-]\\d{6}|\\d{4})-(?:\\d\\d-\\d\\d|W\\d\\d-\\d|W\\d\\d|\\d\\d\\d|\\d\\d))(?:(T| )(\\d\\d(?::\\d\\d(?::\\d\\d(?:[.,]\\d+)?)?)?)([+-]\\d\\d(?::?\\d\\d)?|\\s*Z)?)?$/,ut=/^\\s*((?:[+-]\\d{6}|\\d{4})(?:\\d\\d\\d\\d|W\\d\\d\\d|W\\d\\d|\\d\\d\\d|\\d\\d|))(?:(T| )(\\d\\d(?:\\d\\d(?:\\d\\d(?:[.,]\\d+)?)?)?)([+-]\\d\\d(?::?\\d\\d)?|\\s*Z)?)?$/,ht=/Z|[+-]\\d\\d(?::?\\d\\d)?/,pt=[[\"YYYYYY-MM-DD\",/[+-]\\d{6}-\\d\\d-\\d\\d/],[\"YYYY-MM-DD\",/\\d{4}-\\d\\d-\\d\\d/],[\"GGGG-[W]WW-E\",/\\d{4}-W\\d\\d-\\d/],[\"GGGG-[W]WW\",/\\d{4}-W\\d\\d/,!1],[\"YYYY-DDD\",/\\d{4}-\\d{3}/],[\"YYYY-MM\",/\\d{4}-\\d\\d/,!1],[\"YYYYYYMMDD\",/[+-]\\d{10}/],[\"YYYYMMDD\",/\\d{8}/],[\"GGGG[W]WWE\",/\\d{4}W\\d{3}/],[\"GGGG[W]WW\",/\\d{4}W\\d{2}/,!1],[\"YYYYDDD\",/\\d{7}/],[\"YYYYMM\",/\\d{6}/,!1],[\"YYYY\",/\\d{4}/,!1]],ft=[[\"HH:mm:ss.SSSS\",/\\d\\d:\\d\\d:\\d\\d\\.\\d+/],[\"HH:mm:ss,SSSS\",/\\d\\d:\\d\\d:\\d\\d,\\d+/],[\"HH:mm:ss\",/\\d\\d:\\d\\d:\\d\\d/],[\"HH:mm\",/\\d\\d:\\d\\d/],[\"HHmmss.SSSS\",/\\d\\d\\d\\d\\d\\d\\.\\d+/],[\"HHmmss,SSSS\",/\\d\\d\\d\\d\\d\\d,\\d+/],[\"HHmmss\",/\\d\\d\\d\\d\\d\\d/],[\"HHmm\",/\\d\\d\\d\\d/],[\"HH\",/\\d\\d/]],mt=/^\\/?Date\\((-?\\d+)/i,gt=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\\s)?(\\d{1,2})\\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s(\\d{2,4})\\s(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\\d{4}))$/,vt={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function kt(e){var t,o,n,i,r,s,a=e._i,l=dt.exec(a)||ut.exec(a);if(l){for(f(e).iso=!0,t=0,o=pt.length;t<o;t++)if(pt[t][1].exec(l[1])){i=pt[t][0],n=!1!==pt[t][2];break}if(null==i)return void(e._isValid=!1);if(l[3]){for(t=0,o=ft.length;t<o;t++)if(ft[t][1].exec(l[3])){r=(l[2]||\" \")+ft[t][0];break}if(null==r)return void(e._isValid=!1)}if(!n&&null!=r)return void(e._isValid=!1);if(l[4]){if(!ht.exec(l[4]))return void(e._isValid=!1);s=\"Z\"}e._f=i+(r||\"\")+(s||\"\"),xt(e)}else e._isValid=!1}function yt(e){var t=parseInt(e,10);return t<=49?2e3+t:t<=999?1900+t:t}function wt(e){var t,o=gt.exec(function(e){return e.replace(/\\([^)]*\\)|[\\n\\t]/g,\" \").replace(/(\\s\\s+)/g,\" \").replace(/^\\s\\s*/,\"\").replace(/\\s\\s*$/,\"\")}(e._i));if(o){if(t=function(e,t,o,n,i,r){var s=[yt(e),_e.indexOf(t),parseInt(o,10),parseInt(n,10),parseInt(i,10)];return r&&s.push(parseInt(r,10)),s}(o[4],o[3],o[2],o[5],o[6],o[7]),!function(e,t,o){return!e||ze.indexOf(e)===new Date(t[0],t[1],t[2]).getDay()||(f(o).weekdayMismatch=!0,o._isValid=!1,!1)}(o[1],t,e))return;e._a=t,e._tzm=function(e,t,o){if(e)return vt[e];if(t)return 0;var n=parseInt(o,10),i=n%100;return(n-i)/100*60+i}(o[8],o[9],o[10]),e._d=Oe.apply(null,e._a),e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),f(e).rfc2822=!0}else e._isValid=!1}function At(e,t,o){return null!=e?e:null!=t?t:o}function Ct(e){var t=new Date(n.now());return e._useUTC?[t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate()]:[t.getFullYear(),t.getMonth(),t.getDate()]}function _t(e){var t,o,n,i,r,s=[];if(!e._d){for(n=Ct(e),e._w&&null==e._a[2]&&null==e._a[1]&&function(e){var t,o,n,i,r,s,a,l,c;null!=(t=e._w).GG||null!=t.W||null!=t.E?(r=1,s=4,o=At(t.GG,e._a[0],Ve(Lt(),1,4).year),n=At(t.W,1),((i=At(t.E,1))<1||i>7)&&(l=!0)):(r=e._locale._week.dow,s=e._locale._week.doy,c=Ve(Lt(),r,s),o=At(t.gg,e._a[0],c.year),n=At(t.w,c.week),null!=t.d?((i=t.d)<0||i>6)&&(l=!0):null!=t.e?(i=t.e+r,(t.e<0||t.e>6)&&(l=!0)):i=r),n<1||n>He(o,r,s)?f(e)._overflowWeeks=!0:null!=l?f(e)._overflowWeekday=!0:(a=Re(o,n,i,r,s),e._a[0]=a.year,e._dayOfYear=a.dayOfYear)}(e),null!=e._dayOfYear&&(r=At(e._a[0],n[0]),(e._dayOfYear>Me(r)||0===e._dayOfYear)&&(f(e)._overflowDayOfYear=!0),o=Oe(r,0,e._dayOfYear),e._a[1]=o.getUTCMonth(),e._a[2]=o.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=s[t]=n[t];for(;t<7;t++)e._a[t]=s[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[3]&&0===e._a[4]&&0===e._a[5]&&0===e._a[6]&&(e._nextDay=!0,e._a[3]=0),e._d=(e._useUTC?Oe:Pe).apply(null,s),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[3]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(f(e).weekdayMismatch=!0)}}function xt(e){if(e._f!==n.ISO_8601)if(e._f!==n.RFC_2822){e._a=[],f(e).empty=!0;var t,o,i,r,s,a,l=\"\"+e._i,c=l.length,d=0;for(i=V(e._f,e._locale).match(N)||[],t=0;t<i.length;t++)r=i[t],(o=(l.match(me(r,e))||[])[0])&&((s=l.substr(0,l.indexOf(o))).length>0&&f(e).unusedInput.push(s),l=l.slice(l.indexOf(o)+o.length),d+=o.length),P[r]?(o?f(e).empty=!1:f(e).unusedTokens.push(r),we(r,o,e)):e._strict&&!o&&f(e).unusedTokens.push(r);f(e).charsLeftOver=c-d,l.length>0&&f(e).unusedInput.push(l),e._a[3]<=12&&!0===f(e).bigHour&&e._a[3]>0&&(f(e).bigHour=void 0),f(e).parsedDateParts=e._a.slice(0),f(e).meridiem=e._meridiem,e._a[3]=function(e,t,o){var n;return null==o?t:null!=e.meridiemHour?e.meridiemHour(t,o):null!=e.isPM?((n=e.isPM(o))&&t<12&&(t+=12),n||12!==t||(t=0),t):t}(e._locale,e._a[3],e._meridiem),null!==(a=f(e).era)&&(e._a[0]=e._locale.erasConvertYear(a,e._a[0])),_t(e),ct(e)}else wt(e);else kt(e)}function St(e){var t=e._i,o=e._f;return e._locale=e._locale||lt(e._l),null===t||void 0===o&&\"\"===t?g({nullInput:!0}):(\"string\"==typeof t&&(e._i=t=e._locale.preparse(t)),A(t)?new w(ct(t)):(d(t)?e._d=t:i(o)?function(e){var t,o,n,i,r,s,a=!1;if(0===e._f.length)return f(e).invalidFormat=!0,void(e._d=new Date(NaN));for(i=0;i<e._f.length;i++)r=0,s=!1,t=y({},e),null!=e._useUTC&&(t._useUTC=e._useUTC),t._f=e._f[i],xt(t),m(t)&&(s=!0),r+=f(t).charsLeftOver,r+=10*f(t).unusedTokens.length,f(t).score=r,a?r<n&&(n=r,o=t):(null==n||r<n||s)&&(n=r,o=t,s&&(a=!0));h(e,o||t)}(e):o?xt(e):function(e){var t=e._i;l(t)?e._d=new Date(n.now()):d(t)?e._d=new Date(t.valueOf()):\"string\"==typeof t?function(e){var t=mt.exec(e._i);null===t?(kt(e),!1===e._isValid&&(delete e._isValid,wt(e),!1===e._isValid&&(delete e._isValid,e._strict?e._isValid=!1:n.createFromInputFallback(e)))):e._d=new Date(+t[1])}(e):i(t)?(e._a=u(t.slice(0),(function(e){return parseInt(e,10)})),_t(e)):r(t)?function(e){if(!e._d){var t=z(e._i),o=void 0===t.day?t.date:t.day;e._a=u([t.year,t.month,o,t.hour,t.minute,t.second,t.millisecond],(function(e){return e&&parseInt(e,10)})),_t(e)}}(e):c(t)?e._d=new Date(t):n.createFromInputFallback(e)}(e),m(e)||(e._d=null),e))}function Bt(e,t,o,n,s){var l={};return!0!==t&&!1!==t||(n=t,t=void 0),!0!==o&&!1!==o||(n=o,o=void 0),(r(e)&&a(e)||i(e)&&0===e.length)&&(e=void 0),l._isAMomentObject=!0,l._useUTC=l._isUTC=s,l._l=o,l._i=e,l._f=t,l._strict=n,function(e){var t=new w(ct(St(e)));return t._nextDay&&(t.add(1,\"d\"),t._nextDay=void 0),t}(l)}function Lt(e,t,o,n){return Bt(e,t,o,n,!1)}n.createFromInputFallback=b(\"value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.\",(function(e){e._d=new Date(e._i+(e._useUTC?\" UTC\":\"\"))})),n.ISO_8601=function(){},n.RFC_2822=function(){};var Tt=b(\"moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/\",(function(){var e=Lt.apply(null,arguments);return this.isValid()&&e.isValid()?e<this?this:e:g()})),Et=b(\"moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/\",(function(){var e=Lt.apply(null,arguments);return this.isValid()&&e.isValid()?e>this?this:e:g()}));function Nt(e,t){var o,n;if(1===t.length&&i(t[0])&&(t=t[0]),!t.length)return Lt();for(o=t[0],n=1;n<t.length;++n)t[n].isValid()&&!t[n][e](o)||(o=t[n]);return o}var Mt=[\"year\",\"quarter\",\"month\",\"week\",\"day\",\"hour\",\"minute\",\"second\",\"millisecond\"];function It(e){var t,o,n=!1;for(t in e)if(s(e,t)&&(-1===Ae.call(Mt,t)||null!=e[t]&&isNaN(e[t])))return!1;for(o=0;o<Mt.length;++o)if(e[Mt[o]]){if(n)return!1;parseFloat(e[Mt[o]])!==K(e[Mt[o]])&&(n=!0)}return!0}function Pt(e){var t=z(e),o=t.year||0,n=t.quarter||0,i=t.month||0,r=t.week||t.isoWeek||0,s=t.day||0,a=t.hour||0,l=t.minute||0,c=t.second||0,d=t.millisecond||0;this._isValid=It(t),this._milliseconds=+d+1e3*c+6e4*l+1e3*a*60*60,this._days=+s+7*r,this._months=+i+3*n+12*o,this._data={},this._locale=lt(),this._bubble()}function Ot(e){return e instanceof Pt}function Dt(e){return e<0?-1*Math.round(-1*e):Math.round(e)}function Rt(e,t){O(e,0,0,(function(){var e=this.utcOffset(),o=\"+\";return e<0&&(e=-e,o=\"-\"),o+E(~~(e/60),2)+t+E(~~e%60,2)}))}Rt(\"Z\",\":\"),Rt(\"ZZ\",\"\"),fe(\"Z\",he),fe(\"ZZ\",he),ke([\"Z\",\"ZZ\"],(function(e,t,o){o._useUTC=!0,o._tzm=Ht(he,e)}));var Vt=/([\\+\\-]|\\d\\d)/gi;function Ht(e,t){var o,n,i=(t||\"\").match(e);return null===i?null:0===(n=60*(o=((i[i.length-1]||[])+\"\").match(Vt)||[\"-\",0,0])[1]+K(o[2]))?0:\"+\"===o[0]?n:-n}function Yt(e,t){var o,i;return t._isUTC?(o=t.clone(),i=(A(e)||d(e)?e.valueOf():Lt(e).valueOf())-o.valueOf(),o._d.setTime(o._d.valueOf()+i),n.updateOffset(o,!1),o):Lt(e).local()}function Ft(e){return-Math.round(e._d.getTimezoneOffset())}function zt(){return!!this.isValid()&&this._isUTC&&0===this._offset}n.updateOffset=function(){};var jt=/^(-|\\+)?(?:(\\d*)[. ])?(\\d+):(\\d+)(?::(\\d+)(\\.\\d*)?)?$/,Ut=/^(-|\\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;function qt(e,t){var o,n,i,r=e,a=null;return Ot(e)?r={ms:e._milliseconds,d:e._days,M:e._months}:c(e)||!isNaN(+e)?(r={},t?r[t]=+e:r.milliseconds=+e):(a=jt.exec(e))?(o=\"-\"===a[1]?-1:1,r={y:0,d:K(a[2])*o,h:K(a[3])*o,m:K(a[4])*o,s:K(a[5])*o,ms:K(Dt(1e3*a[6]))*o}):(a=Ut.exec(e))?(o=\"-\"===a[1]?-1:1,r={y:Wt(a[2],o),M:Wt(a[3],o),w:Wt(a[4],o),d:Wt(a[5],o),h:Wt(a[6],o),m:Wt(a[7],o),s:Wt(a[8],o)}):null==r?r={}:\"object\"==typeof r&&(\"from\"in r||\"to\"in r)&&(i=function(e,t){var o;return e.isValid()&&t.isValid()?(t=Yt(t,e),e.isBefore(t)?o=Kt(e,t):((o=Kt(t,e)).milliseconds=-o.milliseconds,o.months=-o.months),o):{milliseconds:0,months:0}}(Lt(r.from),Lt(r.to)),(r={}).ms=i.milliseconds,r.M=i.months),n=new Pt(r),Ot(e)&&s(e,\"_locale\")&&(n._locale=e._locale),Ot(e)&&s(e,\"_isValid\")&&(n._isValid=e._isValid),n}function Wt(e,t){var o=e&&parseFloat(e.replace(\",\",\".\"));return(isNaN(o)?0:o)*t}function Kt(e,t){var o={};return o.months=t.month()-e.month()+12*(t.year()-e.year()),e.clone().add(o.months,\"M\").isAfter(t)&&--o.months,o.milliseconds=+t-+e.clone().add(o.months,\"M\"),o}function Jt(e,t){return function(o,n){var i;return null===n||isNaN(+n)||(S(t,\"moment().\"+t+\"(period, number) is deprecated. Please use moment().\"+t+\"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.\"),i=o,o=n,n=i),$t(this,qt(o,n),e),this}}function $t(e,t,o,i){var r=t._milliseconds,s=Dt(t._days),a=Dt(t._months);e.isValid()&&(i=null==i||i,a&&Te(e,$(e,\"Month\")+a*o),s&&X(e,\"Date\",$(e,\"Date\")+s*o),r&&e._d.setTime(e._d.valueOf()+r*o),i&&n.updateOffset(e,s||a))}qt.fn=Pt.prototype,qt.invalid=function(){return qt(NaN)};var Xt=Jt(1,\"add\"),Qt=Jt(-1,\"subtract\");function Gt(e){return\"string\"==typeof e||e instanceof String}function Zt(e){return A(e)||d(e)||Gt(e)||c(e)||function(e){var t=i(e),o=!1;return t&&(o=0===e.filter((function(t){return!c(t)&&Gt(e)})).length),t&&o}(e)||function(e){var t,o,n=r(e)&&!a(e),i=!1,l=[\"years\",\"year\",\"y\",\"months\",\"month\",\"M\",\"days\",\"day\",\"d\",\"dates\",\"date\",\"D\",\"hours\",\"hour\",\"h\",\"minutes\",\"minute\",\"m\",\"seconds\",\"second\",\"s\",\"milliseconds\",\"millisecond\",\"ms\"];for(t=0;t<l.length;t+=1)o=l[t],i=i||s(e,o);return n&&i}(e)||null==e}function eo(e){var t,o=r(e)&&!a(e),n=!1,i=[\"sameDay\",\"nextDay\",\"lastDay\",\"nextWeek\",\"lastWeek\",\"sameElse\"];for(t=0;t<i.length;t+=1)n=n||s(e,i[t]);return o&&n}function to(e,t){if(e.date()<t.date())return-to(t,e);var o=12*(t.year()-e.year())+(t.month()-e.month()),n=e.clone().add(o,\"months\");return-(o+(t-n<0?(t-n)/(n-e.clone().add(o-1,\"months\")):(t-n)/(e.clone().add(o+1,\"months\")-n)))||0}function oo(e){var t;return void 0===e?this._locale._abbr:(null!=(t=lt(e))&&(this._locale=t),this)}n.defaultFormat=\"YYYY-MM-DDTHH:mm:ssZ\",n.defaultFormatUtc=\"YYYY-MM-DDTHH:mm:ss[Z]\";var no=b(\"moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.\",(function(e){return void 0===e?this.localeData():this.locale(e)}));function io(){return this._locale}var ro=1e3,so=6e4,ao=60*so,lo=3506328*ao;function co(e,t){return(e%t+t)%t}function uo(e,t,o){return e<100&&e>=0?new Date(e+400,t,o)-lo:new Date(e,t,o).valueOf()}function ho(e,t,o){return e<100&&e>=0?Date.UTC(e+400,t,o)-lo:Date.UTC(e,t,o)}function po(e,t){return t.erasAbbrRegex(e)}function fo(){var e,t,o=[],n=[],i=[],r=[],s=this.eras();for(e=0,t=s.length;e<t;++e)n.push(ge(s[e].name)),o.push(ge(s[e].abbr)),i.push(ge(s[e].narrow)),r.push(ge(s[e].name)),r.push(ge(s[e].abbr)),r.push(ge(s[e].narrow));this._erasRegex=new RegExp(\"^(\"+r.join(\"|\")+\")\",\"i\"),this._erasNameRegex=new RegExp(\"^(\"+n.join(\"|\")+\")\",\"i\"),this._erasAbbrRegex=new RegExp(\"^(\"+o.join(\"|\")+\")\",\"i\"),this._erasNarrowRegex=new RegExp(\"^(\"+i.join(\"|\")+\")\",\"i\")}function mo(e,t){O(0,[e,e.length],0,t)}function go(e,t,o,n,i){var r;return null==e?Ve(this,n,i).year:(t>(r=He(e,n,i))&&(t=r),vo.call(this,e,t,o,n,i))}function vo(e,t,o,n,i){var r=Re(e,t,o,n,i),s=Oe(r.year,0,r.dayOfYear);return this.year(s.getUTCFullYear()),this.month(s.getUTCMonth()),this.date(s.getUTCDate()),this}O(\"N\",0,0,\"eraAbbr\"),O(\"NN\",0,0,\"eraAbbr\"),O(\"NNN\",0,0,\"eraAbbr\"),O(\"NNNN\",0,0,\"eraName\"),O(\"NNNNN\",0,0,\"eraNarrow\"),O(\"y\",[\"y\",1],\"yo\",\"eraYear\"),O(\"y\",[\"yy\",2],0,\"eraYear\"),O(\"y\",[\"yyy\",3],0,\"eraYear\"),O(\"y\",[\"yyyy\",4],0,\"eraYear\"),fe(\"N\",po),fe(\"NN\",po),fe(\"NNN\",po),fe(\"NNNN\",(function(e,t){return t.erasNameRegex(e)})),fe(\"NNNNN\",(function(e,t){return t.erasNarrowRegex(e)})),ke([\"N\",\"NN\",\"NNN\",\"NNNN\",\"NNNNN\"],(function(e,t,o,n){var i=o._locale.erasParse(e,n,o._strict);i?f(o).era=i:f(o).invalidEra=e})),fe(\"y\",ce),fe(\"yy\",ce),fe(\"yyy\",ce),fe(\"yyyy\",ce),fe(\"yo\",(function(e,t){return t._eraYearOrdinalRegex||ce})),ke([\"y\",\"yy\",\"yyy\",\"yyyy\"],0),ke([\"yo\"],(function(e,t,o,n){var i;o._locale._eraYearOrdinalRegex&&(i=e.match(o._locale._eraYearOrdinalRegex)),o._locale.eraYearOrdinalParse?t[0]=o._locale.eraYearOrdinalParse(e,i):t[0]=parseInt(e,10)})),O(0,[\"gg\",2],0,(function(){return this.weekYear()%100})),O(0,[\"GG\",2],0,(function(){return this.isoWeekYear()%100})),mo(\"gggg\",\"weekYear\"),mo(\"ggggg\",\"weekYear\"),mo(\"GGGG\",\"isoWeekYear\"),mo(\"GGGGG\",\"isoWeekYear\"),Y(\"weekYear\",\"gg\"),Y(\"isoWeekYear\",\"GG\"),U(\"weekYear\",1),U(\"isoWeekYear\",1),fe(\"G\",de),fe(\"g\",de),fe(\"GG\",ne,Z),fe(\"gg\",ne,Z),fe(\"GGGG\",ae,te),fe(\"gggg\",ae,te),fe(\"GGGGG\",le,oe),fe(\"ggggg\",le,oe),ye([\"gggg\",\"ggggg\",\"GGGG\",\"GGGGG\"],(function(e,t,o,n){t[n.substr(0,2)]=K(e)})),ye([\"gg\",\"GG\"],(function(e,t,o,i){t[i]=n.parseTwoDigitYear(e)})),O(\"Q\",0,\"Qo\",\"quarter\"),Y(\"quarter\",\"Q\"),U(\"quarter\",7),fe(\"Q\",G),ke(\"Q\",(function(e,t){t[1]=3*(K(e)-1)})),O(\"D\",[\"DD\",2],\"Do\",\"date\"),Y(\"date\",\"D\"),U(\"date\",9),fe(\"D\",ne),fe(\"DD\",ne,Z),fe(\"Do\",(function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient})),ke([\"D\",\"DD\"],2),ke(\"Do\",(function(e,t){t[2]=K(e.match(ne)[0])}));var ko=J(\"Date\",!0);O(\"DDD\",[\"DDDD\",3],\"DDDo\",\"dayOfYear\"),Y(\"dayOfYear\",\"DDD\"),U(\"dayOfYear\",4),fe(\"DDD\",se),fe(\"DDDD\",ee),ke([\"DDD\",\"DDDD\"],(function(e,t,o){o._dayOfYear=K(e)})),O(\"m\",[\"mm\",2],0,\"minute\"),Y(\"minute\",\"m\"),U(\"minute\",14),fe(\"m\",ne),fe(\"mm\",ne,Z),ke([\"m\",\"mm\"],4);var yo=J(\"Minutes\",!1);O(\"s\",[\"ss\",2],0,\"second\"),Y(\"second\",\"s\"),U(\"second\",15),fe(\"s\",ne),fe(\"ss\",ne,Z),ke([\"s\",\"ss\"],5);var wo,Ao,Co=J(\"Seconds\",!1);for(O(\"S\",0,0,(function(){return~~(this.millisecond()/100)})),O(0,[\"SS\",2],0,(function(){return~~(this.millisecond()/10)})),O(0,[\"SSS\",3],0,\"millisecond\"),O(0,[\"SSSS\",4],0,(function(){return 10*this.millisecond()})),O(0,[\"SSSSS\",5],0,(function(){return 100*this.millisecond()})),O(0,[\"SSSSSS\",6],0,(function(){return 1e3*this.millisecond()})),O(0,[\"SSSSSSS\",7],0,(function(){return 1e4*this.millisecond()})),O(0,[\"SSSSSSSS\",8],0,(function(){return 1e5*this.millisecond()})),O(0,[\"SSSSSSSSS\",9],0,(function(){return 1e6*this.millisecond()})),Y(\"millisecond\",\"ms\"),U(\"millisecond\",16),fe(\"S\",se,G),fe(\"SS\",se,Z),fe(\"SSS\",se,ee),wo=\"SSSS\";wo.length<=9;wo+=\"S\")fe(wo,ce);function bo(e,t){t[6]=K(1e3*(\"0.\"+e))}for(wo=\"S\";wo.length<=9;wo+=\"S\")ke(wo,bo);Ao=J(\"Milliseconds\",!1),O(\"z\",0,0,\"zoneAbbr\"),O(\"zz\",0,0,\"zoneName\");var _o=w.prototype;function xo(e){return e}_o.add=Xt,_o.calendar=function(e,t){1===arguments.length&&(arguments[0]?Zt(arguments[0])?(e=arguments[0],t=void 0):eo(arguments[0])&&(t=arguments[0],e=void 0):(e=void 0,t=void 0));var o=e||Lt(),i=Yt(o,this).startOf(\"day\"),r=n.calendarFormat(this,i)||\"sameElse\",s=t&&(B(t[r])?t[r].call(this,o):t[r]);return this.format(s||this.localeData().calendar(r,this,Lt(o)))},_o.clone=function(){return new w(this)},_o.diff=function(e,t,o){var n,i,r;if(!this.isValid())return NaN;if(!(n=Yt(e,this)).isValid())return NaN;switch(i=6e4*(n.utcOffset()-this.utcOffset()),t=F(t)){case\"year\":r=to(this,n)/12;break;case\"month\":r=to(this,n);break;case\"quarter\":r=to(this,n)/3;break;case\"second\":r=(this-n)/1e3;break;case\"minute\":r=(this-n)/6e4;break;case\"hour\":r=(this-n)/36e5;break;case\"day\":r=(this-n-i)/864e5;break;case\"week\":r=(this-n-i)/6048e5;break;default:r=this-n}return o?r:W(r)},_o.endOf=function(e){var t,o;if(void 0===(e=F(e))||\"millisecond\"===e||!this.isValid())return this;switch(o=this._isUTC?ho:uo,e){case\"year\":t=o(this.year()+1,0,1)-1;break;case\"quarter\":t=o(this.year(),this.month()-this.month()%3+3,1)-1;break;case\"month\":t=o(this.year(),this.month()+1,1)-1;break;case\"week\":t=o(this.year(),this.month(),this.date()-this.weekday()+7)-1;break;case\"isoWeek\":t=o(this.year(),this.month(),this.date()-(this.isoWeekday()-1)+7)-1;break;case\"day\":case\"date\":t=o(this.year(),this.month(),this.date()+1)-1;break;case\"hour\":t=this._d.valueOf(),t+=ao-co(t+(this._isUTC?0:this.utcOffset()*so),ao)-1;break;case\"minute\":t=this._d.valueOf(),t+=so-co(t,so)-1;break;case\"second\":t=this._d.valueOf(),t+=ro-co(t,ro)-1}return this._d.setTime(t),n.updateOffset(this,!0),this},_o.format=function(e){e||(e=this.isUtc()?n.defaultFormatUtc:n.defaultFormat);var t=R(this,e);return this.localeData().postformat(t)},_o.from=function(e,t){return this.isValid()&&(A(e)&&e.isValid()||Lt(e).isValid())?qt({to:this,from:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()},_o.fromNow=function(e){return this.from(Lt(),e)},_o.to=function(e,t){return this.isValid()&&(A(e)&&e.isValid()||Lt(e).isValid())?qt({from:this,to:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()},_o.toNow=function(e){return this.to(Lt(),e)},_o.get=function(e){return B(this[e=F(e)])?this[e]():this},_o.invalidAt=function(){return f(this).overflow},_o.isAfter=function(e,t){var o=A(e)?e:Lt(e);return!(!this.isValid()||!o.isValid())&&(\"millisecond\"===(t=F(t)||\"millisecond\")?this.valueOf()>o.valueOf():o.valueOf()<this.clone().startOf(t).valueOf())},_o.isBefore=function(e,t){var o=A(e)?e:Lt(e);return!(!this.isValid()||!o.isValid())&&(\"millisecond\"===(t=F(t)||\"millisecond\")?this.valueOf()<o.valueOf():this.clone().endOf(t).valueOf()<o.valueOf())},_o.isBetween=function(e,t,o,n){var i=A(e)?e:Lt(e),r=A(t)?t:Lt(t);return!!(this.isValid()&&i.isValid()&&r.isValid())&&(\"(\"===(n=n||\"()\")[0]?this.isAfter(i,o):!this.isBefore(i,o))&&(\")\"===n[1]?this.isBefore(r,o):!this.isAfter(r,o))},_o.isSame=function(e,t){var o,n=A(e)?e:Lt(e);return!(!this.isValid()||!n.isValid())&&(\"millisecond\"===(t=F(t)||\"millisecond\")?this.valueOf()===n.valueOf():(o=n.valueOf(),this.clone().startOf(t).valueOf()<=o&&o<=this.clone().endOf(t).valueOf()))},_o.isSameOrAfter=function(e,t){return this.isSame(e,t)||this.isAfter(e,t)},_o.isSameOrBefore=function(e,t){return this.isSame(e,t)||this.isBefore(e,t)},_o.isValid=function(){return m(this)},_o.lang=no,_o.locale=oo,_o.localeData=io,_o.max=Et,_o.min=Tt,_o.parsingFlags=function(){return h({},f(this))},_o.set=function(e,t){if(\"object\"==typeof e){var o,n=function(e){var t,o=[];for(t in e)s(e,t)&&o.push({unit:t,priority:j[t]});return o.sort((function(e,t){return e.priority-t.priority})),o}(e=z(e));for(o=0;o<n.length;o++)this[n[o].unit](e[n[o].unit])}else if(B(this[e=F(e)]))return this[e](t);return this},_o.startOf=function(e){var t,o;if(void 0===(e=F(e))||\"millisecond\"===e||!this.isValid())return this;switch(o=this._isUTC?ho:uo,e){case\"year\":t=o(this.year(),0,1);break;case\"quarter\":t=o(this.year(),this.month()-this.month()%3,1);break;case\"month\":t=o(this.year(),this.month(),1);break;case\"week\":t=o(this.year(),this.month(),this.date()-this.weekday());break;case\"isoWeek\":t=o(this.year(),this.month(),this.date()-(this.isoWeekday()-1));break;case\"day\":case\"date\":t=o(this.year(),this.month(),this.date());break;case\"hour\":t=this._d.valueOf(),t-=co(t+(this._isUTC?0:this.utcOffset()*so),ao);break;case\"minute\":t=this._d.valueOf(),t-=co(t,so);break;case\"second\":t=this._d.valueOf(),t-=co(t,ro)}return this._d.setTime(t),n.updateOffset(this,!0),this},_o.subtract=Qt,_o.toArray=function(){var e=this;return[e.year(),e.month(),e.date(),e.hour(),e.minute(),e.second(),e.millisecond()]},_o.toObject=function(){var e=this;return{years:e.year(),months:e.month(),date:e.date(),hours:e.hours(),minutes:e.minutes(),seconds:e.seconds(),milliseconds:e.milliseconds()}},_o.toDate=function(){return new Date(this.valueOf())},_o.toISOString=function(e){if(!this.isValid())return null;var t=!0!==e,o=t?this.clone().utc():this;return o.year()<0||o.year()>9999?R(o,t?\"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]\":\"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ\"):B(Date.prototype.toISOString)?t?this.toDate().toISOString():new Date(this.valueOf()+60*this.utcOffset()*1e3).toISOString().replace(\"Z\",R(o,\"Z\")):R(o,t?\"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]\":\"YYYY-MM-DD[T]HH:mm:ss.SSSZ\")},_o.inspect=function(){if(!this.isValid())return\"moment.invalid(/* \"+this._i+\" */)\";var e,t,o,n=\"moment\",i=\"\";return this.isLocal()||(n=0===this.utcOffset()?\"moment.utc\":\"moment.parseZone\",i=\"Z\"),e=\"[\"+n+'(\"]',t=0<=this.year()&&this.year()<=9999?\"YYYY\":\"YYYYYY\",\"-MM-DD[T]HH:mm:ss.SSS\",o=i+'[\")]',this.format(e+t+\"-MM-DD[T]HH:mm:ss.SSS\"+o)},\"undefined\"!=typeof Symbol&&null!=Symbol.for&&(_o[Symbol.for(\"nodejs.util.inspect.custom\")]=function(){return\"Moment<\"+this.format()+\">\"}),_o.toJSON=function(){return this.isValid()?this.toISOString():null},_o.toString=function(){return this.clone().locale(\"en\").format(\"ddd MMM DD YYYY HH:mm:ss [GMT]ZZ\")},_o.unix=function(){return Math.floor(this.valueOf()/1e3)},_o.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},_o.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},_o.eraName=function(){var e,t,o,n=this.localeData().eras();for(e=0,t=n.length;e<t;++e){if(o=this.clone().startOf(\"day\").valueOf(),n[e].since<=o&&o<=n[e].until)return n[e].name;if(n[e].until<=o&&o<=n[e].since)return n[e].name}return\"\"},_o.eraNarrow=function(){var e,t,o,n=this.localeData().eras();for(e=0,t=n.length;e<t;++e){if(o=this.clone().startOf(\"day\").valueOf(),n[e].since<=o&&o<=n[e].until)return n[e].narrow;if(n[e].until<=o&&o<=n[e].since)return n[e].narrow}return\"\"},_o.eraAbbr=function(){var e,t,o,n=this.localeData().eras();for(e=0,t=n.length;e<t;++e){if(o=this.clone().startOf(\"day\").valueOf(),n[e].since<=o&&o<=n[e].until)return n[e].abbr;if(n[e].until<=o&&o<=n[e].since)return n[e].abbr}return\"\"},_o.eraYear=function(){var e,t,o,i,r=this.localeData().eras();for(e=0,t=r.length;e<t;++e)if(o=r[e].since<=r[e].until?1:-1,i=this.clone().startOf(\"day\").valueOf(),r[e].since<=i&&i<=r[e].until||r[e].until<=i&&i<=r[e].since)return(this.year()-n(r[e].since).year())*o+r[e].offset;return this.year()},_o.year=Ie,_o.isLeapYear=function(){return q(this.year())},_o.weekYear=function(e){return go.call(this,e,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)},_o.isoWeekYear=function(e){return go.call(this,e,this.isoWeek(),this.isoWeekday(),1,4)},_o.quarter=_o.quarters=function(e){return null==e?Math.ceil((this.month()+1)/3):this.month(3*(e-1)+this.month()%3)},_o.month=Ee,_o.daysInMonth=function(){return Ce(this.year(),this.month())},_o.week=_o.weeks=function(e){var t=this.localeData().week(this);return null==e?t:this.add(7*(e-t),\"d\")},_o.isoWeek=_o.isoWeeks=function(e){var t=Ve(this,1,4).week;return null==e?t:this.add(7*(e-t),\"d\")},_o.weeksInYear=function(){var e=this.localeData()._week;return He(this.year(),e.dow,e.doy)},_o.weeksInWeekYear=function(){var e=this.localeData()._week;return He(this.weekYear(),e.dow,e.doy)},_o.isoWeeksInYear=function(){return He(this.year(),1,4)},_o.isoWeeksInISOWeekYear=function(){return He(this.isoWeekYear(),1,4)},_o.date=ko,_o.day=_o.days=function(e){if(!this.isValid())return null!=e?this:NaN;var t=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=e?(e=function(e,t){return\"string\"!=typeof e?e:isNaN(e)?\"number\"==typeof(e=t.weekdaysParse(e))?e:null:parseInt(e,10)}(e,this.localeData()),this.add(e-t,\"d\")):t},_o.weekday=function(e){if(!this.isValid())return null!=e?this:NaN;var t=(this.day()+7-this.localeData()._week.dow)%7;return null==e?t:this.add(e-t,\"d\")},_o.isoWeekday=function(e){if(!this.isValid())return null!=e?this:NaN;if(null!=e){var t=function(e,t){return\"string\"==typeof e?t.weekdaysParse(e)%7||7:isNaN(e)?null:e}(e,this.localeData());return this.day(this.day()%7?t:t-7)}return this.day()||7},_o.dayOfYear=function(e){var t=Math.round((this.clone().startOf(\"day\")-this.clone().startOf(\"year\"))/864e5)+1;return null==e?t:this.add(e-t,\"d\")},_o.hour=_o.hours=Ge,_o.minute=_o.minutes=yo,_o.second=_o.seconds=Co,_o.millisecond=_o.milliseconds=Ao,_o.utcOffset=function(e,t,o){var i,r=this._offset||0;if(!this.isValid())return null!=e?this:NaN;if(null!=e){if(\"string\"==typeof e){if(null===(e=Ht(he,e)))return this}else Math.abs(e)<16&&!o&&(e*=60);return!this._isUTC&&t&&(i=Ft(this)),this._offset=e,this._isUTC=!0,null!=i&&this.add(i,\"m\"),r!==e&&(!t||this._changeInProgress?$t(this,qt(e-r,\"m\"),1,!1):this._changeInProgress||(this._changeInProgress=!0,n.updateOffset(this,!0),this._changeInProgress=null)),this}return this._isUTC?r:Ft(this)},_o.utc=function(e){return this.utcOffset(0,e)},_o.local=function(e){return this._isUTC&&(this.utcOffset(0,e),this._isUTC=!1,e&&this.subtract(Ft(this),\"m\")),this},_o.parseZone=function(){if(null!=this._tzm)this.utcOffset(this._tzm,!1,!0);else if(\"string\"==typeof this._i){var e=Ht(ue,this._i);null!=e?this.utcOffset(e):this.utcOffset(0,!0)}return this},_o.hasAlignedHourOffset=function(e){return!!this.isValid()&&(e=e?Lt(e).utcOffset():0,(this.utcOffset()-e)%60==0)},_o.isDST=function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},_o.isLocal=function(){return!!this.isValid()&&!this._isUTC},_o.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},_o.isUtc=zt,_o.isUTC=zt,_o.zoneAbbr=function(){return this._isUTC?\"UTC\":\"\"},_o.zoneName=function(){return this._isUTC?\"Coordinated Universal Time\":\"\"},_o.dates=b(\"dates accessor is deprecated. Use date instead.\",ko),_o.months=b(\"months accessor is deprecated. Use month instead\",Ee),_o.years=b(\"years accessor is deprecated. Use year instead\",Ie),_o.zone=b(\"moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/\",(function(e,t){return null!=e?(\"string\"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()})),_o.isDSTShifted=b(\"isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information\",(function(){if(!l(this._isDSTShifted))return this._isDSTShifted;var e,t={};return y(t,this),(t=St(t))._a?(e=t._isUTC?p(t._a):Lt(t._a),this._isDSTShifted=this.isValid()&&function(e,t,o){var n,i=Math.min(e.length,t.length),r=Math.abs(e.length-t.length),s=0;for(n=0;n<i;n++)(o&&e[n]!==t[n]||!o&&K(e[n])!==K(t[n]))&&s++;return s+r}(t._a,e.toArray())>0):this._isDSTShifted=!1,this._isDSTShifted}));var So=T.prototype;function Bo(e,t,o,n){var i=lt(),r=p().set(n,t);return i[o](r,e)}function Lo(e,t,o){if(c(e)&&(t=e,e=void 0),e=e||\"\",null!=t)return Bo(e,t,o,\"month\");var n,i=[];for(n=0;n<12;n++)i[n]=Bo(e,n,o,\"month\");return i}function To(e,t,o,n){\"boolean\"==typeof e?(c(t)&&(o=t,t=void 0),t=t||\"\"):(o=t=e,e=!1,c(t)&&(o=t,t=void 0),t=t||\"\");var i,r=lt(),s=e?r._week.dow:0,a=[];if(null!=o)return Bo(t,(o+s)%7,n,\"day\");for(i=0;i<7;i++)a[i]=Bo(t,(i+s)%7,n,\"day\");return a}So.calendar=function(e,t,o){var n=this._calendar[e]||this._calendar.sameElse;return B(n)?n.call(t,o):n},So.longDateFormat=function(e){var t=this._longDateFormat[e],o=this._longDateFormat[e.toUpperCase()];return t||!o?t:(this._longDateFormat[e]=o.match(N).map((function(e){return\"MMMM\"===e||\"MM\"===e||\"DD\"===e||\"dddd\"===e?e.slice(1):e})).join(\"\"),this._longDateFormat[e])},So.invalidDate=function(){return this._invalidDate},So.ordinal=function(e){return this._ordinal.replace(\"%d\",e)},So.preparse=xo,So.postformat=xo,So.relativeTime=function(e,t,o,n){var i=this._relativeTime[o];return B(i)?i(e,t,o,n):i.replace(/%d/i,e)},So.pastFuture=function(e,t){var o=this._relativeTime[e>0?\"future\":\"past\"];return B(o)?o(t):o.replace(/%s/i,t)},So.set=function(e){var t,o;for(o in e)s(e,o)&&(B(t=e[o])?this[o]=t:this[\"_\"+o]=t);this._config=e,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+\"|\"+/\\d{1,2}/.source)},So.eras=function(e,t){var o,i,r,s=this._eras||lt(\"en\")._eras;for(o=0,i=s.length;o<i;++o)switch(\"string\"==typeof s[o].since&&(r=n(s[o].since).startOf(\"day\"),s[o].since=r.valueOf()),typeof s[o].until){case\"undefined\":s[o].until=1/0;break;case\"string\":r=n(s[o].until).startOf(\"day\").valueOf(),s[o].until=r.valueOf()}return s},So.erasParse=function(e,t,o){var n,i,r,s,a,l=this.eras();for(e=e.toUpperCase(),n=0,i=l.length;n<i;++n)if(r=l[n].name.toUpperCase(),s=l[n].abbr.toUpperCase(),a=l[n].narrow.toUpperCase(),o)switch(t){case\"N\":case\"NN\":case\"NNN\":if(s===e)return l[n];break;case\"NNNN\":if(r===e)return l[n];break;case\"NNNNN\":if(a===e)return l[n]}else if([r,s,a].indexOf(e)>=0)return l[n]},So.erasConvertYear=function(e,t){var o=e.since<=e.until?1:-1;return void 0===t?n(e.since).year():n(e.since).year()+(t-e.offset)*o},So.erasAbbrRegex=function(e){return s(this,\"_erasAbbrRegex\")||fo.call(this),e?this._erasAbbrRegex:this._erasRegex},So.erasNameRegex=function(e){return s(this,\"_erasNameRegex\")||fo.call(this),e?this._erasNameRegex:this._erasRegex},So.erasNarrowRegex=function(e){return s(this,\"_erasNarrowRegex\")||fo.call(this),e?this._erasNarrowRegex:this._erasRegex},So.months=function(e,t){return e?i(this._months)?this._months[e.month()]:this._months[(this._months.isFormat||xe).test(t)?\"format\":\"standalone\"][e.month()]:i(this._months)?this._months:this._months.standalone},So.monthsShort=function(e,t){return e?i(this._monthsShort)?this._monthsShort[e.month()]:this._monthsShort[xe.test(t)?\"format\":\"standalone\"][e.month()]:i(this._monthsShort)?this._monthsShort:this._monthsShort.standalone},So.monthsParse=function(e,t,o){var n,i,r;if(this._monthsParseExact)return Le.call(this,e,t,o);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),n=0;n<12;n++){if(i=p([2e3,n]),o&&!this._longMonthsParse[n]&&(this._longMonthsParse[n]=new RegExp(\"^\"+this.months(i,\"\").replace(\".\",\"\")+\"$\",\"i\"),this._shortMonthsParse[n]=new RegExp(\"^\"+this.monthsShort(i,\"\").replace(\".\",\"\")+\"$\",\"i\")),o||this._monthsParse[n]||(r=\"^\"+this.months(i,\"\")+\"|^\"+this.monthsShort(i,\"\"),this._monthsParse[n]=new RegExp(r.replace(\".\",\"\"),\"i\")),o&&\"MMMM\"===t&&this._longMonthsParse[n].test(e))return n;if(o&&\"MMM\"===t&&this._shortMonthsParse[n].test(e))return n;if(!o&&this._monthsParse[n].test(e))return n}},So.monthsRegex=function(e){return this._monthsParseExact?(s(this,\"_monthsRegex\")||Ne.call(this),e?this._monthsStrictRegex:this._monthsRegex):(s(this,\"_monthsRegex\")||(this._monthsRegex=Be),this._monthsStrictRegex&&e?this._monthsStrictRegex:this._monthsRegex)},So.monthsShortRegex=function(e){return this._monthsParseExact?(s(this,\"_monthsRegex\")||Ne.call(this),e?this._monthsShortStrictRegex:this._monthsShortRegex):(s(this,\"_monthsShortRegex\")||(this._monthsShortRegex=Se),this._monthsShortStrictRegex&&e?this._monthsShortStrictRegex:this._monthsShortRegex)},So.week=function(e){return Ve(e,this._week.dow,this._week.doy).week},So.firstDayOfYear=function(){return this._week.doy},So.firstDayOfWeek=function(){return this._week.dow},So.weekdays=function(e,t){var o=i(this._weekdays)?this._weekdays:this._weekdays[e&&!0!==e&&this._weekdays.isFormat.test(t)?\"format\":\"standalone\"];return!0===e?Ye(o,this._week.dow):e?o[e.day()]:o},So.weekdaysMin=function(e){return!0===e?Ye(this._weekdaysMin,this._week.dow):e?this._weekdaysMin[e.day()]:this._weekdaysMin},So.weekdaysShort=function(e){return!0===e?Ye(this._weekdaysShort,this._week.dow):e?this._weekdaysShort[e.day()]:this._weekdaysShort},So.weekdaysParse=function(e,t,o){var n,i,r;if(this._weekdaysParseExact)return Ke.call(this,e,t,o);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),n=0;n<7;n++){if(i=p([2e3,1]).day(n),o&&!this._fullWeekdaysParse[n]&&(this._fullWeekdaysParse[n]=new RegExp(\"^\"+this.weekdays(i,\"\").replace(\".\",\"\\\\.?\")+\"$\",\"i\"),this._shortWeekdaysParse[n]=new RegExp(\"^\"+this.weekdaysShort(i,\"\").replace(\".\",\"\\\\.?\")+\"$\",\"i\"),this._minWeekdaysParse[n]=new RegExp(\"^\"+this.weekdaysMin(i,\"\").replace(\".\",\"\\\\.?\")+\"$\",\"i\")),this._weekdaysParse[n]||(r=\"^\"+this.weekdays(i,\"\")+\"|^\"+this.weekdaysShort(i,\"\")+\"|^\"+this.weekdaysMin(i,\"\"),this._weekdaysParse[n]=new RegExp(r.replace(\".\",\"\"),\"i\")),o&&\"dddd\"===t&&this._fullWeekdaysParse[n].test(e))return n;if(o&&\"ddd\"===t&&this._shortWeekdaysParse[n].test(e))return n;if(o&&\"dd\"===t&&this._minWeekdaysParse[n].test(e))return n;if(!o&&this._weekdaysParse[n].test(e))return n}},So.weekdaysRegex=function(e){return this._weekdaysParseExact?(s(this,\"_weekdaysRegex\")||Je.call(this),e?this._weekdaysStrictRegex:this._weekdaysRegex):(s(this,\"_weekdaysRegex\")||(this._weekdaysRegex=Ue),this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex)},So.weekdaysShortRegex=function(e){return this._weekdaysParseExact?(s(this,\"_weekdaysRegex\")||Je.call(this),e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(s(this,\"_weekdaysShortRegex\")||(this._weekdaysShortRegex=qe),this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)},So.weekdaysMinRegex=function(e){return this._weekdaysParseExact?(s(this,\"_weekdaysRegex\")||Je.call(this),e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(s(this,\"_weekdaysMinRegex\")||(this._weekdaysMinRegex=We),this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)},So.isPM=function(e){return\"p\"===(e+\"\").toLowerCase().charAt(0)},So.meridiem=function(e,t,o){return e>11?o?\"pm\":\"PM\":o?\"am\":\"AM\"},st(\"en\",{eras:[{since:\"0001-01-01\",until:1/0,offset:1,name:\"Anno Domini\",narrow:\"AD\",abbr:\"AD\"},{since:\"0000-12-31\",until:-1/0,offset:1,name:\"Before Christ\",narrow:\"BC\",abbr:\"BC\"}],dayOfMonthOrdinalParse:/\\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1===K(e%100/10)?\"th\":1===t?\"st\":2===t?\"nd\":3===t?\"rd\":\"th\")}}),n.lang=b(\"moment.lang is deprecated. Use moment.locale instead.\",st),n.langData=b(\"moment.langData is deprecated. Use moment.localeData instead.\",lt);var Eo=Math.abs;function No(e,t,o,n){var i=qt(t,o);return e._milliseconds+=n*i._milliseconds,e._days+=n*i._days,e._months+=n*i._months,e._bubble()}function Mo(e){return e<0?Math.floor(e):Math.ceil(e)}function Io(e){return 4800*e/146097}function Po(e){return 146097*e/4800}function Oo(e){return function(){return this.as(e)}}var Do=Oo(\"ms\"),Ro=Oo(\"s\"),Vo=Oo(\"m\"),Ho=Oo(\"h\"),Yo=Oo(\"d\"),Fo=Oo(\"w\"),zo=Oo(\"M\"),jo=Oo(\"Q\"),Uo=Oo(\"y\");function qo(e){return function(){return this.isValid()?this._data[e]:NaN}}var Wo=qo(\"milliseconds\"),Ko=qo(\"seconds\"),Jo=qo(\"minutes\"),$o=qo(\"hours\"),Xo=qo(\"days\"),Qo=qo(\"months\"),Go=qo(\"years\");var Zo=Math.round,en={ss:44,s:45,m:45,h:22,d:26,w:null,M:11};function tn(e,t,o,n,i){return i.relativeTime(t||1,!!o,e,n)}var on=Math.abs;function nn(e){return(e>0)-(e<0)||+e}function rn(){if(!this.isValid())return this.localeData().invalidDate();var e,t,o,n,i,r,s,a,l=on(this._milliseconds)/1e3,c=on(this._days),d=on(this._months),u=this.asSeconds();return u?(e=W(l/60),t=W(e/60),l%=60,e%=60,o=W(d/12),d%=12,n=l?l.toFixed(3).replace(/\\.?0+$/,\"\"):\"\",i=u<0?\"-\":\"\",r=nn(this._months)!==nn(u)?\"-\":\"\",s=nn(this._days)!==nn(u)?\"-\":\"\",a=nn(this._milliseconds)!==nn(u)?\"-\":\"\",i+\"P\"+(o?r+o+\"Y\":\"\")+(d?r+d+\"M\":\"\")+(c?s+c+\"D\":\"\")+(t||e||l?\"T\":\"\")+(t?a+t+\"H\":\"\")+(e?a+e+\"M\":\"\")+(l?a+n+\"S\":\"\")):\"P0D\"}var sn=Pt.prototype;return sn.isValid=function(){return this._isValid},sn.abs=function(){var e=this._data;return this._milliseconds=Eo(this._milliseconds),this._days=Eo(this._days),this._months=Eo(this._months),e.milliseconds=Eo(e.milliseconds),e.seconds=Eo(e.seconds),e.minutes=Eo(e.minutes),e.hours=Eo(e.hours),e.months=Eo(e.months),e.years=Eo(e.years),this},sn.add=function(e,t){return No(this,e,t,1)},sn.subtract=function(e,t){return No(this,e,t,-1)},sn.as=function(e){if(!this.isValid())return NaN;var t,o,n=this._milliseconds;if(\"month\"===(e=F(e))||\"quarter\"===e||\"year\"===e)switch(t=this._days+n/864e5,o=this._months+Io(t),e){case\"month\":return o;case\"quarter\":return o/3;case\"year\":return o/12}else switch(t=this._days+Math.round(Po(this._months)),e){case\"week\":return t/7+n/6048e5;case\"day\":return t+n/864e5;case\"hour\":return 24*t+n/36e5;case\"minute\":return 1440*t+n/6e4;case\"second\":return 86400*t+n/1e3;case\"millisecond\":return Math.floor(864e5*t)+n;default:throw new Error(\"Unknown unit \"+e)}},sn.asMilliseconds=Do,sn.asSeconds=Ro,sn.asMinutes=Vo,sn.asHours=Ho,sn.asDays=Yo,sn.asWeeks=Fo,sn.asMonths=zo,sn.asQuarters=jo,sn.asYears=Uo,sn.valueOf=function(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*K(this._months/12):NaN},sn._bubble=function(){var e,t,o,n,i,r=this._milliseconds,s=this._days,a=this._months,l=this._data;return r>=0&&s>=0&&a>=0||r<=0&&s<=0&&a<=0||(r+=864e5*Mo(Po(a)+s),s=0,a=0),l.milliseconds=r%1e3,e=W(r/1e3),l.seconds=e%60,t=W(e/60),l.minutes=t%60,o=W(t/60),l.hours=o%24,s+=W(o/24),a+=i=W(Io(s)),s-=Mo(Po(i)),n=W(a/12),a%=12,l.days=s,l.months=a,l.years=n,this},sn.clone=function(){return qt(this)},sn.get=function(e){return e=F(e),this.isValid()?this[e+\"s\"]():NaN},sn.milliseconds=Wo,sn.seconds=Ko,sn.minutes=Jo,sn.hours=$o,sn.days=Xo,sn.weeks=function(){return W(this.days()/7)},sn.months=Qo,sn.years=Go,sn.humanize=function(e,t){if(!this.isValid())return this.localeData().invalidDate();var o,n,i=!1,r=en;return\"object\"==typeof e&&(t=e,e=!1),\"boolean\"==typeof e&&(i=e),\"object\"==typeof t&&(r=Object.assign({},en,t),null!=t.s&&null==t.ss&&(r.ss=t.s-1)),n=function(e,t,o,n){var i=qt(e).abs(),r=Zo(i.as(\"s\")),s=Zo(i.as(\"m\")),a=Zo(i.as(\"h\")),l=Zo(i.as(\"d\")),c=Zo(i.as(\"M\")),d=Zo(i.as(\"w\")),u=Zo(i.as(\"y\")),h=r<=o.ss&&[\"s\",r]||r<o.s&&[\"ss\",r]||s<=1&&[\"m\"]||s<o.m&&[\"mm\",s]||a<=1&&[\"h\"]||a<o.h&&[\"hh\",a]||l<=1&&[\"d\"]||l<o.d&&[\"dd\",l];return null!=o.w&&(h=h||d<=1&&[\"w\"]||d<o.w&&[\"ww\",d]),(h=h||c<=1&&[\"M\"]||c<o.M&&[\"MM\",c]||u<=1&&[\"y\"]||[\"yy\",u])[2]=t,h[3]=+e>0,h[4]=n,tn.apply(null,h)}(this,!i,r,o=this.localeData()),i&&(n=o.pastFuture(+this,n)),o.postformat(n)},sn.toISOString=rn,sn.toString=rn,sn.toJSON=rn,sn.locale=oo,sn.localeData=io,sn.toIsoString=b(\"toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)\",rn),sn.lang=no,O(\"X\",0,0,\"unix\"),O(\"x\",0,0,\"valueOf\"),fe(\"x\",de),fe(\"X\",/[+-]?\\d+(\\.\\d{1,3})?/),ke(\"X\",(function(e,t,o){o._d=new Date(1e3*parseFloat(e))})),ke(\"x\",(function(e,t,o){o._d=new Date(K(e))})),\n//! moment.js\nn.version=\"2.29.1\",function(e){t=e}(Lt),n.fn=_o,n.min=function(){return Nt(\"isBefore\",[].slice.call(arguments,0))},n.max=function(){return Nt(\"isAfter\",[].slice.call(arguments,0))},n.now=function(){return Date.now?Date.now():+new Date},n.utc=p,n.unix=function(e){return Lt(1e3*e)},n.months=function(e,t){return Lo(e,t,\"months\")},n.isDate=d,n.locale=st,n.invalid=g,n.duration=qt,n.isMoment=A,n.weekdays=function(e,t,o){return To(e,t,o,\"weekdays\")},n.parseZone=function(){return Lt.apply(null,arguments).parseZone()},n.localeData=lt,n.isDuration=Ot,n.monthsShort=function(e,t){return Lo(e,t,\"monthsShort\")},n.weekdaysMin=function(e,t,o){return To(e,t,o,\"weekdaysMin\")},n.defineLocale=at,n.updateLocale=function(e,t){if(null!=t){var o,n,i=et;null!=tt[e]&&null!=tt[e].parentLocale?tt[e].set(L(tt[e]._config,t)):(null!=(n=rt(e))&&(i=n._config),t=L(i,t),null==n&&(t.abbr=e),(o=new T(t)).parentLocale=tt[e],tt[e]=o),st(e)}else null!=tt[e]&&(null!=tt[e].parentLocale?(tt[e]=tt[e].parentLocale,e===st()&&st(e)):null!=tt[e]&&delete tt[e]);return tt[e]},n.locales=function(){return _(tt)},n.weekdaysShort=function(e,t,o){return To(e,t,o,\"weekdaysShort\")},n.normalizeUnits=F,n.relativeTimeRounding=function(e){return void 0===e?Zo:\"function\"==typeof e&&(Zo=e,!0)},n.relativeTimeThreshold=function(e,t){return void 0!==en[e]&&(void 0===t?en[e]:(en[e]=t,\"s\"===e&&(en.ss=t-1),!0))},n.calendarFormat=function(e,t){var o=e.diff(t,\"days\",!0);return o<-6?\"sameElse\":o<-1?\"lastWeek\":o<0?\"lastDay\":o<1?\"sameDay\":o<2?\"nextDay\":o<7?\"nextWeek\":\"sameElse\"},n.prototype=_o,n.HTML5_FMT={DATETIME_LOCAL:\"YYYY-MM-DDTHH:mm\",DATETIME_LOCAL_SECONDS:\"YYYY-MM-DDTHH:mm:ss\",DATETIME_LOCAL_MS:\"YYYY-MM-DDTHH:mm:ss.SSS\",DATE:\"YYYY-MM-DD\",TIME:\"HH:mm\",TIME_SECONDS:\"HH:mm:ss\",TIME_MS:\"HH:mm:ss.SSS\",WEEK:\"GGGG-[W]WW\",MONTH:\"YYYY-MM\"},n}()},St(Tt={exports:{}},Tt.exports),Tt.exports);\"loading\"===document.readyState?document.addEventListener(\"readystatechange\",(function(){\"interactive\"===document.readyState&&(Mt=ut())})):Mt=ut();class Dt{constructor(e){this.state=st(),this._boundedMessageListener=null,this._eventErrLog=[],this._eventLog=[],this._eventSendLog=[],this._previousEvent=null,this._dataAttribute=null,this._uiController=null,this._screenShotMode=!1,this._isTopFrame=window.location===window.parent.location,this._isRecordingClicks=!0,this.socketUrl=e,this.observer=null,this.inputEventListenerFun=L$1(this._recordEvent.bind(this),200)}boot(){this.state.startRecorder=!0,\"loading\"===document.readyState?document.addEventListener(\"readystatechange\",(()=>{\"complete\"===document.readyState&&(this._initializeRecorder(),this.ovserverDom())})):(this._initializeRecorder(),this.ovserverDom())}off(){this.state.startRecorder=!1,this.observer.disconnect()}ovserverDom(){this.observer=new MutationObserver((e=>{for(let t=0;t<e.length;t++)e[t].addedNodes.length>0&&[...document.getElementsByTagName(\"input\"),...document.getElementsByTagName(\"textarea\")].forEach((e=>{switch(e.type){case\"text\":case\"number\":case\"password\":case\"textarea\":e.addEventListener(\"input\",this.inputEventListenerFun,!0)}}))})),this.observer.observe(document.body,{childList:!0,subtree:!0})}_initializeRecorder(){const e=Object.values(_t);window.screenRecorderAddedControlListeners||(console.log(\"_addAllListeners:\",e),this._addAllListeners(e),window.screenRecorderAddedControlListeners=!0),window.document.screenRecorderAddedControlListeners||(window.document.screenRecorderAddedControlListeners=!0)}_addAllListeners(e){e.forEach((e=>{let t=this._recordEvent.bind(this);if(console.log(\"type:\",e),\"input\"===e){let e=[...document.getElementsByTagName(\"input\"),...document.getElementsByTagName(\"textarea\")];return console.log(\"inputList:\",e),void e.forEach((e=>{switch(console.log(e.type),e.type){case\"text\":case\"number\":case\"password\":case\"textarea\":e.addEventListener(\"input\",this.inputEventListenerFun,!0)}}))}\"touchmove\"===e&&(t=P$1(t,50)),\"scroll\"===e&&(t=P$1(t,50)),window.addEventListener(e,t,!0)}))}_sendMessage(e){this.state.mySocket&&this.state.mySocket.webSocketState&&(console.log(\"send message:\",e,window.eventRecorder),this.state.mySocket.send({type:\"BROADCAST\",contentType:\"action\",channelSerial:this.state.channelSerial,data:JSON.stringify({...e,connectSerial:this.state.connectSerial})}),this._eventSendLog.push(e))}_recordEvent(e){if(this.state.aid=R(),this.state.startRecorder)try{let t=\"\",o=e.target;if(e.target===document?(t=\"html\",o=e.target.scrollingElement):(e.target.id,t=this._dataAttribute&&e.target.hasAttribute&&e.target.hasAttribute(this._dataAttribute)?Dt._formatDataSelector(e.target,this._dataAttribute):Mt.find(e.target)),t.indexOf(\"body\")<0&&\"html\"!==t)return;let n={};\"scroll\"===e.type&&(n={scrollLeft:o.scrollLeft,scrollTop:o.scrollTop}),Array.from(e.target.classList||[]).map((e=>`.${yt(e,{isIdentifier:!0})}`));const i={};switch(e.type){case\"click\":case\"click\":case\"touchstart\":case\"touchmove\":case\"touchend\":i.altKey=e?.altKey,i.metaKey=e?.metaKey,i.ctrlKey=e?.ctrlKey,e?.targetTouches&&(i.targetTouches={clientX:e?.targetTouches[0]?.clientX,clientY:e?.targetTouches[0]?.clientY,force:e?.targetTouches[0]?.force,identifier:e?.targetTouches[0]?.identifier,pageX:e?.targetTouches[0]?.pageX,pageY:e?.targetTouches[0]?.pageY,radiusX:e?.targetTouches[0]?.radiusX,radiusY:e?.targetTouches[0]?.radiusY,rotationAngle:e?.targetTouches[0]?.rotationAngle,screenX:e?.targetTouches[0]?.screenX,screenY:e?.targetTouches[0]?.screenY}),e?.changedTouches&&(i.changedTouches={clientX:e?.changedTouches[0]?.clientX,clientY:e?.changedTouches[0]?.clientY,force:e?.changedTouches[0]?.force,identifier:e?.changedTouches[0]?.identifier,pageX:e?.changedTouches[0]?.pageX,pageY:e?.changedTouches[0]?.pageY,radiusX:e?.changedTouches[0]?.radiusX,radiusY:e?.changedTouches[0]?.radiusY,rotationAngle:e?.changedTouches[0]?.rotationAngle,screenX:e?.changedTouches[0]?.screenX,screenY:e?.changedTouches[0]?.screenY})}let r=(new Date).getTime(),s=Dt._getCoordinates(e);const a={eventId:R(),eventType:\"VIEW_COMMON_EVENT\",dateTime:Nt(r).format(\"YYYY-MM-DD HH:mm:ss.SSS\"),diffTime:this.state.actionTime?r-this.state.actionTime:0,params:{},viewC12c:{actionType:F$1[e.type].actionType,actionName:F$1[e.type].actionName,params:{},viewPath:t,viewPathDetail:t,text:kt(o),touchX:s?.x,touchY:s?.y,scrollX:n?.scrollTop,scrollY:n?.scrollLeft,inputValue:o?.value,position:this.getPositon(o),cloneEvent:i}};this.state.actionTime=r,this._eventLog.push(a),this._sendMessage(a)}catch(e){this._eventErrLog.push(e),console.log(\"[eventErrLog]:\",e)}}getPositon(e){let t=-window.scrollY,o=-window.scrollX;const n=e.offsetWidth,i=e.offsetHeight;for(;e&&e!==document.body;)t+=e.offsetTop,o+=e.offsetLeft,e=e.offsetParent;return{width:`${n}px`,height:`${i}px`,top:t-1+\"px\",left:o-1+\"px\"}}_getEventLog(){return this._eventLog}_clearEventLog(){this._eventLog=[]}sendEventRight(){return this._eventLog.length===this._eventSendLog.length}static _getCoordinates(e){return{mouseup:!0,mousedown:!0,mousemove:!0,mouseover:!0,touchstart:!0,touchend:!0,touchmove:!0}[e.type]?{x:e.clientX||e.changedTouches[0].clientX,y:e.clientY||e.changedTouches[0].clientY}:null}static _formatDataSelector(e,t){return`[${t}=\"${e.getAttribute(t)}\"]`}}class Yt{constructor(e){this.instance=getCurrentInstance(),this.state=st(),this.socket=null,this.webSocketState=null,this.reconnectTimer=null,this.waitingServerTime=null,this.startHeartBeatTimee=null,this.heartBeat={time:3e4,timeout:3e3,reconnect:1e4},this.isReconnect=!0,this.wsUrl=e,this.init(),this.socket&&(this.onopen((()=>{this.state=st(),this.webSocketState=!0,this.send({type:\"LOGIN\",channelSerial:this.state.channelSerial,data:JSON.stringify({manufacturer:window.location.host,connectSerial:this.state.connectSerial||void 0})}),$bus$1.emit(\"webSocketState\"),this.startHeartBeat(this.heartBeat.time)})),this.onerror((e=>{console.log(e),this.webSocketState=!1,this.socket=null,this.state.socketConnect=!1})),this.onclose((e=>{console.log(e),this.socket=null,this.state.socketConnect=!1,this.webSocketState=!1,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.waitingServerTime&&clearTimeout(this.waitingServerTime),this.startHeartBeatTime&&clearTimeout(this.startHeartBeatTime)})),this.onmessage((e=>{\"HEART_BEAT\"===JSON.parse(e.data).type&&(this.webSocketState=!0)})))}init(){try{this.socket||(this.socket=new WebSocket(this.wsUrl)),this.socket&&this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null,this.socket=new WebSocket(this.wsUrl))}catch(e){console.error(e)}}onopen(e){this.socket.addEventListener(\"open\",e)}onclose(e){this.socket.addEventListener(\"close\",e)}onmessage(e){this.socket.addEventListener(\"message\",e)}onerror(e){this.socket.addEventListener(\"error\",e)}close(){this.socket&&this.socket?.close(),this.socket=null,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.waitingServerTime&&clearTimeout(this.waitingServerTime),this.startHeartBeatTime&&clearTimeout(this.startHeartBeatTime)}send(e){this.socket&&this.socket.send(JSON.stringify(e))}startHeartBeat(e){this.startHeartBeatTime=setTimeout((()=>{this.send({type:\"HEART_BEAT\",data:JSON.stringify({time:new Date,connectSerial:this.state.connectSerial})}),this.waitingServer()}),e)}waitingServer(){this.webSocketState=!1,this.waitingServerTime=setTimeout((()=>{if(this.webSocketState)this.startHeartBeat(this.heartBeat.time);else{console.log(\"心跳无响应，已断线\");try{this.close(),this.instance.proxy.$toast(\"连接已经断开\",2e3)}catch(e){console.log(\"连接已关闭，无需关闭\")}this.reconnectWebSocket()}}),this.heartBeat.timeout)}reconnectWebSocket(){this.isReconnect&&(this.reconnectTimer=setTimeout((()=>{this.init()}),this.heartBeat.reconnect))}}class Ct{constructor(e){this.socketUrl=e,this.state=st(),this.init()}init(){!this.state.mySocket&&(this.state.mySocket=new Yt(this.socketUrl)),this.state.mySocket&&this.state.mySocket.socket&&(this.state.startPlayback=!0,this.state.mySocket.onmessage((e=>{try{let t,o=JSON.parse(e.data);if(\"LOGIN\"===o.type)t=JSON.parse(o.data),this.state.connectSerial=t.connectSerial,this.state.isNative&&localStorage.setItem(\"nativeConnectSerial\",t.connectSerial);else if(\"BROADCAST\"===o.type)if(t=JSON.parse(o.data),\"action\"===o.contentType){if(this.state.aid=t.eventId,this.state.startPlayback){let e,o=document.querySelector(t.viewC12c.viewPath);switch(t.viewC12c.actionType){case\"ON_CLICK\":o.click();break;case\"ON_TOUCH_START\":e=document.createEvent(\"MouseEvents\"),e.initMouseEvent(\"touchstart\",!0,!0,window,0,t.viewC12c.cloneEvent.targetTouches.screenX,t.viewC12c.cloneEvent.targetTouches.screenY,t.viewC12c.cloneEvent.targetTouches.clientX,t.viewC12c.cloneEvent.targetTouches.clientY,t.viewC12c.cloneEvent.ctrlKey,t.viewC12c.cloneEvent.altKey,t.viewC12c.cloneEvent.shiftKey,t.viewC12c.cloneEvent.metaKey),e.changedTouches=[{clientX:t.viewC12c.cloneEvent.targetTouches.clientX,clientY:t.viewC12c.cloneEvent.targetTouches.clientY,force:t.viewC12c.cloneEvent.targetTouches.force,identifier:t.viewC12c.cloneEvent.targetTouches.identifier,pageX:t.viewC12c.cloneEvent.targetTouches.pageX,pageY:t.viewC12c.cloneEvent.targetTouches.pageY,radiusX:t.viewC12c.cloneEvent.targetTouches.radiusX,radiusY:t.viewC12c.cloneEvent.targetTouches.radiusY,rotationAngle:t.viewC12c.cloneEvent.targetTouches.rotationAngle,screenX:t.viewC12c.cloneEvent.targetTouches.screenX,screenY:t.viewC12c.cloneEvent.targetTouches.screenY}],e.touches=e.changedTouches,e.targetTouches=e.changedTouches,console.log(\"fyq\",e),o.dispatchEvent(e);break;case\"ON_TOUCH_MOVE\":e=document.createEvent(\"MouseEvents\"),e.initMouseEvent(\"touchmove\",!0,!0,window,0,t.viewC12c.cloneEvent.targetTouches.screenX,t.viewC12c.cloneEvent.targetTouches.screenY,t.viewC12c.cloneEvent.targetTouches.clientX,t.viewC12c.cloneEvent.targetTouches.clientY,t.viewC12c.cloneEvent.ctrlKey,t.viewC12c.cloneEvent.altKey,t.viewC12c.cloneEvent.shiftKey,t.viewC12c.cloneEvent.metaKey),e.changedTouches=[{clientX:t.viewC12c.cloneEvent.targetTouches.clientX,clientY:t.viewC12c.cloneEvent.targetTouches.clientY,force:t.viewC12c.cloneEvent.targetTouches.force,identifier:t.viewC12c.cloneEvent.targetTouches.identifier,pageX:t.viewC12c.cloneEvent.targetTouches.pageX,pageY:t.viewC12c.cloneEvent.targetTouches.pageY,radiusX:t.viewC12c.cloneEvent.targetTouches.radiusX,radiusY:t.viewC12c.cloneEvent.targetTouches.radiusY,rotationAngle:t.viewC12c.cloneEvent.targetTouches.rotationAngle,screenX:t.viewC12c.cloneEvent.targetTouches.screenX,screenY:t.viewC12c.cloneEvent.targetTouches.screenY}],e.touches=e.changedTouches,e.targetTouches=e.changedTouches,o.dispatchEvent(e);break;case\"ON_TOUCH_END\":e=document.createEvent(\"MouseEvents\"),e.initMouseEvent(\"touchend\",!0,!0,window,0,t.viewC12c.cloneEvent.changedTouches.screenX,t.viewC12c.cloneEvent.changedTouches.screenY,t.viewC12c.cloneEvent.changedTouches.clientX,t.viewC12c.cloneEvent.changedTouches.clientY,t.viewC12c.cloneEvent.ctrlKey,t.viewC12c.cloneEvent.altKey,t.viewC12c.cloneEvent.shiftKey,t.viewC12c.cloneEvent.metaKey),e.changedTouches=[{clientX:t.viewC12c.cloneEvent.targetTouches.clientX,clientY:t.viewC12c.cloneEvent.targetTouches.clientY,force:t.viewC12c.cloneEvent.targetTouches.force,identifier:t.viewC12c.cloneEvent.targetTouches.identifier,pageX:t.viewC12c.cloneEvent.targetTouches.pageX,pageY:t.viewC12c.cloneEvent.targetTouches.pageY,radiusX:t.viewC12c.cloneEvent.targetTouches.radiusX,radiusY:t.viewC12c.cloneEvent.targetTouches.radiusY,rotationAngle:t.viewC12c.cloneEvent.targetTouches.rotationAngle,screenX:t.viewC12c.cloneEvent.targetTouches.screenX,screenY:t.viewC12c.cloneEvent.targetTouches.screenY}],e.touches=e.changedTouches,e.targetTouches=e.changedTouches,o.dispatchEvent(e);break;case\"ON_INPUT_CHANGE\":e=document.createEvent(\"Events\"),e.initEvent(\"input\",!0,!0),o.value=t.viewC12c.text,o.dispatchEvent(e);break;case\"ON_SCROLL\":o.scrollTop=t.viewC12c.scrollX,o.scrollLeft=t.viewC12c.scrollY}}}else\"mc_host\"===o.contentType&&(this.state.isHost=!1,this.state.startPlayback=!0)}catch(e){console.error(e)}})))}close(){this.state.mySocket&&(this.state.mySocket.close(),this.state.mySocket=null),this.state.startPlayback=!1}}var Et={components:{RouterContainer:$,IndependContainer:q,ElementsHighlight:J,HostSuspendedBall:ot},directives:{dragable:dragable},data:()=>({btnConfig:{name:\"dokit_entry\",opacity:.5,left:window.innerWidth-50,top:window.innerHeight-100,safeBottom:50,eventPlayback:null}}),created(){this.$store.state.aid=R()},watch:{socketConnect:{handler(e,t){e?(this.eventPlayback=new Ct(this.socketUrl),this.$store.state.socketHistoryList.set(this.socketUrl,\"connect\")):this.eventPlayback&&(this.$store.state.socketHistoryList.set(this.socketUrl,\"close\"),this.eventPlayback.close(),this.eventPlayback=null)},immediate:!0},socketHistoryList:{handler(e,t){localStorage.setItem(\"dokit-socket-history-list\",JSON.stringify([...e]))},deep:!0},isHost:{handler(e,t){e?(this.eventPlayback?.state?.mySocket?.webSocketState&&this.eventPlayback.state.mySocket.send({type:\"BROADCAST\",contentType:\"mc_host\",channelSerial:this.channelSerial,data:JSON.stringify({connectSerial:this.eventPlayback.state.connectSerial})}),window.eventRecorder||(window.eventRecorder=new Dt(this.socketUrl)),this.$store.state.startPlayback=!1,window?.eventRecorder?.boot()):window?.eventRecorder?.off()},immediate:!0}},computed:{channelSerial(){return this.state.channelSerial},socketHistoryList(){return this.state.socketHistoryList},highlightElement(){return this.state.highlightElement},showHighlightElement(){return this.state.showHighlightElement},state(){return this.$store.state},showContainer(){return this.state.showContainer},independPlugins(){return this.$store.state.independPlugins},socketConnect(){return this.state.socketConnect},socketUrl(){return this.state.socketUrl},isHost(){return this.state.isHost}},methods:{toggleShowContainer(){lt()}}};const Ot={class:\"dokit-app\"};V(\".dokit-app[data-v-5dbfed36] {\\n  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;\\n  pointer-events: none;\\n  position: fixed;\\n  left: 0;\\n  top: 0;\\n  width: 100%;\\n  height: 100%;\\n  z-index: 100000;\\n}\\n.dokit-app > *[data-v-5dbfed36] {\\n  pointer-events: all;\\n  font-size: 16px;\\n}\\n.dokit-entry-btn[data-v-5dbfed36] {\\n  width: 50px;\\n  height: 50px;\\n  padding: 10px;\\n  box-sizing: border-box;\\n  background-image: url(//pt-starimg.didistatic.com/static/starimg/img/OzaetKDzHr1618905183992.png);\\n  background-size: 50px;\\n  background-position: center;\\n  background-repeat: no-repeat;\\n}\\n.dokit-mask[data-v-5dbfed36] {\\n  position: absolute;\\n  top: 0;\\n  left: 0;\\n  right: 0;\\n  bottom: 0;\\n  z-index: 3;\\n  background-color: #333333;\\n  opacity: 0.3;\\n}\\n\"),Et.render=function(e,t,o,n,i,r){const s=resolveComponent(\"router-container\"),a=resolveComponent(\"independ-container\"),l=resolveComponent(\"elements-highlight\"),c=resolveComponent(\"host-suspendedBall\"),d=resolveDirective(\"dragable\");return openBlock(),createElementBlock(\"div\",Ot,[withDirectives(createBaseVNode(\"div\",{class:\"dokit-entry-btn\",style:{\"z-index\":\"10000\"},onClick:t[0]||(t[0]=(...e)=>r.toggleShowContainer&&r.toggleShowContainer(...e))},null,512),[[d,i.btnConfig]]),withDirectives(createBaseVNode(\"div\",{class:\"dokit-mask\",onClick:t[1]||(t[1]=(...t)=>e.toggleContainer&&e.toggleContainer(...t))},null,512),[[vShow,r.showContainer]]),withDirectives(createVNode(s,null,null,512),[[vShow,r.showContainer]]),withDirectives(createVNode(a,null,null,512),[[vShow,r.independPlugins.length]]),r.showHighlightElement&&r.highlightElement?(openBlock(),createBlock(l,{key:0,element:r.highlightElement},null,8,[\"element\"])):createCommentVNode(\"\",!0),r.socketConnect?(openBlock(),createBlock(c,{key:1})):createCommentVNode(\"\",!0)])},Et.__scopeId=\"data-v-5dbfed36\";var Ht=defineComponent({name:\"DoCol\",componentName:\"DoCol\",props:{span:{type:Number,default:24},tag:{type:String,default:\"div\"},offset:Number,pull:Number,push:Number,xs:[Number,Object],sm:[Number,Object],md:[Number,Object],lg:[Number,Object],xl:[Number,Object]},setup(e,{slots:t}){let o=computed((()=>inject(\"gutter\",0))),n=[],i={paddingLeft:\"\",paddingRight:\"\"};return o&&(i.paddingLeft=o.value/2+\"px\",i.paddingRight=i.paddingLeft),[\"span\",\"offset\",\"pull\",\"push\"].forEach((t=>{(e[t]||0===e[t])&&n.push(\"span\"!==t?`do-col-${t}-${e[t]}`:`do-col-${e[t]}`)})),[\"xs\",\"sm\",\"md\",\"lg\",\"xl\"].forEach((t=>{if(\"number\"==typeof e[t])n.push(`do-col-${t}-${e[t]}`);else if(\"object\"==typeof e[t]){let o=e[t];Object.keys(o).forEach((e=>{n.push(\"span\"!==e?`do-col-${t}-${e}-${o[e]}`:`do-col-${t}-${o[e]}`)}))}})),()=>h$1(e.tag,{class:[\"do-col\",n],style:i},t)}}),At=defineComponent({name:\"DoRow\",componentName:\"DoRow\",props:{tag:{type:String,default:\"div\"},gutter:Number,type:{type:String,default:\"flex\"},justify:{type:String,default:\"start\"},align:{type:String,default:\"top\"}},setup(e,{slots:t}){let o=computed((()=>{const t={};return e.gutter&&(t.marginLeft=`-${e.gutter/2}px`,t.marginRight=t.marginLeft),t}));return provide(\"gutter\",e.gutter),()=>h$1(e.tag,{class:[\"do-row\",\"start\"!==e.justify?`is-justify-${e.justify}`:\"\",\"top\"!==e.align?`is-align-${e.align}`:\"\",{\"do-row--flex\":\"flex\"===e.type}],style:o},t)}});V(\".do-col-pull-0,.do-col-pull-1,.do-col-pull-10,.do-col-pull-11,.do-col-pull-12,.do-col-pull-13,.do-col-pull-14,.do-col-pull-15,.do-col-pull-16,.do-col-pull-17,.do-col-pull-18,.do-col-pull-19,.do-col-pull-2,.do-col-pull-20,.do-col-pull-21,.do-col-pull-22,.do-col-pull-23,.do-col-pull-24,.do-col-pull-3,.do-col-pull-4,.do-col-pull-5,.do-col-pull-6,.do-col-pull-7,.do-col-pull-8,.do-col-pull-9,.do-col-push-0,.do-col-push-1,.do-col-push-10,.do-col-push-11,.do-col-push-12,.do-col-push-13,.do-col-push-14,.do-col-push-15,.do-col-push-16,.do-col-push-17,.do-col-push-18,.do-col-push-19,.do-col-push-2,.do-col-push-20,.do-col-push-21,.do-col-push-22,.do-col-push-23,.do-col-push-24,.do-col-push-3,.do-col-push-4,.do-col-push-5,.do-col-push-6,.do-col-push-7,.do-col-push-8,.do-col-push-9{position:relative}[class*=do-col-]{float:left;-webkit-box-sizing:border-box;box-sizing:border-box}.do-col-0{display:none;width:0%}.do-col-offset-0{margin-left:0}.do-col-pull-0{right:0}.do-col-push-0{left:0}.do-col-1{width:4.16667%}.do-col-offset-1{margin-left:4.16667%}.do-col-pull-1{right:4.16667%}.do-col-push-1{left:4.16667%}.do-col-2{width:8.33333%}.do-col-offset-2{margin-left:8.33333%}.do-col-pull-2{right:8.33333%}.do-col-push-2{left:8.33333%}.do-col-3{width:12.5%}.do-col-offset-3{margin-left:12.5%}.do-col-pull-3{right:12.5%}.do-col-push-3{left:12.5%}.do-col-4{width:16.66667%}.do-col-offset-4{margin-left:16.66667%}.do-col-pull-4{right:16.66667%}.do-col-push-4{left:16.66667%}.do-col-5{width:20.83333%}.do-col-offset-5{margin-left:20.83333%}.do-col-pull-5{right:20.83333%}.do-col-push-5{left:20.83333%}.do-col-6{width:25%}.do-col-offset-6{margin-left:25%}.do-col-pull-6{right:25%}.do-col-push-6{left:25%}.do-col-7{width:29.16667%}.do-col-offset-7{margin-left:29.16667%}.do-col-pull-7{right:29.16667%}.do-col-push-7{left:29.16667%}.do-col-8{width:33.33333%}.do-col-offset-8{margin-left:33.33333%}.do-col-pull-8{right:33.33333%}.do-col-push-8{left:33.33333%}.do-col-9{width:37.5%}.do-col-offset-9{margin-left:37.5%}.do-col-pull-9{right:37.5%}.do-col-push-9{left:37.5%}.do-col-10{width:41.66667%}.do-col-offset-10{margin-left:41.66667%}.do-col-pull-10{right:41.66667%}.do-col-push-10{left:41.66667%}.do-col-11{width:45.83333%}.do-col-offset-11{margin-left:45.83333%}.do-col-pull-11{right:45.83333%}.do-col-push-11{left:45.83333%}.do-col-12{width:50%}.do-col-offset-12{margin-left:50%}.do-col-pull-12{right:50%}.do-col-push-12{left:50%}.do-col-13{width:54.16667%}.do-col-offset-13{margin-left:54.16667%}.do-col-pull-13{right:54.16667%}.do-col-push-13{left:54.16667%}.do-col-14{width:58.33333%}.do-col-offset-14{margin-left:58.33333%}.do-col-pull-14{right:58.33333%}.do-col-push-14{left:58.33333%}.do-col-15{width:62.5%}.do-col-offset-15{margin-left:62.5%}.do-col-pull-15{right:62.5%}.do-col-push-15{left:62.5%}.do-col-16{width:66.66667%}.do-col-offset-16{margin-left:66.66667%}.do-col-pull-16{right:66.66667%}.do-col-push-16{left:66.66667%}.do-col-17{width:70.83333%}.do-col-offset-17{margin-left:70.83333%}.do-col-pull-17{right:70.83333%}.do-col-push-17{left:70.83333%}.do-col-18{width:75%}.do-col-offset-18{margin-left:75%}.do-col-pull-18{right:75%}.do-col-push-18{left:75%}.do-col-19{width:79.16667%}.do-col-offset-19{margin-left:79.16667%}.do-col-pull-19{right:79.16667%}.do-col-push-19{left:79.16667%}.do-col-20{width:83.33333%}.do-col-offset-20{margin-left:83.33333%}.do-col-pull-20{right:83.33333%}.do-col-push-20{left:83.33333%}.do-col-21{width:87.5%}.do-col-offset-21{margin-left:87.5%}.do-col-pull-21{right:87.5%}.do-col-push-21{left:87.5%}.do-col-22{width:91.66667%}.do-col-offset-22{margin-left:91.66667%}.do-col-pull-22{right:91.66667%}.do-col-push-22{left:91.66667%}.do-col-23{width:95.83333%}.do-col-offset-23{margin-left:95.83333%}.do-col-pull-23{right:95.83333%}.do-col-push-23{left:95.83333%}.do-col-24{width:100%}.do-col-offset-24{margin-left:100%}.do-col-pull-24{right:100%}.do-col-push-24{left:100%}@media only screen and (max-width:767px){.do-col-xs-0{display:none;width:0%}.do-col-xs-offset-0{margin-left:0}.do-col-xs-pull-0{position:relative;right:0}.do-col-xs-push-0{position:relative;left:0}.do-col-xs-1{width:4.16667%}.do-col-xs-offset-1{margin-left:4.16667%}.do-col-xs-pull-1{position:relative;right:4.16667%}.do-col-xs-push-1{position:relative;left:4.16667%}.do-col-xs-2{width:8.33333%}.do-col-xs-offset-2{margin-left:8.33333%}.do-col-xs-pull-2{position:relative;right:8.33333%}.do-col-xs-push-2{position:relative;left:8.33333%}.do-col-xs-3{width:12.5%}.do-col-xs-offset-3{margin-left:12.5%}.do-col-xs-pull-3{position:relative;right:12.5%}.do-col-xs-push-3{position:relative;left:12.5%}.do-col-xs-4{width:16.66667%}.do-col-xs-offset-4{margin-left:16.66667%}.do-col-xs-pull-4{position:relative;right:16.66667%}.do-col-xs-push-4{position:relative;left:16.66667%}.do-col-xs-5{width:20.83333%}.do-col-xs-offset-5{margin-left:20.83333%}.do-col-xs-pull-5{position:relative;right:20.83333%}.do-col-xs-push-5{position:relative;left:20.83333%}.do-col-xs-6{width:25%}.do-col-xs-offset-6{margin-left:25%}.do-col-xs-pull-6{position:relative;right:25%}.do-col-xs-push-6{position:relative;left:25%}.do-col-xs-7{width:29.16667%}.do-col-xs-offset-7{margin-left:29.16667%}.do-col-xs-pull-7{position:relative;right:29.16667%}.do-col-xs-push-7{position:relative;left:29.16667%}.do-col-xs-8{width:33.33333%}.do-col-xs-offset-8{margin-left:33.33333%}.do-col-xs-pull-8{position:relative;right:33.33333%}.do-col-xs-push-8{position:relative;left:33.33333%}.do-col-xs-9{width:37.5%}.do-col-xs-offset-9{margin-left:37.5%}.do-col-xs-pull-9{position:relative;right:37.5%}.do-col-xs-push-9{position:relative;left:37.5%}.do-col-xs-10{width:41.66667%}.do-col-xs-offset-10{margin-left:41.66667%}.do-col-xs-pull-10{position:relative;right:41.66667%}.do-col-xs-push-10{position:relative;left:41.66667%}.do-col-xs-11{width:45.83333%}.do-col-xs-offset-11{margin-left:45.83333%}.do-col-xs-pull-11{position:relative;right:45.83333%}.do-col-xs-push-11{position:relative;left:45.83333%}.do-col-xs-12{width:50%}.do-col-xs-offset-12{margin-left:50%}.do-col-xs-pull-12{position:relative;right:50%}.do-col-xs-push-12{position:relative;left:50%}.do-col-xs-13{width:54.16667%}.do-col-xs-offset-13{margin-left:54.16667%}.do-col-xs-pull-13{position:relative;right:54.16667%}.do-col-xs-push-13{position:relative;left:54.16667%}.do-col-xs-14{width:58.33333%}.do-col-xs-offset-14{margin-left:58.33333%}.do-col-xs-pull-14{position:relative;right:58.33333%}.do-col-xs-push-14{position:relative;left:58.33333%}.do-col-xs-15{width:62.5%}.do-col-xs-offset-15{margin-left:62.5%}.do-col-xs-pull-15{position:relative;right:62.5%}.do-col-xs-push-15{position:relative;left:62.5%}.do-col-xs-16{width:66.66667%}.do-col-xs-offset-16{margin-left:66.66667%}.do-col-xs-pull-16{position:relative;right:66.66667%}.do-col-xs-push-16{position:relative;left:66.66667%}.do-col-xs-17{width:70.83333%}.do-col-xs-offset-17{margin-left:70.83333%}.do-col-xs-pull-17{position:relative;right:70.83333%}.do-col-xs-push-17{position:relative;left:70.83333%}.do-col-xs-18{width:75%}.do-col-xs-offset-18{margin-left:75%}.do-col-xs-pull-18{position:relative;right:75%}.do-col-xs-push-18{position:relative;left:75%}.do-col-xs-19{width:79.16667%}.do-col-xs-offset-19{margin-left:79.16667%}.do-col-xs-pull-19{position:relative;right:79.16667%}.do-col-xs-push-19{position:relative;left:79.16667%}.do-col-xs-20{width:83.33333%}.do-col-xs-offset-20{margin-left:83.33333%}.do-col-xs-pull-20{position:relative;right:83.33333%}.do-col-xs-push-20{position:relative;left:83.33333%}.do-col-xs-21{width:87.5%}.do-col-xs-offset-21{margin-left:87.5%}.do-col-xs-pull-21{position:relative;right:87.5%}.do-col-xs-push-21{position:relative;left:87.5%}.do-col-xs-22{width:91.66667%}.do-col-xs-offset-22{margin-left:91.66667%}.do-col-xs-pull-22{position:relative;right:91.66667%}.do-col-xs-push-22{position:relative;left:91.66667%}.do-col-xs-23{width:95.83333%}.do-col-xs-offset-23{margin-left:95.83333%}.do-col-xs-pull-23{position:relative;right:95.83333%}.do-col-xs-push-23{position:relative;left:95.83333%}.do-col-xs-24{width:100%}.do-col-xs-offset-24{margin-left:100%}.do-col-xs-pull-24{position:relative;right:100%}.do-col-xs-push-24{position:relative;left:100%}}@media only screen and (min-width:768px){.do-col-sm-0{display:none;width:0%}.do-col-sm-offset-0{margin-left:0}.do-col-sm-pull-0{position:relative;right:0}.do-col-sm-push-0{position:relative;left:0}.do-col-sm-1{width:4.16667%}.do-col-sm-offset-1{margin-left:4.16667%}.do-col-sm-pull-1{position:relative;right:4.16667%}.do-col-sm-push-1{position:relative;left:4.16667%}.do-col-sm-2{width:8.33333%}.do-col-sm-offset-2{margin-left:8.33333%}.do-col-sm-pull-2{position:relative;right:8.33333%}.do-col-sm-push-2{position:relative;left:8.33333%}.do-col-sm-3{width:12.5%}.do-col-sm-offset-3{margin-left:12.5%}.do-col-sm-pull-3{position:relative;right:12.5%}.do-col-sm-push-3{position:relative;left:12.5%}.do-col-sm-4{width:16.66667%}.do-col-sm-offset-4{margin-left:16.66667%}.do-col-sm-pull-4{position:relative;right:16.66667%}.do-col-sm-push-4{position:relative;left:16.66667%}.do-col-sm-5{width:20.83333%}.do-col-sm-offset-5{margin-left:20.83333%}.do-col-sm-pull-5{position:relative;right:20.83333%}.do-col-sm-push-5{position:relative;left:20.83333%}.do-col-sm-6{width:25%}.do-col-sm-offset-6{margin-left:25%}.do-col-sm-pull-6{position:relative;right:25%}.do-col-sm-push-6{position:relative;left:25%}.do-col-sm-7{width:29.16667%}.do-col-sm-offset-7{margin-left:29.16667%}.do-col-sm-pull-7{position:relative;right:29.16667%}.do-col-sm-push-7{position:relative;left:29.16667%}.do-col-sm-8{width:33.33333%}.do-col-sm-offset-8{margin-left:33.33333%}.do-col-sm-pull-8{position:relative;right:33.33333%}.do-col-sm-push-8{position:relative;left:33.33333%}.do-col-sm-9{width:37.5%}.do-col-sm-offset-9{margin-left:37.5%}.do-col-sm-pull-9{position:relative;right:37.5%}.do-col-sm-push-9{position:relative;left:37.5%}.do-col-sm-10{width:41.66667%}.do-col-sm-offset-10{margin-left:41.66667%}.do-col-sm-pull-10{position:relative;right:41.66667%}.do-col-sm-push-10{position:relative;left:41.66667%}.do-col-sm-11{width:45.83333%}.do-col-sm-offset-11{margin-left:45.83333%}.do-col-sm-pull-11{position:relative;right:45.83333%}.do-col-sm-push-11{position:relative;left:45.83333%}.do-col-sm-12{width:50%}.do-col-sm-offset-12{margin-left:50%}.do-col-sm-pull-12{position:relative;right:50%}.do-col-sm-push-12{position:relative;left:50%}.do-col-sm-13{width:54.16667%}.do-col-sm-offset-13{margin-left:54.16667%}.do-col-sm-pull-13{position:relative;right:54.16667%}.do-col-sm-push-13{position:relative;left:54.16667%}.do-col-sm-14{width:58.33333%}.do-col-sm-offset-14{margin-left:58.33333%}.do-col-sm-pull-14{position:relative;right:58.33333%}.do-col-sm-push-14{position:relative;left:58.33333%}.do-col-sm-15{width:62.5%}.do-col-sm-offset-15{margin-left:62.5%}.do-col-sm-pull-15{position:relative;right:62.5%}.do-col-sm-push-15{position:relative;left:62.5%}.do-col-sm-16{width:66.66667%}.do-col-sm-offset-16{margin-left:66.66667%}.do-col-sm-pull-16{position:relative;right:66.66667%}.do-col-sm-push-16{position:relative;left:66.66667%}.do-col-sm-17{width:70.83333%}.do-col-sm-offset-17{margin-left:70.83333%}.do-col-sm-pull-17{position:relative;right:70.83333%}.do-col-sm-push-17{position:relative;left:70.83333%}.do-col-sm-18{width:75%}.do-col-sm-offset-18{margin-left:75%}.do-col-sm-pull-18{position:relative;right:75%}.do-col-sm-push-18{position:relative;left:75%}.do-col-sm-19{width:79.16667%}.do-col-sm-offset-19{margin-left:79.16667%}.do-col-sm-pull-19{position:relative;right:79.16667%}.do-col-sm-push-19{position:relative;left:79.16667%}.do-col-sm-20{width:83.33333%}.do-col-sm-offset-20{margin-left:83.33333%}.do-col-sm-pull-20{position:relative;right:83.33333%}.do-col-sm-push-20{position:relative;left:83.33333%}.do-col-sm-21{width:87.5%}.do-col-sm-offset-21{margin-left:87.5%}.do-col-sm-pull-21{position:relative;right:87.5%}.do-col-sm-push-21{position:relative;left:87.5%}.do-col-sm-22{width:91.66667%}.do-col-sm-offset-22{margin-left:91.66667%}.do-col-sm-pull-22{position:relative;right:91.66667%}.do-col-sm-push-22{position:relative;left:91.66667%}.do-col-sm-23{width:95.83333%}.do-col-sm-offset-23{margin-left:95.83333%}.do-col-sm-pull-23{position:relative;right:95.83333%}.do-col-sm-push-23{position:relative;left:95.83333%}.do-col-sm-24{width:100%}.do-col-sm-offset-24{margin-left:100%}.do-col-sm-pull-24{position:relative;right:100%}.do-col-sm-push-24{position:relative;left:100%}}@media only screen and (min-width:992px){.do-col-md-0{display:none;width:0%}.do-col-md-offset-0{margin-left:0}.do-col-md-pull-0{position:relative;right:0}.do-col-md-push-0{position:relative;left:0}.do-col-md-1{width:4.16667%}.do-col-md-offset-1{margin-left:4.16667%}.do-col-md-pull-1{position:relative;right:4.16667%}.do-col-md-push-1{position:relative;left:4.16667%}.do-col-md-2{width:8.33333%}.do-col-md-offset-2{margin-left:8.33333%}.do-col-md-pull-2{position:relative;right:8.33333%}.do-col-md-push-2{position:relative;left:8.33333%}.do-col-md-3{width:12.5%}.do-col-md-offset-3{margin-left:12.5%}.do-col-md-pull-3{position:relative;right:12.5%}.do-col-md-push-3{position:relative;left:12.5%}.do-col-md-4{width:16.66667%}.do-col-md-offset-4{margin-left:16.66667%}.do-col-md-pull-4{position:relative;right:16.66667%}.do-col-md-push-4{position:relative;left:16.66667%}.do-col-md-5{width:20.83333%}.do-col-md-offset-5{margin-left:20.83333%}.do-col-md-pull-5{position:relative;right:20.83333%}.do-col-md-push-5{position:relative;left:20.83333%}.do-col-md-6{width:25%}.do-col-md-offset-6{margin-left:25%}.do-col-md-pull-6{position:relative;right:25%}.do-col-md-push-6{position:relative;left:25%}.do-col-md-7{width:29.16667%}.do-col-md-offset-7{margin-left:29.16667%}.do-col-md-pull-7{position:relative;right:29.16667%}.do-col-md-push-7{position:relative;left:29.16667%}.do-col-md-8{width:33.33333%}.do-col-md-offset-8{margin-left:33.33333%}.do-col-md-pull-8{position:relative;right:33.33333%}.do-col-md-push-8{position:relative;left:33.33333%}.do-col-md-9{width:37.5%}.do-col-md-offset-9{margin-left:37.5%}.do-col-md-pull-9{position:relative;right:37.5%}.do-col-md-push-9{position:relative;left:37.5%}.do-col-md-10{width:41.66667%}.do-col-md-offset-10{margin-left:41.66667%}.do-col-md-pull-10{position:relative;right:41.66667%}.do-col-md-push-10{position:relative;left:41.66667%}.do-col-md-11{width:45.83333%}.do-col-md-offset-11{margin-left:45.83333%}.do-col-md-pull-11{position:relative;right:45.83333%}.do-col-md-push-11{position:relative;left:45.83333%}.do-col-md-12{width:50%}.do-col-md-offset-12{margin-left:50%}.do-col-md-pull-12{position:relative;right:50%}.do-col-md-push-12{position:relative;left:50%}.do-col-md-13{width:54.16667%}.do-col-md-offset-13{margin-left:54.16667%}.do-col-md-pull-13{position:relative;right:54.16667%}.do-col-md-push-13{position:relative;left:54.16667%}.do-col-md-14{width:58.33333%}.do-col-md-offset-14{margin-left:58.33333%}.do-col-md-pull-14{position:relative;right:58.33333%}.do-col-md-push-14{position:relative;left:58.33333%}.do-col-md-15{width:62.5%}.do-col-md-offset-15{margin-left:62.5%}.do-col-md-pull-15{position:relative;right:62.5%}.do-col-md-push-15{position:relative;left:62.5%}.do-col-md-16{width:66.66667%}.do-col-md-offset-16{margin-left:66.66667%}.do-col-md-pull-16{position:relative;right:66.66667%}.do-col-md-push-16{position:relative;left:66.66667%}.do-col-md-17{width:70.83333%}.do-col-md-offset-17{margin-left:70.83333%}.do-col-md-pull-17{position:relative;right:70.83333%}.do-col-md-push-17{position:relative;left:70.83333%}.do-col-md-18{width:75%}.do-col-md-offset-18{margin-left:75%}.do-col-md-pull-18{position:relative;right:75%}.do-col-md-push-18{position:relative;left:75%}.do-col-md-19{width:79.16667%}.do-col-md-offset-19{margin-left:79.16667%}.do-col-md-pull-19{position:relative;right:79.16667%}.do-col-md-push-19{position:relative;left:79.16667%}.do-col-md-20{width:83.33333%}.do-col-md-offset-20{margin-left:83.33333%}.do-col-md-pull-20{position:relative;right:83.33333%}.do-col-md-push-20{position:relative;left:83.33333%}.do-col-md-21{width:87.5%}.do-col-md-offset-21{margin-left:87.5%}.do-col-md-pull-21{position:relative;right:87.5%}.do-col-md-push-21{position:relative;left:87.5%}.do-col-md-22{width:91.66667%}.do-col-md-offset-22{margin-left:91.66667%}.do-col-md-pull-22{position:relative;right:91.66667%}.do-col-md-push-22{position:relative;left:91.66667%}.do-col-md-23{width:95.83333%}.do-col-md-offset-23{margin-left:95.83333%}.do-col-md-pull-23{position:relative;right:95.83333%}.do-col-md-push-23{position:relative;left:95.83333%}.do-col-md-24{width:100%}.do-col-md-offset-24{margin-left:100%}.do-col-md-pull-24{position:relative;right:100%}.do-col-md-push-24{position:relative;left:100%}}@media only screen and (min-width:1200px){.do-col-lg-0{display:none;width:0%}.do-col-lg-offset-0{margin-left:0}.do-col-lg-pull-0{position:relative;right:0}.do-col-lg-push-0{position:relative;left:0}.do-col-lg-1{width:4.16667%}.do-col-lg-offset-1{margin-left:4.16667%}.do-col-lg-pull-1{position:relative;right:4.16667%}.do-col-lg-push-1{position:relative;left:4.16667%}.do-col-lg-2{width:8.33333%}.do-col-lg-offset-2{margin-left:8.33333%}.do-col-lg-pull-2{position:relative;right:8.33333%}.do-col-lg-push-2{position:relative;left:8.33333%}.do-col-lg-3{width:12.5%}.do-col-lg-offset-3{margin-left:12.5%}.do-col-lg-pull-3{position:relative;right:12.5%}.do-col-lg-push-3{position:relative;left:12.5%}.do-col-lg-4{width:16.66667%}.do-col-lg-offset-4{margin-left:16.66667%}.do-col-lg-pull-4{position:relative;right:16.66667%}.do-col-lg-push-4{position:relative;left:16.66667%}.do-col-lg-5{width:20.83333%}.do-col-lg-offset-5{margin-left:20.83333%}.do-col-lg-pull-5{position:relative;right:20.83333%}.do-col-lg-push-5{position:relative;left:20.83333%}.do-col-lg-6{width:25%}.do-col-lg-offset-6{margin-left:25%}.do-col-lg-pull-6{position:relative;right:25%}.do-col-lg-push-6{position:relative;left:25%}.do-col-lg-7{width:29.16667%}.do-col-lg-offset-7{margin-left:29.16667%}.do-col-lg-pull-7{position:relative;right:29.16667%}.do-col-lg-push-7{position:relative;left:29.16667%}.do-col-lg-8{width:33.33333%}.do-col-lg-offset-8{margin-left:33.33333%}.do-col-lg-pull-8{position:relative;right:33.33333%}.do-col-lg-push-8{position:relative;left:33.33333%}.do-col-lg-9{width:37.5%}.do-col-lg-offset-9{margin-left:37.5%}.do-col-lg-pull-9{position:relative;right:37.5%}.do-col-lg-push-9{position:relative;left:37.5%}.do-col-lg-10{width:41.66667%}.do-col-lg-offset-10{margin-left:41.66667%}.do-col-lg-pull-10{position:relative;right:41.66667%}.do-col-lg-push-10{position:relative;left:41.66667%}.do-col-lg-11{width:45.83333%}.do-col-lg-offset-11{margin-left:45.83333%}.do-col-lg-pull-11{position:relative;right:45.83333%}.do-col-lg-push-11{position:relative;left:45.83333%}.do-col-lg-12{width:50%}.do-col-lg-offset-12{margin-left:50%}.do-col-lg-pull-12{position:relative;right:50%}.do-col-lg-push-12{position:relative;left:50%}.do-col-lg-13{width:54.16667%}.do-col-lg-offset-13{margin-left:54.16667%}.do-col-lg-pull-13{position:relative;right:54.16667%}.do-col-lg-push-13{position:relative;left:54.16667%}.do-col-lg-14{width:58.33333%}.do-col-lg-offset-14{margin-left:58.33333%}.do-col-lg-pull-14{position:relative;right:58.33333%}.do-col-lg-push-14{position:relative;left:58.33333%}.do-col-lg-15{width:62.5%}.do-col-lg-offset-15{margin-left:62.5%}.do-col-lg-pull-15{position:relative;right:62.5%}.do-col-lg-push-15{position:relative;left:62.5%}.do-col-lg-16{width:66.66667%}.do-col-lg-offset-16{margin-left:66.66667%}.do-col-lg-pull-16{position:relative;right:66.66667%}.do-col-lg-push-16{position:relative;left:66.66667%}.do-col-lg-17{width:70.83333%}.do-col-lg-offset-17{margin-left:70.83333%}.do-col-lg-pull-17{position:relative;right:70.83333%}.do-col-lg-push-17{position:relative;left:70.83333%}.do-col-lg-18{width:75%}.do-col-lg-offset-18{margin-left:75%}.do-col-lg-pull-18{position:relative;right:75%}.do-col-lg-push-18{position:relative;left:75%}.do-col-lg-19{width:79.16667%}.do-col-lg-offset-19{margin-left:79.16667%}.do-col-lg-pull-19{position:relative;right:79.16667%}.do-col-lg-push-19{position:relative;left:79.16667%}.do-col-lg-20{width:83.33333%}.do-col-lg-offset-20{margin-left:83.33333%}.do-col-lg-pull-20{position:relative;right:83.33333%}.do-col-lg-push-20{position:relative;left:83.33333%}.do-col-lg-21{width:87.5%}.do-col-lg-offset-21{margin-left:87.5%}.do-col-lg-pull-21{position:relative;right:87.5%}.do-col-lg-push-21{position:relative;left:87.5%}.do-col-lg-22{width:91.66667%}.do-col-lg-offset-22{margin-left:91.66667%}.do-col-lg-pull-22{position:relative;right:91.66667%}.do-col-lg-push-22{position:relative;left:91.66667%}.do-col-lg-23{width:95.83333%}.do-col-lg-offset-23{margin-left:95.83333%}.do-col-lg-pull-23{position:relative;right:95.83333%}.do-col-lg-push-23{position:relative;left:95.83333%}.do-col-lg-24{width:100%}.do-col-lg-offset-24{margin-left:100%}.do-col-lg-pull-24{position:relative;right:100%}.do-col-lg-push-24{position:relative;left:100%}}@media only screen and (min-width:1920px){.do-col-xl-0{display:none;width:0%}.do-col-xl-offset-0{margin-left:0}.do-col-xl-pull-0{position:relative;right:0}.do-col-xl-push-0{position:relative;left:0}.do-col-xl-1{width:4.16667%}.do-col-xl-offset-1{margin-left:4.16667%}.do-col-xl-pull-1{position:relative;right:4.16667%}.do-col-xl-push-1{position:relative;left:4.16667%}.do-col-xl-2{width:8.33333%}.do-col-xl-offset-2{margin-left:8.33333%}.do-col-xl-pull-2{position:relative;right:8.33333%}.do-col-xl-push-2{position:relative;left:8.33333%}.do-col-xl-3{width:12.5%}.do-col-xl-offset-3{margin-left:12.5%}.do-col-xl-pull-3{position:relative;right:12.5%}.do-col-xl-push-3{position:relative;left:12.5%}.do-col-xl-4{width:16.66667%}.do-col-xl-offset-4{margin-left:16.66667%}.do-col-xl-pull-4{position:relative;right:16.66667%}.do-col-xl-push-4{position:relative;left:16.66667%}.do-col-xl-5{width:20.83333%}.do-col-xl-offset-5{margin-left:20.83333%}.do-col-xl-pull-5{position:relative;right:20.83333%}.do-col-xl-push-5{position:relative;left:20.83333%}.do-col-xl-6{width:25%}.do-col-xl-offset-6{margin-left:25%}.do-col-xl-pull-6{position:relative;right:25%}.do-col-xl-push-6{position:relative;left:25%}.do-col-xl-7{width:29.16667%}.do-col-xl-offset-7{margin-left:29.16667%}.do-col-xl-pull-7{position:relative;right:29.16667%}.do-col-xl-push-7{position:relative;left:29.16667%}.do-col-xl-8{width:33.33333%}.do-col-xl-offset-8{margin-left:33.33333%}.do-col-xl-pull-8{position:relative;right:33.33333%}.do-col-xl-push-8{position:relative;left:33.33333%}.do-col-xl-9{width:37.5%}.do-col-xl-offset-9{margin-left:37.5%}.do-col-xl-pull-9{position:relative;right:37.5%}.do-col-xl-push-9{position:relative;left:37.5%}.do-col-xl-10{width:41.66667%}.do-col-xl-offset-10{margin-left:41.66667%}.do-col-xl-pull-10{position:relative;right:41.66667%}.do-col-xl-push-10{position:relative;left:41.66667%}.do-col-xl-11{width:45.83333%}.do-col-xl-offset-11{margin-left:45.83333%}.do-col-xl-pull-11{position:relative;right:45.83333%}.do-col-xl-push-11{position:relative;left:45.83333%}.do-col-xl-12{width:50%}.do-col-xl-offset-12{margin-left:50%}.do-col-xl-pull-12{position:relative;right:50%}.do-col-xl-push-12{position:relative;left:50%}.do-col-xl-13{width:54.16667%}.do-col-xl-offset-13{margin-left:54.16667%}.do-col-xl-pull-13{position:relative;right:54.16667%}.do-col-xl-push-13{position:relative;left:54.16667%}.do-col-xl-14{width:58.33333%}.do-col-xl-offset-14{margin-left:58.33333%}.do-col-xl-pull-14{position:relative;right:58.33333%}.do-col-xl-push-14{position:relative;left:58.33333%}.do-col-xl-15{width:62.5%}.do-col-xl-offset-15{margin-left:62.5%}.do-col-xl-pull-15{position:relative;right:62.5%}.do-col-xl-push-15{position:relative;left:62.5%}.do-col-xl-16{width:66.66667%}.do-col-xl-offset-16{margin-left:66.66667%}.do-col-xl-pull-16{position:relative;right:66.66667%}.do-col-xl-push-16{position:relative;left:66.66667%}.do-col-xl-17{width:70.83333%}.do-col-xl-offset-17{margin-left:70.83333%}.do-col-xl-pull-17{position:relative;right:70.83333%}.do-col-xl-push-17{position:relative;left:70.83333%}.do-col-xl-18{width:75%}.do-col-xl-offset-18{margin-left:75%}.do-col-xl-pull-18{position:relative;right:75%}.do-col-xl-push-18{position:relative;left:75%}.do-col-xl-19{width:79.16667%}.do-col-xl-offset-19{margin-left:79.16667%}.do-col-xl-pull-19{position:relative;right:79.16667%}.do-col-xl-push-19{position:relative;left:79.16667%}.do-col-xl-20{width:83.33333%}.do-col-xl-offset-20{margin-left:83.33333%}.do-col-xl-pull-20{position:relative;right:83.33333%}.do-col-xl-push-20{position:relative;left:83.33333%}.do-col-xl-21{width:87.5%}.do-col-xl-offset-21{margin-left:87.5%}.do-col-xl-pull-21{position:relative;right:87.5%}.do-col-xl-push-21{position:relative;left:87.5%}.do-col-xl-22{width:91.66667%}.do-col-xl-offset-22{margin-left:91.66667%}.do-col-xl-pull-22{position:relative;right:91.66667%}.do-col-xl-push-22{position:relative;left:91.66667%}.do-col-xl-23{width:95.83333%}.do-col-xl-offset-23{margin-left:95.83333%}.do-col-xl-pull-23{position:relative;right:95.83333%}.do-col-xl-push-23{position:relative;left:95.83333%}.do-col-xl-24{width:100%}.do-col-xl-offset-24{margin-left:100%}.do-col-xl-pull-24{position:relative;right:100%}.do-col-xl-push-24{position:relative;left:100%}}\\n\"),V('.do-row{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box}.do-row::after,.do-row::before{display:table;content:\"\"}.do-row::after{clear:both}.do-row--flex{display:-webkit-box;display:-ms-flexbox;display:flex}.do-row--flex:after,.do-row--flex:before{display:none}.do-row--flex.is-justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.do-row--flex.is-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.do-row--flex.is-justify-space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.do-row--flex.is-justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.do-row--flex.is-align-middle{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.do-row--flex.is-align-bottom{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}\\n');var Rt={install:e=>{e.component(\"DoCol\",Ht),e.component(\"DoRow\",At)}};const Lt=\"load\",Pt=\"unload\",Ft=\"productReady\",Wt=function(e,t){e.forEach((e=>{let{list:o}=e;o.forEach((e=>{Ut(e[t])&&e[t]()}))}))},Ut=function(e){return\"function\"==typeof e};var It={props:{title:{default:\"专区\"},list:{default:[]}},data:()=>({defaultIcon:\"https://pt-starimg.didistatic.com/static/starimg/img/FHqpI3InaS1618997548865.png\"}),methods:{handleClickItem(e){this.$emit(\"handleClickItem\",e)}}};const zt={class:\"dokit-card\"},Gt={class:\"dokit-card-title\"},jt={class:\"dokit-card-title-text\"},Vt={class:\"dokit-item-list\"},$t=[\"onClick\"],Xt={class:\"item-icon\"},Bt=[\"src\"],qt={class:\"item-title\"};V(\".dokit-card[data-v-1c420a41] {\\n  margin-bottom: 10px;\\n  padding: 10px;\\n  background-color: white;\\n}\\n.dokit-card-title-text[data-v-1c420a41] {\\n  font-size: 16px;\\n  color: #333333;\\n}\\n.dokit-item-list[data-v-1c420a41] {\\n  display: flex;\\n  flex-wrap: wrap;\\n  margin-top: 5px;\\n}\\n.item[data-v-1c420a41] {\\n  display: flex;\\n  flex-direction: column;\\n  align-items: center;\\n  width: 25%;\\n  margin-top: 5px;\\n}\\n.item .item-icon-image[data-v-1c420a41] {\\n  width: 30px;\\n}\\n.item .item-title[data-v-1c420a41] {\\n  font-size: 14px;\\n  margin-top: 5px;\\n}\\n\"),It.render=function(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",zt,[createBaseVNode(\"div\",Gt,[createBaseVNode(\"span\",jt,toDisplayString(o.title),1)]),createBaseVNode(\"div\",Vt,[(openBlock(!0),createElementBlock(Fragment,null,renderList(o.list,((e,t)=>(openBlock(),createElementBlock(\"div\",{class:\"item\",key:t,onClick:t=>r.handleClickItem(e)},[createBaseVNode(\"div\",Xt,[createBaseVNode(\"img\",{class:\"item-icon-image\",src:e.icon||i.defaultIcon},null,8,Bt)]),createBaseVNode(\"div\",qt,toDisplayString(e.nameZh||\"默认功能\"),1)],8,$t)))),128))])])},It.__scopeId=\"data-v-1c420a41\";var Zt={props:{version:String},data:()=>({dokitIcon:\"https://pt-starimg.didistatic.com/static/starimg/img/eM7MJKDqVG1618998466986.png\"})};const Jt={class:\"dokit-card dokit-version\"},Kt={class:\"dokit-version-text\"},Qt={class:\"dokit-version-image\"},te=[\"src\"];V(\".dokit-card[data-v-29aa30bc] {\\n  padding: 10px;\\n  background-color: white;\\n}\\n.dokit-version[data-v-29aa30bc] {\\n  padding: 20px 0;\\n  text-align: center;\\n}\\n.dokit-version .dokit-version-text[data-v-29aa30bc] {\\n  font-size: 16px;\\n  color: #999999;\\n}\\n.dokit-version .dokit-version-image[data-v-29aa30bc] {\\n  margin-top: 20px;\\n}\\n.dokit-version .dokit-icon[data-v-29aa30bc] {\\n  width: 150px;\\n}\\n\"),Zt.render=function(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",Jt,[createBaseVNode(\"div\",null,[createBaseVNode(\"span\",Kt,\"当前版本：V\"+toDisplayString(o.version),1)]),createBaseVNode(\"div\",Qt,[createBaseVNode(\"img\",{class:\"dokit-icon\",src:i.dokitIcon},null,8,te)])])},Zt.__scopeId=\"data-v-29aa30bc\";var ee={components:{TopBar:W,Card:It,VersionCard:Zt},data:()=>({version:\"0.0.3-alpha.1\"}),mounted(){},computed:{features(){return this.$store.state.features}},methods:{handleClickItem(e){switch(e.type){case\"RouterPlugin\":this.$router.push({name:e.name});break;case\"IndependPlugin\":dt(e),this.$store.state.showContainer=!1}}}};const oe={class:\"dokit-index-container\"};V(\".dokit-index-container[data-v-51162b22] {\\n  background-color: #f5f6f7;\\n}\\n\"),ee.render=function(e,t,o,n,i,r){const s=resolveComponent(\"card\"),a=resolveComponent(\"version-card\");return openBlock(),createElementBlock(\"div\",oe,[(openBlock(!0),createElementBlock(Fragment,null,renderList(r.features,((e,t)=>(openBlock(),createBlock(s,{key:t,title:e.title,list:e.list,onHandleClickItem:r.handleClickItem},null,8,[\"title\",\"list\",\"onHandleClickItem\"])))),128)),createVNode(a,{version:i.version},null,8,[\"version\"])])},ee.__scopeId=\"data-v-51162b22\";const ie=[{name:\"home\",component:ee}];function ne(e){let t=[];return e.forEach((e=>{let{list:o,title:n}=e;o.forEach((e=>{let{name:o,nameZh:i,component:r}=e;t.push({name:o,component:r,meta:{title:i,zone:n}})}))})),[...ie,...t]}function se(e){return function({routes:e}){const t=e,o=[],n=r(\"home\"),i=shallowRef(n);function r(e){return t.find((t=>t.name===e))}function s({name:e}){let t=r(e)||n;i.value=t}return{currentRoute:i,addRoute:function(e){t.push(e)},removeRoute:function(e){let o=t.findIndex((t=>t.name===e));return-1!=o?t.splice(o,1):null},hasRoute:function(e){return-1!==t.findIndex((t=>t.name===e))},getRoutes:function(){return t},push:function({name:e}){o.push(e),s({name:e})},replace:function(e){o.pop(),o.push(e),s({name:e})},back:function(){o.pop();let e=o.length-1;s({name:o[e]})},install:function(e){e.config.globalProperties.$router=this,Object.defineProperty(e.config.globalProperties,\"$route\",{get:()=>unref(i)})}}}({routes:[...ne(e)]})}var le={data:()=>({visible:!1,message:\"\"}),methods:{showToast(){this.visible=!0},closeToast(){this.visible=!1},setMessage(e){this.message=e}}};V(\".alert-fade-enter-active[data-v-17caa746],\\n.alert-fade-leave-active[data-v-17caa746] {\\n  transition: opacity 0.3s;\\n}\\n.alert-fade-enter[data-v-17caa746],\\n.alert-fade-leave-to[data-v-17caa746] {\\n  opacity: 0;\\n}\\n.dokit-dialog-tips[data-v-17caa746] {\\n  position: fixed;\\n  z-index: 100001;\\n  max-width: 70%;\\n  padding: 8px 12px;\\n  border-radius: 15px;\\n  white-space: nowrap;\\n  background-color: rgba(0, 0, 0, 0.5);\\n  box-shadow: 0px 8px 30px 0 rgba(0, 0, 0, 0.363);\\n  text-align: center;\\n  color: #ffffff;\\n}\\n.dokit-dialog-center[data-v-17caa746] {\\n  top: 20%;\\n  left: 50%;\\n  transform: translate(-50%, -50%);\\n}\\n\"),le.render=function(e,t,o,n,i,r){return openBlock(),createBlock(Transition,{name:\"alert-fade\"},{default:withCtx((()=>[withDirectives(createBaseVNode(\"div\",{id:\"dokit-toast\",class:\"dokit-dialog-tips dokit-dialog-center\"},toDisplayString(i.message),513),[[vShow,i.visible]])])),_:1})},le.__scopeId=\"data-v-17caa746\";const re={install:e=>{const t=createApp(le);let o=document.createElement(\"div\");t.mount(o),document.body.appendChild(o),e.config.globalProperties.$toast=(e,o=3e3)=>{t._instance.ctx.setMessage(e),t._instance.ctx.showToast(),setTimeout((()=>{t._instance.ctx.closeToast()}),o)}}},ae=()=>{};class de{type=\"\";name=\"\";nameZh=\"\";icon=\"\";component=null;_onLoad=ae;_onUnload=ae;_onProductReady=ae;constructor(e){let{name:t,nameZh:o,icon:n,component:i,onLoad:r,onUnload:s,onProductReady:a}=e;this.name=t,this.nameZh=o,this.icon=n,this.component=i,this._onLoad=r||ae,this._onUnload=s||ae,this._onProductReady=a||ae}load(){this._onLoad.call(this)}unload(){this._onUnload.call(this)}productReady(){this._onProductReady.call(this)}}class ce extends de{type=\"RouterPlugin\";constructor(e){super(e)}}class he extends de{type=\"IndependPlugin\";constructor(e){super(e)}}class pe{options=null;constructor(e){this.options=e;let t=createApp(Et),{features:o}=e;t.use(Rt),t.use(se(o)),t.use(it),\"loading\"===document.readyState?document.addEventListener(\"readystatechange\",(function(){\"interactive\"===document.readyState&&t.use(re)})):t.use(re),it.state.features=o,this.app=t,this.init(),this.onLoad()}onLoad(){Wt(this.options.features,Lt)}onUnload(){Wt(this.options.features,Pt)}onProductReady(){Wt(this.options.features,Ft)}init(){let e=document.createElement(\"div\");e.id=\"dokit-root\",document.documentElement.appendChild(e);let t=document.createElement(\"div\");t.id=\"dokit-container\",this.app.mount(t),e.appendChild(t)}setProductId(e){this.productId=e,it.state.productId=e,this.onProductReady()}startMultiControl(e,t){\"client\"!==t&&\"host\"!==t||(\"host\"===t&&(it.state.isHost=!0),it.state.socketUrl=e,it.state.socketConnect=!0)}stopMultiControl(){it.state.socketConnect=!1}isNativeContainer(){if(!it.state.socketConnect){it.state.isNative=!0;let e=localStorage.getItem(\"nativeConnectSerial\");e&&(it.state.connectSerial=e)}}}var script$C={props:{tabs:{type:Array}},data:()=>({curIndex:0}),methods:{handleClickTab(e,t){let{type:o}=e;this.curIndex=t,this.$emit(\"changeTap\",o)}}};const _hoisted_1$A={class:\"dokit-tab-container\"},_hoisted_2$r={class:\"dokit-tab-list\"},_hoisted_3$o=[\"onClick\"],_hoisted_4$n={class:\"dokit-tab-item-text\"};function render$C(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",_hoisted_1$A,[createBaseVNode(\"div\",_hoisted_2$r,[(openBlock(!0),createElementBlock(Fragment,null,renderList(o.tabs,((e,t)=>(openBlock(),createElementBlock(\"div\",{class:normalizeClass([\"dokit-tab-item\",i.curIndex===t?\"dokit-tab-active\":\"dokit-tab-default\"]),key:t,onClick:o=>r.handleClickTab(e,t)},[createBaseVNode(\"span\",_hoisted_4$n,toDisplayString(e.name),1)],10,_hoisted_3$o)))),128))])])}function styleInject(e,t){void 0===t&&(t={});var o=t.insertAt;if(e&&\"undefined\"!=typeof document){var n=document.head||document.getElementsByTagName(\"head\")[0],i=document.createElement(\"style\");i.type=\"text/css\",\"top\"===o&&n.firstChild?n.insertBefore(i,n.firstChild):n.appendChild(i),i.styleSheet?i.styleSheet.cssText=e:i.appendChild(document.createTextNode(e))}}var css_248z$z=\".dokit-tab-container[data-v-b15e8dba] .dokit-tab-list[data-v-b15e8dba] {\\n  display: flex;\\n  height: 38px;\\n  justify-content: space-between;\\n  align-items: center;\\n  border: 1px solid #f5f6f7;\\n}\\n.dokit-tab-container[data-v-b15e8dba] .dokit-tab-item[data-v-b15e8dba] {\\n  flex: 1;\\n  height: 38px;\\n  line-height: 38px;\\n  text-align: center;\\n}\\n.dokit-tab-container[data-v-b15e8dba] .dokit-tab-item-text[data-v-b15e8dba] {\\n  font-size: 16px;\\n  color: #333333;\\n}\\n.dokit-tab-container[data-v-b15e8dba] .dokit-tab-active[data-v-b15e8dba] {\\n  border-bottom: 1px solid #1485ee;\\n}\\n.dokit-tab-container[data-v-b15e8dba] .dokit-tab-default[data-v-b15e8dba] {\\n  border: none;\\n}\\n\";styleInject(css_248z$z),script$C.render=render$C,script$C.__scopeId=\"data-v-b15e8dba\";const LogMap={0:\"All\",1:\"Log\",2:\"Info\",3:\"Warn\",4:\"Error\"},LogEnum={ALL:0,LOG:1,INFO:2,WARN:3,ERROR:4},ConsoleLogMap={log:LogEnum.LOG,info:LogEnum.INFO,warn:LogEnum.WARN,error:LogEnum.ERROR},CONSOLE_METHODS=[\"log\",\"info\",\"warn\",\"error\"],LogTabs=Object.keys(LogMap).map((e=>({type:parseInt(e),name:LogMap[e]}))),excuteScript=function(e){let t;try{t=eval.call(window,`(${e})`)}catch(o){t=eval.call(window,e)}return t},origConsole={},noop=()=>{},overrideConsole=function(e){const t=window.console;CONSOLE_METHODS.forEach((o=>{let n=origConsole[o]=noop;t[o]&&(n=origConsole[o]=t[o].bind(t)),t[o]=(...t)=>{e({name:o,type:ConsoleLogMap[o],value:t}),n(...t)}}))},restoreConsole=function(){const e=window.console;CONSOLE_METHODS.forEach((t=>{e[t]=origConsole[t]}))},request=new Request,getDataType=function(e){return null===e?\"Null\":void 0===e?\"Undefined\":e.constructor&&e.constructor.name||\"Object\"},MAX_DISPLAY_PROPERTY_NUM=5,getDataStructureStr=function(e,t){let o=getDataType(e),n=\"\";switch(o){case\"Number\":case\"String\":case\"Boolean\":case\"RegExp\":case\"Symbol\":case\"Function\":n=e.toString();break;case\"Null\":case\"Undefined\":n=e+\"\";break;case\"Array\":break;case\"Object\":try{if(n+=\"{\",t){let t=Object.getOwnPropertyNames(e).map((t=>n+=`${t}: ${getDataStructureStr(e[t],!1)}`));t.join(\",\"),t.length>MAX_DISPLAY_PROPERTY_NUM&&(n+=\",...\")}else n+=\"...\";n+=\"}\"}catch(e){console.log(e)}}return n},guid=function(){function e(){return(65536*(1+Math.random())|0).toString(16).substring(1)}return e()+e()+\"-\"+e()+\"-\"+e()+\"-\"+e()+\"-\"+e()+e()+e()},$bus=new EventEmitter,debounce=(e,t,o)=>{var n=null,i=null;return function(...r){var s=+new Date,a=this;n||(n=s),s-n>o?(clearTimeout(i),e.apply(a,r),n=s):(clearTimeout(i),i=setTimeout((function(){e.apply(a,r)}),t))}};var script$B={name:\"Detail\",components:{Detail:script$B},props:{detailValue:[String,Number,Object],detailIndex:[String,Number]},data:()=>({unfold:!1,newDetailValue:null}),computed:{dataType(){return getDataType(this.newDetailValue)},canFold(){return!(!this.isObject(this.newDetailValue)&&!this.isArray(this.newDetailValue))},displayDetailValue(){let e=\"\";return this.canFold?(this.isObject(this.newDetailValue)&&(e=\"Function\"!==this.dataType?\"Object\":\"Function\"),this.isArray(this.newDetailValue)&&(e=`Array(${this.newDetailValue.length})`)):e=`<span style=\"color:#1802C7;\">${this.newDetailValue}</span>`,`<span style=\"color:#7D208C;\">${this.detailIndex}</span>: ${e}`}},watch:{detailValue:{immediate:!0,handler(e,t){this.newDetailValue=clone(e)}}},methods:{unfoldDetail(){this.unfold=!this.unfold},isObject:e=>Object.prototype.isPrototypeOf(e),isArray:e=>Array.prototype.isPrototypeOf(e)}};const _hoisted_1$z=[\"innerHTML\"];function render$B(e,t,o,n,i,r){const s=resolveComponent(\"Detail\");return openBlock(),createElementBlock(\"div\",{class:normalizeClass([\"dokit-detail-container\",[r.canFold?\"dokit-can-unfold\":\"\",i.unfold?\"dokit-unfolded\":\"\"]])},[createBaseVNode(\"div\",{onClick:t[0]||(t[0]=(...e)=>r.unfoldDetail&&r.unfoldDetail(...e)),innerHTML:r.displayDetailValue},null,8,_hoisted_1$z),r.canFold?(openBlock(!0),createElementBlock(Fragment,{key:0},renderList(i.newDetailValue,((e,t)=>withDirectives((openBlock(),createElementBlock(\"div\",{key:t},[createVNode(s,{detailValue:e,detailIndex:t},null,8,[\"detailValue\",\"detailIndex\"])])),[[vShow,i.unfold]]))),128)):createCommentVNode(\"\",!0)],2)}var css_248z$y='.dokit-detail-container[data-v-1e1ac878] {\\n  font-size: 12px;\\n  margin-left: 24px;\\n  position: relative;\\n  white-space: pre-wrap;\\n  word-wrap: break-word;\\n  max-width: 100%;\\n}\\n.dokit-can-unfold[data-v-1e1ac878][data-v-1e1ac878]::before {\\n  content: \"\";\\n  width: 0;\\n  height: 0;\\n  border: 4px solid transparent;\\n  position: absolute;\\n  border-left-color: #333;\\n  left: -12px;\\n  top: 3px;\\n}\\n.dokit-unfolded[data-v-1e1ac878][data-v-1e1ac878]::before {\\n  border: 4px solid transparent;\\n  border-top-color: #333;\\n  top: 6px;\\n}\\n';styleInject(css_248z$y),script$B.render=render$B,script$B.__scopeId=\"data-v-1e1ac878\";const DATATYPE_NOT_DISPLAY=[\"Number\",\"String\",\"Boolean\",\"Undefined\",\"Null\"];var script$A={components:{Detail:script$B},props:{type:[Number],value:[String,Number,Object],logType:[String]},data:()=>({showDetail:!1}),computed:{logPreview(){let e=\"\",t=null,o=\"<div>\";return\"log\"===this.logType||\"info\"===this.logType?t=t=>{e=getDataType(t),-1===DATATYPE_NOT_DISPLAY.indexOf(e)&&(o+=`<span class=\"data-type\">${e}</span>`),o+=`<span class=\"data-structure\">${getDataStructureStr(t,!0)}</span>`}:\"error\"!==this.logType&&\"warn\"!==this.logType||(t=t=>{t.stack?o+=`<span style=\"white-space: pre-wrap;\">${t.stack}</span>`:(e=getDataType(t),-1===DATATYPE_NOT_DISPLAY.indexOf(e)&&(o+=`<span class=\"data-type\">${e}</span>`),o+=`<span class=\"data-structure\">${getDataStructureStr(t,!0)}</span>`)}),this.value.forEach(t),o+=\"</div>\",o},canShowDetail(){return this.showDetail&&\"object\"==typeof this.value&&!this.value.stack}},methods:{toggleDetail(){this.showDetail=!this.showDetail}}};const _hoisted_1$y=[\"innerHTML\"],_hoisted_2$q={key:0};function render$A(e,t,o,n,i,r){const s=resolveComponent(\"Detail\");return openBlock(),createElementBlock(\"div\",{class:normalizeClass([\"dokit-log-ltem\",o.logType])},[createBaseVNode(\"div\",{class:\"dokit-log-preview\",innerHTML:r.logPreview,onClick:t[0]||(t[0]=(...e)=>r.toggleDetail&&r.toggleDetail(...e))},null,8,_hoisted_1$y),r.canShowDetail?(openBlock(),createElementBlock(\"div\",_hoisted_2$q,[(openBlock(!0),createElementBlock(Fragment,null,renderList(o.value,((e,t)=>(openBlock(),createElementBlock(\"div\",{class:\"dokit-list-item\",key:t},[createVNode(s,{detailValue:e,detailIndex:t},null,8,[\"detailValue\",\"detailIndex\"])])))),128))])):createCommentVNode(\"\",!0)],2)}var css_248z$x='.dokit-log-ltem[data-v-037bd26e] {\\n  padding: 5px;\\n  padding-left: 20px;\\n  border-top: 1px solid #eee;\\n  text-align: left;\\n  font-size: 12px;\\n}\\n.info[data-v-037bd26e] {\\n  background-color: #ECF1F7;\\n  position: relative;\\n}\\n.info[data-v-037bd26e][data-v-037bd26e]::before {\\n  content: \"\";\\n  background: url(\"https://pt-starimg.didistatic.com/static/starimg/img/M3nz7HYPH21621412737959.png\") no-repeat;\\n  background-size: 10px 10px;\\n  width: 10px;\\n  height: 10px;\\n  position: absolute;\\n  top: 7px;\\n  left: 8px;\\n}\\n.warn[data-v-037bd26e] {\\n  background-color: #FFFBE4;\\n  color: #5C3C01;\\n  position: relative;\\n}\\n.warn[data-v-037bd26e][data-v-037bd26e]::before {\\n  content: \"\";\\n  background: url(\"https://pt-starimg.didistatic.com/static/starimg/img/39hzJzObhZ1621411397522.png\") no-repeat;\\n  background-size: 10px 10px;\\n  width: 10px;\\n  height: 10px;\\n  position: absolute;\\n  top: 7px;\\n  left: 8px;\\n}\\n.error[data-v-037bd26e] {\\n  background-color: #FEF0F0;\\n  color: #FF161A;\\n  position: relative;\\n}\\n.error[data-v-037bd26e][data-v-037bd26e]::before {\\n  content: \"\";\\n  background: url(\"https://pt-starimg.didistatic.com/static/starimg/img/z6EndYs29d1621411397532.png\") no-repeat;\\n  background-size: 10px 10px;\\n  width: 10px;\\n  height: 10px;\\n  position: absolute;\\n  top: 7px;\\n  left: 8px;\\n}\\n.log-ltem[data-v-037bd26e]:first-child {\\n  border: none;\\n}\\n.dokit-log-preview[data-v-037bd26e][data-v-037bd26e] .data-type {\\n  margin-left: 5px;\\n  margin-right: 5px;\\n  font-style: italic;\\n  font-weight: bold;\\n  color: #aaa;\\n}\\n.dokit-log-preview[data-v-037bd26e][data-v-037bd26e] .data-structure {\\n  display: inline-block;\\n  max-width: 100%;\\n  font-style: italic;\\n  white-space: pre-wrap;\\n  word-wrap: break-word;\\n  overflow: hidden;\\n}\\n';styleInject(css_248z$x),script$A.render=render$A,script$A.__scopeId=\"data-v-037bd26e\";var script$z={components:{LogItem:script$A},props:{logList:{type:Array,default:[]}},data:()=>({})};const _hoisted_1$x={class:\"dokit-log-container\"};function render$z(e,t,o,n,i,r){const s=resolveComponent(\"log-item\");return openBlock(),createElementBlock(\"div\",_hoisted_1$x,[(openBlock(!0),createElementBlock(Fragment,null,renderList(o.logList,((e,t)=>(openBlock(),createBlock(s,{key:t,value:e.value,type:e.type,logType:e.name},null,8,[\"value\",\"type\",\"logType\"])))),128))])}var css_248z$w=\".dokit-tab-container[data-v-2996fcff] .dokit-tab-list[data-v-2996fcff] {\\n  display: flex;\\n  height: 38px;\\n  justify-content: space-between;\\n  align-items: center;\\n  border: 1px solid #f5f6f7;\\n}\\n.dokit-tab-container[data-v-2996fcff] .dokit-tab-item[data-v-2996fcff] {\\n  flex: 1;\\n  height: 38px;\\n  line-height: 38px;\\n  text-align: center;\\n}\\n.dokit-tab-container[data-v-2996fcff] .dokit-tab-item-text[data-v-2996fcff] {\\n  font-size: 16px;\\n  color: #333333;\\n}\\n.dokit-tab-container[data-v-2996fcff] .dokit-tab-active[data-v-2996fcff] {\\n  border-bottom: 1px solid #1485ee;\\n}\\n.dokit-tab-container[data-v-2996fcff] .dokit-tab-default[data-v-2996fcff] {\\n  border: none;\\n}\\n\";styleInject(css_248z$w),script$z.render=render$z,script$z.__scopeId=\"data-v-2996fcff\";var script$y={data:()=>({command:\"\"}),methods:{excuteCommand(){if(this.command){try{let e=excuteScript(this.command);console.log(e)}catch(e){console.error(e)}this.command=\"\"}}}};const _withScopeId$c=e=>(pushScopeId(\"data-v-4c84e48c\"),e=e(),popScopeId(),e),_hoisted_1$w={class:\"dokit-operation\"},_hoisted_2$p={class:\"dokit-input-wrapper\"},_hoisted_3$n=_withScopeId$c((()=>createBaseVNode(\"span\",null,\"Excute\",-1))),_hoisted_4$m=[_hoisted_3$n];function render$y(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",_hoisted_1$w,[createBaseVNode(\"div\",_hoisted_2$p,[withDirectives(createBaseVNode(\"input\",{class:\"dokit-input\",placeholder:\"Command……\",\"onUpdate:modelValue\":t[0]||(t[0]=e=>i.command=e)},null,512),[[vModelText,i.command]])]),createBaseVNode(\"div\",{class:\"dokit-button-wrapper\",onClick:t[1]||(t[1]=(...e)=>r.excuteCommand&&r.excuteCommand(...e))},_hoisted_4$m)])}var css_248z$v=\".dokit-operation[data-v-4c84e48c] {\\n  height: 50px;\\n  display: flex;\\n  justify-content: space-between;\\n  align-items: center;\\n}\\n.dokit-input-wrapper[data-v-4c84e48c] {\\n  flex: 1;\\n  height: 100%;\\n}\\n.dokit-input-wrapper[data-v-4c84e48c] .dokit-input[data-v-4c84e48c] {\\n  height: 100%;\\n  width: 100%;\\n  outline: none;\\n  border: none;\\n  line-height: 100%;\\n  padding: 0 10px;\\n  font-size: 18px;\\n}\\n.dokit-button-wrapper[data-v-4c84e48c] {\\n  height: 100%;\\n  line-height: 100%;\\n  margin-left: 10px;\\n  padding: 0 10px;\\n  border-left: 1px solid #f5f6f7;\\n  display: flex;\\n  align-items: center;\\n  font-size: 18px;\\n}\\n\";styleInject(css_248z$v),script$y.render=render$y,script$y.__scopeId=\"data-v-4c84e48c\";var script$x={components:{ConsoleTap:script$C,LogContainer:script$z,OperationCommand:script$y},data:()=>({logTabs:LogTabs,curTab:LogEnum.ALL}),computed:{logList(){return this.$store.state.logList||[]},curLogList(){return this.curTab==LogEnum.ALL?this.logList:this.logList.filter((e=>e.type==this.curTab))}},created(){},methods:{handleChangeTab(e){this.curTab=e}}};const _hoisted_1$v={class:\"dokit-console-container\"},_hoisted_2$o={class:\"dokit-log-container\"},_hoisted_3$m={class:\"dokit-info-container\"},_hoisted_4$l={class:\"dokit-operation-container\"};function render$x(e,t,o,n,i,r){const s=resolveComponent(\"console-tap\"),a=resolveComponent(\"log-container\"),l=resolveComponent(\"operation-command\");return openBlock(),createElementBlock(\"div\",_hoisted_1$v,[createVNode(s,{tabs:i.logTabs,onChangeTap:r.handleChangeTab},null,8,[\"tabs\",\"onChangeTap\"]),createBaseVNode(\"div\",_hoisted_2$o,[createBaseVNode(\"div\",_hoisted_3$m,[createVNode(a,{logList:r.curLogList},null,8,[\"logList\"])]),createBaseVNode(\"div\",_hoisted_4$l,[createVNode(l)])])])}var css_248z$u=\".dokit-console-container[data-v-1fcec258] {\\n  display: flex;\\n  flex-direction: column;\\n  height: 100%;\\n}\\n.dokit-log-container[data-v-1fcec258] {\\n  flex: 1;\\n  display: flex;\\n  flex-direction: column;\\n  overflow: hidden;\\n}\\n.dokit-log-container[data-v-1fcec258] .dokit-info-container[data-v-1fcec258] {\\n  flex: 1;\\n  background-color: #ffffff;\\n  border-bottom: 1px solid #f5f6f7;\\n  overflow-y: scroll;\\n}\\n\";styleInject(css_248z$u),script$x.render=render$x,script$x.__scopeId=\"data-v-1fcec258\";var Console=new ce({name:\"console\",nameZh:\"日志\",component:script$x,icon:\"https://pt-starimg.didistatic.com/static/starimg/img/PbNXVyzTbq1618997544543.png\",onLoad(){overrideConsole((({name:e,type:t,value:o})=>{let n=st();n.logList=n.logList||[],n.logList.push({type:t,name:e,value:o})}))},onUnload(){restoreConsole()}}),script$w={props:{title:{default:\"\"}}};const _hoisted_1$u={class:\"dokit-common-card\"},_hoisted_2$n={class:\"dokit-common-card-header\"},_hoisted_3$l={class:\"dokit-common-card-header__title\"},_hoisted_4$k={class:\"dokit-common-card-header__extra-info\"},_hoisted_5$i={class:\"dokit-common-card-body\"};function render$w(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",_hoisted_1$u,[createBaseVNode(\"div\",_hoisted_2$n,[createBaseVNode(\"div\",_hoisted_3$l,toDisplayString(o.title),1),createBaseVNode(\"div\",_hoisted_4$k,[renderSlot(e.$slots,\"extra\")])]),createBaseVNode(\"div\",_hoisted_5$i,[renderSlot(e.$slots,\"body\")])])}var css_248z$t=\".dokit-common-card {\\n  border-radius: 5px;\\n  overflow: hidden;\\n  border: 1px solid #d6e4ef;\\n}\\n.dokit-common-card-header {\\n  border-bottom: 1px solid #d6e4ef;\\n  display: flex;\\n  justify-content: space-between;\\n  align-items: center;\\n}\\n.dokit-common-card-header .dokit-common-card-header__title {\\n  padding: 8px;\\n  font-size: 18px;\\n  color: #2c405a;\\n}\\n.dokit-common-card-body {\\n  padding: 8px;\\n}\\n\";styleInject(css_248z$t),script$w.render=render$w;var script$v={components:{Card:script$w},data:()=>({ua:window.navigator.userAgent,url:window.location.href,ratio:window.devicePixelRatio,screen:window.screen,viewport:{width:document.documentElement.clientWidth,height:document.documentElement.clientHeight}})};const _hoisted_1$t={class:\"dokit-app-info-container\"},_hoisted_2$m={class:\"dokit-info-wrapper\"},_hoisted_3$k=createTextVNode(\"UA\"),_hoisted_4$j=createTextVNode(\"URL\"),_hoisted_5$h={class:\"dokit-info-wrapper\",style:{\"margin-top\":\"20px\"}},_hoisted_6$g=createTextVNode(\"设备缩放比\"),_hoisted_7$b=createTextVNode(\"screen\"),_hoisted_8$a=createTextVNode(\"viewport\");function render$v(e,t,o,n,i,r){const s=resolveComponent(\"DoCol\"),a=resolveComponent(\"DoRow\"),l=resolveComponent(\"Card\");return openBlock(),createElementBlock(\"div\",_hoisted_1$t,[createBaseVNode(\"div\",_hoisted_2$m,[createVNode(l,{title:\"Page Info\"},{body:withCtx((()=>[createVNode(a,{class:\"dokit-app-info-item\"},{default:withCtx((()=>[createVNode(s,{span:8},{default:withCtx((()=>[_hoisted_3$k])),_:1}),createVNode(s,{span:16},{default:withCtx((()=>[createTextVNode(toDisplayString(i.ua),1)])),_:1})])),_:1}),createVNode(a,{class:\"dokit-app-info-item\"},{default:withCtx((()=>[createVNode(s,{span:8},{default:withCtx((()=>[_hoisted_4$j])),_:1}),createVNode(s,{span:16},{default:withCtx((()=>[createTextVNode(toDisplayString(i.url),1)])),_:1})])),_:1})])),_:1})]),createBaseVNode(\"div\",_hoisted_5$h,[createVNode(l,{title:\"Device Info\"},{body:withCtx((()=>[createVNode(a,{class:\"dokit-app-info-item\"},{default:withCtx((()=>[createVNode(s,{span:8},{default:withCtx((()=>[_hoisted_6$g])),_:1}),createVNode(s,{span:16},{default:withCtx((()=>[createTextVNode(toDisplayString(i.ratio),1)])),_:1})])),_:1}),createVNode(a,{class:\"dokit-app-info-item\"},{default:withCtx((()=>[createVNode(s,{span:8},{default:withCtx((()=>[_hoisted_7$b])),_:1}),createVNode(s,{span:16},{default:withCtx((()=>[createTextVNode(toDisplayString(i.screen.width)+\" X \"+toDisplayString(i.screen.height),1)])),_:1})])),_:1}),createVNode(a,{class:\"dokit-app-info-item\"},{default:withCtx((()=>[createVNode(s,{span:8},{default:withCtx((()=>[_hoisted_8$a])),_:1}),createVNode(s,{span:16},{default:withCtx((()=>[createTextVNode(toDisplayString(i.viewport.width)+\" X \"+toDisplayString(i.viewport.height),1)])),_:1})])),_:1})])),_:1})])])}var css_248z$s=\".dokit-app-info-container[data-v-c97bdae2] {\\n  font-size: 14px;\\n  height: 100%;\\n  overflow: hidden;\\n}\\n.dokit-info-wrapper[data-v-c97bdae2] {\\n  margin: 5px 5px 0 5px;\\n}\\n.dokit-info-wrapper[data-v-c97bdae2] .key[data-v-c97bdae2] {\\n  font-weight: bold;\\n}\\n.dokit-info-wrapper[data-v-c97bdae2] .dokit-app-info-item[data-v-c97bdae2] {\\n  padding: 5px 0;\\n}\\ntable[data-v-c97bdae2] {\\n  border-color: #eee;\\n  width: 100%;\\n  border-collapse: collapse;\\n  border-spacing: 0;\\n}\\ntr[data-v-c97bdae2] {\\n  width: 100%;\\n}\\ntd[data-v-c97bdae2],\\nth[data-v-c97bdae2] {\\n  padding: 5px;\\n}\\n\";styleInject(css_248z$s),script$v.render=render$v,script$v.__scopeId=\"data-v-c97bdae2\";var AppInfo=new ce({nameZh:\"应用信息\",name:\"app-info\",icon:\"https://pt-starimg.didistatic.com/static/starimg/img/z1346TQD531618997547642.png\",component:script$v}),script$u={name:\"ElementTree\",components:{ElementTree:script$u},data:()=>({unfold:!1,isShow:!0}),props:{node:Object,parentIsUnfold:Boolean},watch:{unfold(e){e?$bus.on(this.node.key+\"refreshChild\",this.refreshSon):$bus.off(this.node.key+\"refreshChild\",this.refreshSon)},parentIsUnfold(e){e?$bus.on(this.node.key+\"refreshMy\",this.refresh):$bus.off(this.node.key+\"refreshMy\",this.refresh)}},created(){\"HTML\"===this?.node?.tagName&&$bus.on(this.node.key,this.refresh)},destroyed(){$bus.off(this.node.key,this.refresh),$bus.off(this.node.key,this.refreshSon)},methods:{refreshSon(){this.unfold=!1,this.$nextTick((()=>{this.unfold=!0}))},refresh(){this.isShow=!1,this.$nextTick((()=>{this.isShow=!0}))},isNullEndTag:e=>(e=e?e.toLowerCase():\"\",[\"br\",\"hr\",\"img\",\"input\",\"link\",\"meta\"].indexOf(e)>-1),unfoldDetail(){this.canFold()&&(this.unfold=!this.unfold)},canFold(){return this.node.childNodes.length>0},_trim:e=>e.replace(/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g,\"\")}};const _hoisted_1$s={key:0,class:\"dkelm-k\"},_hoisted_2$l={key:0},_hoisted_3$j={class:\"dkelm-v\"},_hoisted_4$i={key:1},_hoisted_5$g=createTextVNode(\"> \"),_hoisted_6$f={key:1,class:\"dkelm-node\"};function render$u(e,t,o,n,i,r){const s=resolveComponent(\"ElementTree\",!0);return i.isShow?(openBlock(),createElementBlock(\"div\",{key:0,class:normalizeClass([\"element-tree-container\",[r.canFold?\"can-unfold\":\"\",i.unfold?\"unfolded\":\"\"]])},[o.node&&1===o.node.nodeType?(openBlock(),createElementBlock(\"div\",{key:0,class:normalizeClass([\"dkelm-l\",[r.isNullEndTag(o.node.tagName)||0==o.node.childNodes.length?\"dkelm-noc\":\"\",i.unfold?\"dk-toggle\":\"\"]])},[createBaseVNode(\"span\",{class:\"dkelm-node\",onClick:t[0]||(t[0]=(...e)=>r.unfoldDetail&&r.unfoldDetail(...e))},[createTextVNode(\" <\"+toDisplayString(o.node.tagName.toLowerCase())+\" \",1),o.node.className||o.node.attributes.length?(openBlock(),createElementBlock(\"i\",_hoisted_1$s,[(openBlock(!0),createElementBlock(Fragment,null,renderList(o.node.attributes,((e,t)=>(openBlock(),createElementBlock(\"span\",{key:t},[\"\"!==e.value?(openBlock(),createElementBlock(\"span\",_hoisted_2$l,[createTextVNode(toDisplayString(\" \"+e.name)+\"=\",1),createBaseVNode(\"i\",_hoisted_3$j,toDisplayString(e.value),1)])):(openBlock(),createElementBlock(\"span\",_hoisted_4$i,toDisplayString(\" \"+e.name),1))])))),128))])):createCommentVNode(\"\",!0),_hoisted_5$g]),r.canFold?(openBlock(!0),createElementBlock(Fragment,{key:0},renderList(o.node.childNodes,(e=>withDirectives((openBlock(),createElementBlock(\"div\",{key:e.key},[createVNode(s,{node:e,parentIsUnfold:i.unfold},null,8,[\"node\",\"parentIsUnfold\"])])),[[vShow,i.unfold]]))),128)):createCommentVNode(\"\",!0),r.isNullEndTag(o.node.tagName)?createCommentVNode(\"\",!0):(openBlock(),createElementBlock(\"span\",_hoisted_6$f,\"</\"+toDisplayString(o.node.tagName.toLowerCase())+\">\",1))],2)):o.node&&3===o.node.nodeType&&o.node.textContent?(openBlock(),createElementBlock(Fragment,{key:1},[createTextVNode(toDisplayString(r._trim(o.node.textContent)),1)],64)):createCommentVNode(\"\",!0)],2)):createCommentVNode(\"\",!0)}var css_248z$r='.element-tree-container[data-v-754a8304] {\\n  font-size: 16px;\\n  position: relative;\\n}\\n.element-tree-container[data-v-754a8304] .dkelm-l[data-v-754a8304] {\\n  padding-left: 8px;\\n  position: relative;\\n  word-wrap: break-word;\\n  line-height: 1;\\n}\\n.element-tree-container[data-v-754a8304] .dkelm-l[data-v-754a8304][data-v-754a8304]::before {\\n  content: \"\";\\n  display: block;\\n  position: absolute;\\n  top: 6px;\\n  left: 3px;\\n  width: 0;\\n  height: 0;\\n  border: 3px solid transparent;\\n  border-left-color: #000;\\n}\\n.element-tree-container[data-v-754a8304] .dkelm-l[data-v-754a8304].dkelm-noc[data-v-754a8304]:before {\\n  display: none;\\n}\\n.element-tree-container[data-v-754a8304] .dkelm-l[data-v-754a8304].dk-toggle[data-v-754a8304]:before {\\n  display: block;\\n  top: 8px;\\n  left: 0;\\n  border-top-color: #000;\\n  border-left-color: transparent;\\n}\\n.element-tree-container[data-v-754a8304] .dkelm-node[data-v-754a8304] {\\n  color: #183691;\\n}\\n.element-tree-container[data-v-754a8304] .dkelm-k[data-v-754a8304] {\\n  color: #0086b3;\\n}\\n.element-tree-container[data-v-754a8304] .dkelm-v[data-v-754a8304] {\\n  color: #905;\\n}\\n';function formatStyle(e){const t={};for(let o=0,n=e.length;o<n;o++){const n=e[o];\"initial\"!==e[n]&&(t[n]=e[n])}return t}styleInject(css_248z$r),script$u.render=render$u,script$u.__scopeId=\"data-v-754a8304\";const elProto=Element.prototype;let matchesSel=function(){return!1};elProto.webkitMatchesSelector?matchesSel=(e,t)=>e.webkitMatchesSelector(t):elProto.mozMatchesSelector&&(matchesSel=(e,t)=>e.mozMatchesSelector(t));class CssStore{constructor(e){this._el=e}getComputedStyle(){return formatStyle(window.getComputedStyle(this._el))}getMatchedCSSRules(){const e=[];return[].slice.call(document.styleSheets).forEach((t=>{try{if(!t.cssRules)return}catch(e){return}for(let o in t.cssRules){let n=!1,i=t.cssRules[o];try{n=this._elMatchesSel(i.selectorText)}catch(e){}if(!n)return;e.push({selectorText:i.selectorText,style:formatStyle(i.style)})}})),e}_elMatchesSel(e){return matchesSel(this._el,e)}}var script$t={props:{highlightnNode:Object,contentWidth:String|Number,contentHeight:String|Number}};const _hoisted_1$r={class:\"dokit-margin\"},_hoisted_2$k=createBaseVNode(\"div\",{class:\"dokit-label\"},\"margin\",-1),_hoisted_3$i={class:\"dokit-top\"},_hoisted_4$h=createBaseVNode(\"br\",null,null,-1),_hoisted_5$f={class:\"dokit-left\"},_hoisted_6$e={class:\"dokit-border\"},_hoisted_7$a=createBaseVNode(\"div\",{class:\"dokit-label\"},\"border\",-1),_hoisted_8$9={class:\"dokit-top\"},_hoisted_9$9=createBaseVNode(\"br\",null,null,-1),_hoisted_10$8={class:\"dokit-left\"},_hoisted_11$6={class:\"dokit-padding\"},_hoisted_12$4=createBaseVNode(\"div\",{class:\"dokit-label\"},\"padding\",-1),_hoisted_13$4={class:\"dokit-top\"},_hoisted_14$4=createBaseVNode(\"br\",null,null,-1),_hoisted_15$3={class:\"dokit-left\"},_hoisted_16$2={class:\"dokit-content\"},_hoisted_17$2=createTextVNode(\" × \"),_hoisted_18$2={class:\"dokit-right\"},_hoisted_19$2=createBaseVNode(\"br\",null,null,-1),_hoisted_20$1={class:\"dokit-bottom\"},_hoisted_21$1={class:\"dokit-right\"},_hoisted_22$1=createBaseVNode(\"br\",null,null,-1),_hoisted_23$1={class:\"dokit-bottom\"},_hoisted_24$1={class:\"dokit-right\"},_hoisted_25$1=createBaseVNode(\"br\",null,null,-1),_hoisted_26$1={class:\"dokit-bottom\"};function render$t(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",_hoisted_1$r,[_hoisted_2$k,createBaseVNode(\"div\",_hoisted_3$i,toDisplayString(o.highlightnNode.boxStyle.marginTop),1),_hoisted_4$h,createBaseVNode(\"div\",_hoisted_5$f,toDisplayString(o.highlightnNode.boxStyle.marginLeft),1),createBaseVNode(\"div\",_hoisted_6$e,[_hoisted_7$a,createBaseVNode(\"div\",_hoisted_8$9,toDisplayString(o.highlightnNode.boxStyle.borderTopWidth),1),_hoisted_9$9,createBaseVNode(\"div\",_hoisted_10$8,toDisplayString(o.highlightnNode.boxStyle.borderLeftWidth),1),createBaseVNode(\"div\",_hoisted_11$6,[_hoisted_12$4,createBaseVNode(\"div\",_hoisted_13$4,toDisplayString(o.highlightnNode.boxStyle.paddingTop),1),_hoisted_14$4,createBaseVNode(\"div\",_hoisted_15$3,toDisplayString(o.highlightnNode.boxStyle.paddingLeft),1),createBaseVNode(\"div\",_hoisted_16$2,[createBaseVNode(\"span\",null,toDisplayString(o.contentWidth),1),_hoisted_17$2,createBaseVNode(\"span\",null,toDisplayString(o.contentHeight),1)]),createBaseVNode(\"div\",_hoisted_18$2,toDisplayString(o.highlightnNode.boxStyle.paddingRight),1),_hoisted_19$2,createBaseVNode(\"div\",_hoisted_20$1,toDisplayString(o.highlightnNode.boxStyle.paddingBottom),1)]),createBaseVNode(\"div\",_hoisted_21$1,toDisplayString(o.highlightnNode.boxStyle.borderRightWidth),1),_hoisted_22$1,createBaseVNode(\"div\",_hoisted_23$1,toDisplayString(o.highlightnNode.boxStyle.borderBottomWidth),1)]),createBaseVNode(\"div\",_hoisted_24$1,toDisplayString(o.highlightnNode.boxStyle.marginRight),1),_hoisted_25$1,createBaseVNode(\"div\",_hoisted_26$1,toDisplayString(o.highlightnNode.boxStyle.marginBottom),1)])}script$t.render=render$t;var script$s={components:{ElementBoxModel:script$t},props:{highlightElement:Object,highlightnNode:Object,contentWidth:String|Number,contentHeight:String|Number}};const _withScopeId$b=e=>(pushScopeId(\"data-v-080bc273\"),e=e(),popScopeId(),e),_hoisted_1$q={class:\"dokit-computed-style dokit-section\"},_hoisted_2$j={class:\"dokit-box-model\"},_hoisted_3$h={key:0,class:\"dokit-position\"},_hoisted_4$g=_withScopeId$b((()=>createBaseVNode(\"div\",{class:\"dokit-label\"},\"position\",-1))),_hoisted_5$e={class:\"dokit-top\"},_hoisted_6$d=_withScopeId$b((()=>createBaseVNode(\"br\",null,null,-1))),_hoisted_7$9={class:\"dokit-left\"},_hoisted_8$8={class:\"dokit-right\"},_hoisted_9$8=_withScopeId$b((()=>createBaseVNode(\"br\",null,null,-1))),_hoisted_10$7={class:\"dokit-bottom\"};function render$s(e,t,o,n,i,r){const s=resolveComponent(\"ElementBoxModel\");return openBlock(),createElementBlock(\"div\",_hoisted_1$q,[createBaseVNode(\"div\",_hoisted_2$j,[\"static\"!==o.highlightnNode.boxStyle.position?(openBlock(),createElementBlock(\"div\",_hoisted_3$h,[_hoisted_4$g,createBaseVNode(\"div\",_hoisted_5$e,toDisplayString(o.highlightnNode.boxStyle.top),1),_hoisted_6$d,createBaseVNode(\"div\",_hoisted_7$9,toDisplayString(o.highlightnNode.boxStyle.left),1),createVNode(s,{highlightnNode:o.highlightnNode,contentWidth:o.contentWidth,contentHeight:o.contentHeight},null,8,[\"highlightnNode\",\"contentWidth\",\"contentHeight\"]),createBaseVNode(\"div\",_hoisted_8$8,toDisplayString(o.highlightnNode.boxStyle.right),1),_hoisted_9$8,createBaseVNode(\"div\",_hoisted_10$7,toDisplayString(o.highlightnNode.boxStyle.bottom),1)])):(openBlock(),createBlock(s,{key:1,highlightnNode:o.highlightnNode,contentWidth:o.contentWidth,contentHeight:o.contentHeight},null,8,[\"highlightnNode\",\"contentWidth\",\"contentHeight\"]))])])}var css_248z$q=\".dokit-computed-style[data-v-080bc273] {\\n  font-size: 15px;\\n}\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273] {\\n  overflow-x: auto;\\n  color: #222;\\n  font-size: 12px;\\n  padding: 10px;\\n  text-align: center;\\n  white-space: nowrap;\\n}\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-label {\\n  position: absolute;\\n  margin-left: 3px;\\n  padding: 0 2px;\\n}\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-top,\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-left,\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-right,\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-bottom {\\n  display: inline-block;\\n}\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-left,\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-right {\\n  vertical-align: middle;\\n}\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-border,\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-padding,\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-content,\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-position,\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-margin {\\n  position: relative;\\n  background: #fff;\\n  display: inline-block;\\n  text-align: center;\\n  vertical-align: middle;\\n  padding: 3px;\\n  margin: 3px;\\n}\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273] .dokit-position[data-v-080bc273] {\\n  border: 1px grey dotted;\\n}\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-margin {\\n  border: 1px dashed;\\n  background: rgba(246, 178, 107, 0.66);\\n}\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-border {\\n  border: 1px #000 solid;\\n  background: rgba(255, 229, 153, 0.66);\\n}\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-padding {\\n  border: 1px grey dashed;\\n  background: rgba(147, 196, 125, 0.55);\\n}\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-content {\\n  border: 1px grey solid;\\n  min-width: 100px;\\n  background: rgba(111, 168, 220, 0.66);\\n}\\n.dokit-computed-style[data-v-080bc273] .dokit-box-model[data-v-080bc273][data-v-080bc273] .dokit-content[data-v-080bc273] span {\\n  margin: 0;\\n  padding: 0;\\n  border: 0;\\n  font: inherit;\\n  vertical-align: baseline;\\n}\\n\";styleInject(css_248z$q),script$s.render=render$s,script$s.__scopeId=\"data-v-080bc273\";var script$r={props:{styles:Array}};const _withScopeId$a=e=>(pushScopeId(\"data-v-782aac7b\"),e=e(),popScopeId(),e),_hoisted_1$p={class:\"dokit-styles dokit-section\"},_hoisted_2$i=_withScopeId$a((()=>createBaseVNode(\"h2\",null,\"Styles\",-1))),_hoisted_3$g={class:\"dokit-style-wrapper\"},_hoisted_4$f=createTextVNode(\": \"),_hoisted_5$d=[\"innerHTML\"],_hoisted_6$c=_withScopeId$a((()=>createBaseVNode(\"div\",null,\"}\",-1)));function render$r(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",_hoisted_1$p,[_hoisted_2$i,createBaseVNode(\"div\",_hoisted_3$g,[(openBlock(!0),createElementBlock(Fragment,null,renderList(o.styles,((e,t)=>(openBlock(),createElementBlock(\"div\",{class:\"dokit-style-rules\",key:t},[createBaseVNode(\"div\",null,toDisplayString(e.selectorText)+\" {\",1),(openBlock(!0),createElementBlock(Fragment,null,renderList(e.style,((e,t,o)=>(openBlock(),createElementBlock(\"div\",{class:\"dokit-rule\",key:o},[createBaseVNode(\"span\",null,toDisplayString(t),1),_hoisted_4$f,createBaseVNode(\"span\",{innerHTML:`${e};`},null,8,_hoisted_5$d)])))),128)),_hoisted_6$c])))),128))])])}var css_248z$p=\".dokit-styles[data-v-782aac7b] {\\n  font-size: 15px;\\n}\\n.dokit-styles[data-v-782aac7b] .dokit-style-wrapper[data-v-782aac7b] {\\n  padding: 10px;\\n}\\n.dokit-styles[data-v-782aac7b] .dokit-style-wrapper[data-v-782aac7b] .dokit-style-rules[data-v-782aac7b] {\\n  border: 1px solid #ccc;\\n  padding: 10px;\\n  margin-bottom: 10px;\\n}\\n.dokit-styles[data-v-782aac7b] .dokit-style-wrapper[data-v-782aac7b] .dokit-style-rules[data-v-782aac7b][data-v-782aac7b]:last-child {\\n  margin-bottom: 0;\\n}\\n.dokit-styles[data-v-782aac7b] .dokit-style-wrapper[data-v-782aac7b] .dokit-style-rules[data-v-782aac7b] .dokit-rule[data-v-782aac7b] {\\n  padding-left: 2em;\\n  word-break: break-all;\\n}\\n.dokit-styles[data-v-782aac7b] .dokit-style-wrapper[data-v-782aac7b] .dokit-style-rules[data-v-782aac7b] .dokit-rule[data-v-782aac7b][data-v-782aac7b] .dokit-style-color {\\n  position: relative;\\n  top: 1px;\\n  width: 10px;\\n  height: 10px;\\n  border-radius: 50%;\\n  margin-right: 2px;\\n  border: 1px solid #ccc;\\n  display: inline-block;\\n}\\n.dokit-styles[data-v-782aac7b] .dokit-style-wrapper[data-v-782aac7b] .dokit-style-rules[data-v-782aac7b] .dokit-rule[data-v-782aac7b] span[data-v-782aac7b] {\\n  color: #c80000;\\n}\\n\";styleInject(css_248z$p),script$r.render=render$r,script$r.__scopeId=\"data-v-782aac7b\";var script$q={props:{highlightnNode:Object}};const _withScopeId$9=e=>(pushScopeId(\"data-v-6185e768\"),e=e(),popScopeId(),e),_hoisted_1$o={class:\"dokit-attributes dokit-section\"},_hoisted_2$h=_withScopeId$9((()=>createBaseVNode(\"h2\",null,\"Attributes\",-1))),_hoisted_3$f={class:\"dokit-table-wrapper\"},_hoisted_4$e={key:0},_hoisted_5$c={key:0},_hoisted_6$b=_withScopeId$9((()=>createBaseVNode(\"td\",null,\"Empty\",-1))),_hoisted_7$8=[_hoisted_6$b],_hoisted_8$7={class:\"dokit-attribute-name-color\"},_hoisted_9$7={key:0,class:\"dokit-string-color\"},_hoisted_10$6=[\"href\"],_hoisted_11$5={key:1,class:\"dokit-string-color\"};function render$q(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",_hoisted_1$o,[_hoisted_2$h,createBaseVNode(\"div\",_hoisted_3$f,[createBaseVNode(\"table\",null,[o.highlightnNode?(openBlock(),createElementBlock(\"tbody\",_hoisted_4$e,[0===o.highlightnNode.attributes.length?(openBlock(),createElementBlock(\"tr\",_hoisted_5$c,_hoisted_7$8)):(openBlock(!0),createElementBlock(Fragment,{key:1},renderList(o.highlightnNode.attributes,((e,t)=>(openBlock(),createElementBlock(\"tr\",{key:t},[createBaseVNode(\"td\",_hoisted_8$7,toDisplayString(e.name),1),\"href\"===e.name||\"src\"===e.name?(openBlock(),createElementBlock(\"td\",_hoisted_9$7,[createBaseVNode(\"a\",{href:e.value,target:\"_blank\"},toDisplayString(e.value),9,_hoisted_10$6)])):(openBlock(),createElementBlock(\"td\",_hoisted_11$5,toDisplayString(e.value),1))])))),128))])):createCommentVNode(\"\",!0)])])])}var css_248z$o=\".dokit-attributes[data-v-6185e768] {\\n  font-size: 15px;\\n  margin-top: 10px;\\n}\\n.dokit-attributes[data-v-6185e768] .dokit-table-wrapper[data-v-6185e768] {\\n  overflow-x: auto;\\n}\\n.dokit-attributes[data-v-6185e768] .dokit-attribute-name-color[data-v-6185e768] {\\n  color: #994500;\\n}\\n.dokit-attributes[data-v-6185e768] .dokit-string-color[data-v-6185e768] {\\n  color: #1a1aa6;\\n}\\n.dokit-attributes[data-v-6185e768] table[data-v-6185e768] td[data-v-6185e768] {\\n  padding: 5px 10px;\\n  vertical-align: baseline;\\n}\\n\";styleInject(css_248z$o),script$q.render=render$q,script$q.__scopeId=\"data-v-6185e768\";var script$p={props:{parentTag:Array,highlightElement:Object},methods:{selectElement(e){at(e)}}};const _withScopeId$8=e=>(pushScopeId(\"data-v-bc25b728\"),e=e(),popScopeId(),e),_hoisted_1$n={class:\"dokit-breadcrumb\"},_hoisted_2$g={key:0,class:\"dokit-parents\"},_hoisted_3$e=[\"onClick\"],_hoisted_4$d={class:\"nodeName\"},_hoisted_5$b={key:0,class:\"nodeaId\"},_hoisted_6$a={key:1,class:\"nodeaClass\"},_hoisted_7$7=_withScopeId$8((()=>createBaseVNode(\"span\",{class:\"dokit-icon-arrow-right\"},\">\",-1))),_hoisted_8$6={key:1,class:\"dokit-element\"},_hoisted_9$6={class:\"nodeName\"},_hoisted_10$5={key:0,class:\"nodeaId\"},_hoisted_11$4={key:1,class:\"nodeaClass\"};function render$p(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",_hoisted_1$n,[o.parentTag.length>0?(openBlock(),createElementBlock(\"ul\",_hoisted_2$g,[(openBlock(!0),createElementBlock(Fragment,null,renderList(o.parentTag,((e,t)=>(openBlock(),createElementBlock(\"li\",{key:t},[createBaseVNode(\"div\",{class:\"dokit-parent\",onClick:t=>r.selectElement(e)},[createBaseVNode(\"span\",_hoisted_4$d,toDisplayString(e.nodeName.toLowerCase()),1),\"\"!==e.id?(openBlock(),createElementBlock(\"span\",_hoisted_5$b,toDisplayString(`#${e.id}`),1)):createCommentVNode(\"\",!0),\"\"!==e.className?(openBlock(),createElementBlock(\"span\",_hoisted_6$a,toDisplayString(`.${e.className}`),1)):createCommentVNode(\"\",!0)],8,_hoisted_3$e),_hoisted_7$7])))),128))])):createCommentVNode(\"\",!0),o.highlightElement?(openBlock(),createElementBlock(\"div\",_hoisted_8$6,[createBaseVNode(\"span\",_hoisted_9$6,toDisplayString(o.highlightElement.nodeName.toLowerCase()),1),\"\"!==o.highlightElement.id?(openBlock(),createElementBlock(\"span\",_hoisted_10$5,toDisplayString(`#${o.highlightElement.id}`),1)):createCommentVNode(\"\",!0),\"\"!==o.highlightElement.className?(openBlock(),createElementBlock(\"span\",_hoisted_11$4,toDisplayString(`.${o.highlightElement.className}`),1)):createCommentVNode(\"\",!0)])):createCommentVNode(\"\",!0)])}var css_248z$n=\".dokit-breadcrumb[data-v-bc25b728] .dokit-parents[data-v-bc25b728] {\\n  overflow-x: auto;\\n  background: #f3f3f3;\\n  color: #333;\\n  padding: 10px;\\n  white-space: nowrap;\\n  border-bottom: 1px solid #ccc;\\n  font-size: 15px;\\n  margin: 0;\\n}\\n.dokit-breadcrumb[data-v-bc25b728] .dokit-parents[data-v-bc25b728] li[data-v-bc25b728] {\\n  display: inline-block;\\n}\\n.dokit-breadcrumb[data-v-bc25b728] .dokit-parents[data-v-bc25b728] .dokit-parent[data-v-bc25b728] {\\n  display: inline-block;\\n}\\n.dokit-breadcrumb[data-v-bc25b728] .dokit-parents[data-v-bc25b728] .dokit-icon-arrow-right[data-v-bc25b728] {\\n  font-size: 15px;\\n  margin: 0 5px;\\n}\\n.dokit-breadcrumb[data-v-bc25b728] .dokit-element[data-v-bc25b728] {\\n  background: #f3f3f3;\\n  color: #333;\\n  user-select: text;\\n  word-break: break-all;\\n  padding: 10px;\\n  font-size: 16px;\\n  border-bottom: 1px solid #ccc;\\n}\\n.dokit-breadcrumb[data-v-bc25b728] .nodeName[data-v-bc25b728] {\\n  color: #881280;\\n}\\n.dokit-breadcrumb[data-v-bc25b728] .nodeaId[data-v-bc25b728] {\\n  color: #1a1aa8;\\n}\\n.dokit-breadcrumb[data-v-bc25b728] .nodeaClass[data-v-bc25b728] {\\n  color: #8f4919;\\n}\\n\";styleInject(css_248z$n),script$p.render=render$p,script$p.__scopeId=\"data-v-bc25b728\";var script$o={components:{ElementComputedStyle:script$s,ElementStyles:script$r,ElementAttributes:script$q,ElementBreadcrumb:script$p},data:()=>({parentTag:[],curCssStore:null,styles:[],computedStyle:null}),computed:{state(){return this.$store.state},highlightElement(){return this.state.highlightElement},highlightnNode(){return this.highlightElement?.__dokitForWeb_node},contentWidth(){return\"auto\"===this.computedStyle.width?\"auto\":this.highlightnNode.boxStyle.contentWidth},contentHeight(){return\"auto\"===this.computedStyle.height?\"auto\":this.highlightnNode.boxStyle.contentHeight}},watch:{highlightElement(e){e&&(this.curCssStore=new CssStore(e),this.getStyle(),this.parentTag=this.getParentTag(e).reverse(),this.computedStyle=this.curCssStore.getComputedStyle())}},methods:{getParentTag(e,t=[]){let o=this;return e?.parentElement?(t.push(e?.parentElement),o.getParentTag(e?.parentElement,t)):t},getStyle(){const e=this.curCssStore.getMatchedCSSRules();e.unshift(this.getInlineStyle(this.highlightElement?.style)),e.forEach((e=>this.processStyleRules(e?.style))),this.styles=e},getInlineStyle(e){const t={selectorText:\"element.style\",style:{}};for(let o=0,n=e?.length;o<n;o++){const n=e[o];\"initial\"!==e[n]&&(t.style[n]=e[n])}return t},processStyleRules(e){Object.keys(e).forEach((t=>{e[t]=this.processStyleRule(e[t])}))},processStyleRule(e){return(e+=\"\").replace(/rgba?\\((.*?)\\)/g,'<span class=\"dokit-style-color\" style=\"background-color: $&\"></span>$&').replace(/url\\(\"?(.*?)\"?\\)/g,((e,t)=>`url(\"${this.wrapLink(t)}\")`))},wrapLink:e=>`<a href=\"${e}\" target=\"_blank\">${e}</a>`}};const _hoisted_1$m={class:\"dokit-element-details\"};function render$o(e,t,o,n,i,r){const s=resolveComponent(\"ElementBreadcrumb\"),a=resolveComponent(\"ElementAttributes\"),l=resolveComponent(\"ElementStyles\"),c=resolveComponent(\"ElementComputedStyle\");return openBlock(),createElementBlock(\"div\",_hoisted_1$m,[createVNode(s,{parentTag:i.parentTag,highlightElement:r.highlightElement},null,8,[\"parentTag\",\"highlightElement\"]),createVNode(a,{highlightnNode:r.highlightnNode},null,8,[\"highlightnNode\"]),createVNode(l,{styles:i.styles},null,8,[\"styles\"]),r.highlightElement?(openBlock(),createBlock(c,{key:0,highlightElement:r.highlightElement,highlightnNode:r.highlightnNode,contentWidth:r.contentWidth,contentHeight:r.contentHeight},null,8,[\"highlightElement\",\"highlightnNode\",\"contentWidth\",\"contentHeight\"])):createCommentVNode(\"\",!0)])}var css_248z$m=\".dokit-element-details[data-v-34e094cc] {\\n  overflow-y: auto;\\n  height: calc(100% - 76px);\\n}\\n.dokit-element-details[data-v-34e094cc][data-v-34e094cc] .dokit-section {\\n  border-bottom: 1px solid #ccc;\\n  color: #333;\\n  margin-bottom: 10px;\\n}\\n.dokit-element-details[data-v-34e094cc][data-v-34e094cc] .dokit-section[data-v-34e094cc] h2 {\\n  background: #f3f3f3;\\n  border-top: 1px solid #ccc;\\n  padding: 10px;\\n  font-size: 18px;\\n  margin: 0;\\n}\\n\";styleInject(css_248z$m),script$o.render=render$o,script$o.__scopeId=\"data-v-34e094cc\";var script$n={data:()=>({selectElement:!1,checkCurrentElement:!1,oldElement:null}),computed:{state(){return this.$store.state},showHighlightElement(){return this.state.showHighlightElement},highlightElement(){return this.state.highlightElement}},created(){this.onScroll=debounce(this.onScroll,300)},watch:{showHighlightElement(e){e?(document.body.addEventListener(\"click\",this.elementClick,!0),window.addEventListener(\"scroll\",this.onScroll)):(document.body.removeEventListener(\"click\",this.elementClick,!0),window.removeEventListener(\"scroll\",this.onScroll))}},methods:{openCheck(){this.selectElement=!this.selectElement,this.showHighlightElement||lt(),rt()},elementClick(e){e.preventDefault(),e.stopImmediatePropagation(),e.target!==this.highlightElement&&at(e.target)},onScroll(){this.oldElement=this.highlightElement,at(null),this.$nextTick((()=>{at(this.oldElement)}))}}};const _hoisted_1$l={class:\"element-snippet-component\"},_hoisted_2$f=[\"src\"];function render$n(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",_hoisted_1$l,[createBaseVNode(\"div\",{class:\"element-btn\",onClick:t[0]||(t[0]=(...e)=>r.openCheck&&r.openCheck(...e))},[createBaseVNode(\"img\",{src:i.selectElement?\"https://pt-starimg.didistatic.com/static/starimg/img/mP3782Ooy71635737733419.png\":\"https://pt-starimg.didistatic.com/static/starimg/img/6Yjqj9hBVz1635736368868.png\"},null,8,_hoisted_2$f)])])}var css_248z$l=\".element-snippet-component {\\n  height: 40px;\\n  background: #f3f3f3;\\n  position: absolute;\\n  z-index: 100;\\n  left: 0;\\n  bottom: 0;\\n  width: 100%;\\n  display: flex;\\n  align-items: center;\\n  justify-content: center;\\n}\\n.element-snippet-component .element-btn {\\n  height: 100%;\\n  width: 100%;\\n  display: flex;\\n  align-items: center;\\n  justify-content: center;\\n}\\n.element-snippet-component .element-btn img {\\n  height: 20px;\\n}\\n\";styleInject(css_248z$l),script$n.render=render$n;var script$m={components:{ElementTree:script$u,ElementSnippet:script$n,ElementDetails:script$o},data:()=>({node:null,observer:null,isRealTime:null,config:{attributes:!0,childList:!0,characterData:!0,subtree:!0},active:0}),mounted(){this.node=this.getNode(document.documentElement),this.observer=new MutationObserver((e=>{for(let t=0;t<e.length;t++){let o=e[t];this._isInDokit(o.target)||this.onMutation(o)}})),this.isRealTime=JSON.parse(localStorage.getItem(\"dokitElementRealTime\"))??!0,at(this.node.$view)},computed:{state(){return this.$store.state},highlightElement(){return this.state.highlightElement}},destroyed(){this.observer.disconnect()},watch:{isRealTime:{handler:function(e){e?(this.node=this.getNode(document.documentElement),$bus.emit(this.node.key),localStorage.setItem(\"dokitElementRealTime\",e),this.observer.observe(document.documentElement,this.config)):!1===e&&(this.observer.disconnect(),localStorage.setItem(\"dokitElementRealTime\",e)),console.log(this.node)},immediate:!0}},methods:{changeMode(e){this.active=e},getNode(e){if(this._isIgnoredElement(e))return;let t=e.__dokitForWeb_node||{};if(t.$view=e,t.nodeType=e.nodeType,t.key=t.key||guid(),t.nodeName=e.nodeName,t.tagName=e.tagName||\"\",t.textContent=\"\",t.nodeType!=e.TEXT_NODE&&t.nodeType!=e.DOCUMENT_TYPE_NODE||(t.textContent=e.textContent),t.nodeType===e.ELEMENT_NODE&&(t.boxStyle=this.getBoxModelValue(e)),t.id=e.id||\"\",t.className=e.className||\"\",t.attributes=[],e.hasAttributes&&e.hasAttributes())for(let o=0;o<e.attributes.length;o++)t.attributes.push({name:e.attributes[o].name,value:e.attributes[o].value||\"\"});if(t.childNodes=[],e.childNodes.length>0)for(let o=0;o<e.childNodes.length;o++){let n=this.getNode(e.childNodes[o]);n&&t.childNodes.push(n)}return e.__dokitForWeb_node=t,t},_isIgnoredElement:e=>e.nodeType==e.TEXT_NODE&&\"\"==e.textContent.replace(/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$|\\n+/g,\"\"),_isInDokit(e){let t=e;for(;null!=t;){if(\"dokit-root\"==t.id)return!0;t=t.parentNode||void 0}return!1},onMutation(e){switch(e.type){case\"childList\":e.removedNodes.length>0&&this.onChildRemove(e),e.addedNodes.length>0&&this.onChildAdd(e);break;case\"attributes\":this.onAttributesChange(e);break;case\"characterData\":this.onCharacterDataChange(e)}},onAttributesChange(e){let t=e.target.__dokitForWeb_node;t&&(e.target===this.highlightElement&&(at(null),this.$nextTick((()=>{at(e.target)}))),t=this.getNode(e.target),$bus.emit(t.key+\"refreshMy\"))},onCharacterDataChange(e){let t=e.target.__dokitForWeb_node;t&&(e.target!==this.highlightElement&&e.target.parentNode!==this.highlightElement||(at(null),this.$nextTick((()=>{at(e.target.parentNode)}))),t=this.getNode(e.target),$bus.emit(t.key+\"refreshMy\"))},onChildAdd(e){let t=e.target,o=t.__dokitForWeb_node;o&&(t===this.highlightElement&&(at(null),this.$nextTick((()=>{at(t)}))),this.getNode(t),$bus.emit(o.key+\"refreshChild\"))},onChildRemove(e){let t=e.target.__dokitForWeb_node;if(t){for(let o=0;o<e.removedNodes.length;o++){let n=e.removedNodes[o],i=n?.__dokitForWeb_node?.key;i&&(n===this.highlightElement&&at(null),this.deleteNode(t,i))}$bus.emit(t.key+\"refreshChild\")}},deleteNode(e,t){let o=e.childNodes.findIndex((e=>e.key===t));e.childNodes.splice(o,1)},getBoxModelValue(e){return{display:this.getStyle(e,\"display\"),position:this.getStyle(e,\"position\"),top:this.getStyle(e,\"top\"),right:this.getStyle(e,\"right\"),bottom:this.getStyle(e,\"bottom\"),left:this.getStyle(e,\"left\"),marginTop:this.getStyle(e,\"marginTop\"),marginRight:this.getStyle(e,\"marginRight\"),marginBottom:this.getStyle(e,\"marginBottom\"),marginLeft:this.getStyle(e,\"marginLeft\"),borderTopWidth:this.getStyle(e,\"borderTopWidth\"),borderRightWidth:this.getStyle(e,\"borderRightWidth\"),borderBottomWidth:this.getStyle(e,\"borderBottomWidth\"),borderLeftWidth:this.getStyle(e,\"borderLeftWidth\"),paddingTop:this.getStyle(e,\"paddingTop\"),paddingRight:this.getStyle(e,\"paddingRight\"),paddingBottom:this.getStyle(e,\"paddingBottom\"),paddingLeft:this.getStyle(e,\"paddingLeft\"),contentWidth:e.offsetWidth-parseInt(this.getStyle(e,\"paddingLeft\"))-parseInt(this.getStyle(e,\"paddingRight\"))-parseInt(this.getStyle(e,\"borderLeftWidth\"))-parseInt(this.getStyle(e,\"borderRightWidth\")),contentHeight:e.offsetHeight-parseInt(this.getStyle(e,\"paddingTop\"))-parseInt(this.getStyle(e,\"paddingBottom\"))-parseInt(this.getStyle(e,\"borderTopWidth\"))-parseInt(this.getStyle(e,\"borderBottomWidth\"))}},getStyle(e,t){let o=null;return o=e?.currentStyle?e.currentStyle[t]:document.defaultView.getComputedStyle(e,null)[t],-1!==o.indexOf(\"px\")?/\\d+/.exec(o)[0]:o}}};const _hoisted_1$k={class:\"element-container\"},_hoisted_2$e={class:\"element-tab\"},_hoisted_3$d={class:\"tree-container\"},_hoisted_4$c={class:\"real-time-switch\"},_hoisted_5$a=createBaseVNode(\"div\",null,\"实时更新\",-1);function render$m(e,t,o,n,i,r){const s=resolveComponent(\"ElementTree\"),a=resolveComponent(\"ElementDetails\"),l=resolveComponent(\"ElementSnippet\");return openBlock(),createElementBlock(\"div\",_hoisted_1$k,[createBaseVNode(\"div\",_hoisted_2$e,[createBaseVNode(\"div\",{onClick:t[0]||(t[0]=e=>r.changeMode(0)),class:normalizeClass(\"elemen-tab-item \"+(0===i.active?\"elemen-tab-item-active\":\"\"))},\" 视图树 \",2),createBaseVNode(\"div\",{onClick:t[1]||(t[1]=e=>r.changeMode(1)),class:normalizeClass(\"elemen-tab-item \"+(1===i.active?\"elemen-tab-item-active\":\"\"))},\" 元素属性 \",2)]),withDirectives(createBaseVNode(\"div\",_hoisted_3$d,[createBaseVNode(\"div\",_hoisted_4$c,[_hoisted_5$a,withDirectives(createBaseVNode(\"input\",{class:\"switch\",type:\"checkbox\",\"onUpdate:modelValue\":t[2]||(t[2]=e=>i.isRealTime=e)},null,512),[[vModelCheckbox,i.isRealTime]])]),createVNode(s,{node:i.node},null,8,[\"node\"])],512),[[vShow,0===i.active]]),withDirectives(createVNode(a,null,null,512),[[vShow,1===i.active]]),createVNode(l)])}var css_248z$k='.element-container {\\n  position: relative;\\n  height: 100%;\\n  overflow: hidden;\\n}\\n.element-container .tree-container {\\n  overflow-y: auto;\\n  height: calc(100% - 76px);\\n}\\n.element-container .tree-container .real-time-switch {\\n  display: flex;\\n  flex-direction: row;\\n  align-items: center;\\n  font-size: 18px;\\n  padding: 5px;\\n}\\n.element-container .tree-container .real-time-switch .switch {\\n  appearance: none;\\n  -moz-appearance: button;\\n  -webkit-appearance: none;\\n}\\n.element-container .tree-container .real-time-switch .switch {\\n  position: relative;\\n  margin: 0;\\n  width: 40px;\\n  height: 24px;\\n  border: 1px solid #ebebf9;\\n  outline: 0;\\n  border-radius: 16px;\\n  box-sizing: border-box;\\n  background-color: #ebebf9;\\n  -webkit-transition: background-color 0.1s, border 0.1s;\\n  transition: background-color 0.1s, border 0.1s;\\n  margin-left: auto;\\n}\\n.element-container .tree-container .real-time-switch .switch:before {\\n  content: \" \";\\n  position: absolute;\\n  top: 0;\\n  left: 0;\\n  width: 38px;\\n  height: 22px;\\n  border-radius: 19px;\\n  background-color: #ebebf9;\\n  -webkit-transition: -webkit-transform 0.35s cubic-bezier(0.45, 1, 0.4, 1);\\n  transition: -webkit-transform 0.35s cubic-bezier(0.45, 1, 0.4, 1);\\n  transition: transform 0.35s cubic-bezier(0.45, 1, 0.4, 1);\\n}\\n.element-container .tree-container .real-time-switch .switch:after {\\n  content: \" \";\\n  position: absolute;\\n  top: 0;\\n  left: 1px;\\n  width: 22px;\\n  height: 22px;\\n  border-radius: 15px;\\n  background-color: #ffffff;\\n  /*box-shadow: 0 1PX 3PX rgba(0, 0, 0, 0.4);*/\\n  -webkit-transition: -webkit-transform 0.35s cubic-bezier(0.4, 0.4, 0.25, 1.35);\\n  transition: -webkit-transform 0.35s cubic-bezier(0.4, 0.4, 0.25, 1.35);\\n  transition: transform 0.35s cubic-bezier(0.4, 0.4, 0.25, 1.35);\\n}\\n.element-container .tree-container .real-time-switch .switch:checked {\\n  background: #457cbe;\\n  border: solid 1px #457cbe;\\n}\\n.element-container .tree-container .real-time-switch .switch:checked:before {\\n  transform: scale(0);\\n}\\n.element-container .tree-container .real-time-switch .switch:checked:after {\\n  transform: translateX(15px);\\n}\\n.element-container .element-tab {\\n  width: 100%;\\n  display: flex;\\n  align-items: center;\\n  justify-content: center;\\n}\\n.element-container .element-tab .elemen-tab-item {\\n  font-size: 18px;\\n  width: 50%;\\n  display: flex;\\n  align-items: center;\\n  justify-content: center;\\n  padding: 5px 10px;\\n  border-bottom: 1px #eee solid;\\n}\\n.element-container .element-tab .elemen-tab-item:first-child {\\n  border-right: 1px #eee solid;\\n}\\n.element-container .element-tab .elemen-tab-item-active {\\n  border-bottom: 1px transparent solid;\\n  color: #457cbe;\\n}\\n';styleInject(css_248z$k),script$m.render=render$m;var Element$1=new ce({nameZh:\"元素查看\",name:\"element\",icon:\"https://pt-starimg.didistatic.com/static/starimg/img/nktYHc1alL1635323338193.png\",component:script$m}),commonjsGlobal=\"undefined\"!=typeof globalThis?globalThis:\"undefined\"!=typeof window?window:\"undefined\"!=typeof global?global:\"undefined\"!=typeof self?self:{};function commonjsRequire(){throw new Error(\"Dynamic requires are not currently supported by rollup-plugin-commonjs\")}function unwrapExports(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,\"default\")?e.default:e}function createCommonjsModule(e,t){return e(t={exports:{}},t.exports),t.exports}var jsQR=createCommonjsModule((function(e,t){var o;\"undefined\"!=typeof self&&self,o=function(){return function(e){var t={};function o(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,o),i.l=!0,i.exports}return o.m=e,o.c=t,o.d=function(e,t,n){o.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,\"a\",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p=\"\",o(o.s=3)}([function(e,t,o){Object.defineProperty(t,\"__esModule\",{value:!0});var n=function(){function e(e,t){this.width=t,this.height=e.length/t,this.data=e}return e.createEmpty=function(t,o){return new e(new Uint8ClampedArray(t*o),t)},e.prototype.get=function(e,t){return!(e<0||e>=this.width||t<0||t>=this.height||!this.data[t*this.width+e])},e.prototype.set=function(e,t,o){this.data[t*this.width+e]=o?1:0},e.prototype.setRegion=function(e,t,o,n,i){for(var r=t;r<t+n;r++)for(var s=e;s<e+o;s++)this.set(s,r,!!i)},e}();t.BitMatrix=n},function(e,t,o){Object.defineProperty(t,\"__esModule\",{value:!0});var n=o(2);t.addOrSubtractGF=function(e,t){return e^t};var i=function(){function e(e,t,o){this.primitive=e,this.size=t,this.generatorBase=o,this.expTable=new Array(this.size),this.logTable=new Array(this.size);for(var i=1,r=0;r<this.size;r++)this.expTable[r]=i,(i*=2)>=this.size&&(i=(i^this.primitive)&this.size-1);for(r=0;r<this.size-1;r++)this.logTable[this.expTable[r]]=r;this.zero=new n.default(this,Uint8ClampedArray.from([0])),this.one=new n.default(this,Uint8ClampedArray.from([1]))}return e.prototype.multiply=function(e,t){return 0===e||0===t?0:this.expTable[(this.logTable[e]+this.logTable[t])%(this.size-1)]},e.prototype.inverse=function(e){if(0===e)throw new Error(\"Can't invert 0\");return this.expTable[this.size-this.logTable[e]-1]},e.prototype.buildMonomial=function(e,t){if(e<0)throw new Error(\"Invalid monomial degree less than 0\");if(0===t)return this.zero;var o=new Uint8ClampedArray(e+1);return o[0]=t,new n.default(this,o)},e.prototype.log=function(e){if(0===e)throw new Error(\"Can't take log(0)\");return this.logTable[e]},e.prototype.exp=function(e){return this.expTable[e]},e}();t.default=i},function(e,t,o){Object.defineProperty(t,\"__esModule\",{value:!0});var n=o(1),i=function(){function e(e,t){if(0===t.length)throw new Error(\"No coefficients.\");this.field=e;var o=t.length;if(o>1&&0===t[0]){for(var n=1;n<o&&0===t[n];)n++;if(n===o)this.coefficients=e.zero.coefficients;else{this.coefficients=new Uint8ClampedArray(o-n);for(var i=0;i<this.coefficients.length;i++)this.coefficients[i]=t[n+i]}}else this.coefficients=t}return e.prototype.degree=function(){return this.coefficients.length-1},e.prototype.isZero=function(){return 0===this.coefficients[0]},e.prototype.getCoefficient=function(e){return this.coefficients[this.coefficients.length-1-e]},e.prototype.addOrSubtract=function(t){var o;if(this.isZero())return t;if(t.isZero())return this;var i=this.coefficients,r=t.coefficients;i.length>r.length&&(i=(o=[r,i])[0],r=o[1]);for(var s=new Uint8ClampedArray(r.length),a=r.length-i.length,l=0;l<a;l++)s[l]=r[l];for(l=a;l<r.length;l++)s[l]=n.addOrSubtractGF(i[l-a],r[l]);return new e(this.field,s)},e.prototype.multiply=function(t){if(0===t)return this.field.zero;if(1===t)return this;for(var o=this.coefficients.length,n=new Uint8ClampedArray(o),i=0;i<o;i++)n[i]=this.field.multiply(this.coefficients[i],t);return new e(this.field,n)},e.prototype.multiplyPoly=function(t){if(this.isZero()||t.isZero())return this.field.zero;for(var o=this.coefficients,i=o.length,r=t.coefficients,s=r.length,a=new Uint8ClampedArray(i+s-1),l=0;l<i;l++)for(var c=o[l],d=0;d<s;d++)a[l+d]=n.addOrSubtractGF(a[l+d],this.field.multiply(c,r[d]));return new e(this.field,a)},e.prototype.multiplyByMonomial=function(t,o){if(t<0)throw new Error(\"Invalid degree less than 0\");if(0===o)return this.field.zero;for(var n=this.coefficients.length,i=new Uint8ClampedArray(n+t),r=0;r<n;r++)i[r]=this.field.multiply(this.coefficients[r],o);return new e(this.field,i)},e.prototype.evaluateAt=function(e){var t=0;if(0===e)return this.getCoefficient(0);var o=this.coefficients.length;if(1===e)return this.coefficients.forEach((function(e){t=n.addOrSubtractGF(t,e)})),t;t=this.coefficients[0];for(var i=1;i<o;i++)t=n.addOrSubtractGF(this.field.multiply(e,t),this.coefficients[i]);return t},e}();t.default=i},function(e,t,o){Object.defineProperty(t,\"__esModule\",{value:!0});var n=o(4),i=o(5),r=o(11),s=o(12);function a(e){var t=s.locate(e);if(!t)return null;for(var o=0,n=t;o<n.length;o++){var a=n[o],l=r.extract(e,a),c=i.decode(l.matrix);if(c)return{binaryData:c.bytes,data:c.text,chunks:c.chunks,version:c.version,location:{topRightCorner:l.mappingFunction(a.dimension,0),topLeftCorner:l.mappingFunction(0,0),bottomRightCorner:l.mappingFunction(a.dimension,a.dimension),bottomLeftCorner:l.mappingFunction(0,a.dimension),topRightFinderPattern:a.topRight,topLeftFinderPattern:a.topLeft,bottomLeftFinderPattern:a.bottomLeft,bottomRightAlignmentPattern:a.alignmentPattern}}}return null}var l={inversionAttempts:\"attemptBoth\"};function c(e,t,o,i){void 0===i&&(i={});var r=l;Object.keys(r||{}).forEach((function(e){r[e]=i[e]||r[e]}));var s=\"attemptBoth\"===r.inversionAttempts||\"invertFirst\"===r.inversionAttempts,c=\"onlyInvert\"===r.inversionAttempts||\"invertFirst\"===r.inversionAttempts,d=n.binarize(e,t,o,s),u=d.binarized,h=d.inverted,p=a(c?h:u);return p||\"attemptBoth\"!==r.inversionAttempts&&\"invertFirst\"!==r.inversionAttempts||(p=a(c?u:h)),p}c.default=c,t.default=c},function(e,t,o){Object.defineProperty(t,\"__esModule\",{value:!0});var n=o(0);function i(e,t,o){return e<t?t:e>o?o:e}var r=function(){function e(e,t){this.width=e,this.data=new Uint8ClampedArray(e*t)}return e.prototype.get=function(e,t){return this.data[t*this.width+e]},e.prototype.set=function(e,t,o){this.data[t*this.width+e]=o},e}();t.binarize=function(e,t,o,s){if(e.length!==t*o*4)throw new Error(\"Malformed data passed to binarizer.\");for(var a=new r(t,o),l=0;l<t;l++)for(var c=0;c<o;c++){var d=e[4*(c*t+l)+0],u=e[4*(c*t+l)+1],h=e[4*(c*t+l)+2];a.set(l,c,.2126*d+.7152*u+.0722*h)}for(var p=Math.ceil(t/8),f=Math.ceil(o/8),m=new r(p,f),g=0;g<f;g++)for(var v=0;v<p;v++){var k=0,y=1/0,w=0;for(c=0;c<8;c++)for(l=0;l<8;l++){var A=a.get(8*v+l,8*g+c);k+=A,y=Math.min(y,A),w=Math.max(w,A)}var C=k/Math.pow(8,2);if(w-y<=24&&(C=y/2,g>0&&v>0)){var b=(m.get(v,g-1)+2*m.get(v-1,g)+m.get(v-1,g-1))/4;y<b&&(C=b)}m.set(v,g,C)}var _=n.BitMatrix.createEmpty(t,o),x=null;for(s&&(x=n.BitMatrix.createEmpty(t,o)),g=0;g<f;g++)for(v=0;v<p;v++){for(var S=i(v,2,p-3),B=i(g,2,f-3),L=(k=0,-2);L<=2;L++)for(var T=-2;T<=2;T++)k+=m.get(S+L,B+T);var E=k/25;for(L=0;L<8;L++)for(T=0;T<8;T++){l=8*v+L,c=8*g+T;var N=a.get(l,c);_.set(l,c,N<=E),s&&x.set(l,c,!(N<=E))}}return s?{binarized:_,inverted:x}:{binarized:_}}},function(e,t,o){Object.defineProperty(t,\"__esModule\",{value:!0});var n=o(0),i=o(6),r=o(9),s=o(10);function a(e,t){for(var o=e^t,n=0;o;)n++,o&=o-1;return n}function l(e,t){return t<<1|e}var c=[{bits:21522,formatInfo:{errorCorrectionLevel:1,dataMask:0}},{bits:20773,formatInfo:{errorCorrectionLevel:1,dataMask:1}},{bits:24188,formatInfo:{errorCorrectionLevel:1,dataMask:2}},{bits:23371,formatInfo:{errorCorrectionLevel:1,dataMask:3}},{bits:17913,formatInfo:{errorCorrectionLevel:1,dataMask:4}},{bits:16590,formatInfo:{errorCorrectionLevel:1,dataMask:5}},{bits:20375,formatInfo:{errorCorrectionLevel:1,dataMask:6}},{bits:19104,formatInfo:{errorCorrectionLevel:1,dataMask:7}},{bits:30660,formatInfo:{errorCorrectionLevel:0,dataMask:0}},{bits:29427,formatInfo:{errorCorrectionLevel:0,dataMask:1}},{bits:32170,formatInfo:{errorCorrectionLevel:0,dataMask:2}},{bits:30877,formatInfo:{errorCorrectionLevel:0,dataMask:3}},{bits:26159,formatInfo:{errorCorrectionLevel:0,dataMask:4}},{bits:25368,formatInfo:{errorCorrectionLevel:0,dataMask:5}},{bits:27713,formatInfo:{errorCorrectionLevel:0,dataMask:6}},{bits:26998,formatInfo:{errorCorrectionLevel:0,dataMask:7}},{bits:5769,formatInfo:{errorCorrectionLevel:3,dataMask:0}},{bits:5054,formatInfo:{errorCorrectionLevel:3,dataMask:1}},{bits:7399,formatInfo:{errorCorrectionLevel:3,dataMask:2}},{bits:6608,formatInfo:{errorCorrectionLevel:3,dataMask:3}},{bits:1890,formatInfo:{errorCorrectionLevel:3,dataMask:4}},{bits:597,formatInfo:{errorCorrectionLevel:3,dataMask:5}},{bits:3340,formatInfo:{errorCorrectionLevel:3,dataMask:6}},{bits:2107,formatInfo:{errorCorrectionLevel:3,dataMask:7}},{bits:13663,formatInfo:{errorCorrectionLevel:2,dataMask:0}},{bits:12392,formatInfo:{errorCorrectionLevel:2,dataMask:1}},{bits:16177,formatInfo:{errorCorrectionLevel:2,dataMask:2}},{bits:14854,formatInfo:{errorCorrectionLevel:2,dataMask:3}},{bits:9396,formatInfo:{errorCorrectionLevel:2,dataMask:4}},{bits:8579,formatInfo:{errorCorrectionLevel:2,dataMask:5}},{bits:11994,formatInfo:{errorCorrectionLevel:2,dataMask:6}},{bits:11245,formatInfo:{errorCorrectionLevel:2,dataMask:7}}],d=[function(e){return(e.y+e.x)%2==0},function(e){return e.y%2==0},function(e){return e.x%3==0},function(e){return(e.y+e.x)%3==0},function(e){return(Math.floor(e.y/2)+Math.floor(e.x/3))%2==0},function(e){return e.x*e.y%2+e.x*e.y%3==0},function(e){return(e.y*e.x%2+e.y*e.x%3)%2==0},function(e){return((e.y+e.x)%2+e.y*e.x%3)%2==0}];function u(e,t,o){for(var i=d[o.dataMask],r=e.height,s=function(e){var t=17+4*e.versionNumber,o=n.BitMatrix.createEmpty(t,t);o.setRegion(0,0,9,9,!0),o.setRegion(t-8,0,8,9,!0),o.setRegion(0,t-8,9,8,!0);for(var i=0,r=e.alignmentPatternCenters;i<r.length;i++)for(var s=r[i],a=0,l=e.alignmentPatternCenters;a<l.length;a++){var c=l[a];6===s&&6===c||6===s&&c===t-7||s===t-7&&6===c||o.setRegion(s-2,c-2,5,5,!0)}return o.setRegion(6,9,1,t-17,!0),o.setRegion(9,6,t-17,1,!0),e.versionNumber>6&&(o.setRegion(t-11,0,3,6,!0),o.setRegion(0,t-11,6,3,!0)),o}(t),a=[],c=0,u=0,h=!0,p=r-1;p>0;p-=2){6===p&&p--;for(var f=0;f<r;f++)for(var m=h?r-1-f:f,g=0;g<2;g++){var v=p-g;if(!s.get(v,m)){u++;var k=e.get(v,m);i({y:m,x:v})&&(k=!k),c=l(k,c),8===u&&(a.push(c),u=0,c=0)}}h=!h}return a}function h(e){var t=function(e){var t=e.height,o=Math.floor((t-17)/4);if(o<=6)return s.VERSIONS[o-1];for(var n=0,i=5;i>=0;i--)for(var r=t-9;r>=t-11;r--)n=l(e.get(r,i),n);var c=0;for(r=5;r>=0;r--)for(i=t-9;i>=t-11;i--)c=l(e.get(r,i),c);for(var d,u=1/0,h=0,p=s.VERSIONS;h<p.length;h++){var f=p[h];if(f.infoBits===n||f.infoBits===c)return f;var m=a(n,f.infoBits);m<u&&(d=f,u=m),(m=a(c,f.infoBits))<u&&(d=f,u=m)}return u<=3?d:void 0}(e);if(!t)return null;var o=function(e){for(var t=0,o=0;o<=8;o++)6!==o&&(t=l(e.get(o,8),t));for(var n=7;n>=0;n--)6!==n&&(t=l(e.get(8,n),t));var i=e.height,r=0;for(n=i-1;n>=i-7;n--)r=l(e.get(8,n),r);for(o=i-8;o<i;o++)r=l(e.get(o,8),r);for(var s=1/0,d=null,u=0,h=c;u<h.length;u++){var p=h[u],f=p.bits,m=p.formatInfo;if(f===t||f===r)return m;var g=a(t,f);g<s&&(d=m,s=g),t!==r&&(g=a(r,f))<s&&(d=m,s=g)}return s<=3?d:null}(e);if(!o)return null;var n=function(e,t,o){var n=t.errorCorrectionLevels[o],i=[],r=0;if(n.ecBlocks.forEach((function(e){for(var t=0;t<e.numBlocks;t++)i.push({numDataCodewords:e.dataCodewordsPerBlock,codewords:[]}),r+=e.dataCodewordsPerBlock+n.ecCodewordsPerBlock})),e.length<r)return null;e=e.slice(0,r);for(var s=n.ecBlocks[0].dataCodewordsPerBlock,a=0;a<s;a++)for(var l=0,c=i;l<c.length;l++)c[l].codewords.push(e.shift());if(n.ecBlocks.length>1){var d=n.ecBlocks[0].numBlocks,u=n.ecBlocks[1].numBlocks;for(a=0;a<u;a++)i[d+a].codewords.push(e.shift())}for(;e.length>0;)for(var h=0,p=i;h<p.length;h++)p[h].codewords.push(e.shift());return i}(u(e,t,o),t,o.errorCorrectionLevel);if(!n)return null;for(var d=n.reduce((function(e,t){return e+t.numDataCodewords}),0),h=new Uint8ClampedArray(d),p=0,f=0,m=n;f<m.length;f++){var g=m[f],v=r.decode(g.codewords,g.codewords.length-g.numDataCodewords);if(!v)return null;for(var k=0;k<g.numDataCodewords;k++)h[p++]=v[k]}try{return i.decode(h,t.versionNumber)}catch(e){return null}}t.decode=function(e){if(null==e)return null;var t=h(e);if(t)return t;for(var o=0;o<e.width;o++)for(var n=o+1;n<e.height;n++)e.get(o,n)!==e.get(n,o)&&(e.set(o,n,!e.get(o,n)),e.set(n,o,!e.get(n,o)));return h(e)}},function(e,t,o){Object.defineProperty(t,\"__esModule\",{value:!0});var n,i,r=o(7),s=o(8);function a(e,t){for(var o=[],n=\"\",i=[10,12,14][t],r=e.readBits(i);r>=3;){if((c=e.readBits(10))>=1e3)throw new Error(\"Invalid numeric value above 999\");var s=Math.floor(c/100),a=Math.floor(c/10)%10,l=c%10;o.push(48+s,48+a,48+l),n+=s.toString()+a.toString()+l.toString(),r-=3}if(2===r){if((c=e.readBits(7))>=100)throw new Error(\"Invalid numeric value above 99\");s=Math.floor(c/10),a=c%10,o.push(48+s,48+a),n+=s.toString()+a.toString()}else if(1===r){var c;if((c=e.readBits(4))>=10)throw new Error(\"Invalid numeric value above 9\");o.push(48+c),n+=c.toString()}return{bytes:o,text:n}}!function(e){e.Numeric=\"numeric\",e.Alphanumeric=\"alphanumeric\",e.Byte=\"byte\",e.Kanji=\"kanji\",e.ECI=\"eci\"}(n=t.Mode||(t.Mode={})),function(e){e[e.Terminator=0]=\"Terminator\",e[e.Numeric=1]=\"Numeric\",e[e.Alphanumeric=2]=\"Alphanumeric\",e[e.Byte=4]=\"Byte\",e[e.Kanji=8]=\"Kanji\",e[e.ECI=7]=\"ECI\"}(i||(i={}));var l=[\"0\",\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\",\"9\",\"A\",\"B\",\"C\",\"D\",\"E\",\"F\",\"G\",\"H\",\"I\",\"J\",\"K\",\"L\",\"M\",\"N\",\"O\",\"P\",\"Q\",\"R\",\"S\",\"T\",\"U\",\"V\",\"W\",\"X\",\"Y\",\"Z\",\" \",\"$\",\"%\",\"*\",\"+\",\"-\",\".\",\"/\",\":\"];function c(e,t){for(var o=[],n=\"\",i=[9,11,13][t],r=e.readBits(i);r>=2;){var s=e.readBits(11),a=Math.floor(s/45),c=s%45;o.push(l[a].charCodeAt(0),l[c].charCodeAt(0)),n+=l[a]+l[c],r-=2}return 1===r&&(a=e.readBits(6),o.push(l[a].charCodeAt(0)),n+=l[a]),{bytes:o,text:n}}function d(e,t){for(var o=[],n=\"\",i=[8,16,16][t],r=e.readBits(i),s=0;s<r;s++){var a=e.readBits(8);o.push(a)}try{n+=decodeURIComponent(o.map((function(e){return\"%\"+(\"0\"+e.toString(16)).substr(-2)})).join(\"\"))}catch(e){}return{bytes:o,text:n}}function u(e,t){for(var o=[],n=\"\",i=[8,10,12][t],r=e.readBits(i),a=0;a<r;a++){var l=e.readBits(13),c=Math.floor(l/192)<<8|l%192;c+=c<7936?33088:49472,o.push(c>>8,255&c),n+=String.fromCharCode(s.shiftJISTable[c])}return{bytes:o,text:n}}t.decode=function(e,t){for(var o,s,l,h,p=new r.BitStream(e),f=t<=9?0:t<=26?1:2,m={text:\"\",bytes:[],chunks:[],version:t};p.available()>=4;){var g=p.readBits(4);if(g===i.Terminator)return m;if(g===i.ECI)0===p.readBits(1)?m.chunks.push({type:n.ECI,assignmentNumber:p.readBits(7)}):0===p.readBits(1)?m.chunks.push({type:n.ECI,assignmentNumber:p.readBits(14)}):0===p.readBits(1)?m.chunks.push({type:n.ECI,assignmentNumber:p.readBits(21)}):m.chunks.push({type:n.ECI,assignmentNumber:-1});else if(g===i.Numeric){var v=a(p,f);m.text+=v.text,(o=m.bytes).push.apply(o,v.bytes),m.chunks.push({type:n.Numeric,text:v.text})}else if(g===i.Alphanumeric){var k=c(p,f);m.text+=k.text,(s=m.bytes).push.apply(s,k.bytes),m.chunks.push({type:n.Alphanumeric,text:k.text})}else if(g===i.Byte){var y=d(p,f);m.text+=y.text,(l=m.bytes).push.apply(l,y.bytes),m.chunks.push({type:n.Byte,bytes:y.bytes,text:y.text})}else if(g===i.Kanji){var w=u(p,f);m.text+=w.text,(h=m.bytes).push.apply(h,w.bytes),m.chunks.push({type:n.Kanji,bytes:w.bytes,text:w.text})}}if(0===p.available()||0===p.readBits(p.available()))return m}},function(e,t,o){Object.defineProperty(t,\"__esModule\",{value:!0});var n=function(){function e(e){this.byteOffset=0,this.bitOffset=0,this.bytes=e}return e.prototype.readBits=function(e){if(e<1||e>32||e>this.available())throw new Error(\"Cannot read \"+e.toString()+\" bits\");var t=0;if(this.bitOffset>0){var o=8-this.bitOffset,n=e<o?e:o,i=255>>8-n<<(r=o-n);t=(this.bytes[this.byteOffset]&i)>>r,e-=n,this.bitOffset+=n,8===this.bitOffset&&(this.bitOffset=0,this.byteOffset++)}if(e>0){for(;e>=8;)t=t<<8|255&this.bytes[this.byteOffset],this.byteOffset++,e-=8;var r;if(e>0)i=255>>(r=8-e)<<r,t=t<<e|(this.bytes[this.byteOffset]&i)>>r,this.bitOffset+=e}return t},e.prototype.available=function(){return 8*(this.bytes.length-this.byteOffset)-this.bitOffset},e}();t.BitStream=n},function(e,t,o){Object.defineProperty(t,\"__esModule\",{value:!0}),t.shiftJISTable={32:32,33:33,34:34,35:35,36:36,37:37,38:38,39:39,40:40,41:41,42:42,43:43,44:44,45:45,46:46,47:47,48:48,49:49,50:50,51:51,52:52,53:53,54:54,55:55,56:56,57:57,58:58,59:59,60:60,61:61,62:62,63:63,64:64,65:65,66:66,67:67,68:68,69:69,70:70,71:71,72:72,73:73,74:74,75:75,76:76,77:77,78:78,79:79,80:80,81:81,82:82,83:83,84:84,85:85,86:86,87:87,88:88,89:89,90:90,91:91,92:165,93:93,94:94,95:95,96:96,97:97,98:98,99:99,100:100,101:101,102:102,103:103,104:104,105:105,106:106,107:107,108:108,109:109,110:110,111:111,112:112,113:113,114:114,115:115,116:116,117:117,118:118,119:119,120:120,121:121,122:122,123:123,124:124,125:125,126:8254,33088:12288,33089:12289,33090:12290,33091:65292,33092:65294,33093:12539,33094:65306,33095:65307,33096:65311,33097:65281,33098:12443,33099:12444,33100:180,33101:65344,33102:168,33103:65342,33104:65507,33105:65343,33106:12541,33107:12542,33108:12445,33109:12446,33110:12291,33111:20189,33112:12293,33113:12294,33114:12295,33115:12540,33116:8213,33117:8208,33118:65295,33119:92,33120:12316,33121:8214,33122:65372,33123:8230,33124:8229,33125:8216,33126:8217,33127:8220,33128:8221,33129:65288,33130:65289,33131:12308,33132:12309,33133:65339,33134:65341,33135:65371,33136:65373,33137:12296,33138:12297,33139:12298,33140:12299,33141:12300,33142:12301,33143:12302,33144:12303,33145:12304,33146:12305,33147:65291,33148:8722,33149:177,33150:215,33152:247,33153:65309,33154:8800,33155:65308,33156:65310,33157:8806,33158:8807,33159:8734,33160:8756,33161:9794,33162:9792,33163:176,33164:8242,33165:8243,33166:8451,33167:65509,33168:65284,33169:162,33170:163,33171:65285,33172:65283,33173:65286,33174:65290,33175:65312,33176:167,33177:9734,33178:9733,33179:9675,33180:9679,33181:9678,33182:9671,33183:9670,33184:9633,33185:9632,33186:9651,33187:9650,33188:9661,33189:9660,33190:8251,33191:12306,33192:8594,33193:8592,33194:8593,33195:8595,33196:12307,33208:8712,33209:8715,33210:8838,33211:8839,33212:8834,33213:8835,33214:8746,33215:8745,33224:8743,33225:8744,33226:172,33227:8658,33228:8660,33229:8704,33230:8707,33242:8736,33243:8869,33244:8978,33245:8706,33246:8711,33247:8801,33248:8786,33249:8810,33250:8811,33251:8730,33252:8765,33253:8733,33254:8757,33255:8747,33256:8748,33264:8491,33265:8240,33266:9839,33267:9837,33268:9834,33269:8224,33270:8225,33271:182,33276:9711,33359:65296,33360:65297,33361:65298,33362:65299,33363:65300,33364:65301,33365:65302,33366:65303,33367:65304,33368:65305,33376:65313,33377:65314,33378:65315,33379:65316,33380:65317,33381:65318,33382:65319,33383:65320,33384:65321,33385:65322,33386:65323,33387:65324,33388:65325,33389:65326,33390:65327,33391:65328,33392:65329,33393:65330,33394:65331,33395:65332,33396:65333,33397:65334,33398:65335,33399:65336,33400:65337,33401:65338,33409:65345,33410:65346,33411:65347,33412:65348,33413:65349,33414:65350,33415:65351,33416:65352,33417:65353,33418:65354,33419:65355,33420:65356,33421:65357,33422:65358,33423:65359,33424:65360,33425:65361,33426:65362,33427:65363,33428:65364,33429:65365,33430:65366,33431:65367,33432:65368,33433:65369,33434:65370,33439:12353,33440:12354,33441:12355,33442:12356,33443:12357,33444:12358,33445:12359,33446:12360,33447:12361,33448:12362,33449:12363,33450:12364,33451:12365,33452:12366,33453:12367,33454:12368,33455:12369,33456:12370,33457:12371,33458:12372,33459:12373,33460:12374,33461:12375,33462:12376,33463:12377,33464:12378,33465:12379,33466:12380,33467:12381,33468:12382,33469:12383,33470:12384,33471:12385,33472:12386,33473:12387,33474:12388,33475:12389,33476:12390,33477:12391,33478:12392,33479:12393,33480:12394,33481:12395,33482:12396,33483:12397,33484:12398,33485:12399,33486:12400,33487:12401,33488:12402,33489:12403,33490:12404,33491:12405,33492:12406,33493:12407,33494:12408,33495:12409,33496:12410,33497:12411,33498:12412,33499:12413,33500:12414,33501:12415,33502:12416,33503:12417,33504:12418,33505:12419,33506:12420,33507:12421,33508:12422,33509:12423,33510:12424,33511:12425,33512:12426,33513:12427,33514:12428,33515:12429,33516:12430,33517:12431,33518:12432,33519:12433,33520:12434,33521:12435,33600:12449,33601:12450,33602:12451,33603:12452,33604:12453,33605:12454,33606:12455,33607:12456,33608:12457,33609:12458,33610:12459,33611:12460,33612:12461,33613:12462,33614:12463,33615:12464,33616:12465,33617:12466,33618:12467,33619:12468,33620:12469,33621:12470,33622:12471,33623:12472,33624:12473,33625:12474,33626:12475,33627:12476,33628:12477,33629:12478,33630:12479,33631:12480,33632:12481,33633:12482,33634:12483,33635:12484,33636:12485,33637:12486,33638:12487,33639:12488,33640:12489,33641:12490,33642:12491,33643:12492,33644:12493,33645:12494,33646:12495,33647:12496,33648:12497,33649:12498,33650:12499,33651:12500,33652:12501,33653:12502,33654:12503,33655:12504,33656:12505,33657:12506,33658:12507,33659:12508,33660:12509,33661:12510,33662:12511,33664:12512,33665:12513,33666:12514,33667:12515,33668:12516,33669:12517,33670:12518,33671:12519,33672:12520,33673:12521,33674:12522,33675:12523,33676:12524,33677:12525,33678:12526,33679:12527,33680:12528,33681:12529,33682:12530,33683:12531,33684:12532,33685:12533,33686:12534,33695:913,33696:914,33697:915,33698:916,33699:917,33700:918,33701:919,33702:920,33703:921,33704:922,33705:923,33706:924,33707:925,33708:926,33709:927,33710:928,33711:929,33712:931,33713:932,33714:933,33715:934,33716:935,33717:936,33718:937,33727:945,33728:946,33729:947,33730:948,33731:949,33732:950,33733:951,33734:952,33735:953,33736:954,33737:955,33738:956,33739:957,33740:958,33741:959,33742:960,33743:961,33744:963,33745:964,33746:965,33747:966,33748:967,33749:968,33750:969,33856:1040,33857:1041,33858:1042,33859:1043,33860:1044,33861:1045,33862:1025,33863:1046,33864:1047,33865:1048,33866:1049,33867:1050,33868:1051,33869:1052,33870:1053,33871:1054,33872:1055,33873:1056,33874:1057,33875:1058,33876:1059,33877:1060,33878:1061,33879:1062,33880:1063,33881:1064,33882:1065,33883:1066,33884:1067,33885:1068,33886:1069,33887:1070,33888:1071,33904:1072,33905:1073,33906:1074,33907:1075,33908:1076,33909:1077,33910:1105,33911:1078,33912:1079,33913:1080,33914:1081,33915:1082,33916:1083,33917:1084,33918:1085,33920:1086,33921:1087,33922:1088,33923:1089,33924:1090,33925:1091,33926:1092,33927:1093,33928:1094,33929:1095,33930:1096,33931:1097,33932:1098,33933:1099,33934:1100,33935:1101,33936:1102,33937:1103,33951:9472,33952:9474,33953:9484,33954:9488,33955:9496,33956:9492,33957:9500,33958:9516,33959:9508,33960:9524,33961:9532,33962:9473,33963:9475,33964:9487,33965:9491,33966:9499,33967:9495,33968:9507,33969:9523,33970:9515,33971:9531,33972:9547,33973:9504,33974:9519,33975:9512,33976:9527,33977:9535,33978:9501,33979:9520,33980:9509,33981:9528,33982:9538,34975:20124,34976:21782,34977:23043,34978:38463,34979:21696,34980:24859,34981:25384,34982:23030,34983:36898,34984:33909,34985:33564,34986:31312,34987:24746,34988:25569,34989:28197,34990:26093,34991:33894,34992:33446,34993:39925,34994:26771,34995:22311,34996:26017,34997:25201,34998:23451,34999:22992,35e3:34427,35001:39156,35002:32098,35003:32190,35004:39822,35005:25110,35006:31903,35007:34999,35008:23433,35009:24245,35010:25353,35011:26263,35012:26696,35013:38343,35014:38797,35015:26447,35016:20197,35017:20234,35018:20301,35019:20381,35020:20553,35021:22258,35022:22839,35023:22996,35024:23041,35025:23561,35026:24799,35027:24847,35028:24944,35029:26131,35030:26885,35031:28858,35032:30031,35033:30064,35034:31227,35035:32173,35036:32239,35037:32963,35038:33806,35039:34915,35040:35586,35041:36949,35042:36986,35043:21307,35044:20117,35045:20133,35046:22495,35047:32946,35048:37057,35049:30959,35050:19968,35051:22769,35052:28322,35053:36920,35054:31282,35055:33576,35056:33419,35057:39983,35058:20801,35059:21360,35060:21693,35061:21729,35062:22240,35063:23035,35064:24341,35065:39154,35066:28139,35067:32996,35068:34093,35136:38498,35137:38512,35138:38560,35139:38907,35140:21515,35141:21491,35142:23431,35143:28879,35144:32701,35145:36802,35146:38632,35147:21359,35148:40284,35149:31418,35150:19985,35151:30867,35152:33276,35153:28198,35154:22040,35155:21764,35156:27421,35157:34074,35158:39995,35159:23013,35160:21417,35161:28006,35162:29916,35163:38287,35164:22082,35165:20113,35166:36939,35167:38642,35168:33615,35169:39180,35170:21473,35171:21942,35172:23344,35173:24433,35174:26144,35175:26355,35176:26628,35177:27704,35178:27891,35179:27945,35180:29787,35181:30408,35182:31310,35183:38964,35184:33521,35185:34907,35186:35424,35187:37613,35188:28082,35189:30123,35190:30410,35191:39365,35192:24742,35193:35585,35194:36234,35195:38322,35196:27022,35197:21421,35198:20870,35200:22290,35201:22576,35202:22852,35203:23476,35204:24310,35205:24616,35206:25513,35207:25588,35208:27839,35209:28436,35210:28814,35211:28948,35212:29017,35213:29141,35214:29503,35215:32257,35216:33398,35217:33489,35218:34199,35219:36960,35220:37467,35221:40219,35222:22633,35223:26044,35224:27738,35225:29989,35226:20985,35227:22830,35228:22885,35229:24448,35230:24540,35231:25276,35232:26106,35233:27178,35234:27431,35235:27572,35236:29579,35237:32705,35238:35158,35239:40236,35240:40206,35241:40644,35242:23713,35243:27798,35244:33659,35245:20740,35246:23627,35247:25014,35248:33222,35249:26742,35250:29281,35251:20057,35252:20474,35253:21368,35254:24681,35255:28201,35256:31311,35257:38899,35258:19979,35259:21270,35260:20206,35261:20309,35262:20285,35263:20385,35264:20339,35265:21152,35266:21487,35267:22025,35268:22799,35269:23233,35270:23478,35271:23521,35272:31185,35273:26247,35274:26524,35275:26550,35276:27468,35277:27827,35278:28779,35279:29634,35280:31117,35281:31166,35282:31292,35283:31623,35284:33457,35285:33499,35286:33540,35287:33655,35288:33775,35289:33747,35290:34662,35291:35506,35292:22057,35293:36008,35294:36838,35295:36942,35296:38686,35297:34442,35298:20420,35299:23784,35300:25105,35301:29273,35302:30011,35303:33253,35304:33469,35305:34558,35306:36032,35307:38597,35308:39187,35309:39381,35310:20171,35311:20250,35312:35299,35313:22238,35314:22602,35315:22730,35316:24315,35317:24555,35318:24618,35319:24724,35320:24674,35321:25040,35322:25106,35323:25296,35324:25913,35392:39745,35393:26214,35394:26800,35395:28023,35396:28784,35397:30028,35398:30342,35399:32117,35400:33445,35401:34809,35402:38283,35403:38542,35404:35997,35405:20977,35406:21182,35407:22806,35408:21683,35409:23475,35410:23830,35411:24936,35412:27010,35413:28079,35414:30861,35415:33995,35416:34903,35417:35442,35418:37799,35419:39608,35420:28012,35421:39336,35422:34521,35423:22435,35424:26623,35425:34510,35426:37390,35427:21123,35428:22151,35429:21508,35430:24275,35431:25313,35432:25785,35433:26684,35434:26680,35435:27579,35436:29554,35437:30906,35438:31339,35439:35226,35440:35282,35441:36203,35442:36611,35443:37101,35444:38307,35445:38548,35446:38761,35447:23398,35448:23731,35449:27005,35450:38989,35451:38990,35452:25499,35453:31520,35454:27179,35456:27263,35457:26806,35458:39949,35459:28511,35460:21106,35461:21917,35462:24688,35463:25324,35464:27963,35465:28167,35466:28369,35467:33883,35468:35088,35469:36676,35470:19988,35471:39993,35472:21494,35473:26907,35474:27194,35475:38788,35476:26666,35477:20828,35478:31427,35479:33970,35480:37340,35481:37772,35482:22107,35483:40232,35484:26658,35485:33541,35486:33841,35487:31909,35488:21e3,35489:33477,35490:29926,35491:20094,35492:20355,35493:20896,35494:23506,35495:21002,35496:21208,35497:21223,35498:24059,35499:21914,35500:22570,35501:23014,35502:23436,35503:23448,35504:23515,35505:24178,35506:24185,35507:24739,35508:24863,35509:24931,35510:25022,35511:25563,35512:25954,35513:26577,35514:26707,35515:26874,35516:27454,35517:27475,35518:27735,35519:28450,35520:28567,35521:28485,35522:29872,35523:29976,35524:30435,35525:30475,35526:31487,35527:31649,35528:31777,35529:32233,35530:32566,35531:32752,35532:32925,35533:33382,35534:33694,35535:35251,35536:35532,35537:36011,35538:36996,35539:37969,35540:38291,35541:38289,35542:38306,35543:38501,35544:38867,35545:39208,35546:33304,35547:20024,35548:21547,35549:23736,35550:24012,35551:29609,35552:30284,35553:30524,35554:23721,35555:32747,35556:36107,35557:38593,35558:38929,35559:38996,35560:39e3,35561:20225,35562:20238,35563:21361,35564:21916,35565:22120,35566:22522,35567:22855,35568:23305,35569:23492,35570:23696,35571:24076,35572:24190,35573:24524,35574:25582,35575:26426,35576:26071,35577:26082,35578:26399,35579:26827,35580:26820,35648:27231,35649:24112,35650:27589,35651:27671,35652:27773,35653:30079,35654:31048,35655:23395,35656:31232,35657:32e3,35658:24509,35659:35215,35660:35352,35661:36020,35662:36215,35663:36556,35664:36637,35665:39138,35666:39438,35667:39740,35668:20096,35669:20605,35670:20736,35671:22931,35672:23452,35673:25135,35674:25216,35675:25836,35676:27450,35677:29344,35678:30097,35679:31047,35680:32681,35681:34811,35682:35516,35683:35696,35684:25516,35685:33738,35686:38816,35687:21513,35688:21507,35689:21931,35690:26708,35691:27224,35692:35440,35693:30759,35694:26485,35695:40653,35696:21364,35697:23458,35698:33050,35699:34384,35700:36870,35701:19992,35702:20037,35703:20167,35704:20241,35705:21450,35706:21560,35707:23470,35708:24339,35709:24613,35710:25937,35712:26429,35713:27714,35714:27762,35715:27875,35716:28792,35717:29699,35718:31350,35719:31406,35720:31496,35721:32026,35722:31998,35723:32102,35724:26087,35725:29275,35726:21435,35727:23621,35728:24040,35729:25298,35730:25312,35731:25369,35732:28192,35733:34394,35734:35377,35735:36317,35736:37624,35737:28417,35738:31142,35739:39770,35740:20136,35741:20139,35742:20140,35743:20379,35744:20384,35745:20689,35746:20807,35747:31478,35748:20849,35749:20982,35750:21332,35751:21281,35752:21375,35753:21483,35754:21932,35755:22659,35756:23777,35757:24375,35758:24394,35759:24623,35760:24656,35761:24685,35762:25375,35763:25945,35764:27211,35765:27841,35766:29378,35767:29421,35768:30703,35769:33016,35770:33029,35771:33288,35772:34126,35773:37111,35774:37857,35775:38911,35776:39255,35777:39514,35778:20208,35779:20957,35780:23597,35781:26241,35782:26989,35783:23616,35784:26354,35785:26997,35786:29577,35787:26704,35788:31873,35789:20677,35790:21220,35791:22343,35792:24062,35793:37670,35794:26020,35795:27427,35796:27453,35797:29748,35798:31105,35799:31165,35800:31563,35801:32202,35802:33465,35803:33740,35804:34943,35805:35167,35806:35641,35807:36817,35808:37329,35809:21535,35810:37504,35811:20061,35812:20534,35813:21477,35814:21306,35815:29399,35816:29590,35817:30697,35818:33510,35819:36527,35820:39366,35821:39368,35822:39378,35823:20855,35824:24858,35825:34398,35826:21936,35827:31354,35828:20598,35829:23507,35830:36935,35831:38533,35832:20018,35833:27355,35834:37351,35835:23633,35836:23624,35904:25496,35905:31391,35906:27795,35907:38772,35908:36705,35909:31402,35910:29066,35911:38536,35912:31874,35913:26647,35914:32368,35915:26705,35916:37740,35917:21234,35918:21531,35919:34219,35920:35347,35921:32676,35922:36557,35923:37089,35924:21350,35925:34952,35926:31041,35927:20418,35928:20670,35929:21009,35930:20804,35931:21843,35932:22317,35933:29674,35934:22411,35935:22865,35936:24418,35937:24452,35938:24693,35939:24950,35940:24935,35941:25001,35942:25522,35943:25658,35944:25964,35945:26223,35946:26690,35947:28179,35948:30054,35949:31293,35950:31995,35951:32076,35952:32153,35953:32331,35954:32619,35955:33550,35956:33610,35957:34509,35958:35336,35959:35427,35960:35686,35961:36605,35962:38938,35963:40335,35964:33464,35965:36814,35966:39912,35968:21127,35969:25119,35970:25731,35971:28608,35972:38553,35973:26689,35974:20625,35975:27424,35976:27770,35977:28500,35978:31348,35979:32080,35980:34880,35981:35363,35982:26376,35983:20214,35984:20537,35985:20518,35986:20581,35987:20860,35988:21048,35989:21091,35990:21927,35991:22287,35992:22533,35993:23244,35994:24314,35995:25010,35996:25080,35997:25331,35998:25458,35999:26908,36e3:27177,36001:29309,36002:29356,36003:29486,36004:30740,36005:30831,36006:32121,36007:30476,36008:32937,36009:35211,36010:35609,36011:36066,36012:36562,36013:36963,36014:37749,36015:38522,36016:38997,36017:39443,36018:40568,36019:20803,36020:21407,36021:21427,36022:24187,36023:24358,36024:28187,36025:28304,36026:29572,36027:29694,36028:32067,36029:33335,36030:35328,36031:35578,36032:38480,36033:20046,36034:20491,36035:21476,36036:21628,36037:22266,36038:22993,36039:23396,36040:24049,36041:24235,36042:24359,36043:25144,36044:25925,36045:26543,36046:28246,36047:29392,36048:31946,36049:34996,36050:32929,36051:32993,36052:33776,36053:34382,36054:35463,36055:36328,36056:37431,36057:38599,36058:39015,36059:40723,36060:20116,36061:20114,36062:20237,36063:21320,36064:21577,36065:21566,36066:23087,36067:24460,36068:24481,36069:24735,36070:26791,36071:27278,36072:29786,36073:30849,36074:35486,36075:35492,36076:35703,36077:37264,36078:20062,36079:39881,36080:20132,36081:20348,36082:20399,36083:20505,36084:20502,36085:20809,36086:20844,36087:21151,36088:21177,36089:21246,36090:21402,36091:21475,36092:21521,36160:21518,36161:21897,36162:22353,36163:22434,36164:22909,36165:23380,36166:23389,36167:23439,36168:24037,36169:24039,36170:24055,36171:24184,36172:24195,36173:24218,36174:24247,36175:24344,36176:24658,36177:24908,36178:25239,36179:25304,36180:25511,36181:25915,36182:26114,36183:26179,36184:26356,36185:26477,36186:26657,36187:26775,36188:27083,36189:27743,36190:27946,36191:28009,36192:28207,36193:28317,36194:30002,36195:30343,36196:30828,36197:31295,36198:31968,36199:32005,36200:32024,36201:32094,36202:32177,36203:32789,36204:32771,36205:32943,36206:32945,36207:33108,36208:33167,36209:33322,36210:33618,36211:34892,36212:34913,36213:35611,36214:36002,36215:36092,36216:37066,36217:37237,36218:37489,36219:30783,36220:37628,36221:38308,36222:38477,36224:38917,36225:39321,36226:39640,36227:40251,36228:21083,36229:21163,36230:21495,36231:21512,36232:22741,36233:25335,36234:28640,36235:35946,36236:36703,36237:40633,36238:20811,36239:21051,36240:21578,36241:22269,36242:31296,36243:37239,36244:40288,36245:40658,36246:29508,36247:28425,36248:33136,36249:29969,36250:24573,36251:24794,36252:39592,36253:29403,36254:36796,36255:27492,36256:38915,36257:20170,36258:22256,36259:22372,36260:22718,36261:23130,36262:24680,36263:25031,36264:26127,36265:26118,36266:26681,36267:26801,36268:28151,36269:30165,36270:32058,36271:33390,36272:39746,36273:20123,36274:20304,36275:21449,36276:21766,36277:23919,36278:24038,36279:24046,36280:26619,36281:27801,36282:29811,36283:30722,36284:35408,36285:37782,36286:35039,36287:22352,36288:24231,36289:25387,36290:20661,36291:20652,36292:20877,36293:26368,36294:21705,36295:22622,36296:22971,36297:23472,36298:24425,36299:25165,36300:25505,36301:26685,36302:27507,36303:28168,36304:28797,36305:37319,36306:29312,36307:30741,36308:30758,36309:31085,36310:25998,36311:32048,36312:33756,36313:35009,36314:36617,36315:38555,36316:21092,36317:22312,36318:26448,36319:32618,36320:36001,36321:20916,36322:22338,36323:38442,36324:22586,36325:27018,36326:32948,36327:21682,36328:23822,36329:22524,36330:30869,36331:40442,36332:20316,36333:21066,36334:21643,36335:25662,36336:26152,36337:26388,36338:26613,36339:31364,36340:31574,36341:32034,36342:37679,36343:26716,36344:39853,36345:31545,36346:21273,36347:20874,36348:21047,36416:23519,36417:25334,36418:25774,36419:25830,36420:26413,36421:27578,36422:34217,36423:38609,36424:30352,36425:39894,36426:25420,36427:37638,36428:39851,36429:30399,36430:26194,36431:19977,36432:20632,36433:21442,36434:23665,36435:24808,36436:25746,36437:25955,36438:26719,36439:29158,36440:29642,36441:29987,36442:31639,36443:32386,36444:34453,36445:35715,36446:36059,36447:37240,36448:39184,36449:26028,36450:26283,36451:27531,36452:20181,36453:20180,36454:20282,36455:20351,36456:21050,36457:21496,36458:21490,36459:21987,36460:22235,36461:22763,36462:22987,36463:22985,36464:23039,36465:23376,36466:23629,36467:24066,36468:24107,36469:24535,36470:24605,36471:25351,36472:25903,36473:23388,36474:26031,36475:26045,36476:26088,36477:26525,36478:27490,36480:27515,36481:27663,36482:29509,36483:31049,36484:31169,36485:31992,36486:32025,36487:32043,36488:32930,36489:33026,36490:33267,36491:35222,36492:35422,36493:35433,36494:35430,36495:35468,36496:35566,36497:36039,36498:36060,36499:38604,36500:39164,36501:27503,36502:20107,36503:20284,36504:20365,36505:20816,36506:23383,36507:23546,36508:24904,36509:25345,36510:26178,36511:27425,36512:28363,36513:27835,36514:29246,36515:29885,36516:30164,36517:30913,36518:31034,36519:32780,36520:32819,36521:33258,36522:33940,36523:36766,36524:27728,36525:40575,36526:24335,36527:35672,36528:40235,36529:31482,36530:36600,36531:23437,36532:38635,36533:19971,36534:21489,36535:22519,36536:22833,36537:23241,36538:23460,36539:24713,36540:28287,36541:28422,36542:30142,36543:36074,36544:23455,36545:34048,36546:31712,36547:20594,36548:26612,36549:33437,36550:23649,36551:34122,36552:32286,36553:33294,36554:20889,36555:23556,36556:25448,36557:36198,36558:26012,36559:29038,36560:31038,36561:32023,36562:32773,36563:35613,36564:36554,36565:36974,36566:34503,36567:37034,36568:20511,36569:21242,36570:23610,36571:26451,36572:28796,36573:29237,36574:37196,36575:37320,36576:37675,36577:33509,36578:23490,36579:24369,36580:24825,36581:20027,36582:21462,36583:23432,36584:25163,36585:26417,36586:27530,36587:29417,36588:29664,36589:31278,36590:33131,36591:36259,36592:37202,36593:39318,36594:20754,36595:21463,36596:21610,36597:23551,36598:25480,36599:27193,36600:32172,36601:38656,36602:22234,36603:21454,36604:21608,36672:23447,36673:23601,36674:24030,36675:20462,36676:24833,36677:25342,36678:27954,36679:31168,36680:31179,36681:32066,36682:32333,36683:32722,36684:33261,36685:33311,36686:33936,36687:34886,36688:35186,36689:35728,36690:36468,36691:36655,36692:36913,36693:37195,36694:37228,36695:38598,36696:37276,36697:20160,36698:20303,36699:20805,36700:21313,36701:24467,36702:25102,36703:26580,36704:27713,36705:28171,36706:29539,36707:32294,36708:37325,36709:37507,36710:21460,36711:22809,36712:23487,36713:28113,36714:31069,36715:32302,36716:31899,36717:22654,36718:29087,36719:20986,36720:34899,36721:36848,36722:20426,36723:23803,36724:26149,36725:30636,36726:31459,36727:33308,36728:39423,36729:20934,36730:24490,36731:26092,36732:26991,36733:27529,36734:28147,36736:28310,36737:28516,36738:30462,36739:32020,36740:24033,36741:36981,36742:37255,36743:38918,36744:20966,36745:21021,36746:25152,36747:26257,36748:26329,36749:28186,36750:24246,36751:32210,36752:32626,36753:26360,36754:34223,36755:34295,36756:35576,36757:21161,36758:21465,36759:22899,36760:24207,36761:24464,36762:24661,36763:37604,36764:38500,36765:20663,36766:20767,36767:21213,36768:21280,36769:21319,36770:21484,36771:21736,36772:21830,36773:21809,36774:22039,36775:22888,36776:22974,36777:23100,36778:23477,36779:23558,36780:23567,36781:23569,36782:23578,36783:24196,36784:24202,36785:24288,36786:24432,36787:25215,36788:25220,36789:25307,36790:25484,36791:25463,36792:26119,36793:26124,36794:26157,36795:26230,36796:26494,36797:26786,36798:27167,36799:27189,36800:27836,36801:28040,36802:28169,36803:28248,36804:28988,36805:28966,36806:29031,36807:30151,36808:30465,36809:30813,36810:30977,36811:31077,36812:31216,36813:31456,36814:31505,36815:31911,36816:32057,36817:32918,36818:33750,36819:33931,36820:34121,36821:34909,36822:35059,36823:35359,36824:35388,36825:35412,36826:35443,36827:35937,36828:36062,36829:37284,36830:37478,36831:37758,36832:37912,36833:38556,36834:38808,36835:19978,36836:19976,36837:19998,36838:20055,36839:20887,36840:21104,36841:22478,36842:22580,36843:22732,36844:23330,36845:24120,36846:24773,36847:25854,36848:26465,36849:26454,36850:27972,36851:29366,36852:30067,36853:31331,36854:33976,36855:35698,36856:37304,36857:37664,36858:22065,36859:22516,36860:39166,36928:25325,36929:26893,36930:27542,36931:29165,36932:32340,36933:32887,36934:33394,36935:35302,36936:39135,36937:34645,36938:36785,36939:23611,36940:20280,36941:20449,36942:20405,36943:21767,36944:23072,36945:23517,36946:23529,36947:24515,36948:24910,36949:25391,36950:26032,36951:26187,36952:26862,36953:27035,36954:28024,36955:28145,36956:30003,36957:30137,36958:30495,36959:31070,36960:31206,36961:32051,36962:33251,36963:33455,36964:34218,36965:35242,36966:35386,36967:36523,36968:36763,36969:36914,36970:37341,36971:38663,36972:20154,36973:20161,36974:20995,36975:22645,36976:22764,36977:23563,36978:29978,36979:23613,36980:33102,36981:35338,36982:36805,36983:38499,36984:38765,36985:31525,36986:35535,36987:38920,36988:37218,36989:22259,36990:21416,36992:36887,36993:21561,36994:22402,36995:24101,36996:25512,36997:27700,36998:28810,36999:30561,37e3:31883,37001:32736,37002:34928,37003:36930,37004:37204,37005:37648,37006:37656,37007:38543,37008:29790,37009:39620,37010:23815,37011:23913,37012:25968,37013:26530,37014:36264,37015:38619,37016:25454,37017:26441,37018:26905,37019:33733,37020:38935,37021:38592,37022:35070,37023:28548,37024:25722,37025:23544,37026:19990,37027:28716,37028:30045,37029:26159,37030:20932,37031:21046,37032:21218,37033:22995,37034:24449,37035:24615,37036:25104,37037:25919,37038:25972,37039:26143,37040:26228,37041:26866,37042:26646,37043:27491,37044:28165,37045:29298,37046:29983,37047:30427,37048:31934,37049:32854,37050:22768,37051:35069,37052:35199,37053:35488,37054:35475,37055:35531,37056:36893,37057:37266,37058:38738,37059:38745,37060:25993,37061:31246,37062:33030,37063:38587,37064:24109,37065:24796,37066:25114,37067:26021,37068:26132,37069:26512,37070:30707,37071:31309,37072:31821,37073:32318,37074:33034,37075:36012,37076:36196,37077:36321,37078:36447,37079:30889,37080:20999,37081:25305,37082:25509,37083:25666,37084:25240,37085:35373,37086:31363,37087:31680,37088:35500,37089:38634,37090:32118,37091:33292,37092:34633,37093:20185,37094:20808,37095:21315,37096:21344,37097:23459,37098:23554,37099:23574,37100:24029,37101:25126,37102:25159,37103:25776,37104:26643,37105:26676,37106:27849,37107:27973,37108:27927,37109:26579,37110:28508,37111:29006,37112:29053,37113:26059,37114:31359,37115:31661,37116:32218,37184:32330,37185:32680,37186:33146,37187:33307,37188:33337,37189:34214,37190:35438,37191:36046,37192:36341,37193:36984,37194:36983,37195:37549,37196:37521,37197:38275,37198:39854,37199:21069,37200:21892,37201:28472,37202:28982,37203:20840,37204:31109,37205:32341,37206:33203,37207:31950,37208:22092,37209:22609,37210:23720,37211:25514,37212:26366,37213:26365,37214:26970,37215:29401,37216:30095,37217:30094,37218:30990,37219:31062,37220:31199,37221:31895,37222:32032,37223:32068,37224:34311,37225:35380,37226:38459,37227:36961,37228:40736,37229:20711,37230:21109,37231:21452,37232:21474,37233:20489,37234:21930,37235:22766,37236:22863,37237:29245,37238:23435,37239:23652,37240:21277,37241:24803,37242:24819,37243:25436,37244:25475,37245:25407,37246:25531,37248:25805,37249:26089,37250:26361,37251:24035,37252:27085,37253:27133,37254:28437,37255:29157,37256:20105,37257:30185,37258:30456,37259:31379,37260:31967,37261:32207,37262:32156,37263:32865,37264:33609,37265:33624,37266:33900,37267:33980,37268:34299,37269:35013,37270:36208,37271:36865,37272:36973,37273:37783,37274:38684,37275:39442,37276:20687,37277:22679,37278:24974,37279:33235,37280:34101,37281:36104,37282:36896,37283:20419,37284:20596,37285:21063,37286:21363,37287:24687,37288:25417,37289:26463,37290:28204,37291:36275,37292:36895,37293:20439,37294:23646,37295:36042,37296:26063,37297:32154,37298:21330,37299:34966,37300:20854,37301:25539,37302:23384,37303:23403,37304:23562,37305:25613,37306:26449,37307:36956,37308:20182,37309:22810,37310:22826,37311:27760,37312:35409,37313:21822,37314:22549,37315:22949,37316:24816,37317:25171,37318:26561,37319:33333,37320:26965,37321:38464,37322:39364,37323:39464,37324:20307,37325:22534,37326:23550,37327:32784,37328:23729,37329:24111,37330:24453,37331:24608,37332:24907,37333:25140,37334:26367,37335:27888,37336:28382,37337:32974,37338:33151,37339:33492,37340:34955,37341:36024,37342:36864,37343:36910,37344:38538,37345:40667,37346:39899,37347:20195,37348:21488,37349:22823,37350:31532,37351:37261,37352:38988,37353:40441,37354:28381,37355:28711,37356:21331,37357:21828,37358:23429,37359:25176,37360:25246,37361:25299,37362:27810,37363:28655,37364:29730,37365:35351,37366:37944,37367:28609,37368:35582,37369:33592,37370:20967,37371:34552,37372:21482,37440:21481,37441:20294,37442:36948,37443:36784,37444:22890,37445:33073,37446:24061,37447:31466,37448:36799,37449:26842,37450:35895,37451:29432,37452:40008,37453:27197,37454:35504,37455:20025,37456:21336,37457:22022,37458:22374,37459:25285,37460:25506,37461:26086,37462:27470,37463:28129,37464:28251,37465:28845,37466:30701,37467:31471,37468:31658,37469:32187,37470:32829,37471:32966,37472:34507,37473:35477,37474:37723,37475:22243,37476:22727,37477:24382,37478:26029,37479:26262,37480:27264,37481:27573,37482:30007,37483:35527,37484:20516,37485:30693,37486:22320,37487:24347,37488:24677,37489:26234,37490:27744,37491:30196,37492:31258,37493:32622,37494:33268,37495:34584,37496:36933,37497:39347,37498:31689,37499:30044,37500:31481,37501:31569,37502:33988,37504:36880,37505:31209,37506:31378,37507:33590,37508:23265,37509:30528,37510:20013,37511:20210,37512:23449,37513:24544,37514:25277,37515:26172,37516:26609,37517:27880,37518:34411,37519:34935,37520:35387,37521:37198,37522:37619,37523:39376,37524:27159,37525:28710,37526:29482,37527:33511,37528:33879,37529:36015,37530:19969,37531:20806,37532:20939,37533:21899,37534:23541,37535:24086,37536:24115,37537:24193,37538:24340,37539:24373,37540:24427,37541:24500,37542:25074,37543:25361,37544:26274,37545:26397,37546:28526,37547:29266,37548:30010,37549:30522,37550:32884,37551:33081,37552:33144,37553:34678,37554:35519,37555:35548,37556:36229,37557:36339,37558:37530,37559:38263,37560:38914,37561:40165,37562:21189,37563:25431,37564:30452,37565:26389,37566:27784,37567:29645,37568:36035,37569:37806,37570:38515,37571:27941,37572:22684,37573:26894,37574:27084,37575:36861,37576:37786,37577:30171,37578:36890,37579:22618,37580:26626,37581:25524,37582:27131,37583:20291,37584:28460,37585:26584,37586:36795,37587:34086,37588:32180,37589:37716,37590:26943,37591:28528,37592:22378,37593:22775,37594:23340,37595:32044,37596:29226,37597:21514,37598:37347,37599:40372,37600:20141,37601:20302,37602:20572,37603:20597,37604:21059,37605:35998,37606:21576,37607:22564,37608:23450,37609:24093,37610:24213,37611:24237,37612:24311,37613:24351,37614:24716,37615:25269,37616:25402,37617:25552,37618:26799,37619:27712,37620:30855,37621:31118,37622:31243,37623:32224,37624:33351,37625:35330,37626:35558,37627:36420,37628:36883,37696:37048,37697:37165,37698:37336,37699:40718,37700:27877,37701:25688,37702:25826,37703:25973,37704:28404,37705:30340,37706:31515,37707:36969,37708:37841,37709:28346,37710:21746,37711:24505,37712:25764,37713:36685,37714:36845,37715:37444,37716:20856,37717:22635,37718:22825,37719:23637,37720:24215,37721:28155,37722:32399,37723:29980,37724:36028,37725:36578,37726:39003,37727:28857,37728:20253,37729:27583,37730:28593,37731:3e4,37732:38651,37733:20814,37734:21520,37735:22581,37736:22615,37737:22956,37738:23648,37739:24466,37740:26007,37741:26460,37742:28193,37743:30331,37744:33759,37745:36077,37746:36884,37747:37117,37748:37709,37749:30757,37750:30778,37751:21162,37752:24230,37753:22303,37754:22900,37755:24594,37756:20498,37757:20826,37758:20908,37760:20941,37761:20992,37762:21776,37763:22612,37764:22616,37765:22871,37766:23445,37767:23798,37768:23947,37769:24764,37770:25237,37771:25645,37772:26481,37773:26691,37774:26812,37775:26847,37776:30423,37777:28120,37778:28271,37779:28059,37780:28783,37781:29128,37782:24403,37783:30168,37784:31095,37785:31561,37786:31572,37787:31570,37788:31958,37789:32113,37790:21040,37791:33891,37792:34153,37793:34276,37794:35342,37795:35588,37796:35910,37797:36367,37798:36867,37799:36879,37800:37913,37801:38518,37802:38957,37803:39472,37804:38360,37805:20685,37806:21205,37807:21516,37808:22530,37809:23566,37810:24999,37811:25758,37812:27934,37813:30643,37814:31461,37815:33012,37816:33796,37817:36947,37818:37509,37819:23776,37820:40199,37821:21311,37822:24471,37823:24499,37824:28060,37825:29305,37826:30563,37827:31167,37828:31716,37829:27602,37830:29420,37831:35501,37832:26627,37833:27233,37834:20984,37835:31361,37836:26932,37837:23626,37838:40182,37839:33515,37840:23493,37841:37193,37842:28702,37843:22136,37844:23663,37845:24775,37846:25958,37847:27788,37848:35930,37849:36929,37850:38931,37851:21585,37852:26311,37853:37389,37854:22856,37855:37027,37856:20869,37857:20045,37858:20970,37859:34201,37860:35598,37861:28760,37862:25466,37863:37707,37864:26978,37865:39348,37866:32260,37867:30071,37868:21335,37869:26976,37870:36575,37871:38627,37872:27741,37873:20108,37874:23612,37875:24336,37876:36841,37877:21250,37878:36049,37879:32905,37880:34425,37881:24319,37882:26085,37883:20083,37884:20837,37952:22914,37953:23615,37954:38894,37955:20219,37956:22922,37957:24525,37958:35469,37959:28641,37960:31152,37961:31074,37962:23527,37963:33905,37964:29483,37965:29105,37966:24180,37967:24565,37968:25467,37969:25754,37970:29123,37971:31896,37972:20035,37973:24316,37974:20043,37975:22492,37976:22178,37977:24745,37978:28611,37979:32013,37980:33021,37981:33075,37982:33215,37983:36786,37984:35223,37985:34468,37986:24052,37987:25226,37988:25773,37989:35207,37990:26487,37991:27874,37992:27966,37993:29750,37994:30772,37995:23110,37996:32629,37997:33453,37998:39340,37999:20467,38e3:24259,38001:25309,38002:25490,38003:25943,38004:26479,38005:30403,38006:29260,38007:32972,38008:32954,38009:36649,38010:37197,38011:20493,38012:22521,38013:23186,38014:26757,38016:26995,38017:29028,38018:29437,38019:36023,38020:22770,38021:36064,38022:38506,38023:36889,38024:34687,38025:31204,38026:30695,38027:33833,38028:20271,38029:21093,38030:21338,38031:25293,38032:26575,38033:27850,38034:30333,38035:31636,38036:31893,38037:33334,38038:34180,38039:36843,38040:26333,38041:28448,38042:29190,38043:32283,38044:33707,38045:39361,38046:40614,38047:20989,38048:31665,38049:30834,38050:31672,38051:32903,38052:31560,38053:27368,38054:24161,38055:32908,38056:30033,38057:30048,38058:20843,38059:37474,38060:28300,38061:30330,38062:37271,38063:39658,38064:20240,38065:32624,38066:25244,38067:31567,38068:38309,38069:40169,38070:22138,38071:22617,38072:34532,38073:38588,38074:20276,38075:21028,38076:21322,38077:21453,38078:21467,38079:24070,38080:25644,38081:26001,38082:26495,38083:27710,38084:27726,38085:29256,38086:29359,38087:29677,38088:30036,38089:32321,38090:33324,38091:34281,38092:36009,38093:31684,38094:37318,38095:29033,38096:38930,38097:39151,38098:25405,38099:26217,38100:30058,38101:30436,38102:30928,38103:34115,38104:34542,38105:21290,38106:21329,38107:21542,38108:22915,38109:24199,38110:24444,38111:24754,38112:25161,38113:25209,38114:25259,38115:26e3,38116:27604,38117:27852,38118:30130,38119:30382,38120:30865,38121:31192,38122:32203,38123:32631,38124:32933,38125:34987,38126:35513,38127:36027,38128:36991,38129:38750,38130:39131,38131:27147,38132:31800,38133:20633,38134:23614,38135:24494,38136:26503,38137:27608,38138:29749,38139:30473,38140:32654,38208:40763,38209:26570,38210:31255,38211:21305,38212:30091,38213:39661,38214:24422,38215:33181,38216:33777,38217:32920,38218:24380,38219:24517,38220:30050,38221:31558,38222:36924,38223:26727,38224:23019,38225:23195,38226:32016,38227:30334,38228:35628,38229:20469,38230:24426,38231:27161,38232:27703,38233:28418,38234:29922,38235:31080,38236:34920,38237:35413,38238:35961,38239:24287,38240:25551,38241:30149,38242:31186,38243:33495,38244:37672,38245:37618,38246:33948,38247:34541,38248:39981,38249:21697,38250:24428,38251:25996,38252:27996,38253:28693,38254:36007,38255:36051,38256:38971,38257:25935,38258:29942,38259:19981,38260:20184,38261:22496,38262:22827,38263:23142,38264:23500,38265:20904,38266:24067,38267:24220,38268:24598,38269:25206,38270:25975,38272:26023,38273:26222,38274:28014,38275:29238,38276:31526,38277:33104,38278:33178,38279:33433,38280:35676,38281:36e3,38282:36070,38283:36212,38284:38428,38285:38468,38286:20398,38287:25771,38288:27494,38289:33310,38290:33889,38291:34154,38292:37096,38293:23553,38294:26963,38295:39080,38296:33914,38297:34135,38298:20239,38299:21103,38300:24489,38301:24133,38302:26381,38303:31119,38304:33145,38305:35079,38306:35206,38307:28149,38308:24343,38309:25173,38310:27832,38311:20175,38312:29289,38313:39826,38314:20998,38315:21563,38316:22132,38317:22707,38318:24996,38319:25198,38320:28954,38321:22894,38322:31881,38323:31966,38324:32027,38325:38640,38326:25991,38327:32862,38328:19993,38329:20341,38330:20853,38331:22592,38332:24163,38333:24179,38334:24330,38335:26564,38336:20006,38337:34109,38338:38281,38339:38491,38340:31859,38341:38913,38342:20731,38343:22721,38344:30294,38345:30887,38346:21029,38347:30629,38348:34065,38349:31622,38350:20559,38351:22793,38352:29255,38353:31687,38354:32232,38355:36794,38356:36820,38357:36941,38358:20415,38359:21193,38360:23081,38361:24321,38362:38829,38363:20445,38364:33303,38365:37610,38366:22275,38367:25429,38368:27497,38369:29995,38370:35036,38371:36628,38372:31298,38373:21215,38374:22675,38375:24917,38376:25098,38377:26286,38378:27597,38379:31807,38380:33769,38381:20515,38382:20472,38383:21253,38384:21574,38385:22577,38386:22857,38387:23453,38388:23792,38389:23791,38390:23849,38391:24214,38392:25265,38393:25447,38394:25918,38395:26041,38396:26379,38464:27861,38465:27873,38466:28921,38467:30770,38468:32299,38469:32990,38470:33459,38471:33804,38472:34028,38473:34562,38474:35090,38475:35370,38476:35914,38477:37030,38478:37586,38479:39165,38480:40179,38481:40300,38482:20047,38483:20129,38484:20621,38485:21078,38486:22346,38487:22952,38488:24125,38489:24536,38490:24537,38491:25151,38492:26292,38493:26395,38494:26576,38495:26834,38496:20882,38497:32033,38498:32938,38499:33192,38500:35584,38501:35980,38502:36031,38503:37502,38504:38450,38505:21536,38506:38956,38507:21271,38508:20693,38509:21340,38510:22696,38511:25778,38512:26420,38513:29287,38514:30566,38515:31302,38516:37350,38517:21187,38518:27809,38519:27526,38520:22528,38521:24140,38522:22868,38523:26412,38524:32763,38525:20961,38526:30406,38528:25705,38529:30952,38530:39764,38531:40635,38532:22475,38533:22969,38534:26151,38535:26522,38536:27598,38537:21737,38538:27097,38539:24149,38540:33180,38541:26517,38542:39850,38543:26622,38544:40018,38545:26717,38546:20134,38547:20451,38548:21448,38549:25273,38550:26411,38551:27819,38552:36804,38553:20397,38554:32365,38555:40639,38556:19975,38557:24930,38558:28288,38559:28459,38560:34067,38561:21619,38562:26410,38563:39749,38564:24051,38565:31637,38566:23724,38567:23494,38568:34588,38569:28234,38570:34001,38571:31252,38572:33032,38573:22937,38574:31885,38575:27665,38576:30496,38577:21209,38578:22818,38579:28961,38580:29279,38581:30683,38582:38695,38583:40289,38584:26891,38585:23167,38586:23064,38587:20901,38588:21517,38589:21629,38590:26126,38591:30431,38592:36855,38593:37528,38594:40180,38595:23018,38596:29277,38597:28357,38598:20813,38599:26825,38600:32191,38601:32236,38602:38754,38603:40634,38604:25720,38605:27169,38606:33538,38607:22916,38608:23391,38609:27611,38610:29467,38611:30450,38612:32178,38613:32791,38614:33945,38615:20786,38616:26408,38617:40665,38618:30446,38619:26466,38620:21247,38621:39173,38622:23588,38623:25147,38624:31870,38625:36016,38626:21839,38627:24758,38628:32011,38629:38272,38630:21249,38631:20063,38632:20918,38633:22812,38634:29242,38635:32822,38636:37326,38637:24357,38638:30690,38639:21380,38640:24441,38641:32004,38642:34220,38643:35379,38644:36493,38645:38742,38646:26611,38647:34222,38648:37971,38649:24841,38650:24840,38651:27833,38652:30290,38720:35565,38721:36664,38722:21807,38723:20305,38724:20778,38725:21191,38726:21451,38727:23461,38728:24189,38729:24736,38730:24962,38731:25558,38732:26377,38733:26586,38734:28263,38735:28044,38736:29494,38737:29495,38738:30001,38739:31056,38740:35029,38741:35480,38742:36938,38743:37009,38744:37109,38745:38596,38746:34701,38747:22805,38748:20104,38749:20313,38750:19982,38751:35465,38752:36671,38753:38928,38754:20653,38755:24188,38756:22934,38757:23481,38758:24248,38759:25562,38760:25594,38761:25793,38762:26332,38763:26954,38764:27096,38765:27915,38766:28342,38767:29076,38768:29992,38769:31407,38770:32650,38771:32768,38772:33865,38773:33993,38774:35201,38775:35617,38776:36362,38777:36965,38778:38525,38779:39178,38780:24958,38781:25233,38782:27442,38784:27779,38785:28020,38786:32716,38787:32764,38788:28096,38789:32645,38790:34746,38791:35064,38792:26469,38793:33713,38794:38972,38795:38647,38796:27931,38797:32097,38798:33853,38799:37226,38800:20081,38801:21365,38802:23888,38803:27396,38804:28651,38805:34253,38806:34349,38807:35239,38808:21033,38809:21519,38810:23653,38811:26446,38812:26792,38813:29702,38814:29827,38815:30178,38816:35023,38817:35041,38818:37324,38819:38626,38820:38520,38821:24459,38822:29575,38823:31435,38824:33870,38825:25504,38826:30053,38827:21129,38828:27969,38829:28316,38830:29705,38831:30041,38832:30827,38833:31890,38834:38534,38835:31452,38836:40845,38837:20406,38838:24942,38839:26053,38840:34396,38841:20102,38842:20142,38843:20698,38844:20001,38845:20940,38846:23534,38847:26009,38848:26753,38849:28092,38850:29471,38851:30274,38852:30637,38853:31260,38854:31975,38855:33391,38856:35538,38857:36988,38858:37327,38859:38517,38860:38936,38861:21147,38862:32209,38863:20523,38864:21400,38865:26519,38866:28107,38867:29136,38868:29747,38869:33256,38870:36650,38871:38563,38872:40023,38873:40607,38874:29792,38875:22593,38876:28057,38877:32047,38878:39006,38879:20196,38880:20278,38881:20363,38882:20919,38883:21169,38884:23994,38885:24604,38886:29618,38887:31036,38888:33491,38889:37428,38890:38583,38891:38646,38892:38666,38893:40599,38894:40802,38895:26278,38896:27508,38897:21015,38898:21155,38899:28872,38900:35010,38901:24265,38902:24651,38903:24976,38904:28451,38905:29001,38906:31806,38907:32244,38908:32879,38976:34030,38977:36899,38978:37676,38979:21570,38980:39791,38981:27347,38982:28809,38983:36034,38984:36335,38985:38706,38986:21172,38987:23105,38988:24266,38989:24324,38990:26391,38991:27004,38992:27028,38993:28010,38994:28431,38995:29282,38996:29436,38997:31725,38998:32769,38999:32894,39e3:34635,39001:37070,39002:20845,39003:40595,39004:31108,39005:32907,39006:37682,39007:35542,39008:20525,39009:21644,39010:35441,39011:27498,39012:36036,39013:33031,39014:24785,39015:26528,39016:40434,39017:20121,39018:20120,39019:39952,39020:35435,39021:34241,39022:34152,39023:26880,39024:28286,39025:30871,39026:33109,39071:24332,39072:19984,39073:19989,39074:20010,39075:20017,39076:20022,39077:20028,39078:20031,39079:20034,39080:20054,39081:20056,39082:20098,39083:20101,39084:35947,39085:20106,39086:33298,39087:24333,39088:20110,39089:20126,39090:20127,39091:20128,39092:20130,39093:20144,39094:20147,39095:20150,39096:20174,39097:20173,39098:20164,39099:20166,39100:20162,39101:20183,39102:20190,39103:20205,39104:20191,39105:20215,39106:20233,39107:20314,39108:20272,39109:20315,39110:20317,39111:20311,39112:20295,39113:20342,39114:20360,39115:20367,39116:20376,39117:20347,39118:20329,39119:20336,39120:20369,39121:20335,39122:20358,39123:20374,39124:20760,39125:20436,39126:20447,39127:20430,39128:20440,39129:20443,39130:20433,39131:20442,39132:20432,39133:20452,39134:20453,39135:20506,39136:20520,39137:20500,39138:20522,39139:20517,39140:20485,39141:20252,39142:20470,39143:20513,39144:20521,39145:20524,39146:20478,39147:20463,39148:20497,39149:20486,39150:20547,39151:20551,39152:26371,39153:20565,39154:20560,39155:20552,39156:20570,39157:20566,39158:20588,39159:20600,39160:20608,39161:20634,39162:20613,39163:20660,39164:20658,39232:20681,39233:20682,39234:20659,39235:20674,39236:20694,39237:20702,39238:20709,39239:20717,39240:20707,39241:20718,39242:20729,39243:20725,39244:20745,39245:20737,39246:20738,39247:20758,39248:20757,39249:20756,39250:20762,39251:20769,39252:20794,39253:20791,39254:20796,39255:20795,39256:20799,39257:20800,39258:20818,39259:20812,39260:20820,39261:20834,39262:31480,39263:20841,39264:20842,39265:20846,39266:20864,39267:20866,39268:22232,39269:20876,39270:20873,39271:20879,39272:20881,39273:20883,39274:20885,39275:20886,39276:20900,39277:20902,39278:20898,39279:20905,39280:20906,39281:20907,39282:20915,39283:20913,39284:20914,39285:20912,39286:20917,39287:20925,39288:20933,39289:20937,39290:20955,39291:20960,39292:34389,39293:20969,39294:20973,39296:20976,39297:20981,39298:20990,39299:20996,39300:21003,39301:21012,39302:21006,39303:21031,39304:21034,39305:21038,39306:21043,39307:21049,39308:21071,39309:21060,39310:21067,39311:21068,39312:21086,39313:21076,39314:21098,39315:21108,39316:21097,39317:21107,39318:21119,39319:21117,39320:21133,39321:21140,39322:21138,39323:21105,39324:21128,39325:21137,39326:36776,39327:36775,39328:21164,39329:21165,39330:21180,39331:21173,39332:21185,39333:21197,39334:21207,39335:21214,39336:21219,39337:21222,39338:39149,39339:21216,39340:21235,39341:21237,39342:21240,39343:21241,39344:21254,39345:21256,39346:30008,39347:21261,39348:21264,39349:21263,39350:21269,39351:21274,39352:21283,39353:21295,39354:21297,39355:21299,39356:21304,39357:21312,39358:21318,39359:21317,39360:19991,39361:21321,39362:21325,39363:20950,39364:21342,39365:21353,39366:21358,39367:22808,39368:21371,39369:21367,39370:21378,39371:21398,39372:21408,39373:21414,39374:21413,39375:21422,39376:21424,39377:21430,39378:21443,39379:31762,39380:38617,39381:21471,39382:26364,39383:29166,39384:21486,39385:21480,39386:21485,39387:21498,39388:21505,39389:21565,39390:21568,39391:21548,39392:21549,39393:21564,39394:21550,39395:21558,39396:21545,39397:21533,39398:21582,39399:21647,39400:21621,39401:21646,39402:21599,39403:21617,39404:21623,39405:21616,39406:21650,39407:21627,39408:21632,39409:21622,39410:21636,39411:21648,39412:21638,39413:21703,39414:21666,39415:21688,39416:21669,39417:21676,39418:21700,39419:21704,39420:21672,39488:21675,39489:21698,39490:21668,39491:21694,39492:21692,39493:21720,39494:21733,39495:21734,39496:21775,39497:21780,39498:21757,39499:21742,39500:21741,39501:21754,39502:21730,39503:21817,39504:21824,39505:21859,39506:21836,39507:21806,39508:21852,39509:21829,39510:21846,39511:21847,39512:21816,39513:21811,39514:21853,39515:21913,39516:21888,39517:21679,39518:21898,39519:21919,39520:21883,39521:21886,39522:21912,39523:21918,39524:21934,39525:21884,39526:21891,39527:21929,39528:21895,39529:21928,39530:21978,39531:21957,39532:21983,39533:21956,39534:21980,39535:21988,39536:21972,39537:22036,39538:22007,39539:22038,39540:22014,39541:22013,39542:22043,39543:22009,39544:22094,39545:22096,39546:29151,39547:22068,39548:22070,39549:22066,39550:22072,39552:22123,39553:22116,39554:22063,39555:22124,39556:22122,39557:22150,39558:22144,39559:22154,39560:22176,39561:22164,39562:22159,39563:22181,39564:22190,39565:22198,39566:22196,39567:22210,39568:22204,39569:22209,39570:22211,39571:22208,39572:22216,39573:22222,39574:22225,39575:22227,39576:22231,39577:22254,39578:22265,39579:22272,39580:22271,39581:22276,39582:22281,39583:22280,39584:22283,39585:22285,39586:22291,39587:22296,39588:22294,39589:21959,39590:22300,39591:22310,39592:22327,39593:22328,39594:22350,39595:22331,39596:22336,39597:22351,39598:22377,39599:22464,39600:22408,39601:22369,39602:22399,39603:22409,39604:22419,39605:22432,39606:22451,39607:22436,39608:22442,39609:22448,39610:22467,39611:22470,39612:22484,39613:22482,39614:22483,39615:22538,39616:22486,39617:22499,39618:22539,39619:22553,39620:22557,39621:22642,39622:22561,39623:22626,39624:22603,39625:22640,39626:27584,39627:22610,39628:22589,39629:22649,39630:22661,39631:22713,39632:22687,39633:22699,39634:22714,39635:22750,39636:22715,39637:22712,39638:22702,39639:22725,39640:22739,39641:22737,39642:22743,39643:22745,39644:22744,39645:22757,39646:22748,39647:22756,39648:22751,39649:22767,39650:22778,39651:22777,39652:22779,39653:22780,39654:22781,39655:22786,39656:22794,39657:22800,39658:22811,39659:26790,39660:22821,39661:22828,39662:22829,39663:22834,39664:22840,39665:22846,39666:31442,39667:22869,39668:22864,39669:22862,39670:22874,39671:22872,39672:22882,39673:22880,39674:22887,39675:22892,39676:22889,39744:22904,39745:22913,39746:22941,39747:20318,39748:20395,39749:22947,39750:22962,39751:22982,39752:23016,39753:23004,39754:22925,39755:23001,39756:23002,39757:23077,39758:23071,39759:23057,39760:23068,39761:23049,39762:23066,39763:23104,39764:23148,39765:23113,39766:23093,39767:23094,39768:23138,39769:23146,39770:23194,39771:23228,39772:23230,39773:23243,39774:23234,39775:23229,39776:23267,39777:23255,39778:23270,39779:23273,39780:23254,39781:23290,39782:23291,39783:23308,39784:23307,39785:23318,39786:23346,39787:23248,39788:23338,39789:23350,39790:23358,39791:23363,39792:23365,39793:23360,39794:23377,39795:23381,39796:23386,39797:23387,39798:23397,39799:23401,39800:23408,39801:23411,39802:23413,39803:23416,39804:25992,39805:23418,39806:23424,39808:23427,39809:23462,39810:23480,39811:23491,39812:23495,39813:23497,39814:23508,39815:23504,39816:23524,39817:23526,39818:23522,39819:23518,39820:23525,39821:23531,39822:23536,39823:23542,39824:23539,39825:23557,39826:23559,39827:23560,39828:23565,39829:23571,39830:23584,39831:23586,39832:23592,39833:23608,39834:23609,39835:23617,39836:23622,39837:23630,39838:23635,39839:23632,39840:23631,39841:23409,39842:23660,39843:23662,39844:20066,39845:23670,39846:23673,39847:23692,39848:23697,39849:23700,39850:22939,39851:23723,39852:23739,39853:23734,39854:23740,39855:23735,39856:23749,39857:23742,39858:23751,39859:23769,39860:23785,39861:23805,39862:23802,39863:23789,39864:23948,39865:23786,39866:23819,39867:23829,39868:23831,39869:23900,39870:23839,39871:23835,39872:23825,39873:23828,39874:23842,39875:23834,39876:23833,39877:23832,39878:23884,39879:23890,39880:23886,39881:23883,39882:23916,39883:23923,39884:23926,39885:23943,39886:23940,39887:23938,39888:23970,39889:23965,39890:23980,39891:23982,39892:23997,39893:23952,39894:23991,39895:23996,39896:24009,39897:24013,39898:24019,39899:24018,39900:24022,39901:24027,39902:24043,39903:24050,39904:24053,39905:24075,39906:24090,39907:24089,39908:24081,39909:24091,39910:24118,39911:24119,39912:24132,39913:24131,39914:24128,39915:24142,39916:24151,39917:24148,39918:24159,39919:24162,39920:24164,39921:24135,39922:24181,39923:24182,39924:24186,39925:40636,39926:24191,39927:24224,39928:24257,39929:24258,39930:24264,39931:24272,39932:24271,4e4:24278,40001:24291,40002:24285,40003:24282,40004:24283,40005:24290,40006:24289,40007:24296,40008:24297,40009:24300,40010:24305,40011:24307,40012:24304,40013:24308,40014:24312,40015:24318,40016:24323,40017:24329,40018:24413,40019:24412,40020:24331,40021:24337,40022:24342,40023:24361,40024:24365,40025:24376,40026:24385,40027:24392,40028:24396,40029:24398,40030:24367,40031:24401,40032:24406,40033:24407,40034:24409,40035:24417,40036:24429,40037:24435,40038:24439,40039:24451,40040:24450,40041:24447,40042:24458,40043:24456,40044:24465,40045:24455,40046:24478,40047:24473,40048:24472,40049:24480,40050:24488,40051:24493,40052:24508,40053:24534,40054:24571,40055:24548,40056:24568,40057:24561,40058:24541,40059:24755,40060:24575,40061:24609,40062:24672,40064:24601,40065:24592,40066:24617,40067:24590,40068:24625,40069:24603,40070:24597,40071:24619,40072:24614,40073:24591,40074:24634,40075:24666,40076:24641,40077:24682,40078:24695,40079:24671,40080:24650,40081:24646,40082:24653,40083:24675,40084:24643,40085:24676,40086:24642,40087:24684,40088:24683,40089:24665,40090:24705,40091:24717,40092:24807,40093:24707,40094:24730,40095:24708,40096:24731,40097:24726,40098:24727,40099:24722,40100:24743,40101:24715,40102:24801,40103:24760,40104:24800,40105:24787,40106:24756,40107:24560,40108:24765,40109:24774,40110:24757,40111:24792,40112:24909,40113:24853,40114:24838,40115:24822,40116:24823,40117:24832,40118:24820,40119:24826,40120:24835,40121:24865,40122:24827,40123:24817,40124:24845,40125:24846,40126:24903,40127:24894,40128:24872,40129:24871,40130:24906,40131:24895,40132:24892,40133:24876,40134:24884,40135:24893,40136:24898,40137:24900,40138:24947,40139:24951,40140:24920,40141:24921,40142:24922,40143:24939,40144:24948,40145:24943,40146:24933,40147:24945,40148:24927,40149:24925,40150:24915,40151:24949,40152:24985,40153:24982,40154:24967,40155:25004,40156:24980,40157:24986,40158:24970,40159:24977,40160:25003,40161:25006,40162:25036,40163:25034,40164:25033,40165:25079,40166:25032,40167:25027,40168:25030,40169:25018,40170:25035,40171:32633,40172:25037,40173:25062,40174:25059,40175:25078,40176:25082,40177:25076,40178:25087,40179:25085,40180:25084,40181:25086,40182:25088,40183:25096,40184:25097,40185:25101,40186:25100,40187:25108,40188:25115,40256:25118,40257:25121,40258:25130,40259:25134,40260:25136,40261:25138,40262:25139,40263:25153,40264:25166,40265:25182,40266:25187,40267:25179,40268:25184,40269:25192,40270:25212,40271:25218,40272:25225,40273:25214,40274:25234,40275:25235,40276:25238,40277:25300,40278:25219,40279:25236,40280:25303,40281:25297,40282:25275,40283:25295,40284:25343,40285:25286,40286:25812,40287:25288,40288:25308,40289:25292,40290:25290,40291:25282,40292:25287,40293:25243,40294:25289,40295:25356,40296:25326,40297:25329,40298:25383,40299:25346,40300:25352,40301:25327,40302:25333,40303:25424,40304:25406,40305:25421,40306:25628,40307:25423,40308:25494,40309:25486,40310:25472,40311:25515,40312:25462,40313:25507,40314:25487,40315:25481,40316:25503,40317:25525,40318:25451,40320:25449,40321:25534,40322:25577,40323:25536,40324:25542,40325:25571,40326:25545,40327:25554,40328:25590,40329:25540,40330:25622,40331:25652,40332:25606,40333:25619,40334:25638,40335:25654,40336:25885,40337:25623,40338:25640,40339:25615,40340:25703,40341:25711,40342:25718,40343:25678,40344:25898,40345:25749,40346:25747,40347:25765,40348:25769,40349:25736,40350:25788,40351:25818,40352:25810,40353:25797,40354:25799,40355:25787,40356:25816,40357:25794,40358:25841,40359:25831,40360:33289,40361:25824,40362:25825,40363:25260,40364:25827,40365:25839,40366:25900,40367:25846,40368:25844,40369:25842,40370:25850,40371:25856,40372:25853,40373:25880,40374:25884,40375:25861,40376:25892,40377:25891,40378:25899,40379:25908,40380:25909,40381:25911,40382:25910,40383:25912,40384:30027,40385:25928,40386:25942,40387:25941,40388:25933,40389:25944,40390:25950,40391:25949,40392:25970,40393:25976,40394:25986,40395:25987,40396:35722,40397:26011,40398:26015,40399:26027,40400:26039,40401:26051,40402:26054,40403:26049,40404:26052,40405:26060,40406:26066,40407:26075,40408:26073,40409:26080,40410:26081,40411:26097,40412:26482,40413:26122,40414:26115,40415:26107,40416:26483,40417:26165,40418:26166,40419:26164,40420:26140,40421:26191,40422:26180,40423:26185,40424:26177,40425:26206,40426:26205,40427:26212,40428:26215,40429:26216,40430:26207,40431:26210,40432:26224,40433:26243,40434:26248,40435:26254,40436:26249,40437:26244,40438:26264,40439:26269,40440:26305,40441:26297,40442:26313,40443:26302,40444:26300,40512:26308,40513:26296,40514:26326,40515:26330,40516:26336,40517:26175,40518:26342,40519:26345,40520:26352,40521:26357,40522:26359,40523:26383,40524:26390,40525:26398,40526:26406,40527:26407,40528:38712,40529:26414,40530:26431,40531:26422,40532:26433,40533:26424,40534:26423,40535:26438,40536:26462,40537:26464,40538:26457,40539:26467,40540:26468,40541:26505,40542:26480,40543:26537,40544:26492,40545:26474,40546:26508,40547:26507,40548:26534,40549:26529,40550:26501,40551:26551,40552:26607,40553:26548,40554:26604,40555:26547,40556:26601,40557:26552,40558:26596,40559:26590,40560:26589,40561:26594,40562:26606,40563:26553,40564:26574,40565:26566,40566:26599,40567:27292,40568:26654,40569:26694,40570:26665,40571:26688,40572:26701,40573:26674,40574:26702,40576:26803,40577:26667,40578:26713,40579:26723,40580:26743,40581:26751,40582:26783,40583:26767,40584:26797,40585:26772,40586:26781,40587:26779,40588:26755,40589:27310,40590:26809,40591:26740,40592:26805,40593:26784,40594:26810,40595:26895,40596:26765,40597:26750,40598:26881,40599:26826,40600:26888,40601:26840,40602:26914,40603:26918,40604:26849,40605:26892,40606:26829,40607:26836,40608:26855,40609:26837,40610:26934,40611:26898,40612:26884,40613:26839,40614:26851,40615:26917,40616:26873,40617:26848,40618:26863,40619:26920,40620:26922,40621:26906,40622:26915,40623:26913,40624:26822,40625:27001,40626:26999,40627:26972,40628:27e3,40629:26987,40630:26964,40631:27006,40632:26990,40633:26937,40634:26996,40635:26941,40636:26969,40637:26928,40638:26977,40639:26974,40640:26973,40641:27009,40642:26986,40643:27058,40644:27054,40645:27088,40646:27071,40647:27073,40648:27091,40649:27070,40650:27086,40651:23528,40652:27082,40653:27101,40654:27067,40655:27075,40656:27047,40657:27182,40658:27025,40659:27040,40660:27036,40661:27029,40662:27060,40663:27102,40664:27112,40665:27138,40666:27163,40667:27135,40668:27402,40669:27129,40670:27122,40671:27111,40672:27141,40673:27057,40674:27166,40675:27117,40676:27156,40677:27115,40678:27146,40679:27154,40680:27329,40681:27171,40682:27155,40683:27204,40684:27148,40685:27250,40686:27190,40687:27256,40688:27207,40689:27234,40690:27225,40691:27238,40692:27208,40693:27192,40694:27170,40695:27280,40696:27277,40697:27296,40698:27268,40699:27298,40700:27299,40768:27287,40769:34327,40770:27323,40771:27331,40772:27330,40773:27320,40774:27315,40775:27308,40776:27358,40777:27345,40778:27359,40779:27306,40780:27354,40781:27370,40782:27387,40783:27397,40784:34326,40785:27386,40786:27410,40787:27414,40788:39729,40789:27423,40790:27448,40791:27447,40792:30428,40793:27449,40794:39150,40795:27463,40796:27459,40797:27465,40798:27472,40799:27481,40800:27476,40801:27483,40802:27487,40803:27489,40804:27512,40805:27513,40806:27519,40807:27520,40808:27524,40809:27523,40810:27533,40811:27544,40812:27541,40813:27550,40814:27556,40815:27562,40816:27563,40817:27567,40818:27570,40819:27569,40820:27571,40821:27575,40822:27580,40823:27590,40824:27595,40825:27603,40826:27615,40827:27628,40828:27627,40829:27635,40830:27631,40832:40638,40833:27656,40834:27667,40835:27668,40836:27675,40837:27684,40838:27683,40839:27742,40840:27733,40841:27746,40842:27754,40843:27778,40844:27789,40845:27802,40846:27777,40847:27803,40848:27774,40849:27752,40850:27763,40851:27794,40852:27792,40853:27844,40854:27889,40855:27859,40856:27837,40857:27863,40858:27845,40859:27869,40860:27822,40861:27825,40862:27838,40863:27834,40864:27867,40865:27887,40866:27865,40867:27882,40868:27935,40869:34893,40870:27958,40871:27947,40872:27965,40873:27960,40874:27929,40875:27957,40876:27955,40877:27922,40878:27916,40879:28003,40880:28051,40881:28004,40882:27994,40883:28025,40884:27993,40885:28046,40886:28053,40887:28644,40888:28037,40889:28153,40890:28181,40891:28170,40892:28085,40893:28103,40894:28134,40895:28088,40896:28102,40897:28140,40898:28126,40899:28108,40900:28136,40901:28114,40902:28101,40903:28154,40904:28121,40905:28132,40906:28117,40907:28138,40908:28142,40909:28205,40910:28270,40911:28206,40912:28185,40913:28274,40914:28255,40915:28222,40916:28195,40917:28267,40918:28203,40919:28278,40920:28237,40921:28191,40922:28227,40923:28218,40924:28238,40925:28196,40926:28415,40927:28189,40928:28216,40929:28290,40930:28330,40931:28312,40932:28361,40933:28343,40934:28371,40935:28349,40936:28335,40937:28356,40938:28338,40939:28372,40940:28373,40941:28303,40942:28325,40943:28354,40944:28319,40945:28481,40946:28433,40947:28748,40948:28396,40949:28408,40950:28414,40951:28479,40952:28402,40953:28465,40954:28399,40955:28466,40956:28364,161:65377,162:65378,163:65379,164:65380,165:65381,166:65382,167:65383,168:65384,169:65385,170:65386,171:65387,172:65388,173:65389,174:65390,175:65391,176:65392,177:65393,178:65394,179:65395,180:65396,181:65397,182:65398,183:65399,184:65400,185:65401,186:65402,187:65403,188:65404,189:65405,190:65406,191:65407,192:65408,193:65409,194:65410,195:65411,196:65412,197:65413,198:65414,199:65415,200:65416,201:65417,202:65418,203:65419,204:65420,205:65421,206:65422,207:65423,208:65424,209:65425,210:65426,211:65427,212:65428,213:65429,214:65430,215:65431,216:65432,217:65433,218:65434,219:65435,220:65436,221:65437,222:65438,223:65439,57408:28478,57409:28435,57410:28407,57411:28550,57412:28538,57413:28536,57414:28545,57415:28544,57416:28527,57417:28507,57418:28659,57419:28525,57420:28546,57421:28540,57422:28504,57423:28558,57424:28561,57425:28610,57426:28518,57427:28595,57428:28579,57429:28577,57430:28580,57431:28601,57432:28614,57433:28586,57434:28639,57435:28629,57436:28652,57437:28628,57438:28632,57439:28657,57440:28654,57441:28635,57442:28681,57443:28683,57444:28666,57445:28689,57446:28673,57447:28687,57448:28670,57449:28699,57450:28698,57451:28532,57452:28701,57453:28696,57454:28703,57455:28720,57456:28734,57457:28722,57458:28753,57459:28771,57460:28825,57461:28818,57462:28847,57463:28913,57464:28844,57465:28856,57466:28851,57467:28846,57468:28895,57469:28875,57470:28893,57472:28889,57473:28937,57474:28925,57475:28956,57476:28953,57477:29029,57478:29013,57479:29064,57480:29030,57481:29026,57482:29004,57483:29014,57484:29036,57485:29071,57486:29179,57487:29060,57488:29077,57489:29096,57490:29100,57491:29143,57492:29113,57493:29118,57494:29138,57495:29129,57496:29140,57497:29134,57498:29152,57499:29164,57500:29159,57501:29173,57502:29180,57503:29177,57504:29183,57505:29197,57506:29200,57507:29211,57508:29224,57509:29229,57510:29228,57511:29232,57512:29234,57513:29243,57514:29244,57515:29247,57516:29248,57517:29254,57518:29259,57519:29272,57520:29300,57521:29310,57522:29314,57523:29313,57524:29319,57525:29330,57526:29334,57527:29346,57528:29351,57529:29369,57530:29362,57531:29379,57532:29382,57533:29380,57534:29390,57535:29394,57536:29410,57537:29408,57538:29409,57539:29433,57540:29431,57541:20495,57542:29463,57543:29450,57544:29468,57545:29462,57546:29469,57547:29492,57548:29487,57549:29481,57550:29477,57551:29502,57552:29518,57553:29519,57554:40664,57555:29527,57556:29546,57557:29544,57558:29552,57559:29560,57560:29557,57561:29563,57562:29562,57563:29640,57564:29619,57565:29646,57566:29627,57567:29632,57568:29669,57569:29678,57570:29662,57571:29858,57572:29701,57573:29807,57574:29733,57575:29688,57576:29746,57577:29754,57578:29781,57579:29759,57580:29791,57581:29785,57582:29761,57583:29788,57584:29801,57585:29808,57586:29795,57587:29802,57588:29814,57589:29822,57590:29835,57591:29854,57592:29863,57593:29898,57594:29903,57595:29908,57596:29681,57664:29920,57665:29923,57666:29927,57667:29929,57668:29934,57669:29938,57670:29936,57671:29937,57672:29944,57673:29943,57674:29956,57675:29955,57676:29957,57677:29964,57678:29966,57679:29965,57680:29973,57681:29971,57682:29982,57683:29990,57684:29996,57685:30012,57686:30020,57687:30029,57688:30026,57689:30025,57690:30043,57691:30022,57692:30042,57693:30057,57694:30052,57695:30055,57696:30059,57697:30061,57698:30072,57699:30070,57700:30086,57701:30087,57702:30068,57703:30090,57704:30089,57705:30082,57706:30100,57707:30106,57708:30109,57709:30117,57710:30115,57711:30146,57712:30131,57713:30147,57714:30133,57715:30141,57716:30136,57717:30140,57718:30129,57719:30157,57720:30154,57721:30162,57722:30169,57723:30179,57724:30174,57725:30206,57726:30207,57728:30204,57729:30209,57730:30192,57731:30202,57732:30194,57733:30195,57734:30219,57735:30221,57736:30217,57737:30239,57738:30247,57739:30240,57740:30241,57741:30242,57742:30244,57743:30260,57744:30256,57745:30267,57746:30279,57747:30280,57748:30278,57749:30300,57750:30296,57751:30305,57752:30306,57753:30312,57754:30313,57755:30314,57756:30311,57757:30316,57758:30320,57759:30322,57760:30326,57761:30328,57762:30332,57763:30336,57764:30339,57765:30344,57766:30347,57767:30350,57768:30358,57769:30355,57770:30361,57771:30362,57772:30384,57773:30388,57774:30392,57775:30393,57776:30394,57777:30402,57778:30413,57779:30422,57780:30418,57781:30430,57782:30433,57783:30437,57784:30439,57785:30442,57786:34351,57787:30459,57788:30472,57789:30471,57790:30468,57791:30505,57792:30500,57793:30494,57794:30501,57795:30502,57796:30491,57797:30519,57798:30520,57799:30535,57800:30554,57801:30568,57802:30571,57803:30555,57804:30565,57805:30591,57806:30590,57807:30585,57808:30606,57809:30603,57810:30609,57811:30624,57812:30622,57813:30640,57814:30646,57815:30649,57816:30655,57817:30652,57818:30653,57819:30651,57820:30663,57821:30669,57822:30679,57823:30682,57824:30684,57825:30691,57826:30702,57827:30716,57828:30732,57829:30738,57830:31014,57831:30752,57832:31018,57833:30789,57834:30862,57835:30836,57836:30854,57837:30844,57838:30874,57839:30860,57840:30883,57841:30901,57842:30890,57843:30895,57844:30929,57845:30918,57846:30923,57847:30932,57848:30910,57849:30908,57850:30917,57851:30922,57852:30956,57920:30951,57921:30938,57922:30973,57923:30964,57924:30983,57925:30994,57926:30993,57927:31001,57928:31020,57929:31019,57930:31040,57931:31072,57932:31063,57933:31071,57934:31066,57935:31061,57936:31059,57937:31098,57938:31103,57939:31114,57940:31133,57941:31143,57942:40779,57943:31146,57944:31150,57945:31155,57946:31161,57947:31162,57948:31177,57949:31189,57950:31207,57951:31212,57952:31201,57953:31203,57954:31240,57955:31245,57956:31256,57957:31257,57958:31264,57959:31263,57960:31104,57961:31281,57962:31291,57963:31294,57964:31287,57965:31299,57966:31319,57967:31305,57968:31329,57969:31330,57970:31337,57971:40861,57972:31344,57973:31353,57974:31357,57975:31368,57976:31383,57977:31381,57978:31384,57979:31382,57980:31401,57981:31432,57982:31408,57984:31414,57985:31429,57986:31428,57987:31423,57988:36995,57989:31431,57990:31434,57991:31437,57992:31439,57993:31445,57994:31443,57995:31449,57996:31450,57997:31453,57998:31457,57999:31458,58e3:31462,58001:31469,58002:31472,58003:31490,58004:31503,58005:31498,58006:31494,58007:31539,58008:31512,58009:31513,58010:31518,58011:31541,58012:31528,58013:31542,58014:31568,58015:31610,58016:31492,58017:31565,58018:31499,58019:31564,58020:31557,58021:31605,58022:31589,58023:31604,58024:31591,58025:31600,58026:31601,58027:31596,58028:31598,58029:31645,58030:31640,58031:31647,58032:31629,58033:31644,58034:31642,58035:31627,58036:31634,58037:31631,58038:31581,58039:31641,58040:31691,58041:31681,58042:31692,58043:31695,58044:31668,58045:31686,58046:31709,58047:31721,58048:31761,58049:31764,58050:31718,58051:31717,58052:31840,58053:31744,58054:31751,58055:31763,58056:31731,58057:31735,58058:31767,58059:31757,58060:31734,58061:31779,58062:31783,58063:31786,58064:31775,58065:31799,58066:31787,58067:31805,58068:31820,58069:31811,58070:31828,58071:31823,58072:31808,58073:31824,58074:31832,58075:31839,58076:31844,58077:31830,58078:31845,58079:31852,58080:31861,58081:31875,58082:31888,58083:31908,58084:31917,58085:31906,58086:31915,58087:31905,58088:31912,58089:31923,58090:31922,58091:31921,58092:31918,58093:31929,58094:31933,58095:31936,58096:31941,58097:31938,58098:31960,58099:31954,58100:31964,58101:31970,58102:39739,58103:31983,58104:31986,58105:31988,58106:31990,58107:31994,58108:32006,58176:32002,58177:32028,58178:32021,58179:32010,58180:32069,58181:32075,58182:32046,58183:32050,58184:32063,58185:32053,58186:32070,58187:32115,58188:32086,58189:32078,58190:32114,58191:32104,58192:32110,58193:32079,58194:32099,58195:32147,58196:32137,58197:32091,58198:32143,58199:32125,58200:32155,58201:32186,58202:32174,58203:32163,58204:32181,58205:32199,58206:32189,58207:32171,58208:32317,58209:32162,58210:32175,58211:32220,58212:32184,58213:32159,58214:32176,58215:32216,58216:32221,58217:32228,58218:32222,58219:32251,58220:32242,58221:32225,58222:32261,58223:32266,58224:32291,58225:32289,58226:32274,58227:32305,58228:32287,58229:32265,58230:32267,58231:32290,58232:32326,58233:32358,58234:32315,58235:32309,58236:32313,58237:32323,58238:32311,58240:32306,58241:32314,58242:32359,58243:32349,58244:32342,58245:32350,58246:32345,58247:32346,58248:32377,58249:32362,58250:32361,58251:32380,58252:32379,58253:32387,58254:32213,58255:32381,58256:36782,58257:32383,58258:32392,58259:32393,58260:32396,58261:32402,58262:32400,58263:32403,58264:32404,58265:32406,58266:32398,58267:32411,58268:32412,58269:32568,58270:32570,58271:32581,58272:32588,58273:32589,58274:32590,58275:32592,58276:32593,58277:32597,58278:32596,58279:32600,58280:32607,58281:32608,58282:32616,58283:32617,58284:32615,58285:32632,58286:32642,58287:32646,58288:32643,58289:32648,58290:32647,58291:32652,58292:32660,58293:32670,58294:32669,58295:32666,58296:32675,58297:32687,58298:32690,58299:32697,58300:32686,58301:32694,58302:32696,58303:35697,58304:32709,58305:32710,58306:32714,58307:32725,58308:32724,58309:32737,58310:32742,58311:32745,58312:32755,58313:32761,58314:39132,58315:32774,58316:32772,58317:32779,58318:32786,58319:32792,58320:32793,58321:32796,58322:32801,58323:32808,58324:32831,58325:32827,58326:32842,58327:32838,58328:32850,58329:32856,58330:32858,58331:32863,58332:32866,58333:32872,58334:32883,58335:32882,58336:32880,58337:32886,58338:32889,58339:32893,58340:32895,58341:32900,58342:32902,58343:32901,58344:32923,58345:32915,58346:32922,58347:32941,58348:20880,58349:32940,58350:32987,58351:32997,58352:32985,58353:32989,58354:32964,58355:32986,58356:32982,58357:33033,58358:33007,58359:33009,58360:33051,58361:33065,58362:33059,58363:33071,58364:33099,58432:38539,58433:33094,58434:33086,58435:33107,58436:33105,58437:33020,58438:33137,58439:33134,58440:33125,58441:33126,58442:33140,58443:33155,58444:33160,58445:33162,58446:33152,58447:33154,58448:33184,58449:33173,58450:33188,58451:33187,58452:33119,58453:33171,58454:33193,58455:33200,58456:33205,58457:33214,58458:33208,58459:33213,58460:33216,58461:33218,58462:33210,58463:33225,58464:33229,58465:33233,58466:33241,58467:33240,58468:33224,58469:33242,58470:33247,58471:33248,58472:33255,58473:33274,58474:33275,58475:33278,58476:33281,58477:33282,58478:33285,58479:33287,58480:33290,58481:33293,58482:33296,58483:33302,58484:33321,58485:33323,58486:33336,58487:33331,58488:33344,58489:33369,58490:33368,58491:33373,58492:33370,58493:33375,58494:33380,58496:33378,58497:33384,58498:33386,58499:33387,58500:33326,58501:33393,58502:33399,58503:33400,58504:33406,58505:33421,58506:33426,58507:33451,58508:33439,58509:33467,58510:33452,58511:33505,58512:33507,58513:33503,58514:33490,58515:33524,58516:33523,58517:33530,58518:33683,58519:33539,58520:33531,58521:33529,58522:33502,58523:33542,58524:33500,58525:33545,58526:33497,58527:33589,58528:33588,58529:33558,58530:33586,58531:33585,58532:33600,58533:33593,58534:33616,58535:33605,58536:33583,58537:33579,58538:33559,58539:33560,58540:33669,58541:33690,58542:33706,58543:33695,58544:33698,58545:33686,58546:33571,58547:33678,58548:33671,58549:33674,58550:33660,58551:33717,58552:33651,58553:33653,58554:33696,58555:33673,58556:33704,58557:33780,58558:33811,58559:33771,58560:33742,58561:33789,58562:33795,58563:33752,58564:33803,58565:33729,58566:33783,58567:33799,58568:33760,58569:33778,58570:33805,58571:33826,58572:33824,58573:33725,58574:33848,58575:34054,58576:33787,58577:33901,58578:33834,58579:33852,58580:34138,58581:33924,58582:33911,58583:33899,58584:33965,58585:33902,58586:33922,58587:33897,58588:33862,58589:33836,58590:33903,58591:33913,58592:33845,58593:33994,58594:33890,58595:33977,58596:33983,58597:33951,58598:34009,58599:33997,58600:33979,58601:34010,58602:34e3,58603:33985,58604:33990,58605:34006,58606:33953,58607:34081,58608:34047,58609:34036,58610:34071,58611:34072,58612:34092,58613:34079,58614:34069,58615:34068,58616:34044,58617:34112,58618:34147,58619:34136,58620:34120,58688:34113,58689:34306,58690:34123,58691:34133,58692:34176,58693:34212,58694:34184,58695:34193,58696:34186,58697:34216,58698:34157,58699:34196,58700:34203,58701:34282,58702:34183,58703:34204,58704:34167,58705:34174,58706:34192,58707:34249,58708:34234,58709:34255,58710:34233,58711:34256,58712:34261,58713:34269,58714:34277,58715:34268,58716:34297,58717:34314,58718:34323,58719:34315,58720:34302,58721:34298,58722:34310,58723:34338,58724:34330,58725:34352,58726:34367,58727:34381,58728:20053,58729:34388,58730:34399,58731:34407,58732:34417,58733:34451,58734:34467,58735:34473,58736:34474,58737:34443,58738:34444,58739:34486,58740:34479,58741:34500,58742:34502,58743:34480,58744:34505,58745:34851,58746:34475,58747:34516,58748:34526,58749:34537,58750:34540,58752:34527,58753:34523,58754:34543,58755:34578,58756:34566,58757:34568,58758:34560,58759:34563,58760:34555,58761:34577,58762:34569,58763:34573,58764:34553,58765:34570,58766:34612,58767:34623,58768:34615,58769:34619,58770:34597,58771:34601,58772:34586,58773:34656,58774:34655,58775:34680,58776:34636,58777:34638,58778:34676,58779:34647,58780:34664,58781:34670,58782:34649,58783:34643,58784:34659,58785:34666,58786:34821,58787:34722,58788:34719,58789:34690,58790:34735,58791:34763,58792:34749,58793:34752,58794:34768,58795:38614,58796:34731,58797:34756,58798:34739,58799:34759,58800:34758,58801:34747,58802:34799,58803:34802,58804:34784,58805:34831,58806:34829,58807:34814,58808:34806,58809:34807,58810:34830,58811:34770,58812:34833,58813:34838,58814:34837,58815:34850,58816:34849,58817:34865,58818:34870,58819:34873,58820:34855,58821:34875,58822:34884,58823:34882,58824:34898,58825:34905,58826:34910,58827:34914,58828:34923,58829:34945,58830:34942,58831:34974,58832:34933,58833:34941,58834:34997,58835:34930,58836:34946,58837:34967,58838:34962,58839:34990,58840:34969,58841:34978,58842:34957,58843:34980,58844:34992,58845:35007,58846:34993,58847:35011,58848:35012,58849:35028,58850:35032,58851:35033,58852:35037,58853:35065,58854:35074,58855:35068,58856:35060,58857:35048,58858:35058,58859:35076,58860:35084,58861:35082,58862:35091,58863:35139,58864:35102,58865:35109,58866:35114,58867:35115,58868:35137,58869:35140,58870:35131,58871:35126,58872:35128,58873:35148,58874:35101,58875:35168,58876:35166,58944:35174,58945:35172,58946:35181,58947:35178,58948:35183,58949:35188,58950:35191,58951:35198,58952:35203,58953:35208,58954:35210,58955:35219,58956:35224,58957:35233,58958:35241,58959:35238,58960:35244,58961:35247,58962:35250,58963:35258,58964:35261,58965:35263,58966:35264,58967:35290,58968:35292,58969:35293,58970:35303,58971:35316,58972:35320,58973:35331,58974:35350,58975:35344,58976:35340,58977:35355,58978:35357,58979:35365,58980:35382,58981:35393,58982:35419,58983:35410,58984:35398,58985:35400,58986:35452,58987:35437,58988:35436,58989:35426,58990:35461,58991:35458,58992:35460,58993:35496,58994:35489,58995:35473,58996:35493,58997:35494,58998:35482,58999:35491,59e3:35524,59001:35533,59002:35522,59003:35546,59004:35563,59005:35571,59006:35559,59008:35556,59009:35569,59010:35604,59011:35552,59012:35554,59013:35575,59014:35550,59015:35547,59016:35596,59017:35591,59018:35610,59019:35553,59020:35606,59021:35600,59022:35607,59023:35616,59024:35635,59025:38827,59026:35622,59027:35627,59028:35646,59029:35624,59030:35649,59031:35660,59032:35663,59033:35662,59034:35657,59035:35670,59036:35675,59037:35674,59038:35691,59039:35679,59040:35692,59041:35695,59042:35700,59043:35709,59044:35712,59045:35724,59046:35726,59047:35730,59048:35731,59049:35734,59050:35737,59051:35738,59052:35898,59053:35905,59054:35903,59055:35912,59056:35916,59057:35918,59058:35920,59059:35925,59060:35938,59061:35948,59062:35960,59063:35962,59064:35970,59065:35977,59066:35973,59067:35978,59068:35981,59069:35982,59070:35988,59071:35964,59072:35992,59073:25117,59074:36013,59075:36010,59076:36029,59077:36018,59078:36019,59079:36014,59080:36022,59081:36040,59082:36033,59083:36068,59084:36067,59085:36058,59086:36093,59087:36090,59088:36091,59089:36100,59090:36101,59091:36106,59092:36103,59093:36111,59094:36109,59095:36112,59096:40782,59097:36115,59098:36045,59099:36116,59100:36118,59101:36199,59102:36205,59103:36209,59104:36211,59105:36225,59106:36249,59107:36290,59108:36286,59109:36282,59110:36303,59111:36314,59112:36310,59113:36300,59114:36315,59115:36299,59116:36330,59117:36331,59118:36319,59119:36323,59120:36348,59121:36360,59122:36361,59123:36351,59124:36381,59125:36382,59126:36368,59127:36383,59128:36418,59129:36405,59130:36400,59131:36404,59132:36426,59200:36423,59201:36425,59202:36428,59203:36432,59204:36424,59205:36441,59206:36452,59207:36448,59208:36394,59209:36451,59210:36437,59211:36470,59212:36466,59213:36476,59214:36481,59215:36487,59216:36485,59217:36484,59218:36491,59219:36490,59220:36499,59221:36497,59222:36500,59223:36505,59224:36522,59225:36513,59226:36524,59227:36528,59228:36550,59229:36529,59230:36542,59231:36549,59232:36552,59233:36555,59234:36571,59235:36579,59236:36604,59237:36603,59238:36587,59239:36606,59240:36618,59241:36613,59242:36629,59243:36626,59244:36633,59245:36627,59246:36636,59247:36639,59248:36635,59249:36620,59250:36646,59251:36659,59252:36667,59253:36665,59254:36677,59255:36674,59256:36670,59257:36684,59258:36681,59259:36678,59260:36686,59261:36695,59262:36700,59264:36706,59265:36707,59266:36708,59267:36764,59268:36767,59269:36771,59270:36781,59271:36783,59272:36791,59273:36826,59274:36837,59275:36834,59276:36842,59277:36847,59278:36999,59279:36852,59280:36869,59281:36857,59282:36858,59283:36881,59284:36885,59285:36897,59286:36877,59287:36894,59288:36886,59289:36875,59290:36903,59291:36918,59292:36917,59293:36921,59294:36856,59295:36943,59296:36944,59297:36945,59298:36946,59299:36878,59300:36937,59301:36926,59302:36950,59303:36952,59304:36958,59305:36968,59306:36975,59307:36982,59308:38568,59309:36978,59310:36994,59311:36989,59312:36993,59313:36992,59314:37002,59315:37001,59316:37007,59317:37032,59318:37039,59319:37041,59320:37045,59321:37090,59322:37092,59323:25160,59324:37083,59325:37122,59326:37138,59327:37145,59328:37170,59329:37168,59330:37194,59331:37206,59332:37208,59333:37219,59334:37221,59335:37225,59336:37235,59337:37234,59338:37259,59339:37257,59340:37250,59341:37282,59342:37291,59343:37295,59344:37290,59345:37301,59346:37300,59347:37306,59348:37312,59349:37313,59350:37321,59351:37323,59352:37328,59353:37334,59354:37343,59355:37345,59356:37339,59357:37372,59358:37365,59359:37366,59360:37406,59361:37375,59362:37396,59363:37420,59364:37397,59365:37393,59366:37470,59367:37463,59368:37445,59369:37449,59370:37476,59371:37448,59372:37525,59373:37439,59374:37451,59375:37456,59376:37532,59377:37526,59378:37523,59379:37531,59380:37466,59381:37583,59382:37561,59383:37559,59384:37609,59385:37647,59386:37626,59387:37700,59388:37678,59456:37657,59457:37666,59458:37658,59459:37667,59460:37690,59461:37685,59462:37691,59463:37724,59464:37728,59465:37756,59466:37742,59467:37718,59468:37808,59469:37804,59470:37805,59471:37780,59472:37817,59473:37846,59474:37847,59475:37864,59476:37861,59477:37848,59478:37827,59479:37853,59480:37840,59481:37832,59482:37860,59483:37914,59484:37908,59485:37907,59486:37891,59487:37895,59488:37904,59489:37942,59490:37931,59491:37941,59492:37921,59493:37946,59494:37953,59495:37970,59496:37956,59497:37979,59498:37984,59499:37986,59500:37982,59501:37994,59502:37417,59503:38e3,59504:38005,59505:38007,59506:38013,59507:37978,59508:38012,59509:38014,59510:38017,59511:38015,59512:38274,59513:38279,59514:38282,59515:38292,59516:38294,59517:38296,59518:38297,59520:38304,59521:38312,59522:38311,59523:38317,59524:38332,59525:38331,59526:38329,59527:38334,59528:38346,59529:28662,59530:38339,59531:38349,59532:38348,59533:38357,59534:38356,59535:38358,59536:38364,59537:38369,59538:38373,59539:38370,59540:38433,59541:38440,59542:38446,59543:38447,59544:38466,59545:38476,59546:38479,59547:38475,59548:38519,59549:38492,59550:38494,59551:38493,59552:38495,59553:38502,59554:38514,59555:38508,59556:38541,59557:38552,59558:38549,59559:38551,59560:38570,59561:38567,59562:38577,59563:38578,59564:38576,59565:38580,59566:38582,59567:38584,59568:38585,59569:38606,59570:38603,59571:38601,59572:38605,59573:35149,59574:38620,59575:38669,59576:38613,59577:38649,59578:38660,59579:38662,59580:38664,59581:38675,59582:38670,59583:38673,59584:38671,59585:38678,59586:38681,59587:38692,59588:38698,59589:38704,59590:38713,59591:38717,59592:38718,59593:38724,59594:38726,59595:38728,59596:38722,59597:38729,59598:38748,59599:38752,59600:38756,59601:38758,59602:38760,59603:21202,59604:38763,59605:38769,59606:38777,59607:38789,59608:38780,59609:38785,59610:38778,59611:38790,59612:38795,59613:38799,59614:38800,59615:38812,59616:38824,59617:38822,59618:38819,59619:38835,59620:38836,59621:38851,59622:38854,59623:38856,59624:38859,59625:38876,59626:38893,59627:40783,59628:38898,59629:31455,59630:38902,59631:38901,59632:38927,59633:38924,59634:38968,59635:38948,59636:38945,59637:38967,59638:38973,59639:38982,59640:38991,59641:38987,59642:39019,59643:39023,59644:39024,59712:39025,59713:39028,59714:39027,59715:39082,59716:39087,59717:39089,59718:39094,59719:39108,59720:39107,59721:39110,59722:39145,59723:39147,59724:39171,59725:39177,59726:39186,59727:39188,59728:39192,59729:39201,59730:39197,59731:39198,59732:39204,59733:39200,59734:39212,59735:39214,59736:39229,59737:39230,59738:39234,59739:39241,59740:39237,59741:39248,59742:39243,59743:39249,59744:39250,59745:39244,59746:39253,59747:39319,59748:39320,59749:39333,59750:39341,59751:39342,59752:39356,59753:39391,59754:39387,59755:39389,59756:39384,59757:39377,59758:39405,59759:39406,59760:39409,59761:39410,59762:39419,59763:39416,59764:39425,59765:39439,59766:39429,59767:39394,59768:39449,59769:39467,59770:39479,59771:39493,59772:39490,59773:39488,59774:39491,59776:39486,59777:39509,59778:39501,59779:39515,59780:39511,59781:39519,59782:39522,59783:39525,59784:39524,59785:39529,59786:39531,59787:39530,59788:39597,59789:39600,59790:39612,59791:39616,59792:39631,59793:39633,59794:39635,59795:39636,59796:39646,59797:39647,59798:39650,59799:39651,59800:39654,59801:39663,59802:39659,59803:39662,59804:39668,59805:39665,59806:39671,59807:39675,59808:39686,59809:39704,59810:39706,59811:39711,59812:39714,59813:39715,59814:39717,59815:39719,59816:39720,59817:39721,59818:39722,59819:39726,59820:39727,59821:39730,59822:39748,59823:39747,59824:39759,59825:39757,59826:39758,59827:39761,59828:39768,59829:39796,59830:39827,59831:39811,59832:39825,59833:39830,59834:39831,59835:39839,59836:39840,59837:39848,59838:39860,59839:39872,59840:39882,59841:39865,59842:39878,59843:39887,59844:39889,59845:39890,59846:39907,59847:39906,59848:39908,59849:39892,59850:39905,59851:39994,59852:39922,59853:39921,59854:39920,59855:39957,59856:39956,59857:39945,59858:39955,59859:39948,59860:39942,59861:39944,59862:39954,59863:39946,59864:39940,59865:39982,59866:39963,59867:39973,59868:39972,59869:39969,59870:39984,59871:40007,59872:39986,59873:40006,59874:39998,59875:40026,59876:40032,59877:40039,59878:40054,59879:40056,59880:40167,59881:40172,59882:40176,59883:40201,59884:40200,59885:40171,59886:40195,59887:40198,59888:40234,59889:40230,59890:40367,59891:40227,59892:40223,59893:40260,59894:40213,59895:40210,59896:40257,59897:40255,59898:40254,59899:40262,59900:40264,59968:40285,59969:40286,59970:40292,59971:40273,59972:40272,59973:40281,59974:40306,59975:40329,59976:40327,59977:40363,59978:40303,59979:40314,59980:40346,59981:40356,59982:40361,59983:40370,59984:40388,59985:40385,59986:40379,59987:40376,59988:40378,59989:40390,59990:40399,59991:40386,59992:40409,59993:40403,59994:40440,59995:40422,59996:40429,59997:40431,59998:40445,59999:40474,6e4:40475,60001:40478,60002:40565,60003:40569,60004:40573,60005:40577,60006:40584,60007:40587,60008:40588,60009:40594,60010:40597,60011:40593,60012:40605,60013:40613,60014:40617,60015:40632,60016:40618,60017:40621,60018:38753,60019:40652,60020:40654,60021:40655,60022:40656,60023:40660,60024:40668,60025:40670,60026:40669,60027:40672,60028:40677,60029:40680,60030:40687,60032:40692,60033:40694,60034:40695,60035:40697,60036:40699,60037:40700,60038:40701,60039:40711,60040:40712,60041:30391,60042:40725,60043:40737,60044:40748,60045:40766,60046:40778,60047:40786,60048:40788,60049:40803,60050:40799,60051:40800,60052:40801,60053:40806,60054:40807,60055:40812,60056:40810,60057:40823,60058:40818,60059:40822,60060:40853,60061:40860,60062:40864,60063:22575,60064:27079,60065:36953,60066:29796,60067:20956,60068:29081}},function(e,t,o){Object.defineProperty(t,\"__esModule\",{value:!0});var n=o(1),i=o(2);t.decode=function(e,t){var o=new Uint8ClampedArray(e.length);o.set(e);for(var r=new n.default(285,256,0),s=new i.default(r,o),a=new Uint8ClampedArray(t),l=!1,c=0;c<t;c++){var d=s.evaluateAt(r.exp(c+r.generatorBase));a[a.length-1-c]=d,0!==d&&(l=!0)}if(!l)return o;var u=new i.default(r,a),h=function(e,t,o,n){var i;t.degree()<o.degree()&&(t=(i=[o,t])[0],o=i[1]);for(var r=t,s=o,a=e.zero,l=e.one;s.degree()>=n/2;){var c=r,d=a;if(a=l,(r=s).isZero())return null;s=c;for(var u=e.zero,h=r.getCoefficient(r.degree()),p=e.inverse(h);s.degree()>=r.degree()&&!s.isZero();){var f=s.degree()-r.degree(),m=e.multiply(s.getCoefficient(s.degree()),p);u=u.addOrSubtract(e.buildMonomial(f,m)),s=s.addOrSubtract(r.multiplyByMonomial(f,m))}if(l=u.multiplyPoly(a).addOrSubtract(d),s.degree()>=r.degree())return null}var g=l.getCoefficient(0);if(0===g)return null;var v=e.inverse(g);return[l.multiply(v),s.multiply(v)]}(r,r.buildMonomial(t,1),u,t);if(null===h)return null;var p=function(e,t){var o=t.degree();if(1===o)return[t.getCoefficient(1)];for(var n=new Array(o),i=0,r=1;r<e.size&&i<o;r++)0===t.evaluateAt(r)&&(n[i]=e.inverse(r),i++);return i!==o?null:n}(r,h[0]);if(null==p)return null;for(var f=function(e,t,o){for(var i=o.length,r=new Array(i),s=0;s<i;s++){for(var a=e.inverse(o[s]),l=1,c=0;c<i;c++)s!==c&&(l=e.multiply(l,n.addOrSubtractGF(1,e.multiply(o[c],a))));r[s]=e.multiply(t.evaluateAt(a),e.inverse(l)),0!==e.generatorBase&&(r[s]=e.multiply(r[s],a))}return r}(r,h[1],p),m=0;m<p.length;m++){var g=o.length-1-r.log(p[m]);if(g<0)return null;o[g]=n.addOrSubtractGF(o[g],f[m])}return o}},function(e,t,o){Object.defineProperty(t,\"__esModule\",{value:!0}),t.VERSIONS=[{infoBits:null,versionNumber:1,alignmentPatternCenters:[],errorCorrectionLevels:[{ecCodewordsPerBlock:7,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:10,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:16}]},{ecCodewordsPerBlock:13,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:13}]},{ecCodewordsPerBlock:17,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:9}]}]},{infoBits:null,versionNumber:2,alignmentPatternCenters:[6,18],errorCorrectionLevels:[{ecCodewordsPerBlock:10,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:34}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:28}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:22}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:16}]}]},{infoBits:null,versionNumber:3,alignmentPatternCenters:[6,22],errorCorrectionLevels:[{ecCodewordsPerBlock:15,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:55}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:13}]}]},{infoBits:null,versionNumber:4,alignmentPatternCenters:[6,26],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:80}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:32}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:9}]}]},{infoBits:null,versionNumber:5,alignmentPatternCenters:[6,30],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:43}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:15},{numBlocks:2,dataCodewordsPerBlock:16}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:11},{numBlocks:2,dataCodewordsPerBlock:12}]}]},{infoBits:null,versionNumber:6,alignmentPatternCenters:[6,34],errorCorrectionLevels:[{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:68}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:27}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:15}]}]},{infoBits:31892,versionNumber:7,alignmentPatternCenters:[6,22,38],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:78}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:31}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:4,dataCodewordsPerBlock:15}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:13},{numBlocks:1,dataCodewordsPerBlock:14}]}]},{infoBits:34236,versionNumber:8,alignmentPatternCenters:[6,24,42],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:97}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:38},{numBlocks:2,dataCodewordsPerBlock:39}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:18},{numBlocks:2,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:14},{numBlocks:2,dataCodewordsPerBlock:15}]}]},{infoBits:39577,versionNumber:9,alignmentPatternCenters:[6,26,46],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:36},{numBlocks:2,dataCodewordsPerBlock:37}]},{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:16},{numBlocks:4,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:12},{numBlocks:4,dataCodewordsPerBlock:13}]}]},{infoBits:42195,versionNumber:10,alignmentPatternCenters:[6,28,50],errorCorrectionLevels:[{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:68},{numBlocks:2,dataCodewordsPerBlock:69}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:43},{numBlocks:1,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:19},{numBlocks:2,dataCodewordsPerBlock:20}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:15},{numBlocks:2,dataCodewordsPerBlock:16}]}]},{infoBits:48118,versionNumber:11,alignmentPatternCenters:[6,30,54],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:81}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:50},{numBlocks:4,dataCodewordsPerBlock:51}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:22},{numBlocks:4,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:12},{numBlocks:8,dataCodewordsPerBlock:13}]}]},{infoBits:51042,versionNumber:12,alignmentPatternCenters:[6,32,58],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:92},{numBlocks:2,dataCodewordsPerBlock:93}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:36},{numBlocks:2,dataCodewordsPerBlock:37}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:20},{numBlocks:6,dataCodewordsPerBlock:21}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:14},{numBlocks:4,dataCodewordsPerBlock:15}]}]},{infoBits:55367,versionNumber:13,alignmentPatternCenters:[6,34,62],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:107}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:37},{numBlocks:1,dataCodewordsPerBlock:38}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:20},{numBlocks:4,dataCodewordsPerBlock:21}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:11},{numBlocks:4,dataCodewordsPerBlock:12}]}]},{infoBits:58893,versionNumber:14,alignmentPatternCenters:[6,26,46,66],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:115},{numBlocks:1,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:40},{numBlocks:5,dataCodewordsPerBlock:41}]},{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:16},{numBlocks:5,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:12},{numBlocks:5,dataCodewordsPerBlock:13}]}]},{infoBits:63784,versionNumber:15,alignmentPatternCenters:[6,26,48,70],errorCorrectionLevels:[{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:87},{numBlocks:1,dataCodewordsPerBlock:88}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:41},{numBlocks:5,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:24},{numBlocks:7,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:12},{numBlocks:7,dataCodewordsPerBlock:13}]}]},{infoBits:68472,versionNumber:16,alignmentPatternCenters:[6,26,50,74],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:98},{numBlocks:1,dataCodewordsPerBlock:99}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:45},{numBlocks:3,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:19},{numBlocks:2,dataCodewordsPerBlock:20}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:15},{numBlocks:13,dataCodewordsPerBlock:16}]}]},{infoBits:70749,versionNumber:17,alignmentPatternCenters:[6,30,54,78],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:107},{numBlocks:5,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:46},{numBlocks:1,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:22},{numBlocks:15,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:17,dataCodewordsPerBlock:15}]}]},{infoBits:76311,versionNumber:18,alignmentPatternCenters:[6,30,56,82],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:120},{numBlocks:1,dataCodewordsPerBlock:121}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:9,dataCodewordsPerBlock:43},{numBlocks:4,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:22},{numBlocks:1,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:19,dataCodewordsPerBlock:15}]}]},{infoBits:79154,versionNumber:19,alignmentPatternCenters:[6,30,58,86],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:113},{numBlocks:4,dataCodewordsPerBlock:114}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:44},{numBlocks:11,dataCodewordsPerBlock:45}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:21},{numBlocks:4,dataCodewordsPerBlock:22}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:9,dataCodewordsPerBlock:13},{numBlocks:16,dataCodewordsPerBlock:14}]}]},{infoBits:84390,versionNumber:20,alignmentPatternCenters:[6,34,62,90],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:107},{numBlocks:5,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:41},{numBlocks:13,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:24},{numBlocks:5,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:15},{numBlocks:10,dataCodewordsPerBlock:16}]}]},{infoBits:87683,versionNumber:21,alignmentPatternCenters:[6,28,50,72,94],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:116},{numBlocks:4,dataCodewordsPerBlock:117}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:22},{numBlocks:6,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:16},{numBlocks:6,dataCodewordsPerBlock:17}]}]},{infoBits:92361,versionNumber:22,alignmentPatternCenters:[6,26,50,74,98],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:111},{numBlocks:7,dataCodewordsPerBlock:112}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:24},{numBlocks:16,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:34,dataCodewordsPerBlock:13}]}]},{infoBits:96236,versionNumber:23,alignmentPatternCenters:[6,30,54,74,102],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:121},{numBlocks:5,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:47},{numBlocks:14,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:16,dataCodewordsPerBlock:15},{numBlocks:14,dataCodewordsPerBlock:16}]}]},{infoBits:102084,versionNumber:24,alignmentPatternCenters:[6,28,54,80,106],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:117},{numBlocks:4,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:45},{numBlocks:14,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:24},{numBlocks:16,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:30,dataCodewordsPerBlock:16},{numBlocks:2,dataCodewordsPerBlock:17}]}]},{infoBits:102881,versionNumber:25,alignmentPatternCenters:[6,32,58,84,110],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:106},{numBlocks:4,dataCodewordsPerBlock:107}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:47},{numBlocks:13,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:24},{numBlocks:22,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:15},{numBlocks:13,dataCodewordsPerBlock:16}]}]},{infoBits:110507,versionNumber:26,alignmentPatternCenters:[6,30,58,86,114],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:114},{numBlocks:2,dataCodewordsPerBlock:115}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:46},{numBlocks:4,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:28,dataCodewordsPerBlock:22},{numBlocks:6,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:33,dataCodewordsPerBlock:16},{numBlocks:4,dataCodewordsPerBlock:17}]}]},{infoBits:110734,versionNumber:27,alignmentPatternCenters:[6,34,62,90,118],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:122},{numBlocks:4,dataCodewordsPerBlock:123}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:45},{numBlocks:3,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:23},{numBlocks:26,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:15},{numBlocks:28,dataCodewordsPerBlock:16}]}]},{infoBits:117786,versionNumber:28,alignmentPatternCenters:[6,26,50,74,98,122],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:117},{numBlocks:10,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:45},{numBlocks:23,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:24},{numBlocks:31,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:15},{numBlocks:31,dataCodewordsPerBlock:16}]}]},{infoBits:119615,versionNumber:29,alignmentPatternCenters:[6,30,54,78,102,126],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:116},{numBlocks:7,dataCodewordsPerBlock:117}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:21,dataCodewordsPerBlock:45},{numBlocks:7,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:23},{numBlocks:37,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:15},{numBlocks:26,dataCodewordsPerBlock:16}]}]},{infoBits:126325,versionNumber:30,alignmentPatternCenters:[6,26,52,78,104,130],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:115},{numBlocks:10,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:47},{numBlocks:10,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:24},{numBlocks:25,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:23,dataCodewordsPerBlock:15},{numBlocks:25,dataCodewordsPerBlock:16}]}]},{infoBits:127568,versionNumber:31,alignmentPatternCenters:[6,30,56,82,108,134],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:115},{numBlocks:3,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:46},{numBlocks:29,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:42,dataCodewordsPerBlock:24},{numBlocks:1,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:23,dataCodewordsPerBlock:15},{numBlocks:28,dataCodewordsPerBlock:16}]}]},{infoBits:133589,versionNumber:32,alignmentPatternCenters:[6,34,60,86,112,138],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:115}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:46},{numBlocks:23,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:24},{numBlocks:35,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:15},{numBlocks:35,dataCodewordsPerBlock:16}]}]},{infoBits:136944,versionNumber:33,alignmentPatternCenters:[6,30,58,86,114,142],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:115},{numBlocks:1,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:14,dataCodewordsPerBlock:46},{numBlocks:21,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:29,dataCodewordsPerBlock:24},{numBlocks:19,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:15},{numBlocks:46,dataCodewordsPerBlock:16}]}]},{infoBits:141498,versionNumber:34,alignmentPatternCenters:[6,34,62,90,118,146],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:115},{numBlocks:6,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:14,dataCodewordsPerBlock:46},{numBlocks:23,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:44,dataCodewordsPerBlock:24},{numBlocks:7,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:59,dataCodewordsPerBlock:16},{numBlocks:1,dataCodewordsPerBlock:17}]}]},{infoBits:145311,versionNumber:35,alignmentPatternCenters:[6,30,54,78,102,126,150],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:121},{numBlocks:7,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:47},{numBlocks:26,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:39,dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:15},{numBlocks:41,dataCodewordsPerBlock:16}]}]},{infoBits:150283,versionNumber:36,alignmentPatternCenters:[6,24,50,76,102,128,154],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:121},{numBlocks:14,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:47},{numBlocks:34,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:46,dataCodewordsPerBlock:24},{numBlocks:10,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:15},{numBlocks:64,dataCodewordsPerBlock:16}]}]},{infoBits:152622,versionNumber:37,alignmentPatternCenters:[6,28,54,80,106,132,158],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:122},{numBlocks:4,dataCodewordsPerBlock:123}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:29,dataCodewordsPerBlock:46},{numBlocks:14,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:49,dataCodewordsPerBlock:24},{numBlocks:10,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:24,dataCodewordsPerBlock:15},{numBlocks:46,dataCodewordsPerBlock:16}]}]},{infoBits:158308,versionNumber:38,alignmentPatternCenters:[6,32,58,84,110,136,162],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:122},{numBlocks:18,dataCodewordsPerBlock:123}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:46},{numBlocks:32,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:48,dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:42,dataCodewordsPerBlock:15},{numBlocks:32,dataCodewordsPerBlock:16}]}]},{infoBits:161089,versionNumber:39,alignmentPatternCenters:[6,26,54,82,110,138,166],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:20,dataCodewordsPerBlock:117},{numBlocks:4,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:40,dataCodewordsPerBlock:47},{numBlocks:7,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:43,dataCodewordsPerBlock:24},{numBlocks:22,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:15},{numBlocks:67,dataCodewordsPerBlock:16}]}]},{infoBits:167017,versionNumber:40,alignmentPatternCenters:[6,30,58,86,114,142,170],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:118},{numBlocks:6,dataCodewordsPerBlock:119}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:18,dataCodewordsPerBlock:47},{numBlocks:31,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:34,dataCodewordsPerBlock:24},{numBlocks:34,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:20,dataCodewordsPerBlock:15},{numBlocks:61,dataCodewordsPerBlock:16}]}]}]},function(e,t,o){Object.defineProperty(t,\"__esModule\",{value:!0});var n=o(0);function i(e,t,o,n){var i=e.x-t.x+o.x-n.x,r=e.y-t.y+o.y-n.y;if(0===i&&0===r)return{a11:t.x-e.x,a12:t.y-e.y,a13:0,a21:o.x-t.x,a22:o.y-t.y,a23:0,a31:e.x,a32:e.y,a33:1};var s=t.x-o.x,a=n.x-o.x,l=t.y-o.y,c=n.y-o.y,d=s*c-a*l,u=(i*c-a*r)/d,h=(s*r-i*l)/d;return{a11:t.x-e.x+u*t.x,a12:t.y-e.y+u*t.y,a13:u,a21:n.x-e.x+h*n.x,a22:n.y-e.y+h*n.y,a23:h,a31:e.x,a32:e.y,a33:1}}t.extract=function(e,t){for(var o=function(e,t,o,n){var r=i(e,t,o,n);return{a11:r.a22*r.a33-r.a23*r.a32,a12:r.a13*r.a32-r.a12*r.a33,a13:r.a12*r.a23-r.a13*r.a22,a21:r.a23*r.a31-r.a21*r.a33,a22:r.a11*r.a33-r.a13*r.a31,a23:r.a13*r.a21-r.a11*r.a23,a31:r.a21*r.a32-r.a22*r.a31,a32:r.a12*r.a31-r.a11*r.a32,a33:r.a11*r.a22-r.a12*r.a21}}({x:3.5,y:3.5},{x:t.dimension-3.5,y:3.5},{x:t.dimension-6.5,y:t.dimension-6.5},{x:3.5,y:t.dimension-3.5}),r=function(e,t){return{a11:e.a11*t.a11+e.a21*t.a12+e.a31*t.a13,a12:e.a12*t.a11+e.a22*t.a12+e.a32*t.a13,a13:e.a13*t.a11+e.a23*t.a12+e.a33*t.a13,a21:e.a11*t.a21+e.a21*t.a22+e.a31*t.a23,a22:e.a12*t.a21+e.a22*t.a22+e.a32*t.a23,a23:e.a13*t.a21+e.a23*t.a22+e.a33*t.a23,a31:e.a11*t.a31+e.a21*t.a32+e.a31*t.a33,a32:e.a12*t.a31+e.a22*t.a32+e.a32*t.a33,a33:e.a13*t.a31+e.a23*t.a32+e.a33*t.a33}}(i(t.topLeft,t.topRight,t.alignmentPattern,t.bottomLeft),o),s=n.BitMatrix.createEmpty(t.dimension,t.dimension),a=function(e,t){var o=r.a13*e+r.a23*t+r.a33;return{x:(r.a11*e+r.a21*t+r.a31)/o,y:(r.a12*e+r.a22*t+r.a32)/o}},l=0;l<t.dimension;l++)for(var c=0;c<t.dimension;c++){var d=a(c+.5,l+.5);s.set(c,l,e.get(Math.floor(d.x),Math.floor(d.y)))}return{matrix:s,mappingFunction:a}}},function(e,t,o){Object.defineProperty(t,\"__esModule\",{value:!0});var n=function(e,t){return Math.sqrt(Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2))};function i(e){return e.reduce((function(e,t){return e+t}))}function r(e,t,o,i){var r,s,a,l,c=[{x:Math.floor(e.x),y:Math.floor(e.y)}],d=Math.abs(t.y-e.y)>Math.abs(t.x-e.x);d?(r=Math.floor(e.y),s=Math.floor(e.x),a=Math.floor(t.y),l=Math.floor(t.x)):(r=Math.floor(e.x),s=Math.floor(e.y),a=Math.floor(t.x),l=Math.floor(t.y));for(var u=Math.abs(a-r),h=Math.abs(l-s),p=Math.floor(-u/2),f=r<a?1:-1,m=s<l?1:-1,g=!0,v=r,k=s;v!==a+f;v+=f){var y=d?k:v,w=d?v:k;if(o.get(y,w)!==g&&(g=!g,c.push({x:y,y:w}),c.length===i+1))break;if((p+=h)>0){if(k===l)break;k+=m,p-=u}}for(var A=[],C=0;C<i;C++)c[C]&&c[C+1]?A.push(n(c[C],c[C+1])):A.push(0);return A}function s(e,t,o,n){var i,s=t.y-e.y,a=t.x-e.x,l=r(e,t,o,Math.ceil(n/2)),c=r(e,{x:e.x-a,y:e.y-s},o,Math.ceil(n/2)),d=l.shift()+c.shift()-1;return(i=c.concat(d)).concat.apply(i,l)}function a(e,t){var o=i(e)/i(t),n=0;return t.forEach((function(t,i){n+=Math.pow(e[i]-t*o,2)})),{averageSize:o,error:n}}function l(e,t,o){try{var n=s(e,{x:-1,y:e.y},o,t.length),i=s(e,{x:e.x,y:-1},o,t.length),r=s(e,{x:Math.max(0,e.x-e.y)-1,y:Math.max(0,e.y-e.x)-1},o,t.length),l=s(e,{x:Math.min(o.width,e.x+e.y)+1,y:Math.min(o.height,e.y+e.x)+1},o,t.length),c=a(n,t),d=a(i,t),u=a(r,t),h=a(l,t),p=Math.sqrt(c.error*c.error+d.error*d.error+u.error*u.error+h.error*h.error),f=(c.averageSize+d.averageSize+u.averageSize+h.averageSize)/4;return p+(Math.pow(c.averageSize-f,2)+Math.pow(d.averageSize-f,2)+Math.pow(u.averageSize-f,2)+Math.pow(h.averageSize-f,2))/f}catch(e){return 1/0}}function c(e,t){for(var o=Math.round(t.x);e.get(o,Math.round(t.y));)o--;for(var n=Math.round(t.x);e.get(n,Math.round(t.y));)n++;for(var i=(o+n)/2,r=Math.round(t.y);e.get(Math.round(i),r);)r--;for(var s=Math.round(t.y);e.get(Math.round(i),s);)s++;return{x:i,y:(r+s)/2}}function d(e,t,o,r,a){var c,d,u;try{c=function(e,t,o,r){var a=(i(s(e,o,r,5))/7+i(s(e,t,r,5))/7+i(s(o,e,r,5))/7+i(s(t,e,r,5))/7)/4;if(a<1)throw new Error(\"Invalid module size\");var l=Math.round(n(e,t)/a),c=Math.round(n(e,o)/a),d=Math.floor((l+c)/2)+7;switch(d%4){case 0:d++;break;case 2:d--}return{dimension:d,moduleSize:a}}(r,o,a,e),d=c.dimension,u=c.moduleSize}catch(e){return null}var h=o.x-r.x+a.x,p=o.y-r.y+a.y,f=(n(r,a)+n(r,o))/2/u,m=1-3/f,g={x:r.x+m*(h-r.x),y:r.y+m*(p-r.y)},v=t.map((function(t){var o=(t.top.startX+t.top.endX+t.bottom.startX+t.bottom.endX)/4,r=(t.top.y+t.bottom.y+1)/2;if(e.get(Math.floor(o),Math.floor(r))){var s=[t.top.endX-t.top.startX,t.bottom.endX-t.bottom.startX,t.bottom.y-t.top.y+1];return i(s),{x:o,y:r,score:l({x:Math.floor(o),y:Math.floor(r)},[1,1,1],e)+n({x:o,y:r},g)}}})).filter((function(e){return!!e})).sort((function(e,t){return e.score-t.score}));return{alignmentPattern:f>=15&&v.length?v[0]:g,dimension:d}}t.locate=function(e){for(var t=[],o=[],r=[],s=[],a=function(n){for(var a=0,l=!1,c=[0,0,0,0,0],d=function(t){var r=e.get(t,n);if(r===l)a++;else{c=[c[1],c[2],c[3],c[4],a],a=1,l=r;var d=i(c)/7,u=Math.abs(c[0]-d)<d&&Math.abs(c[1]-d)<d&&Math.abs(c[2]-3*d)<3*d&&Math.abs(c[3]-d)<d&&Math.abs(c[4]-d)<d&&!r,h=i(c.slice(-3))/3,p=Math.abs(c[2]-h)<h&&Math.abs(c[3]-h)<h&&Math.abs(c[4]-h)<h&&r;if(u){var f=t-c[3]-c[4],m=f-c[2],g={startX:m,endX:f,y:n},v=o.filter((function(e){return m>=e.bottom.startX&&m<=e.bottom.endX||f>=e.bottom.startX&&m<=e.bottom.endX||m<=e.bottom.startX&&f>=e.bottom.endX&&c[2]/(e.bottom.endX-e.bottom.startX)<1.5&&c[2]/(e.bottom.endX-e.bottom.startX)>.5}));v.length>0?v[0].bottom=g:o.push({top:g,bottom:g})}if(p){var k=t-c[4],y=k-c[3];g={startX:y,y:n,endX:k},v=s.filter((function(e){return y>=e.bottom.startX&&y<=e.bottom.endX||k>=e.bottom.startX&&y<=e.bottom.endX||y<=e.bottom.startX&&k>=e.bottom.endX&&c[2]/(e.bottom.endX-e.bottom.startX)<1.5&&c[2]/(e.bottom.endX-e.bottom.startX)>.5})),v.length>0?v[0].bottom=g:s.push({top:g,bottom:g})}}},u=-1;u<=e.width;u++)d(u);t.push.apply(t,o.filter((function(e){return e.bottom.y!==n&&e.bottom.y-e.top.y>=2}))),o=o.filter((function(e){return e.bottom.y===n})),r.push.apply(r,s.filter((function(e){return e.bottom.y!==n}))),s=s.filter((function(e){return e.bottom.y===n}))},u=0;u<=e.height;u++)a(u);t.push.apply(t,o.filter((function(e){return e.bottom.y-e.top.y>=2}))),r.push.apply(r,s);var h=t.filter((function(e){return e.bottom.y-e.top.y>=2})).map((function(t){var o=(t.top.startX+t.top.endX+t.bottom.startX+t.bottom.endX)/4,n=(t.top.y+t.bottom.y+1)/2;if(e.get(Math.round(o),Math.round(n))){var r=[t.top.endX-t.top.startX,t.bottom.endX-t.bottom.startX,t.bottom.y-t.top.y+1],s=i(r)/r.length;return{score:l({x:Math.round(o),y:Math.round(n)},[1,1,3,1,1],e),x:o,y:n,size:s}}})).filter((function(e){return!!e})).sort((function(e,t){return e.score-t.score})).map((function(e,t,o){if(t>4)return null;var n=o.filter((function(e,o){return t!==o})).map((function(t){return{x:t.x,y:t.y,score:t.score+Math.pow(t.size-e.size,2)/e.size,size:t.size}})).sort((function(e,t){return e.score-t.score}));if(n.length<2)return null;var i=e.score+n[0].score+n[1].score;return{points:[e].concat(n.slice(0,2)),score:i}})).filter((function(e){return!!e})).sort((function(e,t){return e.score-t.score}));if(0===h.length)return null;var p=function(e,t,o){var i,r,s,a,l,c,d,u=n(e,t),h=n(t,o),p=n(e,o);return h>=u&&h>=p?(l=(i=[t,e,o])[0],c=i[1],d=i[2]):p>=h&&p>=u?(l=(r=[e,t,o])[0],c=r[1],d=r[2]):(l=(s=[e,o,t])[0],c=s[1],d=s[2]),(d.x-c.x)*(l.y-c.y)-(d.y-c.y)*(l.x-c.x)<0&&(l=(a=[d,l])[0],d=a[1]),{bottomLeft:l,topLeft:c,topRight:d}}(h[0].points[0],h[0].points[1],h[0].points[2]),f=p.topRight,m=p.topLeft,g=p.bottomLeft,v=d(e,r,f,m,g),k=[];v&&k.push({alignmentPattern:{x:v.alignmentPattern.x,y:v.alignmentPattern.y},bottomLeft:{x:g.x,y:g.y},dimension:v.dimension,topLeft:{x:m.x,y:m.y},topRight:{x:f.x,y:f.y}});var y=c(e,f),w=c(e,m),A=c(e,g),C=d(e,r,y,w,A);return C&&k.push({alignmentPattern:{x:C.alignmentPattern.x,y:C.alignmentPattern.y},bottomLeft:{x:A.x,y:A.y},topLeft:{x:w.x,y:w.y},topRight:{x:y.x,y:y.y},dimension:C.dimension}),0===k.length?null:k}}]).default},e.exports=o()})),jsQR$1=unwrapExports(jsQR),script$l={name:\"Scaner\",props:{useBackCamera:{type:Boolean,default:!0},stopOnScaned:{type:Boolean,default:!0},drawOnfound:{type:Boolean,default:!0},lineColor:{type:String,default:\"#03C03C\"},lineWidth:{type:Number,default:2},videoWidth:{type:Number,default:document.documentElement.clientWidth||document.body.clientWidth},videoHeight:{type:Number,default:document.documentElement.clientHeight-48||document.body.clientHeight-48},responsive:{type:Boolean,default:!1}},data:()=>({showPlay:!1,showBanner:!0,containerWidth:null,active:!1,timeIndex:null}),computed:{videoWH(){if(this.containerWidth){const e=this.containerWidth;return{width:e,height:.75*e}}return{width:this.videoWidth,height:this.videoHeight}}},watch:{active:{immediate:!0,handler(e){e||this.fullStop()}}},methods:{drawLine(e,t){this.canvas.beginPath(),this.canvas.moveTo(e.x,e.y),this.canvas.lineTo(t.x,t.y),this.canvas.lineWidth=this.lineWidth,this.canvas.strokeStyle=this.lineColor,this.canvas.stroke()},drawBox(e){this.drawOnfound&&(this.drawLine(e.topLeftCorner,e.topRightCorner),this.drawLine(e.topRightCorner,e.bottomRightCorner),this.drawLine(e.bottomRightCorner,e.bottomLeftCorner),this.drawLine(e.bottomLeftCorner,e.topLeftCorner))},tick(){if(this.$refs.video&&this.$refs.video.readyState===this.$refs.video.HAVE_ENOUGH_DATA){this.$refs.canvas.height=this.videoWH.height,this.$refs.canvas.width=this.videoWH.width,this.canvas.drawImage(this.$refs.video,0,0,this.$refs.canvas.width,this.$refs.canvas.height);const e=this.canvas.getImageData(0,0,this.$refs.canvas.width,this.$refs.canvas.height);let t=!1;try{t=jsQR$1(e.data,e.width,e.height)}catch(e){console.error(e)}t&&(this.drawBox(t.location),this.found(t.data))}this.run()},setup(){if(this.responsive&&this.$nextTick((()=>{this.containerWidth=this.$refs.scaner.clientWidth})),console.log(navigator.mediaDevices),navigator.mediaDevices&&navigator.mediaDevices.getUserMedia){this.previousCode=null,this.parity=0,this.active=!0,this.canvas=this.$refs.canvas.getContext(\"2d\");const e=this.useBackCamera?{exact:\"environment\"}:\"user\",t=e=>{void 0!==this.$refs.video.srcObject?this.$refs.video.srcObject=e:void 0!==window.videoEl.mozSrcObject?this.$refs.video.mozSrcObject=e:window.URL.createObjectURL?this.$refs.video.src=window.URL.createObjectURL(e):window.webkitURL?this.$refs.video.src=window.webkitURL.createObjectURL(e):this.$refs.video.src=e,this.$refs.video.playsInline=!0;const t=this.$refs.video.play();t.catch((()=>this.showPlay=!0)),t.then(this.run)};navigator.mediaDevices.getUserMedia({video:{facingMode:e}}).then(t).catch((()=>{navigator.mediaDevices.getUserMedia({video:!0}).then(t).catch((e=>{this.$emit(\"error-captured\",e)}))}))}},run(){this.active&&(this.timeIndex=requestAnimationFrame(this.tick))},found(e){this.previousCode!==e?this.previousCode=e:this.previousCode===e&&(this.parity+=1),this.parity>2&&(this.active=!this.stopOnScanned,this.parity=0,this.$emit(\"code-scanned\",e))},fullStop(){this.$refs.video&&this.$refs.video.srcObject&&this.$refs.video.srcObject.getTracks().forEach((e=>e.stop()))}},activated(){this.setup()},deactivated(){this.fullStop(),cancelAnimationFrame(this.timeIndex),this.timeIndex=null}};const _withScopeId$7=e=>(pushScopeId(\"data-v-9262226e\"),e=e(),popScopeId(),e),_hoisted_1$j={class:\"dokit-scaner\",ref:\"scaner\"},_hoisted_2$d={key:0,class:\"dokit-banner\"},_hoisted_3$c=_withScopeId$7((()=>createBaseVNode(\"p\",{class:\"dokit-text\"},\"若当前浏览器无法扫码，请切换其他浏览器尝试，目前支持的url:localhost、file和https\",-1))),_hoisted_4$b=createStaticVNode('<div class=\"dokit-cover\" data-v-9262226e><p class=\"dokit-line\" data-v-9262226e></p><span class=\"dokit-square top left\" data-v-9262226e></span><span class=\"dokit-square top right\" data-v-9262226e></span><span class=\"dokit-square bottom right\" data-v-9262226e></span><span class=\"dokit-square bottom left\" data-v-9262226e></span><p class=\"dokit-tips\" data-v-9262226e>将二维码放入框内，即可自动扫描</p></div>',1),_hoisted_5$9=[\"width\",\"height\"],_hoisted_6$9={ref:\"canvas\"};function render$l(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",_hoisted_1$j,[i.showBanner?(openBlock(),createElementBlock(\"div\",_hoisted_2$d,[createBaseVNode(\"i\",{class:\"dokit-close_icon\",onClick:t[0]||(t[0]=()=>i.showBanner=!1)}),_hoisted_3$c])):createCommentVNode(\"\",!0),_hoisted_4$b,withDirectives(createBaseVNode(\"video\",{class:\"dokit-source\",ref:\"video\",width:r.videoWH.width,height:r.videoWH.height,controls:\"\"},null,8,_hoisted_5$9),[[vShow,i.showPlay]]),withDirectives(createBaseVNode(\"canvas\",_hoisted_6$9,null,512),[[vShow,!i.showPlay]]),withDirectives(createBaseVNode(\"button\",{onClick:t[1]||(t[1]=(...e)=>r.run&&r.run(...e))},\"开始\",512),[[vShow,i.showPlay]])],512)}var css_248z$j='.dokit-scaner[data-v-9262226e] {\\n  background: #000000;\\n  position: fixed;\\n  top: 48px;\\n  left: 0;\\n  width: 100%;\\n  height: 100%;\\n  height: -webkit-calc(52%);\\n  height: -moz-calc(52%);\\n  height: -ms-calc(52%);\\n  height: -o-calc(52%);\\n  height: calc(100% - 48px);\\n}\\n.dokit-scaner[data-v-9262226e] .dokit-banner[data-v-9262226e] {\\n  width: 340px;\\n  position: absolute;\\n  top: 16px;\\n  left: 50%;\\n  margin-left: -170px;\\n  background: #fa74a2;\\n  border-radius: 8px;\\n  box-sizing: border-box;\\n  padding: 12px;\\n  padding-right: 39px;\\n  opacity: 0.9;\\n  box-shadow: 1px 1px 10px rgba(0, 0, 0, 0.2);\\n}\\n.dokit-scaner[data-v-9262226e] .dokit-banner[data-v-9262226e] .dokit-text[data-v-9262226e] {\\n  padding: 0;\\n  margin: 0;\\n  color: #ffffff;\\n  font-size: 12px;\\n  text-align: justify;\\n  text-align-last: left;\\n}\\n.dokit-scaner[data-v-9262226e] .dokit-banner[data-v-9262226e] .dokit-close_icon[data-v-9262226e] {\\n  display: inline-block;\\n  height: 24px;\\n  width: 24px;\\n  background: url(\"https://pt-starimg.didistatic.com/static/starimg/img/9by73wJnr31645431185877.png\") no-repeat center;\\n  background-size: auto 100%;\\n  position: absolute;\\n  right: 8px;\\n  top: 50%;\\n  transform: translateY(-50%);\\n}\\n.dokit-scaner[data-v-9262226e] .dokit-cover[data-v-9262226e] {\\n  height: 220px;\\n  width: 220px;\\n  position: absolute;\\n  top: 50%;\\n  left: 50%;\\n  -webkit-transform: translate(-50%, -50%);\\n  -moz-transform: translate(-50%, -50%);\\n  -ms-transform: translate(-50%, -50%);\\n  -o-transform: translate(-50%, -50%);\\n  transform: translate(-50%, -50%);\\n  border: 0.5px solid #999999;\\n  z-index: 1111;\\n}\\n.dokit-scaner[data-v-9262226e] .dokit-cover[data-v-9262226e] .dokit-line[data-v-9262226e] {\\n  width: 200px;\\n  height: 1px;\\n  margin-left: 10px;\\n  background: #5f68e8;\\n  background: linear-gradient(to right, transparent, #5f68e8, #0165ff, #5f68e8, transparent);\\n  position: absolute;\\n  -webkit-animation: scan-9262226e 1.75s infinite linear;\\n  -moz-animation: scan-9262226e 1.75s infinite linear;\\n  -ms-animation: scan-9262226e 1.75s infinite linear;\\n  -o-animation: scan-9262226e 1.75s infinite linear;\\n  animation: scan-9262226e 1.75s infinite linear;\\n  -webkit-animation-fill-mode: both;\\n  -moz-animation-fill-mode: both;\\n  -ms-animation-fill-mode: both;\\n  -o-animation-fill-mode: both;\\n  animation-fill-mode: both;\\n  border-radius: 1px;\\n}\\n.dokit-scaner[data-v-9262226e] .dokit-cover[data-v-9262226e] .dokit-square[data-v-9262226e] {\\n  display: inline-block;\\n  height: 20px;\\n  width: 20px;\\n  position: absolute;\\n}\\n.dokit-scaner[data-v-9262226e] .dokit-cover[data-v-9262226e] .top[data-v-9262226e] {\\n  top: 0;\\n  border-top: 1px solid #5f68e8;\\n}\\n.dokit-scaner[data-v-9262226e] .dokit-cover[data-v-9262226e] .left[data-v-9262226e] {\\n  left: 0;\\n  border-left: 1px solid #5f68e8;\\n}\\n.dokit-scaner[data-v-9262226e] .dokit-cover[data-v-9262226e] .bottom[data-v-9262226e] {\\n  bottom: 0;\\n  border-bottom: 1px solid #5f68e8;\\n}\\n.dokit-scaner[data-v-9262226e] .dokit-cover[data-v-9262226e] .right[data-v-9262226e] {\\n  right: 0;\\n  border-right: 1px solid #5f68e8;\\n}\\n.dokit-scaner[data-v-9262226e] .dokit-cover[data-v-9262226e] .dokit-tips[data-v-9262226e] {\\n  position: absolute;\\n  bottom: -48px;\\n  width: 100%;\\n  font-size: 14px;\\n  color: #ffffff;\\n  opacity: 0.8;\\n}\\n@-webkit-keyframes scan-9262226e {\\n  0% {\\n    top: 0;\\n  }\\n  25% {\\n    top: 50px;\\n  }\\n  50% {\\n    top: 100px;\\n  }\\n  75% {\\n    top: 150px;\\n  }\\n  100% {\\n    top: 200px;\\n  }\\n}\\n@-moz-keyframes scan-9262226e {\\n  0% {\\n    top: 0;\\n  }\\n  25% {\\n    top: 50px;\\n  }\\n  50% {\\n    top: 100px;\\n  }\\n  75% {\\n    top: 150px;\\n  }\\n  100% {\\n    top: 200px;\\n  }\\n}\\n@-o-keyframes scan-9262226e {\\n  0% {\\n    top: 0;\\n  }\\n  25% {\\n    top: 50px;\\n  }\\n  50% {\\n    top: 100px;\\n  }\\n  75% {\\n    top: 150px;\\n  }\\n  100% {\\n    top: 200px;\\n  }\\n}\\n@keyframes scan-9262226e {\\n  0% {\\n    top: 0;\\n  }\\n  25% {\\n    top: 50px;\\n  }\\n  50% {\\n    top: 100px;\\n  }\\n  75% {\\n    top: 150px;\\n  }\\n  100% {\\n    top: 200px;\\n  }\\n}\\n';styleInject(css_248z$j),script$l.render=render$l,script$l.__scopeId=\"data-v-9262226e\";var script$k={name:\"Scan\",components:{Scaner:script$l},data:()=>({errorMessage:\"\",scanned:\"\"}),methods:{codeScanned(e){this.scanned=e,$bus.emit(\"scanCode\",e),setTimeout((()=>{alert(`扫码解析成功: ${e}`)}),200)},errorCaptured(e){switch(e.name){case\"NotAllowedError\":this.errorMessage=\"Camera permission denied.\";break;case\"NotFoundError\":this.errorMessage=\"There is no connected camera.\";break;case\"NotSupportedError\":this.errorMessage=\"Seems like this page is served in non-secure context.\";break;case\"NotReadableError\":this.errorMessage=\"Couldn't access your camera. Is it already in use?\";break;case\"OverconstrainedError\":this.errorMessage=\"Constraints don't match any installed camera.\";break;default:this.errorMessage=\"UNKNOWN ERROR: \"+e.message}console.error(this.errorMessage),alert(\"相机调用失败\")}},mounted(){var e=navigator.userAgent.toLowerCase().match(/cpu iphone os (.*?) like mac os/);e&&e[1].replace(/_/g,\".\")<\"10.3.3\"&&alert(\"相机调用失败\")}};const _withScopeId$6=e=>(pushScopeId(\"data-v-2705d4fe\"),e=e(),popScopeId(),e),_hoisted_1$i={class:\"scan\"},_hoisted_2$c={class:\"nav\"},_hoisted_3$b=_withScopeId$6((()=>createBaseVNode(\"p\",{class:\"title\"},\"Scan QRcode\",-1))),_hoisted_4$a={class:\"scroll-container\"};function render$k(e,t,o,n,i,r){const s=resolveComponent(\"Scaner\");return openBlock(),createElementBlock(\"div\",_hoisted_1$i,[createBaseVNode(\"div\",_hoisted_2$c,[createBaseVNode(\"a\",{class:\"close\",onClick:t[0]||(t[0]=()=>e.$router.back())}),_hoisted_3$b]),createBaseVNode(\"div\",_hoisted_4$a,[createVNode(s,{onCodeScanned:r.codeScanned,onErrorCaptured:r.errorCaptured,\"stop-on-scanned\":!0,\"draw-on-found\":!0,responsive:!1},null,8,[\"onCodeScanned\",\"onErrorCaptured\"])])])}var css_248z$i='.scan[data-v-2705d4fe] {\\n  height: 100%;\\n  width: 100%;\\n}\\n.scan[data-v-2705d4fe] .nav[data-v-2705d4fe] {\\n  width: 100%;\\n  height: 48px;\\n  line-height: 48px;\\n  position: fixed;\\n  top: 0;\\n  left: 0;\\n  background-color: #ffffff;\\n}\\n.scan[data-v-2705d4fe] .nav[data-v-2705d4fe] .title[data-v-2705d4fe] {\\n  padding: 0;\\n  margin: 0;\\n  font-size: 16px;\\n  color: #000000;\\n  text-align: center;\\n}\\n.scan[data-v-2705d4fe] .nav[data-v-2705d4fe] .close[data-v-2705d4fe] {\\n  display: inline-block;\\n  height: 22px;\\n  width: 22px;\\n  background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAzCAMAAADIDVqJAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAXFQTFRFAAAANX7GNHzFM3zFM3zFNn3ENYDHNHzFM3zENIDHNH7INH3ENX3GNn7GM33ENYDFNYDGM4DINH7HM3zENH3HNHzENYDGM37FM33FNH3FNn3FNX7HN4DINHzEM33HNH7FNIDGNn7GM33ENX3FNXzHNoDENH7FNH3EM3zEN33INHzFNH3FM4DHNX3HNH3GNX7ENHzENHzFNYDFNH3FM33ENn3JM33GN4DINX7EM3zEM33GNH7FM33FM33FNIDFNH3GNX3KM37HNHzFN4DINX7FNH3EOHzHNH3GM3zFM4DGM33EM3zENHzENH3EM33FOoTFgID/NHzFOIDHM3zEM3zFNoPJM33FM33ENH3FNHzENYDL////NHzENH3FM4PFM3zFOYDGNH3FM3zFN3zINHzEM33FNoDJM3zFM33FNH3ENHzFM37FNH3ENH3FM33ENH7GNX7FM33ENH3FNH7FM37ENH7FM3zFNH3EM4jMM3zE////TdHL6gAAAHl0Uk5TAEPy+vA9RPb0QEX3P0fzPkg8Sfg7SjpL+fE5TThON082Ue81UjRT++4zVO0yVjFX/OwwWOsvWi5b6i1d/eksXitf6Cph5yli/ihk5ieh3x8CoiCg4CGf4Z3iIgGc4yOaJJnkJZjlJpaVk5KQj42LioiHhYSCgHOyD3AmCqsAAAABYktHRFt0vJU0AAAACXBIWXMAAABIAAAASABGyWs+AAABPUlEQVQ4y43UxVoCUBQE4Ctgd3cXKAYoKqioINiBrdjdXby9C+6c3ZzPWf+rE2MMS4bDmY4rk5qs7FQ6ObnU5OXDFFBTCFNUTE2JmFJqysqtcVRQUymmiprqGmtq66ipF9NATSOMq4maZidMCzWtMG3t1HSI6aSmq9sat4eaHpheLzV9YvqpGRi0xuenZghmOEDNiJhRasZggiFqxiesmQxTMyVmmpoZmEiUmlmYWJyauXmYBWoWYZaWqTEr1qRW1zhaT4jaUNQm1Na2onagdjW1B7V/wFXyEOroWFEnok4VdQZ1fqGoS6ira0XdpP4x+uStKL5EY+6gYpq6h4rEFfUgKqqoRyjlkI15ggqGFfUsKqSoFyjlTY15hfIFFPUmyq+odyilhIz5gHJ7FfUpyqOoLyilYo35/rFJ/Jo/DZ3bT7fEcIgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDQtMjFUMTc6MzI6MjgrMDg6MDBBnT5hAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA0LTIxVDE3OjMyOjI4KzA4OjAwMMCG3QAAAABJRU5ErkJggg==\") no-repeat center;\\n  background-size: auto 100%;\\n  position: absolute;\\n  left: 16px;\\n  top: 14px;\\n}\\n';styleInject(css_248z$i),script$k.render=render$k,script$k.__scopeId=\"data-v-2705d4fe\";var scanCode=new ce({nameZh:\"扫码\",name:\"scanCode\",icon:\"https://pt-starimg.didistatic.com/static/starimg/img/jvD7qcMXX51645432343946.png\",component:script$k}),script$j={data:()=>({recording:!1,socketUrl:\"\",historyList:[],testSocket:null}),watch:{socketHistoryList:{handler:function(e,t){this.historyList=[...e]},deep:!0,immediate:!0}},computed:{connect(){return this.$store.state.socketConnect},isHost(){return this.$store.state.isHost},socketHistoryList(){return this.$store.state.socketHistoryList}},created(){JSON.parse(localStorage.getItem(\"dokit-socket-history-list\")||\"[]\").forEach((e=>{this.connect&&e[0]===this.$store.state.socketUrl?(this.$store.state.socketHistoryList.set(e[0],\"connect\"),this.socketUrl=this.$store.state.socketUrl):this.$store.state.socketHistoryList.set(e[0],\"close\")})),$bus.on(\"scanCode\",this.scanCodeCallback)},destroyed(){$bus.off(\"scanCode\",this.scanCodeCallback)},methods:{recordHandle(){},connectHandle(e){if(this.$store.state.socketConnect)this.$store.state.socketConnect=!1;else if(e&&!/^(ws?s:\\/\\/)/.test(e)||this.socketUrl&&!/^(ws?s:\\/\\/)/.test(this.socketUrl))this.$toast(\"url地址格式不对\",1e3);else try{this.testSocket=new WebSocket(e||this.socketUrl),this.testSocket.addEventListener(\"error\",(e=>{this.$toast(\"url地址无法连接\",2e3),this.testSocket.close(),this.testSocket=null})),this.testSocket.addEventListener(\"open\",(t=>{e&&(this.socketUrl=e),this.$store.state.socketUrl=this.socketUrl,this.$nextTick((()=>{this.$store.state.socketConnect=!this.$store.state.socketConnect,this.$store.state.socketConnect&&(this.$store.state.showContainer=!1)})),this.testSocket.close(),this.testSocket=null}))}catch(e){return void this.$toast(\"url地址无法连接\",1e3)}},scanCode(){this.$router.push({name:\"scanCode\",params:{multiControl:!0}})},scanCodeCallback(e){this.$router.back(),this.socketUrl=e,this.connectHandle()},delHistory(e){this.$store.state.socketHistoryList.delete(e)},connectStateColor:e=>\"connect\"===e?\"#1afa29\":\"#d81e06\"}};const _withScopeId$5=e=>(pushScopeId(\"data-v-03351b1c\"),e=e(),popScopeId(),e),_hoisted_1$h={class:\"one-machine-with-multiple-controls-content\"},_hoisted_2$b={class:\"portal-textarea-container\"},_hoisted_3$a={class:\"portal-opt-area\"},_hoisted_4$9={class:\"portal-opt-area\"},_hoisted_5$8={class:\"portal-opt-area\"},_hoisted_6$8={class:\"history-record-container\"},_hoisted_7$6=_withScopeId$5((()=>createBaseVNode(\"div\",{class:\"history-record-title\"},\"历史记录\",-1))),_hoisted_8$5={key:0,class:\"history-record-list\"},_hoisted_9$5={t:\"1646903397439\",class:\"connectState\",viewBox:\"0 0 1024 1024\",version:\"1.1\",xmlns:\"http://www.w3.org/2000/svg\",\"p-id\":\"7559\",width:\"200\",height:\"200\"},_hoisted_10$4=[\"fill\"],_hoisted_11$3={class:\"name\"},_hoisted_12$3=[\"onClick\"],_hoisted_13$3=[\"onClick\"],_hoisted_14$3={key:1,class:\"history-record-list-empty\"};function render$j(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",_hoisted_1$h,[createBaseVNode(\"div\",_hoisted_2$b,[withDirectives(createBaseVNode(\"textarea\",{class:\"portal-textarea\",rows:\"5\",\"onUpdate:modelValue\":t[0]||(t[0]=e=>i.socketUrl=e),placeholder:\"请输入联网地址\"},null,512),[[vModelText,i.socketUrl]]),createBaseVNode(\"div\",_hoisted_3$a,[createBaseVNode(\"div\",{class:\"opt-btn\",onClick:t[1]||(t[1]=(...e)=>r.scanCode&&r.scanCode(...e))},\"扫码\")]),createBaseVNode(\"div\",_hoisted_4$9,[createBaseVNode(\"div\",{class:\"opt-btn\",onClick:t[2]||(t[2]=e=>r.connectHandle())},toDisplayString(r.connect?\"断开联网\":\"联网\"),1)]),createBaseVNode(\"div\",_hoisted_5$8,[createBaseVNode(\"div\",{class:\"opt-btn\",onClick:t[3]||(t[3]=(...e)=>r.recordHandle&&r.recordHandle(...e))},toDisplayString(i.recording?\"暂停录制\":\"开始录制\"),1)])]),createBaseVNode(\"div\",_hoisted_6$8,[_hoisted_7$6,i.historyList.length>0?(openBlock(),createElementBlock(\"div\",_hoisted_8$5,[(openBlock(!0),createElementBlock(Fragment,null,renderList(i.historyList,((e,t)=>(openBlock(),createElementBlock(\"div\",{class:\"history-record-list-item\",key:t},[(openBlock(),createElementBlock(\"svg\",_hoisted_9$5,[createBaseVNode(\"path\",{d:\"M969.586403 624.769747l-129.946199-130.564991c-34.911813-35.071501-77.458713-52.607251-127.401169-52.607251-50.830721 0-94.146121 18.30425-129.946199 55.172242l-54.912749-55.172242c36.568577-36.089513 54.912749-79.74425 54.912749-131.203743 0-50.172008-17.32616-92.678986-51.848733-127.361247L401.885195 53.246004C367.362622 17.795244 324.815721 0 274.484025 0c-49.942456 0-92.229864 17.406004-126.762417 52.098246l-91.850604 91.521247c-35.291072 34.682261-52.996491 77.179259-52.996492 127.351267 0 50.172008 17.455906 92.928499 52.357739 128l129.946199 130.684757c34.901832 35.071501 77.458713 52.617232 127.40117 52.617232 50.830721 0 94.156101-18.30425 129.946198-55.172242l54.912749 55.172242c-36.688343 36.089513-54.912749 79.74425-54.912749 131.203742 0 50.172008 17.32616 92.669006 51.858714 127.361248l128.538947 129.916257c34.522573 35.460741 76.949708 53.246004 127.40117 53.246004 49.942456 0 92.239844-17.406004 126.762417-52.227992l91.850604-91.521248c35.291072-34.692242 52.996491-77.179259 52.996492-127.361247 0.019961-50.042261-17.435945-92.798752-52.347759-128.119766zM460.790146 365.556023c-1.626823-1.337388-5.90846-5.938402-13.453723-13.813021-7.395556-7.575205-12.555478-12.924756-15.519688-15.599532-2.95423-2.525068-7.545263-6.237817-13.753139-10.838831-6.357583-4.601014-12.41575-7.724912-18.473918-9.511423-6.058168-1.77653-12.715166-2.664795-19.961014-2.664795-19.212476 0-35.620429 6.676959-49.223859 20.350253-13.603431 13.513606-20.250448 30.001404-20.250449 49.453412 0 7.28577 1.027992 13.97271 2.664796 20.050838 1.626823 5.938402 4.870487 12.325926 9.45154 18.563743 4.581053 6.237817 8.283821 10.998519 10.788928 13.813021 2.515088 2.824483 7.68499 8.024327 15.529668 15.589552 7.68499 7.575205 12.276023 12.026511 13.743158 13.513606-14.481715 15.010682-31.917661 22.585887-52.17809 22.585887-19.801326 0-36.069552-6.387524-49.223859-19.601715L110.145 315.952904C96.541569 302.429318 89.894551 285.951501 89.894551 266.489513c0-19.012865 6.647018-35.201248 20.250449-48.864562l106.591813-106.3423c14.042573-13.074464 30.300819-19.611696 49.223859-19.611696 19.222456 0 35.630409 6.68694 49.22386 20.350254l149.168655 150.755555c13.59345 13.513606 20.250448 30.001404 20.250448 49.463392-0.009981 20.350253-7.844678 38.165458-23.813489 53.315867z m455.849669 443.334113L809.62882 915.511891c-13.503626 12.824951-29.981442 19.082729-49.423469 19.082729-19.89115 0-36.209279-6.417466-49.42347-19.68156L561.034356 763.708382c-13.663314-13.573489-20.330292-30.12117-20.330292-49.653021 0-20.430097 8.014347-38.325146 24.043041-53.385731 1.626823 1.347368 6.078129 6.118051 13.503626 13.872904 7.425497 7.754854 12.615361 13.124366 15.589551 15.649435 2.964211 2.535049 7.565224 6.257778 13.793061 10.888733 6.387524 4.620975 12.475634 7.904561 18.553762 9.541364 6.088109 1.936218 12.765068 2.684756 20.040858 2.684757 19.29232 0 35.770136-6.716881 49.423469-20.430098 13.653333-13.573489 20.330292-30.12117 20.330293-49.653021 0-7.305731-1.037973-14.012632-2.674776-20.130683-1.626823-5.958363-4.900429-12.375828-9.501442-18.643586-4.601014-6.257778-8.164055-10.888733-10.838831-13.862924-2.525068-2.844444-7.714932-8.054269-15.579571-15.659415-7.714932-7.605146-12.315945-12.076413-13.803041-13.563509 14.541598-15.509708 32.057388-23.414269 52.397661-23.414269 19.29232 0 35.760156 6.716881 49.413489 20.430097l151.384328 152.102924c13.653333 13.563509 20.330292 30.111189 20.330292 49.653022 0.009981 19.082729-6.816686 35.490682-20.470019 48.754775z\",\"p-id\":\"7560\",fill:r.connectStateColor(e[1])},null,8,_hoisted_10$4)])),createBaseVNode(\"div\",_hoisted_11$3,toDisplayString(e[0]),1),createBaseVNode(\"div\",{class:\"opt-btn\",style:normalizeStyle(\"background-color:\"+(\"connect\"===e[1]?\"#d81e06\":\"#28af31\")),onClick:t=>r.connectHandle(e[0])},toDisplayString(\"connect\"===e[1]?\"断开连接\":\"连接\"),13,_hoisted_12$3),createBaseVNode(\"img\",{class:\"delBtn\",onClick:t=>r.delHistory(e[0]),src:\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAAGl1JREFUeNrtnXtwVNeZ4H9HEg8JCYQkY8xLPHWbh2PAjl+AXzDenZrM1OxkQiubWjsbvLXZTGVeroHmj63a2tktNcw4M1OZ8nq9djZOzUzUTqamau2qZGyMk2BjHMc22Dz6ykZGYGwshAAhhBCSvv3j3gZZ1qMf5/a53X1+VV2y2tK532nuT9/tvud8n8ISCCJSDjhAk/+oB6qBmlFfRz8HcAnoHfG1d4znzgFtgAu0KaWGTM+5GFGmAyh0RGQhNyQYKcSyPIdynBHCpB5KqVOmX6NCxgqSISLSBNwH/Jb/mG06pknoBl72H79SSn1gOqBCwgoyCSLSCNwLbMATosl0TDni4smyH9ivlOowHVCYsYKMgYg8DPwbPDHuNh1PwBzAk+XnSqmXTQcTNqwgPiJyL/AV/3Gr6XgM8T7wIvCiUmq/6WDCQEkLIiK3cUOKYs8UmXIAeAFPlvdMB2OKkhNEROYDf4gnxRbT8RQIe/Ayy0+UUp+YDiaflIwgIrIYeAzYBsw1HU+BcgZ4BnhWKXXCdDD5oOgF8T+WTYlRZzqeIqGbG6K0mQ4mSIpWEBFZww0xqk3HU6T04onyjFLqiOlggqDoBBGRdXhiPAZMNR1PiTDADVHeNR2MTopGEBGZDezwHxZz7AJ2KaXOmw5EB0UhiIg8gifGKtOxWAA4iifJj0wHkisFLYiIfBnYjvexrSV8/BTYrZR6y3Qg2VKQgohINZ4YO7DvM8LOAN5l126lVK/pYDKl4AQRka/jybHWdCyWjDiIJ8mPTQeSCQUjiIjUALuBb5uOxZITTwHblVKXTAeSDgUhiIhswpPDrpcqDg7gSbLPdCCTEXpBROS7eHJMNx2LRSv9eJJ833QgExFaQUSkHk+Mb5mOxRIoP8AT5ZzpQMaizHQAYyEim4GXsHKUAt8C/tX/Nw8docsgIvI4XuYIpbyWwBgCdiilnjAdyEhCJYiIPAH8uek4LEb5nlLqcdNBpAiNICLyQ+BR03FYQsFzSqlvmg4CQiKIiLyAt8PPYknxolLqd00HYVwQEdkP3GM6Dkso2a+U2mAyAKOCiIhL4deZsgSLq5SKmDq4MUFEpAuvXq3FMhldSqmbTBzYiCAiIiaOayloRCmV94/+835AETmZ72NaigIlInkvk5pXQUTkdWBhvidpKRoWichr+Txg3gQRkVa8WrcWSy5sEJG87SnJiyAi8rdANF+TshQ9zSLyN/k4UOCCiMhu4E/yMRlLSfGnIrIr6IMEKoiI/CXwF0FPwlKybPfPscAI7GNef1XuXwcZvMXi87hS6ntBDByIICP2c9gl65Z8MAQ8rJTaq3tg7YL4OwFfAtbn4YWxWFK8jSdJt85Bg/gLvxsrhyX/3I537mlFqyB+gQW7TdZiim3+OagNbZdYfmmel7DVRyxm6ce71NJSUkiLIH5Rt5ewdass4eAAniQ5F6fTdYlli7pZwsTdaHo/knMG8Wvl/pPpV8RiGYOvK6VacxkgJ0H8Kuv7sIWkLeHkILBRKXU52wFyvcSyVdYtYWYtOXYcyzqD+M1rXsP257CEmwG8LJJVE59cMohtXmMpBKaSQxbJShAReRT4qumZWyxp8lW/j2XGZHyJJSJ1eG/MQ90w82eHzvJW+0U6uq7Q0XWF8jJYMXcGixsq2by6gXWLZ5oOsaB450QPLx/uor2zj/bOPgCWzqmisaGSNQuq+b31N5sOcTKO4l1qZdR9NxtB4oS81fLTe0/y9Kunxv3/FeWK72xu5JFN802HWhA8t+80T+7pYGh4/GI090fqeOIbK02HOhm7lFKxTH4hI0FEZD3eqsnQcsd/fT3tn92ypoGWqGO+vGRIEYFYwuWVI11p/85v/tJoIcR0WK+UejfdH870Pcg207ObiMf/8VhGP7/ncBex1iTDtkrXFxgWIZZIZiQHMGHmDgmPZfLDaQsiImsyHTyf/L93PuOXycy3Arxy5Byx1uSElw+lxtCwEGt1eeVI5k2fnt57kp8fOmt6ChPxmIisTveHM8kg2wjxx7qHP86+Bffeo+eIJVwGrSQMDnly7D2afUe0t9ovmp7GREwlgz/0aQkiIk2ZDGqCjq4rOf3+q0fPEWt1GRwqXUmuDXmXVa8ey61d4Ikc/y3ywGP+OT0p6WaQbUC16VlNROqjx1z4xbFz7EgkuTY0bHo6eWdgcJhYa5JfHMt9x2pHV+7/FgFTTZrvpycVREQWE/LsAVCm6aOoXx7rZkery8Bg6UhydXCYWMLN6j3cWChVEJ8LbvPP7QlJJ4M8BtSZns1krJg7Q9tYv0p6kly9VvyS9F8bJtbq8itNcoB3A7EAqCeNLDKhICIyL51BwkBjQ6XW8fa53cQSLv1FLMmVgWFiiST7XK2FQApFEPCyyLyJfmCyDPI1YK7pWaTDltUNVOi6zvLZ53YTa01yZWDI9PS00zcwRCyR5DU3o5UXk1JRrnh4TYPp6aXLLXjn+LhMJkjBNNZct3gm/2VLo/ZxX2s7Tyzh0ldEkvRdHSLW6vJ6m145AL6zpbHQ1rlNeI6PK4iI3AZsMR19Jjy6aT5bAvjr9XrbeWKtLpevFr4kl68OsSPhsv8D/XL81poGHtlYcOvbtvjn+phMlEEKJnuMpCXqsHm1/taH+z84TyyRpLe/cCXp7R9kR2uSNwKQY8tqb11bgTLuuV50giigJRrhoVX6JXnjgwvEEkku9Q+anmbGXLoyyI6Ey4EPL2gfe/Pq+kKWA+B3xvsfYwoiIhso4DI+ZcrLJA8GIMmBDy8Qa3XpuVI4kvRcGSSWcHkzADkeWuXJURi3PsblHhEZs/vZeBmkILPHSMrLFC1RhwdW6pfkzeMXiCVcLhaAJBf7Bom1Jnnz+AXtYz/oy1FW4Hb4jHnOF60gABVlinjU4f6V+u9z/vr4BWKtSS70XTM9zXG50HeNWCLJrwNYPPjAynpatjqUa/5o3SDpCSIiDwNrTEeri4pyT5L7Ivoleav9IrGEy/nL4ZPk/OVrxFrdQFbW3h+poyXqUFFeNHIA3Oqf+59jrAzy26Yj1c2U8jLiUYdNjn5JfuNL0h0iSbovXyOWcPnNR/rluM+XY0pxyZHi345+YixBNpqOMgimVniSbAxAkrc/ukis1eVcr3lJzvVeI9aa5O0A5NjkeHJMrSjaxmFfOPc/92dARJYDH5iOMkj6rw2zozUZyF3kdYtnEo861Feb2VfWdWmAnc+7vHuiR/vYG53ZxKMRpk8pWjlSLFdKHU99M3q2D5iOLmimT/EyyYam2drHfvdED7FWl65LA3mf19lLA8QSwcixoWk2LVtLQg4Y5cDoGW8yHV0+qJxaTkvU4d4VAUjS0UMs4XK2J3+SdPYMEGt1OdihX457V8ymJepQObUk5IBRDpSkIABVviT3LK/VPvbBjh5iiSSdeZCks+cqsUSSQyf1y3GPL0fV1PLA5xEixhZERFYBS0xHl09mTCunJRrh7gAkOXTyErHWJJ9dvBpY/J9dvMqOVpf3TubcSOkL3L28lnjUYca0kpIDYKnvAvD5DPKQ6chMUD3dyyR3LavVPvZ7py4RS7icuaBfkjMXrhJLuLx/Sr8cd5WuHCkeTP1H2VhPlho10ytoiTrcGYAk7/uSfKpRkk8DlOPOZbXEtzpUT6/QPnYBcd2F6x/zish5oNZ0ZCa52DdILJEM5O7z6gXVtGx1mDc7tybAn5zvZ2fC5cjp7OuAjcedS2uJNzvMrCxpOQDOK6XqwM8gIrKQEpcDYFaVl0nuWDJL+9hHPu4llnA5fb4/6zFOn+8nFpAcX146i5aolcNntu/E9UustIpolQK1VVNoiTrcHoAkR0/3Emt1+bg7c0k+7u4n1upyNAA57ljiyTGrysoxgiawgozJ7BmeJOsX65fk2CdeJjl1Ln1JTp3zMsexT/TLcbsvR23VFO1jFzhWkImo8yUJogBB8pNeYokkJ89NXqLz5LkrxBJJkgHIsX7xTFq2OsyeYeUYg88JUtD7JYOivnoKLVsd1jbql8T99DKxhDthTeGOrivEWl3cT7PuYjwu6xpn0hKNUFdt5RgHm0HSoaFmKvGow22L9EvS5ksyVrHnE11XiCVc2s7ol2Nt40xaog71Vo6JcACUiJQD4d87apizPQPsSCQDuWu9/OYqWqIRltzkVYf86OwVdiZcPvxMvxy3LZpJvNnhpprQdrIIExVKRFbiNTi0TEJnzwA7WpOB3KBbdnMVLVu9K92dz7sc/0x/hfQvLaohHo0wZ6aVI01WKRH5feBfTEdSKHx28So7Ei6Hg5BkThWCnlYOo7l1YQ3xqMPNs6bl4VUqGv5dGfb9R0bcPGsa8a0OaxbUaB/7+IgWyzpZs6CGFitHNjSV4ZWBt2TA3NpptEQdVs8PdU8hwF/iEnWYa+XIhvoyQt45Kqzc4kuyKsSSrJpfTUs0wi21Vo4sqS4D9F8rlAjzZk+nJeqwcl74JFk1v5p41GGelSMXamwGyZH5viSREEmycp6elcMWm0G0sKBuOi1bHZxb9LWBy5bILTNoiTrMr7NyaMBmEF0srPcySZPGXomZ4twyg5ZohAVWDl1UW0E0sqi+kpaoo7WhaLo0zfUyx8J6K4dG7CWWbhobPEmW35y/RpYrfDkW1ettZGqxl1iBsLihkpZohGV5kGS5v0RFd5dfCwDVSkSuAnZxTgC0d/YRS7iB3B0Hb/1WPOqw5KaCabtcaAyUTLm8okRMB1D8lAH6V91ZvCXrzweXPcBbuxVrdfnobHDHKHEulQH693KWOCe6rrAzkQxkyfporksSoIglTK/NIJrp6EptdsrfCXs84Pc6JYzNIDo5ec6T44MAtslOxvHOPnZaSXTTawXRxKlzXsXDIPaQp4vNJNq5ZC+xNPBxdz87nw+m+kimpD5aPnF28pJClkmxGSRXTvu1coOoW5Ut7Z19bG9NciqNuluWCbEZJBdShaSDqHiYK+2dfTz+T8mcagFbbAbJmk8vXGVnIphaubpo7+zjz/7hGGcCbOJT5FwqA86ZjqLQOOPLEUSVdd20d/bxxz86mteeiUVEdxnQZjqKQuKzi1eJPe9y+ONgyv4snaN/XVV7Zx9/9NwRukPQx73AaCsDXNNRFAqdPV6r5UBqYt1cRUvUIR51AlkF3N7Zx7f/72Eu9tkimhng2tKjaVIspUeXzqni2f90KzWl3WItXSrKlFJDwHHTkYSZrkte5ghCjhVzZxBvviEHwJKbKok3B7Mzsb2zj23/5336BoYCfc2KgONKqaHUcnf7PmQcUnIE0Ye86ZYZxKMOi8fY7LS4oZJ4QHvc2zv7+Ob/fo+rg8OBvGZFggs32h/Y9yFjcK73GjufdznYoV8Ox5djop2AjQ1eJgmiWkp7Zx//4X8dYnDIbioZhza4IYjNIKPovnyNnQmXd0/olyMyr5p4NJLWHvJF9ZXEo5FA6m61d/bx7588iFhHxsIKMh7nfTneOaG/HfTKeV7Fw0yqjyysn048oAqO7Z19RP/+Xe3jFgFWkLG40OfJ8fZH+uVYNb+aeLOTVd2qBXXTiTcHUwu4vbOPrd+3koyiDUClvhORbmC26ahMcrFvkFgiyVvt+uVYvUBPOdDU+q8g7uIvnVPF899dp33cAqRbKVUPNzIIwMumozJJz5VBdj7vBiLHmgVeZycdtXLnzZ5OvDkSSH8Sm0muc90FKwhwqX+QnQmXXx+/oH3sVGcnnS0IbqmdRjzqcOtCK0lAjCnIr0xHZYLe/iF2JlzeDECOL/lyzA2gBcFcK0mQXHdBjXxWRFxKqCXb5atDxFqTvPHhBe1j37aohpY8NMzs7Lka2F3+En1P4iqlIqlvRheOK5nLrL4BL3MEIcfaxpl56yY7Z+Y04tFIIH3cSzST7Bn5zWhB9puOLh9c8eXY/8F57WOva5xJPOpwUx5bLc+ZOZV4s8PaRiuJBl4f+U3JCdJ/bZhYwuX1tgDkWDyTeLNDQ03+Sx3fVDOVeNRh3WIrSY58zoHPCaKUOgG8aTrCoLgaoBzrF88iHo1QX22uDnhDzVTi0QjrrSTZckAp1THyibGKVxdlFhkY9OR4ze3WPvbtS2YRb3aor55ieprUV08h3hzh9iWztI9dApJ84dwfS5Cfm45SN9eGPDn2BSDHHUtnEY861M0wL0eKuhlTiEcd7rCSZMoXzn011k+JyPvAGtPR6mBwSNiRSPLLY/rl+PLSWbREHWqrwiPHSFLryoJYHbBi7gx+/EdrTU9RJ+8rpb40+snx+oO8aDpaHQwOC7GEG4gcdy6rJd4cCa0cALVVU4hHI9y5VH8m+eDMZb7x5EHTU9TJmOd80QoyNCzsTLj84pj+qkZ3LaslHnWYVRn+fd2zqiqIN0e4a1mt9rHdTy/zyFOHTE9RF+kLopR6HThgOuJsGRbYmXB59ah+Oe5eXku82WFmAciRYmZlBfGow13La7WPffR0L//x6fdMTzFX3lBKjfnh1EQt2AoyiwiwM5FkbwBy3LOilng0UpAVQWoqK9gVdbg7AEneP3WJx5553/QUc2Hcc73oBNmZcHnliH457l0xm3g0QvX0ctNTzJrq6RXsao5wzwr9234OdvTwn39w2PQUsyVzQZRSh4BXTEeeCT/ad5o9h7u0j7uhaTbxZocZ0wpXjhQzppWzK+pwbwCSvP3RRf7mZx+ZnmKm7FFKjXuNOFmX2xdMR58u757o4ck9HbkPNIqNTbOJRx2qpha+HCmqppUTb3bY0KRfkn/c/wlPv3rK9BQzYcIrpckE+QlwxvQM0mHPkS4Gh/WW59jk1BFvjlBZRHKkqJpaTjwaYaOjX5Kn954slC5XZ/DO8XGZUBCl1CfAs6ZnkQ4dXXqbxWxy6ohHHaZPKd5W8pVTy4hHI2xy6rSPfTCAQnsB8Kx/jo9LOv/6zwD677RpRmfjzPsidexqdphWxHKkmD6ljHizw30RvZIc6gh9X6ZuvHN7QiY9A/wVvqHPIrquru5f6ckxtaL45UgxraKMeNThfo2SSPir0T3rn9sTku5Z8Awh70Slo6/GAyvr2RWNMKW8dORIMbWijHhzhAdW6pFkdQBVVzTSSxrZA9IURCnVRsizSGMaZTwn4sFV9cSbHSrKVU7jFDJTyhXxaIQHV9bnPFYQZYk08qx/Tk9KJn8qnwFC28drzcLsKw4+tKqeeNShoqx05UhRUa6INzs8tCp7SR5YWc/qBforQGpigDSzB2QgiFLqcCYD55vfW39zVtfQm1fXE2+OUG7luE55mSfJ5tWZS7KucSb/7Q+Wm57CRDzjn8tpkenFdqgvs574xsqMfn7LmgbizRGsG1+kTHmXW5tXN6T9O01zZ/Df/7CJ6nCvVcvoj3zGp4aIxIEdpmc5EU/vPTnh3dyKcsV3NjfyyKb5pkMtCJ7bd5on93QwNMFHhV+9cy7f2dzIrKpQy7FLKRXL5BeyEaQO2AesMj3bifjZobO81X6Rjq4rdHRdobzM2wW3uKGSzasbAqn+Ucy8c6KHlw930d7Zd/0u+dI5VTQ2VHLbohp+Z+0c0yFOxlFgo1Iqo4odWV1ciMijwA9Nz9hiyYBHlVI/yvSXsr76FpGfAl81PWuLJQ1+qpT6Wja/mIsgXwZeA8wVgrJYJmcA79LqrWx+Oetbxv4Bd5mevcUyCbuylQNyyCAAIlKN94Z9relXwWIZg4N42SPrlaw5LTpSSvUCu02/ChbLOOzKRQ7IURAApdSPgadMvxIWyyieUkq15jqIlnvIIlIDvATcbfpVsVjwSlY9rJTKeVOKtkUWIrIJT5LcO1VaLNnTjyfHPh2Dadv44Ae03dSrYrH4bNclB2jMIClE5FngW3l9SSwWjx8opbbpHDAIQerxLrXW5+tVsViAd/AurbRWDQxkobeIbMaTpPT2rlpMMIwnh/ZCh4GcwH6g9v2IJV9sD0IOCCiDpBCR7wF/FuQxLCXP95RSjwc1eOB76UTkh8CjQR/HUpI8p5T6ZpAHyMtmUxF5AfhKPo5lKRleVEr9btAHydtubBHZD9yTr+NZipo3lFL35uNAeS1XICIu0JTPY1qKjjallJOvg+W9noeIdAG5VyazlCJdSqmb8nlAIwVvpAAKt1pChyil8n5fzciNPKWUAgqqy4rFKCdNyAEG73QrpRYB+3MeyFLsvK6UajR1cKNLQZRSG4CEyRgsoaZVKbXRZADG10oppZqBvzMdhyV0/J1S6uumgzAuCIBS6k+BvzIdhyU0/JV/ThgnFIIAKKW2A//TdBwW4/wP/1wIBaGray4ij+NVSgmNvJa8MIy3KvcJ04GMJHSCwPX9JLuxm65KhXcIcMl6LoRSELi+M3E3dvtusfMDPDm07gTURWgFSSEi38UTxVZLKS768cT4vulAJiL0gsD1kkK7sXW3ioUDaK4+EhQFIQhcL063G/i26VgsOfEUnhw5F3XLBwUjSAoR+Trefve1pmOxZMRBYLdfqrZgKDhB4HpV+e14vRJtf5JwM4DXJmO3X+y8oChIQVL4TXx2YDtdhZV/Jsf+HKYpaEFS+D0TtxPyxqIlxFG8jPGc6UBypSgEgevdd1OXXRZzpC6nuk0HooOiESSFiKwHtgGPYd+f5IsB4BngWaXUO6aD0UnRCZJCRNbgSbINqDYdT5HSyw0xDpsOJgiKVpAUItLEDVHqTMdTJHRzQ4w208EESdELkkJEFnNDlLmm4ylQznBDjBOmg8kHJSNIChGZD3wNr9LjZtPxFAh7gBeBnyilPjEdTD4pOUFGIiK34YnyFew6r9EcwJPiRaXUIdPBmKKkBRmJiGzghixrTMdjiMPAC3hS2IozWEHGREQeBn4b2AjcYTqegPkNsA/4uVLqJdPBhA0ryCSIyHLgAWCT/1hiOqYcaQdew5PiF0qpD00HFGasIBkiIquBB/3HQ0Ct6Zgm4Tzwqv/Yq5Q6ajqgQsIKkiMishCvYn0T4Iz472V5DuU40Aa4/tc2vErotsRrDlhBAkJEyvm8MPV4d/RrRn0d/RzAJby71KmvvWM8d44RQiilhkzPuRj5/2omzr+oqKWjAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTA2LTI1VDE0OjE5OjU3KzA4OjAwANMjHwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wNi0yNVQxNDoxOTo1NyswODowMHGOm6MAAAAASUVORK5CYII=\",alt:\"\",srcset:\"\"},null,8,_hoisted_13$3)])))),128))])):(openBlock(),createElementBlock(\"div\",_hoisted_14$3,\"暂无\"))])])}var css_248z$h=\".one-machine-with-multiple-controls-content[data-v-03351b1c] {\\n  padding: 5px;\\n}\\n.one-machine-with-multiple-controls-content[data-v-03351b1c] textarea[data-v-03351b1c] {\\n  font-size: 13px;\\n}\\n.one-machine-with-multiple-controls-content[data-v-03351b1c] .portal-textarea-container[data-v-03351b1c] .portal-textarea[data-v-03351b1c] {\\n  font-size: 13px;\\n  border-radius: 5px;\\n  box-sizing: border-box;\\n  width: 100%;\\n  border: 1px solid #d6e4ef;\\n  resize: vertical;\\n}\\n.one-machine-with-multiple-controls-content[data-v-03351b1c] .portal-textarea-container[data-v-03351b1c] .portal-opt-area[data-v-03351b1c] {\\n  margin-top: 5px;\\n  height: 32px;\\n  line-height: 32px;\\n  display: flex;\\n  justify-content: space-between;\\n  align-items: center;\\n}\\n.one-machine-with-multiple-controls-content[data-v-03351b1c] .portal-textarea-container[data-v-03351b1c] .portal-opt-area[data-v-03351b1c] .opt-btn[data-v-03351b1c] {\\n  background-color: #337cc4;\\n  border-radius: 5px;\\n  font-size: 16px;\\n  width: 100%;\\n  text-align: center;\\n  color: #fff;\\n}\\n.one-machine-with-multiple-controls-content[data-v-03351b1c] .history-record-container[data-v-03351b1c] {\\n  margin-top: 20px;\\n  padding-top: 20px;\\n  border-top: 1px solid #d6e4ef;\\n}\\n.one-machine-with-multiple-controls-content[data-v-03351b1c] .history-record-container[data-v-03351b1c] .history-record-title[data-v-03351b1c] {\\n  text-align: center;\\n  font-size: 18px;\\n  color: #2c405a;\\n  margin-bottom: 20px;\\n}\\n.one-machine-with-multiple-controls-content[data-v-03351b1c] .history-record-container[data-v-03351b1c] .history-record-list-item[data-v-03351b1c] {\\n  margin-top: 5px;\\n  background-color: #337cc4;\\n  border-radius: 5px;\\n  color: #fff;\\n  padding: 5px;\\n  display: flex;\\n  align-items: center;\\n}\\n.one-machine-with-multiple-controls-content[data-v-03351b1c] .history-record-container[data-v-03351b1c] .history-record-list-item[data-v-03351b1c] .delBtn[data-v-03351b1c] {\\n  width: 15px;\\n  height: 15px;\\n}\\n.one-machine-with-multiple-controls-content[data-v-03351b1c] .history-record-container[data-v-03351b1c] .history-record-list-item[data-v-03351b1c] .name[data-v-03351b1c] {\\n  word-break: break-all;\\n  margin-right: 5px;\\n  font-size: 15px;\\n}\\n.one-machine-with-multiple-controls-content[data-v-03351b1c] .history-record-container[data-v-03351b1c] .history-record-list-item[data-v-03351b1c] .connectState[data-v-03351b1c] {\\n  width: 20px;\\n  height: 20px;\\n  margin-right: 7px;\\n}\\n.one-machine-with-multiple-controls-content[data-v-03351b1c] .history-record-container[data-v-03351b1c] .history-record-list-item[data-v-03351b1c] .opt-btn[data-v-03351b1c] {\\n  border-radius: 5px;\\n  font-size: 16px;\\n  text-align: center;\\n  color: #fff;\\n  white-space: nowrap;\\n  padding: 2px 15px;\\n  margin-right: 5px;\\n}\\n.one-machine-with-multiple-controls-content[data-v-03351b1c] .history-record-container[data-v-03351b1c] .history-record-list-empty[data-v-03351b1c] {\\n  display: flex;\\n  align-items: center;\\n  justify-content: center;\\n  margin-top: 30px;\\n  font-size: 17px;\\n}\\n\";styleInject(css_248z$h),script$j.render=render$j,script$j.__scopeId=\"data-v-03351b1c\";var moment=createCommonjsModule((function(e,t){e.exports=function(){var t,o;function n(){return t.apply(null,arguments)}function i(e){t=e}function r(e){return e instanceof Array||\"[object Array]\"===Object.prototype.toString.call(e)}function s(e){return null!=e&&\"[object Object]\"===Object.prototype.toString.call(e)}function a(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function l(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;var t;for(t in e)if(a(e,t))return!1;return!0}function c(e){return void 0===e}function d(e){return\"number\"==typeof e||\"[object Number]\"===Object.prototype.toString.call(e)}function u(e){return e instanceof Date||\"[object Date]\"===Object.prototype.toString.call(e)}function h(e,t){var o,n=[];for(o=0;o<e.length;++o)n.push(t(e[o],o));return n}function p(e,t){for(var o in t)a(t,o)&&(e[o]=t[o]);return a(t,\"toString\")&&(e.toString=t.toString),a(t,\"valueOf\")&&(e.valueOf=t.valueOf),e}function f(e,t,o,n){return Ko(e,t,o,n,!0).utc()}function m(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidEra:null,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],era:null,meridiem:null,rfc2822:!1,weekdayMismatch:!1}}function g(e){return null==e._pf&&(e._pf=m()),e._pf}function v(e){if(null==e._isValid){var t=g(e),n=o.call(t.parsedDateParts,(function(e){return null!=e})),i=!isNaN(e._d.getTime())&&t.overflow<0&&!t.empty&&!t.invalidEra&&!t.invalidMonth&&!t.invalidWeekday&&!t.weekdayMismatch&&!t.nullInput&&!t.invalidFormat&&!t.userInvalidated&&(!t.meridiem||t.meridiem&&n);if(e._strict&&(i=i&&0===t.charsLeftOver&&0===t.unusedTokens.length&&void 0===t.bigHour),null!=Object.isFrozen&&Object.isFrozen(e))return i;e._isValid=i}return e._isValid}function k(e){var t=f(NaN);return null!=e?p(g(t),e):g(t).userInvalidated=!0,t}o=Array.prototype.some?Array.prototype.some:function(e){var t,o=Object(this),n=o.length>>>0;for(t=0;t<n;t++)if(t in o&&e.call(this,o[t],t,o))return!0;return!1};var y=n.momentProperties=[],w=!1;function A(e,t){var o,n,i;if(c(t._isAMomentObject)||(e._isAMomentObject=t._isAMomentObject),c(t._i)||(e._i=t._i),c(t._f)||(e._f=t._f),c(t._l)||(e._l=t._l),c(t._strict)||(e._strict=t._strict),c(t._tzm)||(e._tzm=t._tzm),c(t._isUTC)||(e._isUTC=t._isUTC),c(t._offset)||(e._offset=t._offset),c(t._pf)||(e._pf=g(t)),c(t._locale)||(e._locale=t._locale),y.length>0)for(o=0;o<y.length;o++)c(i=t[n=y[o]])||(e[n]=i);return e}function C(e){A(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===w&&(w=!0,n.updateOffset(this),w=!1)}function b(e){return e instanceof C||null!=e&&null!=e._isAMomentObject}function _(e){!1===n.suppressDeprecationWarnings&&\"undefined\"!=typeof console&&console.warn&&console.warn(\"Deprecation warning: \"+e)}function x(e,t){var o=!0;return p((function(){if(null!=n.deprecationHandler&&n.deprecationHandler(null,e),o){var i,r,s,l=[];for(r=0;r<arguments.length;r++){if(i=\"\",\"object\"==typeof arguments[r]){for(s in i+=\"\\n[\"+r+\"] \",arguments[0])a(arguments[0],s)&&(i+=s+\": \"+arguments[0][s]+\", \");i=i.slice(0,-2)}else i=arguments[r];l.push(i)}_(e+\"\\nArguments: \"+Array.prototype.slice.call(l).join(\"\")+\"\\n\"+(new Error).stack),o=!1}return t.apply(this,arguments)}),t)}var S,B={};function L(e,t){null!=n.deprecationHandler&&n.deprecationHandler(e,t),B[e]||(_(t),B[e]=!0)}function T(e){return\"undefined\"!=typeof Function&&e instanceof Function||\"[object Function]\"===Object.prototype.toString.call(e)}function E(e){var t,o;for(o in e)a(e,o)&&(T(t=e[o])?this[o]=t:this[\"_\"+o]=t);this._config=e,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+\"|\"+/\\d{1,2}/.source)}function N(e,t){var o,n=p({},e);for(o in t)a(t,o)&&(s(e[o])&&s(t[o])?(n[o]={},p(n[o],e[o]),p(n[o],t[o])):null!=t[o]?n[o]=t[o]:delete n[o]);for(o in e)a(e,o)&&!a(t,o)&&s(e[o])&&(n[o]=p({},n[o]));return n}function M(e){null!=e&&this.set(e)}n.suppressDeprecationWarnings=!1,n.deprecationHandler=null,S=Object.keys?Object.keys:function(e){var t,o=[];for(t in e)a(e,t)&&o.push(t);return o};var I={sameDay:\"[Today at] LT\",nextDay:\"[Tomorrow at] LT\",nextWeek:\"dddd [at] LT\",lastDay:\"[Yesterday at] LT\",lastWeek:\"[Last] dddd [at] LT\",sameElse:\"L\"};function P(e,t,o){var n=this._calendar[e]||this._calendar.sameElse;return T(n)?n.call(t,o):n}function O(e,t,o){var n=\"\"+Math.abs(e),i=t-n.length;return(e>=0?o?\"+\":\"\":\"-\")+Math.pow(10,Math.max(0,i)).toString().substr(1)+n}var D=/(\\[[^\\[]*\\])|(\\\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,R=/(\\[[^\\[]*\\])|(\\\\)?(LTS|LT|LL?L?L?|l{1,4})/g,V={},H={};function Y(e,t,o,n){var i=n;\"string\"==typeof n&&(i=function(){return this[n]()}),e&&(H[e]=i),t&&(H[t[0]]=function(){return O(i.apply(this,arguments),t[1],t[2])}),o&&(H[o]=function(){return this.localeData().ordinal(i.apply(this,arguments),e)})}function F(e){return e.match(/\\[[\\s\\S]/)?e.replace(/^\\[|\\]$/g,\"\"):e.replace(/\\\\/g,\"\")}function z(e){var t,o,n=e.match(D);for(t=0,o=n.length;t<o;t++)H[n[t]]?n[t]=H[n[t]]:n[t]=F(n[t]);return function(t){var i,r=\"\";for(i=0;i<o;i++)r+=T(n[i])?n[i].call(t,e):n[i];return r}}function j(e,t){return e.isValid()?(t=U(t,e.localeData()),V[t]=V[t]||z(t),V[t](e)):e.localeData().invalidDate()}function U(e,t){var o=5;function n(e){return t.longDateFormat(e)||e}for(R.lastIndex=0;o>=0&&R.test(e);)e=e.replace(R,n),R.lastIndex=0,o-=1;return e}var q={LTS:\"h:mm:ss A\",LT:\"h:mm A\",L:\"MM/DD/YYYY\",LL:\"MMMM D, YYYY\",LLL:\"MMMM D, YYYY h:mm A\",LLLL:\"dddd, MMMM D, YYYY h:mm A\"};function W(e){var t=this._longDateFormat[e],o=this._longDateFormat[e.toUpperCase()];return t||!o?t:(this._longDateFormat[e]=o.match(D).map((function(e){return\"MMMM\"===e||\"MM\"===e||\"DD\"===e||\"dddd\"===e?e.slice(1):e})).join(\"\"),this._longDateFormat[e])}var K=\"Invalid date\";function J(){return this._invalidDate}var $=\"%d\",X=/\\d{1,2}/;function Q(e){return this._ordinal.replace(\"%d\",e)}var G={future:\"in %s\",past:\"%s ago\",s:\"a few seconds\",ss:\"%d seconds\",m:\"a minute\",mm:\"%d minutes\",h:\"an hour\",hh:\"%d hours\",d:\"a day\",dd:\"%d days\",w:\"a week\",ww:\"%d weeks\",M:\"a month\",MM:\"%d months\",y:\"a year\",yy:\"%d years\"};function Z(e,t,o,n){var i=this._relativeTime[o];return T(i)?i(e,t,o,n):i.replace(/%d/i,e)}function ee(e,t){var o=this._relativeTime[e>0?\"future\":\"past\"];return T(o)?o(t):o.replace(/%s/i,t)}var te={};function oe(e,t){var o=e.toLowerCase();te[o]=te[o+\"s\"]=te[t]=e}function ne(e){return\"string\"==typeof e?te[e]||te[e.toLowerCase()]:void 0}function ie(e){var t,o,n={};for(o in e)a(e,o)&&(t=ne(o))&&(n[t]=e[o]);return n}var re={};function se(e,t){re[e]=t}function ae(e){var t,o=[];for(t in e)a(e,t)&&o.push({unit:t,priority:re[t]});return o.sort((function(e,t){return e.priority-t.priority})),o}function le(e){return e%4==0&&e%100!=0||e%400==0}function ce(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function de(e){var t=+e,o=0;return 0!==t&&isFinite(t)&&(o=ce(t)),o}function ue(e,t){return function(o){return null!=o?(pe(this,e,o),n.updateOffset(this,t),this):he(this,e)}}function he(e,t){return e.isValid()?e._d[\"get\"+(e._isUTC?\"UTC\":\"\")+t]():NaN}function pe(e,t,o){e.isValid()&&!isNaN(o)&&(\"FullYear\"===t&&le(e.year())&&1===e.month()&&29===e.date()?(o=de(o),e._d[\"set\"+(e._isUTC?\"UTC\":\"\")+t](o,e.month(),Ze(o,e.month()))):e._d[\"set\"+(e._isUTC?\"UTC\":\"\")+t](o))}function fe(e){return T(this[e=ne(e)])?this[e]():this}function me(e,t){if(\"object\"==typeof e){var o,n=ae(e=ie(e));for(o=0;o<n.length;o++)this[n[o].unit](e[n[o].unit])}else if(T(this[e=ne(e)]))return this[e](t);return this}var ge,ve=/\\d/,ke=/\\d\\d/,ye=/\\d{3}/,we=/\\d{4}/,Ae=/[+-]?\\d{6}/,Ce=/\\d\\d?/,be=/\\d\\d\\d\\d?/,_e=/\\d\\d\\d\\d\\d\\d?/,xe=/\\d{1,3}/,Se=/\\d{1,4}/,Be=/[+-]?\\d{1,6}/,Le=/\\d+/,Te=/[+-]?\\d+/,Ee=/Z|[+-]\\d\\d:?\\d\\d/gi,Ne=/Z|[+-]\\d\\d(?::?\\d\\d)?/gi,Me=/[+-]?\\d+(\\.\\d{1,3})?/,Ie=/[0-9]{0,256}['a-z\\u00A0-\\u05FF\\u0700-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFF07\\uFF10-\\uFFEF]{1,256}|[\\u0600-\\u06FF\\/]{1,256}(\\s*?[\\u0600-\\u06FF]{1,256}){1,2}/i;function Pe(e,t,o){ge[e]=T(t)?t:function(e,n){return e&&o?o:t}}function Oe(e,t){return a(ge,e)?ge[e](t._strict,t._locale):new RegExp(De(e))}function De(e){return Re(e.replace(\"\\\\\",\"\").replace(/\\\\(\\[)|\\\\(\\])|\\[([^\\]\\[]*)\\]|\\\\(.)/g,(function(e,t,o,n,i){return t||o||n||i})))}function Re(e){return e.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g,\"\\\\$&\")}ge={};var Ve={};function He(e,t){var o,n=t;for(\"string\"==typeof e&&(e=[e]),d(t)&&(n=function(e,o){o[t]=de(e)}),o=0;o<e.length;o++)Ve[e[o]]=n}function Ye(e,t){He(e,(function(e,o,n,i){n._w=n._w||{},t(e,n._w,n,i)}))}function Fe(e,t,o){null!=t&&a(Ve,e)&&Ve[e](t,o._a,o,e)}var ze,je=0,Ue=1,qe=2,We=3,Ke=4,Je=5,$e=6,Xe=7,Qe=8;function Ge(e,t){return(e%t+t)%t}function Ze(e,t){if(isNaN(e)||isNaN(t))return NaN;var o=Ge(t,12);return e+=(t-o)/12,1===o?le(e)?29:28:31-o%7%2}ze=Array.prototype.indexOf?Array.prototype.indexOf:function(e){var t;for(t=0;t<this.length;++t)if(this[t]===e)return t;return-1},Y(\"M\",[\"MM\",2],\"Mo\",(function(){return this.month()+1})),Y(\"MMM\",0,0,(function(e){return this.localeData().monthsShort(this,e)})),Y(\"MMMM\",0,0,(function(e){return this.localeData().months(this,e)})),oe(\"month\",\"M\"),se(\"month\",8),Pe(\"M\",Ce),Pe(\"MM\",Ce,ke),Pe(\"MMM\",(function(e,t){return t.monthsShortRegex(e)})),Pe(\"MMMM\",(function(e,t){return t.monthsRegex(e)})),He([\"M\",\"MM\"],(function(e,t){t[Ue]=de(e)-1})),He([\"MMM\",\"MMMM\"],(function(e,t,o,n){var i=o._locale.monthsParse(e,n,o._strict);null!=i?t[Ue]=i:g(o).invalidMonth=e}));var et=\"January_February_March_April_May_June_July_August_September_October_November_December\".split(\"_\"),tt=\"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec\".split(\"_\"),ot=/D[oD]?(\\[[^\\[\\]]*\\]|\\s)+MMMM?/,nt=Ie,it=Ie;function rt(e,t){return e?r(this._months)?this._months[e.month()]:this._months[(this._months.isFormat||ot).test(t)?\"format\":\"standalone\"][e.month()]:r(this._months)?this._months:this._months.standalone}function st(e,t){return e?r(this._monthsShort)?this._monthsShort[e.month()]:this._monthsShort[ot.test(t)?\"format\":\"standalone\"][e.month()]:r(this._monthsShort)?this._monthsShort:this._monthsShort.standalone}function at(e,t,o){var n,i,r,s=e.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],n=0;n<12;++n)r=f([2e3,n]),this._shortMonthsParse[n]=this.monthsShort(r,\"\").toLocaleLowerCase(),this._longMonthsParse[n]=this.months(r,\"\").toLocaleLowerCase();return o?\"MMM\"===t?-1!==(i=ze.call(this._shortMonthsParse,s))?i:null:-1!==(i=ze.call(this._longMonthsParse,s))?i:null:\"MMM\"===t?-1!==(i=ze.call(this._shortMonthsParse,s))||-1!==(i=ze.call(this._longMonthsParse,s))?i:null:-1!==(i=ze.call(this._longMonthsParse,s))||-1!==(i=ze.call(this._shortMonthsParse,s))?i:null}function lt(e,t,o){var n,i,r;if(this._monthsParseExact)return at.call(this,e,t,o);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),n=0;n<12;n++){if(i=f([2e3,n]),o&&!this._longMonthsParse[n]&&(this._longMonthsParse[n]=new RegExp(\"^\"+this.months(i,\"\").replace(\".\",\"\")+\"$\",\"i\"),this._shortMonthsParse[n]=new RegExp(\"^\"+this.monthsShort(i,\"\").replace(\".\",\"\")+\"$\",\"i\")),o||this._monthsParse[n]||(r=\"^\"+this.months(i,\"\")+\"|^\"+this.monthsShort(i,\"\"),this._monthsParse[n]=new RegExp(r.replace(\".\",\"\"),\"i\")),o&&\"MMMM\"===t&&this._longMonthsParse[n].test(e))return n;if(o&&\"MMM\"===t&&this._shortMonthsParse[n].test(e))return n;if(!o&&this._monthsParse[n].test(e))return n}}function ct(e,t){var o;if(!e.isValid())return e;if(\"string\"==typeof t)if(/^\\d+$/.test(t))t=de(t);else if(!d(t=e.localeData().monthsParse(t)))return e;return o=Math.min(e.date(),Ze(e.year(),t)),e._d[\"set\"+(e._isUTC?\"UTC\":\"\")+\"Month\"](t,o),e}function dt(e){return null!=e?(ct(this,e),n.updateOffset(this,!0),this):he(this,\"Month\")}function ut(){return Ze(this.year(),this.month())}function ht(e){return this._monthsParseExact?(a(this,\"_monthsRegex\")||ft.call(this),e?this._monthsShortStrictRegex:this._monthsShortRegex):(a(this,\"_monthsShortRegex\")||(this._monthsShortRegex=nt),this._monthsShortStrictRegex&&e?this._monthsShortStrictRegex:this._monthsShortRegex)}function pt(e){return this._monthsParseExact?(a(this,\"_monthsRegex\")||ft.call(this),e?this._monthsStrictRegex:this._monthsRegex):(a(this,\"_monthsRegex\")||(this._monthsRegex=it),this._monthsStrictRegex&&e?this._monthsStrictRegex:this._monthsRegex)}function ft(){function e(e,t){return t.length-e.length}var t,o,n=[],i=[],r=[];for(t=0;t<12;t++)o=f([2e3,t]),n.push(this.monthsShort(o,\"\")),i.push(this.months(o,\"\")),r.push(this.months(o,\"\")),r.push(this.monthsShort(o,\"\"));for(n.sort(e),i.sort(e),r.sort(e),t=0;t<12;t++)n[t]=Re(n[t]),i[t]=Re(i[t]);for(t=0;t<24;t++)r[t]=Re(r[t]);this._monthsRegex=new RegExp(\"^(\"+r.join(\"|\")+\")\",\"i\"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp(\"^(\"+i.join(\"|\")+\")\",\"i\"),this._monthsShortStrictRegex=new RegExp(\"^(\"+n.join(\"|\")+\")\",\"i\")}function mt(e){return le(e)?366:365}Y(\"Y\",0,0,(function(){var e=this.year();return e<=9999?O(e,4):\"+\"+e})),Y(0,[\"YY\",2],0,(function(){return this.year()%100})),Y(0,[\"YYYY\",4],0,\"year\"),Y(0,[\"YYYYY\",5],0,\"year\"),Y(0,[\"YYYYYY\",6,!0],0,\"year\"),oe(\"year\",\"y\"),se(\"year\",1),Pe(\"Y\",Te),Pe(\"YY\",Ce,ke),Pe(\"YYYY\",Se,we),Pe(\"YYYYY\",Be,Ae),Pe(\"YYYYYY\",Be,Ae),He([\"YYYYY\",\"YYYYYY\"],je),He(\"YYYY\",(function(e,t){t[je]=2===e.length?n.parseTwoDigitYear(e):de(e)})),He(\"YY\",(function(e,t){t[je]=n.parseTwoDigitYear(e)})),He(\"Y\",(function(e,t){t[je]=parseInt(e,10)})),n.parseTwoDigitYear=function(e){return de(e)+(de(e)>68?1900:2e3)};var gt=ue(\"FullYear\",!0);function vt(){return le(this.year())}function kt(e,t,o,n,i,r,s){var a;return e<100&&e>=0?(a=new Date(e+400,t,o,n,i,r,s),isFinite(a.getFullYear())&&a.setFullYear(e)):a=new Date(e,t,o,n,i,r,s),a}function yt(e){var t,o;return e<100&&e>=0?((o=Array.prototype.slice.call(arguments))[0]=e+400,t=new Date(Date.UTC.apply(null,o)),isFinite(t.getUTCFullYear())&&t.setUTCFullYear(e)):t=new Date(Date.UTC.apply(null,arguments)),t}function wt(e,t,o){var n=7+t-o;return-(7+yt(e,0,n).getUTCDay()-t)%7+n-1}function At(e,t,o,n,i){var r,s,a=1+7*(t-1)+(7+o-n)%7+wt(e,n,i);return a<=0?s=mt(r=e-1)+a:a>mt(e)?(r=e+1,s=a-mt(e)):(r=e,s=a),{year:r,dayOfYear:s}}function Ct(e,t,o){var n,i,r=wt(e.year(),t,o),s=Math.floor((e.dayOfYear()-r-1)/7)+1;return s<1?n=s+bt(i=e.year()-1,t,o):s>bt(e.year(),t,o)?(n=s-bt(e.year(),t,o),i=e.year()+1):(i=e.year(),n=s),{week:n,year:i}}function bt(e,t,o){var n=wt(e,t,o),i=wt(e+1,t,o);return(mt(e)-n+i)/7}function _t(e){return Ct(e,this._week.dow,this._week.doy).week}Y(\"w\",[\"ww\",2],\"wo\",\"week\"),Y(\"W\",[\"WW\",2],\"Wo\",\"isoWeek\"),oe(\"week\",\"w\"),oe(\"isoWeek\",\"W\"),se(\"week\",5),se(\"isoWeek\",5),Pe(\"w\",Ce),Pe(\"ww\",Ce,ke),Pe(\"W\",Ce),Pe(\"WW\",Ce,ke),Ye([\"w\",\"ww\",\"W\",\"WW\"],(function(e,t,o,n){t[n.substr(0,1)]=de(e)}));var xt={dow:0,doy:6};function St(){return this._week.dow}function Bt(){return this._week.doy}function Lt(e){var t=this.localeData().week(this);return null==e?t:this.add(7*(e-t),\"d\")}function Tt(e){var t=Ct(this,1,4).week;return null==e?t:this.add(7*(e-t),\"d\")}function Et(e,t){return\"string\"!=typeof e?e:isNaN(e)?\"number\"==typeof(e=t.weekdaysParse(e))?e:null:parseInt(e,10)}function Nt(e,t){return\"string\"==typeof e?t.weekdaysParse(e)%7||7:isNaN(e)?null:e}function Mt(e,t){return e.slice(t,7).concat(e.slice(0,t))}Y(\"d\",0,\"do\",\"day\"),Y(\"dd\",0,0,(function(e){return this.localeData().weekdaysMin(this,e)})),Y(\"ddd\",0,0,(function(e){return this.localeData().weekdaysShort(this,e)})),Y(\"dddd\",0,0,(function(e){return this.localeData().weekdays(this,e)})),Y(\"e\",0,0,\"weekday\"),Y(\"E\",0,0,\"isoWeekday\"),oe(\"day\",\"d\"),oe(\"weekday\",\"e\"),oe(\"isoWeekday\",\"E\"),se(\"day\",11),se(\"weekday\",11),se(\"isoWeekday\",11),Pe(\"d\",Ce),Pe(\"e\",Ce),Pe(\"E\",Ce),Pe(\"dd\",(function(e,t){return t.weekdaysMinRegex(e)})),Pe(\"ddd\",(function(e,t){return t.weekdaysShortRegex(e)})),Pe(\"dddd\",(function(e,t){return t.weekdaysRegex(e)})),Ye([\"dd\",\"ddd\",\"dddd\"],(function(e,t,o,n){var i=o._locale.weekdaysParse(e,n,o._strict);null!=i?t.d=i:g(o).invalidWeekday=e})),Ye([\"d\",\"e\",\"E\"],(function(e,t,o,n){t[n]=de(e)}));var It=\"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday\".split(\"_\"),Pt=\"Sun_Mon_Tue_Wed_Thu_Fri_Sat\".split(\"_\"),Ot=\"Su_Mo_Tu_We_Th_Fr_Sa\".split(\"_\"),Dt=Ie,Rt=Ie,Vt=Ie;function Ht(e,t){var o=r(this._weekdays)?this._weekdays:this._weekdays[e&&!0!==e&&this._weekdays.isFormat.test(t)?\"format\":\"standalone\"];return!0===e?Mt(o,this._week.dow):e?o[e.day()]:o}function Yt(e){return!0===e?Mt(this._weekdaysShort,this._week.dow):e?this._weekdaysShort[e.day()]:this._weekdaysShort}function Ft(e){return!0===e?Mt(this._weekdaysMin,this._week.dow):e?this._weekdaysMin[e.day()]:this._weekdaysMin}function zt(e,t,o){var n,i,r,s=e.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],n=0;n<7;++n)r=f([2e3,1]).day(n),this._minWeekdaysParse[n]=this.weekdaysMin(r,\"\").toLocaleLowerCase(),this._shortWeekdaysParse[n]=this.weekdaysShort(r,\"\").toLocaleLowerCase(),this._weekdaysParse[n]=this.weekdays(r,\"\").toLocaleLowerCase();return o?\"dddd\"===t?-1!==(i=ze.call(this._weekdaysParse,s))?i:null:\"ddd\"===t?-1!==(i=ze.call(this._shortWeekdaysParse,s))?i:null:-1!==(i=ze.call(this._minWeekdaysParse,s))?i:null:\"dddd\"===t?-1!==(i=ze.call(this._weekdaysParse,s))||-1!==(i=ze.call(this._shortWeekdaysParse,s))||-1!==(i=ze.call(this._minWeekdaysParse,s))?i:null:\"ddd\"===t?-1!==(i=ze.call(this._shortWeekdaysParse,s))||-1!==(i=ze.call(this._weekdaysParse,s))||-1!==(i=ze.call(this._minWeekdaysParse,s))?i:null:-1!==(i=ze.call(this._minWeekdaysParse,s))||-1!==(i=ze.call(this._weekdaysParse,s))||-1!==(i=ze.call(this._shortWeekdaysParse,s))?i:null}function jt(e,t,o){var n,i,r;if(this._weekdaysParseExact)return zt.call(this,e,t,o);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),n=0;n<7;n++){if(i=f([2e3,1]).day(n),o&&!this._fullWeekdaysParse[n]&&(this._fullWeekdaysParse[n]=new RegExp(\"^\"+this.weekdays(i,\"\").replace(\".\",\"\\\\.?\")+\"$\",\"i\"),this._shortWeekdaysParse[n]=new RegExp(\"^\"+this.weekdaysShort(i,\"\").replace(\".\",\"\\\\.?\")+\"$\",\"i\"),this._minWeekdaysParse[n]=new RegExp(\"^\"+this.weekdaysMin(i,\"\").replace(\".\",\"\\\\.?\")+\"$\",\"i\")),this._weekdaysParse[n]||(r=\"^\"+this.weekdays(i,\"\")+\"|^\"+this.weekdaysShort(i,\"\")+\"|^\"+this.weekdaysMin(i,\"\"),this._weekdaysParse[n]=new RegExp(r.replace(\".\",\"\"),\"i\")),o&&\"dddd\"===t&&this._fullWeekdaysParse[n].test(e))return n;if(o&&\"ddd\"===t&&this._shortWeekdaysParse[n].test(e))return n;if(o&&\"dd\"===t&&this._minWeekdaysParse[n].test(e))return n;if(!o&&this._weekdaysParse[n].test(e))return n}}function Ut(e){if(!this.isValid())return null!=e?this:NaN;var t=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=e?(e=Et(e,this.localeData()),this.add(e-t,\"d\")):t}function qt(e){if(!this.isValid())return null!=e?this:NaN;var t=(this.day()+7-this.localeData()._week.dow)%7;return null==e?t:this.add(e-t,\"d\")}function Wt(e){if(!this.isValid())return null!=e?this:NaN;if(null!=e){var t=Nt(e,this.localeData());return this.day(this.day()%7?t:t-7)}return this.day()||7}function Kt(e){return this._weekdaysParseExact?(a(this,\"_weekdaysRegex\")||Xt.call(this),e?this._weekdaysStrictRegex:this._weekdaysRegex):(a(this,\"_weekdaysRegex\")||(this._weekdaysRegex=Dt),this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex)}function Jt(e){return this._weekdaysParseExact?(a(this,\"_weekdaysRegex\")||Xt.call(this),e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(a(this,\"_weekdaysShortRegex\")||(this._weekdaysShortRegex=Rt),this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function $t(e){return this._weekdaysParseExact?(a(this,\"_weekdaysRegex\")||Xt.call(this),e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(a(this,\"_weekdaysMinRegex\")||(this._weekdaysMinRegex=Vt),this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function Xt(){function e(e,t){return t.length-e.length}var t,o,n,i,r,s=[],a=[],l=[],c=[];for(t=0;t<7;t++)o=f([2e3,1]).day(t),n=Re(this.weekdaysMin(o,\"\")),i=Re(this.weekdaysShort(o,\"\")),r=Re(this.weekdays(o,\"\")),s.push(n),a.push(i),l.push(r),c.push(n),c.push(i),c.push(r);s.sort(e),a.sort(e),l.sort(e),c.sort(e),this._weekdaysRegex=new RegExp(\"^(\"+c.join(\"|\")+\")\",\"i\"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp(\"^(\"+l.join(\"|\")+\")\",\"i\"),this._weekdaysShortStrictRegex=new RegExp(\"^(\"+a.join(\"|\")+\")\",\"i\"),this._weekdaysMinStrictRegex=new RegExp(\"^(\"+s.join(\"|\")+\")\",\"i\")}function Qt(){return this.hours()%12||12}function Gt(){return this.hours()||24}function Zt(e,t){Y(e,0,0,(function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)}))}function eo(e,t){return t._meridiemParse}function to(e){return\"p\"===(e+\"\").toLowerCase().charAt(0)}Y(\"H\",[\"HH\",2],0,\"hour\"),Y(\"h\",[\"hh\",2],0,Qt),Y(\"k\",[\"kk\",2],0,Gt),Y(\"hmm\",0,0,(function(){return\"\"+Qt.apply(this)+O(this.minutes(),2)})),Y(\"hmmss\",0,0,(function(){return\"\"+Qt.apply(this)+O(this.minutes(),2)+O(this.seconds(),2)})),Y(\"Hmm\",0,0,(function(){return\"\"+this.hours()+O(this.minutes(),2)})),Y(\"Hmmss\",0,0,(function(){return\"\"+this.hours()+O(this.minutes(),2)+O(this.seconds(),2)})),Zt(\"a\",!0),Zt(\"A\",!1),oe(\"hour\",\"h\"),se(\"hour\",13),Pe(\"a\",eo),Pe(\"A\",eo),Pe(\"H\",Ce),Pe(\"h\",Ce),Pe(\"k\",Ce),Pe(\"HH\",Ce,ke),Pe(\"hh\",Ce,ke),Pe(\"kk\",Ce,ke),Pe(\"hmm\",be),Pe(\"hmmss\",_e),Pe(\"Hmm\",be),Pe(\"Hmmss\",_e),He([\"H\",\"HH\"],We),He([\"k\",\"kk\"],(function(e,t,o){var n=de(e);t[We]=24===n?0:n})),He([\"a\",\"A\"],(function(e,t,o){o._isPm=o._locale.isPM(e),o._meridiem=e})),He([\"h\",\"hh\"],(function(e,t,o){t[We]=de(e),g(o).bigHour=!0})),He(\"hmm\",(function(e,t,o){var n=e.length-2;t[We]=de(e.substr(0,n)),t[Ke]=de(e.substr(n)),g(o).bigHour=!0})),He(\"hmmss\",(function(e,t,o){var n=e.length-4,i=e.length-2;t[We]=de(e.substr(0,n)),t[Ke]=de(e.substr(n,2)),t[Je]=de(e.substr(i)),g(o).bigHour=!0})),He(\"Hmm\",(function(e,t,o){var n=e.length-2;t[We]=de(e.substr(0,n)),t[Ke]=de(e.substr(n))})),He(\"Hmmss\",(function(e,t,o){var n=e.length-4,i=e.length-2;t[We]=de(e.substr(0,n)),t[Ke]=de(e.substr(n,2)),t[Je]=de(e.substr(i))}));var oo=/[ap]\\.?m?\\.?/i,no=ue(\"Hours\",!0);function io(e,t,o){return e>11?o?\"pm\":\"PM\":o?\"am\":\"AM\"}var ro,so={calendar:I,longDateFormat:q,invalidDate:K,ordinal:$,dayOfMonthOrdinalParse:X,relativeTime:G,months:et,monthsShort:tt,week:xt,weekdays:It,weekdaysMin:Ot,weekdaysShort:Pt,meridiemParse:oo},ao={},lo={};function co(e,t){var o,n=Math.min(e.length,t.length);for(o=0;o<n;o+=1)if(e[o]!==t[o])return o;return n}function uo(e){return e?e.toLowerCase().replace(\"_\",\"-\"):e}function ho(e){for(var t,o,n,i,r=0;r<e.length;){for(t=(i=uo(e[r]).split(\"-\")).length,o=(o=uo(e[r+1]))?o.split(\"-\"):null;t>0;){if(n=po(i.slice(0,t).join(\"-\")))return n;if(o&&o.length>=t&&co(i,o)>=t-1)break;t--}r++}return ro}function po(t){var o=null;if(void 0===ao[t]&&e&&e.exports)try{o=ro._abbr,commonjsRequire(\"./locale/\"+t),fo(o)}catch(e){ao[t]=null}return ao[t]}function fo(e,t){var o;return e&&((o=c(t)?vo(e):mo(e,t))?ro=o:\"undefined\"!=typeof console&&console.warn&&console.warn(\"Locale \"+e+\" not found. Did you forget to load it?\")),ro._abbr}function mo(e,t){if(null!==t){var o,n=so;if(t.abbr=e,null!=ao[e])L(\"defineLocaleOverride\",\"use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info.\"),n=ao[e]._config;else if(null!=t.parentLocale)if(null!=ao[t.parentLocale])n=ao[t.parentLocale]._config;else{if(null==(o=po(t.parentLocale)))return lo[t.parentLocale]||(lo[t.parentLocale]=[]),lo[t.parentLocale].push({name:e,config:t}),null;n=o._config}return ao[e]=new M(N(n,t)),lo[e]&&lo[e].forEach((function(e){mo(e.name,e.config)})),fo(e),ao[e]}return delete ao[e],null}function go(e,t){if(null!=t){var o,n,i=so;null!=ao[e]&&null!=ao[e].parentLocale?ao[e].set(N(ao[e]._config,t)):(null!=(n=po(e))&&(i=n._config),t=N(i,t),null==n&&(t.abbr=e),(o=new M(t)).parentLocale=ao[e],ao[e]=o),fo(e)}else null!=ao[e]&&(null!=ao[e].parentLocale?(ao[e]=ao[e].parentLocale,e===fo()&&fo(e)):null!=ao[e]&&delete ao[e]);return ao[e]}function vo(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return ro;if(!r(e)){if(t=po(e))return t;e=[e]}return ho(e)}function ko(){return S(ao)}function yo(e){var t,o=e._a;return o&&-2===g(e).overflow&&(t=o[Ue]<0||o[Ue]>11?Ue:o[qe]<1||o[qe]>Ze(o[je],o[Ue])?qe:o[We]<0||o[We]>24||24===o[We]&&(0!==o[Ke]||0!==o[Je]||0!==o[$e])?We:o[Ke]<0||o[Ke]>59?Ke:o[Je]<0||o[Je]>59?Je:o[$e]<0||o[$e]>999?$e:-1,g(e)._overflowDayOfYear&&(t<je||t>qe)&&(t=qe),g(e)._overflowWeeks&&-1===t&&(t=Xe),g(e)._overflowWeekday&&-1===t&&(t=Qe),g(e).overflow=t),e}var wo=/^\\s*((?:[+-]\\d{6}|\\d{4})-(?:\\d\\d-\\d\\d|W\\d\\d-\\d|W\\d\\d|\\d\\d\\d|\\d\\d))(?:(T| )(\\d\\d(?::\\d\\d(?::\\d\\d(?:[.,]\\d+)?)?)?)([+-]\\d\\d(?::?\\d\\d)?|\\s*Z)?)?$/,Ao=/^\\s*((?:[+-]\\d{6}|\\d{4})(?:\\d\\d\\d\\d|W\\d\\d\\d|W\\d\\d|\\d\\d\\d|\\d\\d|))(?:(T| )(\\d\\d(?:\\d\\d(?:\\d\\d(?:[.,]\\d+)?)?)?)([+-]\\d\\d(?::?\\d\\d)?|\\s*Z)?)?$/,Co=/Z|[+-]\\d\\d(?::?\\d\\d)?/,bo=[[\"YYYYYY-MM-DD\",/[+-]\\d{6}-\\d\\d-\\d\\d/],[\"YYYY-MM-DD\",/\\d{4}-\\d\\d-\\d\\d/],[\"GGGG-[W]WW-E\",/\\d{4}-W\\d\\d-\\d/],[\"GGGG-[W]WW\",/\\d{4}-W\\d\\d/,!1],[\"YYYY-DDD\",/\\d{4}-\\d{3}/],[\"YYYY-MM\",/\\d{4}-\\d\\d/,!1],[\"YYYYYYMMDD\",/[+-]\\d{10}/],[\"YYYYMMDD\",/\\d{8}/],[\"GGGG[W]WWE\",/\\d{4}W\\d{3}/],[\"GGGG[W]WW\",/\\d{4}W\\d{2}/,!1],[\"YYYYDDD\",/\\d{7}/],[\"YYYYMM\",/\\d{6}/,!1],[\"YYYY\",/\\d{4}/,!1]],_o=[[\"HH:mm:ss.SSSS\",/\\d\\d:\\d\\d:\\d\\d\\.\\d+/],[\"HH:mm:ss,SSSS\",/\\d\\d:\\d\\d:\\d\\d,\\d+/],[\"HH:mm:ss\",/\\d\\d:\\d\\d:\\d\\d/],[\"HH:mm\",/\\d\\d:\\d\\d/],[\"HHmmss.SSSS\",/\\d\\d\\d\\d\\d\\d\\.\\d+/],[\"HHmmss,SSSS\",/\\d\\d\\d\\d\\d\\d,\\d+/],[\"HHmmss\",/\\d\\d\\d\\d\\d\\d/],[\"HHmm\",/\\d\\d\\d\\d/],[\"HH\",/\\d\\d/]],xo=/^\\/?Date\\((-?\\d+)/i,So=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\\s)?(\\d{1,2})\\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s(\\d{2,4})\\s(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\\d{4}))$/,Bo={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function Lo(e){var t,o,n,i,r,s,a=e._i,l=wo.exec(a)||Ao.exec(a);if(l){for(g(e).iso=!0,t=0,o=bo.length;t<o;t++)if(bo[t][1].exec(l[1])){i=bo[t][0],n=!1!==bo[t][2];break}if(null==i)return void(e._isValid=!1);if(l[3]){for(t=0,o=_o.length;t<o;t++)if(_o[t][1].exec(l[3])){r=(l[2]||\" \")+_o[t][0];break}if(null==r)return void(e._isValid=!1)}if(!n&&null!=r)return void(e._isValid=!1);if(l[4]){if(!Co.exec(l[4]))return void(e._isValid=!1);s=\"Z\"}e._f=i+(r||\"\")+(s||\"\"),Yo(e)}else e._isValid=!1}function To(e,t,o,n,i,r){var s=[Eo(e),tt.indexOf(t),parseInt(o,10),parseInt(n,10),parseInt(i,10)];return r&&s.push(parseInt(r,10)),s}function Eo(e){var t=parseInt(e,10);return t<=49?2e3+t:t<=999?1900+t:t}function No(e){return e.replace(/\\([^)]*\\)|[\\n\\t]/g,\" \").replace(/(\\s\\s+)/g,\" \").replace(/^\\s\\s*/,\"\").replace(/\\s\\s*$/,\"\")}function Mo(e,t,o){return!e||Pt.indexOf(e)===new Date(t[0],t[1],t[2]).getDay()||(g(o).weekdayMismatch=!0,o._isValid=!1,!1)}function Io(e,t,o){if(e)return Bo[e];if(t)return 0;var n=parseInt(o,10),i=n%100;return(n-i)/100*60+i}function Po(e){var t,o=So.exec(No(e._i));if(o){if(t=To(o[4],o[3],o[2],o[5],o[6],o[7]),!Mo(o[1],t,e))return;e._a=t,e._tzm=Io(o[8],o[9],o[10]),e._d=yt.apply(null,e._a),e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),g(e).rfc2822=!0}else e._isValid=!1}function Oo(e){var t=xo.exec(e._i);null===t?(Lo(e),!1===e._isValid&&(delete e._isValid,Po(e),!1===e._isValid&&(delete e._isValid,e._strict?e._isValid=!1:n.createFromInputFallback(e)))):e._d=new Date(+t[1])}function Do(e,t,o){return null!=e?e:null!=t?t:o}function Ro(e){var t=new Date(n.now());return e._useUTC?[t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate()]:[t.getFullYear(),t.getMonth(),t.getDate()]}function Vo(e){var t,o,n,i,r,s=[];if(!e._d){for(n=Ro(e),e._w&&null==e._a[qe]&&null==e._a[Ue]&&Ho(e),null!=e._dayOfYear&&(r=Do(e._a[je],n[je]),(e._dayOfYear>mt(r)||0===e._dayOfYear)&&(g(e)._overflowDayOfYear=!0),o=yt(r,0,e._dayOfYear),e._a[Ue]=o.getUTCMonth(),e._a[qe]=o.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=s[t]=n[t];for(;t<7;t++)e._a[t]=s[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[We]&&0===e._a[Ke]&&0===e._a[Je]&&0===e._a[$e]&&(e._nextDay=!0,e._a[We]=0),e._d=(e._useUTC?yt:kt).apply(null,s),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[We]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(g(e).weekdayMismatch=!0)}}function Ho(e){var t,o,n,i,r,s,a,l,c;null!=(t=e._w).GG||null!=t.W||null!=t.E?(r=1,s=4,o=Do(t.GG,e._a[je],Ct(Jo(),1,4).year),n=Do(t.W,1),((i=Do(t.E,1))<1||i>7)&&(l=!0)):(r=e._locale._week.dow,s=e._locale._week.doy,c=Ct(Jo(),r,s),o=Do(t.gg,e._a[je],c.year),n=Do(t.w,c.week),null!=t.d?((i=t.d)<0||i>6)&&(l=!0):null!=t.e?(i=t.e+r,(t.e<0||t.e>6)&&(l=!0)):i=r),n<1||n>bt(o,r,s)?g(e)._overflowWeeks=!0:null!=l?g(e)._overflowWeekday=!0:(a=At(o,n,i,r,s),e._a[je]=a.year,e._dayOfYear=a.dayOfYear)}function Yo(e){if(e._f!==n.ISO_8601)if(e._f!==n.RFC_2822){e._a=[],g(e).empty=!0;var t,o,i,r,s,a,l=\"\"+e._i,c=l.length,d=0;for(i=U(e._f,e._locale).match(D)||[],t=0;t<i.length;t++)r=i[t],(o=(l.match(Oe(r,e))||[])[0])&&((s=l.substr(0,l.indexOf(o))).length>0&&g(e).unusedInput.push(s),l=l.slice(l.indexOf(o)+o.length),d+=o.length),H[r]?(o?g(e).empty=!1:g(e).unusedTokens.push(r),Fe(r,o,e)):e._strict&&!o&&g(e).unusedTokens.push(r);g(e).charsLeftOver=c-d,l.length>0&&g(e).unusedInput.push(l),e._a[We]<=12&&!0===g(e).bigHour&&e._a[We]>0&&(g(e).bigHour=void 0),g(e).parsedDateParts=e._a.slice(0),g(e).meridiem=e._meridiem,e._a[We]=Fo(e._locale,e._a[We],e._meridiem),null!==(a=g(e).era)&&(e._a[je]=e._locale.erasConvertYear(a,e._a[je])),Vo(e),yo(e)}else Po(e);else Lo(e)}function Fo(e,t,o){var n;return null==o?t:null!=e.meridiemHour?e.meridiemHour(t,o):null!=e.isPM?((n=e.isPM(o))&&t<12&&(t+=12),n||12!==t||(t=0),t):t}function zo(e){var t,o,n,i,r,s,a=!1;if(0===e._f.length)return g(e).invalidFormat=!0,void(e._d=new Date(NaN));for(i=0;i<e._f.length;i++)r=0,s=!1,t=A({},e),null!=e._useUTC&&(t._useUTC=e._useUTC),t._f=e._f[i],Yo(t),v(t)&&(s=!0),r+=g(t).charsLeftOver,r+=10*g(t).unusedTokens.length,g(t).score=r,a?r<n&&(n=r,o=t):(null==n||r<n||s)&&(n=r,o=t,s&&(a=!0));p(e,o||t)}function jo(e){if(!e._d){var t=ie(e._i),o=void 0===t.day?t.date:t.day;e._a=h([t.year,t.month,o,t.hour,t.minute,t.second,t.millisecond],(function(e){return e&&parseInt(e,10)})),Vo(e)}}function Uo(e){var t=new C(yo(qo(e)));return t._nextDay&&(t.add(1,\"d\"),t._nextDay=void 0),t}function qo(e){var t=e._i,o=e._f;return e._locale=e._locale||vo(e._l),null===t||void 0===o&&\"\"===t?k({nullInput:!0}):(\"string\"==typeof t&&(e._i=t=e._locale.preparse(t)),b(t)?new C(yo(t)):(u(t)?e._d=t:r(o)?zo(e):o?Yo(e):Wo(e),v(e)||(e._d=null),e))}function Wo(e){var t=e._i;c(t)?e._d=new Date(n.now()):u(t)?e._d=new Date(t.valueOf()):\"string\"==typeof t?Oo(e):r(t)?(e._a=h(t.slice(0),(function(e){return parseInt(e,10)})),Vo(e)):s(t)?jo(e):d(t)?e._d=new Date(t):n.createFromInputFallback(e)}function Ko(e,t,o,n,i){var a={};return!0!==t&&!1!==t||(n=t,t=void 0),!0!==o&&!1!==o||(n=o,o=void 0),(s(e)&&l(e)||r(e)&&0===e.length)&&(e=void 0),a._isAMomentObject=!0,a._useUTC=a._isUTC=i,a._l=o,a._i=e,a._f=t,a._strict=n,Uo(a)}function Jo(e,t,o,n){return Ko(e,t,o,n,!1)}n.createFromInputFallback=x(\"value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.\",(function(e){e._d=new Date(e._i+(e._useUTC?\" UTC\":\"\"))})),n.ISO_8601=function(){},n.RFC_2822=function(){};var $o=x(\"moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/\",(function(){var e=Jo.apply(null,arguments);return this.isValid()&&e.isValid()?e<this?this:e:k()})),Xo=x(\"moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/\",(function(){var e=Jo.apply(null,arguments);return this.isValid()&&e.isValid()?e>this?this:e:k()}));function Qo(e,t){var o,n;if(1===t.length&&r(t[0])&&(t=t[0]),!t.length)return Jo();for(o=t[0],n=1;n<t.length;++n)t[n].isValid()&&!t[n][e](o)||(o=t[n]);return o}function Go(){return Qo(\"isBefore\",[].slice.call(arguments,0))}function Zo(){return Qo(\"isAfter\",[].slice.call(arguments,0))}var en=function(){return Date.now?Date.now():+new Date},tn=[\"year\",\"quarter\",\"month\",\"week\",\"day\",\"hour\",\"minute\",\"second\",\"millisecond\"];function on(e){var t,o,n=!1;for(t in e)if(a(e,t)&&(-1===ze.call(tn,t)||null!=e[t]&&isNaN(e[t])))return!1;for(o=0;o<tn.length;++o)if(e[tn[o]]){if(n)return!1;parseFloat(e[tn[o]])!==de(e[tn[o]])&&(n=!0)}return!0}function nn(){return this._isValid}function rn(){return Ln(NaN)}function sn(e){var t=ie(e),o=t.year||0,n=t.quarter||0,i=t.month||0,r=t.week||t.isoWeek||0,s=t.day||0,a=t.hour||0,l=t.minute||0,c=t.second||0,d=t.millisecond||0;this._isValid=on(t),this._milliseconds=+d+1e3*c+6e4*l+1e3*a*60*60,this._days=+s+7*r,this._months=+i+3*n+12*o,this._data={},this._locale=vo(),this._bubble()}function an(e){return e instanceof sn}function ln(e){return e<0?-1*Math.round(-1*e):Math.round(e)}function cn(e,t,o){var n,i=Math.min(e.length,t.length),r=Math.abs(e.length-t.length),s=0;for(n=0;n<i;n++)(o&&e[n]!==t[n]||!o&&de(e[n])!==de(t[n]))&&s++;return s+r}function dn(e,t){Y(e,0,0,(function(){var e=this.utcOffset(),o=\"+\";return e<0&&(e=-e,o=\"-\"),o+O(~~(e/60),2)+t+O(~~e%60,2)}))}dn(\"Z\",\":\"),dn(\"ZZ\",\"\"),Pe(\"Z\",Ne),Pe(\"ZZ\",Ne),He([\"Z\",\"ZZ\"],(function(e,t,o){o._useUTC=!0,o._tzm=hn(Ne,e)}));var un=/([\\+\\-]|\\d\\d)/gi;function hn(e,t){var o,n,i=(t||\"\").match(e);return null===i?null:0===(n=60*(o=((i[i.length-1]||[])+\"\").match(un)||[\"-\",0,0])[1]+de(o[2]))?0:\"+\"===o[0]?n:-n}function pn(e,t){var o,i;return t._isUTC?(o=t.clone(),i=(b(e)||u(e)?e.valueOf():Jo(e).valueOf())-o.valueOf(),o._d.setTime(o._d.valueOf()+i),n.updateOffset(o,!1),o):Jo(e).local()}function fn(e){return-Math.round(e._d.getTimezoneOffset())}function mn(e,t,o){var i,r=this._offset||0;if(!this.isValid())return null!=e?this:NaN;if(null!=e){if(\"string\"==typeof e){if(null===(e=hn(Ne,e)))return this}else Math.abs(e)<16&&!o&&(e*=60);return!this._isUTC&&t&&(i=fn(this)),this._offset=e,this._isUTC=!0,null!=i&&this.add(i,\"m\"),r!==e&&(!t||this._changeInProgress?In(this,Ln(e-r,\"m\"),1,!1):this._changeInProgress||(this._changeInProgress=!0,n.updateOffset(this,!0),this._changeInProgress=null)),this}return this._isUTC?r:fn(this)}function gn(e,t){return null!=e?(\"string\"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}function vn(e){return this.utcOffset(0,e)}function kn(e){return this._isUTC&&(this.utcOffset(0,e),this._isUTC=!1,e&&this.subtract(fn(this),\"m\")),this}function yn(){if(null!=this._tzm)this.utcOffset(this._tzm,!1,!0);else if(\"string\"==typeof this._i){var e=hn(Ee,this._i);null!=e?this.utcOffset(e):this.utcOffset(0,!0)}return this}function wn(e){return!!this.isValid()&&(e=e?Jo(e).utcOffset():0,(this.utcOffset()-e)%60==0)}function An(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Cn(){if(!c(this._isDSTShifted))return this._isDSTShifted;var e,t={};return A(t,this),(t=qo(t))._a?(e=t._isUTC?f(t._a):Jo(t._a),this._isDSTShifted=this.isValid()&&cn(t._a,e.toArray())>0):this._isDSTShifted=!1,this._isDSTShifted}function bn(){return!!this.isValid()&&!this._isUTC}function _n(){return!!this.isValid()&&this._isUTC}function xn(){return!!this.isValid()&&this._isUTC&&0===this._offset}n.updateOffset=function(){};var Sn=/^(-|\\+)?(?:(\\d*)[. ])?(\\d+):(\\d+)(?::(\\d+)(\\.\\d*)?)?$/,Bn=/^(-|\\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;function Ln(e,t){var o,n,i,r=e,s=null;return an(e)?r={ms:e._milliseconds,d:e._days,M:e._months}:d(e)||!isNaN(+e)?(r={},t?r[t]=+e:r.milliseconds=+e):(s=Sn.exec(e))?(o=\"-\"===s[1]?-1:1,r={y:0,d:de(s[qe])*o,h:de(s[We])*o,m:de(s[Ke])*o,s:de(s[Je])*o,ms:de(ln(1e3*s[$e]))*o}):(s=Bn.exec(e))?(o=\"-\"===s[1]?-1:1,r={y:Tn(s[2],o),M:Tn(s[3],o),w:Tn(s[4],o),d:Tn(s[5],o),h:Tn(s[6],o),m:Tn(s[7],o),s:Tn(s[8],o)}):null==r?r={}:\"object\"==typeof r&&(\"from\"in r||\"to\"in r)&&(i=Nn(Jo(r.from),Jo(r.to)),(r={}).ms=i.milliseconds,r.M=i.months),n=new sn(r),an(e)&&a(e,\"_locale\")&&(n._locale=e._locale),an(e)&&a(e,\"_isValid\")&&(n._isValid=e._isValid),n}function Tn(e,t){var o=e&&parseFloat(e.replace(\",\",\".\"));return(isNaN(o)?0:o)*t}function En(e,t){var o={};return o.months=t.month()-e.month()+12*(t.year()-e.year()),e.clone().add(o.months,\"M\").isAfter(t)&&--o.months,o.milliseconds=+t-+e.clone().add(o.months,\"M\"),o}function Nn(e,t){var o;return e.isValid()&&t.isValid()?(t=pn(t,e),e.isBefore(t)?o=En(e,t):((o=En(t,e)).milliseconds=-o.milliseconds,o.months=-o.months),o):{milliseconds:0,months:0}}function Mn(e,t){return function(o,n){var i;return null===n||isNaN(+n)||(L(t,\"moment().\"+t+\"(period, number) is deprecated. Please use moment().\"+t+\"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.\"),i=o,o=n,n=i),In(this,Ln(o,n),e),this}}function In(e,t,o,i){var r=t._milliseconds,s=ln(t._days),a=ln(t._months);e.isValid()&&(i=null==i||i,a&&ct(e,he(e,\"Month\")+a*o),s&&pe(e,\"Date\",he(e,\"Date\")+s*o),r&&e._d.setTime(e._d.valueOf()+r*o),i&&n.updateOffset(e,s||a))}Ln.fn=sn.prototype,Ln.invalid=rn;var Pn=Mn(1,\"add\"),On=Mn(-1,\"subtract\");function Dn(e){return\"string\"==typeof e||e instanceof String}function Rn(e){return b(e)||u(e)||Dn(e)||d(e)||Hn(e)||Vn(e)||null==e}function Vn(e){var t,o,n=s(e)&&!l(e),i=!1,r=[\"years\",\"year\",\"y\",\"months\",\"month\",\"M\",\"days\",\"day\",\"d\",\"dates\",\"date\",\"D\",\"hours\",\"hour\",\"h\",\"minutes\",\"minute\",\"m\",\"seconds\",\"second\",\"s\",\"milliseconds\",\"millisecond\",\"ms\"];for(t=0;t<r.length;t+=1)o=r[t],i=i||a(e,o);return n&&i}function Hn(e){var t=r(e),o=!1;return t&&(o=0===e.filter((function(t){return!d(t)&&Dn(e)})).length),t&&o}function Yn(e){var t,o,n=s(e)&&!l(e),i=!1,r=[\"sameDay\",\"nextDay\",\"lastDay\",\"nextWeek\",\"lastWeek\",\"sameElse\"];for(t=0;t<r.length;t+=1)o=r[t],i=i||a(e,o);return n&&i}function Fn(e,t){var o=e.diff(t,\"days\",!0);return o<-6?\"sameElse\":o<-1?\"lastWeek\":o<0?\"lastDay\":o<1?\"sameDay\":o<2?\"nextDay\":o<7?\"nextWeek\":\"sameElse\"}function zn(e,t){1===arguments.length&&(arguments[0]?Rn(arguments[0])?(e=arguments[0],t=void 0):Yn(arguments[0])&&(t=arguments[0],e=void 0):(e=void 0,t=void 0));var o=e||Jo(),i=pn(o,this).startOf(\"day\"),r=n.calendarFormat(this,i)||\"sameElse\",s=t&&(T(t[r])?t[r].call(this,o):t[r]);return this.format(s||this.localeData().calendar(r,this,Jo(o)))}function jn(){return new C(this)}function Un(e,t){var o=b(e)?e:Jo(e);return!(!this.isValid()||!o.isValid())&&(\"millisecond\"===(t=ne(t)||\"millisecond\")?this.valueOf()>o.valueOf():o.valueOf()<this.clone().startOf(t).valueOf())}function qn(e,t){var o=b(e)?e:Jo(e);return!(!this.isValid()||!o.isValid())&&(\"millisecond\"===(t=ne(t)||\"millisecond\")?this.valueOf()<o.valueOf():this.clone().endOf(t).valueOf()<o.valueOf())}function Wn(e,t,o,n){var i=b(e)?e:Jo(e),r=b(t)?t:Jo(t);return!!(this.isValid()&&i.isValid()&&r.isValid())&&(\"(\"===(n=n||\"()\")[0]?this.isAfter(i,o):!this.isBefore(i,o))&&(\")\"===n[1]?this.isBefore(r,o):!this.isAfter(r,o))}function Kn(e,t){var o,n=b(e)?e:Jo(e);return!(!this.isValid()||!n.isValid())&&(\"millisecond\"===(t=ne(t)||\"millisecond\")?this.valueOf()===n.valueOf():(o=n.valueOf(),this.clone().startOf(t).valueOf()<=o&&o<=this.clone().endOf(t).valueOf()))}function Jn(e,t){return this.isSame(e,t)||this.isAfter(e,t)}function $n(e,t){return this.isSame(e,t)||this.isBefore(e,t)}function Xn(e,t,o){var n,i,r;if(!this.isValid())return NaN;if(!(n=pn(e,this)).isValid())return NaN;switch(i=6e4*(n.utcOffset()-this.utcOffset()),t=ne(t)){case\"year\":r=Qn(this,n)/12;break;case\"month\":r=Qn(this,n);break;case\"quarter\":r=Qn(this,n)/3;break;case\"second\":r=(this-n)/1e3;break;case\"minute\":r=(this-n)/6e4;break;case\"hour\":r=(this-n)/36e5;break;case\"day\":r=(this-n-i)/864e5;break;case\"week\":r=(this-n-i)/6048e5;break;default:r=this-n}return o?r:ce(r)}function Qn(e,t){if(e.date()<t.date())return-Qn(t,e);var o=12*(t.year()-e.year())+(t.month()-e.month()),n=e.clone().add(o,\"months\");return-(o+(t-n<0?(t-n)/(n-e.clone().add(o-1,\"months\")):(t-n)/(e.clone().add(o+1,\"months\")-n)))||0}function Gn(){return this.clone().locale(\"en\").format(\"ddd MMM DD YYYY HH:mm:ss [GMT]ZZ\")}function Zn(e){if(!this.isValid())return null;var t=!0!==e,o=t?this.clone().utc():this;return o.year()<0||o.year()>9999?j(o,t?\"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]\":\"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ\"):T(Date.prototype.toISOString)?t?this.toDate().toISOString():new Date(this.valueOf()+60*this.utcOffset()*1e3).toISOString().replace(\"Z\",j(o,\"Z\")):j(o,t?\"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]\":\"YYYY-MM-DD[T]HH:mm:ss.SSSZ\")}function ei(){if(!this.isValid())return\"moment.invalid(/* \"+this._i+\" */)\";var e,t,o,n,i=\"moment\",r=\"\";return this.isLocal()||(i=0===this.utcOffset()?\"moment.utc\":\"moment.parseZone\",r=\"Z\"),e=\"[\"+i+'(\"]',t=0<=this.year()&&this.year()<=9999?\"YYYY\":\"YYYYYY\",o=\"-MM-DD[T]HH:mm:ss.SSS\",n=r+'[\")]',this.format(e+t+o+n)}function ti(e){e||(e=this.isUtc()?n.defaultFormatUtc:n.defaultFormat);var t=j(this,e);return this.localeData().postformat(t)}function oi(e,t){return this.isValid()&&(b(e)&&e.isValid()||Jo(e).isValid())?Ln({to:this,from:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()}function ni(e){return this.from(Jo(),e)}function ii(e,t){return this.isValid()&&(b(e)&&e.isValid()||Jo(e).isValid())?Ln({from:this,to:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()}function ri(e){return this.to(Jo(),e)}function si(e){var t;return void 0===e?this._locale._abbr:(null!=(t=vo(e))&&(this._locale=t),this)}n.defaultFormat=\"YYYY-MM-DDTHH:mm:ssZ\",n.defaultFormatUtc=\"YYYY-MM-DDTHH:mm:ss[Z]\";var ai=x(\"moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.\",(function(e){return void 0===e?this.localeData():this.locale(e)}));function li(){return this._locale}var ci=1e3,di=60*ci,ui=60*di,hi=3506328*ui;function pi(e,t){return(e%t+t)%t}function fi(e,t,o){return e<100&&e>=0?new Date(e+400,t,o)-hi:new Date(e,t,o).valueOf()}function mi(e,t,o){return e<100&&e>=0?Date.UTC(e+400,t,o)-hi:Date.UTC(e,t,o)}function gi(e){var t,o;if(void 0===(e=ne(e))||\"millisecond\"===e||!this.isValid())return this;switch(o=this._isUTC?mi:fi,e){case\"year\":t=o(this.year(),0,1);break;case\"quarter\":t=o(this.year(),this.month()-this.month()%3,1);break;case\"month\":t=o(this.year(),this.month(),1);break;case\"week\":t=o(this.year(),this.month(),this.date()-this.weekday());break;case\"isoWeek\":t=o(this.year(),this.month(),this.date()-(this.isoWeekday()-1));break;case\"day\":case\"date\":t=o(this.year(),this.month(),this.date());break;case\"hour\":t=this._d.valueOf(),t-=pi(t+(this._isUTC?0:this.utcOffset()*di),ui);break;case\"minute\":t=this._d.valueOf(),t-=pi(t,di);break;case\"second\":t=this._d.valueOf(),t-=pi(t,ci)}return this._d.setTime(t),n.updateOffset(this,!0),this}function vi(e){var t,o;if(void 0===(e=ne(e))||\"millisecond\"===e||!this.isValid())return this;switch(o=this._isUTC?mi:fi,e){case\"year\":t=o(this.year()+1,0,1)-1;break;case\"quarter\":t=o(this.year(),this.month()-this.month()%3+3,1)-1;break;case\"month\":t=o(this.year(),this.month()+1,1)-1;break;case\"week\":t=o(this.year(),this.month(),this.date()-this.weekday()+7)-1;break;case\"isoWeek\":t=o(this.year(),this.month(),this.date()-(this.isoWeekday()-1)+7)-1;break;case\"day\":case\"date\":t=o(this.year(),this.month(),this.date()+1)-1;break;case\"hour\":t=this._d.valueOf(),t+=ui-pi(t+(this._isUTC?0:this.utcOffset()*di),ui)-1;break;case\"minute\":t=this._d.valueOf(),t+=di-pi(t,di)-1;break;case\"second\":t=this._d.valueOf(),t+=ci-pi(t,ci)-1}return this._d.setTime(t),n.updateOffset(this,!0),this}function ki(){return this._d.valueOf()-6e4*(this._offset||0)}function yi(){return Math.floor(this.valueOf()/1e3)}function wi(){return new Date(this.valueOf())}function Ai(){var e=this;return[e.year(),e.month(),e.date(),e.hour(),e.minute(),e.second(),e.millisecond()]}function Ci(){var e=this;return{years:e.year(),months:e.month(),date:e.date(),hours:e.hours(),minutes:e.minutes(),seconds:e.seconds(),milliseconds:e.milliseconds()}}function bi(){return this.isValid()?this.toISOString():null}function _i(){return v(this)}function xi(){return p({},g(this))}function Si(){return g(this).overflow}function Bi(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}}function Li(e,t){var o,i,r,s=this._eras||vo(\"en\")._eras;for(o=0,i=s.length;o<i;++o)switch(\"string\"==typeof s[o].since&&(r=n(s[o].since).startOf(\"day\"),s[o].since=r.valueOf()),typeof s[o].until){case\"undefined\":s[o].until=1/0;break;case\"string\":r=n(s[o].until).startOf(\"day\").valueOf(),s[o].until=r.valueOf()}return s}function Ti(e,t,o){var n,i,r,s,a,l=this.eras();for(e=e.toUpperCase(),n=0,i=l.length;n<i;++n)if(r=l[n].name.toUpperCase(),s=l[n].abbr.toUpperCase(),a=l[n].narrow.toUpperCase(),o)switch(t){case\"N\":case\"NN\":case\"NNN\":if(s===e)return l[n];break;case\"NNNN\":if(r===e)return l[n];break;case\"NNNNN\":if(a===e)return l[n]}else if([r,s,a].indexOf(e)>=0)return l[n]}function Ei(e,t){var o=e.since<=e.until?1:-1;return void 0===t?n(e.since).year():n(e.since).year()+(t-e.offset)*o}function Ni(){var e,t,o,n=this.localeData().eras();for(e=0,t=n.length;e<t;++e){if(o=this.clone().startOf(\"day\").valueOf(),n[e].since<=o&&o<=n[e].until)return n[e].name;if(n[e].until<=o&&o<=n[e].since)return n[e].name}return\"\"}function Mi(){var e,t,o,n=this.localeData().eras();for(e=0,t=n.length;e<t;++e){if(o=this.clone().startOf(\"day\").valueOf(),n[e].since<=o&&o<=n[e].until)return n[e].narrow;if(n[e].until<=o&&o<=n[e].since)return n[e].narrow}return\"\"}function Ii(){var e,t,o,n=this.localeData().eras();for(e=0,t=n.length;e<t;++e){if(o=this.clone().startOf(\"day\").valueOf(),n[e].since<=o&&o<=n[e].until)return n[e].abbr;if(n[e].until<=o&&o<=n[e].since)return n[e].abbr}return\"\"}function Pi(){var e,t,o,i,r=this.localeData().eras();for(e=0,t=r.length;e<t;++e)if(o=r[e].since<=r[e].until?1:-1,i=this.clone().startOf(\"day\").valueOf(),r[e].since<=i&&i<=r[e].until||r[e].until<=i&&i<=r[e].since)return(this.year()-n(r[e].since).year())*o+r[e].offset;return this.year()}function Oi(e){return a(this,\"_erasNameRegex\")||zi.call(this),e?this._erasNameRegex:this._erasRegex}function Di(e){return a(this,\"_erasAbbrRegex\")||zi.call(this),e?this._erasAbbrRegex:this._erasRegex}function Ri(e){return a(this,\"_erasNarrowRegex\")||zi.call(this),e?this._erasNarrowRegex:this._erasRegex}function Vi(e,t){return t.erasAbbrRegex(e)}function Hi(e,t){return t.erasNameRegex(e)}function Yi(e,t){return t.erasNarrowRegex(e)}function Fi(e,t){return t._eraYearOrdinalRegex||Le}function zi(){var e,t,o=[],n=[],i=[],r=[],s=this.eras();for(e=0,t=s.length;e<t;++e)n.push(Re(s[e].name)),o.push(Re(s[e].abbr)),i.push(Re(s[e].narrow)),r.push(Re(s[e].name)),r.push(Re(s[e].abbr)),r.push(Re(s[e].narrow));this._erasRegex=new RegExp(\"^(\"+r.join(\"|\")+\")\",\"i\"),this._erasNameRegex=new RegExp(\"^(\"+n.join(\"|\")+\")\",\"i\"),this._erasAbbrRegex=new RegExp(\"^(\"+o.join(\"|\")+\")\",\"i\"),this._erasNarrowRegex=new RegExp(\"^(\"+i.join(\"|\")+\")\",\"i\")}function ji(e,t){Y(0,[e,e.length],0,t)}function Ui(e){return Xi.call(this,e,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)}function qi(e){return Xi.call(this,e,this.isoWeek(),this.isoWeekday(),1,4)}function Wi(){return bt(this.year(),1,4)}function Ki(){return bt(this.isoWeekYear(),1,4)}function Ji(){var e=this.localeData()._week;return bt(this.year(),e.dow,e.doy)}function $i(){var e=this.localeData()._week;return bt(this.weekYear(),e.dow,e.doy)}function Xi(e,t,o,n,i){var r;return null==e?Ct(this,n,i).year:(t>(r=bt(e,n,i))&&(t=r),Qi.call(this,e,t,o,n,i))}function Qi(e,t,o,n,i){var r=At(e,t,o,n,i),s=yt(r.year,0,r.dayOfYear);return this.year(s.getUTCFullYear()),this.month(s.getUTCMonth()),this.date(s.getUTCDate()),this}function Gi(e){return null==e?Math.ceil((this.month()+1)/3):this.month(3*(e-1)+this.month()%3)}Y(\"N\",0,0,\"eraAbbr\"),Y(\"NN\",0,0,\"eraAbbr\"),Y(\"NNN\",0,0,\"eraAbbr\"),Y(\"NNNN\",0,0,\"eraName\"),Y(\"NNNNN\",0,0,\"eraNarrow\"),Y(\"y\",[\"y\",1],\"yo\",\"eraYear\"),Y(\"y\",[\"yy\",2],0,\"eraYear\"),Y(\"y\",[\"yyy\",3],0,\"eraYear\"),Y(\"y\",[\"yyyy\",4],0,\"eraYear\"),Pe(\"N\",Vi),Pe(\"NN\",Vi),Pe(\"NNN\",Vi),Pe(\"NNNN\",Hi),Pe(\"NNNNN\",Yi),He([\"N\",\"NN\",\"NNN\",\"NNNN\",\"NNNNN\"],(function(e,t,o,n){var i=o._locale.erasParse(e,n,o._strict);i?g(o).era=i:g(o).invalidEra=e})),Pe(\"y\",Le),Pe(\"yy\",Le),Pe(\"yyy\",Le),Pe(\"yyyy\",Le),Pe(\"yo\",Fi),He([\"y\",\"yy\",\"yyy\",\"yyyy\"],je),He([\"yo\"],(function(e,t,o,n){var i;o._locale._eraYearOrdinalRegex&&(i=e.match(o._locale._eraYearOrdinalRegex)),o._locale.eraYearOrdinalParse?t[je]=o._locale.eraYearOrdinalParse(e,i):t[je]=parseInt(e,10)})),Y(0,[\"gg\",2],0,(function(){return this.weekYear()%100})),Y(0,[\"GG\",2],0,(function(){return this.isoWeekYear()%100})),ji(\"gggg\",\"weekYear\"),ji(\"ggggg\",\"weekYear\"),ji(\"GGGG\",\"isoWeekYear\"),ji(\"GGGGG\",\"isoWeekYear\"),oe(\"weekYear\",\"gg\"),oe(\"isoWeekYear\",\"GG\"),se(\"weekYear\",1),se(\"isoWeekYear\",1),Pe(\"G\",Te),Pe(\"g\",Te),Pe(\"GG\",Ce,ke),Pe(\"gg\",Ce,ke),Pe(\"GGGG\",Se,we),Pe(\"gggg\",Se,we),Pe(\"GGGGG\",Be,Ae),Pe(\"ggggg\",Be,Ae),Ye([\"gggg\",\"ggggg\",\"GGGG\",\"GGGGG\"],(function(e,t,o,n){t[n.substr(0,2)]=de(e)})),Ye([\"gg\",\"GG\"],(function(e,t,o,i){t[i]=n.parseTwoDigitYear(e)})),Y(\"Q\",0,\"Qo\",\"quarter\"),oe(\"quarter\",\"Q\"),se(\"quarter\",7),Pe(\"Q\",ve),He(\"Q\",(function(e,t){t[Ue]=3*(de(e)-1)})),Y(\"D\",[\"DD\",2],\"Do\",\"date\"),oe(\"date\",\"D\"),se(\"date\",9),Pe(\"D\",Ce),Pe(\"DD\",Ce,ke),Pe(\"Do\",(function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient})),He([\"D\",\"DD\"],qe),He(\"Do\",(function(e,t){t[qe]=de(e.match(Ce)[0])}));var Zi=ue(\"Date\",!0);function er(e){var t=Math.round((this.clone().startOf(\"day\")-this.clone().startOf(\"year\"))/864e5)+1;return null==e?t:this.add(e-t,\"d\")}Y(\"DDD\",[\"DDDD\",3],\"DDDo\",\"dayOfYear\"),oe(\"dayOfYear\",\"DDD\"),se(\"dayOfYear\",4),Pe(\"DDD\",xe),Pe(\"DDDD\",ye),He([\"DDD\",\"DDDD\"],(function(e,t,o){o._dayOfYear=de(e)})),Y(\"m\",[\"mm\",2],0,\"minute\"),oe(\"minute\",\"m\"),se(\"minute\",14),Pe(\"m\",Ce),Pe(\"mm\",Ce,ke),He([\"m\",\"mm\"],Ke);var tr=ue(\"Minutes\",!1);Y(\"s\",[\"ss\",2],0,\"second\"),oe(\"second\",\"s\"),se(\"second\",15),Pe(\"s\",Ce),Pe(\"ss\",Ce,ke),He([\"s\",\"ss\"],Je);var or,nr,ir=ue(\"Seconds\",!1);for(Y(\"S\",0,0,(function(){return~~(this.millisecond()/100)})),Y(0,[\"SS\",2],0,(function(){return~~(this.millisecond()/10)})),Y(0,[\"SSS\",3],0,\"millisecond\"),Y(0,[\"SSSS\",4],0,(function(){return 10*this.millisecond()})),Y(0,[\"SSSSS\",5],0,(function(){return 100*this.millisecond()})),Y(0,[\"SSSSSS\",6],0,(function(){return 1e3*this.millisecond()})),Y(0,[\"SSSSSSS\",7],0,(function(){return 1e4*this.millisecond()})),Y(0,[\"SSSSSSSS\",8],0,(function(){return 1e5*this.millisecond()})),Y(0,[\"SSSSSSSSS\",9],0,(function(){return 1e6*this.millisecond()})),oe(\"millisecond\",\"ms\"),se(\"millisecond\",16),Pe(\"S\",xe,ve),Pe(\"SS\",xe,ke),Pe(\"SSS\",xe,ye),or=\"SSSS\";or.length<=9;or+=\"S\")Pe(or,Le);function rr(e,t){t[$e]=de(1e3*(\"0.\"+e))}for(or=\"S\";or.length<=9;or+=\"S\")He(or,rr);function sr(){return this._isUTC?\"UTC\":\"\"}function ar(){return this._isUTC?\"Coordinated Universal Time\":\"\"}nr=ue(\"Milliseconds\",!1),Y(\"z\",0,0,\"zoneAbbr\"),Y(\"zz\",0,0,\"zoneName\");var lr=C.prototype;function cr(e){return Jo(1e3*e)}function dr(){return Jo.apply(null,arguments).parseZone()}function ur(e){return e}lr.add=Pn,lr.calendar=zn,lr.clone=jn,lr.diff=Xn,lr.endOf=vi,lr.format=ti,lr.from=oi,lr.fromNow=ni,lr.to=ii,lr.toNow=ri,lr.get=fe,lr.invalidAt=Si,lr.isAfter=Un,lr.isBefore=qn,lr.isBetween=Wn,lr.isSame=Kn,lr.isSameOrAfter=Jn,lr.isSameOrBefore=$n,lr.isValid=_i,lr.lang=ai,lr.locale=si,lr.localeData=li,lr.max=Xo,lr.min=$o,lr.parsingFlags=xi,lr.set=me,lr.startOf=gi,lr.subtract=On,lr.toArray=Ai,lr.toObject=Ci,lr.toDate=wi,lr.toISOString=Zn,lr.inspect=ei,\"undefined\"!=typeof Symbol&&null!=Symbol.for&&(lr[Symbol.for(\"nodejs.util.inspect.custom\")]=function(){return\"Moment<\"+this.format()+\">\"}),lr.toJSON=bi,lr.toString=Gn,lr.unix=yi,lr.valueOf=ki,lr.creationData=Bi,lr.eraName=Ni,lr.eraNarrow=Mi,lr.eraAbbr=Ii,lr.eraYear=Pi,lr.year=gt,lr.isLeapYear=vt,lr.weekYear=Ui,lr.isoWeekYear=qi,lr.quarter=lr.quarters=Gi,lr.month=dt,lr.daysInMonth=ut,lr.week=lr.weeks=Lt,lr.isoWeek=lr.isoWeeks=Tt,lr.weeksInYear=Ji,lr.weeksInWeekYear=$i,lr.isoWeeksInYear=Wi,lr.isoWeeksInISOWeekYear=Ki,lr.date=Zi,lr.day=lr.days=Ut,lr.weekday=qt,lr.isoWeekday=Wt,lr.dayOfYear=er,lr.hour=lr.hours=no,lr.minute=lr.minutes=tr,lr.second=lr.seconds=ir,lr.millisecond=lr.milliseconds=nr,lr.utcOffset=mn,lr.utc=vn,lr.local=kn,lr.parseZone=yn,lr.hasAlignedHourOffset=wn,lr.isDST=An,lr.isLocal=bn,lr.isUtcOffset=_n,lr.isUtc=xn,lr.isUTC=xn,lr.zoneAbbr=sr,lr.zoneName=ar,lr.dates=x(\"dates accessor is deprecated. Use date instead.\",Zi),lr.months=x(\"months accessor is deprecated. Use month instead\",dt),lr.years=x(\"years accessor is deprecated. Use year instead\",gt),lr.zone=x(\"moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/\",gn),lr.isDSTShifted=x(\"isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information\",Cn);var hr=M.prototype;function pr(e,t,o,n){var i=vo(),r=f().set(n,t);return i[o](r,e)}function fr(e,t,o){if(d(e)&&(t=e,e=void 0),e=e||\"\",null!=t)return pr(e,t,o,\"month\");var n,i=[];for(n=0;n<12;n++)i[n]=pr(e,n,o,\"month\");return i}function mr(e,t,o,n){\"boolean\"==typeof e?(d(t)&&(o=t,t=void 0),t=t||\"\"):(o=t=e,e=!1,d(t)&&(o=t,t=void 0),t=t||\"\");var i,r=vo(),s=e?r._week.dow:0,a=[];if(null!=o)return pr(t,(o+s)%7,n,\"day\");for(i=0;i<7;i++)a[i]=pr(t,(i+s)%7,n,\"day\");return a}function gr(e,t){return fr(e,t,\"months\")}function vr(e,t){return fr(e,t,\"monthsShort\")}function kr(e,t,o){return mr(e,t,o,\"weekdays\")}function yr(e,t,o){return mr(e,t,o,\"weekdaysShort\")}function wr(e,t,o){return mr(e,t,o,\"weekdaysMin\")}hr.calendar=P,hr.longDateFormat=W,hr.invalidDate=J,hr.ordinal=Q,hr.preparse=ur,hr.postformat=ur,hr.relativeTime=Z,hr.pastFuture=ee,hr.set=E,hr.eras=Li,hr.erasParse=Ti,hr.erasConvertYear=Ei,hr.erasAbbrRegex=Di,hr.erasNameRegex=Oi,hr.erasNarrowRegex=Ri,hr.months=rt,hr.monthsShort=st,hr.monthsParse=lt,hr.monthsRegex=pt,hr.monthsShortRegex=ht,hr.week=_t,hr.firstDayOfYear=Bt,hr.firstDayOfWeek=St,hr.weekdays=Ht,hr.weekdaysMin=Ft,hr.weekdaysShort=Yt,hr.weekdaysParse=jt,hr.weekdaysRegex=Kt,hr.weekdaysShortRegex=Jt,hr.weekdaysMinRegex=$t,hr.isPM=to,hr.meridiem=io,fo(\"en\",{eras:[{since:\"0001-01-01\",until:1/0,offset:1,name:\"Anno Domini\",narrow:\"AD\",abbr:\"AD\"},{since:\"0000-12-31\",until:-1/0,offset:1,name:\"Before Christ\",narrow:\"BC\",abbr:\"BC\"}],dayOfMonthOrdinalParse:/\\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1===de(e%100/10)?\"th\":1===t?\"st\":2===t?\"nd\":3===t?\"rd\":\"th\")}}),n.lang=x(\"moment.lang is deprecated. Use moment.locale instead.\",fo),n.langData=x(\"moment.langData is deprecated. Use moment.localeData instead.\",vo);var Ar=Math.abs;function Cr(){var e=this._data;return this._milliseconds=Ar(this._milliseconds),this._days=Ar(this._days),this._months=Ar(this._months),e.milliseconds=Ar(e.milliseconds),e.seconds=Ar(e.seconds),e.minutes=Ar(e.minutes),e.hours=Ar(e.hours),e.months=Ar(e.months),e.years=Ar(e.years),this}function br(e,t,o,n){var i=Ln(t,o);return e._milliseconds+=n*i._milliseconds,e._days+=n*i._days,e._months+=n*i._months,e._bubble()}function _r(e,t){return br(this,e,t,1)}function xr(e,t){return br(this,e,t,-1)}function Sr(e){return e<0?Math.floor(e):Math.ceil(e)}function Br(){var e,t,o,n,i,r=this._milliseconds,s=this._days,a=this._months,l=this._data;return r>=0&&s>=0&&a>=0||r<=0&&s<=0&&a<=0||(r+=864e5*Sr(Tr(a)+s),s=0,a=0),l.milliseconds=r%1e3,e=ce(r/1e3),l.seconds=e%60,t=ce(e/60),l.minutes=t%60,o=ce(t/60),l.hours=o%24,s+=ce(o/24),a+=i=ce(Lr(s)),s-=Sr(Tr(i)),n=ce(a/12),a%=12,l.days=s,l.months=a,l.years=n,this}function Lr(e){return 4800*e/146097}function Tr(e){return 146097*e/4800}function Er(e){if(!this.isValid())return NaN;var t,o,n=this._milliseconds;if(\"month\"===(e=ne(e))||\"quarter\"===e||\"year\"===e)switch(t=this._days+n/864e5,o=this._months+Lr(t),e){case\"month\":return o;case\"quarter\":return o/3;case\"year\":return o/12}else switch(t=this._days+Math.round(Tr(this._months)),e){case\"week\":return t/7+n/6048e5;case\"day\":return t+n/864e5;case\"hour\":return 24*t+n/36e5;case\"minute\":return 1440*t+n/6e4;case\"second\":return 86400*t+n/1e3;case\"millisecond\":return Math.floor(864e5*t)+n;default:throw new Error(\"Unknown unit \"+e)}}function Nr(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*de(this._months/12):NaN}function Mr(e){return function(){return this.as(e)}}var Ir=Mr(\"ms\"),Pr=Mr(\"s\"),Or=Mr(\"m\"),Dr=Mr(\"h\"),Rr=Mr(\"d\"),Vr=Mr(\"w\"),Hr=Mr(\"M\"),Yr=Mr(\"Q\"),Fr=Mr(\"y\");function zr(){return Ln(this)}function jr(e){return e=ne(e),this.isValid()?this[e+\"s\"]():NaN}function Ur(e){return function(){return this.isValid()?this._data[e]:NaN}}var qr=Ur(\"milliseconds\"),Wr=Ur(\"seconds\"),Kr=Ur(\"minutes\"),Jr=Ur(\"hours\"),$r=Ur(\"days\"),Xr=Ur(\"months\"),Qr=Ur(\"years\");function Gr(){return ce(this.days()/7)}var Zr=Math.round,es={ss:44,s:45,m:45,h:22,d:26,w:null,M:11};function ts(e,t,o,n,i){return i.relativeTime(t||1,!!o,e,n)}function os(e,t,o,n){var i=Ln(e).abs(),r=Zr(i.as(\"s\")),s=Zr(i.as(\"m\")),a=Zr(i.as(\"h\")),l=Zr(i.as(\"d\")),c=Zr(i.as(\"M\")),d=Zr(i.as(\"w\")),u=Zr(i.as(\"y\")),h=r<=o.ss&&[\"s\",r]||r<o.s&&[\"ss\",r]||s<=1&&[\"m\"]||s<o.m&&[\"mm\",s]||a<=1&&[\"h\"]||a<o.h&&[\"hh\",a]||l<=1&&[\"d\"]||l<o.d&&[\"dd\",l];return null!=o.w&&(h=h||d<=1&&[\"w\"]||d<o.w&&[\"ww\",d]),(h=h||c<=1&&[\"M\"]||c<o.M&&[\"MM\",c]||u<=1&&[\"y\"]||[\"yy\",u])[2]=t,h[3]=+e>0,h[4]=n,ts.apply(null,h)}function ns(e){return void 0===e?Zr:\"function\"==typeof e&&(Zr=e,!0)}function is(e,t){return void 0!==es[e]&&(void 0===t?es[e]:(es[e]=t,\"s\"===e&&(es.ss=t-1),!0))}function rs(e,t){if(!this.isValid())return this.localeData().invalidDate();var o,n,i=!1,r=es;return\"object\"==typeof e&&(t=e,e=!1),\"boolean\"==typeof e&&(i=e),\"object\"==typeof t&&(r=Object.assign({},es,t),null!=t.s&&null==t.ss&&(r.ss=t.s-1)),n=os(this,!i,r,o=this.localeData()),i&&(n=o.pastFuture(+this,n)),o.postformat(n)}var ss=Math.abs;function as(e){return(e>0)-(e<0)||+e}function ls(){if(!this.isValid())return this.localeData().invalidDate();var e,t,o,n,i,r,s,a,l=ss(this._milliseconds)/1e3,c=ss(this._days),d=ss(this._months),u=this.asSeconds();return u?(e=ce(l/60),t=ce(e/60),l%=60,e%=60,o=ce(d/12),d%=12,n=l?l.toFixed(3).replace(/\\.?0+$/,\"\"):\"\",i=u<0?\"-\":\"\",r=as(this._months)!==as(u)?\"-\":\"\",s=as(this._days)!==as(u)?\"-\":\"\",a=as(this._milliseconds)!==as(u)?\"-\":\"\",i+\"P\"+(o?r+o+\"Y\":\"\")+(d?r+d+\"M\":\"\")+(c?s+c+\"D\":\"\")+(t||e||l?\"T\":\"\")+(t?a+t+\"H\":\"\")+(e?a+e+\"M\":\"\")+(l?a+n+\"S\":\"\")):\"P0D\"}var cs=sn.prototype;return cs.isValid=nn,cs.abs=Cr,cs.add=_r,cs.subtract=xr,cs.as=Er,cs.asMilliseconds=Ir,cs.asSeconds=Pr,cs.asMinutes=Or,cs.asHours=Dr,cs.asDays=Rr,cs.asWeeks=Vr,cs.asMonths=Hr,cs.asQuarters=Yr,cs.asYears=Fr,cs.valueOf=Nr,cs._bubble=Br,cs.clone=zr,cs.get=jr,cs.milliseconds=qr,cs.seconds=Wr,cs.minutes=Kr,cs.hours=Jr,cs.days=$r,cs.weeks=Gr,cs.months=Xr,cs.years=Qr,cs.humanize=rs,cs.toISOString=ls,cs.toString=ls,cs.toJSON=ls,cs.locale=si,cs.localeData=li,cs.toIsoString=x(\"toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)\",ls),cs.lang=ai,Y(\"X\",0,0,\"unix\"),Y(\"x\",0,0,\"valueOf\"),Pe(\"x\",Te),Pe(\"X\",Me),He(\"X\",(function(e,t,o){o._d=new Date(1e3*parseFloat(e))})),He(\"x\",(function(e,t,o){o._d=new Date(de(e))})),\n//! moment.js\nn.version=\"2.29.1\",i(Jo),n.fn=lr,n.min=Go,n.max=Zo,n.now=en,n.utc=f,n.unix=cr,n.months=gr,n.isDate=u,n.locale=fo,n.invalid=k,n.duration=Ln,n.isMoment=b,n.weekdays=kr,n.parseZone=dr,n.localeData=vo,n.isDuration=an,n.monthsShort=vr,n.weekdaysMin=wr,n.defineLocale=mo,n.updateLocale=go,n.locales=ko,n.weekdaysShort=yr,n.normalizeUnits=ne,n.relativeTimeRounding=ns,n.relativeTimeThreshold=is,n.calendarFormat=Fn,n.prototype=lr,n.HTML5_FMT={DATETIME_LOCAL:\"YYYY-MM-DDTHH:mm\",DATETIME_LOCAL_SECONDS:\"YYYY-MM-DDTHH:mm:ss\",DATETIME_LOCAL_MS:\"YYYY-MM-DDTHH:mm:ss.SSS\",DATE:\"YYYY-MM-DD\",TIME:\"HH:mm\",TIME_SECONDS:\"HH:mm:ss\",TIME_MS:\"HH:mm:ss.SSS\",WEEK:\"GGGG-[W]WW\",MONTH:\"YYYY-MM\"},n}()})),OneMachineWithMultipleControls=new ce({nameZh:\"一机多控\",name:\"OneMachineWithMultipleControls\",icon:\"https://pt-starimg.didistatic.com/static/starimg/img/nktYHc1alL1635323338193.png\",component:script$j,onLoad(){let e=st();e.channelSerial=`web-${hex_md5(location.pathname)}`,console.log(\"channelSerial:\",e.channelSerial),request.multiControlhook({xhrHostRequest:function(e){let t=st();const o=new URL(e.reqConf.requestInfo.url);if(console.log(\"url:\",e.reqConf.requestInfo.url),o?.href.indexOf(\"http://sso.weidian.com/user/cookie/setvisitor\")>=0&&(console.log(\"socketConnect\",t.socketConnect),console.log(\"isHost\",t.isHost),console.log(\"webSocketState\",t?.mySocket?.webSocketState),console.log(\"urlObject\",o),console.log(\"pid\",e?.reqConf?.pid),console.log(\"did\",e?.reqConf?.did),console.log(\"requestContentType\",e?.reqConf?.requestHeaders[\"Content-Type\"]||e?.reqConf?.requestHeaders[\"content-type\"]||\"\"),console.log(\"requestHeaders\",e?.reqConf?.requestHeaders),console.log(\"requestBody\",e?.reqConf?.requestInfo?.body||\"\"),console.log(\"method\",e?.reqConf?.requestInfo?.method||\"GET\")),t.socketConnect&&t.isHost){let n={type:\"DATA\",contentType:\"request\",channelSerial:t.channelSerial,pid:e.reqConf.pid,data:JSON.stringify({did:e.reqConf.did,aid:t.aid,url:o?.href,scheme:o?.protocol,host:o?.host,port:o?.port,path:`${o?.pathname}${getQueryVariable(\"api\",o?.href)?`?api=${getQueryVariable(\"api\",o?.href)}`:\"\"}`,searchKey:hex_md5(`${o?.pathname}${getQueryVariable(\"api\",o?.href)?`?api=${getQueryVariable(\"api\",o?.href)}`:\"\"}`),query:o?.search?.split(\"?\")[1]||\"\",fragment:o?.hash,requestTime:moment(new Date).format(\"YYYY-MM-DD HH:mm:ss.SSS\"),requestHeaders:JSON.stringify(e?.reqConf?.requestHeaders),requestContentType:e?.reqConf?.requestHeaders[\"Content-Type\"]||e?.reqConf?.requestHeaders[\"content-type\"]||\"\",requestBody:e?.reqConf?.requestInfo?.body||\"\",method:e?.reqConf?.requestInfo?.method||\"GET\",clientProtocol:\"http\",connectSerial:t.connectSerial})};if(t?.mySocket?.webSocketState)t.mySocket.send(n),console.log(\"主机发送Request请求\");else{let e=function(){console.log(\"主机队列发送Request请求\",n),t.mySocket.send(n)};$bus$1.once(\"webSocketState\",e)}}},xhrHostResponse:function(e){try{let i=st();var t=e.getAllResponseHeaders(),o=t.trim().split(/[\\r\\n]+/),n={};if(o.forEach((function(e){var t=e.split(\": \"),o=t.shift(),i=t.join(\": \");n[o]=i})),e.reqConf.headerMap=n,i.socketConnect&&i.isHost){let o={type:\"DATA\",contentType:\"response\",channelSerial:i?.channelSerial,pid:e?.reqConf?.pid,data:JSON.stringify({did:e?.reqConf?.did,responseTime:moment(new Date).format(\"YYYY-MM-DD HH:mm:ss.SSS\"),responseHeaders:JSON.stringify(e?.reqConf?.headerMap),protocol:\"http\",responseContentType:e?.reqConf?.headerMap[\"Content-Type\"]||e?.reqConf?.headerMap[\"content-type\"]||\"\",responseBody:e?.response,responseCode:e?.status,image:(e?.reqConf?.headerMap[\"Content-Type\"]||e?.reqConf?.headerMap[\"content-type\"])?.indexOf(\"image/\")>=0,source:\"\",connectSerial:i.connectSerial,headersString:t,responseXML:\"document\"===e?.responseType?e?.responseXML:null,resRaw:e?.reqConf?.responseInfo?.resRaw})};if(i?.mySocket?.webSocketState)console.log(\"主机发送Response请求\"),i.mySocket.send(o);else{let e=function(){console.log(\"主机队列发送Response请求\",o),i.mySocket.send(o)};$bus$1.once(\"webSocketState\",e)}}}catch(e){console.error(e)}},xhrClientQuery:function(e,...t){let o=st(),n=guid$1(),i=guid$1();this.reqConf.did=n,this.reqConf.pid=i;const r=new URL(this.reqConf.requestInfo.url);if(o.socketConnect&&!o.isHost){let n={type:\"DATA\",contentType:\"query\",channelSerial:o.channelSerial,pid:i,data:JSON.stringify({aid:o.aid,url:r?.href,scheme:r?.protocol,host:r?.host,port:r?.port,path:`${r?.pathname}${getQueryVariable(\"api\",r?.href)?`?api=${getQueryVariable(\"api\",r?.href)}`:\"\"}`,searchKey:hex_md5(`${r?.pathname}${getQueryVariable(\"api\",r?.href)?`?api=${getQueryVariable(\"api\",r?.href)}`:\"\"}`),query:r?.search?.split(\"?\")[1]||\"\",fragment:r?.hash,requestTime:moment(new Date).format(\"YYYY-MM-DD HH:mm:ss.SSS\"),requestHeaders:JSON.stringify(this.reqConf?.requestHeaders),requestContentType:this.reqConf.requestHeaders[\"Content-Type\"]||this.reqConf.requestHeaders[\"content-type\"]||\"\",requestBody:this.reqConf?.requestInfo?.body||\"\",method:this.reqConf?.requestInfo?.method||\"GET\",clientProtocol:\"http\",connectSerial:o.connectSerial})};if(o?.mySocket?.webSocketState){console.log(\"从机发送请求\",r?.href);let s=n=>{let r=JSON.parse(n.data);if(\"DATA\"===r.type&&r.pid===i){if(404!==r.code){[\"readyState\",\"response\",\"responseText\",\"status\",\"statusText\",\"responseURL\",\"responseXML\"].forEach((e=>{Object.defineProperty(this,e,{writable:!0})}));let e=JSON.parse(r.data),t=e.responseBody;this.response=this.responseText=t,this.status=e.responseCode||200,this.statusText=HTTP_STATUS_CODES[this.status],this.responseHeader=JSON.parse(e.responseHeaders),this.headersString=e.headersString,this.responseURL=e.url,this.responseXML=e.responseXML,this.readyState=2,this.dispatchEvent(new Event(\"readystatechange\")),this.readyState=3,this.dispatchEvent(new Event(\"readystatechange\")),this.readyState=4,this.dispatchEvent(new Event(\"readystatechange\")),this.dispatchEvent(new Event(\"load\")),this.dispatchEvent(new Event(\"loadend\"))}else this.socketCode=404,e.apply(this,t);o.mySocket.socket.removeEventListener(\"message\",s)}};o.mySocket.socket.addEventListener(\"message\",s),o.mySocket.send(n),console.log(\"从机发送请求成功\",r?.href)}else{let r=this,s=function(){let s=n=>{let a=JSON.parse(n.data);if(\"DATA\"===a.type&&a.pid===i){if(404!==a.code){[\"readyState\",\"response\",\"responseText\",\"status\",\"statusText\",\"responseURL\",\"responseXML\"].forEach((e=>{Object.defineProperty(r,e,{writable:!0})}));let e=JSON.parse(a.data),t=e.responseBody;r.response=r.responseText=t,r.status=e.responseCode||200,r.statusText=HTTP_STATUS_CODES[r.status],r.responseHeader=JSON.parse(e.responseHeaders),r.headersString=e.headersString,r.responseURL=e.url,r.responseXML=e.responseXML,r.readyState=2,r.dispatchEvent(new Event(\"readystatechange\")),r.readyState=3,r.dispatchEvent(new Event(\"readystatechange\")),r.readyState=4,r.dispatchEvent(new Event(\"readystatechange\")),r.dispatchEvent(new Event(\"load\")),r.dispatchEvent(new Event(\"loadend\"))}else r.socketCode=404,e.apply(r,t);o.mySocket.socket.removeEventListener(\"message\",s)}};o.mySocket.socket.addEventListener(\"message\",s),o.mySocket.send(n),console.log(\"从机队列发送请求成功\",n)};$bus$1.once(\"webSocketState\",s)}}},getResponseHeader:function(e,t){let o=st();return!o.socketConnect||o.isHost||404===this.socketCode?e.call(this,t):this.responseHeader[t]},getAllResponseHeaders:function(e,...t){let o=st();return!o.socketConnect||o.isHost||404===this.socketCode?e.apply(this,t):this.headersString},isOriginSend:function(e,...t){let o=st();o.socketConnect&&!o.isHost||e.apply(this,t)},fetchHostRequest:function(e,t,...o){let n=st();const i=new URL(completionUrlProtocol(o[0]));if(console.log(\"url:\",i?.href),n.socketConnect&&n.isHost){let r={type:\"DATA\",contentType:\"request\",channelSerial:n.channelSerial,pid:t,data:JSON.stringify({did:e,aid:n.aid,url:i?.href,scheme:i?.protocol,host:i?.host,port:i?.port,query:i?.search?.split(\"?\")[1]||\"\",fragment:i?.hash,requestTime:moment(new Date).format(\"YYYY-MM-DD HH:mm:ss.SSS\"),requestHeaders:o.length>1?JSON.stringify(o[1].headers||{}):JSON.stringify({}),requestContentType:o.length>1&&o[1].headers&&o[1].headers[\"Content-Type\"]||\"\",requestBody:o.length>1&&o[1].body||\"\",method:o.length>1&&o[1].method||\"GET\",clientProtocol:\"http\",path:`${i?.pathname}${getQueryVariable(\"api\",i?.href)?`?api=${getQueryVariable(\"api\",i?.href)}`:\"\"}`,searchKey:hex_md5(`${i?.pathname}${getQueryVariable(\"api\",i?.href)?`?api=${getQueryVariable(\"api\",i?.href)}`:\"\"}`),connectSerial:n.connectSerial})};if(n?.mySocket?.webSocketState)console.log(\"主机发送请求\"),n.mySocket.send(r);else{let e=function(){console.log(\"主机队列发送Fetch,Request请求\",r),n.mySocket.send(r)};$bus$1.once(\"webSocketState\",e)}}},fetchHostResponse:function(e,t,o){let n=st();if(n.socketConnect&&n.isHost){const i=strMapToObj(t.headers);let r={type:\"DATA\",contentType:\"response\",channelSerial:n.channelSerial,data:JSON.stringify({did:e,responseTime:moment(new Date).format(\"YYYY-MM-DD HH:mm:ss.SSS\"),responseHeaders:JSON.stringify(i),protocol:\"http\",responseContentType:t.headers.get(\"Content-Type\"),responseBody:JSON.stringify(JSON.parse(o)),responseCode:t.status,image:t.headers.get(\"Content-Type\").indexOf(\"image/\")>=0,source:\"\",connectSerial:n.connectSerial,resRaw:o})};if(n?.mySocket?.webSocketState)n.mySocket.send(r);else{let e=function(){console.log(\"主机队列发送Fetch,Response请求\",item),n.mySocket.send(r)};$bus$1.once(\"webSocketState\",e)}}},fetchClientQuery:function(e,...t){const o=new URL(completionUrlProtocol(t[0]));let n=st();if(n.socketConnect&&!n.isHost){let i={type:\"DATA\",contentType:\"query\",channelSerial:n.channelSerial,pid:e,data:JSON.stringify({aid:n.aid,url:o?.href,scheme:o?.protocol,host:o?.host,port:o?.port,path:`${o?.pathname}${getQueryVariable(\"api\",o?.href)?`?api=${getQueryVariable(\"api\",o?.href)}`:\"\"}`,searchKey:hex_md5(`${o?.pathname}${getQueryVariable(\"api\",o?.href)?`?api=${getQueryVariable(\"api\",o?.href)}`:\"\"}`),query:t.length>1&&t[1].query||\"\",fragment:o?.hash,requestTime:moment(new Date).format(\"YYYY-MM-DD HH:mm:ss.SSS\"),requestHeaders:t.length>1?JSON.stringify(t[1].headers||{}):JSON.stringify({}),requestContentType:t.length>1&&t[1].headers&&t[1].headers[\"Content-Type\"]||\"\",requestBody:t.length>1&&t[1].body||\"\",method:t.length>1&&t[1].method||\"GET\",clientProtocol:\"http\",connectSerial:n.connectSerial})};if(n?.mySocket?.webSocketState)n.mySocket.send(i);else{let e=function(){console.log(\"从机队列发送Fetch请求成功\",i),n.mySocket.send(i)};$bus$1.once(\"webSocketState\",e)}}},fetchResult:function(e,t,o,...n){let i=null,r=st();return r.socketConnect&&(r.isHost||(i=new Promise(((s,a)=>{let l=a=>{let c=JSON.parse(a.data);if(\"DATA\"===c.type&&c.pid===e){if(404!==c.code){let e=JSON.parse(c.data);this.emit(\"REQUEST.DONE\",{id:t,responseInfo:{contentType:e.contentType,status:e.responseCode,resRaw:e.resRaw}});let o=e.responseBody,n={status:e.responseCode,statusText:\"OK\"},i=new Response(o,n);s(i)}else i=o(...n),i.then((e=>{e.clone().text().then((o=>{s(e),this.emit(\"REQUEST.DONE\",{id:t,responseInfo:{contentType:e.headers.get(\"Content-Type\"),status:e.status,resRaw:o}})}))}));r.mySocket.socket.removeEventListener(\"message\",l)}};if(r?.mySocket?.webSocketState)r.mySocket.socket.addEventListener(\"message\",l);else{let e=function(){r.mySocket.socket.addEventListener(\"message\",l)};$bus$1.once(\"webSocketState\",e)}})))),i}})}}),script$i={props:{requestItem:[Object],index:[Number]},computed:{requestInfo(){let{requestInfo:{url:e,method:t,contentType:o,body:n},responseInfo:{status:i,resRaw:r,type:s}={},type:a}=this.requestItem,l=getPartUrlByParam(e,\"query\"),c=l?getQueryMap(l):null;try{r=JSON.stringify(JSON.parse(r),null,2)}catch(e){}return{path:getPartUrlByParam(e,\"path\"),url:e,method:t,status:i,type:a,resRaw:r,headers:{contentType:o},queryMap:c,body:n,resType:s}}},data:()=>({showContent:!1})};const _withScopeId$4=e=>(pushScopeId(\"data-v-ae8504fe\"),e=e(),popScopeId(),e),_hoisted_1$g={class:\"request-url\"},_hoisted_2$a={class:\"request-type\"},_hoisted_3$9={class:\"request-method\"},_hoisted_4$8={class:\"request-status\"},_hoisted_5$7={class:\"request-toggle-icon\"},_hoisted_6$7={key:0},_hoisted_7$5={key:1},_hoisted_8$4={class:\"response-info\"},_hoisted_9$4={class:\"response-info-item\"},_hoisted_10$3=_withScopeId$4((()=>createBaseVNode(\"div\",{class:\"response-info-item_title\"},\"origin url\",-1))),_hoisted_11$2={class:\"response-info-item_content\"},_hoisted_12$2={class:\"response-info-item_content-key\"},_hoisted_13$2={key:0,class:\"response-info-item\"},_hoisted_14$2=_withScopeId$4((()=>createBaseVNode(\"div\",{class:\"response-info-item_title\"},\"query params\",-1))),_hoisted_15$2={class:\"response-info-item_content-key\"},_hoisted_16$1={class:\"response-info-item_content-value\"},_hoisted_17$1={key:1,class:\"response-info-item\"},_hoisted_18$1=_withScopeId$4((()=>createBaseVNode(\"div\",{class:\"response-info-item_title\"},\"body params\",-1))),_hoisted_19$1={class:\"response-info-item_content\"},_hoisted_20={class:\"response-info-item_content-value\"},_hoisted_21={key:2,class:\"response-info-item\"},_hoisted_22=_withScopeId$4((()=>createBaseVNode(\"div\",{class:\"response-info-item_title\"},\"headers\",-1))),_hoisted_23={class:\"response-info-item_content\"},_hoisted_24=_withScopeId$4((()=>createBaseVNode(\"span\",{class:\"response-info-item_content-key\"},\"content-type\",-1))),_hoisted_25={class:\"response-info-item_content-value\"},_hoisted_26={key:3,class:\"response-info-item\"},_hoisted_27=_withScopeId$4((()=>createBaseVNode(\"div\",{class:\"response-info-item_title\"},\"response\",-1)));function render$i(e,t,o,n,i,r){const s=resolveComponent(\"do-col\"),a=resolveComponent(\"do-row\");return openBlock(),createElementBlock(\"div\",{class:normalizeClass([\"request-item\",\"error\"===r.requestInfo.resType&&\"request-item-error\"])},[createVNode(a,{class:\"request-info\",onClick:t[0]||(t[0]=e=>i.showContent=!i.showContent)},{default:withCtx((()=>[createVNode(s,{span:12},{default:withCtx((()=>[createBaseVNode(\"div\",_hoisted_1$g,toDisplayString(r.requestInfo.path),1)])),_:1}),createVNode(s,{span:3},{default:withCtx((()=>[createBaseVNode(\"div\",_hoisted_2$a,toDisplayString(r.requestInfo.type),1)])),_:1}),createVNode(s,{span:4},{default:withCtx((()=>[createBaseVNode(\"div\",_hoisted_3$9,toDisplayString(r.requestInfo.method),1)])),_:1}),createVNode(s,{span:3},{default:withCtx((()=>[createBaseVNode(\"div\",_hoisted_4$8,toDisplayString(r.requestInfo.status),1)])),_:1}),createVNode(s,{span:2},{default:withCtx((()=>[createBaseVNode(\"div\",_hoisted_5$7,[i.showContent?(openBlock(),createElementBlock(\"span\",_hoisted_7$5,\"▾\")):(openBlock(),createElementBlock(\"span\",_hoisted_6$7,\"▸\"))])])),_:1})])),_:1}),withDirectives(createBaseVNode(\"div\",_hoisted_8$4,[createBaseVNode(\"div\",_hoisted_9$4,[_hoisted_10$3,createBaseVNode(\"div\",_hoisted_11$2,[createBaseVNode(\"span\",_hoisted_12$2,toDisplayString(r.requestInfo.url),1)])]),r.requestInfo.queryMap?(openBlock(),createElementBlock(\"div\",_hoisted_13$2,[_hoisted_14$2,(openBlock(!0),createElementBlock(Fragment,null,renderList(r.requestInfo.queryMap,((e,t)=>(openBlock(),createElementBlock(\"div\",{class:\"response-info-item_content\",key:t},[createBaseVNode(\"span\",_hoisted_15$2,toDisplayString(t),1),createBaseVNode(\"span\",_hoisted_16$1,toDisplayString(e),1)])))),128))])):createCommentVNode(\"\",!0),r.requestInfo.body?(openBlock(),createElementBlock(\"div\",_hoisted_17$1,[_hoisted_18$1,createBaseVNode(\"div\",_hoisted_19$1,[createBaseVNode(\"span\",_hoisted_20,toDisplayString(r.requestInfo.body),1)])])):createCommentVNode(\"\",!0),r.requestInfo.headers&&r.requestInfo.headers.contentType?(openBlock(),createElementBlock(\"div\",_hoisted_21,[_hoisted_22,createBaseVNode(\"div\",_hoisted_23,[_hoisted_24,createBaseVNode(\"span\",_hoisted_25,toDisplayString(r.requestInfo.headers&&r.requestInfo.headers.contentType),1)])])):createCommentVNode(\"\",!0),r.requestInfo.resRaw?(openBlock(),createElementBlock(\"div\",_hoisted_26,[_hoisted_27,createBaseVNode(\"pre\",null,toDisplayString(r.requestInfo.resRaw),1)])):createCommentVNode(\"\",!0)],512),[[vShow,i.showContent]])],2)}var css_248z$g=\".request-item-error[data-v-ae8504fe] {\\n  background-color: #FEF0F0;\\n  border: 1px solid #F8D8D7 !important;\\n}\\n.request-item[data-v-ae8504fe] {\\n  margin-top: 10px;\\n  border-radius: 5px;\\n  overflow: hidden;\\n  border: 1px solid #d6e4ef;\\n  font-size: 12px;\\n  padding: 5px;\\n}\\n.request-item[data-v-ae8504fe] .request-info[data-v-ae8504fe] {\\n  display: flex;\\n  line-height: 24px;\\n  text-align: center;\\n}\\n.request-item[data-v-ae8504fe] .request-info[data-v-ae8504fe] .request-url[data-v-ae8504fe] {\\n  text-align: left;\\n  word-wrap: break-word;\\n  word-break: normal;\\n  color: #1485ee;\\n}\\n.request-item[data-v-ae8504fe] .response-info[data-v-ae8504fe] {\\n  border-top: 1px solid #d6e4ef;\\n}\\n.request-item[data-v-ae8504fe] .response-info[data-v-ae8504fe] .response-info-item[data-v-ae8504fe] {\\n  margin-top: 5px;\\n}\\n.request-item[data-v-ae8504fe] .response-info[data-v-ae8504fe] .response-info-item[data-v-ae8504fe] .response-info-item_title[data-v-ae8504fe] {\\n  font-size: 16px;\\n}\\n.request-item[data-v-ae8504fe] .response-info[data-v-ae8504fe] .response-info-item[data-v-ae8504fe] .response-info-item_content[data-v-ae8504fe] .response-info-item_content-key[data-v-ae8504fe] {\\n  color: #1485ee;\\n  margin-right: 10px;\\n  word-break: break-all;\\n  white-space: normal;\\n}\\n.request-item[data-v-ae8504fe] .response-info[data-v-ae8504fe] pre[data-v-ae8504fe] {\\n  margin: 0;\\n  margin-top: 5px;\\n  min-height: 100px;\\n  max-height: 300px;\\n  overflow-y: scroll;\\n  border: 1px solid #aaa;\\n  border-radius: 5px;\\n  white-space: pre-wrap;\\n  word-break: break-word;\\n}\\n\";styleInject(css_248z$g),script$i.render=render$i,script$i.__scopeId=\"data-v-ae8504fe\";var script$h={components:{requestItem:script$i},data(){return{requestList:this.$store.state.requestList||[]}}};const _hoisted_1$f={class:\"network-plugin\"},_hoisted_2$9={class:\"network-header\"},_hoisted_3$8=createTextVNode(\"url\"),_hoisted_4$7=createTextVNode(\"type\"),_hoisted_5$6=createTextVNode(\"method\"),_hoisted_6$6=createTextVNode(\"status\");function render$h(e,t,o,n,i,r){const s=resolveComponent(\"do-col\"),a=resolveComponent(\"do-row\"),l=resolveComponent(\"requestItem\");return openBlock(),createElementBlock(\"div\",_hoisted_1$f,[createBaseVNode(\"div\",_hoisted_2$9,[createVNode(a,null,{default:withCtx((()=>[createVNode(s,{span:12},{default:withCtx((()=>[_hoisted_3$8])),_:1}),createVNode(s,{span:3},{default:withCtx((()=>[_hoisted_4$7])),_:1}),createVNode(s,{span:4},{default:withCtx((()=>[_hoisted_5$6])),_:1}),createVNode(s,{span:3},{default:withCtx((()=>[_hoisted_6$6])),_:1})])),_:1})]),(openBlock(!0),createElementBlock(Fragment,null,renderList(i.requestList,((e,t)=>(openBlock(),createBlock(l,{key:e.id,index:t,requestItem:e},null,8,[\"index\",\"requestItem\"])))),128))])}var css_248z$f=\".network-plugin {\\n  padding: 5px;\\n}\\n.network-plugin .network-header {\\n  text-align: center;\\n  font-weight: bold;\\n  font-size: 14px;\\n}\\n\";styleInject(css_248z$f),script$h.render=render$h;var Network=new ce({name:\"Network\",nameZh:\"网络请求\",component:script$h,icon:\"https://pt-starimg.didistatic.com/static/starimg/img/bZhn7wsssA1621588946807.png\",onLoad(){let e=st();e.requestList=[],e.recorderRequestList=[],request.on(\"REQUEST.SEND\",(t=>{console.log(\"REQUEST.SEND\",t),e.requestList.push(t),e.startRecorder&&e.recorderRequestList.push(t)})),request.on(\"REQUEST.DONE\",(t=>{console.log(\"REQUEST.DONE\",t);let o=e.requestList.findIndex((e=>e.id===t.id)),n=e.recorderRequestList.findIndex((e=>e.id===t.id));e.requestList[o].responseInfo=t.responseInfo,e.startRecorder&&-1!==n&&(e.recorderRequestList[n].responseInfo=t.responseInfo,console.log(e.recorderRequestList))})),request.on(\"REQUEST.ERROR\",(t=>{console.log(\"REQUEST.ERROR\",t);let o=e.requestList.findIndex((e=>e.id===t.id)),n=e.recorderRequestList.findIndex((e=>e.id===t.id));e.requestList[o].responseInfo={...e.requestList[o].responseInfo,...t.responseInfo},e.startRecorder&&(e.recorderRequestList[n].responseInfo={...e.recorderRequestList[n].responseInfo,...t.responseInfo},console.log(e.recorderRequestList))}))},onUnload(){}}),script$g={components:{Card:script$w},props:{title:{default:\"\"},infoMap:{default:{}}},data:()=>({keyword:\"\"}),computed:{filteredMap(){if(this.keyword){let e=Object.create({});for(const t in this.infoMap)Object.hasOwnProperty.call(this.infoMap,t)&&(this.infoMap[t].indexOf(this.keyword)>-1||t.indexOf(this.keyword)>-1)&&(e[t]=this.infoMap[t]);return e}return this.infoMap}},methods:{removeItem(e){window.confirm(`是否确认清除${e}`)&&this.$emit(\"removeItem\",e)},openPrompt(){this.keyword=window.prompt(\"请输入过滤关键词\",this.keyword?this.keyword:\"\")},clearAll(){window.confirm(`将清空所有${this.title}数据，是否确认清空？`)&&this.$emit(\"clear\")},refresh(){this.$emit(\"refresh\")}}};const _hoisted_1$e={class:\"info-card-header__opt\"},_hoisted_2$8={class:\"filter-text\"},_hoisted_3$7={class:\"info-card-body\"},_hoisted_4$6=[\"onClick\"],_hoisted_5$5={class:\"info-card-empty\"},_hoisted_6$5=createBaseVNode(\"span\",null,\"empty\",-1),_hoisted_7$4=[_hoisted_6$5];function render$g(e,t,o,n,i,r){const s=resolveComponent(\"DoCol\"),a=resolveComponent(\"DoRow\"),l=resolveComponent(\"Card\");return openBlock(),createBlock(l,{title:o.title},{extra:withCtx((()=>[createBaseVNode(\"div\",_hoisted_1$e,[createBaseVNode(\"div\",{class:normalizeClass([\"filter-box\",i.keyword?\"filter-box-actived\":\"\"])},[createBaseVNode(\"span\",_hoisted_2$8,toDisplayString(i.keyword),1),createBaseVNode(\"span\",{class:\"filter opt-icon\",onClick:t[0]||(t[0]=(...e)=>r.openPrompt&&r.openPrompt(...e))})],2),createBaseVNode(\"span\",{class:\"clear-all opt-icon\",onClick:t[1]||(t[1]=(...e)=>r.clearAll&&r.clearAll(...e))}),createBaseVNode(\"span\",{class:\"refresh opt-icon\",onClick:t[2]||(t[2]=(...e)=>r.refresh&&r.refresh(...e))})])])),body:withCtx((()=>[withDirectives(createBaseVNode(\"div\",_hoisted_3$7,[(openBlock(!0),createElementBlock(Fragment,null,renderList(r.filteredMap,((e,t)=>(openBlock(),createBlock(a,{class:\"\",key:t},{default:withCtx((()=>[createVNode(s,{span:6,class:\"info-key\"},{default:withCtx((()=>[createTextVNode(toDisplayString(t),1)])),_:2},1024),createVNode(s,{span:16,class:\"info-value\"},{default:withCtx((()=>[createTextVNode(\": \"+toDisplayString(e),1)])),_:2},1024),createVNode(s,{span:2,class:\"info-opt\"},{default:withCtx((()=>[createBaseVNode(\"span\",{class:\"info-delete\",onClick:e=>r.removeItem(t)},null,8,_hoisted_4$6)])),_:2},1024)])),_:2},1024)))),128))],512),[[vShow,Object.keys(r.filteredMap).length]]),withDirectives(createBaseVNode(\"div\",_hoisted_5$5,_hoisted_7$4,512),[[vShow,0===Object.keys(r.filteredMap).length]])])),_:1},8,[\"title\"])}var css_248z$e='.info-card-header__opt {\\n  display: flex;\\n  align-items: center;\\n  justify-content: center;\\n}\\n.info-card-header__opt .opt-icon {\\n  display: inline-block;\\n  width: 15px;\\n  height: 15px;\\n  background-size: 15px;\\n  background-repeat: no-repeat;\\n  margin: 0 10px;\\n}\\n.info-card-header__opt .refresh {\\n  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAvFQTFRFAAAAJCQkLCwsLS0tLCwsLCwsLCwsLS0tLCwsLCwsLS0tLCwsLCwsLCwsLCwsLCwsKysrLCwsLCwsLCwsLS0tKysrLCwsLCwsLCwsLCwsKCgoKioqKysrLCwsLCwsLCwsLCwsLS0tLS0tMDAwLCwsKysrLCwsLCwsICAgLS0tKysrLCwsLCwsKysrJiYmLS0tLCwsKysrKioqLS0tKysrLCwsLi4uKioqLCwsLCwsLS0tLi4uLS0tLCwsLS0tKSkpLCwsLCwsLS0tKioqLCwsLCwsLCwsLCwsLi4uKysrLCwsLCwsMzMzLCwsLCwsLCwsLCwsLi4uLS0tLS0tLS0tLCwsLy8vLCwsLCwsLCwsKysrLCwsLS0tLCwsKysrLCwsLCwsLCwsKysrLCwsKysrLS0tLS0tKysrLCwsLS0tKysrLCwsLCwsLCwsLCwsKioqLy8vLCwsLCwsKysrLS0tLCwsLCwsLCwsKysrMDAwLCwsLCwsKCgoLCwsKysrKysrQEBALCwsLS0tLCwsLCwsKysrJycnLCwsLCwsLCwsLS0tLCwsLCwsKysrLS0tLi4uKysrKysrLS0tLCwsLi4uMzMzLS0tLS0tLS0tLCwsLCwsLCwsAAAALCwsLCwsLCwsLCwsLCwsLCwsLCwsKysrLS0tKysrLCwsLS0tKysrLS0tKysrKysrLS0tLCwsKioqLS0tKSkpLCwsJycnLCwsLCwsLCwsKysrLCwsLCwsLi4uKioqLCwsLi4uKysrLCwsLi4uLS0tLCwsLCwsLCwsJCQkLCwsKysrLS0tOTk5LCwsLCwsLi4uKysrLCwsLCwsAAAALCwsKysrLCwsKioqKysrLS0tVVVVLS0tLS0tKysrLS0tKioqLCwsLS0tKSkpLCwsLS0tKysrLCwsLCwsLS0tKysrLS0tLS0tMTExMTExLCwsLCwsLCwsLCwsLS0tKysrLS0tLCwsKysrKysrLS0tKysrKioqLCwsLCws////4ewITwAAAPl0Uk5TAAcuVXuiwcjQ2N/n7/b+8eXLv7KlmYx/USMgYJO54PXHaz4QndnhqghEjsqpWRR9zGUYZ7azVDbwxGY9oNZ4GajoiStMr/qSHCqeaQV67kBWFj+m2WMbreTisClbYof71XlwXFNJT19ufo2su+NLPSbqbUcidKPTdiD06xO+gjsENHehrl4aF8bJKJf3Eo9DJEHlhicPbIjUdVe0AeaVnN2KxYCBSmpSmusRTS9xRTBhJe0Ni/y4ZKTNCzf9OM5oIbfa29EOm5QtCZBdTnzDbwK1vPkxVGUDq4NIMwy6gh+Y9vGnz72rjloaFZHs89yxwpSW3585sQbSvsraFAAAAAFiS0dE+tVtBkoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAk2SURBVHja7Z15XBVVFMfHhyiguBCIKW6kspmKSypqKAKKgQhugLgguOAWLqkpbqGFmrsZ5paSQYiSaQtZAS1WmqatWtmila22aTX/9Z4iMufeee/emXvnzufT/P59nN85X+bNm5m7nJEkS5YsWbJkyZIlS5YsWbJkiaPq2Nzquter7+Hp1aChd6PGTTyb+tzh69fMv/mdoisjV4uWAa1ay6pq09avXeBdoot0DdG+g0yijkFuwaKLVVVIaBgRRLUadbq7s+iSMbJ16UpDUc0SFN5CdOEKdeveg56imqXnPaKrr1Evv95aMW6oT0Qd0QgO9e2ni+KG+t8bKRojZIB+DIcGRg0SiREdwwbDodjBQ0RhxA1lh+GQ131CMOIThrHlsKtHovEcw6kufsRqluQsabfkJNL6CGUbwQXDrpGj1HKOHpNi/zw1bSxDjvQGvDjsGoO/2CfW/ME4ZhzjiQqaMDEmKmNSRGbW5MQpU6dlT28/Y+YsokDP2ZicLWv9wf1sMHJ6uixlju/cefNxsZ1DHliw0DXKIiTwwcW1Pl6Sy4Ijy8vFgVi6bLlzhxUrH6rngiQPljpd8XEoAw5/5xWsWv0wkc0jmXlLnPl0DVT+fb7i06b6OZyeHmsy1lJYBUeva6Pu9aji6rgefKr7u7VBPbP3uo3UdpuWOfmeLqv1h5vBZ8k6OdSvHlu2btPk+Nh29Qvr49xAVHMW7NBxxX1ip5rtBj4gu9TurXZPi9f3D5qmdt7v4QKyWCXbXv1DVYM6qXgHcAB5Ep8qrLluDIciUvD2+5iD7McnKmT1uL0pD5/gKcYgq7BZPA4wwnDoaW9sjiKmIOuwOYrZDrAF4n8VtzEEwd6XPBPOFMOuEuyzc2wuM5CD2LO8lDWHXYcaYTL5sAIZ3R/nzmck6jAuVxQjkAU4Dl6Dgzbcz/wOJiCH8Eebl8q2YPI9ywAkOdZQDkk68hyasLF+kKNtUdsCnhySFKl2M6QL5JjhHPZ/3vPsQRIxHPwnAXaxB3kB5TjCnUOS4liDzEUs5tgM4JCkIrYg85sgFi8awiFJdZmCFCMOxQZxSNJLDEGiEQMPAyeUy9mBoPNRLJ8/oKZk5Pko5M0K5DASX8iRI1SmEB0IMs/p8TI/jt00HHQggUh4Jj+O/VQcdCDIaMAYfhyL6DioQI7D4Fde5QfyGkcQ5BoSxY8jPpYfSCRcXzKL4yqLOEoOGpAKGJvBj4MepJLcuwqEvs5z9R7tV+sNcutSGDuJIwf1yd6e3DkbhL7J92lqEh0Ixe9nOQit4MqhNrasIopHiRAQmvIWZxDpBDlHEIUtvIN7mzeHJI0jxOi4ncY1H0TTT9jSq/PycYUuVfTOuzSeQwDHGgM4uMgNgPC8GHLVSQBCs6DBVCpQctQXXY9WwVNksOiCtOoUAEkXXZBWBQCQTaIL0qpWSo5ZouvRLLAfh+aWwFQKBt+sRfotxcgGQN4TXZBWweu6tiVlJtBpACK6Hs1yV3J4iq5Hs84oQdJE16NZYKk0x4E5OvkHFcgeC/zJA1KVIPeKBrip9bdma8qJd/6AZQbvi0a4obW1KiIc8zwLfrQiRDPcUO2ds0vJQkoACMdZEXIVKkoiO08qAUiWaAiHzilK+oAopgyATBYNISGDwx+uIAmCy08E7ERzWRPRtMJHJjwiB0BNZSRB8014jnwMaqokCco14a8WHEVYTxK0AgR9IprCrghQ06dEUWAlnhmu7GD+ZDFZFFjbZIZ7rQxlSU3IosAaYjPc/e5TltSfLAqsK40RTWFXmrKktmRR55VR3BeVEmiisqTzZFEXlFGxoinsmqAs6QJZVKaWqw9XwWs04WD0ShA2TzSH9Bmo6HOysC9A2FzRHNI0UBHpAjiw7ZxinQEngR2pqaRxM5Vx4ge25igLukgaB+dHhDUsqdZyUA/xDBq813QTDPIlqGcqaSCcQ2SxoV+PfEA9XxFHglndfLEco8F2kg7koeaaZ58KqqH4FTXXyge4sp34FDHZWpSxoBiKU0TM6iA1JYBawmiCBazXUhXcikz1GxoCglOOCuOApVB+O8pBtLgh+a9BJV3pwrP1hbPT2m9AJV3o4pF1v6IOyVZYCO2uuypzHJIkuAW5B61DhTkOyQ5YRndah8hLZjgk8eCuT+7djdoD2T/STgDIdliEH70HsqOn9WjDOYYgDZB7aXBB9lht0GCiT/AuXO6nxQXd9aa3qxWtkpEK+mryQfYh9jQYBOlXdVmbD7ozlF2HRBKNQvKHaHRC9uoONGYz+02VDoTpV2m1QndPtzKOY9dFJPtszWZoL59jhoE0Q3IP1W4W1xBxM2qO9xSSeZiebqwJiF0DY04T9ASRE/T4xaMtr741og93CbqbOkxfx77hiKEhw3VBaFq947aYHpPfcefYiyYdodfThulR/D1njitoSganZjrqyrX3A1wtd1M/MPDFNZSdzpFjBibfeCbOuI7LJ7hxLMVkY3S3ug3Xy7ZTCReMHzHtvGSvHEbuWRhzuQ+ProClHrhU7Ja+GdSnUQrHtpmlWETuUoZ0zvwJv4OazYl+Swb0Mj2A/VrJV5hyqHeXZeUfX4hP4MuYQ7Xfrw+bZcGz8/H2PObK1JoOMjgoST+reP/CgUO9J7bug5LpqeL8Kw8OyUmX8n6bdbhmnVOzvcqJQ5Iuq6WUfyvSaJm5VNWzDzcOp538fx9FP894NLytuiHHLliS83crpATQzVNu/CPFidufXDnwDwu3Rf6aiLEZzl/yw/K+BK+s1k4LkMP2THX1Iroj6YPrOzf5y4gtEtt2yq509dr2eZX44CmLgly/4KZnjgEcEuk7emILYqIOrZ59PSdXys25Hr36UNTfnkSB8lZjMCTOb03azeL5nFT83mMl+xo54i9xe7NYmPHrJ/9JaKi/bqBhCTrfZKJNcdcYcwxl+fYzKh1M0199jWKiRWE4dJjRGyrlAYdFYjgUmKefQs4L1F+Ifh0vvqSLonfxcdEItxRZUaUZo6pC+BtcFSrNLtdAUZ7NY7xSr0Ku0F0k80O1LgDgrxYt248kgig46SZ6MwcBjNP3s8utWwWcMj1EjYJtbqfdzyxMvd3Ro3Hqv2fc67rZzPtaduc6W1JZllhWWXJWdCGWLFmyZMmSJUuWLFmyZOn/ov8AOnjh87MOpuoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDYtMjNUMjE6MjY6MDkrMDg6MDBCOpSGAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA2LTIzVDIxOjI2OjA5KzA4OjAwM2csOgAAAABJRU5ErkJggg==);\\n}\\n.info-card-header__opt .clear-all {\\n  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAtBQTFRFAAAAKysrLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsKysrLi4uLi4uKysrLCwsLCwsLS0tMTExKysrLCwsLS0tLCwsKysrLCwsLCwsKioqLCwsKysrLCwsLCwsLCwsLS0tLCwsLCwsLCwsLCwsLS0tLS0tLCwsKysrLCwsLCwsLCwsLCwsLCwsLS0tKysrLCwsLCwsLCwsLCwsLCwsLCwsKysrLCwsLCwsLCwsLCwsLS0tLCwsLCwsLCwsLCwsLS0tKysrLCwsLCwsLCwsLCwsLCwsLCwsLS0tLCwsKysrLCwsLCwsLCwsLCwsLCwsLS0tKysrLCwsLCwsLCwsLS0tLS0tKysrKysrLCwsLCwsLCwsLCwsLCwsLCwsLS0tLCwsLCwsLCwsLCwsKysrLCwsLCwsLS0tKysrLCwsLCwsLCwsKioqLCwsLCwsKysrKysrLCwsLCwsLCwsLS0tJCQkLCwsLCwsLS0tLCwsLS0tLCwsKysrLCwsLS0tLCwsLCwsLCwsLCwsLS0tLS0tLS0tKysrLCwsLCwsLCwsLCwsLCwsKysrJycnLCwsLCwsLS0tLCwsLCwsKysrLCwsLS0tLCwsMzMzKysrLS0tLS0tLCwsLS0tLCwsLCwsLCwsLCwsLS0tKysrLCwsLS0tLi4uKysrLCwsKysrKysrLS0tLCwsLCwsLCwsLCwsLCwsLS0tKioqKysrLCwsKysrKysrLCwsLCwsLi4uLS0tLCwsLS0tLS0tLS0tLCwsKysrKysrLCwsKysrLCwsKCgoKysrLS0tLi4uLCwsLCwsLCwsKysrKysrLi4uLCwsKysrLS0tLi4uAAAAKysrLS0tLS0tKysrLS0tLS0tKysrLCwsKysrLi4uLS0tLCwsKysrLi4uJCQkQEBALy8vKioqMDAwKSkpOTk5KioqMzMzKSkpLi4uLCwsLCws////LfJrEgAAAO50Uk5TACqAosTm++nYx6NtQRYyfMmqVRWH+NR/atu5NvNw0ae+vbv+uv38t/y2tfq0svmxsPevrfas9aup9KjypqTx8O+gn+6e7Z2c7JrrmeqYlpXolJPnkZDlj+WO5Izji4riieHghoXfhN6Cgd3cTDzaLtlY11zW9gfVV37TfdKCe4N60HnPeHfOds11Rsx0iA1zy3I0ynFvyG4KjU9sxo5AxWloZ0LDZkNlksJkRMFjRcBiYWBHoV9IXZc3SbNbSlpLWZqbTVYgTlA4Ub9SU1RUKaU5HAFepWBra3F3HSQ9PrgvCw4EJjAQHwkYDxknrvwwQkkAAAABYktHRO+4sOKhAAAACXBIWXMAAABIAAAASABGyWs+AAAHlklEQVR42t3d+Z/NZRQH8GOGGGRMlkGWoj1SFJMoZE3GkiWJpkikREoxmjRMImWniFC0i0xFVEoS7atKizbt52/IGDL33nPnPss5z7lzP7/e7/k+5/0a7r3f53m+3wsQLJXS0itXOaFqtYzqNWqeGG5Y5tTKrI1lk3WSdkdOqVO3HkanfrZ2V/Zp0BCpNDpZuzHLpGOcNG6i3ZpVmmL8NNNuziJYbk7Rbo/JgXiqdoNMDsTm2i0apUViCJ6m3aRBTjdwHH4b1m4zYc4wciCeqd1ogpxl6EA8W7vVcnOCsQPxHO1my8m5Fg7EltrtMjkQW2k3HCfnWToQW2u3TKaetQPxfO2miVzg4EBso902kwOxrXbjUbnQ0YF4kXbrEWnn7EBsr908kwMxR7v9/3OxlwOxgzbgaBp7OpLl6vcSbwdiR20EkwOxkzYDLmVxIFZXdlzG5EBMV3V0ZnMgdkkRB2JXNcflrA7EbkqO7swOxB4p4kDsqeDoJeBA7B3ccYWIA7FPYMeVQg7EviniQMwN6Kgq6EDsF8zRX9SBOCBFHIgDgziuEncgDgrgqBLAgThY3DEkiANxqLDj6kAOxGGijmuCORCHCzquDehAHJEiDsSRQo7rAjsQ80Qc1wd3INYVcNyg4EAclSIOxNHMjhuVHIhjWB03qTkQx6aIA3Ecm+NmVQdiGpNjvLIDkWdzqr4D8RYGR0P/Nhhyq7djgjahNP1v83RM1BYci+emqKRxIE7ycVTT7r5MKns4btduPiKTnR13aLceGecNEknmwDunuDnu0m48Jnc7OeoTZ8pOU4U4TXVRjqkA4zQhLjsgpxHnyS95QVMyzd4xnThNZulL9+hB2lk7mhJnKTj24r1qkBYcjhnHXx5TUSD3EecoLHuAlsQSMpM4xazIQ0ZXBAjlKIo+aJQKJMPGcT9xgtmxh6lIbK7cHyDq51AH1lWAzDV3PEiUz6MPfSi4o//DXo758Q7OCw0xX4tbQFQvjH94aMkiU8dionhJeQUjgzqMbwagHAnudB4RElLT0FGbqF2aqCigxHTFZxlRuzxx2fBQjsWPmDkyiNoaJoWPBoKscHesNCsdFsRhODv3GFHawPDfJKwK4Fht1srjROkaUwfAUHGH4RsW5Vhr7gAYnByOykTpOhuHtOQJsyaeJErX2zkABgk6Nrg7nrJ1ADyt7XiGKH3W3iEnec5s+OeJ0hdcHAADRRwbzQZ/kSjd5OYAGCDgMLwmpBybXR0A/bQcjYjSl9wd/JItZsMWE6Uv+zgAclkdr5gNmkmUvurnANga3rGNeLCB+2qjgOQ1wxG3x5bu8HcA9GVyvG443ubY0jc4HAB9WBxvmg7XPKZ0J48D4K2Qjg0xpV4bJCLT29thvqmpR3Sp28pvnLzt6dhlPlT0HsV3OB0APUM5dkeVenwv4Ze8azHOnsjSLG4H8W/XOIbzPqXJjqx9jx8C3UI4ouY9Ggo4ALo6Ofb4DCJ0/6KLZK/lGG0iqqVulesi7oB9EeViz4kptnS8bz1C5HvKAimIpeQD+wEKIs/A+rEekXQLx4cO598ZeYrtYhCobuz4yOX0H0edhOtuAA+J4+Oyo/cFCD6qupORw/XLXiGGk3wi6IBFGFDSMaHD490mK5kkPhd1n2JISbNyHTu9zj0sqKSDmAM+G58kEu/Jm70YVJITx8EwmbYmrCRLyhFznSgs+bw9MRzDJG1JpoaUUH8R70nzY8kPJ8mRdNBrCyIS6pPkC84BCsJIqM92z0Wl6BSGkHxJDOK1yEdllryE+ib/FbcDoEhaQl1bsU/SlmS2rIS6bt8k4QCYJykpDucAmC8noWbpHDeJmGShlISaN3XatGOaJTKSbqEdAE0kJNQqicNmMLss55dQ61bWm/PsU4NbQq2JWm6WdMtKXklvLQfAfk4JtQPCahOuTxivfqk9KWtCOQDWckm26joA1vFIconTGG+258l6Dgm1J3B/WAfL34Tab/p1aAfD/xNqB7DhzSi88XzvovZkG90clGQSar+/wc1aMmngLhmcTA6Pz3jqPqWENwFKxvF7F3UPnPLPzzp9Fx6efA6n6xPqPtFvtB0OkpFEwULT0SSz1E6Sl6wOy+t46okD821Gk4zF3Ar1DIh5dqNJxni+i3oqxxzb0ZJAMjrZHYazqdQzeGa7jCYZgxnuscQh32r3HZs5iSTUs8OKXEeTTIL1E+q5dLPcRwst6VSp9LUD1HxJod94cqFW57rnbpny3feDqB8Xm6Hdb/xQK6Zxk8QOehU7Tgr8R5PMDFNHpnaniVKQIg5DSb52lybJTOyYqt2jWfJTxEHvVCsTwY2E3Mkuz8GyP1Ff0usH7d7ssnEZ7fjxoHZntvlpFfHb3/UFt5fIZfecqMfVTU/7Wbsn18wtyplQiug8c4Dhw6+SNr/U+XXbb4e0uyjJ7tUVNgeOK35vK/87k4IZ0vpgqWOHdif+ObJ+tMf/PPqpBXBI9+ecmJLxB0zS7oEnk8v/7ldxkgettFvgSUvPx30kTYrJxYIKmELYpd0CT1bQTzWscOlx+APxT+0mOPJXyUf73/v8T6Sbav8c/daY9m8y/KqeYyY2OzJj8x9fB7UeKfkpPwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wNi0yM1QyMToyNjowOSswODowMEI6lIYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDYtMjNUMjE6MjY6MDkrMDg6MDAzZyw6AAAAAElFTkSuQmCC);\\n}\\n.info-card-header__opt .filter-box {\\n  display: inline-flex;\\n  justify-content: space-between;\\n  align-items: center;\\n}\\n.info-card-header__opt .filter-box .filter-text {\\n  font-size: 12px;\\n}\\n.info-card-header__opt .filter-box .opt-icon {\\n  margin: 0 10px 0px 5px;\\n}\\n.info-card-header__opt .filter-box-actived {\\n  border-radius: 10px;\\n  padding: 3px 0px 3px 10px;\\n  border: 1px solid #d9e1e8;\\n}\\n.info-card-header__opt .filter {\\n  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAvdQTFRFAAAALy8vLCwsLS0tLCwsLCwsKysrKysrLS0tJiYmMDAwLCwsLCwsLCwsLS0tLCwsLCwsLCwsKysrKysrLCwsLS0tKioqLCwsLCwsLCwsLCwsKysrLS0tLCwsKysrLCwsLCwsLi4uLCwsKysrKysrLCwsLS0tLCwsLCwsLCwsKysrLS0tKysrLCwsLCwsLCwsLCwsLCwsKysrLS0tLCwsLi4uLCwsLCwsLS0tLCwsLy8vKysrLCwsKysrLS0tLCwsLCwsLCwsKysrKioqLCwsKysrLCwsLCwsLCwsLCwsLCwsLCwsLCwsMzMzAAAALS0tLS0tLS0tMzMzKysrLi4uICAgLCwsLS0tKSkpKioqLS0tLCwsKioqLCwsKysrOTk5LCwsLS0tKCgoLCwsLCwsLCwsLCwsKysrLCwsKioqLS0tLCwsMTExLS0tLCwsKysrLCwsLCwsLCwsLCwsLi4uLCwsLS0tLCwsLS0tKysrLi4uKysrLCwsLCwsLS0tLCwsKysrLi4uKysrLS0tLCwsLCwsLCwsLCwsLS0tKysrLCwsKysrLCwsLCwsLS0tLCwsKysrLCwsAAAAKysrLCwsLi4uJycnLCwsLS0tLS0tKysrKioqMTExLi4uLS0tLCwsHBwcKysrKysrLCwsLCwsLCwsLCwsLCwsLi4uLCwsKysrLS0tLS0tLCwsMDAwLCwsLCwsLCwsLS0tKSkpLCwsLCwsLi4uLS0tJCQkLS0tLCwsLS0tLCwsLS0tLi4uLS0tLCwsLy8vLCwsLCwsLCwsKioqLCwsKysrKSkpLCwsLCwsKioqLCwsLCwsLS0tLS0tVVVVLCwsKysrMzMzJCQkLCwsLS0tLS0tLS0tLCwsLCwsLCwsLCwsKioqJycnKCgoLCwsKioqLCwsLCwsLCwsLCwsKysrLS0tLCwsLCwsLCwsLCwsLS0tKysrLCwsKysrLCwsLCwsMzMzLCwsLS0tLS0tLCwsKioqLCwsLCwsLS0tLCws////cOkf3AAAAPt0Uk5TADFiiK2/n3ZVFBCE99pmUta5KkH11DDhKbWsWTPFjlHoC387avCa8sDVtqYv4JfQozrroEVUXZ1P6hsS+plai6SWZSvzk2+GaJB6sowPAYmPPgWwMgizgiUMt68Yu3wJuvwTvnnEF8J1Hs7pGnLY38k04tIhzWvrSVMsXttpW3OHN1h+5vxt+WFC7U1G8UqnSO8CsfhDDS4/IuU8FTgt3gk1X+zKY1fRJ24kbHHLIMcdgHgZhcEWgwexihH7jhxEkibnlakx3dQfovYGm5irlAORjQoOe71nyMzPQNM2GiacQ9fjruRk5fRcTMbfTv2l3MMUuGB3/jeqSzmNlSUkAAAAAWJLR0T8PA6jfwAAAAlwSFlzAAAASAAAAEgARslrPgAAB1dJREFUeNrtnXmcTlUchw9mLClS1skyjLFnyZ5hypZMM2OIbEnGaJK9ZE3SqJjRYinKUlLZEiFSKYqQVm3I0qpI+6Lef2JmGu/93vXcc8/yzuc8f5/zO99n5t733nPuRkgeRYoWi4pWmOIlSpYi7lxQ+sKQ8lxUxtWj7MWyQ3qj3CXOHpeWl53QKxUcTSpWkp2PwqSyg0gV2eloiLH3uEx2Njqq2opUkx2Njup2HjVkJ6MktqaNSC3ZyWiJKwy7+jlq24jEyw5GSx0bkboFLeqpTP2CmA1sRMKOhg2JqjS6/HzKxjZtmoT915rKDmxDsyvCQpawaRQdvv01lx3ZkhaGc8GWNq1aGfak1rJDW9DGkLDtlTbN2iUY2rWXHdtEB+OPVqJtw6uMDa+WHRzoaIyX0Mm2ZbPOxqZdZEc30BWOItc4tO0Gba+VHT6M7pAtybF1Y2h9nez4BZTGw3qyY/OUGGieKlsgnx7okebSoWcvii1RHHXQ43rXLr37QJcbZEsQ089pKNTXQ6d+/aFTLdkaZAB6DPTULflG6DZIrsZNgyHPzf089hySDj2HyvQolQFp6nrvO+wW6Jspz+PW4ZDlNpreI3A9ZaQsj8qjIMloyr/DGOjfVY7H2HGQ43baCneMhwp3yvBImwApqtHX6NsEanQT7zERl9TL+alSuQJU6SjaYxIePmL81ZmM+1lRsR5T0MP37LviVKg0RaTHXejBMDsaOxpqTRPncTd6TGepdg/++s0Q5XEvenRnqxeXZSw3syVbPa8UQ4/7WCumtTUWvL+GCI8u6PEAe81Zs40ly2fz92iNHoFsBzlzjEXrPsjb4yH0eDiYunh8fSSHq8bceegRx140j+z5xsITFnD0eBSnUTMfC654jZnG2rELuXkMNE2jFgVZviVUf/wJTh6LcBoVuzjYAWZA/XFLuHgsxdO7ZU8GPcQ0HOEpDh5L8ESiAodBlsMYU92XyGiZ9TSMsYKDByHPwCijAvw1ySUbp1H1uHgQUgb/753Ya4bxLB4++nPyIKQ2jPTcygCLF0WPKG4ehKyCsYavDqz0GvRoytGDkLUw2vPrAipcAj14X8DMhPFeWB9I2Q3o0YOzByEvwogZGwMomooem7h7EDIIxtz8EnNJ0zRKzPI/bgbpQxgLmqZRohaat8C4g5OZypmmUeKWNKfjkcvrhRcLtr6MHiIXz7bB2H16+630Cl4de1XI4kYBr8HwvXr6q7Mdp1FzXhfqQcgbECAmxU+VHTiN2slnmuPEmxDhLR81duE0anewp6HeaA8h6G8ejMNp1Nt7JHiYf/73UvZvYZpGtZPiQUhzCFKcqnebfdC93juSPMw3Vuyn6IurGba3vgoBD8rveu5pmkb5vKoWFO9BnPc99luDHrQ7WNB8MAACfeipm2ka5f1fyYutSRDpIw+dJEyj3DnQAEJtc+2CZ2pCplHufIyr52633JmmUZ/IVsin3acQbItjc9M0aq1sgQIWb4ZoGxwam6ZRn8mOH8ZGOBlPd2iLHq1khzew/XOfIgmTZEcH1h30JRLP/boqNasP+RDJOiw7tgUrv6AW2X1EdmhLDlOLTJYdOSgR2Ym1iBbRIiqJVC1Ti4aFDMvQPEWO4sMsroxjvtWOh8gxWo1zsF595CEynlYil6ruhQWLTPTlwfqkHgeR5bQKeUQrJzLUnwjFEz+CRMr6E2G8N4qDyGp/IseVEyHUR5FcvlRPJNuPR6J7XeEipNVsWg3WDYvXuVaj6V9RaTRgf4yVj4gEtIhqaBHV0CKqoUVUQ4uohhZRDS2iGlpENbSIamgR1dAiqqFFXNhxmIKFAdzNzUdk09chKvZ9o6RISnM6jXN8q6LISHoPn6+u4itSk3K7yodxHZuDyBFfHi5va5Uh8p0/kRNahJdIodm0+mXROuSywbWwaBHz8xmetizGB9N5iIwYQ2sRYn8ZLg8RQhJpT1HmMd9MzEek0Jw0SkCLqIYWUQ0tohpaRDW0iGpoEdXQIqqhRVRDi6iGFlENLaIaWsSRpWsbRtMwlP2d9lxEcr4P0bKX9b1pPER+oNYIMV8e4SEyxN/1kZPKibShVciD8aljDiIdaRXyOKiciK8LVl4KixbJ8edxSjmRgf5EqisnYv5SqBeyGN8Bvyus1vgWwYiM9SPC+tKH9YZqUceCECELfhTuQQ5AQZv3HNKJkMniz7XICjApvyoAERmYb4GpfzoiRYZZnKlWMX0PIwJESHWrfQ+/8R4JIuSUlclPxk/ZRoQICVmy7LR1E9lpnfjZWiX9/K4SISLmz8HnczzSREhalI1KZoSJEFIyw9ok63SEiRCy5hdrlV9XRpgI+a10Z7tdJbJEzp7s7Q+5IjujRxYkFRIRQjqsKCQiZO7ISg4e8bLj0XA0db6tCOvSpmB22T6Uk8leXCwTf7f0SCgiOxg9fxyyEGF8CaEcev+5Ez12yvmMDTN7evxlFBH0VXEO/N2wcHic5eSZ/zXO/CM7CyP/Jg6OPZGUyvurW/8B9GbxTqZIPjkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDYtMjNUMjE6MjY6MDkrMDg6MDBCOpSGAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA2LTIzVDIxOjI2OjA5KzA4OjAwM2csOgAAAABJRU5ErkJggg==);\\n}\\n.info-card-body {\\n  font-size: 18px;\\n}\\n.info-card-body .info-key {\\n  font-weight: bolder;\\n  overflow-x: scroll;\\n  margin-right: 5px;\\n}\\n.info-card-body .info-value {\\n  overflow-x: scroll;\\n}\\n.info-card-body .info-opt {\\n  text-align: center;\\n}\\n.info-card-body .info-opt span {\\n  display: inline-block;\\n  width: 15px;\\n  height: 15px;\\n  background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAvFQTFRFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////A6m1bgAAAPl0Uk5TAB1FbIaTn6y5xtLf7Pn4697RxLeqnZCDYjsTCjFYgKfO9XZOJwhCy7p1NAI8tvvqpGctG2vm1ZtXCQNSovLhlEY6itrQghxywr5wLo3piD+e/jNRsKDBS7WXDIX99u/n4NnKw+jwZlTYsoxBJnGWvK81JN1jqe5+BW30LH3Ax1APIGCB1qEHaLMrf/wVKeVcKLF8IzaVjgZa489vDoc3hGEZebRTC9tkvRe4X6gSiUe/MlZ4AWqS7V7i3C+LXUNZaQTxq6bNETDFkXpMGK1ApUjkyZkeFER0zI+jWyUa07tNrnNle/MfIhBPPpwWbppV+iGYSUo9yNd3C18G8gAAAAFiS0dE+tVtBkoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAw7SURBVHja7V17XBXHFb4KiIjiEzWC8tAkGlFE8REVQVHjOxrxASQRQQWNJho1CWjwEW3UKD4qiqaGh2ISfBFTMGLU+GgTq+KLVmtsaJJGLKk1Tart/a9cNpe7Z2Z2d2Z3dufya74/Z2fO+b4Luzs7c84Zm80MNGjo4enVyLuxTxPfps38mrdo2ap1G/+27do/1sEUdyYgILBjp6DgELsimrfyD+3cRTRNdTz+hOeTdkp07faUaLpkdA8L7dGTVoWE8F4RvUXTRtAnsk1fNhFO9Os/4GnR7J0YOGhwlD4VEoZEx4iW4MDQYbFGVEgYPkLwDTPymVHGVUgYPaaPMBljxzXlJcOB8RPEPJSfnTiJpwwHQqKfs1zG5DjeKiRMedZSGe2nmiPDAe9plskIm26eDAfiG1giIyGR9t54/oUXZyTNbJmckpLcJHbIrNl+tEp851igIzWYQkHa3HkRL43EB8+ftuDlV4ZQSFm4yGQZr/prUVgcP+hVDSOPj12y9DUNM36vv2GmjnT1GVXGQo9ltKbClr+pLiWzs2kyRvZSc5zktWIlm71Vq4My1Cy+ZZKOzmuUfb62NvVXemz2eVvtCbhuvRk6Nij6e2dh+4367W4KVZ6vZW3mLmPLVsW7e9uvjRrfnq34abxjJ18dObsUHO1+9zc87M/fM0vBwZvv8dSRq+Blah43F/kFCt81URz/veLJLvbu4/lj2Qo9dpP9vMvLQxui+f3vc5XhQMKIZkRXI/iY/4B8F37IXUcNFnkTnWXzsD2cZLnoJTNkOHCA+LY6aNww6XE1JNIsGTU4dJikZK5Rsy8QjG41/OJQx5F+BKevGLM5m2DS01wZNWhAerx4G7HYBLcXzn/WQEAn0uNFv7m9uLXij6zQYbMdJdybY/Qae5nwHNQ1x9WD9Y1x7xH6TM3DLX1slYwa/HYt7j9PjyFP3E6JhTpqUIozyGG3gn9+9JxsrQ7iXJWZQypm4pjJbw8SPsFYtJjPZmHTcdRCmfUyanACU7KOzUBrdHymEB01tzymZALL8E+xv6ggHTbbh5iSk/SDV6NjM1YKE2LrjZIJp34nL8KWAYVuvp5C2RRTLkgEYMsz1i30E3Ea5RNNNw77HvhMrA6bbQzKqCPNqDB01DDROmy2cQil4EKKQeuQQdNFq6jBmWKEFMWy8BxkiK9bxL+8dxay8tPcl89fjAixfIJFxmcIrXNaA9oiA7aJVuBEf4TYJ+rd0Wf2edH869Dhd5BZv9+rdkc+y1rmi+bvQkPkN96j1vl9pPMA0ezlQN5vkz5X6fsF7NtLNHeAQ8ga5AXlrh1hz7NuFt92ANKLUt49Ru6QP4hmjgJZ4U5U6ncR9sscKJo4ikVw16HvJYV+l6GQFaJ54xgBGRaQe5XDXv6iWRNQCPe0YslzR2RBjOsmJC94QI7ppD5PwT5XRHMmohDumAaT+iCrYQ1FcyajALIcROgCtxAYl48sQ/4srf+bq1Aq351njtgDaDbDlz/hHsJU0XwV0QVGe4Si1xOugeurRfNVRjYgeh29nAMuzxTNVgXb4T0Qg1w+CK7OE81WDXBGeANerIDb0NZGETMCLo80hREY8I35pGiuqvjjeEAWbqPB+eLrormqAz5gQXTHwHBwLVA0VXX8CZD1Vb70gWimWvABdOXf7nCer3tj3ircBHTlET5waXWTaKJaWAboDnZdCHhHfuGWaJ7aSJPzjXW1TwMK/yyapjbgN6ArmwAGa5gXk84NJYCwa9sHZFKEnBFNUxtbgBAvZ3MAaC4SzZIGvnLGdTPgj4AQblG2ZuK2nPFZZ+uXQMgd0SRpkAco/+Xn1nbyxqaiOVIBRhE88XPrOXnjF6I5UmEnEOKcAH8lb2wkmiMdwN3ufGyB97qV0X4GUCTnXCm13QF/JjfZxdXCBTnnXVLbX4EQU9Ka+CMSkE6obesG2urBe90B+AUlpV+CTUYxEX/sqABCrta2gRUvg7H01gE8oVJrmwbLmy6LJkgLEByXV9v0tbxpomiCtAAvEmnDp1Le1FY0QVq0lLOW9p8z5U3s+bEVm9sU80Ak264+iFeUUlrADOUbVh2Jdm64zeIXxMdJc5QkeRNrTl4lG1d1sMy8r8gHShNEUNzkKJuOEjamWmDYgQXrplLaEggnOMKkI4avDrt9A7XrCfJhRUaF3GSjqQ361xhBiIF/rUa8heyndh0vHyZFOIJXy7dMQrYysaTAbGrX5+XDJtY2gQ32m0xC/sZbSGtq16DEQmltk4+8qR2TkBW8hRymdt1KPkyK3AKbi6VMQmzcymtJ+O4utWfwGpe+z0Fc2lo2IZf4CjlA7/m4fJy0RQIePXFsQmxVaUxM1XGVwTGIMZd+AC95UzGjEFvhvGJDpfTqcOsGy9YlzL2SgoRA5mRfViGCANfjpZgsuMfOpTqI+egMSEtLPzBW200Lc6LoBkhLSz8NQNuXoinSASQvOJd+QASUWSWUOAMUwXMmHYGdNw71LqwAiAh01h4IkjfuFk2RCvfA7eAsWQcyvkNEc6QCjD5zRmXBJAXri1fqQDqg7IyRDwStoYY8WISlcsZ1HzFwe7qraJI0yJIzdgVoJsubn08QzVIb68FP74o5Adu6olOMabAEEP57XXs1aHeD3FwtfA8I/6OufSAol3jdgAeLANLG5G8+GJu5RTRPLcBbRF5T6D64Us9CAeXJ7TCspp4FZ1bJL8Ev73oVLjscXIPVb908gDkekP0nuAanW/UqpPwBuBhwHFysR0H+ad3h1WhwtR6lXaB5rnDLph4lwmBla2BdySWi6SoD3up7seswPN5HNF1F9P4BEM3DOiCJoZYUMdQDWF3jGmE9cTToUSyasALmfwdokqoMIAV6HjD7sARI4YdqQpc+MKvHPQOy78ITT3rQiHXLWH8kMz+V2KkLTOGrFE2agE3wDzJKoRtMHrXfF00bxxTIUCkRF3kCZ/1LNG8U+yDBYMWOSLGEH0UTR7AzGfLzUOyJlK9wt4Wh5ZBdrErZM6SgyCh6Jxbgc/guVComUgukxIu9m2jycgRBbkmq9d7hypc9I0w0excikB9ZvWbvHfgZaU8+JJq/E4uQOuNa4TfPILrdJZ1kIHqeQLnWCLSQoJvcJmiZ2HGaI35CRtj/LVqDA2gV0P0UJzugtcnTVolWUfMVgp59RRPVe3cXMsgN4v4XIpS2Uo1CKia5QYEq9NipSY/RjZuCKjFwGAAPYKeZLaAceAg7HJc+rtgEYAcKfE899CF2WIfAXDjsvIjMlfSD30YH2wUUjZdwAGUSNZRluBemxLLTISCwgtiMIdY7R2MGhGRV52A0WFOOAsdjJk5ZrwMrhm2v7M5qAysNrvPQDCPYg1FoQX3eogvf4Eo6WSrj3hWMgF+MHkOEg1T8aaqDc8KJFNw/Q6S2HOdxSylmH0pah9MzcO8Feo0V47aOWVSdrh3umuIbRBEzCeasiLlZ9TXB8RQjFklh79NNryQ/jfQDGiyB2Zxg0tfkmVc3gk/mNAoMrUhW+5v4mj/VleSRPtVHEcSzjWfRfhKwIr8tyZ29PQ/b24imR181Q8ecxURnqcYtOzCGaNweXcFbRtg6oqNJbFmeKlhCVjLDk/FQKXVUK6RnllF+odNggMLh2SHR3NJNLl4mu7CnjOX5ay3bq+DGPpjL6aE5XZXslz7NU4cN20+VIe60wZDnVemPlGyPN2FGNDlWUUrWj/r3tg4dOOenaLg1138rJ+4etCvjP8Me6jB5ZrJ/uIrRRB0mqRBqV8OjwxeZju7O33yjTM1emYl1uR/2UJVif3HuAsoZ5dBcrTTfeO6vKYD7MzT82/eXLqm+pGai6kj6leZaVh6ZHkFyZ6IWBweibgW9tTms9917dYseZzpUBZZ/+2ncmp4Uw/02cD5fnogHwRRUXJKu9fPJXJOVwTImbrsFMmqQ4MnCihlJOk+d1YMTO46ZJSO24JJxfgyoWF5mnDSO4HTrE6MSIpONE4cYlWe5Cgmni4yTd6GHyDDK8uxw4wocuHah2jgbQ9hYUmxcxvQ8t6gHYPDPkpZo0VHpFNhYsjRJn4om/Y+623FZgR63Y9lE3Mo+WWXcrymo6Oh1fTaNhr7FuT+5xW2hhvzyiB3TFyso+OGrc7knt68UzZEBbyyL2bfao8DTq5F348bejS7k/rfk5IpT7vqv9At+wf8D/geF3QB8rZaJCgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wNS0yNVQxNzowNTozMiswODowMFGq0BYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDUtMjVUMTc6MDU6MzIrMDg6MDAg92iqAAAAAElFTkSuQmCC\");\\n  background-size: 15px;\\n  background-repeat: no-repeat;\\n}\\n.info-card-empty {\\n  text-align: center;\\n}\\n';styleInject(css_248z$e),script$g.render=render$g;const overrideLocalStorage=function(e){const t=localStorage.setItem.bind(localStorage);localStorage.setItem=function(o,n){t(o,n),e({type:\"setItem\"})};const o=localStorage.removeItem.bind(localStorage);localStorage.removeItem=function(t){o(t),e({type:\"removeItem\"})};const n=localStorage.clear.bind(localStorage);localStorage.clear=function(t){n(),e({type:\"clear\"})}},overrideSessionStorage=function(e){const t=sessionStorage.setItem.bind(sessionStorage);sessionStorage.setItem=function(o,n){t(o,n),e({type:\"setItem\"})};const o=sessionStorage.removeItem.bind(sessionStorage);sessionStorage.removeItem=function(t){o(t),e({type:\"removeItem\"})};const n=sessionStorage.clear.bind(sessionStorage);sessionStorage.clear=function(t){n(),e({type:\"clear\"})}},clearCookie=function(){let e=getCookieMap();for(const t in e)e.hasOwnProperty.call(e,t)&&removeCookieItem(t)},removeCookieItem=function(e){document.cookie=encodeURIComponent(e)+\"=; expires=Thu, 01 Jan 1970 00:00:00 GMT\"},getCookieMap=function(e){const t=Object.create({}),o=document.cookie;return\"\"!==o.trim()&&o.split(\";\").forEach((e=>{const o=(e=e.split(\"=\")).shift().trim();e=decodeURIComponent(e.join(\"=\")),t[o]=e})),t};var script$f={components:{InfoCard:script$g},data:()=>({storageMap:{}}),created(){overrideLocalStorage((()=>{this.updateList()})),this.updateList()},methods:{updateList(){let e={...window.localStorage};for(const t in e)Object.hasOwnProperty.call(e,t)&&(~t.indexOf(\"dokit\")||\"string\"!=typeof e[t])&&delete e[t];this.storageMap=e},removeItem(e){window.localStorage.removeItem(e)},clear(){window.localStorage.clear()}}};function render$f(e,t,o,n,i,r){const s=resolveComponent(\"InfoCard\");return openBlock(),createElementBlock(\"div\",null,[createVNode(s,{infoMap:i.storageMap,title:\"localStorage\",onRefresh:r.updateList,onClear:r.clear,onRemoveItem:r.removeItem},null,8,[\"infoMap\",\"onRefresh\",\"onClear\",\"onRemoveItem\"])])}script$f.render=render$f;var script$e={components:{InfoCard:script$g},data:()=>({storageMap:{}}),created(){overrideSessionStorage((()=>{this.updateList()})),this.updateList()},methods:{updateList(){let e={...window.sessionStorage};for(const t in e)Object.hasOwnProperty.call(e,t)&&(~t.indexOf(\"dokit\")||\"string\"!=typeof e[t])&&delete e[t];this.storageMap=e},removeItem(e){window.sessionStorage.removeItem(e)},clear(){window.sessionStorage.clear()}}};const _hoisted_1$d={style:{\"margin-top\":\"20px\"}};function render$e(e,t,o,n,i,r){const s=resolveComponent(\"InfoCard\");return openBlock(),createElementBlock(\"div\",_hoisted_1$d,[createVNode(s,{infoMap:i.storageMap,title:\"sessionStorage\",onRefresh:r.updateList,onClear:r.clear,onRemoveItem:r.removeItem},null,8,[\"infoMap\",\"onRefresh\",\"onClear\",\"onRemoveItem\"])])}script$e.render=render$e;var script$d={components:{InfoCard:script$g},data:()=>({storageMap:{}}),created(){this.updateList()},methods:{updateList(){this.storageMap=getCookieMap()},removeItem(e){removeCookieItem(e),this.updateList()},clear(){clearCookie(),this.updateList()}}};const _hoisted_1$c={style:{\"margin-top\":\"20px\"}};function render$d(e,t,o,n,i,r){const s=resolveComponent(\"InfoCard\");return openBlock(),createElementBlock(\"div\",_hoisted_1$c,[createVNode(s,{infoMap:i.storageMap,title:\"Cookie\",onRefresh:r.updateList,onClear:r.clear,onRemoveItem:r.removeItem},null,8,[\"infoMap\",\"onRefresh\",\"onClear\",\"onRemoveItem\"])])}script$d.render=render$d;var script$c={components:{localStorage:script$f,sessionStorage:script$e,cookie:script$d}};const _hoisted_1$b={class:\"storage-plugin\"};function render$c(e,t,o,n,i,r){const s=resolveComponent(\"localStorage\"),a=resolveComponent(\"sessionStorage\"),l=resolveComponent(\"cookie\");return openBlock(),createElementBlock(\"div\",_hoisted_1$b,[createVNode(s),createVNode(a),createVNode(l)])}var css_248z$d=\"\\n.storage-plugin{\\n  padding: 5px;\\n}\\n\";styleInject(css_248z$d),script$c.render=render$c;var Storage=new ce({name:\"Storage\",nameZh:\"存储\",component:script$c,icon:\"https://pt-starimg.didistatic.com/static/starimg/img/LM74BpA9bS1621926286444.png\",onLoad(){},onUnload(){}}),script$b={};const _hoisted_1$a={class:\"dokit-hello-world\"},_hoisted_2$7=createBaseVNode(\"div\",{style:{\"font-weight\":\"bold\",\"font-size\":\"30px\",\"font-style\":\"italic\"}},\"Hello Dokit\",-1),_hoisted_3$6=createBaseVNode(\"div\",null,\"Demo Plugin\",-1),_hoisted_4$5=[_hoisted_2$7,_hoisted_3$6];function render$b(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",_hoisted_1$a,_hoisted_4$5)}var css_248z$c=\"\\n.dokit-hello-world{\\n  padding:10px;\\n  text-align: center;\\n}\\n\";styleInject(css_248z$c),script$b.render=render$b;var DemoPlugin=new ce({nameZh:\"测试\",name:\"test\",icon:\"https://pt-starimg.didistatic.com/static/starimg/img/6WONqJCVks1621926657356.png\",component:script$b}),script$a={directives:{dragable:dragable},methods:{remove(){ct(\"test\")}}};const _withScopeId$3=e=>(pushScopeId(\"data-v-b8c750ee\"),e=e(),popScopeId(),e),_hoisted_1$9={class:\"hello-independ\"},_hoisted_2$6=_withScopeId$3((()=>createBaseVNode(\"div\",{style:{\"font-weight\":\"bold\",\"font-size\":\"30px\",\"font-style\":\"italic\"}},\" Hello Dokit \",-1))),_hoisted_3$5=_withScopeId$3((()=>createBaseVNode(\"div\",null,\"Demo Independ Plugin\",-1)));function render$a(e,t,o,n,i,r){const s=resolveDirective(\"dragable\");return withDirectives((openBlock(),createElementBlock(\"div\",_hoisted_1$9,[_hoisted_2$6,_hoisted_3$5,createBaseVNode(\"div\",{onClick:t[0]||(t[0]=(...e)=>r.remove&&r.remove(...e)),style:{\"background-color\":\"red\",color:\"white\",\"margin-top\":\"10px\"}},\" 点击移除当前独立插件 \")])),[[s]])}var css_248z$b=\"\\n.hello-independ[data-v-b8c750ee] {\\n  display: inline-block;\\n  width: 200px;\\n  /* padding: 10px; */\\n  text-align: center;\\n  background-color: white;\\n  border-radius: 20px;\\n  box-shadow: 0 8px 12px #ebedf0;\\n  overflow: hidden;\\n  border: 1px solid red;\\n}\\n\";styleInject(css_248z$b),script$a.render=render$a,script$a.__scopeId=\"data-v-b8c750ee\";var DemoIndependPlugin=new he({nameZh:\"独立插件\",name:\"test\",icon:\"https://pt-starimg.didistatic.com/static/starimg/img/z1346TQD531618997547642.png\",component:script$a});let URL_REG=new RegExp(\"(https?(://))?[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]\");var script$9={data:()=>({url:\"\",historyList:[],editUrlInfo:{baseUrl:\"\",queryList:[{key:\"\",value:\"\"}]}}),created(){this.historyList=JSON.parse(localStorage.getItem(\"dokit-history-list\")||\"[]\")},methods:{jumpToTarget(){if(!URL_REG.test(this.url))return void window.alert(\"输入的地址不符合URL规则\");let e=this.url.startsWith(\"http\")?this.url:`${location.protocol}//${this.url}`;-1===this.historyList.indexOf(e)&&this.addHistory(e),window.location.href=e,this.url=\"\"},addQuery(){this.editUrlInfo.queryList.push({key:\"\",value:\"\"})},delQuery(e){this.editUrlInfo.queryList.splice(e,1)},handleQuickEdit(){let e=this.editUrlInfo.baseUrl.startsWith(\"http\")?this.editUrlInfo.baseUrl:`${location.protocol}//${this.editUrlInfo.baseUrl}`;e=e.indexOf(\"?\")>-1?e:e+\"?\",this.editUrlInfo.queryList.forEach(((t,o)=>{t&&(0===o&&e.endsWith(\"?\")?e+=`${t.key}=${t.value}`:e+=`&${t.key}=${t.value}`)})),-1===this.historyList.indexOf(e)&&this.addHistory(e),window.location.href=e},jumpToUrl(e){window.location.href=e},addHistory(e){this.historyList.push(e),this.updateStorage()},delHistory(e){this.historyList.splice(e,1),this.updateStorage()},clearHistory(){this.historyList=[],this.updateStorage()},updateStorage(){localStorage.setItem(\"dokit-history-list\",window.JSON.stringify(this.historyList))}}};const _withScopeId$2=e=>(pushScopeId(\"data-v-e4e3a6c4\"),e=e(),popScopeId(),e),_hoisted_1$8={class:\"h5-portal\"},_hoisted_2$5={class:\"portal-textarea-container\"},_hoisted_3$4={class:\"portal-opt-area\"},_hoisted_4$4={class:\"url-edit-container\"},_hoisted_5$4=_withScopeId$2((()=>createBaseVNode(\"div\",{class:\"url-edit-container-title\"},\"快捷编辑\",-1))),_hoisted_6$4=createTextVNode(\"baseUrl\"),_hoisted_7$3=[\"onUpdate:modelValue\"],_hoisted_8$3=createTextVNode(\"=\"),_hoisted_9$3=[\"onUpdate:modelValue\"],_hoisted_10$2=_withScopeId$2((()=>createBaseVNode(\"img\",{src:\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAAHzVJREFUeNrtnXl0U1ee57+/J9nYxmYzmH0ziSUIYZMhAdskZKGnTlNzuk91kDM9p+gqmExPzeRMTWfGSMGu41OIsuyuVHdPTXenaiCdpKe6LKiePmfCzKmTsAQsGwKWTRIwemaJ2ZfYZrGxjS29O39ISgx4kaz7dLXcz1/B1rvvd5X38e/q6d3fjyDRhdf2MsP55jqTYkABMUOBpmi5BCWbMS2HiLIZYzkEygaQDSAHoGyA5QSOpi6AdQPoAtDNwLqJqIsx1k2kdDFN61agdDDytzKkqYvUK6379m32i55zMkKiA0h0Vu84MtdnSCtQNFYAaCbGqIAIBQAWxTiUC4yxVkUhlTFq1TR/q5FY68ldL1wR/R4lMlKQCFljryvwG2k9GF4F8CqAyaJjGoVOAj4B8IkPxqOnHM+fEx1QIiEFGYWVO+rnG0DrQP4ixuhVBLJDIqMywieKxhr8UBqadxVdEh1QPCMFGQJL+ZGNIOUPwGgdgOdFx6Mzx8FYAxno940/Lf5EdDDxhhQkyOodDes00jYBtAlgz4qORxBfAtivMP/+k7teaBAdTDyQ0oKsrKhbbmDYxECbkPyZIlKOM8JHMBj3N1U+/4XoYESRcoIs39Ew20j4EwZtEwGviI4nEWDAAQLbrxlpX3Nl8XXR8cSSlBFkReWxBYrPt42ItoJhhuh4EpSbINrtH+jfc8q5oU10MLEg6QVZY68r0Ay0jQFbAUwRHU+S0ElEuxWftudEVUmr6GD0JGkFKXy7filTtG0AbUXg22oJf7qJsJuA3Sd3Fp8RHYweJJ0ghRUNKxnYNjC2DUC66HhShH4Q7SbQ7sad65pFB8OTpBHkWVvd5HQjtgO0XXQsqQ2r7veh+ktnyR3RkfAgKQQprKj7PgNtB8MS0bFIABBaCKy6cWfJh6JDiX4qCczqivrVGkMZwP5EdCySoaDfKYSakzuLToqOZMwzEB3AWFhSeTg705dWBmA75OeMeKcfQHWvcaCmpXJDt+hgIiXhBCncUfc6IyoDsEJ0LJKIOEWM1TTuKvmt6EAiIWEEKap25/TdRw0Ify46FkkUMLybMQFl9duLu0SHEg4JIciqCncJMdRAPi+VLBxnhLKmncV1ogMZjbgXxFJe/ybAagBkiI5FwpU+gMo8jqJfig5kJOJWkDX2z3J9iq+GiP1QdCwS/WCMvWfU0stOVD3XITqWoYhLQVb+pOFlRfPXALRKdCySmODRNG1788/WHxQdyOPEnSCW8rq3AKoBoIiORRJT/ADb7nGUvCM6kMHElSCWivp3wNhfiI5DIhDGfuHZVfKW6DBCxI0glnL3+wC2iI5DEhd84HEU/5noIIA4EcRSXv8RwDaJjkMST7D9HkfJd0VHIVyQwnJ3AwPWio5DEn8QqKHRUVQkNgaBWCrcKljC15mS6IvqcRSbRZ1cmCCWcnc7gFxR55ckFO0eR/E0EScWIoil3M1EnFeSwDAwz67imN/6j/kJLeXuy7E+pyQJIJCl3B3zMqkxFWRVeV09gLmxnqQkaZhnKXe7Y3nCmAlSWO6uJdC6WE5OkpQUrSqvi9mekpgIYimv/2sGWGM1KUlyQ6DSwgr3X8XiXLoLUljurgHYf47FZCSpA2P48aqKumq9z6OrIJaK+p0M+G96T0KSmhCjMktF/U5dz6HXwMGncn+uZ/ASCQCA6C3PzqJf6DK0HoMG9nNoH0M+si6JDX4G2tjkKDrEe2DuF/Aa+2e5isbkfg5JLDEQWM3aygbuxcm5X8Q+xVcDMLkTUBJrLP0+rYb3oFwFsZTXvyn3kEsEstVSfvRNngNy+wwSLM3zMWT1EYlY+pgfG5uq+JQU4pJBiqrdOcG6VVIOiWgyyICaojJ3Do/BuAjS1y2Lukniiuf70sDl80jUS6xgrdx/Ev2OSCSPQ0SvN+4sqo1mjKgyyJLKw9nBQtISSdzBGNu+7L9+Pj6aMaISJNiCYIXoN0IiGYYVaRldUXUcG/MSK9C8hrkh+3NI4pt+hah4rE18xpxB/GCyeY0kEUj3a2zMWWRMglh2uLcQw/dEz1wiCQcifK+wouH7Yzo20gPWVjZM6fdrdfHeMPM7y6dhdf5EzJ+aiflTM+HXgHM3H6CtvRcHz7Sjue2+6BATilULJuDVpVORn5eF/LwsAMDF2z241N6L01e78X+abokOcWQILf0DrDjS7rsRC2Ipr3cCY09ZseCNl+bijQ3zhv29z8/wdwcv4cO6a6JDTQi2lMzGj16ZD4My/OVyxNuJt35zVnSoo0DVHkeRLaIjInmx5e2jq6AoHtHTHInGneEX4jtwuh12lwpZg2hoiACn1YSXn5ka9jGFFfWiwx5lTsqqxp3rmsN9fWSfQQyGraInOBLv/OniiF7/ytKpcJaaoQgvwBp/KERwWs0RyQEAb2yI76I1TPNvi+T1hnBfWPh2/VIQ+1Ukx8SSf71qOraUzI74uPy8LCyaPh6HWjrAZCoBABgUgrPUhJefibzwpWXhRFzu7MP5Wz2ipzE0RMvnvPDDf7l+9L2vw3l5+BnEwLYijm/rLp2TPeZjX1qSC6fVBKNMJTAaCE6rCS8tGXtV2NX5E0VPYyTSmYaws0hYgqyx1xUwFv6gIpg/NTOq4zcsyYWz1ASjIXUlSTMEllUbopADABZE+f9Cbxhh2xp7XVhF08MSRDMqWwGM/U90DAjdeoyGFxfnotpqRpoh9XYLpxsVOEvNeHFx9LtWo/1jFQOyfQrC+jw96pWwovLYAsZYXGcPANA4fX54YfEUVJeakG5MHUnGGRU4rSa8YOazpTsRPssRKVtXVB5eMNrrRr0KFJ9vGwDum+F5c+7mA25jrTcHJBmXlvySZKQpcJaasJ6THEDgC8T4h+Ua/OmjZpERr4CVle5ZBIrrW7shLrX3ch2vxDQFTqsJGUksSWa6AqfVjBIT379/iSEIAMa2rqx0zxrpJSP+31d87DUAM0TPIxwOnGmHj9c6K0iJaQqcpWZkpsflne2oyEo3wGk1o9g0meu4Pj/Dx6fbRU8vXGYGr/FhGVEQBkqYxprNbffx9wf4t48oLpgMp9WErCSSJGucAc5SE4oK+MoBAH934FJCPec22jU+rCArK+qWE/CK6AlEwgd113BAh79eRQWT4Sw1Yfy4xJdk/DgDqq0mrHuavxyfnG7Hh+7Eer6NgFdWVtQtH+73wwpiYErCZI/B2F0qDp7p4D7uuqcnw2k1IzsjcSXJzjCiutSMtTrIceBM4Lm2RMTAMOy1PqwgLEH7ljMAdpcXh1r4S7L26UlwWs3IyTCKnmbE5GQaUW014fmnJnEf++CZjoSVI4Dyh8P9Zsg/h4X2o0VQ6Ceiwx4rDMChlg48NT0LC6dF/wXiYOZMycDi2dlwq3fw0KeJnmpYTMg0wmk14Tkd5DjUEpAjEb77GIG5c0q2fXK9bs+Vx38xZAZhxsRcXg3GrzHYXSo+Pcs/kzy3aBKcVhMmZsZ/JpmYZYSz1ITnFk3iPvbhoBxagtsBABppQ17zQy+xRliTJRI+jcHmUnHkbCf3sdcsmgRnqRmTstJET3NYJmWlwWk1Y03+JO5jf3q2E/a9Kvycb62LY+i7WU8ssSzl9RsBvCU6XF5oDDjc0omCGeO5PyM0e3IGlszORv25O+gbiK/l1uTxaXCWmnR5svaItxN2lxc+f7LIAQCYPmv9lmM3jr5/YfAPn8ggxNh3REfKmwG/BptLRZ3KP5MU5k+E02rClPHxk0mmjE+D02pC4UL+chz1dsLuUjGQXHIAABgM/+rxnz25xCIUiw5UD/p9AUncakR79sPCsnAinKUm5GaLlyQ3Ow3OUjMsOshRpwbk6E+QmxORQ09c+49sflhla3iKjNo50WHqSUaagupSsy7fIje33Ydtr4qOrn4hc5uak46qzSasXDCB+9hu9Q5sLm/cLSX5Q095HEXfLLMeySCUpr0oOjy96RsIZJL6Vv6ZZOWCCXBaTZiaE/uNl9Ny0uG06iNHfesd2PemghwAgb04+N+PLrEYSkQHGAt6+/2w71XRcE4HSeYHJJk2IXaS5E1Ih7PUhBXz+cvRcO4O7C4Vvf3JLwcAMDzqwGOfQVhKCAIAPQ/9sLtUHDt/l/vYK+ZPgNNqRl4MJJk+IR1OqxnL5/GX41hQjp5+v+7ziBvYMIKsrDy+BKCFouOLJQ8e+mF3eXFcB0mWz8uBs9SM6RPH6Rb/9Inj4Cw1Y9k8Ls2UHuH4+buwuVQ8eJhCcgAAIX+l/eg3VUO/EcQw4HtJdGwi6O4LZJLPLtzlPvayuTlwWk2YMYm/JDMmjYPTasKzc/nL8VmqyhGEDLQh9N/fLrEIG8Y0WhLQ1eeD3aXihA6SPBuUZCZHSWbqKMeJC3dh26uiu8/HfeyEgYYQhIGlZAYJcb/XB/teFScv3uM+9tI5OXBazZg1Ofoep7MmZ8BZasbSOTrIcTGQObp6U1gOAMTwjQsKAKzecWQuQJNEByaaez2BTNL4FX9JnpmTDafVhNlRSDJ7cgacVhOemc2/AtPJi/dgd6m4n+JyBJkccCIoiM9AYRXRSgXu9gzA7lLh0UGSJbOz4Sw1Yc6UyCWZMyUDzlITluggR+NXATnu9Ug5QvhYwAkFABTNIAUZxJ0HAUmadNhbvXhWIJPMzQ1fkrm5gcyxeBZ/OTxBOe72DHAfO5FRFMO3ghAxKchjdD4YgN3l1aUAgXlWNpxWM+bljv508bzcTDitZph1kKOp7T7se1XceSDleJyQEwoAaBpMogOKRzq6B2Dfq+LUJf6SmGaOh9NqGvER/PlTM+EsNcE0M6pOxkPSfOk+7C4vOrulHEPBGAZnEMgMMgztXf2wuVR8fpm/JAVBSYYq9rxgaiacVhMKZvCX49Sl+7C7VHRIOUbCBAD02t69hotfzJKfzkZh2oR0VFv1+db6/K0HsLtUfPV1oDrkwmmZqLKa8NR0/nJ8fvk+bLUqvhb0xHEikb/sutGQYfoLM4H9R9HBxDs9D/04dv4Ols2bwP3xkSnZ6bAsmIimtvuBnYBWsy5yfHG5C3aXlCNcOm9NqiVL+dE/ApR/ER1MojB94jhUW01YqsO32Bdu94DAp5XD43x5pQs2l4pb9x7G4F1KFrQ/VojJW7yRcOveQ9j2qjh9tYv72IsGtVjmyemrgcwh5YgMYoYCRYMWXTuhFOTm3Yewu1ScudYtOpRROROU46aUI2I0aLkKKUpcd46KV24EJWmJY0larnXD7lJx466UYyyQomQrjGn8F9MpwvU7fbC7VJy9Hn+StFzrhs2l4rqUY8wwpuUoRCQzSBRcC0rivc6vw1W0nL3eDfteFdfv9IkOJaEhomyFMSYzSJRc7eyDfa8X6g3xknhvBL5TudYp5YgWxliOQpAZhAdXOgKZpJVjr8RIUW88gN3lxVUpBxcIlK0gzts7JxKXO3phd6lcG4qGS+vNQOa40iHl4Ei2AkAusThyqT0gyflbsWtkeS4ox+UOvo1MJchRIJdY3Glr74Xd5cWFGEhy/lYP7HtV7l1+JQACSyz5IV0Pvvq6F/a9qq4tkS/c6oHdpaLtaymHPrCc5G0CngoQHquuLOGNAhD/h4okgUfWN5t0ebYqxKK8LDitJizU8RypDXUpAIu/r4ETnAVTM1FlNWPRdP0vXCmJnrBuBYDMIByZPzW02Sl2F2xIEj2zVYrSpQCQGYQT83IDcjytwzbZ0ViUl4UqKQlvuhUml1hcmJubgSqd9pCHi8wknCF0KUTyQ3q0zJmSgarNZl2qj0RKflCSBdP4NixNSTTWrTAmM0g0zJ4cyBzmWeLlCJGfl4WaUjPmhlF3SzICRF0KkSIzyBiZFZRDj4qH0ZKfl4V3/o05qlrAKQ9j3QrTNJlBxsDMSeNQZdWnVi4v8vOy8Ff/djFm6NjEJ6khpUtRoHSIjiPRmBGUQ48q67zJz8vCf//+kpj2TEwWiKFTYeRvFR1IIjF94jg4N5t06c9x4XaPLs9u5edl4W+3PIMpcdDHPZFg5G9VGNJU0YEkCnkTAq2WdamJFXzw0OZSdXkKOD8vC+/+YCkmZhlj8VYlBQxpqrJo2RWZQcJgWrCbrB5tz87fegCby4uLwQxic3lx/hb/TVf5eVn4n1ufRU6GlCQcFi270qrs27zZD+CC6GDimak5gcyhR13eczcfwFb7bV1eIPCovK1Wn52J+XlZ2PPvnkVWukHX9ywJuLBv82a/AgCMQWaRYQjJoUcf8tYbD2BzqWgbYrNTW3svbDrtcc/Py8L7/34ZxhnlbocRUIFQhykF8nPIEORmp6Fqswkr5vOXQw3KMdJOwEvtgUyiR7WU/Lws/ON/WA6jQW4oGQoKJo1gBiGZQR5jyvg0VFnNWLmAvxze692wubxh7SG/3NELm8sLrw7F6fLzsvBPP1oBko48ASPtW0E0Rd7qHczk8WmospqwSgc5zl4PVDyMpPrIlY4+2HSq4JiflwXXf1rJfdxER9MGZRCjn0lBgkzKCshhWTiR+9gt17phq1XHVLfqamcfbLX61ALOz8vC3jelJIMxUsCJb5KrpdzdCWCy6MBEMjHLCKfVjNX5/OU4c5VPOdDQ8196fIt/8XYPNv+ymfu4CUinx1GcCwQzSJBPREclkgmZRlRtNukix+mrXbC5vFxq5V6/0wdbrVeX/iQykwSgQS4og36asoLkZBhRZTVhzaJJ3McOdXbi2YLgxt2HsLlUfHlFSqITTwpi8LGjoqMSQXaGAVVWE57TQY4vgnLc1KEFwU0piW74YPzGhW8EOVFV0gpKrS8Mx48zoMpqxvNPTeI+9ueXu2Cr9era9uzWvYew1XrxxWUpCUfUU47nz4X+8chXqcRSZ5mVNS6QOdbqIMepS/dhc3lx+77+3WRv3e+HzeXVpY97KkpCYAcG//sRQRihQXSAsSAz3YCqzSase5r/TbvmS/dhc6n4OgZyhLh9vx+2WhWnLklJooYZ6gf/8xFB/AMDSS9IRpoCp9WEogId5GgLyNEuoA/51139sLlUNLdJSaLBD/aIA48Icsq5oQ3AZ6KD1ItxaQqcVrMucjS13YPN5UWHADlCtHcFlltNUpKxcrx5V9GlwT944nFOhuRcZqUbA5mj2MRfDs9X92CrVdHRPSB6mujoHoCt1gvPV/e4j530khB74tp/QhAC/V50nLxJMwTkKDFN4T5248V7sLlUdD4QL0eIzgcDsLlUNEpJIoLoyWt/yOc4LeXuLwEsFR0wD4wGQrXVjBcW85fj5MV7sLtU3O2JHzkGE3quTI+nA87dfIDX//aU6Cny5EuPo3jZ4z8cescMYb/oaHlgVAhOq0kXOU5cuAtbrTdu5QCAuz0DsLm8OHHxLvexn54xHr/50QrRU+TJkNf8kIKQT0t4QQwKocpqwouLc7mP/dmFu7C5VNzr9Yme5qjc6/HBVqviswt3uY9tmjkeH/75ctFT5ILC/ENe88NulbGUu48BeF504GOaLAHOUjNeWsJfjuPn78LuUtHVF/9yDCYn0wjnZhOe0+GL0S+vdOEHv/5C9BTHDIGONTqK1g31O2WEgxIyixCAKqs+chw7dxc2lzfh5ACArl4ftrtUHD9/l/vYz87Nwe5tz4qe4pjRGBv2Wh9WED8l5jKrymrCy8/wl6Ph3B3YXF509/lFT3HMdPf5sL3Wi2Pn7nAfe8X8CfjVDxP0vk6acdhrfdjaLzeP/sOt2SU/KAFRvuj4w+X7JbPx+tpZ3Metb70Du0tFT3/iyhFiwM9wxNsJ08zx3Ku/z5qcgfEZRl2ylF4w4EDTT9f9fLjfj1j3hZHhI9ETCJeVCybgR6/M5z6uu/UObEkiR4ieh37YalXUt/LPJH+6bhbe2DBX9BTDhsBGXCmNKIhm1PaBcFP0JMLhlWemwqjwLc9Rp3bCVutFbxLJEaKn3w+bywu3yl+SN16alyBdrthNzUj7RnrFiII0VxZfZ4ztET2NcJg/le9yoU7thM2lom9AEz013ejt12BzeVGndnIfe4UOhfZ4w4A9zZXF10d6zail9TSjcTcA/u8gZ3g2zjzq7cT2WhUPk1iOEH0DGmy1Ko56+f4vXj6ff5lWznQGr+0RGVWQU5Vr2wiI+yzCa3V15GxAjn5f8ssR4qFPg82l4ghHSSjOq9ERsOdU5dq20V4XVnFWxch2I87bRfPoq/Hp2Q5sd3kx4E8dOUL0+zTYar349CwfSc7oUHWFI92Kn42aPYAwBTlRWdIKxPdnkUthlPEcicMtHbDVqvD5meipCGPAz2BzeXG4JfqmY3qUJeIH23OiqiSs+gthl/cmTdkNQNxuoFE4fWXsCe5QSwdsLhU+LXXlCOHzM9hcKg5FIcmnZztw5mrcLjj6SfOFlT2AEb4ofJzrde/dnvXC1pkAVoue4VCoNx7ANCsbCyK8m3XwTAfsLi+kG9/CWOB9yc/Livh2bfOl+7C7WuP3MxzRrzy71r8f7ssjaxDh98f1Muut35yN6PUHTrfDVivlGAqNBZZbB8+0h31M680H+MnvWtEdx8+qESjs7AFEkEEA4EbdP9yYtX5rJoBi0RMdjl8fvgIQRiw+7fMz/I8Dl/DO//tKdLhxz4HTHXjo01C4cCKUEe5M/fOJm6j83+fQ3hW/+2MAVu1xFP+vSI6IuFldupFq+v3su2BYInq6w/HrQ1dwpaMPq/MnYv7UTMyfmgm/FtgF19bei4Nn2nWp/pGsfFB3DV9e6cKrS6c+suy6eLsHl9p78fnlLvzfU7dFhzkyhJb+AVRHftgYsOxwbwHhfdFzlkjChYhtadxZ8mHEx431hKsq3L8jhu+JnrhEMjr0O4+j6LWxHDnmLo4GUDXi+LavRBKkX6GBmrEePGZBTu4sOglEvqaTSGJM9cmdL54c68FR9QHuNQ7UADgl+h2QSIbh1EBfTlR/xKMSpKVyQzcxNub0JZHoCRFVf/Hz5VH10I66k3zjrpLfgvCu6DdDInkEhncbdxbVRjtM1IIAQEY2ygAcF/2eSCRBjmcMoIzHQNwe2l9V4S4hho8BZAh7WyQSoI8RNjbtLK7jMRiXDAIAgYCIi7USydihMl5yABwzSIhVFfV7iLEfxvZNkUgAxth7TbtKtvIck1sGCWH0GcsAaord2yKRAABrMmoPua9gdNk4vPInDS8rGvsYYNwFlEiGQNM0bWPzz9Yf5D2wLhdw80/XHQT43EWQSEaHlekhB6BTBgmxqtz9CwL+i57nkKQ4xH7h2Vnylm7D6x2/pdz9PoAtep9HkpJ84HEU/5meJ4hJ8SJLef1HANsUi3NJUgW23+Mo+a7eZ4lZda/CcncDA9bG6nyS5IWAY42O4nXRjxTWuWKHpcKtgqEglueUJBmMtXp2lZhidbqY14e0lLvbAfDvcCNJBdo9juJpsTyhkAKqlnK3LLQjiQwG5tlVHPPv1YR8kedxFBOAKyLOLUlILouQAxAkCAB4HMXzGKhB1PklCUO9x1HMv3VYmAh9FKTJUVREgEtkDJL4hYFqPY5ioUUKhT8r1egoLgXob0THIYk32N80OYpeFx2FcEEAwOMo+jEBfyk6Dkl8QMBfehwlPxYdBxAnggBAo6O4jIF2iY5DIhgiR6OjOG4edI27PlmW8vq3ANTIR+VTDg1gZR5HyTuiAxlM3AkCfLOfpAZgq0THIokF1KQpVBbcJhFXxKUgALDG/lmuz+irkdt3kxvG6D2jZiw7UfVc9H3fdCBuBQlhKa9/E2A1kNVSko0+gMo8jqJfig5kJOJ+ne9xFP2SETZC1t1KJo4zwsZ4lwNIAEGAQEmhjBxslBUckwDCuxk5/OpW6R9uglG4o+51RlQGYIXoWCQRcYoYq2ncVfJb0YFEQsIJAgBLKg9nZ/rSygBsB5AuOh7JiPQDqO41DtS0VG6I297Qw5GQgoRYXVG/2g+2XXa6ik8Y4Z8NoOpgL5mEJKEFCWHZ4d4CBWXx3Fg0pSC0QEONZ1fxB6JDiX4qScLayoYp/T5WBrDtomNJbag63Ug1xyrXdYqOhMtsRAfAG8vbR1fBYNgKxrZBfj6JFf0g2g2/f4/nZ+uTquxs0gkSovDt+qVM0bYBtBVAtuh4kpRuIuyGn/Y0/qzotOhg9CBpBQmxprKuQPPRNgZsBTBFdDxJQicR7VZ82p4TVSWtooPRk6QXJMSKymMLFJ9vGxFtBcMM0fEkKDdBtNtvUPacqlzbJjqYWJAygoRYvqNhtgHaawrYJkb0suh4EgEGHCCw/ZqR9jVXFl8XHU8sSTlBBrOyom65gSmbWKAs6vOi44kzjhPYfj9hf/POks9FByOKlBZkMIUVR4sYlE1g2ARgqeh4hMBwGoSPFKbsP7lrnaw4AynIkFjK6zcSY98BoZgBhaLj0RMCGjWgjkC/9ziKPhYdT7whBRmFVZUNT5FfexEMJQArAWih6JiignARjLlBVMcGBj5tcm44LzqkeEYKEiGrK9zPMGADGDYw4CUAk0THNAp3CDgMhsP+NO1Qc+X6FtEBJRJSkChZvePIXJ8hrUDRWAGgmRijAiIUAFgU41AuMMZaFYVUxqhV0/ytRmKtJ3e9IEu8RoEURCde28sM55vrTIoBBcQMBZqi5RKUbMa0HCLKZozlECgbgW/5cwDKBlhO4GjqAlg3gC4A3Qysm4i6GGPdREoX07RuBUoHI38rQ5q6SL3Sum/fZr/oOScj/x/TB/10FjyBwQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wNi0yNVQxNzoxODowOCswODowMOxzWmIAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDYtMjVUMTc6MTg6MDgrMDg6MDCdLuLeAAAAAElFTkSuQmCC\",alt:\"\",srcset:\"\"},null,-1))),_hoisted_11$1={class:\"history-record-container\"},_hoisted_12$1=_withScopeId$2((()=>createBaseVNode(\"div\",{class:\"history-record-title\"},\"历史记录\",-1))),_hoisted_13$1={class:\"history-record-list\"},_hoisted_14$1=_withScopeId$2((()=>createBaseVNode(\"img\",{src:\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAutQTFRFAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////4uz3xdrup8fmrMrnzN7w6fH55e73bqLVM3zEhbHc8vf7x9vvXJbQydzvutPrtdDq/v//OH/FfKvZpMTlm7/iqMfmkrnfiLPdibPdYprSgK3aPILGd6jY+vz+WJTP8fb7i7XeZZzT6PD4t9Hq3+r24Ov2U5HN1uXzfavZSovLzd/xRojJQYXIxNnuqcjmOH/Gu9Prss7piLLdoMLkkLjfl7zhmL3hjrbeocLkc6XX+Pv9vNTsNH3Eap/U7/X6xtruPYLHYZrR5u/40OHxRYjJ3en12ef0TY3MT47M1OPz4+33VZLORonKy97w7fT6XpjQPYPHwtjt9/r9aJ7TudLr7vT60+PycqTWsMzo0uLyk7ngwdftdafXnsHjZZzSy93wa6DUUpDN7PP67fP6jbbeUI/MyNzvc6XWpcXl9Pj8lbvgNn7Fg6/bYJnR2uf0eqrZ/P3+ncDjTo3Mv9btcaTW4ez2gq/blLrgpMXlZ57TWpXP8/f7irTdwNbtTYzLzuDxb6LVkbnfXZfQ4u33tM/pVJHO6vL5d6fYTY3Lmb7hsc3o2+j1TIzLbaHVmb3hOoDGWZXPpsbl0eHyQ4bJdabXQIXIoMLjO4HG3ur1fqzafazZ3On1vdXshbDcutLrs87pw9juZJvS9vn85e74hrHcdKbX1OPyn8HjSYrKlrvgjrfebKDUUY/NOIDG9fj8Y5vSSIrKuNHqQITI1OTzk7rgW5XPh7Lc5O73eKjYi7XdcKPWkLffV5PPOoHGwtjun8LjS4vLr8zoNX3Er8voPILHRIfJUZDNz+Dx1+XzW5bQe6rZosPkqcfm3Oj11eTz3ur2+/3+4Vk3TwAAACd0Uk5TABiAmLDI4fngfxdHnyrKySHUxBO0BZdV+Isc7B7fxayVtwbGFMsi6XbypQAAAAFiS0dEAf8CLd4AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAYDSURBVHja7dt5XJRFGMDxkd1lJVjKDlG0+5gSkFHI0iLLEBMiscw88gi1tLSMLs1M7DAPLLUozMwyO8UOU7q02w7TopPuzO47u/8MXFb3mnnnet+Z4TO//332+bq77PDuCwCttUvx+QOpQWhMwfYBvy9tDxBbeobqvXjLSI9ihDJVryPSnnvtcnRQvYtYe+/TNhwQ7rvfTojRr6twHVscWaq3kFCnUDPEr3oLGXUGIFv1DlLq0hWkqd5BTinAp3oFOfnaxlsEQj8IqF5BTgGQqnoFOaWCoOoV5BQEqjeQlYXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXI6cijuuXk5hkP6Z6Pdtajp9mQAhSp8GiTIb1QVMeYCzm2dzQE5RgL6YNiO85QSAGK73gzIUUJEHSCkZC+iRB0oomQk5JAUD8DIScng6Di/sZBSpJCUP4A0yCnJIeggaWGQcowEFR4qlmQchwEnTbIKAiswEoGn24UpPwMrGTImSZB4FAsBJ01zCQILMZL0HCTILAfQTLUJAgcQZCMNAkCzyZIRpkEgaMJkjEmQeBYgqSHSRDST2HUxyQIHE6QnFNpEASOG4+XTDjXIAg8byJeMul8gyDwgsl4SckUgyDwwovwkqkXGwSBVZfgJZdeZhAEXn4FXjJtukEQeOUMvGT8VWohM6/OZWkW4QOlWhVk9jXFhUOQxLopgVx7nUxDuOu9h1TPkc9ATt8FyYfcMNcVBkLz5nsJWVDjEgM5XKyXDKle6J4DoRu9gsy+yU0GQou8gSxecrO7DnSLJ5CyEvFNHar1AHLrba4zEJroOqRu6TQPHOh2tyHLar1gIHSHu5Dld3rDQGiFq5C7JotvSFmZi5CCuz1joJX3uAbJW+UdA6F7SasIQXLu89JRQ9xFAHL/A14yyC8sAciABz1loIdWk/fhhdSv8dYx52GHhfgggx6hefBHH1vLcgfp4+vwo9Y73r7JA6lseIKC8eRTbPeOPv0MftYG53ttOCDDCikYG59lnFrwHH5YBcW/Z4Y8/wLNq+rFlxjHvkwYtolmACtkLM3FqpWvsP73vEqYVkM1gQ3y2usUjN6jNrM6SF+/LaEbwQJ5YwvNq2rrm6wM+BZh3FLKGQyQokYKxry1zAz4NmHeItoh1BC6627vzGR3kK6E0d+fTQlZMIaG8S7NNzLxbSIMrKcfQwepfo+C0VjEwUi4szyq98cxzKGB0F1329Kdg1G3FT9wI9PtZ86QD6iuuzVRfDuWWE/CG+/DXKZRjpCPaK67fcx6IAlX2oQfufATtllOkE9pXlXMB5Jw5Z/hR5awjnSAfE7BYD+QhKskPB+1X7BOI0O2OTM4DiStfYkfOmM+8zQiZLqzg+NA0lo9fuj2xezjiJDtTgyeA0mk9dip2+o4xpEgjk8Iz4Ek0nLsVL4/IiFBHL4O5DqQ7GokbuwIvnkkyAYSg+9AsruBmLlfcc4jQb4mOLgOJNFhvosfzTuPBPkGy2j6VpCBg3zHPY8E+R7D4DyQxJb0peV0wwknZEJyRwXfgSSuFUkm/yAwjwRJ+is674EkvsTrPz+WicwjQX5KZPAfSOJbHT/652VC84if7AlvSP4DSWJxz/cvBWLjiJCq2Mf6VeBAktjmSdGz1/0mOI58+m2IfiyRA0mypkTdWLrmd9FpDr+PVOVHHmqu0IEkuWRHZPgfoh+vFL/qNswajNDUVb2kM5rr/2djyw+QHX9JmEVzFSXvbzcUYUrpP//+J2WSPvf9WoiF6JmF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6BYIqt5ATkGQqnoFOe0PAqpXkNMBwK96BTkdCHyqV5DTQSBF9QpySgPtVK8gpS5dAchQvYSMOgMA0lUvIaFOoWYIyFS9hngdWxwg1EH1HqIdfAhoE5JDDwOthQ5XvYtImUeA3WUZ+wHvzwKxZaf5/IHUoOq96Au2D/h9admR/f8HhQjj09PWljsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDYtMjVUMTQ6MTk6NTcrMDg6MDAA0yMfAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA2LTI1VDE0OjE5OjU3KzA4OjAwcY6bowAAAABJRU5ErkJggg==\",alt:\"\",srcset:\"\"},null,-1))),_hoisted_15$1=_withScopeId$2((()=>createBaseVNode(\"img\",{src:\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAAGl1JREFUeNrtnXtwVNeZ4H9HEg8JCYQkY8xLPHWbh2PAjl+AXzDenZrM1OxkQiubWjsbvLXZTGVeroHmj63a2tktNcw4M1OZ8nq9djZOzUzUTqamau2qZGyMk2BjHMc22Dz6ykZGYGwshAAhhBCSvv3j3gZZ1qMf5/a53X1+VV2y2tK532nuT9/tvud8n8ISCCJSDjhAk/+oB6qBmlFfRz8HcAnoHfG1d4znzgFtgAu0KaWGTM+5GFGmAyh0RGQhNyQYKcSyPIdynBHCpB5KqVOmX6NCxgqSISLSBNwH/Jb/mG06pknoBl72H79SSn1gOqBCwgoyCSLSCNwLbMATosl0TDni4smyH9ivlOowHVCYsYKMgYg8DPwbPDHuNh1PwBzAk+XnSqmXTQcTNqwgPiJyL/AV/3Gr6XgM8T7wIvCiUmq/6WDCQEkLIiK3cUOKYs8UmXIAeAFPlvdMB2OKkhNEROYDf4gnxRbT8RQIe/Ayy0+UUp+YDiaflIwgIrIYeAzYBsw1HU+BcgZ4BnhWKXXCdDD5oOgF8T+WTYlRZzqeIqGbG6K0mQ4mSIpWEBFZww0xqk3HU6T04onyjFLqiOlggqDoBBGRdXhiPAZMNR1PiTDADVHeNR2MTopGEBGZDezwHxZz7AJ2KaXOmw5EB0UhiIg8gifGKtOxWAA4iifJj0wHkisFLYiIfBnYjvexrSV8/BTYrZR6y3Qg2VKQgohINZ4YO7DvM8LOAN5l126lVK/pYDKl4AQRka/jybHWdCyWjDiIJ8mPTQeSCQUjiIjUALuBb5uOxZITTwHblVKXTAeSDgUhiIhswpPDrpcqDg7gSbLPdCCTEXpBROS7eHJMNx2LRSv9eJJ833QgExFaQUSkHk+Mb5mOxRIoP8AT5ZzpQMaizHQAYyEim4GXsHKUAt8C/tX/Nw8docsgIvI4XuYIpbyWwBgCdiilnjAdyEhCJYiIPAH8uek4LEb5nlLqcdNBpAiNICLyQ+BR03FYQsFzSqlvmg4CQiKIiLyAt8PPYknxolLqd00HYVwQEdkP3GM6Dkso2a+U2mAyAKOCiIhL4deZsgSLq5SKmDq4MUFEpAuvXq3FMhldSqmbTBzYiCAiIiaOayloRCmV94/+835AETmZ72NaigIlInkvk5pXQUTkdWBhvidpKRoWichr+Txg3gQRkVa8WrcWSy5sEJG87SnJiyAi8rdANF+TshQ9zSLyN/k4UOCCiMhu4E/yMRlLSfGnIrIr6IMEKoiI/CXwF0FPwlKybPfPscAI7GNef1XuXwcZvMXi87hS6ntBDByIICP2c9gl65Z8MAQ8rJTaq3tg7YL4OwFfAtbn4YWxWFK8jSdJt85Bg/gLvxsrhyX/3I537mlFqyB+gQW7TdZiim3+OagNbZdYfmmel7DVRyxm6ce71NJSUkiLIH5Rt5ewdass4eAAniQ5F6fTdYlli7pZwsTdaHo/knMG8Wvl/pPpV8RiGYOvK6VacxkgJ0H8Kuv7sIWkLeHkILBRKXU52wFyvcSyVdYtYWYtOXYcyzqD+M1rXsP257CEmwG8LJJVE59cMohtXmMpBKaSQxbJShAReRT4qumZWyxp8lW/j2XGZHyJJSJ1eG/MQ90w82eHzvJW+0U6uq7Q0XWF8jJYMXcGixsq2by6gXWLZ5oOsaB450QPLx/uor2zj/bOPgCWzqmisaGSNQuq+b31N5sOcTKO4l1qZdR9NxtB4oS81fLTe0/y9Kunxv3/FeWK72xu5JFN802HWhA8t+80T+7pYGh4/GI090fqeOIbK02HOhm7lFKxTH4hI0FEZD3eqsnQcsd/fT3tn92ypoGWqGO+vGRIEYFYwuWVI11p/85v/tJoIcR0WK+UejfdH870Pcg207ObiMf/8VhGP7/ncBex1iTDtkrXFxgWIZZIZiQHMGHmDgmPZfLDaQsiImsyHTyf/L93PuOXycy3Arxy5Byx1uSElw+lxtCwEGt1eeVI5k2fnt57kp8fOmt6ChPxmIisTveHM8kg2wjxx7qHP86+Bffeo+eIJVwGrSQMDnly7D2afUe0t9ovmp7GREwlgz/0aQkiIk2ZDGqCjq4rOf3+q0fPEWt1GRwqXUmuDXmXVa8ey61d4Ikc/y3ywGP+OT0p6WaQbUC16VlNROqjx1z4xbFz7EgkuTY0bHo6eWdgcJhYa5JfHMt9x2pHV+7/FgFTTZrvpycVREQWE/LsAVCm6aOoXx7rZkery8Bg6UhydXCYWMLN6j3cWChVEJ8LbvPP7QlJJ4M8BtSZns1krJg7Q9tYv0p6kly9VvyS9F8bJtbq8itNcoB3A7EAqCeNLDKhICIyL51BwkBjQ6XW8fa53cQSLv1FLMmVgWFiiST7XK2FQApFEPCyyLyJfmCyDPI1YK7pWaTDltUNVOi6zvLZ53YTa01yZWDI9PS00zcwRCyR5DU3o5UXk1JRrnh4TYPp6aXLLXjn+LhMJkjBNNZct3gm/2VLo/ZxX2s7Tyzh0ldEkvRdHSLW6vJ6m145AL6zpbHQ1rlNeI6PK4iI3AZsMR19Jjy6aT5bAvjr9XrbeWKtLpevFr4kl68OsSPhsv8D/XL81poGHtlYcOvbtvjn+phMlEEKJnuMpCXqsHm1/taH+z84TyyRpLe/cCXp7R9kR2uSNwKQY8tqb11bgTLuuV50giigJRrhoVX6JXnjgwvEEkku9Q+anmbGXLoyyI6Ey4EPL2gfe/Pq+kKWA+B3xvsfYwoiIhso4DI+ZcrLJA8GIMmBDy8Qa3XpuVI4kvRcGSSWcHkzADkeWuXJURi3PsblHhEZs/vZeBmkILPHSMrLFC1RhwdW6pfkzeMXiCVcLhaAJBf7Bom1Jnnz+AXtYz/oy1FW4Hb4jHnOF60gABVlinjU4f6V+u9z/vr4BWKtSS70XTM9zXG50HeNWCLJrwNYPPjAynpatjqUa/5o3SDpCSIiDwNrTEeri4pyT5L7Ivoleav9IrGEy/nL4ZPk/OVrxFrdQFbW3h+poyXqUFFeNHIA3Oqf+59jrAzy26Yj1c2U8jLiUYdNjn5JfuNL0h0iSbovXyOWcPnNR/rluM+XY0pxyZHi345+YixBNpqOMgimVniSbAxAkrc/ukis1eVcr3lJzvVeI9aa5O0A5NjkeHJMrSjaxmFfOPc/92dARJYDH5iOMkj6rw2zozUZyF3kdYtnEo861Feb2VfWdWmAnc+7vHuiR/vYG53ZxKMRpk8pWjlSLFdKHU99M3q2D5iOLmimT/EyyYam2drHfvdED7FWl65LA3mf19lLA8QSwcixoWk2LVtLQg4Y5cDoGW8yHV0+qJxaTkvU4d4VAUjS0UMs4XK2J3+SdPYMEGt1OdihX457V8ymJepQObUk5IBRDpSkIABVviT3LK/VPvbBjh5iiSSdeZCks+cqsUSSQyf1y3GPL0fV1PLA5xEixhZERFYBS0xHl09mTCunJRrh7gAkOXTyErHWJJ9dvBpY/J9dvMqOVpf3TubcSOkL3L28lnjUYca0kpIDYKnvAvD5DPKQ6chMUD3dyyR3LavVPvZ7py4RS7icuaBfkjMXrhJLuLx/Sr8cd5WuHCkeTP1H2VhPlho10ytoiTrcGYAk7/uSfKpRkk8DlOPOZbXEtzpUT6/QPnYBcd2F6x/zish5oNZ0ZCa52DdILJEM5O7z6gXVtGx1mDc7tybAn5zvZ2fC5cjp7OuAjcedS2uJNzvMrCxpOQDOK6XqwM8gIrKQEpcDYFaVl0nuWDJL+9hHPu4llnA5fb4/6zFOn+8nFpAcX146i5aolcNntu/E9UustIpolQK1VVNoiTrcHoAkR0/3Emt1+bg7c0k+7u4n1upyNAA57ljiyTGrysoxgiawgozJ7BmeJOsX65fk2CdeJjl1Ln1JTp3zMsexT/TLcbsvR23VFO1jFzhWkImo8yUJogBB8pNeYokkJ89NXqLz5LkrxBJJkgHIsX7xTFq2OsyeYeUYg88JUtD7JYOivnoKLVsd1jbql8T99DKxhDthTeGOrivEWl3cT7PuYjwu6xpn0hKNUFdt5RgHm0HSoaFmKvGow22L9EvS5ksyVrHnE11XiCVc2s7ol2Nt40xaog71Vo6JcACUiJQD4d87apizPQPsSCQDuWu9/OYqWqIRltzkVYf86OwVdiZcPvxMvxy3LZpJvNnhpprQdrIIExVKRFbiNTi0TEJnzwA7WpOB3KBbdnMVLVu9K92dz7sc/0x/hfQvLaohHo0wZ6aVI01WKRH5feBfTEdSKHx28So7Ei6Hg5BkThWCnlYOo7l1YQ3xqMPNs6bl4VUqGv5dGfb9R0bcPGsa8a0OaxbUaB/7+IgWyzpZs6CGFitHNjSV4ZWBt2TA3NpptEQdVs8PdU8hwF/iEnWYa+XIhvoyQt45Kqzc4kuyKsSSrJpfTUs0wi21Vo4sqS4D9F8rlAjzZk+nJeqwcl74JFk1v5p41GGelSMXamwGyZH5viSREEmycp6elcMWm0G0sKBuOi1bHZxb9LWBy5bILTNoiTrMr7NyaMBmEF0srPcySZPGXomZ4twyg5ZohAVWDl1UW0E0sqi+kpaoo7WhaLo0zfUyx8J6K4dG7CWWbhobPEmW35y/RpYrfDkW1ettZGqxl1iBsLihkpZohGV5kGS5v0RFd5dfCwDVSkSuAnZxTgC0d/YRS7iB3B0Hb/1WPOqw5KaCabtcaAyUTLm8okRMB1D8lAH6V91ZvCXrzweXPcBbuxVrdfnobHDHKHEulQH693KWOCe6rrAzkQxkyfporksSoIglTK/NIJrp6EptdsrfCXs84Pc6JYzNIDo5ec6T44MAtslOxvHOPnZaSXTTawXRxKlzXsXDIPaQp4vNJNq5ZC+xNPBxdz87nw+m+kimpD5aPnF28pJClkmxGSRXTvu1coOoW5Ut7Z19bG9NciqNuluWCbEZJBdShaSDqHiYK+2dfTz+T8mcagFbbAbJmk8vXGVnIphaubpo7+zjz/7hGGcCbOJT5FwqA86ZjqLQOOPLEUSVdd20d/bxxz86mteeiUVEdxnQZjqKQuKzi1eJPe9y+ONgyv4snaN/XVV7Zx9/9NwRukPQx73AaCsDXNNRFAqdPV6r5UBqYt1cRUvUIR51AlkF3N7Zx7f/72Eu9tkimhng2tKjaVIspUeXzqni2f90KzWl3WItXSrKlFJDwHHTkYSZrkte5ghCjhVzZxBvviEHwJKbKok3B7Mzsb2zj23/5336BoYCfc2KgONKqaHUcnf7PmQcUnIE0Ye86ZYZxKMOi8fY7LS4oZJ4QHvc2zv7+Ob/fo+rg8OBvGZFggs32h/Y9yFjcK73GjufdznYoV8Ox5djop2AjQ1eJgmiWkp7Zx//4X8dYnDIbioZhza4IYjNIKPovnyNnQmXd0/olyMyr5p4NJLWHvJF9ZXEo5FA6m61d/bx7588iFhHxsIKMh7nfTneOaG/HfTKeV7Fw0yqjyysn048oAqO7Z19RP/+Xe3jFgFWkLG40OfJ8fZH+uVYNb+aeLOTVd2qBXXTiTcHUwu4vbOPrd+3koyiDUClvhORbmC26ahMcrFvkFgiyVvt+uVYvUBPOdDU+q8g7uIvnVPF899dp33cAqRbKVUPNzIIwMumozJJz5VBdj7vBiLHmgVeZycdtXLnzZ5OvDkSSH8Sm0muc90FKwhwqX+QnQmXXx+/oH3sVGcnnS0IbqmdRjzqcOtCK0lAjCnIr0xHZYLe/iF2JlzeDECOL/lyzA2gBcFcK0mQXHdBjXxWRFxKqCXb5atDxFqTvPHhBe1j37aohpY8NMzs7Lka2F3+En1P4iqlIqlvRheOK5nLrL4BL3MEIcfaxpl56yY7Z+Y04tFIIH3cSzST7Bn5zWhB9puOLh9c8eXY/8F57WOva5xJPOpwUx5bLc+ZOZV4s8PaRiuJBl4f+U3JCdJ/bZhYwuX1tgDkWDyTeLNDQ03+Sx3fVDOVeNRh3WIrSY58zoHPCaKUOgG8aTrCoLgaoBzrF88iHo1QX22uDnhDzVTi0QjrrSTZckAp1THyibGKVxdlFhkY9OR4ze3WPvbtS2YRb3aor55ieprUV08h3hzh9iWztI9dApJ84dwfS5Cfm45SN9eGPDn2BSDHHUtnEY861M0wL0eKuhlTiEcd7rCSZMoXzn011k+JyPvAGtPR6mBwSNiRSPLLY/rl+PLSWbREHWqrwiPHSFLryoJYHbBi7gx+/EdrTU9RJ+8rpb40+snx+oO8aDpaHQwOC7GEG4gcdy6rJd4cCa0cALVVU4hHI9y5VH8m+eDMZb7x5EHTU9TJmOd80QoyNCzsTLj84pj+qkZ3LaslHnWYVRn+fd2zqiqIN0e4a1mt9rHdTy/zyFOHTE9RF+kLopR6HThgOuJsGRbYmXB59ah+Oe5eXku82WFmAciRYmZlBfGow13La7WPffR0L//x6fdMTzFX3lBKjfnh1EQt2AoyiwiwM5FkbwBy3LOilng0UpAVQWoqK9gVdbg7AEneP3WJx5553/QUc2Hcc73oBNmZcHnliH457l0xm3g0QvX0ctNTzJrq6RXsao5wzwr9234OdvTwn39w2PQUsyVzQZRSh4BXTEeeCT/ad5o9h7u0j7uhaTbxZocZ0wpXjhQzppWzK+pwbwCSvP3RRf7mZx+ZnmKm7FFKjXuNOFmX2xdMR58u757o4ck9HbkPNIqNTbOJRx2qpha+HCmqppUTb3bY0KRfkn/c/wlPv3rK9BQzYcIrpckE+QlwxvQM0mHPkS4Gh/WW59jk1BFvjlBZRHKkqJpaTjwaYaOjX5Kn954slC5XZ/DO8XGZUBCl1CfAs6ZnkQ4dXXqbxWxy6ohHHaZPKd5W8pVTy4hHI2xy6rSPfTCAQnsB8Kx/jo9LOv/6zwD677RpRmfjzPsidexqdphWxHKkmD6ljHizw30RvZIc6gh9X6ZuvHN7QiY9A/wVvqHPIrquru5f6ckxtaL45UgxraKMeNThfo2SSPir0T3rn9sTku5Z8Awh70Slo6/GAyvr2RWNMKW8dORIMbWijHhzhAdW6pFkdQBVVzTSSxrZA9IURCnVRsizSGMaZTwn4sFV9cSbHSrKVU7jFDJTyhXxaIQHV9bnPFYQZYk08qx/Tk9KJn8qnwFC28drzcLsKw4+tKqeeNShoqx05UhRUa6INzs8tCp7SR5YWc/qBforQGpigDSzB2QgiFLqcCYD55vfW39zVtfQm1fXE2+OUG7luE55mSfJ5tWZS7KucSb/7Q+Wm57CRDzjn8tpkenFdqgvs574xsqMfn7LmgbizRGsG1+kTHmXW5tXN6T9O01zZ/Df/7CJ6nCvVcvoj3zGp4aIxIEdpmc5EU/vPTnh3dyKcsV3NjfyyKb5pkMtCJ7bd5on93QwNMFHhV+9cy7f2dzIrKpQy7FLKRXL5BeyEaQO2AesMj3bifjZobO81X6Rjq4rdHRdobzM2wW3uKGSzasbAqn+Ucy8c6KHlw930d7Zd/0u+dI5VTQ2VHLbohp+Z+0c0yFOxlFgo1Iqo4odWV1ciMijwA9Nz9hiyYBHlVI/yvSXsr76FpGfAl81PWuLJQ1+qpT6Wja/mIsgXwZeA8wVgrJYJmcA79LqrWx+Oetbxv4Bd5mevcUyCbuylQNyyCAAIlKN94Z9relXwWIZg4N42SPrlaw5LTpSSvUCu02/ChbLOOzKRQ7IURAApdSPgadMvxIWyyieUkq15jqIlnvIIlIDvATcbfpVsVjwSlY9rJTKeVOKtkUWIrIJT5LcO1VaLNnTjyfHPh2Dadv44Ae03dSrYrH4bNclB2jMIClE5FngW3l9SSwWjx8opbbpHDAIQerxLrXW5+tVsViAd/AurbRWDQxkobeIbMaTpPT2rlpMMIwnh/ZCh4GcwH6g9v2IJV9sD0IOCCiDpBCR7wF/FuQxLCXP95RSjwc1eOB76UTkh8CjQR/HUpI8p5T6ZpAHyMtmUxF5AfhKPo5lKRleVEr9btAHydtubBHZD9yTr+NZipo3lFL35uNAeS1XICIu0JTPY1qKjjallJOvg+W9noeIdAG5VyazlCJdSqmb8nlAIwVvpAAKt1pChyil8n5fzciNPKWUAgqqy4rFKCdNyAEG73QrpRYB+3MeyFLsvK6UajR1cKNLQZRSG4CEyRgsoaZVKbXRZADG10oppZqBvzMdhyV0/J1S6uumgzAuCIBS6k+BvzIdhyU0/JV/ThgnFIIAKKW2A//TdBwW4/wP/1wIBaGray4ij+NVSgmNvJa8MIy3KvcJ04GMJHSCwPX9JLuxm65KhXcIcMl6LoRSELi+M3E3dvtusfMDPDm07gTURWgFSSEi38UTxVZLKS768cT4vulAJiL0gsD1kkK7sXW3ioUDaK4+EhQFIQhcL063G/i26VgsOfEUnhw5F3XLBwUjSAoR+Trefve1pmOxZMRBYLdfqrZgKDhB4HpV+e14vRJtf5JwM4DXJmO3X+y8oChIQVL4TXx2YDtdhZV/Jsf+HKYpaEFS+D0TtxPyxqIlxFG8jPGc6UBypSgEgevdd1OXXRZzpC6nuk0HooOiESSFiKwHtgGPYd+f5IsB4BngWaXUO6aD0UnRCZJCRNbgSbINqDYdT5HSyw0xDpsOJgiKVpAUItLEDVHqTMdTJHRzQ4w208EESdELkkJEFnNDlLmm4ylQznBDjBOmg8kHJSNIChGZD3wNr9LjZtPxFAh7gBeBnyilPjEdTD4pOUFGIiK34YnyFew6r9EcwJPiRaXUIdPBmKKkBRmJiGzghixrTMdjiMPAC3hS2IozWEHGREQeBn4b2AjcYTqegPkNsA/4uVLqJdPBhA0ryCSIyHLgAWCT/1hiOqYcaQdew5PiF0qpD00HFGasIBkiIquBB/3HQ0Ct6Zgm4Tzwqv/Yq5Q6ajqgQsIKkiMishCvYn0T4Iz472V5DuU40Aa4/tc2vErotsRrDlhBAkJEyvm8MPV4d/RrRn0d/RzAJby71KmvvWM8d44RQiilhkzPuRj5/2omzr+oqKWjAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTA2LTI1VDE0OjE5OjU3KzA4OjAwANMjHwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wNi0yNVQxNDoxOTo1NyswODowMHGOm6MAAAAASUVORK5CYII=\",alt:\"\",srcset:\"\"},null,-1)));function render$9(e,t,o,n,i,r){const s=resolveComponent(\"DoCol\"),a=resolveComponent(\"DoRow\");return openBlock(),createElementBlock(\"div\",_hoisted_1$8,[createBaseVNode(\"div\",_hoisted_2$5,[withDirectives(createBaseVNode(\"textarea\",{class:\"portal-textarea\",rows:\"5\",\"onUpdate:modelValue\":t[0]||(t[0]=e=>i.url=e),placeholder:\"请输入目标传送地址\"},null,512),[[vModelText,i.url]]),createBaseVNode(\"div\",_hoisted_3$4,[createBaseVNode(\"div\",{class:\"opt-btn\",onClick:t[1]||(t[1]=(...e)=>r.jumpToTarget&&r.jumpToTarget(...e))},\"跳转\")])]),createBaseVNode(\"div\",_hoisted_4$4,[_hoisted_5$4,createVNode(a,null,{default:withCtx((()=>[createVNode(s,{span:6,class:\"url-edit-key\"},{default:withCtx((()=>[_hoisted_6$4])),_:1}),createVNode(s,{span:18},{default:withCtx((()=>[withDirectives(createBaseVNode(\"textarea\",{rows:\"3\",\"onUpdate:modelValue\":t[2]||(t[2]=e=>i.editUrlInfo.baseUrl=e),placeholder:\"eg. https://dokit.didi.cn\"},null,512),[[vModelText,i.editUrlInfo.baseUrl]])])),_:1})])),_:1}),(openBlock(!0),createElementBlock(Fragment,null,renderList(i.editUrlInfo.queryList,((e,t)=>(openBlock(),createBlock(a,{class:\"url-edit-container-query-item\",key:t},{default:withCtx((()=>[createVNode(s,{span:6,class:\"url-edit-key\"},{default:withCtx((()=>[createTextVNode(\"query参数\"+toDisplayString(t+1),1)])),_:2},1024),createVNode(s,{span:7},{default:withCtx((()=>[withDirectives(createBaseVNode(\"input\",{type:\"text\",\"onUpdate:modelValue\":e=>i.editUrlInfo.queryList[t].key=e},null,8,_hoisted_7$3),[[vModelText,i.editUrlInfo.queryList[t].key]])])),_:2},1024),createVNode(s,{span:2,style:{\"text-align\":\"center\"}},{default:withCtx((()=>[_hoisted_8$3])),_:1}),createVNode(s,{span:7},{default:withCtx((()=>[withDirectives(createBaseVNode(\"input\",{type:\"text\",\"onUpdate:modelValue\":e=>i.editUrlInfo.queryList[t].value=e},null,8,_hoisted_9$3),[[vModelText,i.editUrlInfo.queryList[t].value]])])),_:2},1024),createVNode(s,{class:\"url-edit-container-query-item__icon\",span:2,onClick:e=>r.delQuery(t)},{default:withCtx((()=>[_hoisted_10$2])),_:2},1032,[\"onClick\"])])),_:2},1024)))),128)),createVNode(a,null,{default:withCtx((()=>[createVNode(s,{span:16,offset:6},{default:withCtx((()=>[createBaseVNode(\"div\",{class:\"add-btn\",onClick:t[3]||(t[3]=(...e)=>r.addQuery&&r.addQuery(...e))},\"新增query参数\")])),_:1})])),_:1}),createVNode(a,null,{default:withCtx((()=>[createVNode(s,{span:16,offset:6},{default:withCtx((()=>[createBaseVNode(\"div\",{class:\"add-btn\",onClick:t[4]||(t[4]=(...e)=>r.handleQuickEdit&&r.handleQuickEdit(...e))},\"跳转\")])),_:1})])),_:1})]),createBaseVNode(\"div\",_hoisted_11$1,[_hoisted_12$1,createBaseVNode(\"div\",_hoisted_13$1,[(openBlock(!0),createElementBlock(Fragment,null,renderList(i.historyList,((e,t)=>(openBlock(),createElementBlock(\"div\",{class:\"history-record-list-item\",key:t},[createVNode(a,null,{default:withCtx((()=>[createVNode(s,{class:\"history-record-list-item__url\",span:18},{default:withCtx((()=>[createTextVNode(toDisplayString(e),1)])),_:2},1024),createVNode(s,{class:\"history-record-list-item__icon\",span:3,onClick:t=>r.jumpToUrl(e)},{default:withCtx((()=>[_hoisted_14$1])),_:2},1032,[\"onClick\"]),createVNode(s,{class:\"history-record-list-item__icon\",span:3,onClick:e=>r.delHistory(t)},{default:withCtx((()=>[_hoisted_15$1])),_:2},1032,[\"onClick\"])])),_:2},1024)])))),128))])])])}var css_248z$a=\".h5-portal[data-v-e4e3a6c4] {\\n  padding: 5px;\\n}\\n.h5-portal[data-v-e4e3a6c4] textarea[data-v-e4e3a6c4],\\n.h5-portal[data-v-e4e3a6c4] input[data-v-e4e3a6c4] {\\n  font-size: 13px;\\n}\\n.h5-portal[data-v-e4e3a6c4] .portal-textarea-container[data-v-e4e3a6c4] .portal-textarea[data-v-e4e3a6c4] {\\n  font-size: 13px;\\n  border-radius: 5px;\\n  box-sizing: border-box;\\n  width: 100%;\\n  border: 1px solid #d6e4ef;\\n  resize: vertical;\\n}\\n.h5-portal[data-v-e4e3a6c4] .portal-textarea-container[data-v-e4e3a6c4] .portal-opt-area[data-v-e4e3a6c4] {\\n  margin-top: 5px;\\n  height: 32px;\\n  line-height: 32px;\\n  display: flex;\\n  justify-content: space-between;\\n  align-items: center;\\n}\\n.h5-portal[data-v-e4e3a6c4] .portal-textarea-container[data-v-e4e3a6c4] .portal-opt-area[data-v-e4e3a6c4] .opt-btn[data-v-e4e3a6c4] {\\n  background-color: #337CC4;\\n  border-radius: 5px;\\n  font-size: 16px;\\n  width: 100%;\\n  text-align: center;\\n  color: #fff;\\n}\\n.h5-portal[data-v-e4e3a6c4] .url-edit-container[data-v-e4e3a6c4] {\\n  margin-top: 20px;\\n  padding-top: 20px;\\n  border-top: 1px solid #d6e4ef;\\n}\\n.h5-portal[data-v-e4e3a6c4] .url-edit-container[data-v-e4e3a6c4] .url-edit-key[data-v-e4e3a6c4] {\\n  font-size: 15px;\\n}\\n.h5-portal[data-v-e4e3a6c4] .url-edit-container[data-v-e4e3a6c4] .url-edit-container-title[data-v-e4e3a6c4] {\\n  text-align: center;\\n  font-size: 18px;\\n  color: #2c405a;\\n  margin-bottom: 20px;\\n}\\n.h5-portal[data-v-e4e3a6c4] .url-edit-container[data-v-e4e3a6c4] .url-edit-container-query-item[data-v-e4e3a6c4] {\\n  margin-top: 5px;\\n  height: 32px;\\n  line-height: 32px;\\n}\\n.h5-portal[data-v-e4e3a6c4] .url-edit-container[data-v-e4e3a6c4] .url-edit-container-query-item__icon[data-v-e4e3a6c4] {\\n  height: 32px;\\n  line-height: 32px;\\n  text-align: center;\\n}\\n.h5-portal[data-v-e4e3a6c4] .url-edit-container[data-v-e4e3a6c4] .url-edit-container-query-item__icon[data-v-e4e3a6c4] img[data-v-e4e3a6c4] {\\n  width: 15px;\\n  height: 15px;\\n}\\n.h5-portal[data-v-e4e3a6c4] .url-edit-container[data-v-e4e3a6c4] .add-btn[data-v-e4e3a6c4] {\\n  margin-top: 5px;\\n  height: 32px;\\n  line-height: 32px;\\n  background-color: #337CC4;\\n  border-radius: 5px;\\n  font-size: 16px;\\n  text-align: center;\\n  color: #fff;\\n}\\n.h5-portal[data-v-e4e3a6c4] .url-edit-container[data-v-e4e3a6c4] textarea[data-v-e4e3a6c4] {\\n  border-radius: 5px;\\n  box-sizing: border-box;\\n  width: 100%;\\n  border: 1px solid #d6e4ef;\\n  resize: vertical;\\n}\\n.h5-portal[data-v-e4e3a6c4] .url-edit-container[data-v-e4e3a6c4] input[data-v-e4e3a6c4] {\\n  height: 32px;\\n  line-height: 32px;\\n  border-radius: 5px;\\n  box-sizing: border-box;\\n  width: 100%;\\n  border: 1px solid #d6e4ef;\\n}\\n.h5-portal[data-v-e4e3a6c4] .history-record-container[data-v-e4e3a6c4] {\\n  margin-top: 20px;\\n  padding-top: 20px;\\n  border-top: 1px solid #d6e4ef;\\n}\\n.h5-portal[data-v-e4e3a6c4] .history-record-container[data-v-e4e3a6c4] .history-record-title[data-v-e4e3a6c4] {\\n  text-align: center;\\n  font-size: 18px;\\n  color: #2c405a;\\n  margin-bottom: 20px;\\n}\\n.h5-portal[data-v-e4e3a6c4] .history-record-container[data-v-e4e3a6c4] .history-record-list-item[data-v-e4e3a6c4] {\\n  margin-top: 5px;\\n  background-color: #337CC4;\\n  border-radius: 5px;\\n  height: 32px;\\n  line-height: 32px;\\n  color: #fff;\\n  padding: 0 5px;\\n}\\n.h5-portal[data-v-e4e3a6c4] .history-record-container[data-v-e4e3a6c4] .history-record-list-item[data-v-e4e3a6c4] .history-record-list-item__url[data-v-e4e3a6c4] {\\n  overflow: hidden;\\n  text-overflow: ellipsis;\\n  white-space: nowrap;\\n}\\n.h5-portal[data-v-e4e3a6c4] .history-record-container[data-v-e4e3a6c4] .history-record-list-item[data-v-e4e3a6c4] .history-record-list-item__icon[data-v-e4e3a6c4] {\\n  text-align: center;\\n}\\n.h5-portal[data-v-e4e3a6c4] .history-record-container[data-v-e4e3a6c4] .history-record-list-item[data-v-e4e3a6c4] .history-record-list-item__icon[data-v-e4e3a6c4] img[data-v-e4e3a6c4] {\\n  width: 15px;\\n  height: 15px;\\n}\\n\";styleInject(css_248z$a),script$9.render=render$9,script$9.__scopeId=\"data-v-e4e3a6c4\";var H5DoorPlugin=new ce({nameZh:\"任意门\",name:\"h5-door\",icon:\"https://pt-starimg.didistatic.com/static/starimg/img/FHqpI3InaS1618997548865.png\",component:script$9}),script$8={props:{position:{top:0,right:0,bottom:0,left:0}}};const _hoisted_1$7={class:\"dokit-position-infobox-container\"};function render$8(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",_hoisted_1$7,[createTextVNode(\" 位置: 左 \"+toDisplayString(o.position.left)+\", 右 \"+toDisplayString(o.position.right)+\", 上 \"+toDisplayString(o.position.top)+\", 下 \"+toDisplayString(o.position.bottom)+\" \",1),createBaseVNode(\"span\",{class:\"dokit-close-button\",onClick:t[0]||(t[0]=t=>e.$emit(\"remove\"))})])}var css_248z$9='\\n.dokit-position-infobox-container[data-v-70e2ec7e] {\\n  position: fixed;\\n  left: 0;\\n  right: 0;\\n  margin: auto;\\n  bottom: 20px;\\n\\n  width: 250px;\\n  height: 50px;\\n  padding: 0px 20px;\\n  line-height: 50px;\\n  font-size: 12px;\\n\\n  border-radius: 5px;\\n  box-shadow: 0px 0px 2px #cccccc;\\n}\\n.position-info[data-v-70e2ec7e] {\\n  font-size: 10px;\\n}\\n.dokit-close-button[data-v-70e2ec7e] {\\n  background: #324456;\\n  color: #fff;\\n\\n  border-radius: 10px;\\n  width: 20px;\\n  height: 20px;\\n\\n  line-height: 18px;\\n  font-size: 15px;\\n  text-align: center;\\n\\n  top: 15px;\\n  right: 10px;\\n  position: absolute;\\n}\\n.close-button[data-v-70e2ec7e]::before {\\n  content: \"×\";\\n}\\n';styleInject(css_248z$9),script$8.render=render$8,script$8.__scopeId=\"data-v-70e2ec7e\";var script$7={name:\"align-ruler\",components:{InfoBox:script$8},data:()=>({posX:200,posY:200,screenWidth:document.documentElement.clientWidth,screenHeight:document.documentElement.clientHeight}),computed:{position(){return{top:this.posY,left:this.posX,right:this.screenWidth-this.posX,bottom:this.screenHeight-this.posY}},centerStyle(){return{top:this.posY+\"px\",left:this.posX+\"px\"}},horizonLineStyle(){return{width:this.screenWidth+\"px\",top:this.posY+\"px\"}},verticalLineStyle(){return{height:this.screenHeight+\"px\",left:this.posX+\"px\"}},topInfoStyle(){return{top:this.posY/2+\"px\",left:this.posX+\"px\"}},rightInfoStyle(){return{top:this.posY+\"px\",left:(this.posX+this.screenWidth)/2+\"px\"}},bottomInfoStyle(){return{top:(this.screenHeight+this.posY)/2+\"px\",left:this.posX+\"px\"}},leftInfoStyle(){return{top:this.posY+\"px\",left:this.posX/2+\"px\"}}},mounted(){window.addEventListener(\"resize\",this.handleResize)},beforeUnmount(){window.removeEventListener(\"resize\",this.handleResize)},methods:{remove(){ct(\"align-ruler\")},handleResize(e){this.screenWidth=document.documentElement.clientWidth,this.screenHeight=document.documentElement.clientHeight,this.posX=.3*this.screenWidth,this.posY=.3*this.screenHeight},drag(e){e.preventDefault(),e.stopPropagation();let t=e.target,o=e.clientX?e.clientX:e.touches[0].clientX,n=e.clientY?e.clientY:e.touches[0].clientY,i=o-t.offsetLeft,r=n-t.offsetTop,s=(e,t)=>{let o=e-i,n=t-r;this.posX=Math.round(o),this.posY=Math.round(n)},a=e=>{s(e.clientX,e.clientY)},l=e=>{s(e.touches[0].clientX,e.touches[0].clientY)},c=e=>{document.removeEventListener(\"mousemove\",a),document.removeEventListener(\"touchmove\",l),document.removeEventListener(\"mouseup\",c),document.removeEventListener(\"touchend\",c)};document.addEventListener(\"mousemove\",a),document.addEventListener(\"touchmove\",l),document.addEventListener(\"mouseup\",c),document.addEventListener(\"touchend\",c)}}};function render$7(e,t,o,n,i,r){const s=resolveComponent(\"InfoBox\");return openBlock(),createElementBlock(\"div\",null,[createBaseVNode(\"div\",{class:\"dokit-ruler-center-bg\",style:normalizeStyle(r.centerStyle)},null,4),createBaseVNode(\"div\",{class:\"dokit-ruler-center-round\",style:normalizeStyle(r.centerStyle)},null,4),createBaseVNode(\"div\",{class:\"dokit-ruler-center-dot\",style:normalizeStyle(r.centerStyle)},null,4),createBaseVNode(\"div\",{class:\"dokit-ruler-line dokit-ruler-horizon-line\",style:normalizeStyle(r.horizonLineStyle)},null,4),createBaseVNode(\"div\",{class:\"dokit-ruler-line dokit-ruler-vertical-line\",style:normalizeStyle(r.verticalLineStyle)},null,4),createBaseVNode(\"div\",{class:\"dokit-ruler-info\",style:normalizeStyle(r.topInfoStyle)},toDisplayString(r.position.top),5),createBaseVNode(\"div\",{class:\"dokit-ruler-info\",style:normalizeStyle(r.rightInfoStyle)},toDisplayString(r.position.right),5),createBaseVNode(\"div\",{class:\"dokit-ruler-info\",style:normalizeStyle(r.bottomInfoStyle)},toDisplayString(r.position.bottom),5),createBaseVNode(\"div\",{class:\"dokit-ruler-info\",style:normalizeStyle(r.leftInfoStyle)},toDisplayString(r.position.left),5),createVNode(s,{position:r.position,onRemove:r.remove},null,8,[\"position\",\"onRemove\"]),createBaseVNode(\"div\",{class:\"dokit-ruler-drag-mask\",style:normalizeStyle(r.centerStyle),onMousedown:t[0]||(t[0]=(...e)=>r.drag&&r.drag(...e)),onTouchstart:t[1]||(t[1]=(...e)=>r.drag&&r.drag(...e))},null,36)])}var css_248z$8=\"\\n.color[data-v-cdb64f42] {\\n  color: #ffffff;\\n  color: #cc3a4b;\\n  color: #337cc4;\\n  color: #cc3a4b30;\\n}\\n.dokit-ruler-center-bg[data-v-cdb64f42] {\\n  box-sizing: border-box;\\n  background-color: rgba(255, 255, 255, 0.392156863);\\n  position: fixed;\\n  width: 60px;\\n  height: 60px;\\n  transform: translate(-30px, -30px);\\n  border-radius: 30px;\\n  border: solid 1px rgba(51, 124, 196, 0.392156863);\\n}\\n.dokit-ruler-center-round[data-v-cdb64f42] {\\n  background-color: rgba(204, 58, 75, 0.196078431);\\n  position: fixed;\\n  width: 40px;\\n  height: 40px;\\n  transform: translate(-20px, -20px);\\n  border-radius: 20px;\\n}\\n.dokit-ruler-center-dot[data-v-cdb64f42] {\\n  background-color: #cc3a4b;\\n  position: fixed;\\n  width: 6px;\\n  height: 6px;\\n  transform: translate(-3px, -3px);\\n  border-radius: 3px;\\n}\\n.dokit-ruler-line[data-v-cdb64f42] {\\n  position: fixed;\\n  background-color: #cc3a4b;\\n}\\n.dokit-ruler-horizon-line[data-v-cdb64f42] {\\n  left: 0;\\n  height: 1px;\\n  transform: translate(0px, -0.5px);\\n}\\n.dokit-ruler-vertical-line[data-v-cdb64f42] {\\n  top: 0;\\n  width: 1px;\\n  transform: translate(-0.5px, 0px);\\n}\\n.dokit-ruler-info[data-v-cdb64f42] {\\n  position: fixed;\\n}\\n.dokit-ruler-drag-mask[data-v-cdb64f42] {\\n  position: fixed;\\n  width: 60px;\\n  height: 60px;\\n  transform: translate(-30px, -30px);\\n  border-radius: 30px;\\n}\\n\";styleInject(css_248z$8),script$7.render=render$7,script$7.__scopeId=\"data-v-cdb64f42\";var AlignRuler=new he({nameZh:\"对齐标尺\",name:\"align-ruler\",icon:\"https://pt-starimg.didistatic.com/static/starimg/img/a5UTjMn6lO1618997535798.png\",component:script$7}),css_248z$7=\"\\n.dokit-hello-world{\\n  padding:10px;\\n  text-align: center;\\n}\\n\";styleInject(css_248z$7);var script$6={props:{tabs:{type:Array}},data:()=>({curIndex:0}),methods:{handleClickTab(e,t){let{type:o}=e;this.curIndex=t,this.$emit(\"changeTap\",o)},handleRefresh(){this.$emit(\"refreshResource\")}}};const _withScopeId$1=e=>(pushScopeId(\"data-v-f815a588\"),e=e(),popScopeId(),e),_hoisted_1$6={class:\"tab-container\"},_hoisted_2$4={class:\"tab-list\"},_hoisted_3$3=[\"onClick\"],_hoisted_4$3={class:\"tab-item-text\"},_hoisted_5$3=_withScopeId$1((()=>createBaseVNode(\"img\",{style:{width:\"16px\",\"margin-top\":\"12px\"},src:\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAvFQTFRFAAAAJCQkLCwsLS0tLCwsLCwsLCwsLS0tLCwsLCwsLS0tLCwsLCwsLCwsLCwsLCwsKysrLCwsLCwsLCwsLS0tKysrLCwsLCwsLCwsLCwsKCgoKioqKysrLCwsLCwsLCwsLCwsLS0tLS0tMDAwLCwsKysrLCwsLCwsICAgLS0tKysrLCwsLCwsKysrJiYmLS0tLCwsKysrKioqLS0tKysrLCwsLi4uKioqLCwsLCwsLS0tLi4uLS0tLCwsLS0tKSkpLCwsLCwsLS0tKioqLCwsLCwsLCwsLCwsLi4uKysrLCwsLCwsMzMzLCwsLCwsLCwsLCwsLi4uLS0tLS0tLS0tLCwsLy8vLCwsLCwsLCwsKysrLCwsLS0tLCwsKysrLCwsLCwsLCwsKysrLCwsKysrLS0tLS0tKysrLCwsLS0tKysrLCwsLCwsLCwsLCwsKioqLy8vLCwsLCwsKysrLS0tLCwsLCwsLCwsKysrMDAwLCwsLCwsKCgoLCwsKysrKysrQEBALCwsLS0tLCwsLCwsKysrJycnLCwsLCwsLCwsLS0tLCwsLCwsKysrLS0tLi4uKysrKysrLS0tLCwsLi4uMzMzLS0tLS0tLS0tLCwsLCwsLCwsAAAALCwsLCwsLCwsLCwsLCwsLCwsLCwsKysrLS0tKysrLCwsLS0tKysrLS0tKysrKysrLS0tLCwsKioqLS0tKSkpLCwsJycnLCwsLCwsLCwsKysrLCwsLCwsLi4uKioqLCwsLi4uKysrLCwsLi4uLS0tLCwsLCwsLCwsJCQkLCwsKysrLS0tOTk5LCwsLCwsLi4uKysrLCwsLCwsAAAALCwsKysrLCwsKioqKysrLS0tVVVVLS0tLS0tKysrLS0tKioqLCwsLS0tKSkpLCwsLS0tKysrLCwsLCwsLS0tKysrLS0tLS0tMTExMTExLCwsLCwsLCwsLCwsLS0tKysrLS0tLCwsKysrKysrLS0tKysrKioqLCwsLCws////4ewITwAAAPl0Uk5TAAcuVXuiwcjQ2N/n7/b+8eXLv7KlmYx/USMgYJO54PXHaz4QndnhqghEjsqpWRR9zGUYZ7azVDbwxGY9oNZ4GajoiStMr/qSHCqeaQV67kBWFj+m2WMbreTisClbYof71XlwXFNJT19ufo2su+NLPSbqbUcidKPTdiD06xO+gjsENHehrl4aF8bJKJf3Eo9DJEHlhicPbIjUdVe0AeaVnN2KxYCBSmpSmusRTS9xRTBhJe0Ni/y4ZKTNCzf9OM5oIbfa29EOm5QtCZBdTnzDbwK1vPkxVGUDq4NIMwy6gh+Y9vGnz72rjloaFZHs89yxwpSW3585sQbSvsraFAAAAAFiS0dE+tVtBkoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAk2SURBVHja7Z15XBVVFMfHhyiguBCIKW6kspmKSypqKAKKgQhugLgguOAWLqkpbqGFmrsZ5paSQYiSaQtZAS1WmqatWtmila22aTX/9Z4iMufeee/emXvnzufT/P59nN85X+bNm5m7nJEkS5YsWbJkyZIlS5YsWbJkiaPq2Nzquter7+Hp1aChd6PGTTyb+tzh69fMv/mdoisjV4uWAa1ay6pq09avXeBdoot0DdG+g0yijkFuwaKLVVVIaBgRRLUadbq7s+iSMbJ16UpDUc0SFN5CdOEKdeveg56imqXnPaKrr1Evv95aMW6oT0Qd0QgO9e2ni+KG+t8bKRojZIB+DIcGRg0SiREdwwbDodjBQ0RhxA1lh+GQ131CMOIThrHlsKtHovEcw6kufsRqluQsabfkJNL6CGUbwQXDrpGj1HKOHpNi/zw1bSxDjvQGvDjsGoO/2CfW/ME4ZhzjiQqaMDEmKmNSRGbW5MQpU6dlT28/Y+YsokDP2ZicLWv9wf1sMHJ6uixlju/cefNxsZ1DHliw0DXKIiTwwcW1Pl6Sy4Ijy8vFgVi6bLlzhxUrH6rngiQPljpd8XEoAw5/5xWsWv0wkc0jmXlLnPl0DVT+fb7i06b6OZyeHmsy1lJYBUeva6Pu9aji6rgefKr7u7VBPbP3uo3UdpuWOfmeLqv1h5vBZ8k6OdSvHlu2btPk+Nh29Qvr49xAVHMW7NBxxX1ip5rtBj4gu9TurXZPi9f3D5qmdt7v4QKyWCXbXv1DVYM6qXgHcAB5Ep8qrLluDIciUvD2+5iD7McnKmT1uL0pD5/gKcYgq7BZPA4wwnDoaW9sjiKmIOuwOYrZDrAF4n8VtzEEwd6XPBPOFMOuEuyzc2wuM5CD2LO8lDWHXYcaYTL5sAIZ3R/nzmck6jAuVxQjkAU4Dl6Dgzbcz/wOJiCH8Eebl8q2YPI9ywAkOdZQDkk68hyasLF+kKNtUdsCnhySFKl2M6QL5JjhHPZ/3vPsQRIxHPwnAXaxB3kB5TjCnUOS4liDzEUs5tgM4JCkIrYg85sgFi8awiFJdZmCFCMOxQZxSNJLDEGiEQMPAyeUy9mBoPNRLJ8/oKZk5Pko5M0K5DASX8iRI1SmEB0IMs/p8TI/jt00HHQggUh4Jj+O/VQcdCDIaMAYfhyL6DioQI7D4Fde5QfyGkcQ5BoSxY8jPpYfSCRcXzKL4yqLOEoOGpAKGJvBj4MepJLcuwqEvs5z9R7tV+sNcutSGDuJIwf1yd6e3DkbhL7J92lqEh0Ixe9nOQit4MqhNrasIopHiRAQmvIWZxDpBDlHEIUtvIN7mzeHJI0jxOi4ncY1H0TTT9jSq/PycYUuVfTOuzSeQwDHGgM4uMgNgPC8GHLVSQBCs6DBVCpQctQXXY9WwVNksOiCtOoUAEkXXZBWBQCQTaIL0qpWSo5ZouvRLLAfh+aWwFQKBt+sRfotxcgGQN4TXZBWweu6tiVlJtBpACK6Hs1yV3J4iq5Hs84oQdJE16NZYKk0x4E5OvkHFcgeC/zJA1KVIPeKBrip9bdma8qJd/6AZQbvi0a4obW1KiIc8zwLfrQiRDPcUO2ds0vJQkoACMdZEXIVKkoiO08qAUiWaAiHzilK+oAopgyATBYNISGDwx+uIAmCy08E7ERzWRPRtMJHJjwiB0BNZSRB8014jnwMaqokCco14a8WHEVYTxK0AgR9IprCrghQ06dEUWAlnhmu7GD+ZDFZFFjbZIZ7rQxlSU3IosAaYjPc/e5TltSfLAqsK40RTWFXmrKktmRR55VR3BeVEmiisqTzZFEXlFGxoinsmqAs6QJZVKaWqw9XwWs04WD0ShA2TzSH9Bmo6HOysC9A2FzRHNI0UBHpAjiw7ZxinQEngR2pqaRxM5Vx4ge25igLukgaB+dHhDUsqdZyUA/xDBq813QTDPIlqGcqaSCcQ2SxoV+PfEA9XxFHglndfLEco8F2kg7koeaaZ58KqqH4FTXXyge4sp34FDHZWpSxoBiKU0TM6iA1JYBawmiCBazXUhXcikz1GxoCglOOCuOApVB+O8pBtLgh+a9BJV3pwrP1hbPT2m9AJV3o4pF1v6IOyVZYCO2uuypzHJIkuAW5B61DhTkOyQ5YRndah8hLZjgk8eCuT+7djdoD2T/STgDIdliEH70HsqOn9WjDOYYgDZB7aXBB9lht0GCiT/AuXO6nxQXd9aa3qxWtkpEK+mryQfYh9jQYBOlXdVmbD7ozlF2HRBKNQvKHaHRC9uoONGYz+02VDoTpV2m1QndPtzKOY9dFJPtszWZoL59jhoE0Q3IP1W4W1xBxM2qO9xSSeZiebqwJiF0DY04T9ASRE/T4xaMtr741og93CbqbOkxfx77hiKEhw3VBaFq947aYHpPfcefYiyYdodfThulR/D1njitoSganZjrqyrX3A1wtd1M/MPDFNZSdzpFjBibfeCbOuI7LJ7hxLMVkY3S3ug3Xy7ZTCReMHzHtvGSvHEbuWRhzuQ+ProClHrhU7Ja+GdSnUQrHtpmlWETuUoZ0zvwJv4OazYl+Swb0Mj2A/VrJV5hyqHeXZeUfX4hP4MuYQ7Xfrw+bZcGz8/H2PObK1JoOMjgoST+reP/CgUO9J7bug5LpqeL8Kw8OyUmX8n6bdbhmnVOzvcqJQ5Iuq6WUfyvSaJm5VNWzDzcOp538fx9FP894NLytuiHHLliS83crpATQzVNu/CPFidufXDnwDwu3Rf6aiLEZzl/yw/K+BK+s1k4LkMP2THX1Iroj6YPrOzf5y4gtEtt2yq509dr2eZX44CmLgly/4KZnjgEcEuk7emILYqIOrZ59PSdXys25Hr36UNTfnkSB8lZjMCTOb03azeL5nFT83mMl+xo54i9xe7NYmPHrJ/9JaKi/bqBhCTrfZKJNcdcYcwxl+fYzKh1M0199jWKiRWE4dJjRGyrlAYdFYjgUmKefQs4L1F+Ifh0vvqSLonfxcdEItxRZUaUZo6pC+BtcFSrNLtdAUZ7NY7xSr0Ku0F0k80O1LgDgrxYt248kgig46SZ6MwcBjNP3s8utWwWcMj1EjYJtbqfdzyxMvd3Ro3Hqv2fc67rZzPtaduc6W1JZllhWWXJWdCGWLFmyZMmSJUuWLFmyZOn/ov8AOnjh87MOpuoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDYtMjNUMjE6MjY6MDkrMDg6MDBCOpSGAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA2LTIzVDIxOjI2OjA5KzA4OjAwM2csOgAAAABJRU5ErkJggg==\",alt:\"\",srcset:\"\"},null,-1))),_hoisted_6$3=[_hoisted_5$3];function render$6(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",_hoisted_1$6,[createBaseVNode(\"div\",_hoisted_2$4,[(openBlock(!0),createElementBlock(Fragment,null,renderList(o.tabs,((e,t)=>(openBlock(),createElementBlock(\"div\",{class:normalizeClass([\"tab-item\",i.curIndex===t?\"tab-active\":\"tab-default\"]),key:t,onClick:o=>r.handleClickTab(e,t)},[createBaseVNode(\"span\",_hoisted_4$3,toDisplayString(e.name),1)],10,_hoisted_3$3)))),128)),createBaseVNode(\"div\",{class:\"tab-item\",onClick:t[0]||(t[0]=(...e)=>r.handleRefresh&&r.handleRefresh(...e))},_hoisted_6$3)])])}var css_248z$6=\".tab-container[data-v-f815a588] .tab-list[data-v-f815a588] {\\n  display: flex;\\n  height: 38px;\\n  justify-content: space-between;\\n  align-items: center;\\n  border: 1px solid #f5f6f7;\\n}\\n.tab-container[data-v-f815a588] .tab-item[data-v-f815a588] {\\n  flex: 1;\\n  height: 38px;\\n  line-height: 38px;\\n  text-align: center;\\n}\\n.tab-container[data-v-f815a588] .tab-item-text[data-v-f815a588] {\\n  font-size: 16px;\\n  color: #333333;\\n}\\n.tab-container[data-v-f815a588] .tab-active[data-v-f815a588] {\\n  border-bottom: 1px solid #1485ee;\\n}\\n.tab-container[data-v-f815a588] .tab-default[data-v-f815a588] {\\n  border: none;\\n}\\n\";styleInject(css_248z$6),script$6.render=render$6,script$6.__scopeId=\"data-v-f815a588\";const ResourceMap={0:\"css\",1:\"script\",2:\"img\"},ResourceEnum={CSS:0,SCRIPT:1,IMG:2,OTHER:3},ResourceEntriesMap={css:ResourceEnum.CSS,script:ResourceEnum.SCRIPT,img:ResourceEnum.IMG},Resource_METHODS=[\"css\",\"script\",\"img\"],ResourceTabs=Object.keys(ResourceMap).map((e=>({type:parseInt(e),name:ResourceMap[e]}))),regImg=/\\.(jpeg|jpg|gif|png|bmp)$/,regCss=/\\.(css|less)$/,isImg=e=>regImg.test(e),isCss=e=>regCss.test(e),getResourceEntries=function(e){const t=window.performance||window.webkitPerformance||window.msPerformance;if(t&&t.getEntriesByType){const o=t.getEntriesByType(\"resource\");let n=[];o.forEach((e=>{let t={initiatorType:e.initiatorType,name:e.name},o=0;for(let e=0;e<n.length;e+=1)if(n[e].initiatorType==t.initiatorType&&n[e].name==t.name){o=1;break}o||n.push(t)})),n.forEach((t=>{let o=ResourceEntriesMap.other;Resource_METHODS.forEach((e=>{(t.initiatorType===e||\"img\"===e&&\"css\"===t.initiatorType&&isImg(t.name)||\"css\"===e&&\"link\"===t.initiatorType&&isCss(t.name))&&(o=ResourceEntriesMap[e])})),e({type:o,initiatorType:t.initiatorType,entryName:t.name,base64:\"\"})}))}},imgLoad=function(e,t){var o=new Image;o.src=e,o.complete?t(o.width,o.height):o.onload=function(){t(o.width,o.height),o.onload=null}},url2blobPromise=function(e){return new Promise((function(t,o){let n=new XMLHttpRequest;n.open(\"get\",e),n.responseType=\"blob\",n.addEventListener(\"load\",(function(){if(200==this.status){const e=new FileReader;e.onload=function(){t(e.result)},e.readAsText(this.response)}else o()})),n.addEventListener(\"error\",(function(){o()})),n.send()}))},trimLeft=function(e){if(null==e)return\"\";var t=new String(\" \\t\\n\\r\"),o=new String(e);if(-1!=t.indexOf(o.charAt(0))){for(var n=0,i=o.length;n<i&&-1!=t.indexOf(o.charAt(n));)n++;o=o.substring(n,i),o=new Array(n).join(\"&nbsp;\")+o}return o};var script$5={components:{},props:{index:[Number],type:[Number],initiatorType:[String],entryName:[String],base64:[String]},data:()=>({showContent:!1,detailImgThumb:\"\",detailHtml:\"\"}),computed:{resourcePreview(){return`<div>${this.index+1}.${this.entryName}</div>`}},methods:{toggleDetail(){this.showContent=!this.showContent,this.detailHtml||(\"img\"===ResourceMap[this.type]?this.getDetailImgThumb((e=>{this.detailHtml=e})):this.getDetailCode((e=>{this.detailHtml='<div style=\"max-height: 300px;overflow-y: scroll;overflow-x:hidden;word-break:break-all;text-align: left;\">'+e+\"</div>\"})))},getDetailImgThumb(e){let t;t=\"\"!==this.base64?this.base64:this.entryName;let o=\"\";o=`<img src = \"${t}\" style=\"object-fit: cover;height:100px\" />`,this.getDetailImgSize((t=>{o+=t,e(o)}))},getDetailImgSize(e){let t;t=\"\"!==this.base64?this.base64:this.entryName,imgLoad(t,((t,o)=>{e(`<div>${t}*${o}</div>`)}))},getDetailCode(e){url2blobPromise(this.entryName).then((t=>{let o=t=(t=t.replace(/</g,\"&lt;\")).replace(/>/g,\"&gt;\");o=o.split(\"\\n\");let n=o.length,i=\"\";for(let e=0;e<n;e+=1)o[e]=trimLeft(o[e]),i+=`\\n              <div class=\"codeline\" style=\"display:flex;line-height:14px;\">\\n                <div class=\"codeindex\" style=\"min-width: 40px;background-color:#F0F0F0;color: #808080;text-align:right;padding-right:5px;\">${e+1}</div>\\n                <div class=\"codevalue\">${o[e]}</div>\\n              </div>\\n            `;e(i)})).catch((()=>{e(\"fail to load resource\")}))}}};const _hoisted_1$5={class:\"resource-item\"},_hoisted_2$3=[\"innerHTML\"],_hoisted_3$2={class:\"resource-toggle-icon\"},_hoisted_4$2={key:0},_hoisted_5$2={key:1},_hoisted_6$2={key:0},_hoisted_7$2={class:\"resource-detail\"},_hoisted_8$2=[\"innerHTML\"],_hoisted_9$2={key:0,class:\"resource-empty\"};function render$5(e,t,o,n,i,r){const s=resolveComponent(\"do-col\"),a=resolveComponent(\"do-row\");return openBlock(),createElementBlock(\"div\",_hoisted_1$5,[createVNode(a,null,{default:withCtx((()=>[createVNode(s,{span:22},{default:withCtx((()=>[createBaseVNode(\"div\",{class:\"resource-preview\",innerHTML:r.resourcePreview,onClick:t[0]||(t[0]=(...e)=>r.toggleDetail&&r.toggleDetail(...e))},null,8,_hoisted_2$3)])),_:1}),createVNode(s,{span:2},{default:withCtx((()=>[createBaseVNode(\"div\",_hoisted_3$2,[i.showContent?(openBlock(),createElementBlock(\"span\",_hoisted_5$2,\"▾\")):(openBlock(),createElementBlock(\"span\",_hoisted_4$2,\"▸\"))])])),_:1})])),_:1}),i.showContent?(openBlock(),createElementBlock(\"div\",_hoisted_6$2,[createBaseVNode(\"div\",_hoisted_7$2,[createBaseVNode(\"div\",{innerHTML:i.detailHtml},null,8,_hoisted_8$2),i.detailHtml?createCommentVNode(\"\",!0):(openBlock(),createElementBlock(\"div\",_hoisted_9$2,\"Loading ~\"))])])):createCommentVNode(\"\",!0)])}var css_248z$5=\".resource-item[data-v-51c2b43c] {\\n  margin-top: 10px;\\n  border-radius: 5px;\\n  overflow: hidden;\\n  border: 1px solid #d6e4ef;\\n  font-size: 12px;\\n}\\n.resource-preview[data-v-51c2b43c] {\\n  word-break: break-all;\\n  color: #1485ee;\\n  padding: 5px;\\n}\\n.resource-toggle-icon[data-v-51c2b43c] {\\n  line-height: 24px;\\n}\\n.resource-detail[data-v-51c2b43c] {\\n  border-top: 1px solid #d6e4ef;\\n  text-align: center;\\n}\\n.resource-detail[data-v-51c2b43c] .resource-empty[data-v-51c2b43c] {\\n  padding: 10px;\\n  font-size: 16px;\\n}\\n\";styleInject(css_248z$5),script$5.render=render$5,script$5.__scopeId=\"data-v-51c2b43c\";var script$4={components:{ResourceItem:script$5},props:{resourceList:{type:Array,default:[]}},data:()=>({}),methods:{}};const _hoisted_1$4={class:\"resource-container\"};function render$4(e,t,o,n,i,r){const s=resolveComponent(\"resource-item\");return openBlock(),createElementBlock(\"div\",_hoisted_1$4,[(openBlock(!0),createElementBlock(Fragment,null,renderList(o.resourceList,((e,t)=>(openBlock(),createBlock(s,{key:e.entryName,index:t,type:e.type,initiatorType:e.initiatorType,entryName:e.entryName,base64:e.base64},null,8,[\"index\",\"type\",\"initiatorType\",\"entryName\",\"base64\"])))),128))])}var css_248z$4=\".resource-container[data-v-1dec987b] {\\n  padding: 0 5px;\\n}\\n\";styleInject(css_248z$4),script$4.render=render$4,script$4.__scopeId=\"data-v-1dec987b\";var script$3={components:{ResourceTap:script$6,ResourceContainer:script$4},data:()=>({resourceTabs:ResourceTabs,curTab:ResourceEnum.CSS}),computed:{resourceList(){return this.$store.state.resourceList||[]},curResourceList(){return this.resourceList.filter((e=>e.type==this.curTab))}},created(){this.$nextTick((()=>{this.refreshResource()}))},mounted(){},methods:{handleChangeTab(e){this.curTab=e},refreshResource(){let e=[];getResourceEntries((({type:t,initiatorType:o,entryName:n,base64:i})=>{e.push({type:t,initiatorType:o,entryName:n,base64:i})})),this.$store.state.resourceList=e}}};const _hoisted_1$3={class:\"all-resources-container\"},_hoisted_2$2={class:\"resource-container\"};function render$3(e,t,o,n,i,r){const s=resolveComponent(\"resource-tap\"),a=resolveComponent(\"resource-container\");return openBlock(),createElementBlock(\"div\",_hoisted_1$3,[createVNode(s,{tabs:i.resourceTabs,onChangeTap:r.handleChangeTab,onRefreshResource:r.refreshResource},null,8,[\"tabs\",\"onChangeTap\",\"onRefreshResource\"]),createBaseVNode(\"div\",_hoisted_2$2,[createVNode(a,{resourceList:r.curResourceList},null,8,[\"resourceList\"])])])}var css_248z$3=\".all-resources-container[data-v-7468c67f] {\\n  display: flex;\\n  flex-direction: column;\\n  height: 100%;\\n}\\n\";styleInject(css_248z$3),script$3.render=render$3,script$3.__scopeId=\"data-v-7468c67f\";var Resource=new ce({name:\"resources\",nameZh:\"静态资源\",component:script$3,icon:\"https://pt-starimg.didistatic.com/static/starimg/img/Sc7OC34ccw1622432302129.png\",onLoad(){},onUnload(){}}),script$2={props:{interfaceIndex:[Number],interfaceItem:[Object]},data:()=>({showContent:!1}),methods:{toggleInterfaceSwitch(){this.$emit(\"toggleInterfaceSwitch\",{interfaceIndex:this.interfaceIndex})},setScene(e){this.$emit(\"setScene\",{interfaceIndex:this.interfaceIndex,sceneIndex:e})}}};const _hoisted_1$2={class:\"dokit-interface-item\"},_hoisted_2$1={class:\"dokit-interface-title-text\"},_hoisted_3$1={key:0,src:\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAAEN1JREFUeNrt3X9sHGV6B/DvM2uvbZJsAs5vDBFWUwpNVAF3B4JQcDyz6xhIQo7mTgX1EEdboeTKXaXmD3o9pUJ3FeGP5g/fqXdcgbtCdQ1QaB2MPTNJCuVocjSgXiIQl9Yo55DESRw7JrHxj52nf3gd7S/bu57dHe/m+5Esed+ZeeeZV/vsvPPrHYCIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIio7EnQAaSzbfsewzAaRKQhHo9fJyINIrJEVSOqulBEIgAiQcdJs3IJwCCAQVUdFJHzInJCVU8A6FHVE9XV1e80NTV9EXSgkwJPEMdxbhGRJlVdD6AJwFVBx0SB22sYRpeqvmOa5q+DDCSQBHFdtxHAZgCbVfXuIBuA5jYReQ/A3lAo1N7U1HS05Osv5cocx9kCYCsmkqOm1BtL5U1VbQCvDw8Pv7xp06bPS7HOoifIgQMHFo2Pjz8C4GEAd+S42CiA3sk/ETmd/FlVe0VkGYDLf6q6PPkzgHApGpAC8amIvDQ2Nvbyhg0bPinmioqaILZt7xCRbQCuz2H24wDeBPCKZVn/4XfdXV1dfxgKhbao6hYA1xVzOykwIwCe8zxvVywW6ynGCoqSILZtbxSRHQDumm4+VT1qGIatqu2FSIqpOI5zL4D7VHWziPxOsdZDgfktgGcty2ordMUFTRDXdetVdReAx6aZrQ9Am6o60Wj0l4XeoJk4jhMTkYdU9fFSr5uKS1UdVf2bWCx2qFB1FixBbNu+S0R2A/jSFLOMAWgTkTbTNLuL21Qzc133blXdjomTBlQ5+gF827KsnxeisoIkiOM4jwHYDWDBFLM8D6DNsqwPS9FCecZ+H4DtAFqCjoUKR0R+YJrmX/uux28FjuM8DeC72aap6qsA2qLR6Nulb6K8t+OPRWSbqt4ZdCxUMK9ZlvWQnwp8JYjrurtV9cksk04C+JZlWf8aZOvMhm3bT4nI94OOgwrmfcuyvjLbhWedIK7r/req3pZl0rue5z0Ri8VKftWzUGzbflxEngs6DioMETltmuaKWS07m4Ucx7kIYF6WST/v7+//061bt44G3Sh+ua7bpKq/ALA06FjIPxH53DTNvG9yzTtBXNc9pKoZuyzP8/42FovtDLohCmnPnj3hRYsWHRaRNUHHQgVxyLKsXO/mAJBngriu+4KqPppebhjGnzQ3N/9T0FtfLI7jvArgq0HHQQWxx7Ksr+U6s5HrjLZt78iWHABaKjk5AMCyrIdE5Jmg46CC2Oq67ou5zpzTHiRx68i/ZSws8rRpmt8LeotLxXXdPar6R0HHQf6p6vZoNPrDmeabMUHeeuutG6uqqt4CcEPapH+3LGtT0BtaatOcvaPyMigipmma708304xdrKqqqt3ITI6DV2JyJNwHYCDoIMi3iKr+YKaZpk0Qx3H+Cmm3YKjqp6Ojow8GvXVBMU2zV0Q2Bh0HFYSZ+I5Pacou1v79+2/0PO9dVV2cVHzJMIyNzc3N+4PesqDxYmLFOCcid5mm+ZtsE6fcg8Tj8R1pyQEA32FyTIhGoz9V1R8HHQf5thjAjqkmZk2QxB2u6c90/KdlWfzFTKKqbQCGgo6D/FHVb7que3+2acYUCzySpbjgT2uVu1gsdlRE2C4VwPO8P8tWnpEgiXGqvp5W/KZlWXuC3oi5KB6PtwE4HXQc5I+IPJBtL5KRINn2HvyVnFpisAC2TwXIthdJSZADBw4sFpGH0+b5Z9M0O4MOfo5rE5GiDj9DxSciD+zbt29DcllKgoyPjz+IiTGlkhfir+MMLMu6kDhgpzLned7m5M/pXayUiSLykmma/xV00OUgMeTM/wUdB/mjqg+6rls/+flygnR2dv4BgNa0+d8MOuAy0xV0AOSPiCzxPO/ynSKXEyQUCm1OnlFVB2tra5kgeVBVJkgFEJHNl/+f/MdxHAeAmTTfC5ZlPZZHvQTAdd0eVW0IYt1XXXUVlixZgsWLFyMcDiMcDiMUCmF0dBSjo6MYHBzEuXPnMDAwgPHx8bzrb2xs9B1jd3fqkGiFqDNbvX6o6ohhGNeaptlXBQAdHR1LkJocUNX2gq3xytIF4JulXGF9fT2WL1+OFSuyj0tQW1uL2tpaRCIRNDQ0YHR0FKdOncKpU6dw8eLFnNfj98vc399flHoLmRwAICI1mMiHfzEAIBwOp4yhq6qjAwMD7F7Ngqq+Usr1NTY24pZbbpkyObIJh8NYtWoV7rjjDqxatarkbVQOVLUZSByDqGrKwbmIvFYJI5MEwbKsLgBnS7GuNWvWTPvrO9mtms7q1asL1s2pMM0AUAUAInKbql6eIiJlN+DbHPMqgCeKuYJbb70V11xzTUb5yZMn0d/fjwsXLmBoaOI+SsMwEIlEEIlE0NjYiKqqqpRlGhsbMTAwgPPnz+cVQ6G7NsWuN0+NHR0dkSoA8DzvOpGUR0N+M7s6CQBU9ZO09iyo1atXZ02OI0eOoLe3N6Pc8zwMDAxgYGAAfX19WLNmDRYsSB1Gee3atTh8+HBexyQVniAIh8MNRkdHR42ILEmb1jurGgkAoKpFa79FixZlPW54++23syZHukuXLuHQoUMZX8Lq6moej6RR1euMmpqajFOSpmkyQXwwDKNod/def33my7o+/vhjjI2N5VVPd3d3xvHJihUrUF9fn1c9lUxEGgzP89JfT8bk8K8obRiJRLB0aepIqGfOnMFnn302q/o++uijjLJ8zoZVung8fp2hqunjlTJBfIrH40Vpw/TjDs/zcOzYsVnXd/HixYyu1tKlS1HM46dyEgqFFhgA0hOED//41NLSch4Tb+otqCVLUg8Vh4eHMTw87KvO9INywzBw9dVXF7+RykOkyjCMSPIpXnAPUii9KPDbdcPh1Ddb+00OALhw4cKM65lKPtdP+vv7p7yS7qfeYp7xUtVIFbtYRVMWCTIyMoLh4WHU1dVdLqupqclp2Xy+yIcPH8553rmSIAAiOQ9eTXQlMkQk/V6EZbOqidIVvB1HR1MPa5J/9WerpqYmo56RkZEiN03ZGKzyPG8w7awFE6QwipIgyV/mQiTIwoULs64nF8Xo3uRzrFJsIjJYBSB9D7I86MDKXWdn5zUAcjvSzcPZs2dTvtB1dXWoq6vzdSwyf/78lM+Tt6XkotJvNQEwyC5WEYRCoaK0YfrNhIZhYPXq1bOub/78+RkHxGfPnoXneSVopbkvHo9/bhiG0ZNWzgTxryhtODg4iDNnzqSULV26FNdee+2s6rv55pszyk6ePFmC5ikPoVCoxxgZGTmRPsF1XSaJD57nFa2b2tPTk1F20003obq6Oq96GhsbEYmknuE/deoU+vr6StRKc5+qnjBaW1tHVDX9AR8miA8iUrT26+/vx/HjxzPK77nnHixbNvNq582bh9tvvz2jazU2Npa13itZPB4/UQUAhmH0qOqSpAm/C+DXQQdYrkTkxmLWf+zYMSxYsCDj3qy1a9eivr5+2gembrjhhqx7myNHjuT1LMiVwPO8nioAUNXDAG6dnBAKhTZh4qk4mp0txV7BBx98gDVr1mD58tTe3MqVK7Fy5UoAE6drv/jii4yuVLru7u68nyasdKr6aWtr66ABACLSkTZxc9ABlivHce5FibqoR48enfaUaDgcnjE5jh07NpdOq84ZIuICiUEbRkdHf5k2fX5XV9eGvGslACjpa6K7u7vx4Ycf4vTp3G/CHh0dxfHjx3Hw4EEed0xBRPYBiUEbWltbzzqO4yJpbCzDMFoBvBV0oGXIKvUK+/r60NfXh+7u7qIOHFesK9xz5cp5khEALpBIkIR3kZQgiW7Wt4KOtJwkulezv3Ln09DQEI4fP160vUI+d+Tmao5271zTNPuApLF54/H4G8lziEgDu1l5iwUdAPmnqm9M/n85QVpaWv4HQMrBeigUasm9WlLVknevqODOGobx+uSH9OdB3kj+oKpbHce5Ppdar3S2bT8oIrcFHQf5IyKvT3avgLQEqaqqeh2pTxQuB7A96KDLBNupAojIG8mfUxKkqanpnKq+nLbMdsdx1gYd+Fzmuu43RGR90HGQP6ra3tzcnHLmNuORWxF5Ka2oTkS2BR38XLVz505DVbn3qACGYfwkoyy9wLKsD1X1F8llqvrntm3fBcqwbt267QC+FHQc5I+qtpumuTe9POugDVn2IgD72Bn27t17NfcelSHb3gOYIkEsy3oTwPPJZSLyddu2W0GXhcPh7QjwwiAVzPPZ9h7AFAkCAKFQaJeInEsuE5Gn29vbZ/f4WoWxbfseEXkq6DjIt3Pj4+O7ppo4ZYKsX7/+E8/znk0rvrW2tvanQW9R0Do7O1eIyI8A1AYdC/m2a8OGDZ9MNXHageOi0eguAJ1pxS2u674Y9FYFKRQK/QTAzb4roqC5lmU9O90MM46sGAqFvg3g0+QyVf2GbdvfD3rrguC67m4A9wcdB/k2mEsXecYEWb9+/SeJJEkhIk/t27fvirqZ0XXdv1DVJ4OOg/xT1adM03x/pvlyfhGEbds7ROSZ9PKxsbGFra2tg7nWU65c172f746vDCLyM9M0H81l3pwHr45Go7tE5MX08urq6gsdHR0NudZTjmzbfpzJUTH25JocQB57kEmO4/wKwJfTyz3Pa47FYvuD3vpCc133BVV9NOg4yD9V/VU0Gr09n2Vm9a4tx3EuApiXJYAnotHoPwTdEIWSeAy5Oeg4qCAuWpa1IN+FZv0yOsdxUoYKSvL3lmX9ZdCt4Yfrul9W1X8EwLuYK0OvZVmzGu3S19saXdfdne2sjqp2eJ73ZEtLy/8G3TL5sm17o4g8B2Cp78poLnjfsqyvzHZh368zdRznaQDfzTLpMwBtQ0NDP9y0adPnATZQTrq6un7PMIxt4E2ZleQ1y7Ie8lNBQd736zjOYwB2A8jWx/tYRNpM0/xR6dtnZp2dnSsMw9gOYLuIRHxXSHOCqv5dNBr1fa9cwV6Ibdv2XSKyG1M/G3FQVX8YjUZfyqPaounq6poXCoW2JW5XL+jLNilQ/Z7nfScWi/2sEJUV9I3x7e3ti2tra58B8Ng0s9mGYbQ1NzcHcl1h7969V4fD4a9hYo/x+0HEQMUhIo6qfs+yrIMFq7MYge7fv39jPB7fAWC6pxCPi4jjeZ4djUZfKUYck9577726oaGh+wDcp6oPAKgv5vqo5H4L4FnLstoKXXFREmRS4vaUbQCmHTpIVfsT2f9qoZJl586dxrp16zaq6hZV/aqIXFXMbaVAjAB4zvO8XbFYrMd3bVkUNUEA4MCBA4vGx8cfAfAwgDtmml9V44mBg3sn/1S1F0CvYRinAfSaptmbeAvWssTbnJYlXlpz+U9VTRHhe+Ar06ci8tLY2NjL0z3LUQhFT5BkjuNsAbAVwGYANaVcN5U/VbUBvD48PPxyqS4dlDRBJrmu24iJJNmsqncHEQOVBxF5D8DeUCjU3tTUdLTk6w+6ARzHuUVEmlR1PYAmADxWoL0A7Hg8/k5izOjABJ4g6RzHuVdVG0SkARPXJxoALBGRiOd5CxMX83hBrzxdAjCY9HdeVU+ISI+InFDVE/PmzXvnzjvvHA46UCIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIqIK8v+29fNNeUoBkwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wNi0yM1QxOTo1MDowNyswODowMBBCfKkAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDYtMjNUMTk6NTA6MDcrMDg6MDBhH8QVAAAAAElFTkSuQmCC\",alt:\"\"},_hoisted_4$1={key:1,src:\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAAEzlJREFUeNrt3Xt0VNW9B/Dv78wEkEd4RAFLQAtorVKETHhoJmCQ1scVTb2t1Wt7W7Gl7YIuq6simIkrbSY+8N6FvYL3FnuxtnK1rtuiBfGiVDSZ8MwkiI/KAopCpETeISBkMud3/5Bg5hGYzJnMzsTv5y/OnnP2/p1jfp7X3vsARERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERBlPTAcQbUJp5dSwbeWKSK5Ah6sgV1UuEGg2oP0BZAOSbTpOSspxAI0QNEK1USCHVFGvovWWuPaoan0/d3Plm2VFJ00H2sp4guSXrhuv0CLYOg2CIgC9TcdEpulKEVltu9yVtWWTt5qMxEiCTJhXNdJ2WcWwUAzVQpMHgLo2gaxT6Epxy4qasoJ3099+GuX5ArdawG0KFAPome6dpYz3GgTLe53CsuoF3mPpaLDTE2RcWd0Ad+j4d1VwJ4DJ6dgpMqYBqg0Q2aeQBhFtgI0GgTao2A2qVh+xZIiqDhWVIbAwRFWHCGQIgCEA+ibYzi4AzwmwrMbv3daZO9SpCZJfUjlXLWs2FCM6sx0y5hQgr4qE/1hTPuUFp5V5ytZdhlC4GCLFACYl0r4qnnYhvGBzxdQ9nbGDnZIg+b7AzQrMBVDQGfWTUY0QWa3AqmxX8wud9cRpnO+tS1xq3QDLuhGq1511ZcFusfF4TYV3UarjSGmCTJy/MafFal4gIjNTHSgZt0pFng+F7FfeebTwcDobPpMsIncDGNvuioLXAS0NlhduTFXbKUuQ/PmVBXBZTyiQ37mHi9JLq0V0USouoZy6pmxtr2MtPWZDMAeqF7ez2mER/XlNeeHvU9FmShIkv7Rqpqo8AaBfeg4VpcE7Ktai2vKrl5gOJNrYsurBWS06B8AcAAPjrSMiD9eUF5Q4bctxgnhKq8qh4kv/YaJO8hEEi3o0WYvXL7z6U9PBnE1e2brR0qJzoPYciLiif1fBn2rLvd9y0oajBPH4qp8A9B7Dx4lSRfFfbnWXbXx4coPpUDoir7TKIyqLEefJlwKba/3eicnWnXSCeHyBGgAe0weHUkOhc2r9hYtNx5GsgrmBfqd6YpkqZsT5eV/Q770wmXqTShCPr6oJkD6mDwqlRLOt8vW6ioJK04GkgscX+C2Au+P8dCzo93a4k6vV8QCqNzI5uo1gD7d1YXdJDgAI+r0/BGRenJ/6eXyBDR2tr0NnEE9J4BkIfmD6IFBKPBv0e39gOojO4imp8kKkKs5PLwb93u8kWk/CCZJfUjlXxXrM9I5TCqj+R7CisPs/XFEVT2m1HeeXhP/nkNAlVr4vcDOTo9vY+oVIDgAQUQEui/PL9/N8VbMTquJcK+T7Al9R4FUAXza9v+TYkaDfO9B5NZklr6Rqhoj8Jaq40ZKW6ZvLr9l8tm0TOYM8ASZHt6DQO0zHYEJtReEKQH8RVZwdVvfD59r2rAmS7wvcr8D1pneQUkHm1foL/890FKYE/YX/LioR3WYEmJ7nC9x/tu3aTZDTl1ZzTe8YpYDIU0F/wRf+HrJvVvM9AN5sWybA3IllVZe2t027CWKrzAVwvumdImcEsi5YXpDQDWl392ZZ0UkV/QWAcJvi88Mt0u6JIG6CeB6q/icR5ZiO7mGh6QC6ktrywiAg0QOr7s7zVd0Ub/24CaK2/V3TO0Ip8WKNv+B/TQfR1YTRshhAxKAvS2RWvHVjEiS/dN14gdxueifIMVXl2SOeLf6p2wFEnEVUMSPeWSQmQdQO8+zRHagurK3wdrjv0RdFyC2LIPJh2zJLEXMWiUgQT9na8yFyp+ngybE97lALzx5nsbWs4BPYGtG9X0Vm5D8YuKFtWUSCaIvrm/hsfiLKYAJ74cYFRfWm4+jq+mWFFgGInNrUJcVtFyMSRGAVn6tS6vJ21vin8OyRgM8e++KZtmWq+s2J8zfmtC6fSZDxpVVXArjRdNDkjELXmI4hk7jC8mZU0QVhCX2zdeFMgojy7NEdWGq9bjqGTLL54YItAOoiCl0obv2n+/NS29sFvoZwxuTRAzBp1ACMHZGNnL5ZyOmbBQA42BTCwaYQNu44jM1/P4q6jxo7XPesouExZUvWJjdzZSrrSoGQtpzgGaSjVNdCZPyZRVunT5y/MWfTI5MOugFg/PzKCwQy3XScOX2zcOO4wbjxygtwydD4o3pzB7mQO6gXrhzRD7OmAdv3Hceqt/dj1ZZPcLAplFhDAswqipwu2O2y8NSajzoetEhEktTsOgqYShDBquBjXz9qpvHMZbt0lWXLfa3LItKzxQpNB/BHCwAsl/k5dEcO7o3/vGsM7rnu4naTI55LhvbBPdddjP/+0Vh4vtw/6fZnTs3FN76W2V3PFNYq0zFkorpfTfkrgP1ty0T0WuDMPYgYvTkfk9sPL/5sPEYOTv7jUrmDeuE3M8c4ShJf8WhHMZgmVvgL2509BV6LWBK5FjhzD2J5ADUS1bCBvfC7H8efj3jJ2t3Ye/gU9hw8iY8OnIAqMDznPAzP6YXhg3ph1rTYryr8ZuYYfOPRTTh0PMHLrTZ693DBd8tozHza6Fe/kqRbg78s3G06ikwlkJcU+vlLcsXISWUbsk8niA5Psl7Hyv75kpiyoydacO9z72PrntiPCB2tP4Z36z8rrz98EvNmjELvHpGzTvqKR+O+ZX9LKp6xI/rhwZtH4eG/7DR1SJIikB2mY8hk4XD4fcsV2fOqGci1Rv9sVU8AF5gI6o6rvoTxF0XO5bV933Fc+8jGuMkRbdWW/ZhSvgHv1UeuO+WyQbjjqi8lHMf+Y80Ry7dOGIrbJiU1EZ8xCmTUdKFdTTj0acwTGmluHm71798/10RAg/pk4farYv8If/rMex2uK942t191IQb1yUpo+90HYudonnvTSEf3M+mmUCaIA1v/7brjgB5oW2ZZVq4lsI1cXk27IgfDBvaKKFuydg+OnOj4vcOJ5jDK/rw9omzYwF64YVziJ8Z4j3hLbhmFgQkmmWkWXPtMx5D5JOKPwIYOtwDp8HylqTD1skERyxt2HMGSN5K/x1xZ9wk27DgSUVb01ZyEt1/6Vj1efTviSR9G5JyH+TNGmTg8HcZLLOcEiEgQEaufZUv6EyTLJbjqksjpmd772PlXfbfvOx6xPO6ibGS5Eu8d8NjKndj2j8g6pl2RE/dteVdjCy+xnLKjEgTQbEuAtCfIoL49Ysre/7jJcb0f7I2tI15b7Wk6GcZjK3ciFI585D1r2ghMv6Jrv0R0h2wmiEMCjUwQRbaRS6zWflVtpSJB3t4dexaK19bZbN19DI+tiH3E++Ato7r0S8RTLSeYII65Ii+xgOwOf/6gK7M1NS87Xwo2YNm6vRFl2ee5MW/GSNO7SGlmAdrx7rAOxetUePmwvo7rHZMb+w3RhDswRln46i6s334koizv4v544KaumSQ93b05EtSx8EVtlxRotBRIe4IcamqOKUtFgnzlwthOjvHaStQjK3ai4eipiLJvT7oQ3544NA1HqWNasiwmiEMKiUgQCBotS9N/BgmFFeu3R36L/ophzr8gPWpI5D3Clo8aY264O2Lv4ZN4dMXfY8ofmDEKnou71ktES4UJ4pAFRCYIpNHIJRYAvPXBoYjlyaMHOHqc6r10IK6Jeu+x9m8HHcdZte0Qnnztw5jyB2aMxIAu9BJRONmGYxqVIKr2MUthGRnd88b7B7H38MmIslnTRmBA747/0bktweP/8tWIsr2HT8a8+EvWs1Uf45Utn0SUjRzcG/O60P2IjXDXu+7LOBqRIBZkj3X06FEj08Mcagrh+fX/iCl/6q4rOlzXkh9+LeaF4PPr/4FDSd6gx1Px8k78Leo9y/QxXefdiICXWE6M/cXqPoBE/Ae1bbve2vHkjacQNZoqXZ5fvzdmTPmlQ/tgzfyJGDv83Pcklw7tg1X3T4hZt/KDQ3h+/d5zbt8RzS02Kl7eiVOhyE/edZW37LzEcsaVdd5FMWUuq751RKGxWQZ+GdXJEAAG9M7C0lljMatoOIouz8H5/SLfhn9pQE9ce0UOfvujr2Fwduybcv/LnTM04oO9TV12nIgCmdFprItyuXB5dJnlcu85PWDKDgKSZyKw+kMncdeSrXhmVuyowrYjBusPncSnzWEMG9gLvXu62q3vJ0vfTemlVbRXtnyCkYN74/uFw0wcrrO5ctLctbmcUTE5Citq4mrdtbFscuPpM4jL6GD/d/Ycw3eerMOu/SfaXSd3UC9cMrRPu8nx8aGT+MnSdz+bVaSTPfnahwhsO+y8ohQL9ejxddMxZC79RlTBGuD0pA12uKXadHg7PzmBnyx9D79e/SF2NBxPeLvtDcfx69UfYubT76QlOVr5X96BvYdPOa8ohSzodaZjyEQTStZdDUjE6D1V+StwetKGukem7M/zBdYIYHRurINNzfhD4GP8IfDxmYnjrhyRjUFRE8cdagphw84j2Pz3I6j7MLnXOEGHyXTgWDP8L+/A3VONDMiMS6HXe2bVZAWX5HfeNWY3FJbwjdJ20kTVU267xxogYmZFCQBqfPK4Vht2HIkZAJUqS97YA8D5c4lNO49g087OiTE50l+HNk8DsNp0JJlExJqCth1dLWvNpopJB4E2c/Oq2C+ZDpScs2z7WtMxZJKJvre+DNXCyFJ9qfVfZxKkrrzwbQCcmS/DKTDNdAyZpAXWNVFF+10tWctbFyI/oAOeRboBT76v8lumg8gYKt9uuygiyzc9MulMJ77ID+i4w8vBwf8Zz4brXtMxZIL80urbRRDxyTWEP7+8AqISJFhWdACqy0wHTs4I9Oq80sDPTcfR1anqnLbLorqi5mHvq23LYobciuV6znTg5Jyo3ndlybou97q/q8j3BX4MRH7VwBYsiV4vJkFqyq+uU+gLpneAnJLhboTvc15P9+MpW9FbFZFnD8GKWn/hyuh1407aIJbFs0h3IHJvXklgsukwuhptGTgbgjFty2zVJfHWjZsgwV8VvKIqS03vCDkmIuANexsTyjYNFYk8e6jq0nhnD6CdBAEAS3QBgAOgTHdbXglv2FvZoeZyKNp+WOaAJbKgvfXbTZAav3ebqP246R0i50SwMM8XuNV0HKbl+QL3Q/DDtmUKLKjxe7e1t805J67N9wVeVeB60ztHDgn2NYf08nceLex6/fTTIN8XuFmBl9uWKbCm1u896xCBRGZW/DmAXaZ3kBxSDO3htr6Q3zD0lK27LDo5ADS6RB4817bnTJAav3ebfJYklPF0Yp6v+vemo0irMrXQYtfFHAnog5vLCzafa/OE5uat8Xv/Imo/YHpfyTmBfi+/pOoO03GkiycUeAdAr6jiZ2v9hYsT2T7xj2cA8JQEnoHgB6Z3mlJAcGew3Ps/psPoTJ7SwHvQmMkYXgz6vd9JtI4OJQgA5PkCmwSYYHrnKSUeCfq957wOzzQTHqweZ1saABA1WbNuCvoLJ3Wkrg4nCAB4fIGm2MYpM+nKsLvle1vKio6YjiQV8nxVswWyKM5PTUG/t8MTQCf1fZCg39sXkFrTB4NSQW5yhbPqPCVVXtOROJVfWl3RTnI0JJMcQJIJAgBBf4EHkF+bPiiUAoqLIVLl8VX+1HQoSRKPL/BbVY25XFRgc9DvTXre4qQusdrylFaXQ9Vn+ghRiqguV7EW1foL3jAdSiLyS6v+VdWaDejEmF0R/Km23OtodKXjBDkd5ExVeQKA8498UNcg8jvb1sV1Fd4a06HEk/9Q9S22rXPOMlVVSh5ApCRBACC/tLIAaj2hQH7ajhJ1Nhuqi8KStWiLf/J259U553koUCQ25ijQXt+yw1DcG6zwPpuK9lKWIADgKVt7voZ6PCaiMzv9SFE6HYbo4rDav9/in2okUTylVZNErR8r9K52VxK8rjYeqq3wbkhVuylNkFanO4bNRdSQRuoGFK9DdLUL9p83+ad2ah+9z5ICNyhwAyAT211RsFtsPF5T4V3UgeoT0ikJ0iq/pHKuWtbsqP731E0IUGkrlruyQi9sLival4o680oCk0W0GGLdAtXLzrH6KVU87UJ4weaKqZ3yCY9OTRAAGFdWN8AdOv5dFdwJgMM/u683BWhQ1QaFNliWe5+KNthhbXAj3HA8q6HBdfLCPllue6gFa4itMkQsGSLQIbatQwUyBAIPgEQeye4C8JwAy842liMVOj1B2srzBW61gNsUKAbQM51tU7fwGgTLe53CsuoF3mPpaDCtCdJqwryqkbbLKoaF4th5UYk+J5B1Cl0pbllRU1bwbvrbNyy/dN14hRbB1mkQFAHo7bhSynCyEsBrttiVp+eMNheJ6UMRLd9XdY0tVq5AcmGHh6tIrgVcoEA2oP0ByQaQbTpOSspxAI0AGhVoFNVDEKlXyB4LWm/bdn3PT92V6xde/anpQImIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi6kf8H0v8psCtpcmoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDYtMjNUMTk6NTA6MDcrMDg6MDAQQnypAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA2LTIzVDE5OjUwOjA3KzA4OjAwYR/EFQAAAABJRU5ErkJggg==\",alt:\"\",srcset:\"\"},_hoisted_5$1={class:\"dokit-interface-title-arrow\"},_hoisted_6$1={key:0},_hoisted_7$1={key:1},_hoisted_8$1={class:\"dokit-interface-content\"},_hoisted_9$1=createBaseVNode(\"div\",{class:\"dokit-interface-sub-title\"},\"接口信息\",-1),_hoisted_10$1={class:\"dokit-interface-info\"},_hoisted_11=createTextVNode(\"请求路径\"),_hoisted_12=createTextVNode(\"query\"),_hoisted_13=createTextVNode(\"body\"),_hoisted_14=createTextVNode(\"分类\"),_hoisted_15=createTextVNode(\"创建人\"),_hoisted_16={class:\"dokit-scene-list\"},_hoisted_17=createBaseVNode(\"div\",{class:\"dokit-interface-sub-title\"},\"场景选择\",-1),_hoisted_18=[\"onClick\"],_hoisted_19=createBaseVNode(\"span\",{class:\"dokit-scene-checkbox\"},null,-1);function render$2(e,t,o,n,i,r){const s=resolveComponent(\"DoCol\"),a=resolveComponent(\"DoRow\");return openBlock(),createElementBlock(\"div\",_hoisted_1$2,[createBaseVNode(\"div\",{class:\"dokit-interface-title\",onClick:t[1]||(t[1]=e=>i.showContent=!i.showContent)},[createBaseVNode(\"span\",_hoisted_2$1,toDisplayString(o.interfaceItem.name),1),createBaseVNode(\"div\",{class:\"dokit-interface-title-opt\",onClick:t[0]||(t[0]=withModifiers(((...e)=>r.toggleInterfaceSwitch&&r.toggleInterfaceSwitch(...e)),[\"stop\"]))},[o.interfaceItem.checked?(openBlock(),createElementBlock(\"img\",_hoisted_4$1)):(openBlock(),createElementBlock(\"img\",_hoisted_3$1))]),createBaseVNode(\"span\",_hoisted_5$1,[i.showContent?(openBlock(),createElementBlock(\"span\",_hoisted_7$1,\"▾\")):(openBlock(),createElementBlock(\"span\",_hoisted_6$1,\"▸\"))])]),withDirectives(createBaseVNode(\"div\",_hoisted_8$1,[_hoisted_9$1,createBaseVNode(\"div\",_hoisted_10$1,[createVNode(a,{class:\"dokit-interface-info-item\"},{default:withCtx((()=>[createVNode(s,{span:8},{default:withCtx((()=>[_hoisted_11])),_:1}),createVNode(s,{span:16},{default:withCtx((()=>[createTextVNode(toDisplayString(o.interfaceItem.path),1)])),_:1})])),_:1}),createVNode(a,{class:\"dokit-interface-info-item\"},{default:withCtx((()=>[createVNode(s,{span:8},{default:withCtx((()=>[_hoisted_12])),_:1}),createVNode(s,{span:16},{default:withCtx((()=>[createTextVNode(toDisplayString(o.interfaceItem.query),1)])),_:1})])),_:1}),createVNode(a,{class:\"dokit-interface-info-item\"},{default:withCtx((()=>[createVNode(s,{span:8},{default:withCtx((()=>[_hoisted_13])),_:1}),createVNode(s,{span:16},{default:withCtx((()=>[createTextVNode(toDisplayString(o.interfaceItem.body),1)])),_:1})])),_:1}),createVNode(a,{class:\"dokit-interface-info-item\"},{default:withCtx((()=>[createVNode(s,{span:8},{default:withCtx((()=>[_hoisted_14])),_:1}),createVNode(s,{span:16},{default:withCtx((()=>[createTextVNode(toDisplayString(o.interfaceItem.categoryName),1)])),_:1})])),_:1}),createVNode(a,{class:\"dokit-interface-info-item\"},{default:withCtx((()=>[createVNode(s,{span:8},{default:withCtx((()=>[_hoisted_15])),_:1}),createVNode(s,{span:16},{default:withCtx((()=>[createTextVNode(toDisplayString(o.interfaceItem.owner.name),1)])),_:1})])),_:1})]),createBaseVNode(\"div\",_hoisted_16,[_hoisted_17,(openBlock(!0),createElementBlock(Fragment,null,renderList(o.interfaceItem.sceneList,((e,t)=>(openBlock(),createElementBlock(\"div\",{class:normalizeClass([\"dokit-scene-item\",e.checked?\"dokit-actived-scene\":\"\"]),key:e._id,onClick:e=>r.setScene(t)},[_hoisted_19,createTextVNode(\" \"+toDisplayString(e.name),1)],10,_hoisted_18)))),128))])],512),[[vShow,i.showContent]])])}var css_248z$2=\".dokit-interface-item {\\n  margin-bottom: 20px;\\n  border: 1px solid #d6e4ef;\\n  border-radius: 5px;\\n  overflow: hidden;\\n}\\n.dokit-interface-item .dokit-interface-title {\\n  padding: 0 8px;\\n  font-size: 18px;\\n  color: #2c405a;\\n  display: flex;\\n  align-items: center;\\n  justify-content: space-between;\\n  border-bottom: 1px solid #d6e4ef;\\n}\\n.dokit-interface-item .dokit-interface-title img {\\n  width: 40px;\\n}\\n.dokit-interface-item .dokit-interface-title .dokit-interface-title-opt {\\n  flex: 1;\\n  display: flex;\\n  align-items: center;\\n  justify-content: space-between;\\n  font-style: normal;\\n}\\n.dokit-interface-item .dokit-interface-title .dokit-interface-title-text {\\n  flex: 10;\\n}\\n.dokit-interface-item .dokit-interface-title .dokit-interface-title-arrow {\\n  flex: 1;\\n  text-align: right;\\n}\\n.dokit-interface-item .dokit-interface-sub-title {\\n  text-align: center;\\n  font-weight: bold;\\n}\\n.dokit-interface-item .dokit-interface-content {\\n  padding: 0 10px;\\n}\\n.dokit-interface-item .dokit-interface-content .dokit-interface-info {\\n  font-size: 14px;\\n}\\n.dokit-interface-item .dokit-interface-content .dokit-interface-info .dokit-interface-info-item {\\n  padding: 5px 0;\\n}\\n.dokit-interface-item .dokit-interface-content .dokit-scene-list {\\n  border-top: 1px solid #eee;\\n}\\n.dokit-interface-item .dokit-interface-content .dokit-scene-list .dokit-scene-item {\\n  margin: 5px 0;\\n  padding: 0 10px;\\n  border-radius: 5px;\\n  border: 1px solid #fff;\\n  background: #cccccc;\\n  color: white;\\n  height: 30px;\\n  display: flex;\\n  justify-content: space-between;\\n  align-items: center;\\n}\\n.dokit-interface-item .dokit-interface-content .dokit-scene-list .dokit-scene-item .dokit-scene-checkbox {\\n  display: inline-block;\\n  box-sizing: border-box;\\n  line-height: 30px;\\n  width: 20px;\\n  height: 20px;\\n  border-radius: 10px;\\n  background: #eee;\\n  border: 5px solid #fff;\\n  font-size: 18px;\\n}\\n.dokit-interface-item .dokit-interface-content .dokit-scene-list .dokit-actived-scene {\\n  background: #337CC4;\\n}\\n.dokit-interface-item .dokit-interface-content .dokit-scene-list .dokit-actived-scene .dokit-scene-checkbox {\\n  background: #337CC4;\\n}\\n\";styleInject(css_248z$2),script$2.render=render$2;var script$1={components:{interfaceItem:script$2},data(){return{interfaceList:this.$store.state.interfaceList}},created(){localStorage.setItem(\"dokit-interface-list\",JSON.stringify(this.$store.state.interfaceList||[]))},methods:{toggleInterfaceSwitch(e){this.$store.state.interfaceList[e.interfaceIndex].checked=!this.interfaceList[e.interfaceIndex].checked,localStorage.setItem(\"dokit-interface-list\",JSON.stringify(this.$store.state.interfaceList))},setScene(e){this.$store.state.interfaceList[e.interfaceIndex].sceneList.forEach(((t,o)=>{t.checked=!1,o===e.sceneIndex&&(t.checked=!0)})),localStorage.setItem(\"dokit-interface-list\",JSON.stringify(this.$store.state.interfaceList))}}};const _hoisted_1$1={class:\"dokit-api-mock-plugin\"};function render$1(e,t,o,n,i,r){const s=resolveComponent(\"interfaceItem\");return openBlock(),createElementBlock(\"div\",_hoisted_1$1,[(openBlock(!0),createElementBlock(Fragment,null,renderList(i.interfaceList,((e,t)=>(openBlock(),createBlock(s,{key:e._id,interfaceItem:e,interfaceIndex:t,onToggleInterfaceSwitch:r.toggleInterfaceSwitch,onSetScene:r.setScene},null,8,[\"interfaceItem\",\"interfaceIndex\",\"onToggleInterfaceSwitch\",\"onSetScene\"])))),128))])}var css_248z$1=\"\\n.dokit-api-mock-plugin {\\n  padding: 5px;\\n}\\n\";styleInject(css_248z$1),script$1.render=render$1;const mockBaseUrl=\"https://www.dokit.cn\",getCheckedInterfaceList=function(e){return e.filter((e=>e.checked))},getMergeData=function(e){let t=JSON.parse(localStorage.getItem(\"dokit-interface-list\")||\"[]\");return e.forEach((e=>{t.forEach((t=>{e._id===t._id&&(e.checked=t.checked,e.sceneList.forEach((e=>{t.sceneList.forEach((t=>{e._id===t._id&&(e.checked=t.checked)}))})))}))})),e};var ApiMock=new ce({name:\"ApiMock\",nameZh:\"数据mock\",component:script$1,icon:\"https://pt-starimg.didistatic.com/static/starimg/img/GEAC1clsH81623297652210.png\",onProductReady(){let e=st();fetch(`${mockBaseUrl}/api/app/interface?projectId=${e.productId}&isfull=1`,{mode:\"cors\"}).then((e=>e.json())).then((t=>{let o=t.data&&t.data.datalist||[];o.forEach((e=>{e.checked=!1,e.sceneList.forEach(((e,t)=>{e.checked=!1,0===t&&(e.checked=!0)}))})),e.interfaceList=getMergeData(o)})),request.hookFetch({onBeforeFetch:t=>{let o=getCheckedInterfaceList(e.interfaceList),n=t[0],i=\"/\"+getPartUrlByParam(n,\"path\"),r=\"\";return o.forEach((e=>{e.path===i&&e.sceneList.forEach((e=>{e.checked&&(r=e._id)}))})),r&&t[1]&&(t[1].method=\"get\")&&t[1].headers&&delete t[1].headers,r&&(t[0]=`${mockBaseUrl}/api/app/scene/${r}`),t}}),request.hookXhr({onBeforeOpen:t=>{let o=getCheckedInterfaceList(e.interfaceList),n=t[1],i=\"/\"+getPartUrlByParam(n,\"path\"),r=\"\";return o.forEach((e=>{e.path===i&&e.sceneList.forEach((e=>{e.checked&&(r=e._id)}))})),r&&(t[0]=\"get\"),r&&(t[1]=`${mockBaseUrl}/api/app/scene/${r}`),t},onBeforeSetRequestHeader:(t,o)=>{let n=getCheckedInterfaceList(e.interfaceList),i=o.originRequestInfo.url,r=\"/\"+getPartUrlByParam(i,\"path\"),s=\"\";return n.forEach((e=>{e.path===r&&e.sceneList.forEach((e=>{e.checked&&(s=e._id)}))})),!s&&t}})},onUnload(){}}),e,t,n,i,r=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:\"v2-\".concat(Date.now(),\"-\").concat(Math.floor(8999999999999*Math.random())+1e12)}},a=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if(\"first-input\"===e&&!(\"PerformanceEventTiming\"in self))return;var o=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return o.observe({type:e,buffered:!0}),o}}catch(e){}},o=function(e,t){var o=function o(n){\"pagehide\"!==n.type&&\"hidden\"!==document.visibilityState||(e(n),t&&(removeEventListener(\"visibilitychange\",o,!0),removeEventListener(\"pagehide\",o,!0)))};addEventListener(\"visibilitychange\",o,!0),addEventListener(\"pagehide\",o,!0)},u=function(e){addEventListener(\"pageshow\",(function(t){t.persisted&&e(t)}),!0)},c=function(e,t,o){var n;return function(i){t.value>=0&&(i||o)&&(t.delta=t.value-(n||0),(t.delta||void 0===n)&&(n=t.value,e(t)))}},f=-1,s=function(){return\"hidden\"===document.visibilityState?0:1/0},m=function(){o((function(e){var t=e.timeStamp;f=t}),!0)},v=function(){return f<0&&(f=s(),m(),u((function(){setTimeout((function(){f=s(),m()}),0)}))),{get firstHiddenTime(){return f}}},d=function(e,t){var o,n=v(),i=r(\"FCP\"),s=function(e){\"first-contentful-paint\"===e.name&&(d&&d.disconnect(),e.startTime<n.firstHiddenTime&&(i.value=e.startTime,i.entries.push(e),o(!0)))},l=window.performance&&performance.getEntriesByName&&performance.getEntriesByName(\"first-contentful-paint\")[0],d=l?null:a(\"paint\",s);(l||d)&&(o=c(e,i,t),l&&s(l),u((function(n){i=r(\"FCP\"),o=c(e,i,t),requestAnimationFrame((function(){requestAnimationFrame((function(){i.value=performance.now()-n.timeStamp,o(!0)}))}))})))},p=!1,l=-1,h=function(e,t){p||(d((function(e){l=e.value})),p=!0);var n,i=function(t){l>-1&&e(t)},s=r(\"CLS\",0),h=0,f=[],m=function(e){if(!e.hadRecentInput){var t=f[0],o=f[f.length-1];h&&e.startTime-o.startTime<1e3&&e.startTime-t.startTime<5e3?(h+=e.value,f.push(e)):(h=e.value,f=[e]),h>s.value&&(s.value=h,s.entries=f,n())}},g=a(\"layout-shift\",m);g&&(n=c(i,s,t),o((function(){g.takeRecords().map(m),n(!0)})),u((function(){h=0,l=-1,s=r(\"CLS\",0),n=c(i,s,t)})))},T={passive:!0,capture:!0},y=new Date,g=function(o,i){e||(e=i,t=o,n=new Date,w(removeEventListener),E())},E=function(){if(t>=0&&t<n-y){var o={entryType:\"first-input\",name:e.type,target:e.target,cancelable:e.cancelable,startTime:e.timeStamp,processingStart:e.timeStamp+t};i.forEach((function(e){e(o)})),i=[]}},S=function(e){if(e.cancelable){var t=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;\"pointerdown\"==e.type?function(e,t){var o=function(){g(e,t),i()},n=function(){i()},i=function(){removeEventListener(\"pointerup\",o,T),removeEventListener(\"pointercancel\",n,T)};addEventListener(\"pointerup\",o,T),addEventListener(\"pointercancel\",n,T)}(t,e):g(t,e)}},w=function(e){[\"mousedown\",\"keydown\",\"touchstart\",\"pointerdown\"].forEach((function(t){return e(t,S,T)}))},L=function(n,s){var l,d=v(),h=r(\"FID\"),p=function(e){e.startTime<d.firstHiddenTime&&(h.value=e.processingStart-e.startTime,h.entries.push(e),l(!0))},f=a(\"first-input\",p);l=c(n,h,s),f&&o((function(){f.takeRecords().map(p),f.disconnect()}),!0),f&&u((function(){var o;h=r(\"FID\"),l=c(n,h,s),i=[],t=-1,e=null,w(addEventListener),o=p,i.push(o),E()}))},b={},F=function(e,t){var n,i=v(),s=r(\"LCP\"),l=function(e){var t=e.startTime;t<i.firstHiddenTime&&(s.value=t,s.entries.push(e),n())},d=a(\"largest-contentful-paint\",l);if(d){n=c(e,s,t);var h=function(){b[s.id]||(d.takeRecords().map(l),d.disconnect(),b[s.id]=!0,n(!0))};[\"keydown\",\"click\"].forEach((function(e){addEventListener(e,h,{once:!0,capture:!0})})),o(h,!0),u((function(o){s=r(\"LCP\"),n=c(e,s,t),requestAnimationFrame((function(){requestAnimationFrame((function(){s.value=performance.now()-o.timeStamp,b[s.id]=!0,n(!0)}))}))}))}},P=function(e){var t,o=r(\"TTFB\");t=function(){try{var t=performance.getEntriesByType(\"navigation\")[0]||function(){var e=performance.timing,t={entryType:\"navigation\",startTime:0};for(var o in e)\"navigationStart\"!==o&&\"toJSON\"!==o&&(t[o]=Math.max(e[o]-e.navigationStart,0));return t}();if(o.value=o.delta=t.responseStart,o.value<0||o.value>performance.now())return;o.entries=[t],e(o)}catch(e){}},\"complete\"===document.readyState?setTimeout(t,0):addEventListener(\"load\",(function(){return setTimeout(t,0)}))},script={data:()=>({loadedTime:\"--\",FCP:\"--\",CLS:\"--\",FID:\"--\",LCP:\"--\",TTFB:\"--\"}),mounted(){d(this.handleData),F(this.handleData,!0),h(this.handleData,!0),L(this.handleData),P(this.handleData),this.getTiming()},methods:{handleData(e){console.log(JSON.parse(JSON.stringify(e)));const{name:t,value:o}=e;this[t]=Math.round(o)+\" ms\"},getTiming(){setTimeout((()=>{let e=window.performance.timing;const t=e.loadEventEnd-e.navigationStart;this.loadedTime=t+\" ms\";let o=[{key:\"Redirect\",desc:\"网页重定向的耗时\",\"value(ms)\":e.redirectEnd-e.redirectStart},{key:\"AppCache\",desc:\"检查本地缓存的耗时\",\"value(ms)\":e.domainLookupStart-e.fetchStart},{key:\"DNS\",desc:\"DNS查询的耗时\",\"value(ms)\":e.domainLookupEnd-e.domainLookupStart},{key:\"TCP\",desc:\"TCP链接的耗时\",\"value(ms)\":e.connectEnd-e.connectStart},{key:\"Waiting(TTFB)\",desc:\"从客户端发起请求到接收响应的时间\",\"value(ms)\":e.responseStart-e.requestStart},{key:\"Content Download\",desc:\"下载服务端返回数据的时间\",\"value(ms)\":e.responseEnd-e.responseStart},{key:\"HTTP Total Time\",desc:\"http请求总耗时\",\"value(ms)\":e.responseEnd-e.requestStart},{key:\"First Time\",desc:\"首包时间\",\"value(ms)\":e.responseStart-e.domainLookupStart},{key:\"White screen time\",desc:\"白屏时间\",\"value(ms)\":e.responseEnd-e.fetchStart},{key:\"Time to Interactive(TTI)\",desc:\"首次可交互时间\",\"value(ms)\":e.domInteractive-e.fetchStart},{key:\"DOM Parsing\",desc:\"DOM 解析耗时\",\"value(ms)\":e.domInteractive-e.responseEnd},{key:\"DOMContentLoaded\",desc:\"DOM 加载完成的时间\",\"value(ms)\":e.domInteractive-e.navigationStart},{key:\"Loaded\",desc:\"页面load的总耗时\",\"value(ms)\":t}];console.table(o)}),0)}}};const _withScopeId=e=>(pushScopeId(\"data-v-07dc8110\"),e=e(),popScopeId(),e),_hoisted_1={class:\"web-vitals-time\"},_hoisted_2=_withScopeId((()=>createBaseVNode(\"div\",{class:\"title\"},\"性能指标(web vitals)\",-1))),_hoisted_3=_withScopeId((()=>createBaseVNode(\"div\",{class:\"sub-title\"},\"请在页面加载完成后再获取指标\",-1))),_hoisted_4={class:\"content\"},_hoisted_5=_withScopeId((()=>createBaseVNode(\"span\",{class:\"item important\"},\"LCP：\",-1))),_hoisted_6=_withScopeId((()=>createBaseVNode(\"span\",{class:\"item\"},\"LOADED：\",-1))),_hoisted_7=_withScopeId((()=>createBaseVNode(\"span\",{class:\"item\"},\"CLS：\",-1))),_hoisted_8=_withScopeId((()=>createBaseVNode(\"span\",{class:\"item\"},\"FID：\",-1))),_hoisted_9=_withScopeId((()=>createBaseVNode(\"span\",{class:\"item\"},\"TTFB：\",-1))),_hoisted_10=_withScopeId((()=>createBaseVNode(\"div\",{class:\"desc\"},[createBaseVNode(\"div\",{class:\"title\"},\"备注\"),createBaseVNode(\"div\",{class:\"container\"},[createBaseVNode(\"div\",null,\"1、LOADED指页面完全加载时间（页面load的总耗时）；\"),createBaseVNode(\"div\",null,\"2、由于IOS系统暂不支持获取LCP指标相关接口，可用LOADED时间作为参考；\")])],-1)));function render(e,t,o,n,i,r){return openBlock(),createElementBlock(\"div\",_hoisted_1,[_hoisted_2,_hoisted_3,createBaseVNode(\"div\",_hoisted_4,[createBaseVNode(\"div\",null,[_hoisted_5,createTextVNode(toDisplayString(i.LCP),1)]),createBaseVNode(\"div\",null,[_hoisted_6,createTextVNode(toDisplayString(i.loadedTime),1)]),createBaseVNode(\"div\",null,[_hoisted_7,createTextVNode(toDisplayString(i.CLS),1)]),createBaseVNode(\"div\",null,[_hoisted_8,createTextVNode(toDisplayString(i.FID),1)]),createBaseVNode(\"div\",null,[_hoisted_9,createTextVNode(toDisplayString(i.TTFB),1)])]),_hoisted_10])}var css_248z=\".web-vitals-time[data-v-07dc8110] {\\n  padding: 10px;\\n  text-align: center;\\n  font-size: 16px;\\n}\\n.web-vitals-time[data-v-07dc8110] .title[data-v-07dc8110] {\\n  font-weight: bold;\\n  font-size: 22px;\\n}\\n.web-vitals-time[data-v-07dc8110] .sub-title[data-v-07dc8110] {\\n  font-size: 12px;\\n  color: #999999;\\n}\\n.web-vitals-time[data-v-07dc8110] .content[data-v-07dc8110] {\\n  text-align: left;\\n  margin-top: 20px;\\n  padding-left: 5%;\\n}\\n.web-vitals-time[data-v-07dc8110] .content[data-v-07dc8110] > div[data-v-07dc8110] {\\n  font-size: 22px;\\n}\\n.web-vitals-time[data-v-07dc8110] .item[data-v-07dc8110] {\\n  display: inline-block;\\n  text-align: right;\\n  width: 80px;\\n  margin-top: 4px;\\n  font-size: 16px;\\n}\\n.web-vitals-time[data-v-07dc8110] .important[data-v-07dc8110] {\\n  color: red;\\n}\\n.web-vitals-time[data-v-07dc8110] .desc[data-v-07dc8110] {\\n  text-align: left;\\n  margin-top: 40px;\\n}\\n.web-vitals-time[data-v-07dc8110] .desc[data-v-07dc8110] .title[data-v-07dc8110] {\\n  text-align: center;\\n  font-size: 18px;\\n  font-weight: bold;\\n}\\n.web-vitals-time[data-v-07dc8110] .desc[data-v-07dc8110] .container[data-v-07dc8110] {\\n  margin-top: 4px;\\n  font-size: 14px;\\n  padding: 10px;\\n  background-color: rgba(133, 122, 122, 0.12);\\n  border-radius: 6px;\\n}\\n\";styleInject(css_248z),script.render=render,script.__scopeId=\"data-v-07dc8110\";var WebVitals=new ce({nameZh:\"性能指标\",name:\"webVitalsTime\",icon:\"https://pt-starimg.didistatic.com/static/starimg/img/AilMLHvhF91634895636692.png\",component:script});const BasicFeatures={title:\"常用工具\",list:[Console,AppInfo,Resource,Network,Storage,DemoPlugin,DemoIndependPlugin,H5DoorPlugin,WebVitals,Element$1,OneMachineWithMultipleControls,scanCode]},DokitFeatures={title:\"平台功能\",list:[ApiMock]},UIFeatures={title:\"视觉功能\",list:[AlignRuler]},Features=[BasicFeatures,DokitFeatures,UIFeatures];window.Dokit=new pe({features:Features})})();\n"
  },
  {
    "path": "Android/dokit/src/main/assets/h5help/dokit_js_hook.html",
    "content": "<script type=\"text/javascript\">\n /**\n   * generate an unique id string (32)\n   * @private\n   * @return string\n   */\n function getUniqueID() {\n    var id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n        let r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);\n        return v.toString(16);\n    });\n    return id;\n  }\n\n  /**\n   * mock ajax request\n   */\n  function mockAjax(){\n    var _XMLHttpRequest = window.XMLHttpRequest;\n    if (!_XMLHttpRequest) { return; }\n\n    var that = this;\n    var _open = window.XMLHttpRequest.prototype.open;\n    var _send = window.XMLHttpRequest.prototype.send;\n    var _setRequestHeader = window.XMLHttpRequest.prototype.setRequestHeader;\n    var _overrideMimeType = window.XMLHttpRequest.prototype.overrideMimeType;\n    // mock open()\n    window.XMLHttpRequest.prototype.open = function() {\n        var XMLReq = this;\n        var args = [].slice.call(arguments);\n        var method = args[0];\n        var url = args[1];\n        var requestId = that.getUniqueID();\n        var hasQuery = url.split(\"?\")[1];\n        var urlWrap = null;\n        if(hasQuery){\n            urlWrap = url + \"&\" + \"dokit_flag=\" + requestId\n        }else{\n            urlWrap = url + \"?\" + \"dokit_flag=\" + requestId\n        }\n        // may be used by other functions\n        XMLReq._requestID = requestId;\n        XMLReq._method = method;\n        XMLReq._url = urlWrap;\n        args[1] = urlWrap;\n\n        //console.log(\"urlWrap===>\",urlWrap)\n        window.dokitJsi.open(requestId,urlWrap,method,window.location.origin);\n        return _open.apply(XMLReq,args);\n    }\n\n    // mock send()\n    window.XMLHttpRequest.prototype.send = function() {\n        var XMLReq = this;\n        var args = [].slice.call(arguments);\n        var body = args[0];\n        window.dokitJsi.send(XMLReq._requestID,body);\n        return _send.apply(XMLReq,args);\n    }\n\n    //mock  setRequestHeader()\n    window.XMLHttpRequest.prototype.setRequestHeader = function(){\n        var XMLReq = this;\n        var args = [].slice.call(arguments);\n        var header = args[0];\n        var value = args[1];\n        window.dokitJsi.setRequestHeader(XMLReq._requestID,header,value);\n        return _setRequestHeader.apply(XMLReq,args);\n    }\n\n     //mock  overrideMimeType()\n    window.XMLHttpRequest.prototype.overrideMimeType = function(){\n        var XMLReq = this;\n        var args = [].slice.call(arguments);\n        var mimeType = args[0];\n        window.dokitJsi.overrideMimeType(XMLReq._requestID,mimeType);\n        return _overrideMimeType.apply(XMLReq,args);\n    }\n  }\n\n\n  /**\n   * mock fetch request\n   */\n  function mockFetch(){\n    var _fetch = window.fetch;\n    if(!_fetch){ return; }\n    var that = this;\n    var _prevFetch  = function(url,init){\n        var requestId = that.getUniqueID();\n        var body = init.body;\n        var method = init.method||'GET';\n        var headers = JSON.stringify(init.headers);\n        var hasQuery = url.split(\"?\")[1];\n        var urlWrap = null;\n        if(hasQuery){\n            urlWrap = url + \"&\" + \"dokit_flag=\" + requestId\n        }else{\n            urlWrap = url + \"?\" + \"dokit_flag=\" + requestId\n        }\n        console.log(\"mockFetch\",requestId,urlWrap,method,window.location.origin,headers,body);\n        window.dokitJsi.fetch(requestId,urlWrap,method,window.location.origin,headers,body);\n        return _fetch(urlWrap,init)\n            .then(function(response) {\n                return response;\n            })\n            .catch(function(err){\n                return err;\n            });\n\n    }\n    window.fetch = _prevFetch;\n  }\n\n  /**\n   * mock Storage & Cookie\n   */\n   function mockStorage(){\n     var _Storage = window.Storage;\n     if (!_Storage) { return; }\n     var that = this;\n     var _setItem = window.Storage.prototype.setItem;\n     var _removeItem = window.Storage.prototype.removeItem;\n     var _clear = window.Storage.prototype.clear;\n    // mock setItem()\n    window.Storage.prototype.setItem = function() {\n        var storage = this;\n        var args = [].slice.call(arguments);\n        if(storage === localStorage){\n            window.dokitJsi.localStorageSetItem(args[0],args[1]);\n        }else if(storage === sessionStorage){\n            window.dokitJsi.sessionStorageSetItem(args[0],args[1]);\n        }\n\n        return _setItem.apply(storage,args);\n    }\n\n    // mock removeItem()\n    window.Storage.prototype.removeItem = function() {\n        var storage = this;\n        var args = [].slice.call(arguments);\n        if(storage === localStorage){\n            window.dokitJsi.localStorageRemoveItem(args[0]);\n        }else if(storage === sessionStorage){\n            window.dokitJsi.sessionStorageRemoveItem(args[0]);\n        }\n\n        return _removeItem.apply(storage,args);\n    }\n\n    // mock clear()\n    window.Storage.prototype.clear = function() {\n        var storage = this;\n        var args = [].slice.call(arguments);\n        if(storage === localStorage){\n            window.dokitJsi.localStorageClear();\n        }else if(storage === sessionStorage){\n            window.dokitJsi.sessionStorageClear();\n        }\n\n        return _clear.apply(storage,args);\n    }\n\n   }\n\n\n  mockAjax();\n  mockFetch();\n  mockStorage();\n\n</script>"
  },
  {
    "path": "Android/dokit/src/main/assets/h5help/dokit_js_vconsole_hook.html",
    "content": "<script src=\"https://cdn.bootcdn.net/ajax/libs/vConsole/3.3.4/vconsole.min.js\"></script>\n<script type=\"text/javascript\">\n        var vConsole = new VConsole();\n</script>\n"
  },
  {
    "path": "Android/dokit/src/main/assets/map/map.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n    <meta name=\"viewport\"\n          content=\"width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\"/>\n    <style type=\"text/css\">\n    html,body{\n        width:100%;\n        height:100%;\n    }\n    * {\n      margin: 0px;\n      padding: 0px;\n    }\n\n    body,\n    button,\n    input,\n    select,\n    textarea {\n      font: 12px/16px Verdana, Helvetica, Arial, sans-serif;\n    }\n\n    #container {\n      min-width: 600px;\n      min-height: 767px;\n    }\n    </style>\n\n    <script charset=\"utf-8\" src=\"https://map.qq.com/api/js?v=2.exp\"></script>\n    <script>\n    var init = function (lat,lng) {\n\n        if(lat===0 && lng===0){\n            lat = 39.916527;\n            lng = 116.397128;\n        }\n        console.log('lat===>' + lat + \"  lng===>\" + lng);\n        var center = new qq.maps.LatLng(lat, lng);\n        var map= new qq.maps.Map(document.getElementById(\"container\"), {\n            center: center,\n            zoom: 13\n        });\n        //赋值给window对象\n        window['map'] = map;\n\n        //移动地图中心位置\n        map.panBy(120, 150);\n        //创建marker\n        var marker = new qq.maps.Marker({\n            position: center,\n            map: map\n        });\n\n        window['marker'] = marker;\n\n        //设置Marker的动画属性为从落下\n        marker.setAnimation(qq.maps.MarkerAnimation.DOWN);\n        marker.setDraggable(true);\n        //当标注拖动结束时回调\n        qq.maps.event.addListener(marker, 'dragend', function (event) {\n            var latLng = event.latLng;\n            lat = latLng.getLat().toFixed(6);\n            lng = latLng.getLng().toFixed(6);\n            window.location = \"doraemon://invokeNative/sendLocation?lat=\" + lat + \"&lng=\" + lng;\n        });\n\n        window.location = \"doraemon://invokeNative/sendLocation?lat=\" + center.getLat().toFixed(6) + \"&lng=\" + center.getLng().toFixed(6);\n    }\n\n    var updateLocation = function(lat,lng){\n        var center = new qq.maps.LatLng(lat, lng);\n        if(window['map']){\n            var map = window['map'];\n            map.setCenter(center);\n            //移动地图中心位置\n            map.panBy(120, 150);\n       }\n\n       if(window['marker']){\n           var marker = window['marker'];\n           marker.setPosition(center);\n           //设置Marker的动画属性为从落下\n           marker.setAnimation(qq.maps.MarkerAnimation.DOWN);\n       }\n    }\n\n    </script>\n</head>\n<!--onload=\"init(0, 0)\"-->\n<body >\n<div id=\"container\"></div>\n</body>\n\n</html>"
  },
  {
    "path": "Android/dokit/src/main/cpp/CMakeLists.txt",
    "content": "# For more information about using CMake with Android Studio, read the\n# documentation: https://d.android.com/studio/projects/add-native-code.html\n\n# Sets the minimum version of CMake required to build the native library.\n\ncmake_minimum_required(VERSION 3.4.1)\n\n# Creates and names a library, sets it as either STATIC\n# or SHARED, and provides the relative paths to its source code.\n# You can define multiple libraries, and CMake builds them for you.\n# Gradle automatically packages shared libraries with your APK.\n\nadd_library( # Sets the name of the library.\n             dokit-reflection\n             SHARED\n             main.cpp art.cpp )\n\n# Searches for a specified prebuilt library and stores the path as a\n# variable. Because CMake includes system libraries in the search path by\n# default, you only need to specify the name of the public NDK library\n# you want to add. CMake verifies that the library exists before\n# completing its build.\n\nfind_library( # Sets the name of the path variable.\n              log-lib\n\n              # Specifies the name of the NDK library that\n              # you want CMake to locate.\n              log )\n\n# Specifies libraries CMake should link to your target library. You\n# can link multiple libraries, such as libraries you define in this\n# build script, prebuilt third-party libraries, or system libraries.\n\ntarget_link_libraries( # Specifies the target library.\n                       dokit-reflection\n\n                       # Links the target library to the log library\n                       # included in the NDK.\n                       ${log-lib} )\n"
  },
  {
    "path": "Android/dokit/src/main/cpp/art.cpp",
    "content": "//\n// Created by weishu on 2018/6/7.\n//\n#include \"art.h\"\n#include <android/log.h>\n#include <vector>\n#include <string>\n#include <stdlib.h>\n#include <sys/system_properties.h>\n\n#define LOGV(...)  ((void)__android_log_print(ANDROID_LOG_INFO, \"FreeReflect\", __VA_ARGS__))\n\ntemplate<typename T>\nint findOffset(void *start, int regionStart, int regionEnd, T value) {\n\n    if (NULL == start || regionEnd <= 0 || regionStart < 0) {\n        return -1;\n    }\n    char *c_start = (char *) start;\n\n    for (int i = regionStart; i < regionEnd; i += 4) {\n        T *current_value = (T *) (c_start + i);\n        if (value == *current_value) {\n            LOGV(\"found offset: %d\", i);\n            return i;\n        }\n    }\n    return -2;\n}\n\ntemplate<typename Runtime>\nint unseal0(Runtime *partialRuntime) {\n    bool is_java_debuggable = partialRuntime->is_java_debuggable_;\n    bool is_native_debuggable = partialRuntime->is_native_debuggable_;\n    bool safe_mode = partialRuntime->safe_mode_;\n\n    // TODO validate\n\n    LOGV(\"is_java_debuggable: %d, is_native_debuggable: %d, safe_mode: %d\", is_java_debuggable,\n         is_native_debuggable, safe_mode);\n    LOGV(\"hidden api policy before : %d\", partialRuntime->hidden_api_policy_);\n    LOGV(\"fingerprint: %s\", partialRuntime->fingerprint_.c_str());\n\n    partialRuntime->hidden_api_policy_ = EnforcementPolicy::kNoChecks;\n    LOGV(\"hidden api policy after: %d\", partialRuntime->hidden_api_policy_);\n    return 0;\n}\n\nint unseal(JNIEnv *env, jint targetSdkVersion) {\n\n    char api_level_str[5];\n    char preview_api_str[5];\n    __system_property_get(\"ro.build.version.sdk\", api_level_str);\n    __system_property_get(\"ro.build.version.preview_sdk\", preview_api_str);\n\n    int api_level = atoi(api_level_str);\n    bool is_preview = atoi(preview_api_str) > 0;\n\n    bool isAndroidR = api_level >= 30 || (api_level == 29 && is_preview);\n\n    JavaVM *javaVM;\n    env->GetJavaVM(&javaVM);\n\n    JavaVMExt *javaVMExt = (JavaVMExt *) javaVM;\n    void *runtime = javaVMExt->runtime;\n\n    LOGV(\"runtime ptr: %p, vmExtPtr: %p\", runtime, javaVMExt);\n\n    const int MAX = 2000;\n    int offsetOfVmExt = findOffset(runtime, 0, MAX, (size_t) javaVMExt);\n    LOGV(\"offsetOfVmExt: %d\", offsetOfVmExt);\n\n    if (offsetOfVmExt < 0) {\n        return -1;\n    }\n\n    int startOffset = offsetOfVmExt;\n    if (isAndroidR) {\n        startOffset += 200;\n    }\n\n    int targetSdkVersionOffset = findOffset(runtime, startOffset, MAX, targetSdkVersion);\n    LOGV(\"target: %d\", targetSdkVersionOffset);\n\n    if (targetSdkVersionOffset < 0) {\n        return -2;\n    }\n\n    /*\n    const int retry = 3;\n    bool targetSdkVersionCorrect = false;\n    int vector_string_size = sizeof(std::vector<std::string>);\n    LOGV(\"vector string size: %d\", vector_string_size);\n\n    for (int i = 0; i < retry; ++i) {\n        int cpu_abilist_offset = targetSdkVersionOffset - vector_string_size;\n        void *cpu_abilist_address = ((char *) runtime + cpu_abilist_offset);\n\n        auto cpu_abi_list_ = reinterpret_cast<std::vector<std::string>*>(cpu_abilist_address);\n\n        bool correct = false;\n        for (auto itr = cpu_abi_list_->begin(); itr != cpu_abi_list_->end(); itr++) {\n            std::string value = *itr;\n            LOGV(\"value : %s\", value.c_str());\n            if (strcmp(value.c_str(), \"x86\") == 0) {\n                correct = true;\n                break;\n            }\n        }\n        if (correct) {\n            targetSdkVersionCorrect = true;\n            break;\n        }\n    }\n\n    if (!targetSdkVersionCorrect) {\n        return -3;\n    }\n    */\n\n    if (isAndroidR) {\n        auto *partialRuntime = reinterpret_cast<PartialRuntimeR *>((char *) runtime +\n                                                                              targetSdkVersionOffset);\n        unseal0<PartialRuntimeR>(partialRuntime);\n    } else {\n        auto *partialRuntime = (PartialRuntime *) ((char *) runtime +\n                                                             targetSdkVersionOffset);\n        unseal0<PartialRuntime>(partialRuntime);\n    }\n\n    return 0;\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/cpp/art.h",
    "content": "//\n// Created by weishu on 2018/6/7.\n//\n\n#ifndef FREEREFLECTION_ART_H\n#define FREEREFLECTION_ART_H\n\n#include <jni.h>\n#include <string>\n\nstruct JavaVMExt {\n    void *functions;\n    void *runtime;\n};\n\n// Refer: https://android.googlesource.com/platform/art/+/master/runtime/experimental_flags.h\nstruct ExperimentalFlags {\n    uint32_t value;\n};\n\n// Refer: https://android.googlesource.com/platform/art/+/master/runtime/hidden_api.h\n// Hidden API enforcement policy\n// This must be kept in sync with ApplicationInfo.ApiEnforcementPolicy in\n// frameworks/base/core/java/android/content/pm/ApplicationInfo.java\nenum class EnforcementPolicy {\n    kNoChecks = 0,\n    kJustWarn = 1,  // keep checks enabled, but allow everything (enables logging)\n    kDarkGreyAndBlackList = 2,  // ban dark grey & blacklist\n    kBlacklistOnly = 3,  // ban blacklist violations only\n    kMax = 3,\n};\n\nstruct PartialRuntime {\n    // Specifies target SDK version to allow workarounds for certain API levels.\n    int32_t target_sdk_version_;\n\n    // Implicit checks flags.\n    bool implicit_null_checks_;       // NullPointer checks are implicit.\n    bool implicit_so_checks_;         // StackOverflow checks are implicit.\n    bool implicit_suspend_checks_;    // Thread suspension checks are implicit.\n\n    // Whether or not the sig chain (and implicitly the fault handler) should be\n    // disabled. Tools like dex2oat or patchoat don't need them. This enables\n    // building a statically link version of dex2oat.\n    bool no_sig_chain_;\n\n    // Force the use of native bridge even if the app ISA matches the runtime ISA.\n    bool force_native_bridge_;\n\n    // Whether or not a native bridge has been loaded.\n    //\n    // The native bridge allows running native code compiled for a foreign ISA. The way it works is,\n    // if standard dlopen fails to load native library associated with native activity, it calls to\n    // the native bridge to load it and then gets the trampoline for the entry to native activity.\n    //\n    // The option 'native_bridge_library_filename' specifies the name of the native bridge.\n    // When non-empty the native bridge will be loaded from the given file. An empty value means\n    // that there's no native bridge.\n    bool is_native_bridge_loaded_;\n\n    // Whether we are running under native debugger.\n    bool is_native_debuggable_;\n\n    // whether or not any async exceptions have ever been thrown. This is used to speed up the\n    // MterpShouldSwitchInterpreters function.\n    bool async_exceptions_thrown_;\n\n    // Whether Java code needs to be debuggable.\n    bool is_java_debuggable_;\n\n    // The maximum number of failed boots we allow before pruning the dalvik cache\n    // and trying again. This option is only inspected when we're running as a\n    // zygote.\n    uint32_t zygote_max_failed_boots_;\n\n    // Enable experimental opcodes that aren't fully specified yet. The intent is to\n    // eventually publish them as public-usable opcodes, but they aren't ready yet.\n    //\n    // Experimental opcodes should not be used by other production code.\n    ExperimentalFlags experimental_flags_;\n\n    // Contains the build fingerprint, if given as a parameter.\n    std::string fingerprint_;\n\n    // Oat file manager, keeps track of what oat files are open.\n    // OatFileManager* oat_file_manager_;\n    void *oat_file_manager_;\n\n    // Whether or not we are on a low RAM device.\n    bool is_low_memory_mode_;\n\n    // Whether or not we use MADV_RANDOM on files that are thought to have random access patterns.\n    // This is beneficial for low RAM devices since it reduces page cache thrashing.\n    bool madvise_random_access_;\n\n    // Whether the application should run in safe mode, that is, interpreter only.\n    bool safe_mode_;\n\n    // Whether access checks on hidden API should be performed.\n    EnforcementPolicy hidden_api_policy_;\n};\n\n// Android R: https://android.googlesource.com/platform/art/+/refs/tags/android-11.0.0_r3/runtime/runtime.h#1182\nstruct PartialRuntimeR {\n    // Specifies target SDK version to allow workarounds for certain API levels.\n    uint32_t target_sdk_version_;\n\n    // A set of disabled compat changes for the running app, all other changes are enabled.\n    // std::set<uint64_t> disabled_compat_changes_;\n    void *disabled_compat_changes_[3];\n\n    // Implicit checks flags.\n    bool implicit_null_checks_;       // NullPointer checks are implicit.\n    bool implicit_so_checks_;         // StackOverflow checks are implicit.\n    bool implicit_suspend_checks_;    // Thread suspension checks are implicit.\n\n    // Whether or not the sig chain (and implicitly the fault handler) should be\n    // disabled. Tools like dex2oat don't need them. This enables\n    // building a statically link version of dex2oat.\n    bool no_sig_chain_;\n\n    // Force the use of native bridge even if the app ISA matches the runtime ISA.\n    bool force_native_bridge_;\n\n    // Whether or not a native bridge has been loaded.\n    //\n    // The native bridge allows running native code compiled for a foreign ISA. The way it works is,\n    // if standard dlopen fails to load native library associated with native activity, it calls to\n    // the native bridge to load it and then gets the trampoline for the entry to native activity.\n    //\n    // The option 'native_bridge_library_filename' specifies the name of the native bridge.\n    // When non-empty the native bridge will be loaded from the given file. An empty value means\n    // that there's no native bridge.\n    bool is_native_bridge_loaded_;\n\n    // Whether we are running under native debugger.\n    bool is_native_debuggable_;\n\n    // whether or not any async exceptions have ever been thrown. This is used to speed up the\n    // MterpShouldSwitchInterpreters function.\n    bool async_exceptions_thrown_;\n\n    // Whether anything is going to be using the shadow-frame APIs to force a function to return\n    // early. Doing this requires that (1) we be debuggable and (2) that mterp is exited.\n    bool non_standard_exits_enabled_;\n\n    // Whether Java code needs to be debuggable.\n    bool is_java_debuggable_;\n\n    bool is_profileable_from_shell_ = false;\n\n    // The maximum number of failed boots we allow before pruning the dalvik cache\n    // and trying again. This option is only inspected when we're running as a\n    // zygote.\n    uint32_t zygote_max_failed_boots_;\n\n    // Enable experimental opcodes that aren't fully specified yet. The intent is to\n    // eventually publish them as public-usable opcodes, but they aren't ready yet.\n    //\n    // Experimental opcodes should not be used by other production code.\n    ExperimentalFlags experimental_flags_;\n\n    // Contains the build fingerprint, if given as a parameter.\n    std::string fingerprint_;\n\n    // Oat file manager, keeps track of what oat files are open.\n    // OatFileManager* oat_file_manager_;\n    void *oat_file_manager_;\n\n    // Whether or not we are on a low RAM device.\n    bool is_low_memory_mode_;\n\n    // Whether or not we use MADV_RANDOM on files that are thought to have random access patterns.\n    // This is beneficial for low RAM devices since it reduces page cache thrashing.\n    bool madvise_random_access_;\n\n    // Whether the application should run in safe mode, that is, interpreter only.\n    bool safe_mode_;\n\n    // Whether access checks on hidden API should be performed.\n    EnforcementPolicy hidden_api_policy_;\n};\n\n\nint unseal(JNIEnv *env, jint targetSdkVersion);\n\n#endif //FREEREFLECTION_ART_H\n"
  },
  {
    "path": "Android/dokit/src/main/cpp/main.cpp",
    "content": "#include <jni.h>\n#include \"art.h\"\n\nextern \"C\"\nJNIEXPORT jint JNICALL\nJava_me_weishu_reflection_Reflection_unsealNative(JNIEnv *env, jclass type, jint targetSdkVersion) {\n    return unseal(env, targetSdkVersion);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/DebugFileProvider.java",
    "content": "package com.didichuxing.doraemonkit;\n\nimport androidx.core.content.FileProvider;\n\n/**\n * Created by wanglikun on 2018/11/14.\n */\n\npublic class DebugFileProvider extends FileProvider {\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/DoKit.kt",
    "content": "package com.didichuxing.doraemonkit\n\nimport android.app.Activity\nimport android.app.Application\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.View\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.core.*\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitExtInterceptor\nimport com.didichuxing.doraemonkit.kit.webdoor.WebDoorManager\nimport kotlin.reflect.KClass\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：4/7/21-16:00\n * 描    述：DoKit 入口类\n * 修订历史：\n * ================================================\n */\nobject DoKit {\n\n    const val TAG = \"DoKit\"\n\n    @Deprecated(\n        message = \"Instead with DokitEnv.requireApp()\",\n        replaceWith = ReplaceWith(\"DokitEnv.requireApp()\", \"com.didichuxing.doraemonkit.DokitEnv\")\n    )\n    val APPLICATION: Application\n        get() = DoKitEnv.requireApp()\n\n    /**\n     * 主icon是否处于显示状态\n     */\n    @JvmStatic\n    val isMainIconShow: Boolean\n        get() = DoKitReal.isShow\n\n    /**\n     * 是否已完成初始化\n     */\n    @JvmStatic\n    val isInit: Boolean\n        get() = DoKitReal.isInit\n\n    /**\n     * 显示主icon\n     */\n    @JvmStatic\n    fun show() {\n        DoKitReal.show()\n    }\n\n    /**\n     * 直接显示工具面板页面\n     */\n    @JvmStatic\n    fun showToolPanel() {\n        DoKitReal.showToolPanel()\n    }\n\n    /**\n     * 直接隐藏工具面板\n     */\n    @JvmStatic\n    fun hideToolPanel() {\n        DoKitReal.hideToolPanel()\n    }\n\n    /**\n     * 隐藏主icon\n     */\n    @JvmStatic\n    fun hide() {\n        DoKitReal.hide()\n    }\n\n    /**\n     * 获取MC当前链接地址\n     */\n    @JvmStatic\n    fun getMcConnectUrl(): String {\n        return DoKitReal.getMcConnectUrl()\n    }\n\n    /**\n     * 启动悬浮窗\n     * @JvmStatic:允许使用java的静态方法的方式调用\n     * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n     */\n    @JvmStatic\n    @JvmOverloads\n    fun launchFloating(targetClass: Class<out AbsDoKitView>, mode: DoKitViewLaunchMode = DoKitViewLaunchMode.SINGLE_INSTANCE, bundle: Bundle? = null) {\n        DoKitReal.launchFloating(targetClass, mode, bundle)\n    }\n\n    /**\n     * 启动悬浮窗\n     */\n    @Deprecated(\n        \"Use launchFloating(DoKitViewLaunchMode, Bundle) directly\",\n        ReplaceWith(\"Dokit.launchFloating(mode, bundle)\")\n    )\n    fun launchFloating(targetClass: KClass<out AbsDoKitView>, mode: DoKitViewLaunchMode = DoKitViewLaunchMode.SINGLE_INSTANCE, bundle: Bundle? = null) {\n        launchFloating(targetClass.java, mode, bundle)\n    }\n\n    /**\n     * 启动悬浮窗\n     */\n    inline fun <reified T : AbsDoKitView> launchFloating(mode: DoKitViewLaunchMode = DoKitViewLaunchMode.SINGLE_INSTANCE, bundle: Bundle? = null) {\n        DoKitReal.launchFloating(T::class.java, mode, bundle)\n    }\n\n    /**\n     * 移除悬浮窗\n     * @JvmStatic:允许使用java的静态方法的方式调用\n     * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n     */\n    @JvmStatic\n    fun removeFloating(targetClass: Class<out AbsDoKitView>) {\n        DoKitReal.removeFloating(targetClass)\n    }\n\n    /**\n     * 移除悬浮窗\n     * @JvmStatic:允许使用java的静态方法的方式调用\n     * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n     */\n    @Deprecated(\"Use removeFloating(Class) directly\", ReplaceWith(\"Dokit.removeFloating(class)\"))\n    fun removeFloating(targetClass: KClass<out AbsDoKitView>) {\n        removeFloating(targetClass.java)\n    }\n\n    /**\n     * 移除悬浮窗\n     * @JvmStatic:允许使用java的静态方法的方式调用\n     * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n     */\n    @JvmStatic\n    fun removeFloating(dokitView: AbsDoKitView) {\n        DoKitReal.removeFloating(dokitView)\n    }\n\n    /**\n     * 启动全屏页面\n     * @JvmStatic:允许使用java的静态方法的方式调用\n     * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n     */\n    @JvmStatic\n    @JvmOverloads\n    fun launchFullScreen(targetClass: Class<out BaseFragment>, context: Context? = null, bundle: Bundle? = null, isSystemFragment: Boolean = false) {\n        DoKitReal.launchFullScreen(targetClass, context, bundle, isSystemFragment)\n    }\n\n    /**\n     * 启动全屏页面\n     * @JvmStatic:允许使用java的静态方法的方式调用\n     * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n     */\n    @Deprecated(\n        \"Use launchFullScreen(Class, Context, Bundle, Boolean) directly\",\n        ReplaceWith(\"Dokit.launchFullScreen(class, context, bundle, isSystemFragment)\")\n    )\n    fun launchFullScreen(targetClass: KClass<out BaseFragment>, context: Context? = null, bundle: Bundle? = null, isSystemFragment: Boolean = false) {\n        launchFullScreen(targetClass.java, context, bundle, isSystemFragment)\n    }\n\n    @JvmStatic\n    fun <T : AbsDoKitView> getDoKitView(activity: Activity?, clazz: Class<out T>): T? {\n        return DoKitReal.getDoKitView<T>(activity, clazz)\n    }\n\n    @Deprecated(\"Use getDoKitView(activity) directly\", ReplaceWith(\"DoKit.getDoKitView(activity)\"))\n    fun <T : AbsDoKitView> getDoKitView(activity: Activity?, clazz: KClass<out T>): T? {\n        return getDoKitView(activity, clazz.java)\n    }\n\n    inline fun <reified T : AbsDoKitView> getDoKitView(activity: Activity): T? = DoKitReal.getDoKitView(activity, T::class.java)\n\n    /**\n     * 发送自定义一机多控事件\n     */\n    @JvmStatic\n    fun sendCustomEvent(eventType: String, view: View? = null, param: Map<String, String>? = null) {\n        DoKitReal.sendCustomEvent(eventType, view, param)\n    }\n\n    /**\n     * 获取一机多控类型\n     */\n    @JvmStatic\n    fun mcMode(): String {\n       return DoKitReal.getMode()\n    }\n\n    class Builder(private val app: Application) {\n        private var productId: String = \"\"\n        private var mapKits: LinkedHashMap<String, List<AbstractKit>> = linkedMapOf()\n        private var listKits: List<AbstractKit> = arrayListOf()\n\n        init {\n            DoKitEnv.app = app\n        }\n\n        fun productId(productId: String): Builder = apply { this.productId = productId }\n\n        /**\n         * mapKits & listKits 二选一\n         */\n        fun customKits(mapKits: LinkedHashMap<String, List<AbstractKit>>): Builder = apply { this.mapKits = mapKits }\n\n        /**\n         * mapKits & listKits 二选一\n         */\n        fun customKits(listKits: List<AbstractKit>): Builder = apply { this.listKits = listKits }\n\n        /**\n         * H5任意门全局回调\n         */\n        fun webDoorCallback(callback: WebDoorManager.WebDoorCallback): Builder = this.apply {\n            DoKitReal.setWebDoorCallback(callback)\n        }\n\n        /**\n         * 禁用app信息上传开关，该上传信息只为做DoKit接入量的统计，如果用户需要保护app隐私，可调用该方法进行禁用\n         */\n        fun disableUpload(): Builder = this.apply {\n            DoKitReal.disableUpload()\n        }\n\n        fun debug(debug: Boolean): Builder = this.apply {\n            DoKitReal.setDebug(debug)\n        }\n\n        /**\n         * 是否显示主入口icon\n         */\n        fun alwaysShowMainIcon(alwaysShow: Boolean): Builder = this.apply {\n            DoKitReal.setAlwaysShowMainIcon(alwaysShow)\n        }\n\n        /**\n         * 设置加密数据库密码\n         */\n        fun databasePass(map: Map<String, String>): Builder = this.apply {\n            DoKitReal.setDatabasePass(map)\n        }\n\n        /**\n         * 设置文件管理助手http端口号\n         */\n        fun fileManagerHttpPort(port: Int): Builder = this.apply {\n            DoKitReal.setFileManagerHttpPort(port)\n        }\n\n        /**\n         * 一机多控端口号\n         */\n        fun mcWSPort(port: Int): Builder = this.apply {\n            DoKitReal.setMCWSPort(port)\n        }\n\n        /**\n         * 一机多控自定义拦截器\n         */\n        fun mcClientProcess(interceptor: McClientProcessor): Builder = this.apply {\n            DoKitReal.setMCIntercept(interceptor)\n        }\n\n        /**\n         *设置dokit的性能监控全局回调\n         */\n        fun callBack(callback: DoKitCallBack): Builder = this.apply {\n            DoKitReal.setCallBack(callback)\n        }\n\n        /**\n         * 设置扩展网络拦截器的代理对象\n         */\n        fun netExtInterceptor(extInterceptorProxy: DokitExtInterceptor.DokitExtInterceptorProxy): Builder = this.apply {\n            DoKitReal.setNetExtInterceptor(extInterceptorProxy)\n        }\n\n        fun build() {\n            DoKitReal.install(app, mapKits, listKits, productId)\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/DoKitActivityLifecycleCallbacks.kt",
    "content": "package com.didichuxing.doraemonkit\n\nimport android.app.Activity\nimport android.app.Application\nimport android.content.Context\nimport android.os.Bundle\nimport android.widget.Toast\nimport androidx.fragment.app.FragmentActivity\nimport androidx.fragment.app.FragmentManager\nimport com.didichuxing.doraemonkit.datapick.DataPickManager\nimport com.didichuxing.doraemonkit.kit.core.ActivityLifecycleStatusInfo\nimport com.didichuxing.doraemonkit.kit.core.DoKitLifeCycleStatus\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewManager\nimport com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil\nimport com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo.DataBean.UiLevelBean\nimport com.didichuxing.doraemonkit.kit.uiperformance.UIPerformanceUtil\nimport com.didichuxing.doraemonkit.model.ViewInfo\nimport com.didichuxing.doraemonkit.util.DoKitPermissionUtil\nimport com.didichuxing.doraemonkit.util.LifecycleListenerUtil\nimport com.didichuxing.doraemonkit.util.UIUtils\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-31-10:04\n * 描    述：全局的activity生命周期回调\n * 修订历史：\n * ================================================\n */\nclass DoKitActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {\n    private var startedActivityCounts = 0\n    private var sHasRequestPermission = false\n    //private Map<String, DoKitOrientationEventListener> mOrientationEventListeners = new HashMap<>();\n    /**\n     * fragment 生命周期回调\n     */\n    private val sFragmentLifecycleCallbacks: FragmentManager.FragmentLifecycleCallbacks\n\n    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {\n        try {\n            recordActivityLifeCycleStatus(activity, DoKitLifeCycleStatus.CREATED)\n            if (ignoreCurrentActivityDokitView(activity)) {\n                return\n            }\n            if (activity is FragmentActivity) {\n                //注册fragment生命周期回调\n                activity.supportFragmentManager.registerFragmentLifecycleCallbacks(\n                    sFragmentLifecycleCallbacks,\n                    true\n                )\n            }\n            //暂时无法很好的解决屏幕旋转的问题\n            //DoKitOrientationEventListener orientationEventListener = new DoKitOrientationEventListener(activity);\n            //orientationEventListener.enable();\n            //mOrientationEventListeners.put(activity.getClass().getSimpleName(), orientationEventListener);\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    override fun onActivityStarted(activity: Activity) {\n        try {\n            if (ignoreCurrentActivityDokitView(activity)) {\n                return\n            }\n            if (startedActivityCounts == 0) {\n                DoKitViewManager.INSTANCE.notifyForeground()\n            }\n            startedActivityCounts++\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    override fun onActivityResumed(activity: Activity) {\n        try {\n            recordActivityLifeCycleStatus(activity, DoKitLifeCycleStatus.RESUME)\n            //记录页面层级\n            if (activity.javaClass.canonicalName != \"com.didichuxing.doraemonkit.kit.base.UniversalActivity\") {\n                recordActivityUiLevel(activity)\n            }\n            //如果是leakCanary页面不进行添加\n            if (ignoreCurrentActivityDokitView(activity)) {\n                return\n            }\n\n\n            //设置app的直接子view的Id\n            UIUtils.getDokitAppContentView(activity)\n            dispatchOnActivityResumed(activity)\n            for (listener in LifecycleListenerUtil.LIFECYCLE_LISTENERS) {\n                listener.onActivityResumed(activity)\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    override fun onActivityPaused(activity: Activity) {\n        try {\n            if (ignoreCurrentActivityDokitView(activity)) {\n                return\n            }\n            for (listener in LifecycleListenerUtil.LIFECYCLE_LISTENERS) {\n                listener.onActivityPaused(activity)\n            }\n            DoKitViewManager.INSTANCE.onActivityPaused(activity)\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    override fun onActivityStopped(activity: Activity) {\n        try {\n            recordActivityLifeCycleStatus(activity, DoKitLifeCycleStatus.STOPPED)\n            if (ignoreCurrentActivityDokitView(activity)) {\n                return\n            }\n            startedActivityCounts--\n            //通知app退出到后台\n            if (startedActivityCounts == 0) {\n                DoKitViewManager.INSTANCE.notifyBackground()\n                //app 切换到后台 上传埋点数据\n                DataPickManager.getInstance().postData()\n            }\n            DoKitViewManager.INSTANCE.onActivityStopped(activity)\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {\n        try {\n            if (ignoreCurrentActivityDokitView(activity)) {\n                return\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    override fun onActivityDestroyed(activity: Activity) {\n        try {\n            recordActivityLifeCycleStatus(activity, DoKitLifeCycleStatus.DESTROYED)\n            if (ignoreCurrentActivityDokitView(activity)) {\n                return\n            }\n            //注销fragment的生命周期回调\n            if (activity is FragmentActivity) {\n                activity.supportFragmentManager.unregisterFragmentLifecycleCallbacks(\n                    sFragmentLifecycleCallbacks\n                )\n            }\n            DoKitViewManager.INSTANCE.onActivityDestroyed(activity)\n\n            //暂时无法很好的解决屏幕旋转的问题\n            //DoKitOrientationEventListener orientationEventListener = mOrientationEventListeners.get(activity.getClass().getSimpleName());\n\n            //if (orientationEventListener != null) {\n            //orientationEventListener.disable();\n            //mOrientationEventListeners.remove(activity.getClass().getSimpleName());\n            //}\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    /**\n     * 显示所有应该显示的dokitView\n     *\n     * @param activity\n     */\n    private fun dispatchOnActivityResumed(activity: Activity) {\n        activity.window.decorView.also {\n            it.post { DoKitEnv.windowSize.set(it.width, it.height) }\n        }\n        if (DoKitManager.IS_NORMAL_FLOAT_MODE) {\n            //显示内置dokitView icon\n            DoKitViewManager.INSTANCE.dispatchOnActivityResumed(activity)\n            return\n        }\n        // FIXME: consider handle permission down to activity-layer, just dispatch resumed-event here\n        //悬浮窗权限 vivo 华为可以不需要动态权限 小米需要\n        if (DoKitPermissionUtil.canDrawOverlays(activity)) {\n            DoKitViewManager.INSTANCE.dispatchOnActivityResumed(activity)\n        } else {\n            //请求悬浮窗权限\n            requestPermission(activity)\n        }\n    }\n\n    /**\n     * 请求悬浮窗权限\n     *\n     * @param context\n     */\n    private fun requestPermission(context: Context) {\n        if (!DoKitPermissionUtil.canDrawOverlays(context) && !sHasRequestPermission) {\n            Toast.makeText(\n                context,\n                context.getText(R.string.dk_float_permission_toast),\n                Toast.LENGTH_SHORT\n            ).show()\n            //请求悬浮窗权限\n            DoKitPermissionUtil.requestDrawOverlays(context)\n            sHasRequestPermission = true\n        }\n    }\n\n    /**\n     * 记录当前activity的UILevel\n     *\n     * @param activity\n     */\n    private fun recordActivityUiLevel(activity: Activity) {\n        try {\n            if (!DoKitManager.APP_HEALTH_RUNNING) {\n                return\n            }\n            val viewInfos = UIPerformanceUtil.getViewInfos(activity)\n            var maxLevel = 0\n            var maxTime = 0f\n            var totalTime = 0f\n            var maxLevelViewInfo: ViewInfo? = null\n            var maxTimeViewInfo: ViewInfo? = null\n            for (viewInfo in viewInfos) {\n                if (viewInfo.layerNum > maxLevel) {\n                    maxLevel = viewInfo.layerNum\n                    maxLevelViewInfo = viewInfo\n                }\n                if (viewInfo.drawTime > maxTime) {\n                    maxTime = viewInfo.drawTime\n                    maxTimeViewInfo = viewInfo\n                }\n                totalTime += viewInfo.drawTime\n            }\n            val detail = \"\"\"\n                最大层级:$maxLevel\n                控件id:${if (maxLevelViewInfo == null) \"no id\" else maxLevelViewInfo.id}\n                总绘制耗时:${totalTime}ms\n                绘制耗时最长控件:${maxTime}ms\n                绘制耗时最长控件id:${if (maxTimeViewInfo == null) \"no id\" else maxTimeViewInfo.id}\n                \n                \"\"\".trimIndent()\n            val uiLevelBean = UiLevelBean()\n            uiLevelBean.page = activity.javaClass.canonicalName\n            uiLevelBean.level = \"\" + maxLevel\n            uiLevelBean.detail = detail\n            AppHealthInfoUtil.getInstance().addUiLevelInfo(uiLevelBean)\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    /**\n     * 记录当前Activity的生命周期状态\n     */\n    private fun recordActivityLifeCycleStatus(\n        activity: Activity,\n        lifeCycleStatus: DoKitLifeCycleStatus\n    ) {\n        var activityLifeCycleStatusInfo: ActivityLifecycleStatusInfo? =\n            DoKitManager.ACTIVITY_LIFECYCLE_INFOS[activity.javaClass.canonicalName]\n\n        if (activityLifeCycleStatusInfo == null) {\n            activityLifeCycleStatusInfo = ActivityLifecycleStatusInfo(\n                isInvokeStopMethod = false,\n                lifeCycleStatus = DoKitLifeCycleStatus.CREATED,\n                activityName = activity.javaClass.canonicalName\n            )\n\n            DoKitManager.ACTIVITY_LIFECYCLE_INFOS[activity.javaClass.canonicalName!!] =\n                activityLifeCycleStatusInfo\n        }\n\n        when (lifeCycleStatus) {\n            DoKitLifeCycleStatus.CREATED -> {\n                activityLifeCycleStatusInfo.lifeCycleStatus =\n                    DoKitLifeCycleStatus.CREATED\n            }\n            DoKitLifeCycleStatus.RESUME -> {\n                activityLifeCycleStatusInfo.lifeCycleStatus =\n                    DoKitLifeCycleStatus.RESUME\n            }\n            DoKitLifeCycleStatus.STOPPED -> {\n                activityLifeCycleStatusInfo.lifeCycleStatus =\n                    DoKitLifeCycleStatus.STOPPED\n                activityLifeCycleStatusInfo.isInvokeStopMethod = true\n            }\n            DoKitLifeCycleStatus.DESTROYED -> {\n                DoKitManager.ACTIVITY_LIFECYCLE_INFOS.remove(activity.javaClass.canonicalName)\n            }\n\n        }\n    }\n\n    companion object {\n        private const val TAG = \"ActivityLifecycleCallback\"\n\n        /**\n         * 是否忽略在当前的activity上显示浮标\n         *\n         * @param activity\n         * @return\n         */\n        private fun ignoreCurrentActivityDokitView(activity: Activity): Boolean {\n            val ignoreActivityClassNames = arrayOf(\"DisplayLeakActivity\")\n            for (activityClassName in ignoreActivityClassNames) {\n                if (activity.javaClass.simpleName == activityClassName) {\n                    return true\n                }\n            }\n            return false\n        }\n    }\n\n    init {\n        sFragmentLifecycleCallbacks = DoKitFragmentLifecycleCallbacks()\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/DoKitCallBack.kt",
    "content": "package com.didichuxing.doraemonkit\n\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/5/19-11:00\n * 描    述：\n * 修订历史：\n * ================================================\n */\ninterface DoKitCallBack {\n\n    fun onCpuCallBack(value: Float, filePath: String) {\n\n    }\n\n    fun onFpsCallBack(value: Float, filePath: String) {\n\n    }\n\n    fun onMemoryCallBack(value: Float, filePath: String) {\n\n    }\n\n    fun onNetworkCallBack(record: NetworkRecord) {\n\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/DoKitEnv.kt",
    "content": "package com.didichuxing.doraemonkit\n\nimport android.app.Application\nimport android.graphics.Point\n\n/**\n * Created by alvince on 2021/9/29\n *\n * @author alvince.zy@gmail.com\n */\nobject DoKitEnv {\n\n    @Volatile\n    var app: Application? = null\n\n    val windowSize: Point= Point()\n\n    @JvmStatic\n    fun requireApp(): Application {\n        return app ?: throw IllegalStateException(\"Dokit app no set\")\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/DoKitFragmentLifecycleCallbacks.kt",
    "content": "package com.didichuxing.doraemonkit\n\nimport android.content.Context\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentManager\nimport com.didichuxing.doraemonkit.util.LifecycleListenerUtil\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-31-10:56\n * 描    述：全局的fragment 生命周期回调\n * 修订历史：\n * ================================================\n */\nclass DoKitFragmentLifecycleCallbacks : FragmentManager.FragmentLifecycleCallbacks() {\n    override fun onFragmentAttached(fm: FragmentManager, fragment: Fragment, context: Context) {\n        super.onFragmentAttached(fm, fragment, context)\n        for (listener in LifecycleListenerUtil.LIFECYCLE_LISTENERS) {\n            listener.onFragmentAttached(fragment)\n        }\n    }\n\n    override fun onFragmentDetached(fm: FragmentManager, fragment: Fragment) {\n        super.onFragmentDetached(fm, fragment)\n        for (listener in LifecycleListenerUtil.LIFECYCLE_LISTENERS) {\n            listener.onFragmentDetached(fragment)\n        }\n    }\n\n    companion object {\n        private const val TAG = \"DokitFragmentLifecycleCallbacks\"\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/DoKitOrientationEventListener.kt",
    "content": "package com.didichuxing.doraemonkit\n\nimport android.app.Activity\nimport android.content.res.Configuration\nimport android.view.OrientationEventListener\nimport com.didichuxing.doraemonkit.kit.main.MainIconDoKitView\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/11/4-14:10\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitOrientationEventListener(context: Activity) : OrientationEventListener(context) {\n    val mActivity: Activity = context\n    val TAG = \"DoKitOrientationEventListener\"\n\n    /**\n     * 上一次的屏幕方向位置\n     */\n    var lastOrientation = -1\n\n    override fun onOrientationChanged(orientation: Int) {\n        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {\n            return;  //手机平放时，检测不到有效的角度\n        }\n        var innerOrientation = 0\n        //只检测是否有四个角度的改变\n        if (orientation > 350 || orientation < 10) { //0度\n            innerOrientation = 0\n        } else if (orientation in 81..99) { //90度\n            innerOrientation = 90\n        } else if (orientation in 171..189) { //180度\n            innerOrientation = 180\n        } else if (orientation in 261..279) { //270度\n            innerOrientation = 270\n        } else {\n            return\n        }\n\n        if (lastOrientation != innerOrientation) {\n            //针对dokitView的根布局进行旋转操作\n            //LogHelper.i(TAG, \"innerOrientation===>$innerOrientation\")\n            val dokitView = DoKit.getDoKitView<MainIconDoKitView>(mActivity, MainIconDoKitView::class)\n\n\n            var currentOrientation = Configuration.ORIENTATION_PORTRAIT\n            //竖向\n            if (innerOrientation == 0 || innerOrientation == 180) {\n                currentOrientation = Configuration.ORIENTATION_PORTRAIT\n            }\n            //横向\n            else if (innerOrientation == 90 || innerOrientation == 270) {\n                currentOrientation = Configuration.ORIENTATION_LANDSCAPE\n            }\n//            dokitView?.let {\n//                it.rootView.postDelayed({\n//                    it.portraitOrLandscape(currentOrientation)\n//                }, 1000)\n//            }\n\n            lastOrientation = innerOrientation\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/DoKitReal.kt",
    "content": "package com.didichuxing.doraemonkit\n\nimport android.app.Activity\nimport android.app.Application\nimport android.content.Context\nimport android.os.Build\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.util.Log\nimport android.view.View\nimport com.didichuxing.doraemonkit.config.GlobalConfig\nimport com.didichuxing.doraemonkit.config.PerformanceSpInfoConfig\nimport com.didichuxing.doraemonkit.constant.DoKitModule\nimport com.didichuxing.doraemonkit.constant.SharedPrefsKey\nimport com.didichuxing.doraemonkit.datapick.DataPickManager\nimport com.didichuxing.doraemonkit.extension.doKitGlobalExceptionHandler\nimport com.didichuxing.doraemonkit.extension.doKitGlobalScope\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.core.*\nimport com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil\nimport com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo.DataBean.BigFileBean\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitExtInterceptor\nimport com.didichuxing.doraemonkit.kit.timecounter.instrumentation.HandlerHooker\nimport com.didichuxing.doraemonkit.kit.toolpanel.KitWrapItem\nimport com.didichuxing.doraemonkit.kit.toolpanel.ToolPanelUtil\nimport com.didichuxing.doraemonkit.kit.webdoor.WebDoorManager\nimport com.didichuxing.doraemonkit.kit.webdoor.WebDoorManager.WebDoorCallback\nimport com.didichuxing.doraemonkit.util.*\nimport kotlinx.coroutines.*\nimport java.io.File\nimport java.util.*\n\n/**\n * Created by jintai on 2019/12/18.\n * DoKit 真正执行的类  不建议外部app调用\n */\nobject DoKitReal {\n\n\n    private lateinit var APPLICATION: Application\n    var isInit = false\n\n    fun setDebug(debug: Boolean) {\n        LogHelper.setDebug(debug)\n    }\n\n    /**\n     * @param app\n     * @param mapKits  自定义kits  根据用户传进来的分组 建议优选选择mapKits 两者都传的话会选择mapKits\n     * @param listKits  自定义kits\n     * @param productId Dokit平台端申请的productId\n     */\n    fun install(\n        app: Application,\n        mapKits: LinkedHashMap<String, List<AbstractKit>>,\n        listKits: List<AbstractKit>,\n        productId: String\n    ) {\n\n        pluginConfig()\n        initThirdLibraryInfo()\n        DoKitManager.PRODUCT_ID = productId\n        DoKitManager.APP_HEALTH_RUNNING = GlobalConfig.getAppHealth()\n        //赋值\n        APPLICATION = app\n        //初始化工具类\n        initAndroidUtil(app)\n\n\n        //判断进程名\n        if (!ProcessUtils.isMainProcess()) {\n            return\n        }\n\n        //解锁系统隐藏api限制权限以及hook Instrumentation\n        HandlerHooker.doHook(app)\n\n        val strDokitMode = DoKitSPUtil.getString(SharedPrefsKey.FLOAT_START_MODE, \"normal\")\n        DoKitManager.IS_NORMAL_FLOAT_MODE = strDokitMode == \"normal\"\n        //初始化第三方工具\n        //建议业务自己接入\n        //installLeakCanary(app)\n        checkLargeImgIsOpen()\n        registerNetworkStatusChangedListener()\n        startAppHealth()\n        initGpsMock()\n\n        globalRunTimeHook()\n\n        //注册全局的activity生命周期回调\n        app.registerActivityLifecycleCallbacks(DoKitActivityLifecycleCallbacks())\n        //注册App前后台切换监听\n        registerAppStatusChangedListener()\n        //DokitConstant.KIT_MAPS.clear()\n        DoKitManager.GLOBAL_KITS.clear()\n        //添加用户的自定义kit\n        when {\n            mapKits.isNotEmpty() -> {\n                mapKits.forEach { map ->\n                    val kitWraps: MutableList<KitWrapItem> = map.value.map {\n                        KitWrapItem(\n                            KitWrapItem.TYPE_KIT,\n                            DoKitCommUtil.getString(it.name),\n                            true,\n                            map.key,\n                            it\n                        )\n                    } as MutableList<KitWrapItem>\n\n                    DoKitManager.GLOBAL_KITS[map.key] = kitWraps\n                }\n            }\n\n            mapKits.isEmpty() && listKits.isNotEmpty() -> {\n                val kitWraps: MutableList<KitWrapItem> = listKits.map {\n                    KitWrapItem(\n                        KitWrapItem.TYPE_KIT,\n                        DoKitCommUtil.getString(it.name),\n                        true,\n                        DoKitCommUtil.getString(R.string.dk_category_biz),\n                        it\n                    )\n                } as MutableList<KitWrapItem>\n                DoKitManager.GLOBAL_KITS[DoKitCommUtil.getString(R.string.dk_category_biz)] =\n                    kitWraps\n            }\n\n        }\n\n        //添加自定义的kit 需要读取配置文件\n        doKitGlobalScope.launch(CoroutineName(\"DoKit全局异常\") + doKitGlobalExceptionHandler) {\n            addInnerKit(app)\n        }\n\n\n        //添加自定义的kit 需要读取配置文件\n//        ThreadUtils.executeByIo(object : ThreadUtils.SimpleTask<Any>() {\n//            override fun doInBackground(): Any {\n//                addInnerKit(app)\n//                return Any()\n//            }\n//\n//            override fun onSuccess(result: Any?) {\n//            }\n//        })\n\n        //addSystemKitForTest(app)\n        //初始化悬浮窗管理类\n        DoKitViewManager.INSTANCE.init()\n        //上传app基本信息便于统计\n        if (DoKitManager.ENABLE_UPLOAD) {\n            try {\n                DoraemonStatisticsUtil.uploadUserInfo(app)\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n\n\n        //上传埋点\n        DataPickManager.getInstance().postData()\n        isInit = true\n    }\n\n\n    /**\n     * 注册App前后台切换监听\n     */\n    private fun registerAppStatusChangedListener() {\n        //前后台监听\n        AppUtils.registerAppStatusChangedListener(object : Utils.OnAppStatusChangedListener {\n            //进入前台\n            override fun onForeground(activity: Activity?) {\n                DoKitServiceManager.dispatch(\n                    DoKitServiceEnum.onForeground,\n                    activity!!\n                )\n            }\n\n            //进入后台\n            override fun onBackground(activity: Activity?) {\n                DoKitServiceManager.dispatch(\n                    DoKitServiceEnum.onBackground,\n                    activity!!\n                )\n            }\n\n        })\n    }\n\n\n    /**\n     * 添加内置kit\n     */\n    private suspend fun addInnerKit(application: Application) = withContext(Dispatchers.IO) {\n        var json: String?\n        if (FileUtils.isFileExists(DoKitManager.SYSTEM_KITS_BAK_PATH)) {\n            json = FileIOUtils.readFile2String(DoKitManager.SYSTEM_KITS_BAK_PATH)\n            if (TextUtils.isEmpty(json) || json == \"[]\") {\n                val open = application.assets.open(\"dokit_system_kits.json\")\n                json = ConvertUtils.inputStream2String(open, \"UTF-8\")\n            }\n        } else {\n            val open = application.assets.open(\"dokit_system_kits.json\")\n            json = ConvertUtils.inputStream2String(open, \"UTF-8\")\n        }\n\n        ToolPanelUtil.jsonConfig2InnerKits(json)\n        //悬浮窗模式\n        DoKitManager.GLOBAL_KITS[DoKitCommUtil.getString(R.string.dk_category_mode)] =\n            mutableListOf()\n        //添加退出项\n        DoKitManager.GLOBAL_KITS[DoKitCommUtil.getString(R.string.dk_category_exit)] =\n            mutableListOf()\n        //版本号\n        DoKitManager.GLOBAL_KITS[DoKitCommUtil.getString(R.string.dk_category_version)] =\n            mutableListOf()\n\n        //遍历初始化\n        DoKitManager.GLOBAL_KITS.forEach { map ->\n            map.value.forEach { kitWrap ->\n                kitWrap.kit?.onAppInit(application)\n            }\n        }\n    }\n\n\n    /**\n     * 插件会在当前方法中插入插件配置代码\n     */\n    private fun pluginConfig() {}\n\n    /**\n     * 插件会在当前方法中插入三方库的基本信息\n     */\n    private fun initThirdLibraryInfo() {\n    }\n\n    /**\n     * 全局方法hook\n     */\n    private fun globalRunTimeHook() {\n        try {\n            val mcProcessor = DoKitManager.getModuleProcessor(DoKitModule.MODULE_MC)\n            mcProcessor?.proceed(mapOf(\"action\" to \"global_hook\"))\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n\n    }\n\n    /**\n     * 获取MC当前的链接地址\n     */\n    fun getMcConnectUrl(): String {\n        try {\n            val mcProcessor = DoKitManager.getModuleProcessor(DoKitModule.MODULE_MC)\n            val map = mcProcessor?.proceed(mapOf(\"action\" to \"dokit_mc_connect_url\"))\n            val url = map?.get(\"url\") as String\n            return url\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n        return \"\"\n    }\n    private fun initGpsMock(){\n        val map = mapOf(\n            \"action\" to \"init_gps_mock\"\n        )\n        DoKitManager.getModuleProcessor(DoKitModule.MODULE_GPS_MOCK)?.proceed(map)\n    }\n\n    /**\n     * 单个文件的阈值为1M\n     */\n    private const val FILE_LENGTH_THRESHOLD = 1 * 1024 * 1024.toLong()\n\n    //todo 测试时为1k 对外时需要修改回来\n    //private static long FILE_LENGTH_THRESHOLD = 1024;\n    private fun traverseFile(rootFileDir: File?) {\n        if (rootFileDir == null) {\n            return\n        }\n        val files = rootFileDir.listFiles()\n        files?.forEach { file ->\n            if (file.isDirectory) {\n                //若是目录，则递归打印该目录下的文件\n                //LogHelper.i(TAG, \"文件夹==>\" + file.getAbsolutePath());\n                traverseFile(file)\n            }\n            if (file.isFile) {\n                //若是文件，直接打印 byte\n                val fileLength = FileUtils.getLength(file)\n                if (fileLength > FILE_LENGTH_THRESHOLD) {\n                    val fileBean = BigFileBean()\n                    fileBean.fileName = FileUtils.getFileName(file)\n                    fileBean.filePath = file.absolutePath\n                    fileBean.fileSize = \"\" + fileLength\n                    AppHealthInfoUtil.getInstance().addBigFilrInfo(fileBean)\n                }\n                //LogHelper.i(TAG, \"文件==>\" + file.getAbsolutePath() + \"   fileName===>\" + FileUtils.getFileName(file) + \" fileLength===>\" + fileLength);\n            }\n        }\n\n    }\n\n    /**\n     * 开启大文件检测\n     * https://blog.csdn.net/csdn_aiyang/article/details/80665185 内部存储和外部存储的概念\n     */\n    private fun startBigFileInspect() {\n        ThreadUtils.executeByIo(object : ThreadUtils.SimpleTask<Any?>() {\n            @Throws(Throwable::class)\n            override fun doInBackground(): Any? {\n                val externalCacheDir =\n                    APPLICATION.externalCacheDir\n                if (externalCacheDir != null) {\n                    val externalRootDir = externalCacheDir.parentFile\n                    traverseFile(externalRootDir)\n                }\n                val innerCacheDir = APPLICATION.cacheDir\n                if (innerCacheDir != null) {\n                    val innerRootDir = innerCacheDir.parentFile\n                    traverseFile(innerRootDir)\n                }\n                return null\n            }\n\n            override fun onSuccess(result: Any?) {}\n        })\n    }\n\n    /**\n     * 开启健康体检\n     */\n    private fun startAppHealth() {\n        if (!DoKitManager.APP_HEALTH_RUNNING) {\n            return\n        }\n        if (TextUtils.isEmpty(DoKitManager.PRODUCT_ID)) {\n            ToastUtils.showShort(\"要使用健康体检功能必须先去平台端注册\")\n            return\n        }\n        AppHealthInfoUtil.getInstance().start()\n        //开启大文件检测\n        startBigFileInspect()\n    }\n\n    fun setWebDoorCallback(callback: WebDoorCallback?) {\n        WebDoorManager.getInstance().webDoorCallback = callback\n    }\n\n    /**\n     * 注册全局的网络状态监听\n     */\n    private fun registerNetworkStatusChangedListener() {\n        NetworkUtils.registerNetworkStatusChangedListener(object :\n            NetworkUtils.OnNetworkStatusChangedListener {\n            override fun onDisconnected() {\n                //ToastUtils.showShort(\"当前网络已断开\");\n                Log.i(\"Doraemon\", \"当前网络已断开\")\n\n            }\n\n            override fun onConnected(networkType: NetworkUtils.NetworkType) {\n                //重启DebugDB\n                //ToastUtils.showShort(\"当前网络类型:\" + networkType.name());\n                Log.i(\"Doraemon\", \"当前网络类型\" + networkType.name)\n\n            }\n        })\n    }\n\n    /**\n     * 确认大图检测功能时候被打开\n     */\n    private fun checkLargeImgIsOpen() {\n        if (PerformanceSpInfoConfig.isLargeImgOpen()) {\n            NetworkManager.get().startMonitor()\n        }\n    }\n\n    /**\n     * 安装leackCanary\n     *\n     * @param app\n     */\n    private fun installLeakCanary(app: Application) {\n        //反射调用\n        try {\n            val leakCanaryManager = Class.forName(\"com.didichuxing.doraemonkit.LeakCanaryManager\")\n            val install = leakCanaryManager.getMethod(\"install\", Application::class.java)\n            //调用静态的install方法\n            install.invoke(null, app)\n            val initAidlBridge =\n                leakCanaryManager.getMethod(\"initAidlBridge\", Application::class.java)\n            //调用静态initAidlBridge方法\n            initAidlBridge.invoke(null, app)\n        } catch (e: Exception) {\n        }\n    }\n\n    private fun initAndroidUtil(app: Application) {\n        Utils.init(app)\n        LogUtils.getConfig()\n            // 设置 log 总开关，包括输出到控制台和文件，默认开\n            .setLogSwitch(true)\n            // 设置是否输出到控制台开关，默认开\n            .setConsoleSwitch(true)\n            // 设置 log 全局标签，默认为空，当全局标签不为空时，我们输出的 log 全部为该 tag， 为空时，如果传入的 tag 为空那就显示类名，否则显示 tag\n            .setGlobalTag(\"Doraemon\")\n            // 设置 log 头信息开关，默认为开\n            .setLogHeadSwitch(true)\n            // 打印 log 时是否存到文件的开关，默认关\n            .setLog2FileSwitch(true)\n            // 当自定义路径为空时，写入应用的/cache/log/目录中\n            .setDir(\"\")\n            // 当文件前缀为空时，默认为\"util\"，即写入文件为\"util-MM-dd.txt\"\n            .setFilePrefix(\"djx-table-log\")\n            // 输出日志是否带边框开关，默认开\n            .setBorderSwitch(true)\n            // 一条日志仅输出一条，默认开，为美化 AS 3.1 的 Logcat\n            .setSingleTagSwitch(true)\n            // log 的控制台过滤器，和 logcat 过滤器同理，默认 Verbose\n            .setConsoleFilter(LogUtils.V)\n            // log 文件过滤器，和 logcat 过滤器同理，默认 Verbose\n            .setFileFilter(LogUtils.E)\n            // log 栈深度，默认为 1\n            .setStackDeep(2)\n            .stackOffset = 0\n    }\n\n    /**\n     * 显示系统悬浮窗icon\n     */\n    private fun showMainIcon() {\n        DoKitViewManager.INSTANCE.attachMainIcon(ActivityUtils.getTopActivity())\n    }\n\n    fun show() {\n        DoKitManager.ALWAYS_SHOW_MAIN_ICON = true\n        if (!isShow) {\n            showMainIcon()\n        }\n    }\n\n    /**\n     * 直接显示工具面板页面\n     */\n    fun showToolPanel() {\n        DoKitViewManager.INSTANCE.attachToolPanel(ActivityUtils.getTopActivity())\n    }\n\n    fun hideToolPanel() {\n        DoKitViewManager.INSTANCE.detachToolPanel()\n    }\n\n    fun hide() {\n        DoKitManager.MAIN_ICON_HAS_SHOW = false\n        DoKitManager.ALWAYS_SHOW_MAIN_ICON = false\n        DoKitViewManager.INSTANCE.detachMainIcon()\n    }\n\n    fun sendCustomEvent(eventType: String, view: View? = null, param: Map<String, String>? = null) {\n        val map = mapOf(\n            \"action\" to \"mc_custom_event\",\n            \"eventType\" to eventType,\n            \"view\" to view,\n            \"param\" to param\n        )\n        DoKitManager.getModuleProcessor(DoKitModule.MODULE_MC)?.proceed(map)\n    }\n\n    fun getMode(): String {\n        val map = mapOf(\n            \"action\" to \"mc_mode\"\n        )\n        val result = DoKitManager.getModuleProcessor(DoKitModule.MODULE_MC)?.proceed(map)\n        return result?.get(\"mode\") as String\n    }\n\n    /**\n     * 禁用app信息上传开关，该上传信息只为做DoKit接入量的统计，如果用户需要保护app隐私，可调用该方法进行禁用\n     */\n    fun disableUpload() {\n        DoKitManager.ENABLE_UPLOAD = false\n    }\n\n    val isShow: Boolean\n        get() = DoKitManager.MAIN_ICON_HAS_SHOW\n\n    /**\n     * 设置加密数据库的密码\n     */\n    fun setDatabasePass(map: Map<String, String>) {\n        DoKitManager.DATABASE_PASS = map\n    }\n\n    /**\n     * 设置平台端文件管理端口号\n     */\n    fun setFileManagerHttpPort(port: Int) {\n        DoKitManager.FILE_MANAGER_HTTP_PORT = port\n    }\n\n    /**\n     * 设置一机多控长连接端口号\n     */\n    fun setMCWSPort(port: Int) {\n        DoKitManager.MC_WS_PORT = port\n    }\n\n    /**\n     * 是否显示主入口icon\n     */\n    fun setAlwaysShowMainIcon(alwaysShow: Boolean) {\n        DoKitManager.ALWAYS_SHOW_MAIN_ICON = alwaysShow\n    }\n\n\n    /**\n     * 设置一机多控自定义拦截器\n     */\n    fun setMCIntercept(interceptor: McClientProcessor) {\n        DoKitManager.MC_CLIENT_PROCESSOR = interceptor\n    }\n\n    /**\n     * 设置扩展网络拦截器的代理对象\n     */\n    fun setNetExtInterceptor(extInterceptorProxy: DokitExtInterceptor.DokitExtInterceptorProxy) {\n        DokitExtInterceptor.dokitExtInterceptorProxy = extInterceptorProxy\n    }\n\n\n    /**\n     * 设置一机多控自定义拦截器\n     */\n    fun setCallBack(callback: DoKitCallBack) {\n        DoKitManager.CALLBACK = callback\n    }\n\n\n    /**\n     * 启动悬浮窗\n     * @JvmStatic:允许使用java的静态方法的方式调用\n     * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n     */\n    fun launchFloating(\n        targetClass: Class<out AbsDoKitView>,\n        mode: DoKitViewLaunchMode = DoKitViewLaunchMode.SINGLE_INSTANCE,\n        bundle: Bundle? = null\n    ) {\n        SimpleDoKitLauncher.launchFloating(targetClass, mode, bundle)\n    }\n\n\n    /**\n     * 移除悬浮窗\n     * @JvmStatic:允许使用java的静态方法的方式调用\n     * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n     */\n    fun removeFloating(targetClass: Class<out AbsDoKitView>) {\n        SimpleDoKitLauncher.removeFloating(targetClass)\n    }\n\n    /**\n     * 移除悬浮窗\n     * @JvmStatic:允许使用java的静态方法的方式调用\n     * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n     */\n    fun removeFloating(dokitView: AbsDoKitView) {\n        SimpleDoKitLauncher.removeFloating(dokitView)\n    }\n\n    /**\n     * 启动全屏页面\n     * @JvmStatic:允许使用java的静态方法的方式调用\n     * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n     */\n    fun launchFullScreen(\n        targetClass: Class<out BaseFragment>,\n        context: Context? = null,\n        bundle: Bundle? = null,\n        isSystemFragment: Boolean = false\n    ) {\n        SimpleDoKitLauncher.launchFullScreen(targetClass, context, bundle, isSystemFragment)\n    }\n\n    @JvmStatic\n    fun <T : AbsDoKitView> getDoKitView(\n        activity: Activity?,\n        clazz: Class<out T>\n    ): T? {\n        return if (DoKitViewManager.INSTANCE.getDoKitView(activity, clazz) == null) {\n            null\n        } else {\n            DoKitViewManager.INSTANCE.getDoKitView(activity, clazz) as T\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/DoraemonKit.kt",
    "content": "package com.didichuxing.doraemonkit\n\nimport android.app.Application\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.core.McClientProcessor\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitExtInterceptor\nimport com.didichuxing.doraemonkit.kit.webdoor.WebDoorManager.WebDoorCallback\n\n/**\n * Created by jint on 2018/6/22.\n */\n@Deprecated(\"请用DoKit来代替\")\nobject DoraemonKit {\n    @JvmField\n    var APPLICATION: Application? = null\n    private const val TAG = \"DoraemonKit\"\n\n\n    @JvmStatic\n    fun install(app: Application) {\n        install(app, linkedMapOf(), mutableListOf(), \"\")\n    }\n\n    @JvmStatic\n    fun install(app: Application, productId: String) {\n        install(app, linkedMapOf(), mutableListOf(), productId)\n    }\n\n\n    @JvmStatic\n    fun install(app: Application, mapKits: LinkedHashMap<String, List<AbstractKit>>) {\n        install(app, mapKits, mutableListOf(), \"\")\n    }\n\n    @JvmStatic\n    fun install(\n        app: Application,\n        mapKits: LinkedHashMap<String, List<AbstractKit>>,\n        productId: String\n    ) {\n        install(app, mapKits, mutableListOf(), productId)\n    }\n\n    @JvmStatic\n    fun install(app: Application, listKits: List<AbstractKit>) {\n        install(app, linkedMapOf(), listKits, \"\")\n    }\n\n    @JvmStatic\n    fun install(app: Application, listKits: List<AbstractKit>, productId: String) {\n        install(app, linkedMapOf(), listKits, productId)\n    }\n\n    /**\n     * @param app\n     * @param mapKits  自定义kits  根据用户传进来的分组 建议优先选择mapKits 两者都传的话会选择mapKits\n     * @param listKits  自定义kits 兼容原先老的api\n     * @param productId Dokit平台端申请的productId\n     */\n    @JvmStatic\n    private fun install(\n        app: Application,\n        mapKits: LinkedHashMap<String, List<AbstractKit>>? = linkedMapOf(),\n        listKits: List<AbstractKit>? = mutableListOf(),\n        productId: String? = \"\"\n    ) {\n        APPLICATION = app\n        DoKitEnv.app = app\n        try {\n            DoKitReal.install(\n                app, mapKits ?: linkedMapOf(), listKits\n                    ?: mutableListOf(), productId ?: \"\"\n            )\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    @JvmStatic\n    fun setWebDoorCallback(callback: WebDoorCallback?) {\n        DoKitReal.setWebDoorCallback(callback)\n    }\n\n    @JvmStatic\n    fun show() {\n        DoKitReal.show()\n    }\n\n    /**\n     * 直接显示工具面板页面\n     */\n    @JvmStatic\n    fun showToolPanel() {\n        DoKitReal.showToolPanel()\n    }\n\n    /**\n     * 直接隐藏工具面板\n     */\n    @JvmStatic\n    fun hideToolPanel() {\n        DoKitReal.hideToolPanel()\n    }\n\n    @JvmStatic\n    fun hide() {\n        DoKitReal.hide()\n    }\n\n    /**\n     * 禁用app信息上传开关，该上传信息只为做DoKit接入量的统计，如果用户需要保护app隐私，可调用该方法进行禁用\n     */\n    @JvmStatic\n    fun disableUpload() {\n        DoKitReal.disableUpload()\n    }\n\n    @JvmStatic\n    val isShow: Boolean\n        get() = DoKitReal.isShow\n\n    @JvmStatic\n    fun setDebug(debug: Boolean) {\n        DoKitReal.setDebug(debug)\n    }\n\n    /**\n     * 是否显示主入口icon\n     */\n    @JvmStatic\n    fun setAlwaysShowMainIcon(alwaysShow: Boolean) {\n        DoKitReal.setAlwaysShowMainIcon(alwaysShow)\n    }\n\n    /**\n     * 设置加密数据库密码\n     */\n    @JvmStatic\n    fun setDatabasePass(map: Map<String, String>) {\n        DoKitReal.setDatabasePass(map)\n    }\n\n\n    /**\n     * 设置文件管理助手http端口号\n     */\n    @JvmStatic\n    fun setFileManagerHttpPort(port: Int) {\n        DoKitReal.setFileManagerHttpPort(port)\n    }\n\n    @JvmStatic\n    fun setMCIntercept(interceptor: McClientProcessor) {\n        DoKitReal.setMCIntercept(interceptor)\n    }\n\n    @JvmStatic\n    fun setMCWSPort(port: Int) {\n        DoKitReal.setMCWSPort(port)\n    }\n\n    /**\n     *设置dokit的性能监控全局回调\n     */\n    @JvmStatic\n    fun setCallBack(callback: DoKitCallBack) {\n        DoKitReal.setCallBack(callback)\n    }\n\n    /**\n     * 设置扩展网络拦截器的代理对象\n     */\n    @JvmStatic\n    fun setNetExtInterceptor(extInterceptorProxy: DokitExtInterceptor.DokitExtInterceptorProxy) {\n        DoKitReal.setNetExtInterceptor(extInterceptorProxy)\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/DokitPluginConfig.java",
    "content": "package com.didichuxing.doraemonkit.aop;\n\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.util.Map;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/27-18:24\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class DokitPluginConfig {\n    private static final String TAG = \"DokitPluginConfig\";\n\n    public static int STRATEGY_STACK = 0;\n    public static int STRATEGY_NORMAL = 1;\n    //字节码被关闭\n    public static int STRATEGY_NULL = -1;\n    public static boolean SWITCH_DOKIT_PLUGIN = false;\n    public static boolean SWITCH_BIG_IMG = false;\n    public static boolean SWITCH_NETWORK = false;\n    public static boolean SWITCH_GPS = false;\n    public static boolean SWITCH_METHOD = false;\n    /**\n     * 0 代表调用栈 1:代表普通模式 -1:字节码插装关闭\n     */\n    public static int VALUE_METHOD_STRATEGY = STRATEGY_NULL;\n\n\n    /**\n     * 注入插件配置 动态注入到DoraemonKitReal#pluginConfig方法中\n     */\n    public static void inject(Map config) {\n        //LogHelper.i(TAG, \"map====>\" + config);\n        SWITCH_DOKIT_PLUGIN = (boolean) config.get(\"dokitPluginSwitch\");\n        SWITCH_METHOD = (boolean) config.get(\"methodSwitch\");\n        SWITCH_BIG_IMG = (boolean) config.get(\"bigImgSwitch\");\n        SWITCH_NETWORK = (boolean) config.get(\"networkSwitch\");\n        SWITCH_GPS = (boolean) config.get(\"gpsSwitch\");\n        VALUE_METHOD_STRATEGY = (int) config.get(\"methodStrategy\");\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/DokitThirdLibInfo.java",
    "content": "package com.didichuxing.doraemonkit.aop;\n\n\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/27-18:24\n * 描    述：app三方库信息\n * 修订历史：\n * ================================================\n */\npublic class DokitThirdLibInfo {\n    private static final String TAG = \"DokitThirdLibInfo\";\n    public static Map<String, String> THIRD_LIB_INFOS = new HashMap<>();\n    /**\n     * key===>groupId:artifactId\n     */\n    public static Map<String, String> THIRD_LIB_INFOS_SIMPLE = new HashMap<>();\n\n\n    /**\n     * 注入插件配置 动态注入到DoraemonKitReal#pluginConfig方法中\n     */\n    public static void inject(Map config) {\n        //LogHelper.i(TAG, \"map====>\" + config);\n        THIRD_LIB_INFOS = config;\n        THIRD_LIB_INFOS_SIMPLE.clear();\n        Iterator entries = THIRD_LIB_INFOS.entrySet().iterator();\n        while (entries.hasNext()) {\n            Map.Entry entry = (Map.Entry) entries.next();\n            String key = (String) entry.getKey();\n            String[] keys = key.split(\":\");\n            if (keys.length == 3) {\n                String groupId = keys[0];\n                String artifactId = keys[1];\n                String newKey = groupId + \":\" + artifactId;\n                String value = (String) entry.getValue();\n                THIRD_LIB_INFOS_SIMPLE.put(newKey, value);\n            }\n\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/MethodCostUtil.kt",
    "content": "package com.didichuxing.doraemonkit.aop\n\nimport android.app.Activity\nimport android.app.Application\nimport android.app.Service\nimport android.os.SystemClock\nimport android.util.Log\nimport com.didichuxing.doraemonkit.aop.method_stack.StaticMethodObject\nimport com.didichuxing.doraemonkit.kit.timecounter.TimeCounterManager\nimport java.util.concurrent.ConcurrentHashMap\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/2/29-15:31\n * 描    述：全局的函数耗时工具类\n * 修订历史：\n * ================================================\n */\npublic object MethodCostUtil {\n    private const val TAG = \"DOKIT_SLOW_METHOD\"\n\n    /**\n     * 用来标识是静态函数对象\n     */\n    private val staticMethodObject: StaticMethodObject by lazy {\n        StaticMethodObject()\n    }\n\n    /**\n     * key className&method\n     */\n    private val METHOD_COSTS: ConcurrentHashMap<String, Long?> by lazy { ConcurrentHashMap<String, Long?>() }\n\n    @Synchronized\n    fun recodeObjectMethodCostStart(thresholdTime: Int, methodName: String, classObj: Any?) {\n        try {\n            METHOD_COSTS[methodName] = SystemClock.elapsedRealtime()\n            if (classObj is Application) {\n                val methods = methodName.split(\"&\".toRegex()).toTypedArray()\n                if (methods.size == 2) {\n                    if (methods[1] == \"onCreate\") {\n                        TimeCounterManager.get().onAppCreateStart()\n                    }\n                    if (methods[1] == \"attachBaseContext\") {\n                        TimeCounterManager.get().onAppAttachBaseContextStart()\n                    }\n                }\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    fun recodeStaticMethodCostStart(thresholdTime: Int, methodName: String) {\n        recodeObjectMethodCostStart(thresholdTime, methodName, staticMethodObject)\n\n    }\n\n    /**\n     * 对象方法\n     *\n     * @param thresholdTime 预设的值 单位为us 1000us = 1ms\n     * @param methodName\n     * @param classObj      调用该函数的对象\n     */\n    fun recodeObjectMethodCostEnd(thresholdTime: Int, methodName: String, classObj: Any?) {\n        synchronized(MethodCostUtil::class.java) {\n            try {\n                if (METHOD_COSTS.containsKey(methodName)) {\n                    val startTime = METHOD_COSTS[methodName]!!\n                    val costTime = (SystemClock.elapsedRealtime() - startTime).toInt()\n                    METHOD_COSTS.remove(methodName)\n                    if (classObj is Application) {\n                        //Application 启动时间统计\n                        val methods = methodName.split(\"&\".toRegex()).toTypedArray()\n                        if (methods.size == 2) {\n                            if (methods[1] == \"onCreate\") {\n                                TimeCounterManager.get().onAppCreateEnd()\n                            }\n                            if (methods[1] == \"attachBaseContext\") {\n                                TimeCounterManager.get().onAppAttachBaseContextEnd()\n                            }\n                        }\n                        //printApplicationStartTime(methodName);\n                    } else if (classObj is Activity) {\n                        //Activity 启动时间统计\n                        //printActivityStartTime(methodName);\n                    } else if (classObj is Service) {\n                        //service 启动时间统计\n                    }\n\n\n                    //如果该方法的执行时间大于1ms 则记录\n                    if (costTime >= thresholdTime) {\n                        val threadName = Thread.currentThread().name\n                        Log.i(TAG, \"================Dokit================\")\n                        Log.i(\n                            TAG,\n                            \"\\t methodName===>$methodName  threadName==>$threadName  thresholdTime===>$thresholdTime   costTime===>$costTime\"\n                        )\n                        val stackTraceElements = Thread.currentThread().stackTrace\n                        for (stackTraceElement in stackTraceElements) {\n                            if (stackTraceElement.toString().contains(\"MethodCostUtil\")) {\n                                continue\n                            }\n                            if (stackTraceElement.toString()\n                                    .contains(\"dalvik.system.VMStack.getThreadStackTrace\")\n                            ) {\n                                continue\n                            }\n                            if (stackTraceElement.toString()\n                                    .contains(\"java.lang.Thread.getStackTrace\")\n                            ) {\n                                continue\n                            }\n                            Log.i(TAG, \"\\tat $stackTraceElement\")\n                        }\n                    }\n                }\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    private fun printApplicationStartTime(methodName: String) {\n        Log.i(TAG, \"================Dokit Application start================\")\n        val stackTraceElements = Thread.currentThread().stackTrace\n        for (stackTraceElement in stackTraceElements) {\n            if (stackTraceElement.toString().contains(\"MethodCostUtil\")) {\n                continue\n            }\n            if (stackTraceElement.toString()\n                    .contains(\"dalvik.system.VMStack.getThreadStackTrace\")\n            ) {\n                continue\n            }\n            if (stackTraceElement.toString().contains(\"java.lang.Thread.getStackTrace\")) {\n                continue\n            }\n            Log.i(TAG, \"\\tat $stackTraceElement\")\n        }\n        Log.i(TAG, \"================Dokit Application  end================\")\n        Log.i(TAG, \"\\n\")\n    }\n\n    private fun printActivityStartTime(methodName: String) {\n        Log.i(TAG, \"================Dokit Activity start================\")\n        val stackTraceElements = Thread.currentThread().stackTrace\n        for (stackTraceElement in stackTraceElements) {\n            if (stackTraceElement.toString().contains(\"MethodCostUtil\")) {\n                continue\n            }\n            if (stackTraceElement.toString()\n                    .contains(\"dalvik.system.VMStack.getThreadStackTrace\")\n            ) {\n                continue\n            }\n            if (stackTraceElement.toString().contains(\"java.lang.Thread.getStackTrace\")) {\n                continue\n            }\n            Log.i(TAG, \"\\tat $stackTraceElement\")\n        }\n        Log.i(TAG, \"================Dokit Activity end================\")\n        Log.i(TAG, \"\\n\")\n    }\n\n    /**\n     * 静态方法\n     *\n     * @param thresholdTime 预设的值 单位为us 1000us = 1ms\n     * @param methodName\n     */\n    fun recodeStaticMethodCostEnd(thresholdTime: Int, methodName: String) {\n        recodeObjectMethodCostEnd(thresholdTime, methodName, staticMethodObject)\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/OkHttpHook.java",
    "content": "package com.didichuxing.doraemonkit.aop;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.constant.DoKitModule;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.core.DokitAbility;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.AbsDoKitInterceptor;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitCapInterceptor;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitExtInterceptor;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitLargePicInterceptor;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitMockInterceptor;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitWeakNetworkInterceptor;\nimport com.didichuxing.doraemonkit.util.ReflectUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport okhttp3.Interceptor;\nimport okhttp3.OkHttpClient;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-13-10:40\n * 描    述：用来通过ASM在编译器进行hook\n * 修订历史：\n * ================================================\n */\npublic class OkHttpHook {\n//    每个拦截器都有自己的相对优点。\n\n//    应用拦截器\n//    不需要担心中间响应，如重定向和重试。\n//    总是调用一次，即使从缓存提供HTTP响应。\n//    遵守应用程序的原始意图。\n//    不注意OkHttp注入的头像If-None-Match。\n//    允许短路和不通话Chain.proceed()。\n//    允许重试并进行多次呼叫Chain.proceed()。\n\n//    网络拦截器\n//    能够对重定向和重试等中间响应进行操作。\n//    不调用缓存的响应来短路网络。\n//    观察数据，就像通过网络传输一样。\n//    访问Connection该请求。\n\n\n    /**\n     * 添加dokit 的拦截器 通过字节码插入\n     */\n    public static void addDoKitIntercept(OkHttpClient client) {\n        if (!DoKit.isInit()) {\n            return;\n        }\n        try {\n            List<Interceptor> interceptors = new ArrayList<>(client.interceptors());\n            List<Interceptor> networkInterceptors = new ArrayList<>(client.networkInterceptors());\n            DokitAbility.DokitModuleProcessor processor = DoKitManager.INSTANCE.getModuleProcessor(DoKitModule.MODULE_MC);\n            if (processor != null) {\n                Object interceptor = processor.values().get(\"okhttp_interceptor\");\n                Object interceptorProxy = processor.values().get(\"okhttp_proxy_interceptor\");\n                if (interceptor instanceof AbsDoKitInterceptor) {\n//                    noDuplicateAdd(interceptors, (AbsDoKitInterceptor) interceptor);\n                    noDuplicateAdd(interceptors, (AbsDoKitInterceptor) interceptorProxy);\n                }\n            }\n            noDuplicateAdd(interceptors, new DokitMockInterceptor());\n            noDuplicateAdd(interceptors, new DokitLargePicInterceptor());\n            noDuplicateAdd(interceptors, new DokitCapInterceptor());\n            noDuplicateAdd(interceptors, new DokitExtInterceptor());\n            noDuplicateAdd(networkInterceptors, new DokitWeakNetworkInterceptor());\n            //需要用反射重新赋值 因为源码中创建了一个不可变的list\n            ReflectUtils.reflect(client).field(\"interceptors\", interceptors);\n            ReflectUtils.reflect(client).field(\"networkInterceptors\", networkInterceptors);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n\n    }\n\n    //list判断是否重复添加\n    private static void noDuplicateAdd(List<Interceptor> interceptors, AbsDoKitInterceptor interceptor) {\n        boolean hasInterceptor = false;\n        for (Interceptor i : interceptors) {\n            if (i instanceof AbsDoKitInterceptor) {\n                if (((AbsDoKitInterceptor) i).getTAG().equals(interceptor.getTAG())) {\n                    hasInterceptor = true;\n                    break;\n                }\n            }\n        }\n        if (!hasInterceptor) {\n            interceptors.add(interceptor);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/WebViewHook.java",
    "content": "package com.didichuxing.doraemonkit.aop;\n\nimport android.annotation.SuppressLint;\nimport android.os.Build;\nimport android.text.TextUtils;\nimport android.webkit.WebSettings;\nimport android.webkit.WebView;\n\nimport androidx.webkit.WebViewCompat;\n\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.h5_help.DoKitJSI;\nimport com.didichuxing.doraemonkit.kit.h5_help.DoKitWebViewClient;\nimport com.didichuxing.doraemonkit.kit.h5_help.DoKitX5WebViewClient;\nimport com.didichuxing.doraemonkit.kit.h5_help.X5WebViewUtil;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/8/31-11:39\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class WebViewHook {\n    private static final String TAG = \"WebViewHook\";\n\n\n    public static String getSafeUrl(String url) {\n        if (!TextUtils.isEmpty(url)) {\n            if (DoKitManager.H5_DOKIT_MC_INJECT && ((String) url).startsWith(\"https\")) {\n                return url.replaceFirst(\"https\", \"http\");\n            } else {\n                return url;\n            }\n        }\n        return \"\";\n    }\n\n    /**\n     * webview inject java object\n     */\n    public static void inject(Object webView) {\n        //LogHelper.i(TAG, \"====inject====\");\n        if (webView != null) {\n            //先判断是否引入了X5WebView\n            if (X5WebViewUtil.INSTANCE.hasImpX5WebViewLib()) {\n                if (webView instanceof WebView) {\n                    injectNormal((WebView) webView);\n                } else if (webView instanceof com.tencent.smtt.sdk.WebView) {\n                    injectX5((com.tencent.smtt.sdk.WebView) webView);\n                }\n            } else {\n                if (webView instanceof WebView) {\n                    injectNormal((WebView) webView);\n                }\n            }\n        }\n    }\n\n\n    @SuppressLint({\"AddJavascriptInterface\", \"RequiresFeature\", \"SetJavaScriptEnabled\"})\n    private static void injectNormal(WebView webView) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n//            webView.setWebContentsDebuggingEnabled(true);\n            if (!(WebViewCompat.getWebViewClient(webView) instanceof DoKitWebViewClient)) {\n                WebSettings settings = webView.getSettings();\n                settings.setJavaScriptEnabled(true);\n                settings.setDatabaseEnabled(true);\n                settings.setDomStorageEnabled(true);\n                settings.setAllowUniversalAccessFromFileURLs(true);\n                webView.addJavascriptInterface(new DoKitJSI(), \"dokitJsi\");\n                webView.setWebViewClient(new DoKitWebViewClient(WebViewCompat.getWebViewClient(webView), settings.getUserAgentString()));\n            }\n        }\n    }\n\n\n    @SuppressLint(\"SetJavaScriptEnabled\")\n    private static void injectX5(com.tencent.smtt.sdk.WebView webView) {\n        LogHelper.i(TAG, \"====injectX5====\");\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            if (!(webView.getWebViewClient() instanceof DoKitX5WebViewClient)) {\n                com.tencent.smtt.sdk.WebSettings settings = webView.getSettings();\n                settings.setJavaScriptEnabled(true);\n                settings.setAllowUniversalAccessFromFileURLs(true);\n                webView.addJavascriptInterface(new DoKitJSI(), \"dokitJsi\");\n                webView.setWebViewClient(new DoKitX5WebViewClient(webView.getWebViewClient(), settings.getUserAgentString()));\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/bigimg/coil/CoilHook.java",
    "content": "package com.didichuxing.doraemonkit.aop.bigimg.coil;\n\nimport android.util.Log;\n\nimport com.didichuxing.doraemonkit.util.ReflectUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.Consumer;\n\nimport coil.request.ImageRequest;\nimport coil.transform.Transformation;\nimport okhttp3.Interceptor;\n\n/**\n * ================================================\n * 作    者：mikaelzero\n * 版    本：1.0\n * 创建日期：2021/9/17\n * 描    述：注入到 coil.request.ImageRequest#init(方法中)\n * 修订历史：\n * ================================================\n */\npublic class CoilHook {\n\n    /**\n     * hook transformations\n     */\n\n    public static void proxy(Object request) {\n        Log.e(\"CoilHook\", \"CoilHook proxy\");\n        try {\n            if (request instanceof ImageRequest) {\n                ImageRequest requestObj = (ImageRequest) request;\n                List<Transformation> transformations = new ArrayList<>(requestObj.getTransformations());\n                if (!hasDoKitTransformation(transformations)) {\n                    transformations.add(new DokitCoilTransformation(requestObj.getData()));\n                    ReflectUtils.reflect(request).field(\"transformations\", transformations);\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n\n    private static boolean hasDoKitTransformation(List<Transformation> transformations) {\n        for (Transformation transformation : transformations) {\n            if (transformation instanceof DokitCoilTransformation) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/bigimg/coil/DokitCoilTransformation.java",
    "content": "package com.didichuxing.doraemonkit.aop.bigimg.coil;\n\nimport android.graphics.Bitmap;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.config.PerformanceSpInfoConfig;\nimport com.didichuxing.doraemonkit.constant.MemoryConstants;\nimport com.didichuxing.doraemonkit.kit.largepicture.LargePictureManager;\nimport com.didichuxing.doraemonkit.util.ConvertUtils;\n\nimport coil.bitmap.BitmapPool;\nimport coil.size.Size;\nimport coil.transform.Transformation;\nimport kotlin.coroutines.Continuation;\n\n/**\n * ================================================\n * 作    者：mikaelzero\n * 版    本：1.0\n * 创建日期：2021/9/17\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class DokitCoilTransformation implements Transformation {\n    private static final String TAG = \"DokitTransformation\";\n    private Object mUri;\n\n    public DokitCoilTransformation(Object uri) {\n        this.mUri = uri;\n    }\n\n\n    @Override\n    public String key() {\n        return \"Dokit&Coil&LargeBitmapTransformation\";\n    }\n\n    @Nullable\n    @Override\n    public Object transform(@NonNull BitmapPool bitmapPool, @NonNull Bitmap source, @NonNull Size size, @NonNull Continuation<? super Bitmap> continuation) {\n        try {\n            if (PerformanceSpInfoConfig.isLargeImgOpen()) {\n                double imgSize = ConvertUtils.byte2MemorySize(source.getByteCount(), MemoryConstants.MB);\n                if (mUri != null) {\n                    LargePictureManager.getInstance().saveImageInfo(mUri.toString(), imgSize, source.getWidth(), source.getHeight(), \"Coil\");\n                } else {\n                    LargePictureManager.getInstance().saveImageInfo(\"Null Uri\", imgSize, source.getWidth(), source.getHeight(), \"Coil\");\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return source;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/bigimg/fresco/DokitFrescoPostprocessor.java",
    "content": "package com.didichuxing.doraemonkit.aop.bigimg.fresco;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.net.Uri;\n\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.constant.MemoryConstants;\nimport com.didichuxing.doraemonkit.util.ConvertUtils;\nimport com.didichuxing.doraemonkit.config.PerformanceSpInfoConfig;\nimport com.didichuxing.doraemonkit.kit.largepicture.LargePictureManager;\nimport com.facebook.cache.common.CacheKey;\nimport com.facebook.cache.common.SimpleCacheKey;\nimport com.facebook.common.references.CloseableReference;\nimport com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory;\nimport com.facebook.imagepipeline.nativecode.Bitmaps;\nimport com.facebook.imagepipeline.request.Postprocessor;\n\nimport static com.facebook.imagepipeline.request.BasePostprocessor.FALLBACK_BITMAP_CONFIGURATION;\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/23-14:53\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class DokitFrescoPostprocessor implements Postprocessor {\n    private static final String TAG = \"DokitPostprocessor\";\n    @Nullable\n    private Postprocessor mOriginalPostprocessor;\n    private Uri mUri;\n    @Nullable\n    private CacheKey mCacheKey;\n\n    public DokitFrescoPostprocessor(Uri uri, Postprocessor postprocessor) {\n        this.mOriginalPostprocessor = postprocessor;\n        this.mUri = uri;\n    }\n\n    @Override\n    public CloseableReference<Bitmap> process(Bitmap sourceBitmap, PlatformBitmapFactory bitmapFactory) {\n        try {\n            if (PerformanceSpInfoConfig.isLargeImgOpen()) {\n                double imgSize = ConvertUtils.byte2MemorySize(sourceBitmap.getByteCount(), MemoryConstants.MB);\n                LargePictureManager.getInstance().saveImageInfo(mUri.toString(), imgSize, sourceBitmap.getWidth(), sourceBitmap.getHeight(), \"Fresco\");\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        if (mOriginalPostprocessor != null) {\n            return mOriginalPostprocessor.process(sourceBitmap, bitmapFactory);\n        }\n\n        final Bitmap.Config sourceBitmapConfig = sourceBitmap.getConfig();\n        CloseableReference<Bitmap> destBitmapRef =\n                bitmapFactory.createBitmapInternal(\n                        sourceBitmap.getWidth(),\n                        sourceBitmap.getHeight(),\n                        sourceBitmapConfig != null ? sourceBitmapConfig : FALLBACK_BITMAP_CONFIGURATION);\n        try {\n            process(destBitmapRef.get(), sourceBitmap);\n            return CloseableReference.cloneOrNull(destBitmapRef);\n        } finally {\n            CloseableReference.closeSafely(destBitmapRef);\n        }\n\n    }\n\n    public void process(Bitmap destBitmap, Bitmap sourceBitmap) {\n        internalCopyBitmap(destBitmap, sourceBitmap);\n        process(destBitmap);\n    }\n\n    public void process(Bitmap bitmap) {\n    }\n\n    /**\n     * Copies the content of {@code sourceBitmap} to {@code destBitmap}. Both bitmaps must have the\n     * same width and height. If their {@link Bitmap.Config} are identical, the memory is directly\n     * copied. Otherwise, the {@code sourceBitmap} is drawn into {@code destBitmap}.\n     */\n    private static void internalCopyBitmap(Bitmap destBitmap, Bitmap sourceBitmap) {\n        if (destBitmap.getConfig() == sourceBitmap.getConfig()) {\n            Bitmaps.copyBitmap(destBitmap, sourceBitmap);\n        } else {\n            // The bitmap configurations might be different when the source bitmap's configuration is\n            // null, because it uses an internal configuration and the destination bitmap's configuration\n            // is the FALLBACK_BITMAP_CONFIGURATION. This is the case for static images for animated GIFs.\n            Canvas canvas = new Canvas(destBitmap);\n            canvas.drawBitmap(sourceBitmap, 0, 0, null);\n        }\n    }\n\n    @Override\n    public String getName() {\n        if (mOriginalPostprocessor != null) {\n            return mOriginalPostprocessor.getName();\n        }\n        return \"DoKit&Fresco&DokitPostprocessor\";\n    }\n\n    @Nullable\n    @Override\n    public CacheKey getPostprocessorCacheKey() {\n        if (mOriginalPostprocessor != null) {\n            return mOriginalPostprocessor.getPostprocessorCacheKey();\n        }\n        if (mCacheKey == null) {\n            mCacheKey = new SimpleCacheKey(\"DokitFrescoPostprocessor\");\n        }\n        return mCacheKey;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/bigimg/fresco/FrescoHook.java",
    "content": "package com.didichuxing.doraemonkit.aop.bigimg.fresco;\n\nimport android.net.Uri;\n\nimport com.facebook.imagepipeline.request.Postprocessor;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/23-16:57\n * 描    述：在com.facebook.imagepipeline.request.ImageRequest&ImageRequest()中注入\n * 修订历史：\n * ================================================\n */\npublic class FrescoHook {\n    public static Postprocessor proxy(Uri uri, Postprocessor postprocessor) {\n        return new DokitFrescoPostprocessor(uri, postprocessor);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/bigimg/glide/DokitGlideRequestListener.java",
    "content": "package com.didichuxing.doraemonkit.aop.bigimg.glide;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\n\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.constant.MemoryConstants;\nimport com.didichuxing.doraemonkit.util.ConvertUtils;\nimport com.didichuxing.doraemonkit.util.ImageUtils;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.target.Target;\nimport com.didichuxing.doraemonkit.config.PerformanceSpInfoConfig;\nimport com.didichuxing.doraemonkit.kit.largepicture.LargePictureManager;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/20-18:23\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class DokitGlideRequestListener<R> implements RequestListener<R> {\n    private static final String TAG = \"DokitGlideListener\";\n\n    @Override\n    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<R> target, boolean isFirstResource) {\n        return false;\n    }\n\n    @Override\n    public boolean onResourceReady(R resource, Object model, Target<R> target, DataSource dataSource, boolean isFirstResource) {\n        try {\n            if (PerformanceSpInfoConfig.isLargeImgOpen()) {\n                Bitmap bitmap;\n                if (resource instanceof Bitmap) {\n                    bitmap = (Bitmap) resource;\n                    double imgSize = ConvertUtils.byte2MemorySize(bitmap.getByteCount(), MemoryConstants.MB);\n                    LargePictureManager.getInstance().saveImageInfo(model.toString(), imgSize, bitmap.getWidth(), bitmap.getHeight(), \"Glide\");\n                } else if (resource instanceof BitmapDrawable) {\n                    bitmap = ImageUtils.drawable2Bitmap((BitmapDrawable) resource);\n                    double imgSize = ConvertUtils.byte2MemorySize(bitmap.getByteCount(), MemoryConstants.MB);\n                    LargePictureManager.getInstance().saveImageInfo(model.toString(), imgSize, bitmap.getWidth(), bitmap.getHeight(), \"Glide\");\n                }\n            }\n\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        return false;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/bigimg/glide/DokitGlideTransform.java",
    "content": "package com.didichuxing.doraemonkit.aop.bigimg.glide;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.constant.MemoryConstants;\nimport com.didichuxing.doraemonkit.util.ConvertUtils;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.didichuxing.doraemonkit.config.PerformanceSpInfoConfig;\nimport com.didichuxing.doraemonkit.kit.largepicture.LargePictureManager;\nimport com.didichuxing.doraemonkit.util.ReflectUtils;\n\nimport java.security.MessageDigest;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/3-13:04\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class DokitGlideTransform implements Transformation<Bitmap> {\n    private static final String ID = \"com.didichuxing.doraemonkit.aop.bigimg.glide.DokitGlideTransform\";\n    private static final byte[] ID_BYTES = ID.getBytes(CHARSET);\n    private static final String TAG = \"DokitGlideTransform\";\n    private Object mRequestBuilder;\n    private Transformation mWrap;\n\n    public DokitGlideTransform(Object mRequestBuilder, Object transformation) {\n        this.mRequestBuilder = mRequestBuilder;\n        if (transformation instanceof Transformation) {\n            this.mWrap = (Transformation) transformation;\n        }\n    }\n\n\n    @Override\n    public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n        if (mWrap != null) {\n            mWrap.updateDiskCacheKey(messageDigest);\n        } else {\n            messageDigest.update(ID_BYTES);\n        }\n    }\n\n\n    @Override\n    public boolean equals(Object o) {\n        if (mWrap != null) {\n            return mWrap.equals(o);\n        }\n        return false;\n    }\n\n    @Override\n    public int hashCode() {\n        if (mWrap != null) {\n            return mWrap.hashCode();\n        }\n        return 0;\n    }\n\n    @NonNull\n    @Override\n    public Resource<Bitmap> transform(@NonNull Context context, @NonNull Resource<Bitmap> resource, int outWidth, int outHeight) {\n        try {\n            if (mWrap != null) {\n                resource = mWrap.transform(context, resource, outWidth, outHeight);\n            }\n\n            if (PerformanceSpInfoConfig.isLargeImgOpen()) {\n                String url = \"\";\n                if (mRequestBuilder instanceof RequestBuilder) {\n                    if (ReflectUtils.reflect(mRequestBuilder).field(\"model\").get() instanceof String) {\n                        url = ReflectUtils.reflect(mRequestBuilder).field(\"model\").get();\n                    } else if (ReflectUtils.reflect(mRequestBuilder).field(\"model\").get() instanceof Integer) {\n                        url = \"\" + ReflectUtils.reflect(mRequestBuilder).field(\"model\").get();\n                    }\n                }\n                Bitmap bitmap = resource.get();\n                double imgSize = ConvertUtils.byte2MemorySize(bitmap.getByteCount(), MemoryConstants.MB);\n                LargePictureManager.getInstance().saveImageInfo(url, imgSize, bitmap.getWidth(), bitmap.getHeight(), \"Glide\");\n            }\n        } catch (Exception e) {\n            if (mWrap != null) {\n                resource = mWrap.transform(context, resource, outWidth, outHeight);\n            }\n        }\n\n        return resource;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/bigimg/glide/GlideHook.java",
    "content": "package com.didichuxing.doraemonkit.aop.bigimg.glide;\n\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.SingleRequest;\nimport com.didichuxing.doraemonkit.util.ReflectUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/20-18:19\n * 描    述：注入到com.bumptech.glide.request.SingleRequest#init(方法中)\n * 修订历史：\n * ================================================\n */\npublic class GlideHook {\n    /**\n     * hook requestListeners字段\n     *\n     * @param singleRequest\n     * @return\n     */\n\n    public static void proxy(Object singleRequest) {\n        try {\n            List<RequestListener> requestListeners = null;\n            if (singleRequest instanceof SingleRequest) {\n                requestListeners = ReflectUtils.reflect(singleRequest).field(\"requestListeners\").get();\n            }\n            //可能存在用户没有引入okhttp的情况\n            if (requestListeners == null) {\n                requestListeners = new ArrayList<>();\n                requestListeners.add(new DokitGlideRequestListener());\n            } else {\n                requestListeners.add(new DokitGlideRequestListener());\n            }\n            if (singleRequest instanceof SingleRequest) {\n                ReflectUtils.reflect(singleRequest).field(\"requestListeners\",requestListeners);\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/bigimg/glide/GlideTransformHook.java",
    "content": "package com.didichuxing.doraemonkit.aop.bigimg.glide;\n\nimport android.graphics.Bitmap;\n\nimport com.bumptech.glide.load.Transformation;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/3-13:03\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class GlideTransformHook {\n    public static Transformation<Bitmap> transform(Object baseRequestOptions, Object transformation) {\n        return new DokitGlideTransform(baseRequestOptions, transformation);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/bigimg/imageloader/DokitImageLoadingListener.java",
    "content": "package com.didichuxing.doraemonkit.aop.bigimg.imageloader;\n\nimport android.graphics.Bitmap;\n\nimport androidx.annotation.Nullable;\n\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.constant.MemoryConstants;\nimport com.didichuxing.doraemonkit.util.ConvertUtils;\nimport com.didichuxing.doraemonkit.config.PerformanceSpInfoConfig;\nimport com.didichuxing.doraemonkit.kit.largepicture.LargePictureManager;\nimport com.nostra13.universalimageloader.core.assist.FailReason;\nimport com.nostra13.universalimageloader.core.listener.ImageLoadingListener;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/23-15:25\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class DokitImageLoadingListener implements ImageLoadingListener {\n    private static final String TAG = \"DokitImageLoadingListener\";\n    /**\n     * 原始的ImageLoadingListener\n     */\n    @Nullable\n    private ImageLoadingListener mOriginalImageLoadingListener;\n\n    public DokitImageLoadingListener(ImageLoadingListener imageLoadingListener) {\n        this.mOriginalImageLoadingListener = imageLoadingListener;\n    }\n\n    @Override\n    public void onLoadingStarted(String imageUri, View view) {\n        if (mOriginalImageLoadingListener != null) {\n            mOriginalImageLoadingListener.onLoadingStarted(imageUri, view);\n        }\n    }\n\n    @Override\n    public void onLoadingFailed(String imageUri, View view, FailReason failReason) {\n        if (mOriginalImageLoadingListener != null) {\n            mOriginalImageLoadingListener.onLoadingFailed(imageUri, view, failReason);\n        }\n    }\n\n    @Override\n    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {\n        try {\n            if (PerformanceSpInfoConfig.isLargeImgOpen()) {\n                double imgSize = ConvertUtils.byte2MemorySize(loadedImage.getByteCount(), MemoryConstants.MB);\n                LargePictureManager.getInstance().saveImageInfo(imageUri, imgSize, loadedImage.getWidth(), loadedImage.getHeight(), \"ImageLoader\");\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        if (mOriginalImageLoadingListener != null) {\n            mOriginalImageLoadingListener.onLoadingComplete(imageUri, view, loadedImage);\n        }\n    }\n\n    @Override\n    public void onLoadingCancelled(String imageUri, View view) {\n        if (mOriginalImageLoadingListener != null) {\n            mOriginalImageLoadingListener.onLoadingCancelled(imageUri, view);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/bigimg/imageloader/ImageLoaderHook.java",
    "content": "package com.didichuxing.doraemonkit.aop.bigimg.imageloader;\n\nimport com.nostra13.universalimageloader.core.listener.ImageLoadingListener;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/23-17:20\n * 描    述：注入到com.nostra13.universalimageloader.core&ImageLoadingInfo的构造方法中\n * 修订历史：\n * ================================================\n */\npublic class ImageLoaderHook {\n\n    public static ImageLoadingListener proxy(ImageLoadingListener imageLoadingListener) {\n        return new DokitImageLoadingListener(imageLoadingListener);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/bigimg/picasso/DokitPicassoTransformation.java",
    "content": "package com.didichuxing.doraemonkit.aop.bigimg.picasso;\n\nimport android.graphics.Bitmap;\nimport android.net.Uri;\n\nimport com.didichuxing.doraemonkit.constant.MemoryConstants;\nimport com.didichuxing.doraemonkit.util.ConvertUtils;\nimport com.didichuxing.doraemonkit.config.PerformanceSpInfoConfig;\nimport com.didichuxing.doraemonkit.kit.largepicture.LargePictureManager;\nimport com.squareup.picasso.Transformation;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/23-13:54\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class DokitPicassoTransformation implements Transformation {\n    private static final String TAG = \"DokitTransformation\";\n    private Uri mUri;\n    private int mResourceId;\n\n    public DokitPicassoTransformation(Uri uri, int resourceId) {\n        this.mUri = uri;\n        this.mResourceId = resourceId;\n    }\n\n    @Override\n    public Bitmap transform(Bitmap source) {\n        try {\n            if (PerformanceSpInfoConfig.isLargeImgOpen()) {\n                if (mUri != null) {\n                    double imgSize = ConvertUtils.byte2MemorySize(source.getByteCount(), MemoryConstants.MB);\n                    LargePictureManager.getInstance().saveImageInfo(mUri.toString(), imgSize, source.getWidth(), source.getHeight(), \"Picasso\");\n                } else {\n                    double imgSize = ConvertUtils.byte2MemorySize(source.getByteCount(), MemoryConstants.MB);\n                    LargePictureManager.getInstance().saveImageInfo(\"\" + mResourceId, imgSize, source.getWidth(), source.getHeight(), \"Picasso\");\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return source;\n    }\n\n    @Override\n    public String key() {\n        return \"Dokit&Picasso&LargeBitmapTransformation\";\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/bigimg/picasso/PicassoHook.java",
    "content": "package com.didichuxing.doraemonkit.aop.bigimg.picasso;\n\nimport com.didichuxing.doraemonkit.util.ReflectUtils;\nimport com.squareup.picasso.Request;\nimport com.squareup.picasso.Transformation;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/23-13:45\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class PicassoHook {\n\n    /**\n     * 注入到com.squareup.picasso.Request 构造方法中\n     */\n    public static void proxy(Object request) {\n        try {\n            if (request instanceof Request) {\n                Request requestObj = (Request) request;\n                List<Transformation> transformations = requestObj.transformations;\n                if (transformations == null) {\n                    transformations = new ArrayList<>();\n                    transformations.add(new DokitPicassoTransformation(requestObj.uri, requestObj.resourceId));\n                } else {\n                    transformations.clear();\n                    transformations.add(new DokitPicassoTransformation(requestObj.uri, requestObj.resourceId));\n                }\n                ReflectUtils.reflect(request).field(\"transformations\", transformations);\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/mc/DoKitListenerHelper.kt",
    "content": "package com.didichuxing.doraemonkit.aop.mc\n\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/11/26-10:57\n * 描    述：\n * 修订历史：\n * ================================================\n */\n\n\npublic object DoKitListenerHelper {\n\n\n}\n\n\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/mc/DoKitProxyActivity.kt",
    "content": "package com.didichuxing.doraemonkit.aop.mc\n\nimport android.app.Activity\nimport android.content.res.Configuration\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.KeyEvent\nimport android.view.MotionEvent\nimport androidx.annotation.RequiresApi\nimport com.didichuxing.doraemonkit.kit.core.DoKitServiceEnum\nimport com.didichuxing.doraemonkit.kit.core.DoKitServiceManager\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/11/12-16:02\n * 描    述：用于拦截Activity中的所有事件\n * 修订历史：\n * ================================================\n */\npublic open class DoKitProxyActivity : Activity() {\n\n    companion object {\n        private const val TAG = \"DoKitProxyActivity\"\n    }\n\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        DoKitServiceManager.dispatch(DoKitServiceEnum.onCreate, this)\n    }\n\n\n    override fun onStart() {\n        super.onStart()\n        DoKitServiceManager.dispatch(DoKitServiceEnum.onStart, this)\n\n    }\n\n    override fun onResume() {\n        super.onResume()\n        DoKitServiceManager.dispatch(DoKitServiceEnum.onResume, this)\n\n    }\n\n\n    @RequiresApi(Build.VERSION_CODES.M)\n    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {\n        DoKitServiceManager.dispatch(DoKitServiceEnum.dispatchTouchEvent, this)\n        return super.dispatchTouchEvent(ev)\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        super.onConfigurationChanged(newConfig)\n        DoKitServiceManager.dispatch(\n            DoKitServiceEnum.onConfigurationChanged,\n            this\n        )\n    }\n\n\n//    override fun onBackPressed() {\n//        super.onBackPressed()\n//        DokitServiceManager.dispatch(DokitServiceEnum.onBackPressed, this)\n//    }\n\n    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {\n        if (keyCode == KeyEvent.KEYCODE_BACK) {\n            if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.ECLAIR) {\n                DoKitServiceManager.dispatch(DoKitServiceEnum.onBackPressed, this)\n            }\n        }\n\n        return super.onKeyDown(keyCode, event)\n    }\n\n    override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {\n\n        if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.ECLAIR) {\n            if (keyCode == KeyEvent.KEYCODE_BACK && event!!.isTracking && !event.isCanceled) {\n                DoKitServiceManager.dispatch(DoKitServiceEnum.onBackPressed, this)\n            }\n        }\n        return super.onKeyUp(keyCode, event)\n    }\n\n    override fun onPause() {\n        super.onPause()\n        DoKitServiceManager.dispatch(DoKitServiceEnum.onPause, this)\n\n    }\n\n    override fun onStop() {\n        super.onStop()\n        DoKitServiceManager.dispatch(DoKitServiceEnum.onStop, this)\n\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        DoKitServiceManager.dispatch(DoKitServiceEnum.onDestroy, this)\n    }\n\n    override fun finish() {\n        super.finish()\n        DoKitServiceManager.dispatch(DoKitServiceEnum.finish, this)\n    }\n\n    override fun onAttachedToWindow() {\n        super.onAttachedToWindow()\n        //LogHelper.i(TAG, \"===onAttachedToWindow===\")\n\n    }\n\n    //真正可见\n    override fun onWindowFocusChanged(hasFocus: Boolean) {\n        super.onWindowFocusChanged(hasFocus)\n    }\n\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/method_stack/MethodInvokNode.kt",
    "content": "package com.didichuxing.doraemonkit.aop.method_stack\n\nimport java.util.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/23-11:21\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass MethodInvokNode {\n    var parent: MethodInvokNode? = null\n    var startTimeMillis: Long = 0\n    private var endTimeMillis: Long = 0\n    private var costTimeMillis = 0\n    var currentThreadName: String? = null\n    var className: String? = null\n    var methodName: String? = null\n    var level = 0\n    var children: MutableList<MethodInvokNode> = mutableListOf()\n\n    fun getEndTimeMillis(): Long {\n        return endTimeMillis\n    }\n\n    fun setEndTimeMillis(endTimeMillis: Long) {\n        this.endTimeMillis = endTimeMillis\n        costTimeMillis = (endTimeMillis - startTimeMillis).toInt()\n    }\n\n    fun getCostTimeMillis(): Int {\n        return (endTimeMillis - startTimeMillis).toInt()\n    }\n\n    fun addChild(methodInvokNode: MethodInvokNode) {\n        children.add(methodInvokNode)\n    }\n\n    fun setCostTimeMillis(costTimeMillis: Int) {\n        this.costTimeMillis = costTimeMillis\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/method_stack/MethodStackBean.kt",
    "content": "package com.didichuxing.doraemonkit.aop.method_stack\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/23-17:42\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass MethodStackBean {\n    var function: String? = null\n    var costTime: String? = null\n    var children: MutableList<MethodStackBean>? = null\n\n    fun setCostTime(costTime: Int) {\n        this.costTime = costTime.toString() + \"ms\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil.kt",
    "content": "package com.didichuxing.doraemonkit.aop.method_stack\n\nimport android.app.Application\nimport android.util.Log\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport com.didichuxing.doraemonkit.util.LogUtils\nimport com.didichuxing.doraemonkit.aop.MethodCostUtil\nimport com.didichuxing.doraemonkit.kit.timecounter.TimeCounterManager\nimport java.util.*\nimport java.util.concurrent.ConcurrentHashMap\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/22-15:44\n * 描    述：自定义函数入口调用栈的耗时工具类\n * 修订历史：\n * ================================================\n */\npublic object MethodStackUtil {\n    /**\n     * key className&methodName\n     */\n    private val METHOD_STACKS: MutableList<ConcurrentHashMap<String, MethodInvokNode>> by lazy {\n        Collections.synchronizedList(mutableListOf<ConcurrentHashMap<String, MethodInvokNode>>())\n    }\n\n    /**\n     * 用来标识是静态函数对象\n     */\n    private val staticMethodObject: StaticMethodObject by lazy {\n        StaticMethodObject()\n    }\n\n\n    private fun createMethodStackList(totalLevel: Int) {\n        if (METHOD_STACKS.size == totalLevel) {\n            return\n        }\n        METHOD_STACKS.clear()\n        for (index in 0 until totalLevel) {\n            METHOD_STACKS.add(index, ConcurrentHashMap())\n        }\n    }\n\n    /**\n     * @param currentLevel\n     * @param methodName\n     * @param classObj   null 代表静态函数\n     */\n    fun recodeObjectMethodCostStart(\n        totalLevel: Int,\n        thresholdTime: Int,\n        currentLevel: Int,\n        className: String?,\n        methodName: String,\n        desc: String?,\n        classObj: Any?\n    ) {\n        try {\n            //先创建队列\n            createMethodStackList(totalLevel)\n            val methodInvokNode = MethodInvokNode()\n            methodInvokNode.startTimeMillis = System.currentTimeMillis()\n            methodInvokNode.currentThreadName = Thread.currentThread().name\n            methodInvokNode.className = className\n            methodInvokNode.methodName = methodName\n            methodInvokNode.level = currentLevel\n            METHOD_STACKS[currentLevel][String.format(\"%s&%s\", className, methodName)] =\n                methodInvokNode\n\n            //特殊判定\n            if (currentLevel == 0) {\n                if (classObj is Application) {\n                    if (methodName == \"onCreate\") {\n                        TimeCounterManager.get().onAppCreateStart()\n                    }\n                    if (methodName == \"attachBaseContext\") {\n                        TimeCounterManager.get().onAppAttachBaseContextStart()\n                    }\n                }\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    /**\n     * @param currentLevel\n     * @param className\n     * @param methodName\n     * @param desc\n     * @param classObj   null 代表静态函数\n     */\n    fun recodeObjectMethodCostEnd(\n        thresholdTime: Int,\n        currentLevel: Int,\n        className: String,\n        methodName: String,\n        desc: String?,\n        classObj: Any?\n    ) {\n        synchronized(MethodStackUtil::class.java) {\n            try {\n                val methodInvokNode =\n                    METHOD_STACKS[currentLevel][String.format(\"%s&%s\", className, methodName)]\n                if (methodInvokNode != null) {\n                    methodInvokNode.setEndTimeMillis(System.currentTimeMillis())\n                    bindNode(thresholdTime, currentLevel, methodInvokNode)\n                }\n\n                //打印函数调用栈\n                if (currentLevel == 0) {\n                    if (methodInvokNode != null) {\n                        toStack(classObj is Application, methodInvokNode)\n                    }\n                    if (classObj is Application) {\n                        //Application 启动时间统计\n                        if (methodName == \"onCreate\") {\n                            TimeCounterManager.get().onAppCreateEnd()\n                        }\n                        if (methodName == \"attachBaseContext\") {\n                            TimeCounterManager.get().onAppAttachBaseContextEnd()\n                        }\n                    }\n\n                    //移除对象\n                    METHOD_STACKS[0].remove(\"$className&$methodName\")\n                }\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    private fun getParentMethod(currentClassName: String?, currentMethodName: String?): String {\n        val stackTraceElements = Thread.currentThread().stackTrace\n        var index = 0\n        for (i in stackTraceElements.indices) {\n            val stackTraceElement = stackTraceElements[i]\n            if (currentClassName == stackTraceElement.className && currentMethodName == stackTraceElement.methodName) {\n                index = i\n                break\n            }\n        }\n        val parentStackTraceElement = stackTraceElements[index + 1]\n        return String.format(\n            \"%s&%s\",\n            parentStackTraceElement.className,\n            parentStackTraceElement.methodName\n        )\n    }\n\n    private fun bindNode(thresholdTime: Int, currentLevel: Int, methodInvokNode: MethodInvokNode?) {\n        if (methodInvokNode == null) {\n            return\n        }\n\n        //过滤掉小于指定阈值的函数\n        if (methodInvokNode.getCostTimeMillis() <= thresholdTime) {\n            return\n        }\n        if (currentLevel >= 1) {\n            val parentMethodNode = METHOD_STACKS[currentLevel - 1][getParentMethod(\n                methodInvokNode.className,\n                methodInvokNode.methodName\n            )]\n            if (parentMethodNode != null) {\n                methodInvokNode.parent = parentMethodNode\n                parentMethodNode.addChild(methodInvokNode)\n            }\n        }\n    }\n\n    fun recodeStaticMethodCostStart(\n        totalLevel: Int,\n        thresholdTime: Int,\n        currentLevel: Int,\n        className: String?,\n        methodName: String,\n        desc: String?\n    ) {\n        recodeObjectMethodCostStart(\n            totalLevel,\n            thresholdTime,\n            currentLevel,\n            className,\n            methodName,\n            desc,\n            staticMethodObject\n        )\n    }\n\n    fun recodeStaticMethodCostEnd(\n        thresholdTime: Int,\n        currentLevel: Int,\n        className: String,\n        methodName: String,\n        desc: String?\n    ) {\n        recodeObjectMethodCostEnd(\n            thresholdTime,\n            currentLevel,\n            className,\n            methodName,\n            desc,\n            staticMethodObject\n        )\n    }\n\n    private fun jsonTravel(\n        methodStackBeans: MutableList<MethodStackBean>?,\n        methodInvokNodes: List<MethodInvokNode>?\n    ) {\n        if (methodInvokNodes == null) {\n            return\n        }\n        for (methodInvokNode in methodInvokNodes) {\n            val methodStackBean = MethodStackBean()\n            methodStackBean.setCostTime(methodInvokNode.getCostTimeMillis())\n            methodStackBean.function = methodInvokNode.className + \"&\" + methodInvokNode.methodName\n            methodStackBean.children = ArrayList()\n            jsonTravel(methodStackBean.children, methodInvokNode.children)\n            methodStackBeans?.add(methodStackBean)\n        }\n    }\n\n    private fun stackTravel(\n        stringBuilder: StringBuilder,\n        methodInvokNodes: List<MethodInvokNode>?\n    ) {\n        if (methodInvokNodes == null) {\n            return\n        }\n        for (methodInvokNode in methodInvokNodes) {\n            stringBuilder.append(\n                String.format(\n                    \"%s%s%s%s%s\",\n                    methodInvokNode.level,\n                    SPACE_0,\n                    methodInvokNode.getCostTimeMillis().toString() + \"ms\",\n                    getSpaceString(methodInvokNode.level),\n                    methodInvokNode.className + \"&\" + methodInvokNode.methodName\n                )\n            ).append(\"\\n\")\n            stackTravel(stringBuilder, methodInvokNode.children)\n        }\n    }\n\n    fun toJson() {\n        val methodStackBeans: MutableList<MethodStackBean> = ArrayList()\n        for (methodInvokNode in METHOD_STACKS[0].values) {\n            val methodStackBean = MethodStackBean()\n            methodStackBean.setCostTime(methodInvokNode.getCostTimeMillis())\n            methodStackBean.function = methodInvokNode.className + \"&\" + methodInvokNode.methodName\n            methodStackBean.children = ArrayList()\n            jsonTravel(methodStackBean.children, methodInvokNode.children)\n            methodStackBeans.add(methodStackBean)\n        }\n        val json = GsonUtils.toJson(methodStackBeans)\n        LogUtils.json(json)\n    }\n\n    fun toStack(isAppStart: Boolean, methodInvokNode: MethodInvokNode) {\n        val stringBuilder = StringBuilder()\n        stringBuilder.append(\"=========DoKit函数调用栈==========\").append(\"\\n\")\n        stringBuilder.append(String.format(\"%s    %s    %s\", \"level\", \"time\", \"function\"))\n            .append(\"\\n\")\n        stringBuilder.append(\n            String.format(\n                \"%s%s%s%s%s\",\n                methodInvokNode.level,\n                SPACE_0,\n                methodInvokNode.getCostTimeMillis().toString() + \"ms\",\n                getSpaceString(methodInvokNode.level),\n                methodInvokNode.className + \"&\" + methodInvokNode.methodName\n            )\n        ).append(\"\\n\")\n        stackTravel(stringBuilder, methodInvokNode.children)\n        Log.i(TAG, stringBuilder.toString())\n        if (isAppStart && methodInvokNode.level == 0) {\n            if (methodInvokNode.methodName == \"onCreate\") {\n                STR_APP_ON_CREATE = stringBuilder.toString()\n            }\n            if (methodInvokNode.methodName == \"attachBaseContext\") {\n                STR_APP_ATTACH_BASECONTEXT = stringBuilder.toString()\n            }\n        }\n    }\n\n    private fun getSpaceString(level: Int): String {\n        return when (level) {\n            0 -> SPACE_0\n            1 -> SPACE_1\n            2 -> SPACE_2\n            3 -> SPACE_3\n            4 -> SPACE_4\n            5 -> SPACE_5\n            6 -> SPACE_6\n            7 -> SPACE_7\n            else -> SPACE_0\n        }\n\n    }\n\n    private const val TAG = \"DOKIT_SLOW_METHOD\"\n\n\n    private const val SPACE_0 = \"********\"\n    private const val SPACE_1 = \"*************\"\n    private const val SPACE_2 = \"*****************\"\n    private const val SPACE_3 = \"*********************\"\n    private const val SPACE_4 = \"*************************\"\n    private const val SPACE_5 = \"*****************************\"\n    private const val SPACE_6 = \"*********************************\"\n    private const val SPACE_7 = \"*************************************\"\n\n    @JvmField\n    var STR_APP_ON_CREATE: String? = null\n\n    @JvmField\n    var STR_APP_ATTACH_BASECONTEXT: String? = null\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/method_stack/StaticMethodObject.kt",
    "content": "package com.didichuxing.doraemonkit.aop.method_stack\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/28-18:08\n * 描    述：用来判断是否是静态方法的 不需要实现\n * 修订历史：\n * ================================================\n */\nclass StaticMethodObject"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/urlconnection/HttpUrlConnectionProxyUtil.java",
    "content": "package com.didichuxing.doraemonkit.aop.urlconnection;\n\nimport android.net.Uri;\n\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitCapInterceptor;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitExtInterceptor;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitWeakNetworkInterceptor;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitLargePicInterceptor;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitMockInterceptor;\n\nimport java.net.URL;\nimport java.net.URLConnection;\n\nimport okhttp3.HttpUrl;\nimport okhttp3.Interceptor;\nimport okhttp3.OkHttpClient;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-16-14:54\n * 描    述：ams 动态插入代码\n * 修订历史：\n * ================================================\n */\npublic class HttpUrlConnectionProxyUtil {\n    //private static final String TAG = \"HttpUrlConnectionProxyUtil\";\n    private static String[] hosts = new String[]{\"amap.com\"};\n\n    public static URLConnection proxy(URLConnection urlConnection) {\n        try {\n            String host = HttpUrl.parse(urlConnection.getURL().toString()).host();\n            if (isIgnore(host)) {\n                return urlConnection;\n            }\n            return createOkHttpURLConnection(urlConnection);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return urlConnection;\n    }\n\n\n    private static URLConnection createOkHttpURLConnection(URLConnection urlConnection) throws Exception {\n\n//        OkHttpClient.Builder builder = new OkHttpClient.Builder();\n//        //不需要再重复添加拦截器 因为已经通过字节码主如果拦截器了\n//        //addInterceptor(builder);\n//        OkHttpClient mClient = builder\n//                .retryOnConnectionFailure(true)\n//                .readTimeout(DokitOkGo.DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS)\n//                .writeTimeout(DokitOkGo.DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS)\n//                .connectTimeout(DokitOkGo.DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS)\n//                .build();\n\n        //对url进行encode\n        String strUrl = encodeUrl(urlConnection.getURL().toString());\n        //Log.i(\"decode\", decodeUrl(strUrl));\n        URL url = new URL(strUrl);\n        String protocol = url.getProtocol().toLowerCase();\n        if (protocol.equalsIgnoreCase(\"http\")) {\n            return new ObsoleteUrlFactory.OkHttpURLConnection(url, OkhttpClientUtil.INSTANCE.getOkhttpClient());\n        }\n\n        if (protocol.equalsIgnoreCase(\"https\")) {\n            return new ObsoleteUrlFactory.OkHttpsURLConnection(url, OkhttpClientUtil.INSTANCE.getOkhttpClient());\n        }\n\n        return urlConnection;\n\n    }\n\n    public static String encodeUrl(String url) {\n        return Uri.encode(url, \"-![.:/,%?&=]\");\n    }\n\n    public static String decodeUrl(String url) {\n        return Uri.decode(url);\n    }\n\n    private static void addInterceptor(OkHttpClient.Builder builder) {\n        // 判断当前是否已经添加了拦截器，如果已添加则返回\n        for (Interceptor interceptor : builder.interceptors()) {\n            if (interceptor instanceof DokitMockInterceptor) {\n                return;\n            }\n        }\n\n        builder\n                //添加mock拦截器\n                .addInterceptor(new DokitMockInterceptor())\n                //添加大图检测拦截器\n                .addInterceptor(new DokitLargePicInterceptor())\n                //添加dokit拦截器\n                .addInterceptor(new DokitCapInterceptor())\n                //添加弱网 拦截器\n                .addNetworkInterceptor(new DokitWeakNetworkInterceptor())\n                // 添加扩展拦截器\n                .addInterceptor(new DokitExtInterceptor());\n    }\n\n    /**\n     * 判断是否过滤指定的host\n     *\n     * @param host\n     * @return\n     */\n    private static boolean isIgnore(String host) {\n        for (String jumpHost : hosts) {\n            if (host.contains(jumpHost)) {\n                return true;\n            }\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/urlconnection/MyTrustManager.java",
    "content": "package com.didichuxing.doraemonkit.aop.urlconnection;\n\nimport java.lang.reflect.Field;\nimport java.security.SecureRandom;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\n\nimport javax.annotation.Nullable;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocketFactory;\nimport javax.net.ssl.X509TrustManager;\n\n/**\n * didi Create on 2022/5/24 .\n * <p>\n * Copyright (c) 2022/5/24 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/5/24 5:34 下午\n * @Description 用一句话说明文件功能\n */\n\npublic class MyTrustManager {\n\n\n    private static final X509TrustManager trustManager = new X509TrustManager() {\n        @Override\n        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n\n        }\n\n        @Override\n        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n\n        }\n\n        @Override\n        public X509Certificate[] getAcceptedIssuers() {\n            return new X509Certificate[0];\n        }\n    };\n\n    public X509TrustManager getTrustManager() {\n        return trustManager;\n    }\n\n    public X509TrustManager buildTrustManager() {\n        SSLContext sslContext = null;\n        try {\n            sslContext = SSLContext.getInstance(\"TLS\");\n            sslContext.init(null, new X509TrustManager[]{trustManager}, new SecureRandom());\n            SSLSocketFactory ssl = sslContext.getSocketFactory();\n            return trustManager(ssl);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n\n    private X509TrustManager trustManager2(SSLSocketFactory sslSocketFactory) {\n        // Attempt to get the trust manager from an OpenJDK socket factory. We attempt this on all\n        // platforms in order to support Robolectric, which mixes classes from both Android and the\n        // Oracle JDK. Note that we don't support HTTP/2 or other nice features on Robolectric.\n        try {\n            Class<?> sslContextClass = Class.forName(\"sun.security.ssl.SSLContextImpl\");\n            Object context = readFieldOrNull(sslSocketFactory, sslContextClass, \"context\");\n            if (context == null) return null;\n            return readFieldOrNull(context, X509TrustManager.class, \"trustManager\");\n        } catch (ClassNotFoundException e) {\n            return null;\n        }\n    }\n\n    private X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) throws Exception {\n\n        Class sslParametersClass = Class.forName(\"com.android.org.conscrypt.SSLParametersImpl\");\n        Class sslSocketClass = Class.forName(\"com.android.org.conscrypt.OpenSSLSocketImpl\");\n\n        Object context = readFieldOrNull(sslSocketFactory, sslParametersClass, \"sslParameters\");\n        if (context == null) {\n            // If that didn't work, try the Google Play Services SSL provider before giving up. This\n            // must be loaded by the SSLSocketFactory's class loader.\n            try {\n                Class<?> gmsSslParametersClass = Class.forName(\n                    \"com.google.android.gms.org.conscrypt.SSLParametersImpl\", false,\n                    sslSocketFactory.getClass().getClassLoader());\n                context = readFieldOrNull(sslSocketFactory, gmsSslParametersClass, \"sslParameters\");\n            } catch (ClassNotFoundException e) {\n                return trustManager2(sslSocketFactory);\n            }\n        }\n\n        X509TrustManager x509TrustManager = readFieldOrNull(\n            context, X509TrustManager.class, \"x509TrustManager\");\n        if (x509TrustManager != null) return x509TrustManager;\n\n        return readFieldOrNull(context, X509TrustManager.class, \"trustManager\");\n    }\n\n    static @Nullable\n    <T> T readFieldOrNull(Object instance, Class<T> fieldType, String fieldName) {\n        for (Class<?> c = instance.getClass(); c != Object.class; c = c.getSuperclass()) {\n            try {\n                Field field = c.getDeclaredField(fieldName);\n                field.setAccessible(true);\n                Object value = field.get(instance);\n                if (!fieldType.isInstance(value)) return null;\n                return fieldType.cast(value);\n            } catch (NoSuchFieldException ignored) {\n            } catch (IllegalAccessException e) {\n                throw new AssertionError();\n            }\n        }\n\n        // Didn't find the field we wanted. As a last gasp attempt, try to find the value on a delegate.\n        if (!fieldName.equals(\"delegate\")) {\n            Object delegate = readFieldOrNull(instance, Object.class, \"delegate\");\n            if (delegate != null) return readFieldOrNull(delegate, fieldType, fieldName);\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/urlconnection/ObsoleteUrlFactory.java",
    "content": "package com.didichuxing.doraemonkit.aop.urlconnection;\n\nimport android.os.Build;\nimport android.util.Log;\n\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\n\nimport com.didichuxing.doraemonkit.util.ConvertUtils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InterruptedIOException;\nimport java.io.OutputStream;\nimport java.net.HttpURLConnection;\nimport java.net.InetSocketAddress;\nimport java.net.MalformedURLException;\nimport java.net.ProtocolException;\nimport java.net.Proxy;\nimport java.net.SocketPermission;\nimport java.net.SocketTimeoutException;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.net.URLStreamHandler;\nimport java.net.URLStreamHandlerFactory;\nimport java.security.AccessControlException;\nimport java.security.Permission;\nimport java.security.Principal;\nimport java.security.cert.Certificate;\nimport java.text.DateFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Date;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport java.util.TreeMap;\nimport java.util.concurrent.TimeUnit;\n\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.HttpsURLConnection;\nimport javax.net.ssl.SSLSocketFactory;\nimport javax.net.ssl.X509TrustManager;\n\nimport okhttp3.Call;\nimport okhttp3.Callback;\nimport okhttp3.Dispatcher;\nimport okhttp3.Handshake;\nimport okhttp3.Headers;\nimport okhttp3.HttpUrl;\nimport okhttp3.Interceptor;\nimport okhttp3.MediaType;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Protocol;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport okhttp3.Response;\nimport okio.Buffer;\nimport okio.BufferedSink;\nimport okio.Okio;\nimport okio.Pipe;\nimport okio.Timeout;\n\nimport static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;\nimport static java.net.HttpURLConnection.HTTP_NO_CONTENT;\n\n/**\n * OkHttp 3.14 dropped support for the long-deprecated OkUrlFactory class, which allows you to use\n * the HttpURLConnection API with OkHttp's implementation. This class does the same thing using only\n * public APIs in OkHttp. It requires OkHttp 3.14 or newer.\n *\n * <p>Rather than pasting this 1100 line gist into your source code, please upgrade to OkHttp's\n * request/response API. Your code will be shorter, easier to read, and you'll be able to use\n * interceptors.\n */\nfinal class ObsoleteUrlFactory implements URLStreamHandlerFactory, Cloneable {\n    static final String SELECTED_PROTOCOL = \"ObsoleteUrlFactory-Selected-Protocol\";\n\n    static final String RESPONSE_SOURCE = \"ObsoleteUrlFactory-Response-Source\";\n\n    static final Set<String> METHODS = new LinkedHashSet<>(\n            Arrays.asList(\"OPTIONS\", \"GET\", \"HEAD\", \"POST\", \"PUT\", \"DELETE\", \"TRACE\", \"PATCH\"));\n\n    static final TimeZone UTC = TimeZone.getTimeZone(\"GMT\");\n\n    static final int HTTP_CONTINUE = 100;\n\n\n    private static final ThreadLocal<DateFormat> STANDARD_DATE_FORMAT = new ThreadLocal<DateFormat>() {\n        @Override\n        protected DateFormat initialValue() {\n            DateFormat rfc1123 = new SimpleDateFormat(\"EEE, dd MMM yyyy HH:mm:ss 'GMT'\", Locale.US);\n            rfc1123.setLenient(false);\n            rfc1123.setTimeZone(UTC);\n            return rfc1123;\n        }\n    };\n\n    private static final Comparator<String> FIELD_NAME_COMPARATOR = new Comparator<String>() {\n        @Override\n        public int compare(String a, String b) {\n\n            if (a == null) {\n                return -1;\n            } else if (b == null) {\n                return 1;\n            } else if (a.equals(b)) {\n                return 0;\n            } else {\n                return String.CASE_INSENSITIVE_ORDER.compare(a, b);\n            }\n        }\n    };\n\n\n    private OkHttpClient client;\n\n    private ObsoleteUrlFactory(OkHttpClient client) {\n        this.client = client;\n    }\n\n    public OkHttpClient client() {\n        return client;\n    }\n\n    public ObsoleteUrlFactory setClient(OkHttpClient client) {\n        this.client = client;\n        return this;\n    }\n\n    /**\n     * Returns a copy of this stream handler factory that includes a shallow copy of the internal\n     * {@linkplain OkHttpClient HTTP client}.\n     */\n    @Override\n    public ObsoleteUrlFactory clone() {\n        return new ObsoleteUrlFactory(client);\n    }\n\n    public HttpURLConnection open(URL url) {\n        return open(url, client.proxy());\n    }\n\n    HttpURLConnection open(URL url, @Nullable Proxy proxy) {\n        String protocol = url.getProtocol();\n        OkHttpClient copy = client.newBuilder()\n                .proxy(proxy)\n                .build();\n\n        if (protocol.equals(\"http\")) return new OkHttpURLConnection(url, copy);\n        if (protocol.equals(\"https\")) return new OkHttpsURLConnection(url, copy);\n        throw new IllegalArgumentException(\"Unexpected protocol: \" + protocol);\n    }\n\n    /**\n     * Creates a URLStreamHandler as a {@link URL#setURLStreamHandlerFactory}.\n     *\n     * <p>This code configures OkHttp to handle all HTTP and HTTPS connections\n     * created with {@link URL#openConnection()}: <pre>   {@code\n     *\n     *   OkHttpClient okHttpClient = new OkHttpClient();\n     *   URL.setURLStreamHandlerFactory(new ObsoleteUrlFactory(okHttpClient));\n     * }</pre>\n     */\n    @Override\n    public URLStreamHandler createURLStreamHandler(final String protocol) {\n        if (!protocol.equals(\"http\") && !protocol.equals(\"https\")) return null;\n\n        return new URLStreamHandler() {\n            @Override\n            protected URLConnection openConnection(URL url) {\n                return open(url);\n            }\n\n            @Override\n            protected URLConnection openConnection(URL url, Proxy proxy) {\n                return open(url, proxy);\n            }\n\n            @Override\n            protected int getDefaultPort() {\n                if (protocol.equals(\"http\")) return 80;\n                if (protocol.equals(\"https\")) return 443;\n                throw new AssertionError();\n            }\n        };\n    }\n\n    static String format(Date value) {\n        return STANDARD_DATE_FORMAT.get().format(value);\n    }\n\n    static boolean permitsRequestBody(String method) {\n        return !(method.equals(\"GET\") || method.equals(\"HEAD\"));\n    }\n\n    /**\n     * Returns true if the response must have a (possibly 0-length) body. See RFC 7231.\n     */\n    static boolean hasBody(Response response) {\n        // HEAD requests never yield a body regardless of the response headers.\n        if (response.request().method().equals(\"HEAD\")) {\n            return false;\n        }\n\n        int responseCode = response.code();\n        if ((responseCode < HTTP_CONTINUE || responseCode >= 200)\n                && responseCode != HTTP_NO_CONTENT\n                && responseCode != HTTP_NOT_MODIFIED) {\n            return true;\n        }\n\n        // If the Content-Length or Transfer-Encoding headers disagree with the response code, the\n        // response is malformed. For best compatibility, we honor the headers.\n        if (contentLength(response.headers()) != -1\n                || \"chunked\".equalsIgnoreCase(response.header(\"Transfer-Encoding\"))) {\n            return true;\n        }\n\n        return false;\n    }\n\n    static long contentLength(Headers headers) {\n        String s = headers.get(\"Content-Length\");\n        if (s == null) return -1;\n        try {\n            return Long.parseLong(s);\n        } catch (NumberFormatException e) {\n            return -1;\n        }\n    }\n\n    static String responseSourceHeader(Response response) {\n        if (response.networkResponse() == null) {\n            return response.cacheResponse() == null\n                    ? \"NONE\"\n                    : \"CACHE \" + response.code();\n        }\n        return response.cacheResponse() == null\n                ? \"NETWORK \" + response.code()\n                : \"CONDITIONAL_CACHE \" + response.networkResponse().code();\n    }\n\n    static String statusLineToString(Response response) {\n        return (response.protocol() == Protocol.HTTP_1_0 ? \"HTTP/1.0\" : \"HTTP/1.1\")\n                + ' ' + response.code()\n                + ' ' + response.message();\n    }\n\n    static String toHumanReadableAscii(String s) {\n        for (int i = 0, length = s.length(), c; i < length; i += Character.charCount(c)) {\n            c = s.codePointAt(i);\n            if (c > '\\u001f' && c < '\\u007f') continue;\n\n            Buffer buffer = new Buffer();\n            buffer.writeUtf8(s, 0, i);\n            buffer.writeUtf8CodePoint('?');\n            for (int j = i + Character.charCount(c); j < length; j += Character.charCount(c)) {\n                c = s.codePointAt(j);\n                buffer.writeUtf8CodePoint(c > '\\u001f' && c < '\\u007f' ? c : '?');\n            }\n            return buffer.readUtf8();\n        }\n        return s;\n    }\n\n    static Map<String, List<String>> toMultimap(Headers headers, @Nullable String valueForNullKey) {\n        Map<String, List<String>> result = new TreeMap<>(FIELD_NAME_COMPARATOR);\n        for (int i = 0, size = headers.size(); i < size; i++) {\n            String fieldName = headers.name(i);\n            String value = headers.value(i);\n\n            List<String> allValues = new ArrayList<>();\n            List<String> otherValues = result.get(fieldName);\n            if (otherValues != null) {\n                allValues.addAll(otherValues);\n            }\n            allValues.add(value);\n            result.put(fieldName, Collections.unmodifiableList(allValues));\n        }\n        if (valueForNullKey != null) {\n            result.put(null, Collections.unmodifiableList(Collections.singletonList(valueForNullKey)));\n        }\n        return Collections.unmodifiableMap(result);\n    }\n\n    static String getSystemProperty(String key, @Nullable String defaultValue) {\n        String value;\n        try {\n            value = System.getProperty(key);\n        } catch (AccessControlException ex) {\n            return defaultValue;\n        }\n        return value != null ? value : defaultValue;\n    }\n\n    private static String defaultUserAgent() {\n        String agent = getSystemProperty(\"http.agent\", null);\n        return agent != null ? toHumanReadableAscii(agent) : \"ObsoleteUrlFactory\";\n    }\n\n    static IOException propagate(Throwable throwable) throws IOException {\n        if (throwable instanceof IOException) throw (IOException) throwable;\n        if (throwable instanceof Error) throw (Error) throwable;\n        if (throwable instanceof RuntimeException) throw (RuntimeException) throwable;\n        throw new AssertionError();\n    }\n\n    static final class OkHttpURLConnection extends HttpURLConnection implements Callback {\n        // These fields are confined to the application thread that uses HttpURLConnection.\n        OkHttpClient client;\n        final NetworkInterceptor networkInterceptor = new NetworkInterceptor();\n        Headers.Builder requestHeaders = new Headers.Builder();\n        Headers responseHeaders;\n        boolean executed;\n        Call call;\n\n        /**\n         * Like the superclass field of the same name, but a long and available on all platforms.\n         */\n        long fixedContentLength = -1L;\n\n        // These fields are guarded by lock.\n        private final Object lock = new Object();\n        private Response response;\n        private Throwable callFailure;\n        Response networkResponse;\n        boolean connectPending = true;\n        Proxy proxy;\n        Handshake handshake;\n\n        OkHttpURLConnection(URL url, OkHttpClient client) {\n            super(url);\n            this.client = client;\n        }\n\n        @Override\n        public void connect() throws IOException {\n            if (executed) return;\n\n            Call call = buildCall();\n            executed = true;\n            call.enqueue(this);\n\n            synchronized (lock) {\n                try {\n                    while (connectPending && response == null && callFailure == null) {\n                        lock.wait(); // Wait 'til the network interceptor is reached or the call fails.\n                    }\n                    if (callFailure != null) {\n                        throw propagate(callFailure);\n                    }\n                } catch (InterruptedException e) {\n                    Thread.currentThread().interrupt(); // Retain interrupted status.\n                    throw new InterruptedIOException();\n                }\n            }\n        }\n\n        @Override\n        public void disconnect() {\n            // Calling disconnect() before a connection exists should have no effect.\n            if (call == null) return;\n\n            networkInterceptor.proceed(); // Unblock any waiting async thread.\n            call.cancel();\n        }\n\n        @Override\n        public InputStream getErrorStream() {\n            try {\n                Response response = getResponse(true);\n                if (hasBody(response) && response.code() >= HTTP_BAD_REQUEST) {\n                    return response.body().byteStream();\n                }\n                return null;\n            } catch (IOException e) {\n                return null;\n            }\n        }\n\n        Headers getHeaders() throws IOException {\n            if (responseHeaders == null) {\n                Response response = getResponse(true);\n                Headers headers = response.headers();\n                responseHeaders = headers.newBuilder()\n                        .add(SELECTED_PROTOCOL, response.protocol().toString())\n                        .add(RESPONSE_SOURCE, responseSourceHeader(response))\n                        .build();\n            }\n            return responseHeaders;\n        }\n\n        @Override\n        public String getHeaderField(int position) {\n            try {\n                Headers headers = getHeaders();\n                if (position < 0 || position >= headers.size()) return null;\n                return headers.value(position);\n            } catch (IOException e) {\n                return null;\n            }\n        }\n\n        @Override\n        public String getHeaderField(String fieldName) {\n            try {\n                return fieldName == null\n                        ? statusLineToString(getResponse(true))\n                        : getHeaders().get(fieldName);\n            } catch (IOException e) {\n                return null;\n            }\n        }\n\n        @Override\n        public String getHeaderFieldKey(int position) {\n            try {\n                Headers headers = getHeaders();\n                if (position < 0 || position >= headers.size()) return null;\n                return headers.name(position);\n            } catch (IOException e) {\n                return null;\n            }\n        }\n\n        @Override\n        public Map<String, List<String>> getHeaderFields() {\n            try {\n                return toMultimap(getHeaders(), statusLineToString(getResponse(true)));\n            } catch (IOException e) {\n                return Collections.emptyMap();\n            }\n        }\n\n        @Override\n        public Map<String, List<String>> getRequestProperties() {\n            if (connected) {\n                throw new IllegalStateException(\n                        \"Cannot access request header fields after connection is set\");\n            }\n\n            return toMultimap(requestHeaders.build(), null);\n        }\n\n        @Override\n        public InputStream getInputStream() throws IOException {\n            if (!doInput) {\n                throw new ProtocolException(\"This protocol does not support input\");\n            }\n\n            Response response = getResponse(false);\n            if (response.code() >= HTTP_BAD_REQUEST) {\n                Log.e(\"DoKit\", \"FileNotFoundException:\" + url.toString());\n                return ConvertUtils.string2InputStream(\"FileNotFoundException:\" + url.toString(), \"utf-8\");\n            }\n\n            if (response.body() != null) {\n                return response.body().byteStream();\n            } else {\n                return ConvertUtils.string2InputStream(\"response.body() is null:\" + url.toString(), \"utf-8\");\n            }\n\n        }\n\n        @Override\n        public OutputStream getOutputStream() throws IOException {\n            OutputStreamRequestBody requestBody = (OutputStreamRequestBody) buildCall().request().body();\n            if (requestBody == null) {\n                throw new ProtocolException(\"method does not support a request body: \" + method);\n            }\n\n            if (requestBody instanceof StreamedRequestBody) {\n                connect();\n                networkInterceptor.proceed();\n            }\n\n            if (requestBody.closed) {\n                throw new ProtocolException(\"cannot write request body after response has been read\");\n            }\n\n            return requestBody.outputStream;\n        }\n\n        @Override\n        public Permission getPermission() {\n            URL url = getURL();\n            String hostname = url.getHost();\n            int hostPort = url.getPort() != -1\n                    ? url.getPort()\n                    : HttpUrl.defaultPort(url.getProtocol());\n            if (usingProxy()) {\n                InetSocketAddress proxyAddress = (InetSocketAddress) client.proxy().address();\n                hostname = proxyAddress.getHostName();\n                hostPort = proxyAddress.getPort();\n            }\n            return new SocketPermission(hostname + \":\" + hostPort, \"connect, resolve\");\n        }\n\n        @Override\n        public String getRequestProperty(String field) {\n            if (field == null) return null;\n            return requestHeaders.get(field);\n        }\n\n        @Override\n        public void setConnectTimeout(int timeoutMillis) {\n            client = client.newBuilder()\n                    .connectTimeout(timeoutMillis, TimeUnit.MILLISECONDS)\n                    .build();\n        }\n\n        @Override\n        public void setInstanceFollowRedirects(boolean followRedirects) {\n            client = client.newBuilder()\n                    .followRedirects(followRedirects)\n                    .build();\n        }\n\n        @Override\n        public boolean getInstanceFollowRedirects() {\n            return client.followRedirects();\n        }\n\n        @Override\n        public int getConnectTimeout() {\n            return client.connectTimeoutMillis();\n        }\n\n        @Override\n        public void setReadTimeout(int timeoutMillis) {\n            client = client.newBuilder()\n                    .readTimeout(timeoutMillis, TimeUnit.MILLISECONDS)\n                    .build();\n        }\n\n        @Override\n        public int getReadTimeout() {\n            return client.readTimeoutMillis();\n        }\n\n        private Call buildCall() throws IOException {\n            if (call != null) {\n                return call;\n            }\n\n            connected = true;\n            if (doOutput) {\n                if (method.equals(\"GET\")) {\n                    method = \"POST\";\n                } else if (!permitsRequestBody(method)) {\n                    throw new ProtocolException(method + \" does not support writing\");\n                }\n            }\n\n            if (requestHeaders.get(\"User-Agent\") == null) {\n                requestHeaders.add(\"User-Agent\", defaultUserAgent());\n            }\n\n            OutputStreamRequestBody requestBody = null;\n            if (permitsRequestBody(method)) {\n                String contentType = requestHeaders.get(\"Content-Type\");\n                if (contentType == null) {\n                    contentType = \"application/x-www-form-urlencoded\";\n                    requestHeaders.add(\"Content-Type\", contentType);\n                }\n\n                boolean stream = fixedContentLength != -1L || chunkLength > 0;\n\n                long contentLength = -1L;\n                String contentLengthString = requestHeaders.get(\"Content-Length\");\n                if (fixedContentLength != -1L) {\n                    contentLength = fixedContentLength;\n                } else if (contentLengthString != null) {\n                    contentLength = Long.parseLong(contentLengthString);\n                }\n\n                requestBody = stream\n                        ? new StreamedRequestBody(contentLength)\n                        : new BufferedRequestBody(contentLength);\n                requestBody.timeout.timeout(client.writeTimeoutMillis(), TimeUnit.MILLISECONDS);\n            }\n\n            HttpUrl url;\n            try {\n                url = HttpUrl.get(getURL().toString());\n            } catch (IllegalArgumentException e) {\n                MalformedURLException malformedUrl = new MalformedURLException();\n                malformedUrl.initCause(e);\n                throw malformedUrl;\n            }\n\n            Request request = new Request.Builder()\n                    .url(url)\n                    .headers(requestHeaders.build())\n                    .method(method, requestBody)\n                    .build();\n\n            OkHttpClient.Builder clientBuilder = client.newBuilder();\n            //clientBuilder.interceptors().clear();\n            //clientBuilder.interceptors().add(UnexpectedException.INTERCEPTOR);\n            //clientBuilder.networkInterceptors().clear();\n            clientBuilder.networkInterceptors().add(networkInterceptor);\n\n            // Use a separate dispatcher so that limits aren't impacted. But use the same executor service!\n            clientBuilder.dispatcher(new Dispatcher(client.dispatcher().executorService()));\n\n            // If we're currently not using caches, make sure the engine's client doesn't have one.\n            if (!getUseCaches()) {\n                clientBuilder.cache(null);\n            }\n\n            return call = clientBuilder.build().newCall(request);\n        }\n\n        private Response getResponse(boolean networkResponseOnError) throws IOException {\n            synchronized (lock) {\n                if (response != null) return response;\n                if (callFailure != null) {\n                    if (networkResponseOnError && networkResponse != null) return networkResponse;\n                    throw propagate(callFailure);\n                }\n            }\n\n            Call call = buildCall();\n            networkInterceptor.proceed();\n\n            OutputStreamRequestBody requestBody = (OutputStreamRequestBody) call.request().body();\n            if (requestBody != null) requestBody.outputStream.close();\n\n            if (executed) {\n                synchronized (lock) {\n                    try {\n                        while (response == null && callFailure == null) {\n                            lock.wait(); // Wait until the response is returned or the call fails.\n                        }\n                    } catch (InterruptedException e) {\n                        Thread.currentThread().interrupt(); // Retain interrupted status.\n                        throw new InterruptedIOException();\n                    }\n                }\n            } else {\n                executed = true;\n                try {\n                    onResponse(call, call.execute());\n                } catch (IOException e) {\n                    onFailure(call, e);\n                }\n            }\n\n            synchronized (lock) {\n                if (callFailure != null) throw propagate(callFailure);\n                if (response != null) return response;\n            }\n\n            throw new AssertionError();\n        }\n\n        @Override\n        public boolean usingProxy() {\n            if (proxy != null) return true;\n            Proxy clientProxy = client.proxy();\n            return clientProxy != null && clientProxy.type() != Proxy.Type.DIRECT;\n        }\n\n        @Override\n        public String getResponseMessage() throws IOException {\n            return getResponse(true).message();\n        }\n\n        @Override\n        public int getResponseCode() throws IOException {\n            return getResponse(true).code();\n        }\n\n        @Override\n        public void setRequestProperty(String field, String newValue) {\n            if (connected) {\n                throw new IllegalStateException(\"Cannot set request property after connection is made\");\n            }\n            if (field == null) {\n                throw new NullPointerException(\"field == null\");\n            }\n            if (newValue == null) {\n                return;\n            }\n\n            requestHeaders.set(field, newValue);\n        }\n\n        @Override\n        public void setIfModifiedSince(long newValue) {\n            super.setIfModifiedSince(newValue);\n            if (ifModifiedSince != 0) {\n                requestHeaders.set(\"If-Modified-Since\", format(new Date(ifModifiedSince)));\n            } else {\n                requestHeaders.removeAll(\"If-Modified-Since\");\n            }\n        }\n\n        @Override\n        public void addRequestProperty(String field, String value) {\n            if (connected) {\n                throw new IllegalStateException(\"Cannot add request property after connection is made\");\n            }\n            if (field == null) {\n                throw new NullPointerException(\"field == null\");\n            }\n            if (value == null) {\n                return;\n            }\n\n            requestHeaders.add(field, value);\n        }\n\n        @Override\n        public void setRequestMethod(String method) throws ProtocolException {\n            if (!METHODS.contains(method)) {\n                throw new ProtocolException(\"Expected one of \" + METHODS + \" but was \" + method);\n            }\n            this.method = method;\n        }\n\n        @Override\n        public void setFixedLengthStreamingMode(int contentLength) {\n            setFixedLengthStreamingMode((long) contentLength);\n        }\n\n        @Override\n        public void setFixedLengthStreamingMode(long contentLength) {\n            if (super.connected) throw new IllegalStateException(\"Already connected\");\n            if (chunkLength > 0) throw new IllegalStateException(\"Already in chunked mode\");\n            if (contentLength < 0) throw new IllegalArgumentException(\"contentLength < 0\");\n            this.fixedContentLength = contentLength;\n            super.fixedContentLength = (int) Math.min(contentLength, Integer.MAX_VALUE);\n        }\n\n        @Override\n        public void onFailure(Call call, IOException e) {\n            synchronized (lock) {\n                this.callFailure = (e instanceof UnexpectedException) ? e.getCause() : e;\n                lock.notifyAll();\n            }\n        }\n\n        @Override\n        public void onResponse(Call call, Response response) {\n            synchronized (lock) {\n                this.response = response;\n                this.handshake = response.handshake();\n                this.url = response.request().url().url();\n                lock.notifyAll();\n            }\n        }\n\n        final class NetworkInterceptor implements Interceptor {\n            // Guarded by HttpUrlConnection.this.\n            private boolean proceed;\n\n            public void proceed() {\n                synchronized (lock) {\n                    this.proceed = true;\n                    lock.notifyAll();\n                }\n            }\n\n            @Override\n            public Response intercept(Chain chain) throws IOException {\n                Request request = chain.request();\n\n                synchronized (lock) {\n                    connectPending = false;\n                    proxy = chain.connection().route().proxy();\n                    handshake = chain.connection().handshake();\n                    lock.notifyAll();\n\n                    try {\n                        while (!proceed) {\n                            lock.wait(); // Wait until proceed() is called.\n                        }\n                    } catch (InterruptedException e) {\n                        Thread.currentThread().interrupt(); // Retain interrupted status.\n                        throw new InterruptedIOException();\n                    }\n                }\n\n                // Try to lock in the Content-Length before transmitting the request body.\n                if (request.body() instanceof OutputStreamRequestBody) {\n                    OutputStreamRequestBody requestBody = (OutputStreamRequestBody) request.body();\n                    request = requestBody.prepareToSendRequest(request);\n                }\n\n                Response response = chain.proceed(request);\n\n                synchronized (lock) {\n                    networkResponse = response;\n                    url = response.request().url().url();\n                }\n\n                return response;\n            }\n        }\n    }\n\n    abstract static class OutputStreamRequestBody extends RequestBody {\n        Timeout timeout;\n        long expectedContentLength;\n        OutputStream outputStream;\n        boolean closed;\n\n        void initOutputStream(final BufferedSink sink, final long expectedContentLength) {\n            this.timeout = sink.timeout();\n            this.expectedContentLength = expectedContentLength;\n\n            // An output stream that writes to sink. If expectedContentLength is not -1, then this expects\n            // exactly that many bytes to be written.\n            this.outputStream = new OutputStream() {\n                private long bytesReceived;\n\n                @Override\n                public void write(int b) throws IOException {\n                    write(new byte[]{(byte) b}, 0, 1);\n                }\n\n                @Override\n                public void write(byte[] source, int offset, int byteCount) throws IOException {\n                    if (closed) throw new IOException(\"closed\"); // Not IllegalStateException!\n\n                    if (expectedContentLength != -1L && bytesReceived + byteCount > expectedContentLength) {\n                        throw new ProtocolException(\"expected \" + expectedContentLength\n                                + \" bytes but received \" + bytesReceived + byteCount);\n                    }\n\n                    bytesReceived += byteCount;\n                    try {\n                        sink.write(source, offset, byteCount);\n                    } catch (InterruptedIOException e) {\n                        throw new SocketTimeoutException(e.getMessage());\n                    }\n                }\n\n                @Override\n                public void flush() throws IOException {\n                    if (closed) return; // Weird, but consistent with historical behavior.\n                    sink.flush();\n                }\n\n                @Override\n                public void close() throws IOException {\n                    closed = true;\n\n                    if (expectedContentLength != -1L && bytesReceived < expectedContentLength) {\n                        throw new ProtocolException(\"expected \" + expectedContentLength\n                                + \" bytes but received \" + bytesReceived);\n                    }\n\n                    sink.close();\n                }\n            };\n        }\n\n        @Override\n        public long contentLength() {\n            return expectedContentLength;\n        }\n\n        @Override\n        public final @Nullable\n        MediaType contentType() {\n            return null; // Let the caller provide this in a regular header.\n        }\n\n        public Request prepareToSendRequest(Request request) throws IOException {\n            return request;\n        }\n    }\n\n    static final class BufferedRequestBody extends OutputStreamRequestBody {\n        final Buffer buffer = new Buffer();\n        long contentLength = -1L;\n\n        BufferedRequestBody(long expectedContentLength) {\n            initOutputStream(buffer, expectedContentLength);\n        }\n\n        @Override\n        public long contentLength() {\n            return contentLength;\n        }\n\n        @Override\n        public Request prepareToSendRequest(Request request) throws IOException {\n            if (request.header(\"Content-Length\") != null) return request;\n\n            outputStream.close();\n            contentLength = buffer.size();\n            return request.newBuilder()\n                    .removeHeader(\"Transfer-Encoding\")\n                    .header(\"Content-Length\", Long.toString(buffer.size()))\n                    .build();\n        }\n\n        @Override\n        public void writeTo(BufferedSink sink) {\n            buffer.copyTo(sink.buffer(), 0, buffer.size());\n        }\n    }\n\n    static final class StreamedRequestBody extends OutputStreamRequestBody {\n        private final Pipe pipe = new Pipe(8192);\n\n        StreamedRequestBody(long expectedContentLength) {\n            initOutputStream(Okio.buffer(pipe.sink()), expectedContentLength);\n        }\n\n//        @Override\n//        public boolean isOneShot() {\n//            return true;\n//        }\n\n\n        @Override\n        public void writeTo(BufferedSink sink) throws IOException {\n            Buffer buffer = new Buffer();\n            while (pipe.source().read(buffer, 8192) != -1L) {\n                sink.write(buffer, buffer.size());\n            }\n        }\n    }\n\n    abstract static class DelegatingHttpsURLConnection extends HttpsURLConnection {\n        private final HttpURLConnection delegate;\n\n        DelegatingHttpsURLConnection(HttpURLConnection delegate) {\n            super(delegate.getURL());\n            this.delegate = delegate;\n        }\n\n        protected abstract Handshake handshake();\n\n        @Override\n        public abstract void setHostnameVerifier(HostnameVerifier hostnameVerifier);\n\n        @Override\n        public abstract HostnameVerifier getHostnameVerifier();\n\n        @Override\n        public abstract void setSSLSocketFactory(SSLSocketFactory sslSocketFactory);\n\n        @Override\n        public abstract SSLSocketFactory getSSLSocketFactory();\n\n        @Override\n        public String getCipherSuite() {\n            Handshake handshake = handshake();\n            return handshake != null ? handshake.cipherSuite().javaName() : null;\n        }\n\n        @Override\n        public Certificate[] getLocalCertificates() {\n            Handshake handshake = handshake();\n            if (handshake == null) return null;\n            List<Certificate> result = handshake.localCertificates();\n            return !result.isEmpty() ? result.toArray(new Certificate[result.size()]) : null;\n        }\n\n        @Override\n        public Certificate[] getServerCertificates() {\n            Handshake handshake = handshake();\n            if (handshake == null) return null;\n            List<Certificate> result = handshake.peerCertificates();\n            return !result.isEmpty() ? result.toArray(new Certificate[result.size()]) : null;\n        }\n\n        @Override\n        public Principal getPeerPrincipal() {\n            Handshake handshake = handshake();\n            return handshake != null ? handshake.peerPrincipal() : null;\n        }\n\n        @Override\n        public Principal getLocalPrincipal() {\n            Handshake handshake = handshake();\n            return handshake != null ? handshake.localPrincipal() : null;\n        }\n\n        @Override\n        public void connect() throws IOException {\n            connected = true;\n            delegate.connect();\n        }\n\n        @Override\n        public void disconnect() {\n            delegate.disconnect();\n        }\n\n        @Override\n        public InputStream getErrorStream() {\n            return delegate.getErrorStream();\n        }\n\n        @Override\n        public String getRequestMethod() {\n            return delegate.getRequestMethod();\n        }\n\n        @Override\n        public int getResponseCode() throws IOException {\n            return delegate.getResponseCode();\n        }\n\n        @Override\n        public String getResponseMessage() throws IOException {\n            return delegate.getResponseMessage();\n        }\n\n        @Override\n        public void setRequestMethod(String method) throws ProtocolException {\n            delegate.setRequestMethod(method);\n        }\n\n        @Override\n        public boolean usingProxy() {\n            return delegate.usingProxy();\n        }\n\n        @Override\n        public boolean getInstanceFollowRedirects() {\n            return delegate.getInstanceFollowRedirects();\n        }\n\n        @Override\n        public void setInstanceFollowRedirects(boolean followRedirects) {\n            delegate.setInstanceFollowRedirects(followRedirects);\n        }\n\n        @Override\n        public boolean getAllowUserInteraction() {\n            return delegate.getAllowUserInteraction();\n        }\n\n        @Override\n        public Object getContent() throws IOException {\n            return delegate.getContent();\n        }\n\n        @Override\n        public Object getContent(Class[] types) throws IOException {\n            return delegate.getContent(types);\n        }\n\n        @Override\n        public String getContentEncoding() {\n            return delegate.getContentEncoding();\n        }\n\n        @Override\n        public int getContentLength() {\n            return delegate.getContentLength();\n        }\n\n        // Should only be invoked on Java 8+ or Android API 24+.\n        @RequiresApi(api = Build.VERSION_CODES.N)\n        @Override\n        public long getContentLengthLong() {\n            return delegate.getContentLengthLong();\n        }\n\n        @Override\n        public String getContentType() {\n            return delegate.getContentType();\n        }\n\n        @Override\n        public long getDate() {\n            return delegate.getDate();\n        }\n\n        @Override\n        public boolean getDefaultUseCaches() {\n            return delegate.getDefaultUseCaches();\n        }\n\n        @Override\n        public boolean getDoInput() {\n            return delegate.getDoInput();\n        }\n\n        @Override\n        public boolean getDoOutput() {\n            return delegate.getDoOutput();\n        }\n\n        @Override\n        public long getExpiration() {\n            return delegate.getExpiration();\n        }\n\n        @Override\n        public String getHeaderField(int pos) {\n            return delegate.getHeaderField(pos);\n        }\n\n        @Override\n        public Map<String, List<String>> getHeaderFields() {\n            return delegate.getHeaderFields();\n        }\n\n        @Override\n        public Map<String, List<String>> getRequestProperties() {\n            return delegate.getRequestProperties();\n        }\n\n        @Override\n        public void addRequestProperty(String field, String newValue) {\n            try {\n                delegate.addRequestProperty(field, newValue);\n            } catch (Exception e) {\n\n            }\n\n        }\n\n        @Override\n        public String getHeaderField(String key) {\n            return delegate.getHeaderField(key);\n        }\n\n        // Should only be invoked on Java 8+ or Android API 24+.\n        @RequiresApi(api = Build.VERSION_CODES.N)\n        @Override\n        public long getHeaderFieldLong(String field, long defaultValue) {\n            return delegate.getHeaderFieldLong(field, defaultValue);\n        }\n\n        @Override\n        public long getHeaderFieldDate(String field, long defaultValue) {\n            return delegate.getHeaderFieldDate(field, defaultValue);\n        }\n\n        @Override\n        public int getHeaderFieldInt(String field, int defaultValue) {\n            return delegate.getHeaderFieldInt(field, defaultValue);\n        }\n\n        @Override\n        public String getHeaderFieldKey(int position) {\n            return delegate.getHeaderFieldKey(position);\n        }\n\n        @Override\n        public long getIfModifiedSince() {\n            return delegate.getIfModifiedSince();\n        }\n\n        @Override\n        public InputStream getInputStream() throws IOException {\n            return delegate.getInputStream();\n        }\n\n        @Override\n        public long getLastModified() {\n            return delegate.getLastModified();\n        }\n\n        @Override\n        public OutputStream getOutputStream() throws IOException {\n            return delegate.getOutputStream();\n        }\n\n        @Override\n        public Permission getPermission() throws IOException {\n            return delegate.getPermission();\n        }\n\n        @Override\n        public String getRequestProperty(String field) {\n            return delegate.getRequestProperty(field);\n        }\n\n        @Override\n        public URL getURL() {\n            return delegate.getURL();\n        }\n\n        @Override\n        public boolean getUseCaches() {\n            return delegate.getUseCaches();\n        }\n\n        @Override\n        public void setAllowUserInteraction(boolean newValue) {\n            delegate.setAllowUserInteraction(newValue);\n        }\n\n        @Override\n        public void setDefaultUseCaches(boolean newValue) {\n            delegate.setDefaultUseCaches(newValue);\n        }\n\n        @Override\n        public void setDoInput(boolean newValue) {\n            delegate.setDoInput(newValue);\n        }\n\n        @Override\n        public void setDoOutput(boolean newValue) {\n            delegate.setDoOutput(newValue);\n        }\n\n        // Should only be invoked on Java 8+ or Android API 24+.\n        @RequiresApi(api = Build.VERSION_CODES.KITKAT)\n        @Override\n        public void setFixedLengthStreamingMode(long contentLength) {\n            delegate.setFixedLengthStreamingMode(contentLength);\n        }\n\n        @Override\n        public void setIfModifiedSince(long newValue) {\n            delegate.setIfModifiedSince(newValue);\n        }\n\n        @Override\n        public void setRequestProperty(String field, String newValue) {\n            delegate.setRequestProperty(field, newValue);\n        }\n\n        @Override\n        public void setUseCaches(boolean newValue) {\n            delegate.setUseCaches(newValue);\n        }\n\n        @Override\n        public void setConnectTimeout(int timeoutMillis) {\n            delegate.setConnectTimeout(timeoutMillis);\n        }\n\n        @Override\n        public int getConnectTimeout() {\n            return delegate.getConnectTimeout();\n        }\n\n        @Override\n        public void setReadTimeout(int timeoutMillis) {\n            delegate.setReadTimeout(timeoutMillis);\n        }\n\n        @Override\n        public int getReadTimeout() {\n            return delegate.getReadTimeout();\n        }\n\n        @Override\n        public String toString() {\n            return delegate.toString();\n        }\n\n        @Override\n        public void setFixedLengthStreamingMode(int contentLength) {\n            delegate.setFixedLengthStreamingMode(contentLength);\n        }\n\n        @Override\n        public void setChunkedStreamingMode(int chunkLength) {\n            delegate.setChunkedStreamingMode(chunkLength);\n        }\n    }\n\n    static final class OkHttpsURLConnection extends DelegatingHttpsURLConnection {\n        private final OkHttpURLConnection delegate;\n\n        OkHttpsURLConnection(URL url, OkHttpClient client) {\n            this(new OkHttpURLConnection(url, client));\n        }\n\n        OkHttpsURLConnection(OkHttpURLConnection delegate) {\n            super(delegate);\n            this.delegate = delegate;\n        }\n\n        @Override\n        protected Handshake handshake() {\n            if (delegate.call == null) {\n                throw new IllegalStateException(\"Connection has not yet been established\");\n            }\n\n            return delegate.handshake;\n        }\n\n        @Override\n        public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {\n            delegate.client = delegate.client.newBuilder()\n                    .hostnameVerifier(hostnameVerifier)\n                    .build();\n        }\n\n        @Override\n        public HostnameVerifier getHostnameVerifier() {\n            return delegate.client.hostnameVerifier();\n        }\n\n        @Override\n        public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {\n            if (sslSocketFactory == null) {\n                throw new IllegalArgumentException(\"sslSocketFactory == null\");\n            }\n\n            X509TrustManager trustManager = new MyTrustManager().getTrustManager();\n            // This fails in JDK 9 because OkHttp is unable to extract the trust manager.\n            delegate.client = delegate.client.newBuilder()\n                .sslSocketFactory(sslSocketFactory, trustManager)\n                .build();\n        }\n\n\n        @Override\n        public SSLSocketFactory getSSLSocketFactory() {\n            return delegate.client.sslSocketFactory();\n        }\n    }\n\n    static final class UnexpectedException extends IOException {\n        static final Interceptor INTERCEPTOR = new Interceptor() {\n            @Override\n            public Response intercept(Chain chain) throws IOException {\n                try {\n                    return chain.proceed(chain.request());\n                } catch (Error | RuntimeException e) {\n                    throw new UnexpectedException(e);\n                }\n            }\n        };\n\n        UnexpectedException(Throwable cause) {\n            super(cause);\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/aop/urlconnection/OkhttpClientUtil.kt",
    "content": "package com.didichuxing.doraemonkit.aop.urlconnection\n\nimport okhttp3.OkHttpClient\nimport java.util.concurrent.TimeUnit\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/17-10:25\n * 描    述：将urlconnection 代理成okhttpClient\n * 修订历史：\n * ================================================\n */\nobject OkhttpClientUtil {\n    private val DEFAULT_MILLISECONDS: Long = 60000\n    val okhttpClient: OkHttpClient by lazy {\n        OkHttpClient.Builder()\n            .retryOnConnectionFailure(true)\n            .readTimeout(DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS)\n            .writeTimeout(DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS)\n            .connectTimeout(DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS)\n            .build()\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/config/AlignRulerConfig.java",
    "content": "package com.didichuxing.doraemonkit.config;\n\nimport com.didichuxing.doraemonkit.constant.SharedPrefsKey;\nimport com.didichuxing.doraemonkit.util.DoKitSPUtil;\n\n/**\n * @author wanglikun\n */\npublic class AlignRulerConfig {\n    public static boolean isAlignRulerOpen() {\n        return DoKitSPUtil.getBoolean(SharedPrefsKey.ALIGN_RULER_OPEN, false);\n    }\n\n    public static void setAlignRulerOpen(boolean open) {\n        DoKitSPUtil.putBoolean(SharedPrefsKey.ALIGN_RULER_OPEN, open);\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/config/ColorPickConfig.java",
    "content": "package com.didichuxing.doraemonkit.config;\n\nimport com.didichuxing.doraemonkit.constant.SharedPrefsKey;\nimport com.didichuxing.doraemonkit.util.DoKitSPUtil;\n\n/**\n * @author wanglikun\n */\npublic class ColorPickConfig {\n    public static boolean isColorPickOpen() {\n        return DoKitSPUtil.getBoolean(SharedPrefsKey.COLOR_PICK_OPEN, false);\n    }\n\n    public static void setColorPickOpen(boolean open) {\n        DoKitSPUtil.putBoolean(SharedPrefsKey.COLOR_PICK_OPEN, open);\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/config/CrashCaptureConfig.java",
    "content": "package com.didichuxing.doraemonkit.config;\n\nimport com.didichuxing.doraemonkit.constant.SharedPrefsKey;\nimport com.didichuxing.doraemonkit.util.DoKitSPUtil;\n\npublic class CrashCaptureConfig {\n    public static boolean isCrashCaptureOpen() {\n        return DoKitSPUtil.getBoolean(SharedPrefsKey.CRASH_CAPTURE_OPEN, false);\n    }\n\n    public static void setCrashCaptureOpen(boolean open) {\n        DoKitSPUtil.putBoolean(SharedPrefsKey.CRASH_CAPTURE_OPEN, open);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/config/DokitMemoryConfig.java",
    "content": "package com.didichuxing.doraemonkit.config;\n\n/**\n * Created by jintai on 2019/10/10.\n * 将性能检测配置信息保存在内存中 不需要持久化保存\n */\n\npublic class DokitMemoryConfig {\n    //帧率检测全局开关\n    public static boolean FPS_STATUS = false;\n    //CPU检测全局开关\n    public static boolean CPU_STATUS = false;\n    //RAM检测全局开关\n    public static boolean RAM_STATUS = false;\n    //流量检测全局开关\n    public static boolean NETWORK_STATUS = false;\n\n    //日志全局开关\n    public static boolean LOG_STATUS = false;\n    //大图检测全局开关\n    //public static boolean LARGE_IMG_STATUS = false;\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/config/DokitSwitchConfig.java",
    "content": "package com.didichuxing.doraemonkit.config;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/26-14:35\n * 描    述：Dokit 功能开关配置\n * 修订历史：\n * ================================================\n */\npublic class DokitSwitchConfig {\n    private boolean alignRule;\n    private boolean largeImg;\n    private boolean log;\n    private boolean gps;\n    private boolean appHealth;\n    private boolean crash;\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/config/FloatIconConfig.java",
    "content": "package com.didichuxing.doraemonkit.config;\n\nimport com.didichuxing.doraemonkit.constant.SharedPrefsKey;\nimport com.didichuxing.doraemonkit.util.DoKitSPUtil;\n\n/**\n * Created by wanglikun on 2018/12/14.\n */\n\npublic class FloatIconConfig {\n\n    public static int getLastPosX() {\n        return DoKitSPUtil.getInt(SharedPrefsKey.FLOAT_ICON_POS_X, 0);\n    }\n\n    public static int getLastPosY() {\n        return DoKitSPUtil.getInt(SharedPrefsKey.FLOAT_ICON_POS_Y, 0);\n    }\n\n    public static void saveLastPosY(int val) {\n        DoKitSPUtil.putInt(SharedPrefsKey.FLOAT_ICON_POS_Y, val);\n    }\n\n    public static void saveLastPosX(int val) {\n        DoKitSPUtil.putInt(SharedPrefsKey.FLOAT_ICON_POS_X, val);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/config/GlobalConfig.java",
    "content": "package com.didichuxing.doraemonkit.config;\n\nimport com.didichuxing.doraemonkit.constant.SharedPrefsKey;\nimport com.didichuxing.doraemonkit.util.DoKitSPUtil;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020-01-06-11:37\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class GlobalConfig {\n    /**\n     * 设置健康体检\n     *\n     * @param isRunning\n     */\n    public static void setAppHealth(boolean isRunning) {\n        try {\n            DoKitSPUtil.putBoolean(SharedPrefsKey.APP_HEALTH, isRunning);\n        } catch (Exception e) {\n\n        }\n\n    }\n\n    /**\n     * 获得app 健康体检功能的本地状态\n     */\n    public static boolean getAppHealth() {\n        try {\n            return DoKitSPUtil.getBoolean(SharedPrefsKey.APP_HEALTH, false);\n        } catch (Exception e) {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/config/GpsMockConfig.java",
    "content": "package com.didichuxing.doraemonkit.config;\n\nimport com.didichuxing.doraemonkit.constant.CachesKey;\nimport com.didichuxing.doraemonkit.constant.SharedPrefsKey;\nimport com.didichuxing.doraemonkit.model.LatLng;\nimport com.didichuxing.doraemonkit.util.DoKitCacheUtils;\nimport com.didichuxing.doraemonkit.util.DoKitSPUtil;\n\n/**\n * Created by wanglikun on 2018/9/20.\n */\n\npublic class GpsMockConfig {\n    /**\n     * @return\n     */\n    public static boolean isGPSMockOpen() {\n        return DoKitSPUtil.getBoolean(SharedPrefsKey.GPS_MOCK_OPEN, false);\n    }\n\n    /**\n     * @param open\n     */\n    public static void setGPSMockOpen(boolean open) {\n        DoKitSPUtil.putBoolean(SharedPrefsKey.GPS_MOCK_OPEN, open);\n    }\n\n    public static boolean isPosMockOpen() {\n        return DoKitSPUtil.getBoolean(SharedPrefsKey.POS_MOCK_OPEN, false);\n    }\n\n    public static void putPosMockOpen(boolean open) {\n        DoKitSPUtil.putBoolean(SharedPrefsKey.POS_MOCK_OPEN, open);\n    }\n\n    public static boolean isRouteMockOpen() {\n        return DoKitSPUtil.getBoolean(SharedPrefsKey.ROUTE_MOCK_OPEN, false);\n    }\n\n    public static void putRouteMockOpen(boolean open) {\n        DoKitSPUtil.putBoolean(SharedPrefsKey.ROUTE_MOCK_OPEN, open);\n    }\n\n    public static boolean isRouteDriftMockOpen() {\n        return DoKitSPUtil.getBoolean(SharedPrefsKey.ROUTE_DRIFT_MOCK_OPEN, false);\n    }\n\n    public static void putRouteDriftMockOpen(boolean open) {\n        DoKitSPUtil.putBoolean(SharedPrefsKey.ROUTE_DRIFT_MOCK_OPEN, open);\n    }\n\n    public static boolean isRouteDriftMockLostLocOpen() {\n        return DoKitSPUtil.getBoolean(SharedPrefsKey.ROUTE_DRIFT_MOCK_LOST_LOC_OPEN, false);\n    }\n\n    public static void putRouteDriftMockLostLocOpen(boolean open) {\n        DoKitSPUtil.putBoolean(SharedPrefsKey.ROUTE_DRIFT_MOCK_LOST_LOC_OPEN, open);\n    }\n\n    public static void putRouteMockSpeed(float speed) {\n        DoKitSPUtil.putFloat(SharedPrefsKey.ROUTE_MOCK_SPEED, speed);\n    }\n\n    public static float getRouteMockSpeed() {\n        return DoKitSPUtil.getFloat(SharedPrefsKey.ROUTE_MOCK_SPEED, 60.0f);\n    }\n\n    public static void putRouteMockAccuracy(int accuracy) {\n        DoKitSPUtil.putInt(SharedPrefsKey.ROUTE_MOCK_ACCURACY, accuracy);\n    }\n\n    public static int getRouteMockAccuracy() {\n        return DoKitSPUtil.getInt(SharedPrefsKey.ROUTE_MOCK_ACCURACY, 500);\n    }\n\n    public static void putRouteMockDriftMode(int index) {\n        DoKitSPUtil.putInt(SharedPrefsKey.ROUTE_DRIFT_MODE, index);\n    }\n\n    public static int getRouteMockDriftMode() {\n        return DoKitSPUtil.getInt(SharedPrefsKey.ROUTE_DRIFT_MODE, 0);\n    }\n\n    public static void putRouteMockDriftType(int index) {\n        DoKitSPUtil.putInt(SharedPrefsKey.ROUTE_DRIFT_TYPE, index);\n    }\n\n    public static int getRouteMockDriftType() {\n        return DoKitSPUtil.getInt(SharedPrefsKey.ROUTE_DRIFT_TYPE, 0);\n    }\n\n    public static void putSeekBarLow(int progressLow) {\n        DoKitSPUtil.putInt(SharedPrefsKey.ROUTE_DRIFT_SEEKBAR_PROGRESS_LOW, progressLow);\n    }\n\n    public static int getSeekBarLow() {\n        return DoKitSPUtil.getInt(SharedPrefsKey.ROUTE_DRIFT_SEEKBAR_PROGRESS_LOW, 0);\n    }\n\n    public static void putSeekBarHigh(int progressLow) {\n        DoKitSPUtil.putInt(SharedPrefsKey.ROUTE_DRIFT_SEEKBAR_PROGRESS_HIGH, progressLow);\n    }\n\n    public static int getSeekBarHigh() {\n        return DoKitSPUtil.getInt(SharedPrefsKey.ROUTE_DRIFT_SEEKBAR_PROGRESS_HIGH, 100);\n    }\n\n\n    public static void putLostLocSeekBarLow(int progressLow) {\n        DoKitSPUtil.putInt(SharedPrefsKey.ROUTE_DRIFT_LOST_LOC_SEEKBAR_PROGRESS_LOW, progressLow);\n    }\n\n    public static int getLostLocSeekBarLow() {\n        return DoKitSPUtil.getInt(SharedPrefsKey.ROUTE_DRIFT_LOST_LOC_SEEKBAR_PROGRESS_LOW, 0);\n    }\n\n    public static void putLostLocSeekBarHigh(int progressLow) {\n        DoKitSPUtil.putInt(SharedPrefsKey.ROUTE_DRIFT_LOST_LOC_SEEKBAR_PROGRESS_HIGH, progressLow);\n    }\n\n    public static int getLostLocSeekBarHigh() {\n        return DoKitSPUtil.getInt(SharedPrefsKey.ROUTE_DRIFT_LOST_LOC_SEEKBAR_PROGRESS_HIGH, 100);\n    }\n\n    public static LatLng getMockLocation() {\n        return (LatLng) DoKitCacheUtils.readObject(CachesKey.MOCK_LOCATION);\n    }\n\n    public static void saveMockLocation(LatLng latLng) {\n        DoKitCacheUtils.saveObject(CachesKey.MOCK_LOCATION, latLng);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/config/LayoutBorderConfig.java",
    "content": "package com.didichuxing.doraemonkit.config;\n\n/**\n * Created by wanglikun on 2019/1/7\n */\npublic class LayoutBorderConfig {\n    private static boolean sLayoutBorderOpen;\n    private static boolean sLayoutLevelOpen;\n\n    public static boolean isLayoutBorderOpen() {\n        return sLayoutBorderOpen;\n    }\n\n    public static void setLayoutBorderOpen(boolean open) {\n        sLayoutBorderOpen = open;\n    }\n\n    public static boolean isLayoutLevelOpen() {\n        return sLayoutLevelOpen;\n    }\n\n    public static void setLayoutLevelOpen(boolean open) {\n        sLayoutLevelOpen = open;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/config/LogInfoConfig.java",
    "content": "package com.didichuxing.doraemonkit.config;\n\nimport com.didichuxing.doraemonkit.constant.SharedPrefsKey;\nimport com.didichuxing.doraemonkit.util.DoKitSPUtil;\n\n/**\n * @author wanglikun\n */\npublic class LogInfoConfig {\n    public static boolean isLogInfoOpen() {\n        return DoKitSPUtil.getBoolean(SharedPrefsKey.LOG_INFO_OPEN, false);\n    }\n\n    public static void setLogInfoOpen(boolean open) {\n        DoKitSPUtil.putBoolean(SharedPrefsKey.LOG_INFO_OPEN, open);\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/config/PerformanceSpInfoConfig.java",
    "content": "package com.didichuxing.doraemonkit.config;\n\nimport com.didichuxing.doraemonkit.constant.SharedPrefsKey;\nimport com.didichuxing.doraemonkit.kit.largepicture.LargePictureManager;\nimport com.didichuxing.doraemonkit.util.DoKitSPUtil;\n\n/**\n * Created by wanglikun on 2018/9/14.\n * 将配置信息保存在sp中\n */\n\npublic class PerformanceSpInfoConfig {\n\n    /**\n     * 判断是否开启大图检测\n     *\n     * @return\n     */\n    public static boolean isLargeImgOpen() {\n        return DoKitSPUtil.getBoolean(SharedPrefsKey.LARGE_IMG_OPEN, false);\n    }\n\n    /**\n     * 设置打开或者关闭大图检测\n     *\n     * @param open\n     */\n    public static void setLargeImgOpen(boolean open) {\n        DoKitSPUtil.putBoolean(SharedPrefsKey.LARGE_IMG_OPEN, open);\n    }\n\n\n    /**\n     * 设置大图内存阈值\n     *\n     * @param threshold\n     */\n    public static void setLargeImgMemoryThreshold(float threshold) {\n        DoKitSPUtil.putFloat(SharedPrefsKey.LARGE_IMG_MEMORY_THRESHOLD, threshold);\n        LargePictureManager.getInstance().setMemoryThreshold(threshold);\n    }\n\n\n    /**\n     * 获得大图内存阈值\n     *\n     * @param threshold\n     */\n    public static double getLargeImgMemoryThreshold(float threshold) {\n        return DoKitSPUtil.getFloat(SharedPrefsKey.LARGE_IMG_MEMORY_THRESHOLD, threshold);\n    }\n\n    /**\n     * 设置大图文件阈值\n     *\n     * @param threshold\n     */\n    public static void setLargeImgFileThreshold(float threshold) {\n        DoKitSPUtil.putFloat(SharedPrefsKey.LARGE_IMG_FILE_THRESHOLD, threshold);\n        LargePictureManager.getInstance().setFileThreshold(threshold);\n    }\n\n    /**\n     * 获得大图文件阈值\n     *\n     * @param threshold\n     */\n    public static double getLargeImgFileThreshold(float threshold) {\n        return DoKitSPUtil.getFloat(SharedPrefsKey.LARGE_IMG_FILE_THRESHOLD, threshold);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/constant/BroadcastAction.java",
    "content": "package com.didichuxing.doraemonkit.constant;\n\n/**\n * Created by wanglikun on 2018/11/20.\n */\n\npublic interface BroadcastAction {\n    String ACTION_ACCESSIBILITY_UPDATE = \"action_accessibility_update\";\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/constant/BundleKey.java",
    "content": "package com.didichuxing.doraemonkit.constant;\n\n/**\n * Created by wanglikun on 2018/10/11.\n */\n\npublic interface BundleKey {\n    String FILE_KEY = \"file_key\";\n    String DIR_KEY = \"dir_key\";\n    String FRAGMENT_INDEX = \"fragment_index\";\n    String CUSTOM_FRAGMENT_CLASS = \"custom_fragment_class\";\n    String SYSTEM_FRAGMENT_CLASS = \"system_fragment_class\";\n    String ACCESSIBILITY_DATA = \"accessibility_data\";\n    String PERFORMANCE_TYPE = \"performance_type\";\n    String KEY_URL = \"key_url\";\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/constant/CachesKey.java",
    "content": "package com.didichuxing.doraemonkit.constant;\n\n/**\n * Created by wanglikun on 2018/11/17.\n */\n\npublic interface CachesKey {\n    String WEB_DOOR_HISTORY = \"web_door_history\";\n    String CRASH_HISTORY = \"crash\";\n    String MC_SHOT_HISTORY = \"mc\";\n    String MOCK_LOCATION = \"mock_location\";\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/constant/DoKitModule.kt",
    "content": "package com.didichuxing.doraemonkit.constant\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/17-16:05\n * 描    述：\n * 修订历史：\n * ================================================\n */\nenum class DoKitModule {\n    MODULE_FT,\n    MODULE_MC,\n    MODULE_RPC_MC,\n    MODULE_DMAP,\n    MODULE_GPS_MOCK\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/constant/FragmentIndex.java",
    "content": "package com.didichuxing.doraemonkit.constant;\n\n/**\n * Created by wanglikun on 2018/10/26.\n * modify by jintai on 2020/10/20.\n */\n\npublic interface FragmentIndex {\n    //    int FRAGMENT_SYS_INFO = 1;\n//    int FRAGMENT_FILE_EXPLORER = 2;\n//    int FRAGMENT_LOG_INFO_SETTING = 3;\n    int FRAGMENT_COLOR_PICKER_SETTING = 4;\n    //    int FRAGMENT_FRAME_INFO = 5;\n//    int FRAGMENT_GPS_MOCK = 6;\n//    int FRAGMENT_ALIGN_RULER_SETTING = 7;\n    int FRAGMENT_BLOCK_MONITOR = 8;\n//    int FRAGMENT_WEB_DOOR = 9;\n//    int FRAGMENT_DATA_CLEAN = 10;\n//    int FRAGMENT_CRASH = 11;\n//    int FRAGMENT_NETWORK_MONITOR = 13;\n//    int FRAGMENT_CPU = 14;\n//    int FRAGMENT_RAM = 15;\n//    int FRAGMENT_TIME_COUNTER = 17;\n//    int FRAGMENT_WEB_DOOR_DEFAULT = 18;\n//    int FRAGMENT_CUSTOM = 19;\n//    //int FRAGMENT_TOP_ACTIVITY = 20;\n//    int FRAGMENT_WEAK_NETWORK = 21;\n//    /**\n//     * 大图检测\n//     */\n//    int FRAGMENT_LARGE_PICTURE = 22;\n//\n//    /**\n//     * 数据库远程调试\n//     */\n//    int FRAGMENT_DB_DEBUG = 24;\n//\n//    /**\n//     * 数据Mock\n//     */\n//    int FRAGMENT_NETWORK_MOCK = 25;\n//\n//    /**\n//     * 数据模板预览\n//     */\n//    int FRAGMENT_MOCK_TEMPLATE_PREVIEW = 26;\n//\n//\n//    /**\n//     * 健康体检\n//     */\n//    int FRAGMENT_HEALTH = 27;\n//\n//    /**\n//     * APP启动耗时\n//     */\n//    int FRAGMENT_APP_START = 28;\n//\n//\n//    /**\n//     * DoKit 更多页面\n//     */\n//    int FRAGMENT_DOKIT_MORE = 29;\n//\n//    /**\n//     * Dokit 功能管理页面\n//     */\n//    int FRAGMENT_DOKIT_MANAGER = 30;\n//\n//    /**\n//     * Dokit 文件互传\n//     */\n//    int FRAGMENT_FILE_TRANSFER = 31;\n//\n//\n//    /**\n//     * Dokit 通用 webview 页面\n//     */\n//    int FRAGMENT_WEB = 32;\n//\n//    /**\n//     * Dokit 三方库信息\n//     */\n//    int FRAGMENT_THIRD_LIBRARY_INFO = 33;\n    /**\n     * 用户自定义fragment 索引\n     */\n    int FRAGMENT_CUSTOM = 101;\n    /**\n     * dokit 内置Fragment 索引\n     */\n    int FRAGMENT_SYSTEM = 100;\n    //int FRAGMENT_SIMPLE_CUSTOM = 101;\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/constant/PageTag.java",
    "content": "package com.didichuxing.doraemonkit.constant;\n\n/**\n * Created by wanglikun on 2018/10/25.\n */\n\npublic interface PageTag {\n//    String PAGE_ALIGN_RULER_MARKER = \"page_align_ruler_marker\";\n//    String PAGE_VIEW_CHECK = \"page_view_check\";\n//    String PAGE_COLOR_PICKER_INFO = \"page_color_picker_info\";\n//    String PAGE_TIME_COUNTER = \"page_time_counter\";\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/constant/RequestCode.java",
    "content": "package com.didichuxing.doraemonkit.constant;\n\n/**\n * Created by wanglikun on 2018/9/13.\n */\n\npublic interface RequestCode {\n    int CAPTURE_SCREEN = 10001;\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/constant/SharedPrefsKey.java",
    "content": "package com.didichuxing.doraemonkit.constant;\n\n/**\n * Created by wanglikun on 2018/9/14.\n */\n\npublic interface SharedPrefsKey {\n    String FRAME_INFO_FPS_OPEN = \"frame_info_fps_open\";\n    String FRAME_INFO_CPU_OPEN = \"frame_info_cpu_open\";\n    String FRAME_INFO_MEMORY_OPEN = \"frame_info_memory_open\";\n    String FRAME_INFO_TRAFFIC_OPEN = \"frame_info_traffic_open\";\n    String FRAME_INFO_UI_OPEN = \"frame_info_ui_open\";\n\n    String GPS_MOCK_OPEN = \"gps_mock_open\";\n    String POS_MOCK_OPEN = \"pos_mock_open\";\n    String ROUTE_MOCK_OPEN = \"route_mock_open\";\n    String ROUTE_MOCK_SPEED = \"route_mock_speed\";\n    String ROUTE_MOCK_ACCURACY = \"route_mock_accuracy\";\n    String ROUTE_DRIFT_MOCK_OPEN = \"route_drift_mock_open\";\n    String ROUTE_DRIFT_MOCK_LOST_LOC_OPEN = \"route_drift_mock_lost_loc_open\";\n    String ROUTE_DRIFT_MODE= \"route_drift_mode\";\n    String ROUTE_DRIFT_TYPE = \"route_drift_type\";\n    String ROUTE_DRIFT_SEEKBAR_PROGRESS_LOW = \"route_drift_seekbar_progress_low\";\n    String ROUTE_DRIFT_SEEKBAR_PROGRESS_HIGH = \"route_drift_seekbar_progress_high\";\n    String ROUTE_DRIFT_LOST_LOC_SEEKBAR_PROGRESS_LOW = \"route_drift_lost_loc_seekbar_progress_low\";\n    String ROUTE_DRIFT_LOST_LOC_SEEKBAR_PROGRESS_HIGH = \"route_drift_lost_loc_seekbar_progress_high\";\n\n    String CRASH_CAPTURE_OPEN = \"crash_capture_open\";\n    String FLOAT_ICON_POS_X = \"float_icon_pos_x\";\n    String FLOAT_ICON_POS_Y = \"float_icon_pos_y\";\n    String LOG_INFO_OPEN = \"log_info_open\";\n    String COLOR_PICK_OPEN = \"color_pick_open\";\n    String ALIGN_RULER_OPEN = \"align_ruler_open\";\n    String VIEW_CHECK_OPEN = \"view_check_open\";\n    String LAYOUT_BORDER_OPEN = \"layout_border_open\";\n    String LAYOUT_LEVEL_OPEN = \"layout_level_open\";\n    String TOP_ACTIVITY_OPEN = \"top_activity_open\";\n    /**\n     * 悬浮窗启动模式\n     */\n    String FLOAT_START_MODE = \"float_start_mode\";\n    /**\n     * 大图检测开关\n     */\n    String LARGE_IMG_OPEN = \"large_img_open\";\n    /**\n     * 大图内存检测阈值\n     */\n    String LARGE_IMG_MEMORY_THRESHOLD = \"large_img_memory_threshold\";\n\n    /**\n     * 大图文件检测阈值\n     */\n    String LARGE_IMG_FILE_THRESHOLD = \"large_img_file_threshold\";\n\n    /**\n     * 是否处于健康体检状态的key\n     */\n\n    String APP_HEALTH = \"APP_HEALTH\";\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/constant/SpInputType.java",
    "content": "package com.didichuxing.doraemonkit.constant;\n\n\npublic interface SpInputType {\n    String BOOLEAN = \"Boolean\";\n    String INTEGER = \"Integer\";\n    String LONG = \"Long\";\n    String FLOAT = \"Float\";\n    String HASHSET = \"HashSet\";\n    String STRING = \"String\";\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/datapick/DataPickBean.java",
    "content": "package com.didichuxing.doraemonkit.datapick;\n\nimport com.didichuxing.doraemonkit.util.AppUtils;\nimport com.didichuxing.doraemonkit.util.DeviceUtils;\nimport com.didichuxing.doraemonkit.util.TimeUtils;\nimport com.didichuxing.doraemonkit.BuildConfig;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\n\nimport java.util.List;\nimport java.util.Locale;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020-02-18-15:40\n * 描    述：数据指标监控\n * 修订历史：\n * ================================================\n */\npublic class DataPickBean {\n    /**\n     * 基础信息\n     */\n    private String platform;\n    private String pId;\n    /**\n     * 埋点上传时间\n     */\n    private String time;\n    private String phoneMode;\n    /**\n     * 系统版本\n     */\n    private String systemVersion;\n    private String appName;\n    /**\n     * 包名\n     */\n    private String appId;\n    private String dokitVersion;\n    /**\n     * app 或者系统语言\n     */\n    private String language;\n    private List<EventBean> events;\n\n    DataPickBean() {\n        //初始化基础数据\n        this.pId = DoKitManager.PRODUCT_ID;\n        this.appName = AppUtils.getAppName();\n        this.appId = AppUtils.getAppPackageName();\n        this.dokitVersion = BuildConfig.DOKIT_VERSION;\n        this.platform = \"Android\";\n        this.phoneMode = DeviceUtils.getModel();\n        this.time = \"\" + TimeUtils.getNowMills();\n        this.systemVersion = DeviceUtils.getSDKVersionName();\n        this.language = Locale.getDefault().getDisplayLanguage();\n    }\n\n\n    void setEvents(List<EventBean> events) {\n        this.events = events;\n    }\n\n    public static class EventBean {\n\n        /**\n         * 数据名称\n         */\n        private String eventName;\n        /**\n         * 埋点记录时间\n         */\n        private String time;\n        /**\n         * 页面ID\n         */\n        private String pageId;\n        /**\n         * 业务专区名称/功能名称\n         */\n        private String businessName;\n\n        EventBean(String eventName, String pageId, String businessName) {\n            this.eventName = eventName;\n            this.pageId = pageId;\n            this.businessName = businessName;\n            this.time = \"\" + TimeUtils.getNowMills();\n        }\n\n\n        String getTime() {\n            return time;\n        }\n\n        @Override\n        public String toString() {\n            return \"EventBean{\" +\n                \"eventName='\" + eventName + '\\'' +\n                \", time='\" + time + '\\'' +\n                \", pageId='\" + pageId + '\\'' +\n                \", businessName='\" + businessName + '\\'' +\n                '}';\n        }\n    }\n\n    @Override\n    public String toString() {\n        return \"DataPickBean{\" +\n                \"platform='\" + platform + '\\'' +\n                \", pId='\" + pId + '\\'' +\n                \", time='\" + time + '\\'' +\n                \", phoneMode='\" + phoneMode + '\\'' +\n                \", systemVersion='\" + systemVersion + '\\'' +\n                \", appName='\" + appName + '\\'' +\n                \", appId='\" + appId + '\\'' +\n                \", dokitVersion='\" + dokitVersion + '\\'' +\n                \", language='\" + language + '\\'' +\n                \", events=\" + events +\n                '}';\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/datapick/DataPickManager.java",
    "content": "package com.didichuxing.doraemonkit.datapick;\n\nimport android.text.TextUtils;\n\nimport androidx.annotation.NonNull;\n\nimport com.android.volley.Request;\nimport com.android.volley.Response;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.JsonObjectRequest;\nimport com.didichuxing.doraemonkit.util.FileIOUtils;\nimport com.didichuxing.doraemonkit.util.FileUtils;\nimport com.didichuxing.doraemonkit.util.GsonUtils;\nimport com.didichuxing.doraemonkit.util.PathUtils;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.util.LogHelper;\nimport com.didichuxing.doraemonkit.volley.VolleyManager;\n\nimport org.json.JSONObject;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020-02-19-13:27\n * 描    述：dokit 埋点管理类\n * 修订历史：\n * ================================================\n */\npublic class DataPickManager {\n    private static final String TAG = \"DataPickManager\";\n    /**\n     * 埋点集合\n     */\n    private List<DataPickBean.EventBean> events = new ArrayList<>();\n\n    private DataPickBean dataPickBean = new DataPickBean();\n\n    private static class Holder {\n        private static DataPickManager INSTANCE = new DataPickManager();\n    }\n\n    public static DataPickManager getInstance() {\n        return DataPickManager.Holder.INSTANCE;\n    }\n\n\n    public void addData(@NonNull String eventName) {\n        addData(eventName, \"\");\n    }\n\n    public void addData(@NonNull String eventName, @NonNull String pageId) {\n        addData(eventName, pageId,\"\");\n    }\n\n    /**\n     * 添加埋点数据\n     *\n     * @param eventName\n     */\n    public void addData(@NonNull String eventName, @NonNull String pageId,String businessName) {\n\n        DataPickBean.EventBean eventBean = new DataPickBean.EventBean(eventName, pageId,businessName);\n        if (events != null) {\n            events.add(eventBean);\n            //链表数据大于10s 上传数据\n            if (events.size() >= 10) {\n                postData();\n                return;\n            }\n            //两个埋点之间的时间大于等于60s上传数据\n            if (events.size() >= 2) {\n                long lastTime = Long.parseLong(events.get(events.size() - 1).getTime());\n                long lastSecondTime = Long.parseLong(events.get(events.size() - 2).getTime());\n                if (lastTime - lastSecondTime >= 60 * 1000) {\n                    postData();\n                }\n            }\n\n        } else {\n            events = new ArrayList<>();\n            events.add(eventBean);\n        }\n    }\n\n    private static int jsonFromFile = 100;\n\n    private static int jsonFromMemory = 101;\n\n    /**\n     * 上传埋点数据\n     */\n    public void postData() {\n        if (!DoKitManager.INSTANCE.getENABLE_UPLOAD()) {\n            return;\n        }\n\n        //先检查本地是否存在缓存数据\n        String strJson = FileIOUtils.readFile2String(filePath);\n        if (!TextUtils.isEmpty(strJson)) {\n            //上传数据\n            try {\n                realPost(jsonFromFile, strJson);\n            } catch (Exception e) {\n                //e.printStackTrace();\n            }\n            return;\n        }\n        //判断对象是否为null\n        if (events == null || events.size() == 0) {\n            return;\n        }\n        dataPickBean.setEvents(events);\n        strJson = GsonUtils.toJson(dataPickBean);\n        try {\n            realPost(jsonFromMemory, strJson);\n        } catch (Exception e) {\n            //e.printStackTrace();\n        }\n\n    }\n\n    /**\n     * 真正需要上传的方法\n     */\n    private void realPost(final int from, String content) throws Exception {\n\n        //LogHelper.i(TAG,\"content===>\" + content);\n        //LogHelper.i(TAG, \"====realPost======from==>\" + from);\n        Request requset = new JsonObjectRequest(Request.Method.POST, NetworkManager.APP_DATA_PICK_URL, new JSONObject(content), new Response.Listener<JSONObject>() {\n\n            @Override\n            public void onResponse(JSONObject response) {\n//                LogHelper.e(TAG, \"success===>\" + response.toString());\n                if (from == jsonFromFile) {\n                    FileUtils.delete(filePath);\n                }\n                if (from == jsonFromMemory) {\n                    events.clear();\n                }\n            }\n        }, new Response.ErrorListener() {\n            @Override\n            public void onErrorResponse(VolleyError error) {\n                LogHelper.e(TAG, \"error===>\" + error.getMessage());\n                //ToastUtils.showShort(\"上传埋点失败\");\n            }\n        });\n\n        VolleyManager.INSTANCE.add(requset);\n    }\n\n    private String filePath = PathUtils.getInternalAppFilesPath() + File.separator + \"dokit.json\";\n\n    /**\n     * 异常情况下保存到本地保存到本地\n     */\n    public void saveData2Local() {\n        if (events == null || events.size() == 0) {\n            return;\n        }\n        dataPickBean.setEvents(events);\n        //保存数据到本地\n        FileIOUtils.writeFileFromString(filePath, GsonUtils.toJson(dataPickBean));\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/datapick/DataPickUtils.java",
    "content": "package com.didichuxing.doraemonkit.datapick;\n\nimport android.app.Activity;\n\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\n\n/**\n * didi Create on 2022/7/14 .\n * <p>\n * Copyright (c) 2022/7/14 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/7/14 2:57 下午\n * @Description 用一句话说明文件功能\n */\n\npublic class DataPickUtils {\n\n\n    private static String mDoKitHomeClickPage = \"\";\n\n    private DataPickUtils() {\n    }\n\n    public static String getCurrentPage() {\n        Activity activity = ActivityUtils.getTopActivity();\n        if (activity != null) {\n            return activity.getClass().getName();\n        }\n        return \"\";\n    }\n\n\n    public static void setDoKitHomeClickPage(String pageId) {\n        mDoKitHomeClickPage = pageId;\n    }\n\n    public static String getDoKitHomeClickPage() {\n        return mDoKitHomeClickPage;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/extension/DokitExtension.kt",
    "content": "package com.didichuxing.doraemonkit.extension\n\nimport android.app.Activity\nimport android.util.Log\nimport com.didichuxing.doraemonkit.aop.DokitThirdLibInfo\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager\nimport com.didichuxing.doraemonkit.util.EncodeUtils\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport kotlinx.coroutines.*\nimport okhttp3.RequestBody\nimport okio.Buffer\nimport java.util.*\nimport kotlin.reflect.KClass\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/2/8-10:42\n * 描    述：\n * 修订历史：\n * ================================================\n */\n\n\nval doKitGlobalScope: CoroutineScope by lazy {\n    MainScope() + CoroutineName(\"DoKit\")\n}\n\nval doKitGlobalExceptionHandler: CoroutineExceptionHandler by lazy {\n    CoroutineExceptionHandler { coroutineContext, throwable ->\n        Log.e(\"DoKit\", \"${coroutineContext[CoroutineName]} ：$throwable\")\n    }\n}\n\n\nval Activity.tagName: String\n    get() {\n        return this.javaClass.canonicalName ?: \"\"\n    }\n\nval AbsDoKitView.tagName: String\n    get() {\n        return this.javaClass.canonicalName ?: \"\"\n    }\n\nval KClass<out Any>.tagName: String\n    get() {\n        return this.java.canonicalName ?: \"\"\n    }\n\nval Class<out Any>.tagName: String\n    get() {\n        return this.canonicalName ?: \"\"\n    }\n\n\n/**\n * Boolean 扩展函数\n */\nfun Boolean?.isTrue(\n    error: (String) -> Unit = { LogHelper.e(\"DoKit\", it) },\n    isFalse: () -> Unit = {},\n    action: () -> Unit\n) {\n    if (this == null) {\n        error(\"Boolean is null\")\n    }\n    if (this == true) {\n        action()\n    } else {\n        isFalse()\n    }\n}\n\nsuspend fun Boolean?.isTrueWithCor(\n    error: suspend (String) -> Unit = { LogHelper.e(\"DoKit\", it) },\n    isFalse: suspend () -> Unit = {},\n    action: suspend () -> Unit\n) {\n    if (this == null) {\n        error(\"Boolean is null\")\n    }\n    if (this == true) {\n        action()\n    } else {\n        isFalse()\n    }\n}\n\n\n/**\n * Boolean 扩展函数\n */\nfun Boolean?.isFalse(\n    error: (String) -> Unit = { LogHelper.e(\"DoKit\", it) },\n    isTrue: () -> Unit = {},\n    action: () -> Unit\n) {\n    if (this == null) {\n        error(\"Boolean is null\")\n    }\n    if (this == false) {\n        action()\n    } else {\n        isTrue()\n    }\n}\n\n/**\n * Boolean 扩展函数\n */\nsuspend fun Boolean?.isFalseWithCor(\n    error: suspend (String) -> Unit = { LogHelper.e(\"DoKit\", it) },\n    isTrue: suspend () -> Unit = {},\n    action: suspend () -> Unit\n) {\n    if (this == null) {\n        error(\"Boolean is null\")\n    }\n    if (this == false) {\n        action()\n    } else {\n        isTrue()\n    }\n}\n\n/**\n * 查找三方库时候存在\n */\nfun hasThirdLib(groupId: String, artifactId: String): Boolean {\n    return try {\n        val value = DokitThirdLibInfo.THIRD_LIB_INFOS_SIMPLE[\"${groupId}:${artifactId}\"]\n        value != null\n    } catch (e: Exception) {\n        false\n    }\n}\n\n/**\n * a=aa&b=bb&c=cc\n * query键值对字符串转成Map\n */\nfun String.toMap(): MutableMap<String, String> {\n\n    val map = mutableMapOf<String, String>()\n\n    if (this.isBlank()) {\n        return map\n    }\n    val params = this.split(\"&\")\n    params.forEach { kv ->\n        val kvs = kv.split(\"=\")\n        if (kvs.size == 2) {\n            map[kvs[0]] = kvs[1]\n        }\n    }\n    return map\n}\n\n/**\n * RequestBody 转为字符串\n */\nfun RequestBody?.string(): String {\n    if (this != null && this.contentLength() > 0) {\n        val buffer = Buffer()\n        this.writeTo(buffer)\n        val stringBody = EncodeUtils.urlDecode(buffer.readUtf8())\n        return stringBody\n    }\n    return \"\"\n}\n\n\n/**\n * queryBody转成Map\n */\nfun RequestBody?.toMap(): MutableMap<String, String> {\n    var map = mutableMapOf<String, String>()\n    if (this == null || this.contentType() == null) {\n        return map\n    }\n\n    val buffer = Buffer()\n    this.writeTo(buffer)\n    val strBody = EncodeUtils.urlDecode(buffer.readUtf8())\n    strBody?.let {\n        val contentType = this.contentType().toString().toLowerCase(Locale.ROOT)\n        when {\n            contentType.contains(DokitDbManager.MEDIA_TYPE_FORM) -> {\n                return strBody.toMap()\n            }\n            contentType.contains(DokitDbManager.MEDIA_TYPE_JSON) -> {\n                try {\n                    map = GsonUtils.fromJson<MutableMap<String, String>>(strBody, MutableMap::class.java)\n                } catch (e: Exception) {\n                    map[\"json\"] = strBody\n                }\n                return map\n            }\n            contentType.contains(DokitDbManager.MEDIA_TYPE_PLAIN) -> {\n                map[\"plain\"] = strBody\n                return map\n            }\n            else -> {\n                map[\"other\"] = strBody\n                return map\n            }\n        }\n    }\n\n    return map\n}\n\n\n/**\n * 对map 针对key进行排序\n */\nfun MutableMap<String, String>.sortedByKey(): Map<String, String> {\n    return this.toList().sortedBy { (key, _) -> key }.toMap()\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/AbstractKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit\n\nimport android.app.Activity\nimport android.content.Context\nimport android.os.Bundle\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.util.ActivityUtils\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-20-15:29\n * 描    述：内置工具必须重写innerKitId而且需要和doraemonkit模块下的assets/dokit_system_kits.json文件中的innerKitId保持一致\n * 否则该工具无法在工具面板中显示\n * 修订历史：\n * ================================================\n */\nabstract class AbstractKit : IKit {\n    /**\n     * 启动UniversalActivity\n     *\n     * @param fragmentClass\n     * @param context\n     * @param bundle\n     * @param isSystemFragment 是否是内置kit\n     */\n    fun startUniversalActivity(\n        fragmentClass: Class<out BaseFragment>,\n        context: Context?,\n        bundle: Bundle? = null,\n        isSystemFragment: Boolean = false\n    ) {\n        DoKit.launchFullScreen(fragmentClass, context, bundle, isSystemFragment)\n    }\n\n\n    /**\n     * 是否是内置kit 外部kit不需要实现\n     *\n     * @return\n     */\n    open val isInnerKit: Boolean\n        get() = false\n\n    /**\n     * 是否可以显示在工具面板上\n     */\n    var canShow: Boolean = true\n\n    /**\n     * 返回kitId\n     * 内置工具必须返回而且需要和doraemonkit模块下的assets/dokit_system_kits.json文件中的innerKitId保持一致\n     * 否则该工具无法在工具面板中显示\n     * @return\n     */\n    open fun innerKitId(): String {\n        return \"\"\n    }\n\n\n    /**\n     * 返回当前栈顶的activity\n     * @return activity\n     */\n    fun currentActivity(): Activity? {\n        return ActivityUtils.getTopActivity()\n    }\n\n    @Deprecated(\"已废弃，重不重写都不影响功能\", ReplaceWith(\"\"))\n    override val category: Int\n        get() = Category.DEFAULT\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/Category.java",
    "content": "package com.didichuxing.doraemonkit.kit;\n\n/**\n * Created by jint on 2018/6/22.\n * 已弃用 保留是为了兼容以前的api\n */\n\n@Deprecated\npublic interface Category {\n    /**\n     * 业务模块\n     */\n    int BIZ = 0;\n    /**\n     * 常用工具模块\n     */\n    int TOOLS = 1;\n\n    /**\n     * 性能监控模块\n     */\n    int PERFORMANCE = 2;\n\n    /**\n     * 视觉工具模块\n     */\n    int UI = 3;\n    /**\n     * 平台工具模块\n     */\n    int PLATFORM = 4;\n    /**\n     * 关闭\n     */\n    int CLOSE = 5;\n    /**\n     * Dokit版本号\n     */\n    int VERSION = 6;\n\n    /**\n     * 浮标模式 系统或者内置\n     */\n    int FLOAT_MODE = 7;\n    /**\n     * weex\n     */\n    int WEEX = 8;\n\n\n    /**\n     * default\n     */\n    int DEFAULT = 9;\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/IKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit\n\nimport android.app.Activity\nimport android.content.Context\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\n\n/**\n * Created by zhangweida on 2018/6/22.\n * 工具入口 请继承AbstractKit\n */\ninternal interface IKit {\n    /**\n     * 返回分类\n     *\n     * @return int\n     */\n    val category: Int\n\n    /**\n     * 返回名称\n     *\n     * @return\n     */\n    @get:StringRes\n    val name: Int\n\n    /**\n     * 返回图标\n     *\n     * @return\n     */\n    @get:DrawableRes\n    val icon: Int\n\n    /**\n     * 点击回调\n     *\n     * @param context\n     */\n    @Deprecated(\"请使用onClickWithReturn代替\")\n    fun onClick(context: Context?) {\n    }\n\n    /**\n     * 点击回调 带返回值\n     * @return true 隐藏面板 false 不隐藏面板\n     */\n    fun onClickWithReturn(activity: Activity): Boolean {\n        return true\n    }\n\n    /**\n     * app 初始化时调用\n     *\n     * @param context\n     */\n    fun onAppInit(context: Context?)\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/alignruler/AlignLineView.java",
    "content": "package com.didichuxing.doraemonkit.kit.alignruler;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.util.AttributeSet;\nimport android.view.View;\n\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\n\n/**\n * Created by wanglikun on 2018/12/4.\n */\n\npublic class AlignLineView extends View {\n    private Paint mTextPaint;\n    private Paint mLinePaint;\n\n    private int mPosX = -1;\n    private int mPosY = -1;\n\n    private boolean mIncludeStatusBarHeight;\n\n    public AlignLineView(Context context) {\n        super(context);\n        init();\n    }\n\n    public AlignLineView(Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        init();\n    }\n\n    public AlignLineView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init();\n    }\n\n    private void init() {\n        mLinePaint = new Paint();\n        mLinePaint.setAntiAlias(true);\n        mLinePaint.setColor(getResources().getColor(R.color.dk_color_CC3A4B));\n\n        mTextPaint = new Paint();\n        mTextPaint.setAntiAlias(true);\n        mTextPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.dk_font_size_14));\n        mTextPaint.setColor(getResources().getColor(R.color.dk_color_333333));\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        drawLine(canvas);\n        drawText(canvas);\n    }\n\n    private void drawText(Canvas canvas) {\n        if (mPosY == -1 && mPosX == -1) {\n            return;\n        }\n        int left = mPosX;\n        int right = getWidth() - mPosX;\n        int top = mPosY;\n        int bottom = getHeight() - mPosY;\n        canvas.drawText(String.valueOf(left), left / 2, mPosY, mTextPaint);\n        canvas.drawText(String.valueOf(right), (mPosX + getWidth()) / 2, mPosY, mTextPaint);\n        canvas.drawText(String.valueOf(top + (mIncludeStatusBarHeight ? UIUtils.getStatusBarHeight() : 0)), mPosX, top / 2, mTextPaint);\n        canvas.drawText(String.valueOf(bottom), mPosX, (mPosY + getHeight()) / 2, mTextPaint);\n    }\n\n    private void drawLine(Canvas canvas) {\n        if (mPosY == -1 && mPosX == -1) {\n            return;\n        }\n        canvas.drawLine(0, mPosY, getWidth(), mPosY, mLinePaint);\n        canvas.drawLine(mPosX, 0, mPosX, getHeight(), mLinePaint);\n    }\n\n    public void showInfo(int x, int y) {\n        mPosX = x;\n        mPosY = y;\n        invalidate();\n    }\n\n    public void refreshInfo(boolean includeStatusBarHeight){\n        mIncludeStatusBarHeight = includeStatusBarHeight;\n        invalidate();\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/alignruler/AlignRulerInfoDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.alignruler;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.widget.CheckBox;\nimport android.widget.CompoundButton;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.AlignRulerConfig;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\n/**\n * Created by jintai on 2019/09/26.\n */\n\npublic class AlignRulerInfoDoKitView extends AbsDoKitView implements AlignRulerMarkerDoKitView.OnAlignRulerMarkerPositionChangeListener {\n    private TextView mAlignHex;\n    private ImageView mClose;\n\n    private AlignRulerMarkerDoKitView mMarker;\n    private int mWindowWidth;\n    private int mWindowHeight;\n\n    private CheckBox mIncludeStatusBarHeight;\n    private OnCheckedChangeListener mListener;\n\n    private int left, right, top, bottom;\n\n    @Override\n    public void onCreate(Context context) {\n        mWindowWidth = UIUtils.getWidthPixels();\n        mWindowHeight = UIUtils.getHeightPixels();\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        mMarker.removePositionChangeListener(this);\n    }\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_align_ruler_info, null);\n    }\n\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.width = getScreenShortSideLength();\n        params.height = WindowManager.LayoutParams.WRAP_CONTENT;\n        params.x = 0;\n        params.y = UIUtils.getHeightPixels() - UIUtils.dp2px(150);\n    }\n\n    @Override\n    public void onViewCreated(FrameLayout view) {\n        postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                mMarker = DoKit.getDoKitView(ActivityUtils.getTopActivity(), AlignRulerMarkerDoKitView.class);\n                if (mMarker != null) {\n                    mMarker.addPositionChangeListener(AlignRulerInfoDoKitView.this);\n                }\n            }\n        }, 100);\n        initView();\n    }\n\n    private void initView() {\n        getDoKitView().setOnTouchListener(new View.OnTouchListener() {\n            @Override\n            public boolean onTouch(View v, MotionEvent event) {\n                return mTouchProxy.onTouchEvent(v, event);\n            }\n        });\n\n        mAlignHex = findViewById(R.id.align_hex);\n        mClose = findViewById(R.id.close);\n        mClose.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                AlignRulerConfig.setAlignRulerOpen(false);\n                DoKit.removeFloating(AlignRulerMarkerDoKitView.class);\n                DoKit.removeFloating(AlignRulerLineDoKitView.class);\n                DoKit.removeFloating(AlignRulerInfoDoKitView.class);\n            }\n        });\n\n        mIncludeStatusBarHeight = findViewById(R.id.cb_status_bar);\n        mIncludeStatusBarHeight.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {\n            @Override\n            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {\n                if (mListener != null) {\n                    mListener.onCheckedChanged(isChecked);\n                }\n                setTextInfo(isChecked);\n            }\n        });\n    }\n\n    public void setCheckBoxListener(OnCheckedChangeListener mListener) {\n        this.mListener = mListener;\n    }\n\n\n    public interface OnCheckedChangeListener {\n        void onCheckedChanged(boolean isChecked);\n    }\n\n    @Override\n    public void onPositionChanged(int x, int y) {\n        left = x;\n        top = y;\n        right = mWindowWidth - left;\n        bottom = mWindowHeight - top;\n        setTextInfo(mIncludeStatusBarHeight.isChecked());\n    }\n\n\n    private void setTextInfo(boolean includeStatusBar) {\n        if (includeStatusBar) {\n            mAlignHex.setText(getResources().getString(R.string.dk_align_info_text, left, right, top + UIUtils.getStatusBarHeight(), bottom));\n        } else {\n            mAlignHex.setText(getResources().getString(R.string.dk_align_info_text, left, right, top, bottom));\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/alignruler/AlignRulerKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.alignruler\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.config.AlignRulerConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.util.ActivityUtils\nimport com.google.auto.service.AutoService\n\n/**\n * Created by wanglikun on 2018/9/19.\n */\n@AutoService(AbstractKit::class)\nclass AlignRulerKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_align_ruler\n    override val icon: Int\n        get() = R.mipmap.dk_align_ruler\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        DoKit.launchFloating<AlignRulerMarkerDoKitView>()\n        DoKit.launchFloating<AlignRulerLineDoKitView>()\n        DoKit.launchFloating<AlignRulerInfoDoKitView>()\n        DoKit.getDoKitView<AlignRulerInfoDoKitView>(ActivityUtils.getTopActivity())\n            ?.setCheckBoxListener { isChecked ->\n                DoKit.getDoKitView<AlignRulerLineDoKitView>(ActivityUtils.getTopActivity())\n                    ?.alignInfoView\n                    ?.refreshInfo(isChecked)\n            }\n        AlignRulerConfig.setAlignRulerOpen(true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n        AlignRulerConfig.setAlignRulerOpen(false)\n    }\n\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String = \"dokit_sdk_ui_ck_aligin_scaleplate\"\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/alignruler/AlignRulerLineDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.alignruler;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.widget.FrameLayout;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.util.ConvertUtils;\nimport com.didichuxing.doraemonkit.util.ScreenUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\n\n/**\n * Created by jintai on 2019/09/26.\n */\n\npublic class AlignRulerLineDoKitView extends AbsDoKitView implements AlignRulerMarkerDoKitView.OnAlignRulerMarkerPositionChangeListener {\n    private AlignRulerMarkerDoKitView mMarker;\n    private AlignLineView mAlignInfoView;\n\n    @Override\n    public void onCreate(Context context) {\n\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        mMarker.removePositionChangeListener(this);\n    }\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_align_ruler_line, view, false);\n    }\n\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;\n        params.height = DoKitViewLayoutParams.MATCH_PARENT;\n        params.width = DoKitViewLayoutParams.MATCH_PARENT;\n    }\n\n    @Override\n    public void onViewCreated(FrameLayout view) {\n        postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                mMarker = DoKit.getDoKitView(ActivityUtils.getTopActivity(), AlignRulerMarkerDoKitView.class);\n                if (mMarker != null) {\n                    mMarker.addPositionChangeListener(AlignRulerLineDoKitView.this);\n                }\n            }\n        }, 100);\n        setDoKitViewNotResponseTouchEvent(getDoKitView());\n        mAlignInfoView = findViewById(R.id.info_view);\n    }\n\n    @Override\n    public void onPositionChanged(int x, int y) {\n        /**\n         * 限制边界\n         */\n        if (!isNormalMode()) {\n            int iconSize = ConvertUtils.dp2px(30);\n            if (y <= iconSize) {\n                y = iconSize;\n            }\n\n            if (ScreenUtils.isPortrait()) {\n                if (y >= getScreenLongSideLength() - iconSize) {\n                    y = getScreenLongSideLength() - iconSize;\n                }\n            } else {\n                if (y >= getScreenShortSideLength() - iconSize) {\n                    y = getScreenShortSideLength() - iconSize;\n                }\n            }\n\n\n            if (x <= iconSize) {\n                x = iconSize;\n            }\n            if (ScreenUtils.isPortrait()) {\n                if (x >= getScreenShortSideLength() - iconSize) {\n                    x = getScreenShortSideLength() - iconSize;\n                }\n            } else {\n                if (x >= getScreenLongSideLength() - iconSize) {\n                    x = getScreenLongSideLength() - iconSize;\n                }\n            }\n        }\n\n\n        mAlignInfoView.showInfo(x, y);\n    }\n\n    @Override\n    public boolean canDrag() {\n        return false;\n    }\n\n    @Override\n    public boolean restrictBorderline() {\n        return true;\n    }\n\n    public AlignLineView getAlignInfoView() {\n        return mAlignInfoView;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/alignruler/AlignRulerMarkerDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.alignruler;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.FrameLayout;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by jintai on 2019/09/26.\n */\n\npublic class AlignRulerMarkerDoKitView extends AbsDoKitView {\n    private List<OnAlignRulerMarkerPositionChangeListener> mPositionChangeListeners = new ArrayList<>();\n\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_align_ruler_marker, null);\n    }\n\n    @Override\n    public void onViewCreated(FrameLayout view) {\n\n    }\n\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT;\n        params.width = DoKitViewLayoutParams.WRAP_CONTENT;\n        params.x = UIUtils.getWidthPixels() / 2;\n        params.y = UIUtils.getHeightPixels() / 2;\n    }\n\n    @Override\n    public void onCreate(Context context) {\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        removePositionChangeListeners();\n    }\n\n    @Override\n    public void onMove(int x, int y, int dx, int dy) {\n        super.onMove(x, y, dx, dy);\n        for (OnAlignRulerMarkerPositionChangeListener listener : mPositionChangeListeners) {\n            if (isNormalMode()) {\n                listener.onPositionChanged(getNormalLayoutParams().leftMargin + getDoKitView().getWidth() / 2, getNormalLayoutParams().topMargin + getDoKitView().getHeight() / 2);\n            } else {\n                listener.onPositionChanged(getSystemLayoutParams().x + getDoKitView().getWidth() / 2, getSystemLayoutParams().y + getDoKitView().getHeight() / 2);\n            }\n        }\n    }\n\n    @Override\n    public void updateViewLayout(String tag, boolean isActivityBackResume) {\n        super.updateViewLayout(tag, isActivityBackResume);\n        //更新标尺的位置信息\n        for (OnAlignRulerMarkerPositionChangeListener listener : mPositionChangeListeners) {\n            if (isNormalMode()) {\n                listener.onPositionChanged(getNormalLayoutParams().leftMargin + getDoKitView().getWidth() / 2, getNormalLayoutParams().topMargin + getDoKitView().getHeight() / 2);\n            } else {\n                listener.onPositionChanged(getSystemLayoutParams().x + getDoKitView().getWidth() / 2, getSystemLayoutParams().y + getDoKitView().getHeight() / 2);\n            }\n        }\n\n    }\n\n    public interface OnAlignRulerMarkerPositionChangeListener {\n        void onPositionChanged(int x, int y);\n    }\n\n    public void addPositionChangeListener(OnAlignRulerMarkerPositionChangeListener positionChangeListener) {\n        mPositionChangeListeners.add(positionChangeListener);\n        //更新标尺的位置信息\n        for (OnAlignRulerMarkerPositionChangeListener listener : mPositionChangeListeners) {\n            if (isNormalMode()) {\n                listener.onPositionChanged(getNormalLayoutParams().leftMargin + getDoKitView().getWidth() / 2, getNormalLayoutParams().topMargin + getDoKitView().getHeight() / 2);\n            } else {\n                listener.onPositionChanged(getSystemLayoutParams().x + getDoKitView().getWidth() / 2, getSystemLayoutParams().y + getDoKitView().getHeight() / 2);\n            }\n        }\n    }\n\n    public void removePositionChangeListener(OnAlignRulerMarkerPositionChangeListener positionChangeListener) {\n        mPositionChangeListeners.remove(positionChangeListener);\n    }\n\n    private void removePositionChangeListeners() {\n        mPositionChangeListeners.clear();\n    }\n\n    @Override\n    public boolean restrictBorderline() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/alignruler/AlignRulerSettingFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.alignruler;\n\nimport android.os.Bundle;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.AlignRulerConfig;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\n\n/**\n * Created by wanglikun on 2018/9/19.\n */\n\npublic class AlignRulerSettingFragment extends BaseFragment {\n    private HomeTitleBar mTitleBar;\n    private RecyclerView mSettingList;\n    private SettingItemAdapter mSettingItemAdapter;\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initTitleBar();\n    }\n\n    private void initTitleBar() {\n        mTitleBar = findViewById(R.id.title_bar);\n        mTitleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                finish();\n            }\n        });\n        mSettingList = findViewById(R.id.setting_list);\n        mSettingList.setLayoutManager(new LinearLayoutManager(getContext()));\n        mSettingItemAdapter = new SettingItemAdapter(getContext());\n        mSettingItemAdapter.append(new SettingItem(R.string.dk_kit_align_ruler, AlignRulerConfig.isAlignRulerOpen()));\n        mSettingList.setAdapter(mSettingItemAdapter);\n        mSettingItemAdapter.setOnSettingItemSwitchListener(new SettingItemAdapter.OnSettingItemSwitchListener() {\n            @Override\n            public void onSettingItemSwitch(View view, SettingItem data, boolean on) {\n                if (data.desc == R.string.dk_kit_align_ruler) {\n                    if (on) {\n                        DoKit.launchFloating(AlignRulerMarkerDoKitView.class);\n                        DoKit.launchFloating(AlignRulerLineDoKitView.class);\n                    } else {\n                        DoKit.removeFloating(AlignRulerMarkerDoKitView.class);\n                        DoKit.removeFloating(AlignRulerLineDoKitView.class);\n                    }\n                    AlignRulerConfig.setAlignRulerOpen(on);\n                }\n            }\n        });\n    }\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_align_ruler_setting;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/blockmonitor/BlockListAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.blockmonitor;\n\nimport android.content.Context;\nimport android.text.format.DateUtils;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.blockmonitor.bean.BlockInfo;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\n\nimport static android.text.format.DateUtils.FORMAT_SHOW_DATE;\nimport static android.text.format.DateUtils.FORMAT_SHOW_TIME;\n\npublic class BlockListAdapter extends AbsRecyclerAdapter<AbsViewBinder<BlockInfo>, BlockInfo> {\n\n    private OnItemClickListener mListener;\n\n    public BlockListAdapter(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected AbsViewBinder<BlockInfo> createViewHolder(View view, int viewType) {\n        return new ItemViewHolder(view);\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        return inflater.inflate(R.layout.dk_item_block_list, parent, false);\n    }\n\n    private class ItemViewHolder extends AbsViewBinder<BlockInfo> {\n        private TextView tvTime;\n        private TextView tvTitle;\n\n        public ItemViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            tvTime = getView(R.id.time);\n            tvTitle = getView(R.id.title);\n        }\n\n        @Override\n        public void bind(BlockInfo blockInfoEx) {\n\n        }\n\n        @Override\n        public void bind(final BlockInfo info, int position) {\n            String index;\n            index = (BlockListAdapter.this.getItemCount() - position) + \". \";\n            String title = index + info.concernStackString + \" \" +\n                    getContext().getString(R.string.dk_block_class_has_blocked, String.valueOf(info.timeCost));\n            tvTitle.setText(title);\n            String time = DateUtils.formatDateTime(getContext(),\n                    info.time, FORMAT_SHOW_TIME | FORMAT_SHOW_DATE);\n            tvTime.setText(time);\n            itemView.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    if (mListener != null) {\n                        mListener.onClick(info);\n                    }\n                }\n            });\n        }\n    }\n\n    public void setOnItemClickListener(OnItemClickListener listener) {\n        mListener = listener;\n    }\n\n    public interface OnItemClickListener {\n        void onClick(BlockInfo info);\n    }\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/blockmonitor/BlockListFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.blockmonitor;\n\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.blockmonitor.bean.BlockInfo;\nimport com.didichuxing.doraemonkit.kit.blockmonitor.core.BlockMonitorManager;\nimport com.didichuxing.doraemonkit.kit.blockmonitor.core.OnBlockInfoUpdateListener;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.recyclerview.DividerItemDecoration;\nimport com.didichuxing.doraemonkit.widget.titlebar.TitleBar;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\n\n/**\n * @desc: 卡顿检测日志列表\n */\n\npublic class BlockListFragment extends BaseFragment implements OnBlockInfoUpdateListener {\n    private static final String TAG = \"BlockMonitorIndexFragment\";\n\n    private RecyclerView mBlockList;\n    private BlockListAdapter mBlockListAdapter;\n    private TextView mLogDetail;\n    private TitleBar mTitleBar;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_block_list;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n        load();\n        BlockMonitorManager.getInstance().setOnBlockInfoUpdateListener(this);\n    }\n\n\n    private void initView() {\n        mBlockList = findViewById(R.id.block_list);\n        mLogDetail = findViewById(R.id.tx_block_detail);\n        //和长按复制功能冲突\n        //mLogDetail.setMovementMethod(ScrollingMovementMethod.getInstance());\n\n        LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());\n        mBlockList.setLayoutManager(layoutManager);\n        mBlockListAdapter = new BlockListAdapter(getContext());\n        mBlockList.setAdapter(mBlockListAdapter);\n        DividerItemDecoration decoration = new DividerItemDecoration(DividerItemDecoration.VERTICAL);\n        decoration.setDrawable(getResources().getDrawable(R.drawable.dk_divider));\n        mBlockList.addItemDecoration(decoration);\n        mBlockListAdapter.setOnItemClickListener(new BlockListAdapter.OnItemClickListener() {\n            @Override\n            public void onClick(BlockInfo info) {\n                LogHelper.i(TAG, info.toString());\n                mLogDetail.setText(info.toString());\n                mLogDetail.setVisibility(View.VISIBLE);\n                mBlockList.setVisibility(View.GONE);\n                mTitleBar.setTitle(getResources().getString(R.string.dk_kit_block_monitor_detail), false);\n            }\n        });\n        mTitleBar = findViewById(R.id.title_bar);\n        mTitleBar.setOnTitleBarClickListener(new TitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onLeftClick() {\n                getActivity().onBackPressed();\n            }\n\n            @Override\n            public void onRightClick() {\n\n            }\n        });\n    }\n\n    @Override\n    public boolean onBackPressed() {\n        if (mLogDetail.getVisibility() == View.VISIBLE) {\n            mLogDetail.setVisibility(View.GONE);\n            mBlockList.setVisibility(View.VISIBLE);\n            mTitleBar.setTitle(R.string.dk_kit_block_monitor_list);\n            return true;\n        }\n        return super.onBackPressed();\n    }\n\n\n    private void load() {\n        List<BlockInfo> infos = new ArrayList<>(BlockMonitorManager.getInstance().getBlockInfoList());\n        Collections.sort(infos, new Comparator<BlockInfo>() {\n            @Override\n            public int compare(BlockInfo lhs, BlockInfo rhs) {\n                return Long.valueOf(rhs.time)\n                        .compareTo(lhs.time);\n            }\n        });\n        mBlockListAdapter.setData(infos);\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        BlockMonitorManager.getInstance().setOnBlockInfoUpdateListener(null);\n    }\n\n    @Override\n    public void onBlockInfoUpdate(BlockInfo blockInfo) {\n        mBlockListAdapter.append(blockInfo, 0);\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/blockmonitor/BlockMonitorFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.blockmonitor;\n\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.blockmonitor.core.BlockMonitorManager;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\n\n/**\n * @desc: 卡顿检测首页\n */\n\npublic class BlockMonitorFragment extends BaseFragment {\n    private static final String TAG = \"BlockMonitorIndexFragment\";\n    public static final String KEY_JUMP_TO_LIST = \"KEY_JUMP_TO_LIST\";\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_block_monitor_index;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n    }\n\n\n    private void initView() {\n        HomeTitleBar title = findViewById(R.id.title_bar);\n        title.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                getActivity().finish();\n            }\n        });\n        RecyclerView mSettingList = findViewById(R.id.setting_list);\n        mSettingList.setLayoutManager(new LinearLayoutManager(getContext()));\n        SettingItemAdapter settingItemAdapter = new SettingItemAdapter(getContext());\n        mSettingList.setAdapter(settingItemAdapter);\n        settingItemAdapter.append(new SettingItem(R.string.dk_item_block_switch, BlockMonitorManager.getInstance().isRunning()));\n        settingItemAdapter.append(new SettingItem(R.string.dk_item_block_goto_list));\n        settingItemAdapter.append(new SettingItem(R.string.dk_item_block_mock));\n\n        settingItemAdapter.setOnSettingItemSwitchListener(new SettingItemAdapter.OnSettingItemSwitchListener() {\n            @Override\n            public void onSettingItemSwitch(View view, SettingItem data, boolean on) {\n                if (data.desc == R.string.dk_item_block_switch) {\n                    if (on) {\n                        BlockMonitorManager.getInstance().start();\n                    } else {\n                        BlockMonitorManager.getInstance().stop();\n                    }\n                }\n            }\n        });\n        settingItemAdapter.setOnSettingItemClickListener(new SettingItemAdapter.OnSettingItemClickListener() {\n            @Override\n            public void onSettingItemClick(View view, SettingItem data) {\n                if (data.desc == R.string.dk_item_block_goto_list) {\n                    showContent(BlockListFragment.class);\n                } else if (data.desc == R.string.dk_item_block_mock) {\n                    mockBlock();\n                }\n            }\n        });\n\n        if (getArguments() != null) {\n            boolean jump = getArguments().getBoolean(KEY_JUMP_TO_LIST, false);\n            if (jump) {\n                showContent(BlockListFragment.class);\n            }\n        }\n    }\n\n    private void mockBlock() {\n        try {\n            getView().postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    try {\n                        Thread.sleep(2000);\n                    } catch (InterruptedException e) {\n                        e.printStackTrace();\n                    }\n                }\n            }, 1000);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/blockmonitor/BlockMonitorKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.blockmonitor\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n/**\n * @desc: 卡顿检测kit\n */\n@AutoService(AbstractKit::class)\nclass BlockMonitorKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_block_monitor\n    override val icon: Int\n        get() = R.mipmap.dk_block_monitor\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        startUniversalActivity(BlockMonitorFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_performance_ck_block\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/blockmonitor/bean/BlockInfo.java",
    "content": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.kit.blockmonitor.bean;\n\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Locale;\n\n/**\n * Information to trace a block.\n */\npublic class BlockInfo {\n\n    public static final String SEPARATOR = \"\\r\\n\";\n    private static final String KV = \" = \";\n    private static final SimpleDateFormat TIME_FORMATTER =\n            new SimpleDateFormat(\"MM-dd HH:mm:ss.SSS\", Locale.CHINESE);\n\n    public static final String NEW_INSTANCE_METHOD = \"newInstance: \";\n    private static final String KEY_TIME_COST = \"time\";\n    private static final String KEY_THREAD_TIME_COST = \"thread-time\";\n    private static final String KEY_TIME_COST_START = \"time-start\";\n    private static final String KEY_TIME_COST_END = \"time-end\";\n    private static final String KEY_STACK = \"stack\";\n\n\n    // Per Block Info fields\n    public long timeCost;\n    private long threadTimeCost;\n    public long time;\n\n    public String timeStart;\n    private String timeEnd;\n    public ArrayList<String> threadStackEntries = new ArrayList<>();\n\n    private StringBuilder timeSb = new StringBuilder();\n    private StringBuilder stackSb = new StringBuilder();\n    public String concernStackString;\n\n    public static BlockInfo newInstance() {\n        BlockInfo blockInfo = new BlockInfo();\n        return blockInfo;\n    }\n\n    public BlockInfo flushString() {\n        String separator = SEPARATOR;\n\n        timeSb.append(KEY_TIME_COST).append(KV).append(timeCost).append(separator);\n        timeSb.append(KEY_THREAD_TIME_COST).append(KV).append(threadTimeCost).append(separator);\n        timeSb.append(KEY_TIME_COST_START).append(KV).append(timeStart).append(separator);\n        timeSb.append(KEY_TIME_COST_END).append(KV).append(timeEnd).append(separator);\n\n\n        if (threadStackEntries != null && !threadStackEntries.isEmpty()) {\n            StringBuilder temp = new StringBuilder();\n            for (String s : threadStackEntries) {\n                temp.append(s);\n                temp.append(separator);\n            }\n            stackSb.append(KEY_STACK).append(KV).append(temp.toString()).append(separator);\n        }\n        return this;\n    }\n\n\n    public BlockInfo setThreadStackEntries(ArrayList<String> threadStackEntries) {\n        this.threadStackEntries = threadStackEntries;\n        return this;\n    }\n\n    public BlockInfo setMainThreadTimeCost(long realTimeStart, long realTimeEnd, long threadTimeStart, long threadTimeEnd) {\n        timeCost = realTimeEnd - realTimeStart;\n        threadTimeCost = threadTimeEnd - threadTimeStart;\n        timeStart = TIME_FORMATTER.format(realTimeStart);\n        timeEnd = TIME_FORMATTER.format(realTimeEnd);\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        return timeSb.toString() + \"\\n\" + stackSb.toString();\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/blockmonitor/core/BlockCanaryUtils.java",
    "content": "package com.didichuxing.doraemonkit.kit.blockmonitor.core;\n\nimport android.content.Context;\nimport android.text.TextUtils;\n\nimport com.didichuxing.doraemonkit.kit.blockmonitor.bean.BlockInfo;\nimport com.didichuxing.doraemonkit.util.DoKitSystemUtil;\n\npublic final class BlockCanaryUtils {\n\n    private static String sProcessName;\n    private static boolean sProcessNameFirstGetFlag = false;\n\n    private static final String CURRENT_PACKAGE = \"com.didichuxing.doraemonkit\";\n\n    public static String concernStackString(Context context, BlockInfo blockInfo) {\n        String result = \"\";\n        for (String stackEntry : blockInfo.threadStackEntries) {\n            if (!TextUtils.isEmpty(stackEntry)) {\n                String[] lines = stackEntry.split(BlockInfo.SEPARATOR);\n                for (String line : lines) {\n                    String keyStackString = concernStackString(context, line);\n                    if (keyStackString != null) {\n                        return keyStackString;\n                    }\n                }\n                return classSimpleName(lines[0]);\n            }\n        }\n        return result;\n    }\n\n    public static boolean isBlockInfoValid(BlockInfo blockInfo) {\n        boolean isValid = !TextUtils.isEmpty(blockInfo.timeStart);\n        isValid = isValid && blockInfo.timeCost >= 0;\n        return isValid;\n    }\n\n\n    private static String concernStackString(Context context, String line) {\n        if (line == null) {\n            return null;\n        }\n        if (!sProcessNameFirstGetFlag) {\n            sProcessNameFirstGetFlag = true;\n            sProcessName = DoKitSystemUtil.obtainProcessName(context);\n        }\n        if (sProcessName == null || line.startsWith(sProcessName) || line.startsWith(CURRENT_PACKAGE)) {\n            return classSimpleName(line);\n        }\n        return null;\n    }\n\n    private static String classSimpleName(String stackLine) {\n        int index1 = stackLine.indexOf('(');\n        int index2 = stackLine.indexOf(')');\n        if (index1 >= 0 && index2 >= 0) {\n            return stackLine.substring(index1 + 1, index2);\n        }\n        return stackLine;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/blockmonitor/core/BlockMonitorManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.blockmonitor.core;\n\nimport static android.app.PendingIntent.FLAG_UPDATE_CURRENT;\n\nimport android.app.PendingIntent;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.os.Debug;\nimport android.os.Looper;\nimport android.text.TextUtils;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.DoKitEnv;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.constant.BundleKey;\nimport com.didichuxing.doraemonkit.constant.FragmentIndex;\nimport com.didichuxing.doraemonkit.kit.blockmonitor.BlockMonitorFragment;\nimport com.didichuxing.doraemonkit.kit.blockmonitor.bean.BlockInfo;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.core.UniversalActivity;\nimport com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil;\nimport com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo;\nimport com.didichuxing.doraemonkit.kit.timecounter.TimeCounterManager;\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.util.DoKitNotificationUtils;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * @desc: 卡顿检测管理类\n */\npublic class BlockMonitorManager {\n    private static final String TAG = \"BlockMonitorManager\";\n    private static final int MAX_SIZE = 50;\n\n\n    private static class Holder {\n        private static BlockMonitorManager INSTANCE = new BlockMonitorManager();\n    }\n\n    private boolean mIsRunning;\n    private MonitorCore mMonitorCore;\n    private Context mContext;\n    private List<BlockInfo> mBlockInfoList = Collections.synchronizedList(new ArrayList<BlockInfo>());\n    private OnBlockInfoUpdateListener mOnBlockInfoUpdateListener;\n\n\n    public static BlockMonitorManager getInstance() {\n        return BlockMonitorManager.Holder.INSTANCE;\n    }\n\n    private BlockMonitorManager() {\n\n    }\n\n    public void start() {\n        if (mIsRunning) {\n            LogHelper.i(TAG, \"start when manager is running\");\n            return;\n        }\n\n        // 卡顿检测和跳转耗时统计都使用了Printer的方式，无法同时工作\n        TimeCounterManager.get().stop();\n        mContext = DoKitEnv.requireApp().getApplicationContext();\n        if (mMonitorCore == null) {\n            mMonitorCore = new MonitorCore();\n        }\n        mIsRunning = true;\n        Looper.getMainLooper().setMessageLogging(mMonitorCore);\n    }\n\n    public boolean isRunning() {\n        return mIsRunning;\n    }\n\n    public void stop() {\n        if (!mIsRunning) {\n            LogHelper.i(TAG, \"stop when manager is not running\");\n            return;\n        }\n        Looper.getMainLooper().setMessageLogging(null);\n        if (mMonitorCore != null) {\n            mMonitorCore.shutDown();\n            mMonitorCore = null;\n        }\n        DoKitNotificationUtils.cancelNotification(mContext, DoKitNotificationUtils.ID_SHOW_BLOCK_NOTIFICATION);\n        mIsRunning = false;\n        mContext = null;\n    }\n\n    public void setOnBlockInfoUpdateListener(OnBlockInfoUpdateListener onBlockInfoUpdateListener) {\n        mOnBlockInfoUpdateListener = onBlockInfoUpdateListener;\n    }\n\n    /**\n     * 动态添加卡顿信息到appHealth\n     *\n     * @param blockInfo\n     */\n    private void addBlockInfoInAppHealth(@NonNull BlockInfo blockInfo) {\n        try {\n            String activityName = ActivityUtils.getTopActivity().getClass().getCanonicalName();\n            AppHealthInfo.DataBean.BlockBean blockBean = new AppHealthInfo.DataBean.BlockBean();\n            blockBean.setPage(activityName);\n            blockBean.setBlockTime(blockInfo.timeCost);\n            blockBean.setDetail(blockInfo.toString());\n            AppHealthInfoUtil.getInstance().addBlockInfo(blockBean);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n\n    /**\n     * 通知卡顿\n     *\n     * @param blockInfo\n     */\n    void notifyBlockEvent(BlockInfo blockInfo) {\n        blockInfo.concernStackString = BlockCanaryUtils.concernStackString(mContext, blockInfo);\n        blockInfo.time = System.currentTimeMillis();\n        if (!TextUtils.isEmpty(blockInfo.concernStackString)) {\n            //卡顿 debug模式下会造成卡顿\n            if (DoKitManager.APP_HEALTH_RUNNING && !Debug.isDebuggerConnected()) {\n                addBlockInfoInAppHealth(blockInfo);\n            }\n            showNotification(blockInfo);\n            if (mBlockInfoList.size() > MAX_SIZE) {\n                mBlockInfoList.remove(0);\n            }\n            mBlockInfoList.add(blockInfo);\n            if (mOnBlockInfoUpdateListener != null) {\n                mOnBlockInfoUpdateListener.onBlockInfoUpdate(blockInfo);\n            }\n        }\n\n    }\n\n    private void showNotification(BlockInfo info) {\n        String contentTitle = mContext.getString(R.string.dk_block_class_has_blocked, info.timeStart);\n        String contentText = mContext.getString(R.string.dk_block_notification_message);\n\n\n        Intent intent = new Intent(mContext, UniversalActivity.class);\n\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);\n        intent.putExtra(BundleKey.FRAGMENT_INDEX, FragmentIndex.FRAGMENT_BLOCK_MONITOR);\n        intent.putExtra(BlockMonitorFragment.KEY_JUMP_TO_LIST, true);\n        PendingIntent pendingIntent;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            pendingIntent = PendingIntent.getActivity(mContext, 1, intent, FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);\n        } else {\n            pendingIntent = PendingIntent.getActivity(mContext, 1, intent, FLAG_UPDATE_CURRENT);\n        }\n        DoKitNotificationUtils.setInfoNotification(mContext, DoKitNotificationUtils.ID_SHOW_BLOCK_NOTIFICATION,\n                contentTitle, contentText, contentText, pendingIntent);\n    }\n\n    public List<BlockInfo> getBlockInfoList() {\n        return mBlockInfoList;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/blockmonitor/core/MonitorCore.java",
    "content": "package com.didichuxing.doraemonkit.kit.blockmonitor.core;\n\nimport android.os.SystemClock;\nimport android.util.Printer;\n\nimport com.didichuxing.doraemonkit.kit.blockmonitor.bean.BlockInfo;\n\nimport java.util.ArrayList;\n\n/**\n * @desc: 检测卡顿的日志类\n */\nclass MonitorCore implements Printer {\n    private static final String TAG = \"MonitorCore\";\n    /**\n     * 卡顿阈值\n     */\n    private static final int BLOCK_THRESHOLD_MILLIS = 200;\n\n    private long mStartTime = 0;\n    private long mStartThreadTime = 0;\n    private boolean mPrintingStarted = false;\n\n    private StackSampler mStackSampler;\n\n\n    public MonitorCore() {\n        mStackSampler = new StackSampler();\n        mStackSampler.init();\n    }\n\n    @Override\n    public void println(String x) {\n        if (!mPrintingStarted) {\n            mStartTime = System.currentTimeMillis();\n            mStartThreadTime = SystemClock.currentThreadTimeMillis();\n            mPrintingStarted = true;\n            mStackSampler.startDump();\n        } else {\n            final long endTime = System.currentTimeMillis();\n            long endThreadTime = SystemClock.currentThreadTimeMillis();\n            mPrintingStarted = false;\n            if (isBlock(endTime)) {\n                final ArrayList<String> entries = mStackSampler.getThreadStackEntries(mStartTime, endTime);\n                if (entries.size() > 0) {\n                    final BlockInfo blockInfo = BlockInfo.newInstance()\n                            .setMainThreadTimeCost(mStartTime, endTime, mStartThreadTime, endThreadTime)\n                            .setThreadStackEntries(entries)\n                            .flushString();\n                    BlockMonitorManager.getInstance().notifyBlockEvent(blockInfo);\n                }\n            }\n            mStackSampler.stopDump();\n        }\n    }\n\n    private boolean isBlock(long endTime) {\n        return endTime - mStartTime > BLOCK_THRESHOLD_MILLIS;\n    }\n\n\n    public void shutDown() {\n        mStackSampler.shutDown();\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/blockmonitor/core/OnBlockInfoUpdateListener.java",
    "content": "package com.didichuxing.doraemonkit.kit.blockmonitor.core;\n\nimport com.didichuxing.doraemonkit.kit.blockmonitor.bean.BlockInfo;\n\npublic interface OnBlockInfoUpdateListener {\n    void onBlockInfoUpdate(BlockInfo blockInfo);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/blockmonitor/core/StackSampler.java",
    "content": "package com.didichuxing.doraemonkit.kit.blockmonitor.core;\n\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Looper;\nimport android.text.TextUtils;\n\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.Locale;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * @desc: 堆栈信息采集类\n */\npublic class StackSampler {\n    private static final String TAG = \"StackSampler\";\n    private static final int DEFAULT_SAMPLE_INTERVAL = 300;\n    private static final int DEFAULT_MAX_ENTRY_COUNT = 100;\n    private static final String SEPARATOR = \"\\r\\n\";\n    private static final SimpleDateFormat TIME_FORMATTER =\n            new SimpleDateFormat(\"MM-dd HH:mm:ss.SSS\", Locale.CHINESE);\n\n    private AtomicBoolean mRunning = new AtomicBoolean(false);\n    private HandlerThread mStackThread;\n    private Handler mStackHandler;\n\n    private LinkedHashMap<Long, String> sStackMap = new LinkedHashMap<>();\n    private String mFilterCache;\n\n    public void init() {\n        if (mStackThread == null) {\n            mStackThread = new HandlerThread(\"BlockMonitor\") {\n                @Override\n                protected void onLooperPrepared() {\n                    mStackHandler = new Handler(mStackThread.getLooper());\n                }\n            };\n            mStackThread.start();\n        }\n    }\n\n    public void startDump() {\n        if (mStackHandler == null) {\n            return;\n        }\n        if (mRunning.get()) {\n            return;\n        }\n        mRunning.set(true);\n        mStackHandler.removeCallbacks(mRunnable);\n        mStackHandler.postDelayed(mRunnable, DEFAULT_SAMPLE_INTERVAL);\n    }\n\n    public ArrayList<String> getThreadStackEntries(long startTime, long endTime) {\n        ArrayList<String> result = new ArrayList<>();\n        synchronized (sStackMap) {\n            for (Long entryTime : sStackMap.keySet()) {\n                if (startTime < entryTime && entryTime < endTime) {\n                    result.add(TIME_FORMATTER.format(entryTime)\n                            + SEPARATOR\n                            + SEPARATOR\n                            + sStackMap.get(entryTime));\n                }\n            }\n        }\n        return result;\n    }\n\n    public void stopDump() {\n        if (mStackHandler == null) {\n            return;\n        }\n        if (!mRunning.get()) {\n            return;\n        }\n        mRunning.set(false);\n        mFilterCache = null;\n        mStackHandler.removeCallbacks(mRunnable);\n    }\n\n    public void shutDown() {\n        stopDump();\n        if (mStackThread != null) {\n            mStackThread.quit();\n        }\n    }\n\n\n    private Runnable mRunnable = new Runnable() {\n        @Override\n        public void run() {\n            dumpInfo();\n            if (mRunning.get()) {\n                mStackHandler.postDelayed(mRunnable, DEFAULT_SAMPLE_INTERVAL);\n            }\n        }\n    };\n\n    private void dumpInfo() {\n        StringBuilder stringBuilder = new StringBuilder();\n        Thread thread = Looper.getMainLooper().getThread();\n        for (StackTraceElement stackTraceElement : thread.getStackTrace()) {\n            stringBuilder\n                    .append(stackTraceElement.toString())\n                    .append(SEPARATOR);\n        }\n\n        synchronized (sStackMap) {\n            if (sStackMap.size() == DEFAULT_MAX_ENTRY_COUNT) {\n                sStackMap.remove(sStackMap.keySet().iterator().next());\n            }\n            if (!shouldIgnore(stringBuilder)) {\n                sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());\n            }\n        }\n    }\n\n    /**\n     * 过滤掉重复项\n     *\n     * @param builder\n     * @return\n     */\n    private boolean shouldIgnore(StringBuilder builder) {\n        if (TextUtils.equals(mFilterCache, builder.toString())) {\n            return true;\n        }\n        mFilterCache = builder.toString();\n        return false;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/colorpick/ColorPickConstants.java",
    "content": "package com.didichuxing.doraemonkit.kit.colorpick;\n\npublic class ColorPickConstants {\n    public static final int PIX_INTERVAL = 16;\n    public static final int PICK_AREA_SIZE = PIX_INTERVAL * 2;\n    public static final int PICK_VIEW_SIZE = PICK_AREA_SIZE * PIX_INTERVAL;\n    public static final String TEXT_FOCUS_INFO=\"%s   %d,%d\";\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/colorpick/ColorPickManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.colorpick;\n\nimport android.media.projection.MediaProjection;\nimport android.media.projection.MediaProjectionManager;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020-02-21-17:45\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class ColorPickManager {\n    private MediaProjectionManager mMediaProjectionManager;\n    private MediaProjection mMediaProjection;\n\n    private ColorPickerDoKitView mColorPickerDokitView;\n\n    private static class Holder {\n        private static ColorPickManager INSTANCE = new ColorPickManager();\n    }\n\n    public static ColorPickManager getInstance() {\n        return ColorPickManager.Holder.INSTANCE;\n    }\n\n\n    MediaProjection getMediaProjection() {\n        return mMediaProjection;\n    }\n\n    void setMediaProjection(MediaProjection mMediaProjection) {\n        this.mMediaProjection = mMediaProjection;\n    }\n\n    public ColorPickerDoKitView getColorPickerDokitView() {\n        return mColorPickerDokitView;\n    }\n\n    public void setColorPickerDokitView(ColorPickerDoKitView mColorPickerDokitView) {\n        this.mColorPickerDokitView = mColorPickerDokitView;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/colorpick/ColorPickerDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.colorpick;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.os.Build;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.WindowManager;\nimport android.widget.FrameLayout;\n\nimport androidx.annotation.RequiresApi;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.util.DoKitImageUtil;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\n/**\n * @author jintai\n * @date 2019/09/26\n */\n@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\npublic class ColorPickerDoKitView extends AbsDoKitView {\n\n    private ImageCapture mImageCapture;\n    private ColorPickerView mPickerView;\n    private ColorPickerInfoDoKitView mInfoDokitView;\n    private int width;\n    private int height;\n    private int statusBarHeight;\n\n    private Runnable mRunnable;\n\n    @Override\n    public void onCreate(Context context) {\n        ColorPickManager.getInstance().setColorPickerDokitView(this);\n        mInfoDokitView = DoKit.getDoKitView(ActivityUtils.getTopActivity(), ColorPickerInfoDoKitView.class);\n        mImageCapture = new ImageCapture();\n        try {\n            mImageCapture.init(context, getBundle(), this);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * 当服务准备好\n     */\n    void onScreenServiceReady() {\n        mImageCapture.initImageRead(ColorPickManager.getInstance().getMediaProjection());\n    }\n\n\n    @Override\n    public void onViewCreated(FrameLayout view) {\n        initView();\n    }\n\n    private void initView() {\n        mPickerView = findViewById(R.id.picker_view);\n        ViewGroup.LayoutParams params = mPickerView.getLayoutParams();\n        //大小必须是2的倍数\n        params.width = ColorPickConstants.PICK_VIEW_SIZE;\n        params.height = ColorPickConstants.PICK_VIEW_SIZE;\n        mPickerView.setLayoutParams(params);\n\n        width = UIUtils.getWidthPixels();\n        height = UIUtils.getHeightPixels();\n        statusBarHeight = UIUtils.getStatusBarHeight();\n        captureInfo(500);\n    }\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_color_picker, null);\n    }\n\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT;\n        params.width = DoKitViewLayoutParams.WRAP_CONTENT;\n    }\n\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        mImageCapture.destroy();\n    }\n\n    /**\n     * 显示像素信息\n     */\n    private void showInfo() {\n        int x, y;\n        if (isNormalMode()) {\n            x = getNormalLayoutParams().leftMargin;\n            y = getNormalLayoutParams().topMargin;\n        } else {\n            x = getSystemLayoutParams().x;\n            y = getSystemLayoutParams().y;\n        }\n\n        int pickAreaSize = ColorPickConstants.PICK_AREA_SIZE;\n        int startX = x + ColorPickConstants.PICK_VIEW_SIZE / 2 - pickAreaSize / 2;\n        int startY = y + ColorPickConstants.PICK_VIEW_SIZE / 2 - pickAreaSize / 2 + UIUtils.getStatusBarHeight();\n        Bitmap bitmap = mImageCapture.getPartBitmap(startX, startY, pickAreaSize, pickAreaSize);\n        if (bitmap == null) {\n            return;\n        }\n        int xCenter = bitmap.getWidth() / 2;\n        int yCenter = bitmap.getHeight() / 2;\n        int colorInt = DoKitImageUtil.getPixel(bitmap, xCenter, yCenter);\n        mPickerView.setBitmap(bitmap, colorInt, startX, startY);\n        mInfoDokitView.showInfo(colorInt, startX, startY);\n    }\n\n    /**\n     * 捕捉截图信息\n     */\n    private void captureInfo(int delay) {\n\n        mRunnable = new Runnable() {\n            @Override\n            public void run() {\n                mImageCapture.capture();\n                //截图完成以后恢复\n                mPickerView.setVisibility(View.VISIBLE);\n                showInfo();\n            }\n        };\n        //先隐藏拾色器控件 否则会把拾色器也截图进去\n        mPickerView.setVisibility(View.INVISIBLE);\n        getDoKitView().postDelayed(mRunnable, delay);\n    }\n\n\n    @Override\n    public void onDown(int x, int y) {\n        super.onDown(x, y);\n        captureInfo(100);\n    }\n\n    @Override\n    public void onMove(int x, int y, int dx, int dy) {\n        super.onMove(x, y, dx, dy);\n        if (isNormalMode() && getNormalLayoutParams() != null) {\n            checkBound(getNormalLayoutParams());\n        } else {\n            if (getSystemLayoutParams() != null) {\n                checkBound(getSystemLayoutParams());\n            }\n        }\n        showInfo();\n    }\n\n    private void checkBound(FrameLayout.LayoutParams layoutParams) {\n        if (layoutParams.leftMargin < -mPickerView.getWidth() / 2) {\n            layoutParams.leftMargin = -mPickerView.getWidth() / 2;\n        }\n        if (layoutParams.leftMargin > width - mPickerView.getWidth() / 2 - ColorPickConstants.PIX_INTERVAL) {\n            layoutParams.leftMargin = width - mPickerView.getWidth() / 2 - ColorPickConstants.PIX_INTERVAL;\n        }\n        if (layoutParams.topMargin < -mPickerView.getHeight() / 2 - statusBarHeight) {\n            layoutParams.topMargin = -mPickerView.getHeight() / 2 - statusBarHeight;\n        }\n        if (layoutParams.topMargin > height - mPickerView.getHeight() / 2 - ColorPickConstants.PIX_INTERVAL) {\n            layoutParams.topMargin = height - mPickerView.getHeight() / 2 - ColorPickConstants.PIX_INTERVAL;\n        }\n        layoutParams.width = ColorPickConstants.PICK_VIEW_SIZE;\n        layoutParams.height = ColorPickConstants.PICK_VIEW_SIZE;\n        immInvalidate();\n    }\n\n    private void checkBound(WindowManager.LayoutParams layoutParams) {\n        if (layoutParams.x < -mPickerView.getWidth() / 2) {\n            layoutParams.x = -mPickerView.getWidth() / 2;\n        }\n        if (layoutParams.x > width - mPickerView.getWidth() / 2 - ColorPickConstants.PIX_INTERVAL) {\n            layoutParams.x = width - mPickerView.getWidth() / 2 - ColorPickConstants.PIX_INTERVAL;\n        }\n        if (layoutParams.y < -mPickerView.getHeight() / 2 - statusBarHeight) {\n            layoutParams.y = -mPickerView.getHeight() / 2 - statusBarHeight;\n        }\n        if (layoutParams.y > height - mPickerView.getHeight() / 2 - ColorPickConstants.PIX_INTERVAL) {\n            layoutParams.y = height - mPickerView.getHeight() / 2 - ColorPickConstants.PIX_INTERVAL;\n        }\n    }\n\n    @Override\n    public void onEnterBackground() {\n        if (DoKitManager.IS_NORMAL_FLOAT_MODE) {\n            getDoKitView().removeCallbacks(mRunnable);\n        }\n        //不需要调用父类方法 隐藏\n    }\n\n    @Override\n    public void onEnterForeground() {\n        //不需要调用父类方法 显示\n    }\n\n\n    @Override\n    public boolean restrictBorderline() {\n        return false;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/colorpick/ColorPickerInfoDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.colorpick;\n\nimport android.content.Context;\nimport android.graphics.drawable.ColorDrawable;\n\nimport androidx.annotation.ColorInt;\n\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.ColorPickConfig;\nimport com.didichuxing.doraemonkit.kit.core.TranslucentActivity;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.util.ColorUtil;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\n/**\n * Created by jintai on 2019/09/26.\n */\npublic class ColorPickerInfoDoKitView extends AbsDoKitView {\n    private ImageView mColor;\n    private TextView mColorHex;\n    private ImageView mClose;\n\n    @Override\n    public void onCreate(Context context) {\n    }\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_color_picker_info, null);\n    }\n\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.width = getScreenShortSideLength();\n        params.height = WindowManager.LayoutParams.WRAP_CONTENT;\n        params.x = 0;\n        params.y = UIUtils.getHeightPixels() - UIUtils.dp2px(95);\n    }\n\n    @Override\n    public void onViewCreated(FrameLayout view) {\n        initView();\n    }\n\n    private void initView() {\n        mColor = findViewById(R.id.color);\n        mColorHex = findViewById(R.id.color_hex);\n        mClose = findViewById(R.id.close);\n        mClose.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                ColorPickManager.getInstance().setColorPickerDokitView(null);\n                ColorPickConfig.setColorPickOpen(false);\n                DoKit.removeFloating(ColorPickerDoKitView.class);\n                DoKit.removeFloating(ColorPickerInfoDoKitView.class);\n                //取色器kit是依赖在当前透明的Activity上的 所以关闭控件时需要finish\n                if (ActivityUtils.getTopActivity() != null && ActivityUtils.getTopActivity() instanceof TranslucentActivity) {\n                    ActivityUtils.getTopActivity().finish();\n                }\n\n            }\n        });\n    }\n\n    public void showInfo(@ColorInt int colorInt, int x, int y) {\n        mColor.setImageDrawable(new ColorDrawable(colorInt));\n        mColorHex.setText(String.format(ColorPickConstants.TEXT_FOCUS_INFO, ColorUtil.parseColorInt(colorInt), x + ColorPickConstants.PIX_INTERVAL, y + ColorPickConstants.PIX_INTERVAL));\n    }\n\n    @Override\n    public void onEnterBackground() {\n        //不需要调用父类方法 隐藏\n    }\n\n    @Override\n    public void onEnterForeground() {\n        //不需要调用父类方法 显示\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/colorpick/ColorPickerKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.colorpick\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content.Intent\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.config.ColorPickConfig\nimport com.didichuxing.doraemonkit.constant.BundleKey\nimport com.didichuxing.doraemonkit.constant.FragmentIndex\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.core.TranslucentActivity\nimport com.google.auto.service.AutoService\n\n/**\n * Created by wanglikun on 2018/9/13.\n */\n@AutoService(AbstractKit::class)\nclass ColorPickerKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_color_picker\n    override val icon: Int\n        get() = R.mipmap.dk_color_picker\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        val intent = Intent(activity, TranslucentActivity::class.java)\n        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK\n        intent.putExtra(BundleKey.FRAGMENT_INDEX, FragmentIndex.FRAGMENT_COLOR_PICKER_SETTING)\n        activity.startActivity(intent)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n        ColorPickConfig.setColorPickOpen(false)\n    }\n\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_ui_ck_color_pick\"\n    }\n\n    companion object {\n        private const val TAG = \"ColorPicker\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/colorpick/ColorPickerSettingFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.colorpick;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.media.projection.MediaProjectionManager;\nimport android.os.Bundle;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.ColorPickConfig;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.constant.RequestCode;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLaunchMode;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\n\n/**\n * @author wanglikun\n * @date 2018/9/15\n * 屏幕取色器fragment\n */\n\npublic class ColorPickerSettingFragment extends BaseFragment {\n\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        if (requestCaptureScreen()) {\n            ColorPickConfig.setColorPickOpen(true);\n        }\n    }\n\n    private boolean requestCaptureScreen() {\n        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {\n            return false;\n        }\n        //截图与录屏\n        MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getContext().getSystemService(Context.MEDIA_PROJECTION_SERVICE);\n        if (mediaProjectionManager == null) {\n            return false;\n        }\n\n        startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), RequestCode.CAPTURE_SCREEN);\n        return true;\n    }\n\n    @Override\n    public void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        if (requestCode == RequestCode.CAPTURE_SCREEN && resultCode == Activity.RESULT_OK) {\n            if (!DoKitManager.IS_NORMAL_FLOAT_MODE) {\n                finish();\n            }\n            showColorPicker(data);\n        } else {\n            ToastUtils.showShort(\"start color pick fail\");\n            finish();\n        }\n    }\n\n    /**\n     * 显示颜色拾取器\n     *\n     * @param intent\n     */\n    private void showColorPicker(Intent intent) {\n\n        DoKit.launchFloating(ColorPickerInfoDoKitView.class);\n\n\n        Bundle bundle = new Bundle();\n        bundle.putParcelable(\"data\", intent);\n        DoKit.launchFloating(ColorPickerDoKitView.class, DoKitViewLaunchMode.SINGLE_INSTANCE, bundle);\n\n    }\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_color_picker_setting;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/colorpick/ColorPickerView.java",
    "content": "package com.didichuxing.doraemonkit.kit.colorpick;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Matrix;\nimport android.graphics.Paint;\nimport android.graphics.PaintFlagsDrawFilter;\nimport android.graphics.Path;\nimport android.graphics.Rect;\nimport android.graphics.Typeface;\nimport androidx.annotation.Nullable;\nimport androidx.core.graphics.drawable.RoundedBitmapDrawable;\nimport androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;\nimport android.text.TextPaint;\nimport android.text.TextUtils;\nimport android.util.AttributeSet;\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.colorpick.ColorPickConstants;\nimport com.didichuxing.doraemonkit.util.ColorUtil;\n\n/**\n * Created by wanglikun on 2018/12/1.\n */\n\npublic class ColorPickerView extends View {\n    private Paint mRingPaint;\n    private Paint mBitmapPaint;\n    private Paint mFocusPaint;\n    private Paint mGridPaint;\n    private Paint mGridShadowPaint;\n    private TextPaint mTextPaint;\n    private Path mClipPath = new Path();\n    private Matrix mBitmapMatrix = new Matrix();\n    private Rect mGridRect = new Rect();\n    private Bitmap mCircleBitmap;\n    private String mText;\n\n    public ColorPickerView(Context context) {\n        super(context);\n        init();\n    }\n\n    public ColorPickerView(Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        init();\n    }\n\n    public ColorPickerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init();\n    }\n\n    private void init() {\n        mRingPaint = new Paint();\n        mRingPaint.setAntiAlias(true);\n        mRingPaint.setColor(Color.WHITE);\n        mRingPaint.setStyle(Paint.Style.STROKE);\n\n        mFocusPaint = new Paint();\n        mFocusPaint.setAntiAlias(true);\n        mFocusPaint.setStyle(Paint.Style.STROKE);\n        mFocusPaint.setStrokeWidth(3f);\n        mFocusPaint.setColor(Color.BLACK);\n\n        mBitmapPaint = new Paint();\n        mBitmapPaint.setFilterBitmap(false);\n\n        mGridPaint = new Paint();\n        //设置线宽。单位为1像素\n        mGridPaint.setStrokeWidth(1f);\n        mGridPaint.setStyle(Paint.Style.STROKE);\n        //画笔颜色\n        mGridPaint.setColor(-3355444);\n        mGridShadowPaint = new Paint(mGridPaint);\n        mGridShadowPaint.setColor(-12303292);\n\n        mTextPaint = new TextPaint();\n        mTextPaint.setAntiAlias(true);\n        mTextPaint.setTextAlign(Paint.Align.CENTER);\n        mTextPaint.setTypeface(Typeface.MONOSPACE);\n        mTextPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.dk_font_size_12));\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        mClipPath.rewind();\n        mClipPath.moveTo(0, 0);\n        mClipPath.addCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, Path.Direction.CW);\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        drawBitmap(canvas);\n        drawGrid(canvas);\n        drawRing(canvas);\n        drawText(canvas);\n        drawFocus(canvas);\n    }\n\n    private void drawText(Canvas canvas) {\n        if (!TextUtils.isEmpty(mText)) {\n            float ringWidth = ColorPickConstants.PIX_INTERVAL * 2 + 4;\n            float hOffset = (float) (getWidth() * Math.PI * (90 * 1.0 / 360));\n            float vOffset = ringWidth - 5;\n            canvas.drawTextOnPath(mText, mClipPath, hOffset, vOffset, mTextPaint);\n            canvas.setDrawFilter(null);\n        }\n    }\n\n    private RoundedBitmapDrawable mGridDrawable;\n\n    private void drawGrid(Canvas canvas) {\n        if (mGridDrawable == null) {\n            Bitmap gridBitmap = createGridBitmap(ColorPickConstants.PIX_INTERVAL, canvas);\n            mGridDrawable = RoundedBitmapDrawableFactory.create(getResources(), gridBitmap);\n            mGridDrawable.setBounds(0, 0, getRight(), getBottom());\n            mGridDrawable.setCircular(true);\n        }\n        mGridDrawable.draw(canvas);\n    }\n\n\n    private Bitmap createGridBitmap(int pixInterval, Canvas canvas) {\n        int width = getWidth();\n        int height = getHeight();\n\n        canvas.getClipBounds(mGridRect);\n\n        Bitmap gridBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);\n        Canvas gridCanvas = new Canvas(gridBitmap);\n\n        if (pixInterval >= 4) {\n            int alpha = Math.min(pixInterval * 36, 255);\n            mGridPaint.setAlpha(alpha);\n            mGridShadowPaint.setAlpha(alpha);\n            float value;\n            float start;\n            float end;\n            gridCanvas.save();\n            for (int i = 0; i <= getWidth(); i += pixInterval) {\n                value = (float) (i - 1);\n                start = 0f;\n                end = (float) height;\n                gridCanvas.drawLine(value, start, value, end, this.mGridPaint);\n                value = (float) i;\n                gridCanvas.drawLine(value, start, value, end, this.mGridShadowPaint);\n            }\n            for (int i = 0; i <= getHeight(); i += pixInterval) {\n                value = (float) (i - 1);\n                start = 0f;\n                end = (float) width;\n                gridCanvas.drawLine(start, value, end, value, this.mGridPaint);\n                value = (float) i;\n                gridCanvas.drawLine(start, value, end, value, this.mGridShadowPaint);\n            }\n            gridCanvas.restore();\n        }\n        return gridBitmap;\n    }\n\n    private void drawFocus(Canvas canvas) {\n        float focusWidth = ColorPickConstants.PIX_INTERVAL + 4;\n        canvas.drawRect(getWidth() / 2 - 2,\n                getWidth() / 2 - 2,\n                getWidth() / 2 + focusWidth - 2,\n                getWidth() / 2 + focusWidth - 2,\n                mFocusPaint);\n    }\n\n    private void drawRing(Canvas canvas) {\n        canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));\n        float ringWidth = ColorPickConstants.PIX_INTERVAL * 2 + 4;\n\n        mRingPaint.setStrokeWidth(ringWidth);\n        canvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2 - ringWidth / 2, mRingPaint);\n\n        mRingPaint.setColor(getResources().getColor(R.color.dk_color_333333));\n        mRingPaint.setStrokeWidth(0.5f);\n        canvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2, mRingPaint);\n        canvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2 - ringWidth, mRingPaint);\n    }\n\n    private void drawBitmap(Canvas canvas) {\n        if (mCircleBitmap == null || mCircleBitmap.isRecycled()) {\n            return;\n        }\n        canvas.save();\n        canvas.clipPath(mClipPath);\n        mBitmapMatrix.reset();\n        mBitmapMatrix.postScale(getWidth() / (float) mCircleBitmap.getWidth(), getHeight() / (float) mCircleBitmap.getHeight());\n        canvas.drawBitmap(mCircleBitmap, mBitmapMatrix, mBitmapPaint);\n        canvas.restore();\n    }\n\n    public void setBitmap(Bitmap bitmap, int color, int x, int y) {\n        mCircleBitmap = bitmap;\n        mText = String.format(ColorPickConstants.TEXT_FOCUS_INFO, ColorUtil.parseColorInt(color), x + ColorPickConstants.PIX_INTERVAL, y + ColorPickConstants.PIX_INTERVAL);\n        mRingPaint.setColor(color);\n        if (ColorUtil.isColdColor(color)) {\n            mFocusPaint.setColor(Color.WHITE);\n            mTextPaint.setColor(Color.WHITE);\n        } else {\n            mFocusPaint.setColor(Color.BLACK);\n            mTextPaint.setColor(Color.BLACK);\n        }\n        invalidate();\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/colorpick/ImageCapture.java",
    "content": "package com.didichuxing.doraemonkit.kit.colorpick;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\nimport android.graphics.Bitmap;\nimport android.graphics.PixelFormat;\nimport android.hardware.display.DisplayManager;\nimport android.media.Image;\nimport android.media.ImageReader;\nimport android.media.projection.MediaProjection;\nimport android.media.projection.MediaProjectionManager;\nimport android.os.Build;\nimport android.os.Bundle;\n\nimport androidx.annotation.RequiresApi;\n\nimport com.didichuxing.doraemonkit.DoKitEnv;\nimport com.didichuxing.doraemonkit.util.AppUtils;\nimport com.didichuxing.doraemonkit.util.LogHelper;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\nimport java.nio.ByteBuffer;\n\n/**\n * @author wanglikun\n * @date 2018/12/3\n */\n@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\npublic class ImageCapture {\n    private static final String TAG = \"ImageCapture\";\n    private MediaProjectionManager mMediaProjectionManager;\n    private MediaProjection mMediaProjection;\n    private ImageReader mImageReader;\n    private boolean isCapturing;\n    private Bitmap mBitmap;\n    private ColorPickerDoKitView mColorPickerDokitView;\n\n\n    public void init(Context context, Bundle bundle, ColorPickerDoKitView colorPickerDokitView) throws Exception {\n        this.mColorPickerDokitView = colorPickerDokitView;\n        PackageManager packageManager = DoKitEnv.requireApp().getPackageManager();\n        ApplicationInfo applicationInfo = packageManager.getApplicationInfo(AppUtils.getAppPackageName(), 0);\n        //适配Android Q\n        if (applicationInfo.targetSdkVersion >= 29) {\n            if (ColorPickManager.getInstance().getMediaProjection() != null) {\n                colorPickerDokitView.onScreenServiceReady();\n            } else {\n                try {\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && (bundle.getParcelable(\"data\") instanceof Intent)) {\n                        Intent dataIntent = bundle.getParcelable(\"data\");\n                        Intent intent = new Intent(context, ScreenRecorderService.class);\n                        intent.putExtra(\"data\", dataIntent);\n                        context.startForegroundService(intent);\n                    }\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n            }\n        } else {\n            mMediaProjectionManager = (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);\n            if (mMediaProjectionManager != null) {\n                mMediaProjection = mMediaProjectionManager.getMediaProjection(Activity.RESULT_OK, (Intent) bundle.getParcelable(\"data\"));\n                initImageRead(mMediaProjection);\n            }\n        }\n    }\n\n    /**\n     *\n     */\n    @SuppressLint(\"WrongConstant\")\n    void initImageRead(MediaProjection mediaProjection) {\n        if (mediaProjection == null) {\n            LogHelper.e(TAG, \"mediaProjection == null\");\n            return;\n        }\n        int width = UIUtils.getWidthPixels();\n        int height = UIUtils.getRealHeightPixels();\n        int dpi = UIUtils.getDensityDpi();\n        //wiki:https://www.jianshu.com/p/d7eb518195fd\n        mImageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2);\n        /**\n         * 获取getSurface\n         */\n        mediaProjection.createVirtualDisplay(\"ScreenCapture\",\n                width, height, dpi,\n                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,\n                mImageReader.getSurface(), null, null);\n    }\n\n    /**\n     * 截取最后一帧的bitmap\n     */\n    void capture() {\n        if (isCapturing) {\n            return;\n        }\n        if (mImageReader == null) {\n            return;\n        }\n        isCapturing = true;\n        //获取image对象\n        Image image = mImageReader.acquireLatestImage();\n        if (image == null) {\n            return;\n        }\n        int width = image.getWidth();\n        int height = image.getHeight();\n        Image.Plane[] planes = image.getPlanes();\n        ByteBuffer buffer = planes[0].getBuffer();\n        int pixelStride = planes[0].getPixelStride();\n        int rowStride = planes[0].getRowStride();\n        int rowPaddingStride = rowStride - pixelStride * width;\n        int rowPadding = rowPaddingStride / pixelStride;\n        Bitmap recordBitmap = Bitmap.createBitmap(width + rowPadding, height, Bitmap.Config.ARGB_8888);\n        recordBitmap.copyPixelsFromBuffer(buffer);\n        mBitmap = Bitmap.createBitmap(recordBitmap, 0, 0, width, height);\n        image.close();\n        isCapturing = false;\n    }\n\n    Bitmap getPartBitmap(int x, int y, int width, int height) {\n        if (mBitmap == null) {\n            return null;\n        }\n        if (x < 0) {\n            x = 0;\n        }\n        if (x + width > mBitmap.getWidth()) {\n            x = mBitmap.getWidth() - width;\n        }\n        if (y < 0) {\n            y = 0;\n        }\n        if (y + height > mBitmap.getHeight()) {\n            y = mBitmap.getHeight() - height;\n        }\n        return Bitmap.createBitmap(mBitmap, x, y, width, height);\n    }\n\n    void destroy() {\n        if (mImageReader != null) {\n            mImageReader.close();\n            mImageReader = null;\n        }\n        if (mMediaProjection != null) {\n            mMediaProjection.stop();\n            mMediaProjection = null;\n        }\n        mMediaProjectionManager = null;\n\n        if (mBitmap != null) {\n            mBitmap.recycle();\n            mBitmap = null;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/colorpick/ScreenRecorderService.java",
    "content": "package com.didichuxing.doraemonkit.kit.colorpick;\n\nimport android.app.Activity;\nimport android.app.Notification;\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.app.PendingIntent;\nimport android.app.Service;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.graphics.BitmapFactory;\nimport android.media.projection.MediaProjection;\nimport android.media.projection.MediaProjectionManager;\nimport android.os.Build;\nimport android.os.IBinder;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.UniversalActivity;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020-02-21-17:36\n * 描    述：\n * 修订历史：\n * ================================================\n *\n */\npublic class ScreenRecorderService extends Service {\n\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        try {\n            createNotificationChannel();\n            //Android Q 存在兼容性问题\n            MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);\n            MediaProjection mediaProjection = mMediaProjectionManager.getMediaProjection(Activity.RESULT_OK, (Intent) intent.getParcelableExtra(\"data\"));\n            ColorPickManager.getInstance().setMediaProjection(mediaProjection);\n            if (ColorPickManager.getInstance().getColorPickerDokitView() != null) {\n                ColorPickManager.getInstance().getColorPickerDokitView().onScreenServiceReady();\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return super.onStartCommand(intent, flags, startId);\n    }\n\n    private void createNotificationChannel() {\n        Notification.Builder builder = new Notification.Builder(this.getApplicationContext()); //获取一个Notification构造器\n        Intent nfIntent = new Intent(this, UniversalActivity.class); //点击后跳转的界面，可以设置跳转数据\n        PendingIntent pendingIntent;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            pendingIntent = PendingIntent.getActivity(this, 0, nfIntent, 0 | PendingIntent.FLAG_IMMUTABLE);\n        } else {\n            pendingIntent = PendingIntent.getActivity(this, 0, nfIntent, 0);\n        }\n\n        builder.setContentIntent(pendingIntent) // 设置PendingIntent\n                .setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.dk_doraemon)) // 设置下拉列表中的图标(大图标)\n                //.setContentTitle(\"SMI InstantView\") // 设置下拉列表里的标题\n                .setSmallIcon(R.mipmap.dk_doraemon) // 设置状态栏内的小图标\n                .setContentText(\"Dokit屏幕取色器前台服务\") // 设置上下文内容\n                .setWhen(System.currentTimeMillis()); // 设置该通知发生的时间\n\n        /*以下是对Android 8.0的适配*/\n        //普通notification适配\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            builder.setChannelId(\"notification_id\");\n        }\n        //前台服务notification适配\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);\n            NotificationChannel channel = new NotificationChannel(\"notification_id\", \"notification_name\", NotificationManager.IMPORTANCE_LOW);\n            notificationManager.createNotificationChannel(channel);\n        }\n\n        Notification notification = builder.build(); // 获取构建好的Notification\n        notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音\n        startForeground(110, notification);\n\n    }\n\n    @Nullable\n    @Override\n    public IBinder onBind(Intent intent) {\n        return null;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ConnectAddress.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect\n\n/**\n * didi Create on 2022/1/18 .\n *\n * Copyright (c) 2022/1/18 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/1/18 8:12 下午\n * @Description 用一句话说明文件功能\n */\n\ndata class ConnectAddress(\n    val name: String,\n    val url: String = \"\",\n    val time: String,\n    var enable: Boolean = false,\n    var connectSerial: String = \"\"\n)\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ConnectAddressStore.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect\n\nimport android.text.TextUtils\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport com.didichuxing.doraemonkit.util.SPUtils\n\n\n/**\n * didi Create on 2022/1/18 .\n *\n * Copyright (c) 2022/1/18 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/1/18 8:52 下午\n * @Description 用一句话说明文件功能\n */\n\nobject ConnectAddressStore {\n\n    private const val SP_FILE_NAME: String = \"dokit_studio_connect_address\"\n    private const val SP_DATA: String = \"data\"\n\n    private val data: MutableList<ConnectAddress> = mutableListOf()\n\n    fun loadAddress(): MutableList<ConnectAddress> {\n        if (data.isEmpty()) {\n            data.addAll(loadAddressAll())\n        }\n        return data.toMutableList()\n    }\n\n\n    fun saveAddress(connectAddress: ConnectAddress) {\n        val list = mutableListOf<ConnectAddress>()\n        list.addAll(data)\n        list.forEach {\n            if (!TextUtils.isEmpty(it.url) && TextUtils.equals(it.url, connectAddress.url)) {\n                data.remove(it)\n            }\n        }\n        data.add(connectAddress)\n        saveAddressAll(data.toMutableList())\n    }\n\n    fun removeAddress(connectAddress: ConnectAddress) {\n        data.remove(connectAddress)\n        saveAddressAll(data.toMutableList())\n    }\n\n    fun saveAddressAll(histories: MutableList<ConnectAddress>) {\n        data.clear()\n        data.addAll(histories)\n\n        val data2 = GsonUtils.toJson(histories)\n        SPUtils.getInstance(SP_FILE_NAME).put(SP_DATA, data2)\n    }\n\n\n    private fun loadAddressAll(): MutableList<ConnectAddress> {\n        val data = SPUtils.getInstance(SP_FILE_NAME).getString(SP_DATA, \"\")\n        return if (data.isEmpty()) {\n            mutableListOf()\n        } else {\n            val type = GsonUtils.getListType(ConnectAddress::class.java)\n            GsonUtils.fromJson(data, type)\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ConnectConfig.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect\n\nimport com.didichuxing.doraemonkit.util.SPUtils\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 6:07 下午\n * @Description 用一句话说明文件功能\n */\nobject ConnectConfig {\n\n    private const val NAME_CONNECT_CONFIG_FILE = \"dokit_connect_config_all\"\n    private const val KEY_CONNECT_SERIAL = \"connect_serial\"\n\n    private var configSP: SPUtils = SPUtils.getInstance(NAME_CONNECT_CONFIG_FILE)\n\n    private var connectSerial: String? = null\n\n\n    fun getConnectSerial(): String {\n        val text = connectSerial\n        if (text == null) {\n            connectSerial = configSP.getString(KEY_CONNECT_SERIAL, \"\")\n        } else {\n            return text\n        }\n        return configSP.getString(KEY_CONNECT_SERIAL, \"\")\n    }\n\n    fun saveConnectSerial(text: String) {\n        connectSerial = text\n        configSP.put(KEY_CONNECT_SERIAL, text)\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ConnectListAdapter.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect\n\n\nimport android.view.View\nimport android.widget.TextView\nimport androidx.core.view.isGone\nimport com.didichuxing.doraemonkit.R\n\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n\n/**\n * didi Create on 2022/1/18 .\n *\n * Copyright (c) 2022/1/18 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/1/18 8:12 下午\n * @Description 用一句话说明文件功能\n */\n\nclass ConnectListAdapter(clientList: MutableList<ConnectAddress>, callback: (client: ConnectAddress) -> Unit) :\n    BaseQuickAdapter<ConnectAddress, BaseViewHolder>(R.layout.dk_item_connect_address, clientList) {\n\n    val callback2 = callback\n    override fun convert(holder: BaseViewHolder, item: ConnectAddress) {\n        holder.getView<TextView>(R.id.tv_name).text = \"主机名称:${item.name}\"\n        holder.getView<TextView>(R.id.tv_address).text = \"地址:${item.url}\"\n        holder.getView<TextView>(R.id.tv_time).text = \"时间:${item.time}\"\n        holder.getView<TextView>(R.id.connect).setOnClickListener {\n            callback2(item)\n        }\n        holder.getView<View>(R.id.state_dot).isGone = !item.enable\n        holder.getView<TextView>(R.id.connect).isGone = item.enable\n    }\n}\n\n\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/DoKitConnectFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.view.View\nimport android.widget.*\nimport androidx.appcompat.app.AlertDialog\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.extension.isTrueWithCor\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.util.TimeUtils\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.didichuxing.doraemonkit.widget.recyclerview.DividerItemDecoration\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\nimport com.didichuxing.doraemonkit.zxing.activity.CaptureActivity\nimport kotlinx.coroutines.launch\nimport java.util.*\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 6:07 下午\n * @Description 用一句话说明文件功能\n */\nclass DoKitConnectFragment : BaseFragment() {\n\n\n    companion object {\n        private const val REQUEST_CODE_SCAN = 0x1008\n    }\n\n\n    private lateinit var recyclerView: RecyclerView\n    private lateinit var mAdapter: ConnectListAdapter\n    private lateinit var histories: MutableList<ConnectAddress>\n\n\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_dokit_connect\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        findViewById<HomeTitleBar>(R.id.title_bar).setListener {\n            finish()\n        }\n\n        findViewById<Button>(R.id.btn_add).setOnClickListener {\n            startScan()\n        }\n\n        recyclerView = findViewById(R.id.recyclerView)\n\n        mAdapter = ConnectListAdapter(mutableListOf<ConnectAddress>()) { it ->\n            handleConnect(it)\n        }\n\n        mAdapter.setOnItemClickListener { adapter, view, pos ->\n            val data = histories[pos]\n            if (data.enable) {\n                lifecycleScope.launch {\n                    privacyInterceptDialog(\"提示\", \"当前已链接:${data.url} 是否断开链接\").isTrueWithCor {\n                        DoKitConnectManager.stopConnect()\n                        ToastUtils.showShort(\"已断开\")\n                        updateHistoryView()\n                    }\n                }\n            } else {\n                lifecycleScope.launch {\n                    privacyInterceptDialog(\"提示\", \"当前未链接:${data.url} \").isTrueWithCor {\n                        updateHistoryView()\n                    }\n                }\n            }\n\n        }\n\n        mAdapter.setOnItemLongClickListener { adapter, view, pos ->\n            val data = histories[pos]\n            lifecycleScope.launch {\n                privacyInterceptDialog(\"提示\", \"是否删除连接历史记录\").isTrueWithCor {\n                    ConnectAddressStore.removeAddress(data)\n                    ToastUtils.showShort(\"已删除记录\")\n                    updateHistoryView()\n                }\n            }\n            return@setOnItemLongClickListener false\n        }\n\n        recyclerView.apply {\n            adapter = mAdapter\n            layoutManager = LinearLayoutManager(requireActivity())\n            val decoration = DividerItemDecoration(DividerItemDecoration.VERTICAL)\n            decoration.setDrawable(resources.getDrawable(R.drawable.dk_divider))\n            addItemDecoration(decoration)\n        }\n\n        updateHistoryView()\n\n\n    }\n\n    private fun handleConnect(connectAddress: ConnectAddress) {\n        DoKitConnectManager.startConnect(connectAddress)\n        updateHistoryView()\n    }\n\n\n    /**\n     * 处理dialog返回值\n     */\n    private suspend fun privacyInterceptDialog(title: String, content: String): Boolean =\n        suspendCoroutine {\n            AlertDialog.Builder(requireActivity())\n                .setTitle(title)\n                .setMessage(content)\n                .setCancelable(false)\n                .setPositiveButton(\"确认\") { dialog, _ ->\n                    dialog.dismiss()\n                    it.resume(true)\n                }\n                .setNegativeButton(\"取消\") { dialog, _ ->\n                    dialog.dismiss()\n                    it.resume(false)\n                }\n                .show()\n\n        }\n\n\n    override fun onResume() {\n        super.onResume()\n        updateHistoryView()\n    }\n\n    override fun onStart() {\n        super.onStart()\n    }\n\n    private fun updateHistoryView() {\n        lifecycleScope.launch {\n            val clients = ConnectAddressStore.loadAddress()\n            val current = DoKitConnectManager.getCurrentConnectAddress()\n            for (history in clients) {\n                if (current != null) {\n                    history.enable = TextUtils.equals(history.url, current.url)\n                } else {\n                    history.enable = false\n                }\n            }\n\n            histories = clients\n            clients?.let {\n                mAdapter.setList(clients)\n            }\n        }\n    }\n\n\n\n    /**\n     * 开始扫描\n     */\n    private fun startScan() {\n        val intent = Intent(activity, DoKitScanActivity::class.java)\n        startActivityForResult(intent, REQUEST_CODE_SCAN)\n    }\n\n\n    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {\n        super.onActivityResult(requestCode, resultCode, data)\n        if (requestCode == REQUEST_CODE_SCAN && resultCode == Activity.RESULT_OK) {\n            if (data != null && data.hasExtra(CaptureActivity.INTENT_EXTRA_KEY_QR_SCAN)) {\n                val url = data.getStringExtra(CaptureActivity.INTENT_EXTRA_KEY_QR_SCAN)\n                if (!TextUtils.isEmpty(url)) {\n                    try {\n                        val uri = Uri.parse(url)\n                        uri?.let {\n                            val name = uri.host.toString()\n                            val time = TimeUtils.date2String(Date())\n                            val history = ConnectAddress(name, url!!, time)\n                            ConnectAddressStore.saveAddress(history)\n                            handleConnect(history)\n                        }\n\n                    } catch (e: Exception) {\n                        e.printStackTrace()\n                    }\n                } else {\n                    handleNoResult()\n                }\n            } else {\n                handleNoResult()\n            }\n        } else {\n            handleNoResult()\n        }\n    }\n\n    /**\n     * 没有返回结果\n     */\n    private fun handleNoResult() {\n        ToastUtils.showShort(\"没有扫描到任何内容 >_< .\")\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/DoKitConnectManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect\n\nimport com.didichuxing.doraemonkit.kit.connect.data.TextPackage\nimport com.didichuxing.doraemonkit.kit.connect.ws.OkHttpWebSocketSession\nimport com.didichuxing.doraemonkit.kit.connect.ws.OnWebSocketLoginSuccessListener\nimport com.didichuxing.doraemonkit.kit.connect.ws.OnWebSocketTextPackageListener\nimport com.didichuxing.doraemonkit.kit.connect.ws.WebSocketClient\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 6:07 下午\n * @Description 用一句话说明文件功能\n */\nobject DoKitConnectManager {\n\n\n    private var currentConnectAddress: ConnectAddress? = null\n    private var webSocketClient: WebSocketClient? = null\n\n    fun getConnectSerial(): String {\n        return ConnectConfig.getConnectSerial()\n    }\n\n    fun saveConnectSerial(text: String) {\n        ConnectConfig.saveConnectSerial(text)\n    }\n\n    fun getCurrentConnectAddress(): ConnectAddress? {\n        return currentConnectAddress\n    }\n\n    fun setCurrentConnectAddress(address: ConnectAddress?) {\n        currentConnectAddress = address\n    }\n\n    fun getWebSocketClient(): WebSocketClient? {\n        return webSocketClient\n    }\n\n    fun startConnect(address: ConnectAddress) {\n        currentConnectAddress = address\n        if (webSocketClient == null) {\n            webSocketClient = WebSocketClient()\n\n            webSocketClient?.let {\n                it.addOnWebSocketLoginSuccessListener(object : OnWebSocketLoginSuccessListener {\n                    override fun onWebSocketLoginSuccess() {\n                    }\n                })\n                it.addOnWebSocketTextPackageListener(object : OnWebSocketTextPackageListener {\n                    override fun onReceiveTextPackage(webSocket: OkHttpWebSocketSession, textPackage: TextPackage) {\n                    }\n                })\n                it.startAutoConnect()\n                it.connect(address.url)\n            }\n        } else {\n            webSocketClient?.let {\n                it.reConnect(address.url)\n            }\n        }\n\n    }\n\n    fun stopConnect() {\n        currentConnectAddress = null\n        webSocketClient?.let {\n            it.close()\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/DoKitScanActivity.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect\n\nimport android.Manifest\nimport android.content.pm.PackageManager\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport androidx.annotation.IdRes\nimport androidx.core.app.ActivityCompat\nimport androidx.core.content.ContextCompat\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\nimport com.didichuxing.doraemonkit.zxing.activity.CaptureActivity\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 6:07 下午\n * @Description 用一句话说明文件功能\n */\nclass DoKitScanActivity : CaptureActivity() {\n\n    companion object {\n        private const val REQUEST_CODE = 200999\n    }\n\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        checkCameraPermission()\n    }\n    override fun setContentView(@IdRes layoutResID: Int) {\n        super.setContentView(layoutResID)\n        initTitleBar()\n    }\n\n    private fun initTitleBar() {\n        val homeTitleBar = HomeTitleBar(this)\n        homeTitleBar.setBackgroundColor(resources.getColor(R.color.foreground_wtf))\n        homeTitleBar.setIcon(R.mipmap.dk_close_icon)\n        homeTitleBar.setListener { finish() }\n        val params = FrameLayout.LayoutParams(\n            ViewGroup.LayoutParams.MATCH_PARENT,\n            resources.getDimension(R.dimen.dk_home_title_height).toInt()\n        )\n        (findViewById<View>(android.R.id.content) as FrameLayout).addView(homeTitleBar, params)\n    }\n\n    private fun checkCameraPermission() {\n        val hasCameraPermission: Int = ContextCompat.checkSelfPermission(application, Manifest.permission.CAMERA)\n        if (hasCameraPermission != PackageManager.PERMISSION_GRANTED) {\n            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), REQUEST_CODE)\n        }\n    }\n\n    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults)\n        if (requestCode == REQUEST_CODE) {\n\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/DoKitStudioConnectKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.google.auto.service.AutoService\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 6:07 下午\n * @Description 用一句话说明文件功能\n */\n@AutoService(AbstractKit::class)\nclass DoKitStudioConnectKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_dokit_studio\n    override val icon: Int\n        get() = R.mipmap.dk_dokit_for_web\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip))\n            return false\n        }\n        startUniversalActivity(DoKitConnectFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n\n    }\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_platform_ck_dokit_connect\"\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/data/BytePackage.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.data\n\n\n/**\n * didi Create on 2022/2/16 .\n *\n * Copyright (c) 2022/2/16 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/2/16 5:09 下午\n * @Description  byte类型的通信数据包\n */\ndata class BytePackage(\n    val sign: String = \"DoKit\",\n    val headLength: Int = -1,\n    val dataLength: Long = -1,\n    val textPackage: TextPackage,\n    val data: ByteArray\n) {\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (javaClass != other?.javaClass) return false\n\n        other as BytePackage\n\n        if (sign != other.sign) return false\n        if (headLength != other.headLength) return false\n        if (dataLength != other.dataLength) return false\n        if (textPackage != other.textPackage) return false\n        if (!data.contentEquals(other.data)) return false\n\n        return true\n    }\n\n    override fun hashCode(): Int {\n        var result = sign.hashCode()\n        result = 31 * result + headLength\n        result = 31 * result + dataLength.toInt()\n        result = 31 * result + textPackage.hashCode()\n        result = 31 * result + data.contentHashCode()\n        return result\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/data/LoginData.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.data\n\n/**\n *   \"deviceName\": \"小米测试手机-张三\",//设备名称(可通过前端修改)\n *   \"deviceType\": \"android/ios\",//字母全小写\n *   \"osVersion\": \"11\"，//系统版本\n *   \"screen\": \"1080*1960\"，//屏幕分辨率\n *   \"manufacturer\": \"huawei\"，//厂商信息\n *   \"ip\": \"1080*1960\"，//ip地址\n *   \"connectSerial\": \"widszdwewqe7qwer\"，//随机连接序列号16位(没有则发送空)\n *   \"appName\": \"滴滴代驾司机端\"，//应用名称\n *   \"appVersion\": \"3.6.0\"，//应用版本\n */\ndata class LoginData(\n\n    val deviceName: String,\n    val deviceType: String,\n    val osVersion: String,\n    val screen: String,\n    val manufacturer: String,\n    val ip: String,\n    val connectSerial: String = \"\",\n    val appName: String,\n    val appVersion: String\n)\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/data/PackageType.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.data\n\n\n/**\n * didi Create on 2022/2/16 .\n *\n * Copyright (c) 2022/2/16 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/2/16 5:09 下午\n * @Description 通过PC服务端转发的协议包类型\n */\n\nenum class PackageType(name: String, ordinal: Int) {\n    /**\n     * 登陆\n     * 用于连接成功后认证\n     */\n    LOGIN(\"login\", 1),\n\n    /**\n     * 广播\n     * 用户主从数据转发广播\n     */\n    BROADCAST(\"broadcast\", 2),\n\n    /**\n     * 通知\n     * 用于PC服务端通知客户端\n     */\n    NOTIFY(\"notify\", 3),\n\n    /**\n     * 数据代理\n     */\n    DATA(\"data\", 4),\n\n    /**\n     * 自动化测试\n     */\n    AUTOTEST(\"autotest\", 5),\n\n    /**\n     * 心跳\n     */\n    HEART_BEAT(\"heart_beat\", 1),\n\n    ;\n\n\n    override fun toString(): String {\n        super.toString()\n        return name\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/data/TextPackage.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.data\n\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 6:07 下午\n * @Description 用一句话说明文件功能\n */\ndata class TextPackage(\n    val pid: String,\n    val type: PackageType,\n    val data: String,\n    val channelSerial: String = \"android\",\n    var connectSerial: String = \"\",\n    var contentType: String = \"text\",\n    var message: String = \"\",\n    var code: Int = 0\n)\n\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/parser/ByteParser.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.parser\n\nimport com.didichuxing.doraemonkit.kit.connect.data.BytePackage\nimport com.didichuxing.doraemonkit.kit.connect.data.TextPackage\nimport okio.ByteString\nimport java.nio.ByteBuffer\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 6:07 下午\n * @Description 用一句话说明文件功能\n */\n\nobject ByteParser {\n\n    fun parse(bytes: ByteString): BytePackage {\n        val buffer = bytes.asByteBuffer()\n        val headLength = buffer.getInt(5)\n        val dataLength = buffer.getLong(9)\n        val headBytes = ByteArray(headLength)\n        val dataBytes = ByteArray(dataLength.toInt())\n        buffer.position(17)\n        buffer.get(headBytes)\n        buffer.get(dataBytes)\n\n        val headText = String(headBytes)\n        val textPackage = JsonParser.toTextPackage(headText)\n        return BytePackage(\n            headLength = headLength,\n            dataLength = dataLength,\n            textPackage = textPackage,\n            data = dataBytes\n        )\n    }\n\n    fun toByteString(textPackage: TextPackage, data: ByteArray): ByteString {\n        return toByteString(BytePackage(textPackage = textPackage, data = data))\n    }\n\n    fun toByteString(bytePackage: BytePackage): ByteString {\n\n        val headBytes: ByteArray = JsonParser.toJson(bytePackage.textPackage).toByteArray()\n        val dataBytes: ByteArray = bytePackage.data\n        val headLength = headBytes.size\n        val dataLength = dataBytes.size\n\n        val size = 17 + headLength + dataLength\n        val buffer = ByteBuffer.allocate(size)\n\n        buffer.put(\"DoKit\".toByteArray())\n        buffer.putInt(headLength)\n        buffer.putLong(dataLength.toLong())\n        buffer.put(headBytes)\n        buffer.put(dataBytes)\n        buffer.rewind()\n        return ByteString.of(buffer)\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/parser/JsonParser.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.parser\n\nimport com.didichuxing.doraemonkit.kit.connect.ConnectConfig\nimport com.didichuxing.doraemonkit.kit.connect.data.LoginData\nimport com.didichuxing.doraemonkit.kit.connect.data.PackageType\nimport com.didichuxing.doraemonkit.kit.connect.data.TextPackage\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport com.didichuxing.doraemonkit.util.RandomUtils\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 6:07 下午\n * @Description 用一句话说明文件功能\n */\nobject JsonParser {\n\n    fun toTextPackage(text: String): TextPackage {\n        return GsonUtils.fromJson<TextPackage>(text, TextPackage::class.java)\n    }\n\n    fun toLoginData(text: String): LoginData {\n        return GsonUtils.fromJson<LoginData>(text, LoginData::class.java)\n    }\n\n    fun toJson(data: Any): String {\n        return GsonUtils.toJson(data)\n    }\n\n    fun toJson(type: PackageType, data: Any): String {\n        return toJson(type, data, \"text\")\n    }\n\n    fun toJson(type: PackageType, data: Any, contentType: String): String {\n        val text = toJson(data)\n        val textPackage = TextPackage(\n            pid = RandomUtils.random32HexString(),\n            type = type,\n            data = text,\n            contentType = contentType,\n            connectSerial = ConnectConfig.getConnectSerial()\n        )\n        return toJson(textPackage)\n    }\n\n    fun toTextPackage(type: PackageType, data: Any, contentType: String): TextPackage {\n        val text = toJson(data)\n        return TextPackage(\n            pid = RandomUtils.random32HexString(),\n            type = type,\n            data = text,\n            contentType = contentType,\n            connectSerial = ConnectConfig.getConnectSerial()\n        )\n    }\n\n    fun toLoginJson(data: LoginData): String {\n        return toJson(PackageType.LOGIN, data)\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ws/ConnectStatus.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.ws\n\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 2:28 下午\n * @Description 用一句话说明文件功能\n */\n\nenum class ConnectStatus {\n\n    /**\n     * 链接准备中\n     */\n    PREPARE,\n\n    /**\n     * 离线\n     */\n    OFF_LINE,\n\n    /**\n     * 链接中\n     */\n    CONNECT,\n\n    /**\n     * 链接失败\n     */\n    FAILED\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ws/OkHttpWebSocketSession.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.ws\n\nimport okhttp3.*\nimport okio.ByteString\n\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 11:42 上午\n * @Description 自定义的WebSocket 链接辅助类实现，通过自定义的接口可以修改为其他实现方式\n */\n\nclass OkHttpWebSocketSession(val mClient: OkHttpClient) : WebSocketSession {\n\n\n    override var onWebSocketStatusChangeListener: OnWebSocketStatusChangeListener? = null\n    override var onWebSocketMessageListener: OnWebSocketMessageListener? = null\n    override var onWebSocketBytesMessageListener: OnWebSocketBytesMessageListener? = null\n    override var onWebSocketQueueSizeOutListener: OnWebSocketQueueSizeOutListener? = null\n    override var connectStatus: ConnectStatus = ConnectStatus.OFF_LINE\n\n    private var request: Request? = null\n    private var webSocket: WebSocket? = null\n    private val webSocketSession: OkHttpWebSocketSession = this\n\n\n    override fun connect(url: String) {\n        if (webSocket == null) {\n            val request: Request = Request.Builder().get().url(url).build()\n            webSocket = mClient.newWebSocket(request, webSocketListener)\n            this.request = request\n            connectStatus = ConnectStatus.PREPARE\n        }\n    }\n\n    override fun reConnect() {\n        if (webSocket != null) {\n            webSocket?.close(1001, \"close\")\n            webSocket = mClient.newWebSocket(request, webSocketListener)\n            connectStatus = ConnectStatus.PREPARE\n        }\n    }\n\n    override fun send(text: String): Boolean {\n        webSocket?.let {\n            if (it.queueSize() > 20) {\n                onWebSocketQueueSizeOutListener?.onWebSocketQueueSizeOut()\n                return it.send(text)\n            }\n            return it.send(text)\n        }\n        return false\n    }\n\n    override fun send(bytes: ByteString): Boolean {\n        webSocket?.let {\n            if (it.queueSize() > 20) {\n                onWebSocketQueueSizeOutListener?.onWebSocketQueueSizeOut()\n                return it.send(bytes)\n            }\n            return it.send(bytes)\n        }\n        return false\n    }\n\n    override fun close(code: Int, reason: String): Boolean {\n        webSocket?.let {\n            return it.close(code, reason)\n        }\n        return false\n    }\n\n\n    override fun queueSize(): Long {\n        webSocket?.let {\n            return it.queueSize()\n        }\n        return 0\n    }\n\n    override fun cancel() {\n        onWebSocketStatusChangeListener = null\n        onWebSocketMessageListener = null\n        onWebSocketBytesMessageListener = null\n        webSocket?.cancel()\n    }\n\n\n    private val webSocketListener = object : WebSocketListener() {\n\n        override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {\n            super.onClosed(webSocket, code, reason)\n            connectStatus = ConnectStatus.OFF_LINE\n            onWebSocketStatusChangeListener?.onClosed(webSocketSession, code, reason)\n        }\n\n        override fun onOpen(webSocket: WebSocket, response: Response) {\n            super.onOpen(webSocket, response)\n            connectStatus = ConnectStatus.CONNECT\n            val text = response.body()?.string() ?: \"\"\n            onWebSocketStatusChangeListener?.onOpen(webSocketSession, text)\n        }\n\n        override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {\n            super.onFailure(webSocket, t, response)\n            connectStatus = ConnectStatus.FAILED\n            val text = response?.body()?.string() ?: \"\"\n            onWebSocketStatusChangeListener?.onFailure(webSocketSession, t, text)\n        }\n\n        override fun onMessage(webSocket: WebSocket, text: String) {\n            super.onMessage(webSocket, text)\n            onWebSocketMessageListener?.onMessage(webSocketSession, text)\n        }\n\n        override fun onMessage(webSocket: WebSocket, bytes: ByteString) {\n            super.onMessage(webSocket, bytes)\n            onWebSocketBytesMessageListener?.onMessage(webSocketSession, bytes)\n        }\n\n        override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {\n            super.onClosing(webSocket, code, reason)\n            onWebSocketStatusChangeListener?.onClosing(webSocketSession, code, reason)\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ws/OnWebSocketBytesMessageListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.ws\n\nimport okio.ByteString\n\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 12:05 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface OnWebSocketBytesMessageListener {\n\n\n    fun onMessage(webSocket: OkHttpWebSocketSession, bytes: ByteString)\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ws/OnWebSocketCloseListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.ws\n\n\n/**\n * didi Create on 2022/4/15 .\n *\n * Copyright (c) 2022/4/15 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/15 11:14 上午\n * @Description 链接关闭监听，主动的链接关闭，因网络或其他原因导致的关闭不触发通知\n */\n\ninterface OnWebSocketCloseListener {\n    fun onWebSocketClose()\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ws/OnWebSocketLoginSuccessListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.ws\n\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 6:07 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface OnWebSocketLoginSuccessListener {\n    fun onWebSocketLoginSuccess()\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ws/OnWebSocketMessageListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.ws\n\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 12:05 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface OnWebSocketMessageListener {\n    fun onMessage(webSocket: OkHttpWebSocketSession, text: String)\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ws/OnWebSocketQueueSizeOutListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.ws\n\n\n/**\n * didi Create on 2022/4/15 .\n *\n * Copyright (c) 2022/4/15 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/15 11:16 上午\n * @Description 等待发送队列超限制监听，当前队列超限制会触发重新链接，重新链接可能导致部分数据丢失\n */\n\ninterface OnWebSocketQueueSizeOutListener {\n\n    fun onWebSocketQueueSizeOut()\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ws/OnWebSocketReConnectListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.ws\n\n\n/**\n * didi Create on 2022/4/15 .\n *\n * Copyright (c) 2022/4/15 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/15 11:15 上午\n * @Description 重新链接监听 ， 监听自动重新链接\n */\n\n\ninterface OnWebSocketReConnectListener {\n    fun onWebSocketReConnect()\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ws/OnWebSocketStatusChangeListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.ws\n\n\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 12:04 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface OnWebSocketStatusChangeListener {\n\n    fun onClosed(webSocket: OkHttpWebSocketSession, code: Int, reason: String)\n\n    fun onOpen(webSocket: OkHttpWebSocketSession, response: String)\n\n    fun onFailure(webSocket: OkHttpWebSocketSession, t: Throwable, response: String?)\n\n    fun onClosing(webSocket: OkHttpWebSocketSession, code: Int, reason: String)\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ws/OnWebSocketTextPackageListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.ws\n\nimport com.didichuxing.doraemonkit.kit.connect.data.TextPackage\n\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 12:05 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface OnWebSocketTextPackageListener {\n    fun onReceiveTextPackage(webSocket: OkHttpWebSocketSession, textPackage: TextPackage)\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ws/WebSocketClient.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.ws\n\nimport android.text.TextUtils\nimport com.didichuxing.doraemonkit.kit.connect.ConnectConfig\nimport com.didichuxing.doraemonkit.kit.connect.data.LoginData\nimport com.didichuxing.doraemonkit.kit.connect.data.PackageType\nimport com.didichuxing.doraemonkit.kit.connect.data.TextPackage\nimport com.didichuxing.doraemonkit.kit.connect.parser.JsonParser\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.util.*\nimport okhttp3.OkHttpClient\nimport okio.ByteString\nimport java.text.SimpleDateFormat\nimport java.util.*\nimport java.util.concurrent.TimeUnit\n\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 3:46 下午\n * @Description\n */\n\nclass WebSocketClient {\n\n    private val onWebSocketStatusChangeListenerSet: MutableSet<OnWebSocketStatusChangeListener> = mutableSetOf()\n    private val onWebSocketMessageListenerSet: MutableSet<OnWebSocketMessageListener> = mutableSetOf()\n    private val onWebSocketTextPackageListenerSet: MutableSet<OnWebSocketTextPackageListener> = mutableSetOf()\n    private val onWebSocketBytesMessageListenerSet: MutableSet<OnWebSocketBytesMessageListener> = mutableSetOf()\n    private val onWebSocketLoginSuccessListenerSet = mutableSetOf<OnWebSocketLoginSuccessListener>()\n\n    private val onWebSocketCloseListenerSet = mutableSetOf<OnWebSocketCloseListener>()\n    private val onWebSocketQueueSizeOutListenerSet = mutableSetOf<OnWebSocketQueueSizeOutListener>()\n    private val onWebSocketReConnectListenerSet = mutableSetOf<OnWebSocketReConnectListener>()\n\n\n    private var okHttpClient: OkHttpClient = OkHttpClient.Builder()\n        .readTimeout(5, TimeUnit.SECONDS)\n        .writeTimeout(5, TimeUnit.SECONDS)\n        .connectTimeout(5, TimeUnit.SECONDS)\n        .build()\n\n    private var webSocketSession: WebSocketSession = OkHttpWebSocketSession(okHttpClient)\n\n    private var timer: Timer = Timer(true)\n    private var loginSuccess: Boolean = false\n    private var autoConnect: Boolean = false\n    private var heartBeatEnable: Boolean = false\n    private var keepConnectEnable: Boolean = false\n    private var closed: Boolean = true\n\n    constructor() {\n        bindTask()\n        bindSession()\n    }\n\n    fun connect(url: String) {\n        WsLog.i(\"connect. \")\n        closed = false\n        webSocketSession.connect(url)\n    }\n\n    fun reConnect() {\n        WsLog.i(\"reConnect. \")\n        closed = false\n        webSocketSession.reConnect()\n    }\n\n    fun reConnect(url: String) {\n        WsLog.i(\"reConnect url=${url} \")\n        closed = false\n        loginSuccess = false\n        webSocketSession.close(1001, \"close\")\n        webSocketSession.cancel()\n        webSocketSession = OkHttpWebSocketSession(okHttpClient)\n        bindSession()\n        webSocketSession.connect(url)\n    }\n\n    fun close() {\n        WsLog.i(\"onClosed. \")\n        loginSuccess = false\n        stopHeart()\n        stopAutoConnect()\n        webSocketSession.close(1000, \"close\")\n        webSocketSession.cancel()\n        closed = true\n\n        onWebSocketCloseListenerSet.forEach {\n            it.onWebSocketClose()\n        }\n    }\n\n    /**\n     * 不在使用的时候需要销毁释放资源\n     * 一旦销毁就不可继续使用\n     */\n    fun destroy() {\n        close()\n        stopTimer()\n    }\n\n\n    fun isLoginSuccess(): Boolean {\n        return loginSuccess\n    }\n\n    /**\n     * 客户端是否在关闭状态，主动关闭\n     */\n    fun isClosed(): Boolean {\n        return closed\n    }\n\n    fun getConnectStatus(): ConnectStatus {\n        return webSocketSession.connectStatus\n    }\n\n    fun send(text: String): Boolean {\n        return webSocketSession.send(text)\n    }\n\n    fun send(bytes: ByteString): Boolean {\n        return webSocketSession.send(bytes)\n    }\n\n    fun startAutoConnect() {\n        autoConnect = true\n        keepConnectEnable = true\n    }\n\n    fun stopAutoConnect() {\n        autoConnect = false\n        keepConnectEnable = false\n    }\n\n    private fun dispatchTextMessage(webSocket: OkHttpWebSocketSession, text: String) {\n        onWebSocketMessageListenerSet.forEach {\n            try {\n                it.onMessage(webSocket, text)\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    private fun dispatchTextPackage(webSocket: OkHttpWebSocketSession, textPackage: TextPackage) {\n        onWebSocketTextPackageListenerSet.forEach {\n            try {\n                it.onReceiveTextPackage(webSocket, textPackage)\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    private fun onLoginResponse(textPackage: TextPackage) {\n        if (!TextUtils.isEmpty(textPackage.data)) {\n            val loginData = JsonParser.toLoginData(textPackage.data)\n            if (!TextUtils.isEmpty(loginData.connectSerial)) {\n                ConnectConfig.saveConnectSerial(loginData.connectSerial)\n            }\n        }\n        loginSuccess = true\n        onWebSocketLoginSuccessListenerSet.forEach {\n            try {\n                it.onWebSocketLoginSuccess()\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    private fun bindTask() {\n        timer.schedule(KeepConnectTimerTask(), 5000, 2000)\n        timer.schedule(HeartTimerTask(), 5000, 30 * 1000)\n    }\n\n    private fun bindSession() {\n        webSocketSession.onWebSocketBytesMessageListener = object : OnWebSocketBytesMessageListener {\n            override fun onMessage(webSocket: OkHttpWebSocketSession, bytes: ByteString) {\n                onWebSocketBytesMessageListenerSet.forEach {\n                    it.onMessage(webSocket, bytes)\n                }\n            }\n        }\n        webSocketSession.onWebSocketMessageListener = object : OnWebSocketMessageListener {\n            override fun onMessage(webSocket: OkHttpWebSocketSession, text: String) {\n                try {\n                    val textPackage = JsonParser.toTextPackage(text)\n                    if (textPackage.type == PackageType.HEART_BEAT) {\n                        WsLog.d(\"receive HEART_BEAT text=${text}\")\n                        return\n                    } else if (textPackage.type == PackageType.LOGIN) {\n                        WsLog.d(\"receive LOGIN text=${text}\")\n                        onLoginResponse(textPackage)\n                        return\n                    }\n                    //登录及心跳不通知到外层\n                    dispatchTextPackage(webSocket, textPackage)\n                } catch (e: Exception) {\n                    e.printStackTrace()\n                }\n                dispatchTextMessage(webSocket, text)\n            }\n        }\n\n        webSocketSession.onWebSocketQueueSizeOutListener = object : OnWebSocketQueueSizeOutListener {\n\n            override fun onWebSocketQueueSizeOut() {\n                WsLog.i(\"onQueueSize out. \")\n                onWebSocketQueueSizeOutListenerSet.forEach {\n                    try {\n                        it.onWebSocketQueueSizeOut()\n                    } catch (e: Exception) {\n                        e.printStackTrace()\n                    }\n                }\n            }\n        }\n\n        webSocketSession.onWebSocketStatusChangeListener = object : OnWebSocketStatusChangeListener {\n            override fun onClosed(webSocket: OkHttpWebSocketSession, code: Int, reason: String) {\n                WsLog.i(\"onClosed. \")\n                loginSuccess = false\n                stopHeart()\n                checkKeepConnect()\n                onWebSocketStatusChangeListenerSet.forEach {\n                    try {\n                        it.onClosed(webSocket, code, reason)\n                    } catch (e: Exception) {\n                        e.printStackTrace()\n                    }\n                }\n            }\n\n            override fun onOpen(webSocket: OkHttpWebSocketSession, response: String) {\n                WsLog.i(\"onOpen. \")\n                login()\n                startHeart()\n                onWebSocketStatusChangeListenerSet.forEach {\n                    try {\n                        it.onOpen(webSocket, response)\n                    } catch (e: Exception) {\n                        e.printStackTrace()\n                    }\n                }\n            }\n\n            override fun onFailure(webSocket: OkHttpWebSocketSession, t: Throwable, response: String?) {\n                WsLog.i(\"onFailure. \")\n                loginSuccess = false\n                stopHeart()\n                checkKeepConnect()\n                onWebSocketStatusChangeListenerSet.forEach {\n                    try {\n                        it.onFailure(webSocket, t, response)\n                    } catch (e: Exception) {\n                        e.printStackTrace()\n                    }\n                }\n            }\n\n            override fun onClosing(webSocket: OkHttpWebSocketSession, code: Int, reason: String) {\n                WsLog.i(\"onClosing. \")\n                onWebSocketStatusChangeListenerSet.forEach {\n                    try {\n                        it.onClosing(webSocket, code, reason)\n                    } catch (e: Exception) {\n                        e.printStackTrace()\n                    }\n                }\n            }\n        }\n    }\n\n    private fun login() {\n        val pi = DokitDeviceUtils.getPackageInfo(Utils.getApp())\n        val name = \"${DeviceUtils.getManufacturer()}-${DeviceUtils.getModel()}(${DeviceUtils.getSDKVersionName()})\"\n        val connectSerial = ConnectConfig.getConnectSerial()\n        val loginData = LoginData(\n            name,\n            \"android\",\n            \"${DeviceUtils.getSDKVersionName()}\",\n            \"${UIUtils.getWidthPixels()} x ${UIUtils.getRealHeightPixels()}\",\n            \"${DeviceUtils.getManufacturer()}-${DeviceUtils.getModel()}\",\n            \"${DoKitManager.IP_ADDRESS_BY_WIFI}\",\n            connectSerial,\n            \"${pi.packageName}\",\n            \"${pi.versionName}\"\n        )\n\n        webSocketSession.send(JsonParser.toLoginJson(loginData))\n    }\n\n    fun addOnWebSocketReConnectListener(listener: OnWebSocketReConnectListener) {\n        onWebSocketReConnectListenerSet.add(listener)\n    }\n\n    fun removeOnWebSocketReConnectListener(listener: OnWebSocketReConnectListener) {\n        onWebSocketReConnectListenerSet.remove(listener)\n    }\n\n    fun addOnWebSocketQueueSizeOutListener(listener: OnWebSocketQueueSizeOutListener) {\n        onWebSocketQueueSizeOutListenerSet.add(listener)\n    }\n\n    fun removeOnWebSocketQueueSizeOutListener(listener: OnWebSocketQueueSizeOutListener) {\n        onWebSocketQueueSizeOutListenerSet.remove(listener)\n    }\n\n    fun addOnWebSocketCloseListener(listener: OnWebSocketCloseListener) {\n        onWebSocketCloseListenerSet.add(listener)\n    }\n\n    fun removeOnWebSocketCloseListener(listener: OnWebSocketCloseListener) {\n        onWebSocketCloseListenerSet.remove(listener)\n    }\n\n    fun addOnWebSocketLoginSuccessListener(listener: OnWebSocketLoginSuccessListener) {\n        onWebSocketLoginSuccessListenerSet.add(listener)\n    }\n\n    fun removeOnWebSocketLoginSuccessListener(listener: OnWebSocketLoginSuccessListener) {\n        onWebSocketLoginSuccessListenerSet.remove(listener)\n    }\n\n    fun addOnWebSocketBytesMessageListener(listener: OnWebSocketBytesMessageListener) {\n        onWebSocketBytesMessageListenerSet.add(listener)\n    }\n\n    fun removeOnWebSocketBytesMessageListener(listener: OnWebSocketBytesMessageListener) {\n        onWebSocketBytesMessageListenerSet.remove(listener)\n    }\n\n    fun addOnWebSocketTextPackageListener(listener: OnWebSocketTextPackageListener) {\n        onWebSocketTextPackageListenerSet.add(listener)\n    }\n\n    fun removeOnWebSocketTextPackageListener(listener: OnWebSocketTextPackageListener) {\n        onWebSocketTextPackageListenerSet.remove(listener)\n    }\n\n    fun addOnWebSocketMessageListener(listener: OnWebSocketMessageListener) {\n        onWebSocketMessageListenerSet.add(listener)\n    }\n\n    fun removeOnWebSocketMessageListener(listener: OnWebSocketMessageListener) {\n        onWebSocketMessageListenerSet.remove(listener)\n    }\n\n    fun addOnWebSocketStatusChangeListener(listener: OnWebSocketStatusChangeListener) {\n        onWebSocketStatusChangeListenerSet.add(listener)\n    }\n\n    fun removeOnWebSocketStatusChangeListener(listener: OnWebSocketStatusChangeListener) {\n        onWebSocketStatusChangeListenerSet.remove(listener)\n    }\n\n\n    private fun startHeart() {\n        heartBeatEnable = true\n\n    }\n\n\n    private fun stopHeart() {\n        heartBeatEnable = false\n    }\n\n    private fun stopTimer() {\n        timer.cancel()\n    }\n\n    private fun checkKeepConnect() {\n        keepConnectEnable = true\n    }\n\n    private fun checkReConnect() {\n        if (webSocketSession.connectStatus != ConnectStatus.CONNECT\n            && webSocketSession.connectStatus != ConnectStatus.PREPARE\n        ) {\n            webSocketSession.reConnect()\n            onWebSocketReConnectListenerSet.forEach {\n                it.onWebSocketReConnect()\n            }\n        }\n    }\n\n    inner class HeartTimerTask : TimerTask() {\n        override fun run() {\n            if (!heartBeatEnable) {\n                return\n            }\n            val dateTime = TimeUtils.getNowString(SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss SSS\"))\n            val heartPackage = JsonParser.toJson(PackageType.HEART_BEAT, \"{'dateTime':'${dateTime}}'\")\n            val success = webSocketSession.send(heartPackage)\n            if (!success) {\n                WsLog.i(\"HeartTimerTask.send() not success. \")\n                checkReConnect()\n            }\n        }\n    }\n\n    inner class KeepConnectTimerTask : TimerTask() {\n        override fun run() {\n            if (!keepConnectEnable) {\n                return\n            }\n            checkReConnect()\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ws/WebSocketSession.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.ws\n\nimport okio.ByteString\n\n\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 2:44 下午\n * @Description 自定义的WebSocket 链接接口定义\n */\n\ninterface WebSocketSession {\n\n    var onWebSocketStatusChangeListener: OnWebSocketStatusChangeListener?\n    var onWebSocketMessageListener: OnWebSocketMessageListener?\n    var onWebSocketBytesMessageListener: OnWebSocketBytesMessageListener?\n    var onWebSocketQueueSizeOutListener:OnWebSocketQueueSizeOutListener?\n    var connectStatus: ConnectStatus\n\n    fun connect(url: String)\n    fun reConnect()\n    fun send(text: String): Boolean\n    fun send(bytes: ByteString): Boolean\n    fun close(code: Int, reason: String): Boolean\n    fun queueSize(): Long\n    fun cancel()\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/connect/ws/WsLog.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect.ws\n\nimport com.didichuxing.doraemonkit.util.LogHelper\n/**\n * didi Create on 2022/4/12 .\n *\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 6:07 下午\n * @Description 用一句话说明文件功能\n */\nobject WsLog {\n    private const val TAG = \"WebSocket\"\n\n    fun d(msg: String) {\n        LogHelper.d(TAG, msg)\n    }\n\n    fun i(msg: String) {\n        LogHelper.i(TAG, msg)\n    }\n\n    fun e(msg: String) {\n        LogHelper.e(TAG, msg)\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/AbsDoKitFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport androidx.annotation.LayoutRes\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n\n/**\n * @Author: changzuozhen\n * @Date: 2020-12-22\n *\n *\n * 全屏页面\n * @see com.didichuxing.doraemonkit.kit.core.AbsDoKitFragment\n * 启动工具函数\n *\n * @see com.didichuxing.doraemonkit.kit.core.SimpleDoKitStarter.startFullScreen\n */\nabstract class AbsDoKitFragment : BaseFragment() {\n    val bundle: Bundle?\n        get() = if (activity == null || requireActivity().intent == null || requireActivity().intent.extras == null) {\n            null\n        } else requireActivity().intent.extras\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        val rootView = super.onCreateView(inflater, container, savedInstanceState)\n        inflater.inflate(\n            layoutId(),\n            rootView!!.findViewById<View>(R.id.contentContainer) as FrameLayout,\n            true\n        )\n        return rootView\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        initView()\n        onViewCreated(view)\n    }\n\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_simple_dokit_page\n    }\n\n    protected open fun onViewCreated(view: View?) {}\n\n    @LayoutRes\n    abstract fun layoutId(): Int\n\n   open fun initTitle(): String {\n        return this.javaClass.simpleName\n    }\n\n    private fun initView() {\n        val homeTitleBar = findViewById<HomeTitleBar>(R.id.title_bar)\n        homeTitleBar.setTitle(initTitle())\n        homeTitleBar.setListener { requireActivity().finish() }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/AbsDoKitView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.animation.Animator\nimport android.animation.AnimatorListenerAdapter\nimport android.animation.ValueAnimator\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.content.res.Configuration\nimport android.content.res.Resources\nimport android.graphics.PixelFormat\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.Handler\nimport android.os.Looper\nimport android.view.*\nimport android.view.ViewTreeObserver.OnGlobalLayoutListener\nimport android.widget.FrameLayout\nimport androidx.annotation.IdRes\nimport androidx.annotation.StringRes\nimport androidx.core.view.GravityCompat\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.DoKitEnv\nimport com.didichuxing.doraemonkit.config.FloatIconConfig\nimport com.didichuxing.doraemonkit.extension.tagName\nimport com.didichuxing.doraemonkit.kit.main.MainIconDoKitView\nimport com.didichuxing.doraemonkit.util.ActivityUtils\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport com.didichuxing.doraemonkit.util.ScreenUtils\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.MainScope\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.plus\nimport java.lang.ref.WeakReference\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-20-16:22\n * 描    述：dokit 页面浮标抽象类 一般的悬浮窗都需要继承该抽象接口\n * 修订历史：\n * ================================================\n */\nabstract class AbsDoKitView : DoKitView, TouchProxy.OnTouchEventListener, DoKitViewManager.DokitViewAttachedListener {\n\n    class ViewArgs {\n        var mode: DoKitViewLaunchMode = DoKitViewLaunchMode.SINGLE_INSTANCE\n        var normalMode = DoKitManager.IS_NORMAL_FLOAT_MODE\n        var edgePinned = false\n    }\n\n    val doKitViewScope = MainScope() + CoroutineName(this.toString())\n\n    val TAG = this.tagName\n\n    /**\n     * 页面启动模式\n     */\n    var mode: DoKitViewLaunchMode\n        get() = viewProps.mode\n        set(value) {\n            viewProps.mode = value\n        }\n\n    val isNormalMode get() = viewProps.normalMode\n\n    /**\n     * 手势代理\n     */\n    @JvmField\n    var mTouchProxy = TouchProxy(this)\n\n    protected val viewProps = ViewArgs()\n\n    @JvmField\n    protected var mWindowManager = DoKitViewManager.INSTANCE.windowManager\n\n    /**\n     * 创建FrameLayout#LayoutParams 内置悬浮窗调用\n     */\n    var normalLayoutParams: FrameLayout.LayoutParams? = null\n        private set\n\n    /**\n     * 创建FrameLayout#LayoutParams 系统悬浮窗调用\n     */\n    var systemLayoutParams: WindowManager.LayoutParams? = null\n        private set\n\n    private var mHandler: Handler? = Looper.myLooper()?.let { Handler(it) }\n\n    private val mInnerReceiver = InnerReceiver()\n\n    /**\n     * 当前dokitViewName 用来当做map的key 和dokitViewIntent的tag一致\n     */\n    var tag = this.tagName\n    var bundle: Bundle? = null\n\n    /**\n     * weakActivity attach activity\n     */\n    private var mAttachActivity: WeakReference<Activity>? = null\n\n    val activity: Activity\n        get() = if (mAttachActivity != null) {\n            mAttachActivity!!.get()!!\n        } else ActivityUtils.getTopActivity()\n\n    fun setActivity(activity: Activity) {\n        mAttachActivity = WeakReference(activity)\n    }\n\n    /**\n     * 整个悬浮窗的View\n     */\n    private var mRootView: DoKitFrameLayout? = null\n\n    val doKitView: View?\n        get() = mRootView\n\n    /**\n     * rootView的直接子View 一般是用户的xml布局 被添加到mRootView中\n     */\n    private var mChildView: View? = null\n\n    /**\n     * 只控件在布局边界发生大小变化被裁剪的原因：\n     * https://juejin.cn/post/6844903624452079623\n     *\n     */\n    val parentView: DoKitFrameLayout?\n        get() = if (isNormalMode && mRootView != null) {\n            mRootView!!.parent as DoKitFrameLayout\n        } else null\n\n    /**\n     * 用来保存rootview的LayoutParams\n     */\n    private lateinit var mDoKitViewLayoutParams: DoKitViewLayoutParams\n\n    /**\n     * 上一次DoKitview的位置信息\n     */\n    private val mLastDoKitViewPosInfo: LastDoKitViewPosInfo by lazy {\n        if (DoKitViewManager.INSTANCE.getLastDokitViewPosInfo(tag) == null) {\n            val posInfo = LastDoKitViewPosInfo()\n            DoKitViewManager.INSTANCE.saveLastDokitViewPosInfo(tag, posInfo)\n            posInfo\n        } else {\n            DoKitViewManager.INSTANCE.getLastDokitViewPosInfo(tag)!!\n        }\n    }\n\n    /**\n     * 根布局的实际宽\n     */\n    private var mDokitViewWidth = 0\n\n    /**\n     * 根布局的实际高\n     */\n    private var mDokitViewHeight = 0\n    private var mViewTreeObserver: ViewTreeObserver? = null\n\n    private val mOnGlobalLayoutListener: OnGlobalLayoutListener = OnGlobalLayoutListener {\n        //每次布局发生变动的时候重新赋值\n        mRootView?.let {\n            mDokitViewWidth = it.measuredWidth\n            mDokitViewHeight = it.measuredHeight\n            mLastDoKitViewPosInfo.doKitViewWidth = mDokitViewWidth\n            mLastDoKitViewPosInfo.doKitViewHeight = mDokitViewHeight\n        }\n    }\n\n    /**\n     * 执行floatPage create\n     *\n     * @param context 上下文环境\n     */\n    @SuppressLint(\"ClickableViewAccessibility\")\n    fun performCreate(context: Context) {\n        try {\n            //调用onCreate方法\n            onCreate(context)\n            if (!isNormalMode) {\n                DoKitViewManager.INSTANCE.addDokitViewAttachedListener(this)\n            }\n            mRootView = if (isNormalMode) {\n                DoKitFrameLayout(\n                    context,\n                    DoKitFrameLayout.DoKitFrameLayoutFlag_CHILD\n                )\n            } else {\n                //系统悬浮窗的返回按键监听\n                object : DoKitFrameLayout(context, DoKitFrameLayoutFlag_CHILD) {\n                    override fun dispatchKeyEvent(event: KeyEvent): Boolean {\n                        if (event.action == KeyEvent.ACTION_UP && shouldDealBackKey()) {\n                            //监听返回键\n                            if (event.keyCode == KeyEvent.KEYCODE_BACK || event.keyCode == KeyEvent.KEYCODE_HOME) {\n                                return onBackPressed()\n                            }\n                        }\n                        return super.dispatchKeyEvent(event)\n                    }\n                }\n            }\n            //添加根布局的layout回调\n            addViewTreeObserverListener()\n\n            //调用onCreateView抽象方法\n            mChildView = onCreateView(context, mRootView)\n            //将子View添加到rootview中\n            mRootView?.addView(mChildView)\n            mRootView?.title = this.javaClass.name\n            //设置根布局的手势拦截\n            mRootView?.setOnTouchListener { v, event -> mTouchProxy.onTouchEvent(v, event) }\n            //调用onViewCreated回调\n            onViewCreated(mRootView)\n            mDoKitViewLayoutParams = DoKitViewLayoutParams()\n            //分别创建对应的LayoutParams\n            if (isNormalMode) {\n                normalLayoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT)\n                    .apply {\n                        gravity = GravityCompat.START or Gravity.TOP\n                    }\n                mDoKitViewLayoutParams.gravity = GravityCompat.START or Gravity.TOP\n            } else {\n                systemLayoutParams = WindowManager.LayoutParams()\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                    //android 8.0\n                    systemLayoutParams?.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY\n                } else {\n                    systemLayoutParams?.type = WindowManager.LayoutParams.TYPE_PHONE\n                }\n                //shouldDealBackKey : fasle 不自己收返回事件处理\n                if (shouldDealBackKey()) {\n                    //自己处理返回按键\n                    systemLayoutParams?.flags =\n                        WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS\n                    mDoKitViewLayoutParams.flags =\n                        WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or DoKitViewLayoutParams.FLAG_LAYOUT_NO_LIMITS\n                } else {\n                    //参考：http://www.shirlman.com/tec/20160426/362\n                    //设置WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE会导致RootView监听不到返回按键的监听失效 系统处理返回按键\n                    systemLayoutParams?.flags =\n                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS\n                    mDoKitViewLayoutParams.flags =\n                        DoKitViewLayoutParams.FLAG_NOT_FOCUSABLE or DoKitViewLayoutParams.FLAG_LAYOUT_NO_LIMITS\n                }\n                systemLayoutParams?.apply {\n                    format = PixelFormat.TRANSPARENT\n                    gravity = GravityCompat.START or Gravity.TOP\n                }\n\n\n                mDoKitViewLayoutParams.gravity = GravityCompat.START or Gravity.TOP\n                //动态注册关闭系统弹窗的广播\n                val intentFilter = IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)\n                context.registerReceiver(mInnerReceiver, intentFilter)\n            }\n            initDokitViewLayoutParams(mDoKitViewLayoutParams)\n            if (isNormalMode) {\n                normalLayoutParams?.let {\n                    onNormalLayoutParamsCreated()\n                }\n            } else {\n                systemLayoutParams?.let {\n                    onSystemLayoutParamsCreated()\n                }\n            }\n        } catch (e: Exception) {\n            LogHelper.e(TAG, \"e===>\" + e.message)\n            e.printStackTrace()\n        }\n    }\n\n    fun performDestroy() {\n        if (!isNormalMode) {\n            context?.unregisterReceiver(mInnerReceiver)\n        }\n        //移除布局监听\n        removeViewTreeObserverListener()\n        mHandler = null\n        mRootView = null\n        mAttachActivity = null\n        onDestroy()\n    }\n\n    private fun addViewTreeObserverListener() {\n        if (mViewTreeObserver == null && mRootView != null) {\n            mViewTreeObserver = mRootView!!.viewTreeObserver\n            mViewTreeObserver?.addOnGlobalLayoutListener(mOnGlobalLayoutListener)\n\n        }\n    }\n\n    private fun removeViewTreeObserverListener() {\n        mViewTreeObserver?.let {\n            if (it.isAlive) {\n                it.removeOnGlobalLayoutListener(mOnGlobalLayoutListener)\n            }\n        }\n    }\n\n    /**\n     * 确定普通浮标的初始位置\n     * LayoutParams创建完以后调用\n     * 调用时建议放在实现下方\n     *\n     * @param params\n     */\n    private fun onNormalLayoutParamsCreated() {\n        //如果有上一个页面的位置记录 这更新位置\n        normalLayoutParams?.apply {\n            width = mDoKitViewLayoutParams.width\n            height = mDoKitViewLayoutParams.height\n            gravity = mDoKitViewLayoutParams.gravity\n        }\n        val doKitViewInfo = DoKitViewManager.INSTANCE.getDoKitViewPos(tag)\n        if (doKitViewInfo != null) {\n            //竖向\n            if (doKitViewInfo.orientation == Configuration.ORIENTATION_PORTRAIT) {\n                normalLayoutParams?.apply {\n                    leftMargin = doKitViewInfo.portraitPoint.x\n                    topMargin = doKitViewInfo.portraitPoint.y\n                }\n            } else if (doKitViewInfo.orientation == Configuration.ORIENTATION_LANDSCAPE) {\n                normalLayoutParams?.apply {\n                    leftMargin = doKitViewInfo.landscapePoint.x\n                    topMargin = doKitViewInfo.landscapePoint.y\n                }\n            }\n        } else {\n            normalLayoutParams?.apply {\n                leftMargin = mDoKitViewLayoutParams.x\n                topMargin = mDoKitViewLayoutParams.y\n            }\n        }\n        portraitOrLandscape()\n    }\n\n    /**\n     * 用于普通模式下的横竖屏切换\n     */\n    private fun portraitOrLandscape() {\n        DoKitViewManager.INSTANCE.getDoKitViewPos(tag)\n            ?.also { doKitViewInfo ->\n                //横竖屏切换兼容\n                if (ScreenUtils.isPortrait()) {\n                    if (mLastDoKitViewPosInfo.isPortrait) {\n                        normalLayoutParams?.apply {\n                            leftMargin = doKitViewInfo.portraitPoint.x\n                            topMargin = doKitViewInfo.portraitPoint.y\n                        }\n                    } else {\n                        normalLayoutParams?.apply {\n                            leftMargin = (doKitViewInfo.landscapePoint.x * mLastDoKitViewPosInfo.leftMarginPercent).toInt()\n                            topMargin = (doKitViewInfo.landscapePoint.y * mLastDoKitViewPosInfo.topMarginPercent).toInt()\n                        }\n                    }\n                } else {\n                    if (mLastDoKitViewPosInfo.isPortrait) {\n                        normalLayoutParams?.apply {\n                            leftMargin = (doKitViewInfo.portraitPoint.x * mLastDoKitViewPosInfo.leftMarginPercent).toInt()\n                            topMargin = (doKitViewInfo.portraitPoint.y * mLastDoKitViewPosInfo.topMarginPercent).toInt()\n                        }\n                    } else {\n                        normalLayoutParams?.apply {\n                            leftMargin = doKitViewInfo.landscapePoint.x\n                            topMargin = doKitViewInfo.landscapePoint.y\n                        }\n                    }\n                }\n            }\n            ?: run {\n                //横竖屏切换兼容\n                if (ScreenUtils.isPortrait()) {\n                    if (mLastDoKitViewPosInfo.isPortrait) {\n                        normalLayoutParams?.apply {\n                            leftMargin = mDoKitViewLayoutParams.x\n                            topMargin = mDoKitViewLayoutParams.y\n                        }\n                    } else {\n                        normalLayoutParams?.apply {\n                            leftMargin = (mDoKitViewLayoutParams.x * mLastDoKitViewPosInfo.leftMarginPercent).toInt()\n                            topMargin = (mDoKitViewLayoutParams.y * mLastDoKitViewPosInfo.topMarginPercent).toInt()\n                        }\n                    }\n                } else {\n                    if (mLastDoKitViewPosInfo.isPortrait) {\n                        normalLayoutParams?.apply {\n                            leftMargin = (mDoKitViewLayoutParams.x * mLastDoKitViewPosInfo.leftMarginPercent).toInt()\n                            topMargin = (mDoKitViewLayoutParams.y * mLastDoKitViewPosInfo.topMarginPercent).toInt()\n                        }\n                    } else {\n                        normalLayoutParams?.apply {\n                            leftMargin = mDoKitViewLayoutParams.x\n                            topMargin = mDoKitViewLayoutParams.y\n                        }\n                    }\n                }\n            }\n        mLastDoKitViewPosInfo.setPortrait()\n        normalLayoutParams?.also {\n            mLastDoKitViewPosInfo.setLeftMargin(it.leftMargin)\n            mLastDoKitViewPosInfo.setTopMargin(it.topMargin)\n        }\n        if (tag == MainIconDoKitView::class.tagName) {\n            if (isNormalMode) {\n                normalLayoutParams?.also {\n                    FloatIconConfig.saveLastPosX(it.leftMargin)\n                    FloatIconConfig.saveLastPosY(it.topMargin)\n                }\n            } else {\n                systemLayoutParams?.also {\n                    FloatIconConfig.saveLastPosX(it.x)\n                    FloatIconConfig.saveLastPosY(it.y)\n                }\n            }\n        }\n\n        DoKitViewManager.INSTANCE.saveDokitViewPos(tag, normalLayoutParams?.leftMargin ?: 0, normalLayoutParams?.topMargin ?: 0)\n    }\n\n    /**\n     * 确定系统浮标的初始位置\n     * LayoutParams创建完以后调用\n     * 调用时建议放在实现下方\n     *\n     * @param params\n     */\n    private fun onSystemLayoutParamsCreated() {\n        //如果有上一个页面的位置记录 这更新位置\n        systemLayoutParams?.flags = mDoKitViewLayoutParams.flags\n        systemLayoutParams?.gravity = mDoKitViewLayoutParams.gravity\n        systemLayoutParams?.width = mDoKitViewLayoutParams.width\n        systemLayoutParams?.height = mDoKitViewLayoutParams.height\n        val doKitViewInfo = DoKitViewManager.INSTANCE.getDoKitViewPos(\n            tag\n        )\n        if (doKitViewInfo != null) {\n            if (ScreenUtils.isPortrait()) {\n                systemLayoutParams?.x = doKitViewInfo.portraitPoint.x\n                systemLayoutParams?.y = doKitViewInfo.portraitPoint.y\n            } else if (ScreenUtils.isLandscape()) {\n                systemLayoutParams?.x = doKitViewInfo.landscapePoint.x\n                systemLayoutParams?.y = doKitViewInfo.landscapePoint.y\n            }\n        } else {\n            systemLayoutParams?.x = mDoKitViewLayoutParams.x\n            systemLayoutParams?.y = mDoKitViewLayoutParams.y\n        }\n        systemLayoutParams?.let {\n            DoKitViewManager.INSTANCE.saveDokitViewPos(tag, it.x, it.y)\n        }\n    }\n\n    override fun onDestroy() {\n        if (!isNormalMode) {\n            DoKitViewManager.INSTANCE.removeDokitViewAttachedListener(this)\n        }\n        DoKitViewManager.INSTANCE.removeLastDokitViewPosInfo(tag)\n        mAttachActivity = null\n        doKitViewScope.cancel()\n    }\n\n    /**\n     * 默认实现为true\n     *\n     * @return\n     */\n    override fun canDrag(): Boolean {\n        return true\n    }\n\n    /**\n     * 搭配shouldDealBackKey使用 自定义处理完以后需要返回true\n     * 默认模式的onBackPressed 拦截在NormalDokitViewManager#getDokitRootContentView中被处理\n     * 系统模式下的onBackPressed 在当前类的performCreate 初始话DoKitView时被处理\n     * 返回false 表示交由系统处理\n     * 返回 true 表示当前的返回事件已由自己处理 并拦截了改返回事件\n     */\n    override fun onBackPressed(): Boolean {\n        return false\n    }\n\n    /**\n     * 默认不自己处理返回按键\n     *\n     * @return\n     */\n    override fun shouldDealBackKey(): Boolean {\n        return false\n    }\n\n    override fun onEnterBackground() {\n        mRootView?.let {\n            if (!isNormalMode) {\n                it.visibility = View.GONE\n            }\n        }\n    }\n\n    override fun onEnterForeground() {\n        mRootView?.let {\n            if (!isNormalMode) {\n                it.visibility = View.VISIBLE\n            }\n        }\n    }\n\n    override fun onMove(x: Int, y: Int, dx: Int, dy: Int) {\n        if (!canDrag()) {\n            return\n        }\n        if (isNormalMode) {\n            normalLayoutParams?.apply {\n                this.leftMargin += dx\n                this.topMargin += dy\n            }\n\n            //更新图标位置\n            updateViewLayout(tag, false)\n        } else {\n            systemLayoutParams?.apply {\n                this.x += dx\n                this.y += dy\n            }\n            //限制布局边界\n            resetBorderline(normalLayoutParams, systemLayoutParams)\n            mWindowManager.updateViewLayout(mRootView, systemLayoutParams)\n        }\n    }\n\n    /**\n     * 手指弹起时保存当前浮标位置\n     *\n     * @param x\n     * @param y\n     */\n    override fun onUp(x: Int, y: Int) {\n        if (!canDrag()) {\n            return\n        }\n        if (!viewProps.edgePinned) {\n            endMoveAndRecord()\n            return\n        }\n        animatedMoveToEdge()\n    }\n\n    /**\n     * 手指按下时的操作\n     *\n     * @param x\n     * @param y\n     */\n    override fun onDown(x: Int, y: Int) {\n        if (!canDrag()) {\n            return\n        }\n    }\n\n    /**\n     * 广播接收器 系统悬浮窗需要调用\n     */\n    private inner class InnerReceiver : BroadcastReceiver() {\n        val SYSTEM_DIALOG_REASON_KEY = \"reason\"\n        val SYSTEM_DIALOG_REASON_RECENT_APPS = \"recentapps\"\n        val SYSTEM_DIALOG_REASON_HOME_KEY = \"homekey\"\n        override fun onReceive(context: Context, intent: Intent) {\n            val action = intent.action\n            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS == action) {\n                val reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY)\n                if (reason != null) {\n                    if (reason == SYSTEM_DIALOG_REASON_HOME_KEY) {\n                        //点击home键\n                        onHomeKeyPress()\n                    } else if (reason == SYSTEM_DIALOG_REASON_RECENT_APPS) {\n                        //点击menu按钮\n                        onRecentAppKeyPress()\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * home键被点击 只有系统悬浮窗控件才会被调用\n     */\n    open fun onHomeKeyPress() {}\n\n    /**\n     * 菜单键被点击 只有系统悬浮窗控件才会被调用\n     */\n    open fun onRecentAppKeyPress() {}\n\n    /**\n     * 不能在改方法中进行dokitview的添加和删除 因为处于遍历过程在\n     * 只有系统模式下才会调用\n     *\n     * @param doKitView\n     */\n    override fun onDokitViewAdd(doKitView: AbsDoKitView?) {}\n\n    override fun onResume() {\n        mRootView?.requestLayout()\n    }\n\n    override fun onPause() {}\n\n    /**\n     * 系统悬浮窗需要调用\n     *\n     * @return\n     */\n    val context: Context?\n        get() = if (mRootView != null) {\n            mRootView!!.context\n        } else {\n            null\n        }\n\n    val resources: Resources?\n        get() = if (context == null) {\n            null\n        } else context!!.resources\n\n    fun getString(@StringRes resId: Int): String? {\n        return if (context == null) {\n            null\n        } else context!!.getString(resId)\n    }\n\n    val isShow: Boolean\n        get() = mRootView!!.isShown\n\n    protected fun <T : View> findViewById(@IdRes id: Int): T? {\n        if (mRootView == null) {\n            return null\n        }\n        return mRootView?.findViewById(id)\n    }\n\n    /**\n     * 将当前dokitView于activity解绑\n     */\n    fun detach() {\n        DoKit.removeFloating(this)\n    }\n\n    /**\n     * 操作DecorView的直接子布局\n     * 测试专用\n     */\n    fun dealDecorRootView(decorRootView: FrameLayout?) {\n        if (isNormalMode) {\n            if (decorRootView == null) {\n                return\n            }\n        } // FIXME: useless code, what that intention? @jtsky\n    }\n\n    /**\n     * 更新view的位置\n     *\n     * @param isActivityBackResume 是否是从其他页面返回时更新的位置\n     */\n    open fun updateViewLayout(tag: String, isActivityBackResume: Boolean) {\n        if (mRootView == null || mChildView == null || normalLayoutParams == null || !isNormalMode) {\n            return\n        }\n        normalLayoutParams?.apply {\n            if (isActivityBackResume) {\n                if (tag == MainIconDoKitView::class.tagName) {\n                    this.leftMargin = FloatIconConfig.getLastPosX()\n                    this.topMargin = FloatIconConfig.getLastPosY()\n                } else {\n                    val doKitViewInfo = DoKitViewManager.INSTANCE.getDoKitViewPos(tag)\n                    if (doKitViewInfo != null) {\n                        if (doKitViewInfo.orientation == Configuration.ORIENTATION_PORTRAIT) {\n                            this.leftMargin = doKitViewInfo.portraitPoint.x\n                            this.topMargin = doKitViewInfo.portraitPoint.y\n                        } else {\n                            this.leftMargin = doKitViewInfo.landscapePoint.x\n                            this.topMargin = doKitViewInfo.landscapePoint.y\n                        }\n                    }\n                }\n            } else {\n                //非页面切换的时候保存当前位置信息\n                mLastDoKitViewPosInfo.setPortrait()\n                mLastDoKitViewPosInfo.setLeftMargin(this.leftMargin)\n                mLastDoKitViewPosInfo.setTopMargin(this.topMargin)\n            }\n            if (tag == MainIconDoKitView::class.tagName) {\n                this.width = DoKitViewLayoutParams.WRAP_CONTENT\n                this.height = DoKitViewLayoutParams.WRAP_CONTENT\n                //            mFrameLayoutParams.width = ConvertUtils.dp2px(MainIconDokitView.FLOAT_SIZE);\n//            mFrameLayoutParams.height = ConvertUtils.dp2px(MainIconDokitView.FLOAT_SIZE);\n            } else {\n                if (mDokitViewWidth != 0) {\n                    this.width = mDokitViewWidth\n                }\n                if (mDokitViewHeight != 0) {\n                    this.height = mDokitViewHeight\n                }\n            }\n\n            resetBorderline(this, systemLayoutParams)\n            //更新根布局的位置\n            mRootView?.layoutParams = this\n        }\n    }\n\n    /**\n     * 限制边界 调用的时候必须保证是在控件能获取到宽高德前提下\n     */\n    private fun resetBorderline(\n        normalFrameLayoutParams: FrameLayout.LayoutParams?,\n        windowLayoutParams: WindowManager.LayoutParams?\n    ) {\n        //如果是系统模式或者手动关闭动态限制边界\n        if (!restrictBorderline()) {\n            return\n        }\n\n        //普通模式\n        if (isNormalMode) {\n            if (normalFrameLayoutParams != null) {\n                if (ScreenUtils.isPortrait()) {\n                    if (normalFrameLayoutParams.topMargin >= screenLongSideLength - mDokitViewHeight) {\n                        normalFrameLayoutParams.topMargin = screenLongSideLength - mDokitViewHeight\n                    }\n                } else {\n                    if (normalFrameLayoutParams.topMargin >= screenShortSideLength - mDokitViewHeight) {\n                        normalFrameLayoutParams.topMargin = screenShortSideLength - mDokitViewHeight\n                    }\n                }\n\n                if (ScreenUtils.isPortrait()) {\n                    if (normalFrameLayoutParams.leftMargin >= screenShortSideLength - mDokitViewWidth) {\n                        normalFrameLayoutParams.leftMargin = screenShortSideLength - mDokitViewWidth\n                    }\n                } else {\n                    if (normalFrameLayoutParams.leftMargin >= screenLongSideLength - mDokitViewWidth) {\n                        normalFrameLayoutParams.leftMargin = screenLongSideLength - mDokitViewWidth\n                    }\n                }\n\n                if (normalFrameLayoutParams.topMargin <= 0) {\n                    normalFrameLayoutParams.topMargin = 0\n                }\n\n                if (normalFrameLayoutParams.leftMargin <= 0) {\n                    normalFrameLayoutParams.leftMargin = 0\n                }\n            }\n\n        } else {\n            if (windowLayoutParams != null) {\n                if (ScreenUtils.isPortrait()) {\n                    if (windowLayoutParams.y >= screenLongSideLength - mDokitViewHeight) {\n                        windowLayoutParams.y = screenLongSideLength - mDokitViewHeight\n                    }\n                } else {\n                    if (windowLayoutParams.y >= screenShortSideLength - mDokitViewHeight) {\n                        windowLayoutParams.y = screenShortSideLength - mDokitViewHeight\n                    }\n                }\n\n                if (ScreenUtils.isPortrait()) {\n                    if (windowLayoutParams.x >= screenShortSideLength - mDokitViewWidth) {\n                        windowLayoutParams.x = screenShortSideLength - mDokitViewWidth\n                    }\n                } else {\n                    if (windowLayoutParams.x >= screenLongSideLength - mDokitViewWidth) {\n                        windowLayoutParams.x = screenLongSideLength - mDokitViewWidth\n                    }\n                }\n\n                //系统模式\n                if (windowLayoutParams.y <= 0) {\n                    windowLayoutParams.y = 0\n                }\n\n                if (windowLayoutParams.x <= 0) {\n                    windowLayoutParams.x = 0\n                }\n            }\n\n        }\n    }\n\n    /**\n     * 是否限制布局边界\n     *\n     * @return\n     */\n    open fun restrictBorderline(): Boolean {\n        return true\n    }\n\n    fun post(run: Runnable) {\n        mHandler?.post(run)\n    }\n\n    fun postDelayed(run: Runnable, delayMillis: Long) {\n        mHandler?.postDelayed(run, delayMillis)\n    }\n\n    /**\n     * 设置当前 kitView 不响应触摸事件\n     * 控件默认响应触摸事件\n     * 需要在子 view 的 onViewCreated 中调用\n     */\n    fun setDoKitViewNotResponseTouchEvent(view: View?) {\n        if (isNormalMode) {\n            view?.setOnTouchListener { _, _ -> false }\n        } else {\n            view?.setOnTouchListener(null)\n        }\n    }\n\n    /**\n     * 获取屏幕短边的长度 不包含statusBar\n     *\n     * @return\n     */\n    val screenShortSideLength: Int\n        get() = if (ScreenUtils.isPortrait()) {\n            ScreenUtils.getAppScreenWidth()\n        } else {\n            ScreenUtils.getAppScreenHeight()\n        }//ScreenUtils.getScreenHeight(); 包含statusBar\n    //ScreenUtils.getAppScreenHeight(); 不包含statusBar\n    /**\n     * 获取屏幕长边的长度 不包含statusBar\n     *\n     * @return\n     */\n    val screenLongSideLength: Int\n        get() = if (ScreenUtils.isPortrait()) {\n            //ScreenUtils.getScreenHeight(); 包含statusBar\n            //ScreenUtils.getAppScreenHeight(); 不包含statusBar\n            ScreenUtils.getAppScreenHeight()\n        } else {\n            ScreenUtils.getAppScreenWidth()\n        }\n\n    /**\n     * 强制刷新当前dokitview\n     */\n    open fun immInvalidate() {\n        mRootView?.requestLayout()\n    }\n\n    private fun animatedMoveToEdge() {\n        val viewSize = mRootView?.width ?: return\n        if (isNormalMode) {\n            val parent = (mRootView?.parent as? ViewGroup) ?: return\n            normalLayoutParams?.also { layoutAttrs ->\n                makeAnimator(layoutAttrs.leftMargin, viewSize, parent.width) {\n                    addUpdateListener { v ->\n                        layoutAttrs.leftMargin = v.animatedValue as Int\n                        updateViewLayout(tag, false)\n                    }\n                    addListener(object : AnimatorListenerAdapter() {\n                        override fun onAnimationEnd(animation: Animator?) {\n                            endMoveAndRecord()\n                        }\n                    })\n                }\n            }\n            return\n        }\n        systemLayoutParams?.also { layoutAttrs ->\n            makeAnimator(layoutAttrs.x, viewSize, DoKitEnv.windowSize.x) {\n                addUpdateListener { v ->\n                    layoutAttrs.x = v.animatedValue as Int\n                    mWindowManager.updateViewLayout(mRootView, layoutAttrs)\n                }\n                addListener(object : AnimatorListenerAdapter() {\n                    override fun onAnimationEnd(animation: Animator?) {\n                        endMoveAndRecord()\n                    }\n                })\n            }\n        }\n    }\n\n    private fun endMoveAndRecord() {\n        if (tag == MainIconDoKitView::class.tagName) {\n            if (isNormalMode) {\n                normalLayoutParams?.also {\n                    FloatIconConfig.saveLastPosX(it.leftMargin)\n                    FloatIconConfig.saveLastPosY(it.topMargin)\n                }\n            } else {\n                systemLayoutParams?.also {\n                    FloatIconConfig.saveLastPosX(it.x)\n                    FloatIconConfig.saveLastPosY(it.y)\n                }\n            }\n        }\n        // 保存在内存中\n        if (isNormalMode) {\n            normalLayoutParams?.also { DoKitViewManager.INSTANCE.saveDokitViewPos(tag, it.leftMargin, it.topMargin) }\n        } else {\n            systemLayoutParams?.also { DoKitViewManager.INSTANCE.saveDokitViewPos(tag, it.x, it.y) }\n        }\n    }\n\n    private inline fun makeAnimator(from: Int, size: Int, containerSize: Int, setup: ValueAnimator.() -> Unit) {\n        if (size <= 0 || containerSize <= 0) return\n        ValueAnimator.ofInt(from, if (from <= (containerSize - size) / 2) 0 else (containerSize - size))\n            .apply {\n                duration = 150L\n                setup()\n            }\n            .start()\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/AbsDoKitViewManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\nimport com.didichuxing.doraemonkit.constant.DoKitModule\nimport com.didichuxing.doraemonkit.kit.health.CountDownDoKitView\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-28-15:18\n * 描    述：页面浮标管理类接口\n * 修订历史：\n * ================================================\n */\nabstract class AbsDoKitViewManager : DoKitViewManagerInterface {\n    protected var TAG = this.javaClass.simpleName\n\n    /**\n     * 添加倒计时DokitView\n     */\n    fun attachCountDownDoKitView(activity: Activity) {\n        if (!DoKitManager.APP_HEALTH_RUNNING) {\n            return\n        }\n        if (activity is UniversalActivity) {\n            return\n        }\n        val dokitIntent = DoKitIntent(CountDownDoKitView::class.java)\n        dokitIntent.mode = DoKitViewLaunchMode.COUNTDOWN\n        attach(dokitIntent)\n    }\n\n    /**\n     * 添加一机多控标识\n     *\n     * @param activity\n     */\n    fun attachMcRecodingDoKitView(activity: Activity) {\n        val action: Map<String, String> = mapOf(\"action\" to \"launch_recoding_view\")\n        DoKitManager.getModuleProcessor(DoKitModule.MODULE_MC)?.proceed(action)\n    }\n\n    /**\n     * 添加主icon\n     */\n    abstract fun attachMainIcon(activity: Activity?)\n\n    /**\n     * 移除主icon\n     */\n    abstract fun detachMainIcon()\n\n    /**\n     * 添加toolPanel\n     */\n    abstract fun attachToolPanel(activity: Activity?)\n\n    /**\n     * 移除toolPanel\n     */\n    abstract fun detachToolPanel()\n\n    /**\n     * main activity 创建时回调\n     *\n     * @param activity\n     */\n    abstract fun onMainActivityResume(activity: Activity?)\n\n    /**\n     * Activity 创建时回调\n     *\n     * @param activity\n     */\n    abstract fun onActivityResume(activity: Activity?)\n\n    /**\n     * Activity 页面回退的时候回调\n     *\n     * @param activity\n     */\n    abstract fun onActivityBackResume(activity: Activity?)\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/ActivityLifecycleStatusInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-31-11:37\n * 描    述：\n * 修订历史：\n * ================================================\n */\n\ndata class ActivityLifecycleStatusInfo(\n    var isInvokeStopMethod: Boolean? = false,\n    var lifeCycleStatus: DoKitLifeCycleStatus? = DoKitLifeCycleStatus.CREATED,\n    var activityName: String? = \"\"\n)\n\n\nenum class DoKitLifeCycleStatus {\n    /**\n     * Activity 创建\n     */\n    CREATED,\n\n    /**\n     * Activity resume\n     */\n    RESUME,\n\n    /**\n     * Activity stop\n     */\n    STOPPED,\n\n    /**\n     * Activity destroy\n     */\n    DESTROYED\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/BaseActivity.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.R\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport java.util.*\n\n/**\n * Created by wanglikun on 2018/10/26.\n */\nabstract class BaseActivity : AppCompatActivity() {\n    private val mFragments = ArrayDeque<BaseFragment>()\n\n    @JvmOverloads\n    fun showContent(target: Class<out BaseFragment>, bundle: Bundle? = null) {\n        try {\n\n            val fragment = target.newInstance()\n            if (bundle != null) {\n                fragment.arguments = bundle\n            }\n            val fm = supportFragmentManager\n            val fragmentTransaction = fm.beginTransaction()\n            fragmentTransaction.add(R.id.content, fragment)\n            mFragments.push(fragment)\n            fragmentTransaction.addToBackStack(\"\")\n            fragmentTransaction.commit()\n        } catch (e: InstantiationException) {\n            e.printStackTrace()\n        } catch (e: IllegalAccessException) {\n            e.printStackTrace()\n        }\n    }\n\n    override fun onBackPressed() {\n        if (!mFragments.isEmpty()) {\n            val fragment = mFragments.first\n            if (!fragment.onBackPressed()) {\n                mFragments.removeFirst()\n                super.onBackPressed()\n                if (mFragments.isEmpty()) {\n                    finish()\n                }\n            }\n        } else {\n            super.onBackPressed()\n        }\n    }\n\n    fun doBack(fragment: BaseFragment) {\n        if (mFragments.contains(fragment)) {\n            mFragments.remove(fragment)\n            val fm = supportFragmentManager\n            fm.popBackStack()\n            if (mFragments.isEmpty()) {\n                finish()\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/BaseFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.IdRes\nimport androidx.annotation.LayoutRes\nimport androidx.annotation.StringRes\nimport androidx.fragment.app.Fragment\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.extension.tagName\nimport com.didichuxing.doraemonkit.kit.main.MainIconDoKitView\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.didichuxing.doraemonkit.widget.dialog.CommonDialogProvider\nimport com.didichuxing.doraemonkit.widget.dialog.DialogInfo\nimport com.didichuxing.doraemonkit.widget.dialog.DialogProvider\nimport com.didichuxing.doraemonkit.widget.dialog.UniversalDialogFragment\n\n\n/**\n * @author wanglikun\n * @date 2018/10/26\n */\nabstract class BaseFragment : Fragment() {\n    @JvmField\n    val TAG = this.javaClass.simpleName\n\n    /**\n     * @return 资源文件\n     */\n    @LayoutRes\n    protected abstract fun onRequestLayout(): Int\n\n\n    fun <T : View> findViewById(@IdRes id: Int): T {\n        return requireView().findViewById(id)\n    }\n\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        val id = onRequestLayout()\n        var rootView: View? = null\n        if (id > 0) {\n            rootView = inflater.inflate(id, container, false)\n        }\n        if (interceptTouchEvents() && rootView != null) {\n            rootView.setOnTouchListener { _, _ -> true }\n        }\n        return rootView\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        try {\n            if (view.context is Activity) {\n                (view.context as Activity).window.decorView.requestLayout()\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n        DoKit.removeFloating(MainIconDoKitView::class)\n    }\n\n\n    protected fun interceptTouchEvents(): Boolean {\n        return false\n    }\n\n\n    open fun onBackPressed(): Boolean {\n        return false\n    }\n\n\n    @JvmOverloads\n    fun showContent(fragmentClass: Class<out BaseFragment>, bundle: Bundle? = null) {\n        val activity = activity as BaseActivity?\n        activity?.showContent(fragmentClass, bundle)\n    }\n\n    fun finish() {\n        if (activity is BaseActivity) {\n            val activity = activity as BaseActivity?\n            activity?.doBack(this)\n        }\n\n        if (activity is NewBaseActivity) {\n            val activity = activity as NewBaseActivity?\n            activity?.doBack(this)\n        }\n    }\n\n\n    fun showDialog(dialogInfo: DialogInfo): DialogProvider<*> {\n        val provider = CommonDialogProvider(dialogInfo, dialogInfo.listener)\n        showDialog(provider)\n        return provider\n    }\n\n    open fun showDialog(provider: DialogProvider<*>) {\n        val dialog = UniversalDialogFragment()\n        provider.host = dialog\n        dialog.setProvider(provider)\n        provider.show(childFragmentManager)\n    }\n\n    fun dismissDialog(provider: DialogProvider<*>) {\n        provider.dismiss()\n    }\n\n    fun showToast(msg: String) {\n        ToastUtils.showShort(msg)\n    }\n\n    fun showToast(@StringRes res: Int) {\n        ToastUtils.showShort(res)\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/DoKitFrameLayout.java",
    "content": "package com.didichuxing.doraemonkit.kit.core;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.FrameLayout;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-10-08-17:10\n * 描    述：自定义FrameLayout 用来区分原生FrameLayout\n * 修订历史：\n * ================================================\n */\npublic class DoKitFrameLayout extends FrameLayout implements DoKitViewInterface {\n    public static final int DoKitFrameLayoutFlag_ROOT = 100;\n    public static final int DoKitFrameLayoutFlag_CHILD = 200;\n\n    private int mFlag = DoKitFrameLayoutFlag_ROOT;\n    private String mTitle;\n\n    public DoKitFrameLayout(@NonNull Context context, int flag) {\n        super(context);\n        this.mFlag = flag;\n    }\n\n    public DoKitFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public DoKitFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n    public String getTitle() {\n        return mTitle;\n    }\n\n    public void setTitle(String title) {\n        this.mTitle = title;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/DoKitIntent.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\nimport android.os.Bundle\nimport com.didichuxing.doraemonkit.extension.tagName\nimport com.didichuxing.doraemonkit.util.ActivityUtils\nimport kotlin.reflect.KClass\n\n/**\n * Created by jintai on 2019/9/16.\n * dokitView intent\n */\ndata class DoKitIntent(\n    var targetClass: Class<out AbsDoKitView>,\n    var activity: Activity = ActivityUtils.getTopActivity(),\n    var bundle: Bundle? = null,\n    var tag: String = targetClass.tagName,\n    var mode: DoKitViewLaunchMode = DoKitViewLaunchMode.SINGLE_INSTANCE\n)\n\n\nenum class DoKitViewLaunchMode {\n    SINGLE_INSTANCE,\n    /**\n     * 倒计时\n     */\n    COUNTDOWN\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/DoKitKeyEvent.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/8/25-18:47\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class DoKitKeyEvent(\n    val action: Int,\n    val keyCode: Int\n)\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/DoKitLifecycleInterface.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/9-17:00\n * 描    述：\n * 修订历史：\n * ================================================\n */\ninterface DoKitLifecycleInterface {\n    /**\n     * 生命周期\n     */\n    fun onCreate(activity: Activity){}\n    fun onStart(activity: Activity){}\n    fun onResume(activity: Activity){}\n    fun onPause(activity: Activity){}\n    fun onStop(activity: Activity){}\n    fun onDestroy(activity: Activity){}\n    fun finish(activity: Activity){}\n\n    /**\n     * 页面事件\n     */\n    fun onConfigurationChanged(activity: Activity){}\n    fun onBackPressed(activity: Activity){}\n    fun dispatchTouchEvent(activity: Activity){}\n    fun other(activity: Activity){}\n\n    /**\n     * app 切换到前台\n     */\n    fun onForeground(activity: Activity) {}\n\n    /**\n     * app 切换到后台\n     */\n    fun onBackground(activity: Activity) {}\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/DoKitManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport com.didichuxing.doraemonkit.BuildConfig\nimport com.didichuxing.doraemonkit.DoKitCallBack\nimport com.didichuxing.doraemonkit.config.GlobalConfig\nimport com.didichuxing.doraemonkit.constant.DoKitModule\nimport com.didichuxing.doraemonkit.kit.network.bean.WhiteHostBean\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager\nimport com.didichuxing.doraemonkit.kit.toolpanel.KitWrapItem\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport com.didichuxing.doraemonkit.util.NetworkUtils\nimport com.didichuxing.doraemonkit.util.PathUtils\nimport java.io.File\nimport java.util.*\nimport kotlin.collections.LinkedHashMap\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-19-10:21\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject DoKitManager {\n    const val TAG = \"DoKitConstant\"\n    const val GROUP_ID_PLATFORM = \"dk_category_platform\"\n    const val GROUP_ID_COMM = \"dk_category_comms\"\n    const val GROUP_ID_WEEX = \"dk_category_weex\"\n    const val GROUP_ID_PERFORMANCE = \"dk_category_performance\"\n    const val GROUP_ID_UI = \"dk_category_ui\"\n    const val GROUP_ID_LBS = \"dk_category_lbs\"\n\n\n    /**\n     * DoKit 模块能力\n     */\n    private val mDokitModuleAbilityMap: MutableMap<DoKitModule, DokitAbility.DokitModuleProcessor> by lazy {\n        val doKitAbilities =\n            ServiceLoader.load(DokitAbility::class.java, javaClass.classLoader).toList()\n        val abilityMap = mutableMapOf<DoKitModule, DokitAbility.DokitModuleProcessor>()\n        doKitAbilities.forEach {\n            it.init()\n            abilityMap[it.moduleName()] = it.getModuleProcessor()\n        }\n        abilityMap\n    }\n\n\n    /**\n     * 获取ModuleProcessor\n     */\n    fun getModuleProcessor(module: DoKitModule): DokitAbility.DokitModuleProcessor? {\n        if (mDokitModuleAbilityMap[module] == null) {\n            return null\n        }\n        return mDokitModuleAbilityMap[module]\n    }\n\n\n    val SYSTEM_KITS_BAK_PATH: String by lazy {\n        \"${PathUtils.getInternalAppFilesPath()}${File.separator}system_kit_bak_${BuildConfig.DOKIT_VERSION}.json\"\n    }\n\n    /**\n     * 工具面板RV上次的位置\n     */\n    var TOOL_PANEL_RV_LAST_DY = 0\n\n\n    /**\n     * 全局的Kits\n     */\n    @JvmField\n    val GLOBAL_KITS: LinkedHashMap<String, MutableList<KitWrapItem>> = LinkedHashMap()\n\n    /**\n     * 全局系统内置kit\n     */\n    @JvmField\n    val GLOBAL_SYSTEM_KITS: LinkedHashMap<String, MutableList<KitWrapItem>> = LinkedHashMap()\n\n    /**\n     * 加密数据库账号密码配置\n     */\n    var DATABASE_PASS = mapOf<String, String>()\n\n    /**\n     * 平台端文件管理端口号\n     */\n    var FILE_MANAGER_HTTP_PORT = 8089\n\n    /**\n     * 一机多控长连接端口号\n     */\n    var MC_WS_PORT = 4444\n\n    /**\n     * 产品id\n     */\n    @JvmField\n    var PRODUCT_ID = \"\"\n\n    /**\n     * 是否处于健康体检中\n     */\n    @JvmField\n    var APP_HEALTH_RUNNING = GlobalConfig.getAppHealth()\n\n    /**\n     * 是否是普通的浮标模式\n     */\n    @JvmField\n    var IS_NORMAL_FLOAT_MODE = true\n\n    /**\n     * 是否显示icon主入口\n     */\n    @JvmField\n    var ALWAYS_SHOW_MAIN_ICON = true\n\n    /**\n     * icon主入口是否处于显示状态\n     */\n    @JvmField\n    var MAIN_ICON_HAS_SHOW = false\n\n    /**\n     * 流量监控白名单\n     */\n    @JvmField\n    var WHITE_HOSTS = mutableListOf<WhiteHostBean>()\n\n    /**\n     * h5 js 注入代码开关\n     */\n    @JvmField\n    var H5_JS_INJECT = false\n\n\n    /**\n     * h5 vConsole 注入代码开关\n     */\n    @JvmField\n    var H5_VCONSOLE_INJECT = false\n\n    /**\n     * h5 dokit for web 注入代码开关\n     */\n    @JvmField\n    var H5_DOKIT_MC_INJECT = false\n\n    @JvmField\n    var H5_MC_JS_INJECT_MODE = \"file\"\n\n    @JvmField\n    var H5_MC_JS_INJECT_URL = \"http://120.55.183.20/dokit/mc/dokit.js\"\n\n    /**\n     * 是否允许上传统计信息\n     */\n    var ENABLE_UPLOAD = true\n    val ACTIVITY_LIFECYCLE_INFOS: MutableMap<String, ActivityLifecycleStatusInfo?> by lazy {\n        mutableMapOf<String, ActivityLifecycleStatusInfo?>()\n    }\n\n    /**\n     * 一机多控从机自定义处理器\n     */\n    var MC_CLIENT_PROCESSOR: McClientProcessor? = null\n\n    /**\n     * 全局回调\n     */\n    var CALLBACK: DoKitCallBack? = null\n\n    /**\n     *  一机多控地址\n     */\n    @JvmField\n    var MC_CONNECT_URL: String = \"\"\n\n\n    /**\n     * Wifi IP 地址\n     */\n    val IP_ADDRESS_BY_WIFI: String\n        get() {\n            return try {\n                NetworkUtils.getIpAddressByWifi()\n            } catch (e: Exception) {\n                LogHelper.e(TAG, \"get wifi address error===>${e.message}\")\n                \"0.0.0.0\"\n            }\n        }\n\n    /**\n     * 判断接入的是否是滴滴内部的rpc sdk\n     *\n     * @return\n     */\n    @JvmStatic\n    val isRpcSDK: Boolean\n        get() {\n            return try {\n                Class.forName(\"com.didichuxing.doraemonkit.DoraemonKitRpc\")\n                true\n            } catch (e: ClassNotFoundException) {\n                false\n            }\n        }\n\n    /**\n     * 兼容滴滴内部外网映射环境  该环境的 path上会多一级/kop_xxx/路径\n     *\n     * @param oldPath\n     * @param fromSDK\n     * @return\n     */\n    @JvmStatic\n    fun dealDidiPlatformPath(oldPath: String, fromSDK: Int): String {\n        if (fromSDK == DokitDbManager.FROM_SDK_OTHER) {\n            return oldPath\n        }\n        var newPath = oldPath\n        //包含多级路径\n        if (oldPath.contains(\"/kop\") && oldPath.split(\"\\\\/\").toTypedArray().size > 1) {\n            //比如/kop_stable/a/b/gateway 分解以后为 \"\" \"kop_stable\" \"a\" \"b\" \"gateway\"\n            val childPaths = oldPath.split(\"\\\\/\").toTypedArray()\n            val firstPath = childPaths[1]\n            if (firstPath.contains(\"kop\")) {\n                newPath = oldPath.replace(\"/$firstPath\", \"\")\n            }\n        }\n        return newPath\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/DoKitServiceEnum.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/9-17:00\n * 描    述：\n * 修订历史：\n * ================================================\n */\nenum class DoKitServiceEnum {\n    /**\n     * 页面生命周期\n     */\n    onCreate,\n    onStart,\n    onResume,\n    onPause,\n    onStop,\n    onDestroy,\n    finish,\n\n    /**\n     * 页面事件\n     */\n    onConfigurationChanged,\n    onBackPressed,\n    dispatchTouchEvent,\n    other,\n\n    /**\n     * app 切换到前台\n     */\n    onForeground,\n\n    /**\n     * app 切换到后台\n     */\n    onBackground\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/DoKitServiceManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\nimport com.didichuxing.doraemonkit.constant.DoKitModule\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/9-17:00\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject DoKitServiceManager {\n    var lifecycle: DoKitLifecycleInterface? = null\n\n    fun dispatch(activityOverrideEnum: DoKitServiceEnum, activity: Activity) {\n        if (lifecycle == null) {\n            val life = DoKitManager.getModuleProcessor(DoKitModule.MODULE_MC)?.values()\n                ?.get(\"lifecycle\")\n            if (life != null) {\n                lifecycle = life as DoKitLifecycleInterface\n            }\n        }\n\n        when (activityOverrideEnum) {\n            DoKitServiceEnum.onCreate -> lifecycle?.onCreate(activity)\n            DoKitServiceEnum.onStart -> lifecycle?.onStart(activity)\n            DoKitServiceEnum.onResume -> lifecycle?.onResume(activity)\n            DoKitServiceEnum.onPause -> lifecycle?.onPause(activity)\n            DoKitServiceEnum.onStop -> lifecycle?.onStop(activity)\n            DoKitServiceEnum.onDestroy -> lifecycle?.onDestroy(activity)\n            DoKitServiceEnum.finish -> lifecycle?.finish(activity)\n            DoKitServiceEnum.onConfigurationChanged -> lifecycle?.onConfigurationChanged(\n                activity\n            )\n            DoKitServiceEnum.onBackPressed -> lifecycle?.onBackPressed(activity)\n            DoKitServiceEnum.dispatchTouchEvent -> lifecycle?.dispatchTouchEvent(activity)\n            DoKitServiceEnum.onBackground -> lifecycle?.onBackground(activity)\n            DoKitServiceEnum.onForeground -> lifecycle?.onForeground(activity)\n            else -> lifecycle?.other(activity)\n\n        }\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/DoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.core;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.widget.FrameLayout;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-20-16:22\n * 描    述：dokit 页面浮标接口\n * 修订历史：\n * ================================================\n */\ninterface DoKitView {\n\n\n    /**\n     * dokit view 创建时调用 做一些变量的初始化  当还不能进行View的操作\n     *\n     * @param context\n     */\n    void onCreate(Context context);\n\n    /**\n     * 传入rootView 用于创建kit控件\n     *\n     * @param context\n     * @param rootView\n     * @return 返回创建的childView\n     */\n    View onCreateView(Context context, FrameLayout rootView);\n\n\n    /**\n     * 将xml中的控件添加到rootView以后调用，在当前方法中可以进行view的一些操作\n     *\n     * @param rootView\n     */\n    void onViewCreated(FrameLayout rootView);\n\n    /**\n     * 当前的dokitView添加到根布局里时调用\n     */\n    void onResume();\n\n    /**\n     * 当前activity onPause时调用\n     */\n    void onPause();\n\n    /**\n     * 确定浮标的初始位置\n     * LayoutParams创建完以后调用\n     * 调用时建议放在实现下方\n     *\n     * @param layoutParams\n     */\n    //void onNormalLayoutParamsCreated(FrameLayout.LayoutParams layoutParams);\n\n\n    /**\n     * 确定系统悬浮窗浮标的初始位置\n     * LayoutParams创建完以后调用\n     *\n     * @param layoutParams\n     */\n    //void onSystemLayoutParamsCreated(WindowManager.LayoutParams layoutParams);\n\n    /**\n     * 确定系统悬浮窗浮标的初始位置\n     * LayoutParams创建完以后调用\n     *\n     * @param params\n     */\n\n    void initDokitViewLayoutParams(DoKitViewLayoutParams params);\n\n    /**\n     * app进入后台时调用 内置dokitView 不需要实现\n     */\n    void onEnterBackground();\n\n    /**\n     * app回到前台时调用 内置dokitview 不需要实现\n     */\n    void onEnterForeground();\n\n    /**\n     * 浮标控件是否可以拖动\n     *\n     * @return\n     */\n    boolean canDrag();\n\n    /**\n     * 是否需要自己处理返回键\n     *\n     * @return\n     */\n    boolean shouldDealBackKey();\n\n    /**\n     * shuldDealBackKey == true 时调用\n     */\n    boolean onBackPressed();\n\n    /**\n     * 悬浮窗主动销毁时调用 不能在当前生命周期回调函数中调用 detach自己 否则会出现死循环\n     */\n    void onDestroy();\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/DoKitViewInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.graphics.Point\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/11/4-16:28\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class DoKitViewInfo(var orientation: Int, var portraitPoint: Point, var landscapePoint: Point)"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/DoKitViewInterface.java",
    "content": "package com.didichuxing.doraemonkit.kit.core;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-10-08-17:11\n * 描    述：空实现  主要是为了跟原生的view做区分\n * 修订历史：\n * ================================================\n */\npublic interface DoKitViewInterface {\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/DoKitViewLayoutParams.java",
    "content": "package com.didichuxing.doraemonkit.kit.core;\n\nimport android.view.ViewGroup;\nimport android.view.WindowManager;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-24-10:57\n * 描    述：dokitView的初始化位置\n * 修订历史：\n * ================================================\n */\npublic class DoKitViewLayoutParams {\n    /**\n     * 悬浮窗不能获取焦点\n     */\n    public static int FLAG_NOT_FOCUSABLE = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;\n    public static int FLAG_NOT_TOUCHABLE = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;\n    /**\n     * wiki:https://blog.csdn.net/hnlgzb/article/details/108520716\n     * 是否允许超出屏幕\n     */\n    public static int FLAG_LAYOUT_NO_LIMITS = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;\n    /**\n     * 悬浮窗不能获取焦点并且不相应触摸\n     */\n    public static int FLAG_NOT_FOCUSABLE_AND_NOT_TOUCHABLE = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;\n\n    public static int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT;\n    public static int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;\n\n\n    /**\n     * 只针对系统悬浮窗起作用 值基本上为以上2个\n     */\n    public int flags;\n    /**\n     * 只针对系统悬浮窗起作用 值基本上为Gravity\n     */\n    public int gravity;\n    public int x;\n    public int y;\n    public int width;\n    public int height;\n\n    @Override\n    public String toString() {\n        return \"DokitViewLayoutParams{\" +\n                \"flags=\" + flags +\n                \", gravity=\" + gravity +\n                \", x=\" + x +\n                \", y=\" + y +\n                \", width=\" + width +\n                \", height=\" + height +\n                '}';\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/DoKitViewManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content.res.Configuration\nimport android.graphics.Point\nimport android.view.WindowManager\nimport androidx.collection.ArrayMap\nimport androidx.room.Room\nimport com.didichuxing.doraemonkit.DoKitEnv\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDatabase\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager\nimport com.didichuxing.doraemonkit.util.ScreenUtils\n\n/**\n * Created by jintai on 2018/10/23.\n * 浮标管理类\n */\ninternal class DoKitViewManager : DoKitViewManagerInterface {\n\n    companion object {\n        private const val TAG = \"DoKitViewManager\"\n\n        @JvmStatic\n        val INSTANCE: DoKitViewManager by lazy { DoKitViewManager() }\n\n        /**\n         * 每个类型在页面中的位置 只保存marginLeft 和marginTop\n         */\n        private val doKitViewPos: MutableMap<String, DoKitViewInfo> = ArrayMap<String, DoKitViewInfo>()\n    }\n\n    /**\n     * Retrieves app [WindowManager]\n     *\n     * @return WindowManager\n     */\n    val windowManager: WindowManager\n        get() = DoKitEnv.requireApp().getSystemService(Context.WINDOW_SERVICE) as WindowManager\n\n    private val lastDoKitViewPosInfoMaps: MutableMap<String, LastDoKitViewPosInfo> = ArrayMap<String, LastDoKitViewPosInfo>()\n\n    private var _doKitViewManager: AbsDoKitViewManager? = null\n\n    //下面注释表示允许主线程进行数据库操作，但是不推荐这样做。\n    //他可能造成主线程lock以及anr\n    //所以我们的操作都是在新线程完成的\n    val db: DokitDatabase by lazy {\n        Room.databaseBuilder(\n            DoKitEnv.requireApp(),\n            DokitDatabase::class.java,\n            \"dokit-database\"\n        ) //下面注释表示允许主线程进行数据库操作，但是不推荐这样做。\n            //他可能造成主线程lock以及anr\n            //所以我们的操作都是在新线程完成的\n            .allowMainThreadQueries()\n            .build()\n    }\n\n    fun init() {\n        //获取所有的intercept apis\n        DokitDbManager.getInstance().getAllInterceptApis()\n\n        //获取所有的template apis\n        DokitDbManager.getInstance().getAllTemplateApis()\n    }\n\n    /**\n     * 当app进入后台时调用\n     */\n    override fun notifyBackground() {\n        ensureViewManager().notifyBackground()\n    }\n\n    /**\n     * 当app进入前台时调用\n     */\n    override fun notifyForeground() {\n        ensureViewManager().notifyForeground()\n    }\n\n    /**\n     * 只有普通浮标才会调用\n     * 保存每种类型dokitView的位置\n     */\n    fun saveDokitViewPos(tag: String, marginLeft: Int, marginTop: Int) {\n\n        var orientation = -1\n        val portraitPoint = Point()\n        val landscapePoint = Point()\n        if (ScreenUtils.isPortrait()) {\n            orientation = Configuration.ORIENTATION_PORTRAIT\n            portraitPoint.x = marginLeft\n            portraitPoint.y = marginTop\n        } else {\n            orientation = Configuration.ORIENTATION_LANDSCAPE\n            landscapePoint.x = marginLeft\n            landscapePoint.y = marginTop\n        }\n        if (doKitViewPos[tag] == null) {\n            val doKitViewInfo = DoKitViewInfo(orientation, portraitPoint, landscapePoint)\n            doKitViewPos[tag] =\n                doKitViewInfo\n        } else {\n            val doKitViewInfo = doKitViewPos[tag]\n            if (doKitViewInfo != null) {\n                doKitViewInfo.orientation = orientation\n                doKitViewInfo.portraitPoint = portraitPoint\n                doKitViewInfo.landscapePoint = landscapePoint\n            }\n        }\n    }\n\n    /**\n     * 只有普通的浮标才需要调用\n     * 获得指定dokitView的位置信息\n     *\n     * @param tag\n     * @return\n     */\n    fun getDoKitViewPos(tag: String): DoKitViewInfo? = doKitViewPos[tag]\n\n    /**\n     * 只有普通的浮标才需要调用\n     * 添加activity关联的所有dokitView activity resume的时候回调\n     *\n     * @param activity\n     */\n    override fun dispatchOnActivityResumed(activity: Activity?) {\n        activity?.also { ensureViewManager().dispatchOnActivityResumed(it) }\n    }\n\n    override fun onActivityPaused(activity: Activity?) {\n        activity?.also { ensureViewManager().onActivityPaused(it) }\n    }\n\n    override fun onActivityStopped(activity: Activity?) {\n        activity?.also { ensureViewManager().onActivityStopped(it) }\n    }\n\n    /**\n     * 在当前Activity中添加指定悬浮窗\n     *\n     * @param doKitIntent\n     */\n    override fun attach(doKitIntent: DoKitIntent) {\n        ensureViewManager().attach(doKitIntent)\n    }\n\n    /**\n     * 隐藏工具列表dokitView\n     */\n    fun detachToolPanel() {\n        ensureViewManager().detachToolPanel()\n    }\n\n    /**\n     * 显示工具列表dokitView\n     */\n    fun attachToolPanel(activity: Activity) {\n        ensureViewManager().attachToolPanel(activity)\n    }\n\n    /**\n     * 显示主图标 dokitView\n     */\n    fun attachMainIcon(activity: Activity) {\n        ensureViewManager().attachMainIcon(activity)\n    }\n\n    /**\n     * 隐藏首页图标\n     */\n    fun detachMainIcon() {\n        ensureViewManager().detachMainIcon()\n    }\n\n    /**\n     * 移除每个activity指定的dokitView\n     */\n    override fun detach(tag: String) {\n        ensureViewManager().detach(tag)\n    }\n\n    /**\n     * 移除每个activity指定的dokitView\n     */\n    override fun detach(doKitView: AbsDoKitView) {\n        ensureViewManager().detach(doKitView)\n    }\n\n    override fun detach(doKitViewClass: Class<out AbsDoKitView>) {\n        ensureViewManager().detach(doKitViewClass)\n    }\n\n    /**\n     * 移除所有activity的所有dokitView\n     */\n    override fun detachAll() {\n        ensureViewManager().detachAll()\n    }\n\n    /**\n     * 获取页面上指定的dokitView\n     *\n     * @param activity 如果是系统浮标 activity可以为null\n     * @param tag\n     * @return\n     */\n    override fun <T : AbsDoKitView> getDoKitView(activity: Activity?, clazz: Class<T>): AbsDoKitView? {\n        return activity?.let { ensureViewManager().getDoKitView(it, clazz) }\n    }\n\n    /**\n     * Activity销毁时调用\n     */\n    override fun onActivityDestroyed(activity: Activity?) {\n        activity?.also { ensureViewManager().onActivityDestroyed(it) }\n    }\n\n    /**\n     * @param activity\n     * @return\n     */\n    override fun getDoKitViews(activity: Activity?): Map<String, AbsDoKitView>? {\n        return activity?.let { ensureViewManager().getDoKitViews(it) }\n    }\n\n    /**\n     * 系统悬浮窗需要调用\n     */\n    interface DokitViewAttachedListener {\n        fun onDokitViewAdd(dokitView: AbsDoKitView?)\n    }\n\n    /**\n     * 系统悬浮窗需要调用\n     *\n     * @param listener\n     */\n    fun addDokitViewAttachedListener(listener: DokitViewAttachedListener?) {\n        listener?.takeIf { !DoKitManager.IS_NORMAL_FLOAT_MODE && _doKitViewManager is SystemDoKitViewManager }\n            ?.also {\n                (_doKitViewManager as? SystemDoKitViewManager)?.addListener(it)\n            }\n    }\n\n    /**\n     * 系统悬浮窗需要调用\n     *\n     * @param listener\n     */\n    fun removeDokitViewAttachedListener(listener: DokitViewAttachedListener?) {\n        listener?.takeIf { !DoKitManager.IS_NORMAL_FLOAT_MODE && _doKitViewManager is SystemDoKitViewManager }\n            ?.also {\n                (_doKitViewManager as SystemDoKitViewManager).removeListener(it)\n            }\n    }\n\n    fun saveLastDokitViewPosInfo(key: String, lastDoKitViewPosInfo: LastDoKitViewPosInfo) {\n        lastDoKitViewPosInfoMaps[key] = lastDoKitViewPosInfo\n    }\n\n    fun getLastDokitViewPosInfo(key: String): LastDoKitViewPosInfo? = lastDoKitViewPosInfoMaps[key]\n\n    fun removeLastDokitViewPosInfo(key: String) {\n        lastDoKitViewPosInfoMaps.remove(key)\n    }\n\n    @Synchronized\n    private fun ensureViewManager(): AbsDoKitViewManager {\n        return _doKitViewManager\n            ?: run {\n                if (DoKitManager.IS_NORMAL_FLOAT_MODE) NormalDoKitViewManager() else SystemDoKitViewManager()\n            }.also {\n                _doKitViewManager = it\n            }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/DoKitViewManagerInterface.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-28-15:18\n * 描    述：页面浮标管理类接口\n * 修订历史：\n * ================================================\n */\ninterface DoKitViewManagerInterface {\n    /**\n     * 在当前Activity中添加指定悬浮窗\n     *\n     * @param doKitIntent\n     */\n    fun attach(doKitIntent: DoKitIntent)\n\n    /**\n     * 移除每个activity指定的dokitView\n     *\n     * @param doKitView\n     */\n    fun detach(doKitView: AbsDoKitView)\n\n    /**\n     * 移除每个activity指定的dokitView tag\n     *\n     * @param tag 一般为dokitView的className\n     */\n    fun detach(tag: String)\n\n    fun detach(doKitViewClass: Class<out AbsDoKitView>)\n\n    /**\n     * 移除所有activity的所有dokitView\n     */\n    fun detachAll()\n\n    /**\n     * 获取页面上指定的dokitView\n     *\n     * @param activity\n     * @param tag\n     * @return\n     */\n    fun <T : AbsDoKitView> getDoKitView(activity: Activity?, clazz: Class<T>): AbsDoKitView?\n\n    /**\n     * 获取页面上所有的dokitView\n     *\n     * @param activity\n     * @return\n     */\n    fun getDoKitViews(activity: Activity?): Map<String, AbsDoKitView>?\n\n    /**\n     * 当app进入后台时调用\n     */\n    fun notifyBackground()\n\n    /**\n     * 当app进入前台时调用\n     */\n    fun notifyForeground()\n\n    /**\n     * Activity销毁时调用\n     *\n     * @param activity\n     */\n    fun onActivityDestroyed(activity: Activity?)\n\n    /**\n     * 页面onPause时调用\n     *\n     * @param activity\n     */\n    fun onActivityPaused(activity: Activity?)\n\n    /**\n     * 页面onStop时调用\n     */\n    fun onActivityStopped(activity: Activity?)\n\n    /**\n     * Called on [Activity] resumed from activity lifecycle callbacks\n     *\n     * @param activity resumed activity\n     */\n    fun dispatchOnActivityResumed(activity: Activity?)\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/DokitAbility.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport com.didichuxing.doraemonkit.constant.DoKitModule\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/7-19:42\n * 描    述：具体的某块具有哪些能力\n * 修订历史：\n * ================================================\n */\ninterface DokitAbility {\n\n\n    fun init()\n\n    /**\n     * 模块名\n     */\n    fun moduleName(): DoKitModule\n\n    /**\n     *注册模块能力处理器\n     */\n    fun getModuleProcessor(): DokitModuleProcessor\n\n\n    interface DokitModuleProcessor {\n        val TAG: String\n            get() = this.javaClass.simpleName\n\n        fun values(): Map<String, Any?>\n\n        fun proceed(actions: Map<String, Any?>? = null): Map<String, Any>\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/GlobalSingleDoKitViewInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.os.Bundle\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-29-17:39\n * 描    述：关于全局dokitView的基本信息 由于普通的浮标是每个页面自己管理的\n * 需要有一个map用来保存当前每个类型的dokitview 便于新开页面和页面resume时的dokitview添加\n * 修订历史：\n * ================================================\n */\ndata class GlobalSingleDoKitViewInfo(\n    val absDoKitViewClass: Class<out AbsDoKitView?>,\n    val tag: String,\n    val mode: DoKitViewLaunchMode,\n    val bundle: Bundle?\n)\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/LastDoKitViewPosInfo.java",
    "content": "package com.didichuxing.doraemonkit.kit.core;\n\nimport com.didichuxing.doraemonkit.util.ScreenUtils;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-10-29-10:25\n * 描    述：保存上一次DokitView的位置信息\n * 修订历史：\n * ================================================\n */\npublic class LastDoKitViewPosInfo {\n    private boolean isPortrait = true;\n    private int doKitViewWidth;\n    private int doKitViewHeight;\n    private float leftMarginPercent;\n    private float topMarginPercent;\n\n    public int getDoKitViewWidth() {\n        return doKitViewWidth;\n    }\n\n    public void setDoKitViewWidth(int doKitViewWidth) {\n        this.doKitViewWidth = doKitViewWidth;\n    }\n\n    public int getDoKitViewHeight() {\n        return doKitViewHeight;\n    }\n\n    public void setDoKitViewHeight(int doKitViewHeight) {\n        this.doKitViewHeight = doKitViewHeight;\n    }\n\n    public void setPortrait() {\n        this.isPortrait = ScreenUtils.isPortrait();\n    }\n\n    public void setLeftMargin(int leftMargin) {\n        this.leftMarginPercent = (float) leftMargin / (float) ScreenUtils.getAppScreenWidth();\n    }\n\n    public void setTopMargin(int topMargin) {\n        this.topMarginPercent = (float) topMargin / (float) ScreenUtils.getAppScreenHeight();\n    }\n\n    public boolean isPortrait() {\n        return isPortrait;\n    }\n\n    public float getLeftMarginPercent() {\n        return leftMarginPercent;\n    }\n\n    public float getTopMarginPercent() {\n        return topMarginPercent;\n    }\n\n    @Override\n    public String toString() {\n        return \"LastDokitViewPosInfo{\" +\n                \"isPortrait=\" + isPortrait +\n                \", leftMarginPercent=\" + leftMarginPercent +\n                \", topMarginPercent=\" + topMarginPercent +\n                '}';\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/McClientProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\nimport android.view.View\nimport android.view.accessibility.AccessibilityEvent\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2/18/21-17:06\n * 描    述：一机多控从机处理器\n * 修订历史：\n * ================================================\n */\ninterface McClientProcessor {\n\n    /**\n     * 客户端处理特殊的控件手势\n     * @return true :自定义处理成功\n     * @return false :自定义处理失败\n     */\n    fun process(activity: Activity?, view: View?,eventType: String, params: Map<String, String>)\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/NewBaseActivity.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.os.Bundle\nimport androidx.annotation.IdRes\nimport androidx.appcompat.app.AppCompatActivity\nimport java.util.*\n\n/**\n * Created by wanglikun on 2018/10/26.\n */\nabstract class NewBaseActivity : AppCompatActivity() {\n    private val mFragments = ArrayDeque<BaseFragment>()\n    private var contentId = android.R.id.content\n\n    @JvmOverloads\n    fun showContent(target: Class<out BaseFragment>, bundle: Bundle? = null) {\n        try {\n            val fragment = target.newInstance()\n            if (bundle != null) {\n                fragment.arguments = bundle\n            }\n            showContent(android.R.id.content, fragment)\n        } catch (e: InstantiationException) {\n            e.printStackTrace()\n        } catch (e: IllegalAccessException) {\n            e.printStackTrace()\n        }\n    }\n\n    @JvmOverloads\n    fun showContent(@IdRes contentId: Int, fragment: BaseFragment) {\n        this.contentId = contentId\n        try {\n            mFragments.push(fragment)\n            supportFragmentManager.beginTransaction()\n                .replace(contentId, fragment)\n                .commitNowAllowingStateLoss()\n\n        } catch (e: InstantiationException) {\n            e.printStackTrace()\n        } catch (e: IllegalAccessException) {\n            e.printStackTrace()\n        }\n    }\n\n    @JvmOverloads\n    fun replaceContent(@IdRes contentId: Int, fragment: BaseFragment) {\n        this.contentId = contentId\n        mFragments.clear()\n        mFragments.push(fragment)\n        supportFragmentManager.beginTransaction()\n            .replace(contentId, fragment)\n            .commitNowAllowingStateLoss()\n    }\n\n    override fun onBackPressed() {\n        if (!mFragments.isEmpty()) {\n            val old = mFragments.pollFirst()\n            if (!mFragments.isEmpty()) {\n                val fragment = mFragments.peekFirst()\n                supportFragmentManager.beginTransaction()\n                    .replace(contentId, fragment)\n                    .commitNowAllowingStateLoss()\n            } else {\n                finish()\n            }\n        } else {\n            super.onBackPressed()\n        }\n    }\n\n    fun doBack(fragment: BaseFragment) {\n        onBackPressed()\n//        if (mFragments.contains(fragment)) {\n//            mFragments.remove(fragment)\n//            val fm = supportFragmentManager\n//            fm.popBackStack()\n//            if (mFragments.isEmpty()) {\n//                finish()\n//            }\n//        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/NormalDoKitViewManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\nimport android.content.Context\nimport android.text.TextUtils\nimport android.view.Gravity\nimport android.view.KeyEvent\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport androidx.collection.ArrayMap\nimport com.didichuxing.doraemonkit.DoKitEnv\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.extension.tagName\nimport com.didichuxing.doraemonkit.kit.colorpick.ColorPickerDoKitView\nimport com.didichuxing.doraemonkit.kit.colorpick.ColorPickerInfoDoKitView\nimport com.didichuxing.doraemonkit.kit.main.MainIconDoKitView\nimport com.didichuxing.doraemonkit.kit.performance.PerformanceDoKitView\nimport com.didichuxing.doraemonkit.kit.toolpanel.ToolPanelDoKitView\nimport com.didichuxing.doraemonkit.util.*\nimport java.util.*\n\n/**\n * Created by jintai on 2018/10/23.\n * 每个activity悬浮窗管理类\n */\ninternal class NormalDoKitViewManager : AbsDoKitViewManager() {\n    companion object {\n        private const val MC_DELAY = 100\n    }\n\n    private val context: Context get() = DoKitEnv.requireApp()\n\n    /**\n     *\n     * 每个Activity中dokitView的集合 用户手动移除和页面销毁时都需要remove\n     *\n     */\n    private val activityDoKitViewMap: MutableMap<Activity, MutableMap<String, AbsDoKitView>> = ArrayMap<Activity, MutableMap<String, AbsDoKitView>>()\n\n    /**\n     * 只用来记录全局的同步  只有用户手动移除时才会remove\n     */\n    private val globalSingleDoKitViewInfoMap: MutableMap<String, GlobalSingleDoKitViewInfo> = ArrayMap<String, GlobalSingleDoKitViewInfo>()\n\n    /**\n     * 当app进入后台时调用\n     */\n    override fun notifyBackground() {\n        activityDoKitViewMap.forEach { maps ->\n            maps.value.forEach { map ->\n                map.value.onEnterBackground()\n            }\n        }\n    }\n\n    /**\n     * 当app进入前台时调用\n     */\n    override fun notifyForeground() {\n        activityDoKitViewMap.forEach { maps ->\n            maps.value.forEach { map ->\n                map.value.onEnterForeground()\n            }\n        }\n    }\n\n    /**\n     * 添加activity关联的所有dokitView activity resume的时候回调\n     *\n     * @param activity\n     */\n    override fun dispatchOnActivityResumed(activity: Activity?) {\n        if (activity == null) {\n            return\n        }\n\n        //app启动\n        if (DoKitSystemUtil.isOnlyFirstLaunchActivity(activity)) {\n            onMainActivityResume(activity)\n            return\n        }\n        DoKitManager.ACTIVITY_LIFECYCLE_INFOS[activity.javaClass.canonicalName]?.let {\n            //新建Activity\n            if (it.lifeCycleStatus == DoKitLifeCycleStatus.RESUME && it.isInvokeStopMethod == false) {\n                onActivityResume(activity)\n            }\n\n            //activity resume\n            if (it.lifeCycleStatus == DoKitLifeCycleStatus.RESUME && it.isInvokeStopMethod == true) {\n                onActivityBackResume(activity)\n            }\n        }\n    }\n\n    override fun attachMainIcon(activity: Activity?) {\n        if (activity == null) {\n            return\n        }\n\n        //假如不存在全局的icon这需要全局显示主icon\n        if (DoKitManager.ALWAYS_SHOW_MAIN_ICON && activity !is UniversalActivity) {\n            attach(DoKitIntent(MainIconDoKitView::class.java))\n            DoKitManager.MAIN_ICON_HAS_SHOW = true\n        } else {\n            DoKitManager.MAIN_ICON_HAS_SHOW = false\n        }\n    }\n\n    override fun detachMainIcon() {\n        detach(MainIconDoKitView::class.tagName)\n    }\n\n    override fun attachToolPanel(activity: Activity?) {\n        attach(DoKitIntent(ToolPanelDoKitView::class.java))\n    }\n\n    override fun detachToolPanel() {\n        detach(ToolPanelDoKitView::class.tagName)\n    }\n\n    /**\n     * 应用启动\n     */\n    override fun onMainActivityResume(activity: Activity?) {\n        if (activity == null) {\n            return\n        }\n\n        if (activity is UniversalActivity) {\n            return\n        }\n        //主icon\n        attachMainIcon(activity)\n        //倒计时DokitView\n        attachCountDownDoKitView(activity)\n        //启动一机多控悬浮窗\n        attachMcRecodingDoKitView(activity)\n    }\n\n    /**\n     * 新建activity\n     *\n     * @param activity\n     */\n    override fun onActivityResume(activity: Activity?) {\n        if (activity == null) {\n            return\n        }\n\n        //将所有的dokitView添加到新建的Activity中去\n        for (doKitViewInfo: GlobalSingleDoKitViewInfo in globalSingleDoKitViewInfoMap.values) {\n            //如果不是性能kitView 则不显示\n            if (activity is UniversalActivity && doKitViewInfo.absDoKitViewClass != PerformanceDoKitView::class.java) {\n                continue\n            }\n            //是否过滤掉 入口icon\n            if (!DoKitManager.ALWAYS_SHOW_MAIN_ICON && doKitViewInfo.absDoKitViewClass == MainIconDoKitView::class.java) {\n                DoKitManager.MAIN_ICON_HAS_SHOW = false\n                continue\n            }\n\n            if (doKitViewInfo.absDoKitViewClass == MainIconDoKitView::class.java) {\n                DoKitManager.MAIN_ICON_HAS_SHOW = true\n            }\n\n            val dokitIntent = DoKitIntent(doKitViewInfo.absDoKitViewClass)\n            dokitIntent.bundle = doKitViewInfo.bundle\n            dokitIntent.mode = doKitViewInfo.mode\n            dokitIntent.tag = doKitViewInfo.tag\n            attach(dokitIntent)\n        }\n\n        attachMainIcon(activity)\n        //开始之前先移除\n        detachCountDownDoKitView(activity)\n        //倒计时DokitView\n        attachCountDownDoKitView(activity)\n        //启动一机多控悬浮窗\n        attachMcRecodingDoKitView(activity)\n    }\n\n    /**\n     * activity onResume\n     *\n     * @param activity\n     */\n    override fun onActivityBackResume(activity: Activity?) {\n        if (activity == null) {\n            return\n        }\n\n        val existDoKitViews: Map<String, AbsDoKitView>? = activityDoKitViewMap[activity]\n        //更新所有全局DokitView的位置\n        if (globalSingleDoKitViewInfoMap.isNotEmpty()) {\n            for (gDoKitViewInfo in globalSingleDoKitViewInfoMap.values) {\n                //如果不是性能kitView 则需要重新更新位置\n                if (activity is UniversalActivity && gDoKitViewInfo.absDoKitViewClass != PerformanceDoKitView::class.java) {\n                    continue\n                }\n                //取色器特殊处理\n                if (gDoKitViewInfo.absDoKitViewClass == ColorPickerDoKitView::class.java || gDoKitViewInfo.absDoKitViewClass == ColorPickerInfoDoKitView::class.java) {\n                    continue\n                }\n                //是否过滤掉入口icon\n                if (!DoKitManager.ALWAYS_SHOW_MAIN_ICON && gDoKitViewInfo.absDoKitViewClass == MainIconDoKitView::class.java) {\n                    DoKitManager.MAIN_ICON_HAS_SHOW = false\n                    continue\n                }\n                if (gDoKitViewInfo.absDoKitViewClass == MainIconDoKitView::class.java) {\n                    DoKitManager.MAIN_ICON_HAS_SHOW = true\n                }\n\n                //判断resume Activity 中时候存在指定的dokitview\n                var existDoKitView: AbsDoKitView? = null\n                if (existDoKitViews != null && existDoKitViews.isNotEmpty()) {\n                    existDoKitView = existDoKitViews[gDoKitViewInfo.tag]\n                }\n\n                //当前页面已存在dokitview\n                if (existDoKitView?.doKitView != null) {\n                    existDoKitView.doKitView?.visibility = View.VISIBLE\n                    //更新位置\n                    existDoKitView.updateViewLayout(existDoKitView.tag, true)\n                    existDoKitView.onResume()\n                } else {\n                    //添加相应的\n                    val doKitIntent = DoKitIntent(gDoKitViewInfo.absDoKitViewClass)\n                    doKitIntent.mode = gDoKitViewInfo.mode\n                    doKitIntent.bundle = gDoKitViewInfo.bundle\n                    doKitIntent.tag = gDoKitViewInfo.tag\n                    attach(doKitIntent)\n                }\n            }\n        }\n        //假如不存在全局的icon这需要全局显示主icon\n        attachMainIcon(activity)\n        //开始之前先移除\n        detachCountDownDoKitView(activity)\n        attachCountDownDoKitView(activity)\n        //启动一机多控悬浮窗\n        attachMcRecodingDoKitView(activity)\n    }\n\n    override fun onActivityPaused(activity: Activity?) {\n        if (activity == null) {\n            return\n        }\n\n        val doKitViews = getDoKitViews(activity)\n        doKitViews.let {\n            for (doKitView: AbsDoKitView in it.values) {\n                doKitView.onPause()\n            }\n        }\n    }\n\n    private fun detachCountDownDoKitView(activity: Activity) {\n        val countDownDoKitView = mutableListOf<AbsDoKitView>()\n        activityDoKitViewMap[activity]?.forEach {\n            if (it.value.mode == DoKitViewLaunchMode.COUNTDOWN) {\n                countDownDoKitView.add(it.value)\n            }\n        }\n        countDownDoKitView.forEach {\n            detach(it.tag)\n        }\n    }\n\n    override fun onActivityStopped(activity: Activity?) {\n    }\n\n    /**\n     * 在当前Activity中添加指定悬浮窗\n     *\n     * @param doKitIntent\n     */\n    override fun attach(doKitIntent: DoKitIntent) {\n        try {\n            //判断当前Activity是否存在dokitView map\n            val currentActivityDoKitViews: MutableMap<String, AbsDoKitView> = when {\n                (activityDoKitViewMap[doKitIntent.activity] == null) -> {\n                    val doKitViewMap = mutableMapOf<String, AbsDoKitView>()\n                    activityDoKitViewMap[doKitIntent.activity] = doKitViewMap\n                    doKitViewMap\n                }\n                else -> {\n                    activityDoKitViewMap[doKitIntent.activity]!!\n                }\n            }\n\n            //判断该dokitview是否已经显示在页面上 同一个类型的dokitview 在页面上只显示一个\n            if (currentActivityDoKitViews[doKitIntent.tag] != null) {\n                //拿到指定的dokitView并更新位置\n                currentActivityDoKitViews[doKitIntent.tag]?.updateViewLayout(\n                    doKitIntent.tag,\n                    true\n                )\n                return\n            }\n            val doKitView = doKitIntent.targetClass.newInstance()\n            //在当前Activity中保存dokitView\n            //设置dokitview的属性\n            doKitView.mode = doKitIntent.mode\n            doKitView.bundle = doKitIntent.bundle\n            doKitView.tag = doKitIntent.tag\n            doKitView.setActivity(doKitIntent.activity)\n            doKitView.performCreate(context)\n            //在全局dokitviews中保存该类型的\n\n            globalSingleDoKitViewInfoMap[doKitView.tag] =\n                createGlobalSingleDokitViewInfo(doKitView)\n\n            //得到activity window中的根布局\n            //final ViewGroup mDecorView = getDecorView(dokitIntent.activity);\n\n            //往DecorView的子RootView中添加dokitView\n            if (doKitView.normalLayoutParams != null && doKitView.doKitView != null) {\n                getDoKitRootContentView(doKitIntent.activity)\n                    .addView(\n                        doKitView.doKitView,\n                        doKitView.normalLayoutParams\n                    )\n                //延迟100毫秒调用\n                doKitView.postDelayed(Runnable {\n                    doKitView.onResume()\n                    //操作DecorRootView\n                    doKitView.dealDecorRootView(getDoKitRootContentView(doKitIntent.activity))\n                }, MC_DELAY.toLong())\n//                DoKitViewManager.INSTANCE.notifyDokitViewAdd(doKitView)\n            }\n            currentActivityDoKitViews[doKitView.tag] = doKitView\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n    //    private static final String DOKIT_ROOT_VIEW_TAG = \"DokitRootView\";\n    /**\n     * @return rootView\n     */\n    private fun getDoKitRootContentView(activity: Activity): FrameLayout {\n        val decorView = getDecorView(activity)\n        var doKitRootView = decorView.findViewById<FrameLayout>(R.id.dokit_contentview_id)\n        if (doKitRootView != null) {\n            return doKitRootView\n        }\n        doKitRootView = DoKitFrameLayout(\n            context,\n            DoKitFrameLayout.DoKitFrameLayoutFlag_ROOT\n        )\n        //普通模式的返回按键监听\n        doKitRootView.setOnKeyListener(View.OnKeyListener { _, keyCode, _ ->\n            //监听返回键\n            if (keyCode == KeyEvent.KEYCODE_BACK) {\n                val doKitViewMap: Map<String, AbsDoKitView>? = getDoKitViews(activity)\n                if (doKitViewMap == null || doKitViewMap.isEmpty()) {\n                    return@OnKeyListener false\n                }\n                for (doKitView in doKitViewMap.values) {\n                    if (doKitView.shouldDealBackKey()) {\n                        return@OnKeyListener doKitView.onBackPressed()\n                    }\n                }\n                return@OnKeyListener false\n            }\n            false\n        })\n        doKitRootView.setClipChildren(false)\n        doKitRootView.setClipToPadding(false)\n\n        //解决无法获取返回按键的问题\n        doKitRootView.setFocusable(true)\n        doKitRootView.setFocusableInTouchMode(true)\n        doKitRootView.requestFocus()\n        doKitRootView.setId(R.id.dokit_contentview_id)\n        val doKitParams = FrameLayout.LayoutParams(\n            ViewGroup.LayoutParams.MATCH_PARENT,\n            ViewGroup.LayoutParams.MATCH_PARENT\n        )\n        try {\n            //解决由于项目集成SwipeBackLayout而出现的dokit入口不显示\n            if (BarUtils.isStatusBarVisible((activity))) {\n                doKitParams.topMargin = BarUtils.getStatusBarHeight()\n            }\n            if (BarUtils.isSupportNavBar()) {\n                if (BarUtils.isNavBarVisible((activity))) {\n                    doKitParams.bottomMargin = BarUtils.getNavBarHeight()\n                }\n            }\n        } catch (e: Exception) {\n            //e.printStackTrace();\n        }\n        doKitParams.gravity = Gravity.BOTTOM\n        doKitRootView.setLayoutParams(doKitParams)\n        //添加到DecorView中 为了不和用户自己往根布局中添加view干扰\n        decorView.addView(doKitRootView)\n        return doKitRootView\n    }\n\n    /**\n     * 移除每个activity指定的dokitView\n     */\n    override fun detach(doKitView: AbsDoKitView) {\n        //调用当前Activity的指定dokitView的Destroy方法\n        //dokitView.performDestroy();\n        detach(doKitView.tag)\n    }\n\n    /**\n     * 根据tag 移除ui和列表中的数据\n     *\n     * @param tag\n     */\n    override fun detach(tag: String) {\n        realDetach(tag)\n    }\n\n    private fun realDetach(tag: String) {\n        //移除每个activity中指定的dokitView\n        for (activityKey in activityDoKitViewMap.keys) {\n            val doKitViewMap = activityDoKitViewMap[activityKey]\n            //定位到指定dokitView\n            val doKitView = doKitViewMap?.get(tag) ?: continue\n            if (doKitView.doKitView != null) {\n                doKitView.doKitView?.visibility = View.GONE\n                getDoKitRootContentView(doKitView.activity).removeView(doKitView.doKitView)\n            }\n\n            //移除指定UI\n            //请求重新绘制\n            getDecorView(activityKey).requestLayout()\n            //执行dokitView的销毁\n            doKitView.performDestroy()\n            //移除map中的数据\n            doKitViewMap.remove(tag)\n        }\n        //同步移除全局指定类型的dokitView\n        if (globalSingleDoKitViewInfoMap.containsKey(tag)) {\n            globalSingleDoKitViewInfoMap.remove(tag)\n        }\n    }\n\n    override fun detach(doKitViewClass: Class<out AbsDoKitView>) {\n        detach(doKitViewClass.tagName)\n    }\n\n    /**\n     * 移除所有activity的所有dokitView\n     */\n    override fun detachAll() {\n\n        //移除每个activity中所有的dokitView\n        for (activityKey: Activity in activityDoKitViewMap.keys) {\n            val doKitViewMap = activityDoKitViewMap[activityKey]\n            //移除指定UI\n            getDoKitRootContentView(activityKey).removeAllViews()\n            //移除map中的数据\n            doKitViewMap?.clear()\n        }\n        globalSingleDoKitViewInfoMap.clear()\n    }\n\n    /**\n     * 获取当前页面指定的dokitView\n     *\n     * @param activity\n     * @param tag\n     * @return AbsDokitView\n     */\n    override fun <T : AbsDoKitView> getDoKitView(\n        activity: Activity?,\n        clazz: Class<T>\n    ): AbsDoKitView? {\n        if (TextUtils.isEmpty(clazz.tagName)) {\n            return null\n        }\n\n        return if (activityDoKitViewMap[activity] == null) {\n            null\n        } else activityDoKitViewMap[activity]?.get(clazz.tagName)\n    }\n\n    /**\n     * Activity销毁时调用\n     */\n    override fun onActivityDestroyed(activity: Activity?) {\n        if (activity == null) {\n            return\n        }\n\n        //移除dokit根布局\n        val doKitRootView = activity.findViewById<View>(R.id.dokit_contentview_id)\n        if (doKitRootView != null) {\n            getDecorView(activity).removeView(doKitRootView)\n        }\n        val doKitViewMap: Map<String, AbsDoKitView> = getDoKitViews(activity)\n        for (doKitView in doKitViewMap.values) {\n            doKitView.performDestroy()\n        }\n        activityDoKitViewMap.remove(activity)\n    }\n\n    /**\n     * 获取页面根布局\n     *\n     * @param activity\n     * @return\n     */\n    private fun getDecorView(activity: Activity): ViewGroup {\n        return activity.window.decorView as ViewGroup\n    }\n\n    /**\n     * 获取当前页面所有的dokitView\n     *\n     * @param activity\n     * @return\n     */\n    override fun getDoKitViews(activity: Activity?): Map<String, AbsDoKitView> {\n        return activityDoKitViewMap[activity] ?: emptyMap()\n    }\n\n    private fun createGlobalSingleDokitViewInfo(dokitView: AbsDoKitView): GlobalSingleDoKitViewInfo {\n        return GlobalSingleDoKitViewInfo(\n            dokitView.javaClass,\n            dokitView.tag,\n            dokitView.mode,\n            dokitView.bundle\n        )\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/SettingItem.java",
    "content": "package com.didichuxing.doraemonkit.kit.core;\n\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.StringRes;\n\n/**\n * Created by wanglikun on 2018/9/14.\n */\n\npublic class SettingItem {\n    public @StringRes\n    final int desc;\n\n    public String rightDesc;\n\n    public @DrawableRes\n    int icon;\n\n    public boolean isChecked;\n\n    public boolean canCheck;\n\n    public SettingItem(@StringRes int desc) {\n        this.desc = desc;\n    }\n\n    public SettingItem(@StringRes int desc, boolean isChecked) {\n        this.desc = desc;\n        this.isChecked = isChecked;\n        this.canCheck = true;\n    }\n\n    public SettingItem(@StringRes int desc, @DrawableRes int icon) {\n        this.desc = desc;\n        this.icon = icon;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/SettingItemAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.core;\n\nimport android.content.Context;\nimport android.text.TextUtils;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.CheckBox;\nimport android.widget.CompoundButton;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.StringRes;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\n\n/**\n * Created by wanglikun on 2018/9/14.\n */\n\npublic class SettingItemAdapter extends AbsRecyclerAdapter<AbsViewBinder<SettingItem>, SettingItem> {\n    private OnSettingItemClickListener mOnSettingItemClickListener;\n    private OnSettingItemSwitchListener mOnSettingItemSwitchListener;\n\n    public SettingItemAdapter(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected AbsViewBinder<SettingItem> createViewHolder(View view, int viewType) {\n        return new SettingItemViewHolder(view);\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        return inflater.inflate(R.layout.dk_item_setting, parent, false);\n    }\n\n    public class SettingItemViewHolder extends AbsViewBinder<SettingItem> {\n        private TextView mDesc;\n        private CheckBox mMenuSwitch;\n        private ImageView mIcon;\n        private TextView mRightDesc;\n\n        public SettingItemViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            mMenuSwitch = getView(R.id.menu_switch);\n            mDesc = getView(R.id.desc);\n            mIcon = getView(R.id.right_icon);\n            mRightDesc = getView(R.id.right_desc);\n        }\n\n        @Override\n        public void bind(final SettingItem settingItem) {\n            mDesc.setText(settingItem.desc);\n            if (settingItem.canCheck) {\n                mMenuSwitch.setVisibility(View.VISIBLE);\n                mMenuSwitch.setChecked(settingItem.isChecked);\n                mMenuSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {\n                    @Override\n                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {\n                        if (isMatched(settingItem.desc)) {\n                            if (AppHealthInfoUtil.getInstance().isAppHealthRunning()) {\n                                mMenuSwitch.setChecked(true);\n                                return;\n                            }\n                        }\n\n\n                        settingItem.isChecked = isChecked;\n                        mOnSettingItemSwitchListener.onSettingItemSwitch(mMenuSwitch, settingItem, isChecked);\n                    }\n                });\n            }\n            if (settingItem.icon != 0) {\n                mIcon.setVisibility(View.VISIBLE);\n                mIcon.setImageResource(settingItem.icon);\n            }\n            if (!TextUtils.isEmpty(settingItem.rightDesc)) {\n                mRightDesc.setVisibility(View.VISIBLE);\n                mRightDesc.setText(settingItem.rightDesc);\n            }\n        }\n\n        @Override\n        protected void onViewClick(View view, SettingItem data) {\n            super.onViewClick(view, data);\n            if (mOnSettingItemClickListener != null) {\n                mOnSettingItemClickListener.onSettingItemClick(view, data);\n            }\n        }\n    }\n\n    /**\n     * 是否命中\n     *\n     * @return\n     */\n    private boolean isMatched(@StringRes int desc) {\n        int[] resources = new int[]{\n                R.string.dk_weak_network_switch,\n                R.string.dk_item_block_switch,\n                R.string.dk_crash_capture_switch,\n                R.string.dk_cpu_detection_switch,\n                R.string.dk_frameinfo_detection_switch,\n                R.string.dk_ram_detection_switch,\n        };\n        boolean isMatches = false;\n        for (int res : resources) {\n            if (res == desc) {\n                isMatches = true;\n                break;\n            }\n        }\n\n        return isMatches;\n    }\n\n    public void setOnSettingItemClickListener(OnSettingItemClickListener onSettingItemClickListener) {\n        mOnSettingItemClickListener = onSettingItemClickListener;\n    }\n\n    public void setOnSettingItemSwitchListener(OnSettingItemSwitchListener onSettingItemSwitchListener) {\n        mOnSettingItemSwitchListener = onSettingItemSwitchListener;\n    }\n\n    public interface OnSettingItemClickListener {\n        void onSettingItemClick(View view, SettingItem data);\n    }\n\n    public interface OnSettingItemSwitchListener {\n        void onSettingItemSwitch(View view, SettingItem data, boolean on);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/SimpleDoKitLauncher.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Bundle\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.constant.BundleKey\nimport com.didichuxing.doraemonkit.constant.FragmentIndex\nimport com.didichuxing.doraemonkit.extension.tagName\n\n/**\n * 悬浮窗和全屏启动器\n */\ninternal object SimpleDoKitLauncher {\n    /**\n     * @JvmStatic:允许使用java的静态方法的方式调用\n     * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n     */\n    fun launchFloating(\n        targetClass: Class<out AbsDoKitView>,\n        mode: DoKitViewLaunchMode = DoKitViewLaunchMode.SINGLE_INSTANCE,\n        bundle: Bundle? = null\n    ) {\n        val doKitIntent = DoKitIntent(targetClass)\n        doKitIntent.mode = mode\n        doKitIntent.bundle = bundle\n        DoKitViewManager.INSTANCE.attach(doKitIntent)\n    }\n\n    fun removeFloating(\n        targetClass: Class<out AbsDoKitView>\n    ) {\n        DoKitViewManager.INSTANCE.detach(targetClass.tagName)\n    }\n\n    fun removeFloating(\n        dokitView:  AbsDoKitView\n    ) {\n        DoKitViewManager.INSTANCE.detach(dokitView)\n    }\n\n\n\n    /**\n     * @JvmStatic:允许使用java的静态方法的方式调用\n     * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n     */\n    fun launchFullScreen(\n        targetClass: Class<out BaseFragment?>,\n        context: Context? = null,\n        bundle: Bundle? = null,\n        isSystemFragment: Boolean = false\n    ) {\n        val ctx = context ?: DoKit.APPLICATION.applicationContext\n        ctx.startActivity(Intent(ctx, UniversalActivity::class.java).apply {\n            flags = Intent.FLAG_ACTIVITY_NEW_TASK\n            if (isSystemFragment) {\n                putExtra(BundleKey.FRAGMENT_INDEX, FragmentIndex.FRAGMENT_SYSTEM)\n                putExtra(BundleKey.SYSTEM_FRAGMENT_CLASS, targetClass)\n            } else {\n                putExtra(BundleKey.FRAGMENT_INDEX, FragmentIndex.FRAGMENT_CUSTOM)\n                putExtra(BundleKey.CUSTOM_FRAGMENT_CLASS, targetClass)\n            }\n            if (bundle != null) {\n                putExtras(bundle)\n            }\n        })\n    }\n\n\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/SystemDoKitViewManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\nimport android.content.Context\nimport android.text.TextUtils\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.DoKitEnv\nimport com.didichuxing.doraemonkit.extension.tagName\nimport com.didichuxing.doraemonkit.kit.health.CountDownDoKitView\nimport com.didichuxing.doraemonkit.kit.main.MainIconDoKitView\nimport com.didichuxing.doraemonkit.kit.toolpanel.ToolPanelDoKitView\nimport com.didichuxing.doraemonkit.util.DoKitSystemUtil\n\n/**\n * Created by wanglikun on 2018/10/23.\n * 系统悬浮窗管理类\n */\ninternal class SystemDoKitViewManager : AbsDoKitViewManager() {\n\n    private val context: Context get() = DoKitEnv.requireApp()\n\n    /**\n     * 参考:\n     * https://blog.csdn.net/awenyini/article/details/78265284\n     * https://yuqirong.me/2017/09/28/Window%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90(%E4%B8%80)%EF%BC%9A%E4%B8%8EDecorView%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B/\n     */\n    private val windowManager = DoKitViewManager.INSTANCE.windowManager\n    private val doKitViews: MutableList<AbsDoKitView> by lazy { mutableListOf<AbsDoKitView>() }\n    private val listeners: MutableList<DoKitViewManager.DokitViewAttachedListener> by lazy { mutableListOf<DoKitViewManager.DokitViewAttachedListener>() }\n\n    /**\n     * 获取页面上所有的dokitViews\n     *\n     * @return map\n     */\n    override fun getDoKitViews(activity: Activity?): Map<String, AbsDoKitView> {\n        val doKitViewMaps: MutableMap<String, AbsDoKitView> = mutableMapOf()\n        for (doKitView in doKitViews) {\n            doKitViewMaps[doKitView.tag] = doKitView\n        }\n        return doKitViewMaps\n    }\n\n    /**\n     * 当app进入后台时调用\n     */\n    override fun notifyBackground() {\n        for (doKitView in doKitViews) {\n            doKitView.onEnterBackground()\n        }\n    }\n\n    /**\n     * 当app进入前台时调用\n     */\n    override fun notifyForeground() {\n        for (page in doKitViews) {\n            page.onEnterForeground()\n        }\n    }\n\n    override fun dispatchOnActivityResumed(activity: Activity?) {\n        if (activity == null) {\n            return\n        }\n\n        if (activity is UniversalActivity) {\n            DoKit.getDoKitView<CountDownDoKitView>(activity)?.also {\n                detach(CountDownDoKitView::class.tagName)\n            }\n            return\n        }\n        //app启动\n        if (DoKitSystemUtil.isOnlyFirstLaunchActivity(activity)) {\n            onMainActivityResume(activity)\n        }\n\n        DoKitManager.ACTIVITY_LIFECYCLE_INFOS[activity.javaClass.canonicalName]?.let {\n            //新建Activity\n            if (it.lifeCycleStatus == DoKitLifeCycleStatus.RESUME && it.isInvokeStopMethod == false) {\n                onActivityResume(activity)\n                return\n            }\n\n            //activity resume\n            if (it.lifeCycleStatus == DoKitLifeCycleStatus.RESUME && it.isInvokeStopMethod == true) {\n                onActivityBackResume(activity)\n            }\n        }\n\n        //需要手动调用 生命周期回调\n        val dokitViewMap = getDoKitViews(activity)\n        for (absDokitView in dokitViewMap.values) {\n            absDokitView.onResume()\n        }\n    }\n\n    override fun attachMainIcon(activity: Activity?) {\n        if (activity == null) {\n            return\n        }\n        //假如不存在全局的icon这需要全局显示主icon\n        if (DoKitManager.ALWAYS_SHOW_MAIN_ICON && activity !is UniversalActivity) {\n            attach(DoKitIntent(MainIconDoKitView::class.java))\n            DoKitManager.MAIN_ICON_HAS_SHOW = true\n        } else {\n            DoKitManager.MAIN_ICON_HAS_SHOW = false\n        }\n    }\n\n    override fun detachMainIcon() {\n        detach(MainIconDoKitView::class.tagName)\n    }\n\n    override fun attachToolPanel(activity: Activity?) {\n        attach(DoKitIntent(ToolPanelDoKitView::class.java))\n    }\n\n    override fun detachToolPanel() {\n        detach(ToolPanelDoKitView::class.tagName)\n    }\n\n    override fun onMainActivityResume(activity: Activity?) {\n        if (activity == null) {\n            return\n        }\n        attachMainIcon(activity)\n        //倒计时DokitView\n        attachCountDownDoKitView(activity)\n        attachMcRecodingDoKitView(activity)\n\n//        if (SPUtils.getInstance().getBoolean(DoKitConstant.MC_CASE_RECODING_KEY, false)) {\n//            val action: Map<String, String> = mapOf(\"action\" to \"launch_recoding_view\")\n//            DoKitConstant.getModuleProcessor(DoKitModule.MODULE_MC)?.proceed(action)\n//        }\n    }\n\n    override fun onActivityResume(activity: Activity?) {\n        if (activity == null) {\n            return\n        }\n\n        //判断是否有MainIcon\n        if (DoKitManager.ALWAYS_SHOW_MAIN_ICON && !DoKit.isMainIconShow) {\n            DoKit.show()\n        }\n        //如果倒计时浮标没显示则重新添加\n        DoKit.getDoKitView<CountDownDoKitView>(activity)\n            ?.also {\n                if (activity is UniversalActivity) {\n                    detach(CountDownDoKitView::class.tagName)\n                } else {\n                    // 重置倒计时\n                    it.reset()\n                }\n            }\n            ?: also {\n                if (activity is UniversalActivity) {\n                    return@also\n                }\n                attachCountDownDoKitView(activity)\n            }\n    }\n\n    override fun onActivityBackResume(activity: Activity?) {\n        if (activity == null) {\n            return\n        }\n\n        //移除倒计时浮标\n        DoKit.getDoKitView<CountDownDoKitView>(activity)\n            ?.reset() // 重置倒计时\n            ?: attachCountDownDoKitView(activity)\n\n        //判断是否存在主入口icon\n        val dokitViews = getDoKitViews(activity)\n        if (dokitViews[MainIconDoKitView::class.tagName] == null) {\n            if (DoKitManager.ALWAYS_SHOW_MAIN_ICON && activity !is UniversalActivity) {\n                //添加main icon\n                val intent = DoKitIntent(MainIconDoKitView::class.java)\n                attach(intent)\n                DoKitManager.MAIN_ICON_HAS_SHOW = true\n            }\n        }\n    }\n\n    override fun onActivityPaused(activity: Activity?) {\n        if (activity == null) {\n            return\n        }\n\n        val dokitViews = getDoKitViews(activity)\n        for (absDokitView in dokitViews.values) {\n            absDokitView.onPause()\n        }\n    }\n\n    override fun onActivityStopped(activity: Activity?) {\n    }\n\n    /**\n     * 添加悬浮窗\n     *\n     * @param pageIntent\n     */\n    override fun attach(pageIntent: DoKitIntent) {\n        try {\n\n            for (dokitView in doKitViews) {\n                //如果当前page对象已经存在 则直接返回\n                if (pageIntent.targetClass.isInstance(dokitView)) {\n                    return\n                }\n            }\n            //通过newInstance方式创建floatPage\n            val dokitView = pageIntent.targetClass.newInstance()\n            dokitView.bundle = pageIntent.bundle\n            //page.setTag(pageIntent.tag);\n            //添加进page列表\n            doKitViews.add(dokitView)\n            dokitView.performCreate(context)\n            //在window上显示floatIcon\n            //WindowManagerImpl具体实现\n            windowManager.addView(\n                dokitView.doKitView,\n                dokitView.systemLayoutParams\n            )\n            dokitView.onResume()\n            if (!DoKitManager.IS_NORMAL_FLOAT_MODE) {\n                for (listener in listeners) {\n                    listener.onDokitViewAdd(dokitView)\n                }\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    override fun detach(tag: String) {\n        if (TextUtils.isEmpty(tag)) {\n            return\n        }\n        val it = doKitViews.iterator()\n        while (it.hasNext()) {\n            val dokitView = it.next()\n            if (tag == dokitView.tag) {\n                windowManager.removeView(dokitView.doKitView)\n                dokitView.performDestroy()\n                it.remove()\n                return\n            }\n        }\n    }\n\n    override fun detach(doKitView: AbsDoKitView) {\n        detach(doKitView.tagName)\n    }\n\n    override fun detach(doKitViewClass: Class<out AbsDoKitView>) {\n        detach(doKitViewClass.tagName)\n    }\n\n    override fun detachAll() {\n\n        val it = doKitViews.iterator()\n        while (it.hasNext()) {\n            val doKitView = it.next()\n            windowManager.removeView(doKitView.doKitView)\n            doKitView.performDestroy()\n            it.remove()\n        }\n    }\n\n    override fun <T : AbsDoKitView> getDoKitView(\n        activity: Activity?,\n        clazz: Class<T>\n    ): AbsDoKitView? {\n        if (TextUtils.isEmpty(clazz.tagName)) {\n            return null\n        }\n        for (dokitView in doKitViews) {\n            if (clazz.tagName == dokitView.tag) {\n                return dokitView\n            }\n        }\n        return null\n    }\n\n\n    /**\n     * Activity销毁时调用 不需要实现 为了统一api\n     */\n    override fun onActivityDestroyed(activity: Activity?) {}\n\n    /**\n     * 在每一个float page创建时 添加监听器\n     *\n     * @param listener\n     */\n    fun addListener(listener: DoKitViewManager.DokitViewAttachedListener) {\n        listeners.add(listener)\n    }\n\n    fun removeListener(listener: DoKitViewManager.DokitViewAttachedListener) {\n        listeners.remove(listener)\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/TouchProxy.java",
    "content": "package com.didichuxing.doraemonkit.kit.core;\n\nimport android.view.MotionEvent;\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\n/**\n * @author wanglikun\n * touch 事件代理 解决点击和触摸事件的冲突\n */\npublic class TouchProxy {\n    private static final int MIN_DISTANCE_MOVE = 4;\n    private static final int MIN_TAP_TIME = 1000;\n\n    private OnTouchEventListener mEventListener;\n    private int mLastX;\n    private int mLastY;\n    private int mStartX;\n    private int mStartY;\n    private TouchState mState = TouchState.STATE_STOP;\n\n    public TouchProxy(OnTouchEventListener eventListener) {\n        mEventListener = eventListener;\n    }\n\n    public void setEventListener(OnTouchEventListener eventListener) {\n        mEventListener = eventListener;\n    }\n\n    private enum TouchState {\n        STATE_MOVE,\n        STATE_STOP\n    }\n\n    public boolean onTouchEvent(View v, MotionEvent event) {\n        int distance = UIUtils.dp2px(1) * MIN_DISTANCE_MOVE;\n        int x = (int) event.getRawX();\n        int y = (int) event.getRawY();\n        switch (event.getAction()) {\n            case MotionEvent.ACTION_DOWN: {\n                mStartX = x;\n                mStartY = y;\n                mLastY = y;\n                mLastX = x;\n                if (mEventListener != null) {\n                    mEventListener.onDown(x, y);\n                }\n            }\n            break;\n            case MotionEvent.ACTION_MOVE: {\n                if (Math.abs(x - mStartX) < distance\n                        && Math.abs(y - mStartY) < distance) {\n                    if (mState == TouchState.STATE_STOP) {\n                        break;\n                    }\n                } else if (mState != TouchState.STATE_MOVE) {\n                    mState = TouchState.STATE_MOVE;\n                }\n                if (mEventListener != null) {\n                    mEventListener.onMove(mLastX, mLastY, x - mLastX, y - mLastY);\n                }\n                mLastY = y;\n                mLastX = x;\n                mState = TouchState.STATE_MOVE;\n            }\n            break;\n            case MotionEvent.ACTION_UP: {\n                if (mEventListener != null) {\n                    mEventListener.onUp(x, y);\n                }\n                if (mState != TouchState.STATE_MOVE\n                        && event.getEventTime() - event.getDownTime() < MIN_TAP_TIME) {\n                    v.performClick();\n                }\n                mState = TouchState.STATE_STOP;\n            }\n            break;\n            default:\n                break;\n        }\n        return true;\n    }\n\n\n    public interface OnTouchEventListener {\n        void onMove(int x, int y, int dx, int dy);\n\n        void onUp(int x, int y);\n\n        void onDown(int x, int y);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/TranslucentActivity.java",
    "content": "package com.didichuxing.doraemonkit.kit.core;\n\n/**\n * 透明的容器activity\n */\npublic class TranslucentActivity extends UniversalActivity {\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/core/UniversalActivity.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.os.Bundle\nimport android.widget.Toast\nimport com.didichuxing.doraemonkit.constant.BundleKey\nimport com.didichuxing.doraemonkit.constant.FragmentIndex\nimport com.didichuxing.doraemonkit.kit.blockmonitor.BlockMonitorFragment\nimport com.didichuxing.doraemonkit.kit.colorpick.ColorPickerSettingFragment\n\n/**\n * Created by jint on 2018/10/26.\n * app基础信息Activity\n */\nopen class UniversalActivity : BaseActivity() {\n    var mFragmentClass: Class<out BaseFragment>? = null\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        val bundle = intent.extras\n        if (bundle == null) {\n            finish()\n            return\n        }\n        val index = bundle.getInt(BundleKey.FRAGMENT_INDEX)\n        if (index == 0) {\n            finish()\n            return\n        }\n\n        when (index) {\n            FragmentIndex.FRAGMENT_COLOR_PICKER_SETTING -> mFragmentClass =\n                ColorPickerSettingFragment::class.java\n            FragmentIndex.FRAGMENT_BLOCK_MONITOR -> mFragmentClass =\n                BlockMonitorFragment::class.java\n            FragmentIndex.FRAGMENT_SYSTEM -> if (bundle[BundleKey.SYSTEM_FRAGMENT_CLASS] != null) {\n                mFragmentClass = bundle[BundleKey.SYSTEM_FRAGMENT_CLASS] as Class<out BaseFragment>\n            }\n            FragmentIndex.FRAGMENT_CUSTOM -> if (bundle[BundleKey.CUSTOM_FRAGMENT_CLASS] != null) {\n                mFragmentClass = bundle[BundleKey.CUSTOM_FRAGMENT_CLASS] as Class<out BaseFragment>\n            }\n            else -> {\n            }\n        }\n        if (mFragmentClass == null) {\n            finish()\n            Toast.makeText(\n                this,\n                String.format(\"fragment index %s not found\", index),\n                Toast.LENGTH_SHORT\n            ).show()\n            return\n        }\n        showContent(mFragmentClass!!, bundle)\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/crash/CrashCaptureKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.crash\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.config.CrashCaptureConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n/**\n * Created by wanglikun on 2019/6/12\n */\n@AutoService(AbstractKit::class)\nclass CrashCaptureKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_crash\n    override val icon: Int\n        get() = R.mipmap.dk_crash_catch\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        startUniversalActivity(CrashCaptureMainFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n        CrashCaptureManager.getInstance().init(context)\n        if (CrashCaptureConfig.isCrashCaptureOpen()) {\n            CrashCaptureManager.getInstance().start()\n        } else {\n            CrashCaptureManager.getInstance().stop()\n        }\n    }\n\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_comm_ck_crash\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/crash/CrashCaptureMainFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.crash;\n\nimport android.os.Bundle;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.text.format.Formatter;\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.CrashCaptureConfig;\nimport com.didichuxing.doraemonkit.constant.BundleKey;\nimport com.didichuxing.doraemonkit.kit.fileexplorer.FileExplorerFragment;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\nimport com.didichuxing.doraemonkit.util.DoKitFileUtil;\n\npublic class CrashCaptureMainFragment extends BaseFragment {\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_crash_capture_main;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initview();\n    }\n\n    private void initview() {\n        HomeTitleBar titleBar = findViewById(R.id.title_bar);\n        titleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                finish();\n            }\n        });\n        RecyclerView settingList = findViewById(R.id.setting_list);\n        settingList.setLayoutManager(new LinearLayoutManager(getContext()));\n        final SettingItemAdapter mSettingItemAdapter = new SettingItemAdapter(getContext());\n        mSettingItemAdapter.append(new SettingItem(R.string.dk_crash_capture_switch, CrashCaptureConfig.isCrashCaptureOpen()));\n        mSettingItemAdapter.append(new SettingItem(R.string.dk_crash_capture_look, R.mipmap.dk_more_icon));\n        SettingItem item = new SettingItem(R.string.dk_crash_capture_clean_data);\n        item.rightDesc = Formatter.formatFileSize(getContext(), DoKitFileUtil.getDirectorySize(CrashCaptureManager.getInstance().getCrashCacheDir()));\n        mSettingItemAdapter.append(item);\n        mSettingItemAdapter.setOnSettingItemSwitchListener(new SettingItemAdapter.OnSettingItemSwitchListener() {\n            @Override\n            public void onSettingItemSwitch(View view, SettingItem data, boolean on) {\n                if (data.desc == R.string.dk_crash_capture_switch) {\n                    CrashCaptureConfig.setCrashCaptureOpen(on);\n                    if (on) {\n                        CrashCaptureManager.getInstance().start();\n                    } else {\n                        CrashCaptureManager.getInstance().stop();\n                    }\n                }\n            }\n        });\n        mSettingItemAdapter.setOnSettingItemClickListener(new SettingItemAdapter.OnSettingItemClickListener() {\n            @Override\n            public void onSettingItemClick(View view, SettingItem data) {\n                if (data.desc == R.string.dk_crash_capture_look) {\n                    Bundle bundle = new Bundle();\n                    bundle.putSerializable(BundleKey.DIR_KEY, CrashCaptureManager.getInstance().getCrashCacheDir());\n                    showContent(FileExplorerFragment.class, bundle);\n                } else if (data.desc == R.string.dk_crash_capture_clean_data) {\n                    CrashCaptureManager.getInstance().clearCacheHistory();\n                    data.rightDesc = Formatter.formatFileSize(getContext(), DoKitFileUtil.getDirectorySize(CrashCaptureManager.getInstance().getCrashCacheDir()));\n                    mSettingItemAdapter.notifyDataSetChanged();\n                    showToast(R.string.dk_crash_capture_clean_data);\n                }\n            }\n        });\n        settingList.setAdapter(mSettingItemAdapter);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/crash/CrashCaptureManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.crash;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.util.Log;\nimport android.view.View;\n\nimport androidx.core.view.ViewCompat;\n\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.constant.CachesKey;\nimport com.didichuxing.doraemonkit.datapick.DataPickManager;\nimport com.didichuxing.doraemonkit.util.DoKitCacheUtils;\nimport com.didichuxing.doraemonkit.util.DoKitFileUtil;\nimport com.didichuxing.doraemonkit.util.DoKitImageUtil;\n\nimport java.io.File;\nimport java.io.Serializable;\nimport java.lang.reflect.Method;\nimport java.text.SimpleDateFormat;\n\n/**\n * Created by wanglikun on 2019-06-12\n * 系统崩溃异常捕获\n */\npublic class CrashCaptureManager implements Thread.UncaughtExceptionHandler {\n    private static final String TAG = \"CrashCaptureManager\";\n\n    private final Thread.UncaughtExceptionHandler mDefaultHandler;\n    private final Handler mHandler;\n    private Context mContext;\n    private SimpleDateFormat mDateFormat = new SimpleDateFormat(\"yyyyMMdd_HHmmdd\");\n    private String mFilenamePrefix;\n\n    private CrashCaptureManager() {\n        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();\n        HandlerThread handlerThread = new HandlerThread(TAG);\n        handlerThread.start();\n        mHandler = new Handler(handlerThread.getLooper());\n        generateFilenamePrefix();\n    }\n\n    private static class Holder {\n        private static final CrashCaptureManager INSTANCE = new CrashCaptureManager();\n    }\n\n    public static CrashCaptureManager getInstance() {\n        return Holder.INSTANCE;\n    }\n\n    public void init(Context context) {\n        mContext = context.getApplicationContext();\n    }\n\n    public void start() {\n        Thread.setDefaultUncaughtExceptionHandler(this);\n    }\n\n    public void stop() {\n        Thread.setDefaultUncaughtExceptionHandler(mDefaultHandler);\n    }\n\n    @Override\n    public void uncaughtException(final Thread t, final Throwable e) {\n        //让log和图片统一文件名 每次生成新的文件名\n        generateFilenamePrefix();\n        //异步保存崩溃截图在华为emui10.0上会失效 已改成同步\n        asyncSaveCrashScreenshot();\n        //保存崩溃信息\n        DoKitCacheUtils.saveObject((Serializable) Log.getStackTraceString(e), getCrashCacheFile());\n        //保存埋点数据\n        DataPickManager.getInstance().saveData2Local();\n        post(new Runnable() {\n            @Override\n            public void run() {\n                ToastUtils.showShort(mContext.getString(R.string.dk_crash_capture_tips));\n            }\n        });\n        postDelay(new Runnable() {\n            @Override\n            public void run() {\n                if (mDefaultHandler != null) {\n                    mDefaultHandler.uncaughtException(t, e);\n                }\n            }\n        }, 2000);\n    }\n\n    private void post(Runnable r) {\n        mHandler.post(r);\n    }\n\n    private void postDelay(Runnable r, long delayMillis) {\n        mHandler.postDelayed(r, delayMillis);\n    }\n\n    public File getCrashCacheDir() {\n        File dir = new File(mContext.getCacheDir() + File.separator + CachesKey.CRASH_HISTORY);\n        if (!dir.exists()) {\n            dir.mkdir();\n        }\n        return dir;\n    }\n\n    private File getCrashCacheFile() {\n        return new File(getCrashCacheDir(), String.format(\"%s.log\", mFilenamePrefix));\n    }\n\n    private File getCrashCacheScreenshotFile(int num) {\n        return new File(getCrashCacheDir(), String.format(\"%s_%d.png\", mFilenamePrefix, num));\n    }\n\n    public void clearCacheHistory() {\n        DoKitFileUtil.deleteDirectory(getCrashCacheDir());\n    }\n\n    private void generateFilenamePrefix() {\n        mFilenamePrefix = mDateFormat.format(System.currentTimeMillis());\n    }\n\n    public void asyncSaveCrashScreenshot() {\n        saveCrashScreenshot();\n//        AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {\n//            @Override\n//            public void run() {\n//                saveCrashScreenshot();\n//            }\n//        });\n    }\n\n    public void saveCrashScreenshot() {\n        try {\n            Class<?> wmgClass = Class.forName(\"android.view.WindowManagerGlobal\");\n            Object wmgInstance = wmgClass.getMethod(\"getInstance\").invoke(null);\n            Method getViewRootNames = wmgClass.getMethod(\"getViewRootNames\");\n            Method getRootView = wmgClass.getMethod(\"getRootView\", String.class);\n            String[] rootViewNames = (String[]) getViewRootNames.invoke(wmgInstance);\n            if (rootViewNames != null) {\n                for (int i = 0; i < rootViewNames.length; i++) {\n                    View rootView = (View) getRootView.invoke(wmgInstance, rootViewNames[i]);\n                    if (rootView != null) {\n                        if (!ViewCompat.isLaidOut(rootView)) {\n                            Log.d(TAG, \"View尚未绘制完成，可能无法成功截取图片\");\n                        }\n                        Bitmap bitmap = Bitmap.createBitmap(rootView.getWidth(), rootView.getHeight(), Bitmap.Config.ARGB_8888);\n                        Canvas canvas = new Canvas(bitmap);\n                        rootView.draw(canvas);\n\n                        File output = getCrashCacheScreenshotFile(i);\n                        DoKitImageUtil.bitmap2File(bitmap, 100, output);\n                    }\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/crash/CrashHistoryAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.crash;\n\nimport android.content.Context;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\nimport com.didichuxing.doraemonkit.util.DoKitFormatUtil;\n\n/**\n * Created by wanglikun on 2019-06-12\n */\npublic class CrashHistoryAdapter extends AbsRecyclerAdapter<AbsViewBinder<CrashInfo>, CrashInfo> {\n\n    public CrashHistoryAdapter(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected AbsViewBinder<CrashInfo> createViewHolder(View view, int viewType) {\n        return new CrashHistoryViewHolder(view);\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        return inflater.inflate(R.layout.dk_item_crash_history, parent, false);\n    }\n\n    public static class CrashHistoryViewHolder extends AbsViewBinder<CrashInfo> {\n        private TextView mContent;\n        private TextView mTime;\n\n        public CrashHistoryViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            mContent = getView(R.id.content);\n            mTime = getView(R.id.time);\n        }\n\n        @Override\n        public void bind(CrashInfo info) {\n            mContent.setText(Log.getStackTraceString(info.tr));\n            mTime.setText(DoKitFormatUtil.format(info.time));\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/crash/CrashInfo.java",
    "content": "package com.didichuxing.doraemonkit.kit.crash;\n\nimport java.io.Serializable;\n\n/**\n * Created by wanglikun on 2019-06-12\n */\npublic class CrashInfo implements Serializable {\n    public final Throwable tr;\n\n    public final long time;\n\n    public CrashInfo(Throwable tr, long l) {\n        this.tr = tr;\n        this.time = l;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/dataclean/DataCleanFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.dataclean\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.*\nimport androidx.core.view.children\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.didichuxing.doraemonkit.util.PathUtils\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.kit.core.SettingItem\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter\nimport com.didichuxing.doraemonkit.util.DataCleanUtil\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.DoKitFileUtil\nimport com.didichuxing.doraemonkit.widget.dialog.DialogInfo\nimport com.didichuxing.doraemonkit.widget.dialog.DialogListener\nimport com.didichuxing.doraemonkit.widget.dialog.DialogProvider\nimport com.didichuxing.doraemonkit.widget.dialog.SimpleDialogListener\nimport com.didichuxing.doraemonkit.widget.recyclerview.DividerItemDecoration\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\nimport java.io.File\nimport java.util.*\n\n/**\n * Created by wanglikun on 2018/11/17.\n */\nclass DataCleanFragment : BaseFragment() {\n    private lateinit var mSettingList: RecyclerView\n    private lateinit var mSettingItemAdapter: SettingItemAdapter\n    private lateinit var mItemWrap: LinearLayout\n    private lateinit var mBtnClean: Button\n\n    private lateinit var dirs: MutableList<String>\n\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_data_clean\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        initView()\n    }\n\n    private fun initView() {\n        val titleBar = findViewById<HomeTitleBar>(R.id.title_bar)\n        titleBar.setListener { finish() }\n        mSettingList = findViewById(R.id.setting_list)\n        mItemWrap = findViewById(R.id.item_wrap)\n        mBtnClean = findViewById(R.id.btn_clean)\n        dirs = mutableListOf(DoKitCommUtil.getString(R.string.dk_kit_cache_check_all))\n        val innerDirs = File(PathUtils.getInternalAppDataPath()).listFiles()?.filter { file ->\n            file.isDirectory\n        }?.map { file ->\n            file.name\n        }?.asIterable()\n        dirs.addAll(innerDirs!!)\n        dirs.forEach {\n            val item: RelativeLayout = LayoutInflater.from(activity)\n                .inflate(R.layout.dk_item_data_clean, null) as RelativeLayout\n            item.findViewById<TextView>(R.id.tv_name).text = it\n            item.findViewById<Switch>(R.id.switch_btn).isChecked = false\n            item.setOnClickListener { innerItem ->\n                val switch = innerItem.findViewById<Switch>(R.id.switch_btn)\n                val name = innerItem.findViewById<TextView>(R.id.tv_name)\n                if (name.text == DoKitCommUtil.getString(R.string.dk_kit_cache_check_all)) {\n                    if (switch.isChecked) {\n                        mItemWrap.children.forEach {\n                            it.findViewById<Switch>(R.id.switch_btn).isChecked = false\n                        }\n                    } else {\n                        mItemWrap.children.forEach {\n                            it.findViewById<Switch>(R.id.switch_btn).isChecked = true\n                        }\n                    }\n                } else {\n                    switch.isChecked = !switch.isChecked\n                }\n\n            }\n            mItemWrap.addView(item)\n        }\n        val layoutManager = LinearLayoutManager(context)\n        mSettingList.setLayoutManager(layoutManager)\n        val settingItems: MutableList<SettingItem> = ArrayList()\n        val settingItem = SettingItem(R.string.dk_kit_data_clean)\n        settingItem.rightDesc = DataCleanUtil.getApplicationDataSizeStr(context)\n        settingItems.add(settingItem)\n        mSettingItemAdapter = SettingItemAdapter(context)\n        mSettingItemAdapter.setData(settingItems)\n        mBtnClean.setOnClickListener { view ->\n            val dialogInfo = DialogInfo()\n            dialogInfo.title = getString(R.string.dk_hint)\n            dialogInfo.desc = getString(R.string.dk_app_data_clean)\n            dialogInfo.listener = object : DialogListener {\n                override fun onPositive(dialogProvider: DialogProvider<*>): Boolean {\n                    cleanCache()\n                    mSettingItemAdapter.data[0].rightDesc =\n                        DataCleanUtil.getApplicationDataSizeStr(context)\n                    mSettingItemAdapter.notifyDataSetChanged()\n                    return true\n                }\n\n                override fun onNegative(dialogProvider: DialogProvider<*>): Boolean {\n                    return true\n                }\n            }\n            showDialog(dialogInfo)\n        }\n        mSettingList.setAdapter(mSettingItemAdapter)\n        val decoration = DividerItemDecoration(DividerItemDecoration.VERTICAL)\n        decoration.setDrawable(resources.getDrawable(R.drawable.dk_divider))\n        mSettingList.addItemDecoration(decoration)\n    }\n\n    private fun cleanCache() {\n        for (index in 1 until mItemWrap.childCount) {\n            val item = mItemWrap.getChildAt(index)\n            val name = item.findViewById<TextView>(R.id.tv_name)\n            val switch = item.findViewById<Switch>(R.id.switch_btn)\n            if (switch.isChecked) {\n                val file = File(PathUtils.getInternalAppDataPath() + File.separator + name.text)\n                if (file.isDirectory) {\n                    DoKitFileUtil.deleteDirectory(file)\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/dataclean/DataCleanKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.dataclean\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n/**\n * Created by wanglikun on 2018/11/17.\n */\n@AutoService(AbstractKit::class)\nclass DataCleanKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_data_clean\n    override val icon: Int\n        get() = R.mipmap.dk_data_clean\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        startUniversalActivity(DataCleanFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_comm_ck_cache\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/dbdebug/DbDebugFragment.java",
    "content": "//package com.didichuxing.doraemonkit.kit.dbdebug;\n//\n//import android.os.Bundle;\n//import android.text.Html;\n//import android.view.View;\n//import android.widget.TextView;\n//\n//import androidx.annotation.NonNull;\n//import androidx.annotation.Nullable;\n//\n//import com.amitshekhar.DebugDB;\n//import com.amitshekhar.debug.encrypt.sqlite.DebugDBEncryptFactory;\n//import com.amitshekhar.debug.sqlite.DebugDBFactory;\n//import com.didichuxing.doraemonkit.util.NetworkUtils;\n//import com.didichuxing.doraemonkit.DoraemonKit;\n//import com.didichuxing.doraemonkit.R;\n//import com.didichuxing.doraemonkit.kit.core.BaseFragment;\n//import com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\n//\n///**\n// * @author jintai\n// * Created by jintai on 2019/10/17.\n// * 数据库远程调试介绍页面\n// */\n//\n//public class DbDebugFragment extends BaseFragment {\n//    TextView tvIp;\n//\n//    @Override\n//    protected int onRequestLayout() {\n//        return R.layout.dk_fragment_db_debug;\n//    }\n//\n//    @Override\n//    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n//        super.onViewCreated(view, savedInstanceState);\n//        try {\n//            initView();\n//        } catch (Exception e) {\n//            e.printStackTrace();\n//        }\n//    }\n//\n//    private void initView() throws Exception {\n//        if (!DebugDB.isServerRunning()) {\n//            DebugDB.initialize(DoraemonKit.APPLICATION, new DebugDBFactory());\n//            DebugDB.initialize(DoraemonKit.APPLICATION, new DebugDBEncryptFactory());\n//        }\n//        HomeTitleBar titleBar = findViewById(R.id.title_bar);\n//        titleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n//            @Override\n//            public void onRightClick() {\n//                finish();\n//            }\n//        });\n//        TextView tvTip = findViewById(R.id.tv_tip);\n//        tvTip.setText(Html.fromHtml(getResources().getString(R.string.dk_kit_db_debug_desc)));\n//        tvIp = findViewById(R.id.tv_ip);\n//        if (DebugDB.isServerRunning()) {\n//            tvIp.setText(\"\" + DebugDB.getAddressLog().replace(\"Open \", \"\").replace(\"in your browser\", \"\"));\n//        } else {\n//            tvIp.setText(\"servse is not start\");\n//        }\n//    }\n//\n//    /**\n//     * 网络变化时调用\n//     *\n//     * @param networkType\n//     */\n//    public void networkChanged(NetworkUtils.NetworkType networkType) {\n//        if (tvIp == null) {\n//            return;\n//        }\n//        if (networkType == NetworkUtils.NetworkType.NETWORK_NO) {\n//            tvIp.setText(\"please check network is connected\");\n//        } else {\n//            tvIp.setText(\"\" + DebugDB.getAddressLog().replace(\"Open \", \"\").replace(\"in your browser\", \"\"));\n//        }\n//    }\n//}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/dbdebug/DbDebugKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.dbdebug\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-24-17:05\n * 描    述：数据库远程访问入口 去掉\n * 修订历史：\n * ================================================\n */\n//@AutoService(AbstractKit.class)\nclass DbDebugKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_tools_dbdebug\n    override val icon: Int\n        get() = R.mipmap.dk_db_view\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_comm_ck_dbview\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/dokitforweb/DoKitForWebJsInjectFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.dokitforweb\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.*\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/10-10:52\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitForWebJsInjectFragment : BaseFragment() {\n\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_dokit_for_web\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        val homeTitleBar = findViewById<HomeTitleBar>(R.id.title_bar)\n        val editText = findViewById<EditText>(R.id.et_h5_url)\n        val h5_file = findViewById<RadioButton>(R.id.h5_file)\n        val h5_url = findViewById<RadioButton>(R.id.h5_url)\n        val tv_h5jsInject = findViewById<RadioGroup>(R.id.tv_h5jsInject)\n        val switch_btn = findViewById<Switch>(R.id.switch_btn)\n\n        homeTitleBar.setListener { activity?.finish() }\n\n        if (DoKitManager.H5_MC_JS_INJECT_MODE == \"file\") {\n            h5_file.isChecked = true\n        } else {\n            h5_url.isChecked = true\n        }\n\n        val isOk = findViewById<Button>(R.id.btn_ok)\n        isOk.setOnClickListener {\n            DokitForWeb.saveMcH5Inject(switch_btn.isChecked)\n            DokitForWeb.saveMcInjectUrl(editText.text.toString())\n            if (h5_url.isChecked) {\n                DokitForWeb.saveMcInjectMode(\"url\")\n            } else {\n                DokitForWeb.saveMcInjectMode(\"file\")\n            }\n\n            ToastUtils.showShort(\"保存成功\")\n            activity?.finish()\n        }\n        editText.setText(DoKitManager.H5_MC_JS_INJECT_URL)\n        switch_btn.isChecked = DoKitManager.H5_DOKIT_MC_INJECT\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/dokitforweb/DokitForWeb.kt",
    "content": "package com.didichuxing.doraemonkit.kit.dokitforweb\n\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.util.SPUtils\n\nobject DokitForWeb {\n\n\n    const val DOKIT_H5_MC_INJECT_JS = \"dokit_h5_mc_inject_js\"\n    const val NAME_DOKIIT_MC_CONFIGALL = \"dokit-for-web-config-all\"\n\n    const val DOKIT_H5_MC_INJECT_MODE = \"dokit_h5_mc_inject_mode\"\n    const val DOKIT_H5_MC_INJECT_URL = \"dokit_h5_mc_inject_url\"\n    const val DOKIT_H5_MC_INJECT_URL_DEFAULT = \"http://120.55.183.20/dokit/mc/dokit.js\"\n\n    var sp: SPUtils = SPUtils.getInstance(NAME_DOKIIT_MC_CONFIGALL)\n\n\n    fun loadConfig() {\n        DoKitManager.H5_DOKIT_MC_INJECT = sp.getBoolean(DOKIT_H5_MC_INJECT_JS)\n\n        DoKitManager.H5_MC_JS_INJECT_MODE = sp.getString(DOKIT_H5_MC_INJECT_MODE, \"file\")\n        DoKitManager.H5_MC_JS_INJECT_URL = sp.getString(DOKIT_H5_MC_INJECT_URL, DOKIT_H5_MC_INJECT_URL_DEFAULT)\n    }\n\n    fun saveMcH5Inject(switch: Boolean) {\n        DoKitManager.H5_DOKIT_MC_INJECT = switch\n        sp.put(DOKIT_H5_MC_INJECT_JS, switch)\n    }\n\n    fun saveMcInjectMode(mode: String) {\n        DoKitManager.H5_MC_JS_INJECT_MODE = mode\n        sp.put(DOKIT_H5_MC_INJECT_MODE, mode)\n    }\n\n    fun saveMcInjectUrl(url: String) {\n        DoKitManager.H5_MC_JS_INJECT_URL = url\n        sp.put(DOKIT_H5_MC_INJECT_URL, url)\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/dokitforweb/DokitForWebKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.dokitforweb\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.weaknetwork.WeakNetworkFragment\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.google.auto.service.AutoService\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-24-17:05\n * 描    述：数据库远程访问入口 去掉\n * 修订历史：\n * ================================================\n */\n@AutoService(AbstractKit::class)\nclass DokitForWebKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_dokit_for_web\n    override val icon: Int\n        get() = R.mipmap.dk_dokit_for_web\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip))\n            return false\n        }\n        startUniversalActivity(DoKitForWebJsInjectFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n        DokitForWeb.loadConfig()\n    }\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_platform_ck_dokit_for_web\"\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/DBListAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.BaseAdapter;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\n\nimport java.util.List;\n\npublic class DBListAdapter extends BaseAdapter {\n    private List data;\n    private Context context;\n\n    public DBListAdapter(Context context, List data) {\n        this.data = data;\n        this.context = context;\n    }\n\n    @Override\n    public int getCount() {\n        return data.size();\n    }\n\n    @Override\n    public Object getItem(int position) {\n        return data.get(position);\n    }\n\n    @Override\n    public long getItemId(int position) {\n        return position;\n    }\n\n    @Override\n    public View getView(int position, View convertView, ViewGroup parent) {\n        ViewHolder viewHolder;\n        if (convertView == null) {\n            convertView = LayoutInflater.from(context).inflate(R.layout.dk_item_file_info, null);\n            convertView.setTag(new ViewHolder(convertView));\n        }\n        viewHolder = (ViewHolder) convertView.getTag();\n        viewHolder.setData((String) data.get(position));\n        return convertView;\n    }\n\n    public class ViewHolder {\n        public TextView textView;\n\n        public ViewHolder(View convertView) {\n            textView = (TextView) convertView.findViewById(R.id.name);\n            convertView.findViewById(R.id.icon).setVisibility(View.GONE);\n\n        }\n\n        public void setData(String data) {\n            textView.setText(data);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/DatabaseDetailFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer;\n\nimport android.database.sqlite.SQLiteDatabase;\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.content.ContextCompat;\nimport android.view.View;\nimport android.widget.AdapterView;\nimport android.widget.ListView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.constant.BundleKey;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.tableview.TableConfig;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.ArrayTableData;\nimport com.didichuxing.doraemonkit.widget.tableview.format.FastTextDrawFormat;\nimport com.didichuxing.doraemonkit.widget.tableview.style.FontStyle;\nimport com.didichuxing.doraemonkit.widget.tableview.component.SmartTable;\nimport com.didichuxing.doraemonkit.widget.titlebar.TitleBar;\nimport com.didichuxing.doraemonkit.util.DatabaseUtil;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class DatabaseDetailFragment extends BaseFragment {\n\n    private SmartTable table;\n    private ListView tableListView;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_db_detail;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        Bundle data = getArguments();\n        SQLiteDatabase sqLiteDatabase = null;\n        List<String> tableNames = new ArrayList<>();\n        if (data != null) {\n            File mFile = (File) data.getSerializable(BundleKey.FILE_KEY);\n            String path = mFile.getPath();\n            sqLiteDatabase = SQLiteDatabase.openOrCreateDatabase(path, null);\n            tableNames = DatabaseUtil.queryTableName(sqLiteDatabase);\n        }\n        table = findViewById(R.id.table);\n        FontStyle fontStyle = new FontStyle(getContext(), 15, ContextCompat.getColor(getContext(), R.color.dk_color_000000));\n        TableConfig.getInstance().setVerticalPadding(10).setHorizontalPadding(10);\n        TableConfig.getInstance().columnTitleStyle = fontStyle;\n        table.setZoom(true, 2f, 0.4f);\n\n\n        tableListView = findViewById(R.id.lv_table_name);\n        tableListView.setAdapter(new DBListAdapter(getContext(), tableNames));\n        final List<String> finalStrings = tableNames;\n        final SQLiteDatabase finalSqLiteDatabase = sqLiteDatabase;\n        TitleBar titleBar = findViewById(R.id.title_bar);\n        titleBar.setOnTitleBarClickListener(new TitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onLeftClick() {\n                onBackPressed();\n            }\n\n            @Override\n            public void onRightClick() {\n\n            }\n        });\n        tableListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {\n            @Override\n            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n                String selectTableName = finalStrings.get(position);\n\n                String[][] data = DatabaseUtil.queryAll(finalSqLiteDatabase, finalStrings.get(position));\n                String[] titleName = DatabaseUtil.queryTableColumnName(finalSqLiteDatabase, selectTableName);\n                if (table.getTableData() != null) {\n                    table.getTableData().clear();\n                }\n                table.setTableData(ArrayTableData.create(selectTableName, titleName, data, new FastTextDrawFormat<String>()));\n                table.getMatrixHelper().reset();\n                tableListView.setVisibility(View.GONE);\n                table.setVisibility(View.VISIBLE);\n            }\n        });\n    }\n\n    @Override\n    public boolean onBackPressed() {\n        if (table.getVisibility() == View.VISIBLE) {\n            table.setVisibility(View.GONE);\n            tableListView.setVisibility(View.VISIBLE);\n        } else {\n            finish();\n        }\n        return true;\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/FileExplorerChooseDialog.java",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer;\n\n\nimport android.view.View;\n\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.widget.dialog.DialogListener;\nimport com.didichuxing.doraemonkit.widget.dialog.DialogProvider;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter;\n\nimport java.io.File;\n\n/**\n * Created by wanglikun on 2019/4/16\n */\npublic class FileExplorerChooseDialog extends DialogProvider<File> {\n    private RecyclerView mChooseList;\n    private SettingItemAdapter mAdapter;\n\n    public FileExplorerChooseDialog(File data, DialogListener listener) {\n        super(data, listener);\n    }\n\n    @Override\n    public int getLayoutId() {\n        return R.layout.dk_dialog_file_explorer_choose;\n    }\n\n    @Override\n    protected void findViews(View view) {\n        mChooseList = view.findViewById(R.id.choose_list);\n        mAdapter = new SettingItemAdapter(getContext());\n        mChooseList.setAdapter(mAdapter);\n        mChooseList.setLayoutManager(new LinearLayoutManager(getContext()));\n    }\n\n    @Override\n    protected void bindData(final File file) {\n        if (file.isFile()) {\n            mAdapter.append(new SettingItem(R.string.dk_share));\n        }\n        mAdapter.append(new SettingItem(R.string.dk_delete));\n        mAdapter.setOnSettingItemClickListener(new SettingItemAdapter.OnSettingItemClickListener() {\n            @Override\n            public void onSettingItemClick(View view, SettingItem data) {\n                if (data.desc == R.string.dk_delete) {\n                    if (onButtonClickListener != null) {\n                        onButtonClickListener.onDeleteClick(FileExplorerChooseDialog.this);\n                    }\n                } else if (data.desc == R.string.dk_share) {\n                    if (onButtonClickListener != null) {\n                        onButtonClickListener.onShareClick(FileExplorerChooseDialog.this);\n                    }\n                }\n            }\n        });\n    }\n\n    private OnButtonClickListener onButtonClickListener;\n\n    public void setOnButtonClickListener(OnButtonClickListener onButtonClickListener) {\n        this.onButtonClickListener = onButtonClickListener;\n    }\n\n    public interface OnButtonClickListener {\n        void onDeleteClick(FileExplorerChooseDialog dialog);\n\n        void onShareClick(FileExplorerChooseDialog dialog);\n    }\n\n    @Override\n    public boolean isCancellable() {\n        return true;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/FileExplorerFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.constant.BundleKey;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.titlebar.TitleBar;\nimport com.didichuxing.doraemonkit.util.DoKitFileUtil;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * Created by zhangweida on 2018/6/26.\n */\n\npublic class FileExplorerFragment extends BaseFragment {\n    private static final String TAG = \"FileExplorerFragment\";\n    private FileInfoAdapter mFileInfoAdapter;\n    private RecyclerView mFileList;\n    private TitleBar mTitleBar;\n    private File mCurDir;\n\n    private void initFileInfoList() {\n        mTitleBar = findViewById(R.id.title_bar);\n        mTitleBar.setOnTitleBarClickListener(new TitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onLeftClick() {\n                onBackPressed();\n            }\n\n            @Override\n            public void onRightClick() {\n\n            }\n        });\n        mFileList = findViewById(R.id.file_list);\n        mFileList.setLayoutManager(new LinearLayoutManager(getContext()));\n        mFileInfoAdapter = new FileInfoAdapter(getContext());\n        mFileInfoAdapter.setOnViewClickListener(new FileInfoAdapter.OnViewClickListener() {\n            @Override\n            public void onViewClick(View v, FileInfo fileInfo) {\n                if (fileInfo.file.isFile()) {\n                    Bundle bundle = new Bundle();\n                    bundle.putSerializable(BundleKey.FILE_KEY, fileInfo.file);\n                    if (DoKitFileUtil.isImage(fileInfo.file)) {\n                        showContent(ImageDetailFragment.class, bundle);\n                    } else if (DoKitFileUtil.isDB(fileInfo.file)) {\n                        showContent(DatabaseDetailFragment.class, bundle);\n                    } else if (DoKitFileUtil.isVideo(fileInfo.file)) {\n                        showContent(VideoPlayFragment.class, bundle);\n                    } else if (DoKitFileUtil.isSp(fileInfo.file)) {\n                        showContent(SpFragment.class, bundle);\n                    } else {\n                        showContent(TextDetailFragment.class, bundle);\n                    }\n                } else {\n                    mCurDir = fileInfo.file;\n                    mTitleBar.setTitle(mCurDir.getName());\n                    setAdapterData(getFileInfos(mCurDir));\n                }\n            }\n        });\n        mFileInfoAdapter.setOnViewLongClickListener(new FileInfoAdapter.OnViewLongClickListener() {\n            @Override\n            public boolean onViewLongClick(View v, final FileInfo fileInfo) {\n\n                FileExplorerChooseDialog dialog = new FileExplorerChooseDialog(fileInfo.file, null);\n                dialog.setOnButtonClickListener(new FileExplorerChooseDialog.OnButtonClickListener() {\n                    @Override\n                    public void onDeleteClick(FileExplorerChooseDialog dialog) {\n                        DoKitFileUtil.deleteDirectory(fileInfo.file);\n                        dialog.dismiss();\n\n                        if (mCurDir != null) {\n                            mTitleBar.setTitle(mCurDir.getName());\n                            setAdapterData(getFileInfos(mCurDir));\n                        }\n\n                    }\n\n                    @Override\n                    public void onShareClick(FileExplorerChooseDialog dialog) {\n                        DoKitFileUtil.systemShare(getContext(), fileInfo.file);\n                        dialog.dismiss();\n\n                    }\n                });\n                showDialog(dialog);\n                return true;\n            }\n        });\n        setAdapterData(initRootFileInfos(getContext()));\n        mFileList.setAdapter(mFileInfoAdapter);\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        mCurDir = null;\n        initFileInfoList();\n    }\n\n    private List<FileInfo> getFileInfos(File dir) {\n        List<FileInfo> fileInfos = new ArrayList<>();\n        if (dir.listFiles() == null) {\n            return fileInfos;\n        }\n        for (File file : dir.listFiles()) {\n            FileInfo fileInfo = new FileInfo(file);\n            fileInfos.add(fileInfo);\n        }\n        return fileInfos;\n    }\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_file_explorer;\n    }\n\n    @Override\n    public boolean onBackPressed() {\n        if (mCurDir == null) {\n            finish();\n            return true;\n        }\n        if (isRootFile(getContext(), mCurDir)) {\n            mTitleBar.setTitle(R.string.dk_kit_file_explorer);\n            setAdapterData(initRootFileInfos(getContext()));\n            mCurDir = null;\n            return true;\n        } else {\n            mCurDir = mCurDir.getParentFile();\n            mTitleBar.setTitle(mCurDir.getName());\n            List<FileInfo> fileInfos = getFileInfos(mCurDir);\n            setAdapterData(fileInfos);\n            return true;\n        }\n    }\n\n    private void setAdapterData(List<FileInfo> fileInfos) {\n        if (fileInfos.isEmpty()) {\n            mFileInfoAdapter.clear();\n        } else {\n            mFileInfoAdapter.setData(fileInfos);\n        }\n    }\n\n    private List<FileInfo> initRootFileInfos(Context context) {\n        List<File> rootFiles = getRootFiles();\n        if (rootFiles != null) {\n            List<FileInfo> fileInfos = new ArrayList<>();\n            for (File file : rootFiles) {\n                fileInfos.add(new FileInfo(file));\n            }\n            return fileInfos;\n        }\n        return initDefaultRootFileInfos(context);\n    }\n\n    private List<File> getRootFiles() {\n        if (getArguments() != null) {\n            File dir = (File) getArguments().getSerializable(BundleKey.DIR_KEY);\n            if (dir != null && dir.exists()) {\n                return Arrays.asList(dir.listFiles());\n            }\n        }\n        return null;\n    }\n\n    private List<FileInfo> initDefaultRootFileInfos(Context context) {\n        List<FileInfo> fileInfos = new ArrayList<>();\n        fileInfos.add(new FileInfo(context.getFilesDir().getParentFile()));\n        fileInfos.add(new FileInfo(context.getExternalCacheDir()));\n        fileInfos.add(new FileInfo(context.getExternalFilesDir(null)));\n        return fileInfos;\n    }\n\n    private boolean isRootFile(Context context, File file) {\n        if (file == null) {\n            return false;\n        }\n        List<File> rootFiles = getRootFiles();\n        if (rootFiles != null) {\n            for (File rootFile : rootFiles) {\n                return file.equals(rootFile);\n            }\n        }\n        return file.equals(context.getExternalCacheDir())\n                || file.equals(context.getExternalFilesDir(null))\n                || file.equals(context.getFilesDir().getParentFile());\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/FileExplorerKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n/**\n * Created by zhangweida on 2018/6/26.\n */\n@AutoService(AbstractKit::class)\nclass FileExplorerKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_file_explorer\n    override val icon: Int\n        get() = R.mipmap.dk_file_explorer\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        startUniversalActivity(FileExplorerFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_comm_ck_sandbox\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/FileInfo.java",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer;\n\nimport java.io.File;\n\n/**\n * Created by wanglikun on 2018/10/16.\n */\n\npublic class FileInfo {\n\n    public final File file;\n\n    public FileInfo(File file) {\n        this.file = file;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/FileInfoAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\nimport com.didichuxing.doraemonkit.util.DoKitFileUtil;\n\n/**\n * Created by wanglikun on 2018/10/16.\n */\n\npublic class FileInfoAdapter extends AbsRecyclerAdapter<AbsViewBinder<FileInfo>, FileInfo> {\n    private OnViewClickListener mOnViewClickListener;\n    private OnViewLongClickListener mOnViewLongClickListener;\n\n    public FileInfoAdapter(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected AbsViewBinder<FileInfo> createViewHolder(View view, int viewType) {\n        return new FileInfoViewHolder(view);\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        return inflater.inflate(R.layout.dk_item_file_info, parent, false);\n    }\n\n    public class FileInfoViewHolder extends AbsViewBinder<FileInfo> {\n        private TextView mName;\n        private ImageView mIcon;\n        private ImageView mMoreBtn;\n\n        public FileInfoViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            mName = getView(R.id.name);\n            mIcon = getView(R.id.icon);\n            mMoreBtn = getView(R.id.more);\n        }\n\n        @Override\n        public void bind(final FileInfo fileInfo) {\n            getView().setOnLongClickListener(new View.OnLongClickListener() {\n                @Override\n                public boolean onLongClick(View v) {\n                    return mOnViewLongClickListener != null && mOnViewLongClickListener.onViewLongClick(v, fileInfo);\n                }\n            });\n            mName.setText(fileInfo.file.getName());\n            if (fileInfo.file.isDirectory()) {\n                mIcon.setImageResource(R.mipmap.dk_dir_icon);\n                mMoreBtn.setVisibility(View.VISIBLE);\n            } else {\n                if (DoKitFileUtil.getSuffix(fileInfo.file).equals(DoKitFileUtil.JPG)) {\n                    mIcon.setImageResource(R.mipmap.dk_jpg_icon);\n                } else if (DoKitFileUtil.getSuffix(fileInfo.file).equals(DoKitFileUtil.TXT)) {\n                    mIcon.setImageResource(R.mipmap.dk_txt_icon);\n                } else if (DoKitFileUtil.getSuffix(fileInfo.file).equals(DoKitFileUtil.DB)){\n                    mIcon.setImageResource(R.mipmap.dk_file_db);\n                }else {\n                    mIcon.setImageResource(R.mipmap.dk_file_icon);\n                }\n                mMoreBtn.setVisibility(View.GONE);\n            }\n        }\n\n        @Override\n        protected void onViewClick(View view, FileInfo data) {\n            super.onViewClick(view, data);\n            if (mOnViewClickListener != null) {\n                mOnViewClickListener.onViewClick(view, data);\n            }\n        }\n    }\n\n    public void setOnViewClickListener(OnViewClickListener onViewClickListener) {\n        mOnViewClickListener = onViewClickListener;\n    }\n\n    public void setOnViewLongClickListener(OnViewLongClickListener onViewLongClickListener) {\n        mOnViewLongClickListener = onViewLongClickListener;\n    }\n\n    public interface OnViewClickListener {\n        void onViewClick(View v, FileInfo fileInfo);\n    }\n\n    public interface OnViewLongClickListener {\n        boolean onViewLongClick(View v, FileInfo fileInfo);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/ImageDetailFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer;\n\nimport android.graphics.Bitmap;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.view.View;\nimport android.widget.ImageView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.constant.BundleKey;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.util.DoKitImageUtil;\n\nimport java.io.File;\nimport java.lang.ref.WeakReference;\n\n/**\n * Created by wanglikun on 2018/10/30.\n */\n\npublic class ImageDetailFragment extends BaseFragment {\n    private static final String TAG = \"ImageDetailFragment\";\n    private ImageView mImageView;\n    private File mFile;\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        mImageView = findViewById(R.id.image);\n        Bundle data = getArguments();\n        if (data != null) {\n            mFile = (File) data.getSerializable(BundleKey.FILE_KEY);\n        }\n        readImage(mFile);\n    }\n\n    private void readImage(File file) {\n        if (file == null) {\n            return;\n        }\n        ImageReadTask task = new ImageReadTask(this);\n        task.execute(file);\n    }\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_image_detail;\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mImageView.setImageBitmap(null);\n    }\n\n    private static class ImageReadTask extends AsyncTask<File, Void, Bitmap> {\n        private WeakReference<ImageDetailFragment> mReference;\n\n        public ImageReadTask(ImageDetailFragment fragment) {\n            mReference = new WeakReference<>(fragment);\n        }\n\n        @Override\n        protected Bitmap doInBackground(File... files) {\n            return DoKitImageUtil.decodeSampledBitmapFromFilePath(files[0].getPath(), 1080, 1920);\n        }\n\n        @Override\n        protected void onPostExecute(Bitmap bitmap) {\n            super.onPostExecute(bitmap);\n            if (mReference.get() != null) {\n                mReference.get().mImageView.setImageBitmap(bitmap);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/SpAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.constant.SpInputType;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\n\npublic class SpAdapter extends AbsRecyclerAdapter<AbsViewBinder<SpBean>, SpBean> {\n\n    public SpAdapter(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected AbsViewBinder<SpBean> createViewHolder(View view, int viewType) {\n        return new ViewHolder(view);\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        return inflater.inflate(R.layout.dk_item_sp_input, parent, false);\n    }\n\n    private class ViewHolder extends AbsViewBinder<SpBean> {\n\n        private TextView key;\n        private TextView type;\n        private SpInputView inputView;\n\n        public ViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            key = getView(R.id.tv_sp_key);\n            type = getView(R.id.tv_sp_type);\n            inputView = getView(R.id.input_sp_value);\n\n        }\n\n        @Override\n        public void bind(final SpBean spBean) {\n            if (!spBean.value.getClass().getSimpleName().equals(SpInputType.HASHSET)) {\n                key.setText(spBean.key);\n                type.setText(spBean.value.getClass().getSimpleName());\n                inputView.setInput(spBean, new SpInputView.OnDataChangeListener() {\n                    @Override\n                    public void onDataChanged() {\n                        inputView.refresh();\n                        if (onSpDataChangerListener != null) {\n                            onSpDataChangerListener.onDataChanged(spBean);\n                        }\n                    }\n                });\n            }\n        }\n    }\n\n    private OnSpDataChangerListener onSpDataChangerListener;\n\n    public void setOnSpDataChangerListener(OnSpDataChangerListener onSpDataChangerListener) {\n        this.onSpDataChangerListener = onSpDataChangerListener;\n    }\n\n    public interface OnSpDataChangerListener {\n        void onDataChanged(SpBean spBean);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/SpBean.java",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer;\n\nimport com.didichuxing.doraemonkit.constant.SpInputType;\n\npublic class SpBean {\n    public String key;\n    public Object value;\n    public Class clazz;\n\n    private SpBean() {\n\n    }\n\n    public SpBean(String key, Object value) {\n        this.key = key;\n        this.value = value;\n        clazz = value.getClass();\n    }\n\n    public Object toDefaultClass(String string) {\n        setDefaultClass(string);\n        return value;\n    }\n\n    private void setDefaultClass(String string) {\n        switch (clazz.getSimpleName()) {\n            case SpInputType.FLOAT:\n                value = Float.valueOf(string);\n                break;\n            case SpInputType.INTEGER:\n                value = Integer.valueOf(string);\n                break;\n            case SpInputType.STRING:\n                value = String.valueOf(string);\n                break;\n            case SpInputType.LONG:\n                value = Long.valueOf(string);\n                break;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/SpFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer;\n\nimport android.content.SharedPreferences;\nimport android.os.Bundle;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.DividerItemDecoration;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.constant.BundleKey;\nimport com.didichuxing.doraemonkit.constant.SpInputType;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.titlebar.TitleBar;\nimport com.didichuxing.doraemonkit.util.DoKitSPUtil;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport static com.didichuxing.doraemonkit.util.DoKitFileUtil.XML;\n\npublic class SpFragment extends BaseFragment {\n    private SharedPreferences.Editor edit;\n    private String spTableName;\n\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_sp_show;\n    }\n\n    private List<SpBean> getSpBeans() {\n        ArrayList<SpBean> spBeans = new ArrayList<>();\n\n        File mFile = (File) getArguments().getSerializable(BundleKey.FILE_KEY);\n        if (mFile == null) {\n            return spBeans;\n        }\n        spTableName = mFile.getName().replace(XML, \"\");\n        SharedPreferences sp = DoKitSPUtil.getSharedPrefs(spTableName);\n        edit = sp.edit();\n        Map<String, ?> all = sp.getAll();\n        if (all.isEmpty()) {\n            return spBeans;\n        }\n        for (Map.Entry<String, ?> entry : all.entrySet()) {\n            spBeans.add(new SpBean(entry.getKey(), entry.getValue()));\n        }\n        return spBeans;\n\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        List<SpBean> spBeans = getSpBeans();\n        if (spBeans.isEmpty()) {\n            finish();\n            return;\n        }\n        RecyclerView recyclerView = findViewById(R.id.rv_sp);\n        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));\n        recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));\n        SpAdapter spAdapter = new SpAdapter(getActivity());\n        spAdapter.setOnSpDataChangerListener(new SpAdapter.OnSpDataChangerListener() {\n            @Override\n            public void onDataChanged(SpBean bean) {\n                spUpData(bean);\n            }\n        });\n        spAdapter.append(spBeans);\n        recyclerView.setAdapter(spAdapter);\n        if (spTableName != null) {\n            TitleBar mTitleBar = findViewById(R.id.title_bar);\n            mTitleBar.setTitle(spTableName);\n            mTitleBar.setOnTitleBarClickListener(new TitleBar.OnTitleBarClickListener() {\n                @Override\n                public void onLeftClick() {\n                    finish();\n                }\n\n                @Override\n                public void onRightClick() {\n\n                }\n            });\n        }\n\n    }\n\n\n    public void spUpData(SpBean bean) {\n        String key = bean.key;\n        switch (bean.value.getClass().getSimpleName()) {\n            case SpInputType.STRING:\n                DoKitSPUtil.putString(key, bean.value.toString());\n                break;\n            case SpInputType.BOOLEAN:\n                DoKitSPUtil.putBoolean(spTableName, key, (Boolean) bean.value);\n                break;\n            case SpInputType.INTEGER:\n                DoKitSPUtil.putInt(spTableName, key, (Integer) bean.value);\n                break;\n            case SpInputType.FLOAT:\n                DoKitSPUtil.putFloat(spTableName, key, (Float) bean.value);\n                break;\n            case SpInputType.LONG:\n                DoKitSPUtil.putLong(spTableName, key, (Long) bean.value);\n                break;\n            default:\n                break;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/SpInputView.java",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer;\n\nimport android.content.Context;\nimport androidx.annotation.Nullable;\nimport android.text.InputType;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.CompoundButton;\nimport android.widget.FrameLayout;\nimport android.widget.Switch;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.constant.SpInputType;\nimport com.didichuxing.doraemonkit.widget.bottomview.BottomUpWindow;\nimport com.didichuxing.doraemonkit.widget.bottomview.EditSpInputView;\n\npublic class SpInputView extends FrameLayout {\n\n    private OnDataChangeListener onDataChangeListener;\n\n    private static final int FLOAT = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL;\n    private static final int INTEGER = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED;\n    private static final int STRING = InputType.TYPE_CLASS_TEXT;\n\n    private TextView spValue;\n    private Switch switchBtn;\n    private SpBean bean;\n\n\n    public SpInputView(Context context) {\n        super(context, null);\n    }\n\n    public SpInputView(Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        init();\n\n    }\n\n    public SpInputView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init();\n\n    }\n\n    private void init() {\n        View inflate = LayoutInflater.from(getContext()).inflate(R.layout.kd_item_sp_input, this, true);\n        switchBtn = inflate.findViewById(R.id.switch_btn);\n        spValue = inflate.findViewById(R.id.tv_sp_value);\n\n    }\n\n    public void setInput(final SpBean bean, final OnDataChangeListener onDataChangeListener) {\n        this.bean = bean;\n        this.onDataChangeListener = onDataChangeListener;\n        switch (bean.value.getClass().getSimpleName()) {\n            case SpInputType.BOOLEAN:\n                switchBtn.setChecked((Boolean) bean.value);\n                switchBtn.setVisibility(VISIBLE);\n                switchBtn.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {\n                    @Override\n                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {\n                        bean.value = isChecked;\n                        onDataChangeListener.onDataChanged();\n                    }\n                });\n                spValue.setVisibility(GONE);\n                break;\n            case SpInputType.INTEGER:\n            case SpInputType.LONG:\n                initEdt(bean, INTEGER);\n                break;\n            case SpInputType.FLOAT:\n                initEdt(bean, FLOAT);\n                break;\n            case SpInputType.STRING:\n                initEdt(bean, STRING);\n                break;\n            default:\n                break;\n//            case HASHSET:\n//                break;\n\n        }\n    }\n\n    private void initEdt(final SpBean spBean, final int inputType) {\n        spValue.setVisibility(VISIBLE);\n        switchBtn.setVisibility(GONE);\n        spValue.setText(spBean.value.toString());\n        spValue.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                showInputView(v, spBean, inputType);\n            }\n        });\n    }\n\n    public void refresh() {\n        if (bean != null) {\n            spValue.setText(bean.value.toString());\n        }\n    }\n\n    private void showInputView(View view, final SpBean spBean, int inputType) {\n        new BottomUpWindow(getContext()).setContent(new EditSpInputView(getContext(), spBean, inputType))\n                .show(view).setOnSubmitListener(new BottomUpWindow.OnSubmitListener() {\n            @Override\n            public void submit(Object object) {\n                spBean.value = object;\n                if (onDataChangeListener != null) {\n                    onDataChangeListener.onDataChanged();\n                }\n            }\n\n            @Override\n            public void cancel() {\n\n            }\n        });\n    }\n\n    public interface OnDataChangeListener {\n        void onDataChanged();\n    }\n\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/TextContentAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\n\n/**\n * Created by wanglikun on 2018/12/14.\n */\n\npublic class TextContentAdapter extends AbsRecyclerAdapter<AbsViewBinder<String>, String> {\n\n    public TextContentAdapter(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected AbsViewBinder<String> createViewHolder(View view, int viewType) {\n        return new TextContentViewHolder(view);\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        return inflater.inflate(R.layout.dk_item_text_content, parent, false);\n    }\n\n    private class TextContentViewHolder extends AbsViewBinder<String> {\n        private TextView mTextView;\n\n        public TextContentViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            mTextView = getView(R.id.text);\n        }\n\n        @Override\n        public void bind(String s) {\n            mTextView.setText(s);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/TextDetailFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer;\n\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.constant.BundleKey;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.titlebar.TitleBar;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileReader;\nimport java.io.IOException;\nimport java.lang.ref.WeakReference;\n\n/**\n * Created by wanglikun on 2018/10/19.\n */\n\npublic class TextDetailFragment extends BaseFragment {\n    private static final String TAG = \"TextDetailFragment\";\n\n    private RecyclerView mContent;\n    private TextContentAdapter mContentAdapter;\n\n    private File mFile;\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        TitleBar titleBar = findViewById(R.id.title_bar);\n        titleBar.setOnTitleBarClickListener(new TitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onLeftClick() {\n                finish();\n            }\n\n            @Override\n            public void onRightClick() {\n\n            }\n        });\n        initContent();\n        Bundle data = getArguments();\n        if (data != null) {\n            mFile = (File) data.getSerializable(BundleKey.FILE_KEY);\n        }\n        readFile(mFile);\n    }\n\n    public void initContent() {\n        mContent = findViewById(R.id.text_list);\n        mContent.setLayoutManager(new LinearLayoutManager(getContext()));\n        mContentAdapter = new TextContentAdapter(getContext());\n        mContent.setAdapter(mContentAdapter);\n    }\n\n    private void readFile(File file) {\n        if (mFile == null) {\n            return;\n        }\n        FileReadTask task = new FileReadTask(this);\n        task.execute(file);\n    }\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_text_detail;\n    }\n\n    private static class FileReadTask extends AsyncTask<File, String, Void> {\n        private WeakReference<TextDetailFragment> mReference;\n\n        public FileReadTask(TextDetailFragment fragment) {\n            mReference = new WeakReference<>(fragment);\n        }\n\n        @Override\n        protected Void doInBackground(File... files) {\n            try {\n                FileReader fileReader = new FileReader(files[0]);\n                BufferedReader br = new BufferedReader(fileReader);\n                String textLine;\n                while ((textLine = br.readLine()) != null) {\n                    publishProgress(textLine);\n                }\n                br.close();\n                fileReader.close();\n            } catch (IOException e) {\n                LogHelper.e(TAG, e.toString());\n            }\n            return null;\n        }\n\n        @Override\n        protected void onProgressUpdate(String... values) {\n            super.onProgressUpdate(values);\n            if (mReference.get() != null) {\n                mReference.get().mContentAdapter.append(values[0]);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/fileexplorer/VideoPlayFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.fileexplorer;\n\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.constant.BundleKey;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.videoview.MyVideoView;\n\nimport java.io.File;\n\n/**\n * Created by wanglikun on 2019/4/16\n */\npublic class VideoPlayFragment extends BaseFragment {\n    private MyVideoView mVideoView;\n    private File mFile;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_video_play;\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        Bundle data = getArguments();\n        if (data != null) {\n            mFile = (File) data.getSerializable(BundleKey.FILE_KEY);\n        }\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        mVideoView = findViewById(R.id.video_view);\n        mVideoView.register(getActivity());\n        mVideoView.setVideoPath(mFile.getPath());\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        mVideoView.onPause();\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        mVideoView.onResume();\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/h5_help/DoKitJSI.kt",
    "content": "package com.didichuxing.doraemonkit.kit.h5_help\n\nimport android.webkit.JavascriptInterface\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.util.ActivityUtils\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport com.didichuxing.doraemonkit.okhttp_api.OkHttpWrap\nimport com.didichuxing.doraemonkit.kit.h5_help.bean.JsRequestBean\nimport com.didichuxing.doraemonkit.kit.h5_help.bean.StorageBean\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/8/25-15:21\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitJSI {\n\n\n    init {\n        JsHookDataManager.jsLocalStorage.clear()\n        JsHookDataManager.jsSessionStorage.clear()\n    }\n\n\n    @JavascriptInterface\n    fun fetch(\n        requestId: String?,\n        url: String?,\n        method: String?,\n        origin: String?,\n        headers: String?,\n        body: String?\n    ) {\n\n        var headerMap: MutableMap<String?, String?> = mutableMapOf()\n        headers?.let {\n            headerMap = GsonUtils.fromJson(headers, MutableMap::class.java) as MutableMap<String?, String?>\n        }\n\n        val httpUrl = OkHttpWrap.createHttpUrl(url)\n        val newUrl = if (httpUrl == null) {\n            origin + url\n        } else {\n            url\n        }\n\n//        LogHelper.i(\n//            \"DokitJSI\",\n//            \"requestId==>$requestId,url==>$url,method==>$method,origin==>$origin,headers==>$headerMap,body==>$body\"\n//        )\n        if (JsHookDataManager.jsRequestMap[requestId] == null) {\n            JsHookDataManager.jsRequestMap[requestId] =\n                JsRequestBean(requestId, newUrl, method, headerMap, null, body)\n        } else {\n            JsHookDataManager.jsRequestMap[requestId]?.apply {\n                this.requestId = requestId\n                this.url = newUrl\n                this.method = method\n                this.headers = headerMap\n                this.mimeType = null\n                this.body = body\n            }\n        }\n    }\n\n    /**\n     * wiki:https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/open\n     */\n    @JavascriptInterface\n    fun open(\n        requestId: String?,\n        url: String?,\n        method: String?,\n        origin: String?\n    ) {\n        val httpUrl = OkHttpWrap.createHttpUrl(url)\n        val newUrl = if (httpUrl == null) {\n            origin + url\n        } else {\n            url\n        }\n\n        if (JsHookDataManager.jsRequestMap[requestId] == null) {\n            JsHookDataManager.jsRequestMap[requestId] =\n                JsRequestBean(requestId, newUrl, method, null, null, null)\n        } else {\n            JsHookDataManager.jsRequestMap[requestId]?.apply {\n                this.requestId = requestId\n                this.url = newUrl\n                this.method = method\n            }\n        }\n\n    }\n\n    @JavascriptInterface\n    fun setRequestHeader(requestId: String?, key: String?, value: String?) {\n        JsHookDataManager.jsRequestMap[requestId]?.apply {\n            if (this.headers == null) {\n                this.headers = mutableMapOf()\n                this.headers!![key] = value\n            } else {\n                this.headers!![key] = value\n            }\n        }\n\n    }\n\n\n    @JavascriptInterface\n    fun overrideMimeType(requestId: String?, mimeType: String?) {\n        JsHookDataManager.jsRequestMap[requestId]?.apply {\n            this.mimeType = mimeType\n        }\n\n    }\n\n    @JavascriptInterface\n    fun send(requestId: String?, body: String?) {\n        JsHookDataManager.jsRequestMap[requestId]?.apply {\n            this.body = body\n        }\n    }\n\n    /**\n     * localStorage\n     */\n    @JavascriptInterface\n    fun localStorageSetItem(key: String?, value: String?) {\n        JsHookDataManager.jsLocalStorage.forEach {\n            if (it.key == key) {\n                it.value = value\n                updateStorageAdapter(H5DoKitView.STORAGE_TYPE_LOCAL)\n                return\n            }\n        }\n        JsHookDataManager.jsLocalStorage.add(StorageBean(key, value))\n        updateStorageAdapter(H5DoKitView.STORAGE_TYPE_LOCAL)\n\n    }\n\n\n    /**\n     * localStorage\n     */\n    @JavascriptInterface\n    fun localStorageRemoveItem(key: String?) {\n        var index = -1\n        JsHookDataManager.jsLocalStorage.forEachIndexed { innerIndex, localStorageBean ->\n            if (localStorageBean.key == key) {\n                index = innerIndex\n                return@forEachIndexed\n            }\n        }\n        if (index != -1) {\n            JsHookDataManager.jsLocalStorage.removeAt(index)\n        }\n        updateStorageAdapter(H5DoKitView.STORAGE_TYPE_LOCAL)\n    }\n\n\n    /**\n     * localStorage\n     */\n    @JavascriptInterface\n    fun localStorageClear() {\n        JsHookDataManager.jsLocalStorage.clear()\n        updateStorageAdapter(H5DoKitView.STORAGE_TYPE_LOCAL)\n    }\n\n\n    /**\n     * sessionStorage\n     */\n    @JavascriptInterface\n    fun sessionStorageSetItem(key: String?, value: String?) {\n        JsHookDataManager.jsSessionStorage.forEach {\n            if (it.key == key) {\n                it.value = value\n                updateStorageAdapter(H5DoKitView.STORAGE_TYPE_Session)\n                return\n            }\n        }\n        JsHookDataManager.jsSessionStorage.add(StorageBean(key, value))\n        updateStorageAdapter(H5DoKitView.STORAGE_TYPE_Session)\n\n    }\n\n\n    /**\n     * sessionStorage\n     */\n    @JavascriptInterface\n    fun sessionStorageRemoveItem(key: String?) {\n        var index = -1\n        JsHookDataManager.jsSessionStorage.forEachIndexed { innerIndex, sessionStorageBean ->\n            if (sessionStorageBean.key == key) {\n                index = innerIndex\n                return@forEachIndexed\n            }\n        }\n        if (index != -1) {\n            JsHookDataManager.jsSessionStorage.removeAt(index)\n        }\n        updateStorageAdapter(H5DoKitView.STORAGE_TYPE_Session)\n    }\n\n\n    /**\n     * sessionStorage\n     */\n    @JavascriptInterface\n    fun sessionStorageClear() {\n        JsHookDataManager.jsSessionStorage.clear()\n        updateStorageAdapter(H5DoKitView.STORAGE_TYPE_Session)\n    }\n\n    /**\n     * 更新本地localStorage的adapter\n     */\n    private fun updateStorageAdapter(type: Int) {\n        DoKit.getDoKitView<H5DoKitView>(\n            ActivityUtils.getTopActivity(),\n            H5DoKitView::class\n        )?.updateAdapter(type)\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/h5_help/DoKitWebViewClient.kt",
    "content": "package com.didichuxing.doraemonkit.kit.h5_help\n\nimport android.app.Activity\nimport android.os.Build\nimport android.webkit.*\nimport androidx.annotation.RequiresApi\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.DoKitReal\nimport com.didichuxing.doraemonkit.util.ConvertUtils\nimport com.didichuxing.doraemonkit.util.ResourceUtils\nimport com.didichuxing.doraemonkit.okhttp_api.OkHttpWrap\nimport com.didichuxing.doraemonkit.aop.urlconnection.OkhttpClientUtil\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.h5_help.bean.JsRequestBean\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport okhttp3.*\nimport org.jsoup.Jsoup\nimport java.net.URLDecoder\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/8/28-17:22\n * 描    述：切面dokit\n * 修订历史：\n * ================================================\n */\nclass DoKitWebViewClient(webViewClient: WebViewClient, userAgent: String) : ProxyWebViewClient(webViewClient) {\n\n    private val TAG = \"DoKitWebViewClient\"\n    private val mUserAgent = userAgent\n\n    /**\n     * 更新悬浮窗上的链接\n     */\n    private fun updateH5DokitUrl(view: WebView?, url: String?) {\n        view?.let { it ->\n            if (it.context is Activity) {\n                val activity = it.context as Activity\n                val absDokitView: AbsDoKitView? = DoKit.getDoKitView<H5DoKitView>(activity, H5DoKitView::class)\n                absDokitView?.let { h5DokitView ->\n                    (h5DokitView as H5DoKitView).updateUrl(url)\n                }\n\n            }\n        }\n    }\n\n    override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {\n        updateH5DokitUrl(view, url)\n        return super.shouldOverrideUrlLoading(view, url)\n    }\n\n    @RequiresApi(Build.VERSION_CODES.N)\n    override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {\n        updateH5DokitUrl(view, request?.url?.path)\n        return super.shouldOverrideUrlLoading(view, request)\n    }\n\n    override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {\n        updateH5DokitUrl(view, url)\n        super.doUpdateVisitedHistory(view, url, isReload)\n    }\n\n    /**\n     * https://developer.android.google.cn/reference/android/webkit/WebViewClient.html#shouldInterceptRequest(android.webkit.WebView,%20android.webkit.WebResourceRequest)\n     */\n    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n    override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {\n        //开关均被关闭则不进行拦截\n        if (!DoKitManager.H5_JS_INJECT && !DoKitManager.H5_VCONSOLE_INJECT && !DoKitManager.H5_DOKIT_MC_INJECT) {\n            return super.shouldInterceptRequest(view, request)\n        }\n        request?.let { webRequest ->\n            //加载页面资源\n            if (webRequest.isForMainFrame) {\n                LogHelper.i(\n                    TAG,\n                    \"url===>${webRequest.url?.toString()}  method==>${webRequest.method} thread==>${Thread.currentThread().name}\"\n                )\n                val httpUrl = OkHttpWrap.createHttpUrl(webRequest.url?.toString())\n\n                val url = if (OkHttpWrap.toUrl(httpUrl)?.query.isNullOrBlank()) {\n                    webRequest.url?.toString() + \"?dokit_flag=web\"\n                } else {\n                    webRequest.url?.toString() + \"&dokit_flag=web\"\n                }\n                val httpRequest = Request.Builder()\n                    .header(\"User-Agent\", mUserAgent)\n                    .url(url)\n                    .build()\n                val response = OkhttpClientUtil.okhttpClient.newCall(httpRequest).execute()\n\n                //注入本地网络拦截js\n                var newHtml = if (DoKitManager.H5_JS_INJECT) {\n                    injectJsHook(OkHttpWrap.toResponseBody(response)?.string())\n                } else {\n                    OkHttpWrap.toResponseBody(response)?.string()\n                }\n                //注入vConsole的代码\n                if (DoKitManager.H5_VCONSOLE_INJECT) {\n                    newHtml = injectVConsoleHook(newHtml)\n                }\n\n                //注入Dokit js mc代码\n                if (DoKitManager.H5_DOKIT_MC_INJECT) {\n                    newHtml = injectDokitMcHook(newHtml)\n                }\n\n                return WebResourceResponse(\n                    \"text/html\",\n                    response.header(\"content-encoding\", \"utf-8\"),\n                    ConvertUtils.string2InputStream(newHtml, \"utf-8\")\n                )\n            } else {\n                //加载js网络请求\n                if (webRequest.url.toString().contains(\"dokit_flag\")) {\n                    val jsRequestId = getUrlQuery(webRequest.url.toString(), \"dokit_flag\")\n                    val jsRequestBean = JsHookDataManager.jsRequestMap[jsRequestId]\n                    LogHelper.i(TAG, jsRequestBean.toString())\n                    jsRequestBean?.let { requestBean ->\n                        val url = OkHttpWrap.createHttpUrl(requestBean.url)\n                        val host = OkHttpWrap.toRequestHost(url)\n                        //如果是dokit mock host 则不进行拦截\n                        if (host.equals(NetworkManager.MOCK_HOST, true)) {\n                            JsHookDataManager.jsRequestMap.remove(requestBean.requestId)\n                            return null\n                        }\n\n                        // web 数据mock\n                        return dealMock(requestBean, url, view, request)\n                    }\n\n                } else {\n                    return super.shouldInterceptRequest(view, request)\n                }\n\n            }\n        }\n\n        return super.shouldInterceptRequest(view, request)\n    }\n\n    /**\n     * 处理数据mock的相关逻辑\n     */\n    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n    private fun dealMock(requestBean: JsRequestBean, url: HttpUrl?, view: WebView?, request: WebResourceRequest?): WebResourceResponse? {\n        url?.let { httpUrl ->\n            try {\n                val path = URLDecoder.decode(OkHttpWrap.toEncodedPath(httpUrl), \"utf-8\")\n                val queries = OkHttpWrap.toHttpQuery(httpUrl)\n                val jsonQuery = JsHttpUtil.transformQuery(queries)\n                val jsonRequestBody = JsHttpUtil.transformRequestBody(\n                    requestBean.method,\n                    requestBean.body,\n                    requestBean.headers\n                )\n\n                val interceptMatchedId =\n                    DokitDbManager.getInstance().isMockMatched(\n                        path,\n                        jsonQuery,\n                        jsonRequestBody,\n                        DokitDbManager.MOCK_API_INTERCEPT,\n                        DokitDbManager.FROM_SDK_OTHER\n                    )\n\n                val templateMatchedId =\n                    DokitDbManager.getInstance().isMockMatched(\n                        path,\n                        jsonQuery,\n                        jsonRequestBody,\n                        DokitDbManager.MOCK_API_TEMPLATE,\n                        DokitDbManager.FROM_SDK_OTHER\n                    )\n\n                //如果interceptMatchedId和templateMatchedId都为null 直接不进行操作\n                if (interceptMatchedId.isNullOrBlank() && templateMatchedId.isNullOrBlank()) {\n                    //web 抓包\n                    if (NetworkManager.isActive()) {\n                        try {\n                            //构建okhttp用来抓包\n                            val newRequest: Request =\n                                JsHttpUtil.createOkHttpRequest(requestBean, mUserAgent)\n                            if (JsHttpUtil.matchWhiteHost(newRequest)) {\n                                //发送模拟请求\n                                OkhttpClientUtil.okhttpClient.newCall(newRequest).execute()\n                            }\n                        } catch (e: Exception) {\n                            e.printStackTrace()\n                        }\n                    }\n                    return super.shouldInterceptRequest(view, request)\n                }\n\n                val newRequest: Request =\n                    JsHttpUtil.createOkHttpRequest(requestBean, mUserAgent)\n                //发送模拟请求\n                val newResponse =\n                    OkhttpClientUtil.okhttpClient.newCall(newRequest).execute()\n                //是否命中拦截规则\n                if (!interceptMatchedId.isNullOrBlank()) {\n                    JsHookDataManager.jsRequestMap.remove(requestBean.requestId)\n                    return JsHttpUtil.matchedNormalInterceptRule(\n                        httpUrl,\n                        path,\n                        interceptMatchedId,\n                        templateMatchedId,\n                        newRequest,\n                        newResponse,\n                        OkhttpClientUtil.okhttpClient\n                    )\n                }\n\n                //是否命中模板规则\n                if (!templateMatchedId.isNullOrBlank()) {\n                    JsHttpUtil.matchedTemplateRule(\n                        newResponse,\n                        path,\n                        templateMatchedId\n                    )\n                }\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n        JsHookDataManager.jsRequestMap.remove(requestBean.requestId)\n\n        return super.shouldInterceptRequest(view, request)\n    }\n\n\n    private fun getUrlQuery(url: String, key: String): String? {\n        val httpUrl = OkHttpWrap.createHttpUrl(url)\n\n        val queries = OkHttpWrap.toUrl(httpUrl)?.query?.split(\"&\")\n        val queryMap = mutableMapOf<String, String>()\n\n        queries?.forEach {\n            val keyAndValue = it.split(\"=\")\n            queryMap[keyAndValue[0]] = keyAndValue[1]\n        }\n\n        return queryMap[key]\n\n    }\n\n    /**\n     * 注入hook js 哇共诺请求的代码\n     */\n    private fun injectJsHook(html: String?): String {\n        //读取本地js hook 代码\n        val jsHook = ResourceUtils.readAssets2String(\"h5help/dokit_js_hook.html\")\n        val doc = Jsoup.parse(html)\n        doc.outputSettings().prettyPrint(true)\n        val elements = doc.getElementsByTag(\"head\")\n        if (elements.size > 0) {\n            elements[0].prepend(jsHook)\n        }\n        return doc.toString()\n    }\n\n    /**\n     * 注入 vConsole的代码\n     */\n    private fun injectVConsoleHook(html: String?): String {\n        //读取本地js hook 代码\n        val vConsoleHook = ResourceUtils.readAssets2String(\"h5help/dokit_js_vconsole_hook.html\")\n        val doc = Jsoup.parse(html)\n        doc.outputSettings().prettyPrint(true)\n        val elements = doc.getElementsByTag(\"head\")\n        if (elements.size > 0) {\n            elements[elements.size - 1].append(vConsoleHook)\n        }\n        return doc.toString()\n    }\n\n    /**\n     * 注入 vConsole的代码\n     */\n    private fun injectDokitMcHook(html: String?): String {\n        //读取本地js hook 代码\n        var dokitjs = ResourceUtils.readAssets2String(\"h5help/dokit.js\")\n\n        if (!\"file\".equals(DoKitManager.H5_MC_JS_INJECT_MODE)) {\n            val httpRequest = Request.Builder()\n                .header(\"User-Agent\", mUserAgent)\n                .url(DoKitManager.H5_MC_JS_INJECT_URL)\n                .build()\n            val response = OkhttpClientUtil.okhttpClient.newCall(httpRequest).execute()\n            dokitjs = response.body()?.string() ?: dokitjs\n        }\n\n        val mcUrl = DoKitManager.MC_CONNECT_URL\n        val productId = DoKitManager.PRODUCT_ID\n        val mode = DoKitReal.getMode()\n\n        val injectHook = \"<script type=\\\"text/javascript\\\">\\n ${dokitjs}\\n\" +\n            \" window.Dokit.setProductId('${productId}')\\n\" +\n            \" window.Dokit.isNativeContainer()\\n\" +\n            \" window.Dokit.startMultiControl('${mcUrl}','${mode}')\\n\" +\n            \"</script>\"\n\n        val doc = Jsoup.parse(html)\n        doc.outputSettings().prettyPrint(true)\n        val elements = doc.getElementsByTag(\"head\")\n        if (elements.size > 0) {\n            elements[elements.size - 1].append(\"\").append(injectHook)\n        }\n        return doc.toString()\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/h5_help/DoKitX5WebViewClient.kt",
    "content": "package com.didichuxing.doraemonkit.kit.h5_help\n\nimport android.app.Activity\nimport android.os.Build\nimport androidx.annotation.RequiresApi\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.util.ConvertUtils\nimport com.didichuxing.doraemonkit.util.ResourceUtils\nimport com.didichuxing.doraemonkit.okhttp_api.OkHttpWrap\nimport com.didichuxing.doraemonkit.aop.urlconnection.OkhttpClientUtil\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.h5_help.bean.JsRequestBean\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport com.tencent.smtt.export.external.interfaces.*\nimport com.tencent.smtt.sdk.WebView\nimport com.tencent.smtt.sdk.WebViewClient\nimport okhttp3.*\nimport org.jsoup.Jsoup\nimport java.net.URLDecoder\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/8/28-17:22\n * 描    述：切面dokit\n * 修订历史：\n * ================================================\n */\nclass DoKitX5WebViewClient(webViewClient: WebViewClient, userAgent: String) : ProxyX5WebViewClient(webViewClient) {\n\n    private val TAG = \"DoKitWebViewClient\"\n    private val mUserAgent = userAgent\n\n    /**\n     * 更新悬浮窗上的链接\n     */\n    private fun updateH5DokitUrl(view: WebView?, url: String?) {\n        view?.let { it ->\n            if (it.context is Activity) {\n                val activity = it.context as Activity\n                val absDokitView: AbsDoKitView? = DoKit.getDoKitView<H5DoKitView>(activity, H5DoKitView::class)\n                absDokitView?.let { h5DokitView ->\n                    (h5DokitView as H5DoKitView).updateUrl(url)\n                }\n\n            }\n        }\n    }\n\n    override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {\n        updateH5DokitUrl(view, url)\n        return super.shouldOverrideUrlLoading(view, url)\n    }\n\n    override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {\n        updateH5DokitUrl(view, url)\n        super.doUpdateVisitedHistory(view, url, isReload)\n    }\n\n    @RequiresApi(Build.VERSION_CODES.N)\n    override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {\n        updateH5DokitUrl(view, request?.url?.path)\n        return super.shouldOverrideUrlLoading(view, request)\n    }\n\n\n    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n    override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {\n        //开关均被关闭则不进行拦截\n        if (!DoKitManager.H5_JS_INJECT && !DoKitManager.H5_VCONSOLE_INJECT) {\n            return super.shouldInterceptRequest(view, request)\n        }\n        request?.let { webRequest ->\n            //加载页面资源\n            if (webRequest.isForMainFrame) {\n                LogHelper.i(\n                    TAG,\n                    \"url===>${webRequest.url?.toString()}  method==>${webRequest.method} thread==>${Thread.currentThread().name}\"\n                )\n                val httpUrl = OkHttpWrap.createHttpUrl(webRequest.url?.toString())\n\n                val url = if (OkHttpWrap.toUrl(httpUrl)?.query.isNullOrBlank()) {\n                    webRequest.url?.toString() + \"?dokit_flag=web\"\n                } else {\n                    webRequest.url?.toString() + \"&dokit_flag=web\"\n                }\n\n                val httpRequest = Request.Builder()\n                    .header(\"User-Agent\", mUserAgent)\n                    .url(url)\n                    .build()\n                val response = OkhttpClientUtil.okhttpClient.newCall(httpRequest).execute()\n\n                //注入本地网络拦截js\n                var newHtml = if (DoKitManager.H5_JS_INJECT) {\n                    injectJsHook(OkHttpWrap.toResponseBody(response)?.string())\n                } else {\n                    OkHttpWrap.toResponseBody(response)?.string()\n                }\n                //注入vConsole的代码\n                if (DoKitManager.H5_VCONSOLE_INJECT) {\n                    newHtml = injectVConsoleHook(newHtml)\n                }\n\n                return WebResourceResponse(\n                    \"text/html\",\n                    response.header(\"content-encoding\", \"utf-8\"),\n                    ConvertUtils.string2InputStream(newHtml, \"utf-8\")\n                )\n            } else {\n                //加载js网络请求\n                if (webRequest.url.toString().contains(\"dokit_flag\")) {\n                    val jsRequestId = getUrlQuery(webRequest.url.toString(), \"dokit_flag\")\n                    val jsRequestBean = JsHookDataManager.jsRequestMap[jsRequestId]\n                    LogHelper.i(TAG, jsRequestBean.toString())\n                    jsRequestBean?.let { requestBean ->\n                        val url = OkHttpWrap.createHttpUrl(requestBean.url)\n                        val host = OkHttpWrap.toRequestHost(url)\n                        //如果是dokit mock host 则不进行拦截\n                        if (host.equals(NetworkManager.MOCK_HOST, true)) {\n                            JsHookDataManager.jsRequestMap.remove(requestBean.requestId)\n                            return null\n                        }\n\n                        // web 数据mock\n                        return dealMock(requestBean, url, view, request)\n                    }\n\n                } else {\n                    return super.shouldInterceptRequest(view, request)\n                }\n            }\n        }\n\n        return super.shouldInterceptRequest(view, request)\n    }\n\n    /**\n     * 处理数据mock的相关逻辑\n     */\n    private fun dealMock(requestBean: JsRequestBean, url: HttpUrl?, view: WebView?, request: WebResourceRequest?): WebResourceResponse? {\n        url?.let { httpUrl ->\n            try {\n                val path = URLDecoder.decode(OkHttpWrap.toEncodedPath(httpUrl), \"utf-8\")\n                val queries = OkHttpWrap.toHttpQuery(httpUrl)\n                val jsonQuery = JsHttpUtil.transformQuery(queries)\n                val jsonRequestBody = JsHttpUtil.transformRequestBody(\n                    requestBean.method,\n                    requestBean.body,\n                    requestBean.headers\n                )\n\n                val interceptMatchedId =\n                    DokitDbManager.getInstance().isMockMatched(\n                        path,\n                        jsonQuery,\n                        jsonRequestBody,\n                        DokitDbManager.MOCK_API_INTERCEPT,\n                        DokitDbManager.FROM_SDK_OTHER\n                    )\n\n                val templateMatchedId =\n                    DokitDbManager.getInstance().isMockMatched(\n                        path,\n                        jsonQuery,\n                        jsonRequestBody,\n                        DokitDbManager.MOCK_API_TEMPLATE,\n                        DokitDbManager.FROM_SDK_OTHER\n                    )\n\n                //如果interceptMatchedId和templateMatchedId都为null 直接不进行操作\n                if (interceptMatchedId.isNullOrBlank() && templateMatchedId.isNullOrBlank()) {\n                    //web 抓包\n                    if (NetworkManager.isActive()) {\n                        try {\n                            //构建okhttp用来抓包\n                            val newRequest: Request =\n                                JsHttpUtil.createOkHttpRequest(requestBean, mUserAgent)\n\n                            if (JsHttpUtil.matchWhiteHost(newRequest)) {\n                                //发送模拟请求\n                                OkhttpClientUtil.okhttpClient.newCall(newRequest).execute()\n                            }\n                        } catch (e: Exception) {\n                            e.printStackTrace()\n                        }\n                    }\n                    return super.shouldInterceptRequest(view, request)\n                }\n\n                val newRequest: Request =\n                    JsHttpUtil.createOkHttpRequest(requestBean, mUserAgent)\n                //发送模拟请求\n                val newResponse =\n                    OkhttpClientUtil.okhttpClient.newCall(newRequest).execute()\n                //是否命中拦截规则\n                if (!interceptMatchedId.isNullOrBlank()) {\n                    JsHookDataManager.jsRequestMap.remove(requestBean.requestId)\n                    return JsHttpUtil.matchedX5InterceptRule(\n                        httpUrl,\n                        path,\n                        interceptMatchedId,\n                        templateMatchedId,\n                        newRequest,\n                        newResponse,\n                        OkhttpClientUtil.okhttpClient\n                    )\n                }\n\n                //是否命中模板规则\n                if (!templateMatchedId.isNullOrBlank()) {\n                    JsHttpUtil.matchedTemplateRule(\n                        newResponse,\n                        path,\n                        templateMatchedId\n                    )\n                }\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n        JsHookDataManager.jsRequestMap.remove(requestBean.requestId)\n\n        return super.shouldInterceptRequest(view, request)\n    }\n\n\n    private fun getUrlQuery(url: String, key: String): String? {\n        val httpUrl = OkHttpWrap.createHttpUrl(url)\n\n        val queries = OkHttpWrap.toUrl(httpUrl)?.query?.split(\"&\")\n        val queryMap = mutableMapOf<String, String>()\n\n        queries?.forEach {\n            val keyAndValue = it.split(\"=\")\n            queryMap[keyAndValue[0]] = keyAndValue[1]\n        }\n\n        return queryMap[key]\n\n    }\n\n    /**\n     * 注入hook js 哇共诺请求的代码\n     */\n    private fun injectJsHook(html: String?): String {\n        //读取本地js hook 代码\n        val jsHook = ResourceUtils.readAssets2String(\"h5help/dokit_js_hook.html\")\n        val doc = Jsoup.parse(html)\n        doc.outputSettings().prettyPrint(true)\n        val elements = doc.getElementsByTag(\"head\")\n        if (elements.size > 0) {\n            elements[0].prepend(jsHook)\n        }\n        return doc.toString()\n    }\n\n    /**\n     * 注入 vConsole的代码\n     */\n    private fun injectVConsoleHook(html: String?): String {\n        //读取本地js hook 代码\n        val vconsoleHook = ResourceUtils.readAssets2String(\"h5help/dokit_js_vconsole_hook.html\")\n        val doc = Jsoup.parse(html)\n        doc.outputSettings().prettyPrint(true)\n        val elements = doc.getElementsByTag(\"head\")\n        if (elements.size > 0) {\n            elements[elements.size - 1].append(vconsoleHook)\n        }\n        return doc.toString()\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/h5_help/H5DoKitView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.h5_help\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.webkit.WebView\nimport android.widget.*\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.children\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.util.ConvertUtils\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/8/25-11:08\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass H5DoKitView : AbsDoKitView() {\n    companion object {\n        const val STORAGE_TYPE_LOCAL = 100\n        const val STORAGE_TYPE_Session = 101\n    }\n\n    /**\n     * url链接\n     */\n    private lateinit var mTvLink: TextView\n\n    /**\n     * js hook switch\n     */\n    private lateinit var mJsCheckBox: CheckBox\n    private lateinit var mVConsoleCheckBox: CheckBox\n    private lateinit var mRvLocal: RecyclerView\n    private lateinit var mRvSession: RecyclerView\n    private lateinit var mRvWrap: LinearLayout\n    private lateinit var mMoreWrap: RelativeLayout\n    private lateinit var mHolder: TextView\n    private lateinit var mLocalAdapter: LocalStorageAdapter\n    private lateinit var mSessionAdapter: LocalStorageAdapter\n    private lateinit var mNavLocal: TextView\n    private lateinit var mNavSession: TextView\n    private lateinit var mBtnReload: Button\n\n    /**\n     * 更多信息是否处于展开状态\n     */\n    var isOpen: Boolean = false\n\n\n    override fun onCreate(context: Context?) {\n    }\n\n    override fun onCreateView(context: Context?, rootView: FrameLayout?): View {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_h5_info, rootView, false)\n    }\n\n    override fun onViewCreated(rootView: FrameLayout?) {\n        rootView?.let {\n            val close = it.findViewById<ImageView>(R.id.iv_close)\n            close.setOnClickListener {\n                DoKit.removeFloating(this)\n            }\n            mTvLink = it.findViewById(R.id.tv_link)\n            mJsCheckBox = it.findViewById(R.id.js_switch)\n            mBtnReload = it.findViewById(R.id.btn_reload)\n            mBtnReload.setOnClickListener {\n                mWebView?.let { webView ->\n                    if (X5WebViewUtil.hasImpX5WebViewLib()) {\n                        when (webView) {\n                            is WebView -> {\n                                webView.reload()\n                            }\n                            is com.tencent.smtt.sdk.WebView -> {\n                                webView.reload()\n                            }\n                        }\n                    } else {\n                        when (webView) {\n                            is WebView -> {\n                                webView.reload()\n                            }\n                        }\n                    }\n                }\n            }\n            mJsCheckBox.isChecked = DoKitManager.H5_JS_INJECT\n            mJsCheckBox.setOnCheckedChangeListener { _, isChecked ->\n                DoKitManager.H5_JS_INJECT = isChecked\n            }\n\n            mVConsoleCheckBox = it.findViewById(R.id.vConsole_switch)\n            mVConsoleCheckBox.isChecked = DoKitManager.H5_VCONSOLE_INJECT\n            mVConsoleCheckBox.setOnCheckedChangeListener { _, isChecked ->\n                DoKitManager.H5_VCONSOLE_INJECT = isChecked\n            }\n\n            mNavLocal = it.findViewById(R.id.tv_nav_local)\n            mNavLocal.setTextColor(ContextCompat.getColor(activity, R.color.dk_color_55A8FD))\n            mNavLocal.setOnClickListener {\n                mRvLocal.visibility = View.VISIBLE\n                mRvSession.visibility = View.GONE\n                mNavSession.setTextColor(ContextCompat.getColor(activity, R.color.dk_color_333333))\n                mNavLocal.setTextColor(ContextCompat.getColor(activity, R.color.dk_color_55A8FD))\n            }\n            mNavSession = it.findViewById(R.id.tv_nav_session)\n            mNavSession.setTextColor(ContextCompat.getColor(activity, R.color.dk_color_333333))\n            mNavSession.setOnClickListener {\n                mRvLocal.visibility = View.GONE\n                mRvSession.visibility = View.VISIBLE\n                mNavSession.setTextColor(ContextCompat.getColor(activity, R.color.dk_color_55A8FD))\n                mNavLocal.setTextColor(ContextCompat.getColor(activity, R.color.dk_color_333333))\n            }\n\n            mRvWrap = it.findViewById(R.id.ll_rv_wrap)\n            mMoreWrap = it.findViewById(R.id.rl_more_wrap)\n            mHolder = it.findViewById(R.id.tv_holder)\n            mHolder.text = \"更多\"\n            mHolder.setOnClickListener {\n                if (mRvWrap.visibility == View.VISIBLE) {\n                    mRvWrap.visibility = View.GONE\n                    mHolder.text = \"更多\"\n                    isOpen = false\n                } else {\n                    mRvWrap.visibility = View.VISIBLE\n                    mHolder.text = \"收起\"\n                    isOpen = true\n                }\n                immInvalidate()\n            }\n\n\n            //绑定localStorage数据\n\n            mRvLocal = it.findViewById(R.id.rv_localStorage)\n            mRvLocal.visibility = View.VISIBLE\n            mRvLocal.layoutManager = LinearLayoutManager(activity)\n            mLocalAdapter = LocalStorageAdapter(JsHookDataManager.jsLocalStorage)\n            mRvLocal.adapter = mLocalAdapter\n            mLocalAdapter.setEmptyView(R.layout.dk_layout_localstorage_empty)\n\n\n            //绑定sessionStorage数据\n            mRvSession = it.findViewById(R.id.rv_sessionStorage)\n            mRvSession.visibility = View.GONE\n            mRvSession.layoutManager = LinearLayoutManager(activity)\n            mSessionAdapter = LocalStorageAdapter(JsHookDataManager.jsSessionStorage)\n            mRvSession.adapter = mSessionAdapter\n            mSessionAdapter.setEmptyView(R.layout.dk_layout_sessionstorage_empty)\n        }\n    }\n\n\n    override fun initDokitViewLayoutParams(params: DoKitViewLayoutParams?) {\n        params?.let {\n            it.width = ConvertUtils.dp2px(300.0f)\n            it.height = DoKitViewLayoutParams.WRAP_CONTENT\n            it.gravity = Gravity.TOP or Gravity.LEFT\n            it.x = 200\n            it.y = 200\n        }\n    }\n\n    var mWebView: Any? = null\n\n    @SuppressLint(\"JavascriptInterface\", \"AddJavascriptInterface\")\n    override fun onResume() {\n        super.onResume()\n        mWebView = performTraverseView()\n        if (mWebView == null) {\n            mTvLink.text = \"当前页面不存在WebView\"\n            mMoreWrap.visibility = View.GONE\n            mBtnReload.visibility = View.GONE\n        } else {\n            if (X5WebViewUtil.hasImpX5WebViewLib()) {\n                when (mWebView) {\n                    is WebView -> {\n                        mWebView as WebView\n                        mTvLink.text = (mWebView as WebView).url\n                    }\n\n                    is com.tencent.smtt.sdk.WebView -> {\n                        mTvLink.text = (mWebView as com.tencent.smtt.sdk.WebView).url\n                    }\n                }\n            } else {\n                when (mWebView) {\n                    is WebView -> {\n                        mWebView as WebView\n                        mTvLink.text = (mWebView as WebView).url\n                    }\n                }\n            }\n            mMoreWrap.visibility = View.VISIBLE\n            mBtnReload.visibility = View.VISIBLE\n        }\n        mJsCheckBox.isChecked = DoKitManager.H5_JS_INJECT\n        mVConsoleCheckBox.isChecked = DoKitManager.H5_VCONSOLE_INJECT\n        immInvalidate()\n    }\n\n    /**\n     * 更新url\n     */\n    fun updateUrl(url: String?) {\n        mTvLink.text = url\n        immInvalidate()\n    }\n\n\n    private fun performTraverseView(): Any? {\n        val decorView = activity.window.decorView as ViewGroup\n        decorView.children.forEach {\n            if (X5WebViewUtil.hasImpX5WebViewLib()) {\n                when (it) {\n                    is WebView -> return it\n                    is com.tencent.smtt.sdk.WebView -> return it\n                    is ViewGroup -> return traversView(it)\n                }\n            } else {\n                when (it) {\n                    is WebView -> return it\n                    is ViewGroup -> return traversView(it)\n                }\n            }\n\n\n        }\n        return null\n    }\n\n    private fun traversView(viewGroup: ViewGroup): Any? {\n        viewGroup.children.forEach {\n            if (X5WebViewUtil.hasImpX5WebViewLib()) {\n                when (it) {\n                    is WebView -> return it\n                    is com.tencent.smtt.sdk.WebView -> return it\n                    is ViewGroup -> return traversView(it)\n                }\n            } else {\n                when (it) {\n                    is WebView -> return it\n                    is ViewGroup -> return traversView(it)\n                }\n            }\n\n        }\n        return null\n    }\n\n    /**\n     * 更新adapter的数据\n     * @param type 100:localStorage   101:sessionStorage\n     */\n    fun updateAdapter(type: Int) {\n        activity.runOnUiThread {\n            if (type == STORAGE_TYPE_LOCAL) {\n                mLocalAdapter.notifyDataSetChanged()\n            } else if (type == STORAGE_TYPE_Session) {\n                mSessionAdapter.notifyDataSetChanged()\n            }\n        }\n    }\n\n\n    override fun immInvalidate() {\n        normalLayoutParams?.let {\n            it.width = ConvertUtils.dp2px(300.0f)\n            if (isOpen) {\n                it.height = ConvertUtils.dp2px(650.0f)\n            } else {\n                it.height = DoKitViewLayoutParams.WRAP_CONTENT\n            }\n\n        }\n        super.immInvalidate()\n    }\n\n    /**\n     * 不限制屏幕边界\n     */\n    override fun restrictBorderline(): Boolean {\n        return false\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/h5_help/H5Kit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.h5_help\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/8/24-20:41\n * 描    述：\n * 修订历史：\n * ================================================\n */\n@AutoService(AbstractKit::class)\nclass H5Kit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_h5_help\n\n    override val icon: Int\n        get() = R.mipmap.dk_icon_h5help\n\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        DoKit.launchFloating(H5DoKitView::class)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n    }\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_comm_ck_h5kit\"\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/h5_help/JsHookDataManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.h5_help\n\nimport com.didichuxing.doraemonkit.kit.h5_help.bean.JsRequestBean\nimport com.didichuxing.doraemonkit.kit.h5_help.bean.StorageBean\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/8/28-16:08\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject JsHookDataManager {\n    val jsRequestMap: MutableMap<String?, JsRequestBean> by lazy { mutableMapOf<String?, JsRequestBean>() }\n\n    val jsLocalStorage: MutableList<StorageBean> by lazy { mutableListOf<StorageBean>() }\n\n    val jsSessionStorage: MutableList<StorageBean> by lazy { mutableListOf<StorageBean>() }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/h5_help/JsHttpUtil.kt",
    "content": "package com.didichuxing.doraemonkit.kit.h5_help\n\nimport android.text.TextUtils\nimport android.webkit.WebResourceResponse\nimport com.didichuxing.doraemonkit.util.ConvertUtils\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.didichuxing.doraemonkit.okhttp_api.OkHttpWrap\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.h5_help.bean.JsRequestBean\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager\nimport com.didichuxing.doraemonkit.kit.network.bean.WhiteHostBean\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager\nimport com.didichuxing.doraemonkit.kit.network.room_db.MockInterceptApiBean\nimport com.didichuxing.doraemonkit.kit.network.room_db.MockTemplateApiBean\nimport com.didichuxing.doraemonkit.kit.network.utils.bodyContent\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport okhttp3.*\nimport org.json.JSONObject\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/9/1-11:14\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject JsHttpUtil {\n    val TAG = \"JsHttpUtil\"\n\n    /**\n     * 将request query 转化成json字符串\n     *\n     * @return\n     */\n    fun transformQuery(query: String?): String {\n        var json = \"\"\n        if (query.isNullOrBlank()) {\n            return json\n        }\n        try {\n            //query 类似 ccc=ccc&ddd=ddd\n            json = DoKitCommUtil.param2Json(query)\n            //测试是否是json字符串\n            JSONObject(json)\n        } catch (e: Exception) {\n            //e.printStackTrace();\n            json = DokitDbManager.IS_NOT_NORMAL_QUERY_PARAMS\n            //LogHelper.e(TAG, \"===query json====>\" + json);\n        }\n        return json\n    }\n\n\n    /**\n     * 将request body 转化成json字符串\n     *\n     * @return\n     */\n    fun transformRequestBody(\n        method: String?,\n        requestBody: String?,\n        headers: MutableMap<String?, String?>?\n    ): String {\n        //form :\"application/x-www-form-urlencoded\"\n        //json :\"application/json;\"\n        var json = \"\"\n        if (method.equals(\"GET\", true) || requestBody.isNullOrBlank()) {\n            return json\n        }\n\n        try {\n\n            headers?.let {\n                val contentType = it[\"Content-Type\"]\n                if (contentType.isNullOrBlank()) {\n                    return json\n                }\n\n                //表单类型的post\n                if (contentType.contains(DokitDbManager.MEDIA_TYPE_FORM)) {\n                    //类似 ccc=ccc&ddd=ddd\n                    json = DoKitCommUtil.param2Json(requestBody)\n                    //测试是否是json字符串\n                    JSONObject(json)\n                } else if (contentType.contains(DokitDbManager.MEDIA_TYPE_JSON)) {\n                    json = requestBody\n                    JSONObject(json)\n                } else {\n                    json = DokitDbManager.IS_NOT_NORMAL_BODY_PARAMS\n                }\n            }\n        } catch (e: java.lang.Exception) {\n            //e.printStackTrace();\n            json = \"\"\n            LogHelper.e(\n                TAG, \"===body json====>$json\"\n            )\n        }\n        return json\n    }\n\n    /**\n     * 返回null 即代表返回原来的数据\n     */\n    fun matchedNormalInterceptRule(\n        url: HttpUrl,\n        path: String,\n        interceptMatchedId: String,\n        templateMatchedId: String,\n        oldRequest: Request,\n        oldResponse: Response,\n        okHttpClient: OkHttpClient\n    ): WebResourceResponse? {\n        //判断是否需要重定向数据接口\n        //http https\n\n        //判断是否需要重定向数据接口\n        //http https\n        val scheme = OkHttpWrap.toScheme(url)\n        val interceptApiBean = DokitDbManager.getInstance().getInterceptApiByIdInMap(\n            path,\n            interceptMatchedId,\n            DokitDbManager.FROM_SDK_OTHER\n        )\n        if (interceptApiBean == null) {\n            matchedTemplateRule(oldResponse, path, templateMatchedId)\n            return null\n        }\n\n        interceptApiBean as MockInterceptApiBean\n        val selectedSceneId = interceptApiBean.selectedSceneId\n        //开关是否被打开\n        //开关是否被打开\n        if (!interceptApiBean.isOpen) {\n            matchedTemplateRule(oldResponse, path, templateMatchedId)\n            return null\n        }\n\n        //判断是否有选中的场景\n\n        //判断是否有选中的场景\n        if (TextUtils.isEmpty(selectedSceneId)) {\n            matchedTemplateRule(oldResponse, path, templateMatchedId)\n            return null\n        }\n        val sb = StringBuilder()\n        val newUrl: String\n        newUrl = if (NetworkManager.MOCK_SCHEME_HTTP.contains(scheme.toLowerCase())) {\n            sb.append(NetworkManager.MOCK_SCHEME_HTTP).append(NetworkManager.MOCK_HOST)\n                .append(\"/api/app/scene/\").append(selectedSceneId).toString()\n        } else {\n            sb.append(NetworkManager.MOCK_SCHEME_HTTPS).append(NetworkManager.MOCK_HOST)\n                .append(\"/api/app/scene/\").append(selectedSceneId).toString()\n        }\n\n\n        val newRequest = Request.Builder()\n            .method(\"GET\", null)\n            .url(newUrl).build()\n        //需要提前关闭数据流 不然在某些场景下会报错\n        oldResponse.close()\n        val newResponse: Response = okHttpClient.newCall(newRequest).execute()\n        if (OkHttpWrap.toResponseCode(newResponse) == 200) {\n            //拦截命中提示\n            ToastUtils.showShort(\"接口别名:==\" + interceptApiBean.mockApiName + \"==已被拦截\")\n            //判断新的response是否有数据\n            return if (OkHttpWrap.toResponseBody(newResponse) != null) {\n                matchedTemplateRule(newResponse, path, templateMatchedId)\n                createNormalWebResourceResponse(newResponse)\n            } else {\n                matchedTemplateRule(oldResponse, path, templateMatchedId)\n                null\n            }\n        }\n        matchedTemplateRule(oldResponse, path, templateMatchedId)\n        return null\n    }\n\n    private fun createNormalWebResourceResponse(response: Response): WebResourceResponse {\n        val bodyContent = response.bodyContent()\n        try {\n            //mimeType 会影响js的数据展现形式\n            JSONObject(bodyContent)\n            return WebResourceResponse(\n                \"application/json\",\n                \"UTF-8\",\n                ConvertUtils.string2InputStream(bodyContent, \"UTF-8\")\n            )\n        } catch (e: Exception) {\n            return WebResourceResponse(\n                response.header(\"Content-Type\", \"application/json\"),\n                \"UTF-8\",\n                ConvertUtils.string2InputStream(bodyContent, \"UTF-8\")\n            )\n        }\n\n\n    }\n\n    /**\n     * 返回null 即代表返回原来的数据\n     */\n    fun matchedX5InterceptRule(\n        url: HttpUrl,\n        path: String,\n        interceptMatchedId: String,\n        templateMatchedId: String,\n        oldRequest: Request,\n        oldResponse: Response,\n        okHttpClient: OkHttpClient\n    ): com.tencent.smtt.export.external.interfaces.WebResourceResponse? {\n        //判断是否需要重定向数据接口\n        //http https\n\n        //判断是否需要重定向数据接口\n        //http https\n        val scheme = OkHttpWrap.toScheme(url)\n        val interceptApiBean = DokitDbManager.getInstance().getInterceptApiByIdInMap(\n            path,\n            interceptMatchedId,\n            DokitDbManager.FROM_SDK_OTHER\n        )\n        if (interceptApiBean == null) {\n            matchedTemplateRule(oldResponse, path, templateMatchedId)\n            return null\n        }\n\n        interceptApiBean as MockInterceptApiBean\n        val selectedSceneId = interceptApiBean.selectedSceneId\n        //开关是否被打开\n        //开关是否被打开\n        if (!interceptApiBean.isOpen) {\n            matchedTemplateRule(oldResponse, path, templateMatchedId)\n            return null\n        }\n\n        //判断是否有选中的场景\n\n        //判断是否有选中的场景\n        if (TextUtils.isEmpty(selectedSceneId)) {\n            matchedTemplateRule(oldResponse, path, templateMatchedId)\n            return null\n        }\n        val sb = StringBuilder()\n        val newUrl: String\n        newUrl = if (NetworkManager.MOCK_SCHEME_HTTP.contains(scheme.toLowerCase())) {\n            sb.append(NetworkManager.MOCK_SCHEME_HTTP).append(NetworkManager.MOCK_HOST)\n                .append(\"/api/app/scene/\").append(selectedSceneId).toString()\n        } else {\n            sb.append(NetworkManager.MOCK_SCHEME_HTTPS).append(NetworkManager.MOCK_HOST)\n                .append(\"/api/app/scene/\").append(selectedSceneId).toString()\n        }\n\n\n        val newRequest = Request.Builder()\n            .method(\"GET\", null)\n            .url(newUrl).build()\n        //需要提前关闭数据流 不然在某些场景下会报错\n        oldResponse.close()\n        val newResponse: Response = okHttpClient.newCall(newRequest).execute()\n        if (OkHttpWrap.toResponseCode(newResponse) == 200) {\n            //拦截命中提示\n            ToastUtils.showShort(\"接口别名:==\" + interceptApiBean.mockApiName + \"==已被拦截\")\n            //判断新的response是否有数据\n            return if (OkHttpWrap.toResponseBody(newResponse) != null) {\n                matchedTemplateRule(newResponse, path, templateMatchedId)\n                createX5WebResourceResponse(newResponse)\n            } else {\n                matchedTemplateRule(oldResponse, path, templateMatchedId)\n                null\n            }\n        }\n        matchedTemplateRule(oldResponse, path, templateMatchedId)\n        return null\n    }\n\n    private fun createX5WebResourceResponse(response: Response): com.tencent.smtt.export.external.interfaces.WebResourceResponse {\n        val bodyContent = response.bodyContent()\n        try {\n            //mimeType 会影响js的数据展现形式\n            JSONObject(bodyContent)\n            return com.tencent.smtt.export.external.interfaces.WebResourceResponse(\n                \"application/json\",\n                \"UTF-8\",\n                ConvertUtils.string2InputStream(bodyContent, \"UTF-8\")\n            )\n        } catch (e: Exception) {\n            return com.tencent.smtt.export.external.interfaces.WebResourceResponse(\n                response.header(\"Content-Type\", \"application/json\"),\n                \"UTF-8\",\n                ConvertUtils.string2InputStream(bodyContent, \"UTF-8\")\n            )\n        }\n\n    }\n\n\n    /**\n     *是否命中模板功能\n     */\n    fun matchedTemplateRule(response: Response, path: String, templateMatchedId: String) {\n\n        //命中模板规则\n        if (TextUtils.isEmpty(templateMatchedId)) {\n            return\n        }\n        val templateApiBean = DokitDbManager.getInstance().getTemplateApiByIdInMap(\n            path,\n            templateMatchedId,\n            DokitDbManager.FROM_SDK_OTHER\n        )\n        //LogHelper.i(\"MOCK_TEMPLATE\", \"path=====>\" + path + \"isOpen===>\" + templateApiBean.isOpen());\n        //LogHelper.i(\"MOCK_TEMPLATE\", \"path=====>\" + path + \"isOpen===>\" + templateApiBean.isOpen());\n        templateApiBean?.let {\n            it as MockTemplateApiBean\n            if (it.isOpen) {\n                //保存老的response 数据到数据库\n                saveResponse2DB(response, it)\n            }\n        }\n\n    }\n\n    /**\n     * 保存匹配中的数据到本地数据库\n     *\n     * @param response\n     * @param mockApi\n     * @throws Exception\n     */\n    private fun saveResponse2DB(response: Response, mockApi: MockTemplateApiBean) {\n        if (OkHttpWrap.toResponseCode(response) != 200) {\n            return\n        }\n        if (OkHttpWrap.toResponseBody(response) == null) {\n            return\n        }\n        try {\n            val host = OkHttpWrap.toResponseHost(response)\n            //LogHelper.i(TAG, \"host====>\" + host);\n            //这里不能直接使用response.body().string()的方式输出日志\n            //因为response.body().string()之后，response中的流会被关闭，程序会报错，我们需要创建出一\n            //个新的response给应用层处理\n            val responseBody = response.peekBody(Long.MAX_VALUE)\n            val strResponseBody = responseBody.string()\n            if (TextUtils.isEmpty(strResponseBody)) {\n                return\n            }\n            if (host == NetworkManager.MOCK_HOST) {\n                mockApi.responseFrom = MockTemplateApiBean.RESPONSE_FROM_MOCK\n            } else {\n                mockApi.responseFrom = MockTemplateApiBean.RESPONSE_FROM_REAL\n            }\n            mockApi.strResponse = strResponseBody\n            //更新本地数据库\n            DokitDbManager.getInstance().updateTemplateApi(mockApi)\n            //拦截命中提示\n            ToastUtils.showShort(\"模板别名:==\" + mockApi.mockApiName + \"==已被保存\")\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n\n    }\n\n\n    fun createOkHttpRequest(requestBean: JsRequestBean, userAgent: String): Request {\n        requestBean.headers?.let {\n            if (!it.containsKey(\"content-type\")) {\n                it[\"content-type\"] = \"application/json\"\n            }\n\n            if (!it.containsKey(\"User-Agent\")) {\n                it[\"User-Agent\"] = userAgent\n            }\n        }\n        val builder = Headers.Builder()\n        requestBean.headers?.forEach {\n            builder.add(it.key!!, it.value!!)\n        }\n\n\n        val headers = builder.build()\n        return when (requestBean.method?.toUpperCase()) {\n            \"GET\" -> {\n                Request.Builder()\n                    .url(requestBean.url!!)\n                    .headers(headers)\n                    .get()\n                    .build()\n            }\n            \"POST\" -> {\n                var contentType: String? = \"\"\n                contentType = requestBean.headers?.get(\"Content-Type\")\n                if (contentType.isNullOrBlank()) {\n                    contentType = requestBean.headers?.get(\"content-type\")\n                }\n\n\n                val requestBody =\n                    OkHttpWrap.toRequestBody(requestBean.body, OkHttpWrap.toMediaType(contentType))\n\n                Request.Builder()\n                    .url(requestBean.url!!)\n                    .headers(headers)\n                    .post(requestBody!!)\n                    .build()\n            }\n            else -> {\n                Request.Builder()\n                    .url(requestBean.url!!)\n                    .headers(headers)\n                    .get()\n                    .build()\n            }\n        }\n\n    }\n\n    /**\n     * 是否命中白名单规则\n     *\n     * @return bool\n     */\n    fun matchWhiteHost(request: Request): Boolean {\n        val whiteHostBeans: List<WhiteHostBean> = DoKitManager.WHITE_HOSTS\n        if (whiteHostBeans.isEmpty()) {\n            return true\n        }\n\n        for (whiteHostBean in whiteHostBeans) {\n            if (TextUtils.isEmpty(whiteHostBean.host)) {\n                continue\n            }\n            val realHost = OkHttpWrap.toRequestHost(request)\n            //正则判断\n            if (whiteHostBean.host.equals(realHost, ignoreCase = true)) {\n                return true\n            }\n        }\n        return false\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/h5_help/LocalStorageAdapter.kt",
    "content": "package com.didichuxing.doraemonkit.kit.h5_help\n\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.h5_help.bean.StorageBean\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/9/2-18:25\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass LocalStorageAdapter(\n    list: MutableList<StorageBean>\n) : BaseQuickAdapter<StorageBean, BaseViewHolder>(\n    R.layout.dk_item_localstorage,\n    list\n) {\n    override fun convert(holder: BaseViewHolder, item: StorageBean) {\n        holder.getView<TextView>(R.id.tv_key).text = item.key\n        holder.getView<TextView>(R.id.tv_value).text = item.value\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/h5_help/ProxyWebViewClient.kt",
    "content": "package com.didichuxing.doraemonkit.kit.h5_help\n\nimport android.graphics.Bitmap\nimport android.net.http.SslError\nimport android.os.Build\nimport android.os.Message\nimport android.view.KeyEvent\nimport android.webkit.*\nimport androidx.annotation.RequiresApi\n\n/**\n * didi Create on 2023/3/23 .\n *\n * Copyright (c) 2023/3/23 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/23 11:29 上午\n * @Description WebViewClient 代理\n */\n\nopen class ProxyWebViewClient(webViewClient: WebViewClient) : WebViewClient() {\n\n    private val mWebViewClient: WebViewClient = webViewClient\n\n\n    override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {\n        return mWebViewClient.shouldOverrideUrlLoading(view, url)\n    }\n\n    @RequiresApi(Build.VERSION_CODES.N)\n    override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {\n        return mWebViewClient.shouldOverrideUrlLoading(view, request)\n    }\n\n    override fun shouldInterceptRequest(view: WebView?, url: String?): WebResourceResponse? {\n        return mWebViewClient.shouldInterceptRequest(view, url)\n    }\n\n    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n    override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {\n        return mWebViewClient.shouldInterceptRequest(view, request)\n    }\n\n    override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {\n        mWebViewClient.onPageStarted(view, url, favicon)\n    }\n\n    override fun onPageFinished(view: WebView?, url: String?) {\n        mWebViewClient.onPageFinished(view, url)\n    }\n\n    override fun onLoadResource(view: WebView?, url: String?) {\n        mWebViewClient.onLoadResource(view, url)\n    }\n\n    @RequiresApi(Build.VERSION_CODES.M)\n    override fun onPageCommitVisible(view: WebView?, url: String?) {\n        mWebViewClient.onPageCommitVisible(view, url)\n    }\n\n    override fun onTooManyRedirects(view: WebView?, cancelMsg: Message?, continueMsg: Message?) {\n        mWebViewClient.onTooManyRedirects(view, cancelMsg, continueMsg)\n    }\n\n    override fun onReceivedError(view: WebView?, errorCode: Int, description: String?, failingUrl: String?) {\n        mWebViewClient.onReceivedError(view, errorCode, description, failingUrl)\n    }\n\n    @RequiresApi(Build.VERSION_CODES.M)\n    override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {\n        mWebViewClient.onReceivedError(view, request, error)\n    }\n\n    @RequiresApi(Build.VERSION_CODES.M)\n    override fun onReceivedHttpError(view: WebView?, request: WebResourceRequest?, errorResponse: WebResourceResponse?) {\n        mWebViewClient.onReceivedHttpError(view, request, errorResponse)\n    }\n\n    override fun onFormResubmission(view: WebView?, dontResend: Message?, resend: Message?) {\n        mWebViewClient.onFormResubmission(view, dontResend, resend)\n    }\n\n    override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {\n        mWebViewClient.doUpdateVisitedHistory(view, url, isReload)\n    }\n\n    override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {\n        mWebViewClient.onReceivedSslError(view, handler, error)\n    }\n\n    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n    override fun onReceivedClientCertRequest(view: WebView?, request: ClientCertRequest?) {\n        mWebViewClient.onReceivedClientCertRequest(view, request)\n    }\n\n    override fun onReceivedHttpAuthRequest(view: WebView?, handler: HttpAuthHandler?, host: String?, realm: String?) {\n        mWebViewClient.onReceivedHttpAuthRequest(view, handler, host, realm)\n    }\n\n    override fun shouldOverrideKeyEvent(view: WebView?, event: KeyEvent?): Boolean {\n        return mWebViewClient.shouldOverrideKeyEvent(view, event)\n    }\n\n    override fun onUnhandledKeyEvent(view: WebView?, event: KeyEvent?) {\n        mWebViewClient.onUnhandledKeyEvent(view, event)\n    }\n\n    override fun onScaleChanged(view: WebView?, oldScale: Float, newScale: Float) {\n        mWebViewClient.onScaleChanged(view, oldScale, newScale)\n    }\n\n    override fun onReceivedLoginRequest(view: WebView?, realm: String?, account: String?, args: String?) {\n        mWebViewClient.onReceivedLoginRequest(view, realm, account, args)\n    }\n\n    @RequiresApi(Build.VERSION_CODES.O)\n    override fun onRenderProcessGone(view: WebView?, detail: RenderProcessGoneDetail?): Boolean {\n        return mWebViewClient.onRenderProcessGone(view, detail)\n    }\n\n    @RequiresApi(Build.VERSION_CODES.O_MR1)\n    override fun onSafeBrowsingHit(view: WebView?, request: WebResourceRequest?, threatType: Int, callback: SafeBrowsingResponse?) {\n        mWebViewClient.onSafeBrowsingHit(view, request, threatType, callback)\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/h5_help/ProxyX5WebViewClient.kt",
    "content": "package com.didichuxing.doraemonkit.kit.h5_help\n\nimport android.graphics.Bitmap\nimport android.os.Bundle\nimport android.os.Message\nimport android.view.KeyEvent\nimport com.tencent.smtt.export.external.interfaces.*\nimport com.tencent.smtt.sdk.WebView\nimport com.tencent.smtt.sdk.WebViewClient\n\n/**\n * didi Create on 2023/3/23 .\n *\n * Copyright (c) 2023/3/23 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/23 11:29 上午\n * @Description WebViewClient 代理\n */\n\nopen class ProxyX5WebViewClient(webViewClient: WebViewClient) : WebViewClient() {\n\n    private val mWebViewClient: WebViewClient = webViewClient\n\n\n    override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {\n        return mWebViewClient.shouldOverrideUrlLoading(view, url)\n    }\n\n    override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {\n        return mWebViewClient.shouldOverrideUrlLoading(view, request)\n    }\n\n    override fun shouldInterceptRequest(p0: WebView?, p1: WebResourceRequest?, p2: Bundle?): WebResourceResponse {\n        return mWebViewClient.shouldInterceptRequest(p0, p1, p2)\n    }\n\n    override fun shouldInterceptRequest(view: WebView?, url: String?): WebResourceResponse? {\n        return mWebViewClient.shouldInterceptRequest(view, url)\n    }\n\n    override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {\n        return mWebViewClient.shouldInterceptRequest(view, request)\n    }\n\n    override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {\n        mWebViewClient.onPageStarted(view, url, favicon)\n    }\n\n    override fun onPageFinished(view: WebView?, url: String?) {\n        mWebViewClient.onPageFinished(view, url)\n    }\n\n    override fun onLoadResource(view: WebView?, url: String?) {\n        mWebViewClient.onLoadResource(view, url)\n    }\n\n    override fun onPageCommitVisible(view: WebView?, url: String?) {\n        mWebViewClient.onPageCommitVisible(view, url)\n    }\n\n    override fun onTooManyRedirects(view: WebView?, cancelMsg: Message?, continueMsg: Message?) {\n        mWebViewClient.onTooManyRedirects(view, cancelMsg, continueMsg)\n    }\n\n    override fun onReceivedError(view: WebView?, errorCode: Int, description: String?, failingUrl: String?) {\n        mWebViewClient.onReceivedError(view, errorCode, description, failingUrl)\n    }\n\n    override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {\n        mWebViewClient.onReceivedError(view, request, error)\n    }\n\n    override fun onReceivedHttpError(view: WebView?, request: WebResourceRequest?, errorResponse: WebResourceResponse?) {\n        mWebViewClient.onReceivedHttpError(view, request, errorResponse)\n    }\n\n    override fun onFormResubmission(view: WebView?, dontResend: Message?, resend: Message?) {\n        mWebViewClient.onFormResubmission(view, dontResend, resend)\n    }\n\n    override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {\n        mWebViewClient.doUpdateVisitedHistory(view, url, isReload)\n    }\n\n    override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {\n        mWebViewClient.onReceivedSslError(view, handler, error)\n    }\n\n    override fun onReceivedClientCertRequest(view: WebView?, request: ClientCertRequest?) {\n        mWebViewClient.onReceivedClientCertRequest(view, request)\n    }\n\n    override fun onReceivedHttpAuthRequest(view: WebView?, handler: HttpAuthHandler?, host: String?, realm: String?) {\n        mWebViewClient.onReceivedHttpAuthRequest(view, handler, host, realm)\n    }\n\n    override fun onReceivedLoginRequest(view: WebView?, realm: String?, account: String?, args: String?) {\n        mWebViewClient.onReceivedLoginRequest(view, realm, account, args)\n    }\n\n    override fun shouldOverrideKeyEvent(view: WebView?, event: KeyEvent?): Boolean {\n        return mWebViewClient.shouldOverrideKeyEvent(view, event)\n    }\n\n    override fun onUnhandledKeyEvent(view: WebView?, event: KeyEvent?) {\n        mWebViewClient.onUnhandledKeyEvent(view, event)\n    }\n\n    override fun onScaleChanged(view: WebView?, oldScale: Float, newScale: Float) {\n        mWebViewClient.onScaleChanged(view, oldScale, newScale)\n    }\n\n    override fun onDetectedBlankScreen(p0: String?, p1: Int) {\n        mWebViewClient.onDetectedBlankScreen(p0, p1)\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/h5_help/X5WebViewUtil.kt",
    "content": "package com.didichuxing.doraemonkit.kit.h5_help\n\nimport com.didichuxing.doraemonkit.util.ReflectUtils\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/11/3-20:02\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject X5WebViewUtil {\n\n    /**\n     * 项目中是否引入了X5WebView\n     */\n    fun hasImpX5WebViewLib(): Boolean {\n        try {\n\n            val schemeTel = ReflectUtils.reflect(\"com.tencent.smtt.sdk.WebView\").field(\"SCHEME_TEL\")\n                .get<String>()\n            return true\n        } catch (e: Exception) {\n            return false\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/h5_help/bean/JsRequestBean.kt",
    "content": "package com.didichuxing.doraemonkit.kit.h5_help.bean\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/8/28-15:46\n * 描    述：js request bean\n * 修订历史：\n * ================================================\n */\ndata class JsRequestBean(\n    var requestId: String?,\n    var url: String?,\n    var method: String?,\n    var headers: MutableMap<String?, String?>?,\n    var mimeType: String?,\n    var body: String?\n)"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/h5_help/bean/StorageBean.kt",
    "content": "package com.didichuxing.doraemonkit.kit.h5_help.bean\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/9/2-19:35\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class StorageBean(val key: String?, var value: String?)"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/health/AbsCountDownDoKitView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.health\n\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-27-17:59\n * 描    述：页面倒计时浮标\n * 修订历史：\n * ================================================\n */\nabstract class AbsCountDownDoKitView : AbsDoKitView() {\n\n    /**\n     * 重置\n     */\n    abstract fun reset()\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/health/AppHealthInfoUtil.java",
    "content": "package com.didichuxing.doraemonkit.kit.health;\n\nimport com.android.volley.Request;\nimport com.android.volley.Response;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.JsonObjectRequest;\nimport com.didichuxing.doraemonkit.util.AppUtils;\nimport com.didichuxing.doraemonkit.util.DeviceUtils;\nimport com.didichuxing.doraemonkit.util.GsonUtils;\nimport com.didichuxing.doraemonkit.util.TimeUtils;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.BuildConfig;\nimport com.didichuxing.doraemonkit.config.CrashCaptureConfig;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.blockmonitor.core.BlockMonitorManager;\nimport com.didichuxing.doraemonkit.kit.crash.CrashCaptureManager;\nimport com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo;\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.kit.performance.PerformanceDataManager;\nimport com.didichuxing.doraemonkit.volley.VolleyManager;\n\nimport org.json.JSONObject;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020-01-02-16:42\n * 描    述：app 健康体检工具类\n * 修订历史：\n * ================================================\n */\npublic class AppHealthInfoUtil {\n    private static String TAG = \"AppHealthInfoUtil\";\n\n    private AppHealthInfo mAppHealthInfo = new AppHealthInfo();\n\n    /**\n     * 静态内部类单例\n     */\n    private static class Holder {\n        private static AppHealthInfoUtil INSTANCE = new AppHealthInfoUtil();\n    }\n\n    public static AppHealthInfoUtil getInstance() {\n        return AppHealthInfoUtil.Holder.INSTANCE;\n    }\n\n\n    /**\n     * 设置基本信息\n     *\n     * @param caseName   用例名称\n     * @param testPerson 测试人员名字\n     */\n    void setBaseInfo(String caseName, String testPerson) {\n        AppHealthInfo.BaseInfoBean baseInfoBean = new AppHealthInfo.BaseInfoBean();\n        baseInfoBean.setTestPerson(testPerson);\n        baseInfoBean.setCaseName(caseName);\n        baseInfoBean.setAppName(AppUtils.getAppName());\n        baseInfoBean.setAppVersion(AppUtils.getAppVersionName());\n        baseInfoBean.setDokitVersion(BuildConfig.DOKIT_VERSION);\n        baseInfoBean.setPlatform(\"Android\");\n        baseInfoBean.setPhoneMode(DeviceUtils.getModel());\n        baseInfoBean.setTime(TimeUtils.getNowString());\n        baseInfoBean.setSystemVersion(DeviceUtils.getSDKVersionName());\n        baseInfoBean.setpId(\"\" + DoKitManager.PRODUCT_ID);\n        mAppHealthInfo.setBaseInfo(baseInfoBean);\n    }\n\n    /**\n     * 设置app启动耗时的具体信息\n     *\n     * @param costTime\n     * @param costDetail\n     */\n    public void setAppStartInfo(long costTime, String costDetail, List<AppHealthInfo.DataBean.AppStartBean.LoadFuncBean> loadFunc) {\n        AppHealthInfo.DataBean.AppStartBean appStartBean = new AppHealthInfo.DataBean.AppStartBean();\n        appStartBean.setCostTime(costTime);\n        appStartBean.setCostDetail(costDetail);\n        appStartBean.setLoadFunc(loadFunc);\n        getData().setAppStart(appStartBean);\n    }\n\n    /**\n     * 添加cpu信息\n     *\n     * @param cpuBean\n     */\n    public void addCPUInfo(AppHealthInfo.DataBean.PerformanceBean cpuBean) {\n        List<AppHealthInfo.DataBean.PerformanceBean> cpus = getData().getCpu();\n        if (cpus == null) {\n            cpus = new ArrayList<>();\n            getData().setCpu(cpus);\n        }\n        //不过滤最大最小值\n        //cpuBean.setValues(sortValue(cpuBean.getValues()));\n        cpus.add(cpuBean);\n    }\n\n\n    /**\n     * 添加memory信息\n     *\n     * @param memoryBean\n     */\n    public void addMemoryInfo(AppHealthInfo.DataBean.PerformanceBean memoryBean) {\n        List<AppHealthInfo.DataBean.PerformanceBean> memories = getData().getMemory();\n        if (memories == null) {\n            memories = new ArrayList<>();\n            getData().setMemory(memories);\n        }\n        //不过滤最大最小值\n        //memoryBean.setValues(sortValue(memoryBean.getValues()));\n        memories.add(memoryBean);\n    }\n\n    /**\n     * 添加fps信息\n     *\n     * @param fpsBean\n     */\n    public void addFPSInfo(AppHealthInfo.DataBean.PerformanceBean fpsBean) {\n        List<AppHealthInfo.DataBean.PerformanceBean> fpsBeans = getData().getFps();\n        if (fpsBeans == null) {\n            fpsBeans = new ArrayList<>();\n            getData().setFps(fpsBeans);\n        }\n        //不过滤最大最小值\n        //fpsBean.setValues(sortValue(fpsBean.getValues()));\n        fpsBeans.add(fpsBean);\n    }\n\n    /**\n     * 获取当前最后一个PerformanceInfo信息\n     *\n     * @return PerformanceBean\n     */\n    public AppHealthInfo.DataBean.PerformanceBean getLastPerformanceInfo(int performanceType) {\n        List<AppHealthInfo.DataBean.PerformanceBean> performanceBeans = null;\n        if (performanceType == PerformanceDataManager.PERFORMANCE_TYPE_CPU) {\n            performanceBeans = getData().getCpu();\n        } else if (performanceType == PerformanceDataManager.PERFORMANCE_TYPE_MEMORY) {\n            performanceBeans = getData().getMemory();\n        } else if (performanceType == PerformanceDataManager.PERFORMANCE_TYPE_FPS) {\n            performanceBeans = getData().getFps();\n        }\n        if (performanceBeans == null || performanceBeans.size() == 0) {\n            return null;\n        }\n        return performanceBeans.get(performanceBeans.size() - 1);\n    }\n\n    /**\n     * 移除满足条件的最后一个PerformanceInfo信息\n     *\n     * @return PerformanceBean\n     */\n    public void removeLastPerformanceInfo(int performanceType) {\n        List<AppHealthInfo.DataBean.PerformanceBean> performanceBeans = null;\n        if (performanceType == PerformanceDataManager.PERFORMANCE_TYPE_CPU) {\n            performanceBeans = getData().getCpu();\n        } else if (performanceType == PerformanceDataManager.PERFORMANCE_TYPE_MEMORY) {\n            performanceBeans = getData().getMemory();\n        } else if (performanceType == PerformanceDataManager.PERFORMANCE_TYPE_FPS) {\n            performanceBeans = getData().getFps();\n        }\n        if (performanceBeans != null && performanceBeans.size() > 0) {\n            performanceBeans.remove(performanceBeans.size() - 1);\n        }\n    }\n\n\n    /**\n     * 添加网络信息\n     *\n     * @param networkBean\n     */\n    public void addNetWorkInfo(AppHealthInfo.DataBean.NetworkBean networkBean) {\n        List<AppHealthInfo.DataBean.NetworkBean> networks = getData().getNetwork();\n        if (networks == null) {\n            networks = new ArrayList<>();\n            getData().setNetwork(networks);\n        }\n        networks.add(networkBean);\n    }\n\n    /**\n     * 获取指定的NetworkBean\n     *\n     * @param activityName\n     */\n    public AppHealthInfo.DataBean.NetworkBean getNetWorkInfo(String activityName) {\n        List<AppHealthInfo.DataBean.NetworkBean> networks = getData().getNetwork();\n        if (networks == null || networks.size() == 0) {\n            return null;\n        }\n        AppHealthInfo.DataBean.NetworkBean networkBean = null;\n\n        for (AppHealthInfo.DataBean.NetworkBean traverseNetworkBean : networks) {\n            if (traverseNetworkBean.getPage().equals(activityName)) {\n                networkBean = traverseNetworkBean;\n                break;\n            }\n        }\n\n        return networkBean;\n    }\n\n    /**\n     * 添加卡顿信息\n     *\n     * @param blockBean\n     */\n    public void addBlockInfo(AppHealthInfo.DataBean.BlockBean blockBean) {\n        List<AppHealthInfo.DataBean.BlockBean> blocks = getData().getBlock();\n        if (blocks == null) {\n            blocks = new ArrayList<>();\n            getData().setBlock(blocks);\n        }\n        blocks.add(blockBean);\n    }\n\n\n    /**\n     * 添加页面层级信息\n     *\n     * @param uiLevelBean\n     */\n    public void addUiLevelInfo(AppHealthInfo.DataBean.UiLevelBean uiLevelBean) {\n        List<AppHealthInfo.DataBean.UiLevelBean> uiLevels = getData().getUiLevel();\n        if (uiLevels == null) {\n            uiLevels = new ArrayList<>();\n            getData().setUiLevel(uiLevels);\n        }\n        uiLevels.add(uiLevelBean);\n    }\n\n    /**\n     * 添加内存泄漏信息\n     *\n     * @param leakBean\n     */\n    public void addLeakInfo(AppHealthInfo.DataBean.LeakBean leakBean) {\n        List<AppHealthInfo.DataBean.LeakBean> leaks = getData().getLeak();\n        if (leaks == null) {\n            leaks = new ArrayList<>();\n            getData().setLeak(leaks);\n        }\n        leaks.add(leakBean);\n    }\n\n    /**\n     * 添加页面加载耗时信息\n     *\n     * @param pageLoadBean\n     */\n    public void addPageLoadInfo(AppHealthInfo.DataBean.PageLoadBean pageLoadBean) {\n        List<AppHealthInfo.DataBean.PageLoadBean> pageloads = getData().getPageLoad();\n        if (pageloads == null) {\n            pageloads = new ArrayList<>();\n            getData().setPageLoad(pageloads);\n        }\n        pageloads.add(pageLoadBean);\n    }\n\n    /**\n     * 添加页面加载耗时信息\n     *\n     * @param bigFileBean\n     */\n    public void addBigFilrInfo(AppHealthInfo.DataBean.BigFileBean bigFileBean) {\n        List<AppHealthInfo.DataBean.BigFileBean> bigFiles = getData().getBigFile();\n        if (bigFiles == null) {\n            bigFiles = new ArrayList<>();\n            getData().setBigFile(bigFiles);\n        }\n        bigFiles.add(bigFileBean);\n    }\n\n\n    /**\n     * 上传健康体检数据到服务器\n     */\n    public void post(final UploadAppHealthCallback uploadAppHealthCallBack) throws Exception {\n        if (mAppHealthInfo == null) {\n            return;\n        }\n        //线上地址：https://www.dokit.cn/healthCheck/addCheckData\n        //测试环境地址:http://dokit-test.intra.xiaojukeji.com/healthCheck/addCheckData\n\n        Request<JSONObject> request = new JsonObjectRequest(Request.Method.POST, NetworkManager.APP_HEALTH_URL, new JSONObject(GsonUtils.toJson(mAppHealthInfo)), new Response.Listener<JSONObject>() {\n            @Override\n            public void onResponse(JSONObject response) {\n                if (uploadAppHealthCallBack != null) {\n                    uploadAppHealthCallBack.onSuccess(response.toString());\n                }\n            }\n        }, new com.android.volley.Response.ErrorListener() {\n            @Override\n            public void onErrorResponse(VolleyError error) {\n                if (uploadAppHealthCallBack != null) {\n                    uploadAppHealthCallBack.onError(error.getMessage());\n                }\n            }\n        });\n        VolleyManager.INSTANCE.add(request);\n    }\n\n    /**\n     * 获取data对象\n     *\n     * @return\n     */\n    private AppHealthInfo.DataBean getData() {\n        if (mAppHealthInfo.getData() == null) {\n            AppHealthInfo.DataBean dataBean = new AppHealthInfo.DataBean();\n            dataBean.setCpu(new ArrayList<AppHealthInfo.DataBean.PerformanceBean>());\n            dataBean.setMemory(new ArrayList<AppHealthInfo.DataBean.PerformanceBean>());\n            dataBean.setFps(new ArrayList<AppHealthInfo.DataBean.PerformanceBean>());\n            dataBean.setNetwork(new ArrayList<AppHealthInfo.DataBean.NetworkBean>());\n            dataBean.setBlock(new ArrayList<AppHealthInfo.DataBean.BlockBean>());\n            dataBean.setUiLevel(new ArrayList<AppHealthInfo.DataBean.UiLevelBean>());\n            dataBean.setLeak(new ArrayList<AppHealthInfo.DataBean.LeakBean>());\n            dataBean.setPageLoad(new ArrayList<AppHealthInfo.DataBean.PageLoadBean>());\n            dataBean.setBigFile(new ArrayList<AppHealthInfo.DataBean.BigFileBean>());\n            dataBean.setSubThreadUI(new ArrayList<AppHealthInfo.DataBean.SubThreadUIBean>());\n            mAppHealthInfo.setData(dataBean);\n        }\n        return mAppHealthInfo.getData();\n    }\n\n    /**\n     * 时候处于app 健康体检状态\n     *\n     * @return\n     */\n    public boolean isAppHealthRunning() {\n        boolean isRunning = DoKitManager.APP_HEALTH_RUNNING;\n        if (isRunning) {\n            ToastUtils.showShort(\"App当前处于健康体检状态,无法进行此操作\");\n        }\n        return isRunning;\n    }\n\n    /**\n     * 开启健康体检监控\n     */\n    public void start() {\n        PerformanceDataManager.getInstance().init();\n        //帧率\n        PerformanceDataManager.getInstance().startMonitorFrameInfo();\n        //cpu\n        PerformanceDataManager.getInstance().startMonitorCPUInfo();\n        //内存\n        PerformanceDataManager.getInstance().startMonitorMemoryInfo();\n        //网络\n        PerformanceDataManager.getInstance().startMonitorNetFlowInfo();\n        //卡顿\n        BlockMonitorManager.getInstance().start();\n        //crash 开关\n        CrashCaptureConfig.setCrashCaptureOpen(true);\n        CrashCaptureManager.getInstance().start();\n    }\n\n    /**\n     * 结束健康体检监控\n     */\n    public void stop() {\n        //帧率\n        PerformanceDataManager.getInstance().stopMonitorFrameInfo();\n        //cpu\n        PerformanceDataManager.getInstance().stopMonitorCPUInfo();\n        //内存\n        PerformanceDataManager.getInstance().stopMonitorMemoryInfo();\n        //网络\n        PerformanceDataManager.getInstance().stopMonitorNetFlowInfo();\n        //卡顿\n        BlockMonitorManager.getInstance().stop();\n        //crash 开关\n        CrashCaptureConfig.setCrashCaptureOpen(false);\n        CrashCaptureManager.getInstance().stop();\n\n    }\n\n    /**\n     * list 去掉最大值和最小值 并重新 排序\n     */\n    private List<AppHealthInfo.DataBean.PerformanceBean.ValuesBean> sortValue(List<AppHealthInfo.DataBean.PerformanceBean.ValuesBean> valuesBeans) {\n        List<AppHealthInfo.DataBean.PerformanceBean.ValuesBean> newValuesBeans = new ArrayList<>(valuesBeans);\n        Collections.sort(newValuesBeans, new Comparator<AppHealthInfo.DataBean.PerformanceBean.ValuesBean>() {\n            @Override\n            public int compare(AppHealthInfo.DataBean.PerformanceBean.ValuesBean pre, AppHealthInfo.DataBean.PerformanceBean.ValuesBean next) {\n                float preValue = Float.parseFloat(pre.getValue());\n                float nextValue = Float.parseFloat(next.getValue());\n                if (preValue < nextValue) {\n                    return -1;\n                } else {\n                    return 1;\n                }\n\n            }\n        });\n        newValuesBeans.remove(0);\n        newValuesBeans.remove(newValuesBeans.size() - 1);\n        Collections.sort(newValuesBeans, new Comparator<AppHealthInfo.DataBean.PerformanceBean.ValuesBean>() {\n            @Override\n            public int compare(AppHealthInfo.DataBean.PerformanceBean.ValuesBean pre, AppHealthInfo.DataBean.PerformanceBean.ValuesBean next) {\n                long preValue = Long.parseLong(pre.getTime());\n                long nextValue = Long.parseLong(next.getTime());\n                if (preValue < nextValue) {\n                    return -1;\n                } else {\n                    return 1;\n                }\n            }\n        });\n        return newValuesBeans;\n\n    }\n\n    /**\n     * 内存释放\n     */\n    public void release() {\n        if (mAppHealthInfo != null) {\n            mAppHealthInfo = null;\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/health/CountDownDoKitView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.health\n\nimport android.content.Context\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams\nimport com.didichuxing.doraemonkit.util.ConvertUtils\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.flow.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-27-17:59\n * 描    述：页面倒计时浮标\n * 修订历史：\n * ================================================\n */\nclass CountDownDoKitView : AbsCountDownDoKitView() {\n    private var mNum: TextView? = null\n    private lateinit var countDownFlow: Flow<Int>\n    private var countDownJob: Job? = null\n    override fun onCreate(context: Context?) {\n        countDownFlow = flow {\n            (10 downTo 0).forEach {\n                emit(it)\n                delay(1500)\n            }\n        }.flowOn(Dispatchers.IO)\n            .onCompletion {\n                doKitViewScope.launch {\n                    DoKit.removeFloating(CountDownDoKitView::class)\n                }\n            }\n    }\n\n\n    override fun onCreateView(context: Context, rootView: FrameLayout): View {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_count_down, rootView, false)\n    }\n\n    override fun onViewCreated(rootView: FrameLayout) {\n        mNum = findViewById(R.id.tv_number)\n        startCountDown()\n    }\n\n    private fun startCountDown() {\n        countDownJob?.let {\n            if (it.isActive) {\n                it.cancel()\n            }\n        }\n        countDownJob = doKitViewScope.launch {\n            countDownFlow.collect {\n                //LogHelper.i(TAG, \"$activity  ${this@CountDownDoKitView}===>$it\")\n                withContext(Dispatchers.Main) {\n                    mNum?.text = it.toString()\n                }\n\n            }\n\n        }\n\n    }\n\n    override fun onResume() {\n        super.onResume()\n        if (isNormalMode) {\n            immInvalidate()\n        }\n    }\n\n    /**\n     * 重置倒计时 系统倒计时需要\n     */\n    override fun reset() {\n        startCountDown()\n    }\n\n    override fun initDokitViewLayoutParams(params: DoKitViewLayoutParams) {\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT\n        params.width = DoKitViewLayoutParams.WRAP_CONTENT\n        params.gravity = Gravity.TOP or Gravity.LEFT\n        params.x = ConvertUtils.dp2px(280f)\n        params.y = ConvertUtils.dp2px(25f)\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/health/HealthFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.health;\n\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentPagerAdapter;\n\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\nimport com.didichuxing.doraemonkit.widget.verticalviewpager.VerticalViewPager;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 健康体检fragment\n */\npublic class HealthFragment extends BaseFragment {\n    VerticalViewPager mVerticalViewPager;\n    HomeTitleBar mHomeTitleBar;\n    List<Fragment> mFragments = new ArrayList<>();\n    FragmentPagerAdapter mFragmentPagerAdapter;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_health;\n    }\n\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        if (getActivity() == null) {\n            return;\n        }\n        initView();\n    }\n\n    private void initView() {\n        mFragments.clear();\n        mFragments.add(new HealthFragmentChild0());\n        mFragments.add(new HealthFragmentChild1());\n        mHomeTitleBar = findViewById(R.id.title_bar);\n        mHomeTitleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                finish();\n            }\n        });\n        mVerticalViewPager = findViewById(R.id.view_pager);\n        mFragmentPagerAdapter = new FragmentPagerAdapter(getChildFragmentManager()) {\n            @Override\n            public Fragment getItem(int position) {\n                return mFragments.get(position);\n            }\n\n            @Override\n            public int getCount() {\n                return mFragments.size();\n            }\n        };\n        mVerticalViewPager.setAdapter(mFragmentPagerAdapter);\n    }\n\n    /**\n     * 滑动到顶部\n     */\n    protected void scroll2theTop() {\n        if (mVerticalViewPager != null && mFragmentPagerAdapter != null) {\n            mVerticalViewPager.setCurrentItem(0, true);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/health/HealthFragmentChild0.java",
    "content": "package com.didichuxing.doraemonkit.kit.health;\n\nimport android.content.DialogInterface;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AlertDialog;\n\nimport com.didichuxing.doraemonkit.util.AppUtils;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.GlobalConfig;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil;\nimport com.didichuxing.doraemonkit.util.LogHelper;\nimport com.didichuxing.doraemonkit.widget.dialog.DialogListener;\nimport com.didichuxing.doraemonkit.widget.dialog.DialogProvider;\nimport com.didichuxing.doraemonkit.widget.dialog.UniversalDialogFragment;\n\n/**\n * 健康体检fragment\n */\npublic class HealthFragmentChild0 extends BaseFragment {\n    TextView mTitle;\n    ImageView mController;\n    UserInfoDialogProvider mUserInfoDialogProvider;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_health_child0;\n    }\n\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        if (getActivity() == null) {\n            return;\n        }\n\n        mTitle = findViewById(R.id.tv_title);\n        mController = findViewById(R.id.iv_btn);\n        if (DoKitManager.APP_HEALTH_RUNNING) {\n            mTitle.setVisibility(View.VISIBLE);\n            mController.setImageResource(R.mipmap.dk_health_stop);\n        } else {\n            mTitle.setVisibility(View.INVISIBLE);\n            mController.setImageResource(R.mipmap.dk_health_start);\n        }\n        mUserInfoDialogProvider = new UserInfoDialogProvider(null, new DialogListener() {\n            @Override\n            public boolean onPositive(DialogProvider<?> dialogProvider) {\n                if (mUserInfoDialogProvider != null) {\n                    //上传健康体检数据\n                    boolean isCheck = mUserInfoDialogProvider.uploadAppHealthInfo(new UploadAppHealthCallback() {\n                        @Override\n                        public void onSuccess(String response) {\n                            LogHelper.i(TAG, \"上传成功===>\" + response);\n                            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_health_upload_successed));\n                            //重置状态\n                            GlobalConfig.setAppHealth(false);\n                            DoKitManager.APP_HEALTH_RUNNING = false;\n                            mTitle.setVisibility(View.INVISIBLE);\n                            mController.setImageResource(R.mipmap.dk_health_start);\n                            //关闭健康体检监控\n                            AppHealthInfoUtil.getInstance().stop();\n                            AppHealthInfoUtil.getInstance().release();\n                        }\n\n                        @Override\n                        public void onError(String response) {\n                            LogHelper.e(TAG, \"error response===>\" + response);\n                            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_health_upload_failed));\n                        }\n                    });\n\n                    return isCheck;\n                }\n\n                return true;\n\n\n            }\n\n            @Override\n            public boolean onNegative(DialogProvider<?> dialogProvider) {\n                return true;\n            }\n\n            @Override\n            public void onCancel(DialogProvider<?> dialogProvider) {\n                ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_health_upload_droped));\n                //重置状态\n                GlobalConfig.setAppHealth(false);\n                DoKitManager.APP_HEALTH_RUNNING = false;\n                mTitle.setVisibility(View.INVISIBLE);\n                mController.setImageResource(R.mipmap.dk_health_start);\n                //关闭健康体检监控\n                AppHealthInfoUtil.getInstance().stop();\n                AppHealthInfoUtil.getInstance().release();\n            }\n        });\n        mController.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (getActivity() == null) {\n                    return;\n                }\n                //当前处于健康体检状态\n                if (DoKitManager.APP_HEALTH_RUNNING) {\n                    if (mUserInfoDialogProvider != null) {\n                        showDialog(mUserInfoDialogProvider);\n                    }\n                } else {\n                    new AlertDialog.Builder(getActivity())\n                            .setTitle(DoKitCommUtil.getString(R.string.dk_health_upload_title))\n                            .setMessage(DoKitCommUtil.getString(R.string.dk_health_upload_message))\n                            .setCancelable(false)\n                            .setPositiveButton(\"ok\", new DialogInterface.OnClickListener() {\n                                @Override\n                                public void onClick(DialogInterface dialog, int which) {\n                                    dialog.dismiss();\n                                    if (mController != null) {\n                                        ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_health_funcation_start));\n                                        GlobalConfig.setAppHealth(true);\n                                        DoKitManager.APP_HEALTH_RUNNING = true;\n                                        //重启app\n                                        mController.postDelayed(new Runnable() {\n                                            @Override\n                                            public void run() {\n                                                AppUtils.relaunchApp();\n                                                android.os.Process.killProcess(android.os.Process.myPid());\n                                                System.exit(1);\n                                            }\n                                        }, 2000);\n                                    }\n                                }\n                            })\n                            .setNegativeButton(\"cancel\", new DialogInterface.OnClickListener() {\n                                @Override\n                                public void onClick(DialogInterface dialog, int which) {\n                                    dialog.dismiss();\n                                }\n                            }).show();\n\n                }\n            }\n        });\n    }\n\n    @Override\n    public void showDialog(DialogProvider provider) {\n        UniversalDialogFragment dialog = new UniversalDialogFragment();\n        provider.setHost(dialog);\n        dialog.setProvider(provider);\n        provider.show(getChildFragmentManager());\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/health/HealthFragmentChild1.java",
    "content": "package com.didichuxing.doraemonkit.kit.health;\n\nimport android.os.Bundle;\nimport android.view.View;\nimport android.widget.LinearLayout;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\n\n/**\n * 健康体检fragment\n */\npublic class HealthFragmentChild1 extends BaseFragment {\n    LinearLayout mLlBackTop;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_health_child1;\n    }\n\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n\n        mLlBackTop = findViewById(R.id.ll_back_top);\n        mLlBackTop.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                Fragment parentFragment = getParentFragment();\n                if (parentFragment instanceof HealthFragment) {\n                    ((HealthFragment) parentFragment).scroll2theTop();\n                }\n            }\n        });\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/health/HealthKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.health\n\nimport android.app.Activity\nimport android.content.Context\nimport android.text.TextUtils\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.google.auto.service.AutoService\n\n/**\n * @author jintai\n * @desc: 一键体检kit\n */\n@AutoService(AbstractKit::class)\nclass HealthKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_health\n    override val icon: Int\n        get() = R.mipmap.dk_health\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        if (TextUtils.isEmpty(DoKitManager.PRODUCT_ID)) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_platform_tip))\n            return false\n        }\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip))\n            return false\n        }\n        if (!DokitPluginConfig.SWITCH_NETWORK) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_network_close_tip))\n            return false\n        }\n        if (!DokitPluginConfig.SWITCH_METHOD) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_method_close_tip))\n            return false\n        }\n        startUniversalActivity(HealthFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_platform_ck_health\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/health/UploadAppHealthCallback.java",
    "content": "package com.didichuxing.doraemonkit.kit.health;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020-01-07-15:42\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic interface UploadAppHealthCallback {\n    /**\n     * @param response\n     */\n    void onSuccess(String response);\n\n    /**\n     * @param response\n     */\n\n    void onError(String response);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/health/UserInfoDialogProvider.java",
    "content": "package com.didichuxing.doraemonkit.kit.health;\n\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.widget.dialog.DialogListener;\nimport com.didichuxing.doraemonkit.widget.dialog.DialogProvider;\n\n/**\n * Created by jint on 2019/4/12\n * 完善健康体检用户信息dialog\n * @author jintai\n */\npublic class UserInfoDialogProvider<T> extends DialogProvider<T> {\n    private TextView mPositive;\n    private TextView mNegative;\n    private TextView mClose;\n    private EditText mCaseName;\n    private EditText mUserName;\n\n    UserInfoDialogProvider(T data, DialogListener listener) {\n        super(data, listener);\n    }\n\n    @Override\n    public int getLayoutId() {\n        return R.layout.dk_dialog_userinfo;\n    }\n\n    @Override\n    protected void findViews(View view) {\n        mPositive = view.findViewById(R.id.positive);\n        mNegative = view.findViewById(R.id.negative);\n        mClose = view.findViewById(R.id.close);\n        mCaseName = view.findViewById(R.id.edit_case_name);\n        mUserName = view.findViewById(R.id.edit_user_name);\n    }\n\n    @Override\n    protected void bindData(Object data) {\n\n    }\n\n    @Override\n    protected View getPositiveView() {\n        return mPositive;\n    }\n\n    @Override\n    protected View getNegativeView() {\n        return mNegative;\n    }\n\n    @Override\n    protected View getCancelView() {\n        return mClose;\n    }\n\n    /**\n     * 上传健康体检数据\n     */\n    boolean uploadAppHealthInfo(UploadAppHealthCallback uploadAppHealthCallBack) {\n        if (!userInfoCheck()) {\n            ToastUtils.showShort(\"请填写测试用例和测试人\");\n            return false;\n        }\n        String caseName = mCaseName.getText().toString();\n        String userName = mUserName.getText().toString();\n\n        AppHealthInfoUtil.getInstance().setBaseInfo(caseName, userName);\n        //上传数据\n        try {\n            AppHealthInfoUtil.getInstance().post(uploadAppHealthCallBack);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return true;\n    }\n\n    /**\n     * 检查用户数据\n     */\n    private boolean userInfoCheck() {\n        if (mCaseName == null || TextUtils.isEmpty(mCaseName.getText().toString())) {\n            return false;\n        }\n\n        if (mUserName == null || TextUtils.isEmpty(mUserName.getText().toString())) {\n            return false;\n        }\n\n        return true;\n    }\n\n    @Override\n    public boolean isCancellable() {\n        return false;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/health/model/AppHealthInfo.java",
    "content": "package com.didichuxing.doraemonkit.kit.health.model;\n\nimport com.google.gson.annotations.Expose;\n\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020-01-02-16:29\n * 描    述：app 健康体检上传服务器数据\n * 修订历史：\n * ================================================\n */\npublic class AppHealthInfo {\n    /**\n     * baseInfo : {\"caseName\":\"iOS5.0版本性能测试\",\"testPerson\":\"易小翔\",\"platfom\":\"iOS\",\"time\":\"2019-12-12 : 11:12:30\",\"phoneMode\":\"iphone6S\",\"systemVersion\":\"13\",\"appNmae\":\"Dokit\",\"appVersion\":\"1.0.0\",\"dokitVersion\":\"2.0.0\"}\n     * data : {\"appStart\":{\"costTime\":\"3200\",\"costDetail\":\"代码耗时字符串\",\"loadFunc\":[{\"className\":\"ClassA\",\"costTime\":\"15\"},{\"className\":\"ClassB\",\"costTime\":\"30\"}]},\"cpu\":[{\"page\":\"HomeViewController\",\"values\":[{\"time\":\"时间戳\",\"value\":\"0.5\"},{\"time\":\"时间戳\",\"value\":\"0.8\"}]},{\"page\":\"MapViewController\",\"values\":[{\"time\":\"时间戳\",\"value\":\"0.5\"},{\"time\":\"时间戳\",\"value\":\"0.8\"}]}],\"memory\":[{\"page\":\"HomeViewController\",\"values\":[{\"time\":\"时间戳\",\"value\":\"80\"},{\"time\":\"时间戳\",\"value\":\"81\"}]},{\"page\":\"MapViewController\",\"values\":[{\"time\":\"时间戳\",\"value\":\"90\"},{\"time\":\"时间戳\",\"value\":\"91\"}]}],\"fps\":[{\"page\":\"HomeViewController\",\"values\":[{\"time\":\"时间戳\",\"value\":\"60\"},{\"time\":\"时间戳\",\"value\":\"59\"}]},{\"page\":\"MapViewController\",\"values\":[{\"time\":\"时间戳\",\"value\":\"50\"},{\"time\":\"时间戳\",\"value\":\"60\"}]}],\"network\":[{\"page\":\"HomeViewController\",\"values\":[{\"time\":\"时间戳\",\"url\":\"http://www.baidu.com\",\"up\":\"100\",\"down\":\"200\",\"code\":\"200\",\"method\":\"Get\"},{\"time\":\"时间戳\",\"url\":\"http://www.taobao.com\",\"up\":\"100\",\"down\":\"200\",\"code\":\"200\",\"method\":\"Post\"}]},{\"page\":\"MapViewController\",\"values\":[{\"time\":\"时间戳\",\"url\":\"http://www.baidu.com\",\"up\":\"100\",\"down\":\"200\",\"code\":\"200\",\"method\":\"Get\"},{\"time\":\"时间戳\",\"url\":\"http://www.taobao.com\",\"up\":\"100\",\"down\":\"200\",\"code\":\"200\",\"method\":\"Post\"}]}],\"block\":[{\"page\":\"HomeViewController\",\"blockTime\":\"4.2\",\"detail\":\"卡顿堆栈\"},{\"page\":\"MapViewController\",\"blockTime\":\"5.2\",\"detail\":\"卡顿堆栈\"}],\"subThreadUI\":[{\"page\":\"HomeViewController\",\"detail\":\"代码堆栈\"},{\"page\":\"MapViewController\",\"detail\":\"代码堆栈\"}],\"uiLevel\":[{\"page\":\"HomeViewController\",\"level\":\"10\",\"detail\":\"层级引用链\"},{\"page\":\"MapViewController\",\"level\":\"10\",\"detail\":\"层级引用链\"}],\"leak\":[{\"page\":\"HomeViewController\",\"detail\":\"内存泄漏详情\"},{\"page\":\"MapViewController\",\"detail\":\"内存泄漏详情\"}],\"pageLoad\":[{\"page\":\"HomeViewController\",\"time\":\"120\"},{\"page\":\"MapViewController\",\"time\":\"120\"}],\"bigFile\":[{\"fileName\":\"fileName1\",\"fileSize\":\"30M\",\"filePath\":\"/data/json/fileName1\"}]}\n     */\n\n    private BaseInfoBean baseInfo;\n    private DataBean data;\n\n    public BaseInfoBean getBaseInfo() {\n        return baseInfo;\n    }\n\n    public void setBaseInfo(BaseInfoBean baseInfo) {\n        this.baseInfo = baseInfo;\n    }\n\n    public DataBean getData() {\n        return data;\n    }\n\n    public void setData(DataBean data) {\n        this.data = data;\n    }\n\n    public static class BaseInfoBean {\n        /**\n         * caseName : iOS5.0版本性能测试\n         * testPerson : 易小翔\n         * platfom : iOS\n         * time : 2019-12-12 : 11:12:30\n         * phoneMode : iphone6S\n         * systemVersion : 13\n         * appNmae : Dokit\n         * appVersion : 1.0.0\n         * dokitVersion : 2.0.0\n         */\n\n        private String caseName;\n        private String testPerson;\n        private String platform;\n        private String time;\n        private String phoneMode;\n        private String systemVersion;\n        private String appName;\n        private String appVersion;\n        private String dokitVersion;\n        private String pId;\n\n        public String getpId() {\n            return pId;\n        }\n\n        public void setpId(String pId) {\n            this.pId = pId;\n        }\n\n        public String getCaseName() {\n            return caseName;\n        }\n\n        public void setCaseName(String caseName) {\n            this.caseName = caseName;\n        }\n\n        public String getTestPerson() {\n            return testPerson;\n        }\n\n        public void setTestPerson(String testPerson) {\n            this.testPerson = testPerson;\n        }\n\n        public String getPlatform() {\n            return platform;\n        }\n\n        public void setPlatform(String platform) {\n            this.platform = platform;\n        }\n\n        public String getTime() {\n            return time;\n        }\n\n        public void setTime(String time) {\n            this.time = time;\n        }\n\n        public String getPhoneMode() {\n            return phoneMode;\n        }\n\n        public void setPhoneMode(String phoneMode) {\n            this.phoneMode = phoneMode;\n        }\n\n        public String getSystemVersion() {\n            return systemVersion;\n        }\n\n        public void setSystemVersion(String systemVersion) {\n            this.systemVersion = systemVersion;\n        }\n\n        public String getAppName() {\n            return appName;\n        }\n\n        public void setAppName(String appName) {\n            this.appName = appName;\n        }\n\n        public String getAppVersion() {\n            return appVersion;\n        }\n\n        public void setAppVersion(String appVersion) {\n            this.appVersion = appVersion;\n        }\n\n        public String getDokitVersion() {\n            return dokitVersion;\n        }\n\n        public void setDokitVersion(String dokitVersion) {\n            this.dokitVersion = dokitVersion;\n        }\n    }\n\n    public static class DataBean {\n        /**\n         * appStart : {\"costTime\":\"3200\",\"costDetail\":\"代码耗时字符串\",\"loadFunc\":[{\"className\":\"ClassA\",\"costTime\":\"15\"},{\"className\":\"ClassB\",\"costTime\":\"30\"}]}\n         * cpu : [{\"page\":\"HomeViewController\",\"values\":[{\"time\":\"时间戳\",\"value\":\"0.5\"},{\"time\":\"时间戳\",\"value\":\"0.8\"}]},{\"page\":\"MapViewController\",\"values\":[{\"time\":\"时间戳\",\"value\":\"0.5\"},{\"time\":\"时间戳\",\"value\":\"0.8\"}]}]\n         * memory : [{\"page\":\"HomeViewController\",\"values\":[{\"time\":\"时间戳\",\"value\":\"80\"},{\"time\":\"时间戳\",\"value\":\"81\"}]},{\"page\":\"MapViewController\",\"values\":[{\"time\":\"时间戳\",\"value\":\"90\"},{\"time\":\"时间戳\",\"value\":\"91\"}]}]\n         * fps : [{\"page\":\"HomeViewController\",\"values\":[{\"time\":\"时间戳\",\"value\":\"60\"},{\"time\":\"时间戳\",\"value\":\"59\"}]},{\"page\":\"MapViewController\",\"values\":[{\"time\":\"时间戳\",\"value\":\"50\"},{\"time\":\"时间戳\",\"value\":\"60\"}]}]\n         * network : [{\"page\":\"HomeViewController\",\"values\":[{\"time\":\"时间戳\",\"url\":\"http://www.baidu.com\",\"up\":\"100\",\"down\":\"200\",\"code\":\"200\",\"method\":\"Get\"},{\"time\":\"时间戳\",\"url\":\"http://www.taobao.com\",\"up\":\"100\",\"down\":\"200\",\"code\":\"200\",\"method\":\"Post\"}]},{\"page\":\"MapViewController\",\"values\":[{\"time\":\"时间戳\",\"url\":\"http://www.baidu.com\",\"up\":\"100\",\"down\":\"200\",\"code\":\"200\",\"method\":\"Get\"},{\"time\":\"时间戳\",\"url\":\"http://www.taobao.com\",\"up\":\"100\",\"down\":\"200\",\"code\":\"200\",\"method\":\"Post\"}]}]\n         * block : [{\"page\":\"HomeViewController\",\"blockTime\":\"4.2\",\"detail\":\"卡顿堆栈\"},{\"page\":\"MapViewController\",\"blockTime\":\"5.2\",\"detail\":\"卡顿堆栈\"}]\n         * subThreadUI : [{\"page\":\"HomeViewController\",\"detail\":\"代码堆栈\"},{\"page\":\"MapViewController\",\"detail\":\"代码堆栈\"}]\n         * uiLevel : [{\"page\":\"HomeViewController\",\"level\":\"10\",\"detail\":\"层级引用链\"},{\"page\":\"MapViewController\",\"level\":\"10\",\"detail\":\"层级引用链\"}]\n         * leak : [{\"page\":\"HomeViewController\",\"detail\":\"内存泄漏详情\"},{\"page\":\"MapViewController\",\"detail\":\"内存泄漏详情\"}]\n         * pageLoad : [{\"page\":\"HomeViewController\",\"time\":\"120\"},{\"page\":\"MapViewController\",\"time\":\"120\"}]\n         * bigFile : [{\"fileName\":\"fileName1\",\"fileSize\":\"30M\",\"filePath\":\"/data/json/fileName1\"}]\n         */\n\n        private AppStartBean appStart;\n        private List<PerformanceBean> cpu;\n        private List<PerformanceBean> memory;\n        private List<PerformanceBean> fps;\n        private List<NetworkBean> network;\n        private List<BlockBean> block;\n        private List<SubThreadUIBean> subThreadUI;\n        private List<UiLevelBean> uiLevel;\n        private List<LeakBean> leak;\n        private List<PageLoadBean> pageLoad;\n        private List<BigFileBean> bigFile;\n\n        public AppStartBean getAppStart() {\n            return appStart;\n        }\n\n        public void setAppStart(AppStartBean appStart) {\n            this.appStart = appStart;\n        }\n\n        public List<PerformanceBean> getCpu() {\n            return cpu;\n        }\n\n        public void setCpu(List<PerformanceBean> cpu) {\n            this.cpu = cpu;\n        }\n\n        public List<PerformanceBean> getMemory() {\n            return memory;\n        }\n\n        public void setMemory(List<PerformanceBean> memory) {\n            this.memory = memory;\n        }\n\n        public List<PerformanceBean> getFps() {\n            return fps;\n        }\n\n        public void setFps(List<PerformanceBean> fps) {\n            this.fps = fps;\n        }\n\n        public List<NetworkBean> getNetwork() {\n            return network;\n        }\n\n        public void setNetwork(List<NetworkBean> network) {\n            this.network = network;\n        }\n\n        public List<BlockBean> getBlock() {\n            return block;\n        }\n\n        public void setBlock(List<BlockBean> block) {\n            this.block = block;\n        }\n\n        public List<SubThreadUIBean> getSubThreadUI() {\n            return subThreadUI;\n        }\n\n        public void setSubThreadUI(List<SubThreadUIBean> subThreadUI) {\n            this.subThreadUI = subThreadUI;\n        }\n\n        public List<UiLevelBean> getUiLevel() {\n            return uiLevel;\n        }\n\n        public void setUiLevel(List<UiLevelBean> uiLevel) {\n            this.uiLevel = uiLevel;\n        }\n\n        public List<LeakBean> getLeak() {\n            return leak;\n        }\n\n        public void setLeak(List<LeakBean> leak) {\n            this.leak = leak;\n        }\n\n        public List<PageLoadBean> getPageLoad() {\n            return pageLoad;\n        }\n\n        public void setPageLoad(List<PageLoadBean> pageLoad) {\n            this.pageLoad = pageLoad;\n        }\n\n        public List<BigFileBean> getBigFile() {\n            return bigFile;\n        }\n\n        public void setBigFile(List<BigFileBean> bigFile) {\n            this.bigFile = bigFile;\n        }\n\n        public static class AppStartBean {\n            /**\n             * costTime : 3200\n             * costDetail : 代码耗时字符串\n             * loadFunc : [{\"className\":\"ClassA\",\"costTime\":\"15\"},{\"className\":\"ClassB\",\"costTime\":\"30\"}]\n             */\n\n            private long costTime;\n            private String costDetail;\n            private List<LoadFuncBean> loadFunc;\n\n            public long getCostTime() {\n                return costTime;\n            }\n\n            public void setCostTime(long costTime) {\n                this.costTime = costTime;\n            }\n\n            public String getCostDetail() {\n                return costDetail;\n            }\n\n            public void setCostDetail(String costDetail) {\n                this.costDetail = costDetail;\n            }\n\n            public List<LoadFuncBean> getLoadFunc() {\n                return loadFunc;\n            }\n\n            public void setLoadFunc(List<LoadFuncBean> loadFunc) {\n                this.loadFunc = loadFunc;\n            }\n\n            public static class LoadFuncBean {\n                /**\n                 * className : ClassA\n                 * costTime : 15\n                 */\n\n                private String className;\n                private String costTime;\n\n                public String getClassName() {\n                    return className;\n                }\n\n                public void setClassName(String className) {\n                    this.className = className;\n                }\n\n                public String getCostTime() {\n                    return costTime;\n                }\n\n                public void setCostTime(String costTime) {\n                    this.costTime = costTime;\n                }\n            }\n        }\n\n        /**\n         * cpu、内存、fps 共享的Bean\n         */\n        public static class PerformanceBean {\n            /**\n             * page : HomeViewController\n             * values : [{\"time\":\"时间戳\",\"value\":\"0.5\"},{\"time\":\"时间戳\",\"value\":\"0.8\"}]\n             */\n            @Expose\n            private String pageKey;\n            private String page;\n            private List<ValuesBean> values;\n\n            public String getPageKey() {\n                return pageKey;\n            }\n\n            public void setPageKey(String pageKey) {\n                this.pageKey = pageKey;\n            }\n\n            public String getPage() {\n                return page;\n            }\n\n            public void setPage(String page) {\n                this.page = page;\n            }\n\n            public List<ValuesBean> getValues() {\n                return values;\n            }\n\n            public void setValues(List<ValuesBean> values) {\n                this.values = values;\n            }\n\n            /**\n             * cpu、内存、fps 共享的ValueBean\n             */\n            public static class ValuesBean {\n                /**\n                 * time : 时间戳\n                 * value : 0.5\n                 */\n\n                private String time;\n                private String value;\n\n                public ValuesBean(String time, String value) {\n                    this.time = time;\n                    this.value = value;\n                }\n\n                public String getTime() {\n                    return time;\n                }\n\n                public void setTime(String time) {\n                    this.time = time;\n                }\n\n                public String getValue() {\n                    return value;\n                }\n\n                public void setValue(String value) {\n                    this.value = value;\n                }\n            }\n        }\n\n\n        public static class NetworkBean {\n            /**\n             * page : HomeViewController\n             * values : [{\"time\":\"时间戳\",\"url\":\"http://www.baidu.com\",\"up\":\"100\",\"down\":\"200\",\"code\":\"200\",\"method\":\"Get\"},{\"time\":\"时间戳\",\"url\":\"http://www.taobao.com\",\"up\":\"100\",\"down\":\"200\",\"code\":\"200\",\"method\":\"Post\"}]\n             */\n\n            private String page;\n            private List<NetworkValuesBean> values;\n\n            public String getPage() {\n                return page;\n            }\n\n            public void setPage(String page) {\n                this.page = page;\n            }\n\n            public List<NetworkValuesBean> getValues() {\n                return values;\n            }\n\n            public void setValues(List<NetworkValuesBean> values) {\n                this.values = values;\n            }\n\n            public static class NetworkValuesBean {\n                /**\n                 * time : 时间戳\n                 * url : http://www.baidu.com\n                 * up : 100\n                 * down : 200\n                 * code : 200\n                 * method : Get\n                 */\n\n                private String time;\n                private String url;\n                private String up;\n                private String down;\n                private String code;\n                private String method;\n\n                public String getTime() {\n                    return time;\n                }\n\n                public void setTime(String time) {\n                    this.time = time;\n                }\n\n                public String getUrl() {\n                    return url;\n                }\n\n                public void setUrl(String url) {\n                    this.url = url;\n                }\n\n                public String getUp() {\n                    return up;\n                }\n\n                public void setUp(String up) {\n                    this.up = up;\n                }\n\n                public String getDown() {\n                    return down;\n                }\n\n                public void setDown(String down) {\n                    this.down = down;\n                }\n\n                public String getCode() {\n                    return code;\n                }\n\n                public void setCode(String code) {\n                    this.code = code;\n                }\n\n                public String getMethod() {\n                    return method;\n                }\n\n                public void setMethod(String method) {\n                    this.method = method;\n                }\n            }\n        }\n\n        public static class BlockBean {\n            /**\n             * page : HomeViewController\n             * blockTime : 4.2\n             * detail : 卡顿堆栈\n             */\n\n            private String page;\n            private long blockTime;\n            private String detail;\n\n            public String getPage() {\n                return page;\n            }\n\n            public void setPage(String page) {\n                this.page = page;\n            }\n\n            public long getBlockTime() {\n                return blockTime;\n            }\n\n            public void setBlockTime(long blockTime) {\n                this.blockTime = blockTime;\n            }\n\n            public String getDetail() {\n                return detail;\n            }\n\n            public void setDetail(String detail) {\n                this.detail = detail;\n            }\n        }\n\n        public static class SubThreadUIBean {\n            /**\n             * page : HomeViewController\n             * detail : 代码堆栈\n             */\n\n            private String page;\n            private String detail;\n\n            public String getPage() {\n                return page;\n            }\n\n            public void setPage(String page) {\n                this.page = page;\n            }\n\n            public String getDetail() {\n                return detail;\n            }\n\n            public void setDetail(String detail) {\n                this.detail = detail;\n            }\n        }\n\n        public static class UiLevelBean {\n            /**\n             * page : HomeViewController\n             * level : 10\n             * detail : 层级引用链\n             */\n\n            private String page;\n            private String level;\n            private String detail;\n\n            public String getPage() {\n                return page;\n            }\n\n            public void setPage(String page) {\n                this.page = page;\n            }\n\n            public String getLevel() {\n                return level;\n            }\n\n            public void setLevel(String level) {\n                this.level = level;\n            }\n\n            public String getDetail() {\n                return detail;\n            }\n\n            public void setDetail(String detail) {\n                this.detail = detail;\n            }\n        }\n\n        public static class LeakBean {\n            /**\n             * page : HomeViewController\n             * detail : 内存泄漏详情\n             */\n\n            private String page;\n            private String detail;\n\n            public String getPage() {\n                return page;\n            }\n\n            public void setPage(String page) {\n                this.page = page;\n            }\n\n            public String getDetail() {\n                return detail;\n            }\n\n            public void setDetail(String detail) {\n                this.detail = detail;\n            }\n        }\n\n        public static class PageLoadBean {\n            /**\n             * page : HomeViewController\n             * time : 120\n             */\n\n            private String page;\n            private String time;\n            private String trace;\n\n            public String getPage() {\n                return page;\n            }\n\n            public void setPage(String page) {\n                this.page = page;\n            }\n\n            public String getTime() {\n                return time;\n            }\n\n            public void setTime(String time) {\n                this.time = time;\n            }\n\n\n            public String getTrace() {\n                return trace;\n            }\n\n            public void setTrace(String trace) {\n                this.trace = trace;\n            }\n        }\n\n        public static class BigFileBean {\n            /**\n             * fileName : fileName1\n             * fileSize : 30M\n             * filePath : /data/json/fileName1\n             */\n\n            private String fileName;\n            private String fileSize;\n            private String filePath;\n\n            public String getFileName() {\n                return fileName;\n            }\n\n            public void setFileName(String fileName) {\n                this.fileName = fileName;\n            }\n\n            public String getFileSize() {\n                return fileSize;\n            }\n\n            public void setFileSize(String fileSize) {\n                this.fileSize = fileSize;\n            }\n\n            public String getFilePath() {\n                return filePath;\n            }\n\n            public void setFilePath(String filePath) {\n                this.filePath = filePath;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/largepicture/LargeImageInfo.java",
    "content": "package com.didichuxing.doraemonkit.kit.largepicture;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-26-17:28\n * 描    述：大图信息\n * 修订历史：\n * ================================================\n */\npublic class LargeImageInfo {\n    private String framework = \"other\";\n    private String url;\n    private double fileSize;\n    private double memorySize;\n    private int width;\n    private int height;\n\n    public void setUrl(String url) {\n        this.url = url;\n    }\n\n    public double getFileSize() {\n        return fileSize;\n    }\n\n    public void setFileSize(double fileSize) {\n        this.fileSize = fileSize;\n    }\n\n    public void setMemorySize(double memorySize) {\n        this.memorySize = memorySize;\n    }\n\n    public void setWidth(int width) {\n        this.width = width;\n    }\n\n    public void setHeight(int height) {\n        this.height = height;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public double getMemorySize() {\n        return memorySize;\n    }\n\n    public int getWidth() {\n        return width;\n    }\n\n    public int getHeight() {\n        return height;\n    }\n\n\n    public String getFramework() {\n        return framework;\n    }\n\n    public void setFramework(String framework) {\n        this.framework = framework;\n    }\n\n    @Override\n    public String toString() {\n        return \"LargeImageInfo{\" +\n                \"framework='\" + framework + '\\'' +\n                \", url='\" + url + '\\'' +\n                \", fileSize='\" + fileSize + '\\'' +\n                \", memorySize='\" + memorySize + '\\'' +\n                \", width=\" + width +\n                \", height=\" + height +\n                '}';\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/largepicture/LargeImageListAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.largepicture;\n\nimport android.content.Context;\nimport android.net.Uri;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.DoKitEnv;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.picasso.DokitPicasso;\nimport com.didichuxing.doraemonkit.picasso.MemoryPolicy;\nimport com.didichuxing.doraemonkit.util.ConvertUtils;\nimport com.didichuxing.doraemonkit.util.DoKitClipboardUtils;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\n\nimport java.text.DecimalFormat;\n\npublic class LargeImageListAdapter extends AbsRecyclerAdapter<AbsViewBinder<LargeImageInfo>, LargeImageInfo> {\n    private DecimalFormat mDecimalFormat = new DecimalFormat(\"#0.00\");\n\n    public LargeImageListAdapter(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected AbsViewBinder<LargeImageInfo> createViewHolder(View view, int viewType) {\n        return new ItemViewHolder(view);\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        return inflater.inflate(R.layout.dk_item_large_img_list, parent, false);\n    }\n\n    private class ItemViewHolder extends AbsViewBinder<LargeImageInfo> {\n        private ImageView iv;\n        private TextView tvLink;\n        /**\n         * 框架\n         */\n        private TextView tvFrameWork;\n\n        /**\n         * 文件大小\n         */\n        private TextView tvFileSize;\n        /**\n         * 内存大小\n         */\n        private TextView tvMemorySize;\n        /**\n         * 图片尺寸\n         */\n        private TextView tvSize;\n\n        private Button btnCopy;\n\n        public ItemViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            iv = getView(R.id.iv);\n            tvLink = getView(R.id.tv_link);\n            tvFrameWork = getView(R.id.tv_framework);\n            tvFileSize = getView(R.id.tv_file_size);\n            tvMemorySize = getView(R.id.tv_memory_size);\n            tvSize = getView(R.id.tv_size);\n            btnCopy = getView(R.id.btn_copy);\n        }\n\n        @Override\n        public void bind(final LargeImageInfo largeImageInfo) {\n            try {\n                int resourceUrl = Integer.parseInt(largeImageInfo.getUrl());\n                DokitPicasso.with(DoKitEnv.requireApp())\n                        .load(resourceUrl)\n                        .memoryPolicy(MemoryPolicy.NO_CACHE)\n                        .resize(ConvertUtils.dp2px(100), ConvertUtils.dp2px(100))\n                        .centerCrop()\n                        .into(iv);\n                tvLink.setText(\"resource id:\" + resourceUrl);\n            } catch (Exception e) {\n                DokitPicasso.with(DoKitEnv.requireApp())\n                        .load(largeImageInfo.getUrl())\n                        .memoryPolicy(MemoryPolicy.NO_CACHE)\n                        .resize(ConvertUtils.dp2px(100), ConvertUtils.dp2px(100))\n                        .centerCrop()\n                        .into(iv);\n                tvLink.setText(largeImageInfo.getUrl());\n            }\n\n            if (largeImageInfo.getMemorySize() == 0) {\n                tvFrameWork.setText(String.format(\"framework:%s\", \"network\"));\n                tvMemorySize.setVisibility(View.GONE);\n                tvSize.setVisibility(View.GONE);\n            } else {\n                tvFrameWork.setText(String.format(\"framework:%s\", largeImageInfo.getFramework()));\n                tvMemorySize.setVisibility(View.VISIBLE);\n                tvSize.setVisibility(View.VISIBLE);\n            }\n            if (largeImageInfo.getFileSize() == 0) {\n                tvFileSize.setVisibility(View.GONE);\n            } else {\n                tvFileSize.setVisibility(View.VISIBLE);\n            }\n\n            tvFileSize.setText(String.format(\"fileSize:%s\", mDecimalFormat.format(largeImageInfo.getFileSize()) + \"KB\"));\n            tvMemorySize.setText(String.format(\"memorySize:%s\", mDecimalFormat.format(largeImageInfo.getMemorySize()) + \"MB\"));\n            tvSize.setText(String.format(\"width:%s   height:%s\", largeImageInfo.getWidth(), largeImageInfo.getHeight()));\n            btnCopy.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    DoKitClipboardUtils.copyUri(Uri.parse(largeImageInfo.getUrl()));\n                    ToastUtils.showShort(\"image url  has copied\");\n                }\n\n            });\n        }\n\n\n    }\n\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/largepicture/LargeImageListFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.largepicture;\n\nimport android.os.Bundle;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.PerformanceSpInfoConfig;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.recyclerview.DividerItemDecoration;\nimport com.didichuxing.doraemonkit.widget.titlebar.TitleBar;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @desc: 大图检测列表\n */\n\npublic class LargeImageListFragment extends BaseFragment {\n    private static final String TAG = \"LargeImageListFragment\";\n\n    private RecyclerView mLargeImageList;\n    private LargeImageListAdapter mLargeImageListAdapter;\n    private TitleBar mTitleBar;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_large_img_list;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n        load();\n    }\n\n\n    private void initView() {\n        mLargeImageList = findViewById(R.id.block_list);\n\n        LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());\n        mLargeImageList.setLayoutManager(layoutManager);\n        mLargeImageListAdapter = new LargeImageListAdapter(getContext());\n        mLargeImageList.setAdapter(mLargeImageListAdapter);\n        DividerItemDecoration decoration = new DividerItemDecoration(DividerItemDecoration.VERTICAL);\n        decoration.setDrawable(getResources().getDrawable(R.drawable.dk_divider));\n        mLargeImageList.addItemDecoration(decoration);\n\n        mTitleBar = findViewById(R.id.title_bar);\n        mTitleBar.setOnTitleBarClickListener(new TitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onLeftClick() {\n                getActivity().onBackPressed();\n            }\n\n            @Override\n            public void onRightClick() {\n\n            }\n        });\n    }\n\n    private double fileThreshold = PerformanceSpInfoConfig.getLargeImgFileThreshold(LargePictureManager.FILE_DEFAULT_THRESHOLD);\n    private double memoryThreshold = PerformanceSpInfoConfig.getLargeImgMemoryThreshold(LargePictureManager.MEMORY_DEFAULT_THRESHOLD);\n\n\n    private void load() {\n        List<LargeImageInfo> imageInfos = new ArrayList<>();\n        for (LargeImageInfo largeImageInfo : LargePictureManager.LARGE_IMAGE_INFO_MAP.values()) {\n            if (largeImageInfo.getFileSize() < fileThreshold && largeImageInfo.getMemorySize() < memoryThreshold) {\n                continue;\n            }\n            imageInfos.add(largeImageInfo);\n        }\n        mLargeImageListAdapter.setData(imageInfos);\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/largepicture/LargePictureFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.largepicture;\n\nimport android.os.Bundle;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.text.Editable;\nimport android.text.TextUtils;\nimport android.text.TextWatcher;\nimport android.view.View;\nimport android.widget.EditText;\n\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.PerformanceSpInfoConfig;\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\n\nimport java.text.DecimalFormat;\n\n/**\n * 大图功能检测\n */\npublic class LargePictureFragment extends BaseFragment {\n    private LargePictureItemAdapter mSettingItemAdapter;\n    private RecyclerView mSettingList;\n\n    private DecimalFormat mDecimalFormat = new DecimalFormat(\"0.00\");\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_performance_large_picture_setting;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n    }\n\n    private void initView() {\n        HomeTitleBar titleBar = findViewById(R.id.title_bar);\n        titleBar.setTitle(R.string.dk_category_large_image);\n        //TextView tvDesc = findViewById(R.id.tv_desc);\n        //tvDesc.setText(Html.fromHtml(getResources().getString(R.string.dk_large_picture_threshold_desc)));\n\n        EditText fileEditText = findViewById(R.id.ed_file_threshold);\n        fileEditText.addTextChangedListener(new TextWatcher() {\n            @Override\n            public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n\n            }\n\n            @Override\n            public void onTextChanged(CharSequence s, int start, int before, int count) {\n\n            }\n\n            @Override\n            public void afterTextChanged(Editable s) {\n                try {\n                    if (TextUtils.isEmpty(s)) {\n                        ToastUtils.showShort(\"value can not null\");\n                        return;\n                    }\n                    if (Float.parseFloat(s.toString()) < 0) {\n                        ToastUtils.showShort(\"value can not  < 0\");\n                        return;\n                    }\n                    float value = Float.parseFloat(s.toString());\n                    float formateValue = Float.parseFloat(mDecimalFormat.format(value));\n                    //设置文件大小\n                    PerformanceSpInfoConfig.setLargeImgFileThreshold(formateValue);\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n            }\n        });\n\n\n        EditText memoryEditText = findViewById(R.id.ed_memory_threshold);\n        memoryEditText.addTextChangedListener(new TextWatcher() {\n            @Override\n            public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n\n            }\n\n            @Override\n            public void onTextChanged(CharSequence s, int start, int before, int count) {\n\n\n            }\n\n            @Override\n            public void afterTextChanged(Editable s) {\n                try {\n                    if (TextUtils.isEmpty(s)) {\n                        ToastUtils.showShort(\"value can not null\");\n                        return;\n                    }\n                    if (Float.parseFloat(s.toString()) < 0) {\n                        ToastUtils.showShort(\"value can not  < 0\");\n                        return;\n                    }\n                    float value = Float.parseFloat(s.toString());\n                    float formateValue = Float.parseFloat(mDecimalFormat.format(value));\n                    //设置内存大小\n                    PerformanceSpInfoConfig.setLargeImgMemoryThreshold(formateValue);\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n            }\n        });\n\n        double fileThreshold = PerformanceSpInfoConfig.getLargeImgFileThreshold(LargePictureManager.FILE_DEFAULT_THRESHOLD);\n        fileEditText.setText(mDecimalFormat.format(fileThreshold));\n\n        double memoryThreshold = PerformanceSpInfoConfig.getLargeImgMemoryThreshold(LargePictureManager.MEMORY_DEFAULT_THRESHOLD);\n        memoryEditText.setText(mDecimalFormat.format(memoryThreshold));\n\n\n        titleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                getActivity().finish();\n            }\n        });\n        mSettingList = findViewById(R.id.setting_list);\n        mSettingList.setLayoutManager(new LinearLayoutManager(getContext()));\n        mSettingItemAdapter = new LargePictureItemAdapter(getContext());\n        mSettingItemAdapter.append(new SettingItem(R.string.dk_large_picture_switch, PerformanceSpInfoConfig.isLargeImgOpen()));\n        mSettingItemAdapter.append(new SettingItem(R.string.dk_large_picture_look, R.mipmap.dk_more_icon));\n\n        mSettingItemAdapter.setOnSettingItemSwitchListener(new LargePictureItemAdapter.OnSettingItemSwitchListener() {\n            @Override\n            public void onSettingItemSwitch(View view, SettingItem data, boolean on) {\n                if (data.desc == R.string.dk_large_picture_switch) {\n                    PerformanceSpInfoConfig.setLargeImgOpen(on);\n                    if (on) {\n                        if (!NetworkManager.isActive()) {\n                            NetworkManager.get().startMonitor();\n                        }\n                    } else {\n                        NetworkManager.get().stopMonitor();\n                        //清空缓存\n                        LargePictureManager.LARGE_IMAGE_INFO_MAP.clear();\n                    }\n                }\n            }\n        });\n        mSettingItemAdapter.setOnSettingItemClickListener(new LargePictureItemAdapter.OnSettingItemClickListener() {\n            @Override\n            public void onSettingItemClick(View view, SettingItem data) {\n                if (data.desc == R.string.dk_large_picture_look) {\n                    showContent(LargeImageListFragment.class);\n                }\n            }\n        });\n        mSettingList.setAdapter(mSettingItemAdapter);\n\n    }\n\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/largepicture/LargePictureItemAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.largepicture;\n\nimport android.content.Context;\nimport android.text.TextUtils;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.CheckBox;\nimport android.widget.CompoundButton;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\n\n/**\n * Created by jintai on 2019/9/24.\n */\n\npublic class LargePictureItemAdapter extends AbsRecyclerAdapter<AbsViewBinder<SettingItem>, SettingItem> {\n    private OnSettingItemClickListener mOnSettingItemClickListener;\n    private OnSettingItemSwitchListener mOnSettingItemSwitchListener;\n\n    public LargePictureItemAdapter(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected AbsViewBinder<SettingItem> createViewHolder(View view, int viewType) {\n        return new SettingItemViewHolder(view);\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        return inflater.inflate(R.layout.dk_item_setting, parent, false);\n    }\n\n    public class SettingItemViewHolder extends AbsViewBinder<SettingItem> {\n        private TextView mDesc;\n        private CheckBox mMenuSwitch;\n        private ImageView mIcon;\n        private TextView mRightDesc;\n\n        public SettingItemViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            mMenuSwitch = getView(R.id.menu_switch);\n            mDesc = getView(R.id.desc);\n            mIcon = getView(R.id.right_icon);\n            mRightDesc = getView(R.id.right_desc);\n        }\n\n        @Override\n        public void bind(final SettingItem settingItem) {\n            mDesc.setText(settingItem.desc);\n            if (settingItem.canCheck) {\n                mMenuSwitch.setVisibility(View.VISIBLE);\n                mMenuSwitch.setChecked(settingItem.isChecked);\n                mMenuSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {\n                    @Override\n                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {\n                        settingItem.isChecked = isChecked;\n                        mOnSettingItemSwitchListener.onSettingItemSwitch(mMenuSwitch, settingItem, isChecked);\n                    }\n                });\n            }\n            if (settingItem.icon != 0) {\n                mIcon.setVisibility(View.VISIBLE);\n                mIcon.setImageResource(settingItem.icon);\n            }\n            if (!TextUtils.isEmpty(settingItem.rightDesc)) {\n                mRightDesc.setVisibility(View.VISIBLE);\n                mRightDesc.setText(settingItem.rightDesc);\n            }\n        }\n\n        @Override\n        protected void onViewClick(View view, SettingItem data) {\n            super.onViewClick(view, data);\n            if (mOnSettingItemClickListener != null) {\n                mOnSettingItemClickListener.onSettingItemClick(view, data);\n            }\n        }\n    }\n\n    public void setOnSettingItemClickListener(OnSettingItemClickListener onSettingItemClickListener) {\n        mOnSettingItemClickListener = onSettingItemClickListener;\n    }\n\n    public void setOnSettingItemSwitchListener(OnSettingItemSwitchListener onSettingItemSwitchListener) {\n        mOnSettingItemSwitchListener = onSettingItemSwitchListener;\n    }\n\n    public interface OnSettingItemClickListener {\n        void onSettingItemClick(View view, SettingItem data);\n    }\n\n    public interface OnSettingItemSwitchListener {\n        void onSettingItemSwitch(View view, SettingItem data, boolean on);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/largepicture/LargePictureKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.largepicture\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.google.auto.service.AutoService\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-24-17:05\n * 描    述：网络大图检测功能入口\n * 修订历史：\n * ================================================\n */\n@AutoService(AbstractKit::class)\nclass LargePictureKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_frameinfo_big_img\n    override val icon: Int\n        get() = R.mipmap.dk_performance_large_picture\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip))\n            return false\n        }\n        if (!DokitPluginConfig.SWITCH_BIG_IMG) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_big_img_close_tip))\n            return false\n        }\n        startUniversalActivity(LargePictureFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_performance_ck_img\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/largepicture/LargePictureManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.largepicture;\n\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.config.PerformanceSpInfoConfig;\nimport com.didichuxing.doraemonkit.kit.core.UniversalActivity;\n\nimport java.text.DecimalFormat;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * @author: linjizong\n * 2019-07-05\n * @desc: 大图检测管理类\n */\npublic class LargePictureManager {\n    public static float MEMORY_DEFAULT_THRESHOLD = 1.0f;\n    public static float FILE_DEFAULT_THRESHOLD = 150.0f;\n    private double fileThreshold = PerformanceSpInfoConfig.getLargeImgFileThreshold(FILE_DEFAULT_THRESHOLD);\n    private double memoryThreshold = PerformanceSpInfoConfig.getLargeImgMemoryThreshold(MEMORY_DEFAULT_THRESHOLD);\n    private static final String TAG = \"LargePictureManager\";\n    private DecimalFormat mDecimalFormat = new DecimalFormat(\"0.00\");\n\n    public void setFileThreshold(double fileThreshold) {\n        this.fileThreshold = fileThreshold;\n    }\n\n    public void setMemoryThreshold(double memoryThreshold) {\n        this.memoryThreshold = memoryThreshold;\n    }\n\n    /**\n     * 保存大图信息\n     */\n    public static Map<String, LargeImageInfo> LARGE_IMAGE_INFO_MAP = new HashMap<>();\n\n    public static LargePictureManager getInstance() {\n        return Holder.INSTANCE;\n    }\n\n    private static class Holder {\n        private static LargePictureManager INSTANCE = new LargePictureManager();\n    }\n\n\n    /**\n     * @param url\n     * @param size\n     */\n    public void process(String url, int size) {\n        if (ActivityUtils.getTopActivity() instanceof UniversalActivity) {\n            return;\n        }\n        if (PerformanceSpInfoConfig.isLargeImgOpen()) {\n            //转化成kb\n            double fileSize = (double) (size / 1024.0);\n            saveImageInfo(url, fileSize);\n        }\n\n    }\n\n    /**\n     * 保存网络图片信息\n     *\n     * @param url\n     * @param fileSize\n     */\n    private void saveImageInfo(String url, double fileSize) {\n        if (ActivityUtils.getTopActivity() instanceof UniversalActivity) {\n            return;\n        }\n        LargeImageInfo largeImageInfo;\n        if (LARGE_IMAGE_INFO_MAP.containsKey(url)) {\n            largeImageInfo = LARGE_IMAGE_INFO_MAP.get(url);\n        } else {\n            largeImageInfo = new LargeImageInfo();\n            LARGE_IMAGE_INFO_MAP.put(url, largeImageInfo);\n            largeImageInfo.setUrl(url);\n        }\n        largeImageInfo.setFileSize(fileSize);\n    }\n\n\n    /**\n     * 保存在内部里的图片信息\n     *\n     * @param url\n     * @param memorySize\n     * @param width\n     * @param height\n     */\n    public void saveImageInfo(String url, double memorySize, int width, int height, String framework) {\n        if (ActivityUtils.getTopActivity() instanceof UniversalActivity) {\n            return;\n        }\n        LargeImageInfo largeImageInfo;\n        if (LARGE_IMAGE_INFO_MAP.containsKey(url)) {\n            largeImageInfo = LARGE_IMAGE_INFO_MAP.get(url);\n        } else {\n            largeImageInfo = new LargeImageInfo();\n            LARGE_IMAGE_INFO_MAP.put(url, largeImageInfo);\n            largeImageInfo.setUrl(url);\n        }\n        largeImageInfo.setMemorySize(memorySize);\n        largeImageInfo.setWidth(width);\n        largeImageInfo.setHeight(height);\n        largeImageInfo.setFramework(framework);\n    }\n\n//    public Bitmap transform(String imageUrl, BitmapDrawable bitmapDrawable, boolean isPicasso, String framework) {\n//        Bitmap sourceBitmap = ImageUtils.drawable2Bitmap(bitmapDrawable);\n//        return transform(imageUrl, sourceBitmap, isPicasso, framework);\n//    }\n\n\n//    public Bitmap transform(String imageUrl, Bitmap sourceBitmap, boolean isPicasso, String framework) {\n//        if (ActivityUtils.getTopActivity() instanceof UniversalActivity) {\n//            return sourceBitmap;\n//        }\n//        if (PerformanceSpInfoConfig.isLargeImgOpen()) {\n//            double imgSize = ConvertUtils.byte2MemorySize(sourceBitmap.getByteCount(), MemoryConstants.MB);\n//            String strImageSize = mDecimalFormat.format(imgSize) + \"MB\";\n//            //picasso需要创建新的bitmap进行操作\n//            if (isPicasso) {\n//                Bitmap newBitmap = Bitmap.createBitmap(sourceBitmap);\n//                saveImageInfo(imageUrl, imgSize, newBitmap.getWidth(), newBitmap.getHeight(), framework);\n//                if (imgSize > memoryThreshold) {\n//                    newBitmap = ImageUtils.addTextWatermark(newBitmap, \"MS:\" + strImageSize, ConvertUtils.sp2px(16), Color.RED, newBitmap.getWidth() / 2 - ConvertUtils.sp2px(16) * strImageSize.length() / 2, newBitmap.getHeight() / 2);\n//                    sourceBitmap.recycle();\n//                    return newBitmap;\n//                } else {\n//                    return sourceBitmap;\n//                }\n//\n//            }\n//\n//            saveImageInfo(imageUrl, imgSize, sourceBitmap.getWidth(), sourceBitmap.getHeight(), framework);\n//            if (imgSize > memoryThreshold) {\n//                sourceBitmap = ImageUtils.addTextWatermark(sourceBitmap, \"MS:\" + strImageSize, ConvertUtils.sp2px(16), Color.RED, sourceBitmap.getWidth() / 2 - ConvertUtils.sp2px(16) * strImageSize.length() / 2, sourceBitmap.getHeight() / 2);\n//            }\n//            return sourceBitmap;\n//        }\n//        return sourceBitmap;\n//    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/layoutborder/LayoutBorderKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.layoutborder\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.config.LayoutBorderConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n/**\n * Created by wanglikun on 2019/1/7\n */\n@AutoService(AbstractKit::class)\nclass LayoutBorderKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_layout_border\n    override val icon: Int\n        get() = R.mipmap.dk_view_border\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        DoKit.launchFloating(LayoutLevelDoKitView::class.java)\n        LayoutBorderManager.getInstance().start()\n        LayoutBorderConfig.setLayoutBorderOpen(true)\n        LayoutBorderConfig.setLayoutLevelOpen(true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n        LayoutBorderConfig.setLayoutBorderOpen(false)\n        LayoutBorderConfig.setLayoutLevelOpen(false)\n    }\n\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String = \"dokit_sdk_ui_ck_border\"\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/layoutborder/LayoutBorderManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.layoutborder;\n\nimport android.app.Activity;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\n\nimport androidx.fragment.app.Fragment;\n\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.kit.core.UniversalActivity;\nimport com.didichuxing.doraemonkit.util.LifecycleListenerUtil;\n\n/**\n * Created by wanglikun on 2019/1/9\n */\npublic class LayoutBorderManager {\n    private boolean isRunning;\n\n    private ViewBorderFrameLayout mViewBorderFrameLayout;\n\n    private LifecycleListenerUtil.LifecycleListener mLifecycleListener = new LifecycleListenerUtil.LifecycleListener() {\n        @Override\n        public void onActivityResumed(Activity activity) {\n            //UIUtils.getDokitAppContentView(activity).setId(R.id.dokit_app_contentview_id);\n            resolveActivity(activity);\n        }\n\n        @Override\n        public void onActivityPaused(Activity activity) {\n\n        }\n\n        @Override\n        public void onFragmentAttached(Fragment f) {\n\n        }\n\n        @Override\n        public void onFragmentDetached(Fragment f) {\n            if (mViewBorderFrameLayout != null) {\n                mViewBorderFrameLayout = null;\n            }\n        }\n    };\n\n    private void resolveActivity(Activity activity) {\n        if (activity == null || (activity instanceof UniversalActivity)) {\n            return;\n        }\n        Window window = activity.getWindow();\n        if (window == null) {\n            return;\n        }\n        final ViewGroup root = (ViewGroup) window.getDecorView();\n        if (root == null) {\n            return;\n        }\n        mViewBorderFrameLayout = new ViewBorderFrameLayout(root.getContext());\n        while (root.getChildCount() != 0) {\n            View child = root.getChildAt(0);\n            if (child instanceof ViewBorderFrameLayout) {\n                mViewBorderFrameLayout = (ViewBorderFrameLayout) child;\n                return;\n            }\n            root.removeView(child);\n            mViewBorderFrameLayout.addView(child);\n        }\n        root.addView(mViewBorderFrameLayout);\n    }\n\n    private static class Holder {\n        private static LayoutBorderManager INSTANCE = new LayoutBorderManager();\n    }\n\n    private LayoutBorderManager() {\n    }\n\n    public static LayoutBorderManager getInstance() {\n        return Holder.INSTANCE;\n    }\n\n    public void start() {\n        isRunning = true;\n        resolveActivity(ActivityUtils.getTopActivity());\n        LifecycleListenerUtil.registerListener(mLifecycleListener);\n    }\n\n    public void stop() {\n        isRunning = false;\n        if (mViewBorderFrameLayout != null) {\n            mViewBorderFrameLayout.requestLayout();\n        }\n        mViewBorderFrameLayout = null;\n        LifecycleListenerUtil.unRegisterListener(mLifecycleListener);\n    }\n\n    public boolean isRunning() {\n        return isRunning;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/layoutborder/LayoutBorderSettingFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.layoutborder;\n\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.LayoutBorderConfig;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\n\n/**\n * Created by wanglikun on 2018/10/9.\n */\n\npublic class LayoutBorderSettingFragment extends BaseFragment {\n    private static final String TAG = \"LayoutBorderSettingFragment\";\n    private RecyclerView mSettingList;\n    private SettingItemAdapter mSettingItemAdapter;\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n    }\n\n    private void initView() {\n        HomeTitleBar titleBar = findViewById(R.id.title_bar);\n        titleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                finish();\n            }\n        });\n        mSettingList = findViewById(R.id.setting_list);\n        mSettingList.setLayoutManager(new LinearLayoutManager(getContext()));\n        mSettingItemAdapter = new SettingItemAdapter(getContext());\n        mSettingItemAdapter.append(new SettingItem(R.string.dk_kit_layout_border, LayoutBorderConfig.isLayoutBorderOpen()));\n        mSettingItemAdapter.append(new SettingItem(R.string.dk_layout_level, LayoutBorderConfig.isLayoutLevelOpen()));\n        mSettingItemAdapter.setOnSettingItemSwitchListener(new SettingItemAdapter.OnSettingItemSwitchListener() {\n            @Override\n            public void onSettingItemSwitch(View view, SettingItem data, boolean on) {\n                if (data.desc == R.string.dk_kit_layout_border) {\n                    if (on) {\n                        LayoutBorderManager.getInstance().start();\n                    } else {\n                        LayoutBorderManager.getInstance().stop();\n                    }\n                    LayoutBorderConfig.setLayoutBorderOpen(on);\n                } else if (data.desc == R.string.dk_layout_level) {\n                    if (on) {\n                        DoKit.launchFloating(LayoutLevelDoKitView.class);\n                    } else {\n                        DoKit.removeFloating(LayoutLevelDoKitView.class);\n                    }\n                    LayoutBorderConfig.setLayoutLevelOpen(on);\n                }\n            }\n        });\n        mSettingList.setAdapter(mSettingItemAdapter);\n    }\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_layout_border_setting;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/layoutborder/LayoutLevelDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.layoutborder;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.widget.CheckBox;\nimport android.widget.CompoundButton;\nimport android.widget.FrameLayout;\n\nimport androidx.fragment.app.Fragment;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.datapick.DataPickUtils;\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.LayoutBorderConfig;\nimport com.didichuxing.doraemonkit.datapick.DataPickManager;\nimport com.didichuxing.doraemonkit.kit.core.UniversalActivity;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.util.LifecycleListenerUtil;\nimport com.didichuxing.doraemonkit.util.LogHelper;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\n/**\n * Created by jintai on 2019/09/26.\n */\npublic class LayoutLevelDoKitView extends AbsDoKitView {\n    private static final String TAG = \"LayoutLevelDokitView\";\n    private CheckBox mSwitchButton;\n    private View mClose;\n\n    private ScalpelFrameLayout mScalpelFrameLayout;\n\n    private boolean mIsCheck;\n    private LifecycleListenerUtil.LifecycleListener mLifecycleListener = new LifecycleListenerUtil.LifecycleListener() {\n        @Override\n        public void onActivityResumed(Activity activity) {\n            resolveActivity(activity);\n        }\n\n        @Override\n        public void onActivityPaused(Activity activity) {\n\n        }\n\n        @Override\n        public void onFragmentAttached(Fragment f) {\n\n        }\n\n        @Override\n        public void onFragmentDetached(Fragment f) {\n\n        }\n    };\n\n    private void resolveActivity(Activity activity) {\n        if (activity == null || (activity instanceof UniversalActivity)) {\n            return;\n        }\n        Window window = activity.getWindow();\n        if (window == null) {\n            return;\n        }\n        ViewGroup appContentView;\n        if (isNormalMode()) {\n            appContentView = (ViewGroup) UIUtils.getDokitAppContentView(activity);\n        } else {\n            appContentView = (ViewGroup) window.getDecorView();\n        }\n\n        if (appContentView == null) {\n            ToastUtils.showShort(\"当前根布局功能不支持\");\n            return;\n        }\n\n        if (appContentView.toString().contains(\"SwipeBackLayout\")) {\n            LogHelper.i(TAG, \"普通模式下布局层级功能暂不支持以SwipeBackLayout为根布局,请改用系统模式\");\n            ToastUtils.showLong(\"普通模式下布局层级功能暂不支持以SwipeBackLayout为根布局\");\n            return;\n        }\n\n        //将所有控件放入到ScalpelFrameLayout中\n        mScalpelFrameLayout = new ScalpelFrameLayout(appContentView.getContext());\n        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);\n        while (appContentView.getChildCount() != 0) {\n            View child = appContentView.getChildAt(0);\n            if (child instanceof ScalpelFrameLayout) {\n                mScalpelFrameLayout = (ScalpelFrameLayout) child;\n                return;\n            }\n            appContentView.removeView(child);\n            mScalpelFrameLayout.addView(child);\n        }\n        mScalpelFrameLayout.setLayerInteractionEnabled(mIsCheck);\n        mScalpelFrameLayout.setLayoutParams(params);\n        appContentView.addView(mScalpelFrameLayout);\n    }\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_layout_level, view, false);\n    }\n\n    @Override\n    public void onViewCreated(FrameLayout view) {\n        mSwitchButton = findViewById(R.id.switch_btn);\n        mSwitchButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {\n            @Override\n            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {\n                if (isChecked) {\n                    if (mScalpelFrameLayout != null) {\n                        mScalpelFrameLayout.setLayerInteractionEnabled(true);\n                    }\n                    //发送埋点\n                    DataPickManager.getInstance().addData(\"dokit_sdk_ui_ck_widget_3d\", DataPickUtils.getCurrentPage());\n                } else {\n                    if (mScalpelFrameLayout != null) {\n                        mScalpelFrameLayout.setLayerInteractionEnabled(false);\n                    }\n                }\n                mIsCheck = isChecked;\n            }\n        });\n        mClose = findViewById(R.id.close);\n        mClose.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n\n                if (mScalpelFrameLayout != null) {\n                    mScalpelFrameLayout.setLayerInteractionEnabled(false);\n                }\n                LayoutBorderConfig.setLayoutLevelOpen(false);\n\n                LayoutBorderConfig.setLayoutBorderOpen(false);\n                LayoutBorderManager.getInstance().stop();\n\n                DoKit.removeFloating(LayoutLevelDoKitView.class);\n            }\n        });\n\n    }\n\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.gravity = Gravity.CENTER_HORIZONTAL;\n        params.x = 0;\n        params.y = UIUtils.getHeightPixels() - UIUtils.dp2px(125);\n        //解决页面跳转是view的宽度会发生变化\n        params.width = getScreenShortSideLength();\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT;\n    }\n\n    @Override\n    public void onCreate(Context context) {\n        resolveActivity(ActivityUtils.getTopActivity());\n        LifecycleListenerUtil.registerListener(mLifecycleListener);\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        if (mScalpelFrameLayout != null) {\n            mScalpelFrameLayout.setLayerInteractionEnabled(false);\n            mScalpelFrameLayout = null;\n        }\n        LifecycleListenerUtil.unRegisterListener(mLifecycleListener);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/layoutborder/ScalpelFrameLayout.java",
    "content": "package com.didichuxing.doraemonkit.kit.layoutborder;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.content.res.Resources.NotFoundException;\nimport android.graphics.Camera;\nimport android.graphics.Canvas;\nimport android.graphics.Matrix;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\nimport android.graphics.Typeface;\nimport android.os.Build;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.util.SparseArray;\nimport android.view.MotionEvent;\nimport android.view.SurfaceView;\nimport android.view.View;\nimport android.view.ViewConfiguration;\nimport android.view.ViewGroup;\nimport android.widget.FrameLayout;\nimport java.util.ArrayDeque;\nimport java.util.BitSet;\nimport java.util.Deque;\n\nimport static android.graphics.Paint.ANTI_ALIAS_FLAG;\nimport static android.graphics.Paint.Style.STROKE;\nimport static android.graphics.Typeface.NORMAL;\nimport static android.os.Build.VERSION_CODES.JELLY_BEAN;\nimport static android.view.MotionEvent.ACTION_DOWN;\nimport static android.view.MotionEvent.ACTION_POINTER_UP;\nimport static android.view.MotionEvent.INVALID_POINTER_ID;\n\n/**\n * Renders your view hierarchy as an interactive 3D visualization of layers.\n * <p>\n * Interactions supported:\n * <ul>\n * <li>Single touch: controls the rotation of the model.</li>\n * <li>Two finger vertical pinch: Adjust zoom.</li>\n * <li>Two finger horizontal pinch: Adjust layer spacing.</li>\n * </ul>\n */\npublic class ScalpelFrameLayout extends FrameLayout {\n    private static final int TRACKING_UNKNOWN = 0;\n    private static final int TRACKING_VERTICALLY = 1;\n    private static final int TRACKING_HORIZONTALLY = -1;\n    private static final int ROTATION_MAX = 60;\n    private static final int ROTATION_MIN = -ROTATION_MAX;\n    private static final int ROTATION_DEFAULT_X = -10;\n    private static final int ROTATION_DEFAULT_Y = 15;\n    private static final float ZOOM_DEFAULT = 0.6f;\n    private static final float ZOOM_MIN = 0.33f;\n    private static final float ZOOM_MAX = 2f;\n    private static final int SPACING_DEFAULT = 25;\n    private static final int SPACING_MIN = 10;\n    private static final int SPACING_MAX = 100;\n    private static final int CHROME_COLOR = 0xFF888888;\n    private static final int CHROME_SHADOW_COLOR = 0xFF000000;\n    private static final int TEXT_OFFSET_DP = 2;\n    private static final int TEXT_SIZE_DP = 10;\n    private static final int CHILD_COUNT_ESTIMATION = 25;\n    private static final boolean DEBUG = false;\n\n    private static void log(String message, Object... args) {\n        Log.d(\"Scalpel\", String.format(message, args));\n    }\n\n    private static class LayeredView {\n        View view;\n        int layer;\n\n        void set(View view, int layer) {\n            this.view = view;\n            this.layer = layer;\n        }\n\n        void clear() {\n            view = null;\n            layer = -1;\n        }\n    }\n\n    private final Rect viewBoundsRect = new Rect();\n    private final Paint viewBorderPaint = new Paint(ANTI_ALIAS_FLAG);\n    private final Camera camera = new Camera();\n    private final Matrix matrix = new Matrix();\n    private final int[] location = new int[2];\n    private final BitSet visibilities = new BitSet(CHILD_COUNT_ESTIMATION);\n    private final SparseArray<String> idNames = new SparseArray<>();\n    private final Deque<LayeredView> layeredViewQueue = new ArrayDeque<>();\n    private final Pool<LayeredView> layeredViewPool = new Pool<LayeredView>(CHILD_COUNT_ESTIMATION) {\n        @Override protected LayeredView newObject() {\n            return new LayeredView();\n        }\n    };\n\n    private final Resources res;\n    private final float density;\n    private final float slop;\n    private final float textOffset;\n    private final float textSize;\n\n    private boolean enabled;\n    private boolean drawViews = true;\n    private boolean drawIds;\n\n    private int pointerOne = INVALID_POINTER_ID;\n    private float lastOneX;\n    private float lastOneY;\n    private int pointerTwo = INVALID_POINTER_ID;\n    private float lastTwoX;\n    private float lastTwoY;\n    private int multiTouchTracking = TRACKING_UNKNOWN;\n\n    private float rotationY = ROTATION_DEFAULT_Y;\n    private float rotationX = ROTATION_DEFAULT_X;\n    private float zoom = ZOOM_DEFAULT;\n    private float spacing = SPACING_DEFAULT;\n\n    private int chromeColor;\n    private int chromeShadowColor;\n\n    public ScalpelFrameLayout(Context context) {\n        this(context, null);\n    }\n\n    public ScalpelFrameLayout(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public ScalpelFrameLayout(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        res = context.getResources();\n        density = context.getResources().getDisplayMetrics().density;\n        slop = ViewConfiguration.get(context).getScaledTouchSlop();\n\n        textSize = TEXT_SIZE_DP * density;\n        textOffset = TEXT_OFFSET_DP * density;\n\n        setChromeColor(CHROME_COLOR);\n        viewBorderPaint.setStyle(STROKE);\n        viewBorderPaint.setTextSize(textSize);\n        setChromeShadowColor(CHROME_SHADOW_COLOR);\n        if (Build.VERSION.SDK_INT >= JELLY_BEAN) {\n            viewBorderPaint.setTypeface(Typeface.create(\"sans-serif-condensed\", NORMAL));\n        }\n    }\n\n    /** Set the view border chrome color. */\n    public void setChromeColor(int color) {\n        if (chromeColor != color) {\n            viewBorderPaint.setColor(color);\n            chromeColor = color;\n            invalidate();\n        }\n    }\n\n    /** Get the view border chrome color. */\n    public int getChromeColor() {\n        return chromeColor;\n    }\n\n    /** Set the view border chrome shadow color. */\n    public void setChromeShadowColor(int color) {\n        if (chromeShadowColor != color) {\n            viewBorderPaint.setShadowLayer(1, -1, 1, color);\n            chromeShadowColor = color;\n            invalidate();\n        }\n    }\n\n    /** Get the view border chrome shadow color. */\n    public int getChromeShadowColor() {\n        return chromeShadowColor;\n    }\n\n    /** Set whether or not the 3D view layer interaction is enabled. */\n    public void setLayerInteractionEnabled(boolean enabled) {\n        if (this.enabled != enabled) {\n            this.enabled = enabled;\n            setWillNotDraw(!enabled);\n            invalidate();\n        }\n    }\n\n    /** Returns true when 3D view layer interaction is enabled. */\n    public boolean isLayerInteractionEnabled() {\n        return enabled;\n    }\n\n    /** Set whether the view layers draw their contents. When false, only wireframes are shown. */\n    public void setDrawViews(boolean drawViews) {\n        if (this.drawViews != drawViews) {\n            this.drawViews = drawViews;\n            invalidate();\n        }\n    }\n\n    /** Returns true when view layers draw their contents. */\n    public boolean isDrawingViews() {\n        return drawViews;\n    }\n\n    /** Set whether the view layers draw their IDs. */\n    public void setDrawIds(boolean drawIds) {\n        if (this.drawIds != drawIds) {\n            this.drawIds = drawIds;\n            invalidate();\n        }\n    }\n\n    /** Returns true when view layers draw their IDs. */\n    public boolean isDrawingIds() {\n        return drawIds;\n    }\n\n    @Override public boolean onInterceptTouchEvent(MotionEvent ev) {\n        return enabled || super.onInterceptTouchEvent(ev);\n    }\n\n    @Override public boolean onTouchEvent(@SuppressWarnings(\"NullableProblems\") MotionEvent event) {\n        if (!enabled) {\n            return super.onTouchEvent(event);\n        }\n\n        int action = event.getActionMasked();\n        switch (action) {\n            case MotionEvent.ACTION_DOWN:\n            case MotionEvent.ACTION_POINTER_DOWN: {\n                int index = (action == ACTION_DOWN) ? 0 : event.getActionIndex();\n                if (pointerOne == INVALID_POINTER_ID) {\n                    pointerOne = event.getPointerId(index);\n                    lastOneX = event.getX(index);\n                    lastOneY = event.getY(index);\n                    if (DEBUG) log(\"Got pointer 1!  id: %s  x: %s  y: %s\", pointerOne, lastOneY, lastOneY);\n                } else if (pointerTwo == INVALID_POINTER_ID) {\n                    pointerTwo = event.getPointerId(index);\n                    lastTwoX = event.getX(index);\n                    lastTwoY = event.getY(index);\n                    if (DEBUG) log(\"Got pointer 2!  id: %s  x: %s  y: %s\", pointerTwo, lastTwoY, lastTwoY);\n                } else {\n                    if (DEBUG) log(\"Ignoring additional pointer.  id: %s\", event.getPointerId(index));\n                }\n                break;\n            }\n\n            case MotionEvent.ACTION_MOVE: {\n                if (pointerTwo == INVALID_POINTER_ID) {\n                    // Single pointer controlling 3D rotation.\n                    for (int i = 0, count = event.getPointerCount(); i < count; i++) {\n                        if (pointerOne == event.getPointerId(i)) {\n                            float eventX = event.getX(i);\n                            float eventY = event.getY(i);\n                            float dx = eventX - lastOneX;\n                            float dy = eventY - lastOneY;\n                            float drx = 90 * (dx / getWidth());\n                            float dry = 90 * (-dy / getHeight()); // Invert Y-axis.\n                            // An 'x' delta affects 'y' rotation and vise versa.\n                            rotationY = Math.min(Math.max(rotationY + drx, ROTATION_MIN), ROTATION_MAX);\n                            rotationX = Math.min(Math.max(rotationX + dry, ROTATION_MIN), ROTATION_MAX);\n                            if (DEBUG) {\n                                log(\"Single pointer moved (%s, %s) affecting rotation (%s, %s).\", dx, dy, drx, dry);\n                            }\n\n                            lastOneX = eventX;\n                            lastOneY = eventY;\n\n                            invalidate();\n                        }\n                    }\n                } else {\n                    // We know there's two pointers and we only care about pointerOne and pointerTwo\n                    int pointerOneIndex = event.findPointerIndex(pointerOne);\n                    int pointerTwoIndex = event.findPointerIndex(pointerTwo);\n\n                    float xOne = event.getX(pointerOneIndex);\n                    float yOne = event.getY(pointerOneIndex);\n                    float xTwo = event.getX(pointerTwoIndex);\n                    float yTwo = event.getY(pointerTwoIndex);\n\n                    float dxOne = xOne - lastOneX;\n                    float dyOne = yOne - lastOneY;\n                    float dxTwo = xTwo - lastTwoX;\n                    float dyTwo = yTwo - lastTwoY;\n\n                    if (multiTouchTracking == TRACKING_UNKNOWN) {\n                        float adx = Math.abs(dxOne) + Math.abs(dxTwo);\n                        float ady = Math.abs(dyOne) + Math.abs(dyTwo);\n\n                        if (adx > slop * 2 || ady > slop * 2) {\n                            if (adx > ady) {\n                                // Left/right movement wins. Track horizontal.\n                                multiTouchTracking = TRACKING_HORIZONTALLY;\n                            } else {\n                                // Up/down movement wins. Track vertical.\n                                multiTouchTracking = TRACKING_VERTICALLY;\n                            }\n                        }\n                    }\n\n                    if (multiTouchTracking == TRACKING_VERTICALLY) {\n                        if (yOne >= yTwo) {\n                            zoom += dyOne / getHeight() - dyTwo / getHeight();\n                        } else {\n                            zoom += dyTwo / getHeight() - dyOne / getHeight();\n                        }\n\n                        zoom = Math.min(Math.max(zoom, ZOOM_MIN), ZOOM_MAX);\n                        invalidate();\n                    } else if (multiTouchTracking == TRACKING_HORIZONTALLY) {\n                        if (xOne >= xTwo) {\n                            spacing += (dxOne / getWidth() * SPACING_MAX) - (dxTwo / getWidth() * SPACING_MAX);\n                        } else {\n                            spacing += (dxTwo / getWidth() * SPACING_MAX) - (dxOne / getWidth() * SPACING_MAX);\n                        }\n\n                        spacing = Math.min(Math.max(spacing, SPACING_MIN), SPACING_MAX);\n                        invalidate();\n                    }\n\n                    if (multiTouchTracking != TRACKING_UNKNOWN) {\n                        lastOneX = xOne;\n                        lastOneY = yOne;\n                        lastTwoX = xTwo;\n                        lastTwoY = yTwo;\n                    }\n                }\n                break;\n            }\n\n            case MotionEvent.ACTION_CANCEL:\n            case MotionEvent.ACTION_UP:\n            case MotionEvent.ACTION_POINTER_UP: {\n                int index = (action != ACTION_POINTER_UP) ? 0 : event.getActionIndex();\n                int pointerId = event.getPointerId(index);\n                if (pointerOne == pointerId) {\n                    // Shift pointer two (real or invalid) up to pointer one.\n                    pointerOne = pointerTwo;\n                    lastOneX = lastTwoX;\n                    lastOneY = lastTwoY;\n                    if (DEBUG) log(\"Promoting pointer 2 (%s) to pointer 1.\", pointerTwo);\n                    // Clear pointer two and tracking.\n                    pointerTwo = INVALID_POINTER_ID;\n                    multiTouchTracking = TRACKING_UNKNOWN;\n                } else if (pointerTwo == pointerId) {\n                    if (DEBUG) log(\"Lost pointer 2 (%s).\", pointerTwo);\n                    pointerTwo = INVALID_POINTER_ID;\n                    multiTouchTracking = TRACKING_UNKNOWN;\n                }\n                break;\n            }\n        }\n\n        return true;\n    }\n\n    @Override public void draw(@SuppressWarnings(\"NullableProblems\") Canvas canvas) {\n        if (!enabled) {\n            super.draw(canvas);\n            return;\n        }\n\n        getLocationInWindow(location);\n        float x = location[0];\n        float y = location[1];\n\n        int saveCount = canvas.save();\n\n        float cx = getWidth() / 2f;\n        float cy = getHeight() / 2f;\n\n        camera.save();\n        camera.rotate(rotationX, rotationY, 0);\n        camera.getMatrix(matrix);\n        camera.restore();\n\n        matrix.preTranslate(-cx, -cy);\n        matrix.postTranslate(cx, cy);\n        canvas.concat(matrix);\n        canvas.scale(zoom, zoom, cx, cy);\n\n        if (!layeredViewQueue.isEmpty()) {\n            throw new AssertionError(\"View queue is not empty.\");\n        }\n\n        // We don't want to be rendered so seed the queue with our children.\n        for (int i = 0, count = getChildCount(); i < count; i++) {\n            LayeredView layeredView = layeredViewPool.obtain();\n            layeredView.set(getChildAt(i), 0);\n            layeredViewQueue.add(layeredView);\n        }\n\n        while (!layeredViewQueue.isEmpty()) {\n            LayeredView layeredView = layeredViewQueue.removeFirst();\n            View view = layeredView.view;\n            int layer = layeredView.layer;\n\n            // Restore the object to the pool for use later.\n            layeredView.clear();\n            layeredViewPool.restore(layeredView);\n\n            // Hide any visible children.\n            if (view instanceof ViewGroup) {\n                ViewGroup viewGroup = (ViewGroup) view;\n                visibilities.clear();\n                for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) {\n                    View child = viewGroup.getChildAt(i);\n                    //noinspection ConstantConditions\n                    if (child.getVisibility() == VISIBLE) {\n                        visibilities.set(i);\n                        child.setVisibility(INVISIBLE);\n                    }\n                }\n            }\n\n            int viewSaveCount = canvas.save();\n\n            // Scale the layer index translation by the rotation amount.\n            float translateShowX = rotationY / ROTATION_MAX;\n            float translateShowY = rotationX / ROTATION_MAX;\n            float tx = layer * spacing * density * translateShowX;\n            float ty = layer * spacing * density * translateShowY;\n            canvas.translate(tx, -ty);\n\n            view.getLocationInWindow(location);\n            canvas.translate(location[0] - x, location[1] - y);\n\n            viewBoundsRect.set(0, 0, view.getWidth(), view.getHeight());\n            canvas.drawRect(viewBoundsRect, viewBorderPaint);\n\n            if (drawViews) {\n                if (!(view instanceof SurfaceView)) {\n                    view.draw(canvas);\n                }\n            }\n\n            if (drawIds) {\n                int id = view.getId();\n                if (id != NO_ID) {\n                    canvas.drawText(nameForId(id), textOffset, textSize, viewBorderPaint);\n                }\n            }\n\n            canvas.restoreToCount(viewSaveCount);\n\n            // Restore any hidden children and queue them for later drawing.\n            if (view instanceof ViewGroup) {\n                ViewGroup viewGroup = (ViewGroup) view;\n                for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) {\n                    if (visibilities.get(i)) {\n                        View child = viewGroup.getChildAt(i);\n                        //noinspection ConstantConditions\n                        child.setVisibility(VISIBLE);\n                        LayeredView childLayeredView = layeredViewPool.obtain();\n                        childLayeredView.set(child, layer + 1);\n                        layeredViewQueue.add(childLayeredView);\n                    }\n                }\n            }\n        }\n\n        canvas.restoreToCount(saveCount);\n    }\n\n    private String nameForId(int id) {\n        String name = idNames.get(id);\n        if (name == null) {\n            try {\n                name = res.getResourceEntryName(id);\n            } catch (NotFoundException e) {\n                name = String.format(\"0x%8x\", id);\n            }\n            idNames.put(id, name);\n        }\n        return name;\n    }\n\n    private static abstract class Pool<T> {\n        private final Deque<T> pool;\n\n        Pool(int initialSize) {\n            pool = new ArrayDeque<>(initialSize);\n            for (int i = 0; i < initialSize; i++) {\n                pool.addLast(newObject());\n            }\n        }\n\n        T obtain() {\n            return pool.isEmpty() ? newObject() : pool.removeLast();\n        }\n\n        void restore(T instance) {\n            pool.addLast(instance);\n        }\n\n        protected abstract T newObject();\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/layoutborder/ViewBorderDrawable.java",
    "content": "package com.didichuxing.doraemonkit.kit.layoutborder;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.ColorFilter;\nimport android.graphics.DashPathEffect;\nimport android.graphics.Paint;\nimport android.graphics.PixelFormat;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.config.LayoutBorderConfig;\n\n/**\n * Created by wanglikun on 2019/1/11\n */\npublic class ViewBorderDrawable extends Drawable {\n    private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);\n\n    private final Rect rect;\n\n    private final Context context;\n\n    public ViewBorderDrawable(View view) {\n        rect = new Rect(0, 0, view.getWidth(), view.getHeight());\n        context= view.getContext();\n\n        paint.setStyle(Paint.Style.STROKE);\n        paint.setColor(Color.RED);\n        paint.setStrokeWidth(4);\n        paint.setPathEffect(new DashPathEffect(new float[]{4, 4}, 0));\n    }\n\n    @Override\n    public void draw(@NonNull Canvas canvas) {\n        if (LayoutBorderConfig.isLayoutBorderOpen()) {\n            canvas.drawRect(rect, paint);\n        }\n    }\n\n    @Override\n    public void setAlpha(int alpha) {\n\n    }\n\n    @Override\n    public void setColorFilter(@Nullable ColorFilter colorFilter) {\n\n    }\n\n    @Override\n    public int getOpacity() {\n        // to be safe\n        return PixelFormat.TRANSLUCENT;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/layoutborder/ViewBorderFrameLayout.java",
    "content": "package com.didichuxing.doraemonkit.kit.layoutborder;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.LayerDrawable;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.util.AttributeSet;\nimport android.view.TextureView;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.FrameLayout;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.LayoutBorderConfig;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewInterface;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by wanglikun on 2019/1/12\n */\npublic class ViewBorderFrameLayout extends FrameLayout {\n    private static final String TAG = \"ViewBorderFrameLayout\";\n\n    public ViewBorderFrameLayout(@NonNull Context context) {\n        super(context);\n        setId(R.id.dokit_view_border_id);\n        //LogHelper.i(TAG, \"childId=====>\" + UIUtils.getIdText(this));\n    }\n\n    public ViewBorderFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        setId(R.id.dokit_view_border_id);\n    }\n\n    public ViewBorderFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        setId(R.id.dokit_view_border_id);\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        super.onLayout(changed, left, top, right, bottom);\n        if (LayoutBorderConfig.isLayoutBorderOpen()) {\n            traverseChild(this);\n        } else {\n            clearChild(this);\n        }\n    }\n\n    private void traverseChild(View view) {\n        //过滤掉dokitView\n        if (view instanceof ViewGroup && !(view instanceof DoKitViewInterface)) {\n            replaceDrawable(view);\n            int childCount = ((ViewGroup) view).getChildCount();\n            if (childCount != 0) {\n                for (int index = 0; index < childCount; index++) {\n                    traverseChild(((ViewGroup) view).getChildAt(index));\n                }\n            }\n        } else {\n            replaceDrawable(view);\n        }\n    }\n\n    private void replaceDrawable(View view) {\n        if (view instanceof TextureView) {\n            // 过滤TextureView\n            return;\n        }\n        LayerDrawable newDrawable;\n        if (view.getBackground() != null) {\n            Drawable oldDrawable = view.getBackground();\n            if (oldDrawable instanceof LayerDrawable) {\n                for (int i = 0; i < ((LayerDrawable) oldDrawable).getNumberOfLayers(); i++) {\n                    if (((LayerDrawable) oldDrawable).getDrawable(i) instanceof ViewBorderDrawable) {\n                        // already replace\n                        return;\n                    }\n                }\n                newDrawable = new LayerDrawable(new Drawable[]{\n                        oldDrawable,\n                        new ViewBorderDrawable(view)\n                });\n            } else {\n                newDrawable = new LayerDrawable(new Drawable[]{\n                        oldDrawable,\n                        new ViewBorderDrawable(view)\n                });\n            }\n        } else {\n            newDrawable = new LayerDrawable(new Drawable[]{\n                    new ViewBorderDrawable(view)\n            });\n        }\n        try {\n            view.setBackground(newDrawable);\n        } catch (UnsupportedOperationException e) {\n            e.printStackTrace();\n        }\n    }\n\n    private void clearChild(View view) {\n        if (view instanceof ViewGroup) {\n            clearDrawable(view);\n            int childCount = ((ViewGroup) view).getChildCount();\n            if (childCount != 0) {\n                for (int index = 0; index < childCount; index++) {\n                    clearChild(((ViewGroup) view).getChildAt(index));\n                }\n            }\n        } else {\n            clearDrawable(view);\n        }\n    }\n\n    private void clearDrawable(View view) {\n        if (view.getBackground() == null) {\n            return;\n        }\n        Drawable oldDrawable = view.getBackground();\n        if (!(oldDrawable instanceof LayerDrawable)) {\n            return;\n        }\n        LayerDrawable layerDrawable = (LayerDrawable) oldDrawable;\n        List<Drawable> drawables = new ArrayList<>();\n        for (int i = 0; i < layerDrawable.getNumberOfLayers(); i++) {\n            if (layerDrawable.getDrawable(i) instanceof ViewBorderDrawable) {\n                continue;\n            }\n            drawables.add(layerDrawable.getDrawable(i));\n        }\n        LayerDrawable newDrawable = new LayerDrawable(drawables.toArray(new Drawable[drawables.size()]));\n        view.setBackground(newDrawable);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/LogExportDialog.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo;\n\n\nimport android.view.View;\n\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.widget.dialog.DialogListener;\nimport com.didichuxing.doraemonkit.widget.dialog.DialogProvider;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter;\n\n/**\n * Created by wanglikun on 2019/4/16\n */\npublic class LogExportDialog extends DialogProvider<Object> {\n    private RecyclerView mChooseList;\n    private SettingItemAdapter mAdapter;\n\n    public LogExportDialog(Object data, DialogListener listener) {\n        super(data, listener);\n    }\n\n    @Override\n    public int getLayoutId() {\n        return R.layout.dk_dialog_file_explorer_choose;\n    }\n\n    @Override\n    protected void findViews(View view) {\n        mChooseList = view.findViewById(R.id.choose_list);\n        mAdapter = new SettingItemAdapter(getContext());\n        mChooseList.setAdapter(mAdapter);\n        mChooseList.setLayoutManager(new LinearLayoutManager(getContext()));\n    }\n\n    @Override\n    protected void bindData(final Object file) {\n        mAdapter.append(new SettingItem(R.string.dk_save));\n\n        mAdapter.append(new SettingItem(R.string.dk_share));\n        mAdapter.setOnSettingItemClickListener(new SettingItemAdapter.OnSettingItemClickListener() {\n            @Override\n            public void onSettingItemClick(View view, SettingItem data) {\n                if (data.desc == R.string.dk_save) {\n                    if (onButtonClickListener != null) {\n                        onButtonClickListener.onSaveClick(LogExportDialog.this);\n                    }\n                } else if (data.desc == R.string.dk_share) {\n                    if (onButtonClickListener != null) {\n                        onButtonClickListener.onShareClick(LogExportDialog.this);\n                    }\n                }\n            }\n        });\n    }\n\n    private OnButtonClickListener onButtonClickListener;\n\n    public void setOnButtonClickListener(OnButtonClickListener onButtonClickListener) {\n        this.onButtonClickListener = onButtonClickListener;\n    }\n\n    public interface OnButtonClickListener {\n        /**\n         * @param dialog dialog\n         */\n        void onSaveClick(LogExportDialog dialog);\n\n        /**\n         * @param dialog dialog\n         */\n        void onShareClick(LogExportDialog dialog);\n    }\n\n    @Override\n    public boolean isCancellable() {\n        return false;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/LogInfoDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo;\n\nimport android.content.Context;\nimport android.text.Editable;\nimport android.text.TextWatcher;\nimport android.util.Log;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.widget.Button;\nimport android.widget.EditText;\nimport android.widget.FrameLayout;\nimport android.widget.RadioGroup;\nimport android.widget.RelativeLayout;\nimport android.widget.TextView;\n\nimport androidx.fragment.app.FragmentActivity;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.DoKitEnv;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.kit.core.UniversalActivity;\nimport com.didichuxing.doraemonkit.util.AppUtils;\nimport com.didichuxing.doraemonkit.util.DoKitFileUtil;\nimport com.didichuxing.doraemonkit.util.FileIOUtils;\nimport com.didichuxing.doraemonkit.util.FileUtils;\nimport com.didichuxing.doraemonkit.util.PathUtils;\nimport com.didichuxing.doraemonkit.util.ThreadUtils;\nimport com.didichuxing.doraemonkit.util.TimeUtils;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.widget.dialog.DialogProvider;\nimport com.didichuxing.doraemonkit.widget.dialog.UniversalDialogFragment;\nimport com.didichuxing.doraemonkit.widget.titlebar.LogTitleBar;\n\nimport java.io.File;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by jintai on 2019/09/26.\n */\npublic class LogInfoDoKitView extends AbsDoKitView implements LogInfoManager.OnLogCatchListener {\n    private static final String TAG = \"LogInfoFloatPage\";\n\n    private static final int MAX_LOG_LINE_NUM = 10000;\n\n    private RecyclerView mLogRv;\n    private LogItemAdapter mLogItemAdapter;\n    private EditText mLogFilter;\n    private RadioGroup mRadioGroup;\n    /**\n     * 单行的log\n     */\n    private TextView mLogHint;\n    private RelativeLayout mLogRvWrap;\n\n\n    private boolean mIsLoaded;\n\n    @Override\n    public void onCreate(Context context) {\n        LogInfoManager.getInstance().registerListener(this);\n    }\n\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_log_info, null);\n    }\n\n    @Override\n    public void onViewCreated(FrameLayout view) {\n        initView();\n    }\n\n    public void initView() {\n\n\n        mLogHint = findViewById(R.id.log_hint);\n        mLogRvWrap = findViewById(R.id.log_page);\n        mLogRv = findViewById(R.id.log_list);\n        mLogRv.setLayoutManager(new LinearLayoutManager(getContext()));\n        mLogItemAdapter = new LogItemAdapter(getContext());\n        mLogRv.setAdapter(mLogItemAdapter);\n        mLogFilter = findViewById(R.id.log_filter);\n        mLogFilter.addTextChangedListener(new TextWatcher() {\n            @Override\n            public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n\n            }\n\n            @Override\n            public void onTextChanged(CharSequence s, int start, int before, int count) {\n\n            }\n\n            @Override\n            public void afterTextChanged(Editable s) {\n                mLogItemAdapter.getFilter().filter(s);\n            }\n        });\n        LogTitleBar mTitleBar = findViewById(R.id.dokit_title_bar);\n\n        mTitleBar.setListener(new LogTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                //关闭日志服务\n                LogInfoManager.getInstance().stop();\n                //清空回调\n                LogInfoManager.getInstance().removeListener();\n                detach();\n            }\n\n            @Override\n            public void onLeftClick() {\n                minimize();\n            }\n        });\n\n\n        mLogHint.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                maximize();\n            }\n        });\n        mRadioGroup = findViewById(R.id.radio_group);\n        mRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {\n            @Override\n            public void onCheckedChanged(RadioGroup group, int checkedId) {\n                if (checkedId == R.id.verbose) {\n                    mLogItemAdapter.setLogLevelLimit(Log.VERBOSE);\n                } else if (checkedId == R.id.debug) {\n                    mLogItemAdapter.setLogLevelLimit(Log.DEBUG);\n                } else if (checkedId == R.id.info) {\n                    mLogItemAdapter.setLogLevelLimit(Log.INFO);\n                } else if (checkedId == R.id.warn) {\n                    mLogItemAdapter.setLogLevelLimit(Log.WARN);\n                } else if (checkedId == R.id.error) {\n                    mLogItemAdapter.setLogLevelLimit(Log.ERROR);\n                }\n                mLogItemAdapter.getFilter().filter(mLogFilter.getText());\n            }\n        });\n        mLogRv.addOnScrollListener(new RecyclerView.OnScrollListener() {\n            @Override\n            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {\n                super.onScrollStateChanged(recyclerView, newState);\n            }\n\n            @Override\n            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {\n                super.onScrolled(recyclerView, dx, dy);\n\n\n                final LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();\n                // if the bottom of the list isn't visible anymore, then stop autoscrolling\n                mAutoScrollToBottom = (layoutManager.findLastCompletelyVisibleItemPosition() == recyclerView.getAdapter().getItemCount() - 1);\n            }\n        });\n\n        mRadioGroup.check(R.id.verbose);\n        Button mBtnTop = findViewById(R.id.btn_top);\n        Button mBtnBottom = findViewById(R.id.btn_bottom);\n        Button mBtnClean = findViewById(R.id.btn_clean);\n        Button mBtnExport = findViewById(R.id.btn_export);\n        mBtnTop.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (mLogItemAdapter == null || mLogItemAdapter.getItemCount() == 0) {\n                    return;\n                }\n                mLogRv.scrollToPosition(0);\n            }\n        });\n        mBtnBottom.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (mLogItemAdapter == null || mLogItemAdapter.getItemCount() == 0) {\n                    return;\n                }\n                mLogRv.scrollToPosition(mLogItemAdapter.getItemCount() - 1);\n            }\n        });\n\n        mBtnExport.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (mLogItemAdapter == null || mLogItemAdapter.getItemCount() == 0) {\n                    ToastUtils.showShort(\"暂无日志信息可以导出\");\n                    return;\n                }\n\n                LogExportDialog logExportDialog = new LogExportDialog(new Object(), null);\n                logExportDialog.setOnButtonClickListener(new LogExportDialog.OnButtonClickListener() {\n                    @Override\n                    public void onSaveClick(LogExportDialog dialog) {\n                        export2File(100);\n                        dialog.dismiss();\n\n                    }\n\n                    @Override\n                    public void onShareClick(LogExportDialog dialog) {\n                        export2File(101);\n                        dialog.dismiss();\n\n                    }\n                });\n                showDialog(logExportDialog);\n\n            }\n        });\n\n        mBtnClean.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (mLogItemAdapter == null || mLogItemAdapter.getItemCount() == 0) {\n                    return;\n                }\n                counter = 0;\n                mLogItemAdapter.clearLog();\n            }\n        });\n    }\n\n\n    private void showDialog(DialogProvider provider) {\n        if (getActivity() == null || !(getActivity() instanceof FragmentActivity)) {\n            return;\n        }\n        UniversalDialogFragment dialog = new UniversalDialogFragment();\n        provider.setHost(dialog);\n        dialog.setProvider(provider);\n        provider.show(((FragmentActivity) getActivity()).getSupportFragmentManager());\n    }\n\n    /**\n     * 将日志信息保存到文件\n     *\n     * @param operateType 100 保存到本地  101 保存到本地并分享\n     */\n    private void export2File(final int operateType) {\n        ToastUtils.showShort(\"日志保存中,请稍后...\");\n        final String logPath = PathUtils.getInternalAppFilesPath() + File.separator + AppUtils.getAppName() + \"_\" + TimeUtils.getNowString(new SimpleDateFormat(\"yyyy-MM-dd-HH:mm:ss\")) + \".log\";\n        final File logFile = new File(logPath);\n\n        ThreadUtils.executeByCpu(new ThreadUtils.Task<Boolean>() {\n            @Override\n            public Boolean doInBackground() throws Throwable {\n                try {\n                    List<LogLine> logLines = new ArrayList<>(mLogItemAdapter.getTrueValues());\n                    for (LogLine logLine : logLines) {\n                        String strLog = logLine.getProcessId() + \"   \" + \"   \" + logLine.getTimestamp() + \"   \" + logLine.getTag() + \"   \" + logLine.getLogLevelText() + \"   \" + logLine.getLogOutput() + \"\\n\";\n                        FileIOUtils.writeFileFromString(logFile, strLog, true);\n                    }\n                    return true;\n                } catch (Exception e) {\n                    e.printStackTrace();\n                    return false;\n                }\n            }\n\n            @Override\n            public void onSuccess(Boolean result) {\n                if (result) {\n                    ToastUtils.showShort(\"文件保存在:\" + logPath);\n                    //分享\n                    if (operateType == 101) {\n                        DoKitFileUtil.systemShare(DoKitEnv.requireApp(), logFile);\n                    }\n                }\n            }\n\n            @Override\n            public void onCancel() {\n                if (logFile.exists()) {\n                    FileUtils.delete(logFile);\n                }\n                ToastUtils.showShort(\"日志保存失败\");\n            }\n\n            @Override\n            public void onFail(Throwable t) {\n                if (logFile.exists()) {\n                    FileUtils.delete(logFile);\n                }\n                ToastUtils.showShort(\"日志保存失败\");\n            }\n        });\n\n    }\n\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;\n        params.width = DoKitViewLayoutParams.MATCH_PARENT;\n        params.height = DoKitViewLayoutParams.MATCH_PARENT;\n    }\n\n    private int counter = 0;\n    private static final int UPDATE_CHECK_INTERVAL = 200;\n    private boolean mAutoScrollToBottom = true;\n\n    @Override\n    public void onLogCatch(List<LogLine> logLines) {\n        if (mLogRv == null || mLogItemAdapter == null) {\n            return;\n        }\n        if (!mIsLoaded) {\n            mIsLoaded = true;\n            findViewById(R.id.ll_loading).setVisibility(View.GONE);\n            mLogRv.setVisibility(View.VISIBLE);\n        }\n        if (logLines.size() == 1) {\n            mLogItemAdapter.addWithFilter(logLines.get(0), mLogFilter.getText(), true);\n        } else {\n            for (LogLine line : logLines) {\n                mLogItemAdapter.addWithFilter(line, mLogFilter.getText(), false);\n            }\n            mLogItemAdapter.notifyDataSetChanged();\n        }\n        if (logLines.size() > 0) {\n            LogLine line = logLines.get(logLines.size() - 1);\n            mLogHint.setText(line.getTag() + \":\" + line.getLogOutput());\n        }\n        if (++counter % UPDATE_CHECK_INTERVAL == 0\n                && mLogItemAdapter.getTrueValues().size() > MAX_LOG_LINE_NUM) {\n            int numItemsToRemove = mLogItemAdapter.getTrueValues().size() - MAX_LOG_LINE_NUM;\n            mLogItemAdapter.removeFirst(numItemsToRemove);\n            //LogHelper.d(TAG, \"truncating %d lines from log list to avoid out of memory errors:\" + numItemsToRemove);\n        }\n        if (mAutoScrollToBottom) {\n            scrollToBottom();\n        }\n    }\n\n    private void scrollToBottom() {\n        mLogRv.scrollToPosition(mLogItemAdapter.getItemCount() - 1);\n    }\n\n    private int getSelectLogLevel() {\n        int checkedId = mRadioGroup.getCheckedRadioButtonId();\n        if (checkedId == R.id.verbose) {\n            return Log.VERBOSE;\n        } else if (checkedId == R.id.debug) {\n            return Log.DEBUG;\n        } else if (checkedId == R.id.info) {\n            return Log.INFO;\n        } else if (checkedId == R.id.warn) {\n            return Log.WARN;\n        } else if (checkedId == R.id.error) {\n            return Log.ERROR;\n        } else {\n            return Log.VERBOSE;\n        }\n    }\n\n    /**\n     * 最小化\n     */\n    public void minimize() {\n        isMaximize = false;\n        if (isNormalMode()) {\n            mLogHint.setVisibility(View.VISIBLE);\n            mLogRvWrap.setVisibility(View.GONE);\n            FrameLayout.LayoutParams layoutParams = getNormalLayoutParams();\n            if (layoutParams == null) {\n                return;\n            }\n            layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;\n            layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;\n            layoutParams.gravity = Gravity.START | Gravity.TOP;\n            getDoKitView().setLayoutParams(layoutParams);\n        } else {\n            mLogHint.setVisibility(View.VISIBLE);\n            mLogRvWrap.setVisibility(View.GONE);\n\n            WindowManager.LayoutParams layoutParams = getSystemLayoutParams();\n            if (layoutParams == null) {\n                return;\n            }\n            layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;\n            layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;\n            layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;\n            layoutParams.gravity = Gravity.START | Gravity.TOP;\n            mWindowManager.updateViewLayout(getDoKitView(), layoutParams);\n        }\n\n    }\n\n    /**\n     * 是否最大化\n     */\n    private boolean isMaximize = true;\n\n    private void maximize() {\n        isMaximize = true;\n        if (isNormalMode()) {\n            mLogHint.setVisibility(View.GONE);\n            mLogRvWrap.setVisibility(View.VISIBLE);\n            FrameLayout.LayoutParams layoutParams = getNormalLayoutParams();\n            if (layoutParams == null) {\n                return;\n            }\n            layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;\n            layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;\n            layoutParams.gravity = Gravity.START | Gravity.TOP;\n            getDoKitView().setLayoutParams(layoutParams);\n        } else {\n            mLogHint.setVisibility(View.GONE);\n            mLogRvWrap.setVisibility(View.VISIBLE);\n            WindowManager.LayoutParams layoutParams = getSystemLayoutParams();\n            if (layoutParams == null) {\n                return;\n            }\n            layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;\n            layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;\n            layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;\n            layoutParams.gravity = Gravity.START | Gravity.TOP;\n            mWindowManager.updateViewLayout(getDoKitView(), layoutParams);\n        }\n\n    }\n\n    @Override\n    public boolean onBackPressed() {\n        if (isMaximize) {\n            minimize();\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        if (!getActivity().getClass().getCanonicalName().equals(UniversalActivity.class.getCanonicalName())) {\n            minimize();\n        }\n        LogInfoManager.getInstance().registerListener(this);\n    }\n\n    @Override\n    public boolean shouldDealBackKey() {\n        return true;\n    }\n\n    @Override\n    public boolean canDrag() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/LogInfoKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.config.LogInfoConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n/**\n * Created by wanglikun on 2018/10/9.\n */\n@AutoService(AbstractKit::class)\nclass LogInfoKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_log_info\n    override val icon: Int\n        get() = R.mipmap.dk_log_info\n\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        DoKit.launchFloating<LogInfoDoKitView>()\n        //开启日志服务\n        LogInfoManager.getInstance().start()\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n        LogInfoConfig.setLogInfoOpen(false)\n    }\n\n    override fun innerKitId(): String = \"dokit_sdk_comm_ck_log\"\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/LogInfoManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo;\n\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\n\nimport com.didichuxing.doraemonkit.kit.loginfo.reader.LogcatReader;\nimport com.didichuxing.doraemonkit.kit.loginfo.reader.LogcatReaderLoader;\nimport com.didichuxing.doraemonkit.util.DoKitExecutorUtil;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * Created by wanglikun on 2018/10/10.\n */\n\npublic class LogInfoManager {\n    private static final String TAG = \"LogInfoManager\";\n    private static final int MESSAGE_PUBLISH_LOG = 1001;\n\n    private OnLogCatchListener mListener;\n\n    private LogCatchRunnable mLogCatchTask;\n\n    private static class Holder {\n        private static LogInfoManager INSTANCE = new LogInfoManager();\n    }\n\n    private LogInfoManager() {\n    }\n\n    public static LogInfoManager getInstance() {\n        return Holder.INSTANCE;\n    }\n\n    public void start() {\n        if (mLogCatchTask != null) {\n            mLogCatchTask.stop();\n        }\n        mLogCatchTask = new LogCatchRunnable();\n        DoKitExecutorUtil.execute(mLogCatchTask);\n    }\n\n    public void stop() {\n        if (mLogCatchTask != null) {\n            mLogCatchTask.stop();\n        }\n    }\n\n    public interface OnLogCatchListener {\n        /**\n         * 新增日志回调\n         *\n         * @param logLine\n         */\n        void onLogCatch(List<LogLine> logLine);\n    }\n\n    public void registerListener(OnLogCatchListener listener) {\n        mListener = listener;\n    }\n\n    public void removeListener() {\n        mListener = null;\n    }\n\n    /**\n     * 接收log 的内部Handler\n     */\n    private static class InternalHandler extends Handler {\n        public InternalHandler(Looper looper) {\n            super(looper);\n        }\n\n        @Override\n        public void handleMessage(Message msg) {\n            switch (msg.what) {\n                case MESSAGE_PUBLISH_LOG:\n\n                    if (LogInfoManager.getInstance().mListener != null) {\n                        LogInfoManager.getInstance().mListener.onLogCatch((List<LogLine>) msg.obj);\n                    }\n\n                    break;\n                default:\n                    break;\n            }\n        }\n    }\n\n\n    /**\n     * 获取日志的内部线程\n     */\n    private static class LogCatchRunnable implements Runnable {\n        private boolean isRunning = true;\n        private Handler internalHandler;\n        private LogcatReader mReader;\n        private int mPid;\n\n        private LogCatchRunnable() {\n            internalHandler = new InternalHandler(Looper.getMainLooper());\n            mPid = android.os.Process.myPid();\n        }\n\n        @Override\n        public void run() {\n            try {\n                LogcatReaderLoader loader = LogcatReaderLoader.create(true);\n                mReader = loader.loadReader();\n\n                String line;\n                int maxLines = 10000;\n                LinkedList<LogLine> initialLines = new LinkedList<>();\n                while ((line = mReader.readLine()) != null && isRunning) {\n                    LogLine logLine = LogLine.newLogLine(line, false);\n                    if (!mReader.readyToRecord()) {\n                        if (logLine.getProcessId() == mPid) {\n                            initialLines.add(logLine);\n                        }\n                        if (initialLines.size() > maxLines) {\n                            initialLines.removeFirst();\n                        }\n                    } else if (!initialLines.isEmpty()) {\n                        if (logLine.getProcessId() == mPid) {\n                            initialLines.add(logLine);\n                        }\n                        Message message = Message.obtain();\n                        message.what = MESSAGE_PUBLISH_LOG;\n                        message.obj = new ArrayList<>(initialLines);\n                        internalHandler.sendMessage(message);\n                        initialLines.clear();\n                    } else {\n                        // just proceed as normal\n                        if (logLine.getProcessId() == mPid) {\n                            Message message = Message.obtain();\n                            message.what = MESSAGE_PUBLISH_LOG;\n                            message.obj = Collections.singletonList(logLine);\n                            internalHandler.sendMessage(message);\n                        }\n                    }\n                }\n                mReader.killQuietly();\n            } catch (IOException e) {\n                LogHelper.e(TAG, e.toString());\n            }\n        }\n\n        public void stop() {\n            isRunning = false;\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/LogInfoSettingFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo;\n\nimport android.os.Bundle;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.LogInfoConfig;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\n\n/**\n * Created by wanglikun on 2018/10/9.\n */\n\npublic class LogInfoSettingFragment extends BaseFragment {\n    private static final String TAG = \"LogInfoSettingFragment\";\n    private RecyclerView mSettingList;\n    private SettingItemAdapter mSettingItemAdapter;\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n    }\n\n    private void initView() {\n        HomeTitleBar titleBar = findViewById(R.id.title_bar);\n        titleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                finish();\n            }\n        });\n        mSettingList = findViewById(R.id.setting_list);\n        mSettingList.setLayoutManager(new LinearLayoutManager(getContext()));\n        mSettingItemAdapter = new SettingItemAdapter(getContext());\n        mSettingItemAdapter.append(new SettingItem(R.string.dk_kit_log_info, LogInfoConfig.isLogInfoOpen()));\n        mSettingItemAdapter.setOnSettingItemSwitchListener(new SettingItemAdapter.OnSettingItemSwitchListener() {\n            @Override\n            public void onSettingItemSwitch(View view, SettingItem data, boolean on) {\n                if (data.desc == R.string.dk_kit_log_info) {\n                    if (on) {\n                        DoKit.launchFloating(LogInfoDoKitView.class);\n\n                        //开启日志服务\n                        LogInfoManager.getInstance().start();\n                    } else {\n                        DoKit.removeFloating(LogInfoDoKitView.class);\n                        //关闭日志服务\n                        LogInfoManager.getInstance().stop();\n                        //清空回调\n                        LogInfoManager.getInstance().removeListener();\n                    }\n                    LogInfoConfig.setLogInfoOpen(on);\n                }\n            }\n        });\n        mSettingList.setAdapter(mSettingItemAdapter);\n    }\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_log_info_setting;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/LogItemAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo;\n\nimport android.content.ClipData;\nimport android.content.ClipboardManager;\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Filter;\nimport android.widget.Filterable;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.loginfo.util.SearchCriteria;\nimport com.didichuxing.doraemonkit.kit.loginfo.util.TagColorUtil;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static android.content.Context.CLIPBOARD_SERVICE;\n\n/**\n * Created by wanglikun on 2018/10/30.\n */\n\npublic class LogItemAdapter extends AbsRecyclerAdapter<AbsViewBinder<LogLine>, LogLine> implements Filterable {\n\n\n    public LogItemAdapter(Context context) {\n        super(context);\n        mClipboard = (ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE);\n    }\n\n\n    private ArrayList<LogLine> mOriginalValues = new ArrayList<>();\n    private ArrayFilter mFilter = new ArrayFilter();\n    private int logLevelLimit = Log.VERBOSE;\n    private ClipboardManager mClipboard;\n\n    /**\n     * 清空log\n     */\n    public void clearLog() {\n        if (mOriginalValues != null && mOriginalValues.size() > 0) {\n            mOriginalValues.clear();\n        }\n        clear();\n        notifyDataSetChanged();\n    }\n\n    @Override\n    protected AbsViewBinder<LogLine> createViewHolder(View view, int viewType) {\n        return new LogInfoViewHolder(view);\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        return inflater.inflate(R.layout.dk_item_log, parent, false);\n    }\n\n    @Override\n    public Filter getFilter() {\n        return mFilter;\n    }\n\n    public int getLogLevelLimit() {\n        return logLevelLimit;\n    }\n\n\n    public void setLogLevelLimit(int logLevelLimit) {\n        this.logLevelLimit = logLevelLimit;\n    }\n\n    public List<LogLine> getTrueValues() {\n        return mOriginalValues != null ? mOriginalValues : mList;\n    }\n\n    public void removeFirst(int n) {\n        if (mOriginalValues != null) {\n            List<LogLine> subList = mOriginalValues.subList(n, mOriginalValues.size());\n            for (int i = 0; i < n; i++) {\n                // value to delete - delete it from the mObjects as well\n                mList.remove(mOriginalValues.get(i));\n            }\n            mOriginalValues = new ArrayList<>(subList);\n        }\n        notifyDataSetChanged();\n    }\n\n    public class LogInfoViewHolder extends AbsViewBinder<LogLine> {\n\n        private TextView mLogText;\n        private TextView mPid;\n        private TextView mTime;\n        private TextView mTag;\n        private TextView mLevel;\n\n        public LogInfoViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            mLogText = getView(R.id.log_output_text);\n            mLevel = getView(R.id.log_level_text);\n            mPid = getView(R.id.pid_text);\n            mTime = getView(R.id.timestamp_text);\n            mTag = getView(R.id.tag_text);\n        }\n\n        @Override\n        protected void onViewClick(View view, final LogLine data) {\n            super.onViewClick(view, data);\n            data.setExpanded(!data.isExpanded());\n            if (data.isExpanded() && data.getProcessId() != -1) {\n                mLogText.setSingleLine(false);\n                mTime.setVisibility(View.VISIBLE);\n                mPid.setVisibility(View.VISIBLE);\n                view.setBackgroundColor(Color.BLACK);\n                mLogText.setTextColor(TagColorUtil.getTextColor(getContext(), data.getLogLevel(), true));\n                mTag.setTextColor(TagColorUtil.getTextColor(getContext(), data.getLogLevel(), true));\n            } else {\n                mLogText.setSingleLine(true);\n                mTime.setVisibility(View.GONE);\n                mPid.setVisibility(View.GONE);\n                view.setBackgroundColor(Color.WHITE);\n                mLogText.setTextColor(TagColorUtil.getTextColor(getContext(), data.getLogLevel(), false));\n                mTag.setTextColor(TagColorUtil.getTextColor(getContext(), data.getLogLevel(), false));\n            }\n            itemView.setOnLongClickListener(new View.OnLongClickListener() {\n                @Override\n                public boolean onLongClick(View v) {\n                    ClipData clipData = ClipData.newPlainText(\"Label\", data.getOriginalLine());\n                    mClipboard.setPrimaryClip(clipData);\n                    ToastUtils.showShort(\"copy success\");\n                    return true;\n                }\n            });\n        }\n\n        @Override\n        public void bind(LogLine item) {\n\n            mLevel.setText(item.getLogLevelText());\n            mLevel.setTextColor(TagColorUtil.getLevelColor(getContext(), item.getLogLevel()));\n            mLevel.setBackgroundColor(TagColorUtil.getLevelBgColor(getContext(), item.getLogLevel()));\n\n            mPid.setText(String.valueOf(item.getProcessId()));\n            mTime.setText(item.getTimestamp());\n\n            mLogText.setText(item.getLogOutput());\n\n            mTag.setText(item.getTag());\n\n            if (item.isExpanded() && item.getProcessId() != -1) {\n                mLogText.setSingleLine(false);\n                mTime.setVisibility(View.VISIBLE);\n                mPid.setVisibility(View.VISIBLE);\n                mLogText.setTextColor(TagColorUtil.getTextColor(getContext(), item.getLogLevel(), true));\n                mTag.setTextColor(TagColorUtil.getTextColor(getContext(), item.getLogLevel(), true));\n                itemView.setBackgroundColor(Color.BLACK);\n\n            } else {\n                mLogText.setSingleLine(true);\n                mTime.setVisibility(View.GONE);\n                mPid.setVisibility(View.GONE);\n                itemView.setBackgroundColor(Color.WHITE);\n                mLogText.setTextColor(TagColorUtil.getTextColor(getContext(), item.getLogLevel(), false));\n                mTag.setTextColor(TagColorUtil.getTextColor(getContext(), item.getLogLevel(), false));\n            }\n        }\n    }\n\n    /**\n     * 添加日志到adapter中\n     *\n     * @param logObj\n     * @param text\n     * @param notify\n     */\n    public void addWithFilter(LogLine logObj, CharSequence text, boolean notify) {\n\n        if (mOriginalValues != null) {\n\n            List<LogLine> inputList = Collections.singletonList(logObj);\n\n            List<LogLine> filteredObjects = mFilter.performFilteringOnList(inputList, text);\n\n            mOriginalValues.add(logObj);\n\n            mList.addAll(filteredObjects);\n            if (notify) {\n                notifyItemRangeInserted(mList.size() - filteredObjects.size(), filteredObjects.size());\n            }\n        } else {\n            mList.add(logObj);\n            if (notify) {\n                notifyItemInserted(mList.size());\n            }\n        }\n    }\n\n    private class ArrayFilter extends Filter {\n        @Override\n        protected FilterResults performFiltering(CharSequence prefix) {\n            FilterResults results = new FilterResults();\n\n\n            ArrayList<LogLine> allValues = performFilteringOnList(mOriginalValues, prefix);\n\n            results.values = allValues;\n            results.count = allValues.size();\n\n            return results;\n        }\n\n        public ArrayList<LogLine> performFilteringOnList(List<LogLine> inputList, CharSequence query) {\n\n            SearchCriteria searchCriteria = new SearchCriteria(query);\n\n            // search by log level\n            ArrayList<LogLine> allValues = new ArrayList<>();\n\n            ArrayList<LogLine> logLines = new ArrayList<>(inputList);\n\n\n            for (LogLine logLine : logLines) {\n                if (logLine != null && logLine.getLogLevel() >= logLevelLimit) {\n                    allValues.add(logLine);\n                }\n            }\n            ArrayList<LogLine> finalValues = allValues;\n\n            // search by criteria\n            if (!searchCriteria.isEmpty()) {\n\n                final ArrayList<LogLine> values = allValues;\n                final int count = values.size();\n\n                final ArrayList<LogLine> newValues = new ArrayList<>(count);\n\n                for (int i = 0; i < count; i++) {\n                    final LogLine value = values.get(i);\n                    // search the logline based on the criteria\n                    if (searchCriteria.matches(value)) {\n                        newValues.add(value);\n                    }\n                }\n\n                finalValues = newValues;\n            }\n\n            return finalValues;\n        }\n\n        @SuppressWarnings(\"unchecked\")\n        @Override\n        protected void publishResults(CharSequence constraint, FilterResults results) {\n            //noinspection unchecked\n\n            //log.d(\"filtering: %s\", constraint);\n\n\n            mList = (List<LogLine>) results.values;\n            if (results.count > 0) {\n                notifyDataSetChanged();\n            } else {\n                notifyDataSetChanged();\n            }\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/LogLine.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo;\n\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport com.didichuxing.doraemonkit.kit.loginfo.reader.ScrubberUtils;\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n\npublic class LogLine {\n\n    private static final int TIMESTAMP_LENGTH = 19;\n\n    private static Pattern logPattern = Pattern.compile(\n            // log level\n            \"(\\\\w)\" +\n                    \"/\" +\n                    // tag\n                    \"([^(]+)\" +\n                    \"\\\\(\\\\s*\" +\n                    // pid\n                    \"(\\\\d+)\" +\n                    // optional weird number that only occurs on ZTE blade\n                    \"(?:\\\\*\\\\s*\\\\d+)?\" +\n                    \"\\\\): \");\n\n    private static final String filterPattern = \"ResourceType|memtrack|android.os.Debug|BufferItemConsumer|DPM.*|MDM.*|ChimeraUtils|BatteryExternalStats.*|chatty.*|DisplayPowerController|WidgetHelper|WearableService|DigitalWidget.*|^ANDR-PERF-.*\";\n    private int logLevel;\n    private String tag;\n    private String logOutput;\n    private int processId = -1;\n    private String timestamp;\n    private boolean expanded = false;\n    private boolean highlighted = false;\n\n    public static boolean isScrubberEnabled = false;\n\n    public static LogLine newLogLine(String originalLine, boolean expanded) {\n\n        LogLine logLine = new LogLine();\n        logLine.setExpanded(expanded);\n\n        int startIdx = 0;\n\n        // if the first char is a digit, then this starts out with a timestamp\n        // otherwise, it's a legacy log or the beginning of the log output or something\n        if (!TextUtils.isEmpty(originalLine)\n                && Character.isDigit(originalLine.charAt(0))\n                && originalLine.length() >= TIMESTAMP_LENGTH) {\n            String timestamp = originalLine.substring(0, TIMESTAMP_LENGTH - 1);\n            logLine.setTimestamp(timestamp);\n            // cut off timestamp\n            startIdx = TIMESTAMP_LENGTH;\n        }\n\n        Matcher matcher = logPattern.matcher(originalLine);\n\n        if (matcher.find(startIdx)) {\n            char logLevelChar = matcher.group(1).charAt(0);\n\n            String logText = originalLine.substring(matcher.end());\n            if (logText.matches(\"^maxLineHeight.*|Failed to read.*\")) {\n                logLine.setLogLevel(convertCharToLogLevel('V'));\n            } else {\n                logLine.setLogLevel(convertCharToLogLevel(logLevelChar));\n            }\n\n            String tagText = matcher.group(2);\n            if (tagText.matches(filterPattern)) {\n                logLine.setLogLevel(convertCharToLogLevel('V'));\n            }\n\n            logLine.setTag(tagText);\n            logLine.setProcessId(Integer.parseInt(matcher.group(3)));\n\n            logLine.setLogOutput(logText);\n\n        } else {\n            logLine.setLogOutput(originalLine);\n            logLine.setLogLevel(-1);\n        }\n\n        return logLine;\n\n    }\n\n    private static int convertCharToLogLevel(char logLevelChar) {\n\n        switch (logLevelChar) {\n            case 'D':\n                return Log.DEBUG;\n            case 'E':\n                return Log.ERROR;\n            case 'I':\n                return Log.INFO;\n            case 'V':\n                return Log.VERBOSE;\n            case 'W':\n                return Log.WARN;\n            case 'F':\n                return Log.VERBOSE;\n        }\n        return -1;\n    }\n\n    private static char convertLogLevelToChar(int logLevel) {\n\n        switch (logLevel) {\n            case Log.DEBUG:\n                return 'D';\n            case Log.ERROR:\n                return 'E';\n            case Log.INFO:\n                return 'I';\n            case Log.VERBOSE:\n                return 'V';\n            case Log.WARN:\n                return 'W';\n        }\n        return ' ';\n    }\n\n    public String getOriginalLine() {\n\n        if (logLevel == -1) { // starter line like \"begin of log etc. etc.\"\n            return logOutput;\n        }\n\n        StringBuilder stringBuilder = new StringBuilder();\n\n        if (timestamp != null) {\n            stringBuilder.append(timestamp).append(' ');\n        }\n\n        stringBuilder.append(convertLogLevelToChar(logLevel))\n                .append('/')\n                .append(tag)\n                .append('(')\n                .append(processId)\n                .append(\"): \")\n                .append(logOutput);\n\n        return stringBuilder.toString();\n    }\n\n    public String getLogLevelText() {\n        return Character.toString(convertLogLevelToChar(logLevel));\n    }\n\n    public int getLogLevel() {\n        return logLevel;\n    }\n\n    public void setLogLevel(int logLevel) {\n        this.logLevel = logLevel;\n    }\n\n    public String getTag() {\n        return tag;\n    }\n\n    public void setTag(String tag) {\n        this.tag = tag;\n    }\n\n    public String getLogOutput() {\n        return logOutput;\n    }\n\n    public void setLogOutput(String logOutput) {\n        if (isScrubberEnabled) {\n            this.logOutput = ScrubberUtils.scrubLine(logOutput);\n        } else {\n            this.logOutput = logOutput;\n        }\n    }\n\n    public int getProcessId() {\n        return processId;\n    }\n\n    public void setProcessId(int processId) {\n        this.processId = processId;\n    }\n\n    public String getTimestamp() {\n        return timestamp;\n    }\n\n    public void setTimestamp(String timestamp) {\n        this.timestamp = timestamp;\n    }\n\n    public boolean isExpanded() {\n        return expanded;\n    }\n\n    public void setExpanded(boolean expanded) {\n        this.expanded = expanded;\n    }\n\n    public boolean isHighlighted() {\n        return highlighted;\n    }\n\n    public void setHighlighted(boolean highlighted) {\n        this.highlighted = highlighted;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/helper/LogcatHelper.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo.helper;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class LogcatHelper {\n    private static final String TAG = \"LogcatHelper\";\n    public static final String BUFFER_MAIN = \"main\";\n    public static final String BUFFER_EVENTS = \"events\";\n    public static final String BUFFER_RADIO = \"radio\";\n\n\n    public static Process getLogcatProcess(String buffer) throws IOException {\n\n        List<String> args = getLogcatArgs(buffer);\n\n        return RuntimeHelper.exec(args);\n    }\n\n    private static List<String> getLogcatArgs(String buffer) {\n        List<String> args = new ArrayList<>(Arrays.asList(\"logcat\", \"-v\", \"time\"));\n\n        // for some reason, adding -b main excludes log output from AndroidRuntime runtime exceptions,\n        // whereas just leaving it blank keeps them in.  So do not specify the buffer if it is \"main\"\n        if (!buffer.equals(BUFFER_MAIN)) {\n            args.add(\"-b\");\n            args.add(buffer);\n        }\n\n        return args;\n    }\n\n    public static String getLastLogLine(String buffer) {\n        Process dumpLogcatProcess = null;\n        BufferedReader reader = null;\n        String result = null;\n        try {\n\n            List<String> args = getLogcatArgs(buffer);\n            args.add(\"-d\"); // -d just dumps the whole thing\n\n            dumpLogcatProcess = RuntimeHelper.exec(args);\n            reader = new BufferedReader(new InputStreamReader(dumpLogcatProcess\n                    .getInputStream()), 8192);\n\n            String line;\n            while ((line = reader.readLine()) != null) {\n                result = line;\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            if (dumpLogcatProcess != null) {\n                RuntimeHelper.destroy(dumpLogcatProcess);\n                // LogHelper.d(TAG,\"destroyed 1 dump logcat process\");\n            }\n\n        }\n\n        return result;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/helper/RuntimeHelper.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo.helper;\n\nimport com.didichuxing.doraemonkit.kit.loginfo.util.ArrayUtil;\n\nimport java.io.IOException;\nimport java.util.List;\n\n/**\n * Helper functions for running processes.\n *\n * @author nolan\n */\npublic class RuntimeHelper {\n\n    /**\n     * Exec the arguments, using root if necessary.\n     *\n     * @param args\n     */\n    public static Process exec(List<String> args) throws IOException {\n        return Runtime.getRuntime().exec(ArrayUtil.toArray(args, String.class));\n    }\n\n    public static void destroy(Process process) {\n        // if we're in JellyBean, then we need to kill the process as root, which requires all this\n        // extra UnixProcess logic\n            process.destroy();\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/reader/AbsLogcatReader.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo.reader;\n\n\npublic abstract class AbsLogcatReader implements LogcatReader {\n\n    protected boolean recordingMode;\n\n    public AbsLogcatReader(boolean recordingMode) {\n        this.recordingMode = recordingMode;\n    }\n\n    public boolean isRecordingMode() {\n        return recordingMode;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/reader/LogcatReader.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo.reader;\n\nimport java.io.IOException;\nimport java.util.List;\n\npublic interface LogcatReader {\n\n    /**\n     * Read a single log line, ala BufferedReader.readLine().\n     *\n     * @return\n     * @throws IOException\n     */\n    String readLine() throws IOException;\n\n    /**\n     * Kill the reader and close all resources without throwing any exceptions.\n     */\n    void killQuietly();\n\n    boolean readyToRecord();\n\n    List<Process> getProcesses();\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/reader/LogcatReaderLoader.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo.reader;\n\nimport android.os.Bundle;\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\nimport com.didichuxing.doraemonkit.kit.loginfo.helper.LogcatHelper;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\n/**\n * https://github.com/plusCubed/matlog\n */\npublic class LogcatReaderLoader implements Parcelable {\n\n    public static final Parcelable.Creator<LogcatReaderLoader> CREATOR = new Parcelable.Creator<LogcatReaderLoader>() {\n        @Override\n        public LogcatReaderLoader createFromParcel(Parcel in) {\n            return new LogcatReaderLoader(in);\n        }\n\n        @Override\n        public LogcatReaderLoader[] newArray(int size) {\n            return new LogcatReaderLoader[size];\n        }\n    };\n    private Map<String, String> lastLines = new HashMap<>();\n    private boolean recordingMode;\n    private boolean multiple;\n\n    private LogcatReaderLoader(Parcel in) {\n        this.recordingMode = in.readInt() == 1;\n        this.multiple = in.readInt() == 1;\n        Bundle bundle = in.readBundle();\n        for (String key : bundle.keySet()) {\n            lastLines.put(key, bundle.getString(key));\n        }\n    }\n\n    private LogcatReaderLoader(List<String> buffers, boolean recordingMode) {\n        this.recordingMode = recordingMode;\n        this.multiple = buffers.size() > 1;\n        for (String buffer : buffers) {\n            // no need to grab the last line if this isn't recording mode\n            String lastLine = recordingMode ? LogcatHelper.getLastLogLine(buffer) : null;\n            lastLines.put(buffer, lastLine);\n        }\n    }\n\n    public static LogcatReaderLoader create(boolean recordingMode) {\n        List<String> buffers = new ArrayList<>();\n        buffers.add(\"main\");\n        return new LogcatReaderLoader(buffers, recordingMode);\n    }\n\n    public LogcatReader loadReader() throws IOException {\n        LogcatReader reader;\n        // single reader\n        String buffer = lastLines.keySet().iterator().next();\n        String lastLine = lastLines.values().iterator().next();\n        reader = new SingleLogcatReader(recordingMode, buffer, lastLine);\n\n        return reader;\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeInt(recordingMode ? 1 : 0);\n        dest.writeInt(multiple ? 1 : 0);\n        Bundle bundle = new Bundle();\n        for (Entry<String, String> entry : lastLines.entrySet()) {\n            bundle.putString(entry.getKey(), entry.getValue());\n        }\n        dest.writeBundle(bundle);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/reader/ScrubberUtils.java",
    "content": "/*\n * Copyright (C) 2014 The CyanogenMod Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.kit.loginfo.reader;\n\nimport java.util.regex.Pattern;\n\npublic class ScrubberUtils {\n\n    private static final Pattern EMAIL_PATTERN = Pattern.compile(\"[a-zA-Z0-9_]+(?:\\\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*(@|%40)(?!([a-zA-Z0-9]*\\\\.[a-zA-Z0-9]*\\\\.[a-zA-Z0-9]*\\\\.))(?:[A-Za-z0-9](?:[a-zA-Z0-9-]*[A-Za-z0-9])?\\\\.)+[a-zA-Z](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\");\n    private static final Pattern PHONE_NUMBER_PATTERN = Pattern.compile(\"^(?:(?:\\\\+?1\\\\s*(?:[.-]\\\\s*)?)?(?:\\\\(\\\\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\\\\s*\\\\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\\\\s*(?:[.-]\\\\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\\\\s*(?:[.-]\\\\s*)?([0-9]{4})(?:\\\\s*(?:#|x\\\\.?|ext\\\\.?|extension)\\\\s*(\\\\d+))?$\");\n    private static final Pattern WEB_URL_PATTERN = Pattern.compile(\"\\\\b(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]\");\n    private static final Pattern IP_ADDRESS_PATTERN = Pattern.compile(\"^([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])$\");\n    private static final Pattern PHONE_INFO_PATTERN = Pattern.compile(\"(msisdn=|mMsisdn=|iccid=|iccid: |mImsi=)[a-zA-Z0-9]*\", Pattern.CASE_INSENSITIVE);\n    private static final Pattern USER_INFO_PATTERN = Pattern.compile(\"(UserInfo\\\\{\\\\d:)[a-zA-Z0-9\\\\s]*\", Pattern.CASE_INSENSITIVE);\n    private static final Pattern ACCOUNT_INFO_PATTERN = Pattern.compile(\"(Account \\\\{name=)[a-zA-Z0-9]*\", Pattern.CASE_INSENSITIVE);\n\n    private static final String IGNORE_DATA_RESOURCE_CACHE = \"/data/resource-cache\";\n    private static final String IGNORE_DATA_DALVIK_CACHE = \"/data/dalvik-cache\";\n    private static final String IGNORE_CACHE_DALVIK_CACHE = \"/cache/dalvik-cache\";\n\n    public static String scrubLine(String line) {\n        if (line.contains(IGNORE_DATA_RESOURCE_CACHE)\n                || line.contains(IGNORE_DATA_DALVIK_CACHE)\n                || line.contains(IGNORE_CACHE_DALVIK_CACHE)) {\n            // ugly work around :/\n            return line;\n        }\n        line = IP_ADDRESS_PATTERN.matcher(line).replaceAll(\"<IP address omitted>\");\n        line = EMAIL_PATTERN.matcher(line).replaceAll(\"<email omitted>\");\n        line = PHONE_NUMBER_PATTERN.matcher(line).replaceAll(\"<phone number omitted>\");\n        line = WEB_URL_PATTERN.matcher(line).replaceAll(\"<web url omitted>\");\n        line = PHONE_INFO_PATTERN.matcher(line).replaceAll(\"<omitted>\");\n        line = USER_INFO_PATTERN.matcher(line).replaceAll(\"<omitted>\");\n        line = ACCOUNT_INFO_PATTERN.matcher(line).replaceAll(\"<omitted>\");\n\n        return line;\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/reader/SingleLogcatReader.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo.reader;\n\nimport android.text.TextUtils;\n\nimport com.didichuxing.doraemonkit.kit.loginfo.helper.LogcatHelper;\nimport com.didichuxing.doraemonkit.kit.loginfo.helper.RuntimeHelper;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class SingleLogcatReader extends AbsLogcatReader {\n\n    private static final String TAG = \"SingleLogcatReader\";\n\n    private Process logcatProcess;\n    private BufferedReader bufferedReader;\n    private String logBuffer;\n    private String lastLine;\n\n    public SingleLogcatReader(boolean recordingMode, String logBuffer, String lastLine) throws IOException {\n        super(recordingMode);\n        this.logBuffer = logBuffer;\n        this.lastLine = lastLine;\n        init();\n    }\n\n    private void init() throws IOException {\n        // use the \"time\" log so we can see what time the logs were logged at\n        logcatProcess = LogcatHelper.getLogcatProcess(logBuffer);\n\n        bufferedReader = new BufferedReader(new InputStreamReader(logcatProcess\n                .getInputStream()), 8192);\n    }\n\n\n    public String getLogBuffer() {\n        return logBuffer;\n    }\n\n\n    @Override\n    public void killQuietly() {\n        if (logcatProcess != null) {\n            RuntimeHelper.destroy(logcatProcess);\n        }\n    }\n\n    @Override\n    public String readLine() throws IOException {\n        String line = bufferedReader.readLine();\n\n        if (recordingMode && lastLine != null) { // still skipping past the 'last line'\n            if (lastLine.equals(line) /*|| isAfterLastTime(line)*/) {\n                lastLine = null; // indicates we've passed the last line\n            }\n        }\n\n        return line;\n    }\n\n    private boolean isAfterLastTime(String line) {\n        // doing a string comparison is sufficient to determine whether this line is chronologically\n        // after the last line, because the format they use is exactly the same and\n        // lists larger time period before smaller ones\n        return isDatedLogLine(lastLine) && isDatedLogLine(line) && line.compareTo(lastLine) > 0;\n\n    }\n\n    private boolean isDatedLogLine(String line) {\n        // 18 is the size of the logcat timestamp\n        return (!TextUtils.isEmpty(line) && line.length() >= 18 && Character.isDigit(line.charAt(0)));\n    }\n\n\n    @Override\n    public boolean readyToRecord() {\n        return recordingMode && lastLine == null;\n    }\n\n    @Override\n    public List<Process> getProcesses() {\n        return Collections.singletonList(logcatProcess);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/util/ArrayUtil.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo.util;\n\nimport java.lang.reflect.Array;\nimport java.util.List;\n\npublic class ArrayUtil {\n\n    public static <T> int indexOf(T[] array, T object) {\n        for (int i = 0; i < array.length; i++) {\n            if (object.equals(array[i])) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n\n    public static <T> T[] toArray(List<T> list, Class<T> clazz) {\n        @SuppressWarnings(\"unchecked\")\n        T[] result = (T[]) Array.newInstance(clazz, list.size());\n        for (int i = 0; i < list.size(); i++) {\n            result[i] = list.get(i);\n        }\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/util/SearchCriteria.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo.util;\n\nimport android.text.TextUtils;\n\nimport com.didichuxing.doraemonkit.kit.loginfo.LogLine;\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class SearchCriteria {\n\n    public static final String PID_KEYWORD = \"pid:\";\n    public static final String TAG_KEYWORD = \"tag:\";\n\n    private static final Pattern PID_PATTERN = Pattern.compile(\"pid:(\\\\d+)\", Pattern.CASE_INSENSITIVE);\n    private static final Pattern TAG_PATTERN = Pattern.compile(\"tag:(\\\"[^\\\"]+\\\"|\\\\S+)\", Pattern.CASE_INSENSITIVE);\n\n    private int pid = -1;\n    private String tag;\n    private String searchText;\n    private int searchTextAsInt = -1;\n\n    public SearchCriteria(CharSequence inputQuery) {\n\n        // check for the \"pid\" keyword\n\n        StringBuilder query = new StringBuilder(StringUtil.nullToEmpty(inputQuery));\n        Matcher pidMatcher = PID_PATTERN.matcher(query);\n        if (pidMatcher.find()) {\n            try {\n                pid = Integer.parseInt(pidMatcher.group(1));\n                query.replace(pidMatcher.start(), pidMatcher.end(), \"\"); // detach\n                // from\n                // search\n                // string\n            } catch (NumberFormatException ignore) {\n            }\n        }\n\n        // check for the \"tag\" keyword\n\n        Matcher tagMatcher = TAG_PATTERN.matcher(query);\n        if (tagMatcher.find()) {\n            tag = tagMatcher.group(1);\n            if (tag.startsWith(\"\\\"\") && tag.endsWith(\"\\\"\")) {\n                tag = tag.substring(1, tag.length() - 1); // detach quotes\n            }\n            query.replace(tagMatcher.start(), tagMatcher.end(), \"\"); // detach\n            // from\n            // search\n            // string\n        }\n\n        // everything else becomes a search term\n        searchText = query.toString().trim();\n\n        try {\n            searchTextAsInt = Integer.parseInt(searchText);\n        } catch (NumberFormatException ignore) {\n        }\n\n    }\n\n    public boolean isEmpty() {\n        return pid == -1 && TextUtils.isEmpty(tag) && TextUtils.isEmpty(searchText);\n    }\n\n    public boolean matches(LogLine logLine) {\n\n        // consider the criteria to be ANDed\n        if (!checkFoundPid(logLine)) {\n            return false;\n        }\n        if (!checkFoundTag(logLine)) {\n            return false;\n        }\n        return checkFoundText(logLine);\n    }\n\n    private boolean checkFoundText(LogLine logLine) {\n        return TextUtils.isEmpty(searchText)\n                || (searchTextAsInt != -1 && searchTextAsInt == logLine.getProcessId())\n                || (logLine.getTag() != null && StringUtil.containsIgnoreCase(logLine.getTag(), searchText))\n                || (logLine.getLogOutput() != null && StringUtil.containsIgnoreCase(logLine.getLogOutput(), searchText));\n    }\n\n    private boolean checkFoundTag(LogLine logLine) {\n        return TextUtils.isEmpty(tag)\n                || (logLine.getTag() != null && StringUtil.containsIgnoreCase(logLine.getTag(), tag));\n    }\n\n    private boolean checkFoundPid(LogLine logLine) {\n        return pid == -1 || logLine.getProcessId() == pid;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/util/StringUtil.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo.util;\n\nimport android.text.TextUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author nolan\n */\npublic class StringUtil {\n\n    /**\n     * Pad the specified number of spaces to the input string to make it that length\n     *\n     * @param input\n     * @param size\n     * @return\n     */\n    public static String padLeft(String input, int size) {\n\n        if (input.length() > size) {\n            throw new IllegalArgumentException(\"input must be shorter than or equal to the number of spaces: \" + size);\n        }\n\n        StringBuilder sb = new StringBuilder();\n        for (int i = input.length(); i < size; i++) {\n            sb.append(\" \");\n        }\n        return sb.append(input).toString();\n    }\n\n    /**\n     * same as the String.split(), except it doesn't use regexes, so it's faster.\n     *\n     * @param str       - the string to split up\n     * @param delimiter the delimiter\n     * @return the split string\n     */\n    public static String[] split(String str, String delimiter) {\n        List<String> result = new ArrayList<>();\n        int lastIndex = 0;\n        int index = str.indexOf(delimiter);\n        while (index != -1) {\n            result.add(str.substring(lastIndex, index));\n            lastIndex = index + delimiter.length();\n            index = str.indexOf(delimiter, index + delimiter.length());\n        }\n        result.add(str.substring(lastIndex, str.length()));\n\n        return ArrayUtil.toArray(result, String.class);\n    }\n\n    /*\n        * Replace all occurrences of the searchString in the originalString with the replaceString.  Faster than the\n        * String.replace() method.  Does not use regexes.\n        * <p/>\n        * If your searchString is empty, this will spin forever.\n        *\n        *\n        * @param originalString\n        * @param searchString\n        * @param replaceString\n        * @return\n        */\n    public static String replace(String originalString, String searchString, String replaceString) {\n        StringBuilder sb = new StringBuilder(originalString);\n        int index = sb.indexOf(searchString);\n        while (index != -1) {\n            sb.replace(index, index + searchString.length(), replaceString);\n            index += replaceString.length();\n            index = sb.indexOf(searchString, index);\n        }\n        return sb.toString();\n    }\n\n    public static String join(String delimiter, String[] strings) {\n\n        if (strings.length == 0) {\n            return \"\";\n        }\n\n        StringBuilder stringBuilder = new StringBuilder();\n        for (String str : strings) {\n            stringBuilder.append(\" \").append(str);\n        }\n\n        return stringBuilder.substring(1);\n    }\n\n\n    public static int computeLevenshteinDistance(CharSequence str1, CharSequence str2) {\n\n\n        int commonPrefixLength = findCommonPrefixLength(str1, str2);\n\n        if (commonPrefixLength == str1.length() && commonPrefixLength == str2.length()) {\n            return 0; // same exact string\n        }\n\n        int commonSuffixLength = findCommonSuffixLength(str1, str2, commonPrefixLength);\n\n        str1 = str1.subSequence(commonPrefixLength, str1.length() - commonSuffixLength);\n        str2 = str2.subSequence(commonPrefixLength, str2.length() - commonSuffixLength);\n\n        int[][] distance = new int[str1.length() + 1][str2.length() + 1];\n\n        for (int i = 0; i <= str1.length(); i++) {\n            distance[i][0] = i;\n        }\n        for (int j = 0; j <= str2.length(); j++) {\n            distance[0][j] = j;\n        }\n\n        for (int i = 1; i <= str1.length(); i++) {\n            for (int j = 1; j <= str2.length(); j++) {\n                distance[i][j] = minimum(\n                        distance[i - 1][j] + 1,\n                        distance[i][j - 1] + 1,\n                        distance[i - 1][j - 1] + ((str1.charAt(i - 1) == str2.charAt(j - 1)) ? 0\n                                : 1));\n            }\n        }\n\n        return distance[str1.length()][str2.length()];\n    }\n\n    private static int findCommonPrefixLength(CharSequence str1, CharSequence str2) {\n\n        int length = (Math.min(str1.length(), str2.length()));\n        for (int i = 0; i < length; i++) {\n            if (str1.charAt(i) != str2.charAt(i)) {\n                return i;\n            }\n        }\n\n        return 0;\n\n    }\n\n    private static int findCommonSuffixLength(CharSequence str1, CharSequence str2, int commonPrefixLength) {\n        int length = (Math.min(str1.length(), str2.length()));\n        for (int i = 0; i < length - commonPrefixLength; i++) {\n            if (str1.charAt(str1.length() - i - 1) != str2.charAt(str2.length() - i - 1)) {\n                return i;\n            }\n        }\n\n        return 0;\n    }\n\n    private static int minimum(int a, int b, int c) {\n        return Math.min(Math.min(a, b), c);\n    }\n\n    public static String join(int[] arr, String delimiter) {\n\n        if (arr.length == 0) {\n            return \"\";\n        }\n\n        StringBuilder sb = new StringBuilder();\n\n        for (int i : arr) {\n            sb.append(delimiter).append(Integer.toString(i));\n        }\n\n        return sb.substring(delimiter.length());\n\n    }\n\n    public static String capitalize(String str) {\n\n        StringBuilder sb = new StringBuilder(str);\n\n        for (int i = 0; i < sb.length(); i++) {\n            if (i == 0 || Character.isWhitespace(sb.charAt(i - 1))) {\n                sb.replace(i, i + 1, Character.toString(Character.toUpperCase(sb.charAt(i))));\n            }\n        }\n\n        return sb.toString();\n    }\n\n    public static String nullToEmpty(CharSequence str) {\n        return str == null ? \"\" : str.toString();\n    }\n\n    public static boolean isEmptyOrWhitespaceOnly(String str) {\n        if (TextUtils.isEmpty(str)) {\n            return true;\n        }\n        for (int i = 0; i < str.length(); i++) {\n            if (!Character.isWhitespace(str.charAt(i))) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * same as String.contains, but ignores case.\n     *\n     * @param str\n     * @param query\n     * @return\n     */\n    public static boolean containsIgnoreCase(String str, String query) {\n        if (str != null && query != null) {\n            int limit = str.length() - query.length() + 1;\n            for (int i = 0; i < limit; i++) {\n                if (matchesIgnoreCase(str, query, i)) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    private static boolean matchesIgnoreCase(String str, String query, int startingAt) {\n        int len = query.length();\n        for (int i = 0; i < len; i++) {\n            if (Character.toUpperCase(query.charAt(i)) != Character.toUpperCase(str.charAt(startingAt + i))) {\n                return false;\n            }\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/loginfo/util/TagColorUtil.java",
    "content": "package com.didichuxing.doraemonkit.kit.loginfo.util;\n\nimport android.content.Context;\nimport androidx.core.content.ContextCompat;\nimport android.util.Log;\nimport android.util.SparseIntArray;\nimport com.didichuxing.doraemonkit.R;\n\n/**\n * @author: linjizong\n *  2019/4/30\n * @desc:\n */\npublic class TagColorUtil {\n    private static final SparseIntArray TEXT_COLOR = new SparseIntArray(6);\n    private static final SparseIntArray TEXT_COLOR_EXPAND = new SparseIntArray(6);\n    private static final SparseIntArray LEVEL_COLOR = new SparseIntArray(6);\n    private static final SparseIntArray LEVEL_BG_COLOR = new SparseIntArray(6);\n\n    static {\n        TEXT_COLOR.put(Log.DEBUG, R.color.dk_color_000000);\n        TEXT_COLOR.put(Log.INFO, R.color.dk_color_000000);\n        TEXT_COLOR.put(Log.VERBOSE, R.color.dk_color_000000);\n        TEXT_COLOR.put(Log.ASSERT, R.color.dk_color_8F0005);\n        TEXT_COLOR.put(Log.ERROR, R.color.dk_color_FF0006);\n        TEXT_COLOR.put(Log.WARN, R.color.dk_color_0099dd);\n\n        TEXT_COLOR_EXPAND.put(Log.DEBUG, R.color.dk_color_FFFFFF);\n        TEXT_COLOR_EXPAND.put(Log.INFO, R.color.dk_color_FFFFFF);\n        TEXT_COLOR_EXPAND.put(Log.VERBOSE, R.color.dk_color_FFFFFF);\n        TEXT_COLOR_EXPAND.put(Log.ASSERT, R.color.dk_color_8F0005);\n        TEXT_COLOR_EXPAND.put(Log.ERROR, R.color.dk_color_FF0006);\n        TEXT_COLOR_EXPAND.put(Log.WARN, R.color.dk_color_0099dd);\n\n        LEVEL_BG_COLOR.put(Log.DEBUG, R.color.background_debug);\n        LEVEL_BG_COLOR.put(Log.ERROR, R.color.background_error);\n        LEVEL_BG_COLOR.put(Log.INFO, R.color.background_info);\n        LEVEL_BG_COLOR.put(Log.VERBOSE, R.color.background_verbose);\n        LEVEL_BG_COLOR.put(Log.WARN, R.color.background_warn);\n        LEVEL_BG_COLOR.put(Log.ASSERT, R.color.background_wtf);\n\n        LEVEL_COLOR.put(Log.DEBUG, R.color.foreground_debug);\n        LEVEL_COLOR.put(Log.ERROR, R.color.foreground_error);\n        LEVEL_COLOR.put(Log.INFO, R.color.foreground_info);\n        LEVEL_COLOR.put(Log.VERBOSE, R.color.foreground_verbose);\n        LEVEL_COLOR.put(Log.WARN, R.color.foreground_warn);\n        LEVEL_COLOR.put(Log.ASSERT, R.color.foreground_wtf);\n    }\n\n    public static int getTextColor(Context context, int level, boolean expand) {\n        SparseIntArray map = expand ? TEXT_COLOR_EXPAND : TEXT_COLOR;\n        Integer result = map.get(level);\n        if (result == null) {\n            result = map.get(Log.VERBOSE);\n        }\n        return ContextCompat.getColor(context, result);\n    }\n\n    public static int getLevelBgColor(Context context, int level) {\n        Integer result = LEVEL_BG_COLOR.get(level);\n        if (result == null) {\n            result = LEVEL_BG_COLOR.get(Log.VERBOSE);\n        }\n        return ContextCompat.getColor(context, result);\n    }\n\n    public static int getLevelColor(Context context, int level) {\n        Integer result = LEVEL_COLOR.get(level);\n        if (result == null) {\n            result = LEVEL_COLOR.get(Log.VERBOSE);\n        }\n        return ContextCompat.getColor(context, result);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/main/MainIconDoKitView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.main\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.config.FloatIconConfig\nimport com.didichuxing.doraemonkit.datapick.DataPickManager\nimport com.didichuxing.doraemonkit.datapick.DataPickUtils\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams\n\n/**\n * 悬浮按钮\n * Created by jintai on 2019/09/26.\n */\nclass MainIconDoKitView : AbsDoKitView() {\n\n    init {\n        viewProps.edgePinned = true\n    }\n\n    override fun onCreate(context: Context) {}\n\n    override fun onViewCreated(view: FrameLayout) {\n        //设置id便于查找\n        doKitView?.id = R.id.float_icon_id\n        //设置icon 点击事件\n        doKitView?.setOnClickListener { //统计入口\n            val pageId = DataPickUtils.getCurrentPage()\n            DataPickUtils.setDoKitHomeClickPage(pageId)\n            DataPickManager.getInstance().addData(\"dokit_sdk_home_ck_entry\", pageId)\n            DoKit.showToolPanel()\n        }\n\n//        DataPickManager.getInstance().addData(\"dokit_sdk_home_show\", DataPickUtils.getCurrentPage())\n    }\n\n    override fun onCreateView(context: Context, view: FrameLayout): View {\n        return LayoutInflater.from(context).inflate(R.layout.dk_main_launch_icon, view, false)\n    }\n\n    override fun initDokitViewLayoutParams(params: DoKitViewLayoutParams) {\n        params.x = FloatIconConfig.getLastPosX()\n        params.y = FloatIconConfig.getLastPosY()\n        params.width = DoKitViewLayoutParams.WRAP_CONTENT\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT\n    }\n\n    override fun onResume() {\n        super.onResume()\n        if (isNormalMode) {\n            immInvalidate()\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/methodtrace/AppHealthMethodCostBean.java",
    "content": "package com.didichuxing.doraemonkit.kit.methodtrace;\n\n\n/**\n * Created by hasee on 2017/4/20.\n */\npublic class AppHealthMethodCostBean {\n    private String functionName;\n    private String costTime = \"0\";\n\n\n    public String getFunctionName() {\n        return functionName;\n    }\n\n    public void setFunctionName(String functionName) {\n        this.functionName = functionName;\n    }\n\n    public String getCostTime() {\n        return costTime;\n    }\n\n    public void setCostTime(String costTime) {\n        this.costTime = costTime;\n    }\n\n\n    @Override\n    public String toString() {\n        return \"AppHealthMethodCostBean{\" +\n                \"functionName='\" + functionName + '\\'' +\n                \", costTime='\" + costTime + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/methodtrace/AppHealthMethodCostBeanWrap.java",
    "content": "package com.didichuxing.doraemonkit.kit.methodtrace;\n\n\nimport java.util.List;\n\n/**\n * Created by hasee on 2017/4/20.\n */\npublic class AppHealthMethodCostBeanWrap {\n    private String title;\n    private List<AppHealthMethodCostBean> data;\n\n    public String getTitle() {\n        return title;\n    }\n\n    public void setTitle(String title) {\n        this.title = title;\n    }\n\n    public List<AppHealthMethodCostBean> getData() {\n        return data;\n    }\n\n    public void setData(List<AppHealthMethodCostBean> data) {\n        this.data = data;\n    }\n\n    @Override\n    public String toString() {\n        return \"AppHealthMethodCostBeanWrap{\" +\n                \"trace='\" + title + '\\'' +\n                \", data=\" + data +\n                '}';\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/methodtrace/MethodCostKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.methodtrace\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager\nimport com.didichuxing.doraemonkit.kit.webview.CommWebViewFragment\nimport com.didichuxing.doraemonkit.kit.webview.WebViewManager.url\nimport com.google.auto.service.AutoService\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-10-15-18:22\n * 描    述：\n * 修订历史：\n * ================================================\n */\n@AutoService(AbstractKit::class)\nclass MethodCostKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_method_cost\n    override val icon: Int\n        get() = R.mipmap.dk_method_cost\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        url = NetworkManager.APP_DOCUMENT_URL\n        startUniversalActivity(CommWebViewFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_performance_ck_method_coast\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/MockKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.network\n\nimport android.app.Activity\nimport android.content.Context\nimport android.text.TextUtils\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.network.ui.NetWorkMockFragment\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.google.auto.service.AutoService\n\n/**\n * @author jintai\n * @desc: 网络监测kit\n */\n@AutoService(AbstractKit::class)\nclass MockKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_network_mock\n    override val icon: Int\n        get() = R.mipmap.dk_net_mock\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        if (TextUtils.isEmpty(DoKitManager.PRODUCT_ID)) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_platform_tip))\n            return false\n        }\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip))\n            return false\n        }\n        if (!DokitPluginConfig.SWITCH_NETWORK) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_network_close_tip))\n            return false\n        }\n        startUniversalActivity(NetWorkMockFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_platform_ck_mock\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/NetworkKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.network\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.network.ui.NetWorkMonitorFragment\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.google.auto.service.AutoService\n\n/**\n * @desc: 网络监测kit\n */\n@AutoService(AbstractKit::class)\nclass NetworkKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_network_monitor\n    override val icon: Int\n        get() = R.mipmap.dk_net_monitor\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip))\n            return false\n        }\n        if (!DokitPluginConfig.SWITCH_NETWORK) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_network_close_tip))\n            return false\n        }\n        startUniversalActivity(NetWorkMonitorFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_performance_ck_network\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/NetworkManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.network;\n\n\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport com.didichuxing.doraemonkit.BuildConfig;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * @desc: 提供网络抓包功能开启、关闭、数据统计功能的manager\n */\npublic class NetworkManager {\n    /**\n     * ########以下为数据mock 的相关网络接口#########\n     */\n\n    public static final String MOCK_SCHEME_HTTP = \"http://\";\n    public static final String MOCK_SCHEME_HTTPS = \"https://\";\n    //    private static final String MOCK_HOST_DEBUG = \"xyrd.intra.xiaojukeji.com\";\n    public static final String DOKIT_HOST = \"www.dokit.cn\";\n    public static final String MOCK_HOST_DEBUG = \"mock.dokit.cn\";\n    //    private static final String MOCK_HOST_DEBUG = \"pre.dokit.cn\";\n    public static final String MOCK_HOST_RELEASE = \"mock.dokit.cn\";\n    private static final String MOCK_DEBUG_DOMAIN = MOCK_SCHEME_HTTPS + MOCK_HOST_DEBUG;\n    private static final String MOCK_RELEASE_DOMAIN = MOCK_SCHEME_HTTPS + MOCK_HOST_RELEASE;\n    public static final String MOCK_DOMAIN = BuildConfig.DEBUG ? MOCK_DEBUG_DOMAIN : MOCK_RELEASE_DOMAIN;\n    public static final String MOCK_HOST = BuildConfig.DEBUG ? MOCK_HOST_DEBUG : MOCK_HOST_RELEASE;\n    /**\n     * ########以上为数据mock 的相关网络接口#########\n     */\n\n\n    /**\n     * ########app健康体检相关接口 的相关网络接口#########\n     * 线上地址：https://www.dokit.cn/healthCheck/addCheckData\n     * 测试环境地址:http://dokit-test.intra.xiaojukeji.com/healthCheck/addCheckData\n     */\n    public static final String APP_HEALTH_URL = \"https://www.dokit.cn/healthCheck/addCheckData\";\n    /**\n     * ########业务埋点的网络接口#########\n     */\n    public static final String APP_DATA_PICK_URL = \"https://www.dokit.cn/pointData/addPointData\";\n    //public static final String APP_DATA_PICK_URL = \"http://dokit-test.intra.xiaojukeji.com/pointData/addPointData\";\n    /**\n     * 慢函数操作文档\n     */\n\n    public static final String APP_DOCUMENT_URL = \"https://xingyun.xiaojukeji.com/docs/dokit/#/TimeProfiler\";\n\n    /**\n     * 文件同步助手操作文档\n     */\n\n    public static final String FILE_MANAGER_DOCUMENT_URL = \"https://xingyun.xiaojukeji.com/docs/dokit/#/FileList\";\n    /**\n     * 线上环境\n     * app 启动数据埋点\n     */\n    public static final String APP_START_DATA_PICK_URL = \"https://doraemon.xiaojukeji.com/uploadAppData\";\n\n    /**\n     * 测试环境\n     */\n    //public static final String APP_START_DATA_PICK_URL = \"http://dokit-test.intra.xiaojukeji.com/uploadAppData\";\n\n    /**\n     * dokit 更多页面接口请求\n     */\n    public static final String DOKIT_MORE_PAGE_URL = \"https://star.xiaojukeji.com/config/get.node?city=-1&areaid=&name=group\";\n    /**\n     * 预发环境\n     */\n    //public static final String APP_START_DATA_PICK_URL = \"http://pre.dokit.cn/uploadAppData\";\n\n\n    private static final int MAX_SIZE = 100;\n\n    private long mStartTime;\n    private OnNetworkInfoUpdateListener mOnNetworkInfoUpdateListener;\n\n    private int mPostCount;\n    private int mGetCount;\n    private int mTotalCount;\n\n    public NetworkManager() {\n    }\n\n    public NetworkRecord getRecord(int requestId) {\n        for (NetworkRecord record :\n                mRecords) {\n            if (record.mRequestId == requestId) {\n                return record;\n            }\n        }\n        return null;\n    }\n\n\n    private static class Holder {\n        private static NetworkManager INSTANCE = new NetworkManager();\n    }\n\n    private Handler mHandler = new Handler(Looper.getMainLooper());\n    private AtomicBoolean mIsActive = new AtomicBoolean(false);\n\n    /**\n     * 这个数据结构要求有序（方便移除最旧的数据），线程安全（网络请求是在子线程内执行，会在子线程内对数据进行查询插入删除操作），方便查找\n     * （需要根据requestId，找到对应的record），目前没找到同时满足三个条件的数据结构，暂时先保证前两者，因为限制了大小为MAX_SIZE，查找\n     * 的数据量不会很大，直接foreach\n     */\n    private List<NetworkRecord> mRecords = Collections.synchronizedList(new ArrayList<NetworkRecord>());\n\n    public static NetworkManager get() {\n        return NetworkManager.Holder.INSTANCE;\n    }\n\n    public void addRecord(int requestId, NetworkRecord record) {\n        if (mRecords.size() > MAX_SIZE) {\n            mRecords.remove(0);\n        }\n        if (record.isPostRecord()) {\n            mPostCount++;\n        } else if (record.isGetRecord()) {\n            mGetCount++;\n        }\n        mTotalCount++;\n        mRecords.add(record);\n\n        updateRecord(record, true);\n    }\n\n    public void updateRecord(final NetworkRecord record, final boolean add) {\n        /**\n         * post to main thread\n         */\n        mHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                if (DoKitManager.INSTANCE.getCALLBACK() != null && add) {\n                    DoKitManager.INSTANCE.getCALLBACK().onNetworkCallBack(record);\n                }\n                if (mOnNetworkInfoUpdateListener != null) {\n                    mOnNetworkInfoUpdateListener.onNetworkInfoUpdate(record, add);\n                }\n            }\n        });\n    }\n\n    public void startMonitor() {\n        if (mIsActive.get()) {\n            return;\n        }\n        mIsActive.set(true);\n        mStartTime = System.currentTimeMillis();\n    }\n\n    public void stopMonitor() {\n        if (!mIsActive.get()) {\n            return;\n        }\n        mIsActive.set(false);\n        mStartTime = 0;\n    }\n\n    public static boolean isActive() {\n        return get().mIsActive.get();\n    }\n\n    public List<NetworkRecord> getRecords() {\n        return mRecords;\n    }\n\n    public void setOnNetworkInfoUpdateListener(OnNetworkInfoUpdateListener onNetworkInfoUpdateListener) {\n        mOnNetworkInfoUpdateListener = onNetworkInfoUpdateListener;\n    }\n\n    public long getRunningTime() {\n        if (mStartTime == 0) {\n            return mStartTime;\n        }\n        long time = System.currentTimeMillis() - mStartTime;\n        return time;\n    }\n\n    public long getTotalRequestSize() {\n        long totalSize = 0;\n        for (NetworkRecord record : mRecords) {\n            totalSize += record.requestLength;\n        }\n        return totalSize;\n    }\n\n\n    public long getTotalSize() {\n        long totalSize = 0;\n        for (NetworkRecord record : mRecords) {\n            totalSize += record.requestLength;\n            totalSize += record.responseLength;\n        }\n        return totalSize;\n    }\n\n    public long getTotalResponseSize() {\n        long totalSize = 0;\n        for (NetworkRecord record : mRecords) {\n            totalSize += record.responseLength;\n        }\n        return totalSize;\n    }\n\n    public int getPostCount() {\n        return mPostCount;\n    }\n\n    public int getGetCount() {\n        return mGetCount;\n    }\n\n    public int getTotalCount() {\n        return mTotalCount;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/OnNetworkInfoUpdateListener.java",
    "content": "package com.didichuxing.doraemonkit.kit.network;\n\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord;\n\npublic interface OnNetworkInfoUpdateListener {\n    /**\n     * 网络请求更新时候的回调\n     * @param record\n     * @param add true表示添加，false表示更新\n     */\n    void onNetworkInfoUpdate(NetworkRecord record,boolean add);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/bean/MockApiResponseBean.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.bean;\n\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager;\n\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-12-20:19\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class MockApiResponseBean {\n\n    /**\n     * code : 200\n     * data : {\"datalist\":[{\"_id\":\"5dc925a3e6173367388831e0\",\"name\":\"模拟\",\"desc\":\"模拟dokit\",\"groupId\":\"\",\"projectId\":\"5c8e04056144a626ff2542e344\",\"categoryId\":\"5dc920a56de696654ec9b56b\",\"path\":\"/dokit1\",\"method\":\"GET\",\"formatType\":\"json\",\"params\":{\"pathParams\":[{\"name\":\"api\",\"value\":\"api.dj.login\"}]},\"owner\":{},\"interOwner\":{},\"curStatus\":{},\"createDate\":\"2019-11-11 17:10:59\",\"categoryName\":\"用户\",\"query\":{\"api\":\"api.dj.login\"},\"sceneList\":[{\"_id\":\"5dca79a0cf77257a255a0fda\",\"name\":\"场景2\",\"desc\":\"\",\"projectId\":\"5c8e04056144a626ff2542e344\",\"interfaceId\":\"5dc925a3e6173367388831e0\",\"createDate\":1573550496005},{\"_id\":\"5dca795bb073557a27024a24\",\"name\":\"测试场景\",\"desc\":\"\",\"projectId\":\"5c8e04056144a626ff2542e344\",\"interfaceId\":\"5dc925a3e6173367388831e0\",\"createDate\":1573550427707}]},{\"_id\":\"5dc92585b1b0846733194865\",\"name\":\"测试接口1\",\"desc\":\"\",\"groupId\":\"\",\"projectId\":\"5c8e04056144a626ff2542e344\",\"categoryId\":\"5dc920a56de696654ec9b56b\",\"path\":\"/inter1\",\"method\":\"POST\",\"formatType\":\"json\",\"params\":{\"pathParams\":[]},\"owner\":{\"_id\":\"5c87b096513b3e266f4027ca\",\"name\":\"卢群\",\"displayName\":\"卢群(普惠产品技术部)\",\"department\":\"普惠产品技术部\",\"employeeNumber\":\"D00252\",\"accountName\":\"luqun\",\"authority\":{\"admin\":true}},\"interOwner\":\"\",\"curStatus\":{\"status\":\"new\",\"date\":\"2019-11-11 17:10:29\"},\"createDate\":\"2019-11-11 17:10:29\",\"categoryName\":\"用户\",\"query\":{},\"sceneList\":[]}]}\n     */\n\n    private int code;\n    private DataBean data;\n\n    public int getCode() {\n        return code;\n    }\n\n    public void setCode(int code) {\n        this.code = code;\n    }\n\n    public DataBean getData() {\n        return data;\n    }\n\n    public void setData(DataBean data) {\n        this.data = data;\n    }\n\n    /**\n     * 该对象需要做特殊处理\n     */\n    public static class DataBean {\n        private List<DatalistBean> datalist;\n\n        public List<DatalistBean> getDatalist() {\n            return datalist;\n        }\n\n        public void setDatalist(List<DatalistBean> datalist) {\n            this.datalist = datalist;\n        }\n\n        public static class DatalistBean {\n            /**\n             * _id : 5dc925a3e6173367388831e0\n             * name : 模拟\n             * desc : 模拟dokit\n             * groupId :\n             * projectId : 5c8e04056144a626ff2542e344\n             * categoryId : 5dc920a56de696654ec9b56b\n             * path : /dokit1\n             * method : GET\n             * formatType : json\n             * params : {\"pathParams\":[{\"name\":\"api\",\"value\":\"api.dj.login\"}]}\n             * owner : {}\n             * interOwner : {}\n             * curStatus : {}\n             * createDate : 2019-11-11 17:10:59\n             * categoryName : 用户\n             * query : {\"api\":\"api.dj.login\"}\n             * sceneList : [{\"_id\":\"5dca79a0cf77257a255a0fda\",\"name\":\"场景2\",\"desc\":\"\",\"projectId\":\"5c8e04056144a626ff2542e344\",\"interfaceId\":\"5dc925a3e6173367388831e0\",\"createDate\":1573550496005},{\"_id\":\"5dca795bb073557a27024a24\",\"name\":\"测试场景\",\"desc\":\"\",\"projectId\":\"5c8e04056144a626ff2542e344\",\"interfaceId\":\"5dc925a3e6173367388831e0\",\"createDate\":1573550427707}]\n             */\n\n            private String _id;\n            private String name;\n            private String desc;\n            private String groupId;\n            private String projectId;\n            private String categoryId;\n            private String path;\n            private String method;\n            private String formatType;\n            //private ParamsBean params;\n            private OwnerBean owner;\n            //private Object interOwner;\n            private CurStatusBean curStatus;\n            private String createDate;\n            private String categoryName;\n            //private QueryBean query;\n            private List<SceneListBean> sceneList;\n\n            public String get_id() {\n                return _id;\n            }\n\n            public void set_id(String _id) {\n                this._id = _id;\n            }\n\n            public String getName() {\n                return name;\n            }\n\n            public void setName(String name) {\n                this.name = name;\n            }\n\n            public String getDesc() {\n                return desc;\n            }\n\n            public void setDesc(String desc) {\n                this.desc = desc;\n            }\n\n            public String getGroupId() {\n                return groupId;\n            }\n\n            public void setGroupId(String groupId) {\n                this.groupId = groupId;\n            }\n\n            public String getProjectId() {\n                return projectId;\n            }\n\n            public void setProjectId(String projectId) {\n                this.projectId = projectId;\n            }\n\n            public String getCategoryId() {\n                return categoryId;\n            }\n\n            public void setCategoryId(String categoryId) {\n                this.categoryId = categoryId;\n            }\n\n            /**\n             * 拦截一层kop\n             *\n             * @return path\n             */\n            public String getPath() {\n                if (DoKitManager.isRpcSDK()) {\n                    path = DoKitManager.dealDidiPlatformPath(path, DokitDbManager.FROM_SDK_DIDI);\n                }\n                return path;\n            }\n\n            public void setPath(String path) {\n                this.path = path;\n            }\n\n            public String getMethod() {\n                return method;\n            }\n\n            public void setMethod(String method) {\n                this.method = method;\n            }\n\n            public String getFormatType() {\n                return formatType;\n            }\n\n            public void setFormatType(String formatType) {\n                this.formatType = formatType;\n            }\n\n            public OwnerBean getOwner() {\n                return owner;\n            }\n\n            public void setOwner(OwnerBean owner) {\n                this.owner = owner;\n            }\n\n//            public Object getInterOwner() {\n//                return interOwner;\n//            }\n//\n//            public void setInterOwner(Object interOwner) {\n//                this.interOwner = interOwner;\n//            }\n\n            public CurStatusBean getCurStatus() {\n                return curStatus;\n            }\n\n            public void setCurStatus(CurStatusBean curStatus) {\n                this.curStatus = curStatus;\n            }\n\n            public String getCreateDate() {\n                return createDate;\n            }\n\n            public void setCreateDate(String createDate) {\n                this.createDate = createDate;\n            }\n\n            public String getCategoryName() {\n                return categoryName;\n            }\n\n            public void setCategoryName(String categoryName) {\n                this.categoryName = categoryName;\n            }\n\n//            public QueryBean getQuery() {\n//                return query;\n//            }\n//\n//            public void setQuery(QueryBean query) {\n//                this.query = query;\n//            }\n\n            public List<SceneListBean> getSceneList() {\n                return sceneList;\n            }\n\n            public void setSceneList(List<SceneListBean> sceneList) {\n                this.sceneList = sceneList;\n            }\n\n\n            public static class OwnerBean {\n                String _id;\n                String name;\n                String displayName;\n                String department;\n                String employeeNumber;\n                String accountName;\n\n\n                public String getName() {\n                    return name;\n                }\n\n                public String getDisplayName() {\n                    return displayName;\n                }\n\n                public String getDepartment() {\n                    return department;\n                }\n\n                public String getEmployeeNumber() {\n                    return employeeNumber;\n                }\n\n                public String getAccountName() {\n                    return accountName;\n                }\n            }\n\n            public static class InterOwnerBean {\n            }\n\n            public static class CurStatusBean {\n\n                String status;\n                OperatorBean operator;\n\n                public String getStatus() {\n                    return status;\n                }\n\n                public OperatorBean getOperator() {\n                    return operator;\n                }\n\n                public static class OperatorBean {\n                    String name;\n                    String displayName;\n                    String department;\n                    String employeeNumber;\n                    String accountName;\n\n                    public String getName() {\n                        return name;\n                    }\n\n                    public String getDisplayName() {\n                        return displayName;\n                    }\n\n                    public String getDepartment() {\n                        return department;\n                    }\n\n                    public String getEmployeeNumber() {\n                        return employeeNumber;\n                    }\n\n                    public String getAccountName() {\n                        return accountName;\n                    }\n                }\n            }\n\n            public static class QueryBean {\n                /**\n                 * api : api.dj.login\n                 */\n\n                private String api;\n\n                public String getApi() {\n                    return api;\n                }\n\n                public void setApi(String api) {\n                    this.api = api;\n                }\n            }\n\n            public static class SceneListBean {\n                /**\n                 * _id : 5dca79a0cf77257a255a0fda\n                 * name : 场景2\n                 * desc :\n                 * projectId : 5c8e04056144a626ff2542e344\n                 * interfaceId : 5dc925a3e6173367388831e0\n                 * createDate : 1573550496005\n                 */\n\n                private String _id;\n                private String name;\n                private String desc;\n                private String projectId;\n                private String interfaceId;\n                private long createDate;\n\n                public String get_id() {\n                    return _id;\n                }\n\n                public void set_id(String _id) {\n                    this._id = _id;\n                }\n\n                public String getName() {\n                    return name;\n                }\n\n                public void setName(String name) {\n                    this.name = name;\n                }\n\n                public String getDesc() {\n                    return desc;\n                }\n\n                public void setDesc(String desc) {\n                    this.desc = desc;\n                }\n\n                public String getProjectId() {\n                    return projectId;\n                }\n\n                public void setProjectId(String projectId) {\n                    this.projectId = projectId;\n                }\n\n                public String getInterfaceId() {\n                    return interfaceId;\n                }\n\n                public void setInterfaceId(String interfaceId) {\n                    this.interfaceId = interfaceId;\n                }\n\n                public long getCreateDate() {\n                    return createDate;\n                }\n\n                public void setCreateDate(long createDate) {\n                    this.createDate = createDate;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/bean/MockInterceptTitleBean.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.bean;\n\n\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.BaseExpandNode;\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.BaseNode;\n\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-12-15:06\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class MockInterceptTitleBean<T extends BaseNode> extends BaseExpandNode {\n    private String mName;\n    private List<T> mChildNode;\n\n    public MockInterceptTitleBean(String name, List<T> childNode) {\n        this.mName = name;\n        this.mChildNode = childNode;\n        setExpanded(false);\n    }\n\n    public String getName() {\n        return mName;\n    }\n\n\n    @Override\n    public List<BaseNode> getChildNode() {\n        return (List<BaseNode>) mChildNode;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/bean/MockTemplateTitleBean.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.bean;\n\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.BaseExpandNode;\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.BaseNode;\n\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-12-15:06\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class MockTemplateTitleBean<T extends BaseNode> extends BaseExpandNode {\n    private String mName;\n    private List<T> mChildNode;\n\n\n    public MockTemplateTitleBean(String path, List<T> childNode) {\n        this.mName = path;\n        this.mChildNode = childNode;\n        setExpanded(false);\n    }\n\n    public String getName() {\n        return mName;\n    }\n\n\n    @Override\n    public List<BaseNode> getChildNode() {\n        return (List<BaseNode>) mChildNode;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/bean/NetflowInfo.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.bean;\n\npublic class NetflowInfo {\n    public long flow;\n    public long timestamp;\n    public String page;\n    public boolean isUp;\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/bean/NetworkRecord.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.bean;\n\nimport android.text.TextUtils;\n\nimport java.io.Serializable;\n\n/**\n * @desc: 一条网络请求记录\n */\npublic class NetworkRecord implements Serializable {\n    private static final String METHOD_GET = \"get\";\n    private static final String METHOD_POST = \"post\";\n\n    public int mRequestId;\n    public Request mRequest;\n    public Response mResponse;\n    public String mPlatform;\n    public String mResponseBody;\n\n    public long requestLength;\n    public long responseLength;\n\n    public long startTime;\n    public long endTime;\n\n    public boolean filter(String text) {\n        // 目前只支持url筛选，后续需要再扩展\n        if (mRequest != null && mRequest.filter(text)) {\n            return true;\n        }\n        return false;\n    }\n\n\n    public boolean isGetRecord() {\n        return mRequest != null && mRequest.method != null && TextUtils.equals(METHOD_GET, mRequest.method.toLowerCase());\n    }\n\n    public boolean isPostRecord() {\n        return mRequest != null && mRequest.method != null && TextUtils.equals(METHOD_POST, mRequest.method.toLowerCase());\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/bean/Request.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.bean;\n\nimport android.text.TextUtils;\n\nimport java.io.Serializable;\n\n/**\n * 请求bean\n */\npublic class Request implements Serializable {\n\n    public String url;\n\n    public String method;\n\n    public String headers;\n\n    public String postData;\n\n    public String encode;\n\n    @Override\n    public String toString() {\n        return String.format(\"[%s %s %s %s]\", url, method, headers.toString(), postData);\n    }\n\n    public boolean filter(String text) {\n        return !TextUtils.isEmpty(url) && url.contains(text);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/bean/Response.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.bean;\n\nimport java.io.Serializable;\n\n/**\n * 响应bean,不包含内容Body\n */\npublic class Response  implements Serializable{\n\n    public String url;\n\n    public int status;\n\n    public String headers;\n\n    public String mimeType;\n\n    @Override\n    public String toString() {\n        return String.format(\"[%s %d  %s %s]\", url, status, headers.toString(), mimeType);\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/bean/WhiteHostBean.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.bean;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/19-14:45\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class WhiteHostBean {\n    private String host;\n    private boolean canAdd;\n\n    public WhiteHostBean(String host, boolean canAdd) {\n        this.host = host;\n        this.canAdd = canAdd;\n    }\n\n    public String getHost() {\n        return host;\n    }\n\n    public void setHost(String host) {\n        this.host = host;\n    }\n\n    public boolean isCanAdd() {\n        return canAdd;\n    }\n\n    public void setCanAdd(boolean canAdd) {\n        this.canAdd = canAdd;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/common/CommonHeaders.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.common;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.TreeMap;\nimport java.util.TreeSet;\n\n/**\n * The header fields of a single HTTP message. Values are uninterpreted strings; use {@code Request}\n * and {@code Response} for interpreted headers. This class maintains the order of the header fields\n * within the HTTP message.\n *\n * <p>This class tracks header values line-by-line. A field with multiple comma- separated values on\n * the same line will be treated as a field with a single value by this class. It is the caller's\n * responsibility to detect and split on commas if their field permits multiple values. This\n * simplifies use of single-valued fields whose values routinely contain commas, such as cookies or\n * dates.\n *\n * <p>This class trims whitespace from values. It never returns values with leading or trailing\n * whitespace.\n *\n * <p>Instances of this class are immutable. Use {@link Builder} to create instances.\n */\npublic final class CommonHeaders {\n  private final String[] namesAndValues;\n\n  CommonHeaders(Builder builder) {\n    this.namesAndValues = builder.namesAndValues.toArray(new String[builder.namesAndValues.size()]);\n  }\n\n  private CommonHeaders(String[] namesAndValues) {\n    this.namesAndValues = namesAndValues;\n  }\n\n  /** Returns the last value corresponding to the specified field, or null. */\n  public String get(String name) {\n    return get(namesAndValues, name);\n  }\n\n\n  /** Returns the number of field values. */\n  public int size() {\n    return namesAndValues.length / 2;\n  }\n\n  /** Returns the field at {@code position}. */\n  public String name(int index) {\n    return namesAndValues[index * 2];\n  }\n\n  /** Returns the value at {@code index}. */\n  public String value(int index) {\n    return namesAndValues[index * 2 + 1];\n  }\n\n  /** Returns an immutable case-insensitive set of header names. */\n  public Set<String> names() {\n    TreeSet<String> result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);\n    for (int i = 0, size = size(); i < size; i++) {\n      result.add(name(i));\n    }\n    return Collections.unmodifiableSet(result);\n  }\n\n  /** Returns an immutable list of the header values for {@code name}. */\n  public List<String> values(String name) {\n    List<String> result = null;\n    for (int i = 0, size = size(); i < size; i++) {\n      if (name.equalsIgnoreCase(name(i))) {\n        if (result == null) result = new ArrayList<>(2);\n        result.add(value(i));\n      }\n    }\n    return result != null\n        ? Collections.unmodifiableList(result)\n        : Collections.<String>emptyList();\n  }\n\n  public Builder newBuilder() {\n    Builder result = new Builder();\n    Collections.addAll(result.namesAndValues, namesAndValues);\n    return result;\n  }\n\n  /**\n   * Returns true if {@code other} is a {@code Headers} object with the same headers, with the same\n   * casing, in the same order. Note that two headers instances may be <i>semantically</i> equal\n   * but not equal according to this method. In particular, none of the following sets of headers\n   * are equal according to this method: <pre>   {@code\n   *\n   *   1. Original\n   *   Content-Type: text/html\n   *   Content-Length: 50\n   *\n   *   2. Different order\n   *   Content-Length: 50\n   *   Content-Type: text/html\n   *\n   *   3. Different case\n   *   content-type: text/html\n   *   content-length: 50\n   *\n   *   4. Different values\n   *   Content-Type: text/html\n   *   Content-Length: 050\n   * }</pre>\n   *\n   * Applications that require semantically equal headers should convert them into a canonical form\n   * before comparing them for equality.\n   */\n  @Override public boolean equals(Object other) {\n    return other instanceof CommonHeaders\n        && Arrays.equals(((CommonHeaders) other).namesAndValues, namesAndValues);\n  }\n\n  @Override public int hashCode() {\n    return Arrays.hashCode(namesAndValues);\n  }\n\n  @Override public String toString() {\n    StringBuilder result = new StringBuilder();\n    for (int i = 0, size = size(); i < size; i++) {\n      result.append(name(i)).append(\": \").append(value(i)).append(\"\\n\");\n    }\n    return result.toString();\n  }\n\n  public Map<String, List<String>> toMultimap() {\n    Map<String, List<String>> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);\n    for (int i = 0, size = size(); i < size; i++) {\n      String name = name(i).toLowerCase(Locale.US);\n      List<String> values = result.get(name);\n      if (values == null) {\n        values = new ArrayList<>(2);\n        result.put(name, values);\n      }\n      values.add(value(i));\n    }\n    return result;\n  }\n\n  private static String get(String[] namesAndValues, String name) {\n    for (int i = namesAndValues.length - 2; i >= 0; i -= 2) {\n      if (name.equalsIgnoreCase(namesAndValues[i])) {\n        return namesAndValues[i + 1];\n      }\n    }\n    return null;\n  }\n\n  /**\n   * Returns headers for the alternating header names and values. There must be an even number of\n   * arguments, and they must alternate between header names and values.\n   */\n  public static CommonHeaders of(String... namesAndValues) {\n    if (namesAndValues == null) throw new NullPointerException(\"namesAndValues == null\");\n    if (namesAndValues.length % 2 != 0) {\n      throw new IllegalArgumentException(\"Expected alternating header names and values\");\n    }\n\n    // Make a defensive copy and clean it up.\n    namesAndValues = namesAndValues.clone();\n    for (int i = 0; i < namesAndValues.length; i++) {\n      if (namesAndValues[i] == null) throw new IllegalArgumentException(\"Headers cannot be null\");\n      namesAndValues[i] = namesAndValues[i].trim();\n    }\n\n    // Check for malformed headers.\n    for (int i = 0; i < namesAndValues.length; i += 2) {\n      String name = namesAndValues[i];\n      String value = namesAndValues[i + 1];\n      if (name.length() == 0 || name.indexOf('\\0') != -1 || value.indexOf('\\0') != -1) {\n        throw new IllegalArgumentException(\"Unexpected header: \" + name + \": \" + value);\n      }\n    }\n\n    return new CommonHeaders(namesAndValues);\n  }\n\n  /**\n   * Returns headers for the header names and values in the {@link Map}.\n   */\n  public static CommonHeaders of(Map<String, String> headers) {\n    if (headers == null) throw new NullPointerException(\"headers == null\");\n\n    // Make a defensive copy and clean it up.\n    String[] namesAndValues = new String[headers.size() * 2];\n    int i = 0;\n    for (Map.Entry<String, String> header : headers.entrySet()) {\n      if (header.getKey() == null || header.getValue() == null) {\n        throw new IllegalArgumentException(\"Headers cannot be null\");\n      }\n      String name = header.getKey().trim();\n      String value = header.getValue().trim();\n      if (name.length() == 0 || name.indexOf('\\0') != -1 || value.indexOf('\\0') != -1) {\n        throw new IllegalArgumentException(\"Unexpected header: \" + name + \": \" + value);\n      }\n      namesAndValues[i] = name;\n      namesAndValues[i + 1] = value;\n      i += 2;\n    }\n\n    return new CommonHeaders(namesAndValues);\n  }\n\n  public static final class Builder {\n    final List<String> namesAndValues = new ArrayList<>(20);\n\n    /**\n     * Add a header line without any validation. Only appropriate for headers from the remote peer\n     * or cache.\n     */\n    Builder addLenient(String line) {\n      int index = line.indexOf(\":\", 1);\n      if (index != -1) {\n        return addLenient(line.substring(0, index), line.substring(index + 1));\n      } else if (line.startsWith(\":\")) {\n        // Work around empty header names and header names that start with a\n        // colon (created by old broken SPDY versions of the response cache).\n        return addLenient(\"\", line.substring(1)); // Empty header name.\n      } else {\n        return addLenient(\"\", line); // No header name.\n      }\n    }\n\n    /** Add an header line containing a field name, a literal colon, and a value. */\n    public Builder add(String line) {\n      int index = line.indexOf(\":\");\n      if (index == -1) {\n        throw new IllegalArgumentException(\"Unexpected header: \" + line);\n      }\n      return add(line.substring(0, index).trim(), line.substring(index + 1));\n    }\n\n    /** Add a field with the specified value. */\n    public Builder add(String name, String value) {\n      checkNameAndValue(name, value);\n      return addLenient(name, value);\n    }\n\n    /**\n     * Add a field with the specified value without any validation. Only appropriate for headers\n     * from the remote peer or cache.\n     */\n    Builder addLenient(String name, String value) {\n      namesAndValues.add(name);\n      namesAndValues.add(value.trim());\n      return this;\n    }\n\n    public Builder removeAll(String name) {\n      for (int i = 0; i < namesAndValues.size(); i += 2) {\n        if (name.equalsIgnoreCase(namesAndValues.get(i))) {\n          namesAndValues.remove(i); // name\n          namesAndValues.remove(i); // value\n          i -= 2;\n        }\n      }\n      return this;\n    }\n\n    /**\n     * Set a field with the specified value. If the field is not found, it is added. If the field is\n     * found, the existing values are replaced.\n     */\n    public Builder set(String name, String value) {\n      checkNameAndValue(name, value);\n      removeAll(name);\n      addLenient(name, value);\n      return this;\n    }\n\n    private void checkNameAndValue(String name, String value) {\n      if (name == null) throw new NullPointerException(\"name == null\");\n      if (name.isEmpty()) throw new IllegalArgumentException(\"name is empty\");\n      for (int i = 0, length = name.length(); i < length; i++) {\n        char c = name.charAt(i);\n        if (c <= '\\u0020' || c >= '\\u007f') {\n          throw new IllegalArgumentException(format(\n              \"Unexpected char %#04x at %d in header name: %s\", (int) c, i, name));\n        }\n      }\n      if (value == null) throw new NullPointerException(\"value == null\");\n      for (int i = 0, length = value.length(); i < length; i++) {\n        char c = value.charAt(i);\n        if ((c <= '\\u001f' && c != '\\t') || c >= '\\u007f') {\n          throw new IllegalArgumentException(format(\n              \"Unexpected char %#04x at %d in %s value: %s\", (int) c, i, name, value));\n        }\n      }\n    }\n\n    /** Equivalent to {@code build().get(name)}, but potentially faster. */\n    public String get(String name) {\n      for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) {\n        if (name.equalsIgnoreCase(namesAndValues.get(i))) {\n          return namesAndValues.get(i + 1);\n        }\n      }\n      return null;\n    }\n\n    public CommonHeaders build() {\n      return new CommonHeaders(this);\n    }\n  }\n\n  /** Returns a {@link Locale#US} formatted {@link String}. */\n  public static String format(String format, Object... args) {\n    return String.format(Locale.US, format, args);\n  }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/common/CommonInspectorRequest.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.common;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.text.TextUtils;\n\nimport com.didichuxing.doraemonkit.kit.network.core.NetworkInterpreter;\n\nimport java.util.List;\n\n/**\n * @desc: 抓包通用请求bean\n */\npublic class CommonInspectorRequest implements NetworkInterpreter.InspectorRequest {\n    private final int mRequestId;\n    private final String url;\n    private final String method;\n    private final String body;\n    private final CommonHeaders headers;\n\n    public CommonInspectorRequest(\n            int requestId,\n            @NonNull String url,\n            @NonNull String method,\n            @Nullable String body,\n            @Nullable CommonHeaders headers) {\n        mRequestId = requestId;\n        this.url = url;\n        this.method = method;\n        this.body = body;\n        this.headers = headers;\n    }\n\n    @Override\n    public int id() {\n        return mRequestId;\n    }\n\n\n    @Override\n    public String url() {\n        return url;\n    }\n\n    @Override\n    public String method() {\n        return method;\n    }\n\n    @Override\n    public byte[] body() {\n        if (TextUtils.isEmpty(body)) {\n            return null;\n        }\n        return body.getBytes();\n    }\n\n    @Override\n    public int headerCount() {\n        if (headers != null) {\n            return headers.size();\n        }\n        return 0;\n    }\n\n    @Override\n    public String headerName(int index) {\n        if (headers != null) {\n            return headers.name(index);\n        }\n        return null;\n    }\n\n    @Override\n    public String headerValue(int index) {\n        if (headers != null) {\n            return headers.value(index);\n        }\n        return null;\n    }\n\n    @Override\n    public String firstHeaderValue(String name) {\n        if (headers != null) {\n            List<String> values = headers.values(name);\n            if (values != null && values.size() > 0) {\n                return values.get(0);\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/common/CommonInspectorResponse.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.common;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.kit.network.core.NetworkInterpreter;\n\nimport java.util.List;\n\npublic class CommonInspectorResponse implements NetworkInterpreter.InspectorResponse {\n    private final int mRequestId;\n    private final String url;\n    private final int statusCode;\n    private final CommonHeaders headers;\n\n    public CommonInspectorResponse(\n            int requestId,\n            @NonNull String url,\n            int statusCode,\n            @Nullable CommonHeaders headers) {\n        mRequestId = requestId;\n        this.url = url;\n        this.statusCode = statusCode;\n        this.headers = headers;\n    }\n\n    @Override\n    public int requestId() {\n        return mRequestId;\n    }\n\n    @Override\n    public String url() {\n        return url;\n    }\n\n    @Override\n    public int statusCode() {\n        return statusCode;\n    }\n\n    @Override\n    public int headerCount() {\n        if (headers != null) {\n            return headers.size();\n        }\n        return 0;\n    }\n\n    @Override\n    public String headerName(int index) {\n        if (headers != null) {\n            return headers.name(index);\n        }\n        return null;\n    }\n\n    @Override\n    public String headerValue(int index) {\n        if (headers != null) {\n            return headers.value(index);\n        }\n        return null;\n    }\n\n    @Override\n    public String firstHeaderValue(String name) {\n        if (headers != null) {\n            List<String> values = headers.values(name);\n            if (values != null && values.size() > 0) {\n                return values.get(0);\n            }\n        }\n        return null;\n    }\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/common/NetworkPrinterHelper.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.common;\n\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord;\nimport com.didichuxing.doraemonkit.kit.network.core.NetworkInterpreter;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\n/**\n * @desc: 目前DoraemonKit只支持okhttp3和HttpUrlConnection的自动抓包，其他网络库如果想把网络请求打印出来，需要手动调用该类的方法将请求内容写入\n */\npublic class NetworkPrinterHelper {\n    private static final String TAG = \"NetworkLogHelper\";\n    private final NetworkInterpreter mInterpreter = NetworkInterpreter.get();\n\n    private NetworkPrinterHelper() {\n\n    }\n\n\n    private static class Holder {\n        private static NetworkPrinterHelper INSTANCE = new NetworkPrinterHelper();\n    }\n\n    private static NetworkPrinterHelper get() {\n        return Holder.INSTANCE;\n    }\n\n    /**\n     * @return 返回一个请求id，后续所有的更新操作都需要传入这个请求id，用以定位对应的bean\n     */\n    public static int obtainRequestId() {\n        return get().mInterpreter.nextRequestId();\n    }\n\n    public static void updateRequest(@NonNull CommonInspectorRequest request) {\n        get().mInterpreter.createRecord(request.id(), \"native\", request);\n    }\n\n    public static void updateResponse(@NonNull CommonInspectorResponse response) {\n        NetworkRecord networkRecord = NetworkManager.get().getRecord(response.requestId());\n        if (networkRecord != null) {\n            get().mInterpreter.fetchResponseInfo(networkRecord, response);\n        } else {\n            LogHelper.e(TAG, \"updateResponse fail ,record is null for requestId: \" + response.requestId());\n        }\n    }\n\n    public static void updateResponseBody(int requestId, String body) {\n        NetworkRecord networkRecord = NetworkManager.get().getRecord(requestId);\n        if (networkRecord != null) {\n            get().mInterpreter.fetchResponseBody(networkRecord, body);\n        } else {\n            LogHelper.e(TAG, \"updateResponseBody fail ,record is null for requestId: \" + requestId);\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/core/DefaultResponseHandler.java",
    "content": "/*\n * Copyright (c) 2014-present, Facebook, Inc.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree. An additional grant\n * of patent rights can be found in the PATENTS file in the same directory.\n */\n\npackage com.didichuxing.doraemonkit.kit.network.core;\n\n\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\n\npublic class DefaultResponseHandler implements ResponseHandler {\n    private final NetworkInterpreter mNetworkInterpreter;\n    private final int mRequestId;\n    private NetworkRecord mRecord;\n\n\n    public DefaultResponseHandler(NetworkInterpreter networkInterpreter, int requestId, NetworkRecord record) {\n        mNetworkInterpreter = networkInterpreter;\n        mRequestId = requestId;\n        mRecord = record;\n    }\n\n    @Override\n    public void onEOF(ByteArrayOutputStream outputStream) {\n        mNetworkInterpreter.responseReadFinished(mRequestId, mRecord, outputStream);\n    }\n\n    @Override\n    public void onError(IOException e) {\n        mNetworkInterpreter.responseReadFailed(mRequestId, e.toString());\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/core/MimeMatcher.java",
    "content": "/*\n * Copyright (c) 2014-present, Facebook, Inc.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree. An additional grant\n * of patent rights can be found in the PATENTS file in the same directory.\n */\n\npackage com.didichuxing.doraemonkit.kit.network.core;\n\nimport android.annotation.SuppressLint;\n\nimport java.util.ArrayList;\n\npublic class MimeMatcher<T> {\n  private final ArrayList<MimeMatcherRule> mRuleMap = new ArrayList<MimeMatcherRule>();\n\n  public void addRule(String ruleExpression, T resultIfMatched) {\n    mRuleMap.add(new MimeMatcherRule(ruleExpression, resultIfMatched));\n  }\n\n  public void clear() {\n    mRuleMap.clear();\n  }\n\n  public T match(String mimeT) {\n    int ruleMapN = mRuleMap.size();\n    for (int i = 0; i < ruleMapN; i++) {\n      MimeMatcherRule rule = mRuleMap.get(i);\n      if (rule.match(mimeT)) {\n        return rule.getResultIfMatched();\n      }\n    }\n    return null;\n  }\n\n  @SuppressLint(\"BadMethodUse-java.lang.String.length\")\n  private class MimeMatcherRule {\n    private final boolean mHasWildcard;\n    private final String mMatchPrefix;\n    private final T mResultIfMatched;\n\n    public MimeMatcherRule(String ruleExpression, T resultIfMatched) {\n      if (ruleExpression.endsWith(\"*\")) {\n        mHasWildcard = true;\n        mMatchPrefix = ruleExpression.substring(0, ruleExpression.length() - 1);\n      } else {\n        mHasWildcard = false;\n        mMatchPrefix = ruleExpression;\n      }\n      if (mMatchPrefix.contains(\"*\")) {\n        throw new IllegalArgumentException(\"Multiple wildcards present in rule expression \" +\n            ruleExpression);\n      }\n      mResultIfMatched = resultIfMatched;\n    }\n\n    public boolean match(String mimeType) {\n      if (!mimeType.startsWith(mMatchPrefix)) {\n        return false;\n      }\n      return (mHasWildcard || mimeType.length() == mMatchPrefix.length());\n    }\n\n    public T getResultIfMatched() {\n      return mResultIfMatched;\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/core/NetworkInterpreter.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.core;\n\nimport android.text.TextUtils;\n\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord;\nimport com.didichuxing.doraemonkit.kit.network.bean.Request;\nimport com.didichuxing.doraemonkit.kit.network.bean.Response;\nimport com.didichuxing.doraemonkit.kit.network.stream.InputStreamProxy;\nimport com.didichuxing.doraemonkit.kit.network.utils.Utf8Charset;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * @desc: 网络请求解析类\n */\npublic class NetworkInterpreter {\n\n    private static class Holder {\n        private static NetworkInterpreter INSTANCE = new NetworkInterpreter();\n    }\n\n    public static final String TAG = \"NetworkInterpreter\";\n\n    private final AtomicInteger mNextRequestId = new AtomicInteger(0);\n    private ResourceTypeHelper mResourceTypeHelper;\n\n    public static NetworkInterpreter get() {\n        return NetworkInterpreter.Holder.INSTANCE;\n    }\n\n    public void httpExchangeFailed(int requestId, String s) {\n        //LogHelper.i(TAG, \"[httpExchangeFailed] requestId: \" + requestId + \" error: \" + s);\n    }\n\n    public void responseReadFinished(int requestId, NetworkRecord record, ByteArrayOutputStream outputStream) {\n        if (outputStream != null) {\n            record.responseLength = outputStream.size();\n            record.mResponseBody = outputStream.toString();\n            //LogHelper.i(TAG, \"[responseReadFinished] body: \" + record.mResponseBody.toString().length());\n        } else {\n            //LogHelper.i(TAG, \"[responseReadFinished] outputStream is null request id: \" + requestId);\n        }\n    }\n\n    public void responseReadFailed(int requestId, String s) {\n        LogHelper.i(TAG, \"[responseReadFailed] requestId: \" + requestId + \" error: \" + s);\n    }\n\n    public int nextRequestId() {\n        return mNextRequestId.getAndIncrement();\n    }\n\n    public InputStream interpretResponseStream(\n            String contentType,\n            @Nullable InputStream availableInputStream,\n            ResponseHandler responseHandler\n    ) {\n        if (availableInputStream == null) {\n            responseHandler.onEOF(null);\n            return null;\n        }\n        ResourceType resourceType = contentType != null\n                ? getResourceTypeHelper().determineResourceType(contentType)\n                : null;\n        if (resourceType != ResourceType.DOCUMENT && resourceType != ResourceType.XHR) {\n            responseHandler.onEOF(null);\n            return availableInputStream;\n        }\n        return new InputStreamProxy(availableInputStream, responseHandler);\n    }\n\n    public NetworkRecord createRecord(int requestId, String platform, NetworkInterpreter.InspectorRequest request) {\n        NetworkRecord record = new NetworkRecord();\n        record.mRequestId = requestId;\n        record.mPlatform = platform;\n        fetchRequestInfo(record, request);\n        NetworkManager.get().addRecord(requestId, record);\n        return record;\n    }\n\n    private void fetchRequestInfo(NetworkRecord record, NetworkInterpreter.InspectorRequest request) {\n        Request requestJSON = new Request();\n        requestJSON.url = request.url();\n        requestJSON.method = request.method();\n        requestJSON.headers = formatHeadersAsString(request);\n        requestJSON.encode = request.firstHeaderValue(\"Content-Encoding\");\n        requestJSON.postData = readBodyAsString(request);\n        record.mRequest = requestJSON;\n        record.startTime = System.currentTimeMillis();\n        record.requestLength = readBodyLength(request);\n        //Log.e(TAG, requestJSON.toString());\n    }\n\n    public void fetRequestBody(NetworkRecord record, byte[] request) {\n        if (record.mRequest != null) {\n            record.mRequest.postData = readBodyAsString(request);\n            record.requestLength = readBodyLength(request);\n            NetworkManager.get().updateRecord(record, false);\n            //Log.e(TAG, record.mRequest.postData);\n        }\n    }\n\n    public void fetchResponseBody(NetworkRecord record, String body) {\n        if (TextUtils.isEmpty(body)) {\n            record.responseLength = 0;\n            record.mResponseBody = null;\n        } else {\n            record.responseLength = body.getBytes().length;\n            record.mResponseBody = body;\n        }\n    }\n\n    public void fetchResponseInfo(NetworkRecord record, NetworkInterpreter.InspectorResponse response) {\n        Response responseJSON = new Response();\n        responseJSON.url = response.url();\n        responseJSON.status = response.statusCode();\n        responseJSON.headers = formatHeadersAsString(response);\n        String contentType = getContentType(response);\n        responseJSON.mimeType = contentType != null ?\n                getResourceTypeHelper().stripContentExtras(contentType) :\n                \"application/octet-stream\";\n        record.mResponse = responseJSON;\n        record.endTime = System.currentTimeMillis();\n        NetworkManager.get().updateRecord(record, false);\n        //Log.e(TAG, responseJSON.toString());\n    }\n\n    private ResourceTypeHelper getResourceTypeHelper() {\n        if (mResourceTypeHelper == null) {\n            mResourceTypeHelper = new ResourceTypeHelper();\n        }\n        return mResourceTypeHelper;\n    }\n\n    private String formatHeadersAsString(NetworkInterpreter.InspectorHeaders headers) {\n        StringBuilder builder = new StringBuilder();\n        for (int i = 0; i < headers.headerCount(); i++) {\n            String name = headers.headerName(i);\n            String value = headers.headerValue(i);\n            builder.append(name + \":\" + value);\n            if (i != headers.headerCount() - 1) {\n                builder.append(\"\\n\");\n            }\n        }\n        return builder.toString();\n    }\n\n    private String readBodyAsString(NetworkInterpreter.InspectorRequest request) {\n        try {\n            byte[] body = request.body();\n            if (body != null) {\n                return new String(body, Utf8Charset.INSTANCE);\n            }\n        } catch (IOException | OutOfMemoryError e) {\n        }\n        return null;\n    }\n\n    private String readBodyAsString(byte[] body) {\n        try {\n            if (body != null) {\n                return new String(body, Utf8Charset.INSTANCE);\n            }\n        } catch (OutOfMemoryError e) {\n        }\n        return null;\n    }\n\n    private long readBodyLength(NetworkInterpreter.InspectorRequest request) {\n        try {\n            byte[] body = request.body();\n            if (body != null) {\n                return body.length;\n            }\n        } catch (IOException | OutOfMemoryError e) {\n        }\n        return 0;\n    }\n\n    private long readBodyLength(byte[] body) {\n        try {\n            if (body != null) {\n                return body.length;\n            }\n        } catch (OutOfMemoryError e) {\n        }\n        return 0;\n    }\n\n    private String getContentType(NetworkInterpreter.InspectorHeaders headers) {\n        // This may need to change in the future depending on how cumbersome header simulation\n        // is for the various hooks we expose.\n        return headers.firstHeaderValue(\"Content-Type\");\n    }\n\n\n    /**\n     * Represents the request that will be sent over HTTP.  Note that for many implementations\n     * of HTTP the request constructed may differ from the request actually sent over the wire.\n     * For instance, additional headers like {@code Host}, {@code User-Agent}, {@code Content-Type},\n     * etc may not be part of this request but should be injected if necessary.  Some stacks offer\n     * inspection of the raw request about to be sent to the server which is preferable.\n     */\n    public interface InspectorRequest extends InspectorRequestCommon {\n\n        String url();\n\n        /**\n         * HTTP method (\"GET\", \"POST\", \"DELETE\", etc).\n         */\n        String method();\n\n        /**\n         * Provide the body if part of an entity-enclosing request (like \"POST\" or \"PUT\").  May\n         * return null otherwise.\n         */\n        @Nullable\n        byte[] body() throws IOException;\n    }\n\n    public interface InspectorResponse extends InspectorResponseCommon {\n        String url();\n    }\n\n    public interface InspectorRequestCommon extends InspectorHeaders {\n        /**\n         * Unique identifier for this request.  This identifier must be used in all other network\n         * events corresponding to this request.  Identifiers may be re-used for HTTP requests  or\n         * WebSockets that have exhuasted the state machine to its final closed/finished state.\n         */\n        int id();\n    }\n\n    public interface InspectorResponseCommon extends InspectorHeaders {\n        /**\n         * @see InspectorRequest#id()\n         */\n        int requestId();\n\n        int statusCode();\n    }\n\n    public interface InspectorHeaders {\n        int headerCount();\n\n        String headerName(int index);\n\n        String headerValue(int index);\n\n        @Nullable\n        String firstHeaderValue(String name);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/core/RequestBodyHelper.java",
    "content": "/*\n * Copyright (c) 2014-present, Facebook, Inc.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree. An additional grant\n * of patent rights can be found in the PATENTS file in the same directory.\n */\n\npackage com.didichuxing.doraemonkit.kit.network.core;\n\n\nimport com.didichuxing.doraemonkit.kit.network.stream.GunzippingOutputStream;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.zip.InflaterOutputStream;\n\n/**\n * @desc: 请求body解析器\n */\npublic class RequestBodyHelper {\n\n    private ByteArrayOutputStream mDeflatedOutput;\n    private static final String GZIP_ENCODING = \"gzip\";\n    private static final String DEFLATE_ENCODING = \"deflate\";\n\n    public OutputStream createBodySink(String contentEncoding) throws IOException {\n        OutputStream deflatingOutput;\n        ByteArrayOutputStream deflatedOutput = new ByteArrayOutputStream();\n        if (GZIP_ENCODING.equals(contentEncoding)) {\n            deflatingOutput = GunzippingOutputStream.create(deflatedOutput);\n        } else if (DEFLATE_ENCODING.equals(contentEncoding)) {\n            deflatingOutput = new InflaterOutputStream(deflatedOutput);\n        } else {\n            deflatingOutput = deflatedOutput;\n        }\n        mDeflatedOutput = deflatedOutput;\n        return deflatingOutput;\n    }\n\n    public byte[] getDisplayBody() {\n        throwIfNoBody();\n        return mDeflatedOutput.toByteArray();\n    }\n\n    public boolean hasBody() {\n        return mDeflatedOutput != null;\n    }\n\n    private void throwIfNoBody() {\n        if (!hasBody()) {\n            throw new IllegalStateException(\"No body found; has createBodySink been called?\");\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/core/ResourceType.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.core;\n\npublic enum ResourceType {\n    DOCUMENT(\"Document\"),\n    STYLESHEET(\"Stylesheet\"),\n    IMAGE(\"Image\"),\n    FONT(\"Font\"),\n    SCRIPT(\"Script\"),\n    XHR(\"XHR\"),\n    WEBSOCKET(\"WebSocket\"),\n    OTHER(\"Other\");\n\n    private final String mProtocolValue;\n\n    private ResourceType(String protocolValue) {\n      mProtocolValue = protocolValue;\n    }\n\n    public String getProtocolValue() {\n      return mProtocolValue;\n    }\n  }"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/core/ResourceTypeHelper.java",
    "content": "/*\n * Copyright (c) 2014-present, Facebook, Inc.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree. An additional grant\n * of patent rights can be found in the PATENTS file in the same directory.\n */\n\npackage com.didichuxing.doraemonkit.kit.network.core;\n\n\npublic class ResourceTypeHelper {\n    private final MimeMatcher<ResourceType> mMimeMatcher;\n\n    public ResourceTypeHelper() {\n        mMimeMatcher = new MimeMatcher();\n        mMimeMatcher.addRule(\"text/css\", ResourceType.STYLESHEET);\n        mMimeMatcher.addRule(\"image/*\", ResourceType.IMAGE);\n        mMimeMatcher.addRule(\"application/x-javascript\", ResourceType.SCRIPT);\n\n        // This is apparently important to allow the WebKit inspector to do JSON preview.  I don't\n        // know exactly why, but whatever.\n        mMimeMatcher.addRule(\"text/javascript\", ResourceType.XHR);\n        mMimeMatcher.addRule(\"application/json\", ResourceType.XHR);\n\n        // Everything else gets a lame, unformatted blob.\n        mMimeMatcher.addRule(\"text/*\", ResourceType.DOCUMENT);\n\n        // I think this disables preview.  Perhaps that's not what we want as the default but we'll\n        // need some time to soak in real data to see for sure.\n        mMimeMatcher.addRule(\"*\", ResourceType.OTHER);\n    }\n\n    public ResourceType determineResourceType(String contentType) {\n        String mimeType = stripContentExtras(contentType);\n        return mMimeMatcher.match(mimeType);\n    }\n\n    /**\n     * Strip out any extra data following the semicolon (e.g. \\\"text/javascript; charset=UTF-8\").\n     *\n     * @return MIME type with content extras stripped out (if there were any).\n     */\n    public String stripContentExtras(String contentType) {\n        int index = contentType.indexOf(';');\n        return (index >= 0) ? contentType.substring(0, index) : contentType;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/core/ResponseHandler.java",
    "content": "/*\n * Copyright (c) 2014-present, Facebook, Inc.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree. An additional grant\n * of patent rights can be found in the PATENTS file in the same directory.\n */\n\npackage com.didichuxing.doraemonkit.kit.network.core;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\n\npublic interface ResponseHandler {\n\n  void onEOF(ByteArrayOutputStream outputStream);\n\n  void onError(IOException e);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/okhttp/ForwardingResponseBody.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.okhttp;\n\nimport java.io.InputStream;\n\nimport okhttp3.MediaType;\nimport okhttp3.ResponseBody;\nimport okio.BufferedSource;\nimport okio.Okio;\n\npublic class ForwardingResponseBody extends ResponseBody {\n    private final ResponseBody mBody;\n    private final BufferedSource mInterceptedSource;\n\n    public ForwardingResponseBody(ResponseBody body, InputStream interceptedStream) {\n        mBody = body;\n        mInterceptedSource = Okio.buffer(Okio.source(interceptedStream));\n    }\n\n    @Override\n    public MediaType contentType() {\n        return mBody.contentType();\n    }\n\n    @Override\n    public long contentLength() {\n        return mBody.contentLength();\n    }\n\n    @Override\n    public BufferedSource source() {\n        return mInterceptedSource;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/okhttp/InterceptorUtil.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.okhttp;\n\nimport com.didichuxing.doraemonkit.kit.network.core.ResourceType;\nimport com.didichuxing.doraemonkit.kit.network.core.ResourceTypeHelper;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/2/26-17:04\n * 描    述：拦截器工具类\n * 修订历史：\n * ================================================\n */\npublic class InterceptorUtil {\n\n    public static boolean isImg(String contentType) {\n        ResourceTypeHelper resourceTypeHelper = new ResourceTypeHelper();\n        ResourceType resourceType = contentType != null ? resourceTypeHelper.determineResourceType(contentType) : null;\n        if (resourceType == ResourceType.IMAGE) {\n            return true;\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/okhttp/OkHttpInspectorRequest.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.okhttp;\n\nimport com.didichuxing.doraemonkit.kit.network.core.NetworkInterpreter;\nimport com.didichuxing.doraemonkit.kit.network.core.RequestBodyHelper;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport okio.BufferedSink;\nimport okio.Okio;\n\npublic class OkHttpInspectorRequest implements NetworkInterpreter.InspectorRequest {\n    private final int mRequestId;\n    private final Request mRequest;\n    private RequestBodyHelper mRequestBodyHelper;\n\n    public OkHttpInspectorRequest(\n            int requestId,\n            Request request,\n            RequestBodyHelper requestBodyHelper) {\n        mRequestId = requestId;\n        mRequest = request;\n        mRequestBodyHelper = requestBodyHelper;\n    }\n\n    @Override\n    public int id() {\n        return mRequestId;\n    }\n\n\n    @Override\n    public String url() {\n        return mRequest.url().toString();\n    }\n\n    @Override\n    public String method() {\n        return mRequest.method();\n    }\n\n    @Override\n    public byte[] body() throws IOException {\n        RequestBody body = mRequest.body();\n        if (body == null) {\n            return null;\n        }\n        OutputStream out = mRequestBodyHelper.createBodySink(firstHeaderValue(\"Content-Encoding\"));\n        BufferedSink bufferedSink = Okio.buffer(Okio.sink(out));\n        try {\n            body.writeTo(bufferedSink);\n        } finally {\n            bufferedSink.close();\n        }\n        return mRequestBodyHelper.getDisplayBody();\n    }\n\n    @Override\n    public int headerCount() {\n        return mRequest.headers().size();\n    }\n\n    @Override\n    public String headerName(int index) {\n        return mRequest.headers().name(index);\n    }\n\n    @Override\n    public String headerValue(int index) {\n        return mRequest.headers().value(index);\n    }\n\n    @Override\n    public String firstHeaderValue(String name) {\n        return mRequest.header(name);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/okhttp/OkHttpInspectorResponse.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.okhttp;\n\nimport com.didichuxing.doraemonkit.kit.network.core.NetworkInterpreter;\n\nimport okhttp3.Request;\nimport okhttp3.Response;\n\npublic class OkHttpInspectorResponse implements NetworkInterpreter.InspectorResponse {\n    private final int mRequestId;\n    private final Request mRequest;\n    private final Response mResponse;\n\n    public OkHttpInspectorResponse(\n            int requestId,\n            Request request,\n            Response response) {\n        mRequestId = requestId;\n        mRequest = request;\n        mResponse = response;\n    }\n\n    @Override\n    public int requestId() {\n        return mRequestId;\n    }\n\n    @Override\n    public String url() {\n        return mRequest.url().toString();\n    }\n\n    @Override\n    public int statusCode() {\n        return mResponse.code();\n    }\n\n    @Override\n    public int headerCount() {\n        return mResponse.headers().size();\n    }\n\n    @Override\n    public String headerName(int index) {\n        return mResponse.headers().name(index);\n    }\n\n    @Override\n    public String headerValue(int index) {\n        return mResponse.headers().value(index);\n    }\n\n    @Override\n    public String firstHeaderValue(String name) {\n        return mResponse.header(name);\n    }\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/okhttp/interceptor/AbsDoKitInterceptor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.network.okhttp.interceptor\n\nimport android.nfc.Tag\nimport okhttp3.Interceptor\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：4/13/21-13:18\n * 描    述： 不做具体实现 只用来做运行时判定 外部不需要继承\n * 修订历史：\n * ================================================\n */\nabstract class AbsDoKitInterceptor : Interceptor {\n    val TAG = this.javaClass.simpleName\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/okhttp/interceptor/DokitCapInterceptor.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.okhttp.interceptor;\n\nimport android.text.TextUtils;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord;\nimport com.didichuxing.doraemonkit.kit.network.bean.WhiteHostBean;\nimport com.didichuxing.doraemonkit.kit.network.core.DefaultResponseHandler;\nimport com.didichuxing.doraemonkit.kit.network.core.NetworkInterpreter;\nimport com.didichuxing.doraemonkit.kit.network.core.RequestBodyHelper;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.ForwardingResponseBody;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.InterceptorUtil;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.OkHttpInspectorRequest;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.OkHttpInspectorResponse;\nimport com.didichuxing.doraemonkit.kit.network.utils.OkHttpResponseKt;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.List;\n\nimport okhttp3.MediaType;\nimport okhttp3.Protocol;\nimport okhttp3.Request;\nimport okhttp3.Response;\nimport okhttp3.ResponseBody;\n\n/**\n * 抓包拦截器\n */\npublic class DokitCapInterceptor extends AbsDoKitInterceptor {\n\n    private final NetworkInterpreter mNetworkInterpreter = NetworkInterpreter.get();\n\n    @NonNull\n    @Override\n    public Response intercept(@NonNull Chain chain) throws IOException {\n        if (!NetworkManager.isActive()) {\n            Request request = chain.request();\n            try {\n                return chain.proceed(request);\n            } catch (Exception e) {\n                ResponseBody responseBody = ResponseBody.create(MediaType.parse(\"application/json;charset=utf-8\"), \"\" + e.getMessage());\n                return new Response.Builder()\n                        .code(400)\n                        .message(String.format(\"%s==>Exception:%s\", chain.request().url().host(), e.getMessage()))\n                        .request(request)\n                        .body(responseBody)\n                        .protocol(Protocol.HTTP_1_1)\n                        .build();\n            }\n        }\n\n        Request request = chain.request();\n        int requestId = mNetworkInterpreter.nextRequestId();\n        Response response;\n        try {\n            response = chain.proceed(request);\n        } catch (Exception e) {\n            LogHelper.e(getTAG(), \"e===>\" + e.getMessage());\n            mNetworkInterpreter.httpExchangeFailed(requestId, e.toString());\n            ResponseBody responseBody = ResponseBody.create(MediaType.parse(\"application/json;charset=utf-8\"), \"\" + e.getMessage());\n            return new Response.Builder()\n                    .code(400)\n                    .message(String.format(\"%s==>Exception:%s\", chain.request().url().host(), e.getMessage()))\n                    .request(request)\n                    .body(responseBody)\n                    .protocol(Protocol.HTTP_1_1)\n                    .build();\n        }\n\n        String strContentType = response.header(\"Content-Type\");\n        //如果是图片则不进行拦截\n        if (InterceptorUtil.isImg(strContentType)) {\n            return response;\n        }\n        //白名单过滤\n        if (!matchWhiteHost(request)) {\n            return response;\n        }\n\n\n        RequestBodyHelper requestBodyHelper = new RequestBodyHelper();\n        OkHttpInspectorRequest inspectorRequest =\n                new OkHttpInspectorRequest(requestId, request, requestBodyHelper);\n        String platform = \"native\";\n        if (request.url().toString().contains(\"dokit_flag\")) {\n            platform = \"web\";\n        }\n        NetworkRecord record = mNetworkInterpreter.createRecord(requestId, platform, inspectorRequest);\n\n        NetworkInterpreter.InspectorResponse inspectorResponse = new OkHttpInspectorResponse(\n                requestId,\n                request,\n                response);\n        mNetworkInterpreter.fetchResponseInfo(record, inspectorResponse);\n\n        ResponseBody body = response.body();\n        InputStream responseStream = null;\n        MediaType contentType = null;\n        if (body != null) {\n            contentType = body.contentType();\n            responseStream = body.byteStream();\n        }\n\n        responseStream = mNetworkInterpreter.interpretResponseStream(\n                contentType != null ? contentType.toString() : null,\n                responseStream,\n                new DefaultResponseHandler(mNetworkInterpreter, requestId, record));\n        record.mResponseBody = OkHttpResponseKt.bodyContent(response);\n        LogHelper.d(\"http-monitor\", \"response body >>>\\n\" + record.mResponseBody);\n\n        if (responseStream != null) {\n            response = response.newBuilder()\n                    .body(new ForwardingResponseBody(body, responseStream))\n                    .build();\n        }\n\n        return response;\n    }\n\n    /**\n     * 是否命中白名单规则\n     *\n     * @return bool\n     */\n    private boolean matchWhiteHost(Request request) {\n        List<WhiteHostBean> whiteHostBeans = DoKitManager.WHITE_HOSTS;\n        if (whiteHostBeans.isEmpty()) {\n            return true;\n        }\n\n        for (WhiteHostBean whiteHostBean : whiteHostBeans) {\n            if (TextUtils.isEmpty(whiteHostBean.getHost())) {\n                continue;\n            }\n            String realHost = request.url().host();\n            //正则判断\n            if (whiteHostBean.getHost().equalsIgnoreCase(realHost)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/okhttp/interceptor/DokitExtInterceptor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.network.okhttp.interceptor\n\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport java.io.IOException\n\n/**\n * Author: xuweiyu\n * Date: 5/11/21\n * Email: wizz.xu@outlook.com\n * Description: Dokit 扩展网络拦截器\n */\nclass DokitExtInterceptor : AbsDoKitInterceptor() {\n    @Throws(IOException::class)\n    override fun intercept(chain: Interceptor.Chain): Response {\n\n        return dokitExtInterceptorProxy?.intercept(chain) ?: chain.proceed(chain.request())\n    }\n\n    interface DokitExtInterceptorProxy {\n        fun intercept(chain: Interceptor.Chain): Response\n    }\n\n    companion object {\n        var dokitExtInterceptorProxy: DokitExtInterceptorProxy? = null\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/okhttp/interceptor/DokitLargePicInterceptor.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.okhttp.interceptor;\n\n\nimport android.text.TextUtils;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig;\nimport com.didichuxing.doraemonkit.config.PerformanceSpInfoConfig;\nimport com.didichuxing.doraemonkit.kit.largepicture.LargePictureManager;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.InterceptorUtil;\n\n\nimport java.io.IOException;\n\nimport okhttp3.Interceptor;\nimport okhttp3.Request;\nimport okhttp3.Response;\n\n/**\n * 大图拦截器\n */\npublic class DokitLargePicInterceptor extends AbsDoKitInterceptor {\n\n    @NonNull\n    @Override\n    public Response intercept(Chain chain) throws IOException {\n        Request request = chain.request();\n        Response response = chain.proceed(request);\n        if (!DokitPluginConfig.SWITCH_BIG_IMG) {\n            return response;\n        }\n        String contentType = response.header(\"Content-Type\");\n\n        if (InterceptorUtil.isImg(contentType)) {\n            if (PerformanceSpInfoConfig.isLargeImgOpen()) {\n                processResponse(response);\n            }\n        }\n        return response;\n    }\n\n\n    private void processResponse(Response response) {\n        String field = response.header(\"Content-Length\");\n        if (!TextUtils.isEmpty(field)) {\n            LargePictureManager.getInstance().process(response.request().url().toString(), Integer.parseInt(field));\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/okhttp/interceptor/DokitMockInterceptor.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.okhttp.interceptor;\n\n\nimport android.text.TextUtils;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.util.EncodeUtils;\nimport com.didichuxing.doraemonkit.util.TimeUtils;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil;\nimport com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo;\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.kit.network.okhttp.InterceptorUtil;\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager;\nimport com.didichuxing.doraemonkit.kit.network.room_db.MockInterceptApiBean;\nimport com.didichuxing.doraemonkit.kit.network.room_db.MockTemplateApiBean;\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport org.json.JSONObject;\n\nimport java.io.IOException;\nimport java.net.URLDecoder;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport okhttp3.HttpUrl;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport okhttp3.Response;\nimport okhttp3.ResponseBody;\n\n/**\n * @author jintai\n * @desc: 接口mock拦截器\n */\npublic class DokitMockInterceptor extends AbsDoKitInterceptor {\n\n\n    @NonNull\n    @Override\n    public Response intercept(Chain chain) throws IOException {\n        Request oldRequest = chain.request();\n        Response oldResponse = chain.proceed(oldRequest);\n        String contentType = oldResponse.header(\"Content-Type\");\n        //如果是图片则不进行拦截\n        if (InterceptorUtil.isImg(contentType)) {\n            return oldResponse;\n        }\n        HttpUrl url = oldRequest.url();\n        String host = url.host();\n        //如果是mock平台的接口则不进行拦截\n        if (host.equalsIgnoreCase(NetworkManager.MOCK_HOST)) {\n            return oldResponse;\n        }\n\n        //path  /test/upload/img\n        String path = URLDecoder.decode(url.encodedPath(), \"utf-8\");\n        String queries = url.query();\n        String jsonQuery = transformQuery(queries);\n        String jsonRequestBody = transformRequestBody(oldRequest.body());\n        //LogHelper.i(TAG, \"realJsonQuery===>\" + jsonQuery);\n        //LogHelper.i(TAG, \"realJsonRequestBody===>\" + jsonRequestBody);\n        String interceptMatchedId = DokitDbManager.getInstance().isMockMatched(path, jsonQuery, jsonRequestBody, DokitDbManager.MOCK_API_INTERCEPT, DokitDbManager.FROM_SDK_OTHER);\n        String templateMatchedId = DokitDbManager.getInstance().isMockMatched(path, jsonQuery, jsonRequestBody, DokitDbManager.MOCK_API_TEMPLATE, DokitDbManager.FROM_SDK_OTHER);\n        try {\n            //网络的健康体检功能 统计流量大小\n            if (DoKitManager.APP_HEALTH_RUNNING) {\n                addNetWokInfoInAppHealth(oldRequest, oldResponse);\n            }\n\n            //是否命中拦截规则\n            if (!TextUtils.isEmpty(interceptMatchedId)) {\n                return matchedInterceptRule(url, path, interceptMatchedId, templateMatchedId, oldRequest, oldResponse, chain);\n            }\n\n            //是否命中模板规则\n            matchedTemplateRule(oldResponse, path, templateMatchedId);\n\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return oldResponse;\n    }\n\n\n    /**\n     * 将request query 转化成json字符串\n     *\n     * @return\n     */\n    private String transformQuery(String query) {\n        String json = \"\";\n        if (TextUtils.isEmpty(query)) {\n            return json;\n        }\n\n        try {\n            //query 类似 ccc=ccc&ddd=ddd\n            json = DoKitCommUtil.param2Json(EncodeUtils.urlDecode(query));\n            //测试是否是json字符串\n            new JSONObject(json);\n        } catch (Exception e) {\n            //e.printStackTrace();\n            json = DokitDbManager.IS_NOT_NORMAL_QUERY_PARAMS;\n            //LogHelper.e(TAG, \"===query json====>\" + json);\n        }\n\n        return json;\n    }\n\n\n    /**\n     * 将request body 转化成json字符串\n     *\n     * @return\n     */\n    private String transformRequestBody(RequestBody requestBody) {\n        //form :\"application/x-www-form-urlencoded\"\n        //json :\"application/json;\"\n        String json = \"\";\n        if (requestBody == null || requestBody.contentType() == null) {\n            return json;\n        }\n\n        try {\n            String strBody = EncodeUtils.urlDecode(DoKitCommUtil.requestBodyToString(requestBody));\n            if (TextUtils.isEmpty(strBody)) {\n                return \"\";\n            }\n\n            if (requestBody.contentType().toString().toLowerCase().contains(DokitDbManager.MEDIA_TYPE_FORM)) {\n                String form = strBody;\n                //类似 ccc=ccc&ddd=ddd\n                json = DoKitCommUtil.param2Json(form);\n                //测试是否是json字符串\n                new JSONObject(json);\n            } else if (requestBody.contentType().toString().toLowerCase().contains(DokitDbManager.MEDIA_TYPE_JSON)) {\n                json = strBody;\n                //测试是否是json字符串\n                new JSONObject(json);\n            } else if (requestBody.contentType().toString().toLowerCase().contains(DokitDbManager.MEDIA_TYPE_PLAIN)) {\n                json = strBody;\n                //测试是否是json字符串\n                try {\n                    new JSONObject(json);\n                } catch (Exception e) {\n                    //类似 ccc=ccc&ddd=ddd\n                    json = DoKitCommUtil.param2Json(json);\n                    if (json.equals(\"{}\")) {\n                        json = DokitDbManager.IS_NOT_NORMAL_BODY_PARAMS;\n                    }\n                }\n            } else {\n                json = DokitDbManager.IS_NOT_NORMAL_BODY_PARAMS;\n            }\n        } catch (Exception e) {\n            //e.printStackTrace();\n            json = \"\";\n            LogHelper.e(getTAG(), \"===body json====>\" + json);\n        }\n\n        return json;\n    }\n\n\n    /**\n     * 动态添加网络拦截\n     *\n     * @param request\n     * @param response\n     */\n    private void addNetWokInfoInAppHealth(@NonNull Request request, @NonNull Response response) {\n        try {\n            if (ActivityUtils.getTopActivity() == null) {\n                return;\n            }\n\n            long upSize = -1;\n            long downSize = -1;\n            if (request.body() != null) {\n                upSize = request.body().contentLength();\n            }\n            if (response.body() != null) {\n                ResponseBody peekBody = response.peekBody(Long.MAX_VALUE);\n                downSize = peekBody.bytes().length;\n            }\n\n\n            if (upSize < 0 && downSize < 0) {\n                return;\n            }\n\n            upSize = upSize > 0 ? upSize : 0;\n            downSize = downSize > 0 ? downSize : 0;\n\n            String activityName = ActivityUtils.getTopActivity().getClass().getCanonicalName();\n            AppHealthInfo.DataBean.NetworkBean networkBean = AppHealthInfoUtil.getInstance().getNetWorkInfo(activityName);\n            AppHealthInfo.DataBean.NetworkBean.NetworkValuesBean networkValuesBean = new AppHealthInfo.DataBean.NetworkBean.NetworkValuesBean();\n            networkValuesBean.setCode(\"\" + response.code());\n\n            networkValuesBean.setUp(\"\" + upSize);\n            networkValuesBean.setDown(\"\" + downSize);\n            networkValuesBean.setMethod(request.method());\n            networkValuesBean.setTime(\"\" + TimeUtils.getNowMills());\n            networkValuesBean.setUrl(request.url().toString());\n            if (networkBean == null) {\n                networkBean = new AppHealthInfo.DataBean.NetworkBean();\n                networkBean.setPage(activityName);\n                List<AppHealthInfo.DataBean.NetworkBean.NetworkValuesBean> networkValuesBeans = new ArrayList<>();\n                networkValuesBeans.add(networkValuesBean);\n                networkBean.setValues(networkValuesBeans);\n                AppHealthInfoUtil.getInstance().addNetWorkInfo(networkBean);\n            } else {\n                List<AppHealthInfo.DataBean.NetworkBean.NetworkValuesBean> networkValuesBeans = networkBean.getValues();\n                if (networkValuesBeans == null) {\n                    networkValuesBeans = new ArrayList<>();\n                    networkValuesBeans.add(networkValuesBean);\n                    networkBean.setValues(networkValuesBeans);\n                } else {\n                    networkValuesBeans.add(networkValuesBean);\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n\n    /**\n     * 命中拦截规则\n     * 返回新的response\n     *\n     * @param interceptMatchedId\n     * @return\n     */\n    private Response matchedInterceptRule(HttpUrl url, String path, String interceptMatchedId, String templateMatchedId, Request oldRequest, Response oldResponse, Chain chain) throws Exception {\n        //判断是否需要重定向数据接口\n        //http https\n        String scheme = url.scheme();\n        MockInterceptApiBean interceptApiBean = (MockInterceptApiBean) DokitDbManager.getInstance().getInterceptApiByIdInMap(path, interceptMatchedId, DokitDbManager.FROM_SDK_OTHER);\n        if (interceptApiBean == null) {\n            matchedTemplateRule(oldResponse, path, templateMatchedId);\n            return oldResponse;\n        }\n        String selectedSceneId = interceptApiBean.getSelectedSceneId();\n        //开关是否被打开\n        if (!interceptApiBean.isOpen()) {\n            matchedTemplateRule(oldResponse, path, templateMatchedId);\n            return oldResponse;\n        }\n\n        //判断是否有选中的场景\n        if (TextUtils.isEmpty(selectedSceneId)) {\n            matchedTemplateRule(oldResponse, path, templateMatchedId);\n            return oldResponse;\n        }\n        StringBuilder sb = new StringBuilder();\n        String newUrl;\n        if (NetworkManager.MOCK_SCHEME_HTTP.contains(scheme.toLowerCase())) {\n            newUrl = sb.append(NetworkManager.MOCK_SCHEME_HTTP).append(NetworkManager.MOCK_HOST).append(\"/api/app/scene/\").append(selectedSceneId).toString();\n        } else {\n            newUrl = sb.append(NetworkManager.MOCK_SCHEME_HTTPS).append(NetworkManager.MOCK_HOST).append(\"/api/app/scene/\").append(selectedSceneId).toString();\n        }\n\n        //LogHelper.i(\"MOCK_INTERCEPT\", \"path===>\" + path + \"  newUrl=====>\" + newUrl);\n\n        Request newRequest = new Request.Builder()\n                .method(\"GET\", null)\n                .url(newUrl).build();\n        //需要提前关闭数据流 不然在某些场景下会报错\n        oldResponse.close();\n        Response newResponse = chain.proceed(newRequest);\n        if (newResponse.code() == 200) {\n            //拦截命中提示\n            ToastUtils.showShort(\"接口别名:==\" + interceptApiBean.getMockApiName() + \"==已被拦截\");\n            //判断新的response是否有数据\n            if (newResponseHasData(newResponse)) {\n                matchedTemplateRule(newResponse, path, templateMatchedId);\n                return newResponse;\n            } else {\n                matchedTemplateRule(oldResponse, path, templateMatchedId);\n                return oldResponse;\n            }\n\n        }\n        matchedTemplateRule(oldResponse, path, templateMatchedId);\n        return oldResponse;\n    }\n\n    /**\n     * 命中模板规则\n     * 保存正常接口的response到数据库\n     *\n     * @return\n     */\n    private void matchedTemplateRule(Response oldResponse, String path, String templateMatchedId) throws Exception {\n        //命中模板规则\n        if (TextUtils.isEmpty(templateMatchedId)) {\n            return;\n        }\n        MockTemplateApiBean templateApiBean = (MockTemplateApiBean) DokitDbManager.getInstance().getTemplateApiByIdInMap(path, templateMatchedId, DokitDbManager.FROM_SDK_OTHER);\n        if (templateApiBean == null) {\n            return;\n        }\n        //LogHelper.i(\"MOCK_TEMPLATE\", \"path=====>\" + path + \"isOpen===>\" + templateApiBean.isOpen());\n        if (templateApiBean.isOpen()) {\n            //保存老的response 数据到数据库\n            saveResponse2DB(oldResponse, templateApiBean);\n        }\n    }\n\n\n    /**\n     * 保存匹配中的数据到本地数据库\n     *\n     * @param response\n     * @param mockApi\n     * @throws Exception\n     */\n    private void saveResponse2DB(Response response, MockTemplateApiBean mockApi) throws Exception {\n        if (response.code() != 200) {\n            return;\n        }\n\n        if (response.body() == null) {\n            return;\n        }\n\n\n        String host = response.request().url().host();\n        //LogHelper.i(TAG, \"host====>\" + host);\n        //这里不能直接使用response.body().string()的方式输出日志\n        //因为response.body().string()之后，response中的流会被关闭，程序会报错，我们需要创建出一\n        //个新的response给应用层处理\n        ResponseBody responseBody = response.peekBody(Long.MAX_VALUE);\n\n        String strResponseBody = responseBody.string();\n        if (TextUtils.isEmpty(strResponseBody)) {\n            return;\n        }\n\n        if (host.equals(NetworkManager.MOCK_HOST)) {\n            mockApi.setResponseFrom(MockTemplateApiBean.RESPONSE_FROM_MOCK);\n        } else {\n            mockApi.setResponseFrom(MockTemplateApiBean.RESPONSE_FROM_REAL);\n        }\n\n        mockApi.setStrResponse(strResponseBody);\n        //更新本地数据库\n        DokitDbManager.getInstance().updateTemplateApi(mockApi);\n        //拦截命中提示\n        ToastUtils.showShort(\"模板别名:==\" + mockApi.getMockApiName() + \"==已被保存\");\n    }\n\n    /**\n     * 新的mock 接口是否有数据\n     *\n     * @return\n     */\n    private boolean newResponseHasData(Response response) throws Exception {\n        //这里不能直接使用response.body().string()的方式输出日志\n        //因为response.body().string()之后，response中的流会被关闭，程序会报错，我们需要创建出一\n        //个新的response给应用层处理\n        if (response.body() == null) {\n            return false;\n        }\n        return true;\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/okhttp/interceptor/DokitWeakNetworkInterceptor.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.okhttp.interceptor;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.kit.weaknetwork.WeakNetworkManager;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\n\nimport java.io.IOException;\n\nimport okhttp3.HttpUrl;\nimport okhttp3.Interceptor;\nimport okhttp3.Request;\nimport okhttp3.Response;\n\n/**\n * 用于模拟弱网的拦截器\n * <p>\n * Created by jint on 2019-05-09 16:29\n *\n * @author didi\n */\npublic class DokitWeakNetworkInterceptor extends AbsDoKitInterceptor {\n\n    @NonNull\n    @Override\n    public Response intercept(Chain chain) throws IOException {\n        if (!WeakNetworkManager.get().isActive()) {\n            Request request = chain.request();\n            return chain.proceed(request);\n        }\n        final int type = WeakNetworkManager.get().getType();\n        switch (type) {\n            case WeakNetworkManager.TYPE_TIMEOUT:\n                //超时\n                return WeakNetworkManager.get().simulateTimeOut(chain);\n            case WeakNetworkManager.TYPE_SPEED_LIMIT:\n                //限速\n                return WeakNetworkManager.get().simulateSpeedLimit(chain);\n            default:\n                //断网\n                return WeakNetworkManager.get().simulateOffNetwork(chain);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/room_db/AbsMockApiBean.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.room_db;\n\n\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.BaseNode;\n\nimport java.util.List;\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-15-15:44\n * 描    述：mock 上传数据\n * 修订历史：\n * ================================================\n */\npublic abstract class AbsMockApiBean extends BaseNode {\n\n    boolean isOpen() {\n        return false;\n    }\n\n    void setOpen(boolean open) {\n\n    }\n\n    String getId() {\n        return \"\";\n    }\n\n    String getSelectedSceneId() {\n        return \"\";\n    }\n\n    String getQuery() {\n        return \"\";\n    }\n\n    String getBody() {\n        return \"\";\n    }\n\n\n    String getPath() {\n        return \"\";\n    }\n\n    @Override\n    public List<BaseNode> getChildNode() {\n        return null;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/room_db/DokitDatabase.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.room_db;\n\nimport androidx.room.Database;\nimport androidx.room.RoomDatabase;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-15-16:06\n * 描    述：注解指定了database的表映射实体数据以及版本等信息\n * 修订历史：\n * ================================================\n */\n@Database(entities = {MockInterceptApiBean.class, MockTemplateApiBean.class}, version = 2, exportSchema = false)\npublic abstract class DokitDatabase extends RoomDatabase {\n    abstract MockApiDao mockApiDao();\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/room_db/DokitDbManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.room_db;\n\nimport android.text.TextUtils;\n\nimport com.didichuxing.doraemonkit.util.ThreadUtils;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewManager;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-15-16:12\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class DokitDbManager<T extends AbsMockApiBean> {\n    public final static String IS_NOT_NORMAL_QUERY_PARAMS = \"is not normal query parmas\";\n    public final static String IS_NOT_NORMAL_BODY_PARAMS = \"is not normal body parmas\";\n\n    public final static String MEDIA_TYPE_FORM = \"application/x-www-form-urlencoded\";\n    public final static String MEDIA_TYPE_JSON = \"application/json\";\n    public final static String MEDIA_TYPE_PLAIN = \"text/plain\";\n\n    private static final String TAG = \"DokitDbManager\";\n\n    /**\n     * key 为path 可能存在path是一样的 所以value为List\n     */\n    private Map<String, List<T>> mGlobalInterceptApiMaps = new HashMap<>();\n    /**\n     * key 为path 可能存在path是一样的 所以value为List\n     */\n    private Map<String, List<T>> mGlobalTemplateApiMaps = new HashMap<>();\n\n\n    private MockTemplateApiBean mGlobalTemplateApiBean;\n\n    public Map<String, List<T>> getGlobalInterceptApiMaps() {\n        return mGlobalInterceptApiMaps;\n    }\n\n\n    public Map<String, List<T>> getGlobalTemplateApiMaps() {\n        return mGlobalTemplateApiMaps;\n    }\n\n\n    public MockTemplateApiBean getGlobalTemplateApiBean() {\n        return mGlobalTemplateApiBean;\n    }\n\n    public void setGlobalTemplateApiBean(MockTemplateApiBean mGlobalTemplateApiBean) {\n        this.mGlobalTemplateApiBean = mGlobalTemplateApiBean;\n    }\n\n    /**\n     * 静态内部类单例\n     */\n    private static class Holder {\n        private static DokitDbManager INSTANCE = new DokitDbManager();\n    }\n\n    public static DokitDbManager getInstance() {\n        return DokitDbManager.Holder.INSTANCE;\n    }\n\n    /**\n     * 获取所有的mock intercept apis\n     */\n    public void getAllInterceptApis() {\n        ThreadUtils.executeByIo(new ThreadUtils.SimpleTask<List<T>>() {\n            @Override\n            public List<T> doInBackground() throws Throwable {\n                DokitDatabase db = DoKitViewManager.getINSTANCE().getDb();\n                if (db.mockApiDao() != null) {\n                    return (List<T>) db.mockApiDao().getAllInterceptApi();\n                } else {\n                    throw new NullPointerException(\"mDb == null || mDb.mockApiDao()\");\n                }\n            }\n\n            @Override\n            public void onSuccess(List<T> result) {\n                list2mapByIntercept(result);\n            }\n\n            @Override\n            public void onFail(Throwable t) {\n                super.onFail(t);\n                LogHelper.e(TAG, \"error====>\" + t.getMessage());\n            }\n        });\n    }\n\n\n    /**\n     * 获取所有的mock template apis\n     */\n    public void getAllTemplateApis() {\n        ThreadUtils.executeByIo(new ThreadUtils.SimpleTask<List<T>>() {\n            @Override\n            public List<T> doInBackground() throws Throwable {\n                DokitDatabase db = DoKitViewManager.getINSTANCE().getDb();\n                if (db.mockApiDao() != null) {\n                    return (List<T>) db.mockApiDao().getAllTemplateApi();\n                } else {\n                    throw new NullPointerException(\"mDb == null || mDb.mockApiDao()\");\n                }\n\n            }\n\n            @Override\n            public void onSuccess(List<T> result) {\n                list2mapByTemplate(result);\n            }\n\n            @Override\n            public void onFail(Throwable t) {\n                super.onFail(t);\n                LogHelper.e(TAG, \"error====>\" + t.getMessage());\n            }\n        });\n    }\n\n\n    /**\n     * 数据库中获取指定的 template api\n     */\n    public T getTemplateApiByIdInDb(String id) {\n        return (T) DoKitViewManager.getINSTANCE().getDb().mockApiDao().findTemplateApiById(id);\n    }\n\n\n    /**\n     * 数据库中获取指定的mock intercept api\n     */\n    public T getInterceptApiByIdInDb(String id) {\n        return (T) DoKitViewManager.getINSTANCE().getDb().mockApiDao().findInterceptApiById(id);\n    }\n\n    /**\n     * 内存中中获取指定的mock intercept api\n     */\n    public T getInterceptApiByIdInMap(String path, String id, int fromSDK) {\n\n        if (mGlobalInterceptApiMaps == null) {\n            return null;\n        }\n        //先进行全匹配\n        List<T> mGlobalInterceptApis = mGlobalInterceptApiMaps.get(path);\n        if (mGlobalInterceptApis == null) {\n            path = DoKitManager.dealDidiPlatformPath(path, fromSDK);\n            mGlobalInterceptApis = mGlobalInterceptApiMaps.get(path);\n        }\n\n        //再进行滴滴内部匹配\n        if (mGlobalInterceptApis == null) {\n            return null;\n        }\n\n        T selectedMockApi = null;\n        for (T mockApi : mGlobalInterceptApis) {\n            if (mockApi.getId().equals(id)) {\n                selectedMockApi = mockApi;\n                break;\n            }\n        }\n\n        return selectedMockApi;\n    }\n\n    /**\n     * 内存中获取指定的 template api\n     */\n    public T getTemplateApiByIdInMap(String path, String id, int fromSDK) {\n        if (mGlobalTemplateApiMaps == null) {\n            return null;\n        }\n        List<T> mGlobalTemplateApis = mGlobalTemplateApiMaps.get(path);\n        //先进行全匹配\n        if (mGlobalTemplateApis == null) {\n            path = DoKitManager.dealDidiPlatformPath(path, fromSDK);\n            mGlobalTemplateApis = mGlobalTemplateApiMaps.get(path);\n        }\n        //再进行滴滴内部匹配\n        if (mGlobalTemplateApis == null) {\n            return null;\n        }\n\n        T selectedMockApi = null;\n        for (T mockApi : mGlobalTemplateApis) {\n            if (mockApi.getId().equals(id)) {\n                selectedMockApi = mockApi;\n                break;\n            }\n        }\n\n        return selectedMockApi;\n    }\n\n    /**\n     * 插入所有的mock intercept  Api 数据\n     *\n     * @param mockApis\n     */\n    public void insertAllInterceptApi(final List<MockInterceptApiBean> mockApis) {\n        ThreadUtils.executeByIo(new ThreadUtils.SimpleTask<Object>() {\n            @Override\n            public Object doInBackground() throws Throwable {\n                DoKitViewManager.getINSTANCE().getDb().mockApiDao().insertAllInterceptApi(mockApis);\n                //更新本地数据\n                getAllInterceptApis();\n                return null;\n            }\n\n            @Override\n            public void onSuccess(Object result) {\n\n            }\n        });\n    }\n\n\n    /**\n     * 插入所有的mock template  Api 数据\n     *\n     * @param mockApis\n     */\n    public void insertAllTemplateApi(final List<MockTemplateApiBean> mockApis) {\n        ThreadUtils.executeByIo(new ThreadUtils.SimpleTask<Void>() {\n            @Override\n            public Void doInBackground() throws Throwable {\n                DoKitViewManager.getINSTANCE().getDb().mockApiDao().insertAllTemplateApi(mockApis);\n                return null;\n            }\n\n            @Override\n            public void onSuccess(Void result) {\n                //更新本地数据\n                getAllTemplateApis();\n            }\n        });\n    }\n\n\n    /**\n     * 更新某个intercept Api\n     *\n     * @param mockApi\n     */\n    public void updateInterceptApi(final MockInterceptApiBean mockApi) {\n\n        ThreadUtils.executeByIo(new ThreadUtils.SimpleTask<Void>() {\n            @Override\n            public Void doInBackground() throws Throwable {\n                DoKitViewManager.getINSTANCE().getDb().mockApiDao().updateInterceptApi(mockApi);\n\n                return null;\n            }\n\n            @Override\n            public void onSuccess(Void result) {\n                //更新本地数据\n                getAllInterceptApis();\n            }\n        });\n\n    }\n\n\n    /**\n     * 更新某个template Api\n     *\n     * @param mockApi\n     */\n    public void updateTemplateApi(final MockTemplateApiBean mockApi) {\n\n        ThreadUtils.executeByIo(new ThreadUtils.SimpleTask<Object>() {\n            @Override\n            public Object doInBackground() throws Throwable {\n                DoKitViewManager.getINSTANCE().getDb().mockApiDao().updateTemplateApi(mockApi);\n                //更新本地数据\n                getAllTemplateApis();\n                return null;\n            }\n\n            @Override\n            public void onSuccess(Object result) {\n\n            }\n        });\n\n    }\n\n    /**\n     * 返回选中的场景id\n     *\n     * @param path\n     * @param id\n     * @return\n     */\n    public String getMockInterceptSelectedSceneIdByPathAndId(String path, String id) {\n        if (mGlobalInterceptApiMaps.get(path) == null) {\n            return \"\";\n        }\n\n        String selectedSceneId = \"\";\n        for (T mockApi : mGlobalInterceptApiMaps.get(path)) {\n            if (mockApi.getId().equals(id)) {\n                selectedSceneId = mockApi.getSelectedSceneId();\n                break;\n            }\n        }\n\n        return selectedSceneId;\n    }\n\n\n    public static final String CONTENT_TYPE = \"application/json\";\n\n\n    /**\n     * 拦截\n     */\n    public static final int MOCK_API_INTERCEPT = 1;\n    /**\n     * 模板\n     */\n    public static final int MOCK_API_TEMPLATE = 2;\n\n    /**\n     * 来自滴滴内部SDK\n     */\n    public static int FROM_SDK_DIDI = 100;\n    /**\n     * 来自外部SDK\n     */\n    public static int FROM_SDK_OTHER = 101;\n\n    /**\n     * 返回命中的id\n     *\n     * @param path\n     * @param jsonQuery\n     * @param operateType\n     * @return\n     */\n    public String isMockMatched(String path, String jsonQuery, String jsonRequestBody, int operateType, int fromSDK) {\n        //如果是非字符串类型的请求体 直接不匹配\n        if (!TextUtils.isEmpty(jsonQuery) && jsonQuery.equals(IS_NOT_NORMAL_QUERY_PARAMS)) {\n            return \"\";\n        }\n\n        if (!TextUtils.isEmpty(jsonRequestBody) && jsonRequestBody.equals(IS_NOT_NORMAL_BODY_PARAMS)) {\n            return \"\";\n        }\n\n        T mockApi = mockMatched(path, jsonQuery, jsonRequestBody, operateType, fromSDK);\n        if (mockApi == null) {\n            return \"\";\n        }\n        return mockApi.getId();\n    }\n\n\n    /**\n     * 通过path和query查询指定的对象\n     *\n     * @param path\n     * @param jsonQuery\n     * @param operateType 1:代表拦截 2：代表模板\n     * @return\n     */\n    private T mockMatched(String path, String jsonQuery, String jsonRequestBody, int operateType, int fromSDK) {\n        List<T> mockApis = null;\n        if (operateType == DokitDbManager.MOCK_API_INTERCEPT) {\n            //先进行一次全匹配\n            mockApis = mGlobalInterceptApiMaps.get(path);\n            //滴滴内部sdk匹配\n            if (mockApis == null) {\n                path = DoKitManager.dealDidiPlatformPath(path, fromSDK);\n                mockApis = mGlobalInterceptApiMaps.get(path);\n            }\n        } else if (operateType == DokitDbManager.MOCK_API_TEMPLATE) {\n            //先进行一次全匹配\n            mockApis = mGlobalTemplateApiMaps.get(path);\n            //滴滴内部sdk匹配\n            if (mockApis == null) {\n                path = DoKitManager.dealDidiPlatformPath(path, fromSDK);\n                mockApis = mGlobalTemplateApiMaps.get(path);\n            }\n        }\n        if (mockApis == null) {\n            return null;\n        }\n        T matchedMockApi = null;\n        for (T mockApi : mockApis) {\n            if (mockApi.isOpen() && queriesMatched(jsonQuery, mockApi) && bodyMatched(jsonRequestBody, mockApi)) {\n                matchedMockApi = mockApi;\n                break;\n            }\n\n        }\n\n        return matchedMockApi;\n    }\n\n    /**\n     * queries 是否命中\n     *\n     * @param jsonQuery\n     * @param mockApi\n     */\n    private boolean queriesMatched(String jsonQuery, T mockApi) {\n        //{}代表没有配置query\n        String mockQuery = mockApi.getQuery();\n        boolean mockQueryIsEmpty = TextUtils.isEmpty(mockQuery) || \"{}\".equals(mockQuery);\n        //平台没有配置query参数且本地也没有query参数\n        if (mockQueryIsEmpty && TextUtils.isEmpty(jsonQuery)) {\n            return true;\n        }\n\n        //本地有参数 平台没有配置参数\n        if (!TextUtils.isEmpty(jsonQuery) && mockQueryIsEmpty) {\n            return true;\n        }\n\n        //本地没有参数 但是平台有参数\n        if (TextUtils.isEmpty(jsonQuery) && !mockQueryIsEmpty) {\n            return false;\n        }\n\n        //匹配query\n        if (!TextUtils.isEmpty(jsonQuery) && !mockQueryIsEmpty) {\n            try {\n                JSONObject jsonQueryLocal = new JSONObject(jsonQuery);\n                JSONObject jsonQueryMock = new JSONObject(mockQuery);\n                List<String> keys = new ArrayList<>();\n                //通过平台端的来主动匹配\n                Iterator<String> iterator = jsonQueryMock.keys();\n                while (iterator.hasNext()) {\n                    keys.add(iterator.next());\n                }\n                int count = 0;\n\n                for (int index = 0; index < keys.size(); index++) {\n                    String key = keys.get(index);\n                    if (jsonQueryLocal.has(key) && jsonQueryMock.getString(key).equals(jsonQueryLocal.get(key))) {\n                        count++;\n                    }\n                }\n\n                if (count == keys.size()) {\n                    return true;\n                }\n            } catch (JSONException e) {\n                e.printStackTrace();\n                return false;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * body 是否命中\n     *\n     * @param jsonRequestBody\n     * @param mockApi\n     */\n    private boolean bodyMatched(String jsonRequestBody, T mockApi) {\n        //{}代表没有配置query\n        String mockBody = mockApi.getBody();\n        boolean mockQueryIsEmpty = TextUtils.isEmpty(mockBody) || \"{}\".equals(mockBody);\n        //平台没有配置query参数且本地也没有query参数\n        if (mockQueryIsEmpty && TextUtils.isEmpty(jsonRequestBody)) {\n            return true;\n        }\n\n        //本地有参数 平台没有配置参数\n        if (!TextUtils.isEmpty(jsonRequestBody) && mockQueryIsEmpty) {\n            return true;\n        }\n\n        //本地没有参数 但是平台有参数\n        if (TextUtils.isEmpty(jsonRequestBody) && !mockQueryIsEmpty) {\n            return false;\n        }\n\n        //匹配body\n        if (!TextUtils.isEmpty(jsonRequestBody) && !mockQueryIsEmpty) {\n            try {\n                JSONObject jsonBodyLocal = new JSONObject(jsonRequestBody);\n                JSONObject jsonBodyMock = new JSONObject(mockBody);\n\n                List<String> keys = new ArrayList<>();\n                Iterator<String> iterator = jsonBodyMock.keys();\n                while (iterator.hasNext()) {\n                    keys.add(iterator.next());\n                }\n                int count = 0;\n                for (int index = 0; index < keys.size(); index++) {\n                    String key = keys.get(index);\n                    if (jsonBodyLocal.has(key) && jsonBodyMock.getString(key).equals(jsonBodyLocal.get(key))) {\n                        count++;\n                    }\n                }\n                if (count == keys.size()) {\n                    return true;\n                }\n            } catch (JSONException e) {\n                e.printStackTrace();\n                return false;\n            }\n        }\n\n        return false;\n    }\n\n\n    private void list2mapByIntercept(List<T> interceptApiBeans) {\n        mGlobalInterceptApiMaps.clear();\n        for (T mockApi : interceptApiBeans) {\n            if (mGlobalInterceptApiMaps.get(mockApi.getPath()) == null) {\n                List<T> mockInterceptApiBeans = new ArrayList<>();\n                mockInterceptApiBeans.add(mockApi);\n                mGlobalInterceptApiMaps.put(mockApi.getPath(), mockInterceptApiBeans);\n            } else {\n                mGlobalInterceptApiMaps.get(mockApi.getPath()).add(mockApi);\n            }\n\n        }\n    }\n\n\n    private void list2mapByTemplate(List<T> templateApiBeans) {\n        mGlobalTemplateApiMaps.clear();\n        for (T mockApi : templateApiBeans) {\n            if (mGlobalTemplateApiMaps.get(mockApi.getPath()) == null) {\n                List<T> mockTemplateApiBeans = new ArrayList<>();\n                mockTemplateApiBeans.add(mockApi);\n                mGlobalTemplateApiMaps.put(mockApi.getPath(), mockTemplateApiBeans);\n            } else {\n                mGlobalTemplateApiMaps.get(mockApi.getPath()).add(mockApi);\n            }\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/room_db/MockApiDao.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.room_db;\n\n\nimport androidx.room.Dao;\nimport androidx.room.Insert;\nimport androidx.room.OnConflictStrategy;\nimport androidx.room.Query;\nimport androidx.room.Update;\n\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-15-15:44\n * 描    述：mock 数据查询\n * 修订历史：\n * ================================================\n */\n@Dao\npublic interface MockApiDao {\n    /**\n     * 简单sql语句，查询mock_intercept_api表所有的column\n     */\n    @Query(\"SELECT * FROM mock_intercept_api\")\n    List<MockInterceptApiBean> getAllInterceptApi();\n\n    /**\n     * 简单sql语句，查询mock_template_api表所有的column\n     */\n    @Query(\"SELECT * FROM mock_template_api\")\n    List<MockTemplateApiBean> getAllTemplateApi();\n\n    /**\n     * 根据指定字段获取对象\n     */\n    @Query(\"SELECT * FROM mock_intercept_api WHERE path LIKE :path\")\n    List<MockInterceptApiBean> findInterceptApiByPath(String path);\n\n    /**\n     * 根据指定字段获取对象\n     */\n    @Query(\"SELECT * FROM mock_intercept_api WHERE id = :id LIMIT 1\")\n    MockInterceptApiBean findInterceptApiById(String id);\n\n    /**\n     * 根据指定字段获取对象\n     */\n    @Query(\"SELECT * FROM mock_template_api WHERE path LIKE :path\")\n    List<MockTemplateApiBean> findTemplateApiByPath(String path);\n\n\n    /**\n     * 根据指定字段获取对象\n     */\n    @Query(\"SELECT * FROM mock_template_api WHERE id = :id LIMIT 1\")\n    MockTemplateApiBean findTemplateApiById(String id);\n\n    /**\n     * 插入mockApi数据列表\n     */\n    @Insert(onConflict = OnConflictStrategy.ROLLBACK)\n    void insertAllInterceptApi(List<MockInterceptApiBean> mockApis);\n\n    /**\n     * 插入mockApi数据列表\n     */\n    @Insert(onConflict = OnConflictStrategy.ROLLBACK)\n    void insertAllTemplateApi(List<MockTemplateApiBean> mockApis);\n\n    @Update(onConflict = OnConflictStrategy.ROLLBACK)\n    int updateInterceptApi(MockInterceptApiBean mockApi);\n\n\n    @Update(onConflict = OnConflictStrategy.ROLLBACK)\n    int updateTemplateApi(MockTemplateApiBean mockApi);\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/room_db/MockInterceptApiBean.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.room_db;\n\nimport androidx.annotation.NonNull;\nimport androidx.room.ColumnInfo;\nimport androidx.room.Entity;\nimport androidx.room.Ignore;\nimport androidx.room.PrimaryKey;\n\nimport com.didichuxing.doraemonkit.kit.network.bean.MockApiResponseBean;\nimport com.didichuxing.doraemonkit.kit.network.ui.InterceptMockAdapter;\n\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-15-15:44\n * 描    述：mock 拦截数据\n * 修订历史：\n * ================================================\n */\n@Entity(tableName = \"mock_intercept_api\")\npublic class MockInterceptApiBean extends AbsMockApiBean {\n    /**\n     * /设置主键 并且组建不为null\n     */\n    @NonNull\n    @PrimaryKey\n    @ColumnInfo(name = \"id\")\n    private String id = \"\";\n    @ColumnInfo(name = \"mock_api_name\")\n    private String mockApiName;\n    @ColumnInfo(name = \"path\")\n    private String path;\n    @ColumnInfo(name = \"method\")\n    private String method;\n    @ColumnInfo(name = \"query\")\n    private String query;\n    @ColumnInfo(name = \"body\")\n    private String body;\n    @ColumnInfo(name = \"fromType\")\n    private String fromType;\n    @ColumnInfo(name = \"selected_scene_name\")\n    private String selectedSceneName;\n    @ColumnInfo(name = \"selected_scene_id\")\n    private String selectedSceneId;\n    /**\n     * 接口是否被打开\n     */\n    @ColumnInfo(name = \"is_open\")\n    private boolean isOpen;\n\n    @Ignore\n    private String group;\n    @Ignore\n    private String createPerson;\n    @Ignore\n    private String modifyPerson;\n    @Ignore\n    private List<MockApiResponseBean.DataBean.DatalistBean.SceneListBean> sceneList;\n\n\n    public MockInterceptApiBean() {\n    }\n\n    @Ignore\n    public MockInterceptApiBean(@NonNull String id, String mockApiName, String path, String method, String fromType, String query, String body, String group, String createPerson, String modifyPerson, List<MockApiResponseBean.DataBean.DatalistBean.SceneListBean> sceneList) {\n        this.id = id;\n        this.mockApiName = mockApiName;\n        this.path = path;\n        this.method = method;\n        this.fromType = fromType;\n        this.query = query;\n        this.body = body;\n        this.group = group;\n        this.createPerson = createPerson;\n        this.modifyPerson = modifyPerson;\n        this.sceneList = sceneList;\n    }\n\n    @Override\n    public String getId() {\n        return id;\n    }\n\n\n    public String getMockApiName() {\n        return mockApiName;\n    }\n\n    public void setMockApiName(String mockApiName) {\n        this.mockApiName = mockApiName;\n    }\n\n    @Override\n    public String getPath() {\n        return path;\n    }\n\n    public void setPath(String path) {\n        this.path = path;\n    }\n\n    public String getMethod() {\n        return method;\n    }\n\n    public void setMethod(String method) {\n        this.method = method;\n    }\n\n    @Override\n    public String getQuery() {\n        return query;\n    }\n\n    public void setQuery(String query) {\n        this.query = query;\n    }\n\n    @Override\n    public String getBody() {\n        return body;\n    }\n\n    public void setBody(String body) {\n        this.body = body;\n    }\n\n    public String getFromType() {\n        return fromType;\n    }\n\n    public void setFromType(String fromType) {\n        this.fromType = fromType;\n    }\n\n    public String getSelectedSceneName() {\n        return selectedSceneName;\n    }\n\n    public void setSelectedSceneName(String selectedSceneName) {\n        this.selectedSceneName = selectedSceneName;\n    }\n\n    @Override\n    public String getSelectedSceneId() {\n        return selectedSceneId;\n    }\n\n    public void setSelectedSceneId(String selectedSceneId) {\n        this.selectedSceneId = selectedSceneId;\n    }\n\n    public void setId(@NonNull String id) {\n        this.id = id;\n    }\n\n    public String getGroup() {\n        return group;\n    }\n\n    public void setGroup(String group) {\n        this.group = group;\n    }\n\n    public String getCreatePerson() {\n        return createPerson;\n    }\n\n    public void setCreatePerson(String createPerson) {\n        this.createPerson = createPerson;\n    }\n\n    public String getModifyPerson() {\n        return modifyPerson;\n    }\n\n    public void setModifyPerson(String modifyPerson) {\n        this.modifyPerson = modifyPerson;\n    }\n\n    public List<MockApiResponseBean.DataBean.DatalistBean.SceneListBean> getSceneList() {\n        return sceneList;\n    }\n\n    public void setSceneList(List<MockApiResponseBean.DataBean.DatalistBean.SceneListBean> sceneList) {\n        this.sceneList = sceneList;\n    }\n\n    @Override\n    public boolean isOpen() {\n        return isOpen;\n    }\n\n    @Override\n    public void setOpen(boolean open) {\n        this.isOpen = open;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/room_db/MockTemplateApiBean.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.room_db;\n\nimport androidx.annotation.NonNull;\nimport androidx.room.ColumnInfo;\nimport androidx.room.Entity;\nimport androidx.room.Ignore;\nimport androidx.room.PrimaryKey;\n\nimport com.didichuxing.doraemonkit.kit.network.ui.InterceptMockAdapter;\n\nimport java.io.Serializable;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-15-15:44\n * 描    述：mock 上传数据\n * 修订历史：\n * ================================================\n */\n@Entity(tableName = \"mock_template_api\")\npublic class MockTemplateApiBean extends AbsMockApiBean implements Serializable {\n    /**\n     * 来自真实接口返回数据\n     */\n    public static final int RESPONSE_FROM_REAL = 1;\n    /**\n     * 来自mock接口返回的数据\n     */\n    public static final int RESPONSE_FROM_MOCK = 0;\n    /**\n     * /设置主键 并且组建不为null\n     */\n    @NonNull\n    @PrimaryKey\n    @ColumnInfo(name = \"id\")\n    private String id = \"\";\n    @ColumnInfo(name = \"mock_api_name\")\n    private String mockApiName;\n    @ColumnInfo(name = \"path\")\n    private String path;\n    @ColumnInfo(name = \"method\")\n    private String method;\n    @ColumnInfo(name = \"query\")\n    private String query;\n\n    @ColumnInfo(name = \"body\")\n    private String body;\n\n    @ColumnInfo(name = \"fromType\")\n    private String fromType;\n    /**\n     * 接口是否被打开\n     */\n    @ColumnInfo(name = \"is_open\")\n    private boolean isOpen;\n    /**\n     * path 接口域名mock出来的数据\n     */\n    @ColumnInfo(name = \"str_response\")\n    private String strResponse;\n\n    /**\n     * mockResponse的来源 0:mock 1:真实的网络数据\n     */\n    @ColumnInfo(name = \"response_from\")\n    private int responseFrom;\n\n    @Ignore\n    private String group;\n    @Ignore\n    private String createPerson;\n    @Ignore\n    private String modifyPerson;\n    @Ignore\n    private String projectId;\n\n\n    public MockTemplateApiBean() {\n    }\n\n    @Ignore\n    public MockTemplateApiBean(@NonNull String id, String mockApiName, String path, String method, String fromType, String query, String body, String group, String createPerson, String modifyPerson, String projectId) {\n        this.id = id;\n        this.mockApiName = mockApiName;\n        this.path = path;\n        this.method = method;\n        this.fromType = fromType;\n        this.query = query;\n        this.body = body;\n        this.group = group;\n        this.createPerson = createPerson;\n        this.modifyPerson = modifyPerson;\n        this.projectId = projectId;\n\n    }\n\n    @Override\n    public String getId() {\n        return id;\n    }\n\n    @Override\n    public String getSelectedSceneId() {\n        return \"\";\n    }\n\n\n    public String getMockApiName() {\n        return mockApiName;\n    }\n\n    public void setMockApiName(String mockApiName) {\n        this.mockApiName = mockApiName;\n    }\n\n    @Override\n    public String getPath() {\n        return path;\n    }\n\n    public void setPath(String path) {\n        this.path = path;\n    }\n\n    public String getMethod() {\n        return method;\n    }\n\n    public void setMethod(String method) {\n        this.method = method;\n    }\n\n    @Override\n    public String getQuery() {\n        return query;\n    }\n\n    public void setQuery(String query) {\n        this.query = query;\n    }\n\n    @Override\n    public String getBody() {\n        return body;\n    }\n\n    public void setBody(String body) {\n        this.body = body;\n    }\n\n    public String getFromType() {\n        return fromType;\n    }\n\n    public void setFromType(String fromType) {\n        this.fromType = fromType;\n    }\n\n\n    public void setId(@NonNull String id) {\n        this.id = id;\n    }\n\n    public String getGroup() {\n        return group;\n    }\n\n    public void setGroup(String group) {\n        this.group = group;\n    }\n\n    public String getCreatePerson() {\n        return createPerson;\n    }\n\n    public void setCreatePerson(String createPerson) {\n        this.createPerson = createPerson;\n    }\n\n    public String getModifyPerson() {\n        return modifyPerson;\n    }\n\n    public void setModifyPerson(String modifyPerson) {\n        this.modifyPerson = modifyPerson;\n    }\n\n    @Override\n    public boolean isOpen() {\n        return isOpen;\n    }\n\n    @Override\n    public void setOpen(boolean open) {\n        this.isOpen = open;\n    }\n\n\n    public String getStrResponse() {\n        return strResponse;\n    }\n\n    public void setStrResponse(String strResponse) {\n        this.strResponse = strResponse;\n    }\n\n    public int getResponseFrom() {\n        return responseFrom;\n    }\n\n    public void setResponseFrom(int responseFrom) {\n        this.responseFrom = responseFrom;\n    }\n\n    public String getProjectId() {\n        return projectId;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/stream/GunzippingOutputStream.java",
    "content": "/*\n * Copyright (c) 2014-present, Facebook, Inc.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree. An additional grant\n * of patent rights can be found in the PATENTS file in the same directory.\n */\n\npackage com.didichuxing.doraemonkit.kit.network.stream;\n\nimport com.didichuxing.doraemonkit.kit.network.utils.ExceptionUtil;\nimport com.didichuxing.doraemonkit.kit.network.utils.StreamUtil;\n\nimport java.io.FilterOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.PipedInputStream;\nimport java.io.PipedOutputStream;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.zip.GZIPInputStream;\n\n\npublic class GunzippingOutputStream extends FilterOutputStream {\n  private final Future<Void> mCopyFuture;\n\n  private static final ExecutorService sExecutor = Executors.newCachedThreadPool();\n\n  public static GunzippingOutputStream create(OutputStream finalOut) throws IOException {\n    PipedInputStream pipeIn = new PipedInputStream();\n    PipedOutputStream pipeOut = new PipedOutputStream(pipeIn);\n\n    Future<Void> copyFuture = sExecutor.submit(\n        new GunzippingCallable(pipeIn, finalOut));\n\n    return new GunzippingOutputStream(pipeOut, copyFuture);\n  }\n\n  private GunzippingOutputStream(OutputStream out, Future<Void> copyFuture) throws IOException {\n    super(out);\n    mCopyFuture = copyFuture;\n  }\n\n  @Override\n  public void close() throws IOException {\n    boolean success = false;\n    try {\n      super.close();\n      success = true;\n    } finally {\n      try {\n        getAndRethrow(mCopyFuture);\n      } catch (IOException e) {\n        if (success) {\n          throw e;\n        }\n      }\n    }\n  }\n\n  private static <T> T getAndRethrow(Future<T> future) throws IOException {\n    while (true) {\n      try {\n        return future.get();\n      } catch (InterruptedException e) {\n        // Continue...\n      } catch (ExecutionException e) {\n        Throwable cause = e.getCause();\n        ExceptionUtil.propagateIfInstanceOf(cause, IOException.class);\n        ExceptionUtil.propagate(cause);\n      }\n    }\n  }\n\n  private static class GunzippingCallable implements Callable<Void> {\n    private final InputStream mIn;\n    private final OutputStream mOut;\n\n    public GunzippingCallable(InputStream in, OutputStream out) {\n      mIn = in;\n      mOut = out;\n    }\n\n    @Override\n    public Void call() throws IOException {\n      GZIPInputStream in = new GZIPInputStream(mIn);\n      try {\n        StreamUtil.copy(in, mOut, new byte[1024]);\n      } finally {\n        in.close();\n        mOut.close();\n      }\n      return null;\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/stream/HttpOutputStreamProxy.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.stream;\n\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord;\nimport com.didichuxing.doraemonkit.kit.network.core.NetworkInterpreter;\nimport com.didichuxing.doraemonkit.kit.network.core.RequestBodyHelper;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * @author: linjizong\n *  2019/3/14\n * @desc:\n */\npublic class HttpOutputStreamProxy extends OutputStreamProxy {\n    private final int mRequestId;\n    private final NetworkInterpreter mInterpreter;\n\n    public HttpOutputStreamProxy(OutputStream out,int requestId, NetworkInterpreter interpreter) {\n        super(out);\n        mRequestId = requestId;\n        mInterpreter = interpreter;\n    }\n\n    @Override\n    protected  void onStreamComplete() throws IOException {\n        NetworkRecord record = NetworkManager.get().getRecord(mRequestId);\n        if (record != null && record.mRequest != null) {\n            RequestBodyHelper requestBodyHelper = new RequestBodyHelper();\n            try {\n                OutputStream out = requestBodyHelper.createBodySink(record.mRequest.encode);\n                mOutputStream.writeTo(out);\n            } finally {\n                out.close();\n            }\n            byte[] body = requestBodyHelper.getDisplayBody();\n            mInterpreter.fetRequestBody(record, body);\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/stream/InputStreamProxy.java",
    "content": "/*\n * Copyright (c) 2014-present, Facebook, Inc.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree. An additional grant\n * of patent rights can be found in the PATENTS file in the same directory.\n */\n\npackage com.didichuxing.doraemonkit.kit.network.stream;\n\nimport com.didichuxing.doraemonkit.kit.network.core.ResponseHandler;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\npublic final class InputStreamProxy extends FilterInputStream {\n\n    public static final String TAG = \"ResponseHandlingInputStream\";\n\n    private static final int BUFFER_SIZE = 1024;\n\n    private final ByteArrayOutputStream mOutputStream = new ByteArrayOutputStream();\n    private final ResponseHandler mResponseHandler;\n\n    private boolean mClosed;\n\n    private byte[] mSkipBuffer;\n\n    /**\n     * @param inputStream\n     * @param responseHandler Special interface to intercept read events before they are sent\n     *                        to peers via  methods.\n     */\n    public InputStreamProxy(\n            InputStream inputStream,\n            ResponseHandler responseHandler) {\n        super(inputStream);\n        mResponseHandler = responseHandler;\n        mClosed = false;\n    }\n\n    private synchronized int checkEOF(int n) {\n        if (n == -1) {\n            if (mResponseHandler != null) {\n                mResponseHandler.onEOF(mOutputStream);\n            }\n            closeOutputStreamQuietly();\n        }\n        return n;\n    }\n\n    @Override\n    public int read() throws IOException {\n        try {\n            int result = checkEOF(in.read());\n            if (result != -1) {\n                writeToOutputStream(result);\n            }\n            return result;\n        } catch (IOException ex) {\n            throw handleIOException(ex);\n        }\n    }\n\n    @Override\n    public int read(byte[] b) throws IOException {\n        return this.read(b, 0, b.length);\n    }\n\n    @Override\n    public int read(byte[] b, int off, int len) throws IOException {\n        try {\n            int result = checkEOF(in.read(b, off, len));\n            if (result != -1) {\n                writeToOutputStream(b, off, result);\n            }\n            return result;\n        } catch (IOException ex) {\n            throw handleIOException(ex);\n        }\n    }\n\n    @Override\n    public synchronized long skip(long n) throws IOException {\n        byte[] buffer = getSkipBufferLocked();\n        long total = 0;\n        while (total < n) {\n            long bytesDiff = n - total;\n            int bytesToRead = (int) Math.min((long) buffer.length, bytesDiff);\n            int result = this.read(buffer, 0, bytesToRead);\n            if (result == -1) {\n                break;\n            }\n            total += result;\n        }\n        return total;\n    }\n\n    private byte[] getSkipBufferLocked() {\n        if (mSkipBuffer == null) {\n            mSkipBuffer = new byte[BUFFER_SIZE];\n        }\n\n        return mSkipBuffer;\n    }\n\n    @Override\n    public boolean markSupported() {\n        // this can be implemented, but isn't needed for TeedInputStream's behavior\n        return false;\n    }\n\n    @Override\n    public void mark(int readlimit) {\n        // noop -- mark is not supported\n    }\n\n    @Override\n    public void reset() throws IOException {\n        throw new UnsupportedOperationException(\"Mark not supported\");\n    }\n\n    @Override\n    public void close() throws IOException {\n        super.close();\n        closeOutputStreamQuietly();\n    }\n\n    private synchronized void closeOutputStreamQuietly() {\n        if (!mClosed) {\n            try {\n                mOutputStream.close();\n            } catch (IOException e) {\n            } finally {\n                mClosed = true;\n            }\n        }\n    }\n\n    private IOException handleIOException(IOException ex) {\n        if (mResponseHandler != null) {\n            mResponseHandler.onError(ex);\n        }\n        return ex;\n    }\n\n    private synchronized void writeToOutputStream(int oneByte) {\n        if (mClosed) {\n            return;\n        }\n        mOutputStream.write(oneByte);\n    }\n\n    private synchronized void writeToOutputStream(byte[] b, int offset, int count) {\n        if (mClosed) {\n            return;\n        }\n\n        mOutputStream.write(b, offset, count);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/stream/OutputStreamProxy.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.stream;\n\n\nimport androidx.annotation.NonNull;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.FilterOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * @desc: OutputStream的包装类，在写入时记录写入的内容\n */\npublic abstract class OutputStreamProxy extends FilterOutputStream {\n    protected final ByteArrayOutputStream mOutputStream = new ByteArrayOutputStream();\n\n\n    public OutputStreamProxy(@NonNull OutputStream out) {\n        super(out);\n    }\n\n    protected abstract void onStreamComplete() throws IOException;\n\n    @Override\n    public void flush() throws IOException {\n        super.flush();\n        onStreamComplete();\n    }\n\n    @Override\n    public void write(@NonNull byte[] b, int off, int len) throws IOException {\n        super.write(b, off, len);\n        mOutputStream.write(b, off, len);\n    }\n\n\n    @Override\n    public void close() throws IOException {\n        super.close();\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/InterceptDetailNodeProvider.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.RadioButton;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.network.bean.MockApiResponseBean;\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager;\nimport com.didichuxing.doraemonkit.kit.network.room_db.MockInterceptApiBean;\nimport com.didichuxing.doraemonkit.widget.MultiLineRadioGroup;\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.BaseNode;\nimport com.didichuxing.doraemonkit.widget.brvah.provider.BaseNodeProvider;\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder;\nimport com.didichuxing.doraemonkit.widget.jsonviewer.JsonRecyclerView;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/30-15:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class InterceptDetailNodeProvider extends BaseNodeProvider {\n    @Override\n    public int getItemViewType() {\n        return InterceptMockAdapter.TYPE_CONTENT;\n    }\n\n    @Override\n    public int getLayoutId() {\n        return R.layout.dk_mock_intercept_content_item;\n    }\n\n    @Override\n    public void convert(BaseViewHolder holder, BaseNode item) {\n        if (item instanceof MockInterceptApiBean) {\n            final MockInterceptApiBean mockApi = (MockInterceptApiBean) item;\n            holder.setText(R.id.tv_path, \"path:\" + mockApi.getPath());\n            JsonRecyclerView jsonQuery = holder.getView(R.id.jsonviewer_query);\n            JsonRecyclerView jsonBody = holder.getView(R.id.jsonviewer_body);\n\n            try {\n                holder.getView(R.id.rl_query).setVisibility(View.VISIBLE);\n                JSONObject jsonObject = new JSONObject(mockApi.getQuery());\n                if (jsonObject.length() == 0) {\n                    holder.getView(R.id.rl_query).setVisibility(View.GONE);\n                } else {\n                    jsonQuery.bindJson(mockApi.getQuery());\n                }\n            } catch (JSONException e) {\n                e.printStackTrace();\n                holder.getView(R.id.rl_query).setVisibility(View.GONE);\n            }\n\n            try {\n                holder.getView(R.id.rl_body).setVisibility(View.VISIBLE);\n                JSONObject jsonObject = new JSONObject(mockApi.getBody());\n                if (jsonObject.length() == 0) {\n                    holder.getView(R.id.rl_body).setVisibility(View.GONE);\n                } else {\n                    jsonBody.bindJson(mockApi.getBody());\n                }\n            } catch (JSONException e) {\n                e.printStackTrace();\n                holder.getView(R.id.rl_body).setVisibility(View.GONE);\n            }\n\n\n            holder.setText(R.id.tv_group, \"group:\" + mockApi.getGroup());\n            holder.setText(R.id.tv_create, \"create person:\" + mockApi.getCreatePerson());\n            holder.setText(R.id.tv_modify, \"modify person:\" + mockApi.getModifyPerson());\n            final MultiLineRadioGroup radioGroup = holder.getView(R.id.radio_group);\n            if (mockApi.getSceneList() != null && mockApi.getSceneList().size() != 0) {\n                String[] radioButtons = new String[mockApi.getSceneList().size()];\n                for (int index = 0; index < mockApi.getSceneList().size(); index++) {\n                    radioButtons[index] = mockApi.getSceneList().get(index).getName();\n                }\n                radioGroup.removeAllButtons();\n                radioGroup.addButtons(radioButtons);\n                radioGroup.setOnCheckedChangeListener(new MultiLineRadioGroup.OnCheckedChangeListener() {\n                    @Override\n                    public void onCheckedChanged(ViewGroup group, RadioButton button) {\n                        int index = radioGroup.getCheckedRadioButtonIndex();\n                        MockApiResponseBean.DataBean.DatalistBean.SceneListBean sceneListBean = mockApi.getSceneList().get(index);\n                        mockApi.setSelectedSceneName(sceneListBean.getName());\n                        mockApi.setSelectedSceneId(sceneListBean.get_id());\n                        DokitDbManager.getInstance().updateInterceptApi(mockApi);\n                    }\n                });\n                int index = 0;\n                for (int i = 0; i < mockApi.getSceneList().size(); i++) {\n                    if (mockApi.getSceneList().get(i).get_id().equals(mockApi.getSelectedSceneId())) {\n                        index = i;\n                        break;\n                    }\n\n                }\n                //默认选中第一个场景\n                radioGroup.checkAt(index);\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/InterceptMockAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.widget.brvah.BaseNodeAdapter;\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.BaseNode;\nimport com.didichuxing.doraemonkit.widget.brvah.module.LoadMoreModule;\nimport com.didichuxing.doraemonkit.kit.network.bean.MockInterceptTitleBean;\nimport com.didichuxing.doraemonkit.kit.network.room_db.MockInterceptApiBean;\n\nimport java.util.List;\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-12-15:04\n * 描    述：mock adapter\n * 修订历史：\n * ================================================\n */\npublic class InterceptMockAdapter extends BaseNodeAdapter implements LoadMoreModule {\n    public static final String TAG = \"InterceptMockAdapter\";\n    public final static int TYPE_TITLE = 100;\n    public final static int TYPE_CONTENT = 200;\n\n    public InterceptMockAdapter(List<BaseNode> nodeList) {\n        super(nodeList);\n        addFullSpanNodeProvider(new InterceptTitleNodeProvider());\n        addNodeProvider(new InterceptDetailNodeProvider());\n    }\n\n    @Override\n    protected int getItemType(@NonNull List<? extends BaseNode> data, int position) {\n        BaseNode node = data.get(position);\n        if (node instanceof MockInterceptTitleBean) {\n            return InterceptMockAdapter.TYPE_TITLE;\n        } else if (node instanceof MockInterceptApiBean) {\n            return InterceptMockAdapter.TYPE_CONTENT;\n        }\n        return -1;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/InterceptTitleNodeProvider.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.widget.CheckBox;\nimport android.widget.CompoundButton;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.network.bean.MockApiResponseBean;\nimport com.didichuxing.doraemonkit.kit.network.bean.MockInterceptTitleBean;\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager;\nimport com.didichuxing.doraemonkit.kit.network.room_db.MockInterceptApiBean;\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.BaseNode;\nimport com.didichuxing.doraemonkit.widget.brvah.provider.BaseNodeProvider;\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder;\n\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/30-15:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class InterceptTitleNodeProvider extends BaseNodeProvider {\n    @Override\n    public int getItemViewType() {\n        return InterceptMockAdapter.TYPE_TITLE;\n    }\n\n    @Override\n    public int getLayoutId() {\n        return R.layout.dk_mock_title_item;\n    }\n\n\n    @Override\n    public void convert(@NonNull BaseViewHolder holder, BaseNode item) {\n        if (item instanceof MockInterceptTitleBean) {\n            final MockInterceptTitleBean mockTitleBean = (MockInterceptTitleBean) item;\n            MockInterceptApiBean mockApi0 = (MockInterceptApiBean) mockTitleBean.getChildNode().get(0);\n            holder.setText(R.id.tv_title, mockTitleBean.getName());\n            if (mockTitleBean.isExpanded()) {\n                holder.setImageResource(R.id.iv_more, R.mipmap.dk_arrow_open);\n            } else {\n                holder.setImageResource(R.id.iv_more, R.mipmap.dk_arrow_normal);\n            }\n            CheckBox checkBox = holder.getView(R.id.menu_switch);\n            //建议将setOnCheckedChangeListener放在控件checkBox.setChecked前面 否则代码设置选中时会触发回调导致状态不正确\n            checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {\n                @Override\n                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {\n                    MockInterceptApiBean mockApi = (MockInterceptApiBean) mockTitleBean.getChildNode().get(0);\n                    //LogHelper.i(TAG, \"checkBox====>\" + mockApi.getMockApiName() + \"----\" + isChecked);\n                    mockApi.setOpen(isChecked);\n                    //默认选中第一个场景\n                    if (TextUtils.isEmpty(mockApi.getSelectedSceneId())) {\n                        List<MockApiResponseBean.DataBean.DatalistBean.SceneListBean> sceneListBeans = mockApi.getSceneList();\n                        if (sceneListBeans != null && sceneListBeans.size() > 0) {\n                            MockApiResponseBean.DataBean.DatalistBean.SceneListBean sceneListBean = sceneListBeans.get(0);\n                            mockApi.setSelectedSceneName(sceneListBean.getName());\n                            mockApi.setSelectedSceneId(sceneListBean.get_id());\n                        }\n                    }\n\n                    DokitDbManager.getInstance().updateInterceptApi(mockApi);\n\n                }\n            });\n            //LogHelper.i(TAG, \"init====>\" + mockApi0.getMockApiName() + \"----\" + mockApi0.isOpen());\n            if (mockApi0.isOpen()) {\n                checkBox.setChecked(true);\n            } else {\n                checkBox.setChecked(false);\n            }\n        }\n\n    }\n\n    @Override\n    public void onClick(BaseViewHolder holder, View view, BaseNode data, int position) {\n        super.onClick(holder, view, data, position);\n        if (data instanceof MockInterceptTitleBean && getAdapter() != null) {\n            getAdapter().expandOrCollapse(position);\n            final MockInterceptTitleBean mockTitleBean = (MockInterceptTitleBean) data;\n            if (mockTitleBean.isExpanded()) {\n                holder.setImageResource(R.id.iv_more, R.mipmap.dk_arrow_normal);\n            } else {\n                holder.setImageResource(R.id.iv_more, R.mipmap.dk_arrow_open);\n            }\n        }\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/ListDropDownAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.BaseAdapter;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\n\nimport java.util.List;\n\n\n\npublic class ListDropDownAdapter extends BaseAdapter {\n\n    private Context context;\n    private List<String> list;\n    private int checkItemPosition = 0;\n\n    public List<String> getList() {\n        return list;\n    }\n\n    public void setCheckItem(int position) {\n        checkItemPosition = position;\n        notifyDataSetChanged();\n    }\n\n    public ListDropDownAdapter(Context context, List<String> list) {\n        this.context = context;\n        this.list = list;\n    }\n\n    @Override\n    public int getCount() {\n        return list.size();\n    }\n\n    @Override\n    public Object getItem(int position) {\n        return null;\n    }\n\n    @Override\n    public long getItemId(int position) {\n        return position;\n    }\n\n    @Override\n    public View getView(int position, View convertView, ViewGroup parent) {\n        ViewHolder viewHolder;\n        if (convertView != null) {\n            viewHolder = (ViewHolder) convertView.getTag();\n        } else {\n            convertView = LayoutInflater.from(context).inflate(R.layout.dk_item_default_drop_down, null);\n            viewHolder = new ViewHolder(convertView);\n            convertView.setTag(viewHolder);\n        }\n        fillValue(position, viewHolder);\n        return convertView;\n    }\n\n    private void fillValue(int position, ViewHolder viewHolder) {\n        viewHolder.mText.setText(list.get(position));\n        if (checkItemPosition != -1) {\n            if (checkItemPosition == position) {\n                viewHolder.mText.setTextColor(context.getResources().getColor(R.color.dk_drop_down_selected));\n                viewHolder.mText.setBackgroundResource(R.color.dk_check_bg);\n            } else {\n                viewHolder.mText.setTextColor(context.getResources().getColor(R.color.dk_drop_down_unselected));\n                viewHolder.mText.setBackgroundResource(R.color.dk_color_FFFFFF);\n            }\n        }\n    }\n\n    static class ViewHolder {\n        TextView mText;\n\n        ViewHolder(View contenView) {\n            mText = contenView.findViewById(R.id.text);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/MockTemplatePreviewFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.os.Bundle;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.android.volley.Request;\nimport com.android.volley.Response;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.JsonObjectRequest;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager;\nimport com.didichuxing.doraemonkit.kit.network.room_db.MockTemplateApiBean;\nimport com.didichuxing.doraemonkit.util.LogHelper;\nimport com.didichuxing.doraemonkit.volley.VolleyManager;\nimport com.didichuxing.doraemonkit.widget.jsonviewer.JsonRecyclerView;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 魔板数据预览fragment\n */\npublic class MockTemplatePreviewFragment extends BaseFragment {\n\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_mock_template_preview;\n    }\n\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n    }\n\n    private void initView() {\n        if (getActivity() == null || DokitDbManager.getInstance().getGlobalTemplateApiBean() == null) {\n            return;\n        }\n        HomeTitleBar homeTitleBar = findViewById(R.id.title_bar);\n        homeTitleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                finish();\n            }\n        });\n        TextView tvName = findViewById(R.id.tv_name);\n        TextView tvPath = findViewById(R.id.tv_path);\n        tvName.setText(String.format(\"mock api name:%s\", DokitDbManager.getInstance().getGlobalTemplateApiBean().getMockApiName()));\n        tvPath.setText(String.format(\"mock api path:%s\", DokitDbManager.getInstance().getGlobalTemplateApiBean().getPath()));\n        JsonRecyclerView jsonViewQuery = findViewById(R.id.json_query);\n        JsonRecyclerView jsonRecycleView = findViewById(R.id.jsonviewer);\n\n        TextView tvUpload = findViewById(R.id.tv_upload);\n        tvUpload.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (DokitDbManager.getInstance().getGlobalTemplateApiBean() == null) {\n                    ToastUtils.showShort(\"no mock template data\");\n                    return;\n                }\n                MockTemplateApiBean mockApi = DokitDbManager.getInstance().getGlobalTemplateApiBean();\n\n                Map<String, String> values = new HashMap<>();\n                values.put(\"projectId\", mockApi.getProjectId());\n                values.put(\"id\", mockApi.getId());\n                values.put(\"tempData\", mockApi.getStrResponse());\n\n                Request<JSONObject> request = new JsonObjectRequest(Request.Method.PATCH, TemplateMockAdapter.TEMPLATER_UPLOAD_URL, new JSONObject(values), new Response.Listener<JSONObject>() {\n                    @Override\n                    public void onResponse(JSONObject response) {\n                        ToastUtils.showShort(\"upload template succeed\");\n                        LogHelper.i(TAG, \"上传模板===>\" + response.toString());\n                    }\n                }, new Response.ErrorListener() {\n\n                    @Override\n                    public void onErrorResponse(VolleyError error) {\n                        ToastUtils.showShort(\"upload template failed\");\n                        LogHelper.e(TAG, \"error===>\" + error.getMessage());\n                    }\n                });\n\n                VolleyManager.INSTANCE.add(request);\n            }\n        });\n\n        if (DokitDbManager.getInstance().getGlobalTemplateApiBean() == null) {\n            ToastUtils.showShort(\"no mock template data\");\n            return;\n        }\n        try {\n\n            JSONObject jsonQuery = new JSONObject(DokitDbManager.getInstance().getGlobalTemplateApiBean().getQuery());\n            if (jsonQuery.length() == 0) {\n                jsonViewQuery.setVisibility(View.GONE);\n            } else {\n                jsonViewQuery.setVisibility(View.VISIBLE);\n                jsonViewQuery.bindJson(jsonQuery);\n            }\n\n            new JSONObject(DokitDbManager.getInstance().getGlobalTemplateApiBean().getStrResponse());\n            jsonRecycleView.setTextSize(16);\n            jsonRecycleView.bindJson(DokitDbManager.getInstance().getGlobalTemplateApiBean().getStrResponse());\n        } catch (JSONException e) {\n            e.printStackTrace();\n            ToastUtils.showShort(\"the data is not json\");\n        }\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/NetWorkMainPagerAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.annotation.Nullable;\nimport androidx.viewpager.widget.PagerAdapter;\n\nimport com.didichuxing.doraemonkit.R;\n\nimport java.util.ArrayList;\nimport java.util.List;\npublic class NetWorkMainPagerAdapter extends PagerAdapter {\n    private List<View> views;\n    private List<String> tabs = new ArrayList<>();\n\n    public NetWorkMainPagerAdapter(Context context, List<View> views) {\n        this.views = views;\n        tabs.add(context.getString(R.string.dk_net_monitor_title_summary));\n        tabs.add(context.getString(R.string.dk_net_monitor_list));\n    }\n\n    @Nullable\n    @Override\n    public CharSequence getPageTitle(int position) {\n        return tabs.get(position);\n    }\n\n    @Override\n    public int getCount() {\n        return views.size();\n    }\n\n    @Override\n    public boolean isViewFromObject(View view, Object object) {\n        return view == object;\n    }\n\n    @Override\n    public Object instantiateItem(ViewGroup container, int position) {\n        View view = views.get(position);\n        container.addView(view);\n        return view;\n    }\n\n    @Override\n    public void destroyItem(ViewGroup container, int position, Object object) {\n        container.removeView(views.get(position));\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/NetWorkMainPagerFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.os.Bundle;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.viewpager.widget.ViewPager;\n\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.titlebar.TitleBar;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * http 流量监控摘要页面\n */\npublic class NetWorkMainPagerFragment extends BaseFragment implements View.OnClickListener {\n    private ViewPager mViewPager;\n    private NetWorkSummaryView mSummaryView;\n    private NetworkListView mNetworkListView;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_net_main_pager;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n    }\n\n    private void initView() {\n        final TitleBar mTitleBar = findViewById(R.id.title_bar);\n        mTitleBar.setOnTitleBarClickListener(new TitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onLeftClick() {\n                getActivity().onBackPressed();\n            }\n\n            @Override\n            public void onRightClick() {\n\n            }\n        });\n        mViewPager = findViewById(R.id.vp_show);\n        mSummaryView = new NetWorkSummaryView(getContext());\n        mNetworkListView = new NetworkListView(getContext());\n        mNetworkListView.registerNetworkListener();\n        List<View> views = new ArrayList<>();\n        views.add(mSummaryView);\n        views.add(mNetworkListView);\n        mViewPager.setAdapter(new NetWorkMainPagerAdapter(getContext(), views));\n\n        final View tabSummary = findViewById(R.id.tab_summary);\n        ((TextView) tabSummary.findViewById(R.id.tab_text)).setText(R.string.dk_net_monitor_title_summary);\n        ((ImageView) tabSummary.findViewById(R.id.tab_icon)).setImageResource(R.drawable.dk_net_work_monitor_summary_selector);\n        tabSummary.setSelected(true);\n        tabSummary.setOnClickListener(this);\n\n        final View tabList = findViewById(R.id.tab_list);\n        ((TextView) tabList.findViewById(R.id.tab_text)).setText(R.string.dk_net_monitor_list);\n        ((ImageView) tabList.findViewById(R.id.tab_icon)).setImageResource(R.drawable.dk_net_work_monitor_list_selector);\n        tabList.setOnClickListener(this);\n\n        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {\n            @Override\n            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {\n                if (position == 0) {\n                    tabSummary.setSelected(true);\n                    tabList.setSelected(false);\n                } else {\n                    tabList.setSelected(true);\n                    tabSummary.setSelected(false);\n                }\n            }\n\n            @Override\n            public void onPageSelected(int position) {\n\n            }\n\n            @Override\n            public void onPageScrollStateChanged(int state) {\n\n            }\n        });\n    }\n\n    @Override\n    public void onClick(View v) {\n        int id = v.getId();\n        if (id == R.id.tab_summary) {\n            mViewPager.setCurrentItem(0, true);\n        } else if (id == R.id.tab_list) {\n            mViewPager.setCurrentItem(1, true);\n        }\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        if (mNetworkListView != null) {\n            mNetworkListView.unRegisterNetworkListener();\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/NetWorkMockFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.widget.AdapterView;\nimport android.widget.EditText;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.ListView;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.android.volley.Request;\nimport com.android.volley.Response;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.StringRequest;\nimport com.didichuxing.doraemonkit.util.ConvertUtils;\nimport com.didichuxing.doraemonkit.util.GsonUtils;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.kit.network.bean.MockApiResponseBean;\nimport com.didichuxing.doraemonkit.kit.network.bean.MockInterceptTitleBean;\nimport com.didichuxing.doraemonkit.kit.network.bean.MockTemplateTitleBean;\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager;\nimport com.didichuxing.doraemonkit.kit.network.room_db.MockInterceptApiBean;\nimport com.didichuxing.doraemonkit.kit.network.room_db.MockTemplateApiBean;\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil;\nimport com.didichuxing.doraemonkit.util.LogHelper;\nimport com.didichuxing.doraemonkit.volley.VolleyManager;\nimport com.didichuxing.doraemonkit.widget.brvah.listener.OnLoadMoreListener;\nimport com.didichuxing.doraemonkit.widget.brvah.module.BaseLoadMoreModule;\nimport com.didichuxing.doraemonkit.widget.dropdown.DkDropDownMenu;\nimport com.didichuxing.doraemonkit.widget.easyrefresh.EasyRefreshLayout;\nimport com.didichuxing.doraemonkit.widget.easyrefresh.LoadModel;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\n\nimport org.json.JSONArray;\nimport org.json.JSONObject;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * 数据mock 相关设置 详情页\n *\n * @author jintai\n */\npublic class NetWorkMockFragment extends BaseFragment {\n    private String projectId = DoKitManager.PRODUCT_ID;\n    private int pageSize = 50;\n    private String mFormatApiUrl = NetworkManager.MOCK_DOMAIN + \"/api/app/interface?projectId=%s&isfull=1&curPage=%s&pageSize=%s\";\n    private EditText mEditText;\n    private EasyRefreshLayout mInterceptRefreshLayout, mTemplateRefreshLayout;\n    private InterceptMockAdapter mInterceptApiAdapter;\n    private TemplateMockAdapter mTemplateApiAdapter;\n    private BaseLoadMoreModule mInterceptLoadMoreModule;\n    private BaseLoadMoreModule mTemplateLoadMoreModule;\n    private RecyclerView mRvIntercept;\n    private RecyclerView mRvTemplate;\n    private FrameLayout mRvWrap;\n    private TextView mTvMock, mTvTemplate;\n    private ImageView mIvMock, mIvTemplate;\n    private String[] mMenuHeaders = {DoKitCommUtil.getString(R.string.dk_data_mock_group),\n            DoKitCommUtil.getString(R.string.dk_data_mock_switch_status)};\n\n    private DkDropDownMenu mDropDownMenu;\n    /**\n     * drop down 分组adapter\n     */\n    private ListDropDownAdapter mGroupMenuAdapter, mSwitchMenuAdapter;\n\n\n    private String[] mSwitchMenus = {DoKitCommUtil.getString(R.string.dk_data_mock_switch_all),\n            DoKitCommUtil.getString(R.string.dk_data_mock_switch_opened),\n            DoKitCommUtil.getString(R.string.dk_data_mock_switch_closed)};\n    private List<View> popupViews = new ArrayList<>();\n\n    private FilterConditionBean mInterceptFilterBean, mTemplateFilterBean;\n    private static int BOTTOM_TAB_INDEX_0 = 0;\n    private static int BOTTOM_TAB_INDEX_1 = 1;\n    private int mSelectedTableIndex = BOTTOM_TAB_INDEX_0;\n\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n    }\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_net_mock;\n    }\n\n    HomeTitleBar mHomeTitleBar;\n\n    private void initView() {\n        if (getActivity() == null) {\n            return;\n        }\n        mHomeTitleBar = findViewById(R.id.title_bar);\n        mHomeTitleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                finish();\n            }\n        });\n        if (TextUtils.isEmpty(projectId)) {\n            ToastUtils.showLong(DoKitCommUtil.getString(R.string.dk_data_mock_plugin_toast));\n            return;\n        }\n        mEditText = findViewById(R.id.edittext);\n        TextView mTvSearch = findViewById(R.id.tv_search);\n        mTvSearch.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n                    mInterceptFilterBean.setFilterText(mEditText.getText().toString());\n                } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n                    mTemplateFilterBean.setFilterText(mEditText.getText().toString());\n                }\n                filterAndNotifyData();\n            }\n        });\n        LinearLayout mLlBottomInterceptWrap = findViewById(R.id.ll_bottom_tab_mock);\n        mLlBottomInterceptWrap.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                switchBottomTabStatus(BOTTOM_TAB_INDEX_0);\n            }\n        });\n        LinearLayout mLlBottomTemplateWrap = findViewById(R.id.ll_bottom_tab_template);\n        mLlBottomTemplateWrap.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                switchBottomTabStatus(BOTTOM_TAB_INDEX_1);\n            }\n        });\n        mTvMock = findViewById(R.id.tv_mock);\n        mTvTemplate = findViewById(R.id.tv_template);\n        mIvMock = findViewById(R.id.iv_mock);\n        mIvTemplate = findViewById(R.id.iv_template);\n\n        mDropDownMenu = findViewById(R.id.drop_down_menu);\n        mRvWrap = new FrameLayout(getActivity());\n        //mock\n        mInterceptRefreshLayout = new EasyRefreshLayout(getActivity());\n        mInterceptRefreshLayout.setBackgroundColor(getResources().getColor(R.color.dk_color_FFFFFF));\n        mRvIntercept = new RecyclerView(getActivity());\n        mInterceptRefreshLayout.addView(mRvIntercept);\n        mInterceptRefreshLayout.setLoadMoreModel(LoadModel.NONE);\n        //关闭下拉刷新\n        mInterceptRefreshLayout.setEnablePullToRefresh(false);\n        mInterceptRefreshLayout.addEasyEvent(new EasyRefreshLayout.EasyEvent() {\n            @Override\n            public void onLoadMore() {\n            }\n\n            @Override\n            public void onRefreshing() {\n                initResponseApis(1);\n            }\n        });\n        //template\n        mTemplateRefreshLayout = new EasyRefreshLayout(getActivity());\n        mTemplateRefreshLayout.setBackgroundColor(getResources().getColor(R.color.dk_color_FFFFFF));\n        mRvTemplate = new RecyclerView(getActivity());\n        mTemplateRefreshLayout.addView(mRvTemplate);\n        mTemplateRefreshLayout.setLoadMoreModel(LoadModel.NONE);\n        //关闭下拉刷新\n        mTemplateRefreshLayout.setEnablePullToRefresh(false);\n        mTemplateRefreshLayout.addEasyEvent(new EasyRefreshLayout.EasyEvent() {\n            @Override\n            public void onLoadMore() {\n            }\n\n            @Override\n            public void onRefreshing() {\n                initResponseApis(1);\n            }\n        });\n        mRvWrap.setBackgroundColor(getResources().getColor(R.color.dk_color_F5F6F7));\n        mRvWrap.setPadding(0, ConvertUtils.dp2px(4), 0, 0);\n        mRvWrap.addView(mInterceptRefreshLayout);\n        mRvWrap.addView(mTemplateRefreshLayout);\n\n\n        mRvIntercept.setLayoutManager(new LinearLayoutManager(getActivity()));\n\n        mRvTemplate.setLayoutManager(new LinearLayoutManager(getActivity()));\n        //请求接口列表\n        initResponseApis(1);\n    }\n\n    /**\n     * 分组筛选\n     */\n    private String mStrInterceptGroup = \"\", mStrTemplateGroup = \"\";\n\n    /**\n     * 0:所有\n     * 1:打开\n     * 2:关闭\n     */\n    private int mInterceptOpenStatus = 0, mTemplateOpenStatus = 0;\n\n    /**\n     * 全局的列表数据  主要用于筛选\n     */\n    private List<MockInterceptTitleBean> mInterceptTitleBeans = new ArrayList<>();\n    private List<MockTemplateTitleBean> mTemplateTitleBeans = new ArrayList<>();\n\n    /**\n     * 根据删选条件更新数据\n     */\n    private void filterAndNotifyData() {\n        String strFilter = mEditText.getText().toString();\n        if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n            List<MockInterceptTitleBean> interceptTitleBeans = new ArrayList<>();\n            for (MockInterceptTitleBean interceptTitleBean : mInterceptTitleBeans) {\n                MockInterceptApiBean interceptApiBean = (MockInterceptApiBean) interceptTitleBean.getChildNode().get(0);\n                //分组信息是否匹配\n                boolean boolGroupMatched;\n                if (TextUtils.isEmpty(mStrInterceptGroup)) {\n                    boolGroupMatched = true;\n                } else {\n                    boolGroupMatched = interceptApiBean.getGroup().equals(mStrInterceptGroup);\n                }\n                //接口开关是否匹配\n                boolean boolSwitchOpenMatched;\n\n                if (mInterceptOpenStatus == 0) {\n                    boolSwitchOpenMatched = true;\n                } else if (mInterceptOpenStatus == 1) {\n                    if (interceptApiBean.isOpen()) {\n                        boolSwitchOpenMatched = true;\n                    } else {\n                        boolSwitchOpenMatched = false;\n                    }\n                } else if (mInterceptOpenStatus == 2) {\n                    if (interceptApiBean.isOpen()) {\n                        boolSwitchOpenMatched = false;\n                    } else {\n                        boolSwitchOpenMatched = true;\n                    }\n                } else {\n                    boolSwitchOpenMatched = false;\n                }\n\n                //手动过滤信息是否匹配\n                boolean boolStrFilterMatched;\n                if (TextUtils.isEmpty(strFilter)) {\n                    boolStrFilterMatched = true;\n                } else {\n                    if (interceptApiBean.getMockApiName().contains(strFilter)) {\n                        boolStrFilterMatched = true;\n                    } else {\n                        boolStrFilterMatched = false;\n                    }\n                }\n\n\n                if (boolGroupMatched && boolSwitchOpenMatched && boolStrFilterMatched) {\n                    interceptTitleBeans.add(interceptTitleBean);\n                }\n            }\n            mInterceptApiAdapter.setNewInstance((List) interceptTitleBeans);\n            mInterceptLoadMoreModule.loadMoreEnd();\n            if (interceptTitleBeans.isEmpty()) {\n                mInterceptApiAdapter.setEmptyView(R.layout.dk_rv_empty_layout2);\n            }\n\n            //设置数据条数\n            mHomeTitleBar.setTitle(DoKitCommUtil.getString(R.string.dk_kit_network_mock) + \"(\" + interceptTitleBeans.size() + \")\");\n        } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n            List<MockTemplateTitleBean> templateTitleBeans = new ArrayList<>();\n            for (MockTemplateTitleBean templateTitleBean : mTemplateTitleBeans) {\n                MockTemplateApiBean templateApiBean = (MockTemplateApiBean) templateTitleBean.getChildNode().get(0);\n                //分组信息是否匹配\n                boolean boolGroupMatched;\n                if (TextUtils.isEmpty(mStrTemplateGroup)) {\n                    boolGroupMatched = true;\n                } else {\n                    boolGroupMatched = templateApiBean.getGroup().equals(mStrTemplateGroup);\n                }\n                //接口开关是否匹配\n                boolean boolSwitchOpenMatched;\n\n                if (mTemplateOpenStatus == 0) {\n                    boolSwitchOpenMatched = true;\n                } else if (mTemplateOpenStatus == 1) {\n                    if (templateApiBean.isOpen()) {\n                        boolSwitchOpenMatched = true;\n                    } else {\n                        boolSwitchOpenMatched = false;\n                    }\n                } else if (mTemplateOpenStatus == 2) {\n                    if (templateApiBean.isOpen()) {\n                        boolSwitchOpenMatched = false;\n                    } else {\n                        boolSwitchOpenMatched = true;\n                    }\n                } else {\n                    boolSwitchOpenMatched = false;\n                }\n\n                //手动过滤信息是否匹配\n                boolean boolStrFilterMatched;\n                if (TextUtils.isEmpty(strFilter)) {\n                    boolStrFilterMatched = true;\n                } else {\n                    if (templateApiBean.getMockApiName().contains(strFilter)) {\n                        boolStrFilterMatched = true;\n                    } else {\n                        boolStrFilterMatched = false;\n                    }\n                }\n\n\n                if (boolGroupMatched && boolSwitchOpenMatched && boolStrFilterMatched) {\n                    templateTitleBeans.add(templateTitleBean);\n                }\n            }\n            mTemplateApiAdapter.setNewInstance((List) templateTitleBeans);\n            mTemplateLoadMoreModule.loadMoreEnd();\n            if (templateTitleBeans.isEmpty()) {\n                mTemplateApiAdapter.setEmptyView(R.layout.dk_rv_empty_layout2);\n            }\n            //设置数据条数\n            mHomeTitleBar.setTitle(DoKitCommUtil.getString(R.string.dk_kit_network_mock) + \"(\" + templateTitleBeans.size() + \")\");\n        }\n    }\n\n\n    /**\n     * 将数据绑定到intercept RecycleView上\n     *\n     * @param mockTitleBeans\n     */\n    private void attachInterceptRv(@NonNull List<MockInterceptTitleBean> mockTitleBeans) {\n        //全局保存列表数据\n        mInterceptTitleBeans.addAll(mockTitleBeans);\n\n        mInterceptRefreshLayout.refreshComplete();\n        if (mInterceptApiAdapter == null) {\n            mInterceptApiAdapter = new InterceptMockAdapter(null);\n            mRvIntercept.setAdapter(mInterceptApiAdapter);\n            mInterceptLoadMoreModule = mInterceptApiAdapter.getLoadMoreModule();\n            //关闭加载更多\n            mInterceptLoadMoreModule.setEnableLoadMore(false);\n            mInterceptLoadMoreModule.setOnLoadMoreListener(new OnLoadMoreListener() {\n                @Override\n                public void onLoadMore() {\n                    if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n                        mInterceptLoadMoreModule.loadMoreEnd();\n                    } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n                        mTemplateLoadMoreModule.loadMoreEnd();\n                    }\n                    //loadMoreResponseApis();\n                }\n            });\n\n            mInterceptLoadMoreModule.setEnableLoadMoreIfNotFullPage(false);\n        }\n        if (mockTitleBeans.isEmpty()) {\n            mInterceptApiAdapter.setEmptyView(R.layout.dk_rv_empty_layout);\n            return;\n        }\n        mInterceptApiAdapter.setNewInstance((List) mockTitleBeans);\n        if (mockTitleBeans.size() < pageSize) {\n            mInterceptLoadMoreModule.loadMoreEnd();\n        }\n\n    }\n\n    /**\n     * 将数据绑定到template RecycleView上\n     *\n     * @param mockTitleBeans\n     */\n    private void attachTemplateRv(@NonNull List<MockTemplateTitleBean> mockTitleBeans) {\n        //全局保存列表数据\n        mTemplateTitleBeans.addAll(mockTitleBeans);\n        mTemplateRefreshLayout.refreshComplete();\n        if (mTemplateApiAdapter == null) {\n            //template\n            mTemplateApiAdapter = new TemplateMockAdapter(null);\n            mRvTemplate.setAdapter(mTemplateApiAdapter);\n            mTemplateLoadMoreModule = mTemplateApiAdapter.getLoadMoreModule();\n            //关闭加载更多\n            mTemplateLoadMoreModule.setEnableLoadMore(false);\n            mTemplateLoadMoreModule.setOnLoadMoreListener(new OnLoadMoreListener() {\n                @Override\n                public void onLoadMore() {\n                    if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n                        mInterceptLoadMoreModule.loadMoreEnd();\n                    } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n                        mTemplateLoadMoreModule.loadMoreEnd();\n                    }\n                    //loadMoreResponseApis();\n                }\n            });\n\n            mTemplateLoadMoreModule.setEnableLoadMoreIfNotFullPage(false);\n        }\n        if (mockTitleBeans.isEmpty()) {\n            mTemplateApiAdapter.setEmptyView(R.layout.dk_rv_empty_layout);\n            return;\n        }\n\n        mTemplateApiAdapter.setNewInstance((List) mockTitleBeans);\n        if (mockTitleBeans.size() < pageSize) {\n            mTemplateLoadMoreModule.loadMoreEnd();\n        }\n\n    }\n\n    /**\n     * 加载更多intercept 更新rv\n     *\n     * @param mockTitleBeans\n     */\n    private void loadMoreInterceptDates(List<MockInterceptTitleBean> mockTitleBeans) {\n        mInterceptApiAdapter.addData(mockTitleBeans);\n        if (mockTitleBeans.size() < pageSize) {\n            mInterceptLoadMoreModule.loadMoreEnd();\n        } else {\n            mInterceptLoadMoreModule.loadMoreComplete();\n        }\n    }\n\n    /**\n     * 加载更多template 更新rv\n     *\n     * @param mockTitleBeans\n     */\n    private void loadMoreTemplateDates(List<MockTemplateTitleBean> mockTitleBeans) {\n        mTemplateApiAdapter.addData(mockTitleBeans);\n        if (mockTitleBeans.size() < pageSize) {\n            mTemplateLoadMoreModule.loadMoreEnd();\n        } else {\n            mTemplateLoadMoreModule.loadMoreComplete();\n        }\n    }\n\n    /**\n     * 初始化mock 接口列表\n     */\n    private void loadMoreResponseApis() {\n        int curPage = 1;\n        if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n            curPage = mInterceptApiAdapter.getData().size() / pageSize + 1;\n        } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n            curPage = mTemplateApiAdapter.getData().size() / pageSize + 1;\n        }\n        String apiUrl = String.format(mFormatApiUrl, projectId, curPage, pageSize);\n\n        Request<String> request = new StringRequest(Request.Method.GET, apiUrl, new Response.Listener<String>() {\n            @Override\n            public void onResponse(String response) {\n                try {\n                    if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n                        List<MockInterceptTitleBean> mockInterceptTitleBeans = dealInterceptResponseData(response);\n                        //插入拦截接口\n                        loadMoreInterceptDates(mockInterceptTitleBeans);\n\n                    } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n                        List<MockTemplateTitleBean> mockTemplateTitleBeans = dealTemplateResponseData(response);\n                        loadMoreTemplateDates(mockTemplateTitleBeans);\n                    }\n\n                } catch (Exception e) {\n                    e.printStackTrace();\n                    if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n                        mInterceptLoadMoreModule.loadMoreEnd();\n                    } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n                        mTemplateLoadMoreModule.loadMoreEnd();\n                    }\n                }\n            }\n        }, new Response.ErrorListener() {\n            @Override\n            public void onErrorResponse(VolleyError error) {\n                if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n                    mInterceptLoadMoreModule.loadMoreEnd();\n                } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n                    mTemplateLoadMoreModule.loadMoreEnd();\n                }\n            }\n        });\n\n        VolleyManager.INSTANCE.add(request);\n\n    }\n\n    /**\n     * 初始化顶部筛选状态\n     */\n    private void initMenus(List<MockInterceptTitleBean> mockInterceptTitleBeans) {\n        final List<String> groups = new ArrayList<>();\n        groups.add(DoKitCommUtil.getString(R.string.dk_data_mock_group));\n        for (MockInterceptTitleBean mockInterceptTitleBean : mockInterceptTitleBeans) {\n            MockInterceptApiBean mockInterceptApiBean = (MockInterceptApiBean) mockInterceptTitleBean.getChildNode().get(0);\n            if (!groups.contains(mockInterceptApiBean.getGroup())) {\n                groups.add(mockInterceptApiBean.getGroup());\n            }\n        }\n        //init group menu\n        ListView mGroupListView = new ListView(getActivity());\n        mGroupListView.setDividerHeight(0);\n        mGroupMenuAdapter = new ListDropDownAdapter(getActivity(), groups);\n        mGroupListView.setAdapter(mGroupMenuAdapter);\n        mGroupListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {\n            @Override\n            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n                mGroupMenuAdapter.setCheckItem(position);\n                mDropDownMenu.setTabText(groups.get(position));\n                mDropDownMenu.closeMenu();\n                //保存删选状态\n                if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n                    mInterceptFilterBean.setGroupIndex(position);\n                    mStrInterceptGroup = groups.get(position).equals(DoKitCommUtil.getString(R.string.dk_data_mock_group)) ? \"\" : groups.get(position);\n                } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n                    mTemplateFilterBean.setGroupIndex(position);\n                    mStrTemplateGroup = groups.get(position).equals(DoKitCommUtil.getString(R.string.dk_data_mock_group)) ? \"\" : groups.get(position);\n                }\n\n                filterAndNotifyData();\n            }\n        });\n        //init switch menu\n        ListView mSwitchListView = new ListView(getActivity());\n        mSwitchListView.setDividerHeight(0);\n        mSwitchMenuAdapter = new ListDropDownAdapter(getActivity(), Arrays.asList(mSwitchMenus));\n        mSwitchListView.setAdapter(mSwitchMenuAdapter);\n        mSwitchListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {\n            @Override\n            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n                mSwitchMenuAdapter.setCheckItem(position);\n                mDropDownMenu.setTabText(mSwitchMenus[position]);\n                mDropDownMenu.closeMenu();\n                //保存删选状态\n                if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n                    mInterceptFilterBean.setSwitchIndex(position);\n                    mInterceptOpenStatus = position;\n                } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n                    mTemplateFilterBean.setSwitchIndex(position);\n                    mTemplateOpenStatus = position;\n                }\n\n                filterAndNotifyData();\n            }\n        });\n        popupViews.add(mGroupListView);\n        popupViews.add(mSwitchListView);\n        mDropDownMenu.setDropDownMenu(Arrays.asList(mMenuHeaders), popupViews, mRvWrap);\n        mInterceptFilterBean = new FilterConditionBean();\n        mInterceptFilterBean.setFilterText(\"\");\n        mInterceptFilterBean.setGroupIndex(0);\n        mInterceptFilterBean.setSwitchIndex(0);\n        mTemplateFilterBean = new FilterConditionBean();\n        mTemplateFilterBean.setFilterText(\"\");\n        mTemplateFilterBean.setGroupIndex(0);\n        mTemplateFilterBean.setSwitchIndex(0);\n\n        //初始化tab状态\n        switchBottomTabStatus(BOTTOM_TAB_INDEX_0);\n\n    }\n\n    String initMockInterceptResponse = \"\";\n    String initTemplateInterceptResponse = \"\";\n\n    /**\n     * 由于一次性请求数据过多会导致服务挂掉 所以拆分多次请求\n     */\n    private void initResponseApis(int currentPage) {\n        if (currentPage == 1) {\n            if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n                initMockInterceptResponse = \"\";\n            } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n                initTemplateInterceptResponse = \"\";\n            }\n        }\n        String apiUrl = String.format(mFormatApiUrl, projectId, currentPage, pageSize);\n        LogHelper.i(TAG, \"apiUrl===>\" + apiUrl);\n\n        Request<String> request = new StringRequest(Request.Method.GET, apiUrl, new Response.Listener<String>() {\n            @Override\n            public void onResponse(String response) {\n                try {\n                    if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n                        MockApiResponseBean mockApiResponseBean = GsonUtils.fromJson(response, MockApiResponseBean.class);\n                        List<MockApiResponseBean.DataBean.DatalistBean> lists = mockApiResponseBean.getData().getDatalist();\n                        //判断是否为null\n                        if (initMockInterceptResponse.isEmpty()) {\n                            initMockInterceptResponse = response;\n                        } else {\n                            MockApiResponseBean AllMockApiResponseBean = GsonUtils.fromJson(initMockInterceptResponse, MockApiResponseBean.class);\n                            List<MockApiResponseBean.DataBean.DatalistBean> AllLists = AllMockApiResponseBean.getData().getDatalist();\n                            AllLists.addAll(lists);\n                            initMockInterceptResponse = GsonUtils.toJson(AllMockApiResponseBean);\n                        }\n                        if (lists.size() < pageSize) {\n                            List<MockInterceptTitleBean> mockInterceptTitleBeans = dealInterceptResponseData(initMockInterceptResponse);\n                            initMenus(mockInterceptTitleBeans);\n                            attachInterceptRv(mockInterceptTitleBeans);\n                            mHomeTitleBar.setTitle(DoKitCommUtil.getString(R.string.dk_kit_network_mock) + \"(\" + mockInterceptTitleBeans.size() + \")\");\n                        } else {\n                            MockApiResponseBean AllMockApiResponseBean = GsonUtils.fromJson(initMockInterceptResponse, MockApiResponseBean.class);\n                            initResponseApis(AllMockApiResponseBean.getData().getDatalist().size() / pageSize + 1);\n                        }\n\n                        //测试空数据\n                        //attachInterceptRv(null);\n                    } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n                        MockApiResponseBean mockApiResponseBean = GsonUtils.fromJson(response, MockApiResponseBean.class);\n                        List<MockApiResponseBean.DataBean.DatalistBean> lists = mockApiResponseBean.getData().getDatalist();\n                        //判断是否为null\n                        if (initTemplateInterceptResponse.isEmpty()) {\n                            initTemplateInterceptResponse = response;\n                        } else {\n                            MockApiResponseBean AllMockApiResponseBean = GsonUtils.fromJson(initMockInterceptResponse, MockApiResponseBean.class);\n                            List<MockApiResponseBean.DataBean.DatalistBean> AllLists = AllMockApiResponseBean.getData().getDatalist();\n                            AllLists.addAll(lists);\n                            initTemplateInterceptResponse = GsonUtils.toJson(AllMockApiResponseBean);\n                        }\n                        if (lists.size() < pageSize) {\n                            List<MockTemplateTitleBean> mockInterceptTitleBeans = dealTemplateResponseData(initTemplateInterceptResponse);\n                            attachTemplateRv(mockInterceptTitleBeans);\n                            mHomeTitleBar.setTitle(DoKitCommUtil.getString(R.string.dk_kit_network_mock) + \"(\" + mockInterceptTitleBeans.size() + \")\");\n                        } else {\n                            MockApiResponseBean AllMockApiResponseBean = GsonUtils.fromJson(initTemplateInterceptResponse, MockApiResponseBean.class);\n                            initResponseApis(AllMockApiResponseBean.getData().getDatalist().size() / pageSize + 1);\n                        }\n                    }\n\n                } catch (Exception e) {\n                    e.printStackTrace();\n                    if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n                        mInterceptRefreshLayout.refreshComplete();\n                        mHomeTitleBar.setTitle(DoKitCommUtil.getString(R.string.dk_kit_network_mock) + \"(0)\");\n                    } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n                        mTemplateRefreshLayout.refreshComplete();\n                        mHomeTitleBar.setTitle(DoKitCommUtil.getString(R.string.dk_kit_network_mock) + \"(0)\");\n                    }\n                }\n            }\n        }, new Response.ErrorListener() {\n            @Override\n            public void onErrorResponse(VolleyError error) {\n                LogHelper.e(TAG, \"error====>\" + error.getMessage());\n                ToastUtils.showShort(error.getMessage());\n                if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n                    mInterceptRefreshLayout.refreshComplete();\n                } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n                    mTemplateRefreshLayout.refreshComplete();\n                }\n            }\n        });\n\n        VolleyManager.INSTANCE.add(request);\n    }\n\n    /**\n     * 初始化mock 接口列表\n     */\n//    private void initResponseApis() {\n//        String apiUrl = String.format(mFormatApiUrl, projectId, 1, pageSize);\n//        LogHelper.i(TAG, \"apiUrl===>\" + apiUrl);\n//\n//        Request<String> request = new StringRequest(Request.Method.GET, apiUrl, new Response.Listener<String>() {\n//            @Override\n//            public void onResponse(String response) {\n//                try {\n//                    if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n//                        List<MockInterceptTitleBean> mockInterceptTitleBeans = dealInterceptResponseData(response);\n//                        initMenus(mockInterceptTitleBeans);\n//                        attachInterceptRv(mockInterceptTitleBeans);\n//\n//                        mHomeTitleBar.setTitle(DokitUtil.getString(R.string.dk_kit_network_mock) + \"(\" + mockInterceptTitleBeans.size() + \")\");\n//                        //测试空数据\n//                        //attachInterceptRv(null);\n//                    } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n//                        List<MockTemplateTitleBean> mockTemplateTitleBeans = dealTemplateResponseData(response);\n//                        attachTemplateRv(mockTemplateTitleBeans);\n//                        mHomeTitleBar.setTitle(DokitUtil.getString(R.string.dk_kit_network_mock) + \"(\" + mockTemplateTitleBeans.size() + \")\");\n//                    }\n//\n//                } catch (Exception e) {\n//                    e.printStackTrace();\n//                    if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n//                        mInterceptRefreshLayout.refreshComplete();\n//                        mHomeTitleBar.setTitle(DokitUtil.getString(R.string.dk_kit_network_mock) + \"(0)\");\n//                    } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n//                        mTemplateRefreshLayout.refreshComplete();\n//                        mHomeTitleBar.setTitle(DokitUtil.getString(R.string.dk_kit_network_mock) + \"(0)\");\n//                    }\n//                }\n//            }\n//        }, new Response.ErrorListener() {\n//            @Override\n//            public void onErrorResponse(VolleyError error) {\n//                LogHelper.e(TAG, \"error====>\" + error.getMessage());\n//                ToastUtils.showShort(error.getMessage());\n//                if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n//                    mInterceptRefreshLayout.refreshComplete();\n//                } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n//                    mTemplateRefreshLayout.refreshComplete();\n//                }\n//            }\n//        });\n//\n//        VolleyManager.INSTANCE.add(request);\n//\n//\n//    }\n\n\n    //private int rvTypeIntercept = 0;\n    //private int rvTypeTemplate = 1;\n\n    /**\n     * @param strResponse 返回的数据\n     * @return\n     */\n    private @NonNull\n    List<MockInterceptTitleBean> dealInterceptResponseData(String strResponse) throws Exception {\n\n        JSONObject responseJsonObject = new JSONObject(strResponse);\n        JSONArray jsonArray = responseJsonObject.getJSONObject(\"data\").getJSONArray(\"datalist\");\n\n        MockApiResponseBean mockApiResponseBean = GsonUtils.fromJson(strResponse, MockApiResponseBean.class);\n        List<MockApiResponseBean.DataBean.DatalistBean> lists = mockApiResponseBean.getData().getDatalist();\n        ArrayList<MockInterceptTitleBean> mockInterceptTitleBeans = new ArrayList<>();\n        for (int index = 0; index < lists.size(); index++) {\n            MockApiResponseBean.DataBean.DatalistBean datalistBean = lists.get(index);\n            JSONObject queryJsonObject;\n            JSONObject bodyJsonObject;\n            JSONObject mockJsonObject = jsonArray.getJSONObject(index);\n            if (mockJsonObject.has(\"query\")) {\n                queryJsonObject = mockJsonObject.getJSONObject(\"query\");\n            } else {\n                queryJsonObject = new JSONObject();\n            }\n\n            if (mockJsonObject.has(\"body\")) {\n                bodyJsonObject = mockJsonObject.getJSONObject(\"body\");\n            } else {\n                bodyJsonObject = new JSONObject();\n            }\n\n            String modifyName = \"null\";\n            if (datalistBean.getCurStatus() != null && datalistBean.getCurStatus().getOperator() != null) {\n                modifyName = datalistBean.getCurStatus().getOperator().getName();\n            }\n            //新建 intercept\n            List<MockInterceptApiBean> mockInterceptApiBeans = new ArrayList<>();\n            mockInterceptApiBeans.add(new MockInterceptApiBean(datalistBean.get_id(), datalistBean.getName(), datalistBean.getPath()\n                    , datalistBean.getMethod(), datalistBean.getFormatType(),\n                    queryJsonObject.toString(), bodyJsonObject.toString(),\n                    datalistBean.getCategoryName(), datalistBean.getOwner().getName(),\n                    modifyName, datalistBean.getSceneList()));\n            MockInterceptTitleBean mockInterceptTitleBean = new MockInterceptTitleBean(datalistBean.getName(), mockInterceptApiBeans);\n            mockInterceptTitleBeans.add(mockInterceptTitleBean);\n\n        }\n        //插入拦截接口\n        insertAllInterceptApis(mockInterceptTitleBeans);\n        return mockInterceptTitleBeans;\n    }\n\n    /**\n     * @param strResponse 返回的数据\n     * @return\n     */\n    private @NonNull\n    List<MockTemplateTitleBean> dealTemplateResponseData(String strResponse) throws Exception {\n\n        JSONObject responseJsonObject = new JSONObject(strResponse);\n        JSONArray jsonArray = responseJsonObject.getJSONObject(\"data\").getJSONArray(\"datalist\");\n\n        MockApiResponseBean mockApiResponseBean = GsonUtils.fromJson(strResponse, MockApiResponseBean.class);\n        List<MockApiResponseBean.DataBean.DatalistBean> lists = mockApiResponseBean.getData().getDatalist();\n        ArrayList<MockTemplateTitleBean> mockTemplateTitleBeans = new ArrayList<>();\n        for (int index = 0; index < lists.size(); index++) {\n            MockApiResponseBean.DataBean.DatalistBean datalistBean = lists.get(index);\n            JSONObject queryJsonObject;\n            JSONObject bodyJsonObject;\n            JSONObject mockJsonObject = jsonArray.getJSONObject(index);\n            if (mockJsonObject.has(\"query\")) {\n                queryJsonObject = mockJsonObject.getJSONObject(\"query\");\n            } else {\n                queryJsonObject = new JSONObject();\n            }\n\n            if (mockJsonObject.has(\"body\")) {\n                bodyJsonObject = mockJsonObject.getJSONObject(\"body\");\n            } else {\n                bodyJsonObject = new JSONObject();\n            }\n\n            String modifyName = \"null\";\n            if (datalistBean.getCurStatus() != null && datalistBean.getCurStatus().getOperator() != null) {\n                modifyName = datalistBean.getCurStatus().getOperator().getName();\n            }\n            //新建 template\n            List<MockTemplateApiBean> mockTemplateApiBeans = new ArrayList<>();\n            mockTemplateApiBeans.add(new MockTemplateApiBean(datalistBean.get_id(), datalistBean.getName(),\n                    datalistBean.getPath(), datalistBean.getMethod(),\n                    datalistBean.getFormatType(), queryJsonObject.toString(),\n                    bodyJsonObject.toString(), datalistBean.getCategoryName(), datalistBean.getOwner().getName(),\n                    modifyName, datalistBean.getProjectId()));\n            MockTemplateTitleBean mockTemplateTitleBean = new MockTemplateTitleBean(datalistBean.getName(), mockTemplateApiBeans);\n            mockTemplateTitleBeans.add(mockTemplateTitleBean);\n        }\n\n        //插入模板接口\n        insertAllTemplateApis(mockTemplateTitleBeans);\n        return mockTemplateTitleBeans;\n    }\n\n\n    /**\n     * 插入intercept数据\n     *\n     * @param mockTitleBeans\n     */\n    private void insertAllInterceptApis(ArrayList<MockInterceptTitleBean> mockTitleBeans) {\n        List<MockInterceptApiBean> mockApis = new ArrayList<>();\n\n        for (MockInterceptTitleBean mockInterceptTitleBean : mockTitleBeans) {\n            MockInterceptApiBean mockApi = (MockInterceptApiBean) mockInterceptTitleBean.getChildNode().get(0);\n            if (!hasInterceptApiInDb(mockApi.getPath(), mockApi.getId())) {\n                mockApis.add(mockApi);\n            } else {\n                updateInterceptApi(mockApi);\n            }\n        }\n\n        DokitDbManager.getInstance().insertAllInterceptApi(mockApis);\n\n    }\n\n\n    /**\n     * 插入template数据\n     *\n     * @param mockTitleBeans\n     */\n    private void insertAllTemplateApis(ArrayList<MockTemplateTitleBean> mockTitleBeans) {\n        List<MockTemplateApiBean> mockApis = new ArrayList<>();\n\n        for (MockTemplateTitleBean mockTemplateTitleBean : mockTitleBeans) {\n            MockTemplateApiBean mockApi = (MockTemplateApiBean) mockTemplateTitleBean.getChildNode().get(0);\n            if (!hasTemplateApiInDb(mockApi.getPath(), mockApi.getId())) {\n                mockApis.add(mockApi);\n            } else {\n                updateTemplateApi(mockApi);\n            }\n        }\n\n        DokitDbManager.getInstance().insertAllTemplateApi(mockApis);\n\n    }\n\n    /**\n     * 更新本地数据到新的数据列表中\n     *\n     * @param mockApi\n     * @return\n     */\n    private void updateInterceptApi(MockInterceptApiBean mockApi) {\n        List<MockInterceptApiBean> localInterceptApis = (List<MockInterceptApiBean>) DokitDbManager.getInstance().getGlobalInterceptApiMaps().get(mockApi.getPath());\n        if (localInterceptApis == null) {\n            return;\n        }\n        for (MockInterceptApiBean localMockApi : localInterceptApis) {\n            if (localMockApi.getId().equals(mockApi.getId())) {\n                mockApi.setOpen(localMockApi.isOpen());\n                mockApi.setSelectedSceneId(localMockApi.getSelectedSceneId());\n                mockApi.setSelectedSceneName(localMockApi.getSelectedSceneName());\n                break;\n            }\n        }\n\n    }\n\n\n    /**\n     * 更新本地数据到新的数据列表中\n     *\n     * @param mockApi\n     * @return\n     */\n    private void updateTemplateApi(MockTemplateApiBean mockApi) {\n        List<MockTemplateApiBean> localTemplateApis = (List<MockTemplateApiBean>) DokitDbManager.getInstance().getGlobalTemplateApiMaps().get(mockApi.getPath());\n        if (localTemplateApis == null) {\n            return;\n        }\n        for (MockTemplateApiBean localMockApi : localTemplateApis) {\n            if (localMockApi.getId().equals(mockApi.getId())) {\n                mockApi.setOpen(localMockApi.isOpen());\n                mockApi.setResponseFrom(localMockApi.getResponseFrom());\n                mockApi.setStrResponse(localMockApi.getStrResponse());\n                break;\n            }\n        }\n    }\n\n\n    /**\n     * 查找本地数据是否已经存在该条数据\n     *\n     * @param id\n     * @return\n     */\n    private boolean hasInterceptApiInDb(String path, String id) {\n        MockInterceptApiBean mockInterceptApi = (MockInterceptApiBean) DokitDbManager.getInstance().getInterceptApiByIdInMap(path, id, DokitDbManager.FROM_SDK_OTHER);\n        return mockInterceptApi != null;\n    }\n\n\n    /**\n     * 查找本地数据是否已经存在该条数据\n     *\n     * @param id\n     * @return\n     */\n    private boolean hasTemplateApiInDb(String path, String id) {\n        MockTemplateApiBean mockTemplateApi = (MockTemplateApiBean) DokitDbManager.getInstance().getTemplateApiByIdInMap(path, id, DokitDbManager.FROM_SDK_OTHER);\n        return mockTemplateApi != null;\n    }\n\n    /**\n     * 切换底部tabbar 状态\n     *\n     * @param tabIndex\n     */\n    private void switchBottomTabStatus(int tabIndex) {\n        switch (tabIndex) {\n            case 0:\n                mTvMock.setTextColor(getResources().getColor(R.color.dk_color_337CC4));\n                mTvTemplate.setTextColor(getResources().getColor(R.color.dk_color_333333));\n                mIvMock.setImageResource(R.mipmap.dk_mock_highlight);\n                mIvTemplate.setImageResource(R.mipmap.dk_template_normal);\n                mInterceptRefreshLayout.setVisibility(View.VISIBLE);\n                mTemplateRefreshLayout.setVisibility(View.GONE);\n                mSelectedTableIndex = BOTTOM_TAB_INDEX_0;\n                //设置数据条数\n                if (mInterceptApiAdapter != null) {\n                    mHomeTitleBar.setTitle(DoKitCommUtil.getString(R.string.dk_kit_network_mock) + \"(\" + mInterceptApiAdapter.getData().size() + \")\");\n                }\n                break;\n            case 1:\n                mTvMock.setTextColor(getResources().getColor(R.color.dk_color_333333));\n                mTvTemplate.setTextColor(getResources().getColor(R.color.dk_color_337CC4));\n                mIvMock.setImageResource(R.mipmap.dk_mock_normal);\n                mIvTemplate.setImageResource(R.mipmap.dk_template_highlight);\n                mInterceptRefreshLayout.setVisibility(View.GONE);\n                mTemplateRefreshLayout.setVisibility(View.VISIBLE);\n                mSelectedTableIndex = BOTTOM_TAB_INDEX_1;\n                if (mTemplateApiAdapter == null) {\n                    initResponseApis(1);\n                }\n                //设置数据条数\n                //设置数据条数\n                if (mTemplateApiAdapter != null) {\n                    mHomeTitleBar.setTitle(DoKitCommUtil.getString(R.string.dk_kit_network_mock) + \"(\" + mTemplateApiAdapter.getData().size() + \")\");\n                }\n                break;\n            default:\n                break;\n        }\n        resetMenuStatus();\n    }\n\n    /**\n     * 重置删选按钮的状态\n     */\n    private void resetMenuStatus() {\n        if (mSelectedTableIndex == BOTTOM_TAB_INDEX_0) {\n            if (mInterceptFilterBean != null) {\n                mGroupMenuAdapter.setCheckItem(mInterceptFilterBean.getGroupIndex());\n                mSwitchMenuAdapter.setCheckItem(mInterceptFilterBean.getSwitchIndex());\n                mDropDownMenu.resetTabText(new String[]{mGroupMenuAdapter.getList().get(mInterceptFilterBean.getGroupIndex()), mSwitchMenuAdapter.getList().get(mInterceptFilterBean.getSwitchIndex())});\n                mEditText.setText(\"\" + mInterceptFilterBean.getFilterText());\n            }\n\n        } else if (mSelectedTableIndex == BOTTOM_TAB_INDEX_1) {\n            if (mTemplateFilterBean != null) {\n                mGroupMenuAdapter.setCheckItem(mTemplateFilterBean.getGroupIndex());\n                mSwitchMenuAdapter.setCheckItem(mTemplateFilterBean.getSwitchIndex());\n                mDropDownMenu.resetTabText(new String[]{mGroupMenuAdapter.getList().get(mTemplateFilterBean.getGroupIndex()), mSwitchMenuAdapter.getList().get(mTemplateFilterBean.getSwitchIndex())});\n                mEditText.setText(\"\" + mTemplateFilterBean.getFilterText());\n            }\n        }\n\n        mDropDownMenu.closeMenu();\n    }\n\n\n    /**\n     * 删选条件保存的状态\n     */\n    private static class FilterConditionBean {\n        int groupIndex;\n        int switchIndex;\n        String filterText;\n\n        public int getGroupIndex() {\n            return groupIndex;\n        }\n\n        public void setGroupIndex(int groupIndex) {\n            this.groupIndex = groupIndex;\n        }\n\n        public int getSwitchIndex() {\n            return switchIndex;\n        }\n\n        public void setSwitchIndex(int switchIndex) {\n            this.switchIndex = switchIndex;\n        }\n\n        public String getFilterText() {\n            return filterText;\n        }\n\n        public void setFilterText(String filterText) {\n            this.filterText = filterText;\n        }\n\n        @Override\n        public String toString() {\n            return \"FilterConditionBean{\" +\n                    \"groupIndex=\" + groupIndex +\n                    \", switchIndex=\" + switchIndex +\n                    \", filterText='\" + filterText + '\\'' +\n                    '}';\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/NetWorkMonitorFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.os.Bundle;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.text.TextUtils;\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.util.FileIOUtils;\nimport com.didichuxing.doraemonkit.util.FileUtils;\nimport com.didichuxing.doraemonkit.util.GsonUtils;\nimport com.didichuxing.doraemonkit.util.PathUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.DokitMemoryConfig;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.kit.network.bean.WhiteHostBean;\nimport com.didichuxing.doraemonkit.kit.parameter.AbsParameterFragment;\nimport com.didichuxing.doraemonkit.kit.performance.datasource.DataSourceFactory;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * @author jint\n */\npublic class NetWorkMonitorFragment extends AbsParameterFragment {\n    RecyclerView mHostRv;\n    WhiteHostAdapter mHostAdapter;\n    List<WhiteHostBean> mHostBeans = new ArrayList<>();\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_net_monitor;\n    }\n\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initCustomView();\n    }\n\n    @Override\n    protected int getTitle() {\n        return R.string.dk_kit_net_monitor;\n    }\n\n    @Override\n    protected int getPerformanceType() {\n        return DataSourceFactory.TYPE_NETWORK;\n    }\n\n    @Override\n    protected Collection<SettingItem> getSettingItems(List<SettingItem> list) {\n        list.add(new SettingItem(R.string.dk_net_monitor_detection_switch, NetworkManager.isActive()));\n        return list;\n    }\n\n    @Override\n    protected SettingItemAdapter.OnSettingItemSwitchListener getItemSwitchListener() {\n        return new SettingItemAdapter.OnSettingItemSwitchListener() {\n            @Override\n            public void onSettingItemSwitch(View view, SettingItem data, boolean on) {\n                if (on) {\n                    startMonitor();\n                } else {\n                    stopMonitor();\n                }\n                DokitMemoryConfig.NETWORK_STATUS = on;\n            }\n        };\n    }\n\n    @Override\n    protected SettingItemAdapter.OnSettingItemClickListener getItemClickListener() {\n        return new SettingItemAdapter.OnSettingItemClickListener() {\n            @Override\n            public void onSettingItemClick(View view, SettingItem data) {\n\n            }\n        };\n    }\n\n    private void initCustomView() {\n        findViewById(R.id.btn_net_summary).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                showContent(NetWorkMainPagerFragment.class);\n            }\n        });\n        mHostRv = findViewById(R.id.host_list);\n        mHostRv.setLayoutManager(new LinearLayoutManager(getActivity()));\n        if (DoKitManager.WHITE_HOSTS.isEmpty()) {\n            String whiteHostArray = FileIOUtils.readFile2String(whiteHostPath);\n            if (TextUtils.isEmpty(whiteHostArray)) {\n                mHostBeans.add(new WhiteHostBean(\"\", true));\n            } else {\n                mHostBeans = GsonUtils.fromJson(whiteHostArray, GsonUtils.getListType(WhiteHostBean.class));\n                DoKitManager.WHITE_HOSTS.clear();\n                DoKitManager.WHITE_HOSTS.addAll(mHostBeans);\n            }\n        } else {\n            mHostBeans.addAll(DoKitManager.WHITE_HOSTS);\n        }\n\n        mHostAdapter = new WhiteHostAdapter(R.layout.dk_item_white_host, mHostBeans);\n        mHostRv.setAdapter(mHostAdapter);\n    }\n\n    private void startMonitor() {\n        NetworkManager.get().startMonitor();\n        openChartPage(R.string.dk_kit_net_monitor, DataSourceFactory.TYPE_NETWORK);\n    }\n\n    private void stopMonitor() {\n        NetworkManager.get().stopMonitor();\n        closeChartPage();\n    }\n\n    private String whiteHostPath = PathUtils.getInternalAppFilesPath() + File.separator + \"white_host.json\";\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        //保存白名单\n        List<WhiteHostBean> hostBeans = mHostAdapter.getData();\n        if (hostBeans.size() == 1 && TextUtils.isEmpty(hostBeans.get(0).getHost())) {\n            DoKitManager.WHITE_HOSTS.clear();\n            FileUtils.delete(whiteHostPath);\n            return;\n        }\n        DoKitManager.WHITE_HOSTS.clear();\n        DoKitManager.WHITE_HOSTS.addAll(hostBeans);\n        String hostArray = GsonUtils.toJson(hostBeans);\n        //保存到本地\n        FileUtils.delete(whiteHostPath);\n        FileIOUtils.writeFileFromString(whiteHostPath, hostArray);\n        //ToastUtils.showShort(\"host白名单已保存\");\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/NetWorkSummaryView.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport androidx.annotation.Nullable;\nimport android.util.AttributeSet;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.kit.network.utils.ByteUtil;\nimport com.didichuxing.doraemonkit.kit.network.utils.CostTimeUtil;\nimport com.didichuxing.doraemonkit.widget.chart.BarChart;\nimport com.didichuxing.doraemonkit.widget.chart.PieChart;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class NetWorkSummaryView extends LinearLayout {\n\n    public NetWorkSummaryView(Context context) {\n        super(context);\n        inflate(context, R.layout.dk_fragment_network_summary_page, this);\n        initView();\n    }\n\n    public NetWorkSummaryView(Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        inflate(context, R.layout.dk_fragment_network_summary_page, this);\n        initView();\n    }\n\n    private void initView() {\n        TextView totalSec = findViewById(R.id.total_sec);\n        TextView totalNumber = findViewById(R.id.total_number);\n        TextView totalUpload = findViewById(R.id.total_upload);\n        TextView totalDown = findViewById(R.id.total_down);\n\n        int postCount = NetworkManager.get().getPostCount();\n        int getCount = NetworkManager.get().getGetCount();\n\n        int totalCount = NetworkManager.get().getTotalCount();\n        totalNumber.setText(String.valueOf(totalCount));\n\n        long time = NetworkManager.get().getRunningTime();\n        totalSec.setText(CostTimeUtil.formatTime(getContext(), time));\n\n        long requestSize = NetworkManager.get().getTotalRequestSize();\n        long responseSize = NetworkManager.get().getTotalResponseSize();\n\n        totalUpload.setText(ByteUtil.getPrintSizeForSpannable(requestSize));\n        totalDown.setText(ByteUtil.getPrintSizeForSpannable(responseSize));\n\n\n        PieChart chart = findViewById(R.id.network_pier_chart);\n        List<PieChart.PieData> data = new ArrayList<>();\n        Resources resource = getResources();\n        if (postCount != 0) {\n            data.add(new PieChart.PieData(resource.getColor(R.color.dk_color_55A8FD), postCount));\n        }\n        if (getCount != 0) {\n            data.add(new PieChart.PieData(resource.getColor(R.color.dk_color_FAD337), getCount));\n        }\n        chart.setData(data);\n\n\n        BarChart barChart = findViewById(R.id.network_bar_chart);\n        barChart.setData(postCount, getResources().getColor(R.color.dk_color_55A8FD), getCount, getResources().getColor(R.color.dk_color_FAD337));\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/NetworkDetailFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.viewpager.widget.ViewPager;\n\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.titlebar.TitleBar;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @desc: 网络抓包详情页，显示request和response的详情\n */\n\npublic class NetworkDetailFragment extends BaseFragment implements View.OnClickListener {\n    private static final String TAG = \"NetworkDetailFragment\";\n\n    private ViewPager mViewPager;\n    private View mDiverRequest;\n    private View mDiverResponse;\n    private TextView mTvRequest;\n    private TextView mTvResponse;\n\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_network_monitor_detail;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n    }\n\n    @Override\n    public void onActivityCreated(@Nullable Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n    }\n\n    private void initView() {\n        mViewPager = findViewById(R.id.network_viewpager);\n        mDiverRequest = findViewById(R.id.diver_request);\n        mDiverResponse = findViewById(R.id.diver_response);\n        mTvRequest = findViewById(R.id.tv_pager_request);\n        mTvResponse = findViewById(R.id.tv_pager_response);\n        mTvRequest.setSelected(true);\n        mTvResponse.setSelected(false);\n        mTvRequest.setOnClickListener(this);\n        mTvResponse.setOnClickListener(this);\n\n        List<NetworkDetailView> views = new ArrayList<>();\n        Bundle bundle = getArguments();\n        NetworkRecord record = (NetworkRecord) bundle.getSerializable(NetworkListView.KEY_RECORD);\n        views.add(new NetworkDetailView(getContext()));\n        views.add(new NetworkDetailView(getContext()));\n        NetworkPagerAdapter adapter = new NetworkPagerAdapter(views, record);\n        mViewPager.setAdapter(adapter);\n        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {\n            @Override\n            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {\n\n            }\n\n            @Override\n            public void onPageSelected(int position) {\n                mDiverRequest.setVisibility(position == 0 ? View.VISIBLE : View.GONE);\n                mDiverResponse.setVisibility(position == 1 ? View.VISIBLE : View.GONE);\n                mTvRequest.setSelected(position == 0);\n                mTvResponse.setSelected(position == 1);\n            }\n\n            @Override\n            public void onPageScrollStateChanged(int state) {\n\n            }\n        });\n        TitleBar mTitleBar = findViewById(R.id.title_bar);\n        mTitleBar.setOnTitleBarClickListener(new TitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onLeftClick() {\n                getActivity().onBackPressed();\n            }\n\n            @Override\n            public void onRightClick() {\n\n            }\n        });\n    }\n\n    @Override\n    public boolean onBackPressed() {\n        return super.onBackPressed();\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n    }\n\n\n    @Override\n    public void onClick(View v) {\n        if (v.getId() == R.id.tv_pager_request) {\n            mViewPager.setCurrentItem(0, true);\n        } else if (v.getId() == R.id.tv_pager_response) {\n            mViewPager.setCurrentItem(1, true);\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/NetworkDetailView.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport static android.content.Context.CLIPBOARD_SERVICE;\n\nimport android.app.Activity;\nimport android.content.ClipData;\nimport android.content.ClipboardManager;\nimport android.content.Context;\nimport android.text.TextUtils;\nimport android.util.AttributeSet;\nimport android.view.View;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.FragmentActivity;\n\nimport com.didichuxing.doraemonkit.DoKitEnv;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.loginfo.LogExportDialog;\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord;\nimport com.didichuxing.doraemonkit.kit.network.bean.Request;\nimport com.didichuxing.doraemonkit.kit.network.bean.Response;\nimport com.didichuxing.doraemonkit.kit.network.utils.ByteUtil;\nimport com.didichuxing.doraemonkit.util.AppUtils;\nimport com.didichuxing.doraemonkit.util.DoKitFileUtil;\nimport com.didichuxing.doraemonkit.util.FileIOUtils;\nimport com.didichuxing.doraemonkit.util.FileUtils;\nimport com.didichuxing.doraemonkit.util.PathUtils;\nimport com.didichuxing.doraemonkit.util.ThreadUtils;\nimport com.didichuxing.doraemonkit.util.TimeUtils;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.widget.dialog.DialogProvider;\nimport com.didichuxing.doraemonkit.widget.dialog.UniversalDialogFragment;\nimport com.didichuxing.doraemonkit.widget.jsonviewer.JsonRecyclerView;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.File;\nimport java.net.URLDecoder;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\n/**\n * @desc: 显示request和response的view\n */\npublic class NetworkDetailView extends LinearLayout {\n    private SimpleDateFormat mDateFormat = new SimpleDateFormat(\"yyyy-MM-dd-HH:mm:ss:SSS\");\n\n    private TextView url;\n    private TextView method;\n    private TextView size;\n    private TextView header;\n    private TextView body;\n    private TextView time;\n\n    private TextView diverTime;\n    private TextView diverHeader;\n    private TextView diverBody;\n    /**\n     * 针对响应体 格式化\n     */\n    private TextView diverFormat;\n\n    /**\n     * 针对响应体 导出\n     */\n    private TextView diverExport;\n\n    private JsonRecyclerView jsonView;\n\n    private ClipboardManager mClipboard;\n\n    public NetworkDetailView(final Context context) {\n        super(context);\n        inflate(context, R.layout.dk_view_network_request, this);\n        mClipboard = (ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE);\n\n        url = findViewById(R.id.tv_url);\n        method = findViewById(R.id.tv_method);\n        size = findViewById(R.id.tv_data_size);\n        header = findViewById(R.id.tv_header);\n        body = findViewById(R.id.tv_body);\n        time = findViewById(R.id.tv_time);\n        diverTime = findViewById(R.id.diver_time);\n        diverHeader = findViewById(R.id.diver_header);\n        diverBody = findViewById(R.id.diver_body);\n        diverFormat = findViewById(R.id.diver_format);\n        diverExport = findViewById(R.id.diver_export);\n        jsonView = findViewById(R.id.json_body);\n\n        body.setOnLongClickListener(new OnLongClickListener() {\n            @Override\n            public boolean onLongClick(View v) {\n                ClipData clipData = ClipData.newPlainText(\"Label\", body.getText());\n                mClipboard.setPrimaryClip(clipData);\n                Toast.makeText(getContext(), \"copy success\", Toast.LENGTH_SHORT).show();\n                return false;\n            }\n        });\n    }\n\n    public NetworkDetailView(Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public void bindRequest(NetworkRecord record) {\n        diverTime.setText(R.string.dk_network_detail_title_request_time);\n        diverHeader.setText(R.string.dk_network_detail_title_request_header);\n        diverBody.setText(R.string.dk_network_detail_title_request_body);\n        diverFormat.setVisibility(View.GONE);\n        diverExport.setVisibility(View.GONE);\n        jsonView.setVisibility(View.GONE);\n        body.setVisibility(View.VISIBLE);\n        if (record.mRequest != null) {\n            Request request = record.mRequest;\n            url.setText(request.url);\n            method.setText(request.method);\n            try {\n                header.setText(URLDecoder.decode(request.headers, \"utf-8\"));\n            } catch (Exception e) {\n                header.setText(request.headers);\n            }\n            time.setText(mDateFormat.format(new Date(record.startTime)));\n            size.setText(ByteUtil.getPrintSize(record.requestLength));\n            try {\n                String strBody = TextUtils.isEmpty(request.postData) ? \"NULL\" : request.postData;\n                strBody = URLDecoder.decode(strBody, \"utf-8\");\n                body.setText(strBody);\n            } catch (Exception e) {\n                body.setText(TextUtils.isEmpty(request.postData) ? \"NULL\" : request.postData);\n            }\n        }\n    }\n\n    public void bindResponse(final NetworkRecord record) {\n        diverTime.setText(R.string.dk_network_detail_title_response_time);\n        diverHeader.setText(R.string.dk_network_detail_title_response_header);\n        diverBody.setText(R.string.dk_network_detail_title_response_body);\n        diverExport.setVisibility(View.VISIBLE);\n        diverExport.setText(\"导出\");\n        diverFormat.setVisibility(View.VISIBLE);\n        diverFormat.setText(\"unFormat\");\n        jsonView.setVisibility(View.VISIBLE);\n        jsonView.setTextSize(16.0f);\n        jsonView.setScaleEnable(false);\n        body.setVisibility(View.GONE);\n        //响应体格式化\n        diverFormat.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                if (body.getVisibility() == View.VISIBLE) {\n                    //格式化\n                    String strBody = TextUtils.isEmpty(record.mResponseBody) ? \"NULL\" : record.mResponseBody;\n                    try {\n                        new JSONObject(strBody);\n                        jsonView.setVisibility(View.VISIBLE);\n                        body.setVisibility(View.GONE);\n                        diverFormat.setText(\"unFormat\");\n                    } catch (JSONException e) {\n                        jsonView.setVisibility(View.GONE);\n                        body.setVisibility(View.VISIBLE);\n                        body.setText(strBody);\n                        diverFormat.setText(\"format\");\n                        ToastUtils.showShort(\"format error\");\n                    }\n                } else {\n                    //反格式化\n                    String strBody = TextUtils.isEmpty(record.mResponseBody) ? \"NULL\" : record.mResponseBody;\n                    body.setText(strBody);\n                    diverFormat.setText(\"format\");\n                    jsonView.setVisibility(View.GONE);\n                    body.setVisibility(View.VISIBLE);\n                }\n            }\n        });\n        //响应体导出\n        diverExport.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                //格式化\n                final String strBody = TextUtils.isEmpty(record.mResponseBody) ? \"NULL\" : record.mResponseBody;\n                if (strBody.equals(\"NULL\")) {\n                    ToastUtils.showShort(\"暂无响应体可以导出\");\n                    return;\n                }\n\n                LogExportDialog logExportDialog = new LogExportDialog(new Object(), null);\n                logExportDialog.setOnButtonClickListener(new LogExportDialog.OnButtonClickListener() {\n                    @Override\n                    public void onSaveClick(LogExportDialog dialog) {\n                        export2File(100, strBody);\n                        dialog.dismiss();\n\n                    }\n\n                    @Override\n                    public void onShareClick(LogExportDialog dialog) {\n                        export2File(101, strBody);\n                        dialog.dismiss();\n\n                    }\n                });\n                showDialog(logExportDialog);\n\n\n            }\n        });\n\n        if (record.mResponse != null) {\n            Response response = record.mResponse;\n            Request request = record.mRequest;\n            url.setText(response.url);\n            method.setText(request.method);\n            header.setText(response.headers);\n            time.setText(mDateFormat.format(new Date(record.endTime)));\n            size.setText(ByteUtil.getPrintSize(record.responseLength));\n            String strBody = TextUtils.isEmpty(record.mResponseBody) ? \"NULL\" : record.mResponseBody;\n            try {\n                new JSONObject(strBody);\n                jsonView.bindJson(strBody);\n            } catch (JSONException e) {\n                e.printStackTrace();\n                body.setVisibility(View.VISIBLE);\n                jsonView.setVisibility(View.GONE);\n                diverFormat.setText(\"format\");\n                body.setText(strBody);\n            }\n\n        }\n    }\n\n\n    /**\n     * 将响应体保存到文件\n     *\n     * @param operateType  100 保存到本地  101 保存到本地并分享\n     * @param responseBody 响应体\n     */\n    private void export2File(final int operateType, final String responseBody) {\n        ToastUtils.showShort(\"日志保存中,请稍后...\");\n        final String logPath = PathUtils.getInternalAppFilesPath() + File.separator + AppUtils.getAppName() + \"_response_\" + TimeUtils.getNowString(new SimpleDateFormat(\"yyyy-MM-dd-HH:mm:ss\")) + \".txt\";\n        final File logFile = new File(logPath);\n\n        ThreadUtils.executeByCpu(new ThreadUtils.Task<Boolean>() {\n            @Override\n            public Boolean doInBackground() throws Throwable {\n                try {\n                    FileIOUtils.writeFileFromString(logFile, responseBody, true);\n\n                    return true;\n                } catch (Exception e) {\n                    e.printStackTrace();\n                    return false;\n                }\n            }\n\n            @Override\n            public void onSuccess(Boolean result) {\n                if (result) {\n                    ToastUtils.showShort(\"文件保存在:\" + logPath);\n                    //分享\n                    if (operateType == 101) {\n                        DoKitFileUtil.systemShare(DoKitEnv.requireApp(), logFile);\n                    }\n                }\n            }\n\n            @Override\n            public void onCancel() {\n                if (logFile.exists()) {\n                    FileUtils.delete(logFile);\n                }\n                ToastUtils.showShort(\"日志保存失败\");\n            }\n\n            @Override\n            public void onFail(Throwable t) {\n                if (logFile.exists()) {\n                    FileUtils.delete(logFile);\n                }\n                ToastUtils.showShort(\"日志保存失败\");\n            }\n        });\n\n    }\n\n    private void showDialog(DialogProvider provider) {\n        if (getContext() == null || !(getContext() instanceof Activity)) {\n            return;\n        }\n\n        Activity activity = (Activity) getContext();\n\n        UniversalDialogFragment dialog = new UniversalDialogFragment();\n        provider.setHost(dialog);\n        dialog.setProvider(provider);\n        provider.show(((FragmentActivity) activity).getSupportFragmentManager());\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/NetworkListAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Filter;\nimport android.widget.Filterable;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord;\nimport com.didichuxing.doraemonkit.kit.network.bean.Request;\nimport com.didichuxing.doraemonkit.kit.network.bean.Response;\nimport com.didichuxing.doraemonkit.kit.network.utils.ByteUtil;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\n\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * @desc: 抓包列表页适配器\n */\npublic class NetworkListAdapter extends AbsRecyclerAdapter<AbsViewBinder<NetworkRecord>, NetworkRecord> implements Filterable {\n\n    private OnItemClickListener mListener;\n    private List<NetworkRecord> mSourceList = new ArrayList<>();\n\n    public NetworkListAdapter(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected AbsViewBinder<NetworkRecord> createViewHolder(View view, int viewType) {\n        return new ItemViewHolder(view);\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        return inflater.inflate(R.layout.dk_item_network_list, parent, false);\n    }\n\n    @Override\n    public Filter getFilter() {\n        return mFilter;\n    }\n\n    private class ItemViewHolder extends AbsViewBinder<NetworkRecord> {\n        private static final String METHOD_FORMAT = \"%s>%s\";\n        private static final String FLOW_FORMAT = \"↑ %s ↓%s\";\n        private static final String CODE_FORMAT = \"[%d]\";\n        private static final String UNKNOWN = \"unknown\";\n        private TextView url;\n        private TextView platform;\n        private TextView method;\n        private TextView code;\n        private TextView time;\n        private TextView flow;\n        private SimpleDateFormat mDateFormat = new SimpleDateFormat(\"yyyy-MM-dd-HH:mm:ss:SSS\");\n\n        public ItemViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            url = getView(R.id.network_list_url);\n            platform = getView(R.id.network_list_platform);\n            method = getView(R.id.network_list_method);\n            code = getView(R.id.network_list_code);\n            time = getView(R.id.network_list_time_and_cost);\n            flow = getView(R.id.network_list_flow);\n        }\n\n        @Override\n        public void bind(final NetworkRecord record) {\n            if (record.mRequest != null) {\n                Request request = record.mRequest;\n                url.setText(request.url);\n                String cost;\n                if (record.endTime < record.startTime) {\n                    cost = UNKNOWN;\n                } else {\n                    cost = ((float) (record.endTime - record.startTime)) / 1000f + \"s\";\n                }\n                String startTime = mDateFormat.format(new Date(record.startTime));\n                time.setText(getContext().getString(R.string.dk_kit_network_time_format, startTime, cost));\n            } else {\n                url.setText(UNKNOWN);\n                time.setText(getContext().getString(R.string.dk_kit_network_time_format, UNKNOWN, UNKNOWN));\n            }\n            if (record.mResponse != null && record.mRequest != null) {\n                Request request = record.mRequest;\n                Response response = record.mResponse;\n                method.setText(String.format(METHOD_FORMAT, request.method, response.mimeType));\n                code.setText(String.format(CODE_FORMAT, response.status));\n            } else {\n                code.setText(UNKNOWN);\n                method.setText(UNKNOWN);\n            }\n\n            platform.setText(String.format(\"platform: %s\", record.mPlatform));\n\n            flow.setText(String.format(FLOW_FORMAT, ByteUtil.getPrintSize(record.requestLength), ByteUtil.getPrintSize(record.responseLength)));\n            itemView.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    if (mListener != null) {\n                        mListener.onClick(record);\n                    }\n                }\n            });\n        }\n    }\n\n    private Filter mFilter = new Filter() {\n        @Override\n        protected FilterResults performFiltering(CharSequence constraint) {\n            String charString = constraint.toString();\n            List<NetworkRecord> filteredList = new ArrayList<>();\n            if (charString.isEmpty()) {\n                filteredList = mSourceList;\n            } else {\n                for (NetworkRecord record : mSourceList) {\n                    //这里根据需求，添加匹配规则\n                    if (record.filter(charString)) {\n                        filteredList.add(record);\n                    }\n                }\n            }\n\n            FilterResults filterResults = new FilterResults();\n            filterResults.values = filteredList;\n            return filterResults;\n        }\n\n        @Override\n        protected void publishResults(CharSequence constraint, FilterResults results) {\n            List<NetworkRecord> filteredList = (List<NetworkRecord>) results.values;\n            if (filteredList == null || filteredList.size() == 0) {\n                clear();\n            } else {\n                NetworkListAdapter.super.setData(filteredList);\n            }\n            notifyDataSetChanged();\n        }\n    };\n\n    @Override\n    public void setData(Collection<NetworkRecord> items) {\n        mSourceList.clear();\n        mSourceList.addAll(items);\n        super.setData(items);\n    }\n\n    public void setOnItemClickListener(OnItemClickListener listener) {\n        mListener = listener;\n    }\n\n    public interface OnItemClickListener {\n        void onClick(NetworkRecord info);\n    }\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/NetworkListView.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.text.Editable;\nimport android.text.TextWatcher;\nimport android.util.AttributeSet;\nimport android.widget.EditText;\nimport android.widget.LinearLayout;\n\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.kit.network.OnNetworkInfoUpdateListener;\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord;\nimport com.didichuxing.doraemonkit.kit.core.BaseActivity;\nimport com.didichuxing.doraemonkit.widget.recyclerview.DividerItemDecoration;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * @desc: 抓包列表\n */\npublic class NetworkListView extends LinearLayout implements OnNetworkInfoUpdateListener {\n    private static final String TAG = \"NetworkListFragment\";\n    public static final String KEY_RECORD = \"record\";\n    private RecyclerView mNetworkList;\n    private NetworkListAdapter mNetworkListAdapter;\n\n    public NetworkListView(Context context) {\n        super(context);\n        inflate(context, R.layout.dk_fragment_network_monitor_list, this);\n        initView();\n        initData();\n    }\n\n    public NetworkListView(Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        inflate(context, R.layout.dk_fragment_network_monitor_list, this);\n        initView();\n        initData();\n    }\n\n    private void initView() {\n        mNetworkList = findViewById(R.id.network_list);\n        LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());\n        mNetworkList.setLayoutManager(layoutManager);\n        mNetworkListAdapter = new NetworkListAdapter(getContext());\n        mNetworkList.setAdapter(mNetworkListAdapter);\n        DividerItemDecoration decoration = new DividerItemDecoration(DividerItemDecoration.VERTICAL);\n        decoration.setDrawable(getResources().getDrawable(R.drawable.dk_divider));\n        decoration.showHeaderDivider(true);\n        mNetworkList.addItemDecoration(decoration);\n        mNetworkListAdapter.setOnItemClickListener(new NetworkListAdapter.OnItemClickListener() {\n            @Override\n            public void onClick(NetworkRecord record) {\n                Bundle bundle = new Bundle();\n                bundle.putSerializable(KEY_RECORD, record);\n                ((BaseActivity) getContext()).showContent(NetworkDetailFragment.class, bundle);\n            }\n        });\n        ((EditText) findViewById(R.id.network_list_filter)).addTextChangedListener(mTextWatcher);\n    }\n\n    private void initData() {\n        synchronized (this) {\n            List<NetworkRecord> records=new ArrayList<>(NetworkManager.get().getRecords());\n            Collections.reverse(records);\n            mNetworkListAdapter.setData(records);\n        }\n    }\n\n    public void registerNetworkListener() {\n        NetworkManager.get().setOnNetworkInfoUpdateListener(this);\n    }\n\n    public void unRegisterNetworkListener() {\n        NetworkManager.get().setOnNetworkInfoUpdateListener(null);\n    }\n\n\n    private TextWatcher mTextWatcher = new TextWatcher() {\n        @Override\n        public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n\n        }\n\n        @Override\n        public void onTextChanged(CharSequence s, int start, int before, int count) {\n\n        }\n\n        @Override\n        public void afterTextChanged(Editable s) {\n            mNetworkListAdapter.getFilter().filter(s);\n        }\n\n    };\n\n    @Override\n    public void onNetworkInfoUpdate(NetworkRecord record, boolean add) {\n        synchronized (this) {\n            if (add) {\n                mNetworkListAdapter.append(record, 0);\n            }\n            mNetworkListAdapter.notifyDataSetChanged();\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/NetworkPagerAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.viewpager.widget.PagerAdapter;\n\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord;\n\nimport java.util.List;\n\npublic class NetworkPagerAdapter extends PagerAdapter {\n    private List<NetworkDetailView> views;\n    private NetworkRecord mRecord;\n\n    public NetworkPagerAdapter(List<NetworkDetailView> views, NetworkRecord record) {\n        this.views = views;\n        mRecord = record;\n    }\n\n    @Override\n    public int getCount() {\n        return views.size();\n    }\n\n    @Override\n    public boolean isViewFromObject(View view, Object object) {\n        return view == object;\n    }\n\n    @Override\n    public Object instantiateItem(ViewGroup container, int position) {\n        NetworkDetailView view = views.get(position);\n        if (position == 0) {\n            view.bindRequest(mRecord);\n        } else {\n            view.bindResponse(mRecord);\n        }\n        container.addView(view);\n        return view;\n    }\n\n    @Override\n    public void destroyItem(ViewGroup container, int position, Object object) {\n        container.removeView(views.get(position));\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/TemplateDetailNodeProvider.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.android.volley.Request;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.JsonObjectRequest;\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager;\nimport com.didichuxing.doraemonkit.kit.network.room_db.MockTemplateApiBean;\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil;\nimport com.didichuxing.doraemonkit.util.LogHelper;\nimport com.didichuxing.doraemonkit.volley.VolleyManager;\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.BaseNode;\nimport com.didichuxing.doraemonkit.widget.brvah.provider.BaseNodeProvider;\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder;\nimport com.didichuxing.doraemonkit.widget.jsonviewer.JsonRecyclerView;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/30-15:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class TemplateDetailNodeProvider extends BaseNodeProvider {\n    private static final String TAG = \"TemplateDetailNodeProvider\";\n\n    @Override\n    public int getItemViewType() {\n        return TemplateMockAdapter.TYPE_CONTENT;\n    }\n\n    @Override\n    public int getLayoutId() {\n        return R.layout.dk_mock_template_content_item;\n    }\n\n    @Override\n    public void convert(BaseViewHolder holder, BaseNode item) {\n        if (item instanceof MockTemplateApiBean) {\n            final MockTemplateApiBean mockApi = (MockTemplateApiBean) item;\n            holder.setText(R.id.tv_path, \"path:\" + mockApi.getPath());\n            JsonRecyclerView jsonQuery = holder.getView(R.id.jsonviewer_query);\n            JsonRecyclerView jsonBody = holder.getView(R.id.jsonviewer_body);\n\n            try {\n                holder.getView(R.id.rl_query).setVisibility(View.VISIBLE);\n                JSONObject jsonObject = new JSONObject(mockApi.getQuery());\n                if (jsonObject.length() == 0) {\n                    holder.getView(R.id.rl_query).setVisibility(View.GONE);\n                } else {\n                    jsonQuery.bindJson(mockApi.getQuery());\n                }\n            } catch (JSONException e) {\n                e.printStackTrace();\n                holder.getView(R.id.rl_query).setVisibility(View.GONE);\n            }\n\n            try {\n                holder.getView(R.id.rl_body).setVisibility(View.VISIBLE);\n                JSONObject jsonObject = new JSONObject(mockApi.getBody());\n                if (jsonObject.length() == 0) {\n                    holder.getView(R.id.rl_body).setVisibility(View.GONE);\n                } else {\n                    jsonBody.bindJson(mockApi.getBody());\n                }\n            } catch (JSONException e) {\n                e.printStackTrace();\n                holder.getView(R.id.rl_body).setVisibility(View.GONE);\n            }\n\n            holder.setText(R.id.tv_group, \"group:\" + mockApi.getGroup());\n            holder.setText(R.id.tv_create, \"create person:\" + mockApi.getCreatePerson());\n            holder.setText(R.id.tv_modify, \"modify person:\" + mockApi.getModifyPerson());\n            final TextView tvView = holder.getView(R.id.tv_view);\n            tvView.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    if (TextUtils.isEmpty(mockApi.getStrResponse())) {\n                        ToastUtils.showShort(\"no mock template data\");\n                        return;\n                    }\n                    //保存到全局\n                    DokitDbManager.getInstance().setGlobalTemplateApiBean(mockApi);\n\n                    DoKit.launchFullScreen(MockTemplatePreviewFragment.class, tvView.getContext());\n\n                }\n            });\n            TextView tvUpload = holder.getView(R.id.tv_upload);\n            tvUpload.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    Map<String, String> values = new HashMap<>();\n                    values.put(\"projectId\", mockApi.getProjectId());\n                    values.put(\"id\", mockApi.getId());\n                    values.put(\"tempData\", mockApi.getStrResponse());\n\n                    Request<JSONObject> request = new JsonObjectRequest(Request.Method.PATCH, TemplateMockAdapter.TEMPLATER_UPLOAD_URL, new JSONObject(values), new com.android.volley.Response.Listener<JSONObject>() {\n                        @Override\n                        public void onResponse(JSONObject response) {\n                            ToastUtils.showShort(\"upload template succeed\");\n                            LogHelper.i(TAG, \"上传模板===>\" + response.toString());\n                        }\n                    }, new com.android.volley.Response.ErrorListener() {\n\n                        @Override\n                        public void onErrorResponse(VolleyError error) {\n                            ToastUtils.showShort(\"upload template failed\");\n                            LogHelper.e(TAG, \"error===>\" + error.getMessage());\n                        }\n                    });\n\n                    VolleyManager.INSTANCE.add(request);\n                }\n            });\n\n            TextView tvHasLocalMockData = holder.getView(R.id.tv_local_has_mock_template);\n            String hasLocalMockData;\n            if (!TextUtils.isEmpty(mockApi.getStrResponse())) {\n                hasLocalMockData = \"Y\";\n                tvUpload.setClickable(true);\n                tvUpload.setTextColor(tvUpload.getContext().getResources().getColor(R.color.dk_color_337CC4));\n            } else {\n                hasLocalMockData = \"N\";\n                tvUpload.setClickable(false);\n                tvUpload.setTextColor(tvUpload.getContext().getResources().getColor(R.color.dk_color_999999));\n            }\n            tvHasLocalMockData.setText(String.format(DoKitCommUtil.getString(R.string.dk_data_mock_template_tip), hasLocalMockData));\n\n        }\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/TemplateMockAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.widget.brvah.BaseNodeAdapter;\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.BaseNode;\nimport com.didichuxing.doraemonkit.widget.brvah.module.LoadMoreModule;\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.kit.network.bean.MockTemplateTitleBean;\nimport com.didichuxing.doraemonkit.kit.network.room_db.MockTemplateApiBean;\n\nimport java.util.List;\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-12-15:04\n * 描    述：mock adapter\n * 修订历史：\n * ================================================\n */\npublic class TemplateMockAdapter extends BaseNodeAdapter implements LoadMoreModule {\n    public static String TEMPLATER_UPLOAD_URL = NetworkManager.MOCK_DOMAIN + \"/api/app/interface\";\n    public static final String TAG = \"InterceptMockAdapter\";\n    public final static int TYPE_TITLE = 100;\n    public final static int TYPE_CONTENT = 200;\n\n    public TemplateMockAdapter(List<BaseNode> nodeList) {\n        super(nodeList);\n        addFullSpanNodeProvider(new TemplateTitleNodeProvider());\n        addNodeProvider(new TemplateDetailNodeProvider());\n    }\n\n    @Override\n    protected int getItemType(@NonNull List<? extends BaseNode> data, int position) {\n        BaseNode node = data.get(position);\n        if (node instanceof MockTemplateTitleBean) {\n            return TemplateMockAdapter.TYPE_TITLE;\n        } else if (node instanceof MockTemplateApiBean) {\n            return TemplateMockAdapter.TYPE_CONTENT;\n        }\n        return -1;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/TemplateTitleNodeProvider.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport android.view.View;\nimport android.widget.CheckBox;\nimport android.widget.CompoundButton;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.network.bean.MockTemplateTitleBean;\nimport com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager;\nimport com.didichuxing.doraemonkit.kit.network.room_db.MockTemplateApiBean;\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.BaseNode;\nimport com.didichuxing.doraemonkit.widget.brvah.provider.BaseNodeProvider;\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder;\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/30-15:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class TemplateTitleNodeProvider extends BaseNodeProvider {\n    @Override\n    public int getItemViewType() {\n        return TemplateMockAdapter.TYPE_TITLE;\n    }\n\n    @Override\n    public int getLayoutId() {\n        return R.layout.dk_mock_title_item;\n    }\n\n\n    @Override\n    public void convert(@NonNull BaseViewHolder holder, BaseNode item) {\n        if (item instanceof MockTemplateTitleBean) {\n            final MockTemplateTitleBean mockTitleBean = (MockTemplateTitleBean) item;\n            MockTemplateApiBean mockApi = (MockTemplateApiBean) mockTitleBean.getChildNode().get(0);\n\n            holder.setText(R.id.tv_title, mockTitleBean.getName());\n            if (mockTitleBean.isExpanded()) {\n                holder.setImageResource(R.id.iv_more, R.mipmap.dk_arrow_open);\n            } else {\n                holder.setImageResource(R.id.iv_more, R.mipmap.dk_arrow_normal);\n            }\n            CheckBox checkBox = holder.getView(R.id.menu_switch);\n            //建议将setOnCheckedChangeListener放在控件checkBox.setChecked前面 否则代码设置选中时会触发回调导致状态不正确\n            checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {\n                @Override\n                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {\n                    MockTemplateApiBean mockApi = (MockTemplateApiBean) mockTitleBean.getChildNode().get(0);\n                    mockApi.setOpen(isChecked);\n                    DokitDbManager.getInstance().updateTemplateApi(mockApi);\n\n                }\n            });\n            if (mockApi.isOpen()) {\n                checkBox.setChecked(true);\n            } else {\n                checkBox.setChecked(false);\n            }\n        }\n\n    }\n\n    @Override\n    public void onClick(BaseViewHolder holder, View view, BaseNode data, int position) {\n        super.onClick(holder, view, data, position);\n        if (data instanceof MockTemplateTitleBean && getAdapter() != null) {\n            getAdapter().expandOrCollapse(position);\n            final MockTemplateTitleBean mockTitleBean = (MockTemplateTitleBean) data;\n            if (mockTitleBean.isExpanded()) {\n                holder.setImageResource(R.id.iv_more, R.mipmap.dk_arrow_normal);\n            } else {\n                holder.setImageResource(R.id.iv_more, R.mipmap.dk_arrow_open);\n            }\n        }\n\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/ui/WhiteHostAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.ui;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.text.Editable;\nimport android.text.TextUtils;\nimport android.text.TextWatcher;\nimport android.view.View;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.network.bean.WhiteHostBean;\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil;\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter;\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder;\n\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/19-14:41\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class WhiteHostAdapter extends BaseQuickAdapter<WhiteHostBean, BaseViewHolder> {\n\n\n    WhiteHostAdapter(int layoutResId, @Nullable List<WhiteHostBean> data) {\n        super(layoutResId, data);\n    }\n\n    @Override\n    protected void convert(@NonNull final BaseViewHolder helper, final WhiteHostBean item) {\n        if (item.isCanAdd()) {\n            helper.<TextView>getView(R.id.tv_add).setText(\"+\");\n        } else {\n            helper.<TextView>getView(R.id.tv_add).setText(\"-\");\n        }\n        helper.<EditText>getView(R.id.ed_host).setText(item.getHost());\n        helper.<EditText>getView(R.id.ed_host).addTextChangedListener(new TextWatcher() {\n            @Override\n            public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n\n            }\n\n            @Override\n            public void onTextChanged(CharSequence s, int start, int before, int count) {\n\n            }\n\n            @Override\n            public void afterTextChanged(Editable s) {\n                if (s != null) {\n                    item.setHost(s.toString());\n                }\n            }\n        });\n        helper.getView(R.id.fl_add_wrap).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n\n                List<WhiteHostBean> hostBeans = getData();\n                String text = helper.<TextView>getView(R.id.tv_add).getText().toString();\n                if (text.equals(\"+\")) {\n                    String editText = helper.<EditText>getView(R.id.ed_host).getText().toString();\n                    if (TextUtils.isEmpty(editText)) {\n                        ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_kit_net_monitor_white_host_edit_toast));\n                        return;\n                    }\n                    for (WhiteHostBean hostBean : hostBeans) {\n                        hostBean.setCanAdd(false);\n                    }\n                    hostBeans.add(new WhiteHostBean(\"\", true));\n                } else {\n                    hostBeans.remove(item);\n                }\n\n                notifyDataSetChanged();\n            }\n        });\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/utils/ByteUtil.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.utils;\n\nimport android.text.SpannableString;\nimport android.text.Spanned;\nimport android.text.style.RelativeSizeSpan;\n\npublic class ByteUtil {\n    public static String getPrintSize(long size) {\n        if (size < 1024) {\n            return String.valueOf(size) + \"B\";\n        } else {\n            size = size / 1024;\n        }\n        if (size < 1024) {\n            return String.valueOf(size) + \"KB\";\n        } else {\n            size = size / 1024;\n        }\n        if (size < 1024) {\n            size = size * 100;\n            return String.valueOf((size / 100)) + \".\"\n                    + String.valueOf((size % 100)) + \"MB\";\n        } else {\n            size = size * 100 / 1024;\n            return String.valueOf((size / 100)) + \".\"\n                    + String.valueOf((size % 100)) + \"GB\";\n        }\n    }\n\n    public static SpannableString getPrintSizeForSpannable(long size) {\n        SpannableString spannableString;\n        RelativeSizeSpan sizeSpan = new RelativeSizeSpan(0.5f);\n        if (size < 1024) {\n            spannableString = new SpannableString(String.valueOf(size) + \"B\");\n            spannableString.setSpan(sizeSpan, spannableString.length() - 1, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n            return spannableString;\n        } else {\n            size = size / 1024;\n        }\n        if (size < 1024) {\n            spannableString = new SpannableString(String.valueOf(size) + \"KB\");\n            spannableString.setSpan(sizeSpan, spannableString.length() - 2, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n            return spannableString;\n        } else {\n            size = size / 1024;\n        }\n        if (size < 1024) {\n            size = size * 100;\n            String string = String.valueOf((size / 100)) + \".\"\n                    + String.valueOf((size % 100)) + \"MB\";\n            spannableString = new SpannableString(string);\n            spannableString.setSpan(sizeSpan, spannableString.length() - 2, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n            return spannableString;\n        } else {\n            size = size * 100 / 1024;\n            String string = String.valueOf((size / 100)) + \".\"\n                    + String.valueOf((size % 100)) + \"GB\";\n            spannableString = new SpannableString(string);\n            spannableString.setSpan(sizeSpan, spannableString.length() - 2, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n            return spannableString;\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/utils/CostTimeUtil.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.utils;\n\nimport android.content.Context;\nimport android.text.SpannableString;\nimport android.text.Spanned;\nimport android.text.style.RelativeSizeSpan;\n\nimport com.didichuxing.doraemonkit.R;\n\npublic class CostTimeUtil {\n    public final static long SECOND = 1000;\n    public final static long MINUTE = SECOND * 60;\n    public final static long HOUR = MINUTE * 60;\n    public final static long DAY = HOUR * 24;\n\n\n    public static SpannableString formatTime(Context context, long time) {\n        SpannableString spannableString;\n        if (time == 0) {\n            RelativeSizeSpan sizeSpan = new RelativeSizeSpan(0.5f);\n            spannableString = new SpannableString(context.getString(R.string.dk_network_summary_total_time_default));\n            spannableString.setSpan(sizeSpan, spannableString.length() - 1, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n        } else if (time < 100 * SECOND) {\n            RelativeSizeSpan sizeSpan = new RelativeSizeSpan(0.5f);\n            spannableString = new SpannableString(context.getString(R.string.dk_network_summary_total_time_second, time / SECOND));\n            spannableString.setSpan(sizeSpan, spannableString.length() - 1, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n        } else if (time < 100 * MINUTE) {\n            long minute = time / MINUTE;\n            long second = time % MINUTE / SECOND;\n            spannableString = new SpannableString(context.getString(R.string.dk_network_summary_total_time_minute, minute, second));\n            RelativeSizeSpan sizeSpan = new RelativeSizeSpan(0.5f);\n            spannableString.setSpan(sizeSpan, String.valueOf(minute).length(), String.valueOf(minute).length() + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n            sizeSpan = new RelativeSizeSpan(0.5f);\n            spannableString.setSpan(sizeSpan, spannableString.length() - 1, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n        } else if (time < 100 * HOUR) {\n            long hour = time / HOUR;\n            long minute = time % HOUR / MINUTE;\n            spannableString = new SpannableString(context.getString(R.string.dk_network_summary_total_time_hour, hour, minute));\n            RelativeSizeSpan sizeSpan = new RelativeSizeSpan(0.5f);\n            spannableString.setSpan(sizeSpan, String.valueOf(hour).length(), String.valueOf(hour).length() + 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n            sizeSpan = new RelativeSizeSpan(0.5f);\n            spannableString.setSpan(sizeSpan, spannableString.length() - 1, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n        } else {\n            long day = time / DAY;\n            long hour = time % DAY / HOUR;\n            spannableString = new SpannableString(context.getString(R.string.dk_network_summary_total_time_day, day, hour));\n            RelativeSizeSpan sizeSpan = new RelativeSizeSpan(0.5f);\n            spannableString.setSpan(sizeSpan, String.valueOf(day).length(), String.valueOf(day).length() + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n            sizeSpan = new RelativeSizeSpan(0.5f);\n            spannableString.setSpan(sizeSpan, spannableString.length() - 2, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n        }\n        return spannableString;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/utils/ExceptionUtil.java",
    "content": "/*\n * Copyright (c) 2014-present, Facebook, Inc.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree. An additional grant\n * of patent rights can be found in the PATENTS file in the same directory.\n */\n\npackage com.didichuxing.doraemonkit.kit.network.utils;\n\npublic class ExceptionUtil {\n  @SuppressWarnings(\"unchecked\")\n  public static <T extends Throwable> void propagateIfInstanceOf(Throwable t, Class<T> type)\n      throws T {\n    if (type.isInstance(t)) {\n      throw (T)t;\n    }\n  }\n\n  public static RuntimeException propagate(Throwable t) {\n    propagateIfInstanceOf(t, Error.class);\n    propagateIfInstanceOf(t, RuntimeException.class);\n    throw new RuntimeException(t);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  public static <T extends Throwable> void sneakyThrow(Throwable t) throws T {\n    throw (T)t;\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/utils/OkHttpResponse.kt",
    "content": "@file:RestrictTo(RestrictTo.Scope.LIBRARY)\n\npackage com.didichuxing.doraemonkit.kit.network.utils\n\nimport androidx.annotation.RestrictTo\nimport com.didichuxing.doraemonkit.okhttp_api.OkHttpWrap\nimport okhttp3.Response\nimport okio.Buffer\nimport okio.GzipSource\nimport java.nio.charset.Charset\n\ninternal fun Response.encoding() =\n    this.header(\"content-encoding\") ?: this.header(\"Content-Encoding\")\n\ninternal fun Response.charset(): Charset {\n    this.encoding()\n        ?.takeIf { Charset.isSupported(it) }\n        ?.also {\n            return Charset.forName(it)\n        }\n    return OkHttpWrap.toResponseBody(this)?.contentType()?.charset() ?: Charset.defaultCharset()\n}\n\ninternal fun Response.bodyContent(): String = OkHttpWrap.toResponseBody(this)\n    ?.let { body ->\n        val source = body.source()\n            .apply {\n                request(Long.MAX_VALUE)\n            }\n        var buffer = source.buffer\n        val encoding = encoding()\n        if (\"gzip\".equals(encoding, true)) {\n            GzipSource(buffer.clone()).use { gzippedBody ->\n                buffer = Buffer().also { it.writeAll(gzippedBody) }\n            }\n        }\n        buffer\n    }\n    ?.clone()\n    ?.readString(charset())\n    ?: \"\"\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/utils/StreamUtil.java",
    "content": "/*\n * Copyright (c) 2014-present, Facebook, Inc.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree. An additional grant\n * of patent rights can be found in the PATENTS file in the same directory.\n */\n\npackage com.didichuxing.doraemonkit.kit.network.utils;\n\nimport android.util.Pair;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\npublic class StreamUtil {\n\n    public static void copy(InputStream input, OutputStream output, byte[] buffer)\n            throws IOException {\n        int n;\n        while ((n = input.read(buffer)) != -1) {\n            output.write(buffer, 0, n);\n        }\n    }\n\n    public static void close(Closeable closeable, boolean hideException) throws IOException {\n        if (closeable != null) {\n            if (hideException) {\n                try {\n                    closeable.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            } else {\n                closeable.close();\n            }\n        }\n    }\n\n    public static ArrayList<Pair<String, String>> convertHeaders(Map<String, List<String>> map) {\n        ArrayList<Pair<String, String>> array = new ArrayList<Pair<String, String>>();\n        if (map == null) {\n            return array;\n        }\n        for (Map.Entry<String, List<String>> mapEntry : map.entrySet()) {\n            for (String mapEntryValue : mapEntry.getValue()) {\n                // HttpURLConnection puts a weird null entry in the header map that corresponds to\n                // the HTTP response line (for instance, HTTP/1.1 200 OK).  Ignore that weirdness...\n                if (mapEntry.getKey() != null) {\n                    array.add(Pair.create(mapEntry.getKey(), mapEntryValue));\n                }\n            }\n        }\n        return array;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/utils/Utf8Charset.java",
    "content": "/*\n * Copyright (c) 2014-present, Facebook, Inc.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree. An additional grant\n * of patent rights can be found in the PATENTS file in the same directory.\n */\n\npackage com.didichuxing.doraemonkit.kit.network.utils;\n\nimport java.nio.charset.Charset;\n\npublic class Utf8Charset {\n\n  public static final String NAME = \"UTF-8\";\n  public static final Charset INSTANCE = Charset.forName(NAME);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/parameter/AbsParameterFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.parameter;\n\nimport android.content.pm.PackageManager;\nimport android.os.Bundle;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\nimport androidx.core.app.ActivityCompat;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.view.View;\nimport android.widget.CheckBox;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.kit.performance.PerformanceDokitViewManager;\nimport com.didichuxing.doraemonkit.kit.performance.PerformanceFragmentCloseListener;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n\npublic abstract class AbsParameterFragment extends BaseFragment implements PerformanceFragmentCloseListener {\n\n\n    private SettingItemAdapter mSettingItemAdapter;\n    private RecyclerView mSettingList;\n    private static final String[] PERMISSIONS_STORAGE = {\n            \"android.permission.READ_EXTERNAL_STORAGE\",\n            \"android.permission.WRITE_EXTERNAL_STORAGE\"};\n    private static final int REQUEST_EXTERNAL_STORAGE = 2;\n\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_parameter;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n    }\n\n    @StringRes\n    protected abstract int getTitle();\n\n\n    protected abstract int getPerformanceType();\n\n    protected abstract Collection<SettingItem> getSettingItems(List<SettingItem> list);\n\n    protected abstract SettingItemAdapter.OnSettingItemSwitchListener getItemSwitchListener();\n\n    protected abstract SettingItemAdapter.OnSettingItemClickListener getItemClickListener();\n\n    protected void openChartPage(@StringRes int title, int type) {\n\n        PerformanceDokitViewManager.open(type, getString(title), this);\n    }\n\n\n    protected void closeChartPage() {\n        PerformanceDokitViewManager.close(getPerformanceType(), getString(getTitle()));\n        //RealTimeChartDokitView.closeChartPage();\n    }\n\n    private void initView() {\n        HomeTitleBar titleBar = findViewById(R.id.title_bar);\n        titleBar.setTitle(getTitle());\n        titleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                getActivity().finish();\n            }\n        });\n\n        mSettingList = findViewById(R.id.setting_list);\n        mSettingList.setLayoutManager(new LinearLayoutManager(getContext()));\n        mSettingItemAdapter = new SettingItemAdapter(getContext());\n        mSettingItemAdapter.append(getSettingItems(new ArrayList<SettingItem>()));\n\n        mSettingItemAdapter.setOnSettingItemSwitchListener(new SettingItemAdapter.OnSettingItemSwitchListener() {\n            @Override\n            public void onSettingItemSwitch(View view, SettingItem data, boolean on) {\n                if (on && !ownPermissionCheck()) {\n                    if (view instanceof CheckBox) {\n                        ((CheckBox) view).setChecked(false);\n                    }\n                    requestPermissions(PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);\n                    return;\n                }\n                SettingItemAdapter.OnSettingItemSwitchListener itemSwitchListener = getItemSwitchListener();\n                if (itemSwitchListener != null) {\n                    itemSwitchListener.onSettingItemSwitch(view, data, on);\n                }\n            }\n        });\n        mSettingItemAdapter.setOnSettingItemClickListener(new SettingItemAdapter.OnSettingItemClickListener() {\n            @Override\n            public void onSettingItemClick(View view, SettingItem data) {\n                if (!ownPermissionCheck()) {\n                    requestPermissions(PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);\n                    return;\n                }\n                SettingItemAdapter.OnSettingItemClickListener itemClickListener = getItemClickListener();\n                if (itemClickListener != null) {\n                    itemClickListener.onSettingItemClick(view, data);\n                }\n\n            }\n        });\n        mSettingList.setAdapter(mSettingItemAdapter);\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {\n        if (requestCode == REQUEST_EXTERNAL_STORAGE) {\n            for (int grantResult : grantResults) {\n                if (grantResult == -1) {\n                    ToastUtils.showShort(R.string.dk_error_tips_permissions_less);\n                }\n            }\n        }\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n    }\n\n    private boolean ownPermissionCheck() {\n\n        int permission = ActivityCompat.checkSelfPermission(getActivity(), \"android.permission.WRITE_EXTERNAL_STORAGE\");\n        if (permission != PackageManager.PERMISSION_GRANTED) {\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public void onClose(int performanceType) {\n        if (performanceType != getPerformanceType()) {\n            return;\n        }\n        if (mSettingList == null || mSettingList.isComputingLayout()) {\n            return;\n        }\n        if (mSettingItemAdapter == null) {\n            return;\n        }\n        if (!mSettingItemAdapter.getData().get(0).isChecked) {\n            return;\n        }\n        mSettingItemAdapter.getData().get(0).isChecked = false;\n        mSettingItemAdapter.notifyItemChanged(0);\n    }\n\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mSettingItemAdapter = null;\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        //移除监听\n        PerformanceDokitViewManager.onPerformanceSettingFragmentDestroy(this);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/parameter/cpu/CpuKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.parameter.cpu\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n@AutoService(AbstractKit::class)\nclass CpuKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_frameinfo_cpu\n    override val icon: Int\n        get() = R.mipmap.dk_cpu\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        startUniversalActivity(CpuMainPageFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_performance_ck_cpu\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/parameter/cpu/CpuMainPageFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.parameter.cpu;\n\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.DokitMemoryConfig;\nimport com.didichuxing.doraemonkit.constant.BundleKey;\nimport com.didichuxing.doraemonkit.kit.performance.PerformanceDataManager;\nimport com.didichuxing.doraemonkit.kit.performance.PerformanceFragment;\nimport com.didichuxing.doraemonkit.kit.parameter.AbsParameterFragment;\nimport com.didichuxing.doraemonkit.kit.performance.datasource.DataSourceFactory;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter;\n\nimport java.util.Collection;\nimport java.util.List;\n\npublic class CpuMainPageFragment extends AbsParameterFragment {\n    private static final String TAG = \"CpuMainPageFragment\";\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        PerformanceDataManager.getInstance().init();\n    }\n\n    @Override\n    protected int getTitle() {\n        return R.string.dk_frameinfo_cpu;\n    }\n\n    @Override\n    protected int getPerformanceType() {\n        return DataSourceFactory.TYPE_CPU;\n    }\n\n    @Override\n    protected Collection<SettingItem> getSettingItems(List<SettingItem> list) {\n        list.add(new SettingItem(R.string.dk_cpu_detection_switch, DokitMemoryConfig.CPU_STATUS));\n        list.add(new SettingItem(R.string.dk_item_cache_log, R.mipmap.dk_more_icon));\n        return list;\n    }\n\n    @Override\n    protected SettingItemAdapter.OnSettingItemSwitchListener getItemSwitchListener() {\n        return new SettingItemAdapter.OnSettingItemSwitchListener() {\n            @Override\n            public void onSettingItemSwitch(View view, SettingItem data, boolean on) {\n                if (on) {\n                    startMonitor();\n                } else {\n                    stopMonitor();\n                }\n                DokitMemoryConfig.CPU_STATUS = on;\n\n            }\n        };\n    }\n\n    @Override\n    protected SettingItemAdapter.OnSettingItemClickListener getItemClickListener() {\n        return new SettingItemAdapter.OnSettingItemClickListener() {\n            @Override\n            public void onSettingItemClick(View view, SettingItem data) {\n                if (data.desc == R.string.dk_item_cache_log) {\n                    Bundle bundle = new Bundle();\n                    bundle.putInt(BundleKey.PERFORMANCE_TYPE, PerformanceFragment.CPU);\n                    showContent(PerformanceFragment.class, bundle);\n                }\n            }\n        };\n    }\n\n    private void startMonitor() {\n\n        PerformanceDataManager.getInstance().startMonitorCPUInfo();\n        openChartPage(R.string.dk_frameinfo_cpu, DataSourceFactory.TYPE_CPU);\n    }\n\n    private void stopMonitor() {\n        PerformanceDataManager.getInstance().stopMonitorCPUInfo();\n        closeChartPage();\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/parameter/frameInfo/FrameInfoFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.parameter.frameInfo;\n\nimport android.os.Bundle;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.DokitMemoryConfig;\nimport com.didichuxing.doraemonkit.constant.BundleKey;\nimport com.didichuxing.doraemonkit.kit.performance.PerformanceDataManager;\nimport com.didichuxing.doraemonkit.kit.performance.PerformanceFragment;\nimport com.didichuxing.doraemonkit.kit.parameter.AbsParameterFragment;\nimport com.didichuxing.doraemonkit.kit.performance.datasource.DataSourceFactory;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * Created by wanglikun on 2018/9/13.\n */\n\npublic class FrameInfoFragment extends AbsParameterFragment {\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        PerformanceDataManager.getInstance().init();\n    }\n\n    @Override\n    protected int getTitle() {\n        return R.string.dk_kit_frame_info_desc;\n    }\n\n    @Override\n    protected int getPerformanceType() {\n        return DataSourceFactory.TYPE_FPS;\n    }\n\n    @Override\n    protected Collection<SettingItem> getSettingItems(List<SettingItem> list) {\n        list.add(new SettingItem(R.string.dk_frameinfo_detection_switch, DokitMemoryConfig.FPS_STATUS));\n        list.add(new SettingItem(R.string.dk_item_cache_log, R.mipmap.dk_more_icon));\n        return list;\n    }\n\n    @Override\n    protected SettingItemAdapter.OnSettingItemSwitchListener getItemSwitchListener() {\n        return new SettingItemAdapter.OnSettingItemSwitchListener() {\n            @Override\n            public void onSettingItemSwitch(View view, SettingItem data, boolean on) {\n                if (on) {\n                    startMonitor();\n                } else {\n                    stopMonitor();\n                }\n\n                DokitMemoryConfig.FPS_STATUS = on;\n            }\n        };\n    }\n\n    @Override\n    protected SettingItemAdapter.OnSettingItemClickListener getItemClickListener() {\n        return new SettingItemAdapter.OnSettingItemClickListener() {\n            @Override\n            public void onSettingItemClick(View view, SettingItem data) {\n                if (data.desc == R.string.dk_item_cache_log) {\n\n                    Bundle bundle = new Bundle();\n                    bundle.putInt(BundleKey.PERFORMANCE_TYPE, PerformanceFragment.FPS);\n                    showContent(PerformanceFragment.class, bundle);\n                }\n\n            }\n        };\n    }\n\n    private void startMonitor() {\n        PerformanceDataManager.getInstance().startMonitorFrameInfo();\n        openChartPage(R.string.dk_kit_frame_info_desc, DataSourceFactory.TYPE_FPS);\n    }\n\n    private void stopMonitor() {\n        PerformanceDataManager.getInstance().stopMonitorFrameInfo();\n        closeChartPage();\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/parameter/frameInfo/FrameInfoKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.parameter.frameInfo\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n/**\n * Created by wanglikun on 2018/9/13.\n */\n@AutoService(AbstractKit::class)\nclass FrameInfoKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_frame_info\n    override val icon: Int\n        get() = R.mipmap.dk_frame_hist\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        startUniversalActivity(FrameInfoFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_performance_ck_fps\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/parameter/ram/RamKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.parameter.ram\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n@AutoService(AbstractKit::class)\nclass RamKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_frameinfo_ram\n    override val icon: Int\n        get() = R.mipmap.dk_ram\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        startUniversalActivity(RamMainPageFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_performance_ck_arm\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/parameter/ram/RamMainPageFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.parameter.ram;\n\nimport android.os.Bundle;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.DokitMemoryConfig;\nimport com.didichuxing.doraemonkit.constant.BundleKey;\nimport com.didichuxing.doraemonkit.kit.parameter.AbsParameterFragment;\nimport com.didichuxing.doraemonkit.kit.performance.PerformanceDataManager;\nimport com.didichuxing.doraemonkit.kit.performance.PerformanceFragment;\nimport com.didichuxing.doraemonkit.kit.performance.datasource.DataSourceFactory;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter;\n\nimport java.util.Collection;\nimport java.util.List;\n\npublic class RamMainPageFragment extends AbsParameterFragment {\n    private static final String TAG = \"RamMainPageFragment\";\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        PerformanceDataManager.getInstance().init();\n    }\n\n    @Override\n    protected int getTitle() {\n        return R.string.dk_ram_detection_title;\n    }\n\n    @Override\n    protected int getPerformanceType() {\n        return DataSourceFactory.TYPE_RAM;\n    }\n\n    @Override\n    protected Collection<SettingItem> getSettingItems(List<SettingItem> list) {\n        list.add(new SettingItem(R.string.dk_ram_detection_switch, DokitMemoryConfig.RAM_STATUS));\n        list.add(new SettingItem(R.string.dk_item_cache_log, R.mipmap.dk_more_icon));\n        return list;\n    }\n\n    @Override\n    protected SettingItemAdapter.OnSettingItemSwitchListener getItemSwitchListener() {\n        return new SettingItemAdapter.OnSettingItemSwitchListener() {\n            @Override\n            public void onSettingItemSwitch(View view, SettingItem data, boolean on) {\n                if (on) {\n                    startMonitor();\n                } else {\n                    stopMonitor();\n                }\n                DokitMemoryConfig.RAM_STATUS = on;\n            }\n\n        };\n    }\n\n    @Override\n    protected SettingItemAdapter.OnSettingItemClickListener getItemClickListener() {\n        return new SettingItemAdapter.OnSettingItemClickListener() {\n            @Override\n            public void onSettingItemClick(View view, SettingItem data) {\n                if (data.desc == R.string.dk_item_cache_log) {\n                    Bundle bundle = new Bundle();\n                    bundle.putInt(BundleKey.PERFORMANCE_TYPE, PerformanceFragment.RAM);\n                    showContent(PerformanceFragment.class, bundle);\n\n                }\n            }\n        };\n    }\n\n    protected void startMonitor() {\n        PerformanceDataManager.getInstance().startMonitorMemoryInfo();\n        openChartPage(R.string.dk_ram_detection_title, DataSourceFactory.TYPE_RAM);\n    }\n\n    protected void stopMonitor() {\n        PerformanceDataManager.getInstance().stopMonitorMemoryInfo();\n        closeChartPage();\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/PerformanceCloseDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance;\n\nimport android.content.Context;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-10-11-16:05\n * 描    述：性能监控 帧率、 CPU、RAM、流量监控统一显示的DokitView 关闭按钮 因为系统模式下 设置不响应事件  需要在盖一层用来响应事件\n * 修订历史：\n * ================================================\n */\npublic class PerformanceCloseDoKitView extends AbsDoKitView {\n    LinearLayout mLlCloseWrap;\n    FrameLayout mWrap0, mWrap1, mWrap2, mWrap3;\n    ImageView mIvClose0, mIvClose1, mIvClose2, mIvClose3;\n    PerformanceCloseListener mPerformanceCloseListener;\n\n    @Override\n    public void onCreate(Context context) {\n\n    }\n\n    @Override\n    public View onCreateView(Context context, FrameLayout rootView) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_performance_close_wrap, rootView, false);\n    }\n\n    public void addItem(int index, int performanceType) {\n        if (mLlCloseWrap == null) {\n            return;\n        }\n        FrameLayout closeViewWrap = (FrameLayout) mLlCloseWrap.getChildAt(index);\n        closeViewWrap.setVisibility(View.VISIBLE);\n        closeViewWrap.setTag(performanceType);\n\n    }\n\n    public void removeItem(int index) {\n        FrameLayout closeViewWrap = (FrameLayout) mLlCloseWrap.getChildAt(index);\n        closeViewWrap.setVisibility(View.GONE);\n        closeViewWrap.setTag(-1);\n    }\n\n\n    @Override\n    public void onViewCreated(FrameLayout rootView) {\n        mLlCloseWrap = findViewById(R.id.ll_close_wrap);\n        mWrap0 = findViewById(R.id.fl_wrap0);\n        mIvClose0 = findViewById(R.id.iv_close0);\n        mWrap0.setVisibility(View.GONE);\n        mWrap1 = findViewById(R.id.fl_wrap1);\n        mIvClose1 = findViewById(R.id.iv_close1);\n        mWrap1.setVisibility(View.GONE);\n        mWrap2 = findViewById(R.id.fl_wrap2);\n        mIvClose2 = findViewById(R.id.iv_close2);\n        mWrap2.setVisibility(View.GONE);\n        mWrap3 = findViewById(R.id.fl_wrap3);\n        mIvClose3 = findViewById(R.id.iv_close3);\n        mWrap3.setVisibility(View.GONE);\n\n        mWrap0.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                v.setVisibility(View.GONE);\n                if (mPerformanceCloseListener != null) {\n                    mPerformanceCloseListener.onClose((Integer) v.getTag());\n                }\n            }\n        });\n        mWrap1.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                v.setVisibility(View.GONE);\n                if (mPerformanceCloseListener != null) {\n                    mPerformanceCloseListener.onClose((Integer) v.getTag());\n                }\n            }\n        });\n        mWrap2.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                v.setVisibility(View.GONE);\n                if (mPerformanceCloseListener != null) {\n                    mPerformanceCloseListener.onClose((Integer) v.getTag());\n                }\n            }\n        });\n        mWrap3.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                v.setVisibility(View.GONE);\n                if (mPerformanceCloseListener != null) {\n                    mPerformanceCloseListener.onClose((Integer) v.getTag());\n                }\n            }\n        });\n    }\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.gravity = Gravity.RIGHT | Gravity.TOP;\n        params.width = DoKitViewLayoutParams.WRAP_CONTENT;\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT;\n    }\n\n    protected void setPerformanceCloseListener(PerformanceCloseListener listener) {\n        this.mPerformanceCloseListener = listener;\n    }\n\n    @Override\n    public boolean canDrag() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/PerformanceCloseListener.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance;\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-10-11-20:39\n * 描    述：性能监控关闭回调接口 系统模式下专用\n * 修订历史：\n * ================================================\n */\npublic interface PerformanceCloseListener {\n\n    /**\n     * @param performanceType\n     */\n    void onClose(int performanceType);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/PerformanceData.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance;\n\npublic class PerformanceData {\n\n    public String date;\n    public String time;\n    public float parameter;\n\n    public PerformanceData(String date, String time, float parameter) {\n        this.date = date;\n        this.time = time;\n        this.parameter = parameter;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/PerformanceDataAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\n\npublic class PerformanceDataAdapter extends AbsRecyclerAdapter<AbsViewBinder<PerformanceData>, PerformanceData> {\n    private OnViewClickListener mOnViewClickListener;\n    private OnViewLongClickListener mOnViewLongClickListener;\n\n    public PerformanceDataAdapter(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected AbsViewBinder<PerformanceData> createViewHolder(View view, int viewType) {\n        return new PerformanceItemViewHolder(view);\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        return inflater.inflate(R.layout.dk_item_performance_detail, parent, false);\n    }\n\n    public class PerformanceItemViewHolder extends AbsViewBinder<PerformanceData> {\n        private TextView date;\n        private TextView time;\n        private TextView parameter;\n\n\n        public PerformanceItemViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            date = getView(R.id.date);\n            time = getView(R.id.time);\n            parameter = getView(R.id.parameter);\n        }\n\n        @Override\n        public void bind(PerformanceData performanceData) {\n            date.setText(performanceData.date);\n            time.setText(performanceData.time);\n            parameter.setText(String.valueOf(performanceData.parameter));\n        }\n\n        @Override\n        protected void onViewClick(View view, PerformanceData data) {\n            super.onViewClick(view, data);\n            super.onViewClick(view, data);\n            if (mOnViewClickListener != null) {\n                mOnViewClickListener.onViewClick(view, data);\n            }\n        }\n\n    }\n\n    public void setOnViewClickListener(OnViewClickListener onViewClickListener) {\n        mOnViewClickListener = onViewClickListener;\n    }\n\n    public void setOnViewLongClickListener(OnViewLongClickListener onViewLongClickListener) {\n        mOnViewLongClickListener = onViewLongClickListener;\n    }\n\n    public interface OnViewClickListener {\n        void onViewClick(View v, PerformanceData data);\n    }\n\n    public interface OnViewLongClickListener {\n        boolean onViewLongClick(View v, PerformanceData data);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/PerformanceDataManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance;\n\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.Debug;\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Looper;\nimport android.os.Message;\nimport android.os.Process;\nimport android.text.TextUtils;\nimport android.view.Choreographer;\nimport android.view.WindowManager;\n\nimport androidx.annotation.RequiresApi;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.DoKitEnv;\nimport com.didichuxing.doraemonkit.config.DokitMemoryConfig;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil;\nimport com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo;\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.util.AppUtils;\nimport com.didichuxing.doraemonkit.util.TimeUtils;\n\nimport java.io.BufferedReader;\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.RandomAccessFile;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 性能检测管理类 包括 cpu、ram、fps等\n */\n\npublic class PerformanceDataManager {\n    private static final String TAG = \"PerformanceDataManager\";\n    private static final int MAX_FRAME_RATE = 60;\n    /**\n     * 信息采集时间 内存和cpu\n     */\n    private static final int NORMAL_SAMPLING_TIME = 500;\n    /**\n     * fps 采集时间\n     */\n    private static final int FPS_SAMPLING_TIME = 1000;\n    private String memoryFileName = \"memory.txt\";\n    private String cpuFileName = \"cpu.txt\";\n    private String fpsFileName = \"fps.txt\";\n\n    //private int mLastSkippedFrames;\n\n    private int mMaxFrameRate = MAX_FRAME_RATE;\n    /**\n     * cpu 百分比\n     */\n    private float mLastCpuRate;\n    /**\n     * 当前使用内存\n     */\n    private float mLastMemoryRate;\n    /**\n     * 当前的帧率\n     */\n    private int mLastFrameRate = mMaxFrameRate;\n    private long mUpBytes;\n    private long mDownBytes;\n    private long mLastUpBytes;\n    private long mLastDownBytes;\n    /**\n     * 默认的采集时间 通常为1s\n     */\n    private Handler mNormalHandler;\n    private HandlerThread mHandlerThread;\n    private float mMaxMemory;\n    private Context mContext;\n    private ActivityManager mActivityManager;\n    private WindowManager mWindowManager;\n    private RandomAccessFile mProcStatFile;\n    private RandomAccessFile mAppStatFile;\n    private Long mLastCpuTime;\n    private Long mLastAppCpuTime;\n    // 是否是8.0及其以上\n    private boolean mAboveAndroidO;\n    private static final int MSG_CPU = 1;\n    private static final int MSG_MEMORY = 2;\n    private static final int MSG_NET_FLOW = 4;\n    private Handler mMainHandler = new Handler(Looper.getMainLooper());\n    private FrameRateRunnable mRateRunnable = new FrameRateRunnable();\n\n    private void executeCpuData() {\n        if (mAboveAndroidO) {\n            mLastCpuRate = getCpuDataForO();\n            writeCpuDataIntoFile();\n        } else {\n            mLastCpuRate = getCPUData();\n            writeCpuDataIntoFile();\n        }\n    }\n\n    /**\n     * 获取内存数值\n     */\n    private void executeMemoryData() {\n        mLastMemoryRate = getMemoryData();\n        writeMemoryDataIntoFile();\n    }\n\n\n    /**\n     * 8.0以上获取cpu的方式\n     *\n     * @return\n     */\n    private float getCpuDataForO() {\n        java.lang.Process process = null;\n        try {\n            process = Runtime.getRuntime().exec(\"top -n 1\");\n            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));\n            String line;\n            int cpuIndex = -1;\n            while ((line = reader.readLine()) != null) {\n                line = line.trim();\n                if (TextUtils.isEmpty(line)) {\n                    continue;\n                }\n                int tempIndex = getCPUIndex(line);\n                if (tempIndex != -1) {\n                    cpuIndex = tempIndex;\n                    continue;\n                }\n                if (line.startsWith(String.valueOf(Process.myPid()))) {\n                    if (cpuIndex == -1) {\n                        continue;\n                    }\n                    String[] param = line.split(\"\\\\s+\");\n                    if (param.length <= cpuIndex) {\n                        continue;\n                    }\n                    String cpu = param[cpuIndex];\n                    if (cpu.endsWith(\"%\")) {\n                        cpu = cpu.substring(0, cpu.lastIndexOf(\"%\"));\n                    }\n                    float rate = Float.parseFloat(cpu) / Runtime.getRuntime().availableProcessors();\n                    return rate;\n                }\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            if (process != null) {\n                process.destroy();\n            }\n        }\n        return 0;\n    }\n\n    private int getCPUIndex(String line) {\n        if (line.contains(\"CPU\")) {\n            String[] titles = line.split(\"\\\\s+\");\n            for (int i = 0; i < titles.length; i++) {\n                if (titles[i].contains(\"CPU\")) {\n                    return i;\n                }\n            }\n        }\n        return -1;\n    }\n\n\n    private static class Holder {\n        private static PerformanceDataManager INSTANCE = new PerformanceDataManager();\n    }\n\n    private PerformanceDataManager() {\n    }\n\n    public static PerformanceDataManager getInstance() {\n        return Holder.INSTANCE;\n    }\n\n    public void init() {\n        mContext = DoKitEnv.requireApp().getApplicationContext();\n        mActivityManager = (ActivityManager) DoKitEnv.requireApp().getSystemService(Context.ACTIVITY_SERVICE);\n        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);\n        if (mWindowManager != null) {\n            mMaxFrameRate = (int) mWindowManager.getDefaultDisplay().getRefreshRate();\n        } else {\n            mMaxFrameRate = MAX_FRAME_RATE;\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            mAboveAndroidO = true;\n        }\n        if (mHandlerThread == null) {\n            mHandlerThread = new HandlerThread(\"handler-thread\");\n            mHandlerThread.start();\n        }\n        if (mNormalHandler == null) {\n            //loop handler\n            mNormalHandler = new Handler(mHandlerThread.getLooper()) {\n                @Override\n                public void handleMessage(Message msg) {\n                    super.handleMessage(msg);\n                    if (msg.what == MSG_CPU) {\n                        if (AppUtils.isAppForeground()) {\n                            executeCpuData();\n                        }\n                        mNormalHandler.sendEmptyMessageDelayed(MSG_CPU, NORMAL_SAMPLING_TIME);\n                    } else if (msg.what == MSG_MEMORY) {\n                        if (AppUtils.isAppForeground()) {\n                            executeMemoryData();\n                        }\n                        mNormalHandler.sendEmptyMessageDelayed(MSG_MEMORY, NORMAL_SAMPLING_TIME);\n                    } else if (msg.what == MSG_NET_FLOW) {\n                        mLastUpBytes = NetworkManager.get().getTotalRequestSize() - mUpBytes;\n                        mLastDownBytes = NetworkManager.get().getTotalResponseSize() - mDownBytes;\n                        mNormalHandler.sendEmptyMessageDelayed(MSG_NET_FLOW, NORMAL_SAMPLING_TIME);\n                    }\n//                    else if (msg.what == MSG_SAVE_LOCAL) {\n//                        saveToLocal();\n//                        mNormalHandler.sendEmptyMessageDelayed(MSG_SAVE_LOCAL, NORMAL_SAMPLING_TIME);\n//                    }\n                }\n            };\n        }\n    }\n\n    private String getFilePath(Context context) {\n        return context.getCacheDir() + File.separator + \"doraemon/\";\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)\n    public void startMonitorFrameInfo() {\n        DokitMemoryConfig.FPS_STATUS = true;\n        //开启定时任务\n        mMainHandler.postDelayed(mRateRunnable, FPS_SAMPLING_TIME);\n        Choreographer.getInstance().postFrameCallback(mRateRunnable);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)\n    public void stopMonitorFrameInfo() {\n        DokitMemoryConfig.FPS_STATUS = false;\n        Choreographer.getInstance().removeFrameCallback(mRateRunnable);\n        mMainHandler.removeCallbacks(mRateRunnable);\n    }\n\n    public void startMonitorCPUInfo() {\n        DokitMemoryConfig.CPU_STATUS = true;\n        mNormalHandler.sendEmptyMessageDelayed(MSG_CPU, NORMAL_SAMPLING_TIME);\n    }\n\n    public void startMonitorNetFlowInfo() {\n        DokitMemoryConfig.NETWORK_STATUS = true;\n        mNormalHandler.sendEmptyMessageDelayed(MSG_NET_FLOW, NORMAL_SAMPLING_TIME);\n    }\n\n    public void stopMonitorNetFlowInfo() {\n        DokitMemoryConfig.NETWORK_STATUS = false;\n        mNormalHandler.removeMessages(MSG_NET_FLOW);\n    }\n\n\n    public void destroy() {\n        stopMonitorMemoryInfo();\n        stopMonitorCPUInfo();\n        stopMonitorFrameInfo();\n        if (mHandlerThread != null) {\n            mHandlerThread.quit();\n        }\n        mHandlerThread = null;\n        mNormalHandler = null;\n    }\n\n\n    public void stopMonitorCPUInfo() {\n        DokitMemoryConfig.CPU_STATUS = false;\n        mNormalHandler.removeMessages(MSG_CPU);\n    }\n\n\n    public void startMonitorMemoryInfo() {\n        DokitMemoryConfig.RAM_STATUS = true;\n        if (mMaxMemory == 0) {\n            mMaxMemory = mActivityManager.getMemoryClass();\n        }\n        mNormalHandler.sendEmptyMessageDelayed(MSG_MEMORY, NORMAL_SAMPLING_TIME);\n    }\n\n    public void stopMonitorMemoryInfo() {\n        DokitMemoryConfig.RAM_STATUS = false;\n        mNormalHandler.removeMessages(MSG_MEMORY);\n    }\n\n    private void writeCpuDataIntoFile() {\n        if (DoKitManager.INSTANCE.getCALLBACK() != null) {\n            DoKitManager.INSTANCE.getCALLBACK().onCpuCallBack(mLastCpuRate, getCpuFilePath());\n        }\n\n        //保存cpu数据到app健康体检\n        if (DoKitManager.APP_HEALTH_RUNNING) {\n            addPerformanceDataInAppHealth(mLastCpuRate, PERFORMANCE_TYPE_CPU);\n        }\n    }\n\n    private void writeMemoryDataIntoFile() {\n        if (DoKitManager.INSTANCE.getCALLBACK() != null) {\n            DoKitManager.INSTANCE.getCALLBACK().onMemoryCallBack(mLastMemoryRate, getMemoryFilePath());\n        }\n        //保存cpu数据到app健康体检\n        if (DoKitManager.APP_HEALTH_RUNNING) {\n            addPerformanceDataInAppHealth(mLastMemoryRate, PERFORMANCE_TYPE_MEMORY);\n        }\n    }\n\n    private void writeFpsDataIntoFile() {\n        if (DoKitManager.INSTANCE.getCALLBACK() != null) {\n            DoKitManager.INSTANCE.getCALLBACK().onFpsCallBack(mLastFrameRate, getFpsFilePath());\n        }\n        if (DoKitManager.APP_HEALTH_RUNNING) {\n            addPerformanceDataInAppHealth(mLastFrameRate > 60 ? 60 : mLastFrameRate, PERFORMANCE_TYPE_FPS);\n        }\n    }\n\n    /**\n     * 8.0一下获取cpu的方式\n     *\n     * @return\n     */\n    private float getCPUData() {\n        long cpuTime;\n        long appTime;\n        float value = 0.0f;\n        try {\n            if (mProcStatFile == null || mAppStatFile == null) {\n                mProcStatFile = new RandomAccessFile(\"/proc/stat\", \"r\");\n                mAppStatFile = new RandomAccessFile(\"/proc/\" + android.os.Process.myPid() + \"/stat\", \"r\");\n            } else {\n                mProcStatFile.seek(0L);\n                mAppStatFile.seek(0L);\n            }\n            String procStatString = mProcStatFile.readLine();\n            String appStatString = mAppStatFile.readLine();\n            String procStats[] = procStatString.split(\" \");\n            String appStats[] = appStatString.split(\" \");\n            cpuTime = Long.parseLong(procStats[2]) + Long.parseLong(procStats[3])\n                    + Long.parseLong(procStats[4]) + Long.parseLong(procStats[5])\n                    + Long.parseLong(procStats[6]) + Long.parseLong(procStats[7])\n                    + Long.parseLong(procStats[8]);\n            appTime = Long.parseLong(appStats[13]) + Long.parseLong(appStats[14]);\n            if (mLastCpuTime == null && mLastAppCpuTime == null) {\n                mLastCpuTime = cpuTime;\n                mLastAppCpuTime = appTime;\n                return value;\n            }\n            value = ((float) (appTime - mLastAppCpuTime) / (float) (cpuTime - mLastCpuTime)) * 100f;\n            mLastCpuTime = cpuTime;\n            mLastAppCpuTime = appTime;\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return value;\n    }\n\n    private float getMemoryData() {\n        float mem = 0.0F;\n        try {\n            Debug.MemoryInfo memInfo = null;\n            //28 为Android P\n            if (Build.VERSION.SDK_INT > 28) {\n                // 统计进程的内存信息 totalPss\n                memInfo = new Debug.MemoryInfo();\n                Debug.getMemoryInfo(memInfo);\n            } else {\n                //As of Android Q, for regular apps this method will only return information about the memory info for the processes running as the caller's uid;\n                // no other process memory info is available and will be zero. Also of Android Q the sample rate allowed by this API is significantly limited, if called faster the limit you will receive the same data as the previous call.\n\n                Debug.MemoryInfo[] memInfos = mActivityManager.getProcessMemoryInfo(new int[]{Process.myPid()});\n                if (memInfos != null && memInfos.length > 0) {\n                    memInfo = memInfos[0];\n                }\n            }\n            int totalPss = 0;\n            if (memInfo != null) {\n                totalPss = memInfo.getTotalPss();\n            }\n            if (totalPss >= 0) {\n                // Mem in MB\n                mem = totalPss / 1024.0F;\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return mem;\n    }\n\n    private float parseMemoryData(String data) throws IOException {\n        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(data.getBytes())));\n        String line;\n        while ((line = bufferedReader.readLine()) != null) {\n            line = line.trim();\n            if (line.contains(\"Permission Denial\")) {\n                break;\n            } else {\n                String[] lineItems = line.split(\"\\\\s+\");\n                if (lineItems != null && lineItems.length > 1) {\n                    String result = lineItems[0];\n                    bufferedReader.close();\n                    if (!TextUtils.isEmpty(result) && result.contains(\"K:\")) {\n                        result = result.replace(\"K:\", \"\");\n                        if (result.contains(\",\")) {\n                            result = result.replace(\",\", \".\");\n                        }\n                    }\n                    // Mem in MB\n                    return Float.parseFloat(result) / 1024;\n                }\n            }\n        }\n        return 0;\n    }\n\n    private float parseCPUData(String data) throws IOException {\n        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(data.getBytes())));\n        String line;\n        while ((line = bufferedReader.readLine()) != null) {\n            line = line.trim();\n            if (line.contains(\"Permission Denial\")) {\n                break;\n            } else {\n                String[] lineItems = line.split(\"\\\\s+\");\n                if (lineItems != null && lineItems.length > 1) {\n                    bufferedReader.close();\n                    return Float.parseFloat(lineItems[0].replace(\"%\", \"\"));\n                }\n            }\n        }\n        return 0;\n    }\n\n    public String getCpuFilePath() {\n        return getFilePath(mContext) + cpuFileName;\n    }\n\n    public String getMemoryFilePath() {\n        return getFilePath(mContext) + memoryFileName;\n    }\n\n    public String getFpsFilePath() {\n        return getFilePath(mContext) + fpsFileName;\n    }\n\n\n    public long getLastFrameRate() {\n        return mLastFrameRate;\n    }\n\n    public float getLastCpuRate() {\n        return mLastCpuRate;\n    }\n\n    public float getLastMemoryInfo() {\n        return mLastMemoryRate;\n    }\n\n//    public int getLastSkippedFrames() {\n//        return mLastSkippedFrames;\n//    }\n\n    public float getMaxMemory() {\n        return mMaxMemory;\n    }\n\n    /**\n     * 读取fps的线程\n     */\n    private class FrameRateRunnable implements Runnable, Choreographer.FrameCallback {\n        private int totalFramesPerSecond;\n\n        @Override\n        public void run() {\n            mLastFrameRate = totalFramesPerSecond;\n            if (mLastFrameRate > mMaxFrameRate) {\n                mLastFrameRate = mMaxFrameRate;\n            }\n            //保存fps数据\n            if (AppUtils.isAppForeground()) {\n                writeFpsDataIntoFile();\n            }\n            totalFramesPerSecond = 0;\n            //1s中统计一次\n            mMainHandler.postDelayed(this, FPS_SAMPLING_TIME);\n        }\n\n        //\n        @Override\n        public void doFrame(long frameTimeNanos) {\n            totalFramesPerSecond++;\n            Choreographer.getInstance().postFrameCallback(this);\n        }\n\n    }\n\n    public long getLastUpBytes() {\n        return mLastUpBytes;\n    }\n\n    public long getLastDownBytes() {\n        return mLastDownBytes;\n    }\n\n//    private AppHealthInfo.DataBean.PerformanceBean cpuBean;\n//    private AppHealthInfo.DataBean.PerformanceBean memoryBean;\n//    private AppHealthInfo.DataBean.PerformanceBean fpsBean;\n\n    /**\n     * cpu\n     */\n    public static final int PERFORMANCE_TYPE_CPU = 1;\n    /**\n     * memory\n     */\n    public static final int PERFORMANCE_TYPE_MEMORY = 2;\n    /**\n     * fps\n     */\n    public static final int PERFORMANCE_TYPE_FPS = 3;\n\n    /**\n     * 保存cpu数据到健康体检中 统计有问题\n     */\n    private synchronized void addPerformanceDataInAppHealth(float performanceValue, int performanceType) {\n        if (ActivityUtils.getTopActivity() == null) {\n            return;\n        }\n        try {\n            AppHealthInfo.DataBean.PerformanceBean lastPerformanceInfo = AppHealthInfoUtil.getInstance().getLastPerformanceInfo(performanceType);\n            //第一次启动\n            if (lastPerformanceInfo == null) {\n                AppHealthInfo.DataBean.PerformanceBean performanceBean = new AppHealthInfo.DataBean.PerformanceBean();\n                List<AppHealthInfo.DataBean.PerformanceBean.ValuesBean> valuesBeans = new ArrayList<>();\n                valuesBeans.add(new AppHealthInfo.DataBean.PerformanceBean.ValuesBean(\"\" + TimeUtils.getNowMills(), \"\" + performanceValue));\n                performanceBean.setPage(ActivityUtils.getTopActivity().getClass().getCanonicalName());\n                performanceBean.setPageKey(ActivityUtils.getTopActivity().toString());\n                performanceBean.setValues(valuesBeans);\n                if (performanceType == PERFORMANCE_TYPE_CPU) {\n                    AppHealthInfoUtil.getInstance().addCPUInfo(performanceBean);\n                } else if (performanceType == PERFORMANCE_TYPE_MEMORY) {\n                    AppHealthInfoUtil.getInstance().addMemoryInfo(performanceBean);\n                } else {\n                    AppHealthInfoUtil.getInstance().addFPSInfo(performanceBean);\n                }\n            } else {//不是第一次启动\n                String lastPageKey = lastPerformanceInfo.getPageKey();\n                //同一个页面\n                if (ActivityUtils.getTopActivity() != null && lastPageKey.equals(ActivityUtils.getTopActivity().toString())) {\n                    List<AppHealthInfo.DataBean.PerformanceBean.ValuesBean> valuesBeans = lastPerformanceInfo.getValues();\n                    int valueSize = valuesBeans.size();\n                    //判断是否需要上传数据\n                    //采集的点数必须在10~40之间 其中cpu 、 内存必须在20~40 因为fps 1s中采集一次\n                    if (valueSize < 40) {\n                        valuesBeans.add(new AppHealthInfo.DataBean.PerformanceBean.ValuesBean(\"\" + TimeUtils.getNowMills(), \"\" + performanceValue));\n                    }\n                } else {//页面已发生变化\n                    List<AppHealthInfo.DataBean.PerformanceBean.ValuesBean> lastValuesBeans = lastPerformanceInfo.getValues();\n                    int valueSize = lastValuesBeans.size();\n                    //先丢弃上一个页面的数据\n                    if (performanceType == PERFORMANCE_TYPE_CPU && valueSize < 20) {\n                        AppHealthInfoUtil.getInstance().removeLastPerformanceInfo(performanceType);\n                    } else if (performanceType == PERFORMANCE_TYPE_MEMORY && valueSize < 20) {\n                        AppHealthInfoUtil.getInstance().removeLastPerformanceInfo(performanceType);\n                    } else if (performanceType == PERFORMANCE_TYPE_FPS && valueSize < 10) {\n                        AppHealthInfoUtil.getInstance().removeLastPerformanceInfo(performanceType);\n                    }\n\n                    AppHealthInfo.DataBean.PerformanceBean performanceBean = new AppHealthInfo.DataBean.PerformanceBean();\n                    List<AppHealthInfo.DataBean.PerformanceBean.ValuesBean> newValuesBeans = new ArrayList<>();\n                    newValuesBeans.add(new AppHealthInfo.DataBean.PerformanceBean.ValuesBean(\"\" + TimeUtils.getNowMills(), \"\" + performanceValue));\n                    performanceBean.setPage(ActivityUtils.getTopActivity().getClass().getCanonicalName());\n                    performanceBean.setPageKey(ActivityUtils.getTopActivity().toString());\n                    performanceBean.setValues(newValuesBeans);\n                    if (performanceType == PERFORMANCE_TYPE_CPU) {\n                        AppHealthInfoUtil.getInstance().addCPUInfo(performanceBean);\n                    } else if (performanceType == PERFORMANCE_TYPE_MEMORY) {\n                        AppHealthInfoUtil.getInstance().addMemoryInfo(performanceBean);\n                    } else {\n                        AppHealthInfoUtil.getInstance().addFPSInfo(performanceBean);\n                    }\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/PerformanceDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.config.DokitMemoryConfig;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.kit.performance.datasource.DataSourceFactory;\nimport com.didichuxing.doraemonkit.kit.performance.datasource.IDataSource;\nimport com.didichuxing.doraemonkit.kit.performance.widget.LineChart;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-10-11-16:05\n * 描    述：性能监控 帧率、 CPU、RAM、流量监控统一显示的DokitView\n * 修订历史：\n * ================================================\n */\npublic class PerformanceDoKitView extends AbsDoKitView implements PerformanceCloseListener {\n    static final int DEFAULT_REFRESH_INTERVAL = 1000;\n    PerformanceCloseDoKitView mPerformanceCloseDokitView;\n    LinearLayout mPerformanceWrap;\n    FrameLayout mFlWrap0, mFlWrap1, mFlWrap2, mFlWrap3;\n    LineChart mLineChart0, mLineChart1, mLineChart2, mLineChart3;\n    ImageView mIvClose0, mIvClose1, mIvClose2, mIvClose3;\n    private PerformanceFragmentCloseListener mPerformanceFragmentCloseListener;\n\n    /**\n     * 添加性能检测页面的浮标关闭监听\n     *\n     * @param listener\n     */\n    void addPerformanceFragmentCloseListener(PerformanceFragmentCloseListener listener) {\n        this.mPerformanceFragmentCloseListener = listener;\n    }\n\n    /**\n     * 移除性能检测页面的浮标关闭监听\n     *\n     * @param listener\n     */\n    void removePerformanceFragmentCloseListener(PerformanceFragmentCloseListener listener) {\n        if (mPerformanceFragmentCloseListener != null && mPerformanceFragmentCloseListener == listener) {\n            mPerformanceFragmentCloseListener = null;\n        }\n    }\n\n\n    @Override\n    public void onCreate(Context context) {\n\n    }\n\n    @Override\n    public View onCreateView(Context context, FrameLayout rootView) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_performance_wrap, rootView, false);\n    }\n\n    /**\n     * 动态添加性能项目\n     *\n     * @param performanceType\n     * @param title\n     * @param interval\n     */\n    void addItem(int performanceType, String title, int interval) {\n        if (mPerformanceWrap == null) {\n            return;\n        }\n        int needOperateViewIndex = -1;\n        for (int index = 0; index < mPerformanceWrap.getChildCount(); index++) {\n            if (mPerformanceWrap.getChildAt(index).getVisibility() == View.GONE) {\n                needOperateViewIndex = index;\n                break;\n            }\n        }\n\n        if (needOperateViewIndex == -1) {\n            return;\n        }\n\n        FrameLayout needOperateViewWrap = (FrameLayout) mPerformanceWrap.getChildAt(needOperateViewIndex);\n        needOperateViewWrap.setVisibility(View.VISIBLE);\n        LineChart needOperateLineChart = needOperateViewWrap.findViewWithTag(\"lineChart\");\n\n        IDataSource dataSource = DataSourceFactory.createDataSource(performanceType);\n        needOperateLineChart.setPerformanceType(performanceType);\n        needOperateLineChart.setTitle(title);\n        needOperateLineChart.setInterval(interval);\n        needOperateLineChart.setDataSource(dataSource);\n        needOperateLineChart.startMove();\n        //系统模式下添加关闭按钮\n        if (!isNormalMode() && mPerformanceCloseDokitView != null) {\n            mPerformanceCloseDokitView.addItem(needOperateViewIndex, performanceType);\n        }\n\n    }\n\n    void removeItem(int performanceType) {\n        if (mPerformanceWrap == null) {\n            return;\n        }\n        int needOperateViewIndex = -1;\n        for (int index = 0; index < mPerformanceWrap.getChildCount(); index++) {\n            if (mPerformanceWrap.getChildAt(index).getVisibility() != View.GONE) {\n                LineChart needOperateLineChart = mPerformanceWrap.getChildAt(index).findViewWithTag(\"lineChart\");\n                if (needOperateLineChart.getPerformanceType() == performanceType) {\n                    needOperateViewIndex = index;\n                    break;\n                }\n\n            }\n        }\n        if (needOperateViewIndex == -1) {\n            return;\n        }\n\n        FrameLayout frameLayout = (FrameLayout) mPerformanceWrap.getChildAt(needOperateViewIndex);\n        frameLayout.setVisibility(View.GONE);\n        LineChart needOperateLineChart = frameLayout.findViewWithTag(\"lineChart\");\n        needOperateLineChart.stopMove();\n        needOperateLineChart.setPerformanceType(-1);\n        switch (performanceType) {\n            case DataSourceFactory.TYPE_FPS:\n                DokitMemoryConfig.FPS_STATUS = false;\n                break;\n            case DataSourceFactory.TYPE_CPU:\n                DokitMemoryConfig.CPU_STATUS = false;\n                break;\n            case DataSourceFactory.TYPE_RAM:\n                DokitMemoryConfig.RAM_STATUS = false;\n                break;\n            case DataSourceFactory.TYPE_NETWORK:\n                DokitMemoryConfig.NETWORK_STATUS = false;\n                break;\n            default:\n                break;\n        }\n\n        //系统模式下添加关闭按钮\n        if (!isNormalMode() && mPerformanceCloseDokitView != null) {\n            mPerformanceCloseDokitView.removeItem(needOperateViewIndex);\n        }\n\n    }\n\n\n    @Override\n    public void onViewCreated(FrameLayout rootView) {\n        mPerformanceWrap = findViewById(R.id.ll_performance_wrap);\n        mFlWrap0 = findViewById(R.id.fl_chart0);\n        mFlWrap0.setVisibility(View.GONE);\n        mFlWrap1 = findViewById(R.id.fl_chart1);\n        mFlWrap1.setVisibility(View.GONE);\n        mFlWrap2 = findViewById(R.id.fl_chart2);\n        mFlWrap2.setVisibility(View.GONE);\n        mFlWrap3 = findViewById(R.id.fl_chart3);\n        mFlWrap3.setVisibility(View.GONE);\n        mLineChart0 = findViewById(R.id.linechart0);\n        mLineChart1 = findViewById(R.id.linechart1);\n        mLineChart2 = findViewById(R.id.linechart2);\n        mLineChart3 = findViewById(R.id.linechart3);\n        mIvClose0 = findViewById(R.id.iv_close0);\n        mIvClose1 = findViewById(R.id.iv_close1);\n        mIvClose2 = findViewById(R.id.iv_close2);\n        mIvClose3 = findViewById(R.id.iv_close3);\n        setDoKitViewNotResponseTouchEvent(getDoKitView());\n        setDoKitViewNotResponseTouchEvent(mLineChart0);\n        setDoKitViewNotResponseTouchEvent(mLineChart1);\n        setDoKitViewNotResponseTouchEvent(mLineChart2);\n        setDoKitViewNotResponseTouchEvent(mLineChart3);\n        if (isNormalMode()) {\n            mIvClose0.setVisibility(View.VISIBLE);\n            mIvClose1.setVisibility(View.VISIBLE);\n            mIvClose2.setVisibility(View.VISIBLE);\n            mIvClose3.setVisibility(View.VISIBLE);\n        } else {\n            mIvClose0.setVisibility(View.GONE);\n            mIvClose1.setVisibility(View.GONE);\n            mIvClose2.setVisibility(View.GONE);\n            mIvClose3.setVisibility(View.GONE);\n        }\n        mIvClose0.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                LineChart lineChart = ((FrameLayout) v.getParent()).findViewWithTag(\"lineChart\");\n                onClose(lineChart.getPerformanceType());\n            }\n        });\n        mIvClose1.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                LineChart lineChart = ((FrameLayout) v.getParent()).findViewWithTag(\"lineChart\");\n                onClose(lineChart.getPerformanceType());\n            }\n        });\n        mIvClose2.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                LineChart lineChart = ((FrameLayout) v.getParent()).findViewWithTag(\"lineChart\");\n                onClose(lineChart.getPerformanceType());\n            }\n        });\n        mIvClose3.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                LineChart lineChart = ((FrameLayout) v.getParent()).findViewWithTag(\"lineChart\");\n                onClose(lineChart.getPerformanceType());\n            }\n        });\n    }\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.flags = DoKitViewLayoutParams.FLAG_NOT_FOCUSABLE_AND_NOT_TOUCHABLE;\n        params.width = DoKitViewLayoutParams.MATCH_PARENT;\n        params.height = DoKitViewLayoutParams.MATCH_PARENT;\n    }\n\n    @Override\n    public boolean canDrag() {\n        return false;\n    }\n\n    /**\n     * 系统模式下显示单独的关闭按钮\n     */\n    private void showSystemPerfoemanceCloseDokitView() {\n        DoKit.launchFloating(PerformanceCloseDoKitView.class);\n        mPerformanceCloseDokitView = DoKit.getDoKitView(ActivityUtils.getTopActivity(), PerformanceCloseDoKitView.class);\n        if (mPerformanceCloseDokitView != null) {\n            mPerformanceCloseDokitView.setPerformanceCloseListener(PerformanceDoKitView.this);\n        }\n    }\n\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        //系统模式下主动添加关闭按钮\n        if (!isNormalMode()) {\n            showSystemPerfoemanceCloseDokitView();\n        }\n\n        //普通模式下自己处理页面切换\n        if (isNormalMode()) {\n            hideAllPerformanceView();\n            for (performanceViewInfo performanceViewInfo : PerformanceDokitViewManager.singleperformanceViewInfos.values()) {\n                PerformanceDokitViewManager.open(performanceViewInfo.performanceType, performanceViewInfo.title, null);\n            }\n        }\n    }\n\n    @Override\n    public void onClose(int performanceType) {\n        if (performanceType == -1) {\n            return;\n        }\n        /**\n         *点击关闭按钮 回调switch按钮关闭\n         */\n        if (mPerformanceFragmentCloseListener != null) {\n            mPerformanceFragmentCloseListener.onClose(performanceType);\n        }\n\n        PerformanceDokitViewManager.close(performanceType, PerformanceDokitViewManager.getTitleByPerformanceType(getContext(), performanceType));\n    }\n\n\n    @Override\n    public void onEnterForeground() {\n        super.onEnterForeground();\n        if (((FrameLayout) mLineChart0.getParent()).getVisibility() == View.VISIBLE) {\n            mLineChart0.startMove();\n        }\n        if (((FrameLayout) mLineChart1.getParent()).getVisibility() == View.VISIBLE) {\n            mLineChart1.startMove();\n        }\n        if (((FrameLayout) mLineChart2.getParent()).getVisibility() == View.VISIBLE) {\n            mLineChart2.startMove();\n        }\n        if (((FrameLayout) mLineChart3.getParent()).getVisibility() == View.VISIBLE) {\n            mLineChart3.startMove();\n        }\n    }\n\n    @Override\n    public void onEnterBackground() {\n        super.onEnterBackground();\n        mLineChart0.stopMove();\n        mLineChart1.stopMove();\n        mLineChart2.stopMove();\n        mLineChart3.stopMove();\n    }\n\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        mPerformanceFragmentCloseListener = null;\n        mLineChart0.stopMove();\n        mLineChart0 = null;\n        mLineChart1.stopMove();\n        mLineChart1 = null;\n        mLineChart2.stopMove();\n        mLineChart2 = null;\n        mLineChart3.stopMove();\n        mLineChart3 = null;\n    }\n\n    /**\n     * 隐藏所有的\n     */\n    private void hideAllPerformanceView() {\n        if (!isNormalMode()) {\n            return;\n        }\n        mFlWrap0.setVisibility(View.GONE);\n        mFlWrap1.setVisibility(View.GONE);\n        mFlWrap2.setVisibility(View.GONE);\n        mFlWrap3.setVisibility(View.GONE);\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/PerformanceDokitViewManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance;\n\nimport android.content.Context;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.performance.datasource.DataSourceFactory;\n\nimport java.util.TreeMap;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-10-11-16:05\n * 描    述：性能监控 帧率、 CPU、RAM、流量监控统一显示的DokitView 管理类\n * 修订历史：\n * ================================================\n */\npublic class PerformanceDokitViewManager {\n\n    public static TreeMap<String, performanceViewInfo> singleperformanceViewInfos = new TreeMap<>();\n\n    /**\n     * @param performanceType 参考 DataSourceFactory\n     */\n    public static void open(int performanceType, String title, PerformanceFragmentCloseListener listener) {\n        open(performanceType, title, PerformanceDoKitView.DEFAULT_REFRESH_INTERVAL, listener);\n    }\n\n    public static void open(int performanceType, String title, int interval, PerformanceFragmentCloseListener listener) {\n        PerformanceDoKitView performanceDokitView = DoKit.getDoKitView(ActivityUtils.getTopActivity(), PerformanceDoKitView.class);\n        if (performanceDokitView == null) {\n            DoKit.launchFloating(PerformanceDoKitView.class);\n            performanceDokitView = DoKit.getDoKitView(ActivityUtils.getTopActivity(), PerformanceDoKitView.class);\n            performanceDokitView.addItem(performanceType, title, interval);\n        } else {\n            performanceDokitView.addItem(performanceType, title, interval);\n        }\n        performanceDokitView.addPerformanceFragmentCloseListener(listener);\n        singleperformanceViewInfos.put(title, new performanceViewInfo(performanceType, title, interval));\n    }\n\n    /**\n     * 性能检测设置页面关闭时调用\n     *\n     * @param listener\n     */\n    public static void onPerformanceSettingFragmentDestroy(PerformanceFragmentCloseListener listener) {\n        PerformanceDoKitView performanceDokitView = DoKit.getDoKitView(ActivityUtils.getTopActivity(), PerformanceDoKitView.class);\n        if (performanceDokitView != null) {\n            performanceDokitView.removePerformanceFragmentCloseListener(listener);\n        }\n    }\n\n    /**\n     * @param performanceType 参考 DataSourceFactory\n     */\n    public static void close(int performanceType, String title) {\n        PerformanceDoKitView performanceDokitView = DoKit.getDoKitView(ActivityUtils.getTopActivity(), PerformanceDoKitView.class);\n        if (performanceDokitView != null) {\n            performanceDokitView.removeItem(performanceType);\n        }\n\n        singleperformanceViewInfos.remove(title);\n    }\n\n\n    public static String getTitleByPerformanceType(Context context, int performanceType) {\n        String title = \"\";\n        switch (performanceType) {\n            case DataSourceFactory.TYPE_FPS:\n                title = context.getString(R.string.dk_kit_frame_info_desc);\n                break;\n            case DataSourceFactory.TYPE_CPU:\n                title = context.getString(R.string.dk_frameinfo_cpu);\n                break;\n            case DataSourceFactory.TYPE_RAM:\n                title = context.getString(R.string.dk_ram_detection_title);\n                break;\n            case DataSourceFactory.TYPE_NETWORK:\n                title = context.getString(R.string.dk_kit_net_monitor);\n                break;\n            default:\n                break;\n        }\n        return title;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/PerformanceFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance;\n\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.constant.BundleKey;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.titlebar.TitleBar;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileReader;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class PerformanceFragment extends BaseFragment {\n    public final static int CPU = 0;\n    public final static int RAM = 1;\n    public final static int FPS = 2;\n\n    private PerformanceDataAdapter performanceDataAdapter;\n    private PolyLineAdapter adapter;\n    private TextView parameter;\n    private TextView time;\n    private TextView date;\n\n    @Override\n\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_cpu_cache_log;\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        PerformanceDataManager.getInstance().init();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initview();\n    }\n\n    private void initview() {\n\n\n        TitleBar titleBar = findViewById(R.id.title_bar);\n        titleBar.setOnTitleBarClickListener(new TitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onLeftClick() {\n                getActivity().onBackPressed();\n            }\n\n            @Override\n            public void onRightClick() {\n\n            }\n        });\n        RecyclerView dataShow = findViewById(R.id.data_show);\n\n        PolyLineAdapter.Builder builder = new PolyLineAdapter.Builder(getActivity(), 10);\n\n        dataShow.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false));\n\n\n        RecyclerView detail = findViewById(R.id.data_detail);\n        detail.setLayoutManager(new LinearLayoutManager(getActivity()));\n        performanceDataAdapter = new PerformanceDataAdapter(getActivity());\n        detail.setAdapter(performanceDataAdapter);\n\n\n        TextView model = findViewById(R.id.model);\n        Bundle arguments = getArguments();\n        if (arguments != null) {\n            int type = arguments.getInt(BundleKey.PERFORMANCE_TYPE, CPU);\n            if (type == RAM) {\n                builder.setMaxValue((int) PerformanceDataManager.getInstance().getMaxMemory()).setMinValue(0);\n                model.setText(R.string.dk_frameinfo_ram);\n                new LoadDataTask().execute(PerformanceDataManager.getInstance().getMemoryFilePath());\n            } else if (type == FPS) {\n                builder.setMaxValue(100).setMinValue(0);\n                model.setText(R.string.dk_frameinfo_fps);\n                new LoadDataTask().execute(PerformanceDataManager.getInstance().getFpsFilePath());\n            } else {\n                builder.setMaxValue(100).setMinValue(0);\n                model.setText(R.string.dk_frameinfo_cpu);\n                new LoadDataTask().execute(PerformanceDataManager.getInstance().getCpuFilePath());\n            }\n        }\n\n        adapter = builder.build();\n        dataShow.setAdapter(adapter);\n\n\n        parameter = findViewById(R.id.parameter);\n        time = findViewById(R.id.time);\n        date = findViewById(R.id.date);\n\n        performanceDataAdapter.setOnViewClickListener(new PerformanceDataAdapter.OnViewClickListener() {\n            @Override\n            public void onViewClick(View v, PerformanceData data) {\n                updateTips(data);\n            }\n        });\n\n        adapter.setOnViewClickListener(new PolyLineAdapter.OnViewClickListener() {\n            @Override\n            public void onViewClick(int position, PerformanceData data) {\n                updateTips(data);\n            }\n        });\n\n\n    }\n\n    private void updateTips(PerformanceData data) {\n        parameter.setText(String.valueOf(data.parameter));\n        time.setText(data.time);\n        date.setText(data.date);\n    }\n\n\n    private class LoadDataTask extends AsyncTask<String, Integer, List<PerformanceData>> {\n        @Override\n        protected void onPostExecute(List<PerformanceData> result) {\n            performanceDataAdapter.append(result);\n            adapter.setData(result);\n            if (result.size() > 1) {\n                updateTips(result.get(1));\n            }\n        }\n\n        @Override\n        protected List doInBackground(String... strings) {\n            File file = new File(strings[0]);\n            ArrayList<PerformanceData> datas = new ArrayList<>();\n            if (file.exists()) {\n                BufferedReader reader = null;\n                try {\n                    reader = new BufferedReader(new FileReader(file));\n                    String tempString = null;\n                    while ((tempString = reader.readLine()) != null) {\n                        String[] split = tempString.split(\" \");\n                        datas.add(new PerformanceData(split[1], split[2], Float.valueOf(split[0])));\n                    }\n                    reader.close();\n                } catch (Exception e) {\n                    e.printStackTrace();\n                } finally {\n                    if (reader != null) {\n                        try {\n                            reader.close();\n                        } catch (IOException e1) {\n                            e1.printStackTrace();\n                        }\n                    }\n                }\n            }\n            return datas;\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/PerformanceFragmentCloseListener.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-10-11-20:39\n * 描    述：性能监控页面关闭回调接口\n * 修订历史：\n * ================================================\n */\npublic interface PerformanceFragmentCloseListener {\n\n    /**\n     * @param performanceTypee\n     */\n    void onClose(int performanceTypee);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/PolyLineAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class PolyLineAdapter extends RecyclerView.Adapter<PolyLineAdapter.ViewHolder> {\n    private List<PerformanceData> data;\n\n    public int maxValue;\n    public int minValue;\n    public int itemWidth;\n    public boolean drawDiver;\n    public float pointSize;\n    public boolean touchable;\n    private boolean showLatestLabel;\n\n    private OnViewClickListener onViewClickListener;\n\n\n    private PolyLineAdapter() {\n\n    }\n\n    private PolyLineAdapter(List<PerformanceData> data, int maxValue, int minValue, int itemWidth) {\n        this.data = data;\n        this.maxValue = maxValue;\n        this.minValue = minValue;\n        this.itemWidth = itemWidth;\n\n    }\n\n    public void setData(List<PerformanceData> d) {\n        if (data != null) {\n            data.clear();\n            data.addAll(d);\n\n            notifyDataSetChanged();\n        }\n    }\n\n    public void addData(PerformanceData bean) {\n        data.add(bean);\n        notifyDataSetChanged();\n    }\n\n    public void addData(List<PerformanceData> beans) {\n        data.addAll(beans);\n        notifyDataSetChanged();\n    }\n\n    public static class Builder {\n        private int maxValue = 100;\n        private int minValue = 0;\n        private final int itemWidth;\n        private List<PerformanceData> data = new ArrayList<>();\n        private boolean drawDiver = true;\n        private float pointSize;\n        private boolean touchable = true;\n        // 显示最后一个item的文案，用以绘制实时折线图\n        private boolean showLatestLabel;\n\n        public Builder(Context context, int itemNumber) {\n            this.itemWidth = UIUtils.getWidthPixels() / itemNumber;\n        }\n\n        public Builder setMaxValue(int maxValue) {\n            this.maxValue = maxValue;\n            return this;\n        }\n\n        public Builder setMinValue(int minValue) {\n            this.minValue = minValue;\n            return this;\n        }\n\n        public Builder setData(List<PerformanceData> data) {\n            this.data = data;\n            return this;\n        }\n\n        public Builder setDrawDiver(boolean drawDiver) {\n            this.drawDiver = drawDiver;\n            return this;\n        }\n\n        public Builder setPointSize(float size) {\n            this.pointSize = size;\n            return this;\n        }\n\n        public PolyLineAdapter build() {\n            PolyLineAdapter adapter = new PolyLineAdapter(data, maxValue, minValue, itemWidth);\n            adapter.drawDiver = drawDiver;\n            adapter.pointSize = pointSize;\n            adapter.touchable = touchable;\n            adapter.showLatestLabel = showLatestLabel;\n            return adapter;\n        }\n\n        public Builder setTouchable(boolean touchable) {\n            this.touchable = touchable;\n            return this;\n        }\n\n        public Builder setShowLatestLabel(boolean showLatestLabel) {\n            this.showLatestLabel = showLatestLabel;\n            return this;\n        }\n    }\n\n    @Override\n    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n\n        PolyLineItemView item = new PolyLineItemView(parent.getContext());\n        item.setMinValue(minValue);\n        item.setMaxValue(maxValue);\n        RecyclerView.LayoutParams layoutParams = new RecyclerView.LayoutParams(itemWidth, ViewGroup.LayoutParams.MATCH_PARENT);//这个数字表示每一个item的宽度\n        item.setLayoutParams(layoutParams);\n        return new ViewHolder(item);\n    }\n\n    @Override\n    public void onBindViewHolder(@NonNull PolyLineAdapter.ViewHolder holder, int position) {\n        holder.bindData(position);\n    }\n\n    @Override\n    public int getItemCount() {\n        return data.size();\n    }\n\n\n    public void setOnViewClickListener(OnViewClickListener onViewClickListener) {\n        this.onViewClickListener = onViewClickListener;\n    }\n\n    class ViewHolder extends RecyclerView.ViewHolder {\n        PolyLineItemView item;\n\n        public ViewHolder(View itemView) {\n            super(itemView);\n            this.item = (PolyLineItemView) itemView;\n            item.setDrawDiver(drawDiver);\n            item.setPointSize(pointSize);\n            item.setTouchable(touchable);\n        }\n\n        public void bindData(final int position) {\n            if (onViewClickListener != null) {\n                item.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        onViewClickListener.onViewClick(position, data.get(position));\n                    }\n                });\n            }\n            if (position == 0) {\n                item.setDrawLeftLine(false);\n            } else {\n                item.setDrawLeftLine(true);\n                item.setlastValue((data.get(position - 1)).parameter);\n            }\n            item.setCurrentValue((data.get(position)).parameter);\n            item.setLabel(data.get(position).date);\n            if (position == data.size() - 1) {\n                item.setDrawRightLine(false);\n            } else {\n                item.setDrawRightLine(true);\n                item.setNextValue((data.get(position + 1)).parameter);\n            }\n            item.showLabel(showLatestLabel && position > data.size() - 3);\n        }\n    }\n\n    public interface OnViewClickListener {\n        void onViewClick(int position, PerformanceData data);\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/PolyLineItemView.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.LinearGradient;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.Shader;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.R;\n\n\npublic class PolyLineItemView extends View {\n\n    private static float pointBottomY;\n    private static float pointTopY = 50;\n    private static Paint mGradientPaint;\n\n\n    private final int GRAPH_STROKE_WIDTH = 2;\n    private final float SMALL_RADIUS = 10;\n    private final float BIG_RADIUS = 20;\n    private final float CIRCLE_STROKE_WIDTH = 2;\n\n\n    private float maxValue;\n    private float minValue;\n    private float currentValue;\n    private String label;\n    private float lastValue;\n    private float nextValue;\n    private Paint mPaint = new Paint();\n    private float viewHeight;\n    private float viewWidth;\n\n    private float pointX;\n    private float pointY;\n    private float pointSize = SMALL_RADIUS;\n\n\n    private boolean drawLeftLine = true;\n    private boolean drawRightLine = true;\n    private boolean onTouch = false;\n    private boolean touchable = true;\n    private boolean showLabel;\n\n    private boolean drawDiver;\n\n    public PolyLineItemView(Context context) {\n        super(context);\n    }\n\n    public PolyLineItemView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public PolyLineItemView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n\n    public void setCurrentValue(float currentValue) {\n        if (currentValue > maxValue) {\n            currentValue = (int) maxValue;\n        }\n        if (currentValue < minValue) {\n            currentValue = (int) minValue;\n        }\n        this.currentValue = currentValue;\n        invalidate();\n    }\n\n    public void setMaxValue(int maxValue) {\n        this.maxValue = maxValue;\n    }\n\n    public void setMinValue(int minValue) {\n        this.minValue = minValue;\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n        viewHeight = getMeasuredHeight();\n        viewWidth = getMeasuredWidth();\n        pointX = viewWidth / 2;\n        if (pointBottomY == 0) {\n            pointBottomY = viewHeight - pointSize;\n        }\n        pointY = (1 - (currentValue / (maxValue - minValue))) * (pointBottomY - pointTopY) + pointTopY;\n\n        if (mGradientPaint == null) {\n            mGradientPaint = new Paint();\n            mGradientPaint.setShader(new LinearGradient(0, 0, viewWidth, viewHeight, getResources().getColor(R.color.dk_color_3300BFFF),\n                    getResources().getColor(R.color.dk_color_33434352), Shader.TileMode.CLAMP));\n        }\n    }\n\n\n    @Override\n    public void draw(Canvas canvas) {\n        super.draw(canvas);\n\n        drawGraph(canvas);\n        drawPoint(canvas);\n        drawValue(canvas);\n        drawLine(canvas);\n    }\n\n\n    private void drawValue(Canvas canvas) {\n        if (onTouch || showLabel) {\n            mPaint.setTextSize(20);\n            mPaint.setColor(Color.WHITE);\n            mPaint.setStrokeWidth(0);\n            mPaint.setStyle(Paint.Style.FILL);\n            mPaint.setTextAlign(Paint.Align.CENTER);\n            Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();\n            float baseLine = pointY - fontMetrics.bottom * 4;\n            canvas.drawText(label, viewWidth / 2, baseLine, mPaint);\n\n        }\n    }\n\n    public void setlastValue(float lastValue) {\n        if (lastValue > maxValue) {\n            lastValue = (int) maxValue;\n        }\n        if (lastValue < minValue) {\n            lastValue = (int) minValue;\n        }\n        this.lastValue = lastValue;\n    }\n\n    public void setNextValue(float nextValue) {\n        if (nextValue > maxValue) {\n            nextValue = (int) maxValue;\n        }\n        if (nextValue < minValue) {\n            nextValue = (int) minValue;\n        }\n        this.nextValue = nextValue;\n    }\n\n\n    private void drawGraph(Canvas canvas) {\n\n        mPaint.setPathEffect(null);\n\n        mPaint.setStyle(Paint.Style.FILL);\n        mPaint.setColor(getResources().getColor(R.color.dk_color_4c00C9F4));\n        mPaint.setStrokeWidth(GRAPH_STROKE_WIDTH);\n        mPaint.setAntiAlias(true);\n        if (drawLeftLine) {\n            float middleValue = currentValue - (currentValue - lastValue) / 2f;\n            float middleY = ((pointBottomY - pointTopY) * 1f / (maxValue - minValue) * (maxValue - middleValue + minValue) + pointTopY);\n            canvas.drawLine(0, middleY, pointX, pointY, mPaint);\n            drawGradient(canvas, middleY, false);\n        }\n        if (drawRightLine) {\n            float middleValue = currentValue - (currentValue - nextValue) / 2f;\n            float middleY = ((pointBottomY - pointTopY) * 1f / (maxValue - minValue) * (maxValue - middleValue + minValue) + pointTopY);\n            canvas.drawLine(pointX, pointY, viewWidth, middleY, mPaint);\n            drawGradient(canvas, middleY, true);\n        }\n    }\n\n    private void drawGradient(Canvas canvas, float middleY, boolean isRight) {\n        Path path = new Path();\n        if (!isRight) {\n            path.moveTo(0, middleY);\n            path.lineTo(pointX, pointY);\n            path.lineTo(pointX, pointBottomY);\n            path.lineTo(0, pointBottomY);\n        } else {\n            path.moveTo(pointX, pointY);\n            path.lineTo(pointX, pointBottomY);\n            path.lineTo(pointX + viewWidth / 2, pointBottomY);\n            path.lineTo(pointX + viewWidth / 2, middleY);\n        }\n        canvas.drawPath(path, mGradientPaint);\n\n    }\n\n\n    private void drawPoint(Canvas canvas) {\n        int color;\n        if (onTouch) {\n            color = getResources().getColor(R.color.dk_color_4c00C9F4);\n            mPaint.setColor(color);\n            mPaint.setPathEffect(null);\n            mPaint.setStrokeWidth(CIRCLE_STROKE_WIDTH);\n            mPaint.setStyle(Paint.Style.FILL);\n            canvas.drawCircle(pointX, pointY, BIG_RADIUS, mPaint);\n        }\n        color = getResources().getColor(R.color.dk_color_ff00C9F4);\n        mPaint.setColor(color);\n        mPaint.setStrokeWidth(CIRCLE_STROKE_WIDTH);\n        canvas.drawCircle(pointX, pointY, pointSize, mPaint);\n    }\n\n    private void drawLine(Canvas canvas) {\n        if (!drawDiver) {\n            return;\n        }\n        mPaint.setColor((getResources().getColor(R.color.dk_color_999999)));\n        mPaint.setPathEffect(null);\n\n        mPaint.setStrokeWidth(GRAPH_STROKE_WIDTH);\n        mPaint.setStyle(Paint.Style.FILL);\n        if (drawLeftLine) {\n            canvas.drawLine(0, pointBottomY, viewWidth / 2, pointBottomY, mPaint);\n        }\n        if (drawRightLine) {\n            canvas.drawLine(viewWidth / 2, pointBottomY, viewWidth, pointBottomY, mPaint);\n\n        }\n    }\n\n    public void setDrawLeftLine(boolean drawLeftLine) {\n        this.drawLeftLine = drawLeftLine;\n    }\n\n    public void setDrawRightLine(boolean drawRightLine) {\n        this.drawRightLine = drawRightLine;\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        if (!touchable) {\n            return super.onTouchEvent(event);\n        }\n        switch (event.getAction()) {\n            case MotionEvent.ACTION_DOWN:\n                onTouch = true;\n                setBackgroundResource(R.drawable.dk_line_chart_selected_background);\n                break;\n            case MotionEvent.ACTION_UP:\n            case MotionEvent.ACTION_CANCEL:\n                onTouch = false;\n                setBackgroundResource(0);\n                break;\n        }\n        return super.onTouchEvent(event);\n    }\n\n    public void setDrawDiver(boolean drawDiver) {\n        this.drawDiver = drawDiver;\n    }\n\n    public void setPointSize(float pointSize) {\n        if (pointSize != 0) {\n            this.pointSize = pointSize;\n        }\n    }\n\n    public void setTouchable(boolean touchable) {\n        this.touchable = touchable;\n    }\n\n    public void showLabel(boolean showLatestLabel) {\n        this.showLabel = showLatestLabel;\n    }\n\n    public void setLabel(String label) {\n        this.label = label;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/datasource/CpuDataSource.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance.datasource;\n\nimport com.didichuxing.doraemonkit.kit.performance.PerformanceDataManager;\nimport com.didichuxing.doraemonkit.kit.performance.widget.LineData;\n\npublic class CpuDataSource implements IDataSource {\n    @Override\n    public LineData createData() {\n        float rate = PerformanceDataManager.getInstance().getLastCpuRate();\n        return LineData.obtain(rate, Math.round(rate) + \"%\");\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/datasource/DataSourceFactory.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance.datasource;\n\n\nimport androidx.annotation.NonNull;\n\n/**\n * @desc: 产生数据源对象的工厂类\n */\npublic class DataSourceFactory {\n    public static final int TYPE_NETWORK = 1;\n    public static final int TYPE_CPU = 2;\n    public static final int TYPE_RAM = 3;\n    public static final int TYPE_FPS = 4;\n\n    @NonNull\n    public static IDataSource createDataSource(int type) {\n        switch (type) {\n            case TYPE_NETWORK:\n                return new NetworkDataSource();\n            case TYPE_CPU:\n                return new CpuDataSource();\n            case TYPE_RAM:\n                return new RamDataSource();\n            case TYPE_FPS:\n                return new FpsDataSource();\n            default:\n                return new DefaultDataSource();\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/datasource/DefaultDataSource.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance.datasource;\n\nimport com.didichuxing.doraemonkit.kit.performance.widget.LineData;\n\npublic class DefaultDataSource implements IDataSource {\n    @Override\n    public LineData createData() {\n        float rate = 50.0f;\n        return LineData.obtain(rate, Math.round(rate) + \"\");\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/datasource/FpsDataSource.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance.datasource;\n\nimport com.didichuxing.doraemonkit.kit.performance.PerformanceDataManager;\nimport com.didichuxing.doraemonkit.kit.performance.widget.LineData;\n\npublic class FpsDataSource implements IDataSource {\n    @Override\n    public LineData createData() {\n        float rate = PerformanceDataManager.getInstance().getLastFrameRate();\n        return LineData.obtain(rate, Math.round(rate) + \"\");\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/datasource/IDataSource.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance.datasource;\n\nimport com.didichuxing.doraemonkit.kit.performance.widget.LineData;\n\n/**\n * @desc: 折线图绘制的数据源接口\n */\npublic interface IDataSource {\n    /**\n     * 返回在折线图上显示的最新数据\n     *\n     * @return\n     */\n    LineData createData();\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/datasource/NetworkDataSource.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance.datasource;\n\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\nimport com.didichuxing.doraemonkit.kit.network.utils.ByteUtil;\nimport com.didichuxing.doraemonkit.kit.performance.widget.LineData;\n\n/**\n * @desc: 抓包数据源\n */\npublic class NetworkDataSource implements IDataSource {\n    private static final String TAG = \"NetworkDataSource\";\n    private long latestTotalLength = -1;\n\n    @Override\n    public LineData createData() {\n        long diff = 0;\n        long totalSize = NetworkManager.get().getTotalSize();\n        if (latestTotalLength >= 0) {\n            diff = totalSize - latestTotalLength;\n            if (diff < 0) {\n                diff = 0;\n            }\n        }\n        latestTotalLength = totalSize;\n        if (diff == 0) {\n            return LineData.obtain((float) Math.ceil(diff / 1024f), null);\n        } else {\n            return LineData.obtain((float) Math.ceil(diff / 1024f), ByteUtil.getPrintSize(diff));\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/datasource/RamDataSource.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance.datasource;\n\nimport com.didichuxing.doraemonkit.kit.performance.PerformanceDataManager;\nimport com.didichuxing.doraemonkit.kit.performance.widget.LineData;\n\npublic class RamDataSource implements IDataSource {\n    private float mMaxRam;\n\n    public RamDataSource() {\n        mMaxRam = (float) (Runtime.getRuntime().maxMemory() * 1.0 / (1024 * 1024));\n    }\n\n    @Override\n    public LineData createData() {\n        float info = PerformanceDataManager.getInstance().getLastMemoryInfo();\n        return LineData.obtain(info / mMaxRam * 100, Math.round(info) + \"MB\");\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/performanceViewInfo.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-10-14-11:15\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class performanceViewInfo {\n    int performanceType;\n    String title;\n    int interval;\n\n    public performanceViewInfo(int performanceType, String title, int interval) {\n        this.performanceType = performanceType;\n        this.title = title;\n        this.interval = interval;\n    }\n\n\n    public int getPerformanceType() {\n        return performanceType;\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public int getInterval() {\n        return interval;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/widget/CardiogramView.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance.widget;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.os.Handler;\nimport android.util.AttributeSet;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.kit.performance.datasource.IDataSource;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * @desc: 实现平滑移动的折线图\n */\npublic class CardiogramView extends View implements Runnable {\n    /**\n     * 一屏幕可现实的item个数，默认是12个\n     */\n    private static final float DEFAULT_ITEM_COUNT = 12;\n\n    /**\n     * 数组内数据最大值,+2的原因是因为最多有DEFAULT_ITEM_COUNT+1个item被显示（左右各一半），而第13个item必须依赖第14个item的点,才能画出填充区域\n     */\n    private static final float MAX_ITEM_COUNT = DEFAULT_ITEM_COUNT + 2;\n    /**\n     * The default amount of time in ms between animation frames.\n     */\n    private static final int DEFAULT_FRAME_DELAY = 32;\n    private static final int DEFAULT_FRAME_COUNT = 2000 / DEFAULT_FRAME_DELAY;\n    private float mItemWidth;\n    /**\n     * frame count to move an item\n     */\n    private int mTotalFrameCount = DEFAULT_FRAME_COUNT;\n    /**\n     * current frame count while moving an item ,when it get to mTotalFrameCount,call mList.detach(0)\n     */\n    private int mCurrentFrameCount = 0;\n    private LineRender mRender;\n    private List<LineData> mList = Collections.synchronizedList(new ArrayList<LineData>());\n    private IDataSource mDataSource;\n\n    private Handler mHandler = new Handler();\n\n    public CardiogramView(Context context) {\n        super(context);\n        init(context);\n    }\n\n    public CardiogramView(Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        init(context);\n    }\n\n    private void init(Context context) {\n        mRender = new LineRender(context);\n        mRender.setMaxValue(100);\n        mRender.setMinValue(0);\n        mRender.setPointSize(5);\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        mItemWidth = w / DEFAULT_ITEM_COUNT;\n        mRender.measure(mItemWidth, h);\n        super.onSizeChanged(w, h, oldw, oldh);\n    }\n\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        canvas.save();\n        float translateX = getCanvasTranslate();\n        canvas.translate(translateX, 0);\n        drawLine(canvas);\n        checkFirstItemBound();\n        canvas.restore();\n    }\n\n    private void checkFirstItemBound() {\n        mCurrentFrameCount++;\n        if (mCurrentFrameCount >= mTotalFrameCount) {\n            mCurrentFrameCount = 0;\n            if (mDataSource != null) {\n                mList.add(mDataSource.createData());\n            }\n            if (mList.size() > MAX_ITEM_COUNT) {\n                mList.remove(0).release();\n            }\n        }\n    }\n\n    private float getCanvasTranslate() {\n        return -mItemWidth * (mCurrentFrameCount / (float) mTotalFrameCount) + mItemWidth * (MAX_ITEM_COUNT - mList.size());\n    }\n\n    private void drawLine(Canvas canvas) {\n        // 画波动折线\n        for (int index = 0; index < Math.min(mList.size(), DEFAULT_ITEM_COUNT + 1); index++) {\n            mRender.setCurrentValue(index, mList.get(index).value);\n            // 倒数第二个item显示文字\n            if (index == mList.size() - 2) {\n                mRender.setShowLabel(true);\n                mRender.setLabelAlpha(1f);\n                mRender.setLabel(mList.get(index).label);\n            } else if (index == mList.size() - 3) {\n                //倒数第三个item显示渐变文字\n                mRender.setLabel(mList.get(index).label);\n                mRender.setLabelAlpha((1 - mCurrentFrameCount / (float) mTotalFrameCount));\n                mRender.setShowLabel(true);\n            } else {\n                mRender.setLabel(mList.get(index).label);\n                mRender.setShowLabel(false);\n            }\n            if (index == mList.size() - 1) {\n                mRender.setNextValue(0);\n                mRender.setDrawRightLine(false);\n            } else {\n                mRender.setDrawRightLine(true);\n                mRender.setNextValue((mList.get(index + 1).value));\n            }\n            mRender.draw(canvas);\n        }\n    }\n\n    public void startMove() {\n        mHandler.removeCallbacks(this);\n        mHandler.post(this);\n    }\n\n    public void stopMove() {\n        mHandler.removeCallbacks(this);\n    }\n\n\n    /**\n     * 设置滚动间隔\n     *\n     * @param milliSecond 滚动一个item距离需要的时间，单位毫秒\n     */\n    public void setInterval(int milliSecond) {\n        mTotalFrameCount = milliSecond / DEFAULT_FRAME_DELAY;\n    }\n\n    public void setDataSource(@NonNull IDataSource dataSource) {\n        mDataSource = dataSource;\n        mList.clear();\n        mList.add(dataSource.createData());\n    }\n\n    @Override\n    public void run() {\n        invalidate();\n        mHandler.postDelayed(this, DEFAULT_FRAME_DELAY);\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/widget/LineChart.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance.widget;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.widget.FrameLayout;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.performance.datasource.IDataSource;\n\n/**\n * @desc: 实时折线图\n */\npublic class LineChart extends FrameLayout {\n    private TextView mTitle;\n    private int performanceType;\n    private CardiogramView mLine;\n\n    public int getPerformanceType() {\n        return performanceType;\n    }\n\n    public void setPerformanceType(int performanceType) {\n        this.performanceType = performanceType;\n    }\n\n    public LineChart(@NonNull Context context) {\n        super(context);\n        initView(context);\n    }\n\n    public LineChart(@NonNull Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        initView(context);\n    }\n\n    private void initView(Context context) {\n        inflate(context, R.layout.dk_view_line_chart, this);\n        mTitle = findViewById(R.id.tv_title);\n        mLine = findViewById(R.id.line_chart_view);\n    }\n\n    public void setTitle(String title) {\n        mTitle.setText(title);\n    }\n\n    public void startMove() {\n        mLine.startMove();\n    }\n\n    public void stopMove() {\n        mLine.stopMove();\n    }\n\n    public void setInterval(int interval) {\n        mLine.setInterval(interval);\n    }\n\n    public void setDataSource(@NonNull IDataSource dataSource) {\n        mLine.setDataSource(dataSource);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/widget/LineData.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance.widget;\n\nimport androidx.core.util.Pools;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-10-10-16:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class LineData {\n    public float value;\n    public String label;\n    private static Pools.SimplePool<LineData> mPool = new Pools.SimplePool<>(50);\n\n\n    /**\n     * @param value 影响折线幅度的值，必须大于minValue小于maxValue\n     * @param label item从右边进入时显示的值，为null的时候不显示\n     */\n    public LineData(float value, String label) {\n        this.value = value;\n        this.label = label;\n    }\n\n    public static LineData obtain(float value, String label) {\n        LineData lineData = mPool.acquire();\n        if (lineData == null) {\n            return new LineData(value, label);\n        }\n        lineData.value = value;\n        lineData.label = label;\n        return lineData;\n    }\n\n    public void release() {\n        mPool.release(this);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/performance/widget/LineRender.java",
    "content": "package com.didichuxing.doraemonkit.kit.performance.widget;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.LinearGradient;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.Shader;\nimport android.text.TextUtils;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\n/**\n * @desc: 折线图渲染器\n */\npublic class LineRender {\n\n    // 顶部预留50sp,防止文字绘制出界\n    private int mPaddingTop = 50;\n    // 底部预留2dp,防止圆点绘制补全\n    private int mPaddingBottom;\n\n    private final int GRAPH_STROKE_WIDTH = 2;\n    private final float SMALL_RADIUS = 10;\n    private final float CIRCLE_STROKE_WIDTH = 2;\n\n    private Context mContext;\n\n    private float maxValue;//最高值\n    private float minValue;//最低值\n    private String label;//显示值\n    private float nextValue;//下一个值\n    private Paint mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n    private Paint mLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n    private Paint mGradientPaint = new Paint();\n    private Paint mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n    private float viewHeight;\n    private float viewWidth;\n\n    private float pointX;//所有点的x坐标\n    private float pointY;//当前点的Y\n    private float pointSize = SMALL_RADIUS;// 圆点大小\n\n\n    private boolean drawRightLine = true;//是否画右边的线\n    private boolean showLabel;\n    private float labelAlpha;\n    private float startPosition;\n    private float baseLine = 20;\n\n    private Path mGradientPath = new Path();\n\n    public LineRender(Context context) {\n        mContext = context;\n        mPaddingBottom = UIUtils.dp2px(2);\n    }\n\n\n    public void setMaxValue(int maxValue) {\n        this.maxValue = maxValue;\n    }\n\n    public void setMinValue(int minValue) {\n        this.minValue = minValue;\n    }\n\n    protected void measure(float width, float height) {\n        viewHeight = height - mPaddingBottom - mPaddingTop;\n        viewWidth = width;\n\n        initPaint();\n    }\n\n    private void initPaint() {\n        mGradientPaint.setShader(new LinearGradient(0, 0, viewWidth, viewHeight, mContext.getResources().getColor(R.color.dk_color_3300BFFF),\n                mContext.getResources().getColor(R.color.dk_color_33434352), Shader.TileMode.CLAMP));\n\n        mLabelPaint.setTextSize(mContext.getResources().getDimensionPixelSize(R.dimen.dk_font_size_10));\n        mLabelPaint.setColor(Color.WHITE);\n        mLabelPaint.setTextAlign(Paint.Align.CENTER);\n\n        mLinePaint.setPathEffect(null);\n\n        mLinePaint.setStyle(Paint.Style.FILL);\n        mLinePaint.setColor(mContext.getResources().getColor(R.color.dk_color_4c00C9F4));\n        mLinePaint.setStrokeWidth(GRAPH_STROKE_WIDTH);\n        mLinePaint.setAntiAlias(true);\n\n        int color = mContext.getResources().getColor(R.color.dk_color_ff00C9F4);\n        mPointPaint.setColor(color);\n        mPointPaint.setStrokeWidth(CIRCLE_STROKE_WIDTH);\n    }\n\n\n    public void draw(Canvas canvas) {\n        drawGraph(canvas);\n        drawGradient(canvas);\n        drawPoint(canvas);\n        drawLabel(canvas);\n    }\n\n\n    /**\n     * 画数字\n     *\n     * @param canvas\n     */\n    private void drawLabel(Canvas canvas) {\n        if (showLabel && !TextUtils.isEmpty(label)) {\n            mLabelPaint.setAlpha((int) (labelAlpha * 0xff));\n            canvas.drawText(label, startPosition, pointY - baseLine, mLabelPaint);\n        }\n    }\n\n    public void setNextValue(float nextValue) {\n        if (nextValue > maxValue) {\n            nextValue = (int) maxValue;\n        }\n        if (nextValue < minValue) {\n            nextValue = (int) minValue;\n        }\n        this.nextValue = (1 - nextValue / (maxValue - minValue)) * viewHeight + mPaddingTop;\n    }\n\n    public void setCurrentValue(int index, float currentValue) {\n        if (currentValue > maxValue) {\n            currentValue = (int) maxValue;\n        }\n        if (currentValue < minValue) {\n            currentValue = (int) minValue;\n        }\n        startPosition = index * viewWidth;\n        pointX = startPosition;\n        pointY = (1 - currentValue / (maxValue - minValue)) * viewHeight + mPaddingTop;\n    }\n\n\n    /**\n     * 画折线\n     *\n     * @param canvas\n     */\n\n    private void drawGraph(Canvas canvas) {\n        if (drawRightLine) {\n            float middleY = nextValue;\n            canvas.drawLine(startPosition, pointY, viewWidth + startPosition, middleY, mLinePaint);\n        }\n    }\n\n    private void drawGradient(Canvas canvas) {\n        if (drawRightLine) {\n            mGradientPath.rewind();\n            mGradientPath.moveTo(pointX, pointY);\n            mGradientPath.lineTo(pointX, viewHeight + mPaddingTop);\n            mGradientPath.lineTo(pointX + viewWidth, viewHeight + mPaddingTop);\n            mGradientPath.lineTo(pointX + viewWidth, nextValue);\n            canvas.drawPath(mGradientPath, mGradientPaint);\n        }\n\n    }\n\n    /**\n     * 画数字下面的小圆圈\n     *\n     * @param canvas\n     */\n    private void drawPoint(Canvas canvas) {\n        canvas.drawCircle(pointX, pointY, pointSize, mPointPaint);\n    }\n\n\n    public void setDrawRightLine(boolean drawRightLine) {\n        this.drawRightLine = drawRightLine;\n    }\n\n\n    public void setPointSize(float pointSize) {\n        if (pointSize != 0) {\n            this.pointSize = pointSize;\n        }\n    }\n\n\n    public void setLabel(String label) {\n        this.label = label;\n    }\n\n    public void setShowLabel(boolean show) {\n        showLabel = show;\n    }\n\n    public void setLabelAlpha(float alpha) {\n        labelAlpha = alpha;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/sysinfo/DevelopmentPageKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.sysinfo\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.util.DoKitSystemUtil\nimport com.google.auto.service.AutoService\n\n/**\n * 进入开发者选项\n * Created by jint on 2018/6/22.\n */\n@AutoService(AbstractKit::class)\nclass DevelopmentPageKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_develop\n    override val icon: Int\n        get() = R.mipmap.dk_kit_devlop\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        DoKitSystemUtil.startDevelopmentActivity(activity)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_comm_ck_devpage\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/sysinfo/LocalLangKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.sysinfo\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.util.DoKitSystemUtil\nimport com.google.auto.service.AutoService\n\n/**\n * 进入本地语言设置页面\n * Created by jint on 2018/6/22.\n */\n@AutoService(AbstractKit::class)\nclass LocalLangKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_local_lang\n    override val icon: Int\n        get() = R.mipmap.dk_kit_local_lang\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        DoKitSystemUtil.startLocalActivity(activity)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_comm_ck_local_lang\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/sysinfo/ServiceRunningKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.sysinfo\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.util.DoKitSystemUtil\nimport com.google.auto.service.AutoService\n\n/**\n * 进入开发者选项\n * Created by jint on 2018/6/22.\n */\n@AutoService(AbstractKit::class)\nclass ServiceRunningKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_service_running\n    override val icon: Int\n        get() = R.mipmap.dk_kit_s_runing\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        DoKitSystemUtil.startServiceRunningActivity(activity)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_comm_ck_develop\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/sysinfo/SysInfoFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.sysinfo;\n\nimport android.Manifest;\nimport android.annotation.SuppressLint;\nimport android.annotation.TargetApi;\nimport android.content.pm.PackageInfo;\nimport android.os.Build;\nimport android.os.Bundle;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.text.TextUtils;\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.util.DeviceUtils;\nimport com.didichuxing.doraemonkit.util.NetworkUtils;\nimport com.didichuxing.doraemonkit.util.PhoneUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.recyclerview.DividerItemDecoration;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\nimport com.didichuxing.doraemonkit.util.DokitDeviceUtils;\nimport com.didichuxing.doraemonkit.util.DoKitExecutorUtil;\nimport com.didichuxing.doraemonkit.util.DoKitPermissionUtil;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 手机app信息\n * Created by zhangweida on 2018/6/25.\n */\n\npublic class SysInfoFragment extends BaseFragment {\n    private RecyclerView mInfoList;\n    private SysInfoItemAdapter mInfoItemAdapter;\n\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_sys_info;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        try {\n            initView();\n            initData();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n    private void initView() {\n        mInfoList = findViewById(R.id.info_list);\n        HomeTitleBar titleBar = findViewById(R.id.title_bar);\n        titleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                getActivity().finish();\n            }\n        });\n        LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());\n        mInfoList.setLayoutManager(layoutManager);\n        mInfoItemAdapter = new SysInfoItemAdapter(getContext());\n        mInfoList.setAdapter(mInfoItemAdapter);\n        DividerItemDecoration decoration = new DividerItemDecoration(DividerItemDecoration.VERTICAL);\n        decoration.setDrawable(getResources().getDrawable(R.drawable.dk_divider));\n        mInfoList.addItemDecoration(decoration);\n    }\n\n    private void initData() throws Exception {\n        List<SysInfoItem> sysInfoItems = new ArrayList<>();\n        addAppData(sysInfoItems);\n        addDeviceData(sysInfoItems);\n        if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M) {\n            addPermissionData(sysInfoItems);\n        } else {\n            addPermissionDataUnreliable();\n        }\n        mInfoItemAdapter.setData(sysInfoItems);\n    }\n\n    //App 信息\n    private void addAppData(List<SysInfoItem> sysInfoItems) {\n        PackageInfo pi = DokitDeviceUtils.getPackageInfo(getContext());\n        sysInfoItems.add(new TitleItem(getString(R.string.dk_sysinfo_app_info)));\n        sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_package_name), pi.packageName));\n        sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_package_version_name), pi.versionName));\n        sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_package_version_code), String.valueOf(pi.versionCode)));\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_package_min_sdk), String.valueOf(getContext().getApplicationInfo().minSdkVersion)));\n        }\n        sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_package_target_sdk), String.valueOf(getContext().getApplicationInfo().targetSdkVersion)));\n//        try {\n//            sysInfoItems.add(new SysInfoItem(\"Sign MD5\", AppUtils.getAppSignatureMD5()));\n//        } catch (Exception e) {\n//            e.printStackTrace();\n//        }\n//        try {\n//            sysInfoItems.add(new SysInfoItem(\"Sign SHA1\", AppUtils.getAppSignatureSHA1()));\n//        } catch (Exception e) {\n//            e.printStackTrace();\n//        }\n//        try {\n//            sysInfoItems.add(new SysInfoItem(\"Sign SHA256\", AppUtils.getAppSignatureSHA256()));\n//        } catch (Exception e) {\n//            e.printStackTrace();\n//        }\n    }\n\n\n    //手机信息\n    @SuppressLint(\"MissingPermission\")\n    private void addDeviceData(List<SysInfoItem> sysInfoItems) throws Exception {\n        sysInfoItems.add(new TitleItem(getString(R.string.dk_sysinfo_device_info)));\n        sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_brand_and_model), Build.MANUFACTURER + \" \" + Build.MODEL));\n        sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_android_version), Build.VERSION.RELEASE + \" (\" + Build.VERSION.SDK_INT + \")\"));\n        try {\n            sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_ext_storage_free), DokitDeviceUtils.getSDCardSpace(getContext())));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        try {\n            sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_rom_free), DokitDeviceUtils.getRomSpace(getContext())));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        try {\n            sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_display_size), UIUtils.getWidthPixels() + \"x\" + UIUtils.getRealHeightPixels()));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        try {\n            sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_display_inch), \"\" + UIUtils.getScreenInch(getActivity())));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        try {\n            sysInfoItems.add(new SysInfoItem(\"ROOT\", String.valueOf(DeviceUtils.isDeviceRooted())));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        try {\n            sysInfoItems.add(new SysInfoItem(\"DENSITY\", String.valueOf(UIUtils.getDensity())));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        try {\n            sysInfoItems.add(new SysInfoItem(\"IP\", TextUtils.isEmpty(NetworkUtils.getIPAddress(true)) ? \"null\" : NetworkUtils.getIPAddress(true)));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        try {\n            sysInfoItems.add(new SysInfoItem(\"Mac\", TextUtils.isEmpty(DeviceUtils.getMacAddress()) ? \"null\" : DeviceUtils.getMacAddress()));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        try {\n            sysInfoItems.add(new SysInfoItem(\"IMEI\", TextUtils.isEmpty(PhoneUtils.getIMEI()) ? \"null\" : PhoneUtils.getIMEI()));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        try {\n            sysInfoItems.add(new SysInfoItem(\"WebView\", DokitDeviceUtils.getWebViewChromeVersion(getContext())));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n    /**\n     * 不可靠的检测权限方式\n     */\n    private void addPermissionDataUnreliable() {\n        DoKitExecutorUtil.execute(new Runnable() {\n            @Override\n            public void run() {\n                final List<SysInfoItem> list = new ArrayList<>();\n                list.add(new TitleItem(getString(R.string.dk_sysinfo_permission_info_unreliable)));\n                list.add(new SysInfoItem(getString(R.string.dk_sysinfo_permission_location), DoKitPermissionUtil.checkLocationUnreliable(getContext()) ? \"YES\" : \"NO\", true));\n                list.add(new SysInfoItem(getString(R.string.dk_sysinfo_permission_sdcard), DoKitPermissionUtil.checkStorageUnreliable() ? \"YES\" : \"NO\", true));\n                list.add(new SysInfoItem(getString(R.string.dk_sysinfo_permission_camera), DoKitPermissionUtil.checkCameraUnreliable() ? \"YES\" : \"NO\", true));\n                list.add(new SysInfoItem(getString(R.string.dk_sysinfo_permission_record), DoKitPermissionUtil.checkRecordUnreliable() ? \"YES\" : \"NO\", true));\n                list.add(new SysInfoItem(getString(R.string.dk_sysinfo_permission_read_phone), DoKitPermissionUtil.checkReadPhoneUnreliable(getContext()) ? \"YES\" : \"NO\", true));\n                list.add(new SysInfoItem(getString(R.string.dk_sysinfo_permission_contact), DoKitPermissionUtil.checkReadContactUnreliable(getContext()) ? \"YES\" : \"NO\", true));\n                getView().post(new Runnable() {\n                    @Override\n                    public void run() {\n                        if (SysInfoFragment.this.isDetached()) {\n                            return;\n                        }\n                        mInfoItemAdapter.append(list);\n                    }\n                });\n            }\n        });\n    }\n\n    @TargetApi(Build.VERSION_CODES.M)\n    private void addPermissionData(List<SysInfoItem> sysInfoItems) {\n        sysInfoItems.add(new TitleItem(getString(R.string.dk_sysinfo_permission_info)));\n        String[] p1 = {\n            Manifest.permission.ACCESS_COARSE_LOCATION,\n            Manifest.permission.ACCESS_FINE_LOCATION\n        };\n        sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_permission_location), checkPermission(p1), true));\n        String[] p2 = {\n            Manifest.permission.READ_EXTERNAL_STORAGE,\n            Manifest.permission.WRITE_EXTERNAL_STORAGE\n        };\n        sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_permission_sdcard), checkPermission(p2), true));\n        String[] p3 = {\n            Manifest.permission.CAMERA\n        };\n        sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_permission_camera), checkPermission(p3), true));\n        String[] p4 = {\n            Manifest.permission.RECORD_AUDIO\n        };\n        sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_permission_record), checkPermission(p4), true));\n        String[] p5 = {\n            Manifest.permission.READ_PHONE_STATE\n        };\n        sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_permission_read_phone), checkPermission(p5), true));\n        String[] p6 = {\n            Manifest.permission.READ_CONTACTS\n        };\n        sysInfoItems.add(new SysInfoItem(getString(R.string.dk_sysinfo_permission_contact), checkPermission(p6), true));\n    }\n\n    private String checkPermission(String... perms) {\n        try {\n            return DoKitPermissionUtil.hasPermissions(getContext(), perms) ? \"YES\" : \"NO\";\n        } catch (NullPointerException e) {\n            e.printStackTrace();\n        }\n        return \"NO\";\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/sysinfo/SysInfoItem.java",
    "content": "package com.didichuxing.doraemonkit.kit.sysinfo;\n\n/**\n * Created by wanglikun on 2018/9/14.\n */\n\npublic class SysInfoItem {\n    public boolean isPermission;\n    public final String name;\n    public final String value;\n\n\n    public SysInfoItem(String name, String value) {\n        this.name = name;\n        this.value = value;\n    }\n\n    public SysInfoItem(String name, String value, boolean isPermission) {\n        this.name = name;\n        this.value = value;\n        this.isPermission = isPermission;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/sysinfo/SysInfoItemAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.sysinfo;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.util.PermissionUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\nimport com.didichuxing.doraemonkit.widget.textview.LabelTextView;\n\n\n/**\n * Created by wanglikun on 2018/9/14.\n */\n\npublic class SysInfoItemAdapter extends AbsRecyclerAdapter<AbsViewBinder<SysInfoItem>, SysInfoItem> {\n\n    private final static int TYPE_ITEM = 0;\n    private final static int TYPE_TITLE = 1;\n\n    public SysInfoItemAdapter(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected AbsViewBinder<SysInfoItem> createViewHolder(View view, int viewType) {\n        if (viewType == TYPE_TITLE) {\n            return new TitleItemViewHolder(view);\n        } else {\n            return new SysInfoItemViewHolder(view);\n        }\n    }\n\n    @Override\n    public int getItemViewType(int position) {\n        if (getData().get(position) instanceof TitleItem) {\n            return TYPE_TITLE;\n        } else {\n            return TYPE_ITEM;\n        }\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        if (viewType == TYPE_TITLE) {\n            return inflater.inflate(R.layout.dk_item_sys_title, parent, false);\n        } else {\n            return inflater.inflate(R.layout.dk_item_sys_info, parent, false);\n        }\n    }\n\n    public class SysInfoItemViewHolder extends AbsViewBinder<SysInfoItem> {\n        private LabelTextView mLabelText;\n\n        public SysInfoItemViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            mLabelText = getView(R.id.label_text);\n        }\n\n        @Override\n        public void bind(final SysInfoItem sysInfoItem) {\n            mLabelText.setLabel(sysInfoItem.name);\n            mLabelText.setText(sysInfoItem.value);\n            mLabelText.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    if (sysInfoItem.isPermission) {\n                        PermissionUtils.launchAppDetailsSettings();\n                    }\n                }\n            });\n        }\n    }\n\n    public class TitleItemViewHolder extends AbsViewBinder<SysInfoItem> {\n        private TextView mTextView;\n\n        public TitleItemViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            mTextView = getView(R.id.tv_title);\n        }\n\n        @Override\n        public void bind(SysInfoItem sysInfoItem) {\n            mTextView.setText(sysInfoItem.name);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/sysinfo/SysInfoKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.sysinfo\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n/**\n * 设备、app信息\n * Created by zhangweida on 2018/6/22.\n */\n@AutoService(AbstractKit::class)\nclass SysInfoKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_sysinfo\n    override val icon: Int\n        get() = R.mipmap.dk_sys_info\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        startUniversalActivity(SysInfoFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_comm_ck_appinfo\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/sysinfo/ThirdLibInfoFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.sysinfo;\n\nimport android.os.Bundle;\nimport android.text.Editable;\nimport android.text.TextWatcher;\nimport android.view.View;\nimport android.widget.EditText;\nimport android.widget.RadioButton;\nimport android.widget.RadioGroup;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.aop.DokitThirdLibInfo;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.recyclerview.DividerItemDecoration;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 第三方库信息\n * Created by jintai on 2020/10/20.\n */\n\npublic class ThirdLibInfoFragment extends BaseFragment {\n    private RecyclerView mInfoList;\n    private ThirdLibInfoItemAdapter mInfoItemAdapter;\n    private EditText mEditText;\n    private RadioGroup mSortGroup;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_third_lib_info;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        try {\n            initView();\n            initData();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n    private void initView() {\n        mInfoList = findViewById(R.id.info_list);\n        HomeTitleBar titleBar = findViewById(R.id.title_bar);\n        titleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                getActivity().finish();\n            }\n        });\n        mEditText = findViewById(R.id.edittext);\n        mEditText.addTextChangedListener(new TextWatcher() {\n            @Override\n            public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n\n            }\n\n            @Override\n            public void onTextChanged(CharSequence s, int start, int before, int count) {\n                String keyWords = s.toString().trim();\n                if (keyWords.isEmpty()) {\n                    initData();\n                } else {\n                    notifyThirdKeyWordsLibInfos(keyWords);\n                }\n            }\n\n            @Override\n            public void afterTextChanged(Editable s) {\n\n            }\n        });\n\n        LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());\n        mInfoList.setLayoutManager(layoutManager);\n        mInfoItemAdapter = new ThirdLibInfoItemAdapter(getContext());\n        mInfoList.setAdapter(mInfoItemAdapter);\n        DividerItemDecoration decoration = new DividerItemDecoration(DividerItemDecoration.VERTICAL);\n        decoration.setDrawable(getResources().getDrawable(R.drawable.dk_divider));\n        mInfoList.addItemDecoration(decoration);\n\n        mSortGroup = findViewById(R.id.sort_option);\n        mSortGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {\n            @Override\n            public void onCheckedChanged(RadioGroup group, int checkedId) {\n                mInfoItemAdapter.setData(sortSysInfoItems(null));\n            }\n        });\n\n        if (DokitThirdLibInfo.THIRD_LIB_INFOS == null || DokitThirdLibInfo.THIRD_LIB_INFOS.isEmpty()) {\n            ToastUtils.showShort(\"检查gradle.properties中的DOKIT_THIRD_LIB_SWITCH值是否为true\");\n        }\n\n    }\n\n    private void initData() {\n        List<SysInfoItem> sysInfoItems = new ArrayList<>();\n        addThirdLibInfos(sysInfoItems);\n        mInfoItemAdapter.setData(sortSysInfoItems(sysInfoItems));\n    }\n\n    //添加所有三方库信息\n    private void addThirdLibInfos(List<SysInfoItem> sysInfoItems) {\n        for (Map.Entry<String, String> entry : DokitThirdLibInfo.THIRD_LIB_INFOS.entrySet()) {\n            sysInfoItems.add(new SysInfoItem(entry.getKey(), entry.getValue()));\n        }\n    }\n\n    private List<SysInfoItem> sortSysInfoItems(List<SysInfoItem> sysInfoItems) {\n        List<SysInfoItem> data = sysInfoItems == null ? mInfoItemAdapter.getData() : sysInfoItems;\n        if (mSortGroup.getCheckedRadioButtonId() == R.id.sort_size) {\n            Collections.sort(data, new Comparator<SysInfoItem>() {\n                @Override\n                public int compare(SysInfoItem o1, SysInfoItem o2) {\n                    return (int) (Long.parseLong(o2.value) - Long.parseLong(o1.value));\n                }\n            });\n        } else {\n            Collections.sort(data, new Comparator<SysInfoItem>() {\n                @Override\n                public int compare(SysInfoItem o1, SysInfoItem o2) {\n                    return o1.name.compareToIgnoreCase(o2.name);\n                }\n            });\n        }\n        return data;\n    }\n\n    //添加所有三方库信息\n    private void notifyThirdKeyWordsLibInfos(String keyWords) {\n        List<SysInfoItem> sysInfoItems = new ArrayList<>();\n        for (Map.Entry<String, String> entry : DokitThirdLibInfo.THIRD_LIB_INFOS.entrySet()) {\n            if (entry.getKey().toLowerCase().startsWith(keyWords.toLowerCase()) || entry.getKey().toLowerCase().contains(keyWords.toLowerCase())) {\n                sysInfoItems.add(new SysInfoItem(entry.getKey(), entry.getValue()));\n            }\n        }\n        mInfoItemAdapter.setData(sortSysInfoItems(sysInfoItems));\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/sysinfo/ThirdLibInfoItemAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.sysinfo;\n\nimport android.content.Context;\nimport android.text.format.Formatter;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\n\n\n/**\n * Created by wanglikun on 2018/9/14.\n */\n\npublic class ThirdLibInfoItemAdapter extends AbsRecyclerAdapter<AbsViewBinder<SysInfoItem>, SysInfoItem> {\n\n    private final static int TYPE_ITEM = 0;\n    private final static int TYPE_TITLE = 1;\n\n    public ThirdLibInfoItemAdapter(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected AbsViewBinder<SysInfoItem> createViewHolder(View view, int viewType) {\n        if (viewType == TYPE_TITLE) {\n            return new TitleItemViewHolder(view);\n        } else {\n            return new ThirdLibItemViewHolder(view);\n        }\n    }\n\n    @Override\n    public int getItemViewType(int position) {\n        if (getData().get(position) instanceof TitleItem) {\n            return TYPE_TITLE;\n        } else {\n            return TYPE_ITEM;\n        }\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        if (viewType == TYPE_TITLE) {\n            return inflater.inflate(R.layout.dk_item_sys_title, parent, false);\n        } else {\n            return inflater.inflate(R.layout.dk_item_third_lib_info, parent, false);\n        }\n    }\n\n    public class ThirdLibItemViewHolder extends AbsViewBinder<SysInfoItem> {\n        private TextView mTvName;\n        private TextView mTvSize;\n\n        public ThirdLibItemViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            mTvName = getView(R.id.tv_name);\n            mTvSize = getView(R.id.tv_size);\n        }\n\n        @Override\n        public void bind(final SysInfoItem sysInfoItem) {\n            mTvName.setText(sysInfoItem.name);\n            mTvSize.setText(Formatter.formatFileSize(itemView.getContext(), Long.parseLong(sysInfoItem.value)));\n\n        }\n    }\n\n    public class TitleItemViewHolder extends AbsViewBinder<SysInfoItem> {\n        private TextView mTextView;\n\n        public TitleItemViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            mTextView = getView(R.id.tv_title);\n        }\n\n        @Override\n        public void bind(SysInfoItem sysInfoItem) {\n            mTextView.setText(sysInfoItem.name);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/sysinfo/ThirdLibInfoKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.sysinfo\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.google.auto.service.AutoService\n\n/**\n * 设备、app信息\n * Created by zhangweida on 2018/6/22.\n */\n@AutoService(AbstractKit::class)\nclass ThirdLibInfoKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_third_library_info\n    override val icon: Int\n        get() = R.mipmap.dk_icon_third_lib\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip))\n            return false\n        }\n        startUniversalActivity(ThirdLibInfoFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_comm_ck_thirdlibinfo\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/sysinfo/TitleItem.java",
    "content": "package com.didichuxing.doraemonkit.kit.sysinfo;\n\n/**\n * @desc:\n */\npublic class TitleItem extends SysInfoItem {\n    public TitleItem(String title) {\n        super(title, null);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/timecounter/AppStartInfoFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.timecounter;\n\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.DoKitEnv;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.aop.method_stack.MethodStackUtil;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.util.AppUtils;\nimport com.didichuxing.doraemonkit.util.DoKitFileUtil;\nimport com.didichuxing.doraemonkit.util.FileIOUtils;\nimport com.didichuxing.doraemonkit.util.FileUtils;\nimport com.didichuxing.doraemonkit.util.PathUtils;\nimport com.didichuxing.doraemonkit.util.ThreadUtils;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.widget.titlebar.TitleBar;\n\nimport java.io.File;\n\n/**\n * @desc: Activity跳转耗时检测首页\n */\n\npublic class AppStartInfoFragment extends BaseFragment {\n    TextView mInfo;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_app_start_info;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n    }\n\n\n    private void initView() {\n        TitleBar titleBar = findViewById(R.id.title_bar);\n\n        titleBar.setOnTitleBarClickListener(new TitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onLeftClick() {\n                finish();\n            }\n\n            @Override\n            public void onRightClick() {\n                export2File(mInfo.getText().toString());\n            }\n        });\n\n        mInfo = findViewById(R.id.app_start_info);\n        StringBuilder builder = new StringBuilder();\n        if (TextUtils.isEmpty(MethodStackUtil.STR_APP_ON_CREATE) && TextUtils.isEmpty(MethodStackUtil.STR_APP_ATTACH_BASECONTEXT)) {\n            builder.append(\"只有配置slowMethod的strategy=0模式下才能统计到启动函数调用栈\");\n        } else {\n            if(!TextUtils.isEmpty(MethodStackUtil.STR_APP_ATTACH_BASECONTEXT)){\n                builder.append(MethodStackUtil.STR_APP_ATTACH_BASECONTEXT);\n                builder.append(\"\\n\");\n            }\n            builder.append(MethodStackUtil.STR_APP_ON_CREATE);\n        }\n\n        mInfo.setText(builder.toString());\n\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n    }\n\n    /**\n     * 将启动信息保存到文件并分享\n     */\n    private void export2File(final String info) {\n        if (TextUtils.isEmpty(info)) {\n            ToastUtils.showShort(\"启动信息为空\");\n            return;\n        }\n        ToastUtils.showShort(\"启动信息保存中,请稍后...\");\n        final String logPath = PathUtils.getInternalAppFilesPath() + File.separator + AppUtils.getAppName() + \"_\" + \"app_launch.log\";\n        final File logFile = new File(logPath);\n\n        ThreadUtils.executeByCpu(new ThreadUtils.Task<Boolean>() {\n            @Override\n            public Boolean doInBackground() throws Throwable {\n                try {\n\n                    FileIOUtils.writeFileFromString(logFile, info, false);\n\n                    return true;\n                } catch (Exception e) {\n                    e.printStackTrace();\n                    return false;\n                }\n            }\n\n            @Override\n            public void onSuccess(Boolean result) {\n                if (result) {\n                    ToastUtils.showShort(\"启动信息文件保存在:\" + logPath);\n                    //分享\n                    DoKitFileUtil.systemShare(DoKitEnv.requireApp(), logFile);\n                }\n            }\n\n            @Override\n            public void onCancel() {\n                if (logFile.exists()) {\n                    FileUtils.delete(logFile);\n                }\n                ToastUtils.showShort(\"启动信息保存失败\");\n            }\n\n            @Override\n            public void onFail(Throwable t) {\n                if (logFile.exists()) {\n                    FileUtils.delete(logFile);\n                }\n                ToastUtils.showShort(\"启动信息保存失败\");\n            }\n        });\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/timecounter/TimeCounterDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.timecounter;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.timecounter.bean.CounterInfo;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\n/**\n * Created by jintai on 2019/09/26.\n */\npublic class TimeCounterDoKitView extends AbsDoKitView {\n    private TextView tvTitle;\n    private TextView tvTotal;\n    private TextView tvPause;\n    private TextView tvLaunch;\n    private TextView tvRender;\n    private TextView tvOther;\n    private ImageView mClose;\n\n    @Override\n    public void onCreate(Context context) {\n    }\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_time_counter, null);\n    }\n\n//    @Override\n//    public void onNormalLayoutParamsCreated(FrameLayout.LayoutParams params) {\n//        params.width = WindowManager.LayoutParams.WRAP_CONTENT;\n//        params.height = WindowManager.LayoutParams.WRAP_CONTENT;\n//        params.leftMargin = UIUtils.dp2px(getContext(), 30);\n//        params.topMargin = UIUtils.dp2px(getContext(), 30);\n//        super.onNormalLayoutParamsCreated(params);\n//    }\n//\n//    @Override\n//    public void onSystemLayoutParamsCreated(WindowManager.LayoutParams params) {\n//        params.width = WindowManager.LayoutParams.WRAP_CONTENT;\n//        params.height = WindowManager.LayoutParams.WRAP_CONTENT;\n//        params.x = UIUtils.dp2px(getContext(), 30);\n//        params.y = UIUtils.dp2px(getContext(), 30);\n//        super.onSystemLayoutParamsCreated(params);\n//    }\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.width = DoKitViewLayoutParams.WRAP_CONTENT;\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT;\n        params.x = UIUtils.dp2px(30);\n        params.y = UIUtils.dp2px(30);\n    }\n\n    @Override\n    public void onViewCreated(FrameLayout view) {\n        initView();\n    }\n\n    private void initView() {\n        tvTitle = findViewById(R.id.title);\n        tvTotal = findViewById(R.id.total_cost);\n        tvPause = findViewById(R.id.pause_cost);\n        tvLaunch = findViewById(R.id.launch_cost);\n        tvRender = findViewById(R.id.render_cost);\n        tvOther = findViewById(R.id.other_cost);\n\n        CounterInfo counterInfo = TimeCounterManager.get().getAppSetupInfo();\n        showInfo(counterInfo);\n\n        mClose = findViewById(R.id.close);\n        mClose.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                TimeCounterManager.get().stop();\n            }\n        });\n    }\n\n    public void showInfo(CounterInfo info) {\n        tvTitle.setText(info.title);\n        setTotalCost(info.totalCost);\n\n        if (info.type == CounterInfo.TYPE_ACTIVITY) {\n            tvPause.setVisibility(View.VISIBLE);\n            tvLaunch.setVisibility(View.VISIBLE);\n            tvRender.setVisibility(View.VISIBLE);\n            tvOther.setVisibility(View.VISIBLE);\n\n            tvPause.setText(\"Pause Cost: \" + info.pauseCost + \"ms\");\n            tvLaunch.setText(\"Launch Cost: \" + info.launchCost + \"ms\");\n            tvRender.setText(\"Render Cost: \" + info.renderCost + \"ms\");\n            tvOther.setText(\"Other Cost: \" + info.otherCost + \"ms\");\n        } else {\n            tvPause.setVisibility(View.GONE);\n            tvLaunch.setVisibility(View.GONE);\n            tvRender.setVisibility(View.GONE);\n            tvOther.setVisibility(View.GONE);\n        }\n    }\n\n    private void setTotalCost(long cost) {\n        tvTotal.setText(\"Total Cost: \" + cost + \"ms\");\n        if (cost <= CounterInfo.ACTIVITY_COST_FAST) {\n            tvTotal.setTextColor(getContext().getResources().getColor(R.color.dk_color_48BB31));\n        } else if (cost <= CounterInfo.ACTIVITY_COST_SLOW) {\n            tvTotal.setTextColor(getContext().getResources().getColor(R.color.dk_color_FAD337));\n        } else {\n            tvTotal.setTextColor(getContext().getResources().getColor(R.color.dk_color_FF0006));\n        }\n    }\n\n    private void showDetail(CounterInfo info) {\n        if (info.type == CounterInfo.TYPE_APP) {\n            info.show = false;\n        }\n        if (info.show) {\n            tvPause.setVisibility(View.VISIBLE);\n            tvLaunch.setVisibility(View.VISIBLE);\n            tvRender.setVisibility(View.VISIBLE);\n            tvOther.setVisibility(View.VISIBLE);\n\n\n        } else {\n            tvPause.setVisibility(View.GONE);\n            tvLaunch.setVisibility(View.GONE);\n            tvRender.setVisibility(View.GONE);\n            tvOther.setVisibility(View.GONE);\n        }\n    }\n\n\n    @Override\n    public void onEnterForeground() {\n        super.onEnterForeground();\n    }\n\n    @Override\n    public void onEnterBackground() {\n        super.onEnterBackground();\n        TimeCounterManager.get().onEnterBackground();\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/timecounter/TimeCounterFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.timecounter;\n\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\n\n/**\n * @desc: Activity跳转耗时检测首页\n */\n\npublic class TimeCounterFragment extends BaseFragment {\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_time_counter_index;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n    }\n\n\n    private void initView() {\n        HomeTitleBar title = findViewById(R.id.title_bar);\n        title.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                getActivity().finish();\n            }\n        });\n        RecyclerView mSettingList = findViewById(R.id.setting_list);\n        mSettingList.setLayoutManager(new LinearLayoutManager(getContext()));\n        SettingItemAdapter settingItemAdapter = new SettingItemAdapter(getContext());\n        mSettingList.setAdapter(settingItemAdapter);\n        settingItemAdapter.append(new SettingItem(R.string.dk_item_time_counter_switch, TimeCounterManager.get().isRunning()));\n        settingItemAdapter.append(new SettingItem(R.string.dk_item_time_goto_list));\n\n        settingItemAdapter.setOnSettingItemSwitchListener(new SettingItemAdapter.OnSettingItemSwitchListener() {\n            @Override\n            public void onSettingItemSwitch(View view, SettingItem data, boolean on) {\n                if (data.desc == R.string.dk_item_time_counter_switch) {\n                    if (on) {\n                        TimeCounterManager.get().start();\n                    } else {\n                        TimeCounterManager.get().stop();\n                    }\n                }\n            }\n        });\n        settingItemAdapter.setOnSettingItemClickListener(new SettingItemAdapter.OnSettingItemClickListener() {\n            @Override\n            public void onSettingItemClick(View view, SettingItem data) {\n                if (data.desc == R.string.dk_item_time_goto_list) {\n                    showContent(TimeCounterListFragment.class);\n                }\n            }\n        });\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/timecounter/TimeCounterKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.timecounter\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n/**\n * app启动、页面跳转的计时kit\n */\n@AutoService(AbstractKit::class)\nclass TimeCounterKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_time_counter\n    override val icon: Int\n        get() = R.mipmap.dk_time_counter\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        startUniversalActivity(TimeCounterFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_performance_ck_open_coast\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/timecounter/TimeCounterListAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.timecounter;\n\nimport android.content.Context;\nimport android.text.format.DateUtils;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.timecounter.bean.CounterInfo;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\n\nimport static android.text.format.DateUtils.FORMAT_SHOW_TIME;\n\npublic class TimeCounterListAdapter extends AbsRecyclerAdapter<AbsViewBinder<CounterInfo>, CounterInfo> {\n\n    public TimeCounterListAdapter(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected AbsViewBinder<CounterInfo> createViewHolder(View view, int viewType) {\n        return new ItemViewHolder(view);\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        return inflater.inflate(R.layout.dk_item_time_counter_list, parent, false);\n    }\n\n    private class ItemViewHolder extends AbsViewBinder<CounterInfo> {\n\n        private TextView tvTime;\n        private TextView tvTitle;\n        private TextView tvTotal;\n        private TextView tvPause;\n        private TextView tvLaunch;\n        private TextView tvRender;\n        private TextView tvOther;\n\n        public ItemViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            tvTime = getView(R.id.time);\n            tvTitle = getView(R.id.title);\n            tvTotal = getView(R.id.total_cost);\n            tvPause = getView(R.id.pause_cost);\n            tvLaunch = getView(R.id.launch_cost);\n            tvRender = getView(R.id.render_cost);\n            tvOther = getView(R.id.other_cost);\n        }\n\n        @Override\n        public void bind(CounterInfo counterInfo) {\n\n        }\n\n        @Override\n        public void bind(final CounterInfo info, int position) {\n            tvTitle.setText(info.title);\n            String time = DateUtils.formatDateTime(getContext(),\n                    info.time, FORMAT_SHOW_TIME);\n            tvTime.setText(time);\n            setTotalCost(info.totalCost);\n            itemView.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    info.show = !info.show;\n                    showDetail(info);\n                    if (info.type == CounterInfo.TYPE_APP && mContext != null) {\n                        //跳转启动耗时详情页\n                        DoKit.launchFullScreen(AppStartInfoFragment.class,mContext);\n                    }\n                }\n            });\n            showDetail(info);\n        }\n\n        private void setTotalCost(long cost) {\n            tvTotal.setText(\"Total Cost: \" + cost + \"ms\");\n            if (cost <= CounterInfo.ACTIVITY_COST_FAST) {\n                tvTotal.setTextColor(getContext().getResources().getColor(R.color.dk_color_48BB31));\n            } else if (cost <= CounterInfo.ACTIVITY_COST_SLOW) {\n                tvTotal.setTextColor(getContext().getResources().getColor(R.color.dk_color_FAD337));\n            } else {\n                tvTotal.setTextColor(getContext().getResources().getColor(R.color.dk_color_FF0006));\n            }\n        }\n\n        //显示详情\n        private void showDetail(CounterInfo info) {\n            if (info.type == CounterInfo.TYPE_APP) {\n                info.show = false;\n            }\n            if (info.show) {\n                tvPause.setVisibility(View.VISIBLE);\n                tvLaunch.setVisibility(View.VISIBLE);\n                tvRender.setVisibility(View.VISIBLE);\n                tvOther.setVisibility(View.VISIBLE);\n\n                tvPause.setText(\"Pause Cost: \" + info.pauseCost + \"ms\");\n                tvLaunch.setText(\"Launch Cost: \" + info.launchCost + \"ms\");\n                tvRender.setText(\"Render Cost: \" + info.renderCost + \"ms\");\n                tvOther.setText(\"Other Cost: \" + info.otherCost + \"ms\");\n            } else {\n                tvPause.setVisibility(View.GONE);\n                tvLaunch.setVisibility(View.GONE);\n                tvRender.setVisibility(View.GONE);\n                tvOther.setVisibility(View.GONE);\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/timecounter/TimeCounterListFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.timecounter;\n\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.timecounter.bean.CounterInfo;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.recyclerview.DividerItemDecoration;\nimport com.didichuxing.doraemonkit.widget.titlebar.TitleBar;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\n\n/**\n * @desc: 跳转耗时历史记录列表\n */\n\npublic class TimeCounterListFragment extends BaseFragment {\n    private static final String TAG = \"TimeCounterListFragment\";\n\n    private RecyclerView mBlockList;\n    private TimeCounterListAdapter mAdapter;\n    private TitleBar mTitleBar;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_time_counter_list;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n        load();\n    }\n\n\n    private void initView() {\n        mBlockList = findViewById(R.id.block_list);\n\n        LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());\n        mBlockList.setLayoutManager(layoutManager);\n        mAdapter = new TimeCounterListAdapter(getContext());\n        mBlockList.setAdapter(mAdapter);\n        DividerItemDecoration decoration = new DividerItemDecoration(DividerItemDecoration.VERTICAL);\n        decoration.setDrawable(getResources().getDrawable(R.drawable.dk_divider));\n        mBlockList.addItemDecoration(decoration);\n        mTitleBar = findViewById(R.id.title_bar);\n        mTitleBar.setOnTitleBarClickListener(new TitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onLeftClick() {\n                getActivity().onBackPressed();\n            }\n\n            @Override\n            public void onRightClick() {\n\n            }\n        });\n    }\n\n\n    private void load() {\n        List<CounterInfo> infos = new ArrayList<>(TimeCounterManager.get().getHistory());\n        infos.add(0, TimeCounterManager.get().getAppSetupInfo());\n        Collections.sort(infos, new Comparator<CounterInfo>() {\n            @Override\n            public int compare(CounterInfo lhs, CounterInfo rhs) {\n                return Long.valueOf(rhs.time)\n                        .compareTo(lhs.time);\n            }\n        });\n        mAdapter.setData(infos);\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/timecounter/TimeCounterManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.timecounter;\n\nimport android.os.Looper;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.util.GsonUtils;\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig;\nimport com.didichuxing.doraemonkit.aop.method_stack.MethodStackUtil;\nimport com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil;\nimport com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo;\nimport com.didichuxing.doraemonkit.kit.methodtrace.AppHealthMethodCostBean;\nimport com.didichuxing.doraemonkit.kit.methodtrace.AppHealthMethodCostBeanWrap;\nimport com.didichuxing.doraemonkit.kit.timecounter.bean.CounterInfo;\nimport com.didichuxing.doraemonkit.kit.timecounter.counter.ActivityCounter;\nimport com.didichuxing.doraemonkit.kit.timecounter.counter.AppCounter;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @desc: App启动、Activity跳转的耗时统计类\n */\npublic class TimeCounterManager {\n    private static final String TAG = \"TimeCounterManager\";\n    private boolean mIsRunning;\n\n\n    private static class Holder {\n        private static TimeCounterManager INSTANCE = new TimeCounterManager();\n    }\n\n    public static TimeCounterManager get() {\n        return TimeCounterManager.Holder.INSTANCE;\n    }\n\n    private AppCounter mAppCounter = new AppCounter();\n    private ActivityCounter mActivityCounter = new ActivityCounter();\n\n\n    /**\n     * App attachBaseContext\n     */\n    public void onAppAttachBaseContextStart() {\n        mAppCounter.attachStart();\n    }\n\n\n    /**\n     * App attachBaseContext\n     */\n    public void onAppAttachBaseContextEnd() {\n        mAppCounter.attachEnd();\n    }\n\n    /**\n     * App 启动\n     */\n    public void onAppCreateStart() {\n        mAppCounter.start();\n\n    }\n\n    /**\n     * App 启动结束\n     */\n    public void onAppCreateEnd() {\n        mAppCounter.end();\n        CounterInfo counterInfo = getAppSetupInfo();\n        if (DokitPluginConfig.VALUE_METHOD_STRATEGY == DokitPluginConfig.STRATEGY_STACK) {\n            StringBuilder startInfo = new StringBuilder();\n            startInfo.append(MethodStackUtil.STR_APP_ATTACH_BASECONTEXT);\n            startInfo.append(\"\\n\");\n            startInfo.append(MethodStackUtil.STR_APP_ON_CREATE);\n            AppHealthInfoUtil.getInstance().setAppStartInfo(counterInfo.totalCost, startInfo.toString(), new ArrayList<AppHealthInfo.DataBean.AppStartBean.LoadFuncBean>());\n        } else {\n            List<AppHealthMethodCostBean> appHealthMethodCostBeans = new ArrayList<>();\n            AppHealthMethodCostBean onCreate = new AppHealthMethodCostBean();\n            onCreate.setCostTime(mAppCounter.getStartCountTime() + \"ms\");\n            onCreate.setFunctionName(\"Application onCreate\");\n            appHealthMethodCostBeans.add(onCreate);\n            AppHealthMethodCostBean onAttach = new AppHealthMethodCostBean();\n            onAttach.setCostTime(mAppCounter.getAttachCountTime() + \"ms\");\n            onAttach.setFunctionName(\"Application attachBaseContext\");\n            appHealthMethodCostBeans.add(onAttach);\n\n            AppHealthMethodCostBeanWrap appHealthMethodCostBeanWrap = new AppHealthMethodCostBeanWrap();\n            appHealthMethodCostBeanWrap.setTitle(\"App启动耗时\");\n            appHealthMethodCostBeanWrap.setData(appHealthMethodCostBeans);\n            AppHealthInfoUtil.getInstance().setAppStartInfo(counterInfo.totalCost, GsonUtils.toJson(appHealthMethodCostBeanWrap), new ArrayList<AppHealthInfo.DataBean.AppStartBean.LoadFuncBean>());\n\n        }\n\n    }\n\n    public void onActivityPause() {\n        mActivityCounter.pause();\n    }\n\n    public void onActivityPaused() {\n        mActivityCounter.paused();\n    }\n\n    public void onActivityLaunch() {\n        mActivityCounter.launch();\n    }\n\n    public void onActivityLaunched() {\n        mActivityCounter.launchEnd();\n    }\n\n    public void onEnterBackground() {\n        mActivityCounter.enterBackground();\n    }\n\n    public void start() {\n        if (mIsRunning) {\n            return;\n        }\n        mIsRunning = true;\n        DoKit.hideToolPanel();\n\n        DoKit.launchFloating(TimeCounterDoKitView.class);\n\n    }\n\n    public boolean isRunning() {\n        return mIsRunning;\n    }\n\n    public void stop() {\n        if (!mIsRunning) {\n            return;\n        }\n        Looper.getMainLooper().setMessageLogging(null);\n        mIsRunning = false;\n        DoKit.removeFloating(TimeCounterDoKitView.class);\n\n    }\n\n    public List<CounterInfo> getHistory() {\n        return mActivityCounter.getHistory();\n    }\n\n    public CounterInfo getAppSetupInfo() {\n        return mAppCounter.getAppSetupInfo();\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/timecounter/bean/CounterInfo.java",
    "content": "package com.didichuxing.doraemonkit.kit.timecounter.bean;\n\n/**\n * @desc: 统计耗时的Bean\n */\npublic class CounterInfo {\n    public static final int ACTIVITY_COST_FAST = 500;\n    public static final int ACTIVITY_COST_SLOW = 1000;\n\n    public static final int TYPE_APP = 0;\n    public static final int TYPE_ACTIVITY = 1;\n\n    public long time;\n    public int type;\n    public String title;\n    /**\n     * 单位为ms\n     */\n    public long totalCost;\n    public long pauseCost;\n    public long launchCost;\n    public long renderCost;\n    public long otherCost;\n\n    public boolean show;\n\n    @Override\n    public String toString() {\n        return \"CounterInfo{\" +\n                \"time=\" + time +\n                \", type=\" + type +\n                \", title='\" + title + '\\'' +\n                \", totalCost=\" + totalCost +\n                \", pauseCost=\" + pauseCost +\n                \", launchCost=\" + launchCost +\n                \", renderCost=\" + renderCost +\n                \", otherCost=\" + otherCost +\n                \", show=\" + show +\n                '}';\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/timecounter/counter/ActivityCounter.java",
    "content": "package com.didichuxing.doraemonkit.kit.timecounter.counter;\n\nimport android.app.Activity;\nimport android.os.SystemClock;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil;\nimport com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo;\nimport com.didichuxing.doraemonkit.kit.timecounter.TimeCounterDoKitView;\nimport com.didichuxing.doraemonkit.kit.timecounter.bean.CounterInfo;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 统计一个打开Activity操作的耗时分三个阶段，以A打开B为例，第一个阶段是A的pause操作，主要是onPause方法的耗时，第二个阶段是B的Launch操作，主要是\n * onCreate和onResume方法的耗时，第三个阶段是B的Window的渲染操作，调用DecorView的post方法进行统计\n * 这里忽略了和AMS、WMS进行通讯的耗时，主要是因为这部分耗时无法通过非侵入式方式统计，而且用户也无法对这部分耗时做优化（除非重写Activity调用AMS的逻辑），\n * 总耗时=pause+launch+render+other\n */\npublic class ActivityCounter {\n\n    private static final String TAG = \"ActivityCounter\";\n    private long mStartTime;\n    private long mPauseCostTime;\n    private long mLaunchStartTime;\n    private long mLaunchCostTime;\n    private long mRenderStartTime;\n    private long mRenderCostTime;\n    private long mTotalCostTime;\n    private long mOtherCostTime;\n\n    private String mPreviousActivity;\n    private String mCurrentActivity;\n    private List<CounterInfo> mCounterInfos = new ArrayList<>();\n\n    public void pause() {\n        mStartTime = SystemClock.elapsedRealtime();\n        mPauseCostTime = 0;\n        mRenderCostTime = 0;\n        mOtherCostTime = 0;\n        mLaunchCostTime = 0;\n        mLaunchStartTime = 0;\n        mTotalCostTime = 0;\n        mPreviousActivity = null;\n        Activity activity = ActivityUtils.getTopActivity();\n        if (activity != null) {\n            mPreviousActivity = activity.getClass().getCanonicalName();\n        }\n    }\n\n    public void paused() {\n        mPauseCostTime = SystemClock.elapsedRealtime() - mStartTime;\n        //LogHelper.d(TAG, \"pause cost：\" + mPauseCostTime);\n    }\n\n    public void launch() {\n        // 可能不走pause，直接打开新页面，比如从后台点击通知栏\n        if (mStartTime == 0) {\n            mStartTime = SystemClock.elapsedRealtime();\n            mPauseCostTime = 0;\n            mRenderCostTime = 0;\n            mOtherCostTime = 0;\n            mLaunchCostTime = 0;\n            mLaunchStartTime = 0;\n            mTotalCostTime = 0;\n        }\n        mLaunchStartTime = SystemClock.elapsedRealtime();\n        mLaunchCostTime = 0;\n    }\n\n    public void launchEnd() {\n        mLaunchCostTime = SystemClock.elapsedRealtime() - mLaunchStartTime;\n        //LogHelper.d(TAG, \"create cost：\" + mLaunchCostTime);\n        render();\n    }\n\n    public void render() {\n        mRenderStartTime = SystemClock.elapsedRealtime();\n        final Activity activity = ActivityUtils.getTopActivity();\n        if (activity != null && activity.getWindow() != null) {\n            mCurrentActivity = activity.getClass().getCanonicalName();\n            activity.getWindow().getDecorView().post(new Runnable() {\n                @Override\n                public void run() {\n                    renderEnd();\n                }\n            });\n        } else {\n            renderEnd();\n        }\n    }\n\n    /**\n     * 用户退到后台，点击通知栏打开新页面，这时候需要清空下上次pause记录的时间\n     */\n    public void enterBackground() {\n        mStartTime = 0;\n    }\n\n    private void renderEnd() {\n        mRenderCostTime = SystemClock.elapsedRealtime() - mRenderStartTime;\n        //LogHelper.d(TAG, \"render cost：\" + mRenderCostTime);\n        mTotalCostTime = SystemClock.elapsedRealtime() - mStartTime;\n        //LogHelper.d(TAG, \"total cost：\" + mTotalCostTime);\n        mOtherCostTime = mTotalCostTime - mRenderCostTime - mPauseCostTime - mLaunchCostTime;\n        print();\n    }\n\n    private void print() {\n        CounterInfo counterInfo = new CounterInfo();\n        counterInfo.time = SystemClock.elapsedRealtime();\n        counterInfo.type = CounterInfo.TYPE_ACTIVITY;\n        counterInfo.title = mPreviousActivity + \" -> \" + mCurrentActivity;\n        counterInfo.launchCost = mLaunchCostTime;\n        counterInfo.pauseCost = mPauseCostTime;\n        counterInfo.renderCost = mRenderCostTime;\n        counterInfo.totalCost = mTotalCostTime;\n        counterInfo.otherCost = mOtherCostTime;\n        try {\n            //将Activity 打开耗时 添加到AppHealth 中\n            if (DoKitManager.APP_HEALTH_RUNNING) {\n                if (!ActivityUtils.getTopActivity().getClass().getCanonicalName().equals(\"com.didichuxing.doraemonkit.kit.base.UniversalActivity\")) {\n                    AppHealthInfo.DataBean.PageLoadBean pageLoadBean = new AppHealthInfo.DataBean.PageLoadBean();\n                    pageLoadBean.setPage(ActivityUtils.getTopActivity().getClass().getCanonicalName());\n                    pageLoadBean.setTime(\"\" + counterInfo.totalCost);\n                    pageLoadBean.setTrace(counterInfo.title);\n                    AppHealthInfoUtil.getInstance().addPageLoadInfo(pageLoadBean);\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        mCounterInfos.add(counterInfo);\n\n        TimeCounterDoKitView dokitView = DoKit.getDoKitView(ActivityUtils.getTopActivity(), TimeCounterDoKitView.class);\n        if (dokitView != null) {\n            dokitView.showInfo(counterInfo);\n        }\n\n\n    }\n\n    public List<CounterInfo> getHistory() {\n        return mCounterInfos;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/timecounter/counter/AppCounter.java",
    "content": "package com.didichuxing.doraemonkit.kit.timecounter.counter;\n\nimport com.didichuxing.doraemonkit.kit.timecounter.bean.CounterInfo;\n\n/**\n * @desc: App启动耗时\n */\npublic class AppCounter {\n\n    private long mStartTime;\n    private long mStartCountTime;\n    private long mAttachTime;\n    private long mAttachCountTime;\n\n    public long getStartCountTime() {\n        return mStartCountTime;\n    }\n\n\n    public long getAttachCountTime() {\n        return mAttachCountTime;\n    }\n\n\n\n    private CounterInfo mCounterInfo = new CounterInfo();\n\n    public void start() {\n        mStartTime = System.currentTimeMillis();\n    }\n\n    public void attachStart() {\n        mAttachTime = System.currentTimeMillis();\n    }\n\n    public void attachEnd() {\n        mAttachCountTime = System.currentTimeMillis() - mAttachTime;\n    }\n\n    public void end() {\n        mStartCountTime = System.currentTimeMillis() - mStartTime;\n        account();\n    }\n\n    public void account() {\n        mCounterInfo.title = \"App Setup Cost\";\n        mCounterInfo.totalCost = mAttachCountTime + mStartCountTime;\n        mCounterInfo.type = CounterInfo.TYPE_APP;\n        mCounterInfo.time = System.currentTimeMillis();\n    }\n\n    public CounterInfo getAppSetupInfo() {\n        return mCounterInfo;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/timecounter/instrumentation/HandlerHooker.java",
    "content": "package com.didichuxing.doraemonkit.kit.timecounter.instrumentation;\n\nimport android.app.Application;\nimport android.os.Build;\nimport android.os.Handler;\n\nimport com.didichuxing.doraemonkit.reflection.Reflection;\nimport com.didichuxing.doraemonkit.util.ReflectUtils;\n\n\npublic class HandlerHooker {\n    private static final String TAG = \"HandlerHooker\";\n    //是否已经hook成功\n    private static boolean isHookSucceed = false;\n\n    public static void doHook(Application app) {\n        try {\n            if (isHookSucceed()) {\n                return;\n            }\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n                //解锁调用系统隐藏api的权限\n                Reflection.unseal(app);\n            }\n            //hook ActivityThread的Instrumentation\n            hookInstrumentation();\n            isHookSucceed = true;\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n\n    static boolean isHookSucceed() {\n        return isHookSucceed;\n    }\n\n    /**\n     * hook ActivityThread的Instrumentation\n     */\n    private static void hookInstrumentation() {\n        //得到ActivityThread对象\n        Object currentActivityThreadObj = ReflectUtils.reflect(\"android.app.ActivityThread\").method(\"currentActivityThread\").get();\n        //ActivityThread对象的 mH变量\n        Handler handlerObj = ReflectUtils.reflect(currentActivityThreadObj).field(\"mH\").get();\n        Handler.Callback handCallbackObj = ReflectUtils.reflect(handlerObj).field(\"mCallback\").get();\n        ProxyHandlerCallback proxyMHCallback = new ProxyHandlerCallback(handCallbackObj, handlerObj);\n        //替换mCallback 对象\n        ReflectUtils.reflect(handlerObj).field(\"mCallback\", proxyMHCallback);\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/timecounter/instrumentation/ProxyHandlerCallback.java",
    "content": "package com.didichuxing.doraemonkit.kit.timecounter.instrumentation;\n\nimport android.os.Handler;\nimport android.os.Message;\nimport android.text.TextUtils;\n\nimport com.didichuxing.doraemonkit.kit.timecounter.TimeCounterManager;\nimport com.didichuxing.doraemonkit.util.Reflector;\n\n/**\n * @author: linjizong\n *  2019/6/3\n * @desc: 自定义的handlerCallback\n */\nclass ProxyHandlerCallback implements Handler.Callback {\n    private static final String TAG = \"ProxyHandlerCallback\";\n    /**\n     * Android 28开始 变量从110开始\n     */\n    private static final int LAUNCH_ACTIVITY = 100;\n    /**\n     * Android 28开始 变量从110开始\n     */\n    private static final int PAUSE_ACTIVITY = 101;\n    private static final int EXECUTE_TRANSACTION = 159;\n    private static final String LAUNCH_ITEM_CLASS = \"android.app.servertransaction.ResumeActivityItem\";\n    private static final String PAUSE_ITEM_CLASS = \"android.app.servertransaction.PauseActivityItem\";\n\n    private final Handler.Callback mOldCallback;\n    public final Handler mHandler;\n\n    ProxyHandlerCallback(Handler.Callback oldCallback, Handler handler) {\n        mOldCallback = oldCallback;\n        mHandler = handler;\n    }\n\n    @Override\n    public boolean handleMessage(Message msg) {\n        int msgType = preDispatch(msg);\n        if (mOldCallback != null && mOldCallback.handleMessage(msg)) {\n            postDispatch(msgType);\n            return true;\n        }\n        mHandler.handleMessage(msg);\n        postDispatch(msgType);\n        return true;\n    }\n\n    private int preDispatch(Message msg) {\n        switch (msg.what) {\n            case LAUNCH_ACTIVITY:\n                TimeCounterManager.get().onActivityLaunch();\n                break;\n            case PAUSE_ACTIVITY:\n                TimeCounterManager.get().onActivityPause();\n                break;\n            //兼容 Android SDK 28及以上\n            case EXECUTE_TRANSACTION:\n                return handlerActivity(msg);\n            default:\n                break;\n        }\n        return msg.what;\n    }\n\n    private int handlerActivity(Message msg) {\n        Object obj = msg.obj;\n\n        Object activityCallback = Reflector.QuietReflector.with(obj).method(\"getLifecycleStateRequest\").call();\n        if (activityCallback != null) {\n            String transactionName = activityCallback.getClass().getCanonicalName();\n            if (TextUtils.equals(transactionName, LAUNCH_ITEM_CLASS)) {\n                TimeCounterManager.get().onActivityLaunch();\n                return LAUNCH_ACTIVITY;\n            } else if (TextUtils.equals(transactionName, PAUSE_ITEM_CLASS)) {\n                TimeCounterManager.get().onActivityPause();\n                return PAUSE_ACTIVITY;\n            }\n        }\n        return msg.what;\n    }\n\n    private void postDispatch(int msgType) {\n        switch (msgType) {\n            case LAUNCH_ACTIVITY:\n                TimeCounterManager.get().onActivityLaunched();\n                break;\n            case PAUSE_ACTIVITY:\n                TimeCounterManager.get().onActivityPaused();\n                break;\n            default:\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/ConfirmDialogProvider.kt",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel\n\nimport android.view.View\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.widget.dialog.DialogListener\nimport com.didichuxing.doraemonkit.widget.dialog.DialogProvider\n\n/**\n * Created by jint on 2019/4/12\n * 确认弹框\n *\n * @author jintai\n */\nclass ConfirmDialogProvider(data: Any?, listener: DialogListener?) : DialogProvider<Any?>(data, listener) {\n    private lateinit var mTvContent: TextView\n    private lateinit var mTvPositive: TextView\n    private lateinit var mTvNegative: TextView\n\n    override fun getLayoutId(): Int {\n        return R.layout.dk_dialog_confirm\n    }\n\n    override fun findViews(view: View) {\n        mTvContent = view.findViewById(R.id.tv_content)\n        mTvPositive = view.findViewById(R.id.positive)\n        mTvNegative = view.findViewById(R.id.negative)\n    }\n\n    override fun bindData(data: Any?) {\n        if (data is String) {\n            mTvContent.text = data\n        }\n\n    }\n\n    override fun getPositiveView(): View? {\n        return mTvPositive\n    }\n\n    override fun getNegativeView(): View? {\n        return mTvNegative\n    }\n\n    override fun isCancellable(): Boolean {\n        return false\n    }\n\n\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/DokitManagerAdapter.kt",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel\n\nimport android.view.View\nimport android.widget.ImageView\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.widget.brvah.BaseMultiItemQuickAdapter\nimport com.didichuxing.doraemonkit.widget.brvah.module.DraggableModule\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/29-15:21\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DokitManagerAdapter(kitViews: MutableList<KitWrapItem>?)\n    : BaseMultiItemQuickAdapter<KitWrapItem, BaseViewHolder>(kitViews), DraggableModule {\n\n    init {\n        addItemType(KitWrapItem.TYPE_TITLE, R.layout.dk_item_group_title)\n        addItemType(KitWrapItem.TYPE_KIT, R.layout.dk_item_group_kit_manager)\n    }\n\n    override fun convert(holder: BaseViewHolder, item: KitWrapItem) {\n        when (item.itemType) {\n            KitWrapItem.TYPE_TITLE -> {\n                item.name.let {\n                    holder.getView<TextView>(R.id.tv_title_name).text = it\n                }\n            }\n            KitWrapItem.TYPE_KIT -> {\n                item.kit?.let {\n                    holder.getView<TextView>(R.id.name).setText(it.name)\n                    holder.getView<ImageView>(R.id.icon).setImageResource(it.icon)\n                    if (DokitManagerFragment.IS_EDIT) {\n                        holder.getView<ImageView>(R.id.iv_tag).visibility = View.VISIBLE\n                        holder.getView<ImageView>(R.id.iv_tag).apply {\n                            if (item.checked) {\n                                setImageResource(R.mipmap.dk_kit_item_checked)\n                            } else {\n                                setImageResource(R.mipmap.dk_kit_item_normal)\n                            }\n                        }\n                        if (item.checked) {\n                            holder.getView<View>(R.id.view_mask).visibility = View.GONE\n                        } else {\n                            holder.getView<View>(R.id.view_mask).visibility = View.VISIBLE\n                        }\n                    } else {\n                        holder.getView<ImageView>(R.id.iv_tag).visibility = View.GONE\n                        holder.getView<View>(R.id.view_mask).visibility = View.GONE\n                    }\n\n\n                }\n\n            }\n\n        }\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/DokitManagerFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel\n\nimport android.animation.ValueAnimator\nimport android.graphics.Color\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.EditText\nimport android.widget.TextView\nimport androidx.annotation.LayoutRes\nimport androidx.core.content.ContextCompat\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.kit.toolpanel.decoration.HorizontalDividerItemDecoration\nimport com.didichuxing.doraemonkit.kit.toolpanel.decoration.VerticalDividerItemDecoration\nimport com.didichuxing.doraemonkit.util.*\nimport com.didichuxing.doraemonkit.widget.brvah.listener.OnItemDragListener\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\nimport com.didichuxing.doraemonkit.widget.dialog.DialogListener\nimport com.didichuxing.doraemonkit.widget.dialog.DialogProvider\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/29-15:00\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DokitManagerFragment : BaseFragment() {\n    private lateinit var mAdapter: DokitManagerAdapter\n    private val mKits: MutableList<KitWrapItem> = mutableListOf()\n    private val mBakKits: MutableList<KitWrapItem> = mutableListOf()\n    private var mDragStartPos = -1\n    private val mBakGlobalKits: LinkedHashMap<String, MutableList<KitWrapItem>> = LinkedHashMap()\n\n    @LayoutRes\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_kit_manager\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        generateData()\n        initView()\n    }\n\n    private fun updateGlobalBakKits() {\n        //更新备份数据 需要深度拷贝\n        for (group in mBakGlobalKits.keys) {\n            when (group) {\n                DoKitManager.GROUP_ID_PLATFORM,\n                DoKitManager.GROUP_ID_COMM,\n                DoKitManager.GROUP_ID_WEEX,\n                DoKitManager.GROUP_ID_PERFORMANCE,\n                DoKitManager.GROUP_ID_LBS,\n                DoKitManager.GROUP_ID_UI -> {\n                    mBakGlobalKits[group]?.clear()\n                }\n            }\n        }\n\n        for (group in DoKitManager.GLOBAL_KITS.keys) {\n            mBakGlobalKits[group] = mutableListOf()\n            DoKitManager.GLOBAL_KITS[group]?.forEach { it ->\n                mBakGlobalKits[group]?.add(it.clone())\n            }\n        }\n\n    }\n\n    private fun generateData() {\n        updateGlobalBakKits()\n        mKits.clear()\n        DoKitManager.GLOBAL_KITS.forEach { group ->\n            when (group.key) {\n                DoKitManager.GROUP_ID_PLATFORM,\n                DoKitManager.GROUP_ID_COMM,\n                DoKitManager.GROUP_ID_WEEX,\n                DoKitManager.GROUP_ID_PERFORMANCE,\n                DoKitManager.GROUP_ID_LBS,\n                DoKitManager.GROUP_ID_UI -> {\n                    if (group.value.size != 0) {\n                        mKits.add(\n                            KitWrapItem(\n                                KitWrapItem.TYPE_TITLE,\n                                name = DoKitCommUtil.getString(\n                                    DoKitCommUtil.getStringId(group.key)\n                                ),\n                                kit = null\n                            )\n                        )\n                        group.value.forEach { kitWrap ->\n                            if (kitWrap.checked) {\n                                mKits.add(kitWrap)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * 重置kits 数据\n     */\n    private fun reSetKits(isEdit: Boolean) {\n        mKits.clear()\n        if (isEdit) {\n            //全显示\n            DoKitManager.GLOBAL_KITS.forEach { group ->\n                when (group.key) {\n                    DoKitManager.GROUP_ID_PLATFORM,\n                    DoKitManager.GROUP_ID_COMM,\n                    DoKitManager.GROUP_ID_WEEX,\n                    DoKitManager.GROUP_ID_PERFORMANCE,\n                    DoKitManager.GROUP_ID_LBS,\n                    DoKitManager.GROUP_ID_UI -> {\n                        if (group.value.size != 0) {\n                            mKits.add(\n                                KitWrapItem(\n                                    KitWrapItem.TYPE_TITLE,\n                                    name = DoKitCommUtil.getString(\n                                        DoKitCommUtil.getStringId(group.key)\n                                    ),\n                                    kit = null\n                                )\n                            )\n                            group.value.forEach { kitWrap ->\n                                mKits.add(kitWrap)\n                            }\n                        }\n                    }\n                }\n            }\n        } else {\n            //隐藏的不显示\n            generateData()\n        }\n\n        mAdapter.notifyDataSetChanged()\n    }\n\n    /**\n     * 保存数据到本地\n     */\n    private fun saveSystemKits() {\n        val localKits: MutableList<KitGroupBean> = mutableListOf()\n        DoKitManager.GLOBAL_KITS.forEach { group ->\n            when (group.key) {\n                DoKitManager.GROUP_ID_PLATFORM,\n                DoKitManager.GROUP_ID_COMM,\n                DoKitManager.GROUP_ID_WEEX,\n                DoKitManager.GROUP_ID_PERFORMANCE,\n                DoKitManager.GROUP_ID_LBS,\n                DoKitManager.GROUP_ID_UI -> {\n                    val groupBean = KitGroupBean(group.key, mutableListOf())\n                    localKits.add(groupBean)\n                    group.value.forEach {\n                        groupBean.kits.add(\n                            KitBean(\n                                it.kit!!.javaClass.canonicalName!!,\n                                it.checked,\n                                it.kit.innerKitId()\n                            )\n                        )\n                    }\n                }\n            }\n        }\n\n        val json = GsonUtils.toJson(localKits)\n        FileIOUtils.writeFileFromString(DoKitManager.SYSTEM_KITS_BAK_PATH, json, false)\n    }\n\n    /**\n     * 重新进行分组\n     */\n    private fun reGroupForKit() {\n        //先清空分组内的数据\n        for (group: String in DoKitManager.GLOBAL_KITS.keys) {\n            when (group) {\n                DoKitManager.GROUP_ID_PLATFORM,\n                DoKitManager.GROUP_ID_COMM,\n                DoKitManager.GROUP_ID_WEEX,\n                DoKitManager.GROUP_ID_PERFORMANCE,\n                DoKitManager.GROUP_ID_LBS,\n                DoKitManager.GROUP_ID_UI ->\n                    DoKitManager.GLOBAL_KITS[group]?.clear()\n            }\n        }\n\n        mKits.forEach {\n            if (it.itemType == KitWrapItem.TYPE_KIT) {\n                DoKitManager.GLOBAL_KITS[it.groupName]?.add(it)\n            }\n        }\n    }\n\n    private fun dealBack() {\n        if (IS_EDIT) {\n            showDialog(\n                ConfirmDialogProvider(\n                    DoKitCommUtil.getString(R.string.dk_toolpanel_dialog_edit_tip),\n                    object : DialogListener {\n                        override fun onPositive(dialogProvider: DialogProvider<*>): Boolean {\n                            //需要将数据保存在本地备份\n                            saveSystemKits()\n                            finish()\n                            return true\n                        }\n\n\n                        override fun onNegative(dialogProvider: DialogProvider<*>): Boolean {\n                            DoKitManager.GLOBAL_KITS.putAll(mBakGlobalKits)\n                            finish()\n                            return true\n                        }\n\n                    })\n            )\n        } else {\n            finish()\n        }\n\n        IS_EDIT = false\n    }\n\n    private fun dealTitleBar() {\n        findViewById<View>(R.id.tv_reset).visibility = View.GONE\n        findViewById<View>(R.id.ll_back).setOnClickListener {\n            dealBack()\n        }\n\n        findViewById<View>(R.id.tv_edit).setOnClickListener {\n            val textView = it as TextView\n            if (DoKitCommUtil.getString(R.string.dk_edit) == textView.text.toString()) {\n                findViewById<View>(R.id.tv_reset).visibility = View.VISIBLE\n                IS_EDIT = true\n                textView.text = DoKitCommUtil.getString(R.string.dk_complete)\n                textView.setTextColor(\n                    ContextCompat.getColor(\n                        DoKit.APPLICATION!!,\n                        R.color.dk_color_337CC4\n                    )\n                )\n                mAdapter.draggableModule.isDragEnabled = true\n                //需要重新过滤数据\n                reSetKits(true)\n            } else if (DoKitCommUtil.getString(R.string.dk_complete) == textView.text.toString()) {\n                findViewById<View>(R.id.tv_reset).visibility = View.GONE\n                IS_EDIT = false\n                textView.text = DoKitCommUtil.getString(R.string.dk_edit)\n                textView.setTextColor(\n                    ContextCompat.getColor(\n                        DoKit.APPLICATION!!,\n                        R.color.dk_color_333333\n                    )\n                )\n                mAdapter.draggableModule.isDragEnabled = false\n                //需要重新过滤数据\n                reSetKits(false)\n                //需要将数据保存在本地备份\n                saveSystemKits()\n                //弹框\n                showDialog(\n                    TipDialogProvider(\n                        DoKitCommUtil.getString(R.string.dk_toolpanel_save_complete),\n                        null\n                    )\n                )\n            }\n\n            mAdapter.notifyDataSetChanged()\n        }\n        //还原\n        findViewById<View>(R.id.tv_reset).setOnClickListener {\n            showDialog(\n                ConfirmDialogProvider(\n                    DoKitCommUtil.getString(R.string.dk_toolpanel_dialog_reset_tip),\n                    object : DialogListener {\n                        override fun onPositive(dialogProvider: DialogProvider<*>): Boolean {\n                            val open =\n                                DoKit.APPLICATION.assets?.open(\"dokit_system_kits.json\")\n                            val json = ConvertUtils.inputStream2String(open, \"UTF-8\")\n                            //设置成默认的系统控件排序\n                            ToolPanelUtil.jsonConfig2InnerKits(json)\n                            generateData()\n                            mAdapter.notifyDataSetChanged()\n                            saveSystemKits()\n\n                            findViewById<View>(R.id.tv_reset).visibility = View.GONE\n                            IS_EDIT = false\n\n                            findViewById<TextView>(R.id.tv_edit).apply {\n                                text = DoKitCommUtil.getString(R.string.dk_edit)\n                                setTextColor(\n                                    ContextCompat.getColor(\n                                        DoKit.APPLICATION,\n                                        R.color.dk_color_333333\n                                    )\n                                )\n                            }\n\n                            mAdapter.draggableModule.isDragEnabled = false\n                            showDialog(\n                                TipDialogProvider(\n                                    DoKitCommUtil.getString(R.string.dk_toolpanel_reset_complete),\n                                    null\n                                )\n                            )\n                            return true\n                        }\n\n                        override fun onNegative(dialogProvider: DialogProvider<*>): Boolean {\n                            return true\n                        }\n\n                    })\n            )\n\n\n        }\n\n    }\n\n    private fun initView() {\n        dealTitleBar()\n        mAdapter = DokitManagerAdapter(mKits)\n        mAdapter.draggableModule.isDragEnabled = false\n        mAdapter.draggableModule.setOnItemDragListener(object : OnItemDragListener {\n            override fun onItemDragStart(viewHolder: RecyclerView.ViewHolder?, pos: Int) {\n                val holder: BaseViewHolder = viewHolder as BaseViewHolder\n                val startColor = Color.WHITE\n                val endColor = Color.rgb(245, 245, 245)\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                    val v = ValueAnimator.ofArgb(startColor, endColor)\n                    v.addUpdateListener { holder.itemView.setBackgroundColor(it.getAnimatedValue() as Int) }\n                    v.duration = 300\n                    v.start()\n                }\n                VibrateUtils.vibrate(50)\n                mDragStartPos = pos\n                //copy 一份数据用来做位置交换\n                mBakKits.clear()\n                mBakKits.addAll(mKits)\n            }\n\n            /**\n             * 针对drag状态，当前target对应的item是否允许移动\n             * 我们一般用drag来做一些换位置的操作，就是当前对应的target对应的Item可以移动\n             */\n            override fun canDropOver(\n                recyclerView: RecyclerView,\n                current: RecyclerView.ViewHolder,\n                target: RecyclerView.ViewHolder\n            ): Boolean {\n                //如果当前分组只存在一个item 不允许移动\n                val groupName = mKits[current.adapterPosition].groupName\n                if (DoKitManager.GLOBAL_KITS[groupName]?.size == 1) {\n                    ToastUtils.showShort(\"分组中必须存在一个元素\")\n                    return false\n                }\n                return true\n            }\n\n            override fun onItemDragMoving(\n                source: RecyclerView.ViewHolder?,\n                from: Int,\n                target: RecyclerView.ViewHolder?,\n                to: Int\n            ) {\n\n            }\n\n\n            override fun onItemDragEnd(viewHolder: RecyclerView.ViewHolder?, pos: Int) {\n                val holder = viewHolder as BaseViewHolder\n                // 结束时，item背景色变化，demo这里使用了一个动画渐变，使得自然\n                val startColor = Color.rgb(245, 245, 245)\n                val endColor = Color.WHITE\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                    val v = ValueAnimator.ofArgb(startColor, endColor)\n                    v.addUpdateListener { holder.itemView.setBackgroundColor(it.animatedValue as Int) }\n                    v.duration = 300\n                    v.start()\n                }\n                //针对kits重新分组 交换位置\n                if (mDragStartPos != pos) {\n                    //设置当前item的新分组名称\n                    val originItem = mBakKits[pos]\n                    val currentItem = mKits[pos]\n                    if (originItem.itemType == currentItem.itemType) {\n                        currentItem.groupName = originItem.groupName\n                    } else {\n                        currentItem.groupName = mBakKits[pos - 1].groupName\n                    }\n                    //原来的\n                    reGroupForKit()\n                }\n            }\n        })\n\n\n        val gridLayoutManager = GridLayoutManager(activity, 4)\n        mAdapter.setGridSpanSizeLookup { _, viewType, _ ->\n            if (viewType == KitWrapItem.TYPE_TITLE) {\n                4\n            } else {\n                1\n            }\n        }\n\n\n        mAdapter.setOnItemClickListener { _, _, position ->\n            if (IS_EDIT) {\n                val multiKitItem = mKits[position]\n                if (multiKitItem.itemType == KitWrapItem.TYPE_KIT) {\n                    multiKitItem.checked = !multiKitItem.checked\n                    mAdapter.notifyDataSetChanged()\n                    DoKitManager.GLOBAL_KITS[multiKitItem.groupName]?.forEach {\n                        if (it.kit?.innerKitId() == multiKitItem.kit?.innerKitId()) {\n                            it.checked = multiKitItem.checked\n                        }\n                    }\n                }\n            }\n        }\n\n        val horizontalDividerItemDecoration =\n            HorizontalDividerItemDecoration.Builder(requireActivity())\n                .color(ContextCompat.getColor(requireActivity(), R.color.dk_color_E5E5E5))\n                .size(1)\n                .showLastDivider()\n                .build()\n        val verticalDividerItemDecoration = VerticalDividerItemDecoration.Builder(activity)\n            .color(ContextCompat.getColor(requireActivity(), R.color.dk_color_E5E5E5))\n            .size(1)\n            .showLastDivider()\n            .build()\n        findViewById<RecyclerView>(R.id.rv_kits)\n            .apply {\n                addItemDecoration(horizontalDividerItemDecoration)\n                addItemDecoration(verticalDividerItemDecoration)\n                layoutManager = gridLayoutManager\n                adapter = mAdapter\n            }\n\n    }\n\n    override fun onBackPressed(): Boolean {\n        dealBack()\n        return true\n    }\n\n\n    companion object {\n        var IS_EDIT: Boolean = false\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        mAdapter.context = null\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/DokitMoreAdapter.kt",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel\n\nimport androidx.annotation.LayoutRes\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.toolpanel.bean.MorePageGroupBean\nimport com.didichuxing.doraemonkit.widget.brvah.BaseSectionQuickAdapter\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/29-15:21\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DokitMoreAdapter(\n    @LayoutRes headerId: Int,\n    @LayoutRes contentId: Int,\n    groups: MutableList<MorePageGroupBean.DataBean.GroupBean.ListBean>\n) : BaseSectionQuickAdapter<MorePageGroupBean.DataBean.GroupBean.ListBean, BaseViewHolder>(\n    headerId,\n    contentId,\n    groups\n) {\n\n    init {\n        addChildClickViewIds(R.id.more_item)\n    }\n\n    override fun convertHeader(\n        helper: BaseViewHolder,\n        item: MorePageGroupBean.DataBean.GroupBean.ListBean\n    ) {\n        helper.setText(R.id.tv_title, item.name)\n    }\n\n    override fun convert(\n        holder: BaseViewHolder,\n        item: MorePageGroupBean.DataBean.GroupBean.ListBean\n    ) {\n        holder.setText(R.id.tv_name, item.name)\n        holder.setText(R.id.tv_desc, item.desc)\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/DokitMoreFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.annotation.LayoutRes\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.android.volley.Request\nimport com.android.volley.toolbox.StringRequest\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager\nimport com.didichuxing.doraemonkit.kit.toolpanel.bean.MorePageGroupBean\nimport com.didichuxing.doraemonkit.kit.webview.CommWebViewFragment\nimport com.didichuxing.doraemonkit.kit.webview.WebViewManager\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport com.didichuxing.doraemonkit.volley.VolleyManager\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/29-15:00\n * 描    述：dokit 更多页面\n * 修订历史：\n * ================================================\n */\nclass DokitMoreFragment : BaseFragment() {\n    private lateinit var mAdapter: DokitMoreAdapter\n\n    @LayoutRes\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_more\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        getData()\n    }\n\n    /**\n     * 获取列表数据\n     */\n    fun getData() {\n\n        val request = StringRequest(\n            Request.Method.GET,\n            NetworkManager.DOKIT_MORE_PAGE_URL,\n            {\n                val morePageGroupBean = GsonUtils.fromJson(\n                    it,\n                    MorePageGroupBean::class.java\n                )\n                initView(convertGroup2normalItem(morePageGroupBean.data.group))\n            }, {\n                initView(convertGroup2normalItem(createDefaultGroups()))\n            })\n        VolleyManager.add(request)\n\n    }\n\n    /**\n     * 构造默认数据\n     */\n    private fun createDefaultGroups(): MutableList<MorePageGroupBean.DataBean.GroupBean> {\n        val groups: MutableList<MorePageGroupBean.DataBean.GroupBean> = mutableListOf()\n        val groupBean = MorePageGroupBean.DataBean.GroupBean()\n        groupBean.group = \"本地功能\"\n        val list: MutableList<MorePageGroupBean.DataBean.GroupBean.ListBean> =\n            mutableListOf()\n        val listBean = MorePageGroupBean.DataBean.GroupBean.ListBean()\n        listBean.name = \"功能管理\"\n        listBean.desc = \"介绍:可以针对dokit的内置工具列表进行自定义排序\"\n        listBean.link = \"dokit://native/function_manager\"\n        listBean.type = \"native\"\n        list.add(listBean)\n        groupBean.list = list\n        groups.add(groupBean)\n        return groups\n    }\n\n\n    private fun convertGroup2normalItem(groups: MutableList<MorePageGroupBean.DataBean.GroupBean>): MutableList<MorePageGroupBean.DataBean.GroupBean.ListBean> {\n        val items: MutableList<MorePageGroupBean.DataBean.GroupBean.ListBean> = mutableListOf()\n        groups.forEach { group ->\n            val item = MorePageGroupBean.DataBean.GroupBean.ListBean()\n            item.name = group.group\n            item.setHeader(true)\n            items.add(item)\n            group.list.forEach { innerItem ->\n                items.add(innerItem)\n            }\n        }\n        return items\n    }\n\n    private var allItems: MutableList<MorePageGroupBean.DataBean.GroupBean.ListBean>? = null\n\n    fun initView(items: MutableList<MorePageGroupBean.DataBean.GroupBean.ListBean>) {\n        allItems = items\n        findViewById<HomeTitleBar>(R.id.title_bar).setListener {\n            finish()\n        }\n\n        mAdapter =\n            DokitMoreAdapter(R.layout.dk_item_more_header, R.layout.dk_item_more_content, items)\n        findViewById<RecyclerView>(R.id.setting_list).apply {\n            adapter = mAdapter\n            layoutManager = LinearLayoutManager(activity)\n        }\n\n        mAdapter.setOnItemChildClickListener { _, _, position ->\n\n            val item = allItems?.get(position)\n            item?.let { item ->\n                when (item.type) {\n                    \"native\" -> {\n                        if (item.link == \"dokit://native/function_manager\") {\n                            activity?.let {\n                                DoKit.launchFullScreen(\n                                    DokitManagerFragment::class.java,\n                                    it\n                                )\n                            }\n                        } else {\n                        }\n                    }\n                    \"web\" -> {\n                        activity?.let {\n                            WebViewManager.url = item.link\n                            DoKit.launchFullScreen(\n                                CommWebViewFragment::class.java,\n                                it\n                            )\n                        }\n                    }\n                    else -> {\n\n                    }\n                }\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/KitBeans.kt",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/6-16:48\n * 描    述：自定义拖拽kit 数据持久化做准备\n * 修订历史：\n * ================================================\n */\n\ndata class KitGroupBean(var groupId: String, var kits: MutableList<KitBean>)\ndata class KitBean(var allClassName: String, var checked: Boolean, var innerKitId: String = \"\")\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/KitWrapItem.kt",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel\n\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.widget.brvah.entity.MultiItemEntity\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/29-19:33\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class KitWrapItem(override val itemType: Int, val name: String, var checked: Boolean = false, var groupName: String = \"\", val kit: AbstractKit?) : MultiItemEntity, Cloneable {\n    companion object {\n        const val TYPE_TITLE = 999\n        const val TYPE_KIT = 201\n        const val TYPE_MODE = 202\n        const val TYPE_EXIT = 203\n        const val TYPE_VERSION = 204\n    }\n\n    public override fun clone(): KitWrapItem {\n        val item = super.clone() as KitWrapItem\n        item.checked = this.checked\n        item.groupName = this.groupName\n        return item\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/TipDialogProvider.kt",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel\n\nimport android.view.View\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.widget.dialog.DialogListener\nimport com.didichuxing.doraemonkit.widget.dialog.DialogProvider\n\n/**\n * Created by jint on 2019/4/12\n * 完善健康体检用户信息dialog\n *\n * @author jintai\n */\nclass TipDialogProvider internal constructor(data: Any?, listener: DialogListener?) : DialogProvider<Any?>(data, listener) {\n    private lateinit var mTip: TextView\n\n    override fun getLayoutId(): Int {\n        return R.layout.dk_dialog_tip\n    }\n\n    override fun findViews(view: View) {\n        mTip = view.findViewById(R.id.tv_tip)\n    }\n\n    override fun bindData(data: Any?) {\n        if (data is String) {\n            mTip.text = data\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/ToolPanelAdapter.kt",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel\n\nimport android.os.Process\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport android.widget.RadioButton\nimport android.widget.RadioGroup\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.BuildConfig\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.constant.SharedPrefsKey\nimport com.didichuxing.doraemonkit.util.*\nimport com.didichuxing.doraemonkit.widget.brvah.BaseMultiItemQuickAdapter\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/29-15:21\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass ToolPanelAdapter(kitViews: MutableList<KitWrapItem>?) :\n    BaseMultiItemQuickAdapter<KitWrapItem, BaseViewHolder>(kitViews) {\n\n    init {\n        addItemType(KitWrapItem.TYPE_TITLE, R.layout.dk_item_group_title)\n        addItemType(KitWrapItem.TYPE_KIT, R.layout.dk_item_kit)\n        addItemType(KitWrapItem.TYPE_MODE, R.layout.dk_item_group_mode)\n        addItemType(KitWrapItem.TYPE_EXIT, R.layout.dk_item_group_exit)\n        addItemType(KitWrapItem.TYPE_VERSION, R.layout.dk_item_group_version)\n    }\n\n    override fun convert(holder: BaseViewHolder, item: KitWrapItem) {\n        when (item.itemType) {\n            KitWrapItem.TYPE_TITLE -> {\n                item.name.let {\n                    if (it == DoKitCommUtil.getString(R.string.dk_category_platform)) {\n                        holder.getView<TextView>(R.id.tv_sub_title_name).visibility = View.VISIBLE\n                        holder.getView<TextView>(R.id.tv_sub_title_name).text = \"(www.dokit.cn)\"\n                    } else {\n                        holder.getView<TextView>(R.id.tv_sub_title_name).visibility = View.GONE\n                    }\n                    holder.getView<TextView>(R.id.tv_title_name).text = it\n                }\n            }\n            KitWrapItem.TYPE_KIT -> {\n                item.kit?.let {\n                    holder.getView<TextView>(R.id.name).setText(it.name)\n                    holder.getView<ImageView>(R.id.icon).setImageResource(it.icon)\n                }\n\n            }\n\n            KitWrapItem.TYPE_MODE -> {\n                val radioGroup = holder.getView<RadioGroup>(R.id.rb_group)\n                val rbNormal = holder.getView<RadioButton>(R.id.rb_normal)\n                val rbSystem = holder.getView<RadioButton>(R.id.rb_system)\n                radioGroup.setOnCheckedChangeListener { _, checkedId ->\n                    if (checkedId == R.id.rb_normal) {\n                        //选中normal\n                        DoKitSPUtil.putString(SharedPrefsKey.FLOAT_START_MODE, \"normal\")\n                    } else {\n                        //选中系统\n                        DoKitSPUtil.putString(SharedPrefsKey.FLOAT_START_MODE, \"system\")\n                    }\n                }\n\n                rbNormal.setOnClickListener {\n                    rbNormal.postDelayed({\n                        AppUtils.relaunchApp()\n                        Process.killProcess(Process.myPid())\n                        System.exit(1)\n                    }, 500)\n                }\n\n                rbSystem.setOnClickListener {\n                    rbSystem.postDelayed({\n                        AppUtils.relaunchApp()\n                        Process.killProcess(Process.myPid())\n                        System.exit(1)\n                    }, 500)\n                }\n\n                val floatMode = DoKitSPUtil.getString(SharedPrefsKey.FLOAT_START_MODE, \"normal\")\n                if (floatMode == \"normal\") {\n                    rbNormal.isChecked = true\n                } else {\n                    rbSystem.isChecked = true\n                }\n            }\n\n            KitWrapItem.TYPE_EXIT -> {\n                holder.getView<TextView>(R.id.close).setOnClickListener {\n                    DoKit.hideToolPanel()\n                    DoKit.hide()\n                }\n\n            }\n\n            KitWrapItem.TYPE_VERSION -> {\n                val name = holder.getView<TextView>(R.id.version)\n                //适配无法准确获取底部导航栏高度的bug\n                if (name.parent != null) {\n                    (name.parent as ViewGroup).setPadding(0, 0, 0, BarUtils.getNavBarHeight())\n                }\n                val version: String = DoKitCommUtil.getString(R.string.dk_kit_version)\n                name.text = String.format(version, BuildConfig.DOKIT_VERSION)\n\n            }\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/ToolPanelDoKitView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel\n\nimport android.content.Context\nimport android.text.TextUtils\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.datapick.DataPickManager\nimport com.didichuxing.doraemonkit.datapick.DataPickUtils\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewManager\nimport com.didichuxing.doraemonkit.util.ActivityUtils\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n\n/**\n * @author jintai\n * Created by jintai on 2019/09/26.\n * 新的工具面板弹窗\n */\nclass ToolPanelDoKitView : AbsDoKitView() {\n    private lateinit var mAdapter: ToolPanelAdapter\n    private var mKits: MutableList<KitWrapItem> = mutableListOf()\n\n    override fun onCreate(context: Context) {\n\n    }\n\n    override fun onCreateView(context: Context, view: FrameLayout): View {\n        return LayoutInflater.from(context).inflate(R.layout.dk_tool_panel, view, false)\n    }\n\n    override fun onViewCreated(view: FrameLayout) {\n        generateKits()\n        initView()\n    }\n\n    private fun generateKits() {\n        DoKitManager.GLOBAL_KITS.forEach { group ->\n            when (group.key) {\n                DoKitCommUtil.getString(R.string.dk_category_mode) -> {\n                    mKits.add(KitWrapItem(KitWrapItem.TYPE_MODE, name = group.key, kit = null))\n                }\n                DoKitCommUtil.getString(R.string.dk_category_exit) -> {\n                    mKits.add(KitWrapItem(KitWrapItem.TYPE_EXIT, name = group.key, kit = null))\n                }\n                DoKitCommUtil.getString(R.string.dk_category_version) -> {\n                    mKits.add(KitWrapItem(KitWrapItem.TYPE_VERSION, name = group.key, kit = null))\n                }\n                DoKitManager.GROUP_ID_PLATFORM,\n                DoKitManager.GROUP_ID_COMM,\n                DoKitManager.GROUP_ID_WEEX,\n                DoKitManager.GROUP_ID_PERFORMANCE,\n                DoKitManager.GROUP_ID_LBS,\n                DoKitManager.GROUP_ID_UI -> {\n                    if (group.value.size != 0) {\n                        mKits.add(\n                            KitWrapItem(\n                                KitWrapItem.TYPE_TITLE, name = DoKitCommUtil.getString(\n                                    DoKitCommUtil.getStringId(group.key)\n                                ), kit = null\n                            )\n                        )\n                        group.value.forEach { kitWrap ->\n                            if (kitWrap.checked) {\n                                mKits.add(kitWrap)\n                            }\n                        }\n                    }\n                }\n                else -> {\n                    if (group.value.size != 0) {\n                        mKits.add(KitWrapItem(KitWrapItem.TYPE_TITLE, name = group.key, kit = null))\n                        group.value.forEach { kitWrap ->\n                            if (kitWrap.checked) {\n                                mKits.add(kitWrap)\n                            }\n                        }\n                    }\n                }\n            }\n\n        }\n    }\n\n\n    private fun initView() {\n        val titleBar = findViewById<TitleBar>(R.id.title_bar)\n        titleBar?.setOnTitleBarClickListener(object : TitleBar.OnTitleBarClickListener {\n            override fun onLeftClick() {\n                detach()\n            }\n\n            override fun onRightClick() {\n                if (!isNormalMode) {\n                    DoKit.hideToolPanel()\n                }\n                DoKit.launchFullScreen(\n                    DokitMoreFragment::class.java,\n                    activity,\n                    isSystemFragment = true\n                )\n            }\n        })\n        mAdapter = ToolPanelAdapter(mKits)\n        val gridLayoutManager = GridLayoutManager(activity, 4)\n        mAdapter.setGridSpanSizeLookup { _, viewType, _ ->\n            if (viewType == KitWrapItem.TYPE_KIT) {\n                1\n            } else {\n                4\n            }\n        }\n        mAdapter.setOnItemClickListener { _, _, position ->\n            try {\n                val multiKitItem = mKits[position]\n                if (multiKitItem.itemType == KitWrapItem.TYPE_KIT) {\n                    multiKitItem.kit?.let {\n                        //常规模式下点击常用工具不隐藏工具面板\n                        it.onClick(ActivityUtils.getTopActivity())\n                        if (it.onClickWithReturn(ActivityUtils.getTopActivity())) {\n                            DoKitViewManager.INSTANCE.detachToolPanel()\n                        }\n\n                        //添加埋点\n                        if (it.isInnerKit && !TextUtils.isEmpty(it.innerKitId())) {\n                            DataPickManager.getInstance().addData(it.innerKitId(), DataPickUtils.getDoKitHomeClickPage(), multiKitItem.name)\n                        } else {\n                            DataPickManager.getInstance().addData(\"dokit_sdk_business_ck\", DataPickUtils.getDoKitHomeClickPage(), multiKitItem.name)\n                        }\n\n                    }\n                }\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n        val rvKits = findViewById<RecyclerView>(R.id.rv_kits)\n        rvKits?.layoutManager = gridLayoutManager\n        rvKits?.adapter = mAdapter\n\n    }\n\n\n    override fun initDokitViewLayoutParams(params: DoKitViewLayoutParams) {\n        params.x = 0\n        params.y = 0\n        params.width = DoKitViewLayoutParams.MATCH_PARENT\n        params.height = DoKitViewLayoutParams.MATCH_PARENT\n    }\n\n    override fun onBackPressed(): Boolean {\n        detach()\n        return true\n    }\n\n    override fun shouldDealBackKey(): Boolean {\n        return true\n    }\n\n    override fun onHomeKeyPress() {\n        detach()\n    }\n\n    override fun onRecentAppKeyPress() {\n        detach()\n    }\n\n    override fun onResume() {\n        super.onResume()\n        resumeData()\n        mAdapter.notifyDataSetChanged()\n    }\n\n    private fun resumeData() {\n        mKits.clear()\n        generateKits()\n    }\n\n    override fun restrictBorderline(): Boolean {\n        return false\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/ToolPanelUtil.kt",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel\n\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport java.util.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/6-16:59\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass ToolPanelUtil {\n    companion object {\n\n        /**\n         * json 转系统kit\n         */\n        fun jsonConfig2InnerKits(json: String) {\n            val doKitInnerKits =\n                ServiceLoader.load(AbstractKit::class.java, javaClass.classLoader).toList()\n\n            val doKitInnerKitMaps = mutableMapOf<String, AbstractKit>()\n            doKitInnerKits.forEach {\n                doKitInnerKitMaps[it.innerKitId()] = it\n            }\n\n            val localConfigs: MutableList<KitGroupBean> =\n                GsonUtils.fromJson(json, GsonUtils.getListType(KitGroupBean::class.java))\n\n            localConfigs.forEach { group ->\n                DoKitManager.GLOBAL_SYSTEM_KITS[group.groupId] = mutableListOf()\n                group.kits.forEach { kitBean ->\n                    //有可能不存在该模块\n                    val kit: AbstractKit? = doKitInnerKitMaps[kitBean.innerKitId]\n                    kit?.let {\n                        val kitWrapItem = KitWrapItem(\n                            KitWrapItem.TYPE_KIT,\n                            DoKitCommUtil.getString(kit.name),\n                            kitBean.checked,\n                            group.groupId,\n                            kit\n                        )\n                        DoKitManager.GLOBAL_SYSTEM_KITS[group.groupId]?.add(kitWrapItem)\n                    }\n                }\n            }\n\n\n            for (groupId: String in DoKitManager.GLOBAL_SYSTEM_KITS.keys) {\n                DoKitManager.GLOBAL_KITS[groupId] = DoKitManager.GLOBAL_SYSTEM_KITS[groupId]!!\n            }\n        }\n\n\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/bean/MorePageGroupBean.java",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel.bean;\n\nimport com.didichuxing.doraemonkit.widget.brvah.entity.JSectionEntity;\n\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/8/21-15:10\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class MorePageGroupBean {\n\n    /**\n     * success : true\n     * data : {\"group\":[{\"group\":\"本地功能\",\"list\":[{\"name\":\"功能管理\",\"desc\":\"本地功能管理\",\"link\":\"dokit://native/function_manager\",\"type\":\"native\"}]},{\"group\":\"精品推荐\",\"list\":[{\"name\":\"hummer\",\"desc\":\"新一代跨端方案\",\"link\":\"https://m.baidu.com\",\"type\":\"web\"}]},{\"group\":\"官方消息\",\"list\":[{\"name\":\"dokit操作手册\",\"desc\":\"dokit操作手册\",\"link\":\"http://xingyun.xiaojukeji.com/docs/dokit/#/intro\",\"type\":\"web\"}]}]}\n     * code : 200\n     */\n\n    private boolean success;\n    private DataBean data;\n    private int code;\n\n    public boolean isSuccess() {\n        return success;\n    }\n\n    public void setSuccess(boolean success) {\n        this.success = success;\n    }\n\n    public DataBean getData() {\n        return data;\n    }\n\n    public void setData(DataBean data) {\n        this.data = data;\n    }\n\n    public int getCode() {\n        return code;\n    }\n\n    public void setCode(int code) {\n        this.code = code;\n    }\n\n    public static class DataBean {\n        private List<GroupBean> group;\n\n        public List<GroupBean> getGroup() {\n            return group;\n        }\n\n        public void setGroup(List<GroupBean> group) {\n            this.group = group;\n        }\n\n        public static class GroupBean {\n            /**\n             * group : 本地功能\n             * list : [{\"name\":\"功能管理\",\"desc\":\"本地功能管理\",\"link\":\"dokit://native/function_manager\",\"type\":\"native\"}]\n             */\n\n            private String group;\n            private List<ListBean> list;\n\n            public String getGroup() {\n                return group;\n            }\n\n            public void setGroup(String group) {\n                this.group = group;\n            }\n\n            public List<ListBean> getList() {\n                return list;\n            }\n\n            public void setList(List<ListBean> list) {\n                this.list = list;\n            }\n\n\n            public static class ListBean extends JSectionEntity {\n                /**\n                 * name : 功能管理\n                 * desc : 本地功能管理\n                 * link : dokit://native/function_manager\n                 * type : native\n                 */\n\n                private String name;\n                private String desc;\n                private String link;\n                private String type;\n                private boolean header;\n\n                public void setHeader(boolean header) {\n                    this.header = header;\n                }\n\n                public String getName() {\n                    return name;\n                }\n\n                public void setName(String name) {\n                    this.name = name;\n                }\n\n                public String getDesc() {\n                    return desc;\n                }\n\n                public void setDesc(String desc) {\n                    this.desc = desc;\n                }\n\n                public String getLink() {\n                    return link;\n                }\n\n                public void setLink(String link) {\n                    this.link = link;\n                }\n\n                public String getType() {\n                    return type;\n                }\n\n                public void setType(String type) {\n                    this.type = type;\n                }\n\n                @Override\n                public boolean isHeader() {\n                    return header;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/decoration/FlexibleDividerDecoration.java",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel.decoration;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/30-16:05\n * 描    述：\n * 修订历史：\n * ================================================\n */\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport androidx.annotation.ColorRes;\nimport androidx.annotation.DimenRes;\nimport androidx.annotation.DrawableRes;\nimport androidx.core.content.ContextCompat;\nimport androidx.recyclerview.widget.GridLayoutManager;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.OrientationHelper;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.recyclerview.widget.StaggeredGridLayoutManager;\n\n/**\n * Created by yqritc on 2015/01/08.\n */\npublic abstract class FlexibleDividerDecoration extends RecyclerView.ItemDecoration {\n\n    private static final int DEFAULT_SIZE = 2;\n    private static final int[] ATTRS = new int[]{\n            android.R.attr.listDivider\n    };\n\n    protected enum DividerType {\n        DRAWABLE, PAINT, COLOR, SPACE\n    }\n\n    protected DividerType mDividerType = DividerType.DRAWABLE;\n    protected VisibilityProvider mVisibilityProvider;\n    protected PaintProvider mPaintProvider;\n    protected ColorProvider mColorProvider;\n    protected DrawableProvider mDrawableProvider;\n    protected SizeProvider mSizeProvider;\n    protected SizeProvider mSpaceProvider;\n    protected boolean mShowLastDivider;\n    protected boolean mPositionInsideItem;\n    private Paint mPaint;\n\n    protected FlexibleDividerDecoration(Builder builder) {\n        if (builder.mPaintProvider != null) {\n            mDividerType = DividerType.PAINT;\n            mPaintProvider = builder.mPaintProvider;\n        } else if (builder.mColorProvider != null) {\n            mDividerType = DividerType.COLOR;\n            mColorProvider = builder.mColorProvider;\n            mPaint = new Paint();\n            setSizeProvider(builder);\n        } else if (builder.mSpaceProvider != null) {\n            mDividerType = DividerType.SPACE;\n            mSpaceProvider = builder.mSpaceProvider;\n        } else {\n            mDividerType = DividerType.DRAWABLE;\n            if (builder.mDrawableProvider == null) {\n                TypedArray a = builder.mContext.obtainStyledAttributes(ATTRS);\n                final Drawable divider = a.getDrawable(0);\n                a.recycle();\n                mDrawableProvider = new DrawableProvider() {\n                    @Override\n                    public Drawable drawableProvider(int position, RecyclerView parent) {\n                        return divider;\n                    }\n                };\n            } else {\n                mDrawableProvider = builder.mDrawableProvider;\n            }\n            mSizeProvider = builder.mSizeProvider;\n        }\n\n        mVisibilityProvider = builder.mVisibilityProvider;\n        mShowLastDivider = builder.mShowLastDivider;\n        mPositionInsideItem = builder.mPositionInsideItem;\n    }\n\n    private void setSizeProvider(Builder builder) {\n        mSizeProvider = builder.mSizeProvider;\n        if (mSizeProvider == null) {\n            mSizeProvider = new SizeProvider() {\n                @Override\n                public int dividerSize(int position, RecyclerView parent) {\n                    return DEFAULT_SIZE;\n                }\n            };\n        }\n    }\n\n    @Override\n    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {\n        RecyclerView.Adapter adapter = parent.getAdapter();\n        if (adapter == null) {\n            return;\n        }\n\n        int validChildCount = parent.getChildCount();\n        for (int i = 0; i < validChildCount; i++) {\n            View child = parent.getChildAt(i);\n            int childPosition = parent.getChildAdapterPosition(child);\n\n            if (!hasDivider(parent, childPosition)) {\n                continue;\n            }\n\n            if (mVisibilityProvider.shouldHideDivider(childPosition, parent)) {\n                continue;\n            }\n\n            Rect bounds = getDividerBound(childPosition, parent, child);\n            switch (mDividerType) {\n                case DRAWABLE:\n                    Drawable drawable = mDrawableProvider.drawableProvider(childPosition, parent);\n                    drawable.setBounds(bounds);\n                    drawable.draw(c);\n                    break;\n                case PAINT:\n                    mPaint = mPaintProvider.dividerPaint(childPosition, parent);\n                    c.drawLine(bounds.left, bounds.top, bounds.right, bounds.bottom, mPaint);\n                    break;\n                case COLOR:\n                    mPaint.setColor(mColorProvider.dividerColor(childPosition, parent));\n                    mPaint.setStrokeWidth(mSizeProvider.dividerSize(childPosition, parent));\n                    c.drawLine(bounds.left, bounds.top, bounds.right, bounds.bottom, mPaint);\n                    break;\n                case SPACE:\n                    break;\n            }\n        }\n    }\n\n    /**\n     * Whether child has divider\n     *\n     * @param parent\n     * @param childPosition\n     * @return true if child has divider\n     */\n    public boolean hasDivider(RecyclerView parent, int childPosition) {\n        if (mShowLastDivider) {\n            return true;\n        } else if (this instanceof VerticalDividerItemDecoration) {\n            return hasVerticalDivider(parent, childPosition);\n        } else if (this instanceof HorizontalDividerItemDecoration) {\n            return hasHorizontalDivider(parent, childPosition);\n        }\n        return false;\n    }\n\n    private boolean hasVerticalDivider(RecyclerView parent, int position) {\n        RecyclerView.Adapter adapter = parent.getAdapter();\n        int itemCount = adapter.getItemCount();\n        int lastDividerOffset = getLastDividerOffset(parent);\n\n\n        if (parent.getLayoutManager() instanceof GridLayoutManager) {\n            GridLayoutManager manager = (GridLayoutManager) parent.getLayoutManager();\n            int spanCount = manager.getSpanCount();\n\n            if (manager.getOrientation() == RecyclerView.VERTICAL) {\n                return positionTotalSpanSize(manager, position) != spanCount;\n            } else {\n                if (manager.getReverseLayout()) {\n                    return manager.getSpanSizeLookup().getSpanGroupIndex(position, spanCount) != 0;\n                } else {\n                    return position < itemCount - lastDividerOffset;\n                }\n            }\n        } else if (parent.getLayoutManager() instanceof StaggeredGridLayoutManager) {\n            StaggeredGridLayoutManager manager = (StaggeredGridLayoutManager) parent.getLayoutManager();\n            StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) manager.findViewByPosition(position).getLayoutParams();\n            int spanCount = manager.getSpanCount();\n            int spanIndex = params.getSpanIndex();\n\n            if (manager.getOrientation() == OrientationHelper.VERTICAL) {\n                return spanIndex < spanCount - 1;\n            } else {\n                int[] lastPosition = null;\n                if (manager.getReverseLayout()) {\n                    lastPosition = manager.findFirstVisibleItemPositions(null);\n                } else {\n                    lastPosition = manager.findLastVisibleItemPositions(null);\n                }\n                boolean hasDirectionAlign = false;\n                for (int p : lastPosition) {\n                    if (p != position && p != -1) {\n                        StaggeredGridLayoutManager.LayoutParams params1 = (StaggeredGridLayoutManager.LayoutParams) manager.findViewByPosition(p).getLayoutParams();\n                        if (params1.getSpanIndex() == spanIndex) {\n                            hasDirectionAlign = true;\n                            break;\n                        }\n                    }\n                }\n                return hasDirectionAlign;\n            }\n        } else if (parent.getLayoutManager() instanceof LinearLayoutManager) {\n            if (((LinearLayoutManager) parent.getLayoutManager()).getReverseLayout()) {\n                return position > 0;\n            } else {\n                return position < itemCount - 1;\n            }\n        }\n        return false;\n    }\n\n    private boolean hasHorizontalDivider(RecyclerView parent, int position) {\n        RecyclerView.Adapter adapter = parent.getAdapter();\n        int itemCount = adapter.getItemCount();\n        int lastDividerOffset = getLastDividerOffset(parent);\n\n        if (parent.getLayoutManager() instanceof GridLayoutManager) {\n            GridLayoutManager manager = (GridLayoutManager) parent.getLayoutManager();\n            int spanCount = manager.getSpanCount();\n            // 最后一行没有\n            if (manager.getOrientation() == RecyclerView.VERTICAL) {\n                if (manager.getReverseLayout()) {\n                    GridLayoutManager.SpanSizeLookup lookup = manager.getSpanSizeLookup();\n                    return lookup.getSpanGroupIndex(position, spanCount) != 0;\n                } else {\n                    return position < itemCount - lastDividerOffset;\n                }\n            } else {\n                return positionTotalSpanSize(manager, position) != spanCount;\n            }\n        } else if (parent.getLayoutManager() instanceof StaggeredGridLayoutManager) {\n            StaggeredGridLayoutManager manager = (StaggeredGridLayoutManager) parent.getLayoutManager();\n            StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) manager.findViewByPosition(position).getLayoutParams();\n            int spanCount = manager.getSpanCount();\n            int spanIndex = params.getSpanIndex();\n\n            if (manager.getOrientation() == OrientationHelper.VERTICAL) {\n                if (manager.getReverseLayout()) {\n                    return position > spanCount - 1;\n                } else {\n                    int[] lastPosition = manager.findLastVisibleItemPositions(null);\n\n                    boolean hasBottom = false;\n                    for (int p : lastPosition) {\n                        if (p != position && p != -1) {\n                            StaggeredGridLayoutManager.LayoutParams params1 = (StaggeredGridLayoutManager.LayoutParams) manager.findViewByPosition(p).getLayoutParams();\n                            if (params1.getSpanIndex() == spanIndex) {\n                                hasBottom = true;\n                                break;\n                            }\n                        }\n                    }\n                    return hasBottom;\n                }\n            } else {\n                return spanIndex < spanCount - 1;\n            }\n        } else if (parent.getLayoutManager() instanceof LinearLayoutManager) {\n            if (((LinearLayoutManager) parent.getLayoutManager()).getReverseLayout()) {\n                return position > 0;\n            } else {\n                return position < itemCount - 1;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * @param manager\n     * @param position\n     * @return\n     */\n    protected int positionTotalSpanSize(GridLayoutManager manager, int position) {\n        int totalSpanSize = 0;\n        GridLayoutManager.SpanSizeLookup spanSizeLookup = manager.getSpanSizeLookup();\n        int spanCount = manager.getSpanCount();\n        int groupIndex = spanSizeLookup.getSpanGroupIndex(position, spanCount);\n        for (int i = position; i >= 0; i--) {\n            int thisGroupIndex = spanSizeLookup.getSpanGroupIndex(i, spanCount);\n            if (thisGroupIndex == groupIndex) {\n                totalSpanSize += spanSizeLookup.getSpanSize(i);\n            } else {\n                break;\n            }\n        }\n        return totalSpanSize;\n    }\n\n    @Override\n    public void getItemOffsets(Rect rect, View v, RecyclerView parent, RecyclerView.State state) {\n        int position = parent.getChildAdapterPosition(v);\n        if (!hasDivider(parent, position)) {\n            return;\n        }\n\n        if (mVisibilityProvider.shouldHideDivider(position, parent)) {\n            return;\n        }\n\n        setItemOffsets(rect, position, parent);\n    }\n\n    /**\n     * In the case mShowLastDivider = false,\n     * Returns offset for how many views we don't have to draw a divider for,\n     * for LinearLayoutManager it is as simple as not drawing the last child divider,\n     * but for a GridLayoutManager it needs to take the span count for the last items into account\n     * until we use the span count configured for the grid.\n     *\n     * @param parent RecyclerView\n     * @return offset for how many views we don't have to draw a divider or 1 if its a\n     * LinearLayoutManager\n     */\n    private int getLastDividerOffset(RecyclerView parent) {\n        if (parent.getLayoutManager() instanceof GridLayoutManager) {\n            GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();\n            GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup();\n            int spanCount = layoutManager.getSpanCount();\n            int itemCount = parent.getAdapter().getItemCount();\n            for (int i = itemCount - 1; i >= 0; i--) {\n                if (spanSizeLookup.getSpanIndex(i, spanCount) == 0) {\n                    return itemCount - i;\n                }\n            }\n        }\n\n        return 1;\n    }\n\n    protected abstract Rect getDividerBound(int position, RecyclerView parent, View child);\n\n    protected abstract void setItemOffsets(Rect outRect, int position, RecyclerView parent);\n\n    /**\n     * Interface for controlling divider visibility\n     */\n    public interface VisibilityProvider {\n\n        /**\n         * Returns true if divider should be hidden.\n         *\n         * @param position Divider position (or group index for GridLayoutManager)\n         * @param parent   RecyclerView\n         * @return True if the divider at position should be hidden\n         */\n        boolean shouldHideDivider(int position, RecyclerView parent);\n    }\n\n    /**\n     * Interface for controlling paint instance for divider drawing\n     */\n    public interface PaintProvider {\n\n        /**\n         * Returns {@link android.graphics.Paint} for divider\n         *\n         * @param position Divider position (or group index for GridLayoutManager)\n         * @param parent   RecyclerView\n         * @return Paint instance\n         */\n        Paint dividerPaint(int position, RecyclerView parent);\n    }\n\n    /**\n     * Interface for controlling divider color\n     */\n    public interface ColorProvider {\n\n        /**\n         * Returns {@link android.graphics.Color} value of divider\n         *\n         * @param position Divider position (or group index for GridLayoutManager)\n         * @param parent   RecyclerView\n         * @return Color value\n         */\n        int dividerColor(int position, RecyclerView parent);\n    }\n\n    /**\n     * Interface for controlling drawable object for divider drawing\n     */\n    public interface DrawableProvider {\n\n        /**\n         * Returns drawable instance for divider\n         *\n         * @param position Divider position (or group index for GridLayoutManager)\n         * @param parent   RecyclerView\n         * @return Drawable instance\n         */\n        Drawable drawableProvider(int position, RecyclerView parent);\n    }\n\n    /**\n     * Interface for controlling divider size\n     */\n    public interface SizeProvider {\n\n        /**\n         * Returns size value of divider.\n         * Height for horizontal divider, width for vertical divider\n         *\n         * @param position Divider position (or group index for GridLayoutManager)\n         * @param parent   RecyclerView\n         * @return Size of divider\n         */\n        int dividerSize(int position, RecyclerView parent);\n    }\n\n    public static class Builder<T extends Builder> {\n\n        private Context mContext;\n        protected Resources mResources;\n        private PaintProvider mPaintProvider;\n        private ColorProvider mColorProvider;\n        private DrawableProvider mDrawableProvider;\n        private SizeProvider mSizeProvider;\n        private SizeProvider mSpaceProvider;\n        private VisibilityProvider mVisibilityProvider = new VisibilityProvider() {\n            @Override\n            public boolean shouldHideDivider(int position, RecyclerView parent) {\n                return false;\n            }\n        };\n        private boolean mShowLastDivider = false;\n        private boolean mPositionInsideItem = false;\n\n        public Builder(Context context) {\n            mContext = context;\n            mResources = context.getResources();\n        }\n\n        public T paint(final Paint paint) {\n            return paintProvider(new PaintProvider() {\n                @Override\n                public Paint dividerPaint(int position, RecyclerView parent) {\n                    return paint;\n                }\n            });\n        }\n\n        public T paintProvider(PaintProvider provider) {\n            mPaintProvider = provider;\n            return (T) this;\n        }\n\n        public T color(final int color) {\n            return colorProvider(new ColorProvider() {\n                @Override\n                public int dividerColor(int position, RecyclerView parent) {\n                    return color;\n                }\n            });\n        }\n\n        public T colorResId(@ColorRes int colorId) {\n            return color(ContextCompat.getColor(mContext, colorId));\n        }\n\n        public T colorProvider(ColorProvider provider) {\n            mColorProvider = provider;\n            return (T) this;\n        }\n\n        public T drawable(@DrawableRes int id) {\n            return drawable(ContextCompat.getDrawable(mContext, id));\n        }\n\n        public T drawable(final Drawable drawable) {\n            return drawableProvider(new DrawableProvider() {\n                @Override\n                public Drawable drawableProvider(int position, RecyclerView parent) {\n                    return drawable;\n                }\n            });\n        }\n\n        public T drawableProvider(DrawableProvider provider) {\n            mDrawableProvider = provider;\n            return (T) this;\n        }\n\n        public T size(final int size) {\n            return sizeProvider(new SizeProvider() {\n                @Override\n                public int dividerSize(int position, RecyclerView parent) {\n                    return size;\n                }\n            });\n        }\n\n        public T sizeResId(@DimenRes int sizeId) {\n            return size(mResources.getDimensionPixelSize(sizeId));\n        }\n\n        public T sizeProvider(SizeProvider provider) {\n            mSizeProvider = provider;\n            return (T) this;\n        }\n\n        public T space(final int space) {\n            return spaceProvider(new SizeProvider() {\n                @Override\n                public int dividerSize(int position, RecyclerView parent) {\n                    return space;\n                }\n            });\n        }\n\n        public T spaceResId(@DimenRes int spaceId) {\n            return space(mResources.getDimensionPixelSize(spaceId));\n        }\n\n        public T spaceProvider(SizeProvider provider) {\n            mSpaceProvider = provider;\n            return (T) this;\n        }\n\n        public T visibilityProvider(VisibilityProvider provider) {\n            mVisibilityProvider = provider;\n            return (T) this;\n        }\n\n        public T showLastDivider() {\n            mShowLastDivider = true;\n            return (T) this;\n        }\n\n        public T positionInsideItem(boolean positionInsideItem) {\n            mPositionInsideItem = positionInsideItem;\n            return (T) this;\n        }\n\n        protected void checkBuilderParams() {\n            if (mPaintProvider != null) {\n                if (mColorProvider != null) {\n                    throw new IllegalArgumentException(\n                            \"Use setColor method of Paint class to specify line color. Do not provider ColorProvider if you set PaintProvider.\");\n                }\n                if (mSizeProvider != null) {\n                    throw new IllegalArgumentException(\n                            \"Use setStrokeWidth method of Paint class to specify line size. Do not provider SizeProvider if you set PaintProvider.\");\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/decoration/HorizontalDividerItemDecoration.java",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel.decoration;\n\nimport android.content.Context;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport androidx.annotation.DimenRes;\nimport androidx.core.view.ViewCompat;\nimport androidx.recyclerview.widget.GridLayoutManager;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.recyclerview.widget.StaggeredGridLayoutManager;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/30-16:07\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class HorizontalDividerItemDecoration extends FlexibleDividerDecoration {\n\n    private MarginProvider mMarginProvider;\n\n    protected HorizontalDividerItemDecoration(Builder builder) {\n        super(builder);\n        mMarginProvider = builder.mMarginProvider;\n    }\n\n    @Override\n    protected Rect getDividerBound(int position, RecyclerView parent, View child) {\n        Rect bounds = new Rect(0, 0, 0, 0);\n        int transitionX = (int) ViewCompat.getTranslationX(child);\n        int transitionY = (int) ViewCompat.getTranslationY(child);\n        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();\n        bounds.left = child.getLeft() + transitionX;\n        bounds.right = child.getRight() + transitionX;\n\n        int dividerSize = getDividerSize(position, parent);\n        if (mDividerType == DividerType.DRAWABLE || mDividerType == DividerType.SPACE) {\n            if (alignLeftEdge(parent, position)) {\n                bounds.left += mMarginProvider.dividerLeftMargin(position, parent);\n            }\n\n            if (alignRightEdge(parent, position)) {\n                bounds.right -= mMarginProvider.dividerRightMargin(position, parent);\n            } else {\n                // 交叉位置特殊处理\n                bounds.right += getDividerSize(position, parent);\n            }\n            bounds.top = child.getBottom() + params.bottomMargin + transitionY;\n            bounds.bottom = bounds.top + dividerSize;\n        } else {\n            int halfSize = dividerSize / 2;\n            bounds.top = child.getBottom() + params.bottomMargin + halfSize + transitionY;\n            bounds.bottom = bounds.top;\n        }\n\n        if (mPositionInsideItem) {\n            bounds.top -= dividerSize;\n            bounds.bottom -= dividerSize;\n        }\n\n        return bounds;\n    }\n\n    private boolean alignLeftEdge(RecyclerView parent, int position) {\n        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();\n\n        if (layoutManager instanceof GridLayoutManager) {\n            GridLayoutManager manager = (GridLayoutManager) layoutManager;\n            GridLayoutManager.SpanSizeLookup lookup = manager.getSpanSizeLookup();\n            int spanCount = manager.getSpanCount();\n            if (manager.getOrientation() == GridLayoutManager.VERTICAL) // 垂直布局\n            {\n                if (lookup.getSpanIndex(position, spanCount) == 0) // 第一列\n                {\n                    return true;\n                }\n            } else // 水平布局\n            {\n                if (manager.getReverseLayout()) {\n                    return lookup.getSpanGroupIndex(position, spanCount) == lookup.getSpanGroupIndex(parent.getAdapter().getItemCount() - 1, spanCount);\n                } else {\n                    if (lookup.getSpanGroupIndex(position, spanCount) == 0) {\n                        return true;\n                    }\n                }\n            }\n        } else if (layoutManager instanceof StaggeredGridLayoutManager) {\n            StaggeredGridLayoutManager manager = (StaggeredGridLayoutManager) layoutManager;\n            StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) manager.findViewByPosition(position).getLayoutParams();\n            int spanCount = manager.getSpanCount();\n            int spanIndex = params.getSpanIndex();\n\n            if (manager.getOrientation() == StaggeredGridLayoutManager.VERTICAL) // 垂直布局\n            {\n                return spanIndex == 0;\n            } else // 水平布局\n            {\n                if (manager.getReverseLayout()) {\n                    int[] lastPosition = manager.findLastVisibleItemPositions(null);\n                    boolean hasDirectionAlign = false;\n                    for (int p : lastPosition) {\n                        if (p != position && p != -1) {\n                            StaggeredGridLayoutManager.LayoutParams params1 = (StaggeredGridLayoutManager.LayoutParams) manager.findViewByPosition(p).getLayoutParams();\n                            if (params1.getSpanIndex() == spanIndex) {\n                                hasDirectionAlign = true;\n                                break;\n                            }\n                        }\n                    }\n                    return !hasDirectionAlign;\n                } else {\n                    return position < spanCount;\n                }\n            }\n        } else if (layoutManager instanceof LinearLayoutManager) {\n            return true;\n        }\n        return false;\n    }\n\n    private boolean alignRightEdge(RecyclerView parent, int position) {\n        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();\n\n        if (layoutManager instanceof GridLayoutManager) {\n            GridLayoutManager manager = (GridLayoutManager) layoutManager;\n            GridLayoutManager.SpanSizeLookup lookup = manager.getSpanSizeLookup();\n            int spanCount = manager.getSpanCount();\n            int itemCount = parent.getAdapter().getItemCount();\n            if (manager.getOrientation() == GridLayoutManager.VERTICAL) // 垂直布局\n            {\n                if (positionTotalSpanSize(manager, position) == spanCount) {\n                    return true;\n                }\n            } else // 水平布局\n            {\n                if (manager.getReverseLayout()) {\n                    return lookup.getSpanGroupIndex(position, spanCount) == 0;\n                } else {\n                    int lastRowFirstPosition = 0;\n                    for (int i = itemCount - 1; i >= 0; i--) {\n                        if (lookup.getSpanIndex(i, spanCount) == 0) {\n                            lastRowFirstPosition = i;\n                            break;\n                        }\n                    }\n                    if (position >= lastRowFirstPosition) {\n                        return true;\n                    }\n                }\n            }\n        } else if (layoutManager instanceof StaggeredGridLayoutManager) {\n            StaggeredGridLayoutManager manager = (StaggeredGridLayoutManager) layoutManager;\n            StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) manager.findViewByPosition(position).getLayoutParams();\n            int spanCount = manager.getSpanCount();\n            int spanIndex = params.getSpanIndex();\n\n            if (manager.getOrientation() == StaggeredGridLayoutManager.VERTICAL) // 垂直布局\n            {\n                return spanIndex == spanCount - 1;\n            } else // 水平布局\n            {\n                if (manager.getReverseLayout()) {\n                    return position < spanCount;\n                } else {\n                    int[] lastPosition = manager.findLastVisibleItemPositions(null);\n\n                    boolean hasRight = false;\n                    for (int p : lastPosition) {\n                        if (p != position && p != -1) {\n                            StaggeredGridLayoutManager.LayoutParams params1 = (StaggeredGridLayoutManager.LayoutParams) manager.findViewByPosition(p).getLayoutParams();\n                            if (params1.getSpanIndex() == spanIndex) {\n                                hasRight = true;\n                                break;\n                            }\n                        }\n                    }\n                    return !hasRight;\n                }\n            }\n        } else if (layoutManager instanceof LinearLayoutManager) {\n            return true;\n        }\n        return false;\n    }\n\n    @Override\n    protected void setItemOffsets(Rect outRect, int position, RecyclerView parent) {\n        if (mPositionInsideItem) {\n            outRect.set(0, 0, 0, 0);\n            return;\n        }\n        outRect.set(0, 0, 0, getDividerSize(position, parent));\n    }\n\n    private int getDividerSize(int position, RecyclerView parent) {\n        if (mPaintProvider != null) {\n            return (int) mPaintProvider.dividerPaint(position, parent).getStrokeWidth();\n        } else if (mSizeProvider != null) {\n            return mSizeProvider.dividerSize(position, parent);\n        } else if (mDrawableProvider != null) {\n            Drawable drawable = mDrawableProvider.drawableProvider(position, parent);\n            return drawable.getIntrinsicHeight();\n        } else if (mSpaceProvider != null) {\n            return mSpaceProvider.dividerSize(position, parent);\n        }\n        throw new RuntimeException(\"failed to get size\");\n    }\n\n    /**\n     * Interface for controlling divider margin\n     */\n    public interface MarginProvider {\n\n        /**\n         * Returns left margin of divider.\n         *\n         * @param position Divider position (or group index for GridLayoutManager)\n         * @param parent   RecyclerView\n         * @return left margin\n         */\n        int dividerLeftMargin(int position, RecyclerView parent);\n\n        /**\n         * Returns right margin of divider.\n         *\n         * @param position Divider position (or group index for GridLayoutManager)\n         * @param parent   RecyclerView\n         * @return right margin\n         */\n        int dividerRightMargin(int position, RecyclerView parent);\n    }\n\n    public static class Builder extends FlexibleDividerDecoration.Builder<Builder> {\n\n        private MarginProvider mMarginProvider = new MarginProvider() {\n            @Override\n            public int dividerLeftMargin(int position, RecyclerView parent) {\n                return 0;\n            }\n\n            @Override\n            public int dividerRightMargin(int position, RecyclerView parent) {\n                return 0;\n            }\n        };\n\n        public Builder(Context context) {\n            super(context);\n        }\n\n        public Builder margin(final int leftMargin, final int rightMargin) {\n            return marginProvider(new MarginProvider() {\n                @Override\n                public int dividerLeftMargin(int position, RecyclerView parent) {\n                    return leftMargin;\n                }\n\n                @Override\n                public int dividerRightMargin(int position, RecyclerView parent) {\n                    return rightMargin;\n                }\n            });\n        }\n\n        public Builder margin(int horizontalMargin) {\n            return margin(horizontalMargin, horizontalMargin);\n        }\n\n        public Builder marginResId(@DimenRes int leftMarginId, @DimenRes int rightMarginId) {\n            return margin(mResources.getDimensionPixelSize(leftMarginId),\n                    mResources.getDimensionPixelSize(rightMarginId));\n        }\n\n        public Builder marginResId(@DimenRes int horizontalMarginId) {\n            return marginResId(horizontalMarginId, horizontalMarginId);\n        }\n\n        public Builder marginProvider(MarginProvider provider) {\n            mMarginProvider = provider;\n            return this;\n        }\n\n        public HorizontalDividerItemDecoration build() {\n            checkBuilderParams();\n            return new HorizontalDividerItemDecoration(this);\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/toolpanel/decoration/VerticalDividerItemDecoration.java",
    "content": "package com.didichuxing.doraemonkit.kit.toolpanel.decoration;\n\nimport android.content.Context;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport androidx.annotation.DimenRes;\nimport androidx.core.view.ViewCompat;\nimport androidx.recyclerview.widget.GridLayoutManager;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.recyclerview.widget.StaggeredGridLayoutManager;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/30-16:09\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class VerticalDividerItemDecoration extends FlexibleDividerDecoration {\n\n    private MarginProvider mMarginProvider;\n\n    protected VerticalDividerItemDecoration(Builder builder) {\n        super(builder);\n        mMarginProvider = builder.mMarginProvider;\n    }\n\n    @Override\n    protected Rect getDividerBound(int position, RecyclerView parent, View child) {\n        Rect bounds = new Rect(0, 0, 0, 0);\n        int transitionX = (int) ViewCompat.getTranslationX(child);\n        int transitionY = (int) ViewCompat.getTranslationY(child);\n        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();\n        bounds.top = child.getTop() + transitionY;\n        bounds.bottom = child.getBottom() + transitionY;\n\n        int dividerSize = getDividerSize(position, parent);\n        if (mDividerType == DividerType.DRAWABLE || mDividerType == DividerType.SPACE) {\n            if (alignTopEdge(parent, position)) {\n                bounds.top += mMarginProvider.dividerTopMargin(position, parent);\n            }\n            if (alignBottomEdge(parent, position)) {\n                bounds.bottom -= mMarginProvider.dividerBottomMargin(position, parent);\n            }\n\n            bounds.left = child.getRight() + params.rightMargin + transitionX;\n            bounds.right = bounds.left + dividerSize;\n        } else {\n            // set center point of divider\n            int halfSize = dividerSize / 2;\n            bounds.left = child.getRight() + params.rightMargin + halfSize + transitionX;\n            bounds.right = bounds.left;\n        }\n\n        if (mPositionInsideItem) {\n            bounds.left -= dividerSize;\n            bounds.right -= dividerSize;\n        }\n\n        return bounds;\n    }\n\n    private boolean alignTopEdge(RecyclerView parent, int position) {\n        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();\n\n        if (layoutManager instanceof GridLayoutManager) {\n            GridLayoutManager manager = (GridLayoutManager) layoutManager;\n            GridLayoutManager.SpanSizeLookup lookup = manager.getSpanSizeLookup();\n            int spanCount = manager.getSpanCount();\n            if (manager.getOrientation() == GridLayoutManager.VERTICAL) // 垂直布局\n            {\n                if (manager.getReverseLayout()) {\n                    if (lookup.getSpanGroupIndex(position, spanCount) ==\n                            lookup.getSpanGroupIndex(parent.getAdapter().getItemCount() - 1, spanCount)) // 第一行\n                    {\n                        return true;\n                    }\n                } else {\n                    if (lookup.getSpanGroupIndex(position, spanCount) == 0) // 第一行\n                    {\n                        return true;\n                    }\n                }\n            } else // 水平布局\n            {\n                return lookup.getSpanIndex(position, spanCount) == 0;\n            }\n        } else if (layoutManager instanceof StaggeredGridLayoutManager) {\n            StaggeredGridLayoutManager manager = (StaggeredGridLayoutManager) layoutManager;\n            StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) manager.findViewByPosition(position).getLayoutParams();\n            int spanCount = manager.getSpanCount();\n            int spanIndex = params.getSpanIndex();\n\n            if (manager.getOrientation() == StaggeredGridLayoutManager.VERTICAL) // 垂直布局\n            {\n                if (manager.getReverseLayout()) {\n                    int[] lastPosition = manager.findLastVisibleItemPositions(null);\n\n                    boolean hasTop = false;\n                    for (int p : lastPosition) {\n                        if (p != position && p != -1) {\n                            StaggeredGridLayoutManager.LayoutParams params1 = (StaggeredGridLayoutManager.LayoutParams) manager.findViewByPosition(p).getLayoutParams();\n                            if (params1.getSpanIndex() == spanIndex) {\n                                hasTop = true;\n                                break;\n                            }\n                        }\n                    }\n                    return !hasTop;\n                } else {\n                    return position < spanCount;\n                }\n            } else // 水平布局\n            {\n                return params.getSpanIndex() == 0;\n            }\n        } else if (layoutManager instanceof LinearLayoutManager) {\n            return true;\n        }\n        return false;\n    }\n\n    private boolean alignBottomEdge(RecyclerView parent, int position) {\n        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();\n\n        if (layoutManager instanceof GridLayoutManager) {\n            GridLayoutManager manager = (GridLayoutManager) layoutManager;\n            GridLayoutManager.SpanSizeLookup lookup = manager.getSpanSizeLookup();\n            int spanCount = manager.getSpanCount();\n            int itemCount = parent.getAdapter().getItemCount();\n            if (manager.getOrientation() == GridLayoutManager.VERTICAL) // 垂直布局\n            {\n                if (manager.getReverseLayout()) {\n                    return lookup.getSpanGroupIndex(position, spanCount) == 0;\n                } else {\n                    int lastRowFirstPosition = 0;\n                    for (int i = itemCount - 1; i >= 0; i--) {\n                        if (lookup.getSpanIndex(i, spanCount) == 0) {\n                            lastRowFirstPosition = i;\n                            break;\n                        }\n                    }\n                    if (position >= lastRowFirstPosition) {\n                        return true;\n                    }\n                }\n            } else // 水平布局\n            {\n                return positionTotalSpanSize(manager, position) == spanCount;\n            }\n        } else if (layoutManager instanceof StaggeredGridLayoutManager) {\n            StaggeredGridLayoutManager manager = (StaggeredGridLayoutManager) layoutManager;\n            StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) manager.findViewByPosition(position).getLayoutParams();\n            int spanCount = manager.getSpanCount();\n            int spanIndex = params.getSpanIndex();\n\n            if (manager.getOrientation() == StaggeredGridLayoutManager.VERTICAL) // 垂直布局\n            {\n                if (manager.getReverseLayout()) {\n                    return position < spanCount;\n                } else {\n                    int[] lastPosition = manager.findLastVisibleItemPositions(null);\n\n                    boolean hasBottom = false;\n                    for (int p : lastPosition) {\n                        if (p != position && p != -1) {\n                            StaggeredGridLayoutManager.LayoutParams params1 = (StaggeredGridLayoutManager.LayoutParams) manager.findViewByPosition(p).getLayoutParams();\n                            if (params1.getSpanIndex() == spanIndex) {\n                                hasBottom = true;\n                                break;\n                            }\n                        }\n                    }\n                    return !hasBottom;\n                }\n            } else // 水平布局\n            {\n                return spanIndex == spanCount - 1;\n            }\n        } else if (layoutManager instanceof LinearLayoutManager) {\n            return true;\n        }\n        return false;\n    }\n\n    @Override\n    protected void setItemOffsets(Rect outRect, int position, RecyclerView parent) {\n        if (mPositionInsideItem) {\n            outRect.set(0, 0, 0, 0);\n            return;\n        }\n        outRect.set(0, 0, getDividerSize(position, parent), 0);\n    }\n\n    private int getDividerSize(int position, RecyclerView parent) {\n        if (mPaintProvider != null) {\n            return (int) mPaintProvider.dividerPaint(position, parent).getStrokeWidth();\n        } else if (mSizeProvider != null) {\n            return mSizeProvider.dividerSize(position, parent);\n        } else if (mDrawableProvider != null) {\n            Drawable drawable = mDrawableProvider.drawableProvider(position, parent);\n            return drawable.getIntrinsicWidth();\n        }\n        throw new RuntimeException(\"failed to get size\");\n    }\n\n    /**\n     * Interface for controlling divider margin\n     */\n    public interface MarginProvider {\n\n        /**\n         * Returns top margin of divider.\n         *\n         * @param position Divider position (or group index for GridLayoutManager)\n         * @param parent   RecyclerView\n         * @return top margin\n         */\n        int dividerTopMargin(int position, RecyclerView parent);\n\n        /**\n         * Returns bottom margin of divider.\n         *\n         * @param position Divider position (or group index for GridLayoutManager)\n         * @param parent   RecyclerView\n         * @return bottom margin\n         */\n        int dividerBottomMargin(int position, RecyclerView parent);\n    }\n\n    public static class Builder extends FlexibleDividerDecoration.Builder<Builder> {\n\n        private MarginProvider mMarginProvider = new MarginProvider() {\n            @Override\n            public int dividerTopMargin(int position, RecyclerView parent) {\n                return 0;\n            }\n\n            @Override\n            public int dividerBottomMargin(int position, RecyclerView parent) {\n                return 0;\n            }\n        };\n\n        public Builder(Context context) {\n            super(context);\n        }\n\n        public Builder margin(final int topMargin, final int bottomMargin) {\n            return marginProvider(new MarginProvider() {\n                @Override\n                public int dividerTopMargin(int position, RecyclerView parent) {\n                    return topMargin;\n                }\n\n                @Override\n                public int dividerBottomMargin(int position, RecyclerView parent) {\n                    return bottomMargin;\n                }\n            });\n        }\n\n        public Builder margin(int verticalMargin) {\n            return margin(verticalMargin, verticalMargin);\n        }\n\n        public Builder marginResId(@DimenRes int topMarginId, @DimenRes int bottomMarginId) {\n            return margin(mResources.getDimensionPixelSize(topMarginId),\n                    mResources.getDimensionPixelSize(bottomMarginId));\n        }\n\n        public Builder marginResId(@DimenRes int verticalMarginId) {\n            return marginResId(verticalMarginId, verticalMarginId);\n        }\n\n        public Builder marginProvider(MarginProvider provider) {\n            mMarginProvider = provider;\n            return this;\n        }\n\n        public VerticalDividerItemDecoration build() {\n            checkBuilderParams();\n            return new VerticalDividerItemDecoration(this);\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/uiperformance/UIPerformanceDisplayDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.uiperformance;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.FrameLayout;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.model.ViewInfo;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.kit.viewcheck.LayoutBorderView;\n\nimport java.util.List;\n\n/**\n * Created by jintai on 2019/09/26.\n */\npublic class UIPerformanceDisplayDoKitView extends AbsDoKitView implements UIPerformanceManager.PerformanceDataListener {\n    private LayoutBorderView mLayoutBorderView;\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(view.getContext()).inflate(R.layout.dk_float_ui_performance_display, view, false);\n    }\n\n    @Override\n    public void onViewCreated(FrameLayout view) {\n        mLayoutBorderView = findViewById(R.id.rect_view);\n        //设置不响应触摸事件\n        setDoKitViewNotResponseTouchEvent(getDoKitView());\n    }\n\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.flags = DoKitViewLayoutParams.FLAG_NOT_FOCUSABLE_AND_NOT_TOUCHABLE;\n        params.width = DoKitViewLayoutParams.MATCH_PARENT;\n        params.height = DoKitViewLayoutParams.MATCH_PARENT;\n    }\n\n    @Override\n    public void onCreate(Context context) {\n        UIPerformanceManager.getInstance().addListener(UIPerformanceDisplayDoKitView.this);\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        UIPerformanceManager.getInstance().removeListener(this);\n    }\n\n    @Override\n    public void onRefresh(List<ViewInfo> infos) {\n        mLayoutBorderView.showViewLayoutBorder(infos);\n    }\n\n\n    @Override\n    public boolean canDrag() {\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/uiperformance/UIPerformanceInfoDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.uiperformance;\n\nimport android.content.Context;\nimport android.text.TextUtils;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.model.ViewInfo;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.widget.textview.LabelTextView;\n\nimport java.util.List;\n\n/**\n * Created by jintai on 2019/09/26.\n */\npublic class UIPerformanceInfoDoKitView extends AbsDoKitView implements UIPerformanceManager.PerformanceDataListener {\n    private ImageView mClose;\n    private LabelTextView mMaxLevelText;\n    private LabelTextView mMaxLevelViewIdText;\n    private LabelTextView mTotalTimeText;\n    private LabelTextView mMaxTimeText;\n    private LabelTextView mMaxTimeViewIdText;\n\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(view.getContext()).inflate(R.layout.dk_float_ui_performance_info, view, false);\n    }\n\n    @Override\n    public void onViewCreated(FrameLayout view) {\n        mClose = findViewById(R.id.close);\n        mClose.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                DoKit.removeFloating(UIPerformanceDisplayDoKitView.class);\n                DoKit.removeFloating(UIPerformanceInfoDoKitView.class);\n                UIPerformanceManager.getInstance().stop();\n            }\n        });\n        mMaxLevelText = findViewById(R.id.max_level);\n        mMaxLevelViewIdText = findViewById(R.id.max_level_view_id);\n        mTotalTimeText = findViewById(R.id.total_time);\n        mMaxTimeText = findViewById(R.id.max_time);\n        mMaxTimeViewIdText = findViewById(R.id.max_time_view_id);\n\n\n    }\n\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.y = 60;\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT;\n        params.width = DoKitViewLayoutParams.WRAP_CONTENT;\n    }\n\n    @Override\n    public void onCreate(Context context) {\n        UIPerformanceManager.getInstance().addListener(UIPerformanceInfoDoKitView.this);\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        UIPerformanceManager.getInstance().removeListener(this);\n    }\n\n    @Override\n    public void onRefresh(List<ViewInfo> viewInfos) {\n        if (viewInfos == null) {\n            return;\n        }\n        int maxLevel = 0;\n        float maxTime = 0f;\n        float totalTime = 0f;\n        ViewInfo maxLevelViewInfo = null;\n        ViewInfo maxTimeViewInfo = null;\n        for (ViewInfo viewInfo : viewInfos) {\n            if (viewInfo.layerNum > maxLevel) {\n                maxLevel = viewInfo.layerNum;\n                maxLevelViewInfo = viewInfo;\n            }\n            if (viewInfo.drawTime > maxTime) {\n                maxTime = viewInfo.drawTime;\n                maxTimeViewInfo = viewInfo;\n            }\n            totalTime += viewInfo.drawTime;\n        }\n        mMaxLevelText.setText(String.valueOf(maxLevel));\n        if (maxLevelViewInfo != null && !TextUtils.isEmpty(maxLevelViewInfo.id)) {\n            mMaxLevelViewIdText.setText(maxLevelViewInfo.id);\n        }\n        mMaxTimeText.setText(maxTime + \"ms\");\n        if (maxTimeViewInfo != null && !TextUtils.isEmpty(maxTimeViewInfo.id)) {\n            mMaxTimeViewIdText.setText(maxTimeViewInfo.id);\n        }\n        mTotalTimeText.setText(totalTime + \"ms\");\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/uiperformance/UIPerformanceKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.uiperformance\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n/**\n * Created by wanglikun on 2019-06-27\n * UI渲染性能kit\n */\n@AutoService(AbstractKit::class)\nclass UIPerformanceKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_ui_performance\n    override val icon: Int\n        get() = R.mipmap.dk_ui_performance\n\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        UIPerformanceManager.getInstance().start(activity)\n        DoKit.launchFloating<UIPerformanceDisplayDoKitView>()\n        DoKit.launchFloating<UIPerformanceInfoDoKitView>()\n\n        //直接显示层级\n        UIPerformanceManager.getInstance().initRefresh()\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n\n    override fun innerKitId(): String = \"dokit_sdk_performance_ck_hierarchy\"\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/uiperformance/UIPerformanceManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.uiperformance;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.fragment.app.Fragment;\n\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.model.ViewInfo;\nimport com.didichuxing.doraemonkit.util.LifecycleListenerUtil;\nimport com.didichuxing.doraemonkit.util.LogHelper;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by wanglikun on 2019-06-27\n */\npublic class UIPerformanceManager implements LifecycleListenerUtil.LifecycleListener {\n    private static final String TAG = \"UIPerformanceManager\";\n    private Canvas mPerformanceCanvas;\n    private List<PerformanceDataListener> mListeners = new ArrayList<>();\n\n    private static class Holder {\n        private static UIPerformanceManager INSTANCE = new UIPerformanceManager();\n    }\n\n    public static UIPerformanceManager getInstance() {\n        return Holder.INSTANCE;\n    }\n\n    private UIPerformanceManager() {\n\n    }\n\n    public void start(Context context) {\n        Bitmap canvasBitmap = Bitmap.createBitmap(UIUtils.getWidthPixels(), UIUtils.getHeightPixels(), Bitmap.Config.ARGB_8888);\n        mPerformanceCanvas = new Canvas(canvasBitmap);\n        LifecycleListenerUtil.registerListener(this);\n    }\n\n    public void stop() {\n        mListeners.clear();\n        mPerformanceCanvas = null;\n        LifecycleListenerUtil.unRegisterListener(this);\n    }\n\n    public List<ViewInfo> getViewInfos(Activity activity) {\n        if (activity == null) {\n            LogHelper.d(TAG, \"resume activity is null\");\n            return new ArrayList<>();\n        }\n        if (activity.getWindow() == null) {\n            LogHelper.d(TAG, \"resume activity window is null\");\n            return new ArrayList<>();\n        }\n\n        return getViewInfos(UIUtils.getDokitAppContentView(activity));\n    }\n\n    private List<ViewInfo> getViewInfos(View view) {\n        List<ViewInfo> infos = new ArrayList<>();\n        traverseViews(view, infos, 0);\n        return infos;\n    }\n\n    private void traverseViews(View view, List<ViewInfo> infos, int layerNum) {\n        if (view == null) {\n            return;\n        }\n        layerNum++;\n        if (view instanceof ViewGroup) {\n            int childCount = ((ViewGroup) view).getChildCount();\n            if (childCount != 0) {\n                for (int index = childCount - 1; index >= 0; index--) {\n                    traverseViews(((ViewGroup) view).getChildAt(index), infos, layerNum);\n                }\n            }\n        } else {\n            ViewInfo viewInfo = new ViewInfo(view);\n            try {\n                //页面不可见不进行渲染时间的统计,统计时候为0\n                if (view.getVisibility() == View.VISIBLE) {\n                    long startTime = System.nanoTime();\n                    if (mPerformanceCanvas != null) {\n                        view.draw(mPerformanceCanvas);\n                    }\n                    long endTime = System.nanoTime();\n                    float time = (endTime - startTime) / 10_000 / 100f;\n                    //LogHelper.d(TAG, \"drawTime: \" + time + \" ms\");\n                    viewInfo.drawTime = time;\n                    viewInfo.layerNum = layerNum;\n                }\n            } catch (Exception e) {\n                //自定义View某些变量尚未初始化可能会崩溃\n                //方便区分方法走到了异常,可以判断没有成功渲染设置值为-1\n                viewInfo.drawTime = -1;\n                viewInfo.layerNum = -1;\n            }\n            infos.add(viewInfo);\n        }\n    }\n\n    public void addListener(PerformanceDataListener listener) {\n        mListeners.add(listener);\n    }\n\n    public void removeListener(PerformanceDataListener listener) {\n        mListeners.remove(listener);\n    }\n\n    @Override\n    public void onActivityResumed(Activity activity) {\n        for (PerformanceDataListener listener : mListeners) {\n            listener.onRefresh(getViewInfos(activity));\n        }\n    }\n\n    @Override\n    public void onActivityPaused(Activity activity) {\n\n    }\n\n    @Override\n    public void onFragmentAttached(Fragment f) {\n        for (PerformanceDataListener listener : mListeners) {\n            listener.onRefresh(getViewInfos(f.getActivity()));\n        }\n    }\n\n    @Override\n    public void onFragmentDetached(Fragment f) {\n        for (PerformanceDataListener listener : mListeners) {\n            listener.onRefresh(getViewInfos(f.getActivity()));\n        }\n    }\n\n    public interface PerformanceDataListener {\n        void onRefresh(List<ViewInfo> viewInfos);\n    }\n\n    /**\n     * 初始化时直接显示显示层级\n     */\n    public void initRefresh() {\n        for (PerformanceDataListener listener : mListeners) {\n            listener.onRefresh(getViewInfos(ActivityUtils.getTopActivity()));\n        }\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/uiperformance/UIPerformanceUtil.java",
    "content": "package com.didichuxing.doraemonkit.kit.uiperformance;\n\nimport android.app.Activity;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.SimpleCursorTreeAdapter;\n\nimport com.didichuxing.doraemonkit.model.ViewInfo;\nimport com.didichuxing.doraemonkit.util.LogHelper;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020-01-07-11:17\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class UIPerformanceUtil {\n    private static final String TAG = \"UIPerformanceUtil\";\n\n    public static List<ViewInfo> getViewInfos(Activity activity) {\n        if (activity == null) {\n            LogHelper.d(TAG, \"resume activity is null\");\n            return new ArrayList<>();\n        }\n        if (activity.getWindow() == null) {\n            LogHelper.d(TAG, \"resume activity window is null\");\n            return new ArrayList<>();\n        }\n\n        return getViewInfos(UIUtils.getDokitAppContentView(activity));\n    }\n\n    private static List<ViewInfo> getViewInfos(View view) {\n        List<ViewInfo> infos = new ArrayList<>();\n        traverseViews(view, infos, 0);\n        return infos;\n    }\n\n    private static void traverseViews(View view, List<ViewInfo> infos, int layerNum) {\n        if (view == null) {\n            return;\n        }\n        layerNum++;\n        if (view instanceof ViewGroup) {\n            int childCount = ((ViewGroup) view).getChildCount();\n            if (childCount != 0) {\n                for (int index = childCount - 1; index >= 0; index--) {\n                    traverseViews(((ViewGroup) view).getChildAt(index), infos, layerNum);\n                }\n            }\n        } else {\n            long startTime = System.nanoTime();\n            //view.draw(mPerformanceCanvas);\n            long endTime = System.nanoTime();\n            float time = (endTime - startTime) / 10_000 / 100f;\n            //LogHelper.d(TAG, \"drawTime: \" + time + \" ms\");\n            ViewInfo viewInfo = new ViewInfo(view);\n            viewInfo.drawTime = time;\n            viewInfo.layerNum = layerNum;\n            infos.add(viewInfo);\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/viewcheck/AimCircleView.java",
    "content": "package com.didichuxing.doraemonkit.kit.viewcheck;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.util.AttributeSet;\nimport android.view.View;\n\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.R;\n\n/**\n * Created by wanglikun on 2018/12/3.\n */\n\npublic class AimCircleView extends View {\n    private Paint mPaint;\n\n    public AimCircleView(Context context) {\n        super(context);\n        init();\n    }\n\n    public AimCircleView(Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        init();\n    }\n\n    public AimCircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init();\n    }\n\n    public void init() {\n        mPaint = new Paint();\n        mPaint.setAntiAlias(true);\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        drawCircle(canvas);\n    }\n\n    private void drawCircle(Canvas canvas) {\n        float cx = getWidth() / 2;\n        float cy = getWidth() / 2;\n        float radius = getWidth() / 2;\n\n        mPaint.setStyle(Paint.Style.FILL);\n        mPaint.setColor(getResources().getColor(R.color.dk_color_FFFFFF));\n        mPaint.setAlpha(100);\n        canvas.drawCircle(cx, cy, radius, mPaint);\n\n        radius = getResources().getDimensionPixelSize(R.dimen.dk_dp_40) / 2;\n        mPaint.setColor(getResources().getColor(R.color.dk_color_30CC3A4B));\n        mPaint.setAlpha(50);\n        canvas.drawCircle(cx, cy, radius, mPaint);\n\n        radius = getResources().getDimensionPixelSize(R.dimen.dk_dp_5) / 2;\n        mPaint.setColor(getResources().getColor(R.color.dk_color_CC3A4B));\n        canvas.drawCircle(cx, cy, radius, mPaint);\n\n        radius = getWidth() / 2;\n        mPaint.setStyle(Paint.Style.STROKE);\n        mPaint.setStrokeWidth(4);\n        mPaint.setColor(getResources().getColor(R.color.dk_color_337CC4));\n        mPaint.setAlpha(100);\n        canvas.drawCircle(cx, cy, radius - 2, mPaint);\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/viewcheck/DebugAccessibilityService.java",
    "content": "package com.didichuxing.doraemonkit.kit.viewcheck;\n\nimport android.accessibilityservice.AccessibilityService;\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.PackageManager;\nimport android.view.accessibility.AccessibilityEvent;\nimport android.view.accessibility.AccessibilityNodeInfo;\n\nimport androidx.localbroadcastmanager.content.LocalBroadcastManager;\n\nimport com.didichuxing.doraemonkit.constant.BroadcastAction;\nimport com.didichuxing.doraemonkit.constant.BundleKey;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\n/**\n * Created by wanglikun on 2018/11/20.\n */\n\npublic class DebugAccessibilityService extends AccessibilityService {\n    private static final String TAG = \"DebugAccessibilityService\";\n\n    @Override\n    protected void onServiceConnected() {\n        super.onServiceConnected();\n    }\n\n    private boolean isActivityEvent(AccessibilityEvent event) {\n        ComponentName activityComponentName = new ComponentName(\n                event.getPackageName().toString(),\n                event.getClassName().toString());\n        try {\n            ActivityInfo info = getPackageManager().getActivityInfo(activityComponentName, 0);\n            if (info != null) {\n                return true;\n            }\n        } catch (PackageManager.NameNotFoundException e) {\n            LogHelper.e(TAG, e.toString());\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public void onAccessibilityEvent(AccessibilityEvent event) {\n        CharSequence pkgName = event.getPackageName();\n        if (pkgName == null) {\n            return;\n        }\n        if (!pkgName.equals(getPackageName())) {\n            return;\n        }\n        if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {\n            return;\n        }\n        if (!isActivityEvent(event)) {\n            return;\n        }\n        AccessibilityNodeInfo info = event.getSource();\n        if (info == null) {\n            return;\n        }\n        Intent intent = new Intent(BroadcastAction.ACTION_ACCESSIBILITY_UPDATE);\n        intent.putExtra(BundleKey.ACCESSIBILITY_DATA, info);\n        LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);\n    }\n\n    @Override\n    public void onInterrupt() {\n        LogHelper.d(TAG, \"onInterrupt\");\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/viewcheck/LayoutBorderView.java",
    "content": "package com.didichuxing.doraemonkit.kit.viewcheck;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.DashPathEffect;\nimport android.graphics.Paint;\nimport android.util.AttributeSet;\nimport android.view.View;\n\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.model.ViewInfo;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by wanglikun on 2018/11/23.\n */\n\npublic class LayoutBorderView extends View {\n    private static final  String TAG1 = \"LayoutBorderView\";\n    private Paint mRectPaint;\n    private List<ViewInfo> mViewInfos = new ArrayList<>();\n\n    public LayoutBorderView(Context context) {\n        this(context, null);\n    }\n\n    public LayoutBorderView(Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public LayoutBorderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        initView(context, attrs, defStyleAttr);\n\n    }\n\n    private void initView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.LayoutBorderView);\n        boolean fill = a.getBoolean(R.styleable.LayoutBorderView_dkFill, false);\n        mRectPaint = new Paint();\n        if (fill) {\n            mRectPaint.setStyle(Paint.Style.FILL);\n            mRectPaint.setColor(Color.RED);\n        } else {\n            mRectPaint.setStyle(Paint.Style.STROKE);\n            mRectPaint.setStrokeWidth(4);\n            mRectPaint.setPathEffect(new DashPathEffect(new float[]{4, 4}, 0));\n            mRectPaint.setColor(Color.RED);\n        }\n        a.recycle();\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        for (ViewInfo viewInfo : mViewInfos) {\n            if (mRectPaint.getStyle() == Paint.Style.FILL) {\n                mRectPaint.setAlpha(viewInfo.getDrawTimeLevel() * 255);\n            }\n            canvas.drawRect(viewInfo.viewRect, mRectPaint);\n        }\n    }\n\n    public void showViewLayoutBorder(ViewInfo info) {\n        mViewInfos.clear();\n        if (info != null) {\n            mViewInfos.add(info);\n        }\n        invalidate();\n    }\n\n    public void showViewLayoutBorder(List<ViewInfo> viewInfos) {\n        if (viewInfos == null) {\n            return;\n        }\n        mViewInfos.clear();\n        mViewInfos.addAll(viewInfos);\n        invalidate();\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/viewcheck/ViewCheckDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.viewcheck;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Handler;\nimport android.os.HandlerThread;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\n\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.FrameLayout;\n\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.util.BarUtils;\nimport com.didichuxing.doraemonkit.util.LifecycleListenerUtil;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by jintai on 2019/09/26.\n */\npublic class ViewCheckDoKitView extends AbsDoKitView implements LifecycleListenerUtil.LifecycleListener {\n    private static final String TAG = \"ViewCheckFloatPage\";\n\n    private FindCheckViewRunnable mFindCheckViewRunnable;\n    private HandlerThread mTraverHandlerThread;\n    private Handler mTraverHandler;\n    private List<OnViewSelectListener> mViewSelectListeners = new ArrayList<>();\n    private Activity mResumedActivity;\n\n    @Override\n    public void onCreate(Context context) {\n        mTraverHandlerThread = new HandlerThread(TAG);\n        mTraverHandlerThread.start();\n        mTraverHandler = new Handler(mTraverHandlerThread.getLooper());\n        mFindCheckViewRunnable = new FindCheckViewRunnable();\n        mResumedActivity = ActivityUtils.getTopActivity();\n        LifecycleListenerUtil.registerListener(this);\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        mTraverHandler.removeCallbacks(mFindCheckViewRunnable);\n        mTraverHandlerThread.quit();\n        LifecycleListenerUtil.unRegisterListener(this);\n    }\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_view_check, null);\n    }\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.x = UIUtils.getWidthPixels() / 2;\n        params.y = UIUtils.getHeightPixels() / 2;\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT;\n        params.width = DoKitViewLayoutParams.WRAP_CONTENT;\n    }\n\n    @Override\n    public void onUp(int x, int y) {\n        super.onUp(x, y);\n        preformFindCheckView();\n    }\n\n    @Override\n    public void onActivityResumed(Activity activity) {\n        mResumedActivity = activity;\n        preformFindCheckView();\n    }\n\n    @Override\n    public void onViewCreated(FrameLayout view) {\n\n    }\n\n    @Override\n    public void onActivityPaused(Activity activity) {\n\n    }\n\n    @Override\n    public void onFragmentAttached(Fragment f) {\n\n    }\n\n    @Override\n    public void onFragmentDetached(Fragment f) {\n\n    }\n\n    @Override\n    public void onDown(int x, int y) {\n\n    }\n\n    void setViewSelectListener(OnViewSelectListener viewSelectListener) {\n        mViewSelectListeners.add(viewSelectListener);\n        preformFindCheckView();\n    }\n\n    void removeViewSelectListener(OnViewSelectListener viewSelectListener) {\n        mViewSelectListeners.remove(viewSelectListener);\n    }\n\n    void preformPreCheckView() {\n        mFindCheckViewRunnable.mIndex--;\n        if (mFindCheckViewRunnable.mIndex < 0) {\n            mFindCheckViewRunnable.mIndex += mFindCheckViewRunnable.mCheckViewList.size();\n        }\n        mFindCheckViewRunnable.dispatchOnViewSelected();\n    }\n\n    void preformNextCheckView() {\n        mFindCheckViewRunnable.mIndex++;\n        if (mFindCheckViewRunnable.mIndex >= mFindCheckViewRunnable.mCheckViewList.size()) {\n            mFindCheckViewRunnable.mIndex -= mFindCheckViewRunnable.mCheckViewList.size();\n        }\n        mFindCheckViewRunnable.dispatchOnViewSelected();\n    }\n\n    private void preformFindCheckView() {\n        int x, y;\n        if (isNormalMode()) {\n            x = getNormalLayoutParams().leftMargin + getDoKitView().getWidth() / 2;\n            if (BarUtils.isStatusBarVisible(getActivity())) {\n                y = getNormalLayoutParams().topMargin + getDoKitView().getHeight() / 2 + BarUtils.getStatusBarHeight();\n            } else {\n                y = getNormalLayoutParams().topMargin + getDoKitView().getHeight() / 2;\n            }\n        } else {\n            x = getSystemLayoutParams().x + getDoKitView().getWidth() / 2;\n            if (BarUtils.isStatusBarVisible(getActivity())) {\n                y = getSystemLayoutParams().y + getDoKitView().getHeight() / 2 + BarUtils.getStatusBarHeight();\n            } else {\n                y = getSystemLayoutParams().y + getDoKitView().getHeight() / 2;\n            }\n        }\n\n        mTraverHandler.removeCallbacks(mFindCheckViewRunnable);\n        mFindCheckViewRunnable.mX = x;\n        mFindCheckViewRunnable.mY = y;\n        mTraverHandler.post(mFindCheckViewRunnable);\n    }\n\n    private void traverseViews(List<View> viewList, View view, int x, int y) {\n        if (view == null) {\n            return;\n        }\n\n        int[] location = new int[2];\n        //相对window的x y\n        view.getLocationInWindow(location);\n        int left = location[0];\n        int top = location[1];\n        int right = left + view.getWidth();\n        int bottom = top + view.getHeight();\n\n        // 深度优先遍历\n        if (view instanceof ViewGroup) {\n            int childCount = ((ViewGroup) view).getChildCount();\n            if (childCount != 0) {\n                for (int index = childCount - 1; index >= 0; index--) {\n                    traverseViews(viewList, ((ViewGroup) view).getChildAt(index), x, y);\n                }\n            }\n            //noinspection DuplicateExpressions\n            if (left < x && x < right && top < y && y < bottom) {\n                viewList.add(view);\n            }\n        } else {\n            //noinspection DuplicateExpressions\n            if (left < x && x < right && top < y && y < bottom) {\n                viewList.add(view);\n            }\n        }\n    }\n\n    private void onViewSelected(View current, List<View> checkViewList) {\n        for (OnViewSelectListener listener : mViewSelectListeners) {\n            listener.onViewSelected(current, checkViewList);\n        }\n    }\n\n    interface OnViewSelectListener {\n        void onViewSelected(@Nullable View current, @NonNull List<View> checkViewList);\n    }\n\n    class FindCheckViewRunnable implements Runnable {\n\n        private int mX = 0;\n        private int mY = 0;\n        private int mIndex = 0;\n        private List<View> mCheckViewList;\n\n        @Override\n        public void run() {\n            final List<View> viewList = new ArrayList<>(20);\n            if (mResumedActivity != null && mResumedActivity.getWindow() != null) {\n                if (isNormalMode()) {\n                    //LogHelper.d(TAG, \"x: \" + mX + \", y: \" + mY);\n                    traverseViews(viewList, UIUtils.getDokitAppContentView(mResumedActivity), mX, mY);\n                } else {\n                    traverseViews(viewList, mResumedActivity.getWindow().getDecorView(), mX, mY);\n                }\n            }\n            mIndex = 0;\n            mCheckViewList = viewList;\n            dispatchOnViewSelected();\n        }\n\n        private void dispatchOnViewSelected() {\n            post(new Runnable() {\n                @Override\n                public void run() {\n                    onViewSelected(getCurrentCheckView(), mCheckViewList);\n                }\n            });\n        }\n\n        private View getCurrentCheckView() {\n            int size = mCheckViewList.size();\n            if (size == 0) {\n                return null;\n            }\n            return mCheckViewList.get(mIndex);\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/viewcheck/ViewCheckDrawDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.viewcheck;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.FrameLayout;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.model.ViewInfo;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\n\nimport java.util.List;\n\n/**\n * Created by jintai on 2019/09/26.\n * 在改布局上绘制相应的View\n */\npublic class ViewCheckDrawDoKitView extends AbsDoKitView implements ViewCheckDoKitView.OnViewSelectListener {\n    private LayoutBorderView mLayoutBorderView;\n\n    @Override\n    public void onCreate(Context context) {\n\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        ViewCheckDoKitView page = (ViewCheckDoKitView) DoKit.getDoKitView(ActivityUtils.getTopActivity(), ViewCheckDoKitView.class);\n        if (page != null) {\n            page.removeViewSelectListener(this);\n        }\n    }\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_view_check_draw, null);\n    }\n\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.flags = DoKitViewLayoutParams.FLAG_NOT_FOCUSABLE_AND_NOT_TOUCHABLE;\n        params.width = DoKitViewLayoutParams.MATCH_PARENT;\n        params.height = DoKitViewLayoutParams.MATCH_PARENT;\n    }\n\n\n    @Override\n    public void onViewCreated(FrameLayout view) {\n        mLayoutBorderView = findViewById(R.id.rect_view);\n        setDoKitViewNotResponseTouchEvent(getDoKitView());\n        postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                ViewCheckDoKitView dokitView = DoKit.getDoKitView(ActivityUtils.getTopActivity(), ViewCheckDoKitView.class);\n                if (dokitView != null) {\n                    dokitView.setViewSelectListener(ViewCheckDrawDoKitView.this);\n                }\n            }\n        }, 200);\n\n    }\n\n    @Override\n    public void onViewSelected(@Nullable View current, @NonNull List<View> checkViewList) {\n        if (current == null) {\n            mLayoutBorderView.showViewLayoutBorder((ViewInfo) null);\n        } else {\n            mLayoutBorderView.showViewLayoutBorder(new ViewInfo(current));\n        }\n    }\n\n    /**\n     * 解决ViewCheckDrawDokitView的margin被改变的bug\n     */\n    @Override\n    public void onResume() {\n        super.onResume();\n        if (getNormalLayoutParams() != null) {\n            FrameLayout.LayoutParams params = getNormalLayoutParams();\n            params.setMargins(0, 0, 0, 0);\n            params.width = FrameLayout.LayoutParams.MATCH_PARENT;\n            params.height = FrameLayout.LayoutParams.MATCH_PARENT;\n            immInvalidate();\n        }\n    }\n\n    @Override\n    public boolean canDrag() {\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/viewcheck/ViewCheckInfoDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.viewcheck;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentManager;\n\nimport android.text.TextUtils;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.util.ColorUtil;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\nimport java.util.List;\n\n/**\n * Created by jintai on 2019/09/26.\n */\npublic class ViewCheckInfoDoKitView extends AbsDoKitView implements\n        ViewCheckDoKitView.OnViewSelectListener, View.OnClickListener {\n    private TextView mName;\n    private TextView mId;\n    private TextView mPosition;\n    private TextView mDesc;\n    private TextView mActivityInfo;\n    private TextView mFragmentInfo;\n\n    private ImageView mPre;\n    private ImageView mNext;\n    private ImageView mClose;\n\n\n    @Override\n    public void onCreate(Context context) {\n\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        ViewCheckDoKitView dokitView = DoKit.getDoKitView(getActivity(), ViewCheckDoKitView.class);\n        if (dokitView != null) {\n            dokitView.removeViewSelectListener(this);\n        }\n    }\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_view_check_info, null);\n    }\n\n    @Override\n    public void onViewCreated(FrameLayout view) {\n        mId = findViewById(R.id.id);\n        mName = findViewById(R.id.name);\n        mPosition = findViewById(R.id.position);\n        mDesc = findViewById(R.id.desc);\n        mActivityInfo = findViewById(R.id.activity);\n        mFragmentInfo = findViewById(R.id.fragment);\n        mClose = findViewById(R.id.close);\n        mClose.setOnClickListener(this);\n        mPre = findViewById(R.id.pre);\n        mPre.setOnClickListener(this);\n        mNext = findViewById(R.id.next);\n        mNext.setOnClickListener(this);\n        postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                ViewCheckDoKitView dokitView = DoKit.getDoKitView(getActivity(), ViewCheckDoKitView.class);\n                if (dokitView != null) {\n                    dokitView.setViewSelectListener(ViewCheckInfoDoKitView.this);\n                }\n            }\n        }, 200);\n    }\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.flags = DoKitViewLayoutParams.FLAG_NOT_FOCUSABLE;\n        params.x = 0;\n        params.y = UIUtils.getHeightPixels() - UIUtils.dp2px(185);\n        params.width = getScreenShortSideLength();\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT;\n    }\n\n    @Override\n    public void updateViewLayout(String tag, boolean isActivityBackResume) {\n        super.updateViewLayout(tag, isActivityBackResume);\n        // 由于父类在此方法限制了高度无法自适应，所以重新设成wrap_content以自适应\n        final FrameLayout.LayoutParams params = getNormalLayoutParams();\n        params.height = ViewGroup.LayoutParams.WRAP_CONTENT;\n        getDoKitView().setLayoutParams(params);\n    }\n\n    @Override\n    public void onClick(View v) {\n        if (v == mClose) {\n            DoKit.removeFloating(ViewCheckDrawDoKitView.class);\n            DoKit.removeFloating(ViewCheckInfoDoKitView.class);\n            DoKit.removeFloating(ViewCheckDoKitView.class);\n        }\n        if (v == mNext) {\n            ViewCheckDoKitView dokitView = DoKit.getDoKitView(getActivity(), ViewCheckDoKitView.class);\n            if (dokitView != null) {\n                dokitView.preformNextCheckView();\n            }\n        }\n        if (v == mPre) {\n            ViewCheckDoKitView dokitView = DoKit.getDoKitView(getActivity(), ViewCheckDoKitView.class);\n            if (dokitView != null) {\n                dokitView.preformPreCheckView();\n            }\n        }\n    }\n\n    @Override\n    public void onViewSelected(@Nullable View current, @NonNull List<View> checkViewList) {\n\n        mNext.setVisibility(checkViewList.size() > 1 ? View.VISIBLE : View.GONE);\n        mPre.setVisibility(checkViewList.size() > 1 ? View.VISIBLE : View.GONE);\n\n        if (current == null) {\n            mName.setText(\"\");\n            mId.setText(\"\");\n            mPosition.setText(\"\");\n            mDesc.setText(\"\");\n        } else {\n            mName.setText(getResources().getString(R.string.dk_view_check_info_class, current.getClass().getCanonicalName()));\n            String idText = getResources().getString(R.string.dk_view_check_info_id, UIUtils.getIdText(current));\n            mId.setText(idText);\n            String positionText = getResources().getString(R.string.dk_view_check_info_size, current.getWidth(), current.getHeight());\n            mPosition.setText(positionText);\n            String descText = getViewExtraInfo(current);\n            if (TextUtils.isEmpty(descText)) {\n                mDesc.setVisibility(View.GONE);\n            } else {\n                mDesc.setText(descText);\n                mDesc.setVisibility(View.VISIBLE);\n            }\n            Activity activity = ActivityUtils.getTopActivity();\n            if (activity != null) {\n                String activityText = activity.getClass().getSimpleName();\n                setTextAndVisible(mActivityInfo, getResources().getString(R.string.dk_view_check_info_activity, activityText));\n                String fragmentText = getVisibleFragment(activity);\n                if (!TextUtils.isEmpty(fragmentText)) {\n                    setTextAndVisible(mFragmentInfo, getResources().getString(R.string.dk_view_check_info_fragment, fragmentText));\n                } else {\n                    setTextAndVisible(mFragmentInfo, \"\");\n                }\n            } else {\n                setTextAndVisible(mActivityInfo, \"\");\n                setTextAndVisible(mFragmentInfo, \"\");\n            }\n        }\n    }\n\n    private String getViewExtraInfo(View v) {\n        StringBuilder info = new StringBuilder();\n        // 背景色\n        Drawable drawable = v.getBackground();\n        if (drawable != null) {\n            if (drawable instanceof ColorDrawable) {\n                int colorInt = ((ColorDrawable) drawable).getColor();\n                String backgroundColor = ColorUtil.parseColorInt(colorInt);\n                info.append(getResources().getString(R.string.dk_view_check_info_desc, backgroundColor));\n                info.append(\"\\n\");\n            }\n        }\n        // padding\n        if (v.getPaddingLeft() != 0 && v.getPaddingTop() != 0 && v.getPaddingRight() != 0 && v.getPaddingBottom() != 0) {\n            info.append(getResources().getString(R.string.dk_view_check_info_padding, v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), v.getPaddingBottom()));\n            info.append(\"\\n\");\n        }\n        // margin\n        final ViewGroup.LayoutParams layoutParams = v.getLayoutParams();\n        if (layoutParams instanceof ViewGroup.MarginLayoutParams) {\n            final ViewGroup.MarginLayoutParams mp = ((ViewGroup.MarginLayoutParams) layoutParams);\n            if (mp.leftMargin != 0 && mp.topMargin != 0 && mp.rightMargin != 0 && mp.bottomMargin != 0) {\n                info.append(getResources().getString(R.string.dk_view_check_info_margin, mp.leftMargin, mp.topMargin, mp.rightMargin, mp.bottomMargin));\n                info.append(\"\\n\");\n            }\n        }\n        // TextView信息\n        if (v instanceof TextView) {\n            TextView tv = ((TextView) v);\n            String textColor = ColorUtil.parseColorInt(tv.getCurrentTextColor());\n            info.append(getResources().getString(R.string.dk_view_check_info_text_color, textColor));\n            info.append(\"\\n\");\n            info.append(getResources().getString(R.string.dk_view_check_info_text_size, (int) tv.getTextSize()));\n            info.append(\"\\n\");\n\n        }\n        // 删除最后一个换行\n        if (!TextUtils.isEmpty(info)) {\n            info.deleteCharAt(info.length() - 1);\n        }\n        return info.toString();\n    }\n\n    private void setTextAndVisible(TextView textView, String text) {\n        if (TextUtils.isEmpty(text)) {\n            textView.setVisibility(View.GONE);\n            textView.setText(\"\");\n        } else {\n            textView.setVisibility(View.VISIBLE);\n            textView.setText(text);\n        }\n    }\n\n    private String getVisibleFragment(Activity activity) {\n        if (activity == null) {\n            return null;\n        }\n        StringBuilder builder = new StringBuilder();\n        if (activity instanceof AppCompatActivity) {\n            AppCompatActivity compatActivity = (AppCompatActivity) activity;\n            FragmentManager fragmentManager = compatActivity.getSupportFragmentManager();\n            List<Fragment> fragments = fragmentManager.getFragments();\n            if (fragments != null && fragments.size() != 0) {\n                for (int i = 0; i < fragments.size(); i++) {\n                    Fragment fragment = fragments.get(i);\n                    if (fragment != null && fragment.isVisible()) {\n                        builder.append(fragment.getClass().getSimpleName() + \"#\" + fragment.getId());\n                        if (i < fragments.size() - 1) {\n                            builder.append(\";\");\n                        }\n                    }\n                }\n                return builder.toString();\n            } else {\n                return getFragmentForActivity(activity);\n            }\n        } else {\n            return getFragmentForActivity(activity);\n        }\n    }\n\n    private String getFragmentForActivity(Activity activity) {\n        StringBuilder builder = new StringBuilder();\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {\n            android.app.FragmentManager manager = activity.getFragmentManager();\n            List<android.app.Fragment> list = manager.getFragments();\n            if (list != null && list.size() > 0) {\n                for (int i = 0; i < list.size(); i++) {\n                    android.app.Fragment fragment = list.get(i);\n                    if (fragment != null && fragment.isVisible()) {\n                        builder.append(fragment.getClass().getSimpleName() + \"#\" + fragment.getId());\n                        if (i < list.size() - 1) {\n                            builder.append(\";\");\n                        }\n                    }\n                }\n            }\n        }\n        return builder.toString();\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/viewcheck/ViewCheckerKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.viewcheck\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n/**\n * Created by wanglikun on 2018/11/20.\n */\n@AutoService(AbstractKit::class)\nclass ViewCheckerKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_view_check\n    override val icon: Int\n        get() = R.mipmap.dk_view_check\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        DoKit.launchFloating<ViewCheckDoKitView>()\n        DoKit.launchFloating<ViewCheckDrawDoKitView>()\n        DoKit.launchFloating<ViewCheckInfoDoKitView>()\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String = \"dokit_sdk_ui_ck_widget\"\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/weaknetwork/NetWokDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.weaknetwork;\n\nimport android.content.Context;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/18-15:10\n * 描    述：弱网提示悬浮窗\n * 修订历史：\n * ================================================\n */\npublic class NetWokDoKitView extends AbsDoKitView {\n    TextView mTvNetWork;\n    TextView mTvTimeOutTime;\n    TextView mTvRequestSpeed;\n    TextView mTvResponseSpeed;\n    LinearLayout mLlTimeWrap;\n    LinearLayout mLlSpeedWrap;\n    ImageView mIvClose;\n\n    @Override\n\n    public void onCreate(Context context) {\n\n    }\n\n    @Override\n    public View onCreateView(Context context, FrameLayout rootView) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_network, rootView, false);\n    }\n\n    @Override\n    public void onViewCreated(FrameLayout rootView) {\n        mTvNetWork = rootView.findViewById(R.id.tv_net_type);\n        mTvTimeOutTime = rootView.findViewById(R.id.tv_time);\n        mTvRequestSpeed = rootView.findViewById(R.id.tv_request_speed);\n        mTvResponseSpeed = rootView.findViewById(R.id.tv_response_speed);\n        mLlTimeWrap = rootView.findViewById(R.id.ll_timeout_wrap);\n        mLlSpeedWrap = rootView.findViewById(R.id.ll_speed_wrap);\n        mIvClose = rootView.findViewById(R.id.iv_close);\n        mIvClose.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                WeakNetworkManager.get().setActive(false);\n                DoKit.removeFloating(NetWokDoKitView.class);\n            }\n        });\n    }\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.width = DoKitViewLayoutParams.WRAP_CONTENT;\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT;\n        params.gravity = Gravity.TOP | Gravity.LEFT;\n        params.x = 100;\n        params.y = 100;\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        try {\n            if (mTvNetWork == null) {\n                return;\n            }\n            final int type = WeakNetworkManager.get().getType();\n            switch (type) {\n                case WeakNetworkManager.TYPE_TIMEOUT:\n                    mTvNetWork.setText(DoKitCommUtil.getString(R.string.dk_weaknet_type_timeout));\n                    mTvTimeOutTime.setText(\"\" + WeakNetworkManager.get().getTimeOutMillis() + \" ms\");\n                    mLlTimeWrap.setVisibility(View.VISIBLE);\n                    mLlSpeedWrap.setVisibility(View.GONE);\n\n                    break;\n                case WeakNetworkManager.TYPE_SPEED_LIMIT:\n                    mTvNetWork.setText(DoKitCommUtil.getString(R.string.dk_weaknet_type_speed));\n                    mTvRequestSpeed.setText(\"\" + WeakNetworkManager.get().getRequestSpeed() + \" KB/S\");\n                    mTvResponseSpeed.setText(\"\" + WeakNetworkManager.get().getResponseSpeed() + \" KB/S\");\n                    mLlTimeWrap.setVisibility(View.GONE);\n                    mLlSpeedWrap.setVisibility(View.VISIBLE);\n                    break;\n                default:\n                    mTvNetWork.setText(DoKitCommUtil.getString(R.string.dk_weaknet_type_off));\n                    mLlTimeWrap.setVisibility(View.GONE);\n                    mLlSpeedWrap.setVisibility(View.GONE);\n                    break;\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        immInvalidate();\n    }\n\n\n    @Override\n    public void immInvalidate() {\n        if (getDoKitView() == null) {\n            return;\n        }\n        if (isNormalMode()) {\n            FrameLayout.LayoutParams layoutParams = getNormalLayoutParams();\n            if (layoutParams == null) {\n                return;\n            }\n            layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;\n            layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;\n            getDoKitView().setLayoutParams(layoutParams);\n        } else {\n            WindowManager.LayoutParams layoutParams = getSystemLayoutParams();\n            if (layoutParams == null) {\n                return;\n            }\n            layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;\n            layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;\n            mWindowManager.updateViewLayout(getDoKitView(), layoutParams);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/weaknetwork/SpeedLimitRequestBody.java",
    "content": "package com.didichuxing.doraemonkit.kit.weaknetwork;\n\nimport android.os.SystemClock;\n\nimport com.didichuxing.doraemonkit.okhttp_api.OkHttpWrap;\n\nimport java.io.IOException;\n\nimport okhttp3.MediaType;\nimport okhttp3.RequestBody;\nimport okio.Buffer;\nimport okio.BufferedSink;\nimport okio.ForwardingSink;\nimport okio.Sink;\n\n/**\n * Created by xiandanin on 2019-05-09 18:35\n */\npublic class SpeedLimitRequestBody extends RequestBody {\n    private long mSpeedByte;//b/s\n    private RequestBody mRequestBody;\n    private BufferedSink mBufferedSink;\n\n    public SpeedLimitRequestBody(long speed, RequestBody source) {\n        this.mRequestBody = source;\n        this.mSpeedByte = speed * 1024;//转成字节\n    }\n\n    @Override\n    public MediaType contentType() {\n        return mRequestBody.contentType();\n    }\n\n    @Override\n    public long contentLength() throws IOException {\n        return mRequestBody.contentLength();\n    }\n\n    @Override\n    public void writeTo(BufferedSink sink) throws IOException {\n        if (mBufferedSink == null) {\n            //mBufferedSink = Okio.buffer(sink(sink));\n            //默认8K 精确到1K\n            mBufferedSink = OkHttpWrap.INSTANCE.createByteCountBufferedSink(sink(sink), 1024L);\n        }\n        mRequestBody.writeTo(mBufferedSink);\n        mBufferedSink.close();\n    }\n\n    private Sink sink(final BufferedSink sink) {\n        return new ForwardingSink(sink) {\n            private long cacheTotalBytesWritten;\n            private long cacheStartTime;\n\n            @Override\n            public void write(Buffer source, long byteCount) throws IOException {\n                if (cacheStartTime == 0) {\n                    cacheStartTime = SystemClock.uptimeMillis();\n                }\n\n                super.write(source, byteCount);\n                cacheTotalBytesWritten += byteCount;\n\n                long endTime = SystemClock.uptimeMillis() - cacheStartTime;\n                //如果在一秒内\n                if (endTime <= 1000L) {\n                    //大小就超出了限制\n                    if (cacheTotalBytesWritten >= mSpeedByte) {\n                        long sleep = 1000L - endTime;\n                        SystemClock.sleep(sleep);\n\n                        //重置计算\n                        cacheStartTime = 0L;\n                        cacheTotalBytesWritten = 0L;\n                    }\n                }\n            }\n        };\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/weaknetwork/SpeedLimitResponseBody.java",
    "content": "package com.didichuxing.doraemonkit.kit.weaknetwork;\n\nimport android.os.SystemClock;\n\nimport java.io.IOException;\n\nimport okhttp3.MediaType;\nimport okhttp3.ResponseBody;\nimport okio.Buffer;\nimport okio.BufferedSource;\nimport okio.ForwardingSource;\nimport okio.Okio;\nimport okio.Source;\n\n/**\n * Created by xiandanin on 2019-05-09 18:35\n */\npublic class SpeedLimitResponseBody extends ResponseBody {\n    private static String TAG = \"SpeedLimitResponseBody\";\n    /**\n     * 限速字节\n     */\n    private long mSpeedByte;\n    private ResponseBody mResponseBody;\n    private BufferedSource mBufferedSource;\n\n    SpeedLimitResponseBody(long speed, ResponseBody source) {\n        this.mResponseBody = source;\n        //转成字节\n        this.mSpeedByte = speed * 1024L;\n    }\n\n    @Override\n    public MediaType contentType() {\n        return mResponseBody.contentType();\n    }\n\n    @Override\n    public long contentLength() {\n        return mResponseBody.contentLength();\n    }\n\n    @Override\n    public BufferedSource source() {\n        if (mBufferedSource == null) {\n            mBufferedSource = Okio.buffer(source(mResponseBody.source()));\n        }\n        return mBufferedSource;\n    }\n\n    private Source source(Source source) {\n        return new ForwardingSource(source) {\n            /**\n             * 如果小于1s 会重置\n             */\n            private long cacheTotalBytesRead;\n            /**\n             * 分片读取1024个字节开始时间 小于1s会重置\n             */\n            private long cacheStartTime;\n\n            @Override\n            public long read(Buffer sink, long byteCount) throws IOException {\n                if (cacheStartTime == 0) {\n                    cacheStartTime = SystemClock.uptimeMillis();\n                }\n\n                //默认8K 精确到1K -1代表已经读取完毕\n                long bytesRead = super.read(sink.buffer(), 1024L);\n                if (bytesRead == -1) {\n                    return bytesRead;\n                }\n                //一般为1024\n                cacheTotalBytesRead = cacheTotalBytesRead + bytesRead;\n\n                /**\n                 * 判断当前请求累计消耗的时间 即相当于读取1024个字节所需要的时间\n                 */\n                long costTime = SystemClock.uptimeMillis() - cacheStartTime;\n\n                //如果每次分片读取时间小于ls sleep 延迟时间\n                if (costTime <= 1000L) {\n                    if (cacheTotalBytesRead >= mSpeedByte) {\n                        long sleep = 1000L - costTime;\n                        SystemClock.sleep(sleep);\n                        //重置计算\n                        cacheStartTime = 0L;\n                        cacheTotalBytesRead = 0L;\n                    }\n                }\n\n                return bytesRead;\n            }\n        };\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/weaknetwork/WeakNetworkFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.weaknetwork;\n\nimport android.os.Bundle;\nimport android.text.Editable;\nimport android.text.TextUtils;\nimport android.text.TextWatcher;\nimport android.view.View;\nimport android.widget.EditText;\nimport android.widget.RadioButton;\nimport android.widget.RadioGroup;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.kit.core.SettingItem;\nimport com.didichuxing.doraemonkit.kit.core.SettingItemAdapter;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\n\n/**\n * 模拟弱网\n * <p>\n * Created by xiandanin on 2019/5/7 19:10\n */\npublic class WeakNetworkFragment extends BaseFragment implements TextWatcher {\n    private SettingItemAdapter mSettingItemAdapter;\n    private RecyclerView mSettingList;\n    private View mWeakNetworkOptionView;\n    private View mTimeoutOptionView;\n    private View mSpeedLimitView;\n    private EditText mTimeoutValueView, mRequestSpeedView, mResponseSpeedView;\n    private AbsDoKitView mNetWorkDokitView;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_weak_network;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n    }\n\n    private void initView() {\n        HomeTitleBar homeTitleBar = findViewById(R.id.title_bar);\n        homeTitleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                getActivity().finish();\n            }\n        });\n        mWeakNetworkOptionView = findViewById(R.id.weak_network_layout);\n        mSettingList = findViewById(R.id.setting_list);\n        mSettingList.setLayoutManager(new LinearLayoutManager(getContext()));\n        mSettingItemAdapter = new SettingItemAdapter(getContext());\n        mSettingList.setAdapter(mSettingItemAdapter);\n        mSettingItemAdapter.append(new SettingItem(R.string.dk_weak_network_switch, WeakNetworkManager.get().isActive()));\n        mSettingItemAdapter.setOnSettingItemSwitchListener(new SettingItemAdapter.OnSettingItemSwitchListener() {\n            @Override\n            public void onSettingItemSwitch(View view, SettingItem data, boolean on) {\n                if (data.desc == R.string.dk_weak_network_switch) {\n                    setWeakNetworkEnabled(data.isChecked);\n                }\n            }\n        });\n        RadioGroup optionGroup = findViewById(R.id.weak_network_option);\n        optionGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {\n            @Override\n            public void onCheckedChanged(RadioGroup group, int checkedId) {\n\n                if (R.id.timeout == checkedId) {\n                    //超时\n                    showTimeoutOptionView();\n                } else if (R.id.speed_limit == checkedId) {\n                    //限速\n                    showSpeedLimitOptionView();\n                } else {\n                    //断网\n                    showOffNetworkOptionView();\n                }\n\n                if (mNetWorkDokitView == null) {\n                    mNetWorkDokitView = DoKit.getDoKitView(getActivity(), NetWokDoKitView.class);\n                }\n                if (mNetWorkDokitView != null) {\n                    //重新调用刷新\n                    mNetWorkDokitView.onResume();\n                }\n\n            }\n        });\n        mTimeoutOptionView = findViewById(R.id.layout_timeout_option);\n        mSpeedLimitView = findViewById(R.id.layout_speed_limit);\n\n        mTimeoutValueView = findViewById(R.id.value_timeout);\n        mTimeoutValueView.addTextChangedListener(this);\n        mRequestSpeedView = findViewById(R.id.request_speed);\n        mRequestSpeedView.addTextChangedListener(this);\n        mResponseSpeedView = findViewById(R.id.response_speed);\n        mResponseSpeedView.addTextChangedListener(this);\n\n        updateUIState();\n    }\n\n    private void updateUIState() {\n        final boolean active = WeakNetworkManager.get().isActive();\n        mWeakNetworkOptionView.setVisibility(active ? View.VISIBLE : View.GONE);\n        if (active) {\n            int checkButtonId;\n            final int type = WeakNetworkManager.get().getType();\n            switch (type) {\n                case WeakNetworkManager.TYPE_TIMEOUT:\n                    checkButtonId = R.id.timeout;\n                    break;\n                case WeakNetworkManager.TYPE_SPEED_LIMIT:\n                    checkButtonId = R.id.speed_limit;\n                    break;\n                default:\n                    checkButtonId = R.id.off_network;\n                    break;\n            }\n            RadioButton defaultOptionView = findViewById(checkButtonId);\n            defaultOptionView.setChecked(true);\n\n            mTimeoutValueView.setHint(String.valueOf(WeakNetworkManager.get().getTimeOutMillis()));\n            mRequestSpeedView.setHint(String.valueOf(WeakNetworkManager.get().getRequestSpeed()));\n            mResponseSpeedView.setHint(String.valueOf(WeakNetworkManager.get().getResponseSpeed()));\n        }\n    }\n\n    private void setWeakNetworkEnabled(boolean enabled) {\n        WeakNetworkManager.get().setActive(enabled);\n        updateUIState();\n        if (enabled) {\n            DoKit.launchFloating(NetWokDoKitView.class);\n        } else {\n            DoKit.removeFloating(NetWokDoKitView.class);\n        }\n    }\n\n    private void showTimeoutOptionView() {\n        mTimeoutOptionView.setVisibility(View.VISIBLE);\n        mSpeedLimitView.setVisibility(View.GONE);\n\n        WeakNetworkManager.get().setType(WeakNetworkManager.TYPE_TIMEOUT);\n    }\n\n    private void showSpeedLimitOptionView() {\n        mSpeedLimitView.setVisibility(View.VISIBLE);\n        mTimeoutOptionView.setVisibility(View.GONE);\n\n        WeakNetworkManager.get().setType(WeakNetworkManager.TYPE_SPEED_LIMIT);\n    }\n\n    private void showOffNetworkOptionView() {\n        mSpeedLimitView.setVisibility(View.GONE);\n        mTimeoutOptionView.setVisibility(View.GONE);\n\n        WeakNetworkManager.get().setType(WeakNetworkManager.TYPE_OFF_NETWORK);\n    }\n\n    private long getLongValue(EditText editText) {\n        CharSequence text = editText.getText();\n        if (TextUtils.isEmpty(text)) {\n            return 0L;\n        }\n        return Long.parseLong(text.toString());\n    }\n\n    @Override\n    public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n\n    }\n\n    @Override\n    public void onTextChanged(CharSequence s, int start, int before, int count) {\n        long timeOutMillis = getLongValue(mTimeoutValueView);\n        long requestSpeed = getLongValue(mRequestSpeedView);\n        long responseSpeed = getLongValue(mResponseSpeedView);\n        WeakNetworkManager.get().setParameter(timeOutMillis, requestSpeed, responseSpeed);\n    }\n\n    @Override\n    public void afterTextChanged(Editable s) {\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/weaknetwork/WeakNetworkKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.weaknetwork\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.google.auto.service.AutoService\n\n/**\n * 模拟弱网\n *\n *\n * Created by xiandanin on 2019/5/7 19:05\n */\n@AutoService(AbstractKit::class)\nclass WeakNetworkKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_weak_network\n    override val icon: Int\n        get() = R.mipmap.dk_weak_network\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip))\n            return false\n        }\n        if (!DokitPluginConfig.SWITCH_NETWORK) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_network_close_tip))\n            return false\n        }\n        startUniversalActivity(WeakNetworkFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_comm_ck_weaknetwork\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/weaknetwork/WeakNetworkManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.weaknetwork;\n\nimport android.os.SystemClock;\n\nimport java.io.IOException;\nimport java.net.SocketTimeoutException;\nimport java.net.UnknownHostException;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport okhttp3.Interceptor;\nimport okhttp3.MediaType;\nimport okhttp3.Protocol;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport okhttp3.Response;\nimport okhttp3.ResponseBody;\nimport okhttp3.internal.http.RealResponseBody;\nimport okio.Okio;\nimport okio.Source;\n\n/**\n * Created by xiandanin on 2019-05-09 16:30\n */\npublic class WeakNetworkManager {\n    public static final int TYPE_OFF_NETWORK = 0;\n    public static final int TYPE_TIMEOUT = 1;\n    public static final int TYPE_SPEED_LIMIT = 2;\n\n    public static final int DEFAULT_TIMEOUT_MILLIS = 2000;\n    public static final int DEFAULT_REQUEST_SPEED = 1;\n    public static final int DEFAULT_RESPONSE_SPEED = 1;\n\n    private int mType = TYPE_OFF_NETWORK;\n    private long mTimeOutMillis = DEFAULT_TIMEOUT_MILLIS;\n    private long mRequestSpeed = DEFAULT_REQUEST_SPEED;\n    private long mResponseSpeed = DEFAULT_RESPONSE_SPEED;\n\n    private AtomicBoolean mIsActive = new AtomicBoolean(false);\n\n    private static class Holder {\n        private static WeakNetworkManager INSTANCE = new WeakNetworkManager();\n    }\n\n    public static WeakNetworkManager get() {\n        return WeakNetworkManager.Holder.INSTANCE;\n    }\n\n    public boolean isActive() {\n        return mIsActive.get();\n    }\n\n    public void setActive(boolean isActive) {\n        mIsActive.set(isActive);\n    }\n\n\n    public void setParameter(long timeOutMillis, long requestSpeed, long responseSpeed) {\n        if (timeOutMillis > 0) {\n            mTimeOutMillis = timeOutMillis;\n        }\n        mRequestSpeed = requestSpeed;\n        mResponseSpeed = responseSpeed;\n    }\n\n    public void setType(int type) {\n        mType = type;\n    }\n\n    public int getType() {\n        return mType;\n    }\n\n    public long getTimeOutMillis() {\n        return mTimeOutMillis;\n    }\n\n    public long getRequestSpeed() {\n        return mRequestSpeed;\n    }\n\n    public long getResponseSpeed() {\n        return mResponseSpeed;\n    }\n\n    /**\n     * 模拟断网\n     */\n    public Response simulateOffNetwork(Interceptor.Chain chain) throws IOException {\n        final Response response = chain.proceed(chain.request());\n        ResponseBody responseBody = ResponseBody.create(response.body().contentType(), \"\");\n        Response newResponse = response.newBuilder()\n                .code(400)\n                .message(String.format(\"Unable to resolve host %s: No address associated with hostname\", chain.request().url().host()))\n                .body(responseBody)\n                .build();\n        return newResponse;\n    }\n\n    /**\n     * 模拟超时\n     *\n     * @param chain url\n     */\n    public Response simulateTimeOut(Interceptor.Chain chain) throws IOException {\n        SystemClock.sleep(mTimeOutMillis);\n        final Response response = chain.proceed(chain.request());\n        ResponseBody responseBody = ResponseBody.create(response.body().contentType(), \"\");\n        Response newResponse = response.newBuilder()\n                .code(400)\n                .message(String.format(\"failed to connect to %s  after %dms\", chain.request().url().host(), mTimeOutMillis))\n                .body(responseBody)\n                .build();\n        return newResponse;\n    }\n\n    /**\n     * 限速\n     */\n    public Response simulateSpeedLimit(Interceptor.Chain chain) throws IOException {\n        Request request = chain.request();\n        final RequestBody body = request.body();\n        if (body != null) {\n            //大于0使用限速的body 否则使用原始body\n            final RequestBody requestBody = mRequestSpeed > 0 ? new SpeedLimitRequestBody(mRequestSpeed, body) : body;\n            request = request.newBuilder().method(request.method(), requestBody).build();\n        }\n        final Response response = chain.proceed(request);\n        //大于0使用限速的body 否则使用原始body\n        final ResponseBody responseBody = response.body();\n        final ResponseBody newResponseBody = mResponseSpeed > 0 ? new SpeedLimitResponseBody(mResponseSpeed, responseBody) : responseBody;\n        return response.newBuilder().body(newResponseBody).build();\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/webdoor/WebDoorDefaultFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.webdoor;\n\nimport android.os.Bundle;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport android.view.View;\nimport android.webkit.WebView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.constant.BundleKey;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.kit.webview.WebViewManager;\n\n/**\n * Created by wanglikun on 2019/4/4\n */\npublic class WebDoorDefaultFragment extends BaseFragment {\n\n    //private String mUrl;\n\n    private WebView mWebView;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_web_door_default;\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        //mUrl = getArguments() == null ? null : getArguments().getString(BundleKey.KEY_URL);\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        mWebView = findViewById(R.id.webview);\n        if (WebViewManager.INSTANCE.getUrl() != null && !WebViewManager.INSTANCE.getUrl().isEmpty()) {\n            mWebView.loadUrl(WebViewManager.INSTANCE.getUrl());\n        }\n\n    }\n\n    @Override\n    public boolean onBackPressed() {\n        if (mWebView.canGoBack()) {\n            mWebView.goBack();\n            return true;\n        } else {\n            return super.onBackPressed();\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/webdoor/WebDoorFragment.java",
    "content": "package com.didichuxing.doraemonkit.kit.webdoor;\n\nimport android.Manifest;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.os.Bundle;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.app.ActivityCompat;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.text.Editable;\nimport android.text.TextUtils;\nimport android.text.TextWatcher;\nimport android.view.View;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.widget.recyclerview.DividerItemDecoration;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\nimport com.didichuxing.doraemonkit.zxing.activity.CaptureActivity;\n\nimport java.util.List;\n\nimport static android.app.Activity.RESULT_OK;\n\n/**\n * Created by wanglikun on 2018/10/10.\n */\n\npublic class WebDoorFragment extends BaseFragment {\n    private EditText mWebAddressInput;\n    private TextView mUrlExplore;\n    private RecyclerView mHistoryList;\n    private WebDoorHistoryAdapter mWebDoorHistoryAdapter;\n    private static final int REQUEST_CAMERA = 2;\n    private static final int REQUEST_QR_CODE = 3;\n    private static final String[] PERMISSIONS_CAMERA = {\n            Manifest.permission.CAMERA};\n\n\n    @Override\n\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_web_door;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        HomeTitleBar titleBar = findViewById(R.id.title_bar);\n        titleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                finish();\n            }\n        });\n        mWebAddressInput = findViewById(R.id.web_address_input);\n        mWebAddressInput.addTextChangedListener(new TextWatcher() {\n            @Override\n            public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n\n            }\n\n            @Override\n            public void onTextChanged(CharSequence s, int start, int before, int count) {\n                if (checkInput()) {\n                    mUrlExplore.setEnabled(true);\n                } else {\n                    mUrlExplore.setEnabled(false);\n                }\n            }\n\n            @Override\n            public void afterTextChanged(Editable s) {\n\n            }\n        });\n        mUrlExplore = findViewById(R.id.url_explore);\n        findViewById(R.id.clear).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                WebDoorManager.getInstance().clearHistory();\n                mWebDoorHistoryAdapter.clear();\n            }\n        });\n        findViewById(R.id.qr_code).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                qrCode();\n            }\n        });\n        mUrlExplore.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                doSearch(mWebAddressInput.getText().toString());\n            }\n        });\n        mHistoryList = findViewById(R.id.history_list);\n        mHistoryList.setNestedScrollingEnabled(false);\n        LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());\n        mHistoryList.setLayoutManager(layoutManager);\n        List<String> historyItems = WebDoorManager.getInstance().getHistory();\n\n        mWebDoorHistoryAdapter = new WebDoorHistoryAdapter(getContext());\n        mWebDoorHistoryAdapter.setData(historyItems);\n        mWebDoorHistoryAdapter.setOnItemClickListener(new WebDoorHistoryAdapter.OnItemClickListener() {\n            @Override\n            public void onItemClick(View view, String data) {\n                doSearch(data);\n            }\n        });\n        mHistoryList.setAdapter(mWebDoorHistoryAdapter);\n        DividerItemDecoration decoration = new DividerItemDecoration(DividerItemDecoration.VERTICAL);\n        decoration.setDrawable(getResources().getDrawable(R.drawable.dk_divider));\n        mHistoryList.addItemDecoration(decoration);\n    }\n\n    private void doSearch(String url) {\n        WebDoorManager.getInstance().saveHistory(url);\n        WebDoorManager.getInstance().getWebDoorCallback().overrideUrlLoading(getContext(), url);\n        mWebDoorHistoryAdapter.setData(WebDoorManager.getInstance().getHistory());\n    }\n\n    private boolean checkInput() {\n        return !TextUtils.isEmpty(mWebAddressInput.getText());\n    }\n\n    @Override\n    public void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        if (resultCode == RESULT_OK && requestCode == REQUEST_QR_CODE) {\n            Bundle bundle = data.getExtras();\n            String result = bundle.getString(CaptureActivity.INTENT_EXTRA_KEY_QR_SCAN);\n            if (!TextUtils.isEmpty(result)) {\n                doSearch(result);\n            }\n        }\n    }\n\n    private void qrCode() {\n        if (!ownPermissionCheck()) {\n            requestPermissions(PERMISSIONS_CAMERA, REQUEST_CAMERA);\n            return;\n        }\n        Intent intent = new Intent(getActivity(), CaptureActivity.class);\n        startActivityForResult(intent, REQUEST_QR_CODE);\n    }\n\n    private boolean ownPermissionCheck() {\n        int permission = ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA);\n        if (permission != PackageManager.PERMISSION_GRANTED) {\n            return false;\n        }\n        return true;\n    }\n\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {\n        if (requestCode == REQUEST_CAMERA) {\n            for (int grantResult : grantResults) {\n                if (grantResult == -1) {\n                    ToastUtils.showShort(R.string.dk_error_tips_permissions_less);\n                }\n            }\n        }\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/webdoor/WebDoorHistoryAdapter.java",
    "content": "package com.didichuxing.doraemonkit.kit.webdoor;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\n\n/**\n * Created by wanglikun on 2018/11/17.\n */\n\npublic class WebDoorHistoryAdapter extends AbsRecyclerAdapter<AbsViewBinder<String>, String> {\n    private OnItemClickListener mOnItemClickListener;\n\n    public WebDoorHistoryAdapter(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected AbsViewBinder<String> createViewHolder(View view, int viewType) {\n        return new WebDoorHistoryViewHolder(view);\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        return inflater.inflate(R.layout.dk_item_web_door_history, parent, false);\n    }\n\n    public class WebDoorHistoryViewHolder extends AbsViewBinder<String> {\n        private TextView mContent;\n\n        public WebDoorHistoryViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            mContent = getView(R.id.content);\n        }\n\n        @Override\n        public void bind(String s) {\n            mContent.setText(s);\n        }\n\n        @Override\n        protected void onViewClick(View view, String data) {\n            super.onViewClick(view, data);\n            if (mOnItemClickListener != null) {\n                mOnItemClickListener.onItemClick(view, data);\n            }\n        }\n    }\n\n    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {\n        mOnItemClickListener = onItemClickListener;\n    }\n\n    public interface OnItemClickListener {\n        void onItemClick(View view, String data);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/webdoor/WebDoorKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.webdoor\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n/**\n * Created by wanglikun on 2018/10/10.\n */\n@AutoService(AbstractKit::class)\nclass WebDoorKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_web_door\n    override val icon: Int\n        get() = R.mipmap.dk_web_door\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        startUniversalActivity(WebDoorFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_comm_ck_h5\"\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/webdoor/WebDoorManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.webdoor;\n\nimport android.content.Context;\n\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.constant.CachesKey;\nimport com.didichuxing.doraemonkit.kit.webview.WebViewManager;\nimport com.didichuxing.doraemonkit.util.DoKitCacheUtils;\n\nimport java.util.ArrayList;\n\n/**\n * Created by wanglikun on 2018/10/10.\n */\n\npublic class WebDoorManager {\n    private static final String TAG = \"WebDoorManager\";\n    private WebDoorCallback mWebDoorCallback = new DefaultWebDoorCallback();\n    private ArrayList<String> mHistory;\n\n    public WebDoorCallback getWebDoorCallback() {\n        return mWebDoorCallback;\n    }\n\n    public void setWebDoorCallback(WebDoorCallback webDoorCallback) {\n        mWebDoorCallback = webDoorCallback;\n    }\n\n    public void removeWebDoorCallback() {\n        mWebDoorCallback = null;\n    }\n\n    public void saveHistory(String text) {\n        if (mHistory == null) {\n            mHistory = (ArrayList<String>) DoKitCacheUtils.readObject(CachesKey.WEB_DOOR_HISTORY);\n        }\n        if (mHistory == null) {\n            mHistory = new ArrayList<>();\n        }\n        if (mHistory.contains(text)) {\n            return;\n        }\n        if (mHistory.size() == 5) {\n            mHistory.remove(0);\n        }\n        mHistory.add(text);\n        DoKitCacheUtils.saveObject(CachesKey.WEB_DOOR_HISTORY, mHistory);\n    }\n\n    public ArrayList<String> getHistory() {\n        if (mHistory == null) {\n            mHistory = (ArrayList<String>) DoKitCacheUtils.readObject(CachesKey.WEB_DOOR_HISTORY);\n        }\n        if (mHistory == null) {\n            mHistory = new ArrayList<>();\n        }\n        return mHistory;\n    }\n\n    public void clearHistory() {\n        mHistory.clear();\n        DoKitCacheUtils.saveObject(CachesKey.WEB_DOOR_HISTORY, mHistory);\n    }\n\n    private static class Holder {\n        private static WebDoorManager INSTANCE = new WebDoorManager();\n    }\n\n    public static WebDoorManager getInstance() {\n        return Holder.INSTANCE;\n    }\n\n    public interface WebDoorCallback {\n        void overrideUrlLoading(Context context, String url);\n    }\n\n    private class DefaultWebDoorCallback implements WebDoorCallback {\n\n        @Override\n        public void overrideUrlLoading(Context context, String url) {\n            WebViewManager.INSTANCE.setUrl(url);\n            DoKit.launchFullScreen(WebDoorDefaultFragment.class, context);\n\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/webview/CommWebViewFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.webview\n\nimport android.os.Bundle\nimport android.view.View\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\nimport com.didichuxing.doraemonkit.widget.webview.MyWebView\n\n/**\n * @author jintai\n * @desc: 全局webview fragment\n */\nclass CommWebViewFragment : BaseFragment() {\n    private lateinit var mWebView: MyWebView\n    private lateinit var mTitle: HomeTitleBar\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_comm_webview\n    }\n\n    override fun onViewCreated(\n        view: View,\n        savedInstanceState: Bundle?\n    ) {\n        super.onViewCreated(view, savedInstanceState)\n        initView()\n    }\n\n\n    private fun initView() {\n        mTitle = findViewById(R.id.title_bar)\n        mTitle.setListener {\n            activity?.finish()\n        }\n        mWebView = findViewById(R.id.webview)\n        WebViewManager.url?.let { url ->\n            mWebView.loadUrl(url)\n            mWebView.setCallBack { title ->\n                title?.let {\n                    mTitle.setTitle(it)\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/webview/OnWebViewTitleChangeCallBack.java",
    "content": "package com.didichuxing.doraemonkit.kit.webview;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/8/21-18:16\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic  interface OnWebViewTitleChangeCallBack {\n\n    void onChange(String title);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/webview/WebViewManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.webview\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/8/21-18:03\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject WebViewManager {\n    public var url: String? = null\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/model/LatLng.java",
    "content": "package com.didichuxing.doraemonkit.model;\n\nimport java.io.Serializable;\n\n/**\n * Created by wanglikun on 2019-07-19\n */\npublic class LatLng implements Serializable {\n    public double latitude;\n\n    public double longitude;\n\n    public LatLng(double latitude, double longitude) {\n        this.latitude = latitude;\n        this.longitude = longitude;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/model/ViewInfo.java",
    "content": "package com.didichuxing.doraemonkit.model;\n\nimport android.graphics.Rect;\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\n/**\n * Created by wanglikun on 2019-06-27\n */\npublic class ViewInfo {\n    private static String TAG = \"ViewInfo\";\n    private final static int DRAW_TIME_LEVEL_NUM = 4;\n    private final static int DRAW_TIME_LEVEL_GAP = 5;\n    public final String id;\n    public final Rect viewRect;\n    public float drawTime;\n    public int layerNum;\n\n    public ViewInfo(View view) {\n        this.viewRect = UIUtils.getViewRect(view);\n        this.id = UIUtils.getIdText(view);\n    }\n\n    public int getDrawTimeLevel() {\n        return (int) (drawTime) / DRAW_TIME_LEVEL_GAP * 255 / DRAW_TIME_LEVEL_NUM;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/Action.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport java.lang.ref.ReferenceQueue;\nimport java.lang.ref.WeakReference;\n\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.Priority;\n\nabstract class Action<T> {\n  static class RequestWeakReference<M> extends WeakReference<M> {\n    final Action action;\n\n    public RequestWeakReference(Action action, M referent, ReferenceQueue<? super M> q) {\n      super(referent, q);\n      this.action = action;\n    }\n  }\n\n  final DokitPicasso picasso;\n  final Request request;\n  final WeakReference<T> target;\n  final boolean noFade;\n  final int memoryPolicy;\n  final int networkPolicy;\n  final int errorResId;\n  final Drawable errorDrawable;\n  final String key;\n  final Object tag;\n\n  boolean willReplay;\n  boolean cancelled;\n\n  Action(DokitPicasso picasso, T target, Request request, int memoryPolicy, int networkPolicy,\n         int errorResId, Drawable errorDrawable, String key, Object tag, boolean noFade) {\n    this.picasso = picasso;\n    this.request = request;\n    this.target =\n        target == null ? null : new RequestWeakReference<T>(this, target, picasso.referenceQueue);\n    this.memoryPolicy = memoryPolicy;\n    this.networkPolicy = networkPolicy;\n    this.noFade = noFade;\n    this.errorResId = errorResId;\n    this.errorDrawable = errorDrawable;\n    this.key = key;\n    this.tag = (tag != null ? tag : this);\n  }\n\n  abstract void complete(Bitmap result, DokitPicasso.LoadedFrom from);\n\n  abstract void error();\n\n  void cancel() {\n    cancelled = true;\n  }\n\n  Request getRequest() {\n    return request;\n  }\n\n  T getTarget() {\n    return target == null ? null : target.get();\n  }\n\n  String getKey() {\n    return key;\n  }\n\n  boolean isCancelled() {\n    return cancelled;\n  }\n\n  boolean willReplay() {\n    return willReplay;\n  }\n\n  int getMemoryPolicy() {\n    return memoryPolicy;\n  }\n\n  int getNetworkPolicy() {\n    return networkPolicy;\n  }\n\n  DokitPicasso getPicasso() {\n    return picasso;\n  }\n\n  Priority getPriority() {\n    return request.priority;\n  }\n\n  Object getTag() {\n    return tag;\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/AssetRequestHandler.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.content.Context;\nimport android.content.res.AssetManager;\nimport android.net.Uri;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport static android.content.ContentResolver.SCHEME_FILE;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.LoadedFrom.DISK;\n\nclass AssetRequestHandler extends RequestHandler {\n  protected static final String ANDROID_ASSET = \"android_asset\";\n  private static final int ASSET_PREFIX_LENGTH =\n      (SCHEME_FILE + \":///\" + ANDROID_ASSET + \"/\").length();\n\n  private final AssetManager assetManager;\n\n  public AssetRequestHandler(Context context) {\n    assetManager = context.getAssets();\n  }\n\n  @Override public boolean canHandleRequest(Request data) {\n    Uri uri = data.uri;\n    return (SCHEME_FILE.equals(uri.getScheme())\n        && !uri.getPathSegments().isEmpty() && ANDROID_ASSET.equals(uri.getPathSegments().get(0)));\n  }\n\n  @Override public Result load(Request request, int networkPolicy) throws IOException {\n    InputStream is = assetManager.open(getFilePath(request));\n    return new Result(is, DISK);\n  }\n\n  static String getFilePath(Request request) {\n    return request.uri.toString().substring(ASSET_PREFIX_LENGTH);\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/BitmapHunter.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.Matrix;\nimport android.net.NetworkInfo;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static com.didichuxing.doraemonkit.picasso.MemoryPolicy.shouldReadFromMemoryCache;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.LoadedFrom.MEMORY;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.Priority;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.Priority.LOW;\nimport static com.didichuxing.doraemonkit.picasso.Utils.OWNER_HUNTER;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_DECODED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_EXECUTING;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_JOINED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_REMOVED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_TRANSFORMED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.getLogIdsForHunter;\nimport static com.didichuxing.doraemonkit.picasso.Utils.log;\n\nclass BitmapHunter implements Runnable {\n  /**\n   * Global lock for bitmap decoding to ensure that we are only are decoding one at a time. Since\n   * this will only ever happen in background threads we help avoid excessive memory thrashing as\n   * well as potential OOMs. Shamelessly stolen from Volley.\n   */\n  private static final Object DECODE_LOCK = new Object();\n\n  private static final ThreadLocal<StringBuilder> NAME_BUILDER = new ThreadLocal<StringBuilder>() {\n    @Override protected StringBuilder initialValue() {\n      return new StringBuilder(Utils.THREAD_PREFIX);\n    }\n  };\n\n  private static final AtomicInteger SEQUENCE_GENERATOR = new AtomicInteger();\n\n  private static final RequestHandler ERRORING_HANDLER = new RequestHandler() {\n    @Override public boolean canHandleRequest(Request data) {\n      return true;\n    }\n\n    @Override public Result load(Request request, int networkPolicy) throws IOException {\n      throw new IllegalStateException(\"Unrecognized type of request: \" + request);\n    }\n  };\n\n  final int sequence;\n  final DokitPicasso picasso;\n  final Dispatcher dispatcher;\n  final Cache cache;\n  final Stats stats;\n  final String key;\n  final Request data;\n  final int memoryPolicy;\n  int networkPolicy;\n  final RequestHandler requestHandler;\n\n  Action action;\n  List<Action> actions;\n  Bitmap result;\n  Future<?> future;\n  DokitPicasso.LoadedFrom loadedFrom;\n  Exception exception;\n  int exifRotation; // Determined during decoding of original resource.\n  int retryCount;\n  Priority priority;\n\n  BitmapHunter(DokitPicasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action,\n               RequestHandler requestHandler) {\n    this.sequence = SEQUENCE_GENERATOR.incrementAndGet();\n    this.picasso = picasso;\n    this.dispatcher = dispatcher;\n    this.cache = cache;\n    this.stats = stats;\n    this.action = action;\n    this.key = action.getKey();\n    this.data = action.getRequest();\n    this.priority = action.getPriority();\n    this.memoryPolicy = action.getMemoryPolicy();\n    this.networkPolicy = action.getNetworkPolicy();\n    this.requestHandler = requestHandler;\n    this.retryCount = requestHandler.getRetryCount();\n  }\n\n  /**\n   * Decode a byte stream into a Bitmap. This method will take into account additional information\n   * about the supplied request in order to do the decoding efficiently (such as through leveraging\n   * {@code inSampleSize}).\n   */\n  static Bitmap decodeStream(InputStream stream, Request request) throws IOException {\n    MarkableInputStream markStream = new MarkableInputStream(stream);\n    stream = markStream;\n\n    long mark = markStream.savePosition(65536); // TODO fix this crap.\n\n    final BitmapFactory.Options options = RequestHandler.createBitmapOptions(request);\n    final boolean calculateSize = RequestHandler.requiresInSampleSize(options);\n\n    boolean isWebPFile = Utils.isWebPFile(stream);\n    markStream.reset(mark);\n    // When decode WebP network stream, BitmapFactory throw JNI Exception and make app crash.\n    // Decode byte array instead\n    if (isWebPFile) {\n      byte[] bytes = Utils.toByteArray(stream);\n      if (calculateSize) {\n        BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);\n        RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,\n            request);\n      }\n      return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);\n    } else {\n      if (calculateSize) {\n        BitmapFactory.decodeStream(stream, null, options);\n        RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,\n            request);\n\n        markStream.reset(mark);\n      }\n      Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);\n      if (bitmap == null) {\n        // Treat null as an IO exception, we will eventually retry.\n        throw new IOException(\"Failed to decode stream.\");\n      }\n      return bitmap;\n    }\n  }\n\n  @Override public void run() {\n    try {\n      updateThreadName(data);\n\n      if (picasso.loggingEnabled) {\n        log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));\n      }\n\n      result = hunt();\n\n      if (result == null) {\n        dispatcher.dispatchFailed(this);\n      } else {\n        dispatcher.dispatchComplete(this);\n      }\n    } catch (Downloader.ResponseException e) {\n      if (!e.localCacheOnly || e.responseCode != 504) {\n        exception = e;\n      }\n      dispatcher.dispatchFailed(this);\n    } catch (NetworkRequestHandler.ContentLengthException e) {\n      exception = e;\n      dispatcher.dispatchRetry(this);\n    } catch (IOException e) {\n      exception = e;\n      dispatcher.dispatchRetry(this);\n    } catch (OutOfMemoryError e) {\n      StringWriter writer = new StringWriter();\n      stats.createSnapshot().dump(new PrintWriter(writer));\n      exception = new RuntimeException(writer.toString(), e);\n      dispatcher.dispatchFailed(this);\n    } catch (Exception e) {\n      exception = e;\n      dispatcher.dispatchFailed(this);\n    } finally {\n      Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);\n    }\n  }\n\n  Bitmap hunt() throws IOException {\n    Bitmap bitmap = null;\n\n    if (shouldReadFromMemoryCache(memoryPolicy)) {\n      bitmap = cache.get(key);\n      if (bitmap != null) {\n        stats.dispatchCacheHit();\n        loadedFrom = MEMORY;\n        if (picasso.loggingEnabled) {\n          log(OWNER_HUNTER, VERB_DECODED, data.logId(), \"from cache\");\n        }\n        return bitmap;\n      }\n    }\n\n    data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;\n    RequestHandler.Result result = requestHandler.load(data, networkPolicy);\n    if (result != null) {\n      loadedFrom = result.getLoadedFrom();\n      exifRotation = result.getExifOrientation();\n\n      bitmap = result.getBitmap();\n\n      // If there was no Bitmap then we need to decode it from the stream.\n      if (bitmap == null) {\n        InputStream is = result.getStream();\n        try {\n          bitmap = decodeStream(is, data);\n        } finally {\n          Utils.closeQuietly(is);\n        }\n      }\n    }\n\n    if (bitmap != null) {\n      if (picasso.loggingEnabled) {\n        log(OWNER_HUNTER, VERB_DECODED, data.logId());\n      }\n      stats.dispatchBitmapDecoded(bitmap);\n      if (data.needsTransformation() || exifRotation != 0) {\n        synchronized (DECODE_LOCK) {\n          if (data.needsMatrixTransform() || exifRotation != 0) {\n            bitmap = transformResult(data, bitmap, exifRotation);\n            if (picasso.loggingEnabled) {\n              log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());\n            }\n          }\n          if (data.hasCustomTransformations()) {\n            bitmap = applyCustomTransformations(data.transformations, bitmap);\n            if (picasso.loggingEnabled) {\n              log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), \"from custom transformations\");\n            }\n          }\n        }\n        if (bitmap != null) {\n          stats.dispatchBitmapTransformed(bitmap);\n        }\n      }\n    }\n\n    return bitmap;\n  }\n\n  void attach(Action action) {\n    boolean loggingEnabled = picasso.loggingEnabled;\n    Request request = action.request;\n\n    if (this.action == null) {\n      this.action = action;\n      if (loggingEnabled) {\n        if (actions == null || actions.isEmpty()) {\n          log(OWNER_HUNTER, VERB_JOINED, request.logId(), \"to empty hunter\");\n        } else {\n          log(OWNER_HUNTER, VERB_JOINED, request.logId(), getLogIdsForHunter(this, \"to \"));\n        }\n      }\n      return;\n    }\n\n    if (actions == null) {\n      actions = new ArrayList<Action>(3);\n    }\n\n    actions.add(action);\n\n    if (loggingEnabled) {\n      log(OWNER_HUNTER, VERB_JOINED, request.logId(), getLogIdsForHunter(this, \"to \"));\n    }\n\n    Priority actionPriority = action.getPriority();\n    if (actionPriority.ordinal() > priority.ordinal()) {\n      priority = actionPriority;\n    }\n  }\n\n  void detach(Action action) {\n    boolean detached = false;\n    if (this.action == action) {\n      this.action = null;\n      detached = true;\n    } else if (actions != null) {\n      detached = actions.remove(action);\n    }\n\n    // The action being detached had the highest priority. Update this\n    // hunter's priority with the remaining actions.\n    if (detached && action.getPriority() == priority) {\n      priority = computeNewPriority();\n    }\n\n    if (picasso.loggingEnabled) {\n      log(OWNER_HUNTER, VERB_REMOVED, action.request.logId(), getLogIdsForHunter(this, \"from \"));\n    }\n  }\n\n  private Priority computeNewPriority() {\n    Priority newPriority = LOW;\n\n    boolean hasMultiple = actions != null && !actions.isEmpty();\n    boolean hasAny = action != null || hasMultiple;\n\n    // Hunter has no requests, low priority.\n    if (!hasAny) {\n      return newPriority;\n    }\n\n    if (action != null) {\n      newPriority = action.getPriority();\n    }\n\n    if (hasMultiple) {\n      //noinspection ForLoopReplaceableByForEach\n      for (int i = 0, n = actions.size(); i < n; i++) {\n        Priority actionPriority = actions.get(i).getPriority();\n        if (actionPriority.ordinal() > newPriority.ordinal()) {\n          newPriority = actionPriority;\n        }\n      }\n    }\n\n    return newPriority;\n  }\n\n  boolean cancel() {\n    return action == null\n        && (actions == null || actions.isEmpty())\n        && future != null\n        && future.cancel(false);\n  }\n\n  boolean isCancelled() {\n    return future != null && future.isCancelled();\n  }\n\n  boolean shouldRetry(boolean airplaneMode, NetworkInfo info) {\n    boolean hasRetries = retryCount > 0;\n    if (!hasRetries) {\n      return false;\n    }\n    retryCount--;\n    return requestHandler.shouldRetry(airplaneMode, info);\n  }\n\n  boolean supportsReplay() {\n    return requestHandler.supportsReplay();\n  }\n\n  Bitmap getResult() {\n    return result;\n  }\n\n  String getKey() {\n    return key;\n  }\n\n  int getMemoryPolicy() {\n    return memoryPolicy;\n  }\n\n  Request getData() {\n    return data;\n  }\n\n  Action getAction() {\n    return action;\n  }\n\n  DokitPicasso getPicasso() {\n    return picasso;\n  }\n\n  List<Action> getActions() {\n    return actions;\n  }\n\n  Exception getException() {\n    return exception;\n  }\n\n  DokitPicasso.LoadedFrom getLoadedFrom() {\n    return loadedFrom;\n  }\n\n  Priority getPriority() {\n    return priority;\n  }\n\n  static void updateThreadName(Request data) {\n    String name = data.getName();\n\n    StringBuilder builder = NAME_BUILDER.get();\n    builder.ensureCapacity(Utils.THREAD_PREFIX.length() + name.length());\n    builder.replace(Utils.THREAD_PREFIX.length(), builder.length(), name);\n\n    Thread.currentThread().setName(builder.toString());\n  }\n\n  static BitmapHunter forRequest(DokitPicasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,\n                                 Action action) {\n    Request request = action.getRequest();\n    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();\n\n    // Index-based loop to avoid allocating an iterator.\n    //noinspection ForLoopReplaceableByForEach\n    for (int i = 0, count = requestHandlers.size(); i < count; i++) {\n      RequestHandler requestHandler = requestHandlers.get(i);\n      if (requestHandler.canHandleRequest(request)) {\n        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);\n      }\n    }\n\n    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);\n  }\n\n  static Bitmap applyCustomTransformations(List<Transformation> transformations, Bitmap result) {\n    for (int i = 0, count = transformations.size(); i < count; i++) {\n      final Transformation transformation = transformations.get(i);\n      Bitmap newResult;\n      try {\n        newResult = transformation.transform(result);\n      } catch (final RuntimeException e) {\n        DokitPicasso.HANDLER.post(new Runnable() {\n          @Override public void run() {\n            throw new RuntimeException(\n                \"Transformation \" + transformation.key() + \" crashed with exception.\", e);\n          }\n        });\n        return null;\n      }\n\n      if (newResult == null) {\n        final StringBuilder builder = new StringBuilder() //\n            .append(\"Transformation \")\n            .append(transformation.key())\n            .append(\" returned null after \")\n            .append(i)\n            .append(\" previous transformation(s).\\n\\nTransformation list:\\n\");\n        for (Transformation t : transformations) {\n          builder.append(t.key()).append('\\n');\n        }\n        DokitPicasso.HANDLER.post(new Runnable() {\n          @Override public void run() {\n            throw new NullPointerException(builder.toString());\n          }\n        });\n        return null;\n      }\n\n      if (newResult == result && result.isRecycled()) {\n        DokitPicasso.HANDLER.post(new Runnable() {\n          @Override public void run() {\n            throw new IllegalStateException(\"Transformation \"\n                + transformation.key()\n                + \" returned input Bitmap but recycled it.\");\n          }\n        });\n        return null;\n      }\n\n      // If the transformation returned a new bitmap ensure they recycled the original.\n      if (newResult != result && !result.isRecycled()) {\n        DokitPicasso.HANDLER.post(new Runnable() {\n          @Override public void run() {\n            throw new IllegalStateException(\"Transformation \"\n                + transformation.key()\n                + \" mutated input Bitmap but failed to recycle the original.\");\n          }\n        });\n        return null;\n      }\n\n      result = newResult;\n    }\n    return result;\n  }\n\n  static Bitmap transformResult(Request data, Bitmap result, int exifRotation) {\n    int inWidth = result.getWidth();\n    int inHeight = result.getHeight();\n    boolean onlyScaleDown = data.onlyScaleDown;\n\n    int drawX = 0;\n    int drawY = 0;\n    int drawWidth = inWidth;\n    int drawHeight = inHeight;\n\n    Matrix matrix = new Matrix();\n\n    if (data.needsMatrixTransform()) {\n      int targetWidth = data.targetWidth;\n      int targetHeight = data.targetHeight;\n\n      float targetRotation = data.rotationDegrees;\n      if (targetRotation != 0) {\n        if (data.hasRotationPivot) {\n          matrix.setRotate(targetRotation, data.rotationPivotX, data.rotationPivotY);\n        } else {\n          matrix.setRotate(targetRotation);\n        }\n      }\n\n      if (data.centerCrop) {\n        float widthRatio = targetWidth / (float) inWidth;\n        float heightRatio = targetHeight / (float) inHeight;\n        float scaleX, scaleY;\n        if (widthRatio > heightRatio) {\n          int newSize = (int) Math.ceil(inHeight * (heightRatio / widthRatio));\n          drawY = (inHeight - newSize) / 2;\n          drawHeight = newSize;\n          scaleX = widthRatio;\n          scaleY = targetHeight / (float) drawHeight;\n        } else {\n          int newSize = (int) Math.ceil(inWidth * (widthRatio / heightRatio));\n          drawX = (inWidth - newSize) / 2;\n          drawWidth = newSize;\n          scaleX = targetWidth / (float) drawWidth;\n          scaleY = heightRatio;\n        }\n        if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) {\n          matrix.preScale(scaleX, scaleY);\n        }\n      } else if (data.centerInside) {\n        float widthRatio = targetWidth / (float) inWidth;\n        float heightRatio = targetHeight / (float) inHeight;\n        float scale = widthRatio < heightRatio ? widthRatio : heightRatio;\n        if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) {\n          matrix.preScale(scale, scale);\n        }\n      } else if ((targetWidth != 0 || targetHeight != 0) //\n          && (targetWidth != inWidth || targetHeight != inHeight)) {\n        // If an explicit target size has been specified and they do not match the results bounds,\n        // pre-scale the existing matrix appropriately.\n        // Keep aspect ratio if one dimension is set to 0.\n        float sx =\n            targetWidth != 0 ? targetWidth / (float) inWidth : targetHeight / (float) inHeight;\n        float sy =\n            targetHeight != 0 ? targetHeight / (float) inHeight : targetWidth / (float) inWidth;\n        if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) {\n          matrix.preScale(sx, sy);\n        }\n      }\n    }\n\n    if (exifRotation != 0) {\n      matrix.preRotate(exifRotation);\n    }\n\n    Bitmap newResult =\n        Bitmap.createBitmap(result, drawX, drawY, drawWidth, drawHeight, matrix, true);\n    if (newResult != result) {\n      result.recycle();\n      result = newResult;\n    }\n\n    return result;\n  }\n\n  private static boolean shouldResize(boolean onlyScaleDown, int inWidth, int inHeight,\n      int targetWidth, int targetHeight) {\n    return !onlyScaleDown || inWidth > targetWidth || inHeight > targetHeight;\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/Cache.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.graphics.Bitmap;\n\n/**\n * A memory cache for storing the most recently used images.\n * <p>\n * <em>Note:</em> The {@link Cache} is accessed by multiple threads. You must ensure\n * your {@link Cache} implementation is thread safe when {@link Cache#get(String)} or {@link\n * Cache#set(String, android.graphics.Bitmap)} is called.\n */\npublic interface Cache {\n  /** Retrieve an image for the specified {@code key} or {@code null}. */\n  Bitmap get(String key);\n\n  /** Store an image in the cache for the specified {@code key}. */\n  void set(String key, Bitmap bitmap);\n\n  /** Returns the current size of the cache in bytes. */\n  int size();\n\n  /** Returns the maximum size in bytes that the cache can hold. */\n  int maxSize();\n\n  /** Clears the cache. */\n  void clear();\n\n  /** Remove items whose key is prefixed with {@code keyPrefix}. */\n  void clearKeyUri(String keyPrefix);\n\n  /** A cache which does not store any values. */\n  Cache NONE = new Cache() {\n    @Override public Bitmap get(String key) {\n      return null;\n    }\n\n    @Override public void set(String key, Bitmap bitmap) {\n      // Ignore.\n    }\n\n    @Override public int size() {\n      return 0;\n    }\n\n    @Override public int maxSize() {\n      return 0;\n    }\n\n    @Override public void clear() {\n    }\n\n    @Override public void clearKeyUri(String keyPrefix) {\n    }\n  };\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/Callback.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\npublic interface Callback {\n  void onSuccess();\n\n  void onError();\n\n  public static class EmptyCallback implements Callback {\n\n    @Override public void onSuccess() {\n    }\n\n    @Override public void onError() {\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/ContactsPhotoRequestHandler.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.annotation.TargetApi;\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.UriMatcher;\nimport android.net.Uri;\nimport android.provider.ContactsContract;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport static android.content.ContentResolver.SCHEME_CONTENT;\nimport static android.os.Build.VERSION.SDK_INT;\nimport static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;\nimport static android.provider.ContactsContract.Contacts.openContactPhotoInputStream;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.LoadedFrom.DISK;\n\nclass ContactsPhotoRequestHandler extends RequestHandler {\n  /** A lookup uri (e.g. content://com.android.contacts/contacts/lookup/3570i61d948d30808e537) */\n  private static final int ID_LOOKUP = 1;\n  /** A contact thumbnail uri (e.g. content://com.android.contacts/contacts/38/photo) */\n  private static final int ID_THUMBNAIL = 2;\n  /** A contact uri (e.g. content://com.android.contacts/contacts/38) */\n  private static final int ID_CONTACT = 3;\n  /**\n   * A contact display photo (high resolution) uri\n   * (e.g. content://com.android.contacts/display_photo/5)\n   */\n  private static final int ID_DISPLAY_PHOTO = 4;\n\n  private static final UriMatcher matcher;\n\n  static {\n    matcher = new UriMatcher(UriMatcher.NO_MATCH);\n    matcher.addURI(ContactsContract.AUTHORITY, \"contacts/lookup/*/#\", ID_LOOKUP);\n    matcher.addURI(ContactsContract.AUTHORITY, \"contacts/lookup/*\", ID_LOOKUP);\n    matcher.addURI(ContactsContract.AUTHORITY, \"contacts/#/photo\", ID_THUMBNAIL);\n    matcher.addURI(ContactsContract.AUTHORITY, \"contacts/#\", ID_CONTACT);\n    matcher.addURI(ContactsContract.AUTHORITY, \"display_photo/#\", ID_DISPLAY_PHOTO);\n  }\n\n  private final Context context;\n\n  ContactsPhotoRequestHandler(Context context) {\n    this.context = context;\n  }\n\n  @Override public boolean canHandleRequest(Request data) {\n    final Uri uri = data.uri;\n    return (SCHEME_CONTENT.equals(uri.getScheme())\n        && ContactsContract.Contacts.CONTENT_URI.getHost().equals(uri.getHost())\n        && matcher.match(data.uri) != UriMatcher.NO_MATCH);\n  }\n\n  @Override public Result load(Request request, int networkPolicy) throws IOException {\n    InputStream is = getInputStream(request);\n    return is != null ? new Result(is, DISK) : null;\n  }\n\n  private InputStream getInputStream(Request data) throws IOException {\n    ContentResolver contentResolver = context.getContentResolver();\n    Uri uri = data.uri;\n    switch (matcher.match(uri)) {\n      case ID_LOOKUP:\n        uri = ContactsContract.Contacts.lookupContact(contentResolver, uri);\n        if (uri == null) {\n          return null;\n        }\n        // Resolved the uri to a contact uri, intentionally fall through to process the resolved uri\n      case ID_CONTACT:\n        if (SDK_INT < ICE_CREAM_SANDWICH) {\n          return openContactPhotoInputStream(contentResolver, uri);\n        } else {\n          return ContactPhotoStreamIcs.get(contentResolver, uri);\n        }\n      case ID_THUMBNAIL:\n      case ID_DISPLAY_PHOTO:\n        return contentResolver.openInputStream(uri);\n      default:\n        throw new IllegalStateException(\"Invalid uri: \" + uri);\n    }\n  }\n\n  @TargetApi(ICE_CREAM_SANDWICH)\n  private static class ContactPhotoStreamIcs {\n    static InputStream get(ContentResolver contentResolver, Uri uri) {\n      return openContactPhotoInputStream(contentResolver, uri, true);\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/ContentStreamRequestHandler.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport static android.content.ContentResolver.SCHEME_CONTENT;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.LoadedFrom.DISK;\n\nclass ContentStreamRequestHandler extends RequestHandler {\n  final Context context;\n\n  ContentStreamRequestHandler(Context context) {\n    this.context = context;\n  }\n\n  @Override public boolean canHandleRequest(Request data) {\n    return SCHEME_CONTENT.equals(data.uri.getScheme());\n  }\n\n  @Override public Result load(Request request, int networkPolicy) throws IOException {\n    return new Result(getInputStream(request), DISK);\n  }\n\n  InputStream getInputStream(Request request) throws FileNotFoundException {\n    ContentResolver contentResolver = context.getContentResolver();\n    return contentResolver.openInputStream(request.uri);\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/DeferredRequestCreator.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.view.ViewTreeObserver;\nimport android.widget.ImageView;\n\nimport java.lang.ref.WeakReference;\n\nclass DeferredRequestCreator implements ViewTreeObserver.OnPreDrawListener {\n\n    final RequestCreator creator;\n    final WeakReference<ImageView> target;\n    Callback callback;\n\n    DeferredRequestCreator(RequestCreator creator, ImageView target) {\n        this(creator, target, null);\n    }\n\n    DeferredRequestCreator(RequestCreator creator, ImageView target, Callback callback) {\n        this.creator = creator;\n        this.target = new WeakReference<ImageView>(target);\n        this.callback = callback;\n        target.getViewTreeObserver().addOnPreDrawListener(this);\n    }\n\n    @Override\n    public boolean onPreDraw() {\n        ImageView target = this.target.get();\n        if (target == null) {\n            return true;\n        }\n        ViewTreeObserver vto = target.getViewTreeObserver();\n        if (!vto.isAlive()) {\n            return true;\n        }\n\n        int width = target.getWidth();\n        int height = target.getHeight();\n\n        if (width <= 0 || height <= 0) {\n            return true;\n        }\n\n        vto.removeOnPreDrawListener(this);\n\n        this.creator.unfit().resize(width, height).into(target, callback);\n        return true;\n    }\n\n    void cancel() {\n        callback = null;\n        ImageView target = this.target.get();\n        if (target == null) {\n            return;\n        }\n        ViewTreeObserver vto = target.getViewTreeObserver();\n        if (!vto.isAlive()) {\n            return;\n        }\n        vto.removeOnPreDrawListener(this);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/Dispatcher.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.Manifest;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.net.ConnectivityManager;\nimport android.net.NetworkInfo;\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Looper;\nimport android.os.Message;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.WeakHashMap;\nimport java.util.concurrent.ExecutorService;\n\nimport static android.content.Context.CONNECTIVITY_SERVICE;\nimport static android.content.Intent.ACTION_AIRPLANE_MODE_CHANGED;\nimport static android.net.ConnectivityManager.CONNECTIVITY_ACTION;\nimport static android.os.Process.THREAD_PRIORITY_BACKGROUND;\nimport static com.didichuxing.doraemonkit.picasso.BitmapHunter.forRequest;\nimport static com.didichuxing.doraemonkit.picasso.MemoryPolicy.shouldWriteToMemoryCache;\nimport static com.didichuxing.doraemonkit.picasso.Utils.OWNER_DISPATCHER;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_BATCHED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_CANCELED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_DELIVERED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_ENQUEUED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_IGNORED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_PAUSED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_REPLAYING;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_RETRYING;\nimport static com.didichuxing.doraemonkit.picasso.Utils.getLogIdsForHunter;\nimport static com.didichuxing.doraemonkit.picasso.Utils.getService;\nimport static com.didichuxing.doraemonkit.picasso.Utils.hasPermission;\nimport static com.didichuxing.doraemonkit.picasso.Utils.log;\n\nclass Dispatcher {\n  private static final int RETRY_DELAY = 500;\n  private static final int AIRPLANE_MODE_ON = 1;\n  private static final int AIRPLANE_MODE_OFF = 0;\n\n  static final int REQUEST_SUBMIT = 1;\n  static final int REQUEST_CANCEL = 2;\n  static final int REQUEST_GCED = 3;\n  static final int HUNTER_COMPLETE = 4;\n  static final int HUNTER_RETRY = 5;\n  static final int HUNTER_DECODE_FAILED = 6;\n  static final int HUNTER_DELAY_NEXT_BATCH = 7;\n  static final int HUNTER_BATCH_COMPLETE = 8;\n  static final int NETWORK_STATE_CHANGE = 9;\n  static final int AIRPLANE_MODE_CHANGE = 10;\n  static final int TAG_PAUSE = 11;\n  static final int TAG_RESUME = 12;\n  static final int REQUEST_BATCH_RESUME = 13;\n\n  private static final String DISPATCHER_THREAD_NAME = \"Dispatcher\";\n  private static final int BATCH_DELAY = 200; // ms\n\n  final DispatcherThread dispatcherThread;\n  final Context context;\n  final ExecutorService service;\n  final Downloader downloader;\n  final Map<String, BitmapHunter> hunterMap;\n  final Map<Object, Action> failedActions;\n  final Map<Object, Action> pausedActions;\n  final Set<Object> pausedTags;\n  final Handler handler;\n  final Handler mainThreadHandler;\n  final Cache cache;\n  final Stats stats;\n  final List<BitmapHunter> batch;\n  final NetworkBroadcastReceiver receiver;\n  final boolean scansNetworkChanges;\n\n  boolean airplaneMode;\n\n  Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,\n      Downloader downloader, Cache cache, Stats stats) {\n    this.dispatcherThread = new DispatcherThread();\n    this.dispatcherThread.start();\n    Utils.flushStackLocalLeaks(dispatcherThread.getLooper());\n    this.context = context;\n    this.service = service;\n    this.hunterMap = new LinkedHashMap<String, BitmapHunter>();\n    this.failedActions = new WeakHashMap<Object, Action>();\n    this.pausedActions = new WeakHashMap<Object, Action>();\n    this.pausedTags = new HashSet<Object>();\n    this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);\n    this.downloader = downloader;\n    this.mainThreadHandler = mainThreadHandler;\n    this.cache = cache;\n    this.stats = stats;\n    this.batch = new ArrayList<BitmapHunter>(4);\n    this.airplaneMode = Utils.isAirplaneModeOn(this.context);\n    this.scansNetworkChanges = hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE);\n    this.receiver = new NetworkBroadcastReceiver(this);\n    receiver.register();\n  }\n\n  void shutdown() {\n    // Shutdown the thread pool only if it is the one created by Picasso.\n    if (service instanceof PicassoExecutorService) {\n      service.shutdown();\n    }\n    downloader.shutdown();\n    dispatcherThread.quit();\n    // Unregister network broadcast receiver on the main thread.\n    DokitPicasso.HANDLER.post(new Runnable() {\n      @Override public void run() {\n        receiver.unregister();\n      }\n    });\n  }\n\n  void dispatchSubmit(Action action) {\n    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));\n  }\n\n  void dispatchCancel(Action action) {\n    handler.sendMessage(handler.obtainMessage(REQUEST_CANCEL, action));\n  }\n\n  void dispatchPauseTag(Object tag) {\n    handler.sendMessage(handler.obtainMessage(TAG_PAUSE, tag));\n  }\n\n  void dispatchResumeTag(Object tag) {\n    handler.sendMessage(handler.obtainMessage(TAG_RESUME, tag));\n  }\n\n  void dispatchComplete(BitmapHunter hunter) {\n    handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));\n  }\n\n  void dispatchRetry(BitmapHunter hunter) {\n    handler.sendMessageDelayed(handler.obtainMessage(HUNTER_RETRY, hunter), RETRY_DELAY);\n  }\n\n  void dispatchFailed(BitmapHunter hunter) {\n    handler.sendMessage(handler.obtainMessage(HUNTER_DECODE_FAILED, hunter));\n  }\n\n  void dispatchNetworkStateChange(NetworkInfo info) {\n    handler.sendMessage(handler.obtainMessage(NETWORK_STATE_CHANGE, info));\n  }\n\n  void dispatchAirplaneModeChange(boolean airplaneMode) {\n    handler.sendMessage(handler.obtainMessage(AIRPLANE_MODE_CHANGE,\n        airplaneMode ? AIRPLANE_MODE_ON : AIRPLANE_MODE_OFF, 0));\n  }\n\n  void performSubmit(Action action) {\n    performSubmit(action, true);\n  }\n\n  void performSubmit(Action action, boolean dismissFailed) {\n    if (pausedTags.contains(action.getTag())) {\n      pausedActions.put(action.getTarget(), action);\n      if (action.getPicasso().loggingEnabled) {\n        log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),\n            \"because tag '\" + action.getTag() + \"' is paused\");\n      }\n      return;\n    }\n\n    BitmapHunter hunter = hunterMap.get(action.getKey());\n    if (hunter != null) {\n      hunter.attach(action);\n      return;\n    }\n\n    if (service.isShutdown()) {\n      if (action.getPicasso().loggingEnabled) {\n        log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), \"because shut down\");\n      }\n      return;\n    }\n\n    hunter = forRequest(action.getPicasso(), this, cache, stats, action);\n    hunter.future = service.submit(hunter);\n    hunterMap.put(action.getKey(), hunter);\n    if (dismissFailed) {\n      failedActions.remove(action.getTarget());\n    }\n\n    if (action.getPicasso().loggingEnabled) {\n      log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());\n    }\n  }\n\n  void performCancel(Action action) {\n    String key = action.getKey();\n    BitmapHunter hunter = hunterMap.get(key);\n    if (hunter != null) {\n      hunter.detach(action);\n      if (hunter.cancel()) {\n        hunterMap.remove(key);\n        if (action.getPicasso().loggingEnabled) {\n          log(OWNER_DISPATCHER, VERB_CANCELED, action.getRequest().logId());\n        }\n      }\n    }\n\n    if (pausedTags.contains(action.getTag())) {\n      pausedActions.remove(action.getTarget());\n      if (action.getPicasso().loggingEnabled) {\n        log(OWNER_DISPATCHER, VERB_CANCELED, action.getRequest().logId(),\n            \"because paused request got canceled\");\n      }\n    }\n\n    Action remove = failedActions.remove(action.getTarget());\n    if (remove != null && remove.getPicasso().loggingEnabled) {\n      log(OWNER_DISPATCHER, VERB_CANCELED, remove.getRequest().logId(), \"from replaying\");\n    }\n  }\n\n  void performPauseTag(Object tag) {\n    // Trying to pause a tag that is already paused.\n    if (!pausedTags.add(tag)) {\n      return;\n    }\n\n    // Go through all active hunters and detach/pause the requests\n    // that have the paused tag.\n    for (Iterator<BitmapHunter> it = hunterMap.values().iterator(); it.hasNext();) {\n      BitmapHunter hunter = it.next();\n      boolean loggingEnabled = hunter.getPicasso().loggingEnabled;\n\n      Action single = hunter.getAction();\n      List<Action> joined = hunter.getActions();\n      boolean hasMultiple = joined != null && !joined.isEmpty();\n\n      // Hunter has no requests, bail early.\n      if (single == null && !hasMultiple) {\n        continue;\n      }\n\n      if (single != null && single.getTag().equals(tag)) {\n        hunter.detach(single);\n        pausedActions.put(single.getTarget(), single);\n        if (loggingEnabled) {\n          log(OWNER_DISPATCHER, VERB_PAUSED, single.request.logId(),\n              \"because tag '\" + tag + \"' was paused\");\n        }\n      }\n\n      if (hasMultiple) {\n        for (int i = joined.size() - 1; i >= 0; i--) {\n          Action action = joined.get(i);\n          if (!action.getTag().equals(tag)) {\n            continue;\n          }\n\n          hunter.detach(action);\n          pausedActions.put(action.getTarget(), action);\n          if (loggingEnabled) {\n            log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),\n                \"because tag '\" + tag + \"' was paused\");\n          }\n        }\n      }\n\n      // Check if the hunter can be cancelled in case all its requests\n      // had the tag being paused here.\n      if (hunter.cancel()) {\n        it.remove();\n        if (loggingEnabled) {\n          log(OWNER_DISPATCHER, VERB_CANCELED, getLogIdsForHunter(hunter), \"all actions paused\");\n        }\n      }\n    }\n  }\n\n  void performResumeTag(Object tag) {\n    // Trying to resume a tag that is not paused.\n    if (!pausedTags.remove(tag)) {\n      return;\n    }\n\n    List<Action> batch = null;\n    for (Iterator<Action> i = pausedActions.values().iterator(); i.hasNext();) {\n      Action action = i.next();\n      if (action.getTag().equals(tag)) {\n        if (batch == null) {\n          batch = new ArrayList<Action>();\n        }\n        batch.add(action);\n        i.remove();\n      }\n    }\n\n    if (batch != null) {\n      mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(REQUEST_BATCH_RESUME, batch));\n    }\n  }\n\n  void performRetry(BitmapHunter hunter) {\n    if (hunter.isCancelled()) return;\n\n    if (service.isShutdown()) {\n      performError(hunter, false);\n      return;\n    }\n\n    NetworkInfo networkInfo = null;\n    if (scansNetworkChanges) {\n      ConnectivityManager connectivityManager = getService(context, CONNECTIVITY_SERVICE);\n      networkInfo = connectivityManager.getActiveNetworkInfo();\n    }\n\n    boolean hasConnectivity = networkInfo != null && networkInfo.isConnected();\n    boolean shouldRetryHunter = hunter.shouldRetry(airplaneMode, networkInfo);\n    boolean supportsReplay = hunter.supportsReplay();\n\n    if (!shouldRetryHunter) {\n      // Mark for replay only if we observe network info changes and support replay.\n      boolean willReplay = scansNetworkChanges && supportsReplay;\n      performError(hunter, willReplay);\n      if (willReplay) {\n        markForReplay(hunter);\n      }\n      return;\n    }\n\n    // If we don't scan for network changes (missing permission) or if we have connectivity, retry.\n    if (!scansNetworkChanges || hasConnectivity) {\n      if (hunter.getPicasso().loggingEnabled) {\n        log(OWNER_DISPATCHER, VERB_RETRYING, getLogIdsForHunter(hunter));\n      }\n      //noinspection ThrowableResultOfMethodCallIgnored\n      if (hunter.getException() instanceof NetworkRequestHandler.ContentLengthException) {\n        hunter.networkPolicy |= NetworkPolicy.NO_CACHE.index;\n      }\n      hunter.future = service.submit(hunter);\n      return;\n    }\n\n    performError(hunter, supportsReplay);\n\n    if (supportsReplay) {\n      markForReplay(hunter);\n    }\n  }\n\n  void performComplete(BitmapHunter hunter) {\n    if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {\n      cache.set(hunter.getKey(), hunter.getResult());\n    }\n    hunterMap.remove(hunter.getKey());\n    batch(hunter);\n    if (hunter.getPicasso().loggingEnabled) {\n      log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), \"for completion\");\n    }\n  }\n\n  void performBatchComplete() {\n    List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);\n    batch.clear();\n    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));\n    logBatch(copy);\n  }\n\n  void performError(BitmapHunter hunter, boolean willReplay) {\n    if (hunter.getPicasso().loggingEnabled) {\n      log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter),\n          \"for error\" + (willReplay ? \" (will replay)\" : \"\"));\n    }\n    hunterMap.remove(hunter.getKey());\n    batch(hunter);\n  }\n\n  void performAirplaneModeChange(boolean airplaneMode) {\n    this.airplaneMode = airplaneMode;\n  }\n\n  void performNetworkStateChange(NetworkInfo info) {\n    if (service instanceof PicassoExecutorService) {\n      ((PicassoExecutorService) service).adjustThreadCount(info);\n    }\n    // Intentionally check only if isConnected() here before we flush out failed actions.\n    if (info != null && info.isConnected()) {\n      flushFailedActions();\n    }\n  }\n\n  private void flushFailedActions() {\n    if (!failedActions.isEmpty()) {\n      Iterator<Action> iterator = failedActions.values().iterator();\n      while (iterator.hasNext()) {\n        Action action = iterator.next();\n        iterator.remove();\n        if (action.getPicasso().loggingEnabled) {\n          log(OWNER_DISPATCHER, VERB_REPLAYING, action.getRequest().logId());\n        }\n        performSubmit(action, false);\n      }\n    }\n  }\n\n  private void markForReplay(BitmapHunter hunter) {\n    Action action = hunter.getAction();\n    if (action != null) {\n      markForReplay(action);\n    }\n    List<Action> joined = hunter.getActions();\n    if (joined != null) {\n      //noinspection ForLoopReplaceableByForEach\n      for (int i = 0, n = joined.size(); i < n; i++) {\n        Action join = joined.get(i);\n        markForReplay(join);\n      }\n    }\n  }\n\n  private void markForReplay(Action action) {\n    Object target = action.getTarget();\n    if (target != null) {\n      action.willReplay = true;\n      failedActions.put(target, action);\n    }\n  }\n\n  private void batch(BitmapHunter hunter) {\n    if (hunter.isCancelled()) {\n      return;\n    }\n    batch.add(hunter);\n    if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {\n      handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);\n    }\n  }\n\n  private void logBatch(List<BitmapHunter> copy) {\n    if (copy == null || copy.isEmpty()) return;\n    BitmapHunter hunter = copy.get(0);\n    DokitPicasso picasso = hunter.getPicasso();\n    if (picasso.loggingEnabled) {\n      StringBuilder builder = new StringBuilder();\n      for (BitmapHunter bitmapHunter : copy) {\n        if (builder.length() > 0) builder.append(\", \");\n        builder.append(Utils.getLogIdsForHunter(bitmapHunter));\n      }\n      log(OWNER_DISPATCHER, VERB_DELIVERED, builder.toString());\n    }\n  }\n\n  private static class DispatcherHandler extends Handler {\n    private final Dispatcher dispatcher;\n\n    public DispatcherHandler(Looper looper, Dispatcher dispatcher) {\n      super(looper);\n      this.dispatcher = dispatcher;\n    }\n\n    @Override public void handleMessage(final Message msg) {\n      switch (msg.what) {\n        case REQUEST_SUBMIT: {\n          Action action = (Action) msg.obj;\n          dispatcher.performSubmit(action);\n          break;\n        }\n        case REQUEST_CANCEL: {\n          Action action = (Action) msg.obj;\n          dispatcher.performCancel(action);\n          break;\n        }\n        case TAG_PAUSE: {\n          Object tag = msg.obj;\n          dispatcher.performPauseTag(tag);\n          break;\n        }\n        case TAG_RESUME: {\n          Object tag = msg.obj;\n          dispatcher.performResumeTag(tag);\n          break;\n        }\n        case HUNTER_COMPLETE: {\n          BitmapHunter hunter = (BitmapHunter) msg.obj;\n          dispatcher.performComplete(hunter);\n          break;\n        }\n        case HUNTER_RETRY: {\n          BitmapHunter hunter = (BitmapHunter) msg.obj;\n          dispatcher.performRetry(hunter);\n          break;\n        }\n        case HUNTER_DECODE_FAILED: {\n          BitmapHunter hunter = (BitmapHunter) msg.obj;\n          dispatcher.performError(hunter, false);\n          break;\n        }\n        case HUNTER_DELAY_NEXT_BATCH: {\n          dispatcher.performBatchComplete();\n          break;\n        }\n        case NETWORK_STATE_CHANGE: {\n          NetworkInfo info = (NetworkInfo) msg.obj;\n          dispatcher.performNetworkStateChange(info);\n          break;\n        }\n        case AIRPLANE_MODE_CHANGE: {\n          dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON);\n          break;\n        }\n        default:\n          DokitPicasso.HANDLER.post(new Runnable() {\n            @Override public void run() {\n              throw new AssertionError(\"Unknown handler message received: \" + msg.what);\n            }\n          });\n      }\n    }\n  }\n\n  static class DispatcherThread extends HandlerThread {\n    DispatcherThread() {\n      super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);\n    }\n  }\n\n  static class NetworkBroadcastReceiver extends BroadcastReceiver {\n    static final String EXTRA_AIRPLANE_STATE = \"state\";\n\n    private final Dispatcher dispatcher;\n\n    NetworkBroadcastReceiver(Dispatcher dispatcher) {\n      this.dispatcher = dispatcher;\n    }\n\n    void register() {\n      IntentFilter filter = new IntentFilter();\n      filter.addAction(ACTION_AIRPLANE_MODE_CHANGED);\n      if (dispatcher.scansNetworkChanges) {\n        filter.addAction(CONNECTIVITY_ACTION);\n      }\n      dispatcher.context.registerReceiver(this, filter);\n    }\n\n    void unregister() {\n      dispatcher.context.unregisterReceiver(this);\n    }\n\n    @Override public void onReceive(Context context, Intent intent) {\n      // On some versions of Android this may be called with a null Intent,\n      // also without extras (getExtras() == null), in such case we use defaults.\n      if (intent == null) {\n        return;\n      }\n      final String action = intent.getAction();\n      if (ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {\n        if (!intent.hasExtra(EXTRA_AIRPLANE_STATE)) {\n          return; // No airplane state, ignore it. Should we query Utils.isAirplaneModeOn?\n        }\n        dispatcher.dispatchAirplaneModeChange(intent.getBooleanExtra(EXTRA_AIRPLANE_STATE, false));\n      } else if (CONNECTIVITY_ACTION.equals(action)) {\n        ConnectivityManager connectivityManager = getService(context, CONNECTIVITY_SERVICE);\n        dispatcher.dispatchNetworkStateChange(connectivityManager.getActiveNetworkInfo());\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/DokitPicasso.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.net.Uri;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\nimport android.os.Process;\nimport android.widget.ImageView;\nimport android.widget.RemoteViews;\nimport java.io.File;\nimport java.lang.ref.ReferenceQueue;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.WeakHashMap;\nimport java.util.concurrent.ExecutorService;\n\nimport static android.os.Process.THREAD_PRIORITY_BACKGROUND;\nimport static com.didichuxing.doraemonkit.picasso.Action.RequestWeakReference;\nimport static com.didichuxing.doraemonkit.picasso.Dispatcher.HUNTER_BATCH_COMPLETE;\nimport static com.didichuxing.doraemonkit.picasso.Dispatcher.REQUEST_BATCH_RESUME;\nimport static com.didichuxing.doraemonkit.picasso.Dispatcher.REQUEST_GCED;\nimport static com.didichuxing.doraemonkit.picasso.MemoryPolicy.shouldReadFromMemoryCache;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.LoadedFrom.MEMORY;\nimport static com.didichuxing.doraemonkit.picasso.Utils.OWNER_MAIN;\nimport static com.didichuxing.doraemonkit.picasso.Utils.THREAD_LEAK_CLEANING_MS;\nimport static com.didichuxing.doraemonkit.picasso.Utils.THREAD_PREFIX;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_CANCELED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_COMPLETED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_ERRORED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_RESUMED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.checkMain;\nimport static com.didichuxing.doraemonkit.picasso.Utils.log;\n\n/**\n * Image downloading, transformation, and caching manager.\n * <p>\n * Use {@link #with(android.content.Context)} for the global singleton instance or construct your\n * own instance with {@link Builder}.\n */\n@Deprecated\npublic class DokitPicasso {\n\n  /** Callbacks for Picasso events. */\n  public interface Listener {\n    /**\n     * Invoked when an image has failed to load. This is useful for reporting image failures to a\n     * remote analytics service, for example.\n     */\n    void onImageLoadFailed(DokitPicasso picasso, Uri uri, Exception exception);\n  }\n\n  /**\n   * A transformer that is called immediately before every request is submitted. This can be used to\n   * modify any information about a request.\n   * <p>\n   * For example, if you use a CDN you can change the hostname for the image based on the current\n   * location of the user in order to get faster download speeds.\n   * <p>\n   * <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible\n   * way at any time.\n   */\n  public interface RequestTransformer {\n    /**\n     * Transform a request before it is submitted to be processed.\n     *\n     * @return The original request or a new request to replace it. Must not be null.\n     */\n    Request transformRequest(Request request);\n\n    /** A {@link RequestTransformer} which returns the original request. */\n    RequestTransformer IDENTITY = new RequestTransformer() {\n      @Override public Request transformRequest(Request request) {\n        return request;\n      }\n    };\n  }\n\n  /**\n   * The priority of a request.\n   *\n   * @see RequestCreator#priority(Priority)\n   */\n  public enum Priority {\n    LOW,\n    NORMAL,\n    HIGH\n  }\n\n  static final String TAG = \"Picasso\";\n  static final Handler HANDLER = new Handler(Looper.getMainLooper()) {\n    @Override public void handleMessage(Message msg) {\n      switch (msg.what) {\n        case HUNTER_BATCH_COMPLETE: {\n          @SuppressWarnings(\"unchecked\") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;\n          //noinspection ForLoopReplaceableByForEach\n          for (int i = 0, n = batch.size(); i < n; i++) {\n            BitmapHunter hunter = batch.get(i);\n            hunter.picasso.complete(hunter);\n          }\n          break;\n        }\n        case REQUEST_GCED: {\n          Action action = (Action) msg.obj;\n          if (action.getPicasso().loggingEnabled) {\n            log(OWNER_MAIN, VERB_CANCELED, action.request.logId(), \"target got garbage collected\");\n          }\n          action.picasso.cancelExistingRequest(action.getTarget());\n          break;\n        }\n        case REQUEST_BATCH_RESUME:\n          @SuppressWarnings(\"unchecked\") List<Action> batch = (List<Action>) msg.obj;\n          //noinspection ForLoopReplaceableByForEach\n          for (int i = 0, n = batch.size(); i < n; i++) {\n            Action action = batch.get(i);\n            action.picasso.resumeAction(action);\n          }\n          break;\n        default:\n          throw new AssertionError(\"Unknown handler message received: \" + msg.what);\n      }\n    }\n  };\n\n  static volatile DokitPicasso singleton = null;\n\n  private final Listener listener;\n  private final RequestTransformer requestTransformer;\n  private final CleanupThread cleanupThread;\n  private final List<RequestHandler> requestHandlers;\n\n  final Context context;\n  final Dispatcher dispatcher;\n  final Cache cache;\n  final Stats stats;\n  final Map<Object, Action> targetToAction;\n  final Map<ImageView, DeferredRequestCreator> targetToDeferredRequestCreator;\n  final ReferenceQueue<Object> referenceQueue;\n  final Bitmap.Config defaultBitmapConfig;\n\n  boolean indicatorsEnabled;\n  volatile boolean loggingEnabled;\n\n  boolean shutdown;\n\n  DokitPicasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,\n               RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,\n               Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {\n    this.context = context;\n    this.dispatcher = dispatcher;\n    this.cache = cache;\n    this.listener = listener;\n    this.requestTransformer = requestTransformer;\n    this.defaultBitmapConfig = defaultBitmapConfig;\n\n    int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.\n    int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);\n    List<RequestHandler> allRequestHandlers =\n        new ArrayList<RequestHandler>(builtInHandlers + extraCount);\n\n    // ResourceRequestHandler needs to be the first in the list to avoid\n    // forcing other RequestHandlers to perform null checks on request.uri\n    // to cover the (request.resourceId != 0) case.\n    allRequestHandlers.add(new ResourceRequestHandler(context));\n    if (extraRequestHandlers != null) {\n      allRequestHandlers.addAll(extraRequestHandlers);\n    }\n    allRequestHandlers.add(new ContactsPhotoRequestHandler(context));\n    allRequestHandlers.add(new MediaStoreRequestHandler(context));\n    allRequestHandlers.add(new ContentStreamRequestHandler(context));\n    allRequestHandlers.add(new AssetRequestHandler(context));\n    allRequestHandlers.add(new FileRequestHandler(context));\n    allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));\n    requestHandlers = Collections.unmodifiableList(allRequestHandlers);\n\n    this.stats = stats;\n    this.targetToAction = new WeakHashMap<Object, Action>();\n    this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>();\n    this.indicatorsEnabled = indicatorsEnabled;\n    this.loggingEnabled = loggingEnabled;\n    this.referenceQueue = new ReferenceQueue<Object>();\n    this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);\n    this.cleanupThread.start();\n  }\n\n  /** Cancel any existing requests for the specified target {@link ImageView}. */\n  public void cancelRequest(ImageView view) {\n    cancelExistingRequest(view);\n  }\n\n  /** Cancel any existing requests for the specified {@link Target} instance. */\n  public void cancelRequest(Target target) {\n    cancelExistingRequest(target);\n  }\n\n  /**\n   * Cancel any existing requests for the specified {@link RemoteViews} target with the given {@code\n   * viewId}.\n   */\n  public void cancelRequest(RemoteViews remoteViews, int viewId) {\n    cancelExistingRequest(new RemoteViewsAction.RemoteViewsTarget(remoteViews, viewId));\n  }\n\n  /**\n   * Cancel any existing requests with given tag. You can set a tag\n   * on new requests with {@link RequestCreator#tag(Object)}.\n   *\n   * @see RequestCreator#tag(Object)\n   */\n  public void cancelTag(Object tag) {\n    checkMain();\n    List<Action> actions = new ArrayList<Action>(targetToAction.values());\n    //noinspection ForLoopReplaceableByForEach\n    for (int i = 0, n = actions.size(); i < n; i++) {\n      Action action = actions.get(i);\n      if (action.getTag().equals(tag)) {\n        cancelExistingRequest(action.getTarget());\n      }\n    }\n  }\n\n  /**\n   * Pause existing requests with the given tag. Use {@link #resumeTag(Object)}\n   * to resume requests with the given tag.\n   *\n   * @see #resumeTag(Object)\n   * @see RequestCreator#tag(Object)\n   */\n  public void pauseTag(Object tag) {\n    dispatcher.dispatchPauseTag(tag);\n  }\n\n  /**\n   * Resume paused requests with the given tag. Use {@link #pauseTag(Object)}\n   * to pause requests with the given tag.\n   *\n   * @see #pauseTag(Object)\n   * @see RequestCreator#tag(Object)\n   */\n  public void resumeTag(Object tag) {\n    dispatcher.dispatchResumeTag(tag);\n  }\n\n  /**\n   * Start an image request using the specified URI.\n   * <p>\n   * Passing {@code null} as a {@code uri} will not trigger any request but will set a placeholder,\n   * if one is specified.\n   *\n   * @see #load(File)\n   * @see #load(String)\n   * @see #load(int)\n   */\n  public RequestCreator load(Uri uri) {\n    return new RequestCreator(this, uri, 0);\n  }\n\n  /**\n   * Start an image request using the specified path. This is a convenience method for calling\n   * {@link #load(Uri)}.\n   * <p>\n   * This path may be a remote URL, file resource (prefixed with {@code file:}), content resource\n   * (prefixed with {@code content:}), or android resource (prefixed with {@code\n   * android.resource:}.\n   * <p>\n   * Passing {@code null} as a {@code path} will not trigger any request but will set a\n   * placeholder, if one is specified.\n   *\n   * @see #load(Uri)\n   * @see #load(File)\n   * @see #load(int)\n   * @throws IllegalArgumentException if {@code path} is empty or blank string.\n   */\n  public RequestCreator load(String path) {\n    if (path == null) {\n      return new RequestCreator(this, null, 0);\n    }\n    if (path.trim().length() == 0) {\n      throw new IllegalArgumentException(\"Path must not be empty.\");\n    }\n    return load(Uri.parse(path));\n  }\n\n  /**\n   * Start an image request using the specified image file. This is a convenience method for\n   * calling {@link #load(Uri)}.\n   * <p>\n   * Passing {@code null} as a {@code file} will not trigger any request but will set a\n   * placeholder, if one is specified.\n   * <p>\n   * Equivalent to calling {@link #load(Uri) load(Uri.fromFile(file))}.\n   *\n   * @see #load(Uri)\n   * @see #load(String)\n   * @see #load(int)\n   */\n  public RequestCreator load(File file) {\n    if (file == null) {\n      return new RequestCreator(this, null, 0);\n    }\n    return load(Uri.fromFile(file));\n  }\n\n  /**\n   * Start an image request using the specified drawable resource ID.\n   *\n   * @see #load(Uri)\n   * @see #load(String)\n   * @see #load(File)\n   */\n  public RequestCreator load(int resourceId) {\n    if (resourceId == 0) {\n      throw new IllegalArgumentException(\"Resource ID must not be zero.\");\n    }\n    return new RequestCreator(this, null, resourceId);\n  }\n\n  /**\n   * Invalidate all memory cached images for the specified {@code uri}.\n   *\n   * @see #invalidate(String)\n   * @see #invalidate(File)\n   */\n  public void invalidate(Uri uri) {\n    if (uri == null) {\n      throw new IllegalArgumentException(\"uri == null\");\n    }\n    cache.clearKeyUri(uri.toString());\n  }\n\n  /**\n   * Invalidate all memory cached images for the specified {@code path}. You can also pass a\n   * {@linkplain RequestCreator#stableKey stable key}.\n   *\n   * @see #invalidate(Uri)\n   * @see #invalidate(File)\n   */\n  public void invalidate(String path) {\n    if (path == null) {\n      throw new IllegalArgumentException(\"path == null\");\n    }\n    invalidate(Uri.parse(path));\n  }\n\n  /**\n   * Invalidate all memory cached images for the specified {@code file}.\n   *\n   * @see #invalidate(Uri)\n   * @see #invalidate(String)\n   */\n  public void invalidate(File file) {\n    if (file == null) {\n      throw new IllegalArgumentException(\"file == null\");\n    }\n    invalidate(Uri.fromFile(file));\n  }\n\n  /**\n   * {@code true} if debug display, logging, and statistics are enabled.\n   * <p>\n   * @deprecated Use {@link #areIndicatorsEnabled()} and {@link #isLoggingEnabled()} instead.\n   */\n  @SuppressWarnings(\"UnusedDeclaration\") @Deprecated public boolean isDebugging() {\n    return areIndicatorsEnabled() && isLoggingEnabled();\n  }\n\n  /**\n   * Toggle whether debug display, logging, and statistics are enabled.\n   * <p>\n   * @deprecated Use {@link #setIndicatorsEnabled(boolean)} and {@link #setLoggingEnabled(boolean)}\n   * instead.\n   */\n  @SuppressWarnings(\"UnusedDeclaration\") @Deprecated public void setDebugging(boolean debugging) {\n    setIndicatorsEnabled(debugging);\n  }\n\n  /** Toggle whether to display debug indicators on images. */\n  @SuppressWarnings(\"UnusedDeclaration\") public void setIndicatorsEnabled(boolean enabled) {\n    indicatorsEnabled = enabled;\n  }\n\n  /** {@code true} if debug indicators should are displayed on images. */\n  @SuppressWarnings(\"UnusedDeclaration\") public boolean areIndicatorsEnabled() {\n    return indicatorsEnabled;\n  }\n\n  /**\n   * Toggle whether debug logging is enabled.\n   * <p>\n   * <b>WARNING:</b> Enabling this will result in excessive object allocation. This should be only\n   * be used for debugging Picasso behavior. Do NOT pass {@code BuildConfig.DEBUG}.\n   */\n  @SuppressWarnings(\"UnusedDeclaration\") // Public API.\n  public void setLoggingEnabled(boolean enabled) {\n    loggingEnabled = enabled;\n  }\n\n  /** {@code true} if debug logging is enabled. */\n  public boolean isLoggingEnabled() {\n    return loggingEnabled;\n  }\n\n  /**\n   * Creates a {@link StatsSnapshot} of the current stats for this instance.\n   * <p>\n   * <b>NOTE:</b> The snapshot may not always be completely up-to-date if requests are still in\n   * progress.\n   */\n  @SuppressWarnings(\"UnusedDeclaration\") public StatsSnapshot getSnapshot() {\n    return stats.createSnapshot();\n  }\n\n  /** Stops this instance from accepting further requests. */\n  public void shutdown() {\n    if (this == singleton) {\n      throw new UnsupportedOperationException(\"Default singleton instance cannot be shutdown.\");\n    }\n    if (shutdown) {\n      return;\n    }\n    cache.clear();\n    cleanupThread.shutdown();\n    stats.shutdown();\n    dispatcher.shutdown();\n    for (DeferredRequestCreator deferredRequestCreator : targetToDeferredRequestCreator.values()) {\n      deferredRequestCreator.cancel();\n    }\n    targetToDeferredRequestCreator.clear();\n    shutdown = true;\n  }\n\n  List<RequestHandler> getRequestHandlers() {\n    return requestHandlers;\n  }\n\n  Request transformRequest(Request request) {\n    Request transformed = requestTransformer.transformRequest(request);\n    if (transformed == null) {\n      throw new IllegalStateException(\"Request transformer \"\n          + requestTransformer.getClass().getCanonicalName()\n          + \" returned null for \"\n          + request);\n    }\n    return transformed;\n  }\n\n  void defer(ImageView view, DeferredRequestCreator request) {\n    targetToDeferredRequestCreator.put(view, request);\n  }\n\n  void enqueueAndSubmit(Action action) {\n    Object target = action.getTarget();\n    if (target != null && targetToAction.get(target) != action) {\n      // This will also check we are on the main thread.\n      cancelExistingRequest(target);\n      targetToAction.put(target, action);\n    }\n    submit(action);\n  }\n\n  void submit(Action action) {\n    dispatcher.dispatchSubmit(action);\n  }\n\n  Bitmap quickMemoryCacheCheck(String key) {\n    Bitmap cached = cache.get(key);\n    if (cached != null) {\n      stats.dispatchCacheHit();\n    } else {\n      stats.dispatchCacheMiss();\n    }\n    return cached;\n  }\n\n  void complete(BitmapHunter hunter) {\n    Action single = hunter.getAction();\n    List<Action> joined = hunter.getActions();\n\n    boolean hasMultiple = joined != null && !joined.isEmpty();\n    boolean shouldDeliver = single != null || hasMultiple;\n\n    if (!shouldDeliver) {\n      return;\n    }\n\n    Uri uri = hunter.getData().uri;\n    Exception exception = hunter.getException();\n    Bitmap result = hunter.getResult();\n    LoadedFrom from = hunter.getLoadedFrom();\n\n    if (single != null) {\n      deliverAction(result, from, single);\n    }\n\n    if (hasMultiple) {\n      //noinspection ForLoopReplaceableByForEach\n      for (int i = 0, n = joined.size(); i < n; i++) {\n        Action join = joined.get(i);\n        deliverAction(result, from, join);\n      }\n    }\n\n    if (listener != null && exception != null) {\n      listener.onImageLoadFailed(this, uri, exception);\n    }\n  }\n\n  void resumeAction(Action action) {\n    Bitmap bitmap = null;\n    if (shouldReadFromMemoryCache(action.memoryPolicy)) {\n      bitmap = quickMemoryCacheCheck(action.getKey());\n    }\n\n    if (bitmap != null) {\n      // Resumed action is cached, complete immediately.\n      deliverAction(bitmap, MEMORY, action);\n      if (loggingEnabled) {\n        log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), \"from \" + MEMORY);\n      }\n    } else {\n      // Re-submit the action to the executor.\n      enqueueAndSubmit(action);\n      if (loggingEnabled) {\n        log(OWNER_MAIN, VERB_RESUMED, action.request.logId());\n      }\n    }\n  }\n\n  private void deliverAction(Bitmap result, LoadedFrom from, Action action) {\n    if (action.isCancelled()) {\n      return;\n    }\n    if (!action.willReplay()) {\n      targetToAction.remove(action.getTarget());\n    }\n    if (result != null) {\n      if (from == null) {\n        throw new AssertionError(\"LoadedFrom cannot be null.\");\n      }\n      action.complete(result, from);\n      if (loggingEnabled) {\n        log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), \"from \" + from);\n      }\n    } else {\n      action.error();\n      if (loggingEnabled) {\n        log(OWNER_MAIN, VERB_ERRORED, action.request.logId());\n      }\n    }\n  }\n\n  private void cancelExistingRequest(Object target) {\n    checkMain();\n    Action action = targetToAction.remove(target);\n    if (action != null) {\n      action.cancel();\n      dispatcher.dispatchCancel(action);\n    }\n    if (target instanceof ImageView) {\n      ImageView targetImageView = (ImageView) target;\n      DeferredRequestCreator deferredRequestCreator =\n          targetToDeferredRequestCreator.remove(targetImageView);\n      if (deferredRequestCreator != null) {\n        deferredRequestCreator.cancel();\n      }\n    }\n  }\n\n  /**\n   * When the target of an action is weakly reachable but the request hasn't been canceled, it\n   * gets added to the reference queue. This thread empties the reference queue and cancels the\n   * request.\n   */\n  private static class CleanupThread extends Thread {\n    private final ReferenceQueue<Object> referenceQueue;\n    private final Handler handler;\n\n    CleanupThread(ReferenceQueue<Object> referenceQueue, Handler handler) {\n      this.referenceQueue = referenceQueue;\n      this.handler = handler;\n      setDaemon(true);\n      setName(THREAD_PREFIX + \"refQueue\");\n    }\n\n    @Override public void run() {\n      Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);\n      while (true) {\n        try {\n          // Prior to Android 5.0, even when there is no local variable, the result from\n          // remove() & obtainMessage() is kept as a stack local variable.\n          // We're forcing this reference to be cleared and replaced by looping every second\n          // when there is nothing to do.\n          // This behavior has been tested and reproduced with heap dumps.\n          RequestWeakReference<?> remove =\n              (RequestWeakReference<?>) referenceQueue.remove(THREAD_LEAK_CLEANING_MS);\n          Message message = handler.obtainMessage();\n          if (remove != null) {\n            message.what = REQUEST_GCED;\n            message.obj = remove.action;\n            handler.sendMessage(message);\n          } else {\n            message.recycle();\n          }\n        } catch (InterruptedException e) {\n          break;\n        } catch (final Exception e) {\n          handler.post(new Runnable() {\n            @Override public void run() {\n              throw new RuntimeException(e);\n            }\n          });\n          break;\n        }\n      }\n    }\n\n    void shutdown() {\n      interrupt();\n    }\n  }\n\n  /**\n   * The global default {@link DokitPicasso} instance.\n   * <p>\n   * This instance is automatically initialized with defaults that are suitable to most\n   * implementations.\n   * <ul>\n   * <li>LRU memory cache of 15% the available application RAM</li>\n   * <li>Disk cache of 2% storage space up to 50MB but no less than 5MB. (Note: this is only\n   * available on API 14+ <em>or</em> if you are using a standalone library that provides a disk\n   * cache on all API levels like OkHttp)</li>\n   * <li>Three download threads for disk and network access.</li>\n   * </ul>\n   * <p>\n   * If these settings do not meet the requirements of your application you can construct your own\n   * with full control over the configuration by using {@link DokitPicasso.Builder} to create a\n   * {@link DokitPicasso} instance. You can either use this directly or by setting it as the global\n   * instance with {@link #setSingletonInstance}.\n   */\n  public static DokitPicasso with(Context context) {\n    if (singleton == null) {\n      synchronized (DokitPicasso.class) {\n        if (singleton == null) {\n          singleton = new Builder(context).build();\n        }\n      }\n    }\n    return singleton;\n  }\n\n  /**\n   * Set the global instance returned from {@link #with}.\n   * <p>\n   * This method must be called before any calls to {@link #with} and may only be called once.\n   */\n  public static void setSingletonInstance(DokitPicasso picasso) {\n    synchronized (DokitPicasso.class) {\n      if (singleton != null) {\n        throw new IllegalStateException(\"Singleton instance already exists.\");\n      }\n      singleton = picasso;\n    }\n  }\n\n  /** Fluent API for creating {@link DokitPicasso} instances. */\n  @SuppressWarnings(\"UnusedDeclaration\") // Public API.\n  public static class Builder {\n    private final Context context;\n    private Downloader downloader;\n    private ExecutorService service;\n    private Cache cache;\n    private Listener listener;\n    private RequestTransformer transformer;\n    private List<RequestHandler> requestHandlers;\n    private Bitmap.Config defaultBitmapConfig;\n\n    private boolean indicatorsEnabled;\n    private boolean loggingEnabled;\n\n    /** Start building a new {@link DokitPicasso} instance. */\n    public Builder(Context context) {\n      if (context == null) {\n        throw new IllegalArgumentException(\"Context must not be null.\");\n      }\n      this.context = context.getApplicationContext();\n    }\n\n    /**\n     * Specify the default {@link Bitmap.Config} used when decoding images. This can be overridden\n     * on a per-request basis using {@link RequestCreator#config(Bitmap.Config) config(..)}.\n     */\n    public Builder defaultBitmapConfig(Bitmap.Config bitmapConfig) {\n      if (bitmapConfig == null) {\n        throw new IllegalArgumentException(\"Bitmap config must not be null.\");\n      }\n      this.defaultBitmapConfig = bitmapConfig;\n      return this;\n    }\n\n    /** Specify the {@link Downloader} that will be used for downloading images. */\n    public Builder downloader(Downloader downloader) {\n      if (downloader == null) {\n        throw new IllegalArgumentException(\"Downloader must not be null.\");\n      }\n      if (this.downloader != null) {\n        throw new IllegalStateException(\"Downloader already set.\");\n      }\n      this.downloader = downloader;\n      return this;\n    }\n\n    /**\n     * Specify the executor service for loading images in the background.\n     * <p>\n     * Note: Calling {@link DokitPicasso#shutdown() shutdown()} will not shutdown supplied executors.\n     */\n    public Builder executor(ExecutorService executorService) {\n      if (executorService == null) {\n        throw new IllegalArgumentException(\"Executor service must not be null.\");\n      }\n      if (this.service != null) {\n        throw new IllegalStateException(\"Executor service already set.\");\n      }\n      this.service = executorService;\n      return this;\n    }\n\n    /** Specify the memory cache used for the most recent images. */\n    public Builder memoryCache(Cache memoryCache) {\n      if (memoryCache == null) {\n        throw new IllegalArgumentException(\"Memory cache must not be null.\");\n      }\n      if (this.cache != null) {\n        throw new IllegalStateException(\"Memory cache already set.\");\n      }\n      this.cache = memoryCache;\n      return this;\n    }\n\n    /** Specify a listener for interesting events. */\n    public Builder listener(Listener listener) {\n      if (listener == null) {\n        throw new IllegalArgumentException(\"Listener must not be null.\");\n      }\n      if (this.listener != null) {\n        throw new IllegalStateException(\"Listener already set.\");\n      }\n      this.listener = listener;\n      return this;\n    }\n\n    /**\n     * Specify a transformer for all incoming requests.\n     * <p>\n     * <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible\n     * way at any time.\n     */\n    public Builder requestTransformer(RequestTransformer transformer) {\n      if (transformer == null) {\n        throw new IllegalArgumentException(\"Transformer must not be null.\");\n      }\n      if (this.transformer != null) {\n        throw new IllegalStateException(\"Transformer already set.\");\n      }\n      this.transformer = transformer;\n      return this;\n    }\n\n    /** Register a {@link RequestHandler}. */\n    public Builder addRequestHandler(RequestHandler requestHandler) {\n      if (requestHandler == null) {\n        throw new IllegalArgumentException(\"RequestHandler must not be null.\");\n      }\n      if (requestHandlers == null) {\n        requestHandlers = new ArrayList<RequestHandler>();\n      }\n      if (requestHandlers.contains(requestHandler)) {\n        throw new IllegalStateException(\"RequestHandler already registered.\");\n      }\n      requestHandlers.add(requestHandler);\n      return this;\n    }\n\n    /**\n     * @deprecated Use {@link #indicatorsEnabled(boolean)} instead.\n     * Whether debugging is enabled or not.\n     */\n    @Deprecated public Builder debugging(boolean debugging) {\n      return indicatorsEnabled(debugging);\n    }\n\n    /** Toggle whether to display debug indicators on images. */\n    public Builder indicatorsEnabled(boolean enabled) {\n      this.indicatorsEnabled = enabled;\n      return this;\n    }\n\n    /**\n     * Toggle whether debug logging is enabled.\n     * <p>\n     * <b>WARNING:</b> Enabling this will result in excessive object allocation. This should be only\n     * be used for debugging purposes. Do NOT pass {@code BuildConfig.DEBUG}.\n     */\n    public Builder loggingEnabled(boolean enabled) {\n      this.loggingEnabled = enabled;\n      return this;\n    }\n\n    /** Create the {@link DokitPicasso} instance. */\n    public DokitPicasso build() {\n      Context context = this.context;\n\n      if (downloader == null) {\n        downloader = Utils.createDefaultDownloader(context);\n      }\n      if (cache == null) {\n        cache = new LruCache(context);\n      }\n      if (service == null) {\n        service = new PicassoExecutorService();\n      }\n      if (transformer == null) {\n        transformer = RequestTransformer.IDENTITY;\n      }\n\n      Stats stats = new Stats(cache);\n\n      Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);\n\n      return new DokitPicasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,\n          defaultBitmapConfig, indicatorsEnabled, loggingEnabled);\n    }\n  }\n\n  /** Describes where the image was loaded from. */\n  public enum LoadedFrom {\n    MEMORY(Color.GREEN),\n    DISK(Color.BLUE),\n    NETWORK(Color.RED);\n\n    final int debugColor;\n\n    private LoadedFrom(int debugColor) {\n      this.debugColor = debugColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/Downloader.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.graphics.Bitmap;\nimport android.net.Uri;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/** A mechanism to load images from external resources such as a disk cache and/or the internet. */\npublic interface Downloader {\n  /**\n   * Download the specified image {@code url} from the internet.\n   *\n   * @param uri Remote image URL.\n   * @param networkPolicy The {@link com.didichuxing.doraemonkit.picasso.NetworkPolicy} used for this request.\n   * @return {@link Response} containing either a {@link Bitmap} representation of the request or an\n   * {@link InputStream} for the image data. {@code null} can be returned to indicate a problem\n   * loading the bitmap.\n   * @throws IOException if the requested URL cannot successfully be loaded.\n   */\n  Response load(Uri uri, int networkPolicy) throws IOException;\n\n  /**\n   * Allows to perform a clean up for this {@link Downloader} including closing the disk cache and\n   * other resources.\n   */\n  void shutdown();\n\n  /** Thrown for non-2XX responses. */\n  class ResponseException extends IOException {\n    final boolean localCacheOnly;\n    final int responseCode;\n\n    public ResponseException(String message, int networkPolicy, int responseCode) {\n      super(message);\n      this.localCacheOnly = NetworkPolicy.isOfflineOnly(networkPolicy);\n      this.responseCode = responseCode;\n    }\n  }\n\n  /** Response stream or bitmap and info. */\n  class Response {\n    final InputStream stream;\n    final Bitmap bitmap;\n    final boolean cached;\n    final long contentLength;\n\n    /**\n     * Response image and info.\n     *\n     * @param bitmap Image.\n     * @param loadedFromCache {@code true} if the source of the image is from a local disk cache.\n     * @deprecated Use {@link com.didichuxing.doraemonkit.picasso.RequestHandler} for directly loading {@link Bitmap} instances.\n     */\n    @Deprecated\n    public Response(Bitmap bitmap, boolean loadedFromCache) {\n      if (bitmap == null) {\n        throw new IllegalArgumentException(\"Bitmap may not be null.\");\n      }\n      this.stream = null;\n      this.bitmap = bitmap;\n      this.cached = loadedFromCache;\n      this.contentLength = -1;\n    }\n\n    /**\n     * Response stream and info.\n     *\n     * @param stream Image data stream.\n     * @param loadedFromCache {@code true} if the source of the stream is from a local disk cache.\n     * @deprecated Use {@link Response#Response(java.io.InputStream, boolean, long)} instead.\n     */\n    @Deprecated @SuppressWarnings(\"UnusedDeclaration\")\n    public Response(InputStream stream, boolean loadedFromCache) {\n      this(stream, loadedFromCache, -1);\n    }\n\n    /**\n     * Response image and info.\n     *\n     * @param bitmap Image.\n     * @param loadedFromCache {@code true} if the source of the image is from a local disk cache.\n     * @param contentLength The content length of the response, typically derived by the\n     * {@code Content-Length} HTTP header.\n     * @deprecated The {@code contentLength} argument value is ignored. Use {@link #Response(Bitmap,\n     * boolean)}.\n     */\n    @Deprecated @SuppressWarnings(\"UnusedDeclaration\")\n    public Response(Bitmap bitmap, boolean loadedFromCache, long contentLength) {\n      this(bitmap, loadedFromCache);\n    }\n\n    /**\n     * Response stream and info.\n     *\n     * @param stream Image data stream.\n     * @param loadedFromCache {@code true} if the source of the stream is from a local disk cache.\n     * @param contentLength The content length of the response, typically derived by the\n     * {@code Content-Length} HTTP header.\n     */\n    public Response(InputStream stream, boolean loadedFromCache, long contentLength) {\n      if (stream == null) {\n        throw new IllegalArgumentException(\"Stream may not be null.\");\n      }\n      this.stream = stream;\n      this.bitmap = null;\n      this.cached = loadedFromCache;\n      this.contentLength = contentLength;\n    }\n\n    /**\n     * Input stream containing image data.\n     * <p>\n     * If this returns {@code null}, image data will be available via {@link #getBitmap()}.\n     */\n    public InputStream getInputStream() {\n      return stream;\n    }\n\n    /**\n     * Bitmap representing the image.\n     * <p>\n     * If this returns {@code null}, image data will be available via {@link #getInputStream()}.\n     *\n     * @deprecated Use {@link RequestHandler} for directly loading {@link Bitmap} instances.\n     */\n    @Deprecated\n    public Bitmap getBitmap() {\n      return bitmap;\n    }\n\n    /** Content length of the response. Only valid when used with {@link #getInputStream()}. */\n    public long getContentLength() {\n      return contentLength;\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/FetchAction.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.graphics.Bitmap;\n\nclass FetchAction extends Action<Object> {\n\n  private final Object target;\n  private Callback callback;\n\n  FetchAction(DokitPicasso picasso, Request data, int memoryPolicy, int networkPolicy, Object tag,\n              String key, Callback callback) {\n    super(picasso, null, data, memoryPolicy, networkPolicy, 0, null, key, tag, false);\n    this.target = new Object();\n    this.callback = callback;\n  }\n\n  @Override void complete(Bitmap result, DokitPicasso.LoadedFrom from) {\n    if (callback != null) {\n      callback.onSuccess();\n    }\n  }\n\n  @Override void error() {\n    if (callback != null) {\n      callback.onError();\n    }\n  }\n\n  @Override void cancel() {\n    super.cancel();\n    callback = null;\n  }\n\n  @Override Object getTarget() {\n    return target;\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/FileRequestHandler.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.content.Context;\nimport android.media.ExifInterface;\nimport android.net.Uri;\nimport java.io.IOException;\n\nimport static android.content.ContentResolver.SCHEME_FILE;\nimport static android.media.ExifInterface.ORIENTATION_NORMAL;\nimport static android.media.ExifInterface.ORIENTATION_ROTATE_180;\nimport static android.media.ExifInterface.ORIENTATION_ROTATE_270;\nimport static android.media.ExifInterface.ORIENTATION_ROTATE_90;\nimport static android.media.ExifInterface.TAG_ORIENTATION;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.LoadedFrom.DISK;\n\nclass FileRequestHandler extends ContentStreamRequestHandler {\n\n  FileRequestHandler(Context context) {\n    super(context);\n  }\n\n  @Override public boolean canHandleRequest(Request data) {\n    return SCHEME_FILE.equals(data.uri.getScheme());\n  }\n\n  @Override public Result load(Request request, int networkPolicy) throws IOException {\n    return new Result(null, getInputStream(request), DISK, getFileExifRotation(request.uri));\n  }\n\n  static int getFileExifRotation(Uri uri) throws IOException {\n    ExifInterface exifInterface = new ExifInterface(uri.getPath());\n    int orientation = exifInterface.getAttributeInt(TAG_ORIENTATION, ORIENTATION_NORMAL);\n    switch (orientation) {\n      case ORIENTATION_ROTATE_90:\n        return 90;\n      case ORIENTATION_ROTATE_180:\n        return 180;\n      case ORIENTATION_ROTATE_270:\n        return 270;\n      default:\n        return 0;\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/GetAction.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.graphics.Bitmap;\n\nclass GetAction extends Action<Void> {\n  GetAction(DokitPicasso picasso, Request data, int memoryPolicy, int networkPolicy, Object tag,\n            String key) {\n    super(picasso, null, data, memoryPolicy, networkPolicy, 0, null, key, tag, false);\n  }\n\n  @Override void complete(Bitmap result, DokitPicasso.LoadedFrom from) {\n  }\n\n  @Override public void error() {\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/ImageViewAction.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.widget.ImageView;\n\nclass ImageViewAction extends Action<ImageView> {\n\n  Callback callback;\n\n  ImageViewAction(DokitPicasso picasso, ImageView imageView, Request data, int memoryPolicy,\n                  int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag,\n                  Callback callback, boolean noFade) {\n    super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key,\n        tag, noFade);\n    this.callback = callback;\n  }\n\n  @Override public void complete(Bitmap result, DokitPicasso.LoadedFrom from) {\n    if (result == null) {\n      throw new AssertionError(\n          String.format(\"Attempted to complete action with no result!\\n%s\", this));\n    }\n\n    ImageView target = this.target.get();\n    if (target == null) {\n      return;\n    }\n\n    Context context = picasso.context;\n    boolean indicatorsEnabled = picasso.indicatorsEnabled;\n    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);\n\n    if (callback != null) {\n      callback.onSuccess();\n    }\n  }\n\n  @Override public void error() {\n    ImageView target = this.target.get();\n    if (target == null) {\n      return;\n    }\n    if (errorResId != 0) {\n      target.setImageResource(errorResId);\n    } else if (errorDrawable != null) {\n      target.setImageDrawable(errorDrawable);\n    }\n\n    if (callback != null) {\n      callback.onError();\n    }\n  }\n\n  @Override void cancel() {\n    super.cancel();\n    if (callback != null) {\n      callback = null;\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/LruCache.java",
    "content": "/*\n * Copyright (C) 2011 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/** A memory cache which uses a least-recently used eviction policy. */\npublic class LruCache implements Cache {\n  final LinkedHashMap<String, Bitmap> map;\n  private final int maxSize;\n\n  private int size;\n  private int putCount;\n  private int evictionCount;\n  private int hitCount;\n  private int missCount;\n\n  /** Create a cache using an appropriate portion of the available RAM as the maximum size. */\n  public LruCache(Context context) {\n    this(Utils.calculateMemoryCacheSize(context));\n  }\n\n  /** Create a cache with a given maximum size in bytes. */\n  public LruCache(int maxSize) {\n    if (maxSize <= 0) {\n      throw new IllegalArgumentException(\"Max size must be positive.\");\n    }\n    this.maxSize = maxSize;\n    this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);\n  }\n\n  @Override public Bitmap get(String key) {\n    if (key == null) {\n      throw new NullPointerException(\"key == null\");\n    }\n\n    Bitmap mapValue;\n    synchronized (this) {\n      mapValue = map.get(key);\n      if (mapValue != null) {\n        hitCount++;\n        return mapValue;\n      }\n      missCount++;\n    }\n\n    return null;\n  }\n\n  @Override public void set(String key, Bitmap bitmap) {\n    if (key == null || bitmap == null) {\n      throw new NullPointerException(\"key == null || bitmap == null\");\n    }\n\n    Bitmap previous;\n    synchronized (this) {\n      putCount++;\n      size += Utils.getBitmapBytes(bitmap);\n      previous = map.put(key, bitmap);\n      if (previous != null) {\n        size -= Utils.getBitmapBytes(previous);\n      }\n    }\n\n    trimToSize(maxSize);\n  }\n\n  private void trimToSize(int maxSize) {\n    while (true) {\n      String key;\n      Bitmap value;\n      synchronized (this) {\n        if (size < 0 || (map.isEmpty() && size != 0)) {\n          throw new IllegalStateException(\n              getClass().getName() + \".sizeOf() is reporting inconsistent results!\");\n        }\n\n        if (size <= maxSize || map.isEmpty()) {\n          break;\n        }\n\n        Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();\n        key = toEvict.getKey();\n        value = toEvict.getValue();\n        map.remove(key);\n        size -= Utils.getBitmapBytes(value);\n        evictionCount++;\n      }\n    }\n  }\n\n  /** Clear the cache. */\n  public final void evictAll() {\n    trimToSize(-1); // -1 will evict 0-sized elements\n  }\n\n  @Override public final synchronized int size() {\n    return size;\n  }\n\n  @Override public final synchronized int maxSize() {\n    return maxSize;\n  }\n\n  @Override public final synchronized void clear() {\n    evictAll();\n  }\n\n  @Override public final synchronized void clearKeyUri(String uri) {\n    boolean sizeChanged = false;\n    int uriLength = uri.length();\n    for (Iterator<Map.Entry<String, Bitmap>> i = map.entrySet().iterator(); i.hasNext();) {\n      Map.Entry<String, Bitmap> entry = i.next();\n      String key = entry.getKey();\n      Bitmap value = entry.getValue();\n      int newlineIndex = key.indexOf(Utils.KEY_SEPARATOR);\n      if (newlineIndex == uriLength && key.substring(0, newlineIndex).equals(uri)) {\n        i.remove();\n        size -= Utils.getBitmapBytes(value);\n        sizeChanged = true;\n      }\n    }\n    if (sizeChanged) {\n      trimToSize(maxSize);\n    }\n  }\n\n  /** Returns the number of times {@link #get} returned a value. */\n  public final synchronized int hitCount() {\n    return hitCount;\n  }\n\n  /** Returns the number of times {@link #get} returned {@code null}. */\n  public final synchronized int missCount() {\n    return missCount;\n  }\n\n  /** Returns the number of times {@link #set(String, Bitmap)} was called. */\n  public final synchronized int putCount() {\n    return putCount;\n  }\n\n  /** Returns the number of values that have been evicted. */\n  public final synchronized int evictionCount() {\n    return evictionCount;\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/MarkableInputStream.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport java.io.BufferedInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * An input stream wrapper that supports unlimited independent cursors for\n * marking and resetting. Each cursor is a token, and it's the caller's\n * responsibility to keep track of these.\n */\nfinal class MarkableInputStream extends InputStream {\n  private static final int DEFAULT_BUFFER_SIZE = 4096;\n\n  private final InputStream in;\n\n  private long offset;\n  private long reset;\n  private long limit;\n  private long defaultMark = -1;\n\n  public MarkableInputStream(InputStream in) {\n    this(in, DEFAULT_BUFFER_SIZE);\n  }\n\n  public MarkableInputStream(InputStream in, int size) {\n    if (!in.markSupported()) {\n      in = new BufferedInputStream(in, size);\n    }\n    this.in = in;\n  }\n\n  /** Marks this place in the stream so we can reset back to it later. */\n  @Override public void mark(int readLimit) {\n    defaultMark = savePosition(readLimit);\n  }\n\n  /**\n   * Returns an opaque token representing the current position in the stream.\n   * Call {@link #reset(long)} to return to this position in the stream later.\n   * It is an error to call {@link #reset(long)} after consuming more than\n   * {@code readLimit} bytes from this stream.\n   */\n  public long savePosition(int readLimit) {\n    long offsetLimit = offset + readLimit;\n    if (limit < offsetLimit) {\n      setLimit(offsetLimit);\n    }\n    return offset;\n  }\n\n  /**\n   * Makes sure that the underlying stream can backtrack the full range from\n   * {@code reset} thru {@code limit}. Since we can't call {@code mark()}\n   * without also adjusting the reset-to-position on the underlying stream this\n   * method resets first and then marks the union of the two byte ranges. On\n   * buffered streams this additional cursor motion shouldn't result in any\n   * additional I/O.\n   */\n  private void setLimit(long limit) {\n    try {\n      if (reset < offset && offset <= this.limit) {\n        in.reset();\n        in.mark((int) (limit - reset));\n        skip(reset, offset);\n      } else {\n        reset = offset;\n        in.mark((int) (limit - offset));\n      }\n      this.limit = limit;\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Unable to mark: \" + e);\n    }\n  }\n\n  /** Resets the stream to the most recent {@link #mark mark}. */\n  @Override public void reset() throws IOException {\n    reset(defaultMark);\n  }\n\n  /** Resets the stream to the position recorded by {@code token}. */\n  public void reset(long token) throws IOException {\n    if (offset > limit || token < reset) {\n      throw new IOException(\"Cannot reset\");\n    }\n    in.reset();\n    skip(reset, token);\n    offset = token;\n  }\n\n  /** Skips {@code target - current} bytes and returns. */\n  private void skip(long current, long target) throws IOException {\n    while (current < target) {\n      long skipped = in.skip(target - current);\n      if (skipped == 0) {\n        if (read() == -1) {\n          break; // EOF\n        } else {\n          skipped = 1;\n        }\n      }\n      current += skipped;\n    }\n  }\n\n  @Override public int read() throws IOException {\n    int result = in.read();\n    if (result != -1) {\n      offset++;\n    }\n    return result;\n  }\n\n  @Override public int read(byte[] buffer) throws IOException {\n    int count = in.read(buffer);\n    if (count != -1) {\n      offset += count;\n    }\n    return count;\n  }\n\n  @Override public int read(byte[] buffer, int offset, int length) throws IOException {\n    int count = in.read(buffer, offset, length);\n    if (count != -1) {\n      this.offset += count;\n    }\n    return count;\n  }\n\n  @Override public long skip(long byteCount) throws IOException {\n    long skipped = in.skip(byteCount);\n    offset += skipped;\n    return skipped;\n  }\n\n  @Override public int available() throws IOException {\n    return in.available();\n  }\n\n  @Override public void close() throws IOException {\n    in.close();\n  }\n\n  @Override public boolean markSupported() {\n    return in.markSupported();\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/MediaStoreRequestHandler.java",
    "content": "/*\n * Copyright (C) 2014 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.net.Uri;\nimport android.provider.MediaStore;\nimport java.io.IOException;\n\nimport static android.content.ContentResolver.SCHEME_CONTENT;\nimport static android.content.ContentUris.parseId;\nimport static android.provider.MediaStore.Images;\nimport static android.provider.MediaStore.Video;\nimport static android.provider.MediaStore.Images.Thumbnails.FULL_SCREEN_KIND;\nimport static android.provider.MediaStore.Images.Thumbnails.MICRO_KIND;\nimport static android.provider.MediaStore.Images.Thumbnails.MINI_KIND;\nimport static com.didichuxing.doraemonkit.picasso.MediaStoreRequestHandler.PicassoKind.FULL;\nimport static com.didichuxing.doraemonkit.picasso.MediaStoreRequestHandler.PicassoKind.MICRO;\nimport static com.didichuxing.doraemonkit.picasso.MediaStoreRequestHandler.PicassoKind.MINI;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.LoadedFrom.DISK;\n\nclass MediaStoreRequestHandler extends ContentStreamRequestHandler {\n  private static final String[] CONTENT_ORIENTATION = new String[] {\n      Images.ImageColumns.ORIENTATION\n  };\n\n  MediaStoreRequestHandler(Context context) {\n    super(context);\n  }\n\n  @Override public boolean canHandleRequest(Request data) {\n    final Uri uri = data.uri;\n    return (SCHEME_CONTENT.equals(uri.getScheme())\n            && MediaStore.AUTHORITY.equals(uri.getAuthority()));\n  }\n\n  @Override public Result load(Request request, int networkPolicy) throws IOException {\n    ContentResolver contentResolver = context.getContentResolver();\n    int exifOrientation = getExifOrientation(contentResolver, request.uri);\n\n    String mimeType = contentResolver.getType(request.uri);\n    boolean isVideo = mimeType != null && mimeType.startsWith(\"video/\");\n\n    if (request.hasSize()) {\n      PicassoKind picassoKind = getPicassoKind(request.targetWidth, request.targetHeight);\n      if (!isVideo && picassoKind == FULL) {\n        return new Result(null, getInputStream(request), DISK, exifOrientation);\n      }\n\n      long id = parseId(request.uri);\n\n      BitmapFactory.Options options = createBitmapOptions(request);\n      options.inJustDecodeBounds = true;\n\n      calculateInSampleSize(request.targetWidth, request.targetHeight, picassoKind.width,\n              picassoKind.height, options, request);\n\n      Bitmap bitmap;\n\n      if (isVideo) {\n        // Since MediaStore doesn't provide the full screen kind thumbnail, we use the mini kind\n        // instead which is the largest thumbnail size can be fetched from MediaStore.\n        int kind = (picassoKind == FULL) ? Video.Thumbnails.MINI_KIND : picassoKind.androidKind;\n        bitmap = Video.Thumbnails.getThumbnail(contentResolver, id, kind, options);\n      } else {\n        bitmap =\n            Images.Thumbnails.getThumbnail(contentResolver, id, picassoKind.androidKind, options);\n      }\n\n      if (bitmap != null) {\n        return new Result(bitmap, null, DISK, exifOrientation);\n      }\n    }\n\n    return new Result(null, getInputStream(request), DISK, exifOrientation);\n  }\n\n  static PicassoKind getPicassoKind(int targetWidth, int targetHeight) {\n    if (targetWidth <= MICRO.width && targetHeight <= MICRO.height) {\n      return MICRO;\n    } else if (targetWidth <= MINI.width && targetHeight <= MINI.height) {\n      return MINI;\n    }\n    return FULL;\n  }\n\n  static int getExifOrientation(ContentResolver contentResolver, Uri uri) {\n    Cursor cursor = null;\n    try {\n      cursor = contentResolver.query(uri, CONTENT_ORIENTATION, null, null, null);\n      if (cursor == null || !cursor.moveToFirst()) {\n        return 0;\n      }\n      return cursor.getInt(0);\n    } catch (RuntimeException ignored) {\n      // If the orientation column doesn't exist, assume no rotation.\n      return 0;\n    } finally {\n      if (cursor != null) {\n        cursor.close();\n      }\n    }\n  }\n\n  enum PicassoKind {\n    MICRO(MICRO_KIND, 96, 96),\n    MINI(MINI_KIND, 512, 384),\n    FULL(FULL_SCREEN_KIND, -1, -1);\n\n    final int androidKind;\n    final int width;\n    final int height;\n\n    PicassoKind(int androidKind, int width, int height) {\n      this.androidKind = androidKind;\n      this.width = width;\n      this.height = height;\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/MemoryPolicy.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\n/**\n * Designates the policy to use when dealing with memory cache.\n */\n@SuppressWarnings(\"PointlessBitwiseExpression\")\npublic enum MemoryPolicy {\n\n    /**\n     * Skips memory cache lookup when processing a request.\n     */\n    NO_CACHE(1 << 0),\n    /**\n     * Skips storing the final result into memory cache. Useful for one-off requests\n     * to avoid evicting other bitmaps from the cache.\n     */\n    NO_STORE(1 << 1);\n\n    static boolean shouldReadFromMemoryCache(int memoryPolicy) {\n        return (memoryPolicy & MemoryPolicy.NO_CACHE.index) == 0;\n    }\n\n    static boolean shouldWriteToMemoryCache(int memoryPolicy) {\n        return (memoryPolicy & MemoryPolicy.NO_STORE.index) == 0;\n    }\n\n    final int index;\n\n    private MemoryPolicy(int index) {\n        this.index = index;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/NetworkPolicy.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\n/** Designates the policy to use for network requests. */\n@SuppressWarnings(\"PointlessBitwiseExpression\")\npublic enum NetworkPolicy {\n\n  /** Skips checking the disk cache and forces loading through the network. */\n  NO_CACHE(1 << 0),\n\n  /**\n   * Skips storing the result into the disk cache.\n   * <p>\n   * <em>Note</em>: At this time this is only supported if you are using OkHttp.\n   */\n  NO_STORE(1 << 1),\n\n  /** Forces the request through the disk cache only, skipping network. */\n  OFFLINE(1 << 2);\n\n  public static boolean shouldReadFromDiskCache(int networkPolicy) {\n    return (networkPolicy & NetworkPolicy.NO_CACHE.index) == 0;\n  }\n\n  public static boolean shouldWriteToDiskCache(int networkPolicy) {\n    return (networkPolicy & NetworkPolicy.NO_STORE.index) == 0;\n  }\n\n  public static boolean isOfflineOnly(int networkPolicy) {\n    return (networkPolicy & NetworkPolicy.OFFLINE.index) != 0;\n  }\n\n  final int index;\n\n  private NetworkPolicy(int index) {\n    this.index = index;\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/NetworkRequestHandler.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.graphics.Bitmap;\nimport android.net.NetworkInfo;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport static com.didichuxing.doraemonkit.picasso.Downloader.Response;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.LoadedFrom.DISK;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.LoadedFrom.NETWORK;\n\nclass NetworkRequestHandler extends RequestHandler {\n  static final int RETRY_COUNT = 2;\n\n  private static final String SCHEME_HTTP = \"http\";\n  private static final String SCHEME_HTTPS = \"https\";\n\n  private final Downloader downloader;\n  private final Stats stats;\n\n  public NetworkRequestHandler(Downloader downloader, Stats stats) {\n    this.downloader = downloader;\n    this.stats = stats;\n  }\n\n  @Override public boolean canHandleRequest(Request data) {\n    String scheme = data.uri.getScheme();\n    return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));\n  }\n\n  @Override public Result load(Request request, int networkPolicy) throws IOException {\n    Response response = downloader.load(request.uri, request.networkPolicy);\n    if (response == null) {\n      return null;\n    }\n\n    DokitPicasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;\n\n    Bitmap bitmap = response.getBitmap();\n    if (bitmap != null) {\n      return new Result(bitmap, loadedFrom);\n    }\n\n    InputStream is = response.getInputStream();\n    if (is == null) {\n      return null;\n    }\n    // Sometimes response content length is zero when requests are being replayed. Haven't found\n    // root cause to this but retrying the request seems safe to do so.\n    if (loadedFrom == DISK && response.getContentLength() == 0) {\n      Utils.closeQuietly(is);\n      throw new ContentLengthException(\"Received response with 0 content-length header.\");\n    }\n    if (loadedFrom == NETWORK && response.getContentLength() > 0) {\n      stats.dispatchDownloadFinished(response.getContentLength());\n    }\n    return new Result(is, loadedFrom);\n  }\n\n  @Override int getRetryCount() {\n    return RETRY_COUNT;\n  }\n\n  @Override boolean shouldRetry(boolean airplaneMode, NetworkInfo info) {\n    return info == null || info.isConnected();\n  }\n\n  @Override boolean supportsReplay() {\n    return true;\n  }\n\n  static class ContentLengthException extends IOException {\n    public ContentLengthException(String message) {\n      super(message);\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/OkHttpDownloader.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.content.Context;\nimport android.net.Uri;\n\nimport com.squareup.okhttp.CacheControl;\nimport com.squareup.okhttp.OkHttpClient;\nimport com.squareup.okhttp.ResponseBody;\nimport com.squareup.okhttp.Request;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * A {@link Downloader} which uses OkHttp to download images.\n */\npublic class OkHttpDownloader implements Downloader {\n    private static OkHttpClient defaultOkHttpClient() {\n        OkHttpClient client = new OkHttpClient();\n        client.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);\n        client.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);\n        client.setWriteTimeout(Utils.DEFAULT_WRITE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);\n        return client;\n    }\n\n    private final OkHttpClient client;\n\n    /**\n     * Create new downloader that uses OkHttp. This will install an image cache into your application\n     * cache directory.\n     */\n    public OkHttpDownloader(final Context context) {\n        this(Utils.createDefaultCacheDir(context));\n    }\n\n    /**\n     * Create new downloader that uses OkHttp. This will install an image cache into the specified\n     * directory.\n     *\n     * @param cacheDir The directory in which the cache should be stored\n     */\n    public OkHttpDownloader(final File cacheDir) {\n        this(cacheDir, Utils.calculateDiskCacheSize(cacheDir));\n    }\n\n    /**\n     * Create new downloader that uses OkHttp. This will install an image cache into your application\n     * cache directory.\n     *\n     * @param maxSize The size limit for the cache.\n     */\n    public OkHttpDownloader(final Context context, final long maxSize) {\n        this(Utils.createDefaultCacheDir(context), maxSize);\n    }\n\n    /**\n     * Create new downloader that uses OkHttp. This will install an image cache into the specified\n     * directory.\n     *\n     * @param cacheDir The directory in which the cache should be stored\n     * @param maxSize  The size limit for the cache.\n     */\n    public OkHttpDownloader(final File cacheDir, final long maxSize) {\n        this(defaultOkHttpClient());\n        client.setCache(new com.squareup.okhttp.Cache(cacheDir, maxSize));\n    }\n\n    /**\n     * Create a new downloader that uses the specified OkHttp instance. A response cache will not be\n     * automatically configured.\n     */\n    public OkHttpDownloader(OkHttpClient client) {\n        this.client = client;\n    }\n\n    protected final OkHttpClient getClient() {\n        return client;\n    }\n\n    @Override\n    public Response load(Uri uri, int networkPolicy) throws IOException {\n        CacheControl cacheControl = null;\n        if (networkPolicy != 0) {\n            if (NetworkPolicy.isOfflineOnly(networkPolicy)) {\n                cacheControl = CacheControl.FORCE_CACHE;\n            } else {\n                CacheControl.Builder builder = new CacheControl.Builder();\n                if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {\n                    builder.noCache();\n                }\n                if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {\n                    builder.noStore();\n                }\n                cacheControl = builder.build();\n            }\n        }\n\n        Request.Builder builder = new Request.Builder().url(uri.toString());\n        if (cacheControl != null) {\n            builder.cacheControl(cacheControl);\n        }\n\n        com.squareup.okhttp.Response response = client.newCall(builder.build()).execute();\n        int responseCode = response.code();\n        if (responseCode >= 300) {\n            response.body().close();\n            throw new ResponseException(responseCode + \" \" + response.message(), networkPolicy,\n                    responseCode);\n        }\n\n        boolean fromCache = response.cacheResponse() != null;\n\n        ResponseBody responseBody = response.body();\n        return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());\n    }\n\n    @Override\n    public void shutdown() {\n        com.squareup.okhttp.Cache cache = client.getCache();\n        if (cache != null) {\n            try {\n                cache.close();\n            } catch (IOException ignored) {\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/PicassoDrawable.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.ColorFilter;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.Point;\nimport android.graphics.Rect;\nimport android.graphics.drawable.AnimationDrawable;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.os.SystemClock;\nimport android.widget.ImageView;\n\nimport static android.graphics.Color.WHITE;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.LoadedFrom.MEMORY;\n\nfinal class PicassoDrawable extends BitmapDrawable {\n  // Only accessed from main thread.\n  private static final Paint DEBUG_PAINT = new Paint();\n  private static final float FADE_DURATION = 200f; //ms\n\n  /**\n   * Create or update the drawable on the target {@link ImageView} to display the supplied bitmap\n   * image.\n   */\n  static void setBitmap(ImageView target, Context context, Bitmap bitmap,\n                        DokitPicasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {\n    Drawable placeholder = target.getDrawable();\n    if (placeholder instanceof AnimationDrawable) {\n      ((AnimationDrawable) placeholder).stop();\n    }\n    PicassoDrawable drawable =\n        new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);\n    target.setImageDrawable(drawable);\n  }\n\n  /**\n   * Create or update the drawable on the target {@link ImageView} to display the supplied\n   * placeholder image.\n   */\n  static void setPlaceholder(ImageView target, Drawable placeholderDrawable) {\n    target.setImageDrawable(placeholderDrawable);\n    if (target.getDrawable() instanceof AnimationDrawable) {\n      ((AnimationDrawable) target.getDrawable()).start();\n    }\n  }\n\n  private final boolean debugging;\n  private final float density;\n  private final DokitPicasso.LoadedFrom loadedFrom;\n\n  Drawable placeholder;\n\n  long startTimeMillis;\n  boolean animating;\n  int alpha = 0xFF;\n\n  PicassoDrawable(Context context, Bitmap bitmap, Drawable placeholder,\n                  DokitPicasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {\n    super(context.getResources(), bitmap);\n\n    this.debugging = debugging;\n    this.density = context.getResources().getDisplayMetrics().density;\n\n    this.loadedFrom = loadedFrom;\n\n    boolean fade = loadedFrom != MEMORY && !noFade;\n    if (fade) {\n      this.placeholder = placeholder;\n      animating = true;\n      startTimeMillis = SystemClock.uptimeMillis();\n    }\n  }\n\n  @Override public void draw(Canvas canvas) {\n    if (!animating) {\n      super.draw(canvas);\n    } else {\n      float normalized = (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION;\n      if (normalized >= 1f) {\n        animating = false;\n        placeholder = null;\n        super.draw(canvas);\n      } else {\n        if (placeholder != null) {\n          placeholder.draw(canvas);\n        }\n\n        int partialAlpha = (int) (alpha * normalized);\n        super.setAlpha(partialAlpha);\n        super.draw(canvas);\n        super.setAlpha(alpha);\n        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {\n          invalidateSelf();\n        }\n      }\n    }\n\n    if (debugging) {\n      drawDebugIndicator(canvas);\n    }\n  }\n\n  @Override public void setAlpha(int alpha) {\n    this.alpha = alpha;\n    if (placeholder != null) {\n      placeholder.setAlpha(alpha);\n    }\n    super.setAlpha(alpha);\n  }\n\n  @Override public void setColorFilter(ColorFilter cf) {\n    if (placeholder != null) {\n      placeholder.setColorFilter(cf);\n    }\n    super.setColorFilter(cf);\n  }\n\n  @Override protected void onBoundsChange(Rect bounds) {\n    if (placeholder != null) {\n      placeholder.setBounds(bounds);\n    }\n    super.onBoundsChange(bounds);\n  }\n\n  private void drawDebugIndicator(Canvas canvas) {\n    DEBUG_PAINT.setColor(WHITE);\n    Path path = getTrianglePath(new Point(0, 0), (int) (16 * density));\n    canvas.drawPath(path, DEBUG_PAINT);\n\n    DEBUG_PAINT.setColor(loadedFrom.debugColor);\n    path = getTrianglePath(new Point(0, 0), (int) (15 * density));\n    canvas.drawPath(path, DEBUG_PAINT);\n  }\n\n  private static Path getTrianglePath(Point p1, int width) {\n    Point p2 = new Point(p1.x + width, p1.y);\n    Point p3 = new Point(p1.x, p1.y + width);\n\n    Path path = new Path();\n    path.moveTo(p1.x, p1.y);\n    path.lineTo(p2.x, p2.y);\n    path.lineTo(p3.x, p3.y);\n\n    return path;\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/PicassoExecutorService.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.net.ConnectivityManager;\nimport android.net.NetworkInfo;\nimport android.telephony.TelephonyManager;\n\nimport java.util.concurrent.Future;\nimport java.util.concurrent.FutureTask;\nimport java.util.concurrent.PriorityBlockingQueue;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * The default {@link java.util.concurrent.ExecutorService} used for new {@link DokitPicasso} instances.\n * <p>\n * Exists as a custom type so that we can differentiate the use of defaults versus a user-supplied\n * instance.\n */\nclass PicassoExecutorService extends ThreadPoolExecutor {\n  private static final int DEFAULT_THREAD_COUNT = 3;\n\n  PicassoExecutorService() {\n    super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,\n        new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());\n  }\n\n  void adjustThreadCount(NetworkInfo info) {\n    if (info == null || !info.isConnectedOrConnecting()) {\n      setThreadCount(DEFAULT_THREAD_COUNT);\n      return;\n    }\n    switch (info.getType()) {\n      case ConnectivityManager.TYPE_WIFI:\n      case ConnectivityManager.TYPE_WIMAX:\n      case ConnectivityManager.TYPE_ETHERNET:\n        setThreadCount(4);\n        break;\n      case ConnectivityManager.TYPE_MOBILE:\n        switch (info.getSubtype()) {\n          case TelephonyManager.NETWORK_TYPE_LTE:  // 4G\n          case TelephonyManager.NETWORK_TYPE_HSPAP:\n          case TelephonyManager.NETWORK_TYPE_EHRPD:\n            setThreadCount(3);\n            break;\n          case TelephonyManager.NETWORK_TYPE_UMTS: // 3G\n          case TelephonyManager.NETWORK_TYPE_CDMA:\n          case TelephonyManager.NETWORK_TYPE_EVDO_0:\n          case TelephonyManager.NETWORK_TYPE_EVDO_A:\n          case TelephonyManager.NETWORK_TYPE_EVDO_B:\n            setThreadCount(2);\n            break;\n          case TelephonyManager.NETWORK_TYPE_GPRS: // 2G\n          case TelephonyManager.NETWORK_TYPE_EDGE:\n            setThreadCount(1);\n            break;\n          default:\n            setThreadCount(DEFAULT_THREAD_COUNT);\n        }\n        break;\n      default:\n        setThreadCount(DEFAULT_THREAD_COUNT);\n    }\n  }\n\n  private void setThreadCount(int threadCount) {\n    setCorePoolSize(threadCount);\n    setMaximumPoolSize(threadCount);\n  }\n\n  @Override\n  public Future<?> submit(Runnable task) {\n    PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);\n    execute(ftask);\n    return ftask;\n  }\n\n  private static final class PicassoFutureTask extends FutureTask<BitmapHunter>\n      implements Comparable<PicassoFutureTask> {\n    private final BitmapHunter hunter;\n\n    public PicassoFutureTask(BitmapHunter hunter) {\n      super(hunter, null);\n      this.hunter = hunter;\n    }\n\n    @Override\n    public int compareTo(PicassoFutureTask other) {\n      DokitPicasso.Priority p1 = hunter.getPriority();\n      DokitPicasso.Priority p2 = other.hunter.getPriority();\n\n      // High-priority requests are \"lesser\" so they are sorted to the front.\n      // Equal priorities are sorted by sequence number to provide FIFO ordering.\n      return (p1 == p2 ? hunter.sequence - other.hunter.sequence : p2.ordinal() - p1.ordinal());\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/RemoteViewsAction.java",
    "content": "/*\n * Copyright (C) 2014 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.app.Notification;\nimport android.app.NotificationManager;\nimport android.appwidget.AppWidgetManager;\nimport android.graphics.Bitmap;\nimport android.widget.RemoteViews;\n\nimport static android.content.Context.NOTIFICATION_SERVICE;\nimport static com.didichuxing.doraemonkit.picasso.Utils.getService;\n\nabstract class RemoteViewsAction extends Action<RemoteViewsAction.RemoteViewsTarget> {\n  final RemoteViews remoteViews;\n  final int viewId;\n\n  private RemoteViewsTarget target;\n\n  RemoteViewsAction(DokitPicasso picasso, Request data, RemoteViews remoteViews, int viewId,\n                    int errorResId, int memoryPolicy, int networkPolicy, Object tag, String key) {\n    super(picasso, null, data, memoryPolicy, networkPolicy, errorResId, null, key, tag, false);\n    this.remoteViews = remoteViews;\n    this.viewId = viewId;\n  }\n\n  @Override void complete(Bitmap result, DokitPicasso.LoadedFrom from) {\n    remoteViews.setImageViewBitmap(viewId, result);\n    update();\n  }\n\n  @Override public void error() {\n    if (errorResId != 0) {\n      setImageResource(errorResId);\n    }\n  }\n\n  @Override RemoteViewsTarget getTarget() {\n    if (target == null) {\n      target = new RemoteViewsTarget(remoteViews, viewId);\n    }\n    return target;\n  }\n\n  void setImageResource(int resId) {\n    remoteViews.setImageViewResource(viewId, resId);\n    update();\n  }\n\n  abstract void update();\n\n  static class RemoteViewsTarget {\n    final RemoteViews remoteViews;\n    final int viewId;\n\n    RemoteViewsTarget(RemoteViews remoteViews, int viewId) {\n      this.remoteViews = remoteViews;\n      this.viewId = viewId;\n    }\n\n    @Override public boolean equals(Object o) {\n      if (this == o) return true;\n      if (o == null || getClass() != o.getClass()) return false;\n      RemoteViewsTarget remoteViewsTarget = (RemoteViewsTarget) o;\n      return viewId == remoteViewsTarget.viewId && remoteViews.equals(\n          remoteViewsTarget.remoteViews);\n    }\n\n    @Override public int hashCode() {\n      return 31 * remoteViews.hashCode() + viewId;\n    }\n  }\n\n  static class AppWidgetAction extends RemoteViewsAction {\n    private final int[] appWidgetIds;\n\n    AppWidgetAction(DokitPicasso picasso, Request data, RemoteViews remoteViews, int viewId,\n                    int[] appWidgetIds, int memoryPolicy, int networkPolicy, String key, Object tag,\n                    int errorResId) {\n      super(picasso, data, remoteViews, viewId, errorResId, memoryPolicy, networkPolicy, tag, key);\n      this.appWidgetIds = appWidgetIds;\n    }\n\n    @Override void update() {\n      AppWidgetManager manager = AppWidgetManager.getInstance(picasso.context);\n      manager.updateAppWidget(appWidgetIds, remoteViews);\n    }\n  }\n\n  static class NotificationAction extends RemoteViewsAction {\n    private final int notificationId;\n    private final Notification notification;\n\n    NotificationAction(DokitPicasso picasso, Request data, RemoteViews remoteViews, int viewId,\n                       int notificationId, Notification notification, int memoryPolicy, int networkPolicy,\n                       String key, Object tag, int errorResId) {\n      super(picasso, data, remoteViews, viewId, errorResId, memoryPolicy, networkPolicy, tag, key);\n      this.notificationId = notificationId;\n      this.notification = notification;\n    }\n\n    @Override void update() {\n      NotificationManager manager = getService(picasso.context, NOTIFICATION_SERVICE);\n      manager.notify(notificationId, notification);\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/Request.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.graphics.Bitmap;\nimport android.net.Uri;\n\nimport com.didichuxing.doraemonkit.picasso.DokitPicasso.Priority;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport static java.util.Collections.unmodifiableList;\n\n/** Immutable data about an image and the transformations that will be applied to it. */\npublic final class Request {\n  private static final long TOO_LONG_LOG = TimeUnit.SECONDS.toNanos(5);\n\n  /** A unique ID for the request. */\n  int id;\n  /** The time that the request was first submitted (in nanos). */\n  long started;\n  /** The {@link NetworkPolicy} to use for this request. */\n  int networkPolicy;\n\n  /**\n   * The image URI.\n   * <p>\n   * This is mutually exclusive with {@link #resourceId}.\n   */\n  public final Uri uri;\n  /**\n   * The image resource ID.\n   * <p>\n   * This is mutually exclusive with {@link #uri}.\n   */\n  public final int resourceId;\n  /**\n   * Optional stable key for this request to be used instead of the URI or resource ID when\n   * caching. Two requests with the same value are considered to be for the same resource.\n   */\n  public final String stableKey;\n  /** List of custom transformations to be applied after the built-in transformations. */\n  public final List<com.didichuxing.doraemonkit.picasso.Transformation> transformations;\n  /** Target image width for resizing. */\n  public final int targetWidth;\n  /** Target image height for resizing. */\n  public final int targetHeight;\n  /**\n   * True if the final image should use the 'centerCrop' scale technique.\n   * <p>\n   * This is mutually exclusive with {@link #centerInside}.\n   */\n  public final boolean centerCrop;\n  /**\n   * True if the final image should use the 'centerInside' scale technique.\n   * <p>\n   * This is mutually exclusive with {@link #centerCrop}.\n   */\n  public final boolean centerInside;\n  public final boolean onlyScaleDown;\n  /** Amount to rotate the image in degrees. */\n  public final float rotationDegrees;\n  /** Rotation pivot on the X axis. */\n  public final float rotationPivotX;\n  /** Rotation pivot on the Y axis. */\n  public final float rotationPivotY;\n  /** Whether or not {@link #rotationPivotX} and {@link #rotationPivotY} are set. */\n  public final boolean hasRotationPivot;\n  /** Target image config for decoding. */\n  public final Bitmap.Config config;\n  /** The priority of this request. */\n  public final Priority priority;\n\n  private Request(Uri uri, int resourceId, String stableKey, List<com.didichuxing.doraemonkit.picasso.Transformation> transformations,\n      int targetWidth, int targetHeight, boolean centerCrop, boolean centerInside,\n      boolean onlyScaleDown, float rotationDegrees, float rotationPivotX, float rotationPivotY,\n      boolean hasRotationPivot, Bitmap.Config config, Priority priority) {\n    this.uri = uri;\n    this.resourceId = resourceId;\n    this.stableKey = stableKey;\n    if (transformations == null) {\n      this.transformations = null;\n    } else {\n      this.transformations = unmodifiableList(transformations);\n    }\n    this.targetWidth = targetWidth;\n    this.targetHeight = targetHeight;\n    this.centerCrop = centerCrop;\n    this.centerInside = centerInside;\n    this.onlyScaleDown = onlyScaleDown;\n    this.rotationDegrees = rotationDegrees;\n    this.rotationPivotX = rotationPivotX;\n    this.rotationPivotY = rotationPivotY;\n    this.hasRotationPivot = hasRotationPivot;\n    this.config = config;\n    this.priority = priority;\n  }\n\n  @Override public String toString() {\n    final StringBuilder sb = new StringBuilder(\"Request{\");\n    if (resourceId > 0) {\n      sb.append(resourceId);\n    } else {\n      sb.append(uri);\n    }\n    if (transformations != null && !transformations.isEmpty()) {\n      for (com.didichuxing.doraemonkit.picasso.Transformation transformation : transformations) {\n        sb.append(' ').append(transformation.key());\n      }\n    }\n    if (stableKey != null) {\n      sb.append(\" stableKey(\").append(stableKey).append(')');\n    }\n    if (targetWidth > 0) {\n      sb.append(\" resize(\").append(targetWidth).append(',').append(targetHeight).append(')');\n    }\n    if (centerCrop) {\n      sb.append(\" centerCrop\");\n    }\n    if (centerInside) {\n      sb.append(\" centerInside\");\n    }\n    if (rotationDegrees != 0) {\n      sb.append(\" rotation(\").append(rotationDegrees);\n      if (hasRotationPivot) {\n        sb.append(\" @ \").append(rotationPivotX).append(',').append(rotationPivotY);\n      }\n      sb.append(')');\n    }\n    if (config != null) {\n      sb.append(' ').append(config);\n    }\n    sb.append('}');\n\n    return sb.toString();\n  }\n\n  String logId() {\n    long delta = System.nanoTime() - started;\n    if (delta > TOO_LONG_LOG) {\n      return plainId() + '+' + TimeUnit.NANOSECONDS.toSeconds(delta) + 's';\n    }\n    return plainId() + '+' + TimeUnit.NANOSECONDS.toMillis(delta) + \"ms\";\n  }\n\n  String plainId() {\n    return \"[R\" + id + ']';\n  }\n\n  String getName() {\n    if (uri != null) {\n      return String.valueOf(uri.getPath());\n    }\n    return Integer.toHexString(resourceId);\n  }\n\n  public boolean hasSize() {\n    return targetWidth != 0 || targetHeight != 0;\n  }\n\n  boolean needsTransformation() {\n    return needsMatrixTransform() || hasCustomTransformations();\n  }\n\n  boolean needsMatrixTransform() {\n    return hasSize() || rotationDegrees != 0;\n  }\n\n  boolean hasCustomTransformations() {\n    return transformations != null;\n  }\n\n  public Builder buildUpon() {\n    return new Builder(this);\n  }\n\n  /** Builder for creating {@link Request} instances. */\n  public static final class Builder {\n    private Uri uri;\n    private int resourceId;\n    private String stableKey;\n    private int targetWidth;\n    private int targetHeight;\n    private boolean centerCrop;\n    private boolean centerInside;\n    private boolean onlyScaleDown;\n    private float rotationDegrees;\n    private float rotationPivotX;\n    private float rotationPivotY;\n    private boolean hasRotationPivot;\n    private List<com.didichuxing.doraemonkit.picasso.Transformation> transformations;\n    private Bitmap.Config config;\n    private Priority priority;\n\n    /** Start building a request using the specified {@link Uri}. */\n    public Builder(Uri uri) {\n      setUri(uri);\n    }\n\n    /** Start building a request using the specified resource ID. */\n    public Builder(int resourceId) {\n      setResourceId(resourceId);\n    }\n\n    Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {\n      this.uri = uri;\n      this.resourceId = resourceId;\n      this.config = bitmapConfig;\n    }\n\n    private Builder(Request request) {\n      uri = request.uri;\n      resourceId = request.resourceId;\n      stableKey = request.stableKey;\n      targetWidth = request.targetWidth;\n      targetHeight = request.targetHeight;\n      centerCrop = request.centerCrop;\n      centerInside = request.centerInside;\n      rotationDegrees = request.rotationDegrees;\n      rotationPivotX = request.rotationPivotX;\n      rotationPivotY = request.rotationPivotY;\n      hasRotationPivot = request.hasRotationPivot;\n      onlyScaleDown = request.onlyScaleDown;\n      if (request.transformations != null) {\n        transformations = new ArrayList<com.didichuxing.doraemonkit.picasso.Transformation>(request.transformations);\n      }\n      config = request.config;\n      priority = request.priority;\n    }\n\n    boolean hasImage() {\n      return uri != null || resourceId != 0;\n    }\n\n    boolean hasSize() {\n      return targetWidth != 0 || targetHeight != 0;\n    }\n\n    boolean hasPriority() {\n      return priority != null;\n    }\n\n    /**\n     * Set the target image Uri.\n     * <p>\n     * This will clear an image resource ID if one is set.\n     */\n    public Builder setUri(Uri uri) {\n      if (uri == null) {\n        throw new IllegalArgumentException(\"Image URI may not be null.\");\n      }\n      this.uri = uri;\n      this.resourceId = 0;\n      return this;\n    }\n\n    /**\n     * Set the target image resource ID.\n     * <p>\n     * This will clear an image Uri if one is set.\n     */\n    public Builder setResourceId(int resourceId) {\n      if (resourceId == 0) {\n        throw new IllegalArgumentException(\"Image resource ID may not be 0.\");\n      }\n      this.resourceId = resourceId;\n      this.uri = null;\n      return this;\n    }\n\n    /**\n     * Set the stable key to be used instead of the URI or resource ID when caching.\n     * Two requests with the same value are considered to be for the same resource.\n     */\n    public Builder stableKey(String stableKey) {\n      this.stableKey = stableKey;\n      return this;\n    }\n\n    /**\n     * Resize the image to the specified size in pixels.\n     * Use 0 as desired dimension to resize keeping aspect ratio.\n     */\n    public Builder resize(int targetWidth, int targetHeight) {\n      if (targetWidth < 0) {\n        throw new IllegalArgumentException(\"Width must be positive number or 0.\");\n      }\n      if (targetHeight < 0) {\n        throw new IllegalArgumentException(\"Height must be positive number or 0.\");\n      }\n      if (targetHeight == 0 && targetWidth == 0) {\n        throw new IllegalArgumentException(\"At least one dimension has to be positive number.\");\n      }\n      this.targetWidth = targetWidth;\n      this.targetHeight = targetHeight;\n      return this;\n    }\n\n    /** Clear the resize transformation, if any. This will also clear center crop/inside if set. */\n    public Builder clearResize() {\n      targetWidth = 0;\n      targetHeight = 0;\n      centerCrop = false;\n      centerInside = false;\n      return this;\n    }\n\n    /**\n     * Crops an image inside of the bounds specified by {@link #resize(int, int)} rather than\n     * distorting the aspect ratio. This cropping technique scales the image so that it fills the\n     * requested bounds and then crops the extra.\n     */\n    public Builder centerCrop() {\n      if (centerInside) {\n        throw new IllegalStateException(\"Center crop can not be used after calling centerInside\");\n      }\n      centerCrop = true;\n      return this;\n    }\n\n    /** Clear the center crop transformation flag, if set. */\n    public Builder clearCenterCrop() {\n      centerCrop = false;\n      return this;\n    }\n\n    /**\n     * Centers an image inside of the bounds specified by {@link #resize(int, int)}. This scales\n     * the image so that both dimensions are equal to or less than the requested bounds.\n     */\n    public Builder centerInside() {\n      if (centerCrop) {\n        throw new IllegalStateException(\"Center inside can not be used after calling centerCrop\");\n      }\n      centerInside = true;\n      return this;\n    }\n\n    /** Clear the center inside transformation flag, if set. */\n    public Builder clearCenterInside() {\n      centerInside = false;\n      return this;\n    }\n\n    /**\n     * Only resize an image if the original image size is bigger than the target size\n     * specified by {@link #resize(int, int)}.\n     */\n    public Builder onlyScaleDown() {\n      if (targetHeight == 0 && targetWidth == 0) {\n        throw new IllegalStateException(\"onlyScaleDown can not be applied without resize\");\n      }\n      onlyScaleDown = true;\n      return this;\n    }\n\n    /** Clear the onlyScaleUp flag, if set. **/\n    public Builder clearOnlyScaleDown() {\n      onlyScaleDown = false;\n      return this;\n    }\n\n    /** Rotate the image by the specified degrees. */\n    public Builder rotate(float degrees) {\n      rotationDegrees = degrees;\n      return this;\n    }\n\n    /** Rotate the image by the specified degrees around a pivot point. */\n    public Builder rotate(float degrees, float pivotX, float pivotY) {\n      rotationDegrees = degrees;\n      rotationPivotX = pivotX;\n      rotationPivotY = pivotY;\n      hasRotationPivot = true;\n      return this;\n    }\n\n    /** Clear the rotation transformation, if any. */\n    public Builder clearRotation() {\n      rotationDegrees = 0;\n      rotationPivotX = 0;\n      rotationPivotY = 0;\n      hasRotationPivot = false;\n      return this;\n    }\n\n    /** Decode the image using the specified config. */\n    public Builder config(Bitmap.Config config) {\n      this.config = config;\n      return this;\n    }\n\n    /** Execute request using the specified priority. */\n    public Builder priority(Priority priority) {\n      if (priority == null) {\n        throw new IllegalArgumentException(\"Priority invalid.\");\n      }\n      if (this.priority != null) {\n        throw new IllegalStateException(\"Priority already set.\");\n      }\n      this.priority = priority;\n      return this;\n    }\n\n    /**\n     * Add a custom transformation to be applied to the image.\n     * <p>\n     * Custom transformations will always be run after the built-in transformations.\n     */\n    public Builder transform(com.didichuxing.doraemonkit.picasso.Transformation transformation) {\n      if (transformation == null) {\n        throw new IllegalArgumentException(\"Transformation must not be null.\");\n      }\n      if (transformation.key() == null) {\n        throw new IllegalArgumentException(\"Transformation key must not be null.\");\n      }\n      if (transformations == null) {\n        transformations = new ArrayList<com.didichuxing.doraemonkit.picasso.Transformation>(2);\n      }\n      transformations.add(transformation);\n      return this;\n    }\n\n    /**\n     * Add a list of custom transformations to be applied to the image.\n     * <p>\n     * Custom transformations will always be run after the built-in transformations.\n     */\n    public Builder transform(List<? extends Transformation> transformations) {\n      if (transformations == null) {\n        throw new IllegalArgumentException(\"Transformation list must not be null.\");\n      }\n      for (int i = 0, size = transformations.size(); i < size; i++) {\n        transform(transformations.get(i));\n      }\n      return this;\n    }\n\n    /** Create the immutable {@link Request} object. */\n    public Request build() {\n      if (centerInside && centerCrop) {\n        throw new IllegalStateException(\"Center crop and center inside can not be used together.\");\n      }\n      if (centerCrop && (targetWidth == 0 && targetHeight == 0)) {\n        throw new IllegalStateException(\n            \"Center crop requires calling resize with positive width and height.\");\n      }\n      if (centerInside && (targetWidth == 0 && targetHeight == 0)) {\n        throw new IllegalStateException(\n            \"Center inside requires calling resize with positive width and height.\");\n      }\n      if (priority == null) {\n        priority = Priority.NORMAL;\n      }\n      return new Request(uri, resourceId, stableKey, transformations, targetWidth, targetHeight,\n          centerCrop, centerInside, onlyScaleDown, rotationDegrees, rotationPivotX, rotationPivotY,\n          hasRotationPivot, config, priority);\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/RequestCreator.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.app.Notification;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.widget.ImageView;\nimport android.widget.RemoteViews;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static com.didichuxing.doraemonkit.picasso.BitmapHunter.forRequest;\nimport static com.didichuxing.doraemonkit.picasso.MemoryPolicy.NO_CACHE;\nimport static com.didichuxing.doraemonkit.picasso.MemoryPolicy.NO_STORE;\nimport static com.didichuxing.doraemonkit.picasso.MemoryPolicy.shouldReadFromMemoryCache;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.LoadedFrom.MEMORY;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.Priority;\nimport static com.didichuxing.doraemonkit.picasso.PicassoDrawable.setBitmap;\nimport static com.didichuxing.doraemonkit.picasso.PicassoDrawable.setPlaceholder;\nimport static com.didichuxing.doraemonkit.picasso.RemoteViewsAction.AppWidgetAction;\nimport static com.didichuxing.doraemonkit.picasso.RemoteViewsAction.NotificationAction;\nimport static com.didichuxing.doraemonkit.picasso.Utils.OWNER_MAIN;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_CHANGED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_COMPLETED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.VERB_CREATED;\nimport static com.didichuxing.doraemonkit.picasso.Utils.checkMain;\nimport static com.didichuxing.doraemonkit.picasso.Utils.checkNotMain;\nimport static com.didichuxing.doraemonkit.picasso.Utils.createKey;\nimport static com.didichuxing.doraemonkit.picasso.Utils.log;\n\n/**\n * Fluent API for building an image download request.\n */\n@SuppressWarnings(\"UnusedDeclaration\") // Public API.\npublic class RequestCreator {\n    private static final AtomicInteger nextId = new AtomicInteger();\n\n    private final DokitPicasso picasso;\n    private final Request.Builder data;\n\n    private boolean noFade;\n    private boolean deferred;\n    private boolean setPlaceholder = true;\n    private int placeholderResId;\n    private int errorResId;\n    private int memoryPolicy;\n    private int networkPolicy;\n    private Drawable placeholderDrawable;\n    private Drawable errorDrawable;\n    private Object tag;\n\n    RequestCreator(DokitPicasso picasso, Uri uri, int resourceId) {\n        if (picasso.shutdown) {\n            throw new IllegalStateException(\n                    \"Picasso instance already shut down. Cannot submit new requests.\");\n        }\n        this.picasso = picasso;\n        this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);\n    }\n\n    RequestCreator() {\n        this.picasso = null;\n        this.data = new Request.Builder(null, 0, null);\n    }\n\n    /**\n     * Explicitly opt-out to having a placeholder set when calling {@code into}.\n     * <p>\n     * By default, Picasso will either set a supplied placeholder or clear the target\n     * {@link ImageView} in order to ensure behavior in situations where views are recycled. This\n     * method will prevent that behavior and retain any already set image.\n     */\n    public RequestCreator noPlaceholder() {\n        if (placeholderResId != 0) {\n            throw new IllegalStateException(\"Placeholder resource already set.\");\n        }\n        if (placeholderDrawable != null) {\n            throw new IllegalStateException(\"Placeholder image already set.\");\n        }\n        setPlaceholder = false;\n        return this;\n    }\n\n    /**\n     * A placeholder drawable to be used while the image is being loaded. If the requested image is\n     * not immediately available in the memory cache then this resource will be set on the target\n     * {@link ImageView}.\n     */\n    public RequestCreator placeholder(int placeholderResId) {\n        if (!setPlaceholder) {\n            throw new IllegalStateException(\"Already explicitly declared as no placeholder.\");\n        }\n        if (placeholderResId == 0) {\n            throw new IllegalArgumentException(\"Placeholder image resource invalid.\");\n        }\n        if (placeholderDrawable != null) {\n            throw new IllegalStateException(\"Placeholder image already set.\");\n        }\n        this.placeholderResId = placeholderResId;\n        return this;\n    }\n\n    /**\n     * A placeholder drawable to be used while the image is being loaded. If the requested image is\n     * not immediately available in the memory cache then this resource will be set on the target\n     * {@link ImageView}.\n     * <p>\n     * If you are not using a placeholder image but want to clear an existing image (such as when\n     * used in an {@link android.widget.Adapter adapter}), pass in {@code null}.\n     */\n    public RequestCreator placeholder(Drawable placeholderDrawable) {\n        if (!setPlaceholder) {\n            throw new IllegalStateException(\"Already explicitly declared as no placeholder.\");\n        }\n        if (placeholderResId != 0) {\n            throw new IllegalStateException(\"Placeholder image already set.\");\n        }\n        this.placeholderDrawable = placeholderDrawable;\n        return this;\n    }\n\n    /**\n     * An error drawable to be used if the request image could not be loaded.\n     */\n    public RequestCreator error(int errorResId) {\n        if (errorResId == 0) {\n            throw new IllegalArgumentException(\"Error image resource invalid.\");\n        }\n        if (errorDrawable != null) {\n            throw new IllegalStateException(\"Error image already set.\");\n        }\n        this.errorResId = errorResId;\n        return this;\n    }\n\n    /**\n     * An error drawable to be used if the request image could not be loaded.\n     */\n    public RequestCreator error(Drawable errorDrawable) {\n        if (errorDrawable == null) {\n            throw new IllegalArgumentException(\"Error image may not be null.\");\n        }\n        if (errorResId != 0) {\n            throw new IllegalStateException(\"Error image already set.\");\n        }\n        this.errorDrawable = errorDrawable;\n        return this;\n    }\n\n    /**\n     * Assign a tag to this request. Tags are an easy way to logically associate\n     * related requests that can be managed together e.g. paused, resumed,\n     * or canceled.\n     * <p>\n     * You can either use simple {@link String} tags or objects that naturally\n     * define the scope of your requests within your app such as a\n     * {@link android.content.Context}, an {@link android.app.Activity}, or a\n     * {@link android.app.Fragment}.\n     *\n     * <strong>WARNING:</strong>: Picasso will keep a reference to the tag for\n     * as long as this tag is paused and/or has active requests. Look out for\n     * potential leaks.\n     *\n     * @see DokitPicasso#cancelTag(Object)\n     * @see DokitPicasso#pauseTag(Object)\n     * @see DokitPicasso#resumeTag(Object)\n     */\n    public RequestCreator tag(Object tag) {\n        if (tag == null) {\n            throw new IllegalArgumentException(\"Tag invalid.\");\n        }\n        if (this.tag != null) {\n            throw new IllegalStateException(\"Tag already set.\");\n        }\n        this.tag = tag;\n        return this;\n    }\n\n    /**\n     * Attempt to resize the image to fit exactly into the target {@link ImageView}'s bounds. This\n     * will result in delayed execution of the request until the {@link ImageView} has been laid out.\n     * <p>\n     * <em>Note:</em> This method works only when your target is an {@link ImageView}.\n     */\n    public RequestCreator fit() {\n        deferred = true;\n        return this;\n    }\n\n    /**\n     * Internal use only. Used by {@link DeferredRequestCreator}.\n     */\n    RequestCreator unfit() {\n        deferred = false;\n        return this;\n    }\n\n    /**\n     * Resize the image to the specified dimension size.\n     */\n    public RequestCreator resizeDimen(int targetWidthResId, int targetHeightResId) {\n        Resources resources = picasso.context.getResources();\n        int targetWidth = resources.getDimensionPixelSize(targetWidthResId);\n        int targetHeight = resources.getDimensionPixelSize(targetHeightResId);\n        return resize(targetWidth, targetHeight);\n    }\n\n    /**\n     * Resize the image to the specified size in pixels.\n     */\n    public RequestCreator resize(int targetWidth, int targetHeight) {\n        data.resize(targetWidth, targetHeight);\n        return this;\n    }\n\n    /**\n     * Crops an image inside of the bounds specified by {@link #resize(int, int)} rather than\n     * distorting the aspect ratio. This cropping technique scales the image so that it fills the\n     * requested bounds and then crops the extra.\n     */\n    public RequestCreator centerCrop() {\n        data.centerCrop();\n        return this;\n    }\n\n    /**\n     * Centers an image inside of the bounds specified by {@link #resize(int, int)}. This scales\n     * the image so that both dimensions are equal to or less than the requested bounds.\n     */\n    public RequestCreator centerInside() {\n        data.centerInside();\n        return this;\n    }\n\n    /**\n     * Only resize an image if the original image size is bigger than the target size\n     * specified by {@link #resize(int, int)}.\n     */\n    public RequestCreator onlyScaleDown() {\n        data.onlyScaleDown();\n        return this;\n    }\n\n    /**\n     * Rotate the image by the specified degrees.\n     */\n    public RequestCreator rotate(float degrees) {\n        data.rotate(degrees);\n        return this;\n    }\n\n    /**\n     * Rotate the image by the specified degrees around a pivot point.\n     */\n    public RequestCreator rotate(float degrees, float pivotX, float pivotY) {\n        data.rotate(degrees, pivotX, pivotY);\n        return this;\n    }\n\n    /**\n     * Attempt to decode the image using the specified config.\n     * <p>\n     * Note: This value may be ignored by {@link BitmapFactory}. See\n     * {@link BitmapFactory.Options#inPreferredConfig its documentation} for more details.\n     */\n    public RequestCreator config(Bitmap.Config config) {\n        data.config(config);\n        return this;\n    }\n\n    /**\n     * Sets the stable key for this request to be used instead of the URI or resource ID when\n     * caching. Two requests with the same value are considered to be for the same resource.\n     */\n    public RequestCreator stableKey(String stableKey) {\n        data.stableKey(stableKey);\n        return this;\n    }\n\n    /**\n     * Set the priority of this request.\n     * <p>\n     * This will affect the order in which the requests execute but does not guarantee it.\n     * By default, all requests have {@link Priority#NORMAL} priority, except for\n     * {@link #fetch()} requests, which have {@link Priority#LOW} priority by default.\n     */\n    public RequestCreator priority(Priority priority) {\n        data.priority(priority);\n        return this;\n    }\n\n    /**\n     * Add a custom transformation to be applied to the image.\n     * <p>\n     * Custom transformations will always be run after the built-in transformations.\n     */\n    // TODO show example of calling resize after a transform in the javadoc\n    public RequestCreator transform(Transformation transformation) {\n        data.transform(transformation);\n        return this;\n    }\n\n    /**\n     * Add a list of custom transformations to be applied to the image.\n     * <p>\n     * Custom transformations will always be run after the built-in transformations.\n     */\n    public RequestCreator transform(List<? extends Transformation> transformations) {\n        data.transform(transformations);\n        return this;\n    }\n\n    /**\n     * @deprecated Use {@link #memoryPolicy(MemoryPolicy, MemoryPolicy...)} instead.\n     */\n    @Deprecated\n    public RequestCreator skipMemoryCache() {\n        return memoryPolicy(NO_CACHE, NO_STORE);\n    }\n\n    /**\n     * Specifies the {@link MemoryPolicy} to use for this request. You may specify additional policy\n     * options using the varargs parameter.\n     */\n    public RequestCreator memoryPolicy(MemoryPolicy policy, MemoryPolicy... additional) {\n        if (policy == null) {\n            throw new IllegalArgumentException(\"Memory policy cannot be null.\");\n        }\n        this.memoryPolicy |= policy.index;\n        if (additional == null) {\n            throw new IllegalArgumentException(\"Memory policy cannot be null.\");\n        }\n        if (additional.length > 0) {\n            for (MemoryPolicy memoryPolicy : additional) {\n                if (memoryPolicy == null) {\n                    throw new IllegalArgumentException(\"Memory policy cannot be null.\");\n                }\n                this.memoryPolicy |= memoryPolicy.index;\n            }\n        }\n        return this;\n    }\n\n    /**\n     * Specifies the {@link NetworkPolicy} to use for this request. You may specify additional policy\n     * options using the varargs parameter.\n     */\n    public RequestCreator networkPolicy(NetworkPolicy policy, NetworkPolicy... additional) {\n        if (policy == null) {\n            throw new IllegalArgumentException(\"Network policy cannot be null.\");\n        }\n        this.networkPolicy |= policy.index;\n        if (additional == null) {\n            throw new IllegalArgumentException(\"Network policy cannot be null.\");\n        }\n        if (additional.length > 0) {\n            for (NetworkPolicy networkPolicy : additional) {\n                if (networkPolicy == null) {\n                    throw new IllegalArgumentException(\"Network policy cannot be null.\");\n                }\n                this.networkPolicy |= networkPolicy.index;\n            }\n        }\n        return this;\n    }\n\n    /**\n     * Disable brief fade in of images loaded from the disk cache or network.\n     */\n    public RequestCreator noFade() {\n        noFade = true;\n        return this;\n    }\n\n    /**\n     * Synchronously fulfill this request. Must not be called from the main thread.\n     * <p>\n     * <em>Note</em>: The result of this operation is not cached in memory because the underlying\n     * {@link Cache} implementation is not guaranteed to be thread-safe.\n     */\n    public Bitmap get() throws IOException {\n        long started = System.nanoTime();\n        checkNotMain();\n\n        if (deferred) {\n            throw new IllegalStateException(\"Fit cannot be used with get.\");\n        }\n        if (!data.hasImage()) {\n            return null;\n        }\n\n        Request finalData = createRequest(started);\n        String key = createKey(finalData, new StringBuilder());\n\n        Action action = new GetAction(picasso, finalData, memoryPolicy, networkPolicy, tag, key);\n        return forRequest(picasso, picasso.dispatcher, picasso.cache, picasso.stats, action).hunt();\n    }\n\n    /**\n     * Asynchronously fulfills the request without a {@link ImageView} or {@link Target}. This is\n     * useful when you want to warm up the cache with an image.\n     * <p>\n     * <em>Note:</em> It is safe to invoke this method from any thread.\n     */\n    public void fetch() {\n        fetch(null);\n    }\n\n    /**\n     * Asynchronously fulfills the request without a {@link ImageView} or {@link Target},\n     * and invokes the target {@link Callback} with the result. This is useful when you want to warm\n     * up the cache with an image.\n     * <p>\n     * <em>Note:</em> The {@link Callback} param is a strong reference and will prevent your\n     * {@link android.app.Activity} or {@link android.app.Fragment} from being garbage collected\n     * until the request is completed.\n     */\n    public void fetch(Callback callback) {\n        long started = System.nanoTime();\n\n        if (deferred) {\n            throw new IllegalStateException(\"Fit cannot be used with fetch.\");\n        }\n        if (data.hasImage()) {\n            // Fetch requests have lower priority by default.\n            if (!data.hasPriority()) {\n                data.priority(Priority.LOW);\n            }\n\n            Request request = createRequest(started);\n            String key = createKey(request, new StringBuilder());\n            Bitmap bitmap = picasso.quickMemoryCacheCheck(key);\n\n            if (bitmap != null) {\n                if (picasso.loggingEnabled) {\n                    log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), \"from \" + MEMORY);\n                }\n                if (callback != null) {\n                    callback.onSuccess();\n                }\n            } else {\n                Action action =\n                        new FetchAction(picasso, request, memoryPolicy, networkPolicy, tag, key, callback);\n                picasso.submit(action);\n            }\n        }\n    }\n\n    /**\n     * Asynchronously fulfills the request into the specified {@link Target}. In most cases, you\n     * should use this when you are dealing with a custom {@link android.view.View View} or view\n     * holder which should implement the {@link Target} interface.\n     * <p>\n     * Implementing on a {@link android.view.View View}:\n     * <blockquote><pre>\n     * public class ProfileView extends FrameLayout implements Target {\n     *   {@literal @}Override public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) {\n     *     setBackgroundDrawable(new BitmapDrawable(bitmap));\n     *   }\n     *\n     *   {@literal @}Override public void onBitmapFailed() {\n     *     setBackgroundResource(R.drawable.profile_error);\n     *   }\n     *\n     *   {@literal @}Override public void onPrepareLoad(Drawable placeHolderDrawable) {\n     *     frame.setBackgroundDrawable(placeHolderDrawable);\n     *   }\n     * }\n     * </pre></blockquote>\n     * Implementing on a view holder object for use inside of an adapter:\n     * <blockquote><pre>\n     * public class ViewHolder implements Target {\n     *   public FrameLayout frame;\n     *   public TextView name;\n     *\n     *   {@literal @}Override public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) {\n     *     frame.setBackgroundDrawable(new BitmapDrawable(bitmap));\n     *   }\n     *\n     *   {@literal @}Override public void onBitmapFailed() {\n     *     frame.setBackgroundResource(R.drawable.profile_error);\n     *   }\n     *\n     *   {@literal @}Override public void onPrepareLoad(Drawable placeHolderDrawable) {\n     *     frame.setBackgroundDrawable(placeHolderDrawable);\n     *   }\n     * }\n     * </pre></blockquote>\n     * <p>\n     * <em>Note:</em> This method keeps a weak reference to the {@link Target} instance and will be\n     * garbage collected if you do not keep a strong reference to it. To receive callbacks when an\n     * image is loaded use {@link #into(android.widget.ImageView, Callback)}.\n     */\n    public void into(Target target) {\n        long started = System.nanoTime();\n        checkMain();\n\n        if (target == null) {\n            throw new IllegalArgumentException(\"Target must not be null.\");\n        }\n        if (deferred) {\n            throw new IllegalStateException(\"Fit cannot be used with a Target.\");\n        }\n\n        if (!data.hasImage()) {\n            picasso.cancelRequest(target);\n            target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null);\n            return;\n        }\n\n        Request request = createRequest(started);\n        String requestKey = createKey(request);\n\n        if (shouldReadFromMemoryCache(memoryPolicy)) {\n            Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);\n            if (bitmap != null) {\n                picasso.cancelRequest(target);\n                target.onBitmapLoaded(bitmap, MEMORY);\n                return;\n            }\n        }\n\n        target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null);\n\n        Action action =\n                new TargetAction(picasso, target, request, memoryPolicy, networkPolicy, errorDrawable,\n                        requestKey, tag, errorResId);\n        picasso.enqueueAndSubmit(action);\n    }\n\n    /**\n     * Asynchronously fulfills the request into the specified {@link RemoteViews} object with the\n     * given {@code viewId}. This is used for loading bitmaps into a {@link Notification}.\n     */\n    public void into(RemoteViews remoteViews, int viewId, int notificationId,\n                     Notification notification) {\n        long started = System.nanoTime();\n\n        if (remoteViews == null) {\n            throw new IllegalArgumentException(\"RemoteViews must not be null.\");\n        }\n        if (notification == null) {\n            throw new IllegalArgumentException(\"Notification must not be null.\");\n        }\n        if (deferred) {\n            throw new IllegalStateException(\"Fit cannot be used with RemoteViews.\");\n        }\n        if (placeholderDrawable != null || placeholderResId != 0 || errorDrawable != null) {\n            throw new IllegalArgumentException(\n                    \"Cannot use placeholder or error drawables with remote views.\");\n        }\n\n        Request request = createRequest(started);\n        String key = createKey(request, new StringBuilder()); // Non-main thread needs own builder.\n\n        RemoteViewsAction action =\n                new NotificationAction(picasso, request, remoteViews, viewId, notificationId, notification,\n                        memoryPolicy, networkPolicy, key, tag, errorResId);\n\n        performRemoteViewInto(action);\n    }\n\n    /**\n     * Asynchronously fulfills the request into the specified {@link RemoteViews} object with the\n     * given {@code viewId}. This is used for loading bitmaps into all instances of a widget.\n     */\n    public void into(RemoteViews remoteViews, int viewId, int[] appWidgetIds) {\n        long started = System.nanoTime();\n\n        if (remoteViews == null) {\n            throw new IllegalArgumentException(\"remoteViews must not be null.\");\n        }\n        if (appWidgetIds == null) {\n            throw new IllegalArgumentException(\"appWidgetIds must not be null.\");\n        }\n        if (deferred) {\n            throw new IllegalStateException(\"Fit cannot be used with remote views.\");\n        }\n        if (placeholderDrawable != null || placeholderResId != 0 || errorDrawable != null) {\n            throw new IllegalArgumentException(\n                    \"Cannot use placeholder or error drawables with remote views.\");\n        }\n\n        Request request = createRequest(started);\n        String key = createKey(request, new StringBuilder()); // Non-main thread needs own builder.\n\n        RemoteViewsAction action =\n                new AppWidgetAction(picasso, request, remoteViews, viewId, appWidgetIds, memoryPolicy,\n                        networkPolicy, key, tag, errorResId);\n\n        performRemoteViewInto(action);\n    }\n\n    /**\n     * Asynchronously fulfills the request into the specified {@link ImageView}.\n     * <p>\n     * <em>Note:</em> This method keeps a weak reference to the {@link ImageView} instance and will\n     * automatically support object recycling.\n     */\n    public void into(ImageView target) {\n        into(target, null);\n    }\n\n    /**\n     * Asynchronously fulfills the request into the specified {@link ImageView} and invokes the\n     * target {@link Callback} if it's not {@code null}.\n     * <p>\n     * <em>Note:</em> The {@link Callback} param is a strong reference and will prevent your\n     * {@link android.app.Activity} or {@link android.app.Fragment} from being garbage collected. If\n     * you use this method, it is <b>strongly</b> recommended you invoke an adjacent\n     * {@link DokitPicasso#cancelRequest(android.widget.ImageView)} call to prevent temporary leaking.\n     */\n    public void into(ImageView target, Callback callback) {\n        long started = System.nanoTime();\n        checkMain();\n\n        if (target == null) {\n            throw new IllegalArgumentException(\"Target must not be null.\");\n        }\n\n        if (!data.hasImage()) {\n            picasso.cancelRequest(target);\n            if (setPlaceholder) {\n                setPlaceholder(target, getPlaceholderDrawable());\n            }\n            return;\n        }\n\n        if (deferred) {\n            if (data.hasSize()) {\n                throw new IllegalStateException(\"Fit cannot be used with resize.\");\n            }\n            int width = target.getWidth();\n            int height = target.getHeight();\n            if (width == 0 || height == 0) {\n                if (setPlaceholder) {\n                    setPlaceholder(target, getPlaceholderDrawable());\n                }\n                picasso.defer(target, new DeferredRequestCreator(this, target, callback));\n                return;\n            }\n            data.resize(width, height);\n        }\n\n        Request request = createRequest(started);\n        String requestKey = createKey(request);\n\n        if (shouldReadFromMemoryCache(memoryPolicy)) {\n            Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);\n            if (bitmap != null) {\n                picasso.cancelRequest(target);\n                setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);\n                if (picasso.loggingEnabled) {\n                    log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), \"from \" + MEMORY);\n                }\n                if (callback != null) {\n                    callback.onSuccess();\n                }\n                return;\n            }\n        }\n\n        if (setPlaceholder) {\n            setPlaceholder(target, getPlaceholderDrawable());\n        }\n\n        Action action =\n                new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,\n                        errorDrawable, requestKey, tag, callback, noFade);\n\n        picasso.enqueueAndSubmit(action);\n    }\n\n    private Drawable getPlaceholderDrawable() {\n        if (placeholderResId != 0) {\n            return picasso.context.getResources().getDrawable(placeholderResId);\n        } else {\n            return placeholderDrawable; // This may be null which is expected and desired behavior.\n        }\n    }\n\n    /**\n     * Create the request optionally passing it through the request transformer.\n     */\n    private Request createRequest(long started) {\n        int id = nextId.getAndIncrement();\n\n        Request request = data.build();\n        request.id = id;\n        request.started = started;\n\n        boolean loggingEnabled = picasso.loggingEnabled;\n        if (loggingEnabled) {\n            log(OWNER_MAIN, VERB_CREATED, request.plainId(), request.toString());\n        }\n\n        Request transformed = picasso.transformRequest(request);\n        if (transformed != request) {\n            // If the request was changed, copy over the id and timestamp from the original.\n            transformed.id = id;\n            transformed.started = started;\n\n            if (loggingEnabled) {\n                log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), \"into \" + transformed);\n            }\n        }\n\n        return transformed;\n    }\n\n    private void performRemoteViewInto(RemoteViewsAction action) {\n        if (shouldReadFromMemoryCache(memoryPolicy)) {\n            Bitmap bitmap = picasso.quickMemoryCacheCheck(action.getKey());\n            if (bitmap != null) {\n                action.complete(bitmap, MEMORY);\n                return;\n            }\n        }\n\n        if (placeholderResId != 0) {\n            action.setImageResource(placeholderResId);\n        }\n\n        picasso.enqueueAndSubmit(action);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/RequestHandler.java",
    "content": "/*\n * Copyright (C) 2014 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.net.NetworkInfo;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport static com.didichuxing.doraemonkit.picasso.Utils.checkNotNull;\n\n/**\n * {@code RequestHandler} allows you to extend Picasso to load images in ways that are not\n * supported by default in the library.\n * <p>\n * <h2>Usage</h2>\n * {@code RequestHandler} must be subclassed to be used. You will have to override two methods\n * ({@link #canHandleRequest(Request)} and {@link #load(Request, int)}) with your custom logic to\n * load images.\n * <p>\n * You should then register your {@link RequestHandler} using\n * {@link DokitPicasso.Builder#addRequestHandler(RequestHandler)}\n * <p>\n * <b>Note:</b> This is a beta feature. The API is subject to change in a backwards incompatible\n * way at any time.\n *\n * @see DokitPicasso.Builder#addRequestHandler(RequestHandler)\n */\npublic abstract class RequestHandler {\n  /**\n   * {@link Result} represents the result of a {@link #load(Request, int)} call in a\n   * {@link RequestHandler}.\n   *\n   * @see RequestHandler\n   * @see #load(Request, int)\n   */\n  public static final class Result {\n    private final DokitPicasso.LoadedFrom loadedFrom;\n    private final Bitmap bitmap;\n    private final InputStream stream;\n    private final int exifOrientation;\n\n    public Result(Bitmap bitmap, DokitPicasso.LoadedFrom loadedFrom) {\n      this(checkNotNull(bitmap, \"bitmap == null\"), null, loadedFrom, 0);\n    }\n\n    public Result(InputStream stream, DokitPicasso.LoadedFrom loadedFrom) {\n      this(null, checkNotNull(stream, \"stream == null\"), loadedFrom, 0);\n    }\n\n    Result(Bitmap bitmap, InputStream stream, DokitPicasso.LoadedFrom loadedFrom, int exifOrientation) {\n      if (!(bitmap != null ^ stream != null)) {\n        throw new AssertionError();\n      }\n      this.bitmap = bitmap;\n      this.stream = stream;\n      this.loadedFrom = checkNotNull(loadedFrom, \"loadedFrom == null\");\n      this.exifOrientation = exifOrientation;\n    }\n\n    /** The loaded {@link Bitmap}. Mutually exclusive with {@link #getStream()}. */\n    public Bitmap getBitmap() {\n      return bitmap;\n    }\n\n    /** A stream of image data. Mutually exclusive with {@link #getBitmap()}. */\n    public InputStream getStream() {\n      return stream;\n    }\n\n    /**\n     * Returns the resulting {@link DokitPicasso.LoadedFrom} generated from a\n     * {@link #load(Request, int)} call.\n     */\n    public DokitPicasso.LoadedFrom getLoadedFrom() {\n      return loadedFrom;\n    }\n\n    /**\n     * Returns the resulting EXIF orientation generated from a {@link #load(Request, int)} call.\n     * This is only accessible to built-in RequestHandlers.\n     */\n    int getExifOrientation() {\n      return exifOrientation;\n    }\n  }\n\n  /**\n   * Whether or not this {@link RequestHandler} can handle a request with the given {@link Request}.\n   */\n  public abstract boolean canHandleRequest(Request data);\n\n  /**\n   * Loads an image for the given {@link Request}.\n   *\n   * @param request the data from which the image should be resolved.\n   * @param networkPolicy the {@link NetworkPolicy} for this request.\n   */\n  public abstract Result load(Request request, int networkPolicy) throws IOException;\n\n  int getRetryCount() {\n    return 0;\n  }\n\n  boolean shouldRetry(boolean airplaneMode, NetworkInfo info) {\n    return false;\n  }\n\n  boolean supportsReplay() {\n    return false;\n  }\n\n  /**\n   * Lazily create {@link BitmapFactory.Options} based in given\n   * {@link Request}, only instantiating them if needed.\n   */\n  static BitmapFactory.Options createBitmapOptions(Request data) {\n    final boolean justBounds = data.hasSize();\n    final boolean hasConfig = data.config != null;\n    BitmapFactory.Options options = null;\n    if (justBounds || hasConfig) {\n      options = new BitmapFactory.Options();\n      options.inJustDecodeBounds = justBounds;\n      if (hasConfig) {\n        options.inPreferredConfig = data.config;\n      }\n    }\n    return options;\n  }\n\n  static boolean requiresInSampleSize(BitmapFactory.Options options) {\n    return options != null && options.inJustDecodeBounds;\n  }\n\n  static void calculateInSampleSize(int reqWidth, int reqHeight, BitmapFactory.Options options,\n      Request request) {\n    calculateInSampleSize(reqWidth, reqHeight, options.outWidth, options.outHeight, options,\n        request);\n  }\n\n  static void calculateInSampleSize(int reqWidth, int reqHeight, int width, int height,\n      BitmapFactory.Options options, Request request) {\n    int sampleSize = 1;\n    if (height > reqHeight || width > reqWidth) {\n      final int heightRatio;\n      final int widthRatio;\n      if (reqHeight == 0) {\n        sampleSize = (int) Math.floor((float) width / (float) reqWidth);\n      } else if (reqWidth == 0) {\n        sampleSize = (int) Math.floor((float) height / (float) reqHeight);\n      } else {\n        heightRatio = (int) Math.floor((float) height / (float) reqHeight);\n        widthRatio = (int) Math.floor((float) width / (float) reqWidth);\n        sampleSize = request.centerInside\n            ? Math.max(heightRatio, widthRatio)\n            : Math.min(heightRatio, widthRatio);\n      }\n    }\n    options.inSampleSize = sampleSize;\n    options.inJustDecodeBounds = false;\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/ResourceRequestHandler.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport java.io.IOException;\n\nimport static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.LoadedFrom.DISK;\n\nclass ResourceRequestHandler extends RequestHandler {\n  private final Context context;\n\n  ResourceRequestHandler(Context context) {\n    this.context = context;\n  }\n\n  @Override public boolean canHandleRequest(Request data) {\n    if (data.resourceId != 0) {\n      return true;\n    }\n\n    return SCHEME_ANDROID_RESOURCE.equals(data.uri.getScheme());\n  }\n\n  @Override public Result load(Request request, int networkPolicy) throws IOException {\n    Resources res = Utils.getResources(context, request);\n    int id = Utils.getResourceId(res, request);\n    return new Result(decodeResource(res, id, request), DISK);\n  }\n\n  private static Bitmap decodeResource(Resources resources, int id, Request data) {\n    final BitmapFactory.Options options = createBitmapOptions(data);\n    if (requiresInSampleSize(options)) {\n      BitmapFactory.decodeResource(resources, id, options);\n      calculateInSampleSize(data.targetWidth, data.targetHeight, options, data);\n    }\n    return BitmapFactory.decodeResource(resources, id, options);\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/Stats.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.graphics.Bitmap;\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Looper;\nimport android.os.Message;\n\nimport static android.os.Process.THREAD_PRIORITY_BACKGROUND;\n\nclass Stats {\n  private static final int CACHE_HIT = 0;\n  private static final int CACHE_MISS = 1;\n  private static final int BITMAP_DECODE_FINISHED = 2;\n  private static final int BITMAP_TRANSFORMED_FINISHED = 3;\n  private static final int DOWNLOAD_FINISHED = 4;\n\n  private static final String STATS_THREAD_NAME = Utils.THREAD_PREFIX + \"Stats\";\n\n  final HandlerThread statsThread;\n  final Cache cache;\n  final Handler handler;\n\n  long cacheHits;\n  long cacheMisses;\n  long totalDownloadSize;\n  long totalOriginalBitmapSize;\n  long totalTransformedBitmapSize;\n  long averageDownloadSize;\n  long averageOriginalBitmapSize;\n  long averageTransformedBitmapSize;\n  int downloadCount;\n  int originalBitmapCount;\n  int transformedBitmapCount;\n\n  Stats(Cache cache) {\n    this.cache = cache;\n    this.statsThread = new HandlerThread(STATS_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);\n    this.statsThread.start();\n    Utils.flushStackLocalLeaks(statsThread.getLooper());\n    this.handler = new StatsHandler(statsThread.getLooper(), this);\n  }\n\n  void dispatchBitmapDecoded(Bitmap bitmap) {\n    processBitmap(bitmap, BITMAP_DECODE_FINISHED);\n  }\n\n  void dispatchBitmapTransformed(Bitmap bitmap) {\n    processBitmap(bitmap, BITMAP_TRANSFORMED_FINISHED);\n  }\n\n  void dispatchDownloadFinished(long size) {\n    handler.sendMessage(handler.obtainMessage(DOWNLOAD_FINISHED, size));\n  }\n\n  void dispatchCacheHit() {\n    handler.sendEmptyMessage(CACHE_HIT);\n  }\n\n  void dispatchCacheMiss() {\n    handler.sendEmptyMessage(CACHE_MISS);\n  }\n\n  void shutdown() {\n    statsThread.quit();\n  }\n\n  void performCacheHit() {\n    cacheHits++;\n  }\n\n  void performCacheMiss() {\n    cacheMisses++;\n  }\n\n  void performDownloadFinished(Long size) {\n    downloadCount++;\n    totalDownloadSize += size;\n    averageDownloadSize = getAverage(downloadCount, totalDownloadSize);\n  }\n\n  void performBitmapDecoded(long size) {\n    originalBitmapCount++;\n    totalOriginalBitmapSize += size;\n    averageOriginalBitmapSize = getAverage(originalBitmapCount, totalOriginalBitmapSize);\n  }\n\n  void performBitmapTransformed(long size) {\n    transformedBitmapCount++;\n    totalTransformedBitmapSize += size;\n    averageTransformedBitmapSize = getAverage(originalBitmapCount, totalTransformedBitmapSize);\n  }\n\n  StatsSnapshot createSnapshot() {\n    return new StatsSnapshot(cache.maxSize(), cache.size(), cacheHits, cacheMisses,\n        totalDownloadSize, totalOriginalBitmapSize, totalTransformedBitmapSize, averageDownloadSize,\n        averageOriginalBitmapSize, averageTransformedBitmapSize, downloadCount, originalBitmapCount,\n        transformedBitmapCount, System.currentTimeMillis());\n  }\n\n  private void processBitmap(Bitmap bitmap, int what) {\n    // Never send bitmaps to the handler as they could be recycled before we process them.\n    int bitmapSize = Utils.getBitmapBytes(bitmap);\n    handler.sendMessage(handler.obtainMessage(what, bitmapSize, 0));\n  }\n\n  private static long getAverage(int count, long totalSize) {\n    return totalSize / count;\n  }\n\n  private static class StatsHandler extends Handler {\n\n    private final Stats stats;\n\n    public StatsHandler(Looper looper, Stats stats) {\n      super(looper);\n      this.stats = stats;\n    }\n\n    @Override public void handleMessage(final Message msg) {\n      switch (msg.what) {\n        case CACHE_HIT:\n          stats.performCacheHit();\n          break;\n        case CACHE_MISS:\n          stats.performCacheMiss();\n          break;\n        case BITMAP_DECODE_FINISHED:\n          stats.performBitmapDecoded(msg.arg1);\n          break;\n        case BITMAP_TRANSFORMED_FINISHED:\n          stats.performBitmapTransformed(msg.arg1);\n          break;\n        case DOWNLOAD_FINISHED:\n          stats.performDownloadFinished((Long) msg.obj);\n          break;\n        default:\n          DokitPicasso.HANDLER.post(new Runnable() {\n            @Override public void run() {\n              throw new AssertionError(\"Unhandled stats message.\" + msg.what);\n            }\n          });\n      }\n    }\n  }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/StatsSnapshot.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.util.Log;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\n\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.TAG;\n\n/** Represents all stats for a {@link DokitPicasso} instance at a single point in time. */\npublic class StatsSnapshot {\n  public final int maxSize;\n  public final int size;\n  public final long cacheHits;\n  public final long cacheMisses;\n  public final long totalDownloadSize;\n  public final long totalOriginalBitmapSize;\n  public final long totalTransformedBitmapSize;\n  public final long averageDownloadSize;\n  public final long averageOriginalBitmapSize;\n  public final long averageTransformedBitmapSize;\n  public final int downloadCount;\n  public final int originalBitmapCount;\n  public final int transformedBitmapCount;\n\n  public final long timeStamp;\n\n  public StatsSnapshot(int maxSize, int size, long cacheHits, long cacheMisses,\n      long totalDownloadSize, long totalOriginalBitmapSize, long totalTransformedBitmapSize,\n      long averageDownloadSize, long averageOriginalBitmapSize, long averageTransformedBitmapSize,\n      int downloadCount, int originalBitmapCount, int transformedBitmapCount, long timeStamp) {\n    this.maxSize = maxSize;\n    this.size = size;\n    this.cacheHits = cacheHits;\n    this.cacheMisses = cacheMisses;\n    this.totalDownloadSize = totalDownloadSize;\n    this.totalOriginalBitmapSize = totalOriginalBitmapSize;\n    this.totalTransformedBitmapSize = totalTransformedBitmapSize;\n    this.averageDownloadSize = averageDownloadSize;\n    this.averageOriginalBitmapSize = averageOriginalBitmapSize;\n    this.averageTransformedBitmapSize = averageTransformedBitmapSize;\n    this.downloadCount = downloadCount;\n    this.originalBitmapCount = originalBitmapCount;\n    this.transformedBitmapCount = transformedBitmapCount;\n    this.timeStamp = timeStamp;\n  }\n\n  /** Prints out this {@link StatsSnapshot} into log. */\n  @SuppressWarnings(\"UnusedDeclaration\") public void dump() {\n    StringWriter logWriter = new StringWriter();\n    dump(new PrintWriter(logWriter));\n    Log.i(TAG, logWriter.toString());\n  }\n\n  /** Prints out this {@link StatsSnapshot} with the the provided {@link PrintWriter}. */\n  public void dump(PrintWriter writer) {\n    writer.println(\"===============BEGIN PICASSO STATS ===============\");\n    writer.println(\"Memory Cache Stats\");\n    writer.print(\"  Max Cache Size: \");\n    writer.println(maxSize);\n    writer.print(\"  Cache Size: \");\n    writer.println(size);\n    writer.print(\"  Cache % Full: \");\n    writer.println((int) Math.ceil((float) size / maxSize * 100));\n    writer.print(\"  Cache Hits: \");\n    writer.println(cacheHits);\n    writer.print(\"  Cache Misses: \");\n    writer.println(cacheMisses);\n    writer.println(\"Network Stats\");\n    writer.print(\"  Download Count: \");\n    writer.println(downloadCount);\n    writer.print(\"  Total Download Size: \");\n    writer.println(totalDownloadSize);\n    writer.print(\"  Average Download Size: \");\n    writer.println(averageDownloadSize);\n    writer.println(\"Bitmap Stats\");\n    writer.print(\"  Total Bitmaps Decoded: \");\n    writer.println(originalBitmapCount);\n    writer.print(\"  Total Bitmap Size: \");\n    writer.println(totalOriginalBitmapSize);\n    writer.print(\"  Total Transformed Bitmaps: \");\n    writer.println(transformedBitmapCount);\n    writer.print(\"  Total Transformed Bitmap Size: \");\n    writer.println(totalTransformedBitmapSize);\n    writer.print(\"  Average Bitmap Size: \");\n    writer.println(averageOriginalBitmapSize);\n    writer.print(\"  Average Transformed Bitmap Size: \");\n    writer.println(averageTransformedBitmapSize);\n    writer.println(\"===============END PICASSO STATS ===============\");\n    writer.flush();\n  }\n\n  @Override public String toString() {\n    return \"StatsSnapshot{\"\n        + \"maxSize=\"\n        + maxSize\n        + \", size=\"\n        + size\n        + \", cacheHits=\"\n        + cacheHits\n        + \", cacheMisses=\"\n        + cacheMisses\n        + \", downloadCount=\"\n        + downloadCount\n        + \", totalDownloadSize=\"\n        + totalDownloadSize\n        + \", averageDownloadSize=\"\n        + averageDownloadSize\n        + \", totalOriginalBitmapSize=\"\n        + totalOriginalBitmapSize\n        + \", totalTransformedBitmapSize=\"\n        + totalTransformedBitmapSize\n        + \", averageOriginalBitmapSize=\"\n        + averageOriginalBitmapSize\n        + \", averageTransformedBitmapSize=\"\n        + averageTransformedBitmapSize\n        + \", originalBitmapCount=\"\n        + originalBitmapCount\n        + \", transformedBitmapCount=\"\n        + transformedBitmapCount\n        + \", timeStamp=\"\n        + timeStamp\n        + '}';\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/Target.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\n\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.LoadedFrom;\n\n/**\n * Represents an arbitrary listener for image loading.\n * <p>\n * Objects implementing this class <strong>must</strong> have a working implementation of\n * {@link Object#equals(Object)} and {@link Object#hashCode()} for proper storage internally.\n * Instances of this interface will also be compared to determine if view recycling is occurring.\n * It is recommended that you add this interface directly on to a custom view type when using in an\n * adapter to ensure correct recycling behavior.\n */\npublic interface Target {\n  /**\n   * Callback when an image has been successfully loaded.\n   * <p>\n   * <strong>Note:</strong> You must not recycle the bitmap.\n   */\n  void onBitmapLoaded(Bitmap bitmap, LoadedFrom from);\n\n  /**\n   * Callback indicating the image could not be successfully loaded.\n   * <p>\n   * <strong>Note:</strong> The passed {@link Drawable} may be {@code null} if none has been\n   * specified via {@link RequestCreator#error(android.graphics.drawable.Drawable)}\n   * or {@link RequestCreator#error(int)}.\n   */\n  void onBitmapFailed(Drawable errorDrawable);\n\n  /**\n   * Callback invoked right before your request is submitted.\n   * <p>\n   * <strong>Note:</strong> The passed {@link Drawable} may be {@code null} if none has been\n   * specified via {@link RequestCreator#placeholder(android.graphics.drawable.Drawable)}\n   * or {@link RequestCreator#placeholder(int)}.\n   */\n  void onPrepareLoad(Drawable placeHolderDrawable);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/TargetAction.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\n\nfinal class TargetAction extends Action<Target> {\n\n  TargetAction(DokitPicasso picasso, Target target, Request data, int memoryPolicy, int networkPolicy,\n               Drawable errorDrawable, String key, Object tag, int errorResId) {\n    super(picasso, target, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key, tag,\n        false);\n  }\n\n  @Override void complete(Bitmap result, DokitPicasso.LoadedFrom from) {\n    if (result == null) {\n      throw new AssertionError(\n          String.format(\"Attempted to complete action with no result!\\n%s\", this));\n    }\n    Target target = getTarget();\n    if (target != null) {\n      target.onBitmapLoaded(result, from);\n      if (result.isRecycled()) {\n        throw new IllegalStateException(\"Target callback must not recycle bitmap!\");\n      }\n    }\n  }\n\n  @Override void error() {\n    Target target = getTarget();\n    if (target != null) {\n      if (errorResId != 0) {\n        target.onBitmapFailed(picasso.context.getResources().getDrawable(errorResId));\n      } else {\n        target.onBitmapFailed(errorDrawable);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/Transformation.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.graphics.Bitmap;\n\n/** Image transformation. */\npublic interface Transformation {\n  /**\n   * Transform the source bitmap into a new bitmap. If you create a new bitmap instance, you must\n   * call {@link android.graphics.Bitmap#recycle()} on {@code source}. You may return the original\n   * if no transformation is required.\n   */\n  Bitmap transform(Bitmap source);\n\n  /**\n   * Returns a unique key for the transformation, used for caching purposes. If the transformation\n   * has parameters (e.g. size, scale factor, etc) then these should be part of the key.\n   */\n  String key();\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/UrlConnectionDownloader.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.content.Context;\nimport android.net.Uri;\nimport android.net.http.HttpResponseCache;\nimport android.os.Build;\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\n\nimport static com.didichuxing.doraemonkit.picasso.Utils.parseResponseSourceHeader;\n\n/**\n * A {@link Downloader} which uses {@link HttpURLConnection} to download images. A disk cache of 2%\n * of the total available space will be used (capped at 50MB) will automatically be installed in the\n * application's cache directory, when available.\n */\npublic class UrlConnectionDownloader implements Downloader {\n  static final String RESPONSE_SOURCE = \"X-Android-Response-Source\";\n  static volatile Object cache;\n\n  private static final Object lock = new Object();\n  private static final String FORCE_CACHE = \"only-if-cached,max-age=2147483647\";\n  private static final ThreadLocal<StringBuilder> CACHE_HEADER_BUILDER =\n      new ThreadLocal<StringBuilder>() {\n        @Override protected StringBuilder initialValue() {\n          return new StringBuilder();\n        }\n      };\n\n  private final Context context;\n\n  public UrlConnectionDownloader(Context context) {\n    this.context = context.getApplicationContext();\n  }\n\n  protected HttpURLConnection openConnection(Uri path) throws IOException {\n    HttpURLConnection connection = (HttpURLConnection) new URL(path.toString()).openConnection();\n    connection.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT_MILLIS);\n    connection.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT_MILLIS);\n    return connection;\n  }\n\n  @Override public Response load(Uri uri, int networkPolicy) throws IOException {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {\n      installCacheIfNeeded(context);\n    }\n\n    HttpURLConnection connection = openConnection(uri);\n    connection.setUseCaches(true);\n\n    if (networkPolicy != 0) {\n      String headerValue;\n\n      if (NetworkPolicy.isOfflineOnly(networkPolicy)) {\n        headerValue = FORCE_CACHE;\n      } else {\n        StringBuilder builder = CACHE_HEADER_BUILDER.get();\n        builder.setLength(0);\n\n        if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {\n          builder.append(\"no-cache\");\n        }\n        if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {\n          if (builder.length() > 0) {\n            builder.append(',');\n          }\n          builder.append(\"no-store\");\n        }\n\n        headerValue = builder.toString();\n      }\n\n      connection.setRequestProperty(\"Cache-Control\", headerValue);\n    }\n\n    int responseCode = connection.getResponseCode();\n    if (responseCode >= 300) {\n      connection.disconnect();\n      throw new ResponseException(responseCode + \" \" + connection.getResponseMessage(),\n          networkPolicy, responseCode);\n    }\n\n    long contentLength = connection.getHeaderFieldInt(\"Content-Length\", -1);\n    boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));\n\n    return new Response(connection.getInputStream(), fromCache, contentLength);\n  }\n\n  @Override public void shutdown() {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && cache != null) {\n      ResponseCacheIcs.close(cache);\n    }\n  }\n\n  private static void installCacheIfNeeded(Context context) {\n    // DCL + volatile should be safe after Java 5.\n    if (cache == null) {\n      try {\n        synchronized (lock) {\n          if (cache == null) {\n            cache = ResponseCacheIcs.install(context);\n          }\n        }\n      } catch (IOException ignored) {\n      }\n    }\n  }\n\n  private static class ResponseCacheIcs {\n    static Object install(Context context) throws IOException {\n      File cacheDir = Utils.createDefaultCacheDir(context);\n      HttpResponseCache cache = HttpResponseCache.getInstalled();\n      if (cache == null) {\n        long maxSize = Utils.calculateDiskCacheSize(cacheDir);\n        cache = HttpResponseCache.install(cacheDir, maxSize);\n      }\n      return cache;\n    }\n\n    static void close(Object cache) {\n      try {\n        ((HttpResponseCache) cache).close();\n      } catch (IOException ignored) {\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/Utils.java",
    "content": "/*\n * Copyright (C) 2013 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didichuxing.doraemonkit.picasso;\n\nimport android.annotation.TargetApi;\nimport android.app.ActivityManager;\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\nimport android.os.Process;\nimport android.os.StatFs;\nimport android.provider.Settings;\nimport android.util.Log;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.List;\nimport java.util.concurrent.ThreadFactory;\n\nimport static android.content.Context.ACTIVITY_SERVICE;\nimport static android.content.pm.ApplicationInfo.FLAG_LARGE_HEAP;\nimport static android.os.Build.VERSION.SDK_INT;\nimport static android.os.Build.VERSION_CODES.HONEYCOMB;\nimport static android.os.Build.VERSION_CODES.HONEYCOMB_MR1;\nimport static android.os.Process.THREAD_PRIORITY_BACKGROUND;\nimport static android.provider.Settings.System.AIRPLANE_MODE_ON;\nimport static com.didichuxing.doraemonkit.picasso.DokitPicasso.TAG;\nimport static java.lang.String.format;\n\nfinal class Utils {\n  static final String THREAD_PREFIX = \"Picasso-\";\n  static final String THREAD_IDLE_NAME = THREAD_PREFIX + \"Idle\";\n  static final int DEFAULT_READ_TIMEOUT_MILLIS = 20 * 1000; // 20s\n  static final int DEFAULT_WRITE_TIMEOUT_MILLIS = 20 * 1000; // 20s\n  static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 15 * 1000; // 15s\n  private static final String PICASSO_CACHE = \"picasso-cache\";\n  private static final int KEY_PADDING = 50; // Determined by exact science.\n  private static final int MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024; // 5MB\n  private static final int MAX_DISK_CACHE_SIZE = 50 * 1024 * 1024; // 50MB\n  static final int THREAD_LEAK_CLEANING_MS = 1000;\n  static final char KEY_SEPARATOR = '\\n';\n\n  /** Thread confined to main thread for key creation. */\n  static final StringBuilder MAIN_THREAD_KEY_BUILDER = new StringBuilder();\n\n  /** Logging */\n  static final String OWNER_MAIN = \"Main\";\n  static final String OWNER_DISPATCHER = \"Dispatcher\";\n  static final String OWNER_HUNTER = \"Hunter\";\n  static final String VERB_CREATED = \"created\";\n  static final String VERB_CHANGED = \"changed\";\n  static final String VERB_IGNORED = \"ignored\";\n  static final String VERB_ENQUEUED = \"enqueued\";\n  static final String VERB_CANCELED = \"canceled\";\n  static final String VERB_BATCHED = \"batched\";\n  static final String VERB_RETRYING = \"retrying\";\n  static final String VERB_EXECUTING = \"executing\";\n  static final String VERB_DECODED = \"decoded\";\n  static final String VERB_TRANSFORMED = \"transformed\";\n  static final String VERB_JOINED = \"joined\";\n  static final String VERB_REMOVED = \"removed\";\n  static final String VERB_DELIVERED = \"delivered\";\n  static final String VERB_REPLAYING = \"replaying\";\n  static final String VERB_COMPLETED = \"completed\";\n  static final String VERB_ERRORED = \"errored\";\n  static final String VERB_PAUSED = \"paused\";\n  static final String VERB_RESUMED = \"resumed\";\n\n  /* WebP file header\n     0                   1                   2                   3\n     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n    |      'R'      |      'I'      |      'F'      |      'F'      |\n    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n    |                           File Size                           |\n    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n    |      'W'      |      'E'      |      'B'      |      'P'      |\n    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  */\n  private static final int WEBP_FILE_HEADER_SIZE = 12;\n  private static final String WEBP_FILE_HEADER_RIFF = \"RIFF\";\n  private static final String WEBP_FILE_HEADER_WEBP = \"WEBP\";\n\n  private Utils() {\n    // No instances.\n  }\n\n  static int getBitmapBytes(Bitmap bitmap) {\n    int result;\n    if (SDK_INT >= HONEYCOMB_MR1) {\n      result = BitmapHoneycombMR1.getByteCount(bitmap);\n    } else {\n      result = bitmap.getRowBytes() * bitmap.getHeight();\n    }\n    if (result < 0) {\n      throw new IllegalStateException(\"Negative size: \" + bitmap);\n    }\n    return result;\n  }\n\n  static <T> T checkNotNull(T value, String message) {\n    if (value == null) {\n      throw new NullPointerException(message);\n    }\n    return value;\n  }\n\n  static void checkNotMain() {\n    if (isMain()) {\n      throw new IllegalStateException(\"Method call should not happen from the main thread.\");\n    }\n  }\n\n  static void checkMain() {\n    if (!isMain()) {\n      throw new IllegalStateException(\"Method call should happen from the main thread.\");\n    }\n  }\n\n  static boolean isMain() {\n    return Looper.getMainLooper().getThread() == Thread.currentThread();\n  }\n\n  static String getLogIdsForHunter(BitmapHunter hunter) {\n    return getLogIdsForHunter(hunter, \"\");\n  }\n\n  static String getLogIdsForHunter(BitmapHunter hunter, String prefix) {\n    StringBuilder builder = new StringBuilder(prefix);\n    Action action = hunter.getAction();\n    if (action != null) {\n      builder.append(action.request.logId());\n    }\n    List<Action> actions = hunter.getActions();\n    if (actions != null) {\n      for (int i = 0, count = actions.size(); i < count; i++) {\n        if (i > 0 || action != null) builder.append(\", \");\n        builder.append(actions.get(i).request.logId());\n      }\n    }\n    return builder.toString();\n  }\n\n  static void log(String owner, String verb, String logId) {\n    log(owner, verb, logId, \"\");\n  }\n\n  static void log(String owner, String verb, String logId, String extras) {\n    Log.d(TAG, format(\"%1$-11s %2$-12s %3$s %4$s\", owner, verb, logId, extras));\n  }\n\n  static String createKey(Request data) {\n    String result = createKey(data, MAIN_THREAD_KEY_BUILDER);\n    MAIN_THREAD_KEY_BUILDER.setLength(0);\n    return result;\n  }\n\n  static String createKey(Request data, StringBuilder builder) {\n    if (data.stableKey != null) {\n      builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);\n      builder.append(data.stableKey);\n    } else if (data.uri != null) {\n      String path = data.uri.toString();\n      builder.ensureCapacity(path.length() + KEY_PADDING);\n      builder.append(path);\n    } else {\n      builder.ensureCapacity(KEY_PADDING);\n      builder.append(data.resourceId);\n    }\n    builder.append(KEY_SEPARATOR);\n\n    if (data.rotationDegrees != 0) {\n      builder.append(\"rotation:\").append(data.rotationDegrees);\n      if (data.hasRotationPivot) {\n        builder.append('@').append(data.rotationPivotX).append('x').append(data.rotationPivotY);\n      }\n      builder.append(KEY_SEPARATOR);\n    }\n    if (data.hasSize()) {\n      builder.append(\"resize:\").append(data.targetWidth).append('x').append(data.targetHeight);\n      builder.append(KEY_SEPARATOR);\n    }\n    if (data.centerCrop) {\n      builder.append(\"centerCrop\").append(KEY_SEPARATOR);\n    } else if (data.centerInside) {\n      builder.append(\"centerInside\").append(KEY_SEPARATOR);\n    }\n\n    if (data.transformations != null) {\n      //noinspection ForLoopReplaceableByForEach\n      for (int i = 0, count = data.transformations.size(); i < count; i++) {\n        builder.append(data.transformations.get(i).key());\n        builder.append(KEY_SEPARATOR);\n      }\n    }\n\n    return builder.toString();\n  }\n\n  static void closeQuietly(InputStream is) {\n    if (is == null) return;\n    try {\n      is.close();\n    } catch (IOException ignored) {\n    }\n  }\n\n  /** Returns {@code true} if header indicates the response body was loaded from the disk cache. */\n  static boolean parseResponseSourceHeader(String header) {\n    if (header == null) {\n      return false;\n    }\n    String[] parts = header.split(\" \", 2);\n    if (\"CACHE\".equals(parts[0])) {\n      return true;\n    }\n    if (parts.length == 1) {\n      return false;\n    }\n    try {\n      return \"CONDITIONAL_CACHE\".equals(parts[0]) && Integer.parseInt(parts[1]) == 304;\n    } catch (NumberFormatException e) {\n      return false;\n    }\n  }\n\n  static Downloader createDefaultDownloader(Context context) {\n    try {\n      Class.forName(\"com.squareup.okhttp.OkHttpClient\");\n      return OkHttpLoaderCreator.create(context);\n    } catch (ClassNotFoundException ignored) {\n    }\n    return new UrlConnectionDownloader(context);\n  }\n\n  static File createDefaultCacheDir(Context context) {\n    File cache = new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE);\n    if (!cache.exists()) {\n      //noinspection ResultOfMethodCallIgnored\n      cache.mkdirs();\n    }\n    return cache;\n  }\n\n  static long calculateDiskCacheSize(File dir) {\n    long size = MIN_DISK_CACHE_SIZE;\n\n    try {\n      StatFs statFs = new StatFs(dir.getAbsolutePath());\n      long available = ((long) statFs.getBlockCount()) * statFs.getBlockSize();\n      // Target 2% of the total space.\n      size = available / 50;\n    } catch (IllegalArgumentException ignored) {\n    }\n\n    // Bound inside min/max size for disk cache.\n    return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE);\n  }\n\n  static int calculateMemoryCacheSize(Context context) {\n    ActivityManager am = getService(context, ACTIVITY_SERVICE);\n    boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;\n    int memoryClass = am.getMemoryClass();\n    if (largeHeap && SDK_INT >= HONEYCOMB) {\n      memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);\n    }\n    // Target ~15% of the available heap.\n    return 1024 * 1024 * memoryClass / 7;\n  }\n\n  static boolean isAirplaneModeOn(Context context) {\n    ContentResolver contentResolver = context.getContentResolver();\n    try {\n      return Settings.System.getInt(contentResolver, AIRPLANE_MODE_ON, 0) != 0;\n    } catch (NullPointerException e) {\n      // https://github.com/square/picasso/issues/761, some devices might crash here, assume that\n      // airplane mode is off.\n      return false;\n    }\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  static <T> T getService(Context context, String service) {\n    return (T) context.getSystemService(service);\n  }\n\n  static boolean hasPermission(Context context, String permission) {\n    return context.checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;\n  }\n\n  static byte[] toByteArray(InputStream input) throws IOException {\n    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n    byte[] buffer = new byte[1024 * 4];\n    int n;\n    while (-1 != (n = input.read(buffer))) {\n      byteArrayOutputStream.write(buffer, 0, n);\n    }\n    return byteArrayOutputStream.toByteArray();\n  }\n\n  static boolean isWebPFile(InputStream stream) throws IOException {\n    byte[] fileHeaderBytes = new byte[WEBP_FILE_HEADER_SIZE];\n    boolean isWebPFile = false;\n    if (stream.read(fileHeaderBytes, 0, WEBP_FILE_HEADER_SIZE) == WEBP_FILE_HEADER_SIZE) {\n      // If a file's header starts with RIFF and end with WEBP, the file is a WebP file\n      isWebPFile = WEBP_FILE_HEADER_RIFF.equals(new String(fileHeaderBytes, 0, 4, \"US-ASCII\"))\n          && WEBP_FILE_HEADER_WEBP.equals(new String(fileHeaderBytes, 8, 4, \"US-ASCII\"));\n    }\n    return isWebPFile;\n  }\n\n  static int getResourceId(Resources resources, Request data) throws FileNotFoundException {\n    if (data.resourceId != 0 || data.uri == null) {\n      return data.resourceId;\n    }\n\n    String pkg = data.uri.getAuthority();\n    if (pkg == null) throw new FileNotFoundException(\"No package provided: \" + data.uri);\n\n    int id;\n    List<String> segments = data.uri.getPathSegments();\n    if (segments == null || segments.isEmpty()) {\n      throw new FileNotFoundException(\"No path segments: \" + data.uri);\n    } else if (segments.size() == 1) {\n      try {\n        id = Integer.parseInt(segments.get(0));\n      } catch (NumberFormatException e) {\n        throw new FileNotFoundException(\"Last path segment is not a resource ID: \" + data.uri);\n      }\n    } else if (segments.size() == 2) {\n      String type = segments.get(0);\n      String name = segments.get(1);\n\n      id = resources.getIdentifier(name, type, pkg);\n    } else {\n      throw new FileNotFoundException(\"More than two path segments: \" + data.uri);\n    }\n    return id;\n  }\n\n  static Resources getResources(Context context, Request data) throws FileNotFoundException {\n    if (data.resourceId != 0 || data.uri == null) {\n      return context.getResources();\n    }\n\n    String pkg = data.uri.getAuthority();\n    if (pkg == null) throw new FileNotFoundException(\"No package provided: \" + data.uri);\n    try {\n      PackageManager pm = context.getPackageManager();\n      return pm.getResourcesForApplication(pkg);\n    } catch (PackageManager.NameNotFoundException e) {\n      throw new FileNotFoundException(\"Unable to obtain resources for package: \" + data.uri);\n    }\n  }\n\n  /**\n   * Prior to Android 5, HandlerThread always keeps a stack local reference to the last message\n   * that was sent to it. This method makes sure that stack local reference never stays there\n   * for too long by sending new messages to it every second.\n   */\n  static void flushStackLocalLeaks(Looper looper) {\n    Handler handler = new Handler(looper) {\n      @Override public void handleMessage(Message msg) {\n        sendMessageDelayed(obtainMessage(), THREAD_LEAK_CLEANING_MS);\n      }\n    };\n    handler.sendMessageDelayed(handler.obtainMessage(), THREAD_LEAK_CLEANING_MS);\n  }\n\n  @TargetApi(HONEYCOMB)\n  private static class ActivityManagerHoneycomb {\n    static int getLargeMemoryClass(ActivityManager activityManager) {\n      return activityManager.getLargeMemoryClass();\n    }\n  }\n\n  static class PicassoThreadFactory implements ThreadFactory {\n    @SuppressWarnings(\"NullableProblems\")\n    @Override\n    public Thread newThread(Runnable r) {\n      return new PicassoThread(r);\n    }\n  }\n\n  private static class PicassoThread extends Thread {\n    public PicassoThread(Runnable r) {\n      super(r);\n    }\n\n    @Override public void run() {\n      Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);\n      super.run();\n    }\n  }\n\n  @TargetApi(HONEYCOMB_MR1)\n  private static class BitmapHoneycombMR1 {\n    static int getByteCount(Bitmap bitmap) {\n      return bitmap.getByteCount();\n    }\n  }\n\n  private static class OkHttpLoaderCreator {\n    static Downloader create(Context context) {\n      return new OkHttpDownloader(context);\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/reflection/BootstrapClass.java",
    "content": "package com.didichuxing.doraemonkit.reflection;\n\nimport static android.os.Build.VERSION.SDK_INT;\n\nimport android.os.Build;\nimport android.util.Log;\n\nimport java.lang.reflect.Method;\n\n/**\n * @author weishu\n * @date 2020/7/13.\n */\npublic final class BootstrapClass {\n\n    private static final String TAG = \"BootstrapClass\";\n\n    private static Object sVmRuntime;\n    private static Method setHiddenApiExemptions;\n\n    static {\n        if (SDK_INT >= Build.VERSION_CODES.P) {\n            try {\n                Method forName = Class.class.getDeclaredMethod(\"forName\", String.class);\n                Method getDeclaredMethod = Class.class.getDeclaredMethod(\"getDeclaredMethod\", String.class, Class[].class);\n\n                Class<?> vmRuntimeClass = (Class<?>) forName.invoke(null, \"dalvik.system.VMRuntime\");\n                Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, \"getRuntime\", null);\n                setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, \"setHiddenApiExemptions\", new Class[]{String[].class});\n                sVmRuntime = getRuntime.invoke(null);\n            } catch (Exception e) {\n                //Log.w(TAG, \"reflect bootstrap failed:\", e);\n            }\n        }\n    }\n\n    /**\n     * make the method exempted from hidden API check.\n     *\n     * @param method the method signature prefix.\n     * @return true if success.\n     */\n    public static boolean exempt(String method) {\n        return exempt(new String[]{method});\n    }\n\n    /**\n     * make specific methods exempted from hidden API check.\n     *\n     * @param methods the method signature prefix, such as \"Ldalvik/system\", \"Landroid\" or even \"L\"\n     * @return true if success\n     */\n    public static boolean exempt(String... methods) {\n        if (sVmRuntime == null || setHiddenApiExemptions == null) {\n            return false;\n        }\n\n        try {\n            setHiddenApiExemptions.invoke(sVmRuntime, new Object[]{methods});\n            return true;\n        } catch (Throwable e) {\n            return false;\n        }\n    }\n\n    /**\n     * Make all hidden API exempted.\n     *\n     * @return true if success.\n     */\n    public static boolean exemptAll() {\n        return exempt(new String[]{\"L\"});\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/reflection/Reflection.java",
    "content": "package com.didichuxing.doraemonkit.reflection;\n\nimport static android.os.Build.VERSION.SDK_INT;\nimport static com.didichuxing.doraemonkit.reflection.BootstrapClass.exemptAll;\n\nimport android.content.Context;\nimport android.os.Build;\nimport android.text.TextUtils;\nimport android.util.Base64;\n\nimport androidx.annotation.RequiresApi;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.lang.reflect.Method;\n\nimport dalvik.system.DexFile;\n\n/**\n * @author weishu\n * @date 2018/6/7.\n * https://github.com/tiann/FreeReflection\n */\n\n/**\n * @author weishu\n * @date 2020/7/13.\n */\npublic class Reflection {\n    private static final String TAG = \"Reflection\";\n    //封装了FreeReflection Library的dex\n    private static final String DEX = \"ZGV4CjAzNQCl4EprGS2pXI/v3OwlBrlfRnX5rmkKVdN0CwAAcAAAAHhWNBIAAAAAAAAAAMgKAABEAAAAcAAAABMAAACAAQAACwAAAMwBAAAMAAAAUAIAAA8AAACwAgAAAwAAACgDAADsBwAAiAMAABYGAAAYBgAAHQYAACcGAAAvBgAAPwYAAEsGAABbBgAAcAYAAIIGAACJBgAAkQYAAJQGAACYBgAAnAYAAKIGAAClBgAAqgYAAMUGAADrBgAABwcAABsHAAAuBwAARAcAAFgHAABsBwAAgAcAAJcHAACzBwAA2wcAAAIIAAAlCAAAMQgAAEIIAABLCAAAUAgAAFMIAABhCAAAbwgAAHMIAAB2CAAAeggAAI4IAACjCAAAuAgAAMEIAADaCAAA3QgAAOUIAADwCAAA+QgAAAoJAAAeCQAAMQkAAD0JAABFCQAAUgkAAGwJAAB0CQAAfQkAAJgJAAChCQAArQkAAMUJAADXCQAA3QkAAOUJAADzCQAACwAAABEAAAASAAAAEwAAABQAAAAVAAAAFwAAABgAAAAZAAAAGgAAABsAAAAcAAAAHQAAAB4AAAAjAAAAJwAAACkAAAAqAAAAKwAAAAwAAAAAAAAA3AUAAA0AAAAAAAAA5AUAAA4AAAAAAAAA7AUAAA8AAAACAAAAAAAAABAAAAAGAAAA+AUAABAAAAAKAAAAAAYAACMAAAAOAAAAAAAAACYAAAAOAAAACAYAACcAAAAPAAAAAAAAACgAAAAPAAAACAYAACgAAAAPAAAAEAYAAAIAAAA/AAAAAwAAACEAAAALAAcABAAAAAsABwAFAAAACwAPAAkAAAALAAcACgAAAAsAAAAkAAAACwAHACUAAAAMAAcAIgAAAAwABgA9AAAADAAKAD4AAAANAAcAIgAAAAEAAwAzAAAABAACAC4AAAAFAAUANAAAAAYABgADAAAACAAHADcAAAAKAAQANgAAAAsABgADAAAADAAGAAIAAAAMAAYAAwAAAAwACQAvAAAADAAKAC8AAAAMAAgAMAAAAA0ABgADAAAADQABAEEAAAANAAAAQgAAAAsAAAARAAAABgAAAAAAAAAIAAAAAAAAAHgKAABmCgAADAAAABEAAAAGAAAAAAAAAAcAAAAAAAAAjgoAAHIKAAANAAAAAQAAAAYAAAAAAAAAIAAAAAAAAACxCgAAdQoAAAEAAQABAAAAAwoAAAQAAABwEAMAAAAOAAoAAAADAAEACAoAAHsAAABgBQEAEwYcADRlbQAcBQUAGgYxABIXI3cQABIIHAkHAE0JBwhuMAIAZQcMARwFBQAaBjQAEicjdxAAEggcCQcATQkHCBIYHAkQAE0JBwhuMAIAZQcMAhIFEhYjZhEAEgcaCC0ATQgGB24wBQBRBgwEHwQFABIlI1URABIGGgc1AE0HBQYSFhIHTQcFBm4wBQBCBQwDHwMKABIlI1URABIGGgc+AE0HBQYSFhIXI3cQABIIHAkSAE0JBwhNBwUGbjAFAEIFDAUfBQoAaQUKABIFEgYjZhEAbjAFAFMGDAVpBQkADgANABoFBgAaBjsAcTABAGUAKPcAAAYAAABrAAEAAQEJcgEAAQABAAAANwoAAAQAAABwEAMAAAAOAAMAAQABAAAAPAoAAAsAAAASECMAEgASAU0CAAFxEAoAAAAKAA8AAAAIAAEAAwABAEIKAAAdAAAAEhESAmIDCQA4AwYAYgMKADkDBAABIQ8BYgMKAGIECQASFSNVEQASBk0HBQZuMAUAQwUo8g0AASEo7wAADAAAAA0AAQABAQkaAwAAAAEAAABSCgAADQAAABIQIwASABIBGgIPAE0CAAFxEAoAAAAKAA8AAAABAAEAAQAAAFcKAAAEAAAAcBADAAAADgAEAAEAAQAAAFwKAAAeAAAAEgBgAQEAEwIcADUhAwAPAHEACwAAAAoBOQH7/xoAMgBxEAQAAABuEAAAAwAMAFIAAABxEA4AAAAKACjqAQAAAAAAAAABAAAAAQAAAAMAAAAHAAcACQAAAAIAAAAGABEAAgAAAAcAEAABAAAABwAAAAEAAAASAAAAAzEuMAAIPGNsaW5pdD4ABjxpbml0PgAOQVBQTElDQVRJT05fSUQACkJVSUxEX1RZUEUADkJvb3RzdHJhcENsYXNzABNCb290c3RyYXBDbGFzcy5qYXZhABBCdWlsZENvbmZpZy5qYXZhAAVERUJVRwAGRkxBVk9SAAFJAAJJSQACSUwABElMTEwAAUwAA0xMTAAZTGFuZHJvaWQvY29udGVudC9Db250ZXh0OwAkTGFuZHJvaWQvY29udGVudC9wbS9BcHBsaWNhdGlvbkluZm87ABpMYW5kcm9pZC9vcy9CdWlsZCRWRVJTSU9OOwASTGFuZHJvaWQvdXRpbC9Mb2c7ABFMamF2YS9sYW5nL0NsYXNzOwAUTGphdmEvbGFuZy9DbGFzczwqPjsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5nL1N5c3RlbTsAFUxqYXZhL2xhbmcvVGhyb3dhYmxlOwAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsAJkxtZS93ZWlzaHUvZnJlZXJlZmxlY3Rpb24vQnVpbGRDb25maWc7ACVMbWUvd2Vpc2h1L3JlZmxlY3Rpb24vQm9vdHN0cmFwQ2xhc3M7ACFMbWUvd2Vpc2h1L3JlZmxlY3Rpb24vUmVmbGVjdGlvbjsAClJlZmxlY3Rpb24AD1JlZmxlY3Rpb24uamF2YQAHU0RLX0lOVAADVEFHAAFWAAxWRVJTSU9OX0NPREUADFZFUlNJT05fTkFNRQACVkwAAVoAAlpMABJbTGphdmEvbGFuZy9DbGFzczsAE1tMamF2YS9sYW5nL09iamVjdDsAE1tMamF2YS9sYW5nL1N0cmluZzsAB2NvbnRleHQAF2RhbHZpay5zeXN0ZW0uVk1SdW50aW1lAAFlAAZleGVtcHQACWV4ZW1wdEFsbAAHZm9yTmFtZQAPZnJlZS1yZWZsZWN0aW9uABJnZXRBcHBsaWNhdGlvbkluZm8AEWdldERlY2xhcmVkTWV0aG9kAApnZXRSdW50aW1lAAZpbnZva2UAC2xvYWRMaWJyYXJ5ABhtZS53ZWlzaHUuZnJlZXJlZmxlY3Rpb24ABm1ldGhvZAAHbWV0aG9kcwAZcmVmbGVjdCBib290c3RyYXAgZmFpbGVkOgAHcmVsZWFzZQAKc1ZtUnVudGltZQAWc2V0SGlkZGVuQXBpRXhlbXB0aW9ucwAQdGFyZ2V0U2RrVmVyc2lvbgAEdGhpcwAGdW5zZWFsAAx1bnNlYWxOYXRpdmUADnZtUnVudGltZUNsYXNzAAYABw4AFgAHDmr/AwEyCwEVEAMCNQvwBAREBhcBEg8DAzYLARsPqQUCBQMFBBkeAwAvCgAOAAcOACwBOgcOADYBOwcsnRriAQEDAC8KHgBIAAcOAA0ABw4AEwEtBx1yGWtaAAYXOBc8HxcABAEXAQEXBgEXHwYAAQACGQEZARkBGQEZARkGgYAEiAcDAAUACBoBCgEKB4iABKAHAYGABLQJAQnMCQGJAfQJAQnMCgEAAwALGgyBgAT4CgEJkAsBigIAAAAADgAAAAAAAAABAAAAAAAAAAEAAABEAAAAcAAAAAIAAAATAAAAgAEAAAMAAAALAAAAzAEAAAQAAAAMAAAAUAIAAAUAAAAPAAAAsAIAAAYAAAADAAAAKAMAAAEgAAAIAAAAiAMAAAEQAAAHAAAA3AUAAAIgAABEAAAAFgYAAAMgAAAIAAAAAwoAAAUgAAADAAAAZgoAAAAgAAADAAAAeAoAAAAQAAABAAAAyAoAAA==\";\n\n    private static native int unsealNative(int targetSdkVersion);\n\n    public static int unseal(Context context) {\n        if (SDK_INT < 28) {\n            // Below Android P, ignore\n            return 0;\n        }\n\n        // try exempt API first.\n        if (exemptAll()) {\n            return 0;\n        }\n        if (unsealByDexFile(context)) {\n            return 0;\n        }\n\n        return -1;\n    }\n\n    private static boolean unsealByDexFile(Context context) {\n        byte[] bytes = Base64.decode(DEX, Base64.NO_WRAP);\n        File codeCacheDir = null;\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {\n            codeCacheDir = getCodeCacheDir(context);\n        }\n        if (codeCacheDir == null) {\n            return false;\n        }\n        File code = new File(codeCacheDir, System.currentTimeMillis() + \".dex\");\n        try {\n\n            try (FileOutputStream fos = new FileOutputStream(code)) {\n                fos.write(bytes);\n            }\n\n            DexFile dexFile = new DexFile(code);\n            // This class is hardcoded in the dex, Don't use BootstrapClass.class to reference it\n            // it maybe obfuscated!!\n            Class<?> bootstrapClass = dexFile.loadClass(\"me.weishu.reflection.BootstrapClass\", null);\n            Method exemptAll = bootstrapClass.getDeclaredMethod(\"exemptAll\");\n            return (boolean) exemptAll.invoke(null);\n        } catch (Throwable e) {\n            e.printStackTrace();\n            return false;\n        } finally {\n            if (code.exists()) {\n                //noinspection ResultOfMethodCallIgnored\n                code.delete();\n            }\n        }\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    private static File getCodeCacheDir(Context context) {\n        if (context != null) {\n            return context.getCodeCacheDir();\n        }\n        String tmpDir = System.getProperty(\"java.io.tmpdir\");\n        if (TextUtils.isEmpty(tmpDir)) {\n            return null;\n        }\n        File tmp = new File(tmpDir);\n        if (!tmp.exists()) {\n            return null;\n        }\n        return tmp;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/ColorUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.graphics.Color;\nimport androidx.annotation.ColorInt;\n\n/**\n * Created by wanglikun on 2018/9/15.\n */\n\npublic class ColorUtil {\n\n    private ColorUtil() {\n    }\n\n    public static String parseColorInt(@ColorInt int color) {\n        return String.format(\"#%06X\", 0xFFFFFF & color);\n    }\n\n    public static boolean isColdColor(@ColorInt int color) {\n        float[] hsv = new float[3];\n        Color.colorToHSV(color, hsv);\n        return hsv[2] <= 0.8f;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DataCleanUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport java.io.File;\n\nimport android.content.Context;\nimport android.os.Environment;\nimport android.text.format.Formatter;\n\n/**\n * Created by wanglikun on 2018/11/17.\n */\npublic class DataCleanUtil {\n    private DataCleanUtil() {\n    }\n\n    /**\n     * @param context\n     */\n    public static void cleanInternalCache(Context context) {\n        DoKitFileUtil.deleteDirectory(context.getCacheDir());\n    }\n\n    /**\n     * @param context\n     */\n    public static void cleanDatabases(Context context) {\n        DoKitFileUtil.deleteDirectory(new File(context.getFilesDir().getParent() + \"/databases\"));\n    }\n\n    /**\n     * @param context\n     */\n    public static void cleanSharedPreference(Context context) {\n        DoKitFileUtil.deleteDirectory(new File(context.getFilesDir().getParent() + \"/shared_prefs\"));\n    }\n\n    /**\n     * @param context\n     */\n    public static void cleanFiles(Context context) {\n        DoKitFileUtil.deleteDirectory(context.getFilesDir());\n    }\n\n    /**\n     * @param context\n     */\n    public static void cleanExternalCache(Context context) {\n        if (Environment.getExternalStorageState().equals(\n                Environment.MEDIA_MOUNTED)) {\n            DoKitFileUtil.deleteDirectory(context.getExternalCacheDir());\n        }\n    }\n\n    /**\n     * @param filePath\n     */\n    public static void cleanCustomCache(String filePath) {\n        DoKitFileUtil.deleteDirectory(new File(filePath));\n    }\n\n    /**\n     * clean app data\n     *\n     * @param context\n     * @param filepath\n     */\n    public static void cleanApplicationData(Context context, String... filepath) {\n        cleanInternalCache(context);\n        cleanExternalCache(context);\n        cleanDatabases(context);\n        cleanSharedPreference(context);\n        cleanFiles(context);\n        if (filepath == null) {\n            return;\n        }\n        for (String filePath : filepath) {\n            cleanCustomCache(filePath);\n        }\n    }\n\n    public static long getApplicationDataSize(Context context) {\n//        long size = 0;\n//        // internal cache\n//        size += FileUtil.getDirectorySize(context.getCacheDir());\n//        // databases\n//        size += FileUtil.getDirectorySize(new File(context.getFilesDir().getParent() + \"/databases\"));\n//        // shared preference\n//        size += FileUtil.getDirectorySize(new File(context.getFilesDir().getParent() + \"/shared_prefs\"));\n//        // files\n//        size += FileUtil.getDirectorySize(context.getFilesDir());\n        return DoKitFileUtil.getDirectorySize(new File(PathUtils.getInternalAppDataPath()));\n    }\n\n    public static String getApplicationDataSizeStr(Context context) {\n        return Formatter.formatFileSize(context, getApplicationDataSize(context));\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DatabaseUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class DatabaseUtil {\n    private DatabaseUtil() {\n    }\n\n    public static List<String> queryTableName(SQLiteDatabase database) {\n        ArrayList<String> tableName = new ArrayList<>();\n\n        if (database == null) {\n            return tableName;\n        }\n        Cursor cursor = database.rawQuery(\"select name from sqlite_master where type='table' order by name\", null);\n        while (cursor.moveToNext()) {\n            tableName.add(cursor.getString(0));\n        }\n        cursor.close();\n        return tableName;\n    }\n\n    public static String[] queryTableColumnName(SQLiteDatabase database, String tableName) {\n        if (tableName == null || database == null) {\n            return new String[0];\n        }\n        Cursor cursor = database.query(tableName, null, null, null, null, null, null);\n        ArrayList<String> columnNames = new ArrayList<>(Arrays.asList(cursor.getColumnNames()));\n        cursor.close();\n        return columnNames.toArray(new String[0]);\n    }\n\n    public static String[][] queryAll(SQLiteDatabase database, String tableName) {\n        String[] strings = queryTableColumnName(database, tableName);\n        Cursor cursor = database.query(tableName, null, null, null, null, null, null);\n        int rowCount = cursor.getCount();\n        String[][] words = new String[strings.length][rowCount];\n        for (int y = 0; y < rowCount; y++) {\n            if (cursor.moveToNext()) {\n                for (int x = 0; x < strings.length; x++) {\n                    if (cursor.getType(x) == Cursor.FIELD_TYPE_BLOB) {\n                        words[x][y] = new String(cursor.getBlob(x));\n                    } else {\n                        words[x][y] = cursor.getString(x);\n                    }\n                }\n            }\n        }\n        cursor.close();\n        return words;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoKitCacheUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport com.didichuxing.doraemonkit.util.Utils;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InvalidClassException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.io.Serializable;\n\n/**\n * Created by wanglikun on 2018/11/17.\n */\npublic class DoKitCacheUtils {\n    private static final String TAG = \"CacheUtils\";\n\n\n    public static boolean saveObject(String key, Serializable ser) {\n        File file = new File(Utils.getApp().getCacheDir() + \"/\" + key);\n        if (!file.exists()) {\n            try {\n                file.createNewFile();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        return saveObject(ser, file);\n    }\n\n    public static Serializable readObject(String key) {\n        File file = new File(Utils.getApp().getCacheDir() + \"/\" + key);\n        return readObject(file);\n    }\n\n    public static boolean saveObject(Serializable ser, File file) {\n        FileOutputStream fos = null;\n        ObjectOutputStream oos = null;\n        try {\n            fos = new FileOutputStream(file);\n            oos = new ObjectOutputStream(fos);\n            oos.writeObject(ser);\n            oos.flush();\n            return true;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        } finally {\n            if (oos != null) {\n                try {\n                    oos.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n            if (fos != null) {\n                try {\n                    fos.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    public static Serializable readObject(File file) {\n        if (file == null || !file.exists() || file.isDirectory()) {\n            return null;\n        }\n        FileInputStream fis = null;\n        ObjectInputStream ois = null;\n        try {\n            fis = new FileInputStream(file);\n            ois = new ObjectInputStream(fis);\n            return (Serializable) ois.readObject();\n        } catch (IOException e) {\n            if (e instanceof InvalidClassException) {\n                file.delete();\n            }\n            e.printStackTrace();\n            return null;\n        } catch (ClassNotFoundException e) {\n            e.printStackTrace();\n            return null;\n        } finally {\n            if (fis != null) {\n                try {\n                    fis.close();\n                } catch (IOException e) {\n                    LogHelper.e(TAG, e.toString());\n                }\n            }\n            if (ois != null) {\n                try {\n                    ois.close();\n                } catch (IOException e) {\n                    LogHelper.e(TAG, e.toString());\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoKitClipboardUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.ClipData;\nimport android.content.ClipboardManager;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\n\nimport com.didichuxing.doraemonkit.util.Utils;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/09/25\n *     desc  : 剪贴板相关工具类\n * </pre>\n */\npublic final class DoKitClipboardUtils {\n\n    private DoKitClipboardUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * 复制文本到剪贴板\n     *\n     * @param text 文本\n     */\n    public static void copyText(final CharSequence text) {\n        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);\n        //noinspection ConstantConditions\n        cm.setPrimaryClip(ClipData.newPlainText(\"text\", text));\n    }\n\n    /**\n     * 获取剪贴板的文本\n     *\n     * @return 剪贴板的文本\n     */\n    public static CharSequence getText() {\n        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);\n        //noinspection ConstantConditions\n        ClipData clip = cm.getPrimaryClip();\n        if (clip != null && clip.getItemCount() > 0) {\n            return clip.getItemAt(0).coerceToText(Utils.getApp());\n        }\n        return null;\n    }\n\n    /**\n     * 复制uri到剪贴板\n     *\n     * @param uri uri\n     */\n    public static void copyUri(final Uri uri) {\n        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);\n        //noinspection ConstantConditions\n        cm.setPrimaryClip(ClipData.newUri(Utils.getApp().getContentResolver(), \"uri\", uri));\n    }\n\n    /**\n     * 获取剪贴板的uri\n     *\n     * @return 剪贴板的uri\n     */\n    public static Uri getUri() {\n        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);\n        //noinspection ConstantConditions\n        ClipData clip = cm.getPrimaryClip();\n        if (clip != null && clip.getItemCount() > 0) {\n            return clip.getItemAt(0).getUri();\n        }\n        return null;\n    }\n\n    /**\n     * 复制意图到剪贴板\n     *\n     * @param intent 意图\n     */\n    public static void copyIntent(final Intent intent) {\n        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);\n        //noinspection ConstantConditions\n        cm.setPrimaryClip(ClipData.newIntent(\"intent\", intent));\n    }\n\n    /**\n     * 获取剪贴板的意图\n     *\n     * @return 剪贴板的意图\n     */\n    public static Intent getIntent() {\n        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);\n        //noinspection ConstantConditions\n        ClipData clip = cm.getPrimaryClip();\n        if (clip != null && clip.getItemCount() > 0) {\n            return clip.getItemAt(0).getIntent();\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoKitCommUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.content.res.Resources;\n\nimport androidx.annotation.StringRes;\n\nimport com.didichuxing.doraemonkit.DoKitEnv;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.IOException;\n\nimport okhttp3.RequestBody;\nimport okio.Buffer;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/27-15:08\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class DoKitCommUtil {\n\n    public static String getString(@StringRes int stringId) {\n        return DoKitEnv.requireApp().getString(stringId);\n    }\n\n    @StringRes\n    public static int getStringId(String str) {\n        try {\n            Resources r = DoKitEnv.requireApp().getResources();\n            return r.getIdentifier(str, \"string\", DoKitEnv.requireApp().getPackageName());\n        } catch (Exception e) {\n            LogHelper.e(\"getStringId\", \"getStringId===>\" + str);\n        }\n        return -1;\n    }\n\n    public static String requestBodyToString(RequestBody requestBody) {\n        try {\n            Buffer buffer = new Buffer();\n            requestBody.writeTo(buffer);\n            return buffer.readUtf8();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return \"\";\n    }\n\n    /**\n     * 键值对字符串转json\n     *\n     * @param param\n     * @return\n     */\n    public static String param2Json(String param) throws JSONException {\n        String[] params = param.split(\"&\");\n        JSONObject jsonObject = new JSONObject();\n\n        for (String p : params) {\n            String[] ps = p.split(\"=\");\n            if (ps.length == 2) {\n                String key = ps[0];\n                String value = ps[1];\n                jsonObject.put(key, value);\n            }\n        }\n\n        return jsonObject.toString();\n    }\n\n\n    /**\n     * 切换App到前台\n     */\n    public static void changeAppOnForeground(Class<? extends Activity> clazz) {\n        Intent intent = new Intent(DoKitEnv.requireApp(), clazz);\n        intent.addCategory(Intent.CATEGORY_LAUNCHER);\n        intent.setAction(Intent.ACTION_MAIN);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);\n        DoKitEnv.requireApp().startActivity(intent);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoKitExecutorUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.SynchronousQueue;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Created by wanglikun on 2018/11/15.\n */\n\npublic class DoKitExecutorUtil {\n    private static ExecutorService sExecutorService;\n\n    private DoKitExecutorUtil() {\n    }\n\n    public static void execute(Runnable r) {\n        if (sExecutorService == null) {\n            sExecutorService = new ThreadPoolExecutor(1, 5, 60L, TimeUnit.SECONDS,\n                    new SynchronousQueue<Runnable>(),\n                    new ThreadPoolExecutor.AbortPolicy());\n\n        }\n        sExecutorService.execute(r);\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoKitFileUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\nimport androidx.core.content.FileProvider;\nimport android.text.format.Formatter;\n\nimport java.io.File;\nimport java.util.Locale;\n\n/**\n * Created by wanglikun on 2018/10/17.\n */\n\npublic class DoKitFileUtil {\n    private static final String TAG = \"FileUtil\";\n\n    public static final String TXT = \"txt\";\n    public static final String JPG = \"jpg\";\n    public static final String DB = \"db\";\n    public static final String SHARED_PREFS = \"shared_prefs\";\n    public static final String XML = \".xml\";\n\n    private DoKitFileUtil() {\n    }\n\n    public static String getFileSize(Context context, File file) {\n        if (!file.exists() || !file.isFile()) {\n            return null;\n        }\n        return Formatter.formatFileSize(context, file.length());\n    }\n\n    public static String getSuffix(File file) {\n        if (file == null || !file.exists()) {\n            return \"\";\n        }\n        return file.getName()\n                .substring(file.getName().lastIndexOf(\".\") + 1)\n                .toLowerCase(Locale.getDefault());\n    }\n\n    public static void systemShare(Context context, File file) {\n        if (file == null || !file.exists()) {\n            return;\n        }\n        Intent intent = new Intent(Intent.ACTION_SEND);\n        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        Uri uri;\n        try {\n            uri = FileProvider.getUriForFile(context, context.getPackageName() + \".debugfileprovider\", file);\n            String type = context.getContentResolver().getType(uri);\n            intent.setDataAndType(uri, type);\n            intent.putExtra(Intent.EXTRA_STREAM, uri);\n            if (intent.resolveActivity(context.getPackageManager()) == null) {\n                intent.setDataAndType(uri, \"*/*\");\n            }\n            context.startActivity(intent);\n        } catch (Exception e) {\n            LogHelper.e(TAG,\n                    \"The selected file can't be shared: \" + file.toString());\n        }\n    }\n\n    public static boolean isImage(File file) {\n        if (file == null) {\n            return false;\n        }\n        String suffix = getSuffix(file);\n        return \"jpg\".equals(suffix) || \"jpeg\".equals(suffix) || \"png\".equals(suffix) || \"bmp\".equals(suffix);\n    }\n\n    public static boolean isVideo(File file) {\n        if (file == null) {\n            return false;\n        }\n        String suffix = getSuffix(file);\n        return \"3gp\".equals(suffix) || \"mp4\".equals(suffix) || \"mkv\".equals(suffix) || \"webm\".equals(suffix);\n    }\n\n    public static boolean isDB(File file) {\n        if (file == null) {\n            return false;\n        }\n        String suffix = getSuffix(file);\n        return \"db\".equals(suffix);\n    }\n\n    public static boolean isSp(File file) {\n        File parentFile = file.getParentFile();\n        if (parentFile != null && parentFile.getName().equals(SHARED_PREFS) && file.getName().contains(XML)) {\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * @param file\n     */\n    public static void deleteDirectory(File file) {\n        if (file.isDirectory()) {\n            File[] listFiles = file.listFiles();\n            for (File f : listFiles) {\n                deleteDirectory(f);\n            }\n            file.delete();\n        } else {\n            file.delete();\n        }\n    }\n\n    public static long getDirectorySize(File directory) {\n        long size = 0;\n        File[] listFiles = directory.listFiles();\n        if (listFiles == null) {\n            return size;\n        }\n        for (File file : listFiles) {\n            if (file.isDirectory()) {\n                size += getDirectorySize(file);\n            } else {\n                size += file.length();\n            }\n        }\n        return size;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoKitFormatUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport java.util.Date;\n\n/**\n * Created by wanglikun on 2019-06-12\n */\npublic class DoKitFormatUtil {\n    private DoKitFormatUtil() {\n    }\n\n    public static String format(long time) {\n        return new Date(time).toString();\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoKitImageUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\n\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\n\n/**\n * Created by wanglikun on 2018/10/30.\n */\n\npublic class DoKitImageUtil {\n    private DoKitImageUtil() {\n    }\n\n    public static Bitmap decodeSampledBitmapFromFilePath(String imagePath,\n                                                         int reqWidth, int reqHeight) {\n        final BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inJustDecodeBounds = true;\n        BitmapFactory.decodeFile(imagePath, options);\n        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);\n        options.inJustDecodeBounds = false;\n        return BitmapFactory.decodeFile(imagePath,options);\n    }\n\n    public static int calculateInSampleSize(BitmapFactory.Options options,\n                                            int reqWidth, int reqHeight) {\n        final int height = options.outHeight;\n        final int width = options.outWidth;\n        int inSampleSize = 1;\n        if (height > reqHeight || width > reqWidth) {\n            final int heightRatio = Math.round((float) height / (float) reqHeight);\n            final int widthRatio = Math.round((float) width / (float) reqWidth);\n            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;\n        }\n        return inSampleSize;\n    }\n\n    /**\n     * 获得像素点的信息\n     * @param bitmap\n     * @param x\n     * @param y\n     * @return\n     */\n    public static int getPixel(Bitmap bitmap, int x, int y) {\n        if (bitmap == null) {\n            return -1;\n        }\n        if (x < 0 || x > bitmap.getWidth()) {\n            return -1;\n        }\n        if (y < 0 || y > bitmap.getHeight()) {\n            return -1;\n        }\n        return bitmap.getPixel(x, y);\n    }\n\n    public static boolean bitmap2File(Bitmap bitmap, int quality, File output) {\n        try {\n            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(output));\n            bitmap.compress(Bitmap.CompressFormat.PNG, quality, bos);\n            bos.flush();\n            bos.close();\n            if (output.exists()){\n                return true;\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoKitJsonUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.text.TextUtils;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonPrimitive;\nimport com.google.gson.reflect.TypeToken;\n\nimport java.lang.reflect.Type;\nimport java.util.ArrayList;\n\npublic class DoKitJsonUtil {\n\n    private static final String TAG = \"JsonUtil\";\n    private static final Gson gson;\n\n    static {\n        GsonBuilder builder = new GsonBuilder();\n        //Add strategy if needed.\n        gson = builder.create();\n    }\n\n    public static String jsonFromObject(Object object) {\n        if (object == null) {\n            return \"{}\";\n        }\n        try {\n            return gson.toJson(object);\n        } catch (Throwable e) {\n            e.printStackTrace();\n            return \"{}\";\n        }\n    }\n\n    public static <T> T objectFromJson(String json, Class<T> klass) {\n        if (TextUtils.isEmpty(json)) {\n            return null;\n        }\n        if (klass == null) {\n            return null;\n        }\n        try {\n            return gson.fromJson(json, klass);\n        } catch (Throwable e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    public static <T> ArrayList<T> jsonToList(String json, Class<T> classOfT) {\n        ArrayList<T> listOfT = new ArrayList<>();\n\n        Type type;\n        if (classOfT.isPrimitive()) {\n            type = new TypeToken<ArrayList<JsonPrimitive>>() {\n            }.getType();\n\n            ArrayList<JsonPrimitive> jsonObjs = gson.fromJson(json, type);\n\n            for (JsonPrimitive jsonObj : jsonObjs) {\n                listOfT.add(gson.fromJson(jsonObj, classOfT));\n            }\n        } else {\n            type = new TypeToken<ArrayList<JsonObject>>() {\n            }.getType();\n            ArrayList<JsonObject> jsonObjs = gson.fromJson(json, type);\n\n            for (JsonObject jsonObj : jsonObjs) {\n                listOfT.add(gson.fromJson(jsonObj, classOfT));\n            }\n        }\n\n        return listOfT;\n    }\n\n    /**\n     * 判断一个json字符串是不是为空，可能是空字符串，或者空的json串\n     *\n     * @param json\n     * @return\n     */\n    public static boolean isEmpty(String json) {\n        if (TextUtils.isEmpty(json)) {\n            return true;\n        }\n        if (TextUtils.equals(json, \"{}\")) {\n            return true;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoKitMiscUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\n\nimport android.content.Context;\nimport android.graphics.Paint;\nimport android.view.View;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020-01-03-10:49\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class DoKitMiscUtil {\n\n    /**\n     * 测量 View\n     *\n     * @param measureSpec\n     * @param defaultSize View 的默认大小\n     * @return\n     */\n    public static int measure(int measureSpec, int defaultSize) {\n        int result = defaultSize;\n        int specMode = View.MeasureSpec.getMode(measureSpec);\n        int specSize = View.MeasureSpec.getSize(measureSpec);\n\n        if (specMode == View.MeasureSpec.EXACTLY) {\n            result = specSize;\n        } else if (specMode == View.MeasureSpec.AT_MOST) {\n            result = Math.min(result, specSize);\n        }\n        return result;\n    }\n\n    /**\n     * dip 转换成px\n     *\n     * @param dip\n     * @return\n     */\n    public static int dipToPx(Context context, float dip) {\n        float density = context.getResources().getDisplayMetrics().density;\n        return (int) (dip * density + 0.5f * (dip >= 0 ? 1 : -1));\n    }\n\n    /**\n     * 获取数值精度格式化字符串\n     *\n     * @param precision\n     * @return\n     */\n    public static String getPrecisionFormat(int precision) {\n        return \"%.\" + precision + \"f\";\n    }\n\n    /**\n     * 反转数组\n     *\n     * @param arrays\n     * @param <T>\n     * @return\n     */\n    public static <T> T[] reverse(T[] arrays) {\n        if (arrays == null) {\n            return null;\n        }\n        int length = arrays.length;\n        for (int i = 0; i < length / 2; i++) {\n            T t = arrays[i];\n            arrays[i] = arrays[length - i - 1];\n            arrays[length - i - 1] = t;\n        }\n        return arrays;\n    }\n\n    /**\n     * 测量文字高度\n     * @param paint\n     * @return\n     */\n    public static float measureTextHeight(Paint paint) {\n        Paint.FontMetrics fontMetrics = paint.getFontMetrics();\n        return (Math.abs(fontMetrics.ascent) - fontMetrics.descent);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoKitNotificationUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.app.PendingIntent;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.graphics.BitmapFactory;\nimport android.os.Build;\nimport androidx.core.app.NotificationCompat;\nimport android.text.TextUtils;\n\nimport com.didichuxing.doraemonkit.R;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 管理notification\n *\n * @author vinda\n * @since 15/5/22\n */\npublic class DoKitNotificationUtils {\n    private static final String ID_HIGH_CHANNEL = \"channel_1_oncar\";\n    private static final String NAME_HIGH_CHANNEL = \"channel_1_name_oncar\";\n\n    private static final String ID_LOW_CHANNEL = \"channel_low_onecar\";\n    private static final String NAME_LOW_CHANNEL = \"channel_name_low_onecar\";\n\n    private static NotificationManager sNotificationManager;\n    public static final int ID_SHOW_BLOCK_NOTIFICATION = 1001;\n\n\n    /**\n     * 文本消息\n     *\n     * @param notifyId    消息ID\n     * @param smallIconId 小图标\n     * @param title       标题\n     * @param summary     内容\n     */\n    public static void setMessageNotification(Context context, int notifyId, int smallIconId, CharSequence title, CharSequence summary) {\n        setMessageNotification(context, notifyId, smallIconId, title, summary, null);\n    }\n\n    /**\n     * 文本消息\n     *\n     * @param notifyId 消息ID\n     * @param title    标题\n     * @param summary  内容\n     * @param ticker   出现消息时状态栏的提示文字\n     */\n    public static void setMessageNotification(Context context, int notifyId, int smallIconId, CharSequence title, CharSequence summary, CharSequence ticker) {\n        setMessageNotification(context, notifyId, smallIconId, title, summary, ticker, null);\n    }\n\n    /**\n     * 文本消息\n     *\n     * @param notifyId      消息ID\n     * @param title         标题\n     * @param summary       内容\n     * @param ticker        出现消息时状态栏的提示文字\n     * @param pendingIntent 点击后的intent\n     */\n    public static void setMessageNotification(Context context, int notifyId, int smallIconId, CharSequence title, CharSequence summary, CharSequence ticker, PendingIntent pendingIntent) {\n        NotificationCompat.Builder builder;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            builder = new NotificationCompat.Builder(context, ID_HIGH_CHANNEL);\n        } else {\n            builder = new NotificationCompat.Builder(context);\n        }\n        builder.setSmallIcon(smallIconId)\n                .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.mipmap.dk_doraemon))\n                .setContentTitle(title)\n                .setContentText(summary)\n                .setAutoCancel(true)\n                .setProgress(0, 0, false);// Removes the progress bar\n        if (!TextUtils.isEmpty(ticker)) {\n            builder.setTicker(ticker);\n        }\n        if (pendingIntent != null) {\n            builder.setContentIntent(pendingIntent);\n        } else {\n            builder.setContentIntent(createPendingIntent(context));\n        }\n        NotificationManager manager = createNotificationManager(context);\n        manager.notify(notifyId, builder.build());\n    }\n\n    /**\n     * 显示消息中心的消息\n     *\n     * @param notifyId      消息ID\n     * @param title         标题\n     * @param summary       内容\n     * @param ticker        出现消息时状态栏的提示文字\n     * @param pendingIntent 点击后的intent\n     */\n    public static void setInfoNotification(Context context, int notifyId, CharSequence title, CharSequence summary, CharSequence ticker, PendingIntent pendingIntent) {\n        NotificationCompat.Builder builder;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            builder = new NotificationCompat.Builder(context, ID_HIGH_CHANNEL);\n        } else {\n            builder = new NotificationCompat.Builder(context);\n        }\n        builder.setSmallIcon(R.mipmap.dk_doraemon)\n                .setContentTitle(title)\n                .setContentText(summary)\n                .setAutoCancel(true)\n                .setProgress(0, 0, false);// Removes the progress bar\n        if (!TextUtils.isEmpty(ticker)) {\n            builder.setTicker(ticker);\n        }\n        if (pendingIntent != null) {\n            builder.setContentIntent(pendingIntent);\n        } else {\n            builder.setContentIntent(createPendingIntent(context));\n        }\n        NotificationManager manager = createNotificationManager(context);\n        manager.notify(notifyId, builder.build());\n    }\n\n    /**\n     * 设置进度通知\n     *\n     * @param notifyId 消息ID\n     * @param title    标题\n     * @param progress 进度（0-100）\n     */\n    public static void setProgressNotification(Context context, int notifyId, CharSequence title, int progress) {\n        setProgressNotification(context, notifyId, title, null, progress);\n    }\n\n    /**\n     * 设置下载进度通知\n     *\n     * @param notifyId 消息ID\n     * @param title    标题\n     * @param ticker   出现消息时状态栏的提示文字\n     * @param progress 进度（0-100）\n     */\n    public static void setProgressNotification(Context context, int notifyId, CharSequence title, CharSequence ticker, int progress) {\n        setProgressNotification(context, notifyId, title, ticker, progress, null);\n    }\n\n    /**\n     * 设置下载进度通知\n     *\n     * @param notifyId      消息ID\n     * @param title         标题\n     * @param ticker        出现消息时状态栏的提示文字\n     * @param progress      进度（0-100）\n     * @param pendingIntent 点击后的intent\n     */\n    public static void setProgressNotification(Context context, int notifyId, CharSequence title, CharSequence ticker, int progress, PendingIntent pendingIntent) {\n        NotificationCompat.Builder builder;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            builder = new NotificationCompat.Builder(context, ID_HIGH_CHANNEL);\n        } else {\n            builder = new NotificationCompat.Builder(context);\n        }\n        builder.setSmallIcon(android.R.drawable.stat_sys_download)\n                .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.mipmap.dk_doraemon))\n                .setContentTitle(title)\n                .setProgress(100, progress, progress == 0)\n                .setOngoing(progress < 100)\n                .setAutoCancel(progress == 100);\n        if (pendingIntent != null) {\n            builder.setContentIntent(pendingIntent);\n        } else {\n            builder.setContentIntent(createPendingIntent(context));\n        }\n        if (!TextUtils.isEmpty(ticker)) {\n            builder.setTicker(ticker);\n        }\n        NotificationManager manager = createNotificationManager(context);\n        manager.notify(notifyId, builder.build());\n    }\n\n    /**\n     * 取消通知\n     *\n     * @param notifyId 通知ID\n     */\n    public static void cancelNotification(Context context, int notifyId) {\n        NotificationManager manager = createNotificationManager(context);\n        manager.cancel(notifyId);\n    }\n\n\n    /**\n     * 取消所有通知\n     */\n    public static void cancelNotification(Context context) {\n        NotificationManager manager = createNotificationManager(context);\n        manager.cancelAll();\n    }\n\n    private static NotificationManager createNotificationManager(Context context) {\n        if (sNotificationManager != null) {\n            return sNotificationManager;\n        }\n        sNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);\n        // 适配>=7.0手机通知栏显示问题\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            NotificationChannel notificationHightChannel = new NotificationChannel(ID_HIGH_CHANNEL, NAME_HIGH_CHANNEL, NotificationManager.IMPORTANCE_HIGH);\n            NotificationChannel notificationLowChannel = new NotificationChannel(ID_LOW_CHANNEL, NAME_LOW_CHANNEL, NotificationManager.IMPORTANCE_LOW);\n            List<NotificationChannel> channelList = new ArrayList<>();\n            channelList.add(notificationLowChannel);\n            channelList.add(notificationHightChannel);\n            sNotificationManager.createNotificationChannels(channelList);\n        }\n        return sNotificationManager;\n    }\n\n    private static PendingIntent createPendingIntent(Context context) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            return PendingIntent.getBroadcast(context, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);\n        } else {\n            return PendingIntent.getBroadcast(context, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoKitPermissionUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.Manifest;\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.app.AppOpsManager;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.database.Cursor;\nimport android.hardware.Camera;\nimport android.location.Location;\nimport android.location.LocationManager;\nimport android.media.AudioRecord;\nimport android.media.MediaRecorder;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.os.Process;\nimport android.provider.ContactsContract;\nimport android.provider.Settings;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Size;\nimport androidx.core.app.ActivityCompat;\nimport androidx.core.content.ContextCompat;\nimport android.telephony.TelephonyManager;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\n\n/**\n * @desc: 权限检测工具类\n */\npublic class DoKitPermissionUtil {\n    private static final String TAG = \"PermissionUtil\";\n    //音频输入-麦克风\n    private final static int AUDIO_INPUT = MediaRecorder.AudioSource.MIC;\n    //采用频率\n    //44100是目前的标准，但是某些设备仍然支持22050，16000，11025\n    //采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级\n    private final static int AUDIO_SAMPLE_RATE = 16000;\n    //声道 单声道\n    private final static int AUDIO_CHANNEL = android.media.AudioFormat.CHANNEL_IN_MONO;\n    //编码\n    private final static int AUDIO_ENCODING = android.media.AudioFormat.ENCODING_PCM_16BIT;\n\n    private static final int OP_SYSTEM_ALERT_WINDOW = 24;\n\n    private DoKitPermissionUtil() {\n    }\n\n    /**\n     * 判断是否具有悬浮窗权限\n     * @param context\n     * @return\n     */\n    public static boolean canDrawOverlays(Context context) {\n        //android 6.0及以上的判断条件\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            return Settings.canDrawOverlays(context);\n        }\n        //android 4.4~6.0的判断条件\n        return checkOp(context, OP_SYSTEM_ALERT_WINDOW);\n    }\n\n    private static boolean checkOp(Context context, int op) {\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {\n            AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);\n            Class clazz = AppOpsManager.class;\n            try {\n                Method method = clazz.getDeclaredMethod(\"checkOp\", int.class, int.class, String.class);\n                return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Process.myUid(), context.getPackageName());\n            } catch (NoSuchMethodException e) {\n                e.printStackTrace();\n            } catch (IllegalAccessException e) {\n                e.printStackTrace();\n            } catch (InvocationTargetException e) {\n                e.printStackTrace();\n            }\n        }\n        return true;\n    }\n\n    /**\n     * 请求悬浮窗权限\n     * @param context\n     */\n    public static void requestDrawOverlays(Context context) {\n        Intent intent = new Intent(\"android.settings.action.MANAGE_OVERLAY_PERMISSION\", Uri.parse(\"package:\" + context.getPackageName()));\n        if (!(context instanceof Activity)) {\n            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        }\n        if (intent.resolveActivity(context.getPackageManager()) != null) {\n            context.startActivity(intent);\n        } else {\n            LogHelper.e(TAG, \"No activity to handle intent\");\n        }\n    }\n\n\n\n    public static boolean isMockLocationEnabled(Context context) {\n        boolean isMockLocation = false;\n        try {\n            //if marshmallow\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n                AppOpsManager opsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);\n                if (opsManager != null) {\n                    isMockLocation = (opsManager.checkOp(AppOpsManager.OPSTR_MOCK_LOCATION, android.os.Process.myUid(), context.getPackageName()) == AppOpsManager.MODE_ALLOWED);\n                }\n            } else {\n                // in marshmallow this will always return true\n                isMockLocation = !android.provider.Settings.Secure.getString(context.getContentResolver(), \"mock_location\").equals(\"0\");\n            }\n        } catch (Exception e) {\n            return false;\n        }\n        return isMockLocation;\n    }\n\n    /**\n     * TargetVersionSdk大于6.0时的权限检查方法\n     *\n     * @param context\n     * @param perms\n     * @return\n     */\n    public static boolean hasPermissions(@NonNull Context context,\n                                         @Size(min = 1) @NonNull String... perms) {\n        // Always return true for SDK < M, let the system deal with the permissions\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            Log.w(TAG, \"hasPermissions: API version < M, returning true by default\");\n\n            // DANGER ZONE!!! Changing this will break the library.\n            return true;\n        }\n\n        // Null context may be passed if we have detected Low API (less than M) so getting\n        // to this point with a null context should not be possible.\n        if (context == null) {\n            throw new IllegalArgumentException(\"Can't check permissions for null context\");\n        }\n\n        for (String perm : perms) {\n            if (ContextCompat.checkSelfPermission(context, perm)\n                    != PackageManager.PERMISSION_GRANTED) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * 检测是否有sd卡读写权限，不可靠。无sd卡或者磁盘满了的情况下，即使赋予了权限也可能报无权限\n     *\n     * @return\n     */\n    public static boolean checkStorageUnreliable() {\n        FileOutputStream outputStream = null;\n        FileInputStream inputStream = null;\n        try {\n            File file = new File(Environment.getExternalStorageDirectory() + File.separator + \".DoraemonkitTest.kit\");\n            outputStream = new FileOutputStream(file);\n            outputStream.write(1);\n            outputStream.flush();\n            outputStream.close();\n            outputStream = null;\n\n            inputStream = new FileInputStream(file);\n            inputStream.read();\n            inputStream.close();\n            inputStream = null;\n        } catch (Exception e) {\n            return false;\n        } finally {\n            try {\n                if (outputStream != null) {\n                    outputStream.close();\n                }\n                if (inputStream != null) {\n                    inputStream.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        return true;\n    }\n\n    /**\n     * 检测是否有定位权限，不可靠，比如在室内打开app，这种情况下定位模块没有值，会报无权限\n     *\n     * @return\n     */\n    @SuppressLint(\"MissingPermission\")\n    public static boolean checkLocationUnreliable(Context context) {\n        try {\n            LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);\n            if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED\n                    && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {\n                return false;\n            }\n            Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);\n            Location location2 = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);\n            return location != null || location2 != null;\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    /**\n     * 检测是否有相机权限\n     *\n     * @return\n     */\n    public static boolean checkCameraUnreliable() {\n        Camera camera = null;\n        try {\n            camera = android.hardware.Camera.open();\n            return camera != null;\n        } catch (Exception e) {\n            return false;\n        } finally {\n            if (camera != null) {\n                try {\n                    camera.release();\n                } catch (Exception e) {\n\n                }\n            }\n        }\n    }\n\n    /**\n     * 检测是否有录音权限\n     *\n     * @return\n     */\n    public static boolean checkRecordUnreliable() {\n        AudioRecord audioRecord = null;\n        int bufferSizeInBytes = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING);\n        try {\n            audioRecord = new AudioRecord(AUDIO_INPUT, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING, bufferSizeInBytes);\n            return audioRecord != null && audioRecord.getState() == AudioRecord.STATE_INITIALIZED;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        } finally {\n            if (audioRecord != null) {\n                try {\n                    audioRecord.release();\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * 检测是否有读取设备信息权限\n     *\n     * @return\n     */\n    public static boolean checkReadPhoneUnreliable(Context context) {\n        try {\n            TelephonyManager tm = (TelephonyManager) context\n                    .getSystemService(Context.TELEPHONY_SERVICE);\n            @SuppressLint(\"MissingPermission\")\n            String imei = tm.getDeviceId();\n            return !TextUtils.isEmpty(imei);\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    /**\n     * 检测是否有读取联系人权限，不可靠。在联系人列表为空时，即使赋予了读取权限也会报没权限\n     *\n     * @return\n     */\n    public static boolean checkReadContactUnreliable(Context context) {\n        Cursor cursor = null;\n        try {\n            cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);\n            if (cursor != null) {\n                while (cursor.moveToNext()) {\n                    return true;\n                }\n            }\n            return false;\n        } catch (Exception e) {\n            return false;\n        } finally {\n            if (cursor != null) {\n                cursor.close();\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoKitSPUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\n\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.util.AppUtils;\nimport com.didichuxing.doraemonkit.util.Utils;\nimport com.didichuxing.doraemonkit.constant.SharedPrefsKey;\n\nimport static com.didichuxing.doraemonkit.constant.SharedPrefsKey.APP_HEALTH;\n\n/**\n * Created by wanglikun on 2018/9/14.\n */\n\npublic class DoKitSPUtil {\n    /**\n     * 退出时保存sp状态 需要用commit\n     */\n    private static final String SHARED_PREFS_DORAEMON = \"shared_prefs_doraemon\";\n\n    private static SharedPreferences getSharedPrefs() {\n        return getSharedPrefs(SHARED_PREFS_DORAEMON);\n    }\n\n    @Nullable\n    public static SharedPreferences getSharedPrefs(String name) {\n        return Utils.getApp().getSharedPreferences(name, Context.MODE_PRIVATE);\n    }\n\n    public static String getString(String key, String defVal) {\n        return getSharedPrefs().getString(key, defVal);\n    }\n\n    public static void putString(String key, String value) {\n        putString(SHARED_PREFS_DORAEMON, key, value);\n    }\n\n    public static void putString(String table, String key, String value) {\n        try {\n            if (getSharedPrefs(table) != null) {\n                if (key.equals(SHARED_PREFS_DORAEMON)) {\n                    getSharedPrefs(table).edit().putString(key, value).commit();\n                } else {\n                    getSharedPrefs(table).edit().putString(key, value).apply();\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n    public static void putBoolean(String key, boolean value) {\n\n        putBoolean(SHARED_PREFS_DORAEMON, key, value);\n    }\n\n    public static void putBoolean(String table, String key, boolean value) {\n        try {\n            if (getSharedPrefs(table) != null) {\n                if (key.equals(SharedPrefsKey.APP_HEALTH)) {\n                    getSharedPrefs(table).edit().putBoolean(key, value).commit();\n                } else {\n                    getSharedPrefs(table).edit().putBoolean(key, value).apply();\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static boolean getBoolean(String key, boolean defVal) {\n        return getSharedPrefs().getBoolean(key, defVal);\n    }\n\n    public static void putInt(String key, int value) {\n        putInt(SHARED_PREFS_DORAEMON, key, value);\n    }\n\n    public static void putInt(String table, String key, Integer value) {\n        try {\n            getSharedPrefs(table).edit().putInt(key, value).apply();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static int getInt(String key, int defVal) {\n        return getSharedPrefs().getInt(key, defVal);\n    }\n\n    public static void putFloat(String table, String key, Float value) {\n        try {\n            getSharedPrefs(table).edit().putFloat(key, value).apply();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static void putFloat(String key, Float value) {\n        try {\n            getSharedPrefs(SHARED_PREFS_DORAEMON).edit().putFloat(key, value).apply();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static float getFloat(String key, Float value) {\n        return getSharedPrefs().getFloat(key, value);\n    }\n\n    public static void putLong(String table, String key, Long value) {\n        try {\n            getSharedPrefs(table).edit().putLong(key, value).apply();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoKitSystemUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.Activity;\nimport android.app.ActivityManager;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\nimport android.provider.Settings;\nimport android.text.TextUtils;\n\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager;\nimport com.didichuxing.doraemonkit.kit.core.ActivityLifecycleStatusInfo;\n\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Created by wanglikun on 2018/12/21.\n */\n\npublic class DoKitSystemUtil {\n    private static final String TAG = \"SystemUtil\";\n\n    private static final Pattern VERSION_NAME_PATTERN = Pattern.compile(\"(\\\\d+\\\\.\\\\d+\\\\.\\\\d+)-*.*\");\n    private static String sAppVersion;\n    private static int sAppVersionCode = -1;\n    private static String sPackageName;\n    private static String sAppName;\n\n    public static String getVersionName(Context context) {\n        if (!TextUtils.isEmpty(sAppVersion)) {\n            return sAppVersion;\n        } else {\n            String appVersion = \"\";\n            try {\n                String pkgName = context.getApplicationInfo().packageName;\n                appVersion = context.getPackageManager().getPackageInfo(pkgName, 0).versionName;\n                if (appVersion != null && appVersion.length() > 0) {\n                    Matcher matcher = VERSION_NAME_PATTERN.matcher(appVersion);\n                    if (matcher.matches()) {\n                        appVersion = matcher.group(1);\n                        sAppVersion = appVersion;\n                    }\n                }\n            } catch (Throwable t) {\n                LogHelper.e(TAG, t.toString());\n            }\n\n            return appVersion;\n        }\n    }\n\n    public static int getVersionCode(Context context) {\n        if (sAppVersionCode != -1) {\n            return sAppVersionCode;\n        } else {\n            int versionCode;\n\n            try {\n                String pkgName = context.getApplicationInfo().packageName;\n                versionCode = context.getPackageManager().getPackageInfo(pkgName, 0).versionCode;\n                if (versionCode != -1) {\n                    sAppVersionCode = versionCode;\n                }\n            } catch (Throwable t) {\n                LogHelper.e(TAG, t.toString());\n            }\n\n            return sAppVersionCode;\n        }\n    }\n\n    public static String getPackageName(Context context) {\n        if (TextUtils.isEmpty(sPackageName)) {\n            sPackageName = context.getPackageName();\n        }\n        return sPackageName;\n    }\n\n    public static String getAppName(Context context) {\n        if (!TextUtils.isEmpty(sAppName)) {\n            return sAppName;\n        } else {\n            PackageManager packageManager = null;\n            ApplicationInfo applicationInfo = null;\n            packageManager = context.getPackageManager();\n            try {\n                applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0);\n            } catch (PackageManager.NameNotFoundException e) {\n                LogHelper.e(TAG, e.toString());\n            }\n            sAppName = (String) packageManager.getApplicationLabel(applicationInfo);\n            return sAppName;\n        }\n    }\n\n    public static String obtainProcessName(Context context) {\n        final int pid = android.os.Process.myPid();\n        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);\n        List<ActivityManager.RunningAppProcessInfo> listTaskInfo = am.getRunningAppProcesses();\n        if (listTaskInfo != null && !listTaskInfo.isEmpty()) {\n            for (ActivityManager.RunningAppProcessInfo info : listTaskInfo) {\n                if (info != null && info.pid == pid) {\n                    return info.processName;\n                }\n            }\n        }\n        return null;\n    }\n\n\n    /**\n     * 是否是系统main activity\n     *\n     * @return boolean\n     */\n    public static boolean isMainLaunchActivity(Activity activity) {\n        PackageManager packageManager = activity.getApplication().getPackageManager();\n        Intent intent = packageManager.getLaunchIntentForPackage(activity.getPackageName());\n        if (intent == null) {\n            return false;\n        }\n        ComponentName launchComponentName = intent.getComponent();\n        ComponentName componentName = activity.getComponentName();\n        if (launchComponentName != null && componentName.toString().equals(launchComponentName.toString())) {\n            return true;\n        }\n        return false;\n    }\n\n\n    /**\n     * 是否是系统启动第一次调用mainActivity 页面回退不算\n     *\n     * @return boolean\n     */\n    public static boolean isOnlyFirstLaunchActivity(Activity activity) {\n        boolean isMainActivity = isMainLaunchActivity(activity);\n        ActivityLifecycleStatusInfo activityLifecycleInfo = DoKitManager.INSTANCE.getACTIVITY_LIFECYCLE_INFOS().get(activity.getClass().getCanonicalName());\n        return activityLifecycleInfo != null && isMainActivity && !activityLifecycleInfo.isInvokeStopMethod();\n    }\n\n\n    /**\n     * 打开开发者模式界面 https://blog.csdn.net/ouzhuangzhuang/article/details/84029295\n     */\n    public static void startDevelopmentActivity(Context context) {\n        try {\n            Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);\n            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            context.startActivity(intent);\n        } catch (Exception e) {\n            try {\n                ComponentName componentName = new ComponentName(\"com.android.settings\", \"com.android.settings.DevelopmentSettings\");\n                Intent intent = new Intent();\n                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n                intent.setComponent(componentName);\n                intent.setAction(\"android.intent.action.View\");\n                context.startActivity(intent);\n            } catch (Exception e1) {\n                try {\n                    //部分小米手机采用这种方式跳转\n                    Intent intent = new Intent(\"com.android.settings.APPLICATION_DEVELOPMENT_SETTINGS\");\n                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n                    context.startActivity(intent);\n                } catch (Exception e2) {\n                    e2.printStackTrace();\n                }\n\n            }\n        }\n    }\n\n\n    /**\n     * 打开系统语言设置页面\n     *\n     * @param context\n     */\n    public static void startLocalActivity(Context context) {\n        try {\n            Intent intent = new Intent(Settings.ACTION_LOCALE_SETTINGS);\n            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            context.startActivity(intent);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * 打开正在运行的服务界面 https://www.jianshu.com/p/dd491235d113\n     */\n    public static void startServiceRunningActivity(Context context) {\n        try {\n            ComponentName componentName;\n            if (getBrand().equalsIgnoreCase(PHONE_VIVO)) {\n                componentName = new ComponentName(\"com.android.settings\", \"com.vivo.settings.VivoSubSettingsForImmersiveBar\");\n            } else {\n                componentName = new ComponentName(\"com.android.settings\", \"com.android.settings.CleanSubSettings\");\n            }\n            Intent intent = new Intent();\n            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            intent.setComponent(componentName);\n            intent.setAction(\"android.intent.action.View\");\n            context.startActivity(intent);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n\n    public static String getBrand() {\n        return android.os.Build.BRAND;\n    }\n\n    /**\n     * 手机品牌\n     */\n    // 小米\n    public static final String PHONE_XIAOMI = \"xiaomi\";\n    // 华为\n    public static final String PHONE_HUAWEI = \"HUAWEI\";\n    // 华为\n    public static final String PHONE_HONOR = \"HONOR\";\n    // 魅族\n    public static final String PHONE_MEIZU = \"Meizu\";\n    // 索尼\n    public static final String PHONE_SONY = \"sony\";\n    // 三星\n    public static final String PHONE_SAMSUNG = \"samsung\";\n    // LG\n    public static final String PHONE_LG = \"lg\";\n    // HTC\n    public static final String PHONE_HTC = \"htc\";\n    // NOVA\n    public static final String PHONE_NOVA = \"nova\";\n    // OPPO\n    public static final String PHONE_OPPO = \"oppo\";\n    // vivo\n    public static final String PHONE_VIVO = \"vivo\";\n    // 乐视\n    public static final String PHONE_LeMobile = \"LeMobile\";\n    // 联想\n    public static final String PHONE_LENOVO = \"lenovo\";\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoKitWebUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.Context;\nimport android.webkit.WebView;\n\nimport com.didichuxing.doraemonkit.config.GpsMockConfig;\nimport com.didichuxing.doraemonkit.model.LatLng;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\n\n/**\n * Created by wanglikun on 2019/4/15\n */\npublic class DoKitWebUtil {\n    public static void webViewLoadLocalHtml(final WebView view, String jsPath) {\n        String htmlData = assetFileToString(view.getContext(), jsPath);\n        view.loadDataWithBaseURL(\"http://localhost\", htmlData, \"text/html\", \"UTF-8\", null);\n        //必须要延迟一定的时间 方便html字符串先加载完\n        view.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                LatLng latLng = GpsMockConfig.getMockLocation();\n                if (latLng == null) {\n                    latLng = new LatLng(0, 0);\n                }\n                String url = String.format(\"javascript:init(%s,%s)\", latLng.latitude, latLng.longitude);\n                //String url = String.format(\"javascript:init(%s,%s)\", 0, 0);\n                //String url = String.format(\"javascript:init(%s,%s)\", 39.901933, 116.396613);\n                view.loadUrl(url);\n            }\n        }, 1000);\n\n    }\n\n    public static String assetFileToString(Context c, String urlStr) {\n        InputStream in = null;\n\n        try {\n            in = c.getAssets().open(urlStr);\n        } catch (IOException var4) {\n            var4.printStackTrace();\n        }\n\n        return inputStreamToString(in);\n    }\n\n    private static String inputStreamToString(InputStream in) {\n        if (in == null) {\n            return \"\";\n        } else {\n            try {\n                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));\n                String line = null;\n                StringBuilder sb = new StringBuilder();\n\n                do {\n                    line = bufferedReader.readLine();\n                    if (line != null) {\n                        sb.append(line).append(\"\\n\");\n                    }\n                } while (line != null);\n\n                bufferedReader.close();\n                in.close();\n                String var4 = sb.toString();\n                return var4;\n            } catch (Exception var14) {\n                var14.printStackTrace();\n            } finally {\n                if (in != null) {\n                    try {\n                        in.close();\n                    } catch (IOException var13) {\n                        var13.printStackTrace();\n                    }\n                }\n            }\n            return null;\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DokitDeviceUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.hardware.Camera;\nimport android.net.ConnectivityManager;\nimport android.net.NetworkInfo;\nimport android.net.wifi.WifiInfo;\nimport android.net.wifi.WifiManager;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.os.StatFs;\nimport android.telephony.TelephonyManager;\nimport android.text.TextUtils;\nimport android.text.format.Formatter;\nimport android.util.Log;\nimport android.webkit.WebView;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileFilter;\nimport java.io.FileReader;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.regex.Pattern;\n\n/**\n * Created by zhangweida on 2018/6/26.\n */\n\npublic class DokitDeviceUtils {\n    private static final String TAG = \"DeviceUtils\";\n    private static final String BRAND_HUAWEI = \"huawei\";\n    private static final String BRAND_HONOR = \"honor\";\n    private static final String BRAND_SAMSUNG = \"samsung\";\n    private static final String BRAND_XIAOMI = \"xiaomi\";\n    private static final String BRAND_HONGMI = \"hongmi\";\n\n    private static Boolean ROOTED = null;\n\n    /**\n     * 获取CPU个数\n     */\n    public static int getCoreNum() {\n        class CpuFilter implements FileFilter {\n            @Override\n            public boolean accept(File pathname) {\n                // Check if filename is \"cpu\", followed by a single digit number\n                if (Pattern.matches(\"cpu[0-9]\", pathname.getName())) {\n                    return true;\n                }\n                return false;\n            }\n        }\n\n        try {\n            // Get directory containing CPU info\n            File dir = new File(\"/sys/devices/system/cpu/\");\n            // Filter to only list the devices we care about\n            File[] files = dir.listFiles(new CpuFilter());\n            // Return the number of cores (virtual CPU devices)\n            return files.length;\n        } catch (Exception e) {\n            Log.e(TAG, \"getCoreNum\", e);\n            // Default to return 1 core\n            return 1;\n        }\n    }\n\n    /**\n     * @param context\n     * @return 手机总内存(兆)\n     */\n    public static long getTotalMemory(Context context) {\n        String str1 = \"/proc/meminfo\";// 系统内存信息文件\n        String str2;\n        String[] arrayOfString;\n        long initial_memory = 0;\n        try {\n            FileReader localFileReader = new FileReader(str1);\n            BufferedReader localBufferedReader = new BufferedReader(\n                    localFileReader, 8192);\n            str2 = localBufferedReader.readLine();// 读取meminfo第一行，系统总内存大小\n            if (!TextUtils.isEmpty(str2)) {\n                arrayOfString = str2.split(\"\\\\s+\");\n                initial_memory = Integer.valueOf(arrayOfString[1]).intValue() / 1024;// 获得系统总内存，单位是KB，乘以1024转换为Byte\n            }\n            localBufferedReader.close();\n        } catch (IOException e) {\n        }\n        return initial_memory;// Byte转换为KB或者MB，内存大小规格化\n    }\n\n    /**\n     * @param context\n     * @return 手机当前可用内存(兆)\n     */\n    public static long getAvailMemory(Context context) {// 获取android当前可用内存大小\n        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);\n        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();\n        am.getMemoryInfo(mi);\n        return mi.availMem / 1024 / 1024;\n    }\n\n    /**\n     * 版本名\n     */\n    public static String getVersionName(Context context) {\n        PackageInfo pi = getPackageInfo(context);\n        if (pi != null) {\n            return pi.versionName;\n        } else {\n            return \"\";\n        }\n    }\n\n    /**\n     * 版本号\n     */\n    public static int getVersionCode(Context context) {\n        PackageInfo pi = getPackageInfo(context);\n        if (pi != null) {\n            return pi.versionCode;\n        } else {\n            return 0;\n        }\n    }\n\n    public static PackageInfo getPackageInfo(Context context) {\n        PackageInfo pi = null;\n\n        try {\n            PackageManager pm = context.getPackageManager();\n            pi = pm.getPackageInfo(context.getPackageName(),\n                    PackageManager.GET_CONFIGURATIONS);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        return pi;\n    }\n\n    public static PackageInfo getPackageInfoForPermission(Context context) {\n        PackageInfo pi = null;\n\n        try {\n            PackageManager pm = context.getPackageManager();\n            pi = pm.getPackageInfo(context.getPackageName(),\n                    PackageManager.GET_PERMISSIONS);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        return pi;\n    }\n\n    /**\n     * @return 手机链接wifi的路由器的名字\n     */\n    public static String getWifiSSID(Context context) {\n        WifiManager mWifi = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);\n        if (mWifi.isWifiEnabled()) {\n            WifiInfo wifiInfo = mWifi.getConnectionInfo();\n            return wifiInfo.getSSID();\n        }\n        return \"\";\n    }\n\n    /**\n     * @return 手机链接wifi的路由器的mac地址\n     */\n    public static String getWifiBSSID(Context context) {\n        WifiManager mWifi = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);\n        if (mWifi.isWifiEnabled()) {\n            WifiInfo wifiInfo = mWifi.getConnectionInfo();\n            return wifiInfo.getBSSID();\n        }\n        return \"\";\n    }\n\n    /**\n     * @param permission\n     * @param defalutValue 如果检测不到()检测发生异常的默认值\n     * @return App是否拥有permission权限\n     */\n    public static boolean checkPermission(Context context, String permission, boolean defalutValue) {\n        boolean permit = false;\n        // 如果在判断的时候报错（有些机型），则默认不满足此权限\n        try {\n            permit = PackageManager.PERMISSION_GRANTED == context\n                    .checkCallingOrSelfPermission(permission);\n        } catch (Exception e) {\n            permit = defalutValue;\n        }\n        return permit;\n    }\n\n\n    /**\n     * 判断wifi是否开启\n     *\n     * @param context\n     * @return\n     */\n    public static boolean isWifiEnabled(Context context) {\n        WifiManager wm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);\n        return (wm != null)\n                && (wm.getWifiState() == WifiManager.WIFI_STATE_ENABLED || wm.getWifiState() == WifiManager.WIFI_STATE_ENABLING);\n    }\n\n    /**\n     * Is wifi connected\n     *\n     * @param context\n     * @return\n     */\n    public static boolean isWifiConnected(Context context) {\n        ConnectivityManager connectivityManager = (ConnectivityManager) context.\n                getSystemService(Context.CONNECTIVITY_SERVICE);\n        NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();\n        return activeNetInfo != null && activeNetInfo.isConnected()\n                && (activeNetInfo.getType() == ConnectivityManager.TYPE_WIFI);\n    }\n\n    public static boolean hasJellyBeanMr2() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;\n    }\n\n    public static boolean hasJellyBeanMr1() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;\n    }\n\n    /**\n     * 判断是否为某品牌生产的手机\n     */\n    public static boolean isProduceByBrand(String brand) {\n        boolean result = false;\n        // 判断厂家\n        if (Build.MANUFACTURER != null) {\n            if (Build.MANUFACTURER.toLowerCase().contains(brand)) {\n                result = true;\n            }\n        }\n        // 判断品牌\n        if (!result && Build.BRAND != null) {\n            if (Build.BRAND.toLowerCase().contains(brand)) {\n                result = true;\n            }\n        }\n        return result;\n    }\n\n    public static boolean isProduceByXiaomi() {\n        return isProductInBrands(BRAND_XIAOMI, BRAND_HONGMI);\n    }\n\n    public static boolean isProduceByHuaWei() {\n        return isProductInBrands(BRAND_HUAWEI, BRAND_HONOR);\n    }\n\n    public static boolean isProduceBySamsung() {\n        return isProduceByBrand(BRAND_SAMSUNG);\n    }\n\n    public static boolean isProductInBrands(String... brands) {\n        for (String brand : brands) {\n            if (isProduceByBrand(brand)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n\n    public static boolean isSimReady(Context context) {\n        try {\n            TelephonyManager mgr = (TelephonyManager) context\n                    .getSystemService(Context.TELEPHONY_SERVICE);\n            Log.d(TAG, \"[isSimReady]\" + mgr.getSimState());\n            return TelephonyManager.SIM_STATE_READY == mgr.getSimState()\n                    || TelephonyManager.SIM_STATE_NETWORK_LOCKED == mgr.getSimState();\n        } catch (Exception e) {\n            Log.e(TAG, \"[isSimReady]\" + e);\n        }\n        return false;\n    }\n\n    /**\n     * 判断当前手机是否有ROOT权限\n     *\n     * @return 是否ROOT\n     */\n    public static boolean isRoot(Context context) {\n        if (ROOTED != null) {\n            return ROOTED;\n        }\n        try {\n            ROOTED = DeviceUtils.isDeviceRooted();\n            if (ROOTED) {\n                Log.w(TAG, \"Device rooted.\");\n            }\n        } catch (Exception e) {\n            Log.e(TAG, \"Check root failed.\", e);\n            ROOTED = false;\n        }\n        return ROOTED;\n    }\n\n    public static boolean hasFrontCamera() {\n        int cameraCount = Camera.getNumberOfCameras();\n        Camera.CameraInfo info = new Camera.CameraInfo();\n        for (int i = 0; i < cameraCount; i++) {\n            Camera.getCameraInfo(i, info);\n            if (Camera.CameraInfo.CAMERA_FACING_FRONT == info.facing) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * @return Number of bytes available on external storage\n     */\n    public static long getExternalAvailableSpaceInBytes() {\n        long availableSpace = -1L;\n        try {\n            StatFs stat = new StatFs(Environment.getExternalStorageDirectory()\n                    .getPath());\n            availableSpace = (long) stat.getAvailableBlocks()\n                    * (long) stat.getBlockSize();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        return availableSpace;\n    }\n\n    /**\n     * 获得SD卡总大小\n     *\n     * @return\n     */\n    private static String getSDTotalSize(Context context) {\n        File path = Environment.getExternalStorageDirectory();\n        StatFs stat = new StatFs(path.getPath());\n        long blockSize = stat.getBlockSize();\n        long totalBlocks = stat.getBlockCount();\n        return Formatter.formatFileSize(context, blockSize * totalBlocks);\n    }\n\n    /**\n     * 获得sd卡剩余容量，即可用大小\n     *\n     * @return\n     */\n    private static String getSDAvailableSize(Context context) {\n        File path = Environment.getExternalStorageDirectory();\n        StatFs stat = new StatFs(path.getPath());\n        long blockSize = stat.getBlockSize();\n        long availableBlocks = stat.getAvailableBlocks();\n        return Formatter.formatFileSize(context, blockSize * availableBlocks);\n    }\n\n    /**\n     * 获得机身内存总大小\n     *\n     * @return\n     */\n    private static String getRomTotalSize(Context context) {\n        File path = Environment.getDataDirectory();\n        StatFs stat = new StatFs(path.getPath());\n        long blockSize = stat.getBlockSize();\n        long totalBlocks = stat.getBlockCount();\n        return Formatter.formatFileSize(context, blockSize * totalBlocks);\n    }\n\n    /**\n     * 获得机身可用内存\n     *\n     * @return\n     */\n    private static String getRomAvailableSize(Context context) {\n        File path = Environment.getDataDirectory();\n        StatFs stat = new StatFs(path.getPath());\n        long blockSize = stat.getBlockSize();\n        long availableBlocks = stat.getAvailableBlocks();\n        return Formatter.formatFileSize(context, blockSize * availableBlocks);\n    }\n\n\n    public static String getSDCardSpace(Context context) {\n        try {\n            String free = getSDAvailableSize(context);\n            String total = getSDTotalSize(context);\n            return free + \"/\" + total;\n        } catch (Exception e) {\n            return \"-/-\";\n        }\n    }\n\n    public static String getRomSpace(Context context) {\n        try {\n            String free = getRomAvailableSize(context);\n            String total = getRomTotalSize(context);\n            return free + \"/\" + total;\n        } catch (Exception e) {\n            return \"-/-\";\n        }\n    }\n\n    public static String getWebViewChromeVersion(Context context) {\n        WebView webView = new WebView(context);\n        String userAgentString = webView.getSettings().getUserAgentString();\n        webView.destroy();\n        List<String> matches = RegexUtils.getMatches(\"(?<=Chrome/)[.0-9]*(?= Mobile)\", userAgentString);\n        if (matches.isEmpty()) {\n            return null;\n        } else {\n            return matches.get(0);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/DoraemonStatisticsUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.Context;\n\nimport com.didichuxing.doraemonkit.BuildConfig;\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.IOException;\nimport java.util.Locale;\n\nimport okhttp3.Call;\nimport okhttp3.Callback;\nimport okhttp3.MediaType;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport okhttp3.Response;\n\n/**\n * Created by wanglikun on 2018/12/21.\n */\npublic class DoraemonStatisticsUtil {\n    private static final String TAG = \"DoraemonStatisticsUtil\";\n\n    private DoraemonStatisticsUtil() {\n    }\n\n    public static void uploadUserInfo(Context context) throws Exception {\n        String appId = DoKitSystemUtil.getPackageName(context);\n        String appName = DoKitSystemUtil.getAppName(context);\n        String type = \"Android\";\n        //0 代表内部版本  1代表外部版本\n        String from = \"1\";\n\n        MediaType mediaType = MediaType.parse(\"application/json; charset=utf-8\");\n\n        JSONObject jsonObject = new JSONObject();\n        try {\n            jsonObject.put(\"appId\", appId);\n            jsonObject.put(\"appName\", appName);\n            jsonObject.put(\"appVersion\", AppUtils.getAppVersionName());\n            jsonObject.put(\"version\", \"\" + BuildConfig.DOKIT_VERSION);\n            jsonObject.put(\"type\", type);\n            jsonObject.put(\"from\", from);\n            jsonObject.put(\"language\", Locale.getDefault().getDisplayLanguage());\n        } catch (JSONException e) {\n            LogHelper.e(TAG, e.toString());\n        }\n\n        OkHttpClient client = new OkHttpClient();\n        RequestBody requestBody = RequestBody.create(mediaType,\n                jsonObject.toString());\n        Request request = new Request.Builder()\n                .url(NetworkManager.APP_START_DATA_PICK_URL)\n                .post(requestBody)\n                .build();\n        Call call = client.newCall(request);\n        call.enqueue(new Callback() {\n            @Override\n            public void onFailure(Call call, IOException e) {\n                e.printStackTrace();\n            }\n\n            @Override\n            public void onResponse(Call call, Response response) throws IOException {\n                response.close();\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/FileManager.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport java.io.File;\nimport java.io.RandomAccessFile;\n\npublic class FileManager {\n    private static final String TAG = \"FileManager\";\n\n    // 将字符串写入到文本文件中\n    public static void writeTxtToFile(String strcontent, String filePath, String fileName) {\n        //生成文件夹之后，再生成文件，不然会出错\n        makeFilePath(filePath, fileName);\n\n        String strFilePath = filePath+fileName;\n        // 每次写入时，都换行写\n        String strContent = strcontent + \"\\r\\n\";\n        try {\n            File file = new File(strFilePath);\n            if (!file.exists()) {\n                LogHelper.d(TAG, \"Create the file:\" + strFilePath);\n                file.getParentFile().mkdirs();\n                file.createNewFile();\n            }\n            RandomAccessFile raf = new RandomAccessFile(file, \"rwd\");\n            raf.seek(file.length());\n            raf.write(strContent.getBytes());\n            raf.close();\n        } catch (Exception e) {\n            LogHelper.e(TAG, \"Error on write File:\" + e);\n        }\n    }\n\n    // 生成文件\n    private static File makeFilePath(String filePath, String fileName) {\n        File file = null;\n        makeRootDirectory(filePath);\n        try {\n            file = new File(filePath + fileName);\n            if (!file.exists()) {\n                file.createNewFile();\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return file;\n    }\n\n    // 生成文件夹\n    private static void makeRootDirectory(String filePath) {\n        File file = null;\n        try {\n            file = new File(filePath);\n            if (!file.exists()) {\n                file.mkdir();\n            }\n        } catch (Exception e) {\n            LogHelper.i(\"error:\", e+\"\");\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/LifecycleListenerUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.Activity;\n\n\nimport androidx.fragment.app.Fragment;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-18-10:27\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class LifecycleListenerUtil {\n    public static List<LifecycleListener> LIFECYCLE_LISTENERS = new ArrayList<>();\n    /**\n     * 悬浮窗初始化时注册activity以及fragment生命周期回调监听\n     *\n     * @param listener\n     */\n    public static void registerListener(LifecycleListenerUtil.LifecycleListener listener) {\n        LIFECYCLE_LISTENERS.add(listener);\n    }\n\n    /**\n     * 悬浮窗关闭时注销监听\n     *\n     * @param listener\n     */\n    public static void unRegisterListener(LifecycleListenerUtil.LifecycleListener listener) {\n        LIFECYCLE_LISTENERS.remove(listener);\n    }\n\n    public interface LifecycleListener {\n        void onActivityResumed(Activity activity);\n\n        void onActivityPaused(Activity activity);\n\n        void onFragmentAttached(Fragment f);\n\n        void onFragmentDetached(Fragment f);\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/LogHelper.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\n/**\n * Created by wanglikun on 2018/9/10.\n */\n\npublic class LogHelper {\n    private static final String TAG = \"DoKit\";\n    private static boolean IS_DEBUG = true;\n\n    public static void d(String subTag, String msg) {\n        if (!IS_DEBUG) {\n            return;\n        }\n        LogUtils.d(\"[\" + subTag + \"]: \" + msg);\n    }\n\n    public static void i(String subTag, String msg) {\n        if (!IS_DEBUG) {\n            return;\n        }\n        LogUtils.i(\"[\" + subTag + \"]: \" + msg);\n    }\n\n    public static void e(String subTag, String msg) {\n        if (!IS_DEBUG) {\n            return;\n        }\n        LogUtils.e(\"[\" + subTag + \"]: \" + msg);\n    }\n\n    public static void json(String subTag, Object o) {\n        if (!IS_DEBUG) {\n            return;\n        }\n        LogUtils.json(\"[\" + subTag + \"]: \", o);\n    }\n\n    public static void setDebug(boolean debug) {\n        IS_DEBUG = debug;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/Reflector.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Member;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\n\n/**\n * 反射工具类\n */\npublic class Reflector {\n    \n    public static final String TAG = \"Reflector\";\n    \n    protected Class<?> mType;\n    protected Object mCaller;\n    protected Constructor mConstructor;\n    protected Field mField;\n    protected Method mMethod;\n    \n    public static class ReflectedException extends Exception {\n    \n        public ReflectedException(String message) {\n            super(message);\n        }\n        public ReflectedException(String message, Throwable cause) {\n            super(message, cause);\n        }\n    \n    }\n    public static Reflector on(@NonNull String name) throws ReflectedException {\n        return on(name, true, Reflector.class.getClassLoader());\n    }\n    \n    public static Reflector on(@NonNull String name, boolean initialize) throws ReflectedException {\n        return on(name, initialize, Reflector.class.getClassLoader());\n    }\n    \n    public static Reflector on(@NonNull String name, boolean initialize, @Nullable ClassLoader loader) throws ReflectedException {\n        try {\n            return on(Class.forName(name, initialize, loader));\n        } catch (Throwable e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    public static Reflector on(@NonNull Class<?> type) {\n        Reflector reflector = new Reflector();\n        reflector.mType = type;\n        return reflector;\n    }\n    \n    public static Reflector with(@NonNull Object caller) throws ReflectedException {\n        return on(caller.getClass()).bind(caller);\n    }\n    \n    protected Reflector() {\n    \n    }\n    \n    public Reflector constructor(@Nullable Class<?>... parameterTypes) throws ReflectedException {\n        try {\n            mConstructor = mType.getDeclaredConstructor(parameterTypes);\n            mConstructor.setAccessible(true);\n            mField = null;\n            mMethod = null;\n            return this;\n        } catch (Throwable e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    @SuppressWarnings(\"unchecked\")\n    public <R> R newInstance(@Nullable Object ... initargs) throws ReflectedException {\n        if (mConstructor == null) {\n            throw new ReflectedException(\"Constructor was null!\");\n        }\n        try {\n            return (R) mConstructor.newInstance(initargs);\n        } catch (InvocationTargetException e) {\n            throw new ReflectedException(\"Oops!\", e.getTargetException());\n        } catch (Throwable e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    protected Object checked(@Nullable Object caller) throws ReflectedException {\n        if (caller == null || mType.isInstance(caller)) {\n            return caller;\n        }\n        throw new ReflectedException(\"Caller [\" + caller + \"] is not a instance of type [\" + mType + \"]!\");\n    }\n    \n    protected void check(@Nullable Object caller, @Nullable Member member, @NonNull String name) throws ReflectedException {\n        if (member == null) {\n            throw new ReflectedException(name + \" was null!\");\n        }\n        if (caller == null && !Modifier.isStatic(member.getModifiers())) {\n            throw new ReflectedException(\"Need a caller!\");\n        }\n        checked(caller);\n    }\n    \n    public Reflector bind(@Nullable Object caller) throws ReflectedException {\n        mCaller = checked(caller);\n        return this;\n    }\n    \n    public Reflector unbind() {\n        mCaller = null;\n        return this;\n    }\n    \n    public Reflector field(@NonNull String name) throws ReflectedException {\n        try {\n            mField = findField(name);\n            mField.setAccessible(true);\n            mConstructor = null;\n            mMethod = null;\n            return this;\n        } catch (Throwable e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    protected Field findField(@NonNull String name) throws NoSuchFieldException {\n        try {\n            return mType.getField(name);\n        } catch (NoSuchFieldException e) {\n            for (Class<?> cls = mType; cls != null; cls = cls.getSuperclass()) {\n                try {\n                    return cls.getDeclaredField(name);\n                } catch (NoSuchFieldException ex) {\n                    // Ignored\n                }\n            }\n            throw e;\n        }\n    }\n    \n    @SuppressWarnings(\"unchecked\")\n    public <R> R get() throws ReflectedException {\n        return get(mCaller);\n    }\n    \n    @SuppressWarnings(\"unchecked\")\n    public <R> R get(@Nullable Object caller) throws ReflectedException {\n        check(caller, mField, \"Field\");\n        try {\n            return (R) mField.get(caller);\n        } catch (Throwable e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    public Reflector set(@Nullable Object value) throws ReflectedException {\n        return set(mCaller, value);\n    }\n    \n    public Reflector set(@Nullable Object caller, @Nullable Object value) throws ReflectedException {\n        check(caller, mField, \"Field\");\n        try {\n            mField.set(caller, value);\n            return this;\n        } catch (Throwable e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    public Reflector method(@NonNull String name, @Nullable Class<?>... parameterTypes) throws ReflectedException {\n        try {\n            mMethod = findMethod(name, parameterTypes);\n            mMethod.setAccessible(true);\n            mConstructor = null;\n            mField = null;\n            return this;\n        } catch (NoSuchMethodException e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    protected Method findMethod(@NonNull String name, @Nullable Class<?>... parameterTypes) throws NoSuchMethodException {\n        try {\n            return mType.getMethod(name, parameterTypes);\n        } catch (NoSuchMethodException e) {\n            for (Class<?> cls = mType; cls != null; cls = cls.getSuperclass()) {\n                try {\n                    return cls.getDeclaredMethod(name, parameterTypes);\n                } catch (NoSuchMethodException ex) {\n                    // Ignored\n                }\n            }\n            throw e;\n        }\n    }\n    \n    public <R> R call(@Nullable Object... args) throws ReflectedException {\n        return callByCaller(mCaller, args);\n    }\n    \n    @SuppressWarnings(\"unchecked\")\n    public <R> R callByCaller(@Nullable Object caller, @Nullable Object... args) throws ReflectedException {\n        check(caller, mMethod, \"Method\");\n        try {\n            return (R) mMethod.invoke(caller, args);\n        } catch (InvocationTargetException e) {\n            throw new ReflectedException(\"Oops!\", e.getTargetException());\n        } catch (Throwable e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    public static class QuietReflector extends Reflector {\n        \n        protected Throwable mIgnored;\n    \n        public static QuietReflector on(@NonNull String name) {\n            return on(name, true, QuietReflector.class.getClassLoader());\n        }\n    \n        public static QuietReflector on(@NonNull String name, boolean initialize) {\n            return on(name, initialize, QuietReflector.class.getClassLoader());\n        }\n    \n        public static QuietReflector on(@NonNull String name, boolean initialize, @Nullable ClassLoader loader) {\n            Class<?> cls = null;\n            try {\n                cls = Class.forName(name, initialize, loader);\n                return on(cls, null);\n            } catch (Throwable e) {\n//                Log.w(LOG_TAG, \"Oops!\", e);\n                return on(cls, e);\n            }\n        }\n    \n        public static QuietReflector on(@Nullable Class<?> type) {\n            return on(type, (type == null) ? new ReflectedException(\"Type was null!\") : null);\n        }\n    \n        private static QuietReflector on(@Nullable Class<?> type, @Nullable Throwable ignored) {\n            QuietReflector reflector = new QuietReflector();\n            reflector.mType = type;\n            reflector.mIgnored = ignored;\n            return reflector;\n        }\n    \n        public static QuietReflector with(@Nullable Object caller) {\n            if (caller == null) {\n                return on((Class<?>) null);\n            }\n            return on(caller.getClass()).bind(caller);\n        }\n        \n        protected QuietReflector() {\n            \n        }\n    \n        public Throwable getIgnored() {\n            return mIgnored;\n        }\n    \n        protected boolean skip() {\n            return skipAlways() || mIgnored != null;\n        }\n        \n        protected boolean skipAlways() {\n            return mType == null;\n        }\n    \n        @Override\n        public QuietReflector constructor(@Nullable Class<?>... parameterTypes) {\n            if (skipAlways()) {\n                return this;\n            }\n            try {\n                mIgnored = null;\n                super.constructor(parameterTypes);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return this;\n        }\n    \n        @Override\n        public <R> R newInstance(@Nullable Object... initargs) {\n            if (skip()) {\n                return null;\n            }\n            try {\n                mIgnored = null;\n                return super.newInstance(initargs);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return null;\n        }\n    \n        @Override\n        public QuietReflector bind(@Nullable Object obj) {\n            if (skipAlways()) {\n                return this;\n            }\n            try {\n                mIgnored = null;\n                super.bind(obj);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return this;\n        }\n    \n        @Override\n        public QuietReflector unbind() {\n            super.unbind();\n            return this;\n        }\n    \n        @Override\n        public QuietReflector field(@NonNull String name) {\n            if (skipAlways()) {\n                return this;\n            }\n            try {\n                mIgnored = null;\n                super.field(name);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return this;\n        }\n    \n        @Override\n        public <R> R get() {\n            if (skip()) {\n                return null;\n            }\n            try {\n                mIgnored = null;\n                return super.get();\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return null;\n        }\n    \n        @Override\n        public <R> R get(@Nullable Object caller) {\n            if (skip()) {\n                return null;\n            }\n            try {\n                mIgnored = null;\n                return super.get(caller);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return null;\n        }\n    \n        @Override\n        public QuietReflector set(@Nullable Object value) {\n            if (skip()) {\n                return this;\n            }\n            try {\n                mIgnored = null;\n                super.set(value);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return this;\n        }\n    \n        @Override\n        public QuietReflector set(@Nullable Object caller, @Nullable Object value) {\n            if (skip()) {\n                return this;\n            }\n            try {\n                mIgnored = null;\n                super.set(caller, value);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return this;\n        }\n    \n        @Override\n        public QuietReflector method(@NonNull String name, @Nullable Class<?>... parameterTypes) {\n            if (skipAlways()) {\n                return this;\n            }\n            try {\n                mIgnored = null;\n                super.method(name, parameterTypes);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return this;\n        }\n    \n        @Override\n        public <R> R call(@Nullable Object... args)  {\n            if (skip()) {\n                return null;\n            }\n            try {\n                mIgnored = null;\n                return super.call(args);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return null;\n        }\n    \n        @Override\n        public <R> R callByCaller(@Nullable Object caller, @Nullable Object... args) {\n            if (skip()) {\n                return null;\n            }\n            try {\n                mIgnored = null;\n                return super.callByCaller(caller, args);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return null;\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/UIUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Point;\nimport android.graphics.Rect;\nimport android.text.TextUtils;\nimport android.util.DisplayMetrics;\nimport android.util.TypedValue;\nimport android.view.Display;\nimport android.view.View;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.widget.FrameLayout;\nimport android.widget.LinearLayout;\n\nimport androidx.annotation.AnyRes;\n\nimport com.didichuxing.doraemonkit.DoKitEnv;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.layoutborder.ViewBorderFrameLayout;\n\nimport java.lang.reflect.Method;\nimport java.math.BigDecimal;\n\n/**\n * Created by zhangweida on 2018/6/22.\n */\n\npublic class UIUtils {\n    private static final String TAG = \"UIUtils\";\n\n    public static int dp2px(float dpValue) {\n        return ConvertUtils.dp2px(dpValue);\n    }\n\n    public static int px2dp(int px) {\n        return ConvertUtils.px2dp(px);\n    }\n\n    public static float getDensity() {\n        return ScreenUtils.getScreenDensity();\n    }\n\n    public static int getDensityDpi() {\n        return ScreenUtils.getScreenDensityDpi();\n    }\n\n    public static int getWidthPixels() {\n        DisplayMetrics metrics = new DisplayMetrics();\n        WindowManager windowManager = (WindowManager) DoKitEnv.requireApp().getSystemService(Context.WINDOW_SERVICE);\n        if (windowManager == null) {\n            return 0;\n        }\n        windowManager.getDefaultDisplay().getMetrics(metrics);\n        return metrics.widthPixels;\n    }\n\n    public static int getHeightPixels() {\n        return getRealHeightPixels() - getStatusBarHeight();\n    }\n\n    public static int getRealHeightPixels() {\n        WindowManager windowManager = (WindowManager) DoKitEnv.requireApp().getSystemService(Context.WINDOW_SERVICE);\n        int height = 0;\n        Display display = windowManager.getDefaultDisplay();\n        DisplayMetrics dm = new DisplayMetrics();\n        Class c;\n        try {\n            c = Class.forName(\"android.view.Display\");\n            Method method = c.getMethod(\"getRealMetrics\", DisplayMetrics.class);\n            method.invoke(display, dm);\n            height = dm.heightPixels;\n        } catch (Exception e) {\n            LogHelper.d(TAG, e.toString());\n        }\n        return height;\n    }\n\n    public static int getStatusBarHeight() {\n        Resources resources = DoKitEnv.requireApp().getResources();\n        int resourceId = resources.getIdentifier(\"status_bar_height\", \"dimen\", \"android\");\n        int height = resources.getDimensionPixelSize(resourceId);\n        return height;\n    }\n\n    public static Rect getViewRect(View view) {\n        Rect rect = new Rect();\n        int[] locations = new int[2];\n        view.getLocationOnScreen(locations);\n        rect.left = locations[0];\n        rect.top = locations[1];\n        if (!checkStatusBarVisible(view.getContext())) {\n            rect.top -= UIUtils.getStatusBarHeight();\n        }\n        rect.right = rect.left + view.getWidth();\n        rect.bottom = rect.top + view.getHeight();\n        return rect;\n    }\n\n    public static boolean checkStatusBarVisible(Context context) {\n        return checkFullScreenByTheme(context) || checkFullScreenByCode(context) || checkFullScreenByCode2(context);\n    }\n\n    public static boolean checkFullScreenByTheme(Context context) {\n        Resources.Theme theme = context.getTheme();\n        if (theme != null) {\n            TypedValue typedValue = new TypedValue();\n            boolean result = theme.resolveAttribute(android.R.attr.windowFullscreen, typedValue, false);\n            if (result) {\n                typedValue.coerceToString();\n                if (typedValue.type == TypedValue.TYPE_INT_BOOLEAN) {\n                    return typedValue.data != 0;\n                }\n            }\n        }\n        return false;\n    }\n\n    public static boolean checkFullScreenByCode(Context context) {\n        if (context instanceof Activity) {\n            Window window = ((Activity) context).getWindow();\n            if (window != null) {\n                View decorView = window.getDecorView();\n                if (decorView != null) {\n                    return (decorView.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_FULLSCREEN) == View.SYSTEM_UI_FLAG_FULLSCREEN;\n                }\n            }\n        }\n        return false;\n    }\n\n    public static boolean checkFullScreenByCode2(Context context) {\n        if (context instanceof Activity) {\n            return (((Activity) context).getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) == WindowManager.LayoutParams.FLAG_FULLSCREEN;\n        }\n        return false;\n    }\n\n    /**\n     * 要特别注意 返回的字段包含空格  做判断时一定要trim()\n     *\n     * @param view\n     * @return\n     */\n    public static String getIdText(View view) {\n        final int id = view.getId();\n        StringBuilder out = new StringBuilder();\n        if (id != View.NO_ID) {\n            final Resources r = view.getResources();\n            if (id > 0 && resourceHasPackage(id) && r != null) {\n                try {\n                    String pkgname;\n                    switch (id & 0xff000000) {\n                        case 0x7f000000:\n                            pkgname = \"app\";\n                            break;\n                        case 0x01000000:\n                            pkgname = \"android\";\n                            break;\n                        default:\n                            pkgname = r.getResourcePackageName(id);\n                            break;\n                    }\n                    String typename = r.getResourceTypeName(id);\n                    String entryname = r.getResourceEntryName(id);\n                    out.append(\" \");\n                    out.append(pkgname);\n                    out.append(\":\");\n                    out.append(typename);\n                    out.append(\"/\");\n                    out.append(entryname);\n                } catch (Resources.NotFoundException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n        return TextUtils.isEmpty(out.toString()) ? \"\" : out.toString();\n    }\n\n    /**\n     * 去除前缀\n     *\n     * @param view\n     * @return\n     */\n    public static String getRealIdText(View view) {\n        String id = getIdText(view);\n        if (id.isEmpty()) {\n            return \"-1\";\n        } else {\n            return id.split(\"/\")[1];\n        }\n    }\n\n    /**\n     * ViewBorderFrameLayout 的str id\n     */\n    private final static String STR_VIEW_BORDER_Id = \"app:id/dokit_view_border_id\";\n\n    /**\n     * 获得app的contentView\n     *\n     * @param activity\n     * @return\n     */\n    public static View getDokitAppContentView(Activity activity) {\n        FrameLayout decorView = (FrameLayout) activity.getWindow().getDecorView();\n        View mAppContentView = (View) decorView.getTag(R.id.dokit_app_contentview_id);\n        if (mAppContentView != null) {\n            return mAppContentView;\n        }\n        for (int index = 0; index < decorView.getChildCount(); index++) {\n            View child = decorView.getChildAt(index);\n            //LogHelper.i(TAG, \"childId=====>\" + getIdText(child));\n            //解决与布局边框工具冲突的问题\n            if ((child instanceof LinearLayout && TextUtils.isEmpty(getIdText(child).trim())) || child instanceof FrameLayout) {\n                //如果是DokitBorderView 则返回他下面的第一个子child\n                if (getIdText(child).trim().equals(STR_VIEW_BORDER_Id)) {\n                    mAppContentView = ((ViewBorderFrameLayout) child).getChildAt(0);\n                } else {\n                    mAppContentView = child;\n                }\n                mAppContentView.setTag(R.id.dokit_app_contentview_id);\n                break;\n            }\n        }\n\n        return mAppContentView;\n    }\n\n    private static boolean resourceHasPackage(@AnyRes int resid) {\n        return (resid >>> 24) != 0;\n    }\n\n\n    /**\n     * 获取屏幕尺寸\n     *\n     * @param context\n     * @return\n     */\n    public static double getScreenInch(Activity context) {\n        double inch = 0;\n\n        try {\n            int realWidth = 0, realHeight = 0;\n            Display display = context.getWindowManager().getDefaultDisplay();\n            DisplayMetrics metrics = new DisplayMetrics();\n            display.getMetrics(metrics);\n            if (android.os.Build.VERSION.SDK_INT >= 17) {\n                Point size = new Point();\n                display.getRealSize(size);\n                realWidth = size.x;\n                realHeight = size.y;\n            } else if (android.os.Build.VERSION.SDK_INT < 17\n                    && android.os.Build.VERSION.SDK_INT >= 14) {\n                Method mGetRawH = Display.class.getMethod(\"getRawHeight\");\n                Method mGetRawW = Display.class.getMethod(\"getRawWidth\");\n                realWidth = (Integer) mGetRawW.invoke(display);\n                realHeight = (Integer) mGetRawH.invoke(display);\n            } else {\n                realWidth = metrics.widthPixels;\n                realHeight = metrics.heightPixels;\n            }\n\n            inch = formatDouble(Math.sqrt((realWidth / metrics.xdpi) * (realWidth / metrics.xdpi) + (realHeight / metrics.ydpi) * (realHeight / metrics.ydpi)), 1);\n\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        return inch;\n    }\n\n    /**\n     * Double类型保留指定位数的小数，返回double类型（四舍五入）\n     * newScale 为指定的位数\n     */\n    private static double formatDouble(double d, int newScale) {\n        BigDecimal bd = new BigDecimal(d);\n        return bd.setScale(newScale, BigDecimal.ROUND_HALF_UP).doubleValue();\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/threadpool/ThreadPoolProxy.java",
    "content": "package com.didichuxing.doraemonkit.util.threadpool;\n\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.LinkedBlockingDeque;\nimport java.util.concurrent.RejectedExecutionHandler;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\npublic class ThreadPoolProxy {\n\n    ThreadPoolExecutor mExecutor;\n    private int mCorePoolSize;\n    private int mMaximumPoolSize;\n\n\n    /**\n     * @param corePoolSize    核心池的大小\n     * @param maximumPoolSize 最大线程数\n     */\n    public ThreadPoolProxy(int corePoolSize, int maximumPoolSize) {\n        mCorePoolSize = corePoolSize;\n        mMaximumPoolSize = maximumPoolSize;\n    }\n\n    /**\n     * 初始化ThreadPoolExecutor\n     * 双重检查加锁,只有在第一次实例化的时候才启用同步机制,提高了性能\n     */\n    private void initThreadPoolExecutor() {\n        if (mExecutor == null || mExecutor.isShutdown() || mExecutor.isTerminated()) {\n            synchronized (ThreadPoolProxy.class) {\n                if (mExecutor == null || mExecutor.isShutdown() || mExecutor.isTerminated()) {\n                    long keepAliveTime = 3000;\n                    TimeUnit unit = TimeUnit.MILLISECONDS;\n                    BlockingQueue workQueue = new LinkedBlockingDeque<>();\n                    ThreadFactory threadFactory = Executors.defaultThreadFactory();\n                    RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();\n\n                    mExecutor = new ThreadPoolExecutor(mCorePoolSize, mMaximumPoolSize, keepAliveTime, unit, workQueue,\n                            threadFactory, handler);\n                }\n            }\n        }\n    }\n    /**\n     执行任务和提交任务的区别?\n     1.有无返回值\n        execute->没有返回值\n        submit-->有返回值\n     2.Future的具体作用?\n        1.有方法可以接收一个任务执行完成之后的结果,其实就是get方法,get方法是一个阻塞方法\n        2.get方法的签名抛出了异常===>可以处理任务执行过程中可能遇到的异常\n     */\n    /**\n     * 执行任务\n     */\n    public void execute(Runnable task) {\n        initThreadPoolExecutor();\n        mExecutor.execute(task);\n    }\n\n    /**\n     * 提交任务\n     */\n    public Future submit(Runnable task) {\n        initThreadPoolExecutor();\n        return mExecutor.submit(task);\n    }\n\n    /**\n     * 移除任务\n     */\n    public void remove(Runnable task) {\n        initThreadPoolExecutor();\n        mExecutor.remove(task);\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/util/threadpool/ThreadPoolProxyFactory.java",
    "content": "package com.didichuxing.doraemonkit.util.threadpool;\n\npublic class ThreadPoolProxyFactory {\n    private static ThreadPoolProxy mThreadPoolProxy;\n\n    public static ThreadPoolProxy getThreadPoolProxy() {\n        if (mThreadPoolProxy == null) {\n            synchronized (ThreadPoolProxyFactory.class) {\n                if (mThreadPoolProxy == null) {\n                    mThreadPoolProxy = new ThreadPoolProxy(5, 5);\n                }\n            }\n        }\n        return mThreadPoolProxy;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/volley/VolleyManager.kt",
    "content": "package com.didichuxing.doraemonkit.volley\n\nimport com.android.volley.Request\nimport com.android.volley.RequestQueue\nimport com.android.volley.toolbox.Volley\nimport com.didichuxing.doraemonkit.DoKit\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/9/15-14:53\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject VolleyManager {\n    private val requestQueue: RequestQueue by lazy {\n        Volley.newRequestQueue(DoKit.APPLICATION)\n    }\n\n    fun <T> add(request: Request<T>) {\n        requestQueue.add(request)\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/JustifyTextView.java",
    "content": "package com.didichuxing.doraemonkit.widget;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.text.Layout;\nimport android.text.StaticLayout;\nimport android.text.TextPaint;\nimport android.util.AttributeSet;\n\nimport androidx.appcompat.widget.AppCompatTextView;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-30-15:37\n * 描    述：解决文字英文自动换行的问题\n * 修订历史：\n * ================================================\n */\npublic class JustifyTextView extends AppCompatTextView {\n\n    private int mLineY;\n    private int mViewWidth;\n    public static final String TWO_CHINESE_BLANK = \"  \";\n\n    public JustifyTextView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right,\n                            int bottom) {\n        super.onLayout(changed, left, top, right, bottom);\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        TextPaint paint = getPaint();\n        paint.setColor(getCurrentTextColor());\n        paint.drawableState = getDrawableState();\n        mViewWidth = getMeasuredWidth();\n        String text = getText().toString();\n        mLineY = 0;\n        mLineY += getTextSize();\n        Layout layout = getLayout();\n\n        // layout.getLayout()在4.4.3出现NullPointerException\n        if (layout == null) {\n            return;\n        }\n\n        Paint.FontMetrics fm = paint.getFontMetrics();\n\n        int textHeight = (int) (Math.ceil(fm.descent - fm.ascent));\n        textHeight = (int) (textHeight * layout.getSpacingMultiplier() + layout\n                .getSpacingAdd());\n        //解决了最后一行文字间距过大的问题\n        for (int i = 0; i < layout.getLineCount(); i++) {\n            int lineStart = layout.getLineStart(i);\n            int lineEnd = layout.getLineEnd(i);\n            float width = StaticLayout.getDesiredWidth(text, lineStart,\n                    lineEnd, getPaint());\n            String line = text.substring(lineStart, lineEnd);\n\n            if (i < layout.getLineCount() - 1) {\n                if (needScale(line)) {\n                    drawScaledText(canvas, lineStart, line, width);\n                } else {\n                    canvas.drawText(line, 0, mLineY, paint);\n                }\n            } else {\n                canvas.drawText(line, 0, mLineY, paint);\n            }\n            mLineY += textHeight;\n        }\n    }\n\n    private void drawScaledText(Canvas canvas, int lineStart, String line,\n                                float lineWidth) {\n        float x = 0;\n        if (isFirstLineOfParagraph(lineStart, line)) {\n            String blanks = \"  \";\n            canvas.drawText(blanks, x, mLineY, getPaint());\n            float bw = StaticLayout.getDesiredWidth(blanks, getPaint());\n            x += bw;\n\n            line = line.substring(3);\n        }\n\n        int gapCount = line.length() - 1;\n        int i = 0;\n        if (line.length() > 2 && line.charAt(0) == 12288\n                && line.charAt(1) == 12288) {\n            String substring = line.substring(0, 2);\n            float cw = StaticLayout.getDesiredWidth(substring, getPaint());\n            canvas.drawText(substring, x, mLineY, getPaint());\n            x += cw;\n            i += 2;\n        }\n\n        float d = (mViewWidth - lineWidth) / gapCount;\n        for (; i < line.length(); i++) {\n            String c = String.valueOf(line.charAt(i));\n            float cw = StaticLayout.getDesiredWidth(c, getPaint());\n            canvas.drawText(c, x, mLineY, getPaint());\n            x += cw + d;\n        }\n    }\n\n    private boolean isFirstLineOfParagraph(int lineStart, String line) {\n        return line.length() > 3 && line.charAt(0) == ' '\n                && line.charAt(1) == ' ';\n    }\n\n    private boolean needScale(String line) {\n        if (line == null || line.length() == 0) {\n            return false;\n        } else {\n            return line.charAt(line.length() - 1) != '\\n';\n        }\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/MultiLineRadioGroup.java",
    "content": "package com.didichuxing.doraemonkit.widget;\n\nimport android.widget.RadioGroup;\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.os.Build;\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.LinearLayout;\nimport android.widget.RadioButton;\nimport android.widget.TableLayout;\nimport android.widget.TableRow;\n\nimport com.didichuxing.doraemonkit.R;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-15-14:46\n * 描    述：多方的RadioGroup\n * 修订历史：\n * ================================================\n */\npublic class MultiLineRadioGroup extends RadioGroup {\n\n    private static final String XML_DEFAULT_BUTTON_PREFIX_INDEX = \"index:\";\n\n    private static final String XML_DEFAULT_BUTTON_PREFIX_TEXT = \"text:\";\n\n    private static final int DEF_VAL_MAX_IN_ROW = 0;\n\n    // for generating ids to APIs lower than 17\n    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);\n\n    // the listener for callbacks to invoke when radio button checked state changes\n    private OnCheckedChangeListener mOnCheckedChangeListener;\n\n    // the listener for callbacks to invoke when radio button is clicked\n    private OnClickListener mOnClickListener;\n\n    // holds the maximum number of radio buttons that should be in a row\n    private int mMaxInRow;\n\n    // all buttons are stored in table layout\n    private TableLayout mTableLayout;\n\n    // list to store all the buttons\n    private List<RadioButton> mRadioButtons;\n\n    // the checked button\n    private RadioButton mCheckedButton;\n\n    /**\n     * Creates a new MultiLineRadioGroup for the given context.\n     *\n     * @param context the application environment\n     */\n    public MultiLineRadioGroup(Context context) {\n        super(context);\n        init(null);\n    }\n\n    /**\n     * Creates a new MultiLineRadioGroup for the given context\n     * and with the specified set attributes.\n     *\n     * @param context the application environment\n     * @param attrs   a collection of attributes\n     */\n    public MultiLineRadioGroup(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init(attrs);\n    }\n\n    // initializes the layout\n    private void init(AttributeSet attrs) {\n        mRadioButtons = new ArrayList<>();\n\n        mTableLayout = getTableLayout();\n        addView(mTableLayout);\n\n        if (attrs != null)\n            initAttrs(attrs);\n    }\n\n    // initializes the layout with the specified attributes\n    private void initAttrs(AttributeSet attrs) {\n        TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(\n                attrs, R.styleable.dk_multi_line_radio_group,\n                0, 0);\n        try {\n            // gets and sets the max in row.\n            setMaxInRow(typedArray.getInt(R.styleable.dk_multi_line_radio_group_max_in_row,\n                    DEF_VAL_MAX_IN_ROW));\n\n            // gets and adds the starting buttons\n            CharSequence[] radioButtonStrings = typedArray.getTextArray(\n                    R.styleable.dk_multi_line_radio_group_radio_buttons);\n            addButtons(radioButtonStrings);\n\n            // gets the default button and checks it if presents.\n            String string = typedArray.getString(R.styleable.dk_multi_line_radio_group_default_button);\n            if (string != null)\n                setDefaultButton(string);\n\n        } finally {\n            typedArray.recycle();\n        }\n    }\n\n    // checks the default button based on the passed string\n    private void setDefaultButton(String string) {\n        final int START_OF_INDEX = XML_DEFAULT_BUTTON_PREFIX_INDEX.length();\n        final int START_OF_TEXT = XML_DEFAULT_BUTTON_PREFIX_TEXT.length();\n\n        // the text of the button to check\n        String buttonToCheck;\n\n        if (string.startsWith(XML_DEFAULT_BUTTON_PREFIX_INDEX)) {\n            String indexString = string.substring(START_OF_INDEX);\n            int index = Integer.parseInt(indexString);\n            if (index < 0 || index >= mRadioButtons.size())\n                throw new IllegalArgumentException(\"index must be between 0 to getRadioButtonCount() - 1 [\" +\n                        (getRadioButtonCount() - 1) + \"]: \" + index);\n            buttonToCheck = mRadioButtons.get(index).getText().toString();\n\n        } else if (string.startsWith(XML_DEFAULT_BUTTON_PREFIX_TEXT)) {\n            buttonToCheck = string.substring(START_OF_TEXT);\n\n        } else { // when there is no prefix assumes the string is the text of the button\n            buttonToCheck = string;\n        }\n\n        check(buttonToCheck);\n    }\n\n    /**\n     * Returns the table layout to set for this layout.\n     *\n     * @return the table layout\n     */\n    protected TableLayout getTableLayout() {\n        return (TableLayout) LayoutInflater.from(getContext())\n                .inflate(R.layout.dk_radio_table_layout, this, false);\n    }\n\n    /**\n     * Returns the table row to set in this layout.\n     *\n     * @return the table row\n     */\n    protected TableRow getTableRow() {\n        return (TableRow) LayoutInflater.from(getContext())\n                .inflate(R.layout.dk_radio_table_row, mTableLayout, false);\n    }\n\n    /**\n     * Returns the default radio button to set in this layout.\n     * <p>\n     * This radio button will be used when radio buttons are added\n     * with the methods addButtons() that accept string varargs.\n     *\n     * @return the radio button\n     */\n    protected RadioButton getRadioButton() {\n        return (RadioButton) LayoutInflater.from(getContext())\n                .inflate(R.layout.dk_radio_button, null);\n    }\n\n    /**\n     * Register a callback to be invoked when a radio button checked state changes.\n     * The only time the listener is passed a button where isChecked is false will be when\n     * you call clearCheck.\n     *\n     * @param onCheckedChangeListener the listener to attach\n     */\n    public void setOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) {\n        this.mOnCheckedChangeListener = onCheckedChangeListener;\n    }\n\n    /**\n     * Register a callback to be invoked when a radio button is clicked.\n     *\n     * @param listener the listener to attach\n     */\n    public void setOnClickListener(OnClickListener listener) {\n        this.mOnClickListener = listener;\n    }\n\n    /**\n     * Returns the maximum radio buttons in a row, 0 for all in one line.\n     *\n     * @return the maximum radio buttons in a row, 0 for all in one line\n     */\n    public int getMaxInRow() {\n        return mMaxInRow;\n    }\n\n    /**\n     * Sets the maximum radio buttons in a row, 0 for all in one line\n     * and arranges the layout accordingly.\n     *\n     * @param maxInRow the maximum radio buttons in a row\n     * @throws IllegalArgumentException if maxInRow is negative\n     */\n    public void setMaxInRow(int maxInRow) {\n        if (maxInRow < 0)\n            throw new IllegalArgumentException(\"maxInRow must not be negative: \" + maxInRow);\n        this.mMaxInRow = maxInRow;\n        arrangeButtons();\n    }\n\n    /**\n     * Adds a view to the layout\n     * <p>\n     * Consider using addButtons() instead\n     *\n     * @param child the view to add\n     */\n    @Override\n    public void addView(View child) {\n        addView(child, -1, child.getLayoutParams());\n    }\n\n    /**\n     * Adds a view to the layout in the specified index\n     * <p>\n     * Consider using addButtons() instead\n     *\n     * @param child the view to add\n     * @param index the index in which to insert the view\n     */\n    @Override\n    public void addView(View child, int index) {\n        addView(child, index, child.getLayoutParams());\n    }\n\n    /**\n     * Adds a view to the layout with the specified width and height.\n     * Note that for radio buttons the width and the height are ignored.\n     * <p>\n     * Consider using addButtons() instead\n     *\n     * @param child  the view to add\n     * @param width  the width of the view\n     * @param height the height of the view\n     */\n    @Override\n    public void addView(View child, int width, int height) {\n        addView(child, -1, new LinearLayout.LayoutParams(width, height));\n    }\n\n    /**\n     * Adds a view to the layout with the specified layout params.\n     * Note that for radio buttons the width and the height are ignored.\n     * <p>\n     * Consider using addButtons() instead\n     *\n     * @param child  the view to add\n     * @param params the layout params of the view\n     */\n    @Override\n    public void addView(View child, ViewGroup.LayoutParams params) {\n        addView(child, -1, params);\n    }\n\n    /**\n     * Adds a view to the layout in the specified index\n     * with the specified layout params.\n     * Note that for radio buttons the width and the height are ignored.\n     * <p>\n     * * Consider using addButtons() instead\n     *\n     * @param child  the view to add\n     * @param index  the index in which to insert the view\n     * @param params the layout params of the view\n     */\n    @Override\n    public void addView(View child, int index, ViewGroup.LayoutParams params) {\n        if (child instanceof RadioButton) {\n            addButtons(index, ((RadioButton) child));\n\n        } else {\n            // if params are null sets them\n            if (params == null) {\n                params = new ViewGroup.LayoutParams(\n                        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);\n            }\n\n            super.addView(child, index, params);\n        }\n    }\n\n    /**\n     * Adds radio buttons to the layout based on the texts in the radioButtons array.\n     * Adds them in the last index.\n     * If radioButtons is null does nothing.\n     *\n     * @param radioButtons the texts of the buttons to add\n     */\n    public void addButtons(CharSequence... radioButtons) {\n        addButtons(-1, radioButtons);\n    }\n\n    /**\n     * Adds radio buttons to the layout based on the texts in the radioButtons array.\n     * Adds them in the specified index, -1 for the last index.\n     * If radioButtons is null does nothing.\n     *\n     * @param index        the index in which to insert the radio buttons\n     * @param radioButtons the texts of the buttons to add\n     * @throws IllegalArgumentException if index is less than -1 or greater than the number of radio buttons\n     */\n    public void addButtons(int index, CharSequence... radioButtons) {\n        if (index < -1 || index > mRadioButtons.size())\n            throw new IllegalArgumentException(\"index must be between -1 to getRadioButtonCount() [\" +\n                    getRadioButtonCount() + \"]: \" + index);\n\n        if (radioButtons == null)\n            return;\n\n        // creates radio button array\n        RadioButton[] buttons = new RadioButton[radioButtons.length];\n        for (int i = 0; i < buttons.length; i++) {\n            RadioButton radioButton = getRadioButton();\n            radioButton.setText(radioButtons[i]);\n            radioButton.setId(generateId());\n            buttons[i] = radioButton;\n        }\n\n        addButtons(index, buttons);\n    }\n\n    // generates an id\n    private int generateId() {\n        // for API 17 or higher\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            return View.generateViewId();\n\n            // for API lower than 17\n        } else {\n\n            while (true) {\n                final int result = sNextGeneratedId.get();\n\n                // aapt-generated IDs have the high byte nonzero; clamp to the range under that.\n                int newValue = result + 1;\n                if (newValue > 0x00FFFFFF)\n                    newValue = 1; // Roll over to 1, not 0.\n\n                if (sNextGeneratedId.compareAndSet(result, newValue))\n                    return result;\n            }\n\n        }\n    }\n\n    /**\n     * Adds radio buttons to the layout.\n     * Adds them in the last index.\n     * If radioButtons is null does nothing.\n     *\n     * @param radioButtons the texts of the buttons to add\n     */\n    public void addButtons(RadioButton... radioButtons) {\n        addButtons(-1, radioButtons);\n    }\n\n    /**\n     * Adds radio buttons to the layout.\n     * Adds them in the specified index, -1 for the last index.\n     * If radioButtons is null does nothing.\n     *\n     * @param index        the index in which to insert the radio buttons\n     * @param radioButtons the texts of the buttons to add\n     * @throws IllegalArgumentException if index is less than -1 or greater than the number of radio buttons\n     */\n    public void addButtons(int index, RadioButton... radioButtons) {\n        if (index < -1 || index > mRadioButtons.size())\n            throw new IllegalArgumentException(\"index must be between -1 to getRadioButtonCount() [\" +\n                    getRadioButtonCount() + \"]: \" + index);\n\n        if (radioButtons == null)\n            return;\n\n        int realIndex = (index != -1) ? index : mRadioButtons.size();\n\n        // inits the buttons and adds them to the list\n        for (RadioButton radioButton : radioButtons) {\n            initRadioButton(radioButton);\n            mRadioButtons.add(realIndex++, radioButton);\n        }\n\n        arrangeButtons();\n    }\n\n    // inits the radio button\n    private void initRadioButton(RadioButton radioButton) {\n        radioButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                boolean didCheckStateChange = checkButton((RadioButton) v);\n                if (didCheckStateChange && mOnCheckedChangeListener != null) {\n                    mOnCheckedChangeListener.onCheckedChanged(MultiLineRadioGroup.this, mCheckedButton);\n                }\n                if (mOnClickListener != null) {\n                    mOnClickListener.onClick(MultiLineRadioGroup.this, mCheckedButton);\n                }\n            }\n        });\n    }\n\n    /**\n     * Removes a view from the layout.\n     * <p>\n     * Consider using removeButton().\n     *\n     * @param view the view to remove\n     */\n    @Override\n    public void removeView(View view) {\n        super.removeView(view);\n    }\n\n    /**\n     * Removes a view from the layout in the specified index.\n     * <p>\n     * Consider using removeButton().\n     *\n     * @param index the index from which to remove the view\n     */\n    @Override\n    public void removeViewAt(int index) {\n        super.removeViewAt(index);\n    }\n\n    /**\n     * Removes the specified range of views from the layout.\n     * <p>\n     * Consider using removeButtons().\n     *\n     * @param start the start index to remove\n     * @param count the number of views to remove\n     */\n    @Override\n    public void removeViews(int start, int count) {\n        super.removeViews(start, count);\n    }\n\n    /**\n     * Removes all the views from the layout.\n     * <p>\n     * Consider using removeAllButtons().\n     */\n    @Override\n    public void removeAllViews() {\n        super.removeAllViews();\n    }\n\n    /**\n     * Removes a radio button from the layout.\n     * If the radio button is null does nothing.\n     *\n     * @param radioButton the radio button to remove\n     */\n    public void removeButton(RadioButton radioButton) {\n        if (radioButton == null)\n            return;\n\n        removeButton(radioButton.getText());\n    }\n\n    /**\n     * Removes a radio button from the layout based on its text.\n     * Removes the first occurrence.\n     * If the text is null does nothing.\n     *\n     * @param text the text of the radio button to remove\n     */\n    public void removeButton(CharSequence text) {\n        if (text == null)\n            return;\n\n        int index = -1;\n\n        for (int i = 0, len = mRadioButtons.size(); i < len; i++) {\n            // checks if the texts are equal\n            if (mRadioButtons.get(i).getText().equals(text)) {\n                index = i;\n                break;\n            }\n        }\n\n        // removes just if the index was found\n        if (index != -1)\n            removeButton(index);\n    }\n\n    /**\n     * Removes the radio button in the specified index from the layout.\n     *\n     * @param index the index from which to remove the radio button\n     * @throws IllegalArgumentException if index is less than 0\n     *                                  or greater than the number of radio buttons - 1\n     */\n    public void removeButton(int index) {\n        removeButtons(index, 1);\n    }\n\n    /**\n     * Removes all the radio buttons in the specified range from the layout.\n     * Count can be any non-negative number.\n     *\n     * @param start the start index to remove\n     * @param count the number of radio buttons to remove\n     * @throws IllegalArgumentException if index is less than 0\n     *                                  or greater than the number of radio buttons - 1\n     *                                  or count is negative\n     */\n    public void removeButtons(int start, int count) {\n        if (count == 0) {\n            return;\n        }\n        if (start < 0 || start >= mRadioButtons.size())\n            throw new IllegalArgumentException(\"start index must be between 0 to getRadioButtonCount() - 1 [\" +\n                    (getRadioButtonCount() - 1) + \"]: \" + start);\n\n        if (count < 0)\n            throw new IllegalArgumentException(\"count must not be negative: \" + count);\n\n        int endIndex = start + count - 1;\n        // if endIndex is not in the range of the radio buttons sets it to the last index\n        if (endIndex >= mRadioButtons.size())\n            endIndex = mRadioButtons.size() - 1;\n\n        // iterates over the buttons to remove\n        for (int i = endIndex; i >= start; i--) {\n            RadioButton radiobutton = mRadioButtons.get(i);\n            // if the button to remove is the checked button set mCheckedButton to null\n            if (radiobutton == mCheckedButton)\n                mCheckedButton = null;\n            // removes the button from the list\n            mRadioButtons.remove(i);\n        }\n\n        arrangeButtons();\n    }\n\n    /**\n     * Removes all the radio buttons from the layout.\n     */\n    public void removeAllButtons() {\n        removeButtons(0, mRadioButtons.size());\n    }\n\n    // arrange the buttons in the layout\n    private void arrangeButtons() {\n        // iterates over each button and puts it in the right place\n        for (int i = 0, len = mRadioButtons.size(); i < len; i++) {\n            RadioButton radioButtonToPlace = mRadioButtons.get(i);\n            int rowToInsert = (mMaxInRow != 0) ? i / mMaxInRow : 0;\n            int columnToInsert = (mMaxInRow != 0) ? i % mMaxInRow : i;\n            // gets the row to insert. if there is no row create one\n            TableRow tableRowToInsert = (mTableLayout.getChildCount() <= rowToInsert)\n                    ? addTableRow() : (TableRow) mTableLayout.getChildAt(rowToInsert);\n            int tableRowChildCount = tableRowToInsert.getChildCount();\n\n            // if there is already a button in the position\n            if (tableRowChildCount > columnToInsert) {\n                RadioButton currentButton = (RadioButton) tableRowToInsert.getChildAt(columnToInsert);\n\n                // insert the button just if the current button is different\n                if (currentButton != radioButtonToPlace) {\n                    // removes the current button\n                    removeButtonFromParent(currentButton, tableRowToInsert);\n                    // removes the button to place from its current position\n                    removeButtonFromParent(radioButtonToPlace, (ViewGroup) radioButtonToPlace.getParent());\n                    // adds the button to the right place\n                    tableRowToInsert.addView(radioButtonToPlace, columnToInsert);\n                }\n\n                // if there isn't already a button in the position\n            } else {\n                // removes the button to place from its current position\n                removeButtonFromParent(radioButtonToPlace, (ViewGroup) radioButtonToPlace.getParent());\n                // adds the button to the right place\n                tableRowToInsert.addView(radioButtonToPlace, columnToInsert);\n            }\n        }\n\n        removeRedundancies();\n    }\n\n    // removes the redundant rows and radio buttons\n    private void removeRedundancies() {\n        // the number of rows to fit the buttons\n        int rows;\n        if (mRadioButtons.size() == 0)\n            rows = 0;\n        else if (mMaxInRow == 0)\n            rows = 1;\n        else\n            rows = (mRadioButtons.size() - 1) / mMaxInRow + 1;\n\n        int tableChildCount = mTableLayout.getChildCount();\n        // if there are redundant rows remove them\n        if (tableChildCount > rows)\n            mTableLayout.removeViews(rows, tableChildCount - rows);\n\n        tableChildCount = mTableLayout.getChildCount();\n        int maxInRow = (mMaxInRow != 0) ? mMaxInRow : mRadioButtons.size();\n\n        // iterates over the rows\n        for (int i = 0; i < tableChildCount; i++) {\n            TableRow tableRow = (TableRow) mTableLayout.getChildAt(i);\n            int tableRowChildCount = tableRow.getChildCount();\n\n            int startIndexToRemove;\n            int count;\n\n            // if it is the last row removes all redundancies after the last button in the list\n            if (i == tableChildCount - 1) {\n                startIndexToRemove = (mRadioButtons.size() - 1) % maxInRow + 1;\n                count = tableRowChildCount - startIndexToRemove;\n\n                // if it is not the last row removes all the buttons after maxInRow position\n            } else {\n                startIndexToRemove = maxInRow;\n                count = tableRowChildCount - maxInRow;\n            }\n\n            if (count > 0)\n                tableRow.removeViews(startIndexToRemove, count);\n        }\n    }\n\n    // adds and returns a table row\n    private TableRow addTableRow() {\n        TableRow tableRow = getTableRow();\n        mTableLayout.addView(tableRow);\n        return tableRow;\n    }\n\n    // removes a radio button from a parent\n    private void removeButtonFromParent(RadioButton radioButton, ViewGroup parent) {\n        if (radioButton == null || parent == null)\n            return;\n\n        parent.removeView(radioButton);\n    }\n\n    /**\n     * Returns the number of radio buttons.\n     *\n     * @return the number of radio buttons\n     */\n    public int getRadioButtonCount() {\n        return mRadioButtons.size();\n    }\n\n    /**\n     * Returns the radio button in the specified index.\n     * If the index is out of range returns null.\n     *\n     * @param index the index of the radio button\n     * @return the radio button\n     */\n    public RadioButton getRadioButtonAt(int index) {\n        if (index < 0 || index >= mRadioButtons.size())\n            return null;\n\n        return mRadioButtons.get(index);\n    }\n\n    /**\n     * Returns true if there is a button with the specified text, false otherwise.\n     *\n     * @param button the text to search\n     * @return true if there is a button with the specified text, false otherwise\n     */\n    public boolean containsButton(String button) {\n        for (RadioButton radioButton : mRadioButtons)\n            if (radioButton.getText().equals(button))\n                return true;\n\n        return false;\n    }\n\n    /**\n     * Checks the radio button with the specified id.\n     * If the specified id is not found does nothing.\n     *\n     * @param id the radio button's id\n     */\n    @Override\n    public void check(int id) {\n        if (id <= 0)\n            return;\n\n        for (RadioButton radioButton : mRadioButtons) {\n            if (radioButton.getId() == id) {\n                if (checkButton(radioButton)) { // True if the button wasn't already checked.\n                    if (mOnCheckedChangeListener != null) {\n                        mOnCheckedChangeListener.onCheckedChanged(\n                                MultiLineRadioGroup.this, radioButton);\n                    }\n                }\n                return;\n            }\n        }\n    }\n\n    /**\n     * Checks the radio button with the specified text.\n     * If there is more than one radio button associated with this text\n     * checks the first radio button.\n     * If the specified text is not found does nothing.\n     *\n     * @param text the radio button's text\n     */\n    public void check(CharSequence text) {\n        if (text == null)\n            return;\n\n        for (RadioButton radioButton : mRadioButtons) {\n            if (radioButton.getText().equals(text)) {\n                if (checkButton(radioButton)) { // True if the button wasn't already checked.\n                    if (mOnCheckedChangeListener != null) {\n                        mOnCheckedChangeListener.onCheckedChanged(\n                                MultiLineRadioGroup.this, radioButton);\n                    }\n                }\n                return;\n            }\n        }\n    }\n\n    /**\n     * Checks the radio button at the specified index.\n     * If the specified index is invalid does nothing.\n     *\n     * @param index the radio button's index\n     */\n    public void checkAt(int index) {\n        if (index < 0 || index >= mRadioButtons.size())\n            return;\n\n        if (checkButton(mRadioButtons.get(index))) { // True if the button wasn't already checked.\n            if (mOnCheckedChangeListener != null) {\n                mOnCheckedChangeListener.onCheckedChanged(\n                        MultiLineRadioGroup.this, mRadioButtons.get(index));\n            }\n        }\n    }\n\n    /**\n     * Checks and switches the button with mCheckedButton.\n     * Returns true if check state changed, false otherwise.\n     *\n     * @param button the button to check.\n     * @return true if check state changed, false otherwise.\n     */\n    private boolean checkButton(RadioButton button) {\n        if (button == null || button == mCheckedButton) {\n            return false;\n        }\n\n        // if the button to check is different from the current checked button\n        // if exists un-checks mCheckedButton\n        if (mCheckedButton != null) {\n            mCheckedButton.setChecked(false);\n        }\n\n        button.setChecked(true);\n        mCheckedButton = button;\n        return true;\n    }\n\n    /**\n     * Clears the checked radio button.\n     */\n    @Override\n    public void clearCheck() {\n        if (mCheckedButton != null) {\n            mCheckedButton.setChecked(false);\n            if (mOnCheckedChangeListener != null) {\n                mOnCheckedChangeListener.onCheckedChanged(this, mCheckedButton);\n            }\n        }\n        mCheckedButton = null;\n    }\n\n    /**\n     * Returns the checked radio button's id.\n     * If no radio buttons are checked returns -1.\n     *\n     * @return the checked radio button's id\n     */\n    @Override\n    public int getCheckedRadioButtonId() {\n        if (mCheckedButton == null)\n            return -1;\n\n        return mCheckedButton.getId();\n    }\n\n    /**\n     * Returns the checked radio button's index.\n     * If no radio buttons are checked returns -1.\n     *\n     * @return the checked radio button's index\n     */\n    public int getCheckedRadioButtonIndex() {\n        if (mCheckedButton == null)\n            return -1;\n\n        return mRadioButtons.indexOf(mCheckedButton);\n    }\n\n    /**\n     * Returns the checked radio button's text.\n     * If no radio buttons are checked returns null.\n     *\n     * @return the checked radio buttons's text\n     */\n    public CharSequence getCheckedRadioButtonText() {\n        if (mCheckedButton == null)\n            return null;\n\n        return mCheckedButton.getText();\n    }\n\n    /**\n     * Returns a parcelable representing the saved state of this layout.\n     *\n     * @return a parcelable representing the saved state of this layout\n     */\n    @Override\n    protected Parcelable onSaveInstanceState() {\n        Parcelable parcelable = super.onSaveInstanceState();\n\n        SavedState savedState = new SavedState(parcelable);\n\n        savedState.mMaxInRow = this.mMaxInRow;\n        savedState.mCheckedButtonIndex = getCheckedRadioButtonIndex();\n\n        return savedState;\n    }\n\n    /**\n     * Restores the state of this layout from a parcelable.\n     *\n     * @param state the parcelable\n     */\n    @Override\n    protected void onRestoreInstanceState(Parcelable state) {\n        if (!(state instanceof SavedState)) {\n            super.onRestoreInstanceState(state);\n            return;\n        }\n\n        SavedState savedState = (SavedState) state;\n        super.onRestoreInstanceState(savedState.getSuperState());\n        setMaxInRow(savedState.mMaxInRow);\n        checkAt(savedState.mCheckedButtonIndex);\n    }\n\n    /**\n     * Interface definition for a callback to be invoked when a radio button is checked.\n     */\n    public interface OnCheckedChangeListener {\n\n        /**\n         * Called when a radio button is checked.\n         *\n         * @param group  the group that stores the radio button\n         * @param button the radio button that was checked\n         */\n        void onCheckedChanged(ViewGroup group, RadioButton button);\n    }\n\n    /**\n     * Interface definition for a callback to be invoked when a radio button is clicked.\n     * Clicking a radio button multiple times will result in multiple callbacks.\n     */\n    public interface OnClickListener {\n\n        /**\n         * Called when a radio button is clicked.\n         *\n         * @param group  the group that stores the radio button\n         * @param button the radio button that was clicked\n         */\n        void onClick(ViewGroup group, RadioButton button);\n    }\n\n    /**\n     * A class definition to save and restore a state of this layout.\n     */\n    private static class SavedState extends BaseSavedState {\n\n        /**\n         * The creator of this class.\n         */\n        public static final Parcelable.Creator CREATOR =\n                new Creator<SavedState>() {\n\n                    /**\n                     * Creates SavedState instance from a specified parcel.\n                     *\n                     * @param in the parcel to create from\n                     * @return an instance of SavedState\n                     */\n                    @Override\n                    public SavedState createFromParcel(Parcel in) {\n                        return new SavedState(in);\n                    }\n\n                    /**\n                     * Creates a new array of the Parcelable class,\n                     * with every entry initialized to null.\n                     *\n                     * @param size the size of the array.\n                     * @return an array of the Parcelable class\n                     */\n                    @Override\n                    public SavedState[] newArray(int size) {\n                        return new SavedState[size];\n                    }\n\n                };\n\n        int mMaxInRow;\n        int mCheckedButtonIndex;\n\n        /**\n         * Constructor called by derived classes when creating their SavedState objects.\n         *\n         * @param superState the state of the superclass of this view\n         */\n        SavedState(Parcelable superState) {\n            super(superState);\n        }\n\n        /**\n         * Constructor to create a new instance with the specified parcel.\n         *\n         * @param in the parcel\n         */\n        SavedState(Parcel in) {\n            super(in);\n\n            mMaxInRow = in.readInt();\n            mCheckedButtonIndex = in.readInt();\n        }\n\n        /**\n         * Saves this object to a parcel.\n         *\n         * @param out   the parcel to write to\n         * @param flags additional flags about how the object should be written,\n         *              May be 0 or PARCELABLE_WRITE_RETURN_VALUE\n         */\n        @Override\n        public void writeToParcel(Parcel out, int flags) {\n            super.writeToParcel(out, flags);\n\n            out.writeInt(mMaxInRow);\n            out.writeInt(mCheckedButtonIndex);\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/bottomview/AssociationView.java",
    "content": "package com.didichuxing.doraemonkit.widget.bottomview;\n\nimport android.view.View;\n\n/**\n * 可提交的view\n *\n * @author vinda\n * @since 15/5/21\n */\npublic abstract class AssociationView {\n\n    private OnStateChangeListener onStateChangeListener;\n\n    /**\n     * 提交\n     */\n    public abstract Object submit();\n\n    /**\n     * 取消\n     */\n    public abstract void cancel();\n\n    /**\n     * 获取视图\n     *\n     * @return\n     */\n    public abstract View getView();\n\n    /**\n     * 能否提交\n     *\n     * @return\n     */\n    public abstract boolean isCanSubmit();\n\n    public abstract void onShow();\n\n    public abstract void onHide();\n\n\n    final void setOnStateChangeListener(OnStateChangeListener listener) {\n        onStateChangeListener = listener;\n    }\n\n    final OnStateChangeListener getOnStateChangeListener() {\n        return onStateChangeListener;\n    }\n\n\n    interface OnStateChangeListener {\n        void onStateChanged();\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/bottomview/BottomUpWindow.java",
    "content": "package com.didichuxing.doraemonkit.widget.bottomview;\n\nimport android.content.Context;\nimport android.graphics.drawable.ColorDrawable;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.animation.Animation;\nimport android.view.animation.TranslateAnimation;\nimport android.widget.FrameLayout;\nimport android.widget.PopupWindow;\n\nimport com.didichuxing.doraemonkit.R;\n\n/**\n * 从底部向上弹出的选择器\n *\n * @author vinda\n * @since 15/5/21\n */\npublic class BottomUpWindow extends PopupWindow {\n    private final String TAG = \"BottomUpSelectWindow\";\n    private View thisView;\n    private View tv_submit;\n    private final View titleViiew;\n    private FrameLayout contentPanel;\n    private AssociationView associationView;\n\n    private View ll_panel;\n    private View.OnClickListener onClickListener = new View.OnClickListener() {\n        @Override\n        public void onClick(View v) {\n            final int vid = v.getId();\n            if (vid == R.id.tv_submit) {\n                Object submit = associationView.submit();\n                if (mOnSubmitListener != null) {\n                    mOnSubmitListener.submit(submit);\n                }\n                dismiss();\n            } else if (vid == R.id.tv_cancel) {\n                cancel();\n            }\n        }\n\n    };\n\n    public BottomUpWindow(Context context) {\n        super(context);\n        LayoutInflater layoutInflater = LayoutInflater.from(context);\n\n        thisView = layoutInflater.inflate(R.layout.dk_item_layout_bottom_up_select_window, null);\n        ll_panel = thisView.findViewById(R.id.ll_panel);\n        titleViiew = thisView.findViewById(R.id.tv_title);\n\n        contentPanel = thisView.findViewById(R.id.content);\n        this.setContentView(thisView);\n        initView();\n\n        this.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);\n        this.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);\n        this.setFocusable(true);\n        this.setTouchable(true);\n        this.setOutsideTouchable(true);\n\n        ColorDrawable dw = new ColorDrawable(0x80000000);\n\n        this.setBackgroundDrawable(dw);\n    }\n\n    private void initView() {\n        tv_submit = thisView.findViewById(R.id.tv_submit);\n        tv_submit.setOnClickListener(onClickListener);\n        thisView.findViewById(R.id.tv_cancel).setOnClickListener(onClickListener);\n        //点击在上方时关闭\n        thisView.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                cancel();\n\n            }\n        });\n    }\n\n    /**\n     * 设置中间内容的view\n     *\n     * @param view\n     */\n    public BottomUpWindow setContent(AssociationView view) {\n        associationView = view;\n        contentPanel.removeAllViews();\n        contentPanel.addView(associationView.getView());\n        associationView.setOnStateChangeListener(new AssociationView.OnStateChangeListener() {\n            @Override\n            public void onStateChanged() {\n                tv_submit.setEnabled(associationView.isCanSubmit());\n            }\n        });\n        return this;\n    }\n\n    @Override\n    public void dismiss() {\n        //动画\n        TranslateAnimation animation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0,\n                Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1);\n        animation.setDuration(200);\n        animation.setAnimationListener(new Animation.AnimationListener() {\n            @Override\n            public void onAnimationStart(Animation animation) {\n\n            }\n\n            @Override\n            public void onAnimationEnd(Animation animation) {\n                ll_panel.setVisibility(View.GONE);\n                dismissWindow();\n            }\n\n            @Override\n            public void onAnimationRepeat(Animation animation) {\n\n            }\n        });\n        ll_panel.startAnimation(animation);\n        if (associationView != null) {\n            associationView.onHide();\n        }\n    }\n\n    /**\n     * 隐藏整个窗口\n     */\n    private void dismissWindow() {\n        try {\n            super.dismiss();\n        } catch (Throwable e) {\n        }\n    }\n\n    private void cancel() {\n        associationView.cancel();\n        dismiss();\n\n        if (mOnSubmitListener != null) {\n            mOnSubmitListener.cancel();\n        }\n    }\n\n    public BottomUpWindow show(View parent) {\n        this.showAtLocation(parent, Gravity.BOTTOM\n                | Gravity.CENTER_HORIZONTAL, 0, 0);\n        ll_panel.setVisibility(View.VISIBLE);\n        //动画\n        TranslateAnimation animation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0,\n                Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 0);\n        animation.setDuration(200);\n        ll_panel.startAnimation(animation);\n        if (associationView != null) {\n            associationView.onShow();\n        }\n        return this;\n    }\n\n    private OnSubmitListener mOnSubmitListener;\n\n    public void setOnSubmitListener(OnSubmitListener onSubmitListener) {\n        this.mOnSubmitListener = onSubmitListener;\n    }\n\n    public interface OnSubmitListener {\n\n        void submit(Object object);\n\n        void cancel();\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/bottomview/EditSpInputView.java",
    "content": "package com.didichuxing.doraemonkit.widget.bottomview;\n\nimport android.content.Context;\nimport android.text.InputType;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.EditText;\n\nimport com.didichuxing.doraemonkit.kit.fileexplorer.SpBean;\n\n\npublic class EditSpInputView extends AssociationView {\n    private final EditText editText;\n    private SpBean spBean;\n\n    public EditSpInputView(Context context, SpBean spBean, int inputType) {\n        this.spBean = spBean;\n        editText = new EditText(context);\n        editText.setText(spBean.value.toString());\n        editText.setInputType(inputType | InputType.TYPE_TEXT_FLAG_MULTI_LINE);\n        editText.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));\n        editText.setSelection(spBean.value.toString().length());\n    }\n\n    @Override\n    public Object submit() {\n        return spBean.toDefaultClass(editText.getText().toString());\n    }\n\n    @Override\n    public void cancel() {\n\n    }\n\n    @Override\n    public View getView() {\n        return editText;\n    }\n\n    @Override\n    public boolean isCanSubmit() {\n        return true;\n    }\n\n    @Override\n    public void onShow() {\n\n    }\n\n    @Override\n    public void onHide() {\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/BaseBinderAdapter.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah\n\nimport android.annotation.SuppressLint\nimport android.util.SparseArray\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.RecyclerView\nimport com.didichuxing.doraemonkit.widget.brvah.binder.BaseItemBinder\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n/**\n * 使用 Binder 来实现adapter，既可以实现单布局，也能实现多布局\n * 数据实体类也不存继承问题\n *\n * 当有多种条目的时候，避免在convert()中做太多的业务逻辑，把逻辑放在对应的 BaseItemBinder 中。\n * 适用于以下情况：\n * 1、实体类不方便扩展，此Adapter的数据类型可以是任意类型，默认情况下不需要实现 getItemType\n * 2、item 类型较多，在convert()中管理起来复杂\n *\n * ViewHolder 由 [BaseItemBinder] 实现，并且每个[BaseItemBinder]可以拥有自己类型的ViewHolder类型。\n *\n * 数据类型为Any\n */\nopen class BaseBinderAdapter(list: MutableList<Any>? = null) : BaseQuickAdapter<Any, BaseViewHolder>(0, list) {\n\n    /**\n     * 用于存储每个 Binder 类型对应的 Diff\n     */\n    private val classDiffMap = HashMap<Class<*>, DiffUtil.ItemCallback<Any>?>()\n\n    private val mTypeMap = HashMap<Class<*>, Int>()\n    private val mBinderArray = SparseArray<BaseItemBinder<Any, *>>()\n\n    init {\n        setDiffCallback(ItemCallback())\n    }\n\n    /**\n     * 添加 ItemBinder\n     */\n    @JvmOverloads\n    fun <T : Any> addItemBinder(clazz: Class<out T>, baseItemBinder: BaseItemBinder<T, *>, callback: DiffUtil.ItemCallback<T>? = null): BaseBinderAdapter {\n        val itemType = mTypeMap.size + 1\n        mTypeMap[clazz] = itemType\n        mBinderArray.append(itemType, baseItemBinder as BaseItemBinder<Any, *>)\n        baseItemBinder._adapter = this\n        callback?.let {\n            classDiffMap[clazz] = it as DiffUtil.ItemCallback<Any>\n        }\n        return this\n    }\n\n    /**\n     * kotlin 可以使用如下方法添加 ItemBinder，更加简单\n     */\n    inline fun <reified T : Any> addItemBinder(baseItemBinder: BaseItemBinder<T, *>, callback: DiffUtil.ItemCallback<T>? = null): BaseBinderAdapter {\n        addItemBinder(T::class.java, baseItemBinder, callback)\n        return this\n    }\n\n    override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {\n        return getItemBinder(viewType).let {\n            it._context = context\n            it.onCreateViewHolder(parent, viewType)\n        }\n    }\n\n    override fun convert(holder: BaseViewHolder, item: Any) {\n        getItemBinder(holder.itemViewType).convert(holder, item)\n    }\n\n    override fun convert(holder: BaseViewHolder, item: Any, payloads: List<Any>) {\n        getItemBinder(holder.itemViewType).convert(holder, item, payloads)\n    }\n\n    open fun getItemBinder(viewType: Int): BaseItemBinder<Any, BaseViewHolder> {\n        val binder = mBinderArray[viewType]\n        checkNotNull(binder) { \"getItemBinder: viewType '$viewType' no such Binder found，please use addItemBinder() first!\" }\n        return binder as BaseItemBinder<Any, BaseViewHolder>\n    }\n\n    open fun getItemBinderOrNull(viewType: Int): BaseItemBinder<Any, BaseViewHolder>? {\n        val binder = mBinderArray[viewType]\n        return binder as? BaseItemBinder<Any, BaseViewHolder>\n    }\n\n    override fun getDefItemViewType(position: Int): Int {\n        return findViewType(data[position].javaClass)\n    }\n\n    override fun bindViewClickListener(viewHolder: BaseViewHolder, viewType: Int) {\n        super.bindViewClickListener(viewHolder, viewType)\n        bindClick(viewHolder)\n        bindChildClick(viewHolder, viewType)\n    }\n\n    override fun onViewAttachedToWindow(holder: BaseViewHolder) {\n        super.onViewAttachedToWindow(holder)\n        getItemBinderOrNull(holder.itemViewType)?.onViewAttachedToWindow(holder)\n    }\n\n    override fun onViewDetachedFromWindow(holder: BaseViewHolder) {\n        super.onViewDetachedFromWindow(holder)\n        getItemBinderOrNull(holder.itemViewType)?.onViewDetachedFromWindow(holder)\n\n    }\n\n    override fun onFailedToRecycleView(holder: BaseViewHolder): Boolean {\n        return getItemBinderOrNull(holder.itemViewType)?.onFailedToRecycleView(holder) ?: false\n    }\n\n    protected fun findViewType(clazz : Class<*>):Int {\n        val type = mTypeMap[clazz]\n        checkNotNull(type) { \"findViewType: ViewType: $clazz Not Find!\" }\n        return type\n    }\n\n    protected open fun bindClick(viewHolder: BaseViewHolder) {\n        if (getOnItemClickListener() == null) {\n            //如果没有设置点击监听，则回调给 itemProvider\n            //Callback to itemProvider if no click listener is set\n            viewHolder.itemView.setOnClickListener {\n                var position = viewHolder.adapterPosition\n                if (position == RecyclerView.NO_POSITION) {\n                    return@setOnClickListener\n                }\n                position -= headerLayoutCount\n\n                val itemViewType = viewHolder.itemViewType\n                val binder = getItemBinder(itemViewType)\n\n                binder.onClick(viewHolder, it, data[position], position)\n            }\n        }\n        if (getOnItemLongClickListener() == null) {\n            //如果没有设置长按监听，则回调给itemProvider\n            // If you do not set a long press listener, callback to the itemProvider\n            viewHolder.itemView.setOnLongClickListener {\n                var position = viewHolder.adapterPosition\n                if (position == RecyclerView.NO_POSITION) {\n                    return@setOnLongClickListener false\n                }\n                position -= headerLayoutCount\n\n                val itemViewType = viewHolder.itemViewType\n                val binder = getItemBinder(itemViewType)\n                binder.onLongClick(viewHolder, it, data[position], position)\n            }\n        }\n    }\n\n    protected open fun bindChildClick(viewHolder: BaseViewHolder, viewType: Int) {\n        if (getOnItemChildClickListener() == null) {\n            val provider = getItemBinder(viewType)\n            val ids = provider.getChildClickViewIds()\n            ids.forEach { id ->\n                viewHolder.itemView.findViewById<View>(id)?.let {\n                    if (!it.isClickable) {\n                        it.isClickable = true\n                    }\n                    it.setOnClickListener { v ->\n                        var position: Int = viewHolder.adapterPosition\n                        if (position == RecyclerView.NO_POSITION) {\n                            return@setOnClickListener\n                        }\n                        position -= headerLayoutCount\n                        provider.onChildClick(viewHolder, v, data[position], position)\n                    }\n                }\n            }\n        }\n        if (getOnItemChildLongClickListener() == null) {\n            val provider = getItemBinder(viewType)\n            val ids = provider.getChildLongClickViewIds()\n            ids.forEach { id ->\n                viewHolder.itemView.findViewById<View>(id)?.let {\n                    if (!it.isLongClickable) {\n                        it.isLongClickable = true\n                    }\n                    it.setOnLongClickListener { v ->\n                        var position: Int = viewHolder.adapterPosition\n                        if (position == RecyclerView.NO_POSITION) {\n                            return@setOnLongClickListener false\n                        }\n                        position -= headerLayoutCount\n                        provider.onChildLongClick(viewHolder, v, data[position], position)\n                    }\n                }\n            }\n        }\n    }\n\n\n    /**\n     * Diff Callback\n     */\n    private inner class ItemCallback : DiffUtil.ItemCallback<Any>() {\n\n        override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean {\n            if (oldItem.javaClass == newItem.javaClass) {\n                classDiffMap[oldItem.javaClass]?.let {\n                    return it.areItemsTheSame(oldItem, newItem)\n                }\n            }\n\n            return oldItem == newItem\n        }\n\n        @SuppressLint(\"DiffUtilEquals\")\n        override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean {\n            if (oldItem.javaClass == newItem.javaClass) {\n                classDiffMap[oldItem.javaClass]?.let {\n                    return it.areContentsTheSame(oldItem, newItem)\n                }\n            }\n\n            return true\n        }\n\n        override fun getChangePayload(oldItem: Any, newItem: Any): Any? {\n            if (oldItem.javaClass == newItem.javaClass) {\n                return classDiffMap[oldItem.javaClass]?.getChangePayload(oldItem, newItem)\n            }\n            return null\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/BaseDelegateMultiAdapter.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah\n\nimport android.view.ViewGroup\nimport com.didichuxing.doraemonkit.widget.brvah.delegate.BaseMultiTypeDelegate\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n/**\n * 多类型布局，通过代理类的方式，返回布局 id 和 item 类型；\n * 适用于:\n * 1、实体类不方便扩展，此Adapter的数据类型可以是任意类型，只需要在[BaseMultiTypeDelegate.getItemType]中返回对应类型\n * 2、item 类型较少\n * 如果类型较多，为了方便隔离各类型的业务逻辑，推荐使用[BaseBinderAdapter]\n *\n * @param T\n * @param VH : BaseViewHolder\n * @property mMultiTypeDelegate BaseMultiTypeDelegate<T>?\n * @constructor\n */\nabstract class BaseDelegateMultiAdapter<T, VH : BaseViewHolder>(data: MutableList<T>? = null) :\n        BaseQuickAdapter<T, VH>(0, data) {\n\n    private var mMultiTypeDelegate: BaseMultiTypeDelegate<T>? = null\n\n    /**\n     * 通过此方法设置代理\n     * @param multiTypeDelegate BaseMultiTypeDelegate<T>\n     */\n    fun setMultiTypeDelegate(multiTypeDelegate: BaseMultiTypeDelegate<T>) {\n        this.mMultiTypeDelegate = multiTypeDelegate\n    }\n\n    fun getMultiTypeDelegate(): BaseMultiTypeDelegate<T>? = mMultiTypeDelegate\n\n    override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VH {\n        val delegate = getMultiTypeDelegate()\n        checkNotNull(delegate) { \"Please use setMultiTypeDelegate first!\" }\n        val layoutId = delegate.getLayoutId(viewType)\n        return createBaseViewHolder(parent, layoutId)\n    }\n\n    override fun getDefItemViewType(position: Int): Int {\n        val delegate = getMultiTypeDelegate()\n        checkNotNull(delegate) { \"Please use setMultiTypeDelegate first!\" }\n        return delegate.getItemType(data, position)\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/BaseMultiItemQuickAdapter.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah\n\nimport android.util.SparseIntArray\nimport android.view.ViewGroup\nimport androidx.annotation.LayoutRes\nimport com.didichuxing.doraemonkit.widget.brvah.entity.MultiItemEntity\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n/**\n * 多类型布局，适用于类型较少，业务不复杂的场景，便于快速使用。\n * data[T]必须实现[MultiItemEntity]\n *\n * 如果数据类无法实现[MultiItemEntity]，请使用[BaseDelegateMultiAdapter]\n * 如果类型较多，为了方便隔离各类型的业务逻辑，推荐使用[BaseProviderMultiAdapter]\n *\n * @param T : MultiItemEntity\n * @param VH : BaseViewHolder\n * @constructor\n */\nabstract class BaseMultiItemQuickAdapter<T : MultiItemEntity, VH : BaseViewHolder>(data: MutableList<T>? = null)\n    : BaseQuickAdapter<T, VH>(0, data) {\n\n    private val layouts: SparseIntArray by lazy(LazyThreadSafetyMode.NONE) { SparseIntArray() }\n\n    override fun getDefItemViewType(position: Int): Int {\n        return data[position].itemType\n    }\n\n    override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VH {\n        val layoutResId = layouts.get(viewType)\n        require(layoutResId != 0) { \"ViewType: $viewType found layoutResId，please use addItemType() first!\" }\n        return createBaseViewHolder(parent, layoutResId)\n    }\n\n    /**\n     * 调用此方法，设置多布局\n     * @param type Int\n     * @param layoutResId Int\n     */\n    protected fun addItemType(type: Int, @LayoutRes layoutResId: Int) {\n        layouts.put(type, layoutResId)\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/BaseNodeAdapter.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah\n\nimport androidx.annotation.IntRange\nimport androidx.recyclerview.widget.DiffUtil\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.BaseExpandNode\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.BaseNode\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.NodeFooterImp\nimport com.didichuxing.doraemonkit.widget.brvah.provider.BaseItemProvider\nimport com.didichuxing.doraemonkit.widget.brvah.provider.BaseNodeProvider\n\nabstract class BaseNodeAdapter(nodeList: MutableList<BaseNode>? = null)\n    : BaseProviderMultiAdapter<BaseNode>(null) {\n\n    private val fullSpanNodeTypeSet = HashSet<Int>()\n\n    init {\n        if (!nodeList.isNullOrEmpty()) {\n            val flatData = flatData(nodeList)\n            this.data.addAll(flatData)\n        }\n    }\n\n\n    /**\n     * 添加 node provider\n     * @param provider BaseItemProvider\n     */\n    fun addNodeProvider(provider: BaseNodeProvider) {\n        addItemProvider(provider)\n    }\n\n    /**\n     * 添加需要铺满的 node provider\n     * @param provider BaseItemProvider\n     */\n    fun addFullSpanNodeProvider(provider: BaseNodeProvider) {\n        fullSpanNodeTypeSet.add(provider.itemViewType)\n        addItemProvider(provider)\n    }\n\n    /**\n     * 添加脚部 node provider\n     * 铺满一行或者一列\n     * @param provider BaseItemProvider\n     */\n    fun addFooterNodeProvider(provider: BaseNodeProvider) {\n        addFullSpanNodeProvider(provider)\n    }\n\n    /**\n     * 请勿直接通过此方法添加 node provider！\n     * @param provider BaseItemProvider<BaseNode, VH>\n     */\n    override fun addItemProvider(provider: BaseItemProvider<BaseNode>) {\n        if (provider is BaseNodeProvider) {\n            super.addItemProvider(provider)\n        } else {\n            throw IllegalStateException(\"Please add BaseNodeProvider, no BaseItemProvider!\")\n        }\n    }\n\n    override fun isFixedViewType(type: Int): Boolean {\n        return super.isFixedViewType(type) || fullSpanNodeTypeSet.contains(type)\n    }\n\n    override fun setNewInstance(list: MutableList<BaseNode>?) {\n        super.setNewInstance(flatData(list ?: arrayListOf()))\n    }\n\n    /**\n     * 替换整个列表数据，如果需要对某节点下的子节点进行替换，请使用[nodeReplaceChildData]！\n     */\n    override fun setList(list: Collection<BaseNode>?) {\n        super.setList(flatData(list ?: arrayListOf()))\n    }\n\n    /**\n     * 如果需要对某节点下的子节点进行数据操作，请使用[nodeAddData]！\n     *\n     * @param position Int 整个 data 的 index\n     * @param data BaseNode\n     */\n    override fun addData(position: Int, data: BaseNode) {\n        addData(position, arrayListOf(data))\n    }\n\n    override fun addData(data: BaseNode) {\n        addData(arrayListOf(data))\n    }\n\n    override fun addData(position: Int, newData: Collection<BaseNode>) {\n        val nodes = flatData(newData)\n        super.addData(position, nodes)\n    }\n\n    override fun addData(newData: Collection<BaseNode>) {\n        val nodes = flatData(newData)\n        super.addData(nodes)\n    }\n\n    /**\n     * 如果需要对某节点下的子节点进行数据操作，请使用[nodeRemoveData]！\n     *\n     * @param position Int 整个 data 的 index\n     */\n    override fun remove(position: Int) {\n        val removeCount = removeAt(position)\n        notifyItemRangeRemoved(position + headerLayoutCount, removeCount)\n        compatibilityDataSizeChanged(0)\n    }\n\n    /**\n     * 如果需要对某节点下的子节点进行数据操作，请使用[nodeSetData]！\n     * @param index Int\n     * @param data BaseNode\n     */\n    override fun setData(index: Int, data: BaseNode) {\n        // 先移除，再添加\n        val removeCount = removeAt(index)\n\n        val newFlatData = flatData(arrayListOf(data))\n        this.data.addAll(index, newFlatData)\n\n        if (removeCount == newFlatData.size) {\n            notifyItemRangeChanged(index + headerLayoutCount, removeCount)\n        } else {\n            notifyItemRangeRemoved(index + headerLayoutCount, removeCount)\n            notifyItemRangeInserted(index + headerLayoutCount, newFlatData.size)\n\n//        notifyItemRangeChanged(index + getHeaderLayoutCount(), max(removeCount, newFlatData.size)\n        }\n    }\n\n    override fun setDiffNewData(list: MutableList<BaseNode>?) {\n        if (hasEmptyView()) {\n            setNewInstance(list)\n            return\n        }\n        super.setDiffNewData(flatData(list ?: arrayListOf()))\n    }\n\n    override fun setDiffNewData(diffResult: DiffUtil.DiffResult, list: MutableList<BaseNode>) {\n        if (hasEmptyView()) {\n            setNewInstance(list)\n            return\n        }\n        super.setDiffNewData(diffResult, flatData(list))\n    }\n\n    /**\n     * 从数组中移除\n     * @param position Int\n     * @return Int 被移除的数量\n     */\n    private fun removeAt(position: Int): Int {\n        if (position >= data.size) {\n            return 0\n        }\n        // 记录被移除的item数量\n        var removeCount = 0\n\n        // 先移除子项\n        removeCount = removeChildAt(position)\n\n        // 移除node自己\n        this.data.removeAt(position)\n        removeCount += 1\n\n        val node = this.data[position]\n        // 移除脚部\n        if (node is NodeFooterImp && node.footerNode != null) {\n            this.data.removeAt(position)\n            removeCount += 1\n        }\n        return removeCount\n    }\n\n    private fun removeChildAt(position: Int): Int {\n        if (position >= data.size) {\n            return 0\n        }\n        // 记录被移除的item数量\n        var removeCount = 0\n\n        val node = this.data[position]\n        // 移除子项\n        if (!node.childNode.isNullOrEmpty()) {\n            if (node is BaseExpandNode) {\n                if (node.isExpanded) {\n                    val items = flatData(node.childNode!!)\n                    this.data.removeAll(items)\n                    removeCount = items.size\n                }\n            } else {\n                val items = flatData(node.childNode!!)\n                this.data.removeAll(items)\n                removeCount = items.size\n            }\n        }\n        return removeCount\n    }\n\n    /*************************** 重写数据设置方法 END ***************************/\n\n\n    /*************************** Node 数据操作 ***************************/\n\n    /**\n     * 对指定的父node，添加子node\n     * @param parentNode BaseNode 父node\n     * @param data BaseNode 子node\n     */\n    fun nodeAddData(parentNode: BaseNode, data: BaseNode) {\n        parentNode.childNode?.let {\n            it.add(data)\n\n            if (parentNode is BaseExpandNode && !parentNode.isExpanded) {\n                return\n            }\n\n            val parentIndex = this.data.indexOf(parentNode)\n            val childIndex = it.size\n            addData(parentIndex + childIndex, data)\n        }\n    }\n\n    /**\n     * 对指定的父node，在指定位置添加子node\n     * @param parentNode BaseNode 父node\n     * @param childIndex Int 此位置是相对于其childNodes数据的位置！并不是整个data\n     * @param data BaseNode 添加的数据\n     */\n    fun nodeAddData(parentNode: BaseNode, childIndex: Int, data: BaseNode) {\n        parentNode.childNode?.let {\n            it.add(childIndex, data)\n\n            if (parentNode is BaseExpandNode && !parentNode.isExpanded) {\n                return\n            }\n\n            val parentIndex = this.data.indexOf(parentNode)\n            val pos = parentIndex + 1 + childIndex\n            addData(pos, data)\n        }\n    }\n\n    /**\n     * 对指定的父node，在指定位置添加子node集合\n     * @param parentNode BaseNode 父node\n     * @param childIndex Int 此位置是相对于其childNodes数据的位置！并不是整个data\n     * @param newData 添加的数据集合\n     */\n    fun nodeAddData(parentNode: BaseNode, childIndex: Int, newData: Collection<BaseNode>) {\n        parentNode.childNode?.let {\n            it.addAll(childIndex, newData)\n\n            if (parentNode is BaseExpandNode && !parentNode.isExpanded) {\n                return\n            }\n            val parentIndex = this.data.indexOf(parentNode)\n            val pos = parentIndex + 1 + childIndex\n            addData(pos, newData)\n        }\n    }\n\n    /**\n     * 对指定的父node下对子node进行移除\n     * @param parentNode BaseNode 父node\n     * @param childIndex Int 此位置是相对于其childNodes数据的位置！并不是整个data\n     */\n    fun nodeRemoveData(parentNode: BaseNode, childIndex: Int) {\n        parentNode.childNode?.let {\n            if (childIndex >= it.size) {\n                return\n            }\n\n            if (parentNode is BaseExpandNode && !parentNode.isExpanded) {\n                it.removeAt(childIndex)\n                return\n            }\n\n            val parentIndex = this.data.indexOf(parentNode)\n            val pos = parentIndex + 1 + childIndex\n            remove(pos)\n\n            it.removeAt(childIndex)\n        }\n    }\n\n    /**\n     * 对指定的父node下对子node进行移除\n     * @param parentNode BaseNode 父node\n     * @param childNode BaseNode 子node\n     */\n    fun nodeRemoveData(parentNode: BaseNode, childNode: BaseNode) {\n        parentNode.childNode?.let {\n            if (parentNode is BaseExpandNode && !parentNode.isExpanded) {\n                it.remove(childNode)\n                return\n            }\n            remove(childNode)\n\n            it.remove(childNode)\n        }\n    }\n\n    /**\n     * 改变指定的父node下的子node数据\n     * @param parentNode BaseNode\n     * @param childIndex Int 此位置是相对于其childNodes数据的位置！并不是整个data\n     * @param data BaseNode 新数据\n     */\n    fun nodeSetData(parentNode: BaseNode, childIndex: Int, data: BaseNode) {\n        parentNode.childNode?.let {\n            if (childIndex >= it.size) {\n                return\n            }\n\n            if (parentNode is BaseExpandNode && !parentNode.isExpanded) {\n                it[childIndex] = data\n                return\n            }\n\n            val parentIndex = this.data.indexOf(parentNode)\n            val pos = parentIndex + 1 + childIndex\n            setData(pos, data)\n\n            it[childIndex] = data\n        }\n    }\n\n    /**\n     * 替换父节点下的子节点集合\n     * @param parentNode BaseNode\n     * @param newData Collection<BaseNode>\n     */\n    fun nodeReplaceChildData(parentNode: BaseNode, newData: Collection<BaseNode>) {\n        parentNode.childNode?.let {\n            if (parentNode is BaseExpandNode && !parentNode.isExpanded) {\n                it.clear()\n                it.addAll(newData)\n                return\n            }\n\n            val parentIndex = this.data.indexOf(parentNode)\n            val removeCount = removeChildAt(parentIndex)\n\n            it.clear()\n            it.addAll(newData)\n\n            val newFlatData = flatData(newData)\n            this.data.addAll(parentIndex + 1, newFlatData)\n\n            val positionStart = parentIndex + 1 + headerLayoutCount\n            if (removeCount == newFlatData.size) {\n                notifyItemRangeChanged(positionStart, removeCount)\n            } else {\n                notifyItemRangeRemoved(positionStart, removeCount)\n                notifyItemRangeInserted(positionStart, newFlatData.size)\n            }\n//            notifyItemRangeChanged(parentIndex + 1 + getHeaderLayoutCount(), max(removeCount, newFlatData.size))\n        }\n    }\n\n    /*************************** Node 数据操作 END ***************************/\n\n    /**\n     * 将输入的嵌套类型数组循环递归，在扁平化数据的同时，设置展开状态\n     * @param list Collection<BaseNode>\n     * @param isExpanded Boolean? 如果不需要改变状态，设置为null。true 为展开，false 为收起\n     * @return MutableList<BaseNode>\n     */\n    private fun flatData(list: Collection<BaseNode>, isExpanded: Boolean? = null): MutableList<BaseNode> {\n        val newList = ArrayList<BaseNode>()\n\n        for (element in list) {\n            newList.add(element)\n\n            if (element is BaseExpandNode) {\n                // 如果是展开状态 或者需要设置为展开状态\n                if (isExpanded == true || element.isExpanded) {\n                    val childNode = element.childNode\n                    if (!childNode.isNullOrEmpty()) {\n                        val items = flatData(childNode, isExpanded)\n                        newList.addAll(items)\n                    }\n                }\n                isExpanded?.let {\n                    element.isExpanded = it\n                }\n            } else {\n                val childNode = element.childNode\n                if (!childNode.isNullOrEmpty()) {\n                    val items = flatData(childNode, isExpanded)\n                    newList.addAll(items)\n                }\n            }\n\n            if (element is NodeFooterImp) {\n                element.footerNode?.let {\n                    newList.add(it)\n                }\n            }\n        }\n        return newList\n    }\n\n\n    /**\n     * 收起Node\n     * 私有方法，为减少递归复杂度，不对外暴露 isChangeChildExpand 参数，防止错误设置\n     *\n     * @param position Int\n     * @param isChangeChildCollapse Boolean 是否改变子 node 的状态为收起，true 为跟随变为收起，false 表示保持原状态。\n     * @param animate Boolean\n     * @param notify Boolean\n     */\n    private fun collapse(@IntRange(from = 0) position: Int,\n                         isChangeChildCollapse: Boolean = false,\n                         animate: Boolean = true,\n                         notify: Boolean = true,\n                         parentPayload: Any? = null): Int {\n        val node = this.data[position]\n\n        if (node is BaseExpandNode && node.isExpanded) {\n            val adapterPosition = position + headerLayoutCount\n\n            node.isExpanded = false\n            if (node.childNode.isNullOrEmpty()) {\n                notifyItemChanged(adapterPosition, parentPayload)\n                return 0\n            }\n            val items = flatData(node.childNode!!, if (isChangeChildCollapse) false else null)\n            val size = items.size\n            this.data.removeAll(items)\n            if (notify) {\n                if (animate) {\n                    notifyItemChanged(adapterPosition, parentPayload)\n                    notifyItemRangeRemoved(adapterPosition + 1, size)\n                } else {\n                    notifyDataSetChanged()\n                }\n            }\n            return size\n        }\n        return 0\n    }\n\n    /**\n     * 展开Node\n     * 私有方法，为减少递归复杂度，不对外暴露 isChangeChildExpand 参数，防止错误设置\n     *\n     * @param position Int\n     * @param isChangeChildExpand Boolean 是否改变子 node 的状态为展开，true 为跟随变为展开，false 表示保持原状态。\n     * @param animate Boolean\n     * @param notify Boolean\n     */\n    private fun expand(@IntRange(from = 0) position: Int,\n                       isChangeChildExpand: Boolean = false,\n                       animate: Boolean = true,\n                       notify: Boolean = true,\n                       parentPayload: Any? = null): Int {\n        val node = this.data[position]\n\n        if (node is BaseExpandNode && !node.isExpanded) {\n            val adapterPosition = position + headerLayoutCount\n\n            node.isExpanded = true\n            if (node.childNode.isNullOrEmpty()) {\n                notifyItemChanged(adapterPosition, parentPayload)\n                return 0\n            }\n            val items = flatData(node.childNode!!, if (isChangeChildExpand) true else null)\n            val size = items.size\n            this.data.addAll(position + 1, items)\n            if (notify) {\n                if (animate) {\n                    notifyItemChanged(adapterPosition, parentPayload)\n                    notifyItemRangeInserted(adapterPosition + 1, size)\n                } else {\n                    notifyDataSetChanged()\n                }\n            }\n            return size\n        }\n        return 0\n    }\n\n    /**\n     * 收起 node\n     * @param position Int\n     * @param animate Boolean\n     * @param notify Boolean\n     */\n    @JvmOverloads\n    fun collapse(@IntRange(from = 0) position: Int,\n                 animate: Boolean = true,\n                 notify: Boolean = true,\n                 parentPayload: Any? = null): Int {\n        return collapse(position, false, animate, notify, parentPayload)\n    }\n\n    /**\n     * 展开 node\n     * @param position Int\n     * @param animate Boolean\n     * @param notify Boolean\n     */\n    @JvmOverloads\n    fun expand(@IntRange(from = 0) position: Int,\n               animate: Boolean = true,\n               notify: Boolean = true,\n               parentPayload: Any? = null): Int {\n        return expand(position, false, animate, notify, parentPayload)\n    }\n\n    /**\n     * 收起或展开Node\n     * @param position Int\n     * @param animate Boolean\n     * @param notify Boolean\n     */\n    @JvmOverloads\n    fun expandOrCollapse(@IntRange(from = 0) position: Int,\n                         animate: Boolean = true,\n                         notify: Boolean = true,\n                         parentPayload: Any? = null): Int {\n        val node = this.data[position]\n        if (node is BaseExpandNode) {\n            return if (node.isExpanded) {\n                collapse(position, false, animate, notify, parentPayload)\n            } else {\n                expand(position, false, animate, notify, parentPayload)\n            }\n        }\n        return 0\n    }\n\n    @JvmOverloads\n    fun expandAndChild(@IntRange(from = 0) position: Int,\n                       animate: Boolean = true,\n                       notify: Boolean = true,\n                       parentPayload: Any? = null): Int {\n        return expand(position, true, animate, notify, parentPayload)\n    }\n\n    @JvmOverloads\n    fun collapseAndChild(@IntRange(from = 0) position: Int,\n                         animate: Boolean = true,\n                         notify: Boolean = true,\n                         parentPayload: Any? = null): Int {\n        return collapse(position, true, animate, notify, parentPayload)\n    }\n\n    /**\n     * 展开某一个node的时候，折叠其他node\n     * @param position Int\n     * @param isExpandedChild Boolean 展开的时候，是否展开子项目\n     * @param isCollapseChild Boolean 折叠其他node的时候，是否折叠子项目\n     * @param animate Boolean\n     * @param notify Boolean\n     */\n    @JvmOverloads\n    fun expandAndCollapseOther(@IntRange(from = 0) position: Int,\n                               isExpandedChild: Boolean = false,\n                               isCollapseChild: Boolean = true,\n                               animate: Boolean = true,\n                               notify: Boolean = true,\n                               expandPayload: Any? = null,\n                               collapsePayload: Any? = null) {\n\n        val expandCount = expand(position, isExpandedChild, animate, notify, expandPayload)\n        if (expandCount == 0) {\n            return\n        }\n\n        val parentPosition = findParentNode(position)\n        // 当前层级顶部开始位置\n        val firstPosition: Int = if (parentPosition == -1) {\n            0 // 如果没有父节点，则为最外层，从0开始\n        } else {\n            parentPosition + 1 // 如果有父节点，则为子节点，从 父节点+1 的位置开始\n        }\n\n        // 当前 position 之前有 node 收起以后，position的位置就会变化\n        var newPosition = position\n\n        // 在此之前的 node 总数\n        val beforeAllSize = position - firstPosition\n        // 如果此 position 之前有 node\n        if (beforeAllSize > 0) {\n            // 从顶部开始位置循环\n            var i = firstPosition\n            do {\n                val collapseSize = collapse(i, isCollapseChild, animate, notify, collapsePayload)\n                i++\n                // 每次折叠后，重新计算新的 Position\n                newPosition -= collapseSize\n            } while (i < newPosition)\n        }\n\n        // 当前层级最后的位置\n        var lastPosition: Int = if (parentPosition == -1) {\n            data.size - 1 // 如果没有父节点，则为最外层\n        } else {\n            val dataSize = data[parentPosition].childNode?.size ?: 0\n            parentPosition + dataSize + expandCount // 如果有父节点，则为子节点，父节点 + 子节点数量 + 展开的数量\n        }\n\n        //如果此 position 之后有 node\n        if ((newPosition + expandCount) < lastPosition) {\n            var i = newPosition + expandCount + 1\n            while (i <= lastPosition) {\n                val collapseSize = collapse(i, isCollapseChild, animate, notify, collapsePayload)\n                i++\n                lastPosition -= collapseSize\n            }\n\n        }\n\n    }\n\n\n    /**\n     * 查找父节点。如果不存在，则返回-1\n     * @param node BaseNode\n     * @return Int 父节点的position\n     */\n    fun findParentNode(node: BaseNode): Int {\n        val pos = this.data.indexOf(node)\n        if (pos == -1 || pos == 0) {\n            return -1\n        }\n\n        for (i in pos - 1 downTo 0) {\n            val tempNode = this.data[i]\n            if (tempNode.childNode?.contains(node) == true) {\n                return i\n            }\n        }\n        return -1\n    }\n\n    fun findParentNode(@IntRange(from = 0) position: Int): Int {\n        if (position == 0) {\n            return -1\n        }\n        val node = this.data[position]\n        for (i in position - 1 downTo 0) {\n            val tempNode = this.data[i]\n            if (tempNode.childNode?.contains(node) == true) {\n                return i\n            }\n        }\n        return -1\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/BaseProviderMultiAdapter.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah\n\nimport android.util.SparseArray\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.RecyclerView\nimport com.didichuxing.doraemonkit.widget.brvah.provider.BaseItemProvider\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n/**\n * 当有多种条目的时候，避免在convert()中做太多的业务逻辑，把逻辑放在对应的 ItemProvider 中。\n * 适用于以下情况：\n * 1、实体类不方便扩展，此Adapter的数据类型可以是任意类型，只需要在[getItemType]中返回对应类型\n * 2、item 类型较多，在convert()中管理起来复杂\n *\n * ViewHolder 由 [BaseItemProvider] 实现，并且每个[BaseItemProvider]可以拥有自己类型的ViewHolder类型。\n *\n * @param T data 数据类型\n * @constructor\n */\n@Deprecated(\"please use Class BaseBinderAdapter\")\nabstract class BaseProviderMultiAdapter<T>(data: MutableList<T>? = null) :\n        BaseQuickAdapter<T, BaseViewHolder>(0, data) {\n\n    private val mItemProviders by lazy(LazyThreadSafetyMode.NONE) { SparseArray<BaseItemProvider<T>>() }\n\n    /**\n     * 返回 item 类型\n     * @param data List<T>\n     * @param position Int\n     * @return Int\n     */\n    protected abstract fun getItemType(data: List<T>, position: Int): Int\n\n    /**\n     * 必须通过此方法，添加 provider\n     * @param provider BaseItemProvider\n     */\n    open fun addItemProvider(provider: BaseItemProvider<T>) {\n        provider.setAdapter(this)\n        mItemProviders.put(provider.itemViewType, provider)\n    }\n\n    override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {\n        val provider = getItemProvider(viewType)\n        checkNotNull(provider) { \"ViewType: $viewType no such provider found，please use addItemProvider() first!\" }\n        provider.context = parent.context\n        return provider.onCreateViewHolder(parent, viewType).apply {\n            provider.onViewHolderCreated(this, viewType)\n        }\n    }\n\n    override fun getDefItemViewType(position: Int): Int {\n        return getItemType(data, position)\n    }\n\n    override fun convert(holder: BaseViewHolder, item: T) {\n        getItemProvider(holder.itemViewType)!!.convert(holder, item)\n    }\n\n    override fun convert(holder: BaseViewHolder, item: T, payloads: List<Any>) {\n        getItemProvider(holder.itemViewType)!!.convert(holder, item, payloads)\n    }\n\n    override fun bindViewClickListener(viewHolder: BaseViewHolder, viewType: Int) {\n        super.bindViewClickListener(viewHolder, viewType)\n        bindClick(viewHolder)\n        bindChildClick(viewHolder, viewType)\n    }\n\n    /**\n     * 通过 ViewType 获取 BaseItemProvider\n     * 例如：如果ViewType经过特殊处理，可以重写此方法，获取正确的Provider\n     * （比如 ViewType 通过位运算进行的组合的）\n     *\n     * @param viewType Int\n     * @return BaseItemProvider\n     */\n    protected open fun getItemProvider(viewType: Int): BaseItemProvider<T>? {\n        return mItemProviders.get(viewType)\n    }\n\n    protected open fun bindClick(viewHolder: BaseViewHolder) {\n        if (getOnItemClickListener() == null) {\n            //如果没有设置点击监听，则回调给 itemProvider\n            //Callback to itemProvider if no click listener is set\n            viewHolder.itemView.setOnClickListener {\n                var position = viewHolder.adapterPosition\n                if (position == RecyclerView.NO_POSITION) {\n                    return@setOnClickListener\n                }\n                position -= headerLayoutCount\n\n                val itemViewType = viewHolder.itemViewType\n                val provider = mItemProviders.get(itemViewType)\n\n                provider.onClick(viewHolder, it, data[position], position)\n            }\n        }\n        if (getOnItemLongClickListener() == null) {\n            //如果没有设置长按监听，则回调给itemProvider\n            // If you do not set a long press listener, callback to the itemProvider\n            viewHolder.itemView.setOnLongClickListener {\n                var position = viewHolder.adapterPosition\n                if (position == RecyclerView.NO_POSITION) {\n                    return@setOnLongClickListener false\n                }\n                position -= headerLayoutCount\n\n                val itemViewType = viewHolder.itemViewType\n                val provider = mItemProviders.get(itemViewType)\n                provider.onLongClick(viewHolder, it, data[position], position)\n            }\n        }\n    }\n\n    protected open fun bindChildClick(viewHolder: BaseViewHolder, viewType: Int) {\n        if (getOnItemChildClickListener() == null) {\n            val provider = getItemProvider(viewType) ?: return\n            val ids = provider.getChildClickViewIds()\n            ids.forEach { id ->\n                viewHolder.itemView.findViewById<View>(id)?.let {\n                    if (!it.isClickable) {\n                        it.isClickable = true\n                    }\n                    it.setOnClickListener { v ->\n                        var position: Int = viewHolder.adapterPosition\n                        if (position == RecyclerView.NO_POSITION) {\n                            return@setOnClickListener\n                        }\n                        position -= headerLayoutCount\n                        provider.onChildClick(viewHolder, v, data[position], position)\n                    }\n                }\n            }\n        }\n        if (getOnItemChildLongClickListener() == null) {\n            val provider = getItemProvider(viewType) ?: return\n            val ids = provider.getChildLongClickViewIds()\n            ids.forEach { id ->\n                viewHolder.itemView.findViewById<View>(id)?.let {\n                    if (!it.isLongClickable) {\n                        it.isLongClickable = true\n                    }\n                    it.setOnLongClickListener { v ->\n                        var position: Int = viewHolder.adapterPosition\n                        if (position == RecyclerView.NO_POSITION) {\n                            return@setOnLongClickListener false\n                        }\n                        position -= headerLayoutCount\n                        provider.onChildLongClick(viewHolder, v, data[position], position)\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/BaseQuickAdapter.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah\n\nimport android.animation.Animator\nimport android.content.Context\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.view.ViewParent\nimport android.widget.FrameLayout\nimport android.widget.LinearLayout\nimport androidx.annotation.IdRes\nimport androidx.annotation.IntRange\nimport androidx.annotation.LayoutRes\nimport androidx.annotation.NonNull\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.StaggeredGridLayoutManager\nimport com.didichuxing.doraemonkit.widget.brvah.animation.*\nimport com.didichuxing.doraemonkit.widget.brvah.diff.BrvahAsyncDiffer\nimport com.didichuxing.doraemonkit.widget.brvah.diff.BrvahAsyncDifferConfig\nimport com.didichuxing.doraemonkit.widget.brvah.diff.BrvahListUpdateCallback\nimport com.didichuxing.doraemonkit.widget.brvah.listener.*\nimport com.didichuxing.doraemonkit.widget.brvah.module.*\nimport com.didichuxing.doraemonkit.widget.brvah.util.getItemView\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\nimport java.lang.ref.WeakReference\nimport java.lang.reflect.Constructor\nimport java.lang.reflect.InvocationTargetException\nimport java.lang.reflect.Modifier\nimport java.lang.reflect.ParameterizedType\nimport java.util.*\nimport kotlin.collections.ArrayList\n\n/**\n * 获取模块\n */\nprivate interface BaseQuickAdapterModuleImp {\n    /**\n     * 重写此方法，返回自定义模块\n     * @param baseQuickAdapter BaseQuickAdapter<*, *>\n     * @return BaseLoadMoreModule\n     */\n    fun addLoadMoreModule(baseQuickAdapter: BaseQuickAdapter<*, *>): BaseLoadMoreModule {\n        return BaseLoadMoreModule(baseQuickAdapter)\n    }\n\n    /**\n     * 重写此方法，返回自定义模块\n     * @param baseQuickAdapter BaseQuickAdapter<*, *>\n     * @return BaseUpFetchModule\n     */\n    fun addUpFetchModule(baseQuickAdapter: BaseQuickAdapter<*, *>): BaseUpFetchModule {\n        return BaseUpFetchModule(baseQuickAdapter)\n    }\n\n    /**\n     * 重写此方法，返回自定义模块\n     * @param baseQuickAdapter BaseQuickAdapter<*, *>\n     * @return BaseExpandableModule\n     */\n    fun addDraggableModule(baseQuickAdapter: BaseQuickAdapter<*, *>): BaseDraggableModule {\n        return BaseDraggableModule(baseQuickAdapter)\n    }\n}\n\n/**\n * Base Class\n * @param T : type of data, 数据类型\n * @param VH : BaseViewHolder\n * @constructor layoutId, data(Can null parameters, the default is empty data)\n */\nabstract class BaseQuickAdapter<T, VH : BaseViewHolder>\n@JvmOverloads constructor(@LayoutRes private val layoutResId: Int,\n                          data: MutableList<T>? = null)\n    : RecyclerView.Adapter<VH>(), BaseQuickAdapterModuleImp, BaseListenerImp {\n\n    companion object {\n        const val HEADER_VIEW = 0x10000111\n        const val LOAD_MORE_VIEW = 0x10000222\n        const val FOOTER_VIEW = 0x10000333\n        const val EMPTY_VIEW = 0x10000555\n    }\n\n    /***************************** Public property settings *************************************/\n    /**\n     * data, Only allowed to get.\n     * 数据, 只允许 get。\n     */\n    var data: MutableList<T> = data ?: arrayListOf()\n        internal set\n\n    /**\n     * 当显示空布局时，是否显示 Header\n     */\n    var headerWithEmptyEnable = false\n\n    /** 当显示空布局时，是否显示 Foot */\n    var footerWithEmptyEnable = false\n\n    /** 是否使用空布局 */\n    var isUseEmpty = true\n\n    /**\n     * if asFlow is true, footer/header will arrange like normal item view.\n     * only works when use [GridLayoutManager],and it will ignore span size.\n     */\n    var headerViewAsFlow: Boolean = false\n    var footerViewAsFlow: Boolean = false\n\n    /**\n     * 是否打开动画\n     */\n    var animationEnable: Boolean = false\n\n    /**\n     * 动画是否仅第一次执行\n     */\n    var isAnimationFirstOnly = true\n\n    /**\n     * 设置自定义动画\n     */\n    var adapterAnimation: BaseAnimation? = null\n        set(value) {\n            animationEnable = true\n            field = value\n        }\n\n    /**\n     * 加载更多模块\n     */\n    val loadMoreModule: BaseLoadMoreModule\n        get() {\n            checkNotNull(mLoadMoreModule) { \"Please first implements LoadMoreModule\" }\n            return mLoadMoreModule!!\n        }\n\n    /**\n     * 向上加载模块\n     */\n    val upFetchModule: BaseUpFetchModule\n        get() {\n            checkNotNull(mUpFetchModule) { \"Please first implements UpFetchModule\" }\n            return mUpFetchModule!!\n        }\n\n    /**\n     * 拖拽模块\n     */\n    val draggableModule: BaseDraggableModule\n        get() {\n            checkNotNull(mDraggableModule) { \"Please first implements DraggableModule\" }\n            return mDraggableModule!!\n        }\n\n    /********************************* Private property *****************************************/\n    private var mDiffHelper: BrvahAsyncDiffer<T>? = null\n\n    private lateinit var mHeaderLayout: LinearLayout\n    private lateinit var mFooterLayout: LinearLayout\n    private lateinit var mEmptyLayout: FrameLayout\n\n    private var mLastPosition = -1\n\n    private var mSpanSizeLookup: GridSpanSizeLookup? = null\n    private var mOnItemClickListener: OnItemClickListener? = null\n    private var mOnItemLongClickListener: OnItemLongClickListener? = null\n    private var mOnItemChildClickListener: OnItemChildClickListener? = null\n    private var mOnItemChildLongClickListener: OnItemChildLongClickListener? = null\n    private var mLoadMoreModule: BaseLoadMoreModule? = null\n    private var mUpFetchModule: BaseUpFetchModule? = null\n    private var mDraggableModule: BaseDraggableModule? = null\n\n    public var context: Context? = null\n\n    lateinit var weakRecyclerView: WeakReference<RecyclerView>\n\n    /******************************* RecyclerView Method ****************************************/\n\n    init {\n        checkModule()\n    }\n\n    /**\n     * 检查模块\n     */\n    private fun checkModule() {\n        if (this is LoadMoreModule) {\n            mLoadMoreModule = this.addLoadMoreModule(this)\n        }\n        if (this is UpFetchModule) {\n            mUpFetchModule = this.addUpFetchModule(this)\n        }\n        if (this is DraggableModule) {\n            mDraggableModule = this.addDraggableModule(this)\n        }\n    }\n\n    /**\n     * Implement this method and use the helper to adapt the view to the given item.\n     *\n     * 实现此方法，并使用 helper 完成 item 视图的操作\n     *\n     * @param helper A fully initialized helper.\n     * @param item   The item that needs to be displayed.\n     */\n    protected abstract fun convert(holder: VH, item: T)\n\n    /**\n     * Optional implementation this method and use the helper to adapt the view to the given item.\n     * If use [payloads], will perform this method, Please implement this method for partial refresh.\n     * If use [RecyclerView.Adapter.notifyItemChanged(Int, Object)] with payload,\n     * Will execute this method.\n     *\n     * 可选实现，如果你是用了[payloads]刷新item，请实现此方法，进行局部刷新\n     *\n     * @param helper   A fully initialized helper.\n     * @param item     The item that needs to be displayed.\n     * @param payloads payload info.\n     */\n    protected open fun convert(holder: VH, item: T, payloads: List<Any>) {}\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {\n        val baseViewHolder: VH\n        when (viewType) {\n            LOAD_MORE_VIEW -> {\n                val view = mLoadMoreModule!!.loadMoreView.getRootView(parent)\n                baseViewHolder = createBaseViewHolder(view)\n                mLoadMoreModule!!.setupViewHolder(baseViewHolder)\n            }\n            HEADER_VIEW -> {\n                val headerLayoutVp: ViewParent? = mHeaderLayout.parent\n                if (headerLayoutVp is ViewGroup) {\n                    headerLayoutVp.removeView(mHeaderLayout)\n                }\n\n                baseViewHolder = createBaseViewHolder(mHeaderLayout)\n            }\n            EMPTY_VIEW -> {\n                val emptyLayoutVp: ViewParent? = mEmptyLayout.parent\n                if (emptyLayoutVp is ViewGroup) {\n                    emptyLayoutVp.removeView(mEmptyLayout)\n                }\n\n                baseViewHolder = createBaseViewHolder(mEmptyLayout)\n            }\n            FOOTER_VIEW -> {\n                val footerLayoutVp: ViewParent? = mFooterLayout.parent\n                if (footerLayoutVp is ViewGroup) {\n                    footerLayoutVp.removeView(mFooterLayout)\n                }\n\n                baseViewHolder = createBaseViewHolder(mFooterLayout)\n            }\n            else -> {\n                val viewHolder = onCreateDefViewHolder(parent, viewType)\n                bindViewClickListener(viewHolder, viewType)\n                mDraggableModule?.initView(viewHolder)\n                onItemViewHolderCreated(viewHolder, viewType)\n                baseViewHolder = viewHolder\n            }\n        }\n\n        return baseViewHolder\n    }\n\n    /**\n     * Don't override this method. If need, please override [getItemCount]\n     * 不要重写此方法，如果有需要，请重写[getDefItemViewType]\n     * @return Int\n     */\n    override fun getItemCount(): Int {\n        if (hasEmptyView()) {\n            var count = 1\n            if (headerWithEmptyEnable && hasHeaderLayout()) {\n                count++\n            }\n            if (footerWithEmptyEnable && hasFooterLayout()) {\n                count++\n            }\n            return count\n        } else {\n            val loadMoreCount = if (mLoadMoreModule?.hasLoadMoreView() == true) {\n                1\n            } else {\n                0\n            }\n            return headerLayoutCount + getDefItemCount() + footerLayoutCount + loadMoreCount\n        }\n    }\n\n    /**\n     * Don't override this method. If need, please override [getDefItemViewType]\n     * 不要重写此方法，如果有需要，请重写[getDefItemViewType]\n     *\n     * @param position Int\n     * @return Int\n     */\n    override fun getItemViewType(position: Int): Int {\n        if (hasEmptyView()) {\n            val header = headerWithEmptyEnable && hasHeaderLayout()\n            return when (position) {\n                0 -> if (header) {\n                    HEADER_VIEW\n                } else {\n                    EMPTY_VIEW\n                }\n                1 -> if (header) {\n                    EMPTY_VIEW\n                } else {\n                    FOOTER_VIEW\n                }\n                2 -> FOOTER_VIEW\n                else -> EMPTY_VIEW\n            }\n        }\n\n        val hasHeader = hasHeaderLayout()\n        if (hasHeader && position == 0) {\n            return HEADER_VIEW\n        } else {\n            var adjPosition = if (hasHeader) {\n                position - 1\n            } else {\n                position\n            }\n            val dataSize = data.size\n            return if (adjPosition < dataSize) {\n                getDefItemViewType(adjPosition)\n            } else {\n                adjPosition -= dataSize\n                val numFooters = if (hasFooterLayout()) {\n                    1\n                } else {\n                    0\n                }\n                if (adjPosition < numFooters) {\n                    FOOTER_VIEW\n                } else {\n                    LOAD_MORE_VIEW\n                }\n            }\n        }\n    }\n\n    override fun onBindViewHolder(holder: VH, position: Int) {\n        //Add up fetch logic, almost like load more, but simpler.\n        mUpFetchModule?.autoUpFetch(position)\n        //Do not move position, need to change before LoadMoreView binding\n        mLoadMoreModule?.autoLoadMore(position)\n        when (holder.itemViewType) {\n            LOAD_MORE_VIEW -> {\n                mLoadMoreModule?.let {\n                    it.loadMoreView.convert(holder, position, it.loadMoreStatus)\n                }\n            }\n            HEADER_VIEW, EMPTY_VIEW, FOOTER_VIEW -> return\n            else -> convert(holder, getItem(position - headerLayoutCount))\n        }\n    }\n\n    override fun onBindViewHolder(holder: VH, position: Int, payloads: MutableList<Any>) {\n        if (payloads.isEmpty()) {\n            onBindViewHolder(holder, position)\n            return\n        }\n        //Add up fetch logic, almost like load more, but simpler.\n        mUpFetchModule?.autoUpFetch(position)\n        //Do not move position, need to change before LoadMoreView binding\n        mLoadMoreModule?.autoLoadMore(position)\n        when (holder.itemViewType) {\n            LOAD_MORE_VIEW -> {\n                mLoadMoreModule?.let {\n                    it.loadMoreView.convert(holder, position, it.loadMoreStatus)\n                }\n            }\n            HEADER_VIEW, EMPTY_VIEW, FOOTER_VIEW -> return\n            else -> convert(holder, getItem(position - headerLayoutCount), payloads)\n        }\n    }\n\n    override fun getItemId(position: Int): Long {\n        return position.toLong()\n    }\n\n    /**\n     * Called when a view created by this holder has been attached to a window.\n     * simple to solve item will layout using all\n     * [setFullSpan]\n     *\n     * @param holder\n     */\n    override fun onViewAttachedToWindow(holder: VH) {\n        super.onViewAttachedToWindow(holder)\n        val type = holder.itemViewType\n        if (isFixedViewType(type)) {\n            setFullSpan(holder)\n        } else {\n            addAnimation(holder)\n        }\n    }\n\n    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {\n        super.onAttachedToRecyclerView(recyclerView)\n        weakRecyclerView = WeakReference(recyclerView)\n        this.context = recyclerView.context\n        mDraggableModule?.attachToRecyclerView(recyclerView)\n\n        val manager = recyclerView.layoutManager\n        if (manager is GridLayoutManager) {\n            val defSpanSizeLookup = manager.spanSizeLookup\n            manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {\n                override fun getSpanSize(position: Int): Int {\n                    val type = getItemViewType(position)\n                    if (type == HEADER_VIEW && headerViewAsFlow) {\n                        return 1\n                    }\n                    if (type == FOOTER_VIEW && footerViewAsFlow) {\n                        return 1\n                    }\n                    return if (mSpanSizeLookup == null) {\n                        if (isFixedViewType(type)) manager.spanCount else defSpanSizeLookup.getSpanSize(position)\n                    } else {\n                        if (isFixedViewType(type)) {\n                            manager.spanCount\n                        } else {\n                            mSpanSizeLookup!!.getSpanSize(manager, type, position - headerLayoutCount)\n                        }\n                    }\n                }\n\n            }\n        }\n    }\n\n    protected open fun isFixedViewType(type: Int): Boolean {\n        return type == EMPTY_VIEW || type == HEADER_VIEW || type == FOOTER_VIEW || type == LOAD_MORE_VIEW\n    }\n\n    /**\n     * Get the data item associated with the specified position in the data set.\n     *\n     * @param position Position of the item whose data we want within the adapter's\n     * data set.\n     * @return The data at the specified position.\n     */\n    open fun getItem(@IntRange(from = 0) position: Int): T {\n        return data[position]\n    }\n\n    open fun getItemOrNull(@IntRange(from = 0) position: Int): T? {\n        return data.getOrNull(position)\n    }\n\n    /**\n     * 如果返回 -1，表示不存在\n     * @param item T?\n     * @return Int\n     */\n    open fun getItemPosition(item: T?): Int {\n        return if (item != null && data.isNotEmpty()) data.indexOf(item) else -1\n    }\n\n    /**\n     * 用于保存需要设置点击事件的 item\n     */\n    private val childClickViewIds = LinkedHashSet<Int>()\n\n    fun getChildClickViewIds(): LinkedHashSet<Int> {\n        return childClickViewIds\n    }\n\n    /**\n     * 设置需要点击事件的子view\n     * @param viewIds IntArray\n     */\n    fun addChildClickViewIds(@IdRes vararg viewIds: Int) {\n        for (viewId in viewIds) {\n            childClickViewIds.add(viewId)\n        }\n    }\n\n    /**\n     * 用于保存需要设置长按点击事件的 item\n     */\n    private val childLongClickViewIds = LinkedHashSet<Int>()\n\n    fun getChildLongClickViewIds(): LinkedHashSet<Int> {\n        return childLongClickViewIds\n    }\n\n    /**\n     * 设置需要长按点击事件的子view\n     * @param viewIds IntArray\n     */\n    fun addChildLongClickViewIds(@IdRes vararg viewIds: Int) {\n        for (viewId in viewIds) {\n            childLongClickViewIds.add(viewId)\n        }\n    }\n\n    /**\n     * 绑定 item 点击事件\n     * @param viewHolder VH\n     */\n    protected open fun bindViewClickListener(viewHolder: VH, viewType: Int) {\n        mOnItemClickListener?.let {\n            viewHolder.itemView.setOnClickListener { v ->\n                var position = viewHolder.adapterPosition\n                if (position == RecyclerView.NO_POSITION) {\n                    return@setOnClickListener\n                }\n                position -= headerLayoutCount\n                setOnItemClick(v, position)\n            }\n        }\n        mOnItemLongClickListener?.let {\n            viewHolder.itemView.setOnLongClickListener { v ->\n                var position = viewHolder.adapterPosition\n                if (position == RecyclerView.NO_POSITION) {\n                    return@setOnLongClickListener false\n                }\n                position -= headerLayoutCount\n                setOnItemLongClick(v, position)\n            }\n        }\n\n        mOnItemChildClickListener?.let {\n            for (id in getChildClickViewIds()) {\n                viewHolder.itemView.findViewById<View>(id)?.let { childView ->\n                    if (!childView.isClickable) {\n                        childView.isClickable = true\n                    }\n                    childView.setOnClickListener { v ->\n                        var position = viewHolder.adapterPosition\n                        if (position == RecyclerView.NO_POSITION) {\n                            return@setOnClickListener\n                        }\n                        position -= headerLayoutCount\n                        setOnItemChildClick(v, position)\n                    }\n                }\n            }\n        }\n        mOnItemChildLongClickListener?.let {\n            for (id in getChildLongClickViewIds()) {\n                viewHolder.itemView.findViewById<View>(id)?.let { childView ->\n                    if (!childView.isLongClickable) {\n                        childView.isLongClickable = true\n                    }\n                    childView.setOnLongClickListener { v ->\n                        var position = viewHolder.adapterPosition\n                        if (position == RecyclerView.NO_POSITION) {\n                            return@setOnLongClickListener false\n                        }\n                        position -= headerLayoutCount\n                        setOnItemChildLongClick(v, position)\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * override this method if you want to override click event logic\n     *\n     * 如果你想重新实现 item 点击事件逻辑，请重写此方法\n     * @param v\n     * @param position\n     */\n    protected open fun setOnItemClick(v: View, position: Int) {\n        mOnItemClickListener?.onItemClick(this, v, position)\n    }\n\n    /**\n     * override this method if you want to override longClick event logic\n     *\n     * 如果你想重新实现 item 长按事件逻辑，请重写此方法\n     * @param v\n     * @param position\n     * @return\n     */\n    protected open fun setOnItemLongClick(v: View, position: Int): Boolean {\n        return mOnItemLongClickListener?.onItemLongClick(this, v, position) ?: false\n    }\n\n    protected open fun setOnItemChildClick(v: View, position: Int) {\n        mOnItemChildClickListener?.onItemChildClick(this, v, position)\n    }\n\n    protected open fun setOnItemChildLongClick(v: View, position: Int): Boolean {\n        return mOnItemChildLongClickListener?.onItemChildLongClick(this, v, position) ?: false\n    }\n\n    /**\n     * （可选重写）当 item 的 ViewHolder创建完毕后，执行此方法。\n     * 可在此对 ViewHolder 进行处理，例如进行 DataBinding 绑定 view\n     *\n     * @param viewHolder VH\n     * @param viewType Int\n     */\n    protected open fun onItemViewHolderCreated(viewHolder: VH, viewType: Int) {}\n\n    /**\n     * Override this method and return your data size.\n     * 重写此方法，返回你的数据数量。\n     */\n    protected open fun getDefItemCount(): Int {\n        return data.size\n    }\n\n    /**\n     * Override this method and return your ViewType.\n     * 重写此方法，返回你的ViewType。\n     */\n    protected open fun getDefItemViewType(position: Int): Int {\n        return super.getItemViewType(position)\n    }\n\n    /**\n     * Override this method and return your ViewHolder.\n     * 重写此方法，返回你的ViewHolder。\n     */\n    protected open fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VH {\n        return createBaseViewHolder(parent, layoutResId)\n    }\n\n    protected open fun createBaseViewHolder(parent: ViewGroup, @LayoutRes layoutResId: Int): VH {\n        return createBaseViewHolder(parent.getItemView(layoutResId))\n    }\n\n    /**\n     * 创建 ViewHolder。可以重写\n     *\n     * @param view View\n     * @return VH\n     */\n    @Suppress(\"UNCHECKED_CAST\")\n    protected open fun createBaseViewHolder(view: View): VH {\n        var temp: Class<*>? = javaClass\n        var z: Class<*>? = null\n        while (z == null && null != temp) {\n            z = getInstancedGenericKClass(temp)\n            temp = temp.superclass\n        }\n        // 泛型擦除会导致z为null\n        val vh: VH? = if (z == null) {\n            BaseViewHolder(view) as VH\n        } else {\n            createBaseGenericKInstance(z, view)\n        }\n        return vh ?: BaseViewHolder(view) as VH\n    }\n\n    /**\n     * get generic parameter VH\n     *\n     * @param z\n     * @return\n     */\n    private fun getInstancedGenericKClass(z: Class<*>): Class<*>? {\n        try {\n            val type = z.genericSuperclass\n            if (type is ParameterizedType) {\n                val types = type.actualTypeArguments\n                for (temp in types) {\n                    if (temp is Class<*>) {\n                        if (BaseViewHolder::class.java.isAssignableFrom(temp)) {\n                            return temp\n                        }\n                    } else if (temp is ParameterizedType) {\n                        val rawType = temp.rawType\n                        if (rawType is Class<*> && BaseViewHolder::class.java.isAssignableFrom(rawType)) {\n                            return rawType\n                        }\n                    }\n                }\n            }\n        } catch (e: java.lang.reflect.GenericSignatureFormatError) {\n            e.printStackTrace()\n        } catch (e: TypeNotPresentException) {\n            e.printStackTrace()\n        } catch (e: java.lang.reflect.MalformedParameterizedTypeException) {\n            e.printStackTrace()\n        }\n        return null\n    }\n\n    /**\n     * try to create Generic VH instance\n     *\n     * @param z\n     * @param view\n     * @return\n     */\n    @Suppress(\"UNCHECKED_CAST\")\n    private fun createBaseGenericKInstance(z: Class<*>, view: View): VH? {\n        try {\n            val constructor: Constructor<*>\n            // inner and unstatic class\n            return if (z.isMemberClass && !Modifier.isStatic(z.modifiers)) {\n                constructor = z.getDeclaredConstructor(javaClass, View::class.java)\n                constructor.isAccessible = true\n                constructor.newInstance(this, view) as VH\n            } else {\n                constructor = z.getDeclaredConstructor(View::class.java)\n                constructor.isAccessible = true\n                constructor.newInstance(view) as VH\n            }\n        } catch (e: NoSuchMethodException) {\n            e.printStackTrace()\n        } catch (e: IllegalAccessException) {\n            e.printStackTrace()\n        } catch (e: InstantiationException) {\n            e.printStackTrace()\n        } catch (e: InvocationTargetException) {\n            e.printStackTrace()\n        }\n\n        return null\n    }\n\n    /**\n     * When set to true, the item will layout using all span area. That means, if orientation\n     * is vertical, the view will have full width; if orientation is horizontal, the view will\n     * have full height.\n     * if the hold view use StaggeredGridLayoutManager they should using all span area\n     *\n     * @param holder True if this item should traverse all spans.\n     */\n    protected open fun setFullSpan(holder: RecyclerView.ViewHolder) {\n        val layoutParams = holder.itemView.layoutParams\n        if (layoutParams is StaggeredGridLayoutManager.LayoutParams) {\n            layoutParams.isFullSpan = true\n        }\n    }\n\n    /**\n     * get the specific view by position,e.g. getViewByPosition(2, R.id.textView)\n     *\n     * bind [RecyclerView.setAdapter] before use!\n     */\n    fun getViewByPosition(position: Int, @IdRes viewId: Int): View? {\n        val recyclerView = weakRecyclerView.get() ?: return null\n        val viewHolder = recyclerView.findViewHolderForLayoutPosition(position) as BaseViewHolder?\n                ?: return null\n        return viewHolder.getViewOrNull(viewId)\n    }\n\n    /********************************************************************************************/\n    /********************************* HeaderView Method ****************************************/\n    /********************************************************************************************/\n    @JvmOverloads\n    fun addHeaderView(view: View, index: Int = -1, orientation: Int = LinearLayout.VERTICAL): Int {\n        if (!this::mHeaderLayout.isInitialized) {\n            mHeaderLayout = LinearLayout(view.context)\n            mHeaderLayout.orientation = orientation\n            mHeaderLayout.layoutParams = if (orientation == LinearLayout.VERTICAL) {\n                RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n            } else {\n                RecyclerView.LayoutParams(WRAP_CONTENT, MATCH_PARENT)\n            }\n        }\n\n        val childCount = mHeaderLayout.childCount\n        var mIndex = index\n        if (index < 0 || index > childCount) {\n            mIndex = childCount\n        }\n        mHeaderLayout.addView(view, mIndex)\n        if (mHeaderLayout.childCount == 1) {\n            val position = headerViewPosition\n            if (position != -1) {\n                notifyItemInserted(position)\n            }\n        }\n        return mIndex\n    }\n\n    @JvmOverloads\n    fun setHeaderView(view: View, index: Int = 0, orientation: Int = LinearLayout.VERTICAL): Int {\n        return if (!this::mHeaderLayout.isInitialized || mHeaderLayout.childCount <= index) {\n            addHeaderView(view, index, orientation)\n        } else {\n            mHeaderLayout.removeViewAt(index)\n            mHeaderLayout.addView(view, index)\n            index\n        }\n    }\n\n    /**\n     * 是否有 HeaderLayout\n     * @return Boolean\n     */\n    fun hasHeaderLayout(): Boolean {\n        if (this::mHeaderLayout.isInitialized && mHeaderLayout.childCount > 0) {\n            return true\n        }\n        return false\n    }\n\n    fun removeHeaderView(header: View) {\n        if (!hasHeaderLayout()) return\n\n        mHeaderLayout.removeView(header)\n        if (mHeaderLayout.childCount == 0) {\n            val position = headerViewPosition\n            if (position != -1) {\n                notifyItemRemoved(position)\n            }\n        }\n    }\n\n    fun removeAllHeaderView() {\n        if (!hasHeaderLayout()) return\n\n        mHeaderLayout.removeAllViews()\n        val position = headerViewPosition\n        if (position != -1) {\n            notifyItemRemoved(position)\n        }\n    }\n\n    val headerViewPosition: Int\n        get() {\n            if (hasEmptyView()) {\n                if (headerWithEmptyEnable) {\n                    return 0\n                }\n            } else {\n                return 0\n            }\n            return -1\n        }\n\n    /**\n     * if addHeaderView will be return 1, if not will be return 0\n     */\n    val headerLayoutCount: Int\n        get() {\n            return if (hasHeaderLayout()) {\n                1\n            } else {\n                0\n            }\n        }\n\n\n    /**\n     * 获取头布局\n     */\n    val headerLayout: LinearLayout?\n        get() {\n            return if (this::mHeaderLayout.isInitialized) {\n                mHeaderLayout\n            } else {\n                null\n            }\n        }\n\n    /********************************************************************************************/\n    /********************************* FooterView Method ****************************************/\n    /********************************************************************************************/\n    @JvmOverloads\n    fun addFooterView(view: View, index: Int = -1, orientation: Int = LinearLayout.VERTICAL): Int {\n        if (!this::mFooterLayout.isInitialized) {\n            mFooterLayout = LinearLayout(view.context)\n            mFooterLayout.orientation = orientation\n            mFooterLayout.layoutParams = if (orientation == LinearLayout.VERTICAL) {\n                RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n            } else {\n                RecyclerView.LayoutParams(WRAP_CONTENT, MATCH_PARENT)\n            }\n        }\n\n        val childCount = mFooterLayout.childCount\n        var mIndex = index\n        if (index < 0 || index > childCount) {\n            mIndex = childCount\n        }\n        mFooterLayout.addView(view, mIndex)\n        if (mFooterLayout.childCount == 1) {\n            val position = footerViewPosition\n            if (position != -1) {\n                notifyItemInserted(position)\n            }\n        }\n        return mIndex\n    }\n\n    @JvmOverloads\n    fun setFooterView(view: View, index: Int = 0, orientation: Int = LinearLayout.VERTICAL): Int {\n        return if (!this::mFooterLayout.isInitialized || mFooterLayout.childCount <= index) {\n            addFooterView(view, index, orientation)\n        } else {\n            mFooterLayout.removeViewAt(index)\n            mFooterLayout.addView(view, index)\n            index\n        }\n    }\n\n    fun removeFooterView(footer: View) {\n        if (!hasFooterLayout()) return\n\n        mFooterLayout.removeView(footer)\n        if (mFooterLayout.childCount == 0) {\n            val position = footerViewPosition\n            if (position != -1) {\n                notifyItemRemoved(position)\n            }\n        }\n    }\n\n    fun removeAllFooterView() {\n        if (!hasFooterLayout()) return\n\n        mFooterLayout.removeAllViews()\n        val position = footerViewPosition\n        if (position != -1) {\n            notifyItemRemoved(position)\n        }\n    }\n\n    fun hasFooterLayout(): Boolean {\n        if (this::mFooterLayout.isInitialized && mFooterLayout.childCount > 0) {\n            return true\n        }\n        return false\n    }\n\n    val footerViewPosition: Int\n        get() {\n            if (hasEmptyView()) {\n                var position = 1\n                if (headerWithEmptyEnable && hasHeaderLayout()) {\n                    position++\n                }\n                if (footerWithEmptyEnable) {\n                    return position\n                }\n            } else {\n                return headerLayoutCount + data.size\n            }\n            return -1\n        }\n\n    /**\n     * if addHeaderView will be return 1, if not will be return 0\n     */\n    val footerLayoutCount: Int\n        get() {\n            return if (hasFooterLayout()) {\n                1\n            } else {\n                0\n            }\n        }\n\n    /**\n     * 获取脚布局\n     * @return LinearLayout?\n     */\n    val footerLayout: LinearLayout?\n        get() {\n            return if (this::mFooterLayout.isInitialized) {\n                mFooterLayout\n            } else {\n                null\n            }\n        }\n\n    /********************************************************************************************/\n    /********************************** EmptyView Method ****************************************/\n    /********************************************************************************************/\n    /**\n     * 设置空布局视图，注意：[data]必须为空数组\n     * @param emptyView View\n     */\n    fun setEmptyView(emptyView: View) {\n        val oldItemCount = itemCount\n        var insert = false\n        if (!this::mEmptyLayout.isInitialized) {\n            mEmptyLayout = FrameLayout(emptyView.context)\n\n            mEmptyLayout.layoutParams = emptyView.layoutParams?.let {\n                return@let ViewGroup.LayoutParams(it.width, it.height)\n            } ?: ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)\n\n            insert = true\n        } else {\n            emptyView.layoutParams?.let {\n                val lp = mEmptyLayout.layoutParams\n                lp.width = it.width\n                lp.height = it.height\n                mEmptyLayout.layoutParams = lp\n            }\n        }\n\n        mEmptyLayout.removeAllViews()\n        mEmptyLayout.addView(emptyView)\n        isUseEmpty = true\n        if (insert && hasEmptyView()) {\n            var position = 0\n            if (headerWithEmptyEnable && hasHeaderLayout()) {\n                position++\n            }\n            if (itemCount > oldItemCount) {\n                notifyItemInserted(position)\n            } else {\n                notifyDataSetChanged()\n            }\n        }\n    }\n\n    fun setEmptyView(layoutResId: Int) {\n        weakRecyclerView.get()?.let {\n            val view = LayoutInflater.from(it.context).inflate(layoutResId, it, false)\n            setEmptyView(view)\n        }\n    }\n\n    fun removeEmptyView() {\n        if (this::mEmptyLayout.isInitialized) {\n            mEmptyLayout.removeAllViews()\n        }\n    }\n\n    fun hasEmptyView(): Boolean {\n        if (!this::mEmptyLayout.isInitialized || mEmptyLayout.childCount == 0) {\n            return false\n        }\n        if (!isUseEmpty) {\n            return false\n        }\n        return data.isEmpty()\n    }\n\n    /**\n     * When the current adapter is empty, the BaseQuickAdapter can display a special view\n     * called the empty view. The empty view is used to provide feedback to the user\n     * that no data is available in this AdapterView.\n     *\n     * @return The view to show if the adapter is empty.\n     */\n    val emptyLayout: FrameLayout?\n        get() {\n            return if (this::mEmptyLayout.isInitialized) {\n                mEmptyLayout\n            } else {\n                null\n            }\n        }\n\n\n    /*************************** Animation ******************************************/\n\n    /**\n     * add animation when you want to show time\n     *\n     * @param holder\n     */\n    private fun addAnimation(holder: RecyclerView.ViewHolder) {\n        if (animationEnable) {\n            if (!isAnimationFirstOnly || holder.layoutPosition > mLastPosition) {\n                val animation: BaseAnimation = adapterAnimation?.let {\n                    it\n                } ?: AlphaInAnimation()\n                animation.animators(holder.itemView).forEach {\n                    startAnim(it, holder.layoutPosition)\n                }\n                mLastPosition = holder.layoutPosition\n            }\n        }\n    }\n\n    /**\n     * 开始执行动画方法\n     * 可以重写此方法，实行更多行为\n     *\n     * @param anim\n     * @param index\n     */\n    protected open fun startAnim(anim: Animator, index: Int) {\n        anim.start()\n    }\n\n    /**\n     * 内置默认动画类型\n     */\n    enum class AnimationType {\n        AlphaIn, ScaleIn, SlideInBottom, SlideInLeft, SlideInRight\n    }\n\n    /**\n     * 使用内置默认动画设置\n     * @param animationType AnimationType\n     */\n    fun setAnimationWithDefault(animationType: AnimationType) {\n        adapterAnimation = when (animationType) {\n            AnimationType.AlphaIn -> AlphaInAnimation()\n            AnimationType.ScaleIn -> ScaleInAnimation()\n            AnimationType.SlideInBottom -> SlideInBottomAnimation()\n            AnimationType.SlideInLeft -> SlideInLeftAnimation()\n            AnimationType.SlideInRight -> SlideInRightAnimation()\n        }\n    }\n\n    /*************************** 设置数据相关 ******************************************/\n\n    /**\n     * setting up a new instance to data;\n     * 设置新的数据实例\n     *\n     * @param data\n     */\n    @Deprecated(\"Please use setNewInstance(), This method will be removed in the next version\", replaceWith = ReplaceWith(\"setNewInstance(data)\"))\n    open fun setNewData(data: MutableList<T>?) {\n        setNewInstance(data)\n    }\n\n    /**\n     * setting up a new instance to data;\n     * 设置新的数据实例，替换原有内存引用。\n     * 通常情况下，如非必要，请使用[setList]修改内容\n     *\n     * @param data\n     */\n    open fun setNewInstance(list: MutableList<T>?) {\n        if (list === this.data) {\n            return\n        }\n\n        this.data = list ?: arrayListOf()\n        mLoadMoreModule?.reset()\n        mLastPosition = -1\n        notifyDataSetChanged()\n        mLoadMoreModule?.checkDisableLoadMoreIfNotFullPage()\n    }\n\n    /**\n     * use data to replace all item in mData. this method is different [setList],\n     * it doesn't change the [BaseQuickAdapter.data] reference\n     * Deprecated, Please use [setList]\n     *\n     * @param newData data collection\n     */\n    @Deprecated(\"Please use setData()\", replaceWith = ReplaceWith(\"setData(newData)\"))\n    open fun replaceData(newData: Collection<T>) {\n        setList(newData)\n    }\n\n    /**\n     * 使用新的数据集合，改变原有数据集合内容。\n     * 注意：不会替换原有的内存引用，只是替换内容\n     *\n     * @param list Collection<T>?\n     */\n    open fun setList(list: Collection<T>?) {\n        if (list !== this.data) {\n            this.data.clear()\n            if (!list.isNullOrEmpty()) {\n                this.data.addAll(list)\n            }\n        } else {\n            if (!list.isNullOrEmpty()) {\n                val newList = ArrayList(list)\n                this.data.clear()\n                this.data.addAll(newList)\n            } else {\n                this.data.clear()\n            }\n        }\n        mLoadMoreModule?.reset()\n        mLastPosition = -1\n        notifyDataSetChanged()\n        mLoadMoreModule?.checkDisableLoadMoreIfNotFullPage()\n    }\n\n    /**\n     * change data\n     * 改变某一位置数据\n     */\n    open fun setData(@IntRange(from = 0) index: Int, data: T) {\n        if (index >= this.data.size) {\n            return\n        }\n        this.data[index] = data\n        notifyItemChanged(index + headerLayoutCount)\n    }\n\n    /**\n     * add one new data in to certain location\n     * 在指定位置添加一条新数据\n     *\n     * @param position\n     */\n    open fun addData(@IntRange(from = 0) position: Int, data: T) {\n        this.data.add(position, data)\n        notifyItemInserted(position + headerLayoutCount)\n        compatibilityDataSizeChanged(1)\n    }\n\n    /**\n     * add one new data\n     * 添加一条新数据\n     */\n    open fun addData(@NonNull data: T) {\n        this.data.add(data)\n        notifyItemInserted(this.data.size + headerLayoutCount)\n        compatibilityDataSizeChanged(1)\n    }\n\n    /**\n     * add new data in to certain location\n     * 在指定位置添加数据\n     *\n     * @param position the insert position\n     * @param newData  the new data collection\n     */\n    open fun addData(@IntRange(from = 0) position: Int, newData: Collection<T>) {\n        this.data.addAll(position, newData)\n        notifyItemRangeInserted(position + headerLayoutCount, newData.size)\n        compatibilityDataSizeChanged(newData.size)\n    }\n\n    open fun addData(@NonNull newData: Collection<T>) {\n        this.data.addAll(newData)\n        notifyItemRangeInserted(this.data.size - newData.size + headerLayoutCount, newData.size)\n        compatibilityDataSizeChanged(newData.size)\n    }\n\n    /**\n     * remove the item associated with the specified position of adapter\n     * 删除指定位置的数据\n     *\n     * @param position\n     */\n    open fun remove(@IntRange(from = 0) position: Int) {\n        if (position >= data.size) {\n            return\n        }\n        this.data.removeAt(position)\n        val internalPosition = position + headerLayoutCount\n        notifyItemRemoved(internalPosition)\n        compatibilityDataSizeChanged(0)\n        notifyItemRangeChanged(internalPosition, this.data.size - internalPosition)\n    }\n\n    open fun remove(data: T) {\n        val index = this.data.indexOf(data)\n        if (index == -1) {\n            return\n        }\n        remove(index)\n    }\n\n\n    /**\n     * compatible getLoadMoreViewCount and getEmptyViewCount may change\n     *\n     * @param size Need compatible data size\n     */\n    protected fun compatibilityDataSizeChanged(size: Int) {\n        if (this.data.size == size) {\n            notifyDataSetChanged()\n        }\n    }\n\n    /**\n     * 设置Diff Callback，用于快速生成 Diff Config。\n     *\n     * @param diffCallback ItemCallback<T>\n     */\n    fun setDiffCallback(diffCallback: DiffUtil.ItemCallback<T>) {\n        this.setDiffConfig(BrvahAsyncDifferConfig.Builder(diffCallback).build())\n    }\n\n    /**\n     * 设置Diff Config。如需自定义线程，请使用此方法。\n     * 在使用 [setDiffNewData] 前，必须设置此方法\n     * @param config BrvahAsyncDifferConfig<T>\n     */\n    fun setDiffConfig(config: BrvahAsyncDifferConfig<T>) {\n        mDiffHelper = BrvahAsyncDiffer(this, config)\n    }\n\n    fun getDiffHelper(): BrvahAsyncDiffer<T> {\n        checkNotNull(mDiffHelper) {\n            \"Please use setDiffCallback() or setDiffConfig() first!\"\n        }\n        return mDiffHelper!!\n    }\n\n    /**\n     * 使用 Diff 设置新实例.\n     * 此方法为异步Diff，无需考虑性能问题.\n     * 使用之前请先设置 [setDiffCallback] 或者 [setDiffConfig].\n     *\n     * Use Diff setting up a new instance to data.\n     * This method is asynchronous.\n     *\n     * @param newData MutableList<T>?\n     */\n    open fun setDiffNewData(list: MutableList<T>?) {\n        if (hasEmptyView()) {\n            // If the current view is an empty view, set the new data directly without diff\n            setNewInstance(list)\n            return\n        }\n        mDiffHelper?.submitList(list)\n    }\n\n    /**\n     * 使用 DiffResult 设置新实例.\n     * Use DiffResult setting up a new instance to data.\n     *\n     * @param diffResult DiffResult\n     * @param newData New Data\n     */\n    open fun setDiffNewData(@NonNull diffResult: DiffUtil.DiffResult, list: MutableList<T>) {\n        if (hasEmptyView()) {\n            // If the current view is an empty view, set the new data directly without diff\n            setNewInstance(list)\n            return\n        }\n        diffResult.dispatchUpdatesTo(BrvahListUpdateCallback(this))\n        this.data = list\n    }\n\n    /************************************** Set Listener ****************************************/\n\n    override fun setGridSpanSizeLookup(spanSizeLookup: GridSpanSizeLookup?) {\n        this.mSpanSizeLookup = spanSizeLookup\n    }\n\n    override fun setOnItemClickListener(listener: OnItemClickListener?) {\n        this.mOnItemClickListener = listener\n    }\n\n    override fun setOnItemLongClickListener(listener: OnItemLongClickListener?) {\n        this.mOnItemLongClickListener = listener\n    }\n\n    override fun setOnItemChildClickListener(listener: OnItemChildClickListener?) {\n        this.mOnItemChildClickListener = listener\n    }\n\n    override fun setOnItemChildLongClickListener(listener: OnItemChildLongClickListener?) {\n        this.mOnItemChildLongClickListener = listener\n    }\n\n    fun getOnItemClickListener(): OnItemClickListener? = mOnItemClickListener\n\n    fun getOnItemLongClickListener(): OnItemLongClickListener? = mOnItemLongClickListener\n\n    fun getOnItemChildClickListener(): OnItemChildClickListener? = mOnItemChildClickListener\n\n    fun getOnItemChildLongClickListener(): OnItemChildLongClickListener? = mOnItemChildLongClickListener\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/BaseSectionQuickAdapter.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah\n\nimport androidx.annotation.LayoutRes\nimport com.didichuxing.doraemonkit.widget.brvah.entity.SectionEntity\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n/**\n * 快速实现带头部的 Adapter，由于本质属于多布局，所以继承自[BaseMultiItemQuickAdapter]\n * @param T : SectionEntity\n * @param VH : BaseViewHolder\n * @property sectionHeadResId Int\n * @constructor\n */\nabstract class BaseSectionQuickAdapter<T : SectionEntity, VH : BaseViewHolder>\n@JvmOverloads constructor(@LayoutRes private val sectionHeadResId: Int,\n                          data: MutableList<T>? = null)\n    : BaseMultiItemQuickAdapter<T, VH>(data) {\n\n    constructor(@LayoutRes sectionHeadResId: Int,\n                @LayoutRes layoutResId: Int,\n                data: MutableList<T>? = null) : this(sectionHeadResId, data) {\n        setNormalLayout(layoutResId)\n    }\n\n    init {\n        addItemType(SectionEntity.HEADER_TYPE, sectionHeadResId)\n    }\n\n    /**\n     * 重写此处，设置 Header\n     * @param helper ViewHolder\n     * @param item data\n     */\n    protected abstract fun convertHeader(helper: VH, item: T)\n\n    /**\n     * 重写此处，设置 Diff Header\n     * @param helper VH\n     * @param item T?\n     * @param payloads MutableList<Any>\n     */\n    protected open fun convertHeader(helper: VH, item: T, payloads: MutableList<Any>) {}\n\n    /**\n     * 如果 item 不是多布局，可以使用此方法快速设置 item layout\n     * 如果需要多布局 item，请使用[addItemType]\n     * @param layoutResId Int\n     */\n    protected fun setNormalLayout(@LayoutRes layoutResId: Int) {\n        addItemType(SectionEntity.NORMAL_TYPE, layoutResId)\n    }\n\n    override fun isFixedViewType(type: Int): Boolean {\n        return super.isFixedViewType(type) || type == SectionEntity.HEADER_TYPE\n    }\n\n    override fun onBindViewHolder(holder: VH, position: Int) {\n        if (holder.itemViewType == SectionEntity.HEADER_TYPE) {\n//            setFullSpan(holder)\n            convertHeader(holder, getItem(position - headerLayoutCount))\n        } else {\n            super.onBindViewHolder(holder, position)\n        }\n    }\n\n    override fun onBindViewHolder(holder: VH, position: Int, payloads: MutableList<Any>) {\n        if (payloads.isEmpty()) {\n            onBindViewHolder(holder, position)\n            return\n        }\n\n        if (holder.itemViewType == SectionEntity.HEADER_TYPE) {\n            convertHeader(holder, getItem(position - headerLayoutCount), payloads)\n        } else {\n            super.onBindViewHolder(holder, position, payloads)\n        }\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/animation/AlphaInAnimation.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.animation\n\nimport android.animation.Animator\nimport android.animation.ObjectAnimator\nimport android.view.View\nimport android.view.animation.LinearInterpolator\n\n/**\n * https://github.com/CymChad/BaseRecyclerViewAdapterHelper\n */\nclass AlphaInAnimation @JvmOverloads constructor(private val mFrom: Float = DEFAULT_ALPHA_FROM) : BaseAnimation {\n    override fun animators(view: View): Array<Animator> {\n        val animator = ObjectAnimator.ofFloat(view, \"alpha\", mFrom, 1f)\n        animator.duration = 300L\n        animator.interpolator = LinearInterpolator()\n        return arrayOf(animator)\n    }\n\n    companion object {\n        private const val DEFAULT_ALPHA_FROM = 0f\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/animation/BaseAnimation.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.animation\n\nimport android.animation.Animator\nimport android.view.View\n\n/**\n * https://github.com/CymChad/BaseRecyclerViewAdapterHelper\n */\ninterface BaseAnimation {\n    fun animators(view: View): Array<Animator>\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/animation/ScaleInAnimation.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.animation\n\nimport android.animation.Animator\nimport android.animation.ObjectAnimator\nimport android.view.View\nimport android.view.animation.DecelerateInterpolator\n\n/**\n * https://github.com/CymChad/BaseRecyclerViewAdapterHelper\n */\nclass ScaleInAnimation @JvmOverloads constructor(private val mFrom: Float = DEFAULT_SCALE_FROM) : BaseAnimation {\n\n    override fun animators(view: View): Array<Animator> {\n        val scaleX = ObjectAnimator.ofFloat(view, \"scaleX\", mFrom, 1f)\n        scaleX.duration = 300L\n        scaleX.interpolator = DecelerateInterpolator()\n\n        val scaleY = ObjectAnimator.ofFloat(view, \"scaleY\", mFrom, 1f)\n        scaleY.duration = 300L\n        scaleY.interpolator = DecelerateInterpolator()\n        return arrayOf(scaleX, scaleY)\n    }\n\n    companion object {\n        private const val DEFAULT_SCALE_FROM = .5f\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/animation/SlideInBottomAnimation.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.animation\n\nimport android.animation.Animator\nimport android.animation.ObjectAnimator\nimport android.view.View\nimport android.view.animation.DecelerateInterpolator\n\n/**\n * https://github.com/CymChad/BaseRecyclerViewAdapterHelper\n */\nclass SlideInBottomAnimation : BaseAnimation {\n    override fun animators(view: View): Array<Animator> {\n        val animator = ObjectAnimator.ofFloat(view, \"translationY\", view.measuredHeight.toFloat(), 0f)\n        animator.duration = 400L\n        animator.interpolator = DecelerateInterpolator(1.3f)\n        return arrayOf(animator)\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/animation/SlideInLeftAnimation.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.animation\n\nimport android.animation.Animator\nimport android.animation.ObjectAnimator\nimport android.view.View\nimport android.view.animation.DecelerateInterpolator\n\n/**\n * https://github.com/CymChad/BaseRecyclerViewAdapterHelper\n */\nclass SlideInLeftAnimation : BaseAnimation {\n    override fun animators(view: View): Array<Animator> {\n        val animator = ObjectAnimator.ofFloat(view, \"translationX\", -view.rootView.width.toFloat(), 0f)\n        animator.duration = 400L\n        animator.interpolator = DecelerateInterpolator(1.8f)\n        return arrayOf(animator)\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/animation/SlideInRightAnimation.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.animation\n\nimport android.animation.Animator\nimport android.animation.ObjectAnimator\nimport android.view.View\nimport android.view.animation.DecelerateInterpolator\n\n/**\n * https://github.com/CymChad/BaseRecyclerViewAdapterHelper\n */\nclass SlideInRightAnimation : BaseAnimation {\n    override fun animators(view: View): Array<Animator> {\n        val animator = ObjectAnimator.ofFloat(view, \"translationX\", view.rootView.width.toFloat(), 0f)\n        animator.duration = 400L\n        animator.interpolator = DecelerateInterpolator(1.8f)\n        return arrayOf(animator)\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/binder/BaseItemBinder.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.binder\n\nimport android.content.Context\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.IdRes\nimport com.didichuxing.doraemonkit.widget.brvah.BaseBinderAdapter\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n/**\n * Binder 的基类\n */\nabstract class BaseItemBinder<T, VH : BaseViewHolder> {\n\n    private val clickViewIds by lazy(LazyThreadSafetyMode.NONE) { ArrayList<Int>() }\n    private val longClickViewIds by lazy(LazyThreadSafetyMode.NONE) { ArrayList<Int>() }\n\n    internal var _adapter: BaseBinderAdapter? = null\n    internal var _context: Context? = null\n\n    val adapter: BaseBinderAdapter\n        get() {\n            checkNotNull(_adapter) {\n                \"\"\"This $this has not been attached to BaseBinderAdapter yet.\n                    You should not call the method before addItemBinder().\"\"\"\n            }\n            return _adapter!!\n        }\n\n    val context: Context\n        get() {\n            checkNotNull(_context) {\n                \"\"\"This $this has not been attached to BaseBinderAdapter yet.\n                    You should not call the method before onCreateViewHolder().\"\"\"\n            }\n            return _context!!\n        }\n\n    val data: MutableList<Any> get() = adapter.data\n\n    abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH\n\n    /**\n     * 在此处对设置item数据\n     * @param holder VH\n     * @param data T\n     */\n    abstract fun convert(holder: VH, data: T)\n\n    /**\n     * 使用局部刷新时候，会调用此方法\n     * @param holder VH\n     * @param data T\n     * @param payloads List<Any>\n     */\n    open fun convert(holder: VH, data: T, payloads: List<Any>) {}\n\n    open fun onFailedToRecycleView(holder: VH): Boolean {\n        return false\n    }\n\n    /**\n     * Called when a view created by this [BaseItemBinder] has been attached to a window.\n     * 当此[BaseItemBinder]出现在屏幕上的时候，会调用此方法\n     *\n     * This can be used as a reasonable signal that the view is about to be seen\n     * by the user. If the [BaseItemBinder] previously freed any resources in\n     * [onViewDetachedFromWindow][.onViewDetachedFromWindow]\n     * those resources should be restored here.\n     *\n     * @param holder Holder of the view being attached\n     */\n    open fun onViewAttachedToWindow(holder: VH) {}\n\n    /**\n     * Called when a view created by this [BaseItemBinder] has been detached from its\n     * window.\n     * 当此[BaseItemBinder]从屏幕上移除的时候，会调用此方法\n     *\n     * Becoming detached from the window is not necessarily a permanent condition;\n     * the consumer of an Adapter's views may choose to cache views offscreen while they\n     * are not visible, attaching and detaching them as appropriate.\n     *\n     * @param holder Holder of the view being detached\n     */\n    open fun onViewDetachedFromWindow(holder: VH) {}\n\n    /**\n     * item 若想实现条目点击事件则重写该方法\n     * @param holder VH\n     * @param data T\n     * @param position Int\n     */\n    open fun onClick(holder: VH, view: View, data: T, position: Int) {}\n\n    /**\n     * item 若想实现条目长按事件则重写该方法\n     * @param holder VH\n     * @param data T\n     * @param position Int\n     * @return Boolean\n     */\n    open fun onLongClick(holder: VH, view: View, data: T, position: Int): Boolean {\n        return false\n    }\n\n    /**\n     * item 子控件的点击事件\n     * @param holder VH\n     * @param view View\n     * @param data T\n     * @param position Int\n     */\n    open fun onChildClick(holder: VH, view: View, data: T, position: Int) {}\n\n    /**\n     * item 子控件的长按事件\n     * @param holder VH\n     * @param view View\n     * @param data T\n     * @param position Int\n     * @return Boolean\n     */\n    open fun onChildLongClick(holder: VH, view: View, data: T, position: Int): Boolean {\n        return false\n    }\n\n    fun addChildClickViewIds(@IdRes vararg ids: Int) {\n        ids.forEach {\n            this.clickViewIds.add(it)\n        }\n    }\n\n    fun getChildClickViewIds() = this.clickViewIds\n\n    fun addChildLongClickViewIds(@IdRes vararg ids: Int) {\n        ids.forEach {\n            this.longClickViewIds.add(it)\n        }\n    }\n\n    fun getChildLongClickViewIds() = this.longClickViewIds\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/binder/QuickDataBindingItemBinder.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.binder\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.databinding.ViewDataBinding\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n/**\n * 使用 DataBinding 快速构建 Binder\n * @param T item数据类型\n * @param DB : ViewDataBinding\n */\nabstract class QuickDataBindingItemBinder<T, DB : ViewDataBinding> : BaseItemBinder<T, QuickDataBindingItemBinder.BinderDataBindingHolder<DB>>() {\n\n    /**\n     * 此 Holder 不适用于其他 BaseAdapter，仅针对[BaseBinderAdapter]\n     */\n    class BinderDataBindingHolder<DB : ViewDataBinding>(val dataBinding: DB) : BaseViewHolder(dataBinding.root)\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BinderDataBindingHolder<DB> {\n        return BinderDataBindingHolder(onCreateDataBinding(LayoutInflater.from(parent.context), parent, viewType))\n    }\n\n    abstract fun onCreateDataBinding(layoutInflater: LayoutInflater, parent: ViewGroup, viewType: Int): DB\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/binder/QuickItemBinder.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.binder\n\nimport android.view.ViewGroup\nimport androidx.annotation.LayoutRes\nimport com.didichuxing.doraemonkit.widget.brvah.util.getItemView\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n/**\n * 使用布局 ID 快速构建 Binder\n * @param T item 数据类型\n */\nabstract class QuickItemBinder<T> : BaseItemBinder<T, BaseViewHolder>() {\n\n    @LayoutRes\n    abstract fun getLayoutId(): Int\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder =\n            BaseViewHolder(parent.getItemView(getLayoutId()))\n\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/binder/QuickViewBindingItemBinder.kt",
    "content": "//package com.didichuxing.doraemonkit.widget.bravh.binder\n//\n//import android.view.LayoutInflater\n//import android.view.ViewGroup\n//import androidx.viewbinding.ViewBinding\n//import com.didichuxing.doraemonkit.widget.bravh.viewholder.BaseViewHolder\n//\n///**\n// * 使用 ViewBinding 快速构建 Binder\n// * @param T item数据类型\n// * @param VB : ViewBinding\n// */\n//abstract class QuickViewBindingItemBinder<T, VB : ViewBinding> : BaseItemBinder<T, QuickViewBindingItemBinder.BinderVBHolder<VB>>() {\n//\n//    /**\n//     * 此 Holder 不适用于其他 BaseAdapter，仅针对[BaseBinderAdapter]\n//     */\n//    class BinderVBHolder<VB : ViewBinding>(val viewBinding: VB) : BaseViewHolder(viewBinding.root)\n//\n//    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BinderVBHolder<VB> {\n//        return BinderVBHolder(onCreateViewBinding(LayoutInflater.from(parent.context), parent, viewType))\n//    }\n//\n//    abstract fun onCreateViewBinding(layoutInflater: LayoutInflater, parent: ViewGroup, viewType: Int): VB\n//}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/delegate/BaseMultiTypeDelegate.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.delegate\n\nimport android.util.SparseIntArray\nimport androidx.annotation.LayoutRes\n\n/**\n * help you to achieve multi type easily\n *\n *\n * Created by tysheng\n * Date: 2017/4/6 08:41.\n * Email: tyshengsx@gmail.com\n *\n * more information: https://github.com/CymChad/BaseRecyclerViewAdapterHelper/issues/968\n */\n\nabstract class BaseMultiTypeDelegate<T>(private var layouts: SparseIntArray = SparseIntArray()) {\n    private var autoMode: Boolean = false\n    private var selfMode: Boolean = false\n\n    /**\n     * get the item type from specific entity.\n     *\n     * @param data entity\n     * @param position\n     * @return item type\n     */\n    abstract fun getItemType(data: List<T>, position: Int): Int\n\n    fun getLayoutId(viewType: Int): Int {\n        val layoutResId = layouts.get(viewType)\n        require(layoutResId != 0) { \"ViewType: $viewType found layoutResId，please use registerItemType() first!\" }\n        return layoutResId\n    }\n\n    private fun registerItemType(type: Int, @LayoutRes layoutResId: Int) {\n        this.layouts.put(type, layoutResId)\n    }\n\n    /**\n     * auto increase type vale, start from 0.\n     *\n     * @param layoutResIds layout id arrays\n     * @return MultiTypeDelegate\n     */\n    fun addItemTypeAutoIncrease(@LayoutRes vararg layoutResIds: Int): BaseMultiTypeDelegate<T> {\n        autoMode = true\n        checkMode(selfMode)\n        for (i in layoutResIds.indices) {\n            registerItemType(i, layoutResIds[i])\n        }\n        return this\n    }\n\n    /**\n     * set your own type one by one.\n     *\n     * @param type        type value\n     * @param layoutResId layout id\n     * @return MultiTypeDelegate\n     */\n    fun addItemType(type: Int, @LayoutRes layoutResId: Int): BaseMultiTypeDelegate<T> {\n        selfMode = true\n        checkMode(autoMode)\n        registerItemType(type, layoutResId)\n        return this\n    }\n\n    private fun checkMode(mode: Boolean) {\n        require(!mode) { \"Don't mess two register mode\" }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/diff/BrvahAsyncDiffer.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.diff\n\nimport android.os.Handler\nimport android.os.Looper\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.DiffUtil.DiffResult\nimport androidx.recyclerview.widget.ListUpdateCallback\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter\nimport java.util.concurrent.CopyOnWriteArrayList\nimport java.util.concurrent.Executor\n\nclass BrvahAsyncDiffer<T>(private val adapter: BaseQuickAdapter<T, *>,\n                          private val config: BrvahAsyncDifferConfig<T>) : DifferImp<T> {\n    private val mUpdateCallback: ListUpdateCallback = BrvahListUpdateCallback(adapter)\n    private var mMainThreadExecutor: Executor\n\n    private class MainThreadExecutor internal constructor() : Executor {\n        val mHandler = Handler(Looper.getMainLooper())\n        override fun execute(command: Runnable) {\n            mHandler.post(command)\n        }\n    }\n\n    private val sMainThreadExecutor: Executor = MainThreadExecutor()\n\n    init {\n        mMainThreadExecutor = config.mainThreadExecutor ?: sMainThreadExecutor\n    }\n\n    private val mListeners: MutableList<ListChangeListener<T>> = CopyOnWriteArrayList()\n\n    private var mMaxScheduledGeneration = 0\n\n    @JvmOverloads\n    fun submitList(newList: MutableList<T>?, commitCallback: Runnable? = null) {\n        // incrementing generation means any currently-running diffs are discarded when they finish\n        val runGeneration: Int = ++mMaxScheduledGeneration\n        if (newList === adapter.data) {\n            // nothing to do (Note - still had to inc generation, since may have ongoing work)\n            commitCallback?.run()\n            return\n        }\n        val oldList: List<T> = adapter.data\n        // fast simple remove all\n        if (newList == null) {\n            val countRemoved: Int = adapter.data.size\n            adapter.data = arrayListOf()\n            // notify last, after list is updated\n            mUpdateCallback.onRemoved(0, countRemoved)\n            onCurrentListChanged(oldList, commitCallback)\n            return\n        }\n        // fast simple first insert\n        if (adapter.data.isEmpty()) {\n            adapter.data = newList\n            // notify last, after list is updated\n            mUpdateCallback.onInserted(0, newList.size)\n            onCurrentListChanged(oldList, commitCallback)\n            return\n        }\n\n        config.backgroundThreadExecutor.execute {\n            val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {\n                override fun getOldListSize(): Int {\n                    return oldList.size\n                }\n\n                override fun getNewListSize(): Int {\n                    return newList.size\n                }\n\n                override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {\n                    val oldItem: T? = oldList[oldItemPosition]\n                    val newItem: T? = newList[newItemPosition]\n                    return if (oldItem != null && newItem != null) {\n                        config.diffCallback.areItemsTheSame(oldItem, newItem)\n                    } else oldItem == null && newItem == null\n                    // If both items are null we consider them the same.\n                }\n\n                override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {\n                    val oldItem: T? = oldList[oldItemPosition]\n                    val newItem: T? = newList[newItemPosition]\n                    if (oldItem != null && newItem != null) {\n                        return config.diffCallback.areContentsTheSame(oldItem, newItem)\n                    }\n                    if (oldItem == null && newItem == null) {\n                        return true\n                    }\n                    throw AssertionError()\n                }\n\n                override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {\n                    val oldItem: T? = oldList[oldItemPosition]\n                    val newItem: T? = newList[newItemPosition]\n                    if (oldItem != null && newItem != null) {\n                        return config.diffCallback.getChangePayload(oldItem, newItem)\n                    }\n                    throw AssertionError()\n                }\n            })\n            mMainThreadExecutor.execute {\n                if (mMaxScheduledGeneration == runGeneration) {\n                    latchList(newList, result, commitCallback)\n                }\n            }\n        }\n    }\n\n    private fun latchList(\n            newList: MutableList<T>,\n            diffResult: DiffResult,\n            commitCallback: Runnable?) {\n        val previousList: List<T> = adapter.data\n        adapter.data = newList\n\n        diffResult.dispatchUpdatesTo(mUpdateCallback)\n        onCurrentListChanged(previousList, commitCallback)\n    }\n\n    private fun onCurrentListChanged(previousList: List<T>,\n                                     commitCallback: Runnable?) {\n        for (listener in mListeners) {\n            listener.onCurrentListChanged(previousList, adapter.data)\n        }\n        commitCallback?.run()\n    }\n\n    /**\n     * Add a ListListener to receive updates when the current List changes.\n     *\n     * @param listener Listener to receive updates.\n     *\n     * @see .getCurrentList\n     * @see .removeListListener\n     */\n    override fun addListListener(listener: ListChangeListener<T>) {\n        mListeners.add(listener)\n    }\n\n    /**\n     * Remove a previously registered ListListener.\n     *\n     * @param listener Previously registered listener.\n     * @see .getCurrentList\n     * @see .addListListener\n     */\n    fun removeListListener(listener: ListChangeListener<T>) {\n        mListeners.remove(listener)\n    }\n\n    fun clearAllListListener() {\n        mListeners.clear()\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/diff/BrvahAsyncDifferConfig.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.diff\n\nimport androidx.annotation.RestrictTo\nimport androidx.recyclerview.widget.DiffUtil\nimport java.util.concurrent.Executor\nimport java.util.concurrent.Executors\n\nclass BrvahAsyncDifferConfig<T>(\n        @RestrictTo(RestrictTo.Scope.LIBRARY)\n        val mainThreadExecutor: Executor?,\n        val backgroundThreadExecutor: Executor,\n        val diffCallback: DiffUtil.ItemCallback<T>) {\n\n    /**\n     * Builder class for [BrvahAsyncDifferConfig].\n     *\n     * @param <T>\n    </T> */\n    class Builder<T>(private val mDiffCallback: DiffUtil.ItemCallback<T>) {\n        private var mMainThreadExecutor: Executor? = null\n        private var mBackgroundThreadExecutor: Executor? = null\n        /**\n         * If provided, defines the main thread executor used to dispatch adapter update\n         * notifications on the main thread.\n         *\n         *\n         * If not provided, it will default to the main thread.\n         *\n         * @param executor The executor which can run tasks in the UI thread.\n         * @return this\n         *\n         * @hide\n         */\n        fun setMainThreadExecutor(executor: Executor?): Builder<T> {\n            mMainThreadExecutor = executor\n            return this\n        }\n\n        /**\n         * If provided, defines the background executor used to calculate the diff between an old\n         * and a new list.\n         *\n         *\n         * If not provided, defaults to two thread pool executor, shared by all ListAdapterConfigs.\n         *\n         * @param executor The background executor to run list diffing.\n         * @return this\n         */\n        fun setBackgroundThreadExecutor(executor: Executor?): Builder<T> {\n            mBackgroundThreadExecutor = executor\n            return this\n        }\n\n        /**\n         * Creates a [BrvahAsyncDifferConfig] with the given parameters.\n         *\n         * @return A new AsyncDifferConfig.\n         */\n        fun build(): BrvahAsyncDifferConfig<T> {\n            if (mBackgroundThreadExecutor == null) {\n                synchronized(sExecutorLock) {\n                    if (sDiffExecutor == null) {\n                        sDiffExecutor = Executors.newFixedThreadPool(2)\n                    }\n                }\n                mBackgroundThreadExecutor = sDiffExecutor\n            }\n            return BrvahAsyncDifferConfig(\n                    mMainThreadExecutor,\n                    mBackgroundThreadExecutor!!,\n                    mDiffCallback)\n        }\n\n        companion object {\n            // TODO: remove the below once supportlib has its own appropriate executors\n            private val sExecutorLock = Any()\n            private var sDiffExecutor: Executor? = null\n        }\n\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/diff/BrvahListUpdateCallback.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.diff\n\nimport androidx.recyclerview.widget.ListUpdateCallback\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter\n\nclass BrvahListUpdateCallback(private val mAdapter: BaseQuickAdapter<*, *>) : ListUpdateCallback {\n\n    override fun onInserted(position: Int, count: Int) {\n        mAdapter.notifyItemRangeInserted(position + mAdapter.headerLayoutCount, count)\n    }\n\n    override fun onRemoved(position: Int, count: Int) {\n        mAdapter.notifyItemRangeRemoved(position + mAdapter.headerLayoutCount, count)\n    }\n\n    override fun onMoved(fromPosition: Int, toPosition: Int) {\n        mAdapter.notifyItemMoved(fromPosition + mAdapter.headerLayoutCount, toPosition + mAdapter.headerLayoutCount)\n    }\n\n    override fun onChanged(position: Int, count: Int, payload: Any?) {\n        mAdapter.notifyItemRangeChanged(position + mAdapter.headerLayoutCount, count, payload)\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/diff/DifferImp.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.diff;\n\nimport androidx.annotation.NonNull;\n\n/**\n * 使用java接口定义方法\n * @param <T>\n */\npublic interface DifferImp<T> {\n    void addListListener(@NonNull ListChangeListener<T> listChangeListener);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/diff/ListChangeListener.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.diff;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.List;\n\npublic interface ListChangeListener<T> {\n    /**\n     * Called after the current List has been updated.\n     *\n     * @param previousList The previous list.\n     * @param currentList The new current list.\n     */\n    void onCurrentListChanged(@NonNull List<T> previousList, @NonNull List<T> currentList);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/dragswipe/DragAndSwipeCallback.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.dragswipe;\n\nimport android.graphics.Canvas;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.ItemTouchHelper;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.toolpanel.KitWrapItem;\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter;\nimport com.didichuxing.doraemonkit.widget.brvah.module.BaseDraggableModule;\n\nimport static androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_IDLE;\n\n\n/**\n * @author luoxw\n * @date 2016/6/20\n */\npublic class DragAndSwipeCallback extends ItemTouchHelper.Callback {\n    private static final String TAG = \"DragAndSwipeCallback\";\n\n    private BaseDraggableModule mDraggableModule;\n    private float mMoveThreshold = 0.1f;\n    private float mSwipeThreshold = 0.7f;\n\n\n    private int mDragMoveFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;\n    private int mSwipeMoveFlags = ItemTouchHelper.END;\n\n\n    public DragAndSwipeCallback(BaseDraggableModule draggableModule) {\n        mDraggableModule = draggableModule;\n    }\n\n    @Override\n    public boolean isLongPressDragEnabled() {\n        if (mDraggableModule != null) {\n            return mDraggableModule.isDragEnabled() && !mDraggableModule.hasToggleView();\n        }\n        return false;\n    }\n\n    @Override\n    public boolean isItemViewSwipeEnabled() {\n        if (mDraggableModule != null) {\n            return mDraggableModule.isSwipeEnabled();\n        }\n        return false;\n    }\n\n    @Override\n    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {\n        if (actionState == ItemTouchHelper.ACTION_STATE_DRAG\n                && !isViewCreateByAdapter(viewHolder)) {\n            if (mDraggableModule != null) {\n                mDraggableModule.onItemDragStart(viewHolder);\n            }\n            viewHolder.itemView.setTag(R.id.dokit_baseQuickAdapter_dragging_support, true);\n        } else if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE\n                && !isViewCreateByAdapter(viewHolder)) {\n            if (mDraggableModule != null) {\n                mDraggableModule.onItemSwipeStart(viewHolder);\n            }\n            viewHolder.itemView.setTag(R.id.dokit_baseQuickAdapter_swiping_support, true);\n        }\n        super.onSelectedChanged(viewHolder, actionState);\n    }\n\n    @Override\n    public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {\n        super.clearView(recyclerView, viewHolder);\n        if (isViewCreateByAdapter(viewHolder)) {\n            return;\n        }\n\n        if (viewHolder.itemView.getTag(R.id.dokit_baseQuickAdapter_dragging_support) != null\n                && (Boolean) viewHolder.itemView.getTag(R.id.dokit_baseQuickAdapter_dragging_support)) {\n            if (mDraggableModule != null) {\n                mDraggableModule.onItemDragEnd(viewHolder);\n            }\n            viewHolder.itemView.setTag(R.id.dokit_baseQuickAdapter_dragging_support, false);\n        }\n        if (viewHolder.itemView.getTag(R.id.dokit_baseQuickAdapter_swiping_support) != null\n                && (Boolean) viewHolder.itemView.getTag(R.id.dokit_baseQuickAdapter_swiping_support)) {\n            if (mDraggableModule != null) {\n                mDraggableModule.onItemSwipeClear(viewHolder);\n            }\n            viewHolder.itemView.setTag(R.id.dokit_baseQuickAdapter_swiping_support, false);\n        }\n    }\n\n    /**\n     * 这个方法用于让RecyclerView拦截向上滑动，向下滑动，想左滑动\n     * makeMovementFlags(dragFlags, swipeFlags);dragFlags是上下方向的滑动 swipeFlags是左右方向上的滑动\n     *\n     * @param recyclerView\n     * @param viewHolder\n     * @return\n     */\n    @Override\n    public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {\n        //设置指定item 不可拖动\n        if (viewHolder.getItemViewType() == KitWrapItem.TYPE_TITLE) {\n            return makeMovementFlags(ACTION_STATE_IDLE, ACTION_STATE_IDLE);\n        }\n\n        if (isViewCreateByAdapter(viewHolder)) {\n            return makeMovementFlags(ACTION_STATE_IDLE, ACTION_STATE_IDLE);\n        }\n\n        return makeMovementFlags(mDragMoveFlags, mSwipeMoveFlags);\n    }\n\n    /**\n     * drag状态下，在canDropOver()返回true时，会调用该方法让我们拖动换位置的逻辑(需要自己处理变换位置的逻辑)\n     */\n    @Override\n    public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder source, @NonNull RecyclerView.ViewHolder target) {\n        return source.getItemViewType() == target.getItemViewType();\n    }\n\n    /**\n     * 针对drag状态，当前target对应的item是否允许移动\n     * 我们一般用drag来做一些换位置的操作，就是当前对应的target对应的Item可以移动\n     */\n    @Override\n    public boolean canDropOver(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder current, @NonNull RecyclerView.ViewHolder target) {\n        return mDraggableModule.canDropOver(recyclerView, current, target);\n    }\n\n    @Override\n    public void onMoved(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder source, int fromPos, @NonNull RecyclerView.ViewHolder target, int toPos, int x, int y) {\n        super.onMoved(recyclerView, source, fromPos, target, toPos, x, y);\n        if (mDraggableModule != null) {\n            mDraggableModule.onItemDragMoving(source, target);\n        }\n    }\n\n    @Override\n    public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {\n        if (!isViewCreateByAdapter(viewHolder)) {\n            if (mDraggableModule != null) {\n                mDraggableModule.onItemSwiped(viewHolder);\n            }\n        }\n    }\n\n    @Override\n    public float getMoveThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {\n        return mMoveThreshold;\n    }\n\n    @Override\n    public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {\n        return mSwipeThreshold;\n    }\n\n    /**\n     * Set the fraction that the user should move the View to be considered as swiped.\n     * The fraction is calculated with respect to RecyclerView's bounds.\n     * <p>\n     * Default value is .5f, which means, to swipe a View, user must move the View at least\n     * half of RecyclerView's width or height, depending on the swipe direction.\n     *\n     * @param swipeThreshold A float value that denotes the fraction of the View size. Default value\n     *                       is .8f .\n     */\n    public void setSwipeThreshold(float swipeThreshold) {\n        mSwipeThreshold = swipeThreshold;\n    }\n\n\n    /**\n     * Set the fraction that the user should move the View to be considered as it is\n     * dragged. After a view is moved this amount, ItemTouchHelper starts checking for Views\n     * below it for a possible drop.\n     *\n     * @param moveThreshold A float value that denotes the fraction of the View size. Default value is\n     *                      .1f .\n     */\n    public void setMoveThreshold(float moveThreshold) {\n        mMoveThreshold = moveThreshold;\n    }\n\n    /**\n     * <p>Set the drag movement direction.</p>\n     * <p>The value should be ItemTouchHelper.UP, ItemTouchHelper.DOWN, ItemTouchHelper.LEFT, ItemTouchHelper.RIGHT or their combination.</p>\n     * You can combine them like ItemTouchHelper.UP | ItemTouchHelper.DOWN, it means that the item could only move up and down when dragged.\n     *\n     * @param dragMoveFlags the drag movement direction. Default value is ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT.\n     */\n\n    public void setDragMoveFlags(int dragMoveFlags) {\n        mDragMoveFlags = dragMoveFlags;\n    }\n\n    /**\n     * <p>Set the swipe movement direction.</p>\n     * <p>The value should be ItemTouchHelper.START, ItemTouchHelper.END or their combination.</p>\n     * You can combine them like ItemTouchHelper.START | ItemTouchHelper.END, it means that the item could swipe to both left or right.\n     *\n     * @param swipeMoveFlags the swipe movement direction. Default value is ItemTouchHelper.END.\n     */\n    public void setSwipeMoveFlags(int swipeMoveFlags) {\n        mSwipeMoveFlags = swipeMoveFlags;\n    }\n\n    @Override\n    public void onChildDrawOver(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder,\n                                float dX, float dY, int actionState, boolean isCurrentlyActive) {\n        super.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);\n\n        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE\n                && !isViewCreateByAdapter(viewHolder)) {\n            View itemView = viewHolder.itemView;\n\n            c.save();\n            if (dX > 0) {\n                c.clipRect(itemView.getLeft(), itemView.getTop(),\n                        itemView.getLeft() + dX, itemView.getBottom());\n                c.translate(itemView.getLeft(), itemView.getTop());\n            } else {\n                c.clipRect(itemView.getRight() + dX, itemView.getTop(),\n                        itemView.getRight(), itemView.getBottom());\n                c.translate(itemView.getRight() + dX, itemView.getTop());\n            }\n            if (mDraggableModule != null) {\n                mDraggableModule.onItemSwiping(c, viewHolder, dX, dY, isCurrentlyActive);\n            }\n            c.restore();\n\n        }\n    }\n\n    private boolean isViewCreateByAdapter(@NonNull RecyclerView.ViewHolder viewHolder) {\n        int type = viewHolder.getItemViewType();\n        return type == BaseQuickAdapter.HEADER_VIEW || type == BaseQuickAdapter.LOAD_MORE_VIEW\n                || type == BaseQuickAdapter.FOOTER_VIEW || type == BaseQuickAdapter.EMPTY_VIEW;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/entity/JSectionEntity.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.entity;\n\n/**\n * 仅供java使用\n *\n * 由于java无法实现{@link SectionEntity}中的默认接口实现，所以使用抽象类再封装一次，用于提供默认实现。\n */\npublic abstract class JSectionEntity implements SectionEntity {\n\n    /**\n     * 用于返回item类型，除了头布局外，默认只有 NORMAL_TYPE 一种布局\n     * 如果需要实现 item 多布局，请重写此方法，返回自己的type\n     */\n    @Override\n    public int getItemType() {\n        if (isHeader()) {\n            return SectionEntity.Companion.HEADER_TYPE;\n        } else {\n            // 拷贝 重写此处，返回自己的多布局类型\n            return SectionEntity.Companion.NORMAL_TYPE;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/entity/MultiItemEntity.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.entity\n\n/**\n * 多布局类型\n */\ninterface MultiItemEntity {\n    val itemType: Int\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/entity/SectionEntity.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.entity\n\n\n/**\n * 带头部布局的实体类接口\n * 实体类请继承此接口；如果使用java，请使用[JSectionEntity]抽象类\n */\ninterface SectionEntity : MultiItemEntity {\n\n    val isHeader: Boolean\n\n    /**\n     * 用于返回item类型，除了头布局外，默认只有[NORMAL_TYPE]一种布局\n     * 如果需要实现 item 多布局，请重写此方法，返回自己的type\n     */\n    override val itemType: Int\n        get() = if (isHeader) HEADER_TYPE else NORMAL_TYPE\n\n    companion object {\n        const val NORMAL_TYPE = -100\n        const val HEADER_TYPE = -99\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/entity/node/BaseExpandNode.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.entity.node\n\nabstract class BaseExpandNode : BaseNode() {\n    var isExpanded: Boolean = true\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/entity/node/BaseNode.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.entity.node\n\nabstract class BaseNode {\n\n    /**\n     * 重写此方法，获取子节点。如果没有子节点，返回 null 或者 空数组\n     *\n     * 如果返回 null，则无法对子节点的数据进行新增和删除等操作\n     */\n    abstract val childNode: MutableList<BaseNode>?\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/entity/node/NodeFooterImp.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.entity.node\n\n/**\n * 如果需要，可以实现此接口，返回脚部节点\n */\ninterface NodeFooterImp {\n    /**\n     * 返回脚部节点\n     * @return BaseNode? 如果返回 null，则代表没有脚部节点\n     */\n    val footerNode: BaseNode?\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/listener/BaseListenerImp.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.listener;\n\nimport androidx.annotation.Nullable;\n\n/**\n * @author: limuyang\n *  2019-12-03\n * @Description: BaseQuickAdapter需要设置的接口。使用java定义，以兼容java写法\n */\npublic interface BaseListenerImp {\n    /**\n     * Register a callback to be invoked when an item in this RecyclerView has\n     * been clicked.\n     *\n     * @param listener The callback that will be invoked.\n     */\n    void setOnItemClickListener(@Nullable OnItemClickListener listener);\n\n    void setOnItemLongClickListener(@Nullable OnItemLongClickListener listener);\n\n    void setOnItemChildClickListener(@Nullable OnItemChildClickListener listener);\n\n    void setOnItemChildLongClickListener(@Nullable OnItemChildLongClickListener listener);\n\n    void setGridSpanSizeLookup(@Nullable GridSpanSizeLookup spanSizeLookup);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/listener/DraggableListenerImp.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.listener;\n\nimport androidx.annotation.Nullable;\n\n/**\n * @author: limuyang\n *  2019-12-05\n * @Description:\n */\npublic interface DraggableListenerImp {\n\n    void setOnItemDragListener(@Nullable OnItemDragListener onItemDragListener);\n\n    void setOnItemSwipeListener(@Nullable OnItemSwipeListener onItemSwipeListener);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/listener/GridSpanSizeLookup.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.listener;\n\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.GridLayoutManager;\n\n/**\n * @author: limuyang\n *  2019-12-03\n * @Description:\n */\npublic interface GridSpanSizeLookup {\n\n    int getSpanSize(@NonNull GridLayoutManager gridLayoutManager, int viewType, int position);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/listener/LoadMoreListenerImp.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.listener;\n\nimport androidx.annotation.Nullable;\n\n/**\n * @author: limuyang\n *  2019-12-03\n * @Description: LoadMore需要设置的接口。使用java定义，以兼容java写法\n */\npublic interface LoadMoreListenerImp {\n\n    void setOnLoadMoreListener(@Nullable OnLoadMoreListener listener);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/listener/OnItemChildClickListener.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.listener;\n\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter;\n\n/**\n * @author: limuyang\n *  2019-12-03\n * @Description:\n */\npublic interface OnItemChildClickListener {\n    /**\n     * callback method to be invoked when an item child in this view has been click\n     *\n     * @param adapter  BaseQuickAdapter\n     * @param view     The view whihin the ItemView that was clicked\n     * @param position The position of the view int the adapter\n     */\n    void onItemChildClick(@NonNull BaseQuickAdapter adapter, @NonNull View view, int position);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/listener/OnItemChildLongClickListener.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.listener;\n\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter;\n\n/**\n * @author: limuyang\n *  2019-12-03\n * @Description:\n */\npublic interface OnItemChildLongClickListener {\n    /**\n     * callback method to be invoked when an item in this view has been\n     * click and held\n     *\n     * @param adapter  this BaseQuickAdapter adapter\n     * @param view     The childView whihin the itemView that was clicked and held.\n     * @param position The position of the view int the adapter\n     * @return true if the callback consumed the long click ,false otherwise\n     */\n    boolean onItemChildLongClick(@NonNull BaseQuickAdapter adapter, @NonNull View view, int position);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/listener/OnItemClickListener.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.listener;\n\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter;\n\n/**\n * @author: limuyang\n *  2019-12-03\n * @Description: Interface definition for a callback to be invoked when an item in this\n * RecyclerView itemView has been clicked.\n */\npublic interface OnItemClickListener {\n    /**\n     * Callback method to be invoked when an item in this RecyclerView has\n     * been clicked.\n     *\n     * @param adapter  the adapter\n     * @param view     The itemView within the RecyclerView that was clicked (this\n     *                 will be a view provided by the adapter)\n     * @param position The position of the view in the adapter.\n     */\n    void onItemClick(@NonNull BaseQuickAdapter<?, ?> adapter, @NonNull View view, int position);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/listener/OnItemDragListener.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.listener;\n\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.RecyclerView;\n\n/**\n * Created by luoxw on 2016/6/20.\n */\npublic interface OnItemDragListener {\n    void onItemDragStart(RecyclerView.ViewHolder viewHolder, int pos);\n\n    void onItemDragMoving(RecyclerView.ViewHolder source, int from, RecyclerView.ViewHolder target, int to);\n\n    void onItemDragEnd(RecyclerView.ViewHolder viewHolder, int pos);\n\n    boolean canDropOver(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder current, @NonNull RecyclerView.ViewHolder target);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/listener/OnItemLongClickListener.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.listener;\n\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter;\n\n/**\n * @author: limuyang\n *  2019-12-03\n * @Description:\n */\npublic interface OnItemLongClickListener {\n    /**\n     * callback method to be invoked when an item in this view has been\n     * click and held\n     *\n     * @param adapter  the adapter\n     * @param view     The view whihin the RecyclerView that was clicked and held.\n     * @param position The position of the view int the adapter\n     * @return true if the callback consumed the long click ,false otherwise\n     */\n    boolean onItemLongClick(@NonNull BaseQuickAdapter adapter, @NonNull View view, int position);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/listener/OnItemSwipeListener.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.listener;\n\nimport android.graphics.Canvas;\n\nimport androidx.recyclerview.widget.RecyclerView;\n\n/**\n * Created by luoxw on 2016/6/23.\n */\npublic interface OnItemSwipeListener {\n    /**\n     * Called when the swipe action start.\n     */\n    void onItemSwipeStart(RecyclerView.ViewHolder viewHolder, int pos);\n\n    /**\n     * Called when the swipe action is over.\n     * If you change the view on the start, you should reset is here, no matter the item has swiped or not.\n     *\n     * @param pos If the view is swiped, pos will be negative.\n     */\n    void clearView(RecyclerView.ViewHolder viewHolder, int pos);\n\n    /**\n     * Called when item is swiped, the view is going to be removed from the adapter.\n     */\n    void onItemSwiped(RecyclerView.ViewHolder viewHolder, int pos);\n\n    /**\n     * Draw on the empty edge when swipe moving\n     *\n     * @param canvas            the empty edge's canvas\n     * @param viewHolder        The ViewHolder which is being interacted by the User or it was\n     *                          interacted and simply animating to its original position\n     * @param dX                The amount of horizontal displacement caused by user's action\n     * @param dY                The amount of vertical displacement caused by user's action\n     * @param isCurrentlyActive True if this view is currently being controlled by the user or\n     *                          false it is simply animating back to its original state.\n     */\n    void onItemSwipeMoving(Canvas canvas, RecyclerView.ViewHolder viewHolder, float dX, float dY, boolean isCurrentlyActive);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/listener/OnLoadMoreListener.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.listener;\n\n/**\n * @author: limuyang\n *  2019-12-03\n * @Description:\n */\npublic interface OnLoadMoreListener {\n\n    void onLoadMore();\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/listener/OnUpFetchListener.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.listener;\n\n/**\n * @author: limuyang\n *  2019-12-03\n * @Description:\n */\npublic interface OnUpFetchListener {\n    void onUpFetch();\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/listener/UpFetchListenerImp.java",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.listener;\n\nimport androidx.annotation.Nullable;\n\n/**\n * @author: limuyang\n *  2019-12-03\n * @Description: UpFetch需要设置的接口。使用java定义，以兼容java写法\n */\npublic interface UpFetchListenerImp {\n    void setOnUpFetchListener(@Nullable OnUpFetchListener listener);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/loadmore/BaseLoadMoreView.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.loadmore\n\nimport android.view.View\nimport android.view.ViewGroup\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n/**\n *\n * @author limuyang\n */\n\nenum class LoadMoreStatus {\n    Complete, Loading, Fail, End\n}\n\n/**\n * 继承此类，实行自定义loadMore视图\n */\nabstract class BaseLoadMoreView {\n\n    /**\n     * 根布局\n     * @param parent ViewGroup\n     * @return View\n     */\n    abstract fun getRootView(parent: ViewGroup): View\n\n    /**\n     * 布局中的 加载更多视图\n     * @param holder BaseViewHolder\n     * @return View\n     */\n    abstract fun getLoadingView(holder: BaseViewHolder): View\n\n    /**\n     * 布局中的 加载完成布局\n     * @param holder BaseViewHolder\n     * @return View\n     */\n    abstract fun getLoadComplete(holder: BaseViewHolder): View\n\n    /**\n     * 布局中的 加载结束布局\n     * @param holder BaseViewHolder\n     * @return View\n     */\n    abstract fun getLoadEndView(holder: BaseViewHolder): View\n\n    /**\n     * 布局中的 加载失败布局\n     * @param holder BaseViewHolder\n     * @return View\n     */\n    abstract fun getLoadFailView(holder: BaseViewHolder): View\n\n    /**\n     * 可重写此方式，实行自定义逻辑\n     * @param holder BaseViewHolder\n     * @param position Int\n     * @param loadMoreStatus LoadMoreStatus\n     */\n    open fun convert(holder: BaseViewHolder, position: Int, loadMoreStatus: LoadMoreStatus) {\n        when (loadMoreStatus) {\n            LoadMoreStatus.Complete -> {\n                getLoadingView(holder).isVisible(false)\n                getLoadComplete(holder).isVisible(true)\n                getLoadFailView(holder).isVisible(false)\n                getLoadEndView(holder).isVisible(false)\n            }\n            LoadMoreStatus.Loading -> {\n                getLoadingView(holder).isVisible(true)\n                getLoadComplete(holder).isVisible(false)\n                getLoadFailView(holder).isVisible(false)\n                getLoadEndView(holder).isVisible(false)\n            }\n            LoadMoreStatus.Fail -> {\n                getLoadingView(holder).isVisible(false)\n                getLoadComplete(holder).isVisible(false)\n                getLoadFailView(holder).isVisible(true)\n                getLoadEndView(holder).isVisible(false)\n            }\n            LoadMoreStatus.End -> {\n                getLoadingView(holder).isVisible(false)\n                getLoadComplete(holder).isVisible(false)\n                getLoadFailView(holder).isVisible(false)\n                getLoadEndView(holder).isVisible(true)\n            }\n        }\n    }\n\n    private fun View.isVisible(visible: Boolean) {\n        this.visibility = if (visible) {\n            View.VISIBLE\n        } else {\n            View.GONE\n        }\n    }\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/loadmore/SimpleLoadMoreView.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.loadmore\n\nimport android.view.View\nimport android.view.ViewGroup\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\nimport com.didichuxing.doraemonkit.widget.brvah.util.getItemView\n\nclass SimpleLoadMoreView : BaseLoadMoreView() {\n\n    override fun getRootView(parent: ViewGroup): View =\n            parent.getItemView(R.layout.dk_brvah_quick_view_load_more)\n\n    override fun getLoadingView(holder: BaseViewHolder): View =\n            holder.getView(R.id.load_more_loading_view)\n\n    override fun getLoadComplete(holder: BaseViewHolder): View =\n            holder.getView(R.id.load_more_load_complete_view)\n\n    override fun getLoadEndView(holder: BaseViewHolder): View =\n            holder.getView(R.id.load_more_load_end_view)\n\n    override fun getLoadFailView(holder: BaseViewHolder): View =\n            holder.getView(R.id.load_more_load_fail_view)\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/module/DraggableModule.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.module\n\nimport android.graphics.Canvas\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.View.OnLongClickListener\nimport android.view.View.OnTouchListener\nimport androidx.annotation.NonNull\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.RecyclerView\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter\nimport com.didichuxing.doraemonkit.widget.brvah.dragswipe.DragAndSwipeCallback\nimport com.didichuxing.doraemonkit.widget.brvah.listener.DraggableListenerImp\nimport com.didichuxing.doraemonkit.widget.brvah.listener.OnItemDragListener\nimport com.didichuxing.doraemonkit.widget.brvah.listener.OnItemSwipeListener\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\nimport java.util.*\n\n/**\n * @author: limuyang\n * @date: 2019-12-05\n * @Description:\n */\n\n/**\n * 需要【拖拽】功能的，[BaseQuickAdapter]继承此接口\n */\ninterface DraggableModule\n\nopen class BaseDraggableModule(private val baseQuickAdapter: BaseQuickAdapter<*, *>) : DraggableListenerImp {\n\n    var isDragEnabled = false\n    var isSwipeEnabled = false\n    var toggleViewId = NO_TOGGLE_VIEW\n    lateinit var itemTouchHelper: ItemTouchHelper\n    lateinit var itemTouchHelperCallback: DragAndSwipeCallback\n\n    protected var mOnToggleViewTouchListener: OnTouchListener? = null\n    protected var mOnToggleViewLongClickListener: OnLongClickListener? = null\n    protected var mOnItemDragListener: OnItemDragListener? = null\n    protected var mOnItemSwipeListener: OnItemSwipeListener? = null\n\n    init {\n        initItemTouch()\n    }\n\n    private fun initItemTouch() {\n        itemTouchHelperCallback = DragAndSwipeCallback(this)\n        itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback)\n    }\n\n    internal fun initView(holder: BaseViewHolder) {\n        if (isDragEnabled) {\n            if (hasToggleView()) {\n                val toggleView = holder.itemView.findViewById<View>(toggleViewId)\n                if (toggleView != null) {\n                    toggleView.setTag(R.id.dokit_baseQuickAdapter_viewholder_support, holder)\n                    if (isDragOnLongPressEnabled) {\n                        toggleView.setOnLongClickListener(mOnToggleViewLongClickListener)\n                    } else {\n                        toggleView.setOnTouchListener(mOnToggleViewTouchListener)\n                    }\n                }\n            }\n        }\n    }\n\n\n    fun attachToRecyclerView(recyclerView: RecyclerView) {\n        itemTouchHelper.attachToRecyclerView(recyclerView)\n    }\n\n    /**\n     * Is there a toggle view which will trigger drag event.\n     */\n    open fun hasToggleView(): Boolean {\n        return toggleViewId != NO_TOGGLE_VIEW\n    }\n\n    /**\n     * Set the drag event should be trigger on long press.\n     * Work when the toggleViewId has been set.\n     *\n     */\n    open var isDragOnLongPressEnabled = true\n        set(value) {\n            field = value\n            if (value) {\n                mOnToggleViewTouchListener = null\n                mOnToggleViewLongClickListener = OnLongClickListener { v ->\n                    if (isDragEnabled) {\n                        itemTouchHelper.startDrag(v.getTag(R.id.dokit_baseQuickAdapter_viewholder_support) as RecyclerView.ViewHolder)\n                    }\n                    true\n                }\n            } else {\n                mOnToggleViewTouchListener = OnTouchListener { v, event ->\n                    if (event.action == MotionEvent.ACTION_DOWN && !isDragOnLongPressEnabled) {\n                        if (isDragEnabled) {\n                            itemTouchHelper.startDrag(v.getTag(R.id.dokit_baseQuickAdapter_viewholder_support) as RecyclerView.ViewHolder)\n                        }\n                        true\n                    } else {\n                        false\n                    }\n                }\n                mOnToggleViewLongClickListener = null\n            }\n        }\n\n\n    protected fun getViewHolderPosition(viewHolder: RecyclerView.ViewHolder): Int {\n        return viewHolder.adapterPosition - baseQuickAdapter.headerLayoutCount\n    }\n\n    /************************* Drag *************************/\n\n    open fun onItemDragStart(viewHolder: RecyclerView.ViewHolder) {\n        mOnItemDragListener?.onItemDragStart(viewHolder, getViewHolderPosition(viewHolder))\n    }\n\n    open fun onItemDragMoving(source: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder) {\n        val from = getViewHolderPosition(source)\n        val to = getViewHolderPosition(target)\n        if (inRange(from) && inRange(to)) {\n            if (from < to) {\n                for (i in from until to) {\n                    Collections.swap(baseQuickAdapter.data, i, i + 1)\n                }\n            } else {\n                for (i in from downTo to + 1) {\n                    Collections.swap(baseQuickAdapter.data, i, i - 1)\n                }\n            }\n            baseQuickAdapter.notifyItemMoved(source.adapterPosition, target.adapterPosition)\n        }\n        mOnItemDragListener?.onItemDragMoving(source, from, target, to)\n    }\n\n    open fun onItemDragEnd(viewHolder: RecyclerView.ViewHolder) {\n        mOnItemDragListener?.onItemDragEnd(viewHolder, getViewHolderPosition(viewHolder))\n    }\n\n    open fun canDropOver(@NonNull recyclerView: RecyclerView, @NonNull current: RecyclerView.ViewHolder, @NonNull target: RecyclerView.ViewHolder): Boolean {\n        return mOnItemDragListener?.canDropOver(recyclerView, current, target)!!\n    }\n\n\n    /************************* Swipe *************************/\n\n    open fun onItemSwipeStart(viewHolder: RecyclerView.ViewHolder) {\n        if (isSwipeEnabled) {\n            mOnItemSwipeListener?.onItemSwipeStart(viewHolder, getViewHolderPosition(viewHolder))\n        }\n    }\n\n    open fun onItemSwipeClear(viewHolder: RecyclerView.ViewHolder) {\n        if (isSwipeEnabled) {\n            mOnItemSwipeListener?.clearView(viewHolder, getViewHolderPosition(viewHolder))\n        }\n    }\n\n    open fun onItemSwiped(viewHolder: RecyclerView.ViewHolder) {\n        val pos = getViewHolderPosition(viewHolder)\n        if (inRange(pos)) {\n            baseQuickAdapter.data.removeAt(pos)\n            baseQuickAdapter.notifyItemRemoved(viewHolder.adapterPosition)\n            if (isSwipeEnabled) {\n                mOnItemSwipeListener?.onItemSwiped(viewHolder, pos)\n            }\n        }\n    }\n\n    open fun onItemSwiping(canvas: Canvas?, viewHolder: RecyclerView.ViewHolder?, dX: Float, dY: Float, isCurrentlyActive: Boolean) {\n        if (isSwipeEnabled) {\n            mOnItemSwipeListener?.onItemSwipeMoving(canvas, viewHolder, dX, dY, isCurrentlyActive)\n        }\n    }\n\n    private fun inRange(position: Int): Boolean {\n        return position >= 0 && position < baseQuickAdapter.data.size\n    }\n\n    /**\n     * 设置监听\n     * @param onItemDragListener OnItemDragListener?\n     */\n    override fun setOnItemDragListener(onItemDragListener: OnItemDragListener?) {\n        this.mOnItemDragListener = onItemDragListener\n    }\n\n    override fun setOnItemSwipeListener(onItemSwipeListener: OnItemSwipeListener?) {\n        this.mOnItemSwipeListener = onItemSwipeListener\n    }\n\n    companion object {\n        private const val NO_TOGGLE_VIEW = 0\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/module/LoadMoreModule.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.module\n\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.StaggeredGridLayoutManager\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter\nimport com.didichuxing.doraemonkit.widget.brvah.listener.LoadMoreListenerImp\nimport com.didichuxing.doraemonkit.widget.brvah.listener.OnLoadMoreListener\nimport com.didichuxing.doraemonkit.widget.brvah.loadmore.BaseLoadMoreView\nimport com.didichuxing.doraemonkit.widget.brvah.loadmore.LoadMoreStatus\nimport com.didichuxing.doraemonkit.widget.brvah.loadmore.SimpleLoadMoreView\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n\n/**\n * @author: limuyang\n * @date: 2019-11-29\n * @Description: 向下加载更多\n */\n\n/**\n * 需要【向下加载更多】功能的，[BaseQuickAdapter]继承此接口\n */\ninterface LoadMoreModule\n\nobject LoadMoreModuleConfig {\n\n    /**\n     * 设置全局的LodeMoreView\n     */\n    @JvmStatic\n    var defLoadMoreView: BaseLoadMoreView = SimpleLoadMoreView()\n}\n\n/**\n * 加载更多基类\n */\nopen class BaseLoadMoreModule(private val baseQuickAdapter: BaseQuickAdapter<*, *>) : LoadMoreListenerImp {\n\n    private var mLoadMoreListener: OnLoadMoreListener? = null\n    /** 不满一屏时，是否可以继续加载的标记位 */\n    private var mNextLoadEnable = true\n\n    var loadMoreStatus = LoadMoreStatus.Complete\n        private set\n\n    var isLoadEndMoreGone: Boolean = false\n        private set\n\n    /** 设置加载更多布局 */\n    var loadMoreView = LoadMoreModuleConfig.defLoadMoreView\n    /** 加载完成后是否允许点击 */\n    var enableLoadMoreEndClick = false\n    /** 是否打开自动加载更多 */\n    var isAutoLoadMore = true\n    /** 当自动加载开启，同时数据不满一屏时，是否继续执行自动加载更多 */\n    var isEnableLoadMoreIfNotFullPage = true\n    /**\n     * 预加载\n     */\n    var preLoadNumber = 1\n        set(value) {\n            if (value > 1) {\n                field = value\n            }\n        }\n    /**\n     * 是否加载中\n     */\n    val isLoading: Boolean\n        get() {\n            return loadMoreStatus == LoadMoreStatus.Loading\n        }\n\n    /**\n     * Gets to load more locations\n     *\n     * @return\n     */\n    val loadMoreViewPosition: Int\n        get() {\n            if (baseQuickAdapter.hasEmptyView()) {\n                return -1\n            }\n            return baseQuickAdapter.let {\n                it.headerLayoutCount + it.data.size + it.footerLayoutCount\n            }\n        }\n\n    /**\n     * 是否打开加载更多\n     */\n    var isEnableLoadMore = false\n        set(value) {\n            val oldHasLoadMore = hasLoadMoreView()\n            field = value\n            val newHasLoadMore = hasLoadMoreView()\n\n            if (oldHasLoadMore) {\n                if (!newHasLoadMore) {\n                    baseQuickAdapter.notifyItemRemoved(loadMoreViewPosition)\n                }\n            } else {\n                if (newHasLoadMore) {\n                    loadMoreStatus = LoadMoreStatus.Complete\n                    baseQuickAdapter.notifyItemInserted(loadMoreViewPosition)\n                }\n            }\n        }\n\n\n    internal fun setupViewHolder(viewHolder: BaseViewHolder) {\n        viewHolder.itemView.setOnClickListener {\n            if (loadMoreStatus == LoadMoreStatus.Fail) {\n                loadMoreToLoading()\n            } else if (loadMoreStatus == LoadMoreStatus.Complete) {\n                loadMoreToLoading()\n            } else if (enableLoadMoreEndClick && loadMoreStatus == LoadMoreStatus.End) {\n                loadMoreToLoading()\n            }\n        }\n    }\n\n    /**\n     * The notification starts the callback and loads more\n     */\n    fun loadMoreToLoading() {\n        if (loadMoreStatus == LoadMoreStatus.Loading) {\n            return\n        }\n        loadMoreStatus = LoadMoreStatus.Loading\n        baseQuickAdapter.notifyItemChanged(loadMoreViewPosition)\n        invokeLoadMoreListener()\n    }\n\n\n    fun hasLoadMoreView(): Boolean {\n        if (mLoadMoreListener == null || !isEnableLoadMore) {\n            return false\n        }\n        if (loadMoreStatus == LoadMoreStatus.End && isLoadEndMoreGone) {\n            return false\n        }\n        return baseQuickAdapter.data.isNotEmpty()\n    }\n\n    /**\n     * 自动加载数据\n     * @param position Int\n     */\n    internal fun autoLoadMore(position: Int) {\n        if (!isAutoLoadMore) {\n            //如果不需要自动加载更多，直接返回\n            return\n        }\n        if (!hasLoadMoreView()) {\n            return\n        }\n        if (position < baseQuickAdapter.itemCount - preLoadNumber) {\n            return\n        }\n        if (loadMoreStatus != LoadMoreStatus.Complete) {\n            return\n        }\n        if (loadMoreStatus == LoadMoreStatus.Loading) {\n            return\n        }\n        if (!mNextLoadEnable) {\n            return\n        }\n\n        invokeLoadMoreListener()\n    }\n\n    /**\n     * 触发加载更多监听\n     */\n    private fun invokeLoadMoreListener() {\n        loadMoreStatus = LoadMoreStatus.Loading\n        baseQuickAdapter.weakRecyclerView.get()?.let {\n            it.post { mLoadMoreListener?.onLoadMore() }\n        } ?: mLoadMoreListener?.onLoadMore()\n    }\n\n    /**\n     * check if full page after [BaseQuickAdapter.setNewInstance] [BaseQuickAdapter.setList],\n     * if full, it will enable load more again.\n     *\n     * 用来检查数据是否满一屏，如果满足条件，再开启\n     *\n     */\n    fun checkDisableLoadMoreIfNotFullPage() {\n        if (isEnableLoadMoreIfNotFullPage) {\n            return\n        }\n        // 先把标记位设置为false\n        mNextLoadEnable = false\n        val recyclerView = baseQuickAdapter.weakRecyclerView.get() ?: return\n        val manager = recyclerView.layoutManager ?: return\n        if (manager is LinearLayoutManager) {\n            recyclerView.postDelayed({\n                if (isFullScreen(manager)) {\n                    mNextLoadEnable = true\n                }\n            }, 50)\n        } else if (manager is StaggeredGridLayoutManager) {\n            recyclerView.postDelayed({\n                val positions = IntArray(manager.spanCount)\n                manager.findLastCompletelyVisibleItemPositions(positions)\n                val pos = getTheBiggestNumber(positions) + 1\n                if (pos != baseQuickAdapter.itemCount) {\n                    mNextLoadEnable = true\n                }\n            }, 50)\n        }\n    }\n\n    private fun isFullScreen(llm: LinearLayoutManager): Boolean {\n        return (llm.findLastCompletelyVisibleItemPosition() + 1) != baseQuickAdapter.itemCount ||\n                llm.findFirstCompletelyVisibleItemPosition() != 0\n    }\n\n    private fun getTheBiggestNumber(numbers: IntArray?): Int {\n        var tmp = -1\n        if (numbers == null || numbers.isEmpty()) {\n            return tmp\n        }\n        for (num in numbers) {\n            if (num > tmp) {\n                tmp = num\n            }\n        }\n        return tmp\n    }\n\n    /**\n     * Refresh end, no more data\n     *\n     * @param gone if true gone the load more view\n     */\n    @JvmOverloads\n    fun loadMoreEnd(gone: Boolean = false) {\n        if (!hasLoadMoreView()) {\n            return\n        }\n//        mNextLoadEnable = false\n        isLoadEndMoreGone = gone\n\n        loadMoreStatus = LoadMoreStatus.End\n\n        if (gone) {\n            baseQuickAdapter.notifyItemRemoved(loadMoreViewPosition)\n        } else {\n            baseQuickAdapter.notifyItemChanged(loadMoreViewPosition)\n        }\n    }\n\n    /**\n     * Refresh complete\n     */\n    fun loadMoreComplete() {\n        if (!hasLoadMoreView()) {\n            return\n        }\n//        mNextLoadEnable = true\n        loadMoreStatus = LoadMoreStatus.Complete\n\n        baseQuickAdapter.notifyItemChanged(loadMoreViewPosition)\n\n        checkDisableLoadMoreIfNotFullPage()\n    }\n\n    /**\n     * Refresh failed\n     */\n    fun loadMoreFail() {\n        if (!hasLoadMoreView()) {\n            return\n        }\n        loadMoreStatus = LoadMoreStatus.Fail\n        baseQuickAdapter.notifyItemChanged(loadMoreViewPosition)\n    }\n\n    /**\n     * 设置加载监听事件\n     * @param listener OnLoadMoreListener?\n     */\n    override fun setOnLoadMoreListener(listener: OnLoadMoreListener?) {\n        this.mLoadMoreListener = listener\n        isEnableLoadMore = true\n    }\n\n    /**\n     * 重置状态\n     */\n    internal fun reset() {\n        if (mLoadMoreListener != null) {\n            isEnableLoadMore = true\n            loadMoreStatus = LoadMoreStatus.Complete\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/module/UpFetchModule.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.module\n\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter\nimport com.didichuxing.doraemonkit.widget.brvah.listener.OnUpFetchListener\nimport com.didichuxing.doraemonkit.widget.brvah.listener.UpFetchListenerImp\n\n/**\n * @author: limuyang\n * @date: 2019-11-29\n * @Description: 向上加载\n */\n\n/**\n * 需要【向上加载更多】功能的，[BaseQuickAdapter]继承此接口\n */\ninterface UpFetchModule\n\nopen class BaseUpFetchModule(private val baseQuickAdapter: BaseQuickAdapter<*, *>) : UpFetchListenerImp {\n\n    private var mUpFetchListener: OnUpFetchListener? = null\n\n    var isUpFetchEnable = false\n    var isUpFetching = false\n    /**\n     * start up fetch position, default is 1.\n     */\n    var startUpFetchPosition = 1\n\n    internal fun autoUpFetch(position: Int) {\n        if (!isUpFetchEnable || isUpFetching) {\n            return\n        }\n        if (position <= startUpFetchPosition) {\n            mUpFetchListener?.onUpFetch()\n        }\n    }\n\n    override fun setOnUpFetchListener(listener: OnUpFetchListener?) {\n        this.mUpFetchListener = listener\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/provider/BaseItemProvider.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.provider\n\nimport android.content.Context\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.IdRes\nimport androidx.annotation.LayoutRes\nimport com.didichuxing.doraemonkit.widget.brvah.BaseProviderMultiAdapter\nimport com.didichuxing.doraemonkit.widget.brvah.util.getItemView\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\nimport java.lang.ref.WeakReference\n\n/**\n * [BaseProviderMultiAdapter] 的Provider基类\n * @param T 数据类型\n */\nabstract class BaseItemProvider<T> {\n\n    lateinit var context: Context\n\n    private var weakAdapter: WeakReference<BaseProviderMultiAdapter<T>>? = null\n    private val clickViewIds by lazy(LazyThreadSafetyMode.NONE) { ArrayList<Int>() }\n    private val longClickViewIds by lazy(LazyThreadSafetyMode.NONE) { ArrayList<Int>() }\n\n    internal fun setAdapter(adapter: BaseProviderMultiAdapter<T>) {\n        weakAdapter = WeakReference(adapter)\n    }\n\n    open fun getAdapter(): BaseProviderMultiAdapter<T>? {\n        return weakAdapter?.get()\n    }\n\n    abstract val itemViewType: Int\n\n    abstract val layoutId: Int\n        @LayoutRes\n        get\n\n    abstract fun convert(helper: BaseViewHolder, item: T)\n\n    open fun convert(helper: BaseViewHolder, item: T, payloads: List<Any>) {}\n\n    /**\n     * （可选重写）创建 ViewHolder。\n     * 默认实现返回[BaseViewHolder]，可重写返回自定义 ViewHolder\n     *\n     * @param parent\n     */\n    open fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {\n        return BaseViewHolder(parent.getItemView(layoutId))\n    }\n\n    /**\n     * （可选重写）ViewHolder创建完毕以后的回掉方法。\n     * @param viewHolder VH\n     */\n    open fun onViewHolderCreated(viewHolder: BaseViewHolder, viewType: Int) {}\n\n    /**\n     * item 若想实现条目点击事件则重写该方法\n     * @param helper VH\n     * @param data T\n     * @param position Int\n     */\n    open fun onClick(helper: BaseViewHolder, view: View, data: T, position: Int) {}\n\n    /**\n     * item 若想实现条目长按事件则重写该方法\n     * @param helper VH\n     * @param data T\n     * @param position Int\n     * @return Boolean\n     */\n    open fun onLongClick(helper: BaseViewHolder, view: View, data: T, position: Int): Boolean {\n        return false\n    }\n\n    open fun onChildClick(helper: BaseViewHolder, view: View, data: T, position: Int) {}\n\n    open fun onChildLongClick(helper: BaseViewHolder, view: View, data: T, position: Int): Boolean {\n        return false\n    }\n\n    fun addChildClickViewIds(@IdRes vararg ids: Int) {\n        ids.forEach {\n            this.clickViewIds.add(it)\n        }\n    }\n\n    fun getChildClickViewIds() = this.clickViewIds\n\n    fun addChildLongClickViewIds(@IdRes vararg ids: Int) {\n        ids.forEach {\n            this.longClickViewIds.add(it)\n        }\n    }\n\n    fun getChildLongClickViewIds() = this.longClickViewIds\n\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/provider/BaseNodeProvider.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.provider\n\nimport com.didichuxing.doraemonkit.widget.brvah.BaseNodeAdapter\nimport com.didichuxing.doraemonkit.widget.brvah.entity.node.BaseNode\n\nabstract class BaseNodeProvider : BaseItemProvider<BaseNode>() {\n\n    override fun getAdapter(): BaseNodeAdapter? {\n        return super.getAdapter() as? BaseNodeAdapter\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/util/AdapterUtils.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.util\n\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.LayoutRes\n\n/**\n * 扩展方法，用于获取View\n * @receiver ViewGroup parent\n * @param layoutResId Int\n * @return View\n */\nfun ViewGroup.getItemView(@LayoutRes layoutResId: Int): View {\n    return LayoutInflater.from(this.context).inflate(layoutResId, this, false)\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/viewholder/BaseDataBindingHolder.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.viewholder\n\nimport android.view.View\nimport androidx.databinding.DataBindingUtil\nimport androidx.databinding.ViewDataBinding\n\n/**\n * 方便 DataBinding 的使用\n *\n * @param BD : ViewDataBinding\n * @property dataBinding BD?\n * @constructor\n */\nopen class BaseDataBindingHolder<BD : ViewDataBinding>(view: View) : BaseViewHolder(view) {\n\n    val dataBinding = DataBindingUtil.bind<BD>(view)\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/brvah/viewholder/BaseViewHolder.kt",
    "content": "package com.didichuxing.doraemonkit.widget.brvah.viewholder\n\nimport android.graphics.Bitmap\nimport android.graphics.drawable.Drawable\nimport android.util.SparseArray\nimport android.view.View\nimport android.widget.ImageView\nimport android.widget.TextView\nimport androidx.annotation.*\nimport androidx.databinding.DataBindingUtil\nimport androidx.databinding.ViewDataBinding\nimport androidx.recyclerview.widget.RecyclerView\n\n/**\n * ViewHolder 基类\n */\n@Keep\nopen class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) {\n    /**\n     * Views indexed with their IDs\n     */\n    private val views: SparseArray<View> = SparseArray()\n\n    /**\n     * 如果使用了 DataBinding 绑定 View，可调用此方法获取 [ViewDataBinding]\n     *\n     * Deprecated, Please use [BaseDataBindingHolder]\n     *\n     * @return B?\n     */\n    @Deprecated(\"Please use BaseDataBindingHolder class\", ReplaceWith(\"DataBindingUtil.getBinding(itemView)\", \"androidx.databinding.DataBindingUtil\"))\n    open fun <B : ViewDataBinding> getBinding(): B? = DataBindingUtil.getBinding(itemView)\n\n\n    fun <T : View> getView(@IdRes viewId: Int): T {\n        val view = getViewOrNull<T>(viewId)\n        checkNotNull(view) { \"No view found with id $viewId\" }\n        return view\n    }\n\n    @Suppress(\"UNCHECKED_CAST\")\n    fun <T : View> getViewOrNull(@IdRes viewId: Int): T? {\n        val view = views.get(viewId)\n        if (view == null) {\n            itemView.findViewById<T>(viewId)?.let {\n                views.put(viewId, it)\n                return it\n            }\n        }\n        return view as? T\n    }\n\n    fun <T : View> Int.findView(): T? {\n        return itemView.findViewById(this)\n    }\n\n    open fun setText(@IdRes viewId: Int, value: CharSequence?): BaseViewHolder {\n        getView<TextView>(viewId).text = value\n        return this\n    }\n\n    open fun setText(@IdRes viewId: Int, @StringRes strId: Int): BaseViewHolder? {\n        getView<TextView>(viewId).setText(strId)\n        return this\n    }\n\n    open fun setTextColor(@IdRes viewId: Int, @ColorInt color: Int): BaseViewHolder {\n        getView<TextView>(viewId).setTextColor(color)\n        return this\n    }\n\n    open fun setTextColorRes(@IdRes viewId: Int, @ColorRes colorRes: Int): BaseViewHolder {\n        getView<TextView>(viewId).setTextColor(itemView.resources.getColor(colorRes))\n        return this\n    }\n\n    open fun setImageResource(@IdRes viewId: Int, @DrawableRes imageResId: Int): BaseViewHolder {\n        getView<ImageView>(viewId).setImageResource(imageResId)\n        return this\n    }\n\n    open fun setImageDrawable(@IdRes viewId: Int, drawable: Drawable?): BaseViewHolder {\n        getView<ImageView>(viewId).setImageDrawable(drawable)\n        return this\n    }\n\n    open fun setImageBitmap(@IdRes viewId: Int, bitmap: Bitmap?): BaseViewHolder {\n        getView<ImageView>(viewId).setImageBitmap(bitmap)\n        return this\n    }\n\n    open fun setBackgroundColor(@IdRes viewId: Int, @ColorInt color: Int): BaseViewHolder {\n        getView<View>(viewId).setBackgroundColor(color)\n        return this\n    }\n\n    open fun setBackgroundResource(@IdRes viewId: Int, @DrawableRes backgroundRes: Int): BaseViewHolder {\n        getView<View>(viewId).setBackgroundResource(backgroundRes)\n        return this\n    }\n\n    open fun setVisible(@IdRes viewId: Int, isVisible: Boolean): BaseViewHolder {\n        val view = getView<View>(viewId)\n        view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE\n        return this\n    }\n\n    open fun setGone(@IdRes viewId: Int, isGone: Boolean): BaseViewHolder {\n        val view = getView<View>(viewId)\n        view.visibility = if (isGone) View.GONE else View.VISIBLE\n        return this\n    }\n\n    open fun setEnabled(@IdRes viewId: Int, isEnabled: Boolean): BaseViewHolder {\n        getView<View>(viewId).isEnabled = isEnabled\n        return this\n    }\n\n\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/chart/BarChart.java",
    "content": "package com.didichuxing.doraemonkit.widget.chart;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.LinearLayout;\nimport android.widget.RelativeLayout;\nimport android.widget.TextView;\n\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.R;\n\n/**\n * @desc: 条形图\n */\npublic class BarChart extends LinearLayout {\n\n    private TextView markFirst;\n    private TextView markSecond;\n    private TextView markThird;\n    private View getValue;\n    private View postValue;\n    private View lineEnd;\n\n    public BarChart(Context context) {\n        super(context, null);\n    }\n\n\n    public BarChart(Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        initView();\n    }\n\n    public BarChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        initView();\n    }\n\n    private void initView() {\n        View inflate = LayoutInflater.from(getContext()).inflate(R.layout.dk_item_bar_chart, this, true);\n        markFirst = inflate.findViewById(R.id.mark_first);\n        markSecond = inflate.findViewById(R.id.mark_second);\n        markThird = inflate.findViewById(R.id.mark_third);\n        postValue = findViewById(R.id.post_value);\n        getValue = findViewById(R.id.get_value);\n        lineEnd = findViewById(R.id.solid_line_end);\n\n    }\n\n    public void setData(int postNumber, @ColorInt int postColor, int getNumber, @ColorInt int getColor) {\n        int max = postNumber > getNumber ? postNumber : getNumber;\n        max = max + 10 - (max % 10);\n        float proportion = calculationProportion(max);\n        markFirst.setText(\"0\");\n        markThird.setText(String.valueOf(max));\n        markSecond.setText(String.valueOf(max / 2));\n\n        postValue.setBackgroundColor(postColor);\n        ViewGroup.LayoutParams layoutParams = postValue.getLayoutParams();\n        layoutParams.width = (int) (proportion * postNumber);\n        postValue.setLayoutParams(layoutParams);\n\n        layoutParams = getValue.getLayoutParams();\n        layoutParams.width = (int) (proportion * getNumber);\n        getValue.setBackgroundColor(getColor);\n    }\n\n    private float calculationProportion(float max) {\n        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) lineEnd.getLayoutParams();\n        return layoutParams.leftMargin / max;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/chart/PieChart.java",
    "content": "package com.didichuxing.doraemonkit.widget.chart;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.RectF;\nimport android.util.AttributeSet;\nimport android.view.View;\n\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\nimport java.util.List;\n\n/**\n * @desc: 流量监控圆环view, 使用drawPath方式实现了等间空隙. 调用方式：{@link PieChart#setData(List)}}\n */\npublic class PieChart extends View {\n    private final static float DEFAULT_RING_WIDTH = 19.5f;\n    private final static float DEFAULT_SLICE_SPACE = 2;\n    private Paint mRenderPaint;\n    protected Paint mTransparentCirclePaint;\n\n    /**\n     * View自身的宽和高\n     */\n    private int mHeight;\n    private int mWidth;\n\n    /**\n     * 圆环宽度\n     */\n    private float mRingWidth;\n    private float mSliceSpace;\n    private List<PieData> mPieData;\n    private Path mPath = new Path();\n\n    private float FDEG2RAD = ((float) Math.PI / 180.f);\n    private double DEG2RAD = (Math.PI / 180.0);\n\n    public PieChart(Context context) {\n        super(context);\n        setRingWidth(DEFAULT_RING_WIDTH);\n        setSliceSpace(DEFAULT_SLICE_SPACE);\n        //初始化画笔\n        initPaint();\n    }\n\n    public PieChart(Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        setRingWidth(DEFAULT_RING_WIDTH);\n        setSliceSpace(DEFAULT_SLICE_SPACE);\n        //初始化画笔\n        initPaint();\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        mWidth = w;\n        mHeight = h;\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        if (mPieData == null) {\n            return;\n        }\n        //画圆形\n        drawPie(canvas);\n        //画镂空透明区域\n        drawHolo(canvas);\n    }\n\n\n    /**\n     * 初始化画笔\n     */\n    private void initPaint() {\n        mRenderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n        mRenderPaint.setStyle(Paint.Style.FILL);\n\n        mTransparentCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n        mTransparentCirclePaint.setColor(Color.WHITE);\n        mTransparentCirclePaint.setStyle(Paint.Style.FILL);\n    }\n\n\n    /**\n     * 画圆环\n     *\n     * @param canvas\n     */\n    private void drawPie(Canvas canvas) {\n        RectF rectF = new RectF(0, 0, mWidth, mHeight);\n        Center center = new Center();\n        center.x = mWidth / 2f;\n        center.y = mHeight / 2f;\n        float radius = mWidth / 2f;\n        float sliceSpace = mSliceSpace;\n        final float sliceSpaceAngleOuter = mPieData.size() == 1 ?\n                0.f :\n                sliceSpace / (FDEG2RAD * radius);\n        float sliceAngle;\n        float angle = 0;\n        float startAngleOuter;\n        float sweepAngleOuter;\n        for (int i = 0; i < mPieData.size(); i++) {\n            PieData data = mPieData.get(i);\n            mRenderPaint.setColor(data.color);\n            mPath.reset();\n            sliceAngle = data.angel;\n            startAngleOuter = (angle + sliceSpaceAngleOuter / 2.f) - 90;\n            sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter);\n            if (sweepAngleOuter < 0.f) {\n                sweepAngleOuter = 0.f;\n            }\n            angle += sliceAngle;\n\n            if (sweepAngleOuter >= 360) {\n                mPath.addCircle(center.x, center.y, radius, Path.Direction.CW);\n            } else {\n                mPath.arcTo(rectF, startAngleOuter, sweepAngleOuter);\n                float angleMiddle = startAngleOuter + sweepAngleOuter / 2.f;\n                float arcStartPointX = center.x + radius * (float) Math.cos(startAngleOuter * FDEG2RAD);\n                float arcStartPointY = center.y + radius * (float) Math.sin(startAngleOuter * FDEG2RAD);\n                float sliceSpaceOffset =\n                        calculateMinimumRadiusForSpacedSlice(\n                                center,\n                                radius,\n                                sliceAngle,\n                                arcStartPointX,\n                                arcStartPointY,\n                                startAngleOuter,\n                                sweepAngleOuter);\n\n                float arcEndPointX = center.x +\n                        sliceSpaceOffset * (float) Math.cos(angleMiddle * FDEG2RAD);\n                float arcEndPointY = center.y +\n                        sliceSpaceOffset * (float) Math.sin(angleMiddle * FDEG2RAD);\n\n                mPath.lineTo(\n                        arcEndPointX,\n                        arcEndPointY);\n            }\n\n            canvas.drawPath(mPath, mRenderPaint);\n        }\n    }\n\n\n    private void drawHolo(Canvas canvas) {\n        float holoRadius = mWidth / 2f - mRingWidth;\n        canvas.drawCircle(mWidth / 2f, mHeight / 2f, holoRadius, mTransparentCirclePaint);\n    }\n\n    protected float calculateMinimumRadiusForSpacedSlice(\n            Center center,\n            float radius,\n            float angle,\n            float arcStartPointX,\n            float arcStartPointY,\n            float startAngle,\n            float sweepAngle) {\n        final float angleMiddle = startAngle + sweepAngle / 2.f;\n\n        // Other point of the arc\n        float arcEndPointX = center.x + radius * (float) Math.cos((startAngle + sweepAngle) * FDEG2RAD);\n        float arcEndPointY = center.y + radius * (float) Math.sin((startAngle + sweepAngle) * FDEG2RAD);\n\n        // Middle point on the arc\n        float arcMidPointX = center.x + radius * (float) Math.cos(angleMiddle * FDEG2RAD);\n        float arcMidPointY = center.y + radius * (float) Math.sin(angleMiddle * FDEG2RAD);\n\n        // This is the base of the contained triangle\n        double basePointsDistance = Math.sqrt(\n                Math.pow(arcEndPointX - arcStartPointX, 2) +\n                        Math.pow(arcEndPointY - arcStartPointY, 2));\n\n        // After reducing space from both sides of the \"slice\",\n        //   the angle of the contained triangle should stay the same.\n        // So let's find out the height of that triangle.\n        float containedTriangleHeight = (float) (basePointsDistance / 2.0 *\n                Math.tan((180.0 - angle) / 2.0 * DEG2RAD));\n\n        // Now we subtract that from the radius\n        float spacedRadius = radius - containedTriangleHeight;\n\n        // And now subtract the height of the arc that's between the triangle and the outer circle\n        spacedRadius -= Math.sqrt(\n                Math.pow(arcMidPointX - (arcEndPointX + arcStartPointX) / 2.f, 2) +\n                        Math.pow(arcMidPointY - (arcEndPointY + arcStartPointY) / 2.f, 2));\n\n        return spacedRadius;\n    }\n\n\n    /**\n     * 设置圆环每个item间隔大小，单位dp\n     *\n     * @param space\n     */\n    public void setSliceSpace(float space) {\n        mSliceSpace = UIUtils.dp2px(space);\n    }\n\n    /**\n     * 设置圆环宽度，单位DP\n     *\n     * @param ringWidth\n     */\n    public void setRingWidth(float ringWidth) {\n        mRingWidth = UIUtils.dp2px(ringWidth);\n    }\n\n    /**\n     * 设置圆环数据\n     *\n     * @param pieData\n     */\n    public void setData(List<PieData> pieData) {\n        mPieData = pieData;\n        int weightSum = 0;\n        for (PieData data :\n                pieData) {\n            weightSum += data.weight;\n        }\n        for (PieData data :\n                pieData) {\n            if (weightSum == 0) {\n                data.angel = 360 / pieData.size();\n            } else {\n                data.angel = data.weight * 360f / weightSum;\n            }\n        }\n    }\n\n    public static class PieData {\n        public final int color;\n        /**\n         * 比重，角度根据比重算出,angel=(weight/weightSum)*360\n         */\n        public final long weight;\n        private float angel;\n\n        public PieData(int color, long weight) {\n            this.color = color;\n            this.weight = weight;\n        }\n    }\n\n    private class Center {\n        public float x;\n        public float y;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/dialog/CommonDialogProvider.java",
    "content": "package com.didichuxing.doraemonkit.widget.dialog;\n\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\n\n/**\n * Created by wanglikun on 2019/4/12\n */\npublic class CommonDialogProvider<T extends DialogInfo> extends DialogProvider<T> {\n    private TextView mPositive;\n    private TextView mNegative;\n    private TextView mTitle;\n    private TextView mDesc;\n\n    public CommonDialogProvider(T data, DialogListener listener) {\n        super(data, listener);\n    }\n\n    @Override\n    public int getLayoutId() {\n        return R.layout.dk_dialog_common;\n    }\n\n    @Override\n    protected void findViews(View view) {\n        mPositive = view.findViewById(R.id.positive);\n        mNegative = view.findViewById(R.id.negative);\n        mTitle = view.findViewById(R.id.title);\n        mDesc = view.findViewById(R.id.desc);\n    }\n\n    @Override\n    protected void bindData(T data) {\n        mTitle.setText(data.title);\n        if (TextUtils.isEmpty(data.desc)) {\n            mDesc.setVisibility(View.GONE);\n        } else {\n            mDesc.setVisibility(View.VISIBLE);\n            mDesc.setText(data.desc);\n        }\n    }\n\n    @Override\n    protected View getPositiveView() {\n        return mPositive;\n    }\n\n    @Override\n    protected View getNegativeView() {\n        return mNegative;\n    }\n\n    @Override\n    public boolean isCancellable() {\n        return false;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/dialog/DialogInfo.java",
    "content": "package com.didichuxing.doraemonkit.widget.dialog;\n\n/**\n * Created by wanglikun on 2019/4/12\n */\npublic class DialogInfo {\n    public DialogListener listener;\n\n    public CharSequence title;\n\n    public CharSequence desc;\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/dialog/DialogListener.kt",
    "content": "package com.didichuxing.doraemonkit.widget.dialog\n\n/**\n * Created by wanglikun on 2019/4/12\n */\ninterface DialogListener {\n    fun onPositive(dialogProvider: DialogProvider<*>): Boolean\n    fun onNegative(dialogProvider: DialogProvider<*>): Boolean\n    fun onCancel(dialogProvider: DialogProvider<*>) {}\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/dialog/DialogProvider.java",
    "content": "package com.didichuxing.doraemonkit.widget.dialog;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.fragment.app.DialogFragment;\nimport androidx.fragment.app.FragmentManager;\n\n/**\n * Created by wanglikun on 2019/4/12\n */\npublic abstract class DialogProvider<T> {\n    private T mData;\n    private DialogFragment mHost;\n    private View mView;\n    private DialogListener mDialogListener;\n    private boolean cancellable = true;\n\n    public DialogProvider(T data, DialogListener listener) {\n        this.mDialogListener = listener;\n        this.mData = data;\n    }\n\n    public void setHost(DialogFragment host) {\n        mHost = host;\n    }\n\n    public DialogFragment getHost() {\n        return mHost;\n    }\n\n    public Context getContext() {\n        if (mHost == null) {\n            return null;\n        }\n        return mHost.getContext();\n    }\n\n    public abstract int getLayoutId();\n\n    public final View createView(LayoutInflater inflater, ViewGroup parent) {\n        mView = inflater.inflate(getLayoutId(), parent, false);\n        return mView;\n    }\n\n    public final void onViewCreated(View view) {\n        findViews(view);\n        registerForListeners();\n        bindData(mData);\n    }\n\n\n    protected void bindData(T data) {\n\n    }\n\n    protected abstract void findViews(View view);\n\n    private void registerForListeners() {\n        View positiveView = getPositiveView();\n        if (positiveView != null) {\n            positiveView.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    onPositive();\n                }\n            });\n        }\n        View negativeView = getNegativeView();\n        if (negativeView != null) {\n            negativeView.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    onNegative();\n                }\n            });\n        }\n        final View cancelView = getCancelView();\n        if (cancelView != null) {\n            cancelView.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    cancel();\n                }\n            });\n        }\n    }\n\n    private void onPositive() {\n        boolean dismiss = true;\n        if (mDialogListener != null) {\n            dismiss = mDialogListener.onPositive(this);\n        }\n        if (dismiss) {\n            dismiss();\n        }\n    }\n\n    private void onNegative() {\n        boolean dismiss = true;\n        if (mDialogListener != null) {\n            dismiss = mDialogListener.onNegative(this);\n        }\n        if (dismiss) {\n            dismiss();\n        }\n    }\n\n    public void show(FragmentManager childFragmentManager) {\n        mHost.show(childFragmentManager, null);\n    }\n\n    public void dismiss() {\n        mHost.dismiss();\n    }\n\n    protected void cancel() {\n        dismiss();\n        if (mDialogListener != null) {\n            mDialogListener.onCancel(this);\n        }\n    }\n\n    void onCancel() {\n        if (mDialogListener != null) {\n            mDialogListener.onCancel(this);\n        }\n    }\n\n    public boolean isCancellable() {\n        return cancellable;\n    }\n\n    public void setCancellable(boolean cancellable) {\n        this.cancellable = cancellable;\n    }\n\n    protected View getPositiveView() {\n        return null;\n    }\n\n    protected View getNegativeView() {\n        return null;\n    }\n\n    protected View getCancelView() {\n        return null;\n    }\n\n    public void setDialogListener(DialogListener dialogListener) {\n        mDialogListener = dialogListener;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/dialog/SimpleDialogListener.java",
    "content": "package com.didichuxing.doraemonkit.widget.dialog;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Created by wanglikun on 2019/4/12\n */\npublic class SimpleDialogListener implements DialogListener {\n    @Override\n    public boolean onPositive(@NotNull DialogProvider<?> dialogProvider) {\n        return false;\n    }\n\n    @Override\n    public boolean onNegative(@NotNull DialogProvider<?> dialogProvider) {\n        return false;\n    }\n\n    @Override\n    public void onCancel(@NotNull DialogProvider<?> dialogProvider) {\n\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/dialog/UniversalDialogFragment.java",
    "content": "package com.didichuxing.doraemonkit.widget.dialog;\n\nimport android.graphics.drawable.ColorDrawable;\nimport android.os.Bundle;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.view.WindowManager;\n\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.DialogFragment;\n\nimport com.didichuxing.doraemonkit.R;\n\n/**\n * Created by wanglikun on 2019/4/12\n */\npublic class UniversalDialogFragment extends DialogFragment {\n\n    private DialogProvider mProvider;\n\n    public void setProvider(DialogProvider provider) {\n        mProvider = provider;\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setStyle(androidx.fragment.app.DialogFragment.STYLE_NO_TITLE, 0);\n    }\n\n\n    @Override\n    public int getTheme() {\n        return R.style.DK_Dialog;\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);\n        if (mProvider != null) {\n            setCancelable(mProvider.isCancellable());\n        }\n\n        Window window = getDialog().getWindow();\n        WindowManager.LayoutParams lp = window.getAttributes();\n        lp.gravity = getGravity();\n        lp.width = getWidth();\n        lp.height = getHeight();\n        window.setAttributes(lp);\n\n        return mProvider.createView(inflater, container);\n    }\n\n    @Override\n    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {\n        mProvider.onViewCreated(view);\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        getDialog().getWindow().setLayout(getWidth(), getHeight());\n        getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(getResources().getColor(android.R.color.transparent)));\n    }\n\n    @Override\n    public void onCancel(android.content.DialogInterface dialog) {\n        super.onCancel(dialog);\n        mProvider.onCancel();\n    }\n\n    protected int getGravity() {\n        return Gravity.CENTER;\n    }\n\n    protected int getWidth() {\n        return WindowManager.LayoutParams.WRAP_CONTENT;\n    }\n\n    protected int getHeight() {\n        return WindowManager.LayoutParams.WRAP_CONTENT;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/dropdown/DkDropDownMenu.java",
    "content": "package com.didichuxing.doraemonkit.widget.dropdown;\n\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\n\nimport androidx.annotation.NonNull;\n\nimport android.util.AttributeSet;\nimport android.util.DisplayMetrics;\nimport android.util.TypedValue;\nimport android.view.Gravity;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.animation.AnimationUtils;\nimport android.widget.FrameLayout;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-11-19:17\n * 描    述：下拉菜单\n * 修订历史：\n * ================================================\n */\n\npublic class DkDropDownMenu extends LinearLayout {\n    private static final String TAG = \"DkDropDownMenu\";\n    //记录tabTexts的顺序\n    List<View> dropTabViews = new ArrayList<>();\n    //顶部菜单布局\n    private LinearLayout tabMenuView;\n    //底部容器，包含popupMenuViews，maskView\n    private FrameLayout containerView;\n    //弹出菜单父布局\n    private FrameLayout popupMenuViews;\n    //遮罩半透明View，点击可关闭DkDropDownMenu\n    private View maskView;\n    //tabMenuView里面选中的tab位置，-1表示未选中\n    private int current_tab_position = -1;\n    private float dividerHeight;\n    //分割线颜色\n    private int dividerColor = 0xffcccccc;\n    //tab选中颜色\n    private int textSelectedColor = 0xff890c85;\n    //tab未选中颜色\n    private int textUnselectedColor = 0xff111111;\n    //遮罩颜色\n    private int maskColor = 0x88888888;\n    //tab字体大小\n    private int menuTextSize = 14;\n    //icon的方向\n    private static int iconOrientation = Orientation.right;//默认右则\n    private Orientation mOrientation;\n    //tab选中图标\n    private int menuSelectedIcon;\n    //tab未选中图标\n    private int menuUnselectedIcon;\n\n\n    public DkDropDownMenu(Context context) {\n        super(context, null);\n    }\n\n    public DkDropDownMenu(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public DkDropDownMenu(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n\n        setOrientation(VERTICAL);\n\n        //为DkDropDownMenu添加自定义属性\n        int menuBackgroundColor = 0xffffffff;\n        int underlineColor = 0xffcccccc;\n        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DkDropDownMenu);\n        underlineColor = a.getColor(R.styleable.DkDropDownMenu_dk_ddunderlineColor, underlineColor);\n        dividerColor = a.getColor(R.styleable.DkDropDownMenu_dk_dddividerColor, dividerColor);\n        textSelectedColor = a.getColor(R.styleable.DkDropDownMenu_dk_ddtextSelectedColor,\n                textSelectedColor);\n        textUnselectedColor = a.getColor(R.styleable.DkDropDownMenu_dk_ddtextUnselectedColor,\n                textUnselectedColor);\n        menuBackgroundColor = a.getColor(R.styleable.DkDropDownMenu_dk_ddmenuBackgroundColor,\n                menuBackgroundColor);\n        maskColor = a.getColor(R.styleable.DkDropDownMenu_dk_ddmaskColor, maskColor);\n        menuTextSize = a.getDimensionPixelSize(R.styleable.DkDropDownMenu_dk_ddmenuTextSize,\n                menuTextSize);\n        dividerHeight = a.getDimensionPixelSize(R.styleable.DkDropDownMenu_dk_dddividerHeight, ViewGroup.LayoutParams.MATCH_PARENT);\n        menuSelectedIcon = a.getResourceId(R.styleable.DkDropDownMenu_dk_ddmenuSelectedIcon,\n                menuSelectedIcon);\n        menuUnselectedIcon = a.getResourceId(R.styleable.DkDropDownMenu_dk_ddmenuUnselectedIcon,\n                menuUnselectedIcon);\n        iconOrientation = a.getInt(R.styleable.DkDropDownMenu_dk_ddmenuIconOrientation, iconOrientation);\n        a.recycle();\n        //初始化位置参数\n        mOrientation = new Orientation(getContext());\n        mOrientation.init(iconOrientation, menuSelectedIcon, menuUnselectedIcon);\n\n        //初始化tabMenuView并添加到tabMenuView\n        tabMenuView = new LinearLayout(context);\n        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup\n                .LayoutParams.WRAP_CONTENT);\n        tabMenuView.setOrientation(HORIZONTAL);\n        tabMenuView.setBackgroundColor(menuBackgroundColor);\n        tabMenuView.setLayoutParams(params);\n        addView(tabMenuView, 0);\n\n        //为tabMenuView添加下划线\n        View underLine = new View(getContext());\n        underLine.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,\n                dpTpPx(1.0f)));\n        underLine.setBackgroundColor(underlineColor);\n        addView(underLine, 1);\n\n        //初始化containerView并将其添加到DkDropDownMenu\n        containerView = new FrameLayout(context);\n        containerView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams\n                .MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));\n        addView(containerView, 2);\n\n\n    }\n\n\n    /**\n     * 初始化DkDropDownMenu\n     *\n     * @param tabTexts\n     * @param popupViews\n     * @param contentView\n     */\n    public void setDropDownMenu(@NonNull List<String> tabTexts, @NonNull List<View> popupViews,\n                                @NonNull View contentView) {\n        if (tabTexts.size() != popupViews.size()) {\n            throw new IllegalArgumentException(\"params not match, tabTexts.size() should be equal\" +\n                    \" popupViews.size()\");\n        }\n\n        for (int i = 0; i < tabTexts.size(); i++) {\n            addTab(tabTexts, i);\n        }\n        containerView.addView(contentView, 0);\n\n        maskView = new View(getContext());\n        maskView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams\n                .MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));\n        maskView.setBackgroundColor(maskColor);\n        maskView.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                closeMenu();\n            }\n        });\n        containerView.addView(maskView, 1);\n        maskView.setVisibility(GONE);\n\n        popupMenuViews = new FrameLayout(getContext());\n        popupMenuViews.setVisibility(GONE);\n        containerView.addView(popupMenuViews, 2);\n\n        for (int i = 0; i < popupViews.size(); i++) {\n            if (popupViews.get(i).getLayoutParams() == null) {\n                popupViews.get(i).setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams\n                        .MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));\n            }\n            popupMenuViews.addView(popupViews.get(i), i);\n        }\n\n    }\n\n    /**\n     * 自定义插入的tabView，如果包含R.id.tv_tab就当做普通的tabtext会统一做变色处理和向下的角标处理\n     *\n     * @param tab\n     * @param index 0start\n     */\n    public void addTab(View tab, int index) {\n        if (index == (tabMenuView.getChildCount() + 1) / 2) {\n            addTabEnd(tab);\n            return;\n        }\n        tabMenuView.addView(tab, index * 2);\n        tabMenuView.addView(getDividerView(), index * 2 + 1);\n    }\n\n    public void addTabEnd(View tab) {\n        tabMenuView.addView(getDividerView(), tabMenuView.getChildCount());\n        tabMenuView.addView(tab, tabMenuView.getChildCount());\n    }\n\n    private void addTab(@NonNull List<String> tabTexts, final int i) {\n        final View tab = inflate(getContext(), R.layout.dk_dropdownmenu_tab_item, null);\n        tab.setLayoutParams(new LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1.0f));\n        final TextView textView = getTabTextView(tab);\n        textView.setText(tabTexts.get(i));\n        textView.setTextColor(textUnselectedColor);\n        textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, menuTextSize);\n        setTextDrawables(textView, true);\n        tabMenuView.addView(tab);\n        tab.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (itemMenuClickListener != null) {\n                    itemMenuClickListener.OnItemMenuClick(textView, i);\n                }\n                switchMenu(v);\n            }\n        });\n\n        //添加分割线\n        if (i < tabTexts.size() - 1) {\n            tabMenuView.addView(getDividerView());\n        }\n        dropTabViews.add(tab);\n    }\n\n    private View getDividerView() {\n        View view = new View(getContext());\n        double height = dividerHeight > 0 ? dpTpPx(dividerHeight) : dividerHeight;\n        LayoutParams params = new LayoutParams(dpTpPx(0.5f), (int) height);\n        params.gravity = Gravity.CENTER_VERTICAL;\n        view.setLayoutParams(params);\n        view.setBackgroundColor(dividerColor);\n        return view;\n    }\n\n    /**\n     * 获取tabView中id为tv_tab的textView\n     *\n     * @param tabView\n     * @return\n     */\n    private TextView getTabTextView(View tabView) {\n        TextView tabtext = (TextView) tabView.findViewById(R.id.tv_tab);\n        return tabtext;\n    }\n\n    /**\n     * 改变tab文字\n     *\n     * @param text\n     */\n    public void setTabText(String text) {\n        if (current_tab_position != -1) {\n            getTabTextView(tabMenuView.getChildAt(current_tab_position)).setText(text);\n        }\n    }\n\n    /**\n     * 重置tab状态\n     *\n     * @param tabs\n     */\n    public void resetTabText(String[] tabs) {\n        //int count = tabMenuView.getChildCount();\n        for (int index = 0; index < tabs.length; index++) {\n            TextView tv;\n            int tvIndex;\n            if (index == 0) {\n                tvIndex = 0;\n            } else {\n                tvIndex = index + 1;\n            }\n            tv = getTabTextView(tabMenuView.getChildAt(tvIndex));\n            if (tv != null) {\n                tv.setText(tabs[index]);\n            }\n        }\n    }\n\n    public void setTabClickable(boolean clickable) {\n        for (int i = 0; i < tabMenuView.getChildCount(); i = i + 2) {\n            tabMenuView.getChildAt(i).setClickable(clickable);\n        }\n    }\n\n    /**\n     * 关闭菜单\n     */\n    public void closeMenu() {\n        if (current_tab_position != -1) {\n            TextView textView = getTabTextView(tabMenuView.getChildAt(current_tab_position));\n            textView.setTextColor(textUnselectedColor);\n            setTextDrawables(textView, true);\n            popupMenuViews.setVisibility(View.GONE);\n            popupMenuViews.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim\n                    .dk_dd_menu_out));\n            maskView.setVisibility(GONE);\n            maskView.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.dk_dd_mask_out));\n            current_tab_position = -1;\n        }\n    }\n\n    public boolean isActive() {\n        return current_tab_position != -1;\n    }\n\n    public void setTextDrawables(TextView textview, boolean close) {\n        textview.setCompoundDrawablesWithIntrinsicBounds(mOrientation.getLeft(close), mOrientation.getTop(close),\n                mOrientation.getRight(close), mOrientation.getBottom(close));\n    }\n\n    /**\n     * DkDropDownMenu是否处于可见状态\n     *\n     * @return\n     */\n    public boolean isShowing() {\n        return current_tab_position != -1;\n    }\n\n    /**\n     * 切换菜单\n     *\n     * @param target\n     */\n    private void switchMenu(View target) {\n        //LogHelper.i(TAG, \"current===>\" + current_tab_position);\n        for (int i = 0; i < tabMenuView.getChildCount(); i = i + 2) {\n            if (target == tabMenuView.getChildAt(i)) {\n                if (current_tab_position == i) {\n                    closeMenu();//关闭\n                } else {//打开\n                    if (current_tab_position == -1) {\n                        popupMenuViews.setVisibility(View.VISIBLE);\n                        popupMenuViews.setAnimation(AnimationUtils.loadAnimation(getContext(), R\n                                .anim.dk_dd_menu_in));\n                        maskView.setVisibility(VISIBLE);\n                        maskView.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim\n                                .dk_dd_mask_in));\n                    }\n                    View listView = getListView(tabMenuView.getChildAt(i));\n                    if (listView != null) {\n                        listView.setVisibility(View.VISIBLE);\n                    }\n                    current_tab_position = i;\n                    TextView textView = getTabTextView(tabMenuView.getChildAt(i));\n                    textView.setTextColor(textSelectedColor);\n                    setTextDrawables(textView, false);\n                }\n            } else {//关闭\n                TextView textView = getTabTextView(tabMenuView.getChildAt(i));\n                if (textView != null) {\n                    textView.setTextColor(textUnselectedColor);\n\n                }\n                View listView = getListView(tabMenuView.getChildAt(i));\n                if (listView != null) {\n                    if (textView != null) {\n                        setTextDrawables(textView, true);\n                    }\n                    listView.setVisibility(View.GONE);\n                }\n            }\n        }\n    }\n\n    OnItemMenuClickListener itemMenuClickListener;\n\n    public void setOnItemMenuClickListener(OnItemMenuClickListener listener) {\n        itemMenuClickListener = listener;\n    }\n\n    public interface OnItemMenuClickListener {\n        void OnItemMenuClick(TextView tabView, int position);\n    }\n\n    /**\n     * 获取dropTabViews中对应popupMenuViews数组中的ListView\n     *\n     * @param view\n     * @return\n     */\n    private View getListView(View view) {\n        if (dropTabViews.contains(view)) {\n            int index = dropTabViews.indexOf(view);\n            return popupMenuViews.getChildAt(index);\n        } else {\n            return null;\n        }\n    }\n\n    public int dpTpPx(float value) {\n        DisplayMetrics dm = getResources().getDisplayMetrics();\n        return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, dm) + 0.5);\n    }\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/dropdown/Orientation.java",
    "content": "package com.didichuxing.doraemonkit.widget.dropdown;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-11-19:17\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass Orientation {\n    Context mContext;\n    private Drawable unSelectedDrawable;\n    private Drawable selectedDrawable;\n    private int orientation;\n\n    public Orientation(Context context) {\n        mContext = context;\n    }\n\n    public static final int left = 0;\n    public static final int top = 1;\n    public static final int right = 2;\n    public static final int bottom = 3;\n\n    public Drawable getLeft(boolean close) {\n        return orientation == left ? (close ? unSelectedDrawable : selectedDrawable) : null;\n    }\n\n    public Drawable getTop(boolean close) {\n        return orientation == top ? (close ? unSelectedDrawable : selectedDrawable) : null;\n    }\n\n    public Drawable getRight(boolean close) {\n        return orientation == right ? (close ? unSelectedDrawable : selectedDrawable) : null;\n    }\n\n    public Drawable getBottom(boolean close) {\n        return orientation == bottom ? (close ? unSelectedDrawable : selectedDrawable) : null;\n    }\n\n    /**\n     * 初始化位置参数\n     *\n     * @param orientation\n     * @param menuUnselectedIcon\n     */\n    public void init(int orientation, int menuSelectedIcon, int menuUnselectedIcon) {\n\n        unSelectedDrawable = mContext.getResources().getDrawable(menuUnselectedIcon);\n        selectedDrawable = mContext.getResources().getDrawable(menuSelectedIcon);\n        this.orientation = orientation;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/easyrefresh/ELog.java",
    "content": "package com.didichuxing.doraemonkit.widget.easyrefresh;\n\nimport android.text.TextUtils;\n\n\npublic final class ELog {\n\n    /**\n     * Priority constant for the println method; use Log.v.\n     */\n    public static final int VERBOSE = 2;\n\n    /**\n     * Priority constant for the println method; use Log.d.\n     */\n    public static final int DEBUG = 3;\n\n    /**\n     * Priority constant for the println method; use Log.i.\n     */\n    public static final int INFO = 4;\n\n    /**\n     * Priority constant for the println method; use Log.w.\n     */\n    public static final int WARN = 5;\n\n    /**\n     * Priority constant for the println method; use Log.e.\n     */\n    public static final int ERROR = 6;\n\n    /**\n     * Priority constant for the println method.\n     */\n    public static final int ASSERT = 7;\n    \n    /**\n     * log是否可用\n     */\n    private static boolean isEnabled = true; \n\n\n    private ELog() {\n    }\n    \n    /**\n     * 设置log是否可用\n     * @param enableBoolean\n     */\n    public static void setEnable(boolean enableBoolean) {\n    \tisEnabled  = enableBoolean;\n    }\n    \n    /**\n     * 检查log是否可用\n     * @return boolean isEnabled\n     */\n    public static boolean isEnable() {\n    \treturn isEnabled;\n    }\n    \n    private static void log(LEVEL level, String tag, String msg, Throwable thr) {\n    \tif(!isEnabled ) {\n    \t\treturn ;\n    \t}\n    \t\n    \tlog2console(level, tag, msg, thr);\n    }\n    \n    private static void log2console(LEVEL level, String tag, String msg, Throwable thr) {\n    \tboolean isFilter = false;\n\n        \n        if(!isFilter) {\n        \tswitch (level) {\n            case VERBOSE:\n                if (thr == null) {\n                    android.util.Log.v(tag, msg);\n                } else {\n                \tandroid.util.Log.v(tag, msg, thr);\n                }\n                break;\n            case DEBUG:\n                if (thr == null) {\n                \tandroid.util.Log.d(tag, msg);\n                } else {\n                \tandroid.util.Log.d(tag, msg, thr);\n                }\n                break;\n            case INFO:\n                if (thr == null) {\n                \tandroid.util.Log.i(tag, msg);\n                } else {\n                \tandroid.util.Log.i(tag, msg, thr);\n                }\n                break;\n            case WARN:\n                if (thr == null) {\n                \tandroid.util.Log.w(tag, msg);\n                } else if (TextUtils.isEmpty(msg)) {\n                \tandroid.util.Log.w(tag, thr);\n                } else {\n                \tandroid.util.Log.w(tag, msg, thr);\n                }\n                break;\n            case ERROR:\n                if (thr == null) {\n                \tandroid.util.Log.e(tag, msg);\n                } else {\n                \tandroid.util.Log.e(tag, msg, thr);\n                }\n                break;\n            case ASSERT:\n                if (thr == null) {\n                \tandroid.util.Log.wtf(tag, msg);\n                } else if (TextUtils.isEmpty(msg)) {\n                \tandroid.util.Log.wtf(tag, thr);\n                } else {\n                \tandroid.util.Log.wtf(tag, msg, thr);\n                }\n                break;\n            default:\n            \tbreak;\n        \t}\n        } else {\n        \treturn;\n        }\n    }\n\n    public static void v(String tag, String msg) {\n    \tlog(LEVEL.VERBOSE, tag, msg, null);\n    }\n\n    public static void v(String tag, String msg, Throwable thr) {\n    \tlog(LEVEL.VERBOSE, tag, msg, thr);\n    }\n\n    public static void d(String tag, String msg) {\n    \tlog(LEVEL.DEBUG, tag, msg, null);\n    }\n\n    public static void d(String tag, String msg, Throwable thr) {\n    \tlog(LEVEL.DEBUG, tag, msg, thr);\n    }\n\n    public static void i(String tag, String msg) {\n    \tlog(LEVEL.INFO, tag, msg, null);\n    }\n\n    public static void i(String tag, String msg, Throwable thr) {\n    \tlog(LEVEL.INFO, tag, msg, thr);   \t\n    }\n\n    public static void w(String tag, String msg) {\n    \tlog(LEVEL.WARN, tag, msg, null);   \t\n    }\n\n    public static void w(String tag, String msg, Throwable thr) {\n    \tlog(LEVEL.WARN, tag, msg, thr);  \t\n    }\n\n    public static void w(String tag, Throwable thr) {\n    \tlog(LEVEL.WARN, tag, \"\", thr);      \n    }\n\n    public static void e(String tag, String msg) {\n    \tlog(LEVEL.ERROR, tag, msg, null);      \n    }\n\n    public static void e(String tag, String msg, Throwable thr) {\n    \tlog(LEVEL.ERROR, tag, msg, thr);       \n    }\n    \n    /**\n     * 等级枚举，对应android原生log的等级\n     * @author jjzheng\n     *\n     */\n    public enum LEVEL {\n        VERBOSE(2, \"V\"),\n        DEBUG(3, \"D\"),\n        INFO(4, \"I\"),\n        WARN(5, \"W\"),\n        ERROR(6, \"E\"),\n        ASSERT(7, \"A\");\n\n        final String levelString;\n        final int level;\n\n        //Supress default constructor for noninstantiability\n        private LEVEL() {\n            throw new AssertionError();\n        }\n\n        private LEVEL(int level, String levelString) {\n            this.level = level;\n            this.levelString = levelString;\n        }\n\n        public String getLevelString() {\n            return this.levelString;\n        }\n\n        public int getLevel() {\n            return this.level;\n        }\n    }\n\n\n    \n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/easyrefresh/EasyRefreshLayout.java",
    "content": "package com.didichuxing.doraemonkit.widget.easyrefresh;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimport android.animation.ValueAnimator;\nimport android.content.Context;\nimport android.os.Handler;\n\n\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewConfiguration;\nimport android.view.ViewGroup;\nimport android.widget.AbsListView;\nimport android.widget.Scroller;\n\nimport androidx.core.view.MotionEventCompat;\nimport androidx.core.view.ViewCompat;\nimport androidx.recyclerview.widget.GridLayoutManager;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.recyclerview.widget.StaggeredGridLayoutManager;\n\nimport com.didichuxing.doraemonkit.widget.easyrefresh.exception.ERVHRuntimeException;\nimport com.didichuxing.doraemonkit.widget.easyrefresh.view.SimpleLoadMoreView;\nimport com.didichuxing.doraemonkit.widget.easyrefresh.view.SimpleRefreshHeaderView;\n\n\n/**\n * Created by guanaj on 16/9/2.\n */\npublic class EasyRefreshLayout extends ViewGroup {\n    private static final String TAG = \"EsayRefreshLayout\";\n    private static int SCROLL_TO_REFRESH_DURATION = 250;\n    private static int SCROLL_TO_TOP_DURATION = 800;\n    private static final int INVALID_POINTER = -1;\n    private static final float DRAG_RATE = 1.0f;\n    private static final int START_POSITION = 0;\n    private static long SHOW_COMPLETED_TIME = 500;\n    private static long SCROLL_TO_LOADING_DURATION = 500;\n    private static long SHOW_SCROLL_DOWN_DURATION = 300;\n    private double pull_resistance = 2.0f;\n\n    private State state = State.RESET;\n\n    private boolean isEnablePullToRefresh = true;\n    private boolean isRefreshing;\n\n\n    private int touchSlop;\n    private View refreshHeaderView;\n    private int currentOffsetTop;\n\n    private View contentView;\n    private boolean hasMeasureHeaderView = false;\n    private int headerViewHight;\n    private int totalDragDistance;\n    private int activePointerId;\n    private boolean isTouch;\n    private boolean hasSendCancelEvent;\n    private boolean isBeginDragged;\n    private int lastOffsetTop;\n    private float lastMotionX;\n    private float lastMotionY;\n    private float initDownY;\n    private float initDownX;\n    private MotionEvent lastEvent;\n    private AutoScroll autoScroll;\n    private boolean isAutoRefresh;\n    private EasyEvent easyEvent;\n\n\n    private RecyclerView mRecyclerView;\n    boolean isCanLoad = false;\n    private LayoutInflater mInflater;\n    private boolean isLoading = false;\n    private View mLoadMoreView;\n    private boolean isLoadingFail = false;\n    private float offsetY;\n    private float yDiff;\n    private float mDistance;\n    //开启预加载标识位\n    //1、表示不开启\n    //2、表示开启普通加载\n    //3、表示开启预加载模式\n    private LoadModel loadMoreModel = LoadModel.COMMON_MODEL;\n    //还剩多少个item时触发预加载\n    private int advanceCount = 0;\n\n    public EasyRefreshLayout(Context context) {\n        this(context, null);\n\n    }\n\n    public EasyRefreshLayout(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        initParameter(context, attrs);\n\n    }\n\n    /**\n     * 延迟几秒后恢复原状\n     */\n    private Runnable delayToScrollTopRunnable = new Runnable() {\n        @Override\n        public void run() {\n            autoScroll.scrollTo(START_POSITION, SCROLL_TO_TOP_DURATION);\n        }\n    };\n    /**\n     * 自动变换到刷新状态\n     */\n    private Runnable autoRefreshRunnable = new Runnable() {\n        @Override\n        public void run() {\n            isAutoRefresh = true;\n            changeState(State.PULL);\n            autoScroll.scrollTo(totalDragDistance, SCROLL_TO_REFRESH_DURATION);\n        }\n    };\n    /**\n     * 是否已测量加载更多的view\n     */\n    private boolean hasMeasureLoadMoreView;\n\n    private int loadMoreViewHeight;\n    private boolean isRecycerView;\n    private boolean isNotMoreLoading;\n\n\n    private void initParameter(Context context, AttributeSet attrs) {\n        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();\n        /*init an refreshHeadView*/\n        View refreshHeadView = getDefaultRefreshView();\n        setRefreshHeadView(refreshHeadView);\n        View loadMoreView = getDefaultLoadMoreView();\n        setLoadMoreView(loadMoreView);\n        autoScroll = new AutoScroll();\n    }\n\n    //设置头部视图\n    public void setRefreshHeadView(View headerView) {\n        if (headerView != null && headerView != refreshHeaderView)\n            removeView(refreshHeaderView);\n\n        /*设置默认的布局参数*/\n        LayoutParams layoutParams = headerView.getLayoutParams();\n        if (layoutParams == null) {\n            layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);\n            headerView.setLayoutParams(layoutParams);\n        }\n\n        /*设置为新的headerView*/\n        refreshHeaderView = headerView;\n        addView(refreshHeaderView);\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        //获取父控件高度\n//        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);\n//        System.out.println(\">>>>>parentHeight = \"+parentHeight);\n//        int expandParentHeight = View.MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, View.MeasureSpec.AT_MOST);\n//        super.onMeasure(widthMeasureSpec, expandParentHeight);\n\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n        if (contentView == null) {\n            initContentView();\n        }\n        //没有contentView\n        if (contentView == null)\n            return;\n\n\n\n        /*测量填充主要内容的View*/\n        /*让contentView占满整个屏幕*/\n        int contentViewWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();\n        int contentViewHight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();\n        contentView.measure(MeasureSpec.makeMeasureSpec(contentViewWidth, MeasureSpec.EXACTLY),\n                MeasureSpec.makeMeasureSpec(contentViewHight, MeasureSpec.EXACTLY));\n\n\n        /*测量headerView*/\n\n        measureChild(refreshHeaderView, widthMeasureSpec, heightMeasureSpec);\n        if (!hasMeasureHeaderView) {\n            /*headerView还没有被测量*/\n            hasMeasureHeaderView = true;\n            //获取测量的高度\n            headerViewHight = refreshHeaderView.getMeasuredHeight();\n            totalDragDistance = headerViewHight;\n        }\n\n\n        /*测量loadMoreView*/\n        measureChild(mLoadMoreView, widthMeasureSpec, heightMeasureSpec);\n        if (!hasMeasureLoadMoreView) {\n            /*headerView还没有被测量*/\n            hasMeasureLoadMoreView = true;\n            //获取测量的高度\n            loadMoreViewHeight = mLoadMoreView.getMeasuredHeight();\n        }\n    }\n\n    private void initContentView() {\n        // Don't bother getting the parent height if the parent hasn't been laid\n        // out yet.\n        if (contentView == null) {\n            for (int i = 0; i < getChildCount(); i++) {\n                View child = getChildAt(i);\n                if (!child.equals(refreshHeaderView) && !child.equals(mLoadMoreView)) {\n                    contentView = child;\n                    if (contentView instanceof RecyclerView) {\n                        isRecycerView = true;\n                    } else {\n                        isRecycerView = false;\n                    }\n                    break;\n                }\n            }\n        }\n        if (isRecycerView)\n            initERVH();\n    }\n\n    @Override\n    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {\n\n        final int width = getMeasuredWidth();\n        final int height = getMeasuredHeight();\n        if (getChildCount() == 0) {\n            /*不存在子View*/\n            throw new RuntimeException(\"child view can not be empty\");\n        }\n\n        if (contentView == null) {\n            initContentView();\n        }\n        if (contentView == null) {\n            throw new RuntimeException(\"main content of view can not be empty \");\n        }\n\n        final View child = contentView;\n        final int childLeft = getPaddingLeft();\n        final int childTop = getPaddingTop() + currentOffsetTop;\n        final int childWidth = width - getPaddingLeft() - getPaddingRight();\n        final int childHeight = height - getPaddingTop() - getPaddingBottom();\n        /*设置contentView 的位置*/\n\n        child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);\n\n        // header放到contentView的上方，水平居中\n        int refreshViewWidth = refreshHeaderView.getMeasuredWidth();\n        refreshHeaderView.layout((width / 2 - refreshViewWidth / 2),\n                -headerViewHight + currentOffsetTop,\n                (width / 2 + refreshViewWidth / 2),\n                currentOffsetTop);\n\n\n        //loadMoreView 放到contentView 下方\n\n        int loadMoreViewWidth = mLoadMoreView.getMeasuredWidth();\n        int loadL = width / 2 - loadMoreViewWidth / 2;\n        int loadT = childHeight;\n        int loadR = width / 2 + loadMoreViewWidth / 2;\n        int loadB = childHeight + loadMoreViewHeight;\n        mLoadMoreView.layout(loadL, loadT, loadR, loadB);\n\n    }\n\n    /*处理触摸事件*/\n\n    @Override\n    public boolean dispatchTouchEvent(MotionEvent ev) {\n\n\n        if (isLoading || contentView == null) {\n            return super.dispatchTouchEvent(ev);\n        }\n\n        //获取支持多点触控的action\n        final int actionMasked = ev.getActionMasked();\n        switch (actionMasked) {\n            case MotionEvent.ACTION_DOWN: {\n                mDistance = 0;\n                //Log.i(TAG, \"ACTION_DOWN\");\n                //获得首次按下的触摸事件的id\n                activePointerId = ev.getPointerId(0);\n                isTouch = true;\n                hasSendCancelEvent = false;\n                // 是否开始下拉\n                isBeginDragged = false;\n                // 上一次contentView的偏移高度\n                lastOffsetTop = currentOffsetTop;\n                currentOffsetTop = contentView.getTop();\n                // 手指按下时的坐标\n                initDownX = lastMotionX = ev.getX(0);\n                initDownY = lastMotionY = ev.getY(0);\n                autoScroll.stop();\n                removeCallbacks(delayToScrollTopRunnable);\n                removeCallbacks(autoRefreshRunnable);\n                super.dispatchTouchEvent(ev);\n                //表示消耗了该事件,以便子view都没有消耗而导致后面接收不到该事件的事件序列\n                return true;\n            }\n\n            case MotionEvent.ACTION_MOVE: {\n                if (activePointerId == INVALID_POINTER) {\n                    // Log.e(TAG, \"Got ACTION_MOVE event but don't have an active pointer id.\");\n                    return super.dispatchTouchEvent(ev);\n                }\n                autoScroll.stop();\n\n                // 最后一次move事件\n                lastEvent = ev;\n                float x = ev.getX(MotionEventCompat.findPointerIndex(ev, activePointerId));\n                float y = ev.getY(MotionEventCompat.findPointerIndex(ev, activePointerId));\n                float xDiff = x - lastMotionX;\n                yDiff = y - lastMotionY;\n                mDistance = mDistance + yDiff;\n                offsetY = yDiff * DRAG_RATE;\n                lastMotionX = x;\n                lastMotionY = y;\n                if (Math.abs(xDiff) > touchSlop /*&& isInterceptMoveEvent*/) {\n                    /*左右滑动了*/\n                    break;\n                }\n\n                if (!isBeginDragged && Math.abs(y - initDownY) > touchSlop) {\n                    isBeginDragged = true;\n                }\n                if (isBeginDragged) {\n                    boolean isMoveHeadDown = offsetY > 0; // ↓\n                    boolean canMoveHeadDown = !canChildScrollUp();\n                    boolean isMoveHeadUp = !isMoveHeadDown;     // ↑\n                    boolean canMoveHeadUp = currentOffsetTop > START_POSITION;\n\n                    // 判断是否拦截事件\n                    if ((isMoveHeadDown && canMoveHeadDown) || (isMoveHeadUp && canMoveHeadUp)) {\n                        moveSpinner(offsetY);\n                        return true;\n                    }\n                }\n                break;\n            }\n\n            case MotionEvent.ACTION_CANCEL:\n                // Log.i(TAG, \"ACTION_CANCEL\");\n            case MotionEvent.ACTION_UP: {\n                // Log.i(TAG, \"ACTION_UP\");\n                if (currentOffsetTop > START_POSITION) {\n                    finishSpinner();\n                }\n                isTouch = false;\n                activePointerId = INVALID_POINTER;\n                break;\n            }\n            case MotionEvent.ACTION_POINTER_DOWN: {\n                // Log.i(TAG, \"ACTION_POINTER_DOWN\");\n                int pointerIndex = MotionEventCompat.getActionIndex(ev);\n                if (pointerIndex < 0) {\n                    //   Log.e(TAG, \"Got ACTION_POINTER_DOWN event but have an invalid action index.\");\n                    return super.dispatchTouchEvent(ev);\n                }\n                lastMotionX = ev.getX(pointerIndex);\n                lastMotionY = ev.getY(pointerIndex);\n                activePointerId = MotionEventCompat.getPointerId(ev, pointerIndex);\n                break;\n            }\n            case MotionEvent.ACTION_POINTER_UP: {\n                // Log.i(TAG, \"ACTION_POINTER_UP\");\n                onSecondaryPointerUp(ev);\n                lastMotionY = ev.getY(ev.findPointerIndex(activePointerId));\n                lastMotionX = ev.getX(ev.findPointerIndex(activePointerId));\n                break;\n            }\n            default:\n                break;\n        }\n        return super.dispatchTouchEvent(ev);\n\n    }\n\n    private void onSecondaryPointerUp(MotionEvent ev) {\n        /*目前只支持两点触控*/\n        final int pointerIndex = MotionEventCompat.getActionIndex(ev);\n        // Log.i(TAG, \"pointerIndex:\" + pointerIndex);\n        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);\n        if (pointerId == activePointerId) {\n            // This was our active pointer going up. Choose a new\n            // active pointer and adjust accordingly.\n            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;\n            lastMotionY = ev.getY(newPointerIndex);\n            lastMotionX = ev.getX(newPointerIndex);\n            activePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);\n        }\n    }\n\n    private void moveSpinner(float offsetY) {\n        if (!isEnablePullToRefresh) {\n            return;\n        }\n        int offset = Math.round(offsetY);\n        if (offset == 0) {\n            return;\n        }\n\n        // 发送cancel事件给child\n        if (!hasSendCancelEvent && isTouch && currentOffsetTop > START_POSITION) {\n            sendCancelEvent();\n            hasSendCancelEvent = true;\n        }\n        int nextOffsetTop = Math.max(0, currentOffsetTop + offset); // contentView不能移动到小于0的位置……\n        offset = nextOffsetTop - currentOffsetTop;\n\n        // y = x - (x/2)^2\n        float extraOS = nextOffsetTop - totalDragDistance;\n        float slingshotDist = totalDragDistance;\n        float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2) / slingshotDist);\n        float tensionPercent = (float) (tensionSlingshotPercent - Math.pow(tensionSlingshotPercent / this.pull_resistance, 2));\n\n        if (offset > 0) { // 下拉的时候才添加阻力\n            offset = (int) (offset * (1f - tensionPercent));\n            nextOffsetTop = Math.max(0, currentOffsetTop + offset);\n        }\n        // 1. 在RESET状态时，第一次下拉出现header的时候，设置状态变成PULL\n        if (state == State.RESET && currentOffsetTop == START_POSITION && nextOffsetTop > 0) {\n            if (isNotMoreLoading || isLoadingFail)\n                closeLoadView();\n            changeState(State.PULL);\n        }\n\n        // 2. 在PULL或者COMPLETE状态时，header回到顶部的时候，状态变回RESET\n\n\n        if (currentOffsetTop > START_POSITION && nextOffsetTop <= START_POSITION) {\n            //  Log.i(TAG, \"2--contentViewY:\" + nextOffsetTop + \"--START_POSITION:\" + START_POSITION);\n\n            if (state == State.PULL || state == State.COMPLETE) {\n                changeState(State.RESET);\n            }\n        }\n\n        // 3. 如果是从底部回到顶部的过程(往上滚动)，并且手指是松开状态, 并且当前是PULL状态，状态变成LOADING，这时候我们需要强制停止autoScroll\n\n        if (state == State.PULL && !isTouch && currentOffsetTop > totalDragDistance && nextOffsetTop <= totalDragDistance) {\n            //  Log.i(TAG, \"3--contentViewY:\" + nextOffsetTop + \"--totalDragDistance:\" + totalDragDistance);\n\n            autoScroll.stop();\n\n            changeState(State.REFRESHING);\n            if (easyEvent != null) {\n                isRefreshing = true;\n                easyEvent.onRefreshing();\n            }\n            // 因为判断条件targetY <= totalDragDistance，会导致不能回到正确的刷新高度（有那么一丁点偏差），调整change\n            int adjustOffset = totalDragDistance - nextOffsetTop;\n            offset += adjustOffset;\n        }\n\n        setTargetOffsetTopAndBottom(offset);\n        // 别忘了回调header的位置改变方法。\n        if (refreshHeaderView instanceof IRefreshHeader) {\n            ((IRefreshHeader) refreshHeaderView)\n                    .onPositionChange(currentOffsetTop, lastOffsetTop, totalDragDistance, isTouch, state);\n        }\n\n\n    }\n\n    private void finishSpinner() {\n        if (state == State.REFRESHING) {\n            if (currentOffsetTop > totalDragDistance) {\n                autoScroll.scrollTo(totalDragDistance, SCROLL_TO_REFRESH_DURATION);\n            }\n        } else {\n            autoScroll.scrollTo(START_POSITION, SCROLL_TO_TOP_DURATION);\n        }\n    }\n\n    private boolean canChildScrollUp() {\n        if (android.os.Build.VERSION.SDK_INT < 14) {\n            if (contentView instanceof AbsListView) {\n                final AbsListView absListView = (AbsListView) contentView;\n                return absListView.getChildCount() > 0\n                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)\n                        .getTop() < absListView.getPaddingTop());\n            } else {\n                return ViewCompat.canScrollVertically(contentView, -1) || contentView.getScrollY() > 0;\n            }\n        } else {\n            /*return true can  swipe up*/\n            return ViewCompat.canScrollVertically(contentView, -1);\n        }\n    }\n\n    private void setTargetOffsetTopAndBottom(int offset) {\n        if (offset == 0) {\n            return;\n        }\n\n        contentView.offsetTopAndBottom(offset);\n        refreshHeaderView.offsetTopAndBottom(offset);\n        lastOffsetTop = currentOffsetTop;\n        currentOffsetTop = contentView.getTop();\n        invalidate();\n    }\n\n    /**\n     * ACTION——MOVE事件已被处理,子View不再接收该事件序列\n     * 直到下一次DOWN事件触发\n     */\n    private void sendCancelEvent() {\n        if (lastEvent == null) {\n            return;\n        }\n        // Log.i(TAG, \"start sendCancelEvent\");\n        MotionEvent ev = MotionEvent.obtain(lastEvent);\n        ev.setAction(MotionEvent.ACTION_CANCEL);\n        super.dispatchTouchEvent(ev);\n    }\n\n    private void changeState(State state) {\n        // Toast.makeText(getContext(), state.toString(), Toast.LENGTH_SHORT).show();\n\n        this.state = state;\n\n        IRefreshHeader refreshHeader = this.refreshHeaderView instanceof IRefreshHeader ? ((IRefreshHeader) this.refreshHeaderView) : null;\n        if (refreshHeader != null) {\n            switch (state) {\n                case RESET:\n                    refreshHeader.reset();\n                    break;\n                case PULL:\n                    refreshHeader.pull();\n                    break;\n                case REFRESHING:\n                    refreshHeader.refreshing();\n                    break;\n                case COMPLETE:\n                    refreshHeader.complete();\n                    break;\n            }\n        }\n    }\n\n    public void refreshComplete() {\n        isRefreshing = false;\n        changeState(State.COMPLETE);\n        // if refresh completed and the contentView at top, change state to reset.\n        if (currentOffsetTop == START_POSITION) {\n            changeState(State.RESET);\n        } else {\n            // waiting for a time to show refreshView completed state.\n            // at next touch event, remove this runnable\n            if (!isTouch) {\n                postDelayed(delayToScrollTopRunnable, SHOW_COMPLETED_TIME);\n            }\n        }\n    }\n\n    public void autoRefresh() {\n        autoRefresh(500);\n    }\n\n    /**\n     * 在onCreate中调用autoRefresh，此时View可能还没有初始化好，需要延长一段时间执行。\n     *\n     * @param duration 延时执行的毫秒值\n     */\n    public void autoRefresh(long duration) {\n        if (state != State.RESET) {\n            return;\n        }\n        postDelayed(autoRefreshRunnable, duration);\n    }\n\n    public View getDefaultRefreshView() {\n        return new SimpleRefreshHeaderView(getContext());\n    }\n\n    private class AutoScroll implements Runnable {\n        private Scroller scroller;\n        private int lastY;\n\n        public AutoScroll() {\n            scroller = new Scroller(getContext());\n        }\n\n        @Override\n        public void run() {\n            boolean finished = !scroller.computeScrollOffset() || scroller.isFinished();\n            if (!finished) {\n                int currY = scroller.getCurrY();\n                int offset = currY - lastY;\n                lastY = currY;\n                moveSpinner(offset); // 调用此方法移动header和contentView\n                /*使用当前线程运行*/\n                post(this);\n                onScrollFinish(false);\n            } else {\n                stop();\n                onScrollFinish(true);\n            }\n        }\n\n        public void scrollTo(int to, int duration) {\n            int from = currentOffsetTop;\n            int distance = to - from;\n            stop();\n            if (distance == 0) {\n                return;\n            }\n            //滑动到原位\n            scroller.startScroll(0, 0, 0, distance, duration);\n            post(this);\n        }\n\n        private void stop() {\n            removeCallbacks(this);\n            if (!scroller.isFinished()) {\n                scroller.forceFinished(true);\n            }\n            lastY = 0;\n        }\n    }\n\n\n    /**\n     * 在scroll结束的时候会回调这个方法\n     *\n     * @param isForceFinish 是否是强制结束的\n     */\n\n    private void onScrollFinish(boolean isForceFinish) {\n        if (isAutoRefresh && !isForceFinish) {\n            isAutoRefresh = false;\n            changeState(State.REFRESHING);\n            if (easyEvent != null) {\n                easyEvent.onRefreshing();\n            }\n            finishSpinner();\n        }\n    }\n\n    // 定义一个侦听器\n    public interface OnRefreshListener {\n        void onRefreshing();\n    }\n\n    public interface EasyEvent extends OnRefreshListener, LoadMoreEvent {\n\n    }\n\n    // 提供外部设置方法\n    public void addEasyEvent(EasyEvent event) {\n        if (event == null) {\n            throw new ERVHRuntimeException(\"adapter can not be null\");\n        }\n        this.easyEvent = event;\n    }\n\n    public boolean isEnablePullToRefresh() {\n        return isEnablePullToRefresh;\n    }\n\n    public void setEnablePullToRefresh(boolean enable) {\n        isEnablePullToRefresh = enable;\n    }\n\n    public boolean isRefreshing() {\n        return isRefreshing;\n\n    }\n\n    public void setRefreshing(boolean refreshing) {\n        if (refreshing) {\n            changeState(State.REFRESHING);\n            if (isNotMoreLoading || isLoadingFail) {\n                closeLoadView();\n            }\n        }\n        changeState(State.RESET);\n    }\n\n\n    private void initERVH() {\n        if (mLoadMoreView == null) {\n            getDefaultLoadMoreView();\n            setLoadMoreView(mLoadMoreView);\n        }\n        if (!isRecycerView) {\n            return;\n        }\n        mRecyclerView = (RecyclerView) contentView;\n        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {\n\n\n            @Override\n            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {\n                super.onScrolled(recyclerView, dx, dy);\n                if (loadMoreModel == LoadModel.ADVANCE_MODEL) {\n                    if (!isLoading && !isRefreshing && !isLoadingFail && !isNotMoreLoading) {\n                        final int lastVisibleItem = getLastVisiBleItem();\n                        int totalItemCount = mRecyclerView.getLayoutManager().getItemCount();\n                        int totalChildCount = mRecyclerView.getLayoutManager().getChildCount();\n                        if (totalChildCount > 0 && lastVisibleItem >= totalItemCount - 1 - advanceCount && totalItemCount >= totalChildCount) {\n                            isCanLoad = true;\n                        }\n                        if (isCanLoad) {\n                            isCanLoad = false;\n                            isLoading = true;\n                            if (easyEvent != null) {\n                                easyEvent.onLoadMore();\n                            }\n\n                        }\n                    }\n\n                }\n\n\n            }\n\n            @Override\n            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {\n                super.onScrollStateChanged(recyclerView, newState);\n                // Log.i(TAG, \">>>>mDistance:\" + mDistance);\n                // Log.i(TAG, \">>>>touchSlop:\" + touchSlop);\n                if (loadMoreModel == LoadModel.ADVANCE_MODEL) {\n                    return;\n                }\n                if (Math.abs(mDistance) > touchSlop && mDistance < 0) {\n                    if (!isLoading && loadMoreModel == LoadModel.COMMON_MODEL && !isRefreshing && !isLoadingFail && !isNotMoreLoading) {\n                        final int lastVisibleItem = getLastVisiBleItem();\n                        int totalItemCount = mRecyclerView.getLayoutManager().getItemCount();\n                        int totalChildCount = mRecyclerView.getLayoutManager().getChildCount();\n                        if (totalChildCount > 0 && lastVisibleItem >= totalItemCount - 1 && totalItemCount >= totalChildCount) {\n                            isCanLoad = true;\n                        }\n                        if (isCanLoad) {\n                            isCanLoad = false;\n                            isLoading = true;\n                            ((ILoadMoreView) mLoadMoreView).reset();\n\n                            mLoadMoreView.measure(0, 0);\n                            ((ILoadMoreView) mLoadMoreView).loading();\n                            showLoadView();\n\n                        }\n                    }\n                }\n            }\n        });\n\n\n    }\n\n    private void showLoadView() {\n\n        ValueAnimator animator = ValueAnimator.ofInt(0, -mLoadMoreView.getMeasuredHeight());\n        animator.setTarget(mLoadMoreView);\n        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n\n            private int lastDs;\n\n            @Override\n            public void onAnimationUpdate(ValueAnimator animation) {\n                final int ds = (int) animation.getAnimatedValue();\n                // setTargetOffsetTopAndBottom((ds - lastDs));\n                lastDs = ds;\n                mLoadMoreView.bringToFront();\n                mLoadMoreView.setTranslationY(ds);\n            }\n        });\n        animator.addListener(new AnimatorListenerAdapter() {\n                                 @Override\n                                 public void onAnimationEnd(Animator animation) {\n                                     super.onAnimationEnd(animation);\n                                     if (easyEvent != null) {\n                                         easyEvent.onLoadMore();\n                                     }\n                                 }\n                             }\n\n        );\n        animator.setDuration(SCROLL_TO_LOADING_DURATION);\n        animator.start();\n\n\n    }\n\n    private void hideLoadView() {\n        // setTargetOffsetTopAndBottom( mLoadMoreView.getMeasuredHeight());\n        if (mLoadMoreView != null && isRecycerView) {\n            ValueAnimator animator = ValueAnimator.ofInt(0, mLoadMoreView.getMeasuredHeight());\n            animator.setTarget(mLoadMoreView);\n            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n\n                private int lastDs;\n\n                @Override\n                public void onAnimationUpdate(ValueAnimator animation) {\n\n                    int ds = (int) animation.getAnimatedValue();\n                    //  setTargetOffsetTopAndBottom((ds - lastDs));\n                    lastDs = ds;\n                    mLoadMoreView.bringToFront();\n                    mLoadMoreView.setTranslationY(ds);\n\n\n                }\n\n            });\n            animator.addListener(new Animator.AnimatorListener() {\n                @Override\n                public void onAnimationStart(Animator animator) {\n\n                }\n\n                @Override\n                public void onAnimationEnd(Animator animator) {\n                    isLoading = false;\n\n                }\n\n                @Override\n                public void onAnimationCancel(Animator animator) {\n                    isLoading = false;\n\n                }\n\n                @Override\n                public void onAnimationRepeat(Animator animator) {\n                    isLoading = false;\n\n                }\n            });\n            animator.setDuration(SHOW_SCROLL_DOWN_DURATION);\n            animator.start();\n        }\n\n    }\n\n    public void closeLoadView() {\n        if (loadMoreModel == LoadModel.ADVANCE_MODEL) {\n            throw new RuntimeException(\"enableAdance Model cant not called closeLoadView method\");\n\n        } else {\n            // setTargetOffsetTopAndBottom( mLoadMoreView.getMeasuredHeight());\n            if (mLoadMoreView != null && isRecycerView) {\n                // setTargetOffsetTopAndBottom(mLoadMoreView.getMeasuredHeight());\n                mLoadMoreView.bringToFront();\n                mLoadMoreView.setTranslationY(mLoadMoreView.getMeasuredHeight());\n                resetLoadMoreState();\n\n            }\n        }\n\n    }\n\n\n    public View getLoadMoreView() {\n        return (View) getDefaultLoadMoreView();\n    }\n\n    public void setLoadMoreView(View loadMoreView) {\n        if (loadMoreView == null)\n            throw new ERVHRuntimeException(\"loadMoreView can not be null\");\n        if (loadMoreView != null && loadMoreView != mLoadMoreView)\n            removeView(mLoadMoreView);\n\n        /*设置默认的布局参数*/\n        LayoutParams layoutParams = loadMoreView.getLayoutParams();\n        if (layoutParams == null) {\n            layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);\n            loadMoreView.setLayoutParams(layoutParams);\n        }\n        mLoadMoreView = loadMoreView;\n        addView(mLoadMoreView);\n        resetLoadMoreState();\n        ((ILoadMoreView) mLoadMoreView).reset();\n\n        ((ILoadMoreView) mLoadMoreView).getCanClickFailView().setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (isLoadingFail && easyEvent != null) {\n                    isLoading = true;\n                    ((ILoadMoreView) mLoadMoreView).loading();\n                    easyEvent.onLoadMore();\n                }\n            }\n        });\n\n    }\n\n    public void loadMoreComplete() {\n        if (loadMoreModel == LoadModel.ADVANCE_MODEL) {\n            isLoading = false;\n        } else if (loadMoreModel == LoadModel.COMMON_MODEL) {\n            loadMoreComplete(null);\n        }\n    }\n\n    @Deprecated\n    public void loadMoreComplete(EasyRefreshLayout.Event event) {\n        if (loadMoreModel == LoadModel.ADVANCE_MODEL) {\n            throw new RuntimeException(\"enableAdance Model cant not called closeLoadView method\");\n\n        } else {\n            loadMoreComplete(event, 500);\n        }\n    }\n\n    @Deprecated\n    public void loadMoreComplete(final EasyRefreshLayout.Event event, long delayedTime) {\n        if (loadMoreModel == LoadModel.ADVANCE_MODEL) {\n            throw new RuntimeException(\"enableAdance Model cant not called closeLoadView method\");\n\n        } else {\n\n            ((ILoadMoreView) mLoadMoreView).loadComplete();\n\n            if (event == null) {\n                hideLoadView();\n                resetLoadMoreState();\n                return;\n            }\n            new Handler().postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    event.complete();\n                    hideLoadView();\n                    resetLoadMoreState();\n                }\n            }, delayedTime);\n        }\n    }\n\n    private void resetLoadMoreState() {\n        isCanLoad = false;\n        isLoading = false;\n        isLoadingFail = false;\n        isNotMoreLoading = false;\n    }\n\n\n    public void loadMoreFail() {\n        if (loadMoreModel == LoadModel.ADVANCE_MODEL) {\n            throw new RuntimeException(\"enableAdance Model cant not called closeLoadView method\");\n\n        } else {\n            ((ILoadMoreView) mLoadMoreView).loadFail();\n            resetLoadMoreState();\n            isLoadingFail = true;\n        }\n    }\n\n    public void loadNothing() {\n        if (loadMoreModel == LoadModel.ADVANCE_MODEL) {\n            throw new RuntimeException(\"enableAdance Model cant not called closeLoadView method\");\n\n        } else {\n            ((ILoadMoreView) mLoadMoreView).loadNothing();\n            resetLoadMoreState();\n            isNotMoreLoading = true;\n        }\n    }\n\n    private View getDefaultLoadMoreView() {\n        return new SimpleLoadMoreView(getContext());\n    }\n\n    /**\n     * 最后一个的位置\n     */\n    private int getLastVisiBleItem() {\n        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();\n        int lastVisibleItemPosition = -1;\n\n        int layoutManagerType = 0;\n        if (layoutManager instanceof GridLayoutManager) {\n            layoutManagerType = 1;\n        } else if (layoutManager instanceof LinearLayoutManager) {\n            layoutManagerType = 0;\n        } else if (layoutManager instanceof StaggeredGridLayoutManager) {\n            layoutManagerType = 2;\n        } else {\n            throw new RuntimeException(\n                    \"Unsupported LayoutManager used. Valid ones are LinearLayoutManager, GridLayoutManager and StaggeredGridLayoutManager\");\n        }\n\n        switch (layoutManagerType) {\n            case 0:\n                lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();\n                break;\n            case 1:\n                lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();\n                break;\n            case 2:\n                StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;\n                int[] lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];\n                staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);\n                lastVisibleItemPosition = findMax(lastPositions);\n                break;\n        }\n        return lastVisibleItemPosition;\n    }\n\n    private int findMax(int[] lastPositions) {\n        int max = lastPositions[0];\n        for (int value : lastPositions) {\n            if (value > max) {\n                max = value;\n            }\n        }\n        return max;\n    }\n\n\n    public boolean isEnableLoadMore() {\n        return loadMoreModel == LoadModel.COMMON_MODEL || loadMoreModel == LoadModel.ADVANCE_MODEL;\n    }\n\n    public LoadModel getLoadMoreModel() {\n        return loadMoreModel;\n    }\n\n    public void setLoadMoreModel(LoadModel loadMoreModel, int advanceCount) {\n        this.loadMoreModel = loadMoreModel;\n        this.advanceCount = advanceCount;\n    }\n\n    public int getAdvanceCount() {\n        return advanceCount;\n    }\n\n    public void setAdvanceCount(int advanceCount) {\n        this.advanceCount = advanceCount;\n    }\n\n    /**\n     * 默认预加载触发数量为0\n     *\n     * @param loadMoreModel\n     */\n    public void setLoadMoreModel(LoadModel loadMoreModel) {\n        this.setLoadMoreModel(loadMoreModel, 0);\n    }\n\n\n    public boolean isLoading() {\n        return isLoading;\n    }\n\n    public interface LoadMoreEvent {\n        /***\n         * 加载更多\n         */\n        void onLoadMore();\n    }\n\n    public interface Event {\n        void complete();\n\n    }\n\n    public long getShowLoadViewAnimatorDuration() {\n        return SCROLL_TO_LOADING_DURATION;\n    }\n\n    public void setShowLoadViewAnimatorDuration(long scrollToLoadingDuration) {\n        SCROLL_TO_LOADING_DURATION = scrollToLoadingDuration;\n    }\n\n    public int getScrollToRefreshDuration() {\n        return SCROLL_TO_REFRESH_DURATION;\n    }\n\n    public void setScrollToRefreshDuration(int scrollToRefreshDuration) {\n        SCROLL_TO_REFRESH_DURATION = scrollToRefreshDuration;\n    }\n\n    public int getScrollToTopDuration() {\n        return SCROLL_TO_TOP_DURATION;\n    }\n\n    public void setScrollToTopDuration(int scrollToTopDuration) {\n        SCROLL_TO_TOP_DURATION = scrollToTopDuration;\n    }\n\n    public long getHideLoadViewAnimatorDuration() {\n        return SHOW_COMPLETED_TIME;\n    }\n\n    public void setHideLoadViewAnimatorDuration(long showCompletedTime) {\n        SHOW_COMPLETED_TIME = showCompletedTime;\n    }\n\n\n    public double getPullResistance() {\n        return this.pull_resistance;\n    }\n\n    /**\n     * Set the pull-down refresh resistance factor\n     *\n     * @param PullResistance resistance factor\n     */\n    public void setPullResistance(double PullResistance) {\n        this.pull_resistance = PullResistance;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/easyrefresh/ILoadMoreView.java",
    "content": "package com.didichuxing.doraemonkit.widget.easyrefresh;\n\nimport android.view.View;\n\n/**\n * Created by guanaj on 16/9/22.\n */\n\npublic interface ILoadMoreView {\n    /**\n     * 重置\n     */\n    void reset();\n\n    /**\n     * 加载中\n     */\n    void loading();\n\n    /**\n     * 加载完成\n     */\n    void loadComplete();\n\n    void loadFail();\n\n    void loadNothing();\n\n    View getCanClickFailView();\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/easyrefresh/IRefreshHeader.java",
    "content": "package com.didichuxing.doraemonkit.widget.easyrefresh;\n\n\n/**\n * Created by guanaj on 16/9/2.\n */\npublic interface IRefreshHeader {\n    /**\n     * 松手，头部隐藏后会回调这个方法\n     */\n    void reset();\n\n    /**\n     * 下拉出头部的一瞬间调用\n     */\n    void pull();\n\n    /**\n     * 正在刷新的时候调用\n     */\n    void refreshing();\n\n    /**\n     * 头部滚动的时候持续调用\n     *\n     * @param currentOffsetdistance target当前偏移高度\n     * @param lastOffsetDistance    target上一次的偏移高度\n     * @param canRefreshOffsetDistance 可以松手刷新的高度\n     * @param isTouch    手指是否按下状态（通过scroll自动滚动时需要判断）\n     * @param state      当前状态\n     */\n    void onPositionChange(float currentOffsetdistance, float lastOffsetDistance, float canRefreshOffsetDistance, boolean isTouch, State state);\n\n    /**\n     * 刷新成功的时候调用\n     */\n    void complete();\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/easyrefresh/LoadModel.java",
    "content": "package com.didichuxing.doraemonkit.widget.easyrefresh;\n\n/**\n * Created by guanaj on 2017/7/27.\n */\n\npublic enum LoadModel {\n    NONE, COMMON_MODEL, ADVANCE_MODEL\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/easyrefresh/State.java",
    "content": "package com.didichuxing.doraemonkit.widget.easyrefresh;\n\n/**\n * Created by guanaj on 16/9/2.\n */\npublic enum State {\n    /**\n     * 重置\n     */\n    RESET,\n    /**\n     * 下拉状态\n     */\n    PULL,\n    /**\n     * 刷新中\n     */\n    REFRESHING,\n    /**\n     * 完成状态\n     */\n    COMPLETE\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/easyrefresh/exception/ERVHRuntimeException.java",
    "content": "package com.didichuxing.doraemonkit.widget.easyrefresh.exception;\n\n/**\n * Created by guanaj on 16/9/21.\n */\n\npublic class ERVHRuntimeException extends RuntimeException {\n\n    public ERVHRuntimeException() {\n        super();\n    }\n\n    public ERVHRuntimeException(Throwable throwable) {\n        super(throwable);\n    }\n\n    public ERVHRuntimeException(String detailMessage, Throwable throwable) {\n        super(detailMessage, throwable);\n    }\n\n    public ERVHRuntimeException(String detailMessage) {\n        super(detailMessage);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/easyrefresh/view/SimpleLoadMoreView.java",
    "content": "package com.didichuxing.doraemonkit.widget.easyrefresh.view;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.View;\nimport android.widget.FrameLayout;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.widget.easyrefresh.ILoadMoreView;\nimport com.github.ybq.android.spinkit.SpinKitView;\n\n\n\n/**\n * Created by guanaj on 16/9/22.\n */\n\npublic class SimpleLoadMoreView extends FrameLayout implements ILoadMoreView {\n\n    private TextView tvHitText;\n    private SpinKitView spinKitView;\n    private View view;\n\n    public SimpleLoadMoreView(Context context) {\n        this(context, null);\n    }\n\n    public SimpleLoadMoreView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        view = inflate(context, R.layout.dk_refresh_default_load_more, this);\n        tvHitText = (TextView) view.findViewById(R.id.tv_hit_content);\n        spinKitView = (SpinKitView) view.findViewById(R.id.spin_kit);\n\n    }\n\n\n    @Override\n    public void reset() {\n        spinKitView.setVisibility(INVISIBLE);\n        tvHitText.setVisibility(INVISIBLE);\n        tvHitText.setText(\"正在加载...\");\n    }\n\n    @Override\n    public void loading() {\n        spinKitView.setVisibility(VISIBLE);\n        tvHitText.setVisibility(VISIBLE);\n        tvHitText.setText(\"正在加载...\");\n    }\n\n    @Override\n    public void loadComplete() {\n        spinKitView.setVisibility(INVISIBLE);\n        tvHitText.setVisibility(VISIBLE);\n        tvHitText.setText(\"加载完成\");\n\n    }\n\n    @Override\n    public void loadFail() {\n        spinKitView.setVisibility(INVISIBLE);\n        tvHitText.setVisibility(VISIBLE);\n        tvHitText.setText(\"加载失败,点击重新加载\");\n\n    }\n\n    @Override\n    public void loadNothing() {\n        spinKitView.setVisibility(INVISIBLE);\n        tvHitText.setVisibility(VISIBLE);\n        tvHitText.setText(\"没有更多可以加载\");\n    }\n\n    @Override\n    public View getCanClickFailView() {\n        return view;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/easyrefresh/view/SimpleRefreshHeaderView.java",
    "content": "package com.didichuxing.doraemonkit.widget.easyrefresh.view;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.animation.Animation;\nimport android.view.animation.AnimationUtils;\nimport android.widget.FrameLayout;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.widget.easyrefresh.IRefreshHeader;\nimport com.didichuxing.doraemonkit.widget.easyrefresh.State;\n\n\n\npublic class SimpleRefreshHeaderView extends FrameLayout implements IRefreshHeader {\n\n\n    private Animation rotate_up;\n    private Animation rotate_down;\n    private Animation rotate_infinite;\n    private TextView textView;\n    private View arrowIcon;\n    private View successIcon;\n    private View loadingIcon;\n\n    public SimpleRefreshHeaderView(Context context) {\n        this(context, null);\n    }\n\n    public SimpleRefreshHeaderView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n\n        // 初始化动画\n        rotate_up = AnimationUtils.loadAnimation(context, R.anim.dk_easy_refresh_rotate_up);\n        rotate_down = AnimationUtils.loadAnimation(context, R.anim.dk_easy_refresh_rotate_down);\n        rotate_infinite = AnimationUtils.loadAnimation(context, R.anim.dk_easy_refresh_rotate_infinite);\n\n        inflate(context, R.layout.dk_refresh_default_refresh_header, this);\n\n        textView = (TextView) findViewById(R.id.text);\n        arrowIcon = findViewById(R.id.arrowIcon);\n        successIcon = findViewById(R.id.successIcon);\n        loadingIcon = findViewById(R.id.loadingIcon);\n    }\n\n    @Override\n    public void reset() {\n        textView.setText(getResources().getText(R.string.dk_header_reset));\n        successIcon.setVisibility(INVISIBLE);\n        arrowIcon.setVisibility(VISIBLE);\n        arrowIcon.clearAnimation();\n        loadingIcon.setVisibility(INVISIBLE);\n        loadingIcon.clearAnimation();\n    }\n\n    @Override\n    public void pull() {\n\n    }\n\n    @Override\n    public void refreshing() {\n        arrowIcon.setVisibility(INVISIBLE);\n        loadingIcon.setVisibility(VISIBLE);\n        textView.setText(getResources().getText(R.string.dk_header_refreshing));\n        arrowIcon.clearAnimation();\n        loadingIcon.startAnimation(rotate_infinite);\n    }\n\n    @Override\n    public void onPositionChange(float currentPos, float lastPos, float refreshPos, boolean isTouch, State state) {\n        // 往上拉\n        if (currentPos < refreshPos && lastPos >= refreshPos) {\n            Log.i(\"\", \">>>>up\");\n            if (isTouch && state == State.PULL) {\n                textView.setText(getResources().getText(R.string.dk_header_pull));\n                arrowIcon.clearAnimation();\n                arrowIcon.startAnimation(rotate_down);\n            }\n            // 往下拉\n        } else if (currentPos > refreshPos && lastPos <= refreshPos) {\n            Log.i(\"\", \">>>>down\");\n            if (isTouch && state == State.PULL) {\n                textView.setText(getResources().getText(R.string.dk_header_pull_over));\n                arrowIcon.clearAnimation();\n                arrowIcon.startAnimation(rotate_up);\n            }\n        }\n    }\n\n    @Override\n    public void complete() {\n        loadingIcon.setVisibility(INVISIBLE);\n        loadingIcon.clearAnimation();\n        successIcon.setVisibility(VISIBLE);\n        textView.setText(getResources().getText(R.string.dk_header_completed));\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/jsonviewer/JsonRecyclerView.java",
    "content": "package com.didichuxing.doraemonkit.widget.jsonviewer;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.view.View;\n\n\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.widget.jsonviewer.adapter.BaseJsonViewerAdapter;\nimport com.didichuxing.doraemonkit.widget.jsonviewer.adapter.JsonViewerAdapter;\nimport com.didichuxing.doraemonkit.widget.jsonviewer.view.JsonItemView;\n\nimport org.json.JSONArray;\nimport org.json.JSONObject;\n\n/**\n * Created by yuyuhang on 2017/11/30.\n * https://github.com/smuyyh/JsonViewer\n */\npublic class JsonRecyclerView extends RecyclerView {\n\n    private BaseJsonViewerAdapter mAdapter;\n\n    public JsonRecyclerView(Context context) {\n        this(context, null);\n    }\n\n    public JsonRecyclerView(Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public JsonRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n\n        initView();\n    }\n\n    private void initView() {\n        setLayoutManager(new LinearLayoutManager(getContext()));\n    }\n\n    public void bindJson(String jsonStr) {\n        mAdapter = null;\n        mAdapter = new JsonViewerAdapter(jsonStr);\n        setAdapter(mAdapter);\n    }\n\n    public void bindJson(JSONArray array) {\n        mAdapter = null;\n        mAdapter = new JsonViewerAdapter(array);\n        setAdapter(mAdapter);\n    }\n\n    public void bindJson(JSONObject object) {\n        mAdapter = null;\n        mAdapter = new JsonViewerAdapter(object);\n        setAdapter(mAdapter);\n    }\n\n    public void setKeyColor(int color) {\n        BaseJsonViewerAdapter.KEY_COLOR = color;\n    }\n\n    public void setValueTextColor(int color) {\n        BaseJsonViewerAdapter.TEXT_COLOR = color;\n    }\n\n    public void setValueNumberColor(int color) {\n        BaseJsonViewerAdapter.NUMBER_COLOR = color;\n    }\n\n    public void setValueBooleanColor(int color) {\n        BaseJsonViewerAdapter.BOOLEAN_COLOR = color;\n    }\n\n    public void setValueUrlColor(int color) {\n        BaseJsonViewerAdapter.URL_COLOR = color;\n    }\n\n    public void setValueNullColor(int color) {\n        BaseJsonViewerAdapter.NUMBER_COLOR = color;\n    }\n\n    public void setBracesColor(int color) {\n        BaseJsonViewerAdapter.BRACES_COLOR = color;\n    }\n\n    public void setTextSize(float sizeDP) {\n        if (sizeDP < 10) {\n            sizeDP = 10;\n        } else if (sizeDP > 30) {\n            sizeDP = 30;\n        }\n\n        if (BaseJsonViewerAdapter.TEXT_SIZE_DP != sizeDP) {\n            BaseJsonViewerAdapter.TEXT_SIZE_DP = sizeDP;\n            if (mAdapter != null) {\n                updateAll(sizeDP);\n            }\n        }\n    }\n\n    public void setScaleEnable(boolean enable) {\n        if (enable) {\n            addOnItemTouchListener(touchListener);\n        } else {\n            removeOnItemTouchListener(touchListener);\n        }\n    }\n\n    public void updateAll(float textSize) {\n        LayoutManager manager = getLayoutManager();\n\n        int count = manager.getChildCount();\n\n        for (int i = 0; i < count; i++) {\n            View view = manager.getChildAt(i);\n            loop(view, textSize);\n        }\n    }\n\n    private void loop(View view, float textSize) {\n        if (view instanceof JsonItemView) {\n            JsonItemView group = (JsonItemView) view;\n\n            group.setTextSize(textSize);\n\n            int childCount = group.getChildCount();\n\n            for (int i = 0; i < childCount; i++) {\n                View view1 = group.getChildAt(i);\n                loop(view1, textSize);\n            }\n        }\n    }\n\n    int mode;\n    float oldDist;\n\n    private void zoom(float f) {\n        setTextSize(BaseJsonViewerAdapter.TEXT_SIZE_DP * f);\n    }\n\n    private float spacing(MotionEvent event) {\n        float x = event.getX(0) - event.getX(1);\n        float y = event.getY(0) - event.getY(1);\n        return (float) Math.sqrt(x * x + y * y);\n    }\n\n    private OnItemTouchListener touchListener = new OnItemTouchListener() {\n        @Override\n        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent event) {\n            switch (event.getAction() & event.getActionMasked()) {\n                case MotionEvent.ACTION_DOWN:\n                    mode = 1;\n                    break;\n                case MotionEvent.ACTION_UP:\n                    mode = 0;\n                    break;\n                case MotionEvent.ACTION_POINTER_UP:\n                    mode -= 1;\n                    break;\n                case MotionEvent.ACTION_POINTER_DOWN:\n                    oldDist = spacing(event);\n                    mode += 1;\n                    break;\n\n                case MotionEvent.ACTION_MOVE:\n                    if (mode >= 2) {\n                        float newDist = spacing(event);\n                        if (Math.abs(newDist - oldDist) > 0.5f) {\n                            zoom(newDist / oldDist);\n                            oldDist = newDist;\n                        }\n                    }\n                    break;\n\n                default:\n                    break;\n            }\n            return false;\n        }\n\n        @Override\n        public void onTouchEvent(RecyclerView rv, MotionEvent event) {\n\n        }\n\n        @Override\n        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {\n\n        }\n    };\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/jsonviewer/adapter/BaseJsonViewerAdapter.java",
    "content": "package com.didichuxing.doraemonkit.widget.jsonviewer.adapter;\n\n\nimport androidx.recyclerview.widget.RecyclerView;\n\n/**\n * Created by yuyuhang on 2017/11/30.\n */\npublic abstract class BaseJsonViewerAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {\n\n    public static int KEY_COLOR = 0xFF922799;\n    public static int TEXT_COLOR = 0xFF3AB54A;\n    public static int NUMBER_COLOR = 0xFF25AAE2;\n    public static int BOOLEAN_COLOR = 0xFFF98280;\n    public static int URL_COLOR = 0xFF66D2D5;\n    public static int NULL_COLOR = 0xFFEF5935;\n    public static int BRACES_COLOR = 0xFF4A555F;\n\n    public static float TEXT_SIZE_DP = 12;\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/jsonviewer/adapter/JsonViewerAdapter.java",
    "content": "package com.didichuxing.doraemonkit.widget.jsonviewer.adapter;\n\nimport android.text.SpannableStringBuilder;\nimport android.text.Spanned;\nimport android.text.style.ForegroundColorSpan;\nimport android.view.View;\nimport android.view.ViewGroup;\n\n\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.widget.jsonviewer.utils.Utils;\nimport com.didichuxing.doraemonkit.widget.jsonviewer.view.JsonItemView;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.json.JSONTokener;\n\n/**\n * Created by yuyuhang on 2017/11/29.\n */\npublic class JsonViewerAdapter extends BaseJsonViewerAdapter<JsonViewerAdapter.JsonItemViewHolder> {\n\n    private String jsonStr;\n\n    private JSONObject mJSONObject;\n    private JSONArray mJSONArray;\n\n    public JsonViewerAdapter(String jsonStr) {\n        this.jsonStr = jsonStr;\n\n        Object object = null;\n        try {\n            object = new JSONTokener(jsonStr).nextValue();\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n        if (object != null && object instanceof JSONObject) {\n            mJSONObject = (JSONObject) object;\n        } else if (object != null && object instanceof JSONArray) {\n            mJSONArray = (JSONArray) object;\n        } else {\n            throw new IllegalArgumentException(\"jsonStr is illegal.\");\n        }\n    }\n\n    public JsonViewerAdapter(JSONObject jsonObject) {\n        this.mJSONObject = jsonObject;\n        if (mJSONObject == null) {\n            throw new IllegalArgumentException(\"jsonObject can not be null.\");\n        }\n    }\n\n    public JsonViewerAdapter(JSONArray jsonArray) {\n        this.mJSONArray = jsonArray;\n        if (mJSONArray == null) {\n            throw new IllegalArgumentException(\"jsonArray can not be null.\");\n        }\n    }\n\n    @Override\n    public JsonItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        return new JsonItemViewHolder(new JsonItemView(parent.getContext()));\n    }\n\n    @Override\n    public void onBindViewHolder(JsonItemViewHolder holder, int position) {\n        JsonItemView itemView = holder.itemView;\n        itemView.setTextSize(TEXT_SIZE_DP);\n        itemView.setRightColor(BRACES_COLOR);\n        if (mJSONObject != null) {\n            if (position == 0) {\n                itemView.hideLeft();\n                itemView.hideIcon();\n                itemView.showRight(\"{\");\n                return;\n            } else if (position == getItemCount() - 1) {\n                itemView.hideLeft();\n                itemView.hideIcon();\n                itemView.showRight(\"}\");\n                return;\n            } else if (mJSONObject.names() == null) {\n                return;\n            }\n            // 遍历key\n            String key = mJSONObject.names().optString(position - 1);\n            Object value = mJSONObject.opt(key);\n            if (position < getItemCount() - 2) {\n                handleJsonObject(key, value, itemView, true, 1);\n            } else {\n                handleJsonObject(key, value, itemView, false, 1); // 最后一组，结尾不需要逗号\n            }\n        }\n\n        if (mJSONArray != null) {\n            if (position == 0) {\n                itemView.hideLeft();\n                itemView.hideIcon();\n                itemView.showRight(\"[\");\n                return;\n            } else if (position == getItemCount() - 1) {\n                itemView.hideLeft();\n                itemView.hideIcon();\n                itemView.showRight(\"]\");\n                return;\n            }\n\n            Object value = mJSONArray.opt(position - 1); // 遍历array\n            if (position < getItemCount() - 2) {\n                handleJsonArray(value, itemView, true, 1);\n            } else {\n                handleJsonArray(value, itemView, false, 1); // 最后一组，结尾不需要逗号\n            }\n        }\n    }\n\n    @Override\n    public int getItemCount() {\n        if (mJSONObject != null) {\n            if (mJSONObject.names() != null) {\n                return mJSONObject.names().length() + 2;\n            } else {\n                return 2;\n            }\n        }\n        if (mJSONArray != null) {\n            return mJSONArray.length() + 2;\n        }\n        return 0;\n    }\n\n    /**\n     * 处理 value 上级为 JsonObject 的情况，value有key\n     *\n     * @param value\n     * @param key\n     * @param itemView\n     * @param appendComma\n     * @param hierarchy\n     */\n    private void handleJsonObject(String key, Object value, JsonItemView itemView, boolean appendComma, int hierarchy) {\n        SpannableStringBuilder keyBuilder = new SpannableStringBuilder(Utils.getHierarchyStr(hierarchy));\n        keyBuilder.append(\"\\\"\").append(key).append(\"\\\"\").append(\":\");\n        keyBuilder.setSpan(new ForegroundColorSpan(KEY_COLOR), 0, keyBuilder.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n        keyBuilder.setSpan(new ForegroundColorSpan(BRACES_COLOR), keyBuilder.length() - 1, keyBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n\n        itemView.showLeft(keyBuilder);\n\n        handleValue(value, itemView, appendComma, hierarchy);\n    }\n\n    /**\n     * 处理 value 上级为 JsonArray 的情况，value无key\n     *\n     * @param value\n     * @param itemView\n     * @param appendComma 结尾是否需要逗号(最后一组 value 不需要逗号)\n     * @param hierarchy   缩进层级\n     */\n    private void handleJsonArray(Object value, JsonItemView itemView, boolean appendComma, int hierarchy) {\n        itemView.showLeft(new SpannableStringBuilder(Utils.getHierarchyStr(hierarchy)));\n\n        handleValue(value, itemView, appendComma, hierarchy);\n    }\n\n    /**\n     * @param value\n     * @param itemView\n     * @param appendComma 结尾是否需要逗号(最后一组 key:value 不需要逗号)\n     * @param hierarchy   缩进层级\n     */\n    private void handleValue(Object value, JsonItemView itemView, boolean appendComma, int hierarchy) {\n        SpannableStringBuilder valueBuilder = new SpannableStringBuilder();\n        if (value instanceof Number) {\n            valueBuilder.append(value.toString());\n            valueBuilder.setSpan(new ForegroundColorSpan(NUMBER_COLOR), 0, valueBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n        } else if (value instanceof Boolean) {\n            valueBuilder.append(value.toString());\n            valueBuilder.setSpan(new ForegroundColorSpan(BOOLEAN_COLOR), 0, valueBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n        } else if (value instanceof JSONObject) {\n            itemView.showIcon(true);\n            valueBuilder.append(\"Object{...}\");\n            valueBuilder.setSpan(new ForegroundColorSpan(BRACES_COLOR), 0, valueBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n            itemView.setIconClickListener(new JsonItemClickListener(value, itemView, appendComma, hierarchy + 1));\n        } else if (value instanceof JSONArray) {\n            itemView.showIcon(true);\n            valueBuilder.append(\"Array[\").append(String.valueOf(((JSONArray) value).length())).append(\"]\");\n            int len = valueBuilder.length();\n            valueBuilder.setSpan(new ForegroundColorSpan(BRACES_COLOR), 0, 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n            valueBuilder.setSpan(new ForegroundColorSpan(NUMBER_COLOR), 6, len - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n            valueBuilder.setSpan(new ForegroundColorSpan(BRACES_COLOR), len - 1, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n            itemView.setIconClickListener(new JsonItemClickListener(value, itemView, appendComma, hierarchy + 1));\n        } else if (value instanceof String) {\n            itemView.hideIcon();\n            valueBuilder.append(\"\\\"\").append(value.toString()).append(\"\\\"\");\n            if (Utils.isUrl(value.toString())) {\n                valueBuilder.setSpan(new ForegroundColorSpan(TEXT_COLOR), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n                valueBuilder.setSpan(new ForegroundColorSpan(URL_COLOR), 1, valueBuilder.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n                valueBuilder.setSpan(new ForegroundColorSpan(TEXT_COLOR), valueBuilder.length() - 1, valueBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n            } else {\n                valueBuilder.setSpan(new ForegroundColorSpan(TEXT_COLOR), 0, valueBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n            }\n        } else if (valueBuilder.length() == 0 || value == null) {\n            itemView.hideIcon();\n            valueBuilder.append(\"null\");\n            valueBuilder.setSpan(new ForegroundColorSpan(NULL_COLOR), 0, valueBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n        }\n        if (appendComma) {\n            valueBuilder.append(\",\");\n        }\n\n        itemView.showRight(valueBuilder);\n    }\n\n    class JsonItemClickListener implements View.OnClickListener {\n\n        private Object value;\n        private JsonItemView itemView;\n        private boolean appendComma;\n        private int hierarchy;\n\n        private boolean isCollapsed = true;\n        private boolean isJsonArray;\n\n        JsonItemClickListener(Object value, JsonItemView itemView, boolean appendComma, int hierarchy) {\n            this.value = value;\n            this.itemView = itemView;\n            this.appendComma = appendComma;\n            this.hierarchy = hierarchy;\n            this.isJsonArray = value != null && value instanceof JSONArray;\n        }\n\n        @Override\n        public void onClick(View view) {\n            if (itemView.getChildCount() == 1) { // 初始（折叠） --> 展开\"\"\n                isCollapsed = false;\n                itemView.showIcon(false);\n                itemView.setTag(itemView.getRightText());\n                itemView.showRight(isJsonArray ? \"[\" : \"{\");\n                JSONArray array = isJsonArray ? (JSONArray) value : ((JSONObject) value).names();\n                for (int i = 0; array != null && i < array.length(); i++) {\n                    JsonItemView childItemView = new JsonItemView(itemView.getContext());\n                    childItemView.setTextSize(TEXT_SIZE_DP);\n                    childItemView.setRightColor(BRACES_COLOR);\n                    Object childValue = array.opt(i);\n                    if (isJsonArray) {\n                        handleJsonArray(childValue, childItemView, i < array.length() - 1, hierarchy);\n                    } else {\n                        handleJsonObject((String) childValue, ((JSONObject) value).opt((String) childValue), childItemView, i < array.length() - 1, hierarchy);\n                    }\n                    itemView.addViewNoInvalidate(childItemView);\n                }\n\n                JsonItemView childItemView = new JsonItemView(itemView.getContext());\n                childItemView.setTextSize(TEXT_SIZE_DP);\n                childItemView.setRightColor(BRACES_COLOR);\n                StringBuilder builder = new StringBuilder(Utils.getHierarchyStr(hierarchy - 1));\n                builder.append(isJsonArray ? \"]\" : \"}\").append(appendComma ? \",\" : \"\");\n                childItemView.showRight(builder);\n                itemView.addViewNoInvalidate(childItemView);\n                itemView.requestLayout();\n                itemView.invalidate();\n            } else {                            // 折叠 <--> 展开\n                CharSequence temp = itemView.getRightText();\n                itemView.showRight((CharSequence) itemView.getTag());\n                itemView.setTag(temp);\n                itemView.showIcon(!isCollapsed);\n                for (int i = 1; i < itemView.getChildCount(); i++) {\n                    itemView.getChildAt(i).setVisibility(isCollapsed ? View.VISIBLE : View.GONE);\n                }\n                isCollapsed = !isCollapsed;\n            }\n        }\n    }\n\n    class JsonItemViewHolder extends RecyclerView.ViewHolder {\n\n        JsonItemView itemView;\n\n        JsonItemViewHolder(JsonItemView itemView) {\n            super(itemView);\n            setIsRecyclable(false);\n            this.itemView = itemView;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/jsonviewer/utils/Utils.java",
    "content": "package com.didichuxing.doraemonkit.widget.jsonviewer.utils;\n\nimport java.util.regex.Pattern;\n\n/**\n * Created by yuyuhang on 2017/11/30.\n */\npublic class Utils {\n\n    private static Pattern urlPattern = Pattern.compile(\"^((https|http|ftp|rtsp|mms)?://)\"\n            + \"?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?\" //ftp的user@\n            + \"(([0-9]{1,3}\\\\.){3}[0-9]{1,3}\" // IP形式的URL- 199.194.52.184\n            + \"|\" // 允许IP和DOMAIN（域名）\n            + \"([0-9a-z_!~*'()-]+\\\\.)*\" // 域名- www.\n            + \"([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\\\\.\" // 二级域名\n            + \"[a-z]{2,6})\" // first level domain- .com or .museum\n            + \"(:[0-9]{1,4})?\" // 端口- :80\n            + \"((/?)|\" // a slash isn't required if there is no file name\n            + \"(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$\");\n\n    /**\n     * 判断字符串是否是url\n     *\n     * @param str\n     * @return\n     */\n    public static boolean isUrl(String str) {\n        return urlPattern.matcher(str).matches();\n    }\n\n    /**\n     * json 格式化缩进(格式化前不能有缩进，最好是格式化从服务端下发的)\n     *\n     * @param jsonStr\n     * @return\n     */\n    public static String jsonFormat(String jsonStr) {\n        if (jsonStr == null) return \"\";\n        int level = 0;\n        StringBuilder builder = new StringBuilder();\n        for (int i = 0; i < jsonStr.length(); i++) {\n            char c = jsonStr.charAt(i);\n            if (level > 0 && '\\n' == builder.charAt(builder.length() - 1)) {\n                builder.append(getHierarchyStr(level));\n            }\n            switch (c) {\n                case '{':\n                case '[':\n                    builder.append(c).append(\"\\n\");\n                    level++;\n                    break;\n                case ',':\n                    builder.append(c).append(\"\\n\");\n                    break;\n                case '}':\n                case ']':\n                    builder.append(\"\\n\");\n                    level--;\n                    builder.append(getHierarchyStr(level));\n                    builder.append(c);\n                    break;\n                default:\n                    builder.append(c);\n                    break;\n            }\n        }\n\n        return builder.toString();\n    }\n\n    /**\n     * 对应层级前面所需的空格数\n     *\n     * @param hierarchy 缩进层级\n     * @return\n     */\n    public static String getHierarchyStr(int hierarchy) {\n        StringBuilder levelStr = new StringBuilder();\n        for (int levelI = 0; levelI < hierarchy; levelI++) {\n            levelStr.append(\"      \");\n        }\n        return levelStr.toString();\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/jsonviewer/view/JsonItemView.java",
    "content": "package com.didichuxing.doraemonkit.widget.jsonviewer.view;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.util.TypedValue;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.widget.jsonviewer.adapter.BaseJsonViewerAdapter;\n\n\n/**\n * Created by yuyuhang on 2017/11/29.\n */\npublic class JsonItemView extends LinearLayout {\n\n    public static int TEXT_SIZE_DP = 12;\n\n    private Context mContext;\n\n    private TextView mTvLeft, mTvRight;\n    private ImageView mIvIcon;\n\n    public JsonItemView(Context context) {\n        this(context, null);\n    }\n\n    public JsonItemView(Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public JsonItemView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n\n        mContext = context;\n\n        initView();\n    }\n\n    private void initView() {\n        setOrientation(VERTICAL);\n        LayoutInflater.from(mContext).inflate(R.layout.dk_jsonviewer_layout_item_view, this, true);\n\n        mTvLeft = findViewById(R.id.tv_left);\n        mTvRight = findViewById(R.id.tv_right);\n        mIvIcon = findViewById(R.id.iv_icon);\n    }\n\n    public void setTextSize(float textSizeDp) {\n        if (textSizeDp < 12) {\n            textSizeDp = 12;\n        } else if (textSizeDp > 30) {\n            textSizeDp = 30;\n        }\n\n        TEXT_SIZE_DP = (int) textSizeDp;\n\n        mTvLeft.setTextSize(TEXT_SIZE_DP);\n        mTvRight.setTextSize(TEXT_SIZE_DP);\n        mTvRight.setTextColor(BaseJsonViewerAdapter.BRACES_COLOR);\n\n        // align the vertically expand/collapse icon to the text\n        int textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, TEXT_SIZE_DP, getResources().getDisplayMetrics());\n\n        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mIvIcon.getLayoutParams();\n        layoutParams.height = textSize;\n        layoutParams.width = textSize;\n        layoutParams.topMargin = textSize / 5;\n\n        mIvIcon.setLayoutParams(layoutParams);\n    }\n\n    public void setRightColor(int color) {\n        mTvRight.setTextColor(color);\n    }\n\n    public void hideLeft() {\n        mTvLeft.setVisibility(GONE);\n    }\n\n    public void showLeft(CharSequence text) {\n        mTvLeft.setVisibility(VISIBLE);\n        if (text != null) {\n            mTvLeft.setText(text);\n        }\n    }\n\n    public void hideRight() {\n        mTvRight.setVisibility(GONE);\n    }\n\n    public void showRight(CharSequence text) {\n        mTvRight.setVisibility(VISIBLE);\n        if (text != null) {\n            mTvRight.setText(text);\n        }\n    }\n\n    public CharSequence getRightText() {\n        return mTvRight.getText();\n    }\n\n    public void hideIcon() {\n        mIvIcon.setVisibility(GONE);\n    }\n\n    public void showIcon(boolean isPlus) {\n        mIvIcon.setVisibility(VISIBLE);\n        mIvIcon.setImageResource(isPlus ? R.drawable.dk_jsonviewer_plus : R.drawable.dk_jsonviewer_minus);\n        mIvIcon.setContentDescription(getResources().getString(isPlus ? R.string.dk_jsonViewer_icon_plus : R.string.dk_jsonViewer_icon_minus));\n    }\n\n    public void setIconClickListener(OnClickListener listener) {\n        mIvIcon.setOnClickListener(listener);\n    }\n\n    public void addViewNoInvalidate(View child) {\n        ViewGroup.LayoutParams params = child.getLayoutParams();\n        if (params == null) {\n            params = generateDefaultLayoutParams();\n            if (params == null) {\n                throw new IllegalArgumentException(\"generateDefaultLayoutParams() cannot return null\");\n            }\n        }\n        addViewInLayout(child, -1, params);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/recyclerview/AbsRecyclerAdapter.java",
    "content": "package com.didichuxing.doraemonkit.widget.recyclerview;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 内置一个List的通用、简化的适用于RecyclerView的Adapter。\n * <p/>\n *\n * @author Jin Liang\n * @since 16/1/6\n */\npublic abstract class AbsRecyclerAdapter<T extends AbsViewBinder, V> extends RecyclerView.Adapter<T> {\n    private static final String TAG = \"AbsRecyclerAdapter\";\n    protected List<V> mList;\n    private LayoutInflater mInflater;\n    protected Context mContext;\n\n    public AbsRecyclerAdapter(Context context) {\n        if (context == null) {\n            LogHelper.e(TAG, \"Context should not be null\");\n            return;\n        }\n        mContext = context;\n        mList = new ArrayList<>();\n        mInflater = LayoutInflater.from(context);\n    }\n\n    @Override\n    public final T onCreateViewHolder(ViewGroup parent, int viewType) {\n        View view = createView(mInflater, parent, viewType);\n        return createViewHolder(view, viewType);\n    }\n\n    protected abstract T createViewHolder(View view, int viewType);\n\n    /**\n     * 如果是通过LayoutInflater创建的View,不要绑定到父View,RecyclerView会负责添加。\n     *\n     * @param inflater\n     * @param parent\n     * @param viewType\n     * @return\n     */\n    protected abstract View createView(LayoutInflater inflater, ViewGroup parent, int viewType);\n\n    @Override\n    public final void onBindViewHolder(T holder, int position) {\n        V data = mList.get(position);\n        holder.setData(data);\n        holder.bind(data, position);\n    }\n\n    @Override\n    public int getItemCount() {\n        return mList.size();\n    }\n\n    /**\n     * 列表末尾追加一个元素\n     *\n     * @param item\n     */\n    public void append(V item) {\n        if (item == null) {\n            return;\n        }\n        mList.add(item);\n        notifyDataSetChanged();\n    }\n\n    /**\n     * 在特定位置增加一个元素\n     *\n     * @param item\n     * @param position\n     */\n    public void append(V item, int position) {\n        if (item == null) {\n            return;\n        }\n        if (position < 0) {\n            position = 0;\n        } else if (position > mList.size()) {\n            position = mList.size();\n        }\n        mList.add(position, item);\n        notifyDataSetChanged();\n    }\n\n    /**\n     * 追加一个集合\n     *\n     * @param items\n     */\n    public final void append(Collection<V> items) {\n        if (items == null || items.size() == 0) {\n            return;\n        }\n        mList.addAll(items);\n        notifyDataSetChanged();\n    }\n\n    /**\n     * 清空集合\n     */\n    public final void clear() {\n        if (mList.isEmpty()) {\n            return;\n        }\n        mList.clear();\n        notifyDataSetChanged();\n    }\n\n    /**\n     * 删除一个元素\n     *\n     * @param item\n     */\n    public final void remove(V item) {\n        if (item == null) {\n            return;\n        }\n        if (mList.contains(item)) {\n            mList.remove(item);\n            notifyDataSetChanged();\n        }\n    }\n\n    /**\n     * 删除一个元素\n     *\n     * @param index\n     */\n    public final void remove(int index) {\n        if (index < mList.size()) {\n            mList.remove(index);\n            notifyDataSetChanged();\n        }\n    }\n\n    /**\n     * 删除一个集合\n     *\n     * @param items\n     */\n    public final void remove(Collection<V> items) {\n        if (items == null || items.size() == 0) {\n            return;\n        }\n        if (mList.removeAll(items)) {\n            notifyDataSetChanged();\n        }\n    }\n\n    /**\n     * 替换数据集合\n     *\n     * @param items\n     */\n    public void setData(Collection<V> items) {\n        if (items == null || items.size() == 0) {\n            return;\n        }\n        if (mList.size() > 0) {\n            mList.clear();\n        }\n        mList.addAll(items);\n        notifyDataSetChanged();\n    }\n\n    public List<V> getData() {\n        return new ArrayList<>(mList);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/recyclerview/AbsViewBinder.java",
    "content": "package com.didichuxing.doraemonkit.widget.recyclerview;\n\nimport android.content.Context;\nimport android.view.View;\n\nimport androidx.annotation.IdRes;\nimport androidx.recyclerview.widget.RecyclerView;\n\n/**\n * 简单封装的适用于RecyclerView的ViewHolder\n *\n * @author Jin Liang\n * @since 16/1/5\n */\npublic abstract class AbsViewBinder<T> extends RecyclerView.ViewHolder {\n    private T data;\n\n    private View mView;\n\n    public AbsViewBinder(final View view) {\n        super(view);\n        mView = view;\n        getViews();\n        view.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onViewClick(view, data);\n            }\n        });\n    }\n\n    protected final View getView() {\n        return mView;\n    }\n\n    protected abstract void getViews();\n\n    public final <V extends View> V getView(@IdRes int id) {\n        return (V) mView.findViewById(id);\n    }\n\n    public abstract void bind(T t);\n\n    public void bind(T t, int position) {\n        bind(t);\n    }\n\n    protected void onViewClick(View view, T data) {\n    }\n\n    protected final void setData(T data) {\n        this.data = data;\n    }\n\n    protected final Context getContext() {\n        return mView.getContext();\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/recyclerview/DividerItemDecoration.java",
    "content": "package com.didichuxing.doraemonkit.widget.recyclerview;\n\nimport android.annotation.TargetApi;\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.RecyclerView;\n\n/**\n * Created by jinliang on 2017/9/29.\n */\n\npublic class DividerItemDecoration extends RecyclerView.ItemDecoration {\n    public static final int HORIZONTAL = 0;\n    public static final int VERTICAL = 1;\n    private static final String TAG = \"TitleItem\";\n    private Drawable mDivider;\n    private int mOrientation;\n    private final Rect mBounds = new Rect();\n    private boolean mShowHeaderDivider;\n    private boolean mShowFooterDivider = true;\n\n    public DividerItemDecoration(int orientation) {\n        this.setOrientation(orientation);\n    }\n\n    public void setOrientation(int orientation) {\n        if (orientation != 0 && orientation != 1) {\n            throw new IllegalArgumentException(\"Invalid orientation. It should be either HORIZONTAL or VERTICAL\");\n        } else {\n            this.mOrientation = orientation;\n        }\n    }\n\n    public void setDrawable(@NonNull Drawable drawable) {\n        this.mDivider = drawable;\n    }\n\n    @Override\n    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {\n        if (parent.getLayoutManager() != null && this.mDivider != null) {\n            if (this.mOrientation == 1) {\n                this.drawVertical(c, parent);\n            } else {\n                this.drawHorizontal(c, parent);\n            }\n        }\n    }\n\n    public void showHeaderDivider(boolean show) {\n        mShowHeaderDivider = show;\n    }\n\n    public void showFooterDivider(boolean show) {\n        mShowFooterDivider = show;\n    }\n\n    private void drawVertical(Canvas canvas, RecyclerView parent) {\n        canvas.save();\n        int left;\n        int right;\n        if (parent.getClipToPadding()) {\n            left = parent.getPaddingLeft();\n            right = parent.getWidth() - parent.getPaddingRight();\n            int top = parent.getPaddingTop();\n            int bottom = parent.getHeight() - parent.getPaddingBottom();\n            canvas.clipRect(left, top, right, bottom);\n        } else {\n            left = 0;\n            right = parent.getWidth();\n        }\n\n        int childCount = parent.getChildCount();\n\n        for (int i = 0; i < childCount; ++i) {\n            View child = parent.getChildAt(i);\n            parent.getDecoratedBoundsWithMargins(child, this.mBounds);\n\n            int top;\n            int bottom;\n            if (i == 0 && mShowHeaderDivider) {\n                top = 0;\n                this.mDivider.setBounds(left, top, right, top + this.mDivider.getIntrinsicHeight());\n                this.mDivider.draw(canvas);\n            }\n\n            if (i != childCount - 1 || mShowFooterDivider) {\n                bottom = this.mBounds.bottom + Math.round(child.getTranslationY());\n                top = bottom - this.mDivider.getIntrinsicHeight();\n                this.mDivider.setBounds(left, top, right, bottom);\n                this.mDivider.draw(canvas);\n            }\n        }\n        canvas.restore();\n    }\n\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private void drawHorizontal(Canvas canvas, RecyclerView parent) {\n        canvas.save();\n        int top;\n        int bottom;\n        if (parent.getClipToPadding()) {\n            top = parent.getPaddingTop();\n            bottom = parent.getHeight() - parent.getPaddingBottom();\n            canvas.clipRect(parent.getPaddingLeft(), top, parent.getWidth() - parent.getPaddingRight(), bottom);\n        } else {\n            top = 0;\n            bottom = parent.getHeight();\n        }\n\n        int childCount = parent.getChildCount();\n\n        for (int i = 0; i < childCount; ++i) {\n            View child = parent.getChildAt(i);\n            parent.getLayoutManager().getDecoratedBoundsWithMargins(child, this.mBounds);\n            int right = this.mBounds.right + Math.round(child.getTranslationX());\n            int left = right - this.mDivider.getIntrinsicWidth();\n            this.mDivider.setBounds(left, top, right, bottom);\n            this.mDivider.draw(canvas);\n        }\n\n        canvas.restore();\n    }\n\n    @Override\n    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {\n        if (this.mDivider == null) {\n            outRect.set(0, 0, 0, 0);\n        } else {\n            if (this.mOrientation == 1) {\n                outRect.set(0, 0, 0, this.mDivider.getIntrinsicHeight());\n            } else {\n                outRect.set(0, 0, this.mDivider.getIntrinsicWidth(), 0);\n            }\n\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/SelectionOperation.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview;\n\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\nimport android.view.MotionEvent;\n\nimport com.didichuxing.doraemonkit.widget.tableview.intface.ISelectFormat;\nimport com.didichuxing.doraemonkit.widget.tableview.utils.MatrixHelper;\n\n\npublic class SelectionOperation implements MatrixHelper.OnInterceptListener {\n    /**\n     * 选中区域\n     */\n    private static final int INVALID = -1; //无效坐标\n    private Rect selectionRect;\n    private ISelectFormat selectFormat;\n    private int selectRow = INVALID;\n    private int selectColumn = INVALID;\n    private boolean isShow;\n\n    public void reset() {\n        isShow = false;\n    }\n\n    public SelectionOperation() {\n        this.selectionRect = new Rect();\n    }\n\n    public void setSelectionRect(int selectColumn, int selectRow, Rect rect) {\n        this.selectRow = selectRow;\n        this.selectColumn = selectColumn;\n        selectionRect.set(rect);\n        isShow = true;\n    }\n\n    public boolean isSelectedPoint(int selectColumn, int selectRow) {\n\n        return selectRow == this.selectRow && selectColumn == this.selectColumn;\n    }\n\n    public void checkSelectedPoint(int selectColumn, int selectRow, Rect rect) {\n\n        if (isSelectedPoint(selectColumn, selectRow)) {\n\n            selectionRect.set(rect);\n            isShow = true;\n        }\n    }\n\n\n    public void draw(Canvas canvas, Rect showRect, TableConfig config) {\n\n        if (selectFormat != null && isShow) {\n            selectFormat.draw(canvas, selectionRect, showRect, config);\n        }\n    }\n\n    public ISelectFormat getSelectFormat() {\n        return selectFormat;\n    }\n\n    public void setSelectFormat(ISelectFormat selectFormat) {\n        this.selectFormat = selectFormat;\n    }\n\n    @Override\n    public boolean isIntercept(MotionEvent e1, float distanceX, float distanceY) {\n        return false;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/TableConfig.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview;\n\nimport android.graphics.Paint;\n\nimport com.didichuxing.doraemonkit.widget.tableview.style.FontStyle;\nimport com.didichuxing.doraemonkit.widget.tableview.style.LineStyle;\n\n\npublic class TableConfig {\n    /**\n     * 默认字体样式\n     */\n    private static final FontStyle defaultFontStyle = new FontStyle();\n    /**\n     * 默认网格样式\n     */\n    private static final LineStyle defaultGridStyle = new LineStyle();\n    /**\n     * 无效值\n     */\n    public static final int INVALID_COLOR = 0;\n    /**\n     * 内容字体样式\n     */\n    public FontStyle contentStyle = defaultFontStyle;\n    /**\n     * 左侧序号列字体样式\n     */\n    public FontStyle YSequenceStyle = defaultFontStyle;\n    /**\n     * 列标题字体样式\n     */\n    public FontStyle columnTitleStyle = defaultFontStyle;\n    /**\n     * 表格标题字体样式\n     */\n    public FontStyle tableTitleStyle = defaultFontStyle;\n    /**\n     * 列标题网格样式\n     */\n    public LineStyle columnTitleGridStyle = defaultGridStyle;\n    /**\n     * 序列网格样式\n     */\n    public LineStyle SequenceGridStyle = defaultGridStyle;\n    /**\n     * 表格网格\n     */\n    public LineStyle contentGridStyle = defaultGridStyle;\n    /**\n     * 上下padding(为了表格的美观，暂只支持统一的padding)\n     */\n    private int verticalPadding = 10;\n    /**\n     * 文字左边偏移\n     */\n    private int textLeftOffset = 0;\n    /**\n     * 增加列序列左右padding\n     */\n    private int sequenceHorizontalPadding = 40;\n\n    /**\n     * 增加列标题上下padding\n     */\n    private int columnTitleVerticalPadding = 10;\n    /**\n     * 增加列标题左右padding\n     */\n    private int columnTitleHorizontalPadding = 40;\n    /**\n     * 左右padding(为了表格的美观，暂只支持统一的padding)\n     */\n    private int horizontalPadding = 40;\n\n    /**\n     * 是否显示左侧序号列\n     */\n    private boolean isShowYSequence = true;\n    /**\n     * 是否显示表格标题\n     */\n    private boolean isShowTableTitle = true;\n    /**\n     * 是否显示列标题\n     */\n    private boolean isShowColumnTitle = true;\n    /**\n     * 是否固定左侧\n     */\n    private boolean fixedYSequence = false;\n    /**\n     * 固定顶部\n     */\n    private boolean fixedXSequence = false;\n    /**\n     * 固定标题\n     */\n    private boolean fixedTitle = false;\n    /**\n     * 是否固定统计行\n     */\n    private boolean fixedCountRow = true;\n    /**\n     * 左上角空隙背景颜色\n     */\n    private int leftAndTopBackgroundColor;\n\n\n    private int minTableWidth = -1;\n    /**\n     * 画笔\n     */\n    private Paint paint;\n    /**\n     * 缩放值\n     */\n    private float zoom = 1;\n    private static TableConfig tableConfig;\n\n    public static TableConfig getInstance() {\n        if (tableConfig == null) {\n            tableConfig = new TableConfig();\n        }\n        return tableConfig;\n    }\n\n    private TableConfig() {\n\n    }\n\n    public int getVerticalPadding() {\n        return verticalPadding;\n    }\n\n    public TableConfig setVerticalPadding(int verticalPadding) {\n        this.verticalPadding = verticalPadding;\n        return this;\n    }\n\n    public int getHorizontalPadding() {\n        return horizontalPadding;\n    }\n\n    public TableConfig setHorizontalPadding(int horizontalPadding) {\n        this.horizontalPadding = horizontalPadding;\n        return this;\n    }\n\n    public Paint getPaint() {\n        return paint;\n    }\n\n    public void setPaint(Paint paint) {\n        this.paint = paint;\n    }\n\n\n    public boolean isFixedYSequence() {\n        return fixedYSequence;\n    }\n\n    public TableConfig setFixedYSequence(boolean fixedYSequence) {\n        this.fixedYSequence = fixedYSequence;\n        return this;\n    }\n\n    public boolean isFixedXSequence() {\n        return fixedXSequence;\n    }\n\n    public TableConfig setFixedXSequence(boolean fixedXSequence) {\n        this.fixedXSequence = fixedXSequence;\n        return this;\n    }\n\n    public boolean isFixedTitle() {\n        return fixedTitle;\n    }\n\n    public boolean isFixedCountRow() {\n        return fixedCountRow;\n    }\n\n    public TableConfig setFixedCountRow(boolean fixedCountRow) {\n        this.fixedCountRow = fixedCountRow;\n        return this;\n    }\n\n    public boolean isShowYSequence() {\n        return isShowYSequence;\n    }\n\n    public TableConfig setShowYSequence(boolean showYSequence) {\n        isShowYSequence = showYSequence;\n        return this;\n    }\n\n    public float getZoom() {\n        return zoom;\n    }\n\n    public void setZoom(float zoom) {\n        this.zoom = zoom;\n    }\n\n    public int getColumnTitleHorizontalPadding() {\n        return columnTitleHorizontalPadding;\n    }\n\n    public TableConfig setColumnTitleHorizontalPadding(int columnTitleHorizontalPadding) {\n        this.columnTitleHorizontalPadding = columnTitleHorizontalPadding;\n        return this;\n    }\n\n    public boolean isShowTableTitle() {\n        return isShowTableTitle;\n    }\n\n    public TableConfig setShowTableTitle(boolean showTableTitle) {\n        isShowTableTitle = showTableTitle;\n        return this;\n    }\n\n    public boolean isShowColumnTitle() {\n\n        return isShowColumnTitle;\n    }\n\n    public int getLeftAndTopBackgroundColor() {\n        return leftAndTopBackgroundColor;\n    }\n\n    public TableConfig setLeftAndTopBackgroundColor(int leftAndTopBackgroundColor) {\n        this.leftAndTopBackgroundColor = leftAndTopBackgroundColor;\n        return this;\n    }\n\n    public TableConfig setShowColumnTitle(boolean showColumnTitle) {\n        isShowColumnTitle = showColumnTitle;\n        return this;\n    }\n\n    public TableConfig setMinTableWidth(int minTableWidth) {\n        this.minTableWidth = minTableWidth;\n        return this;\n    }\n\n    public int getMinTableWidth() {\n        return minTableWidth;\n    }\n\n\n    public int getColumnTitleVerticalPadding() {\n        return columnTitleVerticalPadding;\n    }\n\n    public TableConfig setColumnTitleVerticalPadding(int columnTitleVerticalPadding) {\n        this.columnTitleVerticalPadding = columnTitleVerticalPadding;\n        return this;\n    }\n\n\n    public int getSequenceHorizontalPadding() {\n        return sequenceHorizontalPadding;\n    }\n\n    public TableConfig setSequenceHorizontalPadding(int sequenceHorizontalPadding) {\n        this.sequenceHorizontalPadding = sequenceHorizontalPadding;\n        return this;\n    }\n\n    public int getTextLeftOffset() {\n        return textLeftOffset;\n    }\n\n    public TableConfig setTextLeftOffset(int textLeftOffset) {\n        this.textLeftOffset = textLeftOffset;\n        return this;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/TableMeasurer.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview;\n\nimport android.graphics.Paint;\nimport android.graphics.Rect;\n\n\nimport com.didichuxing.doraemonkit.widget.tableview.bean.Cell;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.Column;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.ColumnInfo;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.TableData;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.TableInfo;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.IComponent;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.ITableTitle;\n\nimport java.util.List;\n\npublic class TableMeasurer<T> {\n\n    private boolean isReMeasure; //是否重新计算\n\n    public TableInfo measure(TableData<T> tableData, int width, int height) {\n        isReMeasure = true;\n        TableInfo tableInfo = tableData.getTableInfo();\n\n        width = Math.max(getTableWidth(tableData), width);\n        height = Math.max(getTableHeight(tableData), height);\n\n        tableInfo.setTableRect(new Rect(0, 0, width, height));\n        measureColumnSize(tableData);\n        return tableInfo;\n    }\n\n\n    public void measureTableTitle(TableData<T> tableData, ITableTitle tableTitle, Rect showRect) {\n        TableInfo tableInfo = tableData.getTableInfo();\n        Rect tableRect = tableInfo.getTableRect();\n        if (isReMeasure) {\n            isReMeasure = false;\n            int size = tableTitle.getSize();\n            tableInfo.setTitleDirection(tableTitle.getDirection());\n            tableInfo.setTableTitleSize(size);\n            if (tableTitle.getDirection() == IComponent.TOP ||\n                    tableTitle.getDirection() == IComponent.BOTTOM) {\n                int height = size;\n                tableRect.bottom += height;\n                reSetShowRect(showRect, tableRect);\n            } else {\n                int width = size;\n                tableRect.right += width;\n                reSetShowRect(showRect, tableRect);\n            }\n        } else {\n            reSetShowRect(showRect, tableRect);\n        }\n\n    }\n\n    /**\n     * 重新计算显示大小\n     *\n     * @param showRect\n     * @param tableRect\n     */\n    public void reSetShowRect(Rect showRect, Rect tableRect) {\n        if (showRect.bottom > tableRect.bottom) {\n            showRect.bottom = tableRect.bottom;\n        }\n        if (showRect.right > tableRect.right) {\n            showRect.right = tableRect.right;\n        }\n    }\n\n    /**\n     * 添加table高度\n     *\n     * @param tableData\n     * @return\n     */\n    public void addTableHeight(TableData<T> tableData) {\n//        TableInfo tableInfo = tableData.getTableInfo();\n//        Rect tableRect = tableInfo.getTableRect();\n//        int[] lineArray = tableInfo.getLineHeightArray();\n//        for(int i = startPosition;i<lineArray.length;i++){\n//           tableRect.bottom+= lineArray[i];\n//        }\n        TableInfo tableInfo = tableData.getTableInfo();\n        int width = getTableWidth(tableData);\n        int height = getTableHeight(tableData);\n        tableInfo.setTableRect(new Rect(0, 0, width, height));\n    }\n\n\n    /**\n     * 计算table高度\n     *\n     * @param tableData\n     * @param config\n     * @return\n     */\n    private int getTableHeight(TableData<T> tableData) {\n        TableConfig config = TableConfig.getInstance();\n        int topHeight = 0;\n        int titleHeight = config.isShowColumnTitle() ? (tableData.getTitleDrawFormat().measureHeight(config)\n                + 2 * config.getColumnTitleVerticalPadding()) : 0;\n        TableInfo tableInfo = tableData.getTableInfo();\n        tableInfo.setTitleHeight(titleHeight);\n        tableInfo.setTopHeight(topHeight);\n        int totalContentHeight = 0;\n        for (int height : tableInfo.getLineHeightArray()) {\n            totalContentHeight += height;\n        }\n        int totalTitleHeight = titleHeight * tableInfo.getMaxLevel();\n        int totalHeight = topHeight + totalTitleHeight + totalContentHeight;\n\n        return totalHeight;\n    }\n\n    /**\n     * 计算table宽度\n     *\n     * @param tableData\n     * @param config\n     * @return\n     */\n    private int getTableWidth(TableData<T> tableData) {\n        int totalWidth = 0;\n        TableConfig config = TableConfig.getInstance();\n        Paint paint = config.getPaint();\n        config.YSequenceStyle.fillPaint(paint);\n        int totalSize = tableData.getLineSize();\n        //计算Y轴宽度距离\n        if (config.isShowYSequence()) {\n            int yAxisWidth = (int) paint.measureText(tableData.getYSequenceFormat().format(totalSize)\n                    + 2 * config.getSequenceHorizontalPadding());\n            tableData.getTableInfo().setyAxisWidth(yAxisWidth);\n            totalWidth += yAxisWidth;\n        }\n        int columnPos = 0;\n        int contentWidth = 0;\n        int[] lineHeightArray = tableData.getTableInfo().getLineHeightArray();\n        int currentPosition, size;\n        for (Column column : tableData.getChildColumns()) {\n            float columnNameWidth = tableData.getTitleDrawFormat().measureWidth(column, config) + config.getColumnTitleHorizontalPadding() * 2;\n            int columnWidth = 0;\n            size = column.getDatas().size();\n            currentPosition = 0;\n            Cell[][] rangeCells = tableData.getTableInfo().getRangeCells();\n            for (int position = 0; position < size; position++) {\n                int width = column.getDrawFormat().measureWidth(column, position);\n                measureRowHeight(lineHeightArray, column, currentPosition, position);\n                currentPosition += 1;\n                //为了解决合并单元宽度过大问题\n                if (rangeCells != null) {\n                    Cell cell = rangeCells[position][columnPos];\n                    if (cell != null) {\n                        if (cell.row != Cell.INVALID && cell.col != Cell.INVALID) {\n                            cell.width = width;\n                            width = width / cell.col;\n                        } else if (cell.realCell != null) {\n                            width = cell.realCell.width / cell.realCell.col;\n                        }\n\n                    }\n                }\n                if (columnWidth < width) {\n                    columnWidth = width;\n                }\n            }\n            int width = (int) (Math.max(columnNameWidth, columnWidth + 2 * config.getHorizontalPadding()));\n            width = Math.max(column.getMinWidth(), width);\n            column.setComputeWidth(width);\n            contentWidth += width;\n            columnPos++;\n        }\n        int minWidth = config.getMinTableWidth();\n        //计算出来的宽度大于最小宽度\n        if (minWidth == -1 || minWidth - totalWidth < contentWidth) {\n            totalWidth += contentWidth;\n        } else {\n            minWidth -= totalWidth;\n            float widthScale = ((float) minWidth) / contentWidth;\n            for (Column column : tableData.getChildColumns()) {\n                column.setComputeWidth((int) (widthScale * column.getComputeWidth()));\n            }\n            totalWidth += minWidth;\n        }\n        return totalWidth;\n    }\n\n\n    /**\n     * 测量行高\n     *\n     * @param config\n     * @param lineHeightArray\n     * @param column\n     * @param position\n     */\n    private void measureRowHeight(int[] lineHeightArray, Column column, int currentPosition, int position) {\n        TableConfig config = TableConfig.getInstance();\n\n        int height = 0;\n        if (height == 0) {\n            height = column.getDrawFormat().measureHeight(column, position) +\n                    2 * config.getVerticalPadding();\n        }\n        height = Math.max(column.getMinHeight(), height);\n        if (height > lineHeightArray[currentPosition]) {\n            lineHeightArray[currentPosition] = height;\n        }\n    }\n\n    /**\n     * 测量列的Rect\n     *\n     * @param tableData\n     */\n    private void measureColumnSize(TableData<T> tableData) {\n        List<Column> columnList = tableData.getColumns();\n        int left = 0;\n        int maxLevel = tableData.getTableInfo().getMaxLevel();\n        tableData.getColumnInfos().clear();\n        tableData.getChildColumnInfos().clear();\n        for (int i = 0; i < columnList.size(); i++) {\n            int top = 0;\n            Column column = columnList.get(i);\n            ColumnInfo columnInfo = getColumnInfo(tableData, column, null, left, top, maxLevel);\n            left += columnInfo.width;\n        }\n    }\n\n    public ColumnInfo getColumnInfo(TableData<T> tableData, Column column, ColumnInfo parent, int left, int top, int overLevel) {\n        TableInfo tableInfo = tableData.getTableInfo();\n        ColumnInfo columnInfo = new ColumnInfo();\n        columnInfo.value = column.getColumnName();\n        columnInfo.column = column;\n        columnInfo.width = column.getComputeWidth();\n        columnInfo.top = top;\n        columnInfo.height = tableInfo.getTitleHeight() * overLevel;\n        columnInfo.left = left;\n\n        tableData.getChildColumnInfos().add(columnInfo);\n        tableData.getColumnInfos().add(columnInfo);\n\n\n        return columnInfo;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/TableParser.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview;\n\nimport com.didichuxing.doraemonkit.widget.tableview.bean.ArrayTableData;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.Column;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.TableData;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.TableInfo;\n\nimport java.util.List;\n\npublic class TableParser<T> {\n\n    /**\n     * 解析数据\n     */\n    public List<Column> parse(TableData<T> tableData) {\n\n        tableData.getChildColumns().clear();\n        tableData.getColumnInfos().clear();\n        int maxLevel = getChildColumn(tableData);\n        TableInfo tableInfo = tableData.getTableInfo();\n        tableInfo.setColumnSize(tableData.getChildColumns().size());\n        tableInfo.setMaxLevel(maxLevel);\n        if (!(tableData instanceof ArrayTableData)) {\n            for (Column column : tableData.getChildColumns()) {\n                column.getDatas().clear();\n            }\n        }\n        return tableData.getColumns();\n    }\n\n    /**\n     * 添加数据\n     */\n    public void addData(TableData<T> tableData, List<T> addData, boolean isFoot) {\n        if (isFoot) {\n            tableData.getT().addAll(addData);\n        } else {\n            tableData.getT().addAll(0, addData);\n        }\n        TableInfo tableInfo = tableData.getTableInfo();\n        tableInfo.addLine(addData.size(), isFoot);\n\n\n    }\n\n    private int getChildColumn(TableData<T> tableData) {\n        int maxLevel = 0;\n        for (Column column : tableData.getColumns()) {\n            int level = getColumnLevel(tableData, column, 0);\n            if (level > maxLevel) {\n                maxLevel = level;\n            }\n        }\n        return maxLevel;\n    }\n\n    /**\n     * 得到列的层级\n     *\n     * @param tableData 表格数据\n     * @param column    列\n     * @param level     层级\n     * @return\n     */\n    private int getColumnLevel(TableData<T> tableData, Column column, int level) {\n        level++;\n        tableData.getChildColumns().add(column);\n        return level;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/TableProvider.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview;\n\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.PointF;\nimport android.graphics.Rect;\n\nimport com.didichuxing.doraemonkit.widget.tableview.bean.Cell;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.CellInfo;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.Column;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.ColumnInfo;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.TableData;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.TableInfo;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.ISelectFormat;\nimport com.didichuxing.doraemonkit.widget.tableview.listener.OnColumnClickListener;\nimport com.didichuxing.doraemonkit.widget.tableview.listener.TableClickObserver;\nimport com.didichuxing.doraemonkit.widget.tableview.utils.DrawUtils;\n\nimport java.util.List;\npublic class TableProvider<T> implements TableClickObserver {\n\n\n    private Rect scaleRect;\n    private Rect showRect;\n    private TableConfig config;\n    private PointF clickPoint;\n    private ColumnInfo clickColumnInfo;\n    private boolean isClickPoint;\n    private OnColumnClickListener onColumnClickListener;\n    /**\n     * 选中格子格式化\n     */\n    private SelectionOperation operation;\n    private TableData<T> tableData;\n    private Rect clipRect;\n    private Rect tempRect; //用于存储数据\n    private PointF tipPoint = new PointF();\n    private CellInfo cellInfo = new CellInfo();\n\n    public TableProvider() {\n\n        clickPoint = new PointF(-1, -1);\n        clipRect = new Rect();\n        tempRect = new Rect();\n        operation = new SelectionOperation();\n        config = TableConfig.getInstance();\n    }\n\n    /**\n     * 绘制\n     *\n     * @param canvas    画布\n     * @param scaleRect 缩放Rect\n     * @param showRect  显示Rect\n     * @param tableData 表格数据\n     * @param config    配置\n     */\n    public void onDraw(Canvas canvas, Rect scaleRect, Rect showRect,\n                       TableData<T> tableData) {\n        setData(scaleRect, showRect, tableData);\n        canvas.save();\n        canvas.clipRect(this.showRect);\n        drawColumnTitle(canvas);\n        drawCount(canvas);\n        drawContent(canvas);\n        operation.draw(canvas, showRect, config);\n        canvas.restore();\n        if (isClickPoint && clickColumnInfo != null) {\n            onColumnClickListener.onClick(clickColumnInfo);\n        }\n    }\n\n    /**\n     * 设置基本信息和清除数据\n     *\n     * @param scaleRect 缩放Rect\n     * @param showRect  显示Rect\n     * @param tableData 表格数据\n     */\n    private void setData(Rect scaleRect, Rect showRect, TableData<T> tableData) {\n        isClickPoint = false;\n        clickColumnInfo = null;\n        operation.reset();\n        this.scaleRect = scaleRect;\n        this.showRect = showRect;\n        this.tableData = tableData;\n    }\n\n\n    private void drawColumnTitle(Canvas canvas) {\n        if (config.isShowColumnTitle()) {\n            if (config.isFixedTitle()) {\n                drawTitle(canvas);\n                canvas.restore();\n                canvas.save();\n                canvas.clipRect(this.showRect);\n            } else {\n                drawTitle(canvas);\n            }\n        }\n    }\n\n    /**\n     * 绘制统计行\n     *\n     * @param canvas 画布\n     */\n    private void drawCount(Canvas canvas) {\n\n        float left = scaleRect.left;\n        float bottom = config.isFixedCountRow() ? Math.min(scaleRect.bottom, showRect.bottom) : scaleRect.bottom;\n        int countHeight = tableData.getTableInfo().getCountHeight();\n        float top = bottom - countHeight;\n        List<ColumnInfo> childColumnInfos = tableData.getChildColumnInfos();\n        if (DrawUtils.isVerticalMixRect(showRect, (int) top, (int) bottom)) {\n            List<Column> columns = tableData.getChildColumns();\n            int columnSize = columns.size();\n            boolean isPerColumnFixed = false;\n            clipRect.set(showRect);\n            int clipCount = 0;\n            for (int i = 0; i < columnSize; i++) {\n                Column column = columns.get(i);\n                float tempLeft = left;\n                float width = column.getComputeWidth() * config.getZoom();\n                if (childColumnInfos.get(i).column.isFixed()) {\n                    if (left < clipRect.left) {\n                        left = clipRect.left;\n                        clipRect.left += width;\n                        isPerColumnFixed = true;\n                    }\n                } else if (isPerColumnFixed) {\n                    canvas.save();\n                    clipCount++;\n                    canvas.clipRect(clipRect.left, showRect.bottom - countHeight,\n                            showRect.right, showRect.bottom);\n                }\n                tempRect.set((int) left, (int) top, (int) (left + width), (int) bottom);\n                left = tempLeft;\n                left += width;\n            }\n            for (int i = 0; i < clipCount; i++) {\n                canvas.restore();\n            }\n        }\n    }\n\n    /**\n     * 绘制列标题\n     *\n     * @param canvas 画布\n     */\n    private void drawTitle(Canvas canvas) {\n        int dis = showRect.top - scaleRect.top;\n        TableInfo tableInfo = tableData.getTableInfo();\n        int titleHeight = tableInfo.getTitleHeight() * tableInfo.getMaxLevel();\n        int clipHeight = config.isFixedTitle() ? titleHeight : Math.max(0, titleHeight - dis);\n\n        clipRect.set(showRect);\n        List<ColumnInfo> columnInfoList = tableData.getColumnInfos();\n        float zoom = config.getZoom();\n        boolean isPerColumnFixed = false;\n        int clipCount = 0;\n        ColumnInfo parentColumnInfo = null;\n        for (ColumnInfo info : columnInfoList) {\n            int left = (int) (info.left * zoom + scaleRect.left);\n            //根据top ==0是根部，根据最根部的Title判断是否需要固定\n            if (info.top == 0 && info.column.isFixed()) {\n                if (left < clipRect.left) {\n                    parentColumnInfo = info;\n                    left = clipRect.left;\n                    fillColumnTitle(canvas, info, left);\n                    clipRect.left += info.width * zoom;\n                    isPerColumnFixed = true;\n                    continue;\n                }\n                //根部需要固定，同时固定所有子类\n            } else if (isPerColumnFixed && info.top != 0) {\n                left = (int) (clipRect.left - info.width * zoom);\n                left += (info.left - parentColumnInfo.left);\n            } else if (isPerColumnFixed) {\n                canvas.save();\n                canvas.clipRect(clipRect.left, showRect.top, showRect.right,\n                        showRect.top + clipHeight);\n                isPerColumnFixed = false;\n                clipCount++;\n            }\n            fillColumnTitle(canvas, info, left);\n        }\n        for (int i = 0; i < clipCount; i++) {\n            canvas.restore();\n        }\n        if (config.isFixedTitle()) {\n            scaleRect.top += titleHeight;\n            showRect.top += titleHeight;\n        } else {\n            showRect.top += clipHeight;\n            scaleRect.top += titleHeight;\n        }\n\n    }\n\n    /**\n     * 填充列标题\n     *\n     * @param canvas 画布\n     * @param info   列信息\n     * @param left   左边\n     */\n    private void fillColumnTitle(Canvas canvas, ColumnInfo info, int left) {\n\n        int top = (int) (info.top * config.getZoom())\n                + (config.isFixedTitle() ? showRect.top : scaleRect.top);\n        int right = (int) (left + info.width * config.getZoom());\n        int bottom = (int) (top + info.height * config.getZoom());\n        if (DrawUtils.isMixRect(showRect, left, top, right, bottom)) {\n            if (!isClickPoint && onColumnClickListener != null) {\n                if (DrawUtils.isClick(left, top, right, bottom, clickPoint)) {\n                    isClickPoint = true;\n                    clickColumnInfo = info;\n                    clickPoint.set(-1, -1);\n                }\n            }\n\n            Paint paint = config.getPaint();\n            tempRect.set(left, top, right, bottom);\n            config.columnTitleGridStyle.fillPaint(paint);\n            canvas.drawRect(tempRect, paint);\n\n            tableData.getTitleDrawFormat().draw(canvas, info.column, tempRect, config);\n\n        }\n    }\n\n    /**\n     * 绘制内容\n     *\n     * @param canvas 画布\n     */\n    private void drawContent(Canvas canvas) {\n        float top;\n        float left = scaleRect.left;\n        List<Column> columns = tableData.getChildColumns();\n        clipRect.set(showRect);\n        TableInfo info = tableData.getTableInfo();\n        int columnSize = columns.size();\n\n        if (config.isFixedCountRow()) {\n            canvas.save();\n            canvas.clipRect(showRect.left, showRect.top, showRect.right, showRect.bottom - info.getCountHeight());\n        }\n        List<ColumnInfo> childColumnInfo = tableData.getChildColumnInfos();\n        boolean isPerFixed = false;\n        int clipCount = 0;\n        Rect correctCellRect;\n        for (int i = 0; i < columnSize; i++) {\n            top = scaleRect.top;\n            Column column = columns.get(i);\n            float width = column.getComputeWidth() * config.getZoom();\n            float tempLeft = left;\n            //根据根部标题是否固定\n            Column topColumn = childColumnInfo.get(i).column;\n            if (topColumn.isFixed()) {\n                isPerFixed = false;\n                if (tempLeft < clipRect.left) {\n                    left = clipRect.left;\n                    clipRect.left += width;\n                    isPerFixed = true;\n                }\n            } else if (isPerFixed) {\n                canvas.save();\n                canvas.clipRect(clipRect);\n                isPerFixed = false;\n                clipCount++;\n            }\n            float right = left + width;\n\n            if (left < showRect.right) {\n                int size = column.getDatas().size();\n                int realPosition = 0;\n                for (int j = 0; j < size; j++) {\n                    String value = column.format(j);\n                    int totalLineHeight = 0;\n                    for (int k = realPosition; k < realPosition + 1; k++) {\n                        totalLineHeight += info.getLineHeightArray()[k];\n                    }\n                    realPosition += 1;\n                    float bottom = top + totalLineHeight * config.getZoom();\n                    tempRect.set((int) left, (int) top, (int) right, (int) bottom);\n                    correctCellRect = correctCellRect(j, i, tempRect, config.getZoom()); //矫正格子的大小\n                    if (correctCellRect != null) {\n                        if (correctCellRect.top < showRect.bottom) {\n                            if (correctCellRect.right > showRect.left && correctCellRect.bottom > showRect.top) {\n                                Object data = column.getDatas().get(j);\n                                if (DrawUtils.isClick(correctCellRect, clickPoint)) {\n                                    operation.setSelectionRect(i, j, correctCellRect);\n                                    tipPoint.x = (left + right) / 2;\n                                    tipPoint.y = (top + bottom) / 2;\n                                    clickColumn(column, j, value, data);\n                                    isClickPoint = true;\n                                    clickPoint.set(-Integer.MAX_VALUE, -Integer.MAX_VALUE);\n                                }\n                                operation.checkSelectedPoint(i, j, correctCellRect);\n                                cellInfo.set(column, data, value, i, j);\n                                drawContentCell(canvas, cellInfo, correctCellRect);\n\n                            }\n                        } else {\n                            break;\n                        }\n                    }\n                    top = bottom;\n                }\n                left = tempLeft + width;\n            } else {\n                break;\n            }\n        }\n        for (int i = 0; i < clipCount; i++) {\n            canvas.restore();\n        }\n        if (config.isFixedCountRow()) {\n            canvas.restore();\n        }\n    }\n\n    /**\n     * 绘制内容格子\n     *\n     * @param c        画布\n     * @param cellInfo 格子信息\n     * @param rect     方位\n     * @param config   表格配置\n     */\n    protected void drawContentCell(Canvas c, CellInfo<T> cellInfo, Rect rect) {\n\n        config.contentGridStyle.fillPaint(config.getPaint());\n        c.drawRect(rect, config.getPaint());\n\n        rect.left += config.getTextLeftOffset();\n        cellInfo.column.getDrawFormat().draw(c, rect, cellInfo);\n    }\n\n    /**\n     * 点击格子\n     *\n     * @param column   列\n     * @param position 位置\n     * @param value    值\n     * @param data     数据\n     */\n    private void clickColumn(Column column, int position, String value, Object data) {\n        if (!isClickPoint && column.getOnColumnItemClickListener() != null) {\n            column.getOnColumnItemClickListener().onClick(column, value, data, position);\n        }\n    }\n\n    @Override\n    public void onClick(float x, float y) {\n        clickPoint.x = x;\n        clickPoint.y = y;\n    }\n\n    public OnColumnClickListener getOnColumnClickListener() {\n        return onColumnClickListener;\n    }\n\n    public void setOnColumnClickListener(OnColumnClickListener onColumnClickListener) {\n        this.onColumnClickListener = onColumnClickListener;\n    }\n\n    public void setSelectFormat(ISelectFormat selectFormat) {\n        this.operation.setSelectFormat(selectFormat);\n    }\n\n    /**\n     * 计算任何point在View的位置\n     *\n     * @param row 列\n     * @param col 行\n     * @return\n     */\n    public int[] getPointLocation(double row, double col) {\n        List<Column> childColumns = tableData.getChildColumns();\n        int[] lineHeights = tableData.getTableInfo().getLineHeightArray();\n        int x = 0, y = 0;\n        int columnSize = childColumns.size();\n        for (int i = 0; i <= (columnSize > col + 1 ? col + 1 : columnSize - 1); i++) {\n            int w = childColumns.get(i).getComputeWidth();\n            if (i == (int) col + 1) {\n                x += w * (col - (int) col);\n            } else {\n                x += w;\n            }\n        }\n        for (int i = 0; i <= (lineHeights.length > row + 1 ? row + 1 : lineHeights.length - 1); i++) {\n            int h = lineHeights[i];\n            if (i == (int) row + 1) {\n                y += h * (row - (int) row);\n            } else {\n                y += h;\n            }\n        }\n        x *= config.getZoom();\n        y *= config.getZoom();\n        x += scaleRect.left;\n        y += scaleRect.top;\n        return new int[]{x, y};\n\n    }\n\n    /**\n     * 计算任何point在View的大小\n     *\n     * @param row 列\n     * @param col 行\n     * @return\n     */\n    public int[] getPointSize(int row, int col) {\n        List<Column> childColumns = tableData.getChildColumns();\n        int[] lineHeights = tableData.getTableInfo().getLineHeightArray();\n        col = col < childColumns.size() ? col : childColumns.size() - 1;//列\n        row = row < lineHeights.length ? row : lineHeights.length;//行\n        col = col < 0 ? 0 : col;\n        row = row < 0 ? 0 : row;\n        return new int[]{(int) (childColumns.get(col).getComputeWidth() * config.getZoom()),\n                (int) (lineHeights[row] * config.getZoom())};\n\n    }\n\n    private Rect correctCellRect(int row, int col, Rect rect, float zoom) {\n        Cell[][] rangePoints = tableData.getTableInfo().getRangeCells();\n        if (rangePoints != null && rangePoints.length > row) {\n            Cell point = rangePoints[row][col];\n            if (point != null) {\n                if (point.col != Cell.INVALID && point.row != Cell.INVALID) {\n                    List<Column> childColumns = tableData.getChildColumns();\n                    int[] lineHeights = tableData.getTableInfo().getLineHeightArray();\n                    int width = 0, height = 0;\n                    for (int i = col; i < Math.min(childColumns.size(), col + point.col); i++) {\n                        width += childColumns.get(i).getComputeWidth();\n                    }\n                    for (int i = row; i < Math.min(lineHeights.length, row + point.row); i++) {\n                        height += lineHeights[i];\n                    }\n                    rect.right = (int) (rect.left + width * zoom);\n                    rect.bottom = (int) (rect.top + height * zoom);\n                    return rect;\n                }\n                return null;\n            }\n        }\n        return rect;\n    }\n\n    public SelectionOperation getOperation() {\n        return operation;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/bean/ArrayTableData.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.bean;\n\n\nimport com.didichuxing.doraemonkit.widget.tableview.TableConfig;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.IDrawFormat;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.IFormat;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class ArrayTableData<T> extends TableData<T> {\n\n    private T[][] data;\n    private List<Column<T>> arrayColumns;\n\n    /**\n     * 创建二维数组表格数据\n     * 如果数据不是数组[row][col]，可以使用transformColumnArray方法转换\n     *\n     * @param tableName  表名\n     * @param titleNames 列名\n     * @param data       数据 数组[row][col]\n     * @param drawFormat 数据格式化\n     * @return 创建的二维数组表格数据\n     */\n    public static <T> ArrayTableData<T> create(String tableName, String[] titleNames, T[][] data, IDrawFormat<T> drawFormat) {\n        List<Column<T>> columns = new ArrayList<>();\n        for (int i = 0; i < data.length; i++) {\n            T[] dataArray = data[i];\n            Column<T> column = new Column<>(titleNames == null ? \"\" : titleNames[i], null, drawFormat);\n            column.setDatas(Arrays.asList(dataArray));\n            columns.add(column);\n        }\n        ArrayList<T> arrayList = new ArrayList<>(Arrays.asList(data[0]));\n        ArrayTableData<T> tableData = new ArrayTableData<>(tableName, arrayList, columns);\n        tableData.setData(data);\n        return tableData;\n    }\n\n    /**\n     * 创建不需要显示列名的二维数组表格数据\n     * 如果数据不是数组[row][col]，可以使用transformColumnArray方法转换\n     *\n     * @param tableName  表名\n     * @param data       数据 数组[row][col]\n     * @param drawFormat 数据格式化\n     * @return 创建的二维数组表格数据\n     */\n    public static <T> ArrayTableData<T> create(String tableName, T[][] data, IDrawFormat<T> drawFormat) {\n        TableConfig.getInstance().setShowColumnTitle(false);\n        return create(tableName, null, data, drawFormat);\n    }\n\n    /**\n     * 设置默认格式化\n     *\n     * @param format\n     */\n    public void setFormat(IFormat<T> format) {\n        for (Column<T> column : arrayColumns) {\n            column.setFormat(format);\n        }\n    }\n\n    /**\n     * 设置绘制格式化\n     *\n     * @param format\n     */\n    public void setDrawFormat(IDrawFormat<T> format) {\n        for (Column<T> column : arrayColumns) {\n            column.setDrawFormat(format);\n        }\n    }\n\n    /**\n     * 设置最小宽度\n     *\n     * @param minWidth\n     */\n    public void setMinWidth(int minWidth) {\n        for (Column<T> column : arrayColumns) {\n            column.setMinWidth(minWidth);\n        }\n    }\n\n    /**\n     * 设置最小高度\n     *\n     * @param minHeight\n     */\n    public void setMinHeight(int minHeight) {\n        for (Column<T> column : arrayColumns) {\n            column.setMinHeight(minHeight);\n        }\n    }\n\n\n    /**\n     * 二维数组的构造方法\n     *\n     * @param tableName 表名\n     * @param t         数据\n     * @param columns   列\n     */\n    protected ArrayTableData(String tableName, List<T> t, List<Column<T>> columns) {\n        super(tableName, t, new ArrayList<Column>(columns));\n        this.arrayColumns = columns;\n    }\n\n    /**\n     * 获取当前的列\n     */\n    public List<Column<T>> getArrayColumns() {\n        return arrayColumns;\n    }\n\n\n    /**\n     * 获取二维数组数据\n     */\n    public T[][] getData() {\n        return data;\n    }\n\n    /**\n     * 设置二维数组数据\n     *\n     * @param data\n     */\n    public void setData(T[][] data) {\n        this.data = data;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/bean/Cell.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.bean;\n\npublic class Cell {\n    public static final int INVALID = -1;\n\n    public int col;\n    public int row;\n    public Cell realCell;\n    public int width;\n    public int height;\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/bean/CellInfo.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.bean;\n\npublic class CellInfo<T> {\n    /**\n     * 数据\n     */\n    public T data;\n    /**\n     * 所在行位置\n     */\n    public int row;\n    /**\n     * 所在列位置\n     */\n    public int col;\n\n    /**\n     * 所在列\n     */\n    public Column<T> column;\n    /**\n     * 显示的值\n     */\n    public String value;\n\n    public void set(Column<T> column, T t, String value, int col, int row) {\n        this.column = column;\n        this.value = value;\n        this.data = t;\n        this.row = row;\n        this.col = col;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/bean/Column.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.bean;\n\nimport android.graphics.Paint;\n\nimport com.didichuxing.doraemonkit.widget.tableview.format.FastTextDrawFormat;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.IDrawFormat;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.IFormat;\nimport com.didichuxing.doraemonkit.widget.tableview.listener.OnColumnItemClickListener;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n\npublic class Column<T> implements Comparable<Column> {\n\n    public static final String INVAL_VALUE = \"\";\n    /**\n     * 列名\n     */\n    private String columnName;\n\n    private IFormat<T> format;\n    private IDrawFormat<T> drawFormat = new FastTextDrawFormat<T>();\n    private List<T> datas;\n    private boolean isFixed;\n    private int computeWidth;\n    private OnColumnItemClickListener<T> onColumnItemClickListener;\n    private Paint.Align textAlign;\n    private Paint.Align titleAlign;\n    private int id;\n    private int minWidth;\n    private int minHeight;\n    private int width;\n\n\n    /**\n     * 列构造方法\n     * 用于构造子列\n     *\n     * @param columnName 列名\n     * @param fieldName  需要解析的反射字段\n     * @param format     文字格式化\n     * @param drawFormat 绘制格式化\n     */\n    public Column(String columnName, IFormat<T> format, IDrawFormat<T> drawFormat) {\n        this.columnName = columnName;\n        this.format = format;\n        if (drawFormat != null) {\n            this.drawFormat = drawFormat;\n        }\n        datas = new ArrayList<>();\n    }\n\n\n    /**\n     * 获取列名\n     *\n     * @return 列名\n     */\n    public String getColumnName() {\n        return columnName;\n    }\n\n    /**\n     * 设置列名\n     *\n     * @param columnName 列名\n     */\n    public void setColumnName(String columnName) {\n        this.columnName = columnName;\n    }\n\n    /**\n     * 获取文字格式化\n     *\n     * @return 文字格式化\n     */\n    public IFormat<T> getFormat() {\n        return format;\n    }\n\n    /**\n     * 设置文字格式化\n     */\n    public void setFormat(IFormat<T> format) {\n        this.format = format;\n    }\n\n    /**\n     * 获取绘制格式化\n     *\n     * @return 绘制格式化\n     */\n    public IDrawFormat<T> getDrawFormat() {\n        return drawFormat;\n    }\n\n    /**\n     * 设置绘制格式化\n     */\n    public void setDrawFormat(IDrawFormat<T> drawFormat) {\n        this.drawFormat = drawFormat;\n    }\n\n    /**\n     * 获取需要解析的数据\n     *\n     * @return 数据\n     */\n    public List<T> getDatas() {\n        return datas;\n    }\n\n    /**\n     * 设置需要解析的数据\n     * 直接设置数据，不需要反射获取值\n     */\n    public void setDatas(List<T> datas) {\n        this.datas = datas;\n    }\n\n\n    public String format(int position) {\n        if (position >= 0 && position < datas.size()) {\n            return format(datas.get(position));\n        }\n        return INVAL_VALUE;\n    }\n\n    public String format(T t) {\n        String value;\n        if (format != null) {\n            value = format.format(t);\n        } else {\n            value = t == null ? INVAL_VALUE : t.toString();\n        }\n        return value;\n    }\n\n    /**\n     * 获取列的计算的宽度\n     *\n     * @return 宽度\n     */\n    public int getComputeWidth() {\n        return computeWidth;\n    }\n\n    /**\n     * 设置列的计算宽度\n     */\n    public void setComputeWidth(int computeWidth) {\n        this.computeWidth = computeWidth;\n    }\n\n    /**\n     * 获取列ID\n     *\n     * @return ID\n     */\n    public int getId() {\n        return id;\n    }\n\n    /**\n     * 设置列ID\n     */\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    /**\n     * 比较\n     */\n    @Override\n    public int compareTo(Column o) {\n        return this.id - o.getId();\n    }\n\n\n    /**\n     * 获取点击列监听\n     *\n     * @return 点击列监听\n     */\n    public OnColumnItemClickListener<T> getOnColumnItemClickListener() {\n        return onColumnItemClickListener;\n    }\n\n    /**\n     * 设置点击列监听\n     */\n    public void setOnColumnItemClickListener(OnColumnItemClickListener<T> onColumnItemClickListener) {\n        this.onColumnItemClickListener = onColumnItemClickListener;\n    }\n\n\n    /**\n     * 判断是否固定\n     *\n     * @return 是否固定\n     */\n    public boolean isFixed() {\n        return isFixed;\n    }\n\n    /**\n     * 设置是否固定\n     */\n    public void setFixed(boolean fixed) {\n        isFixed = fixed;\n    }\n\n    /**\n     * 获取字体位置\n     *\n     * @return Align\n     */\n    public Paint.Align getTextAlign() {\n        return textAlign;\n    }\n\n    /**\n     * 设置字体位置\n     */\n    public void setTextAlign(Paint.Align textAlign) {\n        this.textAlign = textAlign;\n    }\n\n    public int getMinWidth() {\n        return minWidth;\n    }\n\n    public void setMinWidth(int minWidth) {\n        this.minWidth = minWidth;\n    }\n\n    public int getMinHeight() {\n        return minHeight;\n    }\n\n    public void setMinHeight(int minHeight) {\n        this.minHeight = minHeight;\n    }\n\n\n    public Paint.Align getTitleAlign() {\n        return titleAlign;\n    }\n\n    /**\n     * 设置标题对齐方式\n     *\n     * @param titleAlign\n     */\n    public void setTitleAlign(Paint.Align titleAlign) {\n        this.titleAlign = titleAlign;\n    }\n\n    /**\n     * 设置列的宽度\n     *\n     * @param width\n     */\n    public void setWidth(int width) {\n        if (width > 0) {\n            this.width = width;\n            this.setDrawFormat(new FastTextDrawFormat<T>());\n        }\n    }\n\n    /**\n     * 获取列的宽度\n     *\n     * @param\n     */\n    public int getWidth() {\n        if (width == 0) {\n            return computeWidth;\n        }\n        return width;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/bean/ColumnInfo.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.bean;\n\n\n\npublic class ColumnInfo {\n    /**\n     * 列宽度\n     */\n    public int width;\n    /**\n     * 列高度\n     */\n    public int height;\n    /**\n     * 列左边\n     */\n    public int left;\n    /**\n     * 列顶部\n     */\n    public int top;\n    /**\n     * 值\n     */\n    public String value;\n    /**\n     * 列\n     */\n    public Column column;\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/bean/TableData.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.bean;\n\nimport com.didichuxing.doraemonkit.widget.tableview.format.NumberSequenceFormat;\nimport com.didichuxing.doraemonkit.widget.tableview.format.TitleDrawFormat;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.ISequenceFormat;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.ITitleDrawFormat;\nimport com.didichuxing.doraemonkit.widget.tableview.listener.OnColumnItemClickListener;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n\npublic class TableData<T> {\n\n    private String tableName;\n    private List<Column> columns;\n    private List<T> t;\n    private List<Column> childColumns;\n    private TableInfo tableInfo = new TableInfo();\n    private List<ColumnInfo> columnInfos;\n    private List<ColumnInfo> childColumnInfos;\n    private Column sortColumn;\n    private ITitleDrawFormat titleDrawFormat;\n    private ISequenceFormat YSequenceFormat;\n\n    private OnItemClickListener onItemClickListener;\n    private OnRowClickListener<T> onRowClickListener;\n    private OnColumnClickListener<?> onColumnClickListener;\n\n    /**\n     * @param tableName 表名\n     * @param t         数据\n     * @param columns   列列表\n     */\n    public TableData(String tableName, List<T> t, List<Column> columns) {\n        this(tableName, t, columns, null);\n\n    }\n\n    /**\n     * @param tableName 表名\n     * @param t         数据\n     * @param columns   列列表\n     */\n    public TableData(String tableName, List<T> t, Column... columns) {\n        this(tableName, t, Arrays.asList(columns));\n    }\n\n    /**\n     * @param tableName       表名\n     * @param t               数据\n     * @param columns         列列表\n     * @param titleDrawFormat 列标题绘制格式化\n     */\n    public TableData(String tableName, List<T> t, List<Column> columns, ITitleDrawFormat titleDrawFormat) {\n        this.tableName = tableName;\n        this.columns = columns;\n        this.t = t;\n        tableInfo.setLineSize(t.size());\n        childColumns = new ArrayList<>();\n        columnInfos = new ArrayList<>();\n        childColumnInfos = new ArrayList<>();\n        this.titleDrawFormat = titleDrawFormat == null ? new TitleDrawFormat() : titleDrawFormat;\n    }\n\n\n    /**\n     * 获取表名\n     *\n     * @return 表名\n     */\n    public String getTableName() {\n        return tableName;\n    }\n\n    /**\n     * 设置表名\n     */\n    public void setTableName(String tableName) {\n        this.tableName = tableName;\n    }\n\n    /**\n     * 获取所有列\n     *\n     * @return 所有列\n     */\n    public List<Column> getColumns() {\n        return columns;\n    }\n\n    /**\n     * 设置新列列表\n     */\n    public void setColumns(List<Column> columns) {\n        this.columns = columns;\n    }\n\n    /**\n     * 获取解析数据\n     *\n     * @return 解析数据\n     */\n    public List<T> getT() {\n        return t;\n    }\n\n    /**\n     * 设置解析数据\n     */\n    public void setT(List<T> t) {\n        this.t = t;\n        tableInfo.setLineSize(t.size());\n    }\n\n\n    /**\n     * 获取所有需要显示列数据的列\n     * isParent true的列不包含\n     *\n     * @return 所有需要显示列数据的列\n     */\n    public List<Column> getChildColumns() {\n        return childColumns;\n    }\n\n    /**\n     * 获取表格信息\n     *\n     * @return 表格信息tableInfo\n     */\n    public TableInfo getTableInfo() {\n        return tableInfo;\n    }\n\n    /**\n     * 设置表格信息\n     * 一般情况下不会使用到这个方法\n     */\n    public void setTableInfo(TableInfo tableInfo) {\n        this.tableInfo = tableInfo;\n    }\n\n    /**\n     * 获取列信息列表\n     *\n     * @return 列信息列表\n     */\n    public List<ColumnInfo> getColumnInfos() {\n        return columnInfos;\n    }\n\n    /**\n     * 获取isParent false列(子列)信息列表\n     *\n     * @return 子列信息列表\n     */\n    public List<ColumnInfo> getChildColumnInfos() {\n        return childColumnInfos;\n    }\n\n    /**\n     * 设置子列信息列表\n     */\n    public void setChildColumnInfos(List<ColumnInfo> childColumnInfos) {\n        this.childColumnInfos = childColumnInfos;\n    }\n\n    /**\n     * 设置列信息列表\n     */\n    public void setColumnInfos(List<ColumnInfo> columnInfos) {\n        this.columnInfos = columnInfos;\n    }\n\n    /**\n     * 设置子列\n     */\n    public void setChildColumns(List<Column> childColumns) {\n        this.childColumns = childColumns;\n    }\n\n    /**\n     * 获取需要根据排序的列\n     *\n     * @return 排序的列\n     */\n    public Column getSortColumn() {\n        return sortColumn;\n    }\n\n    /**\n     * 设置需要根据排序的列\n     */\n    public void setSortColumn(Column sortColumn) {\n        this.sortColumn = sortColumn;\n    }\n\n\n    /**\n     * 获取列标题绘制格式化\n     *\n     * @return 列标题绘制格式化\n     */\n    public ITitleDrawFormat getTitleDrawFormat() {\n        return titleDrawFormat;\n    }\n\n    /**\n     * 设置列标题绘制格式化\n     * 通过这个方法可以对列名进行格式化\n     */\n    public void setTitleDrawFormat(ITitleDrawFormat titleDrawFormat) {\n        this.titleDrawFormat = titleDrawFormat;\n    }\n\n    /**\n     * 获取Y序号列文字格式化\n     *\n     * @return Y序号列文字格式化\n     */\n    public ISequenceFormat getYSequenceFormat() {\n        if (YSequenceFormat == null) {\n            YSequenceFormat = new NumberSequenceFormat();\n        }\n        return YSequenceFormat;\n    }\n\n    /**\n     * 设置Y序号列文字格式化\n     */\n    public void setYSequenceFormat(ISequenceFormat YSequenceFormat) {\n        this.YSequenceFormat = YSequenceFormat;\n    }\n\n    /**\n     * 获取包含ID的子列\n     *\n     * @param id 列ID\n     * @return 包含ID的子列\n     */\n    public Column getColumnByID(int id) {\n        List<Column> columns = getChildColumns();\n        for (Column column : columns) {\n            if (column.getId() == id) {\n                return column;\n            }\n        }\n        return null;\n    }\n\n\n    /**\n     * 获取行数\n     *\n     * @return 行数\n     */\n    public int getLineSize() {\n        return tableInfo.getLineHeightArray().length;\n    }\n\n    public void clear() {\n        if (t != null) {\n            t.clear();\n            t = null;\n        }\n        if (childColumns != null) {\n            childColumns.clear();\n            childColumns = null;\n        }\n        if (columns != null) {\n            columns = null;\n        }\n        if (childColumnInfos != null) {\n            childColumnInfos.clear();\n            childColumnInfos = null;\n        }\n  /*      if(cellRangeAddresses !=null){\n            cellRangeAddresses.clear();\n            cellRangeAddresses =null;\n        }*/\n\n        if (tableInfo != null) {\n            tableInfo.clear();\n            tableInfo = null;\n        }\n        sortColumn = null;\n        titleDrawFormat = null;\n        YSequenceFormat = null;\n\n    }\n\n    /**\n     * 获取表格单元格Cell点击事件\n     */\n    public OnItemClickListener getOnItemClickListener() {\n        return onItemClickListener;\n\n    }\n\n    /**\n     * 设置表格单元格Cell点击事件\n     *\n     * @param onItemClickListener 点击事件\n     */\n    public void setOnItemClickListener(final OnItemClickListener onItemClickListener) {\n        this.onItemClickListener = onItemClickListener;\n        for (Column column : columns) {\n            column.setOnColumnItemClickListener(new OnColumnItemClickListener() {\n                @Override\n                public void onClick(Column column, String value, Object t, int position) {\n                    if (onItemClickListener != null) {\n                        int index = childColumns.indexOf(column);\n                        TableData.this.onItemClickListener.onClick(column, value, t, index, position);\n                    }\n                }\n            });\n        }\n    }\n\n\n    /**\n     * 设置表格行点击事件\n     *\n     * @param onRowClickListener 行点击事件\n     */\n    public void setOnRowClickListener(final OnRowClickListener<T> onRowClickListener) {\n        this.onRowClickListener = onRowClickListener;\n        if (this.onRowClickListener != null) {\n            setOnItemClickListener(new OnItemClickListener() {\n                @Override\n                public void onClick(Column column, String value, Object o, int col, int row) {\n                    TableData.this.onRowClickListener.onClick(column, t.get(row), col, row);\n                }\n            });\n        }\n\n    }\n\n\n    /**\n     * 设置表格列点击事件\n     */\n    public void setOnColumnClickListener(final OnColumnClickListener onColumnClickListener) {\n        this.onColumnClickListener = onColumnClickListener;\n        if (this.onRowClickListener != null) {\n            setOnItemClickListener(new OnItemClickListener() {\n                @Override\n                public void onClick(Column column, String value, Object o, int col, int row) {\n                    TableData.this.onColumnClickListener.onClick(column, column.getDatas(), col, row);\n                }\n            });\n        }\n    }\n\n\n    public OnRowClickListener getOnRowClickListener() {\n        return onRowClickListener;\n    }\n\n    /**\n     * 表格单元格Cell点击事件接口\n     */\n    public interface OnItemClickListener<T> {\n        void onClick(Column<T> column, String value, T t, int col, int row);\n    }\n\n    /**\n     * 表格行点击事件接口\n     */\n    public interface OnRowClickListener<T> {\n        void onClick(Column column, T t, int col, int row);\n    }\n\n    public interface OnColumnClickListener<T> {\n        void onClick(Column column, List<T> t, int col, int row);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/bean/TableInfo.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.bean;\n\nimport android.graphics.Rect;\n\npublic class TableInfo {\n\n    private int topHeight;\n    private int titleHeight;\n    private int tableTitleSize;\n    private int yAxisWidth;\n    private int countHeight;\n    private int titleDirection;\n    private Rect tableRect;\n    private int maxLevel = 1;\n    private int columnSize;\n    private int[] lineHeightArray;\n    private float zoom = 1;\n    private Cell[][] rangeCells;\n    private int lineSize;\n\n    /**\n     * 获取最大层级\n     *\n     * @return 最大层级\n     */\n    public int getMaxLevel() {\n        return maxLevel;\n    }\n\n    /**\n     * 设置最大层级\n     * 该方法提供用于表格递归\n     *\n     * @return 最大层级\n     */\n    public void setMaxLevel(int maxLevel) {\n        this.maxLevel = maxLevel;\n    }\n\n    /**\n     * 获取列总数\n     *\n     * @return 列总数\n     */\n    public int getColumnSize() {\n        return columnSize;\n    }\n\n    public void setColumnSize(int columnSize) {\n        this.columnSize = columnSize;\n        rangeCells = new Cell[lineSize][columnSize];\n\n    }\n\n    public int getTopHeight() {\n        return topHeight;\n    }\n\n    public int getTopHeight(float zoom) {\n        return (int) (topHeight * zoom);\n    }\n\n    public void setTopHeight(int topHeight) {\n        this.topHeight = topHeight;\n    }\n\n    public int getTitleHeight() {\n        return (int) (titleHeight * zoom);\n    }\n\n    public void setTitleHeight(int titleHeight) {\n        this.titleHeight = titleHeight;\n    }\n\n\n    public Rect getTableRect() {\n        return tableRect;\n    }\n\n    public void setTableRect(Rect tableRect) {\n        this.tableRect = tableRect;\n    }\n\n    public int getyAxisWidth() {\n        return yAxisWidth;\n    }\n\n\n    public void setLineSize(int lineSize) {\n        this.lineSize = lineSize;\n        this.lineHeightArray = new int[lineSize];\n\n    }\n\n    /**\n     * 动态添加列，数组重新创建Copy\n     *\n     * @param count 添加数量\n     */\n    public void addLine(int count, boolean isFoot) {\n        lineSize += count;\n        int size = lineHeightArray.length;\n        int[] tempArray = new int[size + count];\n        //数组复制\n        if (isFoot) {\n            System.arraycopy(lineHeightArray, 0, tempArray, 0, size);\n        } else {\n            System.arraycopy(lineHeightArray, 0, tempArray, count, size);\n        }\n        lineHeightArray = tempArray;\n        if (size == rangeCells.length) {\n            Cell[][] tempRangeCells = new Cell[size + count][columnSize];\n            for (int i = 0; i < size; i++) {\n                tempRangeCells[i + (isFoot ? 0 : count)] = rangeCells[i];\n            }\n            rangeCells = tempRangeCells;\n        }\n    }\n\n    public int getCountHeight() {\n        return (int) (zoom * countHeight);\n    }\n\n    public void setCountHeight(int countHeight) {\n        this.countHeight = countHeight;\n    }\n\n\n    public int[] getLineHeightArray() {\n        return lineHeightArray;\n    }\n\n    /**\n     * 获取缩放值\n     *\n     * @return 缩放值\n     */\n    public float getZoom() {\n        return zoom;\n    }\n\n    /**\n     * 设置缩放值\n     */\n    public void setZoom(float zoom) {\n        this.zoom = zoom;\n    }\n\n    public void setyAxisWidth(int yAxisWidth) {\n        this.yAxisWidth = yAxisWidth;\n    }\n\n    public int getTableTitleSize() {\n        return tableTitleSize;\n    }\n\n    public void setTableTitleSize(int tableTitleSize) {\n        this.tableTitleSize = tableTitleSize;\n    }\n\n    public int getTitleDirection() {\n        return titleDirection;\n    }\n\n    public void setTitleDirection(int titleDirection) {\n        this.titleDirection = titleDirection;\n    }\n\n    public Cell[][] getRangeCells() {\n        return rangeCells;\n    }\n\n    public void clear() {\n        rangeCells = null;\n        lineHeightArray = null;\n        tableRect = null;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/component/SmartTable.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.component;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\nimport android.util.AttributeSet;\nimport android.util.DisplayMetrics;\nimport android.view.MotionEvent;\nimport android.view.View;\n\n\nimport com.didichuxing.doraemonkit.widget.tableview.utils.MatrixHelper;\nimport com.didichuxing.doraemonkit.widget.tableview.TableConfig;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.TableData;\nimport com.didichuxing.doraemonkit.widget.tableview.TableMeasurer;\nimport com.didichuxing.doraemonkit.widget.tableview.TableParser;\nimport com.didichuxing.doraemonkit.widget.tableview.TableProvider;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.Column;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.TableInfo;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.IComponent;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.ISelectFormat;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.ITableTitle;\nimport com.didichuxing.doraemonkit.widget.tableview.listener.OnColumnClickListener;\nimport com.didichuxing.doraemonkit.widget.tableview.listener.OnTableChangeListener;\nimport com.didichuxing.doraemonkit.widget.tableview.style.FontStyle;\n\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\npublic class SmartTable<T> extends View implements OnTableChangeListener {\n\n    private YSequence<T> yAxis;\n    private ITableTitle tableTitle;\n    private TableProvider<T> provider;\n    private Rect showRect;\n    private Rect tableRect;\n    private TableConfig config = TableConfig.getInstance();\n    private TableParser<T> parser;\n    private TableData<T> tableData;\n    private int defaultHeight = 300;\n    private int defaultWidth = 300;\n    private TableMeasurer<T> measurer;\n    protected Paint paint;\n    private MatrixHelper matrixHelper;\n    private boolean isExactly = true; //是否是测量精准模式\n    private AtomicBoolean isNotifying = new AtomicBoolean(false); //是否正在更新数据\n    private boolean isYSequenceRight;\n\n\n    public SmartTable(Context context) {\n        super(context);\n        init();\n    }\n\n    public SmartTable(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init();\n    }\n\n    public SmartTable(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init();\n    }\n\n    /**\n     * 初始化\n     */\n    private void init() {\n        FontStyle.setDefaultTextSpSize(getContext(), 13);\n        paint = new Paint(Paint.ANTI_ALIAS_FLAG);\n        showRect = new Rect();\n        tableRect = new Rect();\n        yAxis = new YSequence<>();\n        parser = new TableParser<>();\n        provider = new TableProvider<>();\n        config.setPaint(paint);\n        measurer = new TableMeasurer<>();\n        tableTitle = new TableTitle();\n        tableTitle.setDirection(IComponent.TOP);\n        matrixHelper = new MatrixHelper(getContext());\n        matrixHelper.setOnTableChangeListener(this);\n        matrixHelper.register(provider);\n        matrixHelper.setOnInterceptListener(provider.getOperation());\n\n    }\n\n    /**\n     * 绘制\n     * 首先通过计算的table大小，计算table title大小\n     * 再通过 matrixHelper getZoomProviderRect计算实现缩放和位移的Rect\n     * 再绘制背景\n     * 绘制XY序号列\n     * 最后绘制内容\n     *\n     * @param canvas\n     */\n    @Override\n    protected void onDraw(Canvas canvas) {\n        if (!isNotifying.get()) {\n            setScrollY(0);\n            showRect.set(getPaddingLeft(), getPaddingTop(),\n                    getWidth() - getPaddingRight(),\n                    getHeight() - getPaddingBottom());\n            if (tableData != null) {\n                //表格Rect\n                Rect rect = tableData.getTableInfo().getTableRect();\n                if (rect != null) {\n                    if (config.isShowTableTitle()) {\n                        measurer.measureTableTitle(tableData, tableTitle, showRect);\n                    }\n                    tableRect.set(rect);\n                    Rect scaleRect = matrixHelper.getZoomProviderRect(showRect, tableRect,\n                            tableData.getTableInfo());\n                    if (config.isShowTableTitle()) {\n                        tableTitle.onMeasure(scaleRect, showRect, config);\n                        tableTitle.onDraw(canvas, showRect, tableData.getTableName(), config);\n                    }\n                    if (config.isShowYSequence()) {\n                        yAxis.onMeasure(scaleRect, showRect, config);\n                        if (isYSequenceRight) {\n                            canvas.save();\n                            canvas.translate(showRect.width(), 0);\n                            yAxis.onDraw(canvas, showRect, tableData, config);\n                            canvas.restore();\n                        } else {\n                            yAxis.onDraw(canvas, showRect, tableData, config);\n                        }\n                    }\n                    if (isYSequenceRight) {\n                        canvas.save();\n                        canvas.translate(-yAxis.getWidth(), 0);\n                        provider.onDraw(canvas, scaleRect, showRect, tableData);\n                        canvas.restore();\n                    } else {\n                        provider.onDraw(canvas, scaleRect, showRect, tableData);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * 绘制表格边框背景\n     *\n     * @param canvas\n     */\n    private void drawGridBackground(Canvas canvas, Rect showRect, Rect scaleRect) {\n        config.contentGridStyle.fillPaint(paint);\n        canvas.drawRect(Math.max(showRect.left, scaleRect.left),\n                Math.max(showRect.top, scaleRect.top),\n                Math.min(showRect.right, scaleRect.right),\n                Math.min(scaleRect.bottom, showRect.bottom), paint);\n    }\n\n    /**\n     * 获取表格配置\n     * 可以使用TableConfig进行样式的配置，包括颜色，是否固定，开启统计行等\n     *\n     * @return 表格配置\n     */\n    public TableConfig getConfig() {\n        return config;\n    }\n\n    /**\n     * 设置表格数据\n     *\n     * @param tableData\n     */\n    public void setTableData(TableData<T> tableData) {\n        if (tableData != null) {\n            this.tableData = tableData;\n            notifyDataChanged();\n        }\n    }\n\n    public ITableTitle getTableTitle() {\n        return tableTitle;\n    }\n\n    /**\n     * 通知更新\n     */\n    public void notifyDataChanged() {\n\n        if (tableData != null) {\n            config.setPaint(paint);\n            //开启线程\n            isNotifying.set(true);\n            new Thread(new Runnable() {\n                @Override\n                public void run() {\n                    //long start = System.currentTimeMillis();\n                    parser.parse(tableData);\n                    TableInfo info = measurer.measure(tableData, getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());\n                    yAxis.setWidth(info.getyAxisWidth());\n                    requestReMeasure();\n                    postInvalidate();\n                    isNotifying.set(false);\n                    //long end = System.currentTimeMillis();\n                    //Log.e(\"smartTable\",\"notifyDataChanged timeMillis=\"+(end-start));\n                }\n\n            }).start();\n\n        }\n    }\n\n    /**\n     * 添加数据\n     * 通过这个方法可以实现动态添加数据，参数isFoot可以实现首尾添加\n     *\n     * @param t      新增数据\n     * @param isFoot 是否在尾部添加\n     */\n    public void addData(final List<T> t, final boolean isFoot) {\n        if (t != null && t.size() > 0) {\n            isNotifying.set(true);\n            new Thread(new Runnable() {\n                @Override\n                public void run() {\n                    parser.addData(tableData, t, isFoot);\n                    measurer.measure(tableData, getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());\n                    requestReMeasure();\n                    postInvalidate();\n                    isNotifying.set(false);\n\n                }\n            }).start();\n        }\n    }\n\n\n    /**\n     * 通知重绘\n     * 增加锁机制，避免闪屏和数据更新异常\n     */\n    @Override\n    public void invalidate() {\n        if (!isNotifying.get()) {\n            super.invalidate();\n        }\n\n    }\n\n    /**\n     * 通知重新测量大小\n     */\n    private void requestReMeasure() {\n        //不是精准模式 且已经测量了\n        if (!isExactly && getMeasuredHeight() != 0 && tableData != null) {\n            if (tableData.getTableInfo().getTableRect() != null) {\n                int defaultHeight = tableData.getTableInfo().getTableRect().height()\n                        + getPaddingTop();\n                int defaultWidth = tableData.getTableInfo().getTableRect().width();\n                int[] realSize = new int[2];\n                getLocationInWindow(realSize);\n                DisplayMetrics dm = getContext().getResources().getDisplayMetrics();\n                int screenWidth = dm.widthPixels;\n                int screenHeight = dm.heightPixels;\n                int maxWidth = screenWidth - realSize[0];\n                int maxHeight = screenHeight - realSize[1];\n                defaultHeight = Math.min(defaultHeight, maxHeight);\n                defaultWidth = Math.min(defaultWidth, maxWidth);\n                if (this.defaultHeight != defaultHeight\n                        || this.defaultWidth != defaultWidth) {\n                    this.defaultHeight = defaultHeight;\n                    this.defaultWidth = defaultWidth;\n                    post(new Runnable() {\n                        @Override\n                        public void run() {\n                            requestLayout();\n                        }\n                    });\n\n                }\n            }\n        }\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));\n        requestReMeasure();\n    }\n\n    /**\n     * 计算组件宽度\n     */\n\n    private int measureWidth(int widthMeasureSpec) {\n        int result;\n        int specMode = MeasureSpec.getMode(widthMeasureSpec);\n        int specSize = MeasureSpec.getSize(widthMeasureSpec);\n        if (specMode == MeasureSpec.EXACTLY) {//精确模式\n            result = specSize;\n        } else {\n            isExactly = false;\n            result = defaultWidth;//最大尺寸模式，getDefaultWidth方法需要我们根据控件实际需要自己实现\n            if (specMode == MeasureSpec.AT_MOST) {\n                result = Math.min(result, specSize);\n            }\n        }\n        return result;\n    }\n\n    /**\n     * 计算组件高度\n     */\n\n    private int measureHeight(int measureSpec) {\n\n        int result;\n        int specMode = MeasureSpec.getMode(measureSpec);\n        int specSize = MeasureSpec.getSize(measureSpec);\n        if (specMode == MeasureSpec.EXACTLY) {\n            result = specSize;\n        } else {\n            isExactly = false;\n            result = defaultHeight;\n            if (specMode == MeasureSpec.AT_MOST) {\n                result = Math.min(result, specSize);\n            }\n        }\n        return result;\n    }\n\n    /**\n     * 将触摸事件交给Iouch处理\n     */\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        return matrixHelper.handlerTouchEvent(event);\n    }\n\n    /**\n     * 分发事件\n     * 在这里会去调用MatrixHelper onDisallowInterceptEvent方法\n     * 判断是否阻止parent拦截自己的事件\n     *\n     * @param event\n     * @return\n     */\n    @Override\n    public boolean dispatchTouchEvent(MotionEvent event) {\n        matrixHelper.onDisallowInterceptEvent(this, event);\n        return super.dispatchTouchEvent(event);\n    }\n\n\n    /**\n     * 表格移动缩放改变回调\n     *\n     * @param scale      缩放值\n     * @param translateX X位移值\n     * @param translateY Y位移值\n     */\n    @Override\n    public void onTableChanged(float scale, float translateX, float translateY) {\n        if (tableData != null) {\n            config.setZoom(scale);\n            tableData.getTableInfo().setZoom(scale);\n            invalidate();\n        }\n    }\n\n    /**\n     * 获取列点击事件\n     */\n    public OnColumnClickListener getOnColumnClickListener() {\n        return provider.getOnColumnClickListener();\n    }\n\n    /**\n     * 设置列点击事件,实现对列的监听\n     *\n     * @param onColumnClickListener 列点击事件\n     */\n    public void setOnColumnClickListener(OnColumnClickListener onColumnClickListener) {\n        this.provider.setOnColumnClickListener(onColumnClickListener);\n    }\n\n    /**\n     * 列排序\n     * 你可以调用这个方法，对所有数据进行排序，排序根据设置的column排序\n     *\n     * @param column    列\n     * @param isReverse 是否反序\n     */\n    public void setSortColumn(Column column, boolean isReverse) {\n        if (tableData != null && column != null) {\n            tableData.setSortColumn(column);\n            setTableData(tableData);\n        }\n    }\n\n    public Rect getShowRect() {\n        return showRect;\n    }\n\n\n    /**\n     * 获取绘制表格内容者\n     *\n     * @return 绘制表格内容者\n     */\n    public TableProvider<T> getProvider() {\n        return provider;\n    }\n\n\n    /**\n     * 获取表格数据\n     * TableData是解析数据之后对数据的封装对象，包含table column,rect等信息\n     *\n     * @return 表格数据\n     */\n    public TableData<T> getTableData() {\n        return tableData;\n    }\n\n\n    /**\n     * 开启缩放\n     *\n     * @param zoom 是否缩放\n     */\n    public void setZoom(boolean zoom) {\n\n        matrixHelper.setCanZoom(zoom);\n        invalidate();\n\n    }\n\n    /**\n     * 开启缩放设置缩放值\n     *\n     * @param zoom    是否缩放\n     * @param maxZoom 最大缩放值\n     * @param minZoom 最小缩放值\n     */\n    public void setZoom(boolean zoom, float maxZoom, float minZoom) {\n\n        matrixHelper.setCanZoom(zoom);\n        matrixHelper.setMinZoom(minZoom);\n        matrixHelper.setMaxZoom(maxZoom);\n        invalidate();\n\n    }\n\n\n    /**\n     * 获取缩放移动辅助类\n     * 如果你需要更多的移动功能，可以使用它\n     *\n     * @return 缩放移动辅助类\n     */\n    public MatrixHelper getMatrixHelper() {\n        return matrixHelper;\n    }\n\n\n    /**\n     * 设置选中格子格式化\n     *\n     * @param selectFormat 选中格子格式化\n     */\n    public void setSelectFormat(ISelectFormat selectFormat) {\n        this.provider.setSelectFormat(selectFormat);\n    }\n\n\n    @Override\n    public int computeHorizontalScrollRange() {\n        final int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight();\n        int scrollRange = matrixHelper.getZoomRect().right;\n        final int scrollX = -matrixHelper.getZoomRect().right;\n        final int overScrollRight = Math.max(0, scrollRange - contentWidth);\n        if (scrollX < 0) {\n            scrollRange -= scrollX;\n        } else if (scrollX > overScrollRight) {\n            scrollRange += scrollX - overScrollRight;\n        }\n        return scrollRange;\n    }\n\n    @Override\n    public boolean canScrollVertically(int direction) {\n        if (direction < 0) {\n            return matrixHelper.getZoomRect().top != 0;\n        } else {\n            return matrixHelper.getZoomRect().bottom > matrixHelper.getOriginalRect().bottom;\n        }\n\n    }\n\n    @Override\n    public int computeHorizontalScrollOffset() {\n        return Math.max(0, -matrixHelper.getZoomRect().top);\n    }\n\n\n    @Override\n    public int computeHorizontalScrollExtent() {\n        return super.computeHorizontalScrollExtent();\n    }\n\n    @Override\n    public int computeVerticalScrollRange() {\n\n        final int contentHeight = getHeight() - getPaddingBottom() - getPaddingTop();\n        int scrollRange = matrixHelper.getZoomRect().bottom;\n        final int scrollY = -matrixHelper.getZoomRect().left;\n        final int overScrollBottom = Math.max(0, scrollRange - contentHeight);\n        if (scrollY < 0) {\n            scrollRange -= scrollY;\n        } else if (scrollY > overScrollBottom) {\n            scrollRange += scrollY - overScrollBottom;\n        }\n\n        return scrollRange;\n    }\n\n    @Override\n    public int computeVerticalScrollOffset() {\n\n        return Math.max(0, -matrixHelper.getZoomRect().left);\n    }\n\n    @Override\n    public int computeVerticalScrollExtent() {\n\n        return super.computeVerticalScrollExtent();\n\n    }\n\n    public YSequence getYSequence() {\n        return yAxis;\n    }\n\n    @Override\n    protected void onDetachedFromWindow() {\n        super.onDetachedFromWindow();\n        if (tableData != null && getContext() != null) {\n            if (((Activity) getContext()).isFinishing()) {\n                release();\n            }\n        }\n    }\n\n    /**\n     * 可以在Activity onDestroy释放\n     */\n    private void release() {\n        matrixHelper.unRegisterAll();\n        measurer = null;\n        provider = null;\n        matrixHelper = null;\n        provider = null;\n        if (tableData != null) {\n            tableData.clear();\n            tableData = null;\n        }\n        yAxis = null;\n    }\n\n    public boolean isYSequenceRight() {\n        return isYSequenceRight;\n    }\n\n    public void setYSequenceRight(boolean YSequenceRight) {\n        isYSequenceRight = YSequenceRight;\n    }\n}\n\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/component/TableTitle.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.component;\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.Rect;\n\nimport com.didichuxing.doraemonkit.widget.tableview.TableConfig;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.ITableTitle;\nimport com.didichuxing.doraemonkit.widget.tableview.utils.DrawUtils;\n\npublic class TableTitle implements ITableTitle {\n\n    private int size = 100;\n    private Rect rect = new Rect();\n    protected int direction;\n\n\n    @Override\n    public void onDraw(Canvas canvas, Rect showRect, String tableName, TableConfig config) {\n        Paint paint = config.getPaint();\n        config.tableTitleStyle.fillPaint(paint);\n        Rect rect = getRect();\n        int startX = rect.centerX();\n        Path path = new Path();\n        switch (direction) {\n            case TOP:\n            case BOTTOM:\n                DrawUtils.drawMultiText(canvas, paint, rect, tableName.split(\"\\n\"));\n                break;\n            case LEFT:\n            case RIGHT:\n                int textWidth = (int) paint.measureText(tableName);\n                path.moveTo(startX, rect.top);\n                path.lineTo(startX, rect.bottom);\n                canvas.drawTextOnPath(tableName, path, textWidth / 2, 0, paint);\n                break;\n        }\n    }\n\n    @Override\n    public void onMeasure(Rect scaleRect, Rect showRect, TableConfig config) {\n        rect.left = showRect.left;\n        rect.right = showRect.right;\n        rect.top = showRect.top;\n        rect.bottom = Math.min(showRect.bottom, scaleRect.bottom);\n        int h = size;\n        int w = size;\n        switch (direction) {\n            case TOP:\n                rect.bottom = rect.top + h;\n                scaleRect.top += h;\n                showRect.top += h;\n                break;\n            case LEFT:\n                rect.right = rect.left + w;\n                scaleRect.left += w;\n                showRect.left += w;\n                break;\n            case RIGHT:\n                rect.left = rect.right - w;\n                scaleRect.right -= w;\n                showRect.right -= w;\n                break;\n            case BOTTOM:\n                rect.top = rect.bottom - h;\n                scaleRect.bottom -= h;\n                showRect.bottom -= h;\n                break;\n        }\n    }\n\n\n    public int getSize() {\n        return size;\n    }\n\n    public void setSize(int size) {\n        this.size = size;\n    }\n\n    public Rect getRect() {\n        return rect;\n    }\n\n    public void setRect(Rect rect) {\n        this.rect = rect;\n    }\n\n    public int getDirection() {\n        return direction;\n    }\n\n    public void setDirection(int direction) {\n        this.direction = direction;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/component/YSequence.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.component;\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\n\nimport com.didichuxing.doraemonkit.widget.tableview.TableConfig;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.TableData;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.TableInfo;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.IComponent;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.ISequenceFormat;\nimport com.didichuxing.doraemonkit.widget.tableview.utils.DrawUtils;\n\npublic class YSequence<T> implements IComponent<TableData<T>> {\n\n    private Rect rect;\n    private int width;\n    private int clipWidth;\n    private Rect scaleRect;\n    private ISequenceFormat format;\n    private Rect tempRect; //临时使用\n\n    public YSequence() {\n        rect = new Rect();\n        tempRect = new Rect();\n    }\n\n    @Override\n    public void onMeasure(Rect scaleRect, Rect showRect, TableConfig config) {\n        this.scaleRect = scaleRect;\n        int scaleWidth = (int) (width * (config.getZoom() > 1 ? 1 : config.getZoom()));\n        boolean fixed = config.isFixedYSequence();\n        rect.top = scaleRect.top;\n        rect.bottom = scaleRect.bottom;\n        rect.left = fixed ? showRect.left : scaleRect.left;\n        rect.right = rect.left + scaleWidth;\n        if (fixed) {\n            scaleRect.left += scaleWidth;\n            showRect.left += scaleWidth;\n            clipWidth = scaleWidth;\n        } else {\n            int disX = showRect.left - scaleRect.left;\n            clipWidth = Math.max(0, scaleWidth - disX);\n            showRect.left += clipWidth;\n            scaleRect.left += scaleWidth;\n        }\n    }\n\n    @Override\n    public void onDraw(Canvas canvas, Rect showRect, TableData<T> tableData, TableConfig config) {\n        format = tableData.getYSequenceFormat();\n        float hZoom = (config.getZoom() > 1 ? 1 : config.getZoom());\n        int totalSize = tableData.getLineSize();\n        TableInfo info = tableData.getTableInfo();\n        int topHeight = info.getTopHeight(hZoom);\n        float top = rect.top + topHeight;\n        int showLeft = showRect.left - clipWidth;\n        boolean isFixTop = config.isFixedXSequence();\n        int showTop = isFixTop ? (showRect.top + topHeight) : showRect.top;\n        float tempTop = top;\n        boolean isFixedTitle = config.isFixedTitle();\n        boolean isFixedCount = config.isFixedCountRow();\n        if (isFixedTitle) {\n            int clipHeight;\n            if (isFixTop) {\n                clipHeight = info.getTopHeight(hZoom);\n            } else {\n                int disY = showRect.top - scaleRect.top;\n                clipHeight = Math.max(0, topHeight - disY);\n            }\n            tempTop = showRect.top + clipHeight;\n        }\n\n        tempRect.set(showLeft, (int) tempTop - topHeight, showRect.left, (int) tempTop);\n        drawLeftAndTop(canvas, showRect, tempRect, config);\n        canvas.save();\n        canvas.clipRect(showLeft, showTop,\n                showRect.left, showRect.bottom);\n        if (config.isShowColumnTitle()) {\n            for (int i = 0; i < info.getMaxLevel(); i++) {\n                float bottom = tempTop + info.getTitleHeight();\n                if (DrawUtils.isVerticalMixRect(showRect, (int) top, (int) bottom)) {\n                    tempRect.set(rect.left, (int) tempTop, rect.right, (int) bottom);\n                    drawRect(canvas, tempRect, config);\n                }\n                tempTop = bottom;\n                top += info.getTitleHeight();\n            }\n        }\n        int tempBottom = showRect.bottom;\n        if (isFixedTitle || isFixedCount) {\n            canvas.save();\n            canvas.clipRect(showLeft, tempTop, showRect.left, tempBottom);\n        }\n        int num = 0;\n        for (int i = 0; i < totalSize; i++) {\n            num++;\n            float bottom = top + info.getLineHeightArray()[i] * config.getZoom();\n            if (showRect.bottom >= rect.top) {\n                if (DrawUtils.isVerticalMixRect(showRect, (int) top, (int) bottom)) {\n                    tempRect.set(rect.left, (int) top, rect.right, (int) bottom);\n                    draw(canvas, tempRect, num, config);\n                }\n            } else {\n                break;\n            }\n            top = bottom;\n        }\n        if (isFixedTitle || isFixedCount) {\n            canvas.restore();\n        }\n        canvas.restore();\n\n    }\n\n    /**\n     * 绘制左上角空隙\n     *\n     * @param canvas\n     * @param rect\n     * @param config\n     */\n    private void drawLeftAndTop(Canvas canvas, Rect showRect, Rect rect, TableConfig config) {\n        canvas.save();\n        canvas.clipRect(Math.max(this.rect.left, rect.left), showRect.top,\n                showRect.left, rect.bottom);\n        Paint paint = config.getPaint();\n        if (config.getLeftAndTopBackgroundColor() != 0) {\n            paint.setStyle(Paint.Style.FILL);\n            paint.setColor(config.getLeftAndTopBackgroundColor());\n            canvas.drawRect(rect, paint);\n        }\n\n        config.SequenceGridStyle.fillPaint(paint);\n        canvas.drawRect(rect, paint);\n\n        canvas.restore();\n    }\n\n    private void draw(Canvas canvas, Rect rect, int position, TableConfig config) {\n        drawRect(canvas, rect, config);\n        format.draw(canvas, position - 1, rect, config);\n    }\n\n    private void drawRect(Canvas canvas, Rect rect, TableConfig config) {\n        Paint paint = config.getPaint();\n        int textColor = TableConfig.INVALID_COLOR;\n\n        config.SequenceGridStyle.fillPaint(paint);\n        canvas.drawRect(rect, paint);\n\n        config.YSequenceStyle.fillPaint(paint);\n\n        if (textColor != TableConfig.INVALID_COLOR) {\n            paint.setColor(textColor);\n        }\n    }\n\n    public int getWidth() {\n        return width;\n    }\n\n    public void setWidth(int width) {\n        this.width = width;\n    }\n\n    public Rect getRect() {\n        return rect;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/format/BaseSequenceFormat.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.format;\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\n\nimport com.didichuxing.doraemonkit.widget.tableview.utils.DrawUtils;\nimport com.didichuxing.doraemonkit.widget.tableview.TableConfig;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.ISequenceFormat;\n\npublic abstract class BaseSequenceFormat implements ISequenceFormat {\n    @Override\n    public void draw(Canvas canvas, int sequence, Rect rect, TableConfig config) {\n        //字体缩放\n        Paint paint = config.getPaint();\n        paint.setTextSize(paint.getTextSize() * (config.getZoom() > 1 ? 1 : config.getZoom()));\n        paint.setTextAlign(Paint.Align.CENTER);\n        canvas.drawText(format(sequence + 1), rect.centerX(), DrawUtils.getTextCenterY(rect.centerY(), paint), paint);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/format/FastTextDrawFormat.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.format;\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\n\nimport com.didichuxing.doraemonkit.widget.tableview.bean.Column;\nimport com.didichuxing.doraemonkit.widget.tableview.utils.DrawUtils;\nimport com.didichuxing.doraemonkit.widget.tableview.TableConfig;\n\nimport java.util.HashMap;\n\n\npublic class FastTextDrawFormat<T> extends TextDrawFormat<T> {\n\n\n    private String HEIGHT_KEY = \"dk_height\";\n    private HashMap<String, Integer> cacheMap = new HashMap<>();\n\n    @Override\n\n    public int measureWidth(Column<T> column, int position) {\n        int width = 0;\n        String value = column.format(position);\n        Integer maxLengthValue = cacheMap.get(column.getColumnName());\n        if (maxLengthValue == null) {\n            width = comperLength(column, value);\n        } else if (value.length() > maxLengthValue) {\n            width = comperLength(column, value);\n        }\n        return width;\n    }\n\n    private int comperLength(Column<T> column, String value) {\n        TableConfig config = TableConfig.getInstance();\n        Paint paint = config.getPaint();\n        config.contentStyle.fillPaint(paint);\n        cacheMap.put(column.getColumnName(), value.length());\n        return (int) paint.measureText(value);\n    }\n\n    @Override\n    public int measureHeight(Column<T> column, int position) {\n        int height;\n        if (cacheMap.get(HEIGHT_KEY) == null) {\n            TableConfig config = TableConfig.getInstance();\n            Paint paint = config.getPaint();\n            config.contentStyle.fillPaint(paint);\n            cacheMap.put(HEIGHT_KEY, DrawUtils.getTextHeight(paint));\n        }\n        height = cacheMap.get(HEIGHT_KEY);\n        return height;\n    }\n\n\n    protected void drawText(Canvas c, String value, Rect rect, Paint paint) {\n        DrawUtils.drawSingleText(c, paint, rect, value);\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/format/NumberSequenceFormat.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.format;\n\n\npublic class NumberSequenceFormat extends BaseSequenceFormat {\n\n    @Override\n    public String format(Integer position) {\n        return String.valueOf(position);\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/format/TextDrawFormat.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.format;\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\n\n\nimport com.didichuxing.doraemonkit.widget.tableview.bean.CellInfo;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.Column;\nimport com.didichuxing.doraemonkit.widget.tableview.utils.DrawUtils;\nimport com.didichuxing.doraemonkit.widget.tableview.TableConfig;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.IDrawFormat;\n\nimport java.lang.ref.SoftReference;\nimport java.util.HashMap;\nimport java.util.Map;\n\n\npublic class TextDrawFormat<T> implements IDrawFormat<T> {\n\n\n    private Map<String, SoftReference<String[]>> valueMap; //避免产生大量对象\n\n    public TextDrawFormat() {\n        valueMap = new HashMap<>();\n    }\n\n    @Override\n    public int measureWidth(Column<T> column, int position) {\n\n        TableConfig config = TableConfig.getInstance();\n        Paint paint = config.getPaint();\n        config.contentStyle.fillPaint(paint);\n        return DrawUtils.getMultiTextWidth(paint, getSplitString(column.format(position)));\n    }\n\n\n    @Override\n    public int measureHeight(Column<T> column, int position) {\n        TableConfig config = TableConfig.getInstance();\n        Paint paint = config.getPaint();\n        config.contentStyle.fillPaint(paint);\n        return DrawUtils.getMultiTextHeight(paint, getSplitString(column.format(position)));\n    }\n\n    @Override\n    public void draw(Canvas c, Rect rect, CellInfo<T> cellInfo) {\n        TableConfig config = TableConfig.getInstance();\n        Paint paint = config.getPaint();\n        setTextPaint(config, cellInfo, paint);\n        if (cellInfo.column.getTextAlign() != null) {\n            paint.setTextAlign(cellInfo.column.getTextAlign());\n        }\n        drawText(c, cellInfo.value, rect, paint);\n    }\n\n    protected void drawText(Canvas c, String value, Rect rect, Paint paint) {\n        DrawUtils.drawMultiText(c, paint, rect, getSplitString(value));\n    }\n\n\n    public void setTextPaint(TableConfig config, CellInfo<T> cellInfo, Paint paint) {\n        config.contentStyle.fillPaint(paint);\n        paint.setTextSize(paint.getTextSize() * config.getZoom());\n\n    }\n\n    protected String[] getSplitString(String val) {\n        String[] values = null;\n        if (valueMap.get(val) != null) {\n            values = valueMap.get(val).get();\n        }\n        if (values == null) {\n            values = val.split(\"\\n\");\n\n            valueMap.put(val, new SoftReference<>(values));\n        }\n        return values;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/format/TitleDrawFormat.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.format;\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\n\nimport com.didichuxing.doraemonkit.widget.tableview.bean.Column;\nimport com.didichuxing.doraemonkit.widget.tableview.utils.DrawUtils;\nimport com.didichuxing.doraemonkit.widget.tableview.TableConfig;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.ITitleDrawFormat;\n\n\npublic class TitleDrawFormat implements ITitleDrawFormat {\n\n    private boolean isDrawBg;\n\n    @Override\n    public int measureWidth(Column column, TableConfig config) {\n        Paint paint = config.getPaint();\n        config.columnTitleStyle.fillPaint(paint);\n        return (int) (paint.measureText(column.getColumnName()));\n    }\n\n\n    @Override\n    public int measureHeight(TableConfig config) {\n        Paint paint = config.getPaint();\n        config.columnTitleStyle.fillPaint(paint);\n        return DrawUtils.getTextHeight(config.columnTitleStyle,config.getPaint());\n    }\n\n    @Override\n    public void draw(Canvas c, Column column, Rect rect, TableConfig config) {\n        Paint paint = config.getPaint();\n        config.columnTitleStyle.fillPaint(paint);\n\n        paint.setTextSize(paint.getTextSize()*config.getZoom());\n\n        drawText(c, column, rect, paint);\n    }\n\n    private void drawText(Canvas c, Column column, Rect rect, Paint paint) {\n        if(column.getTitleAlign() !=null) { //如果列设置Align ，则使用列的Align\n            paint.setTextAlign(column.getTitleAlign());\n        }\n        c.drawText(column.getColumnName(), DrawUtils.getTextCenterX(rect.left,rect.right,paint), DrawUtils.getTextCenterY((rect.bottom+rect.top)/2,paint) ,paint);\n    }\n\n    public boolean isDrawBg() {\n        return isDrawBg;\n    }\n\n    public void setDrawBg(boolean drawBg) {\n        isDrawBg = drawBg;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/intface/IComponent.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.intface;\n\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\n\nimport com.didichuxing.doraemonkit.widget.tableview.TableConfig;\n\npublic interface IComponent<T> {\n\n    int LEFT = 0;\n    int TOP = 1;\n    int RIGHT = 2;\n    int BOTTOM = 3;\n\n    /**\n     * 计算组件Rect\n     */\n    void onMeasure(Rect scaleRect, Rect showRect, TableConfig config);\n\n    /**\n     * 绘制组件\n     *\n     * @param canvas 画布\n     * @param t      数据\n     */\n    void onDraw(Canvas canvas, Rect showRect, T t, TableConfig config);\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/intface/IDrawFormat.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.intface;\n\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\n\nimport com.didichuxing.doraemonkit.widget.tableview.bean.CellInfo;\nimport com.didichuxing.doraemonkit.widget.tableview.bean.Column;\n\n\npublic interface IDrawFormat<T> {\n\n    /**\n     * 测量宽\n     */\n    int measureWidth(Column<T> column, int position);\n\n    /**\n     * 测量高\n     */\n    int measureHeight(Column<T> column, int position);\n\n\n    void draw(Canvas c, Rect rect, CellInfo<T> cellInfo);\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/intface/IFormat.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.intface;\n\n\npublic interface IFormat<T> {\n\n    String format(T t);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/intface/ISelectFormat.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.intface;\n\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\n\nimport com.didichuxing.doraemonkit.widget.tableview.TableConfig;\n\npublic interface ISelectFormat {\n\n    void draw(Canvas canvas, Rect rect, Rect showRect, TableConfig config);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/intface/ISequenceFormat.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.intface;\n\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\n\nimport com.didichuxing.doraemonkit.widget.tableview.TableConfig;\n\npublic interface ISequenceFormat extends IFormat<Integer> {\n\n\n    void draw(Canvas canvas, int sequence, Rect rect, TableConfig config);\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/intface/IStyle.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.intface;\n\nimport android.graphics.Paint;\n\n\npublic interface IStyle {\n\n    void fillPaint(Paint paint);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/intface/ITableTitle.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.intface;\n\n\n\n\npublic interface ITableTitle extends IComponent<String> {\n\n    int getSize();\n\n    void setSize(int size);\n\n\n    int getDirection();\n\n    void setDirection(int direction);\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/intface/ITitleDrawFormat.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.intface;\n\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\n\nimport com.didichuxing.doraemonkit.widget.tableview.bean.Column;\nimport com.didichuxing.doraemonkit.widget.tableview.TableConfig;\n\n\n\npublic interface ITitleDrawFormat {\n\n    /**\n     *测量宽\n     */\n    int measureWidth(Column column, TableConfig config);\n\n    /**\n     *测量高\n     */\n    int measureHeight(TableConfig config);\n\n    /**\n     * 绘制\n     * @param c 画笔\n     * @param column 列信息\n     */\n    void draw(Canvas c, Column column, Rect rect, TableConfig config);\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/intface/ITouch.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.intface;\n\nimport android.view.MotionEvent;\nimport android.view.View;\n\n\npublic interface ITouch {\n    /**\n     * 用于判断是否请求不拦截事件\n     * 解决手势冲突问题\n     *\n     * @param view\n     * @param event\n     */\n    void onDisallowInterceptEvent(View view, MotionEvent event);\n\n    /**\n     * 处理touchEvent\n     *\n     * @param event\n     */\n    boolean handlerTouchEvent(MotionEvent event);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/listener/Observable.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.listener;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n\npublic abstract  class Observable<T> {\n\n    public final ArrayList<T> observables = new ArrayList<>();\n\n    /**AttachObserver（通过实例注册观察者）\n     **/\n    public void register(T observer){\n        if(observer==null) throw new NullPointerException();\n        synchronized(observables){\n            if(!observables.contains(observer)){\n                observables.add(observer);\n            }\n        }\n    }\n\n\n\n    /**UnattachObserver（注销观察者）\n     **/\n    public void unRegister(T observer){\n        if(observer==null) throw new NullPointerException();\n        if(observables.contains(observer)){\n            observables.remove(observer);\n        }\n    }\n\n\n    public void unRegisterAll(){\n        synchronized(observables){\n            observables.clear();\n        }\n    }\n\n    /**Ruturnthesizeofobservers*/\n    public int countObservers(){\n        synchronized(observables){\n            return observables.size();\n        }\n    }\n\n    /**\n     *notify all observer（通知所有观察者，在子类中实现）\n     */\n    public abstract void notifyObservers(List<T> observers);\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/listener/OnColumnClickListener.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.listener;\n\nimport com.didichuxing.doraemonkit.widget.tableview.bean.ColumnInfo;\n\n\npublic interface OnColumnClickListener {\n\n    void onClick(ColumnInfo columnInfo);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/listener/OnColumnItemClickListener.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.listener;\n\nimport com.didichuxing.doraemonkit.widget.tableview.bean.Column;\n\n\npublic interface OnColumnItemClickListener<T> {\n\n    void onClick(Column<T> column, String value, T t, int position);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/listener/OnTableChangeListener.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.listener;\n\n\npublic interface OnTableChangeListener {\n\n    void onTableChanged(float scale, float translateX, float translateY);\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/listener/TableClickObserver.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.listener;\n\n\npublic interface TableClickObserver {\n\n    void onClick(float x, float y);\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/style/FontStyle.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.style;\n\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.graphics.Paint;\n\nimport com.didichuxing.doraemonkit.widget.tableview.intface.IStyle;\nimport com.didichuxing.doraemonkit.widget.tableview.utils.DensityUtils;\n\n\npublic class FontStyle implements IStyle {\n\n    private static int defaultFontSize = 12;\n    private static int defaultFontColor = Color.parseColor(\"#636363\");\n    private static Paint.Align defaultAlign = Paint.Align.CENTER;\n    private int textSize;\n    private int textColor;\n    private Paint.Align align;\n\n    /**\n     * 设置表格全局默认字体大小\n     *\n     * @param defaultTextSize 默认字体大小\n     */\n    public static void setDefaultTextSize(int defaultTextSize) {\n        defaultFontSize = defaultTextSize;\n    }\n\n    /**\n     * 设置表格全局默认字体位置\n     *\n     * @param align 默认字体位置\n     */\n    public static void setDefaultTextAlign(Paint.Align align) {\n        defaultAlign = align;\n    }\n\n    /**\n     * 设置表格全局默认字体大小\n     *\n     * @param defaultTextSpSize 默认字体Sp大小\n     */\n    public static void setDefaultTextSpSize(Context context, int defaultTextSpSize) {\n        defaultFontSize = DensityUtils.sp2px(context, defaultTextSpSize);\n    }\n\n    /**\n     * 设置表格全局默认字体颜色\n     *\n     * @param defaultTextColor 默认字体颜色\n     */\n    public static void setDefaultTextColor(int defaultTextColor) {\n        defaultFontColor = defaultTextColor;\n    }\n\n    public FontStyle() {\n    }\n\n    public FontStyle(int textSize, int textColor) {\n        this.textSize = textSize;\n        this.textColor = textColor;\n    }\n\n    public FontStyle(Context context, int sp, int textColor) {\n        this.textSize = DensityUtils.sp2px(context, sp);\n        this.textColor = textColor;\n    }\n\n    public Paint.Align getAlign() {\n        if (align == null) {\n            return defaultAlign;\n        }\n        return align;\n    }\n\n    public FontStyle setAlign(Paint.Align align) {\n        this.align = align;\n        return this;\n    }\n\n    public int getTextSize() {\n        if (textSize == 0) {\n            return defaultFontSize;\n        }\n        return textSize;\n    }\n\n    public FontStyle setTextSize(int textSize) {\n        this.textSize = textSize;\n        return this;\n    }\n\n    public void setTextSpSize(Context context, int sp) {\n        this.setTextSize(DensityUtils.sp2px(context, sp));\n    }\n\n    public int getTextColor() {\n        if (textColor == 0) {\n            return defaultFontColor;\n        }\n        return textColor;\n    }\n\n    public FontStyle setTextColor(int textColor) {\n\n        this.textColor = textColor;\n        return this;\n    }\n\n\n    @Override\n    public void fillPaint(Paint paint) {\n        paint.setColor(getTextColor());\n        paint.setTextAlign(getAlign());\n        paint.setTextSize(getTextSize());\n        paint.setStyle(Paint.Style.FILL);\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/style/LineStyle.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.style;\n\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.PathEffect;\n\nimport com.didichuxing.doraemonkit.widget.tableview.intface.IStyle;\nimport com.didichuxing.doraemonkit.widget.tableview.utils.DensityUtils;\n\n\npublic class LineStyle implements IStyle {\n\n    private float width = -1;\n    private int color = -1;\n    private boolean isFill;\n    private PathEffect effect = new PathEffect();\n    private static float defaultLineSize = 2f;\n    private static int defaultLineColor = Color.parseColor(\"#e6e6e6\");\n\n    public LineStyle() {\n\n    }\n\n\n    public LineStyle(float width, int color) {\n        this.width = width;\n        this.color = color;\n    }\n\n    public LineStyle(Context context, float dp, int color) {\n        this.width = DensityUtils.dp2px(context, dp);\n        this.color = color;\n    }\n\n    public static void setDefaultLineSize(float width) {\n        defaultLineSize = width;\n    }\n\n    public static void setDefaultLineSize(Context context, float dp) {\n        defaultLineSize = DensityUtils.dp2px(context, dp);\n    }\n\n    public static void setDefaultLineColor(int color) {\n        defaultLineColor = color;\n    }\n\n    public float getWidth() {\n        if (width == -1) {\n            return defaultLineSize;\n        }\n        return width;\n    }\n\n    public LineStyle setWidth(float width) {\n        this.width = width;\n        return this;\n    }\n\n    public LineStyle setWidth(Context context, int dp) {\n        this.width = DensityUtils.dp2px(context, dp);\n        return this;\n    }\n\n    public int getColor() {\n        if (color == -1) {\n            return defaultLineColor;\n        }\n        return color;\n    }\n\n    public boolean isFill() {\n        return isFill;\n    }\n\n    public LineStyle setFill(boolean fill) {\n        isFill = fill;\n        return this;\n    }\n\n    public LineStyle setColor(int color) {\n\n        this.color = color;\n        return this;\n    }\n\n    public LineStyle setEffect(PathEffect effect) {\n        this.effect = effect;\n        return this;\n    }\n\n    @Override\n    public void fillPaint(Paint paint) {\n        paint.setColor(getColor());\n        paint.setStyle(isFill ? Paint.Style.FILL : Paint.Style.STROKE);\n        paint.setStrokeWidth(getWidth());\n        paint.setPathEffect(effect);\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/utils/DensityUtils.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.utils;\n\nimport android.content.Context;\nimport android.util.TypedValue;\n\n\n\npublic class DensityUtils {\n\n    private DensityUtils() {\n    }\n\n\n    /**\n     * dp转px\n     *\n     * @param context\n     * @return\n     */\n\n    public static int dp2px(Context context, float dpVal) {\n\n        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,\n\n                dpVal, context.getResources().getDisplayMetrics());\n\n    }\n\n\n    /**\n     * sp转px\n     *\n     * @param context\n     * @return\n     */\n\n    public static int sp2px(Context context, float spVal) {\n\n        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,\n\n                spVal, context.getResources().getDisplayMetrics());\n\n    }\n\n\n    /**\n     * px转dp\n     *\n     * @param context\n     * @param pxVal\n     * @return\n     */\n\n    public static float px2dp(Context context, float pxVal) {\n\n        final float scale = context.getResources().getDisplayMetrics().density;\n\n        return (pxVal / scale);\n\n    }\n\n\n    /**\n     * px转sp\n     *\n     * @param pxVal\n     * @return\n     */\n\n    public static float px2sp(Context context, float pxVal) {\n\n        return (pxVal / context.getResources().getDisplayMetrics().scaledDensity);\n\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/utils/DrawUtils.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.utils;\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.PointF;\nimport android.graphics.Rect;\n\nimport com.didichuxing.doraemonkit.widget.tableview.style.FontStyle;\nimport com.didichuxing.doraemonkit.widget.tableview.TableConfig;\n\n\npublic class DrawUtils {\n\n    public static int getTextHeight(FontStyle style, Paint paint){\n        style.fillPaint(paint);\n        Paint.FontMetrics fontMetrics = paint.getFontMetrics();\n        return (int) (fontMetrics.descent - fontMetrics.ascent);\n    }\n\n    public static int getTextHeight(Paint paint){\n        Paint.FontMetrics fontMetrics = paint.getFontMetrics();\n        return (int) (fontMetrics.descent - fontMetrics.ascent);\n    }\n\n    public static float getTextCenterY(int centerY, Paint paint){\n       return centerY-((paint.descent() + paint.ascent()) / 2);\n    }\n\n    public static float getTextCenterX(int left, int right, Paint paint){\n        Paint.Align align = paint.getTextAlign();\n        if(align == Paint.Align.RIGHT){\n            return right;\n        }else if(align == Paint.Align.LEFT){\n            return left;\n        }else{\n            return (right +left)/2;\n        }\n    }\n\n    public static boolean isMixRect(Rect rect, int left, int top, int right, int bottom){\n\n        return rect.bottom>= top && rect.right >= left && rect.top <bottom && rect.left< right;\n    }\n\n    public static boolean isClick(int left, int top, int right, int bottom, PointF clickPoint){\n        return clickPoint.x >= left && clickPoint.x <=right && clickPoint.y>=top && clickPoint.y <=bottom;\n    }\n\n    public static boolean isClick(Rect rect, PointF clickPoint){\n        return rect.contains((int)clickPoint.x,(int)clickPoint.y);\n    }\n    public static void fillBackground(Canvas canvas, int left, int top, int right, int bottom, int bgColor, Paint paint){\n        if(bgColor != TableConfig.INVALID_COLOR) {\n            paint.setColor(bgColor);\n            paint.setStyle(Paint.Style.FILL);\n            canvas.drawRect(left, top, right, bottom, paint);\n        }\n    }\n\n    public static boolean isMixHorizontalRect(Rect rect, int left, int right){\n\n        return   rect.right >= left  && rect.left<= right;\n    }\n    public static boolean isVerticalMixRect(Rect rect, int top, int bottom){\n\n        return rect.bottom>= top  && rect.top <=bottom;\n    }\n\n    /**\n     * 获取多行文字高度\n     * @param paint\n     * @return\n     */\n    public static int getMultiTextHeight(Paint paint, String[] values){\n\n        return getTextHeight(paint)* values.length;\n    }\n\n    /**\n     * 获取多行文字宽度\n     * @param paint\n     * @return\n     */\n    public static int getMultiTextWidth(Paint paint, String[] values){\n\n        int maxWidth  =0;\n        for(String val :values){\n            int width = (int) paint.measureText(val);\n            if(maxWidth < width){\n                maxWidth = width;\n            }\n        }\n        return maxWidth;\n    }\n\n\n\n    /**\n     * 绘制多行文字\n     * @param canvas\n     * @param paint\n     * @param rect\n     */\n    public static void drawMultiText(Canvas canvas, Paint paint, Rect rect, String[] values){\n        for(int i =0;i <values.length;i++) {\n            int centerY = (int) ((rect.bottom + rect.top) / 2+ (values.length/2f-i-0.5)*getTextHeight(paint));\n            canvas.drawText(values[values.length-i-1], DrawUtils.getTextCenterX(rect.left, rect.right, paint),\n                    DrawUtils.getTextCenterY(centerY, paint), paint);\n        }\n    }\n\n    /**\n     * 绘制单行文字\n     * @param canvas\n     * @param paint\n     * @param rect\n     * @param value\n     */\n    public static void drawSingleText(Canvas canvas, Paint paint, Rect rect, String value){\n        canvas.drawText(value, DrawUtils.getTextCenterX(rect.left, rect.right, paint),\n                DrawUtils.getTextCenterY(rect.centerY(), paint), paint);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/tableview/utils/MatrixHelper.java",
    "content": "package com.didichuxing.doraemonkit.widget.tableview.utils;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimport android.animation.TimeInterpolator;\nimport android.animation.TypeEvaluator;\nimport android.animation.ValueAnimator;\nimport android.content.Context;\nimport android.graphics.Point;\nimport android.graphics.Rect;\nimport android.view.GestureDetector;\nimport android.view.MotionEvent;\nimport android.view.ScaleGestureDetector;\nimport android.view.View;\nimport android.view.ViewConfiguration;\nimport android.view.ViewParent;\nimport android.view.animation.DecelerateInterpolator;\nimport android.widget.Scroller;\n\nimport com.didichuxing.doraemonkit.widget.tableview.bean.TableInfo;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.IComponent;\nimport com.didichuxing.doraemonkit.widget.tableview.intface.ITouch;\nimport com.didichuxing.doraemonkit.widget.tableview.listener.Observable;\nimport com.didichuxing.doraemonkit.widget.tableview.listener.OnTableChangeListener;\nimport com.didichuxing.doraemonkit.widget.tableview.listener.TableClickObserver;\n\nimport java.util.List;\n\n\npublic class MatrixHelper extends Observable<TableClickObserver> implements ITouch, ScaleGestureDetector.OnScaleGestureListener {\n\n    private float maxZoom = 5;\n    private float minZoom = 1;\n    private float zoom = minZoom; //缩放比例  不得小于1\n    private int translateX; //以左上角为准，X轴位移的距离\n    private int translateY;//以左上角为准，y轴位移的距离\n    private ScaleGestureDetector mScaleGestureDetector;\n    private GestureDetector mGestureDetector;\n    private boolean isCanZoom = false;\n    private boolean isScale; //是否正在缩小\n    private Rect originalRect; //原始大小\n    private Rect zoomRect;\n    private float mDownX, mDownY;\n    private int pointMode; //屏幕的手指点个数\n    private Scroller scroller;\n    private int mMinimumVelocity;\n    private boolean isFling;\n    private OnTableChangeListener listener;\n    private float flingRate = 1f; //速率\n    private Rect scaleRect = new Rect();\n    private boolean isZooming; //是否正在缩放\n    private boolean isAutoFling = false;\n    private OnInterceptListener onInterceptListener;\n    int touchSlop; //最小滚动距离\n\n    /**\n     * 手势帮助类构造方法\n     *\n     * @param context 用于获取GestureDetector，scroller ViewConfiguration\n     */\n    public MatrixHelper(Context context) {\n        mScaleGestureDetector = new ScaleGestureDetector(context, this);\n        mGestureDetector = new GestureDetector(context, new OnTableGestureListener());\n        final ViewConfiguration configuration = ViewConfiguration.get(context);\n        touchSlop = configuration.getScaledTouchSlop();\n        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();\n        scroller = new Scroller(context);\n        zoomRect = new Rect();\n        originalRect = new Rect();\n    }\n\n    /**\n     * 处理手势\n     */\n    @Override\n    public boolean handlerTouchEvent(MotionEvent event) {\n        if (isCanZoom) {\n            mScaleGestureDetector.onTouchEvent(event);\n        }\n        mGestureDetector.onTouchEvent(event);\n        return true;\n    }\n\n\n    /**\n     * 判断是否需要接收触摸事件\n     */\n    @Override\n    public void onDisallowInterceptEvent(View view, MotionEvent event) {\n\n        ViewParent parent = view.getParent();\n        if (zoomRect == null || originalRect == null) {\n            parent.requestDisallowInterceptTouchEvent(false);\n            return;\n        }\n        switch (event.getAction() & MotionEvent.ACTION_MASK) {\n            case MotionEvent.ACTION_DOWN:\n                pointMode = 1;\n                //ACTION_DOWN的时候，赶紧把事件hold住\n                mDownX = event.getX();\n                mDownY = event.getY();\n                if (originalRect.contains((int) mDownX, (int) mDownY)) { //判断是否落在图表内容区中\n                    parent.requestDisallowInterceptTouchEvent(true);\n                } else {\n                    parent.requestDisallowInterceptTouchEvent(false);\n                }\n\n                break;\n            case MotionEvent.ACTION_POINTER_DOWN:\n                //判断是否是多指操作\n                pointMode += 1;\n                parent.requestDisallowInterceptTouchEvent(true);\n                break;\n            case MotionEvent.ACTION_MOVE:\n                if (pointMode > 1) {\n                    parent.requestDisallowInterceptTouchEvent(true);\n                    return;\n                }\n                float disX = event.getX() - mDownX;\n                float disY = event.getY() - mDownY;\n                boolean isDisallowIntercept = true;\n                if (Math.abs(disX) > Math.abs(disY)) {\n                    if ((disX > 0 && toRectLeft()) || (disX < 0 && toRectRight())) { //向右滑动\n                        isDisallowIntercept = false;\n                    }\n                } else {\n                    if ((disY > 0 && toRectTop()) || (disY < 0 && toRectBottom())) {\n                        isDisallowIntercept = false;\n                    }\n                }\n                parent.requestDisallowInterceptTouchEvent(isDisallowIntercept);\n                break;\n            case MotionEvent.ACTION_POINTER_UP:\n                pointMode -= 1;\n                break;\n            case MotionEvent.ACTION_CANCEL:\n            case MotionEvent.ACTION_UP:\n                pointMode = 0;\n                parent.requestDisallowInterceptTouchEvent(false);\n        }\n\n    }\n\n    /**\n     * 通过translateX值判断是否到左边界\n     * 通过该方法可以判断是否继续拦截滑动事件\n     *\n     * @return 是否到左边界\n     */\n    private boolean toRectLeft() {\n        return translateX <= 0;\n    }\n\n    /**\n     * 通过translateX值判断是否到右边界\n     *\n     * @return 是否到右边界\n     */\n    private boolean toRectRight() {\n        return translateX >= zoomRect.width() - originalRect.width();\n    }\n\n    /**\n     * 通过translateY值判断是否到底部边界\n     *\n     * @return 是否到底部边界\n     */\n    private boolean toRectBottom() {\n        int height = zoomRect.height() - originalRect.height();\n        return translateY >= height;\n    }\n\n    /**\n     * 通过translateY值判断是否到顶部边界\n     *\n     * @return 是否到顶部边界\n     */\n    private boolean toRectTop() {\n        return translateY <= 0;\n    }\n\n    /**\n     * 通知View更新\n     */\n    private void notifyViewChanged() {\n        if (listener != null) {\n            listener.onTableChanged(zoom, translateX, translateY);\n        }\n    }\n\n    /**\n     * 被观察者通知方法\n     *\n     * @param observers\n     */\n    @Override\n    public void notifyObservers(List<TableClickObserver> observers) {\n        //暂时不需要\n    }\n\n    /**\n     * 临时保存TranslateX值\n     */\n    private int tempTranslateX; //以左上角为准，X轴位移的距离\n    /**\n     * 临时保存TranslateY值\n     */\n    private int tempTranslateY;//以左上角为准，y轴位移的距离\n\n    /**\n     * 临时保存缩放值\n     */\n    private float tempZoom = minZoom; //缩放比例  不得小于1\n\n    /**\n     * 手势监听\n     */\n    class OnTableGestureListener extends GestureDetector.SimpleOnGestureListener {\n\n        @Override\n        public void onLongPress(MotionEvent e) {\n            super.onLongPress(e);\n        }\n\n        @Override\n        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {\n            if (onInterceptListener == null || !onInterceptListener.isIntercept(e1, distanceX, distanceY)) {\n\n                translateX += distanceX;\n                translateY += distanceY;\n                notifyViewChanged();\n            }\n            return true;\n        }\n\n        @Override\n        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {\n            //根据滑动速率 设置Scroller final值,然后使用属性动画计算\n            if (Math.abs(velocityX) > mMinimumVelocity || Math.abs(velocityY) > mMinimumVelocity) {\n                scroller.setFinalX(0);\n                scroller.setFinalY(0);\n                tempTranslateX = translateX;\n                tempTranslateY = translateY;\n                scroller.fling(0, 0, (int) velocityX, (int) velocityY, -50000, 50000\n                        , -50000, 50000);\n                isFling = true;\n                startFilingAnim(false);\n            }\n\n            return true;\n        }\n\n        @Override\n        public boolean onDown(MotionEvent e) {\n            isFling = false;\n\n            return true;\n        }\n\n\n        //双击\n        @Override\n        public boolean onDoubleTap(MotionEvent e) {\n            if (isCanZoom) {\n                float oldZoom = zoom;\n                if (isScale) { //缩小\n                    zoom = zoom / 1.5f;\n                    if (zoom < minZoom) {\n                        zoom = minZoom;\n                        isScale = false;\n                    }\n                } else { //放大\n                    zoom = zoom * 1.5f;\n                    if (zoom > maxZoom) {\n                        zoom = maxZoom;\n                        isScale = true;\n                    }\n                }\n                float factor = zoom / oldZoom;\n                resetTranslate(factor);\n                notifyViewChanged();\n            }\n\n            return true;\n        }\n\n        //单击\n        @Override\n        public boolean onSingleTapUp(MotionEvent e) {\n            notifyViewChanged();\n            for (TableClickObserver observer : observables) {\n                observer.onClick(e.getX(), e.getY());\n            }\n            return true;\n        }\n    }\n\n\n    @Override\n    public boolean onScaleBegin(ScaleGestureDetector detector) {\n        tempZoom = this.zoom;\n        isZooming = true;\n        return true;\n    }\n\n    private boolean isScaleMax;\n    private boolean isScaleMin;\n\n    @Override\n    public boolean onScale(ScaleGestureDetector detector) {\n        float oldZoom = zoom;\n        boolean isScaleEnd = false;\n        float scale = detector.getScaleFactor();\n        if (scale > 1 && isScaleMax) {\n            isScaleMin = false;\n            return true;\n        } else if (scale < 1 && isScaleMin) {\n            isScaleMax = false;\n            return true;\n        }\n        this.zoom = tempZoom * scale;\n        if (zoom >= maxZoom) {\n            isScaleMax = true;\n            this.zoom = maxZoom;\n            isScaleEnd = true;\n        } else if (this.zoom <= minZoom) {\n            isScaleMin = true;\n            this.zoom = minZoom;\n            isScaleEnd = true;\n        } else {\n            isScaleMin = false;\n            isScaleMax = false;\n        }\n        float factor = zoom / oldZoom;\n        resetTranslate(factor);\n        notifyViewChanged();\n        return isScaleEnd;\n    }\n\n    @Override\n    public void onScaleEnd(ScaleGestureDetector detector) {\n        isZooming = false;\n    }\n\n\n    private Point startPoint = new Point(0, 0);\n    private Point endPoint = new Point();\n    private TimeInterpolator interpolator = new DecelerateInterpolator();\n    private TypeEvaluator evaluator = new TypeEvaluator() {\n\n        private Point point = new Point();\n\n        @Override\n        public Object evaluate(float fraction, Object startValue, Object endValue) {\n            Point startPoint = (Point) startValue;\n            Point endPoint = (Point) endValue;\n            int x = (int) (startPoint.x + fraction * (endPoint.x - startPoint.x));\n            int y = (int) (startPoint.y + fraction * (endPoint.y - startPoint.y));\n            point.set(x, y);\n            return point;\n        }\n    };\n\n    /**\n     * 开始飞滚\n     *\n     * @param doubleWay 双向飞滚\n     */\n    private void startFilingAnim(boolean doubleWay) {\n\n        int scrollX = Math.abs(scroller.getFinalX());\n        int scrollY = Math.abs(scroller.getFinalY());\n        if (doubleWay) {\n            endPoint.set((int) (scroller.getFinalX() * flingRate),\n                    (int) (scroller.getFinalY() * flingRate));\n        } else {\n            if (scrollX > scrollY) {\n                endPoint.set((int) (scroller.getFinalX() * flingRate), 0);\n            } else {\n                endPoint.set(0, (int) (scroller.getFinalY() * flingRate));\n            }\n        }\n        final ValueAnimator valueAnimator = ValueAnimator.ofObject(evaluator, startPoint, endPoint);\n        valueAnimator.setInterpolator(interpolator);\n        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator animation) {\n                if (isFling) {\n                    Point point = (Point) animation.getAnimatedValue();\n                    translateX = tempTranslateX - point.x;\n                    translateY = tempTranslateY - point.y;\n                    notifyViewChanged();\n                } else {\n                    animation.cancel();\n                }\n            }\n        });\n        int duration = (int) (Math.max(scrollX, scrollY) * flingRate) / 2;\n        valueAnimator.setDuration(duration > 300 ? 300 : duration);\n        valueAnimator.start();\n    }\n\n    /**\n     * 重新计算偏移量\n     * * @param factor\n     */\n    private void resetTranslate(float factor) {\n\n        translateX = (int) (translateX * factor);\n        translateY = (int) (translateY * factor);\n    }\n\n\n    /**\n     * 获取图片内容的缩放大小\n     *\n     * @param showRect     当前View显示大小\n     * @param providerRect 表格实际需要的大小\n     * @return 缩放后内容的大小\n     */\n    public Rect getZoomProviderRect(Rect showRect, Rect providerRect, TableInfo tableInfo) {\n        originalRect.set(showRect);\n        int showWidth = showRect.width();\n        int showHeight = showRect.height();\n        int offsetX = (int) (showWidth * (zoom - 1)) / 2;\n        int offsetY = (int) (showHeight * (zoom - 1)) / 2;\n        if (!isAutoFling) {\n            int oldw = providerRect.width();\n            int oldh = providerRect.height();\n            int newWidth = (int) (oldw * zoom);\n            int newHeight = (int) (oldh * zoom);\n            /**\n             * 在表格中，x序列和Y序列不需要跟随放大，需要减掉多计算部分\n             */\n            if (zoom > 1) {\n                newWidth -= (int) (tableInfo.getyAxisWidth() * (zoom - 1));\n                newHeight -= (int) (tableInfo.getTopHeight() * (zoom - 1));\n            }\n\n            /**\n             * 表格的标题不会跟随放大和缩小，也需要减掉多计算部分\n             * 根据表格标题方向来判断减掉高还是宽\n             */\n            if (tableInfo.getTitleDirection() == IComponent.TOP\n                    || tableInfo.getTitleDirection() == IComponent.BOTTOM) {\n                newHeight -= (int) (tableInfo.getTableTitleSize() * (zoom - 1));\n            } else {\n                newWidth -= (int) (tableInfo.getTableTitleSize() * (zoom - 1));\n            }\n            int minTranslateX = -offsetX;\n            int maxTranslateX = newWidth - showWidth - offsetX;\n            int minTranslateY = -offsetY;\n            int maxTranslateY = newHeight - showHeight - offsetY;\n            boolean isFullShowX = false, isFullShowY = false;\n            //计算出对比当前中心点的偏移量\n            if (maxTranslateX > minTranslateX) {\n                if (translateX < minTranslateX) {\n                    translateX = minTranslateX;\n\n                } else if (translateX > maxTranslateX) {\n                    translateX = maxTranslateX;\n                }\n            } else {\n                isFullShowX = true;\n            }\n            if (maxTranslateY > minTranslateY) {\n                if (translateY < minTranslateY) {\n                    translateY = minTranslateY;\n                } else if (translateY > maxTranslateY) {\n                    translateY = maxTranslateY;\n                }\n            } else {\n                isFullShowY = true;\n            }\n            scaleRect.left = providerRect.left - offsetX - translateX + showRect.left;\n            scaleRect.top = providerRect.top - offsetY - translateY + showRect.top;\n            if (isFullShowX) {\n                if (isZooming) {\n                    scaleRect.left = scaleRect.left < showRect.left ? showRect.left : scaleRect.left;\n                    scaleRect.left = scaleRect.left > showRect.right - newWidth ? showRect.right - newWidth : scaleRect.left;\n                } else {\n                    scaleRect.left = showRect.left;\n                    translateX = minTranslateX;\n                }\n            }\n            if (isFullShowY) {\n                if (isZooming) {\n                    scaleRect.top = scaleRect.top < showRect.top ? showRect.top : scaleRect.top;\n                    scaleRect.top = scaleRect.top > showRect.bottom - newHeight ? showRect.bottom - newHeight : scaleRect.top;\n                } else {\n                    scaleRect.top = showRect.top;\n                    translateY = minTranslateY;\n                }\n            }\n            scaleRect.right = scaleRect.left + newWidth;\n            scaleRect.bottom = scaleRect.top + newHeight;\n            zoomRect.set(scaleRect);\n        } else {\n            translateX = providerRect.left - zoomRect.left - offsetX;\n            translateY = providerRect.top - zoomRect.top - offsetY;\n            scaleRect.set(zoomRect);\n        }\n        return scaleRect;\n    }\n\n    public void setZoom(float zoom) {\n        this.zoom = zoom;\n    }\n\n    public Rect getZoomRect() {\n        return zoomRect;\n    }\n\n    public Rect getOriginalRect() {\n        return originalRect;\n    }\n\n\n    /**\n     * 是否可以缩放\n     *\n     * @return 是否可以缩放\n     */\n    public boolean isCanZoom() {\n        zoom = 1f;\n        return isCanZoom;\n\n    }\n\n    /**\n     * 获取表格改变监听\n     * 主要用于SmartTable view监听matrixHelper 移动和缩放\n     */\n    public OnTableChangeListener getOnTableChangeListener() {\n        return listener;\n    }\n\n    /**\n     * 设置表格改变监听\n     * 主要用于SmartTable view监听matrixHelper 移动和缩放\n     * 请不要改变原来设置值\n     *\n     * @param onTableChangeListener 改变监听\n     */\n    public void setOnTableChangeListener(OnTableChangeListener onTableChangeListener) {\n        this.listener = onTableChangeListener;\n    }\n\n    /**\n     * 设置是否可以缩放\n     *\n     * @param canZoom\n     */\n    public void setCanZoom(boolean canZoom) {\n        isCanZoom = canZoom;\n        if (!isCanZoom) {\n            zoom = 1;\n        }\n    }\n\n    /**\n     * 设置最大缩放值\n     *\n     * @return 最大缩放值\n     */\n    public float getMaxZoom() {\n        return maxZoom;\n    }\n\n    /**\n     * 获取最小缩放值\n     *\n     * @return 最小缩放值\n     */\n    public float getMinZoom() {\n        return minZoom;\n    }\n\n\n    /**\n     * 设置最小缩放值\n     */\n    public void setMinZoom(float minZoom) {\n\n        if (minZoom < 0) {\n            minZoom = 0.1f;\n        }\n        this.minZoom = minZoom;\n    }\n\n    /**\n     * 设置最大缩放值\n     */\n    public void setMaxZoom(float maxZoom) {\n        if (maxZoom < 1) {\n            maxZoom = 1;\n        }\n        this.maxZoom = maxZoom;\n    }\n\n    public void reset() {\n        this.zoom = 1;\n        this.translateX = 0;\n        this.translateY = 0;\n        notifyViewChanged();\n    }\n\n    /**\n     * 飞滚到最左边\n     */\n    public void flingLeft(int duration) {\n        final int width = zoomRect.width();\n        ValueAnimator valueAnimator = ValueAnimator.ofInt(zoomRect.left, 0).setDuration(duration);\n        valueAnimator.addListener(animatorListenerAdapter);\n        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator animation) {\n                zoomRect.left = (int) animation.getAnimatedValue();\n                zoomRect.right = zoomRect.left + width;\n                notifyViewChanged();\n            }\n        });\n        valueAnimator.start();\n\n    }\n\n\n    /**\n     * 飞滚到最右边\n     */\n    public void flingRight(int duration) {\n        final int width = zoomRect.width();\n        ValueAnimator valueAnimator = ValueAnimator.ofInt(zoomRect.right,\n                originalRect.right).setDuration(duration);\n        valueAnimator.addListener(animatorListenerAdapter);\n        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator animation) {\n                zoomRect.right = (int) animation.getAnimatedValue();\n                zoomRect.left = zoomRect.right - width;\n                notifyViewChanged();\n            }\n        });\n        valueAnimator.start();\n    }\n\n\n    /**\n     * 飞滚到顶部\n     */\n    public void flingTop(int duration) {\n        final int height = zoomRect.height();\n        ValueAnimator valueAnimator = ValueAnimator.ofInt(zoomRect.top, 0).setDuration(duration);\n        valueAnimator.addListener(animatorListenerAdapter);\n        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator animation) {\n                zoomRect.top = (int) animation.getAnimatedValue();\n                zoomRect.bottom = zoomRect.top + height;\n                notifyViewChanged();\n            }\n        });\n        valueAnimator.start();\n    }\n\n\n    /**\n     * 飞滚到底部\n     */\n    public void flingBottom(int duration) {\n        final int height = zoomRect.height();\n        ValueAnimator valueAnimator = ValueAnimator.ofInt(zoomRect.bottom,\n                originalRect.bottom).setDuration(duration);\n        valueAnimator.addListener(animatorListenerAdapter);\n        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator animation) {\n                zoomRect.bottom = (int) animation.getAnimatedValue();\n                zoomRect.top = zoomRect.bottom - height;\n                notifyViewChanged();\n            }\n        });\n        valueAnimator.start();\n    }\n\n    private AnimatorListenerAdapter animatorListenerAdapter = new AnimatorListenerAdapter() {\n        @Override\n        public void onAnimationStart(Animator animation) {\n            isAutoFling = true;\n        }\n\n        @Override\n        public void onAnimationCancel(Animator animation) {\n            isAutoFling = false;\n        }\n\n        @Override\n        public void onAnimationEnd(Animator animation) {\n            isAutoFling = false;\n        }\n    };\n\n    /**\n     * 获取当前的缩放值\n     *\n     * @return 当前的缩放值\n     */\n    public float getZoom() {\n        return zoom;\n    }\n\n\n    /**\n     * 获取飞滚的速率\n     *\n     * @return 飞滚的速率\n     */\n    public float getFlingRate() {\n        return flingRate;\n    }\n\n    /**\n     * 动态设置飞滚的速率\n     *\n     * @param flingRate 速率\n     */\n    public void setFlingRate(float flingRate) {\n        this.flingRate = flingRate;\n    }\n\n    public OnInterceptListener getOnInterceptListener() {\n        return onInterceptListener;\n    }\n\n    public void setOnInterceptListener(OnInterceptListener onInterceptListener) {\n        this.onInterceptListener = onInterceptListener;\n    }\n\n    public interface OnInterceptListener {\n        boolean isIntercept(MotionEvent e1, float distanceX, float distanceY);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/textview/LabelTextView.java",
    "content": "package com.didichuxing.doraemonkit.widget.textview;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport androidx.annotation.AttrRes;\nimport androidx.annotation.ColorRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\n\nimport com.didichuxing.doraemonkit.R;\n\n/**\n * Created by jinliang on 2017/9/29.\n */\n\npublic class LabelTextView extends LinearLayout {\n    private TextView mLabel;\n    private TextView mText;\n\n    public LabelTextView(@NonNull Context context) {\n        this(context, null);\n    }\n\n    public LabelTextView(@NonNull Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public LabelTextView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init(context, attrs);\n    }\n\n    private void init(Context context, AttributeSet attrs) {\n        setOrientation(HORIZONTAL);\n        LayoutInflater.from(context).inflate(R.layout.dk_label_text_view, this);\n\n        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.LabelTextView);\n        String label = a.getString(R.styleable.LabelTextView_dkLabel);\n        String text = a.getString(R.styleable.LabelTextView_dkText);\n        int maxLines = a.getInt(R.styleable.LabelTextView_dkMaxLines, 0);\n        a.recycle();\n\n        mLabel = findViewById(R.id.label);\n        mText = findViewById(R.id.text);\n\n        setLabel(label);\n        setText(text);\n        if (maxLines > 0) {\n            mText.setMaxLines(maxLines);\n        }\n    }\n\n    public void setText(String text) {\n        mText.setText(text);\n    }\n\n    public void setText(@StringRes int text) {\n        mText.setText(text);\n    }\n\n    public void setLabel(String label) {\n        mLabel.setText(label);\n    }\n\n    public void setLabel(@StringRes int label) {\n        mLabel.setText(label);\n    }\n\n    public void setTextColor(@ColorRes int color) {\n        mText.setTextColor(getResources().getColor(color));\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/titlebar/HomeTitleBar.java",
    "content": "package com.didichuxing.doraemonkit.widget.titlebar;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.text.TextUtils;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\n\nimport com.didichuxing.doraemonkit.R;\n\n/**\n * Created by wanglikun on 2018/12/4.\n */\n\npublic class HomeTitleBar extends FrameLayout {\n    private OnTitleBarClickListener mListener;\n    private TextView mTitle;\n    private ImageView mIcon;\n\n    public HomeTitleBar(@NonNull Context context) {\n        this(context, null);\n    }\n\n    public HomeTitleBar(@NonNull Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public HomeTitleBar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init(context, attrs);\n    }\n\n    private void init(Context context, AttributeSet attrs) {\n        LayoutInflater.from(context).inflate(R.layout.dk_home_title_bar, this);\n        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.HomeTitleBar);\n        int icon = a.getResourceId(R.styleable.HomeTitleBar_dkIcon, R.mipmap.dk_close_icon_big);\n        String title = a.getString(R.styleable.HomeTitleBar_dkTitle);\n        a.recycle();\n\n        mIcon = findViewById(R.id.icon);\n        mTitle = findViewById(R.id.title);\n        mTitle.setSingleLine(true);\n        mIcon.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (mListener != null) {\n                    mListener.onRightClick();\n                }\n            }\n        });\n\n        setTitle(title);\n        setIcon(icon);\n    }\n\n    /**\n     * TitleBar 点击事件回调\n     */\n    public interface OnTitleBarClickListener {\n        void onRightClick();\n    }\n\n    public void setTitle(@StringRes int title) {\n        setTitle(getResources().getString(title));\n    }\n\n    public void setTitle(String title) {\n        if (TextUtils.isEmpty(title)) {\n            mTitle.setText(\"\");\n        } else {\n            mTitle.setText(title);\n            mTitle.setAlpha(0);\n            mTitle.setVisibility(View.VISIBLE);\n            mTitle.animate().alpha(1).start();\n        }\n    }\n\n    public void setIcon(@DrawableRes int id) {\n        if (id == 0) {\n            return;\n        }\n        mIcon.setImageResource(id);\n        mIcon.setVisibility(View.VISIBLE);\n    }\n\n    public void setListener(OnTitleBarClickListener listener) {\n        mListener = listener;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/titlebar/LogTitleBar.java",
    "content": "package com.didichuxing.doraemonkit.widget.titlebar;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.text.TextUtils;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\n\nimport com.didichuxing.doraemonkit.R;\n\n/**\n * 日志专属TitleBar\n */\npublic class LogTitleBar extends FrameLayout {\n    private OnTitleBarClickListener mListener;\n    private TextView mBack;\n    private TextView mTitle;\n    private ImageView mIcon;\n\n    public LogTitleBar(@NonNull Context context) {\n        this(context, null);\n    }\n\n    public LogTitleBar(@NonNull Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public LogTitleBar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init(context, attrs);\n    }\n\n    private void init(Context context, AttributeSet attrs) {\n        LayoutInflater.from(context).inflate(R.layout.dk_log_title_bar, this, true);\n        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.LogTitleBar);\n        int icon = a.getResourceId(R.styleable.LogTitleBar_dkIcon, 0);\n        String title = a.getString(R.styleable.LogTitleBar_dkTitle);\n        String back = a.getString(R.styleable.LogTitleBar_dkBack);\n        a.recycle();\n\n        mBack = findViewById(R.id.back);\n        mIcon = findViewById(R.id.icon);\n        mTitle = findViewById(R.id.title);\n\n        mIcon.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (mListener != null) {\n                    mListener.onRightClick();\n                }\n            }\n        });\n\n        mBack.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (mListener != null) {\n                    mListener.onLeftClick();\n                }\n            }\n        });\n\n        setBack(back);\n        setTitle(title);\n        setIcon(icon);\n    }\n\n    /**\n     * TitleBar 点击事件回调\n     */\n    public interface OnTitleBarClickListener {\n        void onRightClick();\n\n        void onLeftClick();\n    }\n\n    public void setTitle(@StringRes int title) {\n        setTitle(getResources().getString(title));\n    }\n\n    public void setTitle(String title) {\n        if (TextUtils.isEmpty(title)) {\n            mTitle.setText(\"\");\n        } else {\n            mTitle.setText(title);\n            mTitle.setAlpha(0);\n            mTitle.setVisibility(View.VISIBLE);\n            mTitle.animate().alpha(1).start();\n        }\n    }\n\n    public void setBack(String back) {\n        if (TextUtils.isEmpty(back)) {\n            mBack.setText(\"\");\n        } else {\n            mBack.setText(back);\n        }\n    }\n\n    public void setIcon(@DrawableRes int id) {\n        if (id == 0) {\n            return;\n        }\n        mIcon.setImageResource(id);\n        mIcon.setVisibility(View.VISIBLE);\n    }\n\n    public void setListener(OnTitleBarClickListener listener) {\n        mListener = listener;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/titlebar/TitleBar.java",
    "content": "package com.didichuxing.doraemonkit.widget.titlebar;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Bitmap;\nimport android.text.TextUtils;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.AttrRes;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\n\nimport com.didichuxing.doraemonkit.R;\n\n/**\n * Created by jinliang on 2017/8/28.\n */\n\npublic class TitleBar extends FrameLayout {\n    private ImageView mLeftIcon;\n    private TextView mTitle;\n    private TextView mLeftText;\n    private ImageView mRightIcon;\n    private TextView mRightText;\n\n    private OnTitleBarClickListener mListener;\n\n    public TitleBar(@NonNull Context context) {\n        this(context, null);\n    }\n\n    public TitleBar(@NonNull Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public TitleBar(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init(context, attrs);\n    }\n\n    private void init(Context context, AttributeSet attrs) {\n        LayoutInflater.from(context).inflate(R.layout.dk_title_bar, this, true);\n        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.TitleBar);\n        int leftIcon = a.getResourceId(R.styleable.TitleBar_dkLeftIcon, 0);\n        int rightIcon = a.getResourceId(R.styleable.TitleBar_dkRightIcon, 0);\n        int rightSubIcon = a.getResourceId(R.styleable.TitleBar_dkRightSubIcon, 0);\n        String title = a.getString(R.styleable.TitleBar_dkTitle);\n        int titleColor = a.getColor(R.styleable.TitleBar_dkTitleColor, 0);\n        int titleBackground = a.getColor(R.styleable.TitleBar_dkTitleBackground, getResources().getColor(R.color.dk_color_FFFFFF));\n        String rightText = a.getString(R.styleable.TitleBar_dkRightText);\n        String leftText = a.getString(R.styleable.TitleBar_dkLeftText);\n        a.recycle();\n\n        mLeftIcon = findViewById(R.id.left_icon);\n        mRightIcon = findViewById(R.id.right_icon);\n        mTitle = findViewById(R.id.title);\n\n        mRightText = findViewById(R.id.right_text);\n        mLeftText = findViewById(R.id.left_text);\n\n        ((ViewGroup) mLeftIcon.getParent()).setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                if (mListener != null) {\n                    mListener.onLeftClick();\n                }\n            }\n        });\n        if (rightSubIcon == 0) {\n            ((ViewGroup) mRightIcon.getParent()).setOnClickListener(new OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    if (mListener != null) {\n                        mListener.onRightClick();\n                    }\n                }\n            });\n            ((ViewGroup) mRightText.getParent()).setOnClickListener(new OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    if (mListener != null) {\n                        mListener.onRightClick();\n                    }\n                }\n            });\n        } else {\n            mRightIcon.setOnClickListener(new OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    if (mListener != null) {\n                        mListener.onRightClick();\n                    }\n                }\n            });\n        }\n\n        setLeftIcon(leftIcon);\n        setLeftText(leftText);\n        setRightText(rightText);\n        setRightIcon(rightIcon);\n        setRightTextColor(titleColor);\n        setTitle(title);\n        setTitleColor(titleColor);\n        setBackgroundColor(titleBackground);\n    }\n\n    private void setRightTextColor(int titleColor) {\n        if (titleColor == 0) {\n            return;\n        }\n        mRightText.setTextColor(titleColor);\n        mRightText.setVisibility(View.VISIBLE);\n    }\n\n    private void setTitleColor(int titleColor) {\n        if (titleColor == 0) {\n            return;\n        }\n        mTitle.setTextColor(titleColor);\n        mTitle.setVisibility(View.VISIBLE);\n    }\n\n    public void setTitle(String title) {\n        setTitle(title, true);\n    }\n\n    public void setTitle(String title, boolean alpha) {\n        if (TextUtils.isEmpty(title)) {\n            mTitle.setText(\"\");\n        } else {\n            mTitle.setText(title);\n            mTitle.setVisibility(View.VISIBLE);\n            if (alpha) {\n                mTitle.setAlpha(0);\n                mTitle.animate().alpha(1).start();\n            }\n        }\n    }\n\n    public void setTitle(@StringRes int title) {\n        setTitle(getResources().getString(title));\n    }\n\n    public void setTitleImage(int resId) {\n        mTitle.setBackgroundResource(resId);\n        mTitle.setVisibility(VISIBLE);\n    }\n\n    public void setOnTitleBarClickListener(OnTitleBarClickListener listener) {\n        mListener = listener;\n    }\n\n    public void setLeftIcon(@DrawableRes int id) {\n        if (id == 0) {\n            return;\n        }\n        mLeftIcon.setImageResource(id);\n        mLeftIcon.setVisibility(View.VISIBLE);\n    }\n\n    public void setRightIcon(@DrawableRes int id) {\n        if (id == 0) {\n            return;\n        }\n        mRightIcon.setImageResource(id);\n        mRightIcon.setVisibility(View.VISIBLE);\n    }\n\n    public void setRightIcon(Bitmap bitmap) {\n        if (bitmap == null) {\n            return;\n        }\n        mRightIcon.setImageBitmap(bitmap);\n        mRightIcon.setVisibility(VISIBLE);\n        mRightText.setVisibility(GONE);\n    }\n\n    public View getLeftView() {\n        return mLeftIcon;\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        return true;\n    }\n\n    public void setRightText(String text) {\n        if (TextUtils.isEmpty(text)) {\n            return;\n        }\n        mRightText.setText(text);\n        mRightText.setVisibility(View.VISIBLE);\n        mRightIcon.setVisibility(GONE);\n    }\n\n    public void setLeftText(String text) {\n        if (TextUtils.isEmpty(text)) {\n            return;\n        }\n        mLeftText.setText(text);\n        mLeftText.setVisibility(View.VISIBLE);\n    }\n\n    public void hideRight() {\n        mRightText.setVisibility(View.GONE);\n        mRightIcon.setVisibility(View.GONE);\n    }\n\n    public ImageView getRightIcon() {\n        return mRightIcon;\n    }\n\n    public TextView getRightText() {\n        return mRightText;\n    }\n\n    public ImageView getLeftIcon() {\n        return mLeftIcon;\n    }\n\n\n    /**\n     * TitleBar 点击事件回调\n     */\n    public interface OnTitleBarClickListener {\n        void onLeftClick();\n\n        void onRightClick();\n    }\n\n    public interface OnTitleBarCheckListener {\n        void onCheckChange(boolean isOn);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/verticalviewpager/VerticalViewPager.java",
    "content": "package com.didichuxing.doraemonkit.widget.verticalviewpager;\n\nimport android.content.Context;\nimport androidx.viewpager.widget.ViewPager;\n\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\n\nimport com.didichuxing.doraemonkit.widget.verticalviewpager.transforms.DefaultTransformer;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-27-14:53\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class VerticalViewPager extends ViewPager {\n\n    public VerticalViewPager(Context context) {\n        this(context, null);\n    }\n\n    public VerticalViewPager(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        setPageTransformer(false, new DefaultTransformer());\n    }\n\n    private MotionEvent swapTouchEvent(MotionEvent event) {\n        float width = getWidth();\n        float height = getHeight();\n\n        float swappedX = (event.getY() / height) * width;\n        float swappedY = (event.getX() / width) * height;\n\n        event.setLocation(swappedX, swappedY);\n\n        return event;\n    }\n\n    @Override\n    public boolean onInterceptTouchEvent(MotionEvent event) {\n        boolean intercept = super.onInterceptTouchEvent(swapTouchEvent(event));\n        //If not intercept, touch event should not be swapped.\n        swapTouchEvent(event);\n        return intercept;\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent ev) {\n        return super.onTouchEvent(swapTouchEvent(ev));\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/verticalviewpager/transforms/DefaultTransformer.java",
    "content": "package com.didichuxing.doraemonkit.widget.verticalviewpager.transforms;\n\nimport androidx.viewpager.widget.ViewPager;\n\nimport android.view.View;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-27-14:55\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class DefaultTransformer implements ViewPager.PageTransformer {\n\n    @Override\n    public void transformPage(View view, float position) {\n        float alpha = 0;\n        if (0 <= position && position <= 1) {\n            alpha = 1 - position;\n        } else if (-1 < position && position < 0) {\n            alpha = position + 1;\n        }\n        view.setAlpha(alpha);\n        view.setTranslationX(view.getWidth() * -position);\n        float yPosition = position * view.getHeight();\n        view.setTranslationY(yPosition);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/verticalviewpager/transforms/StackTransformer.java",
    "content": "package com.didichuxing.doraemonkit.widget.verticalviewpager.transforms;\n\nimport android.view.View;\n\nimport androidx.viewpager.widget.ViewPager;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-27-14:56\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class StackTransformer implements ViewPager.PageTransformer {\n    @Override\n    public void transformPage(View page, float position) {\n        page.setTranslationX(page.getWidth() * -position);\n        page.setTranslationY(position < 0 ? position * page.getHeight() : 0f);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/verticalviewpager/transforms/ZoomOutTransformer.java",
    "content": "package com.didichuxing.doraemonkit.widget.verticalviewpager.transforms;\n\nimport android.view.View;\n\nimport androidx.viewpager.widget.ViewPager;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-27-14:57\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class ZoomOutTransformer implements ViewPager.PageTransformer {\n    private static final float MIN_SCALE = 0.90f;\n\n    @Override\n    public void transformPage(View view, float position) {\n        int pageWidth = view.getWidth();\n        int pageHeight = view.getHeight();\n        float alpha = 0;\n        if (0 <= position && position <= 1) {\n            alpha = 1 - position;\n        } else if (-1 < position && position < 0) {\n            float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));\n            float verticalMargin = pageHeight * (1 - scaleFactor) / 2;\n            float horizontalMargin = pageWidth * (1 - scaleFactor) / 2;\n            if (position < 0) {\n                view.setTranslationX(horizontalMargin - verticalMargin / 2);\n            } else {\n                view.setTranslationX(-horizontalMargin + verticalMargin / 2);\n            }\n\n            view.setScaleX(scaleFactor);\n            view.setScaleY(scaleFactor);\n\n            alpha = position + 1;\n        }\n\n        view.setAlpha(alpha);\n        view.setTranslationX(view.getWidth() * -position);\n        float yPosition = position * view.getHeight();\n        view.setTranslationY(yPosition);\n    }\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/videoview/CustomVideoView.java",
    "content": "package com.didichuxing.doraemonkit.widget.videoview;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.widget.VideoView;\n\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\n/**\n * Created by wanglikun on 2019/4/16\n */\npublic class CustomVideoView extends VideoView implements View.OnTouchListener{\n\n    private float lastX;\n    private float lastY;\n    private int thresold = 30;\n    private Context mContext;\n    private StateListener mStateListener;\n\n    public interface StateListener{\n        void changeVolumn(float detlaY);\n        void changeBrightness(float detlaX);\n        void hideHint();\n    }\n\n    public void setStateListener(StateListener stateListener) {\n        this.mStateListener = stateListener;\n    }\n\n    public CustomVideoView(Context context) {\n        this(context,null);\n    }\n\n    public CustomVideoView(Context context, AttributeSet attrs) {\n        this(context, attrs,0);\n    }\n\n    public CustomVideoView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        this.mContext = context;\n        setOnTouchListener(this);\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n        int width = getDefaultSize(1920,widthMeasureSpec);\n        int height = getDefaultSize(1080,heightMeasureSpec);\n        setMeasuredDimension(width,height);\n    }\n\n    @Override\n    public boolean onTouch(View v, MotionEvent event) {\n        switch (event.getAction()) {\n            case MotionEvent.ACTION_DOWN:\n                lastX = event.getX();\n                lastY = event.getY();\n                break;\n            case MotionEvent.ACTION_MOVE:\n                float detlaX = event.getX() - lastX;\n                float detlaY = event.getY() - lastY;\n                if (Math.abs(detlaX) < thresold && Math.abs(detlaY) > thresold) {\n                    if (event.getX() < UIUtils.getWidthPixels() / 2) {\n                        mStateListener.changeBrightness(detlaY);\n                    } else {\n                        mStateListener.changeVolumn(detlaY);\n                    }\n                }\n                lastX = event.getX();\n                lastY = event.getY();\n                break;\n            case MotionEvent.ACTION_UP:\n                mStateListener.hideHint();\n                break;\n        }\n        return true;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/videoview/MyVideoView.java",
    "content": "package com.didichuxing.doraemonkit.widget.videoview;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.pm.ActivityInfo;\nimport android.content.res.Configuration;\nimport android.graphics.drawable.Drawable;\nimport android.media.AudioManager;\nimport android.net.Uri;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.WindowManager;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.RelativeLayout;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.util.UIUtils;\n\n/**\n * Created by wanglikun on 2019/4/16\n */\npublic class MyVideoView extends RelativeLayout {\n\n    private CustomVideoView videoView;\n    private SeekBar seekbarProgress;\n    private ImageView btnController;\n    private TextView tvCurrentProgress;\n    private TextView tvTotalProgress;\n    private ImageView ivVolume;\n    private SeekBar seekbarVolume;\n    private ImageView btnScreen;\n    private FrameLayout flVolume;\n    private FrameLayout flLight;\n    private LinearLayout llyController;\n    private RelativeLayout rlContainer;\n    private AudioManager mAudioManager;\n    private int screenWidth;\n    private int screenHeight;\n    private Context mContext;\n    private View videoLayout;\n    private Activity mActivity;\n    private int videoPos;\n    private int state = 0;\n    private String mVideoPath;\n    private boolean isVerticalScreen = true;\n    private static final int UPDATE_PROGRESS = 1;\n\n    public MyVideoView(Context context) {\n        super(context, null);\n    }\n\n    public MyVideoView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public MyVideoView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        this.mContext = context;\n        init();\n        initView();\n        initData();\n        initListener();\n    }\n\n    public void register(Activity activity) {\n        this.mActivity = activity;\n    }\n\n    public void setVideoPath(String path) {\n\n        this.mVideoPath = path;\n        if (path.startsWith(\"http\") || path.startsWith(\"https\")) {\n            videoView.setVideoURI(Uri.parse(path));\n        } else {\n            videoView.setVideoPath(mVideoPath);\n        }\n    }\n\n    private void init() {\n        screenWidth = UIUtils.getWidthPixels();\n        screenHeight = UIUtils.getRealHeightPixels();\n        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n    }\n\n    private void initView() {\n\n        videoLayout = LayoutInflater.from(mContext).inflate(R.layout.dk_video_layout, this, true);\n        flVolume = videoLayout.findViewById(R.id.fl_volume);\n        flLight = videoLayout.findViewById(R.id.fl_light);\n        videoView = videoLayout.findViewById(R.id.videoView);\n        seekbarProgress = videoLayout.findViewById(R.id.seekbar_progress);\n        seekbarVolume = videoLayout.findViewById(R.id.seekbar_volume);\n        btnController = videoLayout.findViewById(R.id.btn_controller);\n        btnScreen = videoLayout.findViewById(R.id.btn_screen);\n        tvCurrentProgress = videoLayout.findViewById(R.id.tv_currentProgress);\n        tvTotalProgress = videoLayout.findViewById(R.id.tv_totalProgress);\n        ivVolume = videoLayout.findViewById(R.id.iv_volume);\n        llyController = videoLayout.findViewById(R.id.lly_controller);\n        rlContainer = videoLayout.findViewById(R.id.rl_container);\n    }\n\n    private void initData() {\n        int currentVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);\n        seekbarVolume.setProgress(currentVolume);\n    }\n\n    private void initListener() {\n\n        btnScreen.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (isVerticalScreen) {\n                    mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);\n                } else {\n                    mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\n                }\n            }\n        });\n\n        btnController.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (videoView.isPlaying()) {\n                    btnController.setImageResource(R.drawable.dk_btn_play_style);\n                    videoView.pause();\n                    mHandler.removeMessages(UPDATE_PROGRESS);\n                } else {\n                    btnController.setImageResource(R.drawable.dk_btn_pause_style);\n                    videoView.start();\n                    mHandler.sendEmptyMessage(UPDATE_PROGRESS);\n                    if (state == 0) state = 1;\n                }\n            }\n        });\n\n        videoView.setStateListener(new CustomVideoView.StateListener() {\n\n            @Override\n            public void changeVolumn(float detlaY) {\n\n                if (flVolume.getVisibility() == View.GONE) {\n                    flVolume.setVisibility(View.VISIBLE);\n                }\n                int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);\n                int currentVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);\n                int index = (int) (detlaY / screenHeight * maxVolume * 3);\n                int volume = Math.max(0, currentVolume - index);\n                mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);\n                seekbarVolume.setProgress(volume);\n            }\n\n            @Override\n            public void changeBrightness(float detlaY) {\n\n                if (flLight.getVisibility() == View.GONE) {\n                    flLight.setVisibility(View.VISIBLE);\n                }\n                WindowManager.LayoutParams wml = mActivity.getWindow().getAttributes();\n                float screenBrightness = wml.screenBrightness;\n                float index = -detlaY / screenHeight / 5;\n                screenBrightness += index;\n                if (screenBrightness > 1.0f) {\n                    screenBrightness = 1.0f;\n                } else if (screenBrightness < 0.01f) {\n                    screenBrightness = 0.01f;\n                }\n                wml.screenBrightness = screenBrightness;\n                mActivity.getWindow().setAttributes(wml);\n            }\n\n            @Override\n            public void hideHint() {\n                if (flLight.getVisibility() == View.VISIBLE) {\n                    flLight.setVisibility(GONE);\n                }\n\n                if (flVolume.getVisibility() == View.VISIBLE) {\n                    flVolume.setVisibility(GONE);\n                }\n            }\n        });\n\n        seekbarVolume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {\n\n            @Override\n            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {\n                mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress, 0);\n            }\n\n            @Override\n            public void onStartTrackingTouch(SeekBar seekBar) {\n            }\n\n            @Override\n            public void onStopTrackingTouch(SeekBar seekBar) {\n            }\n        });\n\n        seekbarProgress.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {\n\n            @Override\n            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {\n                updateTextViewFormat(tvCurrentProgress, progress);\n            }\n\n            @Override\n            public void onStartTrackingTouch(SeekBar seekBar) {\n                // 暂停刷新\n                mHandler.removeMessages(UPDATE_PROGRESS);\n            }\n\n            @Override\n            public void onStopTrackingTouch(SeekBar seekBar) {\n                if (state != 0) {\n                    mHandler.sendEmptyMessage(UPDATE_PROGRESS);\n                }\n                videoView.seekTo(seekBar.getProgress());\n            }\n        });\n    }\n\n    @Override\n    public void onConfigurationChanged(Configuration newConfig) {\n        super.onConfigurationChanged(newConfig);\n        if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {\n            //竖屏\n            isVerticalScreen = true;\n            ivVolume.setVisibility(View.GONE);\n            seekbarVolume.setVisibility(View.GONE);\n            setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT, UIUtils.dp2px(290));\n            mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n            mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);\n        } else {\n            isVerticalScreen = false;\n            ivVolume.setVisibility(View.VISIBLE);\n            seekbarVolume.setVisibility(View.VISIBLE);\n            setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);\n            // 移除半屏状态\n            mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);\n            mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n        }\n    }\n\n    public void setVideoViewScale(int width, int height) {\n\n        ViewGroup.LayoutParams videoViewLayoutParams = videoView.getLayoutParams();\n        videoViewLayoutParams.width = width;\n        videoViewLayoutParams.height = height;\n        videoView.setLayoutParams(videoViewLayoutParams);\n\n        RelativeLayout.LayoutParams rlContainerLayoutParams = (RelativeLayout.LayoutParams) rlContainer.getLayoutParams();\n        rlContainerLayoutParams.width = width;\n        rlContainerLayoutParams.height = height;\n        rlContainer.setLayoutParams(rlContainerLayoutParams);\n    }\n\n    private void updateTextViewFormat(TextView tv, int m) {\n\n        String result;\n        // 毫秒转成秒\n        int second = m / 1000;\n        int hour = second / 3600;\n        int minute = second % 3600 / 60;\n        int ss = second % 60;\n\n        if (hour != 0) {\n            result = String.format(\"%02d:%02d:%02d\", hour, minute, ss);\n        } else {\n            result = String.format(\"%02d:%02d\", minute, ss);\n        }\n        tv.setText(result);\n    }\n\n    private Handler mHandler = new Handler() {\n\n        @Override\n        public void handleMessage(Message msg) {\n\n            if (msg.what == UPDATE_PROGRESS) {\n\n                // 获取当前时间\n                int currentTime = videoView.getCurrentPosition();\n                // 获取总时间\n                int totalTime = videoView.getDuration() - 100;\n                if (currentTime >= totalTime) {\n                    videoView.pause();\n                    videoView.seekTo(0);\n                    seekbarProgress.setProgress(0);\n                    btnController.setImageResource(R.drawable.dk_btn_play_style);\n                    updateTextViewFormat(tvCurrentProgress, 0);\n                    mHandler.removeMessages(UPDATE_PROGRESS);\n                } else {\n                    seekbarProgress.setMax(totalTime);\n                    seekbarProgress.setProgress(currentTime);\n                    updateTextViewFormat(tvCurrentProgress, currentTime);\n                    updateTextViewFormat(tvTotalProgress, totalTime);\n                    mHandler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 100);\n                }\n            }\n        }\n    };\n\n    public void onPause() {\n        videoPos = videoView.getCurrentPosition();\n        videoView.stopPlayback();\n        mHandler.removeMessages(UPDATE_PROGRESS);\n    }\n\n    public void onResume() {\n        videoView.seekTo(videoPos);\n        videoView.resume();\n    }\n\n    public void setProgressBg(Drawable drawable) {\n        seekbarProgress.setProgressDrawable(drawable);\n    }\n\n    public void setVolumeBg(Drawable drawable) {\n        seekbarVolume.setProgressDrawable(drawable);\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/webview/MyWebView.java",
    "content": "package com.didichuxing.doraemonkit.widget.webview;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.res.Configuration;\nimport android.graphics.drawable.ClipDrawable;\nimport android.graphics.drawable.ColorDrawable;\nimport android.os.Build;\nimport android.text.TextUtils;\nimport android.util.AttributeSet;\nimport android.view.Gravity;\nimport android.webkit.WebChromeClient;\nimport android.webkit.WebSettings;\nimport android.webkit.WebView;\nimport android.widget.ProgressBar;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.webview.OnWebViewTitleChangeCallBack;\n\n/**\n * Created by wanglikun on 2019/4/8\n */\npublic class MyWebView extends WebView {\n    private ProgressBar mProgressBar;\n    private MyWebViewClient mMyWebViewClient;\n    OnWebViewTitleChangeCallBack callBack;\n\n\n    public void setCallBack(OnWebViewTitleChangeCallBack callBack) {\n        this.callBack = callBack;\n    }\n\n    public MyWebView(Context context) {\n        super(getFixedContext(context));\n        init(context);\n    }\n\n    public MyWebView(Context context, AttributeSet attrs) {\n        super(getFixedContext(context), attrs);\n        init(context);\n    }\n\n    public MyWebView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(getFixedContext(context), attrs, defStyleAttr);\n        init(context);\n    }\n\n    /**\n     * 参考: https://www.jianshu.com/p/d86de6a1e791\n     *\n     * @param context\n     * @return\n     */\n    @SuppressLint(\"ObsoleteSdkInt\")\n    private static Context getFixedContext(Context context) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            return context.createConfigurationContext(new Configuration());\n        } else {\n            return context;\n        }\n    }\n\n    private Activity mContainerActivity;\n\n    private void init(Context context) {\n        if (!(context instanceof Activity)) {\n            throw new RuntimeException(\"only support Activity context\");\n        } else {\n            this.mContainerActivity = (Activity) context;\n            WebSettings webSettings = this.getSettings();\n            webSettings.setPluginState(WebSettings.PluginState.ON);\n            webSettings.setJavaScriptEnabled(true);\n            webSettings.setAllowFileAccess(false);\n            webSettings.setLoadsImagesAutomatically(true);\n            webSettings.setUseWideViewPort(true);\n            webSettings.setBuiltInZoomControls(false);\n            webSettings.setDefaultTextEncodingName(\"UTF-8\");\n            webSettings.setDomStorageEnabled(true);\n            webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);\n            webSettings.setJavaScriptCanOpenWindowsAutomatically(false);\n\n            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {\n                webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH);\n            }\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n                setWebContentsDebuggingEnabled(true);\n            }\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                webSettings.setMixedContentMode(0);\n            }\n\n            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {\n                this.removeJavascriptInterface(\"searchBoxJavaBridge_\");\n                this.removeJavascriptInterface(\"accessibilityTraversal\");\n                this.removeJavascriptInterface(\"accessibility\");\n            }\n            mMyWebViewClient = new MyWebViewClient();\n            this.setWebViewClient(mMyWebViewClient);\n            this.setWebChromeClient(new WebChromeClient() {\n                @Override\n                public void onProgressChanged(WebView view, int newProgress) {\n                    super.onProgressChanged(view, newProgress);\n                    if (newProgress < 100) {\n                        showLoadProgress(newProgress);\n                    } else {\n                        hideLoadProgress();\n                    }\n                }\n\n                @Override\n                public void onReceivedTitle(WebView view, String title) {\n                    super.onReceivedTitle(view, title);\n                    if (callBack != null) {\n                        callBack.onChange(title);\n                    }\n                }\n            });\n\n            addProgressView();\n        }\n    }\n\n    public void addInvokeListener(MyWebViewClient.InvokeListener listener) {\n        mMyWebViewClient.addInvokeListener(listener);\n    }\n\n    public void removeInvokeListener(MyWebViewClient.InvokeListener listener) {\n        mMyWebViewClient.removeInvokeListener(listener);\n    }\n\n    private void addProgressView() {\n        this.mProgressBar = new ProgressBar(this.getContext(), null, android.R.attr.progressBarStyleHorizontal);\n        this.mProgressBar.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 10, 0, 0));\n        Integer progressBarColor = getResources().getColor(R.color.dk_color_55A8FD);\n\n        ClipDrawable d = new ClipDrawable(new ColorDrawable(progressBarColor), Gravity.LEFT, ClipDrawable.HORIZONTAL);\n        this.mProgressBar.setProgressDrawable(d);\n        this.mProgressBar.setVisibility(GONE);\n        this.addView(this.mProgressBar);\n    }\n\n    public void showLoadProgress(int progress) {\n        if (null != this.mProgressBar) {\n            if (this.mProgressBar.getVisibility() == GONE) {\n                this.mProgressBar.setVisibility(VISIBLE);\n            }\n\n            this.mProgressBar.setProgress(progress);\n        }\n    }\n\n    public void hideLoadProgress() {\n        if (null != this.mProgressBar) {\n            this.mProgressBar.setVisibility(GONE);\n        }\n\n    }\n\n    @Override\n    public void loadUrl(String url) {\n        if (!TextUtils.isEmpty(url)) {\n            if (!url.startsWith(\"http://\") && !url.startsWith(\"https://\") && !url.startsWith(\"javascript:\")) {\n                url = \"http://\" + url;\n            }\n        }\n        super.loadUrl(url);\n    }\n\n    public Activity getActivity() {\n        return this.mContainerActivity;\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/widget/webview/MyWebViewClient.java",
    "content": "package com.didichuxing.doraemonkit.widget.webview;\n\nimport android.webkit.WebView;\nimport android.webkit.WebViewClient;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by wanglikun on 2019/4/15\n */\npublic class MyWebViewClient extends WebViewClient {\n    private List<InvokeListener> mListeners = new ArrayList<>();\n\n    @Override\n    public boolean shouldOverrideUrlLoading(WebView view, String url) {\n        if (url.startsWith(\"doraemon://invokeNative\")) {\n            handleInvokeFromJs(url);\n            return true;\n        }\n        return super.shouldOverrideUrlLoading(view, url);\n    }\n\n    private void handleInvokeFromJs(String url) {\n        for (InvokeListener listener : mListeners) {\n            listener.onNativeInvoke(url);\n        }\n    }\n\n    public void addInvokeListener(InvokeListener listener) {\n        mListeners.add(listener);\n    }\n\n    public void removeInvokeListener(InvokeListener listener) {\n        mListeners.remove(listener);\n    }\n\n    public interface InvokeListener {\n        void onNativeInvoke(String url);\n    }\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/activity/CaptureActivity.java",
    "content": "package com.didichuxing.doraemonkit.zxing.activity;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.media.MediaPlayer;\nimport android.media.MediaPlayer.OnCompletionListener;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Vibrator;\nimport android.text.TextUtils;\nimport android.view.SurfaceHolder;\nimport android.view.SurfaceHolder.Callback;\nimport android.view.SurfaceView;\nimport android.view.WindowManager;\nimport android.widget.Toast;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.zxing.camera.CameraManager;\nimport com.didichuxing.doraemonkit.zxing.decoding.CaptureActivityHandler;\nimport com.didichuxing.doraemonkit.zxing.decoding.InactivityTimer;\nimport com.didichuxing.doraemonkit.zxing.view.ViewfinderView;\nimport com.google.zxing.BarcodeFormat;\nimport com.google.zxing.Result;\n\nimport java.io.IOException;\nimport java.util.Vector;\n\npublic class CaptureActivity extends Activity implements Callback {\n\n\n    private CaptureActivityHandler handler;\n    private ViewfinderView viewfinderView;\n    private boolean hasSurface;\n    private Vector<BarcodeFormat> decodeFormats;\n    private String characterSet;\n    private InactivityTimer inactivityTimer;\n    private boolean vibrate;\n    public static final String INTENT_EXTRA_KEY_QR_SCAN = \"result\";\n\n    /**\n     * Called when the activity is first created.\n     */\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,\n                WindowManager.LayoutParams.FLAG_FULLSCREEN);\n        setContentView(R.layout.dk_zxing_activity_scanner);\n        CameraManager.init(getApplication());\n        viewfinderView = findViewById(R.id.viewfinder_content);\n        hasSurface = false;\n        inactivityTimer = new InactivityTimer(this);\n    }\n\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        SurfaceView surfaceView = findViewById(R.id.scanner_view);\n        SurfaceHolder surfaceHolder = surfaceView.getHolder();\n        if (hasSurface) {\n            initCamera(surfaceHolder);\n        } else {\n            surfaceHolder.addCallback(this);\n            surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);\n        }\n        decodeFormats = null;\n        characterSet = null;\n\n        vibrate = true;\n    }\n\n    @Override\n    protected void onPause() {\n        super.onPause();\n        if (handler != null) {\n            handler.quitSynchronously();\n            handler = null;\n        }\n        CameraManager.get().closeDriver();\n    }\n\n    @Override\n    protected void onDestroy() {\n        inactivityTimer.shutdown();\n        super.onDestroy();\n    }\n\n    /**\n     * Handler scan result\n     *\n     * @param result\n     * @param barcode\n     */\n    public void handleDecode(Result result, Bitmap barcode) {\n        inactivityTimer.onActivity();\n        playBeepSoundAndVibrate();\n        String resultString = result.getText();\n        if (TextUtils.isEmpty(resultString)) {\n            Toast.makeText(CaptureActivity.this, \"Scan failed!\", Toast.LENGTH_SHORT).show();\n        } else {\n            Intent resultIntent = new Intent();\n            Bundle bundle = new Bundle();\n            bundle.putString(INTENT_EXTRA_KEY_QR_SCAN, resultString);\n            resultIntent.putExtras(bundle);\n            this.setResult(RESULT_OK, resultIntent);\n        }\n        CaptureActivity.this.finish();\n    }\n\n    private void initCamera(SurfaceHolder surfaceHolder) {\n        try {\n            CameraManager.get().openDriver(surfaceHolder);\n        } catch (IOException ioe) {\n            return;\n        } catch (RuntimeException e) {\n            return;\n        }\n        if (handler == null) {\n            handler = new CaptureActivityHandler(this, decodeFormats,\n                    characterSet);\n        }\n    }\n\n    @Override\n    public void surfaceChanged(SurfaceHolder holder, int format, int width,\n                               int height) {\n\n    }\n\n    @Override\n    public void surfaceCreated(SurfaceHolder holder) {\n        if (!hasSurface) {\n            hasSurface = true;\n            initCamera(holder);\n        }\n\n    }\n\n    @Override\n    public void surfaceDestroyed(SurfaceHolder holder) {\n        hasSurface = false;\n\n    }\n\n    public ViewfinderView getViewfinderView() {\n        return viewfinderView;\n    }\n\n    public Handler getHandler() {\n        return handler;\n    }\n\n    public void drawViewfinder() {\n        viewfinderView.drawViewfinder();\n\n    }\n\n    private static final long VIBRATE_DURATION = 200L;\n\n    private void playBeepSoundAndVibrate() {\n        if (vibrate) {\n            Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);\n            vibrator.vibrate(VIBRATE_DURATION);\n        }\n    }\n\n    /**\n     * When the beep has finished playing, rewind to queue up another one.\n     */\n    private final OnCompletionListener beepListener = new OnCompletionListener() {\n        public void onCompletion(MediaPlayer mediaPlayer) {\n            mediaPlayer.seekTo(0);\n        }\n    };\n\n}"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/camera/AutoFocusCallback.java",
    "content": "/*\n * Copyright (C) 2010 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.camera;\n\nimport android.hardware.Camera;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.util.Log;\n\nfinal class AutoFocusCallback implements Camera.AutoFocusCallback {\n\n    private static final String TAG = AutoFocusCallback.class.getSimpleName();\n\n    private static final long AUTOFOCUS_INTERVAL_MS = 1500L;\n\n    private Handler autoFocusHandler;\n    private int autoFocusMessage;\n\n    void setHandler(Handler autoFocusHandler, int autoFocusMessage) {\n        this.autoFocusHandler = autoFocusHandler;\n        this.autoFocusMessage = autoFocusMessage;\n    }\n\n    public void onAutoFocus(boolean success, Camera camera) {\n        if (autoFocusHandler != null) {\n            Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success);\n            autoFocusHandler.sendMessageDelayed(message, AUTOFOCUS_INTERVAL_MS);\n            autoFocusHandler = null;\n        } else {\n            Log.d(TAG, \"Got auto-focus callback, but no handler for it\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/camera/CameraConfigurationManager.java",
    "content": "/*\n * Copyright (C) 2010 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.camera;\n\nimport android.content.Context;\nimport android.graphics.Point;\nimport android.hardware.Camera;\nimport android.os.Build;\nimport android.util.Log;\nimport android.view.Display;\nimport android.view.WindowManager;\n\nimport java.util.regex.Pattern;\n\nfinal class CameraConfigurationManager {\n\n    private static final String TAG = CameraConfigurationManager.class.getCanonicalName();\n\n    private static final int TEN_DESIRED_ZOOM = 27;\n    private static final int DESIRED_SHARPNESS = 30;\n\n    private static final Pattern COMMA_PATTERN = Pattern.compile(\",\");\n\n    private final Context context;\n    private Point screenResolution;\n    private Point cameraResolution;\n    private int previewFormat;\n    private String previewFormatString;\n\n    CameraConfigurationManager(Context context) {\n        this.context = context;\n    }\n\n    /**\n     * Reads, one time, values from the camera that are needed by the app.\n     */\n    void initFromCameraParameters(Camera camera) {\n        Camera.Parameters parameters = camera.getParameters();\n        previewFormat = parameters.getPreviewFormat();\n        previewFormatString = parameters.get(\"preview-format\");\n        Log.d(TAG, \"Default preview format: \" + previewFormat + '/' + previewFormatString);\n        WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);\n        Display display = manager.getDefaultDisplay();\n        screenResolution = new Point(display.getWidth(), display.getHeight());\n        Log.d(TAG, \"Screen resolution: \" + screenResolution);\n\n        //图片拉伸\n        Point screenResolutionForCamera = new Point();\n        screenResolutionForCamera.x = screenResolution.x;\n        screenResolutionForCamera.y = screenResolution.y;\n        // preview size is always something like 480*320, other 320*480\n        if (screenResolution.x < screenResolution.y) {\n            screenResolutionForCamera.x = screenResolution.y;\n            screenResolutionForCamera.y = screenResolution.x;\n        }\n\n        cameraResolution = getCameraResolution(parameters, screenResolutionForCamera);\n        Log.d(TAG, \"Camera resolution: \" + screenResolution);\n\n    }\n\n    /**\n     * Sets the camera up to take preview images which are used for both preview and decoding.\n     * We detect the preview format here so that buildLuminanceSource() can build an appropriate\n     * LuminanceSource subclass. In the future we may want to force YUV420SP as it's the smallest,\n     * and the planar Y can be used for barcode scanning without a copy in some cases.\n     */\n    void setDesiredCameraParameters(Camera camera) {\n        Camera.Parameters parameters = camera.getParameters();\n        Log.d(TAG, \"Setting preview size: \" + cameraResolution);\n        parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);\n        setFlash(parameters);\n        setZoom(parameters);\n        //setSharpness(parameters);\n        //modify here\n        camera.setDisplayOrientation(90);\n        camera.setParameters(parameters);\n    }\n\n    Point getCameraResolution() {\n        return cameraResolution;\n    }\n\n    Point getScreenResolution() {\n        return screenResolution;\n    }\n\n    int getPreviewFormat() {\n        return previewFormat;\n    }\n\n    String getPreviewFormatString() {\n        return previewFormatString;\n    }\n\n    private static Point getCameraResolution(Camera.Parameters parameters, Point screenResolution) {\n\n        String previewSizeValueString = parameters.get(\"preview-size-values\");\n        // saw this on Xperia\n        if (previewSizeValueString == null) {\n            previewSizeValueString = parameters.get(\"preview-size-value\");\n        }\n\n        Point cameraResolution = null;\n\n        if (previewSizeValueString != null) {\n            Log.d(TAG, \"preview-size-values parameter: \" + previewSizeValueString);\n            cameraResolution = findBestPreviewSizeValue(previewSizeValueString, screenResolution);\n        }\n\n        if (cameraResolution == null) {\n            // Ensure that the camera resolution is a multiple of 8, as the screen may not be.\n            cameraResolution = new Point(\n                    (screenResolution.x >> 3) << 3,\n                    (screenResolution.y >> 3) << 3);\n        }\n\n        return cameraResolution;\n    }\n\n    private static Point findBestPreviewSizeValue(CharSequence previewSizeValueString, Point screenResolution) {\n        int bestX = 0;\n        int bestY = 0;\n        int diff = Integer.MAX_VALUE;\n        for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) {\n\n            previewSize = previewSize.trim();\n            int dimPosition = previewSize.indexOf('x');\n            if (dimPosition < 0) {\n                Log.w(TAG, \"Bad preview-size: \" + previewSize);\n                continue;\n            }\n\n            int newX;\n            int newY;\n            try {\n                newX = Integer.parseInt(previewSize.substring(0, dimPosition));\n                newY = Integer.parseInt(previewSize.substring(dimPosition + 1));\n            } catch (NumberFormatException nfe) {\n                Log.w(TAG, \"Bad preview-size: \" + previewSize);\n                continue;\n            }\n\n            int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y);\n            if (newDiff == 0) {\n                bestX = newX;\n                bestY = newY;\n                break;\n            } else if (newDiff < diff) {\n                bestX = newX;\n                bestY = newY;\n                diff = newDiff;\n            }\n\n        }\n\n        if (bestX > 0 && bestY > 0) {\n            return new Point(bestX, bestY);\n        }\n        return null;\n    }\n\n    private static int findBestMotZoomValue(CharSequence stringValues, int tenDesiredZoom) {\n        int tenBestValue = 0;\n        for (String stringValue : COMMA_PATTERN.split(stringValues)) {\n            stringValue = stringValue.trim();\n            double value;\n            try {\n                value = Double.parseDouble(stringValue);\n            } catch (NumberFormatException nfe) {\n                return tenDesiredZoom;\n            }\n            int tenValue = (int) (10.0 * value);\n            if (Math.abs(tenDesiredZoom - value) < Math.abs(tenDesiredZoom - tenBestValue)) {\n                tenBestValue = tenValue;\n            }\n        }\n        return tenBestValue;\n    }\n\n    private void setFlash(Camera.Parameters parameters) {\n        // FIXME: This is a hack to turn the flash off on the Samsung Galaxy.\n        // And this is a hack-hack to work around a different value on the Behold II\n        // Restrict Behold II check to Cupcake, per Samsung's advice\n        //if (Build.MODEL.contains(\"Behold II\") &&\n        //    CameraManager.SDK_INT == Build.VERSION_CODES.CUPCAKE) {\n        if (Build.MODEL.contains(\"Behold II\") && CameraManager.SDK_INT == 3) { // 3 = Cupcake\n            parameters.set(\"flash-value\", 1);\n        } else {\n            parameters.set(\"flash-value\", 2);\n        }\n        // This is the standard setting to turn the flash off that all devices should honor.\n        parameters.set(\"flash-mode\", \"off\");\n    }\n\n    private void setZoom(Camera.Parameters parameters) {\n\n        String zoomSupportedString = parameters.get(\"zoom-supported\");\n        if (zoomSupportedString != null && !Boolean.parseBoolean(zoomSupportedString)) {\n            return;\n        }\n\n        int tenDesiredZoom = TEN_DESIRED_ZOOM;\n\n        String maxZoomString = parameters.get(\"max-zoom\");\n        if (maxZoomString != null) {\n            try {\n                int tenMaxZoom = (int) (10.0 * Double.parseDouble(maxZoomString));\n                if (tenDesiredZoom > tenMaxZoom) {\n                    tenDesiredZoom = tenMaxZoom;\n                }\n            } catch (NumberFormatException nfe) {\n                Log.w(TAG, \"Bad max-zoom: \" + maxZoomString);\n            }\n        }\n\n        String takingPictureZoomMaxString = parameters.get(\"taking-picture-zoom-max\");\n        if (takingPictureZoomMaxString != null) {\n            try {\n                int tenMaxZoom = Integer.parseInt(takingPictureZoomMaxString);\n                if (tenDesiredZoom > tenMaxZoom) {\n                    tenDesiredZoom = tenMaxZoom;\n                }\n            } catch (NumberFormatException nfe) {\n                Log.w(TAG, \"Bad taking-picture-zoom-max: \" + takingPictureZoomMaxString);\n            }\n        }\n\n        String motZoomValuesString = parameters.get(\"mot-zoom-values\");\n        if (motZoomValuesString != null) {\n            tenDesiredZoom = findBestMotZoomValue(motZoomValuesString, tenDesiredZoom);\n        }\n\n        String motZoomStepString = parameters.get(\"mot-zoom-step\");\n        if (motZoomStepString != null) {\n            try {\n                double motZoomStep = Double.parseDouble(motZoomStepString.trim());\n                int tenZoomStep = (int) (10.0 * motZoomStep);\n                if (tenZoomStep > 1) {\n                    tenDesiredZoom -= tenDesiredZoom % tenZoomStep;\n                }\n            } catch (NumberFormatException nfe) {\n                // continue\n            }\n        }\n\n        // Set zoom. This helps encourage the user to pull back.\n        // Some devices like the Behold have a zoom parameter\n        if (maxZoomString != null || motZoomValuesString != null) {\n            parameters.set(\"zoom\", String.valueOf(tenDesiredZoom / 10.0));\n        }\n\n        // Most devices, like the Hero, appear to expose this zoom parameter.\n        // It takes on values like \"27\" which appears to mean 2.7x zoom\n        if (takingPictureZoomMaxString != null) {\n            parameters.set(\"taking-picture-zoom\", tenDesiredZoom);\n        }\n    }\n\n    public static int getDesiredSharpness() {\n        return DESIRED_SHARPNESS;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/camera/CameraManager.java",
    "content": "/*\n * Copyright (C) 2008 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.camera;\n\nimport android.content.Context;\nimport android.graphics.PixelFormat;\nimport android.graphics.Point;\nimport android.graphics.Rect;\nimport android.hardware.Camera;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.view.SurfaceHolder;\n\nimport java.io.IOException;\n\n/**\n * This object wraps the Camera service object and expects to be the only one talking to it. The\n * implementation encapsulates the steps needed to take preview-sized images, which are used for\n * both preview and decoding.\n */\npublic final class CameraManager {\n\n    private static final String TAG = CameraManager.class.getCanonicalName();\n\n    private static final int MIN_FRAME_WIDTH = 240;\n    private static final int MIN_FRAME_HEIGHT = 240;\n    private static final int MAX_FRAME_WIDTH = 480;\n    private static final int MAX_FRAME_HEIGHT = 360;\n\n    private static CameraManager cameraManager;\n\n    static final int SDK_INT; // Later we can use Build.VERSION.SDK_INT\n\n    static {\n        int sdkInt;\n        try {\n            sdkInt = Integer.parseInt(Build.VERSION.SDK);\n        } catch (NumberFormatException nfe) {\n            // Just to be safe\n            sdkInt = 10000;\n        }\n        SDK_INT = sdkInt;\n    }\n\n    private final Context context;\n    private final CameraConfigurationManager configManager;\n    private Camera camera;\n    private Rect framingRect;\n    private Rect framingRectInPreview;\n    private boolean initialized;\n    private boolean previewing;\n    private final boolean useOneShotPreviewCallback;\n    /**\n     * Preview frames are delivered here, which we pass on to the registered handler. Make sure to\n     * clear the handler so it will only receive one message.\n     */\n    private final PreviewCallback previewCallback;\n    /**\n     * Autofocus callbacks arrive here, and are dispatched to the Handler which requested them.\n     */\n    private final AutoFocusCallback autoFocusCallback;\n\n    /**\n     * Initializes this static object with the Context of the calling Activity.\n     *\n     * @param context The Activity which wants to use the camera.\n     */\n    public static void init(Context context) {\n        if (cameraManager == null) {\n            cameraManager = new CameraManager(context);\n        }\n    }\n\n    /**\n     * Gets the CameraManager singleton instance.\n     *\n     * @return A reference to the CameraManager singleton.\n     */\n    public static CameraManager get() {\n        return cameraManager;\n    }\n\n    private CameraManager(Context context) {\n\n        this.context = context;\n        this.configManager = new CameraConfigurationManager(context);\n\n        // Camera.setOneShotPreviewCallback() has a race condition in Cupcake, so we use the older\n        // Camera.setPreviewCallback() on 1.5 and earlier. For Donut and later, we need to use\n        // the more efficient one shot callback, as the older one can swamp the system and cause it\n        // to run out of memory. We can't use SDK_INT because it was introduced in the Donut SDK.\n        //useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > Build.VERSION_CODES.CUPCAKE;\n        useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > 3; // 3 = Cupcake\n\n        previewCallback = new PreviewCallback(configManager, useOneShotPreviewCallback);\n        autoFocusCallback = new AutoFocusCallback();\n    }\n\n    /**\n     * Opens the camera driver and initializes the hardware parameters.\n     *\n     * @param holder The surface object which the camera will draw preview frames into.\n     * @throws IOException Indicates the camera driver failed to open.\n     */\n    public void openDriver(SurfaceHolder holder) throws IOException {\n        if (camera == null) {\n            camera = Camera.open();\n            if (camera == null) {\n                throw new IOException();\n            }\n            camera.setPreviewDisplay(holder);\n\n            if (!initialized) {\n                initialized = true;\n                configManager.initFromCameraParameters(camera);\n            }\n            configManager.setDesiredCameraParameters(camera);\n\n            //FIXME\n            //     SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n            //�Ƿ�ʹ��ǰ��\n//      if (prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false)) {\n//        FlashlightManager.enableFlashlight();\n//      }\n            FlashlightManager.enableFlashlight();\n        }\n    }\n\n    /**\n     * Closes the camera driver if still in use.\n     */\n    public void closeDriver() {\n        if (camera != null) {\n            FlashlightManager.disableFlashlight();\n            camera.release();\n            camera = null;\n        }\n    }\n\n    /**\n     * Asks the camera hardware to begin drawing preview frames to the screen.\n     */\n    public void startPreview() {\n        if (camera != null && !previewing) {\n            camera.startPreview();\n            previewing = true;\n        }\n    }\n\n    /**\n     * Tells the camera to stop drawing preview frames.\n     */\n    public void stopPreview() {\n        if (camera != null && previewing) {\n            if (!useOneShotPreviewCallback) {\n                camera.setPreviewCallback(null);\n            }\n            camera.stopPreview();\n            previewCallback.setHandler(null, 0);\n            autoFocusCallback.setHandler(null, 0);\n            previewing = false;\n        }\n    }\n\n    /**\n     * A single preview frame will be returned to the handler supplied. The data will arrive as byte[]\n     * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,\n     * respectively.\n     *\n     * @param handler The handler to send the message to.\n     * @param message The what field of the message to be sent.\n     */\n    public void requestPreviewFrame(Handler handler, int message) {\n        if (camera != null && previewing) {\n            previewCallback.setHandler(handler, message);\n            if (useOneShotPreviewCallback) {\n                camera.setOneShotPreviewCallback(previewCallback);\n            } else {\n                camera.setPreviewCallback(previewCallback);\n            }\n        }\n    }\n\n    /**\n     * Asks the camera hardware to perform an autofocus.\n     *\n     * @param handler The Handler to notify when the autofocus completes.\n     * @param message The message to deliver.\n     */\n    public void requestAutoFocus(Handler handler, int message) {\n        if (camera != null && previewing) {\n            autoFocusCallback.setHandler(handler, message);\n            //Log.d(TAG, \"Requesting auto-focus callback\");\n            camera.autoFocus(autoFocusCallback);\n        }\n    }\n\n    /**\n     * Calculates the framing rect which the UI should draw to show the user where to place the\n     * barcode. This target helps with alignment as well as forces the user to hold the device\n     * far enough away to ensure the image will be in focus.\n     *\n     * @return The rectangle to draw on screen in window coordinates.\n     */\n    public Rect getFramingRect() {\n        Point screenResolution = configManager.getScreenResolution();\n        if (screenResolution == null)\n            return null;\n        if (framingRect == null) {\n            if (camera == null) {\n                return null;\n            }\n\n            //修改之后\n            int width = screenResolution.x * 7 / 10;\n            int height = screenResolution.y * 7 / 10;\n\n            if (height >= width) { //竖屏\n                height = width;\n            } else { //黑屏\n                width = height;\n            }\n\n            int leftOffset = (screenResolution.x - width) / 2;\n            int topOffset = (screenResolution.y - height) / 2;\n            framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);\n\n        }\n        return framingRect;\n    }\n//  public Rect getFramingRect() {\n//    Point screenResolution = configManager.getScreenResolution();\n//    if (framingRect == null) {\n//      if (camera == null) {\n//        return null;\n//      }\n//      int width = screenResolution.x * 3 / 4;\n//      if (width < MIN_FRAME_WIDTH) {\n//        width = MIN_FRAME_WIDTH;\n//      } else if (width > MAX_FRAME_WIDTH) {\n//        width = MAX_FRAME_WIDTH;\n//      }\n//      int height = screenResolution.y * 3 / 4;\n//      if (height < MIN_FRAME_HEIGHT) {\n//        height = MIN_FRAME_HEIGHT;\n//      } else if (height > MAX_FRAME_HEIGHT) {\n//        height = MAX_FRAME_HEIGHT;\n//      }\n//      int leftOffset = (screenResolution.x - width) / 2;\n//      int topOffset = (screenResolution.y - height) / 2;\n//      framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);\n//      Log.d(TAG, \"Calculated framing rect: \" + framingRect);\n//    }\n//    return framingRect;\n//  }\n\n    /**\n     * Like {@link #getFramingRect} but coordinates are in terms of the preview frame,\n     * not UI / screen.\n     */\n    public Rect getFramingRectInPreview() {\n        if (framingRectInPreview == null) {\n            Rect rect = new Rect(getFramingRect());\n            Point cameraResolution = configManager.getCameraResolution();\n            Point screenResolution = configManager.getScreenResolution();\n            //modify here\n//      rect.left = rect.left * cameraResolution.x / screenResolution.x;\n//      rect.right = rect.right * cameraResolution.x / screenResolution.x;\n//      rect.top = rect.top * cameraResolution.y / screenResolution.y;\n//      rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;\n            rect.left = rect.left * cameraResolution.y / screenResolution.x;\n            rect.right = rect.right * cameraResolution.y / screenResolution.x;\n            rect.top = rect.top * cameraResolution.x / screenResolution.y;\n            rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;\n            framingRectInPreview = rect;\n        }\n        return framingRectInPreview;\n    }\n\n    /**\n     * Converts the result points from still resolution coordinates to screen coordinates.\n     *\n     * @param points The points returned by the Reader subclass through Result.getResultPoints().\n     * @return An array of Points scaled to the size of the framing rect and offset appropriately\n     *         so they can be drawn in screen coordinates.\n     */\n  /*\n  public Point[] convertResultPoints(ResultPoint[] points) {\n    Rect frame = getFramingRectInPreview();\n    int count = points.length;\n    Point[] output = new Point[count];\n    for (int x = 0; x < count; x++) {\n      output[x] = new Point();\n      output[x].x = frame.left + (int) (points[x].getX() + 0.5f);\n      output[x].y = frame.top + (int) (points[x].getY() + 0.5f);\n    }\n    return output;\n  }\n   */\n\n    /**\n     * A factory method to build the appropriate LuminanceSource object based on the format\n     * of the preview buffers, as described by Camera.Parameters.\n     *\n     * @param data   A preview frame.\n     * @param width  The width of the image.\n     * @param height The height of the image.\n     * @return A PlanarYUVLuminanceSource instance.\n     */\n    public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {\n        Rect rect = getFramingRectInPreview();\n        int previewFormat = configManager.getPreviewFormat();\n        String previewFormatString = configManager.getPreviewFormatString();\n        switch (previewFormat) {\n            // This is the standard Android format which all devices are REQUIRED to support.\n            // In theory, it's the only one we should ever care about.\n            case PixelFormat.YCbCr_420_SP:\n                // This format has never been seen in the wild, but is compatible as we only care\n                // about the Y channel, so allow it.\n            case PixelFormat.YCbCr_422_SP:\n                return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,\n                        rect.width(), rect.height());\n            default:\n                // The Samsung Moment incorrectly uses this variant instead of the 'sp' version.\n                // Fortunately, it too has all the Y data up front, so we can read it.\n                if (\"yuv420p\".equals(previewFormatString)) {\n                    return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,\n                            rect.width(), rect.height());\n                }\n        }\n        throw new IllegalArgumentException(\"Unsupported picture format: \" +\n                previewFormat + '/' + previewFormatString);\n    }\n\n    public Context getContext() {\n        return context;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/camera/FlashlightManager.java",
    "content": "/*\n * Copyright (C) 2010 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.camera;\n\nimport android.os.IBinder;\nimport android.util.Log;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\n\n/**\n * This class is used to activate the weak light on some camera phones (not flash)\n * in order to illuminate surfaces for scanning. There is no official way to do this,\n * but, classes which allow access to this function still exist on some devices.\n * This therefore proceeds through a great deal of reflection.\n * <p>\n * See <a href=\"http://almondmendoza.com/2009/01/05/changing-the-screen-brightness-programatically/\">\n * http://almondmendoza.com/2009/01/05/changing-the-screen-brightness-programatically/</a> and\n * <a href=\"http://code.google.com/p/droidled/source/browse/trunk/src/com/droidled/demo/DroidLED.java\">\n * http://code.google.com/p/droidled/source/browse/trunk/src/com/droidled/demo/DroidLED.java</a>.\n * Thanks to Ryan Alford for pointing out the availability of this class.\n */\nfinal class FlashlightManager {\n\n    private static final String TAG = FlashlightManager.class.getSimpleName();\n\n    private static final Object iHardwareService;\n    private static final Method setFlashEnabledMethod;\n\n    static {\n        iHardwareService = getHardwareService();\n        setFlashEnabledMethod = getSetFlashEnabledMethod(iHardwareService);\n        if (iHardwareService == null) {\n            Log.v(TAG, \"This device does supports control of a flashlight\");\n        } else {\n            Log.v(TAG, \"This device does not support control of a flashlight\");\n        }\n    }\n\n    private FlashlightManager() {\n    }\n\n    /**\n     * �����������ƿ���\n     */\n    //FIXME\n    static void enableFlashlight() {\n        setFlashlight(false);\n    }\n\n    static void disableFlashlight() {\n        setFlashlight(false);\n    }\n\n    private static Object getHardwareService() {\n        Class<?> serviceManagerClass = maybeForName(\"android.os.ServiceManager\");\n        if (serviceManagerClass == null) {\n            return null;\n        }\n\n        Method getServiceMethod = maybeGetMethod(serviceManagerClass, \"getService\", String.class);\n        if (getServiceMethod == null) {\n            return null;\n        }\n\n        Object hardwareService = invoke(getServiceMethod, null, \"hardware\");\n        if (hardwareService == null) {\n            return null;\n        }\n\n        Class<?> iHardwareServiceStubClass = maybeForName(\"android.os.IHardwareService$Stub\");\n        if (iHardwareServiceStubClass == null) {\n            return null;\n        }\n\n        Method asInterfaceMethod = maybeGetMethod(iHardwareServiceStubClass, \"asInterface\", IBinder.class);\n        if (asInterfaceMethod == null) {\n            return null;\n        }\n\n        return invoke(asInterfaceMethod, null, hardwareService);\n    }\n\n    private static Method getSetFlashEnabledMethod(Object iHardwareService) {\n        if (iHardwareService == null) {\n            return null;\n        }\n        Class<?> proxyClass = iHardwareService.getClass();\n        return maybeGetMethod(proxyClass, \"setFlashlightEnabled\", boolean.class);\n    }\n\n    private static Class<?> maybeForName(String name) {\n        try {\n            return Class.forName(name);\n        } catch (ClassNotFoundException cnfe) {\n            // OK\n            return null;\n        } catch (RuntimeException re) {\n            Log.w(TAG, \"Unexpected error while finding class \" + name, re);\n            return null;\n        }\n    }\n\n    private static Method maybeGetMethod(Class<?> clazz, String name, Class<?>... argClasses) {\n        try {\n            return clazz.getMethod(name, argClasses);\n        } catch (NoSuchMethodException nsme) {\n            // OK\n            return null;\n        } catch (RuntimeException re) {\n            Log.w(TAG, \"Unexpected error while finding method \" + name, re);\n            return null;\n        }\n    }\n\n    private static Object invoke(Method method, Object instance, Object... args) {\n        try {\n            return method.invoke(instance, args);\n        } catch (IllegalAccessException e) {\n            Log.w(TAG, \"Unexpected error while invoking \" + method, e);\n            return null;\n        } catch (InvocationTargetException e) {\n            Log.w(TAG, \"Unexpected error while invoking \" + method, e.getCause());\n            return null;\n        } catch (RuntimeException re) {\n            Log.w(TAG, \"Unexpected error while invoking \" + method, re);\n            return null;\n        }\n    }\n\n    private static void setFlashlight(boolean active) {\n        if (iHardwareService != null) {\n            invoke(setFlashEnabledMethod, iHardwareService, active);\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/camera/PlanarYUVLuminanceSource.java",
    "content": "/*\n * Copyright 2009 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.camera;\n\nimport android.graphics.Bitmap;\n\nimport com.google.zxing.LuminanceSource;\n\n/**\n * This object extends LuminanceSource around an array of YUV data returned from the camera driver,\n * with the option to crop to a rectangle within the full data. This can be used to exclude\n * superfluous pixels around the perimeter and speed up decoding.\n * <p>\n * It works for any pixel format where the Y channel is planar and appears first, including\n * YCbCr_420_SP and YCbCr_422_SP.\n *\n * @author dswitkin@google.com (Daniel Switkin)\n */\npublic final class PlanarYUVLuminanceSource extends LuminanceSource {\n    private final byte[] yuvData;\n    private final int dataWidth;\n    private final int dataHeight;\n    private final int left;\n    private final int top;\n\n    public PlanarYUVLuminanceSource(byte[] yuvData, int dataWidth, int dataHeight, int left, int top,\n                                    int width, int height) {\n        super(width, height);\n\n        if (left + width > dataWidth || top + height > dataHeight) {\n            throw new IllegalArgumentException(\"Crop rectangle does not fit within image data.\");\n        }\n\n        this.yuvData = yuvData;\n        this.dataWidth = dataWidth;\n        this.dataHeight = dataHeight;\n        this.left = left;\n        this.top = top;\n    }\n\n    @Override\n    public byte[] getRow(int y, byte[] row) {\n        if (y < 0 || y >= getHeight()) {\n            throw new IllegalArgumentException(\"Requested row is outside the image: \" + y);\n        }\n        int width = getWidth();\n        if (row == null || row.length < width) {\n            row = new byte[width];\n        }\n        int offset = (y + top) * dataWidth + left;\n        System.arraycopy(yuvData, offset, row, 0, width);\n        return row;\n    }\n\n    @Override\n    public byte[] getMatrix() {\n        int width = getWidth();\n        int height = getHeight();\n\n        // If the caller asks for the entire underlying image, save the copy and give them the\n        // original data. The docs specifically warn that result.length must be ignored.\n        if (width == dataWidth && height == dataHeight) {\n            return yuvData;\n        }\n\n        int area = width * height;\n        byte[] matrix = new byte[area];\n        int inputOffset = top * dataWidth + left;\n\n        // If the width matches the full width of the underlying data, perform a single copy.\n        if (width == dataWidth) {\n            System.arraycopy(yuvData, inputOffset, matrix, 0, area);\n            return matrix;\n        }\n\n        // Otherwise copy one cropped row at a time.\n        byte[] yuv = yuvData;\n        for (int y = 0; y < height; y++) {\n            int outputOffset = y * width;\n            System.arraycopy(yuv, inputOffset, matrix, outputOffset, width);\n            inputOffset += dataWidth;\n        }\n        return matrix;\n    }\n\n    @Override\n    public boolean isCropSupported() {\n        return true;\n    }\n\n    public int getDataWidth() {\n        return dataWidth;\n    }\n\n    public int getDataHeight() {\n        return dataHeight;\n    }\n\n    public Bitmap renderCroppedGreyscaleBitmap() {\n        int width = getWidth();\n        int height = getHeight();\n        int[] pixels = new int[width * height];\n        byte[] yuv = yuvData;\n        int inputOffset = top * dataWidth + left;\n\n        for (int y = 0; y < height; y++) {\n            int outputOffset = y * width;\n            for (int x = 0; x < width; x++) {\n                int grey = yuv[inputOffset + x] & 0xff;\n                pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101);\n            }\n            inputOffset += dataWidth;\n        }\n\n        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);\n        bitmap.setPixels(pixels, 0, width, 0, 0, width, height);\n        return bitmap;\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/camera/PreviewCallback.java",
    "content": "/*\n * Copyright (C) 2010 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.camera;\n\nimport android.graphics.Point;\nimport android.hardware.Camera;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.util.Log;\n\nfinal class PreviewCallback implements Camera.PreviewCallback {\n\n    private static final String TAG = PreviewCallback.class.getSimpleName();\n\n    private final CameraConfigurationManager configManager;\n    private final boolean useOneShotPreviewCallback;\n    private Handler previewHandler;\n    private int previewMessage;\n\n    PreviewCallback(CameraConfigurationManager configManager, boolean useOneShotPreviewCallback) {\n        this.configManager = configManager;\n        this.useOneShotPreviewCallback = useOneShotPreviewCallback;\n    }\n\n    void setHandler(Handler previewHandler, int previewMessage) {\n        this.previewHandler = previewHandler;\n        this.previewMessage = previewMessage;\n    }\n\n    public void onPreviewFrame(byte[] data, Camera camera) {\n        Point cameraResolution = configManager.getCameraResolution();\n        if (!useOneShotPreviewCallback) {\n            camera.setPreviewCallback(null);\n        }\n        if (previewHandler != null) {\n            Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x,\n                    cameraResolution.y, data);\n            message.sendToTarget();\n            previewHandler = null;\n        } else {\n            Log.d(TAG, \"Got preview callback, but no handler for it\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/decoding/CaptureActivityHandler.java",
    "content": "/*\n * Copyright (C) 2008 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.decoding;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.util.Log;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.zxing.activity.CaptureActivity;\nimport com.didichuxing.doraemonkit.zxing.camera.CameraManager;\nimport com.didichuxing.doraemonkit.zxing.view.ViewfinderResultPointCallback;\nimport com.google.zxing.BarcodeFormat;\nimport com.google.zxing.Result;\n\nimport java.util.Vector;\n\n\n/**\n * This class handles all the messaging which comprises the state machine for capture.\n */\npublic final class CaptureActivityHandler extends Handler {\n\n    private static final String TAG = CaptureActivityHandler.class.getSimpleName();\n\n    private final CaptureActivity activity;\n    private final DecodeThread decodeThread;\n    private State state;\n\n    private enum State {\n        PREVIEW,\n        SUCCESS,\n        DONE\n    }\n\n    public CaptureActivityHandler(CaptureActivity activity, Vector<BarcodeFormat> decodeFormats,\n                                  String characterSet) {\n        this.activity = activity;\n        decodeThread = new DecodeThread(activity, decodeFormats, characterSet,\n                new ViewfinderResultPointCallback(activity.getViewfinderView()));\n        decodeThread.start();\n        state = State.SUCCESS;\n        // Start ourselves capturing previews and decoding.\n        CameraManager.get().startPreview();\n        restartPreviewAndDecode();\n    }\n\n    @Override\n    public void handleMessage(Message message) {\n        if (message.what == R.id.auto_focus) {//Log.d(TAG, \"Got auto-focus message\");\n            // When one auto focus pass finishes, start another. This is the closest thing to\n            // continuous AF. It does seem to hunt a bit, but I'm not sure what else to do.\n            if (state == State.PREVIEW) {\n                CameraManager.get().requestAutoFocus(this, R.id.auto_focus);\n            }\n        } else if (message.what == R.id.restart_preview) {\n            Log.d(TAG, \"Got restart preview message\");\n            restartPreviewAndDecode();\n        } else if (message.what == R.id.decode_succeeded) {\n            Log.d(TAG, \"Got decode succeeded message\");\n            state = State.SUCCESS;\n            Bundle bundle = message.getData();\n\n            /***********************************************************************/\n            Bitmap barcode = bundle == null ? null :\n                    (Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP);//���ñ����߳�\n\n            activity.handleDecode((Result) message.obj, barcode);//���ؽ��\n            /***********************************************************************/\n        } else if (message.what == R.id.decode_failed) {// We're decoding as fast as possible, so when one decode fails, start another.\n            state = State.PREVIEW;\n            CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode);\n        } else if (message.what == R.id.return_scan_result) {\n            Log.d(TAG, \"Got return scan result message\");\n            activity.setResult(Activity.RESULT_OK, (Intent) message.obj);\n            activity.finish();\n        } else if (message.what == R.id.launch_product_query) {\n            Log.d(TAG, \"Got product query message\");\n            String url = (String) message.obj;\n            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));\n            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);\n            activity.startActivity(intent);\n        }\n    }\n\n    public void quitSynchronously() {\n        state = State.DONE;\n        CameraManager.get().stopPreview();\n        Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit);\n        quit.sendToTarget();\n        try {\n            decodeThread.join();\n        } catch (InterruptedException e) {\n            // continue\n        }\n\n        // Be absolutely sure we don't send any queued up messages\n        removeMessages(R.id.decode_succeeded);\n        removeMessages(R.id.decode_failed);\n    }\n\n    private void restartPreviewAndDecode() {\n        if (state == State.SUCCESS) {\n            state = State.PREVIEW;\n            CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode);\n            CameraManager.get().requestAutoFocus(this, R.id.auto_focus);\n            activity.drawViewfinder();\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/decoding/DecodeFormatManager.java",
    "content": "/*\n * Copyright (C) 2010 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.decoding;\n\nimport android.content.Intent;\nimport android.net.Uri;\n\nimport com.google.zxing.BarcodeFormat;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Vector;\nimport java.util.regex.Pattern;\n\nfinal class DecodeFormatManager {\n\n    private static final Pattern COMMA_PATTERN = Pattern.compile(\",\");\n\n    static final Vector<BarcodeFormat> PRODUCT_FORMATS;\n    static final Vector<BarcodeFormat> ONE_D_FORMATS;\n    static final Vector<BarcodeFormat> QR_CODE_FORMATS;\n    static final Vector<BarcodeFormat> DATA_MATRIX_FORMATS;\n\n    static {\n        PRODUCT_FORMATS = new Vector<BarcodeFormat>(5);\n        PRODUCT_FORMATS.add(BarcodeFormat.UPC_A);\n        PRODUCT_FORMATS.add(BarcodeFormat.UPC_E);\n        PRODUCT_FORMATS.add(BarcodeFormat.EAN_13);\n        PRODUCT_FORMATS.add(BarcodeFormat.EAN_8);\n        ONE_D_FORMATS = new Vector<BarcodeFormat>(PRODUCT_FORMATS.size() + 4);\n        ONE_D_FORMATS.addAll(PRODUCT_FORMATS);\n        ONE_D_FORMATS.add(BarcodeFormat.CODE_39);\n        ONE_D_FORMATS.add(BarcodeFormat.CODE_93);\n        ONE_D_FORMATS.add(BarcodeFormat.CODE_128);\n        ONE_D_FORMATS.add(BarcodeFormat.ITF);\n        QR_CODE_FORMATS = new Vector<BarcodeFormat>(1);\n        QR_CODE_FORMATS.add(BarcodeFormat.QR_CODE);\n        DATA_MATRIX_FORMATS = new Vector<BarcodeFormat>(1);\n        DATA_MATRIX_FORMATS.add(BarcodeFormat.DATA_MATRIX);\n    }\n\n    private DecodeFormatManager() {\n    }\n\n    static Vector<BarcodeFormat> parseDecodeFormats(Intent intent) {\n        List<String> scanFormats = null;\n        String scanFormatsString = intent.getStringExtra(Intents.Scan.SCAN_FORMATS);\n        if (scanFormatsString != null) {\n            scanFormats = Arrays.asList(COMMA_PATTERN.split(scanFormatsString));\n        }\n        return parseDecodeFormats(scanFormats, intent.getStringExtra(Intents.Scan.MODE));\n    }\n\n    static Vector<BarcodeFormat> parseDecodeFormats(Uri inputUri) {\n        List<String> formats = inputUri.getQueryParameters(Intents.Scan.SCAN_FORMATS);\n        if (formats != null && formats.size() == 1 && formats.get(0) != null) {\n            formats = Arrays.asList(COMMA_PATTERN.split(formats.get(0)));\n        }\n        return parseDecodeFormats(formats, inputUri.getQueryParameter(Intents.Scan.MODE));\n    }\n\n    private static Vector<BarcodeFormat> parseDecodeFormats(Iterable<String> scanFormats,\n                                                            String decodeMode) {\n        if (scanFormats != null) {\n            Vector<BarcodeFormat> formats = new Vector<BarcodeFormat>();\n            try {\n                for (String format : scanFormats) {\n                    formats.add(BarcodeFormat.valueOf(format));\n                }\n                return formats;\n            } catch (IllegalArgumentException iae) {\n                // ignore it then\n            }\n        }\n        if (decodeMode != null) {\n            if (Intents.Scan.PRODUCT_MODE.equals(decodeMode)) {\n                return PRODUCT_FORMATS;\n            }\n            if (Intents.Scan.QR_CODE_MODE.equals(decodeMode)) {\n                return QR_CODE_FORMATS;\n            }\n            if (Intents.Scan.DATA_MATRIX_MODE.equals(decodeMode)) {\n                return DATA_MATRIX_FORMATS;\n            }\n            if (Intents.Scan.ONE_D_MODE.equals(decodeMode)) {\n                return ONE_D_FORMATS;\n            }\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/decoding/DecodeHandler.java",
    "content": "/*\n * Copyright (C) 2010 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.decoding;\n\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\nimport android.util.Log;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.zxing.activity.CaptureActivity;\nimport com.didichuxing.doraemonkit.zxing.camera.CameraManager;\nimport com.didichuxing.doraemonkit.zxing.camera.PlanarYUVLuminanceSource;\nimport com.google.zxing.BinaryBitmap;\nimport com.google.zxing.DecodeHintType;\nimport com.google.zxing.MultiFormatReader;\nimport com.google.zxing.ReaderException;\nimport com.google.zxing.Result;\nimport com.google.zxing.common.HybridBinarizer;\n\nimport java.util.Hashtable;\n\n\nfinal class DecodeHandler extends Handler {\n\n    private static final String TAG = DecodeHandler.class.getSimpleName();\n\n    private final CaptureActivity activity;\n    private final MultiFormatReader multiFormatReader;\n\n    DecodeHandler(CaptureActivity activity, Hashtable<DecodeHintType, Object> hints) {\n        multiFormatReader = new MultiFormatReader();\n        multiFormatReader.setHints(hints);\n        this.activity = activity;\n    }\n\n    @Override\n    public void handleMessage(Message message) {\n        if (message.what == R.id.decode) {\n            decode((byte[]) message.obj, message.arg1, message.arg2);\n        } else if (message.what == R.id.quit) {\n            Looper.myLooper().quit();\n        }\n    }\n\n    /**\n     * Decode the data within the viewfinder rectangle, and time how long it took. For efficiency,\n     * reuse the same reader objects from one decode to the next.\n     *\n     * @param data   The YUV preview frame.\n     * @param width  The width of the preview frame.\n     * @param height The height of the preview frame.\n     */\n    private void decode(byte[] data, int width, int height) {\n        long start = System.currentTimeMillis();\n        Result rawResult = null;\n\n        //modify here\n        byte[] rotatedData = new byte[data.length];\n        for (int y = 0; y < height; y++) {\n            for (int x = 0; x < width; x++)\n                rotatedData[x * height + height - y - 1] = data[x + y * width];\n        }\n        int tmp = width; // Here we are swapping, that's the difference to #11\n        width = height;\n        height = tmp;\n\n        PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(rotatedData, width, height);\n        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));\n        try {\n            rawResult = multiFormatReader.decodeWithState(bitmap);\n        } catch (ReaderException re) {\n            // continue\n        } finally {\n            multiFormatReader.reset();\n        }\n\n        if (rawResult != null) {\n            long end = System.currentTimeMillis();\n            Log.d(TAG, \"Found barcode (\" + (end - start) + \" ms):\\n\" + rawResult.toString());\n            Message message = Message.obtain(activity.getHandler(), R.id.decode_succeeded, rawResult);\n            Bundle bundle = new Bundle();\n            bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());\n            message.setData(bundle);\n            //Log.d(TAG, \"Sending decode succeeded message...\");\n            message.sendToTarget();\n        } else {\n            Message message = Message.obtain(activity.getHandler(), R.id.decode_failed);\n            message.sendToTarget();\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/decoding/DecodeThread.java",
    "content": "/*\n * Copyright (C) 2008 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.decoding;\n\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport com.didichuxing.doraemonkit.zxing.activity.CaptureActivity;\nimport com.google.zxing.BarcodeFormat;\nimport com.google.zxing.DecodeHintType;\nimport com.google.zxing.ResultPointCallback;\n\nimport java.util.Hashtable;\nimport java.util.Vector;\nimport java.util.concurrent.CountDownLatch;\n\n/**\n * This thread does all the heavy lifting of decoding the images.\n */\nfinal class DecodeThread extends Thread {\n\n    public static final String BARCODE_BITMAP = \"barcode_bitmap\";\n    private final CaptureActivity activity;\n    private final Hashtable<DecodeHintType, Object> hints;\n    private Handler handler;\n    private final CountDownLatch handlerInitLatch;\n\n    DecodeThread(CaptureActivity activity,\n                 Vector<BarcodeFormat> decodeFormats,\n                 String characterSet,\n                 ResultPointCallback resultPointCallback) {\n\n        this.activity = activity;\n        handlerInitLatch = new CountDownLatch(1);\n\n        hints = new Hashtable<DecodeHintType, Object>(3);\n\n        if (decodeFormats == null || decodeFormats.isEmpty()) {\n            decodeFormats = new Vector<BarcodeFormat>();\n            decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);\n            decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);\n            decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);\n        }\n\n        hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);\n\n        if (characterSet != null) {\n            hints.put(DecodeHintType.CHARACTER_SET, characterSet);\n        }\n\n        hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);\n    }\n\n    Handler getHandler() {\n        try {\n            handlerInitLatch.await();\n        } catch (InterruptedException ie) {\n            // continue?\n        }\n        return handler;\n    }\n\n    @Override\n    public void run() {\n        Looper.prepare();\n        handler = new DecodeHandler(activity, hints);\n        handlerInitLatch.countDown();\n        Looper.loop();\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/decoding/FinishListener.java",
    "content": "/*\n * Copyright (C) 2010 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.decoding;\n\nimport android.app.Activity;\nimport android.content.DialogInterface;\n\n/**\n * Simple listener used to exit the app in a few cases.\n */\npublic final class FinishListener\n        implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener, Runnable {\n\n    private final Activity activityToFinish;\n\n    public FinishListener(Activity activityToFinish) {\n        this.activityToFinish = activityToFinish;\n    }\n\n    public void onCancel(DialogInterface dialogInterface) {\n        run();\n    }\n\n    public void onClick(DialogInterface dialogInterface, int i) {\n        run();\n    }\n\n    public void run() {\n        activityToFinish.finish();\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/decoding/InactivityTimer.java",
    "content": "/*\n * Copyright (C) 2010 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.decoding;\n\nimport android.app.Activity;\n\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledFuture;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Finishes an activity after a period of inactivity.\n */\npublic final class InactivityTimer {\n\n    private static final int INACTIVITY_DELAY_SECONDS = 5 * 60;\n\n    private final ScheduledExecutorService inactivityTimer =\n            Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory());\n    private final Activity activity;\n    private ScheduledFuture<?> inactivityFuture = null;\n\n    public InactivityTimer(Activity activity) {\n        this.activity = activity;\n        onActivity();\n    }\n\n    public void onActivity() {\n        cancel();\n        inactivityFuture = inactivityTimer.schedule(new FinishListener(activity),\n                INACTIVITY_DELAY_SECONDS,\n                TimeUnit.SECONDS);\n    }\n\n    private void cancel() {\n        if (inactivityFuture != null) {\n            inactivityFuture.cancel(true);\n            inactivityFuture = null;\n        }\n    }\n\n    public void shutdown() {\n        cancel();\n        inactivityTimer.shutdown();\n    }\n\n    private static final class DaemonThreadFactory implements ThreadFactory {\n        public Thread newThread(Runnable runnable) {\n            Thread thread = new Thread(runnable);\n            thread.setDaemon(true);\n            return thread;\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/decoding/Intents.java",
    "content": "/*\n * Copyright (C) 2008 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.decoding;\n\n/**\n * This class provides the constants to use when sending an Intent to Barcode Scanner.\n * These strings are effectively API and cannot be changed.\n */\npublic final class Intents {\n    private Intents() {\n    }\n\n    public static final class Scan {\n        /**\n         * Send this intent to open the Barcodes app in scanning mode, find a barcode, and return\n         * the results.\n         */\n        public static final String ACTION = \"com.google.zxing.client.android.SCAN\";\n\n        /**\n         * By default, sending Scan.ACTION will decode all barcodes that we understand. However it\n         * may be useful to limit scanning to certain formats. Use Intent.putExtra(MODE, value) with\n         * one of the values below ({@link #PRODUCT_MODE}, {@link #ONE_D_MODE}, {@link #QR_CODE_MODE}).\n         * Optional.\n         * <p>\n         * Setting this is effectively shorthnad for setting explicit formats with {@link #SCAN_FORMATS}.\n         * It is overridden by that setting.\n         */\n        public static final String MODE = \"SCAN_MODE\";\n\n        /**\n         * Comma-separated list of formats to scan for. The values must match the names of\n         * {@link com.google.zxing.BarcodeFormat}s, such as {@link com.google.zxing.BarcodeFormat#EAN_13}.\n         * Example: \"EAN_13,EAN_8,QR_CODE\"\n         * <p>\n         * This overrides {@link #MODE}.\n         */\n        public static final String SCAN_FORMATS = \"SCAN_FORMATS\";\n\n        /**\n         * @see com.google.zxing.DecodeHintType#CHARACTER_SET\n         */\n        public static final String CHARACTER_SET = \"CHARACTER_SET\";\n\n        /**\n         * Decode only UPC and EAN barcodes. This is the right choice for shopping apps which get\n         * prices, reviews, etc. for products.\n         */\n        public static final String PRODUCT_MODE = \"PRODUCT_MODE\";\n\n        /**\n         * Decode only 1D barcodes (currently UPC, EAN, Code 39, and Code 128).\n         */\n        public static final String ONE_D_MODE = \"ONE_D_MODE\";\n\n        /**\n         * Decode only QR codes.\n         */\n        public static final String QR_CODE_MODE = \"QR_CODE_MODE\";\n\n        /**\n         * Decode only Data Matrix codes.\n         */\n        public static final String DATA_MATRIX_MODE = \"DATA_MATRIX_MODE\";\n\n        /**\n         * If a barcode is found, Barcodes returns RESULT_OK to onActivityResult() of the app which\n         * requested the scan via startSubActivity(). The barcodes contents can be retrieved with\n         * intent.getStringExtra(RESULT). If the user presses Back, the result code will be\n         * RESULT_CANCELED.\n         */\n        public static final String RESULT = \"SCAN_RESULT\";\n\n        /**\n         * Call intent.getStringExtra(RESULT_FORMAT) to determine which barcode format was found.\n         * See Contents.Format for possible values.\n         */\n        public static final String RESULT_FORMAT = \"SCAN_RESULT_FORMAT\";\n\n        /**\n         * Setting this to false will not save scanned codes in the history.\n         */\n        public static final String SAVE_HISTORY = \"SAVE_HISTORY\";\n\n        private Scan() {\n        }\n    }\n\n    public static final class Encode {\n        /**\n         * Send this intent to encode a piece of data as a QR code and display it full screen, so\n         * that another person can scan the barcode from your screen.\n         */\n        public static final String ACTION = \"com.google.zxing.client.android.ENCODE\";\n\n        /**\n         * The data to encode. Use Intent.putExtra(DATA, data) where data is either a String or a\n         * Bundle, depending on the type and format specified. Non-QR Code formats should\n         * just use a String here. For QR Code, see Contents for details.\n         */\n        public static final String DATA = \"ENCODE_DATA\";\n\n        /**\n         * The type of data being supplied if the format is QR Code. Use\n         * Intent.putExtra(TYPE, type) with one of Contents.Type.\n         */\n        public static final String TYPE = \"ENCODE_TYPE\";\n\n        /**\n         * The barcode format to be displayed. If this isn't specified or is blank,\n         * it defaults to QR Code. Use Intent.putExtra(FORMAT, format), where\n         * format is one of Contents.Format.\n         */\n        public static final String FORMAT = \"ENCODE_FORMAT\";\n\n        private Encode() {\n        }\n    }\n\n    public static final class SearchBookContents {\n        /**\n         * Use Google Book Search to search the contents of the book provided.\n         */\n        public static final String ACTION = \"com.google.zxing.client.android.SEARCH_BOOK_CONTENTS\";\n\n        /**\n         * The book to search, identified by ISBN number.\n         */\n        public static final String ISBN = \"ISBN\";\n\n        /**\n         * An optional field which is the text to search for.\n         */\n        public static final String QUERY = \"QUERY\";\n\n        private SearchBookContents() {\n        }\n    }\n\n    public static final class WifiConnect {\n        /**\n         * Internal intent used to trigger connection to a wi-fi network.\n         */\n        public static final String ACTION = \"com.google.zxing.client.android.WIFI_CONNECT\";\n\n        /**\n         * The network to connect to, all the configuration provided here.\n         */\n        public static final String SSID = \"SSID\";\n\n        /**\n         * The network to connect to, all the configuration provided here.\n         */\n        public static final String TYPE = \"TYPE\";\n\n        /**\n         * The network to connect to, all the configuration provided here.\n         */\n        public static final String PASSWORD = \"PASSWORD\";\n\n        private WifiConnect() {\n        }\n    }\n\n\n    public static final class Share {\n        /**\n         * Give the user a choice of items to encode as a barcode, then render it as a QR Code and\n         * display onscreen for a friend to scan with their phone.\n         */\n        public static final String ACTION = \"com.google.zxing.client.android.SHARE\";\n\n        private Share() {\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/view/ViewfinderResultPointCallback.java",
    "content": "/*\n * Copyright (C) 2009 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.view;\n\nimport com.google.zxing.ResultPoint;\nimport com.google.zxing.ResultPointCallback;\n\npublic final class ViewfinderResultPointCallback implements ResultPointCallback {\n    private final ViewfinderView viewfinderView;\n\n    public ViewfinderResultPointCallback(ViewfinderView viewfinderView) {\n        this.viewfinderView = viewfinderView;\n    }\n\n    public void foundPossibleResultPoint(ResultPoint point) {\n        viewfinderView.addPossibleResultPoint(point);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/java/com/didichuxing/doraemonkit/zxing/view/ViewfinderView.java",
    "content": "/*\n * Copyright (C) 2008 ZXing authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didichuxing.doraemonkit.zxing.view;\n\nimport java.util.Collection;\nimport java.util.HashSet;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.ComposeShader;\nimport android.graphics.LinearGradient;\nimport android.graphics.Paint;\nimport android.graphics.PorterDuff;\nimport android.graphics.RadialGradient;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.graphics.Shader;\nimport android.graphics.SweepGradient;\nimport android.util.AttributeSet;\nimport android.view.View;\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.zxing.camera.CameraManager;\nimport com.google.zxing.ResultPoint;\n\n\n/**\n * This view is overlaid on top of the camera preview. It adds the viewfinder rectangle and partial\n * transparency outside it, as well as the laser scanner animation and result points.\n */\npublic final class ViewfinderView extends View {\n\n    private static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64};\n    private static final long ANIMATION_DELAY = 10L;\n    private static final int OPAQUE = 0xFF;\n    private static final int CORNER_RECT_WIDTH = 8;  //扫描区边角的宽\n    private static final int CORNER_RECT_HEIGHT = 40; //扫描区边角的高\n    private static final int SCANNER_LINE_MOVE_DISTANCE = 5;  //扫描线移动距离\n    private static final int SCANNER_LINE_HEIGHT = 10;  //扫描线宽度\n\n    private final Paint paint;\n    private Bitmap resultBitmap;\n    //模糊区域颜色\n    private final int maskColor;\n    private final int resultColor;\n    //扫描区域边框颜色\n    private final int frameColor;\n    //扫描线颜色\n    private final int laserColor;\n    //四角颜色\n    private final int cornerColor;\n    //扫描点的颜色\n    private final int resultPointColor;\n    //扫描区域提示文本\n    private final String labelText;\n    //扫描区域提示文本颜色\n    private final int labelTextColor;\n    private final float labelTextSize;\n\n    public static int scannerStart = 0;\n    public static int scannerEnd = 0;\n\n    private Collection<ResultPoint> possibleResultPoints;\n    private Collection<ResultPoint> lastPossibleResultPoints;\n\n    // This constructor is used when the class is built from an XML resource.\n    public ViewfinderView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n\n        //初始化自定义属性信息\n        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ViewfinderView);\n\n        try {\n            laserColor = array.getColor(R.styleable.ViewfinderView_dkLaserColor, 0x00FF00);\n            cornerColor = array.getColor(R.styleable.ViewfinderView_dkCornerColor, 0x00FF00);\n            frameColor = array.getColor(R.styleable.ViewfinderView_dkFrameColor, 0xFFFFFF);\n            resultPointColor = array.getColor(R.styleable.ViewfinderView_dkResultPointColor, 0xC0FFFF00);\n            maskColor = array.getColor(R.styleable.ViewfinderView_dkMaskColor, 0x60000000);\n            resultColor = array.getColor(R.styleable.ViewfinderView_dkResultColor, 0xB0000000);\n            labelTextColor = array.getColor(R.styleable.ViewfinderView_dkLabelTextColor, 0x90FFFFFF);\n            labelText = array.getString(R.styleable.ViewfinderView_dkLabelText);\n            labelTextSize = array.getFloat(R.styleable.ViewfinderView_dkLabelTextSize, 36f);\n        } finally {\n            array.recycle();\n        }\n\n        // Initialize these once for performance rather than calling them every time in onDraw().\n        paint = new Paint();\n        paint.setAntiAlias(true);\n        possibleResultPoints = new HashSet<ResultPoint>(5);\n\n\n    }\n\n    @Override\n    public void onDraw(Canvas canvas) {\n        Rect frame = CameraManager.get().getFramingRect();\n        if (frame == null) {\n            return;\n        }\n        if (scannerStart == 0 || scannerEnd == 0) {\n            scannerStart = frame.top;\n            scannerEnd = frame.bottom;\n        }\n\n        int width = canvas.getWidth();\n        int height = canvas.getHeight();\n        // Draw the exterior (i.e. outside the framing rect) darkened\n        drawExterior(canvas, frame, width, height);\n\n\n        if (resultBitmap != null) {\n            // Draw the opaque result bitmap over the scanning rectangle\n            paint.setAlpha(OPAQUE);\n            canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint);\n        } else {\n            // Draw a two pixel solid black border inside the framing rect\n            drawFrame(canvas, frame);\n            // 绘制边角\n            drawCorner(canvas, frame);\n            //绘制提示信息\n            drawTextInfo(canvas, frame);\n            // Draw a red \"laser scanner\" line through the middle to show decoding is active\n            drawLaserScanner(canvas, frame);\n\n            Collection<ResultPoint> currentPossible = possibleResultPoints;\n            Collection<ResultPoint> currentLast = lastPossibleResultPoints;\n            if (currentPossible.isEmpty()) {\n                lastPossibleResultPoints = null;\n            } else {\n                possibleResultPoints = new HashSet<ResultPoint>(5);\n                lastPossibleResultPoints = currentPossible;\n                paint.setAlpha(OPAQUE);\n                paint.setColor(resultPointColor);\n                for (ResultPoint point : currentPossible) {\n                    canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 6.0f, paint);\n                }\n            }\n            if (currentLast != null) {\n                paint.setAlpha(OPAQUE / 2);\n                paint.setColor(resultPointColor);\n                for (ResultPoint point : currentLast) {\n                    canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 3.0f, paint);\n                }\n            }\n\n            // Request another updateInterceptApi at the animation interval, but only repaint the laser line,\n            // not the entire viewfinder mask.\n            //指定重绘区域，该方法会在子线程中执行\n            postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, frame.right, frame.bottom);\n        }\n    }\n\n    //绘制文本\n    private void drawTextInfo(Canvas canvas, Rect frame) {\n        paint.setColor(labelTextColor);\n        paint.setTextSize(labelTextSize);\n        paint.setTextAlign(Paint.Align.CENTER);\n        canvas.drawText(labelText, frame.left + frame.width() / 2, frame.top - CORNER_RECT_HEIGHT, paint);\n    }\n\n\n    //绘制边角\n    private void drawCorner(Canvas canvas, Rect frame) {\n        paint.setColor(cornerColor);\n        //左上\n        canvas.drawRect(frame.left, frame.top, frame.left + CORNER_RECT_WIDTH, frame.top + CORNER_RECT_HEIGHT, paint);\n        canvas.drawRect(frame.left, frame.top, frame.left + CORNER_RECT_HEIGHT, frame.top + CORNER_RECT_WIDTH, paint);\n        //右上\n        canvas.drawRect(frame.right - CORNER_RECT_WIDTH, frame.top, frame.right, frame.top + CORNER_RECT_HEIGHT, paint);\n        canvas.drawRect(frame.right - CORNER_RECT_HEIGHT, frame.top, frame.right, frame.top + CORNER_RECT_WIDTH, paint);\n        //左下\n        canvas.drawRect(frame.left, frame.bottom - CORNER_RECT_WIDTH, frame.left + CORNER_RECT_HEIGHT, frame.bottom, paint);\n        canvas.drawRect(frame.left, frame.bottom - CORNER_RECT_HEIGHT, frame.left + CORNER_RECT_WIDTH, frame.bottom, paint);\n        //右下\n        canvas.drawRect(frame.right - CORNER_RECT_WIDTH, frame.bottom - CORNER_RECT_HEIGHT, frame.right, frame.bottom, paint);\n        canvas.drawRect(frame.right - CORNER_RECT_HEIGHT, frame.bottom - CORNER_RECT_WIDTH, frame.right, frame.bottom, paint);\n    }\n\n    //绘制扫描线\n    private void drawLaserScanner(Canvas canvas, Rect frame) {\n        paint.setColor(laserColor);\n        //线性渐变\n        LinearGradient linearGradient = new LinearGradient(\n                frame.left, scannerStart,\n                frame.left, scannerStart + SCANNER_LINE_HEIGHT,\n                shadeColor(laserColor),\n                laserColor,\n                Shader.TileMode.MIRROR);\n\n        RadialGradient radialGradient = new RadialGradient(\n                (float) (frame.left + frame.width() / 2),\n                (float) (scannerStart + SCANNER_LINE_HEIGHT / 2),\n                360f,\n                laserColor,\n                shadeColor(laserColor),\n                Shader.TileMode.MIRROR);\n\n        SweepGradient sweepGradient = new SweepGradient(\n                (float) (frame.left + frame.width() / 2),\n                (float) (scannerStart + SCANNER_LINE_HEIGHT),\n                shadeColor(laserColor),\n                laserColor);\n\n        ComposeShader composeShader = new ComposeShader(radialGradient, linearGradient, PorterDuff.Mode.ADD);\n\n        paint.setShader(radialGradient);\n        if (scannerStart <= scannerEnd) {\n            //矩形\n//      canvas.drawRect(frame.left, scannerStart, frame.right, scannerStart + SCANNER_LINE_HEIGHT, paint);\n            //椭圆\n            RectF rectF = new RectF(frame.left + 2 * SCANNER_LINE_HEIGHT, scannerStart, frame.right - 2 * SCANNER_LINE_HEIGHT, scannerStart + SCANNER_LINE_HEIGHT);\n            canvas.drawOval(rectF, paint);\n            scannerStart += SCANNER_LINE_MOVE_DISTANCE;\n        } else {\n            scannerStart = frame.top;\n        }\n        paint.setShader(null);\n    }\n\n    //处理颜色模糊\n    public int shadeColor(int color) {\n        String hax = Integer.toHexString(color);\n        String result = \"20\" + hax.substring(2);\n        return Integer.valueOf(result, 16);\n    }\n\n    // 绘制扫描区边框 Draw a two pixel solid black border inside the framing rect\n    private void drawFrame(Canvas canvas, Rect frame) {\n        paint.setColor(frameColor);\n        canvas.drawRect(frame.left, frame.top, frame.right + 1, frame.top + 2, paint);\n        canvas.drawRect(frame.left, frame.top + 2, frame.left + 2, frame.bottom - 1, paint);\n        canvas.drawRect(frame.right - 1, frame.top, frame.right + 1, frame.bottom - 1, paint);\n        canvas.drawRect(frame.left, frame.bottom - 1, frame.right + 1, frame.bottom + 1, paint);\n    }\n\n    // 绘制模糊区域 Draw the exterior (i.e. outside the framing rect) darkened\n    private void drawExterior(Canvas canvas, Rect frame, int width, int height) {\n        paint.setColor(resultBitmap != null ? resultColor : maskColor);\n        canvas.drawRect(0, 0, width, frame.top, paint);\n        canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);\n        canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint);\n        canvas.drawRect(0, frame.bottom + 1, width, height, paint);\n    }\n\n    public void drawViewfinder() {\n        resultBitmap = null;\n        invalidate();\n    }\n\n    /**\n     * Draw a bitmap with the result points highlighted instead of the live scanning display.\n     *\n     * @param barcode An image of the decoded barcode.\n     */\n    public void drawResultBitmap(Bitmap barcode) {\n        resultBitmap = barcode;\n        invalidate();\n    }\n\n    public void addPossibleResultPoint(ResultPoint point) {\n        possibleResultPoints.add(point);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit/src/main/res/anim/dk_dd_mask_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"250\">\n    <alpha\n        android:fromAlpha=\"0\"\n        android:toAlpha=\"1\"/>\n\n</set>"
  },
  {
    "path": "Android/dokit/src/main/res/anim/dk_dd_mask_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"250\">\n    <alpha\n        android:fromAlpha=\"1\"\n        android:toAlpha=\"0\"/>\n\n</set>"
  },
  {
    "path": "Android/dokit/src/main/res/anim/dk_dd_menu_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"250\">\n    <translate\n        android:fromYDelta=\"-100%p\"\n        android:toYDelta=\"0\"/>\n\n</set>"
  },
  {
    "path": "Android/dokit/src/main/res/anim/dk_dd_menu_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"250\">\n    <translate\n        android:fromYDelta=\"0\"\n        android:toYDelta=\"-100%p\"/>\n\n</set>"
  },
  {
    "path": "Android/dokit/src/main/res/anim/dk_easy_refresh_rotate_down.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rotate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"150\"\n    android:fillAfter=\"true\"\n    android:fromDegrees=\"180\"\n    android:interpolator=\"@android:anim/linear_interpolator\"\n    android:pivotX=\"50%\"\n    android:pivotY=\"50%\"\n    android:repeatCount=\"0\"\n    android:toDegrees=\"0\" />\n"
  },
  {
    "path": "Android/dokit/src/main/res/anim/dk_easy_refresh_rotate_infinite.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rotate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"1500\"\n    android:fromDegrees=\"0\"\n    android:interpolator=\"@android:anim/linear_interpolator\"\n    android:pivotX=\"50%\"\n    android:pivotY=\"50%\"\n    android:repeatCount=\"infinite\"\n    android:toDegrees=\"360\" />"
  },
  {
    "path": "Android/dokit/src/main/res/anim/dk_easy_refresh_rotate_up.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<rotate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"150\"\n    android:fillAfter=\"true\"\n    android:fromDegrees=\"0\"\n    android:interpolator=\"@android:anim/linear_interpolator\"\n    android:pivotX=\"50%\"\n    android:pivotY=\"50%\"\n    android:toDegrees=\"180\" />\n"
  },
  {
    "path": "Android/dokit/src/main/res/color/dk_confirm_button_text_color.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"@color/dk_color_FFFFFF\" android:state_enabled=\"false\"/>\n    <item android:color=\"@color/dk_color_F3F4F5\" android:state_enabled=\"true\"/>\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/color/dk_network_pager_color.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"@color/dk_color_337CC4\" android:state_selected=\"true\"/>\n    <item android:color=\"@color/dk_color_333333\"/>\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/color/dk_radio_button_text_color.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"@color/dk_color_FFFFFF\" android:state_checked=\"true\"/>\n    <item android:color=\"@color/dk_color_337CC4\" android:state_checked=\"false\"/>\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_add_shape.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@color/dk_color_E5E5E5\" />\n    <corners android:radius=\"10dp\" />\n    <stroke\n        android:width=\"1dp\"\n        android:color=\"@color/dk_color_333333\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_app_toast_shape.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@color/dk_color_aa000000\" />\n    <stroke\n        android:width=\"1px\"\n        android:color=\"@color/dk_color_aa000000\" />\n    <padding\n        android:bottom=\"1px\"\n        android:left=\"1px\"\n        android:right=\"1px\"\n        android:top=\"1px\" />\n\n    <corners android:radius=\"4dp\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_brvah_sample_footer_loading_progress.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item>\n        <rotate\n            android:drawable=\"@mipmap/dk_brvah_sample_footer_loading\"\n            android:duration=\"500\"\n            android:fromDegrees=\"0.0\"\n            android:pivotX=\"50.0%\"\n            android:pivotY=\"50.0%\"\n            android:toDegrees=\"360.0\" />\n    </item>\n\n</layer-list>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_btn_dokit_for_web_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"rectangle\">\n\n    <corners\n        android:radius=\"3dp\"/>\n    <solid android:color=\"@color/dk_color_337CC4\"/>\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_btn_pause_style.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\" android:drawable=\"@mipmap/dk_ic_pause_pressed\" />\n    <item android:state_enabled=\"false\" android:drawable=\"@mipmap/dk_ic_pause_disable\" />\n    <item android:drawable=\"@mipmap/dk_ic_pause\"/>\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_btn_play_style.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\" android:drawable=\"@mipmap/dk_ic_play_pressed\" />\n    <item android:state_enabled=\"false\" android:drawable=\"@mipmap/dk_ic_play_disable\" />\n    <item android:drawable=\"@mipmap/dk_ic_play\"/>\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_confirm_button_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_enabled=\"true\" android:state_pressed=\"true\">\n        <layer-list>\n            <item>\n                <shape android:shape=\"rectangle\">\n                    <corners android:radius=\"4px\"/>\n                    <solid android:color=\"@color/dk_color_2E2E3A\"/>\n                </shape>\n            </item>\n            <item>\n                <shape android:shape=\"rectangle\">\n                    <corners android:radius=\"4px\"/>\n                    <solid android:color=\"@color/dk_color_33FFFFFF\"/>\n                </shape>\n            </item>\n        </layer-list>\n    </item>\n    <item android:state_enabled=\"true\" android:state_pressed=\"false\">\n        <shape android:shape=\"rectangle\">\n            <corners android:radius=\"4px\"/>\n            <solid android:color=\"@color/dk_color_337CC4\"/>\n        </shape>\n    </item>\n    <item android:state_enabled=\"false\">\n        <shape android:shape=\"rectangle\">\n            <corners android:radius=\"4px\"/>\n            <solid android:color=\"@color/dk_color_CCCCCC\"/>\n        </shape>\n    </item>\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_dialog_button_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@color/dk_color_7AE5E5E5\" android:state_pressed=\"true\" />\n    <item android:drawable=\"@color/dk_color_FFFFFF\" />\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_dialog_complete_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"#ffffff\" />\n    <corners android:radius=\"5dp\" />\n\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_dialog_confirm_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"#ffffff\" />\n    <corners android:radius=\"18dp\" />\n\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_divider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"rectangle\">\n    <size android:height=\"1px\" />\n    <solid android:color=\"@color/dk_color_E5E5E5\"/>\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_divider_gray.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"rectangle\">\n    <size android:height=\"12dp\" />\n    <solid android:color=\"#F5F6F7\"/>\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_dokitview_studio_connect.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <corners android:radius=\"7dp\" />\n    <solid android:color=\"@color/dk_color_48BB31\" />\n\n</shape>\n"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_dotted_line_horizontal.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"line\">\n    <stroke\n        android:width=\"1dp\"\n        android:color=\"@color/dk_color_DDDDDD\"\n        android:dashWidth=\"3dp\"\n        android:dashGap=\"2dp\" />\n</shape>\n\n"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_dotted_line_vertical.xml",
    "content": "<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:left=\"-300dp\"\n        android:right=\"-300dp\">\n        <rotate\n            android:drawable=\"@drawable/dk_dotted_line_horizontal\"\n            android:fromDegrees=\"90\"\n            android:visible=\"true\" />\n    </item>\n</layer-list>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_edittext_shape.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@color/dk_color_FFFFFF\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_float_ui_performance_info_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"#aaffffff\" />\n    <corners android:radius=\"4dp\" />\n    <stroke android:color=\"@color/dk_color_CCCCCC\" android:width=\"1px\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_health_edittext_style.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"#FFFFFF\" />\n    <corners android:radius=\"3dip\" />\n    <stroke\n        android:width=\"1dip\"\n        android:color=\"#DDDDDD\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_hint_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\" android:shape=\"rectangle\">\n    <corners\n        android:radius=\"5dp\"\n        />\n    <solid android:color=\"#54000000\"/>\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_info_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"@color/dk_color_FFFFFF\" />\n    <corners android:radius=\"4dp\" />\n    <stroke android:color=\"@color/dk_color_CCCCCC\" android:width=\"1px\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_input_cursor.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <size android:width=\"2dp\"/>\n    <solid android:color=\"@color/dk_color_CCCCCC\"/>\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_item_btn_img.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"@color/dk_color_337CC4\" />\n    <!-- 这里是设置为四周 也可以单独设置某个位置为圆角-->\n    <corners\n        android:bottomLeftRadius=\"10dp\"\n        android:bottomRightRadius=\"10dp\"\n        android:topLeftRadius=\"10dp\"\n        android:topRightRadius=\"10dp\" />\n    <!--    <stroke-->\n    <!--        android:width=\"1dp\"-->\n    <!--        android:color=\"#000000\" />-->\n</shape>\n"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_item_performance_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@color/dk_color_333339\" />\n    <corners android:radius=\"2dp\" />\n</shape>\n"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_jsonviewer_minus.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape android:shape=\"line\">\n            <stroke\n                android:width=\"1.5dip\"\n                android:color=\"@android:color/black\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_jsonviewer_plus.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape android:shape=\"line\">\n            <stroke\n                android:width=\"1.5dip\"\n                android:color=\"@android:color/black\" />\n        </shape>\n    </item>\n    <item>\n        <rotate\n            android:fromDegrees=\"90\"\n            android:pivotX=\"50%\"\n            android:pivotY=\"50%\"\n            android:toDegrees=\"-90\">\n            <shape android:shape=\"line\">\n                <stroke\n                    android:width=\"1.5dip\"\n                    android:color=\"@android:color/black\" />\n            </shape>\n        </rotate>\n    </item>\n</layer-list>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_kit_group_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <!--<item>-->\n        <!--<shape android:shape=\"rectangle\">-->\n            <!--<corners android:radius=\"4dp\" />-->\n            <!--<solid android:color=\"@color/dk_color_33000000\" />-->\n        <!--</shape>-->\n    <!--</item>-->\n\n    <item>\n        <shape android:shape=\"rectangle\">\n            <corners android:radius=\"4dp\" />\n            <solid android:color=\"@color/dk_color_FFFFFF\" />\n        </shape>\n    </item>\n\n</layer-list>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_line_chart_selected_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <gradient\n        android:angle=\"90\"\n        android:endColor=\"@color/dk_color_3300E0DC\"\n        android:startColor=\"@color/dk_color_3300BFFF\"/>\n</shape>\n"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_log_filter_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <!--<item>-->\n        <!--<shape android:shape=\"rectangle\">-->\n            <!--<corners android:radius=\"4dp\" />-->\n            <!--<solid android:color=\"@color/dk_color_33000000\" />-->\n        <!--</shape>-->\n    <!--</item>-->\n\n    <item>\n        <shape android:shape=\"rectangle\">\n            <corners android:radius=\"4dp\" />\n            <solid android:color=\"@color/dk_color_FFFFFF\" />\n        </shape>\n    </item>\n\n</layer-list>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_net_work_monitor_list_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@mipmap/dk_net_work_monitor_list_selected\" android:state_selected=\"true\" />\n    <item android:drawable=\"@mipmap/dk_net_work_monitor_list\" />\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_net_work_monitor_summary_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@mipmap/dk_net_work_monitor_summary_selected\" android:state_selected=\"true\" />\n    <item android:drawable=\"@mipmap/dk_net_work_monitor_summary\" />\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_network_info_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"#99ffffff\" />\n    <corners android:radius=\"4dp\" />\n    <stroke\n        android:width=\"1px\"\n        android:color=\"#cccccc\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_network_method_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"rectangle\">\n\n    <corners\n        android:radius=\"3dp\"/>\n    <solid android:color=\"@color/dk_color_D26282\"/>\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_network_platform_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"rectangle\">\n\n    <corners\n        android:radius=\"3dp\"/>\n    <solid android:color=\"@color/dk_color_337CC4\"/>\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_perform_data_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"#aa000000\" />\n    <corners android:radius=\"4dp\" />\n    <stroke android:color=\"@color/dk_color_CCCCCC\" android:width=\"1px\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_progressbar_style.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:innerRadiusRatio=\"3\"\n    android:shape=\"ring\"\n    android:thicknessRatio=\"20\"\n    android:useLevel=\"false\">\n\n    <gradient\n        android:centerColor=\"#3CBCA3\"\n        android:endColor=\"#3CBCA3\"\n        android:startColor=\"#3CBCA3\"\n        android:type=\"sweep\"\n        android:useLevel=\"true\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_radio_button_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@drawable/dk_radio_button_checked_background\" android:state_checked=\"true\" />\n    <item android:drawable=\"@drawable/dk_radio_button_normal_background\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_radio_button_background_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@drawable/dk_radio_button_checked_background_left\" android:state_checked=\"true\" />\n    <item android:drawable=\"@drawable/dk_radio_button_normal_background_left\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_radio_button_background_middle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@drawable/dk_radio_button_checked_background_middle\" android:state_checked=\"true\" />\n    <item android:drawable=\"@drawable/dk_radio_button_normal_background_middle\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_radio_button_background_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@drawable/dk_radio_button_checked_background_right\" android:state_checked=\"true\" />\n    <item android:drawable=\"@drawable/dk_radio_button_normal_background_right\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_radio_button_checked_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"rectangle\">\n    <solid android:color=\"@color/dk_color_337CC4\"/>\n    <stroke\n        android:width=\"1px\"\n        android:color=\"@color/dk_color_337CC4\"/>\n    <padding\n        android:bottom=\"1px\"\n        android:left=\"1px\"\n        android:right=\"1px\"\n        android:top=\"1px\"/>\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_radio_button_checked_background_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@color/dk_color_337CC4\" />\n    <stroke\n        android:width=\"1px\"\n        android:color=\"@color/dk_color_337CC4\" />\n    <padding\n        android:bottom=\"1px\"\n        android:left=\"1px\"\n        android:right=\"1px\"\n        android:top=\"1px\" />\n\n    <corners android:topLeftRadius=\"4dp\" android:bottomLeftRadius=\"4dp\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_radio_button_checked_background_middle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@color/dk_color_337CC4\" />\n    <stroke\n        android:width=\"1px\"\n        android:color=\"@color/dk_color_337CC4\" />\n    <padding\n        android:bottom=\"1px\"\n        android:left=\"1px\"\n        android:right=\"1px\"\n        android:top=\"1px\" />\n\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_radio_button_checked_background_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@color/dk_color_337CC4\" />\n    <stroke\n        android:width=\"1px\"\n        android:color=\"@color/dk_color_337CC4\" />\n    <padding\n        android:bottom=\"1px\"\n        android:left=\"1px\"\n        android:right=\"1px\"\n        android:top=\"1px\" />\n\n    <corners android:topRightRadius=\"4dp\" android:bottomRightRadius=\"4dp\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_radio_button_normal_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@android:color/transparent\" />\n    <stroke\n        android:width=\"1px\"\n        android:color=\"@color/dk_color_337CC4\" />\n    <padding\n        android:bottom=\"1px\"\n        android:left=\"1px\"\n        android:right=\"1px\"\n        android:top=\"1px\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_radio_button_normal_background_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@android:color/transparent\" />\n    <stroke\n        android:width=\"1px\"\n        android:color=\"@color/dk_color_337CC4\" />\n    <padding\n        android:bottom=\"1px\"\n        android:left=\"1px\"\n        android:right=\"1px\"\n        android:top=\"1px\" />\n\n    <corners android:topLeftRadius=\"4dp\" android:bottomLeftRadius=\"4dp\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_radio_button_normal_background_middle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@android:color/transparent\" />\n    <stroke\n        android:width=\"1px\"\n        android:color=\"@color/dk_color_337CC4\" />\n    <padding\n        android:bottom=\"1px\"\n        android:left=\"1px\"\n        android:right=\"1px\"\n        android:top=\"1px\" />\n\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_radio_button_normal_background_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@android:color/transparent\" />\n    <stroke\n        android:width=\"1px\"\n        android:color=\"@color/dk_color_337CC4\" />\n    <padding\n        android:bottom=\"1px\"\n        android:left=\"1px\"\n        android:right=\"1px\"\n        android:top=\"1px\" />\n\n    <corners android:topRightRadius=\"4dp\" android:bottomRightRadius=\"4dp\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_refresh_rotate_down.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rotate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"150\"\n    android:fillAfter=\"true\"\n    android:fromDegrees=\"180\"\n    android:interpolator=\"@android:anim/linear_interpolator\"\n    android:pivotX=\"50%\"\n    android:pivotY=\"50%\"\n    android:repeatCount=\"0\"\n    android:toDegrees=\"0\" />\n"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_seekbar_style.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:id=\"@android:id/background\">\n        <shape>\n            <solid android:color=\"#707070\"/>\n            <size android:height=\"5dp\" />\n        </shape>\n    </item>\n    <item android:id=\"@android:id/progress\">\n        <clip>\n            <shape>\n                <solid android:color=\"#FE2D4A\"/>\n                <size android:height=\"5dp\" />\n            </shape>\n        </clip>\n\n    </item>\n</layer-list>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_setting_item_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@color/dk_color_7AE5E5E5\" android:state_pressed=\"true\" />\n    <item android:drawable=\"@color/dk_color_FFFFFF\" />\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_shape_float_view_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"#ffffff\" />\n    <corners android:radius=\"5dp\" />\n    <padding\n        android:bottom=\"1px\"\n        android:left=\"1px\"\n        android:right=\"1px\"\n        android:top=\"1px\" />\n</shape>\n"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_switch_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@mipmap/dk_checkbox_checked\" android:state_checked=\"true\" />\n    <item android:drawable=\"@mipmap/dk_checkbox_unchecked\" />\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_time_counter_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"#aaffffff\" />\n    <corners android:radius=\"4dp\" />\n    <stroke android:color=\"@color/dk_color_CCCCCC\" android:width=\"1px\" />\n</shape>"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_view_dot_connect.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <corners android:radius=\"7dp\" />\n    <solid android:color=\"@color/dk_color_48BB31\" />\n\n</shape>\n"
  },
  {
    "path": "Android/dokit/src/main/res/drawable/dk_web_door_history_item_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@color/dk_color_7AE5E5E5\" android:state_pressed=\"true\" />\n    <item android:drawable=\"@color/dk_color_FFFFFF\" />\n</selector>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_app_toast.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:gravity=\"center\"\n    android:paddingLeft=\"15dp\"\n    android:paddingBottom=\"15dp\"\n    android:paddingRight=\"15dp\">\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/dk_app_toast_shape\"\n        android:fitsSystemWindows=\"true\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/app_toast_text\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"15dp\"\n            android:gravity=\"center\"\n            android:textColor=\"#ffffff\"\n            android:textSize=\"15sp\"/>\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_brvah_quick_view_load_more.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/dk_dp_40\">\n\n    <LinearLayout\n        android:id=\"@+id/load_more_loading_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"center\"\n        android:orientation=\"horizontal\">\n\n        <ProgressBar\n            android:id=\"@+id/loading_progress\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            style=\"?android:attr/progressBarStyleSmall\"\n            android:layout_marginRight=\"@dimen/dk_dp_4\"/>\n\n        <TextView\n            android:id=\"@+id/loading_text\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"@dimen/dk_dp_4\"\n            android:text=\"@string/dk_brvah_loading\"\n            android:textColor=\"@android:color/black\"\n            android:textSize=\"@dimen/dk_sp_14\"/>\n    </LinearLayout>\n\n    <FrameLayout\n        android:id=\"@+id/load_more_load_fail_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:visibility=\"gone\">\n\n\n        <TextView\n            android:id=\"@+id/tv_prompt\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"@string/dk_brvah_load_failed\"/>\n\n    </FrameLayout>\n\n    <FrameLayout\n        android:id=\"@+id/load_more_load_complete_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:visibility=\"gone\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"@string/dk_brvah_load_complete\"\n            android:textColor=\"@android:color/darker_gray\"/>\n    </FrameLayout>\n\n    <FrameLayout\n        android:id=\"@+id/load_more_load_end_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:visibility=\"gone\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"@string/dk_brvah_load_end\"\n            android:textColor=\"@android:color/darker_gray\"/>\n    </FrameLayout>\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_dialog_common.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <LinearLayout\n        android:layout_width=\"200dp\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/dk_color_FFFFFF\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/title\"\n            style=\"@style/DK.TextBig.Dark\"\n            android:layout_gravity=\"center_horizontal\"\n            android:layout_marginTop=\"30dp\" />\n\n        <TextView\n            android:id=\"@+id/desc\"\n            style=\"@style/DK.Text.Gray\"\n            android:layout_gravity=\"center_horizontal\"\n            android:layout_marginTop=\"10dp\" />\n\n        <View\n            style=\"@style/DK.Divider\"\n            android:layout_marginTop=\"30dp\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"60dp\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/negative\"\n                style=\"@style/DK.Text\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_weight=\"1\"\n                android:background=\"@drawable/dk_dialog_button_background\"\n                android:text=\"@string/dk_cancel\" />\n\n            <View style=\"@style/DK.Divider.Vertical\" />\n\n            <TextView\n                android:id=\"@+id/positive\"\n                style=\"@style/DK.Text.Blue\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_weight=\"1\"\n                android:background=\"@drawable/dk_dialog_button_background\"\n                android:text=\"@string/dk_confirm\" />\n\n        </LinearLayout>\n\n    </LinearLayout>\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_dialog_confirm.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <LinearLayout\n        android:layout_width=\"280dp\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/dk_dialog_confirm_bg\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:layout_marginTop=\"30dp\"\n            android:text=\"@string/dk_hint\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"21sp\"\n            android:textStyle=\"bold\" />\n\n        <TextView\n            android:id=\"@+id/tv_content\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:layout_marginTop=\"5dp\"\n            android:layout_marginBottom=\"15dp\"\n            android:textColor=\"@color/dk_color_333333\"\n            tools:text=\"是否保存已编辑的内容\" />\n\n        <View style=\"@style/DK.Divider\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"48dp\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/negative\"\n                style=\"@style/DK.Text.Blue\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_weight=\"1\"\n                android:text=\"@string/dk_cancel\" />\n\n\n            <View style=\"@style/DK.Divider.Vertical\" />\n\n            <TextView\n                android:id=\"@+id/positive\"\n                style=\"@style/DK.Text.Blue\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_weight=\"1\"\n                android:text=\"@string/dk_confirm\" />\n\n        </LinearLayout>\n\n    </LinearLayout>\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_dialog_file_explorer_choose.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/choose_list\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"/>\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_dialog_tip.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <RelativeLayout\n        android:layout_width=\"135dp\"\n        android:layout_height=\"124dp\"\n        android:background=\"@drawable/dk_dialog_complete_bg\">\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerInParent=\"true\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\">\n\n            <ImageView\n                android:layout_width=\"45dp\"\n                android:layout_height=\"45dp\"\n                android:src=\"@mipmap/dk_icon_complete\" />\n\n            <TextView\n                android:id=\"@+id/tv_tip\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"12dp\"\n                android:text=\"@string/dk_toolpanel_save_complete\"\n                android:textColor=\"@color/dk_color_666666\"\n                android:textSize=\"13sp\" />\n        </LinearLayout>\n\n    </RelativeLayout>\n</FrameLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_dialog_userinfo.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <LinearLayout\n        android:layout_width=\"280dp\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/dk_color_FFFFFF\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:layout_marginTop=\"30dp\"\n            android:text=\"@string/dk_health_dialog_title\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"21sp\"\n            android:textStyle=\"bold\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"16dp\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"20dp\"\n            android:paddingRight=\"20dp\">\n\n            <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/dk_health_dialog_test_name\"\n                android:textColor=\"@color/dk_color_BEBEBE\"\n                android:textSize=\"14sp\" />\n\n            <EditText\n                android:id=\"@+id/edit_case_name\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"@dimen/dk_dp_40\"\n                android:layout_marginTop=\"@dimen/dk_dp_5\"\n                android:background=\"@drawable/dk_health_edittext_style\"\n                android:hint=\"@string/dk_health_dialog_test_name_hint\"\n                android:paddingLeft=\"6dp\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"14sp\" />\n\n\n            <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/dk_dp_16\"\n                android:text=\"@string/dk_health_dialog_test_person\"\n                android:textColor=\"@color/dk_color_BEBEBE\"\n                android:textSize=\"14sp\" />\n\n            <EditText\n                android:id=\"@+id/edit_user_name\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"@dimen/dk_dp_40\"\n                android:layout_marginTop=\"@dimen/dk_dp_5\"\n                android:background=\"@drawable/dk_health_edittext_style\"\n                android:hint=\"@string/dk_health_dialog_test_person_hint\"\n                android:paddingLeft=\"6dp\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"14sp\" />\n        </LinearLayout>\n\n        <View\n            style=\"@style/DK.Divider\"\n            android:layout_marginTop=\"20dp\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"60dp\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/negative\"\n                style=\"@style/DK.Text\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_weight=\"1\"\n                android:background=\"@drawable/dk_dialog_button_background\"\n                android:text=\"@string/dk_cancel\" />\n\n            <View style=\"@style/DK.Divider.Vertical\" />\n\n            <TextView\n                android:id=\"@+id/close\"\n                style=\"@style/DK.Text\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_weight=\"1\"\n                android:background=\"@drawable/dk_dialog_button_background\"\n                android:text=\"@string/dk_discard\" />\n\n            <View style=\"@style/DK.Divider.Vertical\" />\n\n            <TextView\n                android:id=\"@+id/positive\"\n                style=\"@style/DK.Text.Blue\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_weight=\"1\"\n                android:background=\"@drawable/dk_dialog_button_background\"\n                android:text=\"@string/dk_post\" />\n\n        </LinearLayout>\n\n    </LinearLayout>\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_dropdownmenu_tab_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_weight=\"1\"\n    android:orientation=\"vertical\"\n    android:gravity=\"center\">\n    <TextView\n        android:id=\"@+id/tv_tab\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:drawablePadding=\"7dp\"\n        android:gravity=\"center\"\n        android:paddingBottom=\"12dp\"\n        android:paddingLeft=\"5dp\"\n        android:paddingRight=\"5dp\"\n        android:paddingTop=\"12dp\"\n        android:textColor=\"#26a8e0\" />\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_align_ruler_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"15dp\"\n        android:layout_marginRight=\"15dp\"\n        android:background=\"@drawable/dk_info_background\"\n        android:orientation=\"vertical\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"50dp\"\n\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/align_hex\"\n                style=\"@style/DK.Text.Darker\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_marginLeft=\"10dp\"\n                android:layout_marginRight=\"10dp\"\n                android:layout_weight=\"1\"\n                android:gravity=\"left\" />\n\n            <ImageView\n                android:id=\"@+id/close\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_marginRight=\"15dp\"\n                android:src=\"@mipmap/dk_close_icon\" />\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"50dp\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/tv_status_bar_switch\"\n                style=\"@style/DK.Text.Darker\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_marginLeft=\"10dp\"\n                android:layout_marginRight=\"10dp\"\n                android:layout_weight=\"1\"\n                android:gravity=\"left\"\n                android:text=\"@string/dk_align_info_include_status_bar\" />\n\n            <CheckBox\n                android:id=\"@+id/cb_status_bar\"\n                style=\"@style/DK.CheckBox\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_marginRight=\"@dimen/dk_dp_5\" />\n        </LinearLayout>\n    </LinearLayout>\n\n</FrameLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_align_ruler_line.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.didichuxing.doraemonkit.kit.alignruler.AlignLineView\n        android:id=\"@+id/info_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_align_ruler_marker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <com.didichuxing.doraemonkit.kit.viewcheck.AimCircleView\n        android:id=\"@+id/ruler_marker\"\n        android:layout_width=\"60dp\"\n        android:layout_height=\"60dp\" />\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_color_picker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <com.didichuxing.doraemonkit.kit.colorpick.ColorPickerView\n        android:id=\"@+id/picker_view\"\n        android:layout_width=\"128dp\"\n        android:layout_height=\"128dp\"\n        android:layout_gravity=\"center\" />\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_color_picker_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:layout_marginLeft=\"15dp\"\n        android:layout_marginRight=\"15dp\"\n        android:background=\"@drawable/dk_info_background\"\n        android:orientation=\"horizontal\">\n\n        <ImageView\n            android:id=\"@+id/color\"\n            android:layout_width=\"14dp\"\n            android:layout_height=\"14dp\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginLeft=\"16dp\"\n            android:src=\"@color/dk_color_333333\" />\n\n        <TextView\n            android:id=\"@+id/color_hex\"\n            style=\"@style/DK.Text.Darker\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_marginRight=\"10dp\"\n            android:layout_weight=\"1\"\n            android:gravity=\"left\" />\n\n        <ImageView\n            android:id=\"@+id/close\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginRight=\"15dp\"\n            android:src=\"@mipmap/dk_close_icon\" />\n    </LinearLayout>\n</FrameLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_count_down.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n\n    <ProgressBar\n        android:id=\"@+id/circle_progress_bar\"\n        android:layout_width=\"70dp\"\n        android:layout_height=\"70dp\"\n        android:layout_gravity=\"center_horizontal\"\n        android:indeterminateDrawable=\"@drawable/dk_progressbar_style\" />\n\n    <TextView\n        android:id=\"@+id/tv_number\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:text=\"10\"\n        android:textColor=\"#3CBCA3\"\n        android:textSize=\"28sp\"\n        android:textStyle=\"bold\" />\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_h5_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/dk_info_background\"\n    android:paddingLeft=\"@dimen/dk_dp_15\"\n    android:paddingTop=\"5dp\"\n    android:paddingRight=\"@dimen/dk_dp_15\"\n    android:paddingBottom=\"5dp\">\n\n    <TextView\n        android:id=\"@+id/tv_title\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_centerHorizontal=\"true\"\n        android:text=\"@string/dk_kit_h5_help\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"16sp\"\n        android:textStyle=\"bold\" />\n\n    <ImageView\n        android:id=\"@+id/iv_close\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentRight=\"true\"\n        android:src=\"@mipmap/dk_close_icon\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/tv_title\"\n        android:orientation=\"vertical\">\n        <!--  url detail-->\n        <LinearLayout\n            android:id=\"@+id/ll_url\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"@dimen/dk_dp_10\"\n            android:layout_marginBottom=\"@dimen/dk_dp_10\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                tools:text=\"url:\" />\n\n            <TextView\n                android:id=\"@+id/tv_link\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                tools:text=\"我是h5悬浮窗\" />\n\n        </LinearLayout>\n\n        <View style=\"@style/DK.Divider\" />\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"tips:js hook和vConsole的代码均在页面加载时注入,状态改变时需要重新加载页面才能生效\"\n            android:textColor=\"@color/dk_color_FF0006\"\n            android:textSize=\"10sp\" />\n        <!-- js hook switch-->\n        <RelativeLayout\n            android:id=\"@+id/rl_js_switch\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"@dimen/dk_dp_10\"\n            android:layout_marginBottom=\"@dimen/dk_dp_10\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_alignParentLeft=\"true\"\n                android:text=\"js hook switch\"\n                android:textSize=\"14sp\"\n                android:textStyle=\"bold\" />\n\n            <CheckBox\n                android:id=\"@+id/js_switch\"\n                style=\"@style/DK.CheckBox\"\n                android:layout_alignParentRight=\"true\"\n                android:layout_centerVertical=\"true\" />\n        </RelativeLayout>\n\n        <View style=\"@style/DK.Divider\" />\n        <!-- vConsole hook switch-->\n        <RelativeLayout\n            android:id=\"@+id/rl_vconsole_switch\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/rl_js_switch\"\n            android:layout_marginTop=\"@dimen/dk_dp_10\"\n            android:layout_marginBottom=\"@dimen/dk_dp_10\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_alignParentLeft=\"true\"\n                android:text=\"vConsole  switch\"\n                android:textSize=\"14sp\"\n                android:textStyle=\"bold\" />\n\n            <CheckBox\n                android:id=\"@+id/vConsole_switch\"\n                style=\"@style/DK.CheckBox\"\n                android:layout_alignParentRight=\"true\"\n                android:layout_centerVertical=\"true\" />\n        </RelativeLayout>\n\n        <View style=\"@style/DK.Divider\" />\n\n        <!-- vConsole hook switch-->\n        <RelativeLayout\n            android:id=\"@+id/rl_dokitjs_switch\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/rl_js_switch\"\n            android:layout_marginTop=\"@dimen/dk_dp_10\"\n            android:layout_marginBottom=\"@dimen/dk_dp_10\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_alignParentLeft=\"true\"\n                android:text=\"dokitjs  switch\"\n                android:textSize=\"14sp\"\n                android:textStyle=\"bold\" />\n\n            <CheckBox\n                android:id=\"@+id/dokitjs_switch\"\n                style=\"@style/DK.CheckBox\"\n                android:layout_alignParentRight=\"true\"\n                android:layout_centerVertical=\"true\" />\n        </RelativeLayout>\n\n        <View style=\"@style/DK.Divider\" />\n\n        <Button\n            android:id=\"@+id/btn_reload\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"30dp\"\n            android:background=\"@drawable/dk_item_btn_img\"\n            android:text=\"Reload\"\n            android:textColor=\"@color/dk_color_FFFFFF\"\n\n            />\n\n        <RelativeLayout\n            android:id=\"@+id/rl_more_wrap\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"10dp\">\n\n            <LinearLayout\n                android:id=\"@+id/ll_rv_wrap\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\"\n                android:visibility=\"gone\">\n\n                <!--nav-->\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"5dp\"\n                    android:orientation=\"horizontal\">\n\n                    <TextView\n                        android:id=\"@+id/tv_nav_local\"\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_weight=\"1\"\n                        android:text=\"LocalStorage\"\n                        android:textSize=\"14sp\"\n                        android:textStyle=\"bold\" />\n\n                    <TextView\n                        android:id=\"@+id/tv_nav_session\"\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginBottom=\"5dp\"\n                        android:layout_weight=\"1\"\n                        android:text=\"SessionStorage\"\n                        android:textSize=\"14sp\"\n                        android:textStyle=\"bold\" />\n                </LinearLayout>\n\n                <View style=\"@style/DK.Divider\" />\n                <!--storage detail-->\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:orientation=\"vertical\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"30dp\"\n                        android:orientation=\"horizontal\"\n\n                        >\n\n                        <TextView\n                            android:layout_width=\"0dp\"\n                            android:layout_height=\"match_parent\"\n                            android:layout_weight=\"1\"\n                            android:gravity=\"center\"\n                            android:text=\"KEY\"\n                            android:textSize=\"14sp\"\n                            android:textStyle=\"bold\" />\n\n                        <View style=\"@style/DK.Divider.Vertical\" />\n\n                        <TextView\n                            android:layout_width=\"0dp\"\n                            android:layout_height=\"match_parent\"\n                            android:layout_weight=\"1\"\n                            android:gravity=\"center\"\n                            android:text=\"VALUE\"\n                            android:textSize=\"14sp\"\n                            android:textStyle=\"bold\" />\n                    </LinearLayout>\n\n                    <View style=\"@style/DK.Divider\" />\n                </LinearLayout>\n\n                <FrameLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"300dp\">\n\n                    <androidx.recyclerview.widget.RecyclerView\n                        android:id=\"@+id/rv_localStorage\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"300dp\" />\n\n                    <androidx.recyclerview.widget.RecyclerView\n                        android:id=\"@+id/rv_sessionStorage\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"300dp\" />\n                </FrameLayout>\n\n            </LinearLayout>\n\n            <TextView\n                android:id=\"@+id/tv_holder\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_below=\"@id/ll_rv_wrap\"\n                android:layout_alignParentRight=\"true\"\n                android:padding=\"10dp\"\n                android:text=\"更多\"\n                android:textColor=\"@color/dk_color_55A8FD\"\n                android:textSize=\"14sp\" />\n        </RelativeLayout>\n    </LinearLayout>\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_layout_level.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"80dp\"\n        android:layout_marginLeft=\"15dp\"\n        android:layout_marginRight=\"15dp\"\n        android:background=\"@drawable/dk_info_background\">\n        <TextView\n            style=\"@style/DK.Text.Darker\"\n            android:id=\"@+id/switch_text\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginRight=\"20dp\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_alignParentLeft=\"true\"\n            android:text=\"@string/dk_layout_level\" />\n        <CheckBox\n            style=\"@style/DK.CheckBox\"\n            android:id=\"@+id/switch_btn\"\n            android:layout_centerVertical=\"true\"\n            android:layout_toRightOf=\"@+id/switch_text\"/>\n\n        <ImageView\n            android:id=\"@+id/close\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_marginRight=\"16dp\"\n            android:layout_centerVertical=\"true\"\n            android:src=\"@mipmap/dk_close_icon\" />\n\n    </RelativeLayout>\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_lbs_route.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/dk_info_background\"\n    android:paddingLeft=\"@dimen/dk_dp_15\"\n    android:paddingTop=\"5dp\"\n    android:paddingRight=\"@dimen/dk_dp_15\"\n    android:paddingBottom=\"5dp\">\n\n    <TextView\n        android:id=\"@+id/tv_title\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_centerHorizontal=\"true\"\n        android:text=\"@string/dk_kit_gps_mock_route\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"16sp\"\n        android:textStyle=\"bold\" />\n\n    <TextView\n        android:id=\"@+id/tv_tip0\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/tv_title\"\n        android:layout_centerHorizontal=\"true\"\n        android:text=\"当前只支持高德地图\\n实时导航功能需要依赖于位置模拟\"\n        android:textColor=\"#FF0000\"\n        android:gravity=\"center\"\n        android:textSize=\"12sp\" />\n\n    <ImageView\n        android:id=\"@+id/iv_close\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentRight=\"true\"\n        android:src=\"@mipmap/dk_close_icon\" />\n\n\n    <TextView\n        android:id=\"@+id/tv_progress\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/tv_tip0\"\n        android:text=\"当前导航进度:\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/tv_progress\"\n        android:layout_marginBottom=\"10dp\">\n\n        <SeekBar\n            android:id=\"@+id/seekbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <TextView\n            android:id=\"@+id/tv_tip\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"当前页面不存在导航路径\"\n            android:textColor=\"#FF0000\"\n            android:textSize=\"16sp\"\n            android:textStyle=\"bold\"\n            android:visibility=\"gone\" />\n    </FrameLayout>\n\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_log_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <RelativeLayout\n        android:id=\"@+id/log_page\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@color/dk_color_FFFFFF\"\n        android:orientation=\"vertical\">\n\n        <com.didichuxing.doraemonkit.widget.titlebar.LogTitleBar\n            android:id=\"@id/dokit_title_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"68dp\"\n            android:layout_alignParentTop=\"true\"\n            android:background=\"@color/foreground_wtf\"\n            app:dkBack=\"@string/dk_kit_log_min\"\n            app:dkIcon=\"@mipmap/dk_close_icon\"\n            app:dkTitle=\"@string/dk_kit_log_info\" />\n\n        <View\n            android:id=\"@+id/view_divider\"\n            style=\"@style/DK.Shadow.Bottom\"\n            android:layout_below=\"@id/dokit_title_bar\" />\n\n        <EditText\n            android:id=\"@+id/log_filter\"\n            style=\"@style/DK.Input\"\n            android:layout_height=\"50dp\"\n            android:layout_below=\"@id/view_divider\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"15dp\"\n            android:layout_marginRight=\"16dp\"\n            android:background=\"@drawable/dk_log_filter_background\"\n            android:elevation=\"1dp\"\n            android:hint=\"@string/dk_log_info_edt_hint\"\n            android:inputType=\"text\"\n            android:paddingLeft=\"15dp\"\n            android:paddingRight=\"15dp\" />\n\n        <LinearLayout\n            android:id=\"@+id/button_wrap\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/log_filter\"\n            android:layout_marginTop=\"16dp\"\n            android:layout_marginBottom=\"6dp\"\n            android:orientation=\"vertical\">\n\n\n            <RadioGroup\n                android:id=\"@+id/radio_group\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n\n                android:orientation=\"horizontal\">\n\n                <RadioButton\n                    android:id=\"@+id/verbose\"\n                    style=\"@style/DK.RadioButton.Left\"\n                    android:text=\"@string/dk_log_info_verbose\" />\n\n                <RadioButton\n                    android:id=\"@+id/debug\"\n                    style=\"@style/DK.RadioButton\"\n                    android:text=\"@string/dk_log_info_debug\" />\n\n                <RadioButton\n                    android:id=\"@+id/info\"\n                    style=\"@style/DK.RadioButton\"\n                    android:text=\"@string/dk_log_info_info\" />\n\n                <RadioButton\n                    android:id=\"@+id/warn\"\n                    style=\"@style/DK.RadioButton\"\n                    android:text=\"@string/dk_log_info_warn\" />\n\n                <RadioButton\n                    android:id=\"@+id/error\"\n                    style=\"@style/DK.RadioButton.Right\"\n                    android:text=\"@string/dk_log_info_error\" />\n\n            </RadioGroup>\n\n            <LinearLayout\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"5dp\"\n                android:orientation=\"horizontal\">\n\n                <Button\n                    android:id=\"@+id/btn_clean\"\n                    style=\"@style/DK.RadioButton.Left\"\n                    android:text=\"@string/dk_log_btn_clean\" />\n\n                <Button\n                    android:id=\"@+id/btn_export\"\n                    style=\"@style/DK.RadioButton.middle\"\n                    android:text=\"@string/dk_log_btn_export\" />\n\n                <Button\n                    android:id=\"@+id/btn_top\"\n                    style=\"@style/DK.RadioButton.middle\"\n                    android:text=\"@string/dk_log_btn_back_top\" />\n\n                <Button\n                    android:id=\"@+id/btn_bottom\"\n                    style=\"@style/DK.RadioButton.Right\"\n                    android:text=\"@string/dk_log_btn_to_bottom\" />\n            </LinearLayout>\n\n        </LinearLayout>\n\n\n        <LinearLayout\n            android:id=\"@+id/ll_loading\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentBottom=\"true\"\n            android:gravity=\"center\"\n            android:orientation=\"horizontal\">\n\n            <ProgressBar\n                style=\"@android:style/Widget.ProgressBar.Small\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"3dp\"\n                android:text=\"@string/dk_log_text_loading\"\n                android:textColor=\"@color/dk_color_666666\"\n                android:textSize=\"@dimen/dk_font_size_14\" />\n        </LinearLayout>\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/log_list\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_above=\"@id/ll_loading\"\n            android:layout_below=\"@id/button_wrap\"\n            android:scrollbars=\"vertical\"\n            android:visibility=\"gone\" />\n    </RelativeLayout>\n\n    <TextView\n        android:id=\"@+id/log_hint\"\n        style=\"@style/DK.Text.White\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"30dp\"\n        android:background=\"@color/dk_color_337CC4\"\n        android:singleLine=\"true\"\n        android:text=\"@string/dk_kit_log_info\"\n        android:visibility=\"gone\" />\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_network.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/dk_network_info_background\"\n        android:orientation=\"vertical\"\n        android:padding=\"10dp\">\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/dk_weaknet_type\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"16sp\" />\n\n            <TextView\n                android:id=\"@+id/tv_net_type\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"off\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"16sp\" />\n        </LinearLayout>\n\n\n        <LinearLayout\n            android:id=\"@+id/ll_timeout_wrap\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"10dp\"\n            android:orientation=\"horizontal\"\n            android:visibility=\"gone\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"timeout:\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"16sp\" />\n\n            <TextView\n                android:id=\"@+id/tv_time\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"2000ms\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"16sp\" />\n        </LinearLayout>\n\n        <LinearLayout\n            android:id=\"@+id/ll_speed_wrap\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"10dp\"\n            android:orientation=\"vertical\"\n            android:visibility=\"gone\">\n\n            <LinearLayout\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <TextView\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"request speed:\"\n                    android:textColor=\"@color/dk_color_333333\"\n                    android:textSize=\"16sp\" />\n\n                <TextView\n                    android:id=\"@+id/tv_request_speed\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"10 K/s\"\n                    android:textColor=\"@color/dk_color_333333\"\n                    android:textSize=\"16sp\" />\n            </LinearLayout>\n\n\n            <LinearLayout\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"10dp\"\n                android:orientation=\"horizontal\">\n\n                <TextView\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"response speed:\"\n                    android:textColor=\"@color/dk_color_333333\"\n                    android:textSize=\"16sp\" />\n\n                <TextView\n                    android:id=\"@+id/tv_response_speed\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"10 K/s\"\n                    android:textColor=\"@color/dk_color_333333\"\n                    android:textSize=\"16sp\" />\n            </LinearLayout>\n\n        </LinearLayout>\n\n    </LinearLayout>\n\n    <ImageView\n        android:id=\"@+id/iv_close\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"right|top\"\n        android:src=\"@mipmap/dk_close_icon\" />\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_perform_data.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <FrameLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\">\n\n        <LinearLayout\n            android:layout_width=\"200dp\"\n            android:layout_height=\"100dp\"\n            android:background=\"@drawable/dk_perform_data_background\"\n            android:orientation=\"vertical\"\n            android:padding=\"10dp\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\">\n\n                <TextView\n                    android:id=\"@+id/memory_txt\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:textColor=\"#FFF\"\n                    android:textSize=\"@dimen/dk_font_size_12\" />\n\n                <TextView\n                    android:id=\"@+id/down_network_txt\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:textColor=\"#FFF\"\n                    android:textSize=\"@dimen/dk_font_size_12\" />\n            </LinearLayout>\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"10dp\"\n                android:layout_marginBottom=\"10dp\">\n\n                <TextView\n                    android:id=\"@+id/cpu_txt\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:textColor=\"#FFF\"\n                    android:textSize=\"@dimen/dk_font_size_12\" />\n\n                <TextView\n                    android:id=\"@+id/up_network_txt\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:textColor=\"#FFF\"\n                    android:textSize=\"@dimen/dk_font_size_12\" />\n            </LinearLayout>\n\n            <TextView\n                android:id=\"@+id/fps_txt\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textColor=\"#FFF\"\n                android:textSize=\"@dimen/dk_font_size_12\" />\n        </LinearLayout>\n\n        <ImageView\n            android:id=\"@+id/iv_close\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"right|top\"\n            android:layout_margin=\"@dimen/dk_dp_5\"\n            android:src=\"@mipmap/dk_close_white\" />\n\n    </FrameLayout>\n\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_time_counter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n             android:layout_width=\"wrap_content\"\n             android:layout_height=\"wrap_content\">\n\n    <FrameLayout\n        android:layout_width=\"200dp\"\n        android:layout_height=\"100dp\"\n        android:background=\"@drawable/dk_time_counter_background\"\n        android:orientation=\"horizontal\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"5dp\"\n            android:paddingRight=\"5dp\">\n\n            <TextView\n                android:id=\"@+id/title\"\n                style=\"@style/DK.TextSmall.Dark\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:ellipsize=\"end\"\n                android:gravity=\"left|center_vertical\"\n                android:singleLine=\"true\"\n                android:text=\"1233\"\n                />\n\n            <TextView\n                android:id=\"@+id/total_cost\"\n                style=\"@style/DK.TextSmall.Dark\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textSize=\"@dimen/dk_font_size_12\"/>\n\n            <TextView\n                android:id=\"@+id/pause_cost\"\n                style=\"@style/DK.TextSmall.Dark\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textSize=\"@dimen/dk_font_size_10\"/>\n\n            <TextView\n                android:id=\"@+id/launch_cost\"\n                style=\"@style/DK.TextSmall.Dark\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textSize=\"@dimen/dk_font_size_10\"/>\n\n            <TextView\n                android:id=\"@+id/render_cost\"\n                style=\"@style/DK.TextSmall.Dark\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textSize=\"@dimen/dk_font_size_10\"/>\n\n            <TextView\n                android:id=\"@+id/other_cost\"\n                style=\"@style/DK.TextSmall.Dark\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textSize=\"@dimen/dk_font_size_10\"/>\n\n        </LinearLayout>\n\n        <ImageView\n            android:id=\"@+id/close\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"right|top\"\n            android:layout_marginTop=\"5dp\"\n            android:layout_marginRight=\"5dp\"\n            android:src=\"@mipmap/dk_close_icon\"/>\n\n    </FrameLayout>\n</FrameLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_ui_performance_display.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.kit.viewcheck.LayoutBorderView\n        android:id=\"@+id/rect_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        app:dkFill=\"true\"/>\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_ui_performance_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <FrameLayout\n        android:layout_width=\"300dp\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/dk_float_ui_performance_info_bg\"\n        android:orientation=\"horizontal\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"15dp\"\n            android:paddingRight=\"15dp\">\n\n            <com.didichuxing.doraemonkit.widget.textview.LabelTextView\n                android:layout_marginTop=\"5dp\"\n                android:layout_marginBottom=\"5dp\"\n                android:id=\"@+id/max_level\"\n                app:dkLabel=\"@string/dk_max_level\"\n                android:layout_height=\"wrap_content\"\n                android:layout_width=\"wrap_content\" />\n\n            <com.didichuxing.doraemonkit.widget.textview.LabelTextView\n                android:layout_marginTop=\"5dp\"\n                android:layout_marginBottom=\"5dp\"\n                android:id=\"@+id/max_level_view_id\"\n                app:dkLabel=\"@string/dk_view_id\"\n                android:layout_height=\"wrap_content\"\n                android:layout_width=\"wrap_content\" />\n\n            <View\n                android:background=\"@color/dk_color_333333\"\n                style=\"@style/DK.Divider\" />\n\n            <com.didichuxing.doraemonkit.widget.textview.LabelTextView\n                android:layout_marginTop=\"5dp\"\n                android:layout_marginBottom=\"5dp\"\n                android:id=\"@+id/total_time\"\n                app:dkLabel=\"@string/dk_total_draw_time\"\n                android:layout_height=\"wrap_content\"\n                android:layout_width=\"wrap_content\" />\n\n            <com.didichuxing.doraemonkit.widget.textview.LabelTextView\n                android:layout_marginTop=\"5dp\"\n                android:layout_marginBottom=\"5dp\"\n                android:id=\"@+id/max_time\"\n                app:dkLabel=\"@string/dk_max_draw_time\"\n                android:layout_height=\"wrap_content\"\n                android:layout_width=\"wrap_content\" />\n\n            <com.didichuxing.doraemonkit.widget.textview.LabelTextView\n                android:layout_marginTop=\"5dp\"\n                android:layout_marginBottom=\"5dp\"\n                android:id=\"@+id/max_time_view_id\"\n                app:dkLabel=\"@string/dk_view_id\"\n                android:layout_height=\"wrap_content\"\n                android:layout_width=\"wrap_content\" />\n\n        </LinearLayout>\n\n        <ImageView\n            android:id=\"@+id/close\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"right|top\"\n            android:layout_marginTop=\"5dp\"\n            android:layout_marginRight=\"5dp\"\n            android:src=\"@mipmap/dk_close_icon\" />\n\n    </FrameLayout>\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_view_check.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.kit.viewcheck.AimCircleView\n        android:layout_width=\"60dp\"\n        android:layout_height=\"60dp\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_view_check_draw.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.kit.viewcheck.LayoutBorderView\n        android:id=\"@+id/rect_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_float_view_check_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n             android:layout_width=\"wrap_content\"\n             android:layout_height=\"wrap_content\">\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"15dp\"\n        android:layout_marginRight=\"15dp\"\n        android:background=\"@drawable/dk_info_background\">\n\n        <FrameLayout\n            android:id=\"@+id/fl_pre\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginLeft=\"16dp\">\n\n            <ImageView\n                android:id=\"@+id/pre\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:src=\"@mipmap/dk_pre_icon\"\n                android:layout_marginRight=\"12dp\"/>\n        </FrameLayout>\n\n        <FrameLayout\n            android:id=\"@+id/fl_next\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginRight=\"16dp\">\n\n            <ImageView\n                android:id=\"@+id/next\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:src=\"@mipmap/dk_next_icon\"\n                android:layout_marginLeft=\"12dp\"/>\n        </FrameLayout>\n\n        <TextView\n            android:id=\"@+id/name\"\n            style=\"@style/DK.Text.Darker\"\n            android:layout_toRightOf=\"@id/fl_pre\"\n            android:layout_toLeftOf=\"@id/fl_next\"\n            android:layout_marginTop=\"14dp\"\n            android:layout_marginBottom=\"6dp\"\n            android:gravity=\"left\"/>\n\n        <TextView\n            android:id=\"@+id/id\"\n            style=\"@style/DK.Text.Darker\"\n            android:layout_below=\"@id/name\"\n            android:layout_alignLeft=\"@id/name\"\n            android:layout_alignRight=\"@id/name\"\n            android:layout_marginBottom=\"6dp\"\n            android:gravity=\"left\" />\n\n        <TextView\n            android:id=\"@+id/position\"\n            style=\"@style/DK.Text.Darker\"\n            android:layout_below=\"@id/id\"\n            android:layout_alignLeft=\"@id/id\"\n            android:layout_alignRight=\"@id/id\"\n            android:layout_marginBottom=\"6dp\"\n            android:gravity=\"left\"/>\n\n        <TextView\n            android:id=\"@+id/desc\"\n            style=\"@style/DK.Text.Darker\"\n            android:layout_below=\"@id/position\"\n            android:layout_alignLeft=\"@id/position\"\n            android:layout_alignRight=\"@id/position\"\n            android:layout_marginBottom=\"6dp\"\n            android:lineSpacingExtra=\"7dp\"\n            android:gravity=\"left\"/>\n\n        <TextView\n            android:id=\"@+id/activity\"\n            style=\"@style/DK.Text.Darker\"\n            android:layout_below=\"@id/desc\"\n            android:layout_alignLeft=\"@id/desc\"\n            android:layout_alignRight=\"@id/desc\"\n            android:layout_marginBottom=\"6dp\"\n            android:gravity=\"left\"/>\n\n        <TextView\n            android:id=\"@+id/fragment\"\n            style=\"@style/DK.Text.Darker\"\n            android:layout_below=\"@id/activity\"\n            android:layout_alignLeft=\"@id/activity\"\n            android:layout_alignRight=\"@id/activity\"\n            android:layout_marginBottom=\"6dp\"\n            android:visibility=\"gone\"\n            android:gravity=\"left\"/>\n\n        <ImageView\n            android:id=\"@+id/close\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_marginTop=\"14dp\"\n            android:layout_marginRight=\"16dp\"\n            android:src=\"@mipmap/dk_close_icon\"/>\n\n    </RelativeLayout>\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_align_ruler_setting.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkTitle=\"@string/dk_kit_align_ruler\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"/>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/setting_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_app_start_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/dk_title_height\"\n        app:dkLeftIcon=\"@mipmap/title_back\"\n        app:dkLeftText=\"@string/dk_back\"\n        app:dkRightText=\"@string/dk_export\"\n        app:dkTitle=\"@string/dk_kit_block_time_app_start_info\" />\n\n    <View style=\"@style/DK.Shadow.Bottom\" />\n\n\n    <TextView\n        android:id=\"@+id/app_start_info\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_marginLeft=\"@dimen/dk_dp_16\"\n        android:layout_marginRight=\"@dimen/dk_dp_16\"\n        android:scrollbars=\"none\"\n        android:textIsSelectable=\"true\"\n        android:textSize=\"10dp\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_block_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/dk_title_height\"\n        app:dkLeftIcon=\"@mipmap/title_back\"\n        app:dkLeftText=\"@string/dk_back\"\n        app:dkTitle=\"@string/dk_kit_block_monitor_list\" />\n\n    <View style=\"@style/DK.Shadow.Bottom\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/block_list\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:overScrollMode=\"never\"\n            android:scrollbars=\"none\" />\n\n        <TextView\n            android:id=\"@+id/tx_block_detail\"\n            style=\"@style/DK.TextSmall.Dark\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_gravity=\"top|left\"\n            android:gravity=\"left\"\n            android:textIsSelectable=\"true\"\n            android:textSize=\"@dimen/dk_font_size_14\"\n            android:visibility=\"gone\" />\n    </FrameLayout>\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_block_monitor_index.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"match_parent\"\n              android:background=\"@color/dk_color_FFFFFF\"\n              android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_block_monitor\"/>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/setting_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"/>\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_color_picker_setting.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"0dp\"\n    android:orientation=\"vertical\">\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_comm_webview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_comm_webview\" />\n\n    <com.didichuxing.doraemonkit.widget.webview.MyWebView\n        android:id=\"@+id/webview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_cpu_cache_log.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_3f3f46\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"44dp\"\n        app:dkLeftIcon=\"@mipmap/title_back\"\n        app:dkLeftText=\"@string/dk_back\"\n        app:dkTitle=\"@string/dk_cpu_title_cache_log\" />\n\n    <include layout=\"@layout/dk_item_tips_view\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/data_show\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"198dp\"\n        android:layout_marginTop=\"15dp\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/data_detail\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_marginTop=\"15dp\" />\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_crash_capture_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n              android:layout_width=\"match_parent\"\n              android:background=\"@color/dk_color_FFFFFF\"\n              android:layout_height=\"match_parent\"\n              android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"44dp\"\n        app:dkLeftIcon=\"@mipmap/title_back\"\n        app:dkLeftText=\"@string/dk_back\"\n        app:dkTitle=\"@string/dk_crash_capture_summary_title\"/>\n\n    <View\n        style=\"@style/DK.Shadow.Bottom\"/>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:visibility=\"gone\"\n        android:id=\"@+id/crash_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n    <TextView\n        android:id=\"@+id/no_record_hint\"\n        android:text=\"@string/dk_crash_capture_no_record\"\n        style=\"@style/DK.TextBig.Dark\"\n        android:gravity=\"center\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>\n\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_crash_capture_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"match_parent\"\n              android:background=\"@color/dk_color_FFFFFF\"\n              android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_crash\"/>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/setting_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"/>\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_crash_detail_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n            android:id=\"@+id/title_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"44dp\"\n            app:dkLeftIcon=\"@mipmap/title_back\"\n            app:dkLeftText=\"@string/dk_back\"\n            app:dkTitle=\"@string/dk_crash_capture_summary_title\" />\n        <View\n            style=\"@style/DK.Shadow.Bottom\"/>\n\n        <TextView\n            android:id=\"@+id/tv_crash_detail\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:textColor=\"@color/dk_color_000000\"\n            android:textSize=\"@dimen/dk_font_size_14\" />\n    </LinearLayout>\n\n\n</ScrollView>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_data_clean.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        android:layout_alignParentTop=\"true\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_data_clean\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/setting_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/title_bar\" />\n\n    <ScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_below=\"@id/setting_list\"\n        android:scrollbars=\"vertical\">\n\n        <LinearLayout\n            android:id=\"@+id/item_wrap\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\" />\n    </ScrollView>\n\n    <Button\n        android:id=\"@+id/btn_clean\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/dk_dp_40\"\n        android:layout_alignParentBottom=\"true\"\n        android:background=\"@color/dk_color_0070BB\"\n        android:text=\"@string/dk_kit_data_clean\"\n        android:textColor=\"@color/dk_color_FFFFFF\"\n        android:textSize=\"18sp\" />\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_db_debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_db_debug\" />\n\n    <TextView\n        android:id=\"@+id/tv_tip\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"20dp\"\n        android:layout_marginRight=\"16dp\"\n        android:lineSpacingExtra=\"8dp\"\n        android:textSize=\"16sp\"\n        tools:text=\"@string/dk_kit_db_debug_desc\" />\n\n    <TextView\n        android:id=\"@+id/tv_ip\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginRight=\"16dp\"\n        android:textColor=\"@color/dk_color_FF0006\"\n        android:textIsSelectable=\"true\"\n        android:textSize=\"18sp\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_db_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"44dp\"\n        app:dkLeftIcon=\"@mipmap/title_back\"\n        app:dkLeftText=\"@string/dk_back\"\n        app:dkTitle=\"@string/dk_kit_db_detail\" />\n\n    <View style=\"@style/DK.Shadow.Bottom\" />\n\n    <ListView\n        android:id=\"@+id/lv_table_name\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n    <com.didichuxing.doraemonkit.widget.tableview.component.SmartTable\n        android:id=\"@+id/table\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_dokit_connect.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n\n        <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n            android:id=\"@+id/title_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"89dp\"\n            app:dkIcon=\"@mipmap/dk_close_icon_big\"\n            app:dkTitle=\"@string/dk_kit_dokit_for_web\" />\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/recyclerView\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_marginTop=\"15dp\"\n            android:layout_weight=\"100\" />\n\n        <Button\n            android:id=\"@+id/btn_add\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"56dp\"\n            android:layout_gravity=\"bottom\"\n            android:background=\"@drawable/dk_btn_dokit_for_web_bg\"\n            android:text=\"添加\"\n            android:textAllCaps=\"false\"\n            android:textColor=\"@color/dk_color_FFFFFF\"\n            android:textSize=\"18sp\"\n            android:textStyle=\"bold\" />\n    </LinearLayout>\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_dokit_for_web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n\n        <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n            android:id=\"@+id/title_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"89dp\"\n            app:dkIcon=\"@mipmap/dk_close_icon_big\"\n            app:dkTitle=\"@string/dk_kit_dokit_for_web\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"18sp\" />\n\n            <Switch\n                android:id=\"@+id/switch_btn\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"40dp\"\n                android:layout_marginLeft=\"16dp\"\n                android:layout_marginTop=\"40dp\"\n                android:layout_marginRight=\"16dp\"\n                android:text=\"是否开启Dokit For Web\"\n                android:textSize=\"20sp\" />\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"10dp\"\n            android:layout_marginRight=\"16dp\"\n            android:gravity=\"center_horizontal\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/label\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"服务器地址:\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"18sp\" />\n\n            <EditText\n                android:id=\"@+id/et_h5_url\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:lines=\"2\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\"\n                tools:text=\"ws://192.168.0.1:88888\" />\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"16dp\"\n            android:layout_marginRight=\"16dp\">\n\n            <RadioGroup\n                android:id=\"@+id/tv_h5jsInject\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\">\n\n                <RadioButton\n                    android:id=\"@+id/h5_file\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"使用默认文件注入\" />\n\n                <RadioButton\n                    android:id=\"@+id/h5_url\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"使用网络地址注入\" />\n\n            </RadioGroup>\n        </LinearLayout>\n\n        <Button\n            android:id=\"@+id/btn_ok\"\n            android:layout_width=\"150dp\"\n            android:layout_height=\"50dp\"\n            android:layout_gravity=\"center_horizontal\"\n            android:layout_marginTop=\"30dp\"\n            android:background=\"@drawable/dk_btn_dokit_for_web_bg\"\n            android:text=\"保存\"\n            android:textAllCaps=\"false\"\n            android:textColor=\"@color/dk_color_FFFFFF\"\n            android:textSize=\"18sp\"\n            android:textStyle=\"bold\" />\n    </LinearLayout>\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_file_explorer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"44dp\"\n        app:dkLeftIcon=\"@mipmap/title_back\"\n        app:dkLeftText=\"@string/dk_back\"\n        app:dkTitle=\"@string/dk_kit_file_explorer\" />\n\n    <View\n        style=\"@style/DK.Shadow.Bottom\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/file_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_file_manager_doc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_file_transfer\" />\n\n    <com.didichuxing.doraemonkit.widget.webview.MyWebView\n        android:id=\"@+id/webview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_health.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_health\" />\n\n    <com.didichuxing.doraemonkit.widget.verticalviewpager.VerticalViewPager\n        android:id=\"@+id/view_pager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_below=\"@id/title_bar\" />\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_health_child0.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <FrameLayout\n        android:layout_width=\"260dp\"\n        android:layout_height=\"435dp\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginTop=\"20dp\">\n\n        <ImageView\n            android:layout_width=\"260dp\"\n            android:layout_height=\"match_parent\"\n            android:src=\"@mipmap/dk_health_bg\" />\n\n        <ImageView\n            android:id=\"@+id/iv_btn\"\n            android:layout_width=\"100dp\"\n            android:layout_height=\"100dp\"\n            android:layout_gravity=\"center_horizontal|bottom\"\n            android:layout_marginBottom=\"80dp\"\n            android:layout_marginLeft=\"3dp\"\n            android:src=\"@mipmap/dk_health_start\" />\n\n        <TextView\n            android:id=\"@+id/tv_title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:layout_marginTop=\"80dp\"\n            android:text=\"@string/dk_health_funcation_running\"\n            android:textColor=\"#27BCB7\"\n            android:textSize=\"18sp\" />\n    </FrameLayout>\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginBottom=\"20dp\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"10dp\"\n            android:text=\"@string/dk_health_step_index\"\n            android:textColor=\"#27BCB7\"\n            android:textSize=\"16sp\" />\n\n        <ImageView\n            android:layout_width=\"30dp\"\n            android:layout_height=\"30dp\"\n            android:layout_gravity=\"center_horizontal\"\n            android:src=\"@mipmap/dk_health_next_page\" />\n    </LinearLayout>\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_health_child1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"20dp\"\n        android:orientation=\"vertical\"\n        android:paddingLeft=\"16dp\"\n        android:paddingRight=\"16dp\">\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"20dp\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                android:layout_width=\"80dp\"\n                android:layout_height=\"36dp\"\n                android:background=\"@mipmap/dk_health_title_bg\"\n                android:gravity=\"center\"\n                android:text=\"@string/dk_health_title_step1\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"16sp\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"12dp\"\n                android:text=\"@string/dk_health_step1\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"14sp\" />\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"20dp\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                android:layout_width=\"80dp\"\n                android:layout_height=\"36dp\"\n                android:background=\"@mipmap/dk_health_title_bg\"\n                android:gravity=\"center\"\n                android:text=\"@string/dk_health_title_step2\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"16sp\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"12dp\"\n                android:text=\"@string/dk_health_step2\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"14sp\" />\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"20dp\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                android:layout_width=\"80dp\"\n                android:layout_height=\"36dp\"\n                android:background=\"@mipmap/dk_health_title_bg\"\n                android:gravity=\"center\"\n                android:text=\"@string/dk_health_title_step3\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"16sp\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"12dp\"\n                android:text=\"@string/dk_health_step2\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"14sp\" />\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"20dp\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                android:layout_width=\"80dp\"\n                android:layout_height=\"36dp\"\n                android:background=\"@mipmap/dk_health_title_bg\"\n                android:gravity=\"center\"\n                android:text=\"@string/dk_health_title_step4\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"16sp\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"12dp\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:text=\"@string/dk_health_step4\"\n                android:textSize=\"14sp\" />\n        </LinearLayout>\n    </LinearLayout>\n\n    <LinearLayout\n        android:id=\"@+id/ll_back_top\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginBottom=\"20dp\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"10dp\"\n            android:text=\"@string/dk_health_back_top\"\n            android:textColor=\"#27BCB7\"\n            android:textSize=\"16sp\" />\n\n        <ImageView\n            android:layout_width=\"30dp\"\n            android:layout_height=\"30dp\"\n            android:layout_gravity=\"center_horizontal\"\n            android:rotation=\"180\"\n            android:src=\"@mipmap/dk_health_next_page\" />\n    </LinearLayout>\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_image_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"44dp\" />\n\n    <View style=\"@style/DK.Shadow.Bottom\" />\n\n    <ImageView\n        android:id=\"@+id/image\"\n        android:scaleType=\"centerInside\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_kit_manager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n\n    <RelativeLayout\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"46dp\"\n        android:paddingLeft=\"16dp\"\n        android:paddingRight=\"16dp\">\n\n        <LinearLayout\n            android:id=\"@+id/ll_back\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerVertical=\"true\"\n            android:orientation=\"horizontal\">\n\n            <ImageView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:src=\"@mipmap/title_back\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/dk_back\"\n                android:textColor=\"@color/dk_color_337CC4\"\n                android:textSize=\"16sp\" />\n        </LinearLayout>\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerInParent=\"true\"\n            android:text=\"@string/dk_manager_kit_title\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"18sp\"\n            android:textStyle=\"bold\" />\n\n        <TextView\n            android:id=\"@+id/tv_edit\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:text=\"@string/dk_edit\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"16sp\" />\n\n        <TextView\n            android:id=\"@+id/tv_reset\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginRight=\"10dp\"\n            android:layout_toLeftOf=\"@id/tv_edit\"\n            android:text=\"@string/dk_reset\"\n            android:textColor=\"@color/dk_color_337CC4\"\n            android:textSize=\"16sp\"\n            android:visibility=\"gone\" />\n    </RelativeLayout>\n\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv_kits\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_below=\"@id/title_bar\" />\n\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_large_img_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"match_parent\"\n              android:background=\"@color/dk_color_FFFFFF\"\n              android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/dk_title_height\"\n        app:dkLeftIcon=\"@mipmap/title_back\"\n        app:dkLeftText=\"@string/dk_back\"\n        app:dkTitle=\"@string/dk_large_picture_list\"/>\n\n    <View\n        style=\"@style/DK.Shadow.Bottom\"/>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/block_list\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:overScrollMode=\"never\"\n            android:scrollbars=\"none\"/>\n\n    </FrameLayout>\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_layout_border_setting.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkTitle=\"@string/dk_kit_layout_border\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"/>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/setting_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_log_info_setting.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkTitle=\"@string/dk_kit_log_info\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"/>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/setting_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_method_cost.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        tools:dkTitle=\"@string/dk_kit_method_cost\" />\n\n    <com.didichuxing.doraemonkit.widget.webview.MyWebView\n        android:id=\"@+id/webview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_mock_template_preview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_mock_template_preview\" />\n\n    <LinearLayout\n        android:id=\"@+id/ll_mock_wrap\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/title_bar\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginRight=\"16dp\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/tv_name\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"16sp\"\n            tools:text=\"接口名称:\" />\n\n        <TextView\n            android:id=\"@+id/tv_path\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"6dp\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"16sp\"\n            tools:text=\"接口路径:\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"6dp\"\n                android:layout_marginRight=\"10dp\"\n                android:text=\"query:\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"16sp\" />\n\n            <com.didichuxing.doraemonkit.widget.jsonviewer.JsonRecyclerView\n                android:id=\"@+id/json_query\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\" />\n        </LinearLayout>\n    </LinearLayout>\n\n\n    <TextView\n        android:id=\"@+id/tv_desc\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/ll_mock_wrap\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"6dp\"\n        android:layout_marginRight=\"16dp\"\n        android:text=\"mock tempalte:\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"16sp\" />\n\n\n    <TextView\n        android:id=\"@+id/tv_upload\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:background=\"@color/dk_drop_down_selected\"\n        android:gravity=\"center\"\n        android:paddingTop=\"12dp\"\n        android:paddingBottom=\"12dp\"\n        android:text=\"@string/dk_data_mock_template_upload\"\n        android:textColor=\"@color/dk_color_FFFFFF\"\n        android:textSize=\"18sp\" />\n\n    <com.didichuxing.doraemonkit.widget.jsonviewer.JsonRecyclerView\n        android:id=\"@+id/jsonviewer\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_above=\"@id/tv_upload\"\n        android:layout_below=\"@id/tv_desc\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"6dp\"\n        android:layout_marginRight=\"16dp\" />\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_monitor_data_upload_page.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"match_parent\"\n              android:background=\"@color/dk_color_FFFFFF\"\n              android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"/>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/setting_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"/>\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"12dp\"\n        android:background=\"@color/dk_color_F5F6F7\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"bottom|center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/commit\"\n            style=\"@style/DK.ConfirmButton.Positive\"\n            android:layout_marginLeft=\"15dp\"\n            android:layout_marginRight=\"15dp\"\n            android:layout_marginBottom=\"15dp\"\n            android:enabled=\"false\"\n            android:text=\"@string/dk_platform_monitor_data_button\"/>\n    </LinearLayout>\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_monitor_pagedata.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\"\n    android:background=\"@color/dk_color_FFFFFF\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"44dp\"\n        app:dkLeftIcon=\"@mipmap/title_back\"\n        app:dkLeftText=\"@string/dk_back\"\n        app:dkTitle=\"@string/dk_platform_monitor_page_data\" />\n    <View\n        style=\"@style/DK.Shadow.Bottom\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/info_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:overScrollMode=\"never\"\n        android:scrollbars=\"none\" />\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_monitor_pagedata_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:padding=\"10dp\"\n    android:orientation=\"vertical\">\n\n    <TextView\n        android:id=\"@+id/page_name_txt\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:textSize=\"16sp\"\n        android:textColor=\"#333\"\n        android:maxLines=\"1\"\n        android:ellipsize=\"end\"\n        android:layout_marginBottom=\"16dp\"\n        android:text=\"\" />\n\n    <include\n        android:id=\"@+id/up_network_item\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        layout=\"@layout/dk_fragment_monitor_pagedata_item_item\"\n         />\n\n    <include\n        android:id=\"@+id/down_network_item\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        layout=\"@layout/dk_fragment_monitor_pagedata_item_item\" />\n\n    <include\n        android:id=\"@+id/memory_item\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        layout=\"@layout/dk_fragment_monitor_pagedata_item_item\" />\n\n    <include\n        android:id=\"@+id/cpu_item\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        layout=\"@layout/dk_fragment_monitor_pagedata_item_item\" />\n\n    <include\n        android:id=\"@+id/fps_item\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        layout=\"@layout/dk_fragment_monitor_pagedata_item_item\" />\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_monitor_pagedata_item_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <TextView\n        android:id=\"@+id/data_name_txt\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"right\"\n        android:paddingRight=\"10dp\"\n        android:textSize=\"12sp\"\n        android:textColor=\"#333\"\n        android:layout_weight=\"1\"\n        android:text=\"\" />\n\n    <TextView\n        android:id=\"@+id/high_data_txt\"\n        android:drawableLeft=\"@mipmap/dk_performance_up_arrow\"\n        android:drawablePadding=\"4dp\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:textSize=\"12sp\"\n        android:textColor=\"#666\"\n        android:text=\"\" />\n\n    <TextView\n        android:id=\"@+id/low_data_txt\"\n        android:drawableLeft=\"@mipmap/dk_performance_down_arrow\"\n        android:drawablePadding=\"4dp\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:textSize=\"12sp\"\n        android:textColor=\"#666\"\n        android:text=\"\" />\n\n    <TextView\n        android:id=\"@+id/avg_data_txt\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:textSize=\"12sp\"\n        android:textColor=\"#666\"\n        android:text=\"\" />\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_more.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\"\n    android:paddingLeft=\"@dimen/dk_dp_15\"\n    android:paddingRight=\"@dimen/dk_dp_15\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_setting\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/setting_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_below=\"@id/title_bar\" />\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_net_main_pager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:background=\"@color/dk_color_F4F5F6\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"44dp\"\n        app:dkLeftIcon=\"@mipmap/title_back\"\n        app:dkLeftText=\"@string/dk_back\"\n        app:dkTitle=\"@string/dk_net_monitor_title_summary\"/>\n\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/vp_show\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_above=\"@id/tabMode\"\n        android:layout_below=\"@+id/title_bar\"\n        android:background=\"@color/dk_color_F4F5F6\"/>\n\n\n    <LinearLayout\n        android:id=\"@+id/tabMode\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:layout_alignParentBottom=\"true\"\n        android:background=\"@color/dk_color_FFFFFF\"\n        android:orientation=\"horizontal\">\n\n        <include\n            android:id=\"@+id/tab_summary\"\n            layout=\"@layout/dk_view_network_tab_layout\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"/>\n\n        <include\n            android:id=\"@+id/tab_list\"\n            layout=\"@layout/dk_view_network_tab_layout\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"/>\n    </LinearLayout>\n\n    <View\n        style=\"@style/DK.Divider\"\n        android:layout_alignTop=\"@id/tabMode\"/>\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_net_mock.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        android:layout_alignParentTop=\"true\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_network_mock\" />\n\n    <RelativeLayout\n        android:id=\"@+id/rl_searchbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/title_bar\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginRight=\"16dp\">\n\n        <TextView\n            android:id=\"@+id/tv_search\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"36dp\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginLeft=\"8dp\"\n            android:gravity=\"center\"\n            android:text=\"@string/dk_mock_search\"\n            android:textColor=\"@color/dk_color_337CC4\"\n            android:textSize=\"14sp\" />\n\n        <androidx.cardview.widget.CardView\n            android:id=\"@+id/cardview\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"36dp\"\n            android:layout_margin=\"5dp\"\n            android:layout_toLeftOf=\"@id/tv_search\"\n            app:cardBackgroundColor=\"@color/dk_color_FFFFFF\"\n            app:cardCornerRadius=\"4dp\"\n            app:cardElevation=\"3dp\"\n            app:contentPaddingBottom=\"5dp\"\n            app:contentPaddingLeft=\"12dp\"\n            app:contentPaddingRight=\"@dimen/dk_dp_5\"\n            app:contentPaddingTop=\"5dp\">\n\n            <EditText\n                android:id=\"@+id/edittext\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:background=\"@drawable/dk_edittext_shape\"\n                android:drawableLeft=\"@mipmap/dk_search\"\n                android:drawablePadding=\"5dp\"\n                android:hint=\"@string/dk_data_mock_et_hint\"\n                android:textColorHint=\"@color/dk_color_BEBEBE\"\n                android:textSize=\"14sp\" />\n\n        </androidx.cardview.widget.CardView>\n\n\n    </RelativeLayout>\n\n\n    <!--底部导航栏-->\n    <LinearLayout\n        android:id=\"@+id/ll_bottom_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"35dp\"\n        android:layout_alignParentBottom=\"true\"\n        android:orientation=\"horizontal\">\n\n        <LinearLayout\n            android:id=\"@+id/ll_bottom_tab_mock\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"\n            android:orientation=\"vertical\">\n\n            <ImageView\n                android:id=\"@+id/iv_mock\"\n                android:layout_width=\"20dp\"\n                android:layout_height=\"20dp\"\n                android:layout_gravity=\"center\"\n                android:src=\"@mipmap/dk_mock_normal\" />\n\n            <TextView\n                android:id=\"@+id/tv_mock\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:text=\"@string/dk_data_mock_bottom_table_mock\"\n                android:textSize=\"10sp\" />\n        </LinearLayout>\n\n        <LinearLayout\n            android:id=\"@+id/ll_bottom_tab_template\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"\n            android:orientation=\"vertical\">\n\n            <ImageView\n                android:id=\"@+id/iv_template\"\n                android:layout_width=\"20dp\"\n                android:layout_height=\"20dp\"\n                android:layout_gravity=\"center\"\n                android:src=\"@mipmap/dk_template_normal\" />\n\n            <TextView\n                android:id=\"@+id/tv_template\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:text=\"@string/dk_data_mock_bottom_table_template\"\n                android:textSize=\"10sp\" />\n        </LinearLayout>\n\n    </LinearLayout>\n\n    <View\n        android:id=\"@+id/divider\"\n        style=\"@style/DK.Divider\"\n        android:layout_above=\"@id/ll_bottom_bar\"\n        android:layout_marginBottom=\"5dp\" />\n    <!--下拉菜单-->\n    <com.didichuxing.doraemonkit.widget.dropdown.DkDropDownMenu\n        android:id=\"@+id/drop_down_menu\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_above=\"@id/divider\"\n        android:layout_below=\"@id/rl_searchbar\"\n        android:layout_marginTop=\"8dp\"\n        app:dk_dddividerColor=\"@color/dk_color_FFFFFF\"\n        app:dk_ddmaskColor=\"@color/dk_mask_color\"\n        app:dk_ddmenuBackgroundColor=\"@color/dk_color_FFFFFF\"\n        app:dk_ddmenuIconOrientation=\"right\"\n        app:dk_ddmenuSelectedIcon=\"@mipmap/dk_arrow_selected\"\n        app:dk_ddmenuTextSize=\"13sp\"\n        app:dk_ddmenuUnselectedIcon=\"@mipmap/dk_arrow_unselected\"\n        app:dk_ddtextSelectedColor=\"@color/dk_color_337CC4\"\n        app:dk_ddtextUnselectedColor=\"@color/dk_color_333333\"\n        app:dk_ddunderlineColor=\"@color/dk_color_FFFFFF\" />\n\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_net_monitor.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        android:layout_alignParentTop=\"true\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_net_monitor\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/setting_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/title_bar\" />\n\n\n    <Button\n        android:id=\"@+id/btn_net_summary\"\n        style=\"@style/DK.Text\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_marginLeft=\"15dp\"\n        android:layout_marginRight=\"15dp\"\n        android:layout_marginBottom=\"10dp\"\n        android:background=\"@drawable/dk_radio_button_checked_background\"\n        android:text=\"@string/dk_net_monitor_show_summary\"\n        android:textColor=\"@color/dk_color_FFFFFF\" />\n\n    <TextView\n        android:id=\"@+id/tv_tip\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/setting_list\"\n        android:layout_marginLeft=\"6dp\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginRight=\"6dp\"\n        android:text=\"@string/dk_kit_net_monitor_white_host_tip\"\n        android:textSize=\"16sp\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/host_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_above=\"@id/btn_net_summary\"\n        android:layout_below=\"@id/tv_tip\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginBottom=\"10dp\" />\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_network_monitor_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"match_parent\"\n              android:background=\"@color/dk_color_FFFFFF\"\n              android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/dk_title_height\"\n        app:dkLeftIcon=\"@mipmap/title_back\"\n        app:dkLeftText=\"@string/dk_back\"\n        app:dkTitle=\"@string/dk_kit_network_monitor_detail\"/>\n\n    <View style=\"@style/DK.Divider\"/>\n\n    <include layout=\"@layout/dk_view_network_detail_pager_title\"/>\n\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/network_viewpager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"/>\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_network_monitor_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n    <View\n        style=\"@style/DK.Shadow.Bottom\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingBottom=\"10dp\"\n        android:paddingLeft=\"12dp\"\n        android:paddingRight=\"12dp\"\n        android:paddingTop=\"5dp\">\n\n        <EditText\n            android:id=\"@+id/network_list_filter\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"60dp\"\n            android:background=\"@mipmap/dk_network_filter_bg\"\n            android:drawableLeft=\"@mipmap/dk_search_icon\"\n            android:drawablePadding=\"10dp\"\n            android:hint=\"@string/dk_kit_network_filter_hint\"\n            android:paddingLeft=\"14dp\"\n            android:paddingRight=\"5dp\"\n            android:textColor=\"@color/dk_color_666666\"\n            android:textCursorDrawable=\"@drawable/dk_input_cursor\"\n            android:textSize=\"@dimen/dk_font_size_16\"/>\n    </LinearLayout>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/network_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:paddingLeft=\"@dimen/dk_dp_16\"\n        android:paddingRight=\"@dimen/dk_dp_16\"/>\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_network_summary_page.xml",
    "content": "<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_margin=\"8dp\"\n        android:orientation=\"vertical\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"160dp\"\n            android:background=\"@color/dk_color_FFFFFF\">\n\n            <TextView\n                android:id=\"@+id/total_sec\"\n                style=\"@style/DK.Text.Darker\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"20dp\"\n                android:gravity=\"center\"\n                android:textSize=\"@dimen/dk_font_size_22\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\" />\n\n            <TextView\n                android:id=\"@+id/total_tips\"\n                style=\"@style/DK.Text.Gray\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"6dp\"\n                android:gravity=\"center\"\n                android:text=\"@string/dk_network_summary_total_number_time_tips\"\n                android:textSize=\"10sp\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/total_sec\" />\n\n            <TextView\n                android:id=\"@+id/total_number\"\n                style=\"@style/DK.Text.Darker\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"20dp\"\n                android:gravity=\"center\"\n                android:textSize=\"@dimen/dk_font_size_22\"\n                app:layout_constraintHorizontal_weight=\"1\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toLeftOf=\"@+id/total_upload\"\n                app:layout_constraintTop_toBottomOf=\"@id/total_tips\"\n                app:layout_constraintWidth_percent=\"0.33\" />\n\n            <TextView\n                android:id=\"@+id/total_upload\"\n                style=\"@style/DK.Text.Darker\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center\"\n                android:textSize=\"@dimen/dk_font_size_22\"\n                app:layout_constraintHorizontal_weight=\"1\"\n                app:layout_constraintLeft_toRightOf=\"@id/total_number\"\n                app:layout_constraintTop_toTopOf=\"@id/total_number\"\n                app:layout_constraintWidth_percent=\"0.33\" />\n\n            <TextView\n                android:id=\"@+id/total_down\"\n                style=\"@style/DK.Text.Darker\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center\"\n                android:textSize=\"@dimen/dk_font_size_22\"\n                app:layout_constraintHorizontal_weight=\"1\"\n                app:layout_constraintLeft_toRightOf=\"@id/total_upload\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"@id/total_number\"\n                app:layout_constraintWidth_percent=\"0.33\" />\n\n            <TextView\n                style=\"@style/DK.Text.Gray\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_below=\"@id/total_upload\"\n                android:layout_centerHorizontal=\"true\"\n                android:layout_marginTop=\"6dp\"\n                android:layout_weight=\"1\"\n                android:gravity=\"center\"\n                android:text=\"@string/dk_network_summary_data_upload\"\n                android:textSize=\"10sp\"\n                app:layout_constraintLeft_toLeftOf=\"@id/total_upload\"\n                app:layout_constraintTop_toBottomOf=\"@id/total_upload\"\n                app:layout_constraintWidth_percent=\"0.33\" />\n\n\n            <TextView\n                style=\"@style/DK.Text.Gray\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_below=\"@+id/total_number\"\n                android:layout_alignParentStart=\"true\"\n                android:layout_alignParentLeft=\"true\"\n                android:layout_marginTop=\"6dp\"\n                android:gravity=\"center\"\n                android:text=\"@string/dk_network_summary_total_number\"\n                android:textSize=\"10sp\"\n                app:layout_constraintLeft_toLeftOf=\"@id/total_number\"\n                app:layout_constraintTop_toBottomOf=\"@id/total_number\"\n                app:layout_constraintWidth_percent=\"0.33\" />\n\n\n            <TextView\n                style=\"@style/DK.Text.Gray\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_below=\"@+id/total_down\"\n                android:layout_marginTop=\"6dp\"\n                android:gravity=\"center\"\n                android:text=\"@string/dk_network_summary_data_down\"\n                android:textSize=\"10sp\"\n                app:layout_constraintLeft_toLeftOf=\"@id/total_down\"\n                app:layout_constraintTop_toBottomOf=\"@id/total_down\"\n                app:layout_constraintWidth_percent=\"0.33\" />\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"250dp\"\n            android:layout_marginTop=\"10dp\"\n            android:background=\"@color/dk_color_FFFFFF\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                style=\"@style/DK.Text.Darker\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"44dp\"\n                android:layout_marginLeft=\"10dp\"\n                android:drawableLeft=\"@mipmap/dk_network_bar\"\n                android:drawablePadding=\"5dp\"\n                android:gravity=\"center_vertical\"\n                android:text=\"@string/dk_network_summary_http_method\"\n                android:textSize=\"14sp\" />\n\n            <View style=\"@style/DK.Divider\" />\n\n            <com.didichuxing.doraemonkit.widget.chart.BarChart\n                android:id=\"@+id/network_bar_chart\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\" />\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"284dp\"\n            android:layout_marginTop=\"10dp\"\n            android:background=\"@color/dk_color_FFFFFF\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                style=\"@style/DK.Text.Darker\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"44dp\"\n                android:layout_marginLeft=\"10dp\"\n                android:drawableLeft=\"@mipmap/dk_network_pie\"\n                android:drawablePadding=\"5dp\"\n                android:text=\"@string/dk_network_summary_data_type\"\n                android:textSize=\"14sp\" />\n\n            <View style=\"@style/DK.Divider\" />\n\n            <com.didichuxing.doraemonkit.widget.chart.PieChart\n                android:id=\"@+id/network_pier_chart\"\n                android:layout_width=\"150dp\"\n                android:layout_height=\"150dp\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"@dimen/dk_dp_40\" />\n        </LinearLayout>\n\n    </LinearLayout>\n\n\n</ScrollView>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_parameter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"match_parent\"\n              android:background=\"@color/dk_color_FFFFFF\"\n              android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"/>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/setting_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"/>\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_performance_large_picture_setting.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\" />\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"@dimen/dk_dp_16\"\n        android:layout_marginRight=\"@dimen/dk_dp_16\">\n\n        <TextView\n            android:id=\"@+id/tv_file_name\"\n            style=\"@style/DK.TextBig.Dark\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:text=\"@string/dk_large_picture_file_threshold\"\n            android:textSize=\"16sp\" />\n\n\n        <TextView\n            android:id=\"@+id/tv_file_unit\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:inputType=\"numberDecimal\"\n            android:text=\"KB\"\n            android:textColor=\"@color/dk_color_151515\"\n            android:textSize=\"16sp\" />\n\n        <EditText\n            android:id=\"@+id/ed_file_threshold\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerVertical=\"true\"\n            android:layout_toLeftOf=\"@id/tv_file_unit\"\n            android:inputType=\"numberDecimal\"\n            android:text=\"150\"\n            android:textColor=\"@color/dk_color_151515\"\n            android:textSize=\"16sp\" />\n\n    </RelativeLayout>\n\n    <View\n        style=\"@style/DK.Divider\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginRight=\"16dp\" />\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"@dimen/dk_dp_16\"\n        android:layout_marginRight=\"@dimen/dk_dp_16\">\n\n        <TextView\n            android:id=\"@+id/name\"\n            style=\"@style/DK.TextBig.Dark\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:text=\"@string/dk_large_picture_threshold\"\n            android:textSize=\"16sp\" />\n\n\n        <TextView\n            android:id=\"@+id/tv_unit\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:inputType=\"numberDecimal\"\n            android:text=\"MB\"\n            android:textColor=\"@color/dk_color_151515\"\n            android:textSize=\"16sp\" />\n\n        <EditText\n            android:id=\"@+id/ed_memory_threshold\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerVertical=\"true\"\n            android:layout_toLeftOf=\"@id/tv_unit\"\n            android:inputType=\"numberDecimal\"\n            android:text=\"1.5\"\n            android:textColor=\"@color/dk_color_151515\"\n            android:textSize=\"16sp\" />\n\n    </RelativeLayout>\n\n<!--    <TextView-->\n<!--        android:id=\"@+id/tv_desc\"-->\n<!--        android:layout_width=\"match_parent\"-->\n<!--        android:layout_height=\"wrap_content\"-->\n<!--        android:layout_marginLeft=\"@dimen/dk_dp_16\"-->\n<!--        android:layout_marginRight=\"@dimen/dk_dp_16\"-->\n<!--        android:layout_marginBottom=\"10dp\"-->\n<!--        tools:text=\"@string/dk_large_picture_threshold_desc\"-->\n<!--        android:textIsSelectable=\"true\"-->\n<!--        android:textSize=\"14sp\" />-->\n\n\n    <View\n        style=\"@style/DK.Divider\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginRight=\"16dp\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/setting_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_simple_dokit_page.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/dk_home_title_height\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"\" />\n\n    <FrameLayout\n        android:id=\"@+id/contentContainer\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"#00FFFFFF\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/title_bar\"\n        tools:background=\"#3900FFFF\">\n\n    </FrameLayout>\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_sp_show.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"44dp\"\n        app:dkLeftIcon=\"@mipmap/title_back\"\n        app:dkLeftText=\"@string/dk_back\"\n        app:dkTitle=\"@string/dk_kit_file_explorer\" />\n\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv_sp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"10\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_sys_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\"\n    android:background=\"@color/dk_color_FFFFFF\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        android:layout_height=\"@dimen/dk_home_title_height\"\n        app:dkTitle=\"@string/dk_kit_sysinfo\"/>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/info_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:overScrollMode=\"never\"\n        android:scrollbars=\"none\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_text_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"44dp\"\n        app:dkLeftIcon=\"@mipmap/title_back\"/>\n\n    <View style=\"@style/DK.Shadow.Bottom\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/text_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_third_lib_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/dk_home_title_height\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_third_library_info\" />\n\n    <RelativeLayout\n        android:id=\"@+id/rl_searchbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/title_bar\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginRight=\"16dp\">\n\n        <RadioGroup\n            android:layout_alignParentRight=\"true\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_centerVertical=\"true\"\n            android:id=\"@+id/sort_option\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\">\n\n            <RadioButton\n                android:checked=\"true\"\n                android:id=\"@+id/sort_name\"\n                style=\"@style/DK.RadioButton.Left\"\n                android:layout_weight=\"1\"\n                android:text=\"@string/dk_third_sort_name\" />\n\n            <RadioButton\n                android:id=\"@+id/sort_size\"\n                style=\"@style/DK.RadioButton.Right\"\n                android:layout_weight=\"1\"\n                android:text=\"@string/dk_third_sort_size\" />\n\n        </RadioGroup>\n\n        <androidx.cardview.widget.CardView\n            android:id=\"@+id/cardview\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"36dp\"\n            android:layout_margin=\"5dp\"\n            android:layout_toLeftOf=\"@id/sort_option\"\n            app:cardBackgroundColor=\"@color/dk_color_FFFFFF\"\n            app:cardCornerRadius=\"4dp\"\n            app:cardElevation=\"3dp\"\n            app:contentPaddingBottom=\"5dp\"\n            app:contentPaddingLeft=\"12dp\"\n            app:contentPaddingRight=\"@dimen/dk_dp_5\"\n            app:contentPaddingTop=\"5dp\">\n\n            <EditText\n                android:singleLine=\"true\"\n                android:id=\"@+id/edittext\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:background=\"@drawable/dk_edittext_shape\"\n                android:drawableLeft=\"@mipmap/dk_search\"\n                android:drawablePadding=\"5dp\"\n                android:hint=\"@string/dk_data_mock_et_hint\"\n                android:textColorHint=\"@color/dk_color_BEBEBE\"\n                android:textSize=\"14sp\" />\n\n        </androidx.cardview.widget.CardView>\n\n\n    </RelativeLayout>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/info_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/dk_dp_5\"\n        android:overScrollMode=\"never\"\n        android:scrollbars=\"none\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_time_counter_index.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"match_parent\"\n              android:background=\"@color/dk_color_FFFFFF\"\n              android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_time_counter\"/>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/setting_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"/>\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_time_counter_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"match_parent\"\n              android:background=\"@color/dk_color_FFFFFF\"\n              android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/dk_title_height\"\n        app:dkLeftIcon=\"@mipmap/title_back\"\n        app:dkLeftText=\"@string/dk_back\"\n        app:dkTitle=\"@string/dk_kit_block_time_counter_list\"/>\n\n    <View\n        style=\"@style/DK.Shadow.Bottom\"/>\n\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/block_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:overScrollMode=\"never\"\n        android:scrollbars=\"none\"/>\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_top_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_top_activity\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/top_activity_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_video_play.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" >\n\n    <com.didichuxing.doraemonkit.widget.videoview.MyVideoView\n        android:id=\"@+id/video_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_view_check.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkTitle=\"@string/dk_kit_view_check\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"/>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/setting_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_weak_network.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\"\n    android:paddingLeft=\"@dimen/dk_dp_15\"\n    android:paddingRight=\"@dimen/dk_dp_15\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_weak_network\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/setting_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/title_bar\" />\n\n\n    <!--    模拟弱网详情View-->\n    <RelativeLayout\n        android:id=\"@+id/weak_network_layout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/setting_list\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginTop=\"@dimen/dk_dp_15\"\n        android:paddingLeft=\"@dimen/dk_dp_15\"\n        android:paddingRight=\"@dimen/dk_dp_15\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\">\n        <!--        radioGroup-->\n        <RadioGroup\n            android:id=\"@+id/weak_network_option\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"@dimen/dk_dp_15\"\n            android:orientation=\"horizontal\">\n\n            <RadioButton\n                android:id=\"@+id/off_network\"\n                style=\"@style/DK.RadioButton.Left\"\n                android:layout_weight=\"1\"\n                android:text=\"@string/dk_weak_network_off\" />\n\n            <RadioButton\n                android:id=\"@+id/timeout\"\n                style=\"@style/DK.RadioButton\"\n                android:layout_weight=\"1\"\n                android:text=\"@string/dk_weak_network_timeout\" />\n\n            <RadioButton\n                android:id=\"@+id/speed_limit\"\n                style=\"@style/DK.RadioButton.Right\"\n                android:layout_weight=\"1\"\n                android:text=\"@string/dk_weak_network_speed_limit\" />\n        </RadioGroup>\n        <!--        超时设置布局设置-->\n        <RelativeLayout\n            android:id=\"@+id/layout_timeout_option\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/weak_network_option\"\n            android:visibility=\"gone\">\n\n            <TextView\n                android:id=\"@+id/label_timeout\"\n                style=\"@style/DK.TextBig.Darker\"\n                android:layout_centerVertical=\"true\"\n                android:text=\"Timeout：\" />\n\n            <EditText\n                android:id=\"@+id/value_timeout\"\n                style=\"@style/DK.Input\"\n                android:layout_width=\"80dp\"\n                android:layout_toRightOf=\"@id/label_timeout\"\n                android:gravity=\"center\"\n                android:hint=\"2000\"\n                android:inputType=\"number\"\n                android:paddingLeft=\"@dimen/dk_dp_5\"\n                android:paddingRight=\"@dimen/dk_dp_5\"\n                android:singleLine=\"true\" />\n\n            <TextView\n                style=\"@style/DK.TextBig.Darker\"\n                android:layout_centerVertical=\"true\"\n                android:layout_toRightOf=\"@id/value_timeout\"\n                android:text=\"ms\" />\n        </RelativeLayout>\n        <!--        限速设置布局-->\n        <RelativeLayout\n            android:id=\"@+id/layout_speed_limit\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/weak_network_option\"\n            android:visibility=\"gone\"\n            tools:visibility=\"visible\">\n\n            <RelativeLayout\n                android:id=\"@+id/layout_limit_request\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\">\n\n                <TextView\n                    android:id=\"@+id/label_request_speed\"\n                    style=\"@style/DK.TextBig.Darker\"\n                    android:layout_centerVertical=\"true\"\n                    android:text=\"@string/dk_weak_network_request_limit\" />\n\n                <EditText\n                    android:id=\"@+id/request_speed\"\n                    style=\"@style/DK.Input\"\n                    android:layout_width=\"80dp\"\n                    android:layout_toRightOf=\"@id/label_request_speed\"\n                    android:gravity=\"center\"\n                    android:hint=\"1\"\n                    android:inputType=\"number\"\n                    android:paddingLeft=\"@dimen/dk_dp_5\"\n                    android:paddingRight=\"@dimen/dk_dp_5\"\n                    android:singleLine=\"true\" />\n\n                <TextView\n                    style=\"@style/DK.TextBig.Darker\"\n                    android:layout_centerVertical=\"true\"\n                    android:layout_toRightOf=\"@id/request_speed\"\n                    android:text=\"@string/dk_weak_network_speed_unit\" />\n            </RelativeLayout>\n\n\n            <RelativeLayout\n                android:id=\"@+id/layout_limit_response\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_below=\"@id/layout_limit_request\">\n\n                <TextView\n                    android:id=\"@+id/label_response_speed\"\n                    style=\"@style/DK.TextBig.Darker\"\n                    android:layout_centerVertical=\"true\"\n                    android:text=\"@string/dk_weak_network_response_limit\" />\n\n                <EditText\n                    android:id=\"@+id/response_speed\"\n                    style=\"@style/DK.Input\"\n                    android:layout_width=\"80dp\"\n                    android:layout_toRightOf=\"@id/label_response_speed\"\n                    android:gravity=\"center\"\n                    android:hint=\"1\"\n                    android:inputType=\"number\"\n                    android:paddingLeft=\"@dimen/dk_dp_5\"\n                    android:paddingRight=\"@dimen/dk_dp_5\"\n                    android:singleLine=\"true\" />\n\n                <TextView\n                    style=\"@style/DK.TextBig.Darker\"\n                    android:layout_centerVertical=\"true\"\n                    android:layout_toRightOf=\"@id/response_speed\"\n                    android:text=\"@string/dk_weak_network_speed_unit\" />\n            </RelativeLayout>\n\n            <TextView\n                style=\"@style/DK.TextSmall\"\n                android:layout_below=\"@id/layout_limit_response\"\n                android:layout_marginTop=\"@dimen/dk_dp_15\"\n                android:gravity=\"left\"\n                android:text=\"@string/dk_weak_network_limit_message\" />\n\n        </RelativeLayout>\n    </RelativeLayout>\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_web_door.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_web_door\" />\n\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"200dp\">\n\n        <EditText\n            android:id=\"@+id/web_address_input\"\n            style=\"@style/DK.Input\"\n            android:layout_height=\"match_parent\"\n            android:layout_marginLeft=\"15dp\"\n            android:layout_marginTop=\"15dp\"\n            android:layout_marginRight=\"15dp\"\n            android:gravity=\"top\"\n            android:hint=\"@string/dk_web_door_hint\"\n            android:inputType=\"textUri\"\n            android:padding=\"5dp\" />\n\n        <ImageView\n            android:id=\"@+id/qr_code\"\n            android:layout_width=\"24dp\"\n            android:layout_height=\"24dp\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_alignParentBottom=\"true\"\n            android:layout_marginRight=\"@dimen/dk_dp_16\"\n            android:layout_marginBottom=\"@dimen/dk_dp_16\"\n            android:src=\"@mipmap/dk_web_door_history_qrcode\" />\n    </RelativeLayout>\n\n    <View style=\"@style/DK.Divider\" />\n\n    <TextView\n        android:id=\"@+id/url_explore\"\n        style=\"@style/DK.ConfirmButton.Positive\"\n        android:layout_marginLeft=\"15dp\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_marginRight=\"15dp\"\n        android:enabled=\"false\"\n        android:text=\"@string/dk_web_door_explore\" />\n\n    <androidx.core.widget.NestedScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:orientation=\"vertical\">\n\n            <androidx.recyclerview.widget.RecyclerView\n                android:id=\"@+id/history_list\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"15dp\"\n                android:layout_marginTop=\"15dp\"\n                android:layout_marginRight=\"15dp\" />\n\n            <TextView\n                android:id=\"@+id/clear\"\n                style=\"@style/DK.Text.Gray\"\n                android:layout_height=\"30dp\"\n                android:layout_gravity=\"center\"\n                android:layout_marginTop=\"16dp\"\n                android:text=\"@string/dk_web_door_clear_history\" />\n\n        </LinearLayout>\n    </androidx.core.widget.NestedScrollView>\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_fragment_web_door_default.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\" android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.didichuxing.doraemonkit.widget.webview.MyWebView\n        android:id=\"@+id/webview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_home_title_bar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:paddingTop=\"8dp\"\n    android:paddingBottom=\"8dp\"\n    android:gravity=\"center_vertical\"\n    android:layout_gravity=\"center_vertical\">\n    <ImageView\n        android:id=\"@+id/icon\"\n        android:layout_width=\"28dp\"\n        android:layout_height=\"28dp\"\n        android:layout_marginLeft=\"12dp\"\n        tools:src=\"@mipmap/dk_close_icon_big\"\n        android:layout_gravity=\"center_vertical\"\n        android:scaleType=\"centerInside\"\n        android:visibility=\"visible\" />\n\n    <TextView\n        style=\"@style/DK.TitleBig\"\n        android:id=\"@+id/title\"\n        android:layout_gravity=\"center_vertical\"\n        tools:text=\"这是标题\"\n        android:visibility=\"visible\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_bar_chart.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n\n    <TextView\n        android:id=\"@+id/post\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"31.5dp\"\n        android:layout_marginTop=\"62dp\"\n        android:text=\"@string/dk_network_post_method\" />\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/post\"\n        android:layout_marginLeft=\"31.5dp\"\n        android:layout_marginTop=\"25.5dp\"\n        android:text=\"@string/dk_network_get_method\" />\n\n    <View\n        android:id=\"@+id/solid_line_fir\"\n        android:layout_width=\"0.5dp\"\n        android:layout_height=\"97dp\"\n        android:layout_marginLeft=\"12.5dp\"\n        android:layout_marginTop=\"40dp\"\n        android:layout_toRightOf=\"@id/post\"\n        android:background=\"@drawable/dk_dotted_line_vertical\"\n        android:layerType=\"software\" />\n\n    <View\n        android:layout_width=\"0.5dp\"\n        android:layout_height=\"97dp\"\n        android:layout_marginLeft=\"95dp\"\n        android:layout_marginTop=\"40dp\"\n        android:layout_toRightOf=\"@+id/solid_line_fir\"\n        android:background=\"@drawable/dk_dotted_line_vertical\"\n        android:layerType=\"software\" />\n\n    <View\n        android:id=\"@+id/solid_line_end\"\n        android:layout_width=\"0.5dp\"\n        android:layout_height=\"97dp\"\n        android:layout_marginLeft=\"190dp\"\n        android:layout_marginTop=\"40dp\"\n        android:layout_toRightOf=\"@id/solid_line_fir\"\n        android:background=\"@drawable/dk_dotted_line_vertical\"\n        android:layerType=\"software\" />\n\n    <View\n        android:layout_width=\"257.5dp\"\n        android:layout_height=\"0.5dp\"\n        android:layout_below=\"@+id/solid_line_fir\"\n        android:layout_toRightOf=\"@+id/solid_line_fir\"\n        android:background=\"@color/dk_color_DDDDDD\" />\n\n    <TextView\n        android:id=\"@+id/mark_first\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignLeft=\"@+id/solid_line_fir\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_marginTop=\"145dp\" />\n\n    <TextView\n        android:id=\"@+id/mark_second\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignLeft=\"@+id/solid_line_fir\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_marginLeft=\"91dp\"\n        android:layout_marginTop=\"142dp\" />\n\n    <TextView\n        android:id=\"@+id/mark_third\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignLeft=\"@+id/solid_line_fir\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_marginLeft=\"182dp\"\n        android:layout_marginTop=\"143dp\" />\n\n    <View\n        android:id=\"@+id/post_value\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"20dp\"\n        android:layout_alignLeft=\"@+id/solid_line_fir\"\n        android:layout_alignTop=\"@+id/post\" />\n\n    <View\n        android:id=\"@+id/get_value\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"20dp\"\n        android:layout_alignLeft=\"@+id/solid_line_fir\"\n        android:layout_alignTop=\"@+id/post\"\n        android:layout_marginTop=\"42dp\" />\n\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_block_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              style=\"@style/DK.CommonItem\">\n\n    <TextView\n        android:id=\"@+id/title\"\n        style=\"@style/DK.Text.Dark\"\n        android:layout_weight=\"1\"\n        android:gravity=\"left|center_vertical\"\n        android:paddingLeft=\"5dp\"/>\n\n    <TextView\n        android:id=\"@+id/time\"\n        style=\"@style/DK.TextSmall.Dark\"\n        android:paddingLeft=\"20dp\"/>\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_connect_address.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\"\n    android:padding=\"@dimen/dk_dp_10\">\n\n    <View\n        android:id=\"@+id/state_dot\"\n        android:layout_width=\"14dp\"\n        android:layout_height=\"14dp\"\n        android:layout_alignBottom=\"@+id/tv_name\"\n        android:layout_marginBottom=\"4dp\"\n        android:layout_toRightOf=\"@+id/tv_name\"\n        android:background=\"@drawable/dk_dokitview_studio_connect\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\" />\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginRight=\"@dimen/dk_dp_10\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"16sp\"\n        android:textStyle=\"bold\"\n        tools:text=\"主机名称:10.10.0.33\" />\n\n\n    <TextView\n        android:id=\"@+id/tv_address\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/tv_name\"\n        android:layout_marginTop=\"@dimen/dk_dp_10\"\n        android:layout_marginRight=\"@dimen/dk_dp_98\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"12sp\"\n        android:textStyle=\"bold\"\n        tools:text=\"地址:2222222222222222222222222222222222222222222222222\" />\n\n\n    <TextView\n        android:id=\"@+id/tv_time\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/tv_address\"\n        android:layout_marginTop=\"@dimen/dk_dp_10\"\n        android:textColor=\"@color/dk_color_666666\"\n        android:textSize=\"12sp\"\n        tools:text=\"上次连接时间:\" />\n\n    <Button\n        android:id=\"@+id/connect\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:text=\"连接\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_crash_history.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <TextView\n        android:layout_marginTop=\"5dp\"\n        android:layout_marginRight=\"5dp\"\n        android:layout_gravity=\"center_horizontal\"\n        style=\"@style/DK.TextBig.Dark\"\n        android:id=\"@+id/time\" />\n\n    <TextView\n        android:id=\"@+id/content\"\n        style=\"@style/DK.Text.Dark\"\n        android:gravity=\"left\" />\n\n    <View style=\"@style/DK.Divider\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_data_clean.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:padding=\"10dp\">\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"14sp\"\n        tools:text=\"webview\" />\n\n    <Switch\n        android:id=\"@+id/switch_btn\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentRight=\"true\"\n        android:checked=\"false\"\n        android:clickable=\"false\" />\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_default_drop_down.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <TextView\n        android:id=\"@+id/text\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:paddingLeft=\"25dp\"\n        android:paddingTop=\"10dp\"\n        android:paddingRight=\"25dp\"\n        android:paddingBottom=\"10dp\"\n        android:text=\"所有\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"14sp\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_file_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n        <ImageView\n            android:id=\"@+id/icon\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"36dp\"\n            android:layout_marginBottom=\"14dp\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"14dp\" />\n\n        <TextView\n            android:id=\"@+id/name\"\n            style=\"@style/DK.TextBig.Darker\"\n            android:layout_width=\"0dp\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginLeft=\"12dp\"\n            android:layout_marginRight=\"16dp\"\n            android:layout_weight=\"1\"\n            android:ellipsize=\"end\"\n            android:gravity=\"left\"\n            android:singleLine=\"true\" />\n\n        <ImageView\n            android:id=\"@+id/more\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginRight=\"16dp\"\n            android:src=\"@mipmap/dk_more_icon\"\n            android:visibility=\"gone\" />\n\n    </LinearLayout>\n\n    <View\n        style=\"@style/DK.Divider\"\n        android:layout_gravity=\"bottom\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginRight=\"16dp\" />\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_group_exit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"15dp\"\n    android:background=\"@drawable/dk_kit_group_background\"\n    android:elevation=\"2dp\">\n\n    <TextView\n        android:id=\"@+id/close\"\n        style=\"@style/DK.Text\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:text=\"@string/dk_kit_exit\"\n        android:textColor=\"@color/dk_color_CC3A4B\" />\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_group_kit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"8dp\"\n    android:background=\"@drawable/dk_kit_group_background\"\n    android:elevation=\"1dp\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"16dp\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/name\"\n            style=\"@style/DK.Title\"\n            android:layout_height=\"wrap_content\"\n            android:singleLine=\"true\"\n            tools:text=\"平台工具\" />\n\n        <TextView\n            android:id=\"@+id/tv_sub_name\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginLeft=\"8dp\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"12sp\"\n            tools:text=\"(www.dokit.com)\" />\n\n    </LinearLayout>\n\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/group_kit_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"16dp\" />\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_group_kit_manager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"100dp\"\n    android:background=\"@drawable/dk_kit_group_background\"\n    android:orientation=\"vertical\">\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\">\n\n            <ImageView\n                android:id=\"@+id/icon\"\n                android:layout_width=\"34dp\"\n                android:layout_height=\"34dp\"\n                tools:src=\"@mipmap/dk_gps_mock\" />\n\n            <TextView\n                android:id=\"@+id/name\"\n                style=\"@style/DK.TextSmall.Dark\"\n                android:layout_width=\"wrap_content\"\n                android:layout_marginTop=\"6dp\"\n                android:gravity=\"center\"\n                android:singleLine=\"true\"\n                tools:text=\"@string/dk_app_name\" />\n        </LinearLayout>\n\n        <ImageView\n            android:id=\"@+id/iv_tag\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"right\"\n            android:layout_margin=\"6dp\"\n            android:src=\"@mipmap/dk_kit_item_checked\" />\n\n        <View\n            android:id=\"@+id/view_mask\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:background=\"#99eeeeee\" />\n    </FrameLayout>\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_group_mode.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"15dp\"\n        android:background=\"@color/dk_color_F5F6F7\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"8dp\">\n\n        <TextView\n            android:id=\"@+id/name\"\n            style=\"@style/DK.Title\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"16dp\"\n            android:gravity=\"center\"\n            android:singleLine=\"true\"\n            android:text=\"@string/dk_category_mode\" />\n\n\n        <RadioGroup\n            android:id=\"@+id/rb_group\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:paddingLeft=\"10dp\"\n            android:paddingTop=\"10dp\">\n\n            <RadioButton\n                android:id=\"@+id/rb_normal\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:checked=\"true\"\n                android:text=\"@string/dk_kit_mode_rb_normal\"\n                android:textColor=\"@color/dk_color_324456\" />\n\n            <RadioButton\n                android:id=\"@+id/rb_system\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:checked=\"false\"\n                android:text=\"@string/dk_kit_mode_rb_system\"\n                android:textColor=\"@color/dk_color_324456\" />\n\n        </RadioGroup>\n\n        <TextView\n            android:id=\"@+id/desc\"\n            style=\"@style/DK.TextSmall\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:layout_marginBottom=\"10dp\"\n            android:gravity=\"left\"\n            android:text=\"@string/dk_kit_mode_desc\" />\n\n\n    </LinearLayout>\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"15dp\"\n        android:background=\"@color/dk_color_F5F6F7\" />\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_group_title.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"15dp\"\n        android:background=\"@color/dk_color_F5F6F7\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginBottom=\"10dp\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/tv_title_name\"\n            style=\"@style/DK.Title\"\n            android:singleLine=\"true\"\n            tools:text=\"平台工具\" />\n\n        <TextView\n            android:id=\"@+id/tv_sub_title_name\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"10dp\"\n            android:singleLine=\"true\"\n            android:textColor=\"@color/dk_color_FF0006\"\n            android:textSize=\"14sp\"\n            android:visibility=\"gone\"\n            tools:text=\"(www.dokit.cn)\" />\n    </LinearLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_group_version.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginLeft=\"15dp\"\n    android:layout_marginRight=\"15dp\"\n    android:orientation=\"vertical\">\n\n    <TextView\n        android:id=\"@+id/version\"\n        style=\"@style/DK.Text\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/dk_kit_version\"\n        android:textColor=\"@color/dk_color_999999\"\n        android:textSize=\"@dimen/dk_font_size_12\" />\n\n    <ImageView\n        android:layout_width=\"180dp\"\n        android:layout_height=\"79.5dp\"\n        android:layout_gravity=\"center\"\n        android:layout_marginTop=\"10dp\"\n        android:src=\"@mipmap/dk_egg\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_kit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/ll_toolpanel_item\"\n    android:layout_width=\"75dp\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginLeft=\"10dp\"\n    android:layout_marginBottom=\"16dp\"\n    android:gravity=\"center_horizontal\"\n    android:orientation=\"vertical\">\n\n    <ImageView\n        android:id=\"@+id/icon\"\n        android:layout_width=\"34dp\"\n        android:layout_height=\"34dp\" />\n\n    <TextView\n        android:id=\"@+id/name\"\n        style=\"@style/DK.TextSmall.Dark\"\n        android:layout_width=\"wrap_content\"\n        android:layout_marginTop=\"6dp\"\n        android:gravity=\"center\"\n        android:singleLine=\"true\"\n        android:text=\"@string/dk_app_name\" />\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_large_img_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:paddingLeft=\"16dp\"\n    android:paddingRight=\"16dp\">\n\n    <ImageView\n        android:id=\"@+id/iv\"\n        android:layout_width=\"100dp\"\n        android:layout_height=\"100dp\"\n        android:layout_centerVertical=\"true\"\n        android:layout_marginRight=\"10dp\" />\n\n    <RelativeLayout\n        android:id=\"@+id/rl_content\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerVertical=\"true\"\n        android:layout_toRightOf=\"@id/iv\">\n\n        <TextView\n            android:id=\"@+id/tv_link\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"1\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"12sp\"\n            tools:text=\"https://www.xxx.com\" />\n\n        <TextView\n            android:id=\"@+id/tv_framework\"\n            style=\"@style/DK.TextSmall.Darker\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/tv_link\"\n            android:layout_marginTop=\"10dp\"\n            tools:text=\"form:memory\" />\n\n        <TextView\n            android:id=\"@+id/tv_file_size\"\n            style=\"@style/DK.TextSmall.Darker\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/tv_framework\"\n            android:layout_marginTop=\"10dp\"\n            tools:text=\"fileSize:10kb\" />\n\n        <TextView\n            android:id=\"@+id/tv_memory_size\"\n            style=\"@style/DK.TextSmall.Darker\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/tv_file_size\"\n            android:layout_marginTop=\"10dp\"\n            tools:text=\"memorySize:1.5mb\" />\n\n        <TextView\n            android:id=\"@+id/tv_size\"\n            style=\"@style/DK.TextSmall.Darker\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/tv_memory_size\"\n            android:layout_marginTop=\"10dp\"\n            tools:text=\"width:1000    height:668\" />\n\n    </RelativeLayout>\n\n    <Button\n        android:id=\"@+id/btn_copy\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"30dp\"\n        android:layout_below=\"@id/rl_content\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginBottom=\"10dp\"\n        android:background=\"@drawable/dk_item_btn_img\"\n        android:text=\"copy url\"\n        android:textColor=\"#ffffff\"\n        android:textSize=\"12sp\" />\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_layout_bottom_up_select_window.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_horizontal\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:id=\"@+id/ll_panel\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:background=\"@color/foreground_wtf\"\n        android:clickable=\"true\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <RelativeLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"42dp\">\n\n            <View\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"1px\"\n                android:layout_alignParentTop=\"true\" />\n\n            <TextView\n                android:id=\"@+id/tv_cancel\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"match_parent\"\n                android:layout_alignParentLeft=\"true\"\n                android:layout_centerVertical=\"true\"\n                android:gravity=\"center\"\n                android:text=\"@string/dk_cancel\" />\n\n            <TextView\n                android:id=\"@+id/tv_title\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_centerInParent=\"true\" />\n\n            <TextView\n                android:id=\"@+id/tv_submit\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"match_parent\"\n                android:layout_alignParentRight=\"true\"\n                android:layout_centerVertical=\"true\"\n                android:gravity=\"center\"\n                android:text=\"@string/dk_submit\" />\n\n            <View\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"1px\"\n                android:layout_alignParentBottom=\"true\"\n                android:background=\"@color/dk_color_000000\" />\n        </RelativeLayout>\n\n        <FrameLayout\n            android:id=\"@+id/content\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@color/foreground_wtf\" />\n\n    </LinearLayout>\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_localstorage.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:minHeight=\"25dp\">\n\n        <TextView\n            android:id=\"@+id/tv_key\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"\n            android:gravity=\"center\"\n            android:paddingLeft=\"5dp\"\n            android:paddingRight=\"5dp\"\n            tools:text=\"key\" />\n\n        <View style=\"@style/DK.Divider.Vertical\" />\n\n        <TextView\n            android:id=\"@+id/tv_value\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"\n            android:gravity=\"center\"\n            android:paddingLeft=\"5dp\"\n            android:paddingRight=\"5dp\"\n            tools:text=\"value\" />\n\n    </LinearLayout>\n\n    <View style=\"@style/DK.Divider\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_log.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n              android:paddingTop=\"2dp\"\n              android:paddingBottom=\"1dp\"\n              android:paddingLeft=\"3dp\"\n              android:paddingRight=\"3dp\">\n\n    <TextView\n        android:id=\"@+id/pid_text\"\n        android:layout_width=\"70dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentStart=\"true\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/dk_color_FFFFFF\"\n        android:paddingEnd=\"2dp\"\n        android:paddingRight=\"2dp\"\n        android:textSize=\"12sp\"/>\n\n    <TextView\n        android:id=\"@+id/timestamp_text\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_toEndOf=\"@+id/pid_text\"\n        android:layout_toRightOf=\"@+id/pid_text\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/dk_color_FFFFFF\"\n        android:paddingLeft=\"2dp\"\n        android:paddingRight=\"2dp\"\n        android:textSize=\"12sp\"/>\n\n    <TextView\n        android:id=\"@+id/tag_text\"\n        android:layout_width=\"70dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentStart=\"true\"\n        android:layout_below=\"@+id/pid_text\"\n        android:ellipsize=\"end\"\n        android:textColor=\"@color/dk_color_000000\"\n        android:singleLine=\"true\"\n        android:paddingEnd=\"2dp\"\n        android:paddingRight=\"2dp\"\n        android:textSize=\"12sp\"/>\n\n    <TextView\n        android:id=\"@+id/log_level_text\"\n        android:layout_width=\"15dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/pid_text\"\n        android:layout_toEndOf=\"@+id/tag_text\"\n        android:layout_toRightOf=\"@+id/tag_text\"\n        android:gravity=\"center_horizontal\"\n        android:singleLine=\"true\"\n        android:textSize=\"12sp\"/>\n\n\n    <TextView\n        android:id=\"@+id/log_output_text\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/pid_text\"\n        android:layout_toEndOf=\"@+id/log_level_text\"\n        android:layout_toRightOf=\"@+id/log_level_text\"\n        android:ellipsize=\"end\"\n        android:singleLine=\"true\"\n        android:paddingLeft=\"2dp\"\n        android:paddingRight=\"2dp\"\n        android:textSize=\"12sp\"/>\n\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_more_content.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/more_item\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/dk_setting_item_background\"\n    android:paddingTop=\"@dimen/dk_dp_5\"\n    android:paddingBottom=\"@dimen/dk_dp_5\">\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentTop=\"true\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"16sp\"\n        tools:text=\"功能管理\" />\n\n    <ImageView\n        android:id=\"@+id/iv_more\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:layout_marginLeft=\"8dp\"\n        android:src=\"@mipmap/dk_more_icon\" />\n\n    <TextView\n        android:id=\"@+id/tv_desc\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/tv_name\"\n        android:layout_marginTop=\"5dp\"\n        android:layout_toLeftOf=\"@id/iv_more\"\n        android:textColor=\"@color/dk_color_999999\"\n        android:textSize=\"14sp\" />\n\n    <View\n        style=\"@style/DK.Divider\"\n        android:layout_below=\"@id/tv_desc\"\n        android:layout_marginTop=\"5dp\" />\n\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_more_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"10dp\"\n    android:background=\"@drawable/dk_setting_item_background\">\n\n    <TextView\n        android:id=\"@+id/tv_title\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerVertical=\"true\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"18sp\"\n        android:textStyle=\"bold\"\n        tools:text=\"功能管理\" />\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_network_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"73dp\">\n\n    <TextView\n        android:id=\"@+id/network_list_url\"\n        style=\"@style/DK.TextSmall.Dark\"\n        android:ellipsize=\"end\"\n        android:paddingRight=\"30dp\"\n        android:singleLine=\"true\"\n        android:textSize=\"@dimen/dk_font_size_10\"\n        app:layout_constraintBottom_toTopOf=\"@+id/network_list_platform\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_chainStyle=\"packed\" />\n\n    <TextView\n        android:id=\"@+id/network_list_platform\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"3dp\"\n        android:background=\"@drawable/dk_network_platform_bg\"\n        android:paddingLeft=\"3dp\"\n        android:paddingRight=\"3dp\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/dk_color_FFFFFF\"\n        android:textSize=\"@dimen/dk_font_size_10\"\n        app:layout_constraintBottom_toTopOf=\"@+id/network_list_method\"\n        app:layout_constraintLeft_toLeftOf=\"@id/network_list_url\"\n        app:layout_constraintTop_toBottomOf=\"@+id/network_list_url\" />\n\n    <TextView\n        android:id=\"@+id/network_list_method\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/dk_network_method_bg\"\n        android:paddingLeft=\"3dp\"\n        android:paddingRight=\"3dp\"\n        android:textColor=\"@color/dk_color_FFFFFF\"\n        android:textSize=\"@dimen/dk_font_size_10\"\n        app:layout_constraintBottom_toTopOf=\"@+id/network_list_time_and_cost\"\n        app:layout_constraintLeft_toLeftOf=\"@id/network_list_platform\"\n        app:layout_constraintTop_toBottomOf=\"@+id/network_list_platform\" />\n\n    <TextView\n        android:id=\"@+id/network_list_code\"\n        style=\"@style/DK.TextSmall.Dark\"\n        android:layout_marginLeft=\"5dp\"\n        android:textSize=\"@dimen/dk_font_size_10\"\n        app:layout_constraintLeft_toRightOf=\"@id/network_list_method\"\n        app:layout_constraintTop_toBottomOf=\"@id/network_list_platform\" />\n\n\n    <TextView\n        android:id=\"@+id/network_list_time_and_cost\"\n        style=\"@style/DK.TextSmall.Dark\"\n        android:textSize=\"@dimen/dk_font_size_10\"\n        app:layout_constraintBottom_toTopOf=\"@+id/network_list_flow\"\n        app:layout_constraintLeft_toLeftOf=\"@id/network_list_platform\"\n        app:layout_constraintTop_toBottomOf=\"@+id/network_list_method\" />\n\n    <TextView\n        android:id=\"@+id/network_list_flow\"\n        style=\"@style/DK.TextSmall.Dark\"\n        android:textSize=\"@dimen/dk_font_size_10\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"@id/network_list_platform\"\n        app:layout_constraintTop_toBottomOf=\"@+id/network_list_time_and_cost\" />\n\n    <ImageView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@mipmap/dk_more_icon\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_bias=\"0.5\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_performance_close.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--性能监控包裹-->\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/fl_chart\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"100dp\"\n    android:layout_gravity=\"right\">\n\n    <ImageView\n        android:id=\"@+id/iv_close\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"top|right\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginRight=\"10dp\"\n        android:src=\"@mipmap/dk_close_white\" />\n</FrameLayout>\n\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_performance_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"40dp\"\n    android:layout_marginLeft=\"15dp\"\n    android:layout_marginRight=\"15dp\"\n    android:layout_marginBottom=\"5dp\"\n    android:background=\"@drawable/dk_item_performance_detail\"\n    android:gravity=\"center_horizontal\">\n\n    <TextView\n        android:id=\"@+id/date\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignTop=\"@+id/time\"\n        android:layout_marginLeft=\"10dp\"\n        android:textColor=\"@color/dk_color_7FFFFFFF\" />\n\n    <TextView\n        android:id=\"@+id/time\"\n        style=\"@style/DK.Text.White\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerVertical=\"true\"\n        android:layout_marginLeft=\"15dp\"\n        android:layout_toRightOf=\"@+id/date\" />\n\n    <TextView\n        android:id=\"@+id/parameter\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignTop=\"@+id/date\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_marginRight=\"50dp\"\n        android:drawableLeft=\"@mipmap/dk_performance_up_arrow\"\n        android:textColor=\"@color/dk_color_79DE79\" />\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_performance_wrap.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--性能监控包裹-->\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/fl_chart\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <com.didichuxing.doraemonkit.kit.performance.widget.LineChart\n        android:id=\"@+id/linechart\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n    <ImageView\n        android:id=\"@+id/iv_close\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"top|right\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginRight=\"10dp\"\n        android:src=\"@mipmap/dk_close_white\" />\n</FrameLayout>\n\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_setting.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"52dp\"\n    android:background=\"@drawable/dk_setting_item_background\">\n\n    <TextView\n        android:id=\"@+id/desc\"\n        style=\"@style/DK.TextBig.Dark\"\n        android:layout_centerVertical=\"true\"\n        android:layout_marginLeft=\"16dp\"\n        android:text=\"@string/dk_app_name\" />\n\n    <CheckBox\n        android:id=\"@+id/menu_switch\"\n        style=\"@style/DK.CheckBox\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_marginRight=\"16dp\"\n        android:layout_centerVertical=\"true\"\n        android:visibility=\"gone\" />\n\n    <ImageView\n        android:id=\"@+id/right_icon\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerVertical=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_marginRight=\"16dp\"\n        android:visibility=\"gone\" />\n\n    <View\n        style=\"@style/DK.Divider\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginRight=\"16dp\" />\n\n    <TextView\n        android:id=\"@+id/right_desc\"\n        style=\"@style/DK.TextBig.Dark\"\n        android:layout_centerVertical=\"true\"\n        android:layout_alignWithParentIfMissing=\"true\"\n        android:layout_toLeftOf=\"@id/right_icon\"\n        android:layout_marginRight=\"15dp\"\n        android:text=\"@string/dk_app_name\"\n        android:visibility=\"gone\"/>\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_sp_input.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"40dp\">\n\n    <TextView\n        android:gravity=\"center\"\n        android:id=\"@+id/tv_sp_key\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\" />\n\n    <TextView\n\n        android:id=\"@+id/tv_sp_type\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:gravity=\"center\" />\n\n    <com.didichuxing.doraemonkit.kit.fileexplorer.SpInputView\n        android:id=\"@+id/input_sp_value\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_sys_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    style=\"@style/DK.CommonItemAppInfo\">\n\n    <com.didichuxing.doraemonkit.widget.textview.LabelTextView\n        android:id=\"@+id/label_text\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_sys_title.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              style=\"@style/DK.CommonItem\"\n              android:layout_height=\"56dp\"\n              android:gravity=\"center_vertical\"\n              android:paddingLeft=\"5dp\">\n\n    <TextView\n        android:id=\"@+id/tv_title\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@color/dk_color_999999\"\n        android:textSize=\"@dimen/dk_font_size_14\"/>\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_text_content.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <TextView\n        style=\"@style/DK.Text.Darker\"\n        android:id=\"@+id/text\"\n        android:gravity=\"left\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_third_lib_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:paddingLeft=\"10dp\"\n    android:paddingTop=\"14dp\"\n    android:paddingRight=\"10dp\"\n    android:paddingBottom=\"14dp\">\n\n\n    <TextView\n        android:id=\"@+id/tv_size\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:layout_marginLeft=\"10dp\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"@dimen/dk_font_size_14\"\n        tools:text=\"sss\" />\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_toLeftOf=\"@id/tv_size\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"@dimen/dk_font_size_14\"\n        tools:text=\"sssss\" />\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_time_counter_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              style=\"@style/DK.CommonItem\"\n              android:layout_height=\"wrap_content\"\n              android:orientation=\"horizontal\">\n\n    <LinearLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginRight=\"80dp\"\n        android:layout_weight=\"1\"\n        android:orientation=\"vertical\"\n        android:paddingLeft=\"5dp\"\n        android:paddingRight=\"5dp\">\n\n        <TextView\n            android:id=\"@+id/title\"\n            style=\"@style/DK.Text.Dark\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:gravity=\"left|center_vertical\"\n            android:text=\"1233\"\n            />\n\n        <TextView\n            android:id=\"@+id/total_cost\"\n            style=\"@style/DK.TextSmall.Dark\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"10dp\"/>\n\n        <TextView\n            android:id=\"@+id/pause_cost\"\n            style=\"@style/DK.TextSmall.Dark\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"10dp\"/>\n\n        <TextView\n            android:id=\"@+id/launch_cost\"\n            style=\"@style/DK.TextSmall.Dark\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"10dp\"/>\n\n        <TextView\n            android:id=\"@+id/render_cost\"\n            style=\"@style/DK.TextSmall.Dark\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"10dp\"/>\n\n        <TextView\n            android:id=\"@+id/other_cost\"\n            style=\"@style/DK.TextSmall.Dark\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"10dp\"/>\n\n    </LinearLayout>\n\n    <TextView\n        android:id=\"@+id/time\"\n        style=\"@style/DK.TextSmall.Dark\"\n        android:layout_width=\"80dp\"\n        android:layout_gravity=\"top\"\n        android:layout_marginTop=\"15dp\"\n        android:gravity=\"right\"\n        android:paddingLeft=\"20dp\"\n        android:text=\"11111111\"/>\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_tips_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"37dp\"\n    android:layout_marginTop=\"15.5dp\">\n\n    <RelativeLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_weight=\"1\">\n\n        <TextView\n            android:id=\"@+id/date\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentEnd=\"true\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_marginRight=\"63.5dp\"\n            android:textColor=\"@color/dk_color_7FFFFFFF\"\n            android:textSize=\"8sp\" />\n\n        <TextView\n            android:id=\"@+id/time\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/date\"\n            android:layout_alignParentEnd=\"true\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginTop=\"3dp\"\n            android:layout_marginRight=\"25dp\"\n            android:textColor=\"@color/dk_color_7FFFFFFF\"\n            android:textSize=\"20sp\" />\n    </RelativeLayout>\n\n    <View\n        android:layout_width=\"0.5dp\"\n        android:layout_height=\"match_parent\"\n        android:background=\"#FF58585E\" />\n\n    <RelativeLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_weight=\"1\">\n\n        <TextView\n            android:id=\"@+id/model\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"25dp\"\n            android:layout_marginRight=\"63.5dp\"\n            android:textColor=\"@color/dk_color_7FFFFFFF\"\n            android:textSize=\"8sp\" />\n\n        <TextView\n            android:id=\"@+id/parameter\"\n            style=\"@style/DK.Text.White\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/model\"\n            android:layout_marginLeft=\"25dp\"\n            android:layout_marginTop=\"3dp\"\n            android:textSize=\"20sp\" />\n    </RelativeLayout>\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_web_door_history.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"45dp\"\n    android:background=\"@drawable/dk_web_door_history_item_background\">\n\n    <TextView\n        android:id=\"@+id/content\"\n        style=\"@style/DK.TextBig.Dark\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"left|center_vertical\"\n        android:layout_marginLeft=\"5dp\"\n        android:drawableLeft=\"@mipmap/dk_web_door_history_item_magnifier\"\n        android:drawablePadding=\"10dp\" />\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_item_white_host.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"50dp\">\n\n\n    <FrameLayout\n        android:id=\"@+id/fl_add_wrap\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:padding=\"10dp\">\n\n        <TextView\n            android:id=\"@+id/tv_add\"\n            android:layout_width=\"20dp\"\n            android:layout_height=\"20dp\"\n            android:background=\"@drawable/dk_add_shape\"\n            android:gravity=\"center\"\n            android:text=\"+\"\n            android:textSize=\"14sp\" />\n    </FrameLayout>\n\n    <EditText\n        android:id=\"@+id/ed_host\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerVertical=\"true\"\n        android:layout_toLeftOf=\"@id/fl_add_wrap\"\n        android:background=\"@null\"\n        android:digits=\"0123456789abcdefghijklmnopqrstuvwxyz.\"\n        android:hint=\"@string/dk_kit_net_monitor_white_host_edit_tip\"\n        android:padding=\"10dp\"\n        android:textSize=\"16sp\" />\n\n    <View\n        style=\"@style/DK.Divider\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginRight=\"16dp\" />\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_jsonviewer_layout_item_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\">\n\n    <TextView\n        android:id=\"@+id/tv_left\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textSize=\"12sp\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_icon\"\n        android:layout_width=\"6dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"center\"\n        android:layout_marginTop=\"1dp\"\n        android:adjustViewBounds=\"true\"\n        android:contentDescription=\"@string/dk_jsonViewer_icon_plus\"\n        android:padding=\"2dp\"\n        android:scaleType=\"fitCenter\"\n        android:src=\"@drawable/dk_jsonviewer_plus\"\n        android:visibility=\"gone\"\n        app:tint=\"#999999\"\n        tools:visibility=\"visible\" />\n\n    <TextView\n        android:id=\"@+id/tv_right\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"1dp\"\n        android:layout_marginStart=\"1dp\"\n        android:gravity=\"center_vertical\"\n        android:textSize=\"12sp\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\" />\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_label_text_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <TextView\n        android:id=\"@+id/label\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:gravity=\"left\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"@dimen/dk_font_size_16\" />\n\n    <TextView\n        android:id=\"@+id/text\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginLeft=\"10dp\"\n        android:gravity=\"right\"\n        android:textColor=\"@color/dk_color_666666\"\n        android:textIsSelectable=\"true\"\n        android:textSize=\"@dimen/dk_font_size_16\" />\n</merge>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_layout_localstorage_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:src=\"@mipmap/dk_logo\" />\n\n        <TextView\n            android:id=\"@+id/tv_value\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:text=\"has no localStorage data\"\n            android:textSize=\"14sp\" />\n\n    </LinearLayout>\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_layout_sessionstorage_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:src=\"@mipmap/dk_logo\" />\n\n        <TextView\n            android:id=\"@+id/tv_value\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:text=\"has no sessionStorage data\"\n            android:textSize=\"14sp\" />\n\n    </LinearLayout>\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_light_hint_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/fl_light\"\n    android:layout_width=\"80dp\"\n    android:layout_height=\"80dp\"\n    android:visibility=\"gone\"\n    android:background=\"@drawable/dk_hint_bg\"\n    android:layout_centerInParent=\"true\">\n    <ImageView\n        android:id=\"@+id/iv_volume_hint\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@mipmap/dk_ic_light_hint\"\n        android:layout_gravity=\"center\"\n        android:scaleType=\"center\"\n        />\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_log_title_bar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <TextView\n        android:id=\"@+id/back\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical|left\"\n        android:layout_marginLeft=\"15dp\"\n        android:textColor=\"#337CC4\"\n        android:textSize=\"14sp\" />\n\n    <TextView\n        android:id=\"@+id/title\"\n        style=\"@style/DK.TitleBig\"\n        android:layout_gravity=\"center\" />\n\n    <ImageView\n        android:id=\"@+id/icon\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical|right\"\n        android:layout_marginRight=\"15dp\"\n        android:scaleType=\"centerInside\" />\n\n</merge>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_main_launch_icon.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <ImageView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@mipmap/dk_doraemon\" />\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_mock_intercept_content_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/dk_color_F5F6F7\"\n    android:orientation=\"vertical\"\n    android:padding=\"16dp\">\n\n    <TextView\n        android:id=\"@+id/tv_path\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@color/dk_color_666666\"\n        android:textSize=\"14sp\"\n        tools:text=\"接口1\" />\n\n    <!--    <TextView-->\n    <!--        android:id=\"@+id/tv_method\"-->\n    <!--        android:layout_marginTop=\"8dp\"-->\n    <!--        android:layout_width=\"wrap_content\"-->\n    <!--        android:layout_height=\"wrap_content\"-->\n    <!--        android:textColor=\"@color/dk_color_666666\"-->\n    <!--        android:textSize=\"14sp\"-->\n    <!--        tools:text=\"get\" />-->\n\n    <RelativeLayout\n        android:id=\"@+id/rl_query\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/tv_query\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_alignParentTop=\"true\"\n            android:text=\"query:\"\n            android:textColor=\"@color/dk_color_666666\"\n            android:textSize=\"14sp\" />\n\n        <com.didichuxing.doraemonkit.widget.jsonviewer.JsonRecyclerView\n            android:id=\"@+id/jsonviewer_query\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_toRightOf=\"@id/tv_query\" />\n    </RelativeLayout>\n\n    <RelativeLayout\n        android:id=\"@+id/rl_body\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/tv_body\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_alignParentTop=\"true\"\n            android:text=\"body:\"\n            android:textColor=\"@color/dk_color_666666\"\n            android:textSize=\"14sp\" />\n\n        <com.didichuxing.doraemonkit.widget.jsonviewer.JsonRecyclerView\n            android:id=\"@+id/jsonviewer_body\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_toRightOf=\"@id/tv_body\" />\n    </RelativeLayout>\n\n\n    <TextView\n        android:id=\"@+id/tv_group\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:textColor=\"@color/dk_color_666666\"\n        android:textSize=\"14sp\"\n        tools:text=\"分组:\" />\n\n    <TextView\n        android:id=\"@+id/tv_create\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:textColor=\"@color/dk_color_666666\"\n        android:textSize=\"14sp\"\n        tools:text=\"创建人:\" />\n\n    <TextView\n        android:id=\"@+id/tv_modify\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:textColor=\"@color/dk_color_666666\"\n        android:textSize=\"14sp\"\n        tools:text=\"修改人:\" />\n\n    <com.didichuxing.doraemonkit.widget.MultiLineRadioGroup\n        android:id=\"@+id/radio_group\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"6dp\"\n        app:max_in_row=\"1\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_mock_template_content_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/dk_color_F5F6F7\"\n    android:orientation=\"vertical\"\n    android:padding=\"16dp\">\n\n    <TextView\n        android:id=\"@+id/tv_path\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@color/dk_color_666666\"\n        android:textSize=\"14sp\"\n        tools:text=\"接口1\" />\n\n\n    <RelativeLayout\n        android:id=\"@+id/rl_query\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/tv_query\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_alignParentTop=\"true\"\n            android:text=\"query:\"\n            android:textColor=\"@color/dk_color_666666\"\n            android:textSize=\"14sp\" />\n\n        <com.didichuxing.doraemonkit.widget.jsonviewer.JsonRecyclerView\n            android:id=\"@+id/jsonviewer_query\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_toRightOf=\"@id/tv_query\" />\n    </RelativeLayout>\n\n    <RelativeLayout\n        android:id=\"@+id/rl_body\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/tv_body\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_alignParentTop=\"true\"\n            android:text=\"body:\"\n            android:textColor=\"@color/dk_color_666666\"\n            android:textSize=\"14sp\" />\n\n        <com.didichuxing.doraemonkit.widget.jsonviewer.JsonRecyclerView\n            android:id=\"@+id/jsonviewer_body\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_toRightOf=\"@id/tv_body\" />\n    </RelativeLayout>\n\n\n    <TextView\n        android:id=\"@+id/tv_group\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:textColor=\"@color/dk_color_666666\"\n        android:textSize=\"14sp\"\n        tools:text=\"分组:\" />\n\n    <TextView\n        android:id=\"@+id/tv_create\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:textColor=\"@color/dk_color_666666\"\n        android:textSize=\"14sp\"\n        tools:text=\"创建人:\" />\n\n    <TextView\n        android:id=\"@+id/tv_modify\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:textColor=\"@color/dk_color_666666\"\n        android:textSize=\"14sp\"\n        tools:text=\"修改人:\" />\n\n\n    <TextView\n        android:id=\"@+id/tv_local_has_mock_template\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:text=\"本地是否存在mock模板数据\"\n        android:textColor=\"@color/dk_color_666666\"\n        android:textSize=\"14sp\" />\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/tv_view\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"Preview\"\n            android:textColor=\"@color/dk_color_337CC4\"\n            android:textSize=\"14sp\" />\n\n        <TextView\n            android:id=\"@+id/tv_upload\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"24dp\"\n            android:text=\"Upload\"\n            android:textColor=\"@color/dk_color_337CC4\"\n            android:textSize=\"14sp\" />\n    </LinearLayout>\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_mock_title_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"52dp\"\n    android:orientation=\"vertical\"\n    android:paddingLeft=\"16dp\"\n    android:paddingRight=\"16dp\">\n\n\n    <ImageView\n        android:id=\"@+id/iv_more\"\n        android:layout_width=\"12dp\"\n        android:layout_height=\"12dp\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:src=\"@mipmap/dk_arrow_normal\" />\n\n\n    <CheckBox\n        android:id=\"@+id/menu_switch\"\n        style=\"@style/DK.CheckBox\"\n        android:layout_centerVertical=\"true\"\n        android:layout_marginRight=\"26dp\"\n        android:layout_toLeftOf=\"@id/iv_more\" />\n\n    <TextView\n        android:id=\"@+id/tv_title\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:layout_toLeftOf=\"@+id/menu_switch\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"16sp\"\n        tools:text=\"接口1\" />\n\n    <View\n        style=\"@style/DK.Divider\"\n        android:layout_alignParentBottom=\"true\" />\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_performance_close_wrap.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--性能监控包裹-->\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/ll_close_wrap\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <FrameLayout\n        android:id=\"@+id/fl_wrap0\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"100dp\"\n        android:layout_gravity=\"right\">\n\n        <ImageView\n            android:id=\"@+id/iv_close0\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top|right\"\n            android:padding=\"10dp\"\n            android:src=\"@mipmap/dk_close_white\" />\n    </FrameLayout>\n\n    <FrameLayout\n        android:id=\"@+id/fl_wrap1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"100dp\"\n        android:layout_gravity=\"right\">\n\n\n        <ImageView\n            android:id=\"@+id/iv_close1\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top|right\"\n            android:padding=\"10dp\"\n            android:src=\"@mipmap/dk_close_white\" />\n    </FrameLayout>\n\n    <FrameLayout\n        android:id=\"@+id/fl_wrap2\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"100dp\"\n        android:layout_gravity=\"right\">\n\n\n        <ImageView\n            android:id=\"@+id/iv_close2\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top|right\"\n            android:padding=\"10dp\"\n            android:src=\"@mipmap/dk_close_white\" />\n    </FrameLayout>\n\n    <FrameLayout\n        android:id=\"@+id/fl_wrap3\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"100dp\"\n        android:layout_gravity=\"right\">\n\n\n        <ImageView\n            android:id=\"@+id/iv_close3\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top|right\"\n            android:padding=\"10dp\"\n            android:src=\"@mipmap/dk_close_white\" />\n    </FrameLayout>\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_performance_wrap.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--性能监控包裹-->\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/ll_performance_wrap\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <FrameLayout\n        android:id=\"@+id/fl_chart0\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.didichuxing.doraemonkit.kit.performance.widget.LineChart\n            android:id=\"@+id/linechart0\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:tag=\"lineChart\" />\n\n        <ImageView\n            android:id=\"@+id/iv_close0\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top|right\"\n            android:padding=\"10dp\"\n            android:src=\"@mipmap/dk_close_white\"\n            android:tag=\"closeView\" />\n    </FrameLayout>\n\n    <FrameLayout\n        android:id=\"@+id/fl_chart1\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.didichuxing.doraemonkit.kit.performance.widget.LineChart\n            android:id=\"@+id/linechart1\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:tag=\"lineChart\" />\n\n        <ImageView\n            android:id=\"@+id/iv_close1\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top|right\"\n            android:padding=\"10dp\"\n            android:src=\"@mipmap/dk_close_white\"\n            android:tag=\"closeView\" />\n\n    </FrameLayout>\n\n    <FrameLayout\n        android:id=\"@+id/fl_chart2\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.didichuxing.doraemonkit.kit.performance.widget.LineChart\n            android:id=\"@+id/linechart2\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:tag=\"lineChart\" />\n\n        <ImageView\n            android:id=\"@+id/iv_close2\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top|right\"\n            android:padding=\"10dp\"\n            android:src=\"@mipmap/dk_close_white\"\n            android:tag=\"closeView\" />\n\n    </FrameLayout>\n\n    <FrameLayout\n        android:id=\"@+id/fl_chart3\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.didichuxing.doraemonkit.kit.performance.widget.LineChart\n            android:id=\"@+id/linechart3\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:tag=\"lineChart\" />\n\n        <ImageView\n            android:id=\"@+id/iv_close3\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top|right\"\n            android:padding=\"10dp\"\n            android:src=\"@mipmap/dk_close_white\"\n            android:tag=\"closeView\" />\n\n    </FrameLayout>\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_radio_button.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RadioButton xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/multi_line_radio_group_default_radio_button\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:textSize=\"16sp\" />"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_radio_table_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TableLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/multi_line_radio_group_default_table_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:stretchColumns=\"*\" />"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_radio_table_row.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TableRow xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/multi_line_radio_group_default_table_row\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\" />"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_refresh_default_load_more.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:paddingBottom=\"16dp\"\n    android:background=\"@android:color/white\"\n    android:paddingTop=\"16dp\">\n\n\n    <TextView\n        android:id=\"@+id/tv_hit_content\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:text=\"正在加载...\"\n        android:textColor=\"#000000\" />\n\n    <com.github.ybq.android.spinkit.SpinKitView\n        android:id=\"@+id/spin_kit\"\n        style=\"@style/SpinKitView.Small.Circle\"\n        android:layout_width=\"30dp\"\n        android:layout_height=\"30dp\"\n        android:layout_centerVertical=\"true\"\n        android:layout_marginRight=\"8dp\"\n        android:layout_toLeftOf=\"@id/tv_hit_content\"\n        app:SpinKit_Color=\"#FF4081\" />\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_refresh_default_refresh_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"60dp\">\n\n\n    <TextView\n        android:id=\"@+id/text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:textColor=\"#000000\"\n        tools:text=\"下拉刷新\" />\n\n    <View\n        android:id=\"@+id/successIcon\"\n        android:layout_width=\"13dp\"\n        android:layout_height=\"13dp\"\n        android:layout_centerInParent=\"true\"\n        android:layout_marginRight=\"5dp\"\n        android:layout_toLeftOf=\"@id/text\"\n        android:background=\"@mipmap/dk_refresh_cpmplete_icon\"\n        android:visibility=\"invisible\"\n        tools:ignore=\"RtlHardcoded\" />\n\n    <ImageView\n        android:id=\"@+id/arrowIcon\"\n        android:layout_width=\"35dp\"\n        android:layout_height=\"35dp\"\n        android:layout_centerVertical=\"true\"\n        android:layout_marginRight=\"16dp\"\n        android:layout_toLeftOf=\"@+id/text\"\n        android:scaleType=\"fitCenter\"\n        android:src=\"@mipmap/dk_refresh_arrow\" />\n\n    <com.github.ybq.android.spinkit.SpinKitView\n        android:id=\"@+id/loadingIcon\"\n        style=\"@style/SpinKitView.Small.Circle\"\n        android:layout_width=\"30dp\"\n        android:layout_height=\"30dp\"\n        android:layout_centerVertical=\"true\"\n        android:layout_gravity=\"center\"\n        android:layout_marginRight=\"5dp\"\n        android:layout_toLeftOf=\"@+id/text\"\n        android:padding=\"3dp\"\n        android:visibility=\"gone\"\n        app:SpinKit_Color=\"#FF4081\" />\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_rv_empty_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginRight=\"16dp\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:layout_width=\"120dp\"\n            android:layout_height=\"120dp\"\n            android:layout_gravity=\"center\"\n            android:src=\"@mipmap/dk_logo\" />\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"10dp\"\n            android:text=\"暂无mock接口列表数据,请到www.dokit.cn添加mock接口\" />\n    </LinearLayout>\n\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_rv_empty_layout2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginRight=\"16dp\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:layout_width=\"120dp\"\n            android:layout_height=\"120dp\"\n            android:layout_gravity=\"center\"\n            android:src=\"@mipmap/dk_logo\" />\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"10dp\"\n            android:text=\"暂无匹配的mock接口列表数据\" />\n    </LinearLayout>\n\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_title_bar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"horizontal\"\n        android:layout_gravity=\"left|center_vertical\">\n\n        <ImageView\n            android:id=\"@+id/left_icon\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginLeft=\"15dp\"\n            android:visibility=\"gone\"/>\n\n        <TextView\n            android:id=\"@+id/left_text\"\n            style=\"@style/DK.TextBig\"\n            android:textColor=\"@color/dk_color_337CC4\"\n            android:layout_gravity=\"center_vertical\"\n            android:visibility=\"gone\"/>\n\n    </LinearLayout>\n\n    <TextView\n        android:id=\"@+id/title\"\n        style=\"@style/DK.TitleBar\"\n        android:visibility=\"gone\"/>\n\n    <FrameLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"right|center_vertical\">\n\n        <CheckBox\n            android:id=\"@+id/right_check\"\n            style=\"@style/DK.CheckBox\"\n            android:layout_gravity=\"center_vertical|right\"\n            android:layout_marginRight=\"15dp\"\n            android:visibility=\"gone\"/>\n\n        <ImageView\n            android:id=\"@+id/right_icon\"\n            android:layout_width=\"30dp\"\n            android:layout_height=\"30dp\"\n            android:layout_gravity=\"center_vertical|right\"\n            android:layout_marginRight=\"15dp\"\n            android:padding=\"5dp\"\n            android:scaleType=\"fitXY\"\n            android:visibility=\"gone\"/>\n\n        <TextView\n            android:id=\"@+id/right_text\"\n            style=\"@style/DK.Text.Normal\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginRight=\"10dp\"\n            android:visibility=\"gone\"/>\n    </FrameLayout>\n</merge>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_tool_panel.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/dk_title_height\"\n        app:dkLeftIcon=\"@mipmap/title_back\"\n        app:dkLeftText=\"@string/dk_back\"\n        app:dkRightText=\"@string/dk_setting\"\n        app:dkTitle=\"DoKit\" />\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/dk_dp_5\"\n        android:background=\"@color/dk_color_F5F6F7\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv_kits\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        android:overScrollMode=\"never\"\n        android:scrollbars=\"none\" />\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_tool_panel_old.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/dk_home_title_height\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_list\" />\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/dk_dp_5\"\n        android:background=\"@color/dk_color_F5F6F7\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/group_kit_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@color/dk_color_F5F6F7\"\n        android:orientation=\"vertical\"\n        android:overScrollMode=\"never\"\n        android:scrollbars=\"none\" />\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_video_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/rl_container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"290dp\">\n    <com.didichuxing.doraemonkit.widget.videoview.CustomVideoView\n        android:id=\"@+id/videoView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"240dp\"\n        />\n    <include layout=\"@layout/dk_volume_hint_layout\"/>\n    <include layout=\"@layout/dk_light_hint_layout\"/>\n    <LinearLayout\n        android:id=\"@+id/lly_controller\"\n        android:orientation=\"vertical\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\">\n        <SeekBar\n            android:id=\"@+id/seekbar_progress\"\n            android:thumb=\"@null\"\n            android:progressDrawable=\"@drawable/dk_seekbar_style\"\n            android:indeterminate=\"false\"\n            android:paddingLeft=\"0dp\"\n            android:paddingRight=\"0dp\"\n            android:layout_marginLeft=\"-20dp\"\n            android:layout_marginRight=\"-20dp\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"10dp\" />\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            android:background=\"#101010\">\n            <ImageView\n                android:id=\"@+id/btn_controller\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"16dp\"\n                android:src=\"@drawable/dk_btn_play_style\"\n                />\n            <TextView\n                android:id=\"@+id/tv_currentProgress\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"00:00:00\"\n                android:textSize=\"14sp\"\n                android:layout_marginLeft=\"32dp\"\n                android:textColor=\"@android:color/white\"\n                />\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\" / \"\n                android:textColor=\"@android:color/white\"\n                />\n            <TextView\n                android:id=\"@+id/tv_totalProgress\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"00:00:00\"\n                android:textSize=\"14sp\"\n                android:textColor=\"@android:color/white\"\n                />\n            <RelativeLayout\n                android:layout_width=\"0dp\"\n                android:layout_weight=\"1\"\n                android:layout_marginLeft=\"32dp\"\n                android:layout_height=\"match_parent\">\n                <ImageView\n                    android:id=\"@+id/iv_volume\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:src=\"@mipmap/dk_ic_volume\"\n                    android:layout_marginLeft=\"16dp\"\n                    android:layout_marginRight=\"16dp\"\n                    android:layout_centerVertical=\"true\"\n                    android:visibility=\"gone\"\n                    />\n                <SeekBar\n                    android:id=\"@+id/seekbar_volume\"\n                    android:layout_width=\"120dp\"\n                    android:layout_height=\"5dp\"\n                    android:layout_marginLeft=\"-20dp\"\n                    android:layout_marginRight=\"-20dp\"\n                    android:thumb=\"@null\"\n                    android:layout_toRightOf=\"@id/iv_volume\"\n                    android:layout_centerVertical=\"true\"\n                    android:progressDrawable=\"@drawable/dk_seekbar_style\"\n                    android:visibility=\"gone\"\n                    />\n                <ImageView\n                    android:visibility=\"gone\"\n                    android:id=\"@+id/btn_screen\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:src=\"@mipmap/dk_ic_screen\"\n                    android:layout_marginRight=\"16dp\"\n                    android:layout_marginLeft=\"16dp\"\n                    android:layout_alignParentRight=\"true\"\n                    android:layout_centerVertical=\"true\"\n                    />\n            </RelativeLayout>\n        </LinearLayout>\n    </LinearLayout>\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_view_line_chart.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"100dp\"\n    android:background=\"@color/dk_color_aa000000\">\n\n    <TextView\n        style=\"@style/DK.Text.White\"\n        android:layout_gravity=\"bottom\"\n        android:layout_marginLeft=\"10dp\"\n        android:layout_marginBottom=\"3dp\"\n        android:text=\"0\"\n        android:textSize=\"@dimen/dk_font_size_10\" />\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0.5dp\"\n        android:layout_gravity=\"bottom\"\n        android:layout_marginLeft=\"25dp\"\n        android:layout_marginBottom=\"10dp\"\n        android:background=\"@color/dk_color_33999999\" />\n\n    <com.didichuxing.doraemonkit.kit.performance.widget.CardiogramView\n        android:id=\"@+id/line_chart_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_marginTop=\"15dp\"\n        android:layout_marginBottom=\"8dp\" />\n\n    <TextView\n        android:id=\"@+id/tv_title\"\n        style=\"@style/DK.Text.White\"\n        android:layout_marginLeft=\"10dp\"\n        android:layout_marginTop=\"5dp\"\n        android:textSize=\"@dimen/dk_font_size_10\" />\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_view_network_detail_pager_title.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"44dp\"\n    android:clickable=\"true\"\n    tools:showIn=\"@layout/dk_fragment_network_monitor_detail\">\n\n    <RelativeLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_weight=\"1\">\n\n        <TextView\n            android:id=\"@+id/tv_pager_request\"\n            style=\"@style/DK.TextBig\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_centerInParent=\"true\"\n            android:text=\"@string/dk_network_request\"\n            android:textColor=\"@color/dk_network_pager_color\" />\n\n        <View\n            android:id=\"@+id/diver_request\"\n            android:layout_width=\"64dp\"\n            android:layout_height=\"2dp\"\n            android:layout_alignParentBottom=\"true\"\n            android:layout_centerHorizontal=\"true\"\n            android:background=\"@color/dk_color_337CC4\" />\n    </RelativeLayout>\n\n    <RelativeLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_weight=\"1\">\n\n        <TextView\n            android:id=\"@+id/tv_pager_response\"\n            style=\"@style/DK.TextBig\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_centerInParent=\"true\"\n            android:text=\"@string/dk_network_response\"\n            android:textColor=\"@color/dk_network_pager_color\" />\n\n        <View\n            android:id=\"@+id/diver_response\"\n            android:layout_width=\"64dp\"\n            android:layout_height=\"2dp\"\n            android:layout_alignParentBottom=\"true\"\n            android:layout_centerHorizontal=\"true\"\n            android:background=\"@color/dk_color_337CC4\"\n            android:visibility=\"gone\" />\n    </RelativeLayout>\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_view_network_request.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <ScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scrollbars=\"none\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"50dp\"\n                android:background=\"#f5f6f7\"\n                android:gravity=\"center_vertical\"\n                android:paddingLeft=\"16dp\"\n                android:text=\"@string/dk_network_detail_title_body\"\n                android:textColor=\"#337cc4\"\n                android:textSize=\"@dimen/dk_font_size_16\" />\n\n            <View style=\"@style/DK.Divider\" />\n\n            <RelativeLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\">\n\n                <TextView\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"50dp\"\n                    android:layout_alignParentLeft=\"true\"\n                    android:gravity=\"center_vertical\"\n                    android:paddingLeft=\"16dp\"\n                    android:text=\"@string/dk_network_detail_title_size\"\n                    android:textColor=\"#333333\"\n                    android:textSize=\"@dimen/dk_font_size_16\" />\n\n                <TextView\n                    android:id=\"@+id/tv_data_size\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"50dp\"\n                    android:layout_alignParentRight=\"true\"\n                    android:gravity=\"center_vertical\"\n                    android:paddingRight=\"16dp\"\n                    android:textColor=\"#666666\"\n                    android:textSize=\"@dimen/dk_font_size_16\" />\n            </RelativeLayout>\n\n            <View style=\"@style/DK.Divider\" />\n\n            <RelativeLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\">\n\n                <TextView\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"50dp\"\n                    android:layout_alignParentLeft=\"true\"\n                    android:gravity=\"center_vertical\"\n                    android:paddingLeft=\"16dp\"\n                    android:text=\"@string/dk_network_method\"\n                    android:textColor=\"#333333\"\n                    android:textSize=\"@dimen/dk_font_size_16\" />\n\n                <TextView\n                    android:id=\"@+id/tv_method\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"50dp\"\n                    android:layout_alignParentRight=\"true\"\n                    android:gravity=\"center_vertical\"\n                    android:paddingRight=\"16dp\"\n                    android:textColor=\"#666666\"\n                    android:textSize=\"@dimen/dk_font_size_16\" />\n            </RelativeLayout>\n\n            <View style=\"@style/DK.Divider\" />\n\n            <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"50dp\"\n                android:background=\"#f5f6f7\"\n                android:gravity=\"center_vertical\"\n                android:paddingLeft=\"16dp\"\n                android:text=\"@string/dk_network_detail_title_url\"\n                android:textColor=\"#337cc4\"\n                android:textSize=\"@dimen/dk_font_size_16\" />\n\n            <View style=\"@style/DK.Divider\" />\n\n            <TextView\n                android:id=\"@+id/tv_url\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center_vertical\"\n                android:minHeight=\"50dp\"\n                android:paddingLeft=\"16dp\"\n                android:paddingTop=\"10dp\"\n                android:paddingRight=\"16dp\"\n                android:paddingBottom=\"10dp\"\n                android:textColor=\"#333333\"\n                android:textIsSelectable=\"true\"\n                android:textSize=\"@dimen/dk_font_size_16\" />\n\n            <View style=\"@style/DK.Divider\" />\n\n            <TextView\n                android:id=\"@+id/diver_time\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"50dp\"\n                android:background=\"#f5f6f7\"\n                android:gravity=\"center_vertical\"\n                android:paddingLeft=\"16dp\"\n                android:text=\"@string/dk_network_detail_title_request_time\"\n                android:textColor=\"#337cc4\"\n                android:textSize=\"@dimen/dk_font_size_16\" />\n\n            <View style=\"@style/DK.Divider\" />\n\n            <TextView\n                android:id=\"@+id/tv_time\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center_vertical\"\n                android:minHeight=\"50dp\"\n                android:paddingLeft=\"16dp\"\n                android:paddingTop=\"10dp\"\n                android:paddingRight=\"16dp\"\n                android:paddingBottom=\"10dp\"\n                android:textColor=\"#333333\"\n                android:textSize=\"@dimen/dk_font_size_16\" />\n\n            <View style=\"@style/DK.Divider\" />\n\n            <TextView\n                android:id=\"@+id/diver_header\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"50dp\"\n                android:background=\"#f5f6f7\"\n                android:gravity=\"center_vertical\"\n                android:paddingLeft=\"16dp\"\n                android:text=\"@string/dk_network_detail_title_request_header\"\n                android:textColor=\"#337cc4\"\n                android:textSize=\"@dimen/dk_font_size_16\" />\n\n            <View style=\"@style/DK.Divider\" />\n\n            <TextView\n                android:id=\"@+id/tv_header\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center_vertical\"\n                android:minHeight=\"50dp\"\n                android:paddingLeft=\"16dp\"\n                android:paddingTop=\"10dp\"\n                android:paddingRight=\"16dp\"\n                android:paddingBottom=\"10dp\"\n                android:textColor=\"#333333\"\n                android:textIsSelectable=\"true\"\n                android:textSize=\"@dimen/dk_font_size_16\" />\n\n            <View style=\"@style/DK.Divider\" />\n\n            <RelativeLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"50dp\"\n                android:background=\"#f5f6f7\">\n\n                <TextView\n                    android:id=\"@+id/diver_body\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"match_parent\"\n                    android:layout_alignParentLeft=\"true\"\n                    android:layout_marginLeft=\"16dp\"\n                    android:gravity=\"center_vertical\"\n                    android:text=\"@string/dk_network_detail_title_request_body\"\n                    android:textColor=\"#337cc4\"\n                    android:textSize=\"@dimen/dk_font_size_16\" />\n\n\n                <TextView\n                    android:id=\"@+id/diver_format\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"match_parent\"\n                    android:layout_alignParentRight=\"true\"\n                    android:layout_marginRight=\"16dp\"\n                    android:gravity=\"center_vertical\"\n                    android:text=\"格式化\"\n                    android:textColor=\"#337cc4\"\n                    android:textSize=\"@dimen/dk_font_size_16\" />\n\n                <TextView\n                    android:id=\"@+id/diver_export\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"match_parent\"\n                    android:layout_marginRight=\"20dp\"\n                    android:layout_toLeftOf=\"@id/diver_format\"\n                    android:gravity=\"center_vertical\"\n                    android:text=\"导出\"\n                    android:textColor=\"#337cc4\"\n                    android:textSize=\"@dimen/dk_font_size_16\" />\n            </RelativeLayout>\n\n\n            <View style=\"@style/DK.Divider\" />\n\n            <TextView\n                android:id=\"@+id/tv_body\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center_vertical\"\n                android:minHeight=\"50dp\"\n                android:paddingLeft=\"16dp\"\n                android:paddingTop=\"14dp\"\n                android:paddingRight=\"16dp\"\n                android:paddingBottom=\"14dp\"\n                android:textColor=\"#333333\"\n                android:textIsSelectable=\"true\"\n                android:textSize=\"@dimen/dk_font_size_16\" />\n\n            <com.didichuxing.doraemonkit.widget.jsonviewer.JsonRecyclerView\n                android:id=\"@+id/json_body\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"16dp\"\n                android:layout_marginTop=\"14dp\"\n                android:layout_marginRight=\"16dp\"\n                android:layout_marginBottom=\"14dp\"\n\n                />\n        </LinearLayout>\n\n    </ScrollView>\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_view_network_tab_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"wrap_content\"\n              android:gravity=\"center\"\n              android:orientation=\"vertical\">\n\n    <ImageView\n        android:id=\"@+id/tab_icon\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"/>\n\n    <TextView\n        android:id=\"@+id/tab_text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"1dp\"\n        android:textColor=\"@color/dk_network_pager_color\"\n        android:textSize=\"10dp\"/>\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_volume_hint_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/fl_volume\"\n    android:layout_width=\"80dp\"\n    android:layout_height=\"80dp\"\n    android:visibility=\"gone\"\n    android:background=\"@drawable/dk_hint_bg\"\n    android:layout_centerInParent=\"true\">\n    <ImageView\n        android:id=\"@+id/iv_volume_hint\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@mipmap/dk_ic_volume_hint\"\n        android:layout_gravity=\"center\"\n        android:scaleType=\"center\"\n        />\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/dk_zxing_activity_scanner.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <SurfaceView\n            android:id=\"@+id/scanner_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_gravity=\"center\" />\n\n        <com.didichuxing.doraemonkit.zxing.view.ViewfinderView\n            android:id=\"@+id/viewfinder_content\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:dkCornerColor=\"@color/dk_color_337CC4\"\n            app:dkFrameColor=\"@color/dk_color_90FFFFFF\"\n            app:dkLabelText=\"\"\n            app:dkLabelTextColor=\"@color/dk_color_333333\"\n            app:dkLaserColor=\"@color/dk_color_337CC4\"\n            app:dkMaskColor=\"@color/dk_color_60000000\"\n            app:dkResultColor=\"@color/dk_color_000000\"\n            app:dkResultPointColor=\"@color/dk_color_60000000\" />\n\n    </FrameLayout>\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/kd_item_sp_input.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginRight=\"5dp\">\n\n    <TextView\n        android:id=\"@+id/tv_sp_value\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:drawableRight=\"@mipmap/dk_sp_modify\"\n        android:ellipsize=\"end\"\n        android:gravity=\"center\"\n        android:maxEms=\"6\"\n        android:singleLine=\"true\"\n        android:visibility=\"gone\" />\n\n    <Switch\n        android:id=\"@+id/switch_btn\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:visibility=\"gone\" />\n</FrameLayout>"
  },
  {
    "path": "Android/dokit/src/main/res/layout/layout_mock_pos_adjust.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/mock_location_root_container\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/dk_color_FFFFFF\">\n\n\n    <Button\n        android:id=\"@+id/btn_reset\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:minWidth=\"1dp\"\n        android:minHeight=\"1dp\"\n        android:text=\"重置\"\n        android:textAllCaps=\"false\"\n        android:textSize=\"9sp\"\n        app:layout_constraintEnd_toStartOf=\"@id/textView3\"\n        app:layout_constraintHorizontal_chainStyle=\"spread\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n\n    <TextView\n        android:id=\"@+id/textView3\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"位置微调\"\n        android:textColor=\"@android:color/black\"\n        app:layout_constraintBottom_toBottomOf=\"@id/btn_reset\"\n        app:layout_constraintEnd_toStartOf=\"@id/env_switch3\"\n        app:layout_constraintStart_toEndOf=\"@id/btn_reset\"\n        app:layout_constraintTop_toTopOf=\"@id/btn_reset\" />\n\n\n    <Switch\n        android:id=\"@+id/env_switch3\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:checked=\"true\"\n        app:layout_constraintBottom_toBottomOf=\"@id/btn_reset\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/textView3\"\n        app:layout_constraintTop_toTopOf=\"@id/btn_reset\" />\n\n\n    <SeekBar\n        android:id=\"@+id/dk_sb_seekBar\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:max=\"500\"\n        android:min=\"10\"\n        android:progress=\"10\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/btn_reset\" />\n\n\n    <LinearLayout\n        android:id=\"@+id/ll_mock_speed\"\n        android:layout_width=\"220dp\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:orientation=\"horizontal\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/dk_sb_seekBar\">\n\n        <Button\n            android:id=\"@+id/dk_btn_downMockSpeed\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:minWidth=\"1dp\"\n            android:minHeight=\"1dp\"\n            android:text=\"-\"\n            android:textSize=\"10dp\" />\n\n        <TextView\n            android:id=\"@+id/tv_mock_speed\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:text=\"步进速度控制:10\"\n            android:textColor=\"@android:color/black\" />\n\n        <Button\n            android:id=\"@+id/dk_btn_upMockSpeed\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:minWidth=\"1dp\"\n            android:minHeight=\"1dp\"\n            android:text=\"+\"\n            android:textSize=\"10dp\" />\n\n    </LinearLayout>\n\n\n    <Button\n        android:id=\"@+id/btn_mock_gps_north\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:minWidth=\"1dp\"\n        android:minHeight=\"1dp\"\n        android:text=\"上\"\n        android:textAllCaps=\"false\"\n        android:textSize=\"9sp\"\n        app:layout_constraintEnd_toStartOf=\"@+id/btn_mock_gps_south\"\n        app:layout_constraintHorizontal_chainStyle=\"spread\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/ll_mock_speed\" />\n\n    <Button\n        android:id=\"@+id/btn_mock_gps_south\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:minWidth=\"1dp\"\n        android:minHeight=\"1dp\"\n        android:text=\"下️\"\n        android:textAllCaps=\"false\"\n        android:textSize=\"9sp\"\n        app:layout_constraintEnd_toStartOf=\"@id/btn_mock_gps_west\"\n        app:layout_constraintStart_toEndOf=\"@id/btn_mock_gps_north\"\n        app:layout_constraintTop_toTopOf=\"@id/btn_mock_gps_north\" />\n\n    <Button\n        android:id=\"@+id/btn_mock_gps_west\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:minWidth=\"1dp\"\n        android:minHeight=\"1dp\"\n        android:text=\"左️️\"\n        android:textAllCaps=\"false\"\n        android:textSize=\"9sp\"\n        app:layout_constraintEnd_toStartOf=\"@id/btn_mock_gps_east\"\n        app:layout_constraintStart_toEndOf=\"@id/btn_mock_gps_south\"\n        app:layout_constraintTop_toTopOf=\"@id/btn_mock_gps_north\" />\n\n    <Button\n        android:id=\"@+id/btn_mock_gps_east\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:minWidth=\"1dp\"\n        android:minHeight=\"1dp\"\n        android:text=\"右️️️\"\n        android:textAllCaps=\"false\"\n        android:textSize=\"9sp\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/btn_mock_gps_west\"\n        app:layout_constraintTop_toTopOf=\"@id/btn_mock_gps_north\" />\n\n    <TextView\n        android:id=\"@+id/env_info3\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:lines=\"3\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"10sp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/btn_mock_gps_north\"\n        tools:text=\"env_info3\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "Android/dokit/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <attr name=\"dkTitle\" format=\"string\" />\n    <attr name=\"dkBack\" format=\"string\" />\n\n    <declare-styleable name=\"LabelTextView\">\n        <attr name=\"dkLabel\" format=\"string\" />\n        <attr name=\"dkText\" format=\"string\" />\n        <attr name=\"dkMaxLines\" format=\"integer\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"TitleBar\">\n        <attr name=\"dkTitle\" />\n        <attr name=\"dkTitleColor\" format=\"color\" />\n        <attr name=\"dkLeftIcon\" format=\"reference\" />\n        <attr name=\"dkLeftText\" format=\"string\" />\n        <attr name=\"dkRightIcon\" format=\"reference\" />\n        <attr name=\"dkRightSubIcon\" format=\"reference\" />\n        <attr name=\"dkTitleBackground\" format=\"color\" />\n        <attr name=\"dkRightText\" format=\"string\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"LayoutBorderView\">\n        <attr name=\"dkFill\" format=\"boolean\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"HomeTitleBar\">\n        <attr name=\"dkTitle\" />\n        <attr name=\"dkIcon\" format=\"reference\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"LogTitleBar\">\n        <attr name=\"dkBack\" />\n        <attr name=\"dkTitle\" />\n        <attr name=\"dkIcon\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"ViewfinderView\">\n        <attr name=\"dkCornerColor\" format=\"color\" />\n        <attr name=\"dkLaserColor\" format=\"color\" />\n        <attr name=\"dkFrameColor\" format=\"color\" />\n        <attr name=\"dkMaskColor\" format=\"color\" />\n        <attr name=\"dkResultPointColor\" format=\"color\" />\n        <attr name=\"dkResultColor\" format=\"color\" />\n        <attr name=\"dkLabelTextColor\" format=\"color\" />\n        <attr name=\"dkLabelText\" format=\"string\" />\n        <attr name=\"dkLabelTextSize\" format=\"float\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"DkDropDownMenu\">\n\n        <attr name=\"dk_ddunderlineColor\" format=\"color\" />\n        <attr name=\"dk_dddividerColor\" format=\"color\" />\n        <attr name=\"dk_ddtextSelectedColor\" format=\"color\" />\n        <attr name=\"dk_ddtextUnselectedColor\" format=\"color\" />\n        <attr name=\"dk_ddmenuBackgroundColor\" format=\"color\" />\n        <attr name=\"dk_ddmaskColor\" format=\"color\" />\n        <attr name=\"dk_ddmenuTextSize\" format=\"dimension\" />\n        <attr name=\"dk_ddmenuSelectedIcon\" format=\"reference\" />\n        <attr name=\"dk_ddmenuUnselectedIcon\" format=\"reference\" />\n        <attr name=\"dk_dddividerHeight\" format=\"dimension\" />\n        <attr name=\"dk_ddmenuIconOrientation\">\n            <flag name=\"left\" value=\"0\" />\n\n            <flag name=\"top\" value=\"1\" />\n\n            <flag name=\"right\" value=\"2\" />\n\n            <flag name=\"bottom\" value=\"3\" />\n        </attr>\n    </declare-styleable>\n\n    <!--多行RadioGroup-->\n\n\n    <declare-styleable name=\"dk_multi_line_radio_group\">\n        <attr name=\"max_in_row\" format=\"integer\" />\n        <attr name=\"radio_buttons\" format=\"reference\" />\n        <attr name=\"default_button\" format=\"string\" />\n    </declare-styleable>\n\n\n</resources>"
  },
  {
    "path": "Android/dokit/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"dk_color_333333\">#333333</color>\n    <color name=\"dk_color_666666\">#666666</color>\n    <color name=\"dk_color_999999\">#999999</color>\n    <color name=\"dk_color_33999999\">#33999999</color>\n    <color name=\"dk_color_CCCCCC\">#CCCCCC</color>\n    <color name=\"dk_color_E5E5E5\">#E5E5E5</color>\n    <color name=\"dk_color_F3F4F5\">#F3F4F5</color>\n    <color name=\"dk_color_FFFFFF\">#FFFFFF</color>\n    <color name=\"dk_color_DDDDDD\">#DDDDDD</color>\n    <color name=\"dk_color_33FFFFFF\">#33FFFFFF</color>\n    <color name=\"dk_color_aa000000\">#aa000000</color>\n    <color name=\"dk_color_000000\">#000000</color>\n    <color name=\"dk_color_151515\">#151515</color>\n    <color name=\"dk_color_2E2E3A\">#2E2E3A</color>\n    <color name=\"dk_color_7AE5E5E5\">#7AE5E5E5</color>\n    <color name=\"dk_color_FAD337\">#FAD337</color>\n    <color name=\"dk_color_55A8FD\">#55A8FD</color>\n    <color name=\"dk_color_BBBBBB\">#BBBBBB</color>\n    <color name=\"dk_color_BEBEBE\">#BEBEBE</color>\n    <color name=\"dk_color_0070BB\">#0070BB</color>\n    <color name=\"dk_color_000a7a\">#000a7a</color>\n    <color name=\"dk_color_0099dd\">#0099dd</color>\n    <color name=\"dk_color_48BB31\">#48BB31</color>\n    <color name=\"dk_color_BBBB23\">#BBBB23</color>\n    <color name=\"dk_color_FF0006\">#FF0006</color>\n    <color name=\"dk_color_8F0005\">#8F0005</color>\n    <color name=\"dk_color_CC3A4B\">#CC3A4B</color>\n    <color name=\"dk_color_30CC3A4B\">#30CC3A4B</color>\n    <color name=\"dk_color_F5F6F7\">#F5F6F7</color>\n    <color name=\"dk_color_324456\">#324456</color>\n    <color name=\"dk_color_D26282\">#D26282</color>\n    <color name=\"dk_color_F4F5F6\">#F4F5F6</color>\n    <color name=\"dk_color_337CC4\">#337CC4</color>\n    <color name=\"dk_color_3300E0DC\">#3300E0DC</color>\n    <color name=\"dk_color_3300BFFF\">#3300BFFF</color>\n    <color name=\"dk_color_33434352\">#33434352</color>\n    <color name=\"dk_color_79DE79\">#79DE79</color>\n    <color name=\"dk_color_EFFFFFFF\">#EFFFFFFF</color>\n    <color name=\"dk_color_7FFFFFFF\">#7FFFFFFF</color>\n    <color name=\"dk_color_3f3f46\">#3f3f46</color>\n    <color name=\"dk_color_333339\">#333339</color>\n    <color name=\"dk_color_4c00C9F4\">#4c00C9F4</color>\n    <color name=\"dk_color_ff00C9F4\">#ff00C9F4</color>\n    <color name=\"dk_color_60000000\">#60000000</color>\n    <color name=\"dk_color_90FFFFFF\">#90FFFFFF</color>\n\n    <color name=\"background_wtf\">#FF999900</color>\n    <color name=\"background_error\">#FFCC0000</color>\n    <color name=\"background_verbose\">#FF666666</color>\n    <color name=\"background_debug\">#FFFFFF00</color>\n    <color name=\"background_info\">#FF00CC00</color>\n    <color name=\"background_warn\">#FF0066CC</color>\n\n    <color name=\"foreground_wtf\">#FFFFFFFF</color>\n    <color name=\"foreground_error\">#FFFFFFFF</color>\n    <color name=\"foreground_verbose\">#FFCCCCCC</color>\n    <color name=\"foreground_debug\">#FF333333</color>\n    <color name=\"foreground_info\">#FF333333</color>\n    <color name=\"foreground_warn\">#FFCCCCCC</color>\n    <!--dropdownmenu-->\n    <color name=\"dk_drop_down_selected\">#337CC4</color>\n    <color name=\"dk_drop_down_unselected\">#333333</color>\n\n    <color name=\"dk_mask_color\">#88000000</color>\n    <color name=\"dk_check_bg\">#f2f2f2</color>\n    <color name=\"dk_un_press_color\">#337CC4</color>\n</resources>"
  },
  {
    "path": "Android/dokit/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <dimen name=\"dk_dp_15\">15dp</dimen>\n    <dimen name=\"dk_dp_5\">5dp</dimen>\n    <dimen name=\"dk_dp_30\">30dp</dimen>\n    <dimen name=\"dk_dp_40\">40dp</dimen>\n    <dimen name=\"dk_dp_48\">48dp</dimen>\n    <dimen name=\"dk_dp_98\">98dp</dimen>\n    <dimen name=\"dk_dp_20\">20dp</dimen>\n    <dimen name=\"dk_dp_16\">16dp</dimen>\n    <dimen name=\"dk_dp_18\">18dp</dimen>\n    <dimen name=\"dk_title_height\">46.5dp</dimen>\n    <dimen name=\"dk_home_title_height\">89dp</dimen>\n    <dimen name=\"dk_def_height\">50dp</dimen>\n\n    <dimen name=\"dk_dp_4\">4dp</dimen>\n    <dimen name=\"dk_dp_10\">10dp</dimen>\n    <dimen name=\"dk_sp_14\">14sp</dimen>\n</resources>\n"
  },
  {
    "path": "Android/dokit/src/main/res/values/font_sizes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <dimen name=\"dk_font_size_10\">10sp</dimen>\n    <dimen name=\"dk_font_size_12\">12sp</dimen>\n    <dimen name=\"dk_font_size_14\">14sp</dimen>\n    <dimen name=\"dk_font_size_16\">16sp</dimen>\n    <dimen name=\"dk_font_size_18\">18sp</dimen>\n    <dimen name=\"dk_font_size_22\">22sp</dimen>\n    <dimen name=\"dk_font_size_24\">24sp</dimen>\n</resources>"
  },
  {
    "path": "Android/dokit/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!--\n Copyright (C) 2008 ZXing authors\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n -->\n<resources>\n    <!-- Messages IDs -->\n    <item name=\"auto_focus\" type=\"id\" />\n    <item name=\"decode\" type=\"id\" />\n    <item name=\"decode_failed\" type=\"id\" />\n    <item name=\"decode_succeeded\" type=\"id\" />\n    <item name=\"launch_product_query\" type=\"id\" />\n    <item name=\"quit\" type=\"id\" />\n    <item name=\"restart_preview\" type=\"id\" />\n    <item name=\"return_scan_result\" type=\"id\" />\n    <item name=\"float_icon_id\" type=\"id\" />\n    <item name=\"dokit_contentview_id\" type=\"id\" />\n\n    <item name=\"dokit_app_contentview_id\" type=\"id\" />\n    <item name=\"dokit_view_border_id\" type=\"id\" />\n\n    <item name=\"dokit_title_bar\" type=\"id\" />\n\n    <item name=\"dokit_baseQuickAdapter_viewholder_support\" type=\"id\"/>\n    <item name=\"dokit_baseQuickAdapter_swiping_support\" type=\"id\"/>\n    <item name=\"dokit_baseQuickAdapter_dragging_support\" type=\"id\"/>\n    <item name=\"dokit_baseQuickAdapter_databinding_support\" type=\"id\"/>\n    <item name=\"dokit_test_windowNode\" type=\"id\"/>\n</resources>\n"
  },
  {
    "path": "Android/dokit/src/main/res/values/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"dk_app_name\">DoKit</string>\n\n    <string name=\"dk_category_biz\">业务专区</string>\n    <string name=\"dk_category_comms\">常用工具</string>\n    <string name=\"dk_category_lbs\">LBS</string>\n    <string name=\"dk_category_performance\">性能监控</string>\n    <string name=\"dk_category_large_image\">大图检测</string>\n    <string name=\"dk_category_ui\">视觉工具</string>\n    <string name=\"dk_category_platform\">平台工具</string>\n    <string name=\"dk_kit_network_mock\">数据Mock</string>\n    <string name=\"dk_kit_health\">健康体检</string>\n    <string name=\"dk_kit_file_transfer\">文件同步助手</string>\n    <string name=\"dk_category_weex\">Weex</string>\n\n    <string name=\"dk_kit_ui_performance\">UI层级</string>\n    <string name=\"dk_kit_sysinfo\">App信息</string>\n    <string name=\"dk_kit_develop\">开发者选项</string>\n    <string name=\"dk_kit_local_lang\">本地语言</string>\n    <string name=\"dk_kit_service_running\">运行中服务</string>\n    <string name=\"dk_kit_list\">工具列表</string>\n    <string name=\"dk_kit_demo\">demo</string>\n    <string name=\"dk_kit_file_explorer\">沙盒浏览</string>\n    <string name=\"dk_kit_frame_info\">帧率</string>\n    <string name=\"dk_kit_frame_info_desc\">帧率检测</string>\n    <string name=\"dk_kit_gps_mock\">位置模拟</string>\n    <string name=\"dk_kit_gps_mock_manual\">位置微调</string>\n\n    <string name=\"dk_kit_gps_mock_route\">实时导航</string>\n    <string name=\"dk_kit_color_picker\">取色器</string>\n    <string name=\"dk_kit_align_ruler\">对齐标尺</string>\n    <string name=\"dk_kit_log_info\">日志</string>\n    <string name=\"dk_kit_log_min\">最小化</string>\n    <string name=\"dk_kit_web_door\">H5任意门</string>\n    <string name=\"dk_kit_temporary_close\">隐藏</string>\n    <string name=\"dk_kit_crash\">Crash</string>\n    <string name=\"dk_kit_data_clean\">清理缓存</string>\n    <string name=\"dk_kit_cache_check_all\">全选</string>\n    <string name=\"dk_kit_db_debug\">DBView</string>\n    <string name=\"dk_kit_weak_network\">模拟弱网</string>\n    <string name=\"dk_kit_setting\">更多</string>\n    <string name=\"dk_kit_view_check\">控件检查</string>\n    <string name=\"dk_kit_net_monitor\">网络</string>\n    <string name=\"dk_kit_net_monitor_white_host_tip\">白名单(默认全部拦截,如有添加白名单则只会拦截白名单中的网络请求,同时白名单规则在关闭当前页面时生效。白名单host示例:m.baidu.com)</string>\n    <string name=\"dk_kit_net_monitor_white_host_edit_tip\">请输入白名单host</string>\n    <string name=\"dk_kit_net_monitor_white_host_edit_toast\">请先输入host</string>\n\n    <string name=\"dk_kit_ui_monitor\">UI显示实时数据</string>\n    <string name=\"dk_kit_time_counter\">启动耗时</string>\n    <string name=\"dk_kit_method_cost\">函数耗时</string>\n    <string name=\"dk_kit_comm_webview\">......</string>\n    <string name=\"dk_kit_mock_template_preview\">数据预览</string>\n    <string name=\"dk_kit_method_cost_desc\">\n        <![CDATA[\n            函数耗时不提供UI操作界面,在你需要分析的代码之前插入<br/>\n            <font color=\"red\"> MethodCost.startMethodTracing(\\\"doramemon\\\") </font> <br/>\n            或者<br />\n           <font color=\"red\"> MethodCost.startMethodTracingSampling(\\\"doramemon\\\")</font> <br/>\n            结束的地方加上<br/>\n           <font color=\"red\"> MethodCost.stopMethodTracingAndPrintLog(\\\"doramemon\\\")</font><br/>\n           <font color=\"red\"> 注意:start和stop的文件名必须配对才能打印出函数耗时。</font><br/>\n            编译完成以后即可在控制台中通过MethodCost TAG过滤出函数耗时<br/>\n           <font color=\"red\"> 分析完成以后你可以手动删除相关代码也可以保留便于下次调试因为release环境下相关代码都是空实现,并不会影响线上性能。</font><br/>\n            <font color=\"red\">详细的操作可参考demo中的实现</font>\n        ]]>\n        </string>\n\n    <string name=\"dk_kit_db_debug_desc\">\n        <![CDATA[\n            温馨提示:<br/>\n            <font color=\"red\"> 请确保当前手机和PC处于同一局域网内 </font> <br/>\n            想要对当前app的数据库进行修改可以在pc上通过以下地址进行操作:\n        ]]>\n        </string>\n    <string name=\"dk_kit_db_detail\">数据库查看</string>\n    <string name=\"dk_kit_top_activity\">当前Activity</string>\n    <string name=\"dk_kit_pkg_name\">包名：</string>\n    <string name=\"dk_kit_class_name\">类名：</string>\n    <string name=\"dk_kit_path_name\">路径：</string>\n    <string name=\"dk_category_mode\">悬浮窗模式</string>\n    <string name=\"dk_category_exit\">退出</string>\n    <string name=\"dk_category_version\">version</string>\n    <string name=\"dk_kit_mode_desc\">悬浮窗模式:\n        \\n\n        1）系统代表系统悬浮窗(需要系统权限)\n        \\n\n        2）常规代表内置浮标(不需要系统权限)\n    </string>\n    <string name=\"dk_kit_mode_rb_system\">系统</string>\n    <string name=\"dk_kit_mode_rb_normal\">常规</string>\n\n    <string name=\"dk_kit_exit\">退出 DoKit</string>\n    <string name=\"dk_kit_version\">当前版本：V%s</string>\n    <string name=\"dk_kit_layout_border\">布局边框</string>\n\n    <string name=\"dk_sysinfo_device_info\">手机信息</string>\n    <string name=\"dk_sysinfo_app_info\">App信息</string>\n    <string name=\"dk_third_library_info\">三方库信息</string>\n    <string name=\"dk_sysinfo_permission_info\">权限信息</string>\n    <string name=\"dk_sysinfo_permission_info_unreliable\">权限信息 (App未做6.0权限适配，以下信息仅供参考)</string>\n    <string name=\"dk_sysinfo_brand_and_model\">手机型号</string>\n    <string name=\"dk_sysinfo_android_version\">系统版本</string>\n    <string name=\"dk_sysinfo_package_name\">包名</string>\n    <string name=\"dk_sysinfo_package_version_name\">应用版本名</string>\n    <string name=\"dk_sysinfo_package_version_code\">应用版本号</string>\n    <string name=\"dk_sysinfo_package_min_sdk\">最低系统版本号</string>\n    <string name=\"dk_sysinfo_package_target_sdk\">目标系统版本号</string>\n    <string name=\"dk_sysinfo_ext_storage_free\">sd卡剩余空间</string>\n    <string name=\"dk_sysinfo_rom_free\">系统剩余空间</string>\n    <string name=\"dk_sysinfo_display_size\">分辨率</string>\n    <string name=\"dk_sysinfo_display_inch\">屏幕尺寸</string>\n    <string name=\"dk_sysinfo_permission_location\">地理位置权限</string>\n    <string name=\"dk_sysinfo_permission_sdcard\">磁盘权限</string>\n    <string name=\"dk_sysinfo_permission_camera\">拍照权限</string>\n    <string name=\"dk_sysinfo_permission_record\">麦克风权限</string>\n    <string name=\"dk_sysinfo_permission_read_phone\">设备信息权限</string>\n    <string name=\"dk_sysinfo_permission_contact\">通讯录权限</string>\n\n    <string name=\"dk_frameinfo_fps\">帧率</string>\n    <string name=\"dk_frameinfo_cpu\">CPU</string>\n    <string name=\"dk_frameinfo_ram\">内存</string>\n    <string name=\"dk_frameinfo_custom\">自定义</string>\n    <string name=\"dk_frameinfo_upstream\">上行流量</string>\n    <string name=\"dk_frameinfo_downstream\">下行流量</string>\n    <string name=\"dk_frameinfo_avg_value\">平均值</string>\n    <string name=\"dk_frameinfo_big_img\">大图检测</string>\n    <string name=\"dk_frameinfo_leakcanary\">LeakCanary</string>\n    <string name=\"dk_tools_dbdebug\">DBView</string>\n\n    <string name=\"dk_log_info_edt_hint\">输入想要过滤的关键字</string>\n    <string name=\"dk_log_info_verbose\">Verbose</string>\n    <string name=\"dk_log_info_debug\">Debug</string>\n    <string name=\"dk_log_info_info\">Info</string>\n    <string name=\"dk_log_info_warn\">Warn</string>\n    <string name=\"dk_log_info_error\">Error</string>\n\n    <string name=\"dk_web_door_hint\">输入地址，点击按钮跳转</string>\n    <string name=\"dk_web_door_explore\">点击跳转</string>\n    <string name=\"dk_web_door_clear_history\">清空搜索历史</string>\n\n    <string name=\"dk_gpsmock_open\">开启模拟定位</string>\n    <string name=\"dk_gpsmock_tv_location\">模拟</string>\n    <string name=\"dk_gpsmock_hint_longlat\">请输入经纬度</string>\n\n    <!--卡顿检测-->\n    <string name=\"dk_kit_block_monitor\">卡顿</string>\n    <string name=\"dk_kit_block_monitor_list\">卡顿列表</string>\n    <string name=\"dk_kit_block_monitor_detail\">卡顿详情</string>\n    <string name=\"dk_item_block_switch\">卡顿检测开关</string>\n    <string name=\"dk_item_block_goto_list\">查看卡顿记录</string>\n    <string name=\"dk_item_block_mock\">模拟卡顿</string>\n    <string name=\"dk_cpu_memory_remind_user\">抱歉，您当前系统版本高于8.0，由于谷歌权限收紧，只能用adb的方式获取性能分析的数据，请手机连接电脑并输入adb tcpip 5555以保证功能正常运行</string>\n    <string name=\"dk_block_class_has_blocked\">blocked %s ms</string>\n    <string name=\"dk_block_notification_message\">Click for more details</string>\n\n    <string name=\"dk_third_sort_name\">按名称</string>\n    <string name=\"dk_third_sort_size\">按大小</string>\n\n    <string name=\"dk_crash_capture_tips\">DoraemonKit正在为您记录Crash</string>\n    <string name=\"dk_crash_capture_no_record\">暂无crash记录</string>\n    <string name=\"dk_crash_capture_switch\">Crash日志收集开关</string>\n    <string name=\"dk_crash_capture_clean_data\">一键清理Crash日志</string>\n    <string name=\"dk_crash_capture_summary_title\">Crash日志列表</string>\n    <string name=\"dk_crash_capture_screenshot\">Crash截图开关</string>\n    <string name=\"dk_crash_need_permission\">请授权读写权限，避免crash文件丢失</string>\n\n    <string name=\"dk_weak_network_switch\">模拟弱网开关</string>\n    <string name=\"dk_weak_network_off\">断网</string>\n    <string name=\"dk_weak_network_timeout\">超时</string>\n    <string name=\"dk_weak_network_speed_limit\">限速</string>\n    <string name=\"dk_weak_network_request_limit\">请求限速：</string>\n    <string name=\"dk_weak_network_response_limit\">响应限速：</string>\n    <string name=\"dk_weak_network_limit_message\">请求限速会在上传时限速，响应限速会在下载时限速，0则不限速</string>\n    <string name=\"dk_weak_network_speed_unit\">K/s</string>\n\n    <!--流量监控-->\n    <string name=\"dk_kit_network_monitor\">网络</string>\n    <string name=\"dk_kit_network_monitor_detail\">流量监测详情</string>\n    <string name=\"dk_kit_network_filter_hint\">支持筛选</string>\n    <string name=\"dk_kit_network_time_format\">%1$s 耗时:%2$s</string>\n\n    <string name=\"dk_view_check_info_class\">控件类型：%1$s</string>\n    <string name=\"dk_view_check_info_id\">控件ID：%1$s</string>\n    <string name=\"dk_view_check_info_size\">控件尺寸：宽%1$d，高%2$d</string>\n    <string name=\"dk_view_check_info_desc\">背景颜色：%1$s</string>\n    <string name=\"dk_view_check_info_padding\">内边距：%1$d, %2$d, %3$d, %4$d</string>\n    <string name=\"dk_view_check_info_margin\">外边距：%1$d, %2$d, %3$d, %4$d</string>\n    <string name=\"dk_view_check_info_text_color\">文字颜色：%1$s</string>\n    <string name=\"dk_view_check_info_text_size\">文字大小：%1$d</string>\n    <string name=\"dk_view_check_info_activity\">当前Activity：%1$s</string>\n    <string name=\"dk_view_check_info_fragment\">可见Fragments：%1$s</string>\n\n    <string name=\"dk_back\">返回</string>\n    <string name=\"dk_setting\">更多</string>\n    <string name=\"dk_edit\">编辑</string>\n    <string name=\"dk_reset\">还原</string>\n    <string name=\"dk_complete\">完成</string>\n    <string name=\"dk_export\">导出</string>\n    <string name=\"dk_manager_kit_title\">管理我的功能</string>\n\n    <string name=\"dk_net_monitor_title_summary\">流量监控摘要</string>\n    <string name=\"dk_net_monitor_list\">流量监控列表</string>\n    <string name=\"dk_net_monitor_detection_switch\">流量检测开关</string>\n    <string name=\"dk_net_monitor_show_summary\">显示流量监控详情</string>\n    <string name=\"dk_network_detail_title_body\">消息体</string>\n    <string name=\"dk_network_detail_title_size\">数据大小</string>\n    <string name=\"dk_network_detail_title_url\">链接</string>\n    <string name=\"dk_network_detail_title_request_time\">请求时间</string>\n    <string name=\"dk_network_detail_title_response_time\">响应时间</string>\n    <string name=\"dk_network_detail_title_request_header\">请求头</string>\n    <string name=\"dk_network_detail_title_response_header\">响应头</string>\n    <string name=\"dk_network_detail_title_request_body\">请求行</string>\n    <string name=\"dk_network_detail_title_response_body\">响应行</string>\n    <string name=\"dk_network_request\">请求</string>\n    <string name=\"dk_network_response\">响应</string>\n    <string name=\"dk_network_summary_total_number_time_tips\">总计已为您抓包</string>\n    <string name=\"dk_network_summary_total_number\">抓包数量</string>\n    <string name=\"dk_network_summary_data_upload\">数据上传</string>\n    <string name=\"dk_network_summary_data_down\">数据下载</string>\n    <string name=\"dk_network_summary_http_method\">HTTP方法</string>\n    <string name=\"dk_network_summary_data_type\">数据类型</string>\n    <string name=\"dk_network_summary_total_time_default\">0秒</string>\n    <string name=\"dk_network_summary_total_time_second\">%d秒</string>\n    <string name=\"dk_network_summary_total_time_minute\">%1$d分%2$d秒</string>\n    <string name=\"dk_network_summary_total_time_hour\">%1$d小时%2$d分</string>\n    <string name=\"dk_network_summary_total_time_day\">%1$d天%2$d小时</string>\n    <string name=\"dk_network_get_method\">GET</string>\n    <string name=\"dk_network_post_method\">POST</string>\n    <string name=\"dk_network_method\">Method</string>\n\n    <string name=\"dk_cpu_detection_switch\">CPU检测开关</string>\n    <string name=\"dk_frameinfo_detection_switch\">帧率检测开关</string>\n    <string name=\"dk_cpu_title_cache_log\">检测记录</string>\n    <string name=\"dk_item_cache_log\">查看检测记录</string>\n\n    <string name=\"dk_ram_detection_switch\">内存检测开关</string>\n    <string name=\"dk_ram_detection_title\">内存检测</string>\n    <string name=\"dk_crash_capture_look\">查看Crash日志</string>\n    <!--大图检测开关-->\n    <string name=\"dk_large_picture_switch\">大图检测开关</string>\n    <string name=\"dk_large_picture_look\">大图检测记录</string>\n    <string name=\"dk_large_picture_list\">大图列表</string>\n    <string name=\"dk_large_picture_threshold\">大图内存检测阈值</string>\n    <string name=\"dk_large_picture_file_threshold\">大图文件检测阈值</string>\n    <string name=\"dk_large_picture_threshold_desc\">\n        <![CDATA[\n        描述:关于图片文件大小和文件被加载到内存以后的区别。<br/>\n        <font color=\"red\">参考:https://juejin.im/post/5bc406b9f265da0aa664ea1e</font><br/>\n        ]]>\n    </string>\n\n\n    <string name=\"dk_float_permission_toast\">哆啦A梦需要打开悬浮窗权限才能正常使用</string>\n    <string name=\"dk_gps_location_change_toast\">地址变为: %1$s , %2$s</string>\n\n    <string name=\"dk_layout_level\">布局层级</string>\n\n    <string name=\"dk_align_info_text\">位置：左%1$d 右%2$d 上%3$d 下%4$d</string>\n    <string name=\"dk_align_info_include_status_bar\">包含状态栏高度</string>\n\n    <string name=\"dk_error_tips_permissions_less\">请进行授权才可以使用该功能</string>\n    <!--启动、跳转耗时-->\n    <string name=\"dk_item_time_counter_switch\">Activity跳转耗时</string>\n    <string name=\"dk_item_time_goto_list\">查看记录</string>\n    <string name=\"dk_kit_block_time_counter_list\">耗时列表</string>\n    <string name=\"dk_kit_block_time_app_start_info\">启动详情</string>\n\n    <string name=\"dk_submit\">提交</string>\n    <string name=\"dk_cancel\">取消</string>\n    <string name=\"dk_discard\">丢弃</string>\n    <string name=\"dk_delete\">删除</string>\n    <string name=\"dk_db_tips_insert\">添加</string>\n    <string name=\"dk_success\">成功</string>\n    <string name=\"dk_fail\">失败</string>\n    <string name=\"dk_confirm\">确定</string>\n    <string name=\"dk_post\">提交</string>\n    <string name=\"dk_app_data_clean\">确认要删除本地数据</string>\n    <string name=\"dk_hint\">提示</string>\n    <string name=\"dk_share\">分享</string>\n    <string name=\"dk_save\">保存</string>\n\n    <string name=\"dk_platform_monitor_data_button\">开始测试</string>\n    <string name=\"dk_platform_monitor_data_button_stop\">结束测试</string>\n    <string name=\"dk_platform_monitor_view_stat_data\">查看统计数据</string>\n    <string name=\"dk_platform_monitor_page_data\">页面数据</string>\n    <string name=\"dk_log_text_loading\">日志加载中...</string>\n    <string name=\"dk_data_clean_toast\">清除系统资料</string>\n\n    <string name=\"dk_view_render_analysis\">View渲染统计</string>\n    <string name=\"dk_max_level\">最大层级: </string>\n    <string name=\"dk_view_id\">控件id: </string>\n    <string name=\"dk_total_draw_time\">总绘制耗时: </string>\n    <string name=\"dk_max_draw_time\">最大绘制耗时: </string>\n\n    <string name=\"dk_mock_search\">搜索</string>\n\n    <!--jsonviewer-->\n    <string name=\"dk_jsonViewer_icon_plus\">expand</string>\n    <string name=\"dk_jsonViewer_icon_minus\">collapse</string>\n\n    <!--数据mock页面-->\n    <string name=\"dk_data_mock_et_hint\">支持筛选</string>\n    <string name=\"dk_data_mock_bottom_table_mock\">Mock数据</string>\n    <string name=\"dk_data_mock_bottom_table_template\">上传模板</string>\n    <string name=\"dk_data_mock_group\">接口分组</string>\n    <string name=\"dk_data_mock_switch_status\">开关状态</string>\n    <string name=\"dk_data_mock_switch_all\">全部</string>\n    <string name=\"dk_data_mock_switch_opened\">打开</string>\n    <string name=\"dk_data_mock_switch_closed\">关闭</string>\n    <string name=\"dk_data_mock_plugin_toast\">请先到https://www.dokit.cn申请projectId,并参考使用中心进行接入</string>\n    <string name=\"dk_data_mock_template_tip\">本地是否存在mock模板数据:%s</string>\n    <string name=\"dk_data_mock_template_upload\">上传模板</string>\n\n    <!--健康体检页面-->\n    <string name=\"dk_health_upload_successed\">上传数据成功</string>\n    <string name=\"dk_health_upload_failed\">上传数据失败,请重新上传</string>\n    <string name=\"dk_health_upload_droped\">本次测试用例已丢弃!</string>\n    <string name=\"dk_health_upload_title\">健康体检</string>\n    <string name=\"dk_health_upload_message\">是否确认开始执行健康体检?</string>\n    <string name=\"dk_health_funcation_start\">App即将重启并开始进入体检模式</string>\n\n    <string name=\"dk_health_funcation_running\">正在体检中...</string>\n\n    <string name=\"dk_health_step_index\">向上滑动查看功能使用说明</string>\n\n    <string name=\"dk_health_title_step1\">第一步</string>\n    <string name=\"dk_health_title_step2\">第二步</string>\n    <string name=\"dk_health_title_step3\">第三步</string>\n    <string name=\"dk_health_title_step4\">第四步</string>\n\n    <string name=\"dk_health_step1\">点击开始体验按钮开始本次的性能测试</string>\n    <string name=\"dk_health_step2\">在每一个页面至少停留10秒钟，如果低于10秒钟的话，我们将会丢弃该页面收集到的数据。</string>\n    <string name=\"dk_health_step3\">测试完毕之后，重新进入本页面并点击结束测试按钮，填写测试用例名称和测试人的名字，即可上传本次性能测试数据</string>\n    <string name=\"dk_health_step4\">打开www.dokit.cn平台，进入app健康体检列表，即可查看本次的性能测试报告</string>\n    <string name=\"dk_health_back_top\">回到顶部</string>\n\n    <!--    健康体检提交dialog文案-->\n    <string name=\"dk_health_dialog_title\">结束前请完善以下信息</string>\n    <string name=\"dk_health_dialog_test_name\">测试用例名称</string>\n    <string name=\"dk_health_dialog_test_name_hint\">测试用例名称</string>\n    <string name=\"dk_health_dialog_test_person\">测试人</string>\n    <string name=\"dk_health_dialog_test_person_hint\">测试人</string>\n\n    <!--日志-->\n    <string name=\"dk_log_btn_clean\">清空日志</string>\n    <string name=\"dk_log_btn_export\">导出</string>\n    <string name=\"dk_log_btn_back_top\">回到顶部</string>\n    <string name=\"dk_log_btn_to_bottom\">滚至底部</string>\n    <!--        refreshlayout-->\n    <string name=\"dk_header_reset\">下拉刷新</string>\n    <string name=\"dk_header_pull\">下拉刷新</string>\n    <string name=\"dk_header_pull_over\">释放立即刷新</string>\n    <string name=\"dk_header_refreshing\">正在刷新...</string>\n    <string name=\"dk_header_completed\">刷新成功</string>\n\n    <!--    bravh-->\n    <string name=\"dk_brvah_loading\">正在加载中...</string>\n    <string name=\"dk_brvah_load_failed\">加载失败，请点我重试</string>\n    <string name=\"dk_brvah_load_end\">没有更多数据</string>\n    <string name=\"dk_brvah_load_complete\">点击加载更多</string>\n\n\n    <!--    弱网dokitView-->\n    <string name=\"dk_weaknet_type\">弱网类型:</string>\n    <string name=\"dk_weaknet_type_off\">断网</string>\n    <string name=\"dk_weaknet_type_timeout\">超时</string>\n    <string name=\"dk_weaknet_type_speed\">限速</string>\n\n\n    <!--    plugin tip-->\n    <string name=\"dk_plugin_close_tip\">未引入插件或插件未开启</string>\n\n    <string name=\"dk_plugin_network_close_tip\">网络插件未开启</string>\n\n    <string name=\"dk_plugin_method_close_tip\">慢函数插件未开启</string>\n    <string name=\"dk_plugin_method_strategy_tip\">请修改慢函数的插件配置等于0</string>\n    <string name=\"dk_plugin_big_img_close_tip\">大图检测插件未开启</string>\n    <string name=\"dk_plugin_gps_close_tip\">GPS插件未开启</string>\n\n    <string name=\"dk_platform_tip\">需要到www.dokit.cn上注册pId才能使用该功能</string>\n\n    <!--toolpanel-->\n    <string name=\"dk_toolpanel_save_complete\">保存成功</string>\n    <string name=\"dk_toolpanel_reset_complete\">功能还原成功</string>\n\n    <string name=\"dk_toolpanel_dialog_edit_tip\">是否保存已编辑的内容</string>\n    <string name=\"dk_toolpanel_dialog_reset_tip\">是否还原到初始状态</string>\n\n\n    <!--    dokit setting-->\n    <string name=\"dk_setting_kit_manager\">功能管理</string>\n\n    <!--    文件同步助手-->\n    <string name=\"dk_file_manager_tip_top\">\n        <![CDATA[\n        请在www.dokit.cn平台端控制台中的<font color=\\\"blue\\\">【文件同步助手】</font>\n        中使用该功能\n        ]]>\n    </string>\n\n    <string name=\"dk_file_manager_tip_bottom\">请在web端通过当前ip:port进行连接</string>\n    <string name=\"dk_file_manager_sd_permission_tip\">你当前的targetSdkVersion>=29,请在AndroidManifest.xml的application下添加android:requestLegacyExternalStorage=\"true\"解决sd卡没有权限的问题</string>\n    <!-- h5助手-->\n    <string name=\"dk_kit_h5_help\">H5助手</string>\n    <string name=\"dk_kit_dokit_for_web\">DoKit Web</string>\n    <string name=\"dk_kit_dokit_studio\">DoKit Studio</string>\n\n</resources>\n"
  },
  {
    "path": "Android/dokit/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"DK\" />\n\n    <style name=\"DK.Divider\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">1px</item>\n        <item name=\"android:background\">@color/dk_color_E5E5E5</item>\n    </style>\n\n    <style name=\"DK.Divider.Vertical\">\n        <item name=\"android:layout_width\">1px</item>\n        <item name=\"android:layout_height\">match_parent</item>\n        <item name=\"android:background\">@color/dk_color_E5E5E5</item>\n    </style>\n\n    <style name=\"DK.Text\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:textSize\">@dimen/dk_font_size_14</item>\n        <item name=\"android:gravity\">center</item>\n    </style>\n\n    <style name=\"DK.Text.Normal\" parent=\"DK.Text\">\n        <item name=\"android:textColor\">@color/dk_color_666666</item>\n    </style>\n\n    <style name=\"DK.Text.Dark\" parent=\"DK.Text\">\n        <item name=\"android:textColor\">@color/dk_color_151515</item>\n    </style>\n\n    <style name=\"DK.Text.Darker\" parent=\"DK.Text\">\n        <item name=\"android:textColor\">@color/dk_color_333333</item>\n    </style>\n\n    <style name=\"DK.Text.White\" parent=\"DK.Text\">\n        <item name=\"android:textColor\">@color/dk_color_FFFFFF</item>\n    </style>\n\n    <style name=\"DK.Text.Gray\" parent=\"DK.Text\">\n        <item name=\"android:textColor\">@color/dk_color_666666</item>\n    </style>\n\n    <style name=\"DK.Text.Blue\" parent=\"DK.Text\">\n        <item name=\"android:textColor\">@color/dk_color_55A8FD</item>\n    </style>\n\n\n    <style name=\"DK.Title\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:textSize\">@dimen/dk_font_size_16</item>\n        <item name=\"android:textColor\">@color/dk_color_324456</item>\n        <item name=\"android:gravity\">center</item>\n        <item name=\"android:textStyle\">bold</item>\n    </style>\n\n    <style name=\"DK.TitleBig\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:textSize\">@dimen/dk_font_size_24</item>\n        <item name=\"android:textColor\">@color/dk_color_324456</item>\n        <item name=\"android:gravity\">center</item>\n        <item name=\"android:textStyle\">bold</item>\n    </style>\n\n    <style name=\"DK.TitleBar\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:textSize\">@dimen/dk_font_size_18</item>\n        <item name=\"android:textColor\">@color/dk_color_333333</item>\n        <item name=\"android:layout_gravity\">center</item>\n        <item name=\"android:gravity\">center</item>\n        <item name=\"android:textStyle\">bold</item>\n    </style>\n\n    <style name=\"DK.TextSmall\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:textSize\">@dimen/dk_font_size_12</item>\n        <item name=\"android:gravity\">center</item>\n    </style>\n\n    <style name=\"DK.TextSmall.Dark\" parent=\"DK.TextSmall\">\n        <item name=\"android:textColor\">@color/dk_color_151515</item>\n    </style>\n\n    <style name=\"DK.TextSmall.Darker\" parent=\"DK.TextSmall\">\n        <item name=\"android:textColor\">@color/dk_color_333333</item>\n    </style>\n\n    <style name=\"DK.TextBig.Dark\" parent=\"DK.TextBig\">\n        <item name=\"android:textColor\">@color/dk_color_151515</item>\n    </style>\n\n    <style name=\"DK.TextBig.Darker\" parent=\"DK.TextBig\">\n        <item name=\"android:textColor\">@color/dk_color_333333</item>\n    </style>\n\n    <style name=\"DK.TextBig\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:textSize\">@dimen/dk_font_size_16</item>\n        <item name=\"android:gravity\">center</item>\n    </style>\n\n\n    <style name=\"DK.Shadow\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">8dp</item>\n    </style>\n\n    <style name=\"DK.Shadow.Bottom\">\n        <item name=\"android:background\">@mipmap/dk_shadow_bottom</item>\n    </style>\n\n    <style name=\"DK.CheckBox\">\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:button\">@null</item>\n        <item name=\"android:background\">@drawable/dk_switch_background</item>\n    </style>\n\n    <style name=\"DK.RadioButton\">\n        <item name=\"android:gravity\">center</item>\n        <item name=\"android:textSize\">@dimen/dk_font_size_14</item>\n        <item name=\"android:textColor\">@color/dk_radio_button_text_color</item>\n        <item name=\"android:layout_height\">34dp</item>\n        <item name=\"android:layout_width\">70dp</item>\n        <item name=\"android:button\">@null</item>\n        <item name=\"android:background\">@drawable/dk_radio_button_background</item>\n    </style>\n\n    <style name=\"DK.RadioButton.Left\">\n        <item name=\"android:gravity\">center</item>\n        <item name=\"android:textSize\">@dimen/dk_font_size_14</item>\n        <item name=\"android:textColor\">@color/dk_radio_button_text_color</item>\n        <item name=\"android:layout_height\">34dp</item>\n        <item name=\"android:layout_width\">70dp</item>\n        <item name=\"android:button\">@null</item>\n        <item name=\"android:background\">@drawable/dk_radio_button_background_left</item>\n    </style>\n\n\n    <style name=\"DK.RadioButton.middle\">\n        <item name=\"android:gravity\">center</item>\n        <item name=\"android:textSize\">@dimen/dk_font_size_14</item>\n        <item name=\"android:textColor\">@color/dk_radio_button_text_color</item>\n        <item name=\"android:layout_height\">34dp</item>\n        <item name=\"android:layout_width\">70dp</item>\n        <item name=\"android:button\">@null</item>\n        <item name=\"android:background\">@drawable/dk_radio_button_background_middle</item>\n    </style>\n\n    <style name=\"DK.RadioButton.Right\">\n        <item name=\"android:gravity\">center</item>\n        <item name=\"android:textSize\">@dimen/dk_font_size_14</item>\n        <item name=\"android:textColor\">@color/dk_radio_button_text_color</item>\n        <item name=\"android:layout_height\">34dp</item>\n        <item name=\"android:layout_width\">70dp</item>\n        <item name=\"android:button\">@null</item>\n        <item name=\"android:background\">@drawable/dk_radio_button_background_right</item>\n    </style>\n\n    <style name=\"DK.Input\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:gravity\">center_vertical|left</item>\n        <item name=\"android:textColor\">@color/dk_color_333333</item>\n        <item name=\"android:textColorHint\">@color/dk_color_CCCCCC</item>\n        <item name=\"android:textSize\">@dimen/dk_font_size_16</item>\n        <item name=\"android:background\">@null</item>\n        <item name=\"android:textCursorDrawable\">@drawable/dk_input_cursor</item>\n    </style>\n\n    <style name=\"DK.ConfirmButton\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">50dp</item>\n        <item name=\"android:textSize\">@dimen/dk_font_size_16</item>\n        <item name=\"android:gravity\">center</item>\n    </style>\n\n    <style name=\"DK.ConfirmButton.Positive\" parent=\"DK.ConfirmButton\">\n        <item name=\"android:background\">@drawable/dk_confirm_button_background</item>\n        <item name=\"android:textColor\">@color/dk_confirm_button_text_color</item>\n    </style>\n\n    <style name=\"DK.CommonItem\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">50dp</item>\n        <item name=\"android:paddingLeft\">10dp</item>\n        <item name=\"android:paddingRight\">10dp</item>\n        <item name=\"android:orientation\">horizontal</item>\n        <item name=\"android:gravity\">center_vertical|left</item>\n        <item name=\"android:background\">@color/dk_color_FFFFFF</item>\n    </style>\n\n    <style name=\"DK.CommonItemAppInfo\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:paddingLeft\">10dp</item>\n        <item name=\"android:paddingRight\">10dp</item>\n        <item name=\"android:paddingTop\">14dp</item>\n        <item name=\"android:paddingBottom\">14dp</item>\n\n        <item name=\"android:orientation\">horizontal</item>\n        <item name=\"android:gravity\">center_vertical|left</item>\n        <item name=\"android:background\">@color/dk_color_FFFFFF</item>\n    </style>\n\n    <style name=\"DK.Theme.Translucent\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:colorBackgroundCacheHint\">@null</item>\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:statusBarColor\">@android:color/transparent</item>\n        <!-- Note that we use the base animation style here (that is no\n             animations) because we really have no idea how this kind of\n             activity will be used. -->\n        <item name=\"android:windowAnimationStyle\">@android:style/Animation\n        </item>\n    </style>\n\n    <style name=\"DK.Dialog\" parent=\"@android:style/Theme.Dialog\">\n        <item name=\"android:windowFrame\">@null</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:background\">@android:color/transparent</item>\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"android:windowIsFloating\">true</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n        <item name=\"android:windowBackgroundFallback\">@android:color/transparent</item>\n    </style>\n\n\n</resources>"
  },
  {
    "path": "Android/dokit/src/main/res/values-en-rUS/strings.xml",
    "content": "<resources>\n    <string name=\"dk_app_name\">DoKit</string>\n\n    <string name=\"dk_category_biz\">Business</string>\n    <string name=\"dk_category_comms\">Common</string>\n    <string name=\"dk_category_lbs\">LBS</string>\n    <string name=\"dk_category_performance\">Performance</string>\n    <string name=\"dk_category_large_image\">Large image detection</string>\n    <string name=\"dk_category_ui\">UI</string>\n    <string name=\"dk_category_platform\">Platform</string>\n    <string name=\"dk_kit_network_mock\">Mock Data</string>\n    <string name=\"dk_kit_health\">Health Check</string>\n    <string name=\"dk_kit_file_transfer\">FileManager</string>\n    <string name=\"dk_category_weex\">Weex</string>\n\n    <string name=\"dk_kit_sysinfo\">App Info</string>\n    <string name=\"dk_third_library_info\">Third Library Info</string>\n    <string name=\"dk_kit_develop\">Developer Options</string>\n    <string name=\"dk_kit_service_running\">running service</string>\n    <string name=\"dk_kit_local_lang\">Language</string>\n\n    <string name=\"dk_kit_list\">Tools</string>\n    <string name=\"dk_kit_demo\">demo</string>\n    <string name=\"dk_kit_file_explorer\">Sandbox</string>\n    <string name=\"dk_kit_frame_info\">FPS</string>\n    <string name=\"dk_kit_gps_mock\">Mock GPS</string>\n    <string name=\"dk_kit_gps_mock_manual\">GPS-Manual</string>\n    <string name=\"dk_kit_gps_mock_preset\">GPS-Preset</string>\n    <string name=\"dk_kit_gps_mock_route\">GPS-Route</string>\n    <string name=\"dk_kit_color_picker\">Color Picker</string>\n    <string name=\"dk_kit_align_ruler\">Align Ruler</string>\n    <string name=\"dk_kit_log_info\">Log</string>\n    <string name=\"dk_kit_web_door\">Browser</string>\n    <string name=\"dk_kit_temporary_close\">Hide Doraemon</string>\n    <string name=\"dk_kit_crash\">Crash</string>\n    <string name=\"dk_kit_data_clean\">Clear Sanbox</string>\n    <string name=\"dk_kit_db_debug\">DBView</string>\n    <string name=\"dk_kit_weak_network\">Weak Network</string>\n    <string name=\"dk_kit_view_check\">View Check</string>\n    <string name=\"dk_kit_net_monitor\">Network</string>\n    <string name=\"dk_kit_ui_monitor\">Show Current Infos On UI</string>\n    <string name=\"dk_kit_time_counter\">Launch Time</string>\n    <string name=\"dk_kit_method_cost\">Time Profiler</string>\n    <string name=\"dk_kit_comm_webview\">......</string>\n    <string name=\"dk_kit_mock_template_preview\">Data Preview</string>\n    <string name=\"dk_kit_method_cost_desc\">\n        <![CDATA[\n            The function does not provide a ui interface, it is inserted before the code you need to analyze.<br/>\n            <font color=\"red\"> MethodCost.startMethodTracing(\\\"doramemon\\\") </font> <br/>\n            or<br/>\n            <font color=\"red\"> MethodCost.startMethodTracingSampling(\\\"doramemon\\\")</font> <br/>\n            End of insertion<br/>\n            <font color=\"red\"> MethodCost.stopMethodTracingAndPrintLog(\\\"doramemon\\\")</font><br/>\n            <font color=\"red\"> Note: The file names of start and stop must be paired to print out the relevant function time.</font><br/>\n            After the compilation is completed, you can filter out the related function time-consuming logs in the console by the methodcost TAG.<br/>\n            <font color=\"red\"> After the analysis is complete, you can manually delete the relevant code or keep it for the next debugging. Because the relevant code in the release environment is empty, it will not affect the online performance.</font><br/>\n            <font color=\"red\">For detailed operations, please refer to the implementation in the demo.</font>\n        ]]>\n    </string>\n    <string name=\"dk_kit_db_debug_desc\">\n        <![CDATA[\n            Tips:<br/>\n            <font color=\"red\"> Please make sure the current phone and PC are in the same Wifi. </font> <br/>\n            If you want to modify the current app\\'s database, you can operate it on the pc with the following address:\n        ]]>\n        </string>\n    <string name=\"dk_kit_exit\">Exit DoKit</string>\n    <string name=\"dk_kit_version\">current version:V%s</string>\n    <string name=\"dk_kit_db_detail\">Data Base</string>\n    <string name=\"dk_kit_top_activity\">Top Activity</string>\n    <string name=\"dk_kit_pkg_name\">Package Name：</string>\n    <string name=\"dk_kit_class_name\">Class Name：</string>\n    <string name=\"dk_kit_path_name\">Class Path：</string>\n    <string name=\"dk_category_mode\">float mode</string>\n    <string name=\"dk_kit_mode_desc\">float mode:\n        \\n\n        1）system is system float(need dialog permission)\n        \\n\n        2）normal is normal float(don\\'t need dialog permission)\n    </string>\n    <string name=\"dk_kit_mode_rb_system\">system</string>\n    <string name=\"dk_kit_mode_rb_normal\">normal</string>\n\n    <string name=\"dk_sysinfo_device_info\">Mobile Info</string>\n    <string name=\"dk_sysinfo_app_info\">App Info</string>\n    <string name=\"dk_sysinfo_permission_info\">Privacy Info</string>\n    <string name=\"dk_sysinfo_permission_info_unreliable\">Privacy Info</string>\n    <string name=\"dk_sysinfo_brand_and_model\">Mobile Model</string>\n    <string name=\"dk_sysinfo_android_version\">System Version</string>\n    <string name=\"dk_sysinfo_package_name\">Package Name</string>\n    <string name=\"dk_sysinfo_package_version_name\">Application Version Name</string>\n    <string name=\"dk_sysinfo_package_version_code\">Application Version Number</string>\n    <string name=\"dk_sysinfo_package_min_sdk\">Minimum System Version Number</string>\n    <string name=\"dk_sysinfo_package_target_sdk\">Target System Version Number</string>\n    <string name=\"dk_sysinfo_ext_storage_free\">Sd Card Remaining Space</string>\n    <string name=\"dk_sysinfo_rom_free\">System Free Space</string>\n    <string name=\"dk_sysinfo_display_size\">Resolution</string>\n    <string name=\"dk_sysinfo_display_inch\">Screen Inch</string>\n    <string name=\"dk_sysinfo_permission_location\">Location Privacy</string>\n    <string name=\"dk_sysinfo_permission_sdcard\">Disk Permissions</string>\n    <string name=\"dk_sysinfo_permission_camera\">Photo Permission</string>\n    <string name=\"dk_sysinfo_permission_record\">Microphone Privacy</string>\n    <string name=\"dk_sysinfo_permission_read_phone\">Device Information Permission</string>\n    <string name=\"dk_sysinfo_permission_contact\">Contacts Privacy</string>\n\n    <string name=\"dk_frameinfo_fps\">FPS</string>\n    <string name=\"dk_frameinfo_cpu\">CPU</string>\n    <string name=\"dk_frameinfo_custom\">CUSTOM</string>\n    <string name=\"dk_frameinfo_upstream\">Upstream traffic</string>\n    <string name=\"dk_frameinfo_downstream\">Downstream traffic</string>\n    <string name=\"dk_frameinfo_avg_value\">Average value</string>\n    <string name=\"dk_frameinfo_big_img\">BigImg</string>\n    <string name=\"dk_frameinfo_leakcanary\">LeakCanary</string>\n    <string name=\"dk_tools_dbdebug\">DBView</string>\n\n    <string name=\"dk_log_info_edt_hint\">Enter the keywords you want to filter</string>\n    <string name=\"dk_log_info_verbose\">Verbose</string>\n    <string name=\"dk_log_info_debug\">Debug</string>\n    <string name=\"dk_log_info_info\">Info</string>\n    <string name=\"dk_log_info_warn\">Warn</string>\n    <string name=\"dk_log_info_error\">Error</string>\n\n    <string name=\"dk_web_door_hint\">Enter the address to jump</string>\n    <string name=\"dk_web_door_explore\">Click To Jump</string>\n    <string name=\"dk_web_door_clear_history\">Clear History</string>\n\n    <string name=\"dk_gpsmock_tv_location\">Mock</string>\n\n\n    <string name=\"dk_gpsmock_hint_longlat\">Please Input latitude and longitude</string>\n\n    <!--卡顿检测-->\n    <string name=\"dk_kit_block_monitor\">ANR</string>\n    <string name=\"dk_kit_block_monitor_list\">Block List</string>\n    <string name=\"dk_kit_block_monitor_detail\">Block Detail</string>\n    <string name=\"dk_item_block_switch\">Block Switch</string>\n    <string name=\"dk_item_block_goto_list\">View Block History</string>\n    <string name=\"dk_item_block_mock\">Mock Block</string>\n\n    <string name=\"dk_third_sort_name\">By Name</string>\n    <string name=\"dk_third_sort_size\">By Size</string>\n\n    <string name=\"dk_block_class_has_blocked\">blocked %s ms</string>\n    <string name=\"dk_block_notification_message\">Click for more details</string>\n\n    <string name=\"dk_crash_capture_tips\">DoraemonKit is recording Crash for you</string>\n    <string name=\"dk_crash_capture_no_record\">No crash record</string>\n    <string name=\"dk_gpsmock_open\">\"Open Gps Mock \"</string>\n\n    <string name=\"dk_crash_capture_switch\">Crash Log Collection Switch</string>\n    <string name=\"dk_crash_capture_look\">View Crash Log</string>\n    <string name=\"dk_crash_capture_clean_data\">Clean Crash Cache</string>\n    <string name=\"dk_kit_cache_check_all\">all check</string>\n    <string name=\"dk_crash_capture_summary_title\">Crash Log List</string>\n    <string name=\"dk_crash_capture_screenshot\">Crash Screenshot Switch</string>\n\n    <!--大图检测开关-->\n    <string name=\"dk_large_picture_switch\">Large image detection switch</string>\n    <string name=\"dk_large_picture_look\">view monitor records</string>\n    <string name=\"dk_large_picture_list\">Large image list</string>\n    <string name=\"dk_large_picture_threshold\">Large image Memory threshold</string>\n    <string name=\"dk_large_picture_file_threshold\">Large image file threshold</string>\n    <string name=\"dk_large_picture_threshold_desc\">\n        <![CDATA[\n        Desc: the difference between the size of the picture file and the fact that the file is loaded into memory.<br/>\n        <font color=\"red\">link: https://juejin.im/post/5bc406b9f265da0aa664ea1e</font><br/>\n        ]]>\n        </string>\n\n\n    <string name=\"dk_weak_network_switch\">Simulate Weak Network Switch</string>\n    <string name=\"dk_weak_network_off\">Off Network</string>\n    <string name=\"dk_weak_network_timeout\">Time Out</string>\n    <string name=\"dk_weak_network_speed_limit\">Speed Limit</string>\n    <string name=\"dk_weak_network_request_limit\">Request Speed Limit:</string>\n    <string name=\"dk_weak_network_response_limit\">Response Speed Limit：</string>\n    <string name=\"dk_weak_network_limit_message\">request speed limit will limit speed when uploading, response speed limit will limit speed when downloading, 0 does not limit speed</string>\n\n    <string name=\"dk_back\">Back</string>\n    <string name=\"dk_frameinfo_ram\">Memory</string>\n    <string name=\"dk_kit_network_monitor\">Network</string>\n    <string name=\"dk_float_permission_toast\">DoraemonKit need draw over other app permission</string>\n    <string name=\"dk_gps_location_change_toast\">Location changed: %1$s , %2$s</string>\n    <string name=\"dk_view_check_info_size\">View Size：width%1$d，height%2$d</string>\n    <string name=\"dk_view_check_info_desc\">View Background：%1$s</string>\n    <string name=\"dk_view_check_info_padding\">View Padding：%1$d, %2$d, %3$d, %4$d</string>\n    <string name=\"dk_view_check_info_margin\">View Margin：%1$d, %2$d, %3$d, %4$d</string>\n    <string name=\"dk_view_check_info_text_color\">Text Color：%1$s</string>\n    <string name=\"dk_view_check_info_text_size\">Text Size：%1$d</string>\n    <string name=\"dk_view_check_info_id\">View ID：%1$s</string>\n    <string name=\"dk_view_check_info_class\">View Class：%1$s</string>\n    <string name=\"dk_view_check_info_activity\">Current Activity：%1$s</string>\n    <string name=\"dk_view_check_info_fragment\">Visible Fragments：%1$s</string>\n    <string name=\"dk_network_get_method\">GET</string>\n    <string name=\"dk_network_post_method\">POST</string>\n    <string name=\"dk_network_request\">Request</string>\n    <string name=\"dk_network_response\">Response</string>\n    <string name=\"dk_kit_frame_info_desc\">FPS Detection</string>\n    <string name=\"dk_network_method\">Method</string>\n    <string name=\"dk_kit_network_monitor_detail\">Traffic Monitoring Details</string>\n    <string name=\"dk_kit_network_filter_hint\">Support screening</string>\n    <string name=\"dk_kit_network_time_format\">%1$s Consume:%2$s</string>\n    <string name=\"dk_net_monitor_title_summary\">Traffic Monitoring Summary</string>\n    <string name=\"dk_net_monitor_list\">Traffic Monitoring List</string>\n    <string name=\"dk_net_monitor_detection_switch\">Flow Detection Switch</string>\n    <string name=\"dk_net_monitor_show_summary\">Show Traffic Monitoring Details</string>\n    <string name=\"dk_network_detail_title_body\">Message Body</string>\n    <string name=\"dk_network_detail_title_size\">Data Size</string>\n    <string name=\"dk_network_detail_title_url\">Link</string>\n    <string name=\"dk_network_detail_title_request_time\">Request Time</string>\n    <string name=\"dk_network_detail_title_response_time\">Response Time</string>\n    <string name=\"dk_network_detail_title_request_header\">Request Header</string>\n    <string name=\"dk_network_detail_title_response_header\">Response Header</string>\n    <string name=\"dk_network_detail_title_request_body\">Request Line</string>\n    <string name=\"dk_network_detail_title_response_body\">Response Line</string>\n    <string name=\"dk_network_summary_total_number_time_tips\">Total Captured</string>\n    <string name=\"dk_network_summary_total_number\">Captured Packets</string>\n    <string name=\"dk_network_summary_data_upload\">Data Upload</string>\n    <string name=\"dk_network_summary_data_down\">Data Download</string>\n    <string name=\"dk_network_summary_http_method\">HTTP Method</string>\n    <string name=\"dk_network_summary_data_type\">Data Type</string>\n    <string name=\"dk_network_summary_total_time_default\">0S</string>\n    <string name=\"dk_network_summary_total_time_second\">%dS</string>\n    <string name=\"dk_network_summary_total_time_minute\">%1$dM%2$dS</string>\n    <string name=\"dk_network_summary_total_time_hour\">%1$dH&#160;%2$dM</string>\n    <string name=\"dk_network_summary_total_time_day\">%1$dD%2$dH&#160;</string>\n    <string name=\"dk_cpu_detection_switch\">CPU Detection Switch</string>\n    <string name=\"dk_frameinfo_detection_switch\">FPS Detection Switch</string>\n    <string name=\"dk_cpu_title_cache_log\">Test Record</string>\n    <string name=\"dk_item_cache_log\">Inspection Records</string>\n    <string name=\"dk_ram_detection_switch\">Memory Detection Switch</string>\n    <string name=\"dk_ram_detection_title\">Memory Detection</string>\n    <string name=\"dk_cpu_memory_remind_user\">Sorry, your current system version is higher than 8.0. Due to Google\\'s privilege tightening, you can only use adb to obtain performance analysis data. Please connect your computer to your computer and enter adb tcpip 5555 to ensure normal function.</string>\n    <string name=\"dk_kit_layout_border\">View Border</string>\n    <string name=\"dk_layout_level\">Layout Level</string>\n    <string name=\"dk_align_info_text\">Position：Left%1$d Right%2$d Top%3$d Bottom%4$d</string>\n    <string name=\"dk_align_info_include_status_bar\">Include Status Bar Height</string>\n\n    <string name=\"dk_item_time_counter_switch\">Activity Time Counter</string>\n    <string name=\"dk_item_time_goto_list\">View History</string>\n    <string name=\"dk_kit_block_time_counter_list\">Time Counter History</string>\n    <string name=\"dk_submit\">submit</string>\n    <string name=\"dk_cancel\">cancel</string>\n    <string name=\"dk_discard\">discard</string>\n    <string name=\"dk_delete\">delete</string>\n    <string name=\"dk_db_tips_insert\">insert</string>\n\n    <string name=\"dk_success\">success</string>\n    <string name=\"dk_fail\">fail</string>\n    <string name=\"dk_confirm\">confirm</string>\n    <string name=\"dk_post\">post</string>\n    <string name=\"dk_app_data_clean\">Confirm that you want to delete local data</string>\n    <string name=\"dk_hint\">Hint</string>\n    <string name=\"dk_share\">share</string>\n    <string name=\"dk_save\">save</string>\n\n    <string name=\"dk_platform_monitor_data_button\">start test</string>\n    <string name=\"dk_platform_monitor_data_button_stop\">stop test</string>\n    <string name=\"dk_platform_monitor_view_stat_data\">view statistics</string>\n    <string name=\"dk_platform_monitor_page_data\">page data</string>\n    <string name=\"dk_log_text_loading\">Loading...</string>\n    <string name=\"dk_crash_need_permission\">Please authorize permissions</string>\n    <string name=\"dk_data_clean_toast\">Clear system data</string>\n    <string name=\"dk_error_tips_permissions_less\">Please authorize permissions</string>\n    <string name=\"dk_kit_ui_performance\">UI Hierarchy</string>\n    <string name=\"dk_view_render_analysis\">Render Analysis</string>\n    <string name=\"dk_max_level\">max level: </string>\n    <string name=\"dk_view_id\">view id: </string>\n    <string name=\"dk_total_draw_time\">total draw time: </string>\n    <string name=\"dk_max_draw_time\">max draw time: </string>\n\n    <string name=\"dk_mock_search\">search</string>\n\n    <!--jsonviewer-->\n    <string name=\"dk_jsonViewer_icon_plus\">expand</string>\n    <string name=\"dk_jsonViewer_icon_minus\">collapse</string>\n    <string name=\"dk_data_mock_et_hint\">support filtrate</string>\n    <string name=\"dk_data_mock_bottom_table_mock\">DataMock</string>\n    <string name=\"dk_data_mock_bottom_table_template\">Template</string>\n    <string name=\"dk_data_mock_switch_all\">All</string>\n    <string name=\"dk_data_mock_switch_opened\">Open</string>\n    <string name=\"dk_data_mock_switch_closed\">Close</string>\n    <string name=\"dk_data_mock_group\">Group</string>\n    <string name=\"dk_data_mock_switch_status\">Group Status</string>\n    <string name=\"dk_health_upload_successed\">Data uploaded successfully</string>\n    <string name=\"dk_health_upload_failed\">upload failed,try again</string>\n    <string name=\"dk_health_upload_droped\">This test case has been discarded!</string>\n    <string name=\"dk_health_upload_title\">Health Check</string>\n    <string name=\"dk_health_upload_message\">Are you sure to start the health checkup?</string>\n    <string name=\"dk_health_funcation_start\">App is about to restart and start to enter medical examination mode</string>\n    <string name=\"dk_health_funcation_running\">Checking...</string>\n    <string name=\"dk_health_step_index\">Swipe up to see how to use the feature</string>\n    <string name=\"dk_health_title_step1\">Step 1</string>\n    <string name=\"dk_health_title_step2\">Step 2</string>\n    <string name=\"dk_health_title_step3\">Step 3</string>\n    <string name=\"dk_health_title_step4\">Step 4</string>\n    <string name=\"dk_health_step1\">Click the Start Experience button to start this performance test</string>\n    <string name=\"dk_health_step2\">Stay on each page for at least 10 seconds, if less than 10 seconds, we will discard the data collected on that page.</string>\n    <string name=\"dk_health_step3\">After the test is completed, re-enter this page and click the end test button, fill in the test case name and the name of the test person, and you can upload the performance test data.</string>\n    <string name=\"dk_health_step4\">Open the www.dokit.cn platform and enter the app health checklist to view the performance test report</string>\n    <string name=\"dk_health_back_top\">back to the top</string>\n    <string name=\"dk_log_btn_clean\">Clear</string>\n    <string name=\"dk_log_btn_export\">Export</string>\n    <string name=\"dk_log_btn_back_top\">Top</string>\n    <string name=\"dk_log_btn_to_bottom\">Bottom</string>\n    <string name=\"dk_kit_net_monitor_white_host_tip\">Whitelist (Block all by default. If you add a whitelist, only network requests in the whitelist will be blocked, and the whitelist rule will take effect when the current page is closed. Example of whitelist host: m.baidu.com)</string>\n    <string name=\"dk_kit_net_monitor_white_host_edit_tip\">Please enter whitelist host</string>\n    <string name=\"dk_kit_net_monitor_white_host_edit_toast\">Please enter host first</string>\n    <string name=\"dk_data_mock_plugin_toast\">Please go to https://www.dokit.cn to apply for projectId first, and refer to the use center for access</string>\n    <string name=\"dk_header_reset\">Pull refresh</string>\n    <string name=\"dk_header_pull\">Pull refresh</string>\n    <string name=\"dk_header_pull_over\">Release Refresh</string>\n    <string name=\"dk_header_refreshing\">Refreshing...</string>\n    <string name=\"dk_header_completed\">refresh succeed</string>\n    <string name=\"dk_brvah_loading\">loading...</string>\n    <string name=\"dk_brvah_load_failed\">load failed,try again</string>\n    <string name=\"dk_brvah_load_end\">no moe data</string>\n    <string name=\"dk_brvah_load_complete\">click load more</string>\n    <string name=\"dk_data_mock_template_tip\">has template data：%s</string>\n    <string name=\"dk_data_mock_template_upload\">Upload Template</string>\n    <string name=\"dk_weaknet_type\">Net Type:</string>\n    <string name=\"dk_weaknet_type_speed\">Limit Speed</string>\n    <string name=\"dk_weaknet_type_timeout\">Timeout</string>\n    <string name=\"dk_weaknet_type_off\">Off</string>\n    <string name=\"dk_weak_network_speed_unit\">K/s</string>\n    <string name=\"dk_health_dialog_title\">Perfect Info</string>\n    <string name=\"dk_health_dialog_test_name\">Test case name</string>\n    <string name=\"dk_health_dialog_test_name_hint\">Test case name</string>\n    <string name=\"dk_health_dialog_test_person\">tester</string>\n    <string name=\"dk_health_dialog_test_person_hint\">tester</string>\n    <string name=\"dk_kit_log_min\">Min</string>\n    <string name=\"dk_kit_setting\">more</string>\n    <string name=\"dk_category_exit\">Exit</string>\n    <string name=\"dk_category_version\">version</string>\n    <string name=\"dk_setting\">More</string>\n    <string name=\"dk_edit\">Edit</string>\n    <string name=\"dk_complete\">OK</string>\n    <string name=\"dk_export\">Export</string>\n    <string name=\"dk_manager_kit_title\">Manager</string>\n    <string name=\"dk_kit_block_time_app_start_info\">Launch Info</string>\n    <string name=\"dk_plugin_close_tip\">The plugin is not introduced or the plugin is not turned on</string>\n    <string name=\"dk_plugin_network_close_tip\">Network plugin is not open</string>\n    <string name=\"dk_plugin_method_close_tip\">Slow function plugin is not enabled</string>\n    <string name=\"dk_plugin_method_strategy_tip\">Please modify the plugin configuration of the slow function to equal 0</string>\n    <string name=\"dk_plugin_big_img_close_tip\">Large image detection plugin is not turned on</string>\n    <string name=\"dk_plugin_gps_close_tip\">GPS plugin is not open</string>\n    <string name=\"dk_platform_tip\">You need to register the pId on www.dokit.cn to use this function</string>\n    <string name=\"dk_reset\">Reset</string>\n    <string name=\"dk_toolpanel_save_complete\">Saved successfully</string>\n    <string name=\"dk_toolpanel_reset_complete\">Function restored successfully</string>\n    <string name=\"dk_toolpanel_dialog_edit_tip\">Whether to save the edited content</string>\n    <string name=\"dk_toolpanel_dialog_reset_tip\">Whether to restore to the initial state</string>\n    <string name=\"dk_setting_kit_manager\">Function management</string>\n\n    <!--    文件同步助手-->\n    <string name=\"dk_file_manager_tip_top\">\n        <![CDATA[\n        Please use this function in the <font color=\\\"blue\\\"> [File Synchronization Assistant] </font> in the platform console of www.dokit.cn\n\n        ]]>\n    </string>\n\n    <string name=\"dk_file_manager_tip_bottom\">Please connect through the current ip:port on the web</string>\n    <string name=\"dk_file_manager_sd_permission_tip\">Your current targetSdkVersion>=29, please add android:requestLegacyExternalStorage=\"true\" under the application of AndroidManifest.xml to solve the problem that the SD card does not have permission</string>\n    <!-- h5助手-->\n    <string name=\"dk_kit_h5_help\">H5 Helper</string>\n    <string name=\"dk_kit_dokit_for_web\">DoKit Web</string>\n    <string name=\"dk_kit_dokit_studio\">DoKit Studio</string>\n</resources>\n"
  },
  {
    "path": "Android/dokit/src/main/res/values-zh-rCN/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"dk_app_name\">DoKit</string>\n\n    <string name=\"dk_category_biz\">业务专区</string>\n    <string name=\"dk_category_comms\">常用工具</string>\n    <string name=\"dk_category_lbs\">LBS</string>\n    <string name=\"dk_category_performance\">性能监控</string>\n    <string name=\"dk_category_large_image\">大图检测</string>\n    <string name=\"dk_category_ui\">视觉工具</string>\n    <string name=\"dk_category_platform\">平台工具</string>\n    <string name=\"dk_kit_network_mock\">数据Mock</string>\n    <string name=\"dk_kit_health\">健康体检</string>\n    <string name=\"dk_kit_file_transfer\">文件同步助手</string>\n    <string name=\"dk_category_weex\">Weex</string>\n\n    <string name=\"dk_kit_sysinfo\">App信息</string>\n    <string name=\"dk_third_library_info\">三方库信息</string>\n    <string name=\"dk_kit_develop\">开发者选项</string>\n    <string name=\"dk_kit_service_running\">运行中服务</string>\n    <string name=\"dk_kit_local_lang\">本地语言</string>\n    <string name=\"dk_kit_list\">工具列表</string>\n    <string name=\"dk_kit_demo\">demo</string>\n    <string name=\"dk_kit_file_explorer\">沙盒浏览</string>\n    <string name=\"dk_kit_frame_info\">帧率</string>\n    <string name=\"dk_kit_frame_info_desc\">帧率检测</string>\n    <string name=\"dk_kit_gps_mock\">位置模拟</string>\n    <string name=\"dk_kit_gps_mock_manual\">位置微调</string>\n    <string name=\"dk_kit_gps_mock_preset\">位置预设</string>\n    <string name=\"dk_kit_gps_mock_route\">实时导航</string>\n    <string name=\"dk_kit_color_picker\">取色器</string>\n    <string name=\"dk_kit_align_ruler\">对齐标尺</string>\n    <string name=\"dk_kit_log_info\">日志</string>\n    <string name=\"dk_kit_web_door\">H5任意门</string>\n    <string name=\"dk_kit_temporary_close\">隐藏</string>\n    <string name=\"dk_kit_crash\">Crash</string>\n    <string name=\"dk_kit_data_clean\">清理缓存</string>\n    <string name=\"dk_kit_cache_check_all\">全选</string>\n    <string name=\"dk_kit_db_debug\">DBView</string>\n    <string name=\"dk_kit_weak_network\">模拟弱网</string>\n    <string name=\"dk_kit_view_check\">控件检查</string>\n    <string name=\"dk_kit_net_monitor\">网络</string>\n    <string name=\"dk_kit_ui_monitor\">UI显示实时数据</string>\n    <string name=\"dk_kit_time_counter\">启动耗时</string>\n    <string name=\"dk_kit_method_cost\">函数耗时</string>\n    <string name=\"dk_kit_comm_webview\">......</string>\n    <string name=\"dk_kit_mock_template_preview\">数据预览</string>\n    <string name=\"dk_kit_method_cost_desc\">\n        <![CDATA[\n            函数耗时不提供UI操作界面,在你需要分析的代码之前插入<br/>\n            <font color=\"red\"> MethodCost.startMethodTracing(\\\"doramemon\\\") </font> <br/>\n            或者<br />\n           <font color=\"red\"> MethodCost.startMethodTracingSampling(\\\"doramemon\\\")</font> <br/>\n            结束的地方加上<br/>\n           <font color=\"red\"> MethodCost.stopMethodTracingAndPrintLog(\\\"doramemon\\\")</font><br/>\n           <font color=\"red\"> 注意:start和stop的文件名必须配对才能打印出函数耗时。</font><br/>\n            编译完成以后即可在控制台中通过MethodCost TAG过滤出函数耗时<br/>\n           <font color=\"red\"> 分析完成以后你可以手动删除相关代码也可以保留便于下次调试因为release环境下相关代码都是空实现,并不会影响线上性能。</font><br/>\n            <font color=\"red\">详细的操作可参考demo中的实现</font>\n        ]]>\n        </string>\n\n    <string name=\"dk_kit_db_debug_desc\">\n        <![CDATA[\n            温馨提示:<br/>\n            <font color=\"red\"> 请确保当前手机和PC处于同一局域网内 </font> <br/>\n            想要对当前app的数据库进行修改可以在pc上通过以下地址进行操作:\n        ]]>\n        </string>\n    <string name=\"dk_kit_exit\">退出 DoKit</string>\n    <string name=\"dk_kit_version\">当前版本：V%s</string>\n    <string name=\"dk_kit_db_detail\">数据库查看</string>\n    <string name=\"dk_kit_top_activity\">当前Activity</string>\n    <string name=\"dk_kit_pkg_name\">包名：</string>\n    <string name=\"dk_kit_class_name\">类名：</string>\n    <string name=\"dk_kit_path_name\">路径：</string>\n    <string name=\"dk_category_mode\">悬浮窗模式</string>\n    <string name=\"dk_kit_mode_desc\">悬浮窗模式:\n        \\n\n        1）系统代表系统悬浮窗(需要系统权限)\n        \\n\n        2）常规代表内置浮标(不需要系统权限)\n    </string>\n    <string name=\"dk_kit_mode_rb_system\">系统</string>\n    <string name=\"dk_kit_mode_rb_normal\">常规</string>\n\n    <string name=\"dk_sysinfo_device_info\">手机信息</string>\n    <string name=\"dk_sysinfo_app_info\">App信息</string>\n    <string name=\"dk_sysinfo_permission_info\">权限信息</string>\n    <string name=\"dk_sysinfo_permission_info_unreliable\">权限信息 (App未做6.0权限适配，以下信息仅供参考)</string>\n    <string name=\"dk_sysinfo_brand_and_model\">手机型号</string>\n    <string name=\"dk_sysinfo_android_version\">系统版本</string>\n    <string name=\"dk_sysinfo_package_name\">包名</string>\n    <string name=\"dk_sysinfo_package_version_name\">应用版本名</string>\n    <string name=\"dk_sysinfo_package_version_code\">应用版本号</string>\n    <string name=\"dk_sysinfo_package_min_sdk\">最低系统版本号</string>\n    <string name=\"dk_sysinfo_package_target_sdk\">目标系统版本号</string>\n    <string name=\"dk_sysinfo_ext_storage_free\">sd卡剩余空间</string>\n    <string name=\"dk_sysinfo_rom_free\">系统剩余空间</string>\n    <string name=\"dk_sysinfo_display_size\">分辨率</string>\n    <string name=\"dk_sysinfo_display_inch\">屏幕尺寸</string>\n    <string name=\"dk_sysinfo_permission_location\">地理位置权限</string>\n    <string name=\"dk_sysinfo_permission_sdcard\">磁盘权限</string>\n    <string name=\"dk_sysinfo_permission_camera\">拍照权限</string>\n    <string name=\"dk_sysinfo_permission_record\">麦克风权限</string>\n    <string name=\"dk_sysinfo_permission_read_phone\">设备信息权限</string>\n    <string name=\"dk_sysinfo_permission_contact\">通讯录权限</string>\n\n    <string name=\"dk_frameinfo_fps\">帧率</string>\n    <string name=\"dk_frameinfo_cpu\">CPU</string>\n    <string name=\"dk_frameinfo_custom\">自定义</string>\n    <string name=\"dk_frameinfo_upstream\">上行流量</string>\n    <string name=\"dk_frameinfo_downstream\">下行流量</string>\n    <string name=\"dk_frameinfo_avg_value\">平均值</string>\n    <string name=\"dk_frameinfo_big_img\">大图检测</string>\n    <string name=\"dk_frameinfo_leakcanary\">LeakCanary</string>\n    <string name=\"dk_tools_dbdebug\">DBView</string>\n\n    <string name=\"dk_log_info_edt_hint\">输入想要过滤的关键字</string>\n    <string name=\"dk_log_info_verbose\">Verbose</string>\n    <string name=\"dk_log_info_debug\">Debug</string>\n    <string name=\"dk_log_info_info\">Info</string>\n    <string name=\"dk_log_info_warn\">Warn</string>\n    <string name=\"dk_log_info_error\">Error</string>\n\n    <string name=\"dk_web_door_hint\">输入地址，点击按钮跳转</string>\n    <string name=\"dk_web_door_explore\">点击跳转</string>\n    <string name=\"dk_web_door_clear_history\">清空搜索歷史</string>\n\n    <string name=\"dk_gpsmock_open\">开启模拟定位</string>\n    <string name=\"dk_gpsmock_tv_location\">模拟</string>\n\n    <string name=\"dk_gpsmock_hint_longlat\">请输入经纬度</string>\n\n    <!--卡顿检测-->\n    <string name=\"dk_kit_block_monitor\">卡顿</string>\n    <string name=\"dk_kit_block_monitor_list\">卡顿列表</string>\n    <string name=\"dk_kit_block_monitor_detail\">卡顿详情</string>\n    <string name=\"dk_item_block_switch\">卡顿检测开关</string>\n    <string name=\"dk_item_block_goto_list\">查看卡顿记录</string>\n    <string name=\"dk_item_block_mock\">模拟卡顿</string>\n\n    <string name=\"dk_third_sort_name\">按名称</string>\n    <string name=\"dk_third_sort_size\">按大小</string>\n\n    <string name=\"dk_block_class_has_blocked\">blocked %s ms</string>\n    <string name=\"dk_block_notification_message\">Click for more details</string>\n\n    <string name=\"dk_crash_capture_tips\">DoraemonKit正在为您记录Crash</string>\n    <string name=\"dk_crash_capture_no_record\">暂无crash记录</string>\n    <string name=\"dk_crash_capture_clean_data\">一键清理Crash日志</string>\n    <string name=\"dk_crash_capture_look\">查看Crash日志</string>\n    <string name=\"dk_crash_capture_switch\">Crash日志收集开关</string>\n    <string name=\"dk_crash_capture_summary_title\">Crash日志列表</string>\n    <string name=\"dk_crash_capture_screenshot\">Crash截图开关</string>\n\n    <!--大图检测开关-->\n    <string name=\"dk_large_picture_switch\">大图检测开关</string>\n    <string name=\"dk_large_picture_look\">大图检测记录</string>\n    <string name=\"dk_large_picture_list\">大图列表</string>\n    <string name=\"dk_large_picture_threshold\">大图内存检测阈值</string>\n    <string name=\"dk_large_picture_file_threshold\">大图文件检测阈值</string>\n\n    <string name=\"dk_large_picture_threshold_desc\">\n        <![CDATA[\n        描述:关于图片文件大小和文件被加载到内存以后的区别。<br/>\n        <font color=\"red\">参考:https://juejin.im/post/5bc406b9f265da0aa664ea1e</font><br/>\n        ]]>\n    </string>\n\n    <string name=\"dk_weak_network_switch\">模拟弱网开关</string>\n    <string name=\"dk_weak_network_off\">断网</string>\n    <string name=\"dk_weak_network_timeout\">超时</string>\n    <string name=\"dk_weak_network_speed_limit\">限速</string>\n    <string name=\"dk_weak_network_request_limit\">请求限速：</string>\n    <string name=\"dk_weak_network_response_limit\">响应限速：</string>\n    <string name=\"dk_weak_network_limit_message\">请求限速会在上传时限速，响应限速会在下载时限速，0则不限速</string>\n\n    <string name=\"dk_back\">返回</string>\n    <string name=\"dk_frameinfo_ram\">内存</string>\n    <string name=\"dk_kit_network_monitor\">网络</string>\n    <string name=\"dk_float_permission_toast\">哆啦A梦需要打开悬浮窗权限才能正常使用</string>\n    <string name=\"dk_gps_location_change_toast\">地址变为: %1$s , %2$s</string>\n    <string name=\"dk_view_check_info_desc\">背景颜色：%1$s</string>\n    <string name=\"dk_view_check_info_padding\">内边距：%1$d, %2$d, %3$d, %4$d</string>\n    <string name=\"dk_view_check_info_margin\">外边距：%1$d, %2$d, %3$d, %4$d</string>\n    <string name=\"dk_view_check_info_text_color\">文字颜色：%1$s</string>\n    <string name=\"dk_view_check_info_text_size\">文字大小：%1$d</string>\n    <string name=\"dk_view_check_info_size\">控件尺寸：宽%1$d，高%2$d</string>\n    <string name=\"dk_view_check_info_id\">控件ID：%1$s</string>\n    <string name=\"dk_view_check_info_class\">控件类型：%1$s</string>\n    <string name=\"dk_view_check_info_activity\">当前Activity：%1$s</string>\n    <string name=\"dk_view_check_info_fragment\">可见Fragments：%1$s</string>\n    <string name=\"dk_network_get_method\">GET</string>\n    <string name=\"dk_network_post_method\">POST</string>\n    <string name=\"dk_network_request\">请求</string>\n    <string name=\"dk_network_response\">响应</string>\n    <string name=\"dk_network_method\">Method</string>\n\n    <string name=\"dk_frameinfo_detection_switch\">帧率检测开关</string>\n    <string name=\"dk_cpu_detection_switch\">CPU检测开关</string>\n    <string name=\"dk_cpu_title_cache_log\">检测记录</string>\n    <string name=\"dk_item_cache_log\">查看检测记录</string>\n\n    <string name=\"dk_ram_detection_switch\">内存检测开关</string>\n    <string name=\"dk_ram_detection_title\">内存检测</string>\n    <string name=\"dk_network_summary_total_time_hour\">%1$d小时%2$d分</string>\n    <string name=\"dk_network_summary_total_time_day\">%1$d天%2$d小时</string>\n    <string name=\"dk_network_summary_total_time_minute\">%1$d分%2$d秒</string>\n    <string name=\"dk_network_summary_total_time_second\">%d秒</string>\n    <string name=\"dk_network_summary_total_time_default\">0秒</string>\n    <string name=\"dk_network_summary_data_type\">数据类型</string>\n    <string name=\"dk_network_summary_http_method\">HTTP方法</string>\n    <string name=\"dk_network_summary_data_upload\">数据上传</string>\n    <string name=\"dk_network_summary_data_down\">数据下载</string>\n    <string name=\"dk_network_summary_total_number\">抓包数量</string>\n    <string name=\"dk_network_summary_total_number_time_tips\">总计已为您抓包</string>\n    <string name=\"dk_network_detail_title_response_body\">响应行</string>\n    <string name=\"dk_network_detail_title_request_body\">请求行</string>\n    <string name=\"dk_net_monitor_title_summary\">流量监控摘要</string>\n    <string name=\"dk_net_monitor_list\">流量监控列表</string>\n    <string name=\"dk_net_monitor_detection_switch\">流量检测开关</string>\n    <string name=\"dk_net_monitor_show_summary\">显示流量监控详情</string>\n    <string name=\"dk_network_detail_title_body\">消息体</string>\n    <string name=\"dk_network_detail_title_size\">数据大小</string>\n    <string name=\"dk_network_detail_title_url\">链接</string>\n    <string name=\"dk_network_detail_title_request_time\">请求时间</string>\n    <string name=\"dk_network_detail_title_response_time\">响应时间</string>\n    <string name=\"dk_network_detail_title_response_header\">响应头</string>\n    <string name=\"dk_network_detail_title_request_header\">请求头</string>\n    <string name=\"dk_kit_network_filter_hint\">支持筛选</string>\n    <string name=\"dk_kit_network_monitor_detail\">流量监测详情</string>\n    <string name=\"dk_cpu_memory_remind_user\">抱歉，您当前系统版本高于8.0，由于谷歌权限收紧，只能用adb的方式获取性能分析的数据，请手机连接电脑并输入adb tcpip 5555以保证功能正常运行</string>\n    <string name=\"dk_kit_network_time_format\">%1$s 耗时:%2$s</string>\n    <string name=\"dk_kit_layout_border\">布局边框</string>\n    <string name=\"dk_layout_level\">布局层级</string>\n    <string name=\"dk_align_info_text\">位置：左%1$d 右%2$d 上%3$d 下%4$d</string>\n    <string name=\"dk_align_info_include_status_bar\">包含状态栏高度</string>\n\n    <string name=\"dk_item_time_counter_switch\">Activity跳转耗时</string>\n    <string name=\"dk_item_time_goto_list\">查看记录</string>\n    <string name=\"dk_kit_block_time_counter_list\">耗时列表</string>\n    <string name=\"dk_delete\">删除</string>\n    <string name=\"dk_db_tips_insert\">添加</string>\n    <string name=\"dk_submit\">提交</string>\n    <string name=\"dk_cancel\">取消</string>\n    <string name=\"dk_discard\">丢弃</string>\n    <string name=\"dk_success\">成功</string>\n    <string name=\"dk_fail\">失败</string>\n    <string name=\"dk_confirm\">确认</string>\n    <string name=\"dk_post\">提交</string>\n    <string name=\"dk_app_data_clean\">确认要删除本地数据</string>\n    <string name=\"dk_hint\">提示</string>\n    <string name=\"dk_share\">分享</string>\n    <string name=\"dk_save\">保存</string>\n\n    <string name=\"dk_platform_monitor_data_button\">开始测试</string>\n    <string name=\"dk_platform_monitor_data_button_stop\">结束测试</string>\n    <string name=\"dk_platform_monitor_view_stat_data\">查看统计数据</string>\n    <string name=\"dk_platform_monitor_page_data\">页面数据</string>\n    <string name=\"dk_log_text_loading\">日志加载中...</string>\n    <string name=\"dk_crash_need_permission\">请授权读写权限，避免crash文件丢失</string>\n    <string name=\"dk_data_clean_toast\">清除系统资料</string>\n    <string name=\"dk_error_tips_permissions_less\">请进行授权才可以使用该功能</string>\n    <string name=\"dk_kit_ui_performance\">UI层级</string>\n    <string name=\"dk_view_render_analysis\">View渲染统计</string>\n    <string name=\"dk_max_level\">最大层级: </string>\n    <string name=\"dk_view_id\">控件id: </string>\n    <string name=\"dk_total_draw_time\">总绘制耗时: </string>\n    <string name=\"dk_max_draw_time\">最大绘制耗时: </string>\n    <string name=\"dk_mock_search\">搜索</string>\n    <!--jsonviewer-->\n    <string name=\"dk_jsonViewer_icon_plus\">expand</string>\n    <string name=\"dk_jsonViewer_icon_minus\">collapse</string>\n    <string name=\"dk_data_mock_et_hint\">支持筛选</string>\n    <string name=\"dk_data_mock_bottom_table_mock\">Mock数据</string>\n    <string name=\"dk_data_mock_bottom_table_template\">上传模板</string>\n    <string name=\"dk_data_mock_switch_all\">全部</string>\n    <string name=\"dk_data_mock_switch_opened\">打开</string>\n    <string name=\"dk_data_mock_switch_closed\">关闭</string>\n    <string name=\"dk_data_mock_group\">接口分组</string>\n    <string name=\"dk_data_mock_switch_status\">开关状态</string>\n    <string name=\"dk_health_upload_successed\">上传数据成功</string>\n    <string name=\"dk_health_upload_failed\">上传数据失败,请重新上传</string>\n    <string name=\"dk_health_upload_droped\">本次测试用例已丢弃!</string>\n    <string name=\"dk_health_upload_title\">健康体检</string>\n    <string name=\"dk_health_upload_message\">是否确认开始执行健康体检?</string>\n    <string name=\"dk_health_funcation_start\">App即将重启并开始进入体检模式</string>\n    <string name=\"dk_health_funcation_running\">正在体检中...</string>\n    <string name=\"dk_health_step_index\">向上滑动查看功能使用说明</string>\n    <string name=\"dk_health_title_step1\">第一步</string>\n    <string name=\"dk_health_title_step2\">第二步</string>\n    <string name=\"dk_health_title_step3\">第三步</string>\n    <string name=\"dk_health_title_step4\">第四步</string>\n    <string name=\"dk_health_step1\">点击开始体验按钮开始本次的性能测试</string>\n    <string name=\"dk_health_step2\">在每一个页面至少停留10秒钟，如果低于10秒钟的话，我们将会丢弃该页面收集到的数据。</string>\n    <string name=\"dk_health_step3\">测试完毕之后，重新进入本页面并点击结束测试按钮，填写测试用例名称和测试人的名字，即可上传本次性能测试数据</string>\n    <string name=\"dk_health_step4\">打开www.dokit.cn平台，进入app健康体检列表，即可查看本次的性能测试报告</string>\n    <string name=\"dk_health_back_top\">回到顶部</string>\n    <string name=\"dk_log_btn_clean\">清空日志</string>\n    <string name=\"dk_log_btn_export\">导出</string>\n    <string name=\"dk_log_btn_back_top\">回到顶部</string>\n    <string name=\"dk_log_btn_to_bottom\">滚至底部</string>\n    <string name=\"dk_kit_net_monitor_white_host_tip\">白名单(默认全部拦截,如有添加白名单则只会拦截白名单中的网络请求,同时白名单规则在关闭当前页面时生效。白名单host示例:m.baidu.com)</string>\n    <string name=\"dk_kit_net_monitor_white_host_edit_tip\">请输入白名单host</string>\n    <string name=\"dk_kit_net_monitor_white_host_edit_toast\">请先输入host</string>\n    <string name=\"dk_data_mock_plugin_toast\">请先到https://www.dokit.cn申请projectId,并参考使用中心进行接入</string>\n    <string name=\"dk_header_reset\">下拉刷新</string>\n    <string name=\"dk_header_pull\">下拉刷新</string>\n    <string name=\"dk_header_pull_over\">释放立即刷新</string>\n    <string name=\"dk_header_refreshing\">正在刷新...</string>\n    <string name=\"dk_header_completed\">刷新成功</string>\n    <string name=\"dk_brvah_loading\">正在加载中...</string>\n    <string name=\"dk_brvah_load_failed\">加载失败，请点我重试</string>\n    <string name=\"dk_brvah_load_end\">没有更多数据</string>\n    <string name=\"dk_brvah_load_complete\">点击加载更多</string>\n    <string name=\"dk_data_mock_template_tip\">本地是否存在mock模板数据:%s</string>\n    <string name=\"dk_data_mock_template_upload\">上传模板</string>\n    <string name=\"dk_weaknet_type\">弱网类型:</string>\n    <string name=\"dk_weaknet_type_speed\">限速</string>\n    <string name=\"dk_weaknet_type_timeout\">超时</string>\n    <string name=\"dk_weaknet_type_off\">断网</string>\n    <string name=\"dk_weak_network_speed_unit\">K/s</string>\n    <string name=\"dk_health_dialog_title\">结束前请完善以下信息</string>\n    <string name=\"dk_health_dialog_test_name\">测试用例名称</string>\n    <string name=\"dk_health_dialog_test_name_hint\">测试用例名称</string>\n    <string name=\"dk_health_dialog_test_person\">测试人</string>\n    <string name=\"dk_health_dialog_test_person_hint\">测试人</string>\n    <string name=\"dk_kit_log_min\">最小化</string>\n    <string name=\"dk_kit_setting\">更多</string>\n    <string name=\"dk_category_exit\">退出</string>\n    <string name=\"dk_category_version\">version</string>\n    <string name=\"dk_setting\">更多</string>\n    <string name=\"dk_edit\">编辑</string>\n    <string name=\"dk_complete\">完成</string>\n    <string name=\"dk_export\">导出</string>\n    <string name=\"dk_manager_kit_title\">管理我的功能</string>\n    <string name=\"dk_kit_block_time_app_start_info\">启动详情</string>\n    <string name=\"dk_plugin_close_tip\">未引入插件或插件未开启</string>\n    <string name=\"dk_plugin_network_close_tip\">网络插件未开启</string>\n    <string name=\"dk_plugin_method_close_tip\">慢函数插件未开启</string>\n    <string name=\"dk_plugin_method_strategy_tip\">请修改慢函数的插件配置等于0</string>\n    <string name=\"dk_plugin_big_img_close_tip\">大图检测插件未开启</string>\n    <string name=\"dk_plugin_gps_close_tip\">GPS插件未开启</string>\n    <string name=\"dk_platform_tip\">需要到www.dokit.cn上注册pId才能使用该功能</string>\n    <string name=\"dk_reset\">还原</string>\n    <string name=\"dk_toolpanel_save_complete\">保存成功</string>\n    <string name=\"dk_toolpanel_reset_complete\">功能还原成功</string>\n    <string name=\"dk_toolpanel_dialog_edit_tip\">是否保存已编辑的内容</string>\n    <string name=\"dk_toolpanel_dialog_reset_tip\">是否还原到初始状态</string>\n    <string name=\"dk_setting_kit_manager\">功能管理</string>\n\n    <!--    文件同步助手-->\n    <string name=\"dk_file_manager_tip_top\">\n        <![CDATA[\n        请在www.dokit.cn平台端控制台中的<font color=\\\"blue\\\">【文件同步助手】</font>\n        中使用该功能\n        ]]>\n    </string>\n\n    <string name=\"dk_file_manager_tip_bottom\">请在web端通过当前ip:port进行连接</string>\n    <string name=\"dk_file_manager_sd_permission_tip\">你当前的targetSdkVersion>=29,请在AndroidManifest.xml的application下添加android:requestLegacyExternalStorage=\"true\" 解决sd卡没有权限的问题</string>\n    <!-- h5助手-->\n    <string name=\"dk_kit_h5_help\">H5助手</string>\n    <string name=\"dk_kit_dokit_for_web\">DoKit Web</string>\n    <string name=\"dk_kit_dokit_studio\">DoKit Studio</string>\n</resources>\n"
  },
  {
    "path": "Android/dokit/src/main/res/values-zh-rTW/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"dk_app_name\">DoKit</string>\n\n    <string name=\"dk_category_biz\">業務專區</string>\n    <string name=\"dk_category_comms\">常用工具</string>\n    <string name=\"dk_category_lbs\">LBS</string>\n    <string name=\"dk_category_performance\">效能監控</string>\n    <string name=\"dk_category_large_image\">大圖檢測</string>\n    <string name=\"dk_category_ui\">視覺工具</string>\n    <string name=\"dk_category_platform\">平台工具</string>\n    <string name=\"dk_kit_network_mock\">数据Mock</string>\n    <string name=\"dk_kit_health\">健康體檢</string>\n    <string name=\"dk_kit_file_transfer\">文件同步助手</string>\n    <string name=\"dk_category_weex\">Weex</string>\n\n    <string name=\"dk_kit_sysinfo\">App信息</string>\n    <string name=\"dk_third_library_info\">三方库信息</string>\n    <string name=\"dk_kit_develop\">開發者選項</string>\n    <string name=\"dk_kit_service_running\">運行中服務</string>\n    <string name=\"dk_kit_local_lang\">本地語言</string>\n    <string name=\"dk_kit_list\">工具列表</string>\n    <string name=\"dk_kit_demo\">demo</string>\n    <string name=\"dk_kit_file_explorer\">沙盒瀏覽</string>\n    <string name=\"dk_kit_frame_info\">幀率</string>\n    <string name=\"dk_kit_frame_info_desc\">幀率檢測</string>\n    <string name=\"dk_kit_gps_mock\">位置模擬</string>\n    <string name=\"dk_kit_gps_mock_manual\">位置微調</string>\n    <string name=\"dk_kit_gps_mock_preset\">位置預設</string>\n    <string name=\"dk_kit_gps_mock_route\">路徑規劃</string>\n    <string name=\"dk_kit_color_picker\">取色器</string>\n    <string name=\"dk_kit_align_ruler\">對齊標尺</string>\n    <string name=\"dk_kit_log_info\">Log</string>\n    <string name=\"dk_kit_web_door\">H5任意門</string>\n    <string name=\"dk_kit_temporary_close\">隱藏</string>\n    <string name=\"dk_kit_crash\">Crash</string>\n    <string name=\"dk_kit_data_clean\">清理缓存</string>\n    <string name=\"dk_kit_cache_check_all\">全选</string>\n    <string name=\"dk_kit_db_debug\">DBView</string>\n    <string name=\"dk_kit_weak_network\">模擬弱網</string>\n    <string name=\"dk_kit_view_check\">View 元件檢查</string>\n    <string name=\"dk_kit_net_monitor\">網絡</string>\n    <string name=\"dk_kit_ui_monitor\">UI顯示實時數據</string>\n    <string name=\"dk_kit_time_counter\">啟動耗時</string>\n    <string name=\"dk_kit_method_cost\">函數耗時</string>\n    <string name=\"dk_kit_comm_webview\">......</string>\n    <string name=\"dk_kit_mock_template_preview\">數據預覽</string>\n    <string name=\"dk_kit_method_cost_desc\">\n        <![CDATA[\n        函数耗时不提供UI操作界面,在你需要分析的代码之前插入<br/>\n        <font color=\"red\"> MethodCost.startMethodTracing(\"doramemon\") </font><br/>\n        或者<br/>\n       <font color=\"red\"> MethodCost.startMethodTracingSampling(\"doramemon\")</font><br/>\n        结束的地方加上<br/>\n       <font color=\"red\"> MethodCost.stopMethodTracingAndPrintLog(\"doramemon\")</font><br/>\n       <font color=\"red\"> 注意:start和stop的文件名必须配对才能打印出函数耗时。</font><br/>\n        编译完成以后即可在控制台中通过MethodCost TAG过滤出函数耗时<br/>\n       <font color=\"red\"> 分析完成以后你可以手动删除相关代码也可以保留便于下次调试因为release环境下相关代码都是空实现,并不会影响线上性能。</font><br/>\n        <font color=\"red\">详细的操作可参考demo中的实现</font>\n        ]]>\n    </string>\n    <string name=\"dk_kit_db_debug_desc\">\n        <![CDATA[\n            溫馨提示:<br/>\n            <font color=\"red\">請確保當前手機和PC處於同一局域網內</font> <br/>\n            想要對當前app的數據庫進行修改可以再pc上通過以下地址進行操作:\n        ]]>\n        </string>\n    <string name=\"dk_kit_exit\">退出 DoKit</string>\n    <string name=\"dk_kit_version\">當前版本：V%s</string>\n    <string name=\"dk_kit_db_detail\">資料庫查看</string>\n    <string name=\"dk_kit_top_activity\">當前Activity</string>\n    <string name=\"dk_kit_pkg_name\">包名：</string>\n    <string name=\"dk_kit_class_name\">類名：</string>\n    <string name=\"dk_kit_path_name\">路徑：</string>\n    <string name=\"dk_category_mode\">懸浮窗模式</string>\n    <string name=\"dk_kit_mode_desc\">懸浮窗模式:\n        \\n\n        1）系統代表系統懸浮窗（需要系統權權限）\n        \\n\n        2）常規代表內置浮標（不需要系統權限\n    </string>\n    <string name=\"dk_kit_mode_rb_system\">系統</string>\n    <string name=\"dk_kit_mode_rb_normal\">常規</string>\n    <string name=\"dk_sysinfo_device_info\">手機資訊</string>\n    <string name=\"dk_sysinfo_app_info\">App 資訊</string>\n    <string name=\"dk_sysinfo_permission_info\">權限資訊</string>\n    <string name=\"dk_sysinfo_permission_info_unreliable\">權限資訊 (App 未做 6.0 權限管理，以下資訊僅供參考)</string>\n    <string name=\"dk_sysinfo_brand_and_model\">手機型號</string>\n    <string name=\"dk_sysinfo_android_version\">系統版本</string>\n    <string name=\"dk_sysinfo_package_name\">Package Name</string>\n    <string name=\"dk_sysinfo_package_version_name\">Application Version Name</string>\n    <string name=\"dk_sysinfo_package_version_code\">Application Version Code</string>\n    <string name=\"dk_sysinfo_package_min_sdk\">Minimum SDK</string>\n    <string name=\"dk_sysinfo_package_target_sdk\">Target SDK</string>\n    <string name=\"dk_sysinfo_ext_storage_free\">SD 卡剩餘空間</string>\n    <string name=\"dk_sysinfo_rom_free\">系統剩餘空間</string>\n    <string name=\"dk_sysinfo_permission_location\">位置權限</string>\n    <string name=\"dk_sysinfo_permission_sdcard\">儲存空間權限</string>\n    <string name=\"dk_sysinfo_permission_camera\">相機權限</string>\n    <string name=\"dk_sysinfo_permission_record\">麥克風權限</string>\n    <string name=\"dk_sysinfo_permission_read_phone\">設備資訊權限</string>\n    <string name=\"dk_sysinfo_permission_contact\">通訊錄權限</string>\n\n    <string name=\"dk_frameinfo_fps\">幀率</string>\n    <string name=\"dk_frameinfo_cpu\">CPU</string>\n    <string name=\"dk_frameinfo_custom\">自定義</string>\n    <string name=\"dk_frameinfo_downstream\">下行流量</string>\n    <string name=\"dk_frameinfo_upstream\">上流量</string>\n    <string name=\"dk_frameinfo_avg_value\">平均值</string>\n    <string name=\"dk_frameinfo_big_img\">大圖檢測</string>\n    <string name=\"dk_frameinfo_leakcanary\">LeakCanary</string>\n    <string name=\"dk_tools_dbdebug\">DBView</string>\n\n    <string name=\"dk_log_info_edt_hint\">輸入想要過濾的關鍵字</string>\n    <string name=\"dk_log_info_verbose\">Verbose</string>\n    <string name=\"dk_log_info_debug\">Debug</string>\n    <string name=\"dk_log_info_info\">Info</string>\n    <string name=\"dk_log_info_warn\">Warn</string>\n    <string name=\"dk_log_info_error\">Error</string>\n\n    <string name=\"dk_web_door_hint\">輸入地址，點擊按鈕切換</string>\n    <string name=\"dk_web_door_explore\">點擊切換</string>\n    <string name=\"dk_web_door_clear_history\">清空搜索歷史</string>\n\n\n    <string name=\"dk_gpsmock_open\">開啟模擬定位</string>\n    <string name=\"dk_gpsmock_tv_location\">模擬地址</string>\n\n\n    <string name=\"dk_gpsmock_hint_longlat\">請輸入經緯度</string>\n\n    <!--卡頓檢測-->\n    <string name=\"dk_kit_block_monitor\">卡頓</string>\n    <string name=\"dk_kit_block_monitor_list\">ANR 列表</string>\n    <string name=\"dk_kit_block_monitor_detail\">ANR 詳情</string>\n    <string name=\"dk_item_block_switch\">ANR 檢測開關</string>\n    <string name=\"dk_item_block_goto_list\">查看 ANR 紀錄</string>\n    <string name=\"dk_item_block_mock\">模擬 ANR</string>\n\n    <string name=\"dk_third_sort_name\">按名稱</string>\n    <string name=\"dk_third_sort_size\">按大小</string>\n\n    <string name=\"dk_block_class_has_blocked\">blocked %s ms</string>\n    <string name=\"dk_block_notification_message\">Click for more details</string>\n\n    <string name=\"dk_crash_capture_tips\">DoraemonKit 正在為您記錄 Crash</string>\n    <string name=\"dk_crash_capture_no_record\">暫無 crash 紀錄</string>\n    <string name=\"dk_crash_capture_clean_data\">一鍵清理 Crash Log</string>\n    <string name=\"dk_crash_capture_look\">查看 Crash Log</string>\n    <string name=\"dk_crash_capture_switch\">Crash Log 收集開關</string>\n    <string name=\"dk_crash_capture_summary_title\">Crash Log 列表</string>\n    <string name=\"dk_crash_capture_screenshot\">Crash截圖開關</string>\n\n    <!--大图检测开关-->\n    <string name=\"dk_large_picture_switch\">大圖檢測開關</string>\n    <string name=\"dk_large_picture_look\">大圖檢測記錄</string>\n    <string name=\"dk_large_picture_list\">大圖列表</string>\n    <string name=\"dk_large_picture_threshold\">大圖內存檢測閾值</string>\n    <string name=\"dk_large_picture_file_threshold\">大圖文件檢測閾值</string>\n\n    <string name=\"dk_large_picture_threshold_desc\">\n        <![CDATA[\n        描述:關於圖片文件大小和文件被加載到內存以後的區別.<br/>\n        <font color=\"red\">參考：https://juejin.im/post/5bc406b9f265da0aa664ea1e</font><br/>\n        ]]>\n    </string>\n\n    <string name=\"dk_weak_network_switch\">模擬弱網開關</string>\n    <string name=\"dk_weak_network_off\">斷網</string>\n    <string name=\"dk_weak_network_timeout\">超時</string>\n    <string name=\"dk_weak_network_speed_limit\">限速</string>\n    <string name=\"dk_weak_network_request_limit\">請求限速：</string>\n    <string name=\"dk_weak_network_response_limit\">響應限速：</string>\n    <string name=\"dk_weak_network_limit_message\">請求限速會在上傳時限速，響應限速會在下載時限速，0則不限速</string>\n\n    <string name=\"dk_back\">返回</string>\n    <string name=\"dk_frameinfo_ram\">RAM</string>\n    <string name=\"dk_kit_network_monitor\">網絡</string>\n    <string name=\"dk_data_clean_toast\">清除系統資料</string>\n    <string name=\"dk_float_permission_toast\">哆啦A夢需要打開懸浮視窗權限才能正常使用</string>\n    <string name=\"dk_gps_location_change_toast\">地址變為: %1$s , %2$s</string>\n    <string name=\"dk_view_check_info_desc\">背景顏色：%1$s</string>\n    <string name=\"dk_view_check_info_padding\">內邊距：%1$d, %2$d, %3$d, %4$d</string>\n    <string name=\"dk_view_check_info_margin\">外邊距：%1$d, %2$d, %3$d, %4$d</string>\n    <string name=\"dk_view_check_info_text_color\">文字顏色：%1$s</string>\n    <string name=\"dk_view_check_info_text_size\">文字大小：%1$d</string>\n    <string name=\"dk_view_check_info_size\">View 尺寸：寬%1$d，高%2$d</string>\n    <string name=\"dk_view_check_info_id\">View ID：%1$s</string>\n    <string name=\"dk_view_check_info_class\">View 類別：%1$s</string>\n    <string name=\"dk_view_check_info_activity\">當前 Activity：%1$s</string>\n    <string name=\"dk_view_check_info_fragment\">可見 Fragments：%1$s</string>\n    <string name=\"dk_network_get_method\">GET</string>\n    <string name=\"dk_network_post_method\">POST</string>\n    <string name=\"dk_network_request\">Request</string>\n    <string name=\"dk_network_response\">Response</string>\n    <string name=\"dk_network_method\">Method</string>\n    <string name=\"dk_frameinfo_detection_switch\">幀率檢測開關</string>\n    <string name=\"dk_cpu_detection_switch\">CPU 檢測開關</string>\n    <string name=\"dk_cpu_title_cache_log\">檢測紀錄</string>\n    <string name=\"dk_item_cache_log\">查看檢測記錄</string>\n    <string name=\"dk_ram_detection_switch\">記憶體檢測開關</string>\n    <string name=\"dk_ram_detection_title\">記憶體檢測</string>\n    <string name=\"dk_network_summary_total_time_hour\">%1$d小時%2$d分</string>\n    <string name=\"dk_network_summary_total_time_day\">%1$d天%2$d小時</string>\n    <string name=\"dk_network_summary_total_time_minute\">%1$d分%2$d秒</string>\n    <string name=\"dk_network_summary_total_time_second\">%d秒</string>\n    <string name=\"dk_network_summary_total_time_default\">0秒</string>\n    <string name=\"dk_network_summary_data_type\">Data Type</string>\n    <string name=\"dk_network_summary_http_method\">HTTP Method</string>\n    <string name=\"dk_network_summary_data_upload\">資料上傳</string>\n    <string name=\"dk_network_summary_data_down\">資料下載</string>\n    <string name=\"dk_network_summary_total_number\">攔截封包數量</string>\n    <string name=\"dk_network_detail_title_response_body\">Response Body</string>\n    <string name=\"dk_network_detail_title_request_body\">Request Body</string>\n    <string name=\"dk_net_monitor_title_summary\">流量監控摘要</string>\n    <string name=\"dk_net_monitor_list\">流量監控列表</string>\n    <string name=\"dk_net_monitor_detection_switch\">流量檢測開關</string>\n    <string name=\"dk_net_monitor_show_summary\">顯示流量監控詳情</string>\n    <string name=\"dk_network_detail_title_size\">Data Size</string>\n    <string name=\"dk_network_detail_title_request_time\">Request Time</string>\n    <string name=\"dk_network_detail_title_response_time\">Response Time</string>\n    <string name=\"dk_network_detail_title_response_header\">Response Header</string>\n    <string name=\"dk_network_detail_title_request_header\">Request Header</string>\n    <string name=\"dk_kit_network_filter_hint\">支援篩選</string>\n    <string name=\"dk_kit_network_monitor_detail\">流量監測詳情</string>\n    <string name=\"dk_cpu_memory_remind_user\">抱歉，您當前系統版本高於 8.0，由於 Google 權限問題，只能透過 ADB 獲取效能分析的資訊，請用手機連接電腦並輸入 adb tcpip 5555 以確保功能正常執行</string>\n    <string name=\"dk_kit_network_time_format\">%1$s 耗時:%2$s</string>\n    <string name=\"dk_kit_layout_border\">版面配置邊界</string>\n    <string name=\"dk_layout_level\">版面階層</string>\n    <string name=\"dk_align_info_text\">位置：左%1$d 右%2$d 上%3$d 下%4$d</string>\n    <string name=\"dk_align_info_include_status_bar\">包含狀態欄高度</string>\n\n    <string name=\"dk_item_time_counter_switch\">Activity 換頁耗時</string>\n    <string name=\"dk_item_time_goto_list\">查看紀錄</string>\n    <string name=\"dk_kit_block_time_counter_list\">耗時列表</string>\n    <string name=\"dk_sysinfo_display_size\">解析度</string>\n    <string name=\"dk_sysinfo_display_inch\">屏幕尺寸</string>\n    <string name=\"dk_network_detail_title_body\">Message Body</string>\n    <string name=\"dk_network_detail_title_url\">鏈接</string>\n    <string name=\"dk_network_summary_total_number_time_tips\">總封包數</string>\n    <string name=\"dk_cancel\">取消</string>\n    <string name=\"dk_discard\">丟棄</string>\n    <string name=\"dk_confirm\">確認</string>\n    <string name=\"dk_post\">提交</string>\n    <string name=\"dk_crash_need_permission\">請授權讀寫權限，避免crash文件丟失</string>\n    <string name=\"dk_db_tips_insert\">添加</string>\n    <string name=\"dk_delete\">刪除</string>\n    <string name=\"dk_error_tips_permissions_less\">請進行授權才可以使用該功能</string>\n    <string name=\"dk_fail\">失敗</string>\n\n    <string name=\"dk_hint\">提示</string>\n    <string name=\"dk_share\">分享</string>\n    <string name=\"dk_save\">保存</string>\n    <string name=\"dk_platform_monitor_data_button\">開始測試</string>\n    <string name=\"dk_platform_monitor_data_button_stop\">結束測試</string>\n    <string name=\"dk_success\">成功</string>\n    <string name=\"dk_submit\">提交</string>\n    <string name=\"dk_log_text_loading\">日誌加載中...</string>\n    <string name=\"dk_platform_monitor_page_data\">頁面數據</string>\n    <string name=\"dk_app_data_clean\">確認要刪除本地數據</string>\n\n    <string name=\"dk_kit_ui_performance\">UI層級</string>\n    <string name=\"dk_view_render_analysis\">View渲染统计</string>\n    <string name=\"dk_max_level\">最大層級: </string>\n    <string name=\"dk_view_id\">控件id: </string>\n    <string name=\"dk_total_draw_time\">總繪製耗時: </string>\n    <string name=\"dk_max_draw_time\">最大繪製耗時: </string>\n    <string name=\"dk_platform_monitor_view_stat_data\">查看統計數據</string>\n\n    <string name=\"dk_mock_search\">搜索</string>\n    <!--jsonviewer-->\n    <string name=\"dk_jsonViewer_icon_plus\">expand</string>\n    <string name=\"dk_jsonViewer_icon_minus\">collapse</string>\n    <string name=\"dk_data_mock_et_hint\">支持篩選</string>\n    <string name=\"dk_data_mock_bottom_table_mock\">Mock數據</string>\n    <string name=\"dk_data_mock_bottom_table_template\">上傳模板</string>\n    <string name=\"dk_data_mock_switch_all\">全部</string>\n    <string name=\"dk_data_mock_switch_opened\">打開</string>\n    <string name=\"dk_data_mock_switch_closed\">關閉</string>\n    <string name=\"dk_data_mock_group\">接口分組</string>\n    <string name=\"dk_data_mock_switch_status\">開關狀態</string>\n    <string name=\"dk_health_upload_successed\">上传数据成功</string>\n    <string name=\"dk_health_upload_failed\">上传数据失败,请重新上传</string>\n    <string name=\"dk_health_upload_droped\">本次测试用例已丢弃!</string>\n    <string name=\"dk_health_upload_title\">健康体检</string>\n    <string name=\"dk_health_upload_message\">是否确认开始执行健康体检?</string>\n    <string name=\"dk_health_funcation_start\">App即将重启并开始进入体检模式</string>\n    <string name=\"dk_health_funcation_running\">正在体检中...</string>\n    <string name=\"dk_health_step_index\">向上滑动查看功能使用说明</string>\n    <string name=\"dk_health_title_step1\">第一步</string>\n    <string name=\"dk_health_title_step2\">第二步</string>\n    <string name=\"dk_health_title_step3\">第三步</string>\n    <string name=\"dk_health_title_step4\">第四步</string>\n    <string name=\"dk_health_step1\">点击开始体验按钮开始本次的性能测试</string>\n    <string name=\"dk_health_step2\">在每一个页面至少停留10秒钟，如果低于10秒钟的话，我们将会丢弃该页面收集到的数据。</string>\n    <string name=\"dk_health_step3\">测试完毕之后，重新进入本页面并点击结束测试按钮，填写测试用例名称和测试人的名字，即可上传本次性能测试数据</string>\n    <string name=\"dk_health_step4\">打开www.dokit.cn平台，进入app健康体检列表，即可查看本次的性能测试报告</string>\n    <string name=\"dk_health_back_top\">回到顶部</string>\n    <string name=\"dk_log_btn_clean\">清空日志</string>\n    <string name=\"dk_log_btn_export\">导出</string>\n    <string name=\"dk_log_btn_back_top\">回到顶部</string>\n    <string name=\"dk_log_btn_to_bottom\">滚至底部</string>\n    <string name=\"dk_kit_net_monitor_white_host_tip\">白名单(默认全部拦截,如有添加白名单则只会拦截白名单中的网络请求,同时白名单规则在关闭当前页面时生效。白名单host示例:m.baidu.com)</string>\n    <string name=\"dk_kit_net_monitor_white_host_edit_tip\">请输入白名单host</string>\n    <string name=\"dk_kit_net_monitor_white_host_edit_toast\">请先输入host</string>\n    <string name=\"dk_data_mock_plugin_toast\">请先到https://www.dokit.cn申请projectId,并参考使用中心进行接入</string>\n    <string name=\"dk_header_reset\">下拉刷新</string>\n    <string name=\"dk_header_pull\">下拉刷新</string>\n    <string name=\"dk_header_pull_over\">释放立即刷新</string>\n    <string name=\"dk_header_refreshing\">正在刷新...</string>\n    <string name=\"dk_header_completed\">刷新成功</string>\n    <string name=\"dk_brvah_loading\">正在加载中...</string>\n    <string name=\"dk_brvah_load_failed\">加载失败，请点我重试</string>\n    <string name=\"dk_brvah_load_end\">没有更多数据</string>\n    <string name=\"dk_brvah_load_complete\">点击加载更多</string>\n    <string name=\"dk_data_mock_template_tip\">本地是否存在mock模板数据:%s</string>\n    <string name=\"dk_data_mock_template_upload\">上传模板</string>\n    <string name=\"dk_weaknet_type\">弱网类型:</string>\n    <string name=\"dk_weaknet_type_speed\">限速</string>\n    <string name=\"dk_weaknet_type_timeout\">超时</string>\n    <string name=\"dk_weaknet_type_off\">断网</string>\n    <string name=\"dk_weak_network_speed_unit\">K/s</string>\n    <string name=\"dk_health_dialog_title\">结束前请完善以下信息</string>\n    <string name=\"dk_health_dialog_test_name\">测试用例名称</string>\n    <string name=\"dk_health_dialog_test_name_hint\">测试用例名称</string>\n    <string name=\"dk_health_dialog_test_person\">测试人</string>\n    <string name=\"dk_health_dialog_test_person_hint\">测试人</string>\n    <string name=\"dk_kit_log_min\">最小化</string>\n    <string name=\"dk_kit_setting\">更多</string>\n    <string name=\"dk_category_exit\">退出</string>\n    <string name=\"dk_category_version\">version</string>\n    <string name=\"dk_setting\">更多</string>\n    <string name=\"dk_edit\">编辑</string>\n    <string name=\"dk_complete\">完成</string>\n    <string name=\"dk_export\">导出</string>\n    <string name=\"dk_manager_kit_title\">管理我的功能</string>\n    <string name=\"dk_kit_block_time_app_start_info\">启动详情</string>\n    <string name=\"dk_plugin_close_tip\">未引入插件或插件未开启</string>\n    <string name=\"dk_plugin_network_close_tip\">网络插件未开启</string>\n    <string name=\"dk_plugin_method_close_tip\">慢函数插件未开启</string>\n    <string name=\"dk_plugin_method_strategy_tip\">请修改慢函数的插件配置等于0</string>\n    <string name=\"dk_plugin_big_img_close_tip\">大图检测插件未开启</string>\n    <string name=\"dk_plugin_gps_close_tip\">GPS插件未开启</string>\n    <string name=\"dk_platform_tip\">需要到www.dokit.cn上注册pId才能使用该功能</string>\n    <string name=\"dk_reset\">还原</string>\n    <string name=\"dk_toolpanel_save_complete\">保存成功</string>\n    <string name=\"dk_toolpanel_reset_complete\">功能还原成功</string>\n    <string name=\"dk_toolpanel_dialog_edit_tip\">是否保存已编辑的内容</string>\n    <string name=\"dk_toolpanel_dialog_reset_tip\">是否还原到初始状态</string>\n    <string name=\"dk_setting_kit_manager\">功能管理</string>\n\n    <!--    文件同步助手-->\n    <string name=\"dk_file_manager_tip_top\">\n        <![CDATA[\n        请在www.dokit.cn平台端控制台中的<font color=\\\"blue\\\">【文件同步助手】</font>\n        中使用该功能\n        ]]>\n    </string>\n\n    <string name=\"dk_file_manager_tip_bottom\">请在web端通过当前ip:port进行连接</string>\n    <string name=\"dk_file_manager_sd_permission_tip\">你当前的targetSdkVersion>=29,请在AndroidManifest.xml的application下添加android:requestLegacyExternalStorage=\"true\" 解决sd卡没有权限的问题</string>\n    <!-- h5助手-->\n    <string name=\"dk_kit_h5_help\">H5助手</string>\n    <string name=\"dk_kit_dokit_for_web\">DoKit Web</string>\n    <string name=\"dk_kit_dokit_studio\">DoKit Studio</string>\n</resources>\n"
  },
  {
    "path": "Android/dokit/src/main/res/xml/dokit_debug_provider_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n    <root-path name=\"name\" path=\"\" />\n</paths>\n"
  },
  {
    "path": "Android/dokit/src/main/res/xml/dokit_network_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config>\n    <base-config cleartextTrafficPermitted=\"true\" />\n</network-security-config>"
  },
  {
    "path": "Android/dokit/src/test/java/com/didichuxing/doraemonkit/DokitTest.java",
    "content": "package com.didichuxing.doraemonkit;\n\n/**\n * didi Create on 2022/4/12 .\n * <p>\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 11:57 上午\n * @Description 用一句话说明文件功能\n */\n\npublic class DokitTest {\n}\n"
  },
  {
    "path": "Android/dokit/src/test/java/com/didichuxing/doraemonkit/kit/connect/ByteParserTest.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect\n\nimport android.graphics.Bitmap\nimport com.didichuxing.doraemonkit.kit.connect.data.PackageType\nimport com.didichuxing.doraemonkit.kit.connect.parser.ByteParser\nimport com.didichuxing.doraemonkit.kit.connect.parser.JsonParser\nimport com.didichuxing.doraemonkit.util.RandomUtils\nimport okio.ByteString\nimport org.junit.Test\nimport java.io.ByteArrayOutputStream\n\n\n/**\n * didi Create on 2022/4/21 .\n *\n * Copyright (c) 2022/4/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/21 7:08 下午\n * @Description 用一句话说明文件功能\n */\n\nclass ByteParserTest {\n\n\n    @Test\n    fun test() {\n\n        val bytes = \"byteArray\".toByteArray()\n\n        val dataMap = mutableMapOf<String, String>()\n        dataMap[\"caseId\"] = RandomUtils.random32HexString()\n        dataMap[\"image\"] = \"fileName\"\n        dataMap[\"type\"] = \"jpeg\"\n\n        val data = JsonParser.toJson(dataMap)\n        val textPackage = JsonParser.toTextPackage(PackageType.AUTOTEST, data, \"image\")\n        val byteString = ByteParser.toByteString(textPackage, bytes)\n\n        ByteParser.parse(byteString)\n\n    }\n\n    fun getByteString(): ByteString {\n        val bytes = \"byteArray\".toByteArray()\n\n        val dataMap = mutableMapOf<String, String>()\n        dataMap[\"caseId\"] = RandomUtils.random32HexString()\n        dataMap[\"image\"] = \"fileName\"\n        dataMap[\"type\"] = \"jpeg\"\n\n        val data = JsonParser.toJson(dataMap)\n        val textPackage = JsonParser.toTextPackage(PackageType.AUTOTEST, data, \"image\")\n        val byteString = ByteParser.toByteString(textPackage, bytes)\n        return byteString\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/test/java/com/didichuxing/doraemonkit/kit/connect/WebSocketClientTest.kt",
    "content": "package com.didichuxing.doraemonkit.kit.connect\n\nimport com.didichuxing.doraemonkit.kit.connect.data.PackageType\nimport com.didichuxing.doraemonkit.kit.connect.data.TextPackage\nimport com.didichuxing.doraemonkit.kit.connect.parser.ByteParser\nimport com.didichuxing.doraemonkit.kit.connect.parser.JsonParser\nimport com.didichuxing.doraemonkit.kit.connect.ws.OkHttpWebSocketSession\nimport com.didichuxing.doraemonkit.kit.connect.ws.OnWebSocketTextPackageListener\nimport com.didichuxing.doraemonkit.kit.connect.ws.WebSocketClient\nimport com.didichuxing.doraemonkit.util.RandomUtils\nimport org.junit.Test\n\n\n/**\n * didi Create on 2022/4/13 .\n *\n * Copyright (c) 2022/4/13 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/13 10:51 上午\n * @Description 用一句话说明文件功能\n */\n\nclass WebSocketClientTest {\n\n    private val webSocketClient: WebSocketClient = WebSocketClient()\n\n\n    @Test\n    fun test() {\n        webSocketClient.connect(\"ws://172.23.141.219:8000/proxy/userInterfaceAutomation/XLQBYYHP\")\n        webSocketClient.addOnWebSocketTextPackageListener(object : OnWebSocketTextPackageListener {\n            override fun onReceiveTextPackage(webSocket: OkHttpWebSocketSession, textPackage: TextPackage) {\n\n            }\n        })\n\n        Thread.currentThread().join()\n    }\n}\n"
  },
  {
    "path": "Android/dokit/src/test/java/com/didichuxing/doraemonkit/kit/connect/WebSocketSessionTest.java",
    "content": "package com.didichuxing.doraemonkit.kit.connect;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.kit.connect.data.LoginData;\nimport com.didichuxing.doraemonkit.kit.connect.data.PackageType;\nimport com.didichuxing.doraemonkit.kit.connect.data.TextPackage;\nimport com.didichuxing.doraemonkit.kit.connect.ws.OkHttpWebSocketSession;\nimport com.didichuxing.doraemonkit.kit.connect.ws.OnWebSocketMessageListener;\nimport com.didichuxing.doraemonkit.kit.connect.ws.OnWebSocketStatusChangeListener;\nimport com.didichuxing.doraemonkit.util.GsonUtils;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.util.concurrent.TimeUnit;\n\nimport okhttp3.OkHttpClient;\n\n/**\n * didi Create on 2022/4/12 .\n * <p>\n * Copyright (c) 2022/4/12 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/12 11:56 上午\n * @Description 用一句话说明文件功能\n */\n\npublic class WebSocketSessionTest {\n\n    private OkHttpClient client;\n\n    @Before\n    public void init() {\n        client = new OkHttpClient.Builder()\n            .readTimeout(3, TimeUnit.SECONDS)//设置读取超时时间\n            .writeTimeout(3, TimeUnit.SECONDS)//设置写的超时时间\n            .connectTimeout(3, TimeUnit.SECONDS)//设置连\n            .build();\n    }\n\n\n    @Test\n    public void testConnect() throws Exception {\n        OkHttpWebSocketSession session = new OkHttpWebSocketSession(client);\n\n        session.setOnWebSocketMessageListener(new OnWebSocketMessageListener() {\n            @Override\n            public void onMessage(@NonNull OkHttpWebSocketSession webSocket, @NonNull String text) {\n                log(\"onMessage() text=\" + text);\n            }\n        });\n        session.setOnWebSocketStatusChangeListener(new OnWebSocketStatusChangeListener() {\n            @Override\n            public void onClosed(@NonNull OkHttpWebSocketSession webSocket, int code, @NonNull String reason) {\n                log(\"onClosed() code=\" + code + \",reason=\" + reason);\n            }\n\n            @Override\n            public void onOpen(@NonNull OkHttpWebSocketSession webSocket, @NonNull String response) {\n                log(\"onOpen() response=\" + response);\n\n                LoginData loginData = new LoginData(\"dn\", \"android\", \"9.0\", \"300*400\",\n                    \"MAC\", \"0.0.0.0\", \"\", \"test\", \"1.0\");\n                String data = GsonUtils.toJson(loginData);\n                TextPackage textPackage = new TextPackage(\"pid0\", PackageType.LOGIN, data,\n                    \"android\", \"cos\", \"text\", \"\", 0);\n                String text = GsonUtils.toJson(textPackage);\n                webSocket.send(text);\n\n                ByteParserTest byteParserTest = new ByteParserTest();\n\n                session.send(byteParserTest.getByteString());\n            }\n\n            @Override\n            public void onFailure(@NonNull OkHttpWebSocketSession webSocket, @NonNull Throwable t, @Nullable String response) {\n                log(\"onFailure() response=\" + response);\n            }\n\n            @Override\n            public void onClosing(@NonNull OkHttpWebSocketSession webSocket, int code, @NonNull String reason) {\n                log(\"onClosing() code=\" + code + \",reason=\" + reason);\n            }\n        });\n        session.connect(\"ws://172.23.141.219:8000/proxy/userInterfaceAutomation/XLQBYYHP\");\n\n\n\n        Thread.currentThread().join();\n    }\n\n    public void log(String msg) {\n        String tn = Thread.currentThread().getName();\n        System.out.println(\"WS[\" + tn + \"]::\" + msg);\n    }\n}\n"
  },
  {
    "path": "Android/dokit/upload.sh",
    "content": "#!/usr/bin/env bash\necho -n  \"please enter bintray userid ->\"\nread  userid_bintray\necho -n  \"please enter bintray apikey ->\"\nread  apikey_bintray\n../gradlew wrapper  clean build --stacktrace  --info  bintrayUpload -PbintrayUser=${userid_bintray} -PbintrayKey=${apikey_bintray} -PdryRun=false"
  },
  {
    "path": "Android/dokit-autotest/.gitignore",
    "content": "/build"
  },
  {
    "path": "Android/dokit-autotest/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-kapt'\napply from: '../upload.gradle'\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_21\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n\n        lintOptions {\n            abortOnError false\n        }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n\n    /**\n     * 支持ViewBinding\n     */\n    buildFeatures {\n        //viewBinding = true\n    }\n\n}\n\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    testImplementation 'junit:junit:4.12'\n    androidTestImplementation 'junit:junit:4.12'\n    if (needKotlinV14()) {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v14\"]\n    } else {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v13\"]\n    }\n    implementation rootProject.ext.dependencies[\"core-ktx\"]\n    implementation rootProject.ext.dependencies[\"fragment-ktx\"]\n    implementation rootProject.ext.dependencies[\"appcompat\"]\n    implementation rootProject.ext.dependencies[\"cardview\"]\n    implementation rootProject.ext.dependencies[\"recyclerview\"]\n    implementation rootProject.ext.dependencies[\"fragment\"]\n    implementation rootProject.ext.dependencies[\"zxing\"]\n\n    implementation rootProject.ext.dependencies[\"volley\"]\n    implementation rootProject.ext.dependencies[\"ktor_server_websockets\"]\n    implementation rootProject.ext.dependencies[\"ktor_client_websockets\"]\n    implementation rootProject.ext.dependencies[\"ktor_server_cio\"]\n    //默认客户端websocket引擎\n    implementation rootProject.ext.dependencies[\"ktor_client_cio\"]\n    //备用客户端websocket引擎\n    compileOnly rootProject.ext.dependencies[\"ktor_client_okhttp\"]\n    implementation project(':dokit-util')\n    implementation project(':dokit-okhttp-api')\n    //此处需要使用api的形式 向上暴露内部api\n    implementation project(':dokit')\n//    implementation project(':dokit-mc')\n    implementation project(':dokit-test')\n\n    implementation rootProject.ext.dependencies[\"auto_service\"]\n    kapt rootProject.ext.dependencies[\"auto_service\"]\n}\nrepositories {\n    mavenCentral()\n}\n"
  },
  {
    "path": "Android/dokit-autotest/gradle.properties",
    "content": "ARTIFACT_ID=dokitx-autotest\n"
  },
  {
    "path": "Android/dokit-autotest/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "Android/dokit-autotest/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.didichuxing.doraemonkit.autotest\">\n\n    <application>\n        <activity\n            android:name=\"com.didichuxing.doraemonkit.kit.autotest.ui.DoKitAutotestActivity\"\n            android:theme=\"@style/Theme.AppCompat.Light.NoActionBar\"\n            android:windowSoftInputMode=\"adjustPan|stateHidden|stateUnchanged\" />\n    </application>\n\n\n</manifest>\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/java/com/didichuxing/doraemonkit/kit/autotest/AutoTestControlKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.autotest\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content.Intent\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.autotest.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.autotest.ui.DoKitAutotestActivity\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.google.auto.service.AutoService\n\n\n/**\n * didi Create on 2022/4/6 .\n *\n * Copyright (c) 2022/4/6 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/6 5:14 下午\n * @Description 用一句话说明文件功能\n */\n\n@AutoService(AbstractKit::class)\nclass AutoTestControlKit : AbstractKit() {\n\n    override val name: Int\n        get() = R.string.dk_kit_autotest\n    override val icon: Int\n        get() = R.drawable.dk_icon_autotest\n\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip))\n            return false\n        }\n        val intent = Intent(activity, DoKitAutotestActivity::class.java)\n        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK\n        activity.startActivity(intent)\n\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n\n    }\n\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_platform_ck_autotest\"\n    }\n}\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/java/com/didichuxing/doraemonkit/kit/autotest/AutoTestManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.autotest\n\nimport android.app.Activity\nimport android.graphics.Bitmap\nimport android.view.View\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.autotest.ui.RecordingCaseDoKitView\nimport com.didichuxing.doraemonkit.kit.connect.ConnectAddress\nimport com.didichuxing.doraemonkit.kit.connect.data.PackageType\nimport com.didichuxing.doraemonkit.kit.connect.data.TextPackage\nimport com.didichuxing.doraemonkit.kit.connect.parser.ByteParser\nimport com.didichuxing.doraemonkit.kit.connect.parser.JsonParser\nimport com.didichuxing.doraemonkit.kit.connect.ws.*\nimport com.didichuxing.doraemonkit.kit.core.DoKitFrameLayout\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.kit.test.event.*\nimport com.didichuxing.doraemonkit.kit.test.mock.MockManager\nimport com.didichuxing.doraemonkit.kit.test.mock.ProxyMockCallback\nimport com.didichuxing.doraemonkit.kit.test.report.ScreenShotManager\nimport com.didichuxing.doraemonkit.util.*\nimport kotlinx.coroutines.*\nimport okio.ByteString\nimport java.io.ByteArrayOutputStream\nimport java.lang.Runnable\n\n/**\n * didi Create on 2022/4/6 .\n *\n * Copyright (c) 2022/4/6 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/6 5:14 下午\n * @Description 自动化测试管理\n */\n\nobject AutoTestManager {\n\n    private val mainScope = MainScope() + CoroutineName(this.toString())\n\n    private val uploadScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + CoroutineName(\"upload\")\n\n    private val delayHandler: DelayHandler = DelayHandler()\n\n    private val autoTestStateSet: MutableMap<String, AutoTestState> = mutableMapOf()\n\n    private var connectAddress: ConnectAddress? = null\n\n    private var webSocketClient: WebSocketClient = WebSocketClient()\n\n    private var mode: TestMode = TestMode.UNKNOWN\n\n    private var screenShotManager: ScreenShotManager = ScreenShotManager(\"doKit/autotest/screen\")\n\n    private var diffEventTask: EventScreenShotTask? = null\n\n    private var webSocketClientCreate = false\n\n\n    class EventScreenShotTask(private val event: ControlEvent) : Runnable {\n        override fun run() {\n            val state = autoTestStateSet.remove(event.eventId)\n            state?.let {\n                val message = state.message\n                val bitmap = screenShotManager.screenshotBitmap()\n                if (bitmap != null) {\n                    val name = screenShotManager.createNextFileName()\n                    message.params[\"imageName\"] = name\n                    message.params[\"type\"] = \"webp\"\n                } else {\n                    message.params[\"imageName\"] = \"\"\n                    message.params[\"type\"] = \"\"\n                }\n                if (event.eventType == EventType.WSE_TCP_EVENT && event.diffTime < 1000) {\n                    onResponseAutoTestAction(message, bitmap)\n                } else {\n                    onResponseAutoTestAction(message, bitmap)\n                }\n            } ?: run {\n                val message = AutoTestMessage(command = \"action_response\", message = \"failed\")\n                message.params[\"eventId\"] = event.eventId\n                message.params[\"imageName\"] = \"\"\n                message.params[\"type\"] = \"\"\n                onResponseAutoTestAction(message)\n            }\n        }\n    }\n\n    fun getMode(): TestMode {\n        return mode\n    }\n\n    fun startRecord() {\n        ControlEventManager.resetLastEventDateTime()\n        ControlEventManager.addOnControlEventInterceptor(eventActionInterceptor)\n        ControlEventManager.addOnControlEventActionListener(eventActionListener)\n        MockManager.proxyMockCallback = proxyMockCallback\n        MockManager.startTest(TestMode.HOST)\n        DoKitTestManager.startTest(TestMode.HOST)\n        changeToRecordView()\n\n        ToastUtils.showShort(\"已开始录制\")\n    }\n\n\n    fun stopRecord() {\n        MockManager.closeTest()\n        DoKitTestManager.closeTest()\n        MockManager.proxyMockCallback = null\n        ControlEventManager.removeOnControlEventActionListener(eventActionListener)\n        ControlEventManager.removeOnControlEventInterceptor(eventActionInterceptor)\n\n        changeToConnectView()\n        ToastUtils.showShort(\"已停止录制\")\n    }\n\n    fun startAutoTest() {\n\n        MockManager.proxyMockCallback = proxyMockCallback\n        ControlEventManager.addOnControlEventActionProcessListener(actionProcessListener)\n        MockManager.startTest(TestMode.CLIENT)\n        DoKitTestManager.startTest(TestMode.CLIENT)\n        changeToTestView()\n        ToastUtils.showShort(\"已开始测试\")\n    }\n\n    fun stopAutoTest() {\n        MockManager.closeTest()\n        DoKitTestManager.closeTest()\n        MockManager.proxyMockCallback = null\n        ControlEventManager.removeOnControlEventActionProcessListener(actionProcessListener)\n        changeToConnectView()\n        ToastUtils.showShort(\"已停止测试\")\n    }\n\n    fun startConnect(address: ConnectAddress) {\n        connectAddress = address\n        connect()\n    }\n\n    fun stopConnect() {\n        webSocketClient?.close()\n    }\n\n    fun send(bytes: ByteString): Boolean {\n        webSocketClient?.let {\n            it.send(bytes)\n            return true\n        }\n        return false\n    }\n\n    private fun changeToRecordView() {\n        mode = TestMode.HOST\n        mainScope.launch {\n            RecordingCaseDoKitView.changeMode(mode)\n        }\n    }\n\n    private fun changeToConnectView() {\n        mode = TestMode.UNKNOWN\n        mainScope.launch {\n            RecordingCaseDoKitView.changeMode(mode)\n        }\n    }\n\n    private fun changeToTestView() {\n        mode = TestMode.CLIENT\n        mainScope.launch {\n            RecordingCaseDoKitView.changeMode(mode)\n        }\n    }\n\n    private fun onReceiveControl(textPackage: TextPackage) {\n        val text = textPackage.data\n        val autoTestMessage = GsonUtils.fromJson<AutoTestMessage>(text, AutoTestMessage::class.java)\n\n        when (autoTestMessage.command) {\n            \"startRecord\" -> {\n                startRecord()\n                val msg = AutoTestMessage(command = \"control_response\", message = \"success\")\n                msg.params[\"command\"] = autoTestMessage.command\n                onResponseAutoTestMessage(msg)\n            }\n            \"stopRecord\" -> {\n                stopRecord()\n                val msg = AutoTestMessage(command = \"control_response\", message = \"success\")\n                msg.params[\"command\"] = autoTestMessage.command\n                onResponseAutoTestMessage(msg)\n            }\n            \"startAutoTest\" -> {\n                startAutoTest()\n                val msg = AutoTestMessage(command = \"control_response\", message = \"success\")\n                msg.params[\"command\"] = autoTestMessage.command\n                onResponseAutoTestMessage(msg)\n            }\n            \"stopAutoTest\" -> {\n                onStopAutoTest(autoTestMessage)\n            }\n        }\n    }\n\n    private fun onStopAutoTest(autoTestMessage: AutoTestMessage) {\n        delayHandler.postDelayed(object : Runnable {\n            override fun run() {\n                stopAutoTest()\n                val msg = AutoTestMessage(command = \"control_response\", message = \"success\")\n                msg.params[\"command\"] = autoTestMessage.command\n                onResponseAutoTestMessage(msg)\n            }\n        }, 3000)\n\n    }\n\n    /**\n     * 控制消息响应\n     */\n    private fun onResponseAutoTestMessage(autoTestMessage: AutoTestMessage) {\n        webSocketClient?.let {\n            it.send(JsonParser.toJson(PackageType.AUTOTEST, autoTestMessage, \"auto_test_control\"))\n        }\n    }\n\n    /**\n     * 自动化测试行为事件响应\n     */\n    private fun onResponseAutoTestAction(autoTestMessage: AutoTestMessage) {\n        webSocketClient?.let {\n            it.send(JsonParser.toJson(PackageType.AUTOTEST, autoTestMessage, \"action\"))\n        }\n    }\n\n    /**\n     * 自动化测试行为事件响应\n     */\n    private fun onResponseAutoTestAction(autoTestMessage: AutoTestMessage, bitmap: Bitmap) {\n        uploadScope.launch {\n            val stream = ByteArrayOutputStream(2048)\n            val ok = bitmap.compress(Bitmap.CompressFormat.WEBP, 10, stream)\n            val bytes = stream.toByteArray()\n\n            val textPackage = JsonParser.toTextPackage(PackageType.AUTOTEST, autoTestMessage, \"action\")\n            val byteString = ByteParser.toByteString(textPackage, bytes)\n            webSocketClient.send(byteString)\n            stream.close()\n        }\n    }\n\n    private fun isDiffTimeEvent(event: ControlEvent): Boolean {\n        when (event.eventType) {\n            EventType.WSE_CUSTOM_EVENT -> {\n                event.params?.let {\n                    var testRecording: String? = it[\"testRecording\"]\n                    if (testRecording == \"false\") {\n                        return false\n                    }\n                }\n                return true\n            }\n            EventType.WSE_TCP_EVENT,\n            EventType.APP_ON_FOREGROUND,\n            EventType.APP_ON_BACKGROUND,\n            EventType.ACTIVITY_BACK_PRESSED -> {\n                return true\n            }\n            EventType.WSE_COMMON_EVENT -> {\n                event.viewC12c?.let {\n                    when (it.actionType) {\n                        ActionType.ON_LONG_CLICK,\n                        ActionType.ON_SCROLL,\n                        ActionType.ON_INPUT_CHANGE,\n                        ActionType.ON_CLICK -> {\n                            return true\n                        }\n                        else -> {\n                        }\n                    }\n                }\n            }\n\n        }\n        return false\n    }\n\n\n    private fun getDiffTimeByEvent(event: ControlEvent, diffTime: Long): Long {\n        when (event.eventType) {\n            EventType.WSE_TCP_EVENT -> {\n                return diffTime\n            }\n            EventType.WSE_COMMON_EVENT -> {\n                event.viewC12c?.let {\n                    when (it.actionType) {\n                        ActionType.ON_SCROLL,\n                        ActionType.ON_INPUT_CHANGE -> {\n                            return if (diffTime > 100) {\n                                diffTime\n                            } else {\n                                100\n                            }\n                        }\n                        else -> {\n                        }\n                    }\n                }\n            }\n        }\n        return 1000\n    }\n\n    /**\n     * 接收到自动化测试事件\n     */\n    private fun onReceiveAction(textPackage: TextPackage) {\n        val text = textPackage.data\n        val event = GsonUtils.fromJson<ControlEvent>(text, ControlEvent::class.java)\n\n        if (isDiffTimeEvent(event)) {\n            val diff: Long = if (event.diffTime < 1000) {\n                getDiffTimeByEvent(event, event.diffTime)\n            } else {\n                event.diffTime\n            }\n            val eventTask = EventScreenShotTask(event)\n            diffEventTask = eventTask\n            delayHandler.postDelayed(eventTask, diff)\n        }\n\n        mainScope.launch {\n            ControlEventManager.onReceiveControlEventAction(event)\n        }\n    }\n\n\n    private fun connect() {\n        if (connectAddress == null) {\n            return\n        }\n        if (!webSocketClientCreate) {\n            webSocketClientCreate = true\n\n            webSocketClient?.let {\n                it.addOnWebSocketLoginSuccessListener(object : OnWebSocketLoginSuccessListener {\n                    override fun onWebSocketLoginSuccess() {\n                        mainScope.launch {\n                            DoKit.launchFloating(RecordingCaseDoKitView::class.java)\n                        }\n                    }\n                })\n                it.addOnWebSocketTextPackageListener(object : OnWebSocketTextPackageListener {\n                    override fun onReceiveTextPackage(webSocket: OkHttpWebSocketSession, textPackage: TextPackage) {\n                        if (textPackage.type == PackageType.AUTOTEST || textPackage.type == PackageType.BROADCAST) {\n                            when (textPackage.contentType) {\n                                \"auto_test_control\" -> {\n                                    onReceiveControl(textPackage)\n                                }\n                                \"action\" -> {\n                                    onReceiveAction(textPackage)\n                                }\n                                else -> {\n\n                                }\n                            }\n                        } else if (textPackage.type == PackageType.DATA) {\n                            MockManager.receiveQueryResponse(textPackage)\n                        }\n\n\n                    }\n                })\n                it.addOnWebSocketCloseListener(object : OnWebSocketCloseListener {\n                    override fun onWebSocketClose() {\n                        mainScope.launch {\n                            DoKit.removeFloating(RecordingCaseDoKitView::class.java)\n                        }\n                    }\n                })\n\n                it.startAutoConnect()\n                it.connect(connectAddress!!.url)\n            }\n        } else {\n            webSocketClient?.let {\n                it.startAutoConnect()\n                it.reConnect(connectAddress!!.url)\n            }\n        }\n    }\n\n    private val eventActionInterceptor = object : OnControlEventInterceptor {\n        override fun onControlEventAction(activity: Activity?, view: View?, controlEvent: ControlEvent): Boolean {\n            if (view is DoKitFrameLayout) {\n                return true\n            }\n            return false\n        }\n    }\n\n    private val eventActionListener = object : OnControlEventActionListener {\n        override fun onControlEventAction(activity: Activity?, view: View?, event: ControlEvent) {\n            webSocketClient.send(JsonParser.toJson(PackageType.BROADCAST, event, \"action\"))\n        }\n    }\n\n    private val proxyMockCallback = object : ProxyMockCallback {\n        override fun send(data: String) {\n            webSocketClient.send(data)\n        }\n    }\n\n\n    private val actionProcessListener = object : OnControlEventActionProcessListener {\n        override fun onControlEventProcessSuccess(activity: Activity?, view: View?, controlEvent: ControlEvent) {\n            val msg = AutoTestMessage(command = \"action_response\", message = \"success\")\n            msg.params[\"eventId\"] = controlEvent.eventId\n\n            if (isDiffTimeEvent(controlEvent)) {\n                val state = AutoTestState(activity, view, controlEvent, msg)\n                autoTestStateSet[controlEvent.eventId] = state\n\n            } else {\n                onResponseAutoTestAction(msg)\n            }\n        }\n\n        override fun onControlEventProcessFailed(activity: Activity?, view: View?, controlEvent: ControlEvent, code: Int, message: String) {\n            // 解决轻微滑动导致的回放滑动异常问题，当作正常响应继续流程\n            if (controlEvent.eventType == EventType.WSE_COMMON_EVENT) {\n                controlEvent.viewC12c?.let {\n                    if (it.actionType == ActionType.ON_SCROLL) {\n                        LogHelper.e(\"AutoTestManager\",\"ON_SCROLL ERROR!\")\n                        onControlEventProcessSuccess(activity, view, controlEvent)\n                        return\n                    }\n                }\n            }\n\n            val msg = AutoTestMessage(command = \"action_response\", message = \"failed\")\n            msg.params[\"eventId\"] = controlEvent.eventId\n            msg.params[\"message\"] = message\n            msg.params[\"code\"] = \"\" + code\n            if (isDiffTimeEvent(controlEvent)) {\n                diffEventTask?.let {\n                    delayHandler.removeCallbacks(it)\n                }\n                onResponseAutoTestAction(msg)\n            } else {\n                onResponseAutoTestAction(msg)\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/java/com/didichuxing/doraemonkit/kit/autotest/AutoTestMessage.kt",
    "content": "package com.didichuxing.doraemonkit.kit.autotest\n\n\n/**\n * didi Create on 2022/4/6 .\n *\n * Copyright (c) 2022/4/6 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/6 5:14 下午\n * @Description 自动化测试通信消息\n */\n\ndata class AutoTestMessage(\n    val message: String = \"\",\n    val command: String = \"\",\n    var params: MutableMap<String, String> = mutableMapOf()\n)\n\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/java/com/didichuxing/doraemonkit/kit/autotest/AutoTestState.kt",
    "content": "package com.didichuxing.doraemonkit.kit.autotest\n\nimport android.app.Activity\nimport android.view.View\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\n\ndata class AutoTestState(\n    val activity: Activity?,\n    val view: View?,\n    val controlEvent: ControlEvent,\n    val message: AutoTestMessage,\n    val success: Boolean = true\n)\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/java/com/didichuxing/doraemonkit/kit/autotest/DelayHandler.kt",
    "content": "package com.didichuxing.doraemonkit.kit.autotest\n\nimport android.os.Handler\nimport android.os.Looper\n\n\n/**\n * didi Create on 2022/4/18 .\n *\n * Copyright (c) 2022/4/18 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/18 3:11 下午\n * @Description 用一句话说明文件功能\n */\n\nclass DelayHandler {\n    private val mainHandler: Handler = Handler(Looper.getMainLooper())\n\n\n    fun postDelayed(runnable: Runnable, delay: Long) {\n        mainHandler.postDelayed(runnable, delay)\n    }\n\n    fun removeCallbacks(runnable: Runnable) {\n        mainHandler.removeCallbacks(runnable)\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/java/com/didichuxing/doraemonkit/kit/autotest/ui/AutotestPage.java",
    "content": "package com.didichuxing.doraemonkit.kit.autotest.ui;\n\n/**\n * didi Create on 2022/4/6 .\n * <p>\n * Copyright (c) 2022/4/6 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/6 5:40 下午\n * @Description 用一句话说明文件功能\n */\n\npublic enum AutotestPage {\n\n    CONNECT,\n    HOME,\n    RECORD,\n    CASE_LIST\n}\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/java/com/didichuxing/doraemonkit/kit/autotest/ui/DoKitAutotestActivity.java",
    "content": "package com.didichuxing.doraemonkit.kit.autotest.ui;\n\nimport android.os.Bundle;\nimport android.text.TextUtils;\n\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.autotest.R;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.kit.core.NewBaseActivity;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\n\n/**\n * didi Create on 2022/4/6 .\n * <p>\n * Copyright (c) 2022/4/6 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/6 5:24 下午\n * @Description 用一句话说明文件功能\n */\n\npublic class DoKitAutotestActivity extends NewBaseActivity {\n\n    private HomeTitleBar homeTitleBar;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.dk_activity_autotest);\n        homeTitleBar = findViewById(R.id.title_bar);\n        homeTitleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                onBackPressed();\n            }\n        });\n\n        String pageName = getIntent().getStringExtra(\"PAGE_NAME\");\n        if (TextUtils.isEmpty(pageName)) {\n            newHomeFragment();\n        } else {\n            AutotestPage page = AutotestPage.valueOf(pageName);\n            changeFragment(page);\n        }\n    }\n\n\n    public void newHomeFragment() {\n        changeFragment(AutotestPage.HOME);\n    }\n\n    public void changeFragment(AutotestPage page) {\n        changeFragment(page, false);\n    }\n\n    public void pushFragment(AutotestPage page) {\n        changeFragment(page, true);\n    }\n\n    public void changeFragment(AutotestPage page, boolean push) {\n        BaseFragment fragment;\n        switch (page) {\n            case CONNECT:\n                fragment = new DoKitAutotestConnectFragment();\n                break;\n            case RECORD:\n            case CASE_LIST:\n\n            case HOME:\n            default:\n                fragment = new DoKitAutotestFragment();\n        }\n\n        if (push) {\n            showContent(R.id.fragment_container_view, fragment);\n        } else {\n            replaceContent(R.id.fragment_container_view, fragment);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/java/com/didichuxing/doraemonkit/kit/autotest/ui/DoKitAutotestConnectFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.autotest.ui\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.autotest.R\nimport com.didichuxing.doraemonkit.kit.autotest.AutoTestManager\nimport com.didichuxing.doraemonkit.kit.connect.ConnectAddress\nimport com.didichuxing.doraemonkit.kit.connect.ConnectAddressStore\nimport com.didichuxing.doraemonkit.kit.connect.DoKitConnectFragment\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.util.ToastUtils\n\n\n/**\n * didi Create on 2022/4/14 .\n *\n * Copyright (c) 2022/4/14 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/14 4:40 下午\n * @Description 用一句话说明文件功能\n */\n\nclass DoKitAutotestConnectFragment : BaseFragment() {\n\n\n    private var urlTextView: TextView? = null\n    private var address: ConnectAddress? = null\n\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_autotest_connect\n    }\n\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        urlTextView = view.findViewById<TextView>(R.id.url)\n\n        view.findViewById<View>(R.id.connect).setOnClickListener {\n            starConnect()\n        }\n        view.findViewById<View>(R.id.change).setOnClickListener {\n            startChange()\n        }\n\n    }\n\n    override fun onResume() {\n        super.onResume()\n        updateUrl()\n    }\n\n    private fun updateUrl() {\n        val list = ConnectAddressStore.loadAddress()\n        if (list.size > 0) {\n            address = list[list.size - 1]\n        }\n        address?.let {\n            urlTextView?.text = \"可使用地址:${it.url}\"\n        } ?: run {\n            urlTextView?.text = \"可使用地址:--}\"\n        }\n    }\n\n    private fun starConnect() {\n        if (address == null) {\n            ToastUtils.showShort(\"无可用地址，请添加\")\n        } else {\n            address?.let {\n                AutoTestManager.startConnect(it)\n                finish()\n            }\n        }\n    }\n\n    private fun startChange() {\n        //使用统一的链接管理\n        DoKit.launchFullScreen(DoKitConnectFragment::class.java, context, null, false)\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/java/com/didichuxing/doraemonkit/kit/autotest/ui/DoKitAutotestFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.autotest.ui\n\nimport android.os.Bundle\nimport android.view.View\nimport com.didichuxing.doraemonkit.autotest.R\nimport com.didichuxing.doraemonkit.kit.autotest.AutoTestManager\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.kit.test.report.ScreenShotManager\nimport com.didichuxing.doraemonkit.util.ToastUtils\n\n\n/**\n * didi Create on 2022/4/14 .\n *\n * Copyright (c) 2022/4/14 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/14 4:40 下午\n * @Description 用一句话说明文件功能\n */\n\nclass DoKitAutotestFragment : BaseFragment() {\n\n\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_autotest_main\n    }\n\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        view.findViewById<View>(R.id.connect).setOnClickListener {\n            starConnect()\n        }\n        view.findViewById<View>(R.id.record).setOnClickListener {\n            startRecording()\n        }\n        view.findViewById<View>(R.id.record_stop).setOnClickListener {\n            stopRecording()\n        }\n        view.findViewById<View>(R.id.test).setOnClickListener {\n            startTest()\n        }\n        view.findViewById<View>(R.id.test_stop).setOnClickListener {\n            stopTest()\n        }\n        view.findViewById<View>(R.id.caseList).setOnClickListener {\n            ToastUtils.showShort(\"不支持\")\n        }\n    }\n\n    private var screenShotManager: ScreenShotManager = ScreenShotManager(\"doKit/autotest/screen2\")\n\n    private fun test() {\n        val bitmap = screenShotManager.screenshotBitmap(activity)\n    }\n\n    private fun starConnect() {\n        //使用统一的链接管理\n        if (activity is DoKitAutotestActivity) {\n            (activity as DoKitAutotestActivity).pushFragment(AutotestPage.CONNECT)\n        }\n    }\n\n    private fun startRecording() {\n        when (AutoTestManager.getMode()) {\n            TestMode.UNKNOWN -> {\n                AutoTestManager.startRecord()\n            }\n            TestMode.HOST -> {\n                ToastUtils.showShort(\"已经在录制中\")\n            }\n            TestMode.CLIENT -> {\n                ToastUtils.showShort(\"在测试中，请先关闭\")\n            }\n        }\n\n    }\n\n    private fun stopRecording() {\n        when (AutoTestManager.getMode()) {\n            TestMode.HOST -> {\n                AutoTestManager.stopRecord()\n            }\n            else -> {\n                ToastUtils.showShort(\"不在录制中\")\n            }\n        }\n    }\n\n    private fun startTest() {\n        when (AutoTestManager.getMode()) {\n            TestMode.UNKNOWN -> {\n                AutoTestManager.startAutoTest()\n            }\n            TestMode.HOST -> {\n                ToastUtils.showShort(\"在录制中，请先关闭\")\n            }\n            TestMode.CLIENT -> {\n                ToastUtils.showShort(\"已经在测试中\")\n            }\n        }\n    }\n\n    private fun stopTest() {\n        when (AutoTestManager.getMode()) {\n            TestMode.CLIENT -> {\n                AutoTestManager.stopAutoTest()\n            }\n            else -> {\n                ToastUtils.showShort(\"不在测试中\")\n            }\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/java/com/didichuxing/doraemonkit/kit/autotest/ui/RecordingCaseDoKitView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.autotest.ui\n\nimport android.content.Context\nimport android.content.Intent\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport android.widget.ImageView\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.autotest.R\nimport com.didichuxing.doraemonkit.kit.autotest.AutoTestManager\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.kit.test.widget.FlashImageView\nimport com.didichuxing.doraemonkit.kit.test.widget.FlashTextView\nimport com.didichuxing.doraemonkit.util.ActivityUtils\nimport com.didichuxing.doraemonkit.util.ConvertUtils\nimport com.didichuxing.doraemonkit.util.ToastUtils\n\n\n/**\n * didi Create on 2022/4/14 .\n *\n * Copyright (c) 2022/4/14 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/14 4:40 下午\n * @Description 用一句话说明文件功能\n */\n\nclass RecordingCaseDoKitView : AbsDoKitView() {\n\n    companion object {\n        val doKitViews: MutableSet<RecordingCaseDoKitView> = mutableSetOf()\n\n        fun changeText(text: String) {\n            doKitViews.forEach {\n                it.changeText(text)\n            }\n        }\n\n        fun changeDotColor(id: Int) {\n            doKitViews.forEach {\n                it.changeDotColor(id)\n            }\n        }\n\n        fun changeMode(mode: TestMode) {\n            doKitViews.forEach {\n                it.changeMode(mode)\n            }\n        }\n    }\n\n    private var mRedDot: FlashImageView? = null\n    private var mExtend: FlashTextView? = null\n    private var mText: TextView? = null\n\n\n    override fun onCreate(context: Context?) {\n\n    }\n\n    override fun onCreateView(context: Context?, rootView: FrameLayout?): View {\n        return LayoutInflater.from(context).inflate(R.layout.dk_autotest_view_recording_case, rootView, false)\n    }\n\n    override fun onViewCreated(rootView: FrameLayout?) {\n\n        mRedDot = findViewById(R.id.dot)\n        mExtend = findViewById(R.id.tv_extend)\n        mText = findViewById(R.id.tv_text)\n\n        rootView?.setOnClickListener {\n            if (ActivityUtils.getTopActivity() is DoKitAutotestActivity) {\n                return@setOnClickListener\n            }\n            val intent = Intent(activity, DoKitAutotestActivity::class.java)\n            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK\n            activity.startActivity(intent)\n        }\n        rootView?.findViewById<ImageView>(R.id.iv_close)?.setOnClickListener {\n\n            if (AutoTestManager.getMode() == TestMode.UNKNOWN) {\n                AutoTestManager.stopConnect()\n                DoKit.removeFloating(RecordingCaseDoKitView::class)\n            } else {\n                ToastUtils.showShort(\"正在测试或录制，请先停止\")\n            }\n\n        }\n\n        mRedDot?.startFlash()\n        mExtend?.startFlash()\n\n        changeMode(AutoTestManager.getMode())\n\n        doKitViews.add(this)\n\n    }\n\n    private fun changeText(text: String) {\n        mText?.text = text\n    }\n\n    private fun changeDotColor(id: Int) {\n        mRedDot?.setBackgroundResource(id)\n    }\n\n    fun changeMode(mode: TestMode) {\n        var dotColor = R.drawable.dk_autotest_flash_red_bg\n        var text = \"待链接\"\n        when (mode) {\n            TestMode.UNKNOWN -> {\n                dotColor = R.drawable.dk_autotest_flash_red_bg\n                text = \"已链接\"\n            }\n            TestMode.HOST -> {\n                dotColor = R.drawable.dk_autotest_flash_green_bg\n                text = \"录制中\"\n            }\n            TestMode.CLIENT -> {\n                dotColor = R.drawable.dk_autotest_flash_blue_bg\n                text = \"测试中\"\n            }\n        }\n        changeText(text)\n        changeDotColor(dotColor)\n    }\n\n    override fun initDokitViewLayoutParams(params: DoKitViewLayoutParams) {\n        params.width = DoKitViewLayoutParams.WRAP_CONTENT\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT\n        params.gravity = Gravity.TOP or Gravity.LEFT\n        params.x = ConvertUtils.dp2px(25f)\n        params.y = ConvertUtils.dp2px(25f)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        mRedDot?.cancelFlash()\n        mExtend?.cancelFlash()\n\n        doKitViews.remove(this)\n    }\n}\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/res/drawable/dk_autotest_dialog_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"@color/dk_color_FFFFFF\" />\n    <corners android:radius=\"4dp\" />\n    <stroke\n        android:width=\"1px\"\n        android:color=\"@color/dk_color_CCCCCC\" />\n</shape>\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/res/drawable/dk_autotest_flash_blue_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <corners android:radius=\"7dp\" />\n    <solid android:color=\"@color/dk_color_0070BB\" />\n\n</shape>\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/res/drawable/dk_autotest_flash_green_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <corners android:radius=\"7dp\" />\n    <solid android:color=\"@color/dk_color_48BB31\" />\n\n</shape>\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/res/drawable/dk_autotest_flash_red_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <corners android:radius=\"7dp\" />\n    <solid android:color=\"@color/background_error\" />\n\n</shape>\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/res/drawable/dk_btn_autotest_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <corners android:radius=\"3dp\" />\n    <solid android:color=\"@color/dk_color_337CC4\" />\n</shape>\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/res/layout/dk_activity_autotest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_autotest\" />\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0.5dp\"\n        android:background=\"@color/dk_color_E5E5E5\" />\n\n    <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/fragment_container_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/res/layout/dk_autotest_view_recording_case.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/dk_autotest_dialog_bg\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:padding=\"10dp\">\n\n\n    <com.didichuxing.doraemonkit.kit.test.widget.FlashImageView\n        android:id=\"@+id/dot\"\n        android:layout_width=\"14dp\"\n        android:layout_height=\"14dp\"\n        android:background=\"@drawable/dk_autotest_flash_red_bg\" />\n\n    <TextView\n        android:id=\"@+id/tv_text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"5dp\"\n        android:text=\"已链接\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"14sp\"\n        android:textStyle=\"bold\" />\n\n    <com.didichuxing.doraemonkit.kit.test.widget.FlashTextView\n        android:id=\"@+id/tv_extend\"\n        android:layout_width=\"20dp\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"14sp\"\n        android:textStyle=\"bold\"\n        tools:text=\"...\" />\n\n    <ImageView\n        android:id=\"@+id/iv_close\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"@dimen/dk_dp_10\"\n        android:src=\"@mipmap/dk_close_icon\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/res/layout/dk_fragment_autotest_connect.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <ScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scrollbars=\"vertical\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_horizontal\"\n            android:orientation=\"vertical\">\n\n\n            <TextView\n                android:id=\"@+id/url\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"20dp\"\n                android:layout_marginTop=\"40dp\"\n                android:layout_marginRight=\"20dp\"\n                android:text=\"可使用地址:ws://172.23.164.46:8000/proxy/multicontrol/ILJLQCCF\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\"\n                android:padding=\"20dp\">\n\n                <Switch\n                    android:id=\"@+id/switch_btn\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"是否显示链接状态(悬浮)\"\n                    android:textColor=\"@color/dk_color_333333\"\n                    android:textSize=\"18sp\"\n                    android:textStyle=\"bold\" />\n\n            </LinearLayout>\n\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"40dp\"\n                android:orientation=\"horizontal\">\n\n                <Button\n                    android:id=\"@+id/connect\"\n                    android:layout_width=\"150dp\"\n                    android:layout_height=\"50dp\"\n                    android:layout_marginLeft=\"20dp\"\n                    android:layout_marginTop=\"15dp\"\n                    android:background=\"@drawable/dk_btn_autotest_bg\"\n                    android:text=\"链接服务\"\n                    android:textAllCaps=\"false\"\n                    android:textColor=\"@color/dk_color_FFFFFF\"\n                    android:textSize=\"18sp\"\n                    android:textStyle=\"bold\" />\n\n                <Button\n                    android:id=\"@+id/change\"\n                    android:layout_width=\"150dp\"\n                    android:layout_height=\"50dp\"\n                    android:layout_marginLeft=\"20dp\"\n                    android:layout_marginTop=\"15dp\"\n                    android:background=\"@drawable/dk_btn_autotest_bg\"\n                    android:text=\"更换地址\"\n                    android:textAllCaps=\"false\"\n                    android:textColor=\"@color/dk_color_FFFFFF\"\n                    android:textSize=\"18sp\"\n                    android:textStyle=\"bold\" />\n            </LinearLayout>\n\n\n        </LinearLayout>\n    </ScrollView>\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/res/layout/dk_fragment_autotest_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <ImageView\n        android:id=\"@+id/iv_banner\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:scaleType=\"centerCrop\"\n        android:src=\"@drawable/dk_autotest_banner\" />\n\n\n    <ScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_below=\"@id/iv_banner\"\n        android:scrollbars=\"vertical\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_horizontal\"\n            android:orientation=\"vertical\">\n\n            <Button\n                android:id=\"@+id/connect\"\n                android:layout_width=\"150dp\"\n                android:layout_height=\"50dp\"\n                android:layout_marginTop=\"15dp\"\n                android:background=\"@drawable/dk_btn_autotest_bg\"\n                android:text=\"链接服务\"\n                android:textAllCaps=\"false\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n            <Button\n                android:id=\"@+id/record\"\n                android:layout_width=\"150dp\"\n                android:layout_height=\"50dp\"\n                android:layout_marginTop=\"15dp\"\n                android:background=\"@drawable/dk_btn_autotest_bg\"\n                android:text=\"用例录制\"\n                android:textAllCaps=\"false\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n\n            <Button\n                android:id=\"@+id/record_stop\"\n                android:layout_width=\"150dp\"\n                android:layout_height=\"50dp\"\n                android:layout_marginTop=\"15dp\"\n                android:background=\"@drawable/dk_btn_autotest_bg\"\n                android:text=\"停止录制\"\n                android:textAllCaps=\"false\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n\n            <Button\n                android:id=\"@+id/test\"\n                android:layout_width=\"150dp\"\n                android:layout_height=\"50dp\"\n                android:layout_marginTop=\"15dp\"\n                android:background=\"@drawable/dk_btn_autotest_bg\"\n                android:text=\"开始测试\"\n                android:textAllCaps=\"false\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n\n            <Button\n                android:id=\"@+id/test_stop\"\n                android:layout_width=\"150dp\"\n                android:layout_height=\"50dp\"\n                android:layout_marginTop=\"15dp\"\n                android:background=\"@drawable/dk_btn_autotest_bg\"\n                android:text=\"停止测试\"\n                android:textAllCaps=\"false\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n            <Button\n                android:id=\"@+id/caseList\"\n                android:layout_width=\"150dp\"\n                android:layout_height=\"50dp\"\n                android:layout_marginTop=\"15dp\"\n                android:background=\"@drawable/dk_btn_autotest_bg\"\n                android:text=\"用例列表\"\n                android:textAllCaps=\"false\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n        </LinearLayout>\n    </ScrollView>\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/res/values/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"dk_kit_autotest\">自动化测试</string>\n</resources>\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/res/values-en-rCN/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n\n\n    <string name=\"dk_kit_autotest\">自动化测试</string>\n</resources>\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/res/values-zh-rTW/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n\n\n    <string name=\"dk_kit_autotest\">自动化测试</string>\n</resources>\n"
  },
  {
    "path": "Android/dokit-autotest/src/main/res/values-zh-rUS/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n\n    <string name=\"dk_kit_autotest\">Auto Test</string>\n</resources>\n"
  },
  {
    "path": "Android/dokit-autotest/src/test/java/com/didichuxing/doraemonkit/kit/autotest/ExampleUnitTest.java",
    "content": "package com.didichuxing.doraemonkit.kit.autotest;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() {\n        assertEquals(4, 2 + 2);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-autotest/upload.sh",
    "content": "#!/usr/bin/env bash\necho -n  \"please enter bintray userid ->\"\nread  userid_bintray\necho -n  \"please enter bintray apikey ->\"\nread  apikey_bintray\n../gradlew clean build --stacktrace  --info   bintrayUpload -PbintrayUser=${userid_bintray} -PbintrayKey=${apikey_bintray} -PdryRun=false"
  },
  {
    "path": "Android/dokit-ft/.gitignore",
    "content": "/build"
  },
  {
    "path": "Android/dokit-ft/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-kapt'\napply from: '../upload.gradle'\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_16\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n    /**\n     * 支持ViewBinding\n     */\n    buildFeatures {\n        //viewBinding = true\n    }\n}\n\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    if (needKotlinV14()) {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v14\"]\n    } else {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v13\"]\n    }\n    implementation rootProject.ext.dependencies[\"appcompat\"]\n    implementation rootProject.ext.dependencies[\"activity-ktx\"]\n    //sql\n    implementation rootProject.ext.dependencies[\"wcdb\"]\n//    implementation rootProject.ext.dependencies[\"core-ktx\"]\n    implementation rootProject.ext.dependencies[\"auto_service\"]\n    kapt rootProject.ext.dependencies[\"auto_service\"]\n    //ktor\n    implementation rootProject.ext.dependencies[\"ktor_server_core\"]\n    implementation rootProject.ext.dependencies[\"ktor_server_cio\"]\n\n    //此处需要使用api的形式 向上暴露内部api\n    implementation project(':dokit')\n    compileOnly project(':dokit-util')\n}\nrepositories {\n    mavenCentral()\n}\n"
  },
  {
    "path": "Android/dokit-ft/gradle.properties",
    "content": "ARTIFACT_ID=dokitx-ft"
  },
  {
    "path": "Android/dokit-ft/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original from file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "Android/dokit-ft/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.didichuxing.doraemonkit.ft\">\n\n</manifest>"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/DokitFileRouter.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager\n\nimport android.os.Build\nimport com.didichuxing.doraemonkit.util.FileUtils\nimport com.didichuxing.doraemonkit.kit.filemanager.action.RequestErrorAction\nimport com.didichuxing.doraemonkit.kit.filemanager.action.file.*\nimport com.didichuxing.doraemonkit.kit.filemanager.action.sql.DatabaseAction\nimport com.didichuxing.doraemonkit.kit.filemanager.bean.DirInfo\nimport com.didichuxing.doraemonkit.kit.filemanager.bean.RenameFileInfo\nimport com.didichuxing.doraemonkit.kit.filemanager.bean.SaveFileInfo\nimport com.didichuxing.doraemonkit.kit.filemanager.convert.GsonConverter\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.bean.RowRequestInfo\nimport io.ktor.application.Application\nimport io.ktor.application.call\nimport io.ktor.application.install\nimport io.ktor.features.*\nimport io.ktor.http.ContentType\nimport io.ktor.http.HttpHeaders\nimport io.ktor.http.HttpMethod\nimport io.ktor.request.receive\nimport io.ktor.request.receiveMultipart\nimport io.ktor.response.respond\nimport io.ktor.response.respondFile\nimport io.ktor.routing.get\nimport io.ktor.routing.post\nimport io.ktor.routing.routing\n\nimport java.io.File\nimport java.time.Duration\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-14:35\n * 描    述：\n * 修订历史：\n * ================================================\n */\nval DoKitFileRouter: Application.() -> Unit = {\n    install(ContentNegotiation) {\n        register(ContentType.Application.Json, GsonConverter())\n    }\n    install(CORS) {\n        method(HttpMethod.Options)\n        method(HttpMethod.Get)\n        method(HttpMethod.Post)\n        method(HttpMethod.Put)\n        method(HttpMethod.Delete)\n        method(HttpMethod.Patch)\n        header(HttpHeaders.AccessControlAllowHeaders)\n        header(HttpHeaders.ContentType)\n        header(HttpHeaders.AccessControlAllowOrigin)\n        allowCredentials = true\n        anyHost()\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            maxAge = Duration.ofDays(1L)\n        }\n    }\n    install(DefaultHeaders)\n    install(CallLogging)\n\n    routing {\n//        static(\"custom\") {\n//            staticRootFolder = File(PathUtils.getInternalAppDataPath())\n//            files(\"img\")\n//        }\n\n        /**\n         * index\n         */\n        get(\"/\") {\n            call.respond(IndexAction.indexInfoRes())\n        }\n\n        /**\n         * 获取设备详情\n         */\n        get(\"/getDeviceInfo\") {\n            call.respond(DeviceInfoAction.deviceInfoRes())\n        }\n\n        /**\n         * 获取文件列表\n         */\n        get(\"/getFileList\") {\n            val queryParameters = call.request.queryParameters\n            val dirPath = FileManagerUtil.absoluteRootPath(queryParameters[\"dirPath\"])\n            if (dirPath.isBlank()) {\n                call.respond(RequestErrorAction.createErrorInfo(\"dirPath is not standard\"))\n            } else {\n                call.respond(FileListAction.fileListRes(dirPath))\n            }\n        }\n\n        /**\n         * 获取文件详情\n         */\n        get(\"/getFileDetail\") {\n            val queryParameters = call.request.queryParameters\n            val dirPath = FileManagerUtil.absoluteRootPath(queryParameters[\"dirPath\"])\n//            val fileType = queryParameters[\"fileType\"]\n            val fileName = queryParameters[\"fileName\"]\n            val filePath = \"$dirPath$fileName\"\n            call.respond(FileDetailAction.fileDetailInfoRes(filePath))\n        }\n\n        /**\n         * 创建文件夹\n         */\n        post(\"/createFolder\") {\n            val params = call.receive<DirInfo>()\n            val dirPath = FileManagerUtil.absoluteRootPath(params.dirPath)\n            val fileName = params.fileName\n            call.respond(CreateFolderAction.createFolderRes(dirPath, fileName))\n        }\n\n        /**\n         * 上传文件\n         */\n        post(\"/uploadFile\") {\n            val multipart = call.receiveMultipart()\n            call.respond(UploadFileAction.uploadFileRes(multipart))\n        }\n\n        /**\n         * 下载文件\n         */\n        get(\"/downloadFile\") {\n            val queryParameters = call.request.queryParameters\n            val dirPath = FileManagerUtil.absoluteRootPath(queryParameters[\"dirPath\"])\n            val fileName = queryParameters[\"fileName\"]\n\n            val file = File(\"$dirPath${File.separator}$fileName\")\n            if (FileUtils.isFileExists(file)) {\n                //call.response.header(\"Content-Disposition\", \"attachment; filename=\\\"${file.name}\\\"\")\n                call.respondFile(file)\n            } else {\n                val response = mutableMapOf<String, Any>()\n                response[\"code\"] = 0\n                response[\"success\"] = false\n                call.respond(response)\n            }\n\n        }\n\n        /**\n         * 删除文件\n         */\n        post(\"/deleteFile\") {\n            val params = call.receive<DirInfo>()\n            val dirPath = FileManagerUtil.absoluteRootPath(params.dirPath)\n            val fileName = params.fileName\n            val filePath = \"$dirPath$fileName\"\n            call.respond(DeleteFileAction.deleteFileRes(filePath, dirPath, fileName))\n        }\n\n        /**\n         * 重命名文件\n         */\n        post(\"/rename\") {\n            val fileInfo = call.receive<RenameFileInfo>()\n            val dirPath = FileManagerUtil.absoluteRootPath(fileInfo.dirPath)\n            val oldName = fileInfo.oldName\n            val filePath = \"$dirPath$oldName\"\n            call.respond(RenameFileAction.renameFileRes(fileInfo.newName, filePath))\n        }\n\n\n        /**\n         * 保存文件\n         */\n        post(\"/saveFile\") {\n            val saveFileInfo = call.receive<SaveFileInfo>()\n            val dirPath = FileManagerUtil.absoluteRootPath(saveFileInfo.dirPath)\n            val fileName = saveFileInfo.fileName\n            val content = saveFileInfo.content\n            val filePath = \"$dirPath$fileName\"\n            call.respond(SaveFileAction.saveFileRes(content, filePath))\n        }\n\n        /**\n         * 数据库相关接口\n         */\n\n        get(\"/getAllTable\") {\n            val queryParameters = call.request.queryParameters\n            val dirPath = FileManagerUtil.absoluteRootPath(queryParameters[\"dirPath\"])\n            val fileName = queryParameters[\"fileName\"]\n            val filePath = \"$dirPath$fileName\"\n            call.respond(DatabaseAction.allTablesRes(filePath, fileName!!))\n        }\n\n        /**\n         * 查询指定表中的数据\n         */\n        get(\"/getTableData\") {\n            val queryParameters = call.request.queryParameters\n            val dirPath = FileManagerUtil.absoluteRootPath(queryParameters[\"dirPath\"])\n            val fileName = queryParameters[\"fileName\"]\n            val tableName = queryParameters[\"tableName\"]\n            val filePath = \"$dirPath$fileName\"\n            call.respond(DatabaseAction.tableDatasRes(filePath, fileName!!, tableName!!))\n        }\n\n        /**\n         * 插入一行数据\n         */\n        post(\"/insertRow\") {\n            val rowRequestInfo = call.receive<RowRequestInfo>()\n            val dirPath = FileManagerUtil.absoluteRootPath(rowRequestInfo.dirPath)\n            val fileName = rowRequestInfo.fileName\n            val tableName = rowRequestInfo.tableName\n            val filePath = \"$dirPath$fileName\"\n            val rowDatas = rowRequestInfo.rowDatas\n            call.respond(DatabaseAction.insertRowRes(filePath, fileName, tableName, rowDatas))\n        }\n\n        /**\n         * 更新一条数据\n         */\n        post(\"/updateRow\") {\n            val rowRequestInfo = call.receive<RowRequestInfo>()\n            val dirPath = FileManagerUtil.absoluteRootPath(rowRequestInfo.dirPath)\n            val fileName = rowRequestInfo.fileName\n            val tableName = rowRequestInfo.tableName\n            val filePath = \"$dirPath$fileName\"\n            val rowDatas = rowRequestInfo.rowDatas\n            call.respond(DatabaseAction.updateRowRes(filePath, fileName, tableName, rowDatas))\n        }\n\n        /**\n         * 删除一条数据\n         */\n        post(\"/deleteRow\") {\n            val rowRequestInfo = call.receive<RowRequestInfo>()\n            val dirPath = FileManagerUtil.absoluteRootPath(rowRequestInfo.dirPath)\n            val fileName = rowRequestInfo.fileName\n            val tableName = rowRequestInfo.tableName\n            val filePath = \"$dirPath$fileName\"\n            val rowDatas = rowRequestInfo.rowDatas\n            call.respond(DatabaseAction.deleteRowRes(filePath, fileName, tableName, rowDatas))\n        }\n\n    }\n}\n\n\n\n\n\n"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/FileManagerUtil.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager\n\nimport com.didichuxing.doraemonkit.util.FileUtils\nimport com.didichuxing.doraemonkit.util.PathUtils\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/29-17:10\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject FileManagerUtil {\n    /**\n     * 根目录匹配字段\n     */\n    const val ROOT_PATH_STR = \"/root/\"\n\n    private val internalAppRootPath by lazy { PathUtils.getInternalAppDataPath() }\n    private val internalAppRootReplacePath by lazy { FileUtils.getFileName(PathUtils.getInternalAppDataPath()) }\n    public val externalStorageRootPath by lazy { PathUtils.getExternalStoragePath() }\n    private val externalStorageRootReplacePath by lazy { \"external\" }\n\n\n    /**\n     * 输出相对路径\n     */\n    fun relativeRootPath(path: String?): String {\n        path?.let {\n            if (it.contains(internalAppRootPath)) {\n                return it.replace(internalAppRootPath, \"$ROOT_PATH_STR$internalAppRootReplacePath\")\n            } else if (it.contains(externalStorageRootPath)) {\n                return it.replace(externalStorageRootPath, \"$ROOT_PATH_STR$externalStorageRootReplacePath\")\n            }\n            return it\n        }\n\n        return \"\"\n    }\n\n    /**\n     * 合并绝对路径\n     */\n    fun absoluteRootPath(path: String?): String {\n        path?.let {\n            if (it.contains(\"$ROOT_PATH_STR$internalAppRootReplacePath\")) {\n                return it.replace(\"$ROOT_PATH_STR$internalAppRootReplacePath\", internalAppRootPath)\n            } else if (it.contains(\"$ROOT_PATH_STR$externalStorageRootReplacePath\")) {\n                return it.replace(\"$ROOT_PATH_STR$externalStorageRootReplacePath\", externalStorageRootPath)\n            }\n            return it\n        }\n        return \"\"\n    }\n\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/FileTransferFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.text.Html\nimport android.view.View\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.ft.R\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.kit.webview.CommWebViewFragment\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\nimport java.net.BindException\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-13:45\n * 描    述：文件互传fragment\n * 修订历史：\n * ================================================\n */\nclass FileTransferFragment : BaseFragment() {\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_file_transfer\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        findViewById<TextView>(R.id.tv_ip).text =\n            \"${DoKitManager.IP_ADDRESS_BY_WIFI}:${DoKitManager.FILE_MANAGER_HTTP_PORT}\"\n        findViewById<HomeTitleBar>(R.id.title_bar).setListener { finish() }\n        findViewById<TextView>(R.id.tv_tip_top).apply {\n            text = Html.fromHtml(DoKitCommUtil.getString(R.string.dk_file_manager_tip_top))\n            setOnClickListener {\n                DoKit.launchFullScreen(\n                    CommWebViewFragment::class,\n                    context,\n                    isSystemFragment = true\n                )\n            }\n        }\n\n        initKtor()\n    }\n\n    private fun initKtor() {\n        try {\n            HttpServer.server.start()\n        } catch (e: BindException) {\n\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/FileTransferKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-13:30\n * 描    述：\n * 修订历史：\n * ================================================\n */\n@AutoService(AbstractKit::class)\nclass FileTransferKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_file_transfer\n    override val icon: Int\n        get() = R.mipmap.dk_icon_file_manager\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        startUniversalActivity(FileTransferFragment::class.java, activity)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n    }\n\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_platform_ck_filetransfer\"\n    }\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/HttpServer.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager\n\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport io.ktor.server.cio.*\nimport io.ktor.server.engine.embeddedServer\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/28-11:02\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject HttpServer {\n    val server: CIOApplicationEngine by lazy {\n        embeddedServer(CIO, port = DoKitManager.FILE_MANAGER_HTTP_PORT, module = DoKitFileRouter)\n    }\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/ability/DokitFtAbility.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.ability\n\nimport com.didichuxing.doraemonkit.constant.DoKitModule\nimport com.didichuxing.doraemonkit.kit.core.DokitAbility\nimport com.google.auto.service.AutoService\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/7-19:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\n@AutoService(DokitAbility::class)\nclass DokitFtAbility : DokitAbility {\n    override fun init() {\n\n    }\n\n    override fun moduleName(): DoKitModule {\n        return DoKitModule.MODULE_FT\n    }\n\n    override fun getModuleProcessor(): DokitAbility.DokitModuleProcessor {\n        return DokitFtModuleProcessor()\n    }\n\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/ability/DokitFtModuleProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.ability\n\nimport com.didichuxing.doraemonkit.kit.core.DokitAbility\nimport com.google.auto.service.AutoService\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/7-19:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DokitFtModuleProcessor : DokitAbility.DokitModuleProcessor {\n\n\n    override fun values(): Map<String, Any> {\n        return mapOf()\n    }\n\n    override  fun proceed(actions: Map<String, Any?>?): Map<String, Any> {\n        return mapOf()\n    }\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/action/RequestErrorAction.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.action\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-16:38\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject RequestErrorAction {\n    fun createErrorInfo(error: String): MutableMap<String, Any> {\n        return mutableMapOf<String, Any>().apply {\n            this[\"code\"] = 400\n            this[\"data\"] = error\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/action/file/CreateFolderAction.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.action.file\n\nimport com.didichuxing.doraemonkit.util.FileUtils\nimport java.io.File\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-15:26\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject CreateFolderAction {\n    fun createFolderRes(dirPath: String, fileName: String): MutableMap<String, Any> {\n        val response = mutableMapOf<String, Any>()\n        if (FileUtils.isDir(dirPath)) {\n            val createOrExistsFile = FileUtils.createOrExistsDir(\"$dirPath${File.separator}$fileName\")\n            if (createOrExistsFile) {\n                response[\"code\"] = 200\n                response[\"success\"] = true\n                response[\"message\"] = \"success\"\n            } else {\n                response[\"code\"] = 0\n                response[\"success\"] = false\n                response[\"message\"] = \"create dir failure\"\n            }\n        } else {\n            response[\"code\"] = 0\n            response[\"success\"] = false\n            response[\"message\"] = \"is not dir\"\n        }\n\n        return response\n    }\n\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/action/file/DeleteFileAction.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.action.file\n\nimport com.didichuxing.doraemonkit.util.FileUtils\nimport java.io.File\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-15:26\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject DeleteFileAction {\n    fun deleteFileRes(filePath: String, dirPath: String, fileName: String): MutableMap<String, Any> {\n        val response = mutableMapOf<String, Any>()\n        //删除文件夹\n        if (FileUtils.isFileExists(filePath)) {\n            //假如是文件夹\n            if (FileUtils.isDir(filePath)) {\n                //先删除文件夹下的所有内容\n                val deleteFilesSuccess = FileUtils.deleteAllInDir(\"$dirPath${File.separator}$fileName\")\n                //再删除文件夹本身\n                val deleteDirSuccess = FileUtils.delete(\"$dirPath${File.separator}$fileName\")\n                if (deleteFilesSuccess && deleteDirSuccess) {\n                    response[\"code\"] = 200\n                    response[\"success\"] = true\n                    response[\"message\"] = \"success\"\n                } else {\n                    response[\"code\"] = 0\n                    response[\"success\"] = false\n                    response[\"message\"] = \"delete $filePath failure\"\n                }\n            } else {\n                //删除文件\n                val deleteSuccess = FileUtils.delete(\"$dirPath${File.separator}$fileName\")\n                if (deleteSuccess) {\n                    response[\"code\"] = 200\n                    response[\"success\"] = true\n                    response[\"message\"] = \"success\"\n                } else {\n                    response[\"code\"] = 0\n                    response[\"success\"] = false\n                    response[\"message\"] = \"delete $filePath failure\"\n                }\n            }\n        } else {\n            response[\"code\"] = 0\n            response[\"success\"] = false\n            response[\"message\"] = \"delete $filePath failure\"\n        }\n\n        return response\n    }\n\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/action/file/DeviceInfoAction.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.action.file\n\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.util.DeviceUtils\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-15:26\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject DeviceInfoAction {\n    fun deviceInfoRes(): MutableMap<String, Any> {\n        return mutableMapOf<String, Any>().apply {\n            this[\"code\"] = 200\n            val data = mutableMapOf<String, String>().apply {\n                this[\"deviceName\"] = \"${DeviceUtils.getManufacturer()}-${DeviceUtils.getModel()}\"\n                this[\"deviceId\"] = DeviceUtils.getUniqueDeviceId()\n                this[\"deviceIp\"] =\n                    \"${DoKitManager.IP_ADDRESS_BY_WIFI}:${DoKitManager.FILE_MANAGER_HTTP_PORT}\"\n            }\n            this[\"data\"] = data\n        }\n    }\n\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/action/file/FileDetailAction.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.action.file\n\nimport com.didichuxing.doraemonkit.util.FileIOUtils\nimport com.didichuxing.doraemonkit.util.FileUtils\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-15:26\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject FileDetailAction {\n    fun fileDetailInfoRes(filePath: String): MutableMap<String, Any> {\n        val params = mutableMapOf<String, Any>()\n        if (FileUtils.isFileExists(filePath) && FileUtils.isFile(filePath)) {\n            params[\"code\"] = 200\n            val data = mutableMapOf<String, Any>()\n            data[\"fileType\"] = FileUtils.getFileExtension(filePath)\n            data[\"fileContent\"] = FileIOUtils.readFile2String(filePath)\n            params[\"data\"] = data\n        } else {\n            params[\"code\"] = 0\n            params[\"data\"] = \"$filePath is not a file\"\n        }\n\n        return params\n    }\n}\n\n\n\n"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/action/file/FileListAction.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.action.file\n\nimport com.didichuxing.doraemonkit.util.ConvertUtils\nimport com.didichuxing.doraemonkit.util.FileUtils\nimport com.didichuxing.doraemonkit.util.PathUtils\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.didichuxing.doraemonkit.R as DoKitR\nimport com.didichuxing.doraemonkit.kit.filemanager.FileManagerUtil\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport java.io.File\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-15:26\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject FileListAction {\n    fun fileListRes(dirPath: String): MutableMap<String, Any> {\n        //root  path\n        val params = mutableMapOf<String, Any>().apply {\n            this[\"code\"] = 200\n        }\n        if (dirPath == FileManagerUtil.ROOT_PATH_STR) {\n            val data = mutableMapOf<String, Any>().apply {\n                this[\"dirPath\"] = FileManagerUtil.ROOT_PATH_STR\n                this[\"fileList\"] = createRootInfo()\n            }\n            params[\"data\"] = data\n        } else {\n            //not root path\n            val data = mutableMapOf<String, Any>().apply {\n                this[\"dirPath\"] = FileManagerUtil.relativeRootPath(dirPath)\n                val fileInfos = traverseDir(dirPath)\n                if (dirPath == FileManagerUtil.externalStorageRootPath && fileInfos.isEmpty()) {\n                    this[\"code\"] = 0\n                    this[\"message\"] =\n                        DoKitCommUtil.getString(DoKitR.string.dk_file_manager_sd_permission_tip)\n                    ToastUtils.showShort(DoKitCommUtil.getString(DoKitR.string.dk_file_manager_sd_permission_tip))\n                }\n                this[\"fileList\"] = fileInfos\n            }\n            params[\"data\"] = data\n        }\n\n        return params\n    }\n\n\n    /**\n     * 遍历根文件夹\n     */\n    private fun createRootInfo(): MutableList<FileInfo> {\n        val fileInfos = mutableListOf<FileInfo>()\n        val internalAppDataPath = PathUtils.getInternalAppDataPath()\n        val externalStoragePath = PathUtils.getExternalStoragePath()\n        fileInfos.add(\n            FileInfo(\n                FileManagerUtil.ROOT_PATH_STR,\n                FileUtils.getFileName(internalAppDataPath),\n                \"\",\n                \"folder\",\n                \"\",\n                \"\" + FileUtils.getFileLastModified(internalAppDataPath),\n                true\n            )\n        )\n        fileInfos.add(\n            FileInfo(\n                FileManagerUtil.ROOT_PATH_STR,\n                \"external\",\n                \"\",\n                \"folder\",\n                \"\",\n                \"\" + FileUtils.getFileLastModified(externalStoragePath),\n                true\n            )\n        )\n        return fileInfos\n    }\n\n    /**\n     * 遍历文件夹\n     */\n    private fun traverseDir(dirPath: String): MutableList<FileInfo> {\n        val fileInfos = mutableListOf<FileInfo>()\n        val dir = File(dirPath)\n        if (FileUtils.isFileExists(dir) && FileUtils.isDir(dir)) {\n            dir.listFiles()?.forEach { file ->\n                val fileInfo = FileInfo(\n                    FileManagerUtil.relativeRootPath(dirPath), file.name,\n                    if (FileUtils.isDir(file)) {\n                        \"\"\n                    } else {\n                        ConvertUtils.byte2FitMemorySize(file.length(), 1)\n                    },\n                    if (FileUtils.isDir(file)) {\n                        \"folder\"\n                    } else if (dir.absolutePath.contains(\"/databases\")) {\n                        \"db\"\n                    } else {\n                        if (FileUtils.getFileExtension(file).isNotBlank()) {\n                            FileUtils.getFileExtension(file)\n                        } else {\n                            \"txt\"\n                        }\n                    }, \"\", \"\" + FileUtils.getFileLastModified(file), false\n                )\n                fileInfos.add(fileInfo)\n            }\n\n        }\n\n        return fileInfos\n    }\n\n\n    data class FileInfo(\n        val dirPath: String,\n        val fileName: String,\n        val fileSize: String,\n        val fileType: String,\n        val fileUri: String,\n        val modifyTime: String,\n        val isRootPath: Boolean\n    )\n}\n\n\n\n"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/action/file/IndexAction.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.action.file\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-15:26\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject IndexAction {\n    fun indexInfoRes(): MutableMap<String, Any> {\n        return mutableMapOf<String, Any>().apply {\n            this[\"code\"] = 200\n            this[\"data\"] = \"请在www.dokit.cn里的控制台中的文件同步助手中使用该功能\"\n        }\n    }\n\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/action/file/RenameFileAction.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.action.file\n\nimport com.didichuxing.doraemonkit.util.FileUtils\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-15:26\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject RenameFileAction {\n    fun renameFileRes(newName: String, filePath: String): MutableMap<String, Any> {\n        val response = mutableMapOf<String, Any>()\n        if (FileUtils.isFileExists(filePath)) {\n            FileUtils.rename(filePath, newName)\n            response[\"code\"] = 200\n            response[\"success\"] = true\n            response[\"message\"] = \"success\"\n        } else {\n            response[\"code\"] = 0\n            response[\"success\"] = false\n            response[\"message\"] = \"$filePath is not exists\"\n        }\n        return response\n    }\n\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/action/file/SaveFileAction.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.action.file\n\nimport com.didichuxing.doraemonkit.util.ConvertUtils\nimport com.didichuxing.doraemonkit.util.FileIOUtils\nimport com.didichuxing.doraemonkit.util.FileUtils\nimport org.json.JSONArray\nimport org.json.JSONObject\nimport org.xml.sax.helpers.DefaultHandler\nimport javax.xml.parsers.SAXParserFactory\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-15:26\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject SaveFileAction {\n    fun saveFileRes(content: String, filePath: String): MutableMap<String, Any> {\n        val response = mutableMapOf<String, Any>()\n\n        if (FileUtils.isFileExists(filePath)) {\n            val fileExtension = FileUtils.getFileExtension(filePath)\n            if (fileExtension.contains(\"xml\")) {\n                if (isXml(content)) {\n                    FileIOUtils.writeFileFromString(filePath, content, false)\n                    response[\"code\"] = 200\n                    response[\"success\"] = true\n                    response[\"message\"] = \"success\"\n                } else {\n                    response[\"code\"] = 0\n                    response[\"success\"] = false\n                    response[\"message\"] = \"is not xml\"\n                }\n            }\n            if (fileExtension.contains(\"json\")) {\n                var isJsonObject = false\n                var isJsonArray = false\n                try {\n                    JSONObject(content)\n                    isJsonObject = true\n                } catch (e: Exception) {\n                    isJsonObject = false\n                }\n\n                try {\n                    JSONArray(content)\n                    isJsonArray = true\n                } catch (e: Exception) {\n                    isJsonArray = false\n                }\n                if (isJsonObject || isJsonArray) {\n                    FileIOUtils.writeFileFromString(filePath, content, false)\n                    response[\"code\"] = 200\n                    response[\"success\"] = true\n                    response[\"message\"] = \"success\"\n                } else {\n                    response[\"code\"] = 0\n                    response[\"success\"] = false\n                    response[\"message\"] = \"is not json\"\n                }\n\n            } else {\n                FileIOUtils.writeFileFromString(filePath, content, false)\n                response[\"code\"] = 200\n                response[\"success\"] = true\n                response[\"message\"] = \"success\"\n            }\n        } else {\n            response[\"code\"] = 0\n            response[\"success\"] = false\n            response[\"message\"] = \"is not a file\"\n        }\n        return response\n    }\n\n    /**\n     * 判断是否是xml\n     */\n    private fun isXml(content: String): Boolean {\n        try {\n            val factory = SAXParserFactory.newInstance()\n            val sp = factory.newSAXParser()\n            sp.parse(ConvertUtils.string2InputStream(content, \"utf-8\"), DefaultHandler())\n            return true\n        } catch (e: Exception) {\n            return false\n        }\n    }\n\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/action/file/UploadFileAction.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.action.file\n\nimport com.didichuxing.doraemonkit.util.FileUtils\nimport com.didichuxing.doraemonkit.kit.filemanager.FileManagerUtil\nimport io.ktor.http.content.MultiPartData\nimport io.ktor.http.content.PartData\nimport io.ktor.http.content.forEachPart\nimport io.ktor.http.content.streamProvider\nimport java.io.File\nimport java.io.InputStream\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-15:26\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject UploadFileAction {\n\n    suspend fun uploadFileRes(multipart: MultiPartData): MutableMap<String, Any> {\n        //val postParams = call.receiveParameters()\n        //val dirPath = postParams[\"filePath\"]\n        var filePart: PartData.FileItem? = null\n        var formPart: PartData.FormItem? = null\n        multipart.forEachPart { part ->\n            when (part) {\n                is PartData.FileItem -> filePart = part\n                is PartData.FormItem -> formPart = part\n            }\n        }\n\n        val response = mutableMapOf<String, Any>()\n\n        filePart?.let {\n            val dirPath = FileManagerUtil.absoluteRootPath(formPart?.value)\n            val fileName = filePart?.originalFileName\n            val file = File(\"$dirPath${File.separator}$fileName\")\n\n            it.streamProvider().use { inputStream: InputStream? ->\n                file.outputStream().buffered().use { outputStream ->\n                    inputStream?.copyTo(outputStream)\n                }\n            }\n            filePart?.dispose\n            formPart?.dispose\n\n            if (FileUtils.isFileExists(file)) {\n                response[\"code\"] = 200\n                response[\"success\"] = true\n                response[\"message\"] = \"success\"\n            } else {\n                response[\"code\"] = 0\n                response[\"success\"] = false\n                response[\"message\"] = \"${file.absolutePath} is not exists\"\n            }\n        } ?: let {\n            response[\"code\"] = 0\n            response[\"success\"] = false\n            response[\"message\"] = \"filePart is null\"\n        }\n\n\n        return response\n    }\n\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/action/sql/DatabaseAction.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.action.sql\n\nimport android.database.sqlite.SQLiteDatabaseCorruptException\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.DBManager\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.bean.RowFiledInfo\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/28-11:36\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject DatabaseAction {\n    fun allTablesRes(filePath: String, fileName: String): MutableMap<String, Any> {\n        val response = mutableMapOf<String, Any>()\n        try {\n            val tables = DBManager.getAllTableName(filePath, fileName)\n            response[\"code\"] = 200\n            response[\"data\"] = tables\n        } catch (e: SQLiteDatabaseCorruptException) {\n            response[\"code\"] = 0\n            response[\"data\"] = arrayListOf<String>()\n            response[\"message\"] = \"${e.message}\"\n        }\n\n        return response\n    }\n\n    fun tableDatasRes(filePath: String, fileName: String, tableName: String): MutableMap<String, Any> {\n        val response = mutableMapOf<String, Any>()\n        val tables = DBManager.getTableData(filePath, fileName, tableName)\n        response[\"code\"] = 200\n        response[\"data\"] = tables\n        return response\n    }\n\n    fun insertRowRes(filePath: String, fileName: String, tableName: String, rowDatas: List<RowFiledInfo>): Map<String, Any> {\n        val response = mutableMapOf<String, Any>()\n        val insertRow = DBManager.insertRow(filePath, fileName, tableName, rowDatas)\n        if (insertRow <= 0) {\n            response[\"code\"] = 0\n            response[\"success\"] = false\n            response[\"message\"] = \"insertRow=$insertRow\"\n        } else {\n            response[\"code\"] = 200\n            response[\"success\"] = true\n            response[\"message\"] = \"insertRow=$insertRow\"\n        }\n        return response\n    }\n\n    fun updateRowRes(filePath: String, fileName: String, tableName: String, rowDatas: List<RowFiledInfo>): Map<String, Any> {\n        val response = mutableMapOf<String, Any>()\n        val updateRow = DBManager.updateRow(filePath, fileName, tableName, rowDatas)\n        if (updateRow <= 0) {\n            response[\"code\"] = 0\n            response[\"success\"] = false\n            response[\"message\"] = \"updateRow=$updateRow\"\n        } else {\n            response[\"code\"] = 200\n            response[\"success\"] = true\n            response[\"message\"] = \"updateRow=$updateRow\"\n        }\n        return response\n\n    }\n\n    fun deleteRowRes(filePath: String, fileName: String, tableName: String, rowDatas: List<RowFiledInfo>): Map<String, Any> {\n        val response = mutableMapOf<String, Any>()\n        val deleteRow = DBManager.deleteRow(filePath, fileName, tableName, rowDatas)\n        if (deleteRow <= 0) {\n            response[\"code\"] = 0\n            response[\"success\"] = false\n            response[\"message\"] = \"deleteRow=$deleteRow\"\n        } else {\n            response[\"code\"] = 200\n            response[\"success\"] = true\n            response[\"message\"] = \"deleteRow=$deleteRow\"\n        }\n        return response\n    }\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/bean/DirInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.bean\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-18:18\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class DirInfo(val dirPath: String, val fileName: String) {\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/bean/RenameFileInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.bean\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-18:18\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class RenameFileInfo(val dirPath: String, val oldName: String, val newName: String)"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/bean/SaveFileInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.bean\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-18:18\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class SaveFileInfo(val dirPath: String, val fileName: String, val content: String)"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/convert/GsonConverter.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.convert\n\nimport com.google.gson.Gson\nimport com.google.gson.GsonBuilder\nimport io.ktor.application.ApplicationCall\nimport io.ktor.application.call\nimport io.ktor.features.ContentConverter\nimport io.ktor.features.ContentNegotiation\nimport io.ktor.features.ContentTransformationException\nimport io.ktor.features.suitableCharset\nimport io.ktor.http.ContentType\nimport io.ktor.http.content.TextContent\nimport io.ktor.http.withCharset\nimport io.ktor.request.ApplicationReceiveRequest\nimport io.ktor.request.contentCharset\nimport io.ktor.util.pipeline.PipelineContext\nimport io.ktor.utils.io.ByteReadChannel\nimport io.ktor.utils.io.jvm.javaio.toInputStream\nimport kotlinx.coroutines.CopyableThrowable\nimport kotlin.reflect.KClass\nimport kotlin.reflect.jvm.jvmName\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/7/16-18:05\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass GsonConverter(private val gson: Gson = Gson()) : ContentConverter {\n    override suspend fun convertForSend(\n            context: PipelineContext<Any, ApplicationCall>,\n            contentType: ContentType,\n            value: Any\n    ): Any? {\n        return TextContent(gson.toJson(value), contentType.withCharset(context.call.suitableCharset()))\n    }\n\n    override suspend fun convertForReceive(context: PipelineContext<ApplicationReceiveRequest, ApplicationCall>): Any? {\n        val request = context.subject\n        val channel = request.value as? ByteReadChannel ?: return null\n        val reader = channel.toInputStream().reader(context.call.request.contentCharset()\n                ?: Charsets.UTF_8)\n        val type = request.type\n\n        if (gson.isExcluded(type)) {\n            throw ExcludedTypeGsonException(type)\n        }\n\n        return gson.fromJson(reader, type.javaObjectType) ?: throw UnsupportedNullValuesException()\n    }\n}\n\n/**\n * Register GSON to [ContentNegotiation] feature\n */\nfun ContentNegotiation.Configuration.gson(\n        contentType: ContentType = ContentType.Application.Json,\n        block: GsonBuilder.() -> Unit = {}\n) {\n    val builder = GsonBuilder()\n    builder.apply(block)\n    val converter = GsonConverter(builder.create())\n    register(contentType, converter)\n}\n\ninternal class ExcludedTypeGsonException(\n        val type: KClass<*>\n) : Exception(\"Type ${type.jvmName} is excluded so couldn't be used in receive\"),\n        CopyableThrowable<ExcludedTypeGsonException> {\n\n    override fun createCopy(): ExcludedTypeGsonException? = ExcludedTypeGsonException(type).also {\n        it.initCause(this)\n    }\n}\n\ninternal class UnsupportedNullValuesException :\n        ContentTransformationException(\"Receiving null values is not supported\")\n\nprivate fun Gson.isExcluded(type: KClass<*>) =\n        excluder().excludeClass(type.java, false)\n"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/sqlite/DBManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.sqlite\n\nimport android.content.ContentValues\nimport android.database.Cursor\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.bean.RowFiledInfo\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.bean.TableFieldInfo\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.dao.SQLiteDB\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.factory.DBFactory\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.factory.EncryptDBFactory\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.factory.NormalDBFactory\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.util.DBUtil\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/24-17:23\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject DBManager {\n    val TAG = \"DBHelper\"\n    private val sqliteDBs: MutableMap<String, SQLiteDB> = mutableMapOf()\n\n    private fun openDB(databasePath: String, databaseName: String): SQLiteDB? {\n        var dbFactory: DBFactory = NormalDBFactory()\n        val password: String?\n        if (DoKitManager.DATABASE_PASS.isEmpty()) {\n            password = null\n        } else {\n            password = DoKitManager.DATABASE_PASS[databaseName]\n            password?.let {\n                dbFactory = EncryptDBFactory()\n            }\n        }\n\n        return if (sqliteDBs.containsKey(databasePath)) {\n            sqliteDBs[\"databasePath\"]\n        } else {\n            sqliteDBs[\"databasePath\"] = dbFactory.create(DoKit.APPLICATION.applicationContext, databasePath, password)\n            sqliteDBs[\"databasePath\"]\n        }\n\n    }\n\n    /**\n     * 获取所有的表名\n     */\n    fun getAllTableName(databasePath: String, databaseName: String): List<String> {\n        val openDB = openDB(databasePath, databaseName)\n        val tables = mutableListOf<String>()\n        openDB?.let { db ->\n            val cursor = db.rawQuery(\"SELECT name FROM sqlite_master WHERE type='table' OR type='view' ORDER BY name COLLATE NOCASE\", null)\n            cursor?.let {\n                if (it.moveToFirst()) {\n                    while (!it.isAfterLast) {\n                        val name = it.getString(0)\n                        tables.add(name)\n                        it.moveToNext()\n                    }\n                }\n                it.close()\n            }\n        }\n\n        return tables\n    }\n\n    /**\n     * 获取表中的数据\n     */\n    fun getTableData(databasePath: String, databaseName: String, tableName: String): Map<String, Any> {\n        val openDB = openDB(databasePath, databaseName)\n        val params = mutableMapOf<String, Any>()\n        openDB?.let { db ->\n            val tableFieldInfos = getTableFieldInfos(db, tableName)\n            val tableRows = getTableRows(db, tableName, tableFieldInfos)\n            params[\"fieldInfo\"] = tableFieldInfos\n            params[\"rows\"] = tableRows\n        }\n        return params\n    }\n\n\n    /**\n     * 插入数据\n     */\n    fun insertRow(databasePath: String, databaseName: String, tableName: String, rowDatas: List<RowFiledInfo>): Long {\n        val openDB = openDB(databasePath, databaseName)\n        if (rowDatas.isEmpty()) {\n            return -1\n        }\n        openDB?.let { db ->\n            val contentValues = ContentValues()\n            rowDatas.forEach { rowInfo ->\n                if (rowInfo.value.isNullOrBlank()) {\n                    contentValues.put(rowInfo.title, \"null\")\n                } else {\n                    contentValues.put(rowInfo.title, rowInfo.value)\n                }\n            }\n            return db.insert(\"[$tableName]\", null, contentValues)\n        }\n\n        return -1\n\n    }\n\n\n    /**\n     * 更新数据\n     */\n    fun updateRow(databasePath: String, databaseName: String, tableName: String, rowDatas: List<RowFiledInfo>): Int {\n        val openDB = openDB(databasePath, databaseName)\n        if (rowDatas.isEmpty()) {\n            return -1\n        }\n\n        openDB?.let { db ->\n            val contentValues = ContentValues()\n            var whereClause = \"\"\n            val whereArgList = mutableListOf<String>()\n            rowDatas.forEach { rowInfo ->\n                if (rowInfo.isPrimary) {\n                    if (whereClause.isBlank()) {\n                        whereClause = \"${rowInfo.title} = ? \"\n                    } else {\n                        whereClause = \"$whereClause and ${rowInfo.title} = ? \"\n                    }\n                    whereArgList.add(if (rowInfo.value.isNullOrBlank()) {\n                        \"null\"\n                    } else {\n                        rowInfo.value\n                    })\n                } else {\n                    if (rowInfo.value.isNullOrBlank()) {\n                        contentValues.put(rowInfo.title, \"null\")\n                    } else {\n                        contentValues.put(rowInfo.title, rowInfo.value)\n                    }\n                }\n            }\n            val whereArgs = whereArgList.toTypedArray()\n            return db.update(\"[$tableName]\", contentValues, whereClause, whereArgs)\n\n        }\n\n\n        return -1\n    }\n\n    /**\n     * 删除数据\n     */\n    fun deleteRow(databasePath: String, databaseName: String, tableName: String, rowDatas: List<RowFiledInfo>): Int {\n        val openDB = openDB(databasePath, databaseName)\n        if (rowDatas.isEmpty()) {\n            return -1\n        }\n\n        openDB?.let { db ->\n            var whereClause = \"\"\n            val whereArgList = mutableListOf<String>()\n            rowDatas.forEach { rowInfo ->\n                if (rowInfo.isPrimary) {\n                    if (whereClause.isBlank()) {\n                        whereClause = \"${rowInfo.title} = ? \"\n                    } else {\n                        whereClause = \"$whereClause and ${rowInfo.title} = ? \"\n                    }\n                    whereArgList.add(if (rowInfo.value.isNullOrBlank()) {\n                        \"null\"\n                    } else {\n                        rowInfo.value\n                    })\n                }\n            }\n            val whereArgs = whereArgList.toTypedArray()\n            return db.delete(\"[$tableName]\", whereClause, whereArgs)\n        }\n\n        return -1\n\n    }\n\n    /**\n     * 获取指定表中所有的字段\n     */\n    private fun getTableFieldInfos(openDB: SQLiteDB, tableName: String): List<TableFieldInfo> {\n        val tableFields = mutableListOf<TableFieldInfo>()\n        val pragmaQuery = \"PRAGMA table_info([$tableName])\"\n        val cursor = openDB.rawQuery(pragmaQuery, null)\n        cursor?.let {\n            if (it.count > 0) {\n                it.moveToFirst()\n                do {\n                    val tableFieldInfo = TableFieldInfo(\"\", false)\n                    for (index in 0 until it.columnCount) {\n                        val columnName = it.getColumnName(index)\n                        when (columnName) {\n                            \"name\" -> tableFieldInfo.title = it.getString(index)\n                            \"pk\" -> tableFieldInfo.isPrimary = cursor.getInt(index) == 1\n                        }\n                    }\n                    tableFields.add(tableFieldInfo)\n                } while (it.moveToNext())\n            }\n\n            it.close()\n        }\n\n        return tableFields\n    }\n\n    /**\n     * 获取表中的所有数据\n     */\n    private fun getTableRows(sqLiteDB: SQLiteDB, tableName: String, tableFieldInfos: List<TableFieldInfo>): MutableList<MutableMap<String, String?>> {\n        val cursor = sqLiteDB.rawQuery(\"SELECT * FROM  $tableName\", null)\n        val rows = mutableListOf<MutableMap<String, String?>>()\n        cursor?.let {\n            if (it.count > 0) {\n                it.moveToFirst()\n                do {\n                    val row = mutableMapOf<String, String?>()\n                    for (index in 0 until it.columnCount) {\n                        when (it.getType(index)) {\n                            Cursor.FIELD_TYPE_BLOB -> row[tableFieldInfos[index].title] = DBUtil.blob2String(cursor.getBlob(index))\n                            Cursor.FIELD_TYPE_FLOAT -> row[tableFieldInfos[index].title] = cursor.getDouble(index).toString()\n                            Cursor.FIELD_TYPE_INTEGER -> row[tableFieldInfos[index].title] = cursor.getLong(index).toString()\n                            Cursor.FIELD_TYPE_STRING -> row[tableFieldInfos[index].title] = cursor.getString(index)\n                            else -> row[tableFieldInfos[index].title] = cursor.getString(index)\n                        }\n                    }\n                    rows.add(row)\n                } while (it.moveToNext())\n            }\n            it.close()\n        }\n        return rows\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/sqlite/bean/RowFiledInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.sqlite.bean\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/29-11:07\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class RowFiledInfo(val title: String, val isPrimary: Boolean, val value: String?)"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/sqlite/bean/RowRequestInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.sqlite.bean\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/29-14:31\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class RowRequestInfo(val dirPath: String, val fileName: String, val tableName: String, val rowDatas: List<RowFiledInfo>)"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/sqlite/bean/TableFieldInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.sqlite.bean\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/28-15:06\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class TableFieldInfo(var title: String, var isPrimary: Boolean)"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/sqlite/dao/EncryptSQLiteDB.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.sqlite.dao\n\nimport android.content.ContentValues\nimport android.database.Cursor\nimport com.tencent.wcdb.database.SQLiteDatabase\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/24-16:40\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass EncryptSQLiteDB(private val database: SQLiteDatabase) : SQLiteDB {\n\n    override fun delete(table: String, whereClause: String, whereArgs: Array<String>):Int {\n      return  database.delete(table, whereClause, whereArgs)\n    }\n\n    override fun isOpen(): Boolean {\n        return database.isOpen\n    }\n\n    override fun close() {\n        database.close()\n    }\n\n    override fun rawQuery(sql: String, selectionArgs: Array<String>?): Cursor? {\n        return database.rawQuery(sql, selectionArgs)\n    }\n\n    override fun execSQL(sql: String) {\n        database.execSQL(sql)\n    }\n\n    override fun insert(table: String, nullColumnHack: String?, values: ContentValues): Long {\n        return database.insert(table, nullColumnHack, values)\n    }\n\n    override fun update(table: String, values: ContentValues, whereClause: String, whereArgs: Array<String>): Int {\n        return database.update(table, values, whereClause, whereArgs)\n    }\n\n    override fun getVersion(): Int {\n        return database.version\n    }\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/sqlite/dao/NormalSQLiteDB.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.sqlite.dao\n\nimport android.content.ContentValues\nimport android.database.Cursor\nimport android.database.sqlite.SQLiteDatabase\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/24-16:40\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass NormalSQLiteDB(private val database: SQLiteDatabase) : SQLiteDB {\n\n    override fun delete(table: String, whereClause: String, whereArgs: Array<String>): Int {\n        return database.delete(table, whereClause, whereArgs)\n    }\n\n    override fun isOpen(): Boolean {\n        return database.isOpen\n    }\n\n    override fun close() {\n        database.close()\n    }\n\n    override fun rawQuery(sql: String, selectionArgs: Array<String>?): Cursor? {\n        return database.rawQuery(sql, selectionArgs)\n    }\n\n    override fun execSQL(sql: String) {\n        database.execSQL(sql)\n    }\n\n    override fun insert(table: String, nullColumnHack: String?, values: ContentValues): Long {\n        return database.insert(table, nullColumnHack, values)\n    }\n\n    override fun update(table: String, values: ContentValues, whereClause: String, whereArgs: Array<String>): Int {\n        return database.update(table, values, whereClause, whereArgs)\n    }\n\n    override fun getVersion(): Int {\n        return database.version\n    }\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/sqlite/dao/SQLiteDB.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.sqlite.dao\n\nimport android.content.ContentValues\nimport android.database.Cursor\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/24-15:39\n * 描    述：数据库相关接口\n * 修订历史：\n * ================================================\n */\ninterface SQLiteDB {\n    fun delete(table: String, whereClause: String, whereArgs: Array<String>): Int\n    fun isOpen(): Boolean\n    fun close()\n    fun rawQuery(sql: String, selectionArgs: Array<String>?): Cursor?\n    fun execSQL(sql: String)\n    fun insert(table: String, nullColumnHack: String?, values: ContentValues): Long\n    fun update(table: String, values: ContentValues, whereClause: String, whereArgs: Array<String>): Int\n    fun getVersion(): Int\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/sqlite/factory/DBFactory.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.sqlite.factory\n\nimport android.content.Context\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.dao.SQLiteDB\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/24-16:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\ninterface DBFactory {\n    fun create(context: Context, path: String, password: String?): SQLiteDB\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/sqlite/factory/EncryptDBFactory.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.sqlite.factory\n\nimport android.content.Context\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.dao.EncryptSQLiteDB\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.dao.SQLiteDB\nimport com.tencent.wcdb.database.SQLiteCipherSpec\nimport com.tencent.wcdb.database.SQLiteDatabase\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/24-16:49\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass EncryptDBFactory : DBFactory {\n    override fun create(context: Context, path: String, password: String?): SQLiteDB {\n        return EncryptSQLiteDB(SQLiteDatabase.openOrCreateDatabase(path, password?.toByteArray(), null, null, 1))\n    }\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/sqlite/factory/NormalDBFactory.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.sqlite.factory\n\nimport android.content.Context\nimport android.database.sqlite.SQLiteDatabase\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.dao.NormalSQLiteDB\nimport com.didichuxing.doraemonkit.kit.filemanager.sqlite.dao.SQLiteDB\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/24-16:49\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass NormalDBFactory : DBFactory {\n    override fun create(context: Context, path: String, password: String?): SQLiteDB {\n        return NormalSQLiteDB(SQLiteDatabase.openOrCreateDatabase(path, null))\n    }\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/java/com/didichuxing/doraemonkit/kit/filemanager/sqlite/util/DBUtil.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.sqlite.util\n\nimport kotlin.experimental.and\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/28-19:56\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject DBUtil {\n    private const val MAX_BLOB_LENGTH = 512\n\n    fun blob2String(blob: ByteArray): String {\n        if (blob.size <= MAX_BLOB_LENGTH) {\n            try {\n\n                return String(blob, Charsets.US_ASCII)\n            } catch (e: Exception) {\n                return \"{blob}\"\n            }\n        } else {\n            return \"{blob}\"\n        }\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit-ft/src/main/res/layout/dk_fragment_file_transfer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_file_transfer\" />\n\n\n    <ImageView\n        android:id=\"@+id/iv_banner\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/title_bar\"\n        android:scaleType=\"centerCrop\"\n        android:src=\"@mipmap/dk_file_manager_banner\" />\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_below=\"@id/iv_banner\">\n\n\n        <TextView\n            android:id=\"@+id/tv_tip_top\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentTop=\"true\"\n            android:layout_margin=\"24dp\"\n            android:text=\"请在www.dokit.cn平台端控制台中的【文件同步助手】中使用该功能\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"14sp\" />\n\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerInParent=\"true\"\n            android:gravity=\"center_horizontal\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                android:id=\"@+id/tv_ip\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"192.168.1.100:8089\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"28sp\"\n                android:textStyle=\"bold\" />\n\n            <TextView\n                android:id=\"@+id/tv_tip_bottom\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"12dp\"\n                android:text=\"@string/dk_file_manager_tip_bottom\"\n                android:textColor=\"@color/dk_color_999999\"\n                android:textSize=\"14sp\" />\n        </LinearLayout>\n    </RelativeLayout>\n\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit-ft/upload.sh",
    "content": "#!/usr/bin/env bash\necho -n  \"please enter bintray userid ->\"\nread  userid_bintray\necho -n  \"please enter bintray apikey ->\"\nread  apikey_bintray\n../gradlew clean build --stacktrace  --info   bintrayUpload -PbintrayUser=${userid_bintray} -PbintrayKey=${apikey_bintray} -PdryRun=false"
  },
  {
    "path": "Android/dokit-gps-mock/.gitignore",
    "content": "/build"
  },
  {
    "path": "Android/dokit-gps-mock/build.gradle",
    "content": "plugins {\n    id 'com.android.library'\n}\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-kapt'\napply from: '../upload.gradle'\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_16\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        consumerProguardFiles \"consumer-rules.pro\"\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n\n    sourceSets {\n        debug {\n            jniLibs.srcDir 'libs'\n        }\n\n        release {\n            jniLibs.srcDir 'libs'\n        }\n    }\n}\n\ndependencies {\n\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    implementation 'androidx.appcompat:appcompat:1.3.0'\n    implementation 'com.google.android.material:material:1.4.0'\n    testImplementation 'junit:junit:4.13.2'\n    androidTestImplementation 'androidx.test.ext:junit:1.1.3'\n    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'\n\n    implementation rootProject.ext.dependencies[\"core-ktx\"]\n    implementation rootProject.ext.dependencies[\"activity-ktx\"]\n\n    if (needKotlinV14()) {\n        implementation rootProject.ext.dependencies[\"kotlin_v14\"]\n        implementation rootProject.ext.dependencies[\"coroutines-core_v14\"]\n    } else {\n        implementation rootProject.ext.dependencies[\"kotlin_v13\"]\n        implementation rootProject.ext.dependencies[\"coroutines-core_v13\"]\n    }\n\n    implementation project(':dokit-util')\n    implementation project(':dokit')\n    //auto-service\n    implementation rootProject.ext.dependencies[\"auto_service\"]\n    kapt rootProject.ext.dependencies[\"auto_service\"]\n\n    api files('libs/BaiduLBS_Android.jar')\n\n    //高德导航\n    compileOnly rootProject.ext.dependencies[\"amap_navi\"]\n    compileOnly rootProject.ext.dependencies[\"amap_location\"]\n    //腾讯地图定位\n    compileOnly rootProject.ext.dependencies[\"tencent_location\"]\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "Android/dokit-gps-mock/gradle.properties",
    "content": "ARTIFACT_ID=dokitx-gps-mock\n"
  },
  {
    "path": "Android/dokit-gps-mock/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "Android/dokit-gps-mock/src/androidTest/java/com/didichuxing/doraemonkit/ExampleInstrumentedTest.java",
    "content": "package com.didichuxing.doraemonkit;\n\nimport android.content.Context;\n\nimport androidx.test.platform.app.InstrumentationRegistry;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n    @Test\n    public void useAppContext() {\n        // Context of the app under test.\n        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();\n        assertEquals(\"com.didichuxing.doraemonkit.test\", appContext.getPackageName());\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.didichuxing.doraemonkit.gps_mock\">\n\n\n</manifest>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/ability/DokitFtAbility.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.ability\n\nimport com.didichuxing.doraemonkit.constant.DoKitModule\nimport com.didichuxing.doraemonkit.kit.core.DokitAbility\nimport com.google.auto.service.AutoService\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/7-19:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\n@AutoService(DokitAbility::class)\nclass DokitGpsMockAbility : DokitAbility {\n    override fun init() {\n\n    }\n\n    override fun moduleName(): DoKitModule {\n        return DoKitModule.MODULE_GPS_MOCK\n    }\n\n    override fun getModuleProcessor(): DokitAbility.DokitModuleProcessor {\n        return DokitGpsMockModuleProcessor()\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/ability/DokitFtModuleProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.filemanager.ability\n\nimport com.baidu.mapapi.SDKInitializer\nimport com.didichuxing.doraemonkit.DoKitEnv.app\nimport com.didichuxing.doraemonkit.config.GpsMockConfig\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockManager\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.ServiceHookManager\nimport com.didichuxing.doraemonkit.kit.core.DokitAbility\nimport java.lang.Exception\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/7-19:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DokitGpsMockModuleProcessor : DokitAbility.DokitModuleProcessor {\n    override fun values(): Map<String, Any> {\n        return mapOf()\n    }\n\n    override  fun proceed(actions: Map<String, Any?>?): Map<String, Any> {\n        try {\n            actions?.let {\n                when (actions[\"action\"]) {\n                    \"init_gps_mock\" -> {\n                        if (GpsMockConfig.isGPSMockOpen()) {\n                            GpsMockManager.getInstance().startMock()\n                        }\n                        val latLng = GpsMockConfig.getMockLocation()\n                        latLng?.let{it2 -> GpsMockManager.getInstance().mockLocationWithNotify(it2.latitude, it2.longitude)}\n                        // 在Application里进行初始化,否则在使用百度SDK的接口时,会报so库链接错误.\n                        SDKInitializer.initialize(app)\n                        //Hook WIFI GPS Telephony系统服务\n                        app?.let { it1 -> ServiceHookManager.install(it1.applicationContext) }\n                    }\n                    else -> {\n\n                    }\n                }\n            }\n        }catch (e:Exception){\n            e.printStackTrace()\n        }\n\n\n        return mapOf()\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/common/BdMapRouteData.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.common;\n\nimport com.baidu.mapapi.model.LatLng;\nimport com.baidu.mapapi.search.core.RouteNode;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class BdMapRouteData {\n    private final List<LatLng> mAllPoints = new ArrayList<>();\n    // 单位米\n    private int mTotalDistance;\n    private RouteNode mStartNode;\n    private RouteNode mTerminalNode;\n\n    // 路线数据是否来自业务方的路径规划\n    private boolean mRouteDataFromBiz = true;\n\n    List<com.baidu.mapapi.model.LatLng> mRandomDriftPoints = new ArrayList<>();\n    List<com.baidu.mapapi.model.LatLng> mRouteDriftPoints = new ArrayList<>();\n    List<com.baidu.mapapi.model.LatLng> mRouteLostLocPoints = new ArrayList<>();\n\n    private int mRandomDriftDistance;\n    private int mRouteDriftDistance;\n\n    public com.baidu.mapapi.model.LatLng mOriginRouteStartLostPoint;\n    public com.baidu.mapapi.model.LatLng mOriginRouteEndLostPoint;\n    public com.baidu.mapapi.model.LatLng mRandomDriftStartLostPoint;\n    public com.baidu.mapapi.model.LatLng mRandomDriftEndLostPoint;\n    public com.baidu.mapapi.model.LatLng mRouteDriftStartLostPoint;\n    public com.baidu.mapapi.model.LatLng mRouteDriftEndLostPoint;\n\n    public List<LatLng> getAllPoints() {\n        return mAllPoints;\n    }\n\n    public void setAllPoints(List<LatLng> allPoints) {\n        mAllPoints.clear();\n        mAllPoints.addAll(allPoints);\n    }\n\n    public int getTotalDistance() {\n        return mTotalDistance;\n    }\n\n    public void setTotalDistance(int totalDistance) {\n        mTotalDistance = totalDistance;\n    }\n\n    public RouteNode getStartNode() {\n        return mStartNode;\n    }\n\n    public void setStartNode(RouteNode startNode) {\n        mStartNode = startNode;\n    }\n\n    public RouteNode getTerminalNode() {\n        return mTerminalNode;\n    }\n\n    public void setTerminalNode(RouteNode terminalNode) {\n        mTerminalNode = terminalNode;\n    }\n\n    public List<LatLng> getRandomDriftPoints() {\n        return mRandomDriftPoints;\n    }\n\n    public void setRandomDriftPoints(List<LatLng> randomDriftPoints) {\n        mRandomDriftPoints.clear();\n        mRandomDriftPoints.addAll(randomDriftPoints);\n    }\n\n    public List<LatLng> getRouteDriftPoints() {\n        return mRouteDriftPoints;\n    }\n\n    public void setRouteDriftPoints(List<LatLng> routeDriftPoints) {\n        mRouteDriftPoints.clear();\n        mRouteDriftPoints.addAll(routeDriftPoints);\n    }\n\n\n    public List<LatLng> getOriginRouteLostLocPoints() {\n        return mRouteLostLocPoints;\n    }\n\n    public void setOriginRouteLostLocPoints(List<LatLng> routeLostLocPoints) {\n        mRouteLostLocPoints.clear();\n        mRouteLostLocPoints.addAll(routeLostLocPoints);\n    }\n\n    public boolean isRouteDataFromBiz() {\n        return mRouteDataFromBiz;\n    }\n\n    public void setRouteDataFromBiz(boolean routeDataFromBiz) {\n        mRouteDataFromBiz = routeDataFromBiz;\n    }\n\n    public int getRandomDriftDistance() {\n        return mRandomDriftDistance;\n    }\n\n    public void setRandomDriftDistance(int randomDriftDistance) {\n        mRandomDriftDistance = randomDriftDistance;\n    }\n\n    public int getRouteDriftDistance() {\n        return mRouteDriftDistance;\n    }\n\n    public void setRouteDriftDistance(int routeDriftDistance) {\n        mRouteDriftDistance = routeDriftDistance;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/common/Utils.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.common;\n\nimport android.animation.Animator;\nimport android.animation.ObjectAnimator;\nimport android.animation.ValueAnimator;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.baidu.mapapi.model.LatLng;\nimport com.baidu.mapapi.utils.DistanceUtil;\n\nimport java.lang.reflect.Field;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Random;\n\npublic class Utils {\n    private static final double PI = 3.14159265;\n\n    public static ValueAnimator createDropAnimator(final View v, int start, int end) {\n        ValueAnimator animator = ValueAnimator.ofInt(start, end);\n        animator.addUpdateListener(arg0 -> {\n            int value = (int) arg0.getAnimatedValue();\n            ViewGroup.LayoutParams layoutParams = v.getLayoutParams();\n            layoutParams.height = value;\n            v.setLayoutParams(layoutParams);\n        });\n        return animator;\n    }\n\n    public static Animator createAlphaAnimator(View view, float start, float end) {\n        ObjectAnimator animator = ObjectAnimator.ofFloat(view, \"alpha\", start, end);\n        return animator;\n    }\n\n    public static Animator createRotateAnimator(View view, float start, float end) {\n        ObjectAnimator animator = ObjectAnimator.ofFloat(view, \"rotation\", start, end);\n        animator.setDuration(300);\n        return animator;\n    }\n\n\n    /**\n     * 功能：通过反射 设置指定类对象中的 指定属性的值\n     *\n     * @param obj           类对象\n     * @param propertyName  要设置的属性名\n     * @param propertyvalue 要设置的属性的值\n     */\n    public static void set(Object obj, String propertyName, Object propertyvalue) {\n        try {\n            // step1 获取属性指针\n            Field declaredField = obj.getClass().getDeclaredField(propertyName);\n            // step2 设置属性可访问\n            declaredField.setAccessible(true);\n            // step3 设置属性的值\n            declaredField.set(obj, propertyvalue);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * @param radius 单位米\n     * @return minLat, minLng, maxLat, maxLng\n     */\n    public static double[] getAround(double centerLat, double centerLng, double radius) {\n        double degree = (24901 * 1609) / 360.0;\n\n        double dpmLat = 1 / degree;\n        double radiusLat = dpmLat * radius;\n        double minLat = centerLat - radiusLat;\n        double maxLat = centerLat + radiusLat;\n\n        double mpdLng = degree * Math.cos(centerLat * (PI / 180));\n        double dpmLng = 1 / mpdLng;\n        double radiusLng = dpmLng * radius;\n        double minLng = centerLng - radiusLng;\n        double maxLng = centerLng + radiusLng;\n        Log.d(\"range_lat_lng\", \"[\" + minLat + \",\" + minLng + \",\" + maxLat + \",\" + maxLng + \"]\");\n        return new double[]{minLat, minLng, maxLat, maxLng};\n    }\n\n    public static double getDistance(LatLng startLagLng, LatLng desLagLng) {\n        double distance = DistanceUtil.getDistance(startLagLng, desLagLng);\n        Log.d(\"distance_between\", \"startLagLng=\" + startLagLng.toString() + \" desLagLng=\" + desLagLng.toString() + \" distance=\" + distance);\n        return distance;\n    }\n\n    /**\n     * 获取一段路径的总路程.\n     */\n    public static double getRouteDistance(List<LatLng> points) {\n        if (points == null || points.size() <= 0) return 0;\n        double distance = 0;\n        for (int i = 0; i + 1 < points.size(); i++) {\n            distance += getDistance(points.get(i), points.get(i + 1));\n        }\n        return distance;\n    }\n\n    /**\n     * 获取随机坐标点:根据给定的圆心,半径和随机生成的目标纬度坐标, 获取随机的经度坐标\n     */\n    public static double[] getRandomLatLng(double latitude, double longitude, double radius, double[] rangeAround) {\n        // 获取随机纬度坐标\n        double randomLat = rangeAround[0] + (rangeAround[2] - rangeAround[0]) * Math.random();\n\n        // 直角三角形一条直角边长度\n        double y = getDistance(new LatLng(randomLat, longitude), new LatLng(latitude, longitude));\n        // 计算直接三角形另一条直角边长度\n        double x = Math.sqrt(Math.pow(radius, 2) - Math.pow(y, 2));\n        // 根据x长度换算出对应经度值\n        double differLng = (x / radius) * ((rangeAround[3] - rangeAround[1]) / 2);\n        Random random = new Random();\n        // 生成对应的随机经度坐标\n        double randomLng = longitude + (random.nextBoolean() ? differLng : -differLng);\n\n        return new double[]{randomLat, randomLng};\n    }\n\n    /**\n     * 获取纬度漂移偏移量\n     */\n    public static double getOrientLatDiffer(double[] rangeAround) {\n        return ((rangeAround[2] - rangeAround[0]) / 2) * Math.random();\n    }\n\n    /**\n     * 获取定向坐标点(给路径漂移制造漂移点)\n     */\n    public static double[] getOrientationLatLng(double centerLat, double centerLng, double radius,  double[] rangeAround, double latDiffer) {\n        double destRandomLat = centerLat + latDiffer;\n\n        // 直角三角形一条直角边长度\n        double y = getDistance(new LatLng(destRandomLat, centerLng), new LatLng(centerLat, centerLng));\n        // 计算直接三角形另一条直角边长度\n        double x = Math.sqrt(Math.pow(radius, 2) - Math.pow(y, 2));\n        // 根据x长度换算出对应经度值\n        double differLng = (x / radius) * ((rangeAround[3] - rangeAround[1]) / 2);\n        // 生成对应的随机经度坐标(默认中心点坐标 + 偏移值)\n        double randomLng = centerLng + differLng;\n\n        return new double[]{destRandomLat, randomLng};\n    }\n\n    public static void randomSet(int min, int max, int n, HashSet<Integer> set) {\n        if (n > (max - min + 1) || max < min) {\n            return;\n        }\n        for (int i = 0; i < n; i++) {\n            // 调用Math.random()方法\n            int num = (int) (Math.random() * (max - min)) + min;\n            set.add(num);// 将不同的数存入HashSet中\n        }\n        int setSize = set.size();\n        // 如果存入的数小于指定生成的个数，则调用递归再生成剩余个数的随机数，如此循环，直到达到指定大小\n        if (setSize < n) {\n            randomSet(min, max, n - setSize, set);// 递归\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/BaseServiceHooker.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock\n\nimport android.content.Context\nimport android.os.IBinder\nimport android.os.IInterface\nimport com.didichuxing.doraemonkit.util.ReflectUtils\nimport java.lang.reflect.InvocationHandler\nimport java.lang.reflect.InvocationTargetException\nimport java.lang.reflect.Method\n\n/**\n * Created by wanglikun on 2019/4/2\n */\nabstract class BaseServiceHooker : InvocationHandler {\n\n\n    var TAG = this.javaClass.simpleName\n\n    private lateinit var mHookMethods: Map<String, MethodHandler>\n\n\n    fun init() {\n        mHookMethods = registerMethodHandlers()\n    }\n\n    /**\n     * 本地Binder对象(同进程)或远程Binder的代理对象(跨进程)\n     */\n    var mBinderStubProxy: IInterface? = null\n\n    abstract fun serviceName(): String\n\n    /**\n     * 用来初始化mBinderStubProxy\n     *\n     * @return\n     */\n    abstract fun stubName(): String\n\n    abstract fun registerMethodHandlers(): Map<String, MethodHandler>\n\n    @Throws(\n        NoSuchFieldException::class,\n        IllegalAccessException::class,\n        ClassNotFoundException::class,\n        NoSuchMethodException::class,\n        InvocationTargetException::class\n    )\n    abstract fun replaceBinderProxy(context: Context, proxy: IBinder)\n\n    /**\n     * @param proxy  就是代理对象，newProxyInstance方法的返回对象\n     * @param method 指代的是我们所要调用真实对象的某个方法的Method对象\n     * @param args   指代的是调用真实对象某个方法时接受的参数\n     * @return\n     * @throws InvocationTargetException\n     * @throws IllegalAccessException\n     * @throws NoSuchFieldException\n     * @throws NoSuchMethodException\n     */\n    @Throws(\n        InvocationTargetException::class,\n        IllegalAccessException::class,\n        NoSuchFieldException::class,\n        NoSuchMethodException::class\n    )\n    override fun invoke(proxy: Any, method: Method, args: Array<Any>?): Any? {\n        if (mBinderStubProxy == null) {\n            return null\n        } else try {\n            //判断要拦截的方法是否实现已注册\n            if (mHookMethods.containsKey(method.name) && mHookMethods[method.name] != null) {\n                return mHookMethods[method.name]?.onInvoke(mBinderStubProxy!!, method, args)\n            } else {\n                return if (args == null) {\n                    method.invoke(mBinderStubProxy, null)\n                } else {\n                    method.invoke(mBinderStubProxy, *args)\n                }\n            }\n        } catch (e: Exception) {\n            return null\n        }\n    }\n\n    /**\n     * 获得 native Binder  proxy\n     *\n     * @param binder\n     */\n    fun asInterface(binder: IBinder?) {\n        try {\n            //IInterface 包含了IBinder的proxy 并实现相应的接口能力\n            mBinderStubProxy =\n                ReflectUtils.reflect(stubName()).method(\"asInterface\", binder).get<IInterface>()\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/BinderHookHandler.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock;\n\nimport android.annotation.SuppressLint;\nimport android.os.IBinder;\nimport android.os.IInterface;\n\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Proxy;\n\n/**\n * Created by wanglikun on 2018/12/18.\n * https://zhuanlan.zhihu.com/p/60805342\n * 嵌套动态代理\n */\npublic class BinderHookHandler implements InvocationHandler {\n    private static final String TAG = \"BinderHookHandler\";\n    final private IBinder mBinderProxy;\n    final private BaseServiceHooker mHooker;\n\n    public BinderHookHandler(IBinder binder, BaseServiceHooker hooker) {\n        this.mBinderProxy = binder;\n        this.mHooker = hooker;\n    }\n\n    @Override\n    @SuppressLint(\"PrivateApi\")\n    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {\n\n        // 这里直接返回真正被Hook掉的Service接口\n        // 这里的 queryLocalInterface 就不是原本的意思了\n        // 我们肯定不会真的返回一个本地接口, 因为我们接管了 asInterface方法的作用\n        // 因此必须是一个完整的 asInterface 过的 IInterface对象, 既要处理本地对象,也要处理代理对象\n        // 这只是一个Hook点而已, 它原始的含义已经被我们重定义了; 因为我们会永远确保这个方法不返回null\n        // 让 asInterface 永远走到if语句的else分支里面\n        if (\"queryLocalInterface\".equals(method.getName())) {\n            try {\n                Class iinterface = Class.forName(String.valueOf(args[0]));\n                //LogHelper.i(TAG, \"BinderHookHandler==iinterface==>\" + iinterface);\n                ClassLoader classLoader = mBinderProxy.getClass().getClassLoader();\n                // asInterface 的时候会检测是否是特定类型的接口然后进行强制转换\n                // 因此这里的动态代理生成的类型信息的类型必须是正确的\n                Class[] interfaces = new Class[]{IInterface.class, IBinder.class, iinterface};\n                //返回被动态代理后的IBinder对象\n                return Proxy.newProxyInstance(classLoader, interfaces, mHooker);\n            } catch (Exception e) {\n                //e.printStackTrace();\n                return method.invoke(mBinderProxy, args);\n            }\n        }\n        return method.invoke(mBinderProxy, args);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/CordTransformUtil.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock;\n\npublic class CordTransformUtil {\n    static double x_PI = 3.14159265358979324 * 3000.0 / 180.0;\n    static double PI = 3.1415926535897932384626;\n    static double a = 6378245.0;\n    static double ee = 0.00669342162296594323;\n\n    /**\n     * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换    * 即谷歌、高德 转 百度    * @param lng    * @param lat    * @returns {*[]}\n     */\n    public static Point gcj02tobd09(double lat, double lng) {\n        double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI);\n        double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI);\n        double bd_lng = z * Math.cos(theta) + 0.0065;\n        double bd_lat = z * Math.sin(theta) + 0.006;\n        return new Point(bd_lat, bd_lng);\n    }\n\n    /**\n     * WGS84转GCj02    * @param lng    * @param lat    * @returns {*[]}\n     */\n    public static Point wgs84togcj02(double lat, double lng) {\n        double dlat = transformlat(lat - 35.0, lng - 105.0);\n        double dlng = transformlng(lat - 35.0, lng - 105.0);\n        double radlat = lat / 180.0 * PI;\n        double magic = Math.sin(radlat);\n        magic = 1 - ee * magic * magic;\n        double sqrtmagic = Math.sqrt(magic);\n        dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);\n        dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);\n        double mglat = lat + dlat;\n        double mglng = lng + dlng;\n        return new Point(mglat, mglng);\n    }\n\n    /**\n     * GCJ02 转换为 WGS84    * @param lng    * @param lat    * @returns {*[]}\n     */\n    public static Point gcj02towgs84(double lat, double lng) {\n        double dlat = transformlat(lat - 35.0, lng - 105.0);\n        double dlng = transformlng(lat - 35.0, lng - 105.0);\n        double radlat = lat / 180.0 * PI;\n        double magic = Math.sin(radlat);\n        magic = 1 - ee * magic * magic;\n        double sqrtmagic = Math.sqrt(magic);\n        dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);\n        dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);\n        double mglat = lat + dlat;\n        double mglng = lng + dlng;      // Point point=new Point(mglng, mglat);      // return point;\n        return new Point(mglat, mglng);\n    }\n\n    ;\n\n    /**\n     * WGS84 转换为 BD-09    * @param lng    * @param lat    * @returns {*[]}    *\n     */\n    public static Point wgs84tobd09(double lat, double lng) {\n        //第一次转换\n        double dlat = transformlat(lat - 35.0, lng - 105.0);\n        double dlng = transformlng(lat - 35.0, lng - 105.0);\n        double radlat = lat / 180.0 * PI;\n        double magic = Math.sin(radlat);\n        magic = 1 - ee * magic * magic;\n        double sqrtmagic = Math.sqrt(magic);\n        dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);\n        dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);\n        double mglat = lat + dlat;\n        double mglng = lng + dlng;\n        //第二次转换\n        double z = Math.sqrt(mglng * mglng + mglat * mglat) + 0.00002 * Math.sin(mglat * x_PI);\n        double theta = Math.atan2(mglat, mglng) + 0.000003 * Math.cos(mglng * x_PI);\n        double bd_lng = z * Math.cos(theta) + 0.0065;\n        double bd_lat = z * Math.sin(theta) + 0.006;\n        return new Point(bd_lat, bd_lng);\n    }\n\n    ;\n\n    /**\n     * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换    * 即 百度 转 谷歌、高德    * @param bd_lon    * @param bd_lat    * @returns {*[]}\n     */\n    public Point bd09togcj02(double bd_lat, double bd_lon) {\n        double x = bd_lon - 0.0065;\n        double y = bd_lat - 0.006;\n        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI);\n        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI);\n        double gg_lng = z * Math.cos(theta);\n        double gg_lat = z * Math.sin(theta);\n        return new Point(gg_lat, gg_lng);\n    }\n\n    private static double transformlat(double lat, double lng) {\n        double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));\n        ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;\n        ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;\n        ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;\n        return ret;\n    }\n\n    ;\n\n    private static double transformlng(double lat, double lng) {\n        double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));\n        ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;\n        ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;\n        ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;\n        return ret;\n    }\n\n    public static void main(String[] args) {    //两次谷歌转换为百度坐标     //第一次  WGS84转GCj02\n        String lnglat = wgs84togcj02(31.841652709281103, 117.20296517261839).toString();\n        double lng = Double.parseDouble(lnglat.split(\",\")[0]);\n        double lat = Double.parseDouble(lnglat.split(\",\")[1]);\n        System.out.println(\"第一次转换的结果:\" + lng + \",\" + lat);    //  第二次 gcj02tobd09\n        System.out.println(\"第二次转换的结果:\" + gcj02tobd09(lat, lng));\n//谷歌转百度一次转换\n        System.out.println(\"谷歌转换为百度一次转换的结果:\" + wgs84tobd09(31.841652709281103, 117.20296517261839));\n    }\n\n    public static class Point {\n        double mLat, mLng;\n\n        @Override\n        public String toString() {\n            return \"纬度: \" + mLat + \", 经度: \" + mLng;\n        }\n\n        public Point(double lat, double lng) {\n            this.mLat = lat;\n            this.mLng = lng;\n        }\n\n        public double getLatitude() {\n            return mLat;\n        }\n\n        public Point setLatitude(double lat) {\n            this.mLat = lat;\n            return this;\n        }\n\n        public double getLongitude() {\n            return mLng;\n        }\n\n        public Point setLongitude(double lng) {\n            this.mLng = lng;\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/GpsMockFragment.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimport android.animation.AnimatorSet;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.widget.AdapterView;\nimport android.widget.ArrayAdapter;\nimport android.widget.Button;\nimport android.widget.CheckBox;\nimport android.widget.CompoundButton;\nimport android.widget.EditText;\nimport android.widget.ImageView;\nimport android.widget.Spinner;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.annotation.IdRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.constraintlayout.motion.widget.MotionLayout;\nimport androidx.constraintlayout.widget.Group;\n\nimport com.baidu.location.BDAbstractLocationListener;\nimport com.baidu.location.BDLocation;\nimport com.baidu.location.LocationClient;\nimport com.baidu.location.LocationClientOption;\nimport com.baidu.mapapi.SDKInitializer;\nimport com.baidu.mapapi.map.BaiduMap;\nimport com.baidu.mapapi.map.MapStatus;\nimport com.baidu.mapapi.map.MapStatusUpdateFactory;\nimport com.baidu.mapapi.map.MapView;\nimport com.baidu.mapapi.map.MyLocationConfiguration;\nimport com.baidu.mapapi.search.core.PoiInfo;\nimport com.baidu.mapapi.search.core.RouteNode;\nimport com.baidu.mapapi.search.core.SearchResult;\nimport com.baidu.mapapi.search.geocode.GeoCodeResult;\nimport com.baidu.mapapi.search.geocode.GeoCoder;\nimport com.baidu.mapapi.search.geocode.OnGetGeoCoderResultListener;\nimport com.baidu.mapapi.search.geocode.ReverseGeoCodeOption;\nimport com.baidu.mapapi.search.geocode.ReverseGeoCodeResult;\nimport com.baidu.mapapi.search.route.BikingRouteResult;\nimport com.baidu.mapapi.search.route.DrivingRouteLine;\nimport com.baidu.mapapi.search.route.DrivingRoutePlanOption;\nimport com.baidu.mapapi.search.route.DrivingRouteResult;\nimport com.baidu.mapapi.search.route.IndoorRouteResult;\nimport com.baidu.mapapi.search.route.MassTransitRouteResult;\nimport com.baidu.mapapi.search.route.OnGetRoutePlanResultListener;\nimport com.baidu.mapapi.search.route.PlanNode;\nimport com.baidu.mapapi.search.route.RoutePlanSearch;\nimport com.baidu.mapapi.search.route.TransitRouteResult;\nimport com.baidu.mapapi.search.route.WalkingRouteResult;\nimport com.baidu.mapapi.search.sug.SuggestionResult;\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.config.GpsMockConfig;\nimport com.didichuxing.doraemonkit.gps_mock.R;\nimport com.didichuxing.doraemonkit.gps_mock.common.BdMapRouteData;\nimport com.didichuxing.doraemonkit.gps_mock.common.Utils;\nimport com.didichuxing.doraemonkit.gps_mock.widget.CustomDialogFragment;\nimport com.didichuxing.doraemonkit.gps_mock.widget.DrivingRouteOverlay;\nimport com.didichuxing.doraemonkit.gps_mock.widget.OverlayManager;\nimport com.didichuxing.doraemonkit.gps_mock.widget.PositionSelectDialogHelper;\nimport com.didichuxing.doraemonkit.gps_mock.widget.PositionSelectRecyclerAdapter;\nimport com.didichuxing.doraemonkit.gps_mock.widget.RouteMockDokitView;\nimport com.didichuxing.doraemonkit.gps_mock.widget.SeekRangeBar;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.model.LatLng;\nimport com.didichuxing.doraemonkit.util.ConvertUtils;\nimport com.didichuxing.doraemonkit.util.LogHelper;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\n\nimport java.math.BigDecimal;\nimport java.util.List;\n\n/**\n * Created by wanglikun on 2018/9/20.\n * gps mock\n */\n\npublic class GpsMockFragment extends BaseFragment implements View.OnClickListener, PositionSelectRecyclerAdapter.IPositionItemSelectedCallback\n    , CompoundButton.OnCheckedChangeListener, MotionLayout.TransitionListener, BaiduMap.OnMapStatusChangeListener, OnGetGeoCoderResultListener\n    , OnGetRoutePlanResultListener, RouteMockThread.RouteMockStatusCallback {\n    private static final String TAG = \"GpsMockFragment\";\n\n    private HomeTitleBar mTitleBar;\n\n    private MotionLayout mRootView;\n    private MapView mMapView;\n    private BaiduMap mBdMapView;\n    private ImageView mIvMapCenterLoc;\n\n    // 位置模拟\n    private CheckBox mCbTogglePosMock;\n    private EditText mEdtInputPos;\n    private Button mBtnMockPos;\n\n    // 轨迹模拟\n    private CheckBox mCbToggleRouteMock;\n    private TextView mTvRouteStart;\n    private TextView mTvRouteEnd;\n    private EditText mEdtRouteSpeed;\n    private Button mBtnMockRoute1;\n\n    // 轨迹漂移\n    private CheckBox mCbToggleRouteDriftMock;\n    private View mDriftSettingLayout;\n    private Spinner mSpinnerDriftType;\n    private EditText mEdtDriftAccuracy;\n    private Spinner mSpinnerDriftMode;\n    private CheckBox mCbOverPass;\n    private CheckBox mCbTunnel;\n    private SeekRangeBar mSeekBar;\n    private Group mGroupSelectPath;\n    private Group mGroupSelectAutoPath;\n    private CheckBox mCbDriftLostLoc;\n    private SeekRangeBar mDriftLostLocSeekBar;\n    private Button mBtnMockRoute2;\n    private ImageView mIvDownExpand;\n    private TextView mTvOriginDistance;\n    private TextView mTvMockDistance;\n\n    private CustomDialogFragment mCustomDialogFragment;\n    private @IdRes\n    int mCurPosViewId;\n    private AnimatorSet mShowDriftSettingAnim;\n    private AnimatorSet mHideDriftSettingAnim;\n    private final int mDriftSettingLayoutH = ConvertUtils.dp2px(150);\n    private DrivingRouteOverlay mDrivingRouteOverlay;\n\n    private int mCurDriftTypeIndex = 0;\n    private int mCurDriftModeIndex = 0;\n\n    private final RouteNode mRouteStartNode = new RouteNode();\n    private final RouteNode mRouteEndNode = new RouteNode();\n\n    /**\n     * 反地理编码\n     */\n    private GeoCoder mGeoCoder;\n    private boolean mInitRouteStart = false;\n    private boolean mInitRouteEnd = false;\n    private float mCurZoomLevel = 18;\n\n    private static final String LOC_EDT_SPLIT_REG = \",\";\n    private LocationClient mBdLocationClient;\n    private final BDAbstractLocationListener mBDAbstractLocationListener = new BDAbstractLocationListener() {\n        @Override\n        public void onReceiveLocation(BDLocation bdLocation) {\n            LogHelper.d(TAG, \"onReceiveLocation latitude=\" + bdLocation.getLatitude() + \" longitude=\" + bdLocation.getLongitude());\n            // 接收到业务方的mock数据,并进行mock\n            if (checkPosMockToggle() || (checkRouteMockToggle() && GpsMockManager.getInstance().isMockingRoute())) {\n                // 定位到输入框的坐标点所在的地图位置\n                moveToLoc(bdLocation.getLatitude(), bdLocation.getLongitude(), mCurZoomLevel);\n                // 此时地图会移动到该位置作为地图中心,但不显示自带的定位图标, 且不会触发地图改变的回调onMapStatusChangeFinish.\n                // mBdMapView.setMapStatus(MapStatusUpdateFactory.newLatLngZoom(new com.baidu.mapapi.model.LatLng(bdLocation.getLatitude(), bdLocation.getLongitude()), mCurZoomLevel));\n                LogHelper.d(TAG, \"onReceiveLocation111 latitude=\" + bdLocation.getLatitude() + \" longitude=\" + bdLocation.getLongitude());\n            }\n        }\n    };\n    private RoutePlanSearch mRoutePlanSearch;\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        SDKInitializer.initialize(getActivity().getApplicationContext());\n        super.onCreate(savedInstanceState);\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        if (view instanceof MotionLayout) {\n            mRootView = (MotionLayout) view;\n        }\n        initView();\n        initData();\n    }\n\n    private void initView() {\n        mTitleBar = findViewById(R.id.title_bar);\n        mTitleBar.setListener(() -> finish());\n\n        mMapView = findViewById(R.id.map_view);\n        mBdMapView = mMapView.getMap();\n        mIvMapCenterLoc = findViewById(R.id.iv_map_center_loc);\n        initBdLocAndMap();\n\n        mCbTogglePosMock = findViewById(R.id.cb_toggle_pos_mock);\n        mEdtInputPos = findViewById(R.id.edt_input_pos);\n        mBtnMockPos = findViewById(R.id.btn_mock_pos);\n\n        mCbToggleRouteMock = findViewById(R.id.cb_toggle_route_mock);\n        mTvRouteStart = findViewById(R.id.tv_route_start);\n        mTvRouteEnd = findViewById(R.id.tv_route_end);\n        mEdtRouteSpeed = findViewById(R.id.edt_route_speed);\n        mBtnMockRoute1 = findViewById(R.id.btn_mock_route1);\n\n        mCbToggleRouteDriftMock = findViewById(R.id.cb_toggle_route_drift_mock);\n        mDriftSettingLayout = findViewById(R.id.drift_mock_set_layout);\n        mSpinnerDriftType = findViewById(R.id.spinner_drift_type);\n        mEdtDriftAccuracy = findViewById(R.id.edt_drift_accuracy);\n        mSpinnerDriftMode = findViewById(R.id.spinner_drift_mode);\n        mCbOverPass = findViewById(R.id.cb_over_pass);\n        mCbTunnel = findViewById(R.id.cb_tunnel);\n        mSeekBar = findViewById(R.id.seekbar_select_path);\n        mGroupSelectPath = findViewById(R.id.group_select_path);\n        mGroupSelectAutoPath = findViewById(R.id.group_select_auto_path);\n        mCbDriftLostLoc = findViewById(R.id.cb_toggle_route_lost_loc);\n        mDriftLostLocSeekBar = findViewById(R.id.seekbar_select_lost_path);\n        mBtnMockRoute2 = findViewById(R.id.btn_mock_route2);\n        mTvOriginDistance = findViewById(R.id.tv_real_distance);\n        mTvMockDistance = findViewById(R.id.tv_mock_distance);\n        mIvDownExpand = findViewById(R.id.iv_down_expand);\n        mSeekBar.setProgressLow(GpsMockConfig.getSeekBarLow());\n        mSeekBar.setProgressHigh(GpsMockConfig.getSeekBarHigh());\n        initSpinner();\n\n        LogHelper.d(TAG, \"initView: \" + GpsMockConfig.isGPSMockOpen() + \" \" + GpsMockConfig.isPosMockOpen() + \" \" + GpsMockConfig.isRouteMockOpen() + \" \" + GpsMockConfig.isRouteDriftMockOpen());\n        mBtnMockPos.setOnClickListener(this);\n        mCbTogglePosMock.setOnCheckedChangeListener(this);\n        mCbTogglePosMock.setChecked(GpsMockConfig.isPosMockOpen());\n\n        mTvRouteStart.setOnClickListener(this);\n        mTvRouteEnd.setOnClickListener(this);\n        mBtnMockRoute1.setOnClickListener(this);\n        mCbToggleRouteMock.setOnCheckedChangeListener(this);\n        mCbToggleRouteMock.setChecked(GpsMockConfig.isRouteMockOpen());\n\n        mBtnMockRoute2.setOnClickListener(this);\n        mCbToggleRouteDriftMock.setOnCheckedChangeListener(this);\n        mCbToggleRouteDriftMock.setChecked(GpsMockConfig.isRouteDriftMockOpen());\n\n        mCbDriftLostLoc.setOnCheckedChangeListener(this);\n        mCbDriftLostLoc.setChecked(GpsMockConfig.isRouteDriftMockLostLocOpen());\n        mDriftLostLocSeekBar.setProgressLow(GpsMockConfig.getLostLocSeekBarLow());\n        mDriftLostLocSeekBar.setProgressHigh(GpsMockConfig.getLostLocSeekBarHigh());\n\n        // 临时先屏蔽\n        // mCbToggleRouteDriftMock.setEnabled(false);\n        // mDriftSettingLayout.setVisibility(View.GONE);\n\n        onPosMockCbChange(GpsMockConfig.isPosMockOpen());\n        onRouteMockCbChange(GpsMockConfig.isRouteMockOpen());\n        mDriftSettingLayout.setVisibility(GpsMockConfig.isRouteDriftMockOpen() ? View.VISIBLE : View.GONE);\n        mBtnMockRoute1.setVisibility(GpsMockConfig.isRouteDriftMockOpen() ? View.GONE : View.VISIBLE);\n\n        if (mRootView != null) {\n            mRootView.setTransitionListener(this);\n        }\n    }\n\n    private void initBdLocAndMap() {\n        LocationClientOption locationClientOption = new LocationClientOption();\n        //可选，默认高精度，设置定位模式，高精度，低功耗，仅设备\n        locationClientOption.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy);\n        //可选，默认gcj02，设置返回的定位结果坐标系，如果配合百度地图使用，建议设置为bd09ll;\n        locationClientOption.setCoorType(\"bd09ll\");\n        //可选，默认0，即仅定位一次，设置发起连续定位请求的间隔需要大于等于1000ms才是有效的\n        locationClientOption.setScanSpan(1000);\n        //可选，设置是否需要地址信息，默认不需要\n        locationClientOption.setIsNeedAddress(true);\n        //可选，设置是否需要地址描述\n        locationClientOption.setIsNeedLocationDescribe(true);\n        //可选，设置是否需要设备方向结果\n        locationClientOption.setNeedDeviceDirect(true);\n        //可选，默认false，设置是否当gps有效时按照1S1次频率输出GPS结果\n        locationClientOption.setLocationNotify(true);\n        //可选，默认true，定位SDK内部是一个SERVICE，并放到了独立进程，设置是否在stop的时候杀死这个进程，默认不杀死\n        locationClientOption.setIgnoreKillProcess(true);\n        //可选，默认false，设置是否需要位置语义化结果，可以在BDLocation.getLocationDescribe里得到，结果类似于“在北京天安门附近”\n        locationClientOption.setIsNeedLocationDescribe(true);\n        //可选，默认false，设置是否需要POI结果，可以在BDLocation.getPoiList里得到\n        locationClientOption.setIsNeedLocationPoiList(true);\n        //可选，默认false，设置是否收集CRASH信息，默认收集\n        locationClientOption.SetIgnoreCacheException(false);\n        //可选，默认false，设置是否开启Gps定位\n        locationClientOption.setOpenGps(true);\n        //可选，默认false，设置定位时是否需要海拔信息，默认不需要，除基础定位版本都可用\n        locationClientOption.setIsNeedAltitude(false);\n        //设置打开自动回调位置模式，该开关打开后，期间只要定位SDK检测到位置变化就会主动回调给开发者，该模式下开发者无需再关心定位间隔是多少，定位SDK本身发现位置变化就会及时回调给开发者\n        locationClientOption.setOpenAutoNotifyMode();\n        //设置打开自动回调位置模式，该开关打开后，期间只要定位SDK检测到位置变化就会主动回调给开发者\n        // locationOption.setOpenAutoNotifyMode(3000, 1, LocationClientOption.LOC_SENSITIVITY_HIGHT)\n        mBdLocationClient = new LocationClient(getActivity());\n        mBdLocationClient.registerLocationListener(mBDAbstractLocationListener);\n        mBdLocationClient.setLocOption(locationClientOption);\n        mBdLocationClient.start();\n\n        // 是否双击放大当前地图中心点 默认：false 即按照双击位置放大地图\n        // 这里设置成true, 解决双击放大地图时, 中心点会被赋值给点击处的坐标, 导致mock地址和中心图标没有重合的问题.\n        mBdMapView.getUiSettings().setEnlargeCenterWithDoubleClickEnable(true);\n        mDrivingRouteOverlay = new DrivingRouteOverlay(mBdMapView);\n        mBdMapView.setOnMarkerClickListener(mDrivingRouteOverlay);\n        // MapStatus mapStatus = new MapStatus.Builder().zoom(mCurZoomLevel).build();\n        // MapStatusUpdate mapStatusUpdate = MapStatusUpdateFactory.newMapStatus(mapStatus);\n        // mBdMapView.setMapStatus(mapStatusUpdate);\n        mBdMapView.setOnMapLoadedCallback(() -> {\n            // 地图加载完后再设置, 否则在初始化的时候会调一次onMapStatusChangeFinish, 且参数坐标是真实坐标.\n            mBdMapView.setOnMapStatusChangeListener(GpsMockFragment.this);\n            LogHelper.d(TAG, \"OnMapLoadedCallback: onMapLoaded\");\n        });\n        mBdMapView.setMyLocationEnabled(true);\n        mBdMapView.setMyLocationConfiguration(new MyLocationConfiguration(MyLocationConfiguration.LocationMode.FOLLOWING, true, null));\n        //创建GeoCoder实例对象\n        mGeoCoder = GeoCoder.newInstance();\n        //设置查询结果监听者\n        mGeoCoder.setOnGetGeoCodeResultListener(this);\n\n        // 路径规划\n        mRoutePlanSearch = RoutePlanSearch.newInstance();\n        mRoutePlanSearch.setOnGetRoutePlanResultListener(this);\n    }\n\n    private void initSpinner() {\n        ArrayAdapter<CharSequence> driftTypeAdapter = ArrayAdapter.createFromResource(getActivity(), R.array.array_drift_type, android.R.layout.simple_spinner_item);\n        driftTypeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);\n        mSpinnerDriftType.setAdapter(driftTypeAdapter);\n        mSpinnerDriftType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {\n            @Override\n            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {\n                LogHelper.d(TAG, \"漂移类型: \" + driftTypeAdapter.getItem(position));\n                mCurDriftTypeIndex = position;\n                GpsMockConfig.putRouteMockDriftType(position);\n            }\n\n            @Override\n            public void onNothingSelected(AdapterView<?> parent) {\n\n            }\n        });\n        mCurDriftTypeIndex = GpsMockConfig.getRouteMockDriftType();\n        mSpinnerDriftType.setSelection(mCurDriftTypeIndex);\n\n        ArrayAdapter<CharSequence> driftModeAdapter = ArrayAdapter.createFromResource(getActivity(), R.array.array_drift_mode, android.R.layout.simple_spinner_item);\n        driftModeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);\n        mSpinnerDriftMode.setAdapter(driftModeAdapter);\n        mSpinnerDriftMode.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {\n            @Override\n            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {\n                LogHelper.d(TAG, \"漂移模式: \" + driftModeAdapter.getItem(position));\n\n                mCurDriftModeIndex = position;\n                GpsMockConfig.putRouteMockDriftMode(position);\n\n                boolean isManual = position == DriftMode.DRIFT_MODE_MANUAL.ordinal();\n                mGroupSelectPath.setVisibility(isManual ? View.VISIBLE : View.GONE);\n                mGroupSelectAutoPath.setVisibility(isManual ? View.GONE : View.VISIBLE);\n            }\n\n            @Override\n            public void onNothingSelected(AdapterView<?> parent) {\n\n            }\n        });\n        mCurDriftModeIndex = GpsMockConfig.getRouteMockDriftMode();\n        mSpinnerDriftMode.setSelection(mCurDriftModeIndex);\n    }\n\n    private void initData() {\n        LatLng latLng = GpsMockConfig.getMockLocation();\n        double longitude = latLng == null ? 120.05280671617048f : latLng.longitude;\n        double latitude = latLng == null ? 30.29458048433665f : latLng.latitude;\n        setPosEdtText(longitude, latitude);\n        mEdtRouteSpeed.setText(String.valueOf(GpsMockConfig.getRouteMockSpeed()));\n        mEdtDriftAccuracy.setText(String.valueOf(GpsMockConfig.getRouteMockAccuracy()));\n\n        if (GpsMockManager.getInstance().isMockingRoute()) {\n            mBtnMockRoute1.setText(R.string.btn_text_stop_mock);\n            mBtnMockRoute2.setText(R.string.btn_text_stop_mock);\n        } else {\n            mBtnMockRoute1.setText(R.string.btn_text_start_mock);\n            mBtnMockRoute2.setText(R.string.btn_text_start_mock);\n        }\n\n        mMapView.post(() -> {\n            // 定位到输入框的坐标点所在的地图位置\n            moveToLoc(latitude, longitude, mCurZoomLevel);\n            // 此时地图会移动到该位置作为地图中心,但不显示自带的定位图标, 且不会触发地图改变的回调onMapStatusChangeFinish.\n            mBdMapView.setMapStatus(MapStatusUpdateFactory.newLatLngZoom(new com.baidu.mapapi.model.LatLng(latitude, longitude), mCurZoomLevel));\n            if (GpsMockManager.getInstance().isMocking()) {\n                GpsMockManager.getInstance().performMock(new LatLng(latitude, longitude));\n            }\n\n            drawRoute();\n        });\n\n        if (GpsMockManager.getInstance().getBdMockDrivingRouteLine() != null\n            && GpsMockManager.getInstance().getBdMockDrivingRouteLine().getStartNode() != null\n            && GpsMockManager.getInstance().getBdMockDrivingRouteLine().getStartNode().getLocation() != null) {\n\n            mRouteStartNode.setLocation(GpsMockManager.getInstance().getBdMockDrivingRouteLine().getStartNode().getLocation());\n        } else {\n            mRouteStartNode.setLocation(new com.baidu.mapapi.model.LatLng(latitude, longitude));\n        }\n        // 将坐标点换算成地址名称填入起点和终点\n        searchPoi(mRouteStartNode.getLocation());\n        mInitRouteStart = true;\n\n        GpsMockManager.getInstance().setStatusCallback(this);\n    }\n\n    private void drawRoute() {\n        if (checkRouteMockToggle()) {\n            if (GpsMockManager.getInstance().getBdMockDrivingRouteLine() != null) {\n                mBdMapView.setOnMarkerClickListener(mDrivingRouteOverlay);\n                mDrivingRouteOverlay.setBdMapRouteData(GpsMockManager.getInstance().getBdMockDrivingRouteLine());\n                mDrivingRouteOverlay.addToMap(mCbDriftLostLoc.isChecked());\n                mDrivingRouteOverlay.zoomToSpan();\n\n                mTvOriginDistance.setText(String.valueOf(GpsMockManager.getInstance().getBdMockDrivingRouteLine().getTotalDistance()));\n            }\n\n            if (checkDriftToggle()) {\n                if (GpsMockManager.getInstance().getBdMockDrivingRouteLine() != null) {\n                    if (mCurDriftTypeIndex == DriftType.DRIFT_TYPE_ROUTE.ordinal()) {\n                        mDrivingRouteOverlay.addDriftRouteToMap(GpsMockManager.getInstance().getBdMockDrivingRouteLine(), OverlayManager.COLOR_ROUTE_DRIFT, mCbDriftLostLoc.isChecked());\n                        mTvMockDistance.setText(String.valueOf(GpsMockManager.getInstance().getBdMockDrivingRouteLine().getRouteDriftDistance()));\n                    } else {\n                        mDrivingRouteOverlay.addDriftRandomRouteToMap(GpsMockManager.getInstance().getBdMockDrivingRouteLine(), OverlayManager.COLOR_ROUTE_DRIFT, mCbDriftLostLoc.isChecked());\n                        mTvMockDistance.setText(String.valueOf(GpsMockManager.getInstance().getBdMockDrivingRouteLine().getRandomDriftDistance()));\n                    }\n                }\n            } else {\n                mDrivingRouteOverlay.removeDriftRouteFromMap();\n            }\n        } else {\n            mDrivingRouteOverlay.removeAllRouteFromMap();\n        }\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        mMapView.onResume();\n    }\n\n    @Override\n    public void onClick(View v) {\n        if (v.getId() == R.id.btn_mock_pos) {\n            GpsMockManager.getInstance().performMock(getPosMockInput());\n        } else if (v.getId() == R.id.tv_route_start) {\n            mCurPosViewId = v.getId();\n            selectPosition();\n        } else if (v.getId() == R.id.tv_route_end) {\n            mCurPosViewId = v.getId();\n            selectPosition();\n        } else if (v.getId() == R.id.btn_mock_route1 || v.getId() == R.id.btn_mock_route2) {\n            // 保存用户的配置\n            GpsMockConfig.putRouteMockSpeed(getInputSpeed());\n            GpsMockConfig.putRouteMockAccuracy(getInputDriftAccuracy());\n            GpsMockConfig.putSeekBarLow((int) mSeekBar.getProgressLow());\n            GpsMockConfig.putSeekBarHigh((int) mSeekBar.getProgressHigh());\n            GpsMockConfig.putLostLocSeekBarLow((int) mDriftLostLocSeekBar.getProgressLow());\n            GpsMockConfig.putLostLocSeekBarHigh((int) mDriftLostLocSeekBar.getProgressHigh());\n\n            if (GpsMockManager.getInstance().isMockingRoute()) {\n                mBtnMockRoute1.setText(R.string.btn_text_start_mock);\n                mBtnMockRoute2.setText(R.string.btn_text_start_mock);\n                // 停止模拟\n                GpsMockManager.getInstance().interruptRouteMockThread();\n            } else {\n                BdMapRouteData bdMapRouteData = GpsMockManager.getInstance().getBdMockDrivingRouteLine();\n                // 如果业务传过来的路径为空或者路径数据标识不是从业务传过来的,说明业务没有做路径规划. 则判断用户是否要在DoKit里规划路径来进行模拟.\n                if (bdMapRouteData == null || !bdMapRouteData.isRouteDataFromBiz()) {\n                    if (mRouteStartNode.getLocation() != null && mRouteEndNode.getLocation() != null) {\n                        mRoutePlanSearch.drivingSearch(new DrivingRoutePlanOption()\n                            .from(PlanNode.withLocation(mRouteStartNode.getLocation()))\n                            .to(PlanNode.withLocation(mRouteEndNode.getLocation())));\n                    } else {\n                        ToastUtils.showShort(\"请先选择起终点\");\n                    }\n                } else {\n                    drawAndMockRoute();\n                }\n            }\n        }\n    }\n\n    private void drawAndMockRoute() {\n        if (checkDriftToggle()) {\n            if (mCurDriftModeIndex == DriftMode.DRIFT_MODE_MANUAL.ordinal()) {\n                // 漂移路径模拟\n                // 计算偏移点(手动选择模式)\n                GpsMockManager.getInstance().calculateDriftRoute(getInputDriftAccuracy(), mSeekBar.getProgressLow(), mSeekBar.getProgressHigh());\n                if (mCbDriftLostLoc.isChecked()){\n                    GpsMockManager.getInstance().calculateDriftRouteWithLocLost(mDriftLostLocSeekBar.getProgressLow(), mDriftLostLocSeekBar.getProgressHigh());\n                }\n            } else {\n                // 计算漂移点(智能模式)待补齐.\n\n            }\n            if (GpsMockManager.getInstance().getBdMockDrivingRouteLine() != null) {\n                if (mCurDriftTypeIndex == DriftType.DRIFT_TYPE_ROUTE.ordinal()) {\n                    GpsMockManager.getInstance().startMockRouteLine(GpsMockManager.getInstance().getBdMockDrivingRouteLine().getRouteDriftPoints(), getInputSpeed(), this);\n                } else {\n                    GpsMockManager.getInstance().startMockRouteLine(GpsMockManager.getInstance().getBdMockDrivingRouteLine().getRandomDriftPoints(), getInputSpeed(), this);\n                }\n\n                mBtnMockRoute1.setText(R.string.btn_text_stop_mock);\n                mBtnMockRoute2.setText(R.string.btn_text_stop_mock);\n            }\n        } else {\n            // 真实路径模拟\n            if (GpsMockManager.getInstance().getBdMockDrivingRouteLine() != null) {\n                if (mCbDriftLostLoc.isChecked()){\n                    GpsMockManager.getInstance().calculateOriginRouteWithLocLost(mDriftLostLocSeekBar.getProgressLow(), mDriftLostLocSeekBar.getProgressHigh());\n                    // 开始模拟\n                    GpsMockManager.getInstance().startMockRouteLine(GpsMockManager.getInstance().getBdMockDrivingRouteLine().getOriginRouteLostLocPoints(), getInputSpeed(), this);\n                } else {\n                    // 开始模拟\n                    GpsMockManager.getInstance().startMockRouteLine(GpsMockManager.getInstance().getBdMockDrivingRouteLine().getAllPoints(), getInputSpeed(), this);\n                }\n\n                mBtnMockRoute1.setText(R.string.btn_text_stop_mock);\n                mBtnMockRoute2.setText(R.string.btn_text_stop_mock);\n            }\n        }\n        drawRoute();\n    }\n\n    @Override\n    public void onRouteMockFinish() {\n        mBtnMockRoute1.setText(R.string.btn_text_start_mock);\n        mBtnMockRoute2.setText(R.string.btn_text_start_mock);\n    }\n\n    @Override\n    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {\n        if (buttonView.getId() == R.id.cb_toggle_pos_mock) {\n            onPosMockCbChange(isChecked);\n            LogHelper.d(TAG, \"cb_toggle_pos_mock onCheckedChanged: \" + \" \" + isChecked);\n        } else if (buttonView.getId() == R.id.cb_toggle_route_mock) {\n            onRouteMockCbChange(isChecked);\n            LogHelper.d(TAG, \"cb_toggle_route_mock onCheckedChanged: \" + \" \" + isChecked);\n        } else if (buttonView.getId() == R.id.cb_toggle_route_drift_mock) {\n            onRouteDriftMockCbChange(isChecked);\n            LogHelper.d(TAG, \"cb_toggle_route_drift_mock onCheckedChanged: \" + \" \" + isChecked);\n        } else if (buttonView.getId() == R.id.cb_toggle_route_lost_loc){\n            onLostLocMockCbChange(isChecked);\n            LogHelper.d(TAG, \"cb_toggle_route_lost_loc onCheckedChanged: \" + \" \" + isChecked);\n        }\n    }\n\n\n    @Override\n    public void onItemSelect(SuggestionResult.SuggestionInfo suggestionInfo) {\n        mCustomDialogFragment.dismiss();\n        if (suggestionInfo == null) return;\n        if (mCurPosViewId == R.id.tv_route_start) {\n            mRouteStartNode.setTitle(suggestionInfo.key);\n            mRouteStartNode.setLocation(suggestionInfo.pt);\n            mTvRouteStart.setText(suggestionInfo.key);\n        } else if (mCurPosViewId == R.id.tv_route_end) {\n            mRouteEndNode.setTitle(suggestionInfo.key);\n            mRouteEndNode.setLocation(suggestionInfo.pt);\n            mTvRouteEnd.setText(suggestionInfo.key);\n        }\n    }\n\n    private void selectPosition() {\n        if (mCustomDialogFragment == null) {\n            mCustomDialogFragment = new CustomDialogFragment(R.layout.dk_position_select_dialog_fragment, new PositionSelectDialogHelper(getActivity(), this));\n        }\n        mCustomDialogFragment.show(getParentFragmentManager(), \"PositionSelectDialog\");\n\n    }\n\n    private boolean checkPosMockToggle() {\n        return mCbTogglePosMock.isEnabled() && mCbTogglePosMock.isChecked();\n    }\n\n    private void onPosMockCbChange(boolean isChecked) {\n        if (isChecked) {\n            if (checkRouteMockToggle() && GpsMockManager.getInstance().isMockingRoute()) {\n                mCbTogglePosMock.setChecked(false);\n                mIvMapCenterLoc.setVisibility(View.GONE);\n                mBtnMockPos.setEnabled(false);\n                ToastUtils.showShort(\"轨迹模拟正在进行, 无法开启位置模拟\");\n                return;\n            }\n            mCbToggleRouteMock.setChecked(false);\n            mIvMapCenterLoc.setVisibility(View.VISIBLE);\n            mBtnMockPos.setEnabled(true);\n        } else {\n            mIvMapCenterLoc.setVisibility(View.GONE);\n            mBtnMockPos.setEnabled(false);\n        }\n        saveMockToggle();\n        GpsMockConfig.putPosMockOpen(isChecked);\n    }\n\n    private boolean checkRouteMockToggle() {\n        return mCbToggleRouteMock.isEnabled() && mCbToggleRouteMock.isChecked();\n    }\n\n\n    private void onRouteMockCbChange(boolean isChecked) {\n        if (isChecked) {\n            mCbTogglePosMock.setChecked(false);\n            mBtnMockPos.setEnabled(false);\n            mIvMapCenterLoc.setVisibility(View.GONE);\n\n            mCbToggleRouteDriftMock.setEnabled(true);\n            mBtnMockRoute1.setEnabled(true);\n            mBtnMockRoute2.setEnabled(true);\n        } else {\n            if (GpsMockManager.getInstance().isMockingRoute()) {\n                mCbToggleRouteMock.setChecked(true);\n                ToastUtils.showShort(\"轨迹模拟正在进行, 请先取消\");\n                return;\n            }\n            mCbToggleRouteDriftMock.setEnabled(false);\n            mBtnMockRoute1.setEnabled(false);\n            mBtnMockRoute2.setEnabled(false);\n        }\n\n        saveMockToggle();\n        GpsMockConfig.putRouteMockOpen(isChecked);\n        drawRoute();\n    }\n\n    /**\n     * 位置或轨迹模拟有一个打开,则是开, 否则是关.\n     */\n    private void saveMockToggle() {\n        boolean toggleStatus = checkPosMockToggle() || checkRouteMockToggle();\n        LogHelper.d(TAG, \"开关状态: \" + (toggleStatus ? \"打开\" : \"关闭\"));\n        GpsMockConfig.setGPSMockOpen(toggleStatus);\n        if (toggleStatus) {\n            GpsMockManager.getInstance().startMock();\n        } else {\n            GpsMockManager.getInstance().stopMock();\n        }\n    }\n\n    private boolean checkDriftToggle() {\n        return mCbToggleRouteDriftMock.isEnabled() && mCbToggleRouteDriftMock.isChecked();\n    }\n\n    private void onRouteDriftMockCbChange(boolean isChecked) {\n        if (!isChecked && GpsMockManager.getInstance().isMockingRoute()) {\n            mCbToggleRouteDriftMock.setChecked(true);\n            ToastUtils.showShort(\"轨迹模拟正在进行, 请先取消\");\n            return;\n        }\n\n        showDriftLayout(isChecked);\n        GpsMockConfig.putRouteDriftMockOpen(isChecked);\n        drawRoute();\n    }\n\n    private void onLostLocMockCbChange(boolean isChecked){\n        GpsMockConfig.putRouteDriftMockLostLocOpen(isChecked);\n    }\n\n    private float getInputSpeed() {\n        float speed = 60f;\n        try {\n            speed = Float.parseFloat(mEdtRouteSpeed.getText().toString());\n        } catch (Exception e) {\n            LogHelper.e(TAG, \"input speed error \" + e.getMessage());\n        }\n        return speed;\n    }\n\n    private int getInputDriftAccuracy() {\n        int radius = 500;\n        try {\n            radius = Math.round(Float.parseFloat(mEdtDriftAccuracy.getText().toString()));\n        } catch (Exception e) {\n            LogHelper.e(TAG, \"input accuracy error \" + e.getMessage());\n        }\n\n        return radius;\n    }\n\n    private void showDriftLayout(boolean isChecked) {\n        if (isChecked) {\n            if (mShowDriftSettingAnim == null) {\n                mShowDriftSettingAnim = showDriftSettingAnim();\n            }\n            mShowDriftSettingAnim.start();\n        } else {\n            if (mHideDriftSettingAnim == null) {\n                mHideDriftSettingAnim = hideDriftSettingAnim();\n            }\n            mHideDriftSettingAnim.start();\n        }\n        LogHelper.d(TAG, \"mDriftSettingLayout height: \" + mDriftSettingLayout.getHeight() + \" isChecked: \" + isChecked);\n    }\n\n    private LatLng getPosMockInput() {\n        if (!checkInput()) {\n            return null;\n        }\n        String strLongLat = mEdtInputPos.getText().toString();\n        String[] longAndLat = strLongLat.trim().split(LOC_EDT_SPLIT_REG);\n        double longitude, latitude;\n        try {\n            longitude = Double.parseDouble(longAndLat[0]);\n            latitude = Double.parseDouble(longAndLat[1]);\n        } catch (Exception e) {\n            ToastUtils.showShort(\"经纬度必须为数字\");\n            return null;\n        }\n\n        return new LatLng(latitude, longitude);\n    }\n\n    /**\n     * 将当前位置定位到指定坐标点处\n     */\n    private void moveToLoc(double latitude, double longitude, float zoomLevel) {\n        // 设置定位数据, 会显示自带的定位图标.\n        // MyLocationData data = new MyLocationData.Builder().latitude(latitude).longitude(longitude).build();\n        // mBdMapView.setMyLocationData(data);\n        // 此时地图会移动到该位置作为地图中心,但不显示自带的定位图标, 且不会触发地图改变的回调onMapStatusChangeFinish.\n        // mBdMapView.setMapStatus(MapStatusUpdateFactory.newLatLngZoom(new com.baidu.mapapi.model.LatLng(latitude, longitude), zoomLevel));\n        mDrivingRouteOverlay.addLocMark(new com.baidu.mapapi.model.LatLng(latitude, longitude));\n        // 绘制范围圆\n        mDrivingRouteOverlay.addCircleOptions(new com.baidu.mapapi.model.LatLng(latitude, longitude), getInputDriftAccuracy());\n\n        // 此时地图会移动到该位置处并显示自带的定位图标,并触发地图改变的回调onMapStatusChangeFinish.\n        // mBdMapView.animateMapStatus(MapStatusUpdateFactory.newLatLngZoom(new com.baidu.mapapi.model.LatLng(latitude, longitude), zoomLevel));\n    }\n\n    private boolean checkInput() {\n        String strLongLat = mEdtInputPos.getText().toString();\n        if (TextUtils.isEmpty(strLongLat)) {\n            ToastUtils.showShort(\"请输入经纬度\");\n            return false;\n        }\n        String[] longAndLat = strLongLat.trim().split(LOC_EDT_SPLIT_REG);\n        if (longAndLat.length != 2) {\n            ToastUtils.showShort(\"请输入符合规范的经纬度格式\");\n            return false;\n        }\n\n        if (TextUtils.isEmpty(longAndLat[0])) {\n            return false;\n        }\n        if (TextUtils.isEmpty(longAndLat[1])) {\n            return false;\n        }\n        double longitude, latitude;\n        try {\n            longitude = Double.parseDouble(longAndLat[0]);\n            latitude = Double.parseDouble(longAndLat[1]);\n        } catch (Exception e) {\n            ToastUtils.showShort(\"经纬度必须为数字\");\n            return false;\n        }\n\n        if (longitude > 180 || longitude < -180) {\n            ToastUtils.showShort(\"经度范围必须是-180到180之间\");\n            return false;\n        }\n        if (latitude > 90 || latitude < -90) {\n            ToastUtils.showShort(\"纬度范围必须是-90到90之间\");\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_gps_mock;\n    }\n\n    @Override\n    public void onMapStatusChangeStart(MapStatus mapStatus) {\n        LogHelper.d(TAG, \"onMapStatusChangeStart:\" + mapStatus.target.longitude + \" \" + mapStatus.target.latitude);\n    }\n\n    @Override\n    public void onMapStatusChangeStart(MapStatus mapStatus, int reason) {\n        // 地图状态改变原因 原因当前有3种:\n        // REASON_GESTURE 1 用户手势触发导致的地图状态改变,比如双击、拖拽、滑动底图\n        // REASON_API_ANIMATION 2 SDK导致的地图状态改变, 比如点击缩放控件、指南针图标\n        // REASON_DEVELOPER_ANIMATION 3 开发者调用,导致的地图状态改变\n        LogHelper.d(TAG, \"onMapStatusChangeStart:\" + mapStatus.target.longitude + \" \" + mapStatus.target.latitude + \" reason:\" + reason);\n    }\n\n    @Override\n    public void onMapStatusChange(MapStatus mapStatus) {\n        LogHelper.d(TAG, \"onMapStatusChange:\" + mapStatus.target.longitude + \" \" + mapStatus.target.latitude);\n    }\n\n    @Override\n    public void onMapStatusChangeFinish(MapStatus mapStatus) {\n        // 当拖动地图, 待拖动结束后, 获取到此时地图的中心店坐标,拿到该坐标,并设置标记图标.\n        com.baidu.mapapi.model.LatLng center = mapStatus.target;\n        mCurZoomLevel = mapStatus.zoom;\n        // searchPoi(center);\n\n        LogHelper.d(TAG, \"onMapStatusChangeFinish:\" + center.latitude + \" \" + center.longitude);\n        if (checkPosMockToggle()) {\n            setPosEdtText(center.longitude, center.latitude);\n            // 拿到新坐标后进行mock.\n            GpsMockManager.getInstance().performMock(new LatLng(center.latitude, center.longitude));\n        }\n    }\n\n    private void searchPoi(com.baidu.mapapi.model.LatLng center) {\n        //发起反地理编码请求(经纬度->地址信息)\n        ReverseGeoCodeOption reverseGeoCodeOption = new ReverseGeoCodeOption();\n        //设置反地理编码位置坐标\n        reverseGeoCodeOption.location(center);\n        // 在监听方法onGetReverseGeoCodeResult里可以拿到由经纬度坐标转成的位置信息\n        mGeoCoder.reverseGeoCode(reverseGeoCodeOption);\n    }\n\n    private void setPosEdtText(double lnt, double lat) {\n        // 四舍五入,保留6位小数.\n        double clipLnt = new BigDecimal(lnt).setScale(6, BigDecimal.ROUND_HALF_UP).doubleValue();\n        double clipLat = new BigDecimal(lat).setScale(6, BigDecimal.ROUND_HALF_UP).doubleValue();\n        mEdtInputPos.setText(String.format(\"%s,%s\", clipLnt, clipLat));\n    }\n\n    @Override\n    public void onGetGeoCodeResult(GeoCodeResult geoCodeResult) {\n\n    }\n\n    @Override\n    public void onGetReverseGeoCodeResult(ReverseGeoCodeResult reverseGeoCodeResult) {\n        LogHelper.d(TAG, \" 当前坐标点地址: \" + reverseGeoCodeResult.getAddress() + \" location: \" + reverseGeoCodeResult.getLocation());\n        if (mInitRouteStart) {\n            mInitRouteStart = false;\n            mRouteStartNode.setTitle(getPoiInfo(reverseGeoCodeResult));\n            mTvRouteStart.setText(mRouteStartNode.getTitle());\n\n            // 查完起始地址信息后,再查询结束地址的信息\n            if (GpsMockManager.getInstance().getBdMockDrivingRouteLine() != null) {\n                RouteNode end = GpsMockManager.getInstance().getBdMockDrivingRouteLine().getTerminalNode();\n                mRouteEndNode.setLocation(end.getLocation());\n                searchPoi(end.getLocation());\n                mInitRouteEnd = true;\n                return;\n            }\n        }\n\n        if (mInitRouteEnd) {\n            mInitRouteEnd = false;\n            mRouteEndNode.setTitle(getPoiInfo(reverseGeoCodeResult));\n            mTvRouteEnd.setText(getPoiInfo(reverseGeoCodeResult));\n        }\n    }\n\n    private String getPoiInfo(ReverseGeoCodeResult reverseGeoCodeResult) {\n        String startAddress = \"未知地址\";\n        if (reverseGeoCodeResult != null) {\n            if (TextUtils.isEmpty(reverseGeoCodeResult.getAddress())) {\n                List<PoiInfo> nearbyPoiList = reverseGeoCodeResult.getPoiList();\n                if (nearbyPoiList != null && nearbyPoiList.size() > 0) {\n                    for (PoiInfo poiInfo : nearbyPoiList) {\n                        if (!TextUtils.isEmpty(poiInfo.address)) {\n                            startAddress = poiInfo.address;\n                            break;\n                        }\n                    }\n                }\n            } else {\n                startAddress = reverseGeoCodeResult.getAddress();\n            }\n        }\n        return startAddress;\n    }\n\n    @Override\n    public void onGetWalkingRouteResult(WalkingRouteResult walkingRouteResult) {\n\n    }\n\n    @Override\n    public void onGetTransitRouteResult(TransitRouteResult transitRouteResult) {\n\n    }\n\n    @Override\n    public void onGetMassTransitRouteResult(MassTransitRouteResult massTransitRouteResult) {\n\n    }\n\n    @Override\n    public void onGetDrivingRouteResult(DrivingRouteResult result) {\n        if (result != null && result.error == SearchResult.ERRORNO.AMBIGUOUS_ROURE_ADDR) {\n            // 起终点或途经点地址有岐义，通过以下接口获取建议查询信息\n            // result.getSuggestAddrInfo()\n            Toast.makeText(this.getActivity(), \"起终点或途经点地址有岐义,通过 result.getSuggestAddrInfo()接口获取建议查询信息\", Toast.LENGTH_SHORT).show();\n            return;\n        }\n        if (result == null || result.error == SearchResult.ERRORNO.RESULT_NOT_FOUND) {\n            Toast.makeText(this.getActivity(), \"抱歉，未找到结果\", Toast.LENGTH_SHORT).show();\n            return;\n        }\n\n        if (result.error == SearchResult.ERRORNO.NO_ERROR) {\n            if (result.getRouteLines().size() > 1) {\n                LogHelper.d(TAG, \"路线信息: \" + result.getRouteLines().get(0).toString());\n                transform2MockDataAndPerformMock(result.getRouteLines().get(0));\n            } else if (result.getRouteLines().size() == 1) {\n                LogHelper.d(TAG, \"路线信息: \" + result.getRouteLines().get(0).toString());\n                // 拿到第一条路线\n                transform2MockDataAndPerformMock(result.getRouteLines().get(0));\n                // 获取到第一条路线上的第一段路的所有坐标点\n                List<com.baidu.mapapi.model.LatLng> listStep = result.getRouteLines().get(0).getAllStep().get(0).getWayPoints();\n            } else {\n                LogHelper.d(\"route result\", \"结果数<0\");\n            }\n        }\n    }\n\n    private void transform2MockDataAndPerformMock(DrivingRouteLine drivingRouteLine) {\n        if (drivingRouteLine == null) return;\n        List<DrivingRouteLine.DrivingStep> steps = drivingRouteLine.getAllStep();\n        if (steps == null || steps.size() <= 0) {\n            return;\n        }\n\n        RouteNode start = drivingRouteLine.getStarting();\n        RouteNode terminal = drivingRouteLine.getTerminal();\n        int distance = drivingRouteLine.getDistance();\n\n        BdMapRouteData bdMapRouteData = new BdMapRouteData();\n        bdMapRouteData.setTotalDistance(distance);\n        bdMapRouteData.setStartNode(start);\n        bdMapRouteData.setTerminalNode(terminal);\n        bdMapRouteData.setRouteDataFromBiz(false);\n\n        for (DrivingRouteLine.DrivingStep step : steps) {\n            bdMapRouteData.getAllPoints().addAll(step.getWayPoints());\n        }\n        int originDistance = (int) Math.round(Utils.getRouteDistance(bdMapRouteData.getAllPoints()));\n        bdMapRouteData.setTotalDistance(originDistance);\n\n        GpsMockManager.getInstance().setBdMockDrivingRouteLine(bdMapRouteData);\n        drawAndMockRoute();\n    }\n\n    @Override\n    public void onGetIndoorRouteResult(IndoorRouteResult indoorRouteResult) {\n\n    }\n\n    @Override\n    public void onGetBikingRouteResult(BikingRouteResult bikingRouteResult) {\n\n    }\n\n    private AnimatorSet hideDriftSettingAnim() {\n        AnimatorSet animatorSet = new AnimatorSet();\n        Animator dropAnim = Utils.createDropAnimator(mDriftSettingLayout, mDriftSettingLayoutH, 0);\n        animatorSet.addListener(new AnimatorListenerAdapter() {\n            @Override\n            public void onAnimationStart(Animator animation, boolean isReverse) {\n                mBtnMockRoute1.setVisibility(View.VISIBLE);\n            }\n\n            @Override\n            public void onAnimationEnd(Animator animation, boolean isReverse) {\n                mDriftSettingLayout.setVisibility(View.GONE);\n            }\n        });\n        animatorSet.setDuration(300);\n        animatorSet.playTogether(Utils.createAlphaAnimator(mBtnMockRoute1, 0.0f, 1.0f), dropAnim);\n        return animatorSet;\n    }\n\n    private AnimatorSet showDriftSettingAnim() {\n        AnimatorSet animatorSet = new AnimatorSet();\n        Animator dropAnim = Utils.createDropAnimator(mDriftSettingLayout, 0, mDriftSettingLayoutH);\n        animatorSet.addListener(new AnimatorListenerAdapter() {\n            @Override\n            public void onAnimationStart(Animator animation, boolean isReverse) {\n                // post解决闪烁\n                mRootView.post(new Runnable() {\n                    @Override\n                    public void run() {\n                        mDriftSettingLayout.setVisibility(View.VISIBLE);\n                    }\n                });\n\n            }\n\n            @Override\n            public void onAnimationEnd(Animator animation, boolean isReverse) {\n                mRootView.post(new Runnable() {\n                    @Override\n                    public void run() {\n                        mBtnMockRoute1.setVisibility(View.GONE);\n                    }\n                });\n\n            }\n        });\n\n        animatorSet.setDuration(300);\n        animatorSet.playTogether(Utils.createAlphaAnimator(mBtnMockRoute1, 1.0f, 0.0f), dropAnim);\n        return animatorSet;\n    }\n\n\n    @Override\n    public void onTransitionStarted(MotionLayout motionLayout, int startId, int endId) {\n        LogHelper.d(TAG, \" onTransitionStarted \" + startId + \"  \" + endId + \" \" + motionLayout.getTargetPosition() + \" \" + R.id.start + \" \" + R.id.end);\n        if (startId == R.id.start) {\n            if (motionLayout.getTargetPosition() == 0) {\n                Utils.createRotateAnimator(mIvDownExpand, 180, 0).start();\n            } else {\n                Utils.createRotateAnimator(mIvDownExpand, 0, 180).start();\n            }\n        }\n    }\n\n    @Override\n    public void onTransitionChange(MotionLayout motionLayout, int startId, int endId, float progress) {\n        LogHelper.d(TAG, \" onTransitionChange \" + startId + \"  \" + endId + \" \" + progress);\n    }\n\n    @Override\n    public void onTransitionCompleted(MotionLayout motionLayout, int endId) {\n        LogHelper.d(TAG, \" onTransitionCompleted \" + endId + \"  \");\n    }\n\n    @Override\n    public void onTransitionTrigger(MotionLayout motionLayout, int i, boolean b, float progress) {\n        LogHelper.d(TAG, \" onTransitionTrigger \" + i + \"  \" + progress);\n    }\n\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        mMapView.onPause();\n    }\n\n    @Override\n    public void onDestroy() {\n        // 显示悬浮窗\n        if (checkPosMockToggle() || checkRouteMockToggle()) {\n            DoKit.launchFloating(RouteMockDokitView.class);\n        } else {\n            DoKit.removeFloating(RouteMockDokitView.class);\n        }\n\n        if (mRootView != null) {\n            mRootView.removeTransitionListener(this);\n        }\n\n        mBdMapView.setMyLocationEnabled(false);\n        mBdMapView = null;\n        mMapView.onDestroy();\n        mMapView = null;\n\n        if (mBdLocationClient != null) {\n            mBdLocationClient.stop();\n            mBdLocationClient.unRegisterLocationListener(mBDAbstractLocationListener);\n            mBdLocationClient = null;\n        }\n\n        if (mRoutePlanSearch != null) {\n            mRoutePlanSearch.destroy();\n            mRoutePlanSearch = null;\n        }\n\n        if (mGeoCoder != null) {\n            mGeoCoder.destroy();\n            mGeoCoder = null;\n        }\n        GpsMockManager.getInstance().removeStatusCallback();\n        super.onDestroy();\n    }\n\n    public static enum DriftMode {\n        // DRIFT_MODE_AUTO(), // 智能模式\n        DRIFT_MODE_MANUAL(); // 手动模式\n\n        private DriftMode() {\n        }\n    }\n\n    public static enum DriftType {\n        DRIFT_TYPE_RANDOM(), // 随机漂移\n        DRIFT_TYPE_ROUTE(); // 路径漂移\n\n        private DriftType() {\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/GpsMockKit.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.google.auto.service.AutoService\n\n/**\n * Created by wanglikun on 2018/9/20.\n */\n@AutoService(AbstractKit::class)\nclass GpsMockKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_gps_mock\n    override val icon: Int\n        get() = R.mipmap.dk_gps_mock\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip))\n            return false\n        }\n        if (!DokitPluginConfig.SWITCH_GPS) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_gps_close_tip))\n            return false\n        }\n        startUniversalActivity(GpsMockFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_comm_ck_gps\"\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/GpsMockManager.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock;\n\nimport android.location.Location;\nimport android.location.LocationManager;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.util.Log;\n\nimport com.didichuxing.doraemonkit.config.GpsMockConfig;\nimport com.didichuxing.doraemonkit.gps_mock.common.BdMapRouteData;\nimport com.didichuxing.doraemonkit.gps_mock.common.Utils;\nimport com.didichuxing.doraemonkit.model.LatLng;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\n\n/**\n * Created by wanglikun on 2018/12/18.\n */\n\npublic class GpsMockManager {\n    private static final String TAG = \"GpsMockManager\";\n\n    private static double mLatitude = -1;\n    private static double mLongitude = -1;\n\n    private static boolean isMocking;\n    private RouteMockThread mRouteMockThread;\n\n    // 待mock的路径(百度驾车路线)\n    private BdMapRouteData mBdMockDrivingRouteLine;\n\n    // 坐标点移动间隔时间\n    private long mIntervalTime;\n\n    /**\n     * 高德导航SDK 在接收到定位之后，会将定位和路线进行吸附，如果这里也进行mock会造成\n     * 1：吸附的结果被覆盖掉\n     * 2：冗余定位回调，因为高德在吸附后也会回调给导航接口位置更新，如果我们再次进行更新，就会是冗余的更新\n     */\n    private static boolean mockAMapNavLocation = false;\n\n    /**\n     * 外部可以通过API得知当前定位点是否是mock的，默认不可知，如果有需要可以打开这个配置，这样外部就可以通过\n     *\n     * @see Location#isFromMockProvider()\n     * 得知当前定位是mock的\n     */\n    private static boolean isFromMockProvider = false;\n    private Bundle extras = new Bundle();\n\n    private GpsMockManager() {\n    }\n\n    public static GpsMockManager getInstance() {\n        return Holder.INSTANCE;\n    }\n\n    public void startMock() {\n        isMocking = true;\n    }\n\n    public void stopMock() {\n        isMocking = false;\n    }\n\n    private void mockLocation(double latitude, double longitude) {\n        mLatitude = latitude;\n        mLongitude = longitude;\n    }\n\n    public void mockLocationWithNotify(double latitude, double longitude) {\n        mockLocation(latitude, longitude);\n        mockLocationWithNotify(new LocationBuilder()\n            .setLatitude(latitude)\n            .setLongitude(longitude)\n            .build());\n    }\n\n    public void mockLocationWithNotify(Location location) {\n        if (location == null) return;\n        mockLocation(location.getLatitude(), location.getLongitude());\n        location.setProvider(LocationManager.GPS_PROVIDER);\n        if (extras.size() == 0) {\n            extras.putInt(\"satellites\", 9);\n        }\n        location.setExtras(extras);\n\n        long currentTimeMillis = System.currentTimeMillis();\n        location.setTime(currentTimeMillis);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            location.setElapsedRealtimeNanos(currentTimeMillis);\n        }\n        if (isFromMockProvider()) {\n            Class<? extends Location> locationClass = location.getClass();\n            try {\n                Method method = locationClass.getMethod(\"setIsFromMockProvider\", boolean.class);\n                method.setAccessible(true);\n                method.invoke(location, true);\n            } catch (NoSuchMethodException e) {\n                e.printStackTrace();\n            } catch (IllegalAccessException e) {\n                e.printStackTrace();\n            } catch (InvocationTargetException e) {\n                e.printStackTrace();\n            }\n        }\n        GpsMockProxyManager.INSTANCE.mockLocationWithNotify(location);\n    }\n\n    public boolean isMocking() {\n        return isMocking && mLongitude != -1 && mLatitude != -1;\n    }\n\n    public double getLatitude() {\n        return mLatitude;\n    }\n\n    public double getLongitude() {\n        return mLongitude;\n    }\n\n    public boolean isMockEnable() {\n        return ServiceHookManager.INSTANCE.isHookSuccess();\n    }\n\n    private static class Holder {\n\n        private static GpsMockManager INSTANCE = new GpsMockManager();\n\n    }\n\n    public static boolean mockAMapNavLocation() {\n        return mockAMapNavLocation;\n    }\n\n    public static void setMockAMapNavLocation(boolean mockAMapNavLocation) {\n        GpsMockManager.mockAMapNavLocation = mockAMapNavLocation;\n    }\n\n    public static boolean isFromMockProvider() {\n        return isFromMockProvider;\n    }\n\n    public static void setIsFromMockProvider(boolean isFromMockProvider) {\n        GpsMockManager.isFromMockProvider = isFromMockProvider;\n    }\n\n    public boolean isMockingRoute() {\n        return mRouteMockThread != null && mRouteMockThread.isMocking();\n    }\n\n    public BdMapRouteData getBdMockDrivingRouteLine() {\n        return mBdMockDrivingRouteLine;\n    }\n\n    public void setBdMockDrivingRouteLine(BdMapRouteData bdMockDrivingRouteLine) {\n        mBdMockDrivingRouteLine = bdMockDrivingRouteLine;\n    }\n\n    public void performMock(LatLng latLng) {\n        if (latLng == null) return;\n        LogHelper.d(TAG, \" performMock \" + latLng.latitude + \" \" + latLng.longitude);\n        GpsMockManager.getInstance().mockLocationWithNotify(latLng.latitude, latLng.longitude);\n        GpsMockConfig.saveMockLocation(new LatLng(latLng.latitude, latLng.longitude));\n    }\n\n    public void startMockRouteLine(List<com.baidu.mapapi.model.LatLng> points, double speed, RouteMockThread.RouteMockStatusCallback statusCallback) {\n        if (isMockingRoute()) return;\n\n        if (isMockingRoute() && mRouteMockThread.isSuspend()) {\n            suspendRouteMock(false);\n            return;\n        }\n\n        if (points != null && points.size() > 0) {\n            mIntervalTime = Math.round(((double) GpsMockManager.getInstance().getBdMockDrivingRouteLine().getTotalDistance()) * 3600000 / (speed * 1000 * points.size()));\n            LogHelper.d(TAG, \"mIntervalTime \" + mIntervalTime);\n\n            mRouteMockThread = new RouteMockThread();\n            mRouteMockThread.setIntervalTime(mIntervalTime);\n            mRouteMockThread.setPoints(points);\n            mRouteMockThread.setRouteMockStatusCallback(statusCallback);\n            mRouteMockThread.start();\n        }\n    }\n\n    public void setStatusCallback(RouteMockThread.RouteMockStatusCallback statusCallback) {\n        if (mRouteMockThread != null) {\n            mRouteMockThread.setRouteMockStatusCallback(statusCallback);\n        }\n    }\n\n    public void removeStatusCallback() {\n        if (mRouteMockThread != null) {\n            mRouteMockThread.clearRouteMockStatusCallback();\n        }\n    }\n\n    /**\n     * 停止模拟.\n     */\n    public void interruptRouteMockThread() {\n        if (GpsMockManager.getInstance().isMockingRoute()) {\n            mRouteMockThread.interrupt();\n        }\n    }\n\n    /**\n     * @param suspend true: 暂停模拟; false:继续模拟\n     */\n    public void suspendRouteMock(boolean suspend) {\n        if (isMockingRoute()) {\n            mRouteMockThread.notifyThread(suspend);\n        }\n    }\n\n    public void calculateOriginRouteWithLocLost(double progressLow, double progressHigh) {\n        BdMapRouteData bdMapRouteData = GpsMockManager.getInstance().getBdMockDrivingRouteLine();\n        if (bdMapRouteData == null) return;\n        List<com.baidu.mapapi.model.LatLng> originRoutePoints = bdMapRouteData.getAllPoints();\n        int totalPointsSize = originRoutePoints.size();\n        if (totalPointsSize < 2) return;\n\n        int startIndex = Math.round((totalPointsSize / 100.0f) * (int) progressLow);\n        int endIndex = Math.round((totalPointsSize / 100.0f) * (int) progressHigh);\n        if (endIndex <= startIndex) return;\n        int startLostIndex = startIndex > 0 ? (startIndex - 1) : 0;\n        if (endIndex > totalPointsSize) {\n            endIndex = totalPointsSize - 1;\n        }\n\n        startIndex = startIndex <= 0 ? 1 : startIndex;\n        endIndex = endIndex == totalPointsSize ? (endIndex - 1) : endIndex;\n        com.baidu.mapapi.model.LatLng originRouteStartLostPoint = originRoutePoints.get(startLostIndex);\n        com.baidu.mapapi.model.LatLng originRouteEndLostPoint = originRoutePoints.get(endIndex);\n        bdMapRouteData.mOriginRouteStartLostPoint = originRouteStartLostPoint;\n        bdMapRouteData.mOriginRouteEndLostPoint = originRouteEndLostPoint;\n\n        List<com.baidu.mapapi.model.LatLng> tempLostLocOriginRoutePoints = new ArrayList<>();\n        tempLostLocOriginRoutePoints.addAll(originRoutePoints.subList(0, startIndex));\n        tempLostLocOriginRoutePoints.addAll(originRoutePoints.subList(endIndex, totalPointsSize));\n        bdMapRouteData.setOriginRouteLostLocPoints(tempLostLocOriginRoutePoints);\n    }\n\n    public void calculateDriftRouteWithLocLost(double progressLow, double progressHigh) {\n        BdMapRouteData bdMapRouteData = GpsMockManager.getInstance().getBdMockDrivingRouteLine();\n        if (bdMapRouteData == null) return;\n        List<com.baidu.mapapi.model.LatLng> randomDriftPoints = bdMapRouteData.getRandomDriftPoints();\n        List<com.baidu.mapapi.model.LatLng> routeDriftPoints = bdMapRouteData.getRouteDriftPoints();\n        int totalPointsSize = randomDriftPoints.size();\n        if (totalPointsSize < 2) return;\n        int startIndex = Math.round((totalPointsSize / 100.0f) * (int) progressLow);\n        int endIndex = Math.round((totalPointsSize / 100.0f) * (int) progressHigh);\n        if (endIndex <= startIndex) return;\n        int startLostIndex = startIndex > 0 ? (startIndex - 1) : 0;\n        if (endIndex > totalPointsSize) {\n            endIndex = totalPointsSize - 1;\n        }\n\n        startIndex = startIndex <= 0 ? 1 : startIndex;\n        endIndex = endIndex == totalPointsSize ? (endIndex - 1) : endIndex;\n\n        com.baidu.mapapi.model.LatLng randomDriftStartLostPoint = randomDriftPoints.get(startLostIndex);\n        com.baidu.mapapi.model.LatLng randomDriftEndLostPoint = randomDriftPoints.get(endIndex);\n        bdMapRouteData.mRandomDriftStartLostPoint = randomDriftStartLostPoint;\n        bdMapRouteData.mRandomDriftEndLostPoint = randomDriftEndLostPoint;\n        com.baidu.mapapi.model.LatLng routeDriftStartLostPoint = routeDriftPoints.get(startLostIndex);\n        com.baidu.mapapi.model.LatLng routeDriftEndLostPoint = routeDriftPoints.get(endIndex);\n        bdMapRouteData.mRouteDriftStartLostPoint = routeDriftStartLostPoint;\n        bdMapRouteData.mRouteDriftEndLostPoint = routeDriftEndLostPoint;\n\n        List<com.baidu.mapapi.model.LatLng> tempLostLocRandomDriftPoints = new ArrayList<>();\n        tempLostLocRandomDriftPoints.addAll(randomDriftPoints.subList(0, startIndex));\n        tempLostLocRandomDriftPoints.addAll(randomDriftPoints.subList(endIndex, totalPointsSize));\n        bdMapRouteData.setRandomDriftPoints(tempLostLocRandomDriftPoints);\n        List<com.baidu.mapapi.model.LatLng> tempLostLocRouteDriftPoints = new ArrayList<>();\n        tempLostLocRouteDriftPoints.addAll(routeDriftPoints.subList(0, startIndex));\n        tempLostLocRouteDriftPoints.addAll(routeDriftPoints.subList(endIndex, totalPointsSize));\n        bdMapRouteData.setRouteDriftPoints(tempLostLocRouteDriftPoints);\n    }\n\n    public void calculateDriftRoute(double radius, double progressLow, double progressHigh) {\n        Double orientLatDiffer = null;\n        BdMapRouteData bdMapRouteData = GpsMockManager.getInstance().getBdMockDrivingRouteLine();\n        if (bdMapRouteData != null && bdMapRouteData.getAllPoints().size() > 0) {\n            List<com.baidu.mapapi.model.LatLng> allPoints = bdMapRouteData.getAllPoints();\n            int totalPointsSize = allPoints.size();\n            List<com.baidu.mapapi.model.LatLng> randomDriftPoints = new ArrayList<>();\n            List<com.baidu.mapapi.model.LatLng> routeDriftPoints = new ArrayList<>();\n\n            List<com.baidu.mapapi.model.LatLng> rangeDriftPoints;\n            int startIndex = Math.round((totalPointsSize / 100.0f) * (int) progressLow);\n            int endIndex = Math.round((totalPointsSize / 100.0f) * (int) progressHigh);\n            if (endIndex >= totalPointsSize) {\n                endIndex = totalPointsSize - 1;\n            }\n            rangeDriftPoints = bdMapRouteData.getAllPoints().subList(startIndex, endIndex);\n\n            int rangeDriftPointsSize = rangeDriftPoints.size();\n            // 从漂移段里再抽稀出10%的坐标点进行随机漂移\n            int randomPointsSize = Math.round(rangeDriftPointsSize * 0.1f);\n            if (randomPointsSize > rangeDriftPointsSize) {\n                randomPointsSize = rangeDriftPointsSize;\n            }\n\n            // 获取随机坐标点的索引值\n            HashSet<Integer> randomIndexSet = new HashSet<>();\n            Utils.randomSet(0, rangeDriftPointsSize, randomPointsSize, randomIndexSet);\n            LogHelper.d(TAG, \"randomPointsSize:\" + randomPointsSize + \" rangeDriftPointsSize:\" + rangeDriftPointsSize + \" randomIndexSet \" + randomIndexSet);\n\n            for (int i = 0; i < rangeDriftPointsSize; i++) {\n                com.baidu.mapapi.model.LatLng point = rangeDriftPoints.get(i);\n                double[] rangeAround = Utils.getAround(point.latitude, point.longitude, radius);\n\n                if (randomIndexSet.contains(i)) {\n                    double[] randomLatLng = Utils.getRandomLatLng(point.latitude, point.longitude, radius, rangeAround);\n                    com.baidu.mapapi.model.LatLng randomPoint = new com.baidu.mapapi.model.LatLng(randomLatLng[0], randomLatLng[1]);\n                    randomDriftPoints.add(randomPoint);\n                } else {\n                    randomDriftPoints.add(point);\n                }\n\n                // 所有点偏移量都一样,即可实现路径整体平移效果.\n                if (orientLatDiffer == null) {\n                    orientLatDiffer = Utils.getOrientLatDiffer(rangeAround);\n                }\n\n                double[] orientLatLng = Utils.getOrientationLatLng(point.latitude, point.longitude, radius, rangeAround, orientLatDiffer);\n                com.baidu.mapapi.model.LatLng orientPoint = new com.baidu.mapapi.model.LatLng(orientLatLng[0], orientLatLng[1]);\n                routeDriftPoints.add(orientPoint);\n            }\n\n            List<com.baidu.mapapi.model.LatLng> start = allPoints.subList(0, startIndex);\n            List<com.baidu.mapapi.model.LatLng> end = allPoints.subList(endIndex, totalPointsSize);\n            randomDriftPoints.addAll(randomDriftPoints.size(), end);\n            randomDriftPoints.addAll(0, start);\n            int randomDriftDistance = (int) Math.round(Utils.getRouteDistance(randomDriftPoints));\n            bdMapRouteData.setRandomDriftPoints(randomDriftPoints);\n            bdMapRouteData.setRandomDriftDistance(randomDriftDistance);\n\n            routeDriftPoints.addAll(routeDriftPoints.size(), end);\n            routeDriftPoints.addAll(0, start);\n            int routeDriftDistance = (int) Math.round(Utils.getRouteDistance(routeDriftPoints));\n            bdMapRouteData.setRouteDriftPoints(routeDriftPoints);\n            bdMapRouteData.setRouteDriftDistance(routeDriftDistance);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/GpsMockProxyManager.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock\n\nimport android.location.Location\nimport android.location.LocationListener\nimport com.amap.api.location.AMapLocationListener\nimport com.amap.api.navi.AMapNaviListener\nimport com.baidu.location.BDAbstractLocationListener\nimport com.baidu.location.BDLocationListener\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.LocationHooker.LocationListenerProxy\nimport com.didichuxing.doraemonkit.gps_mock.map.*\nimport com.tencent.map.geolocation.TencentLocation\nimport com.tencent.map.geolocation.TencentLocationListener\n\n/**\n * 三方地图管理类\n */\nobject GpsMockProxyManager {\n    private val mAMapLocationListenerProxies: MutableList<AMapLocationListenerProxy?> = ArrayList()\n    private val mAMapLocationChangedListenerProxies: MutableList<AMapLocationChangedListenerProxy?> =\n        ArrayList()\n    private val mAMapNaviListenerProxies: MutableList<AMapNaviListenerProxy?> = ArrayList()\n    private val mBDAbsLocationListenerProxies: MutableList<BDAbsLocationListenerProxy?> =\n        ArrayList()\n    private val mBDLocationListenerProxies: MutableList<BDLocationListenerProxy?> = ArrayList()\n    private val mTencentLocationListenerProxies: MutableList<TencentLocationListenerProxy?> =\n        ArrayList()\n    private val mLocationListenerProxies: MutableList<LocationListenerProxy> = ArrayList()\n    private val mDMapLocationListenerProxies: MutableList<DMapLocationListener> = ArrayList()\n    private val mDMapNaviListenerProxies: MutableList<DMapLocationListener?> = ArrayList()\n\n    fun addAMapLocationListenerProxy(aMapLocationListenerProxy: AMapLocationListenerProxy) {\n        mAMapLocationListenerProxies.add(aMapLocationListenerProxy)\n    }\n\n    fun addAMapLocationChangedListenerProxy(aMapLocationChangedListenerProxy: AMapLocationChangedListenerProxy) {\n        mAMapLocationChangedListenerProxies.add(aMapLocationChangedListenerProxy)\n    }\n\n    fun addAMapNaviListenerProxy(aMapNaviListenerProxy: AMapNaviListenerProxy) {\n        mAMapNaviListenerProxies.add(aMapNaviListenerProxy)\n    }\n\n    fun addBDAbsLocationListenerProxy(bdAbsLocationListenerProxy: BDAbsLocationListenerProxy) {\n        mBDAbsLocationListenerProxies.add(bdAbsLocationListenerProxy)\n    }\n\n    fun addBDLocationListenerProxy(bdLocationListenerProxy: BDLocationListenerProxy) {\n        mBDLocationListenerProxies.add(bdLocationListenerProxy)\n    }\n\n    fun addTencentLocationListenerProxy(tencentLocationListenerProxy: TencentLocationListenerProxy) {\n        mTencentLocationListenerProxies.add(tencentLocationListenerProxy)\n    }\n\n    fun addDMapLocationListenerProxy(locationListenerProxy: DMapLocationListener) {\n        mDMapLocationListenerProxies.add(locationListenerProxy)\n    }\n\n    fun addDMapNaviListenerProxy( dMapNaviListener: DMapLocationListener){\n        mDMapNaviListenerProxies.add(dMapNaviListener)\n    }\n\n    fun addLocationListenerProxy(locationListenerProxy: LocationListenerProxy) {\n        mLocationListenerProxies.add(locationListenerProxy)\n    }\n\n    fun removeAMapLocationListener(listener: AMapLocationListener):AMapLocationListenerProxy? {\n        val it = mAMapLocationListenerProxies.iterator()\n        while (it.hasNext()) {\n            val proxy = it.next()\n            if (proxy?.aMapLocationListener === listener) {\n                it.remove()\n                return proxy\n            }\n        }\n        return null\n    }\n\n    fun removeAMapNaviListener(listener: AMapNaviListener) :AMapNaviListenerProxy?{\n        val it = mAMapNaviListenerProxies.iterator()\n        while (it.hasNext()) {\n            val proxy = it.next()\n            if (proxy?.aMapNaviListener === listener) {\n                it.remove()\n                return proxy\n            }\n        }\n\n        return null\n    }\n\n    fun removeTencentLocationListener(listener: TencentLocationListener) :TencentLocationListenerProxy?{\n        val it = mTencentLocationListenerProxies.iterator()\n        while (it.hasNext()) {\n            val proxy = it.next()\n            if (proxy?.mTencentLocationListener === listener) {\n                it.remove()\n                return proxy\n            }\n        }\n        return null\n    }\n\n    fun removeBDLocationListener(listener: BDLocationListener):BDLocationListenerProxy? {\n        val it = mBDLocationListenerProxies.iterator()\n        while (it.hasNext()) {\n            val proxy = it.next()\n            if (proxy?.mBdLocationListener === listener) {\n                it.remove()\n                return proxy\n            }\n        }\n\n        return null\n    }\n\n    fun removeBDAbsLocationListener(listener: BDAbstractLocationListener): BDAbsLocationListenerProxy? {\n        val it = mBDAbsLocationListenerProxies.iterator()\n        while (it.hasNext()) {\n            val proxy = it.next()\n            if (proxy?.mBdLocationListener === listener) {\n                it.remove()\n                return proxy\n            }\n        }\n        return null\n    }\n\n    fun removeLocationListener(listener: LocationListener) {\n        val it = mLocationListenerProxies.iterator()\n        while (it.hasNext()) {\n            val proxy = it.next()\n            if (proxy.locationListener === listener) {\n                it.remove()\n            }\n        }\n    }\n\n    fun removeDMapLocationListener(listener: DMapLocationListener) {\n        val it = mDMapLocationListenerProxies.iterator()\n        while (it.hasNext()) {\n            val proxy = it.next()\n            if (proxy.getDMapLocation() === listener) {\n                it.remove()\n            }\n        }\n    }\n\n    fun clearProxy() {\n        mAMapLocationListenerProxies.clear()\n        mBDAbsLocationListenerProxies.clear()\n        mBDLocationListenerProxies.clear()\n        mTencentLocationListenerProxies.clear()\n        mLocationListenerProxies.clear()\n    }\n\n    fun mockLocationWithNotify(location: Location?) {\n        if (location == null) return\n\n        try {\n            notifyLocationListenerProxy(location)\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n\n        try {\n            notifyAMapLocationListenerProxy(location)\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n        try {\n            notifyBDAbsLocationListenerProxy(location)\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n        try {\n            notifyBDLocationListenerProxy(location)\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n        try {\n            notifyTencentLocationListenerProxy(location)\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n        try {\n            notifyDMapLocationListenerProxy(location)\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    private fun notifyAMapLocationListenerProxy(location: Location?) {\n        if (location != null) {\n            //location\n            for (aMapLocationListenerProxy in mAMapLocationListenerProxies) {\n                aMapLocationListenerProxy?.onLocationChanged(LocationBuilder.toAMapLocation(location))\n            }\n            //location source\n            for (aMapLocationChangedListenerProxy in mAMapLocationChangedListenerProxies) {\n                aMapLocationChangedListenerProxy?.onLocationChanged(location)\n            }\n//            if (GpsMockManager.getInstance().isMockingRoute) {\n                for (aMapNaviListenerProxy in mAMapNaviListenerProxies) {\n                    aMapNaviListenerProxy?.onLocationChange(\n                        LocationBuilder.toAMapNaviLocation(\n                            location\n                        )\n                    )\n                }\n//            }\n\n\n        }\n\n    }\n\n    private fun notifyBDAbsLocationListenerProxy(location: Location?) {\n        if (location != null) {\n            for (bdAbsLocationListenerProxy in mBDAbsLocationListenerProxies) {\n                bdAbsLocationListenerProxy?.onReceiveLocation(LocationBuilder.toBdLocation(location))\n            }\n        }\n    }\n\n    private fun notifyBDLocationListenerProxy(location: Location?) {\n        if (location != null) {\n            for (bdLocationListenerProxy in mBDLocationListenerProxies) {\n                bdLocationListenerProxy?.onReceiveLocation(LocationBuilder.toBdLocation(location))\n            }\n        }\n    }\n\n    private fun notifyTencentLocationListenerProxy(location: Location?) {\n        if (location != null) {\n            for (tencentLocationListenerProxy in mTencentLocationListenerProxies) {\n                tencentLocationListenerProxy?.onLocationChanged(\n                    LocationBuilder.toTencentLocation(\n                        location\n                    ), TencentLocation.ERROR_OK, \"\"\n                )\n            }\n        }\n    }\n\n    private fun notifyLocationListenerProxy(location: Location?) {\n        if (location != null) {\n            for (systemLocationListenerProxy in mLocationListenerProxies) {\n                systemLocationListenerProxy.onLocationChanged(location)\n            }\n        }\n    }\n\n    private fun notifyDMapLocationListenerProxy(location: Location?) {\n        if (location != null) {\n            for (dMapLocationListener in mDMapLocationListenerProxies){\n                dMapLocationListener.onLocationChange(location)\n            }\n\n            for (dMapNavLocationListener in mDMapNaviListenerProxies){\n                dMapNavLocationListener?.onLocationChange(location)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/LocationBuilder.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock;\n\nimport android.location.Location;\n\nimport com.amap.api.location.AMapLocation;\nimport com.amap.api.navi.model.AMapNaviLocation;\nimport com.amap.api.navi.model.NaviLatLng;\nimport com.baidu.location.BDLocation;\n\npublic class LocationBuilder {\n    private double mLatitude = -1;\n    private double mLongitude = -1;\n    private float mSpeed = 0.0f;\n    private float mBearing = 0.0f;\n    private long mTime = 0;\n    private float mHorizontalAccuracyMeters = 0.0f;\n\n    public LocationBuilder() {\n    }\n\n    public static AMapLocation toAMapLocation(Location location) {\n        AMapLocation aMapLocation = new AMapLocation(location);\n        aMapLocation.setSpeed(location.getSpeed());\n        aMapLocation.setBearing(location.getBearing());\n        aMapLocation.setAccuracy(location.getAccuracy());\n        aMapLocation.setTime(location.getTime());\n        return aMapLocation;\n    }\n\n    public static AMapNaviLocation toAMapNaviLocation(Location location) {\n        AMapNaviLocation aMapNaviLocation = new AMapNaviLocation();\n        aMapNaviLocation.setSpeed(location.getSpeed());\n        aMapNaviLocation.setBearing(location.getBearing());\n        aMapNaviLocation.setAccuracy(location.getAccuracy());\n        aMapNaviLocation.setTime(location.getTime());\n        aMapNaviLocation.setCoord(new NaviLatLng(location.getLatitude(),location.getLongitude()));\n        return aMapNaviLocation;\n    }\n\n\n\n    public static TencentLocationImp toTencentLocation(Location location) {\n        if (location == null) return null;\n        TencentLocationImp tencentLocation = new TencentLocationImp();\n        tencentLocation.setLatitude(location.getLatitude())\n                .setLongitude(location.getLongitude())\n                .setSpeed(location.getSpeed())\n                .setBearing(location.getBearing())\n                .setAccuracy(location.getAccuracy())\n                .setTime(location.getTime());\n        return tencentLocation;\n    }\n\n    public static BDLocation toBdLocation(Location location) {\n        if (location == null) return null;\n        BDLocation bdLocation = new BDLocation();\n        CordTransformUtil.Point point = CordTransformUtil.wgs84tobd09(location.getLatitude(),location.getLongitude());\n        bdLocation.setLatitude(point.getLatitude());\n        bdLocation.setLongitude(point.getLongitude());\n        bdLocation.setSpeed(location.getSpeed());\n        bdLocation.setDirection(location.getBearing()); //BDLocation 对应的方向API\n        bdLocation.setRadius(location.getAccuracy()); // BDLocation 对应的精度API\n//            bdLocation.setTime(location.getTime());\n        return bdLocation;\n    }\n\n    public LocationBuilder fromLocation(Location location) {\n        mLatitude = location.getLatitude();\n        mLongitude = location.getLongitude();\n        mSpeed = location.getSpeed();\n        mBearing = location.getBearing();\n        mTime = location.getTime();\n        mHorizontalAccuracyMeters = location.getAccuracy();\n        return this;\n    }\n\n    public LocationBuilder setLatitude(double latitude) {\n        mLatitude = latitude;\n        return this;\n    }\n\n    public LocationBuilder setLongitude(double longitude) {\n        mLongitude = longitude;\n        return this;\n    }\n\n    public LocationBuilder setSpeed(float speed) {\n        mSpeed = speed;\n        return this;\n    }\n\n    public LocationBuilder setBearing(float bearing) {\n        mBearing = bearing;\n        return this;\n    }\n\n    public LocationBuilder setTime(long time) {\n        mTime = time;\n        return this;\n    }\n\n    public LocationBuilder setHorizontalAccuracyMeters(float horizontalAccuracyMeters) {\n        mHorizontalAccuracyMeters = horizontalAccuracyMeters;\n        return this;\n    }\n\n    public Location build() {\n        Location dokit_mock_gps = new Location(\"DOKIT_MOCK\");\n        dokit_mock_gps.setLatitude(mLatitude);\n        dokit_mock_gps.setLongitude(mLongitude);\n        dokit_mock_gps.setSpeed(mSpeed);\n        dokit_mock_gps.setBearing(mBearing);\n        dokit_mock_gps.setTime(mTime);\n        dokit_mock_gps.setAccuracy(mHorizontalAccuracyMeters);\n        return dokit_mock_gps;\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/LocationHookHandler.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock;\n\nimport android.annotation.SuppressLint;\nimport android.location.Location;\nimport android.location.LocationListener;\nimport android.os.Bundle;\nimport android.os.IBinder;\n\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.Method;\n\n/**\n * Created by wanglikun on 2018/12/18.\n */\n\npublic class LocationHookHandler implements InvocationHandler {\n    private static final String TAG = \"LocationHookHandler\";\n\n    private Object mOriginService;\n\n    @SuppressWarnings(\"unchecked\")\n    @SuppressLint(\"PrivateApi\")\n    public LocationHookHandler(IBinder binder) {\n        try {\n            Class iLocationManager$Stub = Class.forName(\"android.location.ILocationManager$Stub\");\n            Method asInterface = iLocationManager$Stub.getDeclaredMethod(\"asInterface\", IBinder.class);\n            this.mOriginService = asInterface.invoke(null, binder);\n        } catch (Exception e) {\n            e.printStackTrace();\n            LogHelper.e(TAG, e.toString());\n        }\n    }\n\n    @Override\n    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n        switch (method.getName()) {\n            case \"requestLocationUpdates\":\n                Field[] fields = args[1].getClass().getDeclaredFields();\n                for (Field field : fields) {\n                    if (field.getType() == LocationListener.class) {\n                        field.setAccessible(true);\n                        final LocationListener originalLocationListener = (LocationListener) field.get(args[1]);\n                        LocationListener newLocationListener = new LocationListener() {\n                            @Override\n                            public void onLocationChanged(Location location) {\n                                if (!GpsMockManager.getInstance().isMocking()) {\n                                    originalLocationListener.onLocationChanged(location);\n                                } else {\n                                    location.setLongitude(GpsMockManager.getInstance().getLongitude());\n                                    location.setLatitude(GpsMockManager.getInstance().getLatitude());\n                                    originalLocationListener.onLocationChanged(location);\n                                }\n                            }\n\n                            @Override\n                            public void onStatusChanged(String provider, int status, Bundle extras) {\n                                originalLocationListener.onStatusChanged(provider, status, extras);\n                            }\n\n                            @Override\n                            public void onProviderEnabled(String provider) {\n                                originalLocationListener.onProviderEnabled(provider);\n                            }\n\n                            @Override\n                            public void onProviderDisabled(String provider) {\n                                originalLocationListener.onProviderDisabled(provider);\n                            }\n                        };\n                        field.set(args[1], newLocationListener);\n                        field.setAccessible(false);\n                    }\n                }\n                break;\n            case \"getLastLocation\":\n                if (!GpsMockManager.getInstance().isMocking()) {\n                    break;\n                }\n                Location lastLocation = (Location) method.invoke(this.mOriginService, args);\n                lastLocation.setLongitude(GpsMockManager.getInstance().getLongitude());\n                lastLocation.setLatitude(GpsMockManager.getInstance().getLatitude());\n                return lastLocation;\n            case \"getLastKnownLocation\":\n                if (!GpsMockManager.getInstance().isMocking()) {\n                    break;\n                }\n                Location lastKnownLocation = (Location) method.invoke(this.mOriginService, args);\n                lastKnownLocation.setLongitude(GpsMockManager.getInstance().getLongitude());\n                lastKnownLocation.setLatitude(GpsMockManager.getInstance().getLatitude());\n                return lastKnownLocation;\n            default:\n                break;\n        }\n        return method.invoke(this.mOriginService, args);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/LocationHooker.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock;\n\nimport android.content.Context;\nimport android.location.GnssStatus;\nimport android.location.Location;\nimport android.location.LocationListener;\nimport android.location.LocationManager;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.IBinder;\nimport android.os.SystemClock;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\n\nimport com.didichuxing.doraemonkit.util.ReflectUtils;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Created by jintai on 2019/4/2\n * http://weishu.me/2016/02/16/understand-plugin-framework-binder-hook/\n */\npublic class LocationHooker extends BaseServiceHooker {\n    private static final String TAG = \"LocationHooker\";\n//    private List<LocationListener> mListeners = new ArrayList<>();\n//    private LocationListener mHookLocationListener = null;\n\n    @Override\n    public String serviceName() {\n        return Context.LOCATION_SERVICE;\n    }\n\n    /**\n     * 编译期动态生成\n     *\n     * @return\n     */\n    @Override\n    public String stubName() {\n        return \"android.location.ILocationManager$Stub\";\n    }\n\n    @NonNull\n    @Override\n    public Map<String, MethodHandler> registerMethodHandlers() {\n        Map<String, MethodHandler> methodHandlers = new HashMap<>();\n        //methodHandlers.put(\"removeUpdates\", new RemoveUpdatesMethodHandler());\n        methodHandlers.put(\"requestLocationUpdates\", new RequestLocationUpdatesMethodHandler());\n        methodHandlers.put(\"getLastLocation\", new GetLastLocationMethodHandler());\n//        methodHandlers.put(\"getLastKnownLocation\", new GetLastKnownLocationMethodHandler());\n        methodHandlers.put(\"registerGnssStatusCallback\", new RegisterGnssStatusCallbackMethodHandler());\n        // methodHandlers.put(\"getGpsStatus\", new getGpsStatusMethodHandler());\n        return methodHandlers;\n    }\n\n    @Override\n    public void replaceBinderProxy(Context context, IBinder proxy) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {\n        //在 frameworks/base/core/java/android/app/SystemServiceRegistry.java中初始化\n        //替换具体服务中的mService\n        LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);\n\n        //IInterface customService = ReflectUtils.reflect(stubName()).method(\"asInterface\", proxy).get();\n        if (getMBinderStubProxy() != null) {\n            ReflectUtils.reflect(locationManager).field(\"mService\", getMBinderStubProxy());\n        }\n\n\n    }\n\n    static class GetLastKnownLocationMethodHandler extends MethodHandler {\n        /**\n         * @param originObject 原始对象 即 LocationManagerService\n         * @param method       需要被代理的方法\n         * @param args         代理方法的参数\n         * @return\n         * @throws InvocationTargetException\n         * @throws IllegalAccessException\n         * @throws NoSuchMethodException\n         */\n        @Override\n        public Object onInvoke(Object originObject, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {\n            if (!GpsMockManager.getInstance().isMocking()) {\n                return method.invoke(originObject, args);\n            }\n            Location lastKnownLocation = (Location) method.invoke(originObject, args);\n            if (lastKnownLocation == null) {\n                String provider = (String) args[0].getClass().getDeclaredMethod(\"getProvider\").invoke(args[0]);\n                lastKnownLocation = buildValidLocation(provider);\n            }\n            lastKnownLocation.setLongitude(GpsMockManager.getInstance().getLongitude());\n            lastKnownLocation.setLatitude(GpsMockManager.getInstance().getLatitude());\n            lastKnownLocation.setTime(System.currentTimeMillis());\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n                lastKnownLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());\n            }\n            return lastKnownLocation;\n        }\n    }\n\n\n    static class GetLastLocationMethodHandler extends MethodHandler {\n\n        @Override\n        public Object onInvoke(Object originObject, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {\n            if (!GpsMockManager.getInstance().isMocking()) {\n                return method.invoke(originObject, args);\n            }\n            Location lastLocation = (Location) method.invoke(originObject, args);\n            if (lastLocation == null) {\n                lastLocation = buildValidLocation(null);\n            }\n            lastLocation.setLongitude(GpsMockManager.getInstance().getLongitude());\n            lastLocation.setLatitude(GpsMockManager.getInstance().getLatitude());\n            lastLocation.setTime(System.currentTimeMillis());\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n                lastLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());\n            }\n            return lastLocation;\n        }\n    }\n\n    /**\n     * 注册全球定位系统的\n     */\n    static class RegisterGnssStatusCallbackMethodHandler extends MethodHandler {\n\n        @Override\n        public Object onInvoke(Object originObject, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {\n            //Log.i(TAG, \"registerGnssStatusCallbackMethodHandler====>registerGnssStatus  \" + originObject.toString() + \"  proxyObject===>\" + proxyObject.toString());\n            if (!GpsMockManager.getInstance().isMocking()) {\n                return method.invoke(originObject, args);\n            }\n            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {\n                Object gnssStatusListenerTransport = args[0];\n                GnssStatus.Callback callback = ReflectUtils.reflect(gnssStatusListenerTransport).field(\"mGnssCallback\").get();\n                GnssStatusCallbackProxy callbackProxy = new GnssStatusCallbackProxy(callback);\n                ReflectUtils.reflect(gnssStatusListenerTransport).field(\"mGnssCallback\", callbackProxy);\n            }\n\n            return method.invoke(originObject, args);\n        }\n    }\n\n\n    /**\n     * LocationListener代理\n     */\n    public static class LocationListenerProxy implements LocationListener {\n        /**\n         * 原始LocationListener\n         */\n        LocationListener locationListener;\n\n        private LocationListenerProxy(LocationListener locationListener) {\n            this.locationListener = locationListener;\n            GpsMockProxyManager.INSTANCE.addLocationListenerProxy(this);\n        }\n\n        @Override\n        public void onLocationChanged(Location location) {\n            if (locationListener != null) {\n                if (GpsMockManager.getInstance().isMocking()) {\n                    location.setLongitude(GpsMockManager.getInstance().getLongitude());\n                    location.setLatitude(GpsMockManager.getInstance().getLatitude());\n                    location.setTime(System.currentTimeMillis());\n                }\n                locationListener.onLocationChanged(location);\n            }\n            Log.d(TAG, \"系统定位===onLocationChanged isMock=\" + GpsMockManager.getInstance().isMocking() + \" \" + location.getLongitude() + \" \"+ location.getLatitude());\n\n        }\n\n        @Override\n        public void onStatusChanged(String provider, int status, Bundle extras) {\n            if (locationListener != null) {\n                locationListener.onStatusChanged(provider, status, extras);\n            }\n        }\n\n        @Override\n        public void onProviderEnabled(String provider) {\n            if (locationListener != null) {\n                locationListener.onProviderEnabled(provider);\n            }\n        }\n\n        @Override\n        public void onProviderDisabled(String provider) {\n            if (locationListener != null) {\n                locationListener.onProviderDisabled(provider);\n            }\n        }\n    }\n\n    /**\n     * transport:ListenerTransport 内部包含LocationListener\n     */\n    static class RequestLocationUpdatesMethodHandler extends MethodHandler {\n        /**\n         * @param originService 原始对象 LocationManager#mService\n         * @param method        需要被代理的方法 LocationManager#mService.requestLocationUpdates(request, transport, intent, packageName)\n         * @param args          代理方法的参数 request, transport, intent, packageName\n         * @return\n         * @throws IllegalAccessException\n         * @throws InvocationTargetException\n         * @throws NoSuchFieldException\n         */\n        @Override\n        public Object onInvoke(Object originService, Method method, Object[] args) throws IllegalAccessException, InvocationTargetException, NoSuchFieldException {\n            try {\n                if (!GpsMockManager.getInstance().isMocking()) {\n                    return method.invoke(originService, args);\n                }\n                Object listenerTransport = args[1];\n                //LocationListener mListener 类型\n                Field mListenerField = listenerTransport.getClass().getDeclaredField(\"mListener\");\n                mListenerField.setAccessible(true);\n                LocationListener locationListener = (LocationListener) mListenerField.get(listenerTransport);\n                LocationListenerProxy locationListenerProxy = new LocationListenerProxy(locationListener);\n                //将原始的LocationListener替换为LocationListenerProxy\n                mListenerField.set(listenerTransport, locationListenerProxy);\n                mListenerField.setAccessible(false);\n                return method.invoke(originService, args);\n            } catch (Exception e) {\n                //处理定位权限未授予的情况\n                return null;\n            }\n\n\n        }\n    }\n\n\n    private static Location buildValidLocation(String provider) {\n        if (TextUtils.isEmpty(provider)) {\n            provider = LocationManager.GPS_PROVIDER;\n        }\n        Location validLocation = new Location(provider);\n        validLocation.setAccuracy(5.36f);\n        validLocation.setBearing(315.0f);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            validLocation.setBearingAccuracyDegrees(52.285362f);\n        }\n        validLocation.setSpeed(0.79f);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            validLocation.setSpeedAccuracyMetersPerSecond(0.9462558f);\n        }\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            validLocation.setVerticalAccuracyMeters(8.0f);\n        }\n        validLocation.setTime(System.currentTimeMillis());\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            validLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());\n        }\n        return validLocation;\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.N)\n    private static class GnssStatusCallbackProxy extends GnssStatus.Callback {\n        GnssStatus.Callback mCallback;\n\n        public GnssStatusCallbackProxy(GnssStatus.Callback mCallback) {\n            this.mCallback = mCallback;\n        }\n\n        @Override\n        public void onStarted() {\n            //Log.i(TAG, \"GnssStatusCallbackProxy===>onStarted\");\n            if (mCallback != null) {\n                mCallback.onStarted();\n            }\n        }\n\n        @Override\n        public void onStopped() {\n            //Log.i(TAG, \"GnssStatusCallbackProxy===>onStopped\");\n            if (mCallback != null) {\n                mCallback.onStopped();\n            }\n        }\n\n        @Override\n        public void onFirstFix(int ttffMillis) {\n            // Log.i(TAG, \"GnssStatusCallbackProxy===>onFirstFix：\" + ttffMillis);\n            if (mCallback != null) {\n                mCallback.onFirstFix(ttffMillis);\n            }\n        }\n\n        @Override\n        public void onSatelliteStatusChanged(GnssStatus status) {\n            // Log.i(TAG, \"GnssStatusCallbackProxy===>onSatelliteStatusChanged：\" + status);\n            if (mCallback != null) {\n                mCallback.onSatelliteStatusChanged(status);\n\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/MethodHandler.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock\n\nimport java.lang.reflect.InvocationTargetException\nimport java.lang.reflect.Method\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/15-16:34\n * 描    述：\n * 修订历史：\n * ================================================\n */\nabstract class MethodHandler {\n    val TAG = this::class.simpleName\n\n    /**\n     * @param originObject 原始对象\n     * @param method       需要被代理的方法\n     * @param args         代理方法的参数\n     * @return Any\n     */\n    @Throws(\n        InvocationTargetException::class,\n        IllegalAccessException::class,\n        NoSuchFieldException::class,\n        NoSuchMethodException::class\n    )\n    abstract fun onInvoke(originObject: Any, method: Method, args: Array<Any>?): Any?\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/RouteMockThread.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock;\n\nimport com.baidu.mapapi.model.LatLng;\nimport com.didichuxing.doraemonkit.util.LogHelper;\nimport com.didichuxing.doraemonkit.util.ThreadUtils;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class RouteMockThread extends Thread {\n    private static final String TAG = RouteMockThread.class.getSimpleName();\n    private volatile boolean mIsMocking = false;\n    private final Object mWaitMonitor = new Object();\n    // 线程暂停标识\n    private volatile boolean mSuspend = false;\n\n    private int mIndex = 0;\n    List<com.baidu.mapapi.model.LatLng> mPoints = new ArrayList<>();\n    // 坐标点移动间隔时间\n    private long mIntervalTime;\n    private RouteMockStatusCallback mRouteMockStatusCallback;\n\n    @Override\n    public void run() {\n        while (mPoints != null && mPoints.size() > 0 && mIndex < mPoints.size()) {\n            mIsMocking = true;\n            com.baidu.mapapi.model.LatLng latLng = mPoints.get(mIndex);\n            LogHelper.d(TAG, \"模拟导航=== step index: \" + mIndex + \" total steps \" + mPoints.size() + \" latitude \" + latLng.latitude + \" longitude \" + latLng.longitude + \" mIntervalTime \" + mIntervalTime);\n            ThreadUtils.runOnUiThread(() -> GpsMockManager.getInstance().performMock(new com.didichuxing.doraemonkit.model.LatLng(latLng.latitude, latLng.longitude)));\n\n            mIndex++;\n            synchronized (mWaitMonitor) {\n                try {\n                    if (mSuspend){\n                        ToastUtils.showShort(\"暂停模拟\");\n                        // 暂停\n                        mWaitMonitor.wait();\n                        ToastUtils.showShort(\"继续模拟\");\n                    }else {\n                        // 延时\n                        mWaitMonitor.wait(mIntervalTime);\n                    }\n                }catch (Exception e){\n                    reset();\n                    LogHelper.d(TAG, \"route mock thread wait error \" + mSuspend + \" \" + e.getMessage());\n                    break;\n                }\n            }\n        }\n\n        LogHelper.d(TAG, \"模拟线程===\");\n        reset();\n    }\n\n    private void reset() {\n        mIndex = 0;\n        mIsMocking = false;\n        ToastUtils.showShort(\"轨迹模拟已结束\");\n        if (mRouteMockStatusCallback != null) {\n            ThreadUtils.runOnUiThread(() -> mRouteMockStatusCallback.onRouteMockFinish());\n        }\n    }\n\n    /**\n     *\n     * @param suspend true: 暂停模拟; false:继续模拟.\n     */\n    public void notifyThread(boolean suspend) {\n        if (!suspend) {\n            synchronized (mWaitMonitor) {\n                mWaitMonitor.notifyAll();\n            }\n        }\n        this.mSuspend = suspend;\n    }\n\n    public boolean isSuspend() {\n        return this.mSuspend;\n    }\n\n    public boolean isMocking() {\n        return mIsMocking;\n    }\n\n    public void setMocking(boolean mocking) {\n        mIsMocking = mocking;\n    }\n\n    public List<LatLng> getPoints() {\n        return mPoints;\n    }\n\n    public void setPoints(List<LatLng> points) {\n        mPoints.clear();\n        mPoints.addAll(points);\n    }\n\n    public void setIntervalTime(long intervalTime) {\n        mIntervalTime = intervalTime;\n    }\n\n    public long getIntervalTime() {\n        return mIntervalTime;\n    }\n\n    public void setRouteMockStatusCallback(RouteMockStatusCallback routeMockStatusCallback) {\n        mRouteMockStatusCallback = routeMockStatusCallback;\n    }\n\n    public void clearRouteMockStatusCallback(){\n        mRouteMockStatusCallback = null;\n    }\n\n    public interface RouteMockStatusCallback{\n        void onRouteMockFinish();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/ServiceHookManager.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock\n\nimport android.content.Context\nimport android.os.IBinder\nimport com.didichuxing.doraemonkit.util.ReflectUtils\nimport java.lang.reflect.Proxy\nimport java.util.*\n\n/**\n * Created by wanglikun on 2019/4/2\n */\nobject ServiceHookManager {\n    //https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/os/ServiceManager.java\n    var isHookSuccess = false\n    private val mHookers: MutableList<BaseServiceHooker> = ArrayList()\n\n    const val TAG = \"ServiceHookManager\"\n\n    init {\n        init()\n    }\n\n    private fun init() {\n        //mHookers.add(new WifiHooker());\n//        mHookers.add(LocationHooker())\n//        mHookers.add(ActivityMangerHooker())\n//        mHookers.add(ActivityTaskMangerHooker())\n//        mHookers.add(PackageManagerHooker())\n        //mHookers.add(new TelephonyHooker());\n    }\n\n    /**\n     * 1、 IBinder是一个接口，它代表了一种跨进程传输的能力；只要实现了这个接口，就能将这个对象进行跨进程传递；这是驱动底层支持的；\n     * 在跨进程数据流经驱动的时候，驱动会识别IBinder类型的数据，从而自动完成不同进程Binder本地对象以及Binder代理对象的转换。\n     * 2、 IBinder负责数据传输，那么client与server端的调用契约（这里不用接口避免混淆）呢？这里的IInterface代表的就是远程server对象具有什么能力。\n     * 具体来说，就是aidl里面的接口。\n     * 3、Java层的Binder类，代表的其实就是Binder本地对象。BinderProxy类是Binder类的一个内部类，它代表远程进程的Binder对象的本地代理；\n     * 这两个类都继承自IBinder, 因而都具有跨进程传输的能力；实际上，在跨越进程的时候，Binder驱动会自动完成这两个对象的转换。\n     * 4、在使用AIDL的时候，编译工具会给我们生成一个Stub的静态内部类；这个类继承了Binder, 说明它是一个Binder本地对象，它实现了IInterface接口，\n     * 表明它具有远程Server承诺给Client的能力；Stub是一个抽象类，具体的IInterface的相关实现需要我们手动完成，这里使用了策略模式。\n     * @param context\n     */\n    fun install(context: Context) {\n        try {\n            //ServiceManager 中存储了本地Binder对象的代理对象\n            val serviceManager = ReflectUtils.reflect(\"android.os.ServiceManager\")\n            for (hooker in mHookers) {\n                hooker.init()\n                //BinderProxy ：反射得到具体的本地Binder对象的代理对象\n                val binderProxy =\n                    serviceManager.method(\"getService\", hooker.serviceName()).get<IBinder>()\n                        ?: continue\n                //LogHelper.i(TAG, \"service in ServiceManager====>\" + nativeBinderProxy);\n                //转化为 native Binder proxy 的 proxy\n                hooker.asInterface(binderProxy)\n                val classLoader = binderProxy.javaClass.classLoader\n                val iBinders = arrayOf<Class<*>>(IBinder::class.java)\n\n                //目的是为了为IBinder.queryLocalInterface每次返回都不为null 而是返回我们自定义的Binder对象\n                /* loader: 用哪个类加载器去加载代理对象\n                 *\n                 * interfaces:动态代理类需要实现的接口 指定newProxyInstance()方法返回的对象要实现哪些接口\n                 *\n                 * h:动态代理方法在执行时，会调用h里面的invoke方法去执行\n                 */\n                val handler = BinderHookHandler(binderProxy, hooker)\n                val proxy = Proxy.newProxyInstance(classLoader, iBinders, handler) as IBinder\n                //替换具体**Manager的mService\n                hooker.replaceBinderProxy(context, proxy)\n\n                //替换serviceManager中的缓存service\n                val cache = serviceManager.field(\"sCache\").get<MutableMap<String, IBinder>>()\n                cache[hooker.serviceName()] = proxy\n            }\n            isHookSuccess = true\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/TelephonyHooker.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock;\n\nimport android.content.Context;\nimport android.os.IBinder;\nimport android.telephony.CellInfo;\nimport android.telephony.gsm.GsmCellLocation;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Created by wanglikun on 2019/4/2\n */\npublic class TelephonyHooker extends BaseServiceHooker {\n    @Override\n    public String serviceName() {\n        return Context.TELEPHONY_SERVICE;\n    }\n\n    @Override\n    public String stubName() {\n        return \"com.android.internal.telephony.ITelephony$Stub\";\n    }\n\n    @Override\n    public Map<String, MethodHandler> registerMethodHandlers() {\n        Map<String, MethodHandler> methodHandlers = new HashMap<>();\n        methodHandlers.put(\"getAllCellInfo\", new GetAllCellInfoMethodHandler());\n        methodHandlers.put(\"getCellLocation\", new GetCellLocationMethodHandler());\n        methodHandlers.put(\"listen\", new ListenMethodHandler());\n        return methodHandlers;\n    }\n\n    @Override\n    public void replaceBinderProxy(Context context, IBinder proxy) {\n\n    }\n\n    static class GetAllCellInfoMethodHandler extends MethodHandler {\n\n        @Override\n        public Object onInvoke(Object originObject, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {\n            if (!GpsMockManager.getInstance().isMocking()) {\n                return method.invoke(originObject, args);\n            }\n            return new ArrayList<CellInfo>();\n        }\n    }\n\n    static class GetCellLocationMethodHandler extends MethodHandler {\n\n        @Override\n        public Object onInvoke(Object originObject, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {\n            if (!GpsMockManager.getInstance().isMocking()) {\n                return method.invoke(originObject, args);\n            }\n\n            return new GsmCellLocation();\n        }\n    }\n\n    static class ListenMethodHandler extends MethodHandler {\n        @Override\n        public Object onInvoke(Object originObject, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {\n            if (!GpsMockManager.getInstance().isMocking()) {\n                return method.invoke(originObject, args);\n            }\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/TencentLocationImp.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock;\n\nimport android.os.Bundle;\n\nimport com.tencent.map.geolocation.TencentLocation;\nimport com.tencent.map.geolocation.TencentPoi;\n\nimport java.util.List;\n\nclass TencentLocationImp implements TencentLocation {\n    public String mProvider;\n    public double mLatitude;\n    public double mLongitude;\n    public double mAltitude;\n    public float mAccuracy;\n    public String mName;\n    public String mAddress;\n    public String mNation;\n    public String mProvince;\n    public String mCity;\n    public String mDistrict;\n    public String mTown;\n    public String mVillage;\n    public String mStreet;\n    public String mStreetNo;\n    public Integer mAreaStat;\n    public List<TencentPoi> mPoiList;\n    public float mBearing;\n    public float mSpeed;\n    public long mTime;\n    public long mElapsedRealtime;\n    public int mGPSRssi;\n    public String mIndoorBuildingId;\n    public String mIndoorBuildingFloor;\n    public int mIndoorLocationType;\n    public double mDirection;\n    public String mCityCode;\n    public String mCityPhoneCode;\n    public int mCoordinateType;\n    public int mIsMockGps;\n    public Bundle mExtra;\n\n    public TencentLocationImp() {\n    }\n\n    public TencentLocationImp setIsMockGps(int isMockGps) {\n        this.mIsMockGps = isMockGps;\n        return this;\n    }\n\n    @Override\n    public String getProvider() {\n        return mProvider;\n    }\n\n    public TencentLocationImp setProvider(String provider) {\n        this.mProvider = provider;\n        return this;\n    }\n\n    @Override\n    public double getLatitude() {\n        return mLatitude;\n    }\n\n    public TencentLocationImp setLatitude(double latitude) {\n        this.mLatitude = latitude;\n        return this;\n    }\n\n    @Override\n    public double getLongitude() {\n        return mLongitude;\n    }\n\n    public TencentLocationImp setLongitude(double longitude) {\n        this.mLongitude = longitude;\n        return this;\n    }\n\n    @Override\n    public double getAltitude() {\n        return mAltitude;\n    }\n\n    public TencentLocationImp setAltitude(double altitude) {\n        this.mAltitude = altitude;\n        return this;\n    }\n\n    @Override\n    public float getAccuracy() {\n        return mAccuracy;\n    }\n\n    public TencentLocationImp setAccuracy(float accuracy) {\n        this.mAccuracy = accuracy;\n        return this;\n    }\n\n    @Override\n    public String getName() {\n        return mName;\n    }\n\n    public TencentLocationImp setName(String name) {\n        this.mName = name;\n        return this;\n    }\n\n    @Override\n    public String getAddress() {\n        return mAddress;\n    }\n\n    public TencentLocationImp setAddress(String address) {\n        this.mAddress = address;\n        return this;\n    }\n\n    @Override\n    public String getNation() {\n        return mNation;\n    }\n\n    public TencentLocationImp setNation(String nation) {\n        this.mNation = nation;\n        return this;\n    }\n\n    @Override\n    public String getProvince() {\n        return mProvince;\n    }\n\n    public TencentLocationImp setProvince(String province) {\n        this.mProvince = province;\n        return this;\n    }\n\n    @Override\n    public String getCity() {\n        return mCity;\n    }\n\n    public TencentLocationImp setCity(String city) {\n        this.mCity = city;\n        return this;\n    }\n\n    @Override\n    public String getDistrict() {\n        return mDistrict;\n    }\n\n    public TencentLocationImp setDistrict(String district) {\n        this.mDistrict = district;\n        return this;\n    }\n\n    @Override\n    public String getTown() {\n        return mTown;\n    }\n\n    public TencentLocationImp setTown(String town) {\n        this.mTown = town;\n        return this;\n    }\n\n    @Override\n    public String getVillage() {\n        return mVillage;\n    }\n\n    public TencentLocationImp setVillage(String village) {\n        this.mVillage = village;\n        return this;\n    }\n\n    @Override\n    public String getStreet() {\n        return mStreet;\n    }\n\n    public TencentLocationImp setStreet(String street) {\n        this.mStreet = street;\n        return this;\n    }\n\n    @Override\n    public String getStreetNo() {\n        return mStreetNo;\n    }\n\n    public TencentLocationImp setStreetNo(String streetNo) {\n        this.mStreetNo = streetNo;\n        return this;\n    }\n\n    @Override\n    public Integer getAreaStat() {\n        return mAreaStat;\n    }\n\n    public TencentLocationImp setAreaStat(Integer areaStat) {\n        this.mAreaStat = areaStat;\n        return this;\n    }\n\n    @Override\n    public List<TencentPoi> getPoiList() {\n        return mPoiList;\n    }\n\n    public TencentLocationImp setPoiList(List<TencentPoi> poiList) {\n        this.mPoiList = poiList;\n        return this;\n    }\n\n    @Override\n    public float getBearing() {\n        return mBearing;\n    }\n\n    public TencentLocationImp setBearing(float bearing) {\n        this.mBearing = bearing;\n        return this;\n    }\n\n    @Override\n    public float getSpeed() {\n        return mSpeed;\n    }\n\n    public TencentLocationImp setSpeed(float speed) {\n        this.mSpeed = speed;\n        return this;\n    }\n\n    @Override\n    public long getTime() {\n        return mTime;\n    }\n\n    public TencentLocationImp setTime(long time) {\n        this.mTime = time;\n        return this;\n    }\n\n    @Override\n    public long getElapsedRealtime() {\n        return mElapsedRealtime;\n    }\n\n    public TencentLocationImp setElapsedRealtime(long elapsedRealtime) {\n        this.mElapsedRealtime = elapsedRealtime;\n        return this;\n    }\n\n    @Override\n    public int getGPSRssi() {\n        return mGPSRssi;\n    }\n\n    public TencentLocationImp setGPSRssi(int gPSRssi) {\n        this.mGPSRssi = gPSRssi;\n        return this;\n    }\n\n    @Override\n    public String getIndoorBuildingId() {\n        return mIndoorBuildingId;\n    }\n\n    public TencentLocationImp setIndoorBuildingId(String indoorBuildingId) {\n        this.mIndoorBuildingId = indoorBuildingId;\n        return this;\n    }\n\n    @Override\n    public String getIndoorBuildingFloor() {\n        return mIndoorBuildingFloor;\n    }\n\n    public TencentLocationImp setIndoorBuildingFloor(String indoorBuildingFloor) {\n        this.mIndoorBuildingFloor = indoorBuildingFloor;\n        return this;\n    }\n\n    @Override\n    public int getIndoorLocationType() {\n        return mIndoorLocationType;\n    }\n\n    public TencentLocationImp setIndoorLocationType(int indoorLocationType) {\n        this.mIndoorLocationType = indoorLocationType;\n        return this;\n    }\n\n    @Override\n    public double getDirection() {\n        return mDirection;\n    }\n\n    public TencentLocationImp setDirection(double direction) {\n        this.mDirection = direction;\n        return this;\n    }\n\n    @Override\n    public String getCityCode() {\n        return mCityCode;\n    }\n\n    public TencentLocationImp setCityCode(String cityCode) {\n        this.mCityCode = cityCode;\n        return this;\n    }\n\n    @Override\n    public String getCityPhoneCode() {\n        return mCityPhoneCode;\n    }\n\n    public TencentLocationImp setCityPhoneCode(String cityPhoneCode) {\n        this.mCityPhoneCode = cityPhoneCode;\n        return this;\n    }\n\n    @Override\n    public int getCoordinateType() {\n        return mCoordinateType;\n    }\n\n    public TencentLocationImp setCoordinateType(int coordinateType) {\n        this.mCoordinateType = coordinateType;\n        return this;\n    }\n\n    @Override\n    public int isMockGps() {\n        return mIsMockGps;\n    }\n\n    @Override\n    public Bundle getExtra() {\n        return mExtra;\n    }\n\n    public TencentLocationImp setExtra(Bundle extra) {\n        this.mExtra = extra;\n        return this;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/gpsmock/WifiHooker.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.gpsmock;\n\nimport android.content.Context;\nimport android.net.wifi.ScanResult;\nimport android.net.wifi.WifiManager;\nimport android.os.IBinder;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Created by wanglikun on 2019/4/2\n */\npublic class WifiHooker extends BaseServiceHooker {\n    @Override\n    public String serviceName() {\n        return Context.WIFI_SERVICE;\n    }\n\n    @Override\n    public String stubName() {\n        return \"android.net.wifi.IWifiManager$Stub\";\n    }\n\n    @Override\n    public Map<String, MethodHandler> registerMethodHandlers() {\n        Map<String, MethodHandler> methodHandlers = new HashMap<>();\n        methodHandlers.put(\"getScanResults\", new GetScanResultsMethodHandler());\n        methodHandlers.put(\"getConnectionInfo\", new GetConnectionInfoMethodHandler());\n        return methodHandlers;\n    }\n\n    @Override\n    public void replaceBinderProxy(Context context, IBinder proxy) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {\n        WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);\n        if (wifiManager == null) {\n            return;\n        }\n        Class<?> wifiManagerClass = wifiManager.getClass();\n        Field mServiceField = wifiManagerClass.getDeclaredField(\"mService\");\n        mServiceField.setAccessible(true);\n        Class stub = Class.forName(stubName());\n        Method asInterface = stub.getDeclaredMethod(\"asInterface\", IBinder.class);\n        mServiceField.set(wifiManager, asInterface.invoke(null, proxy));\n        mServiceField.setAccessible(false);\n    }\n\n    static class GetScanResultsMethodHandler extends MethodHandler {\n\n        @Override\n        public Object onInvoke(Object originService, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {\n            if (!GpsMockManager.getInstance().isMocking()) {\n                return method.invoke(originService, args);\n            }\n            return new ArrayList<ScanResult>();\n        }\n\n\n    }\n\n    static class GetConnectionInfoMethodHandler extends MethodHandler {\n\n        @Override\n        public Object onInvoke(Object originObject, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {\n            if (!GpsMockManager.getInstance().isMocking()) {\n                return method.invoke(originObject, args);\n            }\n            try {\n                return Class.forName(\"android.net.wifi.WifiInfo\").newInstance();\n            } catch (InstantiationException e) {\n                e.printStackTrace();\n            } catch (ClassNotFoundException e) {\n                e.printStackTrace();\n            }\n            return method.invoke(originObject, args);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/common/AMapDrivingRouteOverLay.java",
    "content": "//package com.didichuxing.doraemonkit.kit.lbs.common;\n//\n//import android.content.Context;\n//import android.graphics.Color;\n//\n//import com.amap.api.maps.AMap;\n//import com.amap.api.maps.model.BitmapDescriptor;\n//import com.amap.api.maps.model.BitmapDescriptorFactory;\n//import com.amap.api.maps.model.LatLng;\n//import com.amap.api.maps.model.LatLngBounds;\n//import com.amap.api.maps.model.Marker;\n//import com.amap.api.maps.model.MarkerOptions;\n//import com.amap.api.maps.model.PolylineOptions;\n//import com.amap.api.services.core.LatLonPoint;\n//import com.amap.api.services.route.DrivePath;\n//import com.amap.api.services.route.DriveStep;\n//import com.amap.api.services.route.TMC;\n//import com.didichuxing.doraemonkit.R;\n//\n//import java.util.ArrayList;\n//import java.util.List;\n//\n//\n///**\n// * 导航路线图层类。\n// */\n//public class AMapDrivingRouteOverLay extends AMapRouteOverlay {\n//\n//    private DrivePath drivePath;\n//    private List<LatLonPoint> throughPointList;\n//    private List<Marker> throughPointMarkerList = new ArrayList<Marker>();\n//    private boolean throughPointMarkerVisible = true;\n//    private List<TMC> tmcs;\n//    private PolylineOptions mPolylineOptions;\n//    private PolylineOptions mPolylineOptionscolor = null;\n//    private Context mContext;\n//    private boolean isColorfulline = true;\n//    private float mWidth = 25;\n//    private List<LatLng> mLatLngsOfPath;\n//\n//    public void setIsColorfulline(boolean iscolorfulline) {\n//        this.isColorfulline = iscolorfulline;\n//    }\n//\n//    /**\n//     * 根据给定的参数，构造一个导航路线图层类对象。\n//     *\n//     * @param amap    地图对象。\n//     * @param path    导航路线规划方案。\n//     * @param context 当前的activity对象。\n//     */\n//    public AMapDrivingRouteOverLay(Context context, AMap amap, DrivePath path,\n//                                   LatLonPoint start, LatLonPoint end, List<LatLonPoint> throughPointList) {\n//        super(context);\n//        mContext = context;\n//        mAMap = amap;\n//        this.drivePath = path;\n//        startPoint = AMapUtil.INSTANCE.convertToLatLng(start);\n//        endPoint = AMapUtil.INSTANCE.convertToLatLng(end);\n//        this.throughPointList = throughPointList;\n//    }\n//\n//    public float getRouteWidth() {\n//        return mWidth;\n//    }\n//\n//    /**\n//     * 设置路线宽度\n//     *\n//     * @param mWidth 路线宽度，取值范围：大于0\n//     */\n//    public void setRouteWidth(float mWidth) {\n//        this.mWidth = mWidth;\n//    }\n//\n//    /**\n//     * 添加驾车路线添加到地图上显示。\n//     */\n//    public void addToMap() {\n//        initPolylineOptions();\n//        try {\n//            if (mAMap == null) {\n//                return;\n//            }\n//\n//            if (mWidth == 0 || drivePath == null) {\n//                return;\n//            }\n//            mLatLngsOfPath = new ArrayList<LatLng>();\n//            tmcs = new ArrayList<TMC>();\n//            List<DriveStep> drivePaths = drivePath.getSteps();\n//            mPolylineOptions.add(startPoint);\n//            for (DriveStep step : drivePaths) {\n//                List<LatLonPoint> latlonPoints = step.getPolyline();\n//                List<TMC> tmclist = step.getTMCs();\n//                tmcs.addAll(tmclist);\n//                addDrivingStationMarkers(step, convertToLatLng(latlonPoints.get(0)));\n//                for (LatLonPoint latlonpoint : latlonPoints) {\n//                    mPolylineOptions.add(convertToLatLng(latlonpoint));\n//                    mLatLngsOfPath.add(convertToLatLng(latlonpoint));\n//                }\n//            }\n//            mPolylineOptions.add(endPoint);\n//            if (startMarker != null) {\n//                startMarker.remove();\n//                startMarker = null;\n//            }\n//            if (endMarker != null) {\n//                endMarker.remove();\n//                endMarker = null;\n//            }\n//            addStartAndEndMarker();\n//            addThroughPointMarker();\n//            if (isColorfulline && tmcs.size() > 0) {\n//                colorWayUpdate(tmcs);\n//            } else {\n//                showPolyline();\n//            }\n//\n//        } catch (Throwable e) {\n//            e.printStackTrace();\n//        }\n//    }\n//\n//    /**\n//     * 初始化线段属性\n//     */\n//    private void initPolylineOptions() {\n//\n//        mPolylineOptions = null;\n//\n//        mPolylineOptions = new PolylineOptions();\n//        mPolylineOptions.color(getDriveColor()).width(getRouteWidth());\n//    }\n//\n//    private void showPolyline() {\n//        addPolyLine(mPolylineOptions);\n//    }\n//\n//\n//    /**\n//     * 根据不同的路段拥堵情况展示不同的颜色\n//     *\n//     * @param tmcSection\n//     */\n//    private void colorWayUpdate(List<TMC> tmcSection) {\n//        if (mAMap == null) {\n//            return;\n//        }\n//        if (tmcSection == null || tmcSection.size() <= 0) {\n//            return;\n//        }\n//        TMC segmentTrafficStatus;\n//        addPolyLine(new PolylineOptions().add(startPoint,\n//                AMapUtil.INSTANCE.convertToLatLng(tmcSection.get(0).getPolyline().get(0)))\n//                .setDottedLine(true));\n//        String status = \"\";\n//        for (int i = 0; i < tmcSection.size(); i++) {\n//            segmentTrafficStatus = tmcSection.get(i);\n//            List<LatLonPoint> mployline = segmentTrafficStatus.getPolyline();\n//            if (status.equals(segmentTrafficStatus.getStatus())) {\n//                for (int j = 1; j < mployline.size(); j++) {//第一个点和上一段最后一个点重复，这个不重复添加\n//                    mPolylineOptionscolor.add(AMapUtil.INSTANCE.convertToLatLng(mployline.get(j)));\n//                }\n//            } else {\n//                if (mPolylineOptionscolor != null) {\n//                    addPolyLine(mPolylineOptionscolor.color(getcolor(status)));\n//                }\n//                mPolylineOptionscolor = null;\n//                mPolylineOptionscolor = new PolylineOptions().width(getRouteWidth());\n//                status = segmentTrafficStatus.getStatus();\n//                for (int j = 0; j < mployline.size(); j++) {\n//                    mPolylineOptionscolor.add(AMapUtil.INSTANCE.convertToLatLng(mployline.get(j)));\n//                }\n//            }\n//            if (i == tmcSection.size() - 1 && mPolylineOptionscolor != null) {\n//                addPolyLine(mPolylineOptionscolor.color(getcolor(status)));\n//                addPolyLine(new PolylineOptions().add(\n//                        AMapUtil.INSTANCE.convertToLatLng(mployline.get(mployline.size() - 1)), endPoint)\n//                        .setDottedLine(true));\n//            }\n//        }\n//    }\n//\n//    private int getcolor(String status) {\n//\n//        if (status.equals(\"畅通\")) {\n//            return Color.GREEN;\n//        } else if (status.equals(\"缓行\")) {\n//            return Color.YELLOW;\n//        } else if (status.equals(\"拥堵\")) {\n//            return Color.RED;\n//        } else if (status.equals(\"严重拥堵\")) {\n//            return Color.parseColor(\"#990033\");\n//        } else {\n//            return Color.parseColor(\"#537edc\");\n//        }\n//    }\n//\n//    public LatLng convertToLatLng(LatLonPoint point) {\n//        return new LatLng(point.getLatitude(), point.getLongitude());\n//    }\n//\n//    /**\n//     * @param driveStep\n//     * @param latLng\n//     */\n//    private void addDrivingStationMarkers(DriveStep driveStep, LatLng latLng) {\n//        addStationMarker(new MarkerOptions()\n//                .position(latLng)\n//                .title(\"\\u65B9\\u5411:\" + driveStep.getAction()\n//                        + \"\\n\\u9053\\u8DEF:\" + driveStep.getRoad())\n//                .snippet(driveStep.getInstruction()).visible(nodeIconVisible)\n//                .anchor(0.5f, 0.5f).icon(getDriveBitmapDescriptor()));\n//    }\n//\n//    @Override\n//    protected LatLngBounds getLatLngBounds() {\n//        LatLngBounds.Builder b = LatLngBounds.builder();\n//        b.include(new LatLng(startPoint.latitude, startPoint.longitude));\n//        b.include(new LatLng(endPoint.latitude, endPoint.longitude));\n//        if (this.throughPointList != null && this.throughPointList.size() > 0) {\n//            for (int i = 0; i < this.throughPointList.size(); i++) {\n//                b.include(new LatLng(\n//                        this.throughPointList.get(i).getLatitude(),\n//                        this.throughPointList.get(i).getLongitude()));\n//            }\n//        }\n//        return b.build();\n//    }\n//\n//    public void setThroughPointIconVisibility(boolean visible) {\n//        try {\n//            throughPointMarkerVisible = visible;\n//            if (this.throughPointMarkerList != null\n//                    && this.throughPointMarkerList.size() > 0) {\n//                for (int i = 0; i < this.throughPointMarkerList.size(); i++) {\n//                    this.throughPointMarkerList.get(i).setVisible(visible);\n//                }\n//            }\n//        } catch (Throwable e) {\n//            e.printStackTrace();\n//        }\n//    }\n//\n//    private void addThroughPointMarker() {\n//        if (this.throughPointList != null && this.throughPointList.size() > 0) {\n//            LatLonPoint latLonPoint = null;\n//            for (int i = 0; i < this.throughPointList.size(); i++) {\n//                latLonPoint = this.throughPointList.get(i);\n//                if (latLonPoint != null) {\n//                    throughPointMarkerList.add(mAMap\n//                            .addMarker((new MarkerOptions())\n//                                    .position(\n//                                            new LatLng(latLonPoint\n//                                                    .getLatitude(), latLonPoint\n//                                                    .getLongitude()))\n//                                    .visible(throughPointMarkerVisible)\n//                                    .icon(getThroughPointBitDes())\n//                                    .title(\"\\u9014\\u7ECF\\u70B9\")));\n//                }\n//            }\n//        }\n//    }\n//\n//    private BitmapDescriptor getThroughPointBitDes() {\n//        return BitmapDescriptorFactory.fromResource(R.mipmap.dk_lbs_through);\n//\n//    }\n//\n//    /**\n//     * 获取两点间距离\n//     *\n//     * @param start\n//     * @param end\n//     * @return\n//     */\n//    public static int calculateDistance(LatLng start, LatLng end) {\n//        double x1 = start.longitude;\n//        double y1 = start.latitude;\n//        double x2 = end.longitude;\n//        double y2 = end.latitude;\n//        return calculateDistance(x1, y1, x2, y2);\n//    }\n//\n//    public static int calculateDistance(double x1, double y1, double x2, double y2) {\n//        final double NF_pi = 0.01745329251994329; // 弧度 PI/180\n//        x1 *= NF_pi;\n//        y1 *= NF_pi;\n//        x2 *= NF_pi;\n//        y2 *= NF_pi;\n//        double sinx1 = Math.sin(x1);\n//        double siny1 = Math.sin(y1);\n//        double cosx1 = Math.cos(x1);\n//        double cosy1 = Math.cos(y1);\n//        double sinx2 = Math.sin(x2);\n//        double siny2 = Math.sin(y2);\n//        double cosx2 = Math.cos(x2);\n//        double cosy2 = Math.cos(y2);\n//        double[] v1 = new double[3];\n//        v1[0] = cosy1 * cosx1 - cosy2 * cosx2;\n//        v1[1] = cosy1 * sinx1 - cosy2 * sinx2;\n//        v1[2] = siny1 - siny2;\n//        double dist = Math.sqrt(v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2]);\n//\n//        return (int) (Math.asin(dist / 2) * 12742001.5798544);\n//    }\n//\n//\n//    //获取指定两点之间固定距离点\n//    public static LatLng getPointForDis(LatLng sPt, LatLng ePt, double dis) {\n//        double lSegLength = calculateDistance(sPt, ePt);\n//        double preResult = dis / lSegLength;\n//        return new LatLng((ePt.latitude - sPt.latitude) * preResult + sPt.latitude, (ePt.longitude - sPt.longitude) * preResult + sPt.longitude);\n//    }\n//\n//    /**\n//     * 去掉DriveLineOverlay上的线段和标记。\n//     */\n//    @Override\n//    public void removeFromMap() {\n//        try {\n//            super.removeFromMap();\n//            if (this.throughPointMarkerList != null\n//                    && this.throughPointMarkerList.size() > 0) {\n//                for (int i = 0; i < this.throughPointMarkerList.size(); i++) {\n//                    this.throughPointMarkerList.get(i).remove();\n//                }\n//                this.throughPointMarkerList.clear();\n//            }\n//        } catch (Throwable e) {\n//            e.printStackTrace();\n//        }\n//    }\n//}"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/common/AMapRouteOverlay.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.lbs.common;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\n\nimport com.amap.api.maps.AMap;\nimport com.amap.api.maps.CameraUpdateFactory;\nimport com.amap.api.maps.model.BitmapDescriptor;\nimport com.amap.api.maps.model.BitmapDescriptorFactory;\nimport com.amap.api.maps.model.LatLng;\nimport com.amap.api.maps.model.LatLngBounds;\nimport com.amap.api.maps.model.Marker;\nimport com.amap.api.maps.model.MarkerOptions;\nimport com.amap.api.maps.model.Polyline;\nimport com.amap.api.maps.model.PolylineOptions;\nimport com.didichuxing.doraemonkit.R;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class AMapRouteOverlay {\n    protected List<Marker> stationMarkers = new ArrayList<Marker>();\n    protected List<Polyline> allPolyLines = new ArrayList<Polyline>();\n    protected Marker startMarker;\n    protected Marker endMarker;\n    protected LatLng startPoint;\n    protected LatLng endPoint;\n    protected AMap mAMap;\n    private Context mContext;\n    private Bitmap startBit, endBit, busBit, walkBit, driveBit;\n    protected boolean nodeIconVisible = true;\n\n    public AMapRouteOverlay(Context context) {\n        mContext = context;\n    }\n\n    /**\n     * 去掉BusRouteOverlay上所有的Marker。\n     *\n     * @since V2.1.0\n     */\n    public void removeFromMap() {\n        if (startMarker != null) {\n            startMarker.remove();\n\n        }\n        if (endMarker != null) {\n            endMarker.remove();\n        }\n        for (Marker marker : stationMarkers) {\n            marker.remove();\n        }\n        for (Polyline line : allPolyLines) {\n            line.remove();\n        }\n        destroyBit();\n    }\n\n    private void destroyBit() {\n        if (startBit != null) {\n            startBit.recycle();\n            startBit = null;\n        }\n        if (endBit != null) {\n            endBit.recycle();\n            endBit = null;\n        }\n        if (busBit != null) {\n            busBit.recycle();\n            busBit = null;\n        }\n        if (walkBit != null) {\n            walkBit.recycle();\n            walkBit = null;\n        }\n        if (driveBit != null) {\n            driveBit.recycle();\n            driveBit = null;\n        }\n    }\n\n    /**\n     * 给起点Marker设置图标，并返回更换图标的图片。如不用默认图片，需要重写此方法。\n     *\n     * @return 更换的Marker图片。\n     * @since V2.1.0\n     */\n    protected BitmapDescriptor getStartBitmapDescriptor() {\n        return BitmapDescriptorFactory.fromResource(R.mipmap.dk_lbs_start);\n    }\n\n    /**\n     * 给终点Marker设置图标，并返回更换图标的图片。如不用默认图片，需要重写此方法。\n     *\n     * @return 更换的Marker图片。\n     * @since V2.1.0\n     */\n    protected BitmapDescriptor getEndBitmapDescriptor() {\n        return BitmapDescriptorFactory.fromResource(R.mipmap.dk_lbs_end);\n    }\n\n    /**\n     * 给公交Marker设置图标，并返回更换图标的图片。如不用默认图片，需要重写此方法。\n     *\n     * @return 更换的Marker图片。\n     * @since V2.1.0\n     */\n    protected BitmapDescriptor getBusBitmapDescriptor() {\n        return BitmapDescriptorFactory.fromResource(R.mipmap.dk_lbs_bus);\n    }\n\n    /**\n     * 给步行Marker设置图标，并返回更换图标的图片。如不用默认图片，需要重写此方法。\n     *\n     * @return 更换的Marker图片。\n     * @since V2.1.0\n     */\n    protected BitmapDescriptor getWalkBitmapDescriptor() {\n        return BitmapDescriptorFactory.fromResource(R.mipmap.dk_lbs_man);\n    }\n\n    protected BitmapDescriptor getDriveBitmapDescriptor() {\n        return BitmapDescriptorFactory.fromResource(R.mipmap.dk_lbs_car);\n    }\n\n    protected void addStartAndEndMarker() {\n        startMarker = mAMap.addMarker((new MarkerOptions())\n                .position(startPoint).icon(getStartBitmapDescriptor())\n                .title(\"\\u8D77\\u70B9\"));\n        // startMarker.showInfoWindow();\n\n        endMarker = mAMap.addMarker((new MarkerOptions()).position(endPoint)\n                .icon(getEndBitmapDescriptor()).title(\"\\u7EC8\\u70B9\"));\n        // mAMap.moveCamera(CameraUpdateFactory.newLatLngZoom(startPoint,\n        // getShowRouteZoom()));\n    }\n\n    /**\n     * 移动镜头到当前的视角。\n     *\n     * @since V2.1.0\n     */\n    public void zoomToSpan() {\n        if (startPoint != null) {\n            if (mAMap == null)\n                return;\n            try {\n//\t\t\t\tLatLngBounds bounds = getLatLngBounds();\n//\t\t\t\tmAMap.animateCamera(CameraUpdateFactory\n//\t\t\t\t\t\t.newLatLngBounds(bounds, 10));\n                mAMap.animateCamera(CameraUpdateFactory.newLatLngZoom(startPoint, 10));\n            } catch (Throwable e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    protected LatLngBounds getLatLngBounds() {\n        LatLngBounds.Builder b = LatLngBounds.builder();\n        b.include(new LatLng(startPoint.latitude, startPoint.longitude));\n        b.include(new LatLng(endPoint.latitude, endPoint.longitude));\n        return b.build();\n    }\n\n    /**\n     * 路段节点图标控制显示接口。\n     *\n     * @param visible true为显示节点图标，false为不显示。\n     * @since V2.3.1\n     */\n    public void setNodeIconVisibility(boolean visible) {\n        try {\n            nodeIconVisible = visible;\n            if (this.stationMarkers != null && this.stationMarkers.size() > 0) {\n                for (int i = 0; i < this.stationMarkers.size(); i++) {\n                    this.stationMarkers.get(i).setVisible(visible);\n                }\n            }\n        } catch (Throwable e) {\n            e.printStackTrace();\n        }\n    }\n\n    protected void addStationMarker(MarkerOptions options) {\n        if (options == null) {\n            return;\n        }\n        Marker marker = mAMap.addMarker(options);\n        if (marker != null) {\n            stationMarkers.add(marker);\n        }\n\n    }\n\n    protected void addPolyLine(PolylineOptions options) {\n        if (options == null) {\n            return;\n        }\n        Polyline polyline = mAMap.addPolyline(options);\n        if (polyline != null) {\n            allPolyLines.add(polyline);\n        }\n    }\n\n    protected float getRouteWidth() {\n        return 18f;\n    }\n\n    protected int getWalkColor() {\n        return Color.parseColor(\"#6db74d\");\n    }\n\n    /**\n     * 自定义路线颜色。\n     * return 自定义路线颜色。\n     *\n     * @since V2.2.1\n     */\n    protected int getBusColor() {\n        return Color.parseColor(\"#537edc\");\n    }\n\n    protected int getDriveColor() {\n        return Color.parseColor(\"#537edc\");\n    }\n\n    // protected int getShowRouteZoom() {\n    // return 15;\n    // }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/common/AMapUtil.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.lbs.common\n\nimport com.amap.api.maps.model.LatLng\nimport com.amap.api.navi.model.NaviLatLng\nimport com.amap.api.navi.model.search.LatLonPoint\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2/25/21-15:48\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject AMapUtil {\n    /**\n     * 把LatLng对象转化为LatLonPoint对象\n     */\n    fun convertToLatLonPoint(latlng: NaviLatLng): LatLonPoint {\n        return LatLonPoint(latlng.latitude, latlng.longitude)\n    }\n\n    /**\n     * 把LatLonPoint对象转化为LatLon对象\n     */\n    fun convertToLatLng(latlng: NaviLatLng): LatLng {\n        return LatLng(latlng.latitude, latlng.longitude)\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/common/Constants.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.lbs.common;\n\nimport android.content.Context;\n\nimport com.didichuxing.doraemonkit.util.DoKitSPUtil;\nimport com.google.gson.Gson;\n\n/**\n * @Author: changzuozhen\n * @Date: 2019-10-15 15:37\n */\npublic final class Constants {\n    public static final String TAG = \"Constants\";\n    public static final Gson GSON = new Gson();\n    private static final String MOCK_LOCATION_KEY = \"mock_location_key\";\n    private static Context sContext;\n\n    private Constants() {\n    }\n\n    public static Context getContext() {\n        return sContext;\n    }\n\n    public static void setContext(Context context) {\n        sContext = context;\n    }\n\n    public static String loadLocationConfigJson() {\n        return DoKitSPUtil.getString(MOCK_LOCATION_KEY, \"\");\n    }\n\n    public static void saveLocationConfigJson(String locLocalConfigJson) {\n        DoKitSPUtil.putString(MOCK_LOCATION_KEY, locLocalConfigJson);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/common/LocInfo.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.lbs.common;\n\nimport android.util.ArrayMap;\n\nimport java.util.Arrays;\n\npublic class LocInfo {\n    public String locName;\n    public double lat;\n    public double lng;\n    public ArrayMap<String, Object> extra;\n\n    public LocInfo(String locName, double lat, double lng) {\n        this.locName = locName;\n        this.lat = lat;\n        this.lng = lng;\n\n    }\n\n    public static LocInfo fromGson(String gsonStr) {\n        return Constants.GSON.fromJson(gsonStr, LocInfo.class);\n    }\n\n    public ArrayMap<String, Object> getExtra() {\n        return extra;\n    }\n\n    public LocInfo setExtra(ArrayMap extra) {\n        this.extra = extra;\n        return this;\n    }\n\n    public LocInfo putExtra(String key, Object value) {\n        if (extra == null) {\n            extra = new ArrayMap();\n        }\n        extra.put(key, value);\n        return this;\n    }\n\n    public Object getExtra(String key) {\n        if (extra != null && extra.containsKey(key)) {\n            return extra.get(key);\n        }\n        return null;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        LocInfo locInfo = (LocInfo) o;\n        return Double.compare(locInfo.lat, lat) == 0 &&\n                Double.compare(locInfo.lng, lng) == 0 &&\n                isEquals(locName, locInfo.locName);\n    }\n\n    public static boolean isEquals(Object a, Object b) {\n        return (a == b) || (a != null && a.equals(b));\n    }\n\n    @Override\n    public int hashCode() {\n        return Arrays.hashCode(new Object[]{locName, lat, lng});\n    }\n\n    @Override\n    public String toString() {\n        return String.format(\"%s\\n纬:%s, 经:%s\", locName, lat, lng);\n    }\n\n    public String toGson() {\n        return Constants.GSON.toJson(this);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/manual/FloatGpsMockCache.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.lbs.manual;\n\nimport android.location.Location;\n\nimport com.didichuxing.doraemonkit.util.LogUtils;\nimport com.didichuxing.doraemonkit.gps_mock.lbs.preset.FloatGpsPresetMockCache;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockManager;\n\npublic class FloatGpsMockCache {\n    private static final String TAG = \"FloatGpsMockCache\";\n    private static Location sLastMock;\n    private static float[] sDistanceAndBearing = new float[2];\n\n    public static void mockToLocation(double latitude, double longitude) {\n        LogUtils.d(TAG, \"⚠️ mockToLocation() called with: latitude = [\" + latitude + \"], longitude = [\" + longitude + \"]\");\n        FloatGpsPresetMockCache.updateCustomMockLocation(latitude, longitude);\n\n        Location location = new Location(\"DOKIT_MOCK\");\n        location.setLatitude(latitude);\n        location.setLongitude(longitude);\n\n        if (sLastMock != null) {\n            Location.distanceBetween(sLastMock.getLatitude(), sLastMock.getLongitude(), latitude, longitude, sDistanceAndBearing);\n            location.setSpeed(sDistanceAndBearing[0]);\n            location.setBearing(sDistanceAndBearing[1]);\n        }\n        sLastMock = location;\n        GpsMockManager.getInstance().mockLocationWithNotify(sLastMock);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/manual/GPSTools.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.lbs.manual;\n\npublic final class GPSTools {\n    private GPSTools() {\n    }\n\n    public static double getLngDiff(double lat, double distance) {\n        double r = 6378137.0D * Math.cos(rad(lat));\n        double interval = distance / r;\n        return angle(interval);\n    }\n\n    public static double getLatDiff(double distance) {\n        return angle(distance / 6378137.0D);\n    }\n\n    private static double rad(double d) {\n        return d * 3.141592653589793D / 180.0D;\n    }\n\n    private static double angle(double rad) {\n        return rad * 180.0D / 3.141592653589793D;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/manual/PosAdjustKit.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.lbs.manual\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\n\n/**\n * Created by changzuozhen on 2021年1月22日\n * 功能不完美\n */\n//@AutoService(AbstractKit.class)\nclass PosAdjustKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_gps_mock_manual\n    override val icon: Int\n        get() = R.mipmap.dk_mock_location\n\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip))\n            return false\n        }\n        if (!DokitPluginConfig.SWITCH_GPS) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_gps_close_tip))\n            return false\n        }\n        DoKit.launchFloating<PosAdjustKitView>()\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n\n    override fun innerKitId(): String = \"dokit_sdk_lbs_ck_pos_adjust\"\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/manual/PosAdjustKitView.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.lbs.manual;\n\nimport android.content.ClipData;\nimport android.content.ClipboardManager;\nimport android.content.Context;\nimport android.os.Build;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.CompoundButton;\nimport android.widget.FrameLayout;\nimport android.widget.SeekBar;\nimport android.widget.Switch;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockManager;\nimport com.didichuxing.doraemonkit.gps_mock.lbs.common.LocInfo;\nimport com.didichuxing.doraemonkit.gps_mock.lbs.route.FloatGpsRouteMockCache;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.util.ToastUtils;\n\n/**\n * 定位微调悬浮窗\n * 功能缺陷\n */\npublic class PosAdjustKitView extends AbsDoKitView {\n    public static final String TAG = \"FloatGpsMockKitView\";\n    public static final int MIN_STEP = 5;\n    public static final int MAX_STEP = 500;\n    private View mRootView;\n    private static int sMockStep = 10;\n    private TextView mMockSpeedTv;\n    private SeekBar mSpeedSeekBar;\n    private TextView mEnvInfo = null;\n\n\n    @Override\n    public void onCreate(Context context) {\n\n    }\n\n    @Override\n    public View onCreateView(Context context, FrameLayout rootView) {\n        return LayoutInflater.from(context).inflate(R.layout.layout_mock_pos_adjust, rootView, false);\n    }\n\n    @Override\n    public void onViewCreated(FrameLayout rootView) {\n        mRootView = rootView;\n        setMockLocationConfig();\n    }\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.width = DoKitViewLayoutParams.WRAP_CONTENT;\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT;\n        params.gravity = Gravity.TOP | Gravity.LEFT;\n        params.x = 200;\n        params.y = 200;\n    }\n\n    private void setMockLocationConfig() {\n\n        // 单点模拟\n        // 开关\n        final Switch envSwitch = findViewById(R.id.env_switch3);\n        envSwitch.setChecked(GpsMockManager.getInstance().isMocking());\n        envSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {\n            @Override\n            public void onCheckedChanged(CompoundButton buttonView, boolean on) {\n                if (on) {\n                    GpsMockManager.getInstance().startMock();\n                } else {\n                    GpsMockManager.getInstance().stopMock();\n                }\n            }\n        });\n\n//        ViewSetupHelper.setupButton(mRootView, R.id.btn_reset, \"重试\", new View.OnClickListener() {\n//            @Override\n//            public void onClick(View v) {\n//                LatLng mocLoc = GpsMockConfig.getMockLocation();\n//                if (mocLoc != null) {\n//                    FloatGpsMockCache.mockToLocation(mocLoc.latitude, mocLoc.longitude);\n//                } else {\n//                    ToastUtils.showShort(\"没有设置过模拟位置\");\n//                }\n////                FloatGpsMockCache.mockToLocation(GpsMockManager.getInstance().getLatitude(), GpsMockManager.getInstance().getLongitude());\n//            }\n//        });\n\n        // 单点步进调节功能按钮\n//        ViewSetupHelper.setupButton(mRootView, R.id.btn_mock_gps_north, \"上\", new View.OnClickListener() {\n//            @Override\n//            public void onClick(View v) {\n//                moveMockLocation(sMockStep, 0);\n//            }\n//        });\n//        ViewSetupHelper.setupButton(mRootView, R.id.btn_mock_gps_south, \"下\", new View.OnClickListener() {\n//            @Override\n//            public void onClick(View v) {\n//                moveMockLocation(-sMockStep, 0);\n//            }\n//        });\n//        ViewSetupHelper.setupButton(mRootView, R.id.btn_mock_gps_west, \"左\", new View.OnClickListener() {\n//            @Override\n//            public void onClick(View v) {\n//                moveMockLocation(0, -sMockStep);\n//            }\n//        });\n//        ViewSetupHelper.setupButton(mRootView, R.id.btn_mock_gps_east, \"右\", new View.OnClickListener() {\n//            @Override\n//            public void onClick(View v) {\n//                moveMockLocation(0, sMockStep);\n//            }\n//        });\n\n        // 单点步进速度控制\n        mMockSpeedTv = findViewById(R.id.tv_mock_speed);\n        mSpeedSeekBar = findViewById(R.id.dk_sb_seekBar);\n        updateSpeedView(mMockSpeedTv, mSpeedSeekBar);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            mSpeedSeekBar.setMin(MIN_STEP);\n        }\n        mSpeedSeekBar.setMax(MAX_STEP);\n        mSpeedSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {\n            @Override\n            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {\n                if (fromUser) {\n                    sMockStep = progress;\n                    mMockSpeedTv.setText(String.format(\"步进速度控制:%s米\", sMockStep));\n                }\n            }\n\n            @Override\n            public void onStartTrackingTouch(SeekBar seekBar) {\n            }\n\n            @Override\n            public void onStopTrackingTouch(SeekBar seekBar) {\n            }\n        });\n        findViewById(R.id.dk_btn_downMockSpeed).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                sMockStep -= 10;\n                if (sMockStep < MIN_STEP) sMockStep = MIN_STEP;\n                updateSpeedView(mMockSpeedTv, mSpeedSeekBar);\n            }\n        });\n        findViewById(R.id.dk_btn_upMockSpeed).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                sMockStep += 10;\n                if (sMockStep > MAX_STEP) sMockStep = MAX_STEP;\n                updateSpeedView(mMockSpeedTv, mSpeedSeekBar);\n            }\n        });\n\n\n        mEnvInfo = this.findViewById(R.id.env_info3);\n        mEnvInfo.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                copyToClipboard(mEnvInfo.getText().toString());\n            }\n        });\n    }\n\n    private void updateSpeedView(TextView mockSpeed, SeekBar seekBar) {\n        seekBar.setProgress(sMockStep);\n        mMockSpeedTv.setText(String.format(\"步进速度控制:%s米\", sMockStep));\n    }\n\n    private void updateCurrentLocConfig(LocInfo currentConfig) {\n        if (mEnvInfo == null) return;\n        updateGsonInfo(currentConfig.toString(), mEnvInfo);\n    }\n\n    private void updateGsonInfo(String currentConfig, TextView envInfo) {\n        if (currentConfig == null) {\n            envInfo.setVisibility(View.GONE);\n        } else {\n            envInfo.setVisibility(View.VISIBLE);\n            envInfo.setText(currentConfig);\n        }\n    }\n\n    private void moveMockLocation(int latDistance, int lngDistance) {\n        try {\n            ((Switch) findViewById(R.id.env_switch3)).setChecked(true);\n            FloatGpsMockCache.mockToLocation(\n                    GpsMockManager.getInstance().getLatitude() + GPSTools.getLatDiff(latDistance),\n                    GpsMockManager.getInstance().getLongitude() + GPSTools.getLngDiff(GpsMockManager.getInstance().getLatitude(), lngDistance)\n            );\n            updateCurrentLocConfig(FloatGpsRouteMockCache.getCurrentLocConfig());\n        } catch (Exception e) {\n        }\n    }\n\n    private void copyToClipboard(String s) {\n        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);\n        String label = \"已拷贝值剪切板，可以到别的地方直接粘贴使用了\";\n        ToastUtils.showShort(label);\n        ClipData clip = ClipData.newPlainText(label, s);\n        clipboard.setPrimaryClip(clip);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/preset/FloatGpsPresetMockCache.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.lbs.preset;\n\nimport android.text.TextUtils;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.gps_mock.lbs.common.Constants;\nimport com.didichuxing.doraemonkit.gps_mock.lbs.common.LocInfo;\n\npublic class FloatGpsPresetMockCache {\n\n    public static LocInfo sMockLocInfo = null;\n\n    public FloatGpsPresetMockCache() {\n        if (!sMockLocationList.contains(sCustomLocInfo)) {\n            sMockLocationList.add(0, sCustomLocInfo);\n        }\n    }\n\n    public static MockLocList sMockLocationList = new MockLocList();\n    static LocInfo sCustomLocInfo = new LocInfo(\"自定义点\", 40.06, 116.233);\n\n    public static void addMockLocConfig(@NonNull LocInfo... locInfos) {\n        for (LocInfo locInfo : locInfos) {\n            if (!sMockLocationList.contains(locInfo)) {\n                sMockLocationList.add(locInfo);\n            }\n        }\n    }\n\n    public static void updateCustomMockLocation(double latitude, double longitude) {\n        sCustomLocInfo.lat = latitude;\n        sCustomLocInfo.lng = longitude;\n\n        if (!sMockLocationList.contains(sCustomLocInfo)) {\n            sMockLocationList.add(0, sCustomLocInfo);\n        }\n\n        setMockLocConfig(sCustomLocInfo);\n    }\n\n    public static void addMockLocationConfigWithJson(String jsonStr) {\n        MockLocList locationList = Constants.GSON.fromJson(jsonStr, MockLocList.class);\n        if (locationList == null) {\n            return;\n        }\n        for (LocInfo locInfo : locationList) {\n            addMockLocConfig(locInfo);\n        }\n    }\n\n    @Nullable\n    public static LocInfo getMockLocConfig() {\n        if (sMockLocInfo != null) {\n            return sMockLocInfo;\n        }\n        String configLocalJson = Constants.loadLocationConfigJson();\n        if (!TextUtils.isEmpty(configLocalJson)) {\n            sMockLocInfo = LocInfo.fromGson(configLocalJson);\n        }\n        return sMockLocInfo;\n    }\n\n    public static void setMockLocConfig(LocInfo locInfo) {\n        sMockLocInfo = locInfo;\n        Constants.saveLocationConfigJson(locInfo.toGson());\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/preset/MockLocList.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.lbs.preset;\n\nimport com.didichuxing.doraemonkit.gps_mock.lbs.common.LocInfo;\n\nimport java.util.ArrayList;\n\npublic class MockLocList extends ArrayList<LocInfo> {\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/route/AMapRealNavMockView.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.lbs.route\n\nimport android.content.Context\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport android.widget.ImageView\nimport android.widget.SeekBar\nimport android.widget.TextView\nimport androidx.core.view.children\nimport com.amap.api.navi.AMapNavi\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockManager\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams\nimport com.didichuxing.doraemonkit.util.ConvertUtils\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport kotlin.math.ceil\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2/25/21-14:44\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass AMapRealNavMockView : AbsDoKitView() {\n    companion object {\n        const val TAG = \"RouteKitView\"\n    }\n\n\n    private var mAMapNavi: AMapNavi? = null\n\n    override fun onCreate(context: Context?) {\n        mAMapNavi = AMapNavi.getInstance(activity.application)\n    }\n\n    override fun onCreateView(context: Context?, rootView: FrameLayout?): View {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_lbs_route, rootView, false)\n    }\n\n    var index = 0\n\n    lateinit var mSeekbar: SeekBar\n    lateinit var mTvTip: TextView\n    override fun onViewCreated(rootView: FrameLayout?) {\n        rootView?.let {\n            val close = it.findViewById<ImageView>(R.id.iv_close)\n            mSeekbar = it.findViewById<SeekBar>(R.id.seekbar)\n            mTvTip = it.findViewById<TextView>(R.id.tv_tip)\n            mSeekbar.progress = 0\n            val tvProgress = it.findViewById<TextView>(R.id.tv_progress)\n            tvProgress.text = \"当前导航进度: 0%\"\n            close.setOnClickListener {\n                DoKit.removeFloating(this)\n            }\n\n\n            mSeekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {\n                override fun onProgressChanged(\n                    seekBar: SeekBar?,\n                    progress: Int,\n                    fromUser: Boolean\n                ) {\n                    if (!GpsMockManager.getInstance().isMocking) {\n                        LogHelper.i(TAG, \"实时导航功能需要依赖位置模拟功能\")\n                        return\n                    }\n                    tvProgress.text = \"当前导航进度: $progress%\"\n                    mAMapNavi?.let { navi ->\n                        if (navi.naviPath.coordList.isEmpty()) {\n                            return\n                        }\n                        var index: Int =\n                            ceil(navi.naviPath.coordList.size * progress / 100.0).toInt()\n                        if (index > navi.naviPath.coordList.size - 1) {\n                            index = navi.naviPath.coordList.size - 1\n                        }\n                        val naviLatLng = navi.naviPath.coordList[index]\n                        //LogHelper.i(\"DoKit\", \"mock LatLng===>${naviLatLng}\")\n                        GpsMockManager.getInstance()\n                            .mockLocationWithNotify(naviLatLng.latitude, naviLatLng.longitude)\n\n                    }\n\n\n                }\n\n                override fun onStartTrackingTouch(seekBar: SeekBar?) {\n                }\n\n                override fun onStopTrackingTouch(seekBar: SeekBar?) {\n                }\n\n            })\n\n\n        }\n\n\n    }\n\n    override fun initDokitViewLayoutParams(params: DoKitViewLayoutParams?) {\n        params?.let {\n            it.width = ConvertUtils.dp2px(300.0f)\n            it.height = DoKitViewLayoutParams.WRAP_CONTENT\n            it.gravity = Gravity.TOP or Gravity.LEFT\n            it.x = 200\n            it.y = 200\n        }\n    }\n\n\n    override fun onResume() {\n        super.onResume()\n        traversAMapView(activity.window.decorView as ViewGroup)\n        if (mapView == null) {\n            mSeekbar.visibility = View.GONE\n            mTvTip.visibility = View.VISIBLE\n            return\n        }\n\n        mAMapNavi?.let {\n            it.naviPath?.let { path ->\n                if (path.coordList.isEmpty()) {\n                    mSeekbar.visibility = View.GONE\n                    mTvTip.visibility = View.VISIBLE\n                } else {\n                    mSeekbar.visibility = View.VISIBLE\n                    mTvTip.visibility = View.GONE\n                }\n            }\n\n        }\n\n    }\n\n    private var mapView: com.amap.api.maps.BaseMapView? = null\n\n    private fun traversAMapView(viewGroup: ViewGroup) {\n        viewGroup.children.forEach {\n            when (it) {\n                is com.amap.api.maps.BaseMapView -> {\n                    mapView = it\n                    return\n                }\n                is ViewGroup -> traversAMapView(it)\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/route/FloatGpsRouteMockCache.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.lbs.route;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.util.Pair;\n\n\nimport com.didichuxing.doraemonkit.gps_mock.lbs.common.LocInfo;\nimport com.didichuxing.doraemonkit.gps_mock.lbs.manual.FloatGpsMockCache;\nimport com.didichuxing.doraemonkit.gps_mock.lbs.preset.FloatGpsPresetMockCache;\n\nimport java.util.List;\nimport java.util.Timer;\nimport java.util.TimerTask;\n\n/**\n * @Author: changzuozhen\n * @Date: 2019-10-15 15:37\n */\npublic class FloatGpsRouteMockCache {\n    private static final String TAG = \"EnvironmentKit\";\n    private static List<Pair<Double, Double>> sMockRoute;\n    private static int sMockRouteIndex = 0;\n    private static Handler sHandler = new Handler(Looper.getMainLooper());\n    private static Timer sTimer;\n\n    public FloatGpsRouteMockCache() {\n    }\n\n    public static LocInfo getCurrentLocConfig() {\n        return FloatGpsPresetMockCache.getMockLocConfig();\n    }\n\n    public static void mockGpsRoute(Context context, List<Pair<Double, Double>> routes) {\n        sMockRoute = routes;\n        restartPlayMockRoute(context);\n        if (sIOnRouteChange != null) {\n            sIOnRouteChange.onRouteChange();\n        }\n    }\n\n    public static void restartPlayMockRoute(Context context) {\n        if (sMockRoute == null || sMockRoute.size() == 0) return;\n        sMockRouteIndex = 0;\n        resumePlayMockRoute(context);\n    }\n\n    public static void resumePlayMockRoute(Context context) {\n        if (sTimer != null) {\n            sTimer.cancel();\n        }\n        sTimer = new Timer();\n        final Context finalContext = context;\n        sTimer.scheduleAtFixedRate(new TimerTask() {\n            @Override\n            public void run() {\n                if (sMockRoute != null && sMockRoute.size() > sMockRouteIndex) {\n                    setMockRouteProgress(finalContext, sMockRouteIndex);\n                    sMockRouteIndex++;\n                } else {\n                    sTimer.cancel();\n                }\n            }\n        }, 0, 500);\n    }\n\n    public static void pausePlayMockRoute() {\n        if (sTimer != null) {\n            sTimer.cancel();\n        }\n    }\n\n    public static void clearMockRoute() {\n        if (sTimer != null) {\n            sTimer.cancel();\n            sMockRouteIndex = 0;\n            if (sMockRoute != null) {\n                sMockRoute.clear();\n            }\n        }\n    }\n\n    public static int getMockRouteProgress() {\n        return sMockRouteIndex;\n    }\n\n    public static int getRouteCount() {\n        return sMockRoute == null ? 0 : sMockRoute.size();\n    }\n\n    public static int setMockRouteProgress(Context context, int index) {\n        if (getRouteCount() > index && index > 0) {\n            sMockRouteIndex = index;\n            Pair<Double, Double> mockRoutePoint = sMockRoute.get(index);\n            FloatGpsMockCache.mockToLocation(mockRoutePoint.first, mockRoutePoint.second);\n            if (sIOnRouteChange != null) {\n                sIOnRouteChange.onIndexChange(sMockRouteIndex);\n            }\n        }\n        return sMockRouteIndex;\n    }\n\n    public interface IOnRouteChange {\n        void onRouteChange();\n\n        void onIndexChange(int index);\n    }\n\n    private static IOnRouteChange sIOnRouteChange;\n\n    public static void setRouteChangeListener(IOnRouteChange onRouteChange) {\n        sIOnRouteChange = onRouteChange;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/route/NaviSettings.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.lbs.route\n\n/**\n * Created by kuloud on 4/11/16.\n */\nobject NaviSettings {\n    const val POLYLINE_Z_INDEX = 1\n    const val ROUTE_SHADOW_Z_INDEX = -1\n    const val ROUTE_NORMAL_Z_INDEX = 0\n    const val TEXT_Z_INDEX = 4\n\n    /**\n     * 位置信息获取的频率(长)，单位为毫秒。\n     */\n    const val LOCATION_UPDATE_TIME_LONG_IN_MILLIS = 3000\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/lbs/route/RealNavMockKit.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.lbs.route\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.R\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.extension.hasThirdLib\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.google.auto.service.AutoService\n\n/**\n * Created by changzuozhen on 2021年1月22日\n */\n//@AutoService(AbstractKit::class)\nclass RealNavMockKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_kit_gps_mock_route\n    override val icon: Int\n        get() = R.mipmap.dk_mock_location_route\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip))\n            return false\n        }\n        if (!DokitPluginConfig.SWITCH_GPS) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_gps_close_tip))\n            return false\n        }\n\n\n\n        return when {\n            //高德地图导航\n            hasThirdLib(\"com.amap.api\", \"navi-3dmap\") -> {\n                DoKit.launchFloating(AMapRealNavMockView::class)\n                true\n            }\n//            //腾讯地图导航\n//            hasThirdLib(\"com.amap.api\", \"navi-3dmap\") -> {\n//                SimpleDokitStarter.startFloating(AMapRealNavMockView::class.java)\n//            }\n//            //百度地图导航\n//            hasThirdLib(\"com.amap.api\", \"navi-3dmap\") -> {\n//                SimpleDokitStarter.startFloating(AMapRealNavMockView::class.java)\n//            }\n//\n//            //滴滴地图导航\n//            hasThirdLib(\"com.amap.api\", \"navi-3dmap\") -> {\n//                SimpleDokitStarter.startFloating(AMapRealNavMockView::class.java)\n//            }\n\n            else -> {\n                ToastUtils.showShort(\"未检测到导航模块\")\n                false\n            }\n        }\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_lbs_ck_nav\"\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/location/GpsStatusUtil.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.location;\n\nimport android.location.GpsSatellite;\nimport android.location.GpsStatus;\nimport android.util.SparseArray;\n\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockManager;\nimport com.didichuxing.doraemonkit.util.ReflectUtils;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：4/25/21-20:34\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class GpsStatusUtil {\n\n    /**\n     * status 包装\n     *\n     * @param status\n     * @return new GpsStatus\n     */\n    public static GpsStatus wrap(GpsStatus status) {\n        if (GpsMockManager.getInstance().isMocking()) {\n            //在这里对GpsStatus进行修改\n            modifyGpsStatus(status);\n            return status;\n        }\n        return status;\n    }\n\n    private static void modifyGpsStatus(GpsStatus gpsStatus) {\n        try {\n            checkSatellite();\n            if (sSatellites != null) {\n                ReflectUtils.reflect(gpsStatus).field(\"mSatellites\", sSatellites);\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    private static volatile SparseArray<GpsSatellite> sSatellites;\n\n    private static void checkSatellite()  {\n        if (sSatellites == null) {\n            synchronized (GpsStatusUtil.class) {\n                if (sSatellites == null) {\n                    sSatellites = new SparseArray<>();\n\n                    GpsSatellite satellite = ReflectUtils.reflect(\"android.location.GpsSatellite\").newInstance(-5).get();\n                    ReflectUtils.reflect(satellite)\n                            .field(\"mUsedInFix\", true)\n                            .field(\"mValid\", true)\n                            .field(\"mHasEphemeris\", true)\n                            .field(\"mHasAlmanac\", true);\n\n                    for (int i = 0; i < 12; i++) {\n                        sSatellites.append(i, satellite);\n                    }\n                }\n\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/map/AMapLocationChangedListenerProxy.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.map;\n\nimport android.location.Location;\nimport android.location.LocationManager;\n\nimport com.amap.api.maps.LocationSource;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockManager;\nimport com.didichuxing.doraemonkit.util.CoordinateUtils;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.util.Arrays;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：3/25/21-16:08\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class AMapLocationChangedListenerProxy implements LocationSource.OnLocationChangedListener {\n    private static final String TAG = \"AMapLocationChangedListenerProxy\";\n    LocationSource.OnLocationChangedListener mOnLocationChangedListener;\n\n    public AMapLocationChangedListenerProxy(LocationSource.OnLocationChangedListener mOnLocationChangedListener) {\n        this.mOnLocationChangedListener = mOnLocationChangedListener;\n    }\n\n\n    @Override\n    public void onLocationChanged(Location location) {\n        if (GpsMockManager.getInstance().isMocking()) {\n            double[] res = CoordinateUtils.bd09ToGcj02(GpsMockManager.getInstance().getLongitude(), GpsMockManager.getInstance().getLatitude());\n            LogHelper.d(\"onLocationChanged\", \"===amap===origin_loc==>\" + location.toString()\n                + \"\\n before_trans_loc==>\" + GpsMockManager.getInstance().getLatitude() + \"   lng==>\" +GpsMockManager.getInstance().getLongitude()\n            + \"\\n after_trans_loc==>\" + Arrays.toString(res));\n            location.setLatitude(res[1]);\n            location.setLongitude(res[0]);\n            location.setProvider(LocationManager.GPS_PROVIDER);\n        }\n        if (mOnLocationChangedListener != null) {\n            mOnLocationChangedListener.onLocationChanged(location);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/map/AMapLocationClientProxy.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.map;\n\nimport android.location.LocationManager;\n\nimport com.amap.api.location.AMapLocation;\nimport com.amap.api.location.AMapLocationClient;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockManager;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.LocationBuilder;\nimport com.didichuxing.doraemonkit.util.CoordinateUtils;\nimport com.didichuxing.doraemonkit.util.LogHelper;\nimport com.didichuxing.doraemonkit.util.ReflectUtils;\n\nimport java.util.Arrays;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-15-16:18\n * 描    述：高德AMapLocationListenerProxy 通过ASM代码动态插入 高德不会跟随系统hook 腾讯和百度会跟随系统的hook\n * 修订历史：\n * ================================================\n */\npublic class AMapLocationClientProxy {\n    private static final String TAG = \"AMapLocationClientProxy\";\n\n    public static AMapLocation getLastKnownLocation(AMapLocationClient client) {\n        try {\n            if (client == null) {\n                return null;\n            }\n\n            if (GpsMockManager.getInstance().isMocking()) {\n                AMapLocation mapLocation = LocationBuilder.toAMapLocation(new LocationBuilder().build());\n                double[] res = CoordinateUtils.bd09ToGcj02(GpsMockManager.getInstance().getLongitude(), GpsMockManager.getInstance().getLatitude());\n                LogHelper.d(TAG, \"===amap===origin_loc==>\" + mapLocation.toString()\n                    + \"\\n before_trans_loc==>\" + GpsMockManager.getInstance().getLatitude() + \"   lng==>\" +GpsMockManager.getInstance().getLongitude()\n                    + \"\\n after_trans_loc==>\" + Arrays.toString(res));\n                mapLocation.setLatitude(res[1]);\n                mapLocation.setLongitude(res[0]);\n                mapLocation.setProvider(LocationManager.GPS_PROVIDER);\n                //通过反射强制改变p的值 原因:看mapLocation.setErrorCode\n                ReflectUtils.reflect(mapLocation).field(\"p\", 0);\n                mapLocation.setErrorInfo(\"success\");\n                return mapLocation;\n            } else {\n                //AMapLocationClient sdk 源码\n                com.loc.d b = ReflectUtils.reflect(client).field(\"b\").get();\n                if (b != null) {\n                    return b.e();\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return null;\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/map/AMapLocationListenerProxy.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.map;\n\nimport android.location.LocationManager;\n\nimport com.amap.api.location.AMapLocation;\nimport com.amap.api.location.AMapLocationListener;\nimport com.didichuxing.doraemonkit.config.GpsMockConfig;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockManager;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockProxyManager;\nimport com.didichuxing.doraemonkit.util.CoordinateUtils;\nimport com.didichuxing.doraemonkit.util.LogHelper;\nimport com.didichuxing.doraemonkit.util.ReflectUtils;\n\nimport java.util.Arrays;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-15-16:18\n * 描    述：高德AMapLocationListenerProxy 通过ASM代码动态插入 高德不会跟随系统hook 腾讯和百度会跟随系统的hook\n * 修订历史：\n * ================================================\n */\npublic class AMapLocationListenerProxy implements AMapLocationListener {\n    private static final String TAG = AMapLocationListenerProxy.class.getSimpleName();\n    public AMapLocationListener aMapLocationListener;\n\n    public AMapLocationListenerProxy(AMapLocationListener aMapLocationListener) {\n        this.aMapLocationListener = aMapLocationListener;\n        GpsMockProxyManager.INSTANCE.addAMapLocationListenerProxy(this);\n    }\n\n    @Override\n    public void onLocationChanged(AMapLocation mapLocation) {\n        if (GpsMockManager.getInstance().isMocking()) {\n            try {\n                double[] res = CoordinateUtils.bd09ToGcj02(GpsMockManager.getInstance().getLongitude(), GpsMockManager.getInstance().getLatitude());\n                LogHelper.d(TAG, \"===amap===origin_loc==>\" + mapLocation.toString()\n                    + \"\\n before_trans_loc==>\" + GpsMockManager.getInstance().getLatitude() + \"   lng==>\" +GpsMockManager.getInstance().getLongitude()\n                    + \"\\n after_trans_loc==>\" + Arrays.toString(res));\n                mapLocation.setLatitude(res[1]);\n                mapLocation.setLongitude(res[0]);\n                if (GpsMockManager.getInstance().isMockingRoute()) {\n                    mapLocation.setSpeed(GpsMockConfig.getRouteMockSpeed());\n                }\n                mapLocation.setProvider(LocationManager.GPS_PROVIDER);\n                //通过反射强制改变p的值 原因:看mapLocation.setErrorCode\n                ReflectUtils.reflect(mapLocation).field(\"p\", 0);\n                mapLocation.setErrorInfo(\"success\");\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n        }\n        if (aMapLocationListener != null) {\n            aMapLocationListener.onLocationChanged(mapLocation);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/map/AMapLocationSourceProxy.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.map;\n\nimport com.amap.api.maps.LocationSource;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：3/25/21-16:08\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class AMapLocationSourceProxy implements LocationSource {\n    LocationSource mLocationSource;\n\n    public AMapLocationSourceProxy(LocationSource mLocationSource) {\n        this.mLocationSource = mLocationSource;\n    }\n\n\n    @Override\n    public void activate(OnLocationChangedListener onLocationChangedListener) {\n        LogHelper.d(\"AMapLocationSourceProxy\", \"===amap===activate\");\n        if (mLocationSource != null) {\n            onLocationChangedListener = new AMapLocationChangedListenerProxy(onLocationChangedListener);\n            mLocationSource.activate(onLocationChangedListener);\n        }\n    }\n\n    @Override\n    public void deactivate() {\n        if (mLocationSource != null) {\n            mLocationSource.deactivate();\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/map/AMapNaviListenerProxy.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.map;\n\nimport androidx.annotation.NonNull;\n\nimport com.amap.api.navi.AMapNaviListener;\nimport com.amap.api.navi.model.AMapCalcRouteResult;\nimport com.amap.api.navi.model.AMapNaviLocation;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockProxyManager;\n\n/**\n * didi Create on 2023/3/28 .\n * <p>\n * Copyright (c) 2023/3/28 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/28 8:13 下午\n * @Description 用一句话说明文件功能\n */\n\npublic class AMapNaviListenerProxy extends AMapNaviListenerProxyNoOp {\n\n    public AMapNaviListener aMapNaviListener;\n    private AMapNaviListenerProxyDelegate delegate;\n\n    public AMapNaviListenerProxy(@NonNull AMapNaviListener naviListener) {\n        super(naviListener);\n        this.aMapNaviListener = naviListener;\n        delegate = new AMapNaviListenerProxyDelegate();\n        GpsMockProxyManager.INSTANCE.addAMapNaviListenerProxy(this);\n    }\n\n    @Override\n    public void onLocationChange(AMapNaviLocation aMapNaviLocation) {\n        delegate.onLocationChange(aMapNaviLocation);\n        super.onLocationChange(aMapNaviLocation);\n    }\n\n    @Override\n    public void onCalculateRouteSuccess(AMapCalcRouteResult aMapCalcRouteResult) {\n        delegate.onCalculateRouteSuccess(aMapCalcRouteResult);\n        super.onCalculateRouteSuccess(aMapCalcRouteResult);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/map/AMapNaviListenerProxyDelegate.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.map;\n\nimport com.amap.api.navi.AMapNavi;\nimport com.amap.api.navi.model.AMapCalcRouteResult;\nimport com.amap.api.navi.model.AMapNaviLink;\nimport com.amap.api.navi.model.AMapNaviLocation;\nimport com.amap.api.navi.model.AMapNaviPath;\nimport com.amap.api.navi.model.AMapNaviStep;\nimport com.amap.api.navi.model.NaviLatLng;\nimport com.baidu.mapapi.search.core.RouteNode;\nimport com.baidu.mapapi.utils.CoordinateConverter;\nimport com.didichuxing.doraemonkit.DoKit;\nimport com.didichuxing.doraemonkit.config.GpsMockConfig;\nimport com.didichuxing.doraemonkit.gps_mock.common.BdMapRouteData;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockManager;\nimport com.didichuxing.doraemonkit.util.CoordinateUtils;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-15-16:18\n * 描    述：高德AMapLocationListenerProxy 通过ASM代码动态插入 高德不会跟随系统hook 腾讯和百度会跟随系统的hook\n * 修订历史：\n * ================================================\n */\npublic class AMapNaviListenerProxyDelegate {\n    private static final String TAG = \"AMapNaviListenerProxy\";\n\n    public AMapNaviListenerProxyDelegate() {\n\n    }\n\n    public void onLocationChange(AMapNaviLocation aMapNaviLocation) {\n        if (GpsMockManager.getInstance().isMocking()) {\n            double[] res = CoordinateUtils.bd09ToGcj02(GpsMockManager.getInstance().getLongitude(), GpsMockManager.getInstance().getLatitude());\n            aMapNaviLocation.setCoord(new NaviLatLng(res[1], res[0]));\n            aMapNaviLocation.setSpeed(GpsMockConfig.getRouteMockSpeed());\n        }\n        LogHelper.d(TAG, \"===amap===onLocationChange\" + aMapNaviLocation.getCoord().toString());\n    }\n\n\n    public void onCalculateRouteSuccess(AMapCalcRouteResult aMapCalcRouteResult) {\n        if (GpsMockManager.getInstance().isMocking()) {\n            try {\n                // 重新做了路径规划,则尝试打断当前模拟.\n                GpsMockManager.getInstance().interruptRouteMockThread();\n                int[] routIds = aMapCalcRouteResult.getRouteid();\n                // 获取路线数据对象\n                HashMap<Integer, AMapNaviPath> naviPaths = AMapNavi.getInstance(DoKit.INSTANCE.getAPPLICATION()).getNaviPaths();\n                LogHelper.d(TAG, \"===amap===onCalculateRouteSuccess $naviPaths \" + naviPaths.size() + \" routeId \" + Arrays.toString(routIds));\n\n                CoordinateConverter converter = new CoordinateConverter().from(CoordinateConverter.CoordType.COMMON);\n                // 将所有点转换成百度坐标点\n                List<com.baidu.mapapi.model.LatLng> bdPoints = new ArrayList<>();\n                AMapNaviPath naviPath = naviPaths.get(routIds[0]);\n                List<AMapNaviStep> steps = naviPath.getSteps();\n                int linkSize = 0;\n                int stepSize = 0;\n                for (AMapNaviStep step : steps) {\n                    stepSize += step.getCoords().size();\n                    List<AMapNaviLink> links = step.getLinks();\n                    boolean notNavi = false;\n                    if (links != null && links.size() > 0) {\n                        for (AMapNaviLink link : links) {\n                            if (link == null) continue;\n                            int linkType = link.getLinkType(); // 获取道路类型 0-普通道路 1-航道 2-隧道 3-桥梁 4-高架桥 注意：该接口仅驾车模式有效\n                            int roadClass = link.getRoadClass(); //获取该Link道路等级 * 0 高速公路 * 1 国道 * 2 省道 * 3 县道 * 4 乡公路 * 5 县乡村内部道路 * 6 主要大街、城市快速道 * 7 主要道路 * 8 次要道路 * 9 普通道路 * 10 非导航道路\n                            List<NaviLatLng> coords = link.getCoords();\n                            if (coords == null) continue;\n                            linkSize += coords.size();\n                            // 过滤非开车导航坐标点(下车后)\n                            if (roadClass == 10) {\n                                notNavi = true;\n                                break;\n                            }\n                        }\n                    }\n\n                    if (notNavi) continue;\n                    for (NaviLatLng latLng : step.getCoords()) {\n                        com.baidu.mapapi.model.LatLng sourceLatLng = new com.baidu.mapapi.model.LatLng(latLng.getLatitude(), latLng.getLongitude());\n                        //转换坐标\n                        converter.coord(sourceLatLng);\n                        com.baidu.mapapi.model.LatLng desLatLng = converter.convert();\n                        bdPoints.add(desLatLng);\n                    }\n                }\n\n                LogHelper.d(TAG, \"===amap===total points==>\" + naviPath.getCoordList().size() + \" \" + naviPath.getCoordList().get(naviPath.getCoordList().size() - 1)\n                    + \" \\n\" + bdPoints.size() + \" \" + bdPoints.get(bdPoints.size() - 1)\n                    + \"\\nlinkSize=\" + linkSize + \" stepSize=\" + stepSize);\n                BdMapRouteData bdMapRouteData = new BdMapRouteData();\n                bdMapRouteData.setAllPoints(bdPoints);\n                bdMapRouteData.setTotalDistance(naviPath.getAllLength());\n\n                RouteNode startNode = new RouteNode();\n                com.baidu.mapapi.model.LatLng sourceStartLatLng = new com.baidu.mapapi.model.LatLng(naviPath.getStartPoint().getLatitude(), naviPath.getStartPoint().getLongitude());\n                com.baidu.mapapi.model.LatLng desStartLatLng = converter.coord(sourceStartLatLng).convert();\n                startNode.setLocation(desStartLatLng);\n                RouteNode terminalNode = new RouteNode();\n                com.baidu.mapapi.model.LatLng sourceTerminalLatLng = new com.baidu.mapapi.model.LatLng(naviPath.getEndPoint().getLatitude(), naviPath.getEndPoint().getLongitude());\n                com.baidu.mapapi.model.LatLng desTerminalLatLng = converter.coord(sourceTerminalLatLng).convert();\n                terminalNode.setLocation(desTerminalLatLng);\n                bdMapRouteData.setStartNode(startNode);\n                bdMapRouteData.setTerminalNode(terminalNode);\n                bdMapRouteData.setRouteDataFromBiz(true);\n\n                GpsMockManager.getInstance().setBdMockDrivingRouteLine(bdMapRouteData);\n\n            } catch (Exception e) {\n                LogHelper.e(TAG, \"===amap===onCalculateRouteSuccess error \" + e.getMessage());\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/map/AMapNaviListenerProxyNoOp.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.map;\n\nimport androidx.annotation.NonNull;\n\nimport com.amap.api.navi.AMapNaviListener;\nimport com.amap.api.navi.MyNaviListener;\nimport com.amap.api.navi.model.AMapCalcRouteResult;\nimport com.amap.api.navi.model.AMapLaneInfo;\nimport com.amap.api.navi.model.AMapModelCross;\nimport com.amap.api.navi.model.AMapNaviCameraInfo;\nimport com.amap.api.navi.model.AMapNaviCross;\nimport com.amap.api.navi.model.AMapNaviLocation;\nimport com.amap.api.navi.model.AMapNaviRouteNotifyData;\nimport com.amap.api.navi.model.AMapNaviTrafficFacilityInfo;\nimport com.amap.api.navi.model.AMapServiceAreaInfo;\nimport com.amap.api.navi.model.AimLessModeCongestionInfo;\nimport com.amap.api.navi.model.AimLessModeStat;\nimport com.amap.api.navi.model.InnerNaviInfo;\nimport com.amap.api.navi.model.NaviCongestionInfo;\nimport com.amap.api.navi.model.NaviInfo;\nimport com.amap.api.navi.model.NaviPath;\n\n/**\n * didi Create on 2023/3/28 .\n * <p>\n * Copyright (c) 2023/3/28 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/28 8:06 下午\n * @Description 用一句话说明文件功能\n */\n\npublic class AMapNaviListenerProxyNoOp implements MyNaviListener {\n\n\n    @NonNull\n    private AMapNaviListener naviListener;\n\n    public AMapNaviListenerProxyNoOp(@NonNull AMapNaviListener naviListener) {\n        this.naviListener = naviListener;\n    }\n\n    public AMapNaviListener getNaviListener() {\n        return naviListener;\n    }\n\n\n    @Override\n    public void onInitNaviFailure() {\n        naviListener.onInitNaviFailure();\n    }\n\n    @Override\n    public void onInitNaviSuccess() {\n        naviListener.onInitNaviSuccess();\n    }\n\n    @Override\n    public void onStartNavi(int i) {\n        naviListener.onStartNavi(i);\n    }\n\n    @Override\n    public void onTrafficStatusUpdate() {\n        naviListener.onTrafficStatusUpdate();\n    }\n\n    @Override\n    public void onLocationChange(AMapNaviLocation aMapNaviLocation) {\n        naviListener.onLocationChange(aMapNaviLocation);\n    }\n\n    @Override\n    public void onGetNavigationText(int i, String s) {\n        naviListener.onGetNavigationText(i, s);\n    }\n\n    @Override\n    public void onGetNavigationText(String s) {\n        naviListener.onGetNavigationText(s);\n    }\n\n    @Override\n    public void onEndEmulatorNavi() {\n        naviListener.onEndEmulatorNavi();\n    }\n\n    @Override\n    public void onArriveDestination() {\n        naviListener.onArriveDestination();\n    }\n\n    @Override\n    public void onCalculateRouteFailure(int i) {\n        naviListener.onCalculateRouteFailure(i);\n    }\n\n    @Override\n    public void onReCalculateRouteForYaw() {\n        naviListener.onReCalculateRouteForYaw();\n    }\n\n    @Override\n    public void onReCalculateRouteForTrafficJam() {\n        naviListener.onReCalculateRouteForTrafficJam();\n    }\n\n    @Override\n    public void onArrivedWayPoint(int i) {\n        naviListener.onArrivedWayPoint(i);\n    }\n\n    @Override\n    public void onGpsOpenStatus(boolean b) {\n        naviListener.onGpsOpenStatus(b);\n    }\n\n    @Override\n    public void onNaviInfoUpdate(NaviInfo naviInfo) {\n        naviListener.onNaviInfoUpdate(naviInfo);\n    }\n\n    @Override\n    public void updateCameraInfo(AMapNaviCameraInfo[] aMapNaviCameraInfos) {\n        naviListener.updateCameraInfo(aMapNaviCameraInfos);\n    }\n\n    @Override\n    public void updateIntervalCameraInfo(AMapNaviCameraInfo aMapNaviCameraInfo, AMapNaviCameraInfo aMapNaviCameraInfo1, int i) {\n        naviListener.updateIntervalCameraInfo(aMapNaviCameraInfo, aMapNaviCameraInfo1, i);\n    }\n\n    @Override\n    public void onServiceAreaUpdate(AMapServiceAreaInfo[] aMapServiceAreaInfos) {\n        naviListener.onServiceAreaUpdate(aMapServiceAreaInfos);\n    }\n\n    @Override\n    public void showCross(AMapNaviCross aMapNaviCross) {\n        naviListener.showCross(aMapNaviCross);\n    }\n\n    @Override\n    public void hideCross() {\n        naviListener.hideCross();\n    }\n\n    @Override\n    public void showModeCross(AMapModelCross aMapModelCross) {\n        naviListener.showModeCross(aMapModelCross);\n    }\n\n    @Override\n    public void hideModeCross() {\n        naviListener.hideModeCross();\n    }\n\n    @Override\n    public void showLaneInfo(AMapLaneInfo[] aMapLaneInfos, byte[] bytes, byte[] bytes1) {\n        naviListener.showLaneInfo(aMapLaneInfos, bytes, bytes1);\n    }\n\n    @Override\n    public void showLaneInfo(AMapLaneInfo aMapLaneInfo) {\n        naviListener.showLaneInfo(aMapLaneInfo);\n    }\n\n    @Override\n    public void hideLaneInfo() {\n        naviListener.hideLaneInfo();\n    }\n\n    @Override\n    public void onCalculateRouteSuccess(int[] ints) {\n        naviListener.onCalculateRouteSuccess(ints);\n    }\n\n    @Override\n    public void notifyParallelRoad(int i) {\n        naviListener.notifyParallelRoad(i);\n    }\n\n    @Override\n    public void OnUpdateTrafficFacility(AMapNaviTrafficFacilityInfo[] aMapNaviTrafficFacilityInfos) {\n        naviListener.OnUpdateTrafficFacility(aMapNaviTrafficFacilityInfos);\n    }\n\n    @Override\n    public void OnUpdateTrafficFacility(AMapNaviTrafficFacilityInfo aMapNaviTrafficFacilityInfo) {\n        naviListener.OnUpdateTrafficFacility(aMapNaviTrafficFacilityInfo);\n    }\n\n    @Override\n    public void updateAimlessModeStatistics(AimLessModeStat aimLessModeStat) {\n        naviListener.updateAimlessModeStatistics(aimLessModeStat);\n    }\n\n    @Override\n    public void updateAimlessModeCongestionInfo(AimLessModeCongestionInfo aimLessModeCongestionInfo) {\n        naviListener.updateAimlessModeCongestionInfo(aimLessModeCongestionInfo);\n    }\n\n    @Override\n    public void onPlayRing(int i) {\n        naviListener.onPlayRing(i);\n    }\n\n    @Override\n    public void onCalculateRouteSuccess(AMapCalcRouteResult aMapCalcRouteResult) {\n        naviListener.onCalculateRouteSuccess(aMapCalcRouteResult);\n    }\n\n    @Override\n    public void onCalculateRouteFailure(AMapCalcRouteResult aMapCalcRouteResult) {\n        naviListener.onCalculateRouteFailure(aMapCalcRouteResult);\n    }\n\n    @Override\n    public void onNaviRouteNotify(AMapNaviRouteNotifyData aMapNaviRouteNotifyData) {\n        naviListener.onNaviRouteNotify(aMapNaviRouteNotifyData);\n    }\n\n    @Override\n    public void onGpsSignalWeak(boolean b) {\n        naviListener.onGpsSignalWeak(b);\n    }\n\n    //----------MyNaviListener----------\n\n\n    @Override\n    public void onInnerNaviInfoUpdate(InnerNaviInfo innerNaviInfo) {\n        if (naviListener instanceof MyNaviListener) {\n            ((MyNaviListener) naviListener).onInnerNaviInfoUpdate(innerNaviInfo);\n        }\n    }\n\n    @Override\n    public void onInnerNaviInfoUpdate(InnerNaviInfo[] innerNaviInfos) {\n        if (naviListener instanceof MyNaviListener) {\n            ((MyNaviListener) naviListener).onInnerNaviInfoUpdate(innerNaviInfos);\n        }\n    }\n\n    @Override\n    public void onUpdateTmcStatus(NaviCongestionInfo naviCongestionInfo) {\n        if (naviListener instanceof MyNaviListener) {\n            ((MyNaviListener) naviListener).onUpdateTmcStatus(naviCongestionInfo);\n        }\n    }\n\n    @Override\n    public void onStopNavi() {\n        if (naviListener instanceof MyNaviListener) {\n            ((MyNaviListener) naviListener).onStopNavi();\n        }\n    }\n\n    @Override\n    public void onSelectRouteId(int i) {\n        if (naviListener instanceof MyNaviListener) {\n            ((MyNaviListener) naviListener).onSelectRouteId(i);\n        }\n    }\n\n    @Override\n    public void updateBackupPath(NaviPath[] naviPaths) {\n        if (naviListener instanceof MyNaviListener) {\n            ((MyNaviListener) naviListener).updateBackupPath(naviPaths);\n        }\n    }\n\n    @Override\n    public void onSuggestChangePath(long l, long l1, int i, String s) {\n        if (naviListener instanceof MyNaviListener) {\n            ((MyNaviListener) naviListener).onSuggestChangePath(l, l1, i, s);\n        }\n    }\n\n    @Override\n    public void onUpdateNaviPath() {\n        if (naviListener instanceof MyNaviListener) {\n            ((MyNaviListener) naviListener).onUpdateNaviPath();\n        }\n    }\n\n    @Override\n    public void onUpdateGpsSignalStrength(int i) {\n        if (naviListener instanceof MyNaviListener) {\n            ((MyNaviListener) naviListener).onUpdateGpsSignalStrength(i);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/map/BDAbsLocationListenerProxy.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.map;\n\nimport android.util.Log;\n\nimport com.baidu.location.BDAbstractLocationListener;\nimport com.baidu.location.BDLocation;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockManager;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockProxyManager;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-15-16:18\n * 描    述：百度BDLocationListenerProxy 通过ASM代码动态插入 高德不会跟随系统hook 腾讯和百度会跟随系统的hook\n * 修订历史：\n * ================================================\n */\npublic class BDAbsLocationListenerProxy extends BDAbstractLocationListener {\n    public BDAbstractLocationListener mBdLocationListener;\n\n    public BDAbsLocationListenerProxy(BDAbstractLocationListener bdLocationListener) {\n        this.mBdLocationListener = bdLocationListener;\n        GpsMockProxyManager.INSTANCE.addBDAbsLocationListenerProxy(this);\n    }\n\n\n    @Override\n    public void onReceiveLocation(BDLocation bdLocation) {\n        Log.d(\"onReceiveLocation\", \"BDAbsLocationListenerProxy: \" + bdLocation.toString() + \" mock lat\" + GpsMockManager.getInstance().getLatitude() + \" \" + \" mock lon\" + GpsMockManager.getInstance().getLongitude());\n        if (GpsMockManager.getInstance().isMocking()) {\n            try {\n                bdLocation.setLatitude(GpsMockManager.getInstance().getLatitude());\n                bdLocation.setLongitude(GpsMockManager.getInstance().getLongitude());\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n        }\n\n        if (mBdLocationListener != null) {\n            mBdLocationListener.onReceiveLocation(bdLocation);\n        }\n    }\n\n    @Override\n    public void onConnectHotSpotMessage(String s, int i) {\n        if (mBdLocationListener != null) {\n            mBdLocationListener.onConnectHotSpotMessage(s, i);\n        }\n\n    }\n\n    @Override\n    public void onLocDiagnosticMessage(int var1, int var2, String var3) {\n        if (mBdLocationListener != null) {\n            mBdLocationListener.onLocDiagnosticMessage(var1, var2, var3);\n        }\n\n\n    }\n\n    @Override\n    public void onReceiveVdrLocation(BDLocation var1) {\n        if (mBdLocationListener != null) {\n            mBdLocationListener.onReceiveVdrLocation(var1);\n        }\n\n\n    }\n\n    @Override\n    public void onReceiveLocString(String var1) {\n        if (mBdLocationListener != null) {\n            mBdLocationListener.onReceiveLocString(var1);\n        }\n\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/map/BDLocationListenerProxy.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.map;\n\nimport com.baidu.location.BDLocation;\nimport com.baidu.location.BDLocationListener;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockManager;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockProxyManager;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-15-16:18\n * 描    述：百度BDLocationListenerProxy 通过ASM代码动态插入 高德不会跟随系统hook 腾讯和百度会跟随系统的hook\n * 修订历史：\n * ================================================\n */\npublic class BDLocationListenerProxy implements BDLocationListener {\n    public BDLocationListener mBdLocationListener;\n\n    public BDLocationListenerProxy(BDLocationListener bdLocationListener) {\n        this.mBdLocationListener = bdLocationListener;\n        GpsMockProxyManager.INSTANCE.addBDLocationListenerProxy(this);\n    }\n\n\n    @Override\n    public void onReceiveLocation(BDLocation bdLocation) {\n        LogHelper.d(\"BDLocationListenerProxy\", bdLocation.toString() + \" mock lat\" + GpsMockManager.getInstance().getLatitude() + \" \" + \" mock lon\" + GpsMockManager.getInstance().getLongitude());\n        if (GpsMockManager.getInstance().isMocking()) {\n            try {\n                bdLocation.setLatitude(GpsMockManager.getInstance().getLatitude());\n                bdLocation.setLongitude(GpsMockManager.getInstance().getLongitude());\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n        }\n\n        if (mBdLocationListener != null) {\n            mBdLocationListener.onReceiveLocation(bdLocation);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/map/BDLocationUtil.java",
    "content": "//package com.didichuxing.doraemonkit.aop;\n//\n//import com.baidu.location.BDLocation;\n//import com.didichuxing.doraemonkit.kit.gpsmock.GpsMockManager;\n//\n///**\n// * ================================================\n// * 作    者：jint（金台）\n// * 版    本：1.0\n// * 创建日期：2019-12-18-20:01\n// * 描    述：对百度地图返回的位置信息进行hook\n// * 修订历史：\n// * ================================================\n// */\n//public class BDLocationUtil {\n//    public static BDLocation proxy(BDLocation bdLocation) {\n//        if (GpsMockManager.getInstance().isMocking()) {\n//            try {\n//                bdLocation.setLatitude(GpsMockManager.getInstance().getLatitude());\n//                bdLocation.setLongitude(GpsMockManager.getInstance().getLongitude());\n//            } catch (Exception e) {\n//                e.printStackTrace();\n//            }\n//        }\n//        return bdLocation;\n//    }\n//}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/map/DMapLocationListener.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.map\n\nimport android.location.Location\n\n\n/**\n * Created by mmxb on 2021/9/16.\n */\ninterface DMapLocationListener {\n    fun getDMapLocation(): Any\n    fun onLocationChange(location: Location?)\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/map/TencentLocationListenerProxy.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.map;\n\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockManager;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockProxyManager;\nimport com.didichuxing.doraemonkit.util.ReflectUtils;\nimport com.tencent.map.geolocation.TencentLocation;\nimport com.tencent.map.geolocation.TencentLocationListener;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-15-16:18\n * 描    述：腾讯TencentLocationListenerProxy 通过ASM代码动态插入\n * 修订历史：\n * ================================================\n */\npublic class TencentLocationListenerProxy implements TencentLocationListener {\n    private static final String TAG = \"TencentLocationListenerProxy\";\n    public TencentLocationListener mTencentLocationListener;\n\n    public TencentLocationListenerProxy(TencentLocationListener tencentLocationListener) {\n        this.mTencentLocationListener = tencentLocationListener;\n        GpsMockProxyManager.INSTANCE.addTencentLocationListenerProxy(this);\n    }\n\n    /**\n     * @param tencentLocation - 新的位置, *可能*来自缓存. 定位失败时 location 无效或者为 null\n     * @param error           - 错误码, 仅当错误码为 TencentLocation.ERROR_OK 时表示定位成功, 为其他值时表示定位失败\n     * @param reason          - 错误描述, 简要描述错误原因\n     */\n    @Override\n    public void onLocationChanged(TencentLocation tencentLocation, int error, String reason) {\n        if (GpsMockManager.getInstance().isMocking()) {\n            try {\n                //tencentLocation 的 对象类型为TxLocation\n                //LogHelper.i(TAG, \"matched==onLocationChanged==\" + tencentLocation.toString());\n                //b 为fb类型\n                Object b = ReflectUtils.reflect(tencentLocation).field(\"b\").get();\n                //a 为lat\n                ReflectUtils.reflect(b).field(\"a\", GpsMockManager.getInstance().getLatitude());\n                //b 为lng\n                ReflectUtils.reflect(b).field(\"b\", GpsMockManager.getInstance().getLongitude());\n                //LogHelper.i(TAG, \"b===>\" + b.toString());\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n\n        }\n        if (mTencentLocationListener != null) {\n            mTencentLocationListener.onLocationChanged(tencentLocation, error, reason);\n        }\n    }\n\n    @Override\n    public void onStatusUpdate(String s, int i, String s1) {\n        if (mTencentLocationListener != null) {\n            mTencentLocationListener.onStatusUpdate(s, i, s);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/map/ThirdMapLocationListenerUtil.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.map;\n\nimport android.location.LocationListener;\n\nimport com.amap.api.location.AMapLocationListener;\nimport com.amap.api.navi.AMapNaviListener;\nimport com.baidu.location.BDAbstractLocationListener;\nimport com.baidu.location.BDLocationListener;\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockProxyManager;\nimport com.tencent.map.geolocation.TencentLocationListener;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-12-15-16:18\n * 描    述：高德AMapLocationListenerProxy 通过ASM代码动态插入 高德不会跟随系统hook 腾讯和百度会跟随系统的hook\n * 修订历史：\n * ================================================\n */\npublic class ThirdMapLocationListenerUtil {\n    public static AMapLocationListenerProxy unRegisterAmapLocationListener(AMapLocationListener locationListener) {\n        return GpsMockProxyManager.INSTANCE.removeAMapLocationListener(locationListener);\n    }\n\n    public static AMapNaviListenerProxy unRegisterAmapNaviListener(AMapNaviListener naviListener) {\n        return GpsMockProxyManager.INSTANCE.removeAMapNaviListener(naviListener);\n    }\n\n    public static TencentLocationListenerProxy unRegisterTencentLocationListener(TencentLocationListener locationListener) {\n        return GpsMockProxyManager.INSTANCE.removeTencentLocationListener(locationListener);\n    }\n\n    public static BDLocationListenerProxy unRegisterBDLocationListener(BDLocationListener locationListener) {\n        return GpsMockProxyManager.INSTANCE.removeBDLocationListener(locationListener);\n    }\n\n    public static BDAbsLocationListenerProxy unRegisterBDLocationListener(BDAbstractLocationListener locationListener) {\n        return GpsMockProxyManager.INSTANCE.removeBDAbsLocationListener(locationListener);\n    }\n\n    public static void unRegisterDMapLocationListener(DMapLocationListener locationListener){\n        GpsMockProxyManager.INSTANCE.removeDMapLocationListener(locationListener);\n    }\n\n    public static void unRegisterLocationListener(LocationListener locationListener) {\n        GpsMockProxyManager.INSTANCE.removeLocationListener(locationListener);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/sysservicehook/ActivityMangerHooker.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.sysservicehook\n\nimport android.app.ActivityManager\nimport android.content.Context\nimport android.os.Build\nimport android.os.IBinder\nimport android.os.IInterface\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.BaseServiceHooker\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.MethodHandler\nimport com.didichuxing.doraemonkit.util.ReflectUtils\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/10-14:24\n * 描    述：ActivityManagerService hook\n * 修订历史：\n * ================================================\n */\nclass ActivityMangerHooker : BaseServiceHooker() {\n    companion object {\n        const val TAG = \"ActivityMangerHooker\"\n    }\n\n    override fun serviceName(): String {\n        return Context.ACTIVITY_SERVICE\n    }\n\n    override fun stubName(): String {\n        return \"android.app.IActivityManager\\$Stub\"\n    }\n\n    override fun registerMethodHandlers(): Map<String, MethodHandler> {\n        return mapOf(\"startActivity\" to StartActivityMethodHandler())\n    }\n\n    /**\n     * https://www.androidos.net.cn/android/7.0.0_r31/xref/frameworks/base/core/java/android/app/IActivityManager.java\n     */\n    override fun replaceBinderProxy(\n        context: Context,\n        proxy: IBinder\n    ) {\n\n        //val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE)\n\n        val customService =\n            ReflectUtils.reflect(stubName()).method(\"asInterface\", proxy).get<IInterface>()\n\n        when (Build.VERSION.SDK_INT) {\n            //16\n            Build.VERSION_CODES.JELLY_BEAN,\n                //17\n            Build.VERSION_CODES.JELLY_BEAN_MR1,\n                //18\n            Build.VERSION_CODES.JELLY_BEAN_MR2,\n                //19\n            Build.VERSION_CODES.KITKAT,\n                //20\n            Build.VERSION_CODES.KITKAT_WATCH,\n                //21\n            Build.VERSION_CODES.LOLLIPOP,\n                //22\n            Build.VERSION_CODES.LOLLIPOP_MR1,\n                //23\n            Build.VERSION_CODES.M,\n                //24\n            Build.VERSION_CODES.N,\n                //25\n            Build.VERSION_CODES.N_MR1\n            -> {\n                val gDefault =\n                    ReflectUtils.reflect(\"android.app.ActivityManagerNative\").field(\"gDefault\")\n                        .get<Any>()\n                //替换ActivityManagerNative中gDefault中mInstance的值为proxy\n                ReflectUtils.reflect(gDefault).field(\"mInstance\", customService)\n            }\n            //26\n            Build.VERSION_CODES.O,\n                //27\n            Build.VERSION_CODES.O_MR1,\n                //28\n            Build.VERSION_CODES.P,\n                //29\n            Build.VERSION_CODES.Q,\n            30 -> {\n                val iActivityManagerSingleton =\n                    ReflectUtils.reflect(ActivityManager::class.java)\n                        .field(\"IActivityManagerSingleton\")\n                        .get<Any>()\n                ReflectUtils.reflect(iActivityManagerSingleton).field(\"mInstance\", customService)\n            }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/sysservicehook/ActivityTaskMangerHooker.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.sysservicehook\n\nimport android.content.Context\nimport android.os.Build\nimport android.os.IBinder\nimport android.os.IInterface\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.BaseServiceHooker\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.MethodHandler\nimport com.didichuxing.doraemonkit.util.ReflectUtils\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/10-14:24\n * 描    述：ActivityTaskMangerHooker  hook android 10.0新增\n * 修订历史：\n * ================================================\n */\nclass ActivityTaskMangerHooker : BaseServiceHooker() {\n\n    /**\n     * Context.ACTIVITY_TASK_SERVICE 属于隐藏API\n     */\n    override fun serviceName(): String {\n        return \"activity_task\"\n    }\n\n    override fun stubName(): String {\n        return \"android.app.IActivityTaskManager\\$Stub\"\n    }\n\n    override fun registerMethodHandlers(): Map<String, MethodHandler> {\n        return mapOf(\"startActivity\" to StartActivityMethodHandler())\n    }\n\n    /**\n     * https://www.androidos.net.cn/android/7.0.0_r31/xref/frameworks/base/core/java/android/app/IActivityManager.java\n     */\n    override fun replaceBinderProxy(\n        context: Context,\n        proxy: IBinder\n    ) {\n        //val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE)\n        val customService =\n            ReflectUtils.reflect(stubName()).method(\"asInterface\", proxy).get<IInterface>()\n\n        when (Build.VERSION.SDK_INT) {\n            //29 android 10.0\n            Build.VERSION_CODES.Q,\n            30 -> {\n                val iActivityManagerSingleton =\n                    ReflectUtils.reflect(\"android.app.ActivityTaskManager\")\n                        .field(\"IActivityTaskManagerSingleton\")\n                        .get<Any>()\n                ReflectUtils.reflect(iActivityManagerSingleton).field(\"mInstance\", customService)\n            }\n        }\n\n    }\n\n\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/sysservicehook/GetInstalledApplicationsMethodHandler.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.sysservicehook\n\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.MethodHandler\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport java.lang.reflect.Method\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/15-17:21\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass GetInstalledApplicationsMethodHandler : MethodHandler() {\n    override fun onInvoke(originObject: Any, method: Method, args: Array<Any>?): Any? {\n        LogHelper.i(TAG, \"===method===$method  $args\")\n        return if (args == null) {\n            method.invoke(originObject, null)\n        } else {\n            method.invoke(originObject, *args)\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/sysservicehook/PackageManagerHooker.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.sysservicehook\n\nimport android.content.Context\nimport android.os.IBinder\nimport android.os.IInterface\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.BaseServiceHooker\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.MethodHandler\nimport com.didichuxing.doraemonkit.util.ReflectUtils\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/10-14:24\n * 描    述：ActivityTaskMangerHooker  hook android 10.0新增\n * 修订历史：\n * ================================================\n */\nclass PackageManagerHooker : BaseServiceHooker() {\n\n    /**\n     * Context.ACTIVITY_TASK_SERVICE 属于隐藏API\n     */\n    override fun serviceName(): String {\n        return \"package\"\n    }\n\n    override fun stubName(): String {\n        return \"android.content.pm.IPackageManager\\$Stub\"\n    }\n\n    override fun registerMethodHandlers(): Map<String, MethodHandler> {\n        return mapOf(\"getInstalledApplications\" to GetInstalledApplicationsMethodHandler())\n    }\n\n    /**\n     * https://www.androidos.net.cn/android/7.0.0_r31/xref/frameworks/base/core/java/android/app/IActivityManager.java\n     */\n    override fun replaceBinderProxy(\n        context: Context,\n        proxy: IBinder\n    ) {\n        val customService =\n            ReflectUtils.reflect(stubName()).method(\"asInterface\", proxy).get<IInterface>()\n\n        val activityThread =\n            ReflectUtils.reflect(\"android.app.ActivityThread\").method(\"currentActivityThread\")\n                .get<Any>()\n        //替换ActivityThread中的sPackageManager\n        ReflectUtils.reflect(activityThread).field(\"sPackageManager\", customService)\n        //ApplicationPackageManager\n        val packageManager = context.packageManager\n        ReflectUtils.reflect(packageManager).field(\"mPM\", customService)\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/sysservicehook/StartActivityMethodHandler.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.sysservicehook\n\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.MethodHandler\nimport java.lang.reflect.Method\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/15-17:21\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass StartActivityMethodHandler : MethodHandler() {\n    override fun onInvoke(originObject: Any, method: Method, args: Array<Any>?): Any? {\n        //LogHelper.i(TAG, \"===method===$method  $args\")\n        return if (args == null) {\n            method.invoke(originObject, null)\n        } else {\n            method.invoke(originObject, *args)\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/widget/CustomDialogFragment.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.widget;\n\n\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.DialogFragment;\n\nimport com.didichuxing.doraemonkit.gps_mock.R;\n\npublic class CustomDialogFragment extends DialogFragment {\n\n    private final IDialogHelper mDialogHelper;\n\n    public CustomDialogFragment(int contentLayoutId, IDialogHelper dialogHelper) {\n        super(contentLayoutId);\n        this.mDialogHelper = dialogHelper;\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setStyle(STYLE_NO_TITLE, R.style.Dialog_FullScreen);\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n\n        return super.onCreateView(inflater, container, savedInstanceState);\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        mDialogHelper.init(view);\n    }\n\n    @Override\n    public void onActivityCreated(@Nullable Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        mDialogHelper.onDestroy();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/widget/DrivingRouteOverlay.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.widget;\n\nimport com.baidu.mapapi.map.BaiduMap;\nimport com.baidu.mapapi.map.BitmapDescriptor;\nimport com.baidu.mapapi.map.BitmapDescriptorFactory;\nimport com.baidu.mapapi.map.Marker;\nimport com.baidu.mapapi.map.MarkerOptions;\nimport com.baidu.mapapi.map.Overlay;\nimport com.baidu.mapapi.map.OverlayOptions;\nimport com.baidu.mapapi.map.Polyline;\nimport com.baidu.mapapi.map.PolylineOptions;\nimport com.baidu.mapapi.model.LatLng;\nimport com.baidu.mapapi.search.route.DrivingRouteLine;\nimport com.didichuxing.doraemonkit.gps_mock.R;\nimport com.didichuxing.doraemonkit.gps_mock.common.BdMapRouteData;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 用于显示一条驾车路线的overlay，自3.4.0版本起可实例化多个添加在地图中显示，当数据中包含路况数据时，则默认使用路况纹理分段绘制\n */\npublic class DrivingRouteOverlay extends OverlayManager {\n\n    private DrivingRouteLine mRouteLine = null;\n    boolean focus = false;\n\n    /**\n     * 构造函数\n     *\n     * @param baiduMap 该DrivingRouteOvelray引用的 BaiduMap\n     */\n    public DrivingRouteOverlay(BaiduMap baiduMap) {\n        super(baiduMap);\n    }\n\n    // @Override\n    // public final List<OverlayOptions> getOverlayOptions() {\n    //     if (mRouteLine == null) {\n    //         return null;\n    //     }\n    //\n    //     List<OverlayOptions> overlayOptionses = new ArrayList<OverlayOptions>();\n    //     // step node\n    //     if (mRouteLine.getAllStep() != null\n    //         && mRouteLine.getAllStep().size() > 0) {\n    //\n    //         for (DrivingStep step : mRouteLine.getAllStep()) {\n    //             Bundle b = new Bundle();\n    //             b.putInt(\"index\", mRouteLine.getAllStep().indexOf(step));\n    //             if (step.getEntrance() != null) {\n    //                 overlayOptionses.add((new MarkerOptions())\n    //                     .position(step.getEntrance().getLocation())\n    //                     .anchor(0.5f, 0.5f)\n    //                     .zIndex(10)\n    //                     .rotate((360 - step.getDirection()))\n    //                     .extraInfo(b)\n    //                     .icon(BitmapDescriptorFactory\n    //                         .fromAssetWithDpi(\"Icon_line_node.png\")));\n    //             }\n    //             // 最后路段绘制出口点\n    //             if (mRouteLine.getAllStep().indexOf(step) == (mRouteLine\n    //                 .getAllStep().size() - 1) && step.getExit() != null) {\n    //                 overlayOptionses.add((new MarkerOptions())\n    //                     .position(step.getExit().getLocation())\n    //                     .anchor(0.5f, 0.5f)\n    //                     .zIndex(10)\n    //                     .icon(BitmapDescriptorFactory\n    //                         .fromAssetWithDpi(\"Icon_line_node.png\")));\n    //\n    //             }\n    //         }\n    //     }\n    //\n    //     if (mRouteLine.getStarting() != null) {\n    //         overlayOptionses.add((new MarkerOptions())\n    //             .position(mRouteLine.getStarting().getLocation())\n    //             .icon(getStartMarker() != null ? getStartMarker() :\n    //                 BitmapDescriptorFactory\n    //                     .fromAssetWithDpi(\"Icon_start.png\")).zIndex(10));\n    //     }\n    //     if (mRouteLine.getTerminal() != null) {\n    //         overlayOptionses\n    //             .add((new MarkerOptions())\n    //                 .position(mRouteLine.getTerminal().getLocation())\n    //                 .icon(getTerminalMarker() != null ? getTerminalMarker() :\n    //                     BitmapDescriptorFactory\n    //                         .fromAssetWithDpi(\"Icon_end.png\"))\n    //                 .zIndex(10));\n    //     }\n    //     // poly line\n    //     if (mRouteLine.getAllStep() != null\n    //         && mRouteLine.getAllStep().size() > 0) {\n    //\n    //         List<DrivingStep> steps = mRouteLine.getAllStep();\n    //         int stepNum = steps.size();\n    //\n    //\n    //         List<LatLng> points = new ArrayList<LatLng>();\n    //         ArrayList<Integer> traffics = new ArrayList<Integer>();\n    //         int totalTraffic = 0;\n    //         for (int i = 0; i < stepNum; i++) {\n    //             if (i == stepNum - 1) {\n    //                 points.addAll(steps.get(i).getWayPoints());\n    //             } else {\n    //                 // points.addAll(steps.get(i).getWayPoints().subList(0, steps.get(i).getWayPoints().size() - 1));\n    //                 LogHelper.d(\"路径坐标DrivingRouteOverlay\", \"i \" + i + \" \" + steps.get(i).getWayPoints());\n    //                 points.addAll(steps.get(i).getWayPoints());\n    //             }\n    //\n    //             totalTraffic += steps.get(i).getWayPoints().size() - 1;\n    //             if (steps.get(i).getTrafficList() != null && steps.get(i).getTrafficList().length > 0) {\n    //                 for (int j = 0; j < steps.get(i).getTrafficList().length; j++) {\n    //                     traffics.add(steps.get(i).getTrafficList()[j]);\n    //                 }\n    //             }\n    //         }\n    //\n    //         //            Bundle indexList = new Bundle();\n    //         //            if (traffics.size() > 0) {\n    //         //                int raffic[] = new int[traffics.size()];\n    //         //                int index = 0;\n    //         //                for (Integer tempTraff : traffics) {\n    //         //                    raffic[index] = tempTraff.intValue();\n    //         //                    index++;\n    //         //                }\n    //         //                indexList.putIntArray(\"indexs\", raffic);\n    //         //            }\n    //         boolean isDotLine = false;\n    //\n    //         if (traffics != null && traffics.size() > 0) {\n    //             isDotLine = true;\n    //         }\n    //         PolylineOptions option = new PolylineOptions().points(points).textureIndex(traffics)\n    //             .width(7).dottedLine(isDotLine).focus(true)\n    //             .color(getLineColor() != 0 ? getLineColor() : Color.argb(178, 0, 78, 255)).zIndex(0);\n    //         if (isDotLine) {\n    //             option.customTextureList(getCustomTextureList());\n    //         }\n    //         overlayOptionses.add(option);\n    //     }\n    //     return overlayOptionses;\n    // }\n\n    @Override\n    public final List<OverlayOptions> getOverlayOptions() {\n        if (mBdMapRouteData == null) {\n            return null;\n        }\n\n        List<OverlayOptions> overlayOptionses = new ArrayList<OverlayOptions>();\n        // step node\n        // if (mRouteLine.getAllStep() != null\n        //     && mRouteLine.getAllStep().size() > 0) {\n        //\n        //     for (DrivingStep step : mRouteLine.getAllStep()) {\n        //         Bundle b = new Bundle();\n        //         b.putInt(\"index\", mRouteLine.getAllStep().indexOf(step));\n        //         if (step.getEntrance() != null) {\n        //             overlayOptionses.add((new MarkerOptions())\n        //                 .position(step.getEntrance().getLocation())\n        //                 .anchor(0.5f, 0.5f)\n        //                 .zIndex(10)\n        //                 .rotate((360 - step.getDirection()))\n        //                 .extraInfo(b)\n        //                 .icon(BitmapDescriptorFactory\n        //                     .fromAssetWithDpi(\"Icon_line_node.png\")));\n        //         }\n        //         // 最后路段绘制出口点\n        //         if (mRouteLine.getAllStep().indexOf(step) == (mRouteLine\n        //             .getAllStep().size() - 1) && step.getExit() != null) {\n        //             overlayOptionses.add((new MarkerOptions())\n        //                 .position(step.getExit().getLocation())\n        //                 .anchor(0.5f, 0.5f)\n        //                 .zIndex(10)\n        //                 .icon(BitmapDescriptorFactory\n        //                     .fromAssetWithDpi(\"Icon_line_node.png\")));\n        //\n        //         }\n        //     }\n        // }\n\n        if (mBdMapRouteData.getStartNode() != null) {\n            overlayOptionses.add((new MarkerOptions())\n                .position(mBdMapRouteData.getStartNode().getLocation())\n                .icon(getStartMarker() != null ? getStartMarker() :\n                    BitmapDescriptorFactory\n                        .fromAssetWithDpi(\"Icon_start.png\")).zIndex(10));\n        }\n        if (mBdMapRouteData.getTerminalNode() != null) {\n            overlayOptionses\n                .add((new MarkerOptions())\n                    .position(mBdMapRouteData.getTerminalNode().getLocation())\n                    .icon(getTerminalMarker() != null ? getTerminalMarker() :\n                        BitmapDescriptorFactory\n                            .fromAssetWithDpi(\"Icon_end.png\"))\n                    .zIndex(10));\n        }\n        // poly line\n        if (mBdMapRouteData.getAllPoints() != null && mBdMapRouteData.getAllPoints().size() > 0) {\n\n            // List<DrivingStep> steps = mRouteLine.getAllStep();\n            // int stepNum = steps.size();\n\n\n            // List<LatLng> points = new ArrayList<LatLng>();\n            // ArrayList<Integer> traffics = new ArrayList<Integer>();\n            // int totalTraffic = 0;\n            // for (int i = 0; i < stepNum; i++) {\n            //     if (i == stepNum - 1) {\n            //         points.addAll(steps.get(i).getWayPoints());\n            //     } else {\n            //         // points.addAll(steps.get(i).getWayPoints().subList(0, steps.get(i).getWayPoints().size() - 1));\n            //         LogHelper.d(\"路径坐标DrivingRouteOverlay\", \"i \" + i + \" \" + steps.get(i).getWayPoints());\n            //         points.addAll(steps.get(i).getWayPoints());\n            //     }\n            //\n            //     totalTraffic += steps.get(i).getWayPoints().size() - 1;\n            //     if (steps.get(i).getTrafficList() != null && steps.get(i).getTrafficList().length > 0) {\n            //         for (int j = 0; j < steps.get(i).getTrafficList().length; j++) {\n            //             traffics.add(steps.get(i).getTrafficList()[j]);\n            //         }\n            //     }\n            // }\n\n            //            Bundle indexList = new Bundle();\n            //            if (traffics.size() > 0) {\n            //                int raffic[] = new int[traffics.size()];\n            //                int index = 0;\n            //                for (Integer tempTraff : traffics) {\n            //                    raffic[index] = tempTraff.intValue();\n            //                    index++;\n            //                }\n            //                indexList.putIntArray(\"indexs\", raffic);\n            //            }\n            // boolean isDotLine = false;\n\n            // if (traffics != null && traffics.size() > 0) {\n            //     isDotLine = true;\n            // }\n            // PolylineOptions option = new PolylineOptions().points(mBdMapRouteData.getAllPoints())\n            //     .width(7).dottedLine(isDotLine).focus(true)\n            //     .color(getLineColor() != 0 ? getLineColor() : Color.argb(178, 0, 78, 255)).zIndex(0);\n            // if (isDotLine) {\n            //     option.customTextureList(getCustomTextureList());\n            // }\n            overlayOptionses.add(getPolylineOptions(mBdMapRouteData.getAllPoints(), COLOR_ROUTE));\n        }\n        return overlayOptionses;\n    }\n\n    @Override\n    public PolylineOptions getPolylineOptions(List<LatLng> points, int lineColor){\n        return new PolylineOptions().points(points)\n            .width(10).focus(true)\n            .color(getLineColor() != 0 ? getLineColor() : lineColor).zIndex(0);\n    }\n\n\n    /**\n     * 设置路线数据\n     *\n     * @param routeLine 路线数据\n     */\n    public void setData(DrivingRouteLine routeLine) {\n        this.mRouteLine = routeLine;\n    }\n\n    public void setBdMapRouteData(BdMapRouteData bdMapRouteData){\n        this.mBdMapRouteData = bdMapRouteData;\n    }\n\n    /**\n     * 覆写此方法以改变默认起点图标\n     *\n     * @return 起点图标\n     */\n    public BitmapDescriptor getStartMarker() {\n        return BitmapDescriptorFactory.fromResourceWithDpi(R.mipmap.dk_icon_route_start, 0);\n    }\n\n    /**\n     * 覆写此方法以改变默认绘制颜色\n     *\n     * @return 线颜色\n     */\n    public int getLineColor() {\n        return 0;\n    }\n\n    public List<BitmapDescriptor> getCustomTextureList() {\n        ArrayList<BitmapDescriptor> list = new ArrayList<BitmapDescriptor>();\n        list.add(BitmapDescriptorFactory.fromAsset(\"Icon_road_blue_arrow.png\"));\n        list.add(BitmapDescriptorFactory.fromAsset(\"Icon_road_green_arrow.png\"));\n        list.add(BitmapDescriptorFactory.fromAsset(\"Icon_road_yellow_arrow.png\"));\n        list.add(BitmapDescriptorFactory.fromAsset(\"Icon_road_red_arrow.png\"));\n        list.add(BitmapDescriptorFactory.fromAsset(\"Icon_road_nofocus.png\"));\n        return list;\n    }\n\n    /**\n     * 覆写此方法以改变默认终点图标\n     *\n     * @return 终点图标\n     */\n    public BitmapDescriptor getTerminalMarker() {\n        return BitmapDescriptorFactory.fromResourceWithDpi(R.mipmap.dk_icon_route_end, 0);\n    }\n\n    /**\n     * 覆写此方法以改变默认点击处理\n     *\n     * @param i 线路节点的 index\n     * @return 是否处理了该点击事件\n     */\n    public boolean onRouteNodeClick(int i) {\n        if (mRouteLine.getAllStep() != null\n            && mRouteLine.getAllStep().get(i) != null) {\n            LogHelper.i(\"baidumapsdk\", \"DrivingRouteOverlay onRouteNodeClick\");\n        }\n        return false;\n    }\n\n    @Override\n    public final boolean onMarkerClick(Marker marker) {\n        for (Overlay mMarker : mOriginRouteOverlayList) {\n            if (mMarker instanceof Marker && mMarker.equals(marker)) {\n                if (marker.getExtraInfo() != null) {\n                    onRouteNodeClick(marker.getExtraInfo().getInt(\"index\"));\n                }\n            }\n        }\n        return true;\n    }\n\n    @Override\n    public boolean onPolylineClick(Polyline polyline) {\n        boolean flag = false;\n        for (Overlay mPolyline : mOriginRouteOverlayList) {\n            if (mPolyline instanceof Polyline && mPolyline.equals(polyline)) {\n                // 选中\n                flag = true;\n                break;\n            }\n        }\n        setFocus(flag);\n        return true;\n    }\n\n    public void setFocus(boolean flag) {\n        focus = flag;\n        for (Overlay mPolyline : mOriginRouteOverlayList) {\n            if (mPolyline instanceof Polyline) {\n                // 选中\n                ((Polyline) mPolyline).setFocus(flag);\n\n                break;\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/widget/IDialogHelper.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.widget;\n\nimport android.view.View;\n\npublic interface IDialogHelper {\n    void init(View root);\n\n    void onDestroy();\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/widget/OverlayManager.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.widget;\n\nimport static com.baidu.mapapi.map.BaiduMap.OnMarkerClickListener;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.Canvas;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\n\nimport com.baidu.mapapi.BMapManager;\nimport com.baidu.mapapi.map.BaiduMap;\nimport com.baidu.mapapi.map.BaiduMap.OnPolylineClickListener;\nimport com.baidu.mapapi.map.BitmapDescriptorFactory;\nimport com.baidu.mapapi.map.CircleOptions;\nimport com.baidu.mapapi.map.DotOptions;\nimport com.baidu.mapapi.map.MapStatus;\nimport com.baidu.mapapi.map.MapStatusUpdateFactory;\nimport com.baidu.mapapi.map.Marker;\nimport com.baidu.mapapi.map.MarkerOptions;\nimport com.baidu.mapapi.map.Overlay;\nimport com.baidu.mapapi.map.OverlayOptions;\nimport com.baidu.mapapi.map.Stroke;\nimport com.baidu.mapapi.model.LatLng;\nimport com.baidu.mapapi.model.LatLngBounds;\nimport com.didichuxing.doraemonkit.gps_mock.R;\nimport com.didichuxing.doraemonkit.gps_mock.common.BdMapRouteData;\nimport com.didichuxing.doraemonkit.util.ConvertUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 该类提供一个能够显示和管理多个Overlay的基类\n * <p>\n * 复写{@link #getOverlayOptions()} 设置欲显示和管理的Overlay列表\n * </p>\n * <p>\n * 通过\n * {@link BaiduMap#setOnMarkerClickListener(OnMarkerClickListener)}\n * 将覆盖物点击事件传递给OverlayManager后，OverlayManager才能响应点击事件。\n * <p>\n * 复写{@link #onMarkerClick(Marker)} 处理Marker点击事件\n * </p>\n */\npublic abstract class OverlayManager implements OnMarkerClickListener, OnPolylineClickListener {\n\n    BaiduMap mBaiduMap = null;\n    protected BdMapRouteData mBdMapRouteData;\n    private List<OverlayOptions> mOverlayOptionList = null;\n\n    List<Overlay> mOriginRouteOverlayList = null;\n\n    // 漂移路线\n    // private Overlay mDriftRouteOverlay;\n    // private Overlay mDriftRandomOverlay;\n    // 实时坐标\n    private Overlay mLocMarkOverlay;\n    private Bitmap mLocMarkBitmap;\n    // 漂移范围的指示圆\n    private Overlay mLocRangeCircleOverlay;\n\n    public static final int COLOR_ROUTE_DRIFT = 0x80FF0000;\n    public static final int COLOR_ROUTE = 0xBF004EFF;\n    public static final int COLOR_LOST_LOC_POINT = 0xFFFFFF00; //黄色\n    public static final int COLOR_LOST_LOC_LINE = 0xFFD2691E;\n    public static final int LOST_LOC_POINT_DOT_RADIUS = 16;\n\n    /**\n     * 通过一个BaiduMap 对象构造\n     *\n     * @param baiduMap\n     */\n    public OverlayManager(BaiduMap baiduMap) {\n        mBaiduMap = baiduMap;\n        // mBaiduMap.setOnMarkerClickListener(this);\n        if (mOverlayOptionList == null) {\n            mOverlayOptionList = new ArrayList<OverlayOptions>();\n        }\n        if (mOriginRouteOverlayList == null) {\n            mOriginRouteOverlayList = new ArrayList<Overlay>();\n        }\n    }\n\n    /**\n     * 覆写此方法设置要管理的Overlay列表\n     *\n     * @return 管理的Overlay列表\n     */\n    public abstract List<OverlayOptions> getOverlayOptions();\n\n    public abstract OverlayOptions getPolylineOptions(List<LatLng> points, int lineColor);\n\n    /**\n     * 将所有Overlay 添加到地图上\n     */\n    public final void addToMap(boolean lostLocChecked) {\n        if (mBaiduMap == null) {\n            return;\n        }\n\n        removeAllRouteFromMap();\n        List<OverlayOptions> overlayOptions = getOverlayOptions();\n        if (overlayOptions != null) {\n            mOverlayOptionList.addAll(overlayOptions);\n        }\n\n        for (OverlayOptions option : mOverlayOptionList) {\n            mOriginRouteOverlayList.add(mBaiduMap.addOverlay(option));\n        }\n\n        List<LatLng> points = mBdMapRouteData.getOriginRouteLostLocPoints();\n        if (points == null || points.size() <= 0) return;\n\n        OverlayOptions originRouteLostLocOverlayOption = getPolylineOptions(points, COLOR_LOST_LOC_LINE);\n        mOriginRouteOverlayList.add(mBaiduMap.addOverlay(originRouteLostLocOverlayOption));\n\n        if (lostLocChecked && mBdMapRouteData != null && mBdMapRouteData.mOriginRouteStartLostPoint != null && mBdMapRouteData.mOriginRouteEndLostPoint != null) {\n            mOriginRouteOverlayList.add(addDotToMap(mBdMapRouteData.mOriginRouteStartLostPoint, LOST_LOC_POINT_DOT_RADIUS, COLOR_LOST_LOC_POINT));\n            mOriginRouteOverlayList.add(addDotToMap(mBdMapRouteData.mOriginRouteEndLostPoint, LOST_LOC_POINT_DOT_RADIUS, COLOR_LOST_LOC_POINT));\n        }\n    }\n\n    List<Overlay> mDriftRouteOverlayList = new ArrayList<>();\n\n    public final void addDriftRouteToMap(BdMapRouteData bdMapRouteData, int lineColor, boolean lostLocChecked) {\n        List<LatLng> points = bdMapRouteData.getRouteDriftPoints();\n        if (points == null || points.size() <= 0) return;\n\n        if (mBaiduMap == null) {\n            return;\n        }\n\n        removeDriftRouteFromMap();\n        OverlayOptions driftOverlayOption = getPolylineOptions(points, lineColor);\n        mDriftRouteOverlayList.add(mBaiduMap.addOverlay(driftOverlayOption));\n\n        if (lostLocChecked) {\n            if (bdMapRouteData.mRouteDriftStartLostPoint != null) {\n                mDriftRouteOverlayList.add(addDotToMap(bdMapRouteData.mRouteDriftStartLostPoint, LOST_LOC_POINT_DOT_RADIUS, COLOR_LOST_LOC_POINT));\n            }\n\n            if (bdMapRouteData.mRouteDriftEndLostPoint != null) {\n                mDriftRouteOverlayList.add(addDotToMap(bdMapRouteData.mRouteDriftEndLostPoint, LOST_LOC_POINT_DOT_RADIUS, COLOR_LOST_LOC_POINT));\n            }\n        }\n    }\n\n    public final Overlay addDotToMap(LatLng point, int radiusPx, int color) {\n        return addPointMark(point, radiusPx, color);\n    }\n\n    private final List<Overlay> mDriftRandomDotOverlay = new ArrayList<>();\n\n    public final void addDriftRandomDotToMap(List<LatLng> points, int color) {\n        if (points == null || points.size() <= 0) return;\n\n        if (mBaiduMap == null) {\n            return;\n        }\n\n        removeDriftRouteFromMap();\n        for (LatLng latLng : points) {\n            mDriftRandomDotOverlay.add(addDotToMap(latLng, 8, color));\n        }\n    }\n\n    private final List<Overlay> mDriftRandomOverlayList = new ArrayList<>();\n\n    public final void addDriftRandomRouteToMap(BdMapRouteData bdMapRouteData, int lineColor, boolean lostLocChecked) {\n        List<LatLng> points = bdMapRouteData.getRandomDriftPoints();\n        if (points == null || points.size() <= 0) return;\n\n        if (mBaiduMap == null) {\n            return;\n        }\n\n        removeDriftRouteFromMap();\n        OverlayOptions driftOverlayOption = getPolylineOptions(points, lineColor);\n        mDriftRandomOverlayList.add(mBaiduMap.addOverlay(driftOverlayOption));\n\n        if (lostLocChecked) {\n            if (bdMapRouteData.mRandomDriftStartLostPoint != null) {\n                mDriftRandomOverlayList.add(addDotToMap(bdMapRouteData.mRandomDriftStartLostPoint, LOST_LOC_POINT_DOT_RADIUS, COLOR_LOST_LOC_POINT));\n            }\n\n            if (bdMapRouteData.mRandomDriftEndLostPoint != null) {\n                mDriftRandomOverlayList.add(addDotToMap(bdMapRouteData.mRandomDriftEndLostPoint, LOST_LOC_POINT_DOT_RADIUS, COLOR_LOST_LOC_POINT));\n            }\n        }\n    }\n\n    private Bitmap getBitmap(int vectorDrawableId) {\n        Bitmap bitmap;\n        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {\n            Drawable vectorDrawable = BMapManager.getContext().getDrawable(vectorDrawableId);\n            bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),\n                vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);\n            Canvas canvas = new Canvas(bitmap);\n            vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());\n            vectorDrawable.draw(canvas);\n        } else {\n            bitmap = BitmapFactory.decodeResource(BMapManager.getContext().getResources(), vectorDrawableId);\n        }\n\n        return bitmap;\n    }\n\n\n    public final void addLocMark(LatLng latLng) {\n        if (mLocMarkOverlay != null) {\n            mLocMarkOverlay.remove();\n        }\n        if (mLocMarkBitmap == null || mLocMarkBitmap.isRecycled()) {\n            mLocMarkBitmap = getBitmap(R.drawable.dk_icon_loc_circle_shape);\n        }\n        mLocMarkOverlay = mBaiduMap.addOverlay(new MarkerOptions()\n            .position(latLng)\n            .icon(\n                BitmapDescriptorFactory\n                    //                    .fromResource(R.mipmap.dk_icon_cur_loc))\n                    .fromBitmap(mLocMarkBitmap))\n            .yOffset(ConvertUtils.dp2px(18))\n            .animateType(MarkerOptions.MarkerAnimateType.grow)\n            .zIndex(10));\n    }\n\n\n    public final void addCircleOptions(LatLng latLng, int radius) {\n        if (mLocRangeCircleOverlay != null) {\n            mLocRangeCircleOverlay.remove();\n        }\n\n        mLocRangeCircleOverlay = mBaiduMap.addOverlay(new CircleOptions()\n            .center(latLng)\n            .radius(radius) // 单位:米\n            .fillColor(0xAAFFFF00)\n            .stroke(new Stroke(2, 0xAA00FF00)));\n    }\n\n    public final Overlay addPointMark(LatLng latLng, int radiusPx, int color) {\n        return mBaiduMap.addOverlay(new DotOptions()\n            .center(latLng)\n            .radius(radiusPx) // 单位:像素\n            .color(color));\n    }\n\n    public final void removeAllRouteFromMap() {\n        removeOriginRouteFromMap();\n        removeDriftRouteFromMap();\n    }\n\n    public final void removeDriftRouteFromMap() {\n        if (mBaiduMap == null) {\n            return;\n        }\n\n        if (mDriftRouteOverlayList.size() > 0) {\n            for (Overlay overlay : mDriftRouteOverlayList) {\n                overlay.remove();\n            }\n        }\n        if (mDriftRandomOverlayList.size() > 0) {\n            for (Overlay overlay : mDriftRandomOverlayList) {\n                overlay.remove();\n            }\n        }\n\n        if (mDriftRandomDotOverlay.size() > 0) {\n            for (Overlay overlay : mDriftRandomDotOverlay) {\n                overlay.remove();\n            }\n        }\n    }\n\n    /**\n     * 将所有Overlay 从 地图上消除\n     */\n    public final void removeOriginRouteFromMap() {\n        if (mBaiduMap == null) {\n            return;\n        }\n        for (Overlay marker : mOriginRouteOverlayList) {\n            marker.remove();\n        }\n        mOverlayOptionList.clear();\n        mOriginRouteOverlayList.clear();\n\n    }\n\n    /**\n     * 缩放地图，使所有Overlay都在合适的视野内\n     * <p>\n     * 注： 该方法只对Marker类型的overlay有效\n     * </p>\n     */\n    public void zoomToSpan() {\n        if (mBaiduMap == null) {\n            return;\n        }\n        if (mOriginRouteOverlayList.size() > 0) {\n            LatLngBounds.Builder builder = new LatLngBounds.Builder();\n            for (Overlay overlay : mOriginRouteOverlayList) {\n                // polyline 中的点可能太多，只按marker 缩放\n                if (overlay instanceof Marker) {\n                    builder.include(((Marker) overlay).getPosition());\n                }\n            }\n            MapStatus mapStatus = mBaiduMap.getMapStatus();\n            if (null != mapStatus) {\n                int width = mapStatus.winRound.right - mBaiduMap.getMapStatus().winRound.left - 400;\n                int height = mapStatus.winRound.bottom - mBaiduMap.getMapStatus().winRound.top - 400;\n                mBaiduMap.setMapStatus(MapStatusUpdateFactory\n                    .newLatLngBounds(builder.build(), width, height));\n            }\n\n        }\n    }\n\n    /**\n     * 设置显示在规定宽高中的地图地理范围\n     */\n    public void zoomToSpanPaddingBounds(int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) {\n        if (mBaiduMap == null) {\n            return;\n        }\n        if (mOriginRouteOverlayList.size() > 0) {\n            LatLngBounds.Builder builder = new LatLngBounds.Builder();\n            for (Overlay overlay : mOriginRouteOverlayList) {\n                // polyline 中的点可能太多，只按marker 缩放\n                if (overlay instanceof Marker) {\n                    builder.include(((Marker) overlay).getPosition());\n                }\n            }\n\n            mBaiduMap.setMapStatus(MapStatusUpdateFactory\n                .newLatLngBounds(builder.build(), paddingLeft, paddingTop, paddingRight, paddingBottom));\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/widget/PositionSelectDialogHelper.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.widget;\n\nimport android.content.Context;\nimport android.text.Editable;\nimport android.text.TextUtils;\nimport android.text.TextWatcher;\nimport android.view.View;\nimport android.widget.EditText;\n\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.baidu.mapapi.search.sug.OnGetSuggestionResultListener;\nimport com.baidu.mapapi.search.sug.SuggestionResult;\nimport com.baidu.mapapi.search.sug.SuggestionSearch;\nimport com.baidu.mapapi.search.sug.SuggestionSearchOption;\nimport com.didichuxing.doraemonkit.gps_mock.R;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class PositionSelectDialogHelper implements IDialogHelper, TextWatcher, View.OnClickListener, OnGetSuggestionResultListener {\n    private static final String TAG = PositionSelectDialogHelper.class.getSimpleName();\n    private final Context mContext;\n    private PositionSelectRecyclerAdapter.IPositionItemSelectedCallback mPositionSelectedCallback;\n    private View mHeadView;\n    private EditText mEdtSelectPosition;\n    private RecyclerView mRvPositionList;\n    private SuggestionSearch mSuggestionSearch;\n    private String mLocCityName = \"杭州\";\n    private String mCurArea = \"西溪谷\";\n    private final List<SuggestionResult.SuggestionInfo> mPositionDataList = new ArrayList<>();\n    private PositionSelectRecyclerAdapter mAdapter;\n\n    public PositionSelectDialogHelper(Context context, PositionSelectRecyclerAdapter.IPositionItemSelectedCallback positionSelectedCallback) {\n        this.mContext = context;\n        this.mPositionSelectedCallback = positionSelectedCallback;\n    }\n\n    @Override\n    public void init(View rootView) {\n        mHeadView = rootView.findViewById(R.id.head_view);\n        mEdtSelectPosition = rootView.findViewById(R.id.edt_select_position);\n        mRvPositionList = rootView.findViewById(R.id.rv_position_list);\n        mEdtSelectPosition.setOnClickListener(this);\n        mEdtSelectPosition.addTextChangedListener(this);\n\n        mSuggestionSearch = SuggestionSearch.newInstance();\n        mSuggestionSearch.setOnGetSuggestionResultListener(this);\n\n        initRecyclerView();\n    }\n\n    private void initRecyclerView() {\n        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mContext);\n        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);\n        mRvPositionList.setLayoutManager(linearLayoutManager);\n        mAdapter = new PositionSelectRecyclerAdapter(mPositionDataList);\n        mRvPositionList.setAdapter(mAdapter);\n\n        if (mPositionSelectedCallback != null) {\n            mAdapter.setItemSelectedCallback(mPositionSelectedCallback);\n        }\n\n        searchSuggestPos(mCurArea);\n    }\n\n    private void searchSuggestPos(String keyWord){\n        mSuggestionSearch.requestSuggestion(new SuggestionSearchOption()\n            .keyword(keyWord)\n            .city(mLocCityName));\n    }\n\n    @Override\n    public void onClick(View view) {\n        if (view.getId() == R.id.edt_select_position) {\n\n        }\n    }\n\n    @Override\n    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {\n        LogHelper.d(TAG, \"beforeTextChanged  \" + charSequence + \" i=\" + i + \" i1=\" + i1 + \" i2=\" + i2);\n    }\n\n    @Override\n    public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {\n        LogHelper.d(TAG, \"onTextChanged  \" + charSequence + \" i=\" + i + \" i1=\" + i1 + \" i2=\" + i2);\n        if (TextUtils.isEmpty(charSequence)) {\n            mPositionDataList.clear();\n            mAdapter.notifyDataSetChanged();\n            charSequence = \"\";\n        }\n        searchSuggestPos(charSequence.toString());\n    }\n\n    @Override\n    public void afterTextChanged(Editable editable) {\n        LogHelper.d(TAG, \"afterTextChanged  \" + editable);\n    }\n\n    @Override\n    public void onGetSuggestionResult(SuggestionResult res) {\n        if (res == null || res.getAllSuggestions() == null) {\n            LogHelper.d(TAG, \"search address result: result is null\");\n            return;\n        }\n\n        LogHelper.d(TAG, \"search address result: \" + (\"size: \" + res.getAllSuggestions().size() + res.getAllSuggestions()));\n        mPositionDataList.clear();\n        mPositionDataList.addAll(res.getAllSuggestions());\n        mAdapter.notifyDataSetChanged();\n    }\n\n    @Override\n    public void onDestroy() {\n        if (mSuggestionSearch != null) {\n            mSuggestionSearch.destroy();\n        }\n    }\n}\n\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/widget/PositionSelectRecyclerAdapter.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.widget;\n\nimport android.text.TextUtils;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.baidu.mapapi.search.sug.SuggestionResult;\nimport com.didichuxing.doraemonkit.gps_mock.R;\n\nimport java.util.List;\n\npublic class PositionSelectRecyclerAdapter extends RecyclerView.Adapter<PositionSelectRecyclerAdapter.PositionItemHolder> {\n\n    private final List<SuggestionResult.SuggestionInfo> mData;\n    private IPositionItemSelectedCallback mItemSelectedCallback;\n\n    public PositionSelectRecyclerAdapter(List<SuggestionResult.SuggestionInfo> mData) {\n        this.mData = mData;\n    }\n\n    @NonNull\n    @Override\n    public PositionItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {\n        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.dk_item_position_select, parent, false);\n\n        return new PositionItemHolder(itemView);\n    }\n\n    @Override\n    public void onBindViewHolder(@NonNull PositionItemHolder holder, final int position) {\n        holder.bindView(mData.get(holder.getAdapterPosition()));\n        holder.mItemView.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                if (mItemSelectedCallback != null) {\n                    mItemSelectedCallback.onItemSelect(mData.get(holder.getAdapterPosition()));\n                }\n            }\n        });\n    }\n\n    @Override\n    public int getItemCount() {\n        return mData.size();\n    }\n\n    public static class PositionItemHolder extends RecyclerView.ViewHolder {\n        private final View mItemView;\n        private final TextView mTvAddressName;\n        private final TextView mTvAddressDetail;\n\n\n        public PositionItemHolder(View itemView) {\n            super(itemView);\n            mItemView = itemView;\n            mTvAddressName = itemView.findViewById(R.id.tv_address_name);\n            mTvAddressDetail = itemView.findViewById(R.id.tv_address_detail);\n        }\n\n        public void bindView(SuggestionResult.SuggestionInfo suggestionInfo) {\n            if (suggestionInfo != null) {\n                mTvAddressName.setText(suggestionInfo.key);\n                mTvAddressDetail.setText(TextUtils.isEmpty(suggestionInfo.address) ? (suggestionInfo.city + suggestionInfo.district + suggestionInfo.key) : suggestionInfo.address);\n            }\n\n        }\n    }\n\n    public void setItemSelectedCallback(IPositionItemSelectedCallback mItemSelectedCallback) {\n        this.mItemSelectedCallback = mItemSelectedCallback;\n    }\n\n    public interface IPositionItemSelectedCallback {\n        void onItemSelect(SuggestionResult.SuggestionInfo suggestionInfo);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/widget/RouteMockDokitView.kt",
    "content": "package com.didichuxing.doraemonkit.gps_mock.widget;\n\nimport android.content.Context\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.gps_mock.R\nimport com.didichuxing.doraemonkit.gps_mock.gpsmock.GpsMockFragment\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams\nimport com.didichuxing.doraemonkit.util.ConvertUtils\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\n\nclass RouteMockDokitView : AbsDoKitView() {\n    init {\n        viewProps.edgePinned = true\n    }\n\n    override fun onCreate(context: Context?) {\n    }\n\n    override fun onCreateView(context: Context?, rootView: FrameLayout?): View {\n        return LayoutInflater.from(context).inflate(R.layout.dk_dokitview_route_mock, rootView, false)\n    }\n\n    override fun onViewCreated(rootView: FrameLayout?) {\n        doKitView?.setOnClickListener {\n            if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n                ToastUtils.showShort(DoKitCommUtil.getString(com.didichuxing.doraemonkit.R.string.dk_plugin_close_tip))\n            }\n            if (!DokitPluginConfig.SWITCH_GPS) {\n                ToastUtils.showShort(DoKitCommUtil.getString(com.didichuxing.doraemonkit.R.string.dk_plugin_gps_close_tip))\n            }\n            DoKit.launchFullScreen(GpsMockFragment::class.java, activity, null, true)\n        }\n    }\n\n    override fun initDokitViewLayoutParams(params: DoKitViewLayoutParams) {\n        params.width = ConvertUtils.dp2px(50.0f)\n        params.height = ConvertUtils.dp2px(50.0f)\n        params.gravity = Gravity.TOP or Gravity.LEFT\n        params.x = ConvertUtils.dp2px(280f)\n        params.y = ConvertUtils.dp2px(25f)\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/java/com/didichuxing/doraemonkit/gps_mock/widget/SeekRangeBar.java",
    "content": "package com.didichuxing.doraemonkit.gps_mock.widget;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.drawable.Drawable;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.MotionEvent;\nimport android.view.View;\n\n\nimport com.didichuxing.doraemonkit.gps_mock.R;\nimport com.didichuxing.doraemonkit.util.ConvertUtils;\nimport com.didichuxing.doraemonkit.util.LogHelper;\n\nimport java.math.BigDecimal;\n\n/**\n * 双滑块的进度条（区间选择）\n */\npublic class SeekRangeBar extends View {\n    private static final int CLICK_ON_LOW = 1;    //手指在前滑块上滑动\n    private static final int CLICK_ON_HIGH = 2;    //手指在后滑块上滑动\n    private static final int CLICK_IN_LOW_AREA = 3;  //手指点击离前滑块近\n    private static final int CLICK_IN_HIGH_AREA = 4; //手指点击离后滑块近\n    private static final int CLICK_OUT_AREA = 5;   //手指点击在view外\n    private static final int CLICK_INVAILD = 0;\n    private static final int[] STATE_NORMAL = {};\n    private static final int[] STATE_PRESSED =\n        {android.R.attr.state_pressed, android.R.attr.state_window_focused,};\n    private static int mThumbMarginTop = ConvertUtils.dp2px(20);  //滑动块顶部离view顶部的距离\n    private static int mTextViewMarginTop = ConvertUtils.dp2px(20);  //当前滑块文字距离view顶部距离\n    private final Drawable hasScrollBarBg;    //滑动条滑动后背景图\n    private final Drawable notScrollBarBg;    //滑动条未滑动背景图\n    private final Drawable mThumbLow;     //前滑块\n    private final Drawable mThumbHigh;    //后滑块\n    private int mScollBarWidth;   //控件宽度 = 滑动条宽度 + 滑动块宽度\n    private int mScollBarHeight;  //控件高度\n    private final int mThumbWidth;    //滑动块直径\n    private double mOffsetLow = 0;   //前滑块中心坐标\n    private double mOffsetHigh = 0;  //后滑块中心坐标\n    private int mDistance = 0;   //总刻度是固定距离 两边各去掉半个滑块距离\n    private int mFlag = CLICK_INVAILD;  //手指按下的类型\n    // private double defaultScreenLow = 0;  //默认前滑块位置百分比\n    // private double defaultScreenHigh = 100; //默认后滑块位置百分比\n    private OnSeekBarChangeListener mBarChangeListener;\n    private boolean editable = true;//是否处于可编辑状态\n    private int miniGap = 10;//AB的最小间隔m\n    private double mProgressLow;//起点(百分比)\n    private double mProgressHigh = 100;//终点\n    private int mIndicatorTextSize;\n    //设置绘制样式\n    Paint mTextPaint;\n\n    public SeekRangeBar(Context context) {\n        this(context, null);\n    }\n\n    public SeekRangeBar(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public SeekRangeBar(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SeekRangeBar, defStyle, 0);\n\n        //这里设置背景图及滑块图，自定义过进度条的同学应该很熟悉了\n        notScrollBarBg = typedArray.getDrawable(R.styleable.SeekRangeBar_bar_bg_shape);\n        hasScrollBarBg = typedArray.getDrawable(R.styleable.SeekRangeBar_bar_fg_shape);\n        mThumbLow = typedArray.getDrawable(R.styleable.SeekRangeBar_slide_btn_low_shape);\n        mThumbHigh = typedArray.getDrawable(R.styleable.SeekRangeBar_slide_btn_high_shape);\n        mThumbLow.setState(STATE_NORMAL);\n        mThumbHigh.setState(STATE_NORMAL);\n\n\n        //设置滑动块直径\n        mThumbWidth = typedArray.getDimensionPixelSize(R.styleable.SeekRangeBar_indicator_size, mThumbLow.getIntrinsicWidth());\n        int defaultBarHeight = mThumbWidth / 2;\n        //设置滑动条高度\n        mScollBarHeight = typedArray.getDimensionPixelSize(R.styleable.SeekRangeBar_bar_height, defaultBarHeight);\n\n        // 防止滑动条高度大于滑块的尺寸\n        if (mScollBarHeight >= defaultBarHeight) {\n            mScollBarHeight = defaultBarHeight;\n        }\n\n        int mIndicatorTextColor = typedArray.getColor(R.styleable.SeekRangeBar_indicator_text_color, Color.RED);\n        mIndicatorTextSize = typedArray.getDimensionPixelSize(R.styleable.SeekRangeBar_indicator_text_size, ConvertUtils.dp2px(12));\n        mTextPaint = new Paint();\n        mTextPaint.setTextAlign(Paint.Align.CENTER);\n        mTextPaint.setColor(mIndicatorTextColor);\n        mTextPaint.setAntiAlias(true);\n        mTextPaint.setTextSize(mIndicatorTextSize);\n\n        // 顶部预留空间来绘制数字文案, 默认是数字的高度\n        mTextViewMarginTop = mIndicatorTextSize - ConvertUtils.dp2px(2);\n        mThumbMarginTop = mIndicatorTextSize;\n\n    }\n\n    /**\n     * 测量view尺寸（在onDraw()之前）\n     *\n     * @param widthMeasureSpec\n     * @param heightMeasureSpec\n     */\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        int width = MeasureSpec.getSize(widthMeasureSpec);\n        mScollBarWidth = width;\n        if (mDistance == 0) {//这里滑块中心坐标初始化的时候测量一下（根据mDistance是否赋值判断），并不需要不停地去测量。后面会根据进度计算滑块位置。\n            mOffsetLow = mThumbWidth / 2;\n            mOffsetHigh = width - mThumbWidth / 2;\n            LogHelper.d(\"onMeasure\", \"SeekRangeBar onMeasure mDistance==0 \" + mDistance + \" mOffsetLow \" + mOffsetLow + \" mOffsetHigh \" + mOffsetHigh);\n        }\n        mDistance = width - mThumbWidth;\n        mOffsetLow = formatInt(mProgressLow / 100 * (mDistance)) + mThumbWidth / 2;\n        mOffsetHigh = formatInt(mProgressHigh / 100 * (mDistance)) + mThumbWidth / 2;\n        LogHelper.d(\"onMeasure\", \"SeekRangeBar onMeasure mDistance \" + mDistance + \" mOffsetLow \" + mOffsetLow + \" mOffsetHigh \" + mOffsetHigh);\n        setMeasuredDimension(width, mThumbWidth + mThumbMarginTop * 2 + 2);\n    }\n\n    protected void onLayout(boolean changed, int l, int t, int r, int b) {\n        super.onLayout(changed, l, t, r, b);\n    }\n\n    /**\n     * 绘制进度条\n     */\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n\n\n        int top = mThumbMarginTop + mThumbWidth / 2 - mScollBarHeight / 2;\n        int bottom = top + mScollBarHeight;\n\n        //绘制是否可操作状态的下的不同样式，仅可编辑状态下显示进度条\n        if (editable) {\n            //白色滑动条，两个滑块各两边部分\n            notScrollBarBg.setBounds(mThumbWidth / 2, top, mScollBarWidth - mThumbWidth / 2, bottom);\n            notScrollBarBg.draw(canvas);\n\n            //红色滑动条，两个滑块中间部分\n            hasScrollBarBg.setBounds((int) mOffsetLow, top, (int) mOffsetHigh, bottom);\n            hasScrollBarBg.draw(canvas);\n        }\n\n        //前滑块\n        mThumbLow.setBounds((int) (mOffsetLow - mThumbWidth / 2), mThumbMarginTop, (int) (mOffsetLow + mThumbWidth / 2), mThumbWidth + mThumbMarginTop);\n        mThumbLow.draw(canvas);\n\n        //后滑块\n        mThumbHigh.setBounds((int) (mOffsetHigh - mThumbWidth / 2), mThumbMarginTop, (int) (mOffsetHigh + mThumbWidth / 2), mThumbWidth + mThumbMarginTop);\n        mThumbHigh.draw(canvas);\n\n        //当前滑块刻度\n        mProgressLow = formatInt((mOffsetLow - mThumbWidth / 2) * 100 / mDistance);\n        mProgressHigh = formatInt((mOffsetHigh - mThumbWidth / 2) * 100 / mDistance);\n        canvas.drawText((int) mProgressLow + \"%\", (int) mOffsetLow - 2 - 2, mTextViewMarginTop, mTextPaint);\n        canvas.drawText((int) mProgressHigh + \"%\", (int) mOffsetHigh - 2, mTextViewMarginTop, mTextPaint);\n\n        if (mBarChangeListener != null) {\n            mBarChangeListener.onProgressChanged(this, mProgressLow, mProgressHigh);\n        }\n    }\n\n    //手势监听\n    @Override\n    public boolean onTouchEvent(MotionEvent e) {\n        if (!editable) {\n            return false;\n        }\n        if (e.getAction() == MotionEvent.ACTION_DOWN) {\n            getParent().requestDisallowInterceptTouchEvent(true);\n            mFlag = getAreaFlag(e);\n            if (mFlag == CLICK_ON_LOW) {\n                mThumbLow.setState(STATE_PRESSED);\n            } else if (mFlag == CLICK_ON_HIGH) {\n                mThumbHigh.setState(STATE_PRESSED);\n            } else if (mFlag == CLICK_IN_LOW_AREA) {\n                mThumbLow.setState(STATE_PRESSED);\n                mThumbHigh.setState(STATE_NORMAL);\n                //如果点击0-mThumbWidth/2坐标\n                if (e.getX() < 0 || e.getX() <= mThumbWidth / 2) {\n                    mOffsetLow = mThumbWidth / 2;\n                } else if (e.getX() > mScollBarWidth - mThumbWidth / 2) {\n                    mOffsetLow = mThumbWidth / 2 + mDistance;\n                } else {\n                    mOffsetLow = formatInt(e.getX());\n\n                }\n            } else if (mFlag == CLICK_IN_HIGH_AREA) {\n                mThumbHigh.setState(STATE_PRESSED);\n                mThumbLow.setState(STATE_NORMAL);\n                if (e.getX() >= mScollBarWidth - mThumbWidth / 2) {\n                    mOffsetHigh = mDistance + mThumbWidth / 2;\n                } else {\n                    mOffsetHigh = formatInt(e.getX());\n                }\n            }\n            //更新滑块\n            invalidate();\n        } else if (e.getAction() == MotionEvent.ACTION_MOVE) {\n            if (mFlag == CLICK_ON_LOW) {\n                if (e.getX() < 0 || e.getX() <= mThumbWidth / 2) {\n                    mOffsetLow = mThumbWidth / 2;\n                } else if (e.getX() >= mScollBarWidth - mThumbWidth / 2) {\n                    mOffsetLow = mThumbWidth / 2 + mDistance;\n                    mOffsetHigh = mOffsetLow;\n                } else {\n                    mOffsetLow = formatInt(e.getX());\n                    if (mOffsetHigh - mOffsetLow <= 0) {\n                        mOffsetHigh = (mOffsetLow <= mDistance + mThumbWidth / 2) ? (mOffsetLow) : (mDistance + mThumbWidth / 2);\n                    }\n                }\n            } else if (mFlag == CLICK_ON_HIGH) {\n                if (e.getX() < mThumbWidth / 2) {\n                    mOffsetHigh = mThumbWidth / 2;\n                    mOffsetLow = mThumbWidth / 2;\n                } else if (e.getX() > mScollBarWidth - mThumbWidth / 2) {\n                    mOffsetHigh = mThumbWidth / 2 + mDistance;\n                } else {\n                    mOffsetHigh = formatInt(e.getX());\n                    if (mOffsetHigh - mOffsetLow <= 0) {\n                        mOffsetLow = (mOffsetHigh >= mThumbWidth / 2) ? (mOffsetHigh) : mThumbWidth / 2;\n                    }\n                }\n            }\n            //更新滑块，每次滑块有动作都要执行此函数触发onDraw方法绘制新图片\n            invalidate();\n        } else if (e.getAction() == MotionEvent.ACTION_UP) {\n            getParent().requestDisallowInterceptTouchEvent(false);\n            LogHelper.d(\"LOGCAT\", \"ACTION UP:\" + mProgressHigh + \"-\" + mProgressLow);\n            mThumbLow.setState(STATE_NORMAL);\n            mThumbHigh.setState(STATE_NORMAL);\n            if (miniGap > 0 && mProgressHigh < mProgressLow + miniGap) {\n                mProgressHigh = mProgressLow + miniGap;\n                // 如果超过了100%, 则大滑块设置为100, 小滑块设置为100-miniGap\n                if (mProgressHigh > 100) {\n                    mProgressHigh = 100;\n                    mProgressLow = 100 - miniGap;\n                    // this.defaultScreenLow = mProgressLow;\n                    mOffsetLow = formatInt(mProgressLow / 100 * mDistance) + +mThumbWidth / 2;\n                }\n                // this.defaultScreenHigh = mProgressHigh;\n                mOffsetHigh = formatInt(mProgressHigh / 100 * (mDistance)) + mThumbWidth / 2;\n                invalidate();\n            }\n        }\n        return true;\n    }\n\n    /**\n     * 设置是否可编辑状态，非可编辑状态将不能对AB点进行操作\n     *\n     * @param _b\n     */\n    public void setEditable(boolean _b) {\n        editable = _b;\n        invalidate();\n    }\n\n    /**\n     * 获取当前手指位置\n     */\n    public int getAreaFlag(MotionEvent e) {\n        int top = mThumbMarginTop;\n        int bottom = mThumbWidth + mThumbMarginTop;\n        if (e.getY() >= top && e.getY() <= bottom && e.getX() >= (mOffsetLow - mThumbWidth / 2) && e.getX() <= mOffsetLow + mThumbWidth / 2) {\n            return CLICK_ON_LOW;\n        } else if (e.getY() >= top && e.getY() <= bottom && e.getX() >= (mOffsetHigh - mThumbWidth / 2) && e.getX() <= (mOffsetHigh + mThumbWidth / 2)) {\n            return CLICK_ON_HIGH;\n        } else if (e.getY() >= top\n            && e.getY() <= bottom\n            && ((e.getX() >= 0 && e.getX() < (mOffsetLow - mThumbWidth / 2)) || ((e.getX() > (mOffsetLow + mThumbWidth / 2))\n            && e.getX() <= ((double) mOffsetHigh + mOffsetLow) / 2))) {\n            return CLICK_IN_LOW_AREA;\n        } else if (e.getY() >= top && e.getY() <= bottom && (((e.getX() > ((double) mOffsetHigh + mOffsetLow) / 2) && e.getX() < (mOffsetHigh - mThumbWidth / 2)) || (e.getX() > (mOffsetHigh + mThumbWidth / 2) && e.getX() <= mScollBarWidth))) {\n            return CLICK_IN_HIGH_AREA;\n        } else if (!(e.getX() >= 0 && e.getX() <= mScollBarWidth && e.getY() >= top && e.getY() <= bottom)) {\n            return CLICK_OUT_AREA;\n        } else {\n            return CLICK_INVAILD;\n        }\n    }\n\n    /**\n     * 设置前滑块位置\n     *\n     * @param progressLow\n     */\n    public void setProgressLow(double progressLow) {\n        this.mProgressLow = progressLow;\n        mOffsetLow = formatInt(progressLow / 100 * (mDistance)) + mThumbWidth / 2;\n        invalidate();\n    }\n\n    public double getProgressLow(){\n        return mProgressLow;\n    }\n\n    /**\n     * 设置后滑块位置\n     *\n     * @param progressHigh\n     */\n    public void setProgressHigh(double progressHigh) {\n        this.mProgressHigh = progressHigh;\n        mOffsetHigh = formatInt(progressHigh / 100 * (mDistance)) + mThumbWidth / 2;\n        invalidate();\n    }\n\n    public double getProgressHigh(){\n        return mProgressHigh;\n    }\n\n    /**\n     * 设置滑动监听\n     *\n     * @param mListener\n     */\n    public void setOnSeekBarChangeListener(OnSeekBarChangeListener mListener) {\n        this.mBarChangeListener = mListener;\n    }\n\n    /**\n     * 滑动监听，改变输入框的值\n     */\n    public interface OnSeekBarChangeListener {\n        //滑动时\n        public void onProgressChanged(SeekRangeBar seekBar, double progressLow, double progressHigh);\n    }\n\n    /**\n     * 设置滑动结果为整数\n     */\n    private int formatInt(double value) {\n        BigDecimal bd = new BigDecimal(value);\n        BigDecimal bd1 = bd.setScale(0, BigDecimal.ROUND_HALF_UP);\n        return bd1.intValue();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/drawable/dk_bg_btn_round_rectangle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_enabled=\"true\">\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"@color/color_seek_bar_indicator\"/>\n            <corners android:radius=\"5dp\"/>\n        </shape>\n    </item>\n\n    <item android:state_enabled=\"false\">\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"@color/color_btn_disable\"/>\n            <corners android:radius=\"5dp\"/>\n        </shape>\n    </item>\n\n<!--    <item android:state_pressed=\"true\">-->\n<!--        <shape android:shape=\"rectangle\">-->\n<!--            <solid android:color=\"@color/color_btn_press\"/>-->\n<!--            <corners android:radius=\"5dp\"/>-->\n<!--        </shape>-->\n<!--    </item>-->\n\n    <item android:state_hovered=\"true\">\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"@color/color_btn_press\"/>\n            <corners android:radius=\"5dp\"/>\n        </shape>\n    </item>\n\n</selector>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/drawable/dk_bg_custom_spinner.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!-- 第一组item：设置边框 -->\n    <item>\n        <shape>\n            <!-- 设置边框线宽度和颜色 -->\n            <stroke\n                android:width=\"1px\"\n                android:color=\"@color/color_edt_stroke_line\"/>\n            <!-- 设置圆角度数 -->\n            <corners android:radius=\"5dp\" />\n            <!-- 设置背景颜色 -->\n            <solid android:color=\"#ffffff\" />\n            <!-- padding：设置边距 -->\n        </shape>\n    </item>\n\n    <!-- 第二组item：设置spinner箭头图片(替换默认箭头) -->\n    <item>\n        <bitmap\n            android:gravity=\"end\"\n            android:src=\"@mipmap/dk_icon_arrow_down_expand\"/>\n    </item>\n\n</layer-list>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/drawable/dk_bg_dokitview_route_mock.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape android:shape=\"rectangle\">\n            <corners android:radius=\"30dp\" />\n            <stroke\n                android:width=\"1dp\"\n                android:color=\"#419EF8\" />\n            <solid android:color=\"#8f419EF8\" />\n        </shape>\n\n    </item>\n</layer-list>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/drawable/dk_bg_edt_hint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@color/color_bold_text_black\" />\n    <size android:width=\"1.5dp\" />\n</shape>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/drawable/dk_bg_gray_round_rectangle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@color/color_bg_gray\" />\n    <corners android:radius=\"6dp\" />\n</shape>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/drawable/dk_bg_round_edt.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n<item >\n    <shape android:shape=\"rectangle\">\n        <solid android:color=\"@color/color_white\"/>\n        <stroke android:width=\"1px\" android:color=\"@color/color_edt_stroke_line\"/>\n        <corners android:radius=\"5dp\"/>\n    </shape>\n</item>\n</layer-list>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/drawable/dk_icon_loc_circle_shape.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape android:shape=\"rectangle\">\n            <size\n                android:width=\"36dp\"\n                android:height=\"36dp\" />\n            <corners android:radius=\"20dp\" />\n            <stroke\n                android:width=\"1dp\"\n                android:color=\"#419EF8\" />\n            <solid android:color=\"#8f419EF8\" />\n        </shape>\n\n    </item>\n\n    <item android:gravity=\"center\">\n        <shape android:shape=\"rectangle\">\n            <size\n                android:width=\"8dp\"\n                android:height=\"8dp\" />\n            <corners android:radius=\"6dp\" />\n            <solid android:color=\"#A0419EF8\" />\n            <stroke\n                android:width=\"1dp\"\n                android:color=\"@color/color_white\" />\n        </shape>\n    </item>\n</layer-list>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/drawable/dk_seek_bar_background_shape.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\" android:shape=\"rectangle\">\n    <corners android:radius=\"5dp\"/>\n    <solid android:color=\"@color/color_seek_bar_background\"/>\n\n</shape>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/drawable/dk_seek_bar_circle_indicator.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n    <size android:width=\"20dp\" android:height=\"20dp\"/>\n    <corners android:radius=\"20dp\"/>\n    <solid android:color=\"@color/color_seek_bar_indicator\"/>\n</shape>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/drawable/dk_seek_bar_foreground_shape.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\" android:shape=\"rectangle\">\n    <corners android:radius=\"5dp\"/>\n    <solid android:color=\"@color/color_seek_bar_indicator\"/>\n</shape>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/layout/dk_dokitview_route_mock.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"50dp\"\n    android:layout_height=\"50dp\"\n    android:background=\"@drawable/dk_bg_dokitview_route_mock\">\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"center\"\n        android:gravity=\"center\"\n        android:text=\"位置\\n模拟\"\n        android:textColor=\"@color/dk_color_FFFFFF\"\n        android:textSize=\"13dp\"\n        android:textStyle=\"bold\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/layout/dk_fragment_gps_mock.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    app:layoutDescription=\"@xml/gps_mock_root_scene\">\n\n    <com.baidu.mapapi.map.MapView\n        android:id=\"@+id/map_view\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"fill_parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <ImageView\n        android:id=\"@+id/iv_map_center_loc\"\n        android:layout_width=\"@dimen/map_loc_iv_size\"\n        android:layout_height=\"@dimen/map_loc_iv_size\"\n        android:src=\"@mipmap/dk_icon_cur_loc\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/color_white_translucent\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_gps_mock\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/mock_parent_layout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"@color/color_white_translucent\"\n        android:paddingLeft=\"@dimen/border_margin\"\n        android:paddingRight=\"@dimen/border_margin\"\n        android:paddingBottom=\"@dimen/border_margin\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/title_bar\">\n\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:id=\"@+id/container_pos_mock\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:background=\"@drawable/dk_bg_gray_round_rectangle\"\n            android:paddingLeft=\"@dimen/border_padding\"\n            android:paddingTop=\"@dimen/border_padding\"\n            android:paddingRight=\"@dimen/border_padding\"\n            android:paddingBottom=\"@dimen/border_padding\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\">\n\n\n            <TextView\n                android:id=\"@+id/tv_title_pos_mock\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/title_pos_mock\"\n                android:textColor=\"@color/color_bold_text_black\"\n                android:textSize=\"@dimen/title_text_size\"\n                android:textStyle=\"bold\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\" />\n\n            <CheckBox\n                android:id=\"@+id/cb_toggle_pos_mock\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                app:layout_constraintBottom_toBottomOf=\"@id/tv_title_pos_mock\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"@id/tv_title_pos_mock\" />\n\n            <EditText\n                android:id=\"@+id/edt_input_pos\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"@dimen/edt_height\"\n                android:layout_marginTop=\"@dimen/common_margin_top1\"\n                android:background=\"@drawable/dk_bg_round_edt\"\n                android:digits=\"0123456789.,\"\n                android:hint=\"@string/edt_pos_lat_long_hint\"\n                android:inputType=\"number\"\n                android:paddingLeft=\"@dimen/common_margin_left_right\"\n                android:textColor=\"@color/color_bold_text_black\"\n                android:textCursorDrawable=\"@drawable/dk_bg_edt_hint\"\n                android:textSize=\"@dimen/des_text_size\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toLeftOf=\"@id/btn_mock_pos\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title_pos_mock\" />\n\n            <Button\n                android:id=\"@+id/btn_mock_pos\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"@dimen/confirm_btn_height\"\n                android:layout_marginStart=\"@dimen/dk_dp_10\"\n                android:layout_marginLeft=\"@dimen/dk_dp_10\"\n                android:layout_marginBottom=\"@dimen/dk_dp_10\"\n                android:background=\"@drawable/dk_bg_btn_round_rectangle\"\n                android:text=\"@string/btn_text_start_mock\"\n                android:textColor=\"@color/color_confirm_btn_text\"\n                app:layout_constraintBaseline_toBaselineOf=\"@id/edt_input_pos\"\n                app:layout_constraintBottom_toBottomOf=\"@id/edt_input_pos\"\n                app:layout_constraintLeft_toRightOf=\"@id/edt_input_pos\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"@id/edt_input_pos\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:id=\"@+id/container_route_mock\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_marginTop=\"@dimen/common_margin_top1\"\n            android:background=\"@drawable/dk_bg_gray_round_rectangle\"\n            android:paddingLeft=\"@dimen/border_padding\"\n            android:paddingTop=\"@dimen/border_padding\"\n            android:paddingRight=\"@dimen/border_padding\"\n            android:paddingBottom=\"@dimen/border_padding\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/container_pos_mock\">\n\n            <androidx.constraintlayout.widget.Guideline\n                android:id=\"@+id/guide_line_vertical_center\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\"\n                app:layout_constraintGuide_percent=\"0.5\" />\n\n            <TextView\n                android:id=\"@+id/tv_title_route_mock\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/title_route_mock\"\n                android:textColor=\"@color/color_bold_text_black\"\n                android:textSize=\"@dimen/title_text_size\"\n                android:textStyle=\"bold\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\" />\n\n            <CheckBox\n                android:id=\"@+id/cb_toggle_route_mock\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                app:layout_constraintBottom_toBottomOf=\"@id/tv_title_route_mock\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"@id/tv_title_route_mock\" />\n\n            <TextView\n                android:id=\"@+id/tv_des_start\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/title_route_mock_start\"\n                android:textColor=\"@color/color_bold_text_black\"\n                android:textSize=\"@dimen/edt_text_size\"\n                app:layout_constraintBottom_toBottomOf=\"@id/tv_route_start\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toLeftOf=\"@id/tv_route_start\"\n                app:layout_constraintTop_toTopOf=\"@id/tv_route_start\" />\n\n            <TextView\n                android:id=\"@+id/tv_route_start\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"@dimen/edt_height\"\n                android:layout_marginStart=\"@dimen/edt_text_margin_left\"\n                android:layout_marginLeft=\"@dimen/edt_text_margin_left\"\n                android:layout_marginTop=\"@dimen/common_margin_top1\"\n                android:background=\"@drawable/dk_bg_round_edt\"\n                android:ellipsize=\"marquee\"\n                android:gravity=\"left|center_vertical\"\n                android:maxLines=\"1\"\n                android:scrollbars=\"horizontal\"\n                android:textColor=\"@color/color_bold_text_black\"\n                android:textCursorDrawable=\"@drawable/dk_bg_edt_hint\"\n                android:textSize=\"@dimen/edt_text_size\"\n                app:layout_constraintLeft_toRightOf=\"@id/tv_des_start\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title_route_mock\" />\n\n            <TextView\n                android:id=\"@+id/tv_des_end\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/title_route_mock_end\"\n                android:textColor=\"@color/color_bold_text_black\"\n                android:textSize=\"@dimen/edt_text_size\"\n                app:layout_constraintBottom_toBottomOf=\"@id/tv_route_end\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"@id/tv_route_end\" />\n\n            <TextView\n                android:id=\"@+id/tv_route_end\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"@dimen/edt_height\"\n                android:layout_marginStart=\"@dimen/edt_text_margin_left\"\n                android:layout_marginLeft=\"@dimen/edt_text_margin_left\"\n                android:layout_marginTop=\"@dimen/common_margin_top1\"\n                android:background=\"@drawable/dk_bg_round_edt\"\n                android:ellipsize=\"marquee\"\n                android:gravity=\"left|center_vertical\"\n                android:maxLines=\"1\"\n                android:scrollbars=\"horizontal\"\n                android:textColor=\"@color/color_bold_text_black\"\n                android:textCursorDrawable=\"@drawable/dk_bg_edt_hint\"\n                android:textSize=\"@dimen/edt_text_size\"\n                app:layout_constraintLeft_toRightOf=\"@id/tv_des_end\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_route_start\" />\n\n            <TextView\n                android:id=\"@+id/tv_des_speed\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/title_route_mock_speed\"\n                android:textColor=\"@color/color_bold_text_black\"\n                android:textSize=\"@dimen/edt_text_size\"\n                app:layout_constraintBottom_toBottomOf=\"@id/edt_route_speed\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"@id/edt_route_speed\" />\n\n            <EditText\n                android:id=\"@+id/edt_route_speed\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"@dimen/edt_height\"\n                android:layout_marginStart=\"@dimen/edt_text_margin_left\"\n                android:layout_marginLeft=\"@dimen/edt_text_margin_left\"\n                android:layout_marginTop=\"@dimen/dk_dp_10\"\n                android:layout_marginRight=\"@dimen/dk_dp_5\"\n                android:background=\"@drawable/dk_bg_round_edt\"\n                android:inputType=\"number|numberDecimal\"\n                android:singleLine=\"true\"\n                android:text=\"60\"\n                android:textColor=\"@color/color_bold_text_black\"\n                android:textCursorDrawable=\"@drawable/dk_bg_edt_hint\"\n                android:textSize=\"@dimen/edt_text_size\"\n                app:layout_constraintLeft_toRightOf=\"@id/tv_des_speed\"\n                app:layout_constraintRight_toLeftOf=\"@id/guide_line_vertical_center\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_route_end\" />\n\n            <TextView\n                android:id=\"@+id/tv_speed_unit\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"@dimen/dk_dp_5\"\n                android:text=\"@string/route_mock_speed_unit\"\n                android:textColor=\"@color/color_bold_text_black\"\n                android:textSize=\"@dimen/edt_text_size\"\n                app:layout_constraintBottom_toBottomOf=\"@id/edt_route_speed\"\n                app:layout_constraintLeft_toLeftOf=\"@id/guide_line_vertical_center\"\n                app:layout_constraintTop_toTopOf=\"@id/edt_route_speed\" />\n\n            <!--定位点丢失模拟-->\n            <TextView\n                android:id=\"@+id/title_toggle_route_lost_loc\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/dk_dp_20\"\n                android:text=\"@string/title_route_lost_loc_mock\"\n                android:textColor=\"@color/color_bold_text_black\"\n                android:textSize=\"@dimen/title_text_size\"\n                android:textStyle=\"bold\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toLeftOf=\"@id/cb_toggle_route_lost_loc\"\n                app:layout_constraintTop_toBottomOf=\"@id/edt_route_speed\" />\n\n            <CheckBox\n                android:id=\"@+id/cb_toggle_route_lost_loc\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                app:layout_constraintBottom_toBottomOf=\"@id/title_toggle_route_lost_loc\"\n                app:layout_constraintLeft_toRightOf=\"@id/title_toggle_route_lost_loc\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"@id/title_toggle_route_lost_loc\" />\n\n            <!--选择定位点丢失范围-->\n            <TextView\n                android:id=\"@+id/tv_des_select_lost_path\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/common_margin_top\"\n                android:text=\"@string/des_lost_loc_path\"\n                android:textColor=\"@color/color_bold_text_black\"\n                android:textSize=\"@dimen/edt_text_size\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toLeftOf=\"@id/seekbar_select_lost_path\"\n                app:layout_constraintTop_toBottomOf=\"@id/title_toggle_route_lost_loc\" />\n\n            <com.didichuxing.doraemonkit.gps_mock.widget.SeekRangeBar\n                android:id=\"@+id/seekbar_select_lost_path\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"@dimen/common_margin_left_right\"\n                app:bar_bg_shape=\"@drawable/dk_seek_bar_background_shape\"\n                app:bar_fg_shape=\"@drawable/dk_seek_bar_foreground_shape\"\n                app:bar_height=\"@dimen/seek_range_bar_height\"\n                app:indicator_size=\"@dimen/seek_range_bar_indicator_size\"\n                app:indicator_text_color=\"@color/color_seek_bar_indicator_text\"\n                app:indicator_text_size=\"@dimen/seek_range_bar_indicator_text_size\"\n                app:layout_constraintBottom_toBottomOf=\"@id/tv_des_select_lost_path\"\n                app:layout_constraintLeft_toRightOf=\"@id/tv_des_select_lost_path\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"@id/tv_des_select_lost_path\"\n                app:slide_btn_high_shape=\"@drawable/dk_seek_bar_circle_indicator\"\n                app:slide_btn_low_shape=\"@drawable/dk_seek_bar_circle_indicator\" />\n\n            <Button\n                android:id=\"@+id/btn_mock_route1\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"@dimen/confirm_btn_height\"\n                android:layout_marginTop=\"@dimen/dk_dp_10\"\n                android:background=\"@drawable/dk_bg_btn_round_rectangle\"\n                android:text=\"@string/btn_text_start_mock\"\n                android:textColor=\"@color/color_confirm_btn_text\"\n                app:layout_constraintTop_toBottomOf=\"@id/seekbar_select_lost_path\"\n                app:layout_constraintRight_toRightOf=\"parent\" />\n\n            <TextView\n                android:id=\"@+id/title_toggle_route_drift_mock\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/title_route_drift_mock\"\n                android:textColor=\"@color/color_bold_text_black\"\n                android:textSize=\"@dimen/title_text_size\"\n                android:textStyle=\"bold\"\n                app:layout_constraintBottom_toBottomOf=\"@id/cb_toggle_route_drift_mock\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toLeftOf=\"@id/cb_toggle_route_drift_mock\"\n                app:layout_constraintTop_toTopOf=\"@id/cb_toggle_route_drift_mock\" />\n\n            <CheckBox\n                android:id=\"@+id/cb_toggle_route_drift_mock\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/dk_dp_5\"\n                app:layout_constraintLeft_toRightOf=\"@id/title_toggle_route_drift_mock\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/btn_mock_route1\" />\n\n            <androidx.constraintlayout.widget.ConstraintLayout\n                android:id=\"@+id/drift_mock_set_layout\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"0dp\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/title_toggle_route_drift_mock\">\n\n                <androidx.constraintlayout.widget.Guideline\n                    android:id=\"@+id/guide_line_vertical_center1\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:orientation=\"vertical\"\n                    app:layout_constraintGuide_percent=\"0.5\" />\n\n                <Spinner\n                    android:id=\"@+id/spinner_drift_type\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"@dimen/edt_height\"\n                    android:layout_marginStart=\"@dimen/edt_text_margin_left\"\n                    android:layout_marginLeft=\"@dimen/edt_text_margin_left\"\n                    android:layout_marginTop=\"@dimen/common_margin_top1\"\n                    android:layout_marginRight=\"@dimen/dk_dp_5\"\n                    android:background=\"@drawable/dk_bg_custom_spinner\"\n                    android:overlapAnchor=\"false\"\n                    android:spinnerMode=\"dropdown\"\n                    app:layout_constraintLeft_toRightOf=\"@id/tv_des_drift_type\"\n                    app:layout_constraintRight_toLeftOf=\"@id/guide_line_vertical_center1\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n                <TextView\n                    android:id=\"@+id/tv_des_drift_type\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/des_drift_type\"\n                    android:textColor=\"@color/color_bold_text_black\"\n                    android:textSize=\"@dimen/edt_text_size\"\n                    app:layout_constraintBottom_toBottomOf=\"@id/spinner_drift_type\"\n                    app:layout_constraintLeft_toLeftOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"@id/spinner_drift_type\" />\n\n                <TextView\n                    android:id=\"@+id/tv_des_drift_accuracy\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"@dimen/dk_dp_5\"\n                    android:text=\"@string/des_drift_accuracy\"\n                    android:textColor=\"@color/color_bold_text_black\"\n                    android:textSize=\"@dimen/edt_text_size\"\n                    app:layout_constraintBottom_toBottomOf=\"@id/spinner_drift_type\"\n                    app:layout_constraintLeft_toRightOf=\"@id/guide_line_vertical_center1\"\n                    app:layout_constraintTop_toTopOf=\"@id/spinner_drift_type\" />\n\n                <EditText\n                    android:id=\"@+id/edt_drift_accuracy\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"@dimen/edt_height\"\n                    android:layout_marginStart=\"@dimen/edt_text_margin_left\"\n                    android:layout_marginLeft=\"@dimen/edt_text_margin_left\"\n                    android:background=\"@drawable/dk_bg_round_edt\"\n                    android:inputType=\"number|numberDecimal\"\n                    android:singleLine=\"true\"\n                    android:text=\"500\"\n                    android:textColor=\"@color/color_bold_text_black\"\n                    android:textCursorDrawable=\"@drawable/dk_bg_edt_hint\"\n                    android:textSize=\"@dimen/edt_text_size\"\n                    app:layout_constraintBottom_toBottomOf=\"@id/spinner_drift_type\"\n                    app:layout_constraintLeft_toRightOf=\"@id/tv_des_drift_accuracy\"\n                    app:layout_constraintRight_toLeftOf=\"@id/tv_drift_accuracy_unit\"\n                    app:layout_constraintTop_toTopOf=\"@id/spinner_drift_type\" />\n\n                <TextView\n                    android:id=\"@+id/tv_drift_accuracy_unit\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"@dimen/dk_dp_5\"\n                    android:text=\"@string/unit_drift_accuracy\"\n                    android:textColor=\"@color/color_bold_text_black\"\n                    android:textSize=\"@dimen/edt_text_size\"\n                    app:layout_constraintBottom_toBottomOf=\"@id/spinner_drift_type\"\n                    app:layout_constraintLeft_toRightOf=\"@id/edt_drift_accuracy\"\n                    app:layout_constraintRight_toRightOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"@id/spinner_drift_type\" />\n\n\n                <TextView\n                    android:id=\"@+id/tv_des_drift_mode\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/des_drift_mode\"\n                    android:textColor=\"@color/color_bold_text_black\"\n                    android:textSize=\"@dimen/edt_text_size\"\n                    android:visibility=\"gone\"\n                    app:layout_constraintBottom_toBottomOf=\"@id/spinner_drift_mode\"\n                    app:layout_constraintLeft_toLeftOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"@id/spinner_drift_mode\" />\n\n                <Spinner\n                    android:id=\"@+id/spinner_drift_mode\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"@dimen/edt_height\"\n                    android:layout_marginStart=\"@dimen/edt_text_margin_left\"\n                    android:layout_marginLeft=\"@dimen/edt_text_margin_left\"\n                    android:layout_marginTop=\"@dimen/common_margin_top1\"\n                    android:layout_marginRight=\"@dimen/dk_dp_5\"\n                    android:background=\"@drawable/dk_bg_custom_spinner\"\n                    android:overlapAnchor=\"false\"\n                    android:spinnerMode=\"dropdown\"\n                    android:visibility=\"gone\"\n                    app:layout_constraintLeft_toRightOf=\"@id/tv_des_drift_mode\"\n                    app:layout_constraintRight_toLeftOf=\"@id/guide_line_vertical_center1\"\n                    app:layout_constraintTop_toBottomOf=\"@id/spinner_drift_type\" />\n\n                <View\n                    android:id=\"@+id/reference_view\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"1dp\"\n                    android:layout_marginTop=\"40dp\"\n                    android:visibility=\"invisible\"\n                    app:layout_constraintLeft_toLeftOf=\"parent\"\n                    app:layout_constraintRight_toRightOf=\"parent\"\n                    app:layout_constraintTop_toBottomOf=\"@id/edt_drift_accuracy\" />\n\n                <CheckBox\n                    android:id=\"@+id/cb_over_pass\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/auto_mode_over_pass\"\n                    android:textColor=\"@color/color_bold_text_black\"\n                    android:textSize=\"@dimen/edt_text_size\"\n                    app:layout_constraintBottom_toBottomOf=\"@id/reference_view\"\n                    app:layout_constraintLeft_toLeftOf=\"parent\"\n                    app:layout_constraintRight_toLeftOf=\"@id/cb_tunnel\"\n                    app:layout_constraintTop_toTopOf=\"@id/reference_view\" />\n\n                <CheckBox\n                    android:id=\"@+id/cb_tunnel\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/auto_mode_tunnel\"\n                    android:textColor=\"@color/color_bold_text_black\"\n                    android:textSize=\"@dimen/edt_text_size\"\n                    app:layout_constraintBottom_toBottomOf=\"@id/reference_view\"\n                    app:layout_constraintLeft_toRightOf=\"@id/cb_over_pass\"\n                    app:layout_constraintRight_toRightOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"@id/reference_view\" />\n\n                <TextView\n                    android:id=\"@+id/tv_des_select_path\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/des_drift_path\"\n                    android:textColor=\"@color/color_bold_text_black\"\n                    android:textSize=\"@dimen/edt_text_size\"\n                    app:layout_constraintBottom_toBottomOf=\"@id/reference_view\"\n                    app:layout_constraintLeft_toLeftOf=\"parent\"\n                    app:layout_constraintRight_toLeftOf=\"@id/seekbar_select_path\"\n                    app:layout_constraintTop_toTopOf=\"@id/reference_view\" />\n\n                <com.didichuxing.doraemonkit.gps_mock.widget.SeekRangeBar\n                    android:id=\"@+id/seekbar_select_path\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"@dimen/common_margin_left_right\"\n                    app:bar_bg_shape=\"@drawable/dk_seek_bar_background_shape\"\n                    app:bar_fg_shape=\"@drawable/dk_seek_bar_foreground_shape\"\n                    app:bar_height=\"@dimen/seek_range_bar_height\"\n                    app:indicator_size=\"@dimen/seek_range_bar_indicator_size\"\n                    app:indicator_text_color=\"@color/color_seek_bar_indicator_text\"\n                    app:indicator_text_size=\"@dimen/seek_range_bar_indicator_text_size\"\n                    app:layout_constraintBottom_toBottomOf=\"@id/reference_view\"\n                    app:layout_constraintLeft_toRightOf=\"@id/tv_des_select_path\"\n                    app:layout_constraintRight_toRightOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"@id/reference_view\"\n                    app:slide_btn_high_shape=\"@drawable/dk_seek_bar_circle_indicator\"\n                    app:slide_btn_low_shape=\"@drawable/dk_seek_bar_circle_indicator\" />\n\n                <androidx.constraintlayout.widget.Group\n                    android:id=\"@+id/group_select_path\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:visibility=\"visible\"\n                    app:constraint_referenced_ids=\"tv_des_select_path,seekbar_select_path\" />\n\n                <androidx.constraintlayout.widget.Group\n                    android:id=\"@+id/group_select_auto_path\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:visibility=\"gone\"\n                    app:constraint_referenced_ids=\"cb_over_pass,cb_tunnel\" />\n\n\n                <Button\n                    android:id=\"@+id/btn_mock_route2\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"@dimen/confirm_btn_height\"\n                    android:layout_marginTop=\"@dimen/common_margin_top\"\n                    android:background=\"@drawable/dk_bg_btn_round_rectangle\"\n                    android:text=\"@string/btn_text_start_mock\"\n                    android:textColor=\"@color/color_confirm_btn_text\"\n                    app:layout_constraintRight_toRightOf=\"parent\"\n                    app:layout_constraintTop_toBottomOf=\"@id/reference_view\" />\n            </androidx.constraintlayout.widget.ConstraintLayout>\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n        <TextView\n            android:id=\"@+id/tv_des_real_distance\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"@dimen/common_margin_top1\"\n            android:text=\"@string/text_des_origin_distance\"\n            android:textColor=\"@color/color_bold_text_black\"\n            android:textSize=\"@dimen/edt_text_size\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/container_route_mock\" />\n\n        <TextView\n            android:id=\"@+id/tv_real_distance\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"@dimen/edt_text_margin_left\"\n            android:textColor=\"@color/color_bold_text_black\"\n            android:textSize=\"@dimen/des_text_size\"\n            app:layout_constraintBottom_toBottomOf=\"@id/tv_des_real_distance\"\n            app:layout_constraintLeft_toRightOf=\"@id/tv_des_real_distance\"\n            app:layout_constraintTop_toTopOf=\"@id/tv_des_real_distance\"\n            tools:text=\"23456\" />\n\n        <TextView\n            android:id=\"@+id/tv_des_mock_distance\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/text_des_mock_distance\"\n            android:textColor=\"@color/color_bold_text_black\"\n            android:layout_marginLeft=\"@dimen/common_margin_left_right\"\n            android:textSize=\"@dimen/edt_text_size\"\n            app:layout_constraintBottom_toBottomOf=\"@id/tv_des_real_distance\"\n            app:layout_constraintLeft_toRightOf=\"@id/tv_real_distance\"\n            app:layout_constraintTop_toTopOf=\"@id/tv_des_real_distance\" />\n\n        <TextView\n            android:id=\"@+id/tv_mock_distance\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"@dimen/edt_text_margin_left\"\n            android:textColor=\"@color/color_bold_text_black\"\n            android:textSize=\"@dimen/des_text_size\"\n            app:layout_constraintBottom_toBottomOf=\"@id/tv_des_real_distance\"\n            app:layout_constraintLeft_toRightOf=\"@id/tv_des_mock_distance\"\n            app:layout_constraintTop_toTopOf=\"@id/tv_des_real_distance\"\n            tools:text=\"23456\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <FrameLayout\n        android:id=\"@+id/expand_iv_fl\"\n        android:layout_width=\"80dp\"\n        android:layout_height=\"26dp\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/mock_parent_layout\">\n\n        <ImageView\n            android:id=\"@+id/iv_down_expand\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:background=\"@color/color_white_translucent\"\n            android:src=\"@mipmap/dk_icon_arrow_up\" />\n    </FrameLayout>\n\n</androidx.constraintlayout.motion.widget.MotionLayout>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/layout/dk_item_position_select.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/root_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/color_white\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:padding=\"10dp\">\n\n        <ImageView\n            android:layout_width=\"22dp\"\n            android:layout_height=\"22dp\"\n            android:src=\"@mipmap/dk_icon_map_loc\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"10dp\">\n\n            <TextView\n                android:id=\"@+id/tv_address_name\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:textColor=\"@color/color_bold_text_black\"\n                android:textSize=\"@dimen/edt_text_size\"\n                tools:text=\"西湖文化广场\" />\n\n            <TextView\n                android:id=\"@+id/tv_address_detail\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:textColor=\"@color/color_bold_text_normal\"\n                android:textSize=\"@dimen/des_text_size\"\n                tools:text=\"杭州市拱墅区西湖文化广场\" />\n        </LinearLayout>\n    </LinearLayout>\n\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"1dp\"\n        android:background=\"#f2f2f2\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/layout/dk_position_select_dialog_fragment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/color_white\"\n    android:orientation=\"vertical\">\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/head_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/dk_bg_gray_round_rectangle\"\n        android:padding=\"@dimen/border_padding\">\n\n        <EditText\n            android:id=\"@+id/edt_select_position\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"@dimen/edt_height\"\n            android:background=\"@drawable/dk_bg_round_edt\"\n            android:hint=\"@string/position_address\"\n            android:paddingLeft=\"6dp\"\n            android:singleLine=\"true\"\n            android:textColor=\"@color/color_bold_text_black\"\n            android:textCursorDrawable=\"@drawable/dk_bg_edt_hint\"\n            android:textSize=\"@dimen/edt_text_size\"\n            android:textColorHint=\"@color/color_bold_text_hint\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv_position_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\" />\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <declare-styleable name=\"SeekRangeBar\">\n        <!--滑块背景-->\n        <attr name=\"slide_btn_low_shape\" format=\"reference\" />\n        <attr name=\"slide_btn_high_shape\" format=\"reference\" />\n        <!--单位字体颜色-->\n        <attr name=\"indicator_text_color\" format=\"color\"/>\n        <attr name=\"indicator_text_size\" format=\"dimension\"/>\n        <!--滑块的大小-->\n        <attr name=\"indicator_size\" format=\"dimension\"/>\n        <!--进图条背景-->\n        <attr name=\"bar_bg_shape\" format=\"reference\"/>\n        <!--进度条前景-->\n        <attr name=\"bar_fg_shape\" format=\"reference\"/>\n        <!--进度条宽-->\n        <attr name=\"bar_height\" format=\"dimension\"/>\n    </declare-styleable>\n\n    <style name=\"Dialog.FullScreen\" parent=\"Theme.AppCompat.Dialog\">\n        <item name=\"android:windowIsFloating\">false</item>\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"android:background\">@android:color/transparent</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:windowFullscreen\">true</item>\n        <item name=\"android:colorBackgroundCacheHint\">@null</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"color_bg_gray\">#ebebe8</color>\n    <color name=\"color_bold_text_black\">#333333</color>\n    <color name=\"color_bold_text_normal\">#777777</color>\n    <color name=\"color_confirm_btn_text\">#CEFFFFFF</color>\n    <color name=\"color_bold_text_hint\">#999999</color>\n    <color name=\"color_edt_stroke_line\">#ceccca</color>\n    <color name=\"color_seek_bar_indicator\">#FF419EF8</color>\n    <color name=\"color_seek_bar_indicator_text\">#FF419EF8</color>\n    <color name=\"color_btn_press\">#8F419EF8</color>\n    <color name=\"color_seek_bar_background\">#ceccca</color>\n    <color name=\"color_btn_disable\">#e2e0de</color>\n    <color name=\"color_white\">#FFFFFF</color>\n    <color name=\"color_white_translucent\">#F0FFFFFF</color>\n\n</resources>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <dimen name=\"border_margin\">12dp</dimen>\n    <dimen name=\"border_padding\">8dp</dimen>\n    <dimen name=\"title_text_size\">18dp</dimen>\n    <dimen name=\"edt_height\">36dp</dimen>\n    <dimen name=\"confirm_btn_height\">40dp</dimen>\n    <dimen name=\"confirm_btn_width\">60dp</dimen>\n    <dimen name=\"edt_text_size\">16dp</dimen>\n    <dimen name=\"pos_edt_text_size\">12dp</dimen>\n    <dimen name=\"edt_text_margin_left\">5dp</dimen>\n    <dimen name=\"des_text_size\">14dp</dimen>\n    <dimen name=\"common_margin_top\">20dp</dimen>\n    <dimen name=\"common_margin_top1\">10dp</dimen>\n    <dimen name=\"common_margin_left_right\">6dp</dimen>\n    <dimen name=\"seek_range_bar_indicator_text_size\">12dp</dimen>\n    <dimen name=\"seek_range_bar_height\">6dp</dimen>\n    <dimen name=\"seek_range_bar_indicator_size\">25dp</dimen>\n    <dimen name=\"map_loc_iv_size\">36dp</dimen>\n    <dimen name=\"map_loc_iv_size_radius\">18dp</dimen>\n\n</resources>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/values/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"title_pos_mock\">位置模拟</string>\n    <string name=\"title_route_mock\">轨迹模拟</string>\n    <string name=\"btn_text_start_mock\">开始模拟</string>\n    <string name=\"title_route_drift_mock\">漂移模拟</string>\n    <string name=\"title_route_lost_loc_mock\">定位点丢失</string>\n    <string name=\"title_route_mock_start\">起点:</string>\n    <string name=\"title_route_mock_end\">终点:</string>\n    <string name=\"title_route_mock_speed\">速度:</string>\n    <string name=\"route_mock_speed_unit\">km/h</string>\n    <string name=\"edt_pos_lat_long_hint\"><font size=\"12\">输入经纬度(逗号隔开),例如120.152637,30.283940</font></string>\n    <string name=\"des_drift_type\">漂移类型:</string>\n    <string-array name=\"array_drift_type\">\n        <item>随机漂移</item>\n        <item>路径漂移</item>\n    </string-array>\n    <string name=\"des_drift_accuracy\">漂移范围:</string>\n    <string name=\"unit_drift_accuracy\">米</string>\n    <string name=\"des_drift_mode\">漂移模式:</string>\n    <string-array name=\"array_drift_mode\">\n        <!--<item>智能模式</item>-->\n        <item>手动模式</item>\n    </string-array>\n    <string name=\"des_drift_path\">选择漂移路径范围:</string>\n    <string name=\"des_lost_loc_path\">定位点丢失范围:</string>\n    <string name=\"auto_mode_tunnel\">隧道</string>\n    <string name=\"auto_mode_over_pass\">高架</string>\n\n    <string name=\"position_address\">请输入地址</string>\n    <string name=\"btn_text_stop_mock\">停止模拟</string>\n\n    <string name=\"text_des_origin_distance\">原始路程:</string>\n    <string name=\"text_des_mock_distance\">模拟路程:</string>\n\n</resources>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/values-en-rCN/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"title_pos_mock\">位置模拟</string>\n    <string name=\"title_route_mock\">轨迹模拟</string>\n    <string name=\"btn_text_start_mock\">开始模拟</string>\n    <string name=\"title_route_drift_mock\">漂移模拟</string>\n    <string name=\"title_route_lost_loc_mock\">定位点丢失</string>\n    <string name=\"des_lost_loc_path\">定位点丢失范围:</string>\n    <string name=\"title_route_mock_start\">起点:</string>\n    <string name=\"title_route_mock_end\">终点:</string>\n    <string name=\"title_route_mock_speed\">速度:</string>\n    <string name=\"route_mock_speed_unit\">km/h</string>\n    <string name=\"edt_pos_lat_long_hint\"><font size=\"12\">输入经纬度(逗号隔开),例如120.152637,30.283940</font></string>\n    <string name=\"des_drift_type\">漂移类型:</string>\n    <string-array name=\"array_drift_type\">\n        <item>随机漂移</item>\n        <item>路径漂移</item>\n    </string-array>\n    <string name=\"des_drift_accuracy\">漂移范围:</string>\n    <string name=\"unit_drift_accuracy\">米</string>\n    <string name=\"des_drift_mode\">漂移模式:</string>\n    <string-array name=\"array_drift_mode\">\n        <!--<item>智能模式</item>-->\n        <item>手动模式</item>\n    </string-array>\n    <string name=\"des_drift_path\">选择漂移路径范围:</string>\n    <string name=\"auto_mode_tunnel\">隧道</string>\n    <string name=\"auto_mode_over_pass\">高架</string>\n\n    <string name=\"position_address\">请输入地址</string>\n    <string name=\"btn_text_stop_mock\">停止模拟</string>\n\n    <string name=\"text_des_origin_distance\">原始路程:</string>\n    <string name=\"text_des_mock_distance\">模拟路程:</string>\n\n</resources>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/values-zh-rTW/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"title_pos_mock\">位置模拟</string>\n    <string name=\"title_route_mock\">轨迹模拟</string>\n    <string name=\"btn_text_start_mock\">开始模拟</string>\n    <string name=\"title_route_drift_mock\">漂移模拟</string>\n    <string name=\"title_route_mock_start\">起点:</string>\n    <string name=\"title_route_mock_end\">终点:</string>\n    <string name=\"title_route_mock_speed\">速度:</string>\n    <string name=\"route_mock_speed_unit\">km/h</string>\n    <string name=\"edt_pos_lat_long_hint\"><font size=\"12\">输入经纬度(逗号隔开),例如120.152637,30.283940</font></string>\n    <string name=\"title_route_lost_loc_mock\">定位点丢失</string>\n    <string name=\"des_lost_loc_path\">定位点丢失范围:</string>\n    <string name=\"des_drift_type\">漂移类型:</string>\n    <string-array name=\"array_drift_type\">\n        <item>随机漂移</item>\n        <item>路径漂移</item>\n    </string-array>\n    <string name=\"des_drift_accuracy\">漂移范围:</string>\n    <string name=\"unit_drift_accuracy\">米</string>\n    <string name=\"des_drift_mode\">漂移模式:</string>\n    <string-array name=\"array_drift_mode\">\n        <!--<item>智能模式</item>-->\n        <item>手动模式</item>\n    </string-array>\n    <string name=\"des_drift_path\">选择漂移路径范围:</string>\n    <string name=\"auto_mode_tunnel\">隧道</string>\n    <string name=\"auto_mode_over_pass\">高架</string>\n\n    <string name=\"position_address\">请输入地址</string>\n    <string name=\"btn_text_stop_mock\">停止模拟</string>\n\n    <string name=\"text_des_origin_distance\">原始路程:</string>\n    <string name=\"text_des_mock_distance\">模拟路程:</string>\n\n</resources>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/values-zh-rUS/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"title_pos_mock\">位置模拟</string>\n    <string name=\"title_route_mock\">轨迹模拟</string>\n    <string name=\"btn_text_start_mock\">开始模拟</string>\n    <string name=\"title_route_drift_mock\">漂移模拟</string>\n    <string name=\"title_route_mock_start\">起点:</string>\n    <string name=\"title_route_mock_end\">终点:</string>\n    <string name=\"title_route_mock_speed\">速度:</string>\n    <string name=\"route_mock_speed_unit\">km/h</string>\n    <string name=\"edt_pos_lat_long_hint\"><font size=\"12\">输入经纬度(逗号隔开),例如120.152637,30.283940</font></string>\n    <string name=\"title_route_lost_loc_mock\">定位点丢失</string>\n    <string name=\"des_lost_loc_path\">定位点丢失范围:</string>\n    <string name=\"des_drift_type\">漂移类型:</string>\n    <string-array name=\"array_drift_type\">\n        <item>随机漂移</item>\n        <item>路径漂移</item>\n    </string-array>\n    <string name=\"des_drift_accuracy\">漂移范围:</string>\n    <string name=\"unit_drift_accuracy\">米</string>\n    <string name=\"des_drift_mode\">漂移模式:</string>\n    <string-array name=\"array_drift_mode\">\n        <!--<item>智能模式</item>-->\n        <item>手动模式</item>\n    </string-array>\n    <string name=\"des_drift_path\">选择漂移路径范围:</string>\n    <string name=\"auto_mode_tunnel\">隧道</string>\n    <string name=\"auto_mode_over_pass\">高架</string>\n\n    <string name=\"position_address\">请输入地址</string>\n    <string name=\"btn_text_stop_mock\">停止模拟</string>\n\n    <string name=\"text_des_origin_distance\">原始路程:</string>\n    <string name=\"text_des_mock_distance\">模拟路程:</string>\n\n</resources>\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/main/res/xml/gps_mock_root_scene.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<MotionScene xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <ConstraintSet android:id=\"@+id/start\">\n        <Constraint\n            android:id=\"@id/mock_parent_layout\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/title_bar\" />\n\n        <Constraint android:id=\"@id/iv_map_center_loc\">\n            <PropertySet app:visibilityMode=\"ignore\" />\n        </Constraint>\n        <!--<Constraint android:id=\"@id/drift_mock_set_layout\">-->\n        <!--    <PropertySet app:visibilityMode=\"ignore\" />-->\n        <!--</Constraint>-->\n    </ConstraintSet>\n\n    <ConstraintSet android:id=\"@+id/end\">\n        <Constraint\n            android:id=\"@id/mock_parent_layout\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"1px\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/title_bar\" />\n\n        <Constraint android:id=\"@id/iv_map_center_loc\">\n            <PropertySet app:visibilityMode=\"ignore\" />\n        </Constraint>\n        <!--<Constraint android:id=\"@id/drift_mock_set_layout\">-->\n        <!--    <PropertySet app:visibilityMode=\"ignore\"/>-->\n        <!--</Constraint>-->\n    </ConstraintSet>\n\n    <Transition\n        app:constraintSetEnd=\"@id/end\"\n        app:constraintSetStart=\"@id/start\"\n        app:duration=\"300\"\n        app:motionInterpolator=\"easeInOut\">\n        <OnClick\n            app:clickAction=\"toggle\"\n            app:targetId=\"@id/expand_iv_fl\" />\n    </Transition>\n</MotionScene>\n\n"
  },
  {
    "path": "Android/dokit-gps-mock/src/test/java/com/didichuxing/doraemonkit/ExampleUnitTest.java",
    "content": "package com.didichuxing.doraemonkit;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() {\n        assertEquals(4, 2 + 2);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/.gitignore",
    "content": "/build"
  },
  {
    "path": "Android/dokit-leakcanary/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply from: '../upload.gradle'\n\n/**\n * 必须配置git path 否则会报Error:(29, 0) CreateProcess error=2, The system cannot find the file specified 错误\n * 假如你不想配置git path 可以将下文的GIT_SHA值写死成具体的字符串\n * @return\n */\ndef gitSha() {\n    return 'git rev-parse --short HEAD'.execute().text.trim()\n}\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_16\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n\n        buildConfigField \"String\", \"LEAKCANARY_LIBRARY_VERSION\", \"\\\"1.6.3\\\"\"\n        buildConfigField \"String\", \"GIT_SHA\", \"\\\"${gitSha()}\\\"\"\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n    /**\n     * 支持ViewBinding\n     */\n    buildFeatures {\n        //viewBinding = true\n    }\n\n}\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    implementation rootProject.ext.dependencies[\"kotlin\"]\n    implementation rootProject.ext.dependencies[\"appcompat\"]\n    implementation 'androidx.test.ext:junit:1.1.2'\n    //leakCanary\n    implementation rootProject.ext.dependencies[\"haha\"]\n    compileOnly project(':dokit')\n    //跨进程通信框架\n    compileOnly project(\":dokit-util\")\n\n}"
  },
  {
    "path": "Android/dokit-leakcanary/gradle.properties",
    "content": "ARTIFACT_ID=dokitx-leakcanary"
  },
  {
    "path": "Android/dokit-leakcanary/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original from file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright (C) 2015 Square, Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.squareup.leakcanary\">\n\n    <!-- To store the heap dumps and leak analysis results. -->\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <!-- To allow starting foreground services on Android P+ - https://developer.android.com/preview/behavior-changes#fg-svc -->\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />\n\n    <application>\n        <service\n            android:name=\".internal.HeapAnalyzerService\"\n            android:enabled=\"false\"\n            android:process=\":leakcanary\" />\n        <service\n            android:name=\".UploadLeakService\"\n            android:enabled=\"false\"\n            android:process=\":leakcanary\" />\n\n        <provider\n            android:name=\"com.squareup.leakcanary.internal.LeakCanaryFileProvider\"\n            android:authorities=\"com.squareup.leakcanary.fileprovider.${applicationId}\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/leak_canary_file_paths\" />\n        </provider>\n\n        <activity\n            android:name=\".internal.DisplayLeakActivity\"\n            android:enabled=\"false\"\n            android:icon=\"@mipmap/leak_canary_icon\"\n            android:label=\"@string/leak_canary_display_activity_label\"\n            android:process=\":leakcanary\"\n            android:theme=\"@style/leak_canary_LeakCanary.Base\">\n            <!--            leakCanary桌面图标入口-->\n            <!--            android:taskAffinity=\"com.squareup.leakcanary.${applicationId}\"-->\n            <!--            <intent-filter>-->\n            <!--                <action android:name=\"android.intent.action.MAIN\" />-->\n            <!--                <category android:name=\"android.intent.category.LAUNCHER\" />-->\n            <!--            </intent-filter>-->\n        </activity>\n        <activity\n            android:name=\".internal.RequestStoragePermissionActivity\"\n            android:enabled=\"false\"\n            android:excludeFromRecents=\"true\"\n            android:icon=\"@mipmap/leak_canary_icon\"\n            android:label=\"@string/leak_canary_storage_permission_activity_label\"\n            android:process=\":leakcanary\"\n            android:roundIcon=\"@mipmap/leak_canary_icon\"\n            android:taskAffinity=\"com.squareup.leakcanary.${applicationId}\"\n            android:theme=\"@style/leak_canary_Theme.Transparent\" />\n\n        <service\n            android:name=\"com.didichuxing.doraemonkit.abridge.service.ABridgeService\"\n            android:enabled=\"true\"\n            android:exported=\"true\"\n            android:process=\":aidl\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.ICALL_AIDL_YIFEI\" />\n            </intent-filter>\n        </service>\n\n        <service\n            android:name=\"com.didichuxing.doraemonkit.abridge.service.MessengerService\"\n            android:enabled=\"true\"\n            android:exported=\"true\"\n            android:process=\":messenger\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.ICALL_MESSENGER_YIFEI\" />\n            </intent-filter>\n        </service>\n\n    </application>\n</manifest>\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/aidl/com/didichuxing/doraemonkit/aidl/IReceiverAidlInterface.aidl",
    "content": "// ICallback.aidl\npackage com.didichuxing.doraemonkit.aidl;\n\n// Declare any non-default types here with import statements\n\ninterface IReceiverAidlInterface {\n    void receiveMessage(in String json);\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/aidl/com/didichuxing/doraemonkit/aidl/ISenderAidlInterface.aidl",
    "content": "// ICall.aidl\npackage com.didichuxing.doraemonkit.aidl;\n\nimport  com.didichuxing.doraemonkit.aidl.IReceiverAidlInterface;\n\ninterface ISenderAidlInterface {\n\n    void join(IBinder token);\n\n    void leave(IBinder token);\n\n    void sendMessage(String json);\n\n    void registerCallback(IReceiverAidlInterface cb);\n\n    void unregisterCallback(IReceiverAidlInterface cb);\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/didichuxing/doraemonkit/LeakCanaryManager.java",
    "content": "package com.didichuxing.doraemonkit;\n\nimport android.app.Application;\nimport android.util.Log;\n\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.abridge.AbridgeCallBack;\nimport com.didichuxing.doraemonkit.abridge.IBridge;\nimport com.didichuxing.doraemonkit.constant.DokitConstant;\nimport com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil;\nimport com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo;\nimport com.squareup.leakcanary.LeakCanary;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-10-17-10:11\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass LeakCanaryManager {\n    private static final String TAG = \"LeakCanaryManager\";\n\n    public static void install(Application app) {\n        if (LeakCanary.isInAnalyzerProcess(app)) {\n            // This process is dedicated to LeakCanary for heap analysis.\n            // You should not init your app in this process.\n            return;\n        }\n        LeakCanary.install(app);\n    }\n\n    /**\n     * 初始化跨进程框架\n     * 接受leakcanary 进程泄漏传递过来的数据\n     */\n    public static void initAidlBridge(Application application) {\n        if (!DokitConstant.APP_HEALTH_RUNNING) {\n            return;\n        }\n        IBridge.init(application, application.getPackageName(), IBridge.AbridgeType.AIDL);\n        IBridge.registerAIDLCallBack(new AbridgeCallBack() {\n            @Override\n            public void receiveMessage(String message) {\n                try {\n                    Log.i(TAG, \"====aidl=====>\" + message);\n                    if (DokitConstant.APP_HEALTH_RUNNING) {\n                        AppHealthInfo.DataBean.LeakBean leakBean = new AppHealthInfo.DataBean.LeakBean();\n                        leakBean.setPage(ActivityUtils.getTopActivity().getClass().getCanonicalName());\n                        leakBean.setDetail(message);\n                        AppHealthInfoUtil.getInstance().addLeakInfo(leakBean);\n                    }\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n\n            }\n        });\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/didichuxing/doraemonkit/abridge/AbridgeCallBack.java",
    "content": "package com.didichuxing.doraemonkit.abridge;\n\n/**\n * 类描述：\n * 创建人：yifei\n * 创建时间：2018/12/18\n * 修改人：\n * 修改时间：\n * 修改备注：\n */\npublic interface AbridgeCallBack {\n    void receiveMessage(String message);\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/didichuxing/doraemonkit/abridge/AbridgeManager.java",
    "content": "package com.didichuxing.doraemonkit.abridge;\n\nimport android.app.Application;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.os.Binder;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.RemoteException;\nimport android.text.TextUtils;\nimport android.util.Log;\n\n;\n\nimport com.didichuxing.doraemonkit.aidl.IReceiverAidlInterface;\nimport com.didichuxing.doraemonkit.aidl.ISenderAidlInterface;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 类描述：\n * 创建人：yifei\n * 创建时间：2018/12/18\n * 修改人：\n * 修改时间：\n * 修改备注：\n */\nfinal class AbridgeManager {\n    private static final String TAG = \"AbridgeManager\";\n    private static final String BIND_SERVICE_ACTION = \"android.intent.action.ICALL_AIDL_YIFEI\";\n    private static final String BIND_SERVICE_COMPONENT_NAME_CLS = \"com.didichuxing.doraemonkit.abridge.service.ABridgeService\";\n    private static AbridgeManager instance;\n\n    private Application sApplication;\n    private String sServicePkgName;\n    private Handler sHandler;\n    private List<AbridgeCallBack> sList;\n\n    private AbridgeManager() {\n        sList = new ArrayList<>();\n    }\n\n    public static AbridgeManager getInstance() {\n        if (instance == null) {\n            synchronized (AbridgeManager.class) {\n                if (instance == null) {\n                    instance = new AbridgeManager();\n                }\n            }\n        }\n        return instance;\n    }\n\n    /**\n     * 初始化\n     *\n     * @param sApplication\n     * @param sServicePkgName\n     */\n    public void init(Application sApplication, String sServicePkgName) {\n        this.sApplication = sApplication;\n        this.sServicePkgName = sServicePkgName;\n        sHandler = new Handler(sApplication.getMainLooper());\n    }\n\n    public void registerRemoteCallBack(AbridgeCallBack callBack) {\n        if (sList != null) {\n            sList.add(callBack);\n        }\n    }\n\n    public void uRegisterRemoteCallBack(AbridgeCallBack callBack) {\n        if (sList != null && callBack != null) {\n            sList.remove(callBack);\n        }\n    }\n\n    public void callRemote(String message) {\n        if (iSenderAidlInterface == null) {\n            Log.e(TAG, \"error: ipc process not started，please make sure ipc process is alive\");\n            return;\n        }\n        if (!TextUtils.isEmpty(message)) {\n            try {\n                iSenderAidlInterface.sendMessage(message);\n            } catch (RemoteException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    private IBinder sBinder = new Binder();\n    private ISenderAidlInterface iSenderAidlInterface;\n\n    private IReceiverAidlInterface iReceiverAidlInterface = new IReceiverAidlInterface.Stub() {\n\n        @Override\n        public void receiveMessage(final String json) throws RemoteException {\n            sHandler.post(new Runnable() {\n                @Override\n                public void run() {\n                    for (AbridgeCallBack medium : sList) {\n                        medium.receiveMessage(json);\n                    }\n                }\n            });\n        }\n    };\n\n    private ServiceConnection serviceConnection = new ServiceConnection() {\n        @Override\n        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {\n            iSenderAidlInterface = ISenderAidlInterface.Stub.asInterface(iBinder);\n            if (iSenderAidlInterface == null) {\n                Log.e(TAG, \"error: ipc process not started，please make sure ipc process is alive\");\n                return;\n            }\n            try {\n                iSenderAidlInterface.join(sBinder);\n                iSenderAidlInterface.registerCallback(iReceiverAidlInterface);\n            } catch (RemoteException e) {\n                e.printStackTrace();\n            }\n        }\n\n        @Override\n        public void onServiceDisconnected(ComponentName componentName) {\n            iSenderAidlInterface = null;\n        }\n    };\n\n    /**\n     * 启动服务\n     */\n    public void startAndBindService() {\n        Intent serviceIntent = new Intent();\n        serviceIntent.setAction(BIND_SERVICE_ACTION);\n        serviceIntent.setComponent(new ComponentName(sServicePkgName, BIND_SERVICE_COMPONENT_NAME_CLS));\n        sApplication.startService(serviceIntent);\n        sApplication.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);\n    }\n\n    /**\n     * 关闭服务\n     */\n    public void unBindService() {\n        if (iSenderAidlInterface == null) {\n            Log.e(TAG, \"error: ipc process not started，please make sure ipc process is alive\");\n            return;\n        }\n        try {\n            iSenderAidlInterface.unregisterCallback(iReceiverAidlInterface);\n            iSenderAidlInterface.leave(sBinder);\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n        sApplication.unbindService(serviceConnection);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/didichuxing/doraemonkit/abridge/AbridgeMessengerCallBack.java",
    "content": "package com.didichuxing.doraemonkit.abridge;\n\nimport android.os.Message;\n\n/**\n * [接受远程发送过来的消息]\n * author: yifei\n * created at 18/6/2 下午11:52\n */\npublic interface AbridgeMessengerCallBack {\n    void receiveMessage(Message message);\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/didichuxing/doraemonkit/abridge/AbridgeMessengerManager.java",
    "content": "package com.didichuxing.doraemonkit.abridge;\n\nimport android.app.Application;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.Message;\nimport android.os.Messenger;\nimport android.os.RemoteException;\nimport android.util.Log;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 类描述：\n * 创建人：yifei\n * 创建时间：2018/12/18\n * 修改人：\n * 修改时间：\n * 修改备注：\n */\nfinal class AbridgeMessengerManager {\n    private static final String TAG = \"AbridgeMessengerManager\";\n    private static final String BIND_SERVICE_ACTION = \"android.intent.action.ICALL_MESSENGER_YIFEI\";\n    private static final String BIND_MESSENGER_SERVICE_COMPONENT_NAME_CLS = \"com.didichuxing.doraemonkit.abridge.service.MessengerService\";\n    private static AbridgeMessengerManager instance;\n\n    private Application sApplication;\n    private String sServicePkgName;\n    private List<AbridgeMessengerCallBack> sList;\n\n    private AbridgeMessengerManager() {\n        sList = new ArrayList<>();\n    }\n\n    public static AbridgeMessengerManager getInstance() {\n        if (instance == null) {\n            synchronized (AbridgeMessengerManager.class) {\n                if (instance == null) {\n                    instance = new AbridgeMessengerManager();\n                }\n            }\n        }\n        return instance;\n    }\n\n    /**\n     * 初始化\n     *\n     * @param sApplication\n     * @param sServicePkgName\n     */\n    public void init(Application sApplication, String sServicePkgName) {\n        this.sApplication = sApplication;\n        this.sServicePkgName = sServicePkgName;\n    }\n\n    public void registerRemoteCallBack(AbridgeMessengerCallBack callBack) {\n        if (sList != null) {\n            sList.add(callBack);\n        }\n    }\n\n    public void uRegisterRemoteCallBack(AbridgeMessengerCallBack callBack) {\n        if (sList != null && callBack != null) {\n            sList.remove(callBack);\n        }\n    }\n\n    public void callRemote(Message message) {\n        if (sMessenger == null) {\n            Log.e(TAG, \"error: ipc process not started，please make sure ipc process is alive\");\n            return;\n        }\n        try {\n            message.replyTo = replyMessenger;\n            sMessenger.send(message);\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n    }\n\n    private Messenger replyMessenger = new Messenger(new Handler() {\n        @Override\n        public void handleMessage(Message msg) {\n            super.handleMessage(msg);\n            for (AbridgeMessengerCallBack callBack : sList) {\n                callBack.receiveMessage(msg);\n            }\n        }\n    });\n    private Messenger sMessenger;\n\n    private ServiceConnection serviceConnection = new ServiceConnection() {\n        @Override\n        public void onServiceConnected(ComponentName name, IBinder service) {\n            sMessenger = new Messenger(service);\n        }\n\n        @Override\n        public void onServiceDisconnected(ComponentName name) {\n            sMessenger = null;\n        }\n    };\n\n    public void startAndBindService() {\n        Intent serviceIntent = new Intent();\n        serviceIntent.setAction(BIND_SERVICE_ACTION);\n        serviceIntent.setComponent(new ComponentName(sServicePkgName, BIND_MESSENGER_SERVICE_COMPONENT_NAME_CLS));\n        sApplication.startService(serviceIntent);\n        sApplication.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);\n    }\n\n    public void unBindService() {\n        if (sMessenger == null) {\n            Log.e(TAG, \"error: ipc process not started，please make sure ipc process is alive\");\n            return;\n        }\n        sApplication.unbindService(serviceConnection);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/didichuxing/doraemonkit/abridge/IBridge.java",
    "content": "package com.didichuxing.doraemonkit.abridge;\n\nimport android.app.Application;\nimport android.os.Message;\n\nimport androidx.annotation.NonNull;\n\n/**\n * 类描述：\n * 创建人：yifei\n * 创建时间：2018/5/15\n * 修改人：\n * 修改时间：\n * 修改备注：\n */\n\npublic final class IBridge {\n\n    public enum AbridgeType {\n        AIDL, MESSENGER\n    }\n\n    private IBridge() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    public static void init(@NonNull final Application app, String servicePkgName, AbridgeType type) {\n        if (type == AbridgeType.AIDL) {\n            AbridgeManager.getInstance().init(app, servicePkgName);\n            AbridgeManager.getInstance().startAndBindService();\n        } else {\n            AbridgeMessengerManager.getInstance().init(app, servicePkgName);\n            AbridgeMessengerManager.getInstance().startAndBindService();\n        }\n    }\n\n    public static void recycle() {\n        AbridgeManager.getInstance().unBindService();\n        AbridgeMessengerManager.getInstance().unBindService();\n    }\n\n    public static void sendAIDLMessage(String message) {\n        AbridgeManager.getInstance().callRemote(message);\n    }\n\n    public static void registerAIDLCallBack(AbridgeCallBack callBack) {\n        AbridgeManager.getInstance().registerRemoteCallBack(callBack);\n    }\n\n    public static void uRegisterAIDLCallBack(AbridgeCallBack callBack) {\n        AbridgeManager.getInstance().uRegisterRemoteCallBack(callBack);\n    }\n\n    public static void sendMessengerMessage(Message message) {\n        AbridgeMessengerManager.getInstance().callRemote(message);\n    }\n\n    public static void registerMessengerCallBack(AbridgeMessengerCallBack callBack) {\n        AbridgeMessengerManager.getInstance().registerRemoteCallBack(callBack);\n    }\n\n    public static void uRegisterMessengerCallBack(AbridgeMessengerCallBack callBack) {\n        AbridgeMessengerManager.getInstance().uRegisterRemoteCallBack(callBack);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/didichuxing/doraemonkit/abridge/service/ABridgeService.java",
    "content": "package com.didichuxing.doraemonkit.abridge.service;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.IBinder;\nimport android.os.RemoteCallbackList;\nimport android.os.RemoteException;\nimport android.util.Log;\n\n\nimport com.didichuxing.doraemonkit.aidl.IReceiverAidlInterface;\nimport com.didichuxing.doraemonkit.aidl.ISenderAidlInterface;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class ABridgeService extends Service {\n\n    private static final String TAG = \"ICallService\";\n    private List<Client> mClients = new ArrayList<>();\n    private RemoteCallbackList<IReceiverAidlInterface> mCallbacks = new RemoteCallbackList<>();\n\n    public ABridgeService() {\n        Log.e(TAG, \"launched\");\n    }\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        Log.e(TAG, \"onStartCommand\");\n        return super.onStartCommand(intent, flags, startId);\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        Log.e(TAG, \"onBind\");\n        return mBinder;\n    }\n\n    @Override\n    public void onDestroy() {\n        Log.e(TAG, \"onDestroy\");\n        super.onDestroy();\n        mCallbacks.kill();\n    }\n\n    private final ISenderAidlInterface.Stub mBinder = new ISenderAidlInterface.Stub() {\n        @Override\n        public void join(IBinder token) throws RemoteException {\n            int idx = findClient(token);\n            if (idx >= 0) {\n                Log.d(TAG, token + \" already joined , client size \" + mClients.size());\n                return;\n            }\n            Client client = new Client(token);\n            // 注册客户端死掉的通知\n            token.linkToDeath(client, 0);\n            mClients.add(client);\n            Log.d(TAG, token + \" join , client size \" + mClients.size());\n        }\n\n        @Override\n        public void leave(IBinder token) throws RemoteException {\n            int idx = findClient(token);\n            if (idx < 0) {\n                Log.d(TAG, token + \" already left , client size \" + mClients.size());\n                return;\n            }\n            Client client = mClients.get(idx);\n            mClients.remove(client);\n            // 取消注册\n            client.mToken.unlinkToDeath(client, 0);\n            Log.d(TAG, token + \" left , client size \" + mClients.size());\n\n            if (mClients.size() == 0) {\n                stopSelf();//没有客户端就停止自己\n            }\n        }\n\n        @Override\n        public void sendMessage(String message) throws RemoteException {\n            Log.d(TAG, \" sendMessage :\" + message);\n            onSuccessCallBack(message);\n        }\n\n        @Override\n        public void registerCallback(IReceiverAidlInterface cb) throws RemoteException {\n            Log.d(TAG, \"registerCallback \" + cb);\n            mCallbacks.register(cb);\n        }\n\n        @Override\n        public void unregisterCallback(IReceiverAidlInterface cb) throws RemoteException {\n            Log.d(TAG, \"unregisterCallback \" + cb);\n            mCallbacks.unregister(cb);\n        }\n    };\n\n    private int findClient(IBinder token) {\n        for (int i = 0; i < mClients.size(); i++) {\n            if (mClients.get(i).mToken == token) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    private void onSuccessCallBack(String message) {\n        final int len = mCallbacks.beginBroadcast();\n        for (int i = 0; i < len; i++) {\n            try {\n                // 通知回调\n                mCallbacks.getBroadcastItem(i).receiveMessage(message);\n            } catch (RemoteException e) {\n                e.printStackTrace();\n            }\n        }\n        mCallbacks.finishBroadcast();\n    }\n\n    private final class Client implements IBinder.DeathRecipient {\n        private final IBinder mToken;\n\n        private Client(IBinder token) {\n            mToken = token;\n        }\n\n        @Override\n        public void binderDied() {\n            // 客户端死掉，执行此回调\n            int index = mClients.indexOf(this);\n            if (index < 0) {\n                return;\n            }\n\n            Log.d(TAG, \"client died\" );\n            mClients.remove(this);\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/didichuxing/doraemonkit/abridge/service/MessengerService.java",
    "content": "package com.didichuxing.doraemonkit.abridge.service;\n\nimport android.annotation.SuppressLint;\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.Message;\nimport android.os.Messenger;\nimport android.os.RemoteException;\nimport android.text.TextUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class MessengerService extends Service {\n    public final static String TAG = \"MessengerService\";\n\n    private List<Messenger> messengers = new ArrayList<>();\n\n    @SuppressLint(\"HandlerLeak\")\n    private Messenger messenger = new Messenger(new Handler() {\n        @Override\n        public void handleMessage(Message msg) {\n            super.handleMessage(msg);\n            if (msg.arg1 == 0x0000c1) {//退出接受\n                String str = (String) msg.getData().get(\"MessengerService\");\n                if (!TextUtils.isEmpty(str) && str.equalsIgnoreCase(\"unregisterCallback\")) {\n                    messengers.remove(msg.replyTo);\n                    return;\n                }\n            }\n            if (!messengers.contains(msg.replyTo)) {\n                messengers.add(msg.replyTo);\n            }\n            try {\n                for (Messenger reply : messengers) {\n                    reply.send(msg);\n                }\n            } catch (RemoteException e) {\n                e.printStackTrace();\n            }\n        }\n    });\n\n    public MessengerService() {\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return messenger.getBinder();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/didichuxing/doraemonkit/kit/leakcanary/LeakCanaryKit.java",
    "content": "package com.didichuxing.doraemonkit.kit.leakcanary;\n\nimport android.content.Context;\nimport android.content.Intent;\n\nimport com.didichuxing.doraemonkit.kit.AbstractKit;\nimport com.didichuxing.doraemonkit.kit.Category;\nimport com.squareup.leakcanary.R;\nimport com.squareup.leakcanary.internal.DisplayLeakActivity;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-24-17:05\n * 描    述：内存泄漏leakCanary功能入口\n * 修订历史：\n * ================================================\n */\npublic class LeakCanaryKit extends AbstractKit {\n\n    @Override\n    public int getName() {\n        return R.string.dk_frameinfo_leakcanary;\n    }\n\n    @Override\n    public int getIcon() {\n        return R.mipmap.leak_canary_icon;\n    }\n\n    @Override\n    public void onClick(Context context) {\n        Intent intent = new Intent(context, DisplayLeakActivity.class);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        context.startActivity(intent);\n    }\n\n    @Override\n    public void onAppInit(Context context) {\n\n    }\n\n    @Override\n    public boolean isInnerKit() {\n        return true;\n    }\n\n    @Override\n    public String innerKitId() {\n        return \"dokit_sdk_performance_ck_leak\";\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/haha/perflib/HahaSpy.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.haha.perflib;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\npublic final class HahaSpy {\n\n  public static @NonNull Instance allocatingThread(@NonNull Instance instance) {\n    Snapshot snapshot = instance.mHeap.mSnapshot;\n    int threadSerialNumber;\n    if (instance instanceof RootObj) {\n      threadSerialNumber = ((RootObj) instance).mThread;\n    } else {\n      threadSerialNumber = instance.mStack.mThreadSerialNumber;\n    }\n    ThreadObj thread = snapshot.getThread(threadSerialNumber);\n    return snapshot.findInstance(thread.mId);\n  }\n\n  /**\n   * Returns the GC Roots for all heaps in the Snapshot. Unfortunately,\n   * {@link Snapshot#getGCRoots()} only returns the GC Roots of the first heap.\n   */\n  public static Set<RootObj> allGcRoots(Snapshot snapshot) {\n    Set<RootObj> allRoots = new HashSet<>();\n    for (Heap heap : snapshot.getHeaps()) {\n      allRoots.addAll(heap.mRoots);\n    }\n    return allRoots;\n  }\n\n  private HahaSpy() {\n    throw new AssertionError();\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/AbstractAnalysisResultService.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport android.content.Context;\nimport android.content.Intent;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.content.ContextCompat;\n\nimport com.squareup.leakcanary.internal.ForegroundService;\n\nimport java.io.File;\n\npublic abstract class AbstractAnalysisResultService extends ForegroundService {\n\n    private static final String ANALYZED_HEAP_PATH_EXTRA = \"analyzed_heap_path_extra\";\n\n    public static void sendResultToListener(@NonNull Context context,\n                                            @NonNull String listenerServiceClassName,\n                                            @NonNull HeapDump heapDump,\n                                            @NonNull AnalysisResult result) {\n        Class<?> listenerServiceClass;\n        try {\n            listenerServiceClass = Class.forName(listenerServiceClassName);\n        } catch (ClassNotFoundException e) {\n            throw new RuntimeException(e);\n        }\n        Intent intent = new Intent(context, listenerServiceClass);\n        /**\n         * 将分析结果保存在指定的文件中 并启动 DisplayLeakService\n         */\n        File analyzedHeapFile = AnalyzedHeap.save(heapDump, result);\n        if (analyzedHeapFile != null) {\n            intent.putExtra(ANALYZED_HEAP_PATH_EXTRA, analyzedHeapFile.getAbsolutePath());\n        }\n        ContextCompat.startForegroundService(context, intent);\n    }\n\n    public AbstractAnalysisResultService() {\n        super(AbstractAnalysisResultService.class.getName(),\n                R.string.leak_canary_notification_reporting);\n    }\n\n    @Override\n    protected final void onHandleIntentInForeground(@Nullable Intent intent) {\n        if (intent == null) {\n            CanaryLog.d(\"AbstractAnalysisResultService received a null intent, ignoring.\");\n            return;\n        }\n        if (!intent.hasExtra(ANALYZED_HEAP_PATH_EXTRA)) {\n            onAnalysisResultFailure(getString(R.string.leak_canary_result_failure_no_disk_space));\n            return;\n        }\n        File analyzedHeapFile = new File(intent.getStringExtra(ANALYZED_HEAP_PATH_EXTRA));\n        AnalyzedHeap analyzedHeap = AnalyzedHeap.load(analyzedHeapFile);\n        if (analyzedHeap == null) {\n            onAnalysisResultFailure(getString(R.string.leak_canary_result_failure_no_file));\n            return;\n        }\n        try {\n            onHeapAnalyzed(analyzedHeap);\n        } finally {\n            //noinspection ResultOfMethodCallIgnored\n            analyzedHeap.heapDump.heapDumpFile.delete();\n            //noinspection ResultOfMethodCallIgnored\n            analyzedHeap.selfFile.delete();\n        }\n    }\n\n    /**\n     * Called after a heap dump is analyzed, whether or not a leak was found.\n     * In {@link AnalyzedHeap#result} check {@link AnalysisResult#leakFound} and {@link\n     * AnalysisResult#excludedLeak} to see if there was a leak and if it can be ignored.\n     * <p>\n     * This will be called from a background intent service thread.\n     * <p>\n     * It's OK to block here and wait for the heap dump to be uploaded.\n     * <p>\n     * The analyzed heap file and heap dump file will be deleted immediately after this callback\n     * returns.\n     */\n    protected void onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) {\n        onHeapAnalyzed(analyzedHeap.heapDump, analyzedHeap.result);\n    }\n\n    /**\n     * @deprecated Maintained for backward compatibility. You should override {@link\n     * #onHeapAnalyzed(AnalyzedHeap)} instead.\n     */\n    @SuppressWarnings(\"DeprecatedIsStillUsed\")\n    @Deprecated\n    protected void onHeapAnalyzed(@NonNull HeapDump heapDump, @NonNull AnalysisResult result) {\n    }\n\n    /**\n     * Called when there was an error saving or loading the analysis result. This will be called from\n     * a background intent service thread.\n     */\n    protected void onAnalysisResultFailure(String failureMessage) {\n        CanaryLog.d(failureMessage);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/ActivityRefWatcher.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.content.Context;\n\nimport androidx.annotation.NonNull;\n\nimport com.squareup.leakcanary.internal.ActivityLifecycleCallbacksAdapter;\n\n/**\n * @deprecated This was initially part of the LeakCanary API, but should not be any more.\n * {@link AndroidRefWatcherBuilder#watchActivities} should be used instead.\n * We will make this class internal in the next major version.\n */\n@SuppressWarnings(\"DeprecatedIsStillUsed\")\n@Deprecated\npublic final class ActivityRefWatcher {\n    private static final String TAG = \"ActivityRefWatcher\";\n\n    public static void installOnIcsPlus(@NonNull Application application,\n                                        @NonNull RefWatcher refWatcher) {\n        install(application, refWatcher);\n    }\n\n    public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {\n        Application application = (Application) context.getApplicationContext();\n        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);\n\n        application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);\n    }\n\n    private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =\n            new ActivityLifecycleCallbacksAdapter() {\n                @Override\n                public void onActivityDestroyed(Activity activity) {\n                    //当activity被关闭时 进行内存泄漏查找\n                    refWatcher.watch(activity);\n                }\n            };\n\n\n\n    private final Application application;\n    private final RefWatcher refWatcher;\n\n    private ActivityRefWatcher(Application application, RefWatcher refWatcher) {\n        this.application = application;\n        this.refWatcher = refWatcher;\n    }\n\n    public void watchActivities() {\n        // Make sure you don't get installed twice.\n        stopWatchingActivities();\n        application.registerActivityLifecycleCallbacks(lifecycleCallbacks);\n    }\n\n    public void stopWatchingActivities() {\n        application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/AnalysisResult.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.io.Serializable;\n\npublic final class AnalysisResult implements Serializable {\n\n  public static final long RETAINED_HEAP_SKIPPED = -1;\n\n  public static @NonNull\n  AnalysisResult noLeak(String className, long analysisDurationMs) {\n    return new AnalysisResult(false, false, className, null, null, 0, analysisDurationMs);\n  }\n\n  public static @NonNull\n  AnalysisResult leakDetected(boolean excludedLeak,\n                              @NonNull String className,\n                              @NonNull LeakTrace leakTrace, long retainedHeapSize, long analysisDurationMs) {\n    return new AnalysisResult(true, excludedLeak, className, leakTrace, null, retainedHeapSize,\n        analysisDurationMs);\n  }\n\n  public static @NonNull\n  AnalysisResult failure(@NonNull Throwable failure,\n                         long analysisDurationMs) {\n    return new AnalysisResult(false, false, null, null, failure, 0, analysisDurationMs);\n  }\n\n  /** True if a leak was found in the heap dump. */\n  public final boolean leakFound;\n\n  /**\n   * True if {@link #leakFound} is true and the only path to the leaking reference is\n   * through excluded references. Usually, that means you can safely ignore this report.\n   */\n  public final boolean excludedLeak;\n\n  /**\n   * Class name of the object that leaked, null if {@link #failure} is not null.\n   * The class name format is the same as what would be returned by {@link Class#getName()}.\n   */\n  @Nullable public final String className;\n\n  /**\n   * Shortest path to GC roots for the leaking object if {@link #leakFound} is true, null\n   * otherwise. This can be used as a unique signature for the leak.\n   */\n  @Nullable public final LeakTrace leakTrace;\n\n  /** Null unless the analysis failed. */\n  @Nullable public final Throwable failure;\n\n  /**\n   * The number of bytes which would be freed if all references to the leaking object were\n   * released. {@link #RETAINED_HEAP_SKIPPED} if the retained heap size was not computed. 0 if\n   * {@link #leakFound} is false.\n   */\n  public final long retainedHeapSize;\n\n  /** Total time spent analyzing the heap. */\n  public final long analysisDurationMs;\n\n  /**\n   * <p>Creates a new {@link RuntimeException} with a fake stack trace that maps the leak trace.\n   *\n   * <p>Leak traces uniquely identify memory leaks, much like stack traces uniquely identify\n   * exceptions.\n   *\n   * <p>This method enables you to upload leak traces as stack traces to your preferred\n   * exception reporting tool and benefit from the grouping and counting these tools provide out\n   * of the box. This also means you can track all leaks instead of relying on individuals\n   * reporting them when they happen.\n   *\n   * <p>The following example leak trace:\n   * <pre>\n   * * com.foo.WibbleActivity has leaked:\n   * * GC ROOT static com.foo.Bar.qux\n   * * references com.foo.Quz.context\n   * * leaks com.foo.WibbleActivity instance\n   * </pre>\n   *\n   * <p>Will turn into an exception with the following stacktrace:\n   * <pre>\n   * java.lang.RuntimeException: com.foo.WibbleActivity leak from com.foo.Bar (holder=CLASS,\n   * type=STATIC_FIELD)\n   *         at com.foo.Bar.qux(Bar.java:42)\n   *         at com.foo.Quz.context(Quz.java:42)\n   *         at com.foo.WibbleActivity.leaking(WibbleActivity.java:42)\n   * </pre>\n   */\n  public @NonNull RuntimeException leakTraceAsFakeException() {\n    if (!leakFound) {\n      throw new UnsupportedOperationException(\n          \"leakTraceAsFakeException() can only be called when leakFound is true\");\n    }\n    LeakTraceElement firstElement = leakTrace.elements.get(0);\n    String rootSimpleName = classSimpleName(firstElement.className);\n    String leakSimpleName = classSimpleName(className);\n\n    String exceptionMessage = leakSimpleName\n        + \" leak from \"\n        + rootSimpleName\n        + \" (holder=\"\n        + firstElement.holder\n        + \", type=\"\n        + firstElement.type\n        + \")\";\n    RuntimeException exception = new RuntimeException(exceptionMessage);\n\n    StackTraceElement[] stackTrace = new StackTraceElement[leakTrace.elements.size()];\n    int i = 0;\n    for (LeakTraceElement element : leakTrace.elements) {\n      String methodName = element.referenceName != null ? element.referenceName : \"leaking\";\n      String file = classSimpleName(element.className) + \".java\";\n      stackTrace[i] = new StackTraceElement(element.className, methodName, file, 42);\n      i++;\n    }\n    exception.setStackTrace(stackTrace);\n    return exception;\n  }\n\n  private AnalysisResult(boolean leakFound, boolean excludedLeak, String className,\n                         LeakTrace leakTrace, Throwable failure, long retainedHeapSize, long analysisDurationMs) {\n    this.leakFound = leakFound;\n    this.excludedLeak = excludedLeak;\n    this.className = className;\n    this.leakTrace = leakTrace;\n    this.failure = failure;\n    this.retainedHeapSize = retainedHeapSize;\n    this.analysisDurationMs = analysisDurationMs;\n  }\n\n  private String classSimpleName(String className) {\n    int separator = className.lastIndexOf('.');\n    return separator == -1 ? className : className.substring(separator + 1);\n  }\n}"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/AnalyzedHeap.java",
    "content": "package com.squareup.leakcanary;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\n\n/**\n * Wraps a {@link HeapDump} and corresponding {@link AnalysisResult}.\n */\npublic final class AnalyzedHeap {\n\n  @Nullable public static File save(@NonNull HeapDump heapDump, @NonNull AnalysisResult result) {\n    File analyzedHeapfile = new File(heapDump.heapDumpFile.getParentFile(),\n        heapDump.heapDumpFile.getName() + \".result\");\n    FileOutputStream fos = null;\n    try {\n      fos = new FileOutputStream(analyzedHeapfile);\n      ObjectOutputStream oos = new ObjectOutputStream(fos);\n      oos.writeObject(heapDump);\n      oos.writeObject(result);\n      return analyzedHeapfile;\n    } catch (IOException e) {\n      CanaryLog.d(e, \"Could not save leak analysis result to disk.\");\n    } finally {\n      if (fos != null) {\n        try {\n          fos.close();\n        } catch (IOException ignored) {\n        }\n      }\n    }\n    return null;\n  }\n\n  @Nullable public static AnalyzedHeap load(@NonNull File resultFile) {\n    FileInputStream fis = null;\n    try {\n      fis = new FileInputStream(resultFile);\n      ObjectInputStream ois = new ObjectInputStream(fis);\n      HeapDump heapDump = (HeapDump) ois.readObject();\n      AnalysisResult result = (AnalysisResult) ois.readObject();\n      return new AnalyzedHeap(heapDump, result, resultFile);\n    } catch (IOException | ClassNotFoundException e) {\n      // Likely a change in the serializable result class.\n      // Let's remove the files, we can't read them anymore.\n      boolean deleted = resultFile.delete();\n      if (deleted) {\n        CanaryLog.d(e, \"Could not read result file %s, deleted it.\", resultFile);\n      } else {\n        CanaryLog.d(e, \"Could not read result file %s, could not delete it either.\",\n            resultFile);\n      }\n    } finally {\n      if (fis != null) {\n        try {\n          fis.close();\n        } catch (IOException ignored) {\n        }\n      }\n    }\n    return null;\n  }\n\n  @NonNull public final HeapDump heapDump;\n  @NonNull public final AnalysisResult result;\n  @NonNull public final File selfFile;\n  public final boolean heapDumpFileExists;\n  public final long selfLastModified;\n\n  public AnalyzedHeap(@NonNull HeapDump heapDump, @NonNull AnalysisResult result,\n                      @NonNull File analyzedHeapFile) {\n    this.heapDump = heapDump;\n    this.result = result;\n    this.selfFile = analyzedHeapFile;\n    heapDumpFileExists = heapDump.heapDumpFile.exists();\n    selfLastModified = analyzedHeapFile.lastModified();\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/AnalyzerProgressListener.java",
    "content": "package com.squareup.leakcanary;\n\n\nimport androidx.annotation.NonNull;\n\npublic interface AnalyzerProgressListener {\n\n  @NonNull\n  AnalyzerProgressListener NONE = new AnalyzerProgressListener() {\n    @Override public void onProgressUpdate(@NonNull Step step) {\n    }\n  };\n\n  // These steps should be defined in the order in which they occur.\n  enum Step {\n    READING_HEAP_DUMP_FILE,\n    PARSING_HEAP_DUMP,\n    DEDUPLICATING_GC_ROOTS,\n    FINDING_LEAKING_REF,\n    FINDING_SHORTEST_PATH,\n    BUILDING_LEAK_TRACE,\n    COMPUTING_DOMINATORS,\n    COMPUTING_BITMAP_SIZE,\n  }\n\n  void onProgressUpdate(@NonNull Step step);\n}"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/AndroidDebuggerControl.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport android.os.Debug;\n\npublic final class AndroidDebuggerControl implements DebuggerControl {\n  @Override\n  public boolean isDebuggerAttached() {\n    return Debug.isDebuggerConnected();\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/AndroidExcludedRefs.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport androidx.annotation.NonNull;\n\nimport com.squareup.leakcanary.internal.LeakCanaryInternals;\n\nimport java.lang.ref.PhantomReference;\nimport java.lang.ref.SoftReference;\nimport java.lang.ref.WeakReference;\nimport java.util.EnumSet;\n\nimport static android.os.Build.MANUFACTURER;\nimport static android.os.Build.VERSION.SDK_INT;\nimport static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;\nimport static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1;\nimport static android.os.Build.VERSION_CODES.JELLY_BEAN;\nimport static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;\nimport static android.os.Build.VERSION_CODES.KITKAT;\nimport static android.os.Build.VERSION_CODES.LOLLIPOP;\nimport static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;\nimport static android.os.Build.VERSION_CODES.M;\nimport static android.os.Build.VERSION_CODES.N;\nimport static android.os.Build.VERSION_CODES.N_MR1;\nimport static android.os.Build.VERSION_CODES.O;\nimport static android.os.Build.VERSION_CODES.O_MR1;\n\n/**\n * This class is a work in progress. You can help by reporting leak traces that seem to be caused\n * by the Android SDK, here: https://github.com/square/leakcanary/issues/new\n *\n * We filter on SDK versions and Manufacturers because many of those leaks are specific to a given\n * manufacturer implementation, they usually share their builds across multiple models, and the\n * leaks eventually get fixed in newer versions.\n *\n * Most app developers should use {@link #createAppDefaults()}. However, you can also pick the\n * leaks you want to ignore by creating an {@link EnumSet} that matches your needs and calling\n * {@link #createBuilder(EnumSet)}\n */\n@SuppressWarnings({ \"unused\", \"WeakerAccess\" }) // Public API.\npublic enum AndroidExcludedRefs {\n\n  // ######## Android SDK Excluded refs ########\n\n  ACTIVITY_CLIENT_RECORD__NEXT_IDLE(SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.app.ActivityThread$ActivityClientRecord\", \"nextIdle\")\n          .reason(\"Android AOSP sometimes keeps a reference to a destroyed activity as a\"\n              + \" nextIdle client record in the android.app.ActivityThread.mActivities map.\"\n              + \" Not sure what's going on there, input welcome.\");\n    }\n  },\n\n  SPAN_CONTROLLER(SDK_INT <= KITKAT) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      String reason =\n          \"Editor inserts a special span, which has a reference to the EditText. That span is a\"\n              + \" NoCopySpan, which makes sure it gets dropped when creating a new\"\n              + \" SpannableStringBuilder from a given CharSequence.\"\n              + \" TextView.onSaveInstanceState() does a copy of its mText before saving it in the\"\n              + \" bundle. Prior to KitKat, that copy was done using the SpannableString\"\n              + \" constructor, instead of SpannableStringBuilder. The SpannableString constructor\"\n              + \" does not drop NoCopySpan spans. So we end up with a saved state that holds a\"\n              + \" reference to the textview and therefore the entire view hierarchy & activity\"\n              + \" context. Fix: https://github.com/android/platform_frameworks_base/commit\"\n              + \"/af7dcdf35a37d7a7dbaad7d9869c1c91bce2272b .\"\n              + \" To fix this, you could override TextView.onSaveInstanceState(), and then use\"\n              + \" reflection to access TextView.SavedState.mText and clear the NoCopySpan spans.\";\n      excluded.instanceField(\"android.widget.Editor$EasyEditSpanController\", \"this$0\")\n          .reason(reason);\n      excluded.instanceField(\"android.widget.Editor$SpanController\", \"this$0\").reason(reason);\n    }\n  },\n\n  MEDIA_SESSION_LEGACY_HELPER__SINSTANCE(SDK_INT == LOLLIPOP) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.staticField(\"android.media.session.MediaSessionLegacyHelper\", \"sInstance\")\n          .reason(\"MediaSessionLegacyHelper is a static singleton that is lazily instantiated and\"\n              + \" keeps a reference to the context it's given the first time\"\n              + \" MediaSessionLegacyHelper.getHelper() is called.\"\n              + \" This leak was introduced in android-5.0.1_r1 and fixed in Android 5.1.0_r1 by\"\n              + \" calling context.getApplicationContext().\"\n              + \" Fix: https://github.com/android/platform_frameworks_base/commit\"\n              + \"/9b5257c9c99c4cb541d8e8e78fb04f008b1a9091\"\n              + \" To fix this, you could call MediaSessionLegacyHelper.getHelper() early\"\n              + \" in Application.onCreate() and pass it the application context.\");\n    }\n  },\n\n  TEXT_LINE__SCACHED(SDK_INT <= LOLLIPOP_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.staticField(\"android.text.TextLine\", \"sCached\")\n          .reason(\"TextLine.sCached is a pool of 3 TextLine instances. TextLine.recycle() has had\"\n              + \" at least two bugs that created memory leaks by not correctly clearing the\"\n              + \" recycled TextLine instances. The first was fixed in android-5.1.0_r1:\"\n              + \" https://github.com/android/platform_frameworks_base/commit\"\n              + \"/893d6fe48d37f71e683f722457bea646994a10\"\n              + \" The second was fixed, not released yet:\"\n              + \" https://github.com/android/platform_frameworks_base/commit\"\n              + \"/b3a9bc038d3a218b1dbdf7b5668e3d6c12be5e\"\n              + \" To fix this, you could access TextLine.sCached and clear the pool every now\"\n              + \" and then (e.g. on activity destroy).\");\n    }\n  },\n\n  BLOCKING_QUEUE() {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      String reason = \"A thread waiting on a blocking queue will leak the last\"\n          + \" dequeued object as a stack local reference. So when a HandlerThread becomes idle, it\"\n          + \" keeps a local reference to the last message it received. That message then gets\"\n          + \" recycled and can be used again. As long as all messages are recycled after being\"\n          + \" used, this won't be a problem, because these references are cleared when being\"\n          + \" recycled. However, dialogs create template Message instances to be copied when a\"\n          + \" message needs to be sent. These Message templates holds references to the dialog\"\n          + \" listeners, which most likely leads to holding a reference onto the activity in some\"\n          + \" way. Dialogs never recycle their template Message, assuming these Message instances\"\n          + \" will get GCed when the dialog is GCed.\"\n          + \" The combination of these two things creates a high potential for memory leaks as soon\"\n          + \" as you use dialogs. These memory leaks might be temporary, but some handler threads\"\n          + \" sleep for a long time.\"\n          + \" To fix this, you could post empty messages to the idle handler threads from time to\"\n          + \" time. This won't be easy because you cannot access all handler threads, but a library\"\n          + \" that is widely used should consider doing this for its own handler threads. This leaks\"\n          + \" has been shown to happen in both Dalvik and ART.\";\n      excluded.instanceField(\"android.os.Message\", \"obj\").reason(reason);\n      excluded.instanceField(\"android.os.Message\", \"next\").reason(reason);\n      excluded.instanceField(\"android.os.Message\", \"target\").reason(reason);\n    }\n  },\n\n  INPUT_METHOD_MANAGER__SERVED_VIEW(SDK_INT >= ICE_CREAM_SANDWICH_MR1 && SDK_INT <= O_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      String reason = \"When we detach a view that receives keyboard input, the InputMethodManager\"\n          + \" leaks a reference to it until a new view asks for keyboard input.\"\n          + \" Tracked here: https://code.google.com/p/android/issues/detail?id=171190\"\n          + \" Hack: https://gist.github.com/pyricau/4df64341cc978a7de414\";\n      excluded.instanceField(\"android.view.inputmethod.InputMethodManager\", \"mNextServedView\")\n          .reason(reason);\n      excluded.instanceField(\"android.view.inputmethod.InputMethodManager\", \"mServedView\")\n          .reason(reason);\n      excluded.instanceField(\"android.view.inputmethod.InputMethodManager\",\n          \"mServedInputConnection\").reason(reason);\n    }\n  },\n\n  INPUT_METHOD_MANAGER__ROOT_VIEW(SDK_INT >= ICE_CREAM_SANDWICH_MR1 && SDK_INT <= O_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.view.inputmethod.InputMethodManager\", \"mCurRootView\")\n          .reason(\"The singleton InputMethodManager is holding a reference to mCurRootView long\"\n              + \" after the activity has been destroyed.\"\n              + \" Observed on ICS MR1: https://github.com/square/leakcanary/issues/1\"\n              + \"#issuecomment-100579429\"\n              + \" Hack: https://gist.github.com/pyricau/4df64341cc978a7de414\");\n    }\n  },\n\n  LAYOUT_TRANSITION(SDK_INT >= ICE_CREAM_SANDWICH && SDK_INT <= LOLLIPOP_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.animation.LayoutTransition$1\", \"val$parent\")\n          .reason(\"LayoutTransition leaks parent ViewGroup through\"\n              + \" ViewTreeObserver.OnPreDrawListener When triggered, this leaks stays until the\"\n              + \" window is destroyed. Tracked here:\"\n              + \" https://code.google.com/p/android/issues/detail?id=171830\");\n    }\n  },\n\n  SPELL_CHECKER_SESSION(SDK_INT >= JELLY_BEAN && SDK_INT <= N) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.view.textservice.SpellCheckerSession$1\", \"this$0\")\n          .reason(\"SpellCheckerSessionListenerImpl.mHandler is leaking destroyed Activity when the\"\n              + \" SpellCheckerSession is closed before the service is connected.\"\n              + \" Tracked here: https://code.google.com/p/android/issues/detail?id=172542\");\n    }\n  },\n\n  SPELL_CHECKER(SDK_INT == LOLLIPOP_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.widget.SpellChecker$1\", \"this$0\")\n          .reason(\"SpellChecker holds on to a detached view that points to a destroyed activity.\"\n              + \" mSpellRunnable is being enqueued, and that callback should be removed when \"\n              + \" closeSession() is called. Maybe closeSession() wasn't called, or maybe it was \"\n              + \" called after the view was detached.\");\n    }\n  },\n\n  ACTIVITY_CHOOSE_MODEL(SDK_INT > ICE_CREAM_SANDWICH && SDK_INT <= LOLLIPOP_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      String reason = \"ActivityChooserModel holds a static reference to the last set\"\n          + \" ActivityChooserModelPolicy which can be an activity context.\"\n          + \" Tracked here: https://code.google.com/p/android/issues/detail?id=172659\"\n          + \" Hack: https://gist.github.com/andaag/b05ab66ed0f06167d6e0\";\n      excluded.instanceField(\"androidx.appcompat.widget.ActivityChooserModel\",\n          \"mActivityChoserModelPolicy\").reason(reason);\n      excluded.instanceField(\"android.widget.ActivityChooserModel\", \"mActivityChoserModelPolicy\")\n          .reason(reason);\n    }\n  },\n\n  SPEECH_RECOGNIZER(SDK_INT < LOLLIPOP) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.speech.SpeechRecognizer$InternalListener\", \"this$0\")\n          .reason(\"Prior to Android 5, SpeechRecognizer.InternalListener was a non static inner\"\n              + \" class and leaked the SpeechRecognizer which leaked an activity context.\"\n              + \" Fixed in AOSP: https://github.com/android/platform_frameworks_base/commit\"\n              + \" /b37866db469e81aca534ff6186bdafd44352329b\");\n    }\n  },\n\n  ACCOUNT_MANAGER(SDK_INT <= O_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.accounts.AccountManager$AmsTask$Response\", \"this$1\")\n          .reason(\"AccountManager$AmsTask$Response is a stub and is held in memory by native code,\"\n              + \" probably because the reference to the response in the other process hasn't been\"\n              + \" cleared.\"\n              + \" AccountManager$AmsTask is holding on to the activity reference to use for\"\n              + \" launching a new sub- Activity.\"\n              + \" Tracked here: https://code.google.com/p/android/issues/detail?id=173689\"\n              + \" Fix: Pass a null activity reference to the AccountManager methods and then deal\"\n              + \" with the returned future to to get the result and correctly start an activity\"\n              + \" when it's available.\");\n    }\n  },\n\n  MEDIA_SCANNER_CONNECTION(SDK_INT <= LOLLIPOP_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.media.MediaScannerConnection\", \"mContext\")\n          .reason(\"The static method MediaScannerConnection.scanFile() takes an activity context\"\n              + \" but the service might not disconnect after the activity has been destroyed.\"\n              + \" Tracked here: https://code.google.com/p/android/issues/detail?id=173788\"\n              + \" Fix: Create an instance of MediaScannerConnection yourself and pass in the\"\n              + \" application context. Call connect() and disconnect() manually.\");\n    }\n  },\n\n  USER_MANAGER__SINSTANCE(SDK_INT >= JELLY_BEAN_MR2 && SDK_INT < O) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.os.UserManager\", \"mContext\")\n          .reason(\"UserManager has a static sInstance field that creates an instance and caches it\"\n              + \" the first time UserManager.get() is called. This instance is created with the\"\n              + \" outer context (which is an activity base context).\"\n              + \" Tracked here: https://code.google.com/p/android/issues/detail?id=173789\"\n              + \" Introduced by: https://github.com/android/platform_frameworks_base/commit\"\n              + \"/27db46850b708070452c0ce49daf5f79503fbde6\"\n              + \" Fix: trigger a call to UserManager.get() in Application.onCreate(), so that the\"\n              + \" UserManager instance gets cached with a reference to the application context.\");\n    }\n  },\n\n  APP_WIDGET_HOST_CALLBACKS(SDK_INT < LOLLIPOP_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.appwidget.AppWidgetHost$Callbacks\", \"this$0\")\n          .reason(\"android.appwidget.AppWidgetHost$Callbacks is a stub and is held in memory native\"\n              + \" code. The reference to the `mContext` was not being cleared, which caused the\"\n              + \" Callbacks instance to retain this reference\"\n              + \" Fixed in AOSP: https://github.com/android/platform_frameworks_base/commit\"\n              + \"/7a96f3c917e0001ee739b65da37b2fadec7d7765\");\n    }\n  },\n\n  AUDIO_MANAGER(SDK_INT <= LOLLIPOP_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.media.AudioManager$1\", \"this$0\")\n          .reason(\"Prior to Android M, VideoView required audio focus from AudioManager and\"\n              + \" never abandoned it, which leaks the Activity context through the AudioManager.\"\n              + \" The root of the problem is that AudioManager uses whichever\"\n              + \" context it receives, which in the case of the VideoView example is an Activity,\"\n              + \" even though it only needs the application's context. The issue is fixed in\"\n              + \" Android M, and the AudioManager now uses the application's context.\"\n              + \" Tracked here: https://code.google.com/p/android/issues/detail?id=152173\"\n              + \" Fix: https://gist.github.com/jankovd/891d96f476f7a9ce24e2\");\n    }\n  },\n\n  EDITTEXT_BLINK_MESSAGEQUEUE(SDK_INT <= LOLLIPOP_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.widget.Editor$Blink\", \"this$0\")\n          .reason(\"The EditText Blink of the Cursor is implemented using a callback and Messages,\"\n              + \" which trigger the display of the Cursor. If an AlertDialog or DialogFragment that\"\n              + \" contains a blinking cursor is detached, a message is posted with a delay after the\"\n              + \" dialog has been closed and as a result leaks the Activity.\"\n              + \" This can be fixed manually by calling TextView.setCursorVisible(false) in the\"\n              + \" dismiss() method of the dialog.\"\n              + \" Tracked here: https://code.google.com/p/android/issues/detail?id=188551\"\n              + \" Fixed in AOSP: https://android.googlesource.com/platform/frameworks/base/+\"\n              + \"/5b734f2430e9f26c769d6af8ea5645e390fcf5af%5E%21/\");\n    }\n  },\n\n  CONNECTIVITY_MANAGER__SINSTANCE(SDK_INT <= M) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.net.ConnectivityManager\", \"sInstance\")\n          .reason(\"ConnectivityManager has a sInstance field that is set when the first\"\n              + \" ConnectivityManager instance is created. ConnectivityManager has a mContext field.\"\n              + \" When calling activity.getSystemService(Context.CONNECTIVITY_SERVICE) , the first\"\n              + \" ConnectivityManager instance is created with the activity context and stored in\"\n              + \" sInstance. That activity context then leaks forever.\"\n              + \" Until this is fixed, app developers can prevent this leak by making sure the\"\n              + \" ConnectivityManager is first created with an App Context. E.g. in some static\"\n              + \" init do: context.getApplicationContext()\"\n              + \".getSystemService(Context.CONNECTIVITY_SERVICE)\"\n              + \" Tracked here: https://code.google.com/p/android/issues/detail?id=198852\"\n              + \" Introduced here: https://github.com/android/platform_frameworks_base/commit/\"\n              + \"e0bef71662d81caaaa0d7214fb0bef5d39996a69\");\n    }\n  },\n\n  ACCESSIBILITY_NODE_INFO__MORIGINALTEXT(SDK_INT >= O && SDK_INT <= O_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.view.accessibility.AccessibilityNodeInfo\", \"mOriginalText\")\n          .reason(\"AccessibilityNodeInfo has a static sPool of AccessibilityNodeInfo. When\"\n              + \" AccessibilityNodeInfo instances are released back in the pool,\"\n              + \" AccessibilityNodeInfo.clear() does not clear the mOriginalText field, which\"\n              + \" causes spans to leak which in turns causes TextView.ChangeWatcher to leak and the\"\n              + \" whole view hierarchy. Introduced here: https://android.googlesource.com/platform/\"\n              + \"frameworks/base/+/193520e3dff5248ddcf8435203bf99d2ba667219%5E%21/core/java/\"\n              + \"android/view/accessibility/AccessibilityNodeInfo.java\");\n    }\n  },\n\n  BACKDROP_FRAME_RENDERER__MDECORVIEW(SDK_INT >= N && SDK_INT <= O) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"com.android.internal.policy.BackdropFrameRenderer\", \"mDecorView\")\n          .reason(\"When BackdropFrameRenderer.releaseRenderer() is called, there's an unknown case\"\n              + \" where mRenderer becomes null but mChoreographer doesn't and the thread doesn't\"\n              + \" stop and ends up leaking mDecorView which itself holds on to a destroyed\"\n              + \" activity\");\n    }\n  },\n\n  // ######## Manufacturer specific Excluded refs ########\n\n  INSTRUMENTATION_RECOMMEND_ACTIVITY(\n      LeakCanaryInternals.MEIZU.equals(MANUFACTURER) && SDK_INT >= LOLLIPOP && SDK_INT <= LOLLIPOP_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.staticField(\"android.app.Instrumentation\", \"mRecommendActivity\")\n          .reason(\"Instrumentation would leak com.android.internal.app.RecommendActivity (in\"\n              + \" framework.jar) in Meizu FlymeOS 4.5 and above, which is based on Android 5.0 and \"\n              + \" above\");\n    }\n  },\n\n  DEVICE_POLICY_MANAGER__SETTINGS_OBSERVER(\n      LeakCanaryInternals.MOTOROLA.equals(MANUFACTURER) && SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      if (LeakCanaryInternals.MOTOROLA.equals(MANUFACTURER) && SDK_INT == KITKAT) {\n        excluded.instanceField(\"android.app.admin.DevicePolicyManager$SettingsObserver\", \"this$0\")\n            .reason(\"DevicePolicyManager keeps a reference to the context it has been created with\"\n                + \" instead of extracting the application context. In this Motorola build,\"\n                + \" DevicePolicyManager has an inner SettingsObserver class that is a content\"\n                + \" observer, which is held into memory by a binder transport object.\");\n      }\n    }\n  },\n\n  SPEN_GESTURE_MANAGER(LeakCanaryInternals.SAMSUNG.equals(MANUFACTURER) && SDK_INT == KITKAT) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.staticField(\"com.samsung.android.smartclip.SpenGestureManager\", \"mContext\")\n          .reason(\"SpenGestureManager has a static mContext field that leaks a reference to the\"\n              + \" activity. Yes, a STATIC mContext field.\");\n    }\n  },\n\n  GESTURE_BOOST_MANAGER(LeakCanaryInternals.HUAWEI.equals(MANUFACTURER) && SDK_INT >= N && SDK_INT <= N_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.staticField(\"android.gestureboost.GestureBoostManager\", \"mContext\")\n          .reason(\"GestureBoostManager is a static singleton that leaks an activity context.\"\n              + \" Fix: https://github.com/square/leakcanary/issues/696#issuecomment-296420756\");\n    }\n  },\n\n  INPUT_METHOD_MANAGER__LAST_SERVED_VIEW(\n      LeakCanaryInternals.HUAWEI.equals(MANUFACTURER) && SDK_INT >= M && SDK_INT <= O_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      String reason = \"HUAWEI added a mLastSrvView field to InputMethodManager\"\n          + \" that leaks a reference to the last served view.\";\n      excluded.instanceField(\"android.view.inputmethod.InputMethodManager\", \"mLastSrvView\")\n          .reason(reason);\n    }\n  },\n\n  CLIPBOARD_UI_MANAGER__SINSTANCE(\n      LeakCanaryInternals.SAMSUNG.equals(MANUFACTURER) && SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.sec.clipboard.ClipboardUIManager\", \"mContext\")\n          .reason(\"ClipboardUIManager is a static singleton that leaks an activity context.\"\n              + \" Fix: trigger a call to ClipboardUIManager.getInstance() in Application.onCreate()\"\n              + \" , so that the ClipboardUIManager instance gets cached with a reference to the\"\n              + \" application context. Example: https://gist.github.com/cypressious/\"\n              + \"91c4fb1455470d803a602838dfcd5774\");\n    }\n  },\n\n  SEM_CLIPBOARD_MANAGER__MCONTEXT(\n      LeakCanaryInternals.SAMSUNG.equals(MANUFACTURER) && SDK_INT >= KITKAT && SDK_INT <= N) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"com.samsung.android.content.clipboard.SemClipboardManager\",\n          \"mContext\")\n          .reason(\"SemClipboardManager is held in memory by an anonymous inner class\"\n              + \" implementation of android.os.Binder, thereby leaking an activity context.\");\n    }\n  },\n\n  SEM_EMERGENCY_MANAGER__MCONTEXT(\n      LeakCanaryInternals.SAMSUNG.equals(MANUFACTURER) && SDK_INT >= KITKAT && SDK_INT <= N) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"com.samsung.android.emergencymode.SemEmergencyManager\", \"mContext\")\n          .reason(\"SemEmergencyManager is a static singleton that leaks a DecorContext.\"\n              + \" Fix: https://gist.github.com/jankovd/a210460b814c04d500eb12025902d60d\");\n    }\n  },\n\n  BUBBLE_POPUP_HELPER__SHELPER(\n      LeakCanaryInternals.LG.equals(MANUFACTURER) && SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.staticField(\"android.widget.BubblePopupHelper\", \"sHelper\")\n          .reason(\"A static helper for EditText bubble popups leaks a reference to the latest\"\n              + \" focused view.\");\n    }\n  },\n\n  LGCONTEXT__MCONTEXT(LeakCanaryInternals.LG.equals(MANUFACTURER) && SDK_INT == LOLLIPOP) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"com.lge.systemservice.core.LGContext\", \"mContext\")\n          .reason(\"LGContext is a static singleton that leaks an activity context.\");\n    }\n  },\n\n  AW_RESOURCE__SRESOURCES(LeakCanaryInternals.SAMSUNG.equals(MANUFACTURER) && SDK_INT == KITKAT) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      // AwResource#setResources() is called with resources that hold a reference to the\n      // activity context (instead of the application context) and doesn't clear it.\n      // Not sure what's going on there, input welcome.\n      excluded.staticField(\"com.android.org.chromium.android_webview.AwResource\", \"sResources\");\n    }\n  },\n\n  MAPPER_CLIENT(LeakCanaryInternals.NVIDIA.equals(MANUFACTURER) && SDK_INT == KITKAT) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"com.nvidia.ControllerMapper.MapperClient$ServiceClient\", \"this$0\")\n          .reason(\"Not sure exactly what ControllerMapper is about, but there is an anonymous\"\n              + \" Handler in ControllerMapper.MapperClient.ServiceClient, which leaks\"\n              + \" ControllerMapper.MapperClient which leaks the activity context.\");\n    }\n  },\n\n  TEXT_VIEW__MLAST_HOVERED_VIEW(\n      LeakCanaryInternals.SAMSUNG.equals(MANUFACTURER) && SDK_INT >= KITKAT && SDK_INT <= O) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.staticField(\"android.widget.TextView\", \"mLastHoveredView\")\n          .reason(\"mLastHoveredView is a static field in TextView that leaks the last hovered\"\n              + \" view.\");\n    }\n  },\n\n  PERSONA_MANAGER(LeakCanaryInternals.SAMSUNG.equals(MANUFACTURER) && SDK_INT == KITKAT) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.os.PersonaManager\", \"mContext\")\n          .reason(\"android.app.LoadedApk.mResources has a reference to\"\n              + \" android.content.res.Resources.mPersonaManager which has a reference to\"\n              + \" android.os.PersonaManager.mContext which is an activity.\");\n    }\n  },\n\n  RESOURCES__MCONTEXT(LeakCanaryInternals.SAMSUNG.equals(MANUFACTURER) && SDK_INT == KITKAT) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.content.res.Resources\", \"mContext\")\n          .reason(\"In AOSP the Resources class does not have a context.\"\n              + \" Here we have ZygoteInit.mResources (static field) holding on to a Resources\"\n              + \" instance that has a context that is the activity.\"\n              + \" Observed here: https://github.com/square/leakcanary/issues/1#issue-74450184\");\n    }\n  },\n\n  VIEW_CONFIGURATION__MCONTEXT(LeakCanaryInternals.SAMSUNG.equals(MANUFACTURER) && SDK_INT == KITKAT) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.instanceField(\"android.view.ViewConfiguration\", \"mContext\")\n          .reason(\"In AOSP the ViewConfiguration class does not have a context.\"\n              + \" Here we have ViewConfiguration.sConfigurations (static field) holding on to a\"\n              + \" ViewConfiguration instance that has a context that is the activity.\"\n              + \" Observed here: https://github.com/square/leakcanary/issues\"\n              + \"/1#issuecomment-100324683\");\n    }\n  },\n\n  SYSTEM_SENSOR_MANAGER__MAPPCONTEXTIMPL((LeakCanaryInternals.LENOVO.equals(MANUFACTURER) && SDK_INT == KITKAT) //\n      || (LeakCanaryInternals.VIVO.equals(MANUFACTURER) && SDK_INT == LOLLIPOP_MR1)) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.staticField(\"android.hardware.SystemSensorManager\", \"mAppContextImpl\")\n          .reason(\"SystemSensorManager stores a reference to context\"\n              + \" in a static field in its constructor.\"\n              + \" Fix: use application context to get SensorManager\");\n    }\n  },\n\n  AUDIO_MANAGER__MCONTEXT_STATIC(LeakCanaryInternals.SAMSUNG.equals(MANUFACTURER) && SDK_INT == KITKAT) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.staticField(\"android.media.AudioManager\", \"mContext_static\")\n          .reason(\"Samsung added a static mContext_static field to AudioManager, holds a reference\"\n              + \" to the activity.\"\n              + \" Observed here: https://github.com/square/leakcanary/issues/32\");\n    }\n  },\n\n  ACTIVITY_MANAGER_MCONTEXT(LeakCanaryInternals.SAMSUNG.equals(MANUFACTURER) && SDK_INT == LOLLIPOP_MR1) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.staticField(\"android.app.ActivityManager\", \"mContext\")\n          .reason(\"Samsung added a static mContext field to ActivityManager, holds a reference\"\n              + \" to the activity.\"\n              + \" Observed here: https://github.com/square/leakcanary/issues/177 Fix in comment:\"\n              + \" https://github.com/square/leakcanary/issues/177#issuecomment-222724283\");\n    }\n  },\n\n  // ######## General Excluded refs ########\n\n  SOFT_REFERENCES {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.clazz(WeakReference.class.getName()).alwaysExclude();\n      excluded.clazz(SoftReference.class.getName()).alwaysExclude();\n      excluded.clazz(PhantomReference.class.getName()).alwaysExclude();\n      excluded.clazz(\"java.lang.ref.Finalizer\").alwaysExclude();\n      excluded.clazz(\"java.lang.ref.FinalizerReference\").alwaysExclude();\n    }\n  },\n\n  FINALIZER_WATCHDOG_DAEMON {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      // If the FinalizerWatchdogDaemon thread is on the shortest path, then there was no other\n      // reference to the object and it was about to be GCed.\n      excluded.thread(\"FinalizerWatchdogDaemon\").alwaysExclude();\n    }\n  },\n\n  MAIN {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      // The main thread stack is ever changing so local variables aren't likely to hold references\n      // for long. If this is on the shortest path, it's probably that there's a longer path with\n      // a real leak.\n      excluded.thread(\"main\").alwaysExclude();\n    }\n  },\n\n  LEAK_CANARY_THREAD {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      excluded.thread(AndroidWatchExecutor.LEAK_CANARY_THREAD_NAME).alwaysExclude();\n    }\n  },\n\n  EVENT_RECEIVER__MMESSAGE_QUEUE {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      //  DisplayEventReceiver keeps a reference message queue object so that it is not GC'd while\n      // the native peer of the receiver is using them.\n      // The main thread message queue is held on by the main Looper, but that might be a longer\n      // path. Let's not confuse people with a shorter path that is less meaningful.\n      excluded.instanceField(\"android.view.Choreographer$FrameDisplayEventReceiver\",\n          \"mMessageQueue\").alwaysExclude();\n    }\n  },\n\n  //28 为安卓P\n  VIEWLOCATIONHOLDER_ROOT(SDK_INT == 28) {\n    @Override\n    void add(ExcludedRefs.Builder excluded) {\n      //  In Android P, ViewLocationHolder has an mRoot field that is not cleared in its clear()\n      // method.\n      // Introduced in https://github.com/aosp-mirror/platform_frameworks_base/commit/86b326012813f09d8f1de7d6d26c986a909de894\n      // Bug report: https://issuetracker.google.com/issues/112792715\n      excluded.instanceField(\"android.view.ViewGroup$ViewLocationHolder\",\n          \"mRoot\");\n    }\n  };\n\n  /**\n   * This returns the references in the leak path that should be ignored by all on Android.\n   */\n  public static @NonNull ExcludedRefs.Builder createAndroidDefaults() {\n    return createBuilder(\n        EnumSet.of(SOFT_REFERENCES, FINALIZER_WATCHDOG_DAEMON, MAIN, LEAK_CANARY_THREAD,\n            EVENT_RECEIVER__MMESSAGE_QUEUE));\n  }\n\n  /**\n   * This returns the references in the leak path that can be ignored for app developers. This\n   * doesn't mean there is no memory leak, to the contrary. However, some leaks are caused by bugs\n   * in AOSP or manufacturer forks of AOSP. In such cases, there is very little we can do as app\n   * developers except by resorting to serious hacks, so we remove the noise caused by those leaks.\n   */\n  public static @NonNull ExcludedRefs.Builder createAppDefaults() {\n    return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));\n  }\n\n  public static @NonNull ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {\n    ExcludedRefs.Builder excluded = ExcludedRefs.builder();\n    for (AndroidExcludedRefs ref : refs) {\n      if (ref.applies) {\n        ref.add(excluded);\n        ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());\n      }\n    }\n    return excluded;\n  }\n\n  final boolean applies;\n\n  AndroidExcludedRefs() {\n    this(true);\n  }\n\n  AndroidExcludedRefs(boolean applies) {\n    this.applies = applies;\n  }\n\n  abstract void add(ExcludedRefs.Builder excluded);\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/AndroidHeapDumper.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.app.Notification;\nimport android.app.NotificationManager;\nimport android.content.Context;\nimport android.os.Debug;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.MessageQueue;\nimport android.os.SystemClock;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.squareup.leakcanary.internal.ActivityLifecycleCallbacksAdapter;\nimport com.squareup.leakcanary.internal.FutureResult;\nimport com.squareup.leakcanary.internal.LeakCanaryInternals;\n\nimport java.io.File;\n\nimport static java.util.concurrent.TimeUnit.SECONDS;\n\npublic final class AndroidHeapDumper implements HeapDumper {\n\n  private final Context context;\n  private final LeakDirectoryProvider leakDirectoryProvider;\n  private final Handler mainHandler;\n\n  private Activity resumedActivity;\n\n  public AndroidHeapDumper(@NonNull Context context,\n      @NonNull LeakDirectoryProvider leakDirectoryProvider) {\n    this.leakDirectoryProvider = leakDirectoryProvider;\n    this.context = context.getApplicationContext();\n    mainHandler = new Handler(Looper.getMainLooper());\n\n    Application application = (Application) context.getApplicationContext();\n    application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacksAdapter() {\n      @Override\n      public void onActivityResumed(Activity activity) {\n        resumedActivity = activity;\n      }\n\n      @Override\n      public void onActivityPaused(Activity activity) {\n        if (resumedActivity == activity) {\n          resumedActivity = null;\n        }\n      }\n    });\n  }\n\n  @SuppressWarnings(\"ReferenceEquality\") // Explicitly checking for named null.\n  @Override\n  @Nullable\n  public File dumpHeap() {\n    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();\n\n    if (heapDumpFile == RETRY_LATER) {\n      return RETRY_LATER;\n    }\n\n    FutureResult<Toast> waitingForToast = new FutureResult<>();\n    showToast(waitingForToast);\n\n    if (!waitingForToast.wait(5, SECONDS)) {\n      CanaryLog.d(\"Did not dump heap, too much time waiting for Toast.\");\n      return RETRY_LATER;\n    }\n\n    Notification.Builder builder = new Notification.Builder(context)\n        .setContentTitle(context.getString(R.string.leak_canary_notification_dumping));\n    Notification notification = LeakCanaryInternals.buildNotification(context, builder);\n    NotificationManager notificationManager =\n        (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);\n    int notificationId = (int) SystemClock.uptimeMillis();\n    notificationManager.notify(notificationId, notification);\n\n    Toast toast = waitingForToast.get();\n    try {\n      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());\n      cancelToast(toast);\n      notificationManager.cancel(notificationId);\n      return heapDumpFile;\n    } catch (Exception e) {\n      CanaryLog.d(e, \"Could not dump heap\");\n      // Abort heap dump\n      return RETRY_LATER;\n    }\n  }\n\n  private void showToast(final FutureResult<Toast> waitingForToast) {\n    mainHandler.post(new Runnable() {\n      @Override\n      public void run() {\n        if (resumedActivity == null) {\n          waitingForToast.set(null);\n          return;\n        }\n        final Toast toast = new Toast(resumedActivity);\n        toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);\n        toast.setDuration(Toast.LENGTH_LONG);\n        LayoutInflater inflater = LayoutInflater.from(resumedActivity);\n        toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));\n        toast.show();\n        // Waiting for Idle to make sure Toast gets rendered.\n        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {\n          @Override\n          public boolean queueIdle() {\n            waitingForToast.set(toast);\n            return false;\n          }\n        });\n      }\n    });\n  }\n\n  private void cancelToast(final Toast toast) {\n    if (toast == null) {\n      return;\n    }\n    mainHandler.post(new Runnable() {\n      @Override\n      public void run() {\n        toast.cancel();\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/AndroidReachabilityInspectors.java",
    "content": "/*\n * Copyright (C) 2018 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.app.Dialog;\nimport android.app.Fragment;\nimport android.os.MessageQueue;\nimport androidx.annotation.NonNull;\nimport android.view.View;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A set of default {@link Reachability.Inspector}s that knows about common AOSP and library\n * classes.\n *\n * These are heuristics based on our experience and knownledge of AOSP and various library\n * internals. We only make a reachability decision if we're reasonably sure such reachability is\n * unlikely to be the result of a programmer mistake.\n *\n * For example, no matter how many mistakes we make in our code, the value of Activity.mDestroy\n * will not be influenced by those mistakes.\n */\npublic enum AndroidReachabilityInspectors {\n\n  VIEW(ViewInspector.class),\n\n  ACTIVITY(ActivityInspector.class),\n\n  DIALOG(DialogInspector.class),\n\n  APPLICATION(ApplicationInspector.class),\n\n  FRAGMENT(FragmentInspector.class),\n\n  SUPPORT_FRAGMENT(SupportFragmentInspector.class),\n\n  MESSAGE_QUEUE(MessageQueueInspector.class),\n\n  MORTAR_PRESENTER(MortarPresenterInspector.class),\n\n  VIEW_ROOT_IMPL(ViewImplInspector.class),\n\n  MAIN_THEAD(MainThreadInspector.class),\n\n  WINDOW(WindowInspector.class),\n\n  //\n  ;\n\n  private final Class<? extends Reachability.Inspector> inspectorClass;\n\n  AndroidReachabilityInspectors(Class<? extends Reachability.Inspector> inspectorClass) {\n    this.inspectorClass = inspectorClass;\n  }\n\n  public static @NonNull\n  List<Class<? extends Reachability.Inspector>> defaultAndroidInspectors() {\n    List<Class<? extends Reachability.Inspector>> inspectorClasses = new ArrayList<>();\n    for (AndroidReachabilityInspectors enumValue : AndroidReachabilityInspectors.values()) {\n      inspectorClasses.add(enumValue.inspectorClass);\n    }\n    return inspectorClasses;\n  }\n\n  public static class ViewInspector implements Reachability.Inspector {\n    @Override\n    public @NonNull\n    Reachability expectedReachability(@NonNull LeakTraceElement element) {\n      if (!element.isInstanceOf(View.class)) {\n        return Reachability.UNKNOWN;\n      }\n      String mAttachInfo = element.getFieldReferenceValue(\"mAttachInfo\");\n      if (mAttachInfo == null) {\n        return Reachability.UNKNOWN;\n      }\n      return mAttachInfo.equals(\"null\") ? Reachability.UNREACHABLE : Reachability.REACHABLE;\n    }\n  }\n\n  public static class ActivityInspector implements Reachability.Inspector {\n    @Override\n    public @NonNull\n    Reachability expectedReachability(@NonNull LeakTraceElement element) {\n      if (!element.isInstanceOf(Activity.class)) {\n        return Reachability.UNKNOWN;\n      }\n      String mDestroyed = element.getFieldReferenceValue(\"mDestroyed\");\n      if (mDestroyed == null) {\n        return Reachability.UNKNOWN;\n      }\n      return mDestroyed.equals(\"true\") ? Reachability.UNREACHABLE : Reachability.REACHABLE;\n    }\n  }\n\n  public static class DialogInspector implements Reachability.Inspector {\n    @Override\n    public @NonNull\n    Reachability expectedReachability(@NonNull LeakTraceElement element) {\n      if (!element.isInstanceOf(Dialog.class)) {\n        return Reachability.UNKNOWN;\n      }\n      String mDecor = element.getFieldReferenceValue(\"mDecor\");\n      if (mDecor == null) {\n        return Reachability.UNKNOWN;\n      }\n      return mDecor.equals(\"null\") ? Reachability.UNREACHABLE : Reachability.REACHABLE;\n    }\n  }\n\n  public static class ApplicationInspector implements Reachability.Inspector {\n    @Override\n    public @NonNull\n    Reachability expectedReachability(@NonNull LeakTraceElement element) {\n      if (element.isInstanceOf(Application.class)) {\n        return Reachability.REACHABLE;\n      }\n      return Reachability.UNKNOWN;\n    }\n  }\n\n  public static class FragmentInspector implements Reachability.Inspector {\n    @Override\n    public @NonNull\n    Reachability expectedReachability(@NonNull LeakTraceElement element) {\n      if (!element.isInstanceOf(Fragment.class)) {\n        return Reachability.UNKNOWN;\n      }\n      String mDetached = element.getFieldReferenceValue(\"mDetached\");\n      if (mDetached == null) {\n        return Reachability.UNKNOWN;\n      }\n      return mDetached.equals(\"true\") ? Reachability.UNREACHABLE : Reachability.REACHABLE;\n    }\n  }\n\n  public static class SupportFragmentInspector implements Reachability.Inspector {\n    @Override\n    public @NonNull\n    Reachability expectedReachability(@NonNull LeakTraceElement element) {\n      if (!element.isInstanceOf(\"androidx.fragment.app.Fragment\")) {\n        return Reachability.UNKNOWN;\n      }\n      String mDetached = element.getFieldReferenceValue(\"mDetached\");\n      if (mDetached == null) {\n        return Reachability.UNKNOWN;\n      }\n      return mDetached.equals(\"true\") ? Reachability.UNREACHABLE : Reachability.REACHABLE;\n    }\n  }\n\n  public static class MessageQueueInspector implements Reachability.Inspector {\n    @Override\n    public @NonNull\n    Reachability expectedReachability(@NonNull LeakTraceElement element) {\n      if (!element.isInstanceOf(MessageQueue.class)) {\n        return Reachability.UNKNOWN;\n      }\n      String mQuitting = element.getFieldReferenceValue(\"mQuitting\");\n      // If the queue is not quitting, maybe it should actually have been, we don't know.\n      // However, if it's quitting, it is very likely that's not a bug.\n      if (\"true\".equals(mQuitting)) {\n        return Reachability.UNREACHABLE;\n      }\n      return Reachability.UNKNOWN;\n    }\n  }\n\n  public static class MortarPresenterInspector implements Reachability.Inspector {\n    @Override\n    public @NonNull\n    Reachability expectedReachability(@NonNull LeakTraceElement element) {\n      if (!element.isInstanceOf(\"mortar.Presenter\")) {\n        return Reachability.UNKNOWN;\n      }\n      String view = element.getFieldReferenceValue(\"view\");\n\n      // Bugs in view code tends to cause Mortar presenters to still have a view when they actually\n      // should be a unreachable, so in that case we don't know their reachability status. However,\n      // when the view is null, we're pretty sure they should be unreachable.\n      if (\"null\".equals(view)) {\n        return Reachability.UNREACHABLE;\n      }\n      return Reachability.UNKNOWN;\n    }\n  }\n\n  public static class ViewImplInspector implements Reachability.Inspector {\n    @Override\n    public @NonNull\n    Reachability expectedReachability(@NonNull LeakTraceElement element) {\n      if (!element.isInstanceOf(\"android.view.ViewRootImpl\")) {\n        return Reachability.UNKNOWN;\n      }\n      String mView = element.getFieldReferenceValue(\"mView\");\n      if (mView == null) {\n        return Reachability.UNKNOWN;\n      }\n      return mView.equals(\"null\") ? Reachability.UNREACHABLE : Reachability.REACHABLE;\n    }\n  }\n\n  public static class MainThreadInspector implements Reachability.Inspector {\n    @Override\n    public @NonNull\n    Reachability expectedReachability(@NonNull LeakTraceElement element) {\n      if (!element.isInstanceOf(Thread.class)) {\n        return Reachability.UNKNOWN;\n      }\n      String name = element.getFieldReferenceValue(\"name\");\n      if (\"main\".equals(name)) {\n        return Reachability.REACHABLE;\n      }\n      return Reachability.UNKNOWN;\n    }\n  }\n\n  public static class WindowInspector implements Reachability.Inspector {\n    @Override\n    public @NonNull\n    Reachability expectedReachability(@NonNull LeakTraceElement element) {\n      if (!element.isInstanceOf(\"android.view.Window\")) {\n        return Reachability.UNKNOWN;\n      }\n      String mDestroyed = element.getFieldReferenceValue(\"mDestroyed\");\n      if (mDestroyed == null) {\n        return Reachability.UNKNOWN;\n      }\n      return mDestroyed.equals(\"true\") ? Reachability.UNREACHABLE : Reachability.REACHABLE;\n    }\n  }\n\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/AndroidRefWatcherBuilder.java",
    "content": "package com.squareup.leakcanary;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\n\nimport com.squareup.leakcanary.internal.DisplayLeakActivity;\nimport com.squareup.leakcanary.internal.FragmentRefWatcher;\nimport com.squareup.leakcanary.internal.LeakCanaryInternals;\n\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport static java.util.concurrent.TimeUnit.SECONDS;\n\n/** A {@link RefWatcherBuilder} with appropriate Android defaults. */\npublic final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {\n\n  private static final long DEFAULT_WATCH_DELAY_MILLIS = SECONDS.toMillis(5);\n\n  private final Context context;\n  private boolean watchActivities = true;\n  private boolean watchFragments = true;\n  private boolean enableDisplayLeakActivity = false;\n\n  AndroidRefWatcherBuilder(@NonNull Context context) {\n    this.context = context.getApplicationContext();\n  }\n\n  /**\n   * Sets a custom {@link AbstractAnalysisResultService} to listen to analysis results. This\n   * overrides any call to {@link #heapDumpListener(HeapDump.Listener)}.\n   */\n  public @NonNull\n  AndroidRefWatcherBuilder listenerServiceClass(\n      @NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {\n    enableDisplayLeakActivity = UploadLeakService.class.isAssignableFrom(listenerServiceClass);\n    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));\n  }\n\n  /**\n   * Sets a custom delay for how long the {@link RefWatcher} should wait until it checks if a\n   * tracked object has been garbage collected. This overrides any call to {@link\n   * #watchExecutor(WatchExecutor)}.\n   */\n  public @NonNull\n  AndroidRefWatcherBuilder watchDelay(long delay, @NonNull TimeUnit unit) {\n    return watchExecutor(new AndroidWatchExecutor(unit.toMillis(delay)));\n  }\n\n  /**\n   * Whether we should automatically watch activities when calling {@link #buildAndInstall()}.\n   * Default is true.\n   */\n  public @NonNull\n  AndroidRefWatcherBuilder watchActivities(boolean watchActivities) {\n    this.watchActivities = watchActivities;\n    return this;\n  }\n\n  /**\n   * Whether we should automatically watch fragments when calling {@link #buildAndInstall()}.\n   * Default is true. When true, LeakCanary watches native fragments on Android O+ and support\n   * fragments if the leakcanary-support-fragment dependency is in the classpath.\n   */\n  public @NonNull\n  AndroidRefWatcherBuilder watchFragments(boolean watchFragments) {\n    this.watchFragments = watchFragments;\n    return this;\n  }\n\n  /**\n   * Sets the maximum number of heap dumps stored. This overrides any call to\n   * {@link LeakCanary#setLeakDirectoryProvider(LeakDirectoryProvider)}\n   *\n   * @throws IllegalArgumentException if maxStoredHeapDumps < 1.\n   */\n  public @NonNull\n  AndroidRefWatcherBuilder maxStoredHeapDumps(int maxStoredHeapDumps) {\n    LeakDirectoryProvider leakDirectoryProvider =\n        new DefaultLeakDirectoryProvider(context, maxStoredHeapDumps);\n    LeakCanary.setLeakDirectoryProvider(leakDirectoryProvider);\n    return self();\n  }\n\n  /**\n   * Creates a {@link RefWatcher} instance and makes it available through {@link\n   * LeakCanary#installedRefWatcher()}.\n   *\n   * Also starts watching activity references if {@link #watchActivities(boolean)} was set to true.\n   *\n   * @throws UnsupportedOperationException if called more than once per Android process.\n   */\n  public @NonNull\n  RefWatcher buildAndInstall() {\n    if (LeakCanaryInternals.installedRefWatcher != null) {\n      throw new UnsupportedOperationException(\"buildAndInstall() should only be called once.\");\n    }\n    RefWatcher refWatcher = build();\n    if (refWatcher != RefWatcher.DISABLED) {\n      if (enableDisplayLeakActivity) {\n        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);\n      }\n      if (watchActivities) {\n        ActivityRefWatcher.install(context, refWatcher);\n      }\n      if (watchFragments) {\n        FragmentRefWatcher.Helper.install(context, refWatcher);\n      }\n    }\n    LeakCanaryInternals.installedRefWatcher = refWatcher;\n    return refWatcher;\n  }\n\n  @Override\n  protected boolean isDisabled() {\n    return LeakCanary.isInAnalyzerProcess(context);\n  }\n\n  @Override\n  protected @NonNull\n  HeapDumper defaultHeapDumper() {\n    LeakDirectoryProvider leakDirectoryProvider =\n        LeakCanaryInternals.getLeakDirectoryProvider(context);\n    return new AndroidHeapDumper(context, leakDirectoryProvider);\n  }\n\n  @Override\n  protected @NonNull\n  DebuggerControl defaultDebuggerControl() {\n    return new AndroidDebuggerControl();\n  }\n\n  @Override\n  protected @NonNull HeapDump.Listener defaultHeapDumpListener() {\n    return new ServiceHeapDumpListener(context, UploadLeakService.class);\n  }\n\n  @Override\n  protected @NonNull\n  ExcludedRefs defaultExcludedRefs() {\n    return AndroidExcludedRefs.createAppDefaults().build();\n  }\n\n  @Override\n  protected @NonNull\n  WatchExecutor defaultWatchExecutor() {\n    return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);\n  }\n\n  @Override\n  protected @NonNull\n  List<Class<? extends Reachability.Inspector>> defaultReachabilityInspectorClasses() {\n    return AndroidReachabilityInspectors.defaultAndroidInspectors();\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/AndroidWatchExecutor.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Looper;\nimport android.os.MessageQueue;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * {@link WatchExecutor} suitable for watching Android reference leaks. This executor waits for the\n * main thread to be idle then posts to a serial background thread with the delay specified by\n * {@link AndroidRefWatcherBuilder#watchDelay(long, TimeUnit)}.\n */\npublic final class AndroidWatchExecutor implements WatchExecutor {\n\n    static final String LEAK_CANARY_THREAD_NAME = \"LeakCanary-Heap-Dump\";\n    private final Handler mainHandler;\n    private final Handler backgroundHandler;\n    private final long initialDelayMillis;\n    private final long maxBackoffFactor;\n\n    public AndroidWatchExecutor(long initialDelayMillis) {\n        mainHandler = new Handler(Looper.getMainLooper());\n        HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);\n        handlerThread.start();\n        backgroundHandler = new Handler(handlerThread.getLooper());\n        this.initialDelayMillis = initialDelayMillis;\n        maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;\n    }\n\n    @Override\n    public void execute(@NonNull Retryable retryable) {\n        //主线程\n        if (Looper.getMainLooper().getThread() == Thread.currentThread()) {\n            waitForIdle(retryable, 0);\n        } else {\n            //异步线程\n            postWaitForIdle(retryable, 0);\n        }\n    }\n\n    private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {\n        mainHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                waitForIdle(retryable, failedAttempts);\n            }\n        });\n    }\n\n    private void waitForIdle(final Retryable retryable, final int failedAttempts) {\n        // This needs to be called from the main thread.\n        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {\n            @Override\n            public boolean queueIdle() {\n                postToBackgroundWithDelay(retryable, failedAttempts);\n                return false;\n            }\n        });\n    }\n\n    private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {\n        long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);\n        long delayMillis = initialDelayMillis * exponentialBackoffFactor;\n        backgroundHandler.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                Retryable.Result result = retryable.run();\n                if (result == Retryable.Result.RETRY) {\n                    postWaitForIdle(retryable, failedAttempts + 1);\n                }\n            }\n        }, delayMillis);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/CanaryLog.java",
    "content": "package com.squareup.leakcanary;\n\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\npublic final class CanaryLog {\n\n  private static volatile Logger logger = new DefaultLogger();\n\n  public interface Logger {\n    void d(@NonNull String message, @NonNull Object... args);\n\n    void d(@Nullable Throwable throwable, @NonNull String message, @NonNull Object... args);\n  }\n\n  private static class DefaultLogger implements Logger {\n    DefaultLogger() {\n    }\n\n    @Override\n    public void d(@NonNull String message, @NonNull Object... args) {\n      String formatted = String.format(message, args);\n      if (formatted.length() < 4000) {\n        Log.d(\"LeakCanary\", formatted);\n      } else {\n        String[] lines = formatted.split(\"\\n\", -1);\n        for (String line : lines) {\n          Log.d(\"LeakCanary\", line);\n        }\n      }\n    }\n\n    @Override\n    public void d(@Nullable Throwable throwable,\n                  @NonNull String message,\n                  @NonNull Object... args) {\n      d(String.format(message, args) + '\\n' + Log.getStackTraceString(throwable));\n    }\n  }\n\n  public static void setLogger(@Nullable Logger logger) {\n    CanaryLog.logger = logger;\n  }\n\n  public static void d(@NonNull String message, @NonNull Object... args) {\n    // Local variable to prevent the ref from becoming null after the null check.\n    Logger logger = CanaryLog.logger;\n    if (logger == null) {\n      return;\n    }\n    logger.d(message, args);\n  }\n\n  public static void d(@Nullable Throwable throwable,\n      @NonNull String message,\n      @NonNull Object... args) {\n    // Local variable to prevent the ref from becoming null after the null check.\n    Logger logger = CanaryLog.logger;\n    if (logger == null) {\n      return;\n    }\n    logger.d(throwable, message, args);\n  }\n\n  private CanaryLog() {\n    throw new AssertionError();\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/DebuggerControl.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\n/**\n * Gives the opportunity to skip checking if a reference is gone when the debugger is connected.\n * An attached debugger might retain references and create false positives.\n */\npublic interface DebuggerControl {\n  DebuggerControl NONE = new DebuggerControl() {\n    @Override public boolean isDebuggerAttached() {\n      return false;\n    }\n  };\n\n  boolean isDebuggerAttached();\n}"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/DefaultLeakDirectoryProvider.java",
    "content": "/*\n * Copyright (C) 2016 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport android.annotation.TargetApi;\nimport android.app.PendingIntent;\nimport android.content.Context;\nimport android.os.Environment;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.squareup.leakcanary.R;\nimport com.squareup.leakcanary.internal.RequestStoragePermissionActivity;\nimport com.squareup.leakcanary.internal.LeakCanaryInternals;\n\nimport java.io.File;\nimport java.io.FilenameFilter;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.UUID;\n\nimport static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;\nimport static android.content.pm.PackageManager.PERMISSION_GRANTED;\nimport static android.os.Build.VERSION.SDK_INT;\nimport static android.os.Build.VERSION_CODES.M;\nimport static android.os.Environment.DIRECTORY_DOWNLOADS;\nimport static com.squareup.leakcanary.HeapDumper.RETRY_LATER;\n\npublic final class DefaultLeakDirectoryProvider implements LeakDirectoryProvider {\n\n  private static final int DEFAULT_MAX_STORED_HEAP_DUMPS = 7;\n\n  private static final String HPROF_SUFFIX = \".hprof\";\n  private static final String PENDING_HEAPDUMP_SUFFIX = \"_pending\" + HPROF_SUFFIX;\n\n  /** 10 minutes */\n  private static final int ANALYSIS_MAX_DURATION_MS = 10 * 60 * 1000;\n\n  private final Context context;\n  private final int maxStoredHeapDumps;\n\n  private volatile boolean writeExternalStorageGranted;\n  private volatile boolean permissionNotificationDisplayed;\n\n  public DefaultLeakDirectoryProvider(@NonNull Context context) {\n    this(context, DEFAULT_MAX_STORED_HEAP_DUMPS);\n  }\n\n  public DefaultLeakDirectoryProvider(@NonNull Context context, int maxStoredHeapDumps) {\n    if (maxStoredHeapDumps < 1) {\n      throw new IllegalArgumentException(\"maxStoredHeapDumps must be at least 1\");\n    }\n    this.context = context.getApplicationContext();\n    this.maxStoredHeapDumps = maxStoredHeapDumps;\n  }\n\n  @Override\n  public @NonNull\n  List<File> listFiles(@NonNull FilenameFilter filter) {\n    if (!hasStoragePermission()) {\n      requestWritePermissionNotification();\n    }\n    List<File> files = new ArrayList<>();\n\n    File[] externalFiles = externalStorageDirectory().listFiles(filter);\n    if (externalFiles != null) {\n      files.addAll(Arrays.asList(externalFiles));\n    }\n\n    File[] appFiles = appStorageDirectory().listFiles(filter);\n    if (appFiles != null) {\n      files.addAll(Arrays.asList(appFiles));\n    }\n    return files;\n  }\n\n  @Override\n  public @Nullable\n  File newHeapDumpFile() {\n    List<File> pendingHeapDumps = listFiles(new FilenameFilter() {\n      @Override\n      public boolean accept(File dir, String filename) {\n        return filename.endsWith(PENDING_HEAPDUMP_SUFFIX);\n      }\n    });\n\n    // If a new heap dump file has been created recently and hasn't been processed yet, we skip.\n    // Otherwise we move forward and assume that the analyzer process crashes. The file will\n    // eventually be removed with heap dump file rotation.\n    for (File file : pendingHeapDumps) {\n      if (System.currentTimeMillis() - file.lastModified() < ANALYSIS_MAX_DURATION_MS) {\n        CanaryLog.d(\"Could not dump heap, previous analysis still is in progress.\");\n        return RETRY_LATER;\n      }\n    }\n\n    cleanupOldHeapDumps();\n\n    File storageDirectory = externalStorageDirectory();\n    if (!directoryWritableAfterMkdirs(storageDirectory)) {\n      if (!hasStoragePermission()) {\n        CanaryLog.d(\"WRITE_EXTERNAL_STORAGE permission not granted\");\n        requestWritePermissionNotification();\n      } else {\n        String state = Environment.getExternalStorageState();\n        if (!Environment.MEDIA_MOUNTED.equals(state)) {\n          CanaryLog.d(\"External storage not mounted, state: %s\", state);\n        } else {\n          CanaryLog.d(\"Could not create heap dump directory in external storage: [%s]\",\n              storageDirectory.getAbsolutePath());\n        }\n      }\n      // Fallback to app storage.\n      storageDirectory = appStorageDirectory();\n      if (!directoryWritableAfterMkdirs(storageDirectory)) {\n        CanaryLog.d(\"Could not create heap dump directory in app storage: [%s]\",\n            storageDirectory.getAbsolutePath());\n        return RETRY_LATER;\n      }\n    }\n    // If two processes from the same app get to this step at the same time, they could both\n    // create a heap dump. This is an edge case we ignore.\n    return new File(storageDirectory, UUID.randomUUID().toString() + PENDING_HEAPDUMP_SUFFIX);\n  }\n\n  @Override\n  public void clearLeakDirectory() {\n    List<File> allFilesExceptPending = listFiles(new FilenameFilter() {\n      @Override\n      public boolean accept(File dir, String filename) {\n        return !filename.endsWith(PENDING_HEAPDUMP_SUFFIX);\n      }\n    });\n    for (File file : allFilesExceptPending) {\n      boolean deleted = file.delete();\n      if (!deleted) {\n        CanaryLog.d(\"Could not delete file %s\", file.getPath());\n      }\n    }\n  }\n\n  @TargetApi(M) private boolean hasStoragePermission() {\n    if (SDK_INT < M) {\n      return true;\n    }\n    // Once true, this won't change for the life of the process so we can cache it.\n    if (writeExternalStorageGranted) {\n      return true;\n    }\n    writeExternalStorageGranted =\n        context.checkSelfPermission(WRITE_EXTERNAL_STORAGE) == PERMISSION_GRANTED;\n    return writeExternalStorageGranted;\n  }\n\n  private void requestWritePermissionNotification() {\n    if (permissionNotificationDisplayed) {\n      return;\n    }\n    permissionNotificationDisplayed = true;\n\n    PendingIntent pendingIntent = RequestStoragePermissionActivity.createPendingIntent(context);\n    String contentTitle = context.getString(R.string.leak_canary_permission_notification_title);\n    CharSequence packageName = context.getPackageName();\n    String contentText =\n        context.getString(R.string.leak_canary_permission_notification_text, packageName);\n    LeakCanaryInternals.showNotification(context, contentTitle, contentText, pendingIntent, 0xDEAFBEEF);\n  }\n\n  private File externalStorageDirectory() {\n    File downloadsDirectory = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS);\n    return new File(downloadsDirectory, \"leakcanary-\" + context.getPackageName());\n  }\n\n  private File appStorageDirectory() {\n    File appFilesDirectory = context.getFilesDir();\n    return new File(appFilesDirectory, \"leakcanary\");\n  }\n\n  private boolean directoryWritableAfterMkdirs(File directory) {\n    boolean success = directory.mkdirs();\n    return (success || directory.exists()) && directory.canWrite();\n  }\n\n  private void cleanupOldHeapDumps() {\n    List<File> hprofFiles = listFiles(new FilenameFilter() {\n      @Override\n      public boolean accept(File dir, String filename) {\n        return filename.endsWith(HPROF_SUFFIX);\n      }\n    });\n    int filesToRemove = hprofFiles.size() - maxStoredHeapDumps;\n    if (filesToRemove > 0) {\n      CanaryLog.d(\"Removing %d heap dumps\", filesToRemove);\n      // Sort with oldest modified first.\n      Collections.sort(hprofFiles, new Comparator<File>() {\n        @Override\n        public int compare(File lhs, File rhs) {\n          return Long.valueOf(lhs.lastModified()).compareTo(rhs.lastModified());\n        }\n      });\n      for (int i = 0; i < filesToRemove; i++) {\n        boolean deleted = hprofFiles.get(i).delete();\n        if (!deleted) {\n          CanaryLog.d(\"Could not delete old hprof file %s\", hprofFiles.get(i).getPath());\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/DisplayLeakService.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport android.app.PendingIntent;\nimport android.os.SystemClock;\n\nimport androidx.annotation.NonNull;\n\nimport com.squareup.leakcanary.internal.DisplayLeakActivity;\nimport com.squareup.leakcanary.internal.LeakCanaryInternals;\n\nimport java.io.File;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.Locale;\n\nimport static android.text.format.Formatter.formatShortFileSize;\nimport static com.squareup.leakcanary.LeakCanary.leakInfo;\n\n/**\n * Logs leak analysis results, and then shows a notification which will start {@link\n * DisplayLeakActivity}.\n * <p>\n * You can extend this class and override {@link #afterDefaultHandling(HeapDump, AnalysisResult,\n * String)} to add custom behavior, e.g. uploading the heap dump.\n */\npublic class DisplayLeakService extends AbstractAnalysisResultService {\n    private static final String TAG = \"DisplayLeakService\";\n\n    @Override\n    protected final void onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) {\n\n        HeapDump heapDump = analyzedHeap.heapDump;\n        AnalysisResult result = analyzedHeap.result;\n\n\n\n        String leakInfo = leakInfo(this, heapDump, result, true);\n        CanaryLog.d(\"%s\", leakInfo);\n\n        heapDump = renameHeapdump(heapDump);\n        boolean resultSaved = saveResult(heapDump, result);\n\n        String contentTitle;\n        if (resultSaved) {\n            PendingIntent pendingIntent =\n                    DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);\n            if (result.failure != null) {\n                contentTitle = getString(R.string.leak_canary_analysis_failed);\n            } else {\n                String className = LeakCanaryInternals.classSimpleName(result.className);\n                if (result.leakFound) {\n                    if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {\n                        if (result.excludedLeak) {\n                            contentTitle = getString(R.string.leak_canary_leak_excluded, className);\n                        } else {\n                            contentTitle = getString(R.string.leak_canary_class_has_leaked, className);\n                        }\n                    } else {\n                        String size = formatShortFileSize(this, result.retainedHeapSize);\n                        if (result.excludedLeak) {\n                            contentTitle =\n                                    getString(R.string.leak_canary_leak_excluded_retaining, className, size);\n                        } else {\n                            contentTitle =\n                                    getString(R.string.leak_canary_class_has_leaked_retaining, className, size);\n                        }\n                    }\n                } else {\n                    contentTitle = getString(R.string.leak_canary_class_no_leak, className);\n                }\n            }\n            String contentText = getString(R.string.leak_canary_notification_message);\n            showNotification(pendingIntent, contentTitle, contentText);\n        } else {\n            onAnalysisResultFailure(getString(R.string.leak_canary_could_not_save_text));\n        }\n\n        afterDefaultHandling(heapDump, result, leakInfo);\n    }\n\n    @Override\n    protected final void onAnalysisResultFailure(String failureMessage) {\n        super.onAnalysisResultFailure(failureMessage);\n        String failureTitle = getString(R.string.leak_canary_result_failure_title);\n        showNotification(null, failureTitle, failureMessage);\n    }\n\n    private void showNotification(PendingIntent pendingIntent, String contentTitle,\n                                  String contentText) {\n        // New notification id every second.\n        int notificationId = (int) (SystemClock.uptimeMillis() / 1000);\n        LeakCanaryInternals.showNotification(this, contentTitle, contentText, pendingIntent,\n                notificationId);\n    }\n\n    private boolean saveResult(HeapDump heapDump, AnalysisResult result) {\n        File resultFile = AnalyzedHeap.save(heapDump, result);\n        return resultFile != null;\n    }\n\n    private HeapDump renameHeapdump(HeapDump heapDump) {\n        String fileName =\n                new SimpleDateFormat(\"yyyy-MM-dd_HH-mm-ss_SSS'.hprof'\", Locale.US).format(new Date());\n\n        File newFile = new File(heapDump.heapDumpFile.getParent(), fileName);\n        boolean renamed = heapDump.heapDumpFile.renameTo(newFile);\n        if (!renamed) {\n            CanaryLog.d(\"Could not rename heap dump file %s to %s\", heapDump.heapDumpFile.getPath(),\n                    newFile.getPath());\n        }\n        return heapDump.buildUpon().heapDumpFile(newFile).build();\n    }\n\n    /**\n     * You can override this method and do a blocking call to a server to upload the leak trace and\n     * the heap dump. Don't forget to check {@link AnalysisResult#leakFound} and {@link\n     * AnalysisResult#excludedLeak} first.\n     */\n    protected void afterDefaultHandling(@NonNull HeapDump heapDump, @NonNull AnalysisResult result,\n                                        @NonNull String leakInfo) {\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/ExcludedRefs.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport java.io.Serializable;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport static com.squareup.leakcanary.Preconditions.checkNotNull;\nimport static java.util.Collections.unmodifiableMap;\n\n/**\n * Prevents specific references from being taken into account when computing the shortest strong\n * reference path from a suspected leaking instance to the GC roots.\n *\n * This class lets you ignore known memory leaks that you known about. If the shortest path\n * matches {@link ExcludedRefs}, than the heap analyzer should look for a longer path with nothing\n * matching in {@link ExcludedRefs}.\n */\npublic final class ExcludedRefs implements Serializable {\n\n  public static Builder builder() {\n    return new BuilderWithParams();\n  }\n\n  public final Map<String, Map<String, Exclusion>> fieldNameByClassName;\n  public final Map<String, Map<String, Exclusion>> staticFieldNameByClassName;\n  public final Map<String, Exclusion> threadNames;\n  public final Map<String, Exclusion> classNames;\n\n  ExcludedRefs(BuilderWithParams builder) {\n    this.fieldNameByClassName = unmodifiableRefStringMap(builder.fieldNameByClassName);\n    this.staticFieldNameByClassName = unmodifiableRefStringMap(builder.staticFieldNameByClassName);\n    this.threadNames = unmodifiableRefMap(builder.threadNames);\n    this.classNames = unmodifiableRefMap(builder.classNames);\n  }\n\n  private Map<String, Map<String, Exclusion>> unmodifiableRefStringMap(\n      Map<String, Map<String, ParamsBuilder>> mapmap) {\n    LinkedHashMap<String, Map<String, Exclusion>> fieldNameByClassName = new LinkedHashMap<>();\n    for (Map.Entry<String, Map<String, ParamsBuilder>> entry : mapmap.entrySet()) {\n      fieldNameByClassName.put(entry.getKey(), unmodifiableRefMap(entry.getValue()));\n    }\n    return unmodifiableMap(fieldNameByClassName);\n  }\n\n  private Map<String, Exclusion> unmodifiableRefMap(Map<String, ParamsBuilder> fieldBuilderMap) {\n    Map<String, Exclusion> fieldMap = new LinkedHashMap<>();\n    for (Map.Entry<String, ParamsBuilder> fieldEntry : fieldBuilderMap.entrySet()) {\n      fieldMap.put(fieldEntry.getKey(), new Exclusion(fieldEntry.getValue()));\n    }\n    return unmodifiableMap(fieldMap);\n  }\n\n  @Override public String toString() {\n    String string = \"\";\n    for (Map.Entry<String, Map<String, Exclusion>> classes : fieldNameByClassName.entrySet()) {\n      String clazz = classes.getKey();\n      for (Map.Entry<String, Exclusion> field : classes.getValue().entrySet()) {\n        String always = field.getValue().alwaysExclude ? \" (always)\" : \"\";\n        string += \"| Field: \" + clazz + \".\" + field.getKey() + always + \"\\n\";\n      }\n    }\n    for (Map.Entry<String, Map<String, Exclusion>> classes : staticFieldNameByClassName.entrySet()) {\n      String clazz = classes.getKey();\n      for (Map.Entry<String, Exclusion> field : classes.getValue().entrySet()) {\n        String always = field.getValue().alwaysExclude ? \" (always)\" : \"\";\n        string += \"| Static field: \" + clazz + \".\" + field.getKey() + always + \"\\n\";\n      }\n    }\n    for (Map.Entry<String, Exclusion> thread : threadNames.entrySet()) {\n      String always = thread.getValue().alwaysExclude ? \" (always)\" : \"\";\n      string += \"| Thread:\" + thread.getKey() + always + \"\\n\";\n    }\n    for (Map.Entry<String, Exclusion> clazz : classNames.entrySet()) {\n      String always = clazz.getValue().alwaysExclude ? \" (always)\" : \"\";\n      string += \"| Class:\" + clazz.getKey() + always + \"\\n\";\n    }\n    return string;\n  }\n\n  static final class ParamsBuilder {\n    String name;\n    String reason;\n    boolean alwaysExclude;\n    final String matching;\n\n    ParamsBuilder(String matching) {\n      this.matching = matching;\n    }\n  }\n\n  public interface Builder {\n    BuilderWithParams instanceField(String className, String fieldName);\n\n    BuilderWithParams staticField(String className, String fieldName);\n\n    BuilderWithParams thread(String threadName);\n\n    BuilderWithParams clazz(String className);\n\n    ExcludedRefs build();\n  }\n\n  public static final class BuilderWithParams implements Builder {\n\n    private final Map<String, Map<String, ParamsBuilder>> fieldNameByClassName =\n        new LinkedHashMap<>();\n    private final Map<String, Map<String, ParamsBuilder>> staticFieldNameByClassName =\n        new LinkedHashMap<>();\n    private final Map<String, ParamsBuilder> threadNames = new LinkedHashMap<>();\n    private final Map<String, ParamsBuilder> classNames = new LinkedHashMap<>();\n\n    private ParamsBuilder lastParams;\n\n    BuilderWithParams() {\n    }\n\n    @Override public BuilderWithParams instanceField(String className, String fieldName) {\n      checkNotNull(className, \"className\");\n      checkNotNull(fieldName, \"fieldName\");\n      Map<String, ParamsBuilder> excludedFields = fieldNameByClassName.get(className);\n      if (excludedFields == null) {\n        excludedFields = new LinkedHashMap<>();\n        fieldNameByClassName.put(className, excludedFields);\n      }\n      lastParams = new ParamsBuilder(\"field \" + className + \"#\" + fieldName);\n      excludedFields.put(fieldName, lastParams);\n      return this;\n    }\n\n    @Override public BuilderWithParams staticField(String className, String fieldName) {\n      checkNotNull(className, \"className\");\n      checkNotNull(fieldName, \"fieldName\");\n      Map<String, ParamsBuilder> excludedFields = staticFieldNameByClassName.get(className);\n      if (excludedFields == null) {\n        excludedFields = new LinkedHashMap<>();\n        staticFieldNameByClassName.put(className, excludedFields);\n      }\n      lastParams = new ParamsBuilder(\"static field \" + className + \"#\" + fieldName);\n      excludedFields.put(fieldName, lastParams);\n      return this;\n    }\n\n    @Override public BuilderWithParams thread(String threadName) {\n      checkNotNull(threadName, \"threadName\");\n      lastParams = new ParamsBuilder(\"any threads named \" + threadName);\n      threadNames.put(threadName, lastParams);\n      return this;\n    }\n\n    /** Ignores all fields and static fields of all subclasses of the provided class name. */\n    @Override public BuilderWithParams clazz(String className) {\n      checkNotNull(className, \"className\");\n      lastParams = new ParamsBuilder(\"any subclass of \" + className);\n      classNames.put(className, lastParams);\n      return this;\n    }\n\n    public BuilderWithParams named(String name) {\n      lastParams.name = name;\n      return this;\n    }\n\n    public BuilderWithParams reason(String reason) {\n      lastParams.reason = reason;\n      return this;\n    }\n\n    public BuilderWithParams alwaysExclude() {\n      lastParams.alwaysExclude = true;\n      return this;\n    }\n\n    @Override public ExcludedRefs build() {\n      return new ExcludedRefs(this);\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/Exclusion.java",
    "content": "package com.squareup.leakcanary;\n\nimport java.io.Serializable;\n\npublic final class Exclusion implements Serializable {\n  public final String name;\n  public final String reason;\n  public final boolean alwaysExclude;\n  public final String matching;\n\n  Exclusion(ExcludedRefs.ParamsBuilder builder) {\n    this.name = builder.name;\n    this.reason = builder.reason;\n    this.alwaysExclude = builder.alwaysExclude;\n    this.matching = builder.matching;\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/FailTestOnLeakRunListener.java",
    "content": "/*\n * Copyright (C) 2018 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport android.app.Instrumentation;\nimport android.content.Context;\nimport android.os.Bundle;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport org.junit.runner.Description;\nimport org.junit.runner.Result;\nimport org.junit.runner.notification.Failure;\nimport org.junit.runner.notification.RunListener;\n\nimport java.util.List;\n\nimport static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;\nimport static com.squareup.leakcanary.Preconditions.checkNotNull;\n\n/**\n * <p>A JUnit {@link RunListener} for detecting memory leaks in Android instrumentation tests. It\n * waits for the end of a test, and if the test succeeds then it will look for leaking\n * references, trigger a heap dump if needed and perform an analysis.\n * <p> {@link FailTestOnLeakRunListener} can be subclassed to override\n * {@link #skipLeakDetectionReason(Description)}, {@link #reportLeaks(InstrumentationLeakResults)}\n * or {@link #buildLeakDetectedMessage(List)}\n *\n * @see InstrumentationLeakDetector\n */\npublic class FailTestOnLeakRunListener extends RunListener {\n\n    private static final String SEPARATOR = \"######################################\\n\";\n    private Bundle bundle;\n\n    private String skipLeakDetectionReason;\n\n    @Override\n    public final void testStarted(Description description) {\n        skipLeakDetectionReason = skipLeakDetectionReason(description);\n        if (skipLeakDetectionReason != null) {\n            return;\n        }\n        String testClass = description.getClassName();\n        String testName = description.getMethodName();\n\n        bundle = new Bundle();\n        bundle.putString(Instrumentation.REPORT_KEY_IDENTIFIER,\n                FailTestOnLeakRunListener.class.getName());\n        bundle.putString(\"class\", testClass);\n        bundle.putString(\"test\", testName);\n    }\n\n    /**\n     * Can be overridden to skip leak detection based on the description provided when a test\n     * is started. Returns null to continue leak detection, or a string describing the reason for\n     * skipping otherwise.\n     */\n    protected @Nullable\n    String skipLeakDetectionReason(@NonNull Description description) {\n        return null;\n    }\n\n    @Override\n    public final void testFailure(Failure failure) {\n        skipLeakDetectionReason = \"failed\";\n    }\n\n    @Override\n    public final void testIgnored(Description description) {\n        skipLeakDetectionReason = \"was ignored\";\n    }\n\n    @Override\n    public final void testAssumptionFailure(Failure failure) {\n        skipLeakDetectionReason = \"had an assumption failure\";\n    }\n\n    @Override\n    public final void testFinished(Description description) {\n        detectLeaks();\n        LeakCanary.installedRefWatcher().clearWatchedReferences();\n    }\n\n    @Override\n    public final void testRunStarted(Description description) {\n    }\n\n    @Override\n    public final void testRunFinished(Result result) {\n    }\n\n    private void detectLeaks() {\n        if (skipLeakDetectionReason != null) {\n            CanaryLog.d(\"Skipping leak detection because the test %s\", skipLeakDetectionReason);\n            skipLeakDetectionReason = null;\n            return;\n        }\n\n        InstrumentationLeakDetector leakDetector = new InstrumentationLeakDetector();\n        InstrumentationLeakResults results = leakDetector.detectLeaks();\n\n        reportLeaks(results);\n    }\n\n    /**\n     * Can be overridden to report leaks in a different way or do additional reporting.\n     */\n    protected void reportLeaks(@NonNull InstrumentationLeakResults results) {\n        if (!results.detectedLeaks.isEmpty()) {\n            String message =\n                    checkNotNull(buildLeakDetectedMessage(results.detectedLeaks), \"buildLeakDetectedMessage\");\n\n            bundle.putString(\"stack\", message);\n            //public static final int REPORT_VALUE_RESULT_FAILURE = -2;\n            getInstrumentation().sendStatus(-2, bundle);\n        }\n    }\n\n    /**\n     * Can be overridden to customize the failure string message.\n     */\n    protected @NonNull\n    String buildLeakDetectedMessage(\n            @NonNull List<InstrumentationLeakResults.Result> detectedLeaks) {\n        StringBuilder failureMessage = new StringBuilder();\n        failureMessage.append(\n                \"Test failed because memory leaks were detected, see leak traces below.\\n\");\n        failureMessage.append(SEPARATOR);\n\n        Context context = getInstrumentation().getContext();\n        for (InstrumentationLeakResults.Result detectedLeak : detectedLeaks) {\n            failureMessage.append(\n                    LeakCanary.leakInfo(context, detectedLeak.heapDump, detectedLeak.analysisResult, true));\n            failureMessage.append(SEPARATOR);\n        }\n\n        return failureMessage.toString();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/GcTrigger.java",
    "content": "/*\n * Copyright (C) 2011 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\n/**\n * Called when a watched reference is expected to be weakly reachable, but hasn't been enqueued\n * in the reference queue yet. This gives the application a hook to run the GC before the {@link\n * RefWatcher} checks the reference queue again, to avoid taking a heap dump if possible.\n */\npublic interface GcTrigger {\n  GcTrigger DEFAULT = new GcTrigger() {\n    @Override public void runGc() {\n      // Code taken from AOSP FinalizationTest:\n      // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/\n      // java/lang/ref/FinalizationTester.java\n      // System.gc() does not garbage collect every time. Runtime.gc() is\n      // more likely to perform a gc.\n      Runtime.getRuntime().gc();\n      enqueueReferences();\n      System.runFinalization();\n    }\n\n    private void enqueueReferences() {\n      // Hack. We don't have a programmatic way to wait for the reference queue daemon to move\n      // references to the appropriate queues.\n      try {\n        Thread.sleep(100);\n      } catch (InterruptedException e) {\n        throw new AssertionError();\n      }\n    }\n  };\n\n  void runGc();\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/HahaHelper.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport com.squareup.haha.perflib.ArrayInstance;\nimport com.squareup.haha.perflib.ClassInstance;\nimport com.squareup.haha.perflib.ClassObj;\nimport com.squareup.haha.perflib.Instance;\nimport com.squareup.haha.perflib.Type;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.nio.charset.Charset;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport static com.squareup.leakcanary.Preconditions.checkNotNull;\nimport static java.util.Arrays.asList;\n\npublic final class HahaHelper {\n\n  private static final Set<String> WRAPPER_TYPES = new HashSet<>(\n      asList(Boolean.class.getName(), Character.class.getName(), Float.class.getName(),\n          Double.class.getName(), Byte.class.getName(), Short.class.getName(),\n          Integer.class.getName(), Long.class.getName()));\n\n  static String threadName(Instance holder) {\n    List<ClassInstance.FieldValue> values = classInstanceValues(holder);\n    Object nameField = fieldValue(values, \"name\");\n    if (nameField == null) {\n      // Sometimes we can't find the String at the expected memory address in the heap dump.\n      // See https://github.com/square/leakcanary/issues/417 .\n      return \"Thread name not available\";\n    }\n    return asString(nameField);\n  }\n\n  static boolean extendsThread(ClassObj clazz) {\n    boolean extendsThread = false;\n    ClassObj parentClass = clazz;\n    while (parentClass.getSuperClassObj() != null) {\n      if (parentClass.getClassName().equals(Thread.class.getName())) {\n        extendsThread = true;\n        break;\n      }\n      parentClass = parentClass.getSuperClassObj();\n    }\n    return extendsThread;\n  }\n\n  /**\n   * This returns a string representation of any object or value passed in.\n   */\n  static String valueAsString(Object value) {\n    String stringValue;\n    if (value == null) {\n      stringValue = \"null\";\n    } else if (value instanceof ClassInstance) {\n      String valueClassName = ((ClassInstance) value).getClassObj().getClassName();\n      if (valueClassName.equals(String.class.getName())) {\n        stringValue = '\"' + asString(value) + '\"';\n      } else {\n        stringValue = value.toString();\n      }\n    } else {\n      stringValue = value.toString();\n    }\n    return stringValue;\n  }\n\n  /** Given a string instance from the heap dump, this returns its actual string value. */\n  static String asString(Object stringObject) {\n    checkNotNull(stringObject, \"stringObject\");\n    Instance instance = (Instance) stringObject;\n    List<ClassInstance.FieldValue> values = classInstanceValues(instance);\n\n    Integer count = fieldValue(values, \"count\");\n    checkNotNull(count, \"count\");\n    if (count == 0) {\n      return \"\";\n    }\n\n    Object value = fieldValue(values, \"value\");\n    checkNotNull(value, \"value\");\n\n    Integer offset;\n    ArrayInstance array;\n    if (isCharArray(value)) {\n      array = (ArrayInstance) value;\n\n      offset = 0;\n      // < API 23\n      // As of Marshmallow, substrings no longer share their parent strings' char arrays\n      // eliminating the need for String.offset\n      // https://android-review.googlesource.com/#/c/83611/\n      if (hasField(values, \"offset\")) {\n        offset = fieldValue(values, \"offset\");\n        checkNotNull(offset, \"offset\");\n      }\n\n      char[] chars = array.asCharArray(offset, count);\n      return new String(chars);\n    } else if (isByteArray(value)) {\n      // In API 26, Strings are now internally represented as byte arrays.\n      array = (ArrayInstance) value;\n\n      // HACK - remove when HAHA's perflib is updated to https://goo.gl/Oe7ZwO.\n      try {\n        Method asRawByteArray =\n            ArrayInstance.class.getDeclaredMethod(\"asRawByteArray\", int.class, int.class);\n        asRawByteArray.setAccessible(true);\n        byte[] rawByteArray = (byte[]) asRawByteArray.invoke(array, 0, count);\n        return new String(rawByteArray, Charset.forName(\"UTF-8\"));\n      } catch (NoSuchMethodException e) {\n        throw new RuntimeException(e);\n      } catch (IllegalAccessException e) {\n        throw new RuntimeException(e);\n      } catch (InvocationTargetException e) {\n        throw new RuntimeException(e);\n      }\n    } else {\n      throw new UnsupportedOperationException(\"Could not find char array in \" + instance);\n    }\n  }\n\n  public static boolean isPrimitiveWrapper(Object value) {\n    if (!(value instanceof ClassInstance)) {\n      return false;\n    }\n    return WRAPPER_TYPES.contains(((ClassInstance) value).getClassObj().getClassName());\n  }\n\n  public static boolean isPrimitiveOrWrapperArray(Object value) {\n    if (!(value instanceof ArrayInstance)) {\n      return false;\n    }\n    ArrayInstance arrayInstance = (ArrayInstance) value;\n    if (arrayInstance.getArrayType() != Type.OBJECT) {\n      return true;\n    }\n    return WRAPPER_TYPES.contains(arrayInstance.getClassObj().getClassName());\n  }\n\n  private static boolean isCharArray(Object value) {\n    return value instanceof ArrayInstance && ((ArrayInstance) value).getArrayType() == Type.CHAR;\n  }\n\n  private static boolean isByteArray(Object value) {\n    return value instanceof ArrayInstance && ((ArrayInstance) value).getArrayType() == Type.BYTE;\n  }\n\n  static List<ClassInstance.FieldValue> classInstanceValues(Instance instance) {\n    ClassInstance classInstance = (ClassInstance) instance;\n    return classInstance.getValues();\n  }\n\n  @SuppressWarnings({ \"unchecked\", \"TypeParameterUnusedInFormals\" })\n  static <T> T fieldValue(List<ClassInstance.FieldValue> values, String fieldName) {\n    for (ClassInstance.FieldValue fieldValue : values) {\n      if (fieldValue.getField().getName().equals(fieldName)) {\n        return (T) fieldValue.getValue();\n      }\n    }\n    throw new IllegalArgumentException(\"Field \" + fieldName + \" does not exists\");\n  }\n\n  static boolean hasField(List<ClassInstance.FieldValue> values, String fieldName) {\n    for (ClassInstance.FieldValue fieldValue : values) {\n      if (fieldValue.getField().getName().equals(fieldName)) {\n        //noinspection unchecked\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private HahaHelper() {\n    throw new AssertionError();\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/HeapAnalyzer.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport androidx.annotation.NonNull;\n\nimport com.squareup.haha.perflib.ArrayInstance;\nimport com.squareup.haha.perflib.ClassInstance;\nimport com.squareup.haha.perflib.ClassObj;\nimport com.squareup.haha.perflib.Field;\nimport com.squareup.haha.perflib.HprofParser;\nimport com.squareup.haha.perflib.Instance;\nimport com.squareup.haha.perflib.RootObj;\nimport com.squareup.haha.perflib.RootType;\nimport com.squareup.haha.perflib.Snapshot;\nimport com.squareup.haha.perflib.Type;\nimport com.squareup.haha.perflib.io.HprofBuffer;\nimport com.squareup.haha.perflib.io.MemoryMappedFileBuffer;\n\nimport java.io.File;\nimport java.lang.reflect.Constructor;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport gnu.trove.THashMap;\nimport gnu.trove.TObjectProcedure;\n\nimport static android.os.Build.VERSION.SDK_INT;\nimport static android.os.Build.VERSION_CODES.N_MR1;\nimport static java.util.concurrent.TimeUnit.NANOSECONDS;\n\n/**\n * Analyzes heap dumps generated by a {@link RefWatcher} to verify if suspected leaks are real.\n */\npublic final class HeapAnalyzer {\n\n    private static final String ANONYMOUS_CLASS_NAME_PATTERN = \"^.+\\\\$\\\\d+$\";\n\n    private final ExcludedRefs excludedRefs;\n    private final AnalyzerProgressListener listener;\n    private final List<Reachability.Inspector> reachabilityInspectors;\n\n    /**\n     * @deprecated Use {@link #HeapAnalyzer(ExcludedRefs, AnalyzerProgressListener, List)}.\n     */\n    @Deprecated\n    public HeapAnalyzer(@NonNull ExcludedRefs excludedRefs) {\n        this(excludedRefs, AnalyzerProgressListener.NONE,\n                Collections.<Class<? extends Reachability.Inspector>>emptyList());\n    }\n\n    public HeapAnalyzer(@NonNull ExcludedRefs excludedRefs,\n                        @NonNull AnalyzerProgressListener listener,\n                        @NonNull List<Class<? extends Reachability.Inspector>> reachabilityInspectorClasses) {\n        this.excludedRefs = excludedRefs;\n        this.listener = listener;\n\n        this.reachabilityInspectors = new ArrayList<>();\n        for (Class<? extends Reachability.Inspector> reachabilityInspectorClass\n                : reachabilityInspectorClasses) {\n            try {\n                Constructor<? extends Reachability.Inspector> defaultConstructor =\n                        reachabilityInspectorClass.getDeclaredConstructor();\n                reachabilityInspectors.add(defaultConstructor.newInstance());\n            } catch (Exception e) {\n                throw new RuntimeException(e);\n            }\n        }\n    }\n\n    public @NonNull\n    List<TrackedReference> findTrackedReferences(@NonNull File heapDumpFile) {\n        if (!heapDumpFile.exists()) {\n            throw new IllegalArgumentException(\"File does not exist: \" + heapDumpFile);\n        }\n        try {\n            HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);\n            HprofParser parser = new HprofParser(buffer);\n            Snapshot snapshot = parser.parse();\n            deduplicateGcRoots(snapshot);\n\n            ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());\n            List<TrackedReference> references = new ArrayList<>();\n            for (Instance weakRef : refClass.getInstancesList()) {\n                List<ClassInstance.FieldValue> values = HahaHelper.classInstanceValues(weakRef);\n                String key = HahaHelper.asString(HahaHelper.fieldValue(values, \"key\"));\n                String name =\n                        HahaHelper.hasField(values, \"name\") ? HahaHelper.asString(HahaHelper.fieldValue(values, \"name\")) : \"(No name field)\";\n                Instance instance = HahaHelper.fieldValue(values, \"referent\");\n                if (instance != null) {\n                    String className = getClassName(instance);\n                    List<LeakReference> fields = describeFields(instance);\n                    references.add(new TrackedReference(key, name, className, fields));\n                }\n            }\n            return references;\n        } catch (Throwable e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * Calls {@link #checkForLeak(File, String, boolean)} with computeRetainedSize set to true.\n     *\n     * @deprecated Use {@link #checkForLeak(File, String, boolean)} instead.\n     */\n    @Deprecated\n    public @NonNull\n    AnalysisResult checkForLeak(@NonNull File heapDumpFile,\n                                @NonNull String referenceKey) {\n        return checkForLeak(heapDumpFile, referenceKey, true);\n    }\n\n    /**\n     * Searches the heap dump for a {@link KeyedWeakReference} instance with the corresponding key,\n     * and then computes the shortest strong reference path from that instance to the GC roots.\n     */\n    public @NonNull\n    AnalysisResult checkForLeak(@NonNull File heapDumpFile,\n                                @NonNull String referenceKey,\n                                boolean computeRetainedSize) {\n        long analysisStartNanoTime = System.nanoTime();\n\n        if (!heapDumpFile.exists()) {\n            Exception exception = new IllegalArgumentException(\"File does not exist: \" + heapDumpFile);\n            return AnalysisResult.failure(exception, since(analysisStartNanoTime));\n        }\n\n        try {\n            listener.onProgressUpdate(AnalyzerProgressListener.Step.READING_HEAP_DUMP_FILE);\n            HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);\n            HprofParser parser = new HprofParser(buffer);\n            listener.onProgressUpdate(AnalyzerProgressListener.Step.PARSING_HEAP_DUMP);\n            Snapshot snapshot = parser.parse();\n            listener.onProgressUpdate(AnalyzerProgressListener.Step.DEDUPLICATING_GC_ROOTS);\n            deduplicateGcRoots(snapshot);\n            listener.onProgressUpdate(AnalyzerProgressListener.Step.FINDING_LEAKING_REF);\n            Instance leakingRef = findLeakingReference(referenceKey, snapshot);\n\n            // False alarm, weak reference was cleared in between key check and heap dump.\n            if (leakingRef == null) {\n                String className = leakingRef.getClassObj().getClassName();\n                return AnalysisResult.noLeak(className, since(analysisStartNanoTime));\n            }\n            return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);\n        } catch (Throwable e) {\n            return AnalysisResult.failure(e, since(analysisStartNanoTime));\n        }\n    }\n\n    /**\n     * 以前检查一遍 避免出现dokit内部的异常\n     *\n     * @param heapDumpFile\n     * @param referenceKey\n     * @return\n     */\n    public Instance preCheckForLeak(@NonNull File heapDumpFile,\n                                    @NonNull String referenceKey) {\n\n        if (!heapDumpFile.exists()) {\n            return null;\n        }\n\n        Instance leakingRef;\n        try {\n            HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);\n            HprofParser parser = new HprofParser(buffer);\n            Snapshot snapshot = parser.parse();\n            deduplicateGcRoots(snapshot);\n            leakingRef = findLeakingReference(referenceKey, snapshot);\n        } catch (Exception e) {\n            return null;\n        }\n        return leakingRef;\n    }\n\n\n    /**\n     * Pruning duplicates reduces memory pressure from hprof bloat added in Marshmallow.\n     */\n    void deduplicateGcRoots(Snapshot snapshot) {\n        // THashMap has a smaller memory footprint than HashMap.\n        final THashMap<String, RootObj> uniqueRootMap = new THashMap<>();\n\n        final Collection<RootObj> gcRoots = snapshot.getGCRoots();\n        for (RootObj root : gcRoots) {\n            String key = generateRootKey(root);\n            if (!uniqueRootMap.containsKey(key)) {\n                uniqueRootMap.put(key, root);\n            }\n        }\n\n        // Repopulate snapshot with unique GC roots.\n        gcRoots.clear();\n        uniqueRootMap.forEach(new TObjectProcedure<String>() {\n            @Override\n            public boolean execute(String key) {\n                return gcRoots.add(uniqueRootMap.get(key));\n            }\n        });\n    }\n\n    private String generateRootKey(RootObj root) {\n        return String.format(\"%s@0x%08x\", root.getRootType().getName(), root.getId());\n    }\n\n    private Instance findLeakingReference(String key, Snapshot snapshot) {\n        ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());\n        if (refClass == null) {\n            throw new IllegalStateException(\n                    \"Could not find the \" + KeyedWeakReference.class.getName() + \" class in the heap dump.\");\n        }\n        List<String> keysFound = new ArrayList<>();\n        for (Instance instance : refClass.getInstancesList()) {\n            List<ClassInstance.FieldValue> values = HahaHelper.classInstanceValues(instance);\n            Object keyFieldValue = HahaHelper.fieldValue(values, \"key\");\n            if (keyFieldValue == null) {\n                keysFound.add(null);\n                continue;\n            }\n            String keyCandidate = HahaHelper.asString(keyFieldValue);\n            if (keyCandidate.equals(key)) {\n                return HahaHelper.fieldValue(values, \"referent\");\n            }\n            keysFound.add(keyCandidate);\n        }\n        throw new IllegalStateException(\n                \"Could not find weak reference with key \" + key + \" in \" + keysFound);\n    }\n\n    private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,\n                                         Instance leakingRef, boolean computeRetainedSize) {\n\n        listener.onProgressUpdate(AnalyzerProgressListener.Step.FINDING_SHORTEST_PATH);\n        ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);\n        ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);\n\n        String className = leakingRef.getClassObj().getClassName();\n\n        // False alarm, no strong reference path to GC Roots.\n        if (result.leakingNode == null) {\n            return AnalysisResult.noLeak(className, since(analysisStartNanoTime));\n        }\n\n        listener.onProgressUpdate(AnalyzerProgressListener.Step.BUILDING_LEAK_TRACE);\n        LeakTrace leakTrace = buildLeakTrace(result.leakingNode);\n\n        long retainedSize;\n        if (computeRetainedSize) {\n\n            listener.onProgressUpdate(AnalyzerProgressListener.Step.COMPUTING_DOMINATORS);\n            // Side effect: computes retained size.\n            snapshot.computeDominators();\n\n            Instance leakingInstance = result.leakingNode.instance;\n\n            retainedSize = leakingInstance.getTotalRetainedSize();\n\n            if (SDK_INT <= N_MR1) {\n                listener.onProgressUpdate(AnalyzerProgressListener.Step.COMPUTING_BITMAP_SIZE);\n                retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);\n            }\n        } else {\n            retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED;\n        }\n\n        return AnalysisResult.leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,\n                since(analysisStartNanoTime));\n    }\n\n    /**\n     * Bitmaps and bitmap byte arrays are sometimes held by native gc roots, so they aren't included\n     * in the retained size because their root dominator is a native gc root.\n     * To fix this, we check if the leaking instance is a dominator for each bitmap instance and then\n     * add the bitmap size.\n     * <p>\n     * From experience, we've found that bitmap created in code (Bitmap.createBitmap()) are correctly\n     * accounted for, however bitmaps set in layouts are not.\n     */\n    private long computeIgnoredBitmapRetainedSize(Snapshot snapshot, Instance leakingInstance) {\n        long bitmapRetainedSize = 0;\n        ClassObj bitmapClass = snapshot.findClass(\"android.graphics.Bitmap\");\n\n        for (Instance bitmapInstance : bitmapClass.getInstancesList()) {\n            if (isIgnoredDominator(leakingInstance, bitmapInstance)) {\n                ArrayInstance mBufferInstance = HahaHelper.fieldValue(HahaHelper.classInstanceValues(bitmapInstance), \"mBuffer\");\n                // Native bitmaps have mBuffer set to null. We sadly can't account for them.\n                if (mBufferInstance == null) {\n                    continue;\n                }\n                long bufferSize = mBufferInstance.getTotalRetainedSize();\n                long bitmapSize = bitmapInstance.getTotalRetainedSize();\n                // Sometimes the size of the buffer isn't accounted for in the bitmap retained size. Since\n                // the buffer is large, it's easy to detect by checking for bitmap size < buffer size.\n                if (bitmapSize < bufferSize) {\n                    bitmapSize += bufferSize;\n                }\n                bitmapRetainedSize += bitmapSize;\n            }\n        }\n        return bitmapRetainedSize;\n    }\n\n    private boolean isIgnoredDominator(Instance dominator, Instance instance) {\n        boolean foundNativeRoot = false;\n        while (true) {\n            Instance immediateDominator = instance.getImmediateDominator();\n            if (immediateDominator instanceof RootObj\n                    && ((RootObj) immediateDominator).getRootType() == RootType.UNKNOWN) {\n                // Ignore native roots\n                instance = instance.getNextInstanceToGcRoot();\n                foundNativeRoot = true;\n            } else {\n                instance = immediateDominator;\n            }\n            if (instance == null) {\n                return false;\n            }\n            if (instance == dominator) {\n                return foundNativeRoot;\n            }\n        }\n    }\n\n    private LeakTrace buildLeakTrace(LeakNode leakingNode) {\n        List<LeakTraceElement> elements = new ArrayList<>();\n        // We iterate from the leak to the GC root\n        LeakNode node = new LeakNode(null, null, leakingNode, null);\n        while (node != null) {\n            LeakTraceElement element = buildLeakElement(node);\n            if (element != null) {\n                elements.add(0, element);\n            }\n            node = node.parent;\n        }\n\n        List<Reachability> expectedReachability =\n                computeExpectedReachability(elements);\n\n        return new LeakTrace(elements, expectedReachability);\n    }\n\n    private List<Reachability> computeExpectedReachability(\n            List<LeakTraceElement> elements) {\n        int lastReachableElement = 0;\n        int lastElementIndex = elements.size() - 1;\n        int firstUnreachableElement = lastElementIndex;\n        // No need to inspect the first and last element. We know the first should be reachable (gc\n        // root) and the last should be unreachable (watched instance).\n        elementLoop:\n        for (int i = 1; i < lastElementIndex; i++) {\n            LeakTraceElement element = elements.get(i);\n\n            for (Reachability.Inspector reachabilityInspector : reachabilityInspectors) {\n                Reachability reachability = reachabilityInspector.expectedReachability(element);\n                if (reachability == Reachability.REACHABLE) {\n                    lastReachableElement = i;\n                    break;\n                } else if (reachability == Reachability.UNREACHABLE) {\n                    firstUnreachableElement = i;\n                    break elementLoop;\n                }\n            }\n        }\n\n        List<Reachability> expectedReachability = new ArrayList<>();\n        for (int i = 0; i < elements.size(); i++) {\n            Reachability status;\n            if (i <= lastReachableElement) {\n                status = Reachability.REACHABLE;\n            } else if (i >= firstUnreachableElement) {\n                status = Reachability.UNREACHABLE;\n            } else {\n                status = Reachability.UNKNOWN;\n            }\n            expectedReachability.add(status);\n        }\n        return expectedReachability;\n    }\n\n    private LeakTraceElement buildLeakElement(LeakNode node) {\n        if (node.parent == null) {\n            // Ignore any root node.\n            return null;\n        }\n        Instance holder = node.parent.instance;\n\n        if (holder instanceof RootObj) {\n            return null;\n        }\n        LeakTraceElement.Holder holderType;\n        String className;\n        String extra = null;\n        List<LeakReference> leakReferences = describeFields(holder);\n\n        className = getClassName(holder);\n\n        List<String> classHierarchy = new ArrayList<>();\n        classHierarchy.add(className);\n        String rootClassName = Object.class.getName();\n        if (holder instanceof ClassInstance) {\n            ClassObj classObj = holder.getClassObj();\n            while (!(classObj = classObj.getSuperClassObj()).getClassName().equals(rootClassName)) {\n                classHierarchy.add(classObj.getClassName());\n            }\n        }\n\n        if (holder instanceof ClassObj) {\n            holderType = LeakTraceElement.Holder.CLASS;\n        } else if (holder instanceof ArrayInstance) {\n            holderType = LeakTraceElement.Holder.ARRAY;\n        } else {\n            ClassObj classObj = holder.getClassObj();\n            if (HahaHelper.extendsThread(classObj)) {\n                holderType = LeakTraceElement.Holder.THREAD;\n                String threadName = HahaHelper.threadName(holder);\n                extra = \"(named '\" + threadName + \"')\";\n            } else if (className.matches(ANONYMOUS_CLASS_NAME_PATTERN)) {\n                String parentClassName = classObj.getSuperClassObj().getClassName();\n                if (rootClassName.equals(parentClassName)) {\n                    holderType = LeakTraceElement.Holder.OBJECT;\n                    try {\n                        // This is an anonymous class implementing an interface. The API does not give access\n                        // to the interfaces implemented by the class. We check if it's in the class path and\n                        // use that instead.\n                        Class<?> actualClass = Class.forName(classObj.getClassName());\n                        Class<?>[] interfaces = actualClass.getInterfaces();\n                        if (interfaces.length > 0) {\n                            Class<?> implementedInterface = interfaces[0];\n                            extra = \"(anonymous implementation of \" + implementedInterface.getName() + \")\";\n                        } else {\n                            extra = \"(anonymous subclass of java.lang.Object)\";\n                        }\n                    } catch (ClassNotFoundException ignored) {\n                    }\n                } else {\n                    holderType = LeakTraceElement.Holder.OBJECT;\n                    // Makes it easier to figure out which anonymous class we're looking at.\n                    extra = \"(anonymous subclass of \" + parentClassName + \")\";\n                }\n            } else {\n                holderType = LeakTraceElement.Holder.OBJECT;\n            }\n        }\n        return new LeakTraceElement(node.leakReference, holderType, classHierarchy, extra,\n                node.exclusion, leakReferences);\n    }\n\n    private List<LeakReference> describeFields(Instance instance) {\n        List<LeakReference> leakReferences = new ArrayList<>();\n        if (instance instanceof ClassObj) {\n            ClassObj classObj = (ClassObj) instance;\n            for (Map.Entry<Field, Object> entry : classObj.getStaticFieldValues().entrySet()) {\n                String name = entry.getKey().getName();\n                String stringValue = HahaHelper.valueAsString(entry.getValue());\n                leakReferences.add(new LeakReference(LeakTraceElement.Type.STATIC_FIELD, name, stringValue));\n            }\n        } else if (instance instanceof ArrayInstance) {\n            ArrayInstance arrayInstance = (ArrayInstance) instance;\n            if (arrayInstance.getArrayType() == Type.OBJECT) {\n                Object[] values = arrayInstance.getValues();\n                for (int i = 0; i < values.length; i++) {\n                    String name = Integer.toString(i);\n                    String stringValue = HahaHelper.valueAsString(values[i]);\n                    leakReferences.add(new LeakReference(LeakTraceElement.Type.ARRAY_ENTRY, name, stringValue));\n                }\n            }\n        } else {\n            ClassObj classObj = instance.getClassObj();\n            for (Map.Entry<Field, Object> entry : classObj.getStaticFieldValues().entrySet()) {\n                String name = entry.getKey().getName();\n                String stringValue = HahaHelper.valueAsString(entry.getValue());\n                leakReferences.add(new LeakReference(LeakTraceElement.Type.STATIC_FIELD, name, stringValue));\n            }\n            ClassInstance classInstance = (ClassInstance) instance;\n            for (ClassInstance.FieldValue field : classInstance.getValues()) {\n                String name = field.getField().getName();\n                String stringValue = HahaHelper.valueAsString(field.getValue());\n                leakReferences.add(new LeakReference(LeakTraceElement.Type.INSTANCE_FIELD, name, stringValue));\n            }\n        }\n        return leakReferences;\n    }\n\n    private String getClassName(Instance instance) {\n        String className;\n        if (instance instanceof ClassObj) {\n            ClassObj classObj = (ClassObj) instance;\n            className = classObj.getClassName();\n        } else if (instance instanceof ArrayInstance) {\n            ArrayInstance arrayInstance = (ArrayInstance) instance;\n            className = arrayInstance.getClassObj().getClassName();\n        } else {\n            ClassObj classObj = instance.getClassObj();\n            className = classObj.getClassName();\n        }\n        return className;\n    }\n\n    private long since(long analysisStartNanoTime) {\n        return NANOSECONDS.toMillis(System.nanoTime() - analysisStartNanoTime);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/HeapDump.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport java.io.File;\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static java.util.Collections.unmodifiableList;\n\n/** Data structure holding information about a heap dump. */\npublic final class HeapDump implements Serializable {\n\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /** Receives a heap dump to analyze. */\n  public interface Listener {\n    Listener NONE = new Listener() {\n      @Override public void analyze(HeapDump heapDump) {\n      }\n    };\n\n    void analyze(HeapDump heapDump);\n  }\n\n  /** The heap dump file, which you might want to upload somewhere. */\n  public final File heapDumpFile;\n\n  /**\n   * Key associated to the {@link KeyedWeakReference} used to detect the memory leak.\n   * When analyzing a heap dump, search for all {@link KeyedWeakReference} instances, then open\n   * the one that has its \"key\" field set to this value. Its \"referent\" field contains the\n   * leaking object. Computing the shortest path to GC roots on that leaking object should enable\n   * you to figure out the cause of the leak.\n   */\n  public final String referenceKey;\n\n  /**\n   * User defined name to help identify the leaking instance.\n   */\n  public final String referenceName;\n\n  /** References that should be ignored when analyzing this heap dump. */\n  public final ExcludedRefs excludedRefs;\n\n  /** Time from the request to watch the reference until the GC was triggered. */\n  public final long watchDurationMs;\n  public final long gcDurationMs;\n  public final long heapDumpDurationMs;\n  public final boolean computeRetainedHeapSize;\n  public final List<Class<? extends Reachability.Inspector>> reachabilityInspectorClasses;\n\n  /**\n   * Calls {@link #HeapDump(Builder)} with computeRetainedHeapSize set to true.\n   *\n   * @deprecated Use {@link #HeapDump(Builder)}  instead.\n   */\n  @Deprecated\n  public HeapDump(File heapDumpFile, String referenceKey, String referenceName,\n                  ExcludedRefs excludedRefs, long watchDurationMs, long gcDurationMs, long heapDumpDurationMs) {\n    this(new Builder().heapDumpFile(heapDumpFile)\n        .referenceKey(referenceKey)\n        .referenceName(referenceName)\n        .excludedRefs(excludedRefs)\n        .computeRetainedHeapSize(true)\n        .watchDurationMs(watchDurationMs)\n        .gcDurationMs(gcDurationMs)\n        .heapDumpDurationMs(heapDumpDurationMs));\n  }\n\n  HeapDump(Builder builder) {\n    this.heapDumpFile = builder.heapDumpFile;\n    this.referenceKey = builder.referenceKey;\n    this.referenceName = builder.referenceName;\n    this.excludedRefs = builder.excludedRefs;\n    this.computeRetainedHeapSize = builder.computeRetainedHeapSize;\n    this.watchDurationMs = builder.watchDurationMs;\n    this.gcDurationMs = builder.gcDurationMs;\n    this.heapDumpDurationMs = builder.heapDumpDurationMs;\n    this.reachabilityInspectorClasses = builder.reachabilityInspectorClasses;\n  }\n\n  public Builder buildUpon() {\n    return new Builder(this);\n  }\n\n  public static final class Builder {\n    File heapDumpFile;\n    String referenceKey;\n    String referenceName;\n    ExcludedRefs excludedRefs;\n    long watchDurationMs;\n    long gcDurationMs;\n    long heapDumpDurationMs;\n    boolean computeRetainedHeapSize;\n    List<Class<? extends Reachability.Inspector>> reachabilityInspectorClasses;\n\n    Builder() {\n      this.heapDumpFile = null;\n      this.referenceKey = null;\n      referenceName = \"\";\n      excludedRefs = null;\n      watchDurationMs = 0;\n      gcDurationMs = 0;\n      heapDumpDurationMs = 0;\n      computeRetainedHeapSize = false;\n      reachabilityInspectorClasses = null;\n    }\n\n    Builder(HeapDump heapDump) {\n      this.heapDumpFile = heapDump.heapDumpFile;\n      this.referenceKey = heapDump.referenceKey;\n      this.referenceName = heapDump.referenceName;\n      this.excludedRefs = heapDump.excludedRefs;\n      this.computeRetainedHeapSize = heapDump.computeRetainedHeapSize;\n      this.watchDurationMs = heapDump.watchDurationMs;\n      this.gcDurationMs = heapDump.gcDurationMs;\n      this.heapDumpDurationMs = heapDump.heapDumpDurationMs;\n      this.reachabilityInspectorClasses = heapDump.reachabilityInspectorClasses;\n    }\n\n    public Builder heapDumpFile(File heapDumpFile) {\n      this.heapDumpFile = Preconditions.checkNotNull(heapDumpFile, \"heapDumpFile\");\n      return this;\n    }\n\n    public Builder referenceKey(String referenceKey) {\n      this.referenceKey = Preconditions.checkNotNull(referenceKey, \"referenceKey\");\n      return this;\n    }\n\n    public Builder referenceName(String referenceName) {\n      this.referenceName = Preconditions.checkNotNull(referenceName, \"referenceName\");\n      return this;\n    }\n\n    public Builder excludedRefs(ExcludedRefs excludedRefs) {\n      this.excludedRefs = Preconditions.checkNotNull(excludedRefs, \"excludedRefs\");\n      return this;\n    }\n\n    public Builder watchDurationMs(long watchDurationMs) {\n      this.watchDurationMs = watchDurationMs;\n      return this;\n    }\n\n    public Builder gcDurationMs(long gcDurationMs) {\n      this.gcDurationMs = gcDurationMs;\n      return this;\n    }\n\n    public Builder heapDumpDurationMs(long heapDumpDurationMs) {\n      this.heapDumpDurationMs = heapDumpDurationMs;\n      return this;\n    }\n\n    public Builder computeRetainedHeapSize(boolean computeRetainedHeapSize) {\n      this.computeRetainedHeapSize = computeRetainedHeapSize;\n      return this;\n    }\n\n    public Builder reachabilityInspectorClasses(\n        List<Class<? extends Reachability.Inspector>> reachabilityInspectorClasses) {\n      Preconditions.checkNotNull(reachabilityInspectorClasses, \"reachabilityInspectorClasses\");\n      this.reachabilityInspectorClasses =\n          unmodifiableList(new ArrayList<>(reachabilityInspectorClasses));\n      return this;\n    }\n\n    public HeapDump build() {\n      Preconditions.checkNotNull(excludedRefs, \"excludedRefs\");\n      Preconditions.checkNotNull(heapDumpFile, \"heapDumpFile\");\n      Preconditions.checkNotNull(referenceKey, \"referenceKey\");\n      Preconditions.checkNotNull(reachabilityInspectorClasses, \"reachabilityInspectorClasses\");\n      return new HeapDump(this);\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/HeapDumper.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport java.io.File;\n\n/** Dumps the heap into a file. */\npublic interface HeapDumper {\n  HeapDumper NONE = new HeapDumper() {\n    @Override public File dumpHeap() {\n      return RETRY_LATER;\n    }\n  };\n\n  File RETRY_LATER = null;\n\n  /**\n   * @return a {@link File} referencing the dumped heap, or {@link #RETRY_LATER} if the heap could\n   * not be dumped.\n   */\n  File dumpHeap();\n}"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/InstrumentationLeakDetector.java",
    "content": "/*\n * Copyright (C) 2018 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport android.app.Application;\nimport android.app.Instrumentation;\nimport android.content.Context;\nimport android.os.Debug;\nimport android.os.SystemClock;\n\n\nimport androidx.annotation.NonNull;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\nimport static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;\n\n/**\n * <p>{@link InstrumentationLeakDetector} can be used to detect memory leaks in instrumentation\n * tests.\n *\n * <p>To use it, you need to:\n * <ul>\n * <li>Install a custom RefWatcher that will not trigger heapdumps while the tests run.</li>\n * <li>Add an instrumentation test listener (a {@link RunListener}) that will invoke\n * {@link #detectLeaks()}</li>\n * </ul>\n *\n * <h3>Installing the instrumentation RefWatcher</h3>\n *\n * <p>For {@link #detectLeaks()} to work correctly, the {@link RefWatcher} must keep track of\n * references but not trigger any heap dump until this {@link #detectLeaks()} runs, otherwise an\n * analysis in progress might prevent this listener from performing its own analysis.\n *\n * <p>Create and install the {@link RefWatcher} instance using\n * {@link #instrumentationRefWatcher(Application)} instead of\n * {@link LeakCanary#install(Application)} or {@link LeakCanary#refWatcher(Context)}.\n * <pre><code>\n * public class InstrumentationExampleApplication extends ExampleApplication {\n *   {@literal @}Override protected void setupLeakCanary() {\n *     InstrumentationLeakDetector.instrumentationRefWatcher(this)\n *         .buildAndInstall();\n *   }\n * }\n * </code></pre>\n *\n * <h3>Add an intrumentation test listener</h3>\n *\n * <p>LeakCanary provides {@link FailTestOnLeakRunListener}, but you should feel free to implement\n * your own {@link RunListener} and call {@link #detectLeaks()} directly if you need a more custom\n * behavior (for instance running it only once per test suite, or reporting to a backend).</p>\n *\n * <p>All you need to do is add the following to the defaultConfig of your build.gradle:\n *\n * <pre><code>testInstrumentationRunnerArgument \"listener\", \"FailTestOnLeakRunListener\"</code></pre>\n *\n * <p>Then you can run your instrumentation tests via Gradle as usually, and they will fail when\n * a memory leak is detected:\n *\n * <pre><code>./gradlew leakcanary-sample:connectedCheck</code></pre>\n *\n * <p>If instead you want to run UI tests via adb, add a <em>listener</em> execution argument to\n * your command line for running the UI tests:\n * <code>-e listener FailTestOnLeakRunListener</code>. The full command line\n * should look something like this:\n * <pre><code>adb shell am instrument \\\\\n * -w com.android.foo/android.support.test.runner.AndroidJUnitRunner \\\\\n * -e listener FailTestOnLeakRunListener\n * </code></pre>\n *\n * <h3>Rationale</h3>\n * Instead of using the {@link FailTestOnLeakRunListener}, one could simply enable LeakCanary in\n * instrumentation tests.\n *\n * <p>This approach would have two disadvantages:\n * <ul>\n * <li>Heap dumps freeze the VM, and the leak analysis is IO and CPU heavy. This can slow down\n * the test and introduce flakiness</li>\n * <li>The leak analysis is asynchronous by default, and happens in a separate process. This means\n * the tests could finish and the process die before the analysis is finished.</li>\n * </ul>\n *\n * <p>The approach taken here is to collect all references to watch as you run the test, but not\n * do any heap dump during the test. Then, at the end, if any of the watched objects is still in\n * memory we dump the heap and perform a blocking analysis. There is only one heap dump performed,\n * no matter the number of objects leaking, and then we iterate on the leaking references in the\n * heap dump and provide all result in a {@link InstrumentationLeakResults}.\n */\npublic final class InstrumentationLeakDetector {\n\n  /**\n   * Returns a new {@link} AndroidRefWatcherBuilder that will create a {@link RefWatcher} suitable\n   * for instrumentation tests. This {@link RefWatcher} will never trigger a heap dump. This should\n   * be installed from the test application class, and should be used in combination with a\n   * {@link RunListener} that calls {@link #detectLeaks()}, for instance\n   * {@link FailTestOnLeakRunListener}.\n   */\n  public static @NonNull\n  AndroidRefWatcherBuilder instrumentationRefWatcher(\n      @NonNull Application application) {\n    return LeakCanary.refWatcher(application)\n        .watchExecutor(new WatchExecutor() {\n          // Storing weak refs to ensure they make it to the queue.\n          final List<Retryable> trackedReferences = new CopyOnWriteArrayList<>();\n\n          @Override\n          public void execute(Retryable retryable) {\n            trackedReferences.add(retryable);\n          }\n        });\n  }\n\n  public @NonNull InstrumentationLeakResults detectLeaks() {\n    Instrumentation instrumentation = getInstrumentation();\n    Context context = instrumentation.getTargetContext();\n    RefWatcher refWatcher = LeakCanary.installedRefWatcher();\n    Set<String> retainedKeys = refWatcher.getRetainedKeys();\n\n    if (refWatcher.isEmpty()) {\n      return InstrumentationLeakResults.NONE;\n    }\n\n    instrumentation.waitForIdleSync();\n    if (refWatcher.isEmpty()) {\n      return InstrumentationLeakResults.NONE;\n    }\n\n    GcTrigger.DEFAULT.runGc();\n    if (refWatcher.isEmpty()) {\n      return InstrumentationLeakResults.NONE;\n    }\n\n    // Waiting for any delayed UI post (e.g. scroll) to clear. This shouldn't be needed, but\n    // Android simply has way too many delayed posts that aren't canceled when views are detached.\n    SystemClock.sleep(2000);\n\n    if (refWatcher.isEmpty()) {\n      return InstrumentationLeakResults.NONE;\n    }\n\n    // Aaand we wait some more.\n    // 4 seconds (2+2) is greater than the 3 seconds delay for\n    // FINISH_TOKEN in android.widget.Filter\n    SystemClock.sleep(2000);\n    GcTrigger.DEFAULT.runGc();\n\n    if (refWatcher.isEmpty()) {\n      return InstrumentationLeakResults.NONE;\n    }\n\n    // We're always reusing the same file since we only execute this once at a time.\n    File heapDumpFile = new File(context.getFilesDir(), \"instrumentation_tests_heapdump.hprof\");\n    try {\n      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());\n    } catch (Exception e) {\n      CanaryLog.d(e, \"Could not dump heap\");\n      return InstrumentationLeakResults.NONE;\n    }\n\n    HeapDump.Builder heapDumpBuilder = refWatcher.getHeapDumpBuilder();\n    HeapAnalyzer heapAnalyzer =\n        new HeapAnalyzer(heapDumpBuilder.excludedRefs, AnalyzerProgressListener.NONE,\n            heapDumpBuilder.reachabilityInspectorClasses);\n\n    List<TrackedReference> trackedReferences = heapAnalyzer.findTrackedReferences(heapDumpFile);\n\n    List<InstrumentationLeakResults.Result> detectedLeaks = new ArrayList<>();\n    List<InstrumentationLeakResults.Result> excludedLeaks = new ArrayList<>();\n    List<InstrumentationLeakResults.Result> failures = new ArrayList<>();\n\n    for (TrackedReference trackedReference : trackedReferences) {\n      // Ignore any Weak Reference that this test does not care about.\n      if (!retainedKeys.contains(trackedReference.key)) {\n        continue;\n      }\n\n      HeapDump heapDump = HeapDump.builder()\n          .heapDumpFile(heapDumpFile)\n          .referenceKey(trackedReference.key)\n          .referenceName(trackedReference.name)\n          .excludedRefs(heapDumpBuilder.excludedRefs)\n          .reachabilityInspectorClasses(heapDumpBuilder.reachabilityInspectorClasses)\n          .build();\n\n      AnalysisResult analysisResult =\n          heapAnalyzer.checkForLeak(heapDumpFile, trackedReference.key, false);\n\n      InstrumentationLeakResults.Result leakResult =\n          new InstrumentationLeakResults.Result(heapDump, analysisResult);\n\n      if (analysisResult.leakFound) {\n        if (!analysisResult.excludedLeak) {\n          detectedLeaks.add(leakResult);\n        } else {\n          excludedLeaks.add(leakResult);\n        }\n      } else if (analysisResult.failure != null) {\n        failures.add(leakResult);\n      }\n    }\n\n    CanaryLog.d(\"Found %d proper leaks, %d excluded leaks and %d leak analysis failures\",\n        detectedLeaks.size(),\n        excludedLeaks.size(),\n        failures.size());\n\n    return new InstrumentationLeakResults(detectedLeaks, excludedLeaks, failures);\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/InstrumentationLeakResults.java",
    "content": "/*\n * Copyright (C) 2018 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\n\nimport androidx.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static java.util.Collections.unmodifiableList;\n\npublic final class InstrumentationLeakResults {\n\n  @NonNull public static final InstrumentationLeakResults NONE =\n      new InstrumentationLeakResults(Collections.<Result>emptyList(),\n          Collections.<Result>emptyList(), Collections.<Result>emptyList());\n\n  /** Proper leaks found during instrumentation tests. */\n  @NonNull public final List<Result> detectedLeaks;\n\n  /**\n   * Excluded leaks found during instrumentation tests, based on {@link\n   * RefWatcherBuilder#excludedRefs}\n   */\n  @NonNull public final List<Result> excludedLeaks;\n\n  /**\n   * Leak analysis failures that happened when we tried to detect leaks.\n   */\n  @NonNull public final List<Result> failures;\n\n  public InstrumentationLeakResults(@NonNull List<Result> detectedLeaks,\n                                    @NonNull List<Result> excludedLeaks, @NonNull List<Result> failures) {\n    this.detectedLeaks = unmodifiableList(new ArrayList<>(detectedLeaks));\n    this.excludedLeaks = unmodifiableList(new ArrayList<>(excludedLeaks));\n    this.failures = unmodifiableList(new ArrayList<>(failures));\n  }\n\n  public static final class Result {\n    @NonNull\n    public final HeapDump heapDump;\n    @NonNull public final AnalysisResult analysisResult;\n\n    public Result(@NonNull HeapDump heapDump, @NonNull AnalysisResult analysisResult) {\n      this.heapDump = heapDump;\n      this.analysisResult = analysisResult;\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/KeyedWeakReference.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport java.lang.ref.ReferenceQueue;\nimport java.lang.ref.WeakReference;\n\n/** @see {@link HeapDump#referenceKey}. */\nfinal class KeyedWeakReference extends WeakReference<Object> {\n  public final String key;\n  public final String name;\n\n  KeyedWeakReference(Object referent, String key, String name,\n      ReferenceQueue<Object> referenceQueue) {\n    super(Preconditions.checkNotNull(referent, \"referent\"), Preconditions.checkNotNull(referenceQueue, \"referenceQueue\"));\n    this.key = Preconditions.checkNotNull(key, \"key\");\n    this.name = Preconditions.checkNotNull(name, \"name\");\n  }\n}"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/LeakCanary.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.os.Build;\nimport androidx.annotation.NonNull;\nimport android.util.Log;\n\nimport com.squareup.leakcanary.internal.DisplayLeakActivity;\nimport com.squareup.leakcanary.internal.HeapAnalyzerService;\nimport com.squareup.leakcanary.internal.LeakCanaryInternals;\n\nimport static android.text.format.Formatter.formatShortFileSize;\n\npublic final class LeakCanary {\n\n    /**\n     * Creates a {@link RefWatcher} that works out of the box, and starts watching activity\n     * references (on ICS+).\n     */\n    public static @NonNull\n    RefWatcher install(@NonNull Application application) {\n        //DisplayLeakService 服务的名称\n        return refWatcher(application).listenerServiceClass(UploadLeakService.class)\n                .excludedRefs(AndroidExcludedRefs.createAppDefaults()\n                        .build())\n                .buildAndInstall();\n    }\n\n    /**\n     * Returns the {@link RefWatcher} installed via\n     * {@link AndroidRefWatcherBuilder#buildAndInstall()}, and {@link RefWatcher#DISABLED} is no\n     * {@link RefWatcher} has been installed.\n     */\n    public static @NonNull\n    RefWatcher installedRefWatcher() {\n        RefWatcher refWatcher = LeakCanaryInternals.installedRefWatcher;\n        if (refWatcher == null) {\n            return RefWatcher.DISABLED;\n        }\n        return refWatcher;\n    }\n\n    public static @NonNull\n    AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {\n        return new AndroidRefWatcherBuilder(context);\n    }\n\n    /**\n     * Blocking inter process call that enables the {@link DisplayLeakActivity}. When you first\n     * install the app, {@link DisplayLeakActivity} is enabled by default if LeakCanary is configured\n     * to use {@link DisplayLeakService}. You can call this method to enable\n     * {@link DisplayLeakActivity} manually.\n     */\n    public static void enableDisplayLeakActivity(@NonNull Context context) {\n        LeakCanaryInternals.setEnabledBlocking(context, DisplayLeakActivity.class, true);\n    }\n\n    /**\n     * @deprecated Use {@link #setLeakDirectoryProvider(LeakDirectoryProvider)} instead.\n     */\n    @Deprecated\n    public static void setDisplayLeakActivityDirectoryProvider(\n            @NonNull LeakDirectoryProvider leakDirectoryProvider) {\n        setLeakDirectoryProvider(leakDirectoryProvider);\n    }\n\n    /**\n     * Used to customize the location for the storage of heap dumps. The default implementation is\n     * {@link DefaultLeakDirectoryProvider}.\n     *\n     * @throws IllegalStateException if a LeakDirectoryProvider has already been set, including\n     *                               if the default has been automatically set when installing the ref watcher.\n     */\n    public static void setLeakDirectoryProvider(\n            @NonNull LeakDirectoryProvider leakDirectoryProvider) {\n        LeakCanaryInternals.setLeakDirectoryProvider(leakDirectoryProvider);\n    }\n\n    /**\n     * Returns a string representation of the result of a heap analysis.\n     */\n    public static @NonNull\n    String leakInfo(@NonNull Context context,\n                    @NonNull HeapDump heapDump,\n                    @NonNull AnalysisResult result,\n                    boolean detailed) {\n        PackageManager packageManager = context.getPackageManager();\n        String packageName = context.getPackageName();\n        PackageInfo packageInfo;\n        try {\n            packageInfo = packageManager.getPackageInfo(packageName, 0);\n        } catch (PackageManager.NameNotFoundException e) {\n            throw new RuntimeException(e);\n        }\n        String versionName = packageInfo.versionName;\n        int versionCode = packageInfo.versionCode;\n        String info = \"In \" + packageName + \":\" + versionName + \":\" + versionCode + \".\\n\";\n        String detailedString = \"\";\n        if (result.leakFound) {\n            if (result.excludedLeak) {\n                info += \"* EXCLUDED LEAK.\\n\";\n            }\n            info += \"* \" + result.className;\n            if (!heapDump.referenceName.equals(\"\")) {\n                info += \" (\" + heapDump.referenceName + \")\";\n            }\n            info += \" has leaked:\\n\" + result.leakTrace.toString() + \"\\n\";\n            if (result.retainedHeapSize != AnalysisResult.RETAINED_HEAP_SKIPPED) {\n                info += \"* Retaining: \" + formatShortFileSize(context, result.retainedHeapSize) + \".\\n\";\n            }\n            if (detailed) {\n                detailedString = \"\\n* Details:\\n\" + result.leakTrace.toDetailedString();\n            }\n        } else if (result.failure != null) {\n            // We duplicate the library version & Sha information because bug reports often only contain\n            // the stacktrace.\n            info += \"* FAILURE in \" + BuildConfig.LEAKCANARY_LIBRARY_VERSION + \" \" + BuildConfig.GIT_SHA + \":\" + Log.getStackTraceString(\n                    result.failure) + \"\\n\";\n        } else {\n            info += \"* NO LEAK FOUND.\\n\\n\";\n        }\n        if (detailed) {\n            detailedString += \"* Excluded Refs:\\n\" + heapDump.excludedRefs;\n        }\n\n        info += \"* Reference Key: \"\n                + heapDump.referenceKey\n                + \"\\n\"\n                + \"* Device: \"\n                + Build.MANUFACTURER\n                + \" \"\n                + Build.BRAND\n                + \" \"\n                + Build.MODEL\n                + \" \"\n                + Build.PRODUCT\n                + \"\\n\"\n                + \"* Android Version: \"\n                + Build.VERSION.RELEASE\n                + \" API: \"\n                + Build.VERSION.SDK_INT\n                + \" LeakCanary: \"\n                + BuildConfig.LEAKCANARY_LIBRARY_VERSION\n                + \" \"\n                + BuildConfig.GIT_SHA\n                + \"\\n\"\n                + \"* Durations: watch=\"\n                + heapDump.watchDurationMs\n                + \"ms, gc=\"\n                + heapDump.gcDurationMs\n                + \"ms, heap dump=\"\n                + heapDump.heapDumpDurationMs\n                + \"ms, analysis=\"\n                + result.analysisDurationMs\n                + \"ms\"\n                + \"\\n\"\n                + detailedString;\n\n        return info;\n    }\n\n    /**\n     * Whether the current process is the process running the {@link HeapAnalyzerService}, which is\n     * a different process than the normal app process.\n     */\n    public static boolean isInAnalyzerProcess(@NonNull Context context) {\n        Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;\n        // This only needs to be computed once per process.\n        if (isInAnalyzerProcess == null) {\n            isInAnalyzerProcess = LeakCanaryInternals.isInServiceProcess(context, HeapAnalyzerService.class);\n            LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;\n        }\n        return isInAnalyzerProcess;\n    }\n\n    private LeakCanary() {\n        throw new AssertionError();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/LeakDirectoryProvider.java",
    "content": "/*\n * Copyright (C) 2016 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.io.File;\nimport java.io.FilenameFilter;\nimport java.util.List;\n\n/**\n * Provides access to where heap dumps and analysis results will be stored.\n * When using your own implementation, you should also call {@link\n * LeakCanary#setDisplayLeakActivityDirectoryProvider(LeakDirectoryProvider)} to ensure the\n * provided activity is able to display the leaks.\n */\npublic interface LeakDirectoryProvider {\n\n  @NonNull\n  List<File> listFiles(@NonNull FilenameFilter filter);\n\n  /**\n   * @return {@link HeapDumper#RETRY_LATER} if a new heap dump file could not be created.\n   */\n  @Nullable\n  File newHeapDumpFile();\n\n  /**\n   * Removes all heap dumps and analysis results, except for heap dumps that haven't been\n   * analyzed yet.\n   */\n  void clearLeakDirectory();\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/LeakNode.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport com.squareup.haha.perflib.Instance;\n\nfinal class LeakNode {\n  /** May be null. */\n  final Exclusion exclusion;\n  final Instance instance;\n  final LeakNode parent;\n  final LeakReference leakReference;\n\n  LeakNode(Exclusion exclusion, Instance instance, LeakNode parent, LeakReference leakReference) {\n    this.exclusion = exclusion;\n    this.instance = instance;\n    this.parent = parent;\n    this.leakReference = leakReference;\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/LeakReference.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport java.io.Serializable;\n\n/**\n * A single field in a {@link LeakTraceElement}.\n */\npublic final class LeakReference implements Serializable {\n\n  public final LeakTraceElement.Type type;\n  public final String name;\n  public final String value;\n\n  public LeakReference(LeakTraceElement.Type type, String name, String value) {\n    this.type = type;\n    this.name = name;\n    this.value = value;\n  }\n\n  public String getDisplayName() {\n    switch (type) {\n      case ARRAY_ENTRY:\n        return \"[\" + name + \"]\";\n      case STATIC_FIELD:\n      case INSTANCE_FIELD:\n        return name;\n      case LOCAL:\n        return \"<Java Local>\";\n      default:\n        throw new IllegalStateException(\n            \"Unexpected type \" + type + \" name = \" + name + \" value = \" + value);\n    }\n  }\n\n  @Override public String toString() {\n    switch (type) {\n      case ARRAY_ENTRY:\n      case INSTANCE_FIELD:\n        return getDisplayName() + \" = \" + value;\n      case STATIC_FIELD:\n        return \"static \" + getDisplayName() + \" = \" + value;\n      case LOCAL:\n        return getDisplayName();\n      default:\n        throw new IllegalStateException(\n            \"Unexpected type \" + type + \" name = \" + name + \" value = \" + value);\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/LeakTrace.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\n\nimport androidx.annotation.NonNull;\n\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * A chain of references that constitute the shortest strong reference path from a leaking instance\n * to the GC roots. Fixing the leak usually means breaking one of the references in that chain.\n */\npublic final class LeakTrace implements Serializable {\n\n  @NonNull\n  public final List<LeakTraceElement> elements;\n  @NonNull public final List<Reachability> expectedReachability;\n\n  LeakTrace(List<LeakTraceElement> elements, List<Reachability> expectedReachability) {\n    this.elements = elements;\n    this.expectedReachability = expectedReachability;\n  }\n\n  @Override public String toString() {\n    StringBuilder sb = new StringBuilder();\n    for (int i = 0; i < elements.size(); i++) {\n      LeakTraceElement element = elements.get(i);\n      sb.append(\"* \");\n      if (i != 0) {\n        sb.append(\"↳ \");\n      }\n      boolean maybeLeakCause = false;\n      Reachability currentReachability = expectedReachability.get(i);\n      if (currentReachability == Reachability.UNKNOWN) {\n        maybeLeakCause = true;\n      } else if (currentReachability == Reachability.REACHABLE) {\n        if (i < elements.size() - 1) {\n          Reachability nextReachability = expectedReachability.get(i + 1);\n          if (nextReachability != Reachability.REACHABLE) {\n            maybeLeakCause = true;\n          }\n        } else {\n          maybeLeakCause = true;\n        }\n      }\n      sb.append(element.toString(maybeLeakCause)).append(\"\\n\");\n    }\n    return sb.toString();\n  }\n\n  public @NonNull String toDetailedString() {\n    String string = \"\";\n    for (LeakTraceElement element : elements) {\n      string += element.toDetailedString();\n    }\n    return string;\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/LeakTraceElement.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static com.squareup.leakcanary.LeakTraceElement.Holder.ARRAY;\nimport static com.squareup.leakcanary.LeakTraceElement.Holder.CLASS;\nimport static com.squareup.leakcanary.LeakTraceElement.Holder.THREAD;\nimport static com.squareup.leakcanary.LeakTraceElement.Type.STATIC_FIELD;\nimport static java.util.Collections.unmodifiableList;\nimport static java.util.Locale.US;\n\n/** Represents one reference in the chain of references that holds a leaking object in memory. */\npublic final class LeakTraceElement implements Serializable {\n\n  public enum Type {\n    INSTANCE_FIELD, STATIC_FIELD, LOCAL, ARRAY_ENTRY\n  }\n\n  public enum Holder {\n    OBJECT, CLASS, THREAD, ARRAY\n  }\n\n  /**\n   * Information about the reference that points to the next {@link LeakTraceElement} in the leak\n   * chain. Null if this is the last element in the leak trace, ie the leaking object.\n   */\n  public final LeakReference reference;\n\n  /**\n   * @deprecated Use {@link #reference} and {@link LeakReference#getDisplayName()} instead.\n   * Null if this is the last element in the leak trace, ie the leaking object.\n   */\n  @Deprecated\n  public final String referenceName;\n\n  /**\n   * @deprecated Use {@link #reference} and {@link LeakReference#type} instead.\n   * Null if this is the last element in the leak trace, ie the leaking object.\n   */\n  @Deprecated\n  public final Type type;\n\n  public final Holder holder;\n\n  /**\n   * Class hierarchy for that object. The first element is {@link #className}. {@link Object}\n   * is excluded. There is always at least one element.\n   */\n  public final List<String> classHierarchy;\n\n  public final String className;\n\n  /** Additional information, may be null. */\n  public final String extra;\n\n  /** If not null, there was no path that could exclude this element. */\n  public final Exclusion exclusion;\n\n  /** List of all fields (member and static) for that object. */\n  public final List<LeakReference> fieldReferences;\n\n  /**\n   * @deprecated Use {@link #fieldReferences} instead.\n   */\n  @Deprecated\n  public final List<String> fields;\n\n  LeakTraceElement(LeakReference reference, Holder holder, List<String> classHierarchy,\n                   String extra, Exclusion exclusion, List<LeakReference> leakReferences) {\n    this.reference = reference;\n    this.referenceName = reference == null ? null : reference.getDisplayName();\n    this.type = reference == null ? null : reference.type;\n    this.holder = holder;\n    this.classHierarchy = Collections.unmodifiableList(new ArrayList<>(classHierarchy));\n    this.className = classHierarchy.get(0);\n    this.extra = extra;\n    this.exclusion = exclusion;\n    this.fieldReferences = unmodifiableList(new ArrayList<>(leakReferences));\n    List<String> stringFields = new ArrayList<>();\n    for (LeakReference leakReference : leakReferences) {\n      stringFields.add(leakReference.toString());\n    }\n    fields = Collections.unmodifiableList(stringFields);\n  }\n\n  /**\n   * Returns the string value of the first field reference that has the provided referenceName, or\n   * null if no field reference with that name was found.\n   */\n  public String getFieldReferenceValue(String referenceName) {\n    for (LeakReference fieldReference : fieldReferences) {\n      if (fieldReference.name.equals(referenceName)) {\n        return fieldReference.value;\n      }\n    }\n    return null;\n  }\n\n  /** @see #isInstanceOf(String) */\n  public boolean isInstanceOf(Class<?> expectedClass) {\n    return isInstanceOf(expectedClass.getName());\n  }\n\n  /**\n   * Returns true if this element is an instance of the provided class name, false otherwise.\n   */\n  public boolean isInstanceOf(String expectedClassName) {\n    for (String className : classHierarchy) {\n      if (className.equals(expectedClassName)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  /**\n   * Returns {@link #className} without the package.\n   */\n  public String getSimpleClassName() {\n    int separator = className.lastIndexOf('.');\n    if (separator == -1) {\n      return className;\n    } else {\n      return className.substring(separator + 1);\n    }\n  }\n\n  @Override public String toString() {\n    return toString(false);\n  }\n\n  public String toString(boolean maybeLeakCause) {\n    String string = \"\";\n\n    if (reference != null && reference.type == STATIC_FIELD) {\n      string += \"static \";\n    }\n\n    if (holder == ARRAY || holder == THREAD) {\n      string += holder.name().toLowerCase(US) + \" \";\n    }\n\n    string += getSimpleClassName();\n\n    if (reference != null) {\n      String referenceName = reference.getDisplayName();\n      if (maybeLeakCause) {\n        referenceName = \"!(\" + referenceName + \")!\";\n      }\n      string += \".\" + referenceName;\n    }\n\n    if (extra != null) {\n      string += \" \" + extra;\n    }\n\n    if (exclusion != null) {\n      string += \" , matching exclusion \" + exclusion.matching;\n    }\n\n    return string;\n  }\n\n  public String toDetailedString() {\n    String string = \"* \";\n    if (holder == ARRAY) {\n      string += \"Array of\";\n    } else if (holder == CLASS) {\n      string += \"Class\";\n    } else {\n      string += \"Instance of\";\n    }\n    string += \" \" + className + \"\\n\";\n    for (LeakReference leakReference : fieldReferences) {\n      string += \"|   \" + leakReference + \"\\n\";\n    }\n    return string;\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/Preconditions.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nfinal class Preconditions {\n\n  /**\n   * Returns instance unless it's null.\n   *\n   * @throws NullPointerException if instance is null\n   */\n  static <T> T checkNotNull(T instance, String name) {\n    if (instance == null) {\n      throw new NullPointerException(name + \" must not be null\");\n    }\n    return instance;\n  }\n\n  private Preconditions() {\n    throw new AssertionError();\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/Reachability.java",
    "content": "/*\n * Copyright (C) 2018 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\n/** Result returned by {@link Inspector#expectedReachability(LeakTraceElement)}. */\npublic enum Reachability {\n  /** The instance was needed and therefore expected to be reachable. */\n  REACHABLE,\n\n  /** The instance was no longer needed and therefore expected to be unreachable. */\n  UNREACHABLE,\n\n  /** No decision can be made about the provided instance. */\n  UNKNOWN;\n\n  /**\n   * Evaluates whether a {@link LeakTraceElement} should be reachable or not.\n   *\n   * Implementations should have a public zero argument constructor as instances will be created\n   * via reflection in the LeakCanary analysis process.\n   */\n  public interface Inspector {\n\n    Reachability expectedReachability(LeakTraceElement element);\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/RefWatcher.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport java.io.File;\nimport java.lang.ref.ReferenceQueue;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.CopyOnWriteArraySet;\n\nimport static com.squareup.leakcanary.HeapDumper.RETRY_LATER;\nimport static com.squareup.leakcanary.Preconditions.checkNotNull;\nimport static java.util.concurrent.TimeUnit.NANOSECONDS;\n\n/**\n * Watches references that should become weakly reachable. When the {@link RefWatcher} detects that\n * a reference might not be weakly reachable when it should, it triggers the {@link HeapDumper}.\n *\n * <p>This class is thread-safe: you can call {@link #watch(Object)} from any thread.\n */\npublic final class RefWatcher {\n\n    public static final RefWatcher DISABLED = new RefWatcherBuilder<>().build();\n\n    private final WatchExecutor watchExecutor;\n    private final DebuggerControl debuggerControl;\n    private final GcTrigger gcTrigger;\n    private final HeapDumper heapDumper;\n    private final HeapDump.Listener heapdumpListener;\n    private final HeapDump.Builder heapDumpBuilder;\n    private final Set<String> retainedKeys;\n    private final ReferenceQueue<Object> queue;\n\n    RefWatcher(WatchExecutor watchExecutor, DebuggerControl debuggerControl, GcTrigger gcTrigger,\n               HeapDumper heapDumper, HeapDump.Listener heapdumpListener, HeapDump.Builder heapDumpBuilder) {\n        this.watchExecutor = checkNotNull(watchExecutor, \"watchExecutor\");\n        this.debuggerControl = checkNotNull(debuggerControl, \"debuggerControl\");\n        this.gcTrigger = checkNotNull(gcTrigger, \"gcTrigger\");\n        this.heapDumper = checkNotNull(heapDumper, \"heapDumper\");\n        this.heapdumpListener = checkNotNull(heapdumpListener, \"heapdumpListener\");\n        this.heapDumpBuilder = heapDumpBuilder;\n        retainedKeys = new CopyOnWriteArraySet<>();\n        queue = new ReferenceQueue<>();\n    }\n\n    /**\n     * 监听对象activity、 fragment、fragment的根view\n     * Identical to {@link #watch(Object, String)} with an empty string reference name.\n     *\n     * @see #watch(Object, String)\n     */\n    public void watch(Object watchedReference) {\n        watch(watchedReference, \"\");\n    }\n\n\n    /**\n     * Watches the provided references and checks if it can be GCed. This method is non blocking,\n     * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed\n     * with.\n     *\n     * @param referenceName An logical identifier for the watched object.\n     */\n    public void watch(Object watchedReference, String referenceName) {\n        if (this == DISABLED) {\n            return;\n        }\n        checkNotNull(watchedReference, \"watchedReference\");\n        checkNotNull(referenceName, \"referenceName\");\n        final long watchStartNanoTime = System.nanoTime();\n        String key = UUID.randomUUID().toString();\n        retainedKeys.add(key);\n        //生成一个弱引用对象 并绑定key\n        final KeyedWeakReference reference =\n                new KeyedWeakReference(watchedReference, key, referenceName, queue);\n\n        ensureGoneAsync(watchStartNanoTime, reference);\n    }\n\n    /**\n     * LeakCanary will stop watching any references that were passed to {@link #watch(Object, String)}\n     * so far.\n     */\n    public void clearWatchedReferences() {\n        retainedKeys.clear();\n    }\n\n    boolean isEmpty() {\n        removeWeaklyReachableReferences();\n        return retainedKeys.isEmpty();\n    }\n\n    HeapDump.Builder getHeapDumpBuilder() {\n        return heapDumpBuilder;\n    }\n\n    Set<String> getRetainedKeys() {\n        return new HashSet<>(retainedKeys);\n    }\n\n    private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {\n        //AndroidWatchExecutor类型\n        watchExecutor.execute(new Retryable() {\n            @Override\n            public Result run() {\n                return ensureGone(reference, watchStartNanoTime);\n            }\n        });\n    }\n\n    @SuppressWarnings(\"ReferenceEquality\")\n        // Explicitly checking for named null.\n    Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {\n        long gcStartNanoTime = System.nanoTime();\n        long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);\n\n        removeWeaklyReachableReferences();\n\n        //如果当前处于debug attach模式 这过滤掉\n        if (debuggerControl.isDebuggerAttached()) {\n            // The debugger can create false leaks.\n            return Retryable.Result.RETRY;\n        }\n\n        if (gone(reference)) {\n            return Retryable.Result.DONE;\n        }\n        //执行gc\n        gcTrigger.runGc();\n        removeWeaklyReachableReferences();\n        //\n        if (!gone(reference)) {\n            long startDumpHeap = System.nanoTime();\n            long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);\n\n            File heapDumpFile = heapDumper.dumpHeap();\n            if (heapDumpFile == RETRY_LATER) {\n                // Could not dump the heap.\n                return Retryable.Result.RETRY;\n            }\n            long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);\n\n            HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)\n                    .referenceName(reference.name)\n                    .watchDurationMs(watchDurationMs)\n                    .gcDurationMs(gcDurationMs)\n                    .heapDumpDurationMs(heapDumpDurationMs)\n                    .build();\n\n            heapdumpListener.analyze(heapDump);\n        }\n        return Retryable.Result.DONE;\n    }\n\n    private boolean gone(KeyedWeakReference reference) {\n        return !retainedKeys.contains(reference.key);\n    }\n\n    private void removeWeaklyReachableReferences() {\n        // WeakReferences are enqueued as soon as the object to which they point to becomes weakly\n        // reachable. This is before finalization or garbage collection has actually happened.\n        KeyedWeakReference ref;\n        while ((ref = (KeyedWeakReference) queue.poll()) != null) {\n            retainedKeys.remove(ref.key);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/RefWatcherBuilder.java",
    "content": "package com.squareup.leakcanary;\n\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Responsible for building {@link RefWatcher} instances. Subclasses should provide sane defaults\n * for the platform they support.\n */\npublic class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {\n\n  private HeapDump.Listener heapDumpListener;\n  private DebuggerControl debuggerControl;\n  private HeapDumper heapDumper;\n  private WatchExecutor watchExecutor;\n  private GcTrigger gcTrigger;\n  private final HeapDump.Builder heapDumpBuilder;\n\n  public RefWatcherBuilder() {\n    heapDumpBuilder = new HeapDump.Builder();\n  }\n\n  /** @see HeapDump.Listener */\n  public final T heapDumpListener(HeapDump.Listener heapDumpListener) {\n    this.heapDumpListener = heapDumpListener;\n    return self();\n  }\n\n  /** @see ExcludedRefs */\n  public final T excludedRefs(ExcludedRefs excludedRefs) {\n    heapDumpBuilder.excludedRefs(excludedRefs);\n    return self();\n  }\n\n  /** @see HeapDumper */\n  public final T heapDumper(HeapDumper heapDumper) {\n    this.heapDumper = heapDumper;\n    return self();\n  }\n\n  /** @see DebuggerControl */\n  public final T debuggerControl(DebuggerControl debuggerControl) {\n    this.debuggerControl = debuggerControl;\n    return self();\n  }\n\n  /** @see WatchExecutor */\n  public final T watchExecutor(WatchExecutor watchExecutor) {\n    this.watchExecutor = watchExecutor;\n    return self();\n  }\n\n  /** @see GcTrigger */\n  public final T gcTrigger(GcTrigger gcTrigger) {\n    this.gcTrigger = gcTrigger;\n    return self();\n  }\n\n  /** @see Reachability.Inspector */\n  public final T stethoscopeClasses(\n      List<Class<? extends Reachability.Inspector>> stethoscopeClasses) {\n    heapDumpBuilder.reachabilityInspectorClasses(stethoscopeClasses);\n    return self();\n  }\n\n  /**\n   * Whether LeakCanary should compute the retained heap size when a leak is detected. False by\n   * default, because computing the retained heap size takes a long time.\n   */\n  public final T computeRetainedHeapSize(boolean computeRetainedHeapSize) {\n    heapDumpBuilder.computeRetainedHeapSize(computeRetainedHeapSize);\n    return self();\n  }\n\n  /** Creates a {@link RefWatcher}. */\n  public final RefWatcher build() {\n    if (isDisabled()) {\n      return RefWatcher.DISABLED;\n    }\n\n    if (heapDumpBuilder.excludedRefs == null) {\n      heapDumpBuilder.excludedRefs(defaultExcludedRefs());\n    }\n\n    HeapDump.Listener heapDumpListener = this.heapDumpListener;\n    if (heapDumpListener == null) {\n      heapDumpListener = defaultHeapDumpListener();\n    }\n\n    DebuggerControl debuggerControl = this.debuggerControl;\n    if (debuggerControl == null) {\n      debuggerControl = defaultDebuggerControl();\n    }\n\n    HeapDumper heapDumper = this.heapDumper;\n    if (heapDumper == null) {\n      heapDumper = defaultHeapDumper();\n    }\n\n    WatchExecutor watchExecutor = this.watchExecutor;\n    if (watchExecutor == null) {\n      watchExecutor = defaultWatchExecutor();\n    }\n\n    GcTrigger gcTrigger = this.gcTrigger;\n    if (gcTrigger == null) {\n      gcTrigger = defaultGcTrigger();\n    }\n\n    if (heapDumpBuilder.reachabilityInspectorClasses == null) {\n      heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());\n    }\n\n    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,\n        heapDumpBuilder);\n  }\n\n  protected boolean isDisabled() {\n    return false;\n  }\n\n  protected GcTrigger defaultGcTrigger() {\n    return GcTrigger.DEFAULT;\n  }\n\n  protected DebuggerControl defaultDebuggerControl() {\n    return DebuggerControl.NONE;\n  }\n\n  protected ExcludedRefs defaultExcludedRefs() {\n    return ExcludedRefs.builder().build();\n  }\n\n  protected HeapDumper defaultHeapDumper() {\n    return HeapDumper.NONE;\n  }\n\n  protected HeapDump.Listener defaultHeapDumpListener() {\n    return HeapDump.Listener.NONE;\n  }\n\n  protected WatchExecutor defaultWatchExecutor() {\n    return WatchExecutor.NONE;\n  }\n\n  protected List<Class<? extends Reachability.Inspector>> defaultReachabilityInspectorClasses() {\n    return Collections.emptyList();\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  protected final T self() {\n    return (T) this;\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/Retryable.java",
    "content": "package com.squareup.leakcanary;\n\n/** A unit of work that can be retried later. */\npublic interface Retryable {\n\n  enum Result {\n    DONE, RETRY\n  }\n\n  Result run();\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/ServiceHeapDumpListener.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\n\nimport com.squareup.leakcanary.internal.HeapAnalyzerService;\n\npublic final class ServiceHeapDumpListener implements HeapDump.Listener {\n\n  private final Context context;\n  private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;\n\n  public ServiceHeapDumpListener(@NonNull final Context context,\n      @NonNull final Class<? extends AbstractAnalysisResultService> listenerServiceClass) {\n    this.listenerServiceClass = Preconditions.checkNotNull(listenerServiceClass, \"listenerServiceClass\");\n    this.context = Preconditions.checkNotNull(context, \"context\").getApplicationContext();\n  }\n\n  @Override\n  public void analyze(@NonNull HeapDump heapDump) {\n    Preconditions.checkNotNull(heapDump, \"heapDump\");\n    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/ShortestPathFinder.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport com.squareup.haha.perflib.ArrayInstance;\nimport com.squareup.haha.perflib.ClassInstance;\nimport com.squareup.haha.perflib.ClassObj;\nimport com.squareup.haha.perflib.Field;\nimport com.squareup.haha.perflib.HahaSpy;\nimport com.squareup.haha.perflib.Instance;\nimport com.squareup.haha.perflib.RootObj;\nimport com.squareup.haha.perflib.RootType;\nimport com.squareup.haha.perflib.Snapshot;\nimport com.squareup.haha.perflib.Type;\n\nimport java.util.ArrayDeque;\nimport java.util.Deque;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.Map;\n\n/**\n * Not thread safe.\n *\n * Finds the shortest path from a leaking reference to a gc root, ignoring excluded\n * refs first and then including the ones that are not \"always ignorable\" as needed if no path is\n * found.\n */\nfinal class ShortestPathFinder {\n\n  private final ExcludedRefs excludedRefs;\n  private final Deque<LeakNode> toVisitQueue;\n  private final Deque<LeakNode> toVisitIfNoPathQueue;\n  private final LinkedHashSet<Instance> toVisitSet;\n  private final LinkedHashSet<Instance> toVisitIfNoPathSet;\n  private final LinkedHashSet<Instance> visitedSet;\n  private boolean canIgnoreStrings;\n\n  ShortestPathFinder(ExcludedRefs excludedRefs) {\n    this.excludedRefs = excludedRefs;\n    toVisitQueue = new ArrayDeque<>();\n    toVisitIfNoPathQueue = new ArrayDeque<>();\n    toVisitSet = new LinkedHashSet<>();\n    toVisitIfNoPathSet = new LinkedHashSet<>();\n    visitedSet = new LinkedHashSet<>();\n  }\n\n  static final class Result {\n    final LeakNode leakingNode;\n    final boolean excludingKnownLeaks;\n\n    Result(LeakNode leakingNode, boolean excludingKnownLeaks) {\n      this.leakingNode = leakingNode;\n      this.excludingKnownLeaks = excludingKnownLeaks;\n    }\n  }\n\n  Result findPath(Snapshot snapshot, Instance leakingRef) {\n    clearState();\n    canIgnoreStrings = !isString(leakingRef);\n\n    enqueueGcRoots(snapshot);\n\n    boolean excludingKnownLeaks = false;\n    LeakNode leakingNode = null;\n    while (!toVisitQueue.isEmpty() || !toVisitIfNoPathQueue.isEmpty()) {\n      LeakNode node;\n      if (!toVisitQueue.isEmpty()) {\n        node = toVisitQueue.poll();\n      } else {\n        node = toVisitIfNoPathQueue.poll();\n        if (node.exclusion == null) {\n          throw new IllegalStateException(\"Expected node to have an exclusion \" + node);\n        }\n        excludingKnownLeaks = true;\n      }\n\n      // Termination\n      if (node.instance == leakingRef) {\n        leakingNode = node;\n        break;\n      }\n\n      if (checkSeen(node)) {\n        continue;\n      }\n\n      if (node.instance instanceof RootObj) {\n        visitRootObj(node);\n      } else if (node.instance instanceof ClassObj) {\n        visitClassObj(node);\n      } else if (node.instance instanceof ClassInstance) {\n        visitClassInstance(node);\n      } else if (node.instance instanceof ArrayInstance) {\n        visitArrayInstance(node);\n      } else {\n        throw new IllegalStateException(\"Unexpected type for \" + node.instance);\n      }\n    }\n    return new Result(leakingNode, excludingKnownLeaks);\n  }\n\n  private void clearState() {\n    toVisitQueue.clear();\n    toVisitIfNoPathQueue.clear();\n    toVisitSet.clear();\n    toVisitIfNoPathSet.clear();\n    visitedSet.clear();\n  }\n\n  private void enqueueGcRoots(Snapshot snapshot) {\n    for (RootObj rootObj : HahaSpy.allGcRoots(snapshot)) {\n      switch (rootObj.getRootType()) {\n        case JAVA_LOCAL:\n          Instance thread = HahaSpy.allocatingThread(rootObj);\n          String threadName = HahaHelper.threadName(thread);\n          Exclusion params = excludedRefs.threadNames.get(threadName);\n          if (params == null || !params.alwaysExclude) {\n            enqueue(params, null, rootObj, null);\n          }\n          break;\n        case INTERNED_STRING:\n        case DEBUGGER:\n        case INVALID_TYPE:\n          // An object that is unreachable from any other root, but not a root itself.\n        case UNREACHABLE:\n        case UNKNOWN:\n          // An object that is in a queue, waiting for a finalizer to run.\n        case FINALIZING:\n          break;\n        case SYSTEM_CLASS:\n        case VM_INTERNAL:\n          // A local variable in native code.\n        case NATIVE_LOCAL:\n          // A global variable in native code.\n        case NATIVE_STATIC:\n          // An object that was referenced from an active thread block.\n        case THREAD_BLOCK:\n          // Everything that called the wait() or notify() methods, or that is synchronized.\n        case BUSY_MONITOR:\n        case NATIVE_MONITOR:\n        case REFERENCE_CLEANUP:\n          // Input or output parameters in native code.\n        case NATIVE_STACK:\n        case JAVA_STATIC:\n          enqueue(null, null, rootObj, null);\n          break;\n        default:\n          throw new UnsupportedOperationException(\"Unknown root type:\" + rootObj.getRootType());\n      }\n    }\n  }\n\n  private boolean checkSeen(LeakNode node) {\n    return !visitedSet.add(node.instance);\n  }\n\n  private void visitRootObj(LeakNode node) {\n    RootObj rootObj = (RootObj) node.instance;\n    Instance child = rootObj.getReferredInstance();\n\n    if (rootObj.getRootType() == RootType.JAVA_LOCAL) {\n      Instance holder = HahaSpy.allocatingThread(rootObj);\n      // We switch the parent node with the thread instance that holds\n      // the local reference.\n      Exclusion exclusion = null;\n      if (node.exclusion != null) {\n        exclusion = node.exclusion;\n      }\n      LeakNode parent = new LeakNode(null, holder, null, null);\n      enqueue(exclusion, parent, child, new LeakReference(LeakTraceElement.Type.LOCAL, null, null));\n    } else {\n      enqueue(null, node, child, null);\n    }\n  }\n\n  private void visitClassObj(LeakNode node) {\n    ClassObj classObj = (ClassObj) node.instance;\n    Map<String, Exclusion> ignoredStaticFields =\n        excludedRefs.staticFieldNameByClassName.get(classObj.getClassName());\n    for (Map.Entry<Field, Object> entry : classObj.getStaticFieldValues().entrySet()) {\n      Field field = entry.getKey();\n      if (field.getType() != Type.OBJECT) {\n        continue;\n      }\n      String fieldName = field.getName();\n      if (fieldName.equals(\"$staticOverhead\")) {\n        continue;\n      }\n      Instance child = (Instance) entry.getValue();\n      boolean visit = true;\n      String fieldValue = entry.getValue() == null ? \"null\" : entry.getValue().toString();\n      LeakReference leakReference = new LeakReference(LeakTraceElement.Type.STATIC_FIELD, fieldName, fieldValue);\n      if (ignoredStaticFields != null) {\n        Exclusion params = ignoredStaticFields.get(fieldName);\n        if (params != null) {\n          visit = false;\n          if (!params.alwaysExclude) {\n            enqueue(params, node, child, leakReference);\n          }\n        }\n      }\n      if (visit) {\n        enqueue(null, node, child, leakReference);\n      }\n    }\n  }\n\n  private void visitClassInstance(LeakNode node) {\n    ClassInstance classInstance = (ClassInstance) node.instance;\n    Map<String, Exclusion> ignoredFields = new LinkedHashMap<>();\n    ClassObj superClassObj = classInstance.getClassObj();\n    Exclusion classExclusion = null;\n    while (superClassObj != null) {\n      Exclusion params = excludedRefs.classNames.get(superClassObj.getClassName());\n      if (params != null) {\n        // true overrides null or false.\n        if (classExclusion == null || !classExclusion.alwaysExclude) {\n          classExclusion = params;\n        }\n      }\n      Map<String, Exclusion> classIgnoredFields =\n          excludedRefs.fieldNameByClassName.get(superClassObj.getClassName());\n      if (classIgnoredFields != null) {\n        ignoredFields.putAll(classIgnoredFields);\n      }\n      superClassObj = superClassObj.getSuperClassObj();\n    }\n\n    if (classExclusion != null && classExclusion.alwaysExclude) {\n      return;\n    }\n\n    for (ClassInstance.FieldValue fieldValue : classInstance.getValues()) {\n      Exclusion fieldExclusion = classExclusion;\n      Field field = fieldValue.getField();\n      if (field.getType() != Type.OBJECT) {\n        continue;\n      }\n      Instance child = (Instance) fieldValue.getValue();\n      String fieldName = field.getName();\n      Exclusion params = ignoredFields.get(fieldName);\n      // If we found a field exclusion and it's stronger than a class exclusion\n      if (params != null && (fieldExclusion == null || (params.alwaysExclude\n          && !fieldExclusion.alwaysExclude))) {\n        fieldExclusion = params;\n      }\n      String value = fieldValue.getValue() == null ? \"null\" : fieldValue.getValue().toString();\n      enqueue(fieldExclusion, node, child, new LeakReference(LeakTraceElement.Type.INSTANCE_FIELD, fieldName, value));\n    }\n  }\n\n  private void visitArrayInstance(LeakNode node) {\n    ArrayInstance arrayInstance = (ArrayInstance) node.instance;\n    Type arrayType = arrayInstance.getArrayType();\n    if (arrayType == Type.OBJECT) {\n      Object[] values = arrayInstance.getValues();\n      for (int i = 0; i < values.length; i++) {\n        Instance child = (Instance) values[i];\n        String name = Integer.toString(i);\n        String value = child == null ? \"null\" : child.toString();\n        enqueue(null, node, child, new LeakReference(LeakTraceElement.Type.ARRAY_ENTRY, name, value));\n      }\n    }\n  }\n\n  private void enqueue(Exclusion exclusion, LeakNode parent, Instance child,\n                       LeakReference leakReference) {\n    if (child == null) {\n      return;\n    }\n    if (HahaHelper.isPrimitiveOrWrapperArray(child) || HahaHelper.isPrimitiveWrapper(child)) {\n      return;\n    }\n    // Whether we want to visit now or later, we should skip if this is already to visit.\n    if (toVisitSet.contains(child)) {\n      return;\n    }\n    boolean visitNow = exclusion == null;\n    if (!visitNow && toVisitIfNoPathSet.contains(child)) {\n      return;\n    }\n    if (canIgnoreStrings && isString(child)) {\n      return;\n    }\n    if (visitedSet.contains(child)) {\n      return;\n    }\n    LeakNode childNode = new LeakNode(exclusion, child, parent, leakReference);\n    if (visitNow) {\n      toVisitSet.add(child);\n      toVisitQueue.add(childNode);\n    } else {\n      toVisitIfNoPathSet.add(child);\n      toVisitIfNoPathQueue.add(childNode);\n    }\n  }\n\n  private boolean isString(Instance instance) {\n    return instance.getClassObj() != null && instance.getClassObj()\n        .getClassName()\n        .equals(String.class.getName());\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/TrackedReference.java",
    "content": "package com.squareup.leakcanary;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * An instance tracked by a {@link KeyedWeakReference} that hadn't been cleared when the\n * heap was dumped. May or may not point to a leaking reference.\n */\npublic class TrackedReference {\n\n  /** Corresponds to {@link KeyedWeakReference#key}. */\n  @NonNull public final String key;\n\n  /** Corresponds to {@link KeyedWeakReference#name}. */\n  @NonNull public final String name;\n\n  /** Class of the tracked instance. */\n  @NonNull public final String className;\n\n  /** List of all fields (member and static) for that instance. */\n  @NonNull public final List<LeakReference> fields;\n\n  public TrackedReference(@NonNull String key, @NonNull String name, @NonNull String className,\n      @NonNull List<LeakReference> fields) {\n    this.key = key;\n    this.name = name;\n    this.className = className;\n    this.fields = Collections.unmodifiableList(new ArrayList<>(fields));\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/UploadLeakService.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary;\n\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.abridge.IBridge;\nimport com.squareup.leakcanary.internal.DisplayLeakActivity;\n\n/**\n * Logs leak analysis results, and then shows a notification which will start {@link\n * DisplayLeakActivity}.\n * <p>\n * You can extend this class and override {@link #afterDefaultHandling(HeapDump, AnalysisResult,\n * String)} to add custom behavior, e.g. uploading the heap dump.\n */\npublic class UploadLeakService extends DisplayLeakService {\n    private static final String TAG = \"UploadLeakService\";\n\n\n    /**\n     * 进行上传服务\n     *\n     * @param heapDump\n     * @param result\n     * @param leakInfo\n     */\n    @Override\n    protected void afterDefaultHandling(@NonNull HeapDump heapDump, @NonNull AnalysisResult result, @NonNull String leakInfo) {\n        super.afterDefaultHandling(heapDump, result, leakInfo);\n        if (TextUtils.isEmpty(leakInfo)) {\n            return;\n        }\n        Log.i(TAG, \"====leakInfo====\" + leakInfo);\n        try {\n            IBridge.sendAIDLMessage(leakInfo);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/WatchExecutor.java",
    "content": "package com.squareup.leakcanary;\n\n/**\n * A {@link WatchExecutor} is in charge of executing a {@link Retryable} in the future, and retry\n * later if needed.\n */\npublic interface WatchExecutor {\n  WatchExecutor NONE = new WatchExecutor() {\n    @Override public void execute(Retryable retryable) {\n    }\n  };\n\n  void execute(Retryable retryable);\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/ActivityLifecycleCallbacksAdapter.java",
    "content": "/*\n * Copyright (C) 2018 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.os.Bundle;\n\npublic abstract class ActivityLifecycleCallbacksAdapter\n        implements Application.ActivityLifecycleCallbacks {\n    @Override\n    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {\n    }\n\n    @Override\n    public void onActivityStarted(Activity activity) {\n    }\n\n    @Override\n    public void onActivityResumed(Activity activity) {\n    }\n\n    @Override\n    public void onActivityPaused(Activity activity) {\n    }\n\n    @Override\n    public void onActivityStopped(Activity activity) {\n    }\n\n    @Override\n    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {\n    }\n\n    @Override\n    public void onActivityDestroyed(Activity activity) {\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/AndroidOFragmentRefWatcher.java",
    "content": "/*\n * Copyright (C) 2018 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport android.app.Activity;\nimport android.app.Fragment;\nimport android.app.FragmentManager;\nimport android.os.Build;\nimport androidx.annotation.RequiresApi;\nimport android.view.View;\n\nimport com.squareup.leakcanary.RefWatcher;\n\n@RequiresApi(Build.VERSION_CODES.O) //\nclass AndroidOFragmentRefWatcher implements FragmentRefWatcher {\n\n  private final RefWatcher refWatcher;\n\n  AndroidOFragmentRefWatcher(RefWatcher refWatcher) {\n    this.refWatcher = refWatcher;\n  }\n\n  private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =\n      new FragmentManager.FragmentLifecycleCallbacks() {\n\n        @Override\n        public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {\n          View view = fragment.getView();\n          if (view != null) {\n            refWatcher.watch(view);\n          }\n        }\n\n        @Override\n        public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {\n          refWatcher.watch(fragment);\n        }\n      };\n\n  @Override\n  public void watchFragments(Activity activity) {\n    FragmentManager fragmentManager = activity.getFragmentManager();\n    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/DisplayLeakActivity.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport android.annotation.SuppressLint;\nimport android.app.ActionBar;\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.PendingIntent;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.text.format.DateUtils;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.AdapterView;\nimport android.widget.BaseAdapter;\nimport android.widget.Button;\nimport android.widget.ListAdapter;\nimport android.widget.ListView;\nimport android.widget.TextView;\n\nimport com.squareup.leakcanary.BuildConfig;\nimport com.squareup.leakcanary.R;\nimport com.squareup.leakcanary.AnalysisResult;\nimport com.squareup.leakcanary.AnalyzedHeap;\nimport com.squareup.leakcanary.CanaryLog;\nimport com.squareup.leakcanary.HeapDump;\nimport com.squareup.leakcanary.LeakDirectoryProvider;\nimport com.squareup.leakcanary.LeakCanary;\n\nimport java.io.File;\nimport java.io.FilenameFilter;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.concurrent.Executor;\n\nimport static android.app.PendingIntent.FLAG_UPDATE_CURRENT;\nimport static androidx.core.content.FileProvider.getUriForFile;\nimport static android.text.format.DateUtils.FORMAT_SHOW_DATE;\nimport static android.text.format.DateUtils.FORMAT_SHOW_TIME;\nimport static android.text.format.Formatter.formatShortFileSize;\nimport static android.view.View.GONE;\nimport static android.view.View.VISIBLE;\nimport static com.squareup.leakcanary.BuildConfig.LEAKCANARY_LIBRARY_VERSION;\nimport android.os.Build;\n\n@SuppressWarnings(\"ConstantConditions\")\npublic final class DisplayLeakActivity extends Activity {\n\n  private static final String SHOW_LEAK_EXTRA = \"show_latest\";\n\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  public static PendingIntent createPendingIntent(Context context) {\n    return createPendingIntent(context, null);\n  }\n\n  public static PendingIntent createPendingIntent(Context context, String referenceKey) {\n    LeakCanaryInternals.setEnabledBlocking(context,DisplayLeakActivity.class, true);\n    Intent intent = new Intent(context, DisplayLeakActivity.class);\n    intent.putExtra(SHOW_LEAK_EXTRA, referenceKey);\n    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n        return PendingIntent.getActivity(context, 1, intent, FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);\n    }else{\n        return PendingIntent.getActivity(context, 1, intent, FLAG_UPDATE_CURRENT);\n    }\n  }\n\n  // null until it's been first loaded.\n  List<AnalyzedHeap> leaks;\n  String visibleLeakRefKey;\n\n  private ListView listView;\n  private TextView failureView;\n  private Button actionButton;\n\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    if (savedInstanceState != null) {\n      visibleLeakRefKey = savedInstanceState.getString(\"visibleLeakRefKey\");\n    } else {\n      Intent intent = getIntent();\n      if (intent.hasExtra(SHOW_LEAK_EXTRA)) {\n        visibleLeakRefKey = intent.getStringExtra(SHOW_LEAK_EXTRA);\n      }\n    }\n\n    leaks = (List<AnalyzedHeap>) getLastNonConfigurationInstance();\n\n    setContentView(R.layout.leak_canary_display_leak);\n\n    listView = findViewById(R.id.leak_canary_display_leak_list);\n    failureView = findViewById(R.id.leak_canary_display_leak_failure);\n    actionButton = findViewById(R.id.leak_canary_action);\n\n    updateUi();\n  }\n\n  @Override\n  public Object onRetainNonConfigurationInstance() {\n    return leaks;\n  }\n\n  @Override\n  protected void onSaveInstanceState(Bundle outState) {\n    super.onSaveInstanceState(outState);\n    outState.putString(\"visibleLeakRefKey\", visibleLeakRefKey);\n  }\n\n  @Override\n  protected void onResume() {\n    super.onResume();\n    LoadLeaks.load(this, LeakCanaryInternals.getLeakDirectoryProvider(this));\n  }\n\n  @Override\n  public void setTheme(int resid) {\n    // We don't want this to be called with an incompatible theme.\n    // This could happen if you implement runtime switching of themes\n    // using ActivityLifecycleCallbacks.\n    if (resid != R.style.leak_canary_LeakCanary_Base) {\n      return;\n    }\n    super.setTheme(resid);\n  }\n\n  @Override\n  protected void onDestroy() {\n    super.onDestroy();\n    LoadLeaks.forgetActivity();\n  }\n\n  @Override\n  public boolean onCreateOptionsMenu(Menu menu) {\n    AnalyzedHeap visibleLeak = getVisibleLeak();\n    if (visibleLeak != null) {\n      menu.add(R.string.leak_canary_share_leak)\n          .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {\n            @Override\n            public boolean onMenuItemClick(MenuItem item) {\n              shareLeak();\n              return true;\n            }\n          });\n      if (visibleLeak.heapDumpFileExists) {\n        menu.add(R.string.leak_canary_share_heap_dump)\n            .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {\n              @Override\n              public boolean onMenuItemClick(MenuItem item) {\n                shareHeapDump();\n                return true;\n              }\n            });\n      }\n      return true;\n    }\n    return false;\n  }\n\n  @Override\n  public boolean onOptionsItemSelected(MenuItem item) {\n    if (item.getItemId() == android.R.id.home) {\n      visibleLeakRefKey = null;\n      updateUi();\n    }\n    return true;\n  }\n\n  @Override\n  public void onBackPressed() {\n    if (visibleLeakRefKey != null) {\n      visibleLeakRefKey = null;\n      updateUi();\n    } else {\n      super.onBackPressed();\n    }\n  }\n\n  void shareLeak() {\n    AnalyzedHeap visibleLeak = getVisibleLeak();\n    String leakInfo = LeakCanary.leakInfo(this, visibleLeak.heapDump, visibleLeak.result, true);\n    Intent intent = new Intent(Intent.ACTION_SEND);\n    intent.setType(\"text/plain\");\n    intent.putExtra(Intent.EXTRA_TEXT, leakInfo);\n    startActivity(Intent.createChooser(intent, getString(R.string.leak_canary_share_with)));\n  }\n\n  @SuppressLint(\"SetWorldReadable\")\n  void shareHeapDump() {\n    AnalyzedHeap visibleLeak = getVisibleLeak();\n    final File heapDumpFile = visibleLeak.heapDump.heapDumpFile;\n    AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {\n      @Override\n      public void run() {\n        //noinspection ResultOfMethodCallIgnored\n        heapDumpFile.setReadable(true, false);\n        final Uri heapDumpUri = getUriForFile(getBaseContext(),\n            \"com.squareup.leakcanary.fileprovider.\" + getApplication().getPackageName(),\n            heapDumpFile);\n        runOnUiThread(new Runnable() {\n          @Override\n          public void run() {\n            startShareIntentChooser(heapDumpUri);\n          }\n        });\n      }\n    });\n  }\n\n  private void startShareIntentChooser(Uri heapDumpUri) {\n    Intent intent = new Intent(Intent.ACTION_SEND);\n    intent.setType(\"application/octet-stream\");\n    intent.putExtra(Intent.EXTRA_STREAM, heapDumpUri);\n    startActivity(Intent.createChooser(intent, getString(R.string.leak_canary_share_with)));\n  }\n\n  void deleteVisibleLeak() {\n    final AnalyzedHeap visibleLeak = getVisibleLeak();\n    AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {\n      @Override\n      public void run() {\n        File heapDumpFile = visibleLeak.heapDump.heapDumpFile;\n        File resultFile = visibleLeak.selfFile;\n        boolean resultDeleted = resultFile.delete();\n        if (!resultDeleted) {\n          CanaryLog.d(\"Could not delete result file %s\", resultFile.getPath());\n        }\n        boolean heapDumpDeleted = heapDumpFile.delete();\n        if (!heapDumpDeleted) {\n          CanaryLog.d(\"Could not delete heap dump file %s\", heapDumpFile.getPath());\n        }\n      }\n    });\n    visibleLeakRefKey = null;\n    leaks.remove(visibleLeak);\n    updateUi();\n  }\n\n  void deleteAllLeaks() {\n    final LeakDirectoryProvider leakDirectoryProvider = LeakCanaryInternals.getLeakDirectoryProvider(this);\n    AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {\n      @Override\n      public void run() {\n        leakDirectoryProvider.clearLeakDirectory();\n      }\n    });\n    leaks = Collections.emptyList();\n    updateUi();\n  }\n\n  void updateUi() {\n    if (leaks == null) {\n      setTitle(\"Loading leaks...\");\n      return;\n    }\n    if (leaks.isEmpty()) {\n      visibleLeakRefKey = null;\n    }\n\n    final AnalyzedHeap visibleLeak = getVisibleLeak();\n    if (visibleLeak == null) {\n      visibleLeakRefKey = null;\n    }\n\n    ListAdapter listAdapter = listView.getAdapter();\n    // Reset to defaults\n    listView.setVisibility(VISIBLE);\n    failureView.setVisibility(GONE);\n\n    if (visibleLeak != null) {\n      AnalysisResult result = visibleLeak.result;\n      actionButton.setVisibility(VISIBLE);\n      actionButton.setText(R.string.leak_canary_delete);\n      actionButton.setOnClickListener(new View.OnClickListener() {\n        @Override\n        public void onClick(View v) {\n          deleteVisibleLeak();\n        }\n      });\n      invalidateOptionsMenu();\n      setDisplayHomeAsUpEnabled(true);\n\n      if (result.leakFound) {\n        final DisplayLeakAdapter adapter = new DisplayLeakAdapter(getResources());\n        listView.setAdapter(adapter);\n        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {\n          @Override\n          public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n            adapter.toggleRow(position);\n          }\n        });\n        HeapDump heapDump = visibleLeak.heapDump;\n        adapter.update(result.leakTrace, heapDump.referenceKey, heapDump.referenceName);\n        if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {\n          String className = classSimpleName(result.className);\n          setTitle(getString(R.string.leak_canary_class_has_leaked, className));\n        } else {\n          String size = formatShortFileSize(this, result.retainedHeapSize);\n          String className = classSimpleName(result.className);\n          setTitle(getString(R.string.leak_canary_class_has_leaked_retaining, className, size));\n        }\n      } else {\n        listView.setVisibility(GONE);\n        failureView.setVisibility(VISIBLE);\n        listView.setAdapter(null);\n\n        String failureMessage;\n        if (result.failure != null) {\n          setTitle(R.string.leak_canary_analysis_failed);\n          failureMessage = getString(R.string.leak_canary_failure_report)\n              + LEAKCANARY_LIBRARY_VERSION\n              + \" \"\n              + BuildConfig.GIT_SHA\n              + \"\\n\"\n              + Log.getStackTraceString(result.failure);\n        } else {\n          String className = classSimpleName(result.className);\n          setTitle(getString(R.string.leak_canary_class_no_leak, className));\n          failureMessage = getString(R.string.leak_canary_no_leak_details);\n        }\n        String path = visibleLeak.heapDump.heapDumpFile.getAbsolutePath();\n        failureMessage += \"\\n\\n\" + getString(R.string.leak_canary_download_dump, path);\n        failureView.setText(failureMessage);\n      }\n    } else {\n      if (listAdapter instanceof LeakListAdapter) {\n        ((LeakListAdapter) listAdapter).notifyDataSetChanged();\n      } else {\n        LeakListAdapter adapter = new LeakListAdapter();\n        listView.setAdapter(adapter);\n        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {\n          @Override\n          public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n            visibleLeakRefKey = leaks.get(position).heapDump.referenceKey;\n            updateUi();\n          }\n        });\n        invalidateOptionsMenu();\n        setTitle(getString(R.string.leak_canary_leak_list_title, getPackageName()));\n        setDisplayHomeAsUpEnabled(false);\n        actionButton.setText(R.string.leak_canary_delete_all);\n        actionButton.setOnClickListener(new View.OnClickListener() {\n          @Override\n          public void onClick(View v) {\n            new AlertDialog.Builder(DisplayLeakActivity.this).setIcon(\n                android.R.drawable.ic_dialog_alert)\n                .setTitle(R.string.leak_canary_delete_all)\n                .setMessage(R.string.leak_canary_delete_all_leaks_title)\n                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {\n                  @Override\n                  public void onClick(DialogInterface dialog, int which) {\n                    deleteAllLeaks();\n                  }\n                })\n                .setNegativeButton(android.R.string.cancel, null)\n                .show();\n          }\n        });\n      }\n      actionButton.setVisibility(leaks.size() == 0 ? GONE : VISIBLE);\n    }\n  }\n\n  private void setDisplayHomeAsUpEnabled(boolean enabled) {\n    ActionBar actionBar = getActionBar();\n    if (actionBar == null) {\n      // https://github.com/square/leakcanary/issues/967\n      return;\n    }\n    actionBar.setDisplayHomeAsUpEnabled(enabled);\n  }\n\n  AnalyzedHeap getVisibleLeak() {\n    if (leaks == null) {\n      return null;\n    }\n    for (AnalyzedHeap leak : leaks) {\n      if (leak.heapDump.referenceKey.equals(visibleLeakRefKey)) {\n        return leak;\n      }\n    }\n    return null;\n  }\n\n  class LeakListAdapter extends BaseAdapter {\n\n    @Override\n    public int getCount() {\n      return leaks.size();\n    }\n\n    @Override\n    public AnalyzedHeap getItem(int position) {\n      return leaks.get(position);\n    }\n\n    @Override\n    public long getItemId(int position) {\n      return position;\n    }\n\n    @Override\n    public View getView(int position, View convertView, ViewGroup parent) {\n      if (convertView == null) {\n        convertView = LayoutInflater.from(DisplayLeakActivity.this)\n            .inflate(R.layout.leak_canary_leak_row, parent, false);\n      }\n      TextView titleView = convertView.findViewById(R.id.leak_canary_row_text);\n      TextView timeView = convertView.findViewById(R.id.leak_canary_row_time);\n      AnalyzedHeap leak = getItem(position);\n\n      String index = (leaks.size() - position) + \". \";\n\n      String title;\n      if (leak.result.failure != null) {\n        title = index\n            + leak.result.failure.getClass().getSimpleName()\n            + \" \"\n            + leak.result.failure.getMessage();\n      } else {\n        String className = classSimpleName(leak.result.className);\n        if (leak.result.leakFound) {\n          if (leak.result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {\n            title = getString(R.string.leak_canary_class_has_leaked, className);\n          } else {\n            String size = formatShortFileSize(DisplayLeakActivity.this,\n                leak.result.retainedHeapSize);\n            title = getString(R.string.leak_canary_class_has_leaked_retaining, className, size);\n          }\n          if (leak.result.excludedLeak) {\n            title = getString(R.string.leak_canary_excluded_row, title);\n          }\n          title = index + title;\n        } else {\n          title = index + getString(R.string.leak_canary_class_no_leak, className);\n        }\n      }\n      titleView.setText(title);\n      String time =\n          DateUtils.formatDateTime(DisplayLeakActivity.this, leak.selfLastModified,\n              FORMAT_SHOW_TIME | FORMAT_SHOW_DATE);\n      timeView.setText(time);\n      return convertView;\n    }\n  }\n\n  static class LoadLeaks implements Runnable {\n\n    static final List<LoadLeaks> inFlight = new ArrayList<>();\n\n    static final Executor backgroundExecutor = LeakCanaryInternals.newSingleThreadExecutor(\"LoadLeaks\");\n\n    static void load(DisplayLeakActivity activity, LeakDirectoryProvider leakDirectoryProvider) {\n      LoadLeaks loadLeaks = new LoadLeaks(activity, leakDirectoryProvider);\n      inFlight.add(loadLeaks);\n      backgroundExecutor.execute(loadLeaks);\n    }\n\n    static void forgetActivity() {\n      for (LoadLeaks loadLeaks : inFlight) {\n        loadLeaks.activityOrNull = null;\n      }\n      inFlight.clear();\n    }\n\n    DisplayLeakActivity activityOrNull;\n    private final LeakDirectoryProvider leakDirectoryProvider;\n    private final Handler mainHandler;\n\n    LoadLeaks(DisplayLeakActivity activity, LeakDirectoryProvider leakDirectoryProvider) {\n      this.activityOrNull = activity;\n      this.leakDirectoryProvider = leakDirectoryProvider;\n      mainHandler = new Handler(Looper.getMainLooper());\n    }\n\n    @Override\n    public void run() {\n      final List<AnalyzedHeap> leaks = new ArrayList<>();\n      List<File> files = leakDirectoryProvider.listFiles(new FilenameFilter() {\n        @Override\n        public boolean accept(File dir, String filename) {\n          return filename.endsWith(\".result\");\n        }\n      });\n      for (File resultFile : files) {\n        final AnalyzedHeap leak = AnalyzedHeap.load(resultFile);\n        if (leak != null) {\n          leaks.add(leak);\n        }\n      }\n      Collections.sort(leaks, new Comparator<AnalyzedHeap>() {\n        @Override\n        public int compare(AnalyzedHeap lhs, AnalyzedHeap rhs) {\n          return Long.valueOf(rhs.selfFile.lastModified())\n              .compareTo(lhs.selfFile.lastModified());\n        }\n      });\n      mainHandler.post(new Runnable() {\n        @Override\n        public void run() {\n          inFlight.remove(LoadLeaks.this);\n          if (activityOrNull != null) {\n            activityOrNull.leaks = leaks;\n            activityOrNull.updateUi();\n          }\n        }\n      });\n    }\n  }\n\n  static String classSimpleName(String className) {\n    int separator = className.lastIndexOf('.');\n    if (separator == -1) {\n      return className;\n    } else {\n      return className.substring(separator + 1);\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/DisplayLeakAdapter.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport androidx.annotation.ColorRes;\nimport android.text.Html;\nimport android.text.SpannableStringBuilder;\nimport android.text.Spanned;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.BaseAdapter;\nimport android.widget.TextView;\n\nimport com.squareup.leakcanary.R;\nimport com.squareup.leakcanary.Exclusion;\nimport com.squareup.leakcanary.LeakTrace;\nimport com.squareup.leakcanary.LeakTraceElement;\nimport com.squareup.leakcanary.Reachability;\n\nimport static com.squareup.leakcanary.LeakTraceElement.Type.STATIC_FIELD;\n\nfinal class DisplayLeakAdapter extends BaseAdapter {\n\n    private static final int TOP_ROW = 0;\n    private static final int NORMAL_ROW = 1;\n\n    private boolean[] opened = new boolean[0];\n\n    private LeakTrace leakTrace = null;\n    private String referenceKey;\n    private String referenceName = \"\";\n\n    private final String classNameColorHexString;\n    private final String leakColorHexString;\n    private final String referenceColorHexString;\n    private final String extraColorHexString;\n    private final String helpColorHexString;\n\n    DisplayLeakAdapter(Resources resources) {\n        classNameColorHexString = hexStringColor(resources, R.color.leak_canary_class_name);\n        leakColorHexString = hexStringColor(resources, R.color.leak_canary_leak);\n        referenceColorHexString = hexStringColor(resources, R.color.leak_canary_reference);\n        extraColorHexString = hexStringColor(resources, R.color.leak_canary_extra);\n        helpColorHexString = hexStringColor(resources, R.color.leak_canary_help);\n    }\n\n    // https://stackoverflow.com/a/6540378/703646\n    private static String hexStringColor(Resources resources, @ColorRes int colorResId) {\n        return String.format(\"#%06X\", (0xFFFFFF & resources.getColor(colorResId)));\n    }\n\n    @Override\n    public View getView(int position, View convertView, ViewGroup parent) {\n        Context context = parent.getContext();\n        if (getItemViewType(position) == TOP_ROW) {\n            if (convertView == null) {\n                convertView =\n                        LayoutInflater.from(context).inflate(R.layout.leak_canary_ref_top_row, parent, false);\n            }\n            TextView textView = findById(convertView, R.id.leak_canary_row_text);\n            textView.setText(context.getPackageName());\n        } else {\n            if (convertView == null) {\n                convertView =\n                        LayoutInflater.from(context).inflate(R.layout.leak_canary_ref_row, parent, false);\n            }\n\n            TextView titleView = findById(convertView, R.id.leak_canary_row_title);\n            TextView detailView = findById(convertView, R.id.leak_canary_row_details);\n            DisplayLeakConnectorView connector = findById(convertView, R.id.leak_canary_row_connector);\n            MoreDetailsView moreDetailsView = findById(convertView, R.id.leak_canary_row_more);\n\n            connector.setType(getConnectorType(position));\n            moreDetailsView.setOpened(opened[position]);\n\n            if (opened[position]) {\n                detailView.setVisibility(View.VISIBLE);\n            } else {\n                detailView.setVisibility(View.GONE);\n            }\n\n            Resources resources = convertView.getResources();\n            if (position == 1) {\n                titleView.setText(Html.fromHtml(\"<font color='\"\n                        + helpColorHexString\n                        + \"'>\"\n                        + \"<b>\" + resources.getString(R.string.leak_canary_help_title) + \"</b>\"\n                        + \"</font>\"));\n                SpannableStringBuilder detailText =\n                        (SpannableStringBuilder) Html.fromHtml(\n                                resources.getString(R.string.leak_canary_help_detail));\n                SquigglySpan.replaceUnderlineSpans(detailText, resources);\n                detailView.setText(detailText);\n            } else {\n                boolean isLeakingInstance = position == getCount() - 1;\n                LeakTraceElement element = getItem(position);\n\n                Reachability reachability = leakTrace.expectedReachability.get(elementIndex(position));\n                boolean maybeLeakCause;\n                if (isLeakingInstance || reachability == Reachability.UNREACHABLE) {\n                    maybeLeakCause = false;\n                } else {\n                    Reachability nextReachability =\n                            leakTrace.expectedReachability.get(elementIndex(position + 1));\n                    maybeLeakCause = nextReachability != Reachability.REACHABLE;\n                }\n\n                Spanned htmlTitle =\n                        htmlTitle(element, maybeLeakCause, resources);\n\n                titleView.setText(htmlTitle);\n\n                if (opened[position]) {\n                    Spanned htmlDetail = htmlDetails(isLeakingInstance, element);\n                    detailView.setText(htmlDetail);\n                }\n            }\n        }\n\n        return convertView;\n    }\n\n    private Spanned htmlTitle(LeakTraceElement element, boolean maybeLeakCause, Resources resources) {\n        String htmlString = \"\";\n\n        String simpleName = element.getSimpleClassName();\n        simpleName = simpleName.replace(\"[]\", \"[ ]\");\n\n        String styledClassName =\n                \"<font color='\" + classNameColorHexString + \"'>\" + simpleName + \"</font>\";\n\n        if (element.reference != null) {\n            String referenceName = element.reference.getDisplayName().replaceAll(\"<\", \"&lt;\")\n                    .replaceAll(\">\", \"&gt;\");\n\n            if (maybeLeakCause) {\n                referenceName =\n                        \"<u><font color='\" + leakColorHexString + \"'>\" + referenceName + \"</font></u>\";\n            } else {\n                referenceName =\n                        \"<font color='\" + referenceColorHexString + \"'>\" + referenceName + \"</font>\";\n            }\n\n            if (element.reference.type == STATIC_FIELD) {\n                referenceName = \"<i>\" + referenceName + \"</i>\";\n            }\n\n            String classAndReference = styledClassName + \".\" + referenceName;\n\n            if (maybeLeakCause) {\n                classAndReference = \"<b>\" + classAndReference + \"</b>\";\n            }\n\n            htmlString += classAndReference;\n        } else {\n            htmlString += styledClassName;\n        }\n\n        Exclusion exclusion = element.exclusion;\n        if (exclusion != null) {\n            htmlString += \" (excluded)\";\n        }\n        SpannableStringBuilder builder = (SpannableStringBuilder) Html.fromHtml(htmlString);\n        if (maybeLeakCause) {\n            SquigglySpan.replaceUnderlineSpans(builder, resources);\n        }\n\n        return builder;\n    }\n\n    private Spanned htmlDetails(boolean isLeakingInstance, LeakTraceElement element) {\n        String htmlString = \"\";\n        if (element.extra != null) {\n            htmlString += \" <font color='\" + extraColorHexString + \"'>\" + element.extra + \"</font>\";\n        }\n\n        Exclusion exclusion = element.exclusion;\n        if (exclusion != null) {\n            htmlString += \"<br/><br/>Excluded by rule\";\n            if (exclusion.name != null) {\n                htmlString += \" <font color='#ffffff'>\" + exclusion.name + \"</font>\";\n            }\n            htmlString += \" matching <font color='#f3cf83'>\" + exclusion.matching + \"</font>\";\n            if (exclusion.reason != null) {\n                htmlString += \" because <font color='#f3cf83'>\" + exclusion.reason + \"</font>\";\n            }\n        }\n        htmlString += \"<br>\"\n                + \"<font color='\" + extraColorHexString + \"'>\"\n                + element.toDetailedString().replace(\"\\n\", \"<br>\")\n                + \"</font>\";\n\n        if (isLeakingInstance && !referenceName.equals(\"\")) {\n            htmlString += \" <font color='\" + extraColorHexString + \"'>\" + referenceName + \"</font>\";\n        }\n\n        return Html.fromHtml(htmlString);\n    }\n\n    private DisplayLeakConnectorView.Type getConnectorType(int position) {\n        if (position == 1) {\n            return DisplayLeakConnectorView.Type.HELP;\n        } else if (position == 2) {\n            if (leakTrace.expectedReachability.size() == 1) {\n                return DisplayLeakConnectorView.Type.START_LAST_REACHABLE;\n            }\n            Reachability nextReachability =\n                    leakTrace.expectedReachability.get(elementIndex(position + 1));\n            if (nextReachability != Reachability.REACHABLE) {\n                return DisplayLeakConnectorView.Type.START_LAST_REACHABLE;\n            }\n            return DisplayLeakConnectorView.Type.START;\n        } else {\n            boolean isLeakingInstance = position == getCount() - 1;\n            if (isLeakingInstance) {\n                Reachability previousReachability =\n                        leakTrace.expectedReachability.get(elementIndex(position - 1));\n                if (previousReachability != Reachability.UNREACHABLE) {\n                    return DisplayLeakConnectorView.Type.END_FIRST_UNREACHABLE;\n                }\n                return DisplayLeakConnectorView.Type.END;\n            } else {\n                Reachability reachability = leakTrace.expectedReachability.get(elementIndex(position));\n                switch (reachability) {\n                    case UNKNOWN:\n                        return DisplayLeakConnectorView.Type.NODE_UNKNOWN;\n                    case REACHABLE:\n                        Reachability nextReachability =\n                                leakTrace.expectedReachability.get(elementIndex(position + 1));\n                        if (nextReachability != Reachability.REACHABLE) {\n                            return DisplayLeakConnectorView.Type.NODE_LAST_REACHABLE;\n                        } else {\n                            return DisplayLeakConnectorView.Type.NODE_REACHABLE;\n                        }\n                    case UNREACHABLE:\n                        Reachability previousReachability =\n                                leakTrace.expectedReachability.get(elementIndex(position - 1));\n                        if (previousReachability != Reachability.UNREACHABLE) {\n                            return DisplayLeakConnectorView.Type.NODE_FIRST_UNREACHABLE;\n                        } else {\n                            return DisplayLeakConnectorView.Type.NODE_UNREACHABLE;\n                        }\n                    default:\n                        throw new IllegalStateException(\"Unknown value: \" + reachability);\n                }\n            }\n        }\n    }\n\n    public void update(LeakTrace leakTrace, String referenceKey, String referenceName) {\n        if (referenceKey.equals(this.referenceKey)) {\n            // Same data, nothing to change.\n            return;\n        }\n        this.referenceKey = referenceKey;\n        this.referenceName = referenceName;\n        this.leakTrace = leakTrace;\n        opened = new boolean[2 + leakTrace.elements.size()];\n        notifyDataSetChanged();\n    }\n\n    /**\n     * 展开\n     * @param position\n     */\n    public void toggleRow(int position) {\n        opened[position] = !opened[position];\n        notifyDataSetChanged();\n    }\n\n    @Override\n    public int getCount() {\n        if (leakTrace == null) {\n            return 2;\n        }\n        return 2 + leakTrace.elements.size();\n    }\n\n    @Override\n    public LeakTraceElement getItem(int position) {\n        if (getItemViewType(position) == TOP_ROW) {\n            return null;\n        }\n        if (position == 1) {\n            return null;\n        }\n        return leakTrace.elements.get(elementIndex(position));\n    }\n\n    private int elementIndex(int position) {\n        return position - 2;\n    }\n\n    @Override\n    public int getViewTypeCount() {\n        return 2;\n    }\n\n    @Override\n    public int getItemViewType(int position) {\n        if (position == 0) {\n            return TOP_ROW;\n        }\n        return NORMAL_ROW;\n    }\n\n    @Override\n    public long getItemId(int position) {\n        return position;\n    }\n\n    @SuppressWarnings({\"unchecked\", \"TypeParameterUnusedInFormals\"})\n    private static <T extends View> T findById(View view, int id) {\n        return (T) view.findViewById(id);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/DisplayLeakConnectorView.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.DashPathEffect;\nimport android.graphics.Paint;\nimport android.graphics.PorterDuffXfermode;\nimport android.util.AttributeSet;\nimport android.view.View;\n\nimport com.squareup.leakcanary.R;\n\nimport static android.graphics.Bitmap.Config.ARGB_8888;\nimport static android.graphics.PorterDuff.Mode.CLEAR;\n\npublic final class DisplayLeakConnectorView extends View {\n\n  private static final float SQRT_TWO = (float) Math.sqrt(2);\n  private static final PorterDuffXfermode CLEAR_XFER_MODE = new PorterDuffXfermode(CLEAR);\n\n  public enum Type {\n    HELP,\n    START,\n    START_LAST_REACHABLE,\n    NODE_UNKNOWN,\n    NODE_FIRST_UNREACHABLE,\n    NODE_UNREACHABLE,\n    NODE_REACHABLE,\n    NODE_LAST_REACHABLE,\n    END,\n    END_FIRST_UNREACHABLE,\n  }\n\n  private final Paint classNamePaint;\n  private final Paint leakPaint;\n  private final Paint clearPaint;\n  private final Paint referencePaint;\n  private final float strokeSize;\n  private final float circleY;\n\n  private Type type;\n  private Bitmap cache;\n\n  public DisplayLeakConnectorView(Context context, AttributeSet attrs) {\n    super(context, attrs);\n\n    Resources resources = getResources();\n\n    type = Type.NODE_UNKNOWN;\n    circleY = resources.getDimensionPixelSize(R.dimen.leak_canary_connector_center_y);\n    strokeSize = resources.getDimensionPixelSize(R.dimen.leak_canary_connector_stroke_size);\n\n    classNamePaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n    classNamePaint.setColor(resources.getColor(R.color.leak_canary_class_name));\n    classNamePaint.setStrokeWidth(strokeSize);\n\n    leakPaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n    leakPaint.setColor(resources.getColor(R.color.leak_canary_leak));\n    leakPaint.setStyle(Paint.Style.STROKE);\n    leakPaint.setStrokeWidth(strokeSize);\n    float pathLines = resources.getDimensionPixelSize(R.dimen.leak_canary_connector_leak_dash_line);\n    float pathGaps = resources.getDimensionPixelSize(R.dimen.leak_canary_connector_leak_dash_gap);\n    leakPaint.setPathEffect(new DashPathEffect(new float[] { pathLines, pathGaps }, 0));\n\n    clearPaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n    clearPaint.setColor(Color.TRANSPARENT);\n    clearPaint.setXfermode(CLEAR_XFER_MODE);\n\n    referencePaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n    referencePaint.setColor(resources.getColor(R.color.leak_canary_reference));\n    referencePaint.setStrokeWidth(strokeSize);\n  }\n\n  @SuppressWarnings(\"SuspiciousNameCombination\") @Override\n  protected void onDraw(Canvas canvas) {\n    int width = getMeasuredWidth();\n    int height = getMeasuredHeight();\n\n    if (cache != null && (cache.getWidth() != width || cache.getHeight() != height)) {\n      cache.recycle();\n      cache = null;\n    }\n\n    if (cache == null) {\n      cache = Bitmap.createBitmap(width, height, ARGB_8888);\n\n      Canvas cacheCanvas = new Canvas(cache);\n\n      switch (type) {\n        case NODE_UNKNOWN:\n          drawItems(cacheCanvas, leakPaint, leakPaint);\n          break;\n        case NODE_UNREACHABLE:\n        case NODE_REACHABLE:\n          drawItems(cacheCanvas, referencePaint, referencePaint);\n          break;\n        case NODE_FIRST_UNREACHABLE:\n          drawItems(cacheCanvas, leakPaint, referencePaint);\n          break;\n        case NODE_LAST_REACHABLE:\n          drawItems(cacheCanvas, referencePaint, leakPaint);\n          break;\n        case START: {\n          drawStartLine(cacheCanvas);\n          drawItems(cacheCanvas, null, referencePaint);\n          break;\n        }\n        case START_LAST_REACHABLE:\n          drawStartLine(cacheCanvas);\n          drawItems(cacheCanvas, null, leakPaint);\n          break;\n        case END:\n          drawItems(cacheCanvas, referencePaint, null);\n          break;\n        case END_FIRST_UNREACHABLE:\n          drawItems(cacheCanvas, leakPaint, null);\n          break;\n        case HELP:\n          drawRoot(cacheCanvas);\n          break;\n        default:\n          throw new UnsupportedOperationException(\"Unknown type \" + type);\n      }\n    }\n    canvas.drawBitmap(cache, 0, 0, null);\n  }\n\n  private void drawStartLine(Canvas cacheCanvas) {\n    int width = getMeasuredWidth();\n    float halfWidth = width / 2f;\n    cacheCanvas.drawLine(halfWidth, 0, halfWidth, circleY, classNamePaint);\n  }\n\n  private void drawRoot(Canvas cacheCanvas) {\n    int width = getMeasuredWidth();\n    int height = getMeasuredHeight();\n    float halfWidth = width / 2f;\n    float radiusClear = halfWidth - strokeSize / 2f;\n    cacheCanvas.drawRect(0, 0, width, radiusClear, classNamePaint);\n    cacheCanvas.drawCircle(0, radiusClear, radiusClear, clearPaint);\n    cacheCanvas.drawCircle(width, radiusClear, radiusClear, clearPaint);\n    cacheCanvas.drawLine(halfWidth, 0, halfWidth, height, classNamePaint);\n  }\n\n  private void drawItems(Canvas cacheCanvas, Paint arrowHeadPaint, Paint nextArrowPaint) {\n    if (arrowHeadPaint != null) {\n      drawArrowHead(cacheCanvas, arrowHeadPaint);\n    }\n    if (nextArrowPaint != null) {\n      drawNextArrowLine(cacheCanvas, nextArrowPaint);\n    }\n    drawInstanceCircle(cacheCanvas);\n  }\n\n  private void drawArrowHead(Canvas cacheCanvas, Paint paint) {\n    // Circle center is at half height\n    int width = getMeasuredWidth();\n    float halfWidth = width / 2f;\n    float centerX = halfWidth;\n    float circleRadius = width / 3f;\n    float arrowSideLength = halfWidth;\n    // Splitting the arrow head in two makes an isosceles right triangle.\n    // It's hypotenuse is side * sqrt(2)\n    float arrowHeight = (arrowSideLength / 2) * SQRT_TWO;\n    float halfStrokeSize = strokeSize / 2;\n    float translateY = circleY - arrowHeight - (circleRadius * 2) - strokeSize;\n\n    float lineYEnd = circleY - circleRadius - (strokeSize / 2);\n    cacheCanvas.drawLine(centerX, 0, centerX, lineYEnd, paint);\n    cacheCanvas.translate(centerX, translateY);\n    cacheCanvas.rotate(45);\n    cacheCanvas.drawLine(0, arrowSideLength, arrowSideLength + halfStrokeSize, arrowSideLength,\n        paint);\n    cacheCanvas.drawLine(arrowSideLength, 0, arrowSideLength, arrowSideLength, paint);\n    cacheCanvas.rotate(-45);\n    cacheCanvas.translate(-centerX, -translateY);\n  }\n\n  private void drawNextArrowLine(Canvas cacheCanvas, Paint paint) {\n    int height = getMeasuredHeight();\n    int width = getMeasuredWidth();\n    float centerX = width / 2f;\n    cacheCanvas.drawLine(centerX, circleY, centerX, height, paint);\n  }\n\n  private void drawInstanceCircle(Canvas cacheCanvas) {\n    int width = getMeasuredWidth();\n    float circleX = width / 2f;\n    float circleRadius = width / 3f;\n    cacheCanvas.drawCircle(circleX, circleY, circleRadius, classNamePaint);\n  }\n\n  public void setType(Type type) {\n    if (type != this.type) {\n      this.type = type;\n      if (cache != null) {\n        cache.recycle();\n        cache = null;\n      }\n      invalidate();\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/ForegroundService.java",
    "content": "/*\n * Copyright (C) 2018 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport android.app.IntentService;\nimport android.app.Notification;\nimport android.content.Intent;\nimport android.os.IBinder;\nimport android.os.SystemClock;\nimport androidx.annotation.Nullable;\n\nimport com.squareup.leakcanary.R;\n\npublic abstract class ForegroundService extends IntentService {\n\n  private final int notificationContentTitleResId;\n  private final int notificationId;\n\n  public ForegroundService(String name, int notificationContentTitleResId) {\n    super(name);\n    this.notificationContentTitleResId = notificationContentTitleResId;\n    notificationId = (int) SystemClock.uptimeMillis();\n  }\n\n  @Override\n  public void onCreate() {\n    super.onCreate();\n    showForegroundNotification(100, 0, true,\n        getString(R.string.leak_canary_notification_foreground_text));\n  }\n\n  protected void showForegroundNotification(int max, int progress, boolean indeterminate,\n      String contentText) {\n    Notification.Builder builder = new Notification.Builder(this)\n        .setContentTitle(getString(notificationContentTitleResId))\n        .setContentText(contentText)\n        .setProgress(max, progress, indeterminate);\n    Notification notification = LeakCanaryInternals.buildNotification(this, builder);\n    startForeground(notificationId, notification);\n  }\n\n  /**\n   * service 启动时调用\n   * @param intent\n   */\n  @Override\n  protected void onHandleIntent(@Nullable Intent intent) {\n    onHandleIntentInForeground(intent);\n  }\n\n  protected abstract void onHandleIntentInForeground(@Nullable Intent intent);\n\n  @Override\n  public void onDestroy() {\n    super.onDestroy();\n    stopForeground(true);\n  }\n\n  @Override\n  public IBinder onBind(Intent intent) {\n    return null;\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/FragmentRefWatcher.java",
    "content": "/*\n * Copyright (C) 2018 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.content.Context;\nimport android.os.Bundle;\n\nimport com.squareup.leakcanary.RefWatcher;\n\nimport java.lang.reflect.Constructor;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static android.os.Build.VERSION.SDK_INT;\nimport static android.os.Build.VERSION_CODES.O;\n\n/**\n * Internal class used to watch for fragments leaks.\n */\npublic interface FragmentRefWatcher {\n\n  void watchFragments(Activity activity);\n\n  final class Helper {\n\n    private static final String SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME =\n        \"SupportFragmentRefWatcher\";\n\n    public static void install(Context context, RefWatcher refWatcher) {\n      List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();\n\n      if (SDK_INT >= O) {\n        fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));\n      }\n\n      try {\n        Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);\n        Constructor<?> constructor =\n            fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);\n        FragmentRefWatcher supportFragmentRefWatcher =\n            (FragmentRefWatcher) constructor.newInstance(refWatcher);\n        fragmentRefWatchers.add(supportFragmentRefWatcher);\n      } catch (Exception ignored) {\n      }\n\n      if (fragmentRefWatchers.size() == 0) {\n        return;\n      }\n\n      Helper helper = new Helper(fragmentRefWatchers);\n\n      Application application = (Application) context.getApplicationContext();\n      application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);\n    }\n\n    private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =\n        new ActivityLifecycleCallbacksAdapter() {\n          @Override\n          public void onActivityCreated(Activity activity, Bundle savedInstanceState) {\n            for (FragmentRefWatcher watcher : fragmentRefWatchers) {\n              watcher.watchFragments(activity);\n            }\n          }\n        };\n\n    private final List<FragmentRefWatcher> fragmentRefWatchers;\n\n    private Helper(List<FragmentRefWatcher> fragmentRefWatchers) {\n      this.fragmentRefWatchers = fragmentRefWatchers;\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/FutureResult.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\n\npublic final class FutureResult<T> {\n\n  private final AtomicReference<T> resultHolder;\n  private final CountDownLatch latch;\n\n  public FutureResult() {\n    resultHolder = new AtomicReference<>();\n    latch = new CountDownLatch(1);\n  }\n\n  public boolean wait(long timeout, TimeUnit unit) {\n    try {\n      return latch.await(timeout, unit);\n    } catch (InterruptedException e) {\n      throw new RuntimeException(\"Did not expect thread to be interrupted\", e);\n    }\n  }\n\n  public T get() {\n    if (latch.getCount() > 0) {\n      throw new IllegalStateException(\"Call wait() and check its result\");\n    }\n    return resultHolder.get();\n  }\n\n  public void set(T result) {\n    resultHolder.set(result);\n    latch.countDown();\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/HeapAnalyzerService.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport androidx.annotation.Nullable;\nimport androidx.core.content.ContextCompat;\n\nimport com.didichuxing.doraemonkit.util.Utils;\nimport com.didichuxing.doraemonkit.DoraemonKit;\nimport com.squareup.leakcanary.R;\nimport com.squareup.leakcanary.AbstractAnalysisResultService;\nimport com.squareup.leakcanary.AnalysisResult;\nimport com.squareup.leakcanary.AnalyzerProgressListener;\nimport com.squareup.leakcanary.CanaryLog;\nimport com.squareup.leakcanary.HeapAnalyzer;\nimport com.squareup.leakcanary.HeapDump;\n\n/**\n * This service runs in a separate process to avoid slowing down the app process or making it run\n * out of memory.\n * leakCanary 堆内存分下前台服务\n */\npublic final class HeapAnalyzerService extends ForegroundService\n        implements AnalyzerProgressListener {\n    private static final String TAG = \"HeapAnalyzerService\";\n    private static final String LISTENER_CLASS_EXTRA = \"listener_class_extra\";\n    private static final String HEAPDUMP_EXTRA = \"heapdump_extra\";\n\n    /**\n     * 时候过滤掉dokit sdk 导致的内存泄漏\n     */\n    private boolean isIgnoreDokit = true;\n\n    /**\n     * 启动当前服务\n     *\n     * @param context\n     * @param heapDump\n     * @param listenerServiceClass\n     */\n    public static void runAnalysis(Context context, HeapDump heapDump,\n                                   Class<? extends AbstractAnalysisResultService> listenerServiceClass) {\n        LeakCanaryInternals.setEnabledBlocking(context, HeapAnalyzerService.class, true);\n        LeakCanaryInternals.setEnabledBlocking(context, listenerServiceClass, true);\n        Intent intent = new Intent(context, HeapAnalyzerService.class);\n        intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());\n        intent.putExtra(HEAPDUMP_EXTRA, heapDump);\n        ContextCompat.startForegroundService(context, intent);\n    }\n\n    public HeapAnalyzerService() {\n        super(HeapAnalyzerService.class.getSimpleName(), R.string.leak_canary_notification_analysing);\n//        Utils.init(getApplication());\n    }\n\n    /**\n     * 服务启动时调用\n     *\n     * @param intent\n     */\n    @Override\n    protected void onHandleIntentInForeground(@Nullable Intent intent) {\n        if (intent == null) {\n            CanaryLog.d(\"HeapAnalyzerService received a null intent, ignoring.\");\n            return;\n        }\n        //接受外部传递进来参数\n        String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);\n        HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);\n\n        HeapAnalyzer heapAnalyzer =\n                new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);\n\n//        Instance instance = heapAnalyzer.preCheckForLeak(heapDump.heapDumpFile, heapDump.referenceKey);\n//        String leakClassName = instance.getClassObj().getClassName();\n//        LogHelper.i(TAG, \"====leakClassName====>\" + leakClassName);\n//        //过滤掉dokit内部的内存泄漏\n//        if (isIgnoreDokit && !TextUtils.isEmpty(leakClassName) && leakClassName.contains(\"com.didichuxing.doraemonkit\")) {\n//            return;\n//        }\n\n        /**\n         * 检查对象是否没有被回收\n         */\n        AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,\n                heapDump.computeRetainedHeapSize);\n\n        AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);\n    }\n\n\n    @Override\n    public void onProgressUpdate(Step step) {\n        int percent = (int) ((100f * step.ordinal()) / Step.values().length);\n        CanaryLog.d(\"Analysis in progress, working on: %s\", step.name());\n        String lowercase = step.name().replace(\"_\", \" \").toLowerCase();\n        String message = lowercase.substring(0, 1).toUpperCase() + lowercase.substring(1);\n        showForegroundNotification(100, percent, false, message);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/LeakCanaryFileProvider.java",
    "content": "package com.squareup.leakcanary.internal;\n\nimport androidx.core.content.FileProvider;\n\n/**\n * There can only be one {@link FileProvider} provider registered per app, so we extend that class\n * just to use a distinct name.\n */\npublic class LeakCanaryFileProvider extends FileProvider {\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/LeakCanaryInternals.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport android.app.ActivityManager;\nimport android.app.Notification;\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.app.PendingIntent;\nimport android.app.Service;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ServiceInfo;\nimport android.os.AsyncTask;\n\nimport com.squareup.leakcanary.R;\nimport com.squareup.leakcanary.CanaryLog;\nimport com.squareup.leakcanary.DefaultLeakDirectoryProvider;\nimport com.squareup.leakcanary.LeakDirectoryProvider;\nimport com.squareup.leakcanary.RefWatcher;\n\nimport java.util.List;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.Executors;\n\nimport static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;\nimport static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;\nimport static android.content.pm.PackageManager.DONT_KILL_APP;\nimport static android.content.pm.PackageManager.GET_SERVICES;\nimport static android.os.Build.VERSION.SDK_INT;\nimport static android.os.Build.VERSION_CODES.JELLY_BEAN;\nimport static android.os.Build.VERSION_CODES.O;\n\npublic final class LeakCanaryInternals {\n\n  public static final String SAMSUNG = \"samsung\";\n  public static final String MOTOROLA = \"motorola\";\n  public static final String LENOVO = \"LENOVO\";\n  public static final String LG = \"LGE\";\n  public static final String NVIDIA = \"NVIDIA\";\n  public static final String MEIZU = \"Meizu\";\n  public static final String HUAWEI = \"HUAWEI\";\n  public static final String VIVO = \"vivo\";\n\n  public static volatile RefWatcher installedRefWatcher;\n  private static volatile LeakDirectoryProvider leakDirectoryProvider;\n\n  private static final String NOTIFICATION_CHANNEL_ID = \"leakcanary\";\n\n  public static volatile Boolean isInAnalyzerProcess;\n\n  /** Extracts the class simple name out of a string containing a fully qualified class name. */\n  public static String classSimpleName(String className) {\n    int separator = className.lastIndexOf('.');\n    if (separator == -1) {\n      return className;\n    } else {\n      return className.substring(separator + 1);\n    }\n  }\n\n  public static void setEnabledAsync(Context context, final Class<?> componentClass,\n                                     final boolean enabled) {\n    final Context appContext = context.getApplicationContext();\n    AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {\n      @Override\n      public void run() {\n        setEnabledBlocking(appContext, componentClass, enabled);\n      }\n    });\n  }\n\n  public static void setEnabledBlocking(Context appContext, Class<?> componentClass,\n                                        boolean enabled) {\n    ComponentName component = new ComponentName(appContext, componentClass);\n    PackageManager packageManager = appContext.getPackageManager();\n    int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;\n    // Blocks on IPC.\n    packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);\n  }\n\n  public static boolean isInServiceProcess(Context context, Class<? extends Service> serviceClass) {\n    PackageManager packageManager = context.getPackageManager();\n    PackageInfo packageInfo;\n    try {\n      packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);\n    } catch (Exception e) {\n      CanaryLog.d(e, \"Could not get package info for %s\", context.getPackageName());\n      return false;\n    }\n    String mainProcess = packageInfo.applicationInfo.processName;\n\n    ComponentName component = new ComponentName(context, serviceClass);\n    ServiceInfo serviceInfo;\n    try {\n      serviceInfo = packageManager.getServiceInfo(component, PackageManager.GET_DISABLED_COMPONENTS);\n    } catch (PackageManager.NameNotFoundException ignored) {\n      // Service is disabled.\n      return false;\n    }\n\n    if (serviceInfo.processName.equals(mainProcess)) {\n      CanaryLog.d(\"Did not expect service %s to run in main process %s\", serviceClass, mainProcess);\n      // Technically we are in the service process, but we're not in the service dedicated process.\n      return false;\n    }\n\n    int myPid = android.os.Process.myPid();\n    ActivityManager activityManager =\n        (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);\n    ActivityManager.RunningAppProcessInfo myProcess = null;\n    List<ActivityManager.RunningAppProcessInfo> runningProcesses;\n    try {\n      runningProcesses = activityManager.getRunningAppProcesses();\n    } catch (SecurityException exception) {\n      // https://github.com/square/leakcanary/issues/948\n      CanaryLog.d(\"Could not get running app processes %d\", exception);\n      return false;\n    }\n    if (runningProcesses != null) {\n      for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {\n        if (process.pid == myPid) {\n          myProcess = process;\n          break;\n        }\n      }\n    }\n    if (myProcess == null) {\n      CanaryLog.d(\"Could not find running process for %d\", myPid);\n      return false;\n    }\n\n    return myProcess.processName.equals(serviceInfo.processName);\n  }\n\n  public static void showNotification(Context context, CharSequence contentTitle,\n                                      CharSequence contentText, PendingIntent pendingIntent, int notificationId) {\n    Notification.Builder builder = new Notification.Builder(context)\n        .setContentText(contentText)\n        .setContentTitle(contentTitle)\n        .setAutoCancel(true)\n        .setContentIntent(pendingIntent);\n\n    Notification notification = buildNotification(context, builder);\n    NotificationManager notificationManager =\n        (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);\n    notificationManager.notify(notificationId, notification);\n  }\n\n  public static Notification buildNotification(Context context,\n                                               Notification.Builder builder) {\n    builder.setSmallIcon(R.mipmap.leak_canary_notification)\n        .setWhen(System.currentTimeMillis())\n        .setOnlyAlertOnce(true);\n\n    if (SDK_INT >= O) {\n      NotificationManager notificationManager =\n          (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);\n      NotificationChannel notificationChannel =\n          notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID);\n      if (notificationChannel == null) {\n        String channelName = context.getString(R.string.leak_canary_notification_channel);\n        notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName,\n            NotificationManager.IMPORTANCE_DEFAULT);\n        notificationManager.createNotificationChannel(notificationChannel);\n      }\n      builder.setChannelId(NOTIFICATION_CHANNEL_ID);\n    }\n\n    if (SDK_INT < JELLY_BEAN) {\n      return builder.getNotification();\n    } else {\n      return builder.build();\n    }\n  }\n\n  public static Executor newSingleThreadExecutor(String threadName) {\n    return Executors.newSingleThreadExecutor(new LeakCanarySingleThreadFactory(threadName));\n  }\n\n  public static void setLeakDirectoryProvider(LeakDirectoryProvider leakDirectoryProvider) {\n    if (LeakCanaryInternals.leakDirectoryProvider != null) {\n      throw new IllegalStateException(\"Cannot set the LeakDirectoryProvider after it has already \"\n          + \"been set. Try setting it before installing the RefWatcher.\");\n    }\n    LeakCanaryInternals.leakDirectoryProvider = leakDirectoryProvider;\n  }\n\n  public static LeakDirectoryProvider getLeakDirectoryProvider(Context context) {\n    LeakDirectoryProvider leakDirectoryProvider = LeakCanaryInternals.leakDirectoryProvider;\n    if (leakDirectoryProvider == null) {\n      leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);\n    }\n    return leakDirectoryProvider;\n  }\n\n  private LeakCanaryInternals() {\n    throw new AssertionError();\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/LeakCanarySingleThreadFactory.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport java.util.concurrent.ThreadFactory;\n\n/**\n * This is intended to only be used with a single thread executor.\n */\nfinal class LeakCanarySingleThreadFactory implements ThreadFactory {\n\n  private final String threadName;\n\n  LeakCanarySingleThreadFactory(String threadName) {\n    this.threadName = \"LeakCanary-\" + threadName;\n  }\n\n  @Override\n  public Thread newThread(Runnable runnable) {\n    return new Thread(runnable, threadName);\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/MoreDetailsView.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.util.AttributeSet;\nimport android.view.View;\n\nimport com.squareup.leakcanary.R;\n\npublic final class MoreDetailsView extends View {\n\n  private final Paint iconPaint;\n\n  public MoreDetailsView(Context context, AttributeSet attrs) {\n    super(context, attrs);\n    Resources resources = getResources();\n    iconPaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n    float strokeSize = resources.getDimensionPixelSize(R.dimen.leak_canary_more_stroke_width);\n    iconPaint.setStrokeWidth(strokeSize);\n\n    // This lint check doesn't work for libraries which have a common prefix.\n    @SuppressLint(\"CustomViewStyleable\") //\n            TypedArray a =\n        context.obtainStyledAttributes(attrs, R.styleable.leak_canary_MoreDetailsView);\n    int plusColor =\n        a.getColor(R.styleable.leak_canary_MoreDetailsView_leak_canary_plus_color, Color.BLACK);\n    a.recycle();\n\n    iconPaint.setColor(plusColor);\n  }\n\n  private boolean opened;\n\n  @Override\n  protected void onDraw(Canvas canvas) {\n    int width = getWidth();\n    int height = getHeight();\n    int halfHeight = height / 2;\n    int halfWidth = width / 2;\n\n    if (opened) {\n      canvas.drawLine(0, halfHeight, width, halfHeight, iconPaint);\n    } else {\n      canvas.drawLine(0, halfHeight, width, halfHeight, iconPaint);\n      canvas.drawLine(halfWidth, 0, halfWidth, height, iconPaint);\n    }\n  }\n\n  public void setOpened(boolean opened) {\n    if (opened != this.opened) {\n      this.opened = opened;\n      invalidate();\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/RequestStoragePermissionActivity.java",
    "content": "/*\n * Copyright (C) 2016 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.app.PendingIntent;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.widget.Toast;\n\nimport com.squareup.leakcanary.R;\n\nimport static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;\nimport static android.app.PendingIntent.FLAG_UPDATE_CURRENT;\nimport static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;\nimport static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;\nimport static android.content.pm.PackageManager.PERMISSION_GRANTED;\nimport static android.os.Build.VERSION_CODES.M;\nimport static android.widget.Toast.LENGTH_LONG;\nimport static com.squareup.leakcanary.internal.LeakCanaryInternals.setEnabledBlocking;\n\n@TargetApi(M) //\npublic class RequestStoragePermissionActivity extends Activity {\n\n  public static PendingIntent createPendingIntent(Context context) {\n    setEnabledBlocking(context, RequestStoragePermissionActivity.class, true);\n    Intent intent = new Intent(context, RequestStoragePermissionActivity.class);\n    intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);\n    return PendingIntent.getActivity(context, 1, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE);\n  }\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    if (savedInstanceState == null) {\n      if (hasStoragePermission()) {\n        finish();\n        return;\n      }\n      String[] permissions = {\n          WRITE_EXTERNAL_STORAGE\n      };\n      requestPermissions(permissions, 42);\n    }\n  }\n\n  @Override\n  public void onRequestPermissionsResult(int requestCode, String[] permissions,\n                                         int[] grantResults) {\n    if (!hasStoragePermission()) {\n      Toast.makeText(getApplication(), R.string.leak_canary_permission_not_granted, LENGTH_LONG)\n          .show();\n    }\n    finish();\n  }\n\n  @Override\n  public void finish() {\n    // Reset the animation to avoid flickering.\n    overridePendingTransition(0, 0);\n    super.finish();\n  }\n\n  private boolean hasStoragePermission() {\n    return checkSelfPermission(WRITE_EXTERNAL_STORAGE) == PERMISSION_GRANTED;\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/RowElementLayout.java",
    "content": "/*\n * Copyright (C) 2018 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.util.AttributeSet;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.squareup.leakcanary.R;\n\n\npublic class RowElementLayout extends ViewGroup {\n\n  private final int connectorWidth;\n  private final int rowMargins;\n  private final int moreSize;\n  private final int minHeight;\n  private final int titleMarginTop;\n  private final int moreMarginTop;\n\n  private View connector;\n  private View moreButton;\n  private View title;\n  private View details;\n\n  public RowElementLayout(Context context, AttributeSet attrs) {\n    super(context, attrs);\n    Resources resources = getResources();\n    connectorWidth = resources.getDimensionPixelSize(R.dimen.leak_canary_connector_width);\n    rowMargins = resources.getDimensionPixelSize(R.dimen.leak_canary_row_margins);\n    moreSize = resources.getDimensionPixelSize(R.dimen.leak_canary_more_size);\n    minHeight = resources.getDimensionPixelSize(R.dimen.leak_canary_row_min);\n    titleMarginTop = resources.getDimensionPixelSize(R.dimen.leak_canary_row_title_margin_top);\n    moreMarginTop = resources.getDimensionPixelSize(R.dimen.leak_canary_more_margin_top);\n  }\n\n  @Override\n  protected void onFinishInflate() {\n    super.onFinishInflate();\n    connector = findViewById(R.id.leak_canary_row_connector);\n    moreButton = findViewById(R.id.leak_canary_row_more);\n    title = findViewById(R.id.leak_canary_row_title);\n    details = findViewById(R.id.leak_canary_row_details);\n  }\n\n  @Override\n  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n    int availableWidth = MeasureSpec.getSize(widthMeasureSpec);\n    int titleWidth = availableWidth - connectorWidth - moreSize - 4 * rowMargins;\n    int titleWidthSpec = MeasureSpec.makeMeasureSpec(titleWidth, MeasureSpec.AT_MOST);\n    int titleHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);\n    title.measure(titleWidthSpec, titleHeightSpec);\n\n    int moreSizeSpec = MeasureSpec.makeMeasureSpec(moreSize, MeasureSpec.EXACTLY);\n    moreButton.measure(moreSizeSpec, moreSizeSpec);\n\n    int totalHeight = titleMarginTop + title.getMeasuredHeight();\n\n    int detailsWidth = availableWidth - connectorWidth - 3 * rowMargins;\n    int detailsWidthSpec = MeasureSpec.makeMeasureSpec(detailsWidth, MeasureSpec.AT_MOST);\n    int detailsHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);\n    details.measure(detailsWidthSpec, detailsHeightSpec);\n    if (details.getVisibility() != GONE) {\n      totalHeight += details.getMeasuredHeight();\n    }\n    totalHeight = Math.max(totalHeight, minHeight);\n\n    int connectorWidthSpec = MeasureSpec.makeMeasureSpec(connectorWidth, MeasureSpec.EXACTLY);\n    int connectorHeightSpec = MeasureSpec.makeMeasureSpec(totalHeight, MeasureSpec.EXACTLY);\n\n    connector.measure(connectorWidthSpec, connectorHeightSpec);\n    setMeasuredDimension(availableWidth, totalHeight);\n  }\n\n  @Override\n  protected void onLayout(boolean changed, int l, int t, int r, int b) {\n    int width = getMeasuredWidth();\n    int connectorRight = rowMargins + connector.getMeasuredWidth();\n    connector.layout(rowMargins, 0, connectorRight, connector.getMeasuredHeight());\n\n    moreButton.layout(width - rowMargins - moreSize, moreMarginTop, width - rowMargins,\n        moreMarginTop + moreSize);\n\n    int titleLeft = connectorRight + rowMargins;\n    int titleBottom = titleMarginTop + title.getMeasuredHeight();\n    title.layout(titleLeft, titleMarginTop, titleLeft + title.getMeasuredWidth(), titleBottom);\n\n    if (details.getVisibility() != GONE) {\n      details.layout(titleLeft, titleBottom, width - rowMargins,\n          titleBottom + details.getMeasuredHeight());\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/SquigglySpan.java",
    "content": "/*\n * Copyright (C) 2018 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport android.content.res.Resources;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.text.SpannableStringBuilder;\nimport android.text.style.ReplacementSpan;\nimport android.text.style.UnderlineSpan;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.squareup.leakcanary.R;\n\n/**\n * Inspired from https://github.com/flavienlaurent/spans and\n * https://github.com/andyxialm/WavyLineView\n */\nclass SquigglySpan extends ReplacementSpan {\n\n\n  public static void replaceUnderlineSpans(SpannableStringBuilder builder, Resources resources) {\n    UnderlineSpan[] underlineSpans = builder.getSpans(0, builder.length(), UnderlineSpan.class);\n    for (UnderlineSpan span : underlineSpans) {\n      int start = builder.getSpanStart(span);\n      int end = builder.getSpanEnd(span);\n      builder.removeSpan(span);\n      builder.setSpan(new SquigglySpan(resources), start, end, 0);\n    }\n  }\n\n  private final Paint squigglyPaint;\n  private final Path path;\n  private final int referenceColor;\n  private final float halfStrokeWidth;\n  private final float amplitude;\n  private final float halfWaveHeight;\n  private final float periodDegrees;\n\n  private int width;\n\n  SquigglySpan(Resources resources) {\n    squigglyPaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n    squigglyPaint.setStyle(Paint.Style.STROKE);\n    squigglyPaint.setColor(resources.getColor(R.color.leak_canary_leak));\n    float strokeWidth =\n        resources.getDimensionPixelSize(R.dimen.leak_canary_squiggly_span_stroke_width);\n    squigglyPaint.setStrokeWidth(strokeWidth);\n\n    halfStrokeWidth = strokeWidth / 2;\n    amplitude = resources.getDimensionPixelSize(R.dimen.leak_canary_squiggly_span_amplitude);\n    periodDegrees =\n        resources.getDimensionPixelSize(R.dimen.leak_canary_squiggly_span_period_degrees);\n    path = new Path();\n    float waveHeight = 2 * amplitude + strokeWidth;\n    halfWaveHeight = waveHeight / 2;\n    referenceColor = resources.getColor(R.color.leak_canary_reference);\n  }\n\n  @Override\n  public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,\n                     @Nullable Paint.FontMetricsInt fm) {\n    width = (int) paint.measureText(text, start, end);\n    return width;\n  }\n\n  @Override\n  public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top,\n                   int y, int bottom, @NonNull Paint paint) {\n    squigglyHorizontalPath(path,\n        x + halfStrokeWidth,\n        x + width - halfStrokeWidth,\n        bottom - halfWaveHeight,\n        amplitude, periodDegrees);\n    canvas.drawPath(path, squigglyPaint);\n\n    paint.setColor(referenceColor);\n    canvas.drawText(text, start, end, x, y, paint);\n  }\n\n  private static void squigglyHorizontalPath(Path path, float left, float right, float centerY,\n                                             float amplitude,\n                                             float periodDegrees) {\n    path.reset();\n\n    float y;\n    path.moveTo(left, centerY);\n    float period = (float) (2 * Math.PI / periodDegrees);\n\n    for (float x = 0; x <= right - left; x += 1) {\n      y = (float) (amplitude * Math.sin(40 + period * x) + centerY);\n      path.lineTo(left + x, y);\n    }\n  }\n}"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/internal/SupportFragmentRefWatcher.java",
    "content": "/*\n * Copyright (C) 2018 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.leakcanary.internal;\n\nimport android.app.Activity;\nimport android.view.View;\n\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentActivity;\nimport androidx.fragment.app.FragmentManager;\n\nimport com.squareup.leakcanary.RefWatcher;\n\nclass SupportFragmentRefWatcher implements FragmentRefWatcher {\n  private final RefWatcher refWatcher;\n\n  SupportFragmentRefWatcher(RefWatcher refWatcher) {\n    this.refWatcher = refWatcher;\n  }\n\n  private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =\n      new FragmentManager.FragmentLifecycleCallbacks() {\n\n        @Override\n        public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {\n          View view = fragment.getView();\n          if (view != null) {\n            refWatcher.watch(view);\n          }\n        }\n\n        @Override\n        public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {\n          refWatcher.watch(fragment);\n        }\n      };\n\n  @Override\n  public void watchFragments(Activity activity) {\n    if (activity instanceof FragmentActivity) {\n      FragmentManager supportFragmentManager =\n          ((FragmentActivity) activity).getSupportFragmentManager();\n      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);\n    }\n  }\n}\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/drawable/leak_canary_icon_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"108dp\"\n        android:height=\"108dp\"\n        android:viewportWidth=\"108.0\"\n        android:viewportHeight=\"108.0\">\n    <path\n        android:pathData=\"M108,72.77L75.27,37.54C75.27,37.54 75.36,46.84 75.27,50.38C75.97,68.02 62.84,77.92 54.23,80C51.3,79.28 47.86,77.67 44.62,75.16C44.62,75.16 54.84,86.1 75.27,108L108,108L108,72.77Z\"\n        android:strokeColor=\"#00000000\"\n        android:fillType=\"evenOdd\"\n        android:fillAlpha=\"0.02\"\n        android:fillColor=\"#000000\"\n        android:strokeWidth=\"1\"/>\n    <path\n        android:pathData=\"M33.01,37.54L54.23,28L75.27,37.54C75.27,37.54 75.36,46.84 75.27,50.38C75.97,68.02 62.84,77.92 54.23,80C45.62,77.9 32.65,68.05 33.01,50.38C33.09,46.2 33.01,37.54 33.01,37.54ZM42.36,66.8C42.36,66.8 43.76,64.63 46.14,63.54C46.8,62.76 47.24,61.63 48.35,60.54C48.97,59.94 50.02,59.21 51,58.84C51.99,58.46 53.16,58.27 54.25,58.3C55.35,58.32 56.21,58.49 57.57,58.98C61,60.25 65.15,65.58 66.01,67.88C63.18,69.64 60.5,71.59 57.57,75.08C56.96,74.81 55.42,74.26 52.9,74.55C49.84,75.19 46.9,70.85 47.04,68.48C45.24,67.81 42.36,66.8 42.36,66.8ZM51.95,65.31C51.46,65.31 51.15,65.06 51.09,64.82C51.03,64.59 51.38,64.03 51.58,63.92C52.62,63.38 53.79,63.46 53.79,63.46C53.79,63.46 54.15,65.33 51.95,65.31ZM51.86,48.89L56.53,48.89L56.53,53.75L51.86,53.75L51.86,48.89ZM51.86,36.18L56.53,36.18L56.53,46.52L51.86,46.52L51.86,36.18Z\"\n        android:strokeColor=\"#00000000\"\n        android:fillType=\"evenOdd\"\n        android:fillColor=\"#FDED00\"\n        android:strokeWidth=\"1\"/>\n</vector>\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/drawable/leak_canary_toast_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <solid android:color=\"#cc000000\"/>\n  <corners android:radius=\"16dp\"/>\n</shape>"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/layout/leak_canary_display_leak.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (C) 2015 Square, Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:background=\"@color/leak_canary_background_color\"\n    >\n  <ListView\n      android:id=\"@+id/leak_canary_display_leak_list\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"0dp\"\n      android:layout_weight=\"1\"\n      android:dividerHeight=\"0dp\"\n      android:divider=\"@null\"\n      />\n  <TextView\n      android:id=\"@+id/leak_canary_display_leak_failure\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"0dp\"\n      android:layout_weight=\"1\"\n      android:textSize=\"12sp\"\n      android:visibility=\"gone\"\n      />\n  <Button\n      android:id=\"@+id/leak_canary_action\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:visibility=\"gone\"\n      />\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/layout/leak_canary_heap_dump_toast.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (C) 2015 Square, Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:background=\"@drawable/leak_canary_toast_background\"\n    android:padding=\"16dp\"\n    >\n  <ImageView\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center_horizontal\"\n      android:src=\"@mipmap/leak_canary_icon\"/>\n\n  <TextView\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center_horizontal\"\n      android:textColor=\"#ffffff\"\n      android:textSize=\"18sp\"\n      android:text=\"@string/leak_canary_toast_heap_dump\"\n      />\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/layout/leak_canary_leak_row.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (C) 2015 Square, Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:minHeight=\"48dp\"\n    android:padding=\"16dp\"\n    android:orientation=\"horizontal\"\n    >\n\n  <TextView\n      android:id=\"@+id/leak_canary_row_text\"\n      android:layout_width=\"0dp\"\n      android:layout_weight=\"1\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center_vertical\"\n      android:textSize=\"18sp\"\n      />\n\n  <TextView\n      android:id=\"@+id/leak_canary_row_time\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center_vertical\"\n      android:textColor=\"#919191\"\n      android:textSize=\"14sp\"\n      />\n\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/layout/leak_canary_ref_row.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (C) 2015 Square, Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<com.squareup.leakcanary.internal.RowElementLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:lib=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:id=\"@+id/leak_canary_row_layout\"\n    android:clipChildren=\"false\"\n    >\n\n  <com.squareup.leakcanary.internal.DisplayLeakConnectorView\n      android:id=\"@+id/leak_canary_row_connector\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      />\n\n  <com.squareup.leakcanary.internal.MoreDetailsView\n      android:id=\"@+id/leak_canary_row_more\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      lib:leak_canary_plus_color=\"@color/leak_canary_class_name\"\n      />\n\n  <TextView\n      android:id=\"@+id/leak_canary_row_title\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      />\n\n  <TextView\n      android:id=\"@+id/leak_canary_row_details\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:visibility=\"gone\"\n      />\n\n</com.squareup.leakcanary.internal.RowElementLayout>"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/layout/leak_canary_ref_top_row.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (C) 2015 Square, Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<TextView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/leak_canary_row_text\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"16dp\"\n    android:textSize=\"18sp\"\n    android:background=\"@color/leak_canary_class_name\"\n    android:padding=\"16dp\"\n    android:textColor=\"@color/leak_canary_background_color\"\n    />\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/values/leak_canary_attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (C) 2018 Square, Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<resources>\n  <declare-styleable name=\"leak_canary_MoreDetailsView\">\n    <attr name=\"leak_canary_plus_color\" format=\"color\" />\n  </declare-styleable>\n</resources>\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/values/leak_canary_colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (C) 2018 Square, Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<resources>\n  <color name=\"leak_canary_class_name\">#bababa</color>\n  <color name=\"leak_canary_leak\">#be383f</color>\n  <color name=\"leak_canary_reference\">#9976a8</color>\n  <color name=\"leak_canary_extra\">#919191</color>\n  <color name=\"leak_canary_help\">#6a98b9</color>\n  <color name=\"leak_canary_background_color\">#2b2b2b</color>\n</resources>\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/values/leak_canary_dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (C) 2018 Square, Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<resources>\n  <dimen name=\"leak_canary_squiggly_span_stroke_width\">1dp</dimen>\n  <dimen name=\"leak_canary_squiggly_span_amplitude\">1dp</dimen>\n  <!-- DP because the period increased with the density. -->\n  <dimen name=\"leak_canary_squiggly_span_period_degrees\">4dp</dimen>\n  <dimen name=\"leak_canary_connector_center_y\">24dp</dimen>\n  <dimen name=\"leak_canary_connector_stroke_size\">2dp</dimen>\n  <dimen name=\"leak_canary_connector_leak_dash_line\">5dp</dimen>\n  <dimen name=\"leak_canary_connector_leak_dash_gap\">1dp</dimen>\n  <dimen name=\"leak_canary_more_stroke_width\">2dp</dimen>\n  <dimen name=\"leak_canary_connector_width\">16dp</dimen>\n  <dimen name=\"leak_canary_row_margins\">16dp</dimen>\n  <dimen name=\"leak_canary_more_size\">12dp</dimen>\n  <dimen name=\"leak_canary_more_margin_top\">18dp</dimen>\n  <dimen name=\"leak_canary_row_min\">48dp</dimen>\n  <dimen name=\"leak_canary_row_title_margin_top\">14.5dp</dimen>\n\n</resources>\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/values/leak_canary_icon_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <color name=\"leak_canary_icon_background\">#3C3C3C</color>\n</resources>\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/values/leak_canary_public.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (C) 2015 Square, Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<resources>\n\n  <public name=\"leak_canary_display_activity_label\" type=\"string\"/>\n  <public name=\"leak_canary_heap_dump_toast\" type=\"layout\"/>\n  <public name=\"leak_canary_icon\" type=\"mipmap\"/>\n\n</resources>\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/values/leak_canary_strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (C) 2015 Square, Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<resources>\n  <string name=\"leak_canary_class_has_leaked\">%1$s leaked</string>\n  <string name=\"leak_canary_class_has_leaked_retaining\">%1$s leaked %2$s</string>\n  <string name=\"leak_canary_class_no_leak\">%1$s was never GCed but no leak found</string>\n  <string name=\"leak_canary_download_dump\">You can download the heap dump via \\\"Menu > Share Heap Dump\\\" or \\\"adb pull %1$s\\\"</string>\n  <string name=\"leak_canary_leak_excluded\">[Excluded] %1$s leaked</string>\n  <string name=\"leak_canary_leak_excluded_retaining\">[Excluded] %1$s leaked %2$s</string>\n  <string name=\"leak_canary_analysis_failed\">Leak analysis failed</string>\n  <string name=\"leak_canary_leak_list_title\">Leaks in %s</string>\n  <string name=\"leak_canary_notification_analysing\">Analyzing Heap Dump</string>\n  <string name=\"leak_canary_notification_channel\">LeakCanary</string>\n  <string name=\"leak_canary_notification_dumping\">Dumping Heap</string>\n  <string name=\"leak_canary_notification_foreground_text\">LeakCanary is working.</string>\n  <string name=\"leak_canary_notification_message\">Click for more details</string>\n  <string name=\"leak_canary_notification_reporting\">Reporting LeakCanary result</string>\n  <string name=\"leak_canary_no_leak_details\">LeakCanary could not find a valid path to GC roots. Download the heap dump and investigate with MAT or YourKit.</string>\n  <string name=\"leak_canary_result_failure_no_disk_space\">The analysis result could not be saved to disk</string>\n  <string name=\"leak_canary_result_failure_no_file\">The analysis result could not be loaded from disk</string>\n  <string name=\"leak_canary_result_failure_title\">Analysis result failure</string>\n  <string name=\"leak_canary_share_leak\">Share info</string>\n  <string name=\"leak_canary_share_heap_dump\">Share heap dump</string>\n  <string name=\"leak_canary_share_with\">Share with…</string>\n  <string name=\"leak_canary_display_activity_label\">Leaks</string>\n  <string name=\"leak_canary_storage_permission_activity_label\">Storage permission</string>\n  <string name=\"leak_canary_toast_heap_dump\">Dumping memory, app will freeze. Brrrr.</string>\n  <string name=\"leak_canary_delete\">Delete</string>\n  <string name=\"leak_canary_failure_report\">\"Please report this failure to http://github.com/square/leakcanary and share the heapdump file that caused it.\\n\"</string>\n  <string name=\"leak_canary_delete_all\">Delete all</string>\n  <string name=\"leak_canary_delete_all_leaks_title\">Are you sure you want to delete all leaks?</string>\n  <string name=\"leak_canary_could_not_save_text\">LeakCanary was unable to save the analysis result.</string>\n  <string name=\"leak_canary_excluded_row\">[Excluded] %s</string>\n  <string name=\"leak_canary_permission_not_granted\">Please grant external storage permission, otherwise memory leaks will not be detected.</string>\n  <string name=\"leak_canary_permission_notification_title\">Leak detected, need permission</string>\n  <string name=\"leak_canary_permission_notification_text\">Click to enable storage permission for %s.</string>\n  <string name=\"leak_canary_help_title\">Tap here to learn more</string>\n  <string name=\"leak_canary_help_detail\"><![CDATA[A memory leak is a programming error that causes\n  your application to keep a reference to an object that is no longer needed. As a result, the\n  memory allocated for that object cannot be reclaimed, eventually leading to an OutOfMemoryError\n  crash.<br>\n  <br>For instance, an Android activity instance is no longer needed after its <i>onDestroy()</i>\n  method is called, and storing a reference to that activity in a static field would prevent it from\n  being garbage collected.<br>\n  <br>\n  LeakCanary identifies an object that is no longer needed and finds the chain of\n  <font color=\\'#9976a8\\'>references</font> that prevents it from being garbage collected.<br>\n  <br>\n  To fix a memory leak, you need to look at that chain and find which reference is causing the\n  leak, i.e. which reference should have been cleared at the time of the leak. LeakCanary highlights\n  with a red underline wave the <b><u><font color=\\'#9976a8\\'>references</font></u></b> that are the\n  possible causes of the leak.<br>\n  <br>\n  Tap on each reference row for more details, tap again to close.\n]]></string>\n</resources>\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/values/leak_canary_themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright (C) 2015 Square, Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<resources>\n\n    <style name=\"leak_canary_LeakCanary.Base\" parent=\"android:Theme.Holo\"></style>\n\n    <style name=\"leak_canary_Theme.Transparent\" parent=\"android:Theme\">\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"android:backgroundDimEnabled\">false</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/values/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"dk_frameinfo_leakcanary\">LeakCanary</string>\n</resources>\n"
  },
  {
    "path": "Android/dokit-leakcanary/src/main/res/xml/leak_canary_file_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n  <external-path name=\"downloads\" path=\"Download/\" />\n  <files-path name=\"leakcanary\" path=\"leakcanary/\" />\n\n</paths>\n"
  },
  {
    "path": "Android/dokit-leakcanary/upload.sh",
    "content": "#!/usr/bin/env bash\necho -n  \"please enter bintray userid ->\"\nread  userid_bintray\necho -n  \"please enter bintray apikey ->\"\nread  apikey_bintray\n../gradlew clean build --stacktrace  --info   bintrayUpload -PbintrayUser=${userid_bintray} -PbintrayKey=${apikey_bintray} -PdryRun=false"
  },
  {
    "path": "Android/dokit-mc/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "Android/dokit-mc/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-kapt'\napply from: '../upload.gradle'\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_21\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n\n        lintOptions {\n            abortOnError false\n        }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n\n    /**\n     * 支持ViewBinding\n     */\n    buildFeatures {\n        //viewBinding = true\n    }\n\n}\n\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    testImplementation 'junit:junit:4.12'\n    androidTestImplementation 'junit:junit:4.12'\n    if (needKotlinV14()) {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v14\"]\n    } else {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v13\"]\n    }\n    implementation rootProject.ext.dependencies[\"core-ktx\"]\n    implementation rootProject.ext.dependencies[\"fragment-ktx\"]\n    implementation rootProject.ext.dependencies[\"appcompat\"]\n    implementation rootProject.ext.dependencies[\"cardview\"]\n    implementation rootProject.ext.dependencies[\"recyclerview\"]\n    implementation rootProject.ext.dependencies[\"fragment\"]\n    implementation rootProject.ext.dependencies[\"zxing\"]\n\n    implementation rootProject.ext.dependencies[\"epic\"]\n\n//    implementation rootProject.ext.dependencies[\"sandhook_hooklib\"]\n//    implementation rootProject.ext.dependencies[\"sandhook_nativehook\"]\n//    implementation rootProject.ext.dependencies[\"sandhook_xposedcompat\"]\n\n    implementation rootProject.ext.dependencies[\"volley\"]\n    implementation rootProject.ext.dependencies[\"ktor_server_websockets\"]\n    implementation rootProject.ext.dependencies[\"ktor_client_websockets\"]\n    implementation rootProject.ext.dependencies[\"ktor_server_cio\"]\n    //默认客户端websocket引擎\n    implementation rootProject.ext.dependencies[\"ktor_client_cio\"]\n    //备用客户端websocket引擎\n    compileOnly rootProject.ext.dependencies[\"ktor_client_okhttp\"]\n    implementation project(':dokit-util')\n    implementation project(':dokit-okhttp-api')\n    //此处需要使用api的形式 向上暴露内部api\n    implementation project(':dokit')\n    implementation project(':dokit-test')\n    //okhttp wrap\n    //auto-service\n    implementation rootProject.ext.dependencies[\"auto_service\"]\n    kapt rootProject.ext.dependencies[\"auto_service\"]\n}\nrepositories {\n    mavenCentral()\n}\n"
  },
  {
    "path": "Android/dokit-mc/gradle.properties",
    "content": "ARTIFACT_ID=dokitx-mc"
  },
  {
    "path": "Android/dokit-mc/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original from file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "Android/dokit-mc/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.didichuxing.doraemonkit.mc\">\n\n\n    <application>\n        <activity\n            android:name=\"com.didichuxing.doraemonkit.kit.mc.ui.DoKitMcActivity\"\n            android:theme=\"@style/Theme.AppCompat.Light.NoActionBar\"\n            android:windowSoftInputMode=\"adjustPan|stateHidden|stateUnchanged\" />\n\n        <activity\n            android:name=\"com.didichuxing.doraemonkit.kit.mc.ui.DoKitMcScanActivity\"\n            android:theme=\"@style/Theme.AppCompat.Light.NoActionBar\" />\n\n        <!--        <service-->\n        <!--            android:name=\"com.didichuxing.doraemonkit.kit.mc.server.DoKitAccessibilityService\"-->\n        <!--            android:enabled=\"true\"-->\n        <!--            android:label=\"DoKit一机多控\"-->\n        <!--            android:permission=\"android.permission.BIND_ACCESSIBILITY_SERVICE\">-->\n        <!--            <intent-filter>-->\n        <!--                <action android:name=\"android.accessibilityservice.AccessibilityService\" />-->\n        <!--            </intent-filter>-->\n\n        <!--            <meta-data-->\n        <!--                android:name=\"android.accessibilityservice\"-->\n        <!--                android:resource=\"@xml/mc_accessibity_config\" />-->\n        <!--        </service>-->\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/AbstractMultiController.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc\n\nimport android.graphics.Bitmap\nimport com.didichuxing.doraemonkit.kit.connect.data.PackageType\nimport com.didichuxing.doraemonkit.kit.connect.parser.ByteParser\nimport com.didichuxing.doraemonkit.kit.connect.parser.JsonParser\nimport com.didichuxing.doraemonkit.kit.connect.ws.*\nimport com.didichuxing.doraemonkit.kit.test.event.*\nimport com.didichuxing.doraemonkit.kit.test.report.AutoTestMessage\nimport com.didichuxing.doraemonkit.kit.test.report.AutoTestState\nimport com.didichuxing.doraemonkit.kit.test.report.ScreenShotManager\nimport com.didichuxing.doraemonkit.util.ActivityUtils\nimport kotlinx.coroutines.*\nimport java.io.ByteArrayOutputStream\nimport java.lang.Runnable\n\n\n/**\n * didi Create on 2022/4/22 .\n *\n * Copyright (c) 2022/4/22 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/22 12:26 下午\n * @Description 用一句话说明文件功能\n */\n\nabstract class AbstractMultiController(private val webSocketClient: WebSocketClient) {\n\n    protected val mainScope = MainScope() + CoroutineName(this.toString())\n\n    private val uploadScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + CoroutineName(\"mc-upload\")\n\n    private val autoTestStateSet: MutableMap<String, AutoTestState> = mutableMapOf()\n\n    private val screenShotManager: ScreenShotManager = ScreenShotManager(\"doKit/autotest/screen\")\n\n    private val delayHandler: DelayHandler = DelayHandler()\n\n    private var screenShotEventTask: ScreenShotEventTask? = null\n\n    private var lastControlEvent: ControlEvent? = null\n\n    fun screenShotForEvent(controlEvent: ControlEvent) {\n        if (controlEvent.eventType == EventType.WSE_TCP_EVENT) {\n            return\n        }\n        lastControlEvent?.let {\n            screenShotNow(it)\n        }\n        lastControlEvent = controlEvent\n\n        if (isDiffTimeEvent(controlEvent)) {\n            val task = ScreenShotEventTask(controlEvent)\n            screenShotEventTask = task\n            delayHandler.postDelayed(task, getDiffTimeByEvent(controlEvent, 0))\n        }\n    }\n\n    private fun getDiffTimeByEvent(event: ControlEvent, diffTime: Long): Long {\n        when (event.eventType) {\n            EventType.WSE_COMMON_EVENT -> {\n                event.viewC12c?.let {\n                    when (it.actionType) {\n                        ActionType.ON_SCROLL,\n                        ActionType.ON_INPUT_CHANGE -> {\n                            return if (diffTime > 100) {\n                                diffTime\n                            } else {\n                                100\n                            }\n                        }\n                        else -> {\n                        }\n                    }\n                }\n            }\n        }\n        return 1000\n    }\n\n    /**\n     * 从机事件处理结果\n     */\n    fun onControlEventProcessState(autoTestState: AutoTestState) {\n        val controlEvent = autoTestState.controlEvent\n        val message = autoTestState.message\n        if (isDiffTimeEvent(controlEvent)) {\n            autoTestStateSet[autoTestState.controlEvent.eventId] = autoTestState\n        } else {\n            message.params[\"imageName\"] = \"\"\n            message.params[\"type\"] = \"\"\n            onResponseAutoTestAction(message)\n        }\n    }\n\n    private fun buildAutoTestMessage(controlEvent: ControlEvent): AutoTestMessage {\n        val state: AutoTestState? = autoTestStateSet.remove(controlEvent.eventId)\n        state?.let {\n            autoTestStateSet.remove(controlEvent.eventId)\n            return state.message\n        }\n        val message = AutoTestMessage(command = \"action_response\", message = \"success\")\n        message.params[\"eventId\"] = controlEvent.eventId\n        return message\n    }\n\n\n    private fun screenShotNow(controlEvent: ControlEvent) {\n        val message = buildAutoTestMessage(controlEvent)\n        val bitmap = screenShotManager.screenshotBitmap()\n        if (bitmap != null) {\n            val name = screenShotManager.createNextFileName()\n            message.params[\"imageName\"] = name\n            message.params[\"type\"] = \"webp\"\n            onResponseAutoTestAction(message, bitmap)\n        } else {\n            message.params[\"errorMessage\"] = \"screenShot error.\"\n            message.params[\"imageName\"] = \"\"\n            message.params[\"type\"] = \"\"\n            onResponseAutoTestAction(message)\n        }\n    }\n\n    private fun isDiffTimeEvent(controlEvent: ControlEvent): Boolean {\n        when (controlEvent.eventType) {\n            EventType.WSE_CUSTOM_EVENT -> {\n                controlEvent.params?.let {\n                    var testRecording: String? = it[\"testRecording\"]\n                    if (testRecording == \"false\") {\n                        return false\n                    }\n                }\n                return true\n            }\n            EventType.APP_ON_FOREGROUND,\n            EventType.APP_ON_BACKGROUND,\n            EventType.ACTIVITY_BACK_PRESSED -> {\n                return true\n            }\n            EventType.WSE_COMMON_EVENT -> {\n                controlEvent.viewC12c?.let {\n                    when (it.actionType) {\n                        ActionType.ON_LONG_CLICK,\n                        ActionType.ON_SCROLL,\n                        ActionType.ON_INPUT_CHANGE,\n                        ActionType.ON_CLICK -> {\n                            return true\n                        }\n                        else -> {\n                        }\n                    }\n                }\n            }\n\n        }\n        return false\n    }\n\n\n    /**\n     * 自动化测试行为事件响应\n     */\n    private fun onResponseAutoTestAction(autoTestMessage: AutoTestMessage) {\n        webSocketClient?.let {\n            it.send(JsonParser.toJson(PackageType.AUTOTEST, autoTestMessage, \"action\"))\n        }\n    }\n\n    /**\n     * 自动化测试行为事件响应\n     */\n    private fun onResponseAutoTestAction(autoTestMessage: AutoTestMessage, bitmap: Bitmap) {\n        uploadScope.launch {\n            val stream = ByteArrayOutputStream(2048)\n            val ok = bitmap.compress(Bitmap.CompressFormat.WEBP, 30, stream)\n            val bytes = stream.toByteArray()\n\n            val textPackage = JsonParser.toTextPackage(PackageType.AUTOTEST, autoTestMessage, \"action\")\n            val byteString = ByteParser.toByteString(textPackage, bytes)\n            webSocketClient.send(byteString)\n            stream.close()\n        }\n    }\n\n    inner class ScreenShotEventTask(private val controlEvent: ControlEvent) : Runnable {\n\n        private fun isCurrentEvent(): Boolean {\n            lastControlEvent?.let {\n                return it.eventId == controlEvent.eventId\n            }\n            return false\n        }\n\n        override fun run() {\n            if (isCurrentEvent()) {\n                lastControlEvent = null\n                screenShotNow(controlEvent)\n            }\n        }\n    }\n\n    abstract fun start()\n\n\n    abstract fun close()\n\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/ClientMultiController.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc\n\nimport android.app.Activity\nimport android.view.View\nimport com.didichuxing.doraemonkit.kit.connect.data.TextPackage\nimport com.didichuxing.doraemonkit.kit.connect.ws.WebSocketClient\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEventManager\nimport com.didichuxing.doraemonkit.kit.test.event.OnControlEventActionProcessListener\nimport com.didichuxing.doraemonkit.kit.test.mock.MockManager\nimport com.didichuxing.doraemonkit.kit.test.report.AutoTestMessage\nimport com.didichuxing.doraemonkit.kit.test.report.AutoTestState\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport kotlinx.coroutines.launch\n\n\n/**\n * didi Create on 2022/4/22 .\n *\n * Copyright (c) 2022/4/22 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/22 12:25 下午\n * @Description 用一句话说明文件功能\n */\n\nclass ClientMultiController(webSocketClient: WebSocketClient) : AbstractMultiController(webSocketClient) {\n\n\n    private val onControlEventActionProcessListener = object : OnControlEventActionProcessListener {\n        override fun onControlEventProcessSuccess(activity: Activity?, view: View?, controlEvent: ControlEvent) {\n            val message = AutoTestMessage(command = \"action_response\", message = \"success\")\n            message.params[\"eventId\"] = controlEvent.eventId\n            val state = AutoTestState(activity, view, controlEvent, message)\n            onControlEventProcessState(state)\n        }\n\n        override fun onControlEventProcessFailed(activity: Activity?, view: View?, controlEvent: ControlEvent, code: Int, message: String) {\n            val msg = AutoTestMessage(command = \"action_response\", message = \"failed\")\n            msg.params[\"eventId\"] = controlEvent.eventId\n            msg.params[\"message\"] = message\n            msg.params[\"code\"] = \"\" + code\n            val state = AutoTestState(activity, view, controlEvent, msg)\n            onControlEventProcessState(state)\n        }\n    }\n\n    override fun start() {\n        ControlEventManager.addOnControlEventActionProcessListener(onControlEventActionProcessListener)\n        DoKitTestManager.startTest(TestMode.CLIENT)\n        MockManager.startTest(TestMode.CLIENT)\n    }\n\n    override fun close() {\n        DoKitTestManager.closeTest()\n        MockManager.closeTest()\n        ControlEventManager.removeOnControlEventActionProcessListener(onControlEventActionProcessListener)\n    }\n\n    /**\n     * 接收到自动化测试事件\n     */\n    fun onReceiveAction(textPackage: TextPackage) {\n        val text = textPackage.data\n        val controlEvent = GsonUtils.fromJson<ControlEvent>(text, ControlEvent::class.java)\n\n        mainScope.launch {\n            screenShotForEvent(controlEvent)\n            ControlEventManager.onReceiveControlEventAction(controlEvent)\n        }\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/DelayHandler.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc\n\nimport android.os.Handler\nimport android.os.Looper\n\n\n/**\n * didi Create on 2022/4/18 .\n *\n * Copyright (c) 2022/4/18 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/18 3:11 下午\n * @Description 用一句话说明文件功能\n */\n\nclass DelayHandler {\n    private val mainHandler: Handler = Handler(Looper.getMainLooper())\n\n\n    fun postDelayed(runnable: Runnable, delay: Long) {\n        mainHandler.postDelayed(runnable, delay)\n    }\n\n    fun removeCallbacks(runnable: Runnable) {\n        mainHandler.removeCallbacks(runnable)\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/HostMultiController.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc\n\nimport android.app.Activity\nimport android.view.View\nimport com.didichuxing.doraemonkit.kit.connect.data.PackageType\nimport com.didichuxing.doraemonkit.kit.connect.parser.JsonParser\nimport com.didichuxing.doraemonkit.kit.connect.ws.WebSocketClient\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEventManager\nimport com.didichuxing.doraemonkit.kit.test.event.OnControlEventActionListener\nimport com.didichuxing.doraemonkit.kit.test.event.OnControlEventInterceptor\nimport com.didichuxing.doraemonkit.kit.test.mock.MockManager\nimport com.didichuxing.doraemonkit.mc.R\n\n\n/**\n * didi Create on 2022/4/22 .\n *\n * Copyright (c) 2022/4/22 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/22 12:25 下午\n * @Description 主机控制实现\n */\n\nclass HostMultiController(webSocketClient: WebSocketClient) : AbstractMultiController(webSocketClient) {\n\n\n    private val onControlEventInterceptor = object : OnControlEventInterceptor {\n        override fun onControlEventAction(activity: Activity?, view: View?, controlEvent: ControlEvent): Boolean {\n            if (view != null && view.id == R.id.dokit_mode_switch_btn) {\n                return true\n            }\n            return false\n        }\n    }\n    private val onControlEventActionListener = object : OnControlEventActionListener {\n        override fun onControlEventAction(activity: Activity?, view: View?, event: ControlEvent) {\n            screenShotForEvent(event)\n            webSocketClient.let {\n                it.send(JsonParser.toJson(PackageType.BROADCAST, event, \"action\"))\n            }\n        }\n    }\n\n    override fun start() {\n        ControlEventManager.addOnControlEventInterceptor(onControlEventInterceptor)\n        ControlEventManager.addOnControlEventActionListener(onControlEventActionListener)\n        DoKitTestManager.startTest(TestMode.HOST)\n        MockManager.startTest(TestMode.HOST)\n    }\n\n\n    override fun close() {\n        ControlEventManager.removeOnControlEventInterceptor(onControlEventInterceptor)\n        ControlEventManager.removeOnControlEventActionListener(onControlEventActionListener)\n        DoKitTestManager.closeTest()\n        MockManager.closeTest()\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/MultiControlConfig.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc\n\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.mc.oldui.DoKitMcManager\nimport com.didichuxing.doraemonkit.kit.mc.ui.adapter.McClientHistory\nimport com.didichuxing.doraemonkit.util.SPUtils\n\n\n/**\n * didi Create on 2022/4/22 .\n *\n * Copyright (c) 2022/4/22 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/22 12:25 下午\n * @Description 一机多控配置\n */\n\nobject MultiControlConfig {\n\n\n    private const val DOKIT_MC_CONNECT_URL = \"dokit_mc_connect_url\"\n    private const val NAME_DOKIIT_MC_CONFIGALL = \"dokiit-mc-config-all\"\n\n\n    private var MC_CONNECT_URL = \"\"\n\n\n    var sp: SPUtils = SPUtils.getInstance(NAME_DOKIIT_MC_CONFIGALL)\n\n\n    var currentConnectHistory: McClientHistory? = null\n        set(value) {\n            val url = value?.url ?: \"\"\n            saveMcConnectUrl(url)\n            field = value\n        }\n\n\n    fun init() {\n        loadConfig()\n    }\n\n    fun loadConfig() {\n        val url = sp.getString(DoKitMcManager.DOKIT_MC_CONNECT_URL)\n        DoKitManager.MC_CONNECT_URL = url\n        MC_CONNECT_URL = url\n    }\n\n    fun saveMcConnectUrl(url: String) {\n        MC_CONNECT_URL = url\n        DoKitManager.MC_CONNECT_URL = url\n        sp.put(DOKIT_MC_CONNECT_URL, url)\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/MultiControlKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content.Intent\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.mc.oldui.DoKitMcManager\nimport com.didichuxing.doraemonkit.kit.mc.ui.DoKitMcActivity\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.google.auto.service.AutoService\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-13:30\n * 描    述：一机多控入口\n * 修订历史：\n * ================================================\n */\n@AutoService(AbstractKit::class)\nclass MultiControlKit : AbstractKit() {\n    override val name: Int get() = R.string.dk_kit_multi_control\n    override val icon: Int get() = R.mipmap.dk_icon_mc\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip))\n            return false\n        }\n        val intent = Intent(activity, DoKitMcActivity::class.java)\n        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK\n        activity.startActivity(intent)\n\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n        MultiControlConfig.init()\n        DoKitMcManager.init()\n    }\n\n    override val isInnerKit: Boolean get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_platform_ck_mc\"\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/MultiControlKitTest.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content.Intent\nimport com.didichuxing.doraemonkit.aop.DokitPluginConfig\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.mc.ui.DoKitMcActivity\nimport com.didichuxing.doraemonkit.kit.mc.ui.McPages\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.google.auto.service.AutoService\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-13:30\n * 描    述：一机多控入口\n * 修订历史：\n * ================================================\n */\n@AutoService(AbstractKit::class)\nclass MultiControlKitTest : AbstractKit() {\n    override val name: Int get() = R.string.dk_kit_multi_control_test\n    override val icon: Int get() = R.mipmap.dk_icon_mc\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {\n            ToastUtils.showShort(DoKitCommUtil.getString(R.string.dk_plugin_close_tip))\n            return false\n        }\n        val intent = Intent(activity, DoKitMcActivity::class.java)\n        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK\n        intent.putExtra(\"WS_MODE_ORDINAL\", McPages.MAIN.name)\n        activity.startActivity(intent)\n\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {\n    }\n\n    override val isInnerKit: Boolean get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_platform_ck_mc_test\"\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/MultiControlManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc\n\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.connect.ConnectAddress\nimport com.didichuxing.doraemonkit.kit.connect.data.PackageType\nimport com.didichuxing.doraemonkit.kit.connect.data.TextPackage\nimport com.didichuxing.doraemonkit.kit.connect.parser.JsonParser\nimport com.didichuxing.doraemonkit.kit.connect.ws.*\nimport com.didichuxing.doraemonkit.kit.mc.ui.connect.MultiControlDoKitView\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.kit.test.mock.MockManager\nimport com.didichuxing.doraemonkit.kit.test.mock.ProxyMockCallback\nimport com.didichuxing.doraemonkit.kit.test.report.AutoTestMessage\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.MainScope\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.plus\n\n\n/**\n * didi Create on 2022/4/22 .\n *\n * Copyright (c) 2022/4/22 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/22 12:25 下午\n * @Description 一机多控管理\n */\n\nobject MultiControlManager {\n\n\n    private val mainScope = MainScope() + CoroutineName(this.toString())\n\n    private val webSocketClient: WebSocketClient = WebSocketClient()\n\n    private var connectAddress: ConnectAddress? = null\n\n    private val hostMultiController = HostMultiController(webSocketClient)\n\n    private val clientMultiController = ClientMultiController(webSocketClient)\n\n    private var webSocketClientCreate = false\n\n    private var mode: TestMode = TestMode.UNKNOWN\n\n    private val onModeChangeListeners: MutableSet<OnMultiControlModeChangeListener> = mutableSetOf()\n\n    fun getMode(): TestMode {\n        return mode\n    }\n\n    fun startHostMode(connectAddress: ConnectAddress) {\n        this.connectAddress = connectAddress\n        mode = TestMode.HOST\n        connect()\n        clientMultiController.close()\n        hostMultiController.start()\n        onNotifyMultiControlModeChange(mode)\n    }\n\n    private fun startHostMode() {\n        mode = TestMode.HOST\n        clientMultiController.close()\n        hostMultiController.start()\n        sendChangeHostMode()\n        onNotifyMultiControlModeChange(mode)\n        ToastUtils.showShort(\"主机模式\")\n    }\n\n    fun startClientMode(connectAddress: ConnectAddress) {\n        this.connectAddress = connectAddress\n        mode = TestMode.CLIENT\n        connect()\n        hostMultiController.close()\n        clientMultiController.start()\n        onNotifyMultiControlModeChange(mode)\n    }\n\n    private fun startClientMode() {\n        mode = TestMode.CLIENT\n        hostMultiController.close()\n        clientMultiController.start()\n        onNotifyMultiControlModeChange(mode)\n        ToastUtils.showShort(\"从机模式\")\n    }\n\n    fun changeMode(testMode: TestMode) {\n        if (testMode == TestMode.HOST) {\n            startHostMode()\n        } else if (testMode == TestMode.CLIENT) {\n            startClientMode()\n        }\n        MultiControlDoKitView.updateConnectMode()\n    }\n\n    fun changeMode() {\n        if (mode == TestMode.HOST) {\n            startClientMode()\n        } else if (mode == TestMode.CLIENT) {\n            startHostMode()\n        }\n        MultiControlDoKitView.updateConnectMode()\n    }\n\n    fun closeWorkMode() {\n        mode = TestMode.UNKNOWN\n        clientMultiController.close()\n        hostMultiController.close()\n        stopConnect()\n\n        onNotifyMultiControlModeChange(mode)\n    }\n\n    private fun sendChangeHostMode() {\n        webSocketClient.let {\n            it.send(JsonParser.toJson(PackageType.BROADCAST, \"\", \"mc_host\"))\n        }\n    }\n\n    private fun onReceiveHostModeChange() {\n        mainScope.launch {\n            if (mode == TestMode.HOST) {\n                changeMode(TestMode.CLIENT)\n            }\n        }\n    }\n\n    fun addOnMultiControlModeChangeListener(listener: OnMultiControlModeChangeListener) {\n        onModeChangeListeners.add(listener)\n    }\n\n    fun removeOnMultiControlModeChangeListener(listener: OnMultiControlModeChangeListener) {\n        onModeChangeListeners.remove(listener)\n    }\n\n    private fun onNotifyMultiControlModeChange(mode: TestMode) {\n        onModeChangeListeners.forEach {\n            it.onMultiControlModeChanged(mode)\n        }\n    }\n\n    private fun onReceiveControl(textPackage: TextPackage) {\n        val text = textPackage.data\n        val autoTestMessage = GsonUtils.fromJson<AutoTestMessage>(text, AutoTestMessage::class.java)\n\n        when (autoTestMessage.command) {\n            \"startRecord\" -> {\n                val msg = AutoTestMessage(command = \"control_response\", message = \"success\")\n                onResponseAutoTestMessage(msg)\n            }\n        }\n    }\n\n    /**\n     * 控制消息响应\n     */\n    private fun onResponseAutoTestMessage(autoTestMessage: AutoTestMessage) {\n        webSocketClient?.let {\n            it.send(JsonParser.toJson(PackageType.AUTOTEST, autoTestMessage, \"auto_test_control\"))\n        }\n    }\n\n    /**\n     * 接收到自动化测试事件\n     */\n    private fun onReceiveAction(textPackage: TextPackage) {\n        clientMultiController.onReceiveAction(textPackage)\n    }\n\n\n    private fun stopConnect() {\n        webSocketClient.close()\n    }\n\n    private fun connect() {\n        if (connectAddress == null) {\n            return\n        }\n        if (!webSocketClientCreate) {\n            webSocketClientCreate = true\n            webSocketClient.let {\n                it.addOnWebSocketLoginSuccessListener(object : OnWebSocketLoginSuccessListener {\n                    override fun onWebSocketLoginSuccess() {\n                        mainScope.launch {\n                            DoKit.launchFloating(MultiControlDoKitView::class.java)\n                        }\n                    }\n                })\n                it.addOnWebSocketTextPackageListener(object : OnWebSocketTextPackageListener {\n                    override fun onReceiveTextPackage(webSocket: OkHttpWebSocketSession, textPackage: TextPackage) {\n                        if (textPackage.type == PackageType.AUTOTEST || textPackage.type == PackageType.BROADCAST) {\n                            when (textPackage.contentType) {\n                                \"mc_host\" -> {\n                                    onReceiveHostModeChange()\n                                }\n                                \"auto_test_control\" -> {\n                                    onReceiveControl(textPackage)\n                                }\n                                \"action\" -> {\n                                    onReceiveAction(textPackage)\n                                }\n                                else -> {\n\n                                }\n                            }\n                        } else if (textPackage.type == PackageType.DATA) {\n                            MockManager.receiveQueryResponse(textPackage)\n                        }\n                    }\n                })\n                it.addOnWebSocketCloseListener(object : OnWebSocketCloseListener {\n                    override fun onWebSocketClose() {\n                        mainScope.launch {\n                            DoKit.removeFloating(MultiControlDoKitView::class.java)\n                        }\n                    }\n                })\n\n                it.addOnWebSocketStatusChangeListener(object : OnWebSocketStatusChangeListener {\n                    override fun onClosed(webSocket: OkHttpWebSocketSession, code: Int, reason: String) {\n                    }\n\n                    override fun onOpen(webSocket: OkHttpWebSocketSession, response: String) {\n                    }\n\n                    override fun onFailure(webSocket: OkHttpWebSocketSession, t: Throwable, response: String?) {\n                        ToastUtils.showShort(\"链接失败:\" + t.message)\n                    }\n\n                    override fun onClosing(webSocket: OkHttpWebSocketSession, code: Int, reason: String) {\n                    }\n                })\n\n                MockManager.proxyMockCallback = object : ProxyMockCallback {\n                    override fun send(data: String) {\n                        webSocketClient.send(data)\n                    }\n                }\n\n                it.startAutoConnect()\n                it.connect(connectAddress!!.url)\n            }\n        } else {\n            webSocketClient.startAutoConnect()\n            webSocketClient.reConnect(connectAddress!!.url)\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/OnMultiControlModeChangeListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc\n\nimport com.didichuxing.doraemonkit.kit.test.TestMode\n\n\n/**\n * didi Create on 2022/4/14 .\n *\n * Copyright (c) 2022/4/14 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/14 10:52 上午\n * @Description 用一句话说明文件功能\n */\n\ninterface OnMultiControlModeChangeListener {\n\n    fun onMultiControlModeChanged(testMode: TestMode)\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/ability/DoKitMcAbility.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.ability\n\nimport com.didichuxing.doraemonkit.constant.DoKitModule\nimport com.didichuxing.doraemonkit.kit.core.DokitAbility\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport com.google.auto.service.AutoService\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/7-19:54\n * 描    述：\n * 修订历史：\n * ================================================\n * 模块能力相关代码\n */\n@AutoService(DokitAbility::class)\nclass DoKitMcAbility : DokitAbility {\n\n    private val TAG = \"DoKitMcAbility\"\n    override fun init() {\n        LogHelper.i(TAG, \"init()\")\n    }\n\n    override fun moduleName(): DoKitModule {\n        return DoKitModule.MODULE_MC\n    }\n\n    override fun getModuleProcessor(): DokitAbility.DokitModuleProcessor {\n        return DoKitMcModuleProcessor()\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/ability/DoKitMcModuleProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.ability\n\nimport android.view.View\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.core.DokitAbility\nimport com.didichuxing.doraemonkit.kit.test.event.monitor.LifecycleEventMonitor\nimport com.didichuxing.doraemonkit.kit.mc.oldui.DoKitMcManager\nimport com.didichuxing.doraemonkit.kit.mc.MultiControlConfig\nimport com.didichuxing.doraemonkit.kit.mc.oldui.client.ClientDoKitView\nimport com.didichuxing.doraemonkit.kit.test.mock.http.DoKitMockInterceptor\nimport com.didichuxing.doraemonkit.kit.mc.oldui.host.HostDoKitView\nimport com.didichuxing.doraemonkit.kit.mc.oldui.record.RecordingDoKitView\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.kit.test.event.monitor.CustomEventMonitor\nimport com.didichuxing.doraemonkit.kit.test.mock.http.DoKitProxyMockInterceptor\nimport com.didichuxing.doraemonkit.kit.test.utils.XposedHookUtil\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport com.didichuxing.doraemonkit.util.SPUtils\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/7-19:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitMcModuleProcessor : DokitAbility.DokitModuleProcessor {\n\n    override fun values(): Map<String, Any> {\n        return mapOf(\n            \"okhttp_interceptor\" to DoKitMockInterceptor(),\n            \"okhttp_proxy_interceptor\" to DoKitProxyMockInterceptor(),\n            \"lifecycle\" to LifecycleEventMonitor()\n        )\n    }\n\n    override fun proceed(actions: Map<String, Any?>?): Map<String, Any> {\n        try {\n            actions?.let {\n                when (actions[\"action\"]) {\n                    \"launch_host_view\" -> {\n                        DoKit.launchFloating(HostDoKitView::class)\n\n                    }\n                    \"launch_client_view\" -> {\n                        DoKit.launchFloating(ClientDoKitView::class)\n                    }\n                    \"launch_recoding_view\" -> {\n                        if (DoKitMcManager.IS_MC_RECODING ||\n                            SPUtils.getInstance()\n                                .getBoolean(DoKitMcManager.MC_CASE_RECODING_KEY, false)\n                        ) {\n                            DoKit.launchFloating(RecordingDoKitView::class)\n                            DoKitMcManager.IS_MC_RECODING = true\n                            DoKitMcManager.MC_CASE_ID =\n                                SPUtils.getInstance().getString(DoKitMcManager.MC_CASE_ID_KEY)\n                        } else {\n\n                        }\n                    }\n                    \"mc_mode\" -> {\n                        val mode = if (DoKitTestManager.isHostMode()) {\n                            \"host\"\n                        } else if (DoKitTestManager.isClientMode()) {\n                            \"client\"\n                        } else {\n                            \"unknown\"\n                        }\n                        return mapOf(Pair(\"mode\", mode))\n                    }\n                    \"mc_custom_event\" -> {\n                        CustomEventMonitor.onCustomEvent(\n                            actions[\"eventType\"] as String,\n                            actions[\"view\"] as View?,\n                            actions[\"param\"] as Map<String, String>?\n                        )\n                    }\n                    \"global_hook\" -> {\n                        XposedHookUtil.globalHook()\n                    }\n                    \"dokit_mc_connect_url\" -> {\n                        val map = mutableMapOf<String, String>()\n                        val history = MultiControlConfig.currentConnectHistory\n                        map[\"url\"] = history?.url ?: \"\"\n                    }\n\n                    else -> {\n                        LogHelper.e(\"DokitMcModuleProcessor\", \"not action ${actions[\"action\"]}\")\n                    }\n                }\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n\n        return mapOf()\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/net/ConnectMode.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.net\n\n\n/**\n * didi Create on 2022/2/16 .\n *\n * Copyright (c) 2022/2/16 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/2/16 5:09 下午\n * @Description 通过PC服务端转发的协议包类型\n */\n\nenum class ConnectMode {\n    /**\n     * 关闭\n     * 用于连接成功后认证\n     */\n    CLOSE,\n    /**\n     *\n     * 成功\n     */\n    CONNECT,\n\n    /**\n     * 登陆\n     */\n    LOGIN,\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/net/DoKitMcClient.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.net\n\nimport androidx.appcompat.app.AlertDialog\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.extension.doKitGlobalScope\nimport com.didichuxing.doraemonkit.kit.test.mock.data.HostInfo\nimport com.didichuxing.doraemonkit.kit.mc.oldui.client.ClientDoKitView\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\nimport com.didichuxing.doraemonkit.kit.test.event.EventType\nimport com.didichuxing.doraemonkit.kit.mc.utils.WSPackageUtils\nimport com.didichuxing.doraemonkit.util.*\nimport io.ktor.client.*\nimport io.ktor.client.engine.cio.*\nimport io.ktor.client.engine.okhttp.*\nimport io.ktor.client.features.websocket.*\nimport io.ktor.client.request.*\nimport io.ktor.http.*\nimport io.ktor.http.cio.websocket.*\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.channels.consumeEach\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/11/13-15:38\n * 描    述：\n * 修订历史：\n * ================================================\n */\n\nobject DoKitMcClient {\n    const val TAG = \"DoKitWSClient\"\n    private var clientWebSocketSession: DefaultClientWebSocketSession? = null\n    const val CONNECT_SUCCEED = 200\n    const val CONNECT_FAIL = 0\n\n    private val client: HttpClient by lazy {\n\n        try {\n            Class.forName(\"io.ktor.client.engine.okhttp.OkHttp\")\n            LogHelper.i(TAG, \"client engine is OkHttp\")\n            return@lazy HttpClient(OkHttp) {\n                install(WebSockets)\n            }\n        } catch (e: ClassNotFoundException) {\n            LogHelper.i(TAG, \"client engine is CIO\")\n            return@lazy HttpClient(CIO) {\n                install(WebSockets)\n            }\n        }\n    }\n\n    fun connect(host: String, port: Int, path: String, callBack: suspend (Int, HostInfo?) -> Unit) {\n        CoroutineScope(Dispatchers.IO).launch {\n            try {\n                client.ws(\n                    method = HttpMethod.Get,\n                    host = host,\n                    port = port,\n                    path = path,\n                    request = {\n                        headers {\n                            header(\n                                \"deviceModel\",\n                                \"${DeviceUtils.getManufacturer()}-${DeviceUtils.getModel()}\"\n                            )\n                        }\n                    }\n                ) {\n                    clientWebSocketSession = this\n\n                    /**\n                     * 避免ws在收到第一条消息以后 通道自动关闭的问题\n                     * https://github.com/ktorio/ktor/issues/402\n                     */\n                    incoming.consumeEach {\n                        when (it) {\n                            is Frame.Text -> {\n                                val serverInfo = it.readText()\n                                LogHelper.json(TAG, serverInfo)\n                                try {\n                                    val wsEvent = WSPackageUtils.jsonToEvent(it.readText())\n                                    //连接成功的返回信息\n                                    when (wsEvent.eventType) {\n\n                                        EventType.WSE_CONNECTED -> {\n                                            val mHostInfo = GsonUtils.fromJson<HostInfo>(wsEvent.params?.get(\"hostInfo\"), HostInfo::class.java)\n                                            callBack(CONNECT_SUCCEED, mHostInfo)\n                                            mHostInfo.let {\n                                                ToastUtils.showShort(\"已经连接到${it.deviceName}主机\")\n                                            }\n                                        }\n                                        /**\n                                         * 主机断开\n                                         */\n                                        EventType.WSE_HOST_CLOSE -> {\n                                            doKitGlobalScope.launch {\n                                                DoKitMcClient.close()\n                                            }\n                                            DoKit.removeFloating(ClientDoKitView::class)\n                                            if (ActivityUtils.getTopActivity() != null) {\n                                                AlertDialog.Builder(ActivityUtils.getTopActivity())\n                                                    .setTitle(\"一机多控\")\n                                                    .setMessage(\"主机已断开连接！\")\n                                                    .setPositiveButton(\"确认\") { dialog, _ ->\n                                                        dialog.dismiss()\n                                                    }\n                                                    .setNegativeButton(\"取消\") { dialog, _ ->\n                                                        dialog.dismiss()\n                                                    }\n                                                    .show()\n                                            }\n                                        }\n\n                                        EventType.WSE_TEST -> {\n                                            LogHelper.e(TAG, \"WSE_TEST wsEvent=$wsEvent\")\n                                        }\n                                        else -> {\n                                            WSEventProcessor.process(wsEvent)\n                                        }\n\n                                    }\n\n\n                                    /**\n                                     * 连接成功\n                                     */\n\n\n                                } catch (e: Exception) {\n                                    e.printStackTrace()\n                                    LogHelper.e(TAG, \"client handle error===>${e.message}\")\n                                }\n\n                            }\n                            else -> {\n                                LogHelper.e(TAG, \"client type error===>${it}\")\n                            }\n                        }\n                    }\n                }\n            } catch (e: Exception) {\n                e.printStackTrace()\n                LogHelper.e(TAG, \"client connect error===>${e.message}\")\n                callBack(CONNECT_FAIL, HostInfo(e.message!!, 0f, 0f))\n            }\n\n        }\n    }\n\n    suspend fun close() {\n        try {\n            send(\n                ControlEvent(\n                    \"\",\n                    EventType.WSE_CLOSE,\n                    mutableMapOf(\"command\" to \"bye\"),\n                    null\n                )\n            )\n            clientWebSocketSession?.close()\n            DokitMcConnectManager.currentClientHistory = null\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n\n    }\n\n\n    fun send(wsEvent: ControlEvent) {\n        clientWebSocketSession?.let {\n            CoroutineScope(it.coroutineContext).launch {\n                if (it.isActive) {\n                    it.outgoing.send(Frame.Text(GsonUtils.toJson(wsEvent)))\n                } else {\n                    LogHelper.e(TAG, \"send() clientWebSocketSession is not active.\")\n                }\n            }\n        }\n    }\n\n    fun session(): DefaultClientWebSocketSession? {\n        return clientWebSocketSession;\n    }\n\n\n}\n\n\n\n\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/net/DoKitMcHostServer.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.net\n\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.test.event.EventType\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\nimport com.didichuxing.doraemonkit.kit.mc.utils.WSPackageUtils\nimport io.ktor.http.cio.websocket.*\nimport io.ktor.server.cio.*\nimport io.ktor.server.engine.*\nimport io.ktor.websocket.*\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/11/13-15:38\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject DoKitMcHostServer {\n    /**\n     * 所有的连接\n     */\n    val wsSessionMaps: MutableMap<String?, DefaultWebSocketServerSession> = mutableMapOf()\n\n    private val server: CIOApplicationEngine by lazy {\n        embeddedServer(CIO, port = DoKitManager.MC_WS_PORT, module = WSRouter)\n    }\n\n    //val engine\n    var shotDown: Boolean = true\n\n    fun start(callBack: () -> Unit) {\n        try {\n            server.start()\n            callBack()\n            shotDown = false\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n\n    suspend fun stop(callBack: () -> Unit) {\n        try {\n            shotDown = true\n            send(ControlEvent(\"\", EventType.WSE_HOST_CLOSE))\n            delay(1000)\n            wsSessionMaps.forEach {\n                it.value.close()\n            }\n            wsSessionMaps.clear()\n            //server.stop(1, 1\n            callBack()\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    fun send(wsEvent: ControlEvent) {\n        //一机多控主机事件分发\n        val wsPackage = WSPackageUtils.toPackageJson(wsEvent)\n        wsSessionMaps.forEach {\n            CoroutineScope(it.value.coroutineContext).launch {\n                if (it.value.isActive) {\n                    it.value.outgoing.send(Frame.Text(wsPackage))\n                }\n            }\n        }\n\n    }\n\n    fun shotDown(): Boolean {\n        return shotDown\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/net/DokitMcConnectManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.net\n\nimport com.didichuxing.doraemonkit.kit.mc.oldui.DoKitMcManager\nimport com.didichuxing.doraemonkit.kit.mc.ui.adapter.McClientHistory\n\nobject DokitMcConnectManager {\n\n\n    var connectMode: ConnectMode = ConnectMode.CLOSE\n\n    var itemHistory: McClientHistory? = null\n\n    var currentConnectHistory: McClientHistory? = null\n        set(value) {\n            val url = value?.url ?: \"\"\n            DoKitMcManager.saveMcConnectUrl(url)\n            field = value\n        }\n\n    var currentClientHistory: McClientHistory? = null\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/net/WSEventProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.net\n\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEventManager\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/11/18-17:32\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject WSEventProcessor {\n    suspend fun process(wsEvent: ControlEvent) {\n        try {\n            withContext(Dispatchers.Main) {\n                try {\n                    ControlEventManager.getControlEventProcessor().processControlEvent(wsEvent)\n                } catch (e: Exception) {\n                    e.printStackTrace()\n                }\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/net/WSSRouter.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.net\n\nimport android.os.Build\nimport androidx.annotation.RequiresApi\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\nimport com.didichuxing.doraemonkit.kit.test.event.EventType\nimport com.didichuxing.doraemonkit.kit.test.mock.data.HostInfo\nimport com.didichuxing.doraemonkit.kit.mc.utils.WSPackageUtils\nimport com.didichuxing.doraemonkit.util.*\nimport io.ktor.application.*\nimport io.ktor.features.*\nimport io.ktor.http.cio.websocket.*\nimport io.ktor.routing.*\nimport io.ktor.websocket.*\nimport kotlinx.coroutines.channels.consumeEach\nimport java.time.Duration\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/6/23-14:35\n * 描    述：\n * 修订历史：\n * ================================================\n */\n@RequiresApi(Build.VERSION_CODES.O)\nval WSRouter: Application.() -> Unit = {\n    install(DefaultHeaders)\n    install(CallLogging)\n\n    install(WebSockets) {\n        pingPeriod = Duration.ofSeconds(60)\n        timeout = Duration.ofSeconds(15)\n        maxFrameSize = Long.MAX_VALUE\n        masking = false\n    }\n\n    routing {\n        //一机多控\n        webSocket(\"/mc\") {\n\n            val headers = this.call.request.headers\n            val deviceModels = headers[\"deviceModel\"]\n            //保存\n            DoKitMcHostServer.wsSessionMaps[deviceModels] = this\n            ToastUtils.showShort(\"从机【$deviceModels】已连接\")\n            val hostInfo = HostInfo(\n                \"${DeviceUtils.getManufacturer()}-${DeviceUtils.getModel()}\",\n                ScreenUtils.getAppScreenWidth().toFloat(),\n                ScreenUtils.getAppScreenHeight().toFloat()\n            )\n            val wsEvent = ControlEvent(\n                \"\",\n                EventType.WSE_CONNECTED,\n                mutableMapOf(\n                    \"hostInfo\" to GsonUtils.toJson(hostInfo)\n                ),\n                null\n            )\n\n            outgoing.send(Frame.Text(WSPackageUtils.toPackageJson(wsEvent)))\n            /**\n             * 避免ws在收到第一条消息以后 通道自动关闭的问题\n             * https://github.com/ktorio/ktor/issues/402\n             */\n            incoming.consumeEach {\n                when (it) {\n                    is Frame.Text -> {\n                        try {\n                            val wsEvent = WSPackageUtils.jsonToEvent(it.readText())\n                            if (wsEvent.eventType == EventType.WSE_CLOSE) {\n                                DoKitMcHostServer.send(\n                                    ControlEvent(\n                                        \"\",\n                                        EventType.WSE_CLOSE,\n                                        mutableMapOf(\n                                            \"command\" to \"confirmed bye\"\n                                        ),\n                                        null\n                                    )\n                                )\n                                close(CloseReason(CloseReason.Codes.NORMAL, \"Client said BYE\"))\n                                ToastUtils.showShort(\"从机【$deviceModels】已断开连接\")\n                            } else {\n                                WSServerProcessor.process(wsEvent)\n                            }\n                        } catch (e: Exception) {\n                            e.printStackTrace()\n                        }\n                    }\n                    else -> {\n\n                    }\n                }\n            }\n        }\n    }\n}\n\nconst val TAG = \"WSRouter\"\n\n\n\n\n\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/net/WSServerProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.net\n\nimport com.didichuxing.doraemonkit.kit.test.event.EventType\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\nimport com.didichuxing.doraemonkit.util.LogHelper\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/11/18-17:32\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject WSServerProcessor {\n\n    const val TAG = \"WSServerProcessor\"\n\n    /**\n     * 处理来自从机的消息\n     */\n    fun process(wsEvent: ControlEvent) {\n        when (wsEvent.eventType) {\n            EventType.WSE_TEST -> {\n                LogHelper.e(TAG, \"处理事件类型 wsEvent=$wsEvent\")\n            }\n\n            EventType.WSE_CLOSE -> {\n\n            }\n\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/oldui/DoKitMcManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.oldui\n\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.kit.test.mock.data.HostInfo\nimport com.didichuxing.doraemonkit.util.SPUtils\n\n/**\n * didi Create on 2022/4/22 .\n *\n * Copyright (c) 2022/4/22 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/22 12:25 下午\n * @Description 用一句话说明文件功能\n */\nobject DoKitMcManager {\n\n\n    const val MC_CASE_ID_KEY = \"MC_CASE_ID\"\n    const val MC_CASE_RECODING_KEY = \"MC_CASE_RECODING\"\n    const val DOKIT_MC_CONNECT_URL = \"dokit_mc_connect_url\"\n    const val NAME_DOKIIT_MC_CONFIGALL = \"dokiit-mc-config-all\"\n\n\n    /**\n     * 是否处于录制状态\n     */\n    var IS_MC_RECODING = false\n\n    /**\n     * 主机信息\n     */\n    var HOST_INFO: HostInfo? = null\n\n    var MC_CASE_ID: String = \"\"\n\n\n    var WS_MODE: TestMode = TestMode.UNKNOWN\n\n    var CONNECT_MODE: TestMode = TestMode.UNKNOWN\n\n    var sp: SPUtils = SPUtils.getInstance(NAME_DOKIIT_MC_CONFIGALL)\n\n    private var mode: TestMode = TestMode.UNKNOWN\n\n    fun getMode(): TestMode {\n        return mode\n    }\n\n    fun init() {\n        loadConfig()\n    }\n\n    fun loadConfig() {\n        DoKitManager.MC_CONNECT_URL = sp.getString(DOKIT_MC_CONNECT_URL)\n\n    }\n\n    fun saveMcConnectUrl(url: String) {\n        DoKitManager.MC_CONNECT_URL = url\n        sp.put(DOKIT_MC_CONNECT_URL, url)\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/oldui/client/ClientDoKitView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.oldui.client\n\nimport android.content.Context\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams\nimport com.didichuxing.doraemonkit.kit.mc.ui.McPages\nimport com.didichuxing.doraemonkit.kit.mc.utils.McPageUtils\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.util.ConvertUtils\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/17-14:37\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass ClientDoKitView : AbsDoKitView() {\n    override fun onCreate(context: Context?) {\n    }\n\n    override fun onCreateView(context: Context?, rootView: FrameLayout?): View {\n        val view = LayoutInflater.from(context).inflate(R.layout.dk_dokitview_client, rootView, false)\n        return view\n    }\n\n    override fun onViewCreated(rootView: FrameLayout?) {\n        rootView?.setOnClickListener {\n            McPageUtils.startFragment(McPages.CLIENT)\n        }\n    }\n\n    override fun initDokitViewLayoutParams(params: DoKitViewLayoutParams) {\n        params.width = ConvertUtils.dp2px(70.0f)\n        params.height = ConvertUtils.dp2px(70.0f)\n        params.gravity = Gravity.TOP or Gravity.LEFT\n        params.x = ConvertUtils.dp2px(280f)\n        params.y = ConvertUtils.dp2px(25f)\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/oldui/client/DoKitMcClientFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.oldui.client\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.TextView\nimport androidx.lifecycle.lifecycleScope\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.kit.mc.oldui.DoKitMcManager\nimport com.didichuxing.doraemonkit.kit.mc.ui.DoKitMcActivity\nimport com.didichuxing.doraemonkit.kit.mc.ui.adapter.McClientHistory\nimport com.didichuxing.doraemonkit.kit.mc.net.DoKitMcClient\nimport com.didichuxing.doraemonkit.kit.mc.net.DokitMcConnectManager\nimport com.didichuxing.doraemonkit.kit.mc.ui.McPages\nimport com.didichuxing.doraemonkit.mc.R\nimport kotlinx.coroutines.launch\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/10-10:52\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitMcClientFragment : BaseFragment() {\n    private var history: McClientHistory? = null\n\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_mc_client\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        history = DokitMcConnectManager.itemHistory\n\n        findViewById<TextView>(R.id.tv_host_info).text =\n            \"当前设备已连接主机:ws://${history?.host}:${history?.port}/${history?.path}\"\n        findViewById<View>(R.id.btn_close).setOnClickListener {\n            lifecycleScope.launch {\n                DoKitMcManager.WS_MODE = TestMode.UNKNOWN\n                DoKit.removeFloating(ClientDoKitView::class)\n                DoKitMcClient.close()\n                if (activity is DoKitMcActivity) {\n                    (activity as DoKitMcActivity).onBackPressed()\n                }\n            }\n        }\n\n        findViewById<View>(R.id.btn_history).setOnClickListener {\n            lifecycleScope.launch {\n                if (activity is DoKitMcActivity) {\n                    (activity as DoKitMcActivity).pushFragment(McPages.CLIENT_HISTORY)\n                }\n            }\n        }\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/oldui/client/DoKitMcClientHistoryFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.oldui.client\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.view.View\nimport android.widget.Button\nimport androidx.appcompat.app.AlertDialog\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.extension.isTrueWithCor\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.kit.mc.oldui.DoKitMcManager\nimport com.didichuxing.doraemonkit.kit.mc.net.DoKitMcClient\nimport com.didichuxing.doraemonkit.kit.mc.net.DokitMcConnectManager\nimport com.didichuxing.doraemonkit.kit.mc.ui.*\nimport com.didichuxing.doraemonkit.kit.mc.ui.adapter.McClientHistory\nimport com.didichuxing.doraemonkit.kit.mc.ui.adapter.McClientHistoryAdapter\nimport com.didichuxing.doraemonkit.kit.mc.utils.ClientHistoryUtils\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.util.*\nimport com.didichuxing.doraemonkit.widget.recyclerview.DividerItemDecoration\nimport com.didichuxing.doraemonkit.zxing.activity.CaptureActivity\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.util.*\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/10-10:52\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitMcClientHistoryFragment : BaseFragment() {\n\n    private val REQUEST_CODE_SCAN = 0x101\n\n    private lateinit var mRv: RecyclerView\n    private lateinit var mAdapter: McClientHistoryAdapter\n    private lateinit var histories: MutableList<McClientHistory>\n\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_mc_client_history\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        val add: Button = findViewById(R.id.add)\n        add.setOnClickListener { view ->\n            startScan()\n        }\n\n        mRv = findViewById(R.id.rv)\n\n        mAdapter = McClientHistoryAdapter(mutableListOf<McClientHistory>()) { client ->\n            handleConnect(client)\n        }\n\n        mAdapter.setOnItemClickListener { adapter, view, pos ->\n            val data = histories[pos]\n            DokitMcConnectManager.itemHistory = data\n\n            if (activity is DoKitMcActivity) {\n                (activity as DoKitMcActivity).pushFragment(McPages.CLIENT)\n            }\n        }\n\n        mAdapter.setOnItemLongClickListener { adapter, view, pos ->\n            val data = histories[pos]\n            lifecycleScope.launch {\n                privacyInterceptDialog(\"提示\", \"是否删除连接历史记录\").isTrueWithCor {\n                    ClientHistoryUtils.removeClientHistory(data)\n                    ToastUtils.showShort(\"已删除记录\")\n                    updateHistoryView()\n                }\n            }\n            return@setOnItemLongClickListener false\n        }\n\n        mRv.apply {\n            adapter = mAdapter\n            layoutManager = LinearLayoutManager(requireActivity())\n            val decoration = DividerItemDecoration(DividerItemDecoration.VERTICAL)\n            decoration.setDrawable(resources.getDrawable(R.drawable.dk_divider))\n            addItemDecoration(decoration)\n        }\n\n        updateHistoryView()\n\n    }\n\n    override fun onResume() {\n        super.onResume()\n        updateHistoryView()\n    }\n\n    private fun updateHistoryView() {\n        lifecycleScope.launch {\n            val clients = ClientHistoryUtils.loadClientHistory()\n            val current = DokitMcConnectManager.currentClientHistory\n            for (history in clients) {\n                if (current != null) {\n                    history.enable = TextUtils.equals(history.url, current.url)\n                } else {\n                    history.enable = false\n                }\n\n            }\n\n            histories = clients\n            clients?.let {\n                mAdapter.setList(clients)\n            }\n        }\n    }\n\n    /**\n     * 处理dialog返回值\n     */\n    private suspend fun privacyInterceptDialog(title: String, content: String): Boolean =\n        suspendCoroutine {\n            AlertDialog.Builder(requireActivity())\n                .setTitle(title)\n                .setMessage(content)\n                .setCancelable(false)\n                .setPositiveButton(\"确认\") { dialog, _ ->\n                    dialog.dismiss()\n                    it.resume(true)\n                }\n                .setNegativeButton(\"取消\") { dialog, _ ->\n                    dialog.dismiss()\n                    it.resume(false)\n                }\n                .show()\n\n        }\n\n    /**\n     * 开始扫描\n     */\n    private fun startScan() {\n        val intent = Intent(activity, DoKitMcScanActivity::class.java)\n        startActivityForResult(intent, REQUEST_CODE_SCAN)\n    }\n\n\n    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {\n        super.onActivityResult(requestCode, resultCode, data)\n        if (requestCode == REQUEST_CODE_SCAN && resultCode == Activity.RESULT_OK) {\n            if (data != null && data.hasExtra(CaptureActivity.INTENT_EXTRA_KEY_QR_SCAN)) {\n                val code = data.getStringExtra(CaptureActivity.INTENT_EXTRA_KEY_QR_SCAN)\n                if (!TextUtils.isEmpty(code)) {\n                    try {\n                        val uri = Uri.parse(code)\n                        uri?.let {\n                            val name = uri.host.toString()\n                            val time = TimeUtils.date2String(Date())\n                            val url = \"ws://${uri.host}:${uri.port}/${uri.path}\"\n                            val history = McClientHistory(uri.host!!, uri.port, uri.path!!, name, time, url)\n                            ClientHistoryUtils.saveClientHistory(history)\n                            handleConnect(history)\n                        }\n\n                    } catch (e: Exception) {\n                        e.printStackTrace()\n                    } finally {\n//                        if (activity is DoKitMcActivity) {\n//                            (activity as DoKitMcActivity).onBackPressed()\n//                        }\n                    }\n                } else {\n                    handleNoResult()\n                }\n            } else {\n                handleNoResult()\n            }\n        } else {\n            handleNoResult()\n        }\n    }\n\n    /**\n     * 没有返回结果\n     */\n    private fun handleNoResult() {\n        ToastUtils.showShort(\"没有扫描到任何内容 >_< .\")\n    }\n\n    private fun handleConnect(clientHistory: McClientHistory) {\n        DoKitMcClient.connect(clientHistory.host!!, clientHistory.port, clientHistory.path!!) { code, hostInfo ->\n            withContext(Dispatchers.Main) {\n                when (code) {\n                    DoKitMcClient.CONNECT_SUCCEED -> {\n                        DoKitMcManager.HOST_INFO = hostInfo\n                        DoKitTestManager.startTest(TestMode.CLIENT)\n                        DoKitMcManager.WS_MODE = TestMode.CLIENT\n\n                        DokitMcConnectManager.currentClientHistory = clientHistory\n                        updateHistoryView()\n                        //启动悬浮窗\n                        DoKit.launchFloating(ClientDoKitView::class)\n                    }\n                    DoKitMcClient.CONNECT_FAIL -> {\n                        DoKitTestManager.closeTest()\n                        DoKitMcManager.WS_MODE = TestMode.UNKNOWN\n                        LogHelper.e(TAG, \"message===>$hostInfo\")\n                        ToastUtils.showShort(hostInfo.toString())\n                        DokitMcConnectManager.currentClientHistory = null\n                    }\n                    else -> {\n                        LogHelper.e(TAG, \"code=$code, message===>$hostInfo\")\n                    }\n                }\n            }\n        }\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/oldui/host/DoKitMcHostFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.oldui.host\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.Button\nimport android.widget.ImageView\nimport android.widget.TextView\nimport androidx.lifecycle.lifecycleScope\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.mc.ui.DoKitMcActivity\nimport com.didichuxing.doraemonkit.kit.mc.oldui.DoKitMcManager\nimport com.didichuxing.doraemonkit.kit.mc.net.DoKitMcHostServer\nimport com.didichuxing.doraemonkit.kit.mc.utils.CodeUtils\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.util.ImageUtils\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport kotlinx.coroutines.CoroutineExceptionHandler\nimport kotlinx.coroutines.launch\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/10-10:52\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitMcHostFragment : BaseFragment() {\n    private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->\n        LogHelper.e(TAG, \"error message: ${throwable.message}\")\n    }\n\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_mc_host\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        val ivCode = findViewById<ImageView>(R.id.iv_code)\n        val tvHost = findViewById<TextView>(R.id.tv_host)\n        val btnClose = findViewById<Button>(R.id.btn_close)\n        btnClose.setOnClickListener {\n            lifecycleScope.launch(exceptionHandler) {\n                DoKitMcHostServer.stop {\n                    DoKitMcManager.WS_MODE = TestMode.UNKNOWN\n                    DoKit.removeFloating(HostDoKitView::class)\n                    if (activity is DoKitMcActivity) {\n                        (activity as DoKitMcActivity).onBackPressed()\n                    }\n                }\n            }\n        }\n\n\n        val host = \"ws://${DoKitManager.IP_ADDRESS_BY_WIFI}:${DoKitManager.MC_WS_PORT}/mc\"\n        val logo = ImageUtils.getBitmap(R.mipmap.dk_logo)\n        val qCode = CodeUtils.createCode(activity, host, logo)\n        tvHost.text = host\n        ivCode.setImageBitmap(qCode)\n\n        if (DoKitMcManager.WS_MODE != TestMode.HOST) {\n            DoKitMcHostServer.start {\n                DoKitMcManager.WS_MODE = TestMode.HOST\n                //启动悬浮窗\n                DoKit.launchFloating(HostDoKitView::class)\n            }\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/oldui/host/HostDoKitView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.oldui.host\n\nimport android.content.Context\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams\nimport com.didichuxing.doraemonkit.kit.mc.ui.McPages\nimport com.didichuxing.doraemonkit.kit.mc.utils.McPageUtils\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.util.ConvertUtils\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/17-14:37\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass HostDoKitView : AbsDoKitView() {\n    override fun onCreate(context: Context?) {\n    }\n\n    override fun onCreateView(context: Context?, rootView: FrameLayout?): View {\n        val view = LayoutInflater.from(context).inflate(R.layout.dk_dokitview_host, rootView, false)\n        return view\n    }\n\n    override fun onViewCreated(rootView: FrameLayout?) {\n        rootView?.setOnClickListener {\n            McPageUtils.startFragment(McPages.HOST)\n        }\n    }\n\n    override fun initDokitViewLayoutParams(params: DoKitViewLayoutParams) {\n        params.width = ConvertUtils.dp2px(70.0f)\n        params.height = ConvertUtils.dp2px(70.0f)\n        params.gravity = Gravity.TOP or Gravity.LEFT\n        params.x = ConvertUtils.dp2px(280f)\n        params.y = ConvertUtils.dp2px(25f)\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/oldui/main/DoKitMcMainFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.oldui.main\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.Button\nimport androidx.appcompat.app.AlertDialog\nimport androidx.lifecycle.lifecycleScope\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.extension.isTrueWithCor\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.kit.test.mock.http.HttpMockServer\nimport com.didichuxing.doraemonkit.kit.test.mock.http.HttpMockServer.RESPONSE_OK\nimport com.didichuxing.doraemonkit.kit.mc.oldui.DoKitMcManager\nimport com.didichuxing.doraemonkit.kit.mc.ui.DoKitMcActivity\nimport com.didichuxing.doraemonkit.kit.mc.ui.adapter.McCaseInfoDialogProvider\nimport com.didichuxing.doraemonkit.kit.mc.ui.McPages\nimport com.didichuxing.doraemonkit.kit.test.mock.data.McCaseInfo\nimport com.didichuxing.doraemonkit.kit.test.mock.data.McConfigInfo\nimport com.didichuxing.doraemonkit.kit.mc.oldui.record.RecordingDoKitView\nimport com.didichuxing.doraemonkit.kit.test.mock.data.CaseInfo\nimport com.didichuxing.doraemonkit.kit.mc.utils.McCaseUtils\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport com.didichuxing.doraemonkit.util.SPUtils\nimport com.didichuxing.doraemonkit.widget.dialog.DialogListener\nimport com.didichuxing.doraemonkit.widget.dialog.DialogProvider\nimport kotlinx.coroutines.CoroutineExceptionHandler\nimport kotlinx.coroutines.launch\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/10-10:52\n * 描    述：一机多控main fragment\n * 修订历史：\n * ================================================\n */\nclass DoKitMcMainFragment : BaseFragment() {\n\n    private val mExceptionHandler = CoroutineExceptionHandler { _, throwable ->\n        LogHelper.e(TAG, \"error message: ${throwable.message}\")\n    }\n\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_mc_select\n    }\n\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        if (DoKitMcManager.MC_CASE_ID.isEmpty()) {\n            DoKitMcManager.MC_CASE_ID = McCaseUtils.loadCaseId()\n        }\n\n        val host = findViewById<Button>(R.id.tv_host)\n        host.setOnClickListener {\n            if (DoKitMcManager.CONNECT_MODE != TestMode.UNKNOWN) {\n                ToastUtils.showShort(\"当前处于联网模式，请先关闭！\")\n                return@setOnClickListener\n            }\n            checkMcPreparedState {\n                if (activity is DoKitMcActivity) {\n                    (activity as DoKitMcActivity).pushFragment(McPages.HOST)\n                }\n            }\n        }\n        val client = findViewById<Button>(R.id.tv_client)\n        client.setOnClickListener {\n            if (DoKitMcManager.CONNECT_MODE != TestMode.UNKNOWN) {\n                ToastUtils.showShort(\"当前处于联网模式，请先关闭！\")\n                return@setOnClickListener\n            }\n            checkMcPreparedState {\n                if (activity is DoKitMcActivity) {\n                    (activity as DoKitMcActivity).pushFragment(McPages.CLIENT_HISTORY)\n                }\n            }\n\n        }\n\n        val record = findViewById<Button>(R.id.tv_record)\n        record.setOnClickListener {\n            if (DoKitManager.PRODUCT_ID.isEmpty()) {\n                ToastUtils.showShort(\"DoKit初始化时未传入产品id\")\n                return@setOnClickListener\n            }\n\n            if (DoKitTestManager.isHostMode()) {\n                ToastUtils.showShort(\"当前已处于录制状态\")\n                return@setOnClickListener\n            }\n\n            //请求一个CaseId\n            lifecycleScope.launch(mExceptionHandler) {\n                privacyInterceptDialog(\n                    \"隐私提醒\",\n                    \"\"\"1.用例采集会实时录制并上传接口数据到dokit.cn平台,请确认是否要开启？\n2. 请确认已在dokit.cn平台一机多控模块添加诸如token、sign等无法确认接口唯一性的exclude字段(字段作用于全部录制接口)。\n                            \"\"\"\n                ).isTrueWithCor(\n                    isFalse = {\n                        ToastUtils.showShort(\"取消用例采集\")\n                    }) {\n                    try {\n                        val caseInfo = HttpMockServer.mockStart<McCaseInfo>()\n                        if (caseInfo.code == RESPONSE_OK) {\n                            saveRecodingStatus(caseInfo.data)\n                        }\n                    } catch (e: Exception) {\n                        LogHelper.e(TAG, \"e===>${e.message}\")\n                        DoKitTestManager.closeTest()\n                        ToastUtils.showShort(\"用例采集启动失败\")\n                    }\n                }\n            }\n\n        }\n\n\n        val upload = findViewById<Button>(R.id.tv_upload)\n        upload.setOnClickListener {\n            if (DoKitManager.PRODUCT_ID.isEmpty()) {\n                ToastUtils.showShort(\"DoKit初始化时未传入产品id\")\n                return@setOnClickListener\n            }\n\n            if (DoKitMcManager.MC_CASE_ID.isEmpty()) {\n                ToastUtils.showShort(\"请先开始执行用例采集\")\n                return@setOnClickListener\n            }\n\n            lifecycleScope.launch(mExceptionHandler) {\n                val result = HttpMockServer.mockStop<Any>(mcCaseInfoDialog())\n                if (result.code == RESPONSE_OK) {\n                    DoKit.removeFloating(RecordingDoKitView::class)\n                    SPUtils.getInstance().put(DoKitMcManager.MC_CASE_RECODING_KEY, false)\n                    DoKitTestManager.closeTest()\n                    DoKitMcManager.IS_MC_RECODING = false\n                    ToastUtils.showShort(\"用例上传成功\")\n                } else {\n                    LogHelper.e(TAG, \"error msg===>${result.msg}\")\n                }\n\n            }\n        }\n\n        val datas = findViewById<Button>(R.id.tv_datas)\n        datas.setOnClickListener {\n            if (DoKitManager.PRODUCT_ID.isEmpty()) {\n                ToastUtils.showShort(\"DoKit初始化时未传入产品id\")\n                return@setOnClickListener\n            }\n            (requireActivity() as DoKitMcActivity).pushFragment(McPages.MC_CASELIST)\n        }\n\n        //加载exclude key\n        if (DoKitManager.PRODUCT_ID.isNotBlank()) {\n            lifecycleScope.launch(mExceptionHandler) {\n                val config = HttpMockServer.getMcConfig<McConfigInfo>()\n                if (config.code == RESPONSE_OK) {\n                    config.data?.multiControl?.exclude?.let {\n                        HttpMockServer.mExcludeKey = it\n                    }\n                } else {\n                    ToastUtils.showShort(config.msg)\n                }\n\n            }\n        }\n\n    }\n\n    private fun checkMcPreparedState(callback: () -> Unit) {\n        if (DoKitTestManager.isHostMode()) {\n            ToastUtils.showShort(\"当前处于数据录制状态，请先执行上传操作\")\n            return\n        }\n        if (DoKitMcManager.MC_CASE_ID.isEmpty()) {\n            lifecycleScope.launch(mExceptionHandler) {\n                privacyInterceptDialog(\n                    \"操作提醒\",\n                    \"当前未选中任何的数据用例，请确认要否要以数据不同步模式运行？\"\n                ).isTrueWithCor {\n                    callback()\n                }\n            }\n        } else {\n            callback()\n        }\n    }\n\n    /**\n     * 持久化录制状态 方便重启继续录制\n     */\n    private fun saveRecodingStatus(configInfo: McCaseInfo?) {\n        configInfo?.let {\n            DoKitMcManager.MC_CASE_ID = it.caseId\n            DoKit.launchFloating(RecordingDoKitView::class.java)\n            DoKitTestManager.startTest(TestMode.HOST)\n            SPUtils.getInstance().put(DoKitMcManager.MC_CASE_ID_KEY, DoKitMcManager.MC_CASE_ID)\n            SPUtils.getInstance().put(DoKitMcManager.MC_CASE_RECODING_KEY, true)\n            ToastUtils.showShort(\"开始用例采集\")\n        }\n\n    }\n\n    /**\n     * 处理dialog返回值\n     */\n    private suspend fun privacyInterceptDialog(title: String, content: String): Boolean =\n        suspendCoroutine {\n            AlertDialog.Builder(requireActivity())\n                .setTitle(title)\n                .setMessage(content)\n                .setCancelable(false)\n                .setPositiveButton(\"开启\") { dialog, _ ->\n                    dialog.dismiss()\n                    it.resume(true)\n                }\n                .setNegativeButton(\"取消\") { dialog, _ ->\n                    dialog.dismiss()\n                    it.resume(false)\n                }\n                .show()\n\n        }\n\n    /**\n     * 确认用例信息\n     */\n    private suspend fun mcCaseInfoDialog(): CaseInfo = suspendCoroutine {\n        showDialog(McCaseInfoDialogProvider(null, object : DialogListener {\n            override fun onPositive(dialogProvider: DialogProvider<*>): Boolean {\n                val provider = dialogProvider as McCaseInfoDialogProvider\n                val (_, _, caseName, personName) = provider.getCaseInfo()\n\n                if (caseName.isBlank()) {\n                    ToastUtils.showShort(\"用例名称不能为空\")\n                    return false\n                }\n\n                if (personName.isBlank()) {\n                    ToastUtils.showShort(\"用例采集人不能为空\")\n                    return false\n                }\n\n                it.resume(\n                    CaseInfo(\n                        caseName = caseName,\n                        personName = personName\n                    )\n                )\n                return true\n            }\n\n            override fun onNegative(dialogProvider: DialogProvider<*>): Boolean {\n                return true\n            }\n\n        }))\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/oldui/record/DoKitMcDatasFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.oldui.record\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.TextView\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.didichuxing.doraemonkit.extension.doKitGlobalScope\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.kit.mc.ui.adapter.McCaseInfo\nimport com.didichuxing.doraemonkit.kit.mc.ui.adapter.McCaseListAdapter\nimport com.didichuxing.doraemonkit.kit.test.mock.http.HttpMockServer\nimport com.didichuxing.doraemonkit.kit.mc.utils.McCaseUtils\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.didichuxing.doraemonkit.widget.recyclerview.DividerItemDecoration\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/10-10:52\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitMcDatasFragment : BaseFragment() {\n    private lateinit var mRv: RecyclerView\n    private lateinit var mEmpty: TextView\n    private lateinit var mAdapter: McCaseListAdapter\n\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_mc_datas\n    }\n\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        mRv = findViewById(R.id.rv)\n        mEmpty = findViewById(R.id.tv_empty)\n\n        mAdapter = McCaseListAdapter(mutableListOf<McCaseInfo>())\n        mAdapter.setOnItemClickListener { adapter, _, pos ->\n            val item = adapter.data[pos] as McCaseInfo\n            if (item.isChecked) {\n                for (i in adapter.data) {\n                    (i as McCaseInfo).isChecked = false\n                }\n                McCaseUtils.saveCaseId(\"\")\n            } else {\n                for (i in adapter.data) {\n                    (i as McCaseInfo).isChecked = false\n                }\n                item.isChecked = true\n                McCaseUtils.saveCaseId(item.caseId)\n                ToastUtils.showShort(\"用例: ${item.caseName} 已被选中\")\n            }\n            if (DoKitTestManager.isHostMode()) {\n                doKitGlobalScope.launch {\n                    delay(100)\n                    adapter.notifyDataSetChanged()\n                }\n            } else {\n                adapter.notifyDataSetChanged()\n            }\n\n        }\n        mRv.apply {\n            adapter = mAdapter\n            layoutManager = LinearLayoutManager(requireActivity())\n            val decoration = DividerItemDecoration(DividerItemDecoration.VERTICAL)\n            decoration.setDrawable(resources.getDrawable(R.drawable.dk_divider))\n            addItemDecoration(decoration)\n        }\n\n\n        lifecycleScope.launch {\n            val data = HttpMockServer.caseList<McCaseInfo>().data\n            data?.let {\n                if (it.isEmpty()) {\n                    mEmpty.visibility = View.VISIBLE\n                } else {\n                    val caseId = McCaseUtils.loadCaseId()\n                    it.forEach { info ->\n                        info.isChecked = caseId == info.caseId\n                    }\n                    mAdapter.setList(it)\n                }\n\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/oldui/record/RecordingDoKitView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.oldui.record\n\nimport android.content.Context\nimport android.content.Intent\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport android.widget.ImageView\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams\nimport com.didichuxing.doraemonkit.kit.mc.oldui.DoKitMcManager\nimport com.didichuxing.doraemonkit.kit.mc.ui.DoKitMcActivity\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.util.ActivityUtils\nimport com.didichuxing.doraemonkit.util.ConvertUtils\nimport com.didichuxing.doraemonkit.util.SPUtils\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/17-14:37\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass RecordingDoKitView : AbsDoKitView() {\n    private var mRedDot: View? = null\n    private var mExtend: TextView? = null\n    private lateinit var mDotFlow: Flow<Int>\n    private lateinit var mEllipsisFlow: Flow<Int>\n    override fun onCreate(context: Context?) {\n        mDotFlow = flow {\n            while (true) {\n                emit(0)\n                delay(500)\n                emit(1)\n                delay(500)\n            }\n        }\n\n        mEllipsisFlow = flow {\n            while (true) {\n                (0..3).forEach {\n                    emit(it)\n                    delay(500)\n                }\n            }\n        }\n    }\n\n    override fun onCreateView(context: Context?, rootView: FrameLayout?): View {\n        return LayoutInflater.from(context)\n            .inflate(R.layout.dk_dokitview_recording, rootView, false)\n    }\n\n    override fun onViewCreated(rootView: FrameLayout?) {\n        rootView?.setOnClickListener {\n            if (ActivityUtils.getTopActivity() is DoKitMcActivity) {\n                return@setOnClickListener\n            }\n            val intent = Intent(activity, DoKitMcActivity::class.java)\n            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK\n            activity.startActivity(intent)\n        }\n        rootView?.findViewById<ImageView>(R.id.iv_close)?.setOnClickListener {\n            //TODO 需要补充取消录制接口\n            DoKit.removeFloating(RecordingDoKitView::class)\n            SPUtils.getInstance().put(DoKitMcManager.MC_CASE_RECODING_KEY, false)\n            DoKitTestManager.closeTest()\n            DoKitMcManager.IS_MC_RECODING = false\n            ToastUtils.showShort(\"用例取消录制\")\n        }\n\n        mRedDot = findViewById(R.id.red_dot)\n        mExtend = findViewById(R.id.tv_extend)\n        doKitViewScope.launch {\n            mDotFlow.flowOn(Dispatchers.IO)\n                .collect {\n                    when (it) {\n                        0 -> mRedDot?.visibility = View.VISIBLE\n                        1 -> mRedDot?.visibility = View.INVISIBLE\n                        else -> {\n                        }\n                    }\n                }\n        }\n\n\n        doKitViewScope.launch {\n            mEllipsisFlow.flowOn(Dispatchers.IO)\n                .collect {\n                    when (it) {\n                        0 -> mExtend?.text = \"\"\n                        1 -> mExtend?.text = \".\"\n                        2 -> mExtend?.text = \"..\"\n                        3 -> mExtend?.text = \"...\"\n                        else -> {\n                        }\n                    }\n\n                }\n        }\n\n    }\n\n    override fun initDokitViewLayoutParams(params: DoKitViewLayoutParams) {\n        params.width = DoKitViewLayoutParams.WRAP_CONTENT\n        params.height = DoKitViewLayoutParams.WRAP_CONTENT\n        params.gravity = Gravity.TOP or Gravity.LEFT\n        params.x = ConvertUtils.dp2px(25f)\n        params.y = ConvertUtils.dp2px(25f)\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/report/MCRecordManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.report\n\nimport com.didichuxing.doraemonkit.kit.test.event.EventType\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/10/1-15:51\n * 描    述：主机行为录制\n * 修订历史：\n * ================================================\n */\nobject MCRecordManager {\n    /**\n     * 是否处于录制状态\n     */\n    var isEventRecoding = false\n\n    val mWsEvents: MutableList<ControlEvent> by lazy {\n        mutableListOf<ControlEvent>()\n    }\n\n    /**\n     * 有效的事件类型\n     */\n    private val effectiveEventTypes = arrayOf(\n        EventType.APP_ON_FOREGROUND,\n        EventType.APP_ON_BACKGROUND,\n        EventType.ACTIVITY_BACK_PRESSED,\n        EventType.WSE_COMMON_EVENT\n    )\n\n    /**\n     * 拦截所有事件\n     */\n    fun intercept(wsEvent: ControlEvent) {\n        //拦截无效的事件类型\n        if (needFilter(wsEvent.eventType)) {\n            return\n        }\n\n        //保存有效的事件类型\n        mWsEvents.add(wsEvent)\n    }\n\n    /**\n     * 是否过滤特定的事件类型\n     */\n    private fun needFilter(eventType: EventType): Boolean {\n        if (effectiveEventTypes.contains(eventType)) {\n            return false\n        }\n\n        return true\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/report/RecordActionCase.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.report\n\ndata class RecordActionCase(\n    val name: String,\n    val dateTime: String\n\n)\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/report/RecordActionStep.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.report\n\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\n\ndata class RecordActionStep(\n    val dateTime: String,\n    val type: String,\n    val event: ControlEvent,\n    val dataList: MutableList<RecordData>\n)\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/report/RecordData.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.report\n\ndata class RecordData(\n    val dateTime: String,\n    val type: String,\n    val url: String,\n    val dataKey: String\n)\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/report/ScreenShotManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.mc.report;\n\nimport android.app.Activity;\nimport android.graphics.Bitmap;\nimport android.view.View;\n\n\nimport com.didichuxing.doraemonkit.util.RandomUtils;\n\n\nimport java.io.File;\nimport java.io.FileOutputStream;\n\n/**\n * didi Create on 2022/4/1 .\n * <p>\n * Copyright (c) 2022/4/1 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/1 4:25 下午\n * @Description 截屏管理器\n */\n\npublic class ScreenShotManager {\n\n\n    private String screenFileDir;\n\n\n    public ScreenShotManager(String screenFileDir) {\n        this.screenFileDir = screenFileDir;\n    }\n\n    public String screenshot(Activity activity) {\n        String fileName = createNextFileName();\n        String fullFilepath = screenFileDir + \"/\" + fileName + \".jpeg\";\n        try {\n            View decorView = activity.getWindow().getDecorView();\n            decorView.setDrawingCacheEnabled(true);\n            Bitmap bitmap = decorView.getDrawingCache();\n            //保存图片\n            FileOutputStream outputStream = new FileOutputStream(fullFilepath);\n            boolean ok = bitmap.compress(Bitmap.CompressFormat.JPEG, 50, outputStream);\n            if (ok) {\n                return fileName;\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return \"\";\n    }\n\n    private String createNextFileName() {\n        String name = RandomUtils.random64HexString();\n        String dir = screenFileDir;\n        String fullFilepath = dir + \"/\" + name + \".jpeg\";\n        File file = new File(fullFilepath);\n        int index = 0;\n        while (true) {\n            if (file.exists()) {\n                index++;\n                name = name + \"-\" + index;\n                file = new File(dir + \"/\" + name + \".jpeg\");\n                continue;\n            }\n            break;\n        }\n        return name;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/ui/BorderDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.mc.ui;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.FrameLayout;\n\nimport com.didichuxing.doraemonkit.R;\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView;\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams;\nimport com.didichuxing.doraemonkit.kit.viewcheck.LayoutBorderView;\nimport com.didichuxing.doraemonkit.model.ViewInfo;\n\n/**\n * Created by jintai on 2019/09/26.\n * 在相应的界面上绘制指定View的边框\n */\npublic class BorderDoKitView extends AbsDoKitView {\n    private LayoutBorderView mLayoutBorderView = null;\n\n    @Override\n    public void onCreate(Context context) {\n\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n\n    }\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_float_view_check_draw, null);\n    }\n\n\n    @Override\n    public void initDokitViewLayoutParams(DoKitViewLayoutParams params) {\n        params.flags = DoKitViewLayoutParams.FLAG_NOT_FOCUSABLE_AND_NOT_TOUCHABLE;\n        params.width = DoKitViewLayoutParams.MATCH_PARENT;\n        params.height = DoKitViewLayoutParams.MATCH_PARENT;\n    }\n\n\n    @Override\n    public void onViewCreated(FrameLayout view) {\n        mLayoutBorderView = findViewById(R.id.rect_view);\n        setDoKitViewNotResponseTouchEvent(getDoKitView());\n    }\n\n\n    /**\n     * 解决ViewCheckDrawDokitView的margin被改变的bug\n     */\n    @Override\n    public void onResume() {\n        super.onResume();\n        if (getNormalLayoutParams() != null) {\n            FrameLayout.LayoutParams params = getNormalLayoutParams();\n            params.setMargins(0, 0, 0, 0);\n            params.width = FrameLayout.LayoutParams.MATCH_PARENT;\n            params.height = FrameLayout.LayoutParams.MATCH_PARENT;\n            immInvalidate();\n        }\n    }\n\n    @Override\n    public boolean canDrag() {\n        return false;\n    }\n\n    public void showBorder(View target) {\n        if (target == null) {\n            mLayoutBorderView.showViewLayoutBorder((ViewInfo) null);\n        } else {\n            mLayoutBorderView.showViewLayoutBorder(new ViewInfo(target));\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/ui/DoKitMcActivity.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.ui\n\nimport android.os.Bundle\nimport android.text.TextUtils\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.kit.core.NewBaseActivity\nimport com.didichuxing.doraemonkit.kit.mc.oldui.client.DoKitMcClientFragment\nimport com.didichuxing.doraemonkit.kit.mc.oldui.client.DoKitMcClientHistoryFragment\nimport com.didichuxing.doraemonkit.kit.mc.ui.connect.MultiControlAllFragment\nimport com.didichuxing.doraemonkit.kit.mc.oldui.host.DoKitMcHostFragment\nimport com.didichuxing.doraemonkit.kit.mc.oldui.main.DoKitMcMainFragment\nimport com.didichuxing.doraemonkit.kit.mc.oldui.record.DoKitMcDatasFragment\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/10-10:52\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitMcActivity : NewBaseActivity() {\n    private lateinit var mTitlebar: HomeTitleBar\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.dk_activity_mc)\n\n        mTitlebar = findViewById(R.id.title_bar)\n        mTitlebar.setListener {\n            onBackPressed()\n        }\n\n        val mode: String? = intent?.getStringExtra(\"WS_MODE_ORDINAL\")\n        if (TextUtils.isEmpty(mode)) {\n            newHomeFragment()\n        } else {\n            mode?.let {\n                val vs: McPages = McPages.valueOf(it)\n                changeFragment(vs)\n            }\n        }\n    }\n\n\n    fun changeFragment(wsMode: McPages) {\n        changeFragment(wsMode, false)\n    }\n\n    fun pushFragment(wsMode: McPages) {\n        changeFragment(wsMode, true)\n    }\n\n    private fun newHomeFragment() {\n        changeFragment(McPages.CONNECT_HISTORY)\n    }\n\n\n    private fun changeFragment(wsMode: McPages, push: Boolean) {\n        val fragment: BaseFragment = when (wsMode) {\n            McPages.MAIN -> {\n                mTitlebar.setTitle(\"一机多控（原)\")\n                DoKitMcMainFragment()\n            }\n            McPages.RECORDING,\n            McPages.UNKNOW -> {\n                mTitlebar.setTitle(\"一机多控（原）\")\n                DoKitMcMainFragment()\n            }\n            McPages.CONNECT_HISTORY -> {\n                mTitlebar.setTitle(\"一机多控\")\n                MultiControlAllFragment()\n            }\n            McPages.HOST -> {\n                mTitlebar.setTitle(\"一机多控（主机\")\n                DoKitMcHostFragment()\n            }\n            McPages.CLIENT -> {\n                mTitlebar.setTitle(\"一机多控（从机\")\n                DoKitMcClientFragment()\n            }\n\n            McPages.CLIENT_HISTORY -> {\n                mTitlebar.setTitle(\"一机多控（从机历史\")\n                DoKitMcClientHistoryFragment()\n            }\n\n            McPages.MC_CASELIST -> {\n                mTitlebar.setTitle(\"一机多控（用例列表\")\n                DoKitMcDatasFragment()\n            }\n\n            else -> {\n                mTitlebar.setTitle(\"一机多控（原)\")\n                DoKitMcMainFragment()\n            }\n        }\n\n        if (push) {\n            showContent(R.id.fragment_container_view, fragment)\n        } else {\n            replaceContent(R.id.fragment_container_view, fragment)\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/ui/DoKitMcScanActivity.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.ui\n\nimport android.Manifest\nimport android.content.pm.PackageManager\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport androidx.annotation.IdRes\nimport androidx.core.app.ActivityCompat\nimport androidx.core.content.ContextCompat\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\nimport com.didichuxing.doraemonkit.zxing.activity.CaptureActivity\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/10-10:52\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitMcScanActivity : CaptureActivity() {\n\n    companion object {\n        private const val REQUEST_CODE = 200999\n    }\n\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        checkCameraPermission()\n    }\n    override fun setContentView(@IdRes layoutResID: Int) {\n        super.setContentView(layoutResID)\n        initTitleBar()\n    }\n\n    private fun initTitleBar() {\n        val homeTitleBar = HomeTitleBar(this)\n        homeTitleBar.setBackgroundColor(resources.getColor(R.color.foreground_wtf))\n        homeTitleBar.setTitle(resources.getString(R.string.dk_kit_multi_control))\n        homeTitleBar.setIcon(R.mipmap.dk_close_icon)\n        homeTitleBar.setListener { finish() }\n        val params = FrameLayout.LayoutParams(\n            ViewGroup.LayoutParams.MATCH_PARENT,\n            resources.getDimension(R.dimen.dk_home_title_height).toInt()\n        )\n        (findViewById<View>(android.R.id.content) as FrameLayout).addView(homeTitleBar, params)\n    }\n\n    private fun checkCameraPermission() {\n        val hasCameraPermission: Int = ContextCompat.checkSelfPermission(application, Manifest.permission.CAMERA)\n        if (hasCameraPermission != PackageManager.PERMISSION_GRANTED) {\n            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), REQUEST_CODE)\n        }\n    }\n\n    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults)\n        if (requestCode == REQUEST_CODE) {\n\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/ui/McDialogDoKitView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.ui\n\nimport android.content.Context\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams\nimport com.didichuxing.doraemonkit.mc.R\n\n/**\n * Created by jintai on 2019/09/26.\n * 在相应的界面上弹出提示框\n */\nclass McDialogDoKitView : AbsDoKitView() {\n\n    lateinit var mTvExceptionType: TextView\n    lateinit var mTvOk: TextView\n    lateinit var exception: String\n\n    override fun onCreate(context: Context) {\n        exception = bundle?.getString(\"text\", \"未知同步异常类型\")!!\n    }\n\n    override fun onCreateView(context: Context, view: FrameLayout): View {\n        return LayoutInflater.from(context).inflate(R.layout.dk_dokitview_dialog, null)\n    }\n\n    override fun initDokitViewLayoutParams(params: DoKitViewLayoutParams) {\n        params.flags = DoKitViewLayoutParams.FLAG_NOT_FOCUSABLE_AND_NOT_TOUCHABLE\n        params.gravity = Gravity.CENTER\n        params.width = DoKitViewLayoutParams.MATCH_PARENT\n        params.height = DoKitViewLayoutParams.MATCH_PARENT\n    }\n\n    override fun onViewCreated(view: FrameLayout) {\n        mTvExceptionType = findViewById<TextView>(R.id.exception_type)!!\n        mTvExceptionType.text = exception\n        mTvOk = findViewById<TextView>(R.id.tv_ok)!!\n        mTvOk.setOnClickListener {\n            DoKit.removeFloating(McDialogDoKitView::class.java)\n        }\n    }\n\n\n    override fun canDrag(): Boolean {\n        return false\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/ui/McPages.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.ui\n\n/**\n * 测试工作模式 mode\n */\nenum class McPages {\n\n    /**\n     *\n     */\n    MAIN,\n    /**\n     * 位置类型，即为连接\n     */\n    UNKNOW,\n\n    /**\n     * 联网\n     */\n    CONNECT,\n\n    /**\n     * 联网\n     */\n    CONNECT_HISTORY,\n\n    /**\n     * 主机\n     */\n    HOST,\n\n    /**\n     * 从机\n     */\n    CLIENT,\n\n    /**\n     * 从机链接历史\n     */\n    CLIENT_HISTORY,\n\n    /**\n     *数据抓取中...\n     */\n    RECORDING,\n\n    /**\n     * 查看用例列表\n     */\n    MC_CASELIST,\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/ui/adapter/McCaseInfoDialogProvider.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.ui.adapter\n\nimport android.view.View\nimport android.widget.EditText\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.kit.test.mock.data.CaseInfo\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.widget.dialog.DialogListener\nimport com.didichuxing.doraemonkit.widget.dialog.DialogProvider\n\n/**\n * Created by jint on 2019/4/12\n * 完善健康体检用户信息dialog\n * @author jintai\n */\nclass McCaseInfoDialogProvider internal constructor(data: Any?, listener: DialogListener?) :\n    DialogProvider<Any?>(data, listener) {\n    private lateinit var mPositive: TextView\n    private lateinit var mNegative: TextView\n    private lateinit var mCaseName: EditText\n    private lateinit var mPersonName: EditText\n    override fun getLayoutId(): Int {\n        return R.layout.dk_dialog_mc_case_info\n    }\n\n    override fun findViews(view: View) {\n        mPositive = view.findViewById(R.id.positive)\n        mNegative = view.findViewById(R.id.negative)\n        mCaseName = view.findViewById(R.id.edit_case_name)\n        mPersonName = view.findViewById(R.id.edit_user_name)\n    }\n\n\n    override fun getPositiveView(): View {\n        return mPositive\n    }\n\n    override fun getNegativeView(): View {\n        return mNegative\n    }\n\n    override fun getCancelView(): View? {\n        return null\n    }\n\n    fun getCaseInfo(): CaseInfo {\n        return CaseInfo(\n            caseName = mCaseName.text.toString(),\n            personName = mPersonName.text.toString()\n        )\n    }\n\n    override fun isCancellable(): Boolean {\n        return false\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/ui/adapter/McCaseListAdapter.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.ui.adapter\n\nimport android.widget.RadioButton\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/7/1-15:53\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass McCaseListAdapter(caseList: MutableList<McCaseInfo>) :\n    BaseQuickAdapter<McCaseInfo, BaseViewHolder>(R.layout.dk_item_mc_case, caseList) {\n\n    override fun convert(holder: BaseViewHolder, item: McCaseInfo) {\n        holder.getView<TextView>(R.id.tv_name).text = \"用例名称:${item.caseName}\"\n        holder.getView<TextView>(R.id.tv_person).text = \"采集人:${item.personName}\"\n        holder.getView<TextView>(R.id.tv_caseid).text = \"caseId:${item.caseId}\"\n        holder.getView<TextView>(R.id.tv_time).text = \"采集时间:${item.time}\"\n        holder.getView<RadioButton>(R.id.rb).isChecked = item.isChecked\n    }\n}\n\n\ndata class McCaseInfo(\n    val caseName: String,\n    val personName: String,\n    val time: String,\n    var isChecked: Boolean,\n    val caseId: String\n)\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/ui/adapter/McClientHistoryAdapter.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.ui.adapter\n\nimport android.view.View\nimport android.widget.TextView\nimport androidx.core.view.isGone\nimport com.didichuxing.doraemonkit.mc.R\n\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\n\n\n/**\n * didi Create on 2022/1/18 .\n *\n * Copyright (c) 2022/1/18 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/1/18 8:12 下午\n * @Description 用一句话说明文件功能\n */\n\nclass McClientHistoryAdapter(clientList: MutableList<McClientHistory>, callback: (client: McClientHistory) -> Unit) :\n    BaseQuickAdapter<McClientHistory, BaseViewHolder>(R.layout.dk_item_mc_client, clientList) {\n\n    val callback2 = callback\n    override fun convert(holder: BaseViewHolder, item: McClientHistory) {\n        holder.getView<TextView>(R.id.tv_name).text = \"主机名称:${item.name}\"\n        holder.getView<TextView>(R.id.tv_address).text = \"地址:ws://${item.host}:${item.port}${item.path}\"\n        holder.getView<TextView>(R.id.tv_time).text = \"时间:${item.time}\"\n        holder.getView<TextView>(R.id.connect).setOnClickListener {\n            callback2(item)\n        }\n        holder.getView<View>(R.id.state_dot).isGone = !item.enable\n        holder.getView<TextView>(R.id.connect).isGone = item.enable\n    }\n}\n\ndata class McClientHistory(\n    val host: String,\n    val port: Int,\n    val path: String,\n    val name: String,\n    val time: String,\n    val url: String = \"\",\n    var enable: Boolean = false,\n    var connectSerial: String = \"\"\n)\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/ui/connect/MultiControlAllFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.ui.connect\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.view.View\nimport android.widget.Button\nimport android.widget.CompoundButton\nimport android.widget.Switch\nimport androidx.appcompat.app.AlertDialog\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.extension.isTrueWithCor\nimport com.didichuxing.doraemonkit.kit.connect.ConnectAddress\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\nimport com.didichuxing.doraemonkit.kit.mc.MultiControlConfig\nimport com.didichuxing.doraemonkit.kit.mc.MultiControlManager\nimport com.didichuxing.doraemonkit.kit.mc.OnMultiControlModeChangeListener\nimport com.didichuxing.doraemonkit.kit.mc.utils.ConnectHistoryUtils\nimport com.didichuxing.doraemonkit.kit.mc.ui.*\nimport com.didichuxing.doraemonkit.kit.mc.ui.adapter.McClientHistory\nimport com.didichuxing.doraemonkit.kit.mc.ui.adapter.McClientHistoryAdapter\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.util.TimeUtils\nimport com.didichuxing.doraemonkit.util.ToastUtils\nimport com.didichuxing.doraemonkit.widget.recyclerview.DividerItemDecoration\nimport com.didichuxing.doraemonkit.zxing.activity.CaptureActivity\nimport kotlinx.coroutines.launch\nimport java.util.*\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/10-10:52\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass MultiControlAllFragment : BaseFragment() {\n\n    companion object {\n        private const val REQUEST_CODE_SCAN = 0x108\n    }\n\n    private lateinit var switch: Switch\n    private lateinit var mRv: RecyclerView\n    private lateinit var mAdapter: McClientHistoryAdapter\n    private lateinit var histories: MutableList<McClientHistory>\n\n\n    private val checkedChangeListener = CompoundButton.OnCheckedChangeListener { _, isChecked ->\n        if (isChecked) {\n            MultiControlManager.changeMode(TestMode.HOST)\n        } else {\n            MultiControlManager.changeMode(TestMode.CLIENT)\n        }\n    }\n\n    private val modeChangeListener = object : OnMultiControlModeChangeListener {\n        override fun onMultiControlModeChanged(testMode: TestMode) {\n            if (testMode == TestMode.HOST) {\n                changeSwitchChecked(true)\n            } else {\n                changeSwitchChecked(false)\n            }\n        }\n    }\n\n    private fun changeSwitchChecked(isChecked: Boolean) {\n        switch.setOnCheckedChangeListener(null)\n        switch.isChecked = isChecked\n        switch.setOnCheckedChangeListener(checkedChangeListener)\n    }\n\n\n    override fun onRequestLayout(): Int {\n        return R.layout.dk_fragment_mc_connect_history\n    }\n\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        switch = findViewById(R.id.dokit_mode_switch_btn)\n\n        switch.isChecked = MultiControlManager.getMode() == TestMode.HOST\n        switch.setOnCheckedChangeListener(checkedChangeListener)\n\n        val add: Button = findViewById(R.id.add)\n        add.setOnClickListener {\n            startScan()\n        }\n\n        mRv = findViewById(R.id.rv)\n\n        mAdapter = McClientHistoryAdapter(mutableListOf<McClientHistory>()) { client ->\n            handleConnect(client)\n        }\n\n        mAdapter.setOnItemClickListener { adapter, view, pos ->\n            val data = histories[pos]\n            if (data.enable) {\n                lifecycleScope.launch {\n                    privacyInterceptDialog(\"提示\", \"是否断开与当前主机链接\").isTrueWithCor {\n                        MultiControlConfig.currentConnectHistory = null\n                        MultiControlManager.closeWorkMode()\n                        ToastUtils.showShort(\"已断开链接\")\n                        updateHistoryView()\n                    }\n                }\n            } else {\n                ToastUtils.showShort(\"该主机没有建立链接\")\n            }\n        }\n        mAdapter.setOnItemLongClickListener { adapter, view, pos ->\n            val data = histories[pos]\n            lifecycleScope.launch {\n                privacyInterceptDialog(\"提示\", \"是否删除连接历史记录\").isTrueWithCor {\n                    ConnectHistoryUtils.removeClientHistory(data)\n                    ToastUtils.showShort(\"已删除记录\")\n                    updateHistoryView()\n                }\n            }\n            return@setOnItemLongClickListener false\n        }\n\n        mRv.apply {\n            adapter = mAdapter\n            layoutManager = LinearLayoutManager(requireActivity())\n            val decoration = DividerItemDecoration(DividerItemDecoration.VERTICAL)\n            decoration.setDrawable(resources.getDrawable(R.drawable.dk_divider))\n            addItemDecoration(decoration)\n        }\n\n        updateHistoryView()\n\n        MultiControlManager.addOnMultiControlModeChangeListener(modeChangeListener)\n\n    }\n\n\n    /**\n     * 处理dialog返回值\n     */\n    private suspend fun privacyInterceptDialog(title: String, content: String): Boolean =\n        suspendCoroutine {\n            AlertDialog.Builder(requireActivity())\n                .setTitle(title)\n                .setMessage(content)\n                .setCancelable(false)\n                .setPositiveButton(\"确认\") { dialog, _ ->\n                    dialog.dismiss()\n                    it.resume(true)\n                }\n                .setNegativeButton(\"取消\") { dialog, _ ->\n                    dialog.dismiss()\n                    it.resume(false)\n                }\n                .show()\n\n        }\n\n\n    override fun onResume() {\n        super.onResume()\n        updateHistoryView()\n    }\n\n\n    override fun onStart() {\n        super.onStart()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        MultiControlManager.removeOnMultiControlModeChangeListener(modeChangeListener)\n    }\n\n    private fun updateHistoryView() {\n        lifecycleScope.launch {\n            val clients = ConnectHistoryUtils.loadClientHistory()\n            val current = MultiControlConfig.currentConnectHistory\n            for (history in clients) {\n                if (current != null) {\n                    history.enable = TextUtils.equals(history.url, current.url)\n                } else {\n                    history.enable = false\n                }\n            }\n\n            histories = clients\n            clients?.let {\n                mAdapter.setList(clients)\n            }\n        }\n    }\n\n\n    /**\n     * 开始扫描\n     */\n    private fun startScan() {\n        val intent = Intent(activity, DoKitMcScanActivity::class.java)\n        startActivityForResult(intent, REQUEST_CODE_SCAN)\n    }\n\n\n    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {\n        super.onActivityResult(requestCode, resultCode, data)\n        if (requestCode == REQUEST_CODE_SCAN && resultCode == Activity.RESULT_OK) {\n            if (data != null && data.hasExtra(CaptureActivity.INTENT_EXTRA_KEY_QR_SCAN)) {\n                val code = data.getStringExtra(CaptureActivity.INTENT_EXTRA_KEY_QR_SCAN)\n                if (!TextUtils.isEmpty(code)) {\n                    try {\n                        val uri = Uri.parse(code)\n                        uri?.let {\n                            val name = uri.host.toString()\n                            val time = TimeUtils.date2String(Date())\n                            val url = code\n                            val history = McClientHistory(uri.host!!, uri.port, uri.path!!, name, time, url!!)\n                            ConnectHistoryUtils.saveClientHistory(history)\n                            handleConnect(history)\n                        }\n\n                    } catch (e: Exception) {\n                        e.printStackTrace()\n                    } finally {\n                    }\n                } else {\n                    handleNoResult()\n                }\n            } else {\n                handleNoResult()\n            }\n        } else {\n            handleNoResult()\n        }\n    }\n\n    /**\n     * 没有返回结果\n     */\n    private fun handleNoResult() {\n        ToastUtils.showShort(\"没有扫描到任何内容 >_< .\")\n    }\n\n    private fun handleConnect(clientHistory: McClientHistory) {\n\n        MultiControlConfig.currentConnectHistory = clientHistory\n        updateHistoryView()\n\n        val address = ConnectAddress(\n            clientHistory.name,\n            clientHistory.url,\n            clientHistory.time\n        )\n        MultiControlManager.startClientMode(address)\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/ui/connect/MultiControlDoKitView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.ui.connect\n\nimport android.content.Context\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.kit.core.AbsDoKitView\nimport com.didichuxing.doraemonkit.kit.core.DoKitViewLayoutParams\nimport com.didichuxing.doraemonkit.kit.mc.MultiControlManager\nimport com.didichuxing.doraemonkit.kit.mc.ui.McPages\nimport com.didichuxing.doraemonkit.kit.mc.utils.McPageUtils\nimport com.didichuxing.doraemonkit.mc.R\nimport com.didichuxing.doraemonkit.util.ConvertUtils\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/17-14:37\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass MultiControlDoKitView : AbsDoKitView() {\n\n    companion object {\n        private val HOST = R.string.dk_kit_multi_control_host\n        private val CLIENT = R.string.dk_kit_multi_control_client\n        private val doKitViewSet: MutableSet<MultiControlDoKitView> = mutableSetOf()\n\n        fun updateConnectMode() {\n            doKitViewSet.forEach {\n                it.updateConnectMode()\n            }\n        }\n    }\n\n\n    private var textView: TextView? = null\n\n    override fun onCreate(context: Context?) {\n        doKitViewSet.add(this)\n    }\n\n    override fun onCreateView(context: Context?, rootView: FrameLayout?): View {\n        val view = LayoutInflater.from(context).inflate(R.layout.dk_dokitview_connect, rootView, false)\n        return view\n    }\n\n    override fun onViewCreated(rootView: FrameLayout?) {\n\n        textView = findViewById<TextView>(R.id.tv_name)\n        rootView?.setOnClickListener {\n            McPageUtils.startFragment(McPages.CONNECT_HISTORY)\n        }\n        updateConnectMode()\n    }\n\n    fun updateConnectMode() {\n        textView.let {\n            if (MultiControlManager.getMode() == TestMode.HOST) {\n                textView?.text = getString(HOST)\n            } else {\n                textView?.text = getString(CLIENT)\n            }\n        }\n    }\n\n    override fun initDokitViewLayoutParams(params: DoKitViewLayoutParams) {\n        params.width = ConvertUtils.dp2px(65.0f)\n        params.height = ConvertUtils.dp2px(65.0f)\n        params.gravity = Gravity.TOP or Gravity.LEFT\n        params.x = ConvertUtils.dp2px(280f)\n        params.y = ConvertUtils.dp2px(25f)\n    }\n\n    override fun onDestroy() {\n        doKitViewSet.remove(this)\n        super.onDestroy()\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/utils/ActivityStatusUtil.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.utils\n\nimport android.app.Activity\nimport android.view.View\nimport com.didichuxing.doraemonkit.extension.tagName\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/3-20:14\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject ActivityStatusUtil {\n    const val ACTIVITY_STATUS_UNKNOWN = -1\n    const val ACTIVITY_STATUS_ONCREATE = 0\n    const val ACTIVITY_STATUS_ONSTART = 1\n    const val ACTIVITY_STATUS_ONRESUME = 2\n    const val ACTIVITY_STATUS_ONPAUSE = 3\n    const val ACTIVITY_STATUS_ONSTOP = 4\n    const val ACTIVITY_STATUS_ONDESTROY = 5\n\n    val activityStatus: MutableMap<String, Int> by lazy {\n        mutableMapOf<String, Int>()\n    }\n\n\n    fun isActivityOnResume(view: View): Boolean {\n        if (view.context is Activity) {\n            val activity = view.context as Activity\n            val status = activityStatus[activity::class.tagName]\n            if (status == ACTIVITY_STATUS_ONRESUME) {\n                return true\n            }\n\n        }\n        return false\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/utils/ClientHistoryUtils.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.utils\n\nimport android.text.TextUtils\nimport com.didichuxing.doraemonkit.kit.mc.ui.adapter.McClientHistory\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport com.didichuxing.doraemonkit.util.SPUtils\n\n\n/**\n * didi Create on 2022/1/18 .\n *\n * Copyright (c) 2022/1/18 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/1/18 8:52 下午\n * @Description 用一句话说明文件功能\n */\n\nobject ClientHistoryUtils {\n\n    const val SP_FILE_NAME: String = \"mc_client_history\"\n    const val SP_DATA: String = \"data\"\n\n    val data: MutableList<McClientHistory> = mutableListOf()\n\n    fun loadClientHistory(): MutableList<McClientHistory> {\n        if (data.isEmpty()) {\n            data.addAll(loadClientHistoryAll())\n        }\n        return data.toMutableList()\n    }\n\n\n    fun saveClientHistory(clientHistory: McClientHistory) {\n        val list = mutableListOf<McClientHistory>()\n        list.addAll(ConnectHistoryUtils.data)\n        list.forEach {\n            if (!TextUtils.isEmpty(it.url) && TextUtils.equals(it.url, clientHistory.url)) {\n                ConnectHistoryUtils.data.remove(it)\n            }\n        }\n        data.add(clientHistory)\n        saveClientHistoryAll(data.toMutableList())\n    }\n\n    fun removeClientHistory(clientHistory: McClientHistory) {\n        data.remove(clientHistory)\n        saveClientHistoryAll(data.toMutableList())\n    }\n\n    fun saveClientHistoryAll(histories: MutableList<McClientHistory>) {\n        data.clear()\n        data.addAll(histories)\n\n        val data2 = GsonUtils.toJson(histories)\n        SPUtils.getInstance(SP_FILE_NAME).put(SP_DATA, data2)\n    }\n\n    private fun loadClientHistoryAll(): MutableList<McClientHistory> {\n        val data = SPUtils.getInstance(SP_FILE_NAME).getString(SP_DATA, \"\")\n        return if (data.isEmpty()) {\n            mutableListOf()\n        } else {\n            val type = GsonUtils.getListType(McClientHistory::class.java)\n            GsonUtils.fromJson(data, type)\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/utils/CodeUtils.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.utils\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.Color\nimport android.graphics.Matrix\nimport com.didichuxing.doraemonkit.kit.mc.utils.DensityUtils.dp2px\nimport com.google.zxing.BarcodeFormat\nimport com.google.zxing.EncodeHintType\nimport com.google.zxing.MultiFormatWriter\nimport com.google.zxing.WriterException\nimport com.google.zxing.qrcode.decoder.ErrorCorrectionLevel\nimport java.util.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/10-17:00\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject CodeUtils {\n    // 宽度值，影响中间图片大小\n    private const val IMAGE_HALFWIDTH = 80\n\n    /**\n     * 生成中间带图片的二维码\n     *\n     * @param context 上下文\n     * @param content 二维码内容\n     * @param logo    二维码中间的图片\n     * @return 生成的二维码图片\n     * @throws WriterException 生成二维码异常\n     */\n    @Throws(WriterException::class)\n    fun createCode(context: Context?, content: String?, logo: Bitmap): Bitmap {\n        var logo = logo\n        val m = Matrix()\n        val sx = 2.toFloat() * IMAGE_HALFWIDTH / logo.width\n        val sy = 2.toFloat() * IMAGE_HALFWIDTH / logo.height\n        // 设置缩放信息\n        m.setScale(sx, sy)\n        // 将logo图片按martix设置的信息缩放\n        logo = Bitmap.createBitmap(\n            logo, 0, 0, logo.width,\n            logo.height, m, false\n        )\n        val writer = MultiFormatWriter()\n        val hst: Hashtable<EncodeHintType?, Any?> = Hashtable()\n        // 设置字符编码\n        hst[EncodeHintType.CHARACTER_SET] = \"UTF-8\"\n        // 设置二维码容错率\n        hst[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.H\n        // 生成二维码矩阵信息\n        val matrix = writer.encode(\n            content, BarcodeFormat.QR_CODE,\n            dp2px(context!!, 300f),\n            dp2px(context, 300f), hst\n        )\n        // 矩阵高度\n        val width = matrix.width\n        // 矩阵宽度\n        val height = matrix.height\n        val halfW = width / 2\n        val halfH = height / 2\n        // 定义数组长度为矩阵高度*矩阵宽度，用于记录矩阵中像素信息\n        val pixels = IntArray(width * height)\n        // 从行开始迭代矩阵\n        for (y in 0 until height) {\n            // 迭代列\n            for (x in 0 until width) {\n                // 该位置用于存放图片信息\n                if (x > halfW - IMAGE_HALFWIDTH && x < halfW + IMAGE_HALFWIDTH && y > halfH - IMAGE_HALFWIDTH && y < halfH + IMAGE_HALFWIDTH) {\n                    // 记录图片每个像素信息\n                    pixels[y * width + x] = logo.getPixel(\n                        x - halfW\n                                + IMAGE_HALFWIDTH, y - halfH + IMAGE_HALFWIDTH\n                    )\n                } else {\n                    // 如果有黑块点，记录信息\n                    if (matrix[x, y]) {\n                        // 记录黑块信息\n                        pixels[y * width + x] = -0x1000000\n                    } else {\n                        pixels[y * width + x] = Color.WHITE\n                    }\n                }\n            }\n        }\n        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)\n        // 通过像素数组生成bitmap\n        bitmap.setPixels(pixels, 0, width, 0, 0, width, height)\n        return bitmap\n    }\n\n    /**\n     * 生成用户的二维码\n     *\n     * @param context 上下文\n     * @param content 二维码内容\n     * @return 生成的二维码图片\n     * @throws WriterException 生成二维码异常\n     */\n    @Throws(WriterException::class)\n    fun createCode(context: Context?, content: String?): Bitmap {\n        //生成二维矩阵,编码时指定大小,不要生成了图片以后再进行缩放,这样会模糊导致识别失败\n        val matrix = MultiFormatWriter().encode(\n            content, BarcodeFormat.QR_CODE,\n            dp2px(context!!, 250f),\n            dp2px(context, 250f)\n        )\n        val width = matrix.width\n        val height = matrix.height\n        //二维矩阵转为一维像素数组,也就是一直横着排了\n        val pixels = IntArray(width * height)\n        for (y in 0 until height) {\n            for (x in 0 until width) {\n                if (matrix[x, y]) {\n                    pixels[y * width + x] = -0x1000000\n                } else {\n                    pixels[y * width + x] = Color.WHITE\n                }\n            }\n        }\n        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)\n        //通过像素数组生成bitmap\n        bitmap.setPixels(pixels, 0, width, 0, 0, width, height)\n        return bitmap\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/utils/ConnectHistoryUtils.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.utils\n\nimport android.text.TextUtils\nimport com.didichuxing.doraemonkit.kit.mc.ui.adapter.McClientHistory\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport com.didichuxing.doraemonkit.util.SPUtils\n\n\n/**\n * didi Create on 2022/1/18 .\n *\n * Copyright (c) 2022/1/18 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/1/18 8:52 下午\n * @Description 用一句话说明文件功能\n */\n\nobject ConnectHistoryUtils {\n\n    const val SP_FILE_NAME: String = \"mc_connect_history\"\n    const val SP_DATA: String = \"data\"\n\n    val data: MutableList<McClientHistory> = mutableListOf()\n\n    fun loadClientHistory(): MutableList<McClientHistory> {\n        if (data.isEmpty()) {\n            data.addAll(loadClientHistoryAll())\n        }\n        return data.toMutableList()\n    }\n\n\n    fun saveClientHistory(clientHistory: McClientHistory) {\n        val list = mutableListOf<McClientHistory>()\n        list.addAll(data)\n        list.forEach {\n            if (!TextUtils.isEmpty(it.url) && TextUtils.equals(it.url, clientHistory.url)) {\n                data.remove(it)\n            }\n        }\n        data.add(clientHistory)\n        saveClientHistoryAll(data.toMutableList())\n    }\n\n    fun removeClientHistory(clientHistory: McClientHistory) {\n        data.remove(clientHistory)\n        saveClientHistoryAll(data.toMutableList())\n    }\n\n    fun saveClientHistoryAll(histories: MutableList<McClientHistory>) {\n        data.clear()\n        data.addAll(histories)\n\n        val data2 = GsonUtils.toJson(histories)\n        SPUtils.getInstance(SP_FILE_NAME).put(SP_DATA, data2)\n    }\n\n\n    private fun loadClientHistoryAll(): MutableList<McClientHistory> {\n        val data = SPUtils.getInstance(SP_FILE_NAME).getString(SP_DATA, \"\")\n        return if (data.isEmpty()) {\n            mutableListOf()\n        } else {\n            val type = GsonUtils.getListType(McClientHistory::class.java)\n            GsonUtils.fromJson(data, type)\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/utils/DensityUtils.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.utils\n\nimport android.content.Context\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/10-17:00\n * 描    述：\n * 修订历史：\n * ================================================\n */\n/**\n * px与dp互相转换\n * Created by yangle on 2016/4/12.\n */\nobject DensityUtils {\n    fun dp2px(context: Context, dp: Float): Int {\n        //获取设备密度\n        val density = context.resources.displayMetrics.density\n        //4.3, 4.9, 加0.5是为了四舍五入\n        return (dp * density + 0.5f).toInt()\n    }\n\n    fun px2dp(context: Context, px: Int): Float {\n        //获取设备密度\n        val density = context.resources.displayMetrics.density\n        return px / density\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/utils/McCaseUtils.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.utils\n\nimport com.didichuxing.doraemonkit.kit.mc.oldui.DoKitMcManager\nimport com.didichuxing.doraemonkit.util.SPUtils\n\n\n/**\n * didi Create on 2022/1/18 .\n *\n * Copyright (c) 2022/1/18 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/1/18 6:33 下午\n * @Description 用一句话说明文件功能\n */\n\nobject McCaseUtils {\n\n\n    fun saveCaseId(caseId: String) {\n        DoKitMcManager.MC_CASE_ID = caseId\n        SPUtils.getInstance().put(DoKitMcManager.MC_CASE_ID_KEY, caseId)\n    }\n\n\n    fun loadCaseId(): String = when {\n        DoKitMcManager.MC_CASE_ID.isEmpty() -> {\n            val caseId = SPUtils.getInstance().getString(DoKitMcManager.MC_CASE_ID_KEY, \"\")\n            DoKitMcManager.MC_CASE_ID = caseId\n            DoKitMcManager.MC_CASE_ID\n        }\n        else -> {\n            DoKitMcManager.MC_CASE_ID\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/utils/McPageUtils.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.utils\n\nimport android.content.Intent\nimport com.didichuxing.doraemonkit.kit.mc.ui.DoKitMcActivity\nimport com.didichuxing.doraemonkit.kit.mc.ui.McPages\nimport com.didichuxing.doraemonkit.util.ActivityUtils\n\nobject McPageUtils {\n\n    fun startFragment(wsMode: McPages) {\n        val activity = ActivityUtils.getTopActivity()\n\n        if (activity is DoKitMcActivity){\n            activity.changeFragment(wsMode)\n            return\n        }\n        val intent = Intent(activity, DoKitMcActivity::class.java)\n        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK\n        intent.putExtra(\"WS_MODE_ORDINAL\", wsMode.name)\n        activity.startActivity(intent)\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/java/com/didichuxing/doraemonkit/kit/mc/utils/WSPackageUtils.kt",
    "content": "package com.didichuxing.doraemonkit.kit.mc.utils\n\nimport com.didichuxing.doraemonkit.kit.connect.DoKitConnectManager\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\nimport com.didichuxing.doraemonkit.kit.connect.data.PackageType\nimport com.didichuxing.doraemonkit.kit.connect.data.TextPackage\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport com.didichuxing.doraemonkit.util.RandomUtils\n\n/**\n * 通信数据包处理\n */\nobject WSPackageUtils {\n\n    fun toPackageJson(event: ControlEvent): String {\n        return toPackageJson(RandomUtils.random32HexString(), PackageType.BROADCAST, event)\n    }\n\n    fun toPackageJson(id: String, type: PackageType, event: ControlEvent): String {\n        val wsPackage = TextPackage(\n            pid = id, type = type, data = GsonUtils.toJson(event),\n            contentType = \"action\",\n            connectSerial = DoKitConnectManager.getConnectSerial()\n        )\n        return GsonUtils.toJson(wsPackage)\n    }\n\n    fun toPackageJson(type: PackageType, data: String): String {\n        val wsPackage = TextPackage(\n            pid = RandomUtils.random32HexString(),\n            type = type,\n            data = data,\n            connectSerial = DoKitConnectManager.getConnectSerial()\n        )\n        return GsonUtils.toJson(wsPackage)\n    }\n\n    fun toModePackageJson(type: PackageType, contentType: String): String {\n        val wsPackage = TextPackage(\n            pid = RandomUtils.random32HexString(),\n            type = type,\n            data = \"\",\n            contentType = contentType,\n            connectSerial = DoKitConnectManager.getConnectSerial()\n        )\n        return GsonUtils.toJson(wsPackage)\n    }\n\n    fun jsonToPackage(data: String): TextPackage {\n        val wsPackage = GsonUtils.fromJson<TextPackage>(data, TextPackage::class.java)\n        return wsPackage\n    }\n\n    fun jsonToEvent(data: String): ControlEvent {\n        val wsPackage = jsonToPackage(data)\n        val wsEvent = GsonUtils.fromJson<ControlEvent>(wsPackage.data, ControlEvent::class.java)\n        return wsEvent\n    }\n}\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/drawable/dk_btn_mc_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"rectangle\">\n\n    <corners\n        android:radius=\"3dp\"/>\n    <solid android:color=\"@color/dk_color_337CC4\"/>\n</shape>"
  },
  {
    "path": "Android/dokit-mc/src/main/res/drawable/dk_dokitview_mc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <corners android:radius=\"35dp\" />\n    <solid android:color=\"#3CBCA3\" />\n\n</shape>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/drawable/dk_dokitview_mc_connect.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <corners android:radius=\"7dp\" />\n    <solid android:color=\"@color/dk_color_48BB31\" />\n\n</shape>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/drawable/dk_dokitview_mc_recoding.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <corners android:radius=\"7dp\" />\n    <solid android:color=\"@color/background_error\" />\n\n</shape>"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_activity_mc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"@string/dk_kit_multi_control\" />\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0.5dp\"\n        android:background=\"@color/dk_color_E5E5E5\" />\n\n    <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/fragment_container_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_dialog_mc_case_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <LinearLayout\n        android:layout_width=\"280dp\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/dk_color_FFFFFF\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:layout_marginTop=\"30dp\"\n            android:text=\"@string/dk_health_dialog_title\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"21sp\"\n            android:textStyle=\"bold\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"16dp\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"20dp\"\n            android:paddingRight=\"20dp\">\n\n            <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"用例名称\"\n                android:textColor=\"@color/dk_color_BEBEBE\"\n                android:textSize=\"14sp\" />\n\n            <EditText\n                android:id=\"@+id/edit_case_name\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"@dimen/dk_dp_40\"\n                android:layout_marginTop=\"@dimen/dk_dp_5\"\n                android:background=\"@drawable/dk_health_edittext_style\"\n                android:hint=\"请输入用例名称\"\n                android:paddingLeft=\"6dp\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"14sp\" />\n\n\n            <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/dk_dp_16\"\n                android:text=\"用例采集人\"\n                android:textColor=\"@color/dk_color_BEBEBE\"\n                android:textSize=\"14sp\" />\n\n            <EditText\n                android:id=\"@+id/edit_user_name\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"@dimen/dk_dp_40\"\n                android:layout_marginTop=\"@dimen/dk_dp_5\"\n                android:background=\"@drawable/dk_health_edittext_style\"\n                android:hint=\"请输入用例采集人\"\n                android:paddingLeft=\"6dp\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"14sp\" />\n        </LinearLayout>\n\n        <View\n            style=\"@style/DK.Divider\"\n            android:layout_marginTop=\"20dp\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"60dp\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/negative\"\n                style=\"@style/DK.Text\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_weight=\"1\"\n                android:background=\"@drawable/dk_dialog_button_background\"\n                android:text=\"@string/dk_cancel\" />\n\n            <View style=\"@style/DK.Divider.Vertical\" />\n\n\n            <TextView\n                android:id=\"@+id/positive\"\n                style=\"@style/DK.Text.Blue\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_weight=\"1\"\n                android:background=\"@drawable/dk_dialog_button_background\"\n                android:text=\"@string/dk_post\" />\n\n        </LinearLayout>\n\n    </LinearLayout>\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_dokitview_client.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"70dp\"\n    android:layout_height=\"70dp\"\n    android:background=\"@drawable/dk_dokitview_mc\"\n    android:orientation=\"vertical\">\n\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:text=\"从机\"\n        android:textColor=\"@color/dk_color_FFFFFF\"\n        android:textSize=\"18sp\"\n        android:textStyle=\"bold\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_dokitview_connect.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"65dp\"\n    android:layout_height=\"65dp\"\n    android:orientation=\"vertical\">\n\n    <androidx.cardview.widget.CardView\n        android:layout_width=\"60dp\"\n        android:layout_height=\"60dp\"\n        android:layout_centerInParent=\"true\"\n        app:cardBackgroundColor=\"#ff3CBCA3\"\n        app:cardCornerRadius=\"@dimen/dk_dp_30\"\n        app:cardElevation=\"2dp\"\n        app:cardPreventCornerOverlap=\"true\">\n\n        <TextView\n            android:id=\"@+id/tv_name\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerInParent=\"true\"\n            android:layout_gravity=\"center\"\n            android:text=\"从机\"\n            android:textColor=\"@color/dk_color_FFFFFF\"\n            android:textSize=\"18sp\"\n            android:textStyle=\"bold\" />\n\n    </androidx.cardview.widget.CardView>\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_dokitview_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_aa000000\">\n\n\n    <LinearLayout\n        android:layout_width=\"280dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:background=\"@color/dk_color_FFFFFF\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:layout_marginTop=\"20dp\"\n            android:text=\"一机多控同步异常\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"21sp\"\n            android:textStyle=\"bold\" />\n\n        <View\n            style=\"@style/DK.Divider\"\n            android:layout_marginTop=\"20dp\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"16dp\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"20dp\"\n            android:paddingRight=\"20dp\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"异常类型\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"16sp\" />\n\n            <TextView\n                android:id=\"@+id/exception_type\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/dk_dp_5\"\n                android:maxLines=\"2\"\n                android:text=\"主从机页面不一致\"\n                android:textColor=\"@color/dk_color_FF0006\"\n                android:textSize=\"14sp\" />\n\n\n            <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/dk_dp_16\"\n                android:text=\"备注：详细信息请在dokit.cn平台一机多控模块查看\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"14sp\" />\n\n\n        </LinearLayout>\n\n        <View\n            style=\"@style/DK.Divider\"\n            android:layout_marginTop=\"20dp\" />\n\n\n        <TextView\n            android:id=\"@+id/tv_ok\"\n            style=\"@style/DK.Text.Blue\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@drawable/dk_dialog_button_background\"\n            android:gravity=\"center_horizontal\"\n            android:padding=\"10dp\"\n            android:text=\"确认\"\n            android:textColor=\"@color/dk_color_55A8FD\"\n            android:textSize=\"20sp\" />\n\n    </LinearLayout>\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_dokitview_host.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"70dp\"\n    android:layout_height=\"70dp\"\n    android:background=\"@drawable/dk_dokitview_mc\"\n    android:orientation=\"vertical\">\n\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:text=\"主机\"\n        android:textColor=\"@color/dk_color_FFFFFF\"\n        android:textSize=\"18sp\"\n        android:textStyle=\"bold\" />\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_dokitview_recording.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/dk_info_background\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:padding=\"10dp\">\n\n\n    <View\n        android:id=\"@+id/red_dot\"\n        android:layout_width=\"14dp\"\n        android:layout_height=\"14dp\"\n        android:background=\"@drawable/dk_dokitview_mc_recoding\" />\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"5dp\"\n        android:text=\"Mock数据抓取中\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"14sp\"\n        android:textStyle=\"bold\" />\n\n    <TextView\n        android:id=\"@+id/tv_extend\"\n        android:layout_width=\"20dp\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"14sp\"\n        android:textStyle=\"bold\"\n        tools:text=\"...\" />\n\n    <ImageView\n        android:id=\"@+id/iv_close\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"@dimen/dk_dp_10\"\n        android:src=\"@mipmap/dk_close_icon\" />\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_fragment_mc_client.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/tv_host_info\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"主机信息\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"16sp\"\n            android:textStyle=\"bold\" />\n\n        <Button\n            android:id=\"@+id/btn_close\"\n            android:layout_width=\"150dp\"\n            android:layout_height=\"50dp\"\n            android:layout_marginTop=\"25dp\"\n            android:background=\"@drawable/dk_btn_mc_bg\"\n            android:text=\"断开\"\n            android:textAllCaps=\"false\"\n            android:textColor=\"@color/dk_color_FFFFFF\"\n            android:textSize=\"18sp\"\n            android:textStyle=\"bold\" />\n\n        <Button\n            android:id=\"@+id/btn_history\"\n            android:layout_width=\"150dp\"\n            android:layout_height=\"50dp\"\n            android:layout_marginTop=\"25dp\"\n            android:background=\"@drawable/dk_btn_mc_bg\"\n            android:text=\"连接历史\"\n            android:textAllCaps=\"false\"\n            android:textColor=\"@color/dk_color_FFFFFF\"\n            android:textSize=\"18sp\"\n            android:textStyle=\"bold\" />\n    </LinearLayout>\n\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_fragment_mc_client_history.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/rv\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_marginTop=\"15dp\"\n            android:layout_weight=\"100\" />\n\n\n        <Button\n            android:id=\"@+id/add\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"@dimen/dk_dp_48\"\n            android:layout_gravity=\"center\"\n            android:layout_marginTop=\"2dp\"\n            android:background=\"@color/dk_color_337CC4\"\n            android:text=\"添加\"\n            android:textColor=\"@color/dk_color_FFFFFF\"\n            android:textSize=\"24sp\"\n            android:visibility=\"visible\" />\n\n\n    </LinearLayout>\n\n</FrameLayout>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_fragment_mc_connect.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/tv_host_info\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"主机信息\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"16sp\"\n            android:textStyle=\"bold\" />\n\n        <TextView\n            android:id=\"@+id/info\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"20dp\"\n            android:text=\"ws://10.10.0.66:9000/mc\"\n            android:textColor=\"@color/dk_color_666666\"\n            android:textSize=\"14sp\"\n            android:textStyle=\"bold\" />\n\n        <TextView\n            android:id=\"@+id/state\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"20dp\"\n            android:text=\"正常\"\n            android:textColor=\"@color/dk_color_79DE79\"\n            android:textSize=\"12sp\"\n            android:textStyle=\"bold\" />\n\n        <Button\n            android:id=\"@+id/btn_close\"\n            android:layout_width=\"150dp\"\n            android:layout_height=\"50dp\"\n            android:layout_marginTop=\"25dp\"\n            android:background=\"@drawable/dk_btn_mc_bg\"\n            android:text=\"断开\"\n            android:textAllCaps=\"false\"\n            android:textColor=\"@color/dk_color_FFFFFF\"\n            android:textSize=\"18sp\"\n            android:textStyle=\"bold\" />\n\n    </LinearLayout>\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_fragment_mc_connect_history.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"@dimen/dk_dp_16\"\n            android:layout_marginTop=\"@dimen/dk_dp_16\"\n            android:layout_marginRight=\"@dimen/dk_dp_16\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"主机模式\"\n                android:textStyle=\"bold\"\n                android:textColor=\"@color/dk_color_333333\"\n                android:textSize=\"@dimen/dk_dp_18\" />\n\n            <Switch\n                android:id=\"@+id/dokit_mode_switch_btn\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"@dimen/dk_dp_40\"\n                android:textOff=\"off\"\n                android:textOn=\"on\">\n\n            </Switch>\n\n        </LinearLayout>\n\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/rv\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_marginTop=\"15dp\"\n            android:layout_weight=\"100\" />\n\n\n        <Button\n            android:id=\"@+id/add\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"@dimen/dk_dp_48\"\n            android:layout_gravity=\"center\"\n            android:layout_marginTop=\"2dp\"\n            android:background=\"@color/dk_color_337CC4\"\n            android:text=\"添加\"\n            android:textColor=\"@color/dk_color_FFFFFF\"\n            android:textSize=\"24sp\"\n            android:visibility=\"visible\" />\n\n\n    </LinearLayout>\n\n</FrameLayout>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_fragment_mc_datas.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\">\n\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n    <TextView\n        android:id=\"@+id/tv_empty\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:text=\"暂无用例数据\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"20sp\"\n        android:visibility=\"gone\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_fragment_mc_host.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:id=\"@+id/iv_code\"\n            android:layout_width=\"300dp\"\n            android:layout_height=\"300dp\" />\n\n        <TextView\n            android:id=\"@+id/tv_host\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textColor=\"@color/dk_color_333333\"\n            android:textSize=\"18sp\"\n            android:textStyle=\"bold\"\n            tools:text=\"ws://192.168.0.1:88888\" />\n    </LinearLayout>\n\n    <Button\n        android:id=\"@+id/btn_close\"\n        android:layout_width=\"150dp\"\n        android:layout_height=\"50dp\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginBottom=\"15dp\"\n        android:background=\"@drawable/dk_btn_mc_bg\"\n        android:text=\"断开\"\n        android:textAllCaps=\"false\"\n        android:textColor=\"@color/dk_color_FFFFFF\"\n        android:textSize=\"18sp\"\n        android:textStyle=\"bold\" />\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_fragment_mc_select.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <ImageView\n        android:id=\"@+id/iv_banner\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:scaleType=\"centerCrop\"\n        android:src=\"@mipmap/dk_mc_banner\" />\n\n\n    <ScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_below=\"@id/iv_banner\"\n        android:scrollbars=\"vertical\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_horizontal\"\n            android:orientation=\"vertical\">\n\n\n\n            <Button\n                android:id=\"@+id/tv_host\"\n                android:layout_width=\"150dp\"\n                android:layout_height=\"50dp\"\n                android:layout_marginTop=\"15dp\"\n                android:background=\"@drawable/dk_btn_mc_bg\"\n                android:text=\"@string/dk_kit_multi_control_host\"\n                android:textAllCaps=\"false\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n            <Button\n                android:id=\"@+id/tv_client\"\n                android:layout_width=\"150dp\"\n                android:layout_height=\"50dp\"\n                android:layout_marginTop=\"15dp\"\n                android:background=\"@drawable/dk_btn_mc_bg\"\n                android:text=\"@string/dk_kit_multi_control_client\"\n                android:textAllCaps=\"false\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n            <Button\n                android:id=\"@+id/tv_record\"\n                android:layout_width=\"150dp\"\n                android:layout_height=\"50dp\"\n                android:layout_marginTop=\"15dp\"\n                android:background=\"@drawable/dk_btn_mc_bg\"\n                android:text=\"@string/dk_kit_multi_control_record\"\n                android:textAllCaps=\"false\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n            <Button\n                android:id=\"@+id/tv_upload\"\n                android:layout_width=\"150dp\"\n                android:layout_height=\"50dp\"\n                android:layout_marginTop=\"15dp\"\n                android:background=\"@drawable/dk_btn_mc_bg\"\n                android:text=\"@string/dk_kit_multi_control_upload\"\n                android:textAllCaps=\"false\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n            <Button\n                android:id=\"@+id/tv_datas\"\n                android:layout_width=\"150dp\"\n                android:layout_height=\"50dp\"\n                android:layout_marginTop=\"15dp\"\n                android:background=\"@drawable/dk_btn_mc_bg\"\n                android:text=\"@string/dk_kit_multi_control_list\"\n                android:textAllCaps=\"false\"\n                android:textColor=\"@color/dk_color_FFFFFF\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n\n        </LinearLayout>\n    </ScrollView>\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_item_mc_case.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\"\n    android:padding=\"@dimen/dk_dp_10\">\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@color/dk_color_CC3A4B\"\n        android:textSize=\"16sp\"\n        android:textStyle=\"bold\"\n        tools:text=\"用例名\" />\n\n\n    <TextView\n        android:id=\"@+id/tv_caseid\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/tv_name\"\n        android:layout_marginTop=\"@dimen/dk_dp_10\"\n        android:textColor=\"@color/dk_color_CC3A4B\"\n        android:textStyle=\"bold\"\n        tools:text=\"caseId:\" />\n\n    <TextView\n        android:id=\"@+id/tv_person\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/tv_caseid\"\n        android:layout_marginTop=\"@dimen/dk_dp_10\"\n        android:textColor=\"@color/dk_color_666666\"\n        tools:text=\"采集人:\" />\n\n    <TextView\n        android:id=\"@+id/tv_time\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/tv_person\"\n        android:layout_marginTop=\"@dimen/dk_dp_10\"\n        android:textColor=\"@color/dk_color_666666\"\n        tools:text=\"采集时间:\" />\n\n    <RadioButton\n        android:id=\"@+id/rb\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:clickable=\"false\" />\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit-mc/src/main/res/layout/dk_item_mc_client.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\"\n    android:padding=\"@dimen/dk_dp_10\">\n\n    <View\n        android:id=\"@+id/state_dot\"\n        android:layout_width=\"14dp\"\n        android:layout_height=\"14dp\"\n        android:layout_alignBottom=\"@+id/tv_name\"\n        android:layout_marginBottom=\"4dp\"\n        android:layout_toRightOf=\"@+id/tv_name\"\n        android:background=\"@drawable/dk_dokitview_mc_connect\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\" />\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginRight=\"@dimen/dk_dp_10\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"16sp\"\n        android:textStyle=\"bold\"\n        tools:text=\"主机名称:10.10.0.33\" />\n\n\n    <TextView\n        android:id=\"@+id/tv_address\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/tv_name\"\n        android:layout_marginTop=\"@dimen/dk_dp_10\"\n        android:layout_marginRight=\"@dimen/dk_dp_98\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"12sp\"\n        android:textStyle=\"bold\"\n        tools:text=\"地址:2222222222222222222222222222222222222222222222222\" />\n\n\n    <TextView\n        android:id=\"@+id/tv_time\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/tv_address\"\n        android:layout_marginTop=\"@dimen/dk_dp_10\"\n        android:textColor=\"@color/dk_color_666666\"\n        android:textSize=\"12sp\"\n        tools:text=\"上次连接时间:\" />\n\n    <Button\n        android:id=\"@+id/connect\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:text=\"连接\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/values/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"dk_kit_multi_control\">一机多控</string>\n    <string name=\"dk_kit_multi_control_connect\">联网</string>\n    <string name=\"dk_kit_multi_control_host\">主机</string>\n    <string name=\"dk_kit_multi_control_client\">从机</string>\n    <string name=\"dk_kit_multi_control_record\">用例采集</string>\n    <string name=\"dk_kit_multi_control_upload\">用例上传</string>\n    <string name=\"dk_kit_multi_control_list\">用例列表</string>\n    <string name=\"dokit_access_desc\">DoKit一机多控</string>\n\n    <string name=\"dk_kit_multi_control_test\">一机多控 *</string>\n\n</resources>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/values-en-rCN/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"dk_kit_multi_control\">一机多控</string>\n    <string name=\"dk_kit_multi_control_connect\">联网</string>\n    <string name=\"dk_kit_multi_control_host\">主机</string>\n    <string name=\"dk_kit_multi_control_client\">从机</string>\n    <string name=\"dk_kit_multi_control_record\">用例采集</string>\n    <string name=\"dk_kit_multi_control_upload\">用例上传</string>\n    <string name=\"dk_kit_multi_control_list\">用例列表</string>\n    <string name=\"dokit_access_desc\">DoKit一机多控</string>\n\n    <string name=\"dk_kit_multi_control_test\">一机多控 *</string>\n\n</resources>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/values-zh-rTW/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"dk_kit_multi_control\">一机多控</string>\n    <string name=\"dk_kit_multi_control_connect\">联网</string>\n    <string name=\"dk_kit_multi_control_host\">主机</string>\n    <string name=\"dk_kit_multi_control_client\">从机</string>\n    <string name=\"dk_kit_multi_control_record\">用例采集</string>\n    <string name=\"dk_kit_multi_control_upload\">用例上传</string>\n    <string name=\"dk_kit_multi_control_list\">用例列表</string>\n    <string name=\"dokit_access_desc\">DoKit一机多控</string>\n\n    <string name=\"dk_kit_multi_control_test\">一机多控 *</string>\n\n</resources>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/values-zh-rUS/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"dk_kit_multi_control\">MultiControl</string>\n    <string name=\"dk_kit_multi_control_connect\">connect</string>\n    <string name=\"dk_kit_multi_control_host\">Host</string>\n    <string name=\"dk_kit_multi_control_client\">Client</string>\n    <string name=\"dk_kit_multi_control_record\">Case Record</string>\n    <string name=\"dk_kit_multi_control_upload\">Case Upload</string>\n    <string name=\"dk_kit_multi_control_list\">Case List</string>\n    <string name=\"dokit_access_desc\">DoKit Access</string>\n\n    <string name=\"dk_kit_multi_control_test\">MultiControl *</string>\n\n</resources>\n"
  },
  {
    "path": "Android/dokit-mc/src/main/res/xml/mc_accessibity_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<accessibility-service xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:accessibilityEventTypes=\"typeAllMask\"\n    android:accessibilityFeedbackType=\"feedbackAllMask\"\n    android:accessibilityFlags=\"flagReportViewIds\"\n    android:canPerformGestures=\"true\"\n    android:canRetrieveWindowContent=\"true\"\n    android:description=\"@string/dokit_access_desc\"\n    android:notificationTimeout=\"100\"\n    android:packageNames=\"com.didichuxing.doraemondemo.java\">\n\n</accessibility-service>\n\n"
  },
  {
    "path": "Android/dokit-mc/src/test/java/com/didichuxing/doraemonkit/ExampleUnitTest.kt",
    "content": "package com.didichuxing.doraemonkit\n\nimport com.didichuxing.doraemonkit.kit.test.mock.data.McCaseInfo\nimport com.didichuxing.doraemonkit.kit.test.mock.data.McResInfo\nimport com.google.gson.Gson\nimport org.json.JSONObject\nimport org.junit.Test\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see [Testing documentation](http://d.android.com/tools/testing)\n */\nclass ExampleUnitTest {\n    @Test\n    @Throws(Exception::class)\n    fun addition_isCorrect() {\n//        String json = \"{\\\"eventType\\\":\\\"WSE_ACCESS_EVENT\\\",\\\"from\\\":\\\"HOST\\\",\\\"msgMaps\\\":{\\\"activityName\\\":\\\"com.didichuxing.doraemondemo.mc.MCActivity\\\"},\\\"viewC12c\\\":{\\\"acc\\\":{\\\"mAction\\\":0,\\\"mContentChangeTypes\\\":0,\\\"mEventTime\\\":1163811924,\\\"mEventType\\\":1,\\\"mMovementGranularity\\\":0,\\\"mPackageName\\\":\\\"com.didichuxing.doraemondemo.java\\\",\\\"mRecords\\\":null,\\\"mWindowChangeTypes\\\":0,\\\"originStackTrace\\\":null,\\\"mAddedCount\\\":-1,\\\"mBeforeText\\\":null,\\\"mBooleanProperties\\\":514,\\\"mClassName\\\":\\\"android.widget.ImageView\\\",\\\"mConnectionId\\\":-1,\\\"mContentDescription\\\":null,\\\"mCurrentItemIndex\\\":-1,\\\"mFromIndex\\\":-1,\\\"mIsInPool\\\":false,\\\"mItemCount\\\":-1,\\\"mMaxScrollX\\\":-1,\\\"mMaxScrollY\\\":-1,\\\"mNext\\\":null,\\\"mParcelableData\\\":null,\\\"mRemovedCount\\\":-1,\\\"mScrollDeltaX\\\":-1,\\\"mScrollDeltaY\\\":-1,\\\"mScrollX\\\":-1,\\\"mScrollY\\\":-1,\\\"mSealed\\\":false,\\\"mSourceNodeId\\\":-4294966900,\\\"mSourceWindowId\\\":-1,\\\"mText\\\":[],\\\"mToIndex\\\":-1},\\\"childCount\\\":0,\\\"directParentId \\\":\\\"title_bar\\\",\\\"directParentViewClassName\\\":\\\"com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\\\",\\\"hasChild\\\":false,\\\"id\\\":\\\"icon\\\",\\\"imgWHRatio\\\":\\\"1.00\\\",\\\"indexOfDirectParent\\\":1,\\\"svgc12c\\\":{\\\"currentViewIndexOfSpecialViewGroup\\\":-1,\\\"specialViewGroupClassName\\\":\\\"\\\"},\\\"text\\\":\\\"\\\",\\\"viewClassName\\\":\\\"androidx.appcompat.widget.AppCompatImageView\\\",\\\"windowId\\\":\\\"android.view.ViewRootImpl$W@d37e0b3\\\"}}\";\n//        WSEvent wsEvent = GsonUtils.fromJson(json, WSEvent.class);\n//\n//\n//        System.out.println(wsEvent.getFrom());\n\n//        val list = listOf<Int>(1, 2, 3, 4, 5, 6, 7)\n//        list.forEach {\n//            if (it == 3) {\n//                return@forEach\n//            }\n//            println(it)\n//        }\n\n        val json = \"{\\n\" +\n                \"    \\\"code\\\":200,\\n\" +\n                \"    \\\"data\\\":{\\n\" +\n                \"        \\\"pId\\\":\\\"749a0600b5e48dd77cf8ee680be7b1b7\\\",\\n\" +\n                \"        \\\"appName\\\":\\\"DoKitDemo\\\",\\n\" +\n                \"        \\\"appVersion\\\":\\\"1.0.0\\\",\\n\" +\n                \"        \\\"dokitVersion\\\":\\\"3.4.0-alpha04\\\",\\n\" +\n                \"        \\\"phoneMode\\\":\\\"Pixel\\\",\\n\" +\n                \"        \\\"systemVersion\\\":\\\"9\\\",\\n\" +\n                \"        \\\"time\\\":\\\"2021-07-01 20:58:49\\\",\\n\" +\n                \"        \\\"caseId\\\":\\\"9f753c00ee37b5bbd259b52131d8d101\\\",\\n\" +\n                \"        \\\"createTime\\\":\\\"2021-07-01 20:58:46\\\",\\n\" +\n                \"        \\\"curStatus\\\":{\\n\" +\n                \"            \\\"status\\\":\\\"new\\\",\\n\" +\n                \"            \\\"id\\\":\\\"new\\\",\\n\" +\n                \"            \\\"date\\\":1625144326244\\n\" +\n                \"        },\\n\" +\n                \"        \\\"statusList\\\":[\\n\" +\n                \"            {\\n\" +\n                \"                \\\"status\\\":\\\"new\\\",\\n\" +\n                \"                \\\"id\\\":\\\"new\\\",\\n\" +\n                \"                \\\"date\\\":1625144326244\\n\" +\n                \"            }\\n\" +\n                \"        ],\\n\" +\n                \"        \\\"createDate\\\":1625144326244,\\n\" +\n                \"        \\\"_id\\\":\\\"60ddbc06cd1bac0128fbb736\\\"\\n\" +\n                \"    },\\n\" +\n                \"    \\\"msg\\\":\\\"\\\"\\n\" +\n                \"}\"\n\n\n        val info = convert2McResInfo<McCaseInfo>(JSONObject(json))\n        println(\"info===>${info.data}\")\n    }\n\n    private inline fun <reified T> convert2McResInfo(json: JSONObject): McResInfo<T> {\n\n        val mcInfo = McResInfo<T>(\n            json.getInt(\"code\"),\n            json.getString(\"msg\"),\n            T::class.java.newInstance()\n        )\n\n        val dataInfo = Gson().fromJson(json.getJSONObject(\"data\").toString(), T::class.java)\n\n        mcInfo.data = dataInfo\n        return mcInfo\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-mc/upload.sh",
    "content": "#!/usr/bin/env bash\necho -n  \"please enter bintray userid ->\"\nread  userid_bintray\necho -n  \"please enter bintray apikey ->\"\nread  apikey_bintray\n../gradlew wrapper  clean build --stacktrace  --info  bintrayUpload -PbintrayUser=${userid_bintray} -PbintrayKey=${apikey_bintray} -PdryRun=false"
  },
  {
    "path": "Android/dokit-no-op/.gitignore",
    "content": "/build"
  },
  {
    "path": "Android/dokit-no-op/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply from: '../upload.gradle'\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_16\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n}\n\n\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    //implementation rootProject.ext.dependencies[\"appcompat\"]\n    //compileOnly rootProject.ext.dependencies[\"okio\"]\n    if (needKotlinV14()) {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v14\"]\n    } else {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v13\"]\n    }\n    compileOnly rootProject.ext.dependencies[\"okhttp_v3\"]\n    //三大图片框架\n    compileOnly rootProject.ext.dependencies[\"glide\"]\n    compileOnly rootProject.ext.dependencies[\"picasso\"]\n    compileOnly rootProject.ext.dependencies[\"fresco\"]\n    compileOnly rootProject.ext.dependencies[\"hummer\"]\n}\nrepositories {\n    mavenCentral()\n}\n"
  },
  {
    "path": "Android/dokit-no-op/gradle.properties",
    "content": "ARTIFACT_ID=dokitx-no-op"
  },
  {
    "path": "Android/dokit-no-op/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original from file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "Android/dokit-no-op/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.didichuxing.doraemonkit\">\n\n</manifest>"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/DoKit.kt",
    "content": "package com.didichuxing.doraemonkit\n\nimport android.app.Activity\nimport android.app.Application\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.View\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.core.*\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitExtInterceptor\nimport com.didichuxing.doraemonkit.kit.performance.PerformanceValueListener\nimport com.didichuxing.doraemonkit.kit.webdoor.WebDoorManager\nimport java.lang.NullPointerException\nimport kotlin.reflect.KClass\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：4/7/21-16:00\n * 描    述：DoKit 入口类\n * 修订历史：\n * ================================================\n */\npublic class DoKit private constructor() {\n    companion object {\n        const val TAG = \"DoKit\"\n\n\n        /**\n         * 主icon是否处于显示状态\n         */\n        @JvmStatic\n        val isMainIconShow: Boolean\n            get() = false\n\n\n        /**\n         * 显示主icon\n         */\n        @JvmStatic\n        fun show() {\n        }\n\n        /**\n         * 直接显示工具面板页面\n         */\n        @JvmStatic\n        fun showToolPanel() {\n        }\n\n        /**\n         * 直接隐藏工具面板\n         */\n        @JvmStatic\n        fun hideToolPanel() {\n        }\n\n        /**\n         * 隐藏主icon\n         */\n        @JvmStatic\n        fun hide() {\n        }\n\n        /**\n         * 启动悬浮窗\n         * @JvmStatic:允许使用java的静态方法的方式调用\n         * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n         */\n        @JvmStatic\n        @JvmOverloads\n        fun launchFloating(\n            targetClass: Class<out AbsDokitView>,\n            mode: DoKitViewLaunchMode = DoKitViewLaunchMode.SINGLE_INSTANCE,\n            bundle: Bundle? = null\n        ) {\n        }\n\n\n        /**\n         * 启动悬浮窗\n         * @JvmStatic:允许使用java的静态方法的方式调用\n         * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n         */\n        @JvmStatic\n        @JvmOverloads\n        fun launchFloating(\n            targetClass: KClass<out AbsDokitView>,\n            mode: DoKitViewLaunchMode = DoKitViewLaunchMode.SINGLE_INSTANCE,\n            bundle: Bundle? = null\n        ) {\n        }\n\n        /**\n         * 移除悬浮窗\n         * @JvmStatic:允许使用java的静态方法的方式调用\n         * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n         */\n        @JvmStatic\n        fun removeFloating(targetClass: Class<out AbsDokitView>) {\n        }\n\n        /**\n         * 移除悬浮窗\n         * @JvmStatic:允许使用java的静态方法的方式调用\n         * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n         */\n        @JvmStatic\n        fun removeFloating(targetClass: KClass<out AbsDokitView>) {\n        }\n\n        /**\n         * 移除悬浮窗\n         * @JvmStatic:允许使用java的静态方法的方式调用\n         * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n         */\n        @JvmStatic\n        fun removeFloating(dokitView: AbsDokitView) {\n        }\n\n\n        /**\n         * 启动全屏页面\n         * @JvmStatic:允许使用java的静态方法的方式调用\n         * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n         */\n        @JvmStatic\n        @JvmOverloads\n        fun launchFullScreen(\n            targetClass: Class<out BaseFragment>,\n            context: Context? = null,\n            bundle: Bundle? = null,\n            isSystemFragment: Boolean = false\n        ) {\n        }\n\n        /**\n         * 启动全屏页面\n         * @JvmStatic:允许使用java的静态方法的方式调用\n         * @JvmOverloads :在有默认参数值的方法中使用@JvmOverloads注解，则Kotlin就会暴露多个重载方法。\n         */\n        @JvmStatic\n        @JvmOverloads\n        fun launchFullScreen(\n            targetClass: KClass<out BaseFragment>,\n            context: Context? = null,\n            bundle: Bundle? = null,\n            isSystemFragment: Boolean = false\n        ) {\n        }\n\n\n        @JvmStatic\n        fun <T : AbsDokitView> getDoKitView(\n            activity: Activity?,\n            clazz: Class<out T>\n        ): T? {\n            return null\n        }\n\n        @JvmStatic\n        fun <T : AbsDokitView> getDoKitView(\n            activity: Activity?,\n            clazz: KClass<out T>\n        ): T? {\n            return null\n        }\n\n        /**\n         * 发送自定义一机多控事件\n         */\n        @JvmStatic\n        fun sendCustomEvent(\n            eventType: String,\n            view: View? = null,\n            param: Map<String, String>? = null\n        ) {\n        }\n        /**\n         * 获取一机多控类型\n         */\n        @JvmStatic\n        fun mcMode(): String {\n            return \"\"\n        }\n    }\n\n\n    class Builder(private val app: Application) {\n        private var productId: String = \"\"\n        private var mapKits: LinkedHashMap<String, List<AbstractKit>> = linkedMapOf()\n        private var listKits: List<AbstractKit> = arrayListOf()\n\n        init {\n        }\n\n        fun productId(productId: String): Builder {\n            return this\n        }\n\n        /**\n         * mapKits & listKits 二选一\n         */\n        fun customKits(mapKits: LinkedHashMap<String, List<AbstractKit>>): Builder {\n            return this\n        }\n\n        /**\n         * mapKits & listKits 二选一\n         */\n        fun customKits(listKits: List<AbstractKit>): Builder {\n            return this\n        }\n\n        /**\n         * H5任意门全局回调\n         */\n        fun webDoorCallback(callback: WebDoorManager.WebDoorCallback): Builder {\n            return this\n        }\n\n        /**\n         * 禁用app信息上传开关，该上传信息只为做DoKit接入量的统计，如果用户需要保护app隐私，可调用该方法进行禁用\n         */\n        fun disableUpload(): Builder {\n            return this\n        }\n\n        fun debug(debug: Boolean): Builder {\n            return this\n        }\n\n        /**\n         * 是否显示主入口icon\n         */\n        fun alwaysShowMainIcon(alwaysShow: Boolean): Builder {\n            return this\n        }\n\n        /**\n         * 设置加密数据库密码\n         */\n        fun databasePass(map: Map<String, String>): Builder {\n            return this\n        }\n\n        /**\n         * 设置文件管理助手http端口号\n         */\n        fun fileManagerHttpPort(port: Int): Builder {\n            return this\n        }\n\n        /**\n         * 一机多控端口号\n         */\n        fun mcWSPort(port: Int): Builder {\n            return this\n        }\n\n        /**\n         * 一机多控自定义拦截器\n         */\n        fun mcClientProcess(interceptor: McClientProcessor): Builder {\n            return this\n        }\n\n        /**\n         *设置dokit的性能监控全局回调\n         */\n        fun callBack(callback: DoKitCallBack): Builder {\n            return this\n        }\n\n\n        /**\n         * 设置扩展网络拦截器的代理对象\n         */\n        fun netExtInterceptor(extInterceptorProxy: DokitExtInterceptor.DokitExtInterceptorProxy): Builder {\n            return this\n        }\n\n\n        fun build() {\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/DoKitCallBack.kt",
    "content": "package com.didichuxing.doraemonkit\n\nimport com.didichuxing.doraemonkit.kit.network.bean.NetworkRecord\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/5/19-11:00\n * 描    述：\n * 修订历史：\n * ================================================\n */\ninterface DoKitCallBack {\n\n    fun onCpuCallBack(value: Float, filePath: String) {\n\n    }\n\n    fun onFpsCallBack(value: Float, filePath: String) {\n\n    }\n\n    fun onMemoryCallBack(value: Float, filePath: String) {\n\n    }\n\n    fun onNetworkCallBack(record: NetworkRecord) {\n\n    }\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/DoKitMCHummerHelper.java",
    "content": "package com.didichuxing.doraemonkit;\n\nimport android.view.View;\n\nimport com.didi.hummer.context.HummerContext;\n\nimport java.util.Map;\n\n/**\n * Created by XiaoFeng on 2021/8/12.\n */\npublic class DoKitMCHummerHelper {\n    public static void registerHummerMCEventListener(HummerContext context) {\n\n    }\n\n    public static void processHummerMCClientEvent(HummerContext context, View view, String eventType, Map<String, String> params) {\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/DoraemonKit.kt",
    "content": "package com.didichuxing.doraemonkit\n\nimport android.app.Application\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.core.McClientProcessor\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.DokitExtInterceptor\nimport com.didichuxing.doraemonkit.kit.webdoor.WebDoorManager\n\n\n/**\n * Created by jint on 2018/6/22.\n */\nobject DoraemonKit {\n    @JvmStatic\n    fun install(app: Application) {\n    }\n\n    @JvmStatic\n    fun install(app: Application, productId: String) {\n    }\n\n    @JvmStatic\n    fun install(app: Application, mapKits: LinkedHashMap<String, MutableList<AbstractKit>>) {\n    }\n\n    @JvmStatic\n    fun install(\n        app: Application,\n        mapKits: LinkedHashMap<String, MutableList<AbstractKit>>,\n        productId: String\n    ) {\n    }\n\n    @JvmStatic\n    fun install(app: Application, listKits: MutableList<AbstractKit>) {\n    }\n\n    @JvmStatic\n    fun install(app: Application, listKits: MutableList<AbstractKit>, productId: String) {\n    }\n\n    /**\n     * @param app\n     * @param mapKits  自定义kits  根据用户传进来的分组 建议优先选择mapKits 两者都传的话会选择mapKits\n     * @param listKits  自定义kits 兼容原先老的api\n     * @param productId Dokit平台端申请的productId\n     */\n\n\n    @JvmStatic\n    fun setWebDoorCallback(callback: WebDoorManager.WebDoorCallback?) {\n    }\n\n    @JvmStatic\n    fun show() {\n    }\n\n    /**\n     * 直接显示工具面板页面\n     */\n    @JvmStatic\n    fun showToolPanel() {\n    }\n\n    /**\n     * 直接隐藏工具面板\n     */\n    @JvmStatic\n    fun hideToolPanel() {\n    }\n\n    @JvmStatic\n    fun hide() {\n    }\n\n    /**\n     * 禁用app信息上传开关，该上传信息只为做DoKit接入量的统计，如果用户需要保护app隐私，可调用该方法进行禁用\n     */\n    @JvmStatic\n    fun disableUpload() {\n    }\n\n    @JvmStatic\n    val isShow: Boolean\n        get() = false\n\n    @JvmStatic\n    fun setDebug(debug: Boolean) {\n    }\n\n    /**\n     * 是否显示主入口icon\n     */\n    @JvmStatic\n    fun setAlwaysShowMainIcon(alwaysShow: Boolean) {\n    }\n\n    /**\n     * 设置加密数据库密码\n     */\n    @JvmStatic\n    fun setDatabasePass(map: Map<String, String>) {\n    }\n\n    /**\n     * 设置文件管理助手http端口号\n     */\n    @JvmStatic\n    fun setFileManagerHttpPort(port: Int) {\n    }\n\n    @JvmStatic\n    fun setMCIntercept(interceptor: McClientProcessor) {\n    }\n\n    /**\n     * 设置扩展网络拦截器的代理对象\n     */\n    @JvmStatic\n    fun setNetExtInterceptor(extInterceptorProxy: DokitExtInterceptor.DokitExtInterceptorProxy) {\n    }\n\n    /**\n     *设置dokit的性能监控全局回调\n     */\n    @JvmStatic\n    fun setCallBack(callback: DoKitCallBack) {\n    }\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/AbstractKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit\n\nimport android.app.Activity\nimport android.content.Context\nimport android.os.Bundle\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-11-20-15:29\n * 描    述：\n * 修订历史：\n * ================================================\n */\nabstract class AbstractKit : IKit {\n    /**\n     * 启动UniversalActivity\n     *\n     * @param fragmentClass\n     * @param context\n     * @param bundle\n     * @param isSystemFragment 是否是内置kit\n     */\n    fun startUniversalActivity(\n        fragmentClass: Class<out BaseFragment>,\n        context: Context?,\n        bundle: Bundle? = null,\n        isSystemFragment: Boolean = false\n    ) {\n    }\n\n\n    /**\n     * 是否是内置kit 外部kit不需要实现\n     *\n     * @return\n     */\n    open val isInnerKit: Boolean\n        get() = false\n\n    /**\n     * 是否可以显示在工具面板上\n     */\n    var canShow: Boolean = true\n\n    /**\n     * 返回kitId\n     * 内置工具必须返回而且需要和doraemonkit模块下的assets/dokit_system_kits.json文件中的innerKitId保持一致\n     * 否则该工具无法在工具面板中显示\n     * @return\n     */\n    open fun innerKitId(): String {\n        return \"\"\n    }\n\n\n    /**\n     * 返回当前栈顶的activity\n     * @return activity\n     */\n    fun currentActivity(): Activity? {\n        return null\n    }\n\n\n    override val category: Int\n        get() = Category.DEFAULT\n\n\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/Category.java",
    "content": "package com.didichuxing.doraemonkit.kit;\n\n/**\n * Created by jint on 2018/6/22.\n * 已弃用 保留是为了兼容以前的api\n */\n\n@Deprecated\npublic interface Category {\n    /**\n     * 业务模块\n     */\n    int BIZ = 0;\n    /**\n     * 常用工具模块\n     */\n    int TOOLS = 1;\n\n    /**\n     * 性能监控模块\n     */\n    int PERFORMANCE = 2;\n\n    /**\n     * 视觉工具模块\n     */\n    int UI = 3;\n    /**\n     * 平台工具模块\n     */\n    int PLATFORM = 4;\n    /**\n     * 关闭\n     */\n    int CLOSE = 5;\n    /**\n     * Dokit版本号\n     */\n    int VERSION = 6;\n\n    /**\n     * 浮标模式 系统或者内置\n     */\n    int FLOAT_MODE = 7;\n    /**\n     * weex\n     */\n    int WEEX = 8;\n\n\n    /**\n     * default\n     */\n    int DEFAULT = 9;\n}\n"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/IKit.kt",
    "content": "package com.didichuxing.doraemonkit.kit\n\nimport android.app.Activity\nimport android.content.Context\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\n\n/**\n * Created by zhangweida on 2018/6/22.\n * 工具入口 请继承AbstractKit\n */\ninternal interface IKit {\n    /**\n     * 返回分类\n     *\n     * @return int\n     */\n    val category: Int\n\n    /**\n     * 返回名称\n     *\n     * @return\n     */\n    @get:StringRes\n    val name: Int\n\n    /**\n     * 返回图标\n     *\n     * @return\n     */\n    @get:DrawableRes\n    val icon: Int\n\n    /**\n     * 点击回调\n     *\n     * @param context\n     */\n    @Deprecated(\"请使用onClickWithReturn代替\")\n    fun onClick(context: Context?) {\n    }\n\n    /**\n     * 点击回调 带返回值\n     * @return true 隐藏面板 false 不隐藏面板\n     */\n    fun onClickWithReturn(activity: Activity): Boolean {\n        return true\n    }\n\n    /**\n     * app 初始化时调用\n     *\n     * @param context\n     */\n    fun onAppInit(context: Context?)\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/core/AbsDokitFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.LayoutRes\n\n/**\n * @Author: changzuozhen\n * @Date: 2020-12-22\n *\n *\n * 全屏页面\n * @see com.didichuxing.doraemonkit.kit.core.AbsDokitFragment\n * 启动工具函数\n *\n * @see com.didichuxing.doraemonkit.kit.core.SimpleDoKitStarter.startFullScreen\n */\nabstract class AbsDokitFragment : BaseFragment() {\n    val bundle: Bundle?\n        get() = null\n\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        return null\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n    }\n\n    override fun onRequestLayout(): Int {\n        return -1\n    }\n\n    protected open fun onViewCreated(view: View?) {}\n\n    @LayoutRes\n    abstract fun layoutId(): Int\n\n    open fun initTitle(): String {\n        return \"\"\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/core/AbsDokitView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content.res.Resources\nimport android.os.Bundle\nimport android.view.*\nimport android.widget.FrameLayout\nimport androidx.annotation.IdRes\nimport androidx.annotation.StringRes\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-20-16:22\n * 描    述：dokit 页面浮标抽象类 一般的悬浮窗都需要继承该抽象接口\n * 修订历史：\n * ================================================\n */\nabstract class AbsDokitView : DokitView, TouchProxy.OnTouchEventListener,\n    DokitViewManager.DokitViewAttachedListener {\n\n\n    val TAG = \"\"\n\n    /**\n     * 页面启动模式\n     */\n    var mode: DoKitViewLaunchMode = DoKitViewLaunchMode.SINGLE_INSTANCE\n\n    val isNormalMode = false\n\n\n\n    @JvmField\n    protected var mWindowManager = null\n\n    /**\n     * 创建FrameLayout#LayoutParams 内置悬浮窗调用\n     */\n    var normalLayoutParams: FrameLayout.LayoutParams? = null\n\n    /**\n     * 创建FrameLayout#LayoutParams 系统悬浮窗调用\n     */\n    var systemLayoutParams: WindowManager.LayoutParams? = null\n\n\n\n    /**\n     * 当前dokitViewName 用来当做map的key 和dokitViewIntent的tag一致\n     */\n    var tag = \"\"\n    var bundle: Bundle? = null\n\n    /**\n     * weakActivity attach activity\n     */\n\n    val activity: Activity?\n        get() = null\n\n\n    fun setActivity(activity: Activity) {\n    }\n\n\n\n    val doKitView: View?\n        get() = null\n\n\n\n    /**\n     * 只控件在布局边界发生大小变化被裁剪的原因：\n     * https://juejin.cn/post/6844903624452079623\n     *\n     */\n    val parentView: DokitFrameLayout?\n        get() = null\n\n\n\n\n\n    override fun onDestroy() {\n\n    }\n\n    /**\n     * 默认实现为true\n     *\n     * @return\n     */\n    override fun canDrag(): Boolean {\n        return true\n    }\n\n    /**\n     * 搭配shouldDealBackKey使用 自定义处理完以后需要返回true\n     * 默认模式的onBackPressed 拦截在NormalDokitViewManager#getDokitRootContentView中被处理\n     * 系统模式下的onBackPressed 在当前类的performCreate 初始话DoKitView时被处理\n     * 返回false 表示交由系统处理\n     * 返回 true 表示当前的返回事件已由自己处理 并拦截了改返回事件\n     */\n    override fun onBackPressed(): Boolean {\n        return false\n    }\n\n    /**\n     * 默认不自己处理返回按键\n     *\n     * @return\n     */\n    override fun shouldDealBackKey(): Boolean {\n        return false\n    }\n\n    override fun onEnterBackground() {\n\n\n    }\n\n    override fun onEnterForeground() {\n\n\n    }\n\n    override fun onMove(x: Int, y: Int, dx: Int, dy: Int) {\n\n    }\n\n    /**\n     * 手指弹起时保存当前浮标位置\n     *\n     * @param x\n     * @param y\n     */\n    override fun onUp(x: Int, y: Int) {\n\n    }\n\n    /**\n     * 手指按下时的操作\n     *\n     * @param x\n     * @param y\n     */\n    override fun onDown(x: Int, y: Int) {\n\n    }\n\n\n    /**\n     * home键被点击 只有系统悬浮窗控件才会被调用\n     */\n    open fun onHomeKeyPress() {}\n\n    /**\n     * 菜单键被点击 只有系统悬浮窗控件才会被调用\n     */\n    open fun onRecentAppKeyPress() {}\n\n    /**\n     * 不能在改方法中进行dokitview的添加和删除 因为处于遍历过程在\n     * 只有系统模式下才会调用\n     *\n     * @param dokitView\n     */\n    override fun onDokitViewAdd(dokitView: AbsDokitView?) {}\n\n    override fun onResume() {\n    }\n\n    override fun onPause() {}\n\n    /**\n     * 系统悬浮窗需要调用\n     *\n     * @return\n     */\n    val context: Context?\n        get() = null\n\n    val resources: Resources?\n        get() = null\n\n    fun getString(@StringRes resId: Int): String? {\n        return null\n    }\n\n    val isShow: Boolean\n        get() = false\n\n    protected fun <T : View> findViewById(@IdRes id: Int): T? {\n        return null\n    }\n\n\n    /**\n     * 将当前dokitView于activity解绑\n     */\n    fun detach() {\n    }\n\n    /**\n     * 操作DecorView的直接子布局\n     * 测试专用\n     */\n    fun dealDecorRootView(decorRootView: FrameLayout?) {\n\n    }\n\n    /**\n     * 更新view的位置\n     *\n     * @param isActivityBackResume 是否是从其他页面返回时更新的位置\n     */\n    open fun updateViewLayout(tag: String, isActivityBackResume: Boolean) {\n\n    }\n\n\n    /**\n     * 是否限制布局边界\n     *\n     * @return\n     */\n    open fun restrictBorderline(): Boolean {\n        return true\n    }\n\n\n    fun post(run: Runnable) {\n    }\n\n    fun postDelayed(run: Runnable, delayMillis: Long) {\n    }\n\n\n    /**\n     * 获取屏幕短边的长度 不包含statusBar\n     *\n     * @return\n     */\n    val screenShortSideLength: Int\n        get() = -1\n    //ScreenUtils.getAppScreenHeight(); 不包含statusBar\n    /**\n     * 获取屏幕长边的长度 不包含statusBar\n     *\n     * @return\n     */\n    val screenLongSideLength: Int\n        get() = -1\n\n    /**\n     * 强制刷新当前dokitview\n     */\n    open fun immInvalidate() {\n    }\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/core/BaseFragment.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.IdRes\nimport androidx.annotation.LayoutRes\nimport androidx.fragment.app.Fragment\n\n\n/**\n * @author wanglikun\n * @date 2018/10/26\n */\nabstract class BaseFragment : Fragment() {\n    @JvmField\n    val TAG = this.javaClass.simpleName\n\n    /**\n     * @return 资源文件\n     */\n    @LayoutRes\n    protected abstract fun onRequestLayout(): Int\n\n\n    fun <T : View> findViewById(@IdRes id: Int): T {\n        return Activity().findViewById(id)\n    }\n\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n\n        return null\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n    }\n\n\n    protected fun interceptTouchEvents(): Boolean {\n        return false\n    }\n\n\n    open fun onBackPressed(): Boolean {\n        return false\n    }\n\n\n    @JvmOverloads\n    fun showContent(fragmentClass: Class<out BaseFragment>, bundle: Bundle? = null) {\n    }\n\n    fun finish() {\n    }\n\n\n\n\n\n\n\n\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/core/DokitFrameLayout.java",
    "content": "package com.didichuxing.doraemonkit.kit.core;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.widget.FrameLayout;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-10-08-17:10\n * 描    述：自定义FrameLayout 用来区分原生FrameLayout\n * 修订历史：\n * ================================================\n */\npublic class DokitFrameLayout extends FrameLayout implements DokitViewInterface {\n    public static final int DoKitFrameLayoutFlag_ROOT = 100;\n    public static final int DoKitFrameLayoutFlag_CHILD = 200;\n\n    public DokitFrameLayout(@NonNull Context context, int flag) {\n        super(context);\n    }\n\n    public DokitFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public DokitFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/core/DokitIntent.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\nimport android.os.Bundle\n\n/**\n * Created by jintai on 2019/9/16.\n * dokitView intent\n */\ndata class DokitIntent(\n    var targetClass: Class<out AbsDokitView>,\n    var activity: Activity = Activity(),\n    var bundle: Bundle? = null,\n    var tag: String = \"\",\n    var mode: DoKitViewLaunchMode = DoKitViewLaunchMode.SINGLE_INSTANCE\n)\n\n\nenum class DoKitViewLaunchMode {\n    SINGLE_INSTANCE,\n    /**\n     * 倒计时\n     */\n    COUNTDOWN\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/core/DokitView.java",
    "content": "package com.didichuxing.doraemonkit.kit.core;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.widget.FrameLayout;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-20-16:22\n * 描    述：dokit 页面浮标接口\n * 修订历史：\n * ================================================\n */\ninterface DokitView {\n\n\n    /**\n     * dokit view 创建时调用 做一些变量的初始化  当还不能进行View的操作\n     *\n     * @param context\n     */\n    void onCreate(Context context);\n\n    /**\n     * 传入rootView 用于创建kit控件\n     *\n     * @param context\n     * @param rootView\n     * @return 返回创建的childView\n     */\n    View onCreateView(Context context, FrameLayout rootView);\n\n\n    /**\n     * 将xml中的控件添加到rootView以后调用，在当前方法中可以进行view的一些操作\n     *\n     * @param rootView\n     */\n    void onViewCreated(FrameLayout rootView);\n\n    /**\n     * 当前的dokitView添加到根布局里时调用\n     */\n    void onResume();\n\n    /**\n     * 当前activity onPause时调用\n     */\n    void onPause();\n\n    /**\n     * 确定浮标的初始位置\n     * LayoutParams创建完以后调用\n     * 调用时建议放在实现下方\n     *\n     * @param layoutParams\n     */\n    //void onNormalLayoutParamsCreated(FrameLayout.LayoutParams layoutParams);\n\n\n    /**\n     * 确定系统悬浮窗浮标的初始位置\n     * LayoutParams创建完以后调用\n     *\n     * @param layoutParams\n     */\n    //void onSystemLayoutParamsCreated(WindowManager.LayoutParams layoutParams);\n\n    /**\n     * 确定系统悬浮窗浮标的初始位置\n     * LayoutParams创建完以后调用\n     *\n     * @param params\n     */\n\n    void initDokitViewLayoutParams(DokitViewLayoutParams params);\n\n    /**\n     * app进入后台时调用 内置dokitView 不需要实现\n     */\n    void onEnterBackground();\n\n    /**\n     * app回到前台时调用 内置dokitview 不需要实现\n     */\n    void onEnterForeground();\n\n    /**\n     * 浮标控件是否可以拖动\n     *\n     * @return\n     */\n    boolean canDrag();\n\n    /**\n     * 是否需要自己处理返回键\n     *\n     * @return\n     */\n    boolean shouldDealBackKey();\n\n    /**\n     * shuldDealBackKey == true 时调用\n     */\n    boolean onBackPressed();\n\n    /**\n     * 悬浮窗主动销毁时调用 不能在当前生命周期回调函数中调用 detach自己 否则会出现死循环\n     */\n    void onDestroy();\n\n}\n"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/core/DokitViewInterface.java",
    "content": "package com.didichuxing.doraemonkit.kit.core;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-10-08-17:11\n * 描    述：空实现  主要是为了跟原生的view做区分\n * 修订历史：\n * ================================================\n */\npublic interface DokitViewInterface {\n\n}\n"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/core/DokitViewLayoutParams.java",
    "content": "package com.didichuxing.doraemonkit.kit.core;\n\nimport android.view.ViewGroup;\nimport android.view.WindowManager;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-24-10:57\n * 描    述：dokitView的初始化位置\n * 修订历史：\n * ================================================\n */\npublic class DokitViewLayoutParams {\n\n    /**\n     * 悬浮窗不能获取焦点\n     */\n    public static int FLAG_NOT_FOCUSABLE = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;\n    public static int FLAG_NOT_TOUCHABLE = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;\n    /**\n     * wiki:https://blog.csdn.net/hnlgzb/article/details/108520716\n     * 是否允许超出屏幕\n     */\n    public static int FLAG_LAYOUT_NO_LIMITS = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;\n    /**\n     * 悬浮窗不能获取焦点并且不相应触摸\n     */\n    public static int FLAG_NOT_FOCUSABLE_AND_NOT_TOUCHABLE = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;\n\n    public static int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT;\n    public static int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;\n\n\n    /**\n     * 只针对系统悬浮窗起作用 值基本上为以上2个\n     */\n    public int flags;\n    /**\n     * 只针对系统悬浮窗起作用 值基本上为Gravity\n     */\n    public int gravity;\n    public int x;\n    public int y;\n    public int width;\n    public int height;\n\n    @Override\n    public String toString() {\n        return \"DokitViewLayoutParams{\" +\n                \"flags=\" + flags +\n                \", gravity=\" + gravity +\n                \", x=\" + x +\n                \", y=\" + y +\n                \", width=\" + width +\n                \", height=\" + height +\n                '}';\n    }\n}\n"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/core/DokitViewManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\n\n\n/**\n * Created by jintai on 2018/10/23.\n * 浮标管理类\n */\nclass DokitViewManager : DokitViewManagerInterface {\n\n    companion object {\n        @JvmStatic\n        val instance: DokitViewManager by lazy {\n            DokitViewManager()\n        }\n\n    }\n\n\n    /**\n     * 当app进入后台时调用\n     */\n    override fun notifyBackground() {\n    }\n\n    /**\n     * 当app进入前台时调用\n     */\n    override fun notifyForeground() {\n    }\n\n    override fun onActivityDestroyed(activity: Activity?) {\n    }\n\n    override fun onActivityPaused(activity: Activity?) {\n    }\n\n    override fun onActivityStopped(activity: Activity?) {\n    }\n\n    override fun dispatchOnActivityResumed(activity: Activity?) {\n    }\n\n    override fun attach(dokitIntent: DokitIntent) {\n    }\n\n    override fun detach(dokitView: AbsDokitView) {\n    }\n\n    override fun detach(tag: String) {\n    }\n\n\n    override fun detach(doKitViewClass: Class<out AbsDokitView>) {\n    }\n\n\n    /**\n     * 移除所有activity的所有dokitView\n     */\n    override fun detachAll() {\n    }\n\n    override fun <T : AbsDokitView> getDoKitView(\n        activity: Activity?,\n        clazz: Class<T>\n    ): AbsDokitView? {\n        return null\n    }\n\n    override fun getDoKitViews(activity: Activity?): Map<String, AbsDokitView>? {\n        return null\n    }\n\n\n    /**\n     * 系统悬浮窗需要调用\n     */\n    interface DokitViewAttachedListener {\n        fun onDokitViewAdd(dokitView: AbsDokitView?)\n    }\n\n\n\n\n\n\n\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/core/DokitViewManagerInterface.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2019-09-28-15:18\n * 描    述：页面浮标管理类接口\n * 修订历史：\n * ================================================\n */\ninterface DokitViewManagerInterface {\n    /**\n     * 在当前Activity中添加指定悬浮窗\n     *\n     * @param dokitIntent\n     */\n    fun attach(dokitIntent: DokitIntent)\n\n    /**\n     * 移除每个activity指定的dokitView\n     *\n     * @param dokitView\n     */\n    fun detach(dokitView: AbsDokitView)\n\n\n    /**\n     * 移除每个activity指定的dokitView tag\n     *\n     * @param tag 一般为dokitView的className\n     */\n    fun detach(tag: String)\n\n\n    fun detach(doKitViewClass: Class<out AbsDokitView>)\n\n\n    /**\n     * 移除所有activity的所有dokitView\n     */\n    fun detachAll()\n\n    /**\n     * 获取页面上指定的dokitView\n     *\n     * @param activity\n     * @param tag\n     * @return\n     */\n    fun <T:AbsDokitView> getDoKitView(activity: Activity?, clazz: Class<T>): AbsDokitView?\n\n\n    /**\n     * 获取页面上所有的dokitView\n     *\n     * @param activity\n     * @return\n     */\n    fun getDoKitViews(activity: Activity?): Map<String, AbsDokitView>?\n\n    /**\n     * 当app进入后台时调用\n     */\n    fun notifyBackground()\n\n    /**\n     * 当app进入前台时调用\n     */\n    fun notifyForeground()\n\n    /**\n     * Activity销毁时调用\n     *\n     * @param activity\n     */\n    fun onActivityDestroyed(activity: Activity?)\n\n    /**\n     * 页面onPause时调用\n     *\n     * @param activity\n     */\n    fun onActivityPaused(activity: Activity?)\n\n    /**\n     * 页面onStop时调用\n     */\n    fun onActivityStopped(activity: Activity?)\n\n    /**\n     *\n     * @param activity\n     */\n    fun dispatchOnActivityResumed(activity: Activity?)\n\n\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/core/McClientProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.core\n\nimport android.app.Activity\nimport android.view.View\nimport android.view.accessibility.AccessibilityEvent\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2/18/21-17:06\n * 描    述：一机多控从机处理器\n * 修订历史：\n * ================================================\n */\ninterface McClientProcessor {\n\n    /**\n     * 客户端处理特殊的控件手势\n     * @return true :自定义处理成功\n     * @return false :自定义处理失败\n     */\n    fun process(activity: Activity?, view: View?,eventType: String, params: Map<String, String>)\n\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/core/TouchProxy.java",
    "content": "package com.didichuxing.doraemonkit.kit.core;\n\nimport android.view.MotionEvent;\nimport android.view.View;\n\n\n/**\n * @author wanglikun\n * touch 事件代理 解决点击和触摸事件的冲突\n */\npublic class TouchProxy {\n\n    public TouchProxy(OnTouchEventListener eventListener) {\n    }\n\n    public void setEventListener(OnTouchEventListener eventListener) {\n    }\n\n\n    public boolean onTouchEvent(View v, MotionEvent event) {\n\n        return true;\n    }\n\n\n    public interface OnTouchEventListener {\n        void onMove(int x, int y, int dx, int dy);\n\n        void onUp(int x, int y);\n\n        void onDown(int x, int y);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/network/bean/NetworkRecord.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.bean;\n\nimport android.text.TextUtils;\n\nimport java.io.Serializable;\n\n/**\n * @desc: 一条网络请求记录\n */\npublic class NetworkRecord implements Serializable {\n\n    public int mRequestId;\n    public Request mRequest;\n    public Response mResponse;\n    public String mPlatform;\n    public String mResponseBody;\n\n    public long requestLength;\n    public long responseLength;\n\n    public long startTime;\n    public long endTime;\n\n    public boolean filter(String text) {\n\n        return false;\n    }\n\n\n    public boolean isGetRecord() {\n        return false;\n    }\n\n    public boolean isPostRecord() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/network/bean/Request.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.bean;\n\nimport android.text.TextUtils;\n\nimport java.io.Serializable;\n\n/**\n * 请求bean\n */\npublic class Request implements Serializable {\n\n    public String url;\n\n    public String method;\n\n    public String headers;\n\n    public String postData;\n\n    public String encode;\n\n    @Override\n    public String toString() {\n        return \"\";\n    }\n\n    public boolean filter(String text) {\n        return false;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/network/bean/Response.java",
    "content": "package com.didichuxing.doraemonkit.kit.network.bean;\n\nimport java.io.Serializable;\n\n/**\n * 响应bean,不包含内容Body\n */\npublic class Response implements Serializable {\n\n    public String url;\n\n    public int status;\n\n    public String headers;\n\n    public String mimeType;\n\n    @Override\n    public String toString() {\n        return \"\";\n    }\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/network/okhttp/interceptor/DokitExtInterceptor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.network.okhttp.interceptor\n\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport java.io.IOException\n\n/**\n * Author: xuweiyu\n * Date: 5/11/21\n * Email: wizz.xu@outlook.com\n * Description: Dokit 扩展网络拦截器\n */\nclass DokitExtInterceptor : Interceptor {\n    @Throws(IOException::class)\n    override fun intercept(chain: Interceptor.Chain): Response {\n        return chain.proceed(chain.request())\n    }\n\n    interface DokitExtInterceptorProxy {\n        fun intercept(chain: Interceptor.Chain): Response\n    }\n\n    companion object {\n        const val TAG = \"DokitExtInterceptor\"\n        var dokitExtInterceptorProxy: DokitExtInterceptorProxy? = null\n    }\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/performance/PerformanceValueListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.performance\n/**\n * Author: xuweiyu\n * Date: 5/12/21\n * Email: wizz.xu@outlook.com\n * Description: cpu 内存 FPS 的回调监听\n */\ninterface PerformanceValueListener {\n    fun onGetMemory(value: Float)\n    fun onGetCPU(value: Float)\n    fun onGetFPS(value: Float)\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/test/DoKitTestManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test\n\nobject DoKitTestManager {\n\n    /**\n     * 是否是主机模式\n     */\n    fun isHostMode(): Boolean {\n        return false\n    }\n\n    /**\n     * 是否是从机模式\n     */\n    fun isClientMode(): Boolean {\n        return false\n    }\n\n    /**\n     * 测试功能是否关闭\n     */\n    fun isClose(): Boolean {\n        return true\n    }\n\n    fun getTestMode(): TestMode {\n        return TestMode.UNKNOWN\n    }\n\n    /**\n     * 开始测试功能\n     * 1、开始hook 或者关闭hook\n     */\n    fun startTest(testMode: TestMode) {\n    }\n\n    /**\n     * 关闭测试功能\n     * 备注：关闭后测试相关功能将不工作\n     */\n    fun closeTest() {\n    }\n}\n"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/test/TestMode.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test\n\n/**\n * 测试工作模式 mode\n */\nenum class TestMode {\n    /**\n     *未知 :不允许执行采集也不允许执行模拟事件\n     */\n    UNKNOWN,\n\n    /**\n     * 主机:采集事件\n     */\n    HOST,\n\n    /**\n     * 从机:模拟执行事件\n     */\n    CLIENT,\n\n}\n"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/kit/webdoor/WebDoorManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.webdoor;\n\nimport android.content.Context;\n\n/**\n * Created by wanglikun on 2018/10/10.\n */\n\npublic class WebDoorManager {\n    public interface WebDoorCallback {\n        void overrideUrlLoading(Context context, String url);\n    }\n}"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/tcp/ability/MessageReceiverHook.java",
    "content": "package com.didichuxing.doraemonkit.tcp.ability;\n\n/**\n * didi Create on 2022/9/20 .\n * <p>\n * Copyright (c) 2022/9/20 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/9/20 11:19 上午\n * @Description 用一句话说明文件功能\n */\n\npublic class MessageReceiverHook {\n\n    public static void dispatchMessage(Object message) {\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/tcp/ability/MessageSenderHook.java",
    "content": "package com.didichuxing.doraemonkit.tcp.ability;\n\n/**\n * didi Create on 2022/9/20 .\n * <p>\n * Copyright (c) 2022/9/20 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/9/20 11:18 上午\n * @Description 用一句话说明文件功能\n */\n\npublic class MessageSenderHook {\n\n    public static void sendMessage(String message) {\n    }\n}\n"
  },
  {
    "path": "Android/dokit-no-op/src/main/java/com/didichuxing/doraemonkit/tcp/ability/TcpMessageHook.java",
    "content": "package com.didichuxing.doraemonkit.tcp.ability;\n\n/**\n * didi Create on 2022/9/20 .\n * <p>\n * Copyright (c) 2022/9/20 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/9/20 11:18 上午\n * @Description 用一句话说明文件功能\n */\n\npublic class TcpMessageHook {\n\n    public static void init() {\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit-no-op/upload.sh",
    "content": "#!/usr/bin/env bash\necho -n  \"please enter bintray userid ->\"\nread  userid_bintray\necho -n  \"please enter bintray apikey ->\"\nread  apikey_bintray\n../gradlew clean build --stacktrace  --info   bintrayUpload -PbintrayUser=${userid_bintray} -PbintrayKey=${apikey_bintray} -PdryRun=false"
  },
  {
    "path": "Android/dokit-okhttp-api/.gitignore",
    "content": "/build"
  },
  {
    "path": "Android/dokit-okhttp-api/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply from: '../upload.gradle'\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_16\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n}\n\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    if (needKotlinV14()) {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v14\"]\n    } else {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v13\"]\n    }\n    implementation project(':dokit-okhttp-v3')\n    implementation project(':dokit-okhttp-v4')\n    compileOnly project(':dokit-util')\n}\nrepositories {\n    mavenCentral()\n}\n\n"
  },
  {
    "path": "Android/dokit-okhttp-api/gradle.properties",
    "content": "ARTIFACT_ID=dokitx-okhttp-api"
  },
  {
    "path": "Android/dokit-okhttp-api/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original from file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "Android/dokit-okhttp-api/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.didichuxing.doraemonkit.okhttp_api\">\n\n</manifest>"
  },
  {
    "path": "Android/dokit-okhttp-api/src/main/java/com/didichuxing/doraemonkit/okhttp_api/OkHttpWrap.kt",
    "content": "package com.didichuxing.doraemonkit.okhttp_api\n\nimport com.didichuxing.doraemonkit.util.ReflectUtils\nimport okhttp3.*\nimport okio.BufferedSink\nimport okio.Sink\nimport java.net.URL\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/10/19-14:55\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject OkHttpWrap {\n    val okHttpVersion: String by lazy {\n        var version = \"\"\n        try {\n            version =\n                ReflectUtils.reflect(\"okhttp3.internal.Version\").method(\"userAgent\").get<String>()\n        } catch (e: Exception) {\n\n        }\n\n        try {\n            if (version.isEmpty()) {\n                version = ReflectUtils.reflect(\"okhttp3.internal.Version\").field(\"userAgent\")\n                    .get<String>()\n            }\n        } catch (e: Exception) {\n\n        }\n\n        try {\n            if (version.isEmpty()) {\n                version = ReflectUtils.reflect(\"okhttp3.OkHttp\").field(\"VERSION\").get<String>()\n            }\n        } catch (e: Exception) {\n\n        }\n\n        if (version.isNotEmpty()) {\n            val split = version.split(\"/\")\n            if (split.size >= 2) {\n                version = split[split.size - 1]\n            }\n        }\n        //Log.v(\"OkHttpWrap\", \"version===>$version\")\n        version\n    }\n\n    private val isV4: Boolean by lazy {\n        if (okHttpVersion.startsWith(\"4.\")) {\n            //Log.v(\"OkHttpWrap\", \"isV4===>true\")\n            return@lazy true\n        } else if (okHttpVersion.startsWith(\"3.\")) {\n            //Log.v(\"OkHttpWrap\", \"isV4===>false\")\n            return@lazy false\n        }\n        return@lazy false\n    }\n\n\n    fun createHttpUrl(url: String?): HttpUrl? {\n        return if (isV4) {\n            OkHttpWrapV4.createHttpUrl(url)\n        } else {\n            OkHttpWrapV3.createHttpUrl(url)\n        }\n\n    }\n\n\n    fun toUrl(httpUrl: HttpUrl?): URL? {\n        return if (isV4) {\n            OkHttpWrapV4.toUrl(httpUrl)\n        } else {\n            OkHttpWrapV3.toUrl(httpUrl)\n        }\n    }\n\n    fun toResponseBody(response: Response?): ResponseBody? {\n        return if (isV4) {\n            OkHttpWrapV4.toResponseBody(response)\n        } else {\n            OkHttpWrapV3.toResponseBody(response)\n        }\n    }\n\n\n    fun toHttpQuery(httpUrl: HttpUrl?): String? {\n        return if (isV4) {\n            OkHttpWrapV4.toHttpQuery(httpUrl)\n        } else {\n            OkHttpWrapV3.toHttpQuery(httpUrl)\n        }\n    }\n\n\n    fun toEncodedPath(httpUrl: HttpUrl?): String? {\n        return if (isV4) {\n            OkHttpWrapV4.toEncodedPath(httpUrl)\n        } else {\n            OkHttpWrapV3.toEncodedPath(httpUrl)\n        }\n    }\n\n\n    fun toRequestHost(httpUrl: HttpUrl?): String? {\n        return if (isV4) {\n            OkHttpWrapV4.toRequestHost(httpUrl)\n        } else {\n            OkHttpWrapV3.toRequestHost(httpUrl)\n        }\n    }\n\n    fun toRequestHost(request: Request): String? {\n        return if (isV4) {\n            OkHttpWrapV4.toRequestHost(request)\n        } else {\n            OkHttpWrapV3.toRequestHost(request)\n        }\n    }\n\n    fun toResponseHost(response: Response): String? {\n        return if (isV4) {\n            OkHttpWrapV4.toResponseHost(response)\n        } else {\n            OkHttpWrapV3.toResponseHost(response)\n        }\n    }\n\n\n    fun toScheme(httpUrl: HttpUrl): String {\n        return if (isV4) {\n            OkHttpWrapV4.toScheme(httpUrl)\n        } else {\n            OkHttpWrapV3.toScheme(httpUrl)\n        }\n    }\n\n\n    fun toResponseCode(response: Response): Int {\n        return if (isV4) {\n            OkHttpWrapV4.toResponseCode(response)\n        } else {\n            OkHttpWrapV3.toResponseCode(response)\n        }\n    }\n\n\n    fun toMediaType(contentType: String?): MediaType? {\n        return if (isV4) {\n            OkHttpWrapV4.toMediaType(contentType)\n        } else {\n            OkHttpWrapV3.toMediaType(contentType)\n        }\n    }\n\n    fun toRequestBody(content: String?, mediaType: MediaType?): RequestBody? {\n        return if (isV4) {\n            OkHttpWrapV4.toRequestBody(content, mediaType)\n        } else {\n            OkHttpWrapV3.toRequestBody(content, mediaType)\n        }\n    }\n\n    fun createByteCountBufferedSink(sink: Sink, byteCount: Long): BufferedSink {\n        return if (isV4) {\n            ByteCountBufferedSinkV4(sink, byteCount)\n        } else {\n            ByteCountBufferedSinkV3(sink, byteCount)\n        }\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit-okhttp-api/upload.sh",
    "content": "#!/usr/bin/env bash\necho -n  \"please enter bintray userid ->\"\nread  userid_bintray\necho -n  \"please enter bintray apikey ->\"\nread  apikey_bintray\n../gradlew clean build --stacktrace --info   bintrayUpload -PbintrayUser=${userid_bintray} -PbintrayKey=${apikey_bintray} -PdryRun=false"
  },
  {
    "path": "Android/dokit-okhttp-v3/.gitignore",
    "content": "/build"
  },
  {
    "path": "Android/dokit-okhttp-v3/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply from: '../upload.gradle'\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_16\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n}\n\n\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    if (needKotlinV14()) {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v14\"]\n    } else {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v13\"]\n    }\n    //implementation rootProject.ext.dependencies[\"appcompat\"]\n    //compileOnly rootProject.ext.dependencies[\"okio\"]\n    api rootProject.ext.dependencies[\"okhttp_v3\"]\n}\nrepositories {\n    mavenCentral()\n}\n"
  },
  {
    "path": "Android/dokit-okhttp-v3/gradle.properties",
    "content": "ARTIFACT_ID=dokitx-okhttp-v3"
  },
  {
    "path": "Android/dokit-okhttp-v3/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original from file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "Android/dokit-okhttp-v3/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.didichuxing.doraemonkit.okhttp_v3\">\n\n</manifest>"
  },
  {
    "path": "Android/dokit-okhttp-v3/src/main/java/com/didichuxing/doraemonkit/okhttp_api/ByteCountBufferedSinkV3.java",
    "content": "package com.didichuxing.doraemonkit.okhttp_api;\n\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.Charset;\n\nimport okio.Buffer;\nimport okio.BufferedSink;\nimport okio.ByteString;\nimport okio.Okio;\nimport okio.Sink;\nimport okio.Source;\nimport okio.Timeout;\n\n/**\n * 可以设置每次写入大小的BufferedSink\n * <p>\n * Created by xiandanin on 2019-05-10 16:07\n */\npublic  class ByteCountBufferedSinkV3 implements BufferedSink {\n    private final long mByteCount;\n    private final Sink mOriginalSink;\n    private final BufferedSink mDelegate;\n\n    public ByteCountBufferedSinkV3(Sink sink, long byteCount) {\n        this.mOriginalSink = sink;\n        this.mDelegate = Okio.buffer(mOriginalSink);\n        this.mByteCount = byteCount;\n    }\n\n    @Override\n    public long writeAll(Source source) throws IOException {\n        if (source == null) throw new IllegalArgumentException(\"source == null\");\n        long totalBytesRead = 0;\n        for (long readCount; (readCount = source.read(buffer(), mByteCount)) != -1; ) {\n            totalBytesRead += readCount;\n            emitCompleteSegments();\n        }\n        return totalBytesRead;\n    }\n\n    @Override\n    public BufferedSink write(byte[] source, int offset, int byteCount) throws IOException {\n        if (!isOpen()) throw new IllegalStateException(\"closed\");\n        //计算出要写入的次数\n        long count = (long) Math.ceil((double) source.length / mByteCount);\n        for (int i = 0; i < count; i++) {\n            //让每次写入的字节数精确到mByteCount 分多次写入\n            long newOffset = i * mByteCount;\n            long writeByteCount = Math.min(mByteCount, source.length - newOffset);\n            buffer().write(source, (int) newOffset, (int) writeByteCount);\n            emitCompleteSegments();\n        }\n        return this;\n    }\n\n    @Override\n    public BufferedSink emitCompleteSegments() throws IOException {\n        final Buffer buffer = buffer();\n        mOriginalSink.write(buffer, buffer.size());\n        return this;\n    }\n\n    @Override\n    public Buffer buffer() {\n        return mDelegate.buffer();\n    }\n\n    @Override\n    public BufferedSink write(ByteString byteString) throws IOException {\n        return mDelegate.write(byteString);\n    }\n\n    @Override\n    public BufferedSink write(byte[] source) throws IOException {\n        return mDelegate.write(source);\n    }\n\n    @Override\n    public BufferedSink write(Source source, long byteCount) throws IOException {\n        return mDelegate.write(source, byteCount);\n    }\n\n    @Override\n    public BufferedSink writeUtf8(String string) throws IOException {\n        return mDelegate.writeUtf8(string);\n    }\n\n    @Override\n    public BufferedSink writeUtf8(String string, int beginIndex, int endIndex) throws IOException {\n        return mDelegate.writeUtf8(string, beginIndex, endIndex);\n    }\n\n    @Override\n    public BufferedSink writeUtf8CodePoint(int codePoint) throws IOException {\n        return mDelegate.writeUtf8CodePoint(codePoint);\n    }\n\n    @Override\n    public BufferedSink writeString(String string, Charset charset) throws IOException {\n        return mDelegate.writeString(string, charset);\n    }\n\n    @Override\n    public BufferedSink writeString(String string, int beginIndex, int endIndex, Charset charset) throws IOException {\n        return mDelegate.writeString(string, beginIndex, endIndex, charset);\n    }\n\n    @Override\n    public BufferedSink writeByte(int b) throws IOException {\n        return mDelegate.writeByte(b);\n    }\n\n    @Override\n    public BufferedSink writeShort(int s) throws IOException {\n        return mDelegate.writeShort(s);\n    }\n\n    @Override\n    public BufferedSink writeShortLe(int s) throws IOException {\n        return mDelegate.writeShortLe(s);\n    }\n\n    @Override\n    public BufferedSink writeInt(int i) throws IOException {\n        return mDelegate.writeInt(i);\n    }\n\n    @Override\n    public BufferedSink writeIntLe(int i) throws IOException {\n        return mDelegate.writeIntLe(i);\n    }\n\n    @Override\n    public BufferedSink writeLong(long v) throws IOException {\n        return mDelegate.writeLong(v);\n    }\n\n    @Override\n    public BufferedSink writeLongLe(long v) throws IOException {\n        return mDelegate.writeLongLe(v);\n    }\n\n    @Override\n    public BufferedSink writeDecimalLong(long v) throws IOException {\n        return mDelegate.writeDecimalLong(v);\n    }\n\n    @Override\n    public BufferedSink writeHexadecimalUnsignedLong(long v) throws IOException {\n        return mDelegate.writeHexadecimalUnsignedLong(v);\n    }\n\n    @Override\n    public void flush() throws IOException {\n        mDelegate.flush();\n    }\n\n    @Override\n    public BufferedSink emit() throws IOException {\n        return mDelegate.emit();\n    }\n\n    @Override\n    public OutputStream outputStream() {\n        return mDelegate.outputStream();\n    }\n\n    @Override\n    public int write(ByteBuffer src) throws IOException {\n        return mDelegate.write(src);\n    }\n\n    @Override\n    public boolean isOpen() {\n        return mDelegate.isOpen();\n    }\n\n    @Override\n    public void write(Buffer source, long byteCount) throws IOException {\n        mDelegate.write(source, byteCount);\n    }\n\n    @Override\n    public Timeout timeout() {\n        return mDelegate.timeout();\n    }\n\n    @Override\n    public void close() throws IOException {\n        mDelegate.close();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-okhttp-v3/src/main/java/com/didichuxing/doraemonkit/okhttp_api/OkHttpWrapV3.kt",
    "content": "package com.didichuxing.doraemonkit.okhttp_api\n\nimport okhttp3.*\nimport java.net.URL\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/10/19-14:55\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject OkHttpWrapV3 {\n    fun createHttpUrl(url: String?): HttpUrl? {\n        url?.let {\n            return HttpUrl.parse(it)\n        } ?: return null\n    }\n\n\n    fun toUrl(httpUrl: HttpUrl?): URL? {\n        return httpUrl?.url()\n    }\n\n    fun toResponseBody(response: Response?): ResponseBody? {\n        return response?.body()\n    }\n\n\n    fun toHttpQuery(httpUrl: HttpUrl?): String? {\n        return httpUrl?.query()\n    }\n\n\n    fun toEncodedPath(httpUrl: HttpUrl?): String? {\n        return httpUrl?.encodedPath()\n    }\n\n\n    fun toRequestHost(httpUrl: HttpUrl?): String? {\n        return httpUrl?.host()\n    }\n\n    fun toRequestHost(request: Request): String? {\n        return request.url().host()\n    }\n\n    fun toResponseHost(response: Response): String? {\n        return response.request().url().host()\n    }\n\n\n    fun toScheme(httpUrl: HttpUrl): String {\n        return httpUrl.scheme()\n    }\n\n\n    fun toResponseCode(response: Response): Int {\n        return response.code()\n    }\n\n\n    fun toMediaType(contentType: String?): MediaType? {\n        if (contentType.isNullOrBlank()) {\n            return null\n        }\n        return MediaType.parse(contentType)\n    }\n\n    fun toRequestBody(content: String?, mediaType: MediaType?): RequestBody? {\n        return RequestBody.create(mediaType, content ?: \"\")\n    }\n\n\n}"
  },
  {
    "path": "Android/dokit-okhttp-v3/upload.sh",
    "content": "#!/usr/bin/env bash\necho -n  \"please enter bintray userid ->\"\nread  userid_bintray\necho -n  \"please enter bintray apikey ->\"\nread  apikey_bintray\n../gradlew  clean build  --stacktrace --info   bintrayUpload -PbintrayUser=${userid_bintray} -PbintrayKey=${apikey_bintray} -PdryRun=false"
  },
  {
    "path": "Android/dokit-okhttp-v4/.gitignore",
    "content": "/build"
  },
  {
    "path": "Android/dokit-okhttp-v4/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply from: '../upload.gradle'\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_16\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n}\n\n\n\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    if (needKotlinV14()) {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v14\"]\n    } else {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v13\"]\n    }\n    implementation rootProject.ext.dependencies[\"annotation\"]\n    //implementation rootProject.ext.dependencies[\"appcompat\"]\n//    compileOnly rootProject.ext.dependencies[\"okhttp_v4\"]\n    compileOnly rootProject.ext.dependencies[\"okhttp_v4\"]\n}\nrepositories {\n    mavenCentral()\n}\n\n"
  },
  {
    "path": "Android/dokit-okhttp-v4/gradle.properties",
    "content": "ARTIFACT_ID=dokitx-okhttp-v4"
  },
  {
    "path": "Android/dokit-okhttp-v4/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original from file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "Android/dokit-okhttp-v4/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.didichuxing.doraemonkit.okhttp_v4\">\n\n</manifest>"
  },
  {
    "path": "Android/dokit-okhttp-v4/src/main/java/com/didichuxing/doraemonkit/okhttp_api/ByteCountBufferedSinkV4.java",
    "content": "package com.didichuxing.doraemonkit.okhttp_api;\n\n\n\nimport androidx.annotation.NonNull;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.Charset;\n\nimport okio.Buffer;\nimport okio.BufferedSink;\nimport okio.ByteString;\nimport okio.Okio;\nimport okio.Sink;\nimport okio.Source;\nimport okio.Timeout;\n\n/**\n * 可以设置每次写入大小的BufferedSink\n * <p>\n * Created by jintai on 2020-10-20 16:07\n * 支持4.3.0+ 4.0.0 4.1.0 4.2.0 不支持\n */\npublic class ByteCountBufferedSinkV4 implements BufferedSink {\n\n    private final long mByteCount;\n    private final Sink mOriginalSink;\n    private final BufferedSink mDelegate;\n\n    public ByteCountBufferedSinkV4(Sink sink, long byteCount) {\n        this.mOriginalSink = sink;\n        this.mDelegate = Okio.buffer(mOriginalSink);\n        this.mByteCount = byteCount;\n    }\n\n    @Override\n    public long writeAll(Source source) throws IOException {\n        if (source == null) throw new IllegalArgumentException(\"source == null\");\n        long totalBytesRead = 0;\n        for (long readCount; (readCount = source.read(buffer(), mByteCount)) != -1; ) {\n            totalBytesRead += readCount;\n            emitCompleteSegments();\n        }\n        return totalBytesRead;\n    }\n\n    @Override\n    public BufferedSink write(byte[] source, int offset, int byteCount) throws IOException {\n        if (!isOpen()) throw new IllegalStateException(\"closed\");\n        //计算出要写入的次数\n        long count = (long) Math.ceil((double) source.length / mByteCount);\n        for (int i = 0; i < count; i++) {\n            //让每次写入的字节数精确到mByteCount 分多次写入\n            long newOffset = i * mByteCount;\n            long writeByteCount = Math.min(mByteCount, source.length - newOffset);\n            buffer().write(source, (int) newOffset, (int) writeByteCount);\n            emitCompleteSegments();\n        }\n        return this;\n    }\n\n    @Override\n    public BufferedSink emitCompleteSegments() throws IOException {\n        final Buffer buffer = buffer();\n        mOriginalSink.write(buffer, buffer.size());\n        return this;\n    }\n\n    @Override\n    public Buffer buffer() {\n        return mDelegate.buffer();\n    }\n\n    @Override\n    public BufferedSink write(ByteString byteString) throws IOException {\n        return mDelegate.write(byteString);\n    }\n\n    @Override\n    public BufferedSink write(byte[] source) throws IOException {\n        return mDelegate.write(source);\n    }\n\n    @Override\n    public BufferedSink write(Source source, long byteCount) throws IOException {\n        return mDelegate.write(source, byteCount);\n    }\n\n    @Override\n    public BufferedSink writeUtf8(String string) throws IOException {\n        return mDelegate.writeUtf8(string);\n    }\n\n    @Override\n    public BufferedSink writeUtf8(String string, int beginIndex, int endIndex) throws IOException {\n        return mDelegate.writeUtf8(string, beginIndex, endIndex);\n    }\n\n    @Override\n    public BufferedSink writeUtf8CodePoint(int codePoint) throws IOException {\n        return mDelegate.writeUtf8CodePoint(codePoint);\n    }\n\n    @Override\n    public BufferedSink writeString(String string, Charset charset) throws IOException {\n        return mDelegate.writeString(string, charset);\n    }\n\n    @Override\n    public BufferedSink writeString(String string, int beginIndex, int endIndex, Charset charset) throws IOException {\n        return mDelegate.writeString(string, beginIndex, endIndex, charset);\n    }\n\n    @Override\n    public BufferedSink writeByte(int b) throws IOException {\n        return mDelegate.writeByte(b);\n    }\n\n    @Override\n    public BufferedSink writeShort(int s) throws IOException {\n        return mDelegate.writeShort(s);\n    }\n\n    @Override\n    public BufferedSink writeShortLe(int s) throws IOException {\n        return mDelegate.writeShortLe(s);\n    }\n\n    @Override\n    public BufferedSink writeInt(int i) throws IOException {\n        return mDelegate.writeInt(i);\n    }\n\n    @Override\n    public BufferedSink writeIntLe(int i) throws IOException {\n        return mDelegate.writeIntLe(i);\n    }\n\n    @Override\n    public BufferedSink writeLong(long v) throws IOException {\n        return mDelegate.writeLong(v);\n    }\n\n    @Override\n    public BufferedSink writeLongLe(long v) throws IOException {\n        return mDelegate.writeLongLe(v);\n    }\n\n    @Override\n    public BufferedSink writeDecimalLong(long v) throws IOException {\n        return mDelegate.writeDecimalLong(v);\n    }\n\n    @Override\n    public BufferedSink writeHexadecimalUnsignedLong(long v) throws IOException {\n        return mDelegate.writeHexadecimalUnsignedLong(v);\n    }\n\n    @Override\n    public void flush() throws IOException {\n        mDelegate.flush();\n    }\n\n    @Override\n    public BufferedSink emit() throws IOException {\n        return mDelegate.emit();\n    }\n\n    @Override\n    public OutputStream outputStream() {\n        return mDelegate.outputStream();\n    }\n\n    @Override\n    public int write(ByteBuffer src) throws IOException {\n        return mDelegate.write(src);\n    }\n\n    @Override\n    public boolean isOpen() {\n        return mDelegate.isOpen();\n    }\n\n    @Override\n    public void write(Buffer source, long byteCount) throws IOException {\n        mDelegate.write(source, byteCount);\n    }\n\n    @Override\n    public Timeout timeout() {\n        return mDelegate.timeout();\n    }\n\n    @Override\n    public void close() throws IOException {\n        mDelegate.close();\n    }\n\n    @NonNull\n    @Override\n    public Buffer getBuffer() {\n        return mDelegate.getBuffer();\n    }\n\n    @NonNull\n    @Override\n    public BufferedSink write(@NonNull ByteString byteString, int i, int i1) throws IOException {\n        return mDelegate.write(byteString, i, i1);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-okhttp-v4/src/main/java/com/didichuxing/doraemonkit/okhttp_api/OkHttpWrapV4.kt",
    "content": "package com.didichuxing.doraemonkit.okhttp_api\n\nimport okhttp3.*\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\nimport okhttp3.MediaType.Companion.toMediaTypeOrNull\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport okhttp3.internal.userAgent\nimport java.net.URL\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/10/19-14:55\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject OkHttpWrapV4 {\n\n    fun createHttpUrl(url: String?): HttpUrl? {\n        return url?.toHttpUrlOrNull()\n    }\n\n\n    fun toUrl(httpUrl: HttpUrl?): URL? {\n        return httpUrl?.toUrl()\n    }\n\n    fun toResponseBody(response: Response?): ResponseBody? {\n        return response?.body\n    }\n\n\n    fun toHttpQuery(httpUrl: HttpUrl?): String? {\n        return httpUrl?.query\n    }\n\n    fun toRequestHost(httpUrl: HttpUrl?): String? {\n        return httpUrl?.host\n    }\n\n    fun toRequestHost(request: Request): String? {\n        return request.url.host\n    }\n\n    fun toResponseHost(response: Response): String? {\n        return response.request.url.host\n    }\n\n    fun toEncodedPath(httpUrl: HttpUrl?): String? {\n        return httpUrl?.encodedPath\n    }\n\n    fun toScheme(httpUrl: HttpUrl): String {\n        return httpUrl.scheme\n    }\n\n\n    fun toResponseCode(response: Response): Int {\n        return response.code\n    }\n\n\n    fun toMediaType(contentType: String?): MediaType? {\n        return contentType?.toMediaTypeOrNull()\n    }\n\n    fun toRequestBody(content: String?, mediaType: MediaType?): RequestBody? {\n        return content?.toRequestBody(mediaType)\n    }\n\n}"
  },
  {
    "path": "Android/dokit-okhttp-v4/upload.sh",
    "content": "#!/usr/bin/env bash\necho -n  \"please enter bintray userid ->\"\nread  userid_bintray\necho -n  \"please enter bintray apikey ->\"\nread  apikey_bintray\n../gradlew clean build --stacktrace --info   bintrayUpload -PbintrayUser=${userid_bintray} -PbintrayKey=${apikey_bintray} -PdryRun=false"
  },
  {
    "path": "Android/dokit-plugin/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "Android/dokit-plugin/build.gradle",
    "content": "apply plugin: 'kotlin'\napply plugin: 'kotlin-kapt'\napply from: '../upload.gradle'\n\nsourceSets {\n    main {\n        java {\n            srcDirs += []\n        }\n        kotlin {\n            srcDirs += ['src/main/kotlin', 'src/main/java']\n        }\n    }\n\n\n}\n\ncompileKotlin {\n    kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8\n}\n\n\ndependencies {\n    compileOnly gradleApi()\n    compileOnly localGroovy()\n\n    //版本不能太高 否则会跟项目的as版本冲突\n    compileOnly \"com.android.tools.build:gradle:${rootProject.ext.android[\"agp_plugin_verson\"]}\"\n    /* 👇👇👇👇 引用这两个模块 👇👇👇👇 */\n    api \"com.didiglobal.booster:booster-api:${rootProject.ext.android[\"booster_version\"]}\"\n    api \"com.didiglobal.booster:booster-transform-asm:${rootProject.ext.android[\"booster_version\"]}\"\n}\n\n/**\n * 删除指定目录下的文件\n */\ntask deleteSource(type: Delete) {\n    doLast {\n        delete './src/main/kotlin'\n    }\n}\n\n\n\n\nrepositories {\n    google()\n//    jcenter()\n    mavenCentral()\n}\n\n\n\n"
  },
  {
    "path": "Android/dokit-plugin/gradle.properties",
    "content": "ARTIFACT_ID=dokitx-plugin"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitExt.kt",
    "content": "package com.didichuxing.doraemonkit.plugin\n\nimport com.android.build.gradle.api.BaseVariant\nimport com.android.dex.DexFormat\nimport com.android.dx.command.dexer.Main\nimport com.didiglobal.booster.kotlinx.NCPU\nimport com.didiglobal.booster.kotlinx.redirect\nimport com.didiglobal.booster.kotlinx.search\nimport com.didiglobal.booster.kotlinx.touch\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.util.transform\nimport org.apache.commons.compress.archivers.jar.JarArchiveEntry\nimport org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator\nimport org.apache.commons.compress.archivers.zip.ZipArchiveEntry\nimport org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream\nimport org.apache.commons.compress.parallel.InputStreamSupplier\nimport org.objectweb.asm.Opcodes.*\nimport org.objectweb.asm.tree.*\nimport java.io.File\nimport java.io.IOException\nimport java.io.OutputStream\nimport java.util.concurrent.*\nimport java.util.jar.JarFile\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipFile\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/19-18:00\n * 描    述：dokit 对象扩展\n * 修订历史：\n * ================================================\n */\n\nfun MethodNode.isGetSetMethod(): Boolean {\n    var ignoreCount = 0\n    val iterator = instructions.iterator()\n    while (iterator.hasNext()) {\n        val insnNode = iterator.next()\n        val opcode = insnNode.opcode\n        if (-1 == opcode) {\n            continue\n        }\n        if (opcode != GETFIELD && opcode != GETSTATIC && opcode != H_GETFIELD && opcode != H_GETSTATIC && opcode != RETURN && opcode != ARETURN && opcode != DRETURN && opcode != FRETURN && opcode != LRETURN && opcode != IRETURN && opcode != PUTFIELD && opcode != PUTSTATIC && opcode != H_PUTFIELD && opcode != H_PUTSTATIC && opcode > SALOAD) {\n            if (name.equals(\"<init>\") && opcode == INVOKESPECIAL) {\n                ignoreCount++\n                if (ignoreCount > 1) {\n                    return false\n                }\n                continue\n            }\n            return false\n        }\n    }\n    return true\n}\n\nfun MethodNode.isSingleMethod(): Boolean {\n    val iterator = instructions.iterator()\n    while (iterator.hasNext()) {\n        val insnNode = iterator.next()\n        val opcode = insnNode.opcode\n        if (-1 == opcode) {\n            continue\n        } else if (INVOKEVIRTUAL <= opcode && opcode <= INVOKEDYNAMIC) {\n            return false\n        }\n    }\n    return true\n}\n\nfun MethodNode.isEmptyMethod(): Boolean {\n    val iterator = instructions.iterator()\n    while (iterator.hasNext()) {\n        val insnNode = iterator.next()\n        val opcode = insnNode.opcode\n        return if (-1 == opcode) {\n            continue\n        } else {\n            false\n        }\n    }\n    return true\n}\n\nfun MethodNode.isMainMethod(className: String): Boolean {\n    if (this.name == \"main\" && this.desc == \"([Ljava/lang/String;)V\") {\n        \"====isMainMethod====$className  ${this.name}   ${this.desc}   ${this.access}\".println()\n        return true\n    }\n\n    return false\n}\n\n\nfun InsnList.getMethodExitInsnNodes(): Sequence<InsnNode>? {\n    return this.iterator()?.asSequence()?.filterIsInstance(InsnNode::class.java)?.filter {\n        it.opcode == RETURN ||\n                it.opcode == IRETURN ||\n                it.opcode == FRETURN ||\n                it.opcode == ARETURN ||\n                it.opcode == LRETURN ||\n                it.opcode == DRETURN ||\n                it.opcode == ATHROW\n    }\n}\n\nfun BaseVariant.isRelease(): Boolean {\n    if (this.name.contains(\"release\") || this.name.contains(\"Release\")) {\n        return true\n    }\n    return false\n}\n\n\nfun TransformContext.isRelease(): Boolean {\n    if (this.name.contains(\"release\") || this.name.contains(\"Release\")) {\n        return true\n    }\n    return false\n}\n\n\nfun String.println() {\n    if (DoKitExtUtil.dokitLogSwitchOpen()) {\n        println(\"[dokit plugin]===>$this\")\n    }\n}\n\nfun File.lastPath(): String {\n    return this.path.split(\"/\").last()\n}\n\nval MethodInsnNode.ownerClassName: String\n    get() = owner.replace('/', '.')\n\n\nval ClassNode.formatSuperName: String\n    get() = superName.replace('/', '.')\n\ninternal fun File.dex(output: File, api: Int = DexFormat.API_NO_EXTENDED_OPCODES): Int {\n    val args = Main.Arguments().apply {\n        numThreads = NCPU\n        debug = true\n        warnings = true\n        emptyOk = true\n        multiDex = true\n        jarOutput = true\n        optimize = false\n        minSdkVersion = api\n        fileNames = arrayOf(output.canonicalPath)\n        outName = canonicalPath\n    }\n    return try {\n        Main.run(args)\n    } catch (t: Throwable) {\n        t.printStackTrace()\n        -1\n    }\n}\n\n/**\n * Transform this file or directory to the output by the specified transformer\n *\n * @param output The output location\n * @param transformer The byte data transformer\n */\nfun File.dokitTransform(output: File, transformer: (ByteArray) -> ByteArray = { it -> it }) {\n    when {\n        isDirectory -> this.toURI().let { base ->\n            this.search().parallelStream().forEach {\n                it.transform(File(output, base.relativize(it.toURI()).path), transformer)\n            }\n        }\n        isFile -> when (extension.toLowerCase()) {\n            \"jar\" -> JarFile(this).use {\n                it.dokitTransform(output, ::JarArchiveEntry, transformer)\n            }\n            \"class\" -> this.inputStream().use {\n                it.transform(transformer).redirect(output)\n            }\n            else -> this.copyTo(output, true)\n        }\n        else -> throw IOException(\"Unexpected file: ${this.canonicalPath}\")\n    }\n}\n\nfun ZipFile.dokitTransform(\n    output: File,\n    entryFactory: (ZipEntry) -> ZipArchiveEntry = ::ZipArchiveEntry,\n    transformer: (ByteArray) -> ByteArray = { it -> it }\n) = output.touch().outputStream().buffered().use {\n    this.dokitTransform(it, entryFactory, transformer)\n}\n\n\nfun ZipFile.dokitTransform(\n    output: OutputStream,\n    entryFactory: (ZipEntry) -> ZipArchiveEntry = ::ZipArchiveEntry,\n    transformer: (ByteArray) -> ByteArray = { it -> it }\n) {\n    val entries = mutableSetOf<String>()\n    val creator = ParallelScatterZipCreator(\n        ThreadPoolExecutor(\n            NCPU,\n            NCPU,\n            0L,\n            TimeUnit.MILLISECONDS,\n            LinkedBlockingQueue<Runnable>(),\n            Executors.defaultThreadFactory(),\n            RejectedExecutionHandler { runnable, _ ->\n                runnable.run()\n            })\n    )\n    //将jar包里的文件序列化输出\n    entries().asSequence().forEach { entry ->\n        if (!entries.contains(entry.name)) {\n            val zae = entryFactory(entry)\n\n            val stream = InputStreamSupplier {\n                when (entry.name.substringAfterLast('.', \"\")) {\n                    \"class\" -> getInputStream(entry).use { src ->\n                        try {\n                            src.transform(transformer).inputStream()\n                        } catch (e: Throwable) {\n                            System.err.println(\"Broken class: ${this.name}!/${entry.name}\")\n                            getInputStream(entry)\n                        }\n                    }\n                    else -> getInputStream(entry)\n                }\n            }\n\n            creator.addArchiveEntry(zae, stream)\n            entries.add(entry.name)\n        } else {\n            System.err.println(\"Duplicated jar entry: ${this.name}!/${entry.name}\")\n        }\n    }\n    val zip = ZipArchiveOutputStream(output)\n    zip.use { zipStream ->\n        try {\n            creator.writeTo(zipStream)\n            zipStream.close()\n        } catch (e: Exception) {\n            zipStream.close()\n//            e.printStackTrace()\n//            \"e===>${e.message}\".println()\n            System.err.println(\"Duplicated jar entry: ${this.name}!\")\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitExtUtil.kt",
    "content": "package com.didichuxing.doraemonkit.plugin\n\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.extension.SlowMethodExtension\nimport com.didichuxing.doraemonkit.plugin.thirdlib.ThirdLibInfo\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/3/24-14:58\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject DoKitExtUtil {\n\n    var DOKIT_GPS_MOCK_INCLUDE = false\n\n    /**\n     * 三方库版本信息\n     */\n\n    val THIRD_LIB_INFOS = mutableListOf<ThirdLibInfo>()\n\n    /**\n     * dokit 插件开关 字段权限必须为public 否则无法进行赋值\n     */\n    var DOKIT_PLUGIN_SWITCH = true\n    var DOKIT_LOG_SWITCH = false\n\n    /**\n     * 默认函数调用为5级\n     */\n    var STACK_METHOD_LEVEL = 5\n\n    /**\n     * 自定义webview全限定名\n     */\n    var WEBVIEW_CLASS_NAME: String = \"\"\n\n\n    /**\n     * 慢函数默认关闭\n     */\n    var SLOW_METHOD_SWITCH = false\n\n    /**\n     * 三方库信息开关\n     */\n    var THIRD_LIBINFO_SWITCH = true\n\n\n    /**\n     * 慢函数策略 默认为函数调用栈策略\n     */\n    var SLOW_METHOD_STRATEGY = SlowMethodExtension.STRATEGY_STACK\n\n    private val applications: MutableSet<String> = mutableSetOf()\n\n    /**\n     * app的packageName\n     */\n    private var appPackageName: String = \"\"\n\n\n    val slowMethodExt = SlowMethodExtension()\n\n\n    fun dokitPluginSwitchOpen(): Boolean {\n        return DOKIT_PLUGIN_SWITCH\n    }\n\n\n    fun dokitLogSwitchOpen(): Boolean {\n        return DOKIT_LOG_SWITCH\n    }\n\n    fun dokitSlowMethodSwitchOpen(): Boolean {\n        return SLOW_METHOD_SWITCH\n    }\n\n    /**\n     * 初始化\n     *\n     * @param dokitEx dokitExtension\n     * @param appExtension   appExtension\n     */\n    fun init(dokitEx: DoKitExtension) {\n        //设置普通的配置\n        //slowMethodExt.strategy = dokitEx.slowMethod.strategy\n        //slowMethodExt.methodSwitch = dokitEx.slowMethod.methodSwitch\n        /**\n         * ============慢函数普通策略的配置 start==========\n         */\n        slowMethodExt.normalMethod.thresholdTime = dokitEx.slowMethod.normalMethod.thresholdTime\n        //设置慢函数普通策略插装包名\n        slowMethodExt.normalMethod.packageNames.clear()\n        for (packageName in dokitEx.slowMethod.normalMethod.packageNames) {\n            slowMethodExt.normalMethod.packageNames.add(packageName)\n        }\n        //添加默认的包名\n        if (appPackageName.isNotEmpty()) {\n            if (slowMethodExt.normalMethod.packageNames.isEmpty()) {\n                slowMethodExt.normalMethod.packageNames.add(appPackageName)\n            }\n        }\n\n\n        //设置慢函数普通策略插装包名黑名单\n        slowMethodExt.normalMethod.methodBlacklist.clear()\n        for (blackStr in dokitEx.slowMethod.normalMethod.methodBlacklist) {\n            slowMethodExt.normalMethod.methodBlacklist.add(blackStr)\n        }\n        /**\n         * ============慢函数普通策略的配置end==========\n         */\n        /**\n         * ============慢函数stack策略的配置 start==========\n         */\n        slowMethodExt.stackMethod.thresholdTime = dokitEx.slowMethod.stackMethod.thresholdTime\n        slowMethodExt.stackMethod.enterMethods.clear()\n        //添加默认的入口函数\n        for (application in applications) {\n            val attachBaseContextMethodName = \"$application.attachBaseContext\"\n            val onCreateMethodName = \"$application.onCreate\"\n            slowMethodExt.stackMethod.enterMethods.add(attachBaseContextMethodName)\n            slowMethodExt.stackMethod.enterMethods.add(onCreateMethodName)\n        }\n        for (methodName in dokitEx.slowMethod.stackMethod.enterMethods) {\n            slowMethodExt.stackMethod.enterMethods.add(methodName)\n        }\n\n        //设置慢函数调用栈策略插装包名黑名单\n        slowMethodExt.stackMethod.methodBlacklist.clear()\n        for (blackStr in dokitEx.slowMethod.stackMethod.methodBlacklist) {\n            slowMethodExt.stackMethod.methodBlacklist.add(blackStr)\n        }\n\n        /**\n         * ============慢函数stack策略的配置  end==========\n         */\n\n    }\n\n\n    fun setApplications(applications: MutableSet<String>) {\n        if (applications.isEmpty()) {\n            return\n        }\n        this.applications.clear()\n        for (application in applications) {\n            this.applications.add(application)\n        }\n    }\n\n    /**\n     * 设置packageName\n     */\n    fun setAppPackageName(packageName: String) {\n        appPackageName = packageName\n    }\n\n    fun ignorePackageNames(className: String): Boolean {\n        //命中白名单返回false\n        for (packageName in whitePackageNames) {\n            if (className.startsWith(packageName, true)) {\n                return false\n            }\n        }\n\n        //命中黑名单返回true\n        for (packageName in blackPackageNames) {\n            if (className.startsWith(packageName, true)) {\n                return true\n            }\n        }\n\n        return false\n    }\n\n\n    /**\n     * 白名单\n     */\n    private val whitePackageNames = arrayOf(\n        \"com.didichuxing.doraemonkit.DoraemonKit\",\n        \"com.didichuxing.doraemonkit.DoKit\",\n        \"com.didichuxing.doraemonkit.DoKitReal\"\n\n    )\n\n\n    /**\n     * 黑名单\n     */\n    private val blackPackageNames = arrayOf(\n        \"com.didichuxing.doraemonkit.\",\n        \"kotlin.\",\n        \"java.\",\n        \"android.\",\n        \"androidx.\"\n    )\n\n    fun log(\n        tag: String,\n        className: String,\n        methodName: String,\n        access: Int,\n        desc: String,\n        signature: String,\n        thresholdTime: Int\n    ) {\n        if (DOKIT_LOG_SWITCH) {\n            println(\"$tag===matched====>  className===$className   methodName===$methodName   access===$access   desc===$desc   signature===$signature    thresholdTime===$thresholdTime\")\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitPlugin.kt",
    "content": "package com.didichuxing.doraemonkit.plugin\n\nimport com.android.build.gradle.AppExtension\nimport com.android.build.gradle.LibraryExtension\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.extension.SlowMethodExtension\nimport com.didichuxing.doraemonkit.plugin.processor.DoKitPluginConfigProcessor\nimport com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNodeUtil\nimport com.didichuxing.doraemonkit.plugin.thirdlib.ThirdLibVariantProcessor\nimport com.didichuxing.doraemonkit.plugin.transform.*\nimport com.didiglobal.booster.gradle.GTE_V3_4\nimport com.didiglobal.booster.gradle.getAndroid\nimport com.didiglobal.booster.gradle.getProperty\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\n\n\n/**\n * when 也可以用来取代 if-else if链。\n * 如果不提供参数，所有的分支条件都是简单的布尔表达式，而当一个分支的条件为真时则执行该分支：\n */\n\n/**\n * 作用域函数:let、run、with、apply 以及 also\n * 它们的唯一目的是在对象的上下文中执行代码块\n * 由于作用域函数本质上都非常相似，因此了解它们之间的区别很重要。每个作用域函数之间有两个主要区别：\n * 引用上下文对象的方式:\n * 作为 lambda 表达式的接收者（this）或者作为 lambda 表达式的参数（it）\n * run、with 以及 apply 通过关键字 this 引用上下文对象\n * let 及 also 将上下文对象作为 lambda 表达式参数\n *\n * 返回值:\n * apply 及 also 返回上下文对象。\n * let、run 及 with 返回 lambda 表达式结果.\n */\n/**\n * 函数\t对象引用\t   返回值\t    是否是扩展函数\n * let\t it\t     Lambda 表达式结果\t是\n * run\t this\t Lambda 表达式结果\t是\n * run\t  -\t     Lambda 表达式结果\t不是：调用无需上下文对象\n * with\t this\t Lambda 表达式结果\t不是：把上下文对象当做参数\n * apply this\t 上下文对象\t        是\n * also\t it\t     上下文对象\t        是\n */\n\n/**\n *对一个非空（non-null）对象执行 lambda 表达式：let\n *将表达式作为变量引入为局部作用域中：let\n *对象配置：apply\n *对象配置并且计算结果：run\n *在需要表达式的地方运行语句：非扩展的 run\n *附加效果：also\n *一个对象的一组函数调用：with\n */\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/14-10:01\n * 描    述：\n * 修订历史：\n * ================================================\n *\n * DoKit 插件入口\n */\n\nclass DoKitPlugin : Plugin<Project> {\n    override fun apply(project: Project) {\n        //创建指定扩展 并将project 传入构造函数\n        val doKit = project.extensions.create(\"dokit\", DoKitExtension::class.java)\n        \"dokit plugin apply ${doKit}\".println()\n\n        project.gradle.addListener(DoKitTransformTaskExecutionListener(project))\n\n\n        when {\n            project.plugins.hasPlugin(\"com.android.application\") || project.plugins.hasPlugin(\"com.android.dynamic-feature\") -> {\n                if (!isReleaseTask(project)) {\n                    project.getAndroid<AppExtension>().let { androidExt ->\n                        val pluginSwitch = project.getProperty(\"DOKIT_PLUGIN_SWITCH\", true)\n                        val logSwitch = project.getProperty(\"DOKIT_LOG_SWITCH\", false)\n\n                        val slowMethodSwitch = project.getProperty(\"DOKIT_METHOD_SWITCH\", false)\n                        val slowMethodStrategy = project.getProperty(\"DOKIT_METHOD_STRATEGY\", 0)\n                        val methodStackLevel = project.getProperty(\"DOKIT_METHOD_STACK_LEVEL\", 5)\n                        val webViewClassName = project.getProperty(\"DOKIT_WEBVIEW_CLASS_NAME\", \"\")\n                        val thirdLibInfo = project.getProperty(\"DOKIT_THIRD_LIB_SWITCH\", true)\n                        DoKitExtUtil.DOKIT_PLUGIN_SWITCH = pluginSwitch\n                        DoKitExtUtil.DOKIT_LOG_SWITCH = logSwitch\n                        DoKitExtUtil.SLOW_METHOD_SWITCH = slowMethodSwitch\n                        DoKitExtUtil.SLOW_METHOD_STRATEGY = slowMethodStrategy\n                        DoKitExtUtil.STACK_METHOD_LEVEL = methodStackLevel\n                        DoKitExtUtil.WEBVIEW_CLASS_NAME = webViewClassName\n                        DoKitExtUtil.THIRD_LIBINFO_SWITCH = thirdLibInfo\n\n                        \"application module ${project.name} is executing...\".println()\n\n                        MethodStackNodeUtil.METHOD_STACK_KEYS.clear()\n                        if (DoKitExtUtil.DOKIT_PLUGIN_SWITCH) {\n                            //注册transform\n                            androidExt.registerTransform(commNewInstance(project))\n                            if (slowMethodSwitch && slowMethodStrategy == SlowMethodExtension.STRATEGY_STACK) {\n                                MethodStackNodeUtil.METHOD_STACK_KEYS.add(0, mutableSetOf<String>())\n                                val methodStackRange = 1 until methodStackLevel\n                                if (methodStackLevel > 1) {\n                                    for (index in methodStackRange) {\n                                        MethodStackNodeUtil.METHOD_STACK_KEYS.add(\n                                            index,\n                                            mutableSetOf<String>()\n                                        )\n                                        androidExt.registerTransform(\n                                            dependNewInstance(project, index)\n                                        )\n                                    }\n                                }\n                            }\n                        }\n\n                        /**\n                         * 所有项目的build.gradle执行完毕\n                         * wiki:https://juejin.im/post/6844903607679057934\n                         *\n                         * **/\n                        project.gradle.projectsEvaluated {\n                            \"===projectsEvaluated===\".println()\n                            androidExt.applicationVariants.forEach { variant ->\n                                ThirdLibVariantProcessor(project).process(variant)\n                                DoKitPluginConfigProcessor(project).process(variant)\n                            }\n\n                        }\n\n\n                        //task依赖关系图建立完毕\n                        project.gradle.taskGraph.whenReady {\n                            \"===taskGraph.whenReady===\".println()\n                            \"dokit config :: ${doKit}\".println()\n                        }\n\n                    }\n                }\n\n            }\n\n            project.plugins.hasPlugin(\"com.android.library\") -> {\n                if (!isReleaseTask(project)) {\n                    project.getAndroid<LibraryExtension>().let { libraryExt ->\n                        \"library module ${project.name} is executing...\".println()\n                        if (DoKitExtUtil.DOKIT_PLUGIN_SWITCH) {\n                            libraryExt.registerTransform(commNewInstance(project))\n                        }\n                        project.afterEvaluate {\n                            libraryExt.libraryVariants.forEach { variant ->\n                                ThirdLibVariantProcessor(project).process(variant)\n                                DoKitPluginConfigProcessor(project).process(variant)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    private fun isReleaseTask(project: Project): Boolean {\n        return project.gradle.startParameter.taskNames.any {\n            it.contains(\"release\") || it.contains(\"Release\")\n        }\n    }\n\n    private fun commNewInstance(project: Project): DoKitBaseTransform = when {\n        GTE_V3_4 -> DoKitCommonTransformV34(project)\n        else -> DoKitCommonTransform(project)\n    }\n\n    private fun dependNewInstance(project: Project, index: Int): DoKitBaseTransform = when {\n        GTE_V3_4 -> DoKitDependTransformV34(project, index)\n        else -> DoKitDependTransform(project, index)\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitPluginUtil.kt",
    "content": "package com.didichuxing.doraemonkit.plugin\n\nimport java.io.File\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/10/20-15:13\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject DoKitPluginUtil {\n    const val BYTE = 1\n    const val KB = 1024\n    const val MB = 1048576\n    const val GB = 1073741824\n\n    fun fileSize(file: File, precision: Int): String? {\n        if (!file.isFile) {\n            return \"0kb\"\n        }\n        if (file.isDirectory) {\n            return \"0kb\"\n        }\n        val fileLength = file.length()\n        return byte2FitMemorySize(fileLength, precision)\n    }\n\n    private fun byte2FitMemorySize(byteSize: Long, precision: Int): String? {\n        require(precision >= 0) { \"precision shouldn't be less than zero!\" }\n        return if (byteSize < 0) {\n            throw IllegalArgumentException(\"byteSize shouldn't be less than zero!\")\n        } else if (byteSize < KB) {\n            String.format(\"%.\" + precision + \"fB\", byteSize.toDouble())\n        } else if (byteSize < MB) {\n            String.format(\"%.\" + precision + \"fKB\", byteSize.toDouble() / KB)\n        } else if (byteSize < GB) {\n            String.format(\"%.\" + precision + \"fMB\", byteSize.toDouble() / MB)\n        } else {\n            String.format(\"%.\" + precision + \"fGB\", byteSize.toDouble() / GB)\n        }\n    }\n\n    private fun getNextChunk(version: String, n: Int, p: Int): Pair<Int, Int> {\n        // if pointer is set to the end of string\n        // return 0\n        if (p > n - 1) {\n            return Pair(0, p)\n        }\n        // find the end of chunk\n        var i = 0\n        var pEnd = p\n        while (pEnd < n && version[pEnd].equals(\".\")) {\n            ++pEnd\n        }\n        // retrieve the chunk\n        i = if (pEnd != n - 1) {\n            version.substring(p, pEnd).toInt()\n        } else {\n            version.substring(p, n).toInt()\n        }\n        // find the beginning of next chunk\n        val q = pEnd + 1\n\n        return Pair(i, q)\n\n    }\n\n    /**\n     * 比较version的大小\n     */\n    fun compareVersion(version1: String, version2: String): Int {\n        var p1 = 0\n        var p2 = 0\n        val n1 = version1.length\n        val n2 = version2.length\n        var i1: Int\n        var i2: Int\n        var pair: Pair<Int, Int>\n        while (p1 < n1 || p2 < n2) {\n            pair = getNextChunk(version1, n1, p1)\n            i1 = pair.first\n            p1 = pair.second\n\n            pair = getNextChunk(version2, n2, p2)\n            i2 = pair.first\n            p2 = pair.second\n            if (i1 != i2) {\n                return if (i1 > i2) {\n                    1\n                } else {\n                    -1\n                }\n            }\n        }\n        return 0\n    }\n}"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitTransformTaskExecutionListener.kt",
    "content": "package com.didichuxing.doraemonkit.plugin\n\nimport com.android.build.gradle.internal.pipeline.TransformTask\nimport com.didiglobal.booster.kotlinx.call\nimport com.didiglobal.booster.kotlinx.get\nimport org.gradle.api.Project\nimport org.gradle.api.Task\nimport org.gradle.api.execution.TaskExecutionAdapter\n\n/**\n * @author neighbWang\n */\nclass DoKitTransformTaskExecutionListener(private val project: Project) : TaskExecutionAdapter() {\n\n    override fun beforeExecute(task: Task) {\n        task.takeIf {\n            it.project == project && it is TransformTask && it.transform.scopes.isNotEmpty()\n        }?.run {\n            task[\"outputStream\"]?.call<Unit>(\"init\")\n        }\n    }\n\n}"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/BigImageExtension.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.extension\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 6:07 下午\n * @Description 用一句话说明文件功能\n */\n\nopen class BigImageExtension(\n    var glide: Boolean = true,\n    var picasso: Boolean = true,\n    var fresco: Boolean = true,\n    var imageLoader: Boolean = true,\n    var coil: Boolean = true\n) {\n\n\n    fun glide(boolean: Boolean) {\n        glide = boolean\n    }\n\n    fun picasso(boolean: Boolean) {\n        picasso = boolean\n    }\n\n    fun fresco(boolean: Boolean) {\n        fresco = boolean\n    }\n\n    fun imageLoader(boolean: Boolean) {\n        imageLoader = boolean\n    }\n\n    fun coil(boolean: Boolean) {\n        coil = boolean\n    }\n\n\n    override fun toString(): String {\n        return \"BigImageExtension(glide=$glide, picasso=$picasso, Fresco=$fresco, imageLoader=$imageLoader, coil=$coil)\"\n    }\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/DoKitExtension.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.extension\n\nimport org.gradle.api.Action\n\n/**\n * Created by jint on 07/10/2018.\n *\n * DoKit plugin 配置实例  @see DoKitExtension\n *\n * 支持DSL语法可以在插件中直接配置\n */\nopen class DoKitExtension(\n    var pluginEnable: Boolean = true,\n    var logEnable: Boolean = true,\n    var thirdLibEnable: Boolean = true,\n\n    var gpsEnable: Boolean = true,\n    var gps: GpsExtension = GpsExtension(),\n\n    var networkEnable: Boolean = true,\n    var network: NetworkExtension = NetworkExtension(),\n\n    var bigImageEnable: Boolean = true,\n    var bigImage: BigImageExtension = BigImageExtension(),\n\n    var webViewEnable: Boolean = true,\n    var webView: WebViewExtension = WebViewExtension(),\n\n    var slowMethodEnable: Boolean = true,\n    var slowMethod: SlowMethodExtension = SlowMethodExtension()\n) {\n\n\n    /**\n     * gps 支持 DSL 语法\n     *\n     * @param action\n     */\n    fun gps(action: Action<GpsExtension>) {\n        action.execute(gps)\n    }\n\n    /**\n     * network 支持 DSL 语法\n     *\n     * @param action\n     */\n    fun network(action: Action<NetworkExtension>) {\n        action.execute(network)\n    }\n\n    /**\n     * bigImage 支持 DSL 语法\n     *\n     * @param action\n     */\n    fun bigImage(action: Action<BigImageExtension>) {\n        action.execute(bigImage)\n    }\n    \n    fun webView(action: Action<WebViewExtension>) {\n        action.execute(webView)\n    }\n\n    /**\n     * 让slowMethod 支持 DSL 语法\n     *\n     * @param action\n     */\n    fun slowMethod(action: Action<SlowMethodExtension>) {\n        action.execute(slowMethod)\n    }\n\n\n\n\n    override fun toString(): String {\n        return \"DoKitExtension(pluginEnable=$pluginEnable, logEnable=$logEnable, thirdLibEnable=$thirdLibEnable, gpsEnable=$gpsEnable, gps=$gps, networkEnable=$networkEnable, network=$network, bigImageEnable=$bigImageEnable, bigImage=$bigImage, webViewEnable=$webViewEnable, webView=$webView, slowMethodEnable=$slowMethodEnable, slowMethod=$slowMethod)\"\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/GpsExtension.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.extension\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 5:36 下午\n * @Description 用一句话说明文件功能\n * @see DoKitExtension\n */\n\nopen class GpsExtension(\n    var local: Boolean = true,\n    var baidu: Boolean = true,\n    var tencent: Boolean = true,\n    var amap: Boolean = true,\n    var didi: Boolean = true\n) {\n\n\n    fun local(boolean: Boolean) {\n        local = boolean\n    }\n\n    fun baidu(boolean: Boolean) {\n        baidu = boolean\n    }\n\n    fun tencent(boolean: Boolean) {\n        tencent = boolean\n    }\n\n    fun amap(boolean: Boolean) {\n        amap = boolean\n    }\n\n    fun didi(boolean: Boolean) {\n        didi = boolean\n    }\n\n    override fun toString(): String {\n        return \"GpsExtension(local=$local, baidu=$baidu, tencent=$tencent, didi=$didi)\"\n    }\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/NetworkExtension.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.extension\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 5:36 下午\n * @Description 用一句话说明文件功能\n */\n\nopen class NetworkExtension(\n    var okHttp: Boolean = true,\n    var urlConnect: Boolean = true,\n    var didiHttp: Boolean = true,\n    var didiSocket: Boolean = true,\n    var didiDjSocket: Boolean = true\n) {\n\n\n    fun okHttp(boolean: Boolean) {\n        okHttp = boolean\n    }\n\n    fun urlConnect(boolean: Boolean) {\n        urlConnect = boolean\n    }\n\n    fun didiHttp(boolean: Boolean) {\n        didiHttp = boolean\n    }\n\n    fun didiSocket(boolean: Boolean) {\n        didiSocket = boolean\n    }\n\n    fun didiDjSocket(boolean: Boolean) {\n        didiDjSocket = boolean\n    }\n\n    override fun toString(): String {\n        return \"NetworkExtension(okHttp=$okHttp, urlConnect=$urlConnect, didiHttp=$didiHttp, didiSocket=$didiSocket, didiDjSocket=$didiDjSocket)\"\n    }\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/SlowMethodExtension.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.extension\n\nimport groovy.lang.Closure\nimport org.gradle.util.ConfigureUtil\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/4/28-14:56\n * 描    述：\n * 修订历史：\n * ================================================\n */\n\nopen class SlowMethodExtension(\n    //0:打印函数调用栈  1:普通模式 运行时打印某个函数的耗时 全局业务代码函数插入\n    @Deprecated(\"已弃用,请在项目根目录的gradle.properties中通过DOKIT_METHOD_STRATEGY=0|1 来控制\")\n    var strategy: Int = STRATEGY_STACK,\n    //函数功能开关\n    @Deprecated(\"已弃用,请在项目根目录的gradle.properties中通过DoKit_METHOD_SWITCH=true|false 来控制\")\n    var methodSwitch: Boolean = false,\n    //函数调用栈模式\n    var stackMethod: StackMethodExt = StackMethodExt(),\n    //普通模式\n    var normalMethod: NormalMethodExt = NormalMethodExt()\n) {\n\n\n    /**\n     * 函数功能开关\n     */\n    fun strategy(strategy: Int) {\n        this.strategy = strategy\n    }\n\n    fun methodSwitch(methodSwitch: Boolean) {\n        this.methodSwitch = methodSwitch\n    }\n\n    fun stackMethod(closure: Closure<StackMethodExt?>?) {\n        ConfigureUtil.configure(closure, stackMethod)\n    }\n\n    fun normalMethod(closure: Closure<NormalMethodExt?>?) {\n        ConfigureUtil.configure(closure, normalMethod)\n    }\n\n    class StackMethodExt(\n        //默认阈值为5ms\n        var thresholdTime: Int = 5,\n        //入口函集合\n        var enterMethods: MutableSet<String> = mutableSetOf(),\n        //插桩黑名单\n        var methodBlacklist: MutableSet<String> = mutableSetOf()\n    ) {\n\n        /**\n         * 默认值为5ms\n         */\n        fun thresholdTime(thresholdTime: Int) {\n            this.thresholdTime = thresholdTime\n        }\n\n\n        fun enterMethods(enterMethods: MutableSet<String>) {\n            this.enterMethods = enterMethods\n        }\n\n        fun methodBlacklist(methodBlacklist: MutableSet<String>) {\n            this.methodBlacklist = methodBlacklist\n        }\n\n        override fun toString(): String {\n            return \"StackMethodExt(thresholdTime=$thresholdTime, enterMethods=$enterMethods, methodBlacklist=$methodBlacklist)\"\n        }\n\n\n    }\n\n    class NormalMethodExt(\n        //默认阈值为500ms\n        var thresholdTime: Int = 500,\n        //普通函数的插装包名集合\n        var packageNames: MutableSet<String> = mutableSetOf(),\n        //插桩黑名单\n        var methodBlacklist: MutableSet<String> = mutableSetOf()\n    ) {\n        /**\n         * 默认值为500ms\n         */\n\n        fun thresholdTime(thresholdTime: Int) {\n            this.thresholdTime = thresholdTime\n        }\n\n        fun packageNames(packageNames: MutableSet<String>) {\n            this.packageNames = packageNames\n        }\n\n        fun methodBlacklist(methodBlacklist: MutableSet<String>) {\n            this.methodBlacklist = methodBlacklist\n        }\n\n        override fun toString(): String {\n            return \"NormalMethodExt{\" +\n                    \"thresholdTime=\" + thresholdTime +\n                    \", packageNames=\" + packageNames +\n                    \", methodBlacklist=\" + methodBlacklist +\n                    '}'\n        }\n    }\n\n    override fun toString(): String {\n        return \"SlowMethodExt{\" +\n                \"strategy=\" + strategy +\n                \", methodSwitch=\" + methodSwitch +\n                \", stackMethod=\" + stackMethod +\n                \", normalMethod=\" + normalMethod +\n                '}'\n    }\n\n    companion object {\n        const val STRATEGY_STACK = 0\n        const val STRATEGY_NORMAL = 1\n    }\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/WebViewExtension.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.extension\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 6:10 下午\n * @Description 用一句话说明文件功能\n */\n\nopen class WebViewExtension(\n    var network: Boolean = true,\n    var dokitWeb: Boolean = false,\n    var vConsole: Boolean = false\n) {\n\n    fun network(boolean: Boolean) {\n        network = boolean\n    }\n\n    fun dokitWeb(boolean: Boolean) {\n        dokitWeb = boolean\n    }\n\n    fun vConsole(boolean: Boolean) {\n        vConsole = boolean\n    }\n\n    override fun toString(): String {\n        return \"WebViewExtension(network=$network, dokitWeb=$dokitWeb, vConsole=$vConsole)\"\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/processor/DoKitComponentHandler.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.processor\n\nimport org.xml.sax.Attributes\nimport org.xml.sax.helpers.DefaultHandler\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：3/2/21-17:26\n * 描    述：\n * 修订历史：\n * ================================================\n */\nconst val ATTR_NAME = \"android:name\"\n\nconst val MANIFEST_ATTR_NAME = \"package\"\n\nclass DoKitComponentHandler : DefaultHandler() {\n    var appPackageName: String = \"\"\n    val applications = mutableSetOf<String>()\n    val activities = mutableSetOf<String>()\n    val services = mutableSetOf<String>()\n    val providers = mutableSetOf<String>()\n    val receivers = mutableSetOf<String>()\n\n    override fun startElement(\n        uri: String,\n        localName: String,\n        qName: String,\n        attributes: Attributes\n    ) {\n        val name: String = attributes.getValue(ATTR_NAME) ?: \"\"\n\n        val packageName: String = attributes.getValue(MANIFEST_ATTR_NAME) ?: \"\"\n\n        when (qName) {\n            \"manifest\" -> {\n                appPackageName = packageName\n            }\n            \"application\" -> {\n                applications.add(name)\n            }\n            \"activity\" -> {\n                activities.add(name)\n            }\n            \"service\" -> {\n                services.add(name)\n            }\n            \"provider\" -> {\n                providers.add(name)\n            }\n            \"receiver\" -> {\n                receivers.add(name)\n            }\n\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/processor/DoKitPluginConfigProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.processor\n\nimport com.android.build.gradle.AppExtension\nimport com.android.build.gradle.api.ApplicationVariant\nimport com.android.build.gradle.api.BaseVariant\nimport com.didichuxing.doraemonkit.plugin.*\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.thirdlib.ThirdLibInfo\nimport com.didiglobal.booster.gradle.dependencies\nimport com.didiglobal.booster.gradle.getAndroid\nimport com.didiglobal.booster.gradle.mergedManifests\nimport com.didiglobal.booster.gradle.project\nimport com.didiglobal.booster.task.spi.VariantProcessor\nimport org.gradle.api.Project\nimport org.gradle.api.artifacts.result.ResolvedArtifactResult\nimport javax.xml.parsers.SAXParserFactory\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/15-11:28\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitPluginConfigProcessor(val project: Project) : VariantProcessor {\n    override fun process(variant: BaseVariant) {\n        if (!DoKitExtUtil.DOKIT_PLUGIN_SWITCH) {\n            return\n        }\n\n        if (variant.isRelease()) {\n            return\n        }\n\n        //统计三方库信息\n        if (DoKitExtUtil.THIRD_LIBINFO_SWITCH) {\n            //遍历三方库\n            val dependencies = variant.dependencies\n            for (artifactResult: ResolvedArtifactResult in dependencies) {\n                val variants = artifactResult.variant.displayName.split(\" \")\n                var thirdLibInfo: ThirdLibInfo? = null\n                if (variants.size == 3) {\n                    thirdLibInfo = ThirdLibInfo(variants[0], artifactResult.file.length())\n                    checkConfig(thirdLibInfo.variant)\n                } else if (variants.size == 4) {\n                    thirdLibInfo = ThirdLibInfo(\"porject ${variants[1]}\", artifactResult.file.length())\n                    checkConfig(thirdLibInfo.variant)\n                }\n            }\n        }\n        //查找AndroidManifest.xml 文件 并处理\n        processApplicationVariant(variant)\n\n    }\n\n\n    private fun checkConfig(variant: String) {\n        if (variant.contains(\"dokitx-gps-mock\") || variant.contains(\"dokit-gps-mock\")) {\n            DoKitExtUtil.DOKIT_GPS_MOCK_INCLUDE = true;\n        }\n    }\n\n\n    private fun processApplicationVariant(variant: BaseVariant) {\n        //查找application module下的配置\n        if (variant is ApplicationVariant) {\n\n            project.tasks.find {\n                //\"===task Name is ${it.name}\".println()\n                it.name == \"processDebugManifest\"\n            }?.let { transformTask ->\n                transformTask.doLast {\n                    \"===processDebugManifest task has executed===\".println()\n                    //查找AndroidManifest.xml 文件路径\n                    variant.mergedManifests.forEach { manifest ->\n                        val parser = SAXParserFactory.newInstance().newSAXParser()\n                        val handler = DoKitComponentHandler()\n                        \"App Manifest path====>$manifest\".println()\n                        try {\n                            parser.parse(manifest, handler)\n                        } catch (e: Exception) {\n                            e.printStackTrace()\n                            \"===processDebugManifest task error. ${manifest.absoluteFile}\".println()\n                        }\n                        \"App PackageName is====>${handler.appPackageName}\".println()\n                        \"App Application path====>${handler.applications}\".println()\n                        DoKitExtUtil.setAppPackageName(handler.appPackageName)\n                        DoKitExtUtil.setApplications(handler.applications)\n                    }\n\n                    //读取插件配置\n                    variant.project.getAndroid<AppExtension>().let { appExt ->\n                        //查找Application路径\n                        val doKitExt = variant.project.extensions.getByType(DoKitExtension::class.java)\n                        DoKitExtUtil.init(doKitExt)\n                    }\n                }\n            }\n\n        } else {\n            \"${variant.project.name}-不建议在Library Module下引入dokit插件\".println()\n        }\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/stack_method/MethodStackNode.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.stack_method\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/20-16:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class MethodStackNode(var level: Int,\n                           var className: String,\n                           var methodName: String,\n                           var desc: String,\n                           var parentClassName: String,\n                           var parentMethodName: String,\n                           var parentDesc: String)"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/stack_method/MethodStackNodeUtil.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.stack_method\n\nimport java.util.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/20-16:58\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject MethodStackNodeUtil {\n\n\n    val METHOD_STACK_KEYS: MutableList<MutableSet<String>> by lazy {\n        Collections.synchronizedList(mutableListOf<MutableSet<String>>())\n    }\n\n\n    fun addMethodStackNode(level: Int, methodStackNode: MethodStackNode) {\n        val key = \"${methodStackNode.className}&${methodStackNode.methodName}&${methodStackNode.desc}\"\n        METHOD_STACK_KEYS[level].add(key)\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/thirdlib/ThirdLibInfo.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.thirdlib\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/10/20-14:50\n * 描    述：\n * 修订历史：\n * ================================================\n */\n\ndata class ThirdLibInfo(val variant: String, val fileSize: Long)\n\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/thirdlib/ThirdLibVariantProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.thirdlib\n\nimport com.android.build.gradle.api.BaseVariant\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.isRelease\nimport com.didiglobal.booster.gradle.dependencies\nimport com.didiglobal.booster.task.spi.VariantProcessor\nimport org.gradle.api.Project\nimport org.gradle.api.artifacts.result.ResolvedArtifactResult\n\n\n/**\n * didi Create on 2023/3/22 .\n *\n * Copyright (c) 2023/3/22 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/22 2:57 下午\n * @Description 用一句话说明文件功能\n */\n\nclass ThirdLibVariantProcessor(val project: Project) : VariantProcessor {\n\n    override fun process(variant: BaseVariant) {\n        if (!DoKitExtUtil.DOKIT_PLUGIN_SWITCH) {\n            return\n        }\n\n        if (variant.isRelease()) {\n            return\n        }\n\n        val dokit = project.extensions.getByType(DoKitExtension::class.java)\n\n        //统计三方库信息\n        if (dokit.thirdLibEnable && DoKitExtUtil.THIRD_LIBINFO_SWITCH) {\n            //遍历三方库\n            val dependencies = variant.dependencies\n            DoKitExtUtil.THIRD_LIB_INFOS.clear()\n            for (artifactResult: ResolvedArtifactResult in dependencies) {\n                val variants = artifactResult.variant.displayName.split(\" \")\n                var thirdLibInfo: ThirdLibInfo? = null\n                if (variants.size == 3) {\n                    thirdLibInfo = ThirdLibInfo(variants[0], artifactResult.file.length())\n                    DoKitExtUtil.THIRD_LIB_INFOS.add(thirdLibInfo)\n                } else if (variants.size == 4) {\n                    thirdLibInfo = ThirdLibInfo(\"porject ${variants[1]}\", artifactResult.file.length())\n                    DoKitExtUtil.THIRD_LIB_INFOS.add(thirdLibInfo)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitBaseTransform.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform\n\nimport com.android.build.api.transform.QualifiedContent\nimport com.android.build.api.transform.Transform\nimport com.android.build.api.transform.TransformInvocation\nimport com.android.build.gradle.BaseExtension\nimport com.android.build.gradle.internal.pipeline.TransformManager\nimport com.didiglobal.booster.gradle.*\nimport com.didiglobal.booster.transform.AbstractKlassPool\nimport com.didiglobal.booster.transform.Transformer\nimport org.gradle.api.Project\n\n/**\n * Represents the transform base\n * DoKitCommTransform 作用于 CommTransformer、BigImgTransformer、UrlConnectionTransformer、GlobalSlowMethodTransformer\n * @author johnsonlee\n */\n\nopen class DoKitBaseTransform protected constructor(val project: Project) : Transform() {\n\n    /*transformers\n     * Preload transformers as List to fix NoSuchElementException caused by ServiceLoader in parallel mode\n     * booster 的默认出炉逻辑 DoKit已重写自处理\n     */\n    open val transformers = listOf<Transformer>()\n\n    internal val verifyEnabled = project.getProperty(OPT_TRANSFORM_VERIFY, false)\n\n    private val android: BaseExtension = project.getAndroid()\n\n    private lateinit var androidKlassPool: AbstractKlassPool\n\n    init {\n        project.afterEvaluate {\n            androidKlassPool = object : AbstractKlassPool(android.bootClasspath) {}\n        }\n    }\n\n    val bootKlassPool: AbstractKlassPool\n        get() = androidKlassPool\n\n    override fun getName() = this.javaClass.simpleName\n\n    override fun isIncremental() = !verifyEnabled\n\n    override fun isCacheable() = !verifyEnabled\n\n    override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> =\n        TransformManager.CONTENT_CLASS\n\n    override fun getScopes(): MutableSet<in QualifiedContent.Scope> = when {\n        transformers.isEmpty() -> mutableSetOf()\n        project.plugins.hasPlugin(\"com.android.library\") -> SCOPE_PROJECT\n        project.plugins.hasPlugin(\"com.android.application\") -> SCOPE_FULL_PROJECT\n        project.plugins.hasPlugin(\"com.android.dynamic-feature\") -> SCOPE_FULL_WITH_FEATURES\n        else -> TODO(\"Not an Android project\")\n    }\n\n    override fun getReferencedScopes(): MutableSet<in QualifiedContent.Scope> = when {\n        transformers.isEmpty() -> when {\n            project.plugins.hasPlugin(\"com.android.library\") -> SCOPE_PROJECT\n            project.plugins.hasPlugin(\"com.android.application\") -> SCOPE_FULL_PROJECT\n            project.plugins.hasPlugin(\"com.android.dynamic-feature\") -> SCOPE_FULL_WITH_FEATURES\n            else -> TODO(\"Not an Android project\")\n        }\n        else -> super.getReferencedScopes()\n    }\n\n    final override fun transform(invocation: TransformInvocation) {\n        DoKitTransformInvocation(invocation, this).apply {\n            if (isIncremental) {\n                doIncrementalTransform()\n            } else {\n                outputProvider?.deleteAll()\n                doFullTransform()\n            }\n        }\n    }\n\n\n}\n\n/**\n * The option for transform outputs verifying, default is false\n */\nprivate const val OPT_TRANSFORM_VERIFY = \"dokit.transform.verify\"\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitCommonTransform.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform\n\nimport com.didichuxing.doraemonkit.plugin.transform.asmtransform.DoKitAsmTransformer\nimport com.didichuxing.doraemonkit.plugin.transform.classtransform.*\nimport com.didiglobal.booster.transform.Transformer\nimport org.gradle.api.Project\n\n/**\n * Represents the transform base\n * DoKitCommTransform 作用于 CommTransformer、BigImgTransformer、UrlConnectionTransformer、GlobalSlowMethodTransformer、EnterMethodStackTransformer\n * @author johnsonlee\n */\nclass DoKitCommonTransform(androidProject: Project) : DoKitBaseTransform(androidProject) {\n\n    override val transformers = listOf<Transformer>(\n        DoKitAsmTransformer(\n            listOf(\n                CommClassTransformer(),\n                ThirdLibsClassTransformer(),\n                //网络\n                Okhttp3ClassTransformer(),\n                UrlConnectionTransformer(),\n                WebViewClassTransformer(),\n\n                //地图GPS\n                GPSClassTransformer(),\n                GPSAMapClassTransformer(),\n                GPSBDClassTransformer(),\n                GPSTencentClassTransformer(),\n                //大图检测\n                BigImgClassTransformer(),\n                //全局慢函数\n                GSMClassTransformer(),\n                //入口慢函数\n                EnterMSClassTransformer()\n            )\n        )\n    )\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitCommonTransformV34.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform\n\nimport com.android.build.api.variant.VariantInfo\nimport com.didichuxing.doraemonkit.plugin.transform.asmtransform.DoKitAsmTransformer\nimport com.didichuxing.doraemonkit.plugin.transform.classtransform.*\nimport com.didiglobal.booster.transform.Transformer\nimport org.gradle.api.Project\n\nclass DoKitCommonTransformV34(project: Project) : DoKitBaseTransform(project) {\n\n\n    override val transformers = listOf<Transformer>(\n        DoKitAsmTransformer(\n            listOf(\n                CommClassTransformer(),\n                ThirdLibsClassTransformer(),\n                //网络\n                Okhttp3ClassTransformer(),\n                UrlConnectionTransformer(),\n                WebViewClassTransformer(),\n\n                //地图GPS\n                GPSClassTransformer(),\n                GPSAMapClassTransformer(),\n                GPSBDClassTransformer(),\n                GPSTencentClassTransformer(),\n                //大图检测\n                BigImgClassTransformer(),\n                //全局慢函数\n                GSMClassTransformer(),\n                //入口慢函数\n                EnterMSClassTransformer()\n            )\n        )\n    )\n\n    @Suppress(\"UnstableApiUsage\")\n    override fun applyToVariant(variant: VariantInfo): Boolean {\n        return variant.buildTypeEnabled || (variant.flavorNames.isNotEmpty() && variant.fullVariantEnabled)\n    }\n\n    @Suppress(\"UnstableApiUsage\")\n    private val VariantInfo.fullVariantEnabled: Boolean\n        get() = project.findProperty(\"booster.transform.${fullVariantName}.enabled\")?.toString()?.toBoolean() ?: true\n\n    @Suppress(\"UnstableApiUsage\")\n    private val VariantInfo.buildTypeEnabled: Boolean\n        get() = project.findProperty(\"booster.transform.${buildTypeName}.enabled\")?.toString()?.toBoolean() ?: true\n\n}\n\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitDependTransform.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform\n\nimport com.didichuxing.doraemonkit.plugin.transform.asmtransform.DoKitAsmTransformer\nimport com.didichuxing.doraemonkit.plugin.transform.classtransform.MSDClassTransformer\nimport com.didiglobal.booster.transform.Transformer\nimport org.gradle.api.Project\n\n/**\n * Represents the transform base\n * DoKitCommTransform 作用于 CommTransformer、BigImgTransformer、UrlConnectionTransformer、GlobalSlowMethodTransformer\n * @author johnsonlee\n */\nopen class DoKitDependTransform(androidProject: Project, private val level: Int) :\n    DoKitBaseTransform(androidProject) {\n\n    override val transformers = listOf<Transformer>(DoKitAsmTransformer(listOf(MSDClassTransformer(level))))\n\n    override fun getName(): String {\n        return \"${this.javaClass.simpleName}_$level\"\n    }\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitDependTransformV34.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform\n\nimport com.android.build.api.variant.VariantInfo\nimport com.didichuxing.doraemonkit.plugin.transform.asmtransform.DoKitAsmTransformer\nimport com.didichuxing.doraemonkit.plugin.transform.classtransform.MSDClassTransformer\nimport com.didiglobal.booster.transform.Transformer\nimport org.gradle.api.Project\n\n/**\n * Represents the transform base\n * DoKitCommTransform 作用于 CommTransformer、BigImgTransformer、UrlConnectionTransformer、GlobalSlowMethodTransformer\n * @author johnsonlee\n */\nopen class DoKitDependTransformV34(androidProject: Project, private val level: Int) :\n    DoKitBaseTransform(androidProject) {\n\n    override val transformers = listOf<Transformer>(DoKitAsmTransformer(listOf(MSDClassTransformer(level))))\n\n    override fun getName(): String {\n        return \"${this.javaClass.simpleName}_$level\"\n    }\n\n    @Suppress(\"UnstableApiUsage\")\n    override fun applyToVariant(variant: VariantInfo): Boolean {\n        return variant.buildTypeEnabled || (variant.flavorNames.isNotEmpty() && variant.fullVariantEnabled)\n    }\n\n    @Suppress(\"UnstableApiUsage\")\n    private val VariantInfo.fullVariantEnabled: Boolean\n        get() = project.findProperty(\"booster.transform.${fullVariantName}.enabled\")?.toString()\n            ?.toBoolean() ?: true\n\n    @Suppress(\"UnstableApiUsage\")\n    private val VariantInfo.buildTypeEnabled: Boolean\n        get() = project.findProperty(\"booster.transform.${buildTypeName}.enabled\")?.toString()\n            ?.toBoolean() ?: true\n\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitTransformContext.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform\n\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport org.gradle.api.Project\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 6:31 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface DoKitTransformContext {\n\n    fun project(): Project\n\n    fun dokitExtension(): DoKitExtension\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitTransformInvocation.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform\n\nimport com.android.build.api.transform.*\nimport com.android.build.api.transform.Status.*\nimport com.android.dex.DexFormat\nimport com.didichuxing.doraemonkit.plugin.dex\nimport com.didichuxing.doraemonkit.plugin.dokitTransform\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.gradle.*\nimport com.didiglobal.booster.kotlinx.NCPU\nimport com.didiglobal.booster.kotlinx.file\nimport com.didiglobal.booster.kotlinx.green\nimport com.didiglobal.booster.kotlinx.red\nimport com.didiglobal.booster.transform.*\nimport com.didiglobal.booster.transform.util.transform\nimport org.gradle.api.Project\nimport java.io.File\nimport java.net.URI\nimport java.util.concurrent.*\n\n/**\n * Represents a delegate of TransformInvocation\n *\n * @author johnsonlee\n */\ninternal  class DoKitTransformInvocation(\n    private val delegate: TransformInvocation,\n    internal val transform: DoKitBaseTransform\n) : TransformInvocation by delegate, TransformContext, ArtifactManager,DoKitTransformContext {\n\n    private val project = transform.project\n\n    private val outputs = CopyOnWriteArrayList<File>()\n\n    override val name: String = delegate.context.variantName\n\n    override val projectDir: File = project.projectDir\n\n    override val buildDir: File = project.buildDir\n\n    override val temporaryDir: File = delegate.context.temporaryDir\n\n    override val reportsDir: File = File(buildDir, \"reports\").also { it.mkdirs() }\n\n    override val bootClasspath = delegate.bootClasspath\n\n    override val compileClasspath = delegate.compileClasspath\n\n    override val runtimeClasspath = delegate.runtimeClasspath\n\n    override val artifacts = this\n\n    override val dependencies: Collection<String> by lazy {\n        ResolvedArtifactResults(variant).map {\n            it.id.displayName\n        }\n    }\n\n    override val klassPool: AbstractKlassPool =\n        object : AbstractKlassPool(compileClasspath, transform.bootKlassPool) {}\n\n    override val applicationId = delegate.applicationId\n\n    override val originalApplicationId = delegate.originalApplicationId\n\n    override val isDebuggable = variant.buildType.isDebuggable\n\n    override val isDataBindingEnabled = delegate.isDataBindingEnabled\n\n    override fun hasProperty(name: String) = project.hasProperty(name)\n\n    override fun <T> getProperty(name: String, default: T): T = project.getProperty(name, default)\n\n    override fun get(type: String) = variant.artifacts.get(type)\n\n    internal fun doFullTransform() = doTransform(this::transformFully)\n\n    internal fun doIncrementalTransform() = doTransform(this::transformIncrementally)\n\n    private fun onPreTransform() {\n        transform.transformers.forEach {\n            it.onPreTransform(this)\n        }\n    }\n\n    private fun onPostTransform() {\n        transform.transformers.forEach {\n            it.onPostTransform(this)\n        }\n    }\n\n    private fun doTransform(block: (ExecutorService) -> Iterable<Future<*>>) {\n        this.outputs.clear()\n        this.onPreTransform()\n\n        val executor = Executors.newFixedThreadPool(NCPU)\n        try {\n            block(executor).forEach {\n                it.get()\n            }\n        } finally {\n            executor.shutdown()\n            executor.awaitTermination(1, TimeUnit.HOURS)\n        }\n\n        this.onPostTransform()\n\n        if (transform.verifyEnabled) {\n            this.doVerify()\n        }\n    }\n\n    private fun transformFully(executor: ExecutorService) = this.inputs.map {\n        it.jarInputs + it.directoryInputs\n    }.flatten().map { input ->\n        executor.submit {\n            val format = if (input is DirectoryInput) Format.DIRECTORY else Format.JAR\n            outputProvider?.let { provider ->\n                project.logger.info(\"Transforming ${input.file}\")\n                input.transform(\n                    provider.getContentLocation(\n                        input.name,\n                        input.contentTypes,\n                        input.scopes,\n                        format\n                    )\n                )\n            }\n        }\n    }\n\n    private fun transformIncrementally(executor: ExecutorService) = this.inputs.map { input ->\n        input.jarInputs.filter { it.status != NOTCHANGED }.map { jarInput ->\n            executor.submit {\n                doIncrementalTransform(jarInput)\n            }\n        } + input.directoryInputs.filter { it.changedFiles.isNotEmpty() }.map { dirInput ->\n            val base = dirInput.file.toURI()\n            executor.submit {\n                doIncrementalTransform(dirInput, base)\n            }\n        }\n    }.flatten()\n\n    @Suppress(\"NON_EXHAUSTIVE_WHEN\")\n    private fun doIncrementalTransform(jarInput: JarInput) {\n        when (jarInput.status) {\n            REMOVED -> jarInput.file.delete()\n            CHANGED, ADDED -> {\n                project.logger.info(\"Transforming ${jarInput.file}\")\n                outputProvider?.let { provider ->\n                    jarInput.transform(\n                        provider.getContentLocation(\n                            jarInput.name,\n                            jarInput.contentTypes,\n                            jarInput.scopes,\n                            Format.JAR\n                        )\n                    )\n                }\n            }\n        }\n    }\n\n    @Suppress(\"NON_EXHAUSTIVE_WHEN\")\n    private fun doIncrementalTransform(dirInput: DirectoryInput, base: URI) {\n        dirInput.changedFiles.forEach { (file, status) ->\n            when (status) {\n                REMOVED -> {\n                    project.logger.info(\"Deleting $file\")\n                    outputProvider?.let { provider ->\n                        provider.getContentLocation(\n                            dirInput.name,\n                            dirInput.contentTypes,\n                            dirInput.scopes,\n                            Format.DIRECTORY\n                        ).parentFile.listFiles()?.asSequence()\n                            ?.filter { it.isDirectory }\n                            ?.map { File(it, dirInput.file.toURI().relativize(file.toURI()).path) }\n                            ?.filter { it.exists() }\n                            ?.forEach { it.delete() }\n                    }\n                    file.delete()\n                }\n                ADDED, CHANGED -> {\n                    project.logger.info(\"Transforming $file\")\n                    outputProvider?.let { provider ->\n                        val root = provider.getContentLocation(\n                            dirInput.name,\n                            dirInput.contentTypes,\n                            dirInput.scopes,\n                            Format.DIRECTORY\n                        )\n                        val output = File(root, base.relativize(file.toURI()).path)\n                        outputs += output\n                        file.transform(output) { bytecode ->\n                            bytecode.transform()\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    private fun doVerify() {\n        outputs.sortedBy(File::nameWithoutExtension).forEach { output ->\n            val out = temporaryDir.file(output.name)\n            val rc = out.dex(\n                output,\n                variant.extension.defaultConfig.targetSdkVersion?.apiLevel\n                    ?: DexFormat.API_NO_EXTENDED_OPCODES\n            )\n            println(\"${if (rc != 0) red(\"✗\") else green(\"✓\")} $output\")\n            out.deleteRecursively()\n        }\n    }\n\n    private fun QualifiedContent.transform(output: File) {\n        outputs += output\n        try {\n            this.file.dokitTransform(output) { bytecode ->\n                bytecode.transform()\n            }\n        } catch (e: Exception) {\n            \"e===>${e.message}\".println()\n            e.printStackTrace()\n        }\n\n    }\n\n    private fun ByteArray.transform(): ByteArray {\n        return transform.transformers.fold(this) { bytes, transformer ->\n            transformer.transform(this@DoKitTransformInvocation, bytes)\n        }\n    }\n\n    override fun project(): Project {\n        return project\n    }\n\n    override fun dokitExtension(): DoKitExtension {\n        return project.extensions.getByType(DoKitExtension::class.java)\n    }\n\n    override fun <R> registerCollector(collector: Collector<R>) {\n        TODO(\"Not yet implemented\")\n    }\n\n    override fun <R> unregisterCollector(collector: Collector<R>) {\n        TODO(\"Not yet implemented\")\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/asmtransform/BaseDoKitAsmTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.asmtransform\n\nimport com.didiglobal.booster.annotations.Priority\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.Transformer\nimport com.didiglobal.booster.transform.asm.ClassTransformer\nimport org.objectweb.asm.ClassReader\nimport org.objectweb.asm.ClassWriter\nimport org.objectweb.asm.tree.ClassNode\nimport java.lang.management.ManagementFactory\nimport java.lang.management.ThreadMXBean\nimport java.util.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/21-16:44\n * 描    述：\n * 修订历史：\n * ================================================\n */\nopen class BaseDoKitAsmTransformer : Transformer {\n    private val threadMxBean = ManagementFactory.getThreadMXBean()\n\n    private val durations = mutableMapOf<ClassTransformer, Long>()\n\n    private val classLoader: ClassLoader\n\n    internal val transformers: Iterable<ClassTransformer>\n\n    constructor() : this(Thread.currentThread().contextClassLoader)\n\n    constructor(classLoader: ClassLoader = Thread.currentThread().contextClassLoader) : this(\n        ServiceLoader.load(ClassTransformer::class.java, classLoader).sortedBy {\n            it.javaClass.getAnnotation(Priority::class.java)?.value ?: 0\n        }, classLoader\n    )\n\n    constructor(\n        transformers: Iterable<ClassTransformer>,\n        classLoader: ClassLoader = Thread.currentThread().contextClassLoader\n    ) {\n        this.classLoader = classLoader\n        this.transformers = transformers\n    }\n\n\n    override fun onPreTransform(context: TransformContext) {\n        this.transformers.forEach { transformer ->\n            this.threadMxBean.sumCpuTime(transformer) {\n                transformer.onPreTransform(context)\n            }\n        }\n    }\n\n    override fun transform(context: TransformContext, bytecode: ByteArray): ByteArray {\n        return ClassWriter(ClassWriter.COMPUTE_MAXS).also { writer ->\n            this.transformers.fold(ClassNode().also { klass ->\n                ClassReader(bytecode).accept(klass, 0)\n            }) { klass, transformer ->\n                this.threadMxBean.sumCpuTime(transformer) {\n                    transformer.transform(context, klass)\n                }\n            }.accept(writer)\n        }.toByteArray()\n    }\n\n    override fun onPostTransform(context: TransformContext) {\n        this.transformers.forEach { transformer ->\n            this.threadMxBean.sumCpuTime(transformer) {\n                transformer.onPostTransform(context)\n            }\n        }\n\n        val w1 = this.durations.keys.map {\n            it.javaClass.name.length\n        }.max() ?: 20\n        this.durations.forEach { (transformer, ns) ->\n            println(\"${transformer.javaClass.name.padEnd(w1 + 1)}: ${ns / 1000000} ms\")\n        }\n    }\n\n    private fun <R> ThreadMXBean.sumCpuTime(transformer: ClassTransformer, action: () -> R): R {\n        val ct0 = this.currentThreadCpuTime\n        val result = action()\n        val ct1 = this.currentThreadCpuTime\n        durations[transformer] = durations.getOrDefault(transformer, 0) + (ct1 - ct0)\n        return result\n    }\n\n}\n\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/asmtransform/DoKitAsmTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.asmtransform\n\nimport com.didiglobal.booster.transform.asm.ClassTransformer\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/21-16:44\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass DoKitAsmTransformer(transformers: Iterable<ClassTransformer>) :\n    BaseDoKitAsmTransformer(transformers)\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/AbsClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.isRelease\nimport com.didichuxing.doraemonkit.plugin.transform.DoKitTransformContext\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.ClassTransformer\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.tree.ClassNode\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/5/12-18:06\n * 描    述：\n * 修订历史：\n * ================================================\n */\n\nopen class AbsClassTransformer : ClassTransformer {\n\n\n    open fun onDoKitClassInterceptor(context: TransformContext, klass: ClassNode): Boolean {\n        if (context.isRelease()) {\n            return true\n        }\n        if (!DoKitExtUtil.dokitPluginSwitchOpen()) {\n            return true\n        }\n\n        //过滤kotlin module-info\n        if (klass.className == \"module-info\") {\n            return true\n        }\n        return false\n    }\n\n    override fun transform(context: TransformContext, klass: ClassNode): ClassNode {\n        if (onDoKitClassInterceptor(context, klass)) {\n            return klass\n        }\n\n        if (DoKitExtUtil.ignorePackageNames(klass.className)) {\n            return klass\n        }\n\n        if (context is DoKitTransformContext) {\n            val project = context.project()\n            val dokit = context.dokitExtension()\n            transform(project, dokit, context, klass)\n        }\n        return klass\n    }\n\n    open fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode = klass\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/BigImgClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.getMethodExitInsnNodes\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.ClassNode\nimport org.objectweb.asm.tree.InsnList\nimport org.objectweb.asm.tree.MethodInsnNode\nimport org.objectweb.asm.tree.VarInsnNode\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/14-18:07\n * 描    述：wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b\n * 修订历史：\n * ================================================\n */\nclass BigImgClassTransformer : AbsClassTransformer() {\n\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        if (!dokit.bigImageEnable) {\n            return klass\n        }\n\n        val className = klass.className\n        //glide\n        if (dokit.bigImage.glide && className == \"com.bumptech.glide.request.SingleRequest\") {\n            klass.methods.find { methodNode ->\n                (methodNode.name == \"init\" || methodNode.name == \"<init>\") && methodNode.desc != null\n            }.let { methodNode ->\n                //函数结束的地方插入\n                methodNode?.instructions?.getMethodExitInsnNodes()?.forEach {\n                    \"${context.projectDir.lastPath()}->hook glide  succeed: ${className}_${methodNode.name}_${methodNode.desc}\".println()\n                    methodNode.instructions?.insertBefore(it, createGlideInsnList())\n                }\n            }\n        }\n\n        //picasso\n        if (dokit.bigImage.picasso && className == \"com.squareup.picasso.Request\") {\n            klass.methods.find { methodNode ->\n                methodNode.name == \"<init>\" && methodNode.desc != null\n            }.let { methodNode ->\n                //函数结束的地方插入\n                methodNode?.instructions?.getMethodExitInsnNodes()?.forEach {\n                    \"${context.projectDir.lastPath()}->hook picasso  succeed: ${className}_${methodNode.name}_${methodNode.desc}\".println()\n                    methodNode.instructions?.insertBefore(it, createPicassoInsnList())\n                }\n            }\n        }\n\n        //Fresco\n        if (dokit.bigImage.fresco && className == \"com.facebook.imagepipeline.request.ImageRequest\") {\n            klass.methods.find { methodNode ->\n                methodNode.name == \"<init>\" && methodNode.desc != null\n            }.let { methodNode ->\n                \"${context.projectDir.lastPath()}->hook Fresco succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                //函数开始的地方插入\n                methodNode?.instructions?.insert(createFrescoInsnList())\n            }\n        }\n\n        //ImageLoader\n        if (dokit.bigImage.imageLoader && className == \"com.nostra13.universalimageloader.core.ImageLoadingInfo\") {\n            klass.methods.find { methodNode ->\n                methodNode.name == \"<init>\" && methodNode.desc != null\n            }.let { methodNode ->\n                \"${context.projectDir.lastPath()}->hook ImageLoader  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                methodNode?.instructions?.insert(createImageLoaderInsnList())\n            }\n        }\n\n        //Coil\n        if (dokit.bigImage.coil && className == \"coil.request.ImageRequest\") {\n            \"hook Coil Start\".println()\n            klass.methods.find { methodNode ->\n                methodNode.name == \"<init>\" && methodNode.desc != null\n            }.let { methodNode ->\n                //函数结束的地方插入\n                methodNode?.instructions?.getMethodExitInsnNodes()?.forEach {\n                    \"${context.projectDir.lastPath()}->hook Coil  succeed: ${className}_${methodNode.name}_${methodNode.desc}\".println()\n                    methodNode.instructions?.insertBefore(it, createCoilInsnList())\n                }\n            }\n        }\n        return klass\n    }\n\n    /**\n     * 创建Glide Aop代码指令\n     */\n    private fun createGlideInsnList(): InsnList {\n        return with(InsnList()) {\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/bigimg/glide/GlideHook\",\n                    \"proxy\",\n                    \"(Ljava/lang/Object;)V\",\n                    false\n                )\n            )\n            this\n        }\n    }\n\n    /**\n     * 创建Picasso Aop代码指令\n     */\n    private fun createPicassoInsnList(): InsnList {\n        return with(InsnList()) {\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/bigimg/picasso/PicassoHook\",\n                    \"proxy\",\n                    \"(Ljava/lang/Object;)V\",\n                    false\n                )\n            )\n            this\n        }\n\n    }\n\n    /**\n     * 创建Fresco Aop代码指令\n     */\n    private fun createFrescoInsnList(): InsnList {\n        return with(InsnList()) {\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEVIRTUAL,\n                    \"com/facebook/imagepipeline/request/ImageRequestBuilder\",\n                    \"getSourceUri\",\n                    \"()Landroid/net/Uri;\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEVIRTUAL,\n                    \"com/facebook/imagepipeline/request/ImageRequestBuilder\",\n                    \"getPostprocessor\",\n                    \"()Lcom/facebook/imagepipeline/request/Postprocessor;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/bigimg/fresco/FrescoHook\",\n                    \"proxy\",\n                    \"(Landroid/net/Uri;Lcom/facebook/imagepipeline/request/Postprocessor;)Lcom/facebook/imagepipeline/request/Postprocessor;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEVIRTUAL,\n                    \"com/facebook/imagepipeline/request/ImageRequestBuilder\",\n                    \"setPostprocessor\",\n                    \"(Lcom/facebook/imagepipeline/request/Postprocessor;)Lcom/facebook/imagepipeline/request/ImageRequestBuilder;\",\n                    false\n                )\n            )\n            this\n        }\n\n    }\n\n    /**\n     * 创建ImageLoader Aop代码指令\n     */\n    private fun createImageLoaderInsnList(): InsnList {\n        return with(InsnList()) {\n            add(VarInsnNode(Opcodes.ALOAD, 6))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/bigimg/imageloader/ImageLoaderHook\",\n                    \"proxy\",\n                    \"(Lcom/nostra13/universalimageloader/core/listener/ImageLoadingListener;)Lcom/nostra13/universalimageloader/core/listener/ImageLoadingListener;\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ASTORE, 6))\n            this\n        }\n    }\n\n    private fun createCoilInsnList(): InsnList {\n        return with(InsnList()) {\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/bigimg/coil/CoilHook\",\n                    \"proxy\",\n                    \"(Ljava/lang/Object;)V\",\n                    false\n                )\n            )\n            this\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/CommClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.extension.SlowMethodExtension\nimport com.didichuxing.doraemonkit.plugin.formatSuperName\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/14-18:07\n * 描    述：wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b\n * 修订历史：\n * ================================================\n */\nclass CommClassTransformer : AbsClassTransformer() {\n\n    override fun transform(context: TransformContext, klass: ClassNode): ClassNode {\n\n        val className = klass.className\n        val superName = klass.formatSuperName\n\n        //hook Androidx的ComponentActivity\n        if (className != \"com.didichuxing.doraemonkit.aop.mc.DoKitProxyActivity\" && superName == \"android.app.Activity\") {\n            createComponentActivitySuperActivityImpl(klass)\n        }\n\n        return super.transform(context, klass)\n    }\n\n    /**\n     * 类处理转化实现\n     */\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        //查找DoraemonKitReal&pluginConfig方法并插入指定字节码\n        if (className == \"com.didichuxing.doraemonkit.DoKitReal\") {\n            //插件配置\n            klass.methods?.find {\n                it.name == \"pluginConfig\"\n            }.let { methodNode ->\n                \"${context.projectDir.lastPath()}->insert map to the DoKitReal pluginConfig succeed\".println()\n                methodNode?.instructions?.insert(createPluginConfigInsnList(dokit.gpsEnable, dokit.networkEnable, dokit.bigImageEnable))\n            }\n        }\n        return klass\n    }\n\n    /**\n     * 创建pluginConfig代码指令\n     */\n    private fun createPluginConfigInsnList(gpsSwitch: Boolean, networkSwitch: Boolean, bigImgSwitch: Boolean): InsnList {\n        return with(InsnList()) {\n            //new HashMap\n            add(TypeInsnNode(Opcodes.NEW, \"java/util/HashMap\"))\n            add(InsnNode(Opcodes.DUP))\n            add(MethodInsnNode(Opcodes.INVOKESPECIAL, \"java/util/HashMap\", \"<init>\", \"()V\", false))\n            //保存变量\n            add(VarInsnNode(Opcodes.ASTORE, 0))\n            //获取第一个变量\n            //put(\"dokitPluginSwitch\",true)\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(LdcInsnNode(\"dokitPluginSwitch\"))\n            add(InsnNode(if (DoKitExtUtil.dokitPluginSwitchOpen()) Opcodes.ICONST_1 else Opcodes.ICONST_0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"java/lang/Boolean\",\n                    \"valueOf\",\n                    \"(Z)Ljava/lang/Boolean;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEINTERFACE,\n                    \"java/util/Map\",\n                    \"put\",\n                    \"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\",\n                    true\n                )\n            )\n            add(InsnNode(Opcodes.POP))\n\n            //put(\"gpsSwitch\",true)\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(LdcInsnNode(\"gpsSwitch\"))\n            add(InsnNode(if (gpsSwitch) Opcodes.ICONST_1 else Opcodes.ICONST_0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"java/lang/Boolean\",\n                    \"valueOf\",\n                    \"(Z)Ljava/lang/Boolean;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEINTERFACE,\n                    \"java/util/Map\",\n                    \"put\",\n                    \"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\",\n                    true\n                )\n            )\n            add(InsnNode(Opcodes.POP))\n\n            //put(\"networkSwitch\",true)\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(LdcInsnNode(\"networkSwitch\"))\n            add(InsnNode(if (networkSwitch) Opcodes.ICONST_1 else Opcodes.ICONST_0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"java/lang/Boolean\",\n                    \"valueOf\",\n                    \"(Z)Ljava/lang/Boolean;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEINTERFACE,\n                    \"java/util/Map\",\n                    \"put\",\n                    \"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\",\n                    true\n                )\n            )\n            add(InsnNode(Opcodes.POP))\n\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(LdcInsnNode(\"bigImgSwitch\"))\n            add(InsnNode(if (bigImgSwitch) Opcodes.ICONST_1 else Opcodes.ICONST_0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"java/lang/Boolean\",\n                    \"valueOf\",\n                    \"(Z)Ljava/lang/Boolean;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEINTERFACE,\n                    \"java/util/Map\",\n                    \"put\",\n                    \"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\",\n                    true\n                )\n            )\n            add(InsnNode(Opcodes.POP))\n\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(LdcInsnNode(\"methodSwitch\"))\n            add(InsnNode(if (DoKitExtUtil.dokitSlowMethodSwitchOpen()) Opcodes.ICONST_1 else Opcodes.ICONST_0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"java/lang/Boolean\",\n                    \"valueOf\",\n                    \"(Z)Ljava/lang/Boolean;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEINTERFACE,\n                    \"java/util/Map\",\n                    \"put\",\n                    \"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\",\n                    true\n                )\n            )\n            add(InsnNode(Opcodes.POP))\n\n\n            //put(\"methodStrategy\",0)\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(LdcInsnNode(\"methodStrategy\"))\n            add(InsnNode(if (DoKitExtUtil.SLOW_METHOD_STRATEGY == SlowMethodExtension.STRATEGY_STACK) Opcodes.ICONST_0 else Opcodes.ICONST_1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"java/lang/Integer\",\n                    \"valueOf\",\n                    \"(I)Ljava/lang/Integer;\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKEINTERFACE,\n                    \"java/util/Map\",\n                    \"put\",\n                    \"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\",\n                    true\n                )\n            )\n            add(InsnNode(Opcodes.POP))\n\n            //将HashMap注入到DokitPluginConfig中\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/DokitPluginConfig\",\n                    \"inject\",\n                    \"(Ljava/util/Map;)V\",\n                    false\n                )\n            )\n            this\n        }\n    }\n\n    /**\n     * 重置ComponentActivity的父类\n     */\n    private fun createComponentActivitySuperActivityImpl(klass: ClassNode) {\n        /**\n         * 修改继承的父类\n         */\n        klass.superName = \"com/didichuxing/doraemonkit/aop/mc/DoKitProxyActivity\"\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/EnterMSClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.*\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.extension.SlowMethodExtension\nimport com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNode\nimport com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNodeUtil\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.asIterable\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/14-18:07\n * 描    述：入口函数 慢函数调用栈 wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b\n * 修订历史：不要指定自动注入 需要手动在DoKitAsmTransformer中通过配置创建\n *\n * 原理:transform()方法的调用是无序的\n * 原因:哪一个class会先被transformer执行是不确定的  但是每一个class被transformer执行顺序是遵循transformer的Priority规则的\n * ================================================\n *\n * 入口函数 慢函数调用栈\n *\n */\nclass EnterMSClassTransformer : AbsClassTransformer() {\n\n    private val thresholdTime = DoKitExtUtil.slowMethodExt.stackMethod.thresholdTime\n    private val level = 0\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        if (!DoKitExtUtil.dokitSlowMethodSwitchOpen()) {\n            return klass\n        }\n\n        if (DoKitExtUtil.SLOW_METHOD_STRATEGY == SlowMethodExtension.STRATEGY_NORMAL) {\n            return klass\n        }\n\n        //默认为Application onCreate 和attachBaseContext\n        val enterMethods = DoKitExtUtil.slowMethodExt.stackMethod.enterMethods\n\n        if (enterMethods.isNotEmpty()) {\n            enterMethods.forEach { enterMethodName ->\n                klass.methods.forEach { methodNode ->\n                    val allMethodName = \"${klass.className}.${methodNode.name}\"\n                    if (allMethodName == enterMethodName) {\n                        \"${context.projectDir.lastPath()}->level-->$level mathched enterMethod===>$allMethodName\".println()\n                        operateMethodInsn(klass, methodNode)\n                    }\n                }\n            }\n        }\n\n        return klass\n    }\n\n\n    private fun operateMethodInsn(klass: ClassNode, methodNode: MethodNode) {\n        //读取全是函数调用的指令\n        methodNode.instructions.asIterable().filterIsInstance(MethodInsnNode::class.java)\n            .filter { methodInsnNode ->\n                methodInsnNode.name != \"<init>\"\n            }.forEach { methodInsnNode ->\n                val methodStackNode = MethodStackNode(\n                    level,\n                    methodInsnNode.ownerClassName,\n                    methodInsnNode.name,\n                    methodInsnNode.desc,\n                    klass.className,\n                    methodNode.name,\n                    methodNode.desc\n                )\n                MethodStackNodeUtil.addMethodStackNode(level, methodStackNode)\n            }\n        //函数出入口插入耗时统计代码\n        //方法入口插入\n        methodNode.instructions.insert(\n            createMethodEnterInsnList(\n                level,\n                klass.className,\n                methodNode.name,\n                methodNode.desc,\n                methodNode.access\n            )\n        )\n        //方法出口插入\n        methodNode.instructions.getMethodExitInsnNodes()?.forEach { methodExitInsnNode ->\n            methodNode.instructions.insertBefore(\n                methodExitInsnNode,\n                createMethodExitInsnList(\n                    level,\n                    klass.className,\n                    methodNode.name,\n                    methodNode.desc,\n                    methodNode.access\n                )\n            )\n        }\n    }\n\n\n    /**\n     * 创建慢函数入口指令集\n     */\n    private fun createMethodEnterInsnList(\n        level: Int,\n        className: String,\n        methodName: String,\n        desc: String,\n        access: Int\n    ): InsnList {\n        val isStaticMethod = access and Opcodes.ACC_STATIC != 0\n        return with(InsnList()) {\n            if (isStaticMethod) {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, DoKitExtUtil.STACK_METHOD_LEVEL))\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeStaticMethodCostStart\",\n                        \"(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V\",\n                        false\n                    )\n                )\n            } else {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, DoKitExtUtil.STACK_METHOD_LEVEL))\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(VarInsnNode(Opcodes.ALOAD, 0))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeObjectMethodCostStart\",\n                        \"(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V\",\n                        false\n                    )\n                )\n            }\n            this\n        }\n\n\n    }\n\n\n    /**\n     * 创建慢函数退出时的指令集\n     */\n    private fun createMethodExitInsnList(\n        level: Int,\n        className: String,\n        methodName: String,\n        desc: String,\n        access: Int\n    ): InsnList {\n        val isStaticMethod = access and Opcodes.ACC_STATIC != 0\n\n        return with(InsnList()) {\n            if (isStaticMethod) {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeStaticMethodCostEnd\",\n                        \"(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V\",\n                        false\n                    )\n                )\n            } else {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(VarInsnNode(Opcodes.ALOAD, 0))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeObjectMethodCostEnd\",\n                        \"(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V\",\n                        false\n                    )\n                )\n            }\n            this\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/GPSAMapClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 3:06 下午\n * @Description 高德地图 hooks\n */\n\nclass GPSAMapClassTransformer : AbsClassTransformer() {\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        if (dokit.gpsEnable && dokit.gps.amap && DoKitExtUtil.DOKIT_GPS_MOCK_INCLUDE) {\n            //插入高德地图定位相关字节码\n            if (className == \"com.amap.api.location.AMapLocationClient\") {\n                //设置监听器\n                klass.methods?.find {\n                    it.name == \"setLocationListener\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook amap map  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(createAmapLocationInsnList())\n                }\n                //反注册监听器\n                klass.methods?.find {\n                    it.name == \"unRegisterLocationListener\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook amap map  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(\n                        createAmapLocationUnRegisterInsnList()\n                    )\n                }\n                //代理getLastKnownLocation\n                klass.methods?.find {\n                    it.name == \"getLastKnownLocation\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook AMapLocationClient getLastKnownLocation  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(createAMapClientLastKnownLocation())\n                }\n\n            }\n//            //插入高德 地图定位相关字节码\n//            if (className == \"com.amap.api.maps.AMap\") {\n//                //设置LocationSource代理\n//                klass.methods?.find {\n//                    it.name == \"setLocationSource\"\n//                }.let { methodNode ->\n//                    \"${context.projectDir.lastPath()}->hook amap map LocationSource  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n//                    methodNode?.instructions?.insert(createAmapLocationSourceInsnList())\n//                }\n//            }\n\n            //插入高德地图导航相关字节码\n            if (className == \"com.amap.api.navi.AMapNavi\") {\n                //设置监听器\n                klass.methods?.find {\n                    it.name == \"addAMapNaviListener\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook amap map navi  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(createAmapNaviInsnList())\n                }\n                //反注册监听器\n                klass.methods?.find {\n                    it.name == \"removeAMapNaviListener\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook amap map navi  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(\n                        createAmapNaviUnRegisterInsnList()\n                    )\n                }\n            }\n        }\n        return klass\n    }\n\n    /**\n     * 创建Amap地图代码指令\n     */\n    private fun createAmapLocationInsnList(): InsnList {\n        return with(InsnList()) {\n            //在AMapLocationClient的 setLocationListener 方法之中插入自定义代理回调类\n            add(\n                TypeInsnNode(\n                    Opcodes.NEW,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/AMapLocationListenerProxy\"\n                )\n            )\n            add(InsnNode(Opcodes.DUP))\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESPECIAL,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/AMapLocationListenerProxy\",\n                    \"<init>\",\n                    \"(Lcom/amap/api/location/AMapLocationListener;)V\",\n                    false\n                )\n            )\n            //对第一个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n\n    /**\n     * 创建Amap地图导航代码指令\n     */\n    private fun createAmapNaviInsnList(): InsnList {\n        return with(InsnList()) {\n            //在AMapNavi的addAMapNaviListener方法之中插入自定义代理回调类\n            add(\n                TypeInsnNode(\n                    Opcodes.NEW,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/AMapNaviListenerProxy\"\n                )\n            )\n            add(InsnNode(Opcodes.DUP))\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESPECIAL,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/AMapNaviListenerProxy\",\n                    \"<init>\",\n                    \"(Lcom/amap/api/navi/AMapNaviListener;)V\",\n                    false\n                )\n            )\n            //对第一个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n\n\n    /**\n     * 创建Amap LocationSource代码指令\n     */\n    private fun createAmapLocationSourceInsnList(): InsnList {\n        return with(InsnList()) {\n            //在AMapNavi的addAMapNaviListener方法之中插入自定义代理回调类\n            add(\n                TypeInsnNode(\n                    Opcodes.NEW,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/AMapLocationSourceProxy\"\n                )\n            )\n            add(InsnNode(Opcodes.DUP))\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESPECIAL,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/AMapLocationSourceProxy\",\n                    \"<init>\",\n                    \"(Lcom/amap/api/maps/LocationSource;)V\",\n                    false\n                )\n            )\n            //对第一个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n\n    /**\n     * 创建AMapLocationClient#LastKnownLocation 字节码替换\n     */\n    private fun createAMapClientLastKnownLocation(): InsnList {\n        return with(InsnList()) {\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/AMapLocationClientProxy\",\n                    \"getLastKnownLocation\",\n                    \"(Lcom/amap/api/location/AMapLocationClient;)Lcom/amap/api/location/AMapLocation;\",\n                    false\n                )\n            )\n            add(InsnNode(Opcodes.ARETURN))\n            this\n        }\n\n    }\n\n    /**\n     * 创建Amap地图UnRegister代码指令\n     */\n    private fun createAmapLocationUnRegisterInsnList(): InsnList {\n        return with(InsnList()) {\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/ThirdMapLocationListenerUtil\",\n                    \"unRegisterAmapLocationListener\",\n                    \"(Lcom/amap/api/location/AMapLocationListener;)Lcom/didichuxing/doraemonkit/gps_mock/map/AMapLocationListenerProxy;\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n    }\n\n    /**\n     * 创建Amap地图 Navi UnRegister代码指令\n     */\n    private fun createAmapNaviUnRegisterInsnList(): InsnList {\n        return with(InsnList()) {\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/ThirdMapLocationListenerUtil\",\n                    \"unRegisterAmapNaviListener\",\n                    \"(Lcom/amap/api/navi/AMapNaviListener;)Lcom/didichuxing/doraemonkit/gps_mock/map/AMapNaviListenerProxy;\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/GPSBDClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 3:06 下午\n * @Description 百度地图 hooks\n */\n\nclass GPSBDClassTransformer : AbsClassTransformer() {\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        if (dokit.gpsEnable && dokit.gps.baidu && DoKitExtUtil.DOKIT_GPS_MOCK_INCLUDE) {\n            //插入百度地图相关字节码\n            if (className == \"com.baidu.location.LocationClient\") {\n                //拦截注册监听器\n                klass.methods?.filter {\n                    it.name == \"registerLocationListener\"\n                        && (it.desc == \"(Lcom/baidu/location/BDLocationListener;)V\" || it.desc == \"(Lcom/baidu/location/BDAbstractLocationListener;)V\")\n                }?.forEach { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook baidu map  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    if (methodNode.desc == \"(Lcom/baidu/location/BDLocationListener;)V\") {\n                        methodNode?.instructions?.insert(createBDLocationListenerInsnList())\n                    } else if (methodNode.desc == \"(Lcom/baidu/location/BDAbstractLocationListener;)V\") {\n                        methodNode?.instructions?.insert(createBDLocationAbsListenerInsnList())\n                    }\n                }\n                //反注册监听器\n                klass.methods?.find {\n                    it.name == \"unRegisterLocationListener\" && it.desc == \"(Lcom/baidu/location/BDLocationListener;)V\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook baidu map  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(\n                        createBDLocationUnRegisterInsnList()\n                    )\n                }\n                //反注册监听器\n                klass.methods?.find {\n                    it.name == \"unRegisterLocationListener\" && it.desc == \"(Lcom/baidu/location/BDAbstractLocationListener;)V\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook baidu map  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(\n                        createBDAbsLocationUnRegisterInsnList()\n                    )\n                }\n            }\n        }\n        return klass\n    }\n\n    /**\n     * 创建百度地图代码指令\n     */\n    private fun createBDLocationListenerInsnList(): InsnList {\n        return with(InsnList()) {\n            //在LocationClient的registerLocationListener方法之中插入自定义代理回调类\n            add(\n                TypeInsnNode(\n                    Opcodes.NEW,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/BDLocationListenerProxy\"\n                )\n            )\n            add(InsnNode(Opcodes.DUP))\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESPECIAL,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/BDLocationListenerProxy\",\n                    \"<init>\",\n                    \"(Lcom/baidu/location/BDLocationListener;)V\",\n                    false\n                )\n            )\n            //对第一个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n\n            this\n        }\n\n    }\n\n    /**\n     * 创建百度地图代码指令\n     */\n    private fun createBDLocationAbsListenerInsnList(): InsnList {\n        return with(InsnList()) {\n            //在LocationClient的registerLocationListener方法之中插入自定义代理回调类\n            add(\n                TypeInsnNode(\n                    Opcodes.NEW,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/BDAbsLocationListenerProxy\"\n                )\n            )\n            add(InsnNode(Opcodes.DUP))\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESPECIAL,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/BDAbsLocationListenerProxy\",\n                    \"<init>\",\n                    \"(Lcom/baidu/location/BDAbstractLocationListener;)V\",\n                    false\n                )\n            )\n            //对第一个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n    }\n\n    /**\n     * 创建百度地图UnRegister代码指令\n     */\n    private fun createBDLocationUnRegisterInsnList(): InsnList {\n        return with(InsnList()) {\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/ThirdMapLocationListenerUtil\",\n                    \"unRegisterBDLocationListener\",\n                    \"(Lcom/baidu/location/BDLocationListener;)Lcom/didichuxing/doraemonkit/gps_mock/map/BDLocationListenerProxy;\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n\n    /**\n     * 创建百度地图UnRegister代码指令\n     */\n    private fun createBDAbsLocationUnRegisterInsnList(): InsnList {\n        return with(InsnList()) {\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/ThirdMapLocationListenerUtil\",\n                    \"unRegisterBDLocationListener\",\n                    \"(Lcom/baidu/location/BDAbstractLocationListener;)Lcom/didichuxing/doraemonkit/gps_mock/map/BDAbsLocationListenerProxy;\",\n                    false\n                )\n            )\n            //对第一个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n\n    /**\n     * 创建百度地图代码指令\n     */\n    private fun createBaiduLocationInsnList(): InsnList {\n        return with(InsnList()) {\n            //在AMapLocationClient的setLocationListener方法之中插入自定义代理回调类\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/BDLocationUtil\",\n                    \"proxy\",\n                    \"(Lcom/baidu/location/BDLocation;)Lcom/baidu/location/BDLocation;\",\n                    false\n                )\n            )\n            //对第一个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/GPSClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.kotlinx.asIterable\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.ClassNode\nimport org.objectweb.asm.tree.MethodInsnNode\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 3:06 下午\n * @Description 系统定位hooks\n */\n\nclass GPSClassTransformer : AbsClassTransformer() {\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        //gps字节码操作\n        if (dokit.gpsEnable && dokit.gps.local && DoKitExtUtil.DOKIT_GPS_MOCK_INCLUDE) {\n            //系统 gpsStatus hook\n            klass.methods.forEach { method ->\n                method.instructions?.iterator()?.asIterable()\n                    ?.filterIsInstance(MethodInsnNode::class.java)?.filter {\n                        it.opcode == Opcodes.INVOKEVIRTUAL &&\n                            it.owner == \"android/location/LocationManager\" &&\n                            it.name == \"getGpsStatus\" &&\n                            it.desc == \"(Landroid/location/GpsStatus;)Landroid/location/GpsStatus;\"\n                    }?.forEach {\n                        \"${context.projectDir.lastPath()}->hook LocationManager#getGpsStatus method  succeed in : ${className}_${method.name}_${method.desc}\".println()\n                        method.instructions.insert(\n                            it,\n                            MethodInsnNode(\n                                Opcodes.INVOKESTATIC,\n                                \"com/didichuxing/doraemonkit/gps_mock/location/GpsStatusUtil\",\n                                \"wrap\",\n                                \"(Landroid/location/GpsStatus;)Landroid/location/GpsStatus;\",\n                                false\n                            )\n                        )\n                    }\n            }\n        }\n        return klass\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/GPSTencentClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 3:06 下午\n * @Description 腾讯地图 hooks\n */\n\nclass GPSTencentClassTransformer : AbsClassTransformer() {\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        if (dokit.gpsEnable && dokit.gps.tencent && DoKitExtUtil.DOKIT_GPS_MOCK_INCLUDE) {\n            //插入腾讯地图相关字节码\n            if (className == \"com.tencent.map.geolocation.TencentLocationManager\") {\n                //持续定位和单次定位\n                klass.methods?.filter {\n                    it.name == \"requestSingleFreshLocation\" || it.name == \"requestLocationUpdates\"\n                }?.forEach { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook tencent map  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(createTencentLocationInsnList())\n                }\n\n                //反注册监听器\n                klass.methods?.find {\n                    it.name == \"removeUpdates\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->hook tencent map  succeed: ${className}_${methodNode?.name}_${methodNode?.desc}\".println()\n                    methodNode?.instructions?.insert(\n                        createTencentLocationUnRegisterInsnList()\n                    )\n                }\n            }\n        }\n        return klass\n    }\n\n    /**\n     * 创建tencent地图代码指令\n     */\n    private fun createTencentLocationInsnList(): InsnList {\n        return with(InsnList()) {\n            //在AMapLocationClient的setLocationListener方法之中插入自定义代理回调类\n            add(\n                TypeInsnNode(\n                    Opcodes.NEW,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/TencentLocationListenerProxy\"\n                )\n            )\n            add(InsnNode(Opcodes.DUP))\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 2))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESPECIAL,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/TencentLocationListenerProxy\",\n                    \"<init>\",\n                    \"(Lcom/tencent/map/geolocation/TencentLocationListener;)V\",\n                    false\n                )\n            )\n            //对第二个参数进行重新赋值\n            add(VarInsnNode(Opcodes.ASTORE, 2))\n\n            this\n        }\n\n    }\n\n    /**\n     * 创建Tencent地图UnRegister代码指令\n     */\n    private fun createTencentLocationUnRegisterInsnList(): InsnList {\n        return with(InsnList()) {\n            //访问第一个参数\n            add(VarInsnNode(Opcodes.ALOAD, 1))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/gps_mock/map/ThirdMapLocationListenerUtil\",\n                    \"unRegisterTencentLocationListener\",\n                    \"(Lcom/tencent/map/geolocation/TencentLocationListener;)Lcom/didichuxing/doraemonkit/gps_mock/map/TencentLocationListenerProxy;\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ASTORE, 1))\n            this\n        }\n\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/GSMClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.*\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.extension.SlowMethodExtension\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.asIterable\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/14-18:07\n * 描    述：\n *           全局慢函数业务代码慢函数\n *           wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b\n *\n * 修订历史：\n * ================================================\n *\n *  全局慢函数\n *\n */\nclass GSMClassTransformer : AbsClassTransformer() {\n    val thresholdTime = DoKitExtUtil.slowMethodExt.normalMethod.thresholdTime\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n\n        if (!DoKitExtUtil.dokitSlowMethodSwitchOpen()) {\n            return klass\n        }\n\n        if (DoKitExtUtil.SLOW_METHOD_STRATEGY == SlowMethodExtension.STRATEGY_STACK) {\n            return klass\n        }\n\n        if (DoKitExtUtil.ignorePackageNames(klass.className)) {\n            return klass\n        }\n\n\n        val className = klass.className\n        //没有自定义设置插装包名 默认是以packageName为包名 即全局业务代码插桩\n        DoKitExtUtil.slowMethodExt.normalMethod.packageNames.forEach { packageName ->\n            //包含在白名单中且不在黑名单中\n            if (className.contains(packageName) && notMatchedBlackList(className)) {\n                klass.methods.filter { methodNode ->\n                    methodNode.name != \"<init>\" &&\n                        !methodNode.isEmptyMethod() &&\n                        !methodNode.isSingleMethod() &&\n                        !methodNode.isGetSetMethod() &&\n                        !methodNode.isMainMethod(className)\n                }.forEach { methodNode ->\n                    methodNode.instructions.asIterable()\n                        .filterIsInstance(MethodInsnNode::class.java).let { methodInsnNodes ->\n                            if (methodInsnNodes.isNotEmpty()) {\n                                //方法入口插入\n                                methodNode.instructions.insert(\n                                    createMethodEnterInsnList(\n                                        className,\n                                        methodNode.name,\n                                        methodNode.access\n                                    )\n                                )\n                                //方法出口插入\n                                methodNode.instructions.getMethodExitInsnNodes()\n                                    ?.forEach { methodExitInsnNode ->\n                                        methodNode.instructions.insertBefore(\n                                            methodExitInsnNode,\n                                            createMethodExitInsnList(\n                                                className,\n                                                methodNode.name,\n                                                methodNode.access\n                                            )\n                                        )\n                                    }\n                            }\n                        }\n                }\n            }\n        }\n        return klass\n    }\n\n\n    private fun notMatchedBlackList(className: String): Boolean {\n        for (strBlack in DoKitExtUtil.slowMethodExt.normalMethod.methodBlacklist) {\n            if (className.contains(strBlack)) {\n                return false\n            }\n        }\n\n        return true\n    }\n\n    /**\n     * 创建慢函数入口指令集\n     */\n    private fun createMethodEnterInsnList(\n        className: String,\n        methodName: String,\n        access: Int\n    ): InsnList {\n        val isStaticMethod = access and Opcodes.ACC_STATIC != 0\n        return with(InsnList()) {\n            if (isStaticMethod) {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/MethodCostUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.SIPUSH, thresholdTime))\n                add(LdcInsnNode(\"$className&$methodName\"))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"recodeStaticMethodCostStart\",\n                        \"(ILjava/lang/String;)V\",\n                        false\n                    )\n                )\n            } else {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/MethodCostUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.SIPUSH, thresholdTime))\n                add(LdcInsnNode(\"$className&$methodName\"))\n                add(VarInsnNode(Opcodes.ALOAD, 0))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"recodeObjectMethodCostStart\",\n                        \"(ILjava/lang/String;Ljava/lang/Object;)V\",\n                        false\n                    )\n                )\n            }\n\n            this\n        }\n\n    }\n\n\n    /**\n     * 创建慢函数退出时的指令集\n     */\n    private fun createMethodExitInsnList(\n        className: String,\n        methodName: String,\n        access: Int\n    ): InsnList {\n        val isStaticMethod = access and Opcodes.ACC_STATIC != 0\n        return with(InsnList()) {\n            if (isStaticMethod) {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/MethodCostUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.SIPUSH, thresholdTime))\n                add(LdcInsnNode(\"$className&$methodName\"))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"recodeStaticMethodCostEnd\",\n                        \"(ILjava/lang/String;)V\",\n                        false\n                    )\n                )\n            } else {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/MethodCostUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.SIPUSH, thresholdTime))\n                add(LdcInsnNode(\"$className&$methodName\"))\n                add(VarInsnNode(Opcodes.ALOAD, 0))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/MethodCostUtil\",\n                        \"recodeObjectMethodCostEnd\",\n                        \"(ILjava/lang/String;Ljava/lang/Object;)V\",\n                        false\n                    )\n                )\n            }\n            this\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/MSDClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.*\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.extension.SlowMethodExtension\nimport com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNode\nimport com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNodeUtil\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.asIterable\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/14-18:07\n * 描    述：函数调用栈依赖 慢函数调用栈 wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b\n * 修订历史：不要指定自动注入 需要手动在DoKitAsmTransformer中通过配置创建\n *\n * 原理:transform()方法的调用是无序的\n * 原因:哪一个class会先被transformer执行是不确定的  但是每一个class被transformer执行顺序是遵循transformer的Priority规则的\n * ================================================\n */\nclass MSDClassTransformer(private val level: Int = 1) : AbsClassTransformer() {\n\n    private val thresholdTime = DoKitExtUtil.slowMethodExt.stackMethod.thresholdTime\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n\n        if (!DoKitExtUtil.dokitSlowMethodSwitchOpen()) {\n            return klass\n        }\n\n        if (DoKitExtUtil.SLOW_METHOD_STRATEGY == SlowMethodExtension.STRATEGY_NORMAL) {\n            return klass\n        }\n\n        if (DoKitExtUtil.ignorePackageNames(klass.className)) {\n            return klass\n        }\n\n        //命中黑名单则不插桩\n        if (!notMatchedBlackList(klass.className)) {\n            return klass\n        }\n\n\n        val methodStackKeys: MutableSet<String> = MethodStackNodeUtil.METHOD_STACK_KEYS[level - 1]\n\n        klass.methods.filter { methodNode ->\n            methodNode.name != \"<init>\" &&\n                !methodNode.isEmptyMethod() &&\n                !methodNode.isSingleMethod() &&\n                !methodNode.isGetSetMethod() &&\n                !methodNode.isMainMethod(klass.className)\n        }.forEach { methodNode ->\n            val key = \"${klass.className}&${methodNode.name}&${methodNode.desc}\"\n            if (methodStackKeys.contains(key)) {\n                \"${context.projectDir.lastPath()}->level-->$level   mathched key===>$key\".println()\n                operateMethodInsn(klass, methodNode)\n            }\n\n        }\n\n        return klass\n    }\n\n\n    private fun operateMethodInsn(klass: ClassNode, methodNode: MethodNode) {\n        //读取全是函数调用的指令\n        methodNode.instructions.asIterable().filterIsInstance(MethodInsnNode::class.java)\n            .filter { methodInsnNode ->\n                methodInsnNode.name != \"<init>\"\n            }.forEach { methodInsnNode ->\n                val methodStackNode = MethodStackNode(\n                    level,\n                    methodInsnNode.ownerClassName,\n                    methodInsnNode.name,\n                    methodInsnNode.desc,\n                    klass.className,\n                    methodNode.name,\n                    methodNode.desc\n                )\n                MethodStackNodeUtil.addMethodStackNode(level, methodStackNode)\n            }\n        //函数出入口插入耗时统计代码\n        //方法入口插入\n        methodNode.instructions.insert(\n            createMethodEnterInsnList(\n                level,\n                klass.className,\n                methodNode.name,\n                methodNode.desc,\n                methodNode.access\n            )\n        )\n        //方法出口插入\n        methodNode.instructions.getMethodExitInsnNodes()?.forEach { methodExitInsnNode ->\n            methodNode.instructions.insertBefore(\n                methodExitInsnNode,\n                createMethodExitInsnList(\n                    level,\n                    klass.className,\n                    methodNode.name,\n                    methodNode.desc,\n                    methodNode.access\n                )\n            )\n        }\n    }\n\n\n    /**\n     * 创建慢函数入口指令集\n     */\n    private fun createMethodEnterInsnList(\n        level: Int,\n        className: String,\n        methodName: String,\n        desc: String,\n        access: Int\n    ): InsnList {\n        val isStaticMethod = access and Opcodes.ACC_STATIC != 0\n        return with(InsnList()) {\n            if (isStaticMethod) {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, DoKitExtUtil.STACK_METHOD_LEVEL))\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeStaticMethodCostStart\",\n                        \"(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V\",\n                        false\n                    )\n                )\n            } else {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, DoKitExtUtil.STACK_METHOD_LEVEL))\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(VarInsnNode(Opcodes.ALOAD, 0))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeObjectMethodCostStart\",\n                        \"(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V\",\n                        false\n                    )\n                )\n            }\n            this\n        }\n\n    }\n\n\n    /**\n     * 创建慢函数退出时的指令集\n     */\n    private fun createMethodExitInsnList(\n        level: Int,\n        className: String,\n        methodName: String,\n        desc: String,\n        access: Int\n    ): InsnList {\n        val isStaticMethod = access and Opcodes.ACC_STATIC != 0\n        return with(InsnList()) {\n            if (isStaticMethod) {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeStaticMethodCostEnd\",\n                        \"(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V\",\n                        false\n                    )\n                )\n            } else {\n                add(\n                    FieldInsnNode(\n                        Opcodes.GETSTATIC,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"INSTANCE\",\n                        \"Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;\"\n                    )\n                )\n                add(IntInsnNode(Opcodes.BIPUSH, thresholdTime))\n                add(IntInsnNode(Opcodes.BIPUSH, level))\n                add(LdcInsnNode(className))\n                add(LdcInsnNode(methodName))\n                add(LdcInsnNode(desc))\n                add(VarInsnNode(Opcodes.ALOAD, 0))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEVIRTUAL,\n                        \"com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil\",\n                        \"recodeObjectMethodCostEnd\",\n                        \"(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V\",\n                        false\n                    )\n                )\n            }\n            this\n        }\n\n    }\n\n    private fun notMatchedBlackList(className: String): Boolean {\n        for (strBlack in DoKitExtUtil.slowMethodExt.stackMethod.methodBlacklist) {\n            if (className.contains(strBlack)) {\n                return false\n            }\n        }\n\n        return true\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/Okhttp3ClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.kotlinx.asIterable\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 3:06 下午\n * @Description 用一句话说明文件功能\n */\n\nclass Okhttp3ClassTransformer : AbsClassTransformer() {\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        if (dokit.networkEnable && dokit.network.okHttp) {\n            //hook OkhttpClient\n            if (className == \"okhttp3.OkHttpClient\") {\n                klass.methods?.find {\n                    it.name == \"<init>\" && it.desc != \"()V\"\n                }.let {\n                    \"${context.projectDir.lastPath()}->hook OkhttpClient  succeed: ${className}_${it?.name}_${it?.desc}\".println()\n                    it?.instructions\n                        ?.iterator()\n                        ?.asIterable()\n                        ?.filterIsInstance(FieldInsnNode::class.java)\n                        ?.filter { fieldInsnNode ->\n                            fieldInsnNode.opcode == Opcodes.PUTFIELD\n                                && fieldInsnNode.owner == \"okhttp3/OkHttpClient\"\n                                && fieldInsnNode.name == \"networkInterceptors\"\n                                && fieldInsnNode.desc == \"Ljava/util/List;\"\n                        }?.forEach { fieldInsnNode ->\n                            it.instructions.insert(fieldInsnNode, createOkHttpClientInsnList())\n                        }\n                }\n            }\n        }\n        return klass\n    }\n\n    /**\n     * 创建OkhttpClient一个数构造函数指令\n     */\n    private fun createOkHttpClientInsnList(): InsnList {\n        return with(InsnList()) {\n            //插入application 拦截器\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/OkHttpHook\",\n                    \"addDoKitIntercept\",\n                    \"(Lokhttp3/OkHttpClient;)V\",\n                    false\n                )\n            )\n            this\n        }\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/ThirdLibsClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 3:06 下午\n * @Description 用一句话说明文件功能\n */\n\nclass ThirdLibsClassTransformer : AbsClassTransformer() {\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        if (dokit.thirdLibEnable) {\n            //查找DoraemonKitReal&pluginConfig方法并插入指定字节码\n            if (className == \"com.didichuxing.doraemonkit.DoKitReal\") {\n                //三方库信息注入\n                klass.methods?.find {\n                    it.name == \"initThirdLibraryInfo\"\n                }.let { methodNode ->\n                    \"${context.projectDir.lastPath()}->insert map to the DoKitReal initThirdLibraryInfo succeed\".println()\n                    methodNode?.instructions?.insert(createThirdLibInfoInsnList())\n                }\n            }\n        }\n\n        return klass\n    }\n\n    /**\n     * 创建pluginConfig代码指令\n     */\n    private fun createThirdLibInfoInsnList(): InsnList {\n        //val insnList = InsnList()\n        return with(InsnList()) {\n            //new HashMap\n            add(TypeInsnNode(Opcodes.NEW, \"java/util/HashMap\"))\n            add(InsnNode(Opcodes.DUP))\n            add(MethodInsnNode(Opcodes.INVOKESPECIAL, \"java/util/HashMap\", \"<init>\", \"()V\", false))\n            //保存变量\n            add(VarInsnNode(Opcodes.ASTORE, 0))\n            DoKitExtUtil.THIRD_LIB_INFOS.forEach { thirdLibInfo ->\n                add(VarInsnNode(Opcodes.ALOAD, 0))\n                add(LdcInsnNode(thirdLibInfo.variant))\n                add(LdcInsnNode(thirdLibInfo.fileSize.toString()))\n                add(\n                    MethodInsnNode(\n                        Opcodes.INVOKEINTERFACE,\n                        \"java/util/Map\",\n                        \"put\",\n                        \"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\",\n                        false\n                    )\n                )\n                add(InsnNode(Opcodes.POP))\n            }\n            //将HashMap注入到DokitPluginConfig中\n            add(VarInsnNode(Opcodes.ALOAD, 0))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/DokitThirdLibInfo\",\n                    \"inject\",\n                    \"(Ljava/util/Map;)V\",\n                    false\n                )\n            )\n            this\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/UrlConnectionTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.kotlinx.asIterable\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.ClassNode\nimport org.objectweb.asm.tree.MethodInsnNode\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/5/14-18:07\n * 描    述：wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b\n * 修订历史：\n * ================================================\n */\nclass UrlConnectionTransformer : AbsClassTransformer() {\n\n    private val SHADOW_URL = \"com/didichuxing/doraemonkit/aop/urlconnection/HttpUrlConnectionProxyUtil\"\n\n    private val DESC = \"(Ljava/net/URLConnection;)Ljava/net/URLConnection;\"\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n        val className = klass.className\n\n        if (dokit.networkEnable && dokit.network.urlConnect) {\n            // url connection\n            klass.methods.forEach { method ->\n                method.instructions?.iterator()?.asIterable()\n                    ?.filterIsInstance(MethodInsnNode::class.java)?.filter {\n                        it.opcode == Opcodes.INVOKEVIRTUAL &&\n                            it.owner == \"java/net/URL\" &&\n                            it.name == \"openConnection\" &&\n                            it.desc == \"()Ljava/net/URLConnection;\"\n                    }?.forEach {\n                        \"${context.projectDir.lastPath()}->hook URL#openConnection method  succeed in : ${className}_${method.name}_${method.desc}\".println()\n                        method.instructions.insert(\n                            it,\n                            MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_URL, \"proxy\", DESC, false)\n                        )\n                    }\n            }\n        }\n        return klass\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/classtransform/WebViewClassTransformer.kt",
    "content": "package com.didichuxing.doraemonkit.plugin.transform.classtransform\n\nimport com.didichuxing.doraemonkit.plugin.DoKitExtUtil\nimport com.didichuxing.doraemonkit.plugin.extension.DoKitExtension\nimport com.didichuxing.doraemonkit.plugin.formatSuperName\nimport com.didichuxing.doraemonkit.plugin.lastPath\nimport com.didichuxing.doraemonkit.plugin.println\nimport com.didiglobal.booster.kotlinx.asIterable\nimport com.didiglobal.booster.transform.TransformContext\nimport com.didiglobal.booster.transform.asm.className\nimport org.gradle.api.Project\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.tree.*\n\n\n/**\n * didi Create on 2023/3/21 .\n *\n * Copyright (c) 2023/3/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2023/3/21 3:06 下午\n * @Description WebView hooks\n */\n\nclass WebViewClassTransformer : AbsClassTransformer() {\n\n    override fun transform(project: Project, dokit: DoKitExtension, context: TransformContext, klass: ClassNode): ClassNode {\n\n        val className = klass.className\n        val superName = klass.formatSuperName\n\n        //网络 OkHttp&didi platform aop\n        if (dokit.webViewEnable) {\n            //webView 字节码操作\n            if (dokit.webView.network) {\n                //普通的webview\n                klass.methods.forEach { method ->\n                    method.instructions?.iterator()?.asIterable()\n                        ?.filterIsInstance(MethodInsnNode::class.java)?.filter {\n                            if (\"loadUrl\".equals(it.name)) {\n                                \"hook loadUrl() all ${className} ^${superName}^${it.owner} :: ${it.name} , ${it.desc} ,${it.opcode}\".println()\n                            }\n                            (it.opcode == Opcodes.INVOKEVIRTUAL || it.opcode == Opcodes.INVOKESPECIAL)\n                                && it.name == \"loadUrl\"\n                                && (it.desc == \"(Ljava/lang/String;)V\" || it.desc == \"(Ljava/lang/String;Ljava/util/Map;)V\")\n                                && isWebViewOwnerNameMatched(it.owner)\n                        }?.forEach {\n                            \"${context.projectDir.lastPath()}->hook WebView#loadurl method  succeed in :  ${className}_${method.name}_${method.desc} | ${it.owner}\".println()\n                            if (it.desc == \"(Ljava/lang/String;)V\") {\n                                method.instructions.insertBefore(it, createWebViewInsnList())\n                            } else {\n                                method.instructions.insertBefore(it, createWebViewInsnList(method))\n                            }\n                        }\n                }\n            }\n        }\n        return klass\n    }\n\n\n    private fun isWebViewOwnerNameMatched(ownerName: String): Boolean {\n        return ownerName == \"android/webkit/WebView\" ||\n            ownerName == \"com/tencent/smtt/sdk/WebView\" ||\n            ownerName.contentEquals(\"WebView\") ||\n            ownerName == DoKitExtUtil.WEBVIEW_CLASS_NAME\n    }\n\n\n    /**\n     * 创建webView函数指令集\n     * 参考:https://www.jianshu.com/p/7d623f441bed\n     */\n    private fun createWebViewInsnList(): InsnList {\n        return with(InsnList()) {\n            //复制栈顶的2个指令 指令集变为 比如 aload 2 aload0 / aload 2 aload0\n            add(InsnNode(Opcodes.DUP2))\n            //抛出最上面的指令 指令集变为 aload 2 aload0 / aload 2  其中 aload 2即为我们所需要的对象\n            add(InsnNode(Opcodes.POP))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/WebViewHook\",\n                    \"inject\",\n                    \"(Ljava/lang/Object;)V\",\n                    false\n                )\n            )\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/WebViewHook\",\n                    \"getSafeUrl\",\n                    \"(Ljava/lang/String;)Ljava/lang/String;\",\n                    false\n                )\n            )\n            this\n        }\n    }\n\n    /**\n     * 创建webView函数指令集 (多参数，先存参数然后取出)\n     * 参考:https://www.jianshu.com/p/7d623f441bed\n     */\n    private fun createWebViewInsnList(method: MethodNode): InsnList {\n        val size = method.localVariables.size\n        return with(InsnList()) {\n            add(VarInsnNode(Opcodes.ASTORE, size + 1))\n            add(VarInsnNode(Opcodes.ASTORE, size))\n            add(InsnNode(Opcodes.DUP))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/WebViewHook\",\n                    \"inject\",\n                    \"(Ljava/lang/Object;)V\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ALOAD, size))\n            add(\n                MethodInsnNode(\n                    Opcodes.INVOKESTATIC,\n                    \"com/didichuxing/doraemonkit/aop/WebViewHook\",\n                    \"getSafeUrl\",\n                    \"(Ljava/lang/String;)Ljava/lang/String;\",\n                    false\n                )\n            )\n            add(VarInsnNode(Opcodes.ALOAD, size + 1))\n            this\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-plugin/src/main/resources/META-INF/gradle-plugins/com.didi.dokit.properties",
    "content": "#入口文件\n#文件名为插件名 即 apply plugin \"xxx\"\nimplementation-class=com.didichuxing.doraemonkit.plugin.DoKitPlugin"
  },
  {
    "path": "Android/dokit-plugin/upload.sh",
    "content": "#!/usr/bin/env bash\necho -n  \"please enter bintray userid ->\"\nread  userid_bintray\necho -n  \"please enter bintray apikey ->\"\nread  apikey_bintray\n../gradlew clean build --stacktrace --info   bintrayUpload -PbintrayUser=${userid_bintray} -PbintrayKey=${apikey_bintray} -PdryRun=false"
  },
  {
    "path": "Android/dokit-pthread-hook/.gitignore",
    "content": "/build"
  },
  {
    "path": "Android/dokit-pthread-hook/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-kapt'\napply from: '../upload.gradle'\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_21\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n\n        lintOptions {\n            abortOnError false\n        }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n    /**\n     * 支持ViewBinding\n     */\n    buildFeatures {\n        viewBinding = true\n    }\n\n}\n\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    testImplementation 'junit:junit:4.12'\n    androidTestImplementation 'junit:junit:4.12'\n    compileOnly project(':dokit-util')\n    compileOnly project(':dokit-okhttp-api')\n    //此处需要使用api的形式 向上暴露内部api\n    implementation project(':dokit')\n\n    if (needKotlinV14()) {\n        implementation rootProject.ext.dependencies[\"kotlin_v14\"]\n    } else {\n        implementation rootProject.ext.dependencies[\"kotlin_v13\"]\n    }\n    def matrixVersion = \"2.0.2\"\n    implementation \"com.tencent.matrix:matrix-hooks:$matrixVersion\"\n    implementation \"com.tencent.matrix:matrix-fd:$matrixVersion\"\n//    implementation('com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.4.2') {\n//        transitive = false\n//    }\n    implementation rootProject.ext.dependencies[\"core-ktx\"]\n    implementation rootProject.ext.dependencies[\"fragment-ktx\"]\n    implementation rootProject.ext.dependencies[\"appcompat\"]\n    implementation rootProject.ext.dependencies[\"recyclerview\"]\n    //okhttp wrap\n    //auto-service\n    implementation rootProject.ext.dependencies[\"auto_service\"]\n    kapt rootProject.ext.dependencies[\"auto_service\"]\n}\n"
  },
  {
    "path": "Android/dokit-pthread-hook/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "Android/dokit-pthread-hook/gradle.properties",
    "content": "ARTIFACT_ID=dokitx-pthread-hook\n"
  },
  {
    "path": "Android/dokit-pthread-hook/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "Android/dokit-pthread-hook/src/androidTest/java/com/kronos/dokit/pthread/ExampleInstrumentedTest.kt",
    "content": "package com.kronos.dokit.pthread\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.junit.runners.AndroidJUnit4\n\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\nimport org.junit.Assert.*\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"com.kronos.dokit.pthread.test\", appContext.packageName)\n    }\n}\n"
  },
  {
    "path": "Android/dokit-pthread-hook/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.kronos.dokit.pthread\">\n\n    <application>\n        <activity\n            android:name=\".ui.PThreadHookUiActivity\"\n            android:theme=\"@style/Theme.AppCompat.Light.NoActionBar\" />\n    </application>\n</manifest>\n"
  },
  {
    "path": "Android/dokit-pthread-hook/src/main/java/com/kronos/dokit/pthread/AutoDumpListener.kt",
    "content": "package com.kronos.dokit.pthread\n\nimport android.app.Activity\nimport android.app.Application\nimport android.os.Bundle\nimport com.didichuxing.doraemonkit.DoKit\n\n/**\n *\n *  @Author LiABao\n *  @Since 2021/11/17\n *\n */\nclass AutoDumpListener : Application.ActivityLifecycleCallbacks {\n\n    init {\n        DoKit.APPLICATION.registerActivityLifecycleCallbacks(this)\n    }\n\n\n    private fun getThreadCount(): Int {\n        return getAllThreads().size\n    }\n\n    private var count = 0\n\n    /**\n     * ps -p `self` -t\n     * See http://man7.org/linux/man-pages/man1/ps.1.html\n     */\n    private fun getAllThreads(): MutableList<Thread> {\n        var group = Thread.currentThread().threadGroup\n        var system: ThreadGroup?\n        do {\n            system = group\n            group = group?.parent\n        } while (group != null)\n        val count = system?.activeCount() ?: 0\n        val threads = arrayOfNulls<Thread>(count)\n        system?.enumerate(threads)\n        val list = mutableListOf<Thread>()\n        threads.forEach {\n            it?.let { it1 -> list.add(it1) }\n        }\n        return list\n    }\n\n    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {\n\n    }\n\n    override fun onActivityStarted(activity: Activity) {\n\n    }\n\n    override fun onActivityResumed(activity: Activity) {\n\n    }\n\n    override fun onActivityPaused(activity: Activity) {\n\n    }\n\n    override fun onActivityStopped(activity: Activity) {\n    }\n\n    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {\n\n    }\n\n    override fun onActivityDestroyed(activity: Activity) {\n        if (count > 3) {\n            return\n        }\n        if (getThreadCount() > Threshold) {\n            DoKit.APPLICATION.dump {\n                forEach {\n\n                }\n\n            }\n        }\n    }\n\n\n    companion object {\n        const val Threshold = 200\n    }\n}\n"
  },
  {
    "path": "Android/dokit-pthread-hook/src/main/java/com/kronos/dokit/pthread/PThreadDumpHelper.kt",
    "content": "package com.kronos.dokit.pthread\n\nimport android.content.Context\nimport android.util.Log\nimport com.tencent.matrix.fd.FDDumpBridge\nimport com.tencent.matrix.hook.pthread.PthreadHook\nimport java.io.File\n\n/**\n *\n *  @Author LiABao\n *  @Since 2021/11/16\n *\n */\n\n// 当前没有删除这部分文件 后续需要考虑的\nfun Context.dump(invoke: MutableList<PThreadEntity>.() -> Unit = {}) {\n    try {\n        val parent = \"$cacheDir/pthread\"\n        val output = \"$parent/pthread_hook_${System.currentTimeMillis() / 1000L}.log\"\n        parent.createDirectory()\n        PthreadHook.INSTANCE.dump(output)\n        fdLimit()\n        val pthreads = getJson(output).parserPThread()\n        pthreads.forEach {\n            Log.i(TAG, \"pthread error :$it\\n\")\n        }\n        invoke.invoke(pthreads)\n    } catch (e: Exception) {\n\n    }\n}\n\nfun String.createDirectory() {\n    val file = File(this)\n    if (!file.exists()) {\n        file.mkdir()\n    }\n}\n\nfun fdLimit() {\n    Log.i(TAG, \"FD limit = \" + FDDumpBridge.getFDLimit())\n}\n\nfun getJson(path: String): String {\n    val stream = File(path).bufferedReader().readText()\n    return stream.apply {\n        Log.i(TAG, \"pThread:$this\")\n    }\n}\n\nconst val TAG = \"PThreadDumpHelper\"\n\n\n"
  },
  {
    "path": "Android/dokit-pthread-hook/src/main/java/com/kronos/dokit/pthread/PThreadEntity.kt",
    "content": "package com.kronos.dokit.pthread\n\nimport org.json.JSONObject\n\n/**\n *\n *  @Author LiABao\n *  @Since 2021/11/17\n *\n */\ndata class PThreadEntity(\n    val hash: String, val native: String, val java: String,\n    val count: Int, val threads: MutableList<ThreadNameEntity>\n) {\n\n\n}\n\ndata class ThreadNameEntity(val tid: String, val name: String)\n\n\nfun JSONObject.parserThreads(): MutableList<ThreadNameEntity> {\n    val jsonArray = optJSONArray(\"threads\")\n    val index = jsonArray?.length() ?: 0\n    val threads = mutableListOf<ThreadNameEntity>()\n    for (index in 0 until index) {\n        val thread = jsonArray.optJSONObject(index)\n        threads.add(ThreadNameEntity(thread.optString(\"tid\"), thread.optString(\"name\")))\n    }\n    return threads\n}\n\nfun String.parserPThread(): MutableList<PThreadEntity> {\n    val jsonObject = JSONObject(this)\n    val pThreadArray = jsonObject.optJSONArray(\"PthreadHook_not_exited\")\n    val pThreads = mutableListOf<PThreadEntity>()\n    pThreadArray?.apply {\n        for (index in 0 until length()) {\n            val jsonObject = optJSONObject(index)\n            pThreads.add(\n                PThreadEntity(\n                    jsonObject.optString(\"hash\"), jsonObject.optString(\"native\"),\n                    jsonObject.optString(\"java\"), jsonObject.optInt(\"count\", 0),\n                    jsonObject.parserThreads()\n                )\n            )\n        }\n    }\n    return pThreads\n}\n\n"
  },
  {
    "path": "Android/dokit-pthread-hook/src/main/java/com/kronos/dokit/pthread/PThreadKit.kt",
    "content": "package com.kronos.dokit.pthread\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content.Intent\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.google.auto.service.AutoService\nimport com.kronos.dokit.pthread.ui.PThreadHookUiActivity\nimport com.tencent.matrix.hook.HookManager.HookFailedException\nimport com.tencent.matrix.hook.pthread.PthreadHook\nimport com.tencent.matrix.hook.pthread.PthreadHook.ThreadStackShrinkConfig\n\n/**\n *\n *  @Author LiABao\n *  @Since 2021/11/16\n *\n */\n@AutoService(AbstractKit::class)\nclass PThreadKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.kit_pthread_hook_kit\n    override val icon: Int\n        get() = R.mipmap.dk_ram\n\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        activity.startActivity(Intent(activity, PThreadHookUiActivity::class.java))\n        return super.onClickWithReturn(activity)\n    }\n\n    override fun onAppInit(context: Context?) {\n        try {\n            val config = ThreadStackShrinkConfig()\n                .setEnabled(true)\n                .addIgnoreCreatorSoPatterns(\".*/app_tbs/.*\")\n                .addIgnoreCreatorSoPatterns(\".*/libany\\\\.so$\")\n            PthreadHook.INSTANCE\n                .addHookThread(\".*\")\n                .setThreadStackShrinkConfig(config)\n                .setThreadTraceEnabled(true)\n                .enableQuicken(true)\n            PthreadHook.INSTANCE.hook()\n        } catch (e: HookFailedException) {\n            e.printStackTrace()\n        }\n        AutoDumpListener()\n    }\n\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_platform_thread_hook\"\n    }\n}\n"
  },
  {
    "path": "Android/dokit-pthread-hook/src/main/java/com/kronos/dokit/pthread/ViewExtensions.kt",
    "content": "package com.kronos.dokit.pthread\n\nimport android.view.View\n\n/**\n *\n *  @Author LiABao\n *  @Since 2021/11/24\n *\n */\n\ninternal fun View.visible() {\n    visibility = View.VISIBLE\n}\n\ninternal fun View.gone() {\n    visibility = View.GONE\n}\n\ninternal fun View.isVisible(): Boolean {\n    return visibility == View.VISIBLE\n}\n"
  },
  {
    "path": "Android/dokit-pthread-hook/src/main/java/com/kronos/dokit/pthread/ui/PThreadHookUiActivity.kt",
    "content": "package com.kronos.dokit.pthread.ui\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.didichuxing.doraemonkit.DoKit\nimport com.kronos.dokit.pthread.R\nimport com.kronos.dokit.pthread.databinding.ActivityPthreadHookBinding\nimport com.kronos.dokit.pthread.dump\n\n/**\n *\n *  @Author LiABao\n *  @Since 2021/11/24\n *\n */\nclass PThreadHookUiActivity : AppCompatActivity() {\n\n    private lateinit var viewBinding :ActivityPthreadHookBinding\n    private val threadAdapter by lazy {\n        ThreadAdapter()\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_pthread_hook)\n        viewBinding = ActivityPthreadHookBinding.inflate(layoutInflater)\n        viewBinding.recyclerView.layoutManager = LinearLayoutManager(this)\n        viewBinding.recyclerView.adapter = threadAdapter\n        DoKit.APPLICATION.dump {\n            threadAdapter.setNewData(this)\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-pthread-hook/src/main/java/com/kronos/dokit/pthread/ui/ThreadAdapter.kt",
    "content": "package com.kronos.dokit.pthread.ui\n\nimport android.text.TextUtils\nimport android.view.ViewGroup\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.widget.brvah.BaseQuickAdapter\nimport com.didichuxing.doraemonkit.widget.brvah.viewholder.BaseViewHolder\nimport com.kronos.dokit.pthread.PThreadEntity\nimport com.kronos.dokit.pthread.R\nimport com.kronos.dokit.pthread.gone\nimport com.kronos.dokit.pthread.visible\n\n/**\n * @Author LiABao\n * @Since 2020/9/3\n */\nclass ThreadAdapter : BaseQuickAdapter<PThreadEntity, BaseViewHolder>(R.layout.recycler_view_thread_hook) {\n    override fun convert(holder: BaseViewHolder, item: PThreadEntity) {\n        if (!TextUtils.isEmpty(item.java)) {\n            holder.itemView.findViewById<ViewGroup>(R.id.javaStackLayout).visible()\n            holder.itemView.findViewById<TextView>(R.id.javaStackTv).text = item.java\n        } else {\n            holder.itemView.findViewById<ViewGroup>(R.id.javaStackLayout).gone()\n        }\n        if (!TextUtils.isEmpty(item.native)) {\n            holder.itemView.findViewById<ViewGroup>(R.id.nativeStackLayout).visible()\n            holder.itemView.findViewById<TextView>(R.id.nativeStackTv).text = item.native\n        } else {\n            holder.itemView.findViewById<ViewGroup>(R.id.nativeStackLayout).gone()\n        }\n        holder.itemView.findViewById<TextView>(R.id.threadCountTv).text = item.count.toString()\n        holder.itemView.findViewById<TextView>(R.id.threadSampleTv).text = item.threads.firstOrNull()?.name ?: \"\"\n        holder.itemView.findViewById<TextView>(R.id.positionTv).text = \"第${holder.adapterPosition + 1}项\"\n        //   holder.itemView.findViewById<TextView>(R.id.threadStackTv).text = item.traceElement()\n    }\n}\n"
  },
  {
    "path": "Android/dokit-pthread-hook/src/main/res/layout/activity_pthread_hook.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/dk_color_FFFFFF\"\n    android:orientation=\"vertical\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"89dp\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        app:dkTitle=\"pthread hook\" />\n\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recyclerView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit-pthread-hook/src/main/res/layout/recycler_view_thread_hook.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:padding=\"10dp\">\n\n    <TextView\n        android:id=\"@+id/positionTv\"\n        android:layout_width=\"match_parent\"\n        android:textSize=\"20sp\"\n        android:textColor=\"#1482f0\"\n        android:layout_height=\"wrap_content\" />\n\n    <LinearLayout\n        android:id=\"@+id/javaStackLayout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"java 堆栈\"\n            android:textColor=\"#1482f0\"\n            android:textSize=\"15sp\" />\n\n        <TextView\n            android:id=\"@+id/javaStackTv\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_weight=\"1\"\n            android:maxLines=\"3\"\n            android:textColor=\"@color/dk_color_333333\" />\n    </LinearLayout>\n\n\n    <LinearLayout\n        android:id=\"@+id/nativeStackLayout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"native 堆栈\"\n            android:textColor=\"#1482f0\"\n            android:textSize=\"15sp\" />\n\n        <TextView\n            android:id=\"@+id/nativeStackTv\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_weight=\"1\"\n            android:maxLines=\"3\"\n            android:textColor=\"@color/dk_color_333333\" />\n    </LinearLayout>\n\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"thread 数量\"\n            android:textColor=\"#1482f0\"\n            android:textSize=\"15sp\" />\n\n        <TextView\n            android:id=\"@+id/threadCountTv\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"right\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_weight=\"1\"\n            android:textColor=\"@color/dk_color_333333\" />\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"thread name 例子\"\n            android:textColor=\"#1482f0\"\n            android:textSize=\"15sp\" />\n\n        <TextView\n            android:id=\"@+id/threadSampleTv\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_weight=\"1\"\n            android:textColor=\"@color/dk_color_333333\" />\n    </LinearLayout>\n</LinearLayout>\n"
  },
  {
    "path": "Android/dokit-pthread-hook/src/main/res/values/values.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"kit_pthread_hook_kit\" translatable=\"false\">pthread hook</string>\n</resources>\n"
  },
  {
    "path": "Android/dokit-pthread-hook/src/test/java/com/kronos/dokit/pthread/ExampleUnitTest.kt",
    "content": "package com.kronos.dokit.pthread\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/.gitignore",
    "content": "/build"
  },
  {
    "path": "Android/dokit-test/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-kapt'\napply from: '../upload.gradle'\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_21\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n\n        lintOptions {\n            abortOnError false\n        }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n\n    /**\n     * 支持ViewBinding\n     */\n    buildFeatures {\n        //viewBinding = true\n    }\n\n}\n\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    testImplementation 'junit:junit:4.12'\n    androidTestImplementation 'junit:junit:4.12'\n    if (needKotlinV14()) {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v14\"]\n    } else {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v13\"]\n    }\n    implementation rootProject.ext.dependencies[\"core-ktx\"]\n    implementation rootProject.ext.dependencies[\"fragment-ktx\"]\n    implementation rootProject.ext.dependencies[\"appcompat\"]\n    implementation rootProject.ext.dependencies[\"cardview\"]\n    implementation rootProject.ext.dependencies[\"recyclerview\"]\n    implementation rootProject.ext.dependencies[\"fragment\"]\n    implementation rootProject.ext.dependencies[\"zxing\"]\n\n    implementation rootProject.ext.dependencies[\"epic\"]\n\n//    implementation rootProject.ext.dependencies[\"sandhook_hooklib\"]\n//    implementation rootProject.ext.dependencies[\"sandhook_nativehook\"]\n//    implementation rootProject.ext.dependencies[\"sandhook_xposedcompat\"]\n\n    implementation rootProject.ext.dependencies[\"volley\"]\n    implementation rootProject.ext.dependencies[\"ktor_server_websockets\"]\n    implementation rootProject.ext.dependencies[\"ktor_client_websockets\"]\n    implementation rootProject.ext.dependencies[\"ktor_server_cio\"]\n    //默认客户端websocket引擎\n    implementation rootProject.ext.dependencies[\"ktor_client_cio\"]\n    //备用客户端websocket引擎\n    compileOnly rootProject.ext.dependencies[\"ktor_client_okhttp\"]\n    compileOnly project(':dokit-util')\n    compileOnly project(':dokit-okhttp-api')\n    //此处需要使用api的形式 向上暴露内部api\n    implementation project(':dokit')\n    //okhttp wrap\n    //auto-service\n    implementation rootProject.ext.dependencies[\"auto_service\"]\n    kapt rootProject.ext.dependencies[\"auto_service\"]\n}\nrepositories {\n    mavenCentral()\n}\n"
  },
  {
    "path": "Android/dokit-test/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "Android/dokit-test/gradle.properties",
    "content": "ARTIFACT_ID=dokitx-test\n"
  },
  {
    "path": "Android/dokit-test/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "Android/dokit-test/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.didichuxing.doraemonkit.test\">\n\n</manifest>\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/DoKitTestManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test\n\nimport com.didichuxing.doraemonkit.kit.test.utils.XposedHookUtil\n\n\n/**\n * didi Create on 2022/4/14 .\n *\n * Copyright (c) 2022/4/14 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/14 10:52 上午\n * @Description 测试功能管理类，管理测试相关功能\n */\n\nobject DoKitTestManager {\n\n\n    private var testMode: TestMode = TestMode.UNKNOWN\n    private val onTestModeChangeListenerSet: MutableSet<OnTestModeChangeListener> = mutableSetOf()\n\n    /**\n     * 是否是主机模式\n     */\n    fun isHostMode(): Boolean {\n        return testMode == TestMode.HOST\n    }\n\n    /**\n     * 是否是从机模式\n     */\n    fun isClientMode(): Boolean {\n        return testMode == TestMode.CLIENT\n    }\n\n    /**\n     * 测试功能是否关闭\n     */\n    fun isClose(): Boolean {\n        return testMode == TestMode.UNKNOWN\n    }\n\n    fun getTestMode(): TestMode {\n        return testMode\n    }\n\n    /**\n     * 开始测试功能\n     * 1、开始hook 或者关闭hook\n     */\n    fun startTest(testMode: TestMode) {\n        if (testMode == TestMode.HOST) {\n            startTestByHostMode()\n        } else if (testMode == TestMode.CLIENT) {\n            startTestByClientMode()\n        } else {\n            closeTest()\n        }\n    }\n\n    /**\n     * 关闭测试功能\n     * 备注：关闭后测试相关功能将不工作\n     */\n    fun closeTest() {\n        testMode = TestMode.UNKNOWN\n        if (XposedHookUtil.isRunTimeHookEnable()) {\n            XposedHookUtil.stopRunTimeHook()\n        }\n    }\n\n    private fun dispatchTestModeChange(testMode: TestMode) {\n        onTestModeChangeListenerSet.forEach {\n            try {\n                it.onTestModeChanged(testMode)\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    fun addOnTestModeChangeListener(listener: OnTestModeChangeListener) {\n        onTestModeChangeListenerSet.add(listener)\n    }\n\n\n    fun removeOnTestModeChangeListener(listener: OnTestModeChangeListener) {\n        onTestModeChangeListenerSet.remove(listener)\n    }\n\n    private fun startTestByHostMode() {\n        testMode = TestMode.HOST\n        if (!XposedHookUtil.isRunTimeHookEnable()) {\n            XposedHookUtil.startRunTimeHook()\n        }\n    }\n\n    private fun startTestByClientMode() {\n        testMode = TestMode.CLIENT\n        if (XposedHookUtil.isRunTimeHookEnable()) {\n            XposedHookUtil.stopRunTimeHook()\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/OnTestModeChangeListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test\n\n\n/**\n * didi Create on 2022/4/14 .\n *\n * Copyright (c) 2022/4/14 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/14 10:52 上午\n * @Description 用一句话说明文件功能\n */\n\ninterface OnTestModeChangeListener {\n\n    fun onTestModeChanged(testMode: TestMode)\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/TestMode.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test\n\n/**\n * 测试工作模式 mode\n */\nenum class TestMode {\n    /**\n     *未知 :不允许执行采集也不允许执行模拟事件\n     */\n    UNKNOWN,\n\n    /**\n     * 主机:采集事件\n     */\n    HOST,\n\n    /**\n     * 从机:模拟执行事件\n     */\n    CLIENT,\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/AccessibilityEventNode.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/14-11:37\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class AccessibilityEventNode(\n    /**\n     * View comm & clicked & long clicked & focused Event Property\n     */\n    val eventType: Int? = -1,\n    val className: String? = \"\",\n    val packageName: String? = \"\",\n    val eventTime: Long? = -1,\n    /**\n     * View  Text Changed Event Property\n     */\n    //val text: MutableList<CharSequence>?,\n    val beforeText: String? = \"\",\n    val fromIndex: Int? = -1,\n    val addedCount: Int? = -1,\n    val removedCount: Int? = -1,\n    /**\n     * View text traversed at movement granularity\n     */\n    val movementGranularity: Int? = -1,\n    val toIndex: Int? = -1,\n    val action: Int? = -1,\n    /**\n     * View scrolled\n     */\n    val maxScrollX: Int? = -1,\n    val maxScrollY: Int? = -1,\n    val scrollDeltaX: Int? = -1,\n    val scrollDeltaY: Int? = -1,\n    val scrollX: Int? = -1,\n    val scrollY: Int? = -1,\n    val scrollable: Boolean? = false,\n    /**\n     * Window state changed &  Window content changed\n     */\n    val contentChangeTypes: Int? = -1,\n    /**\n     *Windows changed\n     */\n    val windowChanges: Int? = -1\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/ActionType.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\nimport android.view.accessibility.AccessibilityEvent\n\n\n/**\n * didi Create on 2022/4/13 .\n *\n * Copyright (c) 2022/4/13 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/13 3:07 下午\n * @Description 用一句话说明文件功能\n */\n\nenum class ActionType(private val id: Int, private val nameText: String) {\n\n    UNKNOWN(0, \"未知\"),\n    ON_CLICK(1, \"点击\"),\n    ON_LONG_CLICK(2, \"长按\"),\n    ON_SCROLL(3, \"滑动\"),\n    ON_FOCUS_CHANGE(4, \"焦点改变\"),\n    ON_INPUT_CHANGE(5, \"输入\"),\n    ON_INPUT_SELECTION_CHANGE(5, \"输入光标位置\"),\n    ON_TOUCH(6, \"TOUCH\"),\n    ON_TOUCH_START(7, \"TOUCH START\"),\n    ON_TOUCH_END(8, \"TOUCH END\"),\n    ON_TOUCH_MOVE(9, \"TOUCH MOCVE\"),\n    ON_DBL_CLICK(10, \"双击\"),\n    ON_CUSTOM_EVENT(30, \"自定义\");\n\n\n    val map: Map<Int, ActionType> = mutableMapOf()\n\n    fun getID(): Int {\n        return id\n    }\n\n    fun getDesc(): String {\n        return nameText\n    }\n\n    companion object {\n        fun valueOf(acc: AccessibilityEvent): ActionType {\n\n            var actionType: ActionType\n            when (acc.eventType) {\n                AccessibilityEvent.TYPE_VIEW_CLICKED -> {\n                    actionType = ON_CLICK\n                }\n                AccessibilityEvent.TYPE_VIEW_LONG_CLICKED -> {\n                    actionType = ON_LONG_CLICK\n                }\n                AccessibilityEvent.TYPE_VIEW_SCROLLED -> {\n                    actionType = ON_SCROLL\n                }\n                AccessibilityEvent.TYPE_VIEW_FOCUSED -> {\n                    actionType = ON_FOCUS_CHANGE\n                }\n                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> {\n                    actionType = ON_INPUT_CHANGE\n                }\n                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED -> {\n                    actionType = ON_INPUT_SELECTION_CHANGE\n                }\n                AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> {\n                    actionType = UNKNOWN\n                }\n                AccessibilityEvent.TYPE_VIEW_SELECTED -> {\n                    actionType = UNKNOWN\n                }\n                else -> {\n                    actionType = UNKNOWN\n                }\n            }\n            return actionType\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/ControlEvent.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\nimport com.didichuxing.doraemonkit.kit.test.utils.DateTime\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/11/17-17:11\n * 描    述：事件对象\n * 修订历史：\n * ================================================\n */\ndata class ControlEvent(\n    val eventId: String = \"\",\n    val eventType: EventType,\n    val params: Map<String, String>? = null,\n    val viewC12c: ViewC12c? = null,\n    val dateTime: String = DateTime.nowTime(),\n    val dateTimeMillis: Long = DateTime.nowTimeMillis(),\n    var diffTime: Long = 0\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/ControlEventManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\nimport android.app.Activity\nimport android.view.View\nimport com.didichuxing.doraemonkit.kit.test.utils.RandomIdentityUtil\n\n/**\n * didi Create on 2022/4/13 .\n *\n * Copyright (c) 2022/4/13 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/13 3:07 下午\n * @Description 提供测试事件行为的采集与行为模拟执行，相关事项不建议通过其他方式处理。\n */\n\nobject ControlEventManager {\n\n    private const val MAX_DIFF_TIME: Long = 15 * 1000\n\n    private var currentEventId: String = \"\"\n    private var lastEventDateTime: Long = System.currentTimeMillis()\n\n    private val onControlEventActionListenerSet: MutableSet<OnControlEventActionListener> = mutableSetOf()\n    private val onControlEventActionProcessListenerSet: MutableSet<OnControlEventActionProcessListener> = mutableSetOf()\n    private val onControlEventInterceptorSet: MutableSet<OnControlEventInterceptor> = mutableSetOf()\n\n    private val controlEventProcessor: ControlEventProcessor = ControlEventProcessor()\n\n    private var lastEventDiffTime: Long = 0\n\n    fun updateEventId(id: String) {\n        if (id.isNullOrEmpty()) {\n            currentEventId = createNextEventId()\n        } else {\n            currentEventId = id\n        }\n    }\n\n    fun getCurrentEventId(): String {\n        return currentEventId\n    }\n\n    fun createNextEventId(): String {\n        return RandomIdentityUtil.createAid()\n    }\n\n    /**\n     * ControlEvent 事件发生/执行\n     * 来自与事件监听\n     */\n    fun onControlEventAction(activity: Activity?, view: View?, controlEvent: ControlEvent) {\n        if (!onControlEventActionIntercept(activity, view, controlEvent)) {\n            updateEventId(controlEvent.eventId)\n            controlEvent.diffTime = lastEventDiffTime\n            lastEventDiffTime = getEventDiffTime()\n            onControlEventActionListenerSet.forEach {\n                it.onControlEventAction(activity, view, controlEvent)\n            }\n        }\n    }\n\n    private fun onControlEventActionIntercept(activity: Activity?, view: View?, controlEvent: ControlEvent): Boolean {\n        onControlEventInterceptorSet.forEach {\n            if (it.onControlEventAction(activity, view, controlEvent)) {\n                return true\n            }\n        }\n        return false\n    }\n\n    /**\n     * 重新设置开始时间\n     */\n    fun resetLastEventDateTime() {\n        lastEventDateTime = System.currentTimeMillis()\n    }\n\n    private fun getEventDiffTime(): Long {\n        val currentTime = System.currentTimeMillis()\n        val diffTime = currentTime - lastEventDateTime\n        lastEventDateTime = currentTime\n        return if (diffTime > MAX_DIFF_TIME) {\n            MAX_DIFF_TIME\n        } else if (diffTime < 0) {\n            0\n        } else {\n            diffTime\n        }\n    }\n\n    fun addOnControlEventInterceptor(eventInterceptor: OnControlEventInterceptor) {\n        onControlEventInterceptorSet.add(eventInterceptor)\n    }\n\n    fun removeOnControlEventInterceptor(eventInterceptor: OnControlEventInterceptor) {\n        onControlEventInterceptorSet.remove(eventInterceptor)\n    }\n\n\n    fun addOnControlEventActionListener(actionListener: OnControlEventActionListener) {\n        onControlEventActionListenerSet.add(actionListener)\n    }\n\n\n    fun removeOnControlEventActionListener(actionListener: OnControlEventActionListener) {\n        onControlEventActionListenerSet.remove(actionListener)\n    }\n\n    /**\n     * 从机接收到 ControlEvent 事件\n     * 来自主机事件，从机进行分发和处理\n     */\n    fun onReceiveControlEventAction(controlEvent: ControlEvent) {\n        controlEventProcessor.processControlEvent(controlEvent)\n    }\n\n    fun addOnControlEventActionProcessListener(processListener: OnControlEventActionProcessListener) {\n        onControlEventActionProcessListenerSet.add(processListener)\n    }\n\n    fun removeOnControlEventActionProcessListener(processListener: OnControlEventActionProcessListener) {\n        onControlEventActionProcessListenerSet.add(processListener)\n    }\n\n    /**\n     * 从机执行 ControlEvent 成功\n     */\n    fun onControlEventProcessSuccess(activity: Activity? = null, view: View? = null, controlEvent: ControlEvent) {\n        onControlEventActionProcessListenerSet.forEach {\n            it.onControlEventProcessSuccess(activity, view, controlEvent)\n        }\n    }\n\n    /**\n     * 从机执行 ControlEvent 失败\n     */\n    fun onControlEventProcessFailed(activity: Activity? = null, view: View? = null, controlEvent: ControlEvent, code: Int, message: String) {\n        onControlEventActionProcessListenerSet.forEach {\n            it.onControlEventProcessFailed(activity, view, controlEvent, code, message)\n        }\n    }\n\n    fun getControlEventProcessor(): ControlEventProcessor {\n        return controlEventProcessor\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/ControlEventProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\nimport com.didichuxing.doraemonkit.kit.test.event.processor.AccessibilityEventProcessor\nimport com.didichuxing.doraemonkit.kit.test.event.processor.CustomEventProcessor\nimport com.didichuxing.doraemonkit.kit.test.event.processor.LifecycleEventProcessor\nimport com.didichuxing.doraemonkit.kit.test.event.processor.TcpMessageEventProcessor\nimport com.didichuxing.doraemonkit.util.*\n\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/11/18-17:32\n * 描    述：\n * 修订历史：\n * ================================================\n */\nclass ControlEventProcessor {\n\n    companion object {\n        const val TAG = \"ControlEventProcessor\"\n    }\n\n    private val accessibilityEventProcessor: AccessibilityEventProcessor = AccessibilityEventProcessor()\n    private val customEventProcessor: CustomEventProcessor = CustomEventProcessor()\n    private val LifecycleEventProcessor: LifecycleEventProcessor = LifecycleEventProcessor()\n    private val TcpMessageEventProcessor: TcpMessageEventProcessor = TcpMessageEventProcessor()\n\n\n    /**\n     * 处理来自主机的消息\n     */\n    fun processControlEvent(controlEvent: ControlEvent) {\n        LogHelper.i(TAG, \"process() event=$controlEvent\")\n        ControlEventManager.updateEventId(controlEvent.eventId)\n\n        when (controlEvent.eventType) {\n            /**\n             * 通用事件处理\n             */\n            EventType.WSE_COMMON_EVENT -> {\n                accessibilityEventProcessor.onControlEventAction(controlEvent)\n            }\n            /**\n             * 自定义事件类型\n             */\n            EventType.WSE_CUSTOM_EVENT -> {\n                customEventProcessor.onControlEventAction(controlEvent)\n            }\n\n            EventType.WSE_TCP_EVENT -> {\n                TcpMessageEventProcessor.onTcpMessageEvent(controlEvent)\n            }\n\n            EventType.ACTIVITY_FINISH -> {\n                LifecycleEventProcessor.onActivityFinish(controlEvent)\n            }\n            /**\n             * 模拟返回事件\n             */\n            EventType.ACTIVITY_BACK_PRESSED -> {\n                LifecycleEventProcessor.onBackPressed(controlEvent)\n            }\n            /**\n             * 切换到后台\n             */\n            EventType.APP_ON_BACKGROUND -> {\n                LifecycleEventProcessor.appOnBackground(controlEvent)\n            }\n            /**\n             * 切换到前台\n             */\n            EventType.APP_ON_FOREGROUND -> {\n                LifecycleEventProcessor.appOnForeground(controlEvent)\n            }\n\n            else -> {\n                LogHelper.e(TAG, \"处理事件类型 wsEvent=$controlEvent\")\n            }\n        }\n    }\n\n\n}\n\n\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/DoKitViewNode.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/2/4-14:36\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class DoKitViewNode(val leftMargin: Int, val topMargin: Int)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/DoKitViewPanelNode.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\ndata class DoKitViewPanelNode(\n    val windowIndex: Int = -1,\n    val className: String\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/EventErrorCode.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\nobject EventErrorCode {\n\n    /**\n     * 当前页面 Activity 不匹配\n     */\n    const val ACTIVITY_NOT_MATCH = 1\n\n    /**\n     * View 找不到\n     */\n    const val VIEW_NOT_FIND = 2\n\n    /**\n     * Window 找不到\n     */\n    const val WINDOW_NOT_FIND = 3\n\n    /**\n     * 动作无法模拟执行\n     */\n    const val ACTION_NOT_MOCK = 4\n\n    /**\n     * 动作被忽略\n     */\n    const val ACTION_IGNORE = 6\n\n    /**\n     * 信息缺失，不完整\n     */\n    const val EVENT_ACTION_LOSE = 7\n\n    /**\n     * 其他错误\n     */\n    const val OTHER = 0\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/EventType.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\n\n/**\n * didi Create on 2022/4/11 .\n *\n * Copyright (c) 2022/4/11 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/11 6:37 下午\n * @Description 用一句话说明文件功能\n */\n\nenum class EventType {\n    /**\n     * app 切换到前台\n     */\n    APP_ON_FOREGROUND,\n\n    /**\n     * app 切换到后台\n     */\n    APP_ON_BACKGROUND,\n\n    /**\n     * 测试事件\n     */\n    WSE_TEST,\n\n    /**\n     * Activity 返回键事件\n     */\n    ACTIVITY_BACK_PRESSED,\n\n    /**\n     * Activity finish\n     */\n    ACTIVITY_FINISH,\n\n    /**\n     * WS 连接成功事件\n     */\n    WSE_CONNECTED,\n\n    /**\n     * WS 断开连接\n     */\n    WSE_CLOSE,\n\n    /**\n     * WS HOST 断开连接\n     */\n    WSE_HOST_CLOSE,\n\n    /**\n     * 通用手势事件\n     */\n    WSE_COMMON_EVENT,\n\n    /**\n     * 自定义手势事件\n     */\n    WSE_CUSTOM_EVENT,\n\n    /**\n     * TCP 消息事件\n     */\n    WSE_TCP_EVENT,\n\n    /**\n     * 从机执行指定事件失败\n     */\n    WSE_PERFORM_FAIL\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/OnControlEventActionListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\nimport android.app.Activity\nimport android.view.View\n\n\n/**\n * didi Create on 2022/4/11 .\n *\n * Copyright (c) 2022/4/11 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/11 3:01 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface OnControlEventActionListener {\n\n    fun onControlEventAction(activity: Activity?, view: View?, event: ControlEvent)\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/OnControlEventActionProcessListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\nimport android.app.Activity\nimport android.view.View\n\n\n/**\n * didi Create on 2022/4/13 .\n *\n * Copyright (c) 2022/4/13 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/13 7:00 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface OnControlEventActionProcessListener {\n\n    fun onControlEventProcessSuccess(activity: Activity?, view: View?, controlEvent: ControlEvent)\n\n\n    fun onControlEventProcessFailed(activity: Activity?, view: View?, controlEvent: ControlEvent, code: Int, message: String)\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/OnControlEventInterceptor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\nimport android.app.Activity\nimport android.view.View\n\n\n/**\n * didi Create on 2022/4/18 .\n *\n * Copyright (c) 2022/4/18 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/18 2:51 下午\n * @Description 事件拦截器，在事件发送时，不需要的事件可以通过这里进行拦截\n */\n\ninterface OnControlEventInterceptor {\n    fun onControlEventAction(activity: Activity?, view: View?, controlEvent: ControlEvent): Boolean\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/Position.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\n/**\n * didi Create on 2022/4/13 .\n *\n * Copyright (c) 2022/4/13 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/13 3:07 下午\n * @Description 用一句话说明文件功能\n */\ndata class Position(\n    val width: Long = -1,\n    val height: Long = -1,\n    val top: Long = -1,\n    val left: Long = -1\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/SystemViewNode.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/15-15:18\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class SystemViewNode(\n    val viewClassName: String = \"\",\n    val viewId: String = \"-1\",\n    val childCount: Int = -1,\n    /**\n     * childIndexOfViewParent = -1  代表是当前控件\n     */\n    val childIndexOfViewParent: Int = 0,\n    /**\n     * 是否是特殊控件 RecycleView\n     */\n    val isSpecialView: Boolean = false,\n    /**\n     * 当前事件发在特殊控件中的position\n     */\n    val currentEventPosition: Int = -1,\n    /**\n     * 是否是事件消费的view\n     */\n    val isCurrentEventView: Boolean = false\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/ViewC12c.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\n/**\n * didi Create on 2022/4/13 .\n *\n * Copyright (c) 2022/4/13 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/13 3:07 下午\n * @Description 用一句话说明文件功能\n */\ndata class ViewC12c(\n    val actionType: ActionType = ActionType.UNKNOWN,\n    val actionName: String = ActionType.UNKNOWN.getDesc(),\n    val params: Map<String, String> = mutableMapOf(),\n    val windowIndex: Int = -1,\n    val windowNode: WindowNode? = null,\n    val viewPath: String = \"\",\n    val viewPaths: MutableList<SystemViewNode>? = null,\n    val accEventType: Int = -1,\n    val accEventInfo: AccessibilityEventNode? = null,\n    var text: String? = \"\",\n    var touchX: Long = -1,\n    var touchY: Long = -1,\n    var scrollX: Long = -1,\n    var scrollY: Long = -1,\n    var inputValue: String = \"\",\n    var position: Position? = null,\n    val doKitViewNode: DoKitViewNode? = null,\n    val doKitViewPanelNode: DoKitViewPanelNode? = null\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/WindowNode.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event\n\n/**\n * didi Create on 2022/4/13 .\n *\n * Copyright (c) 2022/4/13 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/13 3:07 下午\n * @Description 窗口信息，帮助精确查找\n */\ndata class WindowNode(\n    val name: String = \"\", //通常获取到的是 activity 的类名称\n    val windowId: String = \"\",//window 唯一识别id，通过id来实现快速查找窗口\n    val type: Int = 0,//窗口类型  0，其他，1：activity ，2：Dialog\n    val rootViewName: String = \"\",\n    var index: Int = 0\n\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/monitor/AccessibilityEventMonitor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event.monitor;\n\nimport android.os.Build\nimport android.view.View\nimport android.view.ViewParent\nimport android.view.accessibility.AccessibilityEvent\nimport android.widget.*\nimport com.didichuxing.doraemonkit.extension.tagName\nimport com.didichuxing.doraemonkit.kit.core.DoKitFrameLayout\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.kit.test.event.*\nimport com.didichuxing.doraemonkit.kit.test.utils.XposedHookUtil\nimport com.didichuxing.doraemonkit.kit.test.utils.ViewPathUtil\nimport com.didichuxing.doraemonkit.kit.test.utils.WindowPathUtil\nimport com.didichuxing.doraemonkit.util.ConvertUtils\nimport com.didichuxing.doraemonkit.util.LogHelper\n\n/**\n * didi Create on 2022/2/22 .\n * <p>\n * Copyright (c) 2022/2/22 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/2/22 6:06 下午\n * @Description 用一句话说明文件功能\n */\n\nobject AccessibilityEventMonitor {\n\n    const val TAG = \"AccessibilityEventHandler\"\n\n    /**\n     * 通用的ws信息处理\n     */\n    fun onAccessibilityEvent(view: View, event: AccessibilityEvent) {\n        if (!DoKitTestManager.isHostMode()) {\n            return\n        }\n        when (event.eventType) {\n            //点击事件只响应给需要处理的控件\n            AccessibilityEvent.TYPE_VIEW_CLICKED -> {\n                if (view.hasOnClickListeners() || view.parent is AdapterView<*> || view is Button) {\n                    onViewHandleEvent(view, event)\n                }\n            }\n            //针对dokit悬浮窗\n            AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> {\n                if (view is DoKitFrameLayout) {\n                    onViewHandleEvent(view, event)\n                }\n            }\n            /**\n             * view 获取焦点\n             */\n            AccessibilityEvent.TYPE_VIEW_FOCUSED,\n                //针对 EditText 文字改变\n            AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,\n                // represents the event of scrolling a view\n            AccessibilityEvent.TYPE_VIEW_SCROLLED,\n                // represents the event of long clicking on a View like Button, CompoundButton\n            AccessibilityEvent.TYPE_VIEW_LONG_CLICKED,\n                // represents the event of changing the text selection of an EditText\n            AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED -> {\n                onViewHandleEvent(view, event)\n            }\n            AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,\n            AccessibilityEvent.TYPE_VIEW_SELECTED -> {\n                LogHelper.i(TAG, \"TYPE_VIEW_SELECTED or TYPE_WINDOW_STATE_CHANGED ,class=${view.javaClass},view=$view\")\n            }\n            else -> {\n                LogHelper.e(TAG, \"type=${event.eventType},class=${view.javaClass},view=$view\")\n            }\n        }\n    }\n\n    private fun onViewHandleEvent(view: View, accessibilityEvent: AccessibilityEvent) {\n        val activity = ViewPathUtil.getActivity(view)\n        val actionId = ControlEventManager.createNextEventId()\n        val viewC12c: ViewC12c = createViewC12c(view, accessibilityEvent)\n        val controlEvent = ControlEvent(\n            actionId,\n            EventType.WSE_COMMON_EVENT,\n            mutableMapOf(\n                \"activityName\" to activity.tagName\n            ),\n            viewC12c\n        )\n        ControlEventManager.onControlEventAction(activity, view, controlEvent)\n\n    }\n\n\n    private fun createViewC12c(view: View, acc: AccessibilityEvent): ViewC12c {\n        var viewRootImplIndex: Int = -1\n        var viewParents = WindowPathUtil.filterViewRoot(XposedHookUtil.ROOT_VIEWS);\n        viewParents?.let {\n            viewRootImplIndex = if (view.rootView.parent == null) {\n                it.size - 1\n            } else {\n                it.indexOf(view.rootView.parent)\n            }\n        }\n        val actionType: ActionType = ActionType.valueOf(acc)\n        return ViewC12c(\n            actionType = actionType,\n            actionName = actionType.getDesc(),\n            accEventType = acc.eventType,\n            windowIndex = viewRootImplIndex,\n            windowNode = createWindowNode(view.rootView),\n            viewPaths = ViewPathUtil.createViewPathOfWindow(view),\n            accEventInfo = transformAccEventInfo(acc),\n            text = if (view is TextView) {\n                view.text.toString()\n            } else {\n                \"\"\n            },\n            doKitViewPanelNode = createDoKitViewPanel(view),\n            doKitViewNode = createDoKitViewInfo(view)\n        )\n    }\n\n    private fun createWindowNode(rootView: View): WindowNode {\n        val windowNode: WindowNode = WindowPathUtil.createWindowNode(rootView)\n        val parents: List<ViewParent> = WindowPathUtil.filterViewRoot(XposedHookUtil.ROOT_VIEWS)\n        val windows: List<ViewParent> = WindowPathUtil.filterWindowViewRoot(parents, windowNode)\n        val index = windows.indexOf(rootView.parent)\n        windowNode.index = index\n        return windowNode\n    }\n\n    private fun createDoKitViewPanel(view: View): DoKitViewPanelNode? {\n        if (view.rootView is DoKitFrameLayout) {\n            val viewParents = WindowPathUtil.filterDoKitViewRoot(XposedHookUtil.ROOT_VIEWS)\n            val windowIndex = viewParents.indexOf(view.rootView.parent)\n            return DoKitViewPanelNode(windowIndex = windowIndex, className = (view.rootView as DoKitFrameLayout).title)\n        }\n        return null\n    }\n\n    /**\n     * 创建dokitview info\n     */\n    private fun createDoKitViewInfo(view: View): DoKitViewNode? {\n        if (view !is DoKitFrameLayout) {\n            return null\n        }\n\n        if (view.layoutParams !is FrameLayout.LayoutParams) {\n            return null\n        }\n\n        return DoKitViewNode(\n            (view.layoutParams as FrameLayout.LayoutParams).leftMargin,\n            (view.layoutParams as FrameLayout.LayoutParams).topMargin\n        )\n\n    }\n\n    private fun transformAccEventInfo(acc: AccessibilityEvent): AccessibilityEventNode {\n        return AccessibilityEventNode(\n            acc.eventType,\n            acc.className?.toString(),\n            acc.packageName?.toString(),\n            acc.eventTime,\n            acc.beforeText?.toString(),\n            acc.fromIndex,\n            acc.addedCount,\n            acc.removedCount,\n            acc.movementGranularity,\n            acc.toIndex,\n            acc.action,\n            ConvertUtils.px2dp(acc.maxScrollX.toFloat()),\n            ConvertUtils.px2dp(acc.maxScrollY.toFloat()),\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n                ConvertUtils.px2dp(acc.scrollDeltaX.toFloat())\n            } else {\n                -1\n            },\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n                ConvertUtils.px2dp(acc.scrollDeltaY.toFloat())\n            } else {\n                -1\n            },\n            ConvertUtils.px2dp(acc.scrollX.toFloat()),\n            ConvertUtils.px2dp(acc.scrollY.toFloat()),\n            acc.isScrollable,\n            acc.contentChangeTypes,\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n                acc.windowChanges\n            } else {\n                -1\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/monitor/CustomEventMonitor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event.monitor\n\nimport android.view.View\nimport android.widget.TextView\nimport com.didichuxing.doraemonkit.extension.tagName\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEventManager\nimport com.didichuxing.doraemonkit.kit.test.utils.XposedHookUtil\nimport com.didichuxing.doraemonkit.kit.test.event.ViewC12c\nimport com.didichuxing.doraemonkit.kit.test.event.EventType\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\nimport com.didichuxing.doraemonkit.kit.test.utils.ViewPathUtil\nimport com.didichuxing.doraemonkit.kit.test.utils.WindowPathUtil\n\n/**\n * didi Create on 2022/4/13 .\n *\n * Copyright (c) 2022/4/13 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/13 3:07 下午\n * @Description 用一句话说明文件功能\n */\nobject CustomEventMonitor {\n\n    /**\n     * 发送自定义事件\n     * @return view\n     * @return eventType 事件类型\n     * @return param 自定义参数\n     */\n    fun onCustomEvent(eventType: String, view: View? = null, param: Map<String, String>? = null) {\n        if (DoKitTestManager.isHostMode()) {\n            val activity = ViewPathUtil.getActivity(view)\n            val actionId = ControlEventManager.createNextEventId()\n            val viewC12c = createViewC12c(view, eventType, param)\n\n            val controlEvent = ControlEvent(\n                actionId,\n                EventType.WSE_CUSTOM_EVENT,\n                mutableMapOf(\n                    \"activityName\" to activity::class.tagName\n                ),\n                viewC12c\n            )\n            ControlEventManager.onControlEventAction(activity, view, controlEvent)\n        }\n    }\n\n    private fun createViewC12c(view: View?, eventType: String, param: Map<String, String>?): ViewC12c {\n        var viewRootImplIndex: Int = -1\n        if (view != null) {\n            val viewParents = WindowPathUtil.filterViewRoot(XposedHookUtil.ROOT_VIEWS);\n            viewParents?.let {\n                viewRootImplIndex = if (view.rootView.parent == null) {\n                    it.size - 1\n                } else {\n                    it.indexOf(view.rootView.parent)\n                }\n            }\n        }\n\n        return ViewC12c(\n            actionName = eventType,\n            params = param ?: mutableMapOf(),\n            windowIndex = viewRootImplIndex,\n            viewPaths = if (view != null) {\n                ViewPathUtil.createViewPathOfWindow(view)\n            } else {\n                null\n            },\n            text = if (view is TextView) {\n                view.text.toString()\n            } else {\n                \"\"\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/monitor/LifecycleEventMonitor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event.monitor\n\nimport android.app.Activity\nimport com.didichuxing.doraemonkit.extension.tagName\nimport com.didichuxing.doraemonkit.kit.core.DoKitLifecycleInterface\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEventManager\nimport com.didichuxing.doraemonkit.kit.test.event.EventType\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/9-17:40\n * 描    述：\n * 修订历史：\n * ================================================\n * 监听页面生命周期并处理\n */\nclass LifecycleEventMonitor : DoKitLifecycleInterface {\n    companion object {\n        const val TAG = \"LifecycleEventMonitor\"\n    }\n\n\n    override fun onBackPressed(activity: Activity) {\n        onLifecycleEvent(activity, EventType.ACTIVITY_BACK_PRESSED)\n    }\n\n    override fun onForeground(activity: Activity) {\n        onLifecycleEvent(activity, EventType.APP_ON_FOREGROUND)\n    }\n\n    override fun onBackground(activity: Activity) {\n        onLifecycleEvent(activity, EventType.APP_ON_BACKGROUND)\n    }\n\n    private fun onLifecycleEvent(activity: Activity, eventType: EventType) {\n        if (DoKitTestManager.isHostMode()) {\n            val actionId = ControlEventManager.createNextEventId()\n            val controlEvent = ControlEvent(\n                actionId,\n                eventType,\n                mutableMapOf(\n                    \"command\" to \"onBackground\",\n                    \"activityName\" to activity::class.tagName\n                ),\n                null\n            )\n            ControlEventManager.onControlEventAction(activity, null, controlEvent)\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/monitor/TcpMessageEventMonitor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event.monitor\n\nimport com.didichuxing.doraemonkit.extension.tagName\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEventManager\nimport com.didichuxing.doraemonkit.kit.test.event.EventType\nimport com.didichuxing.doraemonkit.kit.test.utils.RandomIdentityUtil\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\nimport com.didichuxing.doraemonkit.util.ActivityUtils\n\n/**\n * didi Create on 2022/4/13 .\n *\n * Copyright (c) 2022/4/13 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/13 3:07 下午\n * @Description 用一句话说明文件功能\n */\nobject TcpMessageEventMonitor {\n\n    /**\n     * 发送自定义事件\n     * @return view\n     * @return eventType 事件类型\n     * @return param 自定义参数\n     */\n    fun onTcpMessageEvent(eventType: String, message: String = \"\") {\n        if (DoKitTestManager.isHostMode()) {\n            val actionId = RandomIdentityUtil.createAid()\n            val wsEvent = ControlEvent(\n                actionId,\n                EventType.WSE_TCP_EVENT,\n                mutableMapOf(\n                    \"activityName\" to ActivityUtils.getTopActivity()::class.tagName,\n                    \"eventType\" to eventType,\n                    \"message\" to message\n                ),\n                null\n            )\n            ControlEventManager.onControlEventAction(null, null, wsEvent)\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/processor/AbstractEventProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event.processor\n\nimport android.app.Activity\nimport android.text.TextUtils\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.ViewParent\nimport com.didichuxing.doraemonkit.extension.tagName\nimport com.didichuxing.doraemonkit.kit.test.event.*\nimport com.didichuxing.doraemonkit.kit.test.utils.XposedHookUtil\nimport com.didichuxing.doraemonkit.kit.test.utils.ViewPathUtil\nimport com.didichuxing.doraemonkit.kit.test.utils.WindowPathUtil\nimport com.didichuxing.doraemonkit.util.ActivityUtils\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport com.didichuxing.doraemonkit.util.ReflectUtils\n\n\n/**\n * didi Create on 2022/4/13 .\n *\n * Copyright (c) 2022/4/13 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/13 6:53 下午\n * @Description 用一句话说明文件功能\n */\n\nabstract class AbstractEventProcessor {\n\n    fun onControlEventAction(controlEvent: ControlEvent) {\n        if (!checkActivityNow(controlEvent)) {\n            onControlEventProcessFailed(\n                controlEvent = controlEvent,\n                code = EventErrorCode.ACTIVITY_NOT_MATCH,\n                message = \"activity 不匹配\"\n            )\n        }\n        if (controlEvent.viewC12c == null) {\n            onControlEventProcessFailed(\n                controlEvent = controlEvent,\n                code = EventErrorCode.EVENT_ACTION_LOSE,\n                message = \"event 信息缺失\"\n            )\n        }\n        controlEvent.viewC12c?.let {\n            val viewC12c = it\n            val viewRoot: ViewParent? = findWindowRootView(it)\n            if (viewRoot == null) {\n                val decorView: ViewGroup = ActivityUtils.getTopActivity().window.decorView as ViewGroup\n                onControlEventAction(controlEvent, viewC12c, decorView)\n            } else {\n                val decorView: ViewGroup = ReflectUtils.reflect(viewRoot).field(\"mView\").get<View>() as ViewGroup\n                onControlEventAction(controlEvent, viewC12c, decorView)\n            }\n        }\n\n    }\n\n    private fun onControlEventAction(controlEvent: ControlEvent, viewC12c: ViewC12c, decorView: ViewGroup) {\n        val targetView: View? = ViewPathUtil.findViewByViewParentInfo(decorView, viewC12c.viewPaths)\n        val activity = ViewPathUtil.getActivity(targetView)\n        if (targetView == null && forceViewCheck()) {\n            onControlEventProcessFailed(\n                activity = activity,\n                controlEvent = controlEvent,\n                code = EventErrorCode.VIEW_NOT_FIND,\n                message = \"view 找不到\"\n            )\n        } else {\n            onSimulationEventAction(activity, targetView, viewC12c, controlEvent)\n        }\n    }\n\n    open fun forceViewCheck(): Boolean {\n        return true\n    }\n\n    abstract fun onSimulationEventAction(activity: Activity, view: View?, viewC12c: ViewC12c, controlEvent: ControlEvent)\n\n    /**\n     * 从机执行 ControlEvent 成功\n     */\n    protected fun onControlEventProcessSuccess(activity: Activity? = null, view: View? = null, controlEvent: ControlEvent) {\n        ControlEventManager.onControlEventProcessSuccess(activity, view, controlEvent)\n    }\n\n    /**\n     * 从机执行 ControlEvent 失败\n     */\n    protected fun onControlEventProcessFailed(activity: Activity? = null, view: View? = null, controlEvent: ControlEvent, code: Int, message: String) {\n        ControlEventManager.onControlEventProcessFailed(activity, view, controlEvent, code, message)\n    }\n\n\n    private fun checkActivityNow(controlEvent: ControlEvent): Boolean {\n        controlEvent.params?.let {\n            val activityName = it[\"activityName\"]\n            val nowActivity: Activity? = ActivityUtils.getTopActivity()\n            val nowActivityName: String = nowActivity?.tagName ?: \"\"\n            if (activityName != null && activityName != nowActivityName) {\n                return false\n            }\n        }\n        return true\n    }\n\n\n    private fun findWindowRootView(viewC12c: ViewC12c): ViewParent? {\n        if (XposedHookUtil.ROOT_VIEWS == null) {\n            return null\n        }\n        var viewParent: ViewParent? = null\n        val panelNode = viewC12c.doKitViewPanelNode\n\n        if (panelNode != null) {\n            //增强查找DoKit悬浮窗口\n            val viewParents = WindowPathUtil.filterDoKitViewRoot(XposedHookUtil.ROOT_VIEWS)\n            if (panelNode.windowIndex >= viewParents.size) {\n                return null\n            }\n            val doKitViewParent = viewParents[panelNode.windowIndex]\n            val title = WindowPathUtil.getsDoKitViewRootTitle(doKitViewParent)\n            if (TextUtils.equals(panelNode.className, title)) {\n                viewParent = doKitViewParent\n            } else {\n                LogHelper.e(\"DoKit\", \"findWindowRootView() check failed.\")\n            }\n        } else {\n            if (viewC12c.windowIndex >= 0) {\n                val windowNode = viewC12c.windowNode\n                if (windowNode != null) {\n                    val parents: List<ViewParent> = WindowPathUtil.filterViewRoot(XposedHookUtil.ROOT_VIEWS)\n                    val windows: List<ViewParent> = WindowPathUtil.filterWindowViewRoot(parents, viewC12c.windowNode)\n                    if (windowNode.index >= 0 && windowNode.index < windows.size) {\n                        viewParent = windows[windowNode.index]\n                    } else {\n                        if (windows.isNotEmpty()) {\n                            viewParent = windows.last()\n                        }\n                    }\n                } else {\n                    viewParent = WindowPathUtil.findViewRoot(XposedHookUtil.ROOT_VIEWS, viewC12c.windowIndex)\n                }\n            }\n        }\n        return viewParent\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/processor/AccessibilityEventProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event.processor\n\nimport android.app.Activity\nimport android.view.View\nimport android.view.accessibility.AccessibilityEvent\nimport android.widget.*\nimport androidx.core.widget.NestedScrollView\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewpager.widget.ViewPager\nimport com.didichuxing.doraemonkit.extension.isFalse\nimport com.didichuxing.doraemonkit.kit.core.DoKitFrameLayout\nimport com.didichuxing.doraemonkit.kit.test.event.ViewC12c\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\nimport com.didichuxing.doraemonkit.kit.test.event.EventErrorCode\nimport com.didichuxing.doraemonkit.util.*\n\n/**\n * didi Create on 2022/4/13 .\n *\n * Copyright (c) 2022/4/13 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/13 3:07 下午\n * @Description 用一句话说明文件功能\n */\nclass AccessibilityEventProcessor : AbstractEventProcessor() {\n\n\n    override fun onSimulationEventAction(activity: Activity, view: View?, viewC12c: ViewC12c, controlEvent: ControlEvent) {\n        if (view != null){\n            dispatchSimulationEventAction(activity, view, viewC12c, controlEvent)\n        }\n    }\n\n    /**\n     * 通用的处理方式\n     */\n    private fun dispatchSimulationEventAction(activity: Activity, targetView: View, viewC12c: ViewC12c, controlEvent: ControlEvent) {\n        when (viewC12c.accEventType) {\n            //单击\n            AccessibilityEvent.TYPE_VIEW_CLICKED -> {\n                onSimulationClickEvent(activity, targetView, viewC12c, controlEvent)\n            }\n            //长按\n            AccessibilityEvent.TYPE_VIEW_LONG_CLICKED -> {\n                onSimulationLongClickEvent(activity, targetView, viewC12c, controlEvent)\n            }\n            // view 获取焦点\n            AccessibilityEvent.TYPE_VIEW_FOCUSED -> {\n                onSimulationViewFocusEvent(activity, targetView, viewC12c, controlEvent)\n            }\n            //EditText 文字改变\n            AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> {\n                onSimulationTextChangeEvent(activity, targetView, viewC12c, controlEvent)\n            }\n            //EditText 光标变动\n            AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED -> {\n                onSimulationTextSelectionChangedEvent(activity, targetView, viewC12c, controlEvent)\n            }\n            //滚动\n            AccessibilityEvent.TYPE_VIEW_SCROLLED -> {\n                onSimulationViewScrollEvent(activity, targetView, viewC12c, controlEvent)\n            }\n            //处理dokit view的拖动\n            AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> {\n                onSimulationViewMoveEvent(activity, targetView, viewC12c, controlEvent)\n            }\n            else -> {\n                onControlEventProcessFailed(\n                    activity = activity,\n                    view = targetView,\n                    controlEvent = controlEvent,\n                    code = EventErrorCode.ACTION_IGNORE,\n                    message = \"动作被忽略\"\n                )\n                return\n            }\n        }\n\n        onControlEventProcessSuccess(activity, targetView, controlEvent)\n    }\n\n    private fun onSimulationClickEvent(activity: Activity, targetView: View, viewC12c: ViewC12c, controlEvent: ControlEvent) {\n        if (targetView is Switch) {\n            targetView.toggle()\n        } else if (targetView is CheckBox) {\n            targetView.isChecked = !targetView.isChecked\n        } else if (targetView is RadioButton) {\n            targetView.isChecked = true\n        } else {\n            if (targetView.hasOnClickListeners()) {\n                targetView.performClick().isFalse {\n                    ToastUtils.showShort(\"模拟点击事件失败\")\n                }\n            } else {\n                //兼容adapter\n                if (targetView.parent is AdapterView<*>) {\n                    (targetView.parent as AdapterView<*>).performItemClick(\n                        targetView,\n                        viewC12c.viewPaths?.get(1)!!.currentEventPosition,\n                        targetView.id.toLong()\n                    )\n                } else {\n                    ToastUtils.showShort(\"该控件没有设置点击事件\")\n                }\n            }\n        }\n    }\n\n    private fun onSimulationLongClickEvent(activity: Activity, targetView: View, viewC12c: ViewC12c, controlEvent: ControlEvent) {\n        when (targetView) {\n            is EditText -> {\n                targetView.selectAll()\n            }\n            else -> {\n                targetView.performLongClick().isFalse {\n                    ToastUtils.showShort(\"模拟长按事件失败\")\n                }\n            }\n        }\n    }\n\n    private fun onSimulationViewFocusEvent(activity: Activity, targetView: View, viewC12c: ViewC12c, controlEvent: ControlEvent) {\n        targetView.requestFocus().isFalse {\n            ToastUtils.showShort(\"获取焦点失败\")\n        }\n    }\n\n    private fun onSimulationTextChangeEvent(activity: Activity, targetView: View, viewC12c: ViewC12c, controlEvent: ControlEvent) {\n        if (targetView is TextView) {\n            targetView.text = viewC12c.text\n        }\n    }\n\n    private fun onSimulationTextSelectionChangedEvent(activity: Activity, targetView: View, viewC12c: ViewC12c, controlEvent: ControlEvent) {\n        if (targetView is EditText) {\n            targetView.setSelection(\n                viewC12c.accEventInfo?.fromIndex!!,\n                viewC12c.accEventInfo.toIndex!!\n            )\n        }\n    }\n\n\n    private fun onSimulationViewScrollEvent(activity: Activity, targetView: View, viewC12c: ViewC12c, controlEvent: ControlEvent) {\n        simulationScrollEvent(activity, targetView, viewC12c, controlEvent)\n    }\n\n    private fun onSimulationViewMoveEvent(activity: Activity, targetView: View, viewC12c: ViewC12c, controlEvent: ControlEvent) {\n        if (targetView is DoKitFrameLayout && targetView.layoutParams is FrameLayout.LayoutParams) {\n            val layoutParams: FrameLayout.LayoutParams = targetView.layoutParams as FrameLayout.LayoutParams\n\n            layoutParams.leftMargin = viewC12c.doKitViewNode?.leftMargin!!\n            layoutParams.topMargin = viewC12c.doKitViewNode.topMargin\n            targetView.layoutParams = layoutParams\n        }\n    }\n\n    /**\n     * 处理滑动事件\n     */\n    private fun simulationScrollEvent(activity: Activity, targetView: View, viewC12c: ViewC12c, controlEvent: ControlEvent) {\n        when (targetView) {\n            is ScrollView -> {\n                viewC12c.accEventInfo?.let { accEventInfo ->\n                    targetView.smoothScrollTo(\n                        ConvertUtils.dp2px(accEventInfo.scrollX!!.toFloat()),\n                        ConvertUtils.dp2px(accEventInfo.scrollY!!.toFloat())\n                    )\n                }\n            }\n\n            is NestedScrollView -> {\n                viewC12c.accEventInfo?.let { accEventInfo ->\n                    targetView.smoothScrollTo(\n                        ConvertUtils.dp2px(accEventInfo.scrollX!!.toFloat()),\n                        ConvertUtils.dp2px(accEventInfo.scrollY!!.toFloat())\n                    )\n                }\n            }\n\n            is HorizontalScrollView -> {\n                viewC12c.accEventInfo?.let { accEventInfo ->\n                    targetView.smoothScrollTo(\n                        ConvertUtils.dp2px(accEventInfo.scrollX!!.toFloat()),\n                        ConvertUtils.dp2px(accEventInfo.scrollY!!.toFloat())\n                    )\n                }\n            }\n\n            is RecyclerView -> {\n                viewC12c.accEventInfo?.let { accEventInfo ->\n                    when {\n                        accEventInfo.fromIndex!! == 0 -> {\n                            targetView.smoothScrollToPosition(0)\n                        }\n                        accEventInfo.toIndex!! == targetView.adapter?.itemCount!! - 1 -> {\n                            targetView.smoothScrollToPosition(accEventInfo.toIndex)\n                        }\n                        else -> {\n                            moveToPosition(targetView, accEventInfo.fromIndex)\n                        }\n                    }\n\n                }\n            }\n\n            is ListView -> {\n                viewC12c.accEventInfo?.let { accEventInfo ->\n                    when {\n                        accEventInfo.fromIndex!! == 0 -> {\n                            targetView.smoothScrollToPosition(0)\n                        }\n                        accEventInfo.toIndex!! + 1 == targetView.adapter?.count -> {\n                            targetView.smoothScrollToPosition(accEventInfo.toIndex)\n                        }\n                        else -> {\n                            moveToPosition(targetView, accEventInfo.fromIndex)\n                        }\n                    }\n                }\n            }\n\n            is ViewPager -> {\n                viewC12c.accEventInfo?.let {\n                    targetView.setCurrentItem(it.toIndex!!, true)\n                }\n            }\n\n            else -> {\n                onControlEventProcessFailed(\n                    activity = activity,\n                    view = targetView,\n                    controlEvent = controlEvent,\n                    code = EventErrorCode.ACTION_IGNORE,\n                    message = \"动作被忽略\"\n                )\n            }\n        }\n\n\n    }\n\n    private fun moveToPosition(rv: RecyclerView, position: Int) {\n        if (position != -1) {\n            rv.scrollToPosition(position)\n            if (rv.layoutManager is LinearLayoutManager) {\n                val mLayoutManager = rv.layoutManager as LinearLayoutManager\n                mLayoutManager.scrollToPositionWithOffset(position, 0)\n            }\n        }\n    }\n\n    private fun moveToPosition(lv: ListView, position: Int) {\n        if (position != -1) {\n            lv.smoothScrollToPositionFromTop(position, 0)\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/processor/CustomEventProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event.processor\n\nimport android.app.Activity\nimport android.view.View\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\nimport com.didichuxing.doraemonkit.kit.test.event.ViewC12c\nimport com.didichuxing.doraemonkit.util.*\n\n/**\n * didi Create on 2022/4/13 .\n *\n * Copyright (c) 2022/4/13 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/13 3:07 下午\n * @Description 用一句话说明文件功能\n */\nclass CustomEventProcessor : AbstractEventProcessor() {\n\n\n    override fun onSimulationEventAction(activity: Activity, view: View?, viewC12c: ViewC12c, controlEvent: ControlEvent) {\n        //执行自定义事件\n        DoKitManager.MC_CLIENT_PROCESSOR?.process(\n            ActivityUtils.getTopActivity(),\n            view,\n            viewC12c.actionName,\n            viewC12c.params\n        )\n        onControlEventProcessSuccess(activity, view, controlEvent)\n    }\n\n    override fun forceViewCheck(): Boolean {\n        return false\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/processor/LifecycleEventProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event.processor\n\nimport android.app.Activity\nimport android.view.View\nimport com.didichuxing.doraemonkit.extension.tagName\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\nimport com.didichuxing.doraemonkit.kit.test.event.ViewC12c\nimport com.didichuxing.doraemonkit.util.ActivityUtils\nimport com.didichuxing.doraemonkit.util.DoKitCommUtil\n\n/**\n * didi Create on 2022/4/13 .\n *\n * Copyright (c) 2022/4/13 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/13 3:07 下午\n * @Description 用一句话说明文件功能\n */\nclass LifecycleEventProcessor : AbstractEventProcessor() {\n\n    override fun onSimulationEventAction(activity: Activity, view: View?, viewC12c: ViewC12c, controlEvent: ControlEvent) {\n\n    }\n\n    fun appOnForeground(controlEvent: ControlEvent) {\n        val activityName = controlEvent.params?.get(\"activityName\")\n        activityName?.let {\n            val clazz: Class<Activity> =\n                Class.forName(it) as Class<Activity>\n            DoKitCommUtil.changeAppOnForeground(clazz)\n        }\n\n        onControlEventProcessSuccess(controlEvent = controlEvent)\n    }\n\n    fun appOnBackground(controlEvent: ControlEvent) {\n        ActivityUtils.startHomeActivity()\n        onControlEventProcessSuccess(controlEvent = controlEvent)\n    }\n\n    fun onBackPressed(controlEvent: ControlEvent) {\n        val topActivity = ActivityUtils.getTopActivity()\n        if (controlEvent.params?.get(\"activityName\") == topActivity::class.tagName) {\n            topActivity.onBackPressed()\n        }\n        onControlEventProcessSuccess(controlEvent = controlEvent)\n    }\n\n    fun onActivityFinish(controlEvent: ControlEvent) {\n        val topActivity = ActivityUtils.getTopActivity()\n        if (controlEvent.params?.get(\"activityName\") == topActivity::class.tagName) {\n            topActivity.finish()\n        }\n        onControlEventProcessSuccess(controlEvent = controlEvent)\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/event/processor/TcpMessageEventProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.event.processor\n\n\nimport android.app.Activity\nimport android.view.View\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\nimport com.didichuxing.doraemonkit.kit.test.event.ViewC12c\nimport com.didichuxing.doraemonkit.kit.test.mock.tcp.TcpMockManager\n\n/**\n * didi Create on 2022/4/13 .\n *\n * Copyright (c) 2022/4/13 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/13 3:07 下午\n * @Description 用一句话说明文件功能\n */\nclass TcpMessageEventProcessor : AbstractEventProcessor() {\n\n    override fun onSimulationEventAction(activity: Activity, view: View?, viewC12c: ViewC12c, controlEvent: ControlEvent) {\n\n    }\n\n    fun onTcpMessageEvent(controlEvent: ControlEvent) {\n        val type: String? = controlEvent.params?.get(\"eventType\")\n        val message: String? = controlEvent.params?.get(\"message\")\n        TcpMockManager.onTcpMessageEvent(type ?: \"\", message ?: \"\")\n        onControlEventProcessSuccess(controlEvent = controlEvent)\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/hook/AccessibilityGetInstanceMethodHook.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.hook\n\nimport de.robv.android.xposed.XC_MethodHook\n\n\n/**\n * didi Create on 2022/4/14 .\n *\n * Copyright (c) 2022/4/14 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/14 11:15 上午\n * @Description 用一句话说明文件功能\n */\n\nclass AccessibilityGetInstanceMethodHook : XC_MethodHook() {\n\n    override fun afterHookedMethod(param: MethodHookParam?) {\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/hook/ViewOnClickListenerEventHook.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.hook\n\nimport android.view.View\nimport de.robv.android.xposed.XC_MethodHook\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/11/30-19:55\n * 描    述：hook view#OnTouchListener#onTouch\n * ================================================\n */\nclass ViewOnClickListenerEventHook : XC_MethodHook() {\n\n    companion object {\n        const val TAG = \"ViewOnClickListenerEventHook\"\n    }\n\n    override fun beforeHookedMethod(param: MethodHookParam?) {\n        super.beforeHookedMethod(param)\n        param?.let {\n            val listener = it.args[0]\n            if (listener != null && listener is View.OnClickListener) {\n                it.args[0] = ViewOnClickListenerProxy(listener)\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/hook/ViewOnClickListenerProxy.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.hook\n\nimport android.view.View\nimport com.didichuxing.doraemonkit.extension.doKitGlobalScope\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/8/9-11:41\n * 描    述： View#performClick()\n * 方法内部会优先调用 mOnClickListener.onClick\n * 再发送 sendAccessibilityEvent 如果此时view被销毁\n * AccessibilityEvent 会被中断导致捕获不到对应的手势\n *\n * 修订历史：\n * ================================================\n * 延迟事件处理，解决中断问题\n */\nclass ViewOnClickListenerProxy(private val listener: View.OnClickListener?) : View.OnClickListener {\n    companion object {\n        const val TAG = \"ProxyOnClickListener\"\n    }\n\n    override fun onClick(v: View?) {\n        //人为的制造延迟解决AccessibilityEvent被中断的问题\n        if (DoKitTestManager.isHostMode()) {\n            doKitGlobalScope.launch {\n                withContext(Dispatchers.IO) {\n                    delay(100)\n                }\n                listener?.onClick(v)\n            }\n        } else {\n            listener?.onClick(v)\n        }\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/hook/ViewOnInitializeAccessibilityEventHook.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.hook\n\nimport android.view.View\nimport android.view.accessibility.AccessibilityEvent\nimport com.didichuxing.doraemonkit.kit.core.DoKitFrameLayout\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.kit.test.event.monitor.AccessibilityEventMonitor\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport de.robv.android.xposed.XC_MethodHook\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/11/30-19:55\n * 描    述：View# onInitializeAccessibility hook\n * wiki:https://blog.csdn.net/u011391629/article/details/83343312\n * http://lionoggo.com/2018/03/22/%E6%B7%B1%E5%85%A5Android%E8%BE%85%E5%8A%A9%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E4%B8%8E%E8%AE%BE%E8%AE%A1/\n * https://blog.csdn.net/omnispace/article/details/70598515\n * 修订历史：sendAccessibilityEventUncheckedInternal 中会被调用\n * ================================================\n *\n */\nclass ViewOnInitializeAccessibilityEventHook : XC_MethodHook() {\n\n    companion object {\n        const val TAG = \"ViewOnInitializeAccessibilityEventHook\"\n    }\n\n    /**\n     * https://developer.android.google.cn/reference/android/view/accessibility/AccessibilityEvent\n     */\n    override fun afterHookedMethod(param: MethodHookParam?) {\n        super.afterHookedMethod(param)\n        if (!DoKitTestManager.isHostMode()) {\n            return\n        }\n        param?.let {\n            val view = it.thisObject as View\n            val accessibilityEvent = it.args[0] as AccessibilityEvent\n            LogHelper.i(TAG, \"view==>${view},accessibilityEvent=${accessibilityEvent},eventType===${Integer.toHexString(accessibilityEvent.eventType)}\")\n\n            if (view is DoKitFrameLayout && accessibilityEvent.eventType == AccessibilityEvent.TYPE_VIEW_FOCUSED) {\n                return\n            }\n            AccessibilityEventMonitor.onAccessibilityEvent(view, accessibilityEvent)\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/HttpMockInterceptor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock\n\nimport com.didichuxing.doraemonkit.kit.test.mock.data.HttpMatchedInfo\nimport com.didichuxing.doraemonkit.kit.test.mock.data.McMockKey\nimport com.didichuxing.doraemonkit.kit.test.mock.data.McResInfo\n\n\n/**\n * didi Create on 2022/1/21 .\n *\n * Copyright (c) 2022/1/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/1/21 2:54 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface HttpMockInterceptor {\n\n    fun intercept(key: McMockKey, mcResInfo: McResInfo<HttpMatchedInfo>): McResInfo<HttpMatchedInfo>\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/MockManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock\n\nimport com.didichuxing.doraemonkit.kit.connect.data.TextPackage\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.kit.test.mock.proxy.*\nimport com.didichuxing.doraemonkit.kit.test.utils.XposedHookUtil\nimport com.didichuxing.doraemonkit.util.GsonUtils\n\n\n/**\n * didi Create on 2022/4/14 .\n *\n * Copyright (c) 2022/4/14 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/14 10:52 上午\n * @Description 数据mocl功能管理类，管理一机多控及自动化测试数据\n */\n\nobject MockManager {\n\n    @Deprecated(\"\")\n    var MC_CASE_ID: String = \"\"\n\n\n    var httpMockInterceptor: HttpMockInterceptor? = null\n\n    var proxyMockCallback: ProxyMockCallback? = null\n\n    private val proxyMockManager: ProxyMockManager = ProxyMockManager()\n\n    private val onHttpProxyMockDataListenerSet: MutableSet<OnHttpProxyMockDataListener> = mutableSetOf()\n    private val onHttpProxyMockSendListenerSet: MutableSet<OnHttpProxyMockSendListener> = mutableSetOf()\n\n    private var testMode: TestMode = TestMode.UNKNOWN\n\n\n    /**\n     * 是否是主机模式\n     */\n    fun isHostMode(): Boolean {\n        return testMode == TestMode.HOST\n    }\n\n    /**\n     * 是否是从机模式\n     */\n    fun isClientMode(): Boolean {\n        return testMode == TestMode.CLIENT\n    }\n\n    /**\n     * 测试功能是否关闭\n     */\n    fun isClose(): Boolean {\n        return testMode == TestMode.UNKNOWN\n    }\n\n    fun getTestMode(): TestMode {\n        return testMode\n    }\n\n    /**\n     * 开始测试功能\n     * 1、开始hook 或者关闭hook\n     */\n    fun startTest(testMode: TestMode) {\n        if (testMode == TestMode.HOST) {\n            startTestByHostMode()\n        } else if (testMode == TestMode.CLIENT) {\n            startTestByClientMode()\n        } else {\n            closeTest()\n        }\n    }\n\n    /**\n     * 关闭测试功能\n     * 备注：关闭后测试相关功能将不工作\n     */\n    fun closeTest() {\n        testMode = TestMode.UNKNOWN\n        if (XposedHookUtil.isRunTimeHookEnable()) {\n            XposedHookUtil.stopRunTimeHook()\n        }\n    }\n\n\n    private fun startTestByHostMode() {\n        testMode = TestMode.HOST\n    }\n\n    private fun startTestByClientMode() {\n        testMode = TestMode.CLIENT\n    }\n\n    /**\n     * 发送模拟数据\n     */\n    fun sendMockTextPackage(textPackage: TextPackage) {\n        val data = GsonUtils.toJson(textPackage)\n        proxyMockCallback?.let {\n            try {\n                it.send(data)\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n        onHttpProxyMockSendListenerSet.forEach {\n            try {\n                it.onHttpProxyMockSend(textPackage)\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    fun requestStart(request: ProxyRequest) {\n        proxyMockManager.requestStart(request)\n\n        onHttpProxyMockDataListenerSet.forEach {\n            try {\n                it.onHttpProxyMockRequest(request, true)\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    fun requestStop(response: ProxyResponse) {\n        proxyMockManager.requestStop(response)\n\n        onHttpProxyMockDataListenerSet.forEach {\n            try {\n                it.onHttpProxyMockResponse(response, true)\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    fun requestQuery(request: ProxyRequest, callback: ProxyCallback) {\n        proxyMockManager.requestQuery(request, callback)\n\n        onHttpProxyMockDataListenerSet.forEach {\n            try {\n                it.onHttpProxyMockRequest(request, false)\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    fun receiveQueryResponse(text: String) {\n        val textPackage = GsonUtils.fromJson<TextPackage>(\n            text,\n            TextPackage::class.java\n        )\n        receiveQueryResponse(textPackage)\n    }\n\n    fun receiveQueryResponse(textPackage: TextPackage) {\n\n        val data = textPackage.data\n        val response = if (data != null && data.isNotEmpty()) {\n            GsonUtils.fromJson<ProxyResponse>(data, ProxyResponse::class.java)\n        } else {\n            ProxyMockUtils.createEmptyProxyResponse(\"\")\n        }\n        proxyMockManager.receiveQueryResponse(textPackage, response)\n\n        onHttpProxyMockDataListenerSet.forEach {\n            try {\n                it.onHttpProxyMockResponse(response, false)\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/OnHttpProxyMockDataListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock\n\nimport com.didichuxing.doraemonkit.kit.test.mock.proxy.ProxyRequest\nimport com.didichuxing.doraemonkit.kit.test.mock.proxy.ProxyResponse\n\n\n/**\n * didi Create on 2022/4/14 .\n *\n * Copyright (c) 2022/4/14 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/14 3:17 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface OnHttpProxyMockDataListener {\n\n    /**\n     * host 是否是主机数据\n     */\n    fun onHttpProxyMockRequest(request: ProxyRequest, host: Boolean)\n\n    /**\n     * host 是否是主机数据\n     */\n    fun onHttpProxyMockResponse(response: ProxyResponse, host: Boolean)\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/OnHttpProxyMockSendListener.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock\n\nimport com.didichuxing.doraemonkit.kit.connect.data.TextPackage\n\n\n/**\n * didi Create on 2022/4/14 .\n *\n * Copyright (c) 2022/4/14 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/14 3:16 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface OnHttpProxyMockSendListener {\n\n    fun onHttpProxyMockSend(textPackage: TextPackage)\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/ProxyMockCallback.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock\n\n\n/**\n * didi Create on 2022/4/11 .\n *\n * Copyright (c) 2022/4/11 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/11 2:56 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface ProxyMockCallback {\n\n    fun send(data: String)\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/data/AppInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.data\n\nimport com.didichuxing.doraemonkit.BuildConfig\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.util.AppUtils\nimport com.didichuxing.doraemonkit.util.DeviceUtils\nimport com.didichuxing.doraemonkit.util.TimeUtils\n\n/**\n * didi Create on 2022/3/10 .\n *\n * Copyright (c) 2022/3/10 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/10 7:45 下午\n * @Description 用一句话说明文件功能\n */\n\ndata class AppInfo(\n    val time: String = TimeUtils.getNowString(),\n    val phoneMode: String = DeviceUtils.getModel(),\n    val systemVersion: String = DeviceUtils.getSDKVersionName(),\n    val appName: String = AppUtils.getAppName(),\n    val appVersion: String = AppUtils.getAppVersionName(),\n    val dokitVersion: String = BuildConfig.DOKIT_VERSION,\n    //产品id\n    val pId: String? = DoKitManager.PRODUCT_ID\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/data/CaseInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.data\n\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.test.mock.MockManager\n\n/**\n * didi Create on 2022/3/10 .\n *\n * Copyright (c) 2022/3/10 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/10 7:45 下午\n * @Description 用一句话说明文件功能\n */\n\ndata class CaseInfo(\n\n    val pId: String = DoKitManager.PRODUCT_ID,\n    val caseId: String = MockManager.MC_CASE_ID,\n    val caseName: String,\n    val personName: String\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/data/HostInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.data\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/11/19-15:16\n * 描    述：主机信息\n * 修订历史：\n * ================================================\n */\ndata class HostInfo(\n    val deviceName: String,\n    val width: Float,\n    val height: Float\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/data/HttpMatchedInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.data\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/21-17:04\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class HttpMatchedInfo(\n    val key: String,\n    val responseBody: String\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/data/HttpUploadInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.data\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/21-17:04\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class HttpUploadInfo(\n    val pId: String,\n    val caseId: String,\n    val originKey: String,\n    val key: String,\n    val method: String = \"GET\",\n    val path: String,\n    val fragment: String?,\n    val contentType: String?,\n    val query: Map<String, String>?,\n    val requestBody: Map<String, String>?,\n    val responseBody: String\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/data/McCaseInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.data\n\nimport com.google.gson.annotations.Expose\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/7/1-20:39\n * 描    述：\n * 修订历史：\n * ================================================\n */\ndata class McCaseInfo(\n    @Expose\n    val caseId: String,\n    @Expose\n    val time: String\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/data/McConfigInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.data\n\n/**\n * didi Create on 2022/3/10 .\n *\n * Copyright (c) 2022/3/10 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/10 7:45 下午\n * @Description 用一句话说明文件功能\n */\ndata class McConfigInfo(val multiControl: MultiControl? = null)\ndata class MultiControl(val exclude: List<String>? = null)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/data/McMockKey.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.data\n\n/**\n * didi Create on 2022/3/10 .\n *\n * Copyright (c) 2022/3/10 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/10 7:45 下午\n * @Description 用一句话说明文件功能\n */\ndata class McMockKey(\n    val key: String,\n    val originKey: String,\n    val query: String,\n    val requestBody: String\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/data/McResInfo.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.data\n\n/**\n * didi Create on 2022/3/10 .\n *\n * Copyright (c) 2022/3/10 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/10 7:45 下午\n * @Description 用一句话说明文件功能\n */\ndata class McResInfo<T>(\n    val code: Int = 0,\n    val msg: String = \"\",\n    var data: T? = null\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/http/DoKitMockInterceptor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.http\n\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.test.TestMode\nimport com.didichuxing.doraemonkit.extension.sortedByKey\nimport com.didichuxing.doraemonkit.extension.toMap\nimport com.didichuxing.doraemonkit.kit.test.mock.HttpMockInterceptor\nimport com.didichuxing.doraemonkit.kit.test.mock.data.HttpMatchedInfo\nimport com.didichuxing.doraemonkit.kit.test.mock.data.McMockKey\nimport com.didichuxing.doraemonkit.kit.test.mock.MockManager\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager\nimport com.didichuxing.doraemonkit.kit.network.okhttp.InterceptorUtil\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.AbsDoKitInterceptor\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport kotlinx.coroutines.CoroutineExceptionHandler\nimport kotlinx.coroutines.runBlocking\nimport okhttp3.*\nimport okio.ByteString\nimport java.net.URLDecoder\nimport java.util.*\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/7-20:03\n * 描    述：一机多控抓包拦截器\n * 修订历史：\n * ================================================\n */\n@Deprecated(\"DoKitProxyMockInterceptor\")\nclass DoKitMockInterceptor : AbsDoKitInterceptor() {\n    private val mExceptionHandler = CoroutineExceptionHandler { _, throwable ->\n        LogHelper.e(TAG, \"error message: ${throwable.message}\")\n    }\n\n    override fun intercept(chain: Interceptor.Chain): Response {\n        //数据采集\n        val request = chain.request()\n        val response = chain.proceed(request)\n        try {\n            if (DoKitTestManager.isClose()) {\n                return response\n            }\n            val url = request.url()\n            val host: String = url.host()\n            val scheme = url.scheme()\n            val contentType = response.header(\"Content-Type\") ?: response.header(\"content-type\")\n            //如果是图片则不进行拦截\n            if (InterceptorUtil.isImg(contentType)) {\n                return response\n            }\n            //如果是mock平台的接口则不进行拦截\n            if (host.equals(NetworkManager.MOCK_HOST, ignoreCase = true)) {\n                return response\n            }\n\n            //如果是一机多控相关接口则不拦截\n            if (\"$scheme://$host\".equals(HttpMockServer.host, ignoreCase = true)) {\n//                val strResponseBody = response.peekBody(Long.MAX_VALUE).string()\n//                LogHelper.i(TAG, \"========DoKit SDK 接口数据 Start url:$url ========\")\n//                LogHelper.json(TAG, strResponseBody)\n//                LogHelper.i(TAG, \"========DoKit SDK 接口数据 End url:$url ========\")\n                return response\n            }\n\n            //不包含query字段\n            val method = request.method()\n            val path = URLDecoder.decode(url.encodedPath(), \"utf-8\")\n            val fragment = url.fragment() ?: \"null\"\n            val queryMap = createQueryMap(url)\n\n            val strQuery = GsonUtils.toJson(queryMap)\n            val requestContentType = if (request.body() == null) {\n                \"null\"\n            } else {\n                request.body()?.contentType().toString().toLowerCase(Locale.ROOT)\n            }\n\n            val requestBodyMap = createRequestBodyMap(request)\n            val strRequestBody = GsonUtils.toJson(requestBodyMap)\n            val strResponseBody = response.peekBody(Long.MAX_VALUE).string()\n//            LogHelper.i(TAG, \"========业务接口 Start url:$url ========\")\n//            LogHelper.json(TAG, strResponseBody)\n//            LogHelper.i(TAG, \"========业务接口 End url:$url ========\")\n            val k = \"method=$method&path=$path&fragment=$fragment&query=$strQuery&contentType=$requestContentType&requestBody=$strRequestBody\"\n            LogHelper.i(TAG, \"originKey===>$k\")\n            val key = ByteString.encodeUtf8(k).md5().hex()\n            when (DoKitTestManager.getTestMode()) {\n//                TestMode.RECORDING -> {\n//                    //数据采集\n//                    // val responseBody4Base64 = String(EncodeUtils.base64Encode(strResponseBody))\n//                    doKitGlobalScope.launch(mExceptionHandler) {\n//                        val httInfo = HttpUploadInfo(\n//                            DoKitManager.PRODUCT_ID,\n//                            MockManager.MC_CASE_ID,\n//                            k,\n//                            key,\n//                            method,\n//                            path,\n//                            fragment,\n//                            requestContentType,\n//                            queryMap,\n//                            requestBodyMap,\n//                            strResponseBody\n//                        )\n//\n//                        val result = McHttpManager.uploadHttpInfo<Any>(httInfo)\n//                        if (result.code != McHttpManager.RESPONSE_OK) {\n//                            LogHelper.e(TAG, \"e===>${result.msg}\")\n//                        }\n//                    }\n//                }\n//                WSMode.CLIENT,\n                TestMode.HOST -> {\n                    if (MockManager.MC_CASE_ID.isNotBlank() && DoKitManager.PRODUCT_ID.isNotBlank()) {\n                        //将挂起函数转为阻塞调用 等待协程返回值\n                        return runBlocking(mExceptionHandler) {\n                            try {\n                                var result = HttpMockServer.httpMatch<HttpMatchedInfo>(key)\n\n                                val interceptor: HttpMockInterceptor? = MockManager.httpMockInterceptor\n                                if (interceptor != null) {\n                                    result = interceptor.intercept(McMockKey(key, k, strQuery, strRequestBody), result)\n\n                                }\n                                LogHelper.i(TAG, \"MCMOCKLOG OK key=$key,code=${result.code} originKey===>$k data=${result.data}\")\n                                if (result.code == HttpMockServer.RESPONSE_OK && result.data != null) {\n                                    val responseBody =\n                                        ResponseBody.create(\n                                            response.body()?.contentType(),\n                                            result.data!!.responseBody\n                                        )\n                                    return@runBlocking response.newBuilder()\n                                        .code(response.code())\n                                        .request(request)\n                                        .message(response.message())\n                                        .protocol(response.protocol())\n                                        .headers(response.headers())\n                                        .body(responseBody)\n                                        .build()\n                                } else {\n                                    LogHelper.i(TAG, \"MCMCOKLOG  key=$key,code=${result.code} originKey===>$k data=${result.data}\")\n                                    return@runBlocking response\n                                }\n                            } catch (e: Exception) {\n                                LogHelper.e(TAG, \"IXP:mock error2 all url:$url,msg=${e.message}\")\n                                return@runBlocking response\n                            }\n\n                        }\n                    }\n                }\n                else -> {\n                    LogHelper.e(TAG, \"MCMCOKLOG: mock error3 all url:$url\")\n                    return response\n                }\n            }\n            return response\n        } catch (e: Exception) {\n            return response\n        }\n    }\n\n\n    private fun createQueryMap(url: HttpUrl): Map<String, String> {\n        val maps = url.query()?.toMap()\n        HttpMockServer.mExcludeKey.forEach {\n            maps?.remove(it)\n        }\n        return maps?.sortedByKey() ?: mapOf()\n    }\n\n\n    private fun createRequestBodyMap(request: Request): Map<String, String> {\n        val maps = request.body().toMap()\n        HttpMockServer.mExcludeKey.forEach {\n            maps.remove(it)\n        }\n        return maps.sortedByKey()\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/http/DoKitProxyMockInterceptor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.http\n\nimport com.didichuxing.doraemonkit.kit.test.mock.proxy.*\nimport com.didichuxing.doraemonkit.kit.network.okhttp.interceptor.AbsDoKitInterceptor\nimport com.didichuxing.doraemonkit.kit.test.mock.MockManager\nimport com.didichuxing.doraemonkit.kit.test.utils.RandomIdentityUtil\nimport com.didichuxing.doraemonkit.util.LogHelper\nimport kotlinx.coroutines.CoroutineExceptionHandler\nimport kotlinx.coroutines.runBlocking\nimport okhttp3.*\nimport java.io.IOException\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\nimport kotlin.coroutines.suspendCoroutine\n\n/**\n * didi Create on 2022/3/10 .\n *\n * Copyright (c) 2022/3/10 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/10 7:45 下午\n * @Description 用一句话说明文件功能\n */\nclass DoKitProxyMockInterceptor : AbsDoKitInterceptor() {\n    private val mExceptionHandler = CoroutineExceptionHandler { _, throwable ->\n        LogHelper.e(TAG, \"error message: ${throwable.message}\")\n    }\n\n\n    override fun intercept(chain: Interceptor.Chain): Response {\n        val request = chain.request()\n        if (MockManager.isHostMode() || MockManager.isClientMode()) {\n            if (!ProxyMockUtils.filterRequest(request)) {\n                if (MockManager.isHostMode()) {\n                    //主机处理方式\n                    val did = RandomIdentityUtil.createDid()\n                    val proxyRequest = ProxyMockUtils.createProxyRequest(did, request)\n                    MockManager.requestStart(proxyRequest)\n                    var response: Response\n                    try {\n                        response = chain.proceed(request)\n                    } catch (e: Exception) {\n                        val proxyResponse = ProxyMockUtils.createEmptyProxyResponse(did)\n                        MockManager.requestStop(proxyResponse)\n                        throw IOException(\"dokit mock error\", e)\n                    }\n                    val proxyResponse = ProxyMockUtils.createProxyResponse(did, response)\n                    MockManager.requestStop(proxyResponse)\n                    return response\n                } else {\n                    //从机处理方式\n                    return runBlocking(mExceptionHandler) {\n                        val proxyRequest = ProxyMockUtils.createProxyRequest(\"\", request)\n                        LogHelper.i(TAG, \"PROXY start proxyRequest=${proxyRequest}\")\n                        val proxyResponse: ProxyResponse = requestQuery<ProxyResponse>(proxyRequest)\n                        LogHelper.i(TAG, \"PROXY stop proxyResponse=${proxyResponse}\")\n\n                        //查询不到数据，直接查询结果\n                        if (proxyResponse.image\n                            || proxyResponse.protocol.equals(\"local\", ignoreCase = false)\n                            || proxyResponse.responseContentType == null\n                        ) {\n                            return@runBlocking chain.proceed(request)\n                        }\n\n                        val responseBody =\n                            ResponseBody.create(\n                                MediaType.parse(proxyResponse.responseContentType),\n                                proxyResponse.responseBody\n                            )\n                        return@runBlocking Response.Builder()\n                            .code(proxyResponse.responseCode)\n                            .request(request)\n                            .message(\"ok\")\n                            .protocol(Protocol.get(proxyResponse.protocol))\n                            .headers(ProxyMockUtils.parseHeaders(proxyResponse.responseHeaders))\n                            .body(responseBody)\n                            .build()\n                    }\n                }\n            }\n        }\n        return chain.proceed(request)\n\n    }\n\n    /**\n     * 将异步请求转为阻塞等待\n     */\n    private suspend inline fun <reified T> requestQuery(request: ProxyRequest): ProxyResponse = suspendCoroutine {\n        try {\n            val proxyQuery = object : ProxyCallback {\n                override fun onResponse(proxyResponse: ProxyResponse) {\n                    it.resume(proxyResponse)\n                }\n            }\n            MockManager.requestQuery(request, proxyQuery)\n        } catch (e: Exception) {\n            it.resumeWithException(e)\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/http/HttpMockServer.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.http\n\nimport com.android.volley.Request\nimport com.android.volley.toolbox.JsonObjectRequest\nimport com.android.volley.toolbox.StringRequest\nimport com.didichuxing.doraemonkit.kit.core.DoKitManager\nimport com.didichuxing.doraemonkit.kit.test.mock.*\nimport com.didichuxing.doraemonkit.kit.test.mock.data.AppInfo\nimport com.didichuxing.doraemonkit.kit.test.mock.data.CaseInfo\nimport com.didichuxing.doraemonkit.kit.test.mock.data.HttpUploadInfo\nimport com.didichuxing.doraemonkit.kit.test.mock.data.McResInfo\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport com.didichuxing.doraemonkit.volley.VolleyManager\nimport org.json.JSONObject\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\nimport kotlin.coroutines.suspendCoroutine\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/6/21-17:16\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject HttpMockServer {\n    const val RESPONSE_OK = 200\n\n    const val host = \"https://www.dokit.cn\"\n    const val host_test = \"https://pre.dokit.cn\"\n\n    var mExcludeKey: List<String> = mutableListOf()\n\n\n    suspend inline fun <reified T> getMcConfig(): McResInfo<T> = suspendCoroutine {\n        try {\n            val jsonObject = JSONObject()\n            jsonObject.put(\"pId\", DoKitManager.PRODUCT_ID)\n            val request = JsonObjectRequest(\n                \"$host/app/multiControl/getConfig\",\n                jsonObject,\n                { response ->\n                    it.resume(convert2McResInfoWithObj(response))\n                }, { error ->\n                    it.resumeWithException(error)\n                })\n            VolleyManager.add(request)\n        } catch (e: Exception) {\n            it.resumeWithException(e)\n        }\n\n    }\n\n\n    suspend inline fun <reified T> mockStart(): McResInfo<T> = suspendCoroutine {\n        try {\n            val request = JsonObjectRequest(\n                \"$host/app/multiControl/startRecord\",\n                JSONObject(GsonUtils.toJson(AppInfo())),\n                { response ->\n                    it.resume(convert2McResInfoWithObj(response))\n                }, { error ->\n                    it.resumeWithException(error)\n                })\n            VolleyManager.add(request)\n        } catch (e: Exception) {\n            it.resumeWithException(e)\n        }\n\n    }\n\n\n    suspend inline fun <reified T> uploadHttpInfo(httpInfo: HttpUploadInfo): McResInfo<T> =\n        suspendCoroutine {\n            try {\n                val request = JsonObjectRequest(\n                    \"$host/app/multiControl/uploadApiInfo\",\n                    JSONObject(GsonUtils.toJson(httpInfo)),\n                    { response ->\n                        it.resume(convert2McResInfoWithObj(response))\n                    }, { error ->\n                        it.resumeWithException(error)\n                    })\n                VolleyManager.add(request)\n            } catch (e: Exception) {\n                it.resumeWithException(e)\n            }\n\n        }\n\n    suspend inline fun <reified T> mockStop(caseInfo: CaseInfo): McResInfo<T> =\n        suspendCoroutine {\n            try {\n                val request = JsonObjectRequest(\n                    \"$host/app/multiControl/endRecord\",\n                    JSONObject(GsonUtils.toJson(caseInfo)),\n                    { response ->\n                        it.resume(convert2McResInfoWithObj(response))\n                    }, { error ->\n                        it.resumeWithException(error)\n                    })\n                VolleyManager.add(request)\n            } catch (e: Exception) {\n                it.resumeWithException(e)\n            }\n\n        }\n\n\n    suspend inline fun <reified T> caseList(): McResInfo<List<T>> = suspendCoroutine {\n        try {\n            val request = StringRequest(\n                Request.Method.GET,\n                \"$host/app/multiControl/caseList?pId=${DoKitManager.PRODUCT_ID}\",\n                { response ->\n                    it.resume(convert2McResInfoWithList(JSONObject(response)))\n                }, { error ->\n                    it.resumeWithException(error)\n                })\n            VolleyManager.add(request)\n        } catch (e: Exception) {\n            it.resumeWithException(e)\n        }\n\n    }\n\n\n    suspend inline fun <reified T> httpMatch(requestKey: String): McResInfo<T> = suspendCoroutine {\n        try {\n            val request = StringRequest(\n                \"$host/app/multiControl/getCaseApiInfo?key=${requestKey}&pId=${DoKitManager.PRODUCT_ID}&caseId=${MockManager.MC_CASE_ID}\",\n                { response ->\n                    it.resume(convert2McResInfoWithObj(JSONObject(response)))\n                }, { error ->\n                    it.resumeWithException(error)\n                })\n            VolleyManager.add(request)\n        } catch (e: Exception) {\n            it.resumeWithException(e)\n        }\n\n    }\n\n\n    inline fun <reified T> convert2McResInfoWithObj(json: JSONObject): McResInfo<T> {\n        return try {\n            val mcInfo = McResInfo<T>(\n                json.optInt(\"code\", 0),\n                json.optString(\"msg\", \"has no msg value\")\n            )\n\n            val dataJson = json.optJSONObject(\"data\")\n            val type = GsonUtils.getType(T::class.java)\n            if (dataJson != null) {\n                mcInfo.data = GsonUtils.fromJson(dataJson.toString(), type)\n            }\n            mcInfo\n        } catch (e: Exception) {\n            McResInfo(\n                0,\n                \"Gson format error\",\n                null\n            )\n        }\n    }\n\n\n    inline fun <reified T> convert2McResInfoWithList(json: JSONObject): McResInfo<List<T>> {\n        return try {\n            val mcInfo = McResInfo<List<T>>(\n                json.optInt(\"code\", 0),\n                json.optString(\"msg\", \"has no msg value\")\n            )\n\n            val dataJson = json.optJSONArray(\"data\")\n            val type = GsonUtils.getListType(T::class.java)\n            mcInfo.data = GsonUtils.fromJson(dataJson.toString(), type)\n            mcInfo\n        } catch (e: Exception) {\n            McResInfo(\n                0,\n                \"Gson format error\",\n                null\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/proxy/ProxyCallback.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.proxy\n\n\n/**\n * didi Create on 2022/3/9 .\n *\n * Copyright (c) 2022/3/9 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/9 4:37 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface ProxyCallback {\n\n    fun onResponse(proxyResponse: ProxyResponse)\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/proxy/ProxyMockManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.proxy\n\nimport com.didichuxing.doraemonkit.kit.connect.DoKitConnectManager\nimport com.didichuxing.doraemonkit.kit.test.mock.MockManager\nimport com.didichuxing.doraemonkit.kit.connect.data.PackageType\nimport com.didichuxing.doraemonkit.kit.connect.data.TextPackage\nimport com.didichuxing.doraemonkit.kit.test.utils.RandomIdentityUtil\nimport com.didichuxing.doraemonkit.util.GsonUtils\nimport com.didichuxing.doraemonkit.util.LogHelper\n\n/**\n * didi Create on 2022/3/10 .\n *\n * Copyright (c) 2022/3/10 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/10 7:45 下午\n * @Description 代理数据mock接口管理\n */\n\nclass ProxyMockManager {\n\n    companion object {\n        private const val TAG = \"McProxyManager\"\n    }\n\n    private val mutableMap: MutableMap<String, ProxyQueryData> = mutableMapOf()\n\n    fun requestStart(request: ProxyRequest) {\n        LogHelper.d(TAG, \"requestStart() request=$request\")\n        val textPackage = TextPackage(\n            pid = RandomIdentityUtil.createPid(),\n            type = PackageType.DATA,\n            data = GsonUtils.toJson(request),\n            contentType = \"request\",\n            connectSerial = DoKitConnectManager.getConnectSerial()\n        )\n        MockManager.sendMockTextPackage(textPackage)\n    }\n\n    fun requestStop(response: ProxyResponse) {\n        LogHelper.d(TAG, \"requestStop() response=$response\")\n        val textPackage = TextPackage(\n            pid = RandomIdentityUtil.createPid(),\n            type = PackageType.DATA,\n            data = GsonUtils.toJson(response),\n            contentType = \"response\",\n            connectSerial = DoKitConnectManager.getConnectSerial()\n        )\n        MockManager.sendMockTextPackage(textPackage)\n    }\n\n    fun requestQuery(request: ProxyRequest, callback: ProxyCallback) {\n        requestQueryNow(ProxyQueryData(RandomIdentityUtil.createPid(), request, callback))\n    }\n\n\n    private fun requestQueryNow(request: ProxyQueryData) {\n        mutableMap[request.pid] = request\n        requestQueryLine(request.pid, request.proxyRequest)\n    }\n\n    private fun requestQueryLine(pid: String, request: ProxyRequest) {\n        LogHelper.d(TAG, \"requestQueryLine() request=$request\")\n        val textPackage = TextPackage(\n            pid = pid,\n            type = PackageType.DATA,\n            data = GsonUtils.toJson(request),\n            contentType = \"query\",\n            connectSerial = DoKitConnectManager.getConnectSerial()\n        )\n        MockManager.sendMockTextPackage(textPackage)\n    }\n\n\n    fun receiveQueryResponse(textPackage: TextPackage, response: ProxyResponse) {\n        LogHelper.d(TAG, \"receiveQueryResponse() response=$response\")\n        val queryData = mutableMap[textPackage.pid]\n        if (queryData != null) {\n            queryData.proxyCallback.onResponse(response)\n            mutableMap.remove(textPackage.pid)\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/proxy/ProxyMockUtils.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.proxy\n\nimport com.didichuxing.doraemonkit.extension.string\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEventManager\nimport com.didichuxing.doraemonkit.kit.network.NetworkManager\nimport com.didichuxing.doraemonkit.kit.network.okhttp.InterceptorUtil\nimport com.didichuxing.doraemonkit.util.EncryptUtils\nimport okhttp3.Headers\nimport okhttp3.Request\nimport okhttp3.Response\nimport java.text.SimpleDateFormat\nimport java.util.*\n\n/**\n * didi Create on 2022/3/10 .\n *\n * Copyright (c) 2022/3/10 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/10 7:45 下午\n * @Description 用一句话说明文件功能\n */\nobject ProxyMockUtils {\n\n    private fun createHeaders(headers: Headers): String {\n        return headers.toString()\n    }\n\n    fun parseHeaders(headers: String): Headers {\n        val map = mutableMapOf<String, String>()\n        val values = headers.split(\"\\n\")\n        values.forEach {\n            val e = it.split(\": \")\n            if (e.size >= 2) {\n                map[e[0]] = e[1]\n            }\n        }\n        return Headers.of(map)\n    }\n\n    fun createProxyRequest(did: String, request: Request): ProxyRequest {\n        val aid = ControlEventManager.getCurrentEventId()\n        val url = request.url()\n        val scheme = url.scheme()\n        val host = url.host()\n        val path = url.encodedPath()\n        val query = string(url.encodedQuery())\n        val fragment = string(url.fragment())\n        val headers = createHeaders(request.headers())\n        val body = request.body()\n        val contentType = (body?.contentType() ?: \"\").toString()\n        val contentLength = body?.contentLength() ?: 0\n        val bodyString = body?.string() ?: \"\"\n        val searchKey = EncryptUtils.encryptMD5ToString(path)\n        val method = request.method()\n        val clientProtocol = \"http\"\n        return ProxyRequest(\n            did, aid, url.toString(), scheme, host, path, query, fragment,\n            headers, contentType, contentLength, bodyString, searchKey, method, clientProtocol\n        )\n    }\n\n    fun createEmptyProxyResponse(did: String): ProxyResponse {\n        return ProxyResponse(\n            did, \"\",\n            \"\", 0, \"\", 404, false, \"data\", \"local\"\n        )\n    }\n\n    fun createProxyResponse(did: String, response: Response): ProxyResponse {\n        val headers = createHeaders(response.headers())\n        val code = response.code()\n        val body = response.peekBody(Long.MAX_VALUE)\n        val contentType = (body?.contentType() ?: \"\").toString()\n        val image = InterceptorUtil.isImg(contentType)\n        val contentLength = body?.contentLength() ?: 0\n        var bodyString = \"\"\n        var source = \"\"\n        if (!image) {\n            try {\n                bodyString = body?.string() ?: \"\"\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n            source = \"data\"\n        } else {\n            source = \"image\"\n        }\n\n        val protocol = response.protocol().toString()\n\n        return ProxyResponse(did, headers, contentType, contentLength, bodyString, code, image, source, protocol)\n    }\n\n    fun filterRequest(request: Request): Boolean {\n        val scheme = request.url().scheme()\n        val host = request.url().host()\n        if (host.equals(NetworkManager.MOCK_HOST, ignoreCase = true)) {\n            return true\n        } else if (host.equals(NetworkManager.DOKIT_HOST, ignoreCase = true)) {\n            return true\n        }\n        val wsKey: String? = request.header(\"Sec-WebSocket-Key\")\n        val wsVersion: String? = request.header(\"Sec-WebSocket-Version\")\n        if (wsKey != null && wsVersion != null) {\n            return true\n        }\n        return false\n    }\n\n    private fun string(arg: String?): String {\n        return arg ?: \"\"\n\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/proxy/ProxyQueryData.java",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.proxy;\n\n/**\n * didi Create on 2022/3/9 .\n * <p>\n * Copyright (c) 2022/3/9 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/9 4:50 下午\n * @Description 用一句话说明文件功能\n */\n\npublic class ProxyQueryData {\n\n    private String pid;\n    private ProxyRequest proxyRequest;\n    private ProxyCallback proxyCallback;\n\n    public ProxyQueryData(String pid, ProxyRequest proxyRequest, ProxyCallback proxyCallback) {\n        this.pid = pid;\n        this.proxyRequest = proxyRequest;\n        this.proxyCallback = proxyCallback;\n    }\n\n    public String getPid() {\n        return pid;\n    }\n\n    public ProxyRequest getProxyRequest() {\n        return proxyRequest;\n    }\n\n    public ProxyCallback getProxyCallback() {\n        return proxyCallback;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/proxy/ProxyRequest.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.proxy\n\nimport com.didichuxing.doraemonkit.kit.test.utils.DateTime\n\n\n/**\n * didi Create on 2022/3/10 .\n *\n * Copyright (c) 2022/3/10 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/10 7:45 下午\n * @Description 用一句话说明文件功能\n */\n\ndata class ProxyRequest(\n    val did: String,\n    val aid: String,\n    val url: String,\n    val scheme: String,\n    val host: String,\n    val path: String,\n    val query: String,\n    val fragment: String,\n    val requestHeaders: String,\n    val requestContentType: String,\n    val requestBodyLength: Long,\n    val requestBody: String,\n    val searchKey: String,\n    val method: String,\n    val clientProtocol: String,\n    val requestTime: String = DateTime.nowTime(),\n    val requestTimeMillis: Long = DateTime.nowTimeMillis()\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/proxy/ProxyResponse.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.proxy\n\nimport com.didichuxing.doraemonkit.kit.test.utils.DateTime\n\n\n/**\n * didi Create on 2022/3/10 .\n *\n * Copyright (c) 2022/3/10 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/10 7:45 下午\n * @Description 用一句话说明文件功能\n */\n\ndata class ProxyResponse(\n    val did: String,\n    val responseHeaders: String,\n    val responseContentType: String,\n    val responseBodyLength: Long,\n    val responseBody: String,\n    val responseCode: Int,\n    val image: Boolean,\n    val source: String,\n    val protocol: String,\n    val responseTime: String = DateTime.nowTime(),\n    val requestTimeMillis: Long = DateTime.nowTimeMillis()\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/tcp/TcpMockManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.tcp\n\nimport com.didichuxing.doraemonkit.kit.test.DoKitTestManager\nimport com.didichuxing.doraemonkit.kit.test.event.monitor.TcpMessageEventMonitor\n\n\n/**\n * didi Create on 2022/1/21 .\n *\n * Copyright (c) 2022/1/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/1/21 2:54 下午\n * @Description TCP 数据mock管理类，实现数据的\n */\n\nobject TcpMockManager {\n\n\n    var tcpMockMessageProcessor: TcpMockMessageProcessor? = null\n\n    fun hookTcpSendMessageEvent(message: String): Boolean {\n        //从机收发都拦截不处理\n        if (DoKitTestManager.isClientMode()) {\n            return true\n        }\n        if (DoKitTestManager.isHostMode()) {\n            TcpMessageEventMonitor.onTcpMessageEvent(\"send\", message)\n        }\n        return false\n    }\n\n    fun hookTcpReceiveMessageEvent(message: String): Boolean {\n        //从机收发都拦截不处理\n        if (DoKitTestManager.isClientMode()) {\n            return true\n        }\n        if (DoKitTestManager.isHostMode()) {\n            TcpMessageEventMonitor.onTcpMessageEvent(\"receive\", message)\n        }\n        return false\n    }\n\n    /**\n     * 从机接收到Mock数据处理\n     */\n    fun onTcpMessageEvent(type: String, message: String) {\n        if (tcpMockMessageProcessor != null) {\n            tcpMockMessageProcessor?.onTcpMessageEvent(type, message)\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/mock/tcp/TcpMockMessageProcessor.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.mock.tcp\n\n\n/**\n * didi Create on 2022/1/21 .\n *\n * Copyright (c) 2022/1/21 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/1/21 2:54 下午\n * @Description 用一句话说明文件功能\n */\n\ninterface TcpMockMessageProcessor {\n\n    fun onTcpMessageEvent(type: String, message: String)\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/report/AutoTestMessage.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.report\n\n\n/**\n * didi Create on 2022/4/6 .\n *\n * Copyright (c) 2022/4/6 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/6 5:14 下午\n * @Description 自动化测试通信消息\n */\n\ndata class AutoTestMessage(\n    val message: String = \"\",\n    val command: String = \"\",\n    var params: MutableMap<String, String> = mutableMapOf()\n)\n\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/report/AutoTestState.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.report\n\nimport android.app.Activity\nimport android.view.View\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\n\ndata class AutoTestState(\n    val activity: Activity?,\n    val view: View?,\n    val controlEvent: ControlEvent,\n    val message: AutoTestMessage,\n    val success: Boolean = true\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/report/FileUploadManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.report\n\nimport android.graphics.Bitmap\nimport com.didichuxing.doraemonkit.kit.connect.data.PackageType\nimport com.didichuxing.doraemonkit.kit.connect.parser.ByteParser\nimport com.didichuxing.doraemonkit.kit.connect.parser.JsonParser\nimport com.didichuxing.doraemonkit.kit.connect.ws.WebSocketClient\nimport com.didichuxing.doraemonkit.util.FileIOUtils\nimport com.didichuxing.doraemonkit.util.RandomUtils\nimport kotlinx.coroutines.*\nimport java.io.ByteArrayOutputStream\n\n\n/**\n * didi Create on 2022/4/18 .\n *\n * Copyright (c) 2022/4/18 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/18 4:24 下午\n * @Description 上传文件实现 能将指定文件上传到指定地址\n */\n\nclass FileUploadManager(\n    private val screenShotManager: ScreenShotManager,\n    private val webSocketClient: WebSocketClient\n) {\n\n    private val uploadScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + CoroutineName(\"upload\")\n\n    fun uploadBitmap(bitmap: Bitmap, fileName: String) {\n        uploadScope.launch {\n            screenShotManager.saveBitmap(bitmap, fileName)\n//            upload(screenShotManager.getScreenFile(fileName))\n        }\n    }\n\n    fun uploadBitmap(bitmap: Bitmap) {\n        uploadScope.launch {\n            upload(bitmap, screenShotManager.createNextFileName())\n        }\n    }\n\n    private fun upload(bitmap: Bitmap, fileName: String) {\n        //保存图片\n        val byteArray = ByteArrayOutputStream(2048)\n        val ok = bitmap.compress(Bitmap.CompressFormat.JPEG, 50, byteArray)\n        val bytes = byteArray.toByteArray()\n\n        val dataMap = mutableMapOf<String, String>()\n        dataMap[\"caseId\"] = RandomUtils.random32HexString()\n        dataMap[\"image\"] = fileName\n        dataMap[\"type\"] = \"jpeg\"\n\n        val textPackage = JsonParser.toTextPackage(PackageType.AUTOTEST, dataMap, \"image\")\n        val byteString = ByteParser.toByteString(textPackage, bytes)\n\n        webSocketClient.send(byteString)\n    }\n\n\n    private fun upload(fileName: String) {\n        val bytes = FileIOUtils.readFile2BytesByChannel(fileName)\n        val dataMap = mutableMapOf<String, String>()\n        dataMap[\"caseId\"] = RandomUtils.random32HexString()\n        dataMap[\"image\"] = fileName\n        dataMap[\"type\"] = \"jpeg\"\n\n        val textPackage = JsonParser.toTextPackage(PackageType.AUTOTEST, dataMap, \"image\")\n        val byteString = ByteParser.toByteString(textPackage, bytes)\n        webSocketClient.send(byteString)\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/report/MCRecordManager.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.report\n\nimport com.didichuxing.doraemonkit.kit.test.event.EventType\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2021/10/1-15:51\n * 描    述：主机行为录制\n * 修订历史：\n * ================================================\n */\nobject MCRecordManager {\n    /**\n     * 是否处于录制状态\n     */\n    var isEventRecoding = false\n\n    val mWsEvents: MutableList<ControlEvent> by lazy {\n        mutableListOf<ControlEvent>()\n    }\n\n    /**\n     * 有效的事件类型\n     */\n    private val effectiveEventTypes = arrayOf(\n        EventType.APP_ON_FOREGROUND,\n        EventType.APP_ON_BACKGROUND,\n        EventType.ACTIVITY_BACK_PRESSED,\n        EventType.WSE_COMMON_EVENT\n    )\n\n    /**\n     * 拦截所有事件\n     */\n    fun intercept(wsEvent: ControlEvent) {\n        //拦截无效的事件类型\n        if (needFilter(wsEvent.eventType)) {\n            return\n        }\n\n        //保存有效的事件类型\n        mWsEvents.add(wsEvent)\n    }\n\n    /**\n     * 是否过滤特定的事件类型\n     */\n    private fun needFilter(eventType: EventType): Boolean {\n        if (effectiveEventTypes.contains(eventType)) {\n            return false\n        }\n\n        return true\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/report/MyWindowBitmap.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.report\n\nimport android.graphics.Bitmap\nimport android.graphics.Rect\nimport android.view.View\nimport android.view.ViewParent\n\n\n/**\n * didi Create on 2022/4/1 .\n * <p>\n * Copyright (c) 2022/4/1 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/1 4:25 下午\n * @Description 截屏图片 ，用于合成屏幕截图\n */\n\ndata class MyWindowBitmap(\n    val parent: ViewParent,\n    val view: View,\n    val bitmap: Bitmap?,\n    val winFrame: Rect,\n    val appVisible: Boolean,\n    val decorView: Boolean = false,\n    val doKitView: Boolean = false\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/report/RecordActionCase.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.report\n\ndata class RecordActionCase(\n    val name: String,\n    val dateTime: String\n\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/report/RecordActionStep.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.report\n\nimport com.didichuxing.doraemonkit.kit.test.event.ControlEvent\n\ndata class RecordActionStep(\n    val dateTime: String,\n    val type: String,\n    val event: ControlEvent,\n    val dataList: MutableList<RecordData>\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/report/RecordData.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.report\n\ndata class RecordData(\n    val dateTime: String,\n    val type: String,\n    val url: String,\n    val dataKey: String\n)\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/report/ScreenShotManager.java",
    "content": "package com.didichuxing.doraemonkit.kit.test.report;\n\nimport android.app.Activity;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\nimport android.view.View;\nimport android.view.ViewParent;\n\nimport com.didichuxing.doraemonkit.kit.test.utils.WindowPathUtil;\nimport com.didichuxing.doraemonkit.kit.test.utils.XposedHookUtil;\nimport com.didichuxing.doraemonkit.util.ActivityUtils;\nimport com.didichuxing.doraemonkit.util.RandomUtils;\nimport com.didichuxing.doraemonkit.util.ReflectUtils;\nimport com.didichuxing.doraemonkit.util.Utils;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * didi Create on 2022/4/1 .\n * <p>\n * Copyright (c) 2022/4/1 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/1 4:25 下午\n * @Description 截屏管理器\n */\n\npublic class ScreenShotManager {\n\n\n    private String screenFileDir;\n\n\n    public ScreenShotManager(String screenFileDir) {\n        String dir = Utils.getApp().getFilesDir().getAbsolutePath();\n        this.screenFileDir = dir + \"/\" + screenFileDir;\n    }\n\n    public Bitmap screenshotBitmap(Activity activity) {\n        try {\n            View decorView = activity.getWindow().getDecorView();\n            decorView.setDrawingCacheEnabled(true);\n            Bitmap bitmap = decorView.getDrawingCache();\n            return bitmap;\n\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n\n    /**\n     * 创建各个可见Window的图像\n     * 备注按需要调整图层顺序\n     */\n    public List<MyWindowBitmap> createMyWindowBitmap(List<ViewParent> parents) {\n        List<MyWindowBitmap> pageBitmaps = new ArrayList<>();\n        List<MyWindowBitmap> otherBitmaps = new ArrayList<>();\n        List<MyWindowBitmap> doKitBitmaps = new ArrayList<>();\n\n        for (ViewParent parent : parents) {\n            View view = ReflectUtils.reflect(parent).field(\"mView\").get();\n            Bitmap bitmap = getViewBitmap(parent);\n            Rect winFrame = ReflectUtils.reflect(parent).field(\"mWinFrame\").get();\n            boolean appVisible = ReflectUtils.reflect(parent).field(\"mAppVisible\").get();\n            boolean page = WindowPathUtil.isPageViewRoot(parent);\n            boolean doKit = WindowPathUtil.isDoKitViewRoot(parent);\n\n            MyWindowBitmap windowBitmap = new MyWindowBitmap(parent, view, bitmap, winFrame, appVisible, page, doKit);\n\n            if (doKit) {\n                doKitBitmaps.add(windowBitmap);\n                continue;\n            }\n            if (page) {\n                pageBitmaps.add(windowBitmap);\n                continue;\n            }\n            otherBitmaps.add(windowBitmap);\n\n        }\n        pageBitmaps.addAll(otherBitmaps);\n        pageBitmaps.addAll(doKitBitmaps);\n        return pageBitmaps;\n    }\n\n    /**\n     * 使用图像合成方式生成截图\n     * 备注：无需权限，但是仅可以获取到应用内的图像\n     */\n    public Bitmap screenshotBitmap() {\n        List<ViewParent> parents = XposedHookUtil.INSTANCE.getROOT_VIEWS();\n        List<ViewParent> showParents = WindowPathUtil.filterShowViewRoot(parents);\n        List<MyWindowBitmap> myWindowBitmaps = createMyWindowBitmap(showParents);\n\n        Paint paint = new Paint();\n        if (myWindowBitmaps.size() <= 1) {\n            return screenshotBitmap(ActivityUtils.getTopActivity());\n        }\n        Bitmap canvasBitmap = myWindowBitmaps.get(0).getBitmap();\n        Canvas canvas = new Canvas(canvasBitmap);\n        int size = myWindowBitmaps.size();\n        for (int i = 1; i < size; i++) {\n            MyWindowBitmap bitmap = myWindowBitmaps.get(i);\n            Bitmap map = bitmap.getBitmap();\n            if (map != null){\n                Rect out = bitmap.getWinFrame();\n                if (!bitmap.getDoKitView() && bitmap.getDecorView()) {\n                    canvas.drawARGB(90, 0, 0, 0);\n                }\n                canvas.drawBitmap(map, out.left, out.top, paint);\n            }\n        }\n        return canvasBitmap;\n    }\n\n    private Bitmap getViewBitmap(ViewParent viewParent) {\n        View view = ReflectUtils.reflect(viewParent).field(\"mView\").get();\n        view.setDrawingCacheEnabled(true);\n        Bitmap bitmap = view.getDrawingCache();\n        return bitmap;\n    }\n\n    public String getScreenFile(String fileName) {\n        String fullFilepath = screenFileDir + \"/\" + fileName + \".jpeg\";\n        return fullFilepath;\n    }\n\n    public String saveBitmap(Bitmap bitmap, String fileName) {\n        String fullFilepath = screenFileDir + \"/\" + fileName + \".jpeg\";\n        try {\n            File file = new File(screenFileDir);\n            if (!file.exists()) {\n                file.mkdirs();\n            }\n\n            File file2 = new File(fullFilepath);\n            if (!file2.exists()) {\n                file2.createNewFile();\n            }\n            //保存图片\n            FileOutputStream outputStream = new FileOutputStream(fullFilepath);\n            boolean ok = bitmap.compress(Bitmap.CompressFormat.JPEG, 30, outputStream);\n            if (ok) {\n                return fullFilepath;\n            } else {\n                file2.delete();\n            }\n            outputStream.close();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return fullFilepath;\n    }\n\n    public String screenshot(Activity activity) {\n        Bitmap bitmap = screenshotBitmap(activity);\n        String fileName = createNextFileName();\n        return saveBitmap(bitmap, fileName);\n    }\n\n    public String createNextFileName() {\n        String name = RandomUtils.random64HexString();\n        String dir = screenFileDir;\n        String fullFilepath = dir + \"/\" + name + \".jpeg\";\n        File file = new File(fullFilepath);\n        int index = 0;\n        while (true) {\n            if (file.exists()) {\n                index++;\n                name = name + \"-\" + index;\n                file = new File(dir + \"/\" + name + \".jpeg\");\n                continue;\n            }\n            break;\n        }\n        return name;\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/utils/DateTime.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.utils\n\nimport android.annotation.SuppressLint\nimport java.text.SimpleDateFormat\nimport java.util.*\n\nobject DateTime {\n\n    @SuppressLint(\"SimpleDateFormat\")\n    fun nowTime(): String {\n\n        val df = SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss SSS\")\n        return df.format(Date())\n    }\n\n    fun nowTimeMillis(): Long {\n        return System.currentTimeMillis()\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/utils/RandomIdentityUtil.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.utils\n\nimport com.didichuxing.doraemonkit.util.RandomUtils\n\n\n/**\n * didi Create on 2022/3/10 .\n *\n * Copyright (c) 2022/3/10 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/3/10 7:45 下午\n * @Description 用一句话说明文件功能\n */\nobject RandomIdentityUtil {\n\n\n    fun createPid(): String {\n        return RandomUtils.random128HexString()\n    }\n\n    fun createAid(): String {\n        return RandomUtils.random64HexString()\n    }\n\n    fun createDid(): String {\n        return RandomUtils.random128HexString()\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/utils/ReflectHookUtil.java",
    "content": "package com.didichuxing.doraemonkit.kit.test.utils;\n\nimport android.content.Context;\nimport android.view.ViewParent;\nimport android.view.accessibility.AccessibilityManager;\n\nimport com.didichuxing.doraemonkit.util.ReflectUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/14-20:52\n * 描    述：\n * 修订历史：\n * ================================================\n */\npublic class ReflectHookUtil {\n    public static final String TAG = \"McUtil\";\n\n    /**\n     * WindowManagerGlobal 单例\n     */\n    private static Object mWmgInstnace = null;\n\n    /**\n     * AccessibilityManagerService 单例\n     */\n    public static Object mAccessibilityManagerService;\n\n    /**\n     * 从WindowManagerGlobal中获取ViewParent的实例为ViewRootImpl对象\n     *\n     * @return\n     */\n    public static List<ViewParent> getRootViewsFromWindowManageGlobal() {\n        try {\n            if (mWmgInstnace == null) {\n                Class wmgClass = Class.forName(\"android.view.WindowManagerGlobal\");\n                mWmgInstnace = wmgClass.getMethod(\"getInstance\").invoke(null, (Object[]) null);\n                return ReflectUtils.reflect(mWmgInstnace).field(\"mRoots\").get();\n            } else {\n                return ReflectUtils.reflect(mWmgInstnace).field(\"mRoots\").get();\n            }\n\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return new ArrayList<>();\n    }\n\n    /**\n     * hook 相关方法\n     */\n    public static void hookAccessibilityManager(Context context, boolean enable) {\n        try {\n            Class<AccessibilityManager> accessibilityManagerClass = ReflectUtils.reflect(AccessibilityManager.class).get();\n            AccessibilityManager instance = (AccessibilityManager) accessibilityManagerClass.getMethod(\"getInstance\", Context.class).invoke(null, context);\n            //先重置状态\n            ReflectUtils.reflect(instance).field(\"mIsEnabled\", enable);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/utils/ViewPathUtil.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.utils\n\nimport android.app.Activity\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ListView\nimport androidx.fragment.app.FragmentPagerAdapter\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewpager.widget.PagerAdapter\nimport androidx.viewpager.widget.ViewPager\nimport com.didichuxing.doraemonkit.extension.tagName\nimport com.didichuxing.doraemonkit.kit.core.DoKitFrameLayout\nimport com.didichuxing.doraemonkit.util.ResourceUtils\nimport com.didichuxing.doraemonkit.kit.test.event.SystemViewNode\nimport com.didichuxing.doraemonkit.util.ActivityUtils\nimport com.didichuxing.doraemonkit.util.UIUtils\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/15-16:06\n * 描    述：\n * 修订历史：\n * ================================================\n */\nobject ViewPathUtil {\n\n\n    fun getActivity(view: View?): Activity {\n        return if (view != null && view.context is Activity) {\n            view.context as Activity\n        } else {\n            ActivityUtils.getTopActivity()\n        }\n    }\n\n    private fun addParentViewInfo(\n        systemViewInfos: MutableList<SystemViewNode>,\n        parent: ViewGroup?,\n        view: View\n    ) {\n        parent?.let {\n            when (parent) {\n                is RecyclerView -> {\n                    systemViewInfos.add(\n                        SystemViewNode(\n                            parent::class.tagName,\n                            UIUtils.getRealIdText(parent),\n                            parent.childCount,\n                            parent.indexOfChild(view),\n                            true,\n                            parent.getChildAdapterPosition(view)\n                        )\n                    )\n                }\n\n                is ListView -> {\n                    systemViewInfos.add(\n                        SystemViewNode(\n                            parent::class.tagName,\n                            UIUtils.getRealIdText(parent),\n                            parent.childCount,\n                            parent.indexOfChild(view),\n                            true,\n                            parent.getPositionForView(view)\n                        )\n                    )\n                }\n\n                is ViewPager -> {\n                    systemViewInfos.add(\n                        SystemViewNode(\n                            parent::class.tagName,\n                            UIUtils.getRealIdText(parent),\n                            parent.childCount,\n                            parent.indexOfChild(view),\n                            true,\n                            parent.currentItem\n                        )\n                    )\n                }\n                else -> {\n                    systemViewInfos.add(\n                        SystemViewNode(\n                            parent::class.tagName,\n                            UIUtils.getRealIdText(parent),\n                            parent.childCount,\n                            parent.indexOfChild(view)\n                        )\n                    )\n                }\n            }\n        }\n\n\n    }\n\n\n    private fun addSelfViewInfo(\n        systemViewInfos: MutableList<SystemViewNode>,\n        view: View\n    ) {\n        when (view) {\n            is ViewPager,\n            is ListView,\n            is RecyclerView -> {\n                systemViewInfos.add(\n                    SystemViewNode(\n                        view::class.tagName,\n                        UIUtils.getRealIdText(view),\n                        if (view is ViewGroup) {\n                            view.childCount\n                        } else {\n                            -1\n                        },\n                        -1,\n                        true,\n                        -1,\n                        isCurrentEventView = true\n                    )\n                )\n            }\n            else -> {\n                systemViewInfos.add(\n                    SystemViewNode(\n                        view::class.tagName,\n                        UIUtils.getRealIdText(view),\n                        if (view is ViewGroup) {\n                            view.childCount\n                        } else {\n                            -1\n                        },\n                        -1,\n                        false,\n                        -1,\n                        isCurrentEventView = true\n                    )\n                )\n            }\n        }\n    }\n\n    /**\n     * 一机多控服务端\n     */\n    fun createViewPathOfWindow(view: View): MutableList<SystemViewNode> {\n        val systemViewInfos: MutableList<SystemViewNode> = mutableListOf()\n        addSelfViewInfo(systemViewInfos, view)\n\n        var currentView = view\n        var parentView = currentView.parent\n\n        while (parentView != null && parentView::class.tagName != \"android.view.ViewRootImpl\") {\n            if (parentView is ViewGroup) {\n                addParentViewInfo(systemViewInfos, parentView, currentView)\n                currentView = parentView\n                parentView = currentView.parent\n            }\n        }\n\n\n        return systemViewInfos\n    }\n\n\n    fun findViewByViewParentInfo(\n        decorView: ViewGroup,\n        viewParentInfos: MutableList<SystemViewNode>?\n    ): View? {\n        if (viewParentInfos == null || viewParentInfos.size == 0) {\n            return null\n        }\n\n        var targetView: View? = null\n        var viewParent: View? = null\n        //倒序查找\n        for (index in (viewParentInfos.size - 1) downTo 0) {\n            val viewParentInfo = viewParentInfos[index]\n            //decorView 特殊处理\n            if (index == viewParentInfos.size - 1) {\n                if (viewParentInfo.viewClassName == decorView::class.tagName) {\n                    viewParent = decorView.getChildAt(viewParentInfo.childIndexOfViewParent)\n                }\n                if (decorView is DoKitFrameLayout) {\n                    if (viewParentInfos.size == 1) {\n                        targetView = decorView\n                    } else {\n                        viewParent = decorView.getChildAt(viewParentInfo.childIndexOfViewParent)\n                    }\n                }\n            } else {\n                viewParent?.let {\n                    if (it is ViewGroup) {\n                        //处理特殊view\n                        if (viewParentInfo.isSpecialView) {\n                            //判断当前的view是否和数组的第一个info信息匹配\n                            if (viewParentInfos[index].isCurrentEventView && it::class.tagName == viewParentInfos[0].viewClassName\n                                && UIUtils.getRealIdText(it) == viewParentInfos[0].viewId\n                            ) {\n                                targetView = it\n                            } else {\n                                viewParent = dealSpecialViewGroup(viewParentInfo, it)\n                            }\n                        } else {\n                            //判断当前的view是否和数组的第一个info信息匹配\n                            if (viewParentInfos[index].isCurrentEventView && it::class.tagName == viewParentInfos[0].viewClassName\n                                && UIUtils.getRealIdText(it) == viewParentInfos[0].viewId\n                            ) {\n                                targetView = it\n                            } else {\n                                viewParent =\n                                    it.getChildAt(viewParentInfo.childIndexOfViewParent)\n                            }\n                        }\n\n\n                    } else {\n                        //判断当前的view是否和数组的第一个info信息匹配\n                        if (viewParentInfos[index].isCurrentEventView && it::class.tagName == viewParentInfos[0].viewClassName\n                            && UIUtils.getRealIdText(it) == viewParentInfos[0].viewId\n                        ) {\n                            targetView = it\n                        }\n                    }\n                }\n\n\n            }\n\n        }\n\n        //查询失败\n        if (targetView == null) {\n            val viewParentInfo = viewParentInfos[0]\n            if (viewParentInfo.isCurrentEventView && viewParentInfo.viewId != \"-1\") {\n                val targetViewId = ResourceUtils.getIdByName(viewParentInfo.viewId)\n                targetView = decorView.findViewById(targetViewId)\n            }\n        }\n        return targetView\n\n    }\n\n    private fun dealSpecialViewGroup(viewParentInfo: SystemViewNode, viewGroup: ViewGroup): View? {\n        try {\n            when (viewGroup) {\n                is RecyclerView -> {\n                    return viewGroup.layoutManager?.findViewByPosition(viewParentInfo.currentEventPosition)!!\n                }\n\n                is ListView -> {\n                    return getViewByPosition(viewParentInfo.currentEventPosition, viewGroup)\n                }\n\n                is ViewPager -> {\n                    if (viewGroup.currentItem != viewParentInfo.currentEventPosition) {\n                        viewGroup.currentItem = viewParentInfo.currentEventPosition\n                        Thread.sleep(10)\n                    }\n\n                    val adapter: PagerAdapter? = viewGroup.adapter\n                    adapter?.let {\n                        if (it is FragmentPagerAdapter) {\n                            val fragment = it.getItem(viewParentInfo.currentEventPosition)\n                            return fragment.requireView()\n                        } else {\n                            val item = it.instantiateItem(\n                                viewGroup,\n                                viewParentInfo.currentEventPosition\n                            )\n                            if (item is View) {\n                                return item\n                            } else {\n                                viewGroup.getChildAt(viewParentInfo.childIndexOfViewParent)\n                            }\n                        }\n                    }\n                    return viewGroup.getChildAt(viewParentInfo.childIndexOfViewParent)\n                }\n\n                else -> {\n                    return viewGroup.getChildAt(viewParentInfo.currentEventPosition)\n                }\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n            return null\n        }\n\n    }\n\n    /**\n     * 获取listView中的指定pos itemView\n     */\n    private fun getViewByPosition(pos: Int, listView: ListView): View {\n        val firstListItemPosition: Int = listView.firstVisiblePosition\n        val lastListItemPosition: Int = firstListItemPosition + listView.childCount - 1\n\n        return if (pos < firstListItemPosition || pos > lastListItemPosition) {\n            listView.adapter.getView(pos, null, listView)\n        } else {\n            val childIndex = pos - firstListItemPosition\n            listView.getChildAt(childIndex)\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/utils/WindowPathUtil.java",
    "content": "package com.didichuxing.doraemonkit.kit.test.utils;\n\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.view.ViewParent;\nimport android.view.Window;\n\nimport com.didichuxing.doraemonkit.kit.core.DoKitFrameLayout;\nimport com.didichuxing.doraemonkit.kit.test.event.WindowNode;\nimport com.didichuxing.doraemonkit.test.R;\nimport com.didichuxing.doraemonkit.util.RandomUtils;\nimport com.didichuxing.doraemonkit.util.ReflectUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport javax.annotation.Nullable;\n\n/**\n * didi Create on 2022/1/14\n * <p>\n * Copyright (c) 2022/1/14 by didiglobal.com\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/1/14 3:38 下午\n * @Description 用于查询定位页面位置\n */\n\npublic class WindowPathUtil {\n\n    private static final String DECOR_VIEW = \"com.android.internal.policy.DecorView\";\n\n    /**\n     * 查找Window上页面根视图\n     * 备注：去除Toast类型，Toast会干扰页面及页面元素查找\n     *\n     * @param viewParents viewRootIml 列表\n     * @param index       页面位置\n     * @return viewRootIml 被查找的对象\n     */\n    @Nullable\n    public static ViewParent findViewRoot(List<ViewParent> viewParents, int index) {\n        if (viewParents != null) {\n            int size = viewParents.size();\n            int indexOff = 0;\n            for (int i = 0; i < size; i++) {\n                ViewParent tmp = viewParents.get(i);\n                View view = ReflectUtils.reflect(tmp).field(\"mView\").get();\n                if (isClass(view, DECOR_VIEW)) {\n                    if (indexOff == index) {\n                        return tmp;\n                    }\n                    indexOff++;\n                }\n            }\n        }\n        return null;\n    }\n\n    /**\n     * 过滤出Window上的Activity 和Dialog\n     */\n    public static List<ViewParent> filterViewRoot(List<ViewParent> viewParents) {\n        List<ViewParent> parents = new ArrayList<>();\n        if (viewParents != null) {\n            for (ViewParent parent : viewParents) {\n                if (isPageViewRoot(parent)) {\n                    parents.add(parent);\n                }\n            }\n        }\n        return parents;\n    }\n\n\n    public static List<ViewParent> filterWindowViewRoot(List<ViewParent> viewParents, WindowNode windowNode) {\n        List<ViewParent> parents = new ArrayList<>();\n        if (viewParents != null) {\n            for (ViewParent parent : viewParents) {\n                WindowNode node = createWindowNode(parent);\n                if (TextUtils.equals(node.getName(), windowNode.getName())\n                    && node.getType() == windowNode.getType()\n                    && TextUtils.equals(node.getRootViewName(), windowNode.getRootViewName())) {\n                    parents.add(parent);\n                }\n            }\n        }\n        return parents;\n    }\n\n\n    public static WindowNode createWindowNode(ViewParent parent) {\n        WindowNode windowNode;\n        View rootView = ReflectUtils.reflect(parent).field(\"mView\").get();\n        windowNode = createWindowNode(rootView);\n        return windowNode;\n    }\n\n    public static WindowNode createWindowNode(View rootView) {\n        WindowNode windowNode;\n        Object node = rootView.getTag(R.id.dokit_test_windowNode);\n        if (node == null) {\n            Window window = ReflectUtils.reflect(rootView).field(\"mWindow\").get();\n            windowNode = new WindowNode(\n                window.getAttributes().getTitle().toString(),\n                RandomUtils.random16HexString(),\n                window.getAttributes().type,\n                rootView.getClass().getName(),\n                0\n            );\n            rootView.setTag(R.id.dokit_test_windowNode, windowNode);\n        } else {\n            windowNode = (WindowNode) node;\n        }\n        return windowNode;\n    }\n\n\n    /**\n     * 过滤出Window上的Activity 和Dialog\n     */\n    public static List<ViewParent> filterDoKitViewRoot(List<ViewParent> viewParents) {\n        List<ViewParent> parents = new ArrayList<>();\n        if (viewParents != null) {\n            for (ViewParent parent : viewParents) {\n                if (isDoKitViewRoot(parent)) {\n                    parents.add(parent);\n                }\n            }\n        }\n        return parents;\n    }\n\n    /**\n     * 找出当前屏幕上可见的视图\n     */\n    public static List<ViewParent> filterShowViewRoot(List<ViewParent> viewParents) {\n        List<ViewParent> parents = new ArrayList<>();\n        if (viewParents != null) {\n            for (ViewParent parent : viewParents) {\n                Boolean mAppVisible = ReflectUtils.reflect(parent).field(\"mAppVisible\").get();\n                if (mAppVisible) {\n                    parents.add(parent);\n                }\n            }\n        }\n        return parents;\n    }\n\n\n    public static boolean isPageViewRoot(ViewParent viewParent) {\n        if (viewParent != null) {\n            View view = ReflectUtils.reflect(viewParent).field(\"mView\").get();\n            if (isClass(view, DECOR_VIEW)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    public static boolean isDoKitViewRoot(ViewParent viewParent) {\n        if (viewParent != null) {\n            View view = ReflectUtils.reflect(viewParent).field(\"mView\").get();\n            if (view instanceof DoKitFrameLayout) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    public static String getsDoKitViewRootTitle(ViewParent viewParent) {\n        if (viewParent != null) {\n            View view = ReflectUtils.reflect(viewParent).field(\"mView\").get();\n            if (view instanceof DoKitFrameLayout) {\n                return ((DoKitFrameLayout) view).getTitle();\n            }\n        }\n        return \"\";\n    }\n\n\n    private static boolean isClass(Object o, String name) {\n        if (o != null) {\n            if (TextUtils.equals(o.getClass().getName(), name)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/utils/XposedHookUtil.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.utils\n\nimport android.content.Context\nimport android.os.Build\nimport android.view.View\nimport android.view.ViewParent\nimport android.view.accessibility.AccessibilityEvent\nimport android.view.accessibility.AccessibilityManager\nimport com.didichuxing.doraemonkit.kit.test.hook.AccessibilityGetInstanceMethodHook\nimport com.didichuxing.doraemonkit.kit.test.hook.ViewOnClickListenerEventHook\nimport com.didichuxing.doraemonkit.kit.test.hook.ViewOnInitializeAccessibilityEventHook\nimport com.didichuxing.doraemonkit.util.Utils\nimport de.robv.android.xposed.DexposedBridge\nimport java.lang.reflect.Method\n\n/**\n * ================================================\n * 作    者：jint（金台）\n * 版    本：1.0\n * 创建日期：2020/12/14-20:06\n * 描    述：\n * 修订历史：\n * ================================================\n * hook 实现\n */\nobject XposedHookUtil {\n\n    var ROOT_VIEWS: List<ViewParent>? = null\n\n    private var runTimeHookEnable: Boolean = false\n\n\n    private val accessibilityEventHook = ViewOnInitializeAccessibilityEventHook()\n\n    /**\n     * 运行时hook是否开启\n     */\n    fun isRunTimeHookEnable(): Boolean {\n        return runTimeHookEnable\n    }\n\n    /**\n     * 开始运行时 hook\n     */\n    fun startRunTimeHook(context: Context? = Utils.getApp()) {\n        runTimeHookEnable = true\n        ReflectHookUtil.hookAccessibilityManager(context, true)\n        hookAccessibilityEvent()\n    }\n\n    /**\n     * 停止运行时 hook\n     */\n    fun stopRunTimeHook(context: Context? = Utils.getApp()) {\n        ReflectHookUtil.hookAccessibilityManager(context, false)\n        unHookAccessibilityEvent()\n        runTimeHookEnable = false\n    }\n\n    fun hookAccessibilityManager() {\n        //绕过无障碍的权限\n        DexposedBridge.findAndHookMethod(\n            AccessibilityManager::class.java,\n            \"isEnabled\",\n            AccessibilityGetInstanceMethodHook()\n        )\n    }\n\n    private fun hookAccessibilityEvent() {\n        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {\n            try {\n                DexposedBridge.findAndHookMethod(\n                    View::class.java,\n                    \"onInitializeAccessibilityEvent\",\n                    AccessibilityEvent::class.java,\n                    accessibilityEventHook\n                )\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    private fun unHookAccessibilityEvent() {\n        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {\n            try {\n                val method: Method = View::class.java.getDeclaredMethod(\"onInitializeAccessibilityEvent\", AccessibilityEvent::class.java)\n                DexposedBridge.unhookMethod(method, accessibilityEventHook)\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    /**\n     * 全局Hook应用启动后直接Hook全部实现\n     */\n    fun globalHook() {\n        //hook onClick事件\n        hookViewOnClickListener()\n        //hook WindowGlobal\n        hookWindowManagerGlobal()\n    }\n\n    private fun hookViewOnClickListener() {\n        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {\n            DexposedBridge.findAndHookMethod(\n                View::class.java,\n                \"setOnClickListener\",\n                View.OnClickListener::class.java,\n                ViewOnClickListenerEventHook()\n            )\n        }\n    }\n\n    private fun hookWindowManagerGlobal() {\n        ROOT_VIEWS = ReflectHookUtil.getRootViewsFromWindowManageGlobal()\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/widget/FlashImageView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.widget\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.flowOn\n\n\n/**\n * didi Create on 2022/4/14 .\n *\n * Copyright (c) 2022/4/14 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/14 4:24 下午\n * @Description 用一句话说明文件功能\n */\n\nclass FlashImageView : androidx.appcompat.widget.AppCompatImageView {\n\n    private val flashViewScope = MainScope() + CoroutineName(this.toString())\n\n    private var flashFlow = flow {\n        while (true) {\n            emit(0)\n            delay(500)\n            emit(1)\n            delay(500)\n        }\n    }\n\n    private var flashEnable: Boolean = false\n\n    constructor(context: Context?) : super(context)\n    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)\n    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n\n    fun isFlashEnable(): Boolean {\n        return flashEnable\n    }\n\n    fun cancelFlash() {\n        flashViewScope.cancel()\n        flashEnable = false\n    }\n\n    fun startFlash() {\n        if (!flashEnable) {\n            flashEnable = true\n            flashViewScope.launch {\n                flashFlow.flowOn(Dispatchers.IO)\n                    .collect {\n                        when (it) {\n                            0 -> visibility = View.VISIBLE\n                            1 -> visibility = View.INVISIBLE\n                            else -> {\n                            }\n                        }\n                    }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/main/java/com/didichuxing/doraemonkit/kit/test/widget/FlashTextView.kt",
    "content": "package com.didichuxing.doraemonkit.kit.test.widget\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.flowOn\n\n\n/**\n * didi Create on 2022/4/14 .\n *\n * Copyright (c) 2022/4/14 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/14 4:27 下午\n * @Description 用一句话说明文件功能\n */\n\nclass FlashTextView : androidx.appcompat.widget.AppCompatTextView {\n\n    private val flashViewScope = MainScope() + CoroutineName(this.toString())\n\n    private var flashFlow = flow {\n        while (true) {\n            (0..3).forEach {\n                emit(it)\n                delay(500)\n            }\n        }\n    }\n\n    private var flashEnable: Boolean = false\n\n    constructor(context: Context?) : super(context)\n    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)\n    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\n    fun isFlashEnable(): Boolean {\n        return flashEnable\n    }\n\n    fun cancelFlash() {\n        flashViewScope.cancel()\n        flashEnable = false\n    }\n\n    fun startFlash() {\n        if (!flashEnable) {\n            flashEnable = true\n            flashViewScope.launch {\n                flashFlow.flowOn(Dispatchers.IO)\n                    .collect {\n                        when (it) {\n                            0 -> text = \"\"\n                            1 -> text = \".\"\n                            2 -> text = \"..\"\n                            3 -> text = \"...\"\n                        }\n                    }\n            }\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-test/src/test/java/com/didichuxing/doraemonkit/kit/test/ExampleUnitTest.java",
    "content": "package com.didichuxing.doraemonkit.kit.test;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() {\n        assertEquals(4, 2 + 2);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-test/upload.sh",
    "content": "#!/usr/bin/env bash\necho -n  \"please enter bintray userid ->\"\nread  userid_bintray\necho -n  \"please enter bintray apikey ->\"\nread  apikey_bintray\n../gradlew wrapper  clean build --stacktrace  --info  bintrayUpload -PbintrayUser=${userid_bintray} -PbintrayKey=${apikey_bintray} -PdryRun=false"
  },
  {
    "path": "Android/dokit-util/.gitignore",
    "content": "/build"
  },
  {
    "path": "Android/dokit-util/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply from: '../upload.gradle'\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_16\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n}\n\n\n\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    if (needKotlinV14()) {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v14\"]\n    } else {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v13\"]\n    }\n    implementation rootProject.ext.dependencies[\"annotation\"]\n    implementation rootProject.ext.dependencies[\"fragment\"]\n    implementation rootProject.ext.dependencies[\"drawerlayout\"]\n    implementation rootProject.ext.dependencies[\"material\"]\n    implementation rootProject.ext.dependencies[\"gson\"]\n}\n"
  },
  {
    "path": "Android/dokit-util/gradle.properties",
    "content": "ARTIFACT_ID=dokitx-util"
  },
  {
    "path": "Android/dokit-util/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original from file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "Android/dokit-util/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.didichuxing.doraemonkit.util\">\n\n    <uses-permission android:name=\"android.permission.READ_PRIVILEGED_PHONE_STATE\" />\n</manifest>"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/constant/CacheConstants.java",
    "content": "package com.didichuxing.doraemonkit.constant;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/06/13\n *     desc  : constants of cache\n * </pre>\n */\npublic interface CacheConstants {\n    int SEC  = 1;\n    int MIN  = 60;\n    int HOUR = 3600;\n    int DAY  = 86400;\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/constant/MemoryConstants.java",
    "content": "package com.didichuxing.doraemonkit.constant;\n\n\nimport androidx.annotation.IntDef;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2017/03/13\n *     desc  : constants of memory\n * </pre>\n */\npublic final class MemoryConstants {\n\n    public static final int BYTE = 1;\n    public static final int KB = 1024;\n    public static final int MB = 1048576;\n    public static final int GB = 1073741824;\n\n    @IntDef({BYTE, KB, MB, GB})\n    @Retention(RetentionPolicy.SOURCE)\n    public @interface Unit {\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/constant/PermissionConstants.java",
    "content": "package com.didichuxing.doraemonkit.constant;\n\nimport android.Manifest.permission;\nimport android.annotation.SuppressLint;\nimport android.os.Build;\n\nimport androidx.annotation.StringDef;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2017/12/29\n *     desc  : constants of permission\n * </pre>\n */\n@SuppressLint(\"InlinedApi\")\npublic final class PermissionConstants {\n\n    public static final String CALENDAR             = \"CALENDAR\";\n    public static final String CAMERA               = \"CAMERA\";\n    public static final String CONTACTS             = \"CONTACTS\";\n    public static final String LOCATION             = \"LOCATION\";\n    public static final String MICROPHONE           = \"MICROPHONE\";\n    public static final String PHONE                = \"PHONE\";\n    public static final String SENSORS              = \"SENSORS\";\n    public static final String SMS                  = \"SMS\";\n    public static final String STORAGE              = \"STORAGE\";\n    public static final String ACTIVITY_RECOGNITION = \"ACTIVITY_RECOGNITION\";\n\n    private static final String[] GROUP_CALENDAR             = {\n            permission.READ_CALENDAR, permission.WRITE_CALENDAR\n    };\n    private static final String[] GROUP_CAMERA               = {\n            permission.CAMERA\n    };\n    private static final String[] GROUP_CONTACTS             = {\n            permission.READ_CONTACTS, permission.WRITE_CONTACTS, permission.GET_ACCOUNTS\n    };\n    private static final String[] GROUP_LOCATION             = {\n            permission.ACCESS_FINE_LOCATION, permission.ACCESS_COARSE_LOCATION, permission.ACCESS_BACKGROUND_LOCATION\n    };\n    private static final String[] GROUP_MICROPHONE           = {\n            permission.RECORD_AUDIO\n    };\n    private static final String[] GROUP_PHONE                = {\n            permission.READ_PHONE_STATE, permission.READ_PHONE_NUMBERS, permission.CALL_PHONE,\n            permission.READ_CALL_LOG, permission.WRITE_CALL_LOG, permission.ADD_VOICEMAIL,\n            permission.USE_SIP, permission.PROCESS_OUTGOING_CALLS, permission.ANSWER_PHONE_CALLS\n    };\n    private static final String[] GROUP_PHONE_BELOW_O        = {\n            permission.READ_PHONE_STATE, permission.READ_PHONE_NUMBERS, permission.CALL_PHONE,\n            permission.READ_CALL_LOG, permission.WRITE_CALL_LOG, permission.ADD_VOICEMAIL,\n            permission.USE_SIP, permission.PROCESS_OUTGOING_CALLS\n    };\n    private static final String[] GROUP_SENSORS              = {\n            permission.BODY_SENSORS\n    };\n    private static final String[] GROUP_SMS                  = {\n            permission.SEND_SMS, permission.RECEIVE_SMS, permission.READ_SMS,\n            permission.RECEIVE_WAP_PUSH, permission.RECEIVE_MMS,\n    };\n    private static final String[] GROUP_STORAGE              = {\n            permission.READ_EXTERNAL_STORAGE, permission.WRITE_EXTERNAL_STORAGE,\n    };\n    private static final String[] GROUP_ACTIVITY_RECOGNITION = {\n            permission.ACTIVITY_RECOGNITION,\n    };\n\n    @StringDef({CALENDAR, CAMERA, CONTACTS, LOCATION, MICROPHONE, PHONE, SENSORS, SMS, STORAGE,})\n    @Retention(RetentionPolicy.SOURCE)\n    public @interface PermissionGroup {\n    }\n\n    public static String[] getPermissions(@PermissionGroup final String permission) {\n        if (permission == null) return new String[0];\n        switch (permission) {\n            case CALENDAR:\n                return GROUP_CALENDAR;\n            case CAMERA:\n                return GROUP_CAMERA;\n            case CONTACTS:\n                return GROUP_CONTACTS;\n            case LOCATION:\n                return GROUP_LOCATION;\n            case MICROPHONE:\n                return GROUP_MICROPHONE;\n            case PHONE:\n                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {\n                    return GROUP_PHONE_BELOW_O;\n                } else {\n                    return GROUP_PHONE;\n                }\n            case SENSORS:\n                return GROUP_SENSORS;\n            case SMS:\n                return GROUP_SMS;\n            case STORAGE:\n                return GROUP_STORAGE;\n            case ACTIVITY_RECOGNITION:\n                return GROUP_ACTIVITY_RECOGNITION;\n        }\n        return new String[]{permission};\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/constant/RegexConstants.java",
    "content": "package com.didichuxing.doraemonkit.constant;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2017/03/13\n *     desc  : constants of regex\n * </pre>\n */\npublic final class RegexConstants {\n\n    /**\n     * Regex of simple mobile.\n     */\n    public static final String REGEX_MOBILE_SIMPLE = \"^[1]\\\\d{10}$\";\n    /**\n     * Regex of exact mobile.\n     * <p>china mobile: 134(0-8), 135, 136, 137, 138, 139, 147, 150, 151, 152, 157, 158, 159, 165, 172, 178, 182, 183, 184, 187, 188, 195, 197, 198</p>\n     * <p>china unicom: 130, 131, 132, 145, 155, 156, 166, 167, 175, 176, 185, 186, 196</p>\n     * <p>china telecom: 133, 149, 153, 162, 173, 177, 180, 181, 189, 190, 191, 199</p>\n     * <p>china broadcasting: 192</p>\n     * <p>global star: 1349</p>\n     * <p>virtual operator: 170, 171</p>\n     */\n    public static final String REGEX_MOBILE_EXACT  = \"^((13[0-9])|(14[579])|(15[0-35-9])|(16[2567])|(17[0-35-8])|(18[0-9])|(19[0-35-9]))\\\\d{8}$\";\n    /**\n     * Regex of telephone number.\n     */\n    public static final String REGEX_TEL           = \"^0\\\\d{2,3}[- ]?\\\\d{7,8}$\";\n    /**\n     * Regex of id card number which length is 15.\n     */\n    public static final String REGEX_ID_CARD15     = \"^[1-9]\\\\d{7}((0\\\\d)|(1[0-2]))(([0|1|2]\\\\d)|3[0-1])\\\\d{3}$\";\n    /**\n     * Regex of id card number which length is 18.\n     */\n    public static final String REGEX_ID_CARD18     = \"^[1-9]\\\\d{5}[1-9]\\\\d{3}((0\\\\d)|(1[0-2]))(([0|1|2]\\\\d)|3[0-1])\\\\d{3}([0-9Xx])$\";\n    /**\n     * Regex of email.\n     */\n    public static final String REGEX_EMAIL         = \"^\\\\w+([-+.]\\\\w+)*@\\\\w+([-.]\\\\w+)*\\\\.\\\\w+([-.]\\\\w+)*$\";\n    /**\n     * Regex of url.\n     */\n    public static final String REGEX_URL           = \"[a-zA-z]+://[^\\\\s]*\";\n    /**\n     * Regex of Chinese character.\n     */\n    public static final String REGEX_ZH            = \"^[\\\\u4e00-\\\\u9fa5]+$\";\n    /**\n     * Regex of username.\n     * <p>scope for \"a-z\", \"A-Z\", \"0-9\", \"_\", \"Chinese character\"</p>\n     * <p>can't end with \"_\"</p>\n     * <p>length is between 6 to 20</p>\n     */\n    public static final String REGEX_USERNAME      = \"^[\\\\w\\\\u4e00-\\\\u9fa5]{6,20}(?<!_)$\";\n    /**\n     * Regex of date which pattern is \"yyyy-MM-dd\".\n     */\n    public static final String REGEX_DATE          = \"^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$\";\n    /**\n     * Regex of ip address.\n     */\n    public static final String REGEX_IP            = \"((2[0-4]\\\\d|25[0-5]|[01]?\\\\d\\\\d?)\\\\.){3}(2[0-4]\\\\d|25[0-5]|[01]?\\\\d\\\\d?)\";\n\n    ///////////////////////////////////////////////////////////////////////////\n    // The following come from http://tool.oschina.net/regex\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Regex of double-byte characters.\n     */\n    public static final String REGEX_DOUBLE_BYTE_CHAR     = \"[^\\\\x00-\\\\xff]\";\n    /**\n     * Regex of blank line.\n     */\n    public static final String REGEX_BLANK_LINE           = \"\\\\n\\\\s*\\\\r\";\n    /**\n     * Regex of QQ number.\n     */\n    public static final String REGEX_QQ_NUM               = \"[1-9][0-9]{4,}\";\n    /**\n     * Regex of postal code in China.\n     */\n    public static final String REGEX_CHINA_POSTAL_CODE    = \"[1-9]\\\\d{5}(?!\\\\d)\";\n    /**\n     * Regex of integer.\n     */\n    public static final String REGEX_INTEGER              = \"^(-?[1-9]\\\\d*)|0$\";\n    /**\n     * Regex of positive integer.\n     */\n    public static final String REGEX_POSITIVE_INTEGER     = \"^[1-9]\\\\d*$\";\n    /**\n     * Regex of negative integer.\n     */\n    public static final String REGEX_NEGATIVE_INTEGER     = \"^-[1-9]\\\\d*$\";\n    /**\n     * Regex of non-negative integer.\n     */\n    public static final String REGEX_NOT_NEGATIVE_INTEGER = \"^[1-9]\\\\d*|0$\";\n    /**\n     * Regex of non-positive integer.\n     */\n    public static final String REGEX_NOT_POSITIVE_INTEGER = \"^-[1-9]\\\\d*|0$\";\n    /**\n     * Regex of positive float.\n     */\n    public static final String REGEX_FLOAT                = \"^-?([1-9]\\\\d*\\\\.\\\\d*|0\\\\.\\\\d*[1-9]\\\\d*|0?\\\\.0+|0)$\";\n    /**\n     * Regex of positive float.\n     */\n    public static final String REGEX_POSITIVE_FLOAT       = \"^[1-9]\\\\d*\\\\.\\\\d*|0\\\\.\\\\d*[1-9]\\\\d*$\";\n    /**\n     * Regex of negative float.\n     */\n    public static final String REGEX_NEGATIVE_FLOAT       = \"^-[1-9]\\\\d*\\\\.\\\\d*|-0\\\\.\\\\d*[1-9]\\\\d*$\";\n    /**\n     * Regex of positive float.\n     */\n    public static final String REGEX_NOT_NEGATIVE_FLOAT   = \"^[1-9]\\\\d*\\\\.\\\\d*|0\\\\.\\\\d*[1-9]\\\\d*|0?\\\\.0+|0$\";\n    /**\n     * Regex of negative float.\n     */\n    public static final String REGEX_NOT_POSITIVE_FLOAT   = \"^(-([1-9]\\\\d*\\\\.\\\\d*|0\\\\.\\\\d*[1-9]\\\\d*))|0?\\\\.0+|0$\";\n\n    ///////////////////////////////////////////////////////////////////////////\n    // If u want more please visit http://toutiao.com/i6231678548520731137\n    ///////////////////////////////////////////////////////////////////////////\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/constant/TimeConstants.java",
    "content": "package com.didichuxing.doraemonkit.constant;\n\nimport androidx.annotation.IntDef;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2017/03/13\n *     desc  : constants of time\n * </pre>\n */\npublic final class TimeConstants {\n\n    public static final int MSEC = 1;\n    public static final int SEC  = 1000;\n    public static final int MIN  = 60000;\n    public static final int HOUR = 3600000;\n    public static final int DAY  = 86400000;\n\n    @IntDef({MSEC, SEC, MIN, HOUR, DAY})\n    @Retention(RetentionPolicy.SOURCE)\n    public @interface Unit {\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ActivityUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.Activity;\n//import android.app.Fragment;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.os.Bundle;\n//import androidx.annotation.AnimRes;\n//import androidx.core.app.ActivityOptionsCompat;\n//import androidx.fragment.app.Fragment;\n//import androidx.core.util.Pair;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.View;\n\nimport androidx.annotation.AnimRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.app.ActivityOptionsCompat;\nimport androidx.core.util.Pair;\nimport androidx.fragment.app.Fragment;\n\nimport java.lang.ref.WeakReference;\nimport java.lang.reflect.Field;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/09/23\n *     desc  : utils about activity\n * </pre>\n */\npublic final class ActivityUtils {\n\n    private ActivityUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Add callbacks of activity lifecycle.\n     *\n     * @param callbacks The callbacks.\n     */\n    public static void addActivityLifecycleCallbacks(@Nullable final Utils.ActivityLifecycleCallbacks callbacks) {\n        UtilsBridge.addActivityLifecycleCallbacks(callbacks);\n    }\n\n    /**\n     * Add callbacks of activity lifecycle.\n     *\n     * @param activity  The activity.\n     * @param callbacks The callbacks.\n     */\n    public static void addActivityLifecycleCallbacks(@Nullable final Activity activity,\n                                                     @Nullable final Utils.ActivityLifecycleCallbacks callbacks) {\n        UtilsBridge.addActivityLifecycleCallbacks(activity, callbacks);\n    }\n\n    /**\n     * Remove callbacks of activity lifecycle.\n     *\n     * @param callbacks The callbacks.\n     */\n    public static void removeActivityLifecycleCallbacks(@Nullable final Utils.ActivityLifecycleCallbacks callbacks) {\n        UtilsBridge.removeActivityLifecycleCallbacks(callbacks);\n    }\n\n    /**\n     * Remove callbacks of activity lifecycle.\n     *\n     * @param activity The activity.\n     */\n    public static void removeActivityLifecycleCallbacks(@Nullable final Activity activity) {\n        UtilsBridge.removeActivityLifecycleCallbacks(activity);\n    }\n\n    /**\n     * Remove callbacks of activity lifecycle.\n     *\n     * @param activity  The activity.\n     * @param callbacks The callbacks.\n     */\n    public static void removeActivityLifecycleCallbacks(@Nullable final Activity activity,\n                                                        @Nullable final Utils.ActivityLifecycleCallbacks callbacks) {\n        UtilsBridge.removeActivityLifecycleCallbacks(activity, callbacks);\n    }\n\n    /**\n     * Return the activity by context.\n     *\n     * @param context The context.\n     * @return the activity by context.\n     */\n    @Nullable\n    public static Activity getActivityByContext(@NonNull Context context) {\n        Activity activity = getActivityByContextInner(context);\n        if (!isActivityAlive(activity)) return null;\n        return activity;\n    }\n\n    @Nullable\n    private static Activity getActivityByContextInner(@Nullable Context context) {\n        if (context == null) return null;\n        List<Context> list = new ArrayList<>();\n        while (context instanceof ContextWrapper) {\n            if (context instanceof Activity) {\n                return (Activity) context;\n            }\n            Activity activity = getActivityFromDecorContext(context);\n            if (activity != null) return activity;\n            list.add(context);\n            context = ((ContextWrapper) context).getBaseContext();\n            if (context == null) {\n                return null;\n            }\n            if (list.contains(context)) {\n                // loop context\n                return null;\n            }\n        }\n        return null;\n    }\n\n    @Nullable\n    private static Activity getActivityFromDecorContext(@Nullable Context context) {\n        if (context == null) return null;\n        if (context.getClass().getName().equals(\"com.android.internal.policy.DecorContext\")) {\n            try {\n                Field mActivityContextField = context.getClass().getDeclaredField(\"mActivityContext\");\n                mActivityContextField.setAccessible(true);\n                //noinspection ConstantConditions,unchecked\n                return ((WeakReference<Activity>) mActivityContextField.get(context)).get();\n            } catch (Exception ignore) {\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Return whether the activity exists.\n     *\n     * @param pkg The name of the package.\n     * @param cls The name of the class.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isActivityExists(@NonNull final String pkg,\n                                           @NonNull final String cls) {\n        Intent intent = new Intent();\n        intent.setClassName(pkg, cls);\n        PackageManager pm = Utils.getApp().getPackageManager();\n        return !(pm.resolveActivity(intent, 0) == null ||\n                intent.resolveActivity(pm) == null ||\n                pm.queryIntentActivities(intent, 0).size() == 0);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param clz The activity class.\n     */\n    public static void startActivity(@NonNull final Class<? extends Activity> clz) {\n        Context context = getTopActivityOrApp();\n        startActivity(context, null, context.getPackageName(), clz.getName(), null);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param clz     The activity class.\n     * @param options Additional options for how the Activity should be started.\n     */\n    public static void startActivity(@NonNull final Class<? extends Activity> clz,\n                                     @Nullable final Bundle options) {\n        Context context = getTopActivityOrApp();\n        startActivity(context, null, context.getPackageName(), clz.getName(), options);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param clz       The activity class.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void startActivity(@NonNull final Class<? extends Activity> clz,\n                                     @AnimRes final int enterAnim,\n                                     @AnimRes final int exitAnim) {\n        Context context = getTopActivityOrApp();\n        startActivity(context, null, context.getPackageName(), clz.getName(),\n                getOptionsBundle(context, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && context instanceof Activity) {\n            ((Activity) context).overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity The activity.\n     * @param clz      The activity class.\n     */\n    public static void startActivity(@NonNull final Activity activity,\n                                     @NonNull final Class<? extends Activity> clz) {\n        startActivity(activity, null, activity.getPackageName(), clz.getName(), null);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity The activity.\n     * @param clz      The activity class.\n     * @param options  Additional options for how the Activity should be started.\n     */\n    public static void startActivity(@NonNull final Activity activity,\n                                     @NonNull final Class<? extends Activity> clz,\n                                     @Nullable final Bundle options) {\n        startActivity(activity, null, activity.getPackageName(), clz.getName(), options);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity       The activity.\n     * @param clz            The activity class.\n     * @param sharedElements The names of the shared elements to transfer to the called\n     *                       Activity and their associated Views.\n     */\n    public static void startActivity(@NonNull final Activity activity,\n                                     @NonNull final Class<? extends Activity> clz,\n                                     final View... sharedElements) {\n        startActivity(activity, null, activity.getPackageName(), clz.getName(),\n                getOptionsBundle(activity, sharedElements));\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity  The activity.\n     * @param clz       The activity class.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void startActivity(@NonNull final Activity activity,\n                                     @NonNull final Class<? extends Activity> clz,\n                                     @AnimRes final int enterAnim,\n                                     @AnimRes final int exitAnim) {\n        startActivity(activity, null, activity.getPackageName(), clz.getName(),\n                getOptionsBundle(activity, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {\n            activity.overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras The Bundle of extras to add to this intent.\n     * @param clz    The activity class.\n     */\n    public static void startActivity(@NonNull final Bundle extras,\n                                     @NonNull final Class<? extends Activity> clz) {\n        Context context = getTopActivityOrApp();\n        startActivity(context, extras, context.getPackageName(), clz.getName(), null);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras  The Bundle of extras to add to this intent.\n     * @param clz     The activity class.\n     * @param options Additional options for how the Activity should be started.\n     */\n    public static void startActivity(@NonNull final Bundle extras,\n                                     @NonNull final Class<? extends Activity> clz,\n                                     @Nullable final Bundle options) {\n        Context context = getTopActivityOrApp();\n        startActivity(context, extras, context.getPackageName(), clz.getName(), options);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras    The Bundle of extras to add to this intent.\n     * @param clz       The activity class.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void startActivity(@NonNull final Bundle extras,\n                                     @NonNull final Class<? extends Activity> clz,\n                                     @AnimRes final int enterAnim,\n                                     @AnimRes final int exitAnim) {\n        Context context = getTopActivityOrApp();\n        startActivity(context, extras, context.getPackageName(), clz.getName(),\n                getOptionsBundle(context, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && context instanceof Activity) {\n            ((Activity) context).overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras   The Bundle of extras to add to this intent.\n     * @param activity The activity.\n     * @param clz      The activity class.\n     */\n    public static void startActivity(@NonNull final Bundle extras,\n                                     @NonNull final Activity activity,\n                                     @NonNull final Class<? extends Activity> clz) {\n        startActivity(activity, extras, activity.getPackageName(), clz.getName(), null);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras   The Bundle of extras to add to this intent.\n     * @param activity The activity.\n     * @param clz      The activity class.\n     * @param options  Additional options for how the Activity should be started.\n     */\n    public static void startActivity(@NonNull final Bundle extras,\n                                     @NonNull final Activity activity,\n                                     @NonNull final Class<? extends Activity> clz,\n                                     @Nullable final Bundle options) {\n        startActivity(activity, extras, activity.getPackageName(), clz.getName(), options);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras         The Bundle of extras to add to this intent.\n     * @param activity       The activity.\n     * @param clz            The activity class.\n     * @param sharedElements The names of the shared elements to transfer to the called\n     *                       Activity and their associated Views.\n     */\n    public static void startActivity(@NonNull final Bundle extras,\n                                     @NonNull final Activity activity,\n                                     @NonNull final Class<? extends Activity> clz,\n                                     final View... sharedElements) {\n        startActivity(activity, extras, activity.getPackageName(), clz.getName(),\n                getOptionsBundle(activity, sharedElements));\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras    The Bundle of extras to add to this intent.\n     * @param activity  The activity.\n     * @param clz       The activity class.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void startActivity(@NonNull final Bundle extras,\n                                     @NonNull final Activity activity,\n                                     @NonNull final Class<? extends Activity> clz,\n                                     @AnimRes final int enterAnim,\n                                     @AnimRes final int exitAnim) {\n        startActivity(activity, extras, activity.getPackageName(), clz.getName(),\n                getOptionsBundle(activity, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {\n            activity.overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param pkg The name of the package.\n     * @param cls The name of the class.\n     */\n    public static void startActivity(@NonNull final String pkg,\n                                     @NonNull final String cls) {\n        startActivity(getTopActivityOrApp(), null, pkg, cls, null);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param pkg     The name of the package.\n     * @param cls     The name of the class.\n     * @param options Additional options for how the Activity should be started.\n     */\n    public static void startActivity(@NonNull final String pkg,\n                                     @NonNull final String cls,\n                                     @Nullable final Bundle options) {\n        startActivity(getTopActivityOrApp(), null, pkg, cls, options);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param pkg       The name of the package.\n     * @param cls       The name of the class.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void startActivity(@NonNull final String pkg,\n                                     @NonNull final String cls,\n                                     @AnimRes final int enterAnim,\n                                     @AnimRes final int exitAnim) {\n        Context context = getTopActivityOrApp();\n        startActivity(context, null, pkg, cls, getOptionsBundle(context, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && context instanceof Activity) {\n            ((Activity) context).overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity The activity.\n     * @param pkg      The name of the package.\n     * @param cls      The name of the class.\n     */\n    public static void startActivity(@NonNull final Activity activity,\n                                     @NonNull final String pkg,\n                                     @NonNull final String cls) {\n        startActivity(activity, null, pkg, cls, null);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity The activity.\n     * @param pkg      The name of the package.\n     * @param cls      The name of the class.\n     * @param options  Additional options for how the Activity should be started.\n     */\n    public static void startActivity(@NonNull final Activity activity,\n                                     @NonNull final String pkg,\n                                     @NonNull final String cls,\n                                     @Nullable final Bundle options) {\n        startActivity(activity, null, pkg, cls, options);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity       The activity.\n     * @param pkg            The name of the package.\n     * @param cls            The name of the class.\n     * @param sharedElements The names of the shared elements to transfer to the called\n     *                       Activity and their associated Views.\n     */\n    public static void startActivity(@NonNull final Activity activity,\n                                     @NonNull final String pkg,\n                                     @NonNull final String cls,\n                                     final View... sharedElements) {\n        startActivity(activity, null, pkg, cls, getOptionsBundle(activity, sharedElements));\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity  The activity.\n     * @param pkg       The name of the package.\n     * @param cls       The name of the class.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void startActivity(@NonNull final Activity activity,\n                                     @NonNull final String pkg,\n                                     @NonNull final String cls,\n                                     @AnimRes final int enterAnim,\n                                     @AnimRes final int exitAnim) {\n        startActivity(activity, null, pkg, cls, getOptionsBundle(activity, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {\n            activity.overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras The Bundle of extras to add to this intent.\n     * @param pkg    The name of the package.\n     * @param cls    The name of the class.\n     */\n    public static void startActivity(@NonNull final Bundle extras,\n                                     @NonNull final String pkg,\n                                     @NonNull final String cls) {\n        startActivity(getTopActivityOrApp(), extras, pkg, cls, null);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras  The Bundle of extras to add to this intent.\n     * @param pkg     The name of the package.\n     * @param cls     The name of the class.\n     * @param options Additional options for how the Activity should be started.\n     */\n    public static void startActivity(@NonNull final Bundle extras,\n                                     @NonNull final String pkg,\n                                     @NonNull final String cls,\n                                     @Nullable final Bundle options) {\n        startActivity(getTopActivityOrApp(), extras, pkg, cls, options);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras    The Bundle of extras to add to this intent.\n     * @param pkg       The name of the package.\n     * @param cls       The name of the class.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void startActivity(@NonNull final Bundle extras,\n                                     @NonNull final String pkg,\n                                     @NonNull final String cls,\n                                     @AnimRes final int enterAnim,\n                                     @AnimRes final int exitAnim) {\n        Context context = getTopActivityOrApp();\n        startActivity(context, extras, pkg, cls, getOptionsBundle(context, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && context instanceof Activity) {\n            ((Activity) context).overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity The activity.\n     * @param extras   The Bundle of extras to add to this intent.\n     * @param pkg      The name of the package.\n     * @param cls      The name of the class.\n     */\n    public static void startActivity(@NonNull final Bundle extras,\n                                     @NonNull final Activity activity,\n                                     @NonNull final String pkg,\n                                     @NonNull final String cls) {\n        startActivity(activity, extras, pkg, cls, null);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras   The Bundle of extras to add to this intent.\n     * @param activity The activity.\n     * @param pkg      The name of the package.\n     * @param cls      The name of the class.\n     * @param options  Additional options for how the Activity should be started.\n     */\n    public static void startActivity(@NonNull final Bundle extras,\n                                     @NonNull final Activity activity,\n                                     @NonNull final String pkg,\n                                     @NonNull final String cls,\n                                     @Nullable final Bundle options) {\n        startActivity(activity, extras, pkg, cls, options);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras         The Bundle of extras to add to this intent.\n     * @param activity       The activity.\n     * @param pkg            The name of the package.\n     * @param cls            The name of the class.\n     * @param sharedElements The names of the shared elements to transfer to the called\n     *                       Activity and their associated Views.\n     */\n    public static void startActivity(@NonNull final Bundle extras,\n                                     @NonNull final Activity activity,\n                                     @NonNull final String pkg,\n                                     @NonNull final String cls,\n                                     final View... sharedElements) {\n        startActivity(activity, extras, pkg, cls, getOptionsBundle(activity, sharedElements));\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras    The Bundle of extras to add to this intent.\n     * @param pkg       The name of the package.\n     * @param cls       The name of the class.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void startActivity(@NonNull final Bundle extras,\n                                     @NonNull final Activity activity,\n                                     @NonNull final String pkg,\n                                     @NonNull final String cls,\n                                     @AnimRes final int enterAnim,\n                                     @AnimRes final int exitAnim) {\n        startActivity(activity, extras, pkg, cls, getOptionsBundle(activity, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {\n            activity.overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param intent The description of the activity to start.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean startActivity(@NonNull final Intent intent) {\n        return startActivity(intent, getTopActivityOrApp(), null);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param intent  The description of the activity to start.\n     * @param options Additional options for how the Activity should be started.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean startActivity(@NonNull final Intent intent,\n                                        @Nullable final Bundle options) {\n        return startActivity(intent, getTopActivityOrApp(), options);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param intent    The description of the activity to start.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean startActivity(@NonNull final Intent intent,\n                                        @AnimRes final int enterAnim,\n                                        @AnimRes final int exitAnim) {\n        Context context = getTopActivityOrApp();\n        boolean isSuccess = startActivity(intent, context, getOptionsBundle(context, enterAnim, exitAnim));\n        if (isSuccess) {\n            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && context instanceof Activity) {\n                ((Activity) context).overridePendingTransition(enterAnim, exitAnim);\n            }\n        }\n        return isSuccess;\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity The activity.\n     * @param intent   The description of the activity to start.\n     */\n    public static void startActivity(@NonNull final Activity activity,\n                                     @NonNull final Intent intent) {\n        startActivity(intent, activity, null);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity The activity.\n     * @param intent   The description of the activity to start.\n     * @param options  Additional options for how the Activity should be started.\n     */\n    public static void startActivity(@NonNull final Activity activity,\n                                     @NonNull final Intent intent,\n                                     @Nullable final Bundle options) {\n        startActivity(intent, activity, options);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity       The activity.\n     * @param intent         The description of the activity to start.\n     * @param sharedElements The names of the shared elements to transfer to the called\n     *                       Activity and their associated Views.\n     */\n    public static void startActivity(@NonNull final Activity activity,\n                                     @NonNull final Intent intent,\n                                     final View... sharedElements) {\n        startActivity(intent, activity, getOptionsBundle(activity, sharedElements));\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity  The activity.\n     * @param intent    The description of the activity to start.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void startActivity(@NonNull final Activity activity,\n                                     @NonNull final Intent intent,\n                                     @AnimRes final int enterAnim,\n                                     @AnimRes final int exitAnim) {\n        startActivity(intent, activity, getOptionsBundle(activity, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {\n            activity.overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity    The activity.\n     * @param clz         The activity class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     */\n    public static void startActivityForResult(@NonNull final Activity activity,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode) {\n        startActivityForResult(activity, null, activity.getPackageName(), clz.getName(),\n                requestCode, null);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity    The activity.\n     * @param clz         The activity class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param options     Additional options for how the Activity should be started.\n     */\n    public static void startActivityForResult(@NonNull final Activity activity,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode,\n                                              @Nullable final Bundle options) {\n        startActivityForResult(activity, null, activity.getPackageName(), clz.getName(),\n                requestCode, options);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity       The activity.\n     * @param clz            The activity class.\n     * @param requestCode    if &gt;= 0, this code will be returned in\n     *                       onActivityResult() when the activity exits.\n     * @param sharedElements The names of the shared elements to transfer to the called\n     *                       Activity and their associated Views.\n     */\n    public static void startActivityForResult(@NonNull final Activity activity,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode,\n                                              final View... sharedElements) {\n        startActivityForResult(activity, null, activity.getPackageName(), clz.getName(),\n                requestCode, getOptionsBundle(activity, sharedElements));\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param activity    The activity.\n     * @param clz         The activity class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param enterAnim   A resource ID of the animation resource to use for the\n     *                    incoming activity.\n     * @param exitAnim    A resource ID of the animation resource to use for the\n     *                    outgoing activity.\n     */\n    public static void startActivityForResult(@NonNull final Activity activity,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode,\n                                              @AnimRes final int enterAnim,\n                                              @AnimRes final int exitAnim) {\n        startActivityForResult(activity, null, activity.getPackageName(), clz.getName(),\n                requestCode, getOptionsBundle(activity, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {\n            activity.overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras      The Bundle of extras to add to this intent.\n     * @param activity    The activity.\n     * @param clz         The activity class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Activity activity,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode) {\n        startActivityForResult(activity, extras, activity.getPackageName(), clz.getName(),\n                requestCode, null);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras      The Bundle of extras to add to this intent.\n     * @param activity    The activity.\n     * @param clz         The activity class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param options     Additional options for how the Activity should be started.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Activity activity,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode,\n                                              @Nullable final Bundle options) {\n        startActivityForResult(activity, extras, activity.getPackageName(), clz.getName(),\n                requestCode, options);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras         The Bundle of extras to add to this intent.\n     * @param activity       The activity.\n     * @param clz            The activity class.\n     * @param requestCode    if &gt;= 0, this code will be returned in\n     *                       onActivityResult() when the activity exits.\n     * @param sharedElements The names of the shared elements to transfer to the called\n     *                       Activity and their associated Views.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Activity activity,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode,\n                                              final View... sharedElements) {\n        startActivityForResult(activity, extras, activity.getPackageName(), clz.getName(),\n                requestCode, getOptionsBundle(activity, sharedElements));\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras      The Bundle of extras to add to this intent.\n     * @param activity    The activity.\n     * @param clz         The activity class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param enterAnim   A resource ID of the animation resource to use for the\n     *                    incoming activity.\n     * @param exitAnim    A resource ID of the animation resource to use for the\n     *                    outgoing activity.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Activity activity,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode,\n                                              @AnimRes final int enterAnim,\n                                              @AnimRes final int exitAnim) {\n        startActivityForResult(activity, extras, activity.getPackageName(), clz.getName(),\n                requestCode, getOptionsBundle(activity, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {\n            activity.overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param activity    The activity.\n     * @param extras      The Bundle of extras to add to this intent.\n     * @param pkg         The name of the package.\n     * @param cls         The name of the class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Activity activity,\n                                              @NonNull final String pkg,\n                                              @NonNull final String cls,\n                                              final int requestCode) {\n        startActivityForResult(activity, extras, pkg, cls, requestCode, null);\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param extras      The Bundle of extras to add to this intent.\n     * @param activity    The activity.\n     * @param pkg         The name of the package.\n     * @param cls         The name of the class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param options     Additional options for how the Activity should be started.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Activity activity,\n                                              @NonNull final String pkg,\n                                              @NonNull final String cls,\n                                              final int requestCode,\n                                              @Nullable final Bundle options) {\n        startActivityForResult(activity, extras, pkg, cls, requestCode, options);\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param extras         The Bundle of extras to add to this intent.\n     * @param activity       The activity.\n     * @param pkg            The name of the package.\n     * @param cls            The name of the class.\n     * @param requestCode    if &gt;= 0, this code will be returned in\n     *                       onActivityResult() when the activity exits.\n     * @param sharedElements The names of the shared elements to transfer to the called\n     *                       Activity and their associated Views.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Activity activity,\n                                              @NonNull final String pkg,\n                                              @NonNull final String cls,\n                                              final int requestCode,\n                                              final View... sharedElements) {\n        startActivityForResult(activity, extras, pkg, cls,\n                requestCode, getOptionsBundle(activity, sharedElements));\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param extras      The Bundle of extras to add to this intent.\n     * @param pkg         The name of the package.\n     * @param cls         The name of the class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param enterAnim   A resource ID of the animation resource to use for the\n     *                    incoming activity.\n     * @param exitAnim    A resource ID of the animation resource to use for the\n     *                    outgoing activity.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Activity activity,\n                                              @NonNull final String pkg,\n                                              @NonNull final String cls,\n                                              final int requestCode,\n                                              @AnimRes final int enterAnim,\n                                              @AnimRes final int exitAnim) {\n        startActivityForResult(activity, extras, pkg, cls,\n                requestCode, getOptionsBundle(activity, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {\n            activity.overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param activity    The activity.\n     * @param intent      The description of the activity to start.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     */\n    public static void startActivityForResult(@NonNull final Activity activity,\n                                              @NonNull final Intent intent,\n                                              final int requestCode) {\n        startActivityForResult(intent, activity, requestCode, null);\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param activity    The activity.\n     * @param intent      The description of the activity to start.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param options     Additional options for how the Activity should be started.\n     */\n    public static void startActivityForResult(@NonNull final Activity activity,\n                                              @NonNull final Intent intent,\n                                              final int requestCode,\n                                              @Nullable final Bundle options) {\n        startActivityForResult(intent, activity, requestCode, options);\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param activity       The activity.\n     * @param intent         The description of the activity to start.\n     * @param requestCode    if &gt;= 0, this code will be returned in\n     *                       onActivityResult() when the activity exits.\n     * @param sharedElements The names of the shared elements to transfer to the called\n     *                       Activity and their associated Views.\n     */\n    public static void startActivityForResult(@NonNull final Activity activity,\n                                              @NonNull final Intent intent,\n                                              final int requestCode,\n                                              final View... sharedElements) {\n        startActivityForResult(intent, activity,\n                requestCode, getOptionsBundle(activity, sharedElements));\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param activity    The activity.\n     * @param intent      The description of the activity to start.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param enterAnim   A resource ID of the animation resource to use for the\n     *                    incoming activity.\n     * @param exitAnim    A resource ID of the animation resource to use for the\n     *                    outgoing activity.\n     */\n    public static void startActivityForResult(@NonNull final Activity activity,\n                                              @NonNull final Intent intent,\n                                              final int requestCode,\n                                              @AnimRes final int enterAnim,\n                                              @AnimRes final int exitAnim) {\n        startActivityForResult(intent, activity,\n                requestCode, getOptionsBundle(activity, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {\n            activity.overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param fragment    The fragment.\n     * @param clz         The activity class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     */\n    public static void startActivityForResult(@NonNull final Fragment fragment,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode) {\n        startActivityForResult(fragment, null, Utils.getApp().getPackageName(), clz.getName(),\n                requestCode, null);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param fragment    The fragment.\n     * @param clz         The activity class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param options     Additional options for how the Activity should be started.\n     */\n    public static void startActivityForResult(@NonNull final Fragment fragment,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode,\n                                              @Nullable final Bundle options) {\n        startActivityForResult(fragment, null, Utils.getApp().getPackageName(), clz.getName(),\n                requestCode, options);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param fragment       The fragment.\n     * @param clz            The activity class.\n     * @param requestCode    if &gt;= 0, this code will be returned in\n     *                       onActivityResult() when the activity exits.\n     * @param sharedElements The names of the shared elements to transfer to the called\n     *                       Activity and their associated Views.\n     */\n    public static void startActivityForResult(@NonNull final Fragment fragment,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode,\n                                              final View... sharedElements) {\n        startActivityForResult(fragment, null, Utils.getApp().getPackageName(), clz.getName(),\n                requestCode, getOptionsBundle(fragment, sharedElements));\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param fragment    The fragment.\n     * @param clz         The activity class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param enterAnim   A resource ID of the animation resource to use for the\n     *                    incoming activity.\n     * @param exitAnim    A resource ID of the animation resource to use for the\n     *                    outgoing activity.\n     */\n    public static void startActivityForResult(@NonNull final Fragment fragment,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode,\n                                              @AnimRes final int enterAnim,\n                                              @AnimRes final int exitAnim) {\n        startActivityForResult(fragment, null, Utils.getApp().getPackageName(), clz.getName(),\n                requestCode, getOptionsBundle(fragment, enterAnim, exitAnim));\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras      The Bundle of extras to add to this intent.\n     * @param fragment    The fragment.\n     * @param clz         The activity class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Fragment fragment,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode) {\n        startActivityForResult(fragment, extras, Utils.getApp().getPackageName(), clz.getName(),\n                requestCode, null);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras      The Bundle of extras to add to this intent.\n     * @param fragment    The fragment.\n     * @param clz         The activity class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param options     Additional options for how the Activity should be started.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Fragment fragment,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode,\n                                              @Nullable final Bundle options) {\n        startActivityForResult(fragment, extras, Utils.getApp().getPackageName(), clz.getName(),\n                requestCode, options);\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras         The Bundle of extras to add to this intent.\n     * @param fragment       The fragment.\n     * @param clz            The activity class.\n     * @param requestCode    if &gt;= 0, this code will be returned in\n     *                       onActivityResult() when the activity exits.\n     * @param sharedElements The names of the shared elements to transfer to the called\n     *                       Activity and their associated Views.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Fragment fragment,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode,\n                                              final View... sharedElements) {\n        startActivityForResult(fragment, extras, Utils.getApp().getPackageName(), clz.getName(),\n                requestCode, getOptionsBundle(fragment, sharedElements));\n    }\n\n    /**\n     * Start the activity.\n     *\n     * @param extras      The Bundle of extras to add to this intent.\n     * @param fragment    The fragment.\n     * @param clz         The activity class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param enterAnim   A resource ID of the animation resource to use for the\n     *                    incoming activity.\n     * @param exitAnim    A resource ID of the animation resource to use for the\n     *                    outgoing activity.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Fragment fragment,\n                                              @NonNull final Class<? extends Activity> clz,\n                                              final int requestCode,\n                                              @AnimRes final int enterAnim,\n                                              @AnimRes final int exitAnim) {\n        startActivityForResult(fragment, extras, Utils.getApp().getPackageName(), clz.getName(),\n                requestCode, getOptionsBundle(fragment, enterAnim, exitAnim));\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param fragment    The fragment.\n     * @param extras      The Bundle of extras to add to this intent.\n     * @param pkg         The name of the package.\n     * @param cls         The name of the class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Fragment fragment,\n                                              @NonNull final String pkg,\n                                              @NonNull final String cls,\n                                              final int requestCode) {\n        startActivityForResult(fragment, extras, pkg, cls, requestCode, null);\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param extras      The Bundle of extras to add to this intent.\n     * @param fragment    The fragment.\n     * @param pkg         The name of the package.\n     * @param cls         The name of the class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param options     Additional options for how the Activity should be started.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Fragment fragment,\n                                              @NonNull final String pkg,\n                                              @NonNull final String cls,\n                                              final int requestCode,\n                                              @Nullable final Bundle options) {\n        startActivityForResult(fragment, extras, pkg, cls, requestCode, options);\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param extras         The Bundle of extras to add to this intent.\n     * @param fragment       The fragment.\n     * @param pkg            The name of the package.\n     * @param cls            The name of the class.\n     * @param requestCode    if &gt;= 0, this code will be returned in\n     *                       onActivityResult() when the activity exits.\n     * @param sharedElements The names of the shared elements to transfer to the called\n     *                       Activity and their associated Views.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Fragment fragment,\n                                              @NonNull final String pkg,\n                                              @NonNull final String cls,\n                                              final int requestCode,\n                                              final View... sharedElements) {\n        startActivityForResult(fragment, extras, pkg, cls,\n                requestCode, getOptionsBundle(fragment, sharedElements));\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param extras      The Bundle of extras to add to this intent.\n     * @param pkg         The name of the package.\n     * @param cls         The name of the class.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param enterAnim   A resource ID of the animation resource to use for the\n     *                    incoming activity.\n     * @param exitAnim    A resource ID of the animation resource to use for the\n     *                    outgoing activity.\n     */\n    public static void startActivityForResult(@NonNull final Bundle extras,\n                                              @NonNull final Fragment fragment,\n                                              @NonNull final String pkg,\n                                              @NonNull final String cls,\n                                              final int requestCode,\n                                              @AnimRes final int enterAnim,\n                                              @AnimRes final int exitAnim) {\n        startActivityForResult(fragment, extras, pkg, cls,\n                requestCode, getOptionsBundle(fragment, enterAnim, exitAnim));\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param fragment    The fragment.\n     * @param intent      The description of the activity to start.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     */\n    public static void startActivityForResult(@NonNull final Fragment fragment,\n                                              @NonNull final Intent intent,\n                                              final int requestCode) {\n        startActivityForResult(intent, fragment, requestCode, null);\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param fragment    The fragment.\n     * @param intent      The description of the activity to start.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param options     Additional options for how the Activity should be started.\n     */\n    public static void startActivityForResult(@NonNull final Fragment fragment,\n                                              @NonNull final Intent intent,\n                                              final int requestCode,\n                                              @Nullable final Bundle options) {\n        startActivityForResult(intent, fragment, requestCode, options);\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param fragment       The fragment.\n     * @param intent         The description of the activity to start.\n     * @param requestCode    if &gt;= 0, this code will be returned in\n     *                       onActivityResult() when the activity exits.\n     * @param sharedElements The names of the shared elements to transfer to the called\n     *                       Activity and their associated Views.\n     */\n    public static void startActivityForResult(@NonNull final Fragment fragment,\n                                              @NonNull final Intent intent,\n                                              final int requestCode,\n                                              final View... sharedElements) {\n        startActivityForResult(intent, fragment,\n                requestCode, getOptionsBundle(fragment, sharedElements));\n    }\n\n    /**\n     * Start the activity for result.\n     *\n     * @param fragment    The fragment.\n     * @param intent      The description of the activity to start.\n     * @param requestCode if &gt;= 0, this code will be returned in\n     *                    onActivityResult() when the activity exits.\n     * @param enterAnim   A resource ID of the animation resource to use for the\n     *                    incoming activity.\n     * @param exitAnim    A resource ID of the animation resource to use for the\n     *                    outgoing activity.\n     */\n    public static void startActivityForResult(@NonNull final Fragment fragment,\n                                              @NonNull final Intent intent,\n                                              final int requestCode,\n                                              @AnimRes final int enterAnim,\n                                              @AnimRes final int exitAnim) {\n        startActivityForResult(intent, fragment,\n                requestCode, getOptionsBundle(fragment, enterAnim, exitAnim));\n    }\n\n    /**\n     * Start activities.\n     *\n     * @param intents The descriptions of the activities to start.\n     */\n    public static void startActivities(@NonNull final Intent[] intents) {\n        startActivities(intents, getTopActivityOrApp(), null);\n    }\n\n    /**\n     * Start activities.\n     *\n     * @param intents The descriptions of the activities to start.\n     * @param options Additional options for how the Activity should be started.\n     */\n    public static void startActivities(@NonNull final Intent[] intents,\n                                       @Nullable final Bundle options) {\n        startActivities(intents, getTopActivityOrApp(), options);\n    }\n\n    /**\n     * Start activities.\n     *\n     * @param intents   The descriptions of the activities to start.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void startActivities(@NonNull final Intent[] intents,\n                                       @AnimRes final int enterAnim,\n                                       @AnimRes final int exitAnim) {\n        Context context = getTopActivityOrApp();\n        startActivities(intents, context, getOptionsBundle(context, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && context instanceof Activity) {\n            ((Activity) context).overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start activities.\n     *\n     * @param activity The activity.\n     * @param intents  The descriptions of the activities to start.\n     */\n    public static void startActivities(@NonNull final Activity activity,\n                                       @NonNull final Intent[] intents) {\n        startActivities(intents, activity, null);\n    }\n\n    /**\n     * Start activities.\n     *\n     * @param activity The activity.\n     * @param intents  The descriptions of the activities to start.\n     * @param options  Additional options for how the Activity should be started.\n     */\n    public static void startActivities(@NonNull final Activity activity,\n                                       @NonNull final Intent[] intents,\n                                       @Nullable final Bundle options) {\n        startActivities(intents, activity, options);\n    }\n\n    /**\n     * Start activities.\n     *\n     * @param activity  The activity.\n     * @param intents   The descriptions of the activities to start.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void startActivities(@NonNull final Activity activity,\n                                       @NonNull final Intent[] intents,\n                                       @AnimRes final int enterAnim,\n                                       @AnimRes final int exitAnim) {\n        startActivities(intents, activity, getOptionsBundle(activity, enterAnim, exitAnim));\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {\n            activity.overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Start home activity.\n     */\n    public static void startHomeActivity() {\n        Intent homeIntent = new Intent(Intent.ACTION_MAIN);\n        homeIntent.addCategory(Intent.CATEGORY_HOME);\n        homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        startActivity(homeIntent);\n    }\n\n    /**\n     * Start the launcher activity.\n     */\n    public static void startLauncherActivity() {\n        startLauncherActivity(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Start the launcher activity.\n     *\n     * @param pkg The name of the package.\n     */\n    public static void startLauncherActivity(@NonNull final String pkg) {\n        String launcherActivity = getLauncherActivity(pkg);\n        if (TextUtils.isEmpty(launcherActivity)) return;\n        startActivity(pkg, launcherActivity);\n    }\n\n    /**\n     * Return the list of activity.\n     *\n     * @return the list of activity\n     */\n    public static List<Activity> getActivityList() {\n        return UtilsBridge.getActivityList();\n    }\n\n    /**\n     * Return the name of launcher activity.\n     *\n     * @return the name of launcher activity\n     */\n    public static String getLauncherActivity() {\n        return getLauncherActivity(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the name of launcher activity.\n     *\n     * @param pkg The name of the package.\n     * @return the name of launcher activity\n     */\n    public static String getLauncherActivity(@NonNull final String pkg) {\n        if (UtilsBridge.isSpace(pkg)) return \"\";\n        Intent intent = new Intent(Intent.ACTION_MAIN, null);\n        intent.addCategory(Intent.CATEGORY_LAUNCHER);\n        intent.setPackage(pkg);\n        PackageManager pm = Utils.getApp().getPackageManager();\n        List<ResolveInfo> info = pm.queryIntentActivities(intent, 0);\n        if (info == null || info.size() == 0) {\n            return \"\";\n        }\n        return info.get(0).activityInfo.name;\n    }\n\n    /**\n     * Return the list of main activities.\n     *\n     * @return the list of main activities\n     */\n    public static List<String> getMainActivities() {\n        return getMainActivities(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the list of main activities.\n     *\n     * @param pkg The name of the package.\n     * @return the list of main activities\n     */\n    public static List<String> getMainActivities(@NonNull final String pkg) {\n        List<String> ret = new ArrayList<>();\n        Intent intent = new Intent(Intent.ACTION_MAIN, null);\n        intent.setPackage(pkg);\n        PackageManager pm = Utils.getApp().getPackageManager();\n        List<ResolveInfo> info = pm.queryIntentActivities(intent, 0);\n        int size = info.size();\n        if (size == 0) return ret;\n        for (int i = 0; i < size; i++) {\n            ResolveInfo ri = info.get(i);\n            if (ri.activityInfo.processName.equals(pkg)) {\n                ret.add(ri.activityInfo.name);\n            }\n        }\n        return ret;\n    }\n\n    /**\n     * Return the top activity in activity's stack.\n     *\n     * @return the top activity in activity's stack\n     */\n    public static Activity getTopActivity() {\n        return UtilsBridge.getTopActivity();\n    }\n\n    /**\n     * Return whether the activity is alive.\n     *\n     * @param context The context.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isActivityAlive(final Context context) {\n        return isActivityAlive(getActivityByContext(context));\n    }\n\n    /**\n     * Return whether the activity is alive.\n     *\n     * @param activity The activity.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isActivityAlive(final Activity activity) {\n        return activity != null && !activity.isFinishing()\n                && (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 || !activity.isDestroyed());\n    }\n\n    /**\n     * Return whether the activity exists in activity's stack.\n     *\n     * @param activity The activity.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isActivityExistsInStack(@NonNull final Activity activity) {\n        List<Activity> activities = UtilsBridge.getActivityList();\n        for (Activity aActivity : activities) {\n            if (aActivity.equals(activity)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Return whether the activity exists in activity's stack.\n     *\n     * @param clz The activity class.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isActivityExistsInStack(@NonNull final Class<? extends Activity> clz) {\n        List<Activity> activities = UtilsBridge.getActivityList();\n        for (Activity aActivity : activities) {\n            if (aActivity.getClass().equals(clz)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Finish the activity.\n     *\n     * @param activity The activity.\n     */\n    public static void finishActivity(@NonNull final Activity activity) {\n        finishActivity(activity, false);\n    }\n\n    /**\n     * Finish the activity.\n     *\n     * @param activity   The activity.\n     * @param isLoadAnim True to use animation for the outgoing activity, false otherwise.\n     */\n    public static void finishActivity(@NonNull final Activity activity, final boolean isLoadAnim) {\n        activity.finish();\n        if (!isLoadAnim) {\n            activity.overridePendingTransition(0, 0);\n        }\n    }\n\n    /**\n     * Finish the activity.\n     *\n     * @param activity  The activity.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void finishActivity(@NonNull final Activity activity,\n                                      @AnimRes final int enterAnim,\n                                      @AnimRes final int exitAnim) {\n        activity.finish();\n        activity.overridePendingTransition(enterAnim, exitAnim);\n    }\n\n    /**\n     * Finish the activity.\n     *\n     * @param clz The activity class.\n     */\n    public static void finishActivity(@NonNull final Class<? extends Activity> clz) {\n        finishActivity(clz, false);\n    }\n\n    /**\n     * Finish the activity.\n     *\n     * @param clz        The activity class.\n     * @param isLoadAnim True to use animation for the outgoing activity, false otherwise.\n     */\n    public static void finishActivity(@NonNull final Class<? extends Activity> clz,\n                                      final boolean isLoadAnim) {\n        List<Activity> activities = UtilsBridge.getActivityList();\n        for (Activity activity : activities) {\n            if (activity.getClass().equals(clz)) {\n                activity.finish();\n                if (!isLoadAnim) {\n                    activity.overridePendingTransition(0, 0);\n                }\n            }\n        }\n    }\n\n    /**\n     * Finish the activity.\n     *\n     * @param clz       The activity class.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void finishActivity(@NonNull final Class<? extends Activity> clz,\n                                      @AnimRes final int enterAnim,\n                                      @AnimRes final int exitAnim) {\n        List<Activity> activities = UtilsBridge.getActivityList();\n        for (Activity activity : activities) {\n            if (activity.getClass().equals(clz)) {\n                activity.finish();\n                activity.overridePendingTransition(enterAnim, exitAnim);\n            }\n        }\n    }\n\n    /**\n     * Finish to the activity.\n     *\n     * @param activity      The activity.\n     * @param isIncludeSelf True to include the activity, false otherwise.\n     */\n    public static boolean finishToActivity(@NonNull final Activity activity,\n                                           final boolean isIncludeSelf) {\n        return finishToActivity(activity, isIncludeSelf, false);\n    }\n\n    /**\n     * Finish to the activity.\n     *\n     * @param activity      The activity.\n     * @param isIncludeSelf True to include the activity, false otherwise.\n     * @param isLoadAnim    True to use animation for the outgoing activity, false otherwise.\n     */\n    public static boolean finishToActivity(@NonNull final Activity activity,\n                                           final boolean isIncludeSelf,\n                                           final boolean isLoadAnim) {\n        List<Activity> activities = UtilsBridge.getActivityList();\n        for (Activity act : activities) {\n            if (act.equals(activity)) {\n                if (isIncludeSelf) {\n                    finishActivity(act, isLoadAnim);\n                }\n                return true;\n            }\n            finishActivity(act, isLoadAnim);\n        }\n        return false;\n    }\n\n    /**\n     * Finish to the activity.\n     *\n     * @param activity      The activity.\n     * @param isIncludeSelf True to include the activity, false otherwise.\n     * @param enterAnim     A resource ID of the animation resource to use for the\n     *                      incoming activity.\n     * @param exitAnim      A resource ID of the animation resource to use for the\n     *                      outgoing activity.\n     */\n    public static boolean finishToActivity(@NonNull final Activity activity,\n                                           final boolean isIncludeSelf,\n                                           @AnimRes final int enterAnim,\n                                           @AnimRes final int exitAnim) {\n        List<Activity> activities = UtilsBridge.getActivityList();\n        for (Activity act : activities) {\n            if (act.equals(activity)) {\n                if (isIncludeSelf) {\n                    finishActivity(act, enterAnim, exitAnim);\n                }\n                return true;\n            }\n            finishActivity(act, enterAnim, exitAnim);\n        }\n        return false;\n    }\n\n    /**\n     * Finish to the activity.\n     *\n     * @param clz           The activity class.\n     * @param isIncludeSelf True to include the activity, false otherwise.\n     */\n    public static boolean finishToActivity(@NonNull final Class<? extends Activity> clz,\n                                           final boolean isIncludeSelf) {\n        return finishToActivity(clz, isIncludeSelf, false);\n    }\n\n    /**\n     * Finish to the activity.\n     *\n     * @param clz           The activity class.\n     * @param isIncludeSelf True to include the activity, false otherwise.\n     * @param isLoadAnim    True to use animation for the outgoing activity, false otherwise.\n     */\n    public static boolean finishToActivity(@NonNull final Class<? extends Activity> clz,\n                                           final boolean isIncludeSelf,\n                                           final boolean isLoadAnim) {\n        List<Activity> activities = UtilsBridge.getActivityList();\n        for (Activity act : activities) {\n            if (act.getClass().equals(clz)) {\n                if (isIncludeSelf) {\n                    finishActivity(act, isLoadAnim);\n                }\n                return true;\n            }\n            finishActivity(act, isLoadAnim);\n        }\n        return false;\n    }\n\n    /**\n     * Finish to the activity.\n     *\n     * @param clz           The activity class.\n     * @param isIncludeSelf True to include the activity, false otherwise.\n     * @param enterAnim     A resource ID of the animation resource to use for the\n     *                      incoming activity.\n     * @param exitAnim      A resource ID of the animation resource to use for the\n     *                      outgoing activity.\n     */\n    public static boolean finishToActivity(@NonNull final Class<? extends Activity> clz,\n                                           final boolean isIncludeSelf,\n                                           @AnimRes final int enterAnim,\n                                           @AnimRes final int exitAnim) {\n        List<Activity> activities = UtilsBridge.getActivityList();\n        for (Activity act : activities) {\n            if (act.getClass().equals(clz)) {\n                if (isIncludeSelf) {\n                    finishActivity(act, enterAnim, exitAnim);\n                }\n                return true;\n            }\n            finishActivity(act, enterAnim, exitAnim);\n        }\n        return false;\n    }\n\n    /**\n     * Finish the activities whose type not equals the activity class.\n     *\n     * @param clz The activity class.\n     */\n    public static void finishOtherActivities(@NonNull final Class<? extends Activity> clz) {\n        finishOtherActivities(clz, false);\n    }\n\n\n    /**\n     * Finish the activities whose type not equals the activity class.\n     *\n     * @param clz        The activity class.\n     * @param isLoadAnim True to use animation for the outgoing activity, false otherwise.\n     */\n    public static void finishOtherActivities(@NonNull final Class<? extends Activity> clz,\n                                             final boolean isLoadAnim) {\n        List<Activity> activities = UtilsBridge.getActivityList();\n        for (Activity act : activities) {\n            if (!act.getClass().equals(clz)) {\n                finishActivity(act, isLoadAnim);\n            }\n        }\n    }\n\n    /**\n     * Finish the activities whose type not equals the activity class.\n     *\n     * @param clz       The activity class.\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void finishOtherActivities(@NonNull final Class<? extends Activity> clz,\n                                             @AnimRes final int enterAnim,\n                                             @AnimRes final int exitAnim) {\n        List<Activity> activities = UtilsBridge.getActivityList();\n        for (Activity act : activities) {\n            if (!act.getClass().equals(clz)) {\n                finishActivity(act, enterAnim, exitAnim);\n            }\n        }\n    }\n\n    /**\n     * Finish all of activities.\n     */\n    public static void finishAllActivities() {\n        finishAllActivities(false);\n    }\n\n    /**\n     * Finish all of activities.\n     *\n     * @param isLoadAnim True to use animation for the outgoing activity, false otherwise.\n     */\n    public static void finishAllActivities(final boolean isLoadAnim) {\n        List<Activity> activityList = UtilsBridge.getActivityList();\n        for (Activity act : activityList) {\n            // sActivityList remove the index activity at onActivityDestroyed\n            act.finish();\n            if (!isLoadAnim) {\n                act.overridePendingTransition(0, 0);\n            }\n        }\n    }\n\n    /**\n     * Finish all of activities.\n     *\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void finishAllActivities(@AnimRes final int enterAnim,\n                                           @AnimRes final int exitAnim) {\n        List<Activity> activityList = UtilsBridge.getActivityList();\n        for (Activity act : activityList) {\n            // sActivityList remove the index activity at onActivityDestroyed\n            act.finish();\n            act.overridePendingTransition(enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Finish all of activities except the newest activity.\n     */\n    public static void finishAllActivitiesExceptNewest() {\n        finishAllActivitiesExceptNewest(false);\n    }\n\n    /**\n     * Finish all of activities except the newest activity.\n     *\n     * @param isLoadAnim True to use animation for the outgoing activity, false otherwise.\n     */\n    public static void finishAllActivitiesExceptNewest(final boolean isLoadAnim) {\n        List<Activity> activities = UtilsBridge.getActivityList();\n        for (int i = 1; i < activities.size(); i++) {\n            finishActivity(activities.get(i), isLoadAnim);\n        }\n    }\n\n    /**\n     * Finish all of activities except the newest activity.\n     *\n     * @param enterAnim A resource ID of the animation resource to use for the\n     *                  incoming activity.\n     * @param exitAnim  A resource ID of the animation resource to use for the\n     *                  outgoing activity.\n     */\n    public static void finishAllActivitiesExceptNewest(@AnimRes final int enterAnim,\n                                                       @AnimRes final int exitAnim) {\n        List<Activity> activities = UtilsBridge.getActivityList();\n        for (int i = 1; i < activities.size(); i++) {\n            finishActivity(activities.get(i), enterAnim, exitAnim);\n        }\n    }\n\n    /**\n     * Return the icon of activity.\n     *\n     * @param activity The activity.\n     * @return the icon of activity\n     */\n    @Nullable\n    public static Drawable getActivityIcon(@NonNull final Activity activity) {\n        return getActivityIcon(activity.getComponentName());\n    }\n\n    /**\n     * Return the icon of activity.\n     *\n     * @param clz The activity class.\n     * @return the icon of activity\n     */\n    @Nullable\n    public static Drawable getActivityIcon(@NonNull final Class<? extends Activity> clz) {\n        return getActivityIcon(new ComponentName(Utils.getApp(), clz));\n    }\n\n    /**\n     * Return the icon of activity.\n     *\n     * @param activityName The name of activity.\n     * @return the icon of activity\n     */\n    @Nullable\n    public static Drawable getActivityIcon(@NonNull final ComponentName activityName) {\n        PackageManager pm = Utils.getApp().getPackageManager();\n        try {\n            return pm.getActivityIcon(activityName);\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * Return the logo of activity.\n     *\n     * @param activity The activity.\n     * @return the logo of activity\n     */\n    @Nullable\n    public static Drawable getActivityLogo(@NonNull final Activity activity) {\n        return getActivityLogo(activity.getComponentName());\n    }\n\n    /**\n     * Return the logo of activity.\n     *\n     * @param clz The activity class.\n     * @return the logo of activity\n     */\n    @Nullable\n    public static Drawable getActivityLogo(@NonNull final Class<? extends Activity> clz) {\n        return getActivityLogo(new ComponentName(Utils.getApp(), clz));\n    }\n\n    /**\n     * Return the logo of activity.\n     *\n     * @param activityName The name of activity.\n     * @return the logo of activity\n     */\n    @Nullable\n    public static Drawable getActivityLogo(@NonNull final ComponentName activityName) {\n        PackageManager pm = Utils.getApp().getPackageManager();\n        try {\n            return pm.getActivityLogo(activityName);\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    private static void startActivity(final Context context,\n                                      final Bundle extras,\n                                      final String pkg,\n                                      final String cls,\n                                      @Nullable final Bundle options) {\n        Intent intent = new Intent();\n        if (extras != null) intent.putExtras(extras);\n        intent.setComponent(new ComponentName(pkg, cls));\n        startActivity(intent, context, options);\n    }\n\n    private static boolean startActivity(final Intent intent,\n                                         final Context context,\n                                         final Bundle options) {\n        if (!isIntentAvailable(intent)) {\n            Log.e(\"ActivityUtils\", \"intent is unavailable\");\n            return false;\n        }\n        if (!(context instanceof Activity)) {\n            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        }\n        if (options != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            context.startActivity(intent, options);\n        } else {\n            context.startActivity(intent);\n        }\n        return true;\n    }\n\n    private static boolean isIntentAvailable(final Intent intent) {\n        return Utils.getApp()\n                .getPackageManager()\n                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)\n                .size() > 0;\n    }\n\n    private static boolean startActivityForResult(final Activity activity,\n                                                  final Bundle extras,\n                                                  final String pkg,\n                                                  final String cls,\n                                                  final int requestCode,\n                                                  @Nullable final Bundle options) {\n        Intent intent = new Intent();\n        if (extras != null) intent.putExtras(extras);\n        intent.setComponent(new ComponentName(pkg, cls));\n        return startActivityForResult(intent, activity, requestCode, options);\n    }\n\n    private static boolean startActivityForResult(final Intent intent,\n                                                  final Activity activity,\n                                                  final int requestCode,\n                                                  @Nullable final Bundle options) {\n        if (!isIntentAvailable(intent)) {\n            Log.e(\"ActivityUtils\", \"intent is unavailable\");\n            return false;\n        }\n        if (options != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            activity.startActivityForResult(intent, requestCode, options);\n        } else {\n            activity.startActivityForResult(intent, requestCode);\n        }\n        return true;\n    }\n\n    private static void startActivities(final Intent[] intents,\n                                        final Context context,\n                                        @Nullable final Bundle options) {\n        if (!(context instanceof Activity)) {\n            for (Intent intent : intents) {\n                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            }\n        }\n        if (options != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            context.startActivities(intents, options);\n        } else {\n            context.startActivities(intents);\n        }\n    }\n\n    private static boolean startActivityForResult(final Fragment fragment,\n                                                  final Bundle extras,\n                                                  final String pkg,\n                                                  final String cls,\n                                                  final int requestCode,\n                                                  @Nullable final Bundle options) {\n        Intent intent = new Intent();\n        if (extras != null) intent.putExtras(extras);\n        intent.setComponent(new ComponentName(pkg, cls));\n        return startActivityForResult(intent, fragment, requestCode, options);\n    }\n\n    private static boolean startActivityForResult(final Intent intent,\n                                                  final Fragment fragment,\n                                                  final int requestCode,\n                                                  @Nullable final Bundle options) {\n        if (!isIntentAvailable(intent)) {\n            Log.e(\"ActivityUtils\", \"intent is unavailable\");\n            return false;\n        }\n        if (fragment.getActivity() == null) {\n            Log.e(\"ActivityUtils\", \"Fragment \" + fragment + \" not attached to Activity\");\n            return false;\n        }\n        if (options != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            fragment.startActivityForResult(intent, requestCode, options);\n        } else {\n            fragment.startActivityForResult(intent, requestCode);\n        }\n        return true;\n    }\n\n    private static Bundle getOptionsBundle(final Fragment fragment,\n                                           final int enterAnim,\n                                           final int exitAnim) {\n        Activity activity = fragment.getActivity();\n        if (activity == null) return null;\n        return ActivityOptionsCompat.makeCustomAnimation(activity, enterAnim, exitAnim).toBundle();\n    }\n\n    private static Bundle getOptionsBundle(final Context context,\n                                           final int enterAnim,\n                                           final int exitAnim) {\n        return ActivityOptionsCompat.makeCustomAnimation(context, enterAnim, exitAnim).toBundle();\n    }\n\n    private static Bundle getOptionsBundle(final Fragment fragment,\n                                           final View[] sharedElements) {\n        Activity activity = fragment.getActivity();\n        if (activity == null) return null;\n        return getOptionsBundle(activity, sharedElements);\n    }\n\n    private static Bundle getOptionsBundle(final Activity activity,\n                                           final View[] sharedElements) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null;\n        if (sharedElements == null) return null;\n        int len = sharedElements.length;\n        if (len <= 0) return null;\n        @SuppressWarnings(\"unchecked\")\n        Pair<View, String>[] pairs = new Pair[len];\n        for (int i = 0; i < len; i++) {\n            pairs[i] = Pair.create(sharedElements[i], sharedElements[i].getTransitionName());\n        }\n        return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, pairs).toBundle();\n    }\n\n    private static Context getTopActivityOrApp() {\n        if (UtilsBridge.isAppForeground()) {\n            Activity topActivity = getTopActivity();\n            return topActivity == null ? Utils.getApp() : topActivity;\n        } else {\n            return Utils.getApp();\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/AdaptScreenUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.res.Resources;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.util.DisplayMetrics;\n\nimport java.lang.reflect.Field;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/11/15\n *     desc  : utils about adapt screen\n * </pre>\n */\npublic final class AdaptScreenUtils {\n\n    private static List<Field> sMetricsFields;\n\n    private AdaptScreenUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Adapt for the horizontal screen, and call it in {@link android.app.Activity#getResources()}.\n     */\n    @NonNull\n    public static Resources adaptWidth(@NonNull final Resources resources, final int designWidth) {\n        float newXdpi = (resources.getDisplayMetrics().widthPixels * 72f) / designWidth;\n        applyDisplayMetrics(resources, newXdpi);\n        return resources;\n    }\n\n    /**\n     * Adapt for the vertical screen, and call it in {@link android.app.Activity#getResources()}.\n     */\n    @NonNull\n    public static Resources adaptHeight(@NonNull final Resources resources, final int designHeight) {\n        return adaptHeight(resources, designHeight, false);\n    }\n\n    /**\n     * Adapt for the vertical screen, and call it in {@link android.app.Activity#getResources()}.\n     */\n    @NonNull\n    public static Resources adaptHeight(@NonNull final Resources resources, final int designHeight, final boolean includeNavBar) {\n        float screenHeight = (resources.getDisplayMetrics().heightPixels\n                + (includeNavBar ? getNavBarHeight(resources) : 0)) * 72f;\n        float newXdpi = screenHeight / designHeight;\n        applyDisplayMetrics(resources, newXdpi);\n        return resources;\n    }\n\n    private static int getNavBarHeight(@NonNull final Resources resources) {\n        int resourceId = resources.getIdentifier(\"navigation_bar_height\", \"dimen\", \"android\");\n        if (resourceId != 0) {\n            return resources.getDimensionPixelSize(resourceId);\n        } else {\n            return 0;\n        }\n    }\n\n    /**\n     * @param resources The resources.\n     * @return the resource\n     */\n    @NonNull\n    public static Resources closeAdapt(@NonNull final Resources resources) {\n        float newXdpi = Resources.getSystem().getDisplayMetrics().density * 72f;\n        applyDisplayMetrics(resources, newXdpi);\n        return resources;\n    }\n\n    /**\n     * Value of pt to value of px.\n     *\n     * @param ptValue The value of pt.\n     * @return value of px\n     */\n    public static int pt2Px(final float ptValue) {\n        DisplayMetrics metrics = Utils.getApp().getResources().getDisplayMetrics();\n        return (int) (ptValue * metrics.xdpi / 72f + 0.5);\n    }\n\n    /**\n     * Value of px to value of pt.\n     *\n     * @param pxValue The value of px.\n     * @return value of pt\n     */\n    public static int px2Pt(final float pxValue) {\n        DisplayMetrics metrics = Utils.getApp().getResources().getDisplayMetrics();\n        return (int) (pxValue * 72 / metrics.xdpi + 0.5);\n    }\n\n    private static void applyDisplayMetrics(@NonNull final Resources resources, final float newXdpi) {\n        resources.getDisplayMetrics().xdpi = newXdpi;\n        Utils.getApp().getResources().getDisplayMetrics().xdpi = newXdpi;\n        applyOtherDisplayMetrics(resources, newXdpi);\n    }\n\n    static Runnable getPreLoadRunnable() {\n        return new Runnable() {\n            @Override\n            public void run() {\n                preLoad();\n            }\n        };\n    }\n\n    private static void preLoad() {\n        applyDisplayMetrics(Resources.getSystem(), Resources.getSystem().getDisplayMetrics().xdpi);\n    }\n\n    private static void applyOtherDisplayMetrics(final Resources resources, final float newXdpi) {\n        if (sMetricsFields == null) {\n            sMetricsFields = new ArrayList<>();\n            Class resCls = resources.getClass();\n            Field[] declaredFields = resCls.getDeclaredFields();\n            while (declaredFields != null && declaredFields.length > 0) {\n                for (Field field : declaredFields) {\n                    if (field.getType().isAssignableFrom(DisplayMetrics.class)) {\n                        field.setAccessible(true);\n                        DisplayMetrics tmpDm = getMetricsFromField(resources, field);\n                        if (tmpDm != null) {\n                            sMetricsFields.add(field);\n                            tmpDm.xdpi = newXdpi;\n                        }\n                    }\n                }\n                resCls = resCls.getSuperclass();\n                if (resCls != null) {\n                    declaredFields = resCls.getDeclaredFields();\n                } else {\n                    break;\n                }\n            }\n        } else {\n            applyMetricsFields(resources, newXdpi);\n        }\n    }\n\n    private static void applyMetricsFields(final Resources resources, final float newXdpi) {\n        for (Field metricsField : sMetricsFields) {\n            try {\n                DisplayMetrics dm = (DisplayMetrics) metricsField.get(resources);\n                if (dm != null) dm.xdpi = newXdpi;\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    private static DisplayMetrics getMetricsFromField(final Resources resources, final Field field) {\n        try {\n            return (DisplayMetrics) field.get(resources);\n        } catch (Exception ignore) {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ApiUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.Nullable;\nimport android.util.Log;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/07/09\n *     desc  : utils about api\n * </pre>\n */\npublic final class ApiUtils {\n\n    private static final String TAG = \"ApiUtils\";\n\n    private Map<Class, BaseApi> mApiMap           = new ConcurrentHashMap<>();\n    private Map<Class, Class>   mInjectApiImplMap = new HashMap<>();\n\n    private ApiUtils() {\n        init();\n    }\n\n    /**\n     * It'll be injected the implClasses who have {@link Api} annotation\n     * by function of {@link ApiUtils#registerImpl} when execute transform task.\n     */\n    private void init() {/*inject*/}\n\n    private void registerImpl(Class implClass) {\n        mInjectApiImplMap.put(implClass.getSuperclass(), implClass);\n    }\n\n    /**\n     * Get api.\n     *\n     * @param apiClass The class of api.\n     * @param <T>      The type.\n     * @return the api\n     */\n    @Nullable\n    public static <T extends BaseApi> T getApi(@NonNull final Class<T> apiClass) {\n        return getInstance().getApiInner(apiClass);\n    }\n\n    public static void register(@Nullable Class<? extends BaseApi> implClass) {\n        if (implClass == null) return;\n        getInstance().registerImpl(implClass);\n    }\n\n    @NonNull\n    public static String toString_() {\n        return getInstance().toString();\n    }\n\n    @Override\n    public String toString() {\n        return \"ApiUtils: \" + mInjectApiImplMap;\n    }\n\n    private static ApiUtils getInstance() {\n        return LazyHolder.INSTANCE;\n    }\n\n    private <Result> Result getApiInner(Class apiClass) {\n        BaseApi api = mApiMap.get(apiClass);\n        if (api != null) {\n            return (Result) api;\n        }\n        synchronized (apiClass) {\n            api = mApiMap.get(apiClass);\n            if (api != null) {\n                return (Result) api;\n            }\n            Class implClass = mInjectApiImplMap.get(apiClass);\n            if (implClass != null) {\n                try {\n                    api = (BaseApi) implClass.newInstance();\n                    mApiMap.put(apiClass, api);\n                    return (Result) api;\n                } catch (Exception ignore) {\n                    Log.e(TAG, \"The <\" + implClass + \"> has no parameterless constructor.\");\n                    return null;\n                }\n            } else {\n                Log.e(TAG, \"The <\" + apiClass + \"> doesn't implement.\");\n                return null;\n            }\n        }\n    }\n\n    private static class LazyHolder {\n        private static final ApiUtils INSTANCE = new ApiUtils();\n    }\n\n    @Target({ElementType.TYPE})\n    @Retention(RetentionPolicy.CLASS)\n    public @interface Api {\n        boolean isMock() default false;\n    }\n\n    public static class BaseApi {\n    }\n}"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/AppStoreUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.net.Uri;\nimport android.util.Log;\n\nimport java.util.List;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/05/20\n *     desc  : utils about app store\n * </pre>\n */\npublic final class AppStoreUtils {\n\n    private static final String TAG = \"AppStoreUtils\";\n\n    private static final String GOOGLE_PLAY_APP_STORE_PACKAGE_NAME = \"com.android.vending\";\n\n    /**\n     * 获取跳转到应用商店的 Intent\n     *\n     * @return 跳转到应用商店的 Intent\n     */\n    public static Intent getAppStoreIntent() {\n        return getAppStoreIntent(Utils.getApp().getPackageName(), false);\n    }\n\n    /**\n     * 获取跳转到应用商店的 Intent\n     *\n     * @param isIncludeGooglePlayStore 是否包括 Google Play 商店\n     * @return 跳转到应用商店的 Intent\n     */\n    public static Intent getAppStoreIntent(boolean isIncludeGooglePlayStore) {\n        return getAppStoreIntent(Utils.getApp().getPackageName(), isIncludeGooglePlayStore);\n    }\n\n    /**\n     * 获取跳转到应用商店的 Intent\n     *\n     * @param packageName 包名\n     * @return 跳转到应用商店的 Intent\n     */\n    public static Intent getAppStoreIntent(final String packageName) {\n        return getAppStoreIntent(packageName, false);\n    }\n\n    /**\n     * 获取跳转到应用商店的 Intent\n     * <p>优先跳转到手机自带的应用市场</p>\n     *\n     * @param packageName              包名\n     * @param isIncludeGooglePlayStore 是否包括 Google Play 商店\n     * @return 跳转到应用商店的 Intent\n     */\n    public static Intent getAppStoreIntent(final String packageName, boolean isIncludeGooglePlayStore) {\n        if (RomUtils.isSamsung()) {// 三星单独处理跳转三星市场\n            Intent samsungAppStoreIntent = getSamsungAppStoreIntent(packageName);\n            if (samsungAppStoreIntent != null) return samsungAppStoreIntent;\n        }\n        if (RomUtils.isLeeco()) {// 乐视单独处理跳转乐视市场\n            Intent leecoAppStoreIntent = getLeecoAppStoreIntent(packageName);\n            if (leecoAppStoreIntent != null) return leecoAppStoreIntent;\n        }\n\n        Uri uri = Uri.parse(\"market://details?id=\" + packageName);\n        Intent intent = new Intent();\n        intent.setData(uri);\n        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        List<ResolveInfo> resolveInfos = Utils.getApp().getPackageManager()\n                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);\n        if (resolveInfos == null || resolveInfos.size() == 0) {\n            Log.e(TAG, \"No app store!\");\n            return null;\n        }\n        Intent googleIntent = null;\n        for (ResolveInfo resolveInfo : resolveInfos) {\n            String pkgName = resolveInfo.activityInfo.packageName;\n            if (!GOOGLE_PLAY_APP_STORE_PACKAGE_NAME.equals(pkgName)) {\n                if (AppUtils.isAppSystem(pkgName)) {\n                    intent.setPackage(pkgName);\n                    return intent;\n                }\n            } else {\n                intent.setPackage(GOOGLE_PLAY_APP_STORE_PACKAGE_NAME);\n                googleIntent = intent;\n            }\n        }\n        if (isIncludeGooglePlayStore && googleIntent != null) {\n            return googleIntent;\n        }\n\n        intent.setPackage(resolveInfos.get(0).activityInfo.packageName);\n        return intent;\n    }\n\n    private static Intent getSamsungAppStoreIntent(final String packageName) {\n        Intent intent = new Intent();\n        intent.setClassName(\"com.sec.android.app.samsungapps\", \"com.sec.android.app.samsungapps.Main\");\n        intent.setData(Uri.parse(\"http://www.samsungapps.com/appquery/appDetail.as?appId=\" + packageName));\n        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        if (getAvailableIntentSize(intent) > 0) {\n            return intent;\n        }\n        return null;\n    }\n\n    private static Intent getLeecoAppStoreIntent(final String packageName) {\n        Intent intent = new Intent();\n        intent.setClassName(\"com.letv.app.appstore\", \"com.letv.app.appstore.appmodule.details.DetailsActivity\");\n        intent.setAction(\"com.letv.app.appstore.appdetailactivity\");\n        intent.putExtra(\"packageName\", packageName);\n        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        if (getAvailableIntentSize(intent) > 0) {\n            return intent;\n        }\n        return null;\n    }\n\n    private static int getAvailableIntentSize(final Intent intent) {\n        return Utils.getApp().getPackageManager()\n                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)\n                .size();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/AppUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.Activity;\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.Signature;\nimport android.content.pm.SigningInfo;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.os.Build;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.util.Log;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/02\n *     desc  : utils about app\n * </pre>\n */\npublic final class AppUtils {\n\n    private AppUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Register the status of application changed listener.\n     *\n     * @param listener The status of application changed listener\n     */\n    public static void registerAppStatusChangedListener(@NonNull final Utils.OnAppStatusChangedListener listener) {\n        UtilsBridge.addOnAppStatusChangedListener(listener);\n    }\n\n    /**\n     * Unregister the status of application changed listener.\n     *\n     * @param listener The status of application changed listener\n     */\n    public static void unregisterAppStatusChangedListener(@NonNull final Utils.OnAppStatusChangedListener listener) {\n        UtilsBridge.removeOnAppStatusChangedListener(listener);\n    }\n\n    /**\n     * Install the app.\n     * <p>Target APIs greater than 25 must hold\n     * {@code <uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />}</p>\n     *\n     * @param filePath The path of file.\n     */\n    public static void installApp(final String filePath) {\n        installApp(UtilsBridge.getFileByPath(filePath));\n    }\n\n    /**\n     * Install the app.\n     * <p>Target APIs greater than 25 must hold\n     * {@code <uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />}</p>\n     *\n     * @param file The file.\n     */\n    public static void installApp(final File file) {\n        Intent installAppIntent = UtilsBridge.getInstallAppIntent(file);\n        if (installAppIntent == null) return;\n        Utils.getApp().startActivity(installAppIntent);\n    }\n\n    /**\n     * Install the app.\n     * <p>Target APIs greater than 25 must hold\n     * {@code <uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />}</p>\n     *\n     * @param uri The uri.\n     */\n    public static void installApp(final Uri uri) {\n        Intent installAppIntent = UtilsBridge.getInstallAppIntent(uri);\n        if (installAppIntent == null) return;\n        Utils.getApp().startActivity(installAppIntent);\n    }\n\n    /**\n     * Uninstall the app.\n     * <p>Target APIs greater than 25 must hold\n     * Must hold {@code <uses-permission android:name=\"android.permission.REQUEST_DELETE_PACKAGES\" />}</p>\n     *\n     * @param packageName The name of the package.\n     */\n    public static void uninstallApp(final String packageName) {\n        if (UtilsBridge.isSpace(packageName)) return;\n        Utils.getApp().startActivity(UtilsBridge.getUninstallAppIntent(packageName));\n    }\n\n    /**\n     * Return whether the app is installed.\n     *\n     * @param pkgName The name of the package.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAppInstalled(final String pkgName) {\n        if (UtilsBridge.isSpace(pkgName)) return false;\n        PackageManager pm = Utils.getApp().getPackageManager();\n        try {\n            return pm.getApplicationInfo(pkgName, 0).enabled;\n        } catch (PackageManager.NameNotFoundException e) {\n            return false;\n        }\n    }\n\n    /**\n     * Return whether the application with root permission.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAppRoot() {\n        ShellUtils.CommandResult result = UtilsBridge.execCmd(\"echo root\", true);\n        return result.result == 0;\n    }\n\n    /**\n     * Return whether it is a debug application.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAppDebug() {\n        return isAppDebug(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return whether it is a debug application.\n     *\n     * @param packageName The name of the package.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAppDebug(final String packageName) {\n        if (UtilsBridge.isSpace(packageName)) return false;\n        try {\n            PackageManager pm = Utils.getApp().getPackageManager();\n            ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);\n            return (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * Return whether it is a system application.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAppSystem() {\n        return isAppSystem(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return whether it is a system application.\n     *\n     * @param packageName The name of the package.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAppSystem(final String packageName) {\n        if (UtilsBridge.isSpace(packageName)) return false;\n        try {\n            PackageManager pm = Utils.getApp().getPackageManager();\n            ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);\n            return (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * Return whether application is foreground.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAppForeground() {\n        return UtilsBridge.isAppForeground();\n    }\n\n    /**\n     * Return whether application is foreground.\n     * <p>Target APIs greater than 21 must hold\n     * {@code <uses-permission android:name=\"android.permission.PACKAGE_USAGE_STATS\" />}</p>\n     *\n     * @param pkgName The name of the package.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAppForeground(@NonNull final String pkgName) {\n        return !UtilsBridge.isSpace(pkgName) && pkgName.equals(UtilsBridge.getForegroundProcessName());\n    }\n\n    /**\n     * Return whether application is running.\n     *\n     * @param pkgName The name of the package.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAppRunning(final String pkgName) {\n        if (UtilsBridge.isSpace(pkgName)) return false;\n        ActivityManager am = (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);\n        if (am != null) {\n            List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(Integer.MAX_VALUE);\n            if (taskInfo != null && taskInfo.size() > 0) {\n                for (ActivityManager.RunningTaskInfo aInfo : taskInfo) {\n                    if (aInfo.baseActivity != null) {\n                        if (pkgName.equals(aInfo.baseActivity.getPackageName())) {\n                            return true;\n                        }\n                    }\n                }\n            }\n            List<ActivityManager.RunningServiceInfo> serviceInfo = am.getRunningServices(Integer.MAX_VALUE);\n            if (serviceInfo != null && serviceInfo.size() > 0) {\n                for (ActivityManager.RunningServiceInfo aInfo : serviceInfo) {\n                    if (pkgName.equals(aInfo.service.getPackageName())) {\n                        return true;\n                    }\n                }\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Launch the application.\n     *\n     * @param packageName The name of the package.\n     */\n    public static void launchApp(final String packageName) {\n        if (UtilsBridge.isSpace(packageName)) return;\n        Intent launchAppIntent = UtilsBridge.getLaunchAppIntent(packageName);\n        if (launchAppIntent == null) {\n            Log.e(\"AppUtils\", \"Didn't exist launcher activity.\");\n            return;\n        }\n        Utils.getApp().startActivity(launchAppIntent);\n    }\n\n    /**\n     * Relaunch the application.\n     */\n    public static void relaunchApp() {\n        relaunchApp(false);\n    }\n\n    /**\n     * Relaunch the application.\n     *\n     * @param isKillProcess True to kill the process, false otherwise.\n     */\n    public static void relaunchApp(final boolean isKillProcess) {\n        Intent intent = UtilsBridge.getLaunchAppIntent(Utils.getApp().getPackageName());\n        if (intent == null) {\n            Log.e(\"AppUtils\", \"Didn't exist launcher activity.\");\n            return;\n        }\n        intent.addFlags(\n                Intent.FLAG_ACTIVITY_NEW_TASK\n                        | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK\n        );\n        Utils.getApp().startActivity(intent);\n        if (!isKillProcess) return;\n        android.os.Process.killProcess(android.os.Process.myPid());\n        System.exit(0);\n    }\n\n    /**\n     * Launch the application's details settings.\n     */\n    public static void launchAppDetailsSettings() {\n        launchAppDetailsSettings(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Launch the application's details settings.\n     *\n     * @param pkgName The name of the package.\n     */\n    public static void launchAppDetailsSettings(final String pkgName) {\n        if (UtilsBridge.isSpace(pkgName)) return;\n        Intent intent = UtilsBridge.getLaunchAppDetailsSettingsIntent(pkgName, true);\n        if (!UtilsBridge.isIntentAvailable(intent)) return;\n        Utils.getApp().startActivity(intent);\n    }\n\n    /**\n     * Launch the application's details settings.\n     *\n     * @param activity    The activity.\n     * @param requestCode The requestCode.\n     */\n    public static void launchAppDetailsSettings(final Activity activity, final int requestCode) {\n        launchAppDetailsSettings(activity, requestCode, Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Launch the application's details settings.\n     *\n     * @param activity    The activity.\n     * @param requestCode The requestCode.\n     * @param pkgName     The name of the package.\n     */\n    public static void launchAppDetailsSettings(final Activity activity, final int requestCode, final String pkgName) {\n        if (activity == null || UtilsBridge.isSpace(pkgName)) return;\n        Intent intent = UtilsBridge.getLaunchAppDetailsSettingsIntent(pkgName, false);\n        if (!UtilsBridge.isIntentAvailable(intent)) return;\n        activity.startActivityForResult(intent, requestCode);\n    }\n\n    /**\n     * Exit the application.\n     */\n    public static void exitApp() {\n        UtilsBridge.finishAllActivities();\n        System.exit(0);\n    }\n\n    /**\n     * Return the application's icon.\n     *\n     * @return the application's icon\n     */\n    @Nullable\n    public static Drawable getAppIcon() {\n        return getAppIcon(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the application's icon.\n     *\n     * @param packageName The name of the package.\n     * @return the application's icon\n     */\n    @Nullable\n    public static Drawable getAppIcon(final String packageName) {\n        if (UtilsBridge.isSpace(packageName)) return null;\n        try {\n            PackageManager pm = Utils.getApp().getPackageManager();\n            PackageInfo pi = pm.getPackageInfo(packageName, 0);\n            return pi == null ? null : pi.applicationInfo.loadIcon(pm);\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * Return the application's icon resource identifier.\n     *\n     * @return the application's icon resource identifier\n     */\n    public static int getAppIconId() {\n        return getAppIconId(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the application's icon resource identifier.\n     *\n     * @param packageName The name of the package.\n     * @return the application's icon resource identifier\n     */\n    public static int getAppIconId(final String packageName) {\n        if (UtilsBridge.isSpace(packageName)) return 0;\n        try {\n            PackageManager pm = Utils.getApp().getPackageManager();\n            PackageInfo pi = pm.getPackageInfo(packageName, 0);\n            return pi == null ? 0 : pi.applicationInfo.icon;\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n            return 0;\n        }\n    }\n\n    /**\n     * Return the application's package name.\n     *\n     * @return the application's package name\n     */\n    @NonNull\n    public static String getAppPackageName() {\n        return Utils.getApp().getPackageName();\n    }\n\n    /**\n     * Return the application's name.\n     *\n     * @return the application's name\n     */\n    @NonNull\n    public static String getAppName() {\n        return getAppName(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the application's name.\n     *\n     * @param packageName The name of the package.\n     * @return the application's name\n     */\n    @NonNull\n    public static String getAppName(final String packageName) {\n        if (UtilsBridge.isSpace(packageName)) return \"\";\n        try {\n            PackageManager pm = Utils.getApp().getPackageManager();\n            PackageInfo pi = pm.getPackageInfo(packageName, 0);\n            return pi == null ? \"\" : pi.applicationInfo.loadLabel(pm).toString();\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n            return \"\";\n        }\n    }\n\n    /**\n     * Return the application's path.\n     *\n     * @return the application's path\n     */\n    @NonNull\n    public static String getAppPath() {\n        return getAppPath(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the application's path.\n     *\n     * @param packageName The name of the package.\n     * @return the application's path\n     */\n    @NonNull\n    public static String getAppPath(final String packageName) {\n        if (UtilsBridge.isSpace(packageName)) return \"\";\n        try {\n            PackageManager pm = Utils.getApp().getPackageManager();\n            PackageInfo pi = pm.getPackageInfo(packageName, 0);\n            return pi == null ? \"\" : pi.applicationInfo.sourceDir;\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n            return \"\";\n        }\n    }\n\n    /**\n     * Return the application's version name.\n     *\n     * @return the application's version name\n     */\n    @NonNull\n    public static String getAppVersionName() {\n        return getAppVersionName(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the application's version name.\n     *\n     * @param packageName The name of the package.\n     * @return the application's version name\n     */\n    @NonNull\n    public static String getAppVersionName(final String packageName) {\n        if (UtilsBridge.isSpace(packageName)) return \"\";\n        try {\n            PackageManager pm = Utils.getApp().getPackageManager();\n            PackageInfo pi = pm.getPackageInfo(packageName, 0);\n            return pi == null ? \"\" : pi.versionName;\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n            return \"\";\n        }\n    }\n\n    /**\n     * Return the application's version code.\n     *\n     * @return the application's version code\n     */\n    public static int getAppVersionCode() {\n        return getAppVersionCode(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the application's version code.\n     *\n     * @param packageName The name of the package.\n     * @return the application's version code\n     */\n    public static int getAppVersionCode(final String packageName) {\n        if (UtilsBridge.isSpace(packageName)) return -1;\n        try {\n            PackageManager pm = Utils.getApp().getPackageManager();\n            PackageInfo pi = pm.getPackageInfo(packageName, 0);\n            return pi == null ? -1 : pi.versionCode;\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n            return -1;\n        }\n    }\n\n    /**\n     * Return the application's signature.\n     *\n     * @return the application's signature\n     */\n    @Nullable\n    public static Signature[] getAppSignatures() {\n        return getAppSignatures(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the application's signature.\n     *\n     * @param packageName The name of the package.\n     * @return the application's signature\n     */\n    @Nullable\n    public static Signature[] getAppSignatures(final String packageName) {\n        if (UtilsBridge.isSpace(packageName)) return null;\n        try {\n            PackageManager pm = Utils.getApp().getPackageManager();\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n                PackageInfo pi = pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES);\n                if (pi == null) return null;\n\n                SigningInfo signingInfo = pi.signingInfo;\n                if (signingInfo.hasMultipleSigners()) {\n                    return signingInfo.getApkContentsSigners();\n                } else {\n                    return signingInfo.getSigningCertificateHistory();\n                }\n            } else {\n                PackageInfo pi = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);\n                if (pi == null) return null;\n\n                return pi.signatures;\n            }\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * Return the application's signature.\n     *\n     * @param file The file.\n     * @return the application's signature\n     */\n    @Nullable\n    public static Signature[] getAppSignatures(final File file) {\n        if (file == null) return null;\n        PackageManager pm = Utils.getApp().getPackageManager();\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n            PackageInfo pi = pm.getPackageArchiveInfo(file.getAbsolutePath(), PackageManager.GET_SIGNING_CERTIFICATES);\n            if (pi == null) return null;\n\n            SigningInfo signingInfo = pi.signingInfo;\n            if (signingInfo.hasMultipleSigners()) {\n                return signingInfo.getApkContentsSigners();\n            } else {\n                return signingInfo.getSigningCertificateHistory();\n            }\n        } else {\n            PackageInfo pi = pm.getPackageArchiveInfo(file.getAbsolutePath(), PackageManager.GET_SIGNATURES);\n            if (pi == null) return null;\n\n            return pi.signatures;\n        }\n    }\n\n    /**\n     * Return the application's signature for SHA1 value.\n     *\n     * @return the application's signature for SHA1 value\n     */\n    @NonNull\n    public static List<String> getAppSignaturesSHA1() {\n        return getAppSignaturesSHA1(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the application's signature for SHA1 value.\n     *\n     * @param packageName The name of the package.\n     * @return the application's signature for SHA1 value\n     */\n    @NonNull\n    public static List<String> getAppSignaturesSHA1(final String packageName) {\n        return getAppSignaturesHash(packageName, \"SHA1\");\n    }\n\n    /**\n     * Return the application's signature for SHA256 value.\n     *\n     * @return the application's signature for SHA256 value\n     */\n    @NonNull\n    public static List<String> getAppSignaturesSHA256() {\n        return getAppSignaturesSHA256(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the application's signature for SHA256 value.\n     *\n     * @param packageName The name of the package.\n     * @return the application's signature for SHA256 value\n     */\n    @NonNull\n    public static List<String> getAppSignaturesSHA256(final String packageName) {\n        return getAppSignaturesHash(packageName, \"SHA256\");\n    }\n\n    /**\n     * Return the application's signature for MD5 value.\n     *\n     * @return the application's signature for MD5 value\n     */\n    @NonNull\n    public static List<String> getAppSignaturesMD5() {\n        return getAppSignaturesMD5(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the application's signature for MD5 value.\n     *\n     * @param packageName The name of the package.\n     * @return the application's signature for MD5 value\n     */\n    @NonNull\n    public static List<String> getAppSignaturesMD5(final String packageName) {\n        return getAppSignaturesHash(packageName, \"MD5\");\n    }\n\n    /**\n     * Return the application's user-ID.\n     *\n     * @return the application's signature for MD5 value\n     */\n    public static int getAppUid() {\n        return getAppUid(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the application's user-ID.\n     *\n     * @param pkgName The name of the package.\n     * @return the application's signature for MD5 value\n     */\n    public static int getAppUid(String pkgName) {\n        try {\n            return Utils.getApp().getPackageManager().getApplicationInfo(pkgName, 0).uid;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return -1;\n        }\n    }\n\n    private static List<String> getAppSignaturesHash(final String packageName, final String algorithm) {\n        ArrayList<String> result = new ArrayList<>();\n        if (UtilsBridge.isSpace(packageName)) return result;\n        Signature[] signatures = getAppSignatures(packageName);\n        if (signatures == null || signatures.length <= 0) return result;\n        for (Signature signature : signatures) {\n            String hash = UtilsBridge.bytes2HexString(UtilsBridge.hashTemplate(signature.toByteArray(), algorithm))\n                    .replaceAll(\"(?<=[0-9A-F]{2})[0-9A-F]{2}\", \":$0\");\n            result.add(hash);\n        }\n        return result;\n    }\n\n    /**\n     * Return the application's information.\n     * <ul>\n     * <li>name of package</li>\n     * <li>icon</li>\n     * <li>name</li>\n     * <li>path of package</li>\n     * <li>version name</li>\n     * <li>version code</li>\n     * <li>is system</li>\n     * </ul>\n     *\n     * @return the application's information\n     */\n    @Nullable\n    public static AppInfo getAppInfo() {\n        return getAppInfo(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the application's information.\n     * <ul>\n     * <li>name of package</li>\n     * <li>icon</li>\n     * <li>name</li>\n     * <li>path of package</li>\n     * <li>version name</li>\n     * <li>version code</li>\n     * <li>is system</li>\n     * </ul>\n     *\n     * @param packageName The name of the package.\n     * @return the application's information\n     */\n    @Nullable\n    public static AppInfo getAppInfo(final String packageName) {\n        try {\n            PackageManager pm = Utils.getApp().getPackageManager();\n            if (pm == null) return null;\n            return getBean(pm, pm.getPackageInfo(packageName, 0));\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * Return the applications' information.\n     *\n     * @return the applications' information\n     */\n    @NonNull\n    public static List<AppInfo> getAppsInfo() {\n        List<AppInfo> list = new ArrayList<>();\n        PackageManager pm = Utils.getApp().getPackageManager();\n        if (pm == null) return list;\n        List<PackageInfo> installedPackages = pm.getInstalledPackages(0);\n        for (PackageInfo pi : installedPackages) {\n            AppInfo ai = getBean(pm, pi);\n            if (ai == null) continue;\n            list.add(ai);\n        }\n        return list;\n    }\n\n    /**\n     * Return the application's package information.\n     *\n     * @return the application's package information\n     */\n    @Nullable\n    public static AppUtils.AppInfo getApkInfo(final File apkFile) {\n        if (apkFile == null || !apkFile.isFile() || !apkFile.exists()) return null;\n        return getApkInfo(apkFile.getAbsolutePath());\n    }\n\n    /**\n     * Return the application's package information.\n     *\n     * @return the application's package information\n     */\n    @Nullable\n    public static AppUtils.AppInfo getApkInfo(final String apkFilePath) {\n        if (UtilsBridge.isSpace(apkFilePath)) return null;\n        PackageManager pm = Utils.getApp().getPackageManager();\n        if (pm == null) return null;\n        PackageInfo pi = pm.getPackageArchiveInfo(apkFilePath, 0);\n        if (pi == null) return null;\n        ApplicationInfo appInfo = pi.applicationInfo;\n        appInfo.sourceDir = apkFilePath;\n        appInfo.publicSourceDir = apkFilePath;\n        return getBean(pm, pi);\n    }\n\n    private static AppInfo getBean(final PackageManager pm, final PackageInfo pi) {\n        if (pi == null) return null;\n        String versionName = pi.versionName;\n        int versionCode = pi.versionCode;\n        String packageName = pi.packageName;\n        ApplicationInfo ai = pi.applicationInfo;\n        if (ai == null) {\n            return new AppInfo(packageName, \"\", null, \"\", versionName, versionCode, false);\n        }\n        String name = ai.loadLabel(pm).toString();\n        Drawable icon = ai.loadIcon(pm);\n        String packagePath = ai.sourceDir;\n        boolean isSystem = (ApplicationInfo.FLAG_SYSTEM & ai.flags) != 0;\n        return new AppInfo(packageName, name, icon, packagePath, versionName, versionCode, isSystem);\n    }\n\n    /**\n     * The application's information.\n     */\n    public static class AppInfo {\n\n        private String   packageName;\n        private String   name;\n        private Drawable icon;\n        private String   packagePath;\n        private String   versionName;\n        private int      versionCode;\n        private boolean  isSystem;\n\n        public Drawable getIcon() {\n            return icon;\n        }\n\n        public void setIcon(final Drawable icon) {\n            this.icon = icon;\n        }\n\n        public boolean isSystem() {\n            return isSystem;\n        }\n\n        public void setSystem(final boolean isSystem) {\n            this.isSystem = isSystem;\n        }\n\n        public String getPackageName() {\n            return packageName;\n        }\n\n        public void setPackageName(final String packageName) {\n            this.packageName = packageName;\n        }\n\n        public String getName() {\n            return name;\n        }\n\n        public void setName(final String name) {\n            this.name = name;\n        }\n\n        public String getPackagePath() {\n            return packagePath;\n        }\n\n        public void setPackagePath(final String packagePath) {\n            this.packagePath = packagePath;\n        }\n\n        public int getVersionCode() {\n            return versionCode;\n        }\n\n        public void setVersionCode(final int versionCode) {\n            this.versionCode = versionCode;\n        }\n\n        public String getVersionName() {\n            return versionName;\n        }\n\n        public void setVersionName(final String versionName) {\n            this.versionName = versionName;\n        }\n\n        public AppInfo(String packageName, String name, Drawable icon, String packagePath,\n                       String versionName, int versionCode, boolean isSystem) {\n            this.setName(name);\n            this.setIcon(icon);\n            this.setPackageName(packageName);\n            this.setPackagePath(packagePath);\n            this.setVersionName(versionName);\n            this.setVersionCode(versionCode);\n            this.setSystem(isSystem);\n        }\n\n        @Override\n        @NonNull\n        public String toString() {\n            return \"{\" +\n                    \"\\n    pkg name: \" + getPackageName() +\n                    \"\\n    app icon: \" + getIcon() +\n                    \"\\n    app name: \" + getName() +\n                    \"\\n    app path: \" + getPackagePath() +\n                    \"\\n    app v name: \" + getVersionName() +\n                    \"\\n    app v code: \" + getVersionCode() +\n                    \"\\n    is system: \" + isSystem() +\n                    \"\\n}\";\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ArrayUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.Nullable;\n\nimport java.lang.reflect.Array;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * <pre>\n *     author: blankj\n *     blog  : http://blankj.com\n *     time  : 2019/08/10\n *     desc  : utils about array\n * </pre>\n */\npublic class ArrayUtils {\n\n    public static final int INDEX_NOT_FOUND = -1;\n\n    private ArrayUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Returns a new array only of those given elements.\n     *\n     * @param array The array.\n     * @return a new array only of those given elements.\n     */\n    @NonNull\n    public static <T> T[] newArray(T... array) {\n        return array;\n    }\n\n    @NonNull\n    public static long[] newLongArray(long... array) {\n        return array;\n    }\n\n    @NonNull\n    public static int[] newIntArray(int... array) {\n        return array;\n    }\n\n    @NonNull\n    public static short[] newShortArray(short... array) {\n        return array;\n    }\n\n    @NonNull\n    public static char[] newCharArray(char... array) {\n        return array;\n    }\n\n    @NonNull\n    public static byte[] newByteArray(byte... array) {\n        return array;\n    }\n\n    @NonNull\n    public static double[] newDoubleArray(double... array) {\n        return array;\n    }\n\n    @NonNull\n    public static float[] newFloatArray(float... array) {\n        return array;\n    }\n\n    @NonNull\n    public static boolean[] newBooleanArray(boolean... array) {\n        return array;\n    }\n\n    /**\n     * Return the array is empty.\n     *\n     * @param array The array.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isEmpty(@Nullable Object array) {\n        return getLength(array) == 0;\n    }\n\n    /**\n     * Return the size of array.\n     *\n     * @param array The array.\n     * @return the size of array\n     */\n    public static int getLength(@Nullable Object array) {\n        if (array == null) return 0;\n        return Array.getLength(array);\n    }\n\n    public static boolean isSameLength(@Nullable Object array1, @Nullable Object array2) {\n        return getLength(array1) == getLength(array2);\n    }\n\n    /**\n     * Get the value of the specified index of the array.\n     *\n     * @param array The array.\n     * @param index The index into the array.\n     * @return the value of the specified index of the array\n     */\n    @Nullable\n    public static Object get(@Nullable Object array, int index) {\n        return get(array, index, null);\n    }\n\n    /**\n     * Get the value of the specified index of the array.\n     *\n     * @param array        The array.\n     * @param index        The index into the array.\n     * @param defaultValue The default value.\n     * @return the value of the specified index of the array\n     */\n    @Nullable\n    public static Object get(@Nullable Object array, int index, @Nullable Object defaultValue) {\n        if (array == null) return defaultValue;\n        try {\n            return Array.get(array, index);\n        } catch (Exception ignore) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * Set the value of the specified index of the array.\n     *\n     * @param array The array.\n     * @param index The index into the array.\n     * @param value The new value of the indexed component.\n     */\n    public static void set(@Nullable Object array, int index, @Nullable Object value) {\n        if (array == null) return;\n        Array.set(array, index, value);\n    }\n\n    /**\n     * Return whether the two arrays are equals.\n     *\n     * @param a  One array.\n     * @param a2 The other array.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean equals(@Nullable Object[] a, @Nullable Object[] a2) {\n        return Arrays.deepEquals(a, a2);\n    }\n\n    public static boolean equals(boolean[] a, boolean[] a2) {\n        return Arrays.equals(a, a2);\n    }\n\n    public static boolean equals(byte[] a, byte[] a2) {\n        return Arrays.equals(a, a2);\n    }\n\n    public static boolean equals(char[] a, char[] a2) {\n        return Arrays.equals(a, a2);\n    }\n\n    public static boolean equals(double[] a, double[] a2) {\n        return Arrays.equals(a, a2);\n    }\n\n    public static boolean equals(float[] a, float[] a2) {\n        return Arrays.equals(a, a2);\n    }\n\n    public static boolean equals(int[] a, int[] a2) {\n        return Arrays.equals(a, a2);\n    }\n\n    public static boolean equals(short[] a, short[] a2) {\n        return Arrays.equals(a, a2);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // reverse\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * <p>Reverses the order of the given array.</p>\n     *\n     * <p>There is no special handling for multi-dimensional arrays.</p>\n     *\n     * <p>This method does nothing for a <code>null</code> input array.</p>\n     *\n     * @param array the array to reverse, may be <code>null</code>\n     */\n    public static <T> void reverse(T[] array) {\n        if (array == null) {\n            return;\n        }\n        int i = 0;\n        int j = array.length - 1;\n        T tmp;\n        while (j > i) {\n            tmp = array[j];\n            array[j] = array[i];\n            array[i] = tmp;\n            j--;\n            i++;\n        }\n    }\n\n    public static void reverse(long[] array) {\n        if (array == null) {\n            return;\n        }\n        int i = 0;\n        int j = array.length - 1;\n        long tmp;\n        while (j > i) {\n            tmp = array[j];\n            array[j] = array[i];\n            array[i] = tmp;\n            j--;\n            i++;\n        }\n    }\n\n    public static void reverse(int[] array) {\n        if (array == null) {\n            return;\n        }\n        int i = 0;\n        int j = array.length - 1;\n        int tmp;\n        while (j > i) {\n            tmp = array[j];\n            array[j] = array[i];\n            array[i] = tmp;\n            j--;\n            i++;\n        }\n    }\n\n    public static void reverse(short[] array) {\n        if (array == null) {\n            return;\n        }\n        int i = 0;\n        int j = array.length - 1;\n        short tmp;\n        while (j > i) {\n            tmp = array[j];\n            array[j] = array[i];\n            array[i] = tmp;\n            j--;\n            i++;\n        }\n    }\n\n    public static void reverse(char[] array) {\n        if (array == null) {\n            return;\n        }\n        int i = 0;\n        int j = array.length - 1;\n        char tmp;\n        while (j > i) {\n            tmp = array[j];\n            array[j] = array[i];\n            array[i] = tmp;\n            j--;\n            i++;\n        }\n    }\n\n    public static void reverse(byte[] array) {\n        if (array == null) {\n            return;\n        }\n        int i = 0;\n        int j = array.length - 1;\n        byte tmp;\n        while (j > i) {\n            tmp = array[j];\n            array[j] = array[i];\n            array[i] = tmp;\n            j--;\n            i++;\n        }\n    }\n\n    public static void reverse(double[] array) {\n        if (array == null) {\n            return;\n        }\n        int i = 0;\n        int j = array.length - 1;\n        double tmp;\n        while (j > i) {\n            tmp = array[j];\n            array[j] = array[i];\n            array[i] = tmp;\n            j--;\n            i++;\n        }\n    }\n\n    public static void reverse(float[] array) {\n        if (array == null) {\n            return;\n        }\n        int i = 0;\n        int j = array.length - 1;\n        float tmp;\n        while (j > i) {\n            tmp = array[j];\n            array[j] = array[i];\n            array[i] = tmp;\n            j--;\n            i++;\n        }\n    }\n\n    public static void reverse(boolean[] array) {\n        if (array == null) {\n            return;\n        }\n        int i = 0;\n        int j = array.length - 1;\n        boolean tmp;\n        while (j > i) {\n            tmp = array[j];\n            array[j] = array[i];\n            array[i] = tmp;\n            j--;\n            i++;\n        }\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // copy\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * <p>Copies the specified array and handling\n     * <code>null</code>.</p>\n     *\n     * <p>The objects in the array are not cloned, thus there is no special\n     * handling for multi-dimensional arrays.</p>\n     *\n     * <p>This method returns <code>null</code> for a <code>null</code> input array.</p>\n     *\n     * @param array the array to shallow clone, may be <code>null</code>\n     * @return the cloned array, <code>null</code> if <code>null</code> input\n     */\n    @Nullable\n    public static <T> T[] copy(@Nullable T[] array) {\n        if (array == null) return null;\n        return subArray(array, 0, array.length);\n    }\n\n    @Nullable\n    public static long[] copy(@Nullable long[] array) {\n        if (array == null) return null;\n        return subArray(array, 0, array.length);\n    }\n\n    @Nullable\n    public static int[] copy(@Nullable int[] array) {\n        if (array == null) return null;\n        return subArray(array, 0, array.length);\n    }\n\n    @Nullable\n    public static short[] copy(@Nullable short[] array) {\n        if (array == null) return null;\n        return subArray(array, 0, array.length);\n    }\n\n    @Nullable\n    public static char[] copy(@Nullable char[] array) {\n        if (array == null) return null;\n        return subArray(array, 0, array.length);\n    }\n\n    @Nullable\n    public static byte[] copy(@Nullable byte[] array) {\n        if (array == null) return null;\n        return subArray(array, 0, array.length);\n    }\n\n    @Nullable\n    public static double[] copy(@Nullable double[] array) {\n        if (array == null) return null;\n        return subArray(array, 0, array.length);\n    }\n\n    @Nullable\n    public static float[] copy(@Nullable float[] array) {\n        if (array == null) return null;\n        return subArray(array, 0, array.length);\n    }\n\n    @Nullable\n    public static boolean[] copy(@Nullable boolean[] array) {\n        if (array == null) return null;\n        return subArray(array, 0, array.length);\n    }\n\n    @Nullable\n    private static Object realCopy(@Nullable Object array) {\n        if (array == null) return null;\n        return realSubArray(array, 0, getLength(array));\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // subArray\n    ///////////////////////////////////////////////////////////////////////////\n\n    @Nullable\n    public static <T> T[] subArray(@Nullable T[] array, int startIndexInclusive, int endIndexExclusive) {\n        //noinspection unchecked\n        return (T[]) realSubArray(array, startIndexInclusive, endIndexExclusive);\n    }\n\n    @Nullable\n    public static long[] subArray(@Nullable long[] array, int startIndexInclusive, int endIndexExclusive) {\n        return (long[]) realSubArray(array, startIndexInclusive, endIndexExclusive);\n    }\n\n    @Nullable\n    public static int[] subArray(@Nullable int[] array, int startIndexInclusive, int endIndexExclusive) {\n        return (int[]) realSubArray(array, startIndexInclusive, endIndexExclusive);\n    }\n\n    @Nullable\n    public static short[] subArray(@Nullable short[] array, int startIndexInclusive, int endIndexExclusive) {\n        return (short[]) realSubArray(array, startIndexInclusive, endIndexExclusive);\n    }\n\n    @Nullable\n    public static char[] subArray(@Nullable char[] array, int startIndexInclusive, int endIndexExclusive) {\n        return (char[]) realSubArray(array, startIndexInclusive, endIndexExclusive);\n    }\n\n    @Nullable\n    public static byte[] subArray(@Nullable byte[] array, int startIndexInclusive, int endIndexExclusive) {\n        return (byte[]) realSubArray(array, startIndexInclusive, endIndexExclusive);\n    }\n\n    @Nullable\n    public static double[] subArray(@Nullable double[] array, int startIndexInclusive, int endIndexExclusive) {\n        return (double[]) realSubArray(array, startIndexInclusive, endIndexExclusive);\n    }\n\n    @Nullable\n    public static float[] subArray(@Nullable float[] array, int startIndexInclusive, int endIndexExclusive) {\n        return (float[]) realSubArray(array, startIndexInclusive, endIndexExclusive);\n    }\n\n    @Nullable\n    public static boolean[] subArray(@Nullable boolean[] array, int startIndexInclusive, int endIndexExclusive) {\n        return (boolean[]) realSubArray(array, startIndexInclusive, endIndexExclusive);\n    }\n\n    @Nullable\n    private static Object realSubArray(@Nullable Object array, int startIndexInclusive, int endIndexExclusive) {\n        if (array == null) {\n            return null;\n        }\n        if (startIndexInclusive < 0) {\n            startIndexInclusive = 0;\n        }\n        int length = getLength(array);\n        if (endIndexExclusive > length) {\n            endIndexExclusive = length;\n        }\n        int newSize = endIndexExclusive - startIndexInclusive;\n        Class type = array.getClass().getComponentType();\n        if (newSize <= 0) {\n            return Array.newInstance(type, 0);\n        }\n        Object subArray = Array.newInstance(type, newSize);\n        System.arraycopy(array, startIndexInclusive, subArray, 0, newSize);\n        return subArray;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // add\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * <p>Copies the given array and adds the given element at the end of the new array.</p>\n     *\n     * <p>The new array contains the same elements of the input\n     * array plus the given element in the last position. The component type of\n     * the new array is the same as that of the input array.</p>\n     *\n     * <p>If the input array is <code>null</code>, a new one element array is returned\n     * whose component type is the same as the element.</p>\n     *\n     * <pre>\n     * ArrayUtils.realAdd(null, null)      = [null]\n     * ArrayUtils.realAdd(null, \"a\")       = [\"a\"]\n     * ArrayUtils.realAdd([\"a\"], null)     = [\"a\", null]\n     * ArrayUtils.realAdd([\"a\"], \"b\")      = [\"a\", \"b\"]\n     * ArrayUtils.realAdd([\"a\", \"b\"], \"c\") = [\"a\", \"b\", \"c\"]\n     * </pre>\n     *\n     * @param array   the array to \"realAdd\" the element to, may be <code>null</code>\n     * @param element the object to realAdd\n     * @return A new array containing the existing elements plus the new element\n     */\n    @NonNull\n    public static <T> T[] add(@Nullable T[] array, @Nullable T element) {\n        Class type = array != null ? array.getClass() : (element != null ? element.getClass() : Object.class);\n        return (T[]) realAddOne(array, element, type);\n    }\n\n    @NonNull\n    public static boolean[] add(@Nullable boolean[] array, boolean element) {\n        return (boolean[]) realAddOne(array, element, Boolean.TYPE);\n    }\n\n    @NonNull\n    public static byte[] add(@Nullable byte[] array, byte element) {\n        return (byte[]) realAddOne(array, element, Byte.TYPE);\n    }\n\n    @NonNull\n    public static char[] add(@Nullable char[] array, char element) {\n        return (char[]) realAddOne(array, element, Character.TYPE);\n    }\n\n    @NonNull\n    public static double[] add(@Nullable double[] array, double element) {\n        return (double[]) realAddOne(array, element, Double.TYPE);\n    }\n\n    @NonNull\n    public static float[] add(@Nullable float[] array, float element) {\n        return (float[]) realAddOne(array, element, Float.TYPE);\n    }\n\n    @NonNull\n    public static int[] add(@Nullable int[] array, int element) {\n        return (int[]) realAddOne(array, element, Integer.TYPE);\n    }\n\n    @NonNull\n    public static long[] add(@Nullable long[] array, long element) {\n        return (long[]) realAddOne(array, element, Long.TYPE);\n    }\n\n    @NonNull\n    public static short[] add(@Nullable short[] array, short element) {\n        return (short[]) realAddOne(array, element, Short.TYPE);\n    }\n\n    @NonNull\n    private static Object realAddOne(@Nullable Object array, @Nullable Object element, Class newArrayComponentType) {\n        Object newArray;\n        int arrayLength = 0;\n        if (array != null) {\n            arrayLength = getLength(array);\n            newArray = Array.newInstance(array.getClass().getComponentType(), arrayLength + 1);\n            System.arraycopy(array, 0, newArray, 0, arrayLength);\n        } else {\n            newArray = Array.newInstance(newArrayComponentType, 1);\n        }\n        Array.set(newArray, arrayLength, element);\n        return newArray;\n    }\n\n    /**\n     * <p>Adds all the elements of the given arrays into a new array.</p>\n     * <p>The new array contains all of the element of <code>array1</code> followed\n     * by all of the elements <code>array2</code>. When an array is returned, it is always\n     * a new array.</p>\n     *\n     * <pre>\n     * ArrayUtils.add(null, null)     = null\n     * ArrayUtils.add(array1, null)   = copy of array1\n     * ArrayUtils.add(null, array2)   = copy of array2\n     * ArrayUtils.add([], [])         = []\n     * ArrayUtils.add([null], [null]) = [null, null]\n     * ArrayUtils.add([\"a\", \"b\", \"c\"], [\"1\", \"2\", \"3\"]) = [\"a\", \"b\", \"c\", \"1\", \"2\", \"3\"]\n     * </pre>\n     *\n     * @param array1 the first array whose elements are added to the new array, may be <code>null</code>\n     * @param array2 the second array whose elements are added to the new array, may be <code>null</code>\n     * @return The new array, <code>null</code> if <code>null</code> array inputs.\n     * The type of the new array is the type of the first array.\n     */\n    @Nullable\n    public static <T> T[] add(@Nullable T[] array1, @Nullable T[] array2) {\n        return (T[]) realAddArr(array1, array2);\n    }\n\n    @Nullable\n    public static boolean[] add(@Nullable boolean[] array1, @Nullable boolean[] array2) {\n        return (boolean[]) realAddArr(array1, array2);\n    }\n\n    @Nullable\n    public static char[] add(@Nullable char[] array1, @Nullable char[] array2) {\n        return (char[]) realAddArr(array1, array2);\n    }\n\n    @Nullable\n    public static byte[] add(@Nullable byte[] array1, @Nullable byte[] array2) {\n        return (byte[]) realAddArr(array1, array2);\n    }\n\n    @Nullable\n    public static short[] add(@Nullable short[] array1, @Nullable short[] array2) {\n        return (short[]) realAddArr(array1, array2);\n    }\n\n    @Nullable\n    public static int[] add(@Nullable int[] array1, @Nullable int[] array2) {\n        return (int[]) realAddArr(array1, array2);\n    }\n\n    @Nullable\n    public static long[] add(@Nullable long[] array1, @Nullable long[] array2) {\n        return (long[]) realAddArr(array1, array2);\n    }\n\n    @Nullable\n    public static float[] add(@Nullable float[] array1, @Nullable float[] array2) {\n        return (float[]) realAddArr(array1, array2);\n    }\n\n    @Nullable\n    public static double[] add(@Nullable double[] array1, @Nullable double[] array2) {\n        return (double[]) realAddArr(array1, array2);\n    }\n\n    private static Object realAddArr(@Nullable Object array1, @Nullable Object array2) {\n        if (array1 == null && array2 == null) return null;\n        if (array1 == null) {\n            return realCopy(array2);\n        }\n        if (array2 == null) {\n            return realCopy(array1);\n        }\n        int len1 = getLength(array1);\n        int len2 = getLength(array2);\n        Object joinedArray = Array.newInstance(array1.getClass().getComponentType(), len1 + len2);\n        System.arraycopy(array1, 0, joinedArray, 0, len1);\n        System.arraycopy(array2, 0, joinedArray, len1, len2);\n        return joinedArray;\n    }\n\n    /**\n     * <p>Inserts the specified element at the specified position in the array.\n     * Shifts the element currently at that position (if any) and any subsequent\n     * elements to the right (adds one to their indices).</p>\n     *\n     * <p>This method returns a new array with the same elements of the input\n     * array plus the given element on the specified position. The component\n     * type of the returned array is always the same as that of the input\n     * array.</p>\n     *\n     * <p>If the input array is <code>null</code>, a new one element array is returned\n     * whose component type is the same as the element.</p>\n     *\n     * <pre>\n     * ArrayUtils.add(null, 0, null)        = null\n     * ArrayUtils.add(null, 0, [\"a\"])       = [\"a\"]\n     * ArrayUtils.add([\"a\"], 1, null)       = [\"a\"]\n     * ArrayUtils.add([\"a\"], 1, [\"b\"])      = [\"a\", \"b\"]\n     * ArrayUtils.add([\"a\", \"b\"], 2, [\"c\"]) = [\"a\", \"b\", \"c\"]\n     * </pre>\n     *\n     * @param array1 the array to realAdd the element to, may be <code>null</code>\n     * @param index  the position of the new object\n     * @param array2 the array to realAdd\n     * @return A new array containing the existing elements and the new element\n     * @throws IndexOutOfBoundsException if the index is out of range\n     *                                   (index < 0 || index > array.length).\n     */\n    @Nullable\n    public static <T> T[] add(@Nullable T[] array1, int index, @Nullable T[] array2) {\n        Class clss;\n        if (array1 != null) {\n            clss = array1.getClass().getComponentType();\n        } else if (array2 != null) {\n            clss = array2.getClass().getComponentType();\n        } else {\n            return null;\n        }\n        return (T[]) realAddArr(array1, index, array2, clss);\n    }\n\n    @Nullable\n    public static boolean[] add(@Nullable boolean[] array1, int index, @Nullable boolean[] array2) {\n        Object result = realAddArr(array1, index, array2, Boolean.TYPE);\n        if (result == null) return null;\n        return (boolean[]) result;\n    }\n\n    public static char[] add(@Nullable char[] array1, int index, @Nullable char[] array2) {\n        Object result = realAddArr(array1, index, array2, Character.TYPE);\n        if (result == null) return null;\n        return (char[]) result;\n    }\n\n    @Nullable\n    public static byte[] add(@Nullable byte[] array1, int index, @Nullable byte[] array2) {\n        Object result = realAddArr(array1, index, array2, Byte.TYPE);\n        if (result == null) return null;\n        return (byte[]) result;\n    }\n\n    @Nullable\n    public static short[] add(@Nullable short[] array1, int index, @Nullable short[] array2) {\n        Object result = realAddArr(array1, index, array2, Short.TYPE);\n        if (result == null) return null;\n        return (short[]) result;\n    }\n\n    @Nullable\n    public static int[] add(@Nullable int[] array1, int index, @Nullable int[] array2) {\n        Object result = realAddArr(array1, index, array2, Integer.TYPE);\n        if (result == null) return null;\n        return (int[]) result;\n    }\n\n    @Nullable\n    public static long[] add(@Nullable long[] array1, int index, @Nullable long[] array2) {\n        Object result = realAddArr(array1, index, array2, Long.TYPE);\n        if (result == null) return null;\n        return (long[]) result;\n    }\n\n    @Nullable\n    public static float[] add(@Nullable float[] array1, int index, @Nullable float[] array2) {\n        Object result = realAddArr(array1, index, array2, Float.TYPE);\n        if (result == null) return null;\n        return (float[]) result;\n    }\n\n    @Nullable\n    public static double[] add(@Nullable double[] array1, int index, @Nullable double[] array2) {\n        Object result = realAddArr(array1, index, array2, Double.TYPE);\n        if (result == null) return null;\n        return (double[]) result;\n    }\n\n    @Nullable\n    private static Object realAddArr(@Nullable Object array1, int index, @Nullable Object array2, Class clss) {\n        if (array1 == null && array2 == null) return null;\n        int len1 = getLength(array1);\n        int len2 = getLength(array2);\n        if (len1 == 0) {\n            if (index != 0) {\n                throw new IndexOutOfBoundsException(\"Index: \" + index + \", array1 Length: 0\");\n            }\n            return realCopy(array2);\n        }\n        if (len2 == 0) {\n            return realCopy(array1);\n        }\n        if (index > len1 || index < 0) {\n            throw new IndexOutOfBoundsException(\"Index: \" + index + \", array1 Length: \" + len1);\n        }\n        Object joinedArray = Array.newInstance(array1.getClass().getComponentType(), len1 + len2);\n        if (index == len1) {\n            System.arraycopy(array1, 0, joinedArray, 0, len1);\n            System.arraycopy(array2, 0, joinedArray, len1, len2);\n        } else if (index == 0) {\n            System.arraycopy(array2, 0, joinedArray, 0, len2);\n            System.arraycopy(array1, 0, joinedArray, len2, len1);\n        } else {\n            System.arraycopy(array1, 0, joinedArray, 0, index);\n            System.arraycopy(array2, 0, joinedArray, index, len2);\n            System.arraycopy(array1, index, joinedArray, index + len2, len1 - index);\n        }\n        return joinedArray;\n    }\n\n    /**\n     * <p>Inserts the specified element at the specified position in the array.\n     * Shifts the element currently at that position (if any) and any subsequent\n     * elements to the right (adds one to their indices).</p>\n     *\n     * <p>This method returns a new array with the same elements of the input\n     * array plus the given element on the specified position. The component\n     * type of the returned array is always the same as that of the input\n     * array.</p>\n     *\n     * <p>If the input array is <code>null</code>, a new one element array is returned\n     * whose component type is the same as the element.</p>\n     *\n     * <pre>\n     * ArrayUtils.add(null, 0, null)      = [null]\n     * ArrayUtils.add(null, 0, \"a\")       = [\"a\"]\n     * ArrayUtils.add([\"a\"], 1, null)     = [\"a\", null]\n     * ArrayUtils.add([\"a\"], 1, \"b\")      = [\"a\", \"b\"]\n     * ArrayUtils.add([\"a\", \"b\"], 3, \"c\") = [\"a\", \"b\", \"c\"]\n     * </pre>\n     *\n     * @param array   the array to realAdd the element to, may be <code>null</code>\n     * @param index   the position of the new object\n     * @param element the object to realAdd\n     * @return A new array containing the existing elements and the new element\n     * @throws IndexOutOfBoundsException if the index is out of range\n     *                                   (index < 0 || index > array.length).\n     */\n    @NonNull\n    public static <T> T[] add(@Nullable T[] array, int index, @Nullable T element) {\n        Class clss;\n        if (array != null) {\n            clss = array.getClass().getComponentType();\n        } else if (element != null) {\n            clss = element.getClass();\n        } else {\n            return (T[]) new Object[]{null};\n        }\n        return (T[]) realAdd(array, index, element, clss);\n    }\n\n    @NonNull\n    public static boolean[] add(@Nullable boolean[] array, int index, boolean element) {\n        return (boolean[]) realAdd(array, index, element, Boolean.TYPE);\n    }\n\n    @NonNull\n    public static char[] add(@Nullable char[] array, int index, char element) {\n        return (char[]) realAdd(array, index, element, Character.TYPE);\n    }\n\n    @NonNull\n    public static byte[] add(@Nullable byte[] array, int index, byte element) {\n        return (byte[]) realAdd(array, index, element, Byte.TYPE);\n    }\n\n    @NonNull\n    public static short[] add(@Nullable short[] array, int index, short element) {\n        return (short[]) realAdd(array, index, element, Short.TYPE);\n    }\n\n    @NonNull\n    public static int[] add(@Nullable int[] array, int index, int element) {\n        return (int[]) realAdd(array, index, element, Integer.TYPE);\n    }\n\n    @NonNull\n    public static long[] add(@Nullable long[] array, int index, long element) {\n        return (long[]) realAdd(array, index, element, Long.TYPE);\n    }\n\n    @NonNull\n    public static float[] add(@Nullable float[] array, int index, float element) {\n        return (float[]) realAdd(array, index, element, Float.TYPE);\n    }\n\n    @NonNull\n    public static double[] add(@Nullable double[] array, int index, double element) {\n        return (double[]) realAdd(array, index, element, Double.TYPE);\n    }\n\n    @NonNull\n    private static Object realAdd(@Nullable Object array, int index, @Nullable Object element, Class clss) {\n        if (array == null) {\n            if (index != 0) {\n                throw new IndexOutOfBoundsException(\"Index: \" + index + \", Length: 0\");\n            }\n            Object joinedArray = Array.newInstance(clss, 1);\n            Array.set(joinedArray, 0, element);\n            return joinedArray;\n        }\n        int length = Array.getLength(array);\n        if (index > length || index < 0) {\n            throw new IndexOutOfBoundsException(\"Index: \" + index + \", Length: \" + length);\n        }\n        Object result = Array.newInstance(clss, length + 1);\n        System.arraycopy(array, 0, result, 0, index);\n        Array.set(result, index, element);\n        if (index < length) {\n            System.arraycopy(array, index, result, index + 1, length - index);\n        }\n        return result;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // remove\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * <p>Removes the element at the specified position from the specified array.\n     * All subsequent elements are shifted to the left (substracts one from\n     * their indices).</p>\n     *\n     * <p>This method returns a new array with the same elements of the input\n     * array except the element on the specified position. The component\n     * type of the returned array is always the same as that of the input\n     * array.</p>\n     *\n     * <p>If the input array is <code>null</code>, an IndexOutOfBoundsException\n     * will be thrown, because in that case no valid index can be specified.</p>\n     *\n     * <pre>\n     * ArrayUtils.remove([\"a\"], 0)           = []\n     * ArrayUtils.remove([\"a\", \"b\"], 0)      = [\"b\"]\n     * ArrayUtils.remove([\"a\", \"b\"], 1)      = [\"a\"]\n     * ArrayUtils.remove([\"a\", \"b\", \"c\"], 1) = [\"a\", \"c\"]\n     * </pre>\n     *\n     * @param array the array to remove the element from, may be <code>null</code>\n     * @param index the position of the element to be removed\n     * @return A new array containing the existing elements except the element\n     * at the specified position.\n     * @throws IndexOutOfBoundsException if the index is out of range\n     *                                   (index < 0 || index >= array.length)\n     */\n    @Nullable\n    public static Object[] remove(@Nullable Object[] array, int index) {\n        if (array == null) return null;\n        return (Object[]) remove((Object) array, index);\n    }\n\n    /**\n     * <p>Removes the first occurrence of the specified element from the\n     * specified array. All subsequent elements are shifted to the left\n     * (substracts one from their indices). If the array doesn't contains\n     * such an element, no elements are removed from the array.</p>\n     *\n     * <p>This method returns a new array with the same elements of the input\n     * array except the first occurrence of the specified element. The component\n     * type of the returned array is always the same as that of the input\n     * array.</p>\n     *\n     * <pre>\n     * ArrayUtils.removeElement(null, \"a\")            = null\n     * ArrayUtils.removeElement([], \"a\")              = []\n     * ArrayUtils.removeElement([\"a\"], \"b\")           = [\"a\"]\n     * ArrayUtils.removeElement([\"a\", \"b\"], \"a\")      = [\"b\"]\n     * ArrayUtils.removeElement([\"a\", \"b\", \"a\"], \"a\") = [\"b\", \"a\"]\n     * </pre>\n     *\n     * @param array   the array to remove the element from, may be <code>null</code>\n     * @param element the element to be removed\n     * @return A new array containing the existing elements except the first\n     * occurrence of the specified element.\n     */\n    @Nullable\n    public static Object[] removeElement(@Nullable Object[] array, @Nullable Object element) {\n        int index = indexOf(array, element);\n        if (index == INDEX_NOT_FOUND) {\n            return copy(array);\n        }\n        return remove(array, index);\n    }\n\n    @Nullable\n    public static boolean[] remove(@Nullable boolean[] array, int index) {\n        if (array == null) return null;\n        return (boolean[]) remove((Object) array, index);\n    }\n\n    @Nullable\n    public static boolean[] removeElement(@Nullable boolean[] array, boolean element) {\n        int index = indexOf(array, element);\n        if (index == INDEX_NOT_FOUND) {\n            return copy(array);\n        }\n        return remove(array, index);\n    }\n\n    @Nullable\n    public static byte[] remove(@Nullable byte[] array, int index) {\n        if (array == null) return null;\n        return (byte[]) remove((Object) array, index);\n    }\n\n    @Nullable\n    public static byte[] removeElement(@Nullable byte[] array, byte element) {\n        int index = indexOf(array, element);\n        if (index == INDEX_NOT_FOUND) {\n            return copy(array);\n        }\n        return remove(array, index);\n    }\n\n    @Nullable\n    public static char[] remove(@Nullable char[] array, int index) {\n        if (array == null) return null;\n        return (char[]) remove((Object) array, index);\n    }\n\n    @Nullable\n    public static char[] removeElement(@Nullable char[] array, char element) {\n        int index = indexOf(array, element);\n        if (index == INDEX_NOT_FOUND) {\n            return copy(array);\n        }\n        return remove(array, index);\n    }\n\n    @Nullable\n    public static double[] remove(@Nullable double[] array, int index) {\n        if (array == null) return null;\n        return (double[]) remove((Object) array, index);\n    }\n\n    @Nullable\n    public static double[] removeElement(@Nullable double[] array, double element) {\n        int index = indexOf(array, element);\n        if (index == INDEX_NOT_FOUND) {\n            return copy(array);\n        }\n        //noinspection ConstantConditions\n        return remove(array, index);\n    }\n\n    @Nullable\n    public static float[] remove(@Nullable float[] array, int index) {\n        if (array == null) return null;\n        return (float[]) remove((Object) array, index);\n    }\n\n    @Nullable\n    public static float[] removeElement(@Nullable float[] array, float element) {\n        int index = indexOf(array, element);\n        if (index == INDEX_NOT_FOUND) {\n            return copy(array);\n        }\n        return remove(array, index);\n    }\n\n    @Nullable\n    public static int[] remove(@Nullable int[] array, int index) {\n        if (array == null) return null;\n        return (int[]) remove((Object) array, index);\n    }\n\n    @Nullable\n    public static int[] removeElement(@Nullable int[] array, int element) {\n        int index = indexOf(array, element);\n        if (index == INDEX_NOT_FOUND) {\n            return copy(array);\n        }\n        return remove(array, index);\n    }\n\n    @Nullable\n    public static long[] remove(@Nullable long[] array, int index) {\n        if (array == null) return null;\n        return (long[]) remove((Object) array, index);\n    }\n\n    @Nullable\n    public static long[] removeElement(@Nullable long[] array, long element) {\n        int index = indexOf(array, element);\n        if (index == INDEX_NOT_FOUND) {\n            return copy(array);\n        }\n        return remove(array, index);\n    }\n\n    @Nullable\n    public static short[] remove(@Nullable short[] array, int index) {\n        if (array == null) return null;\n        return (short[]) remove((Object) array, index);\n    }\n\n    @Nullable\n    public static short[] removeElement(@Nullable short[] array, short element) {\n        int index = indexOf(array, element);\n        if (index == INDEX_NOT_FOUND) {\n            return copy(array);\n        }\n        return remove(array, index);\n    }\n\n    @NonNull\n    private static Object remove(@NonNull Object array, int index) {\n        int length = getLength(array);\n        if (index < 0 || index >= length) {\n            throw new IndexOutOfBoundsException(\"Index: \" + index + \", Length: \" + length);\n        }\n\n        Object result = Array.newInstance(array.getClass().getComponentType(), length - 1);\n        System.arraycopy(array, 0, result, 0, index);\n        if (index < length - 1) {\n            System.arraycopy(array, index + 1, result, index, length - index - 1);\n        }\n\n        return result;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // object indexOf\n    ///////////////////////////////////////////////////////////////////////////\n\n    public static int indexOf(@Nullable Object[] array, @Nullable Object objectToFind) {\n        return indexOf(array, objectToFind, 0);\n    }\n\n    public static int indexOf(@Nullable Object[] array, @Nullable final Object objectToFind, int startIndex) {\n        if (array == null) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            startIndex = 0;\n        }\n        if (objectToFind == null) {\n            for (int i = startIndex; i < array.length; i++) {\n                if (array[i] == null) {\n                    return i;\n                }\n            }\n        } else {\n            for (int i = startIndex; i < array.length; i++) {\n                if (objectToFind.equals(array[i])) {\n                    return i;\n                }\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static int lastIndexOf(@Nullable Object[] array, @Nullable Object objectToFind) {\n        return lastIndexOf(array, objectToFind, Integer.MAX_VALUE);\n    }\n\n    public static int lastIndexOf(@Nullable Object[] array, @Nullable Object objectToFind, int startIndex) {\n        if (array == null) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            return INDEX_NOT_FOUND;\n        } else if (startIndex >= array.length) {\n            startIndex = array.length - 1;\n        }\n        if (objectToFind == null) {\n            for (int i = startIndex; i >= 0; i--) {\n                if (array[i] == null) {\n                    return i;\n                }\n            }\n        } else {\n            for (int i = startIndex; i >= 0; i--) {\n                if (objectToFind.equals(array[i])) {\n                    return i;\n                }\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static boolean contains(@Nullable Object[] array, @Nullable Object objectToFind) {\n        return indexOf(array, objectToFind) != INDEX_NOT_FOUND;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // long indexOf\n    ///////////////////////////////////////////////////////////////////////////\n\n    public static int indexOf(@Nullable long[] array, long valueToFind) {\n        return indexOf(array, valueToFind, 0);\n    }\n\n    public static int indexOf(@Nullable long[] array, long valueToFind, int startIndex) {\n        if (array == null) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            startIndex = 0;\n        }\n        for (int i = startIndex; i < array.length; i++) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static int lastIndexOf(@Nullable long[] array, long valueToFind) {\n        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);\n    }\n\n    public static int lastIndexOf(@Nullable long[] array, long valueToFind, int startIndex) {\n        if (array == null) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            return INDEX_NOT_FOUND;\n        } else if (startIndex >= array.length) {\n            startIndex = array.length - 1;\n        }\n        for (int i = startIndex; i >= 0; i--) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static boolean contains(@Nullable long[] array, long valueToFind) {\n        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // int indexOf\n    ///////////////////////////////////////////////////////////////////////////\n\n    public static int indexOf(@Nullable int[] array, int valueToFind) {\n        return indexOf(array, valueToFind, 0);\n    }\n\n    public static int indexOf(@Nullable int[] array, int valueToFind, int startIndex) {\n        if (array == null) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            startIndex = 0;\n        }\n        for (int i = startIndex; i < array.length; i++) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static int lastIndexOf(@Nullable int[] array, int valueToFind) {\n        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);\n    }\n\n    public static int lastIndexOf(@Nullable int[] array, int valueToFind, int startIndex) {\n        if (array == null) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            return INDEX_NOT_FOUND;\n        } else if (startIndex >= array.length) {\n            startIndex = array.length - 1;\n        }\n        for (int i = startIndex; i >= 0; i--) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static boolean contains(@Nullable int[] array, int valueToFind) {\n        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // short indexOf\n    ///////////////////////////////////////////////////////////////////////////\n\n    public static int indexOf(@Nullable short[] array, short valueToFind) {\n        return indexOf(array, valueToFind, 0);\n    }\n\n    public static int indexOf(@Nullable short[] array, short valueToFind, int startIndex) {\n        if (array == null) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            startIndex = 0;\n        }\n        for (int i = startIndex; i < array.length; i++) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static int lastIndexOf(@Nullable short[] array, short valueToFind) {\n        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);\n    }\n\n    public static int lastIndexOf(@Nullable short[] array, short valueToFind, int startIndex) {\n        if (array == null) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            return INDEX_NOT_FOUND;\n        } else if (startIndex >= array.length) {\n            startIndex = array.length - 1;\n        }\n        for (int i = startIndex; i >= 0; i--) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static boolean contains(@Nullable short[] array, short valueToFind) {\n        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // char indexOf\n    ///////////////////////////////////////////////////////////////////////////\n\n    public static int indexOf(@Nullable char[] array, char valueToFind) {\n        return indexOf(array, valueToFind, 0);\n    }\n\n    public static int indexOf(@Nullable char[] array, char valueToFind, int startIndex) {\n        if (array == null) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            startIndex = 0;\n        }\n        for (int i = startIndex; i < array.length; i++) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static int lastIndexOf(@Nullable char[] array, char valueToFind) {\n        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);\n    }\n\n    public static int lastIndexOf(@Nullable char[] array, char valueToFind, int startIndex) {\n        if (array == null) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            return INDEX_NOT_FOUND;\n        } else if (startIndex >= array.length) {\n            startIndex = array.length - 1;\n        }\n        for (int i = startIndex; i >= 0; i--) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static boolean contains(@Nullable char[] array, char valueToFind) {\n        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // byte indexOf\n    ///////////////////////////////////////////////////////////////////////////\n\n    public static int indexOf(@Nullable byte[] array, byte valueToFind) {\n        return indexOf(array, valueToFind, 0);\n    }\n\n    public static int indexOf(@Nullable byte[] array, byte valueToFind, int startIndex) {\n        if (array == null) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            startIndex = 0;\n        }\n        for (int i = startIndex; i < array.length; i++) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static int lastIndexOf(@Nullable byte[] array, byte valueToFind) {\n        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);\n    }\n\n    public static int lastIndexOf(@Nullable byte[] array, byte valueToFind, int startIndex) {\n        if (array == null) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            return INDEX_NOT_FOUND;\n        } else if (startIndex >= array.length) {\n            startIndex = array.length - 1;\n        }\n        for (int i = startIndex; i >= 0; i--) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static boolean contains(@Nullable byte[] array, byte valueToFind) {\n        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // double indexOf\n    ///////////////////////////////////////////////////////////////////////////\n\n    public static int indexOf(@Nullable double[] array, double valueToFind) {\n        return indexOf(array, valueToFind, 0);\n    }\n\n    public static int indexOf(@Nullable double[] array, double valueToFind, double tolerance) {\n        return indexOf(array, valueToFind, 0, tolerance);\n    }\n\n    public static int indexOf(@Nullable double[] array, double valueToFind, int startIndex) {\n        if (ArrayUtils.isEmpty(array)) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            startIndex = 0;\n        }\n        for (int i = startIndex; i < array.length; i++) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static int indexOf(@Nullable double[] array, double valueToFind, int startIndex, double tolerance) {\n        if (ArrayUtils.isEmpty(array)) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            startIndex = 0;\n        }\n        double min = valueToFind - tolerance;\n        double max = valueToFind + tolerance;\n        for (int i = startIndex; i < array.length; i++) {\n            if (array[i] >= min && array[i] <= max) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static int lastIndexOf(@Nullable double[] array, double valueToFind) {\n        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);\n    }\n\n    public static int lastIndexOf(@Nullable double[] array, double valueToFind, double tolerance) {\n        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE, tolerance);\n    }\n\n    public static int lastIndexOf(@Nullable double[] array, double valueToFind, int startIndex) {\n        if (ArrayUtils.isEmpty(array)) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            return INDEX_NOT_FOUND;\n        } else if (startIndex >= array.length) {\n            startIndex = array.length - 1;\n        }\n        for (int i = startIndex; i >= 0; i--) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static int lastIndexOf(@Nullable double[] array, double valueToFind, int startIndex, double tolerance) {\n        if (ArrayUtils.isEmpty(array)) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            return INDEX_NOT_FOUND;\n        } else if (startIndex >= array.length) {\n            startIndex = array.length - 1;\n        }\n        double min = valueToFind - tolerance;\n        double max = valueToFind + tolerance;\n        for (int i = startIndex; i >= 0; i--) {\n            if (array[i] >= min && array[i] <= max) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static boolean contains(@Nullable double[] array, double valueToFind) {\n        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;\n    }\n\n    public static boolean contains(@Nullable double[] array, double valueToFind, double tolerance) {\n        return indexOf(array, valueToFind, 0, tolerance) != INDEX_NOT_FOUND;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // float indexOf\n    ///////////////////////////////////////////////////////////////////////////\n\n    public static int indexOf(@Nullable float[] array, float valueToFind) {\n        return indexOf(array, valueToFind, 0);\n    }\n\n    public static int indexOf(@Nullable float[] array, float valueToFind, int startIndex) {\n        if (ArrayUtils.isEmpty(array)) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            startIndex = 0;\n        }\n        for (int i = startIndex; i < array.length; i++) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static int lastIndexOf(@Nullable float[] array, float valueToFind) {\n        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);\n    }\n\n    public static int lastIndexOf(@Nullable float[] array, float valueToFind, int startIndex) {\n        if (ArrayUtils.isEmpty(array)) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            return INDEX_NOT_FOUND;\n        } else if (startIndex >= array.length) {\n            startIndex = array.length - 1;\n        }\n        for (int i = startIndex; i >= 0; i--) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static boolean contains(@Nullable float[] array, float valueToFind) {\n        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // bool indexOf\n    ///////////////////////////////////////////////////////////////////////////\n\n    public static int indexOf(@Nullable boolean[] array, boolean valueToFind) {\n        return indexOf(array, valueToFind, 0);\n    }\n\n    public static int indexOf(@Nullable boolean[] array, boolean valueToFind, int startIndex) {\n        if (ArrayUtils.isEmpty(array)) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            startIndex = 0;\n        }\n        for (int i = startIndex; i < array.length; i++) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static int lastIndexOf(@Nullable boolean[] array, boolean valueToFind) {\n        return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);\n    }\n\n    public static int lastIndexOf(@Nullable boolean[] array, boolean valueToFind, int startIndex) {\n        if (ArrayUtils.isEmpty(array)) {\n            return INDEX_NOT_FOUND;\n        }\n        if (startIndex < 0) {\n            return INDEX_NOT_FOUND;\n        } else if (startIndex >= array.length) {\n            startIndex = array.length - 1;\n        }\n        for (int i = startIndex; i >= 0; i--) {\n            if (valueToFind == array[i]) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public static boolean contains(@Nullable boolean[] array, boolean valueToFind) {\n        return indexOf(array, valueToFind) != INDEX_NOT_FOUND;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // char converters\n    ///////////////////////////////////////////////////////////////////////////\n\n    @Nullable\n    public static char[] toPrimitive(@Nullable Character[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new char[0];\n        }\n        final char[] result = new char[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = array[i].charValue();\n        }\n        return result;\n    }\n\n    @Nullable\n    public static char[] toPrimitive(@Nullable Character[] array, char valueForNull) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new char[0];\n        }\n        final char[] result = new char[array.length];\n        for (int i = 0; i < array.length; i++) {\n            Character b = array[i];\n            result[i] = (b == null ? valueForNull : b.charValue());\n        }\n        return result;\n    }\n\n    @Nullable\n    public static Character[] toObject(@Nullable char[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new Character[0];\n        }\n        final Character[] result = new Character[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = new Character(array[i]);\n        }\n        return result;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // long converters\n    ///////////////////////////////////////////////////////////////////////////\n\n    @Nullable\n    public static long[] toPrimitive(@Nullable Long[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new long[0];\n        }\n        final long[] result = new long[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = array[i].longValue();\n        }\n        return result;\n    }\n\n    @Nullable\n    public static long[] toPrimitive(@Nullable Long[] array, long valueForNull) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new long[0];\n        }\n        final long[] result = new long[array.length];\n        for (int i = 0; i < array.length; i++) {\n            Long b = array[i];\n            result[i] = (b == null ? valueForNull : b.longValue());\n        }\n        return result;\n    }\n\n    @Nullable\n    public static Long[] toObject(@Nullable long[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new Long[0];\n        }\n        final Long[] result = new Long[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = new Long(array[i]);\n        }\n        return result;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // int converters\n    ///////////////////////////////////////////////////////////////////////////\n\n    @Nullable\n    public static int[] toPrimitive(@Nullable Integer[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new int[0];\n        }\n        final int[] result = new int[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = array[i].intValue();\n        }\n        return result;\n    }\n\n    @Nullable\n    public static int[] toPrimitive(@Nullable Integer[] array, int valueForNull) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new int[0];\n        }\n        final int[] result = new int[array.length];\n        for (int i = 0; i < array.length; i++) {\n            Integer b = array[i];\n            result[i] = (b == null ? valueForNull : b.intValue());\n        }\n        return result;\n    }\n\n    @Nullable\n    public static Integer[] toObject(@Nullable int[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new Integer[0];\n        }\n        final Integer[] result = new Integer[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = new Integer(array[i]);\n        }\n        return result;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // short converters\n    ///////////////////////////////////////////////////////////////////////////\n\n    @Nullable\n    public static short[] toPrimitive(@Nullable Short[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new short[0];\n        }\n        final short[] result = new short[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = array[i].shortValue();\n        }\n        return result;\n    }\n\n    @Nullable\n    public static short[] toPrimitive(@Nullable Short[] array, short valueForNull) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new short[0];\n        }\n        final short[] result = new short[array.length];\n        for (int i = 0; i < array.length; i++) {\n            Short b = array[i];\n            result[i] = (b == null ? valueForNull : b.shortValue());\n        }\n        return result;\n    }\n\n    @Nullable\n    public static Short[] toObject(@Nullable short[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new Short[0];\n        }\n        final Short[] result = new Short[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = new Short(array[i]);\n        }\n        return result;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // byte converters\n    ///////////////////////////////////////////////////////////////////////////\n\n    @Nullable\n    public static byte[] toPrimitive(@Nullable Byte[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new byte[0];\n        }\n        final byte[] result = new byte[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = array[i].byteValue();\n        }\n        return result;\n    }\n\n    @Nullable\n    public static byte[] toPrimitive(@Nullable Byte[] array, byte valueForNull) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new byte[0];\n        }\n        final byte[] result = new byte[array.length];\n        for (int i = 0; i < array.length; i++) {\n            Byte b = array[i];\n            result[i] = (b == null ? valueForNull : b.byteValue());\n        }\n        return result;\n    }\n\n    @Nullable\n    public static Byte[] toObject(@Nullable byte[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new Byte[0];\n        }\n        final Byte[] result = new Byte[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = new Byte(array[i]);\n        }\n        return result;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // double converters\n    ///////////////////////////////////////////////////////////////////////////\n\n    @Nullable\n    public static double[] toPrimitive(@Nullable Double[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new double[0];\n        }\n        final double[] result = new double[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = array[i].doubleValue();\n        }\n        return result;\n    }\n\n    @Nullable\n    public static double[] toPrimitive(@Nullable Double[] array, double valueForNull) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new double[0];\n        }\n        final double[] result = new double[array.length];\n        for (int i = 0; i < array.length; i++) {\n            Double b = array[i];\n            result[i] = (b == null ? valueForNull : b.doubleValue());\n        }\n        return result;\n    }\n\n    @Nullable\n    public static Double[] toObject(@Nullable double[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new Double[0];\n        }\n        final Double[] result = new Double[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = new Double(array[i]);\n        }\n        return result;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // float converters\n    ///////////////////////////////////////////////////////////////////////////\n\n    @Nullable\n    public static float[] toPrimitive(@Nullable Float[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new float[0];\n        }\n        final float[] result = new float[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = array[i].floatValue();\n        }\n        return result;\n    }\n\n    @Nullable\n    public static float[] toPrimitive(@Nullable Float[] array, float valueForNull) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new float[0];\n        }\n        final float[] result = new float[array.length];\n        for (int i = 0; i < array.length; i++) {\n            Float b = array[i];\n            result[i] = (b == null ? valueForNull : b.floatValue());\n        }\n        return result;\n    }\n\n    @Nullable\n    public static Float[] toObject(@Nullable float[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new Float[0];\n        }\n        final Float[] result = new Float[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = new Float(array[i]);\n        }\n        return result;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // boolean converters\n    ///////////////////////////////////////////////////////////////////////////\n\n    @Nullable\n    public static boolean[] toPrimitive(@Nullable Boolean[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new boolean[0];\n        }\n        final boolean[] result = new boolean[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = array[i].booleanValue();\n        }\n        return result;\n    }\n\n    @Nullable\n    public static boolean[] toPrimitive(@Nullable Boolean[] array, boolean valueForNull) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new boolean[0];\n        }\n        final boolean[] result = new boolean[array.length];\n        for (int i = 0; i < array.length; i++) {\n            Boolean b = array[i];\n            result[i] = (b == null ? valueForNull : b.booleanValue());\n        }\n        return result;\n    }\n\n    @Nullable\n    public static Boolean[] toObject(@Nullable boolean[] array) {\n        if (array == null) {\n            return null;\n        } else if (array.length == 0) {\n            return new Boolean[0];\n        }\n        final Boolean[] result = new Boolean[array.length];\n        for (int i = 0; i < array.length; i++) {\n            result[i] = (array[i] ? Boolean.TRUE : Boolean.FALSE);\n        }\n        return result;\n    }\n\n    @NonNull\n    public static <T> List<T> asList(@Nullable T... array) {\n        if (array == null || array.length == 0) {\n            return Collections.emptyList();\n        }\n        return Arrays.asList(array);\n    }\n\n    @NonNull\n    public static <T> List<T> asUnmodifiableList(@Nullable T... array) {\n        return Collections.unmodifiableList(asList(array));\n    }\n\n    @NonNull\n    public static <T> List<T> asArrayList(@Nullable T... array) {\n        List<T> list = new ArrayList<>();\n        if (array == null || array.length == 0) return list;\n        list.addAll(Arrays.asList(array));\n        return list;\n    }\n\n    @NonNull\n    public static <T> List<T> asLinkedList(@Nullable T... array) {\n        List<T> list = new LinkedList<>();\n        if (array == null || array.length == 0) return list;\n        list.addAll(Arrays.asList(array));\n        return list;\n    }\n\n    public static <T> void sort(@Nullable T[] array, Comparator<? super T> c) {\n        if (array == null || array.length < 2) return;\n        Arrays.sort(array, c);\n    }\n\n    public static void sort(@Nullable byte[] array) {\n        if (array == null || array.length < 2) return;\n        Arrays.sort(array);\n    }\n\n    public static void sort(@Nullable char[] array) {\n        if (array == null || array.length < 2) return;\n        Arrays.sort(array);\n    }\n\n    public static void sort(@Nullable double[] array) {\n        if (array == null || array.length < 2) return;\n        Arrays.sort(array);\n    }\n\n    public static void sort(@Nullable float[] array) {\n        if (array == null || array.length < 2) return;\n        Arrays.sort(array);\n    }\n\n    public static void sort(@Nullable int[] array) {\n        if (array == null || array.length < 2) return;\n        Arrays.sort(array);\n    }\n\n    public static void sort(@Nullable long[] array) {\n        if (array == null || array.length < 2) return;\n        Arrays.sort(array);\n    }\n\n    public static void sort(@Nullable short[] array) {\n        if (array == null || array.length < 2) return;\n        Arrays.sort(array);\n    }\n\n    /**\n     * Executes the given closure on each element in the array.\n     * <p>\n     * If the input array or closure is null, there is no change made.\n     *\n     * @param array   The array.\n     * @param closure the closure to perform, may be null\n     */\n    public static <E> void forAllDo(@Nullable Object array, @Nullable Closure<E> closure) {\n        if (array == null || closure == null) return;\n        if (array instanceof Object[]) {\n            Object[] objects = (Object[]) array;\n            for (int i = 0, length = objects.length; i < length; i++) {\n                Object ele = objects[i];\n                closure.execute(i, (E) ele);\n            }\n        } else if (array instanceof boolean[]) {\n            boolean[] booleans = (boolean[]) array;\n            for (int i = 0, length = booleans.length; i < length; i++) {\n                boolean ele = booleans[i];\n                closure.execute(i, (E) (ele ? Boolean.TRUE : Boolean.FALSE));\n            }\n        } else if (array instanceof byte[]) {\n            byte[] bytes = (byte[]) array;\n            for (int i = 0, length = bytes.length; i < length; i++) {\n                byte ele = bytes[i];\n                closure.execute(i, (E) Byte.valueOf(ele));\n            }\n        } else if (array instanceof char[]) {\n            char[] chars = (char[]) array;\n            for (int i = 0, length = chars.length; i < length; i++) {\n                char ele = chars[i];\n                closure.execute(i, (E) Character.valueOf(ele));\n            }\n        } else if (array instanceof short[]) {\n            short[] shorts = (short[]) array;\n            for (int i = 0, length = shorts.length; i < length; i++) {\n                short ele = shorts[i];\n                closure.execute(i, (E) Short.valueOf(ele));\n            }\n        } else if (array instanceof int[]) {\n            int[] ints = (int[]) array;\n            for (int i = 0, length = ints.length; i < length; i++) {\n                int ele = ints[i];\n                closure.execute(i, (E) Integer.valueOf(ele));\n            }\n        } else if (array instanceof long[]) {\n            long[] longs = (long[]) array;\n            for (int i = 0, length = longs.length; i < length; i++) {\n                long ele = longs[i];\n                closure.execute(i, (E) Long.valueOf(ele));\n            }\n        } else if (array instanceof float[]) {\n            float[] floats = (float[]) array;\n            for (int i = 0, length = floats.length; i < length; i++) {\n                float ele = floats[i];\n                closure.execute(i, (E) Float.valueOf(ele));\n            }\n        } else if (array instanceof double[]) {\n            double[] doubles = (double[]) array;\n            for (int i = 0, length = doubles.length; i < length; i++) {\n                double ele = doubles[i];\n                closure.execute(i, (E) Double.valueOf(ele));\n            }\n        } else {\n            throw new IllegalArgumentException(\"Not an array: \" + array.getClass());\n        }\n    }\n\n    /**\n     * Return the string of array.\n     *\n     * @param array The array.\n     * @return the string of array\n     */\n    @NonNull\n    public static String toString(@Nullable Object array) {\n        if (array == null) return \"null\";\n        if (array instanceof Object[]) {\n            return Arrays.deepToString((Object[]) array);\n        } else if (array instanceof boolean[]) {\n            return Arrays.toString((boolean[]) array);\n        } else if (array instanceof byte[]) {\n            return Arrays.toString((byte[]) array);\n        } else if (array instanceof char[]) {\n            return Arrays.toString((char[]) array);\n        } else if (array instanceof double[]) {\n            return Arrays.toString((double[]) array);\n        } else if (array instanceof float[]) {\n            return Arrays.toString((float[]) array);\n        } else if (array instanceof int[]) {\n            return Arrays.toString((int[]) array);\n        } else if (array instanceof long[]) {\n            return Arrays.toString((long[]) array);\n        } else if (array instanceof short[]) {\n            return Arrays.toString((short[]) array);\n        }\n        throw new IllegalArgumentException(\"Array has incompatible type: \" + array.getClass());\n    }\n\n    public interface Closure<E> {\n        void execute(int index, E item);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/BarUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Color;\nimport android.graphics.Point;\nimport android.os.Build;\nimport android.provider.Settings;\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport androidx.annotation.RequiresPermission;\nimport androidx.drawerlayout.widget.DrawerLayout;\nimport android.util.TypedValue;\nimport android.view.Display;\nimport android.view.KeyCharacterMap;\nimport android.view.KeyEvent;\nimport android.view.View;\nimport android.view.ViewConfiguration;\nimport android.view.ViewGroup;\nimport android.view.ViewGroup.MarginLayoutParams;\nimport android.view.Window;\nimport android.view.WindowManager;\n\nimport java.lang.reflect.Method;\n\nimport static android.Manifest.permission.EXPAND_STATUS_BAR;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/09/23\n *     desc  : utils about bar\n * </pre>\n */\npublic final class BarUtils {\n\n    ///////////////////////////////////////////////////////////////////////////\n    // status bar\n    ///////////////////////////////////////////////////////////////////////////\n\n    private static final String TAG_STATUS_BAR = \"TAG_STATUS_BAR\";\n    private static final String TAG_OFFSET     = \"TAG_OFFSET\";\n    private static final int    KEY_OFFSET     = -123;\n\n    private BarUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return the status bar's height.\n     *\n     * @return the status bar's height\n     */\n    public static int getStatusBarHeight() {\n        Resources resources = Utils.getApp().getResources();\n        int resourceId = resources.getIdentifier(\"status_bar_height\", \"dimen\", \"android\");\n        return resources.getDimensionPixelSize(resourceId);\n    }\n\n    /**\n     * Set the status bar's visibility.\n     *\n     * @param activity  The activity.\n     * @param isVisible True to set status bar visible, false otherwise.\n     */\n    public static void setStatusBarVisibility(@NonNull final Activity activity,\n                                              final boolean isVisible) {\n        setStatusBarVisibility(activity.getWindow(), isVisible);\n    }\n\n    /**\n     * Set the status bar's visibility.\n     *\n     * @param window    The window.\n     * @param isVisible True to set status bar visible, false otherwise.\n     */\n    public static void setStatusBarVisibility(@NonNull final Window window,\n                                              final boolean isVisible) {\n        if (isVisible) {\n            window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n            showStatusBarView(window);\n            addMarginTopEqualStatusBarHeight(window);\n        } else {\n            window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n            hideStatusBarView(window);\n            subtractMarginTopEqualStatusBarHeight(window);\n        }\n    }\n\n    /**\n     * Return whether the status bar is visible.\n     *\n     * @param activity The activity.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isStatusBarVisible(@NonNull final Activity activity) {\n        int flags = activity.getWindow().getAttributes().flags;\n        return (flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0;\n    }\n\n    /**\n     * Set the status bar's light mode.\n     *\n     * @param activity    The activity.\n     * @param isLightMode True to set status bar light mode, false otherwise.\n     */\n    public static void setStatusBarLightMode(@NonNull final Activity activity,\n                                             final boolean isLightMode) {\n        setStatusBarLightMode(activity.getWindow(), isLightMode);\n    }\n\n    /**\n     * Set the status bar's light mode.\n     *\n     * @param window      The window.\n     * @param isLightMode True to set status bar light mode, false otherwise.\n     */\n    public static void setStatusBarLightMode(@NonNull final Window window,\n                                             final boolean isLightMode) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            View decorView = window.getDecorView();\n            int vis = decorView.getSystemUiVisibility();\n            if (isLightMode) {\n                vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;\n            } else {\n                vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;\n            }\n            decorView.setSystemUiVisibility(vis);\n        }\n    }\n\n    /**\n     * Is the status bar light mode.\n     *\n     * @param activity The activity.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isStatusBarLightMode(@NonNull final Activity activity) {\n        return isStatusBarLightMode(activity.getWindow());\n    }\n\n    /**\n     * Is the status bar light mode.\n     *\n     * @param window The window.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isStatusBarLightMode(@NonNull final Window window) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            View decorView = window.getDecorView();\n            int vis = decorView.getSystemUiVisibility();\n            return (vis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0;\n        }\n        return false;\n    }\n\n    /**\n     * Add the top margin size equals status bar's height for view.\n     *\n     * @param view The view.\n     */\n    public static void addMarginTopEqualStatusBarHeight(@NonNull View view) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;\n        view.setTag(TAG_OFFSET);\n        Object haveSetOffset = view.getTag(KEY_OFFSET);\n        if (haveSetOffset != null && (Boolean) haveSetOffset) return;\n        MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();\n        layoutParams.setMargins(layoutParams.leftMargin,\n                layoutParams.topMargin + getStatusBarHeight(),\n                layoutParams.rightMargin,\n                layoutParams.bottomMargin);\n        view.setTag(KEY_OFFSET, true);\n    }\n\n    /**\n     * Subtract the top margin size equals status bar's height for view.\n     *\n     * @param view The view.\n     */\n    public static void subtractMarginTopEqualStatusBarHeight(@NonNull View view) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;\n        Object haveSetOffset = view.getTag(KEY_OFFSET);\n        if (haveSetOffset == null || !(Boolean) haveSetOffset) return;\n        MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();\n        layoutParams.setMargins(layoutParams.leftMargin,\n                layoutParams.topMargin - getStatusBarHeight(),\n                layoutParams.rightMargin,\n                layoutParams.bottomMargin);\n        view.setTag(KEY_OFFSET, false);\n    }\n\n    private static void addMarginTopEqualStatusBarHeight(@NonNull final Window window) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;\n        View withTag = window.getDecorView().findViewWithTag(TAG_OFFSET);\n        if (withTag == null) return;\n        addMarginTopEqualStatusBarHeight(withTag);\n    }\n\n    private static void subtractMarginTopEqualStatusBarHeight(@NonNull final Window window) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;\n        View withTag = window.getDecorView().findViewWithTag(TAG_OFFSET);\n        if (withTag == null) return;\n        subtractMarginTopEqualStatusBarHeight(withTag);\n    }\n\n    /**\n     * Set the status bar's color.\n     *\n     * @param activity The activity.\n     * @param color    The status bar's color.\n     */\n    public static View setStatusBarColor(@NonNull final Activity activity,\n                                         @ColorInt final int color) {\n        return setStatusBarColor(activity, color, false);\n    }\n\n    /**\n     * Set the status bar's color.\n     *\n     * @param activity The activity.\n     * @param color    The status bar's color.\n     * @param isDecor  True to add fake status bar in DecorView,\n     *                 false to add fake status bar in ContentView.\n     */\n    public static View setStatusBarColor(@NonNull final Activity activity,\n                                         @ColorInt final int color,\n                                         final boolean isDecor) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return null;\n        transparentStatusBar(activity);\n        return applyStatusBarColor(activity, color, isDecor);\n    }\n\n\n    /**\n     * Set the status bar's color.\n     *\n     * @param window The window.\n     * @param color  The status bar's color.\n     */\n    public static View setStatusBarColor(@NonNull final Window window,\n                                         @ColorInt final int color) {\n        return setStatusBarColor(window, color, false);\n    }\n\n    /**\n     * Set the status bar's color.\n     *\n     * @param window  The window.\n     * @param color   The status bar's color.\n     * @param isDecor True to add fake status bar in DecorView,\n     *                false to add fake status bar in ContentView.\n     */\n    public static View setStatusBarColor(@NonNull final Window window,\n                                         @ColorInt final int color,\n                                         final boolean isDecor) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return null;\n        transparentStatusBar(window);\n        return applyStatusBarColor(window, color, isDecor);\n    }\n\n    /**\n     * Set the status bar's color.\n     *\n     * @param fakeStatusBar The fake status bar view.\n     * @param color         The status bar's color.\n     */\n    public static void setStatusBarColor(@NonNull final View fakeStatusBar,\n                                         @ColorInt final int color) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;\n        Activity activity = UtilsBridge.getActivityByContext(fakeStatusBar.getContext());\n        if (activity == null) return;\n        transparentStatusBar(activity);\n        fakeStatusBar.setVisibility(View.VISIBLE);\n        ViewGroup.LayoutParams layoutParams = fakeStatusBar.getLayoutParams();\n        layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;\n        layoutParams.height = getStatusBarHeight();\n        fakeStatusBar.setBackgroundColor(color);\n    }\n\n    /**\n     * Set the custom status bar.\n     *\n     * @param fakeStatusBar The fake status bar view.\n     */\n    public static void setStatusBarCustom(@NonNull final View fakeStatusBar) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;\n        Activity activity = UtilsBridge.getActivityByContext(fakeStatusBar.getContext());\n        if (activity == null) return;\n        transparentStatusBar(activity);\n        fakeStatusBar.setVisibility(View.VISIBLE);\n        ViewGroup.LayoutParams layoutParams = fakeStatusBar.getLayoutParams();\n        if (layoutParams == null) {\n            layoutParams = new ViewGroup.LayoutParams(\n                    ViewGroup.LayoutParams.MATCH_PARENT,\n                    getStatusBarHeight()\n            );\n            fakeStatusBar.setLayoutParams(layoutParams);\n        } else {\n            layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;\n            layoutParams.height = getStatusBarHeight();\n        }\n    }\n\n    /**\n     * Set the status bar's color for DrawerLayout.\n     * <p>DrawLayout must add {@code android:fitsSystemWindows=\"true\"}</p>\n     *\n     * @param drawer        The DrawLayout.\n     * @param fakeStatusBar The fake status bar view.\n     * @param color         The status bar's color.\n     */\n    public static void setStatusBarColor4Drawer(@NonNull final DrawerLayout drawer,\n                                                @NonNull final View fakeStatusBar,\n                                                @ColorInt final int color) {\n        setStatusBarColor4Drawer(drawer, fakeStatusBar, color, false);\n    }\n\n    /**\n     * Set the status bar's color for DrawerLayout.\n     * <p>DrawLayout must add {@code android:fitsSystemWindows=\"true\"}</p>\n     *\n     * @param drawer        The DrawLayout.\n     * @param fakeStatusBar The fake status bar view.\n     * @param color         The status bar's color.\n     * @param isTop         True to set DrawerLayout at the top layer, false otherwise.\n     */\n    public static void setStatusBarColor4Drawer(@NonNull final DrawerLayout drawer,\n                                                @NonNull final View fakeStatusBar,\n                                                @ColorInt final int color,\n                                                final boolean isTop) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;\n        Activity activity = UtilsBridge.getActivityByContext(fakeStatusBar.getContext());\n        if (activity == null) return;\n        transparentStatusBar(activity);\n        drawer.setFitsSystemWindows(false);\n        setStatusBarColor(fakeStatusBar, color);\n        for (int i = 0, count = drawer.getChildCount(); i < count; i++) {\n            drawer.getChildAt(i).setFitsSystemWindows(false);\n        }\n        if (isTop) {\n            hideStatusBarView(activity);\n        } else {\n            setStatusBarColor(activity, color, false);\n        }\n    }\n\n    private static View applyStatusBarColor(@NonNull final Activity activity,\n                                            final int color,\n                                            boolean isDecor) {\n        return applyStatusBarColor(activity.getWindow(), color, isDecor);\n    }\n\n    private static View applyStatusBarColor(@NonNull final Window window,\n                                            final int color,\n                                            boolean isDecor) {\n        ViewGroup parent = isDecor ?\n                (ViewGroup) window.getDecorView() :\n                (ViewGroup) window.findViewById(android.R.id.content);\n        View fakeStatusBarView = parent.findViewWithTag(TAG_STATUS_BAR);\n        if (fakeStatusBarView != null) {\n            if (fakeStatusBarView.getVisibility() == View.GONE) {\n                fakeStatusBarView.setVisibility(View.VISIBLE);\n            }\n            fakeStatusBarView.setBackgroundColor(color);\n        } else {\n            fakeStatusBarView = createStatusBarView(window.getContext(), color);\n            parent.addView(fakeStatusBarView);\n        }\n        return fakeStatusBarView;\n    }\n\n    private static void hideStatusBarView(@NonNull final Activity activity) {\n        hideStatusBarView(activity.getWindow());\n    }\n\n    private static void hideStatusBarView(@NonNull final Window window) {\n        ViewGroup decorView = (ViewGroup) window.getDecorView();\n        View fakeStatusBarView = decorView.findViewWithTag(TAG_STATUS_BAR);\n        if (fakeStatusBarView == null) return;\n        fakeStatusBarView.setVisibility(View.GONE);\n    }\n\n    private static void showStatusBarView(@NonNull final Window window) {\n        ViewGroup decorView = (ViewGroup) window.getDecorView();\n        View fakeStatusBarView = decorView.findViewWithTag(TAG_STATUS_BAR);\n        if (fakeStatusBarView == null) return;\n        fakeStatusBarView.setVisibility(View.VISIBLE);\n    }\n\n    private static View createStatusBarView(@NonNull final Context context,\n                                            final int color) {\n        View statusBarView = new View(context);\n        statusBarView.setLayoutParams(new ViewGroup.LayoutParams(\n                ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight()));\n        statusBarView.setBackgroundColor(color);\n        statusBarView.setTag(TAG_STATUS_BAR);\n        return statusBarView;\n    }\n\n    public static void transparentStatusBar(@NonNull final Activity activity) {\n        transparentStatusBar(activity.getWindow());\n    }\n\n    public static void transparentStatusBar(@NonNull final Window window) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);\n            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);\n            int option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;\n            int vis = window.getDecorView().getSystemUiVisibility();\n            window.getDecorView().setSystemUiVisibility(option | vis);\n            window.setStatusBarColor(Color.TRANSPARENT);\n        } else {\n            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);\n        }\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // action bar\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Return the action bar's height.\n     *\n     * @return the action bar's height\n     */\n    public static int getActionBarHeight() {\n        TypedValue tv = new TypedValue();\n        if (Utils.getApp().getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {\n            return TypedValue.complexToDimensionPixelSize(\n                    tv.data, Utils.getApp().getResources().getDisplayMetrics()\n            );\n        }\n        return 0;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // notification bar\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Set the notification bar's visibility.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.EXPAND_STATUS_BAR\" />}</p>\n     *\n     * @param isVisible True to set notification bar visible, false otherwise.\n     */\n    @RequiresPermission(EXPAND_STATUS_BAR)\n    public static void setNotificationBarVisibility(final boolean isVisible) {\n        String methodName;\n        if (isVisible) {\n            methodName = (Build.VERSION.SDK_INT <= 16) ? \"expand\" : \"expandNotificationsPanel\";\n        } else {\n            methodName = (Build.VERSION.SDK_INT <= 16) ? \"collapse\" : \"collapsePanels\";\n        }\n        invokePanels(methodName);\n    }\n\n    private static void invokePanels(final String methodName) {\n        try {\n            @SuppressLint(\"WrongConstant\")\n            Object service = Utils.getApp().getSystemService(\"statusbar\");\n            @SuppressLint(\"PrivateApi\")\n            Class<?> statusBarManager = Class.forName(\"android.app.StatusBarManager\");\n            Method expand = statusBarManager.getMethod(methodName);\n            expand.invoke(service);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // navigation bar\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Return the navigation bar's height.\n     *\n     * @return the navigation bar's height\n     */\n    public static int getNavBarHeight() {\n        Resources res = Utils.getApp().getResources();\n        int resourceId = res.getIdentifier(\"navigation_bar_height\", \"dimen\", \"android\");\n        if (resourceId != 0) {\n            return res.getDimensionPixelSize(resourceId);\n        } else {\n            return 0;\n        }\n    }\n\n    /**\n     * Set the navigation bar's visibility.\n     *\n     * @param activity  The activity.\n     * @param isVisible True to set navigation bar visible, false otherwise.\n     */\n    public static void setNavBarVisibility(@NonNull final Activity activity, boolean isVisible) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;\n        setNavBarVisibility(activity.getWindow(), isVisible);\n\n    }\n\n    /**\n     * Set the navigation bar's visibility.\n     *\n     * @param window    The window.\n     * @param isVisible True to set navigation bar visible, false otherwise.\n     */\n    public static void setNavBarVisibility(@NonNull final Window window, boolean isVisible) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;\n        final ViewGroup decorView = (ViewGroup) window.getDecorView();\n        for (int i = 0, count = decorView.getChildCount(); i < count; i++) {\n            final View child = decorView.getChildAt(i);\n            final int id = child.getId();\n            if (id != View.NO_ID) {\n                String resourceEntryName = getResNameById(id);\n                if (\"navigationBarBackground\".equals(resourceEntryName)) {\n                    child.setVisibility(isVisible ? View.VISIBLE : View.INVISIBLE);\n                }\n            }\n        }\n        final int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION\n                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION\n                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;\n        if (isVisible) {\n            decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() & ~uiOptions);\n        } else {\n            decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | uiOptions);\n        }\n    }\n\n    /**\n     * Return whether the navigation bar visible.\n     * <p>Call it in onWindowFocusChanged will get right result.</p>\n     *\n     * @param activity The activity.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isNavBarVisible(@NonNull final Activity activity) {\n        return isNavBarVisible(activity.getWindow());\n    }\n\n    /**\n     * Return whether the navigation bar visible.\n     * <p>Call it in onWindowFocusChanged will get right result.</p>\n     *\n     * @param window The window.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isNavBarVisible(@NonNull final Window window) {\n        boolean isVisible = false;\n        ViewGroup decorView = (ViewGroup) window.getDecorView();\n        for (int i = 0, count = decorView.getChildCount(); i < count; i++) {\n            final View child = decorView.getChildAt(i);\n            final int id = child.getId();\n            if (id != View.NO_ID) {\n                String resourceEntryName = getResNameById(id);\n                if (\"navigationBarBackground\".equals(resourceEntryName)\n                        && child.getVisibility() == View.VISIBLE) {\n                    isVisible = true;\n                    break;\n                }\n            }\n        }\n        if (isVisible) {\n            // 对于三星手机，android10以下非OneUI2的版本，比如 s8，note8 等设备上，\n            // 导航栏显示存在bug：\"当用户隐藏导航栏时显示输入法的时候导航栏会跟随显示\"，会导致隐藏输入法之后判断错误\n            // 这个问题在 OneUI 2 & android 10 版本已修复\n            if (UtilsBridge.isSamsung()\n                    && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1\n                    && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {\n                try {\n                    return Settings.Global.getInt(Utils.getApp().getContentResolver(), \"navigationbar_hide_bar_enabled\") == 0;\n                } catch (Exception ignore) {\n                }\n            }\n\n            int visibility = decorView.getSystemUiVisibility();\n            isVisible = (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;\n        }\n\n        return isVisible;\n    }\n\n    private static String getResNameById(int id) {\n        try {\n            return Utils.getApp().getResources().getResourceEntryName(id);\n        } catch (Exception ignore) {\n            return \"\";\n        }\n    }\n\n    /**\n     * Set the navigation bar's color.\n     *\n     * @param activity The activity.\n     * @param color    The navigation bar's color.\n     */\n    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n    public static void setNavBarColor(@NonNull final Activity activity, @ColorInt final int color) {\n        setNavBarColor(activity.getWindow(), color);\n    }\n\n    /**\n     * Set the navigation bar's color.\n     *\n     * @param window The window.\n     * @param color  The navigation bar's color.\n     */\n    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n    public static void setNavBarColor(@NonNull final Window window, @ColorInt final int color) {\n        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);\n        window.setNavigationBarColor(color);\n    }\n\n    /**\n     * Return the color of navigation bar.\n     *\n     * @param activity The activity.\n     * @return the color of navigation bar\n     */\n    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n    public static int getNavBarColor(@NonNull final Activity activity) {\n        return getNavBarColor(activity.getWindow());\n    }\n\n    /**\n     * Return the color of navigation bar.\n     *\n     * @param window The window.\n     * @return the color of navigation bar\n     */\n    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n    public static int getNavBarColor(@NonNull final Window window) {\n        return window.getNavigationBarColor();\n    }\n\n    /**\n     * Return whether the navigation bar visible.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isSupportNavBar() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            WindowManager wm = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE);\n            if (wm == null) return false;\n            Display display = wm.getDefaultDisplay();\n            Point size = new Point();\n            Point realSize = new Point();\n            display.getSize(size);\n            display.getRealSize(realSize);\n            return realSize.y != size.y || realSize.x != size.x;\n        }\n        boolean menu = ViewConfiguration.get(Utils.getApp()).hasPermanentMenuKey();\n        boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);\n        return !menu && !back;\n    }\n\n    /**\n     * Set the nav bar's light mode.\n     *\n     * @param activity    The activity.\n     * @param isLightMode True to set nav bar light mode, false otherwise.\n     */\n    public static void setNavBarLightMode(@NonNull final Activity activity,\n                                          final boolean isLightMode) {\n        setNavBarLightMode(activity.getWindow(), isLightMode);\n    }\n\n    /**\n     * Set the nav bar's light mode.\n     *\n     * @param window      The window.\n     * @param isLightMode True to set nav bar light mode, false otherwise.\n     */\n    public static void setNavBarLightMode(@NonNull final Window window,\n                                          final boolean isLightMode) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            View decorView = window.getDecorView();\n            int vis = decorView.getSystemUiVisibility();\n            if (isLightMode) {\n                vis |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;\n            } else {\n                vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;\n            }\n            decorView.setSystemUiVisibility(vis);\n        }\n    }\n\n    /**\n     * Is the nav bar light mode.\n     *\n     * @param activity The activity.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isNavBarLightMode(@NonNull final Activity activity) {\n        return isNavBarLightMode(activity.getWindow());\n    }\n\n    /**\n     * Is the nav bar light mode.\n     *\n     * @param window The window.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isNavBarLightMode(@NonNull final Window window) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            View decorView = window.getDecorView();\n            int vis = decorView.getSystemUiVisibility();\n            return (vis & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/BatteryUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.os.BatteryManager;\nimport android.os.Build;\nimport android.os.PowerManager;\n\nimport androidx.annotation.IntDef;\nimport androidx.annotation.RequiresApi;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * <pre>\n *     author: blankj\n *     blog  : http://blankj.com\n *     time  : 2020/03/31\n *     desc  :\n * </pre>\n */\npublic final class BatteryUtils {\n\n    @IntDef({BatteryStatus.UNKNOWN, BatteryStatus.DISCHARGING, BatteryStatus.CHARGING,\n            BatteryStatus.NOT_CHARGING, BatteryStatus.FULL})\n    @Retention(RetentionPolicy.SOURCE)\n    public @interface BatteryStatus {\n        int UNKNOWN      = BatteryManager.BATTERY_STATUS_UNKNOWN;\n        int DISCHARGING  = BatteryManager.BATTERY_STATUS_DISCHARGING;\n        int CHARGING     = BatteryManager.BATTERY_STATUS_CHARGING;\n        int NOT_CHARGING = BatteryManager.BATTERY_STATUS_NOT_CHARGING;\n        int FULL         = BatteryManager.BATTERY_STATUS_FULL;\n    }\n\n    /**\n     * Return whether the app is on the device's power whitelist.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    public static boolean isIgnoringBatteryOptimizations() {\n        return isIgnoringBatteryOptimizations(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return whether the app is on the device's power whitelist.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    public static boolean isIgnoringBatteryOptimizations(String pkgName) {\n        try {\n            PowerManager pm = (PowerManager) Utils.getApp().getSystemService(Context.POWER_SERVICE);\n            //noinspection ConstantConditions\n            return pm.isIgnoringBatteryOptimizations(pkgName);\n        } catch (Exception e) {\n            return true;\n        }\n    }\n\n    /**\n     * Register the status of battery changed listener.\n     *\n     * @param listener The status of battery changed listener.\n     */\n    public static void registerBatteryStatusChangedListener(final OnBatteryStatusChangedListener listener) {\n        BatteryChangedReceiver.getInstance().registerListener(listener);\n    }\n\n    /**\n     * Return whether the status of battery changed listener has been registered.\n     *\n     * @param listener The status of battery changed listener.\n     * @return true to registered, false otherwise.\n     */\n    public static boolean isRegistered(final OnBatteryStatusChangedListener listener) {\n        return BatteryChangedReceiver.getInstance().isRegistered(listener);\n    }\n\n    /**\n     * Unregister the status of battery changed listener.\n     *\n     * @param listener The status of battery changed listener.\n     */\n    public static void unregisterBatteryStatusChangedListener(final OnBatteryStatusChangedListener listener) {\n        BatteryChangedReceiver.getInstance().unregisterListener(listener);\n    }\n\n    public static final class BatteryChangedReceiver extends BroadcastReceiver {\n\n        private static BatteryChangedReceiver getInstance() {\n            return LazyHolder.INSTANCE;\n        }\n\n        private Set<OnBatteryStatusChangedListener> mListeners = new HashSet<>();\n\n        void registerListener(final OnBatteryStatusChangedListener listener) {\n            if (listener == null) return;\n            ThreadUtils.runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    int preSize = mListeners.size();\n                    mListeners.add(listener);\n                    if (preSize == 0 && mListeners.size() == 1) {\n                        IntentFilter intentFilter = new IntentFilter();\n                        intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);\n                        Utils.getApp().registerReceiver(BatteryChangedReceiver.getInstance(), intentFilter);\n                    }\n                }\n            });\n        }\n\n        boolean isRegistered(final OnBatteryStatusChangedListener listener) {\n            if (listener == null) return false;\n            return mListeners.contains(listener);\n        }\n\n        void unregisterListener(final OnBatteryStatusChangedListener listener) {\n            if (listener == null) return;\n            ThreadUtils.runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    int preSize = mListeners.size();\n                    mListeners.remove(listener);\n                    if (preSize == 1 && mListeners.size() == 0) {\n                        Utils.getApp().unregisterReceiver(BatteryChangedReceiver.getInstance());\n                    }\n                }\n            });\n        }\n\n        @Override\n        public void onReceive(Context context, final Intent intent) {\n            if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {\n                ThreadUtils.runOnUiThread(new Runnable() {\n                    @Override\n                    public void run() {\n                        int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);\n                        int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryStatus.UNKNOWN);\n                        for (OnBatteryStatusChangedListener listener : mListeners) {\n                            listener.onBatteryStatusChanged(new Status(level, status));\n                        }\n                    }\n                });\n            }\n        }\n\n        private static class LazyHolder {\n            private static final BatteryChangedReceiver INSTANCE = new BatteryChangedReceiver();\n        }\n    }\n\n    public interface OnBatteryStatusChangedListener {\n        void onBatteryStatusChanged(Status status);\n    }\n\n    public static final class Status {\n        private int level;\n        @BatteryStatus\n        private int status;\n\n        Status(int level, int status) {\n            this.level = level;\n            this.status = status;\n        }\n\n        public int getLevel() {\n            return level;\n        }\n\n        public void setLevel(int level) {\n            this.level = level;\n        }\n\n        @BatteryStatus\n        public int getStatus() {\n            return status;\n        }\n\n        public void setStatus(int status) {\n            this.status = status;\n        }\n\n        @Override\n        public String toString() {\n            return batteryStatus2String(status) + \": \" + level + \"%\";\n        }\n\n        public static String batteryStatus2String(@BatteryStatus int status) {\n            if (status == BatteryStatus.DISCHARGING) {\n                return \"discharging\";\n            }\n            if (status == BatteryStatus.CHARGING) {\n                return \"charging\";\n            }\n            if (status == BatteryStatus.NOT_CHARGING) {\n                return \"not_charging\";\n            }\n            if (status == BatteryStatus.FULL) {\n                return \"full\";\n            }\n            return \"unknown\";\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/BitUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.util.Log;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/03/21\n *     desc  : 位运算工具类\n * </pre>\n */\npublic final class BitUtils {\n\n    private BitUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * 获取运算数指定位置的值<br>\n     * 例如： 0000 1011 获取其第 0 位的值为 1, 第 2 位 的值为 0<br>\n     *\n     * @param source 需要运算的数\n     * @param pos    指定位置 (0...7)\n     * @return 指定位置的值(0 or 1)\n     */\n    public static byte getBitValue(byte source, int pos) {\n        return (byte) ((source >> pos) & 1);\n\n    }\n\n\n    /**\n     * 将运算数指定位置的值置为指定值<br>\n     * 例: 0000 1011 需要更新为 0000 1111, 即第 2 位的值需要置为 1<br>\n     *\n     * @param source 需要运算的数\n     * @param pos    指定位置 (0<=pos<=7)\n     * @param value  只能取值为 0, 或 1, 所有大于0的值作为1处理, 所有小于0的值作为0处理\n     * @return 运算后的结果数\n     */\n    public static byte setBitValue(byte source, int pos, byte value) {\n\n        byte mask = (byte) (1 << pos);\n        if (value > 0) {\n            source |= mask;\n\n        } else {\n            source &= (~mask);\n\n        }\n        return source;\n    }\n\n\n    /**\n     * 将运算数指定位置取反值<br>\n     * 例： 0000 1011 指定第 3 位取反, 结果为 0000 0011; 指定第2位取反, 结果为 0000 1111<br>\n     *\n     * @param source\n     * @param pos    指定位置 (0<=pos<=7)\n     * @return 运算后的结果数\n     */\n    public static byte reverseBitValue(byte source, int pos) {\n        byte mask = (byte) (1 << pos);\n        return (byte) (source ^ mask);\n\n    }\n\n\n    /**\n     * 检查运算数的指定位置是否为1<br>\n     *\n     * @param source 需要运算的数\n     * @param pos    指定位置 (0<=pos<=7)\n     * @return true 表示指定位置值为1, false 表示指定位置值为 0\n     */\n    public static boolean checkBitValue(byte source, int pos) {\n\n        source = (byte) (source >>> pos);\n\n        return (source & 1) == 1;\n\n    }\n\n    /**\n     * 入口函数做测试<br>\n     *\n     * @param args\n     */\n    public static void main(String[] args) {\n        // 取十进制 11 (二级制 0000 1011) 为例子\n        byte source = 11;\n        // 取第2位值并输出, 结果应为 0000 1011\n        for (byte i = 7; i >= 0; i--) {\n            Log.d(\"BitUtils\", getBitValue(source, i) + \"\");\n\n        }\n        // 将第6位置为1并输出 , 结果为 75 (0100 1011)\n        Log.d(\"BitUtils\", setBitValue(source, 6, (byte) 1) + \"\");\n\n        // 将第6位取反并输出, 结果应为75(0100 1011)\n        Log.d(\"BitUtils\", reverseBitValue(source, 6) + \"\");\n\n        // 检查第6位是否为1，结果应为false\n        Log.d(\"BitUtils\", checkBitValue(source, 6) + \"\");\n\n        // 输出为1的位, 结果应为 0 1 3\n        for (byte i = 0; i < 8; i++) {\n            if (checkBitValue(source, i)) {\n                Log.d(\"BitUtils\", i + \"\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/BrightnessUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.ContentResolver;\nimport android.provider.Settings;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.view.Window;\nimport android.view.WindowManager;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/02/08\n *     desc  : utils about brightness\n * </pre>\n */\npublic final class BrightnessUtils {\n\n    private BrightnessUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return whether automatic brightness mode is enabled.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAutoBrightnessEnabled() {\n        try {\n            int mode = Settings.System.getInt(\n                    Utils.getApp().getContentResolver(),\n                    Settings.System.SCREEN_BRIGHTNESS_MODE\n            );\n            return mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;\n        } catch (Settings.SettingNotFoundException e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * Enable or disable automatic brightness mode.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.WRITE_SETTINGS\" />}</p>\n     *\n     * @param enabled True to enabled, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean setAutoBrightnessEnabled(final boolean enabled) {\n        return Settings.System.putInt(\n                Utils.getApp().getContentResolver(),\n                Settings.System.SCREEN_BRIGHTNESS_MODE,\n                enabled ? Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC\n                        : Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL\n        );\n    }\n\n    /**\n     * 获取屏幕亮度\n     *\n     * @return 屏幕亮度 0-255\n     */\n    public static int getBrightness() {\n        try {\n            return Settings.System.getInt(\n                    Utils.getApp().getContentResolver(),\n                    Settings.System.SCREEN_BRIGHTNESS\n            );\n        } catch (Settings.SettingNotFoundException e) {\n            e.printStackTrace();\n            return 0;\n        }\n    }\n\n    /**\n     * 设置屏幕亮度\n     * <p>需添加权限 {@code <uses-permission android:name=\"android.permission.WRITE_SETTINGS\" />}</p>\n     * 并得到授权\n     *\n     * @param brightness 亮度值\n     */\n    public static boolean setBrightness(@IntRange(from = 0, to = 255) final int brightness) {\n        ContentResolver resolver = Utils.getApp().getContentResolver();\n        boolean b = Settings.System.putInt(resolver, Settings.System.SCREEN_BRIGHTNESS, brightness);\n        resolver.notifyChange(Settings.System.getUriFor(\"screen_brightness\"), null);\n        return b;\n    }\n\n    /**\n     * 设置窗口亮度\n     *\n     * @param window     窗口\n     * @param brightness 亮度值\n     */\n    public static void setWindowBrightness(@NonNull final Window window,\n                                           @IntRange(from = 0, to = 255) final int brightness) {\n        WindowManager.LayoutParams lp = window.getAttributes();\n        lp.screenBrightness = brightness / 255f;\n        window.setAttributes(lp);\n    }\n\n    /**\n     * 获取窗口亮度\n     *\n     * @param window 窗口\n     * @return 屏幕亮度 0-255\n     */\n    public static int getWindowBrightness(@NonNull final Window window) {\n        WindowManager.LayoutParams lp = window.getAttributes();\n        float brightness = lp.screenBrightness;\n        if (brightness < 0) return getBrightness();\n        return (int) (brightness * 255);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/BusUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.Nullable;\nimport android.util.Log;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.CopyOnWriteArraySet;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/10/02\n *     desc  : utils about bus\n * </pre>\n */\npublic final class BusUtils {\n\n    private static final Object NULL = \"nULl\";\n    private static final String TAG  = \"BusUtils\";\n\n    private final Map<String, List<BusInfo>>       mTag_BusInfoListMap          = new ConcurrentHashMap<>();\n    private final Map<String, Set<Object>>         mClassName_BusesMap          = new ConcurrentHashMap<>();\n    private final Map<String, List<String>>        mClassName_TagsMap           = new ConcurrentHashMap<>();\n    private final Map<String, Map<String, Object>> mClassName_Tag_Arg4StickyMap = new ConcurrentHashMap<>();\n\n    private BusUtils() {\n        init();\n    }\n\n    /**\n     * It'll be injected the bus who have {@link Bus} annotation\n     * by function of {@link BusUtils#registerBus} when execute transform task.\n     */\n    private void init() {/*inject*/}\n\n    private void registerBus(String tag,\n                             String className, String funName, String paramType, String paramName,\n                             boolean sticky, String threadMode) {\n        registerBus(tag, className, funName, paramType, paramName, sticky, threadMode, 0);\n    }\n\n    private void registerBus(String tag,\n                             String className, String funName, String paramType, String paramName,\n                             boolean sticky, String threadMode, int priority) {\n        List<BusInfo> busInfoList = mTag_BusInfoListMap.get(tag);\n        if (busInfoList == null) {\n            busInfoList = new CopyOnWriteArrayList<>();\n            mTag_BusInfoListMap.put(tag, busInfoList);\n        }\n        busInfoList.add(new BusInfo(tag, className, funName, paramType, paramName, sticky, threadMode, priority));\n    }\n\n    public static void register(@Nullable final Object bus) {\n        getInstance().registerInner(bus);\n    }\n\n    public static void unregister(@Nullable final Object bus) {\n        getInstance().unregisterInner(bus);\n    }\n\n    public static void post(@NonNull final String tag) {\n        post(tag, NULL);\n    }\n\n    public static void post(@NonNull final String tag, @NonNull final Object arg) {\n        getInstance().postInner(tag, arg);\n    }\n\n    public static void postSticky(@NonNull final String tag) {\n        postSticky(tag, NULL);\n    }\n\n    public static void postSticky(@NonNull final String tag, final Object arg) {\n        getInstance().postStickyInner(tag, arg);\n    }\n\n    public static void removeSticky(final String tag) {\n        getInstance().removeStickyInner(tag);\n    }\n\n    public static String toString_() {\n        return getInstance().toString();\n    }\n\n    @Override\n    public String toString() {\n        return \"BusUtils: \" + mTag_BusInfoListMap;\n    }\n\n    private static BusUtils getInstance() {\n        return LazyHolder.INSTANCE;\n    }\n\n    private void registerInner(@Nullable final Object bus) {\n        if (bus == null) return;\n        Class<?> aClass = bus.getClass();\n        String className = aClass.getName();\n        boolean isNeedRecordTags = false;\n        synchronized (mClassName_BusesMap) {\n            Set<Object> buses = mClassName_BusesMap.get(className);\n            if (buses == null) {\n                buses = new CopyOnWriteArraySet<>();\n                mClassName_BusesMap.put(className, buses);\n                isNeedRecordTags = true;\n            }\n            if (buses.contains(bus)) {\n                Log.w(TAG, \"The bus of <\" + bus + \"> already registered.\");\n                return;\n            } else {\n                buses.add(bus);\n            }\n        }\n        if (isNeedRecordTags) {\n            recordTags(aClass, className);\n        }\n        consumeStickyIfExist(bus);\n    }\n\n    private void recordTags(Class<?> aClass, String className) {\n        List<String> tags = mClassName_TagsMap.get(className);\n        if (tags == null) {\n            synchronized (mClassName_TagsMap) {\n                tags = mClassName_TagsMap.get(className);\n                if (tags == null) {\n                    tags = new CopyOnWriteArrayList<>();\n                    for (Map.Entry<String, List<BusInfo>> entry : mTag_BusInfoListMap.entrySet()) {\n                        for (BusInfo busInfo : entry.getValue()) {\n                            try {\n                                if (Class.forName(busInfo.className).isAssignableFrom(aClass)) {\n                                    tags.add(entry.getKey());\n                                    busInfo.subClassNames.add(className);\n                                }\n                            } catch (ClassNotFoundException e) {\n                                e.printStackTrace();\n                            }\n                        }\n                    }\n                    mClassName_TagsMap.put(className, tags);\n                }\n            }\n        }\n    }\n\n    private void consumeStickyIfExist(final Object bus) {\n        Map<String, Object> tagArgMap = mClassName_Tag_Arg4StickyMap.get(bus.getClass().getName());\n        if (tagArgMap == null) return;\n        synchronized (mClassName_Tag_Arg4StickyMap) {\n            for (Map.Entry<String, Object> tagArgEntry : tagArgMap.entrySet()) {\n                consumeSticky(bus, tagArgEntry.getKey(), tagArgEntry.getValue());\n            }\n        }\n    }\n\n    private void consumeSticky(final Object bus, final String tag, final Object arg) {\n        List<BusInfo> busInfoList = mTag_BusInfoListMap.get(tag);\n        if (busInfoList == null) {\n            Log.e(TAG, \"The bus of tag <\" + tag + \"> is not exists.\");\n            return;\n        }\n        for (BusInfo busInfo : busInfoList) {\n            if (!busInfo.subClassNames.contains(bus.getClass().getName())) {\n                continue;\n            }\n            if (!busInfo.sticky) {\n                continue;\n            }\n\n            synchronized (mClassName_Tag_Arg4StickyMap) {\n                Map<String, Object> tagArgMap = mClassName_Tag_Arg4StickyMap.get(busInfo.className);\n                if (tagArgMap == null || !tagArgMap.containsKey(tag)) {\n                    continue;\n                }\n                invokeBus(bus, arg, busInfo, true);\n            }\n        }\n    }\n\n    private void unregisterInner(final Object bus) {\n        if (bus == null) return;\n        String className = bus.getClass().getName();\n        synchronized (mClassName_BusesMap) {\n            Set<Object> buses = mClassName_BusesMap.get(className);\n            if (buses == null || !buses.contains(bus)) {\n                Log.e(TAG, \"The bus of <\" + bus + \"> was not registered before.\");\n                return;\n            }\n            buses.remove(bus);\n        }\n    }\n\n    private void postInner(final String tag, final Object arg) {\n        postInner(tag, arg, false);\n    }\n\n    private void postInner(final String tag, final Object arg, final boolean sticky) {\n        List<BusInfo> busInfoList = mTag_BusInfoListMap.get(tag);\n        if (busInfoList == null) {\n            Log.e(TAG, \"The bus of tag <\" + tag + \"> is not exists.\");\n            if (mTag_BusInfoListMap.isEmpty()) {\n                Log.e(TAG, \"Please check whether the bus plugin is applied.\");\n            }\n            return;\n        }\n        for (BusInfo busInfo : busInfoList) {\n            invokeBus(arg, busInfo, sticky);\n        }\n    }\n\n    private void invokeBus(Object arg, BusInfo busInfo, boolean sticky) {\n        invokeBus(null, arg, busInfo, sticky);\n    }\n\n    private void invokeBus(Object bus, Object arg, BusInfo busInfo, boolean sticky) {\n        if (busInfo.method == null) {\n            Method method = getMethodByBusInfo(busInfo);\n            if (method == null) {\n                return;\n            }\n            busInfo.method = method;\n        }\n        invokeMethod(bus, arg, busInfo, sticky);\n    }\n\n    private Method getMethodByBusInfo(BusInfo busInfo) {\n        try {\n            if (\"\".equals(busInfo.paramType)) {\n                return Class.forName(busInfo.className).getDeclaredMethod(busInfo.funName);\n            } else {\n                return Class.forName(busInfo.className).getDeclaredMethod(busInfo.funName, getClassName(busInfo.paramType));\n            }\n        } catch (ClassNotFoundException e) {\n            e.printStackTrace();\n        } catch (NoSuchMethodException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    private Class getClassName(String paramType) throws ClassNotFoundException {\n        switch (paramType) {\n            case \"boolean\":\n                return boolean.class;\n            case \"int\":\n                return int.class;\n            case \"long\":\n                return long.class;\n            case \"short\":\n                return short.class;\n            case \"byte\":\n                return byte.class;\n            case \"double\":\n                return double.class;\n            case \"float\":\n                return float.class;\n            case \"char\":\n                return char.class;\n            default:\n                return Class.forName(paramType);\n        }\n    }\n\n    private void invokeMethod(final Object arg, final BusInfo busInfo, final boolean sticky) {\n        invokeMethod(null, arg, busInfo, sticky);\n    }\n\n    private void invokeMethod(final Object bus, final Object arg, final BusInfo busInfo, final boolean sticky) {\n        Runnable runnable = new Runnable() {\n            @Override\n            public void run() {\n                realInvokeMethod(bus, arg, busInfo, sticky);\n            }\n        };\n        switch (busInfo.threadMode) {\n            case \"MAIN\":\n                ThreadUtils.runOnUiThread(runnable);\n                return;\n            case \"IO\":\n                ThreadUtils.getIoPool().execute(runnable);\n                return;\n            case \"CPU\":\n                ThreadUtils.getCpuPool().execute(runnable);\n                return;\n            case \"CACHED\":\n                ThreadUtils.getCachedPool().execute(runnable);\n                return;\n            case \"SINGLE\":\n                ThreadUtils.getSinglePool().execute(runnable);\n                return;\n            default:\n                runnable.run();\n        }\n    }\n\n    private void realInvokeMethod(Object bus, Object arg, BusInfo busInfo, boolean sticky) {\n        Set<Object> buses = new HashSet<>();\n        if (bus == null) {\n            for (String subClassName : busInfo.subClassNames) {\n                Set<Object> subBuses = mClassName_BusesMap.get(subClassName);\n                if (subBuses != null && !subBuses.isEmpty()) {\n                    buses.addAll(subBuses);\n                }\n            }\n            if (buses.size() == 0) {\n                if (!sticky) {\n                    Log.e(TAG, \"The \" + busInfo + \" was not registered before.\");\n                }\n                return;\n            }\n        } else {\n            buses.add(bus);\n        }\n        invokeBuses(arg, busInfo, buses);\n    }\n\n    private void invokeBuses(Object arg, BusInfo busInfo, Set<Object> buses) {\n        try {\n            if (arg == NULL) {\n                for (Object bus : buses) {\n                    busInfo.method.invoke(bus);\n                }\n            } else {\n                for (Object bus : buses) {\n                    busInfo.method.invoke(bus, arg);\n                }\n            }\n        } catch (IllegalAccessException e) {\n            e.printStackTrace();\n        } catch (InvocationTargetException e) {\n            e.printStackTrace();\n        }\n    }\n\n    private void postStickyInner(final String tag, final Object arg) {\n        List<BusInfo> busInfoList = mTag_BusInfoListMap.get(tag);\n        if (busInfoList == null) {\n            Log.e(TAG, \"The bus of tag <\" + tag + \"> is not exists.\");\n            return;\n        }\n        // 获取多对象，然后消费各个 busInfoList\n        for (BusInfo busInfo : busInfoList) {\n            if (!busInfo.sticky) { // not sticky bus will post directly.\n                invokeBus(arg, busInfo, false);\n                continue;\n            }\n            synchronized (mClassName_Tag_Arg4StickyMap) {\n                Map<String, Object> tagArgMap = mClassName_Tag_Arg4StickyMap.get(busInfo.className);\n                if (tagArgMap == null) {\n                    tagArgMap = new ConcurrentHashMap<>();\n                    mClassName_Tag_Arg4StickyMap.put(busInfo.className, tagArgMap);\n                }\n                tagArgMap.put(tag, arg);\n            }\n            invokeBus(arg, busInfo, true);\n        }\n    }\n\n    private void removeStickyInner(final String tag) {\n        List<BusInfo> busInfoList = mTag_BusInfoListMap.get(tag);\n        if (busInfoList == null) {\n            Log.e(TAG, \"The bus of tag <\" + tag + \"> is not exists.\");\n            return;\n        }\n        for (BusInfo busInfo : busInfoList) {\n            if (!busInfo.sticky) {\n                continue;\n            }\n            synchronized (mClassName_Tag_Arg4StickyMap) {\n                Map<String, Object> tagArgMap = mClassName_Tag_Arg4StickyMap.get(busInfo.className);\n                if (tagArgMap == null || !tagArgMap.containsKey(tag)) {\n                    return;\n                }\n                tagArgMap.remove(tag);\n            }\n        }\n    }\n\n    static void registerBus4Test(String tag,\n                                 String className, String funName, String paramType, String paramName,\n                                 boolean sticky, String threadMode, int priority) {\n        getInstance().registerBus(tag, className, funName, paramType, paramName, sticky, threadMode, priority);\n    }\n\n    private static final class BusInfo {\n\n        String       tag;\n        String       className;\n        String       funName;\n        String       paramType;\n        String       paramName;\n        boolean      sticky;\n        String       threadMode;\n        int          priority;\n        Method       method;\n        List<String> subClassNames;\n\n        BusInfo(String tag, String className, String funName, String paramType, String paramName,\n                boolean sticky, String threadMode, int priority) {\n            this.tag = tag;\n            this.className = className;\n            this.funName = funName;\n            this.paramType = paramType;\n            this.paramName = paramName;\n            this.sticky = sticky;\n            this.threadMode = threadMode;\n            this.priority = priority;\n            this.subClassNames = new CopyOnWriteArrayList<>();\n        }\n\n        @Override\n        public String toString() {\n            return \"BusInfo { tag : \" + tag +\n                    \", desc: \" + getDesc() +\n                    \", sticky: \" + sticky +\n                    \", threadMode: \" + threadMode +\n                    \", method: \" + method +\n                    \", priority: \" + priority +\n                    \" }\";\n        }\n\n        private String getDesc() {\n            return className + \"#\" + funName +\n                    (\"\".equals(paramType) ? \"()\" : (\"(\" + paramType + \" \" + paramName + \")\"));\n        }\n    }\n\n    public enum ThreadMode {\n        MAIN, IO, CPU, CACHED, SINGLE, POSTING\n    }\n\n    @Target({ElementType.METHOD})\n    @Retention(RetentionPolicy.CLASS)\n    public @interface Bus {\n        String tag();\n\n        boolean sticky() default false;\n\n        ThreadMode threadMode() default ThreadMode.POSTING;\n\n        int priority() default 0;\n    }\n\n    private static class LazyHolder {\n        private static final BusUtils INSTANCE = new BusUtils();\n    }\n}"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/CacheDiskStaticUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.os.Parcelable;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.Nullable;\n\nimport org.json.JSONArray;\nimport org.json.JSONObject;\n\nimport java.io.Serializable;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/01/04\n *     desc  : utils about disk cache\n * </pre>\n */\npublic final class CacheDiskStaticUtils {\n\n    private static CacheDiskUtils sDefaultCacheDiskUtils;\n\n    /**\n     * Set the default instance of {@link CacheDiskUtils}.\n     *\n     * @param cacheDiskUtils The default instance of {@link CacheDiskUtils}.\n     */\n    public static void setDefaultCacheDiskUtils(@Nullable final CacheDiskUtils cacheDiskUtils) {\n        sDefaultCacheDiskUtils = cacheDiskUtils;\n    }\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, @Nullable final byte[] value) {\n        put(key, value, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, @Nullable final byte[] value, final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the bytes in cache.\n     *\n     * @param key The key of cache.\n     * @return the bytes if cache exists or null otherwise\n     */\n    public static byte[] getBytes(@NonNull final String key) {\n        return getBytes(key, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the bytes in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the bytes if cache exists or defaultValue otherwise\n     */\n    public static byte[] getBytes(@NonNull final String key, @Nullable final byte[] defaultValue) {\n        return getBytes(key, defaultValue, getDefaultCacheDiskUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about String\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put string value in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, @Nullable final String value) {\n        put(key, value, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Put string value in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, @Nullable final String value, final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the string value in cache.\n     *\n     * @param key The key of cache.\n     * @return the string value if cache exists or null otherwise\n     */\n    public static String getString(@NonNull final String key) {\n        return getString(key, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the string value in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the string value if cache exists or defaultValue otherwise\n     */\n    public static String getString(@NonNull final String key, @Nullable final String defaultValue) {\n        return getString(key, defaultValue, getDefaultCacheDiskUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about JSONObject\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put JSONObject in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, @Nullable final JSONObject value) {\n        put(key, value, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Put JSONObject in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final JSONObject value,\n                           final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the JSONObject in cache.\n     *\n     * @param key The key of cache.\n     * @return the JSONObject if cache exists or null otherwise\n     */\n    public static JSONObject getJSONObject(@NonNull final String key) {\n        return getJSONObject(key, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the JSONObject in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the JSONObject if cache exists or defaultValue otherwise\n     */\n    public static JSONObject getJSONObject(@NonNull final String key, @Nullable final JSONObject defaultValue) {\n        return getJSONObject(key, defaultValue, getDefaultCacheDiskUtils());\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about JSONArray\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put JSONArray in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, @Nullable final JSONArray value) {\n        put(key, value, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Put JSONArray in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, @Nullable final JSONArray value, final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the JSONArray in cache.\n     *\n     * @param key The key of cache.\n     * @return the JSONArray if cache exists or null otherwise\n     */\n    public static JSONArray getJSONArray(@NonNull final String key) {\n        return getJSONArray(key, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the JSONArray in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the JSONArray if cache exists or defaultValue otherwise\n     */\n    public static JSONArray getJSONArray(@NonNull final String key, @Nullable final JSONArray defaultValue) {\n        return getJSONArray(key, defaultValue, getDefaultCacheDiskUtils());\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Bitmap\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put bitmap in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, @Nullable final Bitmap value) {\n        put(key, value, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Put bitmap in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, @Nullable final Bitmap value, final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the bitmap in cache.\n     *\n     * @param key The key of cache.\n     * @return the bitmap if cache exists or null otherwise\n     */\n    public static Bitmap getBitmap(@NonNull final String key) {\n        return getBitmap(key, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the bitmap in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the bitmap if cache exists or defaultValue otherwise\n     */\n    public static Bitmap getBitmap(@NonNull final String key, @Nullable final Bitmap defaultValue) {\n        return getBitmap(key, defaultValue, getDefaultCacheDiskUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Drawable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put drawable in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, @Nullable final Drawable value) {\n        put(key, value, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Put drawable in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, @Nullable final Drawable value, final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the drawable in cache.\n     *\n     * @param key The key of cache.\n     * @return the drawable if cache exists or null otherwise\n     */\n    public static Drawable getDrawable(@NonNull final String key) {\n        return getDrawable(key, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the drawable in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the drawable if cache exists or defaultValue otherwise\n     */\n    public static Drawable getDrawable(@NonNull final String key, final @Nullable Drawable defaultValue) {\n        return getDrawable(key, defaultValue, getDefaultCacheDiskUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Parcelable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put parcelable in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, @Nullable final Parcelable value) {\n        put(key, value, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Put parcelable in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, @Nullable final Parcelable value, final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the parcelable in cache.\n     *\n     * @param key     The key of cache.\n     * @param creator The creator.\n     * @param <T>     The value type.\n     * @return the parcelable if cache exists or null otherwise\n     */\n    public static <T> T getParcelable(@NonNull final String key,\n                                      @NonNull final Parcelable.Creator<T> creator) {\n        return getParcelable(key, creator, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the parcelable in cache.\n     *\n     * @param key          The key of cache.\n     * @param creator      The creator.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @param <T>          The value type.\n     * @return the parcelable if cache exists or defaultValue otherwise\n     */\n    public static <T> T getParcelable(@NonNull final String key,\n                                      @NonNull final Parcelable.Creator<T> creator,\n                                      @Nullable final T defaultValue) {\n        return getParcelable(key, creator, defaultValue, getDefaultCacheDiskUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Serializable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put serializable in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, @Nullable final Serializable value) {\n        put(key, value, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Put serializable in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, @Nullable final Serializable value, final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the serializable in cache.\n     *\n     * @param key The key of cache.\n     * @return the bitmap if cache exists or null otherwise\n     */\n    public static Object getSerializable(@NonNull final String key) {\n        return getSerializable(key, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the serializable in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the bitmap if cache exists or defaultValue otherwise\n     */\n    public static Object getSerializable(@NonNull final String key, @Nullable final Object defaultValue) {\n        return getSerializable(key, defaultValue, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the size of cache, in bytes.\n     *\n     * @return the size of cache, in bytes\n     */\n    public static long getCacheSize() {\n        return getCacheSize(getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Return the count of cache.\n     *\n     * @return the count of cache\n     */\n    public static int getCacheCount() {\n        return getCacheCount(getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Remove the cache by key.\n     *\n     * @param key The key of cache.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean remove(@NonNull final String key) {\n        return remove(key, getDefaultCacheDiskUtils());\n    }\n\n    /**\n     * Clear all of the cache.\n     *\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean clear() {\n        return clear(getDefaultCacheDiskUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // dividing line\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final byte[] value,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value);\n    }\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param saveTime       The save time of cache, in seconds.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final byte[] value,\n                           final int saveTime,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the bytes in cache.\n     *\n     * @param key            The key of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the bytes if cache exists or null otherwise\n     */\n    public static byte[] getBytes(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getBytes(key);\n    }\n\n    /**\n     * Return the bytes in cache.\n     *\n     * @param key            The key of cache.\n     * @param defaultValue   The default value if the cache doesn't exist.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the bytes if cache exists or defaultValue otherwise\n     */\n    public static byte[] getBytes(@NonNull final String key,\n                                  @Nullable final byte[] defaultValue,\n                                  @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getBytes(key, defaultValue);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about String\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put string value in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final String value,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value);\n    }\n\n    /**\n     * Put string value in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param saveTime       The save time of cache, in seconds.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final String value,\n                           final int saveTime,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the string value in cache.\n     *\n     * @param key            The key of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the string value if cache exists or null otherwise\n     */\n    public static String getString(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getString(key);\n    }\n\n    /**\n     * Return the string value in cache.\n     *\n     * @param key            The key of cache.\n     * @param defaultValue   The default value if the cache doesn't exist.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the string value if cache exists or defaultValue otherwise\n     */\n    public static String getString(@NonNull final String key,\n                                   @Nullable final String defaultValue,\n                                   @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getString(key, defaultValue);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about JSONObject\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put JSONObject in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final JSONObject value,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value);\n    }\n\n    /**\n     * Put JSONObject in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param saveTime       The save time of cache, in seconds.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final JSONObject value,\n                           final int saveTime,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the JSONObject in cache.\n     *\n     * @param key            The key of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the JSONObject if cache exists or null otherwise\n     */\n    public static JSONObject getJSONObject(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getJSONObject(key);\n    }\n\n    /**\n     * Return the JSONObject in cache.\n     *\n     * @param key            The key of cache.\n     * @param defaultValue   The default value if the cache doesn't exist.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the JSONObject if cache exists or defaultValue otherwise\n     */\n    public static JSONObject getJSONObject(@NonNull final String key,\n                                           @Nullable final JSONObject defaultValue,\n                                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getJSONObject(key, defaultValue);\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about JSONArray\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put JSONArray in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final JSONArray value,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value);\n    }\n\n    /**\n     * Put JSONArray in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param saveTime       The save time of cache, in seconds.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final JSONArray value,\n                           final int saveTime,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the JSONArray in cache.\n     *\n     * @param key            The key of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the JSONArray if cache exists or null otherwise\n     */\n    public static JSONArray getJSONArray(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getJSONArray(key);\n    }\n\n    /**\n     * Return the JSONArray in cache.\n     *\n     * @param key            The key of cache.\n     * @param defaultValue   The default value if the cache doesn't exist.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the JSONArray if cache exists or defaultValue otherwise\n     */\n    public static JSONArray getJSONArray(@NonNull final String key,\n                                         @Nullable final JSONArray defaultValue,\n                                         @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getJSONArray(key, defaultValue);\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Bitmap\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put bitmap in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final Bitmap value,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value);\n    }\n\n    /**\n     * Put bitmap in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param saveTime       The save time of cache, in seconds.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final Bitmap value,\n                           final int saveTime,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the bitmap in cache.\n     *\n     * @param key            The key of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the bitmap if cache exists or null otherwise\n     */\n    public static Bitmap getBitmap(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getBitmap(key);\n    }\n\n    /**\n     * Return the bitmap in cache.\n     *\n     * @param key            The key of cache.\n     * @param defaultValue   The default value if the cache doesn't exist.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the bitmap if cache exists or defaultValue otherwise\n     */\n    public static Bitmap getBitmap(@NonNull final String key,\n                                   @Nullable final Bitmap defaultValue,\n                                   @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getBitmap(key, defaultValue);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Drawable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put drawable in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final Drawable value,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value);\n    }\n\n    /**\n     * Put drawable in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param saveTime       The save time of cache, in seconds.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final Drawable value,\n                           final int saveTime,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the drawable in cache.\n     *\n     * @param key            The key of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the drawable if cache exists or null otherwise\n     */\n    public static Drawable getDrawable(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getDrawable(key);\n    }\n\n    /**\n     * Return the drawable in cache.\n     *\n     * @param key            The key of cache.\n     * @param defaultValue   The default value if the cache doesn't exist.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the drawable if cache exists or defaultValue otherwise\n     */\n    public static Drawable getDrawable(@NonNull final String key,\n                                       @Nullable final Drawable defaultValue,\n                                       @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getDrawable(key, defaultValue);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Parcelable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put parcelable in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final Parcelable value,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value);\n    }\n\n    /**\n     * Put parcelable in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param saveTime       The save time of cache, in seconds.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final Parcelable value,\n                           final int saveTime,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the parcelable in cache.\n     *\n     * @param key            The key of cache.\n     * @param creator        The creator.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @param <T>            The value type.\n     * @return the parcelable if cache exists or null otherwise\n     */\n    public static <T> T getParcelable(@NonNull final String key,\n                                      @NonNull final Parcelable.Creator<T> creator,\n                                      @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getParcelable(key, creator);\n    }\n\n    /**\n     * Return the parcelable in cache.\n     *\n     * @param key            The key of cache.\n     * @param creator        The creator.\n     * @param defaultValue   The default value if the cache doesn't exist.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @param <T>            The value type.\n     * @return the parcelable if cache exists or defaultValue otherwise\n     */\n    public static <T> T getParcelable(@NonNull final String key,\n                                      @NonNull final Parcelable.Creator<T> creator,\n                                      @Nullable final T defaultValue,\n                                      @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getParcelable(key, creator, defaultValue);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Serializable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put serializable in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final Serializable value,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value);\n    }\n\n    /**\n     * Put serializable in cache.\n     *\n     * @param key            The key of cache.\n     * @param value          The value of cache.\n     * @param saveTime       The save time of cache, in seconds.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           @Nullable final Serializable value,\n                           final int saveTime,\n                           @NonNull final CacheDiskUtils cacheDiskUtils) {\n        cacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the serializable in cache.\n     *\n     * @param key            The key of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the bitmap if cache exists or null otherwise\n     */\n    public static Object getSerializable(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getSerializable(key);\n    }\n\n    /**\n     * Return the serializable in cache.\n     *\n     * @param key            The key of cache.\n     * @param defaultValue   The default value if the cache doesn't exist.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the bitmap if cache exists or defaultValue otherwise\n     */\n    public static Object getSerializable(@NonNull final String key,\n                                         @Nullable final Object defaultValue,\n                                         @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getSerializable(key, defaultValue);\n    }\n\n    /**\n     * Return the size of cache, in bytes.\n     *\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the size of cache, in bytes\n     */\n    public static long getCacheSize(@NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getCacheSize();\n    }\n\n    /**\n     * Return the count of cache.\n     *\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return the count of cache\n     */\n    public static int getCacheCount(@NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.getCacheCount();\n    }\n\n    /**\n     * Remove the cache by key.\n     *\n     * @param key            The key of cache.\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean remove(@NonNull final String key, @NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.remove(key);\n    }\n\n    /**\n     * Clear all of the cache.\n     *\n     * @param cacheDiskUtils The instance of {@link CacheDiskUtils}.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean clear(@NonNull final CacheDiskUtils cacheDiskUtils) {\n        return cacheDiskUtils.clear();\n    }\n\n    @NonNull\n    private static CacheDiskUtils getDefaultCacheDiskUtils() {\n        return sDefaultCacheDiskUtils != null ? sDefaultCacheDiskUtils : CacheDiskUtils.getInstance();\n    }\n}"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/CacheDiskUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.os.Parcelable;\nimport android.util.Log;\n\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.constant.CacheConstants;\n\nimport org.json.JSONArray;\nimport org.json.JSONObject;\n\nimport java.io.File;\nimport java.io.FilenameFilter;\nimport java.io.Serializable;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2017/05/24\n *     desc  : utils about disk cache\n * </pre>\n */\npublic final class CacheDiskUtils implements CacheConstants {\n\n    private static final long   DEFAULT_MAX_SIZE  = Long.MAX_VALUE;\n    private static final int    DEFAULT_MAX_COUNT = Integer.MAX_VALUE;\n    private static final String CACHE_PREFIX      = \"cdu_\";\n    private static final String TYPE_BYTE         = \"by_\";\n    private static final String TYPE_STRING       = \"st_\";\n    private static final String TYPE_JSON_OBJECT  = \"jo_\";\n    private static final String TYPE_JSON_ARRAY   = \"ja_\";\n    private static final String TYPE_BITMAP       = \"bi_\";\n    private static final String TYPE_DRAWABLE     = \"dr_\";\n    private static final String TYPE_PARCELABLE   = \"pa_\";\n    private static final String TYPE_SERIALIZABLE = \"se_\";\n\n    private static final Map<String, CacheDiskUtils> CACHE_MAP = new HashMap<>();\n\n    private final String           mCacheKey;\n    private final File             mCacheDir;\n    private final long             mMaxSize;\n    private final int              mMaxCount;\n    private       DiskCacheManager mDiskCacheManager;\n\n    /**\n     * Return the single {@link CacheDiskUtils} instance.\n     * <p>cache directory: /data/data/package/cache/cacheUtils</p>\n     * <p>cache size: unlimited</p>\n     * <p>cache count: unlimited</p>\n     *\n     * @return the single {@link CacheDiskUtils} instance\n     */\n    public static CacheDiskUtils getInstance() {\n        return getInstance(\"\", DEFAULT_MAX_SIZE, DEFAULT_MAX_COUNT);\n    }\n\n    /**\n     * Return the single {@link CacheDiskUtils} instance.\n     * <p>cache directory: /data/data/package/cache/cacheUtils</p>\n     * <p>cache size: unlimited</p>\n     * <p>cache count: unlimited</p>\n     *\n     * @param cacheName The name of cache.\n     * @return the single {@link CacheDiskUtils} instance\n     */\n    public static CacheDiskUtils getInstance(final String cacheName) {\n        return getInstance(cacheName, DEFAULT_MAX_SIZE, DEFAULT_MAX_COUNT);\n    }\n\n    /**\n     * Return the single {@link CacheDiskUtils} instance.\n     * <p>cache directory: /data/data/package/cache/cacheUtils</p>\n     *\n     * @param maxSize  The max size of cache, in bytes.\n     * @param maxCount The max count of cache.\n     * @return the single {@link CacheDiskUtils} instance\n     */\n    public static CacheDiskUtils getInstance(final long maxSize, final int maxCount) {\n        return getInstance(\"\", maxSize, maxCount);\n    }\n\n    /**\n     * Return the single {@link CacheDiskUtils} instance.\n     * <p>cache directory: /data/data/package/cache/cacheName</p>\n     *\n     * @param cacheName The name of cache.\n     * @param maxSize   The max size of cache, in bytes.\n     * @param maxCount  The max count of cache.\n     * @return the single {@link CacheDiskUtils} instance\n     */\n    public static CacheDiskUtils getInstance(String cacheName, final long maxSize, final int maxCount) {\n        if (UtilsBridge.isSpace(cacheName)) cacheName = \"cacheUtils\";\n        File file = new File(Utils.getApp().getCacheDir(), cacheName);\n        return getInstance(file, maxSize, maxCount);\n    }\n\n    /**\n     * Return the single {@link CacheDiskUtils} instance.\n     * <p>cache size: unlimited</p>\n     * <p>cache count: unlimited</p>\n     *\n     * @param cacheDir The directory of cache.\n     * @return the single {@link CacheDiskUtils} instance\n     */\n    public static CacheDiskUtils getInstance(@NonNull final File cacheDir) {\n        return getInstance(cacheDir, DEFAULT_MAX_SIZE, DEFAULT_MAX_COUNT);\n    }\n\n    /**\n     * Return the single {@link CacheDiskUtils} instance.\n     *\n     * @param cacheDir The directory of cache.\n     * @param maxSize  The max size of cache, in bytes.\n     * @param maxCount The max count of cache.\n     * @return the single {@link CacheDiskUtils} instance\n     */\n    public static CacheDiskUtils getInstance(@NonNull final File cacheDir,\n                                             final long maxSize,\n                                             final int maxCount) {\n        final String cacheKey = cacheDir.getAbsoluteFile() + \"_\" + maxSize + \"_\" + maxCount;\n        CacheDiskUtils cache = CACHE_MAP.get(cacheKey);\n        if (cache == null) {\n            synchronized (CacheDiskUtils.class) {\n                cache = CACHE_MAP.get(cacheKey);\n                if (cache == null) {\n                    cache = new CacheDiskUtils(cacheKey, cacheDir, maxSize, maxCount);\n                    CACHE_MAP.put(cacheKey, cache);\n                }\n            }\n        }\n        return cache;\n    }\n\n    private CacheDiskUtils(final String cacheKey,\n                           final File cacheDir,\n                           final long maxSize,\n                           final int maxCount) {\n        mCacheKey = cacheKey;\n        mCacheDir = cacheDir;\n        mMaxSize = maxSize;\n        mMaxCount = maxCount;\n    }\n\n    private DiskCacheManager getDiskCacheManager() {\n        if (mCacheDir.exists()) {\n            if (mDiskCacheManager == null) {\n                mDiskCacheManager = new DiskCacheManager(mCacheDir, mMaxSize, mMaxCount);\n            }\n        } else {\n            if (mCacheDir.mkdirs()) {\n                mDiskCacheManager = new DiskCacheManager(mCacheDir, mMaxSize, mMaxCount);\n            } else {\n                Log.e(\"CacheDiskUtils\", \"can't make dirs in \" + mCacheDir.getAbsolutePath());\n            }\n        }\n        return mDiskCacheManager;\n    }\n\n    @Override\n    public String toString() {\n        return mCacheKey + \"@\" + Integer.toHexString(hashCode());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about bytes\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final byte[] value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, final byte[] value, final int saveTime) {\n        realPutBytes(TYPE_BYTE + key, value, saveTime);\n    }\n\n    private void realPutBytes(final String key, byte[] value, int saveTime) {\n        if (value == null) return;\n        DiskCacheManager diskCacheManager = getDiskCacheManager();\n        if (diskCacheManager == null) return;\n        if (saveTime >= 0) value = DiskCacheHelper.newByteArrayWithTime(saveTime, value);\n        File file = diskCacheManager.getFileBeforePut(key);\n        UtilsBridge.writeFileFromBytes(file, value);\n        diskCacheManager.updateModify(file);\n        diskCacheManager.put(file);\n    }\n\n\n    /**\n     * Return the bytes in cache.\n     *\n     * @param key The key of cache.\n     * @return the bytes if cache exists or null otherwise\n     */\n    public byte[] getBytes(@NonNull final String key) {\n        return getBytes(key, null);\n    }\n\n    /**\n     * Return the bytes in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the bytes if cache exists or defaultValue otherwise\n     */\n    public byte[] getBytes(@NonNull final String key, final byte[] defaultValue) {\n        return realGetBytes(TYPE_BYTE + key, defaultValue);\n    }\n\n    private byte[] realGetBytes(@NonNull final String key) {\n        return realGetBytes(key, null);\n    }\n\n    private byte[] realGetBytes(@NonNull final String key, final byte[] defaultValue) {\n        DiskCacheManager diskCacheManager = getDiskCacheManager();\n        if (diskCacheManager == null) return defaultValue;\n        final File file = diskCacheManager.getFileIfExists(key);\n        if (file == null) return defaultValue;\n        byte[] data = UtilsBridge.readFile2Bytes(file);\n        if (DiskCacheHelper.isDue(data)) {\n            diskCacheManager.removeByKey(key);\n            return defaultValue;\n        }\n        diskCacheManager.updateModify(file);\n        return DiskCacheHelper.getDataWithoutDueTime(data);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about String\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put string value in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final String value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put string value in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, final String value, final int saveTime) {\n        realPutBytes(TYPE_STRING + key, UtilsBridge.string2Bytes(value), saveTime);\n    }\n\n    /**\n     * Return the string value in cache.\n     *\n     * @param key The key of cache.\n     * @return the string value if cache exists or null otherwise\n     */\n    public String getString(@NonNull final String key) {\n        return getString(key, null);\n    }\n\n    /**\n     * Return the string value in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the string value if cache exists or defaultValue otherwise\n     */\n    public String getString(@NonNull final String key, final String defaultValue) {\n        byte[] bytes = realGetBytes(TYPE_STRING + key);\n        if (bytes == null) return defaultValue;\n        return UtilsBridge.bytes2String(bytes);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about JSONObject\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put JSONObject in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final JSONObject value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put JSONObject in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key,\n                    final JSONObject value,\n                    final int saveTime) {\n        realPutBytes(TYPE_JSON_OBJECT + key, UtilsBridge.jsonObject2Bytes(value), saveTime);\n    }\n\n    /**\n     * Return the JSONObject in cache.\n     *\n     * @param key The key of cache.\n     * @return the JSONObject if cache exists or null otherwise\n     */\n    public JSONObject getJSONObject(@NonNull final String key) {\n        return getJSONObject(key, null);\n    }\n\n    /**\n     * Return the JSONObject in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the JSONObject if cache exists or defaultValue otherwise\n     */\n    public JSONObject getJSONObject(@NonNull final String key, final JSONObject defaultValue) {\n        byte[] bytes = realGetBytes(TYPE_JSON_OBJECT + key);\n        if (bytes == null) return defaultValue;\n        return UtilsBridge.bytes2JSONObject(bytes);\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about JSONArray\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put JSONArray in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final JSONArray value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put JSONArray in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, final JSONArray value, final int saveTime) {\n        realPutBytes(TYPE_JSON_ARRAY + key, UtilsBridge.jsonArray2Bytes(value), saveTime);\n    }\n\n    /**\n     * Return the JSONArray in cache.\n     *\n     * @param key The key of cache.\n     * @return the JSONArray if cache exists or null otherwise\n     */\n    public JSONArray getJSONArray(@NonNull final String key) {\n        return getJSONArray(key, null);\n    }\n\n    /**\n     * Return the JSONArray in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the JSONArray if cache exists or defaultValue otherwise\n     */\n    public JSONArray getJSONArray(@NonNull final String key, final JSONArray defaultValue) {\n        byte[] bytes = realGetBytes(TYPE_JSON_ARRAY + key);\n        if (bytes == null) return defaultValue;\n        return UtilsBridge.bytes2JSONArray(bytes);\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Bitmap\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put bitmap in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final Bitmap value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put bitmap in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, final Bitmap value, final int saveTime) {\n        realPutBytes(TYPE_BITMAP + key, UtilsBridge.bitmap2Bytes(value), saveTime);\n    }\n\n    /**\n     * Return the bitmap in cache.\n     *\n     * @param key The key of cache.\n     * @return the bitmap if cache exists or null otherwise\n     */\n    public Bitmap getBitmap(@NonNull final String key) {\n        return getBitmap(key, null);\n    }\n\n    /**\n     * Return the bitmap in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the bitmap if cache exists or defaultValue otherwise\n     */\n    public Bitmap getBitmap(@NonNull final String key, final Bitmap defaultValue) {\n        byte[] bytes = realGetBytes(TYPE_BITMAP + key);\n        if (bytes == null) return defaultValue;\n        return UtilsBridge.bytes2Bitmap(bytes);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Drawable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put drawable in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final Drawable value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put drawable in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, final Drawable value, final int saveTime) {\n        realPutBytes(TYPE_DRAWABLE + key, UtilsBridge.drawable2Bytes(value), saveTime);\n    }\n\n    /**\n     * Return the drawable in cache.\n     *\n     * @param key The key of cache.\n     * @return the drawable if cache exists or null otherwise\n     */\n    public Drawable getDrawable(@NonNull final String key) {\n        return getDrawable(key, null);\n    }\n\n    /**\n     * Return the drawable in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the drawable if cache exists or defaultValue otherwise\n     */\n    public Drawable getDrawable(@NonNull final String key, final Drawable defaultValue) {\n        byte[] bytes = realGetBytes(TYPE_DRAWABLE + key);\n        if (bytes == null) return defaultValue;\n        return UtilsBridge.bytes2Drawable(bytes);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Parcelable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put parcelable in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final Parcelable value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put parcelable in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, final Parcelable value, final int saveTime) {\n        realPutBytes(TYPE_PARCELABLE + key, UtilsBridge.parcelable2Bytes(value), saveTime);\n    }\n\n    /**\n     * Return the parcelable in cache.\n     *\n     * @param key     The key of cache.\n     * @param creator The creator.\n     * @param <T>     The value type.\n     * @return the parcelable if cache exists or null otherwise\n     */\n    public <T> T getParcelable(@NonNull final String key,\n                               @NonNull final Parcelable.Creator<T> creator) {\n        return getParcelable(key, creator, null);\n    }\n\n    /**\n     * Return the parcelable in cache.\n     *\n     * @param key          The key of cache.\n     * @param creator      The creator.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @param <T>          The value type.\n     * @return the parcelable if cache exists or defaultValue otherwise\n     */\n    public <T> T getParcelable(@NonNull final String key,\n                               @NonNull final Parcelable.Creator<T> creator,\n                               final T defaultValue) {\n        byte[] bytes = realGetBytes(TYPE_PARCELABLE + key);\n        if (bytes == null) return defaultValue;\n        return UtilsBridge.bytes2Parcelable(bytes, creator);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Serializable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put serializable in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final Serializable value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put serializable in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, final Serializable value, final int saveTime) {\n        realPutBytes(TYPE_SERIALIZABLE + key, UtilsBridge.serializable2Bytes(value), saveTime);\n    }\n\n    /**\n     * Return the serializable in cache.\n     *\n     * @param key The key of cache.\n     * @return the bitmap if cache exists or null otherwise\n     */\n    public Object getSerializable(@NonNull final String key) {\n        return getSerializable(key, null);\n    }\n\n    /**\n     * Return the serializable in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the bitmap if cache exists or defaultValue otherwise\n     */\n    public Object getSerializable(@NonNull final String key, final Object defaultValue) {\n        byte[] bytes = realGetBytes(TYPE_SERIALIZABLE + key);\n        if (bytes == null) return defaultValue;\n        return UtilsBridge.bytes2Object(bytes);\n    }\n\n    /**\n     * Return the size of cache, in bytes.\n     *\n     * @return the size of cache, in bytes\n     */\n    public long getCacheSize() {\n        DiskCacheManager diskCacheManager = getDiskCacheManager();\n        if (diskCacheManager == null) return 0;\n        return diskCacheManager.getCacheSize();\n    }\n\n    /**\n     * Return the count of cache.\n     *\n     * @return the count of cache\n     */\n    public int getCacheCount() {\n        DiskCacheManager diskCacheManager = getDiskCacheManager();\n        if (diskCacheManager == null) return 0;\n        return diskCacheManager.getCacheCount();\n    }\n\n    /**\n     * Remove the cache by key.\n     *\n     * @param key The key of cache.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public boolean remove(@NonNull final String key) {\n        DiskCacheManager diskCacheManager = getDiskCacheManager();\n        if (diskCacheManager == null) return true;\n        return diskCacheManager.removeByKey(TYPE_BYTE + key)\n                && diskCacheManager.removeByKey(TYPE_STRING + key)\n                && diskCacheManager.removeByKey(TYPE_JSON_OBJECT + key)\n                && diskCacheManager.removeByKey(TYPE_JSON_ARRAY + key)\n                && diskCacheManager.removeByKey(TYPE_BITMAP + key)\n                && diskCacheManager.removeByKey(TYPE_DRAWABLE + key)\n                && diskCacheManager.removeByKey(TYPE_PARCELABLE + key)\n                && diskCacheManager.removeByKey(TYPE_SERIALIZABLE + key);\n    }\n\n    /**\n     * Clear all of the cache.\n     *\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public boolean clear() {\n        DiskCacheManager diskCacheManager = getDiskCacheManager();\n        if (diskCacheManager == null) return true;\n        return diskCacheManager.clear();\n    }\n\n    private static final class DiskCacheManager {\n        private final AtomicLong      cacheSize;\n        private final AtomicInteger   cacheCount;\n        private final long            sizeLimit;\n        private final int             countLimit;\n        private final Map<File, Long> lastUsageDates\n                = Collections.synchronizedMap(new HashMap<File, Long>());\n        private final File            cacheDir;\n        private final Thread          mThread;\n\n        private DiskCacheManager(final File cacheDir, final long sizeLimit, final int countLimit) {\n            this.cacheDir = cacheDir;\n            this.sizeLimit = sizeLimit;\n            this.countLimit = countLimit;\n            cacheSize = new AtomicLong();\n            cacheCount = new AtomicInteger();\n            mThread = new Thread(new Runnable() {\n                @Override\n                public void run() {\n                    int size = 0;\n                    int count = 0;\n                    final File[] cachedFiles = cacheDir.listFiles(new FilenameFilter() {\n                        @Override\n                        public boolean accept(File dir, String name) {\n                            return name.startsWith(CACHE_PREFIX);\n                        }\n                    });\n                    if (cachedFiles != null) {\n                        for (File cachedFile : cachedFiles) {\n                            size += cachedFile.length();\n                            count += 1;\n                            lastUsageDates.put(cachedFile, cachedFile.lastModified());\n                        }\n                        cacheSize.getAndAdd(size);\n                        cacheCount.getAndAdd(count);\n                    }\n                }\n            });\n            mThread.start();\n        }\n\n        private long getCacheSize() {\n            wait2InitOk();\n            return cacheSize.get();\n        }\n\n        private int getCacheCount() {\n            wait2InitOk();\n            return cacheCount.get();\n        }\n\n        private File getFileBeforePut(final String key) {\n            wait2InitOk();\n            File file = new File(cacheDir, getCacheNameByKey(key));\n            if (file.exists()) {\n                cacheCount.addAndGet(-1);\n                cacheSize.addAndGet(-file.length());\n            }\n            return file;\n        }\n\n        private void wait2InitOk() {\n            try {\n                mThread.join();\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        }\n\n        private File getFileIfExists(final String key) {\n            File file = new File(cacheDir, getCacheNameByKey(key));\n            if (!file.exists()) return null;\n            return file;\n        }\n\n        private String getCacheNameByKey(final String key) {\n            return CACHE_PREFIX + key.substring(0, 3) + key.substring(3).hashCode();\n        }\n\n        private void put(final File file) {\n            cacheCount.addAndGet(1);\n            cacheSize.addAndGet(file.length());\n            while (cacheCount.get() > countLimit || cacheSize.get() > sizeLimit) {\n                cacheSize.addAndGet(-removeOldest());\n                cacheCount.addAndGet(-1);\n            }\n        }\n\n        private void updateModify(final File file) {\n            Long millis = System.currentTimeMillis();\n            file.setLastModified(millis);\n            lastUsageDates.put(file, millis);\n        }\n\n        private boolean removeByKey(final String key) {\n            File file = getFileIfExists(key);\n            if (file == null) return true;\n            if (!file.delete()) return false;\n            cacheSize.addAndGet(-file.length());\n            cacheCount.addAndGet(-1);\n            lastUsageDates.remove(file);\n            return true;\n        }\n\n        private boolean clear() {\n            File[] files = cacheDir.listFiles(new FilenameFilter() {\n                @Override\n                public boolean accept(File dir, String name) {\n                    return name.startsWith(CACHE_PREFIX);\n                }\n            });\n            if (files == null || files.length <= 0) return true;\n            boolean flag = true;\n            for (File file : files) {\n                if (!file.delete()) {\n                    flag = false;\n                    continue;\n                }\n                cacheSize.addAndGet(-file.length());\n                cacheCount.addAndGet(-1);\n                lastUsageDates.remove(file);\n            }\n            if (flag) {\n                lastUsageDates.clear();\n                cacheSize.set(0);\n                cacheCount.set(0);\n            }\n            return flag;\n        }\n\n        /**\n         * Remove the oldest files.\n         *\n         * @return the size of oldest files, in bytes\n         */\n        private long removeOldest() {\n            if (lastUsageDates.isEmpty()) return 0;\n            Long oldestUsage = Long.MAX_VALUE;\n            File oldestFile = null;\n            Set<Map.Entry<File, Long>> entries = lastUsageDates.entrySet();\n            synchronized (lastUsageDates) {\n                for (Map.Entry<File, Long> entry : entries) {\n                    Long lastValueUsage = entry.getValue();\n                    if (lastValueUsage < oldestUsage) {\n                        oldestUsage = lastValueUsage;\n                        oldestFile = entry.getKey();\n                    }\n                }\n            }\n            if (oldestFile == null) return 0;\n            long fileSize = oldestFile.length();\n            if (oldestFile.delete()) {\n                lastUsageDates.remove(oldestFile);\n                return fileSize;\n            }\n            return 0;\n        }\n    }\n\n    private static final class DiskCacheHelper {\n\n        static final int TIME_INFO_LEN = 14;\n\n        private static byte[] newByteArrayWithTime(final int second, final byte[] data) {\n            byte[] time = createDueTime(second).getBytes();\n            byte[] content = new byte[time.length + data.length];\n            System.arraycopy(time, 0, content, 0, time.length);\n            System.arraycopy(data, 0, content, time.length, data.length);\n            return content;\n        }\n\n        /**\n         * Return the string of due time.\n         *\n         * @param seconds The seconds.\n         * @return the string of due time\n         */\n        private static String createDueTime(final int seconds) {\n            return String.format(\n                    Locale.getDefault(), \"_$%010d$_\",\n                    System.currentTimeMillis() / 1000 + seconds\n            );\n        }\n\n        private static boolean isDue(final byte[] data) {\n            long millis = getDueTime(data);\n            return millis != -1 && System.currentTimeMillis() > millis;\n        }\n\n        private static long getDueTime(final byte[] data) {\n            if (hasTimeInfo(data)) {\n                String millis = new String(copyOfRange(data, 2, 12));\n                try {\n                    return Long.parseLong(millis) * 1000;\n                } catch (NumberFormatException e) {\n                    return -1;\n                }\n            }\n            return -1;\n        }\n\n        private static byte[] getDataWithoutDueTime(final byte[] data) {\n            if (hasTimeInfo(data)) {\n                return copyOfRange(data, TIME_INFO_LEN, data.length);\n            }\n            return data;\n        }\n\n        private static byte[] copyOfRange(final byte[] original, final int from, final int to) {\n            int newLength = to - from;\n            if (newLength < 0) throw new IllegalArgumentException(from + \" > \" + to);\n            byte[] copy = new byte[newLength];\n            System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));\n            return copy;\n        }\n\n        private static boolean hasTimeInfo(final byte[] data) {\n            return data != null\n                    && data.length >= TIME_INFO_LEN\n                    && data[0] == '_'\n                    && data[1] == '$'\n                    && data[12] == '$'\n                    && data[13] == '_';\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/CacheDoubleStaticUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.os.Parcelable;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport org.json.JSONArray;\nimport org.json.JSONObject;\n\nimport java.io.Serializable;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/01/04\n *     desc  : utils about double cache\n * </pre>\n */\npublic final class CacheDoubleStaticUtils {\n\n    private static CacheDoubleUtils sDefaultCacheDoubleUtils;\n\n    /**\n     * Set the default instance of {@link CacheDoubleUtils}.\n     *\n     * @param cacheDoubleUtils The default instance of {@link CacheDoubleUtils}.\n     */\n    public static void setDefaultCacheDoubleUtils(final CacheDoubleUtils cacheDoubleUtils) {\n        sDefaultCacheDoubleUtils = cacheDoubleUtils;\n    }\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, final byte[] value) {\n        put(key, value, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, byte[] value, final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the bytes in cache.\n     *\n     * @param key The key of cache.\n     * @return the bytes if cache exists or null otherwise\n     */\n    public static byte[] getBytes(@NonNull final String key) {\n        return getBytes(key, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the bytes in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the bytes if cache exists or defaultValue otherwise\n     */\n    public static byte[] getBytes(@NonNull final String key, final byte[] defaultValue) {\n        return getBytes(key, defaultValue, getDefaultCacheDoubleUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about String\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put string value in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, final String value) {\n        put(key, value, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Put string value in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, final String value, final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the string value in cache.\n     *\n     * @param key The key of cache.\n     * @return the string value if cache exists or null otherwise\n     */\n    public static String getString(@NonNull final String key) {\n        return getString(key, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the string value in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the string value if cache exists or defaultValue otherwise\n     */\n    public static String getString(@NonNull final String key, final String defaultValue) {\n        return getString(key, defaultValue, getDefaultCacheDoubleUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about JSONObject\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put JSONObject in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, final JSONObject value) {\n        put(key, value, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Put JSONObject in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key,\n                           final JSONObject value,\n                           final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the JSONObject in cache.\n     *\n     * @param key The key of cache.\n     * @return the JSONObject if cache exists or null otherwise\n     */\n    public static JSONObject getJSONObject(@NonNull final String key) {\n        return getJSONObject(key, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the JSONObject in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the JSONObject if cache exists or defaultValue otherwise\n     */\n    public static JSONObject getJSONObject(@NonNull final String key, final JSONObject defaultValue) {\n        return getJSONObject(key, defaultValue, getDefaultCacheDoubleUtils());\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about JSONArray\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put JSONArray in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, final JSONArray value) {\n        put(key, value, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Put JSONArray in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, final JSONArray value, final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the JSONArray in cache.\n     *\n     * @param key The key of cache.\n     * @return the JSONArray if cache exists or null otherwise\n     */\n    public static JSONArray getJSONArray(@NonNull final String key) {\n        return getJSONArray(key, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the JSONArray in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the JSONArray if cache exists or defaultValue otherwise\n     */\n    public static JSONArray getJSONArray(@NonNull final String key, final JSONArray defaultValue) {\n        return getJSONArray(key, defaultValue, getDefaultCacheDoubleUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // Bitmap cache\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put bitmap in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, final Bitmap value) {\n        put(key, value, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Put bitmap in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, final Bitmap value, final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the bitmap in cache.\n     *\n     * @param key The key of cache.\n     * @return the bitmap if cache exists or null otherwise\n     */\n    public static Bitmap getBitmap(@NonNull final String key) {\n        return getBitmap(key, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the bitmap in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the bitmap if cache exists or defaultValue otherwise\n     */\n    public static Bitmap getBitmap(@NonNull final String key, final Bitmap defaultValue) {\n        return getBitmap(key, defaultValue, getDefaultCacheDoubleUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Drawable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put drawable in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, final Drawable value) {\n        put(key, value, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Put drawable in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, final Drawable value, final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the drawable in cache.\n     *\n     * @param key The key of cache.\n     * @return the drawable if cache exists or null otherwise\n     */\n    public static Drawable getDrawable(@NonNull final String key) {\n        return getDrawable(key, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the drawable in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the drawable if cache exists or defaultValue otherwise\n     */\n    public static Drawable getDrawable(@NonNull final String key, final Drawable defaultValue) {\n        return getDrawable(key, defaultValue, getDefaultCacheDoubleUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Parcelable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put parcelable in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, final Parcelable value) {\n        put(key, value, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Put parcelable in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, final Parcelable value, final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the parcelable in cache.\n     *\n     * @param key     The key of cache.\n     * @param creator The creator.\n     * @param <T>     The value type.\n     * @return the parcelable if cache exists or null otherwise\n     */\n    public static <T> T getParcelable(@NonNull final String key,\n                                      @NonNull final Parcelable.Creator<T> creator) {\n        return getParcelable(key, creator, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the parcelable in cache.\n     *\n     * @param key          The key of cache.\n     * @param creator      The creator.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @param <T>          The value type.\n     * @return the parcelable if cache exists or defaultValue otherwise\n     */\n    public static <T> T getParcelable(@NonNull final String key,\n                                      @NonNull final Parcelable.Creator<T> creator,\n                                      final T defaultValue) {\n        return getParcelable(key, creator, defaultValue, getDefaultCacheDoubleUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Serializable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put serializable in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, final Serializable value) {\n        put(key, value, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Put serializable in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, final Serializable value, final int saveTime) {\n        put(key, value, saveTime, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the serializable in cache.\n     *\n     * @param key The key of cache.\n     * @return the bitmap if cache exists or null otherwise\n     */\n    public static Object getSerializable(@NonNull final String key) {\n        return getSerializable(key, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the serializable in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the bitmap if cache exists or defaultValue otherwise\n     */\n    public static Object getSerializable(@NonNull final String key, final Object defaultValue) {\n        return getSerializable(key, defaultValue, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the size of cache in disk.\n     *\n     * @return the size of cache in disk\n     */\n    public static long getCacheDiskSize() {\n        return getCacheDiskSize(getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the count of cache in disk.\n     *\n     * @return the count of cache in disk\n     */\n    public static int getCacheDiskCount() {\n        return getCacheDiskCount(getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Return the count of cache in memory.\n     *\n     * @return the count of cache in memory.\n     */\n    public static int getCacheMemoryCount() {\n        return getCacheMemoryCount(getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Remove the cache by key.\n     *\n     * @param key The key of cache.\n     */\n    public static void remove(@NonNull String key) {\n        remove(key, getDefaultCacheDoubleUtils());\n    }\n\n    /**\n     * Clear all of the cache.\n     */\n    public static void clear() {\n        clear(getDefaultCacheDoubleUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // dividing line\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final byte[] value,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value);\n    }\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param saveTime         The save time of cache, in seconds.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final byte[] value,\n                           final int saveTime,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the bytes in cache.\n     *\n     * @param key              The key of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the bytes if cache exists or null otherwise\n     */\n    public static byte[] getBytes(@NonNull final String key, @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getBytes(key);\n    }\n\n    /**\n     * Return the bytes in cache.\n     *\n     * @param key              The key of cache.\n     * @param defaultValue     The default value if the cache doesn't exist.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the bytes if cache exists or defaultValue otherwise\n     */\n    public static byte[] getBytes(@NonNull final String key,\n                                  final byte[] defaultValue,\n                                  @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getBytes(key, defaultValue);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about String\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put string value in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final String value,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value);\n    }\n\n    /**\n     * Put string value in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param saveTime         The save time of cache, in seconds.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final String value,\n                           final int saveTime,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the string value in cache.\n     *\n     * @param key              The key of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the string value if cache exists or null otherwise\n     */\n    public static String getString(@NonNull final String key, @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getString(key);\n    }\n\n    /**\n     * Return the string value in cache.\n     *\n     * @param key              The key of cache.\n     * @param defaultValue     The default value if the cache doesn't exist.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the string value if cache exists or defaultValue otherwise\n     */\n    public static String getString(@NonNull final String key,\n                                   final String defaultValue,\n                                   @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getString(key, defaultValue);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about JSONObject\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put JSONObject in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final JSONObject value,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value);\n    }\n\n    /**\n     * Put JSONObject in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param saveTime         The save time of cache, in seconds.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final JSONObject value,\n                           final int saveTime,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the JSONObject in cache.\n     *\n     * @param key              The key of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the JSONObject if cache exists or null otherwise\n     */\n    public static JSONObject getJSONObject(@NonNull final String key,\n                                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getJSONObject(key);\n    }\n\n    /**\n     * Return the JSONObject in cache.\n     *\n     * @param key              The key of cache.\n     * @param defaultValue     The default value if the cache doesn't exist.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the JSONObject if cache exists or defaultValue otherwise\n     */\n    public static JSONObject getJSONObject(@NonNull final String key,\n                                           final JSONObject defaultValue,\n                                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getJSONObject(key, defaultValue);\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about JSONArray\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put JSONArray in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final JSONArray value,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value);\n    }\n\n    /**\n     * Put JSONArray in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param saveTime         The save time of cache, in seconds.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final JSONArray value,\n                           final int saveTime,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the JSONArray in cache.\n     *\n     * @param key              The key of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the JSONArray if cache exists or null otherwise\n     */\n    public static JSONArray getJSONArray(@NonNull final String key, @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getJSONArray(key);\n    }\n\n    /**\n     * Return the JSONArray in cache.\n     *\n     * @param key              The key of cache.\n     * @param defaultValue     The default value if the cache doesn't exist.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the JSONArray if cache exists or defaultValue otherwise\n     */\n    public static JSONArray getJSONArray(@NonNull final String key,\n                                         final JSONArray defaultValue,\n                                         @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getJSONArray(key, defaultValue);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // Bitmap cache\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put bitmap in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final Bitmap value,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value);\n    }\n\n    /**\n     * Put bitmap in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param saveTime         The save time of cache, in seconds.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final Bitmap value,\n                           final int saveTime,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the bitmap in cache.\n     *\n     * @param key              The key of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the bitmap if cache exists or null otherwise\n     */\n    public static Bitmap getBitmap(@NonNull final String key, @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getBitmap(key);\n    }\n\n    /**\n     * Return the bitmap in cache.\n     *\n     * @param key              The key of cache.\n     * @param defaultValue     The default value if the cache doesn't exist.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the bitmap if cache exists or defaultValue otherwise\n     */\n    public static Bitmap getBitmap(@NonNull final String key,\n                                   final Bitmap defaultValue,\n                                   @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getBitmap(key, defaultValue);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Drawable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put drawable in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final Drawable value,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value);\n    }\n\n    /**\n     * Put drawable in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param saveTime         The save time of cache, in seconds.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final Drawable value,\n                           final int saveTime,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the drawable in cache.\n     *\n     * @param key              The key of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the drawable if cache exists or null otherwise\n     */\n    public static Drawable getDrawable(@NonNull final String key, @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getDrawable(key);\n    }\n\n    /**\n     * Return the drawable in cache.\n     *\n     * @param key              The key of cache.\n     * @param defaultValue     The default value if the cache doesn't exist.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the drawable if cache exists or defaultValue otherwise\n     */\n    public static Drawable getDrawable(@NonNull final String key,\n                                       final Drawable defaultValue,\n                                       @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getDrawable(key, defaultValue);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Parcelable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put parcelable in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final Parcelable value,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value);\n    }\n\n    /**\n     * Put parcelable in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param saveTime         The save time of cache, in seconds.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final Parcelable value,\n                           final int saveTime,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the parcelable in cache.\n     *\n     * @param key              The key of cache.\n     * @param creator          The creator.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @param <T>              The value type.\n     * @return the parcelable if cache exists or null otherwise\n     */\n    public static <T> T getParcelable(@NonNull final String key,\n                                      @NonNull final Parcelable.Creator<T> creator,\n                                      @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getParcelable(key, creator);\n    }\n\n    /**\n     * Return the parcelable in cache.\n     *\n     * @param key              The key of cache.\n     * @param creator          The creator.\n     * @param defaultValue     The default value if the cache doesn't exist.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @param <T>              The value type.\n     * @return the parcelable if cache exists or defaultValue otherwise\n     */\n    public static <T> T getParcelable(@NonNull final String key,\n                                      @NonNull final Parcelable.Creator<T> creator,\n                                      final T defaultValue,\n                                      @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getParcelable(key, creator, defaultValue);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Serializable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put serializable in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final Serializable value,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value);\n    }\n\n    /**\n     * Put serializable in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param saveTime         The save time of cache, in seconds.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final Serializable value,\n                           final int saveTime,\n                           @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the serializable in cache.\n     *\n     * @param key              The key of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the bitmap if cache exists or null otherwise\n     */\n    public static Object getSerializable(@NonNull final String key, @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getSerializable(key);\n    }\n\n    /**\n     * Return the serializable in cache.\n     *\n     * @param key              The key of cache.\n     * @param defaultValue     The default value if the cache doesn't exist.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the bitmap if cache exists or defaultValue otherwise\n     */\n    public static Object getSerializable(@NonNull final String key,\n                                         final Object defaultValue,\n                                         @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getSerializable(key, defaultValue);\n    }\n\n    /**\n     * Return the size of cache in disk.\n     *\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the size of cache in disk\n     */\n    public static long getCacheDiskSize(@NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getCacheDiskSize();\n    }\n\n    /**\n     * Return the count of cache in disk.\n     *\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the count of cache in disk\n     */\n    public static int getCacheDiskCount(@NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getCacheDiskCount();\n    }\n\n    /**\n     * Return the count of cache in memory.\n     *\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     * @return the count of cache in memory.\n     */\n    public static int getCacheMemoryCount(@NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        return cacheDoubleUtils.getCacheMemoryCount();\n    }\n\n    /**\n     * Remove the cache by key.\n     *\n     * @param key              The key of cache.\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void remove(@NonNull String key, @NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.remove(key);\n    }\n\n    /**\n     * Clear all of the cache.\n     *\n     * @param cacheDoubleUtils The instance of {@link CacheDoubleUtils}.\n     */\n    public static void clear(@NonNull final CacheDoubleUtils cacheDoubleUtils) {\n        cacheDoubleUtils.clear();\n    }\n\n    private static CacheDoubleUtils getDefaultCacheDoubleUtils() {\n        return sDefaultCacheDoubleUtils != null ? sDefaultCacheDoubleUtils : CacheDoubleUtils.getInstance();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/CacheDoubleUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.os.Parcelable;\n\n\nimport androidx.annotation.NonNull;\n\nimport com.didichuxing.doraemonkit.constant.CacheConstants;\n\nimport org.json.JSONArray;\nimport org.json.JSONObject;\n\nimport java.io.Serializable;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/06/13\n *     desc  : utils about double cache\n * </pre>\n */\npublic final class CacheDoubleUtils implements CacheConstants {\n\n    private static final Map<String, CacheDoubleUtils> CACHE_MAP = new HashMap<>();\n\n    private final CacheMemoryUtils mCacheMemoryUtils;\n    private final CacheDiskUtils   mCacheDiskUtils;\n\n    /**\n     * Return the single {@link CacheDoubleUtils} instance.\n     *\n     * @return the single {@link CacheDoubleUtils} instance\n     */\n    public static CacheDoubleUtils getInstance() {\n        return getInstance(CacheMemoryUtils.getInstance(), CacheDiskUtils.getInstance());\n    }\n\n    /**\n     * Return the single {@link CacheDoubleUtils} instance.\n     *\n     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.\n     * @param cacheDiskUtils   The instance of {@link CacheDiskUtils}.\n     * @return the single {@link CacheDoubleUtils} instance\n     */\n    public static CacheDoubleUtils getInstance(@NonNull final CacheMemoryUtils cacheMemoryUtils,\n                                               @NonNull final CacheDiskUtils cacheDiskUtils) {\n        final String cacheKey = cacheDiskUtils.toString() + \"_\" + cacheMemoryUtils.toString();\n        CacheDoubleUtils cache = CACHE_MAP.get(cacheKey);\n        if (cache == null) {\n            synchronized (CacheDoubleUtils.class) {\n                cache = CACHE_MAP.get(cacheKey);\n                if (cache == null) {\n                    cache = new CacheDoubleUtils(cacheMemoryUtils, cacheDiskUtils);\n                    CACHE_MAP.put(cacheKey, cache);\n                }\n            }\n        }\n        return cache;\n    }\n\n    private CacheDoubleUtils(CacheMemoryUtils cacheMemoryUtils, CacheDiskUtils cacheUtils) {\n        mCacheMemoryUtils = cacheMemoryUtils;\n        mCacheDiskUtils = cacheUtils;\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about bytes\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final byte[] value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, byte[] value, final int saveTime) {\n        mCacheMemoryUtils.put(key, value, saveTime);\n        mCacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the bytes in cache.\n     *\n     * @param key The key of cache.\n     * @return the bytes if cache exists or null otherwise\n     */\n    public byte[] getBytes(@NonNull final String key) {\n        return getBytes(key, null);\n    }\n\n    /**\n     * Return the bytes in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the bytes if cache exists or defaultValue otherwise\n     */\n    public byte[] getBytes(@NonNull final String key, final byte[] defaultValue) {\n        byte[] obj = mCacheMemoryUtils.get(key);\n        if (obj != null) return obj;\n        byte[] bytes = mCacheDiskUtils.getBytes(key);\n        if (bytes != null) {\n            mCacheMemoryUtils.put(key, bytes);\n            return bytes;\n        }\n        return defaultValue;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about String\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put string value in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final String value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put string value in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, final String value, final int saveTime) {\n        mCacheMemoryUtils.put(key, value, saveTime);\n        mCacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the string value in cache.\n     *\n     * @param key The key of cache.\n     * @return the string value if cache exists or null otherwise\n     */\n    public String getString(@NonNull final String key) {\n        return getString(key, null);\n    }\n\n    /**\n     * Return the string value in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the string value if cache exists or defaultValue otherwise\n     */\n    public String getString(@NonNull final String key, final String defaultValue) {\n        String obj = mCacheMemoryUtils.get(key);\n        if (obj != null) return obj;\n        String string = mCacheDiskUtils.getString(key);\n        if (string != null) {\n            mCacheMemoryUtils.put(key, string);\n            return string;\n        }\n        return defaultValue;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about JSONObject\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put JSONObject in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final JSONObject value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put JSONObject in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key,\n                    final JSONObject value,\n                    final int saveTime) {\n        mCacheMemoryUtils.put(key, value, saveTime);\n        mCacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the JSONObject in cache.\n     *\n     * @param key The key of cache.\n     * @return the JSONObject if cache exists or null otherwise\n     */\n    public JSONObject getJSONObject(@NonNull final String key) {\n        return getJSONObject(key, null);\n    }\n\n    /**\n     * Return the JSONObject in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the JSONObject if cache exists or defaultValue otherwise\n     */\n    public JSONObject getJSONObject(@NonNull final String key, final JSONObject defaultValue) {\n        JSONObject obj = mCacheMemoryUtils.get(key);\n        if (obj != null) return obj;\n        JSONObject jsonObject = mCacheDiskUtils.getJSONObject(key);\n        if (jsonObject != null) {\n            mCacheMemoryUtils.put(key, jsonObject);\n            return jsonObject;\n        }\n        return defaultValue;\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about JSONArray\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put JSONArray in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final JSONArray value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put JSONArray in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, final JSONArray value, final int saveTime) {\n        mCacheMemoryUtils.put(key, value, saveTime);\n        mCacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the JSONArray in cache.\n     *\n     * @param key The key of cache.\n     * @return the JSONArray if cache exists or null otherwise\n     */\n    public JSONArray getJSONArray(@NonNull final String key) {\n        return getJSONArray(key, null);\n    }\n\n    /**\n     * Return the JSONArray in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the JSONArray if cache exists or defaultValue otherwise\n     */\n    public JSONArray getJSONArray(@NonNull final String key, final JSONArray defaultValue) {\n        JSONArray obj = mCacheMemoryUtils.get(key);\n        if (obj != null) return obj;\n        JSONArray jsonArray = mCacheDiskUtils.getJSONArray(key);\n        if (jsonArray != null) {\n            mCacheMemoryUtils.put(key, jsonArray);\n            return jsonArray;\n        }\n        return defaultValue;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // Bitmap cache\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put bitmap in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final Bitmap value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put bitmap in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, final Bitmap value, final int saveTime) {\n        mCacheMemoryUtils.put(key, value, saveTime);\n        mCacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the bitmap in cache.\n     *\n     * @param key The key of cache.\n     * @return the bitmap if cache exists or null otherwise\n     */\n    public Bitmap getBitmap(@NonNull final String key) {\n        return getBitmap(key, null);\n    }\n\n    /**\n     * Return the bitmap in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the bitmap if cache exists or defaultValue otherwise\n     */\n    public Bitmap getBitmap(@NonNull final String key, final Bitmap defaultValue) {\n        Bitmap obj = mCacheMemoryUtils.get(key);\n        if (obj != null) return obj;\n        Bitmap bitmap = mCacheDiskUtils.getBitmap(key);\n        if (bitmap != null) {\n            mCacheMemoryUtils.put(key, bitmap);\n            return bitmap;\n        }\n        return defaultValue;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Drawable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put drawable in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final Drawable value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put drawable in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, final Drawable value, final int saveTime) {\n        mCacheMemoryUtils.put(key, value, saveTime);\n        mCacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the drawable in cache.\n     *\n     * @param key The key of cache.\n     * @return the drawable if cache exists or null otherwise\n     */\n    public Drawable getDrawable(@NonNull final String key) {\n        return getDrawable(key, null);\n    }\n\n    /**\n     * Return the drawable in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the drawable if cache exists or defaultValue otherwise\n     */\n    public Drawable getDrawable(@NonNull final String key, final Drawable defaultValue) {\n        Drawable obj = mCacheMemoryUtils.get(key);\n        if (obj != null) return obj;\n        Drawable drawable = mCacheDiskUtils.getDrawable(key);\n        if (drawable != null) {\n            mCacheMemoryUtils.put(key, drawable);\n            return drawable;\n        }\n        return defaultValue;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Parcelable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put parcelable in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final Parcelable value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put parcelable in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, final Parcelable value, final int saveTime) {\n        mCacheMemoryUtils.put(key, value, saveTime);\n        mCacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the parcelable in cache.\n     *\n     * @param key     The key of cache.\n     * @param creator The creator.\n     * @param <T>     The value type.\n     * @return the parcelable if cache exists or null otherwise\n     */\n    public <T> T getParcelable(@NonNull final String key,\n                               @NonNull final Parcelable.Creator<T> creator) {\n        return getParcelable(key, creator, null);\n    }\n\n    /**\n     * Return the parcelable in cache.\n     *\n     * @param key          The key of cache.\n     * @param creator      The creator.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @param <T>          The value type.\n     * @return the parcelable if cache exists or defaultValue otherwise\n     */\n    public <T> T getParcelable(@NonNull final String key,\n                               @NonNull final Parcelable.Creator<T> creator,\n                               final T defaultValue) {\n        T value = mCacheMemoryUtils.get(key);\n        if (value != null) return value;\n        T val = mCacheDiskUtils.getParcelable(key, creator);\n        if (val != null) {\n            mCacheMemoryUtils.put(key, val);\n            return val;\n        }\n        return defaultValue;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about Serializable\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put serializable in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final Serializable value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put serializable in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, final Serializable value, final int saveTime) {\n        mCacheMemoryUtils.put(key, value, saveTime);\n        mCacheDiskUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the serializable in cache.\n     *\n     * @param key The key of cache.\n     * @return the bitmap if cache exists or null otherwise\n     */\n    public Object getSerializable(@NonNull final String key) {\n        return getSerializable(key, null);\n    }\n\n    /**\n     * Return the serializable in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @return the bitmap if cache exists or defaultValue otherwise\n     */\n    public Object getSerializable(@NonNull final String key, final Object defaultValue) {\n        Object obj = mCacheMemoryUtils.get(key);\n        if (obj != null) return obj;\n        Object serializable = mCacheDiskUtils.getSerializable(key);\n        if (serializable != null) {\n            mCacheMemoryUtils.put(key, serializable);\n            return serializable;\n        }\n        return defaultValue;\n    }\n\n    /**\n     * Return the size of cache in disk.\n     *\n     * @return the size of cache in disk\n     */\n    public long getCacheDiskSize() {\n        return mCacheDiskUtils.getCacheSize();\n    }\n\n    /**\n     * Return the count of cache in disk.\n     *\n     * @return the count of cache in disk\n     */\n    public int getCacheDiskCount() {\n        return mCacheDiskUtils.getCacheCount();\n    }\n\n    /**\n     * Return the count of cache in memory.\n     *\n     * @return the count of cache in memory.\n     */\n    public int getCacheMemoryCount() {\n        return mCacheMemoryUtils.getCacheCount();\n    }\n\n    /**\n     * Remove the cache by key.\n     *\n     * @param key The key of cache.\n     */\n    public void remove(@NonNull String key) {\n        mCacheMemoryUtils.remove(key);\n        mCacheDiskUtils.remove(key);\n    }\n\n    /**\n     * Clear all of the cache.\n     */\n    public void clear() {\n        mCacheMemoryUtils.clear();\n        mCacheDiskUtils.clear();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/CacheMemoryStaticUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/01/04\n *     desc  : utils about memory cache\n * </pre>\n */\npublic final class CacheMemoryStaticUtils {\n\n    private static CacheMemoryUtils sDefaultCacheMemoryUtils;\n\n    /**\n     * Set the default instance of {@link CacheMemoryUtils}.\n     *\n     * @param cacheMemoryUtils The default instance of {@link CacheMemoryUtils}.\n     */\n    public static void setDefaultCacheMemoryUtils(final CacheMemoryUtils cacheMemoryUtils) {\n        sDefaultCacheMemoryUtils = cacheMemoryUtils;\n    }\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public static void put(@NonNull final String key, final Object value) {\n        put(key, value, getDefaultCacheMemoryUtils());\n    }\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public static void put(@NonNull final String key, final Object value, int saveTime) {\n        put(key, value, saveTime, getDefaultCacheMemoryUtils());\n    }\n\n    /**\n     * Return the value in cache.\n     *\n     * @param key The key of cache.\n     * @param <T> The value type.\n     * @return the value if cache exists or null otherwise\n     */\n    public static <T> T get(@NonNull final String key) {\n        return get(key, getDefaultCacheMemoryUtils());\n    }\n\n    /**\n     * Return the value in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @param <T>          The value type.\n     * @return the value if cache exists or defaultValue otherwise\n     */\n    public static <T> T get(@NonNull final String key, final T defaultValue) {\n        return get(key, defaultValue, getDefaultCacheMemoryUtils());\n    }\n\n    /**\n     * Return the count of cache.\n     *\n     * @return the count of cache\n     */\n    public static int getCacheCount() {\n        return getCacheCount(getDefaultCacheMemoryUtils());\n    }\n\n    /**\n     * Remove the cache by key.\n     *\n     * @param key The key of cache.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static Object remove(@NonNull final String key) {\n        return remove(key, getDefaultCacheMemoryUtils());\n    }\n\n    /**\n     * Clear all of the cache.\n     */\n    public static void clear() {\n        clear(getDefaultCacheMemoryUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // dividing line\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final Object value,\n                           @NonNull final CacheMemoryUtils cacheMemoryUtils) {\n        cacheMemoryUtils.put(key, value);\n    }\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key              The key of cache.\n     * @param value            The value of cache.\n     * @param saveTime         The save time of cache, in seconds.\n     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final Object value,\n                           int saveTime,\n                           @NonNull final CacheMemoryUtils cacheMemoryUtils) {\n        cacheMemoryUtils.put(key, value, saveTime);\n    }\n\n    /**\n     * Return the value in cache.\n     *\n     * @param key              The key of cache.\n     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.\n     * @param <T>              The value type.\n     * @return the value if cache exists or null otherwise\n     */\n    public static <T> T get(@NonNull final String key, @NonNull final CacheMemoryUtils cacheMemoryUtils) {\n        return cacheMemoryUtils.get(key);\n    }\n\n    /**\n     * Return the value in cache.\n     *\n     * @param key              The key of cache.\n     * @param defaultValue     The default value if the cache doesn't exist.\n     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.\n     * @param <T>              The value type.\n     * @return the value if cache exists or defaultValue otherwise\n     */\n    public static <T> T get(@NonNull final String key,\n                            final T defaultValue,\n                            @NonNull final CacheMemoryUtils cacheMemoryUtils) {\n        return cacheMemoryUtils.get(key, defaultValue);\n    }\n\n    /**\n     * Return the count of cache.\n     *\n     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.\n     * @return the count of cache\n     */\n    public static int getCacheCount(@NonNull final CacheMemoryUtils cacheMemoryUtils) {\n        return cacheMemoryUtils.getCacheCount();\n    }\n\n    /**\n     * Remove the cache by key.\n     *\n     * @param key              The key of cache.\n     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static Object remove(@NonNull final String key, @NonNull final CacheMemoryUtils cacheMemoryUtils) {\n        return cacheMemoryUtils.remove(key);\n    }\n\n    /**\n     * Clear all of the cache.\n     *\n     * @param cacheMemoryUtils The instance of {@link CacheMemoryUtils}.\n     */\n    public static void clear(@NonNull final CacheMemoryUtils cacheMemoryUtils) {\n        cacheMemoryUtils.clear();\n    }\n\n    private static CacheMemoryUtils getDefaultCacheMemoryUtils() {\n        return sDefaultCacheMemoryUtils != null ? sDefaultCacheMemoryUtils : CacheMemoryUtils.getInstance();\n    }\n}"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/CacheMemoryUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\nimport androidx.annotation.NonNull;\nimport androidx.collection.LruCache;\nimport com.didichuxing.doraemonkit.constant.CacheConstants;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2017/05/24\n *     desc  : utils about memory cache\n * </pre>\n */\npublic final class CacheMemoryUtils implements CacheConstants {\n\n    private static final int DEFAULT_MAX_COUNT = 256;\n\n    private static final Map<String, CacheMemoryUtils> CACHE_MAP = new HashMap<>();\n\n    private final String mCacheKey;\n    private final LruCache<String, CacheValue> mMemoryCache;\n\n    /**\n     * Return the single {@link CacheMemoryUtils} instance.\n     *\n     * @return the single {@link CacheMemoryUtils} instance\n     */\n    public static CacheMemoryUtils getInstance() {\n        return getInstance(DEFAULT_MAX_COUNT);\n    }\n\n    /**\n     * Return the single {@link CacheMemoryUtils} instance.\n     *\n     * @param maxCount The max count of cache.\n     * @return the single {@link CacheMemoryUtils} instance\n     */\n    public static CacheMemoryUtils getInstance(final int maxCount) {\n        return getInstance(String.valueOf(maxCount), maxCount);\n    }\n\n    /**\n     * Return the single {@link CacheMemoryUtils} instance.\n     *\n     * @param cacheKey The key of cache.\n     * @param maxCount The max count of cache.\n     * @return the single {@link CacheMemoryUtils} instance\n     */\n    public static CacheMemoryUtils getInstance(final String cacheKey, final int maxCount) {\n        CacheMemoryUtils cache = CACHE_MAP.get(cacheKey);\n        if (cache == null) {\n            synchronized (CacheMemoryUtils.class) {\n                cache = CACHE_MAP.get(cacheKey);\n                if (cache == null) {\n                    cache = new CacheMemoryUtils(cacheKey, new LruCache<String, CacheValue>(maxCount));\n                    CACHE_MAP.put(cacheKey, cache);\n                }\n            }\n        }\n        return cache;\n    }\n\n    private CacheMemoryUtils(String cacheKey, LruCache<String, CacheValue> memoryCache) {\n        mCacheKey = cacheKey;\n        mMemoryCache = memoryCache;\n    }\n\n    @Override\n    public String toString() {\n        return mCacheKey + \"@\" + Integer.toHexString(hashCode());\n    }\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key   The key of cache.\n     * @param value The value of cache.\n     */\n    public void put(@NonNull final String key, final Object value) {\n        put(key, value, -1);\n    }\n\n    /**\n     * Put bytes in cache.\n     *\n     * @param key      The key of cache.\n     * @param value    The value of cache.\n     * @param saveTime The save time of cache, in seconds.\n     */\n    public void put(@NonNull final String key, final Object value, int saveTime) {\n        if (value == null) return;\n        long dueTime = saveTime < 0 ? -1 : System.currentTimeMillis() + saveTime * 1000;\n        mMemoryCache.put(key, new CacheValue(dueTime, value));\n    }\n\n    /**\n     * Return the value in cache.\n     *\n     * @param key The key of cache.\n     * @param <T> The value type.\n     * @return the value if cache exists or null otherwise\n     */\n    public <T> T get(@NonNull final String key) {\n        return get(key, null);\n    }\n\n    /**\n     * Return the value in cache.\n     *\n     * @param key          The key of cache.\n     * @param defaultValue The default value if the cache doesn't exist.\n     * @param <T>          The value type.\n     * @return the value if cache exists or defaultValue otherwise\n     */\n    public <T> T get(@NonNull final String key, final T defaultValue) {\n        CacheValue val = mMemoryCache.get(key);\n        if (val == null) return defaultValue;\n        if (val.dueTime == -1 || val.dueTime >= System.currentTimeMillis()) {\n            //noinspection unchecked\n            return (T) val.value;\n        }\n        mMemoryCache.remove(key);\n        return defaultValue;\n    }\n\n    /**\n     * Return the count of cache.\n     *\n     * @return the count of cache\n     */\n    public int getCacheCount() {\n        return mMemoryCache.size();\n    }\n\n    /**\n     * Remove the cache by key.\n     *\n     * @param key The key of cache.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public Object remove(@NonNull final String key) {\n        CacheValue remove = mMemoryCache.remove(key);\n        if (remove == null) return null;\n        return remove.value;\n    }\n\n    /**\n     * Clear all of the cache.\n     */\n    public void clear() {\n        mMemoryCache.evictAll();\n    }\n\n    private static final class CacheValue {\n        long dueTime;\n        Object value;\n\n        CacheValue(long dueTime, Object value) {\n            this.dueTime = dueTime;\n            this.value = value;\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/CameraUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/09/19\n *     desc  : 相机相关工具类\n * </pre>\n */\npublic final class CameraUtils {\n\n//    private CameraUtils() {\n//        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n//    }\n//\n//    /**\n//     * 获取打开照程序界面的Intent\n//     */\n//    public static Intent getOpenCameraIntent() {\n//        return new Intent(MediaStore.ACTION_IMAGE_CAPTURE);\n//    }\n//\n//    /**\n//     * 获取跳转至相册选择界面的Intent\n//     */\n//    public static Intent getImagePickerIntent() {\n//        Intent intent = new Intent(Intent.ACTION_PICK, null);\n//        return intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, \"image/*\");\n//    }\n//\n//    /**\n//     * 获取[跳转至相册选择界面,并跳转至裁剪界面，默认可缩放裁剪区域]的Intent\n//     */\n//    public static Intent getImagePickerIntent(int outputX, int outputY, Uri fromFileURI,\n//                                              Uri saveFileURI) {\n//        return getImagePickerIntent(1, 1, outputX, outputY, true, fromFileURI, saveFileURI);\n//    }\n//\n//    /**\n//     * 获取[跳转至相册选择界面,并跳转至裁剪界面，默认可缩放裁剪区域]的Intent\n//     */\n//    public static Intent getImagePickerIntent(int aspectX, int aspectY, int outputX, int outputY, Uri fromFileURI,\n//                                              Uri saveFileURI) {\n//        return getImagePickerIntent(aspectX, aspectY, outputX, outputY, true, fromFileURI, saveFileURI);\n//    }\n//\n//    /**\n//     * 获取[跳转至相册选择界面,并跳转至裁剪界面，可以指定是否缩放裁剪区域]的Intent\n//     *\n//     * @param aspectX     裁剪框尺寸比例X\n//     * @param aspectY     裁剪框尺寸比例Y\n//     * @param outputX     输出尺寸宽度\n//     * @param outputY     输出尺寸高度\n//     * @param canScale    是否可缩放\n//     * @param fromFileURI 文件来源路径URI\n//     * @param saveFileURI 输出文件路径URI\n//     */\n//    public static Intent getImagePickerIntent(int aspectX, int aspectY, int outputX, int outputY, boolean canScale,\n//                                              Uri fromFileURI, Uri saveFileURI) {\n//        Intent intent = new Intent(Intent.ACTION_PICK);\n//        intent.setDataAndType(fromFileURI, \"image/*\");\n//        intent.putExtra(\"crop\", \"true\");\n//        intent.putExtra(\"aspectX\", aspectX <= 0 ? 1 : aspectX);\n//        intent.putExtra(\"aspectY\", aspectY <= 0 ? 1 : aspectY);\n//        intent.putExtra(\"outputX\", outputX);\n//        intent.putExtra(\"outputY\", outputY);\n//        intent.putExtra(\"scale\", canScale);\n//        // 图片剪裁不足黑边解决\n//        intent.putExtra(\"scaleUpIfNeeded\", true);\n//        intent.putExtra(\"return-data\", false);\n//        intent.putExtra(MediaStore.EXTRA_OUTPUT, saveFileURI);\n//        intent.putExtra(\"outputFormat\", Bitmap.CompressFormat.JPEG.toString());\n//        // 去除人脸识别\n//        return intent.putExtra(\"noFaceDetection\", true);\n//    }\n//\n//    /**\n//     * 获取[跳转至相册选择界面,并跳转至裁剪界面，默认可缩放裁剪区域]的Intent\n//     */\n//    public static Intent getCameraIntent(final Uri saveFileURI) {\n//        Intent mIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);\n//        return mIntent.putExtra(MediaStore.EXTRA_OUTPUT, saveFileURI);\n//    }\n//\n//    /**\n//     * 获取[跳转至裁剪界面,默认可缩放]的Intent\n//     */\n//    public static Intent getCropImageIntent(int outputX, int outputY, Uri fromFileURI,\n//                                            Uri saveFileURI) {\n//        return getCropImageIntent(1, 1, outputX, outputY, true, fromFileURI, saveFileURI);\n//    }\n//\n//    /**\n//     * 获取[跳转至裁剪界面,默认可缩放]的Intent\n//     */\n//    public static Intent getCropImageIntent(int aspectX, int aspectY, int outputX, int outputY, Uri fromFileURI,\n//                                            Uri saveFileURI) {\n//        return getCropImageIntent(aspectX, aspectY, outputX, outputY, true, fromFileURI, saveFileURI);\n//    }\n//\n//\n//    /**\n//     * 获取[跳转至裁剪界面]的Intent\n//     */\n//    public static Intent getCropImageIntent(int aspectX, int aspectY, int outputX, int outputY, boolean canScale,\n//                                            Uri fromFileURI, Uri saveFileURI) {\n//        Intent intent = new Intent(\"com.android.camera.action.CROP\");\n//        intent.setDataAndType(fromFileURI, \"image/*\");\n//        intent.putExtra(\"crop\", \"true\");\n//        // X方向上的比例\n//        intent.putExtra(\"aspectX\", aspectX <= 0 ? 1 : aspectX);\n//        // Y方向上的比例\n//        intent.putExtra(\"aspectY\", aspectY <= 0 ? 1 : aspectY);\n//        intent.putExtra(\"outputX\", outputX);\n//        intent.putExtra(\"outputY\", outputY);\n//        intent.putExtra(\"scale\", canScale);\n//        // 图片剪裁不足黑边解决\n//        intent.putExtra(\"scaleUpIfNeeded\", true);\n//        intent.putExtra(\"return-data\", false);\n//        // 需要将读取的文件路径和裁剪写入的路径区分，否则会造成文件0byte\n//        intent.putExtra(MediaStore.EXTRA_OUTPUT, saveFileURI);\n//        // true-->返回数据类型可以设置为Bitmap，但是不能传输太大，截大图用URI，小图用Bitmap或者全部使用URI\n//        intent.putExtra(\"outputFormat\", Bitmap.CompressFormat.JPEG.toString());\n//        // 取消人脸识别功能\n//        intent.putExtra(\"noFaceDetection\", true);\n//        return intent;\n//    }\n//\n//    /**\n//     * 获得选中相册的图片\n//     *\n//     * @param context 上下文\n//     * @param data    onActivityResult返回的Intent\n//     * @return bitmap\n//     */\n//    public static Bitmap getChoosedImage(final Activity context, final Intent data) {\n//        if (data == null) return null;\n//        Bitmap bm = null;\n//        ContentResolver cr = context.getContentResolver();\n//        Uri originalUri = data.getData();\n//        try {\n//            bm = MediaStore.Images.Media.getBitmap(cr, originalUri);\n//        } catch (IOException e) {\n//            e.printStackTrace();\n//        }\n//        return bm;\n//    }\n//\n//    /**\n//     * 获得选中相册的图片路径\n//     *\n//     * @param context 上下文\n//     * @param data    onActivityResult返回的Intent\n//     * @return\n//     */\n//    public static String getChoosedImagePath(final Activity context, final Intent data) {\n//        if (data == null) return null;\n//        String path = \"\";\n//        ContentResolver resolver = context.getContentResolver();\n//        Uri originalUri = data.getData();\n//        if (null == originalUri) return null;\n//        String[] projection = {MediaStore.Images.Media.DATA};\n//        Cursor cursor = resolver.query(originalUri, projection, null, null, null);\n//        if (null != cursor) {\n//            try {\n//                int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);\n//                cursor.moveToFirst();\n//                path = cursor.getString(column_index);\n//            } catch (IllegalArgumentException e) {\n//                e.printStackTrace();\n//            } finally {\n//                try {\n//                    if (!cursor.isClosed()) {\n//                        cursor.close();\n//                    }\n//                } catch (Exception e) {\n//                    e.printStackTrace();\n//                }\n//            }\n//        }\n//        return StringUtils.isEmpty(path) ? originalUri.getPath() : null;\n//    }\n//\n//    /**\n//     * 获取拍照之后的照片文件（JPG格式）\n//     *\n//     * @param data     onActivityResult回调返回的数据\n//     * @param filePath The path of file.\n//     * @return 文件\n//     */\n//    public static File getTakePictureFile(final Intent data, final String filePath) {\n//        if (data == null) return null;\n//        Bundle extras = data.getExtras();\n//        if (extras == null) return null;\n//        Bitmap photo = extras.getParcelable(\"data\");\n//        File file = new File(filePath);\n//        if (ImageUtils.save(photo, file, Bitmap.CompressFormat.JPEG)) return file;\n//        return null;\n//    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/CleanUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.Environment;\nimport androidx.annotation.RequiresApi;\n\nimport java.io.File;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/09/27\n *     desc  : utils about clean\n * </pre>\n */\npublic final class CleanUtils {\n\n    private CleanUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Clean the internal cache.\n     * <p>directory: /data/data/package/cache</p>\n     *\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean cleanInternalCache() {\n        return UtilsBridge.deleteAllInDir(Utils.getApp().getCacheDir());\n    }\n\n    /**\n     * Clean the internal files.\n     * <p>directory: /data/data/package/files</p>\n     *\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean cleanInternalFiles() {\n        return UtilsBridge.deleteAllInDir(Utils.getApp().getFilesDir());\n    }\n\n    /**\n     * Clean the internal databases.\n     * <p>directory: /data/data/package/databases</p>\n     *\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean cleanInternalDbs() {\n        return UtilsBridge.deleteAllInDir(new File(Utils.getApp().getFilesDir().getParent(), \"databases\"));\n    }\n\n    /**\n     * Clean the internal database by name.\n     * <p>directory: /data/data/package/databases/dbName</p>\n     *\n     * @param dbName The name of database.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean cleanInternalDbByName(final String dbName) {\n        return Utils.getApp().deleteDatabase(dbName);\n    }\n\n    /**\n     * Clean the internal shared preferences.\n     * <p>directory: /data/data/package/shared_prefs</p>\n     *\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean cleanInternalSp() {\n        return UtilsBridge.deleteAllInDir(new File(Utils.getApp().getFilesDir().getParent(), \"shared_prefs\"));\n    }\n\n    /**\n     * Clean the external cache.\n     * <p>directory: /storage/emulated/0/android/data/package/cache</p>\n     *\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean cleanExternalCache() {\n        return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())\n                && UtilsBridge.deleteAllInDir(Utils.getApp().getExternalCacheDir());\n    }\n\n    /**\n     * Clean the custom directory.\n     *\n     * @param dirPath The path of directory.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean cleanCustomDir(final String dirPath) {\n        return UtilsBridge.deleteAllInDir(UtilsBridge.getFileByPath(dirPath));\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.KITKAT)\n    public static void cleanAppUserData() {\n        ActivityManager am = (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);\n        //noinspection ConstantConditions\n        am.clearApplicationUserData();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ClickUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.ColorFilter;\nimport android.graphics.ColorMatrix;\nimport android.graphics.ColorMatrixColorFilter;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.StateListDrawable;\nimport android.os.Build;\nimport android.os.SystemClock;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.core.view.ViewCompat;\nimport android.util.Log;\nimport android.util.StateSet;\nimport android.view.MotionEvent;\nimport android.view.TouchDelegate;\nimport android.view.View;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/06/12\n *     desc  : utils about click\n * </pre>\n */\npublic class ClickUtils {\n\n    private static final int   PRESSED_VIEW_SCALE_TAG           = -1;\n    private static final float PRESSED_VIEW_SCALE_DEFAULT_VALUE = -0.06f;\n\n    private static final int   PRESSED_VIEW_ALPHA_TAG           = -2;\n    private static final int   PRESSED_VIEW_ALPHA_SRC_TAG       = -3;\n    private static final float PRESSED_VIEW_ALPHA_DEFAULT_VALUE = 0.8f;\n\n    private static final int   PRESSED_BG_ALPHA_STYLE         = 4;\n    private static final float PRESSED_BG_ALPHA_DEFAULT_VALUE = 0.9f;\n\n    private static final int   PRESSED_BG_DARK_STYLE         = 5;\n    private static final float PRESSED_BG_DARK_DEFAULT_VALUE = 0.9f;\n\n    private static final long DEBOUNCING_DEFAULT_VALUE = 1000;\n\n    private ClickUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Apply scale animation for the views' click.\n     *\n     * @param views The views.\n     */\n    public static void applyPressedViewScale(final View... views) {\n        applyPressedViewScale(views, null);\n    }\n\n    /**\n     * Apply scale animation for the views' click.\n     *\n     * @param views        The views.\n     * @param scaleFactors The factors of scale for the views.\n     */\n    public static void applyPressedViewScale(final View[] views, final float[] scaleFactors) {\n        if (views == null || views.length == 0) {\n            return;\n        }\n        for (int i = 0; i < views.length; i++) {\n            if (scaleFactors == null || i >= scaleFactors.length) {\n                applyPressedViewScale(views[i], PRESSED_VIEW_SCALE_DEFAULT_VALUE);\n            } else {\n                applyPressedViewScale(views[i], scaleFactors[i]);\n            }\n        }\n    }\n\n    /**\n     * Apply scale animation for the views' click.\n     *\n     * @param view        The view.\n     * @param scaleFactor The factor of scale for the view.\n     */\n    public static void applyPressedViewScale(final View view, final float scaleFactor) {\n        if (view == null) {\n            return;\n        }\n        view.setTag(PRESSED_VIEW_SCALE_TAG, scaleFactor);\n        view.setClickable(true);\n        view.setOnTouchListener(OnUtilsTouchListener.getInstance());\n    }\n\n    /**\n     * Apply alpha for the views' click.\n     *\n     * @param views The views.\n     */\n    public static void applyPressedViewAlpha(final View... views) {\n        applyPressedViewAlpha(views, null);\n    }\n\n    /**\n     * Apply alpha for the views' click.\n     *\n     * @param views  The views.\n     * @param alphas The alphas for the views.\n     */\n    public static void applyPressedViewAlpha(final View[] views, final float[] alphas) {\n        if (views == null || views.length == 0) return;\n        for (int i = 0; i < views.length; i++) {\n            if (alphas == null || i >= alphas.length) {\n                applyPressedViewAlpha(views[i], PRESSED_VIEW_ALPHA_DEFAULT_VALUE);\n            } else {\n                applyPressedViewAlpha(views[i], alphas[i]);\n            }\n        }\n    }\n\n\n    /**\n     * Apply scale animation for the views' click.\n     *\n     * @param view  The view.\n     * @param alpha The alpha for the view.\n     */\n    public static void applyPressedViewAlpha(final View view, final float alpha) {\n        if (view == null) {\n            return;\n        }\n        view.setTag(PRESSED_VIEW_ALPHA_TAG, alpha);\n        view.setTag(PRESSED_VIEW_ALPHA_SRC_TAG, view.getAlpha());\n        view.setClickable(true);\n        view.setOnTouchListener(OnUtilsTouchListener.getInstance());\n    }\n\n    /**\n     * Apply alpha for the view's background.\n     *\n     * @param view The views.\n     */\n    public static void applyPressedBgAlpha(View view) {\n        applyPressedBgAlpha(view, PRESSED_BG_ALPHA_DEFAULT_VALUE);\n    }\n\n    /**\n     * Apply alpha for the view's background.\n     *\n     * @param view  The views.\n     * @param alpha The alpha.\n     */\n    public static void applyPressedBgAlpha(View view, float alpha) {\n        applyPressedBgStyle(view, PRESSED_BG_ALPHA_STYLE, alpha);\n    }\n\n    /**\n     * Apply alpha of dark for the view's background.\n     *\n     * @param view The views.\n     */\n    public static void applyPressedBgDark(View view) {\n        applyPressedBgDark(view, PRESSED_BG_DARK_DEFAULT_VALUE);\n    }\n\n    /**\n     * Apply alpha of dark for the view's background.\n     *\n     * @param view      The views.\n     * @param darkAlpha The alpha of dark.\n     */\n    public static void applyPressedBgDark(View view, float darkAlpha) {\n        applyPressedBgStyle(view, PRESSED_BG_DARK_STYLE, darkAlpha);\n    }\n\n    private static void applyPressedBgStyle(View view, int style, float value) {\n        if (view == null) return;\n        Drawable background = view.getBackground();\n        Object tag = view.getTag(-style);\n        if (tag instanceof Drawable) {\n            ViewCompat.setBackground(view, (Drawable) tag);\n        } else {\n            background = createStyleDrawable(background, style, value);\n            ViewCompat.setBackground(view, background);\n            view.setTag(-style, background);\n        }\n    }\n\n    private static Drawable createStyleDrawable(Drawable src, int style, float value) {\n        if (src == null) {\n            src = new ColorDrawable(0);\n        }\n        if (src.getConstantState() == null) return src;\n\n        Drawable pressed = src.getConstantState().newDrawable().mutate();\n        if (style == PRESSED_BG_ALPHA_STYLE) {\n            pressed = createAlphaDrawable(pressed, value);\n        } else if (style == PRESSED_BG_DARK_STYLE) {\n            pressed = createDarkDrawable(pressed, value);\n        }\n\n        Drawable disable = src.getConstantState().newDrawable().mutate();\n        disable = createAlphaDrawable(disable, 0.5f);\n\n        StateListDrawable drawable = new StateListDrawable();\n        drawable.addState(new int[]{android.R.attr.state_pressed}, pressed);\n        drawable.addState(new int[]{-android.R.attr.state_enabled}, disable);\n        drawable.addState(StateSet.WILD_CARD, src);\n        return drawable;\n    }\n\n    private static Drawable createAlphaDrawable(Drawable drawable, float alpha) {\n        ClickDrawableWrapper drawableWrapper = new ClickDrawableWrapper(drawable);\n        drawableWrapper.setAlpha((int) (alpha * 255));\n        return drawableWrapper;\n    }\n\n    private static Drawable createDarkDrawable(Drawable drawable, float alpha) {\n        ClickDrawableWrapper drawableWrapper = new ClickDrawableWrapper(drawable);\n        drawableWrapper.setColorFilter(getDarkColorFilter(alpha));\n        return drawableWrapper;\n    }\n\n    private static ColorMatrixColorFilter getDarkColorFilter(float darkAlpha) {\n        return new ColorMatrixColorFilter(new ColorMatrix(new float[]{\n                darkAlpha, 0, 0, 0, 0,\n                0, darkAlpha, 0, 0, 0,\n                0, 0, darkAlpha, 0, 0,\n                0, 0, 0, 2, 0\n        }));\n    }\n\n    /**\n     * Apply single debouncing for the view's click.\n     *\n     * @param view     The view.\n     * @param listener The listener.\n     */\n    public static void applySingleDebouncing(final View view, final View.OnClickListener listener) {\n        applySingleDebouncing(new View[]{view}, listener);\n    }\n\n    /**\n     * Apply single debouncing for the view's click.\n     *\n     * @param view     The view.\n     * @param duration The duration of debouncing.\n     * @param listener The listener.\n     */\n    public static void applySingleDebouncing(final View view, @IntRange(from = 0) long duration,\n                                             final View.OnClickListener listener) {\n        applySingleDebouncing(new View[]{view}, duration, listener);\n    }\n\n    /**\n     * Apply single debouncing for the views' click.\n     *\n     * @param views    The views.\n     * @param listener The listener.\n     */\n    public static void applySingleDebouncing(final View[] views, final View.OnClickListener listener) {\n        applySingleDebouncing(views, DEBOUNCING_DEFAULT_VALUE, listener);\n    }\n\n    /**\n     * Apply single debouncing for the views' click.\n     *\n     * @param views    The views.\n     * @param duration The duration of debouncing.\n     * @param listener The listener.\n     */\n    public static void applySingleDebouncing(final View[] views,\n                                             @IntRange(from = 0) long duration,\n                                             final View.OnClickListener listener) {\n        applyDebouncing(views, false, duration, listener);\n    }\n\n    /**\n     * Apply global debouncing for the view's click.\n     *\n     * @param view     The view.\n     * @param listener The listener.\n     */\n    public static void applyGlobalDebouncing(final View view, final View.OnClickListener listener) {\n        applyGlobalDebouncing(new View[]{view}, listener);\n    }\n\n    /**\n     * Apply global debouncing for the view's click.\n     *\n     * @param view     The view.\n     * @param duration The duration of debouncing.\n     * @param listener The listener.\n     */\n    public static void applyGlobalDebouncing(final View view, @IntRange(from = 0) long duration,\n                                             final View.OnClickListener listener) {\n        applyGlobalDebouncing(new View[]{view}, duration, listener);\n    }\n\n\n    /**\n     * Apply global debouncing for the views' click.\n     *\n     * @param views    The views.\n     * @param listener The listener.\n     */\n    public static void applyGlobalDebouncing(final View[] views, final View.OnClickListener listener) {\n        applyGlobalDebouncing(views, DEBOUNCING_DEFAULT_VALUE, listener);\n    }\n\n    /**\n     * Apply global debouncing for the views' click.\n     *\n     * @param views    The views.\n     * @param duration The duration of debouncing.\n     * @param listener The listener.\n     */\n    public static void applyGlobalDebouncing(final View[] views,\n                                             @IntRange(from = 0) long duration,\n                                             final View.OnClickListener listener) {\n        applyDebouncing(views, true, duration, listener);\n    }\n\n    private static void applyDebouncing(final View[] views,\n                                        final boolean isGlobal,\n                                        @IntRange(from = 0) long duration,\n                                        final View.OnClickListener listener) {\n        if (views == null || views.length == 0 || listener == null) return;\n        for (View view : views) {\n            if (view == null) continue;\n            view.setOnClickListener(new OnDebouncingClickListener(isGlobal, duration) {\n                @Override\n                public void onDebouncingClick(View v) {\n                    listener.onClick(v);\n                }\n            });\n        }\n    }\n\n    /**\n     * Expand the click area of ​​the view\n     *\n     * @param view       The view.\n     * @param expandSize The size.\n     */\n    public static void expandClickArea(@NonNull final View view, final int expandSize) {\n        expandClickArea(view, expandSize, expandSize, expandSize, expandSize);\n    }\n\n    public static void expandClickArea(@NonNull final View view,\n                                       final int expandSizeTop,\n                                       final int expandSizeLeft,\n                                       final int expandSizeRight,\n                                       final int expandSizeBottom) {\n        final View parentView = (View) view.getParent();\n        if (parentView == null) {\n            Log.e(\"ClickUtils\", \"expandClickArea must have parent view.\");\n            return;\n        }\n        parentView.post(new Runnable() {\n            @Override\n            public void run() {\n                final Rect rect = new Rect();\n                view.getHitRect(rect);\n                rect.top -= expandSizeTop;\n                rect.bottom += expandSizeBottom;\n                rect.left -= expandSizeLeft;\n                rect.right += expandSizeRight;\n                parentView.setTouchDelegate(new TouchDelegate(rect, view));\n            }\n        });\n    }\n\n    private static final long TIP_DURATION = 2000L;\n    private static       long sLastClickMillis;\n    private static       int  sClickCount;\n\n    public static void back2HomeFriendly(final CharSequence tip) {\n        back2HomeFriendly(tip, TIP_DURATION, Back2HomeFriendlyListener.DEFAULT);\n    }\n\n    public static void back2HomeFriendly(@NonNull final CharSequence tip,\n                                         final long duration,\n                                         @NonNull Back2HomeFriendlyListener listener) {\n        long nowMillis = SystemClock.elapsedRealtime();\n        if (Math.abs(nowMillis - sLastClickMillis) < duration) {\n            sClickCount++;\n            if (sClickCount == 2) {\n                UtilsBridge.startHomeActivity();\n                listener.dismiss();\n                sLastClickMillis = 0;\n            }\n        } else {\n            sClickCount = 1;\n            listener.show(tip, duration);\n            sLastClickMillis = nowMillis;\n        }\n    }\n\n    public interface Back2HomeFriendlyListener {\n        Back2HomeFriendlyListener DEFAULT = new Back2HomeFriendlyListener() {\n            @Override\n            public void show(CharSequence text, long duration) {\n                UtilsBridge.toastShowShort(text);\n            }\n\n            @Override\n            public void dismiss() {\n                UtilsBridge.toastCancel();\n            }\n        };\n\n        void show(CharSequence text, long duration);\n\n        void dismiss();\n    }\n\n    public static abstract class OnDebouncingClickListener implements View.OnClickListener {\n\n        private static boolean mEnabled = true;\n\n        private static final Runnable ENABLE_AGAIN = new Runnable() {\n            @Override\n            public void run() {\n                mEnabled = true;\n            }\n        };\n\n        private static boolean isValid(@NonNull final View view, final long duration) {\n            return UtilsBridge.isValid(view, duration);\n        }\n\n        private long    mDuration;\n        private boolean mIsGlobal;\n\n        public OnDebouncingClickListener() {\n            this(true, DEBOUNCING_DEFAULT_VALUE);\n        }\n\n        public OnDebouncingClickListener(final boolean isGlobal) {\n            this(isGlobal, DEBOUNCING_DEFAULT_VALUE);\n        }\n\n        public OnDebouncingClickListener(final long duration) {\n            this(true, duration);\n        }\n\n        public OnDebouncingClickListener(final boolean isGlobal, final long duration) {\n            mIsGlobal = isGlobal;\n            mDuration = duration;\n        }\n\n        public abstract void onDebouncingClick(View v);\n\n        @Override\n        public final void onClick(View v) {\n            if (mIsGlobal) {\n                if (mEnabled) {\n                    mEnabled = false;\n                    v.postDelayed(ENABLE_AGAIN, mDuration);\n                    onDebouncingClick(v);\n                }\n            } else {\n                if (isValid(v, mDuration)) {\n                    onDebouncingClick(v);\n                }\n            }\n        }\n    }\n\n    public static abstract class OnMultiClickListener implements View.OnClickListener {\n\n        private static final long INTERVAL_DEFAULT_VALUE = 666;\n\n        private final int  mTriggerClickCount;\n        private final long mClickInterval;\n\n        private long mLastClickTime;\n        private int  mClickCount;\n\n        public OnMultiClickListener(int triggerClickCount) {\n            this(triggerClickCount, INTERVAL_DEFAULT_VALUE);\n        }\n\n        public OnMultiClickListener(int triggerClickCount, long clickInterval) {\n            this.mTriggerClickCount = triggerClickCount;\n            this.mClickInterval = clickInterval;\n        }\n\n        public abstract void onTriggerClick(View v);\n\n        public abstract void onBeforeTriggerClick(View v, int count);\n\n        @Override\n        public void onClick(View v) {\n            if (mTriggerClickCount <= 1) {\n                onTriggerClick(v);\n                return;\n            }\n            long curTime = System.currentTimeMillis();\n\n            if (curTime - mLastClickTime < mClickInterval) {\n                mClickCount++;\n                if (mClickCount == mTriggerClickCount) {\n                    onTriggerClick(v);\n                } else if (mClickCount < mTriggerClickCount) {\n                    onBeforeTriggerClick(v, mClickCount);\n                } else {\n                    mClickCount = 1;\n                    onBeforeTriggerClick(v, mClickCount);\n                }\n            } else {\n                mClickCount = 1;\n                onBeforeTriggerClick(v, mClickCount);\n            }\n            mLastClickTime = curTime;\n        }\n    }\n\n    private static class OnUtilsTouchListener implements View.OnTouchListener {\n\n        public static OnUtilsTouchListener getInstance() {\n            return LazyHolder.INSTANCE;\n        }\n\n        private OnUtilsTouchListener() {/**/}\n\n        @Override\n        public boolean onTouch(final View v, MotionEvent event) {\n            int action = event.getAction();\n            if (action == MotionEvent.ACTION_DOWN) {\n                processScale(v, true);\n                processAlpha(v, true);\n            } else if (action == MotionEvent.ACTION_UP\n                    || action == MotionEvent.ACTION_CANCEL) {\n                processScale(v, false);\n                processAlpha(v, false);\n            }\n            return false;\n        }\n\n        private void processScale(final View view, boolean isDown) {\n            Object tag = view.getTag(PRESSED_VIEW_SCALE_TAG);\n            if (!(tag instanceof Float)) return;\n            float value = isDown ? 1 + (Float) tag : 1;\n            view.animate()\n                    .scaleX(value)\n                    .scaleY(value)\n                    .setDuration(200)\n                    .start();\n        }\n\n        private void processAlpha(final View view, boolean isDown) {\n            Object tag = view.getTag(isDown ? PRESSED_VIEW_ALPHA_TAG : PRESSED_VIEW_ALPHA_SRC_TAG);\n            if (!(tag instanceof Float)) return;\n            view.setAlpha((Float) tag);\n        }\n\n        private static class LazyHolder {\n            private static final OnUtilsTouchListener INSTANCE = new OnUtilsTouchListener();\n        }\n    }\n\n    static class ClickDrawableWrapper extends ShadowUtils.DrawableWrapper {\n\n        private BitmapDrawable mBitmapDrawable = null;\n\n        // 低版本ColorDrawable.setColorFilter无效，这里直接用画笔画上\n        private Paint mColorPaint = null;\n\n        public ClickDrawableWrapper(Drawable drawable) {\n            super(drawable);\n            if (drawable instanceof ColorDrawable) {\n                mColorPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);\n                mColorPaint.setColor(((ColorDrawable) drawable).getColor());\n            }\n        }\n\n        @Override\n        public void setColorFilter(ColorFilter cf) {\n            super.setColorFilter(cf);\n            // 低版本 StateListDrawable.selectDrawable 会重置 ColorFilter\n            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {\n                if (mColorPaint != null) {\n                    mColorPaint.setColorFilter(cf);\n                }\n            }\n        }\n\n        @Override\n        public void setAlpha(int alpha) {\n            super.setAlpha(alpha);\n            // 低版本 StateListDrawable.selectDrawable 会重置 Alpha\n            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {\n                if (mColorPaint != null) {\n                    mColorPaint.setColor(((ColorDrawable) getWrappedDrawable()).getColor());\n                }\n            }\n        }\n\n        @Override\n        public void draw(Canvas canvas) {\n            if (mBitmapDrawable == null) {\n                Bitmap bitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Bitmap.Config.ARGB_8888);\n                Canvas myCanvas = new Canvas(bitmap);\n                if (mColorPaint != null) {\n                    myCanvas.drawRect(getBounds(), mColorPaint);\n                } else {\n                    super.draw(myCanvas);\n                }\n                mBitmapDrawable = new BitmapDrawable(Resources.getSystem(), bitmap);\n                mBitmapDrawable.setBounds(getBounds());\n            }\n            mBitmapDrawable.draw(canvas);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ClipboardUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.ClipData;\nimport android.content.ClipDescription;\nimport android.content.ClipboardManager;\nimport android.content.Context;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/09/25\n *     desc  : utils about clipboard\n * </pre>\n */\npublic final class ClipboardUtils {\n\n    private ClipboardUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Copy the text to clipboard.\n     * <p>The label equals name of package.</p>\n     *\n     * @param text The text.\n     */\n    public static void copyText(final CharSequence text) {\n        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);\n        //noinspection ConstantConditions\n        cm.setPrimaryClip(ClipData.newPlainText(Utils.getApp().getPackageName(), text));\n    }\n\n    /**\n     * Copy the text to clipboard.\n     *\n     * @param label The label.\n     * @param text  The text.\n     */\n    public static void copyText(final CharSequence label, final CharSequence text) {\n        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);\n        //noinspection ConstantConditions\n        cm.setPrimaryClip(ClipData.newPlainText(label, text));\n    }\n\n    /**\n     * Clear the clipboard.\n     */\n    public static void clear() {\n        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);\n        //noinspection ConstantConditions\n        cm.setPrimaryClip(ClipData.newPlainText(null, \"\"));\n    }\n\n    /**\n     * Return the label for clipboard.\n     *\n     * @return the label for clipboard\n     */\n    public static CharSequence getLabel() {\n        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);\n        //noinspection ConstantConditions\n        ClipDescription des = cm.getPrimaryClipDescription();\n        if (des == null) {\n            return \"\";\n        }\n        CharSequence label = des.getLabel();\n        if (label == null) {\n            return \"\";\n        }\n        return label;\n    }\n\n    /**\n     * Return the text for clipboard.\n     *\n     * @return the text for clipboard\n     */\n    public static CharSequence getText() {\n        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);\n        //noinspection ConstantConditions\n        ClipData clip = cm.getPrimaryClip();\n        if (clip != null && clip.getItemCount() > 0) {\n            CharSequence text = clip.getItemAt(0).coerceToText(Utils.getApp());\n            if (text != null) {\n                return text;\n            }\n        }\n        return \"\";\n    }\n\n    /**\n     * Add the clipboard changed listener.\n     */\n    public static void addChangedListener(final ClipboardManager.OnPrimaryClipChangedListener listener) {\n        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);\n        //noinspection ConstantConditions\n        cm.addPrimaryClipChangedListener(listener);\n    }\n\n    /**\n     * Remove the clipboard changed listener.\n     */\n    public static void removeChangedListener(final ClipboardManager.OnPrimaryClipChangedListener listener) {\n        ClipboardManager cm = (ClipboardManager) Utils.getApp().getSystemService(Context.CLIPBOARD_SERVICE);\n        //noinspection ConstantConditions\n        cm.removePrimaryClipChangedListener(listener);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/CloneUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport java.lang.reflect.Type;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/01/30\n *     desc  : utils about clone\n * </pre>\n */\npublic final class CloneUtils {\n\n    private CloneUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Deep clone.\n     *\n     * @param data The data.\n     * @param type The type.\n     * @param <T>  The value type.\n     * @return The object of cloned.\n     */\n    public static <T> T deepClone(final T data, final Type type) {\n        try {\n            return UtilsBridge.fromJson(UtilsBridge.toJson(data), type);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/CloseUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport java.io.Closeable;\nimport java.io.IOException;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/10/09\n *     desc  : utils about close\n * </pre>\n */\npublic final class CloseUtils {\n\n    private CloseUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Close the io stream.\n     *\n     * @param closeables The closeables.\n     */\n    public static void closeIO(final Closeable... closeables) {\n        if (closeables == null) return;\n        for (Closeable closeable : closeables) {\n            if (closeable != null) {\n                try {\n                    closeable.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * Close the io stream quietly.\n     *\n     * @param closeables The closeables.\n     */\n    public static void closeIOQuietly(final Closeable... closeables) {\n        if (closeables == null) return;\n        for (Closeable closeable : closeables) {\n            if (closeable != null) {\n                try {\n                    closeable.close();\n                } catch (IOException ignored) {\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/CollectionUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport java.lang.reflect.Array;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Enumeration;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.ListIterator;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.TreeSet;\n\n/**\n * <pre>\n *     author: blankj\n *     blog  : http://blankj.com\n *     time  : 2019/07/26\n *     desc  : utils about collection\n * </pre>\n */\npublic final class CollectionUtils {\n\n    private CollectionUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // listOf\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Returns a new read-only list of given elements.\n     *\n     * @param array The array.\n     * @return a new read-only list of given elements\n     */\n    @SafeVarargs\n    public static <E> List<E> newUnmodifiableList(E... array) {\n        return Collections.unmodifiableList(newArrayList(array));\n    }\n\n    /**\n     * Returns a new read-only list only of those given elements, that are not null.\n     *\n     * @param array The array.\n     * @return a new read-only list only of those given elements, that are not null\n     */\n    @SafeVarargs\n    public static <E> List<E> newUnmodifiableListNotNull(E... array) {\n        return Collections.unmodifiableList(newArrayListNotNull(array));\n    }\n\n    @SafeVarargs\n    public static <E> ArrayList<E> newArrayList(E... array) {\n        ArrayList<E> list = new ArrayList<>();\n        if (array == null || array.length == 0) return list;\n        for (E e : array) {\n            list.add(e);\n        }\n        return list;\n    }\n\n    @SafeVarargs\n    public static <E> ArrayList<E> newArrayListNotNull(E... array) {\n        ArrayList<E> list = new ArrayList<>();\n        if (array == null || array.length == 0) return list;\n        for (E e : array) {\n            if (e == null) continue;\n            list.add(e);\n        }\n        return list;\n    }\n\n    @SafeVarargs\n    public static <E> LinkedList<E> newLinkedList(E... array) {\n        LinkedList<E> list = new LinkedList<>();\n        if (array == null || array.length == 0) return list;\n        for (E e : array) {\n            list.add(e);\n        }\n        return list;\n    }\n\n    @SafeVarargs\n    public static <E> LinkedList<E> newLinkedListNotNull(E... array) {\n        LinkedList<E> list = new LinkedList<>();\n        if (array == null || array.length == 0) return list;\n        for (E e : array) {\n            if (e == null) continue;\n            list.add(e);\n        }\n        return list;\n    }\n\n    @SafeVarargs\n    public static <E> HashSet<E> newHashSet(E... array) {\n        HashSet<E> set = new HashSet<>();\n        if (array == null || array.length == 0) return set;\n        for (E e : array) {\n            set.add(e);\n        }\n        return set;\n    }\n\n    @SafeVarargs\n    public static <E> HashSet<E> newHashSetNotNull(E... array) {\n        HashSet<E> set = new HashSet<>();\n        if (array == null || array.length == 0) return set;\n        for (E e : array) {\n            if (e == null) continue;\n            set.add(e);\n        }\n        return set;\n    }\n\n    @SafeVarargs\n    public static <E> TreeSet<E> newTreeSet(Comparator<E> comparator, E... array) {\n        TreeSet<E> set = new TreeSet<>(comparator);\n        if (array == null || array.length == 0) return set;\n        for (E e : array) {\n            set.add(e);\n        }\n        return set;\n    }\n\n    @SafeVarargs\n    public static <E> TreeSet<E> newTreeSetNotNull(Comparator<E> comparator, E... array) {\n        TreeSet<E> set = new TreeSet<>(comparator);\n        if (array == null || array.length == 0) return set;\n        for (E e : array) {\n            if (e == null) continue;\n            set.add(e);\n        }\n        return set;\n    }\n\n    public static Collection newSynchronizedCollection(Collection collection) {\n        return Collections.synchronizedCollection(collection);\n    }\n\n    public static Collection newUnmodifiableCollection(Collection collection) {\n        return Collections.unmodifiableCollection(collection);\n    }\n\n    /**\n     * Returns a {@link Collection} containing the union\n     * of the given {@link Collection}s.\n     * <p>\n     * The cardinality of each element in the returned {@link Collection}\n     * will be equal to the maximum of the cardinality of that element\n     * in the two given {@link Collection}s.\n     *\n     * @param a the first collection\n     * @param b the second collection\n     * @return the union of the two collections\n     * @see Collection#addAll\n     */\n    public static Collection union(final Collection a, final Collection b) {\n        if (a == null && b == null) return new ArrayList();\n        if (a == null) return new ArrayList<Object>(b);\n        if (b == null) return new ArrayList<Object>(a);\n        ArrayList<Object> list = new ArrayList<>();\n        Map<Object, Integer> mapA = getCardinalityMap(a);\n        Map<Object, Integer> mapB = getCardinalityMap(b);\n        Set<Object> elts = new HashSet<Object>(a);\n        elts.addAll(b);\n        for (Object obj : elts) {\n            for (int i = 0, m = Math.max(getFreq(obj, mapA), getFreq(obj, mapB)); i < m; i++) {\n                list.add(obj);\n            }\n        }\n        return list;\n    }\n\n    /**\n     * Returns a {@link Collection} containing the intersection\n     * of the given {@link Collection}s.\n     * <p>\n     * The cardinality of each element in the returned {@link Collection}\n     * will be equal to the minimum of the cardinality of that element\n     * in the two given {@link Collection}s.\n     *\n     * @param a the first collection\n     * @param b the second collection\n     * @return the intersection of the two collections\n     * @see Collection#retainAll\n     */\n    public static Collection intersection(final Collection a, final Collection b) {\n        if (a == null || b == null) return new ArrayList();\n        ArrayList<Object> list = new ArrayList<>();\n        Map mapA = getCardinalityMap(a);\n        Map mapB = getCardinalityMap(b);\n        Set<Object> elts = new HashSet<Object>(a);\n        elts.addAll(b);\n        for (Object obj : elts) {\n            for (int i = 0, m = Math.min(getFreq(obj, mapA), getFreq(obj, mapB)); i < m; i++) {\n                list.add(obj);\n            }\n        }\n        return list;\n    }\n\n    private static int getFreq(final Object obj, final Map freqMap) {\n        Integer count = (Integer) freqMap.get(obj);\n        if (count != null) {\n            return count;\n        }\n        return 0;\n    }\n\n    /**\n     * Returns a {@link Collection} containing the exclusive disjunction\n     * (symmetric difference) of the given {@link Collection}s.\n     * <p>\n     * The cardinality of each element <i>e</i> in the returned {@link Collection}\n     * will be equal to\n     * <tt>max(cardinality(<i>e</i>,<i>a</i>),cardinality(<i>e</i>,<i>b</i>)) - min(cardinality(<i>e</i>,<i>a</i>),cardinality(<i>e</i>,<i>b</i>))</tt>.\n     * <p>\n     * This is equivalent to\n     * <tt>{@link #subtract subtract}({@link #union union(a,b)},{@link #intersection intersection(a,b)})</tt>\n     * or\n     * <tt>{@link #union union}({@link #subtract subtract(a,b)},{@link #subtract subtract(b,a)})</tt>.\n     *\n     * @param a the first collection\n     * @param b the second collection\n     * @return the symmetric difference of the two collections\n     */\n    public static Collection disjunction(final Collection a, final Collection b) {\n        if (a == null && b == null) return new ArrayList();\n        if (a == null) return new ArrayList<Object>(b);\n        if (b == null) return new ArrayList<Object>(a);\n        ArrayList<Object> list = new ArrayList<>();\n        Map mapA = getCardinalityMap(a);\n        Map mapB = getCardinalityMap(b);\n        Set<Object> elts = new HashSet<Object>(a);\n        elts.addAll(b);\n        for (Object obj : elts) {\n            for (int i = 0, m = ((Math.max(getFreq(obj, mapA), getFreq(obj, mapB)))\n                    - (Math.min(getFreq(obj, mapA), getFreq(obj, mapB)))); i < m; i++) {\n                list.add(obj);\n            }\n        }\n        return list;\n    }\n\n    /**\n     * Returns a new {@link Collection} containing <tt><i>a</i> - <i>b</i></tt>.\n     * The cardinality of each element <i>e</i> in the returned {@link Collection}\n     * will be the cardinality of <i>e</i> in <i>a</i> minus the cardinality\n     * of <i>e</i> in <i>b</i>, or zero, whichever is greater.\n     *\n     * @param a the collection to subtract from\n     * @param b the collection to subtract\n     * @return a new collection with the results\n     * @see Collection#removeAll\n     */\n    public static Collection subtract(final Collection a, final Collection b) {\n        if (a == null) return new ArrayList();\n        if (b == null) return new ArrayList<Object>(a);\n        ArrayList<Object> list = new ArrayList<Object>(a);\n        for (Object o : b) {\n            list.remove(o);\n        }\n        return list;\n    }\n\n    /**\n     * Returns <code>true</code> iff at least one element is in both collections.\n     * <p>\n     * In other words, this method returns <code>true</code> iff the\n     * {@link #intersection} of <i>coll1</i> and <i>coll2</i> is not empty.\n     *\n     * @param coll1 the first collection\n     * @param coll2 the first collection\n     * @return <code>true</code> iff the intersection of the collections is non-empty\n     * @see #intersection\n     */\n    public static boolean containsAny(final Collection coll1, final Collection coll2) {\n        if (coll1 == null || coll2 == null) return false;\n        if (coll1.size() < coll2.size()) {\n            for (Object o : coll1) {\n                if (coll2.contains(o)) {\n                    return true;\n                }\n            }\n        } else {\n            for (Object o : coll2) {\n                if (coll1.contains(o)) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n\n    /**\n     * Returns a {@link Map} mapping each unique element in the given\n     * {@link Collection} to an {@link Integer} representing the number\n     * of occurrences of that element in the {@link Collection}.\n     * <p>\n     * Only those elements present in the collection will appear as\n     * keys in the map.\n     *\n     * @param coll the collection to get the cardinality map for, must not be null\n     * @return the populated cardinality map\n     */\n    public static Map<Object, Integer> getCardinalityMap(final Collection coll) {\n        Map<Object, Integer> count = new HashMap<>();\n        if (coll == null) return count;\n        for (Object obj : coll) {\n            Integer c = count.get(obj);\n            if (c == null) {\n                count.put(obj, 1);\n            } else {\n                count.put(obj, c + 1);\n            }\n        }\n        return count;\n    }\n\n    /**\n     * Returns <tt>true</tt> iff <i>a</i> is a sub-collection of <i>b</i>,\n     * that is, iff the cardinality of <i>e</i> in <i>a</i> is less\n     * than or equal to the cardinality of <i>e</i> in <i>b</i>,\n     * for each element <i>e</i> in <i>a</i>.\n     *\n     * @param a the first (sub?) collection\n     * @param b the second (super?) collection\n     * @return <code>true</code> iff <i>a</i> is a sub-collection of <i>b</i>\n     * @see #isProperSubCollection\n     * @see Collection#containsAll\n     */\n    public static boolean isSubCollection(final Collection a, final Collection b) {\n        if (a == null || b == null) return false;\n        Map mapA = getCardinalityMap(a);\n        Map mapB = getCardinalityMap(b);\n        for (Object obj : a) {\n            if (getFreq(obj, mapA) > getFreq(obj, mapB)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Returns <tt>true</tt> iff <i>a</i> is a <i>proper</i> sub-collection of <i>b</i>,\n     * that is, iff the cardinality of <i>e</i> in <i>a</i> is less\n     * than or equal to the cardinality of <i>e</i> in <i>b</i>,\n     * for each element <i>e</i> in <i>a</i>, and there is at least one\n     * element <i>f</i> such that the cardinality of <i>f</i> in <i>b</i>\n     * is strictly greater than the cardinality of <i>f</i> in <i>a</i>.\n     * <p>\n     * The implementation assumes\n     * <ul>\n     * <li><code>a.size()</code> and <code>b.size()</code> represent the\n     * total cardinality of <i>a</i> and <i>b</i>, resp. </li>\n     * <li><code>a.size() < Integer.MAXVALUE</code></li>\n     * </ul>\n     *\n     * @param a the first (sub?) collection\n     * @param b the second (super?) collection\n     * @return <code>true</code> iff <i>a</i> is a <i>proper</i> sub-collection of <i>b</i>\n     * @see #isSubCollection\n     * @see Collection#containsAll\n     */\n    public static boolean isProperSubCollection(final Collection a, final Collection b) {\n        if (a == null || b == null) return false;\n        return a.size() < b.size() && isSubCollection(a, b);\n    }\n\n    /**\n     * Returns <tt>true</tt> iff the given {@link Collection}s contain\n     * exactly the same elements with exactly the same cardinalities.\n     * <p>\n     * That is, iff the cardinality of <i>e</i> in <i>a</i> is\n     * equal to the cardinality of <i>e</i> in <i>b</i>,\n     * for each element <i>e</i> in <i>a</i> or <i>b</i>.\n     *\n     * @param a the first collection\n     * @param b the second collection\n     * @return <code>true</code> iff the collections contain the same elements with the same cardinalities.\n     */\n    public static boolean isEqualCollection(final Collection a, final Collection b) {\n        if (a == null || b == null) return false;\n        if (a.size() != b.size()) {\n            return false;\n        } else {\n            Map mapA = getCardinalityMap(a);\n            Map mapB = getCardinalityMap(b);\n            if (mapA.size() != mapB.size()) {\n                return false;\n            } else {\n                for (Object obj : mapA.keySet()) {\n                    if (getFreq(obj, mapA) != getFreq(obj, mapB)) {\n                        return false;\n                    }\n                }\n                return true;\n            }\n        }\n    }\n\n    /**\n     * Returns the number of occurrences of <i>obj</i> in <i>coll</i>.\n     *\n     * @param obj  the object to find the cardinality of\n     * @param coll the collection to search\n     * @return the the number of occurrences of obj in coll\n     */\n    public static <E> int cardinality(E obj, final Collection<E> coll) {\n        if (coll == null) return 0;\n        if (coll instanceof Set) {\n            return (coll.contains(obj) ? 1 : 0);\n        }\n        int count = 0;\n        if (obj == null) {\n            for (E e : coll) {\n                if (e == null) {\n                    count++;\n                }\n            }\n        } else {\n            for (E e : coll) {\n                if (obj.equals(e)) {\n                    count++;\n                }\n            }\n        }\n        return count;\n    }\n\n    /**\n     * Finds the first element in the given collection which matches the given predicate.\n     * <p>\n     * If the input collection or predicate is null, or no element of the collection\n     * matches the predicate, null is returned.\n     *\n     * @param collection the collection to search, may be null\n     * @param predicate  the predicate to use, may be null\n     * @return the first element of the collection which matches the predicate or null if none could be found\n     */\n    public static <E> E find(Collection<E> collection, Predicate<E> predicate) {\n        if (collection == null || predicate == null) return null;\n        for (E item : collection) {\n            if (predicate.evaluate(item)) {\n                return item;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Executes the given closure on each element in the collection.\n     * <p>\n     * If the input collection or closure is null, there is no change made.\n     *\n     * @param collection the collection to get the input from, may be null\n     * @param closure    the closure to perform, may be null\n     */\n    public static <E> void forAllDo(Collection<E> collection, Closure<E> closure) {\n        if (collection == null || closure == null) return;\n        int index = 0;\n        for (E e : collection) {\n            closure.execute(index++, e);\n        }\n    }\n\n    /**\n     * Filter the collection by applying a Predicate to each element. If the\n     * predicate returns false, remove the element.\n     * <p>\n     * If the input collection or predicate is null, there is no change made.\n     *\n     * @param collection the collection to get the input from, may be null\n     * @param predicate  the predicate to use as a filter, may be null\n     */\n    public static <E> void filter(Collection<E> collection, Predicate<E> predicate) {\n        if (collection == null || predicate == null) return;\n        for (Iterator it = collection.iterator(); it.hasNext(); ) {\n            if (!predicate.evaluate((E) it.next())) {\n                it.remove();\n            }\n        }\n    }\n\n    /**\n     * Selects all elements from input collection which match the given predicate\n     * into an output collection.\n     * <p>\n     * A <code>null</code> predicate matches no elements.\n     *\n     * @param inputCollection the collection to get the input from, may not be null\n     * @param predicate       the predicate to use, may be null\n     * @return the elements matching the predicate (new list)\n     * @throws NullPointerException if the input collection is null\n     */\n    public static <E> Collection<E> select(Collection<E> inputCollection, Predicate<E> predicate) {\n        if (inputCollection == null || predicate == null) return new ArrayList<>();\n        ArrayList<E> answer = new ArrayList<>(inputCollection.size());\n        for (E o : inputCollection) {\n            if (predicate.evaluate(o)) {\n                answer.add(o);\n            }\n        }\n        return answer;\n    }\n\n    /**\n     * Selects all elements from inputCollection which don't match the given predicate\n     * into an output collection.\n     * <p>\n     * If the input predicate is <code>null</code>, the result is an empty list.\n     *\n     * @param inputCollection the collection to get the input from, may not be null\n     * @param predicate       the predicate to use, may be null\n     * @return the elements <b>not</b> matching the predicate (new list)\n     * @throws NullPointerException if the input collection is null\n     */\n    public static <E> Collection<E> selectRejected(Collection<E> inputCollection, Predicate<E> predicate) {\n        if (inputCollection == null || predicate == null) return new ArrayList<>();\n        ArrayList<E> answer = new ArrayList<>(inputCollection.size());\n        for (E o : inputCollection) {\n            if (!predicate.evaluate(o)) {\n                answer.add(o);\n            }\n        }\n        return answer;\n    }\n\n    /**\n     * Transform the collection by applying a Transformer to each element.\n     * <p>\n     * If the input collection or transformer is null, there is no change made.\n     * <p>\n     * This routine is best for Lists, for which set() is used to do the\n     * transformations \"in place.\"  For other Collections, clear() and addAll()\n     * are used to replace elements.\n     * <p>\n     * If the input collection controls its input, such as a Set, and the\n     * Transformer creates duplicates (or are otherwise invalid), the\n     * collection may reduce in size due to calling this method.\n     *\n     * @param collection  the collection to get the input from, may be null\n     * @param transformer the transformer to perform, may be null\n     */\n    public static <E1, E2> void transform(Collection<E1> collection, Transformer<E1, E2> transformer) {\n        if (collection == null || transformer == null) return;\n        if (collection instanceof List) {\n            List list = (List) collection;\n            for (ListIterator it = list.listIterator(); it.hasNext(); ) {\n                it.set(transformer.transform((E1) it.next()));\n            }\n        } else {\n            Collection resultCollection = collect(collection, transformer);\n            collection.clear();\n            collection.addAll(resultCollection);\n        }\n    }\n\n\n    /**\n     * Returns a new Collection consisting of the elements of inputCollection transformed\n     * by the given transformer.\n     * <p>\n     * If the input transformer is null, the result is an empty list.\n     *\n     * @param inputCollection the collection to get the input from, may be null\n     * @param transformer     the transformer to use, may be null\n     * @return the transformed result (new list)\n     */\n    public static <E1, E2> Collection<E2> collect(final Collection<E1> inputCollection,\n                                                  final Transformer<E1, E2> transformer) {\n        List<E2> answer = new ArrayList<>();\n        if (inputCollection == null || transformer == null) return answer;\n        for (E1 e1 : inputCollection) {\n            answer.add(transformer.transform(e1));\n        }\n        return answer;\n    }\n\n    /**\n     * Counts the number of elements in the input collection that match the predicate.\n     * <p>\n     * A <code>null</code> collection or predicate matches no elements.\n     *\n     * @param collection the collection to get the input from, may be null\n     * @param predicate  the predicate to use, may be null\n     * @return the number of matches for the predicate in the collection\n     */\n    public static <E> int countMatches(Collection<E> collection, Predicate<E> predicate) {\n        if (collection == null || predicate == null) return 0;\n        int count = 0;\n        for (E o : collection) {\n            if (predicate.evaluate(o)) {\n                count++;\n            }\n        }\n        return count;\n    }\n\n    /**\n     * Answers true if a predicate is true for at least one element of a collection.\n     * <p>\n     * A <code>null</code> collection or predicate returns false.\n     *\n     * @param collection the collection to get the input from, may be null\n     * @param predicate  the predicate to use, may be null\n     * @return true if at least one element of the collection matches the predicate\n     */\n    public static <E> boolean exists(Collection<E> collection, Predicate<E> predicate) {\n        if (collection == null || predicate == null) return false;\n        for (E o : collection) {\n            if (predicate.evaluate(o)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Adds an element to the collection unless the element is null.\n     *\n     * @param collection the collection to add to, may be null\n     * @param object     the object to add, if null it will not be added\n     * @return true if the collection changed\n     */\n    public static <E> boolean addIgnoreNull(Collection<E> collection, E object) {\n        if (collection == null) return false;\n        return (object != null && collection.add(object));\n    }\n\n    /**\n     * Adds all elements in the iteration to the given collection.\n     *\n     * @param collection the collection to add to, may be null\n     * @param iterator   the iterator of elements to add, may be null\n     */\n    public static <E> void addAll(Collection<E> collection, Iterator<E> iterator) {\n        if (collection == null || iterator == null) return;\n        while (iterator.hasNext()) {\n            collection.add(iterator.next());\n        }\n    }\n\n    /**\n     * Adds all elements in the enumeration to the given collection.\n     *\n     * @param collection  the collection to add to, may be null\n     * @param enumeration the enumeration of elements to add, may be null\n     */\n    public static <E> void addAll(Collection<E> collection, Enumeration<E> enumeration) {\n        if (collection == null || enumeration == null) return;\n        while (enumeration.hasMoreElements()) {\n            collection.add(enumeration.nextElement());\n        }\n    }\n\n    /**\n     * Adds all elements in the array to the given collection.\n     *\n     * @param collection the collection to add to, may be null\n     * @param elements   the array of elements to add, may be null\n     */\n    public static <E> void addAll(Collection<E> collection, E[] elements) {\n        if (collection == null || elements == null || elements.length == 0) return;\n        collection.addAll(Arrays.asList(elements));\n    }\n\n    /**\n     * Returns the <code>index</code>-th value in <code>object</code>, throwing\n     * <code>IndexOutOfBoundsException</code> if there is no such element or\n     * <code>IllegalArgumentException</code> if <code>object</code> is not an\n     * instance of one of the supported types.\n     * <p>\n     * The supported types, and associated semantics are:\n     * <ul>\n     * <li> Map -- the value returned is the <code>Map.Entry</code> in position\n     * <code>index</code> in the map's <code>entrySet</code> iterator,\n     * if there is such an entry.</li>\n     * <li> List -- this method is equivalent to the list's get method.</li>\n     * <li> Array -- the <code>index</code>-th array entry is returned,\n     * if there is such an entry; otherwise an <code>IndexOutOfBoundsException</code>\n     * is thrown.</li>\n     * <li> Collection -- the value returned is the <code>index</code>-th object\n     * returned by the collection's default iterator, if there is such an element.</li>\n     * <li> Iterator or Enumeration -- the value returned is the\n     * <code>index</code>-th object in the Iterator/Enumeration, if there\n     * is such an element.  The Iterator/Enumeration is advanced to\n     * <code>index</code> (or to the end, if <code>index</code> exceeds the\n     * number of entries) as a side effect of this method.</li>\n     * </ul>\n     *\n     * @param object the object to get a value from\n     * @param index  the index to get\n     * @return the object at the specified index\n     * @throws IndexOutOfBoundsException if the index is invalid\n     * @throws IllegalArgumentException  if the object type is invalid\n     */\n    public static Object get(Object object, int index) {\n        if (object == null) return null;\n        if (index < 0) {\n            throw new IndexOutOfBoundsException(\"Index cannot be negative: \" + index);\n        }\n        if (object instanceof Map) {\n            Map map = (Map) object;\n            Iterator iterator = map.entrySet().iterator();\n            return get(iterator, index);\n        } else if (object instanceof List) {\n            return ((List) object).get(index);\n        } else if (object instanceof Object[]) {\n            return ((Object[]) object)[index];\n        } else if (object instanceof Iterator) {\n            Iterator it = (Iterator) object;\n            while (it.hasNext()) {\n                index--;\n                if (index == -1) {\n                    return it.next();\n                } else {\n                    it.next();\n                }\n            }\n            throw new IndexOutOfBoundsException(\"Entry does not exist: \" + index);\n        } else if (object instanceof Collection) {\n            Iterator iterator = ((Collection) object).iterator();\n            return get(iterator, index);\n        } else if (object instanceof Enumeration) {\n            Enumeration it = (Enumeration) object;\n            while (it.hasMoreElements()) {\n                index--;\n                if (index == -1) {\n                    return it.nextElement();\n                } else {\n                    it.nextElement();\n                }\n            }\n            throw new IndexOutOfBoundsException(\"Entry does not exist: \" + index);\n        } else {\n            try {\n                return Array.get(object, index);\n            } catch (IllegalArgumentException ex) {\n                throw new IllegalArgumentException(\"Unsupported object type: \" + object.getClass().getName());\n            }\n        }\n    }\n\n    /**\n     * Gets the size of the collection/iterator specified.\n     * <p>\n     * This method can handles objects as follows\n     * <ul>\n     * <li>Collection - the collection size\n     * <li>Map - the map size\n     * <li>Array - the array size\n     * <li>Iterator - the number of elements remaining in the iterator\n     * <li>Enumeration - the number of elements remaining in the enumeration\n     * </ul>\n     *\n     * @param object the object to get the size of\n     * @return the size of the specified collection\n     * @throws IllegalArgumentException thrown if object is not recognised or null\n     */\n    public static int size(Object object) {\n        if (object == null) return 0;\n        int total = 0;\n        if (object instanceof Map) {\n            total = ((Map) object).size();\n        } else if (object instanceof Collection) {\n            total = ((Collection) object).size();\n        } else if (object instanceof Object[]) {\n            total = ((Object[]) object).length;\n        } else if (object instanceof Iterator) {\n            Iterator it = (Iterator) object;\n            while (it.hasNext()) {\n                total++;\n                it.next();\n            }\n        } else if (object instanceof Enumeration) {\n            Enumeration it = (Enumeration) object;\n            while (it.hasMoreElements()) {\n                total++;\n                it.nextElement();\n            }\n        } else {\n            try {\n                total = Array.getLength(object);\n            } catch (IllegalArgumentException ex) {\n                throw new IllegalArgumentException(\"Unsupported object type: \" + object.getClass().getName());\n            }\n        }\n        return total;\n    }\n\n    /**\n     * Checks if the specified collection/array/iterator is empty.\n     * <p>\n     * This method can handles objects as follows\n     * <ul>\n     * <li>Collection - via collection isEmpty\n     * <li>Map - via map isEmpty\n     * <li>Array - using array size\n     * <li>Iterator - via hasNext\n     * <li>Enumeration - via hasMoreElements\n     * </ul>\n     * <p>\n     * Note: This method is named to avoid clashing with\n     * {@link #isEmpty(Collection)}.\n     *\n     * @param object the object to get the size of, not null\n     * @return true if empty\n     * @throws IllegalArgumentException thrown if object is not recognised or null\n     */\n    public static boolean sizeIsEmpty(Object object) {\n        if (object == null) return true;\n        if (object instanceof Collection) {\n            return ((Collection) object).isEmpty();\n        } else if (object instanceof Map) {\n            return ((Map) object).isEmpty();\n        } else if (object instanceof Object[]) {\n            return ((Object[]) object).length == 0;\n        } else if (object instanceof Iterator) {\n            return !((Iterator) object).hasNext();\n        } else if (object instanceof Enumeration) {\n            return !((Enumeration) object).hasMoreElements();\n        } else {\n            try {\n                return Array.getLength(object) == 0;\n            } catch (IllegalArgumentException ex) {\n                throw new IllegalArgumentException(\"Unsupported object type: \" + object.getClass().getName());\n            }\n        }\n    }\n\n    /**\n     * Null-safe check if the specified collection is empty.\n     * <p>\n     * Null returns true.\n     *\n     * @param coll the collection to check, may be null\n     * @return true if empty or null\n     */\n    public static boolean isEmpty(Collection coll) {\n        return coll == null || coll.size() == 0;\n    }\n\n    /**\n     * Null-safe check if the specified collection is not empty.\n     * <p>\n     * Null returns false.\n     *\n     * @param coll the collection to check, may be null\n     * @return true if non-null and non-empty\n     */\n    public static boolean isNotEmpty(Collection coll) {\n        return !isEmpty(coll);\n    }\n\n\n    /**\n     * Returns a collection containing all the elements in <code>collection</code>\n     * that are also in <code>retain</code>. The cardinality of an element <code>e</code>\n     * in the returned collection is the same as the cardinality of <code>e</code>\n     * in <code>collection</code> unless <code>retain</code> does not contain <code>e</code>, in which\n     * case the cardinality is zero. This method is useful if you do not wish to modify\n     * the collection <code>c</code> and thus cannot call <code>c.retainAll(retain);</code>.\n     *\n     * @param collection the collection whose contents are the target of the #retailAll operation\n     * @param retain     the collection containing the elements to be retained in the returned collection\n     * @return a <code>Collection</code> containing all the elements of <code>collection</code>\n     * that occur at least once in <code>retain</code>.\n     */\n    public static <E> Collection<E> retainAll(Collection<E> collection, Collection<E> retain) {\n        if (collection == null || retain == null) return new ArrayList<>();\n        List<E> list = new ArrayList<>();\n        for (E item : collection) {\n            if (retain.contains(item)) {\n                list.add(item);\n            }\n        }\n        return list;\n    }\n\n    /**\n     * Removes the elements in <code>remove</code> from <code>collection</code>. That is, this\n     * method returns a collection containing all the elements in <code>c</code>\n     * that are not in <code>remove</code>. The cardinality of an element <code>e</code>\n     * in the returned collection is the same as the cardinality of <code>e</code>\n     * in <code>collection</code> unless <code>remove</code> contains <code>e</code>, in which\n     * case the cardinality is zero. This method is useful if you do not wish to modify\n     * the collection <code>c</code> and thus cannot call <code>collection.removeAll(remove);</code>.\n     *\n     * @param collection the collection from which items are removed (in the returned collection)\n     * @param remove     the items to be removed from the returned <code>collection</code>\n     * @return a <code>Collection</code> containing all the elements of <code>collection</code> except\n     * any elements that also occur in <code>remove</code>.\n     */\n    public static <E> Collection<E> removeAll(Collection<E> collection, Collection<E> remove) {\n        if (collection == null) return new ArrayList<>();\n        if (remove == null) return new ArrayList<>(collection);\n        List<E> list = new ArrayList<>();\n        for (E obj : collection) {\n            if (!remove.contains(obj)) {\n                list.add(obj);\n            }\n        }\n        return list;\n    }\n\n    /**\n     * Randomly permutes the specified list using a default source of randomness.\n     *\n     * @param list the list to be shuffled.\n     * @throws UnsupportedOperationException if the specified list or\n     *                                       its list-iterator does not support the <tt>set</tt> operation.\n     */\n    public static <T> void shuffle(List<T> list) {\n        Collections.shuffle(list);\n    }\n\n    /**\n     * Return the string of collection.\n     *\n     * @param collection The collection.\n     * @return the string of collection\n     */\n    public static String toString(Collection collection) {\n        if (collection == null) return \"null\";\n        return collection.toString();\n    }\n\n    public interface Closure<E> {\n        void execute(int index, E item);\n    }\n\n    public interface Transformer<E1, E2> {\n        E2 transform(E1 input);\n    }\n\n    public interface Predicate<E> {\n        boolean evaluate(E item);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ColorUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.graphics.Color;\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.ColorRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.core.content.ContextCompat;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/01/15\n *     desc  : utils about color\n * </pre>\n */\npublic final class ColorUtils {\n\n    private ColorUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Returns a color associated with a particular resource ID.\n     *\n     * @param id The desired resource identifier.\n     * @return a color associated with a particular resource ID\n     */\n    public static int getColor(@ColorRes int id) {\n        return ContextCompat.getColor(Utils.getApp(), id);\n    }\n\n    /**\n     * Set the alpha component of {@code color} to be {@code alpha}.\n     *\n     * @param color The color.\n     * @param alpha Alpha component \\([0..255]\\) of the color.\n     * @return the {@code color} with {@code alpha} component\n     */\n    public static int setAlphaComponent(@ColorInt final int color,\n                                        @IntRange(from = 0x0, to = 0xFF) int alpha) {\n        return (color & 0x00ffffff) | (alpha << 24);\n    }\n\n    /**\n     * Set the alpha component of {@code color} to be {@code alpha}.\n     *\n     * @param color The color.\n     * @param alpha Alpha component \\([0..1]\\) of the color.\n     * @return the {@code color} with {@code alpha} component\n     */\n    public static int setAlphaComponent(@ColorInt int color,\n                                        @FloatRange(from = 0, to = 1) float alpha) {\n        return (color & 0x00ffffff) | ((int) (alpha * 255.0f + 0.5f) << 24);\n    }\n\n    /**\n     * Set the red component of {@code color} to be {@code red}.\n     *\n     * @param color The color.\n     * @param red   Red component \\([0..255]\\) of the color.\n     * @return the {@code color} with {@code red} component\n     */\n    public static int setRedComponent(@ColorInt int color,\n                                      @IntRange(from = 0x0, to = 0xFF) int red) {\n        return (color & 0xff00ffff) | (red << 16);\n    }\n\n    /**\n     * Set the red component of {@code color} to be {@code red}.\n     *\n     * @param color The color.\n     * @param red   Red component \\([0..1]\\) of the color.\n     * @return the {@code color} with {@code red} component\n     */\n    public static int setRedComponent(@ColorInt int color,\n                                      @FloatRange(from = 0, to = 1) float red) {\n        return (color & 0xff00ffff) | ((int) (red * 255.0f + 0.5f) << 16);\n    }\n\n    /**\n     * Set the green component of {@code color} to be {@code green}.\n     *\n     * @param color The color.\n     * @param green Green component \\([0..255]\\) of the color.\n     * @return the {@code color} with {@code green} component\n     */\n    public static int setGreenComponent(@ColorInt int color,\n                                        @IntRange(from = 0x0, to = 0xFF) int green) {\n        return (color & 0xffff00ff) | (green << 8);\n    }\n\n    /**\n     * Set the green component of {@code color} to be {@code green}.\n     *\n     * @param color The color.\n     * @param green Green component \\([0..1]\\) of the color.\n     * @return the {@code color} with {@code green} component\n     */\n    public static int setGreenComponent(@ColorInt int color,\n                                        @FloatRange(from = 0, to = 1) float green) {\n        return (color & 0xffff00ff) | ((int) (green * 255.0f + 0.5f) << 8);\n    }\n\n    /**\n     * Set the blue component of {@code color} to be {@code blue}.\n     *\n     * @param color The color.\n     * @param blue  Blue component \\([0..255]\\) of the color.\n     * @return the {@code color} with {@code blue} component\n     */\n    public static int setBlueComponent(@ColorInt int color,\n                                       @IntRange(from = 0x0, to = 0xFF) int blue) {\n        return (color & 0xffffff00) | blue;\n    }\n\n    /**\n     * Set the blue component of {@code color} to be {@code blue}.\n     *\n     * @param color The color.\n     * @param blue  Blue component \\([0..1]\\) of the color.\n     * @return the {@code color} with {@code blue} component\n     */\n    public static int setBlueComponent(@ColorInt int color,\n                                       @FloatRange(from = 0, to = 1) float blue) {\n        return (color & 0xffffff00) | (int) (blue * 255.0f + 0.5f);\n    }\n\n    /**\n     * Color-string to color-int.\n     * <p>Supported formats are:</p>\n     *\n     * <ul>\n     * <li><code>#RRGGBB</code></li>\n     * <li><code>#AARRGGBB</code></li>\n     * </ul>\n     *\n     * <p>The following names are also accepted: <code>red</code>, <code>blue</code>,\n     * <code>green</code>, <code>black</code>, <code>white</code>, <code>gray</code>,\n     * <code>cyan</code>, <code>magenta</code>, <code>yellow</code>, <code>lightgray</code>,\n     * <code>darkgray</code>, <code>grey</code>, <code>lightgrey</code>, <code>darkgrey</code>,\n     * <code>aqua</code>, <code>fuchsia</code>, <code>lime</code>, <code>maroon</code>,\n     * <code>navy</code>, <code>olive</code>, <code>purple</code>, <code>silver</code>,\n     * and <code>teal</code>.</p>\n     *\n     * @param colorString The color-string.\n     * @return color-int\n     * @throws IllegalArgumentException The string cannot be parsed.\n     */\n    public static int string2Int(@NonNull String colorString) {\n        return Color.parseColor(colorString);\n    }\n\n    /**\n     * Color-int to color-string.\n     *\n     * @param colorInt The color-int.\n     * @return color-string\n     */\n    public static String int2RgbString(@ColorInt int colorInt) {\n        colorInt = colorInt & 0x00ffffff;\n        String color = Integer.toHexString(colorInt);\n        while (color.length() < 6) {\n            color = \"0\" + color;\n        }\n        return \"#\" + color;\n    }\n\n    /**\n     * Color-int to color-string.\n     *\n     * @param colorInt The color-int.\n     * @return color-string\n     */\n    public static String int2ArgbString(@ColorInt final int colorInt) {\n        String color = Integer.toHexString(colorInt);\n        while (color.length() < 6) {\n            color = \"0\" + color;\n        }\n        while (color.length() < 8) {\n            color = \"f\" + color;\n        }\n        return \"#\" + color;\n    }\n\n    /**\n     * Return the random color.\n     *\n     * @return the random color\n     */\n    public static int getRandomColor() {\n        return getRandomColor(true);\n    }\n\n    /**\n     * Return the random color.\n     *\n     * @param supportAlpha True to support alpha, false otherwise.\n     * @return the random color\n     */\n    public static int getRandomColor(final boolean supportAlpha) {\n        int high = supportAlpha ? (int) (Math.random() * 0x100) << 24 : 0xFF000000;\n        return high | (int) (Math.random() * 0x1000000);\n    }\n\n    /**\n     * Return whether the color is light.\n     *\n     * @param color The color.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isLightColor(@ColorInt int color) {\n        return 0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color) >= 127.5;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ConvertUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.annotation.SuppressLint;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport android.view.View;\n\n\nimport com.didichuxing.doraemonkit.constant.MemoryConstants;\nimport com.didichuxing.doraemonkit.constant.TimeConstants;\n\nimport org.json.JSONArray;\nimport org.json.JSONObject;\n\nimport java.io.BufferedReader;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.io.OutputStream;\nimport java.io.Serializable;\nimport java.io.UnsupportedEncodingException;\nimport java.nio.charset.Charset;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/13\n *     desc  : utils about convert\n * </pre>\n */\npublic final class ConvertUtils {\n\n    private static final int    BUFFER_SIZE      = 8192;\n    private static final char[] HEX_DIGITS_UPPER =\n            {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};\n    private static final char[] HEX_DIGITS_LOWER =\n            {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};\n\n    private ConvertUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Int to hex string.\n     *\n     * @param num The int number.\n     * @return the hex string\n     */\n    public static String int2HexString(int num) {\n        return Integer.toHexString(num);\n    }\n\n    /**\n     * Hex string to int.\n     *\n     * @param hexString The hex string.\n     * @return the int\n     */\n    public static int hexString2Int(String hexString) {\n        return Integer.parseInt(hexString, 16);\n    }\n\n    /**\n     * Bytes to bits.\n     *\n     * @param bytes The bytes.\n     * @return bits\n     */\n    public static String bytes2Bits(final byte[] bytes) {\n        if (bytes == null || bytes.length == 0) return \"\";\n        StringBuilder sb = new StringBuilder();\n        for (byte aByte : bytes) {\n            for (int j = 7; j >= 0; --j) {\n                sb.append(((aByte >> j) & 0x01) == 0 ? '0' : '1');\n            }\n        }\n        return sb.toString();\n    }\n\n    /**\n     * Bits to bytes.\n     *\n     * @param bits The bits.\n     * @return bytes\n     */\n    public static byte[] bits2Bytes(String bits) {\n        int lenMod = bits.length() % 8;\n        int byteLen = bits.length() / 8;\n        // add \"0\" until length to 8 times\n        if (lenMod != 0) {\n            for (int i = lenMod; i < 8; i++) {\n                bits = \"0\" + bits;\n            }\n            byteLen++;\n        }\n        byte[] bytes = new byte[byteLen];\n        for (int i = 0; i < byteLen; ++i) {\n            for (int j = 0; j < 8; ++j) {\n                bytes[i] <<= 1;\n                bytes[i] |= bits.charAt(i * 8 + j) - '0';\n            }\n        }\n        return bytes;\n    }\n\n    /**\n     * Bytes to chars.\n     *\n     * @param bytes The bytes.\n     * @return chars\n     */\n    public static char[] bytes2Chars(final byte[] bytes) {\n        if (bytes == null) return null;\n        int len = bytes.length;\n        if (len <= 0) return null;\n        char[] chars = new char[len];\n        for (int i = 0; i < len; i++) {\n            chars[i] = (char) (bytes[i] & 0xff);\n        }\n        return chars;\n    }\n\n    /**\n     * Chars to bytes.\n     *\n     * @param chars The chars.\n     * @return bytes\n     */\n    public static byte[] chars2Bytes(final char[] chars) {\n        if (chars == null || chars.length <= 0) return null;\n        int len = chars.length;\n        byte[] bytes = new byte[len];\n        for (int i = 0; i < len; i++) {\n            bytes[i] = (byte) (chars[i]);\n        }\n        return bytes;\n    }\n\n    /**\n     * Bytes to hex string.\n     * <p>e.g. bytes2HexString(new byte[] { 0, (byte) 0xa8 }) returns \"00A8\"</p>\n     *\n     * @param bytes The bytes.\n     * @return hex string\n     */\n    public static String bytes2HexString(final byte[] bytes) {\n        return bytes2HexString(bytes, true);\n    }\n\n    /**\n     * Bytes to hex string.\n     * <p>e.g. bytes2HexString(new byte[] { 0, (byte) 0xa8 }, true) returns \"00A8\"</p>\n     *\n     * @param bytes       The bytes.\n     * @param isUpperCase True to use upper case, false otherwise.\n     * @return hex string\n     */\n    public static String bytes2HexString(final byte[] bytes, boolean isUpperCase) {\n        if (bytes == null) return \"\";\n        char[] hexDigits = isUpperCase ? HEX_DIGITS_UPPER : HEX_DIGITS_LOWER;\n        int len = bytes.length;\n        if (len <= 0) return \"\";\n        char[] ret = new char[len << 1];\n        for (int i = 0, j = 0; i < len; i++) {\n            ret[j++] = hexDigits[bytes[i] >> 4 & 0x0f];\n            ret[j++] = hexDigits[bytes[i] & 0x0f];\n        }\n        return new String(ret);\n    }\n\n    /**\n     * Hex string to bytes.\n     * <p>e.g. hexString2Bytes(\"00A8\") returns { 0, (byte) 0xA8 }</p>\n     *\n     * @param hexString The hex string.\n     * @return the bytes\n     */\n    public static byte[] hexString2Bytes(String hexString) {\n        if (UtilsBridge.isSpace(hexString)) return new byte[0];\n        int len = hexString.length();\n        if (len % 2 != 0) {\n            hexString = \"0\" + hexString;\n            len = len + 1;\n        }\n        char[] hexBytes = hexString.toUpperCase().toCharArray();\n        byte[] ret = new byte[len >> 1];\n        for (int i = 0; i < len; i += 2) {\n            ret[i >> 1] = (byte) (hex2Dec(hexBytes[i]) << 4 | hex2Dec(hexBytes[i + 1]));\n        }\n        return ret;\n    }\n\n    private static int hex2Dec(final char hexChar) {\n        if (hexChar >= '0' && hexChar <= '9') {\n            return hexChar - '0';\n        } else if (hexChar >= 'A' && hexChar <= 'F') {\n            return hexChar - 'A' + 10;\n        } else {\n            throw new IllegalArgumentException();\n        }\n    }\n\n    /**\n     * Bytes to string.\n     */\n    public static String bytes2String(final byte[] bytes) {\n        return bytes2String(bytes, \"\");\n    }\n\n    /**\n     * Bytes to string.\n     */\n    public static String bytes2String(final byte[] bytes, final String charsetName) {\n        if (bytes == null) return null;\n        try {\n            return new String(bytes, getSafeCharset(charsetName));\n        } catch (UnsupportedEncodingException e) {\n            e.printStackTrace();\n            return new String(bytes);\n        }\n    }\n\n    /**\n     * String to bytes.\n     */\n    public static byte[] string2Bytes(final String string) {\n        return string2Bytes(string, \"\");\n    }\n\n    /**\n     * String to bytes.\n     */\n    public static byte[] string2Bytes(final String string, final String charsetName) {\n        if (string == null) return null;\n        try {\n            return string.getBytes(getSafeCharset(charsetName));\n        } catch (UnsupportedEncodingException e) {\n            e.printStackTrace();\n            return string.getBytes();\n        }\n    }\n\n    /**\n     * Bytes to JSONObject.\n     */\n    public static JSONObject bytes2JSONObject(final byte[] bytes) {\n        if (bytes == null) return null;\n        try {\n            return new JSONObject(new String(bytes));\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * JSONObject to bytes.\n     */\n    public static byte[] jsonObject2Bytes(final JSONObject jsonObject) {\n        if (jsonObject == null) return null;\n        return jsonObject.toString().getBytes();\n    }\n\n    /**\n     * Bytes to JSONArray.\n     */\n    public static JSONArray bytes2JSONArray(final byte[] bytes) {\n        if (bytes == null) return null;\n        try {\n            return new JSONArray(new String(bytes));\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * JSONArray to bytes.\n     */\n    public static byte[] jsonArray2Bytes(final JSONArray jsonArray) {\n        if (jsonArray == null) return null;\n        return jsonArray.toString().getBytes();\n    }\n\n    /**\n     * Bytes to Parcelable\n     */\n    public static <T> T bytes2Parcelable(final byte[] bytes,\n                                         final Parcelable.Creator<T> creator) {\n        if (bytes == null) return null;\n        Parcel parcel = Parcel.obtain();\n        parcel.unmarshall(bytes, 0, bytes.length);\n        parcel.setDataPosition(0);\n        T result = creator.createFromParcel(parcel);\n        parcel.recycle();\n        return result;\n    }\n\n    /**\n     * Parcelable to bytes.\n     */\n    public static byte[] parcelable2Bytes(final Parcelable parcelable) {\n        if (parcelable == null) return null;\n        Parcel parcel = Parcel.obtain();\n        parcelable.writeToParcel(parcel, 0);\n        byte[] bytes = parcel.marshall();\n        parcel.recycle();\n        return bytes;\n    }\n\n    /**\n     * Bytes to Serializable.\n     */\n    public static Object bytes2Object(final byte[] bytes) {\n        if (bytes == null) return null;\n        ObjectInputStream ois = null;\n        try {\n            ois = new ObjectInputStream(new ByteArrayInputStream(bytes));\n            return ois.readObject();\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        } finally {\n            try {\n                if (ois != null) {\n                    ois.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Serializable to bytes.\n     */\n    public static byte[] serializable2Bytes(final Serializable serializable) {\n        if (serializable == null) return null;\n        ByteArrayOutputStream baos;\n        ObjectOutputStream oos = null;\n        try {\n            oos = new ObjectOutputStream(baos = new ByteArrayOutputStream());\n            oos.writeObject(serializable);\n            return baos.toByteArray();\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        } finally {\n            try {\n                if (oos != null) {\n                    oos.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Bytes to bitmap.\n     */\n    public static Bitmap bytes2Bitmap(final byte[] bytes) {\n        return UtilsBridge.bytes2Bitmap(bytes);\n    }\n\n    /**\n     * Bitmap to bytes.\n     */\n    public static byte[] bitmap2Bytes(final Bitmap bitmap) {\n        return UtilsBridge.bitmap2Bytes(bitmap);\n    }\n\n    /**\n     * Bitmap to bytes.\n     */\n    public static byte[] bitmap2Bytes(final Bitmap bitmap, final Bitmap.CompressFormat format, int quality) {\n        return UtilsBridge.bitmap2Bytes(bitmap, format, quality);\n    }\n\n    /**\n     * Bytes to drawable.\n     */\n    public static Drawable bytes2Drawable(final byte[] bytes) {\n        return UtilsBridge.bytes2Drawable(bytes);\n    }\n\n    /**\n     * Drawable to bytes.\n     */\n    public static byte[] drawable2Bytes(final Drawable drawable) {\n        return UtilsBridge.drawable2Bytes(drawable);\n    }\n\n    /**\n     * Drawable to bytes.\n     */\n    public static byte[] drawable2Bytes(final Drawable drawable, final Bitmap.CompressFormat format, int quality) {\n        return UtilsBridge.drawable2Bytes(drawable, format, quality);\n    }\n\n    /**\n     * Size of memory in unit to size of byte.\n     *\n     * @param memorySize Size of memory.\n     * @param unit       The unit of memory size.\n     *                   <ul>\n     *                   <li>{@link MemoryConstants#BYTE}</li>\n     *                   <li>{@link MemoryConstants#KB}</li>\n     *                   <li>{@link MemoryConstants#MB}</li>\n     *                   <li>{@link MemoryConstants#GB}</li>\n     *                   </ul>\n     * @return size of byte\n     */\n    public static long memorySize2Byte(final long memorySize,\n                                       @MemoryConstants.Unit final int unit) {\n        if (memorySize < 0) return -1;\n        return memorySize * unit;\n    }\n\n    /**\n     * Size of byte to size of memory in unit.\n     *\n     * @param byteSize Size of byte.\n     * @param unit     The unit of memory size.\n     *                 <ul>\n     *                 <li>{@link MemoryConstants#BYTE}</li>\n     *                 <li>{@link MemoryConstants#KB}</li>\n     *                 <li>{@link MemoryConstants#MB}</li>\n     *                 <li>{@link MemoryConstants#GB}</li>\n     *                 </ul>\n     * @return size of memory in unit\n     */\n    public static double byte2MemorySize(final long byteSize,\n                                         @MemoryConstants.Unit final int unit) {\n        if (byteSize < 0) return -1;\n        return (double) byteSize / unit;\n    }\n\n    /**\n     * Size of byte to fit size of memory.\n     * <p>to three decimal places</p>\n     *\n     * @param byteSize Size of byte.\n     * @return fit size of memory\n     */\n    @SuppressLint(\"DefaultLocale\")\n    public static String byte2FitMemorySize(final long byteSize) {\n        return byte2FitMemorySize(byteSize, 3);\n    }\n\n    /**\n     * Size of byte to fit size of memory.\n     * <p>to three decimal places</p>\n     *\n     * @param byteSize  Size of byte.\n     * @param precision The precision\n     * @return fit size of memory\n     */\n    @SuppressLint(\"DefaultLocale\")\n    public static String byte2FitMemorySize(final long byteSize, int precision) {\n        if (precision < 0) {\n            throw new IllegalArgumentException(\"precision shouldn't be less than zero!\");\n        }\n        if (byteSize < 0) {\n            throw new IllegalArgumentException(\"byteSize shouldn't be less than zero!\");\n        } else if (byteSize < MemoryConstants.KB) {\n            return String.format(\"%.\" + precision + \"fB\", (double) byteSize);\n        } else if (byteSize < MemoryConstants.MB) {\n            return String.format(\"%.\" + precision + \"fKB\", (double) byteSize / MemoryConstants.KB);\n        } else if (byteSize < MemoryConstants.GB) {\n            return String.format(\"%.\" + precision + \"fMB\", (double) byteSize / MemoryConstants.MB);\n        } else {\n            return String.format(\"%.\" + precision + \"fGB\", (double) byteSize / MemoryConstants.GB);\n        }\n    }\n\n    /**\n     * Time span in unit to milliseconds.\n     *\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return milliseconds\n     */\n    public static long timeSpan2Millis(final long timeSpan, @TimeConstants.Unit final int unit) {\n        return timeSpan * unit;\n    }\n\n    /**\n     * Milliseconds to time span in unit.\n     *\n     * @param millis The milliseconds.\n     * @param unit   The unit of time span.\n     *               <ul>\n     *               <li>{@link TimeConstants#MSEC}</li>\n     *               <li>{@link TimeConstants#SEC }</li>\n     *               <li>{@link TimeConstants#MIN }</li>\n     *               <li>{@link TimeConstants#HOUR}</li>\n     *               <li>{@link TimeConstants#DAY }</li>\n     *               </ul>\n     * @return time span in unit\n     */\n    public static long millis2TimeSpan(final long millis, @TimeConstants.Unit final int unit) {\n        return millis / unit;\n    }\n\n    /**\n     * Milliseconds to fit time span.\n     *\n     * @param millis    The milliseconds.\n     *                  <p>millis &lt;= 0, return null</p>\n     * @param precision The precision of time span.\n     *                  <ul>\n     *                  <li>precision = 0, return null</li>\n     *                  <li>precision = 1, return 天</li>\n     *                  <li>precision = 2, return 天, 小时</li>\n     *                  <li>precision = 3, return 天, 小时, 分钟</li>\n     *                  <li>precision = 4, return 天, 小时, 分钟, 秒</li>\n     *                  <li>precision &gt;= 5，return 天, 小时, 分钟, 秒, 毫秒</li>\n     *                  </ul>\n     * @return fit time span\n     */\n    public static String millis2FitTimeSpan(long millis, int precision) {\n        return UtilsBridge.millis2FitTimeSpan(millis, precision);\n    }\n\n    /**\n     * Input stream to output stream.\n     */\n    public static ByteArrayOutputStream input2OutputStream(final InputStream is) {\n        if (is == null) return null;\n        try {\n            ByteArrayOutputStream os = new ByteArrayOutputStream();\n            byte[] b = new byte[BUFFER_SIZE];\n            int len;\n            while ((len = is.read(b, 0, BUFFER_SIZE)) != -1) {\n                os.write(b, 0, len);\n            }\n            return os;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        } finally {\n            try {\n                is.close();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Output stream to input stream.\n     */\n    public static ByteArrayInputStream output2InputStream(final OutputStream out) {\n        if (out == null) return null;\n        return new ByteArrayInputStream(((ByteArrayOutputStream) out).toByteArray());\n    }\n\n    /**\n     * Input stream to bytes.\n     */\n    public static byte[] inputStream2Bytes(final InputStream is) {\n        if (is == null) return null;\n        return input2OutputStream(is).toByteArray();\n    }\n\n    /**\n     * Bytes to input stream.\n     */\n    public static InputStream bytes2InputStream(final byte[] bytes) {\n        if (bytes == null || bytes.length <= 0) return null;\n        return new ByteArrayInputStream(bytes);\n    }\n\n    /**\n     * Output stream to bytes.\n     */\n    public static byte[] outputStream2Bytes(final OutputStream out) {\n        if (out == null) return null;\n        return ((ByteArrayOutputStream) out).toByteArray();\n    }\n\n    /**\n     * Bytes to output stream.\n     */\n    public static OutputStream bytes2OutputStream(final byte[] bytes) {\n        if (bytes == null || bytes.length <= 0) return null;\n        ByteArrayOutputStream os = null;\n        try {\n            os = new ByteArrayOutputStream();\n            os.write(bytes);\n            return os;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        } finally {\n            try {\n                if (os != null) {\n                    os.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Input stream to string.\n     */\n    public static String inputStream2String(final InputStream is, final String charsetName) {\n        if (is == null) return \"\";\n        try {\n            ByteArrayOutputStream baos = input2OutputStream(is);\n            if (baos == null) return \"\";\n            return baos.toString(getSafeCharset(charsetName));\n        } catch (UnsupportedEncodingException e) {\n            e.printStackTrace();\n            return \"\";\n        }\n    }\n\n    /**\n     * String to input stream.\n     */\n    public static InputStream string2InputStream(final String string, final String charsetName) {\n        if (string == null) return null;\n        try {\n            return new ByteArrayInputStream(string.getBytes(getSafeCharset(charsetName)));\n        } catch (UnsupportedEncodingException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * Output stream to string.\n     */\n    public static String outputStream2String(final OutputStream out, final String charsetName) {\n        if (out == null) return \"\";\n        try {\n            return new String(outputStream2Bytes(out), getSafeCharset(charsetName));\n        } catch (UnsupportedEncodingException e) {\n            e.printStackTrace();\n            return \"\";\n        }\n    }\n\n    /**\n     * String to output stream.\n     */\n    public static OutputStream string2OutputStream(final String string, final String charsetName) {\n        if (string == null) return null;\n        try {\n            return bytes2OutputStream(string.getBytes(getSafeCharset(charsetName)));\n        } catch (UnsupportedEncodingException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    public static List<String> inputStream2Lines(final InputStream is) {\n        return inputStream2Lines(is, \"\");\n    }\n\n    public static List<String> inputStream2Lines(final InputStream is,\n                                                 final String charsetName) {\n        BufferedReader reader = null;\n        try {\n            List<String> list = new ArrayList<>();\n            reader = new BufferedReader(new InputStreamReader(is, getSafeCharset(charsetName)));\n            String line;\n            while ((line = reader.readLine()) != null) {\n                list.add(line);\n            }\n            return list;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        } finally {\n            try {\n                if (reader != null) {\n                    reader.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Drawable to bitmap.\n     */\n    public static Bitmap drawable2Bitmap(final Drawable drawable) {\n        return UtilsBridge.drawable2Bitmap(drawable);\n    }\n\n    /**\n     * Bitmap to drawable.\n     */\n    public static Drawable bitmap2Drawable(final Bitmap bitmap) {\n        return UtilsBridge.bitmap2Drawable(bitmap);\n    }\n\n    /**\n     * View to bitmap.\n     */\n    public static Bitmap view2Bitmap(final View view) {\n        return UtilsBridge.view2Bitmap(view);\n    }\n\n    /**\n     * Value of dp to value of px.\n     */\n    public static int dp2px(final float dpValue) {\n        return UtilsBridge.dp2px(dpValue);\n    }\n\n    /**\n     * Value of px to value of dp.\n     */\n    public static int px2dp(final float pxValue) {\n        return UtilsBridge.px2dp(pxValue);\n    }\n\n    /**\n     * Value of sp to value of px.\n     */\n    public static int sp2px(final float spValue) {\n        return UtilsBridge.sp2px(spValue);\n    }\n\n    /**\n     * Value of px to value of sp.\n     */\n    public static int px2sp(final float pxValue) {\n        return UtilsBridge.px2sp(pxValue);\n    }\n\n    private static String getSafeCharset(String charsetName) {\n        String cn = charsetName;\n        if (UtilsBridge.isSpace(charsetName) || !Charset.isSupported(charsetName)) {\n            cn = \"UTF-8\";\n        }\n        return cn;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/CoordinateUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport static java.lang.Math.PI;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/03/21\n *     desc  : 坐标相关工具类\n * </pre>\n */\npublic final class CoordinateUtils {\n\n    private final static double X_PI = 3.14159265358979324 * 3000.0 / 180.0;\n    private final static double A    = 6378245.0;\n    private final static double EE   = 0.00669342162296594323;\n\n    /**\n     * BD09 坐标转 GCJ02 坐标\n     *\n     * @param lng BD09 坐标纬度\n     * @param lat BD09 坐标经度\n     * @return GCJ02 坐标：[经度，纬度]\n     */\n    public static double[] bd09ToGcj02(double lng, double lat) {\n        double x = lng - 0.0065;\n        double y = lat - 0.006;\n        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI);\n        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI);\n        double gg_lng = z * Math.cos(theta);\n        double gg_lat = z * Math.sin(theta);\n        return new double[]{gg_lng, gg_lat};\n    }\n\n    /**\n     * GCJ02 坐标转 BD09 坐标\n     *\n     * @param lng GCJ02 坐标经度\n     * @param lat GCJ02 坐标纬度\n     * @return BD09 坐标：[经度，纬度]\n     */\n    public static double[] gcj02ToBd09(double lng, double lat) {\n        double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * X_PI);\n        double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * X_PI);\n        double bd_lng = z * Math.cos(theta) + 0.0065;\n        double bd_lat = z * Math.sin(theta) + 0.006;\n        return new double[]{bd_lng, bd_lat};\n    }\n\n    /**\n     * GCJ02 坐标转 WGS84 坐标\n     *\n     * @param lng GCJ02 坐标经度\n     * @param lat GCJ02 坐标纬度\n     * @return WGS84 坐标：[经度，纬度]\n     */\n    public static double[] gcj02ToWGS84(double lng, double lat) {\n        if (outOfChina(lng, lat)) {\n            return new double[]{lng, lat};\n        }\n        double dlat = transformLat(lng - 105.0, lat - 35.0);\n        double dlng = transformLng(lng - 105.0, lat - 35.0);\n        double radlat = lat / 180.0 * PI;\n        double magic = Math.sin(radlat);\n        magic = 1 - EE * magic * magic;\n        double sqrtmagic = Math.sqrt(magic);\n        dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI);\n        dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * PI);\n        double mglat = lat + dlat;\n        double mglng = lng + dlng;\n        return new double[]{lng * 2 - mglng, lat * 2 - mglat};\n    }\n\n    /**\n     * WGS84 坐标转 GCJ02 坐标\n     *\n     * @param lng WGS84 坐标经度\n     * @param lat WGS84 坐标纬度\n     * @return GCJ02 坐标：[经度，纬度]\n     */\n    public static double[] wgs84ToGcj02(double lng, double lat) {\n        if (outOfChina(lng, lat)) {\n            return new double[]{lng, lat};\n        }\n        double dlat = transformLat(lng - 105.0, lat - 35.0);\n        double dlng = transformLng(lng - 105.0, lat - 35.0);\n        double radlat = lat / 180.0 * PI;\n        double magic = Math.sin(radlat);\n        magic = 1 - EE * magic * magic;\n        double sqrtmagic = Math.sqrt(magic);\n        dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI);\n        dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * PI);\n        double mglat = lat + dlat;\n        double mglng = lng + dlng;\n        return new double[]{mglng, mglat};\n    }\n\n    /**\n     * BD09 坐标转 WGS84 坐标\n     *\n     * @param lng BD09 坐标经度\n     * @param lat BD09 坐标纬度\n     * @return WGS84 坐标：[经度，纬度]\n     */\n    public static double[] bd09ToWGS84(double lng, double lat) {\n        double[] gcj = bd09ToGcj02(lng, lat);\n        return gcj02ToWGS84(gcj[0], gcj[1]);\n    }\n\n\n    /**\n     * WGS84 坐标转 BD09 坐标\n     *\n     * @param lng WGS84 坐标经度\n     * @param lat WGS84 坐标纬度\n     * @return BD09 坐标：[经度，纬度]\n     */\n    public static double[] wgs84ToBd09(double lng, double lat) {\n        double[] gcj = wgs84ToGcj02(lng, lat);\n        return gcj02ToBd09(gcj[0], gcj[1]);\n    }\n\n    /**\n     * Mercator 坐标转 WGS84 坐标\n     *\n     * @param lng Mercator 坐标经度\n     * @param lat Mercator 坐标纬度\n     * @return WGS84 坐标：[经度，纬度]\n     */\n    public static double[] mercatorToWGS84(double lng, double lat) {\n        double x = lng / 20037508.34d * 180.;\n        double y = lat / 20037508.34d * 180.;\n        y = 180 / PI * (2 * Math.atan(Math.exp(y * PI / 180.0)) - PI / 2);\n        return new double[]{x, y};\n    }\n\n    /**\n     * WGS84 坐标转 Mercator 坐标\n     *\n     * @param lng WGS84 坐标经度\n     * @param lat WGS84 坐标纬度\n     * @return Mercator 坐标：[经度，纬度]\n     */\n    public static double[] wgs84ToMercator(double lng, double lat) {\n        double x = lng * 20037508.34D / 180.0;\n        double y = Math.log(Math.tan((90.0 + lat) * PI / 360.0)) / (PI / 180.);\n        y = y * 20037508.34D / 180.0;\n        return new double[]{x, y};\n    }\n\n    private static double transformLat(double lng, double lat) {\n        double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));\n        ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;\n        ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;\n        ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;\n        return ret;\n    }\n\n    private static double transformLng(double lng, double lat) {\n        double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));\n        ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;\n        ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;\n        ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;\n        return ret;\n    }\n\n    private static boolean outOfChina(double lng, double lat) {\n        return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/CountryUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.telephony.TelephonyManager;\n\nimport java.util.HashMap;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/06/11\n *     desc  : utils about country code\n * </pre>\n */\npublic class CountryUtils {\n\n    private static HashMap<String, String> countryCodeMap;\n\n    /**\n     * Return the country code by sim card.\n     *\n     * @param defaultValue The default value.\n     * @return the country code\n     */\n    public static String getCountryCodeBySim(String defaultValue) {\n        String code = getCountryCodeFromMap().get(getCountryBySim());\n        if (code == null) {\n            return defaultValue;\n        }\n        return code;\n    }\n\n    /**\n     * Return the country code by system language.\n     *\n     * @param defaultValue The default value.\n     * @return the country code\n     */\n    public static String getCountryCodeByLanguage(String defaultValue) {\n        String code = getCountryCodeFromMap().get(getCountryByLanguage());\n        if (code == null) {\n            return defaultValue;\n        }\n        return code;\n    }\n\n    /**\n     * Return the country by sim card.\n     *\n     * @return the country\n     */\n    public static String getCountryBySim() {\n        TelephonyManager manager = (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);\n        if (manager != null) {\n            return manager.getSimCountryIso().toUpperCase();\n        }\n        return \"\";\n    }\n\n    /**\n     * Return the country by system language.\n     *\n     * @return the country\n     */\n    public static String getCountryByLanguage() {\n        return Resources.getSystem().getConfiguration().locale.getCountry();\n    }\n\n    private static HashMap<String, String> getCountryCodeFromMap() {\n        if (countryCodeMap == null) {\n            countryCodeMap = new HashMap<>(256);\n            countryCodeMap.put(\"AL\", \"+355\");\n            countryCodeMap.put(\"DZ\", \"+213\");\n            countryCodeMap.put(\"AF\", \"+93\");\n            countryCodeMap.put(\"AR\", \"+54\");\n            countryCodeMap.put(\"AE\", \"+971\");\n            countryCodeMap.put(\"AW\", \"+297\");\n            countryCodeMap.put(\"OM\", \"+968\");\n            countryCodeMap.put(\"AZ\", \"+994\");\n            countryCodeMap.put(\"AC\", \"+247\");\n            countryCodeMap.put(\"EG\", \"+20\");\n            countryCodeMap.put(\"ET\", \"+251\");\n            countryCodeMap.put(\"IE\", \"+353\");\n            countryCodeMap.put(\"EE\", \"+372\");\n            countryCodeMap.put(\"AD\", \"+376\");\n            countryCodeMap.put(\"AO\", \"+244\");\n            countryCodeMap.put(\"AI\", \"+1\");\n            countryCodeMap.put(\"AG\", \"+1\");\n            countryCodeMap.put(\"AT\", \"+43\");\n            countryCodeMap.put(\"AX\", \"+358\");\n            countryCodeMap.put(\"AU\", \"+61\");\n            countryCodeMap.put(\"BB\", \"+1\");\n            countryCodeMap.put(\"PG\", \"+675\");\n            countryCodeMap.put(\"BS\", \"+1\");\n            countryCodeMap.put(\"PK\", \"+92\");\n            countryCodeMap.put(\"PY\", \"+595\");\n            countryCodeMap.put(\"PS\", \"+970\");\n            countryCodeMap.put(\"BH\", \"+973\");\n            countryCodeMap.put(\"PA\", \"+507\");\n            countryCodeMap.put(\"BR\", \"+55\");\n            countryCodeMap.put(\"BY\", \"+375\");\n            countryCodeMap.put(\"BM\", \"+1\");\n            countryCodeMap.put(\"BG\", \"+359\");\n            countryCodeMap.put(\"MP\", \"+1\");\n            countryCodeMap.put(\"BJ\", \"+229\");\n            countryCodeMap.put(\"BE\", \"+32\");\n            countryCodeMap.put(\"IS\", \"+354\");\n            countryCodeMap.put(\"PR\", \"+1\");\n            countryCodeMap.put(\"PL\", \"+48\");\n            countryCodeMap.put(\"BA\", \"+387\");\n            countryCodeMap.put(\"BO\", \"+591\");\n            countryCodeMap.put(\"BZ\", \"+501\");\n            countryCodeMap.put(\"BW\", \"+267\");\n            countryCodeMap.put(\"BT\", \"+975\");\n            countryCodeMap.put(\"BF\", \"+226\");\n            countryCodeMap.put(\"BI\", \"+257\");\n            countryCodeMap.put(\"KP\", \"+850\");\n            countryCodeMap.put(\"GQ\", \"+240\");\n            countryCodeMap.put(\"DK\", \"+45\");\n            countryCodeMap.put(\"DE\", \"+49\");\n            countryCodeMap.put(\"TL\", \"+670\");\n            countryCodeMap.put(\"TG\", \"+228\");\n            countryCodeMap.put(\"DO\", \"+1\");\n            countryCodeMap.put(\"DM\", \"+1\");\n            countryCodeMap.put(\"RU\", \"+7\");\n            countryCodeMap.put(\"EC\", \"+593\");\n            countryCodeMap.put(\"ER\", \"+291\");\n            countryCodeMap.put(\"FR\", \"+33\");\n            countryCodeMap.put(\"FO\", \"+298\");\n            countryCodeMap.put(\"PF\", \"+689\");\n            countryCodeMap.put(\"GF\", \"+594\");\n            countryCodeMap.put(\"VA\", \"+39\");\n            countryCodeMap.put(\"PH\", \"+63\");\n            countryCodeMap.put(\"FJ\", \"+679\");\n            countryCodeMap.put(\"FI\", \"+358\");\n            countryCodeMap.put(\"CV\", \"+238\");\n            countryCodeMap.put(\"FK\", \"+500\");\n            countryCodeMap.put(\"GM\", \"+220\");\n            countryCodeMap.put(\"CG\", \"+242\");\n            countryCodeMap.put(\"CD\", \"+243\");\n            countryCodeMap.put(\"CO\", \"+57\");\n            countryCodeMap.put(\"CR\", \"+506\");\n            countryCodeMap.put(\"GG\", \"+44\");\n            countryCodeMap.put(\"GD\", \"+1\");\n            countryCodeMap.put(\"GL\", \"+299\");\n            countryCodeMap.put(\"GE\", \"+995\");\n            countryCodeMap.put(\"CU\", \"+53\");\n            countryCodeMap.put(\"GP\", \"+590\");\n            countryCodeMap.put(\"GU\", \"+1\");\n            countryCodeMap.put(\"GY\", \"+592\");\n            countryCodeMap.put(\"KZ\", \"+7\");\n            countryCodeMap.put(\"HT\", \"+509\");\n            countryCodeMap.put(\"KR\", \"+82\");\n            countryCodeMap.put(\"NL\", \"+31\");\n            countryCodeMap.put(\"BQ\", \"+599\");\n            countryCodeMap.put(\"SX\", \"+1\");\n            countryCodeMap.put(\"ME\", \"+382\");\n            countryCodeMap.put(\"HN\", \"+504\");\n            countryCodeMap.put(\"KI\", \"+686\");\n            countryCodeMap.put(\"DJ\", \"+253\");\n            countryCodeMap.put(\"KG\", \"+996\");\n            countryCodeMap.put(\"GN\", \"+224\");\n            countryCodeMap.put(\"GW\", \"+245\");\n            countryCodeMap.put(\"CA\", \"+1\");\n            countryCodeMap.put(\"GH\", \"+233\");\n            countryCodeMap.put(\"GA\", \"+241\");\n            countryCodeMap.put(\"KH\", \"+855\");\n            countryCodeMap.put(\"CZ\", \"+420\");\n            countryCodeMap.put(\"ZW\", \"+263\");\n            countryCodeMap.put(\"CM\", \"+237\");\n            countryCodeMap.put(\"QA\", \"+974\");\n            countryCodeMap.put(\"KY\", \"+1\");\n            countryCodeMap.put(\"CC\", \"+61\");\n            countryCodeMap.put(\"KM\", \"+269\");\n            countryCodeMap.put(\"XK\", \"+383\");\n            countryCodeMap.put(\"CI\", \"+225\");\n            countryCodeMap.put(\"KW\", \"+965\");\n            countryCodeMap.put(\"HR\", \"+385\");\n            countryCodeMap.put(\"KE\", \"+254\");\n            countryCodeMap.put(\"CK\", \"+682\");\n            countryCodeMap.put(\"CW\", \"+599\");\n            countryCodeMap.put(\"LV\", \"+371\");\n            countryCodeMap.put(\"LS\", \"+266\");\n            countryCodeMap.put(\"LA\", \"+856\");\n            countryCodeMap.put(\"LB\", \"+961\");\n            countryCodeMap.put(\"LT\", \"+370\");\n            countryCodeMap.put(\"LR\", \"+231\");\n            countryCodeMap.put(\"LY\", \"+218\");\n            countryCodeMap.put(\"LI\", \"+423\");\n            countryCodeMap.put(\"RE\", \"+262\");\n            countryCodeMap.put(\"LU\", \"+352\");\n            countryCodeMap.put(\"RW\", \"+250\");\n            countryCodeMap.put(\"RO\", \"+40\");\n            countryCodeMap.put(\"MG\", \"+261\");\n            countryCodeMap.put(\"IM\", \"+44\");\n            countryCodeMap.put(\"MV\", \"+960\");\n            countryCodeMap.put(\"MT\", \"+356\");\n            countryCodeMap.put(\"MW\", \"+265\");\n            countryCodeMap.put(\"MY\", \"+60\");\n            countryCodeMap.put(\"ML\", \"+223\");\n            countryCodeMap.put(\"MK\", \"+389\");\n            countryCodeMap.put(\"MH\", \"+692\");\n            countryCodeMap.put(\"MQ\", \"+596\");\n            countryCodeMap.put(\"YT\", \"+262\");\n            countryCodeMap.put(\"MU\", \"+230\");\n            countryCodeMap.put(\"MR\", \"+222\");\n            countryCodeMap.put(\"US\", \"+1\");\n            countryCodeMap.put(\"AS\", \"+1\");\n            countryCodeMap.put(\"VI\", \"+1\");\n            countryCodeMap.put(\"MN\", \"+976\");\n            countryCodeMap.put(\"MS\", \"+1\");\n            countryCodeMap.put(\"BD\", \"+880\");\n            countryCodeMap.put(\"PE\", \"+51\");\n            countryCodeMap.put(\"FM\", \"+691\");\n            countryCodeMap.put(\"MM\", \"+95\");\n            countryCodeMap.put(\"MD\", \"+373\");\n            countryCodeMap.put(\"MA\", \"+212\");\n            countryCodeMap.put(\"MC\", \"+377\");\n            countryCodeMap.put(\"MZ\", \"+258\");\n            countryCodeMap.put(\"MX\", \"+52\");\n            countryCodeMap.put(\"NA\", \"+264\");\n            countryCodeMap.put(\"ZA\", \"+27\");\n            countryCodeMap.put(\"SS\", \"+211\");\n            countryCodeMap.put(\"NR\", \"+674\");\n            countryCodeMap.put(\"NI\", \"+505\");\n            countryCodeMap.put(\"NP\", \"+977\");\n            countryCodeMap.put(\"NE\", \"+227\");\n            countryCodeMap.put(\"NG\", \"+234\");\n            countryCodeMap.put(\"NU\", \"+683\");\n            countryCodeMap.put(\"NO\", \"+47\");\n            countryCodeMap.put(\"NF\", \"+672\");\n            countryCodeMap.put(\"PW\", \"+680\");\n            countryCodeMap.put(\"PT\", \"+351\");\n            countryCodeMap.put(\"JP\", \"+81\");\n            countryCodeMap.put(\"SE\", \"+46\");\n            countryCodeMap.put(\"CH\", \"+41\");\n            countryCodeMap.put(\"SV\", \"+503\");\n            countryCodeMap.put(\"WS\", \"+685\");\n            countryCodeMap.put(\"RS\", \"+381\");\n            countryCodeMap.put(\"SL\", \"+232\");\n            countryCodeMap.put(\"SN\", \"+221\");\n            countryCodeMap.put(\"CY\", \"+357\");\n            countryCodeMap.put(\"SC\", \"+248\");\n            countryCodeMap.put(\"SA\", \"+966\");\n            countryCodeMap.put(\"BL\", \"+590\");\n            countryCodeMap.put(\"CX\", \"+61\");\n            countryCodeMap.put(\"ST\", \"+239\");\n            countryCodeMap.put(\"SH\", \"+290\");\n            countryCodeMap.put(\"PN\", \"+870\");\n            countryCodeMap.put(\"KN\", \"+1\");\n            countryCodeMap.put(\"LC\", \"+1\");\n            countryCodeMap.put(\"MF\", \"+590\");\n            countryCodeMap.put(\"SM\", \"+378\");\n            countryCodeMap.put(\"PM\", \"+508\");\n            countryCodeMap.put(\"VC\", \"+1\");\n            countryCodeMap.put(\"LK\", \"+94\");\n            countryCodeMap.put(\"SK\", \"+421\");\n            countryCodeMap.put(\"SI\", \"+386\");\n            countryCodeMap.put(\"SJ\", \"+47\");\n            countryCodeMap.put(\"SZ\", \"+268\");\n            countryCodeMap.put(\"SD\", \"+249\");\n            countryCodeMap.put(\"SR\", \"+597\");\n            countryCodeMap.put(\"SB\", \"+677\");\n            countryCodeMap.put(\"SO\", \"+252\");\n            countryCodeMap.put(\"TJ\", \"+992\");\n            countryCodeMap.put(\"TH\", \"+66\");\n            countryCodeMap.put(\"TZ\", \"+255\");\n            countryCodeMap.put(\"TO\", \"+676\");\n            countryCodeMap.put(\"TC\", \"+1\");\n            countryCodeMap.put(\"TA\", \"+290\");\n            countryCodeMap.put(\"TT\", \"+1\");\n            countryCodeMap.put(\"TN\", \"+216\");\n            countryCodeMap.put(\"TV\", \"+688\");\n            countryCodeMap.put(\"TR\", \"+90\");\n            countryCodeMap.put(\"TM\", \"+993\");\n            countryCodeMap.put(\"TK\", \"+690\");\n            countryCodeMap.put(\"WF\", \"+681\");\n            countryCodeMap.put(\"VU\", \"+678\");\n            countryCodeMap.put(\"GT\", \"+502\");\n            countryCodeMap.put(\"VE\", \"+58\");\n            countryCodeMap.put(\"BN\", \"+673\");\n            countryCodeMap.put(\"UG\", \"+256\");\n            countryCodeMap.put(\"UA\", \"+380\");\n            countryCodeMap.put(\"UY\", \"+598\");\n            countryCodeMap.put(\"UZ\", \"+998\");\n            countryCodeMap.put(\"GR\", \"+30\");\n            countryCodeMap.put(\"ES\", \"+34\");\n            countryCodeMap.put(\"EH\", \"+212\");\n            countryCodeMap.put(\"SG\", \"+65\");\n            countryCodeMap.put(\"NC\", \"+687\");\n            countryCodeMap.put(\"NZ\", \"+64\");\n            countryCodeMap.put(\"HU\", \"+36\");\n            countryCodeMap.put(\"SY\", \"+963\");\n            countryCodeMap.put(\"JM\", \"+1\");\n            countryCodeMap.put(\"AM\", \"+374\");\n            countryCodeMap.put(\"YE\", \"+967\");\n            countryCodeMap.put(\"IQ\", \"+964\");\n            countryCodeMap.put(\"UM\", \"+1\");\n            countryCodeMap.put(\"IR\", \"+98\");\n            countryCodeMap.put(\"IL\", \"+972\");\n            countryCodeMap.put(\"IT\", \"+39\");\n            countryCodeMap.put(\"IN\", \"+91\");\n            countryCodeMap.put(\"ID\", \"+62\");\n            countryCodeMap.put(\"GB\", \"+44\");\n            countryCodeMap.put(\"VG\", \"+1\");\n            countryCodeMap.put(\"IO\", \"+246\");\n            countryCodeMap.put(\"JO\", \"+962\");\n            countryCodeMap.put(\"VN\", \"+84\");\n            countryCodeMap.put(\"ZM\", \"+260\");\n            countryCodeMap.put(\"JE\", \"+44\");\n            countryCodeMap.put(\"TD\", \"+235\");\n            countryCodeMap.put(\"GI\", \"+350\");\n            countryCodeMap.put(\"CL\", \"+56\");\n            countryCodeMap.put(\"CF\", \"+236\");\n            countryCodeMap.put(\"CN\", \"+86\");\n            countryCodeMap.put(\"MO\", \"+853\");\n            countryCodeMap.put(\"TW\", \"+886\");\n            countryCodeMap.put(\"HK\", \"+852\");\n        }\n        return countryCodeMap;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/CrashUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.io.File;\nimport java.lang.Thread.UncaughtExceptionHandler;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.Map;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/09/27\n *     desc  : utils about crash\n * </pre>\n */\npublic final class CrashUtils {\n\n    private static final String FILE_SEP = System.getProperty(\"file.separator\");\n\n    private static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();\n\n    private CrashUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Initialization.\n     */\n    public static void init() {\n        init(\"\");\n    }\n\n    /**\n     * Initialization\n     *\n     * @param crashDir The directory of saving crash information.\n     */\n    public static void init(@NonNull final File crashDir) {\n        init(crashDir.getAbsolutePath(), null);\n    }\n\n    /**\n     * Initialization\n     *\n     * @param crashDirPath The directory's path of saving crash information.\n     */\n    public static void init(final String crashDirPath) {\n        init(crashDirPath, null);\n    }\n\n    /**\n     * Initialization\n     *\n     * @param onCrashListener The crash listener.\n     */\n    public static void init(final OnCrashListener onCrashListener) {\n        init(\"\", onCrashListener);\n    }\n\n    /**\n     * Initialization\n     *\n     * @param crashDir        The directory of saving crash information.\n     * @param onCrashListener The crash listener.\n     */\n    public static void init(@NonNull final File crashDir, final OnCrashListener onCrashListener) {\n        init(crashDir.getAbsolutePath(), onCrashListener);\n    }\n\n    /**\n     * Initialization\n     *\n     * @param crashDirPath    The directory's path of saving crash information.\n     * @param onCrashListener The crash listener.\n     */\n    public static void init(final String crashDirPath, final OnCrashListener onCrashListener) {\n        String dirPath;\n        if (UtilsBridge.isSpace(crashDirPath)) {\n            if (UtilsBridge.isSDCardEnableByEnvironment()\n                    && Utils.getApp().getExternalFilesDir(null) != null)\n                dirPath = Utils.getApp().getExternalFilesDir(null) + FILE_SEP + \"crash\" + FILE_SEP;\n            else {\n                dirPath = Utils.getApp().getFilesDir() + FILE_SEP + \"crash\" + FILE_SEP;\n            }\n        } else {\n            dirPath = crashDirPath.endsWith(FILE_SEP) ? crashDirPath : crashDirPath + FILE_SEP;\n        }\n        Thread.setDefaultUncaughtExceptionHandler(getUncaughtExceptionHandler(dirPath, onCrashListener));\n    }\n\n    private static UncaughtExceptionHandler getUncaughtExceptionHandler(final String dirPath,\n                                                                        final OnCrashListener onCrashListener) {\n        return new UncaughtExceptionHandler() {\n            @Override\n            public void uncaughtException(@NonNull final Thread t, @NonNull final Throwable e) {\n                final String time = new SimpleDateFormat(\"yyyy_MM_dd-HH_mm_ss\").format(new Date());\n                CrashInfo info = new CrashInfo(time, e);\n                if (onCrashListener != null) {\n                    onCrashListener.onCrash(info);\n                }\n                final String crashFile = dirPath + time + \".txt\";\n                UtilsBridge.writeFileFromString(crashFile, info.toString(), true);\n\n                if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) {\n                    DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(t, e);\n                }\n            }\n        };\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // interface\n    ///////////////////////////////////////////////////////////////////////////\n\n    public interface OnCrashListener {\n        void onCrash(CrashInfo crashInfo);\n    }\n\n    public static final class CrashInfo {\n        private UtilsBridge.FileHead mFileHeadProvider;\n        private Throwable            mThrowable;\n\n        private CrashInfo(String time, Throwable throwable) {\n            mThrowable = throwable;\n            mFileHeadProvider = new UtilsBridge.FileHead(\"Crash\");\n            mFileHeadProvider.addFirst(\"Time Of Crash\", time);\n        }\n\n        public final void addExtraHead(Map<String, String> extraHead) {\n            mFileHeadProvider.append(extraHead);\n        }\n\n        public final void addExtraHead(String key, String value) {\n            mFileHeadProvider.append(key, value);\n        }\n\n        public final Throwable getThrowable() {\n            return mThrowable;\n        }\n\n        @Override\n        public String toString() {\n            return mFileHeadProvider.toString() +\n                    UtilsBridge.getFullStackTrace(mThrowable);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/DangerousUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.PendingIntent;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.os.PowerManager;\nimport android.telephony.SmsManager;\nimport android.telephony.TelephonyManager;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport androidx.annotation.RequiresPermission;\n\nimport java.io.File;\nimport java.lang.reflect.Method;\nimport java.util.List;\n\nimport static android.Manifest.permission.MODIFY_PHONE_STATE;\nimport static android.Manifest.permission.SEND_SMS;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/06/29\n *     desc  :\n * </pre>\n */\npublic class DangerousUtils {\n\n    private DangerousUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // AppUtils\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Install the app silently.\n     * <p>Without root permission must hold\n     * {@code android:sharedUserId=\"android.uid.shell\"} and\n     * {@code <uses-permission android:name=\"android.permission.INSTALL_PACKAGES\" />}</p>\n     *\n     * @param filePath The path of file.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean installAppSilent(final String filePath) {\n        return installAppSilent(getFileByPath(filePath), null);\n    }\n\n    /**\n     * Install the app silently.\n     * <p>Without root permission must hold\n     * {@code android:sharedUserId=\"android.uid.shell\"} and\n     * {@code <uses-permission android:name=\"android.permission.INSTALL_PACKAGES\" />}</p>\n     *\n     * @param file The file.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean installAppSilent(final File file) {\n        return installAppSilent(file, null);\n    }\n\n\n    /**\n     * Install the app silently.\n     * <p>Without root permission must hold\n     * {@code android:sharedUserId=\"android.uid.shell\"} and\n     * {@code <uses-permission android:name=\"android.permission.INSTALL_PACKAGES\" />}</p>\n     *\n     * @param filePath The path of file.\n     * @param params   The params of installation(e.g.,<code>-r</code>, <code>-s</code>).\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean installAppSilent(final String filePath, final String params) {\n        return installAppSilent(getFileByPath(filePath), params);\n    }\n\n    /**\n     * Install the app silently.\n     * <p>Without root permission must hold\n     * {@code android:sharedUserId=\"android.uid.shell\"} and\n     * {@code <uses-permission android:name=\"android.permission.INSTALL_PACKAGES\" />}</p>\n     *\n     * @param file   The file.\n     * @param params The params of installation(e.g.,<code>-r</code>, <code>-s</code>).\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean installAppSilent(final File file, final String params) {\n        return installAppSilent(file, params, isDeviceRooted());\n    }\n\n    /**\n     * Install the app silently.\n     * <p>Without root permission must hold\n     * {@code android:sharedUserId=\"android.uid.shell\"} and\n     * {@code <uses-permission android:name=\"android.permission.INSTALL_PACKAGES\" />}</p>\n     *\n     * @param file     The file.\n     * @param params   The params of installation(e.g.,<code>-r</code>, <code>-s</code>).\n     * @param isRooted True to use root, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean installAppSilent(final File file,\n                                           final String params,\n                                           final boolean isRooted) {\n        if (!isFileExists(file)) return false;\n        String filePath = '\"' + file.getAbsolutePath() + '\"';\n        String command = \"LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm install \" +\n                (params == null ? \"\" : params + \" \")\n                + filePath;\n        ShellUtils.CommandResult commandResult = ShellUtils.execCmd(command, isRooted);\n        if (commandResult.successMsg != null\n                && commandResult.successMsg.toLowerCase().contains(\"success\")) {\n            return true;\n        } else {\n            Log.e(\"AppUtils\", \"installAppSilent successMsg: \" + commandResult.successMsg +\n                    \", errorMsg: \" + commandResult.errorMsg);\n            return false;\n        }\n    }\n\n    /**\n     * Uninstall the app silently.\n     * <p>Without root permission must hold\n     * {@code android:sharedUserId=\"android.uid.shell\"} and\n     * {@code <uses-permission android:name=\"android.permission.DELETE_PACKAGES\" />}</p>\n     *\n     * @param packageName The name of the package.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean uninstallAppSilent(final String packageName) {\n        return uninstallAppSilent(packageName, false);\n    }\n\n    /**\n     * Uninstall the app silently.\n     * <p>Without root permission must hold\n     * {@code android:sharedUserId=\"android.uid.shell\"} and\n     * {@code <uses-permission android:name=\"android.permission.DELETE_PACKAGES\" />}</p>\n     *\n     * @param packageName The name of the package.\n     * @param isKeepData  Is keep the data.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean uninstallAppSilent(final String packageName, final boolean isKeepData) {\n        return uninstallAppSilent(packageName, isKeepData, isDeviceRooted());\n    }\n\n    /**\n     * Uninstall the app silently.\n     * <p>Without root permission must hold\n     * {@code android:sharedUserId=\"android.uid.shell\"} and\n     * {@code <uses-permission android:name=\"android.permission.DELETE_PACKAGES\" />}</p>\n     *\n     * @param packageName The name of the package.\n     * @param isKeepData  Is keep the data.\n     * @param isRooted    True to use root, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean uninstallAppSilent(final String packageName,\n                                             final boolean isKeepData,\n                                             final boolean isRooted) {\n        if (isSpace(packageName)) return false;\n        String command = \"LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm uninstall \"\n                + (isKeepData ? \"-k \" : \"\")\n                + packageName;\n        ShellUtils.CommandResult commandResult = ShellUtils.execCmd(command, isRooted);\n        if (commandResult.successMsg != null\n                && commandResult.successMsg.toLowerCase().contains(\"success\")) {\n            return true;\n        } else {\n            Log.e(\"AppUtils\", \"uninstallAppSilent successMsg: \" + commandResult.successMsg +\n                    \", errorMsg: \" + commandResult.errorMsg);\n            return false;\n        }\n    }\n\n    private static boolean isFileExists(final File file) {\n        return file != null && file.exists();\n    }\n\n    private static File getFileByPath(final String filePath) {\n        return isSpace(filePath) ? null : new File(filePath);\n    }\n\n    private static boolean isSpace(final String s) {\n        if (s == null) return true;\n        for (int i = 0, len = s.length(); i < len; ++i) {\n            if (!Character.isWhitespace(s.charAt(i))) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    private static boolean isDeviceRooted() {\n        String su = \"su\";\n        String[] locations = {\"/system/bin/\", \"/system/xbin/\", \"/sbin/\", \"/system/sd/xbin/\",\n                \"/system/bin/failsafe/\", \"/data/local/xbin/\", \"/data/local/bin/\", \"/data/local/\",\n                \"/system/sbin/\", \"/usr/bin/\", \"/vendor/bin/\"};\n        for (String location : locations) {\n            if (new File(location + su).exists()) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // DeviceUtils\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Shutdown the device\n     * <p>Requires root permission\n     * or hold {@code android:sharedUserId=\"android.uid.system\"},\n     * {@code <uses-permission android:name=\"android.permission.SHUTDOWN\" />}\n     * in manifest.</p>\n     *\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean shutdown() {\n        try {\n            ShellUtils.CommandResult result = ShellUtils.execCmd(\"reboot -p\", true);\n            if (result.result == 0) return true;\n            Utils.getApp().startActivity(IntentUtils.getShutdownIntent());\n            return true;\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    /**\n     * Reboot the device.\n     * <p>Requires root permission\n     * or hold {@code android:sharedUserId=\"android.uid.system\"} in manifest.</p>\n     *\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean reboot() {\n        try {\n            ShellUtils.CommandResult result = ShellUtils.execCmd(\"reboot\", true);\n            if (result.result == 0) return true;\n            Intent intent = new Intent(Intent.ACTION_REBOOT);\n            intent.putExtra(\"nowait\", 1);\n            intent.putExtra(\"interval\", 1);\n            intent.putExtra(\"window\", 0);\n            Utils.getApp().sendBroadcast(intent);\n            return true;\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    /**\n     * Reboot the device.\n     * <p>Requires root permission\n     * or hold {@code android:sharedUserId=\"android.uid.system\"},\n     * {@code <uses-permission android:name=\"android.permission.REBOOT\" />}</p>\n     *\n     * @param reason code to pass to the kernel (e.g., \"recovery\") to\n     *               request special boot modes, or null.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean reboot(final String reason) {\n        try {\n            PowerManager pm = (PowerManager) Utils.getApp().getSystemService(Context.POWER_SERVICE);\n            pm.reboot(reason);\n            return true;\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    /**\n     * Reboot the device to recovery.\n     * <p>Requires root permission.</p>\n     *\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean reboot2Recovery() {\n        ShellUtils.CommandResult result = ShellUtils.execCmd(\"reboot recovery\", true);\n        return result.result == 0;\n    }\n\n    /**\n     * Reboot the device to bootloader.\n     * <p>Requires root permission.</p>\n     *\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean reboot2Bootloader() {\n        ShellUtils.CommandResult result = ShellUtils.execCmd(\"reboot bootloader\", true);\n        return result.result == 0;\n    }\n\n\n    /**\n     * Enable or disable mobile data.\n     * <p>Must hold {@code android:sharedUserId=\"android.uid.system\"},\n     * {@code <uses-permission android:name=\"android.permission.MODIFY_PHONE_STATE\" />}</p>\n     *\n     * @param enabled True to enabled, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    @RequiresPermission(MODIFY_PHONE_STATE)\n    public static boolean setMobileDataEnabled(final boolean enabled) {\n        try {\n            TelephonyManager tm =\n                    (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);\n            if (tm == null) return false;\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                tm.setDataEnabled(enabled);\n                return true;\n            }\n            Method setDataEnabledMethod =\n                    tm.getClass().getDeclaredMethod(\"setDataEnabled\", boolean.class);\n            setDataEnabledMethod.invoke(tm, enabled);\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return false;\n    }\n\n    /**\n     * Send sms silently.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.SEND_SMS\" />}</p>\n     *\n     * @param phoneNumber The phone number.\n     * @param content     The content.\n     */\n    @RequiresPermission(SEND_SMS)\n    public static void sendSmsSilent(final String phoneNumber, final String content) {\n        if (TextUtils.isEmpty(content)) return;\n        PendingIntent sentIntent;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            sentIntent = PendingIntent.getBroadcast(Utils.getApp(), 0, new Intent(\"send\"), 0 | PendingIntent.FLAG_IMMUTABLE);\n        } else {\n            sentIntent = PendingIntent.getBroadcast(Utils.getApp(), 0, new Intent(\"send\"), 0);\n        }\n        SmsManager smsManager = SmsManager.getDefault();\n        if (content.length() >= 70) {\n            List<String> ms = smsManager.divideMessage(content);\n            for (String str : ms) {\n                smsManager.sendTextMessage(phoneNumber, null, str, sentIntent, null);\n            }\n        } else {\n            smsManager.sendTextMessage(phoneNumber, null, content, sentIntent, null);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/DebouncingUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.os.SystemClock;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.text.TextUtils;\nimport android.view.View;\n\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2020/09/01\n *     desc  : utils about debouncing\n * </pre>\n */\npublic class DebouncingUtils {\n\n    private static final int               CACHE_SIZE               = 64;\n    private static final Map<String, Long> KEY_MILLIS_MAP           = new ConcurrentHashMap<>(CACHE_SIZE);\n    private static final long              DEBOUNCING_DEFAULT_VALUE = 1000;\n\n    private DebouncingUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return whether the view is not in a jitter state.\n     *\n     * @param view The view.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isValid(@NonNull final View view) {\n        return isValid(view, DEBOUNCING_DEFAULT_VALUE);\n    }\n\n    /**\n     * Return whether the view is not in a jitter state.\n     *\n     * @param view     The view.\n     * @param duration The duration.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isValid(@NonNull final View view, final long duration) {\n        return isValid(String.valueOf(view.hashCode()), duration);\n    }\n\n    /**\n     * Return whether the key is not in a jitter state.\n     *\n     * @param key      The key.\n     * @param duration The duration.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isValid(@NonNull String key, final long duration) {\n        if (TextUtils.isEmpty(key)) {\n            throw new IllegalArgumentException(\"The key is null.\");\n        }\n        if (duration < 0) {\n            throw new IllegalArgumentException(\"The duration is less than 0.\");\n        }\n        long curTime = SystemClock.elapsedRealtime();\n        clearIfNecessary(curTime);\n        Long validTime = KEY_MILLIS_MAP.get(key);\n        if (validTime == null || curTime >= validTime) {\n            KEY_MILLIS_MAP.put(key, curTime + duration);\n            return true;\n        }\n        return false;\n    }\n\n    private static void clearIfNecessary(long curTime) {\n        if (KEY_MILLIS_MAP.size() < CACHE_SIZE) return;\n        for (Iterator<Map.Entry<String, Long>> it = KEY_MILLIS_MAP.entrySet().iterator(); it.hasNext(); ) {\n            Map.Entry<String, Long> entry = it.next();\n            Long validTime = entry.getValue();\n            if (curTime >= validTime) {\n                it.remove();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/DeviceUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.res.Configuration;\nimport android.content.res.Resources;\nimport android.net.Uri;\nimport android.net.wifi.WifiInfo;\nimport android.net.wifi.WifiManager;\nimport android.os.Build;\nimport android.provider.Settings;\nimport androidx.annotation.RequiresApi;\nimport androidx.annotation.RequiresPermission;\nimport android.telephony.TelephonyManager;\nimport android.text.TextUtils;\n\nimport java.io.File;\nimport java.net.InetAddress;\nimport java.net.NetworkInterface;\nimport java.net.SocketException;\nimport java.util.Enumeration;\nimport java.util.UUID;\n\nimport static android.Manifest.permission.ACCESS_WIFI_STATE;\nimport static android.Manifest.permission.CHANGE_WIFI_STATE;\nimport static android.Manifest.permission.INTERNET;\nimport static android.content.Context.WIFI_SERVICE;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/8/1\n *     desc  : utils about device\n * </pre>\n */\npublic final class DeviceUtils {\n\n    private DeviceUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return whether device is rooted.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isDeviceRooted() {\n        String su = \"su\";\n        String[] locations = {\"/system/bin/\", \"/system/xbin/\", \"/sbin/\", \"/system/sd/xbin/\",\n                \"/system/bin/failsafe/\", \"/data/local/xbin/\", \"/data/local/bin/\", \"/data/local/\",\n                \"/system/sbin/\", \"/usr/bin/\", \"/vendor/bin/\"};\n        for (String location : locations) {\n            if (new File(location + su).exists()) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Return whether ADB is enabled.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)\n    public static boolean isAdbEnabled() {\n        return Settings.Secure.getInt(\n                Utils.getApp().getContentResolver(),\n                Settings.Global.ADB_ENABLED, 0\n        ) > 0;\n    }\n\n    /**\n     * Return the version name of device's system.\n     *\n     * @return the version name of device's system\n     */\n    public static String getSDKVersionName() {\n        return Build.VERSION.RELEASE;\n    }\n\n    /**\n     * Return version code of device's system.\n     *\n     * @return version code of device's system\n     */\n    public static int getSDKVersionCode() {\n        return Build.VERSION.SDK_INT;\n    }\n\n    /**\n     * Return the android id of device.\n     *\n     * @return the android id of device\n     */\n    @SuppressLint(\"HardwareIds\")\n    public static String getAndroidID() {\n        String id = Settings.Secure.getString(\n                Utils.getApp().getContentResolver(),\n                Settings.Secure.ANDROID_ID\n        );\n        if (\"9774d56d682e549c\".equals(id)) return \"\";\n        return id == null ? \"\" : id;\n    }\n\n    /**\n     * Return the MAC address.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />},\n     * {@code <uses-permission android:name=\"android.permission.INTERNET\" />},\n     * {@code <uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\" />}</p>\n     *\n     * @return the MAC address\n     */\n    @RequiresPermission(allOf = {ACCESS_WIFI_STATE, CHANGE_WIFI_STATE})\n    public static String getMacAddress() {\n        String macAddress = getMacAddress((String[]) null);\n        if (!TextUtils.isEmpty(macAddress) || getWifiEnabled()) return macAddress;\n        setWifiEnabled(true);\n        setWifiEnabled(false);\n        return getMacAddress((String[]) null);\n    }\n\n    private static boolean getWifiEnabled() {\n        @SuppressLint(\"WifiManagerLeak\")\n        WifiManager manager = (WifiManager) Utils.getApp().getSystemService(WIFI_SERVICE);\n        if (manager == null) return false;\n        return manager.isWifiEnabled();\n    }\n\n    /**\n     * Enable or disable wifi.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\" />}</p>\n     *\n     * @param enabled True to enabled, false otherwise.\n     */\n    @RequiresPermission(CHANGE_WIFI_STATE)\n    private static void setWifiEnabled(final boolean enabled) {\n        @SuppressLint(\"WifiManagerLeak\")\n        WifiManager manager = (WifiManager) Utils.getApp().getSystemService(WIFI_SERVICE);\n        if (manager == null) return;\n        if (enabled == manager.isWifiEnabled()) return;\n        manager.setWifiEnabled(enabled);\n    }\n\n    /**\n     * Return the MAC address.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />},\n     * {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @return the MAC address\n     */\n    @RequiresPermission(allOf = {ACCESS_WIFI_STATE})\n    public static String getMacAddress(final String... excepts) {\n        String macAddress = getMacAddressByNetworkInterface();\n        if (isAddressNotInExcepts(macAddress, excepts)) {\n            return macAddress;\n        }\n        macAddress = getMacAddressByInetAddress();\n        if (isAddressNotInExcepts(macAddress, excepts)) {\n            return macAddress;\n        }\n        macAddress = getMacAddressByWifiInfo();\n        if (isAddressNotInExcepts(macAddress, excepts)) {\n            return macAddress;\n        }\n        macAddress = getMacAddressByFile();\n        if (isAddressNotInExcepts(macAddress, excepts)) {\n            return macAddress;\n        }\n        return \"\";\n    }\n\n    private static boolean isAddressNotInExcepts(final String address, final String... excepts) {\n        if (TextUtils.isEmpty(address)) {\n            return false;\n        }\n        if (\"02:00:00:00:00:00\".equals(address)) {\n            return false;\n        }\n        if (excepts == null || excepts.length == 0) {\n            return true;\n        }\n        for (String filter : excepts) {\n            if (filter != null && filter.equals(address)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    @RequiresPermission(ACCESS_WIFI_STATE)\n    private static String getMacAddressByWifiInfo() {\n        try {\n            final WifiManager wifi = (WifiManager) Utils.getApp()\n                    .getApplicationContext().getSystemService(WIFI_SERVICE);\n            if (wifi != null) {\n                final WifiInfo info = wifi.getConnectionInfo();\n                if (info != null) {\n                    @SuppressLint(\"HardwareIds\")\n                    String macAddress = info.getMacAddress();\n                    if (!TextUtils.isEmpty(macAddress)) {\n                        return macAddress;\n                    }\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return \"02:00:00:00:00:00\";\n    }\n\n    private static String getMacAddressByNetworkInterface() {\n        try {\n            Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();\n            while (nis.hasMoreElements()) {\n                NetworkInterface ni = nis.nextElement();\n                if (ni == null || !ni.getName().equalsIgnoreCase(\"wlan0\")) continue;\n                byte[] macBytes = ni.getHardwareAddress();\n                if (macBytes != null && macBytes.length > 0) {\n                    StringBuilder sb = new StringBuilder();\n                    for (byte b : macBytes) {\n                        sb.append(String.format(\"%02x:\", b));\n                    }\n                    return sb.substring(0, sb.length() - 1);\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return \"02:00:00:00:00:00\";\n    }\n\n    private static String getMacAddressByInetAddress() {\n        try {\n            InetAddress inetAddress = getInetAddress();\n            if (inetAddress != null) {\n                NetworkInterface ni = NetworkInterface.getByInetAddress(inetAddress);\n                if (ni != null) {\n                    byte[] macBytes = ni.getHardwareAddress();\n                    if (macBytes != null && macBytes.length > 0) {\n                        StringBuilder sb = new StringBuilder();\n                        for (byte b : macBytes) {\n                            sb.append(String.format(\"%02x:\", b));\n                        }\n                        return sb.substring(0, sb.length() - 1);\n                    }\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return \"02:00:00:00:00:00\";\n    }\n\n    private static InetAddress getInetAddress() {\n        try {\n            Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();\n            while (nis.hasMoreElements()) {\n                NetworkInterface ni = nis.nextElement();\n                // To prevent phone of xiaomi return \"10.0.2.15\"\n                if (!ni.isUp()) continue;\n                Enumeration<InetAddress> addresses = ni.getInetAddresses();\n                while (addresses.hasMoreElements()) {\n                    InetAddress inetAddress = addresses.nextElement();\n                    if (!inetAddress.isLoopbackAddress()) {\n                        String hostAddress = inetAddress.getHostAddress();\n                        if (hostAddress.indexOf(':') < 0) return inetAddress;\n                    }\n                }\n            }\n        } catch (SocketException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    private static String getMacAddressByFile() {\n        ShellUtils.CommandResult result = UtilsBridge.execCmd(\"getprop wifi.interface\", false);\n        if (result.result == 0) {\n            String name = result.successMsg;\n            if (name != null) {\n                result = UtilsBridge.execCmd(\"cat /sys/class/net/\" + name + \"/address\", false);\n                if (result.result == 0) {\n                    String address = result.successMsg;\n                    if (address != null && address.length() > 0) {\n                        return address;\n                    }\n                }\n            }\n        }\n        return \"02:00:00:00:00:00\";\n    }\n\n    /**\n     * Return the manufacturer of the product/hardware.\n     * <p>e.g. Xiaomi</p>\n     *\n     * @return the manufacturer of the product/hardware\n     */\n    public static String getManufacturer() {\n        return Build.MANUFACTURER;\n    }\n\n    /**\n     * Return the model of device.\n     * <p>e.g. MI2SC</p>\n     *\n     * @return the model of device\n     */\n    public static String getModel() {\n        String model = Build.MODEL;\n        if (model != null) {\n            model = model.trim().replaceAll(\"\\\\s*\", \"\");\n        } else {\n            model = \"\";\n        }\n        return model;\n    }\n\n    /**\n     * Return an ordered list of ABIs supported by this device. The most preferred ABI is the first\n     * element in the list.\n     *\n     * @return an ordered list of ABIs supported by this device\n     */\n    public static String[] getABIs() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            return Build.SUPPORTED_ABIS;\n        } else {\n            if (!TextUtils.isEmpty(Build.CPU_ABI2)) {\n                return new String[]{Build.CPU_ABI, Build.CPU_ABI2};\n            }\n            return new String[]{Build.CPU_ABI};\n        }\n    }\n\n    /**\n     * Return whether device is tablet.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isTablet() {\n        return (Resources.getSystem().getConfiguration().screenLayout\n                & Configuration.SCREENLAYOUT_SIZE_MASK)\n                >= Configuration.SCREENLAYOUT_SIZE_LARGE;\n    }\n\n    /**\n     * Return whether device is emulator.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isEmulator() {\n        boolean checkProperty = Build.FINGERPRINT.startsWith(\"generic\")\n                || Build.FINGERPRINT.toLowerCase().contains(\"vbox\")\n                || Build.FINGERPRINT.toLowerCase().contains(\"test-keys\")\n                || Build.MODEL.contains(\"google_sdk\")\n                || Build.MODEL.contains(\"Emulator\")\n                || Build.MODEL.contains(\"Android SDK built for x86\")\n                || Build.MANUFACTURER.contains(\"Genymotion\")\n                || (Build.BRAND.startsWith(\"generic\") && Build.DEVICE.startsWith(\"generic\"))\n                || \"google_sdk\".equals(Build.PRODUCT);\n        if (checkProperty) return true;\n\n        String operatorName = \"\";\n        TelephonyManager tm = (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);\n        if (tm != null) {\n            String name = tm.getNetworkOperatorName();\n            if (name != null) {\n                operatorName = name;\n            }\n        }\n        boolean checkOperatorName = operatorName.toLowerCase().equals(\"android\");\n        if (checkOperatorName) return true;\n\n        String url = \"tel:\" + \"123456\";\n        Intent intent = new Intent();\n        intent.setData(Uri.parse(url));\n        intent.setAction(Intent.ACTION_DIAL);\n        boolean checkDial = intent.resolveActivity(Utils.getApp().getPackageManager()) == null;\n        if (checkDial) return true;\n\n//        boolean checkDebuggerConnected = Debug.isDebuggerConnected();\n//        if (checkDebuggerConnected) return true;\n\n        return false;\n    }\n\n    /**\n     * Whether user has enabled development settings.\n     *\n     * @return whether user has enabled development settings.\n     */\n    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)\n    public static boolean isDevelopmentSettingsEnabled() {\n        return Settings.Global.getInt(\n                Utils.getApp().getContentResolver(),\n                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0\n        ) > 0;\n    }\n\n\n    private static final    String KEY_UDID = \"KEY_UDID\";\n    private volatile static String udid;\n\n    /**\n     * Return the unique device id.\n     * <pre>{1}{UUID(macAddress)}</pre>\n     * <pre>{2}{UUID(androidId )}</pre>\n     * <pre>{9}{UUID(random    )}</pre>\n     *\n     * @return the unique device id\n     */\n    public static String getUniqueDeviceId() {\n        return getUniqueDeviceId(\"\", true);\n    }\n\n    /**\n     * Return the unique device id.\n     * <pre>android 10 deprecated {prefix}{1}{UUID(macAddress)}</pre>\n     * <pre>{prefix}{2}{UUID(androidId )}</pre>\n     * <pre>{prefix}{9}{UUID(random    )}</pre>\n     *\n     * @param prefix The prefix of the unique device id.\n     * @return the unique device id\n     */\n    public static String getUniqueDeviceId(String prefix) {\n        return getUniqueDeviceId(prefix, true);\n    }\n\n    /**\n     * Return the unique device id.\n     * <pre>{1}{UUID(macAddress)}</pre>\n     * <pre>{2}{UUID(androidId )}</pre>\n     * <pre>{9}{UUID(random    )}</pre>\n     *\n     * @param useCache True to use cache, false otherwise.\n     * @return the unique device id\n     */\n    public static String getUniqueDeviceId(boolean useCache) {\n        return getUniqueDeviceId(\"\", useCache);\n    }\n\n    /**\n     * Return the unique device id.\n     * <pre>android 10 deprecated {prefix}{1}{UUID(macAddress)}</pre>\n     * <pre>{prefix}{2}{UUID(androidId )}</pre>\n     * <pre>{prefix}{9}{UUID(random    )}</pre>\n     *\n     * @param prefix   The prefix of the unique device id.\n     * @param useCache True to use cache, false otherwise.\n     * @return the unique device id\n     */\n    public static String getUniqueDeviceId(String prefix, boolean useCache) {\n        if (!useCache) {\n            return getUniqueDeviceIdReal(prefix);\n        }\n        if (udid == null) {\n            synchronized (DeviceUtils.class) {\n                if (udid == null) {\n                    final String id = UtilsBridge.getSpUtils4Utils().getString(KEY_UDID, null);\n                    if (id != null) {\n                        udid = id;\n                        return udid;\n                    }\n                    return getUniqueDeviceIdReal(prefix);\n                }\n            }\n        }\n        return udid;\n    }\n\n    private static String getUniqueDeviceIdReal(String prefix) {\n        try {\n            final String androidId = getAndroidID();\n            if (!TextUtils.isEmpty(androidId)) {\n                return saveUdid(prefix + 2, androidId);\n            }\n\n        } catch (Exception ignore) {/**/}\n        return saveUdid(prefix + 9, \"\");\n    }\n\n    @RequiresPermission(allOf = {ACCESS_WIFI_STATE, INTERNET, CHANGE_WIFI_STATE})\n    public static boolean isSameDevice(final String uniqueDeviceId) {\n        // {prefix}{type}{32id}\n        if (TextUtils.isEmpty(uniqueDeviceId) && uniqueDeviceId.length() < 33) return false;\n        if (uniqueDeviceId.equals(udid)) return true;\n        final String cachedId = UtilsBridge.getSpUtils4Utils().getString(KEY_UDID, null);\n        if (uniqueDeviceId.equals(cachedId)) return true;\n        int st = uniqueDeviceId.length() - 33;\n        String type = uniqueDeviceId.substring(st, st + 1);\n        if (type.startsWith(\"1\")) {\n            String macAddress = getMacAddress();\n            if (macAddress.equals(\"\")) {\n                return false;\n            }\n            return uniqueDeviceId.substring(st + 1).equals(getUdid(\"\", macAddress));\n        } else if (type.startsWith(\"2\")) {\n            final String androidId = getAndroidID();\n            if (TextUtils.isEmpty(androidId)) {\n                return false;\n            }\n            return uniqueDeviceId.substring(st + 1).equals(getUdid(\"\", androidId));\n        }\n        return false;\n    }\n\n    private static String saveUdid(String prefix, String id) {\n        udid = getUdid(prefix, id);\n        UtilsBridge.getSpUtils4Utils().put(KEY_UDID, udid);\n        return udid;\n    }\n\n    private static String getUdid(String prefix, String id) {\n        if (id.equals(\"\")) {\n            return prefix + UUID.randomUUID().toString().replace(\"-\", \"\");\n        }\n        return prefix + UUID.nameUUIDFromBytes(id.getBytes()).toString().replace(\"-\", \"\");\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/DialogUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;//package com.didichuxing.doraemonkit.util;\n//\n//import android.app.Activity;\n//import android.app.Dialog;\n//import android.content.Context;\n//import android.content.ContextWrapper;\n//import android.graphics.drawable.ColorDrawable;\n//import android.os.Build;\n//import androidx.annotation.LayoutRes;\n//import androidx.annotation.Nullable;\n//import android.util.Log;\n//import android.view.LayoutInflater;\n//import android.view.View;\n//import android.view.Window;\n//\n//import java.util.HashMap;\n//import java.util.TreeSet;\n//\n///**\n// * <pre>\n// *     author: blankj\n// *     blog  : http://blankj.com\n// *     time  : 2019/08/26\n// *     desc  : utils about dialog\n// * </pre>\n// */\n//public class DialogUtils {\n//\n//    private DialogUtils() {\n//        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n//    }\n//\n//    public static void show(final Dialog dialog) {\n//        if (dialog == null) return;\n//        Utils.runOnUiThread(new Runnable() {\n//            @Override\n//            public void run() {\n//                Activity activity = getActivityByContext(dialog.getContext());\n//                if (!isActivityAlive(activity)) return;\n//                dialog.show();\n//            }\n//        });\n//    }\n//\n//    public static void dismiss(final Dialog dialog) {\n//        if (dialog == null) return;\n//        Utils.runOnUiThread(new Runnable() {\n//            @Override\n//            public void run() {\n//                dialog.dismiss();\n//            }\n//        });\n//    }\n//\n//    public static void show(final Utils.TransActivityDelegate delegate) {\n//        Utils.TransActivity.start(null, delegate);\n//    }\n//\n//    public static Dialog create(Activity activity, @LayoutRes int layoutId) {\n//        Dialog dialog = new Dialog(activity);\n//        View dialogContent = LayoutInflater.from(activity).inflate(layoutId, null);\n//\n//        dialog.setContentView(dialogContent);\n//        Window window = dialog.getWindow();\n//        if (window != null) {\n//            window.setBackgroundDrawable(new ColorDrawable(0));\n//        }\n//\n//        return dialog;\n//    }\n//\n//    private static boolean isActivityAlive(final Activity activity) {\n//        return activity != null && !activity.isFinishing()\n//                && (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 || !activity.isDestroyed());\n//    }\n//\n//    private static Activity getActivityByContext(Context context) {\n//        if (context instanceof Activity) return (Activity) context;\n//        while (context instanceof ContextWrapper) {\n//            if (context instanceof Activity) {\n//                return (Activity) context;\n//            }\n//            context = ((ContextWrapper) context).getBaseContext();\n//        }\n//        return null;\n//    }\n//\n//    public static final class UtilsDialog extends Dialog {\n//\n//        private int mPriority = 5;\n//\n//        public UtilsDialog(@NonNull Context context) {\n//            this(context, 0);\n//        }\n//\n//        public UtilsDialog(@NonNull Context context, int themeResId) {\n//            super(context, themeResId);\n//        }\n//\n//        @Override\n//        public void show() {\n//            Utils.runOnUiThread(new Runnable() {\n//                @Override\n//                public void run() {\n//                    Activity activity = getActivityByContext(getContext());\n//                    if (!isActivityAlive(activity)) {\n//                        Log.w(\"DialogUtils\", \"Activity is not alive.\");\n//                        return;\n//                    }\n//                    UtilsDialog.super.show();\n//                }\n//            });\n//        }\n//\n//        @Override\n//        public void dismiss() {\n//            Utils.runOnUiThread(new Runnable() {\n//                @Override\n//                public void run() {\n//                    UtilsDialog.super.dismiss();\n//                }\n//            });\n//        }\n//\n//        public void show(int priority) {\n//            mPriority = priority;\n//            show();\n//        }\n//    }\n//}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/EncodeUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.os.Build;\nimport android.text.Html;\nimport android.util.Base64;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLDecoder;\nimport java.net.URLEncoder;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/07\n *     desc  : utils about encode\n * </pre>\n */\npublic final class EncodeUtils {\n\n    private EncodeUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return the urlencoded string.\n     *\n     * @param input The input.\n     * @return the urlencoded string\n     */\n    public static String urlEncode(final String input) {\n        return urlEncode(input, \"UTF-8\");\n    }\n\n    /**\n     * Return the urlencoded string.\n     *\n     * @param input       The input.\n     * @param charsetName The name of charset.\n     * @return the urlencoded string\n     */\n    public static String urlEncode(final String input, final String charsetName) {\n        if (input == null || input.length() == 0) return \"\";\n        try {\n            return URLEncoder.encode(input, charsetName);\n        } catch (UnsupportedEncodingException e) {\n            throw new AssertionError(e);\n        }\n    }\n\n    /**\n     * Return the string of decode urlencoded string.\n     *\n     * @param input The input.\n     * @return the string of decode urlencoded string\n     */\n    public static String urlDecode(final String input) {\n        return urlDecode(input, \"UTF-8\");\n    }\n\n    /**\n     * Return the string of decode urlencoded string.\n     *\n     * @param input       The input.\n     * @param charsetName The name of charset.\n     * @return the string of decode urlencoded string\n     */\n    public static String urlDecode(final String input, final String charsetName) {\n        if (input == null || input.length() == 0) return \"\";\n        try {\n            String safeInput = input.replaceAll(\"%(?![0-9a-fA-F]{2})\", \"%25\").replaceAll(\"\\\\+\", \"%2B\");\n            return URLDecoder.decode(safeInput, charsetName);\n        } catch (UnsupportedEncodingException e) {\n            throw new AssertionError(e);\n        }\n    }\n\n    /**\n     * Return Base64-encode bytes.\n     *\n     * @param input The input.\n     * @return Base64-encode bytes\n     */\n    public static byte[] base64Encode(final String input) {\n        return base64Encode(input.getBytes());\n    }\n\n    /**\n     * Return Base64-encode bytes.\n     *\n     * @param input The input.\n     * @return Base64-encode bytes\n     */\n    public static byte[] base64Encode(final byte[] input) {\n        if (input == null || input.length == 0) return new byte[0];\n        return Base64.encode(input, Base64.NO_WRAP);\n    }\n\n    /**\n     * Return Base64-encode string.\n     *\n     * @param input The input.\n     * @return Base64-encode string\n     */\n    public static String base64Encode2String(final byte[] input) {\n        if (input == null || input.length == 0) return \"\";\n        return Base64.encodeToString(input, Base64.NO_WRAP);\n    }\n\n    /**\n     * Return the bytes of decode Base64-encode string.\n     *\n     * @param input The input.\n     * @return the string of decode Base64-encode string\n     */\n    public static byte[] base64Decode(final String input) {\n        if (input == null || input.length() == 0) return new byte[0];\n        return Base64.decode(input, Base64.NO_WRAP);\n    }\n\n    /**\n     * Return the bytes of decode Base64-encode bytes.\n     *\n     * @param input The input.\n     * @return the bytes of decode Base64-encode bytes\n     */\n    public static byte[] base64Decode(final byte[] input) {\n        if (input == null || input.length == 0) return new byte[0];\n        return Base64.decode(input, Base64.NO_WRAP);\n    }\n\n    /**\n     * Return html-encode string.\n     *\n     * @param input The input.\n     * @return html-encode string\n     */\n    public static String htmlEncode(final CharSequence input) {\n        if (input == null || input.length() == 0) return \"\";\n        StringBuilder sb = new StringBuilder();\n        char c;\n        for (int i = 0, len = input.length(); i < len; i++) {\n            c = input.charAt(i);\n            switch (c) {\n                case '<':\n                    sb.append(\"&lt;\"); //$NON-NLS-1$\n                    break;\n                case '>':\n                    sb.append(\"&gt;\"); //$NON-NLS-1$\n                    break;\n                case '&':\n                    sb.append(\"&amp;\"); //$NON-NLS-1$\n                    break;\n                case '\\'':\n                    //http://www.w3.org/TR/xhtml1\n                    // The named character reference &apos; (the apostrophe, U+0027) was\n                    // introduced in XML 1.0 but does not appear in HTML. Authors should\n                    // therefore use &#39; instead of &apos; to work as expected in HTML 4\n                    // user agents.\n                    sb.append(\"&#39;\"); //$NON-NLS-1$\n                    break;\n                case '\"':\n                    sb.append(\"&quot;\"); //$NON-NLS-1$\n                    break;\n                default:\n                    sb.append(c);\n            }\n        }\n        return sb.toString();\n    }\n\n    /**\n     * Return the string of decode html-encode string.\n     *\n     * @param input The input.\n     * @return the string of decode html-encode string\n     */\n    public static CharSequence htmlDecode(final String input) {\n        if (input == null || input.length() == 0) return \"\";\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            return Html.fromHtml(input, Html.FROM_HTML_MODE_LEGACY);\n        } else {\n            return Html.fromHtml(input);\n        }\n    }\n\n    /**\n     * Return the binary encoded string padded with one space\n     *\n     * @param input The input.\n     * @return binary string\n     */\n    public static String binaryEncode(final String input) {\n        if (input == null || input.length() == 0) return \"\";\n        StringBuilder sb = new StringBuilder();\n        for (char i : input.toCharArray()) {\n            sb.append(Integer.toBinaryString(i)).append(\" \");\n        }\n        return sb.deleteCharAt(sb.length() - 1).toString();\n    }\n\n    /**\n     * Return UTF-8 String from binary\n     *\n     * @param input binary string\n     * @return UTF-8 String\n     */\n    public static String binaryDecode(final String input) {\n        if (input == null || input.length() == 0) return \"\";\n        String[] splits = input.split(\" \");\n        StringBuilder sb = new StringBuilder();\n        for (String split : splits) {\n            sb.append(((char) Integer.parseInt(split, 2)));\n        }\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/EncryptUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.os.Build;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.security.DigestInputStream;\nimport java.security.InvalidKeyException;\nimport java.security.Key;\nimport java.security.KeyFactory;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.spec.AlgorithmParameterSpec;\nimport java.security.spec.PKCS8EncodedKeySpec;\nimport java.security.spec.X509EncodedKeySpec;\n\nimport javax.crypto.Cipher;\nimport javax.crypto.Mac;\nimport javax.crypto.SecretKey;\nimport javax.crypto.SecretKeyFactory;\nimport javax.crypto.spec.DESKeySpec;\nimport javax.crypto.spec.IvParameterSpec;\nimport javax.crypto.spec.SecretKeySpec;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/02\n *     desc  : utils about encrypt\n * </pre>\n */\npublic final class EncryptUtils {\n\n    private EncryptUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // hash encryption\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Return the hex string of MD2 encryption.\n     *\n     * @param data The data.\n     * @return the hex string of MD2 encryption\n     */\n    public static String encryptMD2ToString(final String data) {\n        if (data == null || data.length() == 0) return \"\";\n        return encryptMD2ToString(data.getBytes());\n    }\n\n    /**\n     * Return the hex string of MD2 encryption.\n     *\n     * @param data The data.\n     * @return the hex string of MD2 encryption\n     */\n    public static String encryptMD2ToString(final byte[] data) {\n        return UtilsBridge.bytes2HexString(encryptMD2(data));\n    }\n\n    /**\n     * Return the bytes of MD2 encryption.\n     *\n     * @param data The data.\n     * @return the bytes of MD2 encryption\n     */\n    public static byte[] encryptMD2(final byte[] data) {\n        return hashTemplate(data, \"MD2\");\n    }\n\n    /**\n     * Return the hex string of MD5 encryption.\n     *\n     * @param data The data.\n     * @return the hex string of MD5 encryption\n     */\n    public static String encryptMD5ToString(final String data) {\n        if (data == null || data.length() == 0) return \"\";\n        return encryptMD5ToString(data.getBytes());\n    }\n\n    /**\n     * Return the hex string of MD5 encryption.\n     *\n     * @param data The data.\n     * @param salt The salt.\n     * @return the hex string of MD5 encryption\n     */\n    public static String encryptMD5ToString(final String data, final String salt) {\n        if (data == null && salt == null) return \"\";\n        if (salt == null) return UtilsBridge.bytes2HexString(encryptMD5(data.getBytes()));\n        if (data == null) return UtilsBridge.bytes2HexString(encryptMD5(salt.getBytes()));\n        return UtilsBridge.bytes2HexString(encryptMD5((data + salt).getBytes()));\n    }\n\n    /**\n     * Return the hex string of MD5 encryption.\n     *\n     * @param data The data.\n     * @return the hex string of MD5 encryption\n     */\n    public static String encryptMD5ToString(final byte[] data) {\n        return UtilsBridge.bytes2HexString(encryptMD5(data));\n    }\n\n    /**\n     * Return the hex string of MD5 encryption.\n     *\n     * @param data The data.\n     * @param salt The salt.\n     * @return the hex string of MD5 encryption\n     */\n    public static String encryptMD5ToString(final byte[] data, final byte[] salt) {\n        if (data == null && salt == null) return \"\";\n        if (salt == null) return UtilsBridge.bytes2HexString(encryptMD5(data));\n        if (data == null) return UtilsBridge.bytes2HexString(encryptMD5(salt));\n        byte[] dataSalt = new byte[data.length + salt.length];\n        System.arraycopy(data, 0, dataSalt, 0, data.length);\n        System.arraycopy(salt, 0, dataSalt, data.length, salt.length);\n        return UtilsBridge.bytes2HexString(encryptMD5(dataSalt));\n    }\n\n    /**\n     * Return the bytes of MD5 encryption.\n     *\n     * @param data The data.\n     * @return the bytes of MD5 encryption\n     */\n    public static byte[] encryptMD5(final byte[] data) {\n        return hashTemplate(data, \"MD5\");\n    }\n\n    /**\n     * Return the hex string of file's MD5 encryption.\n     *\n     * @param filePath The path of file.\n     * @return the hex string of file's MD5 encryption\n     */\n    public static String encryptMD5File2String(final String filePath) {\n        File file = UtilsBridge.isSpace(filePath) ? null : new File(filePath);\n        return encryptMD5File2String(file);\n    }\n\n    /**\n     * Return the bytes of file's MD5 encryption.\n     *\n     * @param filePath The path of file.\n     * @return the bytes of file's MD5 encryption\n     */\n    public static byte[] encryptMD5File(final String filePath) {\n        File file = UtilsBridge.isSpace(filePath) ? null : new File(filePath);\n        return encryptMD5File(file);\n    }\n\n    /**\n     * Return the hex string of file's MD5 encryption.\n     *\n     * @param file The file.\n     * @return the hex string of file's MD5 encryption\n     */\n    public static String encryptMD5File2String(final File file) {\n        return UtilsBridge.bytes2HexString(encryptMD5File(file));\n    }\n\n    /**\n     * Return the bytes of file's MD5 encryption.\n     *\n     * @param file The file.\n     * @return the bytes of file's MD5 encryption\n     */\n    public static byte[] encryptMD5File(final File file) {\n        if (file == null) return null;\n        FileInputStream fis = null;\n        DigestInputStream digestInputStream;\n        try {\n            fis = new FileInputStream(file);\n            MessageDigest md = MessageDigest.getInstance(\"MD5\");\n            digestInputStream = new DigestInputStream(fis, md);\n            byte[] buffer = new byte[256 * 1024];\n            while (true) {\n                if (!(digestInputStream.read(buffer) > 0)) break;\n            }\n            md = digestInputStream.getMessageDigest();\n            return md.digest();\n        } catch (NoSuchAlgorithmException | IOException e) {\n            e.printStackTrace();\n            return null;\n        } finally {\n            try {\n                if (fis != null) {\n                    fis.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Return the hex string of SHA1 encryption.\n     *\n     * @param data The data.\n     * @return the hex string of SHA1 encryption\n     */\n    public static String encryptSHA1ToString(final String data) {\n        if (data == null || data.length() == 0) return \"\";\n        return encryptSHA1ToString(data.getBytes());\n    }\n\n    /**\n     * Return the hex string of SHA1 encryption.\n     *\n     * @param data The data.\n     * @return the hex string of SHA1 encryption\n     */\n    public static String encryptSHA1ToString(final byte[] data) {\n        return UtilsBridge.bytes2HexString(encryptSHA1(data));\n    }\n\n    /**\n     * Return the bytes of SHA1 encryption.\n     *\n     * @param data The data.\n     * @return the bytes of SHA1 encryption\n     */\n    public static byte[] encryptSHA1(final byte[] data) {\n        return hashTemplate(data, \"SHA-1\");\n    }\n\n    /**\n     * Return the hex string of SHA224 encryption.\n     *\n     * @param data The data.\n     * @return the hex string of SHA224 encryption\n     */\n    public static String encryptSHA224ToString(final String data) {\n        if (data == null || data.length() == 0) return \"\";\n        return encryptSHA224ToString(data.getBytes());\n    }\n\n    /**\n     * Return the hex string of SHA224 encryption.\n     *\n     * @param data The data.\n     * @return the hex string of SHA224 encryption\n     */\n    public static String encryptSHA224ToString(final byte[] data) {\n        return UtilsBridge.bytes2HexString(encryptSHA224(data));\n    }\n\n    /**\n     * Return the bytes of SHA224 encryption.\n     *\n     * @param data The data.\n     * @return the bytes of SHA224 encryption\n     */\n    public static byte[] encryptSHA224(final byte[] data) {\n        return hashTemplate(data, \"SHA224\");\n    }\n\n    /**\n     * Return the hex string of SHA256 encryption.\n     *\n     * @param data The data.\n     * @return the hex string of SHA256 encryption\n     */\n    public static String encryptSHA256ToString(final String data) {\n        if (data == null || data.length() == 0) return \"\";\n        return encryptSHA256ToString(data.getBytes());\n    }\n\n    /**\n     * Return the hex string of SHA256 encryption.\n     *\n     * @param data The data.\n     * @return the hex string of SHA256 encryption\n     */\n    public static String encryptSHA256ToString(final byte[] data) {\n        return UtilsBridge.bytes2HexString(encryptSHA256(data));\n    }\n\n    /**\n     * Return the bytes of SHA256 encryption.\n     *\n     * @param data The data.\n     * @return the bytes of SHA256 encryption\n     */\n    public static byte[] encryptSHA256(final byte[] data) {\n        return hashTemplate(data, \"SHA-256\");\n    }\n\n    /**\n     * Return the hex string of SHA384 encryption.\n     *\n     * @param data The data.\n     * @return the hex string of SHA384 encryption\n     */\n    public static String encryptSHA384ToString(final String data) {\n        if (data == null || data.length() == 0) return \"\";\n        return encryptSHA384ToString(data.getBytes());\n    }\n\n    /**\n     * Return the hex string of SHA384 encryption.\n     *\n     * @param data The data.\n     * @return the hex string of SHA384 encryption\n     */\n    public static String encryptSHA384ToString(final byte[] data) {\n        return UtilsBridge.bytes2HexString(encryptSHA384(data));\n    }\n\n    /**\n     * Return the bytes of SHA384 encryption.\n     *\n     * @param data The data.\n     * @return the bytes of SHA384 encryption\n     */\n    public static byte[] encryptSHA384(final byte[] data) {\n        return hashTemplate(data, \"SHA-384\");\n    }\n\n    /**\n     * Return the hex string of SHA512 encryption.\n     *\n     * @param data The data.\n     * @return the hex string of SHA512 encryption\n     */\n    public static String encryptSHA512ToString(final String data) {\n        if (data == null || data.length() == 0) return \"\";\n        return encryptSHA512ToString(data.getBytes());\n    }\n\n    /**\n     * Return the hex string of SHA512 encryption.\n     *\n     * @param data The data.\n     * @return the hex string of SHA512 encryption\n     */\n    public static String encryptSHA512ToString(final byte[] data) {\n        return UtilsBridge.bytes2HexString(encryptSHA512(data));\n    }\n\n    /**\n     * Return the bytes of SHA512 encryption.\n     *\n     * @param data The data.\n     * @return the bytes of SHA512 encryption\n     */\n    public static byte[] encryptSHA512(final byte[] data) {\n        return hashTemplate(data, \"SHA-512\");\n    }\n\n    /**\n     * Return the bytes of hash encryption.\n     *\n     * @param data      The data.\n     * @param algorithm The name of hash encryption.\n     * @return the bytes of hash encryption\n     */\n    static byte[] hashTemplate(final byte[] data, final String algorithm) {\n        if (data == null || data.length <= 0) return null;\n        try {\n            MessageDigest md = MessageDigest.getInstance(algorithm);\n            md.update(data);\n            return md.digest();\n        } catch (NoSuchAlgorithmException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // hmac encryption\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Return the hex string of HmacMD5 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the hex string of HmacMD5 encryption\n     */\n    public static String encryptHmacMD5ToString(final String data, final String key) {\n        if (data == null || data.length() == 0 || key == null || key.length() == 0) return \"\";\n        return encryptHmacMD5ToString(data.getBytes(), key.getBytes());\n    }\n\n    /**\n     * Return the hex string of HmacMD5 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the hex string of HmacMD5 encryption\n     */\n    public static String encryptHmacMD5ToString(final byte[] data, final byte[] key) {\n        return UtilsBridge.bytes2HexString(encryptHmacMD5(data, key));\n    }\n\n    /**\n     * Return the bytes of HmacMD5 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the bytes of HmacMD5 encryption\n     */\n    public static byte[] encryptHmacMD5(final byte[] data, final byte[] key) {\n        return hmacTemplate(data, key, \"HmacMD5\");\n    }\n\n    /**\n     * Return the hex string of HmacSHA1 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the hex string of HmacSHA1 encryption\n     */\n    public static String encryptHmacSHA1ToString(final String data, final String key) {\n        if (data == null || data.length() == 0 || key == null || key.length() == 0) return \"\";\n        return encryptHmacSHA1ToString(data.getBytes(), key.getBytes());\n    }\n\n    /**\n     * Return the hex string of HmacSHA1 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the hex string of HmacSHA1 encryption\n     */\n    public static String encryptHmacSHA1ToString(final byte[] data, final byte[] key) {\n        return UtilsBridge.bytes2HexString(encryptHmacSHA1(data, key));\n    }\n\n    /**\n     * Return the bytes of HmacSHA1 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the bytes of HmacSHA1 encryption\n     */\n    public static byte[] encryptHmacSHA1(final byte[] data, final byte[] key) {\n        return hmacTemplate(data, key, \"HmacSHA1\");\n    }\n\n    /**\n     * Return the hex string of HmacSHA224 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the hex string of HmacSHA224 encryption\n     */\n    public static String encryptHmacSHA224ToString(final String data, final String key) {\n        if (data == null || data.length() == 0 || key == null || key.length() == 0) return \"\";\n        return encryptHmacSHA224ToString(data.getBytes(), key.getBytes());\n    }\n\n    /**\n     * Return the hex string of HmacSHA224 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the hex string of HmacSHA224 encryption\n     */\n    public static String encryptHmacSHA224ToString(final byte[] data, final byte[] key) {\n        return UtilsBridge.bytes2HexString(encryptHmacSHA224(data, key));\n    }\n\n    /**\n     * Return the bytes of HmacSHA224 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the bytes of HmacSHA224 encryption\n     */\n    public static byte[] encryptHmacSHA224(final byte[] data, final byte[] key) {\n        return hmacTemplate(data, key, \"HmacSHA224\");\n    }\n\n    /**\n     * Return the hex string of HmacSHA256 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the hex string of HmacSHA256 encryption\n     */\n    public static String encryptHmacSHA256ToString(final String data, final String key) {\n        if (data == null || data.length() == 0 || key == null || key.length() == 0) return \"\";\n        return encryptHmacSHA256ToString(data.getBytes(), key.getBytes());\n    }\n\n    /**\n     * Return the hex string of HmacSHA256 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the hex string of HmacSHA256 encryption\n     */\n    public static String encryptHmacSHA256ToString(final byte[] data, final byte[] key) {\n        return UtilsBridge.bytes2HexString(encryptHmacSHA256(data, key));\n    }\n\n    /**\n     * Return the bytes of HmacSHA256 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the bytes of HmacSHA256 encryption\n     */\n    public static byte[] encryptHmacSHA256(final byte[] data, final byte[] key) {\n        return hmacTemplate(data, key, \"HmacSHA256\");\n    }\n\n    /**\n     * Return the hex string of HmacSHA384 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the hex string of HmacSHA384 encryption\n     */\n    public static String encryptHmacSHA384ToString(final String data, final String key) {\n        if (data == null || data.length() == 0 || key == null || key.length() == 0) return \"\";\n        return encryptHmacSHA384ToString(data.getBytes(), key.getBytes());\n    }\n\n    /**\n     * Return the hex string of HmacSHA384 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the hex string of HmacSHA384 encryption\n     */\n    public static String encryptHmacSHA384ToString(final byte[] data, final byte[] key) {\n        return UtilsBridge.bytes2HexString(encryptHmacSHA384(data, key));\n    }\n\n    /**\n     * Return the bytes of HmacSHA384 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the bytes of HmacSHA384 encryption\n     */\n    public static byte[] encryptHmacSHA384(final byte[] data, final byte[] key) {\n        return hmacTemplate(data, key, \"HmacSHA384\");\n    }\n\n    /**\n     * Return the hex string of HmacSHA512 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the hex string of HmacSHA512 encryption\n     */\n    public static String encryptHmacSHA512ToString(final String data, final String key) {\n        if (data == null || data.length() == 0 || key == null || key.length() == 0) return \"\";\n        return encryptHmacSHA512ToString(data.getBytes(), key.getBytes());\n    }\n\n    /**\n     * Return the hex string of HmacSHA512 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the hex string of HmacSHA512 encryption\n     */\n    public static String encryptHmacSHA512ToString(final byte[] data, final byte[] key) {\n        return UtilsBridge.bytes2HexString(encryptHmacSHA512(data, key));\n    }\n\n    /**\n     * Return the bytes of HmacSHA512 encryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     * @return the bytes of HmacSHA512 encryption\n     */\n    public static byte[] encryptHmacSHA512(final byte[] data, final byte[] key) {\n        return hmacTemplate(data, key, \"HmacSHA512\");\n    }\n\n    /**\n     * Return the bytes of hmac encryption.\n     *\n     * @param data      The data.\n     * @param key       The key.\n     * @param algorithm The name of hmac encryption.\n     * @return the bytes of hmac encryption\n     */\n    private static byte[] hmacTemplate(final byte[] data,\n                                       final byte[] key,\n                                       final String algorithm) {\n        if (data == null || data.length == 0 || key == null || key.length == 0) return null;\n        try {\n            SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);\n            Mac mac = Mac.getInstance(algorithm);\n            mac.init(secretKey);\n            return mac.doFinal(data);\n        } catch (InvalidKeyException | NoSuchAlgorithmException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // DES encryption\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Return the Base64-encode bytes of DES encryption.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the Base64-encode bytes of DES encryption\n     */\n    public static byte[] encryptDES2Base64(final byte[] data,\n                                           final byte[] key,\n                                           final String transformation,\n                                           final byte[] iv) {\n        return UtilsBridge.base64Encode(encryptDES(data, key, transformation, iv));\n    }\n\n    /**\n     * Return the hex string of DES encryption.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the hex string of DES encryption\n     */\n    public static String encryptDES2HexString(final byte[] data,\n                                              final byte[] key,\n                                              final String transformation,\n                                              final byte[] iv) {\n        return UtilsBridge.bytes2HexString(encryptDES(data, key, transformation, iv));\n    }\n\n    /**\n     * Return the bytes of DES encryption.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the bytes of DES encryption\n     */\n    public static byte[] encryptDES(final byte[] data,\n                                    final byte[] key,\n                                    final String transformation,\n                                    final byte[] iv) {\n        return symmetricTemplate(data, key, \"DES\", transformation, iv, true);\n    }\n\n    /**\n     * Return the bytes of DES decryption for Base64-encode bytes.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the bytes of DES decryption for Base64-encode bytes\n     */\n    public static byte[] decryptBase64DES(final byte[] data,\n                                          final byte[] key,\n                                          final String transformation,\n                                          final byte[] iv) {\n        return decryptDES(UtilsBridge.base64Decode(data), key, transformation, iv);\n    }\n\n    /**\n     * Return the bytes of DES decryption for hex string.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the bytes of DES decryption for hex string\n     */\n    public static byte[] decryptHexStringDES(final String data,\n                                             final byte[] key,\n                                             final String transformation,\n                                             final byte[] iv) {\n        return decryptDES(UtilsBridge.hexString2Bytes(data), key, transformation, iv);\n    }\n\n    /**\n     * Return the bytes of DES decryption.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the bytes of DES decryption\n     */\n    public static byte[] decryptDES(final byte[] data,\n                                    final byte[] key,\n                                    final String transformation,\n                                    final byte[] iv) {\n        return symmetricTemplate(data, key, \"DES\", transformation, iv, false);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // 3DES encryption\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Return the Base64-encode bytes of 3DES encryption.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the Base64-encode bytes of 3DES encryption\n     */\n    public static byte[] encrypt3DES2Base64(final byte[] data,\n                                            final byte[] key,\n                                            final String transformation,\n                                            final byte[] iv) {\n        return UtilsBridge.base64Encode(encrypt3DES(data, key, transformation, iv));\n    }\n\n    /**\n     * Return the hex string of 3DES encryption.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the hex string of 3DES encryption\n     */\n    public static String encrypt3DES2HexString(final byte[] data,\n                                               final byte[] key,\n                                               final String transformation,\n                                               final byte[] iv) {\n        return UtilsBridge.bytes2HexString(encrypt3DES(data, key, transformation, iv));\n    }\n\n    /**\n     * Return the bytes of 3DES encryption.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the bytes of 3DES encryption\n     */\n    public static byte[] encrypt3DES(final byte[] data,\n                                     final byte[] key,\n                                     final String transformation,\n                                     final byte[] iv) {\n        return symmetricTemplate(data, key, \"DESede\", transformation, iv, true);\n    }\n\n    /**\n     * Return the bytes of 3DES decryption for Base64-encode bytes.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the bytes of 3DES decryption for Base64-encode bytes\n     */\n    public static byte[] decryptBase64_3DES(final byte[] data,\n                                            final byte[] key,\n                                            final String transformation,\n                                            final byte[] iv) {\n        return decrypt3DES(UtilsBridge.base64Decode(data), key, transformation, iv);\n    }\n\n    /**\n     * Return the bytes of 3DES decryption for hex string.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the bytes of 3DES decryption for hex string\n     */\n    public static byte[] decryptHexString3DES(final String data,\n                                              final byte[] key,\n                                              final String transformation,\n                                              final byte[] iv) {\n        return decrypt3DES(UtilsBridge.hexString2Bytes(data), key, transformation, iv);\n    }\n\n    /**\n     * Return the bytes of 3DES decryption.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the bytes of 3DES decryption\n     */\n    public static byte[] decrypt3DES(final byte[] data,\n                                     final byte[] key,\n                                     final String transformation,\n                                     final byte[] iv) {\n        return symmetricTemplate(data, key, \"DESede\", transformation, iv, false);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // AES encryption\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Return the Base64-encode bytes of AES encryption.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the Base64-encode bytes of AES encryption\n     */\n    public static byte[] encryptAES2Base64(final byte[] data,\n                                           final byte[] key,\n                                           final String transformation,\n                                           final byte[] iv) {\n        return UtilsBridge.base64Encode(encryptAES(data, key, transformation, iv));\n    }\n\n    /**\n     * Return the hex string of AES encryption.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the hex string of AES encryption\n     */\n    public static String encryptAES2HexString(final byte[] data,\n                                              final byte[] key,\n                                              final String transformation,\n                                              final byte[] iv) {\n        return UtilsBridge.bytes2HexString(encryptAES(data, key, transformation, iv));\n    }\n\n    /**\n     * Return the bytes of AES encryption.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the bytes of AES encryption\n     */\n    public static byte[] encryptAES(final byte[] data,\n                                    final byte[] key,\n                                    final String transformation,\n                                    final byte[] iv) {\n        return symmetricTemplate(data, key, \"AES\", transformation, iv, true);\n    }\n\n    /**\n     * Return the bytes of AES decryption for Base64-encode bytes.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the bytes of AES decryption for Base64-encode bytes\n     */\n    public static byte[] decryptBase64AES(final byte[] data,\n                                          final byte[] key,\n                                          final String transformation,\n                                          final byte[] iv) {\n        return decryptAES(UtilsBridge.base64Decode(data), key, transformation, iv);\n    }\n\n    /**\n     * Return the bytes of AES decryption for hex string.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the bytes of AES decryption for hex string\n     */\n    public static byte[] decryptHexStringAES(final String data,\n                                             final byte[] key,\n                                             final String transformation,\n                                             final byte[] iv) {\n        return decryptAES(UtilsBridge.hexString2Bytes(data), key, transformation, iv);\n    }\n\n    /**\n     * Return the bytes of AES decryption.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param iv             The buffer with the IV. The contents of the\n     *                       buffer are copied to protect against subsequent modification.\n     * @return the bytes of AES decryption\n     */\n    public static byte[] decryptAES(final byte[] data,\n                                    final byte[] key,\n                                    final String transformation,\n                                    final byte[] iv) {\n        return symmetricTemplate(data, key, \"AES\", transformation, iv, false);\n    }\n\n    /**\n     * Return the bytes of symmetric encryption or decryption.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param algorithm      The name of algorithm.\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS5Padding</i>.\n     * @param isEncrypt      True to encrypt, false otherwise.\n     * @return the bytes of symmetric encryption or decryption\n     */\n    private static byte[] symmetricTemplate(final byte[] data,\n                                            final byte[] key,\n                                            final String algorithm,\n                                            final String transformation,\n                                            final byte[] iv,\n                                            final boolean isEncrypt) {\n        if (data == null || data.length == 0 || key == null || key.length == 0) return null;\n        try {\n            SecretKey secretKey;\n            if (\"DES\".equals(algorithm)) {\n                DESKeySpec desKey = new DESKeySpec(key);\n                SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);\n                secretKey = keyFactory.generateSecret(desKey);\n            } else {\n                secretKey = new SecretKeySpec(key, algorithm);\n            }\n            Cipher cipher = Cipher.getInstance(transformation);\n            if (iv == null || iv.length == 0) {\n                cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secretKey);\n            } else {\n                AlgorithmParameterSpec params = new IvParameterSpec(iv);\n                cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secretKey, params);\n            }\n            return cipher.doFinal(data);\n        } catch (Throwable e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // RSA encryption\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Return the Base64-encode bytes of RSA encryption.\n     *\n     * @param data           The data.\n     * @param publicKey      The public key.\n     * @param keySize        The size of key, e.g. 1024, 2048...\n     * @param transformation The name of the transformation, e.g., <i>RSA/CBC/PKCS1Padding</i>.\n     * @return the Base64-encode bytes of RSA encryption\n     */\n    public static byte[] encryptRSA2Base64(final byte[] data,\n                                           final byte[] publicKey,\n                                           final int keySize,\n                                           final String transformation) {\n        return UtilsBridge.base64Encode(encryptRSA(data, publicKey, keySize, transformation));\n    }\n\n    /**\n     * Return the hex string of RSA encryption.\n     *\n     * @param data           The data.\n     * @param publicKey      The public key.\n     * @param keySize        The size of key, e.g. 1024, 2048...\n     * @param transformation The name of the transformation, e.g., <i>RSA/CBC/PKCS1Padding</i>.\n     * @return the hex string of RSA encryption\n     */\n    public static String encryptRSA2HexString(final byte[] data,\n                                              final byte[] publicKey,\n                                              final int keySize,\n                                              final String transformation) {\n        return UtilsBridge.bytes2HexString(encryptRSA(data, publicKey, keySize, transformation));\n    }\n\n    /**\n     * Return the bytes of RSA encryption.\n     *\n     * @param data           The data.\n     * @param publicKey      The public key.\n     * @param keySize        The size of key, e.g. 1024, 2048...\n     * @param transformation The name of the transformation, e.g., <i>RSA/CBC/PKCS1Padding</i>.\n     * @return the bytes of RSA encryption\n     */\n    public static byte[] encryptRSA(final byte[] data,\n                                    final byte[] publicKey,\n                                    final int keySize,\n                                    final String transformation) {\n        return rsaTemplate(data, publicKey, keySize, transformation, true);\n    }\n\n    /**\n     * Return the bytes of RSA decryption for Base64-encode bytes.\n     *\n     * @param data           The data.\n     * @param privateKey     The private key.\n     * @param keySize        The size of key, e.g. 1024, 2048...\n     * @param transformation The name of the transformation, e.g., <i>RSA/CBC/PKCS1Padding</i>.\n     * @return the bytes of RSA decryption for Base64-encode bytes\n     */\n    public static byte[] decryptBase64RSA(final byte[] data,\n                                          final byte[] privateKey,\n                                          final int keySize,\n                                          final String transformation) {\n        return decryptRSA(UtilsBridge.base64Decode(data), privateKey, keySize, transformation);\n    }\n\n    /**\n     * Return the bytes of RSA decryption for hex string.\n     *\n     * @param data           The data.\n     * @param privateKey     The private key.\n     * @param keySize        The size of key, e.g. 1024, 2048...\n     * @param transformation The name of the transformation, e.g., <i>RSA/CBC/PKCS1Padding</i>.\n     * @return the bytes of RSA decryption for hex string\n     */\n    public static byte[] decryptHexStringRSA(final String data,\n                                             final byte[] privateKey,\n                                             final int keySize,\n                                             final String transformation) {\n        return decryptRSA(UtilsBridge.hexString2Bytes(data), privateKey, keySize, transformation);\n    }\n\n    /**\n     * Return the bytes of RSA decryption.\n     *\n     * @param data           The data.\n     * @param privateKey     The private key.\n     * @param keySize        The size of key, e.g. 1024, 2048...\n     * @param transformation The name of the transformation, e.g., <i>RSA/CBC/PKCS1Padding</i>.\n     * @return the bytes of RSA decryption\n     */\n    public static byte[] decryptRSA(final byte[] data,\n                                    final byte[] privateKey,\n                                    final int keySize,\n                                    final String transformation) {\n        return rsaTemplate(data, privateKey, keySize, transformation, false);\n    }\n\n    /**\n     * Return the bytes of RSA encryption or decryption.\n     *\n     * @param data           The data.\n     * @param key            The key.\n     * @param keySize        The size of key, e.g. 1024, 2048...\n     * @param transformation The name of the transformation, e.g., <i>DES/CBC/PKCS1Padding</i>.\n     * @param isEncrypt      True to encrypt, false otherwise.\n     * @return the bytes of RSA encryption or decryption\n     */\n    private static byte[] rsaTemplate(final byte[] data,\n                                      final byte[] key,\n                                      final int keySize,\n                                      final String transformation,\n                                      final boolean isEncrypt) {\n        if (data == null || data.length == 0 || key == null || key.length == 0) {\n            return null;\n        }\n        try {\n            Key rsaKey;\n            KeyFactory keyFactory;\n            if (Build.VERSION.SDK_INT < 28) {\n                keyFactory = KeyFactory.getInstance(\"RSA\", \"BC\");\n            } else {\n                keyFactory = KeyFactory.getInstance(\"RSA\");\n            }\n            if (isEncrypt) {\n                X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key);\n                rsaKey = keyFactory.generatePublic(keySpec);\n            } else {\n                PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key);\n                rsaKey = keyFactory.generatePrivate(keySpec);\n            }\n            if (rsaKey == null) return null;\n            Cipher cipher = Cipher.getInstance(transformation);\n            cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, rsaKey);\n            int len = data.length;\n            int maxLen = keySize / 8;\n            if (isEncrypt) {\n                String lowerTrans = transformation.toLowerCase();\n                if (lowerTrans.endsWith(\"pkcs1padding\")) {\n                    maxLen -= 11;\n                }\n            }\n            int count = len / maxLen;\n            if (count > 0) {\n                byte[] ret = new byte[0];\n                byte[] buff = new byte[maxLen];\n                int index = 0;\n                for (int i = 0; i < count; i++) {\n                    System.arraycopy(data, index, buff, 0, maxLen);\n                    ret = joins(ret, cipher.doFinal(buff));\n                    index += maxLen;\n                }\n                if (index != len) {\n                    int restLen = len - index;\n                    buff = new byte[restLen];\n                    System.arraycopy(data, index, buff, 0, restLen);\n                    ret = joins(ret, cipher.doFinal(buff));\n                }\n                return ret;\n            } else {\n                return cipher.doFinal(data);\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    /**\n     * Return the bytes of RC4 encryption/decryption.\n     *\n     * @param data The data.\n     * @param key  The key.\n     */\n    public static byte[] rc4(byte[] data, byte[] key) {\n        if (data == null || data.length == 0 || key == null) return null;\n        if (key.length < 1 || key.length > 256) {\n            throw new IllegalArgumentException(\"key must be between 1 and 256 bytes\");\n        }\n        final byte[] iS = new byte[256];\n        final byte[] iK = new byte[256];\n        int keyLen = key.length;\n        for (int i = 0; i < 256; i++) {\n            iS[i] = (byte) i;\n            iK[i] = key[i % keyLen];\n        }\n        int j = 0;\n        byte tmp;\n        for (int i = 0; i < 256; i++) {\n            j = (j + iS[i] + iK[i]) & 0xFF;\n            tmp = iS[j];\n            iS[j] = iS[i];\n            iS[i] = tmp;\n        }\n\n        final byte[] ret = new byte[data.length];\n        int i = 0, k, t;\n        for (int counter = 0; counter < data.length; counter++) {\n            i = (i + 1) & 0xFF;\n            j = (j + iS[i]) & 0xFF;\n            tmp = iS[j];\n            iS[j] = iS[i];\n            iS[i] = tmp;\n            t = (iS[i] + iS[j]) & 0xFF;\n            k = iS[t];\n            ret[counter] = (byte) (data[counter] ^ k);\n        }\n        return ret;\n    }\n\n    private static byte[] joins(final byte[] prefix, final byte[] suffix) {\n        byte[] ret = new byte[prefix.length + suffix.length];\n        System.arraycopy(prefix, 0, ret, 0, prefix.length);\n        System.arraycopy(suffix, 0, ret, prefix.length, suffix.length);\n        return ret;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/FileIOUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.util.Log;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.io.RandomAccessFile;\nimport java.io.UnsupportedEncodingException;\nimport java.nio.ByteBuffer;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2017/06/22\n *     desc  : utils about file io\n * </pre>\n */\npublic final class FileIOUtils {\n\n    private static int sBufferSize = 524288;\n\n    private FileIOUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // writeFileFromIS without progress\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Write file from input stream.\n     *\n     * @param filePath The path of file.\n     * @param is       The input stream.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromIS(final String filePath, final InputStream is) {\n        return writeFileFromIS(UtilsBridge.getFileByPath(filePath), is, false, null);\n    }\n\n    /**\n     * Write file from input stream.\n     *\n     * @param filePath The path of file.\n     * @param is       The input stream.\n     * @param append   True to append, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromIS(final String filePath,\n                                          final InputStream is,\n                                          final boolean append) {\n        return writeFileFromIS(UtilsBridge.getFileByPath(filePath), is, append, null);\n    }\n\n    /**\n     * Write file from input stream.\n     *\n     * @param file The file.\n     * @param is   The input stream.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromIS(final File file, final InputStream is) {\n        return writeFileFromIS(file, is, false, null);\n    }\n\n    /**\n     * Write file from input stream.\n     *\n     * @param file   The file.\n     * @param is     The input stream.\n     * @param append True to append, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromIS(final File file,\n                                          final InputStream is,\n                                          final boolean append) {\n        return writeFileFromIS(file, is, append, null);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // writeFileFromIS with progress\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Write file from input stream.\n     *\n     * @param filePath The path of file.\n     * @param is       The input stream.\n     * @param listener The progress update listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromIS(final String filePath,\n                                          final InputStream is,\n                                          final OnProgressUpdateListener listener) {\n        return writeFileFromIS(UtilsBridge.getFileByPath(filePath), is, false, listener);\n    }\n\n    /**\n     * Write file from input stream.\n     *\n     * @param filePath The path of file.\n     * @param is       The input stream.\n     * @param append   True to append, false otherwise.\n     * @param listener The progress update listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromIS(final String filePath,\n                                          final InputStream is,\n                                          final boolean append,\n                                          final OnProgressUpdateListener listener) {\n        return writeFileFromIS(UtilsBridge.getFileByPath(filePath), is, append, listener);\n    }\n\n    /**\n     * Write file from input stream.\n     *\n     * @param file     The file.\n     * @param is       The input stream.\n     * @param listener The progress update listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromIS(final File file,\n                                          final InputStream is,\n                                          final OnProgressUpdateListener listener) {\n        return writeFileFromIS(file, is, false, listener);\n    }\n\n    /**\n     * Write file from input stream.\n     *\n     * @param file     The file.\n     * @param is       The input stream.\n     * @param append   True to append, false otherwise.\n     * @param listener The progress update listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromIS(final File file,\n                                          final InputStream is,\n                                          final boolean append,\n                                          final OnProgressUpdateListener listener) {\n        if (is == null || !UtilsBridge.createOrExistsFile(file)) {\n            Log.e(\"FileIOUtils\", \"create file <\" + file + \"> failed.\");\n            return false;\n        }\n        OutputStream os = null;\n        try {\n            os = new BufferedOutputStream(new FileOutputStream(file, append), sBufferSize);\n            if (listener == null) {\n                byte[] data = new byte[sBufferSize];\n                for (int len; (len = is.read(data)) != -1; ) {\n                    os.write(data, 0, len);\n                }\n            } else {\n                double totalSize = is.available();\n                int curSize = 0;\n                listener.onProgressUpdate(0);\n                byte[] data = new byte[sBufferSize];\n                for (int len; (len = is.read(data)) != -1; ) {\n                    os.write(data, 0, len);\n                    curSize += len;\n                    listener.onProgressUpdate(curSize / totalSize);\n                }\n            }\n            return true;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        } finally {\n            try {\n                is.close();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n            try {\n                if (os != null) {\n                    os.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // writeFileFromBytesByStream without progress\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Write file from bytes by stream.\n     *\n     * @param filePath The path of file.\n     * @param bytes    The bytes.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByStream(final String filePath, final byte[] bytes) {\n        return writeFileFromBytesByStream(UtilsBridge.getFileByPath(filePath), bytes, false, null);\n    }\n\n    /**\n     * Write file from bytes by stream.\n     *\n     * @param filePath The path of file.\n     * @param bytes    The bytes.\n     * @param append   True to append, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByStream(final String filePath,\n                                                     final byte[] bytes,\n                                                     final boolean append) {\n        return writeFileFromBytesByStream(UtilsBridge.getFileByPath(filePath), bytes, append, null);\n    }\n\n    /**\n     * Write file from bytes by stream.\n     *\n     * @param file  The file.\n     * @param bytes The bytes.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByStream(final File file, final byte[] bytes) {\n        return writeFileFromBytesByStream(file, bytes, false, null);\n    }\n\n    /**\n     * Write file from bytes by stream.\n     *\n     * @param file   The file.\n     * @param bytes  The bytes.\n     * @param append True to append, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByStream(final File file,\n                                                     final byte[] bytes,\n                                                     final boolean append) {\n        return writeFileFromBytesByStream(file, bytes, append, null);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // writeFileFromBytesByStream with progress\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Write file from bytes by stream.\n     *\n     * @param filePath The path of file.\n     * @param bytes    The bytes.\n     * @param listener The progress update listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByStream(final String filePath,\n                                                     final byte[] bytes,\n                                                     final OnProgressUpdateListener listener) {\n        return writeFileFromBytesByStream(UtilsBridge.getFileByPath(filePath), bytes, false, listener);\n    }\n\n    /**\n     * Write file from bytes by stream.\n     *\n     * @param filePath The path of file.\n     * @param bytes    The bytes.\n     * @param append   True to append, false otherwise.\n     * @param listener The progress update listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByStream(final String filePath,\n                                                     final byte[] bytes,\n                                                     final boolean append,\n                                                     final OnProgressUpdateListener listener) {\n        return writeFileFromBytesByStream(UtilsBridge.getFileByPath(filePath), bytes, append, listener);\n    }\n\n    /**\n     * Write file from bytes by stream.\n     *\n     * @param file     The file.\n     * @param bytes    The bytes.\n     * @param listener The progress update listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByStream(final File file,\n                                                     final byte[] bytes,\n                                                     final OnProgressUpdateListener listener) {\n        return writeFileFromBytesByStream(file, bytes, false, listener);\n    }\n\n    /**\n     * Write file from bytes by stream.\n     *\n     * @param file     The file.\n     * @param bytes    The bytes.\n     * @param append   True to append, false otherwise.\n     * @param listener The progress update listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByStream(final File file,\n                                                     final byte[] bytes,\n                                                     final boolean append,\n                                                     final OnProgressUpdateListener listener) {\n        if (bytes == null) return false;\n        return writeFileFromIS(file, new ByteArrayInputStream(bytes), append, listener);\n    }\n\n    /**\n     * Write file from bytes by channel.\n     *\n     * @param filePath The path of file.\n     * @param bytes    The bytes.\n     * @param isForce  是否写入文件\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByChannel(final String filePath,\n                                                      final byte[] bytes,\n                                                      final boolean isForce) {\n        return writeFileFromBytesByChannel(UtilsBridge.getFileByPath(filePath), bytes, false, isForce);\n    }\n\n    /**\n     * Write file from bytes by channel.\n     *\n     * @param filePath The path of file.\n     * @param bytes    The bytes.\n     * @param append   True to append, false otherwise.\n     * @param isForce  True to force write file, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByChannel(final String filePath,\n                                                      final byte[] bytes,\n                                                      final boolean append,\n                                                      final boolean isForce) {\n        return writeFileFromBytesByChannel(UtilsBridge.getFileByPath(filePath), bytes, append, isForce);\n    }\n\n    /**\n     * Write file from bytes by channel.\n     *\n     * @param file    The file.\n     * @param bytes   The bytes.\n     * @param isForce True to force write file, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByChannel(final File file,\n                                                      final byte[] bytes,\n                                                      final boolean isForce) {\n        return writeFileFromBytesByChannel(file, bytes, false, isForce);\n    }\n\n    /**\n     * Write file from bytes by channel.\n     *\n     * @param file    The file.\n     * @param bytes   The bytes.\n     * @param append  True to append, false otherwise.\n     * @param isForce True to force write file, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByChannel(final File file,\n                                                      final byte[] bytes,\n                                                      final boolean append,\n                                                      final boolean isForce) {\n        if (bytes == null) {\n            Log.e(\"FileIOUtils\", \"bytes is null.\");\n            return false;\n        }\n        if (!UtilsBridge.createOrExistsFile(file)) {\n            Log.e(\"FileIOUtils\", \"create file <\" + file + \"> failed.\");\n            return false;\n        }\n        FileChannel fc = null;\n        try {\n            fc = new FileOutputStream(file, append).getChannel();\n            if (fc == null) {\n                Log.e(\"FileIOUtils\", \"fc is null.\");\n                return false;\n            }\n            fc.position(fc.size());\n            fc.write(ByteBuffer.wrap(bytes));\n            if (isForce) fc.force(true);\n            return true;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        } finally {\n            try {\n                if (fc != null) {\n                    fc.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Write file from bytes by map.\n     *\n     * @param filePath The path of file.\n     * @param bytes    The bytes.\n     * @param isForce  True to force write file, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByMap(final String filePath,\n                                                  final byte[] bytes,\n                                                  final boolean isForce) {\n        return writeFileFromBytesByMap(filePath, bytes, false, isForce);\n    }\n\n    /**\n     * Write file from bytes by map.\n     *\n     * @param filePath The path of file.\n     * @param bytes    The bytes.\n     * @param append   True to append, false otherwise.\n     * @param isForce  True to force write file, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByMap(final String filePath,\n                                                  final byte[] bytes,\n                                                  final boolean append,\n                                                  final boolean isForce) {\n        return writeFileFromBytesByMap(UtilsBridge.getFileByPath(filePath), bytes, append, isForce);\n    }\n\n    /**\n     * Write file from bytes by map.\n     *\n     * @param file    The file.\n     * @param bytes   The bytes.\n     * @param isForce True to force write file, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByMap(final File file,\n                                                  final byte[] bytes,\n                                                  final boolean isForce) {\n        return writeFileFromBytesByMap(file, bytes, false, isForce);\n    }\n\n    /**\n     * Write file from bytes by map.\n     *\n     * @param file    The file.\n     * @param bytes   The bytes.\n     * @param append  True to append, false otherwise.\n     * @param isForce True to force write file, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromBytesByMap(final File file,\n                                                  final byte[] bytes,\n                                                  final boolean append,\n                                                  final boolean isForce) {\n        if (bytes == null || !UtilsBridge.createOrExistsFile(file)) {\n            Log.e(\"FileIOUtils\", \"create file <\" + file + \"> failed.\");\n            return false;\n        }\n        FileChannel fc = null;\n        try {\n            fc = new FileOutputStream(file, append).getChannel();\n            if (fc == null) {\n                Log.e(\"FileIOUtils\", \"fc is null.\");\n                return false;\n            }\n            MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, fc.size(), bytes.length);\n            mbb.put(bytes);\n            if (isForce) mbb.force();\n            return true;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        } finally {\n            try {\n                if (fc != null) {\n                    fc.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Write file from string.\n     *\n     * @param filePath The path of file.\n     * @param content  The string of content.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromString(final String filePath, final String content) {\n        return writeFileFromString(UtilsBridge.getFileByPath(filePath), content, false);\n    }\n\n    /**\n     * Write file from string.\n     *\n     * @param filePath The path of file.\n     * @param content  The string of content.\n     * @param append   True to append, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromString(final String filePath,\n                                              final String content,\n                                              final boolean append) {\n        return writeFileFromString(UtilsBridge.getFileByPath(filePath), content, append);\n    }\n\n    /**\n     * Write file from string.\n     *\n     * @param file    The file.\n     * @param content The string of content.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromString(final File file, final String content) {\n        return writeFileFromString(file, content, false);\n    }\n\n    /**\n     * Write file from string.\n     *\n     * @param file    The file.\n     * @param content The string of content.\n     * @param append  True to append, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean writeFileFromString(final File file,\n                                              final String content,\n                                              final boolean append) {\n        if (file == null || content == null) return false;\n        if (!UtilsBridge.createOrExistsFile(file)) {\n            Log.e(\"FileIOUtils\", \"create file <\" + file + \"> failed.\");\n            return false;\n        }\n        BufferedWriter bw = null;\n        try {\n            bw = new BufferedWriter(new FileWriter(file, append));\n            bw.write(content);\n            return true;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        } finally {\n            try {\n                if (bw != null) {\n                    bw.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // the divide line of write and read\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Return the lines in file.\n     *\n     * @param filePath The path of file.\n     * @return the lines in file\n     */\n    public static List<String> readFile2List(final String filePath) {\n        return readFile2List(UtilsBridge.getFileByPath(filePath), null);\n    }\n\n    /**\n     * Return the lines in file.\n     *\n     * @param filePath    The path of file.\n     * @param charsetName The name of charset.\n     * @return the lines in file\n     */\n    public static List<String> readFile2List(final String filePath, final String charsetName) {\n        return readFile2List(UtilsBridge.getFileByPath(filePath), charsetName);\n    }\n\n    /**\n     * Return the lines in file.\n     *\n     * @param file The file.\n     * @return the lines in file\n     */\n    public static List<String> readFile2List(final File file) {\n        return readFile2List(file, 0, 0x7FFFFFFF, null);\n    }\n\n    /**\n     * Return the lines in file.\n     *\n     * @param file        The file.\n     * @param charsetName The name of charset.\n     * @return the lines in file\n     */\n    public static List<String> readFile2List(final File file, final String charsetName) {\n        return readFile2List(file, 0, 0x7FFFFFFF, charsetName);\n    }\n\n    /**\n     * Return the lines in file.\n     *\n     * @param filePath The path of file.\n     * @param st       The line's index of start.\n     * @param end      The line's index of end.\n     * @return the lines in file\n     */\n    public static List<String> readFile2List(final String filePath, final int st, final int end) {\n        return readFile2List(UtilsBridge.getFileByPath(filePath), st, end, null);\n    }\n\n    /**\n     * Return the lines in file.\n     *\n     * @param filePath    The path of file.\n     * @param st          The line's index of start.\n     * @param end         The line's index of end.\n     * @param charsetName The name of charset.\n     * @return the lines in file\n     */\n    public static List<String> readFile2List(final String filePath,\n                                             final int st,\n                                             final int end,\n                                             final String charsetName) {\n        return readFile2List(UtilsBridge.getFileByPath(filePath), st, end, charsetName);\n    }\n\n    /**\n     * Return the lines in file.\n     *\n     * @param file The file.\n     * @param st   The line's index of start.\n     * @param end  The line's index of end.\n     * @return the lines in file\n     */\n    public static List<String> readFile2List(final File file, final int st, final int end) {\n        return readFile2List(file, st, end, null);\n    }\n\n    /**\n     * Return the lines in file.\n     *\n     * @param file        The file.\n     * @param st          The line's index of start.\n     * @param end         The line's index of end.\n     * @param charsetName The name of charset.\n     * @return the lines in file\n     */\n    public static List<String> readFile2List(final File file,\n                                             final int st,\n                                             final int end,\n                                             final String charsetName) {\n        if (!UtilsBridge.isFileExists(file)) return null;\n        if (st > end) return null;\n        BufferedReader reader = null;\n        try {\n            String line;\n            int curLine = 1;\n            List<String> list = new ArrayList<>();\n            if (UtilsBridge.isSpace(charsetName)) {\n                reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));\n            } else {\n                reader = new BufferedReader(\n                        new InputStreamReader(new FileInputStream(file), charsetName)\n                );\n            }\n            while ((line = reader.readLine()) != null) {\n                if (curLine > end) break;\n                if (st <= curLine && curLine <= end) list.add(line);\n                ++curLine;\n            }\n            return list;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        } finally {\n            try {\n                if (reader != null) {\n                    reader.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Return the string in file.\n     *\n     * @param filePath The path of file.\n     * @return the string in file\n     */\n    public static String readFile2String(final String filePath) {\n        return readFile2String(UtilsBridge.getFileByPath(filePath), null);\n    }\n\n    /**\n     * Return the string in file.\n     *\n     * @param filePath    The path of file.\n     * @param charsetName The name of charset.\n     * @return the string in file\n     */\n    public static String readFile2String(final String filePath, final String charsetName) {\n        return readFile2String(UtilsBridge.getFileByPath(filePath), charsetName);\n    }\n\n    /**\n     * Return the string in file.\n     *\n     * @param file The file.\n     * @return the string in file\n     */\n    public static String readFile2String(final File file) {\n        return readFile2String(file, null);\n    }\n\n    /**\n     * Return the string in file.\n     *\n     * @param file        The file.\n     * @param charsetName The name of charset.\n     * @return the string in file\n     */\n    public static String readFile2String(final File file, final String charsetName) {\n        byte[] bytes = readFile2BytesByStream(file);\n        if (bytes == null) return null;\n        if (UtilsBridge.isSpace(charsetName)) {\n            return new String(bytes);\n        } else {\n            try {\n                return new String(bytes, charsetName);\n            } catch (UnsupportedEncodingException e) {\n                e.printStackTrace();\n                return \"\";\n            }\n        }\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // readFile2BytesByStream without progress\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Return the bytes in file by stream.\n     *\n     * @param filePath The path of file.\n     * @return the bytes in file\n     */\n    public static byte[] readFile2BytesByStream(final String filePath) {\n        return readFile2BytesByStream(UtilsBridge.getFileByPath(filePath), null);\n    }\n\n    /**\n     * Return the bytes in file by stream.\n     *\n     * @param file The file.\n     * @return the bytes in file\n     */\n    public static byte[] readFile2BytesByStream(final File file) {\n        return readFile2BytesByStream(file, null);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // readFile2BytesByStream with progress\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Return the bytes in file by stream.\n     *\n     * @param filePath The path of file.\n     * @param listener The progress update listener.\n     * @return the bytes in file\n     */\n    public static byte[] readFile2BytesByStream(final String filePath,\n                                                final OnProgressUpdateListener listener) {\n        return readFile2BytesByStream(UtilsBridge.getFileByPath(filePath), listener);\n    }\n\n    /**\n     * Return the bytes in file by stream.\n     *\n     * @param file     The file.\n     * @param listener The progress update listener.\n     * @return the bytes in file\n     */\n    public static byte[] readFile2BytesByStream(final File file,\n                                                final OnProgressUpdateListener listener) {\n        if (!UtilsBridge.isFileExists(file)) return null;\n        try {\n            ByteArrayOutputStream os = null;\n            InputStream is = new BufferedInputStream(new FileInputStream(file), sBufferSize);\n            try {\n                os = new ByteArrayOutputStream();\n                byte[] b = new byte[sBufferSize];\n                int len;\n                if (listener == null) {\n                    while ((len = is.read(b, 0, sBufferSize)) != -1) {\n                        os.write(b, 0, len);\n                    }\n                } else {\n                    double totalSize = is.available();\n                    int curSize = 0;\n                    listener.onProgressUpdate(0);\n                    while ((len = is.read(b, 0, sBufferSize)) != -1) {\n                        os.write(b, 0, len);\n                        curSize += len;\n                        listener.onProgressUpdate(curSize / totalSize);\n                    }\n                }\n                return os.toByteArray();\n            } catch (IOException e) {\n                e.printStackTrace();\n                return null;\n            } finally {\n                try {\n                    is.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n                try {\n                    if (os != null) {\n                        os.close();\n                    }\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        } catch (FileNotFoundException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * Return the bytes in file by channel.\n     *\n     * @param filePath The path of file.\n     * @return the bytes in file\n     */\n    public static byte[] readFile2BytesByChannel(final String filePath) {\n        return readFile2BytesByChannel(UtilsBridge.getFileByPath(filePath));\n    }\n\n    /**\n     * Return the bytes in file by channel.\n     *\n     * @param file The file.\n     * @return the bytes in file\n     */\n    public static byte[] readFile2BytesByChannel(final File file) {\n        if (!UtilsBridge.isFileExists(file)) return null;\n        FileChannel fc = null;\n        try {\n            fc = new RandomAccessFile(file, \"r\").getChannel();\n            if (fc == null) {\n                Log.e(\"FileIOUtils\", \"fc is null.\");\n                return new byte[0];\n            }\n            ByteBuffer byteBuffer = ByteBuffer.allocate((int) fc.size());\n            while (true) {\n                if (!((fc.read(byteBuffer)) > 0)) break;\n            }\n            return byteBuffer.array();\n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        } finally {\n            try {\n                if (fc != null) {\n                    fc.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Return the bytes in file by map.\n     *\n     * @param filePath The path of file.\n     * @return the bytes in file\n     */\n    public static byte[] readFile2BytesByMap(final String filePath) {\n        return readFile2BytesByMap(UtilsBridge.getFileByPath(filePath));\n    }\n\n    /**\n     * Return the bytes in file by map.\n     *\n     * @param file The file.\n     * @return the bytes in file\n     */\n    public static byte[] readFile2BytesByMap(final File file) {\n        if (!UtilsBridge.isFileExists(file)) return null;\n        FileChannel fc = null;\n        try {\n            fc = new RandomAccessFile(file, \"r\").getChannel();\n            if (fc == null) {\n                Log.e(\"FileIOUtils\", \"fc is null.\");\n                return new byte[0];\n            }\n            int size = (int) fc.size();\n            MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size).load();\n            byte[] result = new byte[size];\n            mbb.get(result, 0, size);\n            return result;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        } finally {\n            try {\n                if (fc != null) {\n                    fc.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Set the buffer's size.\n     * <p>Default size equals 8192 bytes.</p>\n     *\n     * @param bufferSize The buffer's size.\n     */\n    public static void setBufferSize(final int bufferSize) {\n        sBufferSize = bufferSize;\n    }\n\n    public interface OnProgressUpdateListener {\n        void onProgressUpdate(double progress);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/FileUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.ContentResolver;\nimport android.content.Intent;\nimport android.content.res.AssetFileDescriptor;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.StatFs;\nimport android.text.TextUtils;\n\nimport java.io.BufferedInputStream;\nimport java.io.File;\nimport java.io.FileFilter;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.security.DigestInputStream;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\n\nimport javax.net.ssl.HttpsURLConnection;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/05/03\n *     desc  : utils about file\n * </pre>\n */\npublic final class FileUtils {\n\n    private static final String LINE_SEP = System.getProperty(\"line.separator\");\n\n    private FileUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return the file by path.\n     *\n     * @param filePath The path of file.\n     * @return the file\n     */\n    public static File getFileByPath(final String filePath) {\n        return UtilsBridge.isSpace(filePath) ? null : new File(filePath);\n    }\n\n    /**\n     * Return whether the file exists.\n     *\n     * @param file The file.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isFileExists(final File file) {\n        if (file == null) return false;\n        if (file.exists()) {\n            return true;\n        }\n        return isFileExists(file.getAbsolutePath());\n    }\n\n    /**\n     * Return whether the file exists.\n     *\n     * @param filePath The path of file.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isFileExists(final String filePath) {\n        File file = getFileByPath(filePath);\n        if (file == null) return false;\n        if (file.exists()) {\n            return true;\n        }\n        return isFileExistsApi29(filePath);\n    }\n\n    private static boolean isFileExistsApi29(String filePath) {\n        if (Build.VERSION.SDK_INT >= 29) {\n            try {\n                Uri uri = Uri.parse(filePath);\n                ContentResolver cr = Utils.getApp().getContentResolver();\n                AssetFileDescriptor afd = cr.openAssetFileDescriptor(uri, \"r\");\n                if (afd == null) return false;\n                try {\n                    afd.close();\n                } catch (IOException ignore) {\n                }\n            } catch (FileNotFoundException e) {\n                return false;\n            }\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Rename the file.\n     *\n     * @param filePath The path of file.\n     * @param newName  The new name of file.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean rename(final String filePath, final String newName) {\n        return rename(getFileByPath(filePath), newName);\n    }\n\n    /**\n     * Rename the file.\n     *\n     * @param file    The file.\n     * @param newName The new name of file.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean rename(final File file, final String newName) {\n        // file is null then return false\n        if (file == null) return false;\n        // file doesn't exist then return false\n        if (!file.exists()) return false;\n        // the new name is space then return false\n        if (UtilsBridge.isSpace(newName)) return false;\n        // the new name equals old name then return true\n        if (newName.equals(file.getName())) return true;\n        File newFile = new File(file.getParent() + File.separator + newName);\n        // the new name of file exists then return false\n        return !newFile.exists()\n                && file.renameTo(newFile);\n    }\n\n    /**\n     * Return whether it is a directory.\n     *\n     * @param dirPath The path of directory.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isDir(final String dirPath) {\n        return isDir(getFileByPath(dirPath));\n    }\n\n    /**\n     * Return whether it is a directory.\n     *\n     * @param file The file.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isDir(final File file) {\n        return file != null && file.exists() && file.isDirectory();\n    }\n\n    /**\n     * Return whether it is a file.\n     *\n     * @param filePath The path of file.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isFile(final String filePath) {\n        return isFile(getFileByPath(filePath));\n    }\n\n    /**\n     * Return whether it is a file.\n     *\n     * @param file The file.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isFile(final File file) {\n        return file != null && file.exists() && file.isFile();\n    }\n\n    /**\n     * Create a directory if it doesn't exist, otherwise do nothing.\n     *\n     * @param dirPath The path of directory.\n     * @return {@code true}: exists or creates successfully<br>{@code false}: otherwise\n     */\n    public static boolean createOrExistsDir(final String dirPath) {\n        return createOrExistsDir(getFileByPath(dirPath));\n    }\n\n    /**\n     * Create a directory if it doesn't exist, otherwise do nothing.\n     *\n     * @param file The file.\n     * @return {@code true}: exists or creates successfully<br>{@code false}: otherwise\n     */\n    public static boolean createOrExistsDir(final File file) {\n        return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());\n    }\n\n    /**\n     * Create a file if it doesn't exist, otherwise do nothing.\n     *\n     * @param filePath The path of file.\n     * @return {@code true}: exists or creates successfully<br>{@code false}: otherwise\n     */\n    public static boolean createOrExistsFile(final String filePath) {\n        return createOrExistsFile(getFileByPath(filePath));\n    }\n\n    /**\n     * Create a file if it doesn't exist, otherwise do nothing.\n     *\n     * @param file The file.\n     * @return {@code true}: exists or creates successfully<br>{@code false}: otherwise\n     */\n    public static boolean createOrExistsFile(final File file) {\n        if (file == null) return false;\n        if (file.exists()) return file.isFile();\n        if (!createOrExistsDir(file.getParentFile())) return false;\n        try {\n            return file.createNewFile();\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * Create a file if it doesn't exist, otherwise delete old file before creating.\n     *\n     * @param filePath The path of file.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean createFileByDeleteOldFile(final String filePath) {\n        return createFileByDeleteOldFile(getFileByPath(filePath));\n    }\n\n    /**\n     * Create a file if it doesn't exist, otherwise delete old file before creating.\n     *\n     * @param file The file.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean createFileByDeleteOldFile(final File file) {\n        if (file == null) return false;\n        // file exists and unsuccessfully delete then return false\n        if (file.exists() && !file.delete()) return false;\n        if (!createOrExistsDir(file.getParentFile())) return false;\n        try {\n            return file.createNewFile();\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * Copy the directory or file.\n     *\n     * @param srcPath  The path of source.\n     * @param destPath The path of destination.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean copy(final String srcPath,\n                               final String destPath) {\n        return copy(getFileByPath(srcPath), getFileByPath(destPath), null);\n    }\n\n    /**\n     * Copy the directory or file.\n     *\n     * @param srcPath  The path of source.\n     * @param destPath The path of destination.\n     * @param listener The replace listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean copy(final String srcPath,\n                               final String destPath,\n                               final OnReplaceListener listener) {\n        return copy(getFileByPath(srcPath), getFileByPath(destPath), listener);\n    }\n\n    /**\n     * Copy the directory or file.\n     *\n     * @param src  The source.\n     * @param dest The destination.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean copy(final File src,\n                               final File dest) {\n        return copy(src, dest, null);\n    }\n\n    /**\n     * Copy the directory or file.\n     *\n     * @param src      The source.\n     * @param dest     The destination.\n     * @param listener The replace listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean copy(final File src,\n                               final File dest,\n                               final OnReplaceListener listener) {\n        if (src == null) return false;\n        if (src.isDirectory()) {\n            return copyDir(src, dest, listener);\n        }\n        return copyFile(src, dest, listener);\n    }\n\n    /**\n     * Copy the directory.\n     *\n     * @param srcDir   The source directory.\n     * @param destDir  The destination directory.\n     * @param listener The replace listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    private static boolean copyDir(final File srcDir,\n                                   final File destDir,\n                                   final OnReplaceListener listener) {\n        return copyOrMoveDir(srcDir, destDir, listener, false);\n    }\n\n    /**\n     * Copy the file.\n     *\n     * @param srcFile  The source file.\n     * @param destFile The destination file.\n     * @param listener The replace listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    private static boolean copyFile(final File srcFile,\n                                    final File destFile,\n                                    final OnReplaceListener listener) {\n        return copyOrMoveFile(srcFile, destFile, listener, false);\n    }\n\n    /**\n     * Move the directory or file.\n     *\n     * @param srcPath  The path of source.\n     * @param destPath The path of destination.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean move(final String srcPath,\n                               final String destPath) {\n        return move(getFileByPath(srcPath), getFileByPath(destPath), null);\n    }\n\n    /**\n     * Move the directory or file.\n     *\n     * @param srcPath  The path of source.\n     * @param destPath The path of destination.\n     * @param listener The replace listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean move(final String srcPath,\n                               final String destPath,\n                               final OnReplaceListener listener) {\n        return move(getFileByPath(srcPath), getFileByPath(destPath), listener);\n    }\n\n    /**\n     * Move the directory or file.\n     *\n     * @param src  The source.\n     * @param dest The destination.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean move(final File src,\n                               final File dest) {\n        return move(src, dest, null);\n    }\n\n    /**\n     * Move the directory or file.\n     *\n     * @param src      The source.\n     * @param dest     The destination.\n     * @param listener The replace listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean move(final File src,\n                               final File dest,\n                               final OnReplaceListener listener) {\n        if (src == null) return false;\n        if (src.isDirectory()) {\n            return moveDir(src, dest, listener);\n        }\n        return moveFile(src, dest, listener);\n    }\n\n    /**\n     * Move the directory.\n     *\n     * @param srcDir   The source directory.\n     * @param destDir  The destination directory.\n     * @param listener The replace listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean moveDir(final File srcDir,\n                                  final File destDir,\n                                  final OnReplaceListener listener) {\n        return copyOrMoveDir(srcDir, destDir, listener, true);\n    }\n\n    /**\n     * Move the file.\n     *\n     * @param srcFile  The source file.\n     * @param destFile The destination file.\n     * @param listener The replace listener.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean moveFile(final File srcFile,\n                                   final File destFile,\n                                   final OnReplaceListener listener) {\n        return copyOrMoveFile(srcFile, destFile, listener, true);\n    }\n\n    private static boolean copyOrMoveDir(final File srcDir,\n                                         final File destDir,\n                                         final OnReplaceListener listener,\n                                         final boolean isMove) {\n        if (srcDir == null || destDir == null) return false;\n        // destDir's path locate in srcDir's path then return false\n        String srcPath = srcDir.getPath() + File.separator;\n        String destPath = destDir.getPath() + File.separator;\n        if (destPath.contains(srcPath)) return false;\n        if (!srcDir.exists() || !srcDir.isDirectory()) return false;\n        if (!createOrExistsDir(destDir)) return false;\n        File[] files = srcDir.listFiles();\n        if (files != null && files.length > 0) {\n            for (File file : files) {\n                File oneDestFile = new File(destPath + file.getName());\n                if (file.isFile()) {\n                    if (!copyOrMoveFile(file, oneDestFile, listener, isMove)) return false;\n                } else if (file.isDirectory()) {\n                    if (!copyOrMoveDir(file, oneDestFile, listener, isMove)) return false;\n                }\n            }\n        }\n        return !isMove || deleteDir(srcDir);\n    }\n\n    private static boolean copyOrMoveFile(final File srcFile,\n                                          final File destFile,\n                                          final OnReplaceListener listener,\n                                          final boolean isMove) {\n        if (srcFile == null || destFile == null) return false;\n        // srcFile equals destFile then return false\n        if (srcFile.equals(destFile)) return false;\n        // srcFile doesn't exist or isn't a file then return false\n        if (!srcFile.exists() || !srcFile.isFile()) return false;\n        if (destFile.exists()) {\n            if (listener == null || listener.onReplace(srcFile, destFile)) {// require delete the old file\n                if (!destFile.delete()) {// unsuccessfully delete then return false\n                    return false;\n                }\n            } else {\n                return true;\n            }\n        }\n        if (!createOrExistsDir(destFile.getParentFile())) return false;\n        try {\n            return UtilsBridge.writeFileFromIS(destFile.getAbsolutePath(), new FileInputStream(srcFile))\n                    && !(isMove && !deleteFile(srcFile));\n        } catch (FileNotFoundException e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * Delete the directory.\n     *\n     * @param filePath The path of file.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean delete(final String filePath) {\n        return delete(getFileByPath(filePath));\n    }\n\n    /**\n     * Delete the directory.\n     *\n     * @param file The file.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean delete(final File file) {\n        if (file == null) return false;\n        if (file.isDirectory()) {\n            return deleteDir(file);\n        }\n        return deleteFile(file);\n    }\n\n    /**\n     * Delete the directory.\n     *\n     * @param dir The directory.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    private static boolean deleteDir(final File dir) {\n        if (dir == null) return false;\n        // dir doesn't exist then return true\n        if (!dir.exists()) return true;\n        // dir isn't a directory then return false\n        if (!dir.isDirectory()) return false;\n        File[] files = dir.listFiles();\n        if (files != null && files.length > 0) {\n            for (File file : files) {\n                if (file.isFile()) {\n                    if (!file.delete()) return false;\n                } else if (file.isDirectory()) {\n                    if (!deleteDir(file)) return false;\n                }\n            }\n        }\n        return dir.delete();\n    }\n\n    /**\n     * Delete the file.\n     *\n     * @param file The file.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    private static boolean deleteFile(final File file) {\n        return file != null && (!file.exists() || file.isFile() && file.delete());\n    }\n\n    /**\n     * Delete the all in directory.\n     *\n     * @param dirPath The path of directory.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean deleteAllInDir(final String dirPath) {\n        return deleteAllInDir(getFileByPath(dirPath));\n    }\n\n    /**\n     * Delete the all in directory.\n     *\n     * @param dir The directory.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean deleteAllInDir(final File dir) {\n        return deleteFilesInDirWithFilter(dir, new FileFilter() {\n            @Override\n            public boolean accept(File pathname) {\n                return true;\n            }\n        });\n    }\n\n    /**\n     * Delete all files in directory.\n     *\n     * @param dirPath The path of directory.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean deleteFilesInDir(final String dirPath) {\n        return deleteFilesInDir(getFileByPath(dirPath));\n    }\n\n    /**\n     * Delete all files in directory.\n     *\n     * @param dir The directory.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean deleteFilesInDir(final File dir) {\n        return deleteFilesInDirWithFilter(dir, new FileFilter() {\n            @Override\n            public boolean accept(File pathname) {\n                return pathname.isFile();\n            }\n        });\n    }\n\n    /**\n     * Delete all files that satisfy the filter in directory.\n     *\n     * @param dirPath The path of directory.\n     * @param filter  The filter.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean deleteFilesInDirWithFilter(final String dirPath,\n                                                     final FileFilter filter) {\n        return deleteFilesInDirWithFilter(getFileByPath(dirPath), filter);\n    }\n\n    /**\n     * Delete all files that satisfy the filter in directory.\n     *\n     * @param dir    The directory.\n     * @param filter The filter.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean deleteFilesInDirWithFilter(final File dir, final FileFilter filter) {\n        if (dir == null || filter == null) return false;\n        // dir doesn't exist then return true\n        if (!dir.exists()) return true;\n        // dir isn't a directory then return false\n        if (!dir.isDirectory()) return false;\n        File[] files = dir.listFiles();\n        if (files != null && files.length != 0) {\n            for (File file : files) {\n                if (filter.accept(file)) {\n                    if (file.isFile()) {\n                        if (!file.delete()) return false;\n                    } else if (file.isDirectory()) {\n                        if (!deleteDir(file)) return false;\n                    }\n                }\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Return the files in directory.\n     * <p>Doesn't traverse subdirectories</p>\n     *\n     * @param dirPath The path of directory.\n     * @return the files in directory\n     */\n    public static List<File> listFilesInDir(final String dirPath) {\n        return listFilesInDir(dirPath, null);\n    }\n\n    /**\n     * Return the files in directory.\n     * <p>Doesn't traverse subdirectories</p>\n     *\n     * @param dir The directory.\n     * @return the files in directory\n     */\n    public static List<File> listFilesInDir(final File dir) {\n        return listFilesInDir(dir, null);\n    }\n\n    /**\n     * Return the files in directory.\n     * <p>Doesn't traverse subdirectories</p>\n     *\n     * @param dirPath    The path of directory.\n     * @param comparator The comparator to determine the order of the list.\n     * @return the files in directory\n     */\n    public static List<File> listFilesInDir(final String dirPath, Comparator<File> comparator) {\n        return listFilesInDir(getFileByPath(dirPath), false, comparator);\n    }\n\n    /**\n     * Return the files in directory.\n     * <p>Doesn't traverse subdirectories</p>\n     *\n     * @param dir        The directory.\n     * @param comparator The comparator to determine the order of the list.\n     * @return the files in directory\n     */\n    public static List<File> listFilesInDir(final File dir, Comparator<File> comparator) {\n        return listFilesInDir(dir, false, comparator);\n    }\n\n    /**\n     * Return the files in directory.\n     *\n     * @param dirPath     The path of directory.\n     * @param isRecursive True to traverse subdirectories, false otherwise.\n     * @return the files in directory\n     */\n    public static List<File> listFilesInDir(final String dirPath, final boolean isRecursive) {\n        return listFilesInDir(getFileByPath(dirPath), isRecursive);\n    }\n\n    /**\n     * Return the files in directory.\n     *\n     * @param dir         The directory.\n     * @param isRecursive True to traverse subdirectories, false otherwise.\n     * @return the files in directory\n     */\n    public static List<File> listFilesInDir(final File dir, final boolean isRecursive) {\n        return listFilesInDir(dir, isRecursive, null);\n    }\n\n    /**\n     * Return the files in directory.\n     *\n     * @param dirPath     The path of directory.\n     * @param isRecursive True to traverse subdirectories, false otherwise.\n     * @param comparator  The comparator to determine the order of the list.\n     * @return the files in directory\n     */\n    public static List<File> listFilesInDir(final String dirPath,\n                                            final boolean isRecursive,\n                                            final Comparator<File> comparator) {\n        return listFilesInDir(getFileByPath(dirPath), isRecursive, comparator);\n    }\n\n    /**\n     * Return the files in directory.\n     *\n     * @param dir         The directory.\n     * @param isRecursive True to traverse subdirectories, false otherwise.\n     * @param comparator  The comparator to determine the order of the list.\n     * @return the files in directory\n     */\n    public static List<File> listFilesInDir(final File dir,\n                                            final boolean isRecursive,\n                                            final Comparator<File> comparator) {\n        return listFilesInDirWithFilter(dir, new FileFilter() {\n            @Override\n            public boolean accept(File pathname) {\n                return true;\n            }\n        }, isRecursive, comparator);\n    }\n\n    /**\n     * Return the files that satisfy the filter in directory.\n     * <p>Doesn't traverse subdirectories</p>\n     *\n     * @param dirPath The path of directory.\n     * @param filter  The filter.\n     * @return the files that satisfy the filter in directory\n     */\n    public static List<File> listFilesInDirWithFilter(final String dirPath,\n                                                      final FileFilter filter) {\n        return listFilesInDirWithFilter(getFileByPath(dirPath), filter);\n    }\n\n    /**\n     * Return the files that satisfy the filter in directory.\n     * <p>Doesn't traverse subdirectories</p>\n     *\n     * @param dir    The directory.\n     * @param filter The filter.\n     * @return the files that satisfy the filter in directory\n     */\n    public static List<File> listFilesInDirWithFilter(final File dir,\n                                                      final FileFilter filter) {\n        return listFilesInDirWithFilter(dir, filter, false, null);\n    }\n\n    /**\n     * Return the files that satisfy the filter in directory.\n     * <p>Doesn't traverse subdirectories</p>\n     *\n     * @param dirPath    The path of directory.\n     * @param filter     The filter.\n     * @param comparator The comparator to determine the order of the list.\n     * @return the files that satisfy the filter in directory\n     */\n    public static List<File> listFilesInDirWithFilter(final String dirPath,\n                                                      final FileFilter filter,\n                                                      final Comparator<File> comparator) {\n        return listFilesInDirWithFilter(getFileByPath(dirPath), filter, comparator);\n    }\n\n    /**\n     * Return the files that satisfy the filter in directory.\n     * <p>Doesn't traverse subdirectories</p>\n     *\n     * @param dir        The directory.\n     * @param filter     The filter.\n     * @param comparator The comparator to determine the order of the list.\n     * @return the files that satisfy the filter in directory\n     */\n    public static List<File> listFilesInDirWithFilter(final File dir,\n                                                      final FileFilter filter,\n                                                      final Comparator<File> comparator) {\n        return listFilesInDirWithFilter(dir, filter, false, comparator);\n    }\n\n    /**\n     * Return the files that satisfy the filter in directory.\n     *\n     * @param dirPath     The path of directory.\n     * @param filter      The filter.\n     * @param isRecursive True to traverse subdirectories, false otherwise.\n     * @return the files that satisfy the filter in directory\n     */\n    public static List<File> listFilesInDirWithFilter(final String dirPath,\n                                                      final FileFilter filter,\n                                                      final boolean isRecursive) {\n        return listFilesInDirWithFilter(getFileByPath(dirPath), filter, isRecursive);\n    }\n\n    /**\n     * Return the files that satisfy the filter in directory.\n     *\n     * @param dir         The directory.\n     * @param filter      The filter.\n     * @param isRecursive True to traverse subdirectories, false otherwise.\n     * @return the files that satisfy the filter in directory\n     */\n    public static List<File> listFilesInDirWithFilter(final File dir,\n                                                      final FileFilter filter,\n                                                      final boolean isRecursive) {\n        return listFilesInDirWithFilter(dir, filter, isRecursive, null);\n    }\n\n\n    /**\n     * Return the files that satisfy the filter in directory.\n     *\n     * @param dirPath     The path of directory.\n     * @param filter      The filter.\n     * @param isRecursive True to traverse subdirectories, false otherwise.\n     * @param comparator  The comparator to determine the order of the list.\n     * @return the files that satisfy the filter in directory\n     */\n    public static List<File> listFilesInDirWithFilter(final String dirPath,\n                                                      final FileFilter filter,\n                                                      final boolean isRecursive,\n                                                      final Comparator<File> comparator) {\n        return listFilesInDirWithFilter(getFileByPath(dirPath), filter, isRecursive, comparator);\n    }\n\n    /**\n     * Return the files that satisfy the filter in directory.\n     *\n     * @param dir         The directory.\n     * @param filter      The filter.\n     * @param isRecursive True to traverse subdirectories, false otherwise.\n     * @param comparator  The comparator to determine the order of the list.\n     * @return the files that satisfy the filter in directory\n     */\n    public static List<File> listFilesInDirWithFilter(final File dir,\n                                                      final FileFilter filter,\n                                                      final boolean isRecursive,\n                                                      final Comparator<File> comparator) {\n        List<File> files = listFilesInDirWithFilterInner(dir, filter, isRecursive);\n        if (comparator != null) {\n            Collections.sort(files, comparator);\n        }\n        return files;\n    }\n\n    private static List<File> listFilesInDirWithFilterInner(final File dir,\n                                                            final FileFilter filter,\n                                                            final boolean isRecursive) {\n        List<File> list = new ArrayList<>();\n        if (!isDir(dir)) return list;\n        File[] files = dir.listFiles();\n        if (files != null && files.length > 0) {\n            for (File file : files) {\n                if (filter.accept(file)) {\n                    list.add(file);\n                }\n                if (isRecursive && file.isDirectory()) {\n                    list.addAll(listFilesInDirWithFilterInner(file, filter, true));\n                }\n            }\n        }\n        return list;\n    }\n\n    /**\n     * Return the time that the file was last modified.\n     *\n     * @param filePath The path of file.\n     * @return the time that the file was last modified\n     */\n\n    public static long getFileLastModified(final String filePath) {\n        return getFileLastModified(getFileByPath(filePath));\n    }\n\n    /**\n     * Return the time that the file was last modified.\n     *\n     * @param file The file.\n     * @return the time that the file was last modified\n     */\n    public static long getFileLastModified(final File file) {\n        if (file == null) return -1;\n        return file.lastModified();\n    }\n\n    /**\n     * Return the charset of file simply.\n     *\n     * @param filePath The path of file.\n     * @return the charset of file simply\n     */\n    public static String getFileCharsetSimple(final String filePath) {\n        return getFileCharsetSimple(getFileByPath(filePath));\n    }\n\n    /**\n     * Return the charset of file simply.\n     *\n     * @param file The file.\n     * @return the charset of file simply\n     */\n    public static String getFileCharsetSimple(final File file) {\n        if (file == null) return \"\";\n        if (isUtf8(file)) return \"UTF-8\";\n        int p = 0;\n        InputStream is = null;\n        try {\n            is = new BufferedInputStream(new FileInputStream(file));\n            p = (is.read() << 8) + is.read();\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            try {\n                if (is != null) {\n                    is.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        switch (p) {\n            case 0xfffe:\n                return \"Unicode\";\n            case 0xfeff:\n                return \"UTF-16BE\";\n            default:\n                return \"GBK\";\n        }\n    }\n\n    /**\n     * Return whether the charset of file is utf8.\n     *\n     * @param filePath The path of file.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isUtf8(final String filePath) {\n        return isUtf8(getFileByPath(filePath));\n    }\n\n    /**\n     * Return whether the charset of file is utf8.\n     *\n     * @param file The file.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isUtf8(final File file) {\n        if (file == null) return false;\n        InputStream is = null;\n        try {\n            byte[] bytes = new byte[24];\n            is = new BufferedInputStream(new FileInputStream(file));\n            int read = is.read(bytes);\n            if (read != -1) {\n                byte[] readArr = new byte[read];\n                System.arraycopy(bytes, 0, readArr, 0, read);\n                return isUtf8(readArr) == 100;\n            } else {\n                return false;\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            try {\n                if (is != null) {\n                    is.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        return false;\n    }\n\n    /**\n     * UTF-8编码方式\n     * ----------------------------------------------\n     * 0xxxxxxx\n     * 110xxxxx 10xxxxxx\n     * 1110xxxx 10xxxxxx 10xxxxxx\n     * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx\n     */\n    private static int isUtf8(byte[] raw) {\n        int i, len;\n        int utf8 = 0, ascii = 0;\n        if (raw.length > 3) {\n            if ((raw[0] == (byte) 0xEF) && (raw[1] == (byte) 0xBB) && (raw[2] == (byte) 0xBF)) {\n                return 100;\n            }\n        }\n        len = raw.length;\n        int child = 0;\n        for (i = 0; i < len; ) {\n            // UTF-8 byte shouldn't be FF and FE\n            if ((raw[i] & (byte) 0xFF) == (byte) 0xFF || (raw[i] & (byte) 0xFE) == (byte) 0xFE) {\n                return 0;\n            }\n            if (child == 0) {\n                // ASCII format is 0x0*******\n                if ((raw[i] & (byte) 0x7F) == raw[i] && raw[i] != 0) {\n                    ascii++;\n                } else if ((raw[i] & (byte) 0xC0) == (byte) 0xC0) {\n                    // 0x11****** maybe is UTF-8\n                    for (int bit = 0; bit < 8; bit++) {\n                        if ((((byte) (0x80 >> bit)) & raw[i]) == ((byte) (0x80 >> bit))) {\n                            child = bit;\n                        } else {\n                            break;\n                        }\n                    }\n                    utf8++;\n                }\n                i++;\n            } else {\n                child = (raw.length - i > child) ? child : (raw.length - i);\n                boolean currentNotUtf8 = false;\n                for (int children = 0; children < child; children++) {\n                    // format must is 0x10******\n                    if ((raw[i + children] & ((byte) 0x80)) != ((byte) 0x80)) {\n                        if ((raw[i + children] & (byte) 0x7F) == raw[i + children] && raw[i] != 0) {\n                            // ASCII format is 0x0*******\n                            ascii++;\n                        }\n                        currentNotUtf8 = true;\n                    }\n                }\n                if (currentNotUtf8) {\n                    utf8--;\n                    i++;\n                } else {\n                    utf8 += child;\n                    i += child;\n                }\n                child = 0;\n            }\n        }\n        // UTF-8 contains ASCII\n        if (ascii == len) {\n            return 100;\n        }\n        return (int) (100 * ((float) (utf8 + ascii) / (float) len));\n    }\n\n    /**\n     * Return the number of lines of file.\n     *\n     * @param filePath The path of file.\n     * @return the number of lines of file\n     */\n    public static int getFileLines(final String filePath) {\n        return getFileLines(getFileByPath(filePath));\n    }\n\n    /**\n     * Return the number of lines of file.\n     *\n     * @param file The file.\n     * @return the number of lines of file\n     */\n    public static int getFileLines(final File file) {\n        int count = 1;\n        InputStream is = null;\n        try {\n            is = new BufferedInputStream(new FileInputStream(file));\n            byte[] buffer = new byte[1024];\n            int readChars;\n            if (LINE_SEP.endsWith(\"\\n\")) {\n                while ((readChars = is.read(buffer, 0, 1024)) != -1) {\n                    for (int i = 0; i < readChars; ++i) {\n                        if (buffer[i] == '\\n') ++count;\n                    }\n                }\n            } else {\n                while ((readChars = is.read(buffer, 0, 1024)) != -1) {\n                    for (int i = 0; i < readChars; ++i) {\n                        if (buffer[i] == '\\r') ++count;\n                    }\n                }\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            try {\n                if (is != null) {\n                    is.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        return count;\n    }\n\n    /**\n     * Return the size.\n     *\n     * @param filePath The path of file.\n     * @return the size\n     */\n    public static String getSize(final String filePath) {\n        return getSize(getFileByPath(filePath));\n    }\n\n    /**\n     * Return the size.\n     *\n     * @param file The directory.\n     * @return the size\n     */\n    public static String getSize(final File file) {\n        if (file == null) return \"\";\n        if (file.isDirectory()) {\n            return getDirSize(file);\n        }\n        return getFileSize(file);\n    }\n\n    /**\n     * Return the size of directory.\n     *\n     * @param dir The directory.\n     * @return the size of directory\n     */\n    private static String getDirSize(final File dir) {\n        long len = getDirLength(dir);\n        return len == -1 ? \"\" : UtilsBridge.byte2FitMemorySize(len);\n    }\n\n    /**\n     * Return the size of file.\n     *\n     * @param file The file.\n     * @return the length of file\n     */\n    private static String getFileSize(final File file) {\n        long len = getFileLength(file);\n        return len == -1 ? \"\" : UtilsBridge.byte2FitMemorySize(len);\n    }\n\n    /**\n     * Return the length.\n     *\n     * @param filePath The path of file.\n     * @return the length\n     */\n    public static long getLength(final String filePath) {\n        return getLength(getFileByPath(filePath));\n    }\n\n    /**\n     * Return the length.\n     *\n     * @param file The file.\n     * @return the length\n     */\n    public static long getLength(final File file) {\n        if (file == null) return 0;\n        if (file.isDirectory()) {\n            return getDirLength(file);\n        }\n        return getFileLength(file);\n    }\n\n    /**\n     * Return the length of directory.\n     *\n     * @param dir The directory.\n     * @return the length of directory\n     */\n    private static long getDirLength(final File dir) {\n        if (!isDir(dir)) return 0;\n        long len = 0;\n        File[] files = dir.listFiles();\n        if (files != null && files.length > 0) {\n            for (File file : files) {\n                if (file.isDirectory()) {\n                    len += getDirLength(file);\n                } else {\n                    len += file.length();\n                }\n            }\n        }\n        return len;\n    }\n\n    /**\n     * Return the length of file.\n     *\n     * @param filePath The path of file.\n     * @return the length of file\n     */\n    public static long getFileLength(final String filePath) {\n        boolean isURL = filePath.matches(\"[a-zA-z]+://[^\\\\s]*\");\n        if (isURL) {\n            try {\n                HttpsURLConnection conn = (HttpsURLConnection) new URL(filePath).openConnection();\n                conn.setRequestProperty(\"Accept-Encoding\", \"identity\");\n                conn.connect();\n                if (conn.getResponseCode() == 200) {\n                    return conn.getContentLength();\n                }\n                return -1;\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        return getFileLength(getFileByPath(filePath));\n    }\n\n    /**\n     * Return the length of file.\n     *\n     * @param file The file.\n     * @return the length of file\n     */\n    private static long getFileLength(final File file) {\n        if (!isFile(file)) return -1;\n        return file.length();\n    }\n\n    /**\n     * Return the MD5 of file.\n     *\n     * @param filePath The path of file.\n     * @return the md5 of file\n     */\n    public static String getFileMD5ToString(final String filePath) {\n        File file = UtilsBridge.isSpace(filePath) ? null : new File(filePath);\n        return getFileMD5ToString(file);\n    }\n\n    /**\n     * Return the MD5 of file.\n     *\n     * @param file The file.\n     * @return the md5 of file\n     */\n    public static String getFileMD5ToString(final File file) {\n        return UtilsBridge.bytes2HexString(getFileMD5(file));\n    }\n\n    /**\n     * Return the MD5 of file.\n     *\n     * @param filePath The path of file.\n     * @return the md5 of file\n     */\n    public static byte[] getFileMD5(final String filePath) {\n        return getFileMD5(getFileByPath(filePath));\n    }\n\n    /**\n     * Return the MD5 of file.\n     *\n     * @param file The file.\n     * @return the md5 of file\n     */\n    public static byte[] getFileMD5(final File file) {\n        if (file == null) return null;\n        DigestInputStream dis = null;\n        try {\n            FileInputStream fis = new FileInputStream(file);\n            MessageDigest md = MessageDigest.getInstance(\"MD5\");\n            dis = new DigestInputStream(fis, md);\n            byte[] buffer = new byte[1024 * 256];\n            while (true) {\n                if (!(dis.read(buffer) > 0)) break;\n            }\n            md = dis.getMessageDigest();\n            return md.digest();\n        } catch (NoSuchAlgorithmException | IOException e) {\n            e.printStackTrace();\n        } finally {\n            try {\n                if (dis != null) {\n                    dis.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Return the file's path of directory.\n     *\n     * @param file The file.\n     * @return the file's path of directory\n     */\n    public static String getDirName(final File file) {\n        if (file == null) return \"\";\n        return getDirName(file.getAbsolutePath());\n    }\n\n    /**\n     * Return the file's path of directory.\n     *\n     * @param filePath The path of file.\n     * @return the file's path of directory\n     */\n    public static String getDirName(final String filePath) {\n        if (UtilsBridge.isSpace(filePath)) return \"\";\n        int lastSep = filePath.lastIndexOf(File.separator);\n        return lastSep == -1 ? \"\" : filePath.substring(0, lastSep + 1);\n    }\n\n    /**\n     * Return the name of file.\n     *\n     * @param file The file.\n     * @return the name of file\n     */\n    public static String getFileName(final File file) {\n        if (file == null) return \"\";\n        return getFileName(file.getAbsolutePath());\n    }\n\n    /**\n     * Return the name of file.\n     *\n     * @param filePath The path of file.\n     * @return the name of file\n     */\n    public static String getFileName(final String filePath) {\n        if (UtilsBridge.isSpace(filePath)) return \"\";\n        int lastSep = filePath.lastIndexOf(File.separator);\n        return lastSep == -1 ? filePath : filePath.substring(lastSep + 1);\n    }\n\n    /**\n     * Return the name of file without extension.\n     *\n     * @param file The file.\n     * @return the name of file without extension\n     */\n    public static String getFileNameNoExtension(final File file) {\n        if (file == null) return \"\";\n        return getFileNameNoExtension(file.getPath());\n    }\n\n    /**\n     * Return the name of file without extension.\n     *\n     * @param filePath The path of file.\n     * @return the name of file without extension\n     */\n    public static String getFileNameNoExtension(final String filePath) {\n        if (UtilsBridge.isSpace(filePath)) return \"\";\n        int lastPoi = filePath.lastIndexOf('.');\n        int lastSep = filePath.lastIndexOf(File.separator);\n        if (lastSep == -1) {\n            return (lastPoi == -1 ? filePath : filePath.substring(0, lastPoi));\n        }\n        if (lastPoi == -1 || lastSep > lastPoi) {\n            return filePath.substring(lastSep + 1);\n        }\n        return filePath.substring(lastSep + 1, lastPoi);\n    }\n\n    /**\n     * Return the extension of file.\n     *\n     * @param file The file.\n     * @return the extension of file\n     */\n    public static String getFileExtension(final File file) {\n        if (file == null) return \"\";\n        return getFileExtension(file.getPath());\n    }\n\n    /**\n     * Return the extension of file.\n     *\n     * @param filePath The path of file.\n     * @return the extension of file\n     */\n    public static String getFileExtension(final String filePath) {\n        if (UtilsBridge.isSpace(filePath)) return \"\";\n        int lastPoi = filePath.lastIndexOf('.');\n        int lastSep = filePath.lastIndexOf(File.separator);\n        if (lastPoi == -1 || lastSep >= lastPoi) return \"\";\n        return filePath.substring(lastPoi + 1);\n    }\n\n    /**\n     * Notify system to scan the file.\n     *\n     * @param filePath The path of file.\n     */\n    public static void notifySystemToScan(final String filePath) {\n        notifySystemToScan(getFileByPath(filePath));\n    }\n\n    /**\n     * Notify system to scan the file.\n     *\n     * @param file The file.\n     */\n    public static void notifySystemToScan(final File file) {\n        if (file == null || !file.exists()) return;\n        Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);\n        intent.setData(Uri.parse(\"file://\" + file.getAbsolutePath()));\n        Utils.getApp().sendBroadcast(intent);\n    }\n\n    /**\n     * Return the total size of file system.\n     *\n     * @param anyPathInFs Any path in file system.\n     * @return the total size of file system\n     */\n    public static long getFsTotalSize(String anyPathInFs) {\n        if (TextUtils.isEmpty(anyPathInFs)) return 0;\n        StatFs statFs = new StatFs(anyPathInFs);\n        long blockSize;\n        long totalSize;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\n            blockSize = statFs.getBlockSizeLong();\n            totalSize = statFs.getBlockCountLong();\n        } else {\n            blockSize = statFs.getBlockSize();\n            totalSize = statFs.getBlockCount();\n        }\n        return blockSize * totalSize;\n    }\n\n    /**\n     * Return the available size of file system.\n     *\n     * @param anyPathInFs Any path in file system.\n     * @return the available size of file system\n     */\n    public static long getFsAvailableSize(final String anyPathInFs) {\n        if (TextUtils.isEmpty(anyPathInFs)) return 0;\n        StatFs statFs = new StatFs(anyPathInFs);\n        long blockSize;\n        long availableSize;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\n            blockSize = statFs.getBlockSizeLong();\n            availableSize = statFs.getAvailableBlocksLong();\n        } else {\n            blockSize = statFs.getBlockSize();\n            availableSize = statFs.getAvailableBlocks();\n        }\n        return blockSize * availableSize;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // interface\n    ///////////////////////////////////////////////////////////////////////////\n\n    public interface OnReplaceListener {\n        boolean onReplace(File srcFile, File destFile);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/FlashlightUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.pm.PackageManager;\nimport android.graphics.SurfaceTexture;\nimport android.hardware.Camera;\nimport android.util.Log;\n\nimport java.io.IOException;\n\nimport static android.hardware.Camera.Parameters.FLASH_MODE_OFF;\nimport static android.hardware.Camera.Parameters.FLASH_MODE_TORCH;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/04/27\n *     desc  : utils about flashlight\n * </pre>\n */\npublic final class FlashlightUtils {\n\n    private static Camera         mCamera;\n    private static SurfaceTexture mSurfaceTexture;\n\n    private FlashlightUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return whether the device supports flashlight.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isFlashlightEnable() {\n        return Utils.getApp()\n                .getPackageManager()\n                .hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);\n    }\n\n    /**\n     * Return whether the flashlight is working.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isFlashlightOn() {\n        if (!init()) return false;\n        Camera.Parameters parameters = mCamera.getParameters();\n        return FLASH_MODE_TORCH.equals(parameters.getFlashMode());\n    }\n\n    /**\n     * Turn on or turn off the flashlight.\n     *\n     * @param isOn True to turn on the flashlight, false otherwise.\n     */\n    public static void setFlashlightStatus(final boolean isOn) {\n        if (!init()) return;\n        final Camera.Parameters parameters = mCamera.getParameters();\n        if (isOn) {\n            if (!FLASH_MODE_TORCH.equals(parameters.getFlashMode())) {\n                try {\n                    mCamera.setPreviewTexture(mSurfaceTexture);\n                    mCamera.startPreview();\n                    parameters.setFlashMode(FLASH_MODE_TORCH);\n                    mCamera.setParameters(parameters);\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        } else {\n            if (!FLASH_MODE_OFF.equals(parameters.getFlashMode())) {\n                parameters.setFlashMode(FLASH_MODE_OFF);\n                mCamera.setParameters(parameters);\n            }\n        }\n    }\n\n    /**\n     * Destroy the flashlight.\n     */\n    public static void destroy() {\n        if (mCamera == null) return;\n        mCamera.release();\n        mSurfaceTexture = null;\n        mCamera = null;\n    }\n\n    private static boolean init() {\n        if (mCamera == null) {\n            try {\n                mCamera = Camera.open(0);\n                mSurfaceTexture = new SurfaceTexture(0);\n            } catch (Throwable t) {\n                Log.e(\"FlashlightUtils\", \"init failed: \", t);\n                return false;\n            }\n        }\n        if (mCamera == null) {\n            Log.e(\"FlashlightUtils\", \"init failed.\");\n            return false;\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/FragmentUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.os.Bundle;\nimport androidx.annotation.AnimRes;\nimport androidx.annotation.AnimatorRes;\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.IdRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.fragment.app.FragmentTransaction;\n\nimport android.util.Log;\nimport android.view.View;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2017/01/17\n *     desc  : utils about fragment\n * </pre>\n */\npublic final class FragmentUtils {\n\n    private static final int TYPE_ADD_FRAGMENT       = 0x01;\n    private static final int TYPE_SHOW_FRAGMENT      = 0x01 << 1;\n    private static final int TYPE_HIDE_FRAGMENT      = 0x01 << 2;\n    private static final int TYPE_SHOW_HIDE_FRAGMENT = 0x01 << 3;\n    private static final int TYPE_REPLACE_FRAGMENT   = 0x01 << 4;\n    private static final int TYPE_REMOVE_FRAGMENT    = 0x01 << 5;\n    private static final int TYPE_REMOVE_TO_FRAGMENT = 0x01 << 6;\n\n    private static final String ARGS_ID           = \"args_id\";\n    private static final String ARGS_IS_HIDE      = \"args_is_hide\";\n    private static final String ARGS_IS_ADD_STACK = \"args_is_add_stack\";\n    private static final String ARGS_TAG          = \"args_tag\";\n\n    private FragmentUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param add         The fragment will be add.\n     * @param containerId The id of container.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId) {\n        add(fm, add, containerId, null, false, false);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param add         The fragment will be add.\n     * @param containerId The id of container.\n     * @param isHide      True to hide, false otherwise.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           final boolean isHide) {\n        add(fm, add, containerId, null, isHide, false);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param add         The fragment will be add.\n     * @param containerId The id of container.\n     * @param isHide      True to hide, false otherwise.\n     * @param isAddStack  True to add fragment in stack, false otherwise.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           final boolean isHide,\n                           final boolean isAddStack) {\n        add(fm, add, containerId, null, isHide, isAddStack);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param add         The fragment will be add.\n     * @param containerId The id of container.\n     * @param enterAnim   An animation or animator resource ID used for the enter animation on the\n     *                    view of the fragment being added or attached.\n     * @param exitAnim    An animation or animator resource ID used for the exit animation on the\n     *                    view of the fragment being removed or detached.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           @AnimatorRes @AnimRes final int enterAnim,\n                           @AnimatorRes @AnimRes final int exitAnim) {\n        add(fm, add, containerId, null, false, enterAnim, exitAnim, 0, 0);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param containerId The id of container.\n     * @param add         The fragment will be add.\n     * @param isAddStack  True to add fragment in stack, false otherwise.\n     * @param enterAnim   An animation or animator resource ID used for the enter animation on the\n     *                    view of the fragment being added or attached.\n     * @param exitAnim    An animation or animator resource ID used for the exit animation on the\n     *                    view of the fragment being removed or detached.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           final boolean isAddStack,\n                           @AnimatorRes @AnimRes final int enterAnim,\n                           @AnimatorRes @AnimRes final int exitAnim) {\n        add(fm, add, containerId, null, isAddStack, enterAnim, exitAnim, 0, 0);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm           The manager of fragment.\n     * @param containerId  The id of container.\n     * @param add          The fragment will be add.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being readded or reattached caused by\n     *                     popBackStack() or similar methods.\n     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being removed or detached caused by\n     *                     popBackStack() or similar methods.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           @AnimatorRes @AnimRes final int enterAnim,\n                           @AnimatorRes @AnimRes final int exitAnim,\n                           @AnimatorRes @AnimRes final int popEnterAnim,\n                           @AnimatorRes @AnimRes final int popExitAnim) {\n        add(fm, add, containerId, null, false, enterAnim, exitAnim, popEnterAnim, popExitAnim);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm           The manager of fragment.\n     * @param containerId  The id of container.\n     * @param add          The fragment will be add.\n     * @param isAddStack   True to add fragment in stack, false otherwise.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being readded or reattached caused by\n     *                     popBackStack() or similar methods.\n     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being removed or detached caused by\n     *                     popBackStack() or similar methods.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           final boolean isAddStack,\n                           @AnimatorRes @AnimRes final int enterAnim,\n                           @AnimatorRes @AnimRes final int exitAnim,\n                           @AnimatorRes @AnimRes final int popEnterAnim,\n                           @AnimatorRes @AnimRes final int popExitAnim) {\n        add(fm, add, containerId, null, isAddStack, enterAnim, exitAnim, popEnterAnim, popExitAnim);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm             The manager of fragment.\n     * @param add            The fragment will be add.\n     * @param containerId    The id of container.\n     * @param sharedElements A View in a disappearing Fragment to match with a View in an\n     *                       appearing Fragment.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           @NonNull final View... sharedElements) {\n        add(fm, add, containerId, null, false, sharedElements);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm             The manager of fragment.\n     * @param add            The fragment will be add.\n     * @param containerId    The id of container.\n     * @param isAddStack     True to add fragment in stack, false otherwise.\n     * @param sharedElements A View in a disappearing Fragment to match with a View in an\n     *                       appearing Fragment.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           final boolean isAddStack,\n                           @NonNull final View... sharedElements) {\n        add(fm, add, containerId, null, isAddStack, sharedElements);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param adds        The fragments will be add.\n     * @param containerId The id of container.\n     * @param showIndex   The index of fragment will be shown.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final List<Fragment> adds,\n                           @IdRes final int containerId,\n                           final int showIndex) {\n        add(fm, adds.toArray(new Fragment[0]), containerId, null, showIndex);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param adds        The fragments will be add.\n     * @param containerId The id of container.\n     * @param showIndex   The index of fragment will be shown.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment[] adds,\n                           @IdRes final int containerId,\n                           final int showIndex) {\n        add(fm, adds, containerId, null, showIndex);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param add         The fragment will be add.\n     * @param containerId The id of container.\n     * @param tag         The tag of fragment.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           final String tag) {\n        add(fm, add, containerId, tag, false, false);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param add         The fragment will be add.\n     * @param containerId The id of container.\n     * @param tag         The tag of fragment.\n     * @param isHide      True to hide, false otherwise.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           final String tag,\n                           final boolean isHide) {\n        add(fm, add, containerId, tag, isHide, false);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param add         The fragment will be add.\n     * @param containerId The id of container.\n     * @param tag         The tag of fragment.\n     * @param isHide      True to hide, false otherwise.\n     * @param isAddStack  True to add fragment in stack, false otherwise.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           final String tag,\n                           final boolean isHide,\n                           final boolean isAddStack) {\n        putArgs(add, new Args(containerId, tag, isHide, isAddStack));\n        operateNoAnim(TYPE_ADD_FRAGMENT, fm, null, add);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param add         The fragment will be add.\n     * @param containerId The id of container.\n     * @param tag         The tag of fragment.\n     * @param enterAnim   An animation or animator resource ID used for the enter animation on the\n     *                    view of the fragment being added or attached.\n     * @param exitAnim    An animation or animator resource ID used for the exit animation on the\n     *                    view of the fragment being removed or detached.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           final String tag,\n                           @AnimatorRes @AnimRes final int enterAnim,\n                           @AnimatorRes @AnimRes final int exitAnim) {\n        add(fm, add, containerId, tag, false, enterAnim, exitAnim, 0, 0);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param add         The fragment will be add.\n     * @param containerId The id of container.\n     * @param tag         The tag of fragment.\n     * @param isAddStack  True to add fragment in stack, false otherwise.\n     * @param enterAnim   An animation or animator resource ID used for the enter animation on the\n     *                    view of the fragment being added or attached.\n     * @param exitAnim    An animation or animator resource ID used for the exit animation on the\n     *                    view of the fragment being removed or detached.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           final String tag,\n                           final boolean isAddStack,\n                           @AnimatorRes @AnimRes final int enterAnim,\n                           @AnimatorRes @AnimRes final int exitAnim) {\n        add(fm, add, containerId, tag, isAddStack, enterAnim, exitAnim, 0, 0);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm           The manager of fragment.\n     * @param add          The fragment will be add.\n     * @param containerId  The id of container.\n     * @param tag          The tag of fragment.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being readded or reattached caused by\n     *                     popBackStack() or similar methods.\n     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being removed or detached caused by\n     *                     popBackStack() or similar methods.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           final String tag,\n                           @AnimatorRes @AnimRes final int enterAnim,\n                           @AnimatorRes @AnimRes final int exitAnim,\n                           @AnimatorRes @AnimRes final int popEnterAnim,\n                           @AnimatorRes @AnimRes final int popExitAnim) {\n        add(fm, add, containerId, tag, false, enterAnim, exitAnim, popEnterAnim, popExitAnim);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm           The manager of fragment.\n     * @param add          The fragment will be add.\n     * @param containerId  The id of container.\n     * @param tag          The tag of fragment.\n     * @param isAddStack   True to add fragment in stack, false otherwise.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being readded or reattached caused by\n     *                     popBackStack() or similar methods.\n     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being removed or detached caused by\n     *                     popBackStack() or similar methods.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           final String tag,\n                           final boolean isAddStack,\n                           @AnimatorRes @AnimRes final int enterAnim,\n                           @AnimatorRes @AnimRes final int exitAnim,\n                           @AnimatorRes @AnimRes final int popEnterAnim,\n                           @AnimatorRes @AnimRes final int popExitAnim) {\n        FragmentTransaction ft = fm.beginTransaction();\n        putArgs(add, new Args(containerId, tag, false, isAddStack));\n        addAnim(ft, enterAnim, exitAnim, popEnterAnim, popExitAnim);\n        operate(TYPE_ADD_FRAGMENT, fm, ft, null, add);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm             The manager of fragment.\n     * @param add            The fragment will be add.\n     * @param tag            The tag of fragment.\n     * @param containerId    The id of container.\n     * @param sharedElements A View in a disappearing Fragment to match with a View in an\n     *                       appearing Fragment.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           final String tag,\n                           @NonNull final View... sharedElements) {\n        add(fm, add, containerId, tag, false, sharedElements);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm             The manager of fragment.\n     * @param add            The fragment will be add.\n     * @param containerId    The id of container.\n     * @param isAddStack     True to add fragment in stack, false otherwise.\n     * @param sharedElements A View in a disappearing Fragment to match with a View in an\n     *                       appearing Fragment.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment add,\n                           @IdRes final int containerId,\n                           final String tag,\n                           final boolean isAddStack,\n                           @NonNull final View... sharedElements) {\n        FragmentTransaction ft = fm.beginTransaction();\n        putArgs(add, new Args(containerId, tag, false, isAddStack));\n        addSharedElement(ft, sharedElements);\n        operate(TYPE_ADD_FRAGMENT, fm, ft, null, add);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param adds        The fragments will be add.\n     * @param containerId The id of container.\n     * @param showIndex   The index of fragment will be shown.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final List<Fragment> adds,\n                           @IdRes final int containerId,\n                           final String[] tags,\n                           final int showIndex) {\n        add(fm, adds.toArray(new Fragment[0]), containerId, tags, showIndex);\n    }\n\n    /**\n     * Add fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param adds        The fragments will be add.\n     * @param containerId The id of container.\n     * @param showIndex   The index of fragment will be shown.\n     */\n    public static void add(@NonNull final FragmentManager fm,\n                           @NonNull final Fragment[] adds,\n                           @IdRes final int containerId,\n                           final String[] tags,\n                           final int showIndex) {\n        if (tags == null) {\n            for (int i = 0, len = adds.length; i < len; ++i) {\n                putArgs(adds[i], new Args(containerId, null, showIndex != i, false));\n            }\n        } else {\n            for (int i = 0, len = adds.length; i < len; ++i) {\n                putArgs(adds[i], new Args(containerId, tags[i], showIndex != i, false));\n            }\n        }\n        operateNoAnim(TYPE_ADD_FRAGMENT, fm, null, adds);\n    }\n\n    /**\n     * Show fragment.\n     *\n     * @param show The fragment will be show.\n     */\n    public static void show(@NonNull final Fragment show) {\n        putArgs(show, false);\n        operateNoAnim(TYPE_SHOW_FRAGMENT, show.getFragmentManager(), null, show);\n    }\n\n    /**\n     * Show fragment.\n     *\n     * @param fm The manager of fragment.\n     */\n    public static void show(@NonNull final FragmentManager fm) {\n        List<Fragment> fragments = getFragments(fm);\n        for (Fragment show : fragments) {\n            putArgs(show, false);\n        }\n        operateNoAnim(TYPE_SHOW_FRAGMENT, fm, null, fragments.toArray(new Fragment[0]));\n    }\n\n    /**\n     * Hide fragment.\n     *\n     * @param hide The fragment will be hide.\n     */\n    public static void hide(@NonNull final Fragment hide) {\n        putArgs(hide, true);\n        operateNoAnim(TYPE_HIDE_FRAGMENT, hide.getFragmentManager(), null, hide);\n    }\n\n    /**\n     * Hide fragment.\n     *\n     * @param fm The manager of fragment.\n     */\n    public static void hide(@NonNull final FragmentManager fm) {\n        List<Fragment> fragments = getFragments(fm);\n        for (Fragment hide : fragments) {\n            putArgs(hide, true);\n        }\n        operateNoAnim(TYPE_HIDE_FRAGMENT, fm, null, fragments.toArray(new Fragment[0]));\n    }\n\n    /**\n     * Show fragment then hide other fragment.\n     *\n     * @param show The fragment will be show.\n     * @param hide The fragment will be hide.\n     */\n    public static void showHide(@NonNull final Fragment show,\n                                @NonNull final Fragment hide) {\n        showHide(show, Collections.singletonList(hide));\n    }\n\n    /**\n     * Show fragment then hide other fragment.\n     *\n     * @param showIndex The index of fragment will be shown.\n     * @param fragments The fragment will be hide.\n     */\n    public static void showHide(final int showIndex, @NonNull final Fragment... fragments) {\n        showHide(fragments[showIndex], fragments);\n    }\n\n    /**\n     * Show fragment then hide other fragment.\n     *\n     * @param show The fragment will be show.\n     * @param hide The fragment will be hide.\n     */\n    public static void showHide(@NonNull final Fragment show, @NonNull final Fragment... hide) {\n        showHide(show, Arrays.asList(hide));\n    }\n\n    /**\n     * Show fragment then hide other fragment.\n     *\n     * @param showIndex The index of fragment will be shown.\n     * @param fragments The fragments will be hide.\n     */\n    public static void showHide(final int showIndex, @NonNull final List<Fragment> fragments) {\n        showHide(fragments.get(showIndex), fragments);\n    }\n\n    /**\n     * Show fragment then hide other fragment.\n     *\n     * @param show The fragment will be show.\n     * @param hide The fragment will be hide.\n     */\n    public static void showHide(@NonNull final Fragment show, @NonNull final List<Fragment> hide) {\n        for (Fragment fragment : hide) {\n            putArgs(fragment, fragment != show);\n        }\n        operateNoAnim(TYPE_SHOW_HIDE_FRAGMENT, show.getFragmentManager(), show, hide.toArray(new Fragment[0]));\n    }\n\n\n    /**\n     * Show fragment then hide other fragment.\n     *\n     * @param show The fragment will be show.\n     * @param hide The fragment will be hide.\n     */\n    public static void showHide(@NonNull final Fragment show,\n                                @NonNull final Fragment hide, @AnimatorRes @AnimRes final int enterAnim,\n                                @AnimatorRes @AnimRes final int exitAnim,\n                                @AnimatorRes @AnimRes final int popEnterAnim,\n                                @AnimatorRes @AnimRes final int popExitAnim) {\n        showHide(show, Collections.singletonList(hide), enterAnim, exitAnim, popEnterAnim, popExitAnim);\n    }\n\n    /**\n     * Show fragment then hide other fragment.\n     *\n     * @param showIndex The index of fragment will be shown.\n     * @param fragments The fragments will be hide.\n     */\n    public static void showHide(final int showIndex, @NonNull final List<Fragment> fragments,\n                                @AnimatorRes @AnimRes final int enterAnim,\n                                @AnimatorRes @AnimRes final int exitAnim,\n                                @AnimatorRes @AnimRes final int popEnterAnim,\n                                @AnimatorRes @AnimRes final int popExitAnim) {\n        showHide(fragments.get(showIndex), fragments, enterAnim, exitAnim, popEnterAnim, popExitAnim);\n    }\n\n    /**\n     * Show fragment then hide other fragment.\n     *\n     * @param show The fragment will be show.\n     * @param hide The fragment will be hide.\n     */\n    public static void showHide(@NonNull final Fragment show, @NonNull final List<Fragment> hide,\n                                @AnimatorRes @AnimRes final int enterAnim,\n                                @AnimatorRes @AnimRes final int exitAnim,\n                                @AnimatorRes @AnimRes final int popEnterAnim,\n                                @AnimatorRes @AnimRes final int popExitAnim) {\n        for (Fragment fragment : hide) {\n            putArgs(fragment, fragment != show);\n        }\n        FragmentManager fm = show.getFragmentManager();\n        if (fm != null) {\n            FragmentTransaction ft = fm.beginTransaction();\n            addAnim(ft, enterAnim, exitAnim, popEnterAnim, popExitAnim);\n            operate(TYPE_SHOW_HIDE_FRAGMENT, fm, ft, show, hide.toArray(new Fragment[0]));\n        }\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment  The source of fragment.\n     * @param destFragment The destination of fragment.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment) {\n        replace(srcFragment, destFragment, null, false);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment  The source of fragment.\n     * @param destFragment The destination of fragment.\n     * @param isAddStack   True to add fragment in stack, false otherwise.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               final boolean isAddStack) {\n        replace(srcFragment, destFragment, null, isAddStack);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment  The source of fragment.\n     * @param destFragment The destination of fragment.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim) {\n        replace(srcFragment, destFragment, null, false, enterAnim, exitAnim, 0, 0);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment  The source of fragment.\n     * @param destFragment The destination of fragment.\n     * @param isAddStack   True to add fragment in stack, false otherwise.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               final boolean isAddStack,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim) {\n        replace(srcFragment, destFragment, null, isAddStack, enterAnim, exitAnim, 0, 0);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment  The source of fragment.\n     * @param destFragment The destination of fragment.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being readded or reattached caused by\n     *                     popBackStack() or similar methods.\n     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being removed or detached caused by\n     *                     popBackStack() or similar methods.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim,\n                               @AnimatorRes @AnimRes final int popEnterAnim,\n                               @AnimatorRes @AnimRes final int popExitAnim) {\n        replace(srcFragment, destFragment, null, false,\n                enterAnim, exitAnim, popEnterAnim, popExitAnim);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment  The source of fragment.\n     * @param destFragment The destination of fragment.\n     * @param isAddStack   True to add fragment in stack, false otherwise.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being readded or reattached caused by\n     *                     popBackStack() or similar methods.\n     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being removed or detached caused by\n     *                     popBackStack() or similar methods.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               final boolean isAddStack,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim,\n                               @AnimatorRes @AnimRes final int popEnterAnim,\n                               @AnimatorRes @AnimRes final int popExitAnim) {\n        replace(srcFragment, destFragment, null, isAddStack,\n                enterAnim, exitAnim, popEnterAnim, popExitAnim);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment    The source of fragment.\n     * @param destFragment   The destination of fragment.\n     * @param sharedElements A View in a disappearing Fragment to match with a View in an\n     *                       appearing Fragment.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               final View... sharedElements) {\n        replace(srcFragment, destFragment, null, false, sharedElements);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment    The source of fragment.\n     * @param destFragment   The destination of fragment.\n     * @param isAddStack     True to add fragment in stack, false otherwise.\n     * @param sharedElements A View in a disappearing Fragment to match with a View in an\n     *                       appearing Fragment.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               final boolean isAddStack,\n                               final View... sharedElements) {\n        replace(srcFragment, destFragment, null, isAddStack, sharedElements);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param fragment    The new fragment to place in the container.\n     * @param containerId The id of container.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId) {\n        replace(fm, fragment, containerId, null, false);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param containerId The id of container.\n     * @param fragment    The new fragment to place in the container.\n     * @param isAddStack  True to add fragment in stack, false otherwise.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               final boolean isAddStack) {\n        replace(fm, fragment, containerId, null, isAddStack);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param containerId The id of container.\n     * @param fragment    The new fragment to place in the container.\n     * @param enterAnim   An animation or animator resource ID used for the enter animation on the\n     *                    view of the fragment being added or attached.\n     * @param exitAnim    An animation or animator resource ID used for the exit animation on the\n     *                    view of the fragment being removed or detached.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim) {\n        replace(fm, fragment, containerId, null, false, enterAnim, exitAnim, 0, 0);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param containerId The id of container.\n     * @param fragment    The new fragment to place in the container.\n     * @param isAddStack  True to add fragment in stack, false otherwise.\n     * @param enterAnim   An animation or animator resource ID used for the enter animation on the\n     *                    view of the fragment being added or attached.\n     * @param exitAnim    An animation or animator resource ID used for the exit animation on the\n     *                    view of the fragment being removed or detached.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               final boolean isAddStack,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim) {\n        replace(fm, fragment, containerId, null, isAddStack, enterAnim, exitAnim, 0, 0);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm           The manager of fragment.\n     * @param containerId  The id of container.\n     * @param fragment     The new fragment to place in the container.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being readded or reattached caused by\n     *                     popBackStack() or similar methods.\n     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being removed or detached caused by\n     *                     popBackStack() or similar methods.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim,\n                               @AnimatorRes @AnimRes final int popEnterAnim,\n                               @AnimatorRes @AnimRes final int popExitAnim) {\n        replace(fm, fragment, containerId, null, false,\n                enterAnim, exitAnim, popEnterAnim, popExitAnim);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm           The manager of fragment.\n     * @param containerId  The id of container.\n     * @param fragment     The new fragment to place in the container.\n     * @param isAddStack   True to add fragment in stack, false otherwise.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being readded or reattached caused by\n     *                     popBackStack() or similar methods.\n     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being removed or detached caused by\n     *                     popBackStack() or similar methods.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               final boolean isAddStack,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim,\n                               @AnimatorRes @AnimRes final int popEnterAnim,\n                               @AnimatorRes @AnimRes final int popExitAnim) {\n        replace(fm, fragment, containerId, null, isAddStack,\n                enterAnim, exitAnim, popEnterAnim, popExitAnim);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm             The manager of fragment.\n     * @param containerId    The id of container.\n     * @param fragment       The new fragment to place in the container.\n     * @param sharedElements A View in a disappearing Fragment to match with a View in an\n     *                       appearing Fragment.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               final View... sharedElements) {\n        replace(fm, fragment, containerId, null, false, sharedElements);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm             The manager of fragment.\n     * @param containerId    The id of container.\n     * @param fragment       The new fragment to place in the container.\n     * @param isAddStack     True to add fragment in stack, false otherwise.\n     * @param sharedElements A View in a disappearing Fragment to match with a View in an\n     *                       appearing Fragment.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               final boolean isAddStack,\n                               final View... sharedElements) {\n        replace(fm, fragment, containerId, null, isAddStack, sharedElements);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment  The source of fragment.\n     * @param destFragment The destination of fragment.\n     * @param destTag      The destination of fragment's tag.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               final String destTag) {\n        replace(srcFragment, destFragment, destTag, false);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment  The source of fragment.\n     * @param destFragment The destination of fragment.\n     * @param destTag      The destination of fragment's tag.\n     * @param isAddStack   True to add fragment in stack, false otherwise.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               final String destTag,\n                               final boolean isAddStack) {\n        FragmentManager fm = srcFragment.getFragmentManager();\n        if (fm == null) return;\n        Args args = getArgs(srcFragment);\n        replace(fm, destFragment, args.id, destTag, isAddStack);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment  The source of fragment.\n     * @param destFragment The destination of fragment.\n     * @param destTag      The destination of fragment's tag.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               final String destTag,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim) {\n        replace(srcFragment, destFragment, destTag, false, enterAnim, exitAnim, 0, 0);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment  The source of fragment.\n     * @param destFragment The destination of fragment.\n     * @param destTag      The destination of fragment's tag.\n     * @param isAddStack   True to add fragment in stack, false otherwise.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               final String destTag,\n                               final boolean isAddStack,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim) {\n        replace(srcFragment, destFragment, destTag, isAddStack, enterAnim, exitAnim, 0, 0);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment  The source of fragment.\n     * @param destFragment The destination of fragment.\n     * @param destTag      The destination of fragment's tag.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being readded or reattached caused by\n     *                     popBackStack() or similar methods.\n     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being removed or detached caused by\n     *                     popBackStack() or similar methods.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               final String destTag,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim,\n                               @AnimatorRes @AnimRes final int popEnterAnim,\n                               @AnimatorRes @AnimRes final int popExitAnim) {\n        replace(srcFragment, destFragment, destTag, false,\n                enterAnim, exitAnim, popEnterAnim, popExitAnim);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment  The source of fragment.\n     * @param destFragment The destination of fragment.\n     * @param destTag      The destination of fragment's tag.\n     * @param isAddStack   True to add fragment in stack, false otherwise.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being readded or reattached caused by\n     *                     popBackStack() or similar methods.\n     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being removed or detached caused by\n     *                     popBackStack() or similar methods.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               final String destTag,\n                               final boolean isAddStack,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim,\n                               @AnimatorRes @AnimRes final int popEnterAnim,\n                               @AnimatorRes @AnimRes final int popExitAnim) {\n        FragmentManager fm = srcFragment.getFragmentManager();\n        if (fm == null) return;\n        Args args = getArgs(srcFragment);\n        replace(fm, destFragment, args.id, destTag, isAddStack,\n                enterAnim, exitAnim, popEnterAnim, popExitAnim);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment    The source of fragment.\n     * @param destFragment   The destination of fragment.\n     * @param destTag        The destination of fragment's tag.\n     * @param sharedElements A View in a disappearing Fragment to match with a View in an\n     *                       appearing Fragment.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               final String destTag,\n                               final View... sharedElements) {\n        replace(srcFragment, destFragment, destTag, false, sharedElements);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param srcFragment    The source of fragment.\n     * @param destFragment   The destination of fragment.\n     * @param destTag        The destination of fragment's tag.\n     * @param isAddStack     True to add fragment in stack, false otherwise.\n     * @param sharedElements A View in a disappearing Fragment to match with a View in an\n     *                       appearing Fragment.\n     */\n    public static void replace(@NonNull final Fragment srcFragment,\n                               @NonNull final Fragment destFragment,\n                               final String destTag,\n                               final boolean isAddStack,\n                               final View... sharedElements) {\n        FragmentManager fm = srcFragment.getFragmentManager();\n        if (fm == null) return;\n        Args args = getArgs(srcFragment);\n        replace(fm,\n                destFragment,\n                args.id,\n                destTag,\n                isAddStack,\n                sharedElements\n        );\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param fragment    The new fragment to place in the container.\n     * @param containerId The id of container.\n     * @param destTag     The destination of fragment's tag.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               final String destTag) {\n        replace(fm, fragment, containerId, destTag, false);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param fragment    The new fragment to place in the container.\n     * @param containerId The id of container.\n     * @param destTag     The destination of fragment's tag.\n     * @param isAddStack  True to add fragment in stack, false otherwise.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               final String destTag,\n                               final boolean isAddStack) {\n        FragmentTransaction ft = fm.beginTransaction();\n        putArgs(fragment, new Args(containerId, destTag, false, isAddStack));\n        operate(TYPE_REPLACE_FRAGMENT, fm, ft, null, fragment);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param fragment    The new fragment to place in the container.\n     * @param containerId The id of container.\n     * @param destTag     The destination of fragment's tag.\n     * @param enterAnim   An animation or animator resource ID used for the enter animation on the\n     *                    view of the fragment being added or attached.\n     * @param exitAnim    An animation or animator resource ID used for the exit animation on the\n     *                    view of the fragment being removed or detached.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               final String destTag,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim) {\n        replace(fm, fragment, containerId, destTag, false, enterAnim, exitAnim, 0, 0);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param fragment    The new fragment to place in the container.\n     * @param containerId The id of container.\n     * @param destTag     The destination of fragment's tag.\n     * @param isAddStack  True to add fragment in stack, false otherwise.\n     * @param enterAnim   An animation or animator resource ID used for the enter animation on the\n     *                    view of the fragment being added or attached.\n     * @param exitAnim    An animation or animator resource ID used for the exit animation on the\n     *                    view of the fragment being removed or detached.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               final String destTag,\n                               final boolean isAddStack,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim) {\n        replace(fm, fragment, containerId, destTag, isAddStack, enterAnim, exitAnim, 0, 0);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm           The manager of fragment.\n     * @param fragment     The new fragment to place in the container.\n     * @param containerId  The id of container.\n     * @param destTag      The destination of fragment's tag.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being readded or reattached caused by\n     *                     popBackStack() or similar methods.\n     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being removed or detached caused by\n     *                     popBackStack() or similar methods.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               final String destTag,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim,\n                               @AnimatorRes @AnimRes final int popEnterAnim,\n                               @AnimatorRes @AnimRes final int popExitAnim) {\n        replace(fm, fragment, containerId, destTag, false,\n                enterAnim, exitAnim, popEnterAnim, popExitAnim);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm           The manager of fragment.\n     * @param fragment     The new fragment to place in the container.\n     * @param containerId  The id of container.\n     * @param destTag      The destination of fragment's tag.\n     * @param isAddStack   True to add fragment in stack, false otherwise.\n     * @param enterAnim    An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being added or attached.\n     * @param exitAnim     An animation or animator resource ID used for the exit animation on the\n     *                     view of the fragment being removed or detached.\n     * @param popEnterAnim An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being readded or reattached caused by\n     *                     popBackStack() or similar methods.\n     * @param popExitAnim  An animation or animator resource ID used for the enter animation on the\n     *                     view of the fragment being removed or detached caused by\n     *                     popBackStack() or similar methods.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               final String destTag,\n                               final boolean isAddStack,\n                               @AnimatorRes @AnimRes final int enterAnim,\n                               @AnimatorRes @AnimRes final int exitAnim,\n                               @AnimatorRes @AnimRes final int popEnterAnim,\n                               @AnimatorRes @AnimRes final int popExitAnim) {\n        FragmentTransaction ft = fm.beginTransaction();\n        putArgs(fragment, new Args(containerId, destTag, false, isAddStack));\n        addAnim(ft, enterAnim, exitAnim, popEnterAnim, popExitAnim);\n        operate(TYPE_REPLACE_FRAGMENT, fm, ft, null, fragment);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm             The manager of fragment.\n     * @param fragment       The new fragment to place in the container.\n     * @param containerId    The id of container.\n     * @param destTag        The destination of fragment's tag.\n     * @param sharedElements A View in a disappearing Fragment to match with a View in an\n     *                       appearing Fragment.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               final String destTag,\n                               final View... sharedElements) {\n        replace(fm, fragment, containerId, destTag, false, sharedElements);\n    }\n\n    /**\n     * Replace fragment.\n     *\n     * @param fm             The manager of fragment.\n     * @param fragment       The new fragment to place in the container.\n     * @param containerId    The id of container.\n     * @param destTag        The destination of fragment's tag.\n     * @param isAddStack     True to add fragment in stack, false otherwise.\n     * @param sharedElements A View in a disappearing Fragment to match with a View in an\n     *                       appearing Fragment.\n     */\n    public static void replace(@NonNull final FragmentManager fm,\n                               @NonNull final Fragment fragment,\n                               @IdRes final int containerId,\n                               final String destTag,\n                               final boolean isAddStack,\n                               final View... sharedElements) {\n        FragmentTransaction ft = fm.beginTransaction();\n        putArgs(fragment, new Args(containerId, destTag, false, isAddStack));\n        addSharedElement(ft, sharedElements);\n        operate(TYPE_REPLACE_FRAGMENT, fm, ft, null, fragment);\n    }\n\n    /**\n     * Pop fragment.\n     *\n     * @param fm The manager of fragment.\n     */\n    public static void pop(@NonNull final FragmentManager fm) {\n        pop(fm, true);\n    }\n\n    /**\n     * Pop fragment.\n     *\n     * @param fm          The manager of fragment.\n     * @param isImmediate True to pop immediately, false otherwise.\n     */\n    public static void pop(@NonNull final FragmentManager fm,\n                           final boolean isImmediate) {\n        if (isImmediate) {\n            fm.popBackStackImmediate();\n        } else {\n            fm.popBackStack();\n        }\n    }\n\n    /**\n     * Pop to fragment.\n     *\n     * @param fm            The manager of fragment.\n     * @param popClz        The class of fragment will be popped to.\n     * @param isIncludeSelf True to include the fragment, false otherwise.\n     */\n    public static void popTo(@NonNull final FragmentManager fm,\n                             final Class<? extends Fragment> popClz,\n                             final boolean isIncludeSelf) {\n        popTo(fm, popClz, isIncludeSelf, true);\n    }\n\n    /**\n     * Pop to fragment.\n     *\n     * @param fm            The manager of fragment.\n     * @param popClz        The class of fragment will be popped to.\n     * @param isIncludeSelf True to include the fragment, false otherwise.\n     * @param isImmediate   True to pop immediately, false otherwise.\n     */\n    public static void popTo(@NonNull final FragmentManager fm,\n                             final Class<? extends Fragment> popClz,\n                             final boolean isIncludeSelf,\n                             final boolean isImmediate) {\n        if (isImmediate) {\n            fm.popBackStackImmediate(popClz.getName(),\n                    isIncludeSelf ? FragmentManager.POP_BACK_STACK_INCLUSIVE : 0);\n        } else {\n            fm.popBackStack(popClz.getName(),\n                    isIncludeSelf ? FragmentManager.POP_BACK_STACK_INCLUSIVE : 0);\n        }\n    }\n\n    /**\n     * Pop all fragments.\n     *\n     * @param fm The manager of fragment.\n     */\n    public static void popAll(@NonNull final FragmentManager fm) {\n        popAll(fm, true);\n    }\n\n    /**\n     * Pop all fragments.\n     *\n     * @param fm The manager of fragment.\n     */\n    public static void popAll(@NonNull final FragmentManager fm, final boolean isImmediate) {\n        if (fm.getBackStackEntryCount() > 0) {\n            FragmentManager.BackStackEntry entry = fm.getBackStackEntryAt(0);\n            if (isImmediate) {\n                fm.popBackStackImmediate(entry.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);\n            } else {\n                fm.popBackStack(entry.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);\n            }\n        }\n    }\n\n    /**\n     * Remove fragment.\n     *\n     * @param remove The fragment will be removed.\n     */\n    public static void remove(@NonNull final Fragment remove) {\n        operateNoAnim(TYPE_REMOVE_FRAGMENT, remove.getFragmentManager(), null, remove);\n    }\n\n    /**\n     * Remove to fragment.\n     *\n     * @param removeTo      The fragment will be removed to.\n     * @param isIncludeSelf True to include the fragment, false otherwise.\n     */\n    public static void removeTo(@NonNull final Fragment removeTo, final boolean isIncludeSelf) {\n        operateNoAnim(TYPE_REMOVE_TO_FRAGMENT, removeTo.getFragmentManager(), isIncludeSelf ? removeTo : null, removeTo);\n    }\n\n    /**\n     * Remove all fragments.\n     *\n     * @param fm The manager of fragment.\n     */\n    public static void removeAll(@NonNull final FragmentManager fm) {\n        List<Fragment> fragments = getFragments(fm);\n        operateNoAnim(TYPE_REMOVE_FRAGMENT, fm, null, fragments.toArray(new Fragment[0]));\n    }\n\n    private static void putArgs(final Fragment fragment, final Args args) {\n        Bundle bundle = fragment.getArguments();\n        if (bundle == null) {\n            bundle = new Bundle();\n            fragment.setArguments(bundle);\n        }\n        bundle.putInt(ARGS_ID, args.id);\n        bundle.putBoolean(ARGS_IS_HIDE, args.isHide);\n        bundle.putBoolean(ARGS_IS_ADD_STACK, args.isAddStack);\n        bundle.putString(ARGS_TAG, args.tag);\n    }\n\n    private static void putArgs(final Fragment fragment, final boolean isHide) {\n        Bundle bundle = fragment.getArguments();\n        if (bundle == null) {\n            bundle = new Bundle();\n            fragment.setArguments(bundle);\n        }\n        bundle.putBoolean(ARGS_IS_HIDE, isHide);\n    }\n\n    private static Args getArgs(final Fragment fragment) {\n        Bundle bundle = fragment.getArguments();\n        if (bundle == null) bundle = Bundle.EMPTY;\n        return new Args(bundle.getInt(ARGS_ID, fragment.getId()),\n                bundle.getBoolean(ARGS_IS_HIDE),\n                bundle.getBoolean(ARGS_IS_ADD_STACK));\n    }\n\n    private static void operateNoAnim(final int type, @Nullable final FragmentManager fm,\n                                      final Fragment src,\n                                      Fragment... dest) {\n        if (fm == null) return;\n        FragmentTransaction ft = fm.beginTransaction();\n        operate(type, fm, ft, src, dest);\n    }\n\n    private static void operate(final int type,\n                                @NonNull final FragmentManager fm,\n                                final FragmentTransaction ft,\n                                final Fragment src,\n                                final Fragment... dest) {\n        if (src != null && src.isRemoving()) {\n            Log.e(\"FragmentUtils\", src.getClass().getName() + \" is isRemoving\");\n            return;\n        }\n        String name;\n        Bundle args;\n        switch (type) {\n            case TYPE_ADD_FRAGMENT:\n                for (Fragment fragment : dest) {\n                    args = fragment.getArguments();\n                    if (args == null) return;\n                    name = args.getString(ARGS_TAG, fragment.getClass().getName());\n                    Fragment fragmentByTag = fm.findFragmentByTag(name);\n                    if (fragmentByTag != null && fragmentByTag.isAdded()) {\n                        ft.remove(fragmentByTag);\n                    }\n                    ft.add(args.getInt(ARGS_ID), fragment, name);\n                    if (args.getBoolean(ARGS_IS_HIDE)) ft.hide(fragment);\n                    if (args.getBoolean(ARGS_IS_ADD_STACK)) ft.addToBackStack(name);\n                }\n                break;\n            case TYPE_HIDE_FRAGMENT:\n                for (Fragment fragment : dest) {\n                    ft.hide(fragment);\n                }\n                break;\n            case TYPE_SHOW_FRAGMENT:\n                for (Fragment fragment : dest) {\n                    ft.show(fragment);\n                }\n                break;\n            case TYPE_SHOW_HIDE_FRAGMENT:\n                ft.show(src);\n                for (Fragment fragment : dest) {\n                    if (fragment != src) {\n                        ft.hide(fragment);\n                    }\n                }\n                break;\n            case TYPE_REPLACE_FRAGMENT:\n                args = dest[0].getArguments();\n                if (args == null) return;\n                name = args.getString(ARGS_TAG, dest[0].getClass().getName());\n                ft.replace(args.getInt(ARGS_ID), dest[0], name);\n                if (args.getBoolean(ARGS_IS_ADD_STACK)) ft.addToBackStack(name);\n                break;\n            case TYPE_REMOVE_FRAGMENT:\n                for (Fragment fragment : dest) {\n                    if (fragment != src) {\n                        ft.remove(fragment);\n                    }\n                }\n                break;\n            case TYPE_REMOVE_TO_FRAGMENT:\n                for (int i = dest.length - 1; i >= 0; --i) {\n                    Fragment fragment = dest[i];\n                    if (fragment == dest[0]) {\n                        if (src != null) ft.remove(fragment);\n                        break;\n                    }\n                    ft.remove(fragment);\n                }\n                break;\n        }\n        ft.commitAllowingStateLoss();\n        fm.executePendingTransactions();\n    }\n\n    private static void addAnim(final FragmentTransaction ft,\n                                final int enter,\n                                final int exit,\n                                final int popEnter,\n                                final int popExit) {\n        ft.setCustomAnimations(enter, exit, popEnter, popExit);\n    }\n\n    private static void addSharedElement(final FragmentTransaction ft,\n                                         final View... views) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            for (View view : views) {\n                ft.addSharedElement(view, view.getTransitionName());\n            }\n        }\n    }\n\n    /**\n     * Return the top fragment.\n     *\n     * @param fm The manager of fragment.\n     * @return the top fragment\n     */\n    public static Fragment getTop(@NonNull final FragmentManager fm) {\n        return getTopIsInStack(fm, null, false);\n    }\n\n    /**\n     * Return the top fragment in stack.\n     *\n     * @param fm The manager of fragment.\n     * @return the top fragment in stack\n     */\n    public static Fragment getTopInStack(@NonNull final FragmentManager fm) {\n        return getTopIsInStack(fm, null, true);\n    }\n\n    private static Fragment getTopIsInStack(@NonNull final FragmentManager fm,\n                                            Fragment parentFragment,\n                                            final boolean isInStack) {\n        List<Fragment> fragments = getFragments(fm);\n        for (int i = fragments.size() - 1; i >= 0; --i) {\n            Fragment fragment = fragments.get(i);\n            if (fragment != null) {\n                if (isInStack) {\n                    Bundle args = fragment.getArguments();\n                    if (args != null && args.getBoolean(ARGS_IS_ADD_STACK)) {\n                        return getTopIsInStack(fragment.getChildFragmentManager(), fragment, true);\n                    }\n                } else {\n                    return getTopIsInStack(fragment.getChildFragmentManager(), fragment, false);\n                }\n            }\n        }\n        return parentFragment;\n    }\n\n    /**\n     * Return the top fragment which is shown.\n     *\n     * @param fm The manager of fragment.\n     * @return the top fragment which is shown\n     */\n    public static Fragment getTopShow(@NonNull final FragmentManager fm) {\n        return getTopShowIsInStack(fm, null, false);\n    }\n\n    /**\n     * Return the top fragment which is shown in stack.\n     *\n     * @param fm The manager of fragment.\n     * @return the top fragment which is shown in stack\n     */\n    public static Fragment getTopShowInStack(@NonNull final FragmentManager fm) {\n        return getTopShowIsInStack(fm, null, true);\n    }\n\n    private static Fragment getTopShowIsInStack(@NonNull final FragmentManager fm,\n                                                Fragment parentFragment,\n                                                final boolean isInStack) {\n        List<Fragment> fragments = getFragments(fm);\n        for (int i = fragments.size() - 1; i >= 0; --i) {\n            Fragment fragment = fragments.get(i);\n            if (fragment != null\n                    && fragment.isResumed()\n                    && fragment.isVisible()\n                    && fragment.getUserVisibleHint()) {\n                if (isInStack) {\n                    Bundle args = fragment.getArguments();\n                    if (args != null && args.getBoolean(ARGS_IS_ADD_STACK)) {\n                        return getTopShowIsInStack(fragment.getChildFragmentManager(), fragment, true);\n                    }\n                } else {\n                    return getTopShowIsInStack(fragment.getChildFragmentManager(), fragment, false);\n                }\n            }\n        }\n        return parentFragment;\n    }\n\n    /**\n     * Return the fragments in manager.\n     *\n     * @param fm The manager of fragment.\n     * @return the fragments in manager\n     */\n    public static List<Fragment> getFragments(@NonNull final FragmentManager fm) {\n        List<Fragment> fragments = fm.getFragments();\n        if (fragments == null || fragments.isEmpty()) return Collections.emptyList();\n        return fragments;\n    }\n\n    /**\n     * Return the fragments in stack in manager.\n     *\n     * @param fm The manager of fragment.\n     * @return the fragments in stack in manager\n     */\n    public static List<Fragment> getFragmentsInStack(@NonNull final FragmentManager fm) {\n        List<Fragment> fragments = getFragments(fm);\n        List<Fragment> result = new ArrayList<>();\n        for (Fragment fragment : fragments) {\n            if (fragment != null) {\n                Bundle args = fragment.getArguments();\n                if (args != null && args.getBoolean(ARGS_IS_ADD_STACK)) {\n                    result.add(fragment);\n                }\n            }\n        }\n        return result;\n    }\n\n    /**\n     * Return all fragments in manager.\n     *\n     * @param fm The manager of fragment.\n     * @return all fragments in manager\n     */\n    public static List<FragmentNode> getAllFragments(@NonNull final FragmentManager fm) {\n        return getAllFragments(fm, new ArrayList<FragmentNode>());\n    }\n\n    private static List<FragmentNode> getAllFragments(@NonNull final FragmentManager fm,\n                                                      final List<FragmentNode> result) {\n        List<Fragment> fragments = getFragments(fm);\n        for (int i = fragments.size() - 1; i >= 0; --i) {\n            Fragment fragment = fragments.get(i);\n            if (fragment != null) {\n                result.add(new FragmentNode(fragment,\n                        getAllFragments(fragment.getChildFragmentManager(),\n                                new ArrayList<FragmentNode>())));\n            }\n        }\n        return result;\n    }\n\n    /**\n     * Return all fragments in stack in manager.\n     *\n     * @param fm The manager of fragment.\n     * @return all fragments in stack in manager\n     */\n    public static List<FragmentNode> getAllFragmentsInStack(@NonNull final FragmentManager fm) {\n        return getAllFragmentsInStack(fm, new ArrayList<FragmentNode>());\n    }\n\n    private static List<FragmentNode> getAllFragmentsInStack(@NonNull final FragmentManager fm,\n                                                             final List<FragmentNode> result) {\n        List<Fragment> fragments = getFragments(fm);\n        for (int i = fragments.size() - 1; i >= 0; --i) {\n            Fragment fragment = fragments.get(i);\n            if (fragment != null) {\n                Bundle args = fragment.getArguments();\n                if (args != null && args.getBoolean(ARGS_IS_ADD_STACK)) {\n                    result.add(new FragmentNode(fragment,\n                            getAllFragmentsInStack(fragment.getChildFragmentManager(),\n                                    new ArrayList<FragmentNode>())));\n                }\n            }\n        }\n        return result;\n    }\n\n    /**\n     * Find fragment.\n     *\n     * @param fm      The manager of fragment.\n     * @param findClz The class of fragment will be found.\n     * @return the fragment matches class\n     */\n    public static Fragment findFragment(@NonNull final FragmentManager fm,\n                                        final Class<? extends Fragment> findClz) {\n        return fm.findFragmentByTag(findClz.getName());\n    }\n\n    /**\n     * Find fragment.\n     *\n     * @param fm  The manager of fragment.\n     * @param tag The tag of fragment will be found.\n     * @return the fragment matches class\n     */\n    public static Fragment findFragment(@NonNull final FragmentManager fm,\n                                        @NonNull final String tag) {\n        return fm.findFragmentByTag(tag);\n    }\n\n    /**\n     * Dispatch the back press for fragment.\n     *\n     * @param fragment The fragment.\n     * @return {@code true}: the fragment consumes the back press<br>{@code false}: otherwise\n     */\n    public static boolean dispatchBackPress(@NonNull final Fragment fragment) {\n        return fragment.isResumed()\n                && fragment.isVisible()\n                && fragment.getUserVisibleHint()\n                && fragment instanceof OnBackClickListener\n                && ((OnBackClickListener) fragment).onBackClick();\n    }\n\n    /**\n     * Dispatch the back press for fragment.\n     *\n     * @param fm The manager of fragment.\n     * @return {@code true}: the fragment consumes the back press<br>{@code false}: otherwise\n     */\n    public static boolean dispatchBackPress(@NonNull final FragmentManager fm) {\n        List<Fragment> fragments = getFragments(fm);\n        if (fragments == null || fragments.isEmpty()) return false;\n        for (int i = fragments.size() - 1; i >= 0; --i) {\n            Fragment fragment = fragments.get(i);\n            if (fragment != null\n                    && fragment.isResumed()\n                    && fragment.isVisible()\n                    && fragment.getUserVisibleHint()\n                    && fragment instanceof OnBackClickListener\n                    && ((OnBackClickListener) fragment).onBackClick()) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Set background color for fragment.\n     *\n     * @param fragment The fragment.\n     * @param color    The background color.\n     */\n    public static void setBackgroundColor(@NonNull final Fragment fragment,\n                                          @ColorInt final int color) {\n        View view = fragment.getView();\n        if (view != null) {\n            view.setBackgroundColor(color);\n        }\n    }\n\n    /**\n     * Set background resource for fragment.\n     *\n     * @param fragment The fragment.\n     * @param resId    The resource id.\n     */\n    public static void setBackgroundResource(@NonNull final Fragment fragment,\n                                             @DrawableRes final int resId) {\n        View view = fragment.getView();\n        if (view != null) {\n            view.setBackgroundResource(resId);\n        }\n    }\n\n    /**\n     * Set background color for fragment.\n     *\n     * @param fragment   The fragment.\n     * @param background The background.\n     */\n    public static void setBackground(@NonNull final Fragment fragment, final Drawable background) {\n        View view = fragment.getView();\n        if (view == null) return;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            view.setBackground(background);\n        } else {\n            view.setBackgroundDrawable(background);\n        }\n    }\n\n    /**\n     * Return the simple name of fragment.\n     *\n     * @param fragment The fragment.\n     * @return the simple name of fragment\n     */\n    public static String getSimpleName(final Fragment fragment) {\n        return fragment == null ? \"null\" : fragment.getClass().getSimpleName();\n    }\n\n    private static class Args {\n        final int     id;\n        final boolean isHide;\n        final boolean isAddStack;\n        final String  tag;\n\n        Args(final int id, final boolean isHide, final boolean isAddStack) {\n            this(id, null, isHide, isAddStack);\n        }\n\n        Args(final int id, final String tag,\n             final boolean isHide, final boolean isAddStack) {\n            this.id = id;\n            this.tag = tag;\n            this.isHide = isHide;\n            this.isAddStack = isAddStack;\n        }\n    }\n\n    public static class FragmentNode {\n        final Fragment           fragment;\n        final List<FragmentNode> next;\n\n        public FragmentNode(final Fragment fragment, final List<FragmentNode> next) {\n            this.fragment = fragment;\n            this.next = next;\n        }\n\n        public Fragment getFragment() {\n            return fragment;\n        }\n\n        public List<FragmentNode> getNext() {\n            return next;\n        }\n\n        @Override\n        public String toString() {\n            return fragment.getClass().getSimpleName()\n                    + \"->\"\n                    + ((next == null || next.isEmpty()) ? \"no child\" : next.toString());\n        }\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // interface\n    ///////////////////////////////////////////////////////////////////////////\n\n    public interface OnBackClickListener {\n        boolean onBackClick();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/GlideUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;//package com.blankj.subutil.util;\n//\n//import android.content.Context;\n//import android.graphics.drawable.PictureDrawable;\n//import android.widget.ImageView;\n//\n//import com.blankj.subutil.R;\n//import com.blankj.subutil.util.image.GlideApp;\n//import com.bumptech.glide.Glide;\n//import com.bumptech.glide.load.engine.DiskCacheStrategy;\n//import com.bumptech.glide.request.RequestOptions;\n//\n///**\n// * <pre>\n// *     author: Blankj\n// *     blog  : http://blankj.com\n// *     time  : 2018/05/16\n// *     desc  :\n// * </pre>\n// */\n//public final class GlideUtils {\n//\n//\n//\n//    public static void setCircleImage(Context context, String url, ImageView view) {\n//        RequestOptions requestOptions = new RequestOptions()\n//                .placeholder(R.drawable.def_img_round_holder)\n//                .error(R.drawable.def_img_round_error)\n//                .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)\n//                .circleCrop().dontAnimate();\n//        Glide.with(context).load(url).apply(requestOptions).into(view);\n//    }\n//\n//    public static void setImage(Context context, String url, ImageView view) {\n//        if (url.endsWith(\".svg\") || url.endsWith(\".SVG\")) {\n//            setSvgImage(context, url, view);\n//            return;\n//        }\n//\n//        RequestOptions requestOptions = new RequestOptions().placeholder(R.drawable.def_img)\n//                .error(R.drawable.def_img).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).dontAnimate();\n//        Glide.with(context).load(url).apply(requestOptions).into(view);\n//    }\n//\n//    private static void setSvgImage(Context context, String url, ImageView view) {\n//        GlideApp.with(context)\n//                .as(PictureDrawable.class)\n//                .error(R.drawable.def_img).load(url).into(view);\n//    }\n//}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/GsonUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.text.TextUtils;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.reflect.TypeToken;\n\nimport java.io.Reader;\nimport java.lang.reflect.Type;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\n\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/04/05\n *     desc  : utils about gson\n * </pre>\n */\npublic final class GsonUtils {\n\n    private static final String KEY_DEFAULT   = \"defaultGson\";\n    private static final String KEY_DELEGATE  = \"delegateGson\";\n    private static final String KEY_LOG_UTILS = \"logUtilsGson\";\n\n    private static final Map<String, Gson> GSONS = new ConcurrentHashMap<>();\n\n    private GsonUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Set the delegate of {@link Gson}.\n     *\n     * @param delegate The delegate of {@link Gson}.\n     */\n    public static void setGsonDelegate(Gson delegate) {\n        if (delegate == null) return;\n        GSONS.put(KEY_DELEGATE, delegate);\n    }\n\n    /**\n     * Set the {@link Gson} with key.\n     *\n     * @param key  The key.\n     * @param gson The {@link Gson}.\n     */\n    public static void setGson(final String key, final Gson gson) {\n        if (TextUtils.isEmpty(key) || gson == null) return;\n        GSONS.put(key, gson);\n    }\n\n    /**\n     * Return the {@link Gson} with key.\n     *\n     * @param key The key.\n     * @return the {@link Gson} with key\n     */\n    public static Gson getGson(final String key) {\n        return GSONS.get(key);\n    }\n\n    public static Gson getGson() {\n        Gson gsonDelegate = GSONS.get(KEY_DELEGATE);\n        if (gsonDelegate != null) {\n            return gsonDelegate;\n        }\n        Gson gsonDefault = GSONS.get(KEY_DEFAULT);\n        if (gsonDefault == null) {\n            gsonDefault = createGson();\n            GSONS.put(KEY_DEFAULT, gsonDefault);\n        }\n        return gsonDefault;\n    }\n\n    /**\n     * Serializes an object into json.\n     *\n     * @param object The object to serialize.\n     * @return object serialized into json.\n     */\n    public static String toJson(final Object object) {\n        return toJson(getGson(), object);\n    }\n\n    /**\n     * Serializes an object into json.\n     *\n     * @param src       The object to serialize.\n     * @param typeOfSrc The specific genericized type of src.\n     * @return object serialized into json.\n     */\n    public static String toJson(final Object src, @NonNull final Type typeOfSrc) {\n        return toJson(getGson(), src, typeOfSrc);\n    }\n\n    /**\n     * Serializes an object into json.\n     *\n     * @param gson   The gson.\n     * @param object The object to serialize.\n     * @return object serialized into json.\n     */\n    public static String toJson(@NonNull final Gson gson, final Object object) {\n        return gson.toJson(object);\n    }\n\n    /**\n     * Serializes an object into json.\n     *\n     * @param gson      The gson.\n     * @param src       The object to serialize.\n     * @param typeOfSrc The specific genericized type of src.\n     * @return object serialized into json.\n     */\n    public static String toJson(@NonNull final Gson gson, final Object src, @NonNull final Type typeOfSrc) {\n        return gson.toJson(src, typeOfSrc);\n    }\n\n    /**\n     * Converts {@link String} to given type.\n     *\n     * @param json The json to convert.\n     * @param type Type json will be converted to.\n     * @return instance of type\n     */\n    public static <T> T fromJson(final String json, @NonNull final Class<T> type) {\n        return fromJson(getGson(), json, type);\n    }\n\n    /**\n     * Converts {@link String} to given type.\n     *\n     * @param json the json to convert.\n     * @param type type type json will be converted to.\n     * @return instance of type\n     */\n    public static <T> T fromJson(final String json, @NonNull final Type type) {\n        return fromJson(getGson(), json, type);\n    }\n\n    /**\n     * Converts {@link Reader} to given type.\n     *\n     * @param reader the reader to convert.\n     * @param type   type type json will be converted to.\n     * @return instance of type\n     */\n    public static <T> T fromJson(@NonNull final Reader reader, @NonNull final Class<T> type) {\n        return fromJson(getGson(), reader, type);\n    }\n\n    /**\n     * Converts {@link Reader} to given type.\n     *\n     * @param reader the reader to convert.\n     * @param type   type type json will be converted to.\n     * @return instance of type\n     */\n    public static <T> T fromJson(@NonNull final Reader reader, @NonNull final Type type) {\n        return fromJson(getGson(), reader, type);\n    }\n\n    /**\n     * Converts {@link String} to given type.\n     *\n     * @param gson The gson.\n     * @param json The json to convert.\n     * @param type Type json will be converted to.\n     * @return instance of type\n     */\n    public static <T> T fromJson(@NonNull final Gson gson, final String json, @NonNull final Class<T> type) {\n        return gson.fromJson(json, type);\n    }\n\n    /**\n     * Converts {@link String} to given type.\n     *\n     * @param gson The gson.\n     * @param json the json to convert.\n     * @param type type type json will be converted to.\n     * @return instance of type\n     */\n    public static <T> T fromJson(@NonNull final Gson gson, final String json, @NonNull final Type type) {\n        return gson.fromJson(json, type);\n    }\n\n    /**\n     * Converts {@link Reader} to given type.\n     *\n     * @param gson   The gson.\n     * @param reader the reader to convert.\n     * @param type   type type json will be converted to.\n     * @return instance of type\n     */\n    public static <T> T fromJson(@NonNull final Gson gson, final Reader reader, @NonNull final Class<T> type) {\n        return gson.fromJson(reader, type);\n    }\n\n    /**\n     * Converts {@link Reader} to given type.\n     *\n     * @param gson   The gson.\n     * @param reader the reader to convert.\n     * @param type   type type json will be converted to.\n     * @return instance of type\n     */\n    public static <T> T fromJson(@NonNull final Gson gson, final Reader reader, @NonNull final Type type) {\n        return gson.fromJson(reader, type);\n    }\n\n    /**\n     * Return the type of {@link List} with the {@code type}.\n     *\n     * @param type The type.\n     * @return the type of {@link List} with the {@code type}\n     */\n    public static Type getListType(@NonNull final Type type) {\n        return TypeToken.getParameterized(List.class, type).getType();\n    }\n\n    /**\n     * Return the type of {@link Set} with the {@code type}.\n     *\n     * @param type The type.\n     * @return the type of {@link Set} with the {@code type}\n     */\n    public static Type getSetType(@NonNull final Type type) {\n        return TypeToken.getParameterized(Set.class, type).getType();\n    }\n\n    /**\n     * Return the type of map with the {@code keyType} and {@code valueType}.\n     *\n     * @param keyType   The type of key.\n     * @param valueType The type of value.\n     * @return the type of map with the {@code keyType} and {@code valueType}\n     */\n    public static Type getMapType(@NonNull final Type keyType, @NonNull final Type valueType) {\n        return TypeToken.getParameterized(Map.class, keyType, valueType).getType();\n    }\n\n    /**\n     * Return the type of array with the {@code type}.\n     *\n     * @param type The type.\n     * @return the type of map with the {@code type}\n     */\n    public static Type getArrayType(@NonNull final Type type) {\n        return TypeToken.getArray(type).getType();\n    }\n\n    /**\n     * Return the type of {@code rawType} with the {@code typeArguments}.\n     *\n     * @param rawType       The raw type.\n     * @param typeArguments The type of arguments.\n     * @return the type of map with the {@code type}\n     */\n    public static Type getType(@NonNull final Type rawType, @NonNull final Type... typeArguments) {\n        return TypeToken.getParameterized(rawType, typeArguments).getType();\n    }\n\n    static Gson getGson4LogUtils() {\n        Gson gson4LogUtils = GSONS.get(KEY_LOG_UTILS);\n        if (gson4LogUtils == null) {\n            gson4LogUtils = new GsonBuilder().setPrettyPrinting().serializeNulls().create();\n            GSONS.put(KEY_LOG_UTILS, gson4LogUtils);\n        }\n        return gson4LogUtils;\n    }\n\n    private static Gson createGson() {\n        return new GsonBuilder().serializeNulls().disableHtmlEscaping().create();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/HttpsUtil.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.util.Scanner;\n\n/**\n * <pre>\n *     author: MilkZS\n *     time  : 2019/01/09\n *     desc  : https 工具类\n * </pre>\n */\npublic final class HttpsUtil {\n\n    private static final int CONNECT_TIMEOUT_TIME = 15000;\n    private static final int READ_TIMEOUT_TIME    = 19000;\n\n    /**\n     * POST + JSON\n     *\n     * @param data send data\n     * @param url  target url\n     * @return data receive from server\n     * @author MilkZS\n     */\n    public static String postJson(String data, String url) {\n        return doHttpAction(data, true, true, url);\n    }\n\n    /**\n     * POST + FORM\n     *\n     * @param data send data\n     * @param url  target url\n     * @return data receive from serv\n     * @author MilkZS\n     */\n    public static String postForm(String data, String url) {\n        return doHttpAction(data, false, true, url);\n    }\n\n    /**\n     * GET + JSON\n     *\n     * @param data send data\n     * @param url  target url\n     * @return data receive from server\n     * @author MilkZS\n     */\n    public static String getJson(String data, String url) {\n        return doHttpAction(data, true, false, url);\n    }\n\n    /**\n     * GET + FORM\n     *\n     * @param data send data\n     * @param url  target url\n     * @return data receive from server\n     * @author MilkZS\n     */\n    public static String getForm(String data, String url) {\n        return doHttpAction(data, false, false, url);\n    }\n\n    private static String doHttpAction(String data, boolean json, boolean post, String url) {\n        HttpURLConnection connection = null;\n        DataOutputStream os = null;\n        InputStream is = null;\n        try {\n            URL sUrl = new URL(url);\n            connection = (HttpURLConnection) sUrl.openConnection();\n            connection.setConnectTimeout(CONNECT_TIMEOUT_TIME);\n            connection.setReadTimeout(READ_TIMEOUT_TIME);\n            if (post) {\n                connection.setRequestMethod(\"POST\");\n            } else {\n                connection.setRequestMethod(\"GET\");\n            }\n            //允许输入输出\n            connection.setDoInput(true);\n            connection.setDoOutput(true);\n            // 是否使用缓冲\n            connection.setUseCaches(false);\n            // 本次连接是否处理重定向，设置成true，系统自动处理重定向；\n            // 设置成false，则需要自己从http reply中分析新的url自己重新连接。\n            connection.setInstanceFollowRedirects(true);\n            // 设置请求头里的属性\n            if (json) {\n                connection.setRequestProperty(\"Content-Type\", \"application/json\");\n            } else {\n                connection.setRequestProperty(\"Content-Type\", \"application/x-www-form-urlencoded\");\n                connection.setRequestProperty(\"Content-Length\", data.length() + \"\");\n            }\n            connection.connect();\n\n            os = new DataOutputStream(connection.getOutputStream());\n            os.write(data.getBytes(), 0, data.getBytes().length);\n            os.flush();\n            os.close();\n\n            is = connection.getInputStream();\n            Scanner scan = new Scanner(is);\n            scan.useDelimiter(\"\\\\A\");\n            if (scan.hasNext()) return scan.next();\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            if (connection != null) connection.disconnect();\n            if (os != null) {\n                try {\n                    os.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n            if (is != null) {\n                try {\n                    is.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ImageUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.Manifest;\nimport android.content.ContentValues;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.CompressFormat;\nimport android.graphics.BitmapFactory;\nimport android.graphics.BitmapShader;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.ColorMatrix;\nimport android.graphics.ColorMatrixColorFilter;\nimport android.graphics.LinearGradient;\nimport android.graphics.Matrix;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.PixelFormat;\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffColorFilter;\nimport android.graphics.PorterDuffXfermode;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.graphics.Shader;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.media.ExifInterface;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.provider.MediaStore;\nimport android.renderscript.Allocation;\nimport android.renderscript.Element;\nimport android.renderscript.RenderScript;\nimport android.renderscript.ScriptIntrinsicBlur;\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport androidx.core.content.ContextCompat;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.View;\n\nimport java.io.BufferedOutputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileDescriptor;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/12\n *     desc  : utils about image\n * </pre>\n */\npublic final class ImageUtils {\n\n    private ImageUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Bitmap to bytes.\n     *\n     * @param bitmap The bitmap.\n     * @return bytes\n     */\n    public static byte[] bitmap2Bytes(final Bitmap bitmap) {\n        return bitmap2Bytes(bitmap, CompressFormat.PNG, 100);\n    }\n\n    /**\n     * Bitmap to bytes.\n     *\n     * @param bitmap  The bitmap.\n     * @param format  The format of bitmap.\n     * @param quality The quality.\n     * @return bytes\n     */\n    public static byte[] bitmap2Bytes(@Nullable final Bitmap bitmap, @NonNull final CompressFormat format, int quality) {\n        if (bitmap == null) return null;\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        bitmap.compress(format, quality, baos);\n        return baos.toByteArray();\n    }\n\n    /**\n     * Bytes to bitmap.\n     *\n     * @param bytes The bytes.\n     * @return bitmap\n     */\n    public static Bitmap bytes2Bitmap(@Nullable final byte[] bytes) {\n        return (bytes == null || bytes.length == 0)\n                ? null\n                : BitmapFactory.decodeByteArray(bytes, 0, bytes.length);\n    }\n\n    /**\n     * Drawable to bitmap.\n     *\n     * @param drawable The drawable.\n     * @return bitmap\n     */\n    public static Bitmap drawable2Bitmap(@Nullable final Drawable drawable) {\n        if (drawable == null) return null;\n        if (drawable instanceof BitmapDrawable) {\n            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;\n            if (bitmapDrawable.getBitmap() != null) {\n                return bitmapDrawable.getBitmap();\n            }\n        }\n        Bitmap bitmap;\n        if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {\n            bitmap = Bitmap.createBitmap(1, 1,\n                    drawable.getOpacity() != PixelFormat.OPAQUE\n                            ? Bitmap.Config.ARGB_8888\n                            : Bitmap.Config.RGB_565);\n        } else {\n            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),\n                    drawable.getIntrinsicHeight(),\n                    drawable.getOpacity() != PixelFormat.OPAQUE\n                            ? Bitmap.Config.ARGB_8888\n                            : Bitmap.Config.RGB_565);\n        }\n        Canvas canvas = new Canvas(bitmap);\n        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());\n        drawable.draw(canvas);\n        return bitmap;\n    }\n\n    /**\n     * Bitmap to drawable.\n     *\n     * @param bitmap The bitmap.\n     * @return drawable\n     */\n    public static Drawable bitmap2Drawable(@Nullable final Bitmap bitmap) {\n        return bitmap == null ? null : new BitmapDrawable(Utils.getApp().getResources(), bitmap);\n    }\n\n    /**\n     * Drawable to bytes.\n     *\n     * @param drawable The drawable.\n     * @return bytes\n     */\n    public static byte[] drawable2Bytes(@Nullable final Drawable drawable) {\n        return drawable == null ? null : bitmap2Bytes(drawable2Bitmap(drawable));\n    }\n\n    /**\n     * Drawable to bytes.\n     *\n     * @param drawable The drawable.\n     * @param format   The format of bitmap.\n     * @return bytes\n     */\n    public static byte[] drawable2Bytes(final Drawable drawable, final CompressFormat format, int quality) {\n        return drawable == null ? null : bitmap2Bytes(drawable2Bitmap(drawable), format, quality);\n    }\n\n    /**\n     * Bytes to drawable.\n     *\n     * @param bytes The bytes.\n     * @return drawable\n     */\n    public static Drawable bytes2Drawable(final byte[] bytes) {\n        return bitmap2Drawable(bytes2Bitmap(bytes));\n    }\n\n    /**\n     * View to bitmap.\n     *\n     * @param view The view.\n     * @return bitmap\n     */\n    public static Bitmap view2Bitmap(final View view) {\n        if (view == null) return null;\n        boolean drawingCacheEnabled = view.isDrawingCacheEnabled();\n        boolean willNotCacheDrawing = view.willNotCacheDrawing();\n        view.setDrawingCacheEnabled(true);\n        view.setWillNotCacheDrawing(false);\n        Bitmap drawingCache = view.getDrawingCache();\n        Bitmap bitmap;\n        if (null == drawingCache || drawingCache.isRecycled()) {\n            view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),\n                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));\n            view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());\n            view.buildDrawingCache();\n            drawingCache = view.getDrawingCache();\n            if (null == drawingCache || drawingCache.isRecycled()) {\n                bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.RGB_565);\n                Canvas canvas = new Canvas(bitmap);\n                view.draw(canvas);\n            } else {\n                bitmap = Bitmap.createBitmap(drawingCache);\n            }\n        } else {\n            bitmap = Bitmap.createBitmap(drawingCache);\n        }\n        view.setWillNotCacheDrawing(willNotCacheDrawing);\n        view.setDrawingCacheEnabled(drawingCacheEnabled);\n        return bitmap;\n    }\n\n    /**\n     * Return bitmap.\n     *\n     * @param file The file.\n     * @return bitmap\n     */\n    public static Bitmap getBitmap(final File file) {\n        if (file == null) return null;\n        return BitmapFactory.decodeFile(file.getAbsolutePath());\n    }\n\n    /**\n     * Return bitmap.\n     *\n     * @param file      The file.\n     * @param maxWidth  The maximum width.\n     * @param maxHeight The maximum height.\n     * @return bitmap\n     */\n    public static Bitmap getBitmap(final File file, final int maxWidth, final int maxHeight) {\n        if (file == null) return null;\n        BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inJustDecodeBounds = true;\n        BitmapFactory.decodeFile(file.getAbsolutePath(), options);\n        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);\n        options.inJustDecodeBounds = false;\n        return BitmapFactory.decodeFile(file.getAbsolutePath(), options);\n    }\n\n    /**\n     * Return bitmap.\n     *\n     * @param filePath The path of file.\n     * @return bitmap\n     */\n    public static Bitmap getBitmap(final String filePath) {\n        if (UtilsBridge.isSpace(filePath)) return null;\n        return BitmapFactory.decodeFile(filePath);\n    }\n\n    /**\n     * Return bitmap.\n     *\n     * @param filePath  The path of file.\n     * @param maxWidth  The maximum width.\n     * @param maxHeight The maximum height.\n     * @return bitmap\n     */\n    public static Bitmap getBitmap(final String filePath, final int maxWidth, final int maxHeight) {\n        if (UtilsBridge.isSpace(filePath)) return null;\n        BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inJustDecodeBounds = true;\n        BitmapFactory.decodeFile(filePath, options);\n        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);\n        options.inJustDecodeBounds = false;\n        return BitmapFactory.decodeFile(filePath, options);\n    }\n\n    /**\n     * Return bitmap.\n     *\n     * @param is The input stream.\n     * @return bitmap\n     */\n    public static Bitmap getBitmap(final InputStream is) {\n        if (is == null) return null;\n        return BitmapFactory.decodeStream(is);\n    }\n\n    /**\n     * Return bitmap.\n     *\n     * @param is        The input stream.\n     * @param maxWidth  The maximum width.\n     * @param maxHeight The maximum height.\n     * @return bitmap\n     */\n    public static Bitmap getBitmap(final InputStream is, final int maxWidth, final int maxHeight) {\n        if (is == null) return null;\n        BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inJustDecodeBounds = true;\n        BitmapFactory.decodeStream(is, null, options);\n        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);\n        options.inJustDecodeBounds = false;\n        return BitmapFactory.decodeStream(is, null, options);\n    }\n\n    /**\n     * Return bitmap.\n     *\n     * @param data   The data.\n     * @param offset The offset.\n     * @return bitmap\n     */\n    public static Bitmap getBitmap(final byte[] data, final int offset) {\n        if (data.length == 0) return null;\n        return BitmapFactory.decodeByteArray(data, offset, data.length);\n    }\n\n    /**\n     * Return bitmap.\n     *\n     * @param data      The data.\n     * @param offset    The offset.\n     * @param maxWidth  The maximum width.\n     * @param maxHeight The maximum height.\n     * @return bitmap\n     */\n    public static Bitmap getBitmap(final byte[] data,\n                                   final int offset,\n                                   final int maxWidth,\n                                   final int maxHeight) {\n        if (data.length == 0) return null;\n        BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inJustDecodeBounds = true;\n        BitmapFactory.decodeByteArray(data, offset, data.length, options);\n        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);\n        options.inJustDecodeBounds = false;\n        return BitmapFactory.decodeByteArray(data, offset, data.length, options);\n    }\n\n    /**\n     * Return bitmap.\n     *\n     * @param resId The resource id.\n     * @return bitmap\n     */\n    public static Bitmap getBitmap(@DrawableRes final int resId) {\n        Drawable drawable = ContextCompat.getDrawable(Utils.getApp(), resId);\n        if (drawable == null) {\n            return null;\n        }\n        Canvas canvas = new Canvas();\n        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),\n                drawable.getIntrinsicHeight(),\n                Bitmap.Config.ARGB_8888);\n        canvas.setBitmap(bitmap);\n        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());\n        drawable.draw(canvas);\n        return bitmap;\n    }\n\n    /**\n     * Return bitmap.\n     *\n     * @param resId     The resource id.\n     * @param maxWidth  The maximum width.\n     * @param maxHeight The maximum height.\n     * @return bitmap\n     */\n    public static Bitmap getBitmap(@DrawableRes final int resId,\n                                   final int maxWidth,\n                                   final int maxHeight) {\n        BitmapFactory.Options options = new BitmapFactory.Options();\n        final Resources resources = Utils.getApp().getResources();\n        options.inJustDecodeBounds = true;\n        BitmapFactory.decodeResource(resources, resId, options);\n        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);\n        options.inJustDecodeBounds = false;\n        return BitmapFactory.decodeResource(resources, resId, options);\n    }\n\n    /**\n     * Return bitmap.\n     *\n     * @param fd The file descriptor.\n     * @return bitmap\n     */\n    public static Bitmap getBitmap(final FileDescriptor fd) {\n        if (fd == null) return null;\n        return BitmapFactory.decodeFileDescriptor(fd);\n    }\n\n    /**\n     * Return bitmap.\n     *\n     * @param fd        The file descriptor\n     * @param maxWidth  The maximum width.\n     * @param maxHeight The maximum height.\n     * @return bitmap\n     */\n    public static Bitmap getBitmap(final FileDescriptor fd,\n                                   final int maxWidth,\n                                   final int maxHeight) {\n        if (fd == null) return null;\n        BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inJustDecodeBounds = true;\n        BitmapFactory.decodeFileDescriptor(fd, null, options);\n        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);\n        options.inJustDecodeBounds = false;\n        return BitmapFactory.decodeFileDescriptor(fd, null, options);\n    }\n\n    /**\n     * Return the bitmap with the specified color.\n     *\n     * @param src   The source of bitmap.\n     * @param color The color.\n     * @return the bitmap with the specified color\n     */\n    public static Bitmap drawColor(@NonNull final Bitmap src, @ColorInt final int color) {\n        return drawColor(src, color, false);\n    }\n\n    /**\n     * Return the bitmap with the specified color.\n     *\n     * @param src     The source of bitmap.\n     * @param color   The color.\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the bitmap with the specified color\n     */\n    public static Bitmap drawColor(@NonNull final Bitmap src,\n                                   @ColorInt final int color,\n                                   final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        Bitmap ret = recycle ? src : src.copy(src.getConfig(), true);\n        Canvas canvas = new Canvas(ret);\n        canvas.drawColor(color, PorterDuff.Mode.DARKEN);\n        return ret;\n    }\n\n    /**\n     * Return the scaled bitmap.\n     *\n     * @param src       The source of bitmap.\n     * @param newWidth  The new width.\n     * @param newHeight The new height.\n     * @return the scaled bitmap\n     */\n    public static Bitmap scale(final Bitmap src, final int newWidth, final int newHeight) {\n        return scale(src, newWidth, newHeight, false);\n    }\n\n    /**\n     * Return the scaled bitmap.\n     *\n     * @param src       The source of bitmap.\n     * @param newWidth  The new width.\n     * @param newHeight The new height.\n     * @param recycle   True to recycle the source of bitmap, false otherwise.\n     * @return the scaled bitmap\n     */\n    public static Bitmap scale(final Bitmap src,\n                               final int newWidth,\n                               final int newHeight,\n                               final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        Bitmap ret = Bitmap.createScaledBitmap(src, newWidth, newHeight, true);\n        if (recycle && !src.isRecycled() && ret != src) src.recycle();\n        return ret;\n    }\n\n    /**\n     * Return the scaled bitmap\n     *\n     * @param src         The source of bitmap.\n     * @param scaleWidth  The scale of width.\n     * @param scaleHeight The scale of height.\n     * @return the scaled bitmap\n     */\n    public static Bitmap scale(final Bitmap src, final float scaleWidth, final float scaleHeight) {\n        return scale(src, scaleWidth, scaleHeight, false);\n    }\n\n    /**\n     * Return the scaled bitmap\n     *\n     * @param src         The source of bitmap.\n     * @param scaleWidth  The scale of width.\n     * @param scaleHeight The scale of height.\n     * @param recycle     True to recycle the source of bitmap, false otherwise.\n     * @return the scaled bitmap\n     */\n    public static Bitmap scale(final Bitmap src,\n                               final float scaleWidth,\n                               final float scaleHeight,\n                               final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        Matrix matrix = new Matrix();\n        matrix.setScale(scaleWidth, scaleHeight);\n        Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);\n        if (recycle && !src.isRecycled() && ret != src) src.recycle();\n        return ret;\n    }\n\n    /**\n     * Return the clipped bitmap.\n     *\n     * @param src    The source of bitmap.\n     * @param x      The x coordinate of the first pixel.\n     * @param y      The y coordinate of the first pixel.\n     * @param width  The width.\n     * @param height The height.\n     * @return the clipped bitmap\n     */\n    public static Bitmap clip(final Bitmap src,\n                              final int x,\n                              final int y,\n                              final int width,\n                              final int height) {\n        return clip(src, x, y, width, height, false);\n    }\n\n    /**\n     * Return the clipped bitmap.\n     *\n     * @param src     The source of bitmap.\n     * @param x       The x coordinate of the first pixel.\n     * @param y       The y coordinate of the first pixel.\n     * @param width   The width.\n     * @param height  The height.\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the clipped bitmap\n     */\n    public static Bitmap clip(final Bitmap src,\n                              final int x,\n                              final int y,\n                              final int width,\n                              final int height,\n                              final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        Bitmap ret = Bitmap.createBitmap(src, x, y, width, height);\n        if (recycle && !src.isRecycled() && ret != src) src.recycle();\n        return ret;\n    }\n\n    /**\n     * Return the skewed bitmap.\n     *\n     * @param src The source of bitmap.\n     * @param kx  The skew factor of x.\n     * @param ky  The skew factor of y.\n     * @return the skewed bitmap\n     */\n    public static Bitmap skew(final Bitmap src, final float kx, final float ky) {\n        return skew(src, kx, ky, 0, 0, false);\n    }\n\n    /**\n     * Return the skewed bitmap.\n     *\n     * @param src     The source of bitmap.\n     * @param kx      The skew factor of x.\n     * @param ky      The skew factor of y.\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the skewed bitmap\n     */\n    public static Bitmap skew(final Bitmap src,\n                              final float kx,\n                              final float ky,\n                              final boolean recycle) {\n        return skew(src, kx, ky, 0, 0, recycle);\n    }\n\n    /**\n     * Return the skewed bitmap.\n     *\n     * @param src The source of bitmap.\n     * @param kx  The skew factor of x.\n     * @param ky  The skew factor of y.\n     * @param px  The x coordinate of the pivot point.\n     * @param py  The y coordinate of the pivot point.\n     * @return the skewed bitmap\n     */\n    public static Bitmap skew(final Bitmap src,\n                              final float kx,\n                              final float ky,\n                              final float px,\n                              final float py) {\n        return skew(src, kx, ky, px, py, false);\n    }\n\n    /**\n     * Return the skewed bitmap.\n     *\n     * @param src     The source of bitmap.\n     * @param kx      The skew factor of x.\n     * @param ky      The skew factor of y.\n     * @param px      The x coordinate of the pivot point.\n     * @param py      The y coordinate of the pivot point.\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the skewed bitmap\n     */\n    public static Bitmap skew(final Bitmap src,\n                              final float kx,\n                              final float ky,\n                              final float px,\n                              final float py,\n                              final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        Matrix matrix = new Matrix();\n        matrix.setSkew(kx, ky, px, py);\n        Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);\n        if (recycle && !src.isRecycled() && ret != src) src.recycle();\n        return ret;\n    }\n\n    /**\n     * Return the rotated bitmap.\n     *\n     * @param src     The source of bitmap.\n     * @param degrees The number of degrees.\n     * @param px      The x coordinate of the pivot point.\n     * @param py      The y coordinate of the pivot point.\n     * @return the rotated bitmap\n     */\n    public static Bitmap rotate(final Bitmap src,\n                                final int degrees,\n                                final float px,\n                                final float py) {\n        return rotate(src, degrees, px, py, false);\n    }\n\n    /**\n     * Return the rotated bitmap.\n     *\n     * @param src     The source of bitmap.\n     * @param degrees The number of degrees.\n     * @param px      The x coordinate of the pivot point.\n     * @param py      The y coordinate of the pivot point.\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the rotated bitmap\n     */\n    public static Bitmap rotate(final Bitmap src,\n                                final int degrees,\n                                final float px,\n                                final float py,\n                                final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        if (degrees == 0) return src;\n        Matrix matrix = new Matrix();\n        matrix.setRotate(degrees, px, py);\n        Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);\n        if (recycle && !src.isRecycled() && ret != src) src.recycle();\n        return ret;\n    }\n\n    /**\n     * Return the rotated degree.\n     *\n     * @param filePath The path of file.\n     * @return the rotated degree\n     */\n    public static int getRotateDegree(final String filePath) {\n        try {\n            ExifInterface exifInterface = new ExifInterface(filePath);\n            int orientation = exifInterface.getAttributeInt(\n                    ExifInterface.TAG_ORIENTATION,\n                    ExifInterface.ORIENTATION_NORMAL\n            );\n            switch (orientation) {\n                case ExifInterface.ORIENTATION_ROTATE_90:\n                    return 90;\n                case ExifInterface.ORIENTATION_ROTATE_180:\n                    return 180;\n                case ExifInterface.ORIENTATION_ROTATE_270:\n                    return 270;\n                default:\n                    return 0;\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n            return -1;\n        }\n    }\n\n    /**\n     * Return the round bitmap.\n     *\n     * @param src The source of bitmap.\n     * @return the round bitmap\n     */\n    public static Bitmap toRound(final Bitmap src) {\n        return toRound(src, 0, 0, false);\n    }\n\n    /**\n     * Return the round bitmap.\n     *\n     * @param src     The source of bitmap.\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the round bitmap\n     */\n    public static Bitmap toRound(final Bitmap src, final boolean recycle) {\n        return toRound(src, 0, 0, recycle);\n    }\n\n    /**\n     * Return the round bitmap.\n     *\n     * @param src         The source of bitmap.\n     * @param borderSize  The size of border.\n     * @param borderColor The color of border.\n     * @return the round bitmap\n     */\n    public static Bitmap toRound(final Bitmap src,\n                                 @IntRange(from = 0) int borderSize,\n                                 @ColorInt int borderColor) {\n        return toRound(src, borderSize, borderColor, false);\n    }\n\n    /**\n     * Return the round bitmap.\n     *\n     * @param src         The source of bitmap.\n     * @param recycle     True to recycle the source of bitmap, false otherwise.\n     * @param borderSize  The size of border.\n     * @param borderColor The color of border.\n     * @return the round bitmap\n     */\n    public static Bitmap toRound(final Bitmap src,\n                                 @IntRange(from = 0) int borderSize,\n                                 @ColorInt int borderColor,\n                                 final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        int width = src.getWidth();\n        int height = src.getHeight();\n        int size = Math.min(width, height);\n        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);\n        Bitmap ret = Bitmap.createBitmap(width, height, src.getConfig());\n        float center = size / 2f;\n        RectF rectF = new RectF(0, 0, width, height);\n        rectF.inset((width - size) / 2f, (height - size) / 2f);\n        Matrix matrix = new Matrix();\n        matrix.setTranslate(rectF.left, rectF.top);\n        if (width != height) {\n            matrix.preScale((float) size / width, (float) size / height);\n        }\n        BitmapShader shader = new BitmapShader(src, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);\n        shader.setLocalMatrix(matrix);\n        paint.setShader(shader);\n        Canvas canvas = new Canvas(ret);\n        canvas.drawRoundRect(rectF, center, center, paint);\n        if (borderSize > 0) {\n            paint.setShader(null);\n            paint.setColor(borderColor);\n            paint.setStyle(Paint.Style.STROKE);\n            paint.setStrokeWidth(borderSize);\n            float radius = center - borderSize / 2f;\n            canvas.drawCircle(width / 2f, height / 2f, radius, paint);\n        }\n        if (recycle && !src.isRecycled() && ret != src) src.recycle();\n        return ret;\n    }\n\n    /**\n     * Return the round corner bitmap.\n     *\n     * @param src    The source of bitmap.\n     * @param radius The radius of corner.\n     * @return the round corner bitmap\n     */\n    public static Bitmap toRoundCorner(final Bitmap src, final float radius) {\n        return toRoundCorner(src, radius, 0, 0, false);\n    }\n\n    /**\n     * Return the round corner bitmap.\n     *\n     * @param src     The source of bitmap.\n     * @param radius  The radius of corner.\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the round corner bitmap\n     */\n    public static Bitmap toRoundCorner(final Bitmap src,\n                                       final float radius,\n                                       final boolean recycle) {\n        return toRoundCorner(src, radius, 0, 0, recycle);\n    }\n\n    /**\n     * Return the round corner bitmap.\n     *\n     * @param src         The source of bitmap.\n     * @param radius      The radius of corner.\n     * @param borderSize  The size of border.\n     * @param borderColor The color of border.\n     * @return the round corner bitmap\n     */\n    public static Bitmap toRoundCorner(final Bitmap src,\n                                       final float radius,\n                                       @FloatRange(from = 0) float borderSize,\n                                       @ColorInt int borderColor) {\n        return toRoundCorner(src, radius, borderSize, borderColor, false);\n    }\n\n    /**\n     * Return the round corner bitmap.\n     *\n     * @param src         The source of bitmap.\n     * @param radii       Array of 8 values, 4 pairs of [X,Y] radii\n     * @param borderSize  The size of border.\n     * @param borderColor The color of border.\n     * @return the round corner bitmap\n     */\n    public static Bitmap toRoundCorner(final Bitmap src,\n                                       final float[] radii,\n                                       @FloatRange(from = 0) float borderSize,\n                                       @ColorInt int borderColor) {\n        return toRoundCorner(src, radii, borderSize, borderColor, false);\n    }\n\n    /**\n     * Return the round corner bitmap.\n     *\n     * @param src         The source of bitmap.\n     * @param radius      The radius of corner.\n     * @param borderSize  The size of border.\n     * @param borderColor The color of border.\n     * @param recycle     True to recycle the source of bitmap, false otherwise.\n     * @return the round corner bitmap\n     */\n    public static Bitmap toRoundCorner(final Bitmap src,\n                                       final float radius,\n                                       @FloatRange(from = 0) float borderSize,\n                                       @ColorInt int borderColor,\n                                       final boolean recycle) {\n        float[] radii = {radius, radius, radius, radius, radius, radius, radius, radius};\n        return toRoundCorner(src, radii, borderSize, borderColor, recycle);\n    }\n\n    /**\n     * Return the round corner bitmap.\n     *\n     * @param src         The source of bitmap.\n     * @param radii       Array of 8 values, 4 pairs of [X,Y] radii\n     * @param borderSize  The size of border.\n     * @param borderColor The color of border.\n     * @param recycle     True to recycle the source of bitmap, false otherwise.\n     * @return the round corner bitmap\n     */\n    public static Bitmap toRoundCorner(final Bitmap src,\n                                       final float[] radii,\n                                       @FloatRange(from = 0) float borderSize,\n                                       @ColorInt int borderColor,\n                                       final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        int width = src.getWidth();\n        int height = src.getHeight();\n        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);\n        Bitmap ret = Bitmap.createBitmap(width, height, src.getConfig());\n        BitmapShader shader = new BitmapShader(src, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);\n        paint.setShader(shader);\n        Canvas canvas = new Canvas(ret);\n        RectF rectF = new RectF(0, 0, width, height);\n        float halfBorderSize = borderSize / 2f;\n        rectF.inset(halfBorderSize, halfBorderSize);\n        Path path = new Path();\n        path.addRoundRect(rectF, radii, Path.Direction.CW);\n        canvas.drawPath(path, paint);\n        if (borderSize > 0) {\n            paint.setShader(null);\n            paint.setColor(borderColor);\n            paint.setStyle(Paint.Style.STROKE);\n            paint.setStrokeWidth(borderSize);\n            paint.setStrokeCap(Paint.Cap.ROUND);\n            canvas.drawPath(path, paint);\n        }\n        if (recycle && !src.isRecycled() && ret != src) src.recycle();\n        return ret;\n    }\n\n    /**\n     * Return the round corner bitmap with border.\n     *\n     * @param src          The source of bitmap.\n     * @param borderSize   The size of border.\n     * @param color        The color of border.\n     * @param cornerRadius The radius of corner.\n     * @return the round corner bitmap with border\n     */\n    public static Bitmap addCornerBorder(final Bitmap src,\n                                         @FloatRange(from = 1) final float borderSize,\n                                         @ColorInt final int color,\n                                         @FloatRange(from = 0) final float cornerRadius) {\n        return addBorder(src, borderSize, color, false, cornerRadius, false);\n    }\n\n    /**\n     * Return the round corner bitmap with border.\n     *\n     * @param src        The source of bitmap.\n     * @param borderSize The size of border.\n     * @param color      The color of border.\n     * @param radii      Array of 8 values, 4 pairs of [X,Y] radii\n     * @return the round corner bitmap with border\n     */\n    public static Bitmap addCornerBorder(final Bitmap src,\n                                         @FloatRange(from = 1) final float borderSize,\n                                         @ColorInt final int color,\n                                         final float[] radii) {\n        return addBorder(src, borderSize, color, false, radii, false);\n    }\n\n    /**\n     * Return the round corner bitmap with border.\n     *\n     * @param src        The source of bitmap.\n     * @param borderSize The size of border.\n     * @param color      The color of border.\n     * @param radii      Array of 8 values, 4 pairs of [X,Y] radii\n     * @param recycle    True to recycle the source of bitmap, false otherwise.\n     * @return the round corner bitmap with border\n     */\n    public static Bitmap addCornerBorder(final Bitmap src,\n                                         @FloatRange(from = 1) final float borderSize,\n                                         @ColorInt final int color,\n                                         final float[] radii,\n                                         final boolean recycle) {\n        return addBorder(src, borderSize, color, false, radii, recycle);\n    }\n\n    /**\n     * Return the round corner bitmap with border.\n     *\n     * @param src          The source of bitmap.\n     * @param borderSize   The size of border.\n     * @param color        The color of border.\n     * @param cornerRadius The radius of corner.\n     * @param recycle      True to recycle the source of bitmap, false otherwise.\n     * @return the round corner bitmap with border\n     */\n    public static Bitmap addCornerBorder(final Bitmap src,\n                                         @FloatRange(from = 1) final float borderSize,\n                                         @ColorInt final int color,\n                                         @FloatRange(from = 0) final float cornerRadius,\n                                         final boolean recycle) {\n        return addBorder(src, borderSize, color, false, cornerRadius, recycle);\n    }\n\n    /**\n     * Return the round bitmap with border.\n     *\n     * @param src        The source of bitmap.\n     * @param borderSize The size of border.\n     * @param color      The color of border.\n     * @return the round bitmap with border\n     */\n    public static Bitmap addCircleBorder(final Bitmap src,\n                                         @FloatRange(from = 1) final float borderSize,\n                                         @ColorInt final int color) {\n        return addBorder(src, borderSize, color, true, 0, false);\n    }\n\n    /**\n     * Return the round bitmap with border.\n     *\n     * @param src        The source of bitmap.\n     * @param borderSize The size of border.\n     * @param color      The color of border.\n     * @param recycle    True to recycle the source of bitmap, false otherwise.\n     * @return the round bitmap with border\n     */\n    public static Bitmap addCircleBorder(final Bitmap src,\n                                         @FloatRange(from = 1) final float borderSize,\n                                         @ColorInt final int color,\n                                         final boolean recycle) {\n        return addBorder(src, borderSize, color, true, 0, recycle);\n    }\n\n    /**\n     * Return the bitmap with border.\n     *\n     * @param src          The source of bitmap.\n     * @param borderSize   The size of border.\n     * @param color        The color of border.\n     * @param isCircle     True to draw circle, false to draw corner.\n     * @param cornerRadius The radius of corner.\n     * @param recycle      True to recycle the source of bitmap, false otherwise.\n     * @return the bitmap with border\n     */\n    private static Bitmap addBorder(final Bitmap src,\n                                    @FloatRange(from = 1) final float borderSize,\n                                    @ColorInt final int color,\n                                    final boolean isCircle,\n                                    final float cornerRadius,\n                                    final boolean recycle) {\n        float[] radii = {cornerRadius, cornerRadius, cornerRadius, cornerRadius,\n                cornerRadius, cornerRadius, cornerRadius, cornerRadius};\n        return addBorder(src, borderSize, color, isCircle, radii, recycle);\n    }\n\n    /**\n     * Return the bitmap with border.\n     *\n     * @param src        The source of bitmap.\n     * @param borderSize The size of border.\n     * @param color      The color of border.\n     * @param isCircle   True to draw circle, false to draw corner.\n     * @param radii      Array of 8 values, 4 pairs of [X,Y] radii\n     * @param recycle    True to recycle the source of bitmap, false otherwise.\n     * @return the bitmap with border\n     */\n    private static Bitmap addBorder(final Bitmap src,\n                                    @FloatRange(from = 1) final float borderSize,\n                                    @ColorInt final int color,\n                                    final boolean isCircle,\n                                    final float[] radii,\n                                    final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        Bitmap ret = recycle ? src : src.copy(src.getConfig(), true);\n        int width = ret.getWidth();\n        int height = ret.getHeight();\n        Canvas canvas = new Canvas(ret);\n        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);\n        paint.setColor(color);\n        paint.setStyle(Paint.Style.STROKE);\n        paint.setStrokeWidth(borderSize);\n        if (isCircle) {\n            float radius = Math.min(width, height) / 2f - borderSize / 2f;\n            canvas.drawCircle(width / 2f, height / 2f, radius, paint);\n        } else {\n            RectF rectF = new RectF(0, 0, width, height);\n            float halfBorderSize = borderSize / 2f;\n            rectF.inset(halfBorderSize, halfBorderSize);\n            Path path = new Path();\n            path.addRoundRect(rectF, radii, Path.Direction.CW);\n            canvas.drawPath(path, paint);\n        }\n        return ret;\n    }\n\n    /**\n     * Return the bitmap with reflection.\n     *\n     * @param src              The source of bitmap.\n     * @param reflectionHeight The height of reflection.\n     * @return the bitmap with reflection\n     */\n    public static Bitmap addReflection(final Bitmap src, final int reflectionHeight) {\n        return addReflection(src, reflectionHeight, false);\n    }\n\n    /**\n     * Return the bitmap with reflection.\n     *\n     * @param src              The source of bitmap.\n     * @param reflectionHeight The height of reflection.\n     * @param recycle          True to recycle the source of bitmap, false otherwise.\n     * @return the bitmap with reflection\n     */\n    public static Bitmap addReflection(final Bitmap src,\n                                       final int reflectionHeight,\n                                       final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        final int REFLECTION_GAP = 0;\n        int srcWidth = src.getWidth();\n        int srcHeight = src.getHeight();\n        Matrix matrix = new Matrix();\n        matrix.preScale(1, -1);\n        Bitmap reflectionBitmap = Bitmap.createBitmap(src, 0, srcHeight - reflectionHeight,\n                srcWidth, reflectionHeight, matrix, false);\n        Bitmap ret = Bitmap.createBitmap(srcWidth, srcHeight + reflectionHeight, src.getConfig());\n        Canvas canvas = new Canvas(ret);\n        canvas.drawBitmap(src, 0, 0, null);\n        canvas.drawBitmap(reflectionBitmap, 0, srcHeight + REFLECTION_GAP, null);\n        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);\n        LinearGradient shader = new LinearGradient(\n                0, srcHeight,\n                0, ret.getHeight() + REFLECTION_GAP,\n                0x70FFFFFF,\n                0x00FFFFFF,\n                Shader.TileMode.MIRROR);\n        paint.setShader(shader);\n        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));\n        canvas.drawRect(0, srcHeight + REFLECTION_GAP, srcWidth, ret.getHeight(), paint);\n        if (!reflectionBitmap.isRecycled()) reflectionBitmap.recycle();\n        if (recycle && !src.isRecycled() && ret != src) src.recycle();\n        return ret;\n    }\n\n    /**\n     * Return the bitmap with text watermarking.\n     *\n     * @param src      The source of bitmap.\n     * @param content  The content of text.\n     * @param textSize The size of text.\n     * @param color    The color of text.\n     * @param x        The x coordinate of the first pixel.\n     * @param y        The y coordinate of the first pixel.\n     * @return the bitmap with text watermarking\n     */\n    public static Bitmap addTextWatermark(final Bitmap src,\n                                          final String content,\n                                          final int textSize,\n                                          @ColorInt final int color,\n                                          final float x,\n                                          final float y) {\n        return addTextWatermark(src, content, textSize, color, x, y, false);\n    }\n\n    /**\n     * Return the bitmap with text watermarking.\n     *\n     * @param src      The source of bitmap.\n     * @param content  The content of text.\n     * @param textSize The size of text.\n     * @param color    The color of text.\n     * @param x        The x coordinate of the first pixel.\n     * @param y        The y coordinate of the first pixel.\n     * @param recycle  True to recycle the source of bitmap, false otherwise.\n     * @return the bitmap with text watermarking\n     */\n    public static Bitmap addTextWatermark(final Bitmap src,\n                                          final String content,\n                                          final float textSize,\n                                          @ColorInt final int color,\n                                          final float x,\n                                          final float y,\n                                          final boolean recycle) {\n        if (isEmptyBitmap(src) || content == null) return null;\n        Bitmap ret = src.copy(src.getConfig(), true);\n        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);\n        Canvas canvas = new Canvas(ret);\n        paint.setColor(color);\n        paint.setTextSize(textSize);\n        Rect bounds = new Rect();\n        paint.getTextBounds(content, 0, content.length(), bounds);\n        canvas.drawText(content, x, y + textSize, paint);\n        if (recycle && !src.isRecycled() && ret != src) src.recycle();\n        return ret;\n    }\n\n    /**\n     * Return the bitmap with image watermarking.\n     *\n     * @param src       The source of bitmap.\n     * @param watermark The image watermarking.\n     * @param x         The x coordinate of the first pixel.\n     * @param y         The y coordinate of the first pixel.\n     * @param alpha     The alpha of watermark.\n     * @return the bitmap with image watermarking\n     */\n    public static Bitmap addImageWatermark(final Bitmap src,\n                                           final Bitmap watermark,\n                                           final int x, final int y,\n                                           final int alpha) {\n        return addImageWatermark(src, watermark, x, y, alpha, false);\n    }\n\n    /**\n     * Return the bitmap with image watermarking.\n     *\n     * @param src       The source of bitmap.\n     * @param watermark The image watermarking.\n     * @param x         The x coordinate of the first pixel.\n     * @param y         The y coordinate of the first pixel.\n     * @param alpha     The alpha of watermark.\n     * @param recycle   True to recycle the source of bitmap, false otherwise.\n     * @return the bitmap with image watermarking\n     */\n    public static Bitmap addImageWatermark(final Bitmap src,\n                                           final Bitmap watermark,\n                                           final int x,\n                                           final int y,\n                                           final int alpha,\n                                           final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        Bitmap ret = src.copy(src.getConfig(), true);\n        if (!isEmptyBitmap(watermark)) {\n            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);\n            Canvas canvas = new Canvas(ret);\n            paint.setAlpha(alpha);\n            canvas.drawBitmap(watermark, x, y, paint);\n        }\n        if (recycle && !src.isRecycled() && ret != src) src.recycle();\n        return ret;\n    }\n\n    /**\n     * Return the alpha bitmap.\n     *\n     * @param src The source of bitmap.\n     * @return the alpha bitmap\n     */\n    public static Bitmap toAlpha(final Bitmap src) {\n        return toAlpha(src, false);\n    }\n\n    /**\n     * Return the alpha bitmap.\n     *\n     * @param src     The source of bitmap.\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the alpha bitmap\n     */\n    public static Bitmap toAlpha(final Bitmap src, final Boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        Bitmap ret = src.extractAlpha();\n        if (recycle && !src.isRecycled() && ret != src) src.recycle();\n        return ret;\n    }\n\n    /**\n     * Return the gray bitmap.\n     *\n     * @param src The source of bitmap.\n     * @return the gray bitmap\n     */\n    public static Bitmap toGray(final Bitmap src) {\n        return toGray(src, false);\n    }\n\n    /**\n     * Return the gray bitmap.\n     *\n     * @param src     The source of bitmap.\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the gray bitmap\n     */\n    public static Bitmap toGray(final Bitmap src, final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        Bitmap ret = Bitmap.createBitmap(src.getWidth(), src.getHeight(), src.getConfig());\n        Canvas canvas = new Canvas(ret);\n        Paint paint = new Paint();\n        ColorMatrix colorMatrix = new ColorMatrix();\n        colorMatrix.setSaturation(0);\n        ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);\n        paint.setColorFilter(colorMatrixColorFilter);\n        canvas.drawBitmap(src, 0, 0, paint);\n        if (recycle && !src.isRecycled() && ret != src) src.recycle();\n        return ret;\n    }\n\n    /**\n     * Return the blur bitmap fast.\n     * <p>zoom out, blur, zoom in</p>\n     *\n     * @param src    The source of bitmap.\n     * @param scale  The scale(0...1).\n     * @param radius The radius(0...25).\n     * @return the blur bitmap\n     */\n    public static Bitmap fastBlur(final Bitmap src,\n                                  @FloatRange(\n                                          from = 0, to = 1, fromInclusive = false\n                                  ) final float scale,\n                                  @FloatRange(\n                                          from = 0, to = 25, fromInclusive = false\n                                  ) final float radius) {\n        return fastBlur(src, scale, radius, false, false);\n    }\n\n    /**\n     * Return the blur bitmap fast.\n     * <p>zoom out, blur, zoom in</p>\n     *\n     * @param src    The source of bitmap.\n     * @param scale  The scale(0...1).\n     * @param radius The radius(0...25).\n     * @return the blur bitmap\n     */\n    public static Bitmap fastBlur(final Bitmap src,\n                                  @FloatRange(\n                                          from = 0, to = 1, fromInclusive = false\n                                  ) final float scale,\n                                  @FloatRange(\n                                          from = 0, to = 25, fromInclusive = false\n                                  ) final float radius,\n                                  final boolean recycle) {\n        return fastBlur(src, scale, radius, recycle, false);\n    }\n\n    /**\n     * Return the blur bitmap fast.\n     * <p>zoom out, blur, zoom in</p>\n     *\n     * @param src           The source of bitmap.\n     * @param scale         The scale(0...1).\n     * @param radius        The radius(0...25).\n     * @param recycle       True to recycle the source of bitmap, false otherwise.\n     * @param isReturnScale True to return the scale blur bitmap, false otherwise.\n     * @return the blur bitmap\n     */\n    public static Bitmap fastBlur(final Bitmap src,\n                                  @FloatRange(\n                                          from = 0, to = 1, fromInclusive = false\n                                  ) final float scale,\n                                  @FloatRange(\n                                          from = 0, to = 25, fromInclusive = false\n                                  ) final float radius,\n                                  final boolean recycle,\n                                  final boolean isReturnScale) {\n        if (isEmptyBitmap(src)) return null;\n        int width = src.getWidth();\n        int height = src.getHeight();\n        Matrix matrix = new Matrix();\n        matrix.setScale(scale, scale);\n        Bitmap scaleBitmap =\n                Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);\n        Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);\n        Canvas canvas = new Canvas();\n        PorterDuffColorFilter filter = new PorterDuffColorFilter(\n                Color.TRANSPARENT, PorterDuff.Mode.SRC_ATOP);\n        paint.setColorFilter(filter);\n        canvas.scale(scale, scale);\n        canvas.drawBitmap(scaleBitmap, 0, 0, paint);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            scaleBitmap = renderScriptBlur(scaleBitmap, radius, recycle);\n        } else {\n            scaleBitmap = stackBlur(scaleBitmap, (int) radius, recycle);\n        }\n        if (scale == 1 || isReturnScale) {\n            if (recycle && !src.isRecycled() && scaleBitmap != src) src.recycle();\n            return scaleBitmap;\n        }\n        Bitmap ret = Bitmap.createScaledBitmap(scaleBitmap, width, height, true);\n        if (!scaleBitmap.isRecycled()) scaleBitmap.recycle();\n        if (recycle && !src.isRecycled() && ret != src) src.recycle();\n        return ret;\n    }\n\n    /**\n     * Return the blur bitmap using render script.\n     *\n     * @param src    The source of bitmap.\n     * @param radius The radius(0...25).\n     * @return the blur bitmap\n     */\n    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n    public static Bitmap renderScriptBlur(final Bitmap src,\n                                          @FloatRange(\n                                                  from = 0, to = 25, fromInclusive = false\n                                          ) final float radius) {\n        return renderScriptBlur(src, radius, false);\n    }\n\n    /**\n     * Return the blur bitmap using render script.\n     *\n     * @param src     The source of bitmap.\n     * @param radius  The radius(0...25).\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the blur bitmap\n     */\n    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n    public static Bitmap renderScriptBlur(final Bitmap src,\n                                          @FloatRange(\n                                                  from = 0, to = 25, fromInclusive = false\n                                          ) final float radius,\n                                          final boolean recycle) {\n        RenderScript rs = null;\n        Bitmap ret = recycle ? src : src.copy(src.getConfig(), true);\n        try {\n            rs = RenderScript.create(Utils.getApp());\n            rs.setMessageHandler(new RenderScript.RSMessageHandler());\n            Allocation input = Allocation.createFromBitmap(rs,\n                    ret,\n                    Allocation.MipmapControl.MIPMAP_NONE,\n                    Allocation.USAGE_SCRIPT);\n            Allocation output = Allocation.createTyped(rs, input.getType());\n            ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));\n            blurScript.setInput(input);\n            blurScript.setRadius(radius);\n            blurScript.forEach(output);\n            output.copyTo(ret);\n        } finally {\n            if (rs != null) {\n                rs.destroy();\n            }\n        }\n        return ret;\n    }\n\n    /**\n     * Return the blur bitmap using stack.\n     *\n     * @param src    The source of bitmap.\n     * @param radius The radius(0...25).\n     * @return the blur bitmap\n     */\n    public static Bitmap stackBlur(final Bitmap src, final int radius) {\n        return stackBlur(src, radius, false);\n    }\n\n    /**\n     * Return the blur bitmap using stack.\n     *\n     * @param src     The source of bitmap.\n     * @param radius  The radius(0...25).\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the blur bitmap\n     */\n    public static Bitmap stackBlur(final Bitmap src, int radius, final boolean recycle) {\n        Bitmap ret = recycle ? src : src.copy(src.getConfig(), true);\n        if (radius < 1) {\n            radius = 1;\n        }\n        int w = ret.getWidth();\n        int h = ret.getHeight();\n\n        int[] pix = new int[w * h];\n        ret.getPixels(pix, 0, w, 0, 0, w, h);\n\n        int wm = w - 1;\n        int hm = h - 1;\n        int wh = w * h;\n        int div = radius + radius + 1;\n\n        int r[] = new int[wh];\n        int g[] = new int[wh];\n        int b[] = new int[wh];\n        int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;\n        int vmin[] = new int[Math.max(w, h)];\n\n        int divsum = (div + 1) >> 1;\n        divsum *= divsum;\n        int dv[] = new int[256 * divsum];\n        for (i = 0; i < 256 * divsum; i++) {\n            dv[i] = (i / divsum);\n        }\n\n        yw = yi = 0;\n\n        int[][] stack = new int[div][3];\n        int stackpointer;\n        int stackstart;\n        int[] sir;\n        int rbs;\n        int r1 = radius + 1;\n        int routsum, goutsum, boutsum;\n        int rinsum, ginsum, binsum;\n\n        for (y = 0; y < h; y++) {\n            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;\n            for (i = -radius; i <= radius; i++) {\n                p = pix[yi + Math.min(wm, Math.max(i, 0))];\n                sir = stack[i + radius];\n                sir[0] = (p & 0xff0000) >> 16;\n                sir[1] = (p & 0x00ff00) >> 8;\n                sir[2] = (p & 0x0000ff);\n                rbs = r1 - Math.abs(i);\n                rsum += sir[0] * rbs;\n                gsum += sir[1] * rbs;\n                bsum += sir[2] * rbs;\n                if (i > 0) {\n                    rinsum += sir[0];\n                    ginsum += sir[1];\n                    binsum += sir[2];\n                } else {\n                    routsum += sir[0];\n                    goutsum += sir[1];\n                    boutsum += sir[2];\n                }\n            }\n            stackpointer = radius;\n\n            for (x = 0; x < w; x++) {\n\n                r[yi] = dv[rsum];\n                g[yi] = dv[gsum];\n                b[yi] = dv[bsum];\n\n                rsum -= routsum;\n                gsum -= goutsum;\n                bsum -= boutsum;\n\n                stackstart = stackpointer - radius + div;\n                sir = stack[stackstart % div];\n\n                routsum -= sir[0];\n                goutsum -= sir[1];\n                boutsum -= sir[2];\n\n                if (y == 0) {\n                    vmin[x] = Math.min(x + radius + 1, wm);\n                }\n                p = pix[yw + vmin[x]];\n\n                sir[0] = (p & 0xff0000) >> 16;\n                sir[1] = (p & 0x00ff00) >> 8;\n                sir[2] = (p & 0x0000ff);\n\n                rinsum += sir[0];\n                ginsum += sir[1];\n                binsum += sir[2];\n\n                rsum += rinsum;\n                gsum += ginsum;\n                bsum += binsum;\n\n                stackpointer = (stackpointer + 1) % div;\n                sir = stack[(stackpointer) % div];\n\n                routsum += sir[0];\n                goutsum += sir[1];\n                boutsum += sir[2];\n\n                rinsum -= sir[0];\n                ginsum -= sir[1];\n                binsum -= sir[2];\n\n                yi++;\n            }\n            yw += w;\n        }\n        for (x = 0; x < w; x++) {\n            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;\n            yp = -radius * w;\n            for (i = -radius; i <= radius; i++) {\n                yi = Math.max(0, yp) + x;\n\n                sir = stack[i + radius];\n\n                sir[0] = r[yi];\n                sir[1] = g[yi];\n                sir[2] = b[yi];\n\n                rbs = r1 - Math.abs(i);\n\n                rsum += r[yi] * rbs;\n                gsum += g[yi] * rbs;\n                bsum += b[yi] * rbs;\n\n                if (i > 0) {\n                    rinsum += sir[0];\n                    ginsum += sir[1];\n                    binsum += sir[2];\n                } else {\n                    routsum += sir[0];\n                    goutsum += sir[1];\n                    boutsum += sir[2];\n                }\n\n                if (i < hm) {\n                    yp += w;\n                }\n            }\n            yi = x;\n            stackpointer = radius;\n            for (y = 0; y < h; y++) {\n                // Preserve alpha channel: ( 0xff000000 & pix[yi] )\n                pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];\n\n                rsum -= routsum;\n                gsum -= goutsum;\n                bsum -= boutsum;\n\n                stackstart = stackpointer - radius + div;\n                sir = stack[stackstart % div];\n\n                routsum -= sir[0];\n                goutsum -= sir[1];\n                boutsum -= sir[2];\n\n                if (x == 0) {\n                    vmin[y] = Math.min(y + r1, hm) * w;\n                }\n                p = x + vmin[y];\n\n                sir[0] = r[p];\n                sir[1] = g[p];\n                sir[2] = b[p];\n\n                rinsum += sir[0];\n                ginsum += sir[1];\n                binsum += sir[2];\n\n                rsum += rinsum;\n                gsum += ginsum;\n                bsum += binsum;\n\n                stackpointer = (stackpointer + 1) % div;\n                sir = stack[stackpointer];\n\n                routsum += sir[0];\n                goutsum += sir[1];\n                boutsum += sir[2];\n\n                rinsum -= sir[0];\n                ginsum -= sir[1];\n                binsum -= sir[2];\n\n                yi += w;\n            }\n        }\n        ret.setPixels(pix, 0, w, 0, 0, w, h);\n        return ret;\n    }\n\n    /**\n     * Save the bitmap.\n     *\n     * @param src      The source of bitmap.\n     * @param filePath The path of file.\n     * @param format   The format of the image.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean save(final Bitmap src,\n                               final String filePath,\n                               final CompressFormat format) {\n        return save(src, filePath, format, 100, false);\n    }\n\n    /**\n     * Save the bitmap.\n     *\n     * @param src    The source of bitmap.\n     * @param file   The file.\n     * @param format The format of the image.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean save(final Bitmap src, final File file, final CompressFormat format) {\n        return save(src, file, format, 100, false);\n    }\n\n    /**\n     * Save the bitmap.\n     *\n     * @param src      The source of bitmap.\n     * @param filePath The path of file.\n     * @param format   The format of the image.\n     * @param recycle  True to recycle the source of bitmap, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean save(final Bitmap src,\n                               final String filePath,\n                               final CompressFormat format,\n                               final boolean recycle) {\n        return save(src, filePath, format, 100, recycle);\n    }\n\n    /**\n     * Save the bitmap.\n     *\n     * @param src     The source of bitmap.\n     * @param file    The file.\n     * @param format  The format of the image.\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean save(final Bitmap src,\n                               final File file,\n                               final CompressFormat format,\n                               final boolean recycle) {\n        return save(src, file, format, 100, recycle);\n    }\n\n    /**\n     * Save the bitmap.\n     *\n     * @param src      The source of bitmap.\n     * @param filePath The path of file.\n     * @param format   The format of the image.\n     * @param quality  Hint to the compressor, 0-100. 0 meaning compress for\n     *                 small size, 100 meaning compress for max quality. Some\n     *                 formats, like PNG which is lossless, will ignore the\n     *                 quality setting\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean save(final Bitmap src,\n                               final String filePath,\n                               final CompressFormat format,\n                               final int quality) {\n        return save(src, UtilsBridge.getFileByPath(filePath), format, quality, false);\n    }\n\n    /**\n     * Save the bitmap.\n     *\n     * @param src    The source of bitmap.\n     * @param file   The file.\n     * @param format The format of the image.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean save(final Bitmap src,\n                               final File file,\n                               final CompressFormat format,\n                               final int quality) {\n        return save(src, file, format, quality, false);\n    }\n\n    /**\n     * Save the bitmap.\n     *\n     * @param src      The source of bitmap.\n     * @param filePath The path of file.\n     * @param format   The format of the image.\n     * @param quality  Hint to the compressor, 0-100. 0 meaning compress for\n     *                 small size, 100 meaning compress for max quality. Some\n     *                 formats, like PNG which is lossless, will ignore the\n     *                 quality setting\n     * @param recycle  True to recycle the source of bitmap, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean save(final Bitmap src,\n                               final String filePath,\n                               final CompressFormat format,\n                               final int quality,\n                               final boolean recycle) {\n        return save(src, UtilsBridge.getFileByPath(filePath), format, quality, recycle);\n    }\n\n    /**\n     * Save the bitmap.\n     *\n     * @param src     The source of bitmap.\n     * @param file    The file.\n     * @param format  The format of the image.\n     * @param quality Hint to the compressor, 0-100. 0 meaning compress for\n     *                small size, 100 meaning compress for max quality. Some\n     *                formats, like PNG which is lossless, will ignore the\n     *                quality setting\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean save(final Bitmap src,\n                               final File file,\n                               final CompressFormat format,\n                               final int quality,\n                               final boolean recycle) {\n        if (isEmptyBitmap(src)) {\n            Log.e(\"ImageUtils\", \"bitmap is empty.\");\n            return false;\n        }\n        if (src.isRecycled()) {\n            Log.e(\"ImageUtils\", \"bitmap is recycled.\");\n            return false;\n        }\n        if (!UtilsBridge.createFileByDeleteOldFile(file)) {\n            Log.e(\"ImageUtils\", \"create or delete file <\" + file + \"> failed.\");\n            return false;\n        }\n        OutputStream os = null;\n        boolean ret = false;\n        try {\n            os = new BufferedOutputStream(new FileOutputStream(file));\n            ret = src.compress(format, quality, os);\n            if (recycle && !src.isRecycled()) src.recycle();\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            try {\n                if (os != null) {\n                    os.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        return ret;\n    }\n\n    /**\n     * @param src    The source of bitmap.\n     * @param format The format of the image.\n     * @return the file if save success, otherwise return null.\n     */\n    @Nullable\n    public static File save2Album(final Bitmap src,\n                                  final CompressFormat format) {\n        return save2Album(src, \"\", format, 100, false);\n    }\n\n    /**\n     * @param src     The source of bitmap.\n     * @param format  The format of the image.\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the file if save success, otherwise return null.\n     */\n    @Nullable\n    public static File save2Album(final Bitmap src,\n                                  final CompressFormat format,\n                                  final boolean recycle) {\n        return save2Album(src, \"\", format, 100, recycle);\n    }\n\n    /**\n     * @param src     The source of bitmap.\n     * @param format  The format of the image.\n     * @param quality Hint to the compressor, 0-100. 0 meaning compress for\n     *                small size, 100 meaning compress for max quality. Some\n     *                formats, like PNG which is lossless, will ignore the\n     *                quality setting\n     * @return the file if save success, otherwise return null.\n     */\n    @Nullable\n    public static File save2Album(final Bitmap src,\n                                  final CompressFormat format,\n                                  final int quality) {\n        return save2Album(src, \"\", format, quality, false);\n    }\n\n    /**\n     * @param src     The source of bitmap.\n     * @param format  The format of the image.\n     * @param quality Hint to the compressor, 0-100. 0 meaning compress for\n     *                small size, 100 meaning compress for max quality. Some\n     *                formats, like PNG which is lossless, will ignore the\n     *                quality setting\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the file if save success, otherwise return null.\n     */\n    @Nullable\n    public static File save2Album(final Bitmap src,\n                                  final CompressFormat format,\n                                  final int quality,\n                                  final boolean recycle) {\n        return save2Album(src, \"\", format, quality, recycle);\n    }\n\n    /**\n     * @param src     The source of bitmap.\n     * @param dirName The name of directory.\n     * @param format  The format of the image.\n     * @return the file if save success, otherwise return null.\n     */\n    @Nullable\n    public static File save2Album(final Bitmap src,\n                                  final String dirName,\n                                  final CompressFormat format) {\n        return save2Album(src, dirName, format, 100, false);\n    }\n\n    /**\n     * @param src     The source of bitmap.\n     * @param dirName The name of directory.\n     * @param format  The format of the image.\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the file if save success, otherwise return null.\n     */\n    @Nullable\n    public static File save2Album(final Bitmap src,\n                                  final String dirName,\n                                  final CompressFormat format,\n                                  final boolean recycle) {\n        return save2Album(src, dirName, format, 100, recycle);\n    }\n\n    /**\n     * @param src     The source of bitmap.\n     * @param dirName The name of directory.\n     * @param format  The format of the image.\n     * @param quality Hint to the compressor, 0-100. 0 meaning compress for\n     *                small size, 100 meaning compress for max quality. Some\n     *                formats, like PNG which is lossless, will ignore the\n     *                quality setting\n     * @return the file if save success, otherwise return null.\n     */\n    @Nullable\n    public static File save2Album(final Bitmap src,\n                                  final String dirName,\n                                  final CompressFormat format,\n                                  final int quality) {\n        return save2Album(src, dirName, format, quality, false);\n    }\n\n    /**\n     * @param src     The source of bitmap.\n     * @param dirName The name of directory.\n     * @param format  The format of the image.\n     * @param quality Hint to the compressor, 0-100. 0 meaning compress for\n     *                small size, 100 meaning compress for max quality. Some\n     *                formats, like PNG which is lossless, will ignore the\n     *                quality setting\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the file if save success, otherwise return null.\n     */\n    @Nullable\n    public static File save2Album(final Bitmap src,\n                                  final String dirName,\n                                  final CompressFormat format,\n                                  final int quality,\n                                  final boolean recycle) {\n        String safeDirName = TextUtils.isEmpty(dirName) ? Utils.getApp().getPackageName() : dirName;\n        String suffix = CompressFormat.JPEG.equals(format) ? \"JPG\" : format.name();\n        String fileName = System.currentTimeMillis() + \"_\" + quality + \".\" + suffix;\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {\n            if (!UtilsBridge.isGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {\n                Log.e(\"ImageUtils\", \"save to album need storage permission\");\n                return null;\n            }\n            File picDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);\n            File destFile = new File(picDir, safeDirName + \"/\" + fileName);\n            if (!save(src, destFile, format, quality, recycle)) {\n                return null;\n            }\n            UtilsBridge.notifySystemToScan(destFile);\n            return destFile;\n        } else {\n            ContentValues contentValues = new ContentValues();\n            contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);\n            contentValues.put(MediaStore.Images.Media.MIME_TYPE, \"image/*\");\n            Uri contentUri;\n            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {\n                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;\n            } else {\n                contentUri = MediaStore.Images.Media.INTERNAL_CONTENT_URI;\n            }\n            contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM + \"/\" + safeDirName);\n            contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1);\n            Uri uri = Utils.getApp().getContentResolver().insert(contentUri, contentValues);\n            if (uri == null) {\n                return null;\n            }\n            OutputStream os = null;\n            try {\n                os = Utils.getApp().getContentResolver().openOutputStream(uri);\n                src.compress(format, quality, os);\n\n                contentValues.clear();\n                contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0);\n                Utils.getApp().getContentResolver().update(uri, contentValues, null, null);\n\n                return UtilsBridge.uri2File(uri);\n            } catch (Exception e) {\n                Utils.getApp().getContentResolver().delete(uri, null, null);\n                e.printStackTrace();\n                return null;\n            } finally {\n                try {\n                    if (os != null) {\n                        os.close();\n                    }\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * Return whether it is a image according to the file name.\n     *\n     * @param file The file.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isImage(final File file) {\n        if (file == null || !file.exists()) {\n            return false;\n        }\n        return isImage(file.getPath());\n    }\n\n    /**\n     * Return whether it is a image according to the file name.\n     *\n     * @param filePath The path of file.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isImage(final String filePath) {\n        try {\n            BitmapFactory.Options options = new BitmapFactory.Options();\n            options.inJustDecodeBounds = true;\n            BitmapFactory.decodeFile(filePath, options);\n            return options.outWidth > 0 && options.outHeight > 0;\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    /**\n     * Return the type of image.\n     *\n     * @param filePath The path of file.\n     * @return the type of image\n     */\n    public static ImageType getImageType(final String filePath) {\n        return getImageType(UtilsBridge.getFileByPath(filePath));\n    }\n\n    /**\n     * Return the type of image.\n     *\n     * @param file The file.\n     * @return the type of image\n     */\n    public static ImageType getImageType(final File file) {\n        if (file == null) return null;\n        InputStream is = null;\n        try {\n            is = new FileInputStream(file);\n            ImageType type = getImageType(is);\n            if (type != null) {\n                return type;\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            try {\n                if (is != null) {\n                    is.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        return null;\n    }\n\n    private static ImageType getImageType(final InputStream is) {\n        if (is == null) return null;\n        try {\n            byte[] bytes = new byte[12];\n            return is.read(bytes) != -1 ? getImageType(bytes) : null;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    private static ImageType getImageType(final byte[] bytes) {\n        String type = UtilsBridge.bytes2HexString(bytes).toUpperCase();\n        if (type.contains(\"FFD8FF\")) {\n            return ImageType.TYPE_JPG;\n        } else if (type.contains(\"89504E47\")) {\n            return ImageType.TYPE_PNG;\n        } else if (type.contains(\"47494638\")) {\n            return ImageType.TYPE_GIF;\n        } else if (type.contains(\"49492A00\") || type.contains(\"4D4D002A\")) {\n            return ImageType.TYPE_TIFF;\n        } else if (type.contains(\"424D\")) {\n            return ImageType.TYPE_BMP;\n        } else if (type.startsWith(\"52494646\") && type.endsWith(\"57454250\")) {//524946461c57000057454250-12个字节\n            return ImageType.TYPE_WEBP;\n        } else if (type.contains(\"00000100\") || type.contains(\"00000200\")) {\n            return ImageType.TYPE_ICO;\n        } else {\n            return ImageType.TYPE_UNKNOWN;\n        }\n    }\n\n    private static boolean isJPEG(final byte[] b) {\n        return b.length >= 2\n                && (b[0] == (byte) 0xFF) && (b[1] == (byte) 0xD8);\n    }\n\n    private static boolean isGIF(final byte[] b) {\n        return b.length >= 6\n                && b[0] == 'G' && b[1] == 'I'\n                && b[2] == 'F' && b[3] == '8'\n                && (b[4] == '7' || b[4] == '9') && b[5] == 'a';\n    }\n\n    private static boolean isPNG(final byte[] b) {\n        return b.length >= 8\n                && (b[0] == (byte) 137 && b[1] == (byte) 80\n                && b[2] == (byte) 78 && b[3] == (byte) 71\n                && b[4] == (byte) 13 && b[5] == (byte) 10\n                && b[6] == (byte) 26 && b[7] == (byte) 10);\n    }\n\n    private static boolean isBMP(final byte[] b) {\n        return b.length >= 2\n                && (b[0] == 0x42) && (b[1] == 0x4d);\n    }\n\n    private static boolean isEmptyBitmap(final Bitmap src) {\n        return src == null || src.getWidth() == 0 || src.getHeight() == 0;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // about compress\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Return the compressed bitmap using scale.\n     *\n     * @param src       The source of bitmap.\n     * @param newWidth  The new width.\n     * @param newHeight The new height.\n     * @return the compressed bitmap\n     */\n    public static Bitmap compressByScale(final Bitmap src,\n                                         final int newWidth,\n                                         final int newHeight) {\n        return scale(src, newWidth, newHeight, false);\n    }\n\n    /**\n     * Return the compressed bitmap using scale.\n     *\n     * @param src       The source of bitmap.\n     * @param newWidth  The new width.\n     * @param newHeight The new height.\n     * @param recycle   True to recycle the source of bitmap, false otherwise.\n     * @return the compressed bitmap\n     */\n    public static Bitmap compressByScale(final Bitmap src,\n                                         final int newWidth,\n                                         final int newHeight,\n                                         final boolean recycle) {\n        return scale(src, newWidth, newHeight, recycle);\n    }\n\n    /**\n     * Return the compressed bitmap using scale.\n     *\n     * @param src         The source of bitmap.\n     * @param scaleWidth  The scale of width.\n     * @param scaleHeight The scale of height.\n     * @return the compressed bitmap\n     */\n    public static Bitmap compressByScale(final Bitmap src,\n                                         final float scaleWidth,\n                                         final float scaleHeight) {\n        return scale(src, scaleWidth, scaleHeight, false);\n    }\n\n    /**\n     * Return the compressed bitmap using scale.\n     *\n     * @param src         The source of bitmap.\n     * @param scaleWidth  The scale of width.\n     * @param scaleHeight The scale of height.\n     * @param recycle     True to recycle the source of bitmap, false otherwise.\n     * @return he compressed bitmap\n     */\n    public static Bitmap compressByScale(final Bitmap src,\n                                         final float scaleWidth,\n                                         final float scaleHeight,\n                                         final boolean recycle) {\n        return scale(src, scaleWidth, scaleHeight, recycle);\n    }\n\n    /**\n     * Return the compressed data using quality.\n     *\n     * @param src     The source of bitmap.\n     * @param quality The quality.\n     * @return the compressed data using quality\n     */\n    public static byte[] compressByQuality(final Bitmap src,\n                                           @IntRange(from = 0, to = 100) final int quality) {\n        return compressByQuality(src, quality, false);\n    }\n\n    /**\n     * Return the compressed data using quality.\n     *\n     * @param src     The source of bitmap.\n     * @param quality The quality.\n     * @param recycle True to recycle the source of bitmap, false otherwise.\n     * @return the compressed data using quality\n     */\n    public static byte[] compressByQuality(final Bitmap src,\n                                           @IntRange(from = 0, to = 100) final int quality,\n                                           final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        src.compress(CompressFormat.JPEG, quality, baos);\n        byte[] bytes = baos.toByteArray();\n        if (recycle && !src.isRecycled()) src.recycle();\n        return bytes;\n    }\n\n    /**\n     * Return the compressed data using quality.\n     *\n     * @param src         The source of bitmap.\n     * @param maxByteSize The maximum size of byte.\n     * @return the compressed data using quality\n     */\n    public static byte[] compressByQuality(final Bitmap src, final long maxByteSize) {\n        return compressByQuality(src, maxByteSize, false);\n    }\n\n    /**\n     * Return the compressed data using quality.\n     *\n     * @param src         The source of bitmap.\n     * @param maxByteSize The maximum size of byte.\n     * @param recycle     True to recycle the source of bitmap, false otherwise.\n     * @return the compressed data using quality\n     */\n    public static byte[] compressByQuality(final Bitmap src,\n                                           final long maxByteSize,\n                                           final boolean recycle) {\n        if (isEmptyBitmap(src) || maxByteSize <= 0) return new byte[0];\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        src.compress(CompressFormat.JPEG, 100, baos);\n        byte[] bytes;\n        if (baos.size() <= maxByteSize) {\n            bytes = baos.toByteArray();\n        } else {\n            baos.reset();\n            src.compress(CompressFormat.JPEG, 0, baos);\n            if (baos.size() >= maxByteSize) {\n                bytes = baos.toByteArray();\n            } else {\n                // find the best quality using binary search\n                int st = 0;\n                int end = 100;\n                int mid = 0;\n                while (st < end) {\n                    mid = (st + end) / 2;\n                    baos.reset();\n                    src.compress(CompressFormat.JPEG, mid, baos);\n                    int len = baos.size();\n                    if (len == maxByteSize) {\n                        break;\n                    } else if (len > maxByteSize) {\n                        end = mid - 1;\n                    } else {\n                        st = mid + 1;\n                    }\n                }\n                if (end == mid - 1) {\n                    baos.reset();\n                    src.compress(CompressFormat.JPEG, st, baos);\n                }\n                bytes = baos.toByteArray();\n            }\n        }\n        if (recycle && !src.isRecycled()) src.recycle();\n        return bytes;\n    }\n\n    /**\n     * Return the compressed bitmap using sample size.\n     *\n     * @param src        The source of bitmap.\n     * @param sampleSize The sample size.\n     * @return the compressed bitmap\n     */\n\n    public static Bitmap compressBySampleSize(final Bitmap src, final int sampleSize) {\n        return compressBySampleSize(src, sampleSize, false);\n    }\n\n    /**\n     * Return the compressed bitmap using sample size.\n     *\n     * @param src        The source of bitmap.\n     * @param sampleSize The sample size.\n     * @param recycle    True to recycle the source of bitmap, false otherwise.\n     * @return the compressed bitmap\n     */\n    public static Bitmap compressBySampleSize(final Bitmap src,\n                                              final int sampleSize,\n                                              final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inSampleSize = sampleSize;\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        src.compress(CompressFormat.JPEG, 100, baos);\n        byte[] bytes = baos.toByteArray();\n        if (recycle && !src.isRecycled()) src.recycle();\n        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);\n    }\n\n    /**\n     * Return the compressed bitmap using sample size.\n     *\n     * @param src       The source of bitmap.\n     * @param maxWidth  The maximum width.\n     * @param maxHeight The maximum height.\n     * @return the compressed bitmap\n     */\n    public static Bitmap compressBySampleSize(final Bitmap src,\n                                              final int maxWidth,\n                                              final int maxHeight) {\n        return compressBySampleSize(src, maxWidth, maxHeight, false);\n    }\n\n    /**\n     * Return the compressed bitmap using sample size.\n     *\n     * @param src       The source of bitmap.\n     * @param maxWidth  The maximum width.\n     * @param maxHeight The maximum height.\n     * @param recycle   True to recycle the source of bitmap, false otherwise.\n     * @return the compressed bitmap\n     */\n    public static Bitmap compressBySampleSize(final Bitmap src,\n                                              final int maxWidth,\n                                              final int maxHeight,\n                                              final boolean recycle) {\n        if (isEmptyBitmap(src)) return null;\n        BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inJustDecodeBounds = true;\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        src.compress(CompressFormat.JPEG, 100, baos);\n        byte[] bytes = baos.toByteArray();\n        BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);\n        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);\n        options.inJustDecodeBounds = false;\n        if (recycle && !src.isRecycled()) src.recycle();\n        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);\n    }\n\n    /**\n     * Return the size of bitmap.\n     *\n     * @param filePath The path of file.\n     * @return the size of bitmap\n     */\n    public static int[] getSize(String filePath) {\n        return getSize(UtilsBridge.getFileByPath(filePath));\n    }\n\n    /**\n     * Return the size of bitmap.\n     *\n     * @param file The file.\n     * @return the size of bitmap\n     */\n    public static int[] getSize(File file) {\n        if (file == null) return new int[]{0, 0};\n        BitmapFactory.Options opts = new BitmapFactory.Options();\n        opts.inJustDecodeBounds = true;\n        BitmapFactory.decodeFile(file.getAbsolutePath(), opts);\n        return new int[]{opts.outWidth, opts.outHeight};\n    }\n\n    /**\n     * Return the sample size.\n     *\n     * @param options   The options.\n     * @param maxWidth  The maximum width.\n     * @param maxHeight The maximum height.\n     * @return the sample size\n     */\n    public static int calculateInSampleSize(final BitmapFactory.Options options,\n                                            final int maxWidth,\n                                            final int maxHeight) {\n        int height = options.outHeight;\n        int width = options.outWidth;\n        int inSampleSize = 1;\n        while (height > maxHeight || width > maxWidth) {\n            height >>= 1;\n            width >>= 1;\n            inSampleSize <<= 1;\n        }\n        return inSampleSize;\n    }\n\n    public enum ImageType {\n        TYPE_JPG(\"jpg\"),\n\n        TYPE_PNG(\"png\"),\n\n        TYPE_GIF(\"gif\"),\n\n        TYPE_TIFF(\"tiff\"),\n\n        TYPE_BMP(\"bmp\"),\n\n        TYPE_WEBP(\"webp\"),\n\n        TYPE_ICO(\"ico\"),\n\n        TYPE_UNKNOWN(\"unknown\");\n\n        String value;\n\n        ImageType(String value) {\n            this.value = value;\n        }\n\n        public String getValue() {\n            return value;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/IntentUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.provider.MediaStore;\nimport android.provider.Settings;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport androidx.annotation.RequiresPermission;\nimport androidx.core.content.FileProvider;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.LinkedList;\nimport java.util.List;\n\nimport static android.Manifest.permission.CALL_PHONE;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/09/23\n *     desc  : utils about intent\n * </pre>\n */\npublic final class IntentUtils {\n\n    private IntentUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return whether the intent is available.\n     *\n     * @param intent The intent.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isIntentAvailable(final Intent intent) {\n        return Utils.getApp()\n                .getPackageManager()\n                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)\n                .size() > 0;\n    }\n\n    /**\n     * Return the intent of install app.\n     * <p>Target APIs greater than 25 must hold\n     * {@code <uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />}</p>\n     *\n     * @param filePath The path of file.\n     * @return the intent of install app\n     */\n    public static Intent getInstallAppIntent(final String filePath) {\n        return getInstallAppIntent(UtilsBridge.getFileByPath(filePath));\n    }\n\n    /**\n     * Return the intent of install app.\n     * <p>Target APIs greater than 25 must hold\n     * {@code <uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />}</p>\n     *\n     * @param file The file.\n     * @return the intent of install app\n     */\n    public static Intent getInstallAppIntent(final File file) {\n        if (!UtilsBridge.isFileExists(file)) return null;\n        Uri uri;\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {\n            uri = Uri.fromFile(file);\n        } else {\n            String authority = Utils.getApp().getPackageName() + \".utilcode.provider\";\n            uri = FileProvider.getUriForFile(Utils.getApp(), authority, file);\n        }\n        return getInstallAppIntent(uri);\n    }\n\n    /**\n     * Return the intent of install app.\n     * <p>Target APIs greater than 25 must hold\n     * {@code <uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />}</p>\n     *\n     * @param uri The uri.\n     * @return the intent of install app\n     */\n    public static Intent getInstallAppIntent(final Uri uri) {\n        if (uri == null) return null;\n        Intent intent = new Intent(Intent.ACTION_VIEW);\n        String type = \"application/vnd.android.package-archive\";\n        intent.setDataAndType(uri, type);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n        }\n        return intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n    }\n\n    /**\n     * Return the intent of uninstall app.\n     * <p>Target APIs greater than 25 must hold\n     * Must hold {@code <uses-permission android:name=\"android.permission.REQUEST_DELETE_PACKAGES\" />}</p>\n     *\n     * @param pkgName The name of the package.\n     * @return the intent of uninstall app\n     */\n    public static Intent getUninstallAppIntent(final String pkgName) {\n        Intent intent = new Intent(Intent.ACTION_DELETE);\n        intent.setData(Uri.parse(\"package:\" + pkgName));\n        return intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n    }\n\n    /**\n     * Return the intent of launch app.\n     *\n     * @param pkgName The name of the package.\n     * @return the intent of launch app\n     */\n    public static Intent getLaunchAppIntent(final String pkgName) {\n        String launcherActivity = UtilsBridge.getLauncherActivity(pkgName);\n        if (UtilsBridge.isSpace(launcherActivity)) return null;\n        Intent intent = new Intent(Intent.ACTION_MAIN);\n        intent.addCategory(Intent.CATEGORY_LAUNCHER);\n        intent.setClassName(pkgName, launcherActivity);\n        return intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n    }\n\n    /**\n     * Return the intent of launch app details settings.\n     *\n     * @param pkgName The name of the package.\n     * @return the intent of launch app details settings\n     */\n    public static Intent getLaunchAppDetailsSettingsIntent(final String pkgName) {\n        return getLaunchAppDetailsSettingsIntent(pkgName, false);\n    }\n\n    /**\n     * Return the intent of launch app details settings.\n     *\n     * @param pkgName The name of the package.\n     * @return the intent of launch app details settings\n     */\n    public static Intent getLaunchAppDetailsSettingsIntent(final String pkgName, final boolean isNewTask) {\n        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);\n        intent.setData(Uri.parse(\"package:\" + pkgName));\n        return getIntent(intent, isNewTask);\n    }\n\n    /**\n     * Return the intent of share text.\n     *\n     * @param content The content.\n     * @return the intent of share text\n     */\n    public static Intent getShareTextIntent(final String content) {\n        Intent intent = new Intent(Intent.ACTION_SEND);\n        intent.setType(\"text/plain\");\n        intent.putExtra(Intent.EXTRA_TEXT, content);\n        intent = Intent.createChooser(intent, \"\");\n        return getIntent(intent, true);\n    }\n\n    /**\n     * Return the intent of share image.\n     *\n     * @param imagePath The path of image.\n     * @return the intent of share image\n     */\n    public static Intent getShareImageIntent(final String imagePath) {\n        return getShareTextImageIntent(\"\", imagePath);\n    }\n\n    /**\n     * Return the intent of share image.\n     *\n     * @param imageFile The file of image.\n     * @return the intent of share image\n     */\n    public static Intent getShareImageIntent(final File imageFile) {\n        return getShareTextImageIntent(\"\", imageFile);\n    }\n\n    /**\n     * Return the intent of share image.\n     *\n     * @param imageUri The uri of image.\n     * @return the intent of share image\n     */\n    public static Intent getShareImageIntent(final Uri imageUri) {\n        return getShareTextImageIntent(\"\", imageUri);\n    }\n\n    /**\n     * Return the intent of share image.\n     *\n     * @param content   The content.\n     * @param imagePath The path of image.\n     * @return the intent of share image\n     */\n    public static Intent getShareTextImageIntent(@Nullable final String content, final String imagePath) {\n        return getShareTextImageIntent(content, UtilsBridge.getFileByPath(imagePath));\n    }\n\n    /**\n     * Return the intent of share image.\n     *\n     * @param content   The content.\n     * @param imageFile The file of image.\n     * @return the intent of share image\n     */\n    public static Intent getShareTextImageIntent(@Nullable final String content, final File imageFile) {\n        return getShareTextImageIntent(content, UtilsBridge.file2Uri(imageFile));\n    }\n\n    /**\n     * Return the intent of share image.\n     *\n     * @param content  The content.\n     * @param imageUri The uri of image.\n     * @return the intent of share image\n     */\n    public static Intent getShareTextImageIntent(@Nullable final String content, final Uri imageUri) {\n        Intent intent = new Intent(Intent.ACTION_SEND);\n        intent.putExtra(Intent.EXTRA_TEXT, content);\n        intent.putExtra(Intent.EXTRA_STREAM, imageUri);\n        intent.setType(\"image/*\");\n        intent = Intent.createChooser(intent, \"\");\n        return getIntent(intent, true);\n    }\n\n    /**\n     * Return the intent of share images.\n     *\n     * @param imagePaths The paths of images.\n     * @return the intent of share images\n     */\n    public static Intent getShareImageIntent(final LinkedList<String> imagePaths) {\n        return getShareTextImageIntent(\"\", imagePaths);\n    }\n\n    /**\n     * Return the intent of share images.\n     *\n     * @param images The files of images.\n     * @return the intent of share images\n     */\n    public static Intent getShareImageIntent(final List<File> images) {\n        return getShareTextImageIntent(\"\", images);\n    }\n\n    /**\n     * Return the intent of share images.\n     *\n     * @param uris The uris of image.\n     * @return the intent of share image\n     */\n    public static Intent getShareImageIntent(final ArrayList<Uri> uris) {\n        return getShareTextImageIntent(\"\", uris);\n    }\n\n    /**\n     * Return the intent of share images.\n     *\n     * @param content    The content.\n     * @param imagePaths The paths of images.\n     * @return the intent of share images\n     */\n    public static Intent getShareTextImageIntent(@Nullable final String content,\n                                                 final LinkedList<String> imagePaths) {\n        List<File> files = new ArrayList<>();\n        if (imagePaths != null) {\n            for (String imagePath : imagePaths) {\n                File file = UtilsBridge.getFileByPath(imagePath);\n                if (file != null) {\n                    files.add(file);\n                }\n            }\n        }\n        return getShareTextImageIntent(content, files);\n    }\n\n    /**\n     * Return the intent of share images.\n     *\n     * @param content The content.\n     * @param images  The files of images.\n     * @return the intent of share images\n     */\n    public static Intent getShareTextImageIntent(@Nullable final String content, final List<File> images) {\n        ArrayList<Uri> uris = new ArrayList<>();\n        if (images != null) {\n            for (File image : images) {\n                Uri uri = UtilsBridge.file2Uri(image);\n                if (uri != null) {\n                    uris.add(uri);\n                }\n            }\n        }\n        return getShareTextImageIntent(content, uris);\n    }\n\n    /**\n     * Return the intent of share images.\n     *\n     * @param content The content.\n     * @param uris    The uris of image.\n     * @return the intent of share image\n     */\n    public static Intent getShareTextImageIntent(@Nullable final String content, final ArrayList<Uri> uris) {\n        Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);\n        intent.putExtra(Intent.EXTRA_TEXT, content);\n        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);\n        intent.setType(\"image/*\");\n        intent = Intent.createChooser(intent, \"\");\n        return getIntent(intent, true);\n    }\n\n    /**\n     * Return the intent of component.\n     *\n     * @param pkgName   The name of the package.\n     * @param className The name of class.\n     * @return the intent of component\n     */\n    public static Intent getComponentIntent(final String pkgName, final String className) {\n        return getComponentIntent(pkgName, className, null, false);\n    }\n\n    /**\n     * Return the intent of component.\n     *\n     * @param pkgName   The name of the package.\n     * @param className The name of class.\n     * @param isNewTask True to add flag of new task, false otherwise.\n     * @return the intent of component\n     */\n    public static Intent getComponentIntent(final String pkgName,\n                                            final String className,\n                                            final boolean isNewTask) {\n        return getComponentIntent(pkgName, className, null, isNewTask);\n    }\n\n    /**\n     * Return the intent of component.\n     *\n     * @param pkgName   The name of the package.\n     * @param className The name of class.\n     * @param bundle    The Bundle of extras to add to this intent.\n     * @return the intent of component\n     */\n    public static Intent getComponentIntent(final String pkgName,\n                                            final String className,\n                                            final Bundle bundle) {\n        return getComponentIntent(pkgName, className, bundle, false);\n    }\n\n    /**\n     * Return the intent of component.\n     *\n     * @param pkgName   The name of the package.\n     * @param className The name of class.\n     * @param bundle    The Bundle of extras to add to this intent.\n     * @param isNewTask True to add flag of new task, false otherwise.\n     * @return the intent of component\n     */\n    public static Intent getComponentIntent(final String pkgName,\n                                            final String className,\n                                            final Bundle bundle,\n                                            final boolean isNewTask) {\n        Intent intent = new Intent();\n        if (bundle != null) intent.putExtras(bundle);\n        ComponentName cn = new ComponentName(pkgName, className);\n        intent.setComponent(cn);\n        return getIntent(intent, isNewTask);\n    }\n\n    /**\n     * Return the intent of shutdown.\n     * <p>Requires root permission\n     * or hold {@code android:sharedUserId=\"android.uid.system\"},\n     * {@code <uses-permission android:name=\"android.permission.SHUTDOWN\" />}\n     * in manifest.</p>\n     *\n     * @return the intent of shutdown\n     */\n    public static Intent getShutdownIntent() {\n        Intent intent;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            intent = new Intent(\"com.android.internal.intent.action.REQUEST_SHUTDOWN\");\n        } else {\n            intent = new Intent(\"android.intent.action.ACTION_REQUEST_SHUTDOWN\");\n        }\n        intent.putExtra(\"android.intent.extra.KEY_CONFIRM\", false);\n        return intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n    }\n\n    /**\n     * Return the intent of dial.\n     *\n     * @param phoneNumber The phone number.\n     * @return the intent of dial\n     */\n    public static Intent getDialIntent(@NonNull final String phoneNumber) {\n        Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(\"tel:\" + Uri.encode(phoneNumber)));\n        return getIntent(intent, true);\n    }\n\n    /**\n     * Return the intent of call.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.CALL_PHONE\" />}</p>\n     *\n     * @param phoneNumber The phone number.\n     * @return the intent of call\n     */\n    @RequiresPermission(CALL_PHONE)\n    public static Intent getCallIntent(@NonNull final String phoneNumber) {\n        Intent intent = new Intent(\"android.intent.action.CALL\", Uri.parse(\"tel:\" + Uri.encode(phoneNumber)));\n        return getIntent(intent, true);\n    }\n\n    /**\n     * Return the intent of send SMS.\n     *\n     * @param phoneNumber The phone number.\n     * @param content     The content of SMS.\n     * @return the intent of send SMS\n     */\n    public static Intent getSendSmsIntent(@NonNull final String phoneNumber, final String content) {\n        Uri uri = Uri.parse(\"smsto:\" + Uri.encode(phoneNumber));\n        Intent intent = new Intent(Intent.ACTION_SENDTO, uri);\n        intent.putExtra(\"sms_body\", content);\n        return getIntent(intent, true);\n    }\n\n    /**\n     * Return the intent of capture.\n     *\n     * @param outUri The uri of output.\n     * @return the intent of capture\n     */\n    public static Intent getCaptureIntent(final Uri outUri) {\n        return getCaptureIntent(outUri, false);\n    }\n\n    /**\n     * Return the intent of capture.\n     *\n     * @param outUri    The uri of output.\n     * @param isNewTask True to add flag of new task, false otherwise.\n     * @return the intent of capture\n     */\n    public static Intent getCaptureIntent(final Uri outUri, final boolean isNewTask) {\n        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);\n        intent.putExtra(MediaStore.EXTRA_OUTPUT, outUri);\n        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n        return getIntent(intent, isNewTask);\n    }\n\n    private static Intent getIntent(final Intent intent, final boolean isNewTask) {\n        return isNewTask ? intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) : intent;\n    }\n\n//    /**\n//     * 获取选择照片的 Intent\n//     *\n//     * @return\n//     */\n//    public static Intent getPickIntentWithGallery() {\n//        Intent intent = new Intent(Intent.ACTION_PICK);\n//        return intent.setType(\"image*//*\");\n//    }\n//\n//    /**\n//     * 获取从文件中选择照片的 Intent\n//     *\n//     * @return\n//     */\n//    public static Intent getPickIntentWithDocuments() {\n//        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);\n//        return intent.setType(\"image*//*\");\n//    }\n//\n//\n//    public static Intent buildImageGetIntent(final Uri saveTo, final int outputX, final int outputY, final boolean returnData) {\n//        return buildImageGetIntent(saveTo, 1, 1, outputX, outputY, returnData);\n//    }\n//\n//    public static Intent buildImageGetIntent(Uri saveTo, int aspectX, int aspectY,\n//                                             int outputX, int outputY, boolean returnData) {\n//        Intent intent = new Intent();\n//        if (Build.VERSION.SDK_INT < 19) {\n//            intent.setAction(Intent.ACTION_GET_CONTENT);\n//        } else {\n//            intent.setAction(Intent.ACTION_OPEN_DOCUMENT);\n//            intent.addCategory(Intent.CATEGORY_OPENABLE);\n//        }\n//        intent.setType(\"image*//*\");\n//        intent.putExtra(\"output\", saveTo);\n//        intent.putExtra(\"aspectX\", aspectX);\n//        intent.putExtra(\"aspectY\", aspectY);\n//        intent.putExtra(\"outputX\", outputX);\n//        intent.putExtra(\"outputY\", outputY);\n//        intent.putExtra(\"scale\", true);\n//        intent.putExtra(\"return-data\", returnData);\n//        intent.putExtra(\"outputFormat\", Bitmap.CompressFormat.PNG.toString());\n//        return intent;\n//    }\n//\n//    public static Intent buildImageCropIntent(final Uri uriFrom, final Uri uriTo, final int outputX, final int outputY, final boolean returnData) {\n//        return buildImageCropIntent(uriFrom, uriTo, 1, 1, outputX, outputY, returnData);\n//    }\n//\n//    public static Intent buildImageCropIntent(Uri uriFrom, Uri uriTo, int aspectX, int aspectY,\n//                                              int outputX, int outputY, boolean returnData) {\n//        Intent intent = new Intent(\"com.android.camera.action.CROP\");\n//        intent.setDataAndType(uriFrom, \"image*//*\");\n//        intent.putExtra(\"crop\", \"true\");\n//        intent.putExtra(\"output\", uriTo);\n//        intent.putExtra(\"aspectX\", aspectX);\n//        intent.putExtra(\"aspectY\", aspectY);\n//        intent.putExtra(\"outputX\", outputX);\n//        intent.putExtra(\"outputY\", outputY);\n//        intent.putExtra(\"scale\", true);\n//        intent.putExtra(\"return-data\", returnData);\n//        intent.putExtra(\"outputFormat\", Bitmap.CompressFormat.PNG.toString());\n//        return intent;\n//    }\n//\n//    public static Intent buildImageCaptureIntent(final Uri uri) {\n//        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);\n//        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);\n//        return intent;\n//    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/JsonUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/01/07\n *     desc  : utils about json\n * </pre>\n */\npublic final class JsonUtils {\n\n    private static final byte TYPE_BOOLEAN     = 0x00;\n    private static final byte TYPE_INT         = 0x01;\n    private static final byte TYPE_LONG        = 0x02;\n    private static final byte TYPE_DOUBLE      = 0x03;\n    private static final byte TYPE_STRING      = 0x04;\n    private static final byte TYPE_JSON_OBJECT = 0x05;\n    private static final byte TYPE_JSON_ARRAY  = 0x06;\n\n    private JsonUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    public static boolean getBoolean(final JSONObject jsonObject,\n                                     final String key) {\n        return getBoolean(jsonObject, key, false);\n    }\n\n    public static boolean getBoolean(final JSONObject jsonObject,\n                                     final String key,\n                                     final boolean defaultValue) {\n        return getValueByType(jsonObject, key, defaultValue, TYPE_BOOLEAN);\n    }\n\n    public static boolean getBoolean(final String json,\n                                     final String key) {\n        return getBoolean(json, key, false);\n    }\n\n    public static boolean getBoolean(final String json,\n                                     final String key,\n                                     final boolean defaultValue) {\n        return getValueByType(json, key, defaultValue, TYPE_BOOLEAN);\n    }\n\n    public static int getInt(final JSONObject jsonObject,\n                             final String key) {\n        return getInt(jsonObject, key, -1);\n    }\n\n    public static int getInt(final JSONObject jsonObject,\n                             final String key,\n                             final int defaultValue) {\n        return getValueByType(jsonObject, key, defaultValue, TYPE_INT);\n    }\n\n    public static int getInt(final String json,\n                             final String key) {\n        return getInt(json, key, -1);\n    }\n\n    public static int getInt(final String json,\n                             final String key,\n                             final int defaultValue) {\n        return getValueByType(json, key, defaultValue, TYPE_INT);\n    }\n\n    public static long getLong(final JSONObject jsonObject,\n                               final String key) {\n        return getLong(jsonObject, key, -1);\n    }\n\n    public static long getLong(final JSONObject jsonObject,\n                               final String key,\n                               final long defaultValue) {\n        return getValueByType(jsonObject, key, defaultValue, TYPE_LONG);\n    }\n\n    public static long getLong(final String json,\n                               final String key) {\n        return getLong(json, key, -1);\n    }\n\n    public static long getLong(final String json,\n                               final String key,\n                               final long defaultValue) {\n        return getValueByType(json, key, defaultValue, TYPE_LONG);\n    }\n\n    public static double getDouble(final JSONObject jsonObject,\n                                   final String key) {\n        return getDouble(jsonObject, key, -1);\n    }\n\n    public static double getDouble(final JSONObject jsonObject,\n                                   final String key,\n                                   final double defaultValue) {\n        return getValueByType(jsonObject, key, defaultValue, TYPE_DOUBLE);\n    }\n\n    public static double getDouble(final String json,\n                                   final String key) {\n        return getDouble(json, key, -1);\n    }\n\n    public static double getDouble(final String json,\n                                   final String key,\n                                   final double defaultValue) {\n        return getValueByType(json, key, defaultValue, TYPE_DOUBLE);\n    }\n\n    public static String getString(final JSONObject jsonObject,\n                                   final String key) {\n        return getString(jsonObject, key, \"\");\n    }\n\n    public static String getString(final JSONObject jsonObject,\n                                   final String key,\n                                   final String defaultValue) {\n        return getValueByType(jsonObject, key, defaultValue, TYPE_STRING);\n    }\n\n    public static String getString(final String json,\n                                   final String key) {\n        return getString(json, key, \"\");\n    }\n\n    public static String getString(final String json,\n                                   final String key,\n                                   final String defaultValue) {\n        return getValueByType(json, key, defaultValue, TYPE_STRING);\n    }\n\n    public static JSONObject getJSONObject(final JSONObject jsonObject,\n                                           final String key,\n                                           final JSONObject defaultValue) {\n        return getValueByType(jsonObject, key, defaultValue, TYPE_JSON_OBJECT);\n    }\n\n    public static JSONObject getJSONObject(final String json,\n                                           final String key,\n                                           final JSONObject defaultValue) {\n        return getValueByType(json, key, defaultValue, TYPE_JSON_OBJECT);\n    }\n\n    public static JSONArray getJSONArray(final JSONObject jsonObject,\n                                         final String key,\n                                         final JSONArray defaultValue) {\n        return getValueByType(jsonObject, key, defaultValue, TYPE_JSON_ARRAY);\n    }\n\n    public static JSONArray getJSONArray(final String json,\n                                         final String key,\n                                         final JSONArray defaultValue) {\n        return getValueByType(json, key, defaultValue, TYPE_JSON_ARRAY);\n    }\n\n    private static <T> T getValueByType(final JSONObject jsonObject,\n                                        final String key,\n                                        final T defaultValue,\n                                        final byte type) {\n        if (jsonObject == null || key == null || key.length() == 0) {\n            return defaultValue;\n        }\n        try {\n            Object ret;\n            if (type == TYPE_BOOLEAN) {\n                ret = jsonObject.getBoolean(key);\n            } else if (type == TYPE_INT) {\n                ret = jsonObject.getInt(key);\n            } else if (type == TYPE_LONG) {\n                ret = jsonObject.getLong(key);\n            } else if (type == TYPE_DOUBLE) {\n                ret = jsonObject.getDouble(key);\n            } else if (type == TYPE_STRING) {\n                ret = jsonObject.getString(key);\n            } else if (type == TYPE_JSON_OBJECT) {\n                ret = jsonObject.getJSONObject(key);\n            } else if (type == TYPE_JSON_ARRAY) {\n                ret = jsonObject.getJSONArray(key);\n            } else {\n                return defaultValue;\n            }\n            //noinspection unchecked\n            return (T) ret;\n        } catch (JSONException e) {\n            e.printStackTrace();\n            return defaultValue;\n        }\n    }\n\n    private static <T> T getValueByType(final String json,\n                                        final String key,\n                                        final T defaultValue,\n                                        final byte type) {\n        if (json == null || json.length() == 0\n                || key == null || key.length() == 0) {\n            return defaultValue;\n        }\n        try {\n            return getValueByType(new JSONObject(json), key, defaultValue, type);\n        } catch (JSONException e) {\n            e.printStackTrace();\n            return defaultValue;\n        }\n    }\n\n    public static String formatJson(final String json) {\n        return formatJson(json, 4);\n    }\n\n    public static String formatJson(final String json, final int indentSpaces) {\n        try {\n            for (int i = 0, len = json.length(); i < len; i++) {\n                char c = json.charAt(i);\n                if (c == '{') {\n                    return new JSONObject(json).toString(indentSpaces);\n                } else if (c == '[') {\n                    return new JSONArray(json).toString(indentSpaces);\n                } else if (!Character.isWhitespace(c)) {\n                    return json;\n                }\n            }\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n        return json;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/KeyboardUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.graphics.Rect;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.ResultReceiver;\nimport android.os.SystemClock;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.ViewTreeObserver.OnGlobalLayoutListener;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.EditText;\nimport android.widget.FrameLayout;\n\nimport java.lang.reflect.Field;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/02\n *     desc  : utils about keyboard\n * </pre>\n */\npublic final class KeyboardUtils {\n\n    private static final int TAG_ON_GLOBAL_LAYOUT_LISTENER = -8;\n\n    private KeyboardUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Show the soft input.\n     */\n    public static void showSoftInput() {\n        InputMethodManager imm = (InputMethodManager) Utils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE);\n        if (imm == null) {\n            return;\n        }\n        imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);\n    }\n\n    /**\n     * Show the soft input.\n     */\n    public static void showSoftInput(@NonNull Activity activity) {\n        if (!isSoftInputVisible(activity)) {\n            toggleSoftInput();\n        }\n    }\n\n    /**\n     * Show the soft input.\n     *\n     * @param view The view.\n     */\n    public static void showSoftInput(@NonNull final View view) {\n        showSoftInput(view, 0);\n    }\n\n    /**\n     * Show the soft input.\n     *\n     * @param view  The view.\n     * @param flags Provides additional operating flags.  Currently may be\n     *              0 or have the {@link InputMethodManager#SHOW_IMPLICIT} bit set.\n     */\n    public static void showSoftInput(@NonNull final View view, final int flags) {\n        InputMethodManager imm =\n                (InputMethodManager) Utils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE);\n        if (imm == null) return;\n        view.setFocusable(true);\n        view.setFocusableInTouchMode(true);\n        view.requestFocus();\n        imm.showSoftInput(view, flags, new ResultReceiver(new Handler()) {\n            @Override\n            protected void onReceiveResult(int resultCode, Bundle resultData) {\n                if (resultCode == InputMethodManager.RESULT_UNCHANGED_HIDDEN\n                        || resultCode == InputMethodManager.RESULT_HIDDEN) {\n                    toggleSoftInput();\n                }\n            }\n        });\n        imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);\n    }\n\n    /**\n     * Hide the soft input.\n     *\n     * @param activity The activity.\n     */\n    public static void hideSoftInput(@NonNull final Activity activity) {\n        hideSoftInput(activity.getWindow());\n    }\n\n    /**\n     * Hide the soft input.\n     *\n     * @param window The window.\n     */\n    public static void hideSoftInput(@NonNull final Window window) {\n        View view = window.getCurrentFocus();\n        if (view == null) {\n            View decorView = window.getDecorView();\n            View focusView = decorView.findViewWithTag(\"keyboardTagView\");\n            if (focusView == null) {\n                view = new EditText(window.getContext());\n                view.setTag(\"keyboardTagView\");\n                ((ViewGroup) decorView).addView(view, 0, 0);\n            } else {\n                view = focusView;\n            }\n            view.requestFocus();\n        }\n        hideSoftInput(view);\n    }\n\n    /**\n     * Hide the soft input.\n     *\n     * @param view The view.\n     */\n    public static void hideSoftInput(@NonNull final View view) {\n        InputMethodManager imm =\n                (InputMethodManager) Utils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE);\n        if (imm == null) return;\n        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);\n    }\n\n    private static long millis;\n\n    /**\n     * Hide the soft input.\n     *\n     * @param activity The activity.\n     */\n    public static void hideSoftInputByToggle(final Activity activity) {\n        long nowMillis = SystemClock.elapsedRealtime();\n        long delta = nowMillis - millis;\n        if (Math.abs(delta) > 500 && KeyboardUtils.isSoftInputVisible(activity)) {\n            KeyboardUtils.toggleSoftInput();\n        }\n        millis = nowMillis;\n    }\n\n    /**\n     * Toggle the soft input display or not.\n     */\n    public static void toggleSoftInput() {\n        InputMethodManager imm =\n                (InputMethodManager) Utils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE);\n        if (imm == null) return;\n        imm.toggleSoftInput(0, 0);\n    }\n\n    private static int sDecorViewDelta = 0;\n\n    /**\n     * Return whether soft input is visible.\n     *\n     * @param activity The activity.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isSoftInputVisible(@NonNull final Activity activity) {\n        return getDecorViewInvisibleHeight(activity.getWindow()) > 0;\n    }\n\n    private static int getDecorViewInvisibleHeight(@NonNull final Window window) {\n        final View decorView = window.getDecorView();\n        final Rect outRect = new Rect();\n        decorView.getWindowVisibleDisplayFrame(outRect);\n        Log.d(\"KeyboardUtils\", \"getDecorViewInvisibleHeight: \"\n                + (decorView.getBottom() - outRect.bottom));\n        int delta = Math.abs(decorView.getBottom() - outRect.bottom);\n        if (delta <= UtilsBridge.getNavBarHeight() + UtilsBridge.getStatusBarHeight()) {\n            sDecorViewDelta = delta;\n            return 0;\n        }\n        return delta - sDecorViewDelta;\n    }\n\n    /**\n     * Register soft input changed listener.\n     *\n     * @param activity The activity.\n     * @param listener The soft input changed listener.\n     */\n    public static void registerSoftInputChangedListener(@NonNull final Activity activity,\n                                                        @NonNull final OnSoftInputChangedListener listener) {\n        registerSoftInputChangedListener(activity.getWindow(), listener);\n    }\n\n    /**\n     * Register soft input changed listener.\n     *\n     * @param window   The window.\n     * @param listener The soft input changed listener.\n     */\n    public static void registerSoftInputChangedListener(@NonNull final Window window,\n                                                        @NonNull final OnSoftInputChangedListener listener) {\n        final int flags = window.getAttributes().flags;\n        if ((flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) != 0) {\n            window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);\n        }\n        final FrameLayout contentView = window.findViewById(android.R.id.content);\n        final int[] decorViewInvisibleHeightPre = {getDecorViewInvisibleHeight(window)};\n        OnGlobalLayoutListener onGlobalLayoutListener = new OnGlobalLayoutListener() {\n            @Override\n            public void onGlobalLayout() {\n                int height = getDecorViewInvisibleHeight(window);\n                if (decorViewInvisibleHeightPre[0] != height) {\n                    listener.onSoftInputChanged(height);\n                    decorViewInvisibleHeightPre[0] = height;\n                }\n            }\n        };\n        contentView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);\n        contentView.setTag(TAG_ON_GLOBAL_LAYOUT_LISTENER, onGlobalLayoutListener);\n    }\n\n    /**\n     * Unregister soft input changed listener.\n     *\n     * @param window The window.\n     */\n    public static void unregisterSoftInputChangedListener(@NonNull final Window window) {\n        final View contentView = window.findViewById(android.R.id.content);\n        if (contentView == null) return;\n        Object tag = contentView.getTag(TAG_ON_GLOBAL_LAYOUT_LISTENER);\n        if (tag instanceof OnGlobalLayoutListener) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n                contentView.getViewTreeObserver().removeOnGlobalLayoutListener((OnGlobalLayoutListener) tag);\n            }\n        }\n    }\n\n    /**\n     * Fix the bug of 5497 in Android.\n     * <p>Don't set adjustResize</p>\n     *\n     * @param activity The activity.\n     */\n    public static void fixAndroidBug5497(@NonNull final Activity activity) {\n        fixAndroidBug5497(activity.getWindow());\n    }\n\n    /**\n     * Fix the bug of 5497 in Android.\n     * <p>It will clean the adjustResize</p>\n     *\n     * @param window The window.\n     */\n    public static void fixAndroidBug5497(@NonNull final Window window) {\n        int softInputMode = window.getAttributes().softInputMode;\n        window.setSoftInputMode(softInputMode & ~WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);\n        final FrameLayout contentView = window.findViewById(android.R.id.content);\n        final View contentViewChild = contentView.getChildAt(0);\n        final int paddingBottom = contentViewChild.getPaddingBottom();\n        final int[] contentViewInvisibleHeightPre5497 = {getContentViewInvisibleHeight(window)};\n        contentView.getViewTreeObserver()\n                .addOnGlobalLayoutListener(new OnGlobalLayoutListener() {\n                    @Override\n                    public void onGlobalLayout() {\n                        int height = getContentViewInvisibleHeight(window);\n                        if (contentViewInvisibleHeightPre5497[0] != height) {\n                            contentViewChild.setPadding(\n                                    contentViewChild.getPaddingLeft(),\n                                    contentViewChild.getPaddingTop(),\n                                    contentViewChild.getPaddingRight(),\n                                    paddingBottom + getDecorViewInvisibleHeight(window)\n                            );\n                            contentViewInvisibleHeightPre5497[0] = height;\n                        }\n                    }\n                });\n    }\n\n    private static int getContentViewInvisibleHeight(final Window window) {\n        final View contentView = window.findViewById(android.R.id.content);\n        if (contentView == null) return 0;\n        final Rect outRect = new Rect();\n        contentView.getWindowVisibleDisplayFrame(outRect);\n        Log.d(\"KeyboardUtils\", \"getContentViewInvisibleHeight: \"\n                + (contentView.getBottom() - outRect.bottom));\n        int delta = Math.abs(contentView.getBottom() - outRect.bottom);\n        if (delta <= UtilsBridge.getStatusBarHeight() + UtilsBridge.getNavBarHeight()) {\n            return 0;\n        }\n        return delta;\n    }\n\n    /**\n     * Fix the leaks of soft input.\n     *\n     * @param activity The activity.\n     */\n    public static void fixSoftInputLeaks(@NonNull final Activity activity) {\n        fixSoftInputLeaks(activity.getWindow());\n    }\n\n    /**\n     * Fix the leaks of soft input.\n     *\n     * @param window The window.\n     */\n    public static void fixSoftInputLeaks(@NonNull final Window window) {\n        InputMethodManager imm =\n                (InputMethodManager) Utils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE);\n        if (imm == null) return;\n        String[] leakViews = new String[]{\"mLastSrvView\", \"mCurRootView\", \"mServedView\", \"mNextServedView\"};\n        for (String leakView : leakViews) {\n            try {\n                Field leakViewField = InputMethodManager.class.getDeclaredField(leakView);\n                if (!leakViewField.isAccessible()) {\n                    leakViewField.setAccessible(true);\n                }\n                Object obj = leakViewField.get(imm);\n                if (!(obj instanceof View)) continue;\n                View view = (View) obj;\n                if (view.getRootView() == window.getDecorView().getRootView()) {\n                    leakViewField.set(imm, null);\n                }\n            } catch (Throwable ignore) {/**/}\n        }\n    }\n\n    /**\n     * Click blank area to hide soft input.\n     * <p>Copy the following code in ur activity.</p>\n     */\n    public static void clickBlankArea2HideSoftInput() {\n        Log.i(\"KeyboardUtils\", \"Please refer to the following code.\");\n        /*\n        @Override\n        public boolean dispatchTouchEvent(MotionEvent ev) {\n            if (ev.getAction() == MotionEvent.ACTION_DOWN) {\n                View v = getCurrentFocus();\n                if (isShouldHideKeyboard(v, ev)) {\n                    KeyboardUtils.hideSoftInput(this);\n                }\n            }\n            return super.dispatchTouchEvent(ev);\n        }\n\n        // Return whether touch the view.\n        private boolean isShouldHideKeyboard(View v, MotionEvent event) {\n            if ((v instanceof EditText)) {\n                int[] l = {0, 0};\n                v.getLocationOnScreen(l);\n                int left = l[0],\n                        top = l[1],\n                        bottom = top + v.getHeight(),\n                        right = left + v.getWidth();\n                return !(event.getRawX() > left && event.getRawX() < right\n                        && event.getRawY() > top && event.getRawY() < bottom);\n            }\n            return false;\n        }\n        */\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // interface\n    ///////////////////////////////////////////////////////////////////////////\n    public interface OnSoftInputChangedListener {\n        void onSoftInputChanged(int height);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/LanguageUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.res.Configuration;\nimport android.content.res.Resources;\nimport android.os.Build;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.Nullable;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport java.util.Locale;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/06/20\n *     desc  : utils about language\n * </pre>\n */\npublic class LanguageUtils {\n\n    private static final String KEY_LOCALE          = \"KEY_LOCALE\";\n    private static final String VALUE_FOLLOW_SYSTEM = \"VALUE_FOLLOW_SYSTEM\";\n\n    private LanguageUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Apply the system language.\n     */\n    public static void applySystemLanguage() {\n        applySystemLanguage(false);\n    }\n\n    /**\n     * Apply the system language.\n     *\n     * @param isRelaunchApp True to relaunch app, false to recreate all activities.\n     */\n    public static void applySystemLanguage(final boolean isRelaunchApp) {\n        applyLanguageReal(null, isRelaunchApp);\n    }\n\n    /**\n     * Apply the language.\n     *\n     * @param locale The language of locale.\n     */\n    public static void applyLanguage(@NonNull final Locale locale) {\n        applyLanguage(locale, false);\n    }\n\n    /**\n     * Apply the language.\n     *\n     * @param locale        The language of locale.\n     * @param isRelaunchApp True to relaunch app, false to recreate all activities.\n     */\n    public static void applyLanguage(@NonNull final Locale locale,\n                                     final boolean isRelaunchApp) {\n        applyLanguageReal(locale, isRelaunchApp);\n    }\n\n    private static void applyLanguageReal(final Locale locale,\n                                          final boolean isRelaunchApp) {\n        if (locale == null) {\n            UtilsBridge.getSpUtils4Utils().put(KEY_LOCALE, VALUE_FOLLOW_SYSTEM, true);\n        } else {\n            UtilsBridge.getSpUtils4Utils().put(KEY_LOCALE, locale2String(locale), true);\n        }\n\n        Locale destLocal = locale == null ? getLocal(Resources.getSystem().getConfiguration()) : locale;\n        updateAppContextLanguage(destLocal, new Utils.Consumer<Boolean>() {\n            @Override\n            public void accept(Boolean success) {\n                if (success) {\n                    restart(isRelaunchApp);\n                } else {\n                    // use relaunch app\n                    UtilsBridge.relaunchApp();\n                }\n            }\n        });\n    }\n\n    private static void restart(final boolean isRelaunchApp) {\n        if (isRelaunchApp) {\n            UtilsBridge.relaunchApp();\n        } else {\n            for (Activity activity : UtilsBridge.getActivityList()) {\n                activity.recreate();\n            }\n        }\n    }\n\n    /**\n     * Return whether applied the language by {@link LanguageUtils}.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAppliedLanguage() {\n        return getAppliedLanguage() != null;\n    }\n\n    /**\n     * Return whether applied the language by {@link LanguageUtils}.\n     *\n     * @param locale The locale.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAppliedLanguage(@NonNull Locale locale) {\n        Locale appliedLocale = getAppliedLanguage();\n        if (appliedLocale == null) {\n            return false;\n        }\n        return isSameLocale(locale, appliedLocale);\n    }\n\n    /**\n     * Return the applied locale.\n     *\n     * @return the applied locale\n     */\n    public static Locale getAppliedLanguage() {\n        final String spLocaleStr = UtilsBridge.getSpUtils4Utils().getString(KEY_LOCALE);\n        if (TextUtils.isEmpty(spLocaleStr) || VALUE_FOLLOW_SYSTEM.equals(spLocaleStr)) {\n            return null;\n        }\n        return string2Locale(spLocaleStr);\n    }\n\n    /**\n     * Return the locale of context.\n     *\n     * @return the locale of context\n     */\n    public static Locale getContextLanguage(Context context) {\n        return getLocal(context.getResources().getConfiguration());\n    }\n\n    /**\n     * Return the locale of applicationContext.\n     *\n     * @return the locale of applicationContext\n     */\n    public static Locale getAppContextLanguage() {\n        return getContextLanguage(Utils.getApp());\n    }\n\n    /**\n     * Return the locale of system\n     *\n     * @return the locale of system\n     */\n    public static Locale getSystemLanguage() {\n        return getLocal(Resources.getSystem().getConfiguration());\n    }\n\n    /**\n     * Update the locale of applicationContext.\n     *\n     * @param destLocale The dest locale.\n     * @param consumer   The consumer.\n     */\n    public static void updateAppContextLanguage(@NonNull Locale destLocale, @Nullable Utils.Consumer<Boolean> consumer) {\n        pollCheckAppContextLocal(destLocale, 0, consumer);\n    }\n\n    static void pollCheckAppContextLocal(final Locale destLocale, final int index, final Utils.Consumer<Boolean> consumer) {\n        Resources appResources = Utils.getApp().getResources();\n        Configuration appConfig = appResources.getConfiguration();\n        Locale appLocal = getLocal(appConfig);\n\n        setLocal(appConfig, destLocale);\n\n        Utils.getApp().getResources().updateConfiguration(appConfig, appResources.getDisplayMetrics());\n\n        if (consumer == null) return;\n\n        if (isSameLocale(appLocal, destLocale)) {\n            consumer.accept(true);\n        } else {\n            if (index < 20) {\n                UtilsBridge.runOnUiThreadDelayed(new Runnable() {\n                    @Override\n                    public void run() {\n                        pollCheckAppContextLocal(destLocale, index + 1, consumer);\n                    }\n                }, 16);\n                return;\n            }\n            Log.e(\"LanguageUtils\", \"appLocal didn't update.\");\n            consumer.accept(false);\n        }\n    }\n\n    /**\n     * If applyLanguage not work, try to call it in {@link Activity#attachBaseContext(Context)}.\n     *\n     * @param context The baseContext.\n     * @return the context with language\n     */\n    public static Context attachBaseContext(Context context) {\n        String spLocaleStr = UtilsBridge.getSpUtils4Utils().getString(KEY_LOCALE);\n        if (TextUtils.isEmpty(spLocaleStr) || VALUE_FOLLOW_SYSTEM.equals(spLocaleStr)) {\n            return context;\n        }\n\n        Locale settingsLocale = string2Locale(spLocaleStr);\n        if (settingsLocale == null) return context;\n\n        Resources resources = context.getResources();\n        Configuration config = resources.getConfiguration();\n\n        setLocal(config, settingsLocale);\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            return context.createConfigurationContext(config);\n        } else {\n            resources.updateConfiguration(config, resources.getDisplayMetrics());\n            return context;\n        }\n    }\n\n    static void applyLanguage(final Activity activity) {\n        String spLocale = UtilsBridge.getSpUtils4Utils().getString(KEY_LOCALE);\n        if (TextUtils.isEmpty(spLocale)) {\n            return;\n        }\n\n        Locale destLocal;\n        if (VALUE_FOLLOW_SYSTEM.equals(spLocale)) {\n            destLocal = getLocal(Resources.getSystem().getConfiguration());\n        } else {\n            destLocal = string2Locale(spLocale);\n        }\n\n        if (destLocal == null) return;\n\n        updateConfiguration(activity, destLocal);\n        updateConfiguration(Utils.getApp(), destLocal);\n    }\n\n    private static void updateConfiguration(Context context, Locale destLocal) {\n        Resources resources = context.getResources();\n        Configuration config = resources.getConfiguration();\n        setLocal(config, destLocal);\n        resources.updateConfiguration(config, resources.getDisplayMetrics());\n    }\n\n    private static String locale2String(Locale locale) {\n        String localLanguage = locale.getLanguage(); // this may be empty\n        String localCountry = locale.getCountry(); // this may be empty\n        return localLanguage + \"$\" + localCountry;\n    }\n\n    private static Locale string2Locale(String str) {\n        Locale locale = string2LocaleReal(str);\n        if (locale == null) {\n            Log.e(\"LanguageUtils\", \"The string of \" + str + \" is not in the correct format.\");\n            UtilsBridge.getSpUtils4Utils().remove(KEY_LOCALE);\n        }\n        return locale;\n    }\n\n    private static Locale string2LocaleReal(String str) {\n        if (!isRightFormatLocalStr(str)) {\n            return null;\n        }\n\n        try {\n            int splitIndex = str.indexOf(\"$\");\n            return new Locale(str.substring(0, splitIndex), str.substring(splitIndex + 1));\n        } catch (Exception ignore) {\n            return null;\n        }\n    }\n\n    private static boolean isRightFormatLocalStr(String localStr) {\n        char[] chars = localStr.toCharArray();\n        int count = 0;\n        for (char c : chars) {\n            if (c == '$') {\n                if (count >= 1) {\n                    return false;\n                }\n                ++count;\n            }\n        }\n        return count == 1;\n    }\n\n    private static boolean isSameLocale(Locale l0, Locale l1) {\n        return UtilsBridge.equals(l1.getLanguage(), l0.getLanguage())\n                && UtilsBridge.equals(l1.getCountry(), l0.getCountry());\n    }\n\n    private static Locale getLocal(Configuration configuration) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            return configuration.getLocales().get(0);\n        } else {\n            return configuration.locale;\n        }\n    }\n\n    private static void setLocal(Configuration configuration, Locale locale) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            configuration.setLocale(locale);\n        } else {\n            configuration.locale = locale;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/LocationUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.location.Address;\nimport android.location.Criteria;\nimport android.location.Geocoder;\nimport android.location.Location;\nimport android.location.LocationListener;\nimport android.location.LocationManager;\nimport android.location.LocationProvider;\nimport android.os.Bundle;\nimport android.provider.Settings;\nimport android.util.Log;\n\nimport androidx.annotation.RequiresPermission;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Locale;\n\nimport static android.Manifest.permission.ACCESS_COARSE_LOCATION;\nimport static android.Manifest.permission.ACCESS_FINE_LOCATION;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 16/11/13\n *     desc  : 定位相关工具类\n * </pre>\n */\npublic final class LocationUtils {\n\n    private static final int TWO_MINUTES = 1000 * 60 * 2;\n\n    private static OnLocationChangeListener mListener;\n    private static MyLocationListener       myLocationListener;\n    private static LocationManager          mLocationManager;\n\n    private LocationUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n\n//    /**\n//     * you have to check for Location Permission before use this method\n//     * add this code <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" /> to your Manifest file.\n//     * you have also implement LocationListener and passed it to the method.\n//     *\n//     * @param Context\n//     * @param LocationListener\n//     * @return {@code Location}\n//     */\n//\n//    @SuppressLint(\"MissingPermission\")\n//    public static Location getLocation(Context context, LocationListener listener) {\n//        Location location = null;\n//        try {\n//            mLocationManager = (LocationManager) context.getSystemService(LOCATION_SERVICE);\n//            if (!isLocationEnabled()) {\n//                //no Network and GPS providers is enabled\n//                Toast.makeText(context\n//                        , \" you have to open GPS or INTERNET\"\n//                        , Toast.LENGTH_LONG)\n//                        .show();\n//            } else {\n//                if (isLocationEnabled()) {\n//                    mLocationManager.requestLocationUpdates(\n//                            LocationManager.NETWORK_PROVIDER,\n//                            MIN_TIME_BETWEEN_UPDATES,\n//                            MIN_DISTANCE_CHANGE_FOR_UPDATES,\n//                            listener);\n//\n//                    if (mLocationManager != null) {\n//                        location = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);\n//                        if (location != null) {\n//                            mLocationManager.removeUpdates(listener);\n//                            return location;\n//                        }\n//                    }\n//                }\n//                //when GPS is enabled.\n//                if (isGpsEnabled()) {\n//                    if (location == null) {\n//                        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,\n//                                MIN_TIME_BETWEEN_UPDATES,\n//                                MIN_DISTANCE_CHANGE_FOR_UPDATES,\n//                                listener);\n//\n//                        if (mLocationManager != null) {\n//                            location =\n//                                    mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);\n//                            if (location != null) {\n//                                mLocationManager.removeUpdates(listener);\n//                                return location;\n//                            }\n//                        }\n//                    }\n//                }\n//\n//            }\n//        } catch (Exception e) {\n//            e.printStackTrace();\n//        }\n//\n//        return location;\n//    }\n\n\n    /**\n     * 判断Gps是否可用\n     *\n     * @return {@code true}: 是<br>{@code false}: 否\n     */\n    public static boolean isGpsEnabled() {\n        LocationManager lm = (LocationManager) Utils.getApp().getSystemService(Context.LOCATION_SERVICE);\n        return lm.isProviderEnabled(LocationManager.GPS_PROVIDER);\n    }\n\n    /**\n     * 判断定位是否可用\n     *\n     * @return {@code true}: 是<br>{@code false}: 否\n     */\n    public static boolean isLocationEnabled() {\n        LocationManager lm = (LocationManager) Utils.getApp().getSystemService(Context.LOCATION_SERVICE);\n        return lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)\n                || lm.isProviderEnabled(LocationManager.GPS_PROVIDER);\n    }\n\n    /**\n     * 打开Gps设置界面\n     */\n    public static void openGpsSettings() {\n        Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);\n        Utils.getApp().startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));\n    }\n\n    /**\n     * 注册\n     * <p>使用完记得调用{@link #unregister()}</p>\n     * <p>需添加权限 {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     * <p>需添加权限 {@code <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />}</p>\n     * <p>需添加权限 {@code <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />}</p>\n     * <p>如果{@code minDistance}为0，则通过{@code minTime}来定时更新；</p>\n     * <p>{@code minDistance}不为0，则以{@code minDistance}为准；</p>\n     * <p>两者都为0，则随时刷新。</p>\n     *\n     * @param minTime     位置信息更新周期（单位：毫秒）\n     * @param minDistance 位置变化最小距离：当位置距离变化超过此值时，将更新位置信息（单位：米）\n     * @param listener    位置刷新的回调接口\n     * @return {@code true}: 初始化成功<br>{@code false}: 初始化失败\n     */\n    @RequiresPermission(ACCESS_FINE_LOCATION)\n    public static boolean register(long minTime, long minDistance, OnLocationChangeListener listener) {\n        if (listener == null) return false;\n        mLocationManager = (LocationManager) Utils.getApp().getSystemService(Context.LOCATION_SERVICE);\n        if (!mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)\n                && !mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {\n            Log.d(\"LocationUtils\", \"无法定位，请打开定位服务\");\n            return false;\n        }\n        mListener = listener;\n        String provider = mLocationManager.getBestProvider(getCriteria(), true);\n        Location location = mLocationManager.getLastKnownLocation(provider);\n        if (location != null) listener.getLastKnownLocation(location);\n        if (myLocationListener == null) myLocationListener = new MyLocationListener();\n        mLocationManager.requestLocationUpdates(provider, minTime, minDistance, myLocationListener);\n        return true;\n    }\n\n    /**\n     * 注销\n     */\n    @RequiresPermission(ACCESS_COARSE_LOCATION)\n    public static void unregister() {\n        if (mLocationManager != null) {\n            if (myLocationListener != null) {\n                mLocationManager.removeUpdates(myLocationListener);\n                myLocationListener = null;\n            }\n            mLocationManager = null;\n        }\n        if (mListener != null) {\n            mListener = null;\n        }\n    }\n\n    /**\n     * 设置定位参数\n     *\n     * @return {@link Criteria}\n     */\n    private static Criteria getCriteria() {\n        Criteria criteria = new Criteria();\n        // 设置定位精确度 Criteria.ACCURACY_COARSE比较粗略，Criteria.ACCURACY_FINE则比较精细\n        criteria.setAccuracy(Criteria.ACCURACY_FINE);\n        // 设置是否要求速度\n        criteria.setSpeedRequired(false);\n        // 设置是否允许运营商收费\n        criteria.setCostAllowed(false);\n        // 设置是否需要方位信息\n        criteria.setBearingRequired(false);\n        // 设置是否需要海拔信息\n        criteria.setAltitudeRequired(false);\n        // 设置对电源的需求\n        criteria.setPowerRequirement(Criteria.POWER_LOW);\n        return criteria;\n    }\n\n    /**\n     * 根据经纬度获取地理位置\n     *\n     * @param latitude  纬度\n     * @param longitude 经度\n     * @return {@link Address}\n     */\n    public static Address getAddress(double latitude, double longitude) {\n        Geocoder geocoder = new Geocoder(Utils.getApp(), Locale.getDefault());\n        try {\n            List<Address> addresses = geocoder.getFromLocation(latitude, longitude, 1);\n            if (addresses.size() > 0) return addresses.get(0);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    /**\n     * 根据经纬度获取所在国家\n     *\n     * @param latitude  纬度\n     * @param longitude 经度\n     * @return 所在国家\n     */\n    public static String getCountryName(double latitude, double longitude) {\n        Address address = getAddress(latitude, longitude);\n        return address == null ? \"unknown\" : address.getCountryName();\n    }\n\n    /**\n     * 根据经纬度获取所在地\n     *\n     * @param latitude  纬度\n     * @param longitude 经度\n     * @return 所在地\n     */\n    public static String getLocality(double latitude, double longitude) {\n        Address address = getAddress(latitude, longitude);\n        return address == null ? \"unknown\" : address.getLocality();\n    }\n\n    /**\n     * 根据经纬度获取所在街道\n     *\n     * @param latitude  纬度\n     * @param longitude 经度\n     * @return 所在街道\n     */\n    public static String getStreet(double latitude, double longitude) {\n        Address address = getAddress(latitude, longitude);\n        return address == null ? \"unknown\" : address.getAddressLine(0);\n    }\n\n    /**\n     * 是否更好的位置\n     *\n     * @param newLocation         The new Location that you want to evaluate\n     * @param currentBestLocation The current Location fix, to which you want to compare the new one\n     * @return {@code true}: 是<br>{@code false}: 否\n     */\n    public static boolean isBetterLocation(Location newLocation, Location currentBestLocation) {\n        if (currentBestLocation == null) {\n            // A new location is always better than no location\n            return true;\n        }\n\n        // Check whether the new location fix is newer or older\n        long timeDelta = newLocation.getTime() - currentBestLocation.getTime();\n        boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;\n        boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;\n        boolean isNewer = timeDelta > 0;\n\n        // If it's been more than two minutes since the current location, use the new location\n        // because the user has likely moved\n        if (isSignificantlyNewer) {\n            return true;\n            // If the new location is more than two minutes older, it must be worse\n        } else if (isSignificantlyOlder) {\n            return false;\n        }\n\n        // Check whether the new location fix is more or less accurate\n        int accuracyDelta = (int) (newLocation.getAccuracy() - currentBestLocation.getAccuracy());\n        boolean isLessAccurate = accuracyDelta > 0;\n        boolean isMoreAccurate = accuracyDelta < 0;\n        boolean isSignificantlyLessAccurate = accuracyDelta > 200;\n\n        // Check if the old and new location are from the same provider\n        boolean isFromSameProvider = isSameProvider(newLocation.getProvider(), currentBestLocation.getProvider());\n\n        // Determine location quality using a combination of timeliness and accuracy\n        if (isMoreAccurate) {\n            return true;\n        } else if (isNewer && !isLessAccurate) {\n            return true;\n        } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * 是否相同的提供者\n     *\n     * @param provider0 提供者1\n     * @param provider1 提供者2\n     * @return {@code true}: 是<br>{@code false}: 否\n     */\n    public static boolean isSameProvider(String provider0, String provider1) {\n        if (provider0 == null) {\n            return provider1 == null;\n        }\n        return provider0.equals(provider1);\n    }\n\n    private static class MyLocationListener\n            implements LocationListener {\n        /**\n         * 当坐标改变时触发此函数，如果Provider传进相同的坐标，它就不会被触发\n         *\n         * @param location 坐标\n         */\n        @Override\n        public void onLocationChanged(Location location) {\n            if (mListener != null) {\n                mListener.onLocationChanged(location);\n            }\n        }\n\n        /**\n         * provider的在可用、暂时不可用和无服务三个状态直接切换时触发此函数\n         *\n         * @param provider 提供者\n         * @param status   状态\n         * @param extras   provider可选包\n         */\n        @Override\n        public void onStatusChanged(String provider, int status, Bundle extras) {\n            if (mListener != null) {\n                mListener.onStatusChanged(provider, status, extras);\n            }\n            switch (status) {\n                case LocationProvider.AVAILABLE:\n                    Log.d(\"LocationUtils\", \"当前GPS状态为可见状态\");\n                    break;\n                case LocationProvider.OUT_OF_SERVICE:\n                    Log.d(\"LocationUtils\", \"当前GPS状态为服务区外状态\");\n                    break;\n                case LocationProvider.TEMPORARILY_UNAVAILABLE:\n                    Log.d(\"LocationUtils\", \"当前GPS状态为暂停服务状态\");\n                    break;\n            }\n        }\n\n        /**\n         * provider被enable时触发此函数，比如GPS被打开\n         */\n        @Override\n        public void onProviderEnabled(String provider) {\n        }\n\n        /**\n         * provider被disable时触发此函数，比如GPS被关闭\n         */\n        @Override\n        public void onProviderDisabled(String provider) {\n        }\n    }\n\n    public interface OnLocationChangeListener {\n\n        /**\n         * 获取最后一次保留的坐标\n         *\n         * @param location 坐标\n         */\n        void getLastKnownLocation(Location location);\n\n        /**\n         * 当坐标改变时触发此函数，如果Provider传进相同的坐标，它就不会被触发\n         *\n         * @param location 坐标\n         */\n        void onLocationChanged(Location location);\n\n        /**\n         * provider的在可用、暂时不可用和无服务三个状态直接切换时触发此函数\n         *\n         * @param provider 提供者\n         * @param status   状态\n         * @param extras   provider可选包\n         */\n        void onStatusChanged(String provider, int status, Bundle extras);//位置状态发生改变\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/LogUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.ClipData;\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.graphics.Rect;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport androidx.annotation.IntDef;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.RequiresApi;\nimport androidx.collection.SimpleArrayMap;\nimport android.util.Log;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.File;\nimport java.io.FilenameFilter;\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.io.StringWriter;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Date;\nimport java.util.Formatter;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport javax.xml.transform.OutputKeys;\nimport javax.xml.transform.Source;\nimport javax.xml.transform.Transformer;\nimport javax.xml.transform.TransformerFactory;\nimport javax.xml.transform.stream.StreamResult;\nimport javax.xml.transform.stream.StreamSource;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/09/21\n *     desc  : utils about log\n * </pre>\n */\npublic final class LogUtils {\n\n    public static final int V = Log.VERBOSE;\n    public static final int D = Log.DEBUG;\n    public static final int I = Log.INFO;\n    public static final int W = Log.WARN;\n    public static final int E = Log.ERROR;\n    public static final int A = Log.ASSERT;\n\n    @IntDef({V, D, I, W, E, A})\n    @Retention(RetentionPolicy.SOURCE)\n    public @interface TYPE {\n    }\n\n    private static final char[] T = new char[]{'V', 'D', 'I', 'W', 'E', 'A'};\n\n    private static final int FILE = 0x10;\n    private static final int JSON = 0x20;\n    private static final int XML  = 0x30;\n\n    private static final String FILE_SEP       = System.getProperty(\"file.separator\");\n    private static final String LINE_SEP       = System.getProperty(\"line.separator\");\n    private static final String TOP_CORNER     = \"┌\";\n    private static final String MIDDLE_CORNER  = \"├\";\n    private static final String LEFT_BORDER    = \"│ \";\n    private static final String BOTTOM_CORNER  = \"└\";\n    private static final String SIDE_DIVIDER   =\n            \"────────────────────────────────────────────────────────\";\n    private static final String MIDDLE_DIVIDER =\n            \"┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄\";\n    private static final String TOP_BORDER     = TOP_CORNER + SIDE_DIVIDER + SIDE_DIVIDER;\n    private static final String MIDDLE_BORDER  = MIDDLE_CORNER + MIDDLE_DIVIDER + MIDDLE_DIVIDER;\n    private static final String BOTTOM_BORDER  = BOTTOM_CORNER + SIDE_DIVIDER + SIDE_DIVIDER;\n    private static final int    MAX_LEN        = 1100;// fit for Chinese character\n    private static final String NOTHING        = \"log nothing\";\n    private static final String NULL           = \"null\";\n    private static final String ARGS           = \"args\";\n    private static final String PLACEHOLDER    = \" \";\n    private static final Config CONFIG         = new Config();\n\n    private static SimpleDateFormat simpleDateFormat;\n\n    private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();\n\n    private static final SimpleArrayMap<Class, IFormatter> I_FORMATTER_MAP = new SimpleArrayMap<>();\n\n    private LogUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    public static Config getConfig() {\n        return CONFIG;\n    }\n\n    public static void v(final Object... contents) {\n        log(V, CONFIG.getGlobalTag(), contents);\n    }\n\n    public static void vTag(final String tag, final Object... contents) {\n        log(V, tag, contents);\n    }\n\n    public static void d(final Object... contents) {\n        log(D, CONFIG.getGlobalTag(), contents);\n    }\n\n    public static void dTag(final String tag, final Object... contents) {\n        log(D, tag, contents);\n    }\n\n    public static void i(final Object... contents) {\n        log(I, CONFIG.getGlobalTag(), contents);\n    }\n\n    public static void iTag(final String tag, final Object... contents) {\n        log(I, tag, contents);\n    }\n\n    public static void w(final Object... contents) {\n        log(W, CONFIG.getGlobalTag(), contents);\n    }\n\n    public static void wTag(final String tag, final Object... contents) {\n        log(W, tag, contents);\n    }\n\n    public static void e(final Object... contents) {\n        log(E, CONFIG.getGlobalTag(), contents);\n    }\n\n    public static void eTag(final String tag, final Object... contents) {\n        log(E, tag, contents);\n    }\n\n    public static void a(final Object... contents) {\n        log(A, CONFIG.getGlobalTag(), contents);\n    }\n\n    public static void aTag(final String tag, final Object... contents) {\n        log(A, tag, contents);\n    }\n\n    public static void file(final Object content) {\n        log(FILE | D, CONFIG.getGlobalTag(), content);\n    }\n\n    public static void file(@TYPE final int type, final Object content) {\n        log(FILE | type, CONFIG.getGlobalTag(), content);\n    }\n\n    public static void file(final String tag, final Object content) {\n        log(FILE | D, tag, content);\n    }\n\n    public static void file(@TYPE final int type, final String tag, final Object content) {\n        log(FILE | type, tag, content);\n    }\n\n    public static void json(final Object content) {\n        log(JSON | D, CONFIG.getGlobalTag(), content);\n    }\n\n    public static void json(@TYPE final int type, final Object content) {\n        log(JSON | type, CONFIG.getGlobalTag(), content);\n    }\n\n    public static void json(final String tag, final Object content) {\n        log(JSON | D, tag, content);\n    }\n\n    public static void json(@TYPE final int type, final String tag, final Object content) {\n        log(JSON | type, tag, content);\n    }\n\n    public static void xml(final String content) {\n        log(XML | D, CONFIG.getGlobalTag(), content);\n    }\n\n    public static void xml(@TYPE final int type, final String content) {\n        log(XML | type, CONFIG.getGlobalTag(), content);\n    }\n\n    public static void xml(final String tag, final String content) {\n        log(XML | D, tag, content);\n    }\n\n    public static void xml(@TYPE final int type, final String tag, final String content) {\n        log(XML | type, tag, content);\n    }\n\n    public static void log(final int type, final String tag, final Object... contents) {\n        if (!CONFIG.isLogSwitch()) return;\n        final int type_low = type & 0x0f, type_high = type & 0xf0;\n        if (CONFIG.isLog2ConsoleSwitch() || CONFIG.isLog2FileSwitch() || type_high == FILE) {\n            if (type_low < CONFIG.mConsoleFilter && type_low < CONFIG.mFileFilter) return;\n            final TagHead tagHead = processTagAndHead(tag);\n            final String body = processBody(type_high, contents);\n            if (CONFIG.isLog2ConsoleSwitch() && type_high != FILE && type_low >= CONFIG.mConsoleFilter) {\n                print2Console(type_low, tagHead.tag, tagHead.consoleHead, body);\n            }\n            if ((CONFIG.isLog2FileSwitch() || type_high == FILE) && type_low >= CONFIG.mFileFilter) {\n                EXECUTOR.execute(new Runnable() {\n                    @Override\n                    public void run() {\n                        print2File(type_low, tagHead.tag, tagHead.fileHead + body);\n                    }\n                });\n            }\n        }\n    }\n\n    public static String getCurrentLogFilePath() {\n        return getCurrentLogFilePath(new Date());\n    }\n\n    public static List<File> getLogFiles() {\n        String dir = CONFIG.getDir();\n        File logDir = new File(dir);\n        if (!logDir.exists()) return new ArrayList<>();\n        File[] files = logDir.listFiles(new FilenameFilter() {\n            @Override\n            public boolean accept(File dir, String name) {\n                return isMatchLogFileName(name);\n            }\n        });\n        List<File> list = new ArrayList<>();\n        Collections.addAll(list, files);\n        return list;\n    }\n\n    private static TagHead processTagAndHead(String tag) {\n        if (!CONFIG.mTagIsSpace && !CONFIG.isLogHeadSwitch()) {\n            tag = CONFIG.getGlobalTag();\n        } else {\n            final StackTraceElement[] stackTrace = new Throwable().getStackTrace();\n            final int stackIndex = 3 + CONFIG.getStackOffset();\n            if (stackIndex >= stackTrace.length) {\n                StackTraceElement targetElement = stackTrace[3];\n                final String fileName = getFileName(targetElement);\n                if (CONFIG.mTagIsSpace && UtilsBridge.isSpace(tag)) {\n                    int index = fileName.indexOf('.');// Use proguard may not find '.'.\n                    tag = index == -1 ? fileName : fileName.substring(0, index);\n                }\n                return new TagHead(tag, null, \": \");\n            }\n            StackTraceElement targetElement = stackTrace[stackIndex];\n            final String fileName = getFileName(targetElement);\n            if (CONFIG.mTagIsSpace && UtilsBridge.isSpace(tag)) {\n                int index = fileName.indexOf('.');// Use proguard may not find '.'.\n                tag = index == -1 ? fileName : fileName.substring(0, index);\n            }\n            if (CONFIG.isLogHeadSwitch()) {\n                String tName = Thread.currentThread().getName();\n                final String head = new Formatter()\n                        .format(\"%s, %s.%s(%s:%d)\",\n                                tName,\n                                targetElement.getClassName(),\n                                targetElement.getMethodName(),\n                                fileName,\n                                targetElement.getLineNumber())\n                        .toString();\n                final String fileHead = \" [\" + head + \"]: \";\n                if (CONFIG.getStackDeep() <= 1) {\n                    return new TagHead(tag, new String[]{head}, fileHead);\n                } else {\n                    final String[] consoleHead =\n                            new String[Math.min(\n                                    CONFIG.getStackDeep(),\n                                    stackTrace.length - stackIndex\n                            )];\n                    consoleHead[0] = head;\n                    int spaceLen = tName.length() + 2;\n                    String space = new Formatter().format(\"%\" + spaceLen + \"s\", \"\").toString();\n                    for (int i = 1, len = consoleHead.length; i < len; ++i) {\n                        targetElement = stackTrace[i + stackIndex];\n                        consoleHead[i] = new Formatter()\n                                .format(\"%s%s.%s(%s:%d)\",\n                                        space,\n                                        targetElement.getClassName(),\n                                        targetElement.getMethodName(),\n                                        getFileName(targetElement),\n                                        targetElement.getLineNumber())\n                                .toString();\n                    }\n                    return new TagHead(tag, consoleHead, fileHead);\n                }\n            }\n        }\n        return new TagHead(tag, null, \": \");\n    }\n\n    private static String getFileName(final StackTraceElement targetElement) {\n        String fileName = targetElement.getFileName();\n        if (fileName != null) return fileName;\n        // If name of file is null, should add\n        // \"-keepattributes SourceFile,LineNumberTable\" in proguard file.\n        String className = targetElement.getClassName();\n        String[] classNameInfo = className.split(\"\\\\.\");\n        if (classNameInfo.length > 0) {\n            className = classNameInfo[classNameInfo.length - 1];\n        }\n        int index = className.indexOf('$');\n        if (index != -1) {\n            className = className.substring(0, index);\n        }\n        return className + \".java\";\n    }\n\n    private static String processBody(final int type, final Object... contents) {\n        String body = NULL;\n        if (contents != null) {\n            if (contents.length == 1) {\n                body = formatObject(type, contents[0]);\n            } else {\n                StringBuilder sb = new StringBuilder();\n                for (int i = 0, len = contents.length; i < len; ++i) {\n                    Object content = contents[i];\n                    sb.append(ARGS)\n                            .append(\"[\")\n                            .append(i)\n                            .append(\"]\")\n                            .append(\" = \")\n                            .append(formatObject(content))\n                            .append(LINE_SEP);\n                }\n                body = sb.toString();\n            }\n        }\n        return body.length() == 0 ? NOTHING : body;\n    }\n\n    private static String formatObject(int type, Object object) {\n        if (object == null) return NULL;\n        if (type == JSON) return LogFormatter.object2String(object, JSON);\n        if (type == XML) return LogFormatter.object2String(object, XML);\n        return formatObject(object);\n    }\n\n    private static String formatObject(Object object) {\n        if (object == null) return NULL;\n        if (!I_FORMATTER_MAP.isEmpty()) {\n            IFormatter iFormatter = I_FORMATTER_MAP.get(getClassFromObject(object));\n            if (iFormatter != null) {\n                //noinspection unchecked\n                return iFormatter.format(object);\n            }\n        }\n        return LogFormatter.object2String(object);\n    }\n\n    private static void print2Console(final int type,\n                                      final String tag,\n                                      final String[] head,\n                                      final String msg) {\n        if (CONFIG.isSingleTagSwitch()) {\n            printSingleTagMsg(type, tag, processSingleTagMsg(type, tag, head, msg));\n        } else {\n            printBorder(type, tag, true);\n            printHead(type, tag, head);\n            printMsg(type, tag, msg);\n            printBorder(type, tag, false);\n        }\n    }\n\n    private static void printBorder(final int type, final String tag, boolean isTop) {\n        if (CONFIG.isLogBorderSwitch()) {\n            print2Console(type, tag, isTop ? TOP_BORDER : BOTTOM_BORDER);\n        }\n    }\n\n    private static void printHead(final int type, final String tag, final String[] head) {\n        if (head != null) {\n            for (String aHead : head) {\n                print2Console(type, tag, CONFIG.isLogBorderSwitch() ? LEFT_BORDER + aHead : aHead);\n            }\n            if (CONFIG.isLogBorderSwitch()) print2Console(type, tag, MIDDLE_BORDER);\n        }\n    }\n\n    private static void printMsg(final int type, final String tag, final String msg) {\n        int len = msg.length();\n        int countOfSub = len / MAX_LEN;\n        if (countOfSub > 0) {\n            int index = 0;\n            for (int i = 0; i < countOfSub; i++) {\n                printSubMsg(type, tag, msg.substring(index, index + MAX_LEN));\n                index += MAX_LEN;\n            }\n            if (index != len) {\n                printSubMsg(type, tag, msg.substring(index, len));\n            }\n        } else {\n            printSubMsg(type, tag, msg);\n        }\n    }\n\n    private static void printSubMsg(final int type, final String tag, final String msg) {\n        if (!CONFIG.isLogBorderSwitch()) {\n            print2Console(type, tag, msg);\n            return;\n        }\n        StringBuilder sb = new StringBuilder();\n        String[] lines = msg.split(LINE_SEP);\n        for (String line : lines) {\n            print2Console(type, tag, LEFT_BORDER + line);\n        }\n    }\n\n    private static String processSingleTagMsg(final int type,\n                                              final String tag,\n                                              final String[] head,\n                                              final String msg) {\n        StringBuilder sb = new StringBuilder();\n        if (CONFIG.isLogBorderSwitch()) {\n            sb.append(PLACEHOLDER).append(LINE_SEP);\n            sb.append(TOP_BORDER).append(LINE_SEP);\n            if (head != null) {\n                for (String aHead : head) {\n                    sb.append(LEFT_BORDER).append(aHead).append(LINE_SEP);\n                }\n                sb.append(MIDDLE_BORDER).append(LINE_SEP);\n            }\n            for (String line : msg.split(LINE_SEP)) {\n                sb.append(LEFT_BORDER).append(line).append(LINE_SEP);\n            }\n            sb.append(BOTTOM_BORDER);\n        } else {\n            if (head != null) {\n                sb.append(PLACEHOLDER).append(LINE_SEP);\n                for (String aHead : head) {\n                    sb.append(aHead).append(LINE_SEP);\n                }\n            }\n            sb.append(msg);\n        }\n        return sb.toString();\n    }\n\n    private static void printSingleTagMsg(final int type, final String tag, final String msg) {\n        int len = msg.length();\n        int countOfSub = CONFIG.isLogBorderSwitch() ? (len - BOTTOM_BORDER.length()) / MAX_LEN : len / MAX_LEN;\n        if (countOfSub > 0) {\n            if (CONFIG.isLogBorderSwitch()) {\n                print2Console(type, tag, msg.substring(0, MAX_LEN) + LINE_SEP + BOTTOM_BORDER);\n                int index = MAX_LEN;\n                for (int i = 1; i < countOfSub; i++) {\n                    print2Console(type, tag, PLACEHOLDER + LINE_SEP + TOP_BORDER + LINE_SEP\n                            + LEFT_BORDER + msg.substring(index, index + MAX_LEN)\n                            + LINE_SEP + BOTTOM_BORDER);\n                    index += MAX_LEN;\n                }\n                if (index != len - BOTTOM_BORDER.length()) {\n                    print2Console(type, tag, PLACEHOLDER + LINE_SEP + TOP_BORDER + LINE_SEP\n                            + LEFT_BORDER + msg.substring(index, len));\n                }\n            } else {\n                print2Console(type, tag, msg.substring(0, MAX_LEN));\n                int index = MAX_LEN;\n                for (int i = 1; i < countOfSub; i++) {\n                    print2Console(type, tag,\n                            PLACEHOLDER + LINE_SEP + msg.substring(index, index + MAX_LEN));\n                    index += MAX_LEN;\n                }\n                if (index != len) {\n                    print2Console(type, tag, PLACEHOLDER + LINE_SEP + msg.substring(index, len));\n                }\n            }\n        } else {\n            print2Console(type, tag, msg);\n        }\n    }\n\n    private static void print2Console(int type, String tag, String msg) {\n        Log.println(type, tag, msg);\n        if (CONFIG.mOnConsoleOutputListener != null) {\n            CONFIG.mOnConsoleOutputListener.onConsoleOutput(type, tag, msg);\n        }\n    }\n\n    private static void print2File(final int type, final String tag, final String msg) {\n        Date d = new Date();\n        String format = getSdf().format(d);\n        String date = format.substring(0, 10);\n        String currentLogFilePath = getCurrentLogFilePath(d);\n        if (!createOrExistsFile(currentLogFilePath, date)) {\n            Log.e(\"LogUtils\", \"create \" + currentLogFilePath + \" failed!\");\n            return;\n        }\n        String time = format.substring(11);\n        final String content = time +\n                T[type - V] +\n                \"/\" +\n                tag +\n                msg +\n                LINE_SEP;\n        input2File(currentLogFilePath, content);\n    }\n\n    private static String getCurrentLogFilePath(Date d) {\n        String format = getSdf().format(d);\n        String date = format.substring(0, 10);\n        return CONFIG.getDir() + CONFIG.getFilePrefix() + \"_\"\n                + date + \"_\" +\n                CONFIG.getProcessName() + CONFIG.getFileExtension();\n    }\n\n\n    private static SimpleDateFormat getSdf() {\n        if (simpleDateFormat == null) {\n            simpleDateFormat = new SimpleDateFormat(\"yyyy_MM_dd HH:mm:ss.SSS \", Locale.getDefault());\n        }\n        return simpleDateFormat;\n    }\n\n    private static boolean createOrExistsFile(final String filePath, final String date) {\n        File file = new File(filePath);\n        if (file.exists()) return file.isFile();\n        if (!UtilsBridge.createOrExistsDir(file.getParentFile())) return false;\n        try {\n            deleteDueLogs(filePath, date);\n            boolean isCreate = file.createNewFile();\n            if (isCreate) {\n                printDeviceInfo(filePath, date);\n            }\n            return isCreate;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    private static void deleteDueLogs(final String filePath, final String date) {\n        if (CONFIG.getSaveDays() <= 0) return;\n        File file = new File(filePath);\n        File parentFile = file.getParentFile();\n        File[] files = parentFile.listFiles(new FilenameFilter() {\n            @Override\n            public boolean accept(File dir, String name) {\n                return isMatchLogFileName(name);\n            }\n        });\n        if (files == null || files.length <= 0) return;\n        final SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy_MM_dd\", Locale.getDefault());\n        try {\n            long dueMillis = sdf.parse(date).getTime() - CONFIG.getSaveDays() * 86400000L;\n            for (final File aFile : files) {\n                String name = aFile.getName();\n                int l = name.length();\n                String logDay = findDate(name);\n                if (sdf.parse(logDay).getTime() <= dueMillis) {\n                    EXECUTOR.execute(new Runnable() {\n                        @Override\n                        public void run() {\n                            boolean delete = aFile.delete();\n                            if (!delete) {\n                                Log.e(\"LogUtils\", \"delete \" + aFile + \" failed!\");\n                            }\n                        }\n                    });\n                }\n            }\n        } catch (ParseException e) {\n            e.printStackTrace();\n        }\n    }\n\n    private static boolean isMatchLogFileName(String name) {\n        return name.matches(\"^\" + CONFIG.getFilePrefix() + \"_[0-9]{4}_[0-9]{2}_[0-9]{2}_.*$\");\n    }\n\n    private static String findDate(String str) {\n        Pattern pattern = Pattern.compile(\"[0-9]{4}_[0-9]{2}_[0-9]{2}\");\n        Matcher matcher = pattern.matcher(str);\n        if (matcher.find()) {\n            return matcher.group();\n        }\n        return \"\";\n    }\n\n    private static void printDeviceInfo(final String filePath, final String date) {\n        CONFIG.mFileHead.addFirst(\"Date of Log\", date);\n        input2File(filePath, CONFIG.mFileHead.toString());\n    }\n\n    private static void input2File(final String filePath, final String input) {\n        if (CONFIG.mFileWriter == null) {\n            UtilsBridge.writeFileFromString(filePath, input, true);\n        } else {\n            CONFIG.mFileWriter.write(filePath, input);\n        }\n        if (CONFIG.mOnFileOutputListener != null) {\n            CONFIG.mOnFileOutputListener.onFileOutput(filePath, input);\n        }\n    }\n\n    public static final class Config {\n        private String                  mDefaultDir;                // The default storage directory of log.\n        private String                  mDir;                       // The storage directory of log.\n        private String                  mFilePrefix        = \"util\";// The file prefix of log.\n        private String                  mFileExtension     = \".txt\";// The file extension of log.\n        private boolean                 mLogSwitch         = true;  // The switch of log.\n        private boolean                 mLog2ConsoleSwitch = true;  // The logcat's switch of log.\n        private String                  mGlobalTag         = \"\";    // The global tag of log.\n        private boolean                 mTagIsSpace        = true;  // The global tag is space.\n        private boolean                 mLogHeadSwitch     = true;  // The head's switch of log.\n        private boolean                 mLog2FileSwitch    = false; // The file's switch of log.\n        private boolean                 mLogBorderSwitch   = true;  // The border's switch of log.\n        private boolean                 mSingleTagSwitch   = true;  // The single tag of log.\n        private int                     mConsoleFilter     = V;     // The console's filter of log.\n        private int                     mFileFilter        = V;     // The file's filter of log.\n        private int                     mStackDeep         = 1;     // The stack's deep of log.\n        private int                     mStackOffset       = 0;     // The stack's offset of log.\n        private int                     mSaveDays          = -1;    // The save days of log.\n        private String                  mProcessName       = UtilsBridge.getCurrentProcessName();\n        private IFileWriter             mFileWriter;\n        private OnConsoleOutputListener mOnConsoleOutputListener;\n        private OnFileOutputListener    mOnFileOutputListener;\n        private UtilsBridge.FileHead    mFileHead          = new UtilsBridge.FileHead(\"Log\");\n\n        private Config() {\n            if (UtilsBridge.isSDCardEnableByEnvironment()\n                    && Utils.getApp().getExternalFilesDir(null) != null)\n                mDefaultDir = Utils.getApp().getExternalFilesDir(null) + FILE_SEP + \"log\" + FILE_SEP;\n            else {\n                mDefaultDir = Utils.getApp().getFilesDir() + FILE_SEP + \"log\" + FILE_SEP;\n            }\n        }\n\n        public final Config setLogSwitch(final boolean logSwitch) {\n            mLogSwitch = logSwitch;\n            return this;\n        }\n\n        public final Config setConsoleSwitch(final boolean consoleSwitch) {\n            mLog2ConsoleSwitch = consoleSwitch;\n            return this;\n        }\n\n        public final Config setGlobalTag(final String tag) {\n            if (UtilsBridge.isSpace(tag)) {\n                mGlobalTag = \"\";\n                mTagIsSpace = true;\n            } else {\n                mGlobalTag = tag;\n                mTagIsSpace = false;\n            }\n            return this;\n        }\n\n        public final Config setLogHeadSwitch(final boolean logHeadSwitch) {\n            mLogHeadSwitch = logHeadSwitch;\n            return this;\n        }\n\n        public final Config setLog2FileSwitch(final boolean log2FileSwitch) {\n            mLog2FileSwitch = log2FileSwitch;\n            return this;\n        }\n\n        public final Config setDir(final String dir) {\n            if (UtilsBridge.isSpace(dir)) {\n                mDir = null;\n            } else {\n                mDir = dir.endsWith(FILE_SEP) ? dir : dir + FILE_SEP;\n            }\n            return this;\n        }\n\n        public final Config setDir(final File dir) {\n            mDir = dir == null ? null : (dir.getAbsolutePath() + FILE_SEP);\n            return this;\n        }\n\n        public final Config setFilePrefix(final String filePrefix) {\n            if (UtilsBridge.isSpace(filePrefix)) {\n                mFilePrefix = \"util\";\n            } else {\n                mFilePrefix = filePrefix;\n            }\n            return this;\n        }\n\n        public final Config setFileExtension(final String fileExtension) {\n            if (UtilsBridge.isSpace(fileExtension)) {\n                mFileExtension = \".txt\";\n            } else {\n                if (fileExtension.startsWith(\".\")) {\n                    mFileExtension = fileExtension;\n                } else {\n                    mFileExtension = \".\" + fileExtension;\n                }\n            }\n            return this;\n        }\n\n        public final Config setBorderSwitch(final boolean borderSwitch) {\n            mLogBorderSwitch = borderSwitch;\n            return this;\n        }\n\n        public final Config setSingleTagSwitch(final boolean singleTagSwitch) {\n            mSingleTagSwitch = singleTagSwitch;\n            return this;\n        }\n\n        public final Config setConsoleFilter(@TYPE final int consoleFilter) {\n            mConsoleFilter = consoleFilter;\n            return this;\n        }\n\n        public final Config setFileFilter(@TYPE final int fileFilter) {\n            mFileFilter = fileFilter;\n            return this;\n        }\n\n        public final Config setStackDeep(@IntRange(from = 1) final int stackDeep) {\n            mStackDeep = stackDeep;\n            return this;\n        }\n\n        public final Config setStackOffset(@IntRange(from = 0) final int stackOffset) {\n            mStackOffset = stackOffset;\n            return this;\n        }\n\n        public final Config setSaveDays(@IntRange(from = 1) final int saveDays) {\n            mSaveDays = saveDays;\n            return this;\n        }\n\n        public final <T> Config addFormatter(final IFormatter<T> iFormatter) {\n            if (iFormatter != null) {\n                I_FORMATTER_MAP.put(getTypeClassFromParadigm(iFormatter), iFormatter);\n            }\n            return this;\n        }\n\n        public final Config setFileWriter(final IFileWriter fileWriter) {\n            mFileWriter = fileWriter;\n            return this;\n        }\n\n        public final Config setOnConsoleOutputListener(final OnConsoleOutputListener listener) {\n            mOnConsoleOutputListener = listener;\n            return this;\n        }\n\n        public final Config setOnFileOutputListener(final OnFileOutputListener listener) {\n            mOnFileOutputListener = listener;\n            return this;\n        }\n\n        public final Config addFileExtraHead(final Map<String, String> fileExtraHead) {\n            mFileHead.append(fileExtraHead);\n            return this;\n        }\n\n        public final Config addFileExtraHead(final String key, final String value) {\n            mFileHead.append(key, value);\n            return this;\n        }\n\n        public final String getProcessName() {\n            if (mProcessName == null) return \"\";\n            return mProcessName.replace(\":\", \"_\");\n        }\n\n        public final String getDefaultDir() {\n            return mDefaultDir;\n        }\n\n        public final String getDir() {\n            return mDir == null ? mDefaultDir : mDir;\n        }\n\n        public final String getFilePrefix() {\n            return mFilePrefix;\n        }\n\n        public final String getFileExtension() {\n            return mFileExtension;\n        }\n\n        public final boolean isLogSwitch() {\n            return mLogSwitch;\n        }\n\n        public final boolean isLog2ConsoleSwitch() {\n            return mLog2ConsoleSwitch;\n        }\n\n        public final String getGlobalTag() {\n            if (UtilsBridge.isSpace(mGlobalTag)) return \"\";\n            return mGlobalTag;\n        }\n\n        public final boolean isLogHeadSwitch() {\n            return mLogHeadSwitch;\n        }\n\n        public final boolean isLog2FileSwitch() {\n            return mLog2FileSwitch;\n        }\n\n        public final boolean isLogBorderSwitch() {\n            return mLogBorderSwitch;\n        }\n\n        public final boolean isSingleTagSwitch() {\n            return mSingleTagSwitch;\n        }\n\n        public final char getConsoleFilter() {\n            return T[mConsoleFilter - V];\n        }\n\n        public final char getFileFilter() {\n            return T[mFileFilter - V];\n        }\n\n        public final int getStackDeep() {\n            return mStackDeep;\n        }\n\n        public final int getStackOffset() {\n            return mStackOffset;\n        }\n\n        public final int getSaveDays() {\n            return mSaveDays;\n        }\n\n        public final boolean haveSetOnConsoleOutputListener() {\n            return mOnConsoleOutputListener != null;\n        }\n\n        public final boolean haveSetOnFileOutputListener() {\n            return mOnFileOutputListener != null;\n        }\n\n        @Override\n        public String toString() {\n            return \"process: \" + getProcessName()\n                    + LINE_SEP + \"logSwitch: \" + isLogSwitch()\n                    + LINE_SEP + \"consoleSwitch: \" + isLog2ConsoleSwitch()\n                    + LINE_SEP + \"tag: \" + (getGlobalTag().equals(\"\") ? \"null\" : getGlobalTag())\n                    + LINE_SEP + \"headSwitch: \" + isLogHeadSwitch()\n                    + LINE_SEP + \"fileSwitch: \" + isLog2FileSwitch()\n                    + LINE_SEP + \"dir: \" + getDir()\n                    + LINE_SEP + \"filePrefix: \" + getFilePrefix()\n                    + LINE_SEP + \"borderSwitch: \" + isLogBorderSwitch()\n                    + LINE_SEP + \"singleTagSwitch: \" + isSingleTagSwitch()\n                    + LINE_SEP + \"consoleFilter: \" + getConsoleFilter()\n                    + LINE_SEP + \"fileFilter: \" + getFileFilter()\n                    + LINE_SEP + \"stackDeep: \" + getStackDeep()\n                    + LINE_SEP + \"stackOffset: \" + getStackOffset()\n                    + LINE_SEP + \"saveDays: \" + getSaveDays()\n                    + LINE_SEP + \"formatter: \" + I_FORMATTER_MAP\n                    + LINE_SEP + \"fileWriter: \" + mFileWriter\n                    + LINE_SEP + \"onConsoleOutputListener: \" + mOnConsoleOutputListener\n                    + LINE_SEP + \"onFileOutputListener: \" + mOnFileOutputListener\n                    + LINE_SEP + \"fileExtraHeader: \" + mFileHead.getAppended();\n        }\n    }\n\n    public abstract static class IFormatter<T> {\n        public abstract String format(T t);\n    }\n\n    public interface IFileWriter {\n        void write(String file, String content);\n    }\n\n    public interface OnConsoleOutputListener {\n        void onConsoleOutput(@TYPE int type, String tag, String content);\n    }\n\n    public interface OnFileOutputListener {\n        void onFileOutput(String filePath, String content);\n    }\n\n    private final static class TagHead {\n        String   tag;\n        String[] consoleHead;\n        String   fileHead;\n\n        TagHead(String tag, String[] consoleHead, String fileHead) {\n            this.tag = tag;\n            this.consoleHead = consoleHead;\n            this.fileHead = fileHead;\n        }\n    }\n\n    private final static class LogFormatter {\n\n        static String object2String(Object object) {\n            return object2String(object, -1);\n        }\n\n        static String object2String(Object object, int type) {\n            if (object.getClass().isArray()) return array2String(object);\n            if (object instanceof Throwable)\n                return UtilsBridge.getFullStackTrace((Throwable) object);\n            if (object instanceof Bundle) return bundle2String((Bundle) object);\n            if (object instanceof Intent) return intent2String((Intent) object);\n            if (type == JSON) {\n                return object2Json(object);\n            } else if (type == XML) {\n                return formatXml(object.toString());\n            }\n            return object.toString();\n        }\n\n        private static String bundle2String(Bundle bundle) {\n            Iterator<String> iterator = bundle.keySet().iterator();\n            if (!iterator.hasNext()) {\n                return \"Bundle {}\";\n            }\n            StringBuilder sb = new StringBuilder(128);\n            sb.append(\"Bundle { \");\n            for (; ; ) {\n                String key = iterator.next();\n                Object value = bundle.get(key);\n                sb.append(key).append('=');\n                if (value instanceof Bundle) {\n                    sb.append(value == bundle ? \"(this Bundle)\" : bundle2String((Bundle) value));\n                } else {\n                    sb.append(formatObject(value));\n                }\n                if (!iterator.hasNext()) return sb.append(\" }\").toString();\n                sb.append(',').append(' ');\n            }\n        }\n\n        private static String intent2String(Intent intent) {\n            StringBuilder sb = new StringBuilder(128);\n            sb.append(\"Intent { \");\n            boolean first = true;\n            String mAction = intent.getAction();\n            if (mAction != null) {\n                sb.append(\"act=\").append(mAction);\n                first = false;\n            }\n            Set<String> mCategories = intent.getCategories();\n            if (mCategories != null) {\n                if (!first) {\n                    sb.append(' ');\n                }\n                first = false;\n                sb.append(\"cat=[\");\n                boolean firstCategory = true;\n                for (String c : mCategories) {\n                    if (!firstCategory) {\n                        sb.append(',');\n                    }\n                    sb.append(c);\n                    firstCategory = false;\n                }\n                sb.append(\"]\");\n            }\n            Uri mData = intent.getData();\n            if (mData != null) {\n                if (!first) {\n                    sb.append(' ');\n                }\n                first = false;\n                sb.append(\"dat=\").append(mData);\n            }\n            String mType = intent.getType();\n            if (mType != null) {\n                if (!first) {\n                    sb.append(' ');\n                }\n                first = false;\n                sb.append(\"typ=\").append(mType);\n            }\n            int mFlags = intent.getFlags();\n            if (mFlags != 0) {\n                if (!first) {\n                    sb.append(' ');\n                }\n                first = false;\n                sb.append(\"flg=0x\").append(Integer.toHexString(mFlags));\n            }\n            String mPackage = intent.getPackage();\n            if (mPackage != null) {\n                if (!first) {\n                    sb.append(' ');\n                }\n                first = false;\n                sb.append(\"pkg=\").append(mPackage);\n            }\n            ComponentName mComponent = intent.getComponent();\n            if (mComponent != null) {\n                if (!first) {\n                    sb.append(' ');\n                }\n                first = false;\n                sb.append(\"cmp=\").append(mComponent.flattenToShortString());\n            }\n            Rect mSourceBounds = intent.getSourceBounds();\n            if (mSourceBounds != null) {\n                if (!first) {\n                    sb.append(' ');\n                }\n                first = false;\n                sb.append(\"bnds=\").append(mSourceBounds.toShortString());\n            }\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n                ClipData mClipData = intent.getClipData();\n                if (mClipData != null) {\n                    if (!first) {\n                        sb.append(' ');\n                    }\n                    first = false;\n                    clipData2String(mClipData, sb);\n                }\n            }\n            Bundle mExtras = intent.getExtras();\n            if (mExtras != null) {\n                if (!first) {\n                    sb.append(' ');\n                }\n                first = false;\n                sb.append(\"extras={\");\n                sb.append(bundle2String(mExtras));\n                sb.append('}');\n            }\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {\n                Intent mSelector = intent.getSelector();\n                if (mSelector != null) {\n                    if (!first) {\n                        sb.append(' ');\n                    }\n                    first = false;\n                    sb.append(\"sel={\");\n                    sb.append(mSelector == intent ? \"(this Intent)\" : intent2String(mSelector));\n                    sb.append(\"}\");\n                }\n            }\n            sb.append(\" }\");\n            return sb.toString();\n        }\n\n        @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)\n        private static void clipData2String(ClipData clipData, StringBuilder sb) {\n            ClipData.Item item = clipData.getItemAt(0);\n            if (item == null) {\n                sb.append(\"ClipData.Item {}\");\n                return;\n            }\n            sb.append(\"ClipData.Item { \");\n            String mHtmlText = item.getHtmlText();\n            if (mHtmlText != null) {\n                sb.append(\"H:\");\n                sb.append(mHtmlText);\n                sb.append(\"}\");\n                return;\n            }\n            CharSequence mText = item.getText();\n            if (mText != null) {\n                sb.append(\"T:\");\n                sb.append(mText);\n                sb.append(\"}\");\n                return;\n            }\n            Uri uri = item.getUri();\n            if (uri != null) {\n                sb.append(\"U:\").append(uri);\n                sb.append(\"}\");\n                return;\n            }\n            Intent intent = item.getIntent();\n            if (intent != null) {\n                sb.append(\"I:\");\n                sb.append(intent2String(intent));\n                sb.append(\"}\");\n                return;\n            }\n            sb.append(\"NULL\");\n            sb.append(\"}\");\n        }\n\n        private static String object2Json(Object object) {\n            if (object instanceof CharSequence) {\n                return UtilsBridge.formatJson(object.toString());\n            }\n            try {\n                return UtilsBridge.getGson4LogUtils().toJson(object);\n            } catch (Throwable t) {\n                return object.toString();\n            }\n        }\n\n        private static String formatJson(String json) {\n            try {\n                for (int i = 0, len = json.length(); i < len; i++) {\n                    char c = json.charAt(i);\n                    if (c == '{') {\n                        return new JSONObject(json).toString(2);\n                    } else if (c == '[') {\n                        return new JSONArray(json).toString(2);\n                    } else if (!Character.isWhitespace(c)) {\n                        return json;\n                    }\n                }\n            } catch (JSONException e) {\n                e.printStackTrace();\n            }\n            return json;\n        }\n\n        private static String formatXml(String xml) {\n            try {\n                Source xmlInput = new StreamSource(new StringReader(xml));\n                StreamResult xmlOutput = new StreamResult(new StringWriter());\n                Transformer transformer = TransformerFactory.newInstance().newTransformer();\n                transformer.setOutputProperty(OutputKeys.INDENT, \"yes\");\n                transformer.setOutputProperty(\"{http://xml.apache.org/xslt}indent-amount\", \"2\");\n                transformer.transform(xmlInput, xmlOutput);\n                xml = xmlOutput.getWriter().toString().replaceFirst(\">\", \">\" + LINE_SEP);\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n            return xml;\n        }\n\n        private static String array2String(Object object) {\n            if (object instanceof Object[]) {\n                return Arrays.deepToString((Object[]) object);\n            } else if (object instanceof boolean[]) {\n                return Arrays.toString((boolean[]) object);\n            } else if (object instanceof byte[]) {\n                return Arrays.toString((byte[]) object);\n            } else if (object instanceof char[]) {\n                return Arrays.toString((char[]) object);\n            } else if (object instanceof double[]) {\n                return Arrays.toString((double[]) object);\n            } else if (object instanceof float[]) {\n                return Arrays.toString((float[]) object);\n            } else if (object instanceof int[]) {\n                return Arrays.toString((int[]) object);\n            } else if (object instanceof long[]) {\n                return Arrays.toString((long[]) object);\n            } else if (object instanceof short[]) {\n                return Arrays.toString((short[]) object);\n            }\n            throw new IllegalArgumentException(\"Array has incompatible type: \" + object.getClass());\n        }\n    }\n\n    private static <T> Class getTypeClassFromParadigm(final IFormatter<T> formatter) {\n        Type[] genericInterfaces = formatter.getClass().getGenericInterfaces();\n        Type type;\n        if (genericInterfaces.length == 1) {\n            type = genericInterfaces[0];\n        } else {\n            type = formatter.getClass().getGenericSuperclass();\n        }\n        type = ((ParameterizedType) type).getActualTypeArguments()[0];\n        while (type instanceof ParameterizedType) {\n            type = ((ParameterizedType) type).getRawType();\n        }\n        String className = type.toString();\n        if (className.startsWith(\"class \")) {\n            className = className.substring(6);\n        } else if (className.startsWith(\"interface \")) {\n            className = className.substring(10);\n        }\n        try {\n            return Class.forName(className);\n        } catch (ClassNotFoundException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    private static Class getClassFromObject(final Object obj) {\n        Class objClass = obj.getClass();\n        if (objClass.isAnonymousClass() || objClass.isSynthetic()) {\n            Type[] genericInterfaces = objClass.getGenericInterfaces();\n            String className;\n            if (genericInterfaces.length == 1) {// interface\n                Type type = genericInterfaces[0];\n                while (type instanceof ParameterizedType) {\n                    type = ((ParameterizedType) type).getRawType();\n                }\n                className = type.toString();\n            } else {// abstract class or lambda\n                Type type = objClass.getGenericSuperclass();\n                while (type instanceof ParameterizedType) {\n                    type = ((ParameterizedType) type).getRawType();\n                }\n                className = type.toString();\n            }\n\n            if (className.startsWith(\"class \")) {\n                className = className.substring(6);\n            } else if (className.startsWith(\"interface \")) {\n                className = className.substring(10);\n            }\n            try {\n                return Class.forName(className);\n            } catch (ClassNotFoundException e) {\n                e.printStackTrace();\n            }\n        }\n        return objClass;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/LunarUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/12/05\n *     desc  : 日历相关工具类\n * </pre>\n */\npublic final class LunarUtils {\n\n    private LunarUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /*\n     * |----4位闰月|-------------13位1为30天，0为29天|\n     */\n    private static final int[] LUNAR_MONTH_DAYS = {1887, 0x1694, 0x16aa, 0x4ad5, 0xab6, 0xc4b7, 0x4ae, 0xa56, 0xb52a, 0x1d2a,\n            0xd54, 0x75aa, 0x156a, 0x1096d, 0x95c, 0x14ae, 0xaa4d, 0x1a4c, 0x1b2a, 0x8d55, 0xad4, 0x135a, 0x495d, 0x95c,\n            0xd49b, 0x149a, 0x1a4a, 0xbaa5, 0x16a8, 0x1ad4, 0x52da, 0x12b6, 0xe937, 0x92e, 0x1496, 0xb64b, 0xd4a, 0xda8,\n            0x95b5, 0x56c, 0x12ae, 0x492f, 0x92e, 0xcc96, 0x1a94, 0x1d4a, 0xada9, 0xb5a, 0x56c, 0x726e, 0x125c, 0xf92d,\n            0x192a, 0x1a94, 0xdb4a, 0x16aa, 0xad4, 0x955b, 0x4ba, 0x125a, 0x592b, 0x152a, 0xf695, 0xd94, 0x16aa, 0xaab5,\n            0x9b4, 0x14b6, 0x6a57, 0xa56, 0x1152a, 0x1d2a, 0xd54, 0xd5aa, 0x156a, 0x96c, 0x94ae, 0x14ae, 0xa4c, 0x7d26,\n            0x1b2a, 0xeb55, 0xad4, 0x12da, 0xa95d, 0x95a, 0x149a, 0x9a4d, 0x1a4a, 0x11aa5, 0x16a8, 0x16d4, 0xd2da,\n            0x12b6, 0x936, 0x9497, 0x1496, 0x1564b, 0xd4a, 0xda8, 0xd5b4, 0x156c, 0x12ae, 0xa92f, 0x92e, 0xc96, 0x6d4a,\n            0x1d4a, 0x10d65, 0xb58, 0x156c, 0xb26d, 0x125c, 0x192c, 0x9a95, 0x1a94, 0x1b4a, 0x4b55, 0xad4, 0xf55b,\n            0x4ba, 0x125a, 0xb92b, 0x152a, 0x1694, 0x96aa, 0x15aa, 0x12ab5, 0x974, 0x14b6, 0xca57, 0xa56, 0x1526,\n            0x8e95, 0xd54, 0x15aa, 0x49b5, 0x96c, 0xd4ae, 0x149c, 0x1a4c, 0xbd26, 0x1aa6, 0xb54, 0x6d6a, 0x12da,\n            0x1695d, 0x95a, 0x149a, 0xda4b, 0x1a4a, 0x1aa4, 0xbb54, 0x16b4, 0xada, 0x495b, 0x936, 0xf497, 0x1496,\n            0x154a, 0xb6a5, 0xda4, 0x15b4, 0x6ab6, 0x126e, 0x1092f, 0x92e, 0xc96, 0xcd4a, 0x1d4a, 0xd64, 0x956c, 0x155c,\n            0x125c, 0x792e, 0x192c, 0xfa95, 0x1a94, 0x1b4a, 0xab55, 0xad4, 0x14da, 0x8a5d, 0xa5a, 0x1152b, 0x152a,\n            0x1694, 0xd6aa, 0x15aa, 0xab4, 0x94ba, 0x14b6, 0xa56, 0x7527, 0xd26, 0xee53, 0xd54, 0x15aa, 0xa9b5, 0x96c,\n            0x14ae, 0x8a4e, 0x1a4c, 0x11d26, 0x1aa4, 0x1b54, 0xcd6a, 0xada, 0x95c, 0x949d, 0x149a, 0x1a2a, 0x5b25,\n            0x1aa4, 0xfb52, 0x16b4, 0xaba, 0xa95b, 0x936, 0x1496, 0x9a4b, 0x154a, 0x136a5, 0xda4, 0x15ac};\n\n    private static final int[] SOLAR_1_1 = {1887, 0xec04c, 0xec23f, 0xec435, 0xec649, 0xec83e, 0xeca51, 0xecc46, 0xece3a,\n            0xed04d, 0xed242, 0xed436, 0xed64a, 0xed83f, 0xeda53, 0xedc48, 0xede3d, 0xee050, 0xee244, 0xee439, 0xee64d,\n            0xee842, 0xeea36, 0xeec4a, 0xeee3e, 0xef052, 0xef246, 0xef43a, 0xef64e, 0xef843, 0xefa37, 0xefc4b, 0xefe41,\n            0xf0054, 0xf0248, 0xf043c, 0xf0650, 0xf0845, 0xf0a38, 0xf0c4d, 0xf0e42, 0xf1037, 0xf124a, 0xf143e, 0xf1651,\n            0xf1846, 0xf1a3a, 0xf1c4e, 0xf1e44, 0xf2038, 0xf224b, 0xf243f, 0xf2653, 0xf2848, 0xf2a3b, 0xf2c4f, 0xf2e45,\n            0xf3039, 0xf324d, 0xf3442, 0xf3636, 0xf384a, 0xf3a3d, 0xf3c51, 0xf3e46, 0xf403b, 0xf424e, 0xf4443, 0xf4638,\n            0xf484c, 0xf4a3f, 0xf4c52, 0xf4e48, 0xf503c, 0xf524f, 0xf5445, 0xf5639, 0xf584d, 0xf5a42, 0xf5c35, 0xf5e49,\n            0xf603e, 0xf6251, 0xf6446, 0xf663b, 0xf684f, 0xf6a43, 0xf6c37, 0xf6e4b, 0xf703f, 0xf7252, 0xf7447, 0xf763c,\n            0xf7850, 0xf7a45, 0xf7c39, 0xf7e4d, 0xf8042, 0xf8254, 0xf8449, 0xf863d, 0xf8851, 0xf8a46, 0xf8c3b, 0xf8e4f,\n            0xf9044, 0xf9237, 0xf944a, 0xf963f, 0xf9853, 0xf9a47, 0xf9c3c, 0xf9e50, 0xfa045, 0xfa238, 0xfa44c, 0xfa641,\n            0xfa836, 0xfaa49, 0xfac3d, 0xfae52, 0xfb047, 0xfb23a, 0xfb44e, 0xfb643, 0xfb837, 0xfba4a, 0xfbc3f, 0xfbe53,\n            0xfc048, 0xfc23c, 0xfc450, 0xfc645, 0xfc839, 0xfca4c, 0xfcc41, 0xfce36, 0xfd04a, 0xfd23d, 0xfd451, 0xfd646,\n            0xfd83a, 0xfda4d, 0xfdc43, 0xfde37, 0xfe04b, 0xfe23f, 0xfe453, 0xfe648, 0xfe83c, 0xfea4f, 0xfec44, 0xfee38,\n            0xff04c, 0xff241, 0xff436, 0xff64a, 0xff83e, 0xffa51, 0xffc46, 0xffe3a, 0x10004e, 0x100242, 0x100437,\n            0x10064b, 0x100841, 0x100a53, 0x100c48, 0x100e3c, 0x10104f, 0x101244, 0x101438, 0x10164c, 0x101842,\n            0x101a35, 0x101c49, 0x101e3d, 0x102051, 0x102245, 0x10243a, 0x10264e, 0x102843, 0x102a37, 0x102c4b,\n            0x102e3f, 0x103053, 0x103247, 0x10343b, 0x10364f, 0x103845, 0x103a38, 0x103c4c, 0x103e42, 0x104036,\n            0x104249, 0x10443d, 0x104651, 0x104846, 0x104a3a, 0x104c4e, 0x104e43, 0x105038, 0x10524a, 0x10543e,\n            0x105652, 0x105847, 0x105a3b, 0x105c4f, 0x105e45, 0x106039, 0x10624c, 0x106441, 0x106635, 0x106849,\n            0x106a3d, 0x106c51, 0x106e47, 0x10703c, 0x10724f, 0x107444, 0x107638, 0x10784c, 0x107a3f, 0x107c53,\n            0x107e48};\n\n    private static int getBitInt(final int data, final int length, final int shift) {\n        return (data & (((1 << length) - 1) << shift)) >> shift;\n    }\n\n\n    /**\n     * 农历年转干支年\n     *\n     * @param lunarYear 农历年份\n     * @return 干支年\n     */\n    public static String lunarYear2GanZhi(final int lunarYear) {\n        final String[] tianGan = {\"甲\", \"乙\", \"丙\", \"丁\", \"戊\", \"己\", \"庚\", \"辛\", \"壬\", \"癸\"};\n        final String[] diZhi = {\"子\", \"丑\", \"寅\", \"卯\", \"辰\", \"巳\", \"午\", \"未\", \"申\", \"酉\", \"戌\", \"亥\"};\n        return tianGan[(lunarYear - 4) % 10] + diZhi[(lunarYear - 4) % 12] + \"年\";\n    }\n\n    /**\n     * 农历转公历\n     *\n     * @param lunar 农历\n     * @return 公历\n     */\n    public static Solar lunar2Solar(final Lunar lunar) {\n        int days = LUNAR_MONTH_DAYS[lunar.lunarYear - LUNAR_MONTH_DAYS[0]];\n        int leap = getBitInt(days, 4, 13);\n        int offset = 0;\n        int loopend = leap;\n        if (!lunar.isLeap) {\n            if (lunar.lunarMonth <= leap || leap == 0) {\n                loopend = lunar.lunarMonth - 1;\n            } else {\n                loopend = lunar.lunarMonth;\n            }\n        }\n        for (int i = 0; i < loopend; i++) {\n            offset += getBitInt(days, 1, 12 - i) == 1 ? 30 : 29;\n        }\n        offset += lunar.lunarDay;\n\n        int solar11 = SOLAR_1_1[lunar.lunarYear - SOLAR_1_1[0]];\n\n        int y = getBitInt(solar11, 12, 9);\n        int m = getBitInt(solar11, 4, 5);\n        int d = getBitInt(solar11, 5, 0);\n\n        return solarFromInt(solarToInt(y, m, d) + offset - 1);\n    }\n\n    /**\n     * 公历转农历\n     *\n     * @param solar 公历\n     * @return 阴历\n     */\n    public static Lunar solar2Lunar(final Solar solar) {\n        Lunar lunar = new Lunar();\n        int index = solar.solarYear - SOLAR_1_1[0];\n        int data = (solar.solarYear << 9) | (solar.solarMonth << 5) | (solar.solarDay);\n        int solar11 = 0;\n        if (SOLAR_1_1[index] > data) {\n            index--;\n        }\n        solar11 = SOLAR_1_1[index];\n        int y = getBitInt(solar11, 12, 9);\n        int m = getBitInt(solar11, 4, 5);\n        int d = getBitInt(solar11, 5, 0);\n        long offset = solarToInt(solar.solarYear, solar.solarMonth, solar.solarDay) - solarToInt(y, m, d);\n\n        int days = LUNAR_MONTH_DAYS[index];\n        int leap = getBitInt(days, 4, 13);\n\n        int lunarY = index + SOLAR_1_1[0];\n        int lunarM = 1;\n        int lunarD = 1;\n        offset += 1;\n\n        for (int i = 0; i < 13; i++) {\n            int dm = getBitInt(days, 1, 12 - i) == 1 ? 30 : 29;\n            if (offset > dm) {\n                lunarM++;\n                offset -= dm;\n            } else {\n                break;\n            }\n        }\n        lunarD = (int) (offset);\n        lunar.lunarYear = lunarY;\n        lunar.lunarMonth = lunarM;\n        lunar.isLeap = false;\n        if (leap != 0 && lunarM > leap) {\n            lunar.lunarMonth = lunarM - 1;\n            if (lunarM == leap + 1) {\n                lunar.isLeap = true;\n            }\n        }\n        lunar.lunarDay = lunarD;\n        return lunar;\n    }\n\n    private static Solar solarFromInt(final long g) {\n        long y = (10000 * g + 14780) / 3652425;\n        long ddd = g - (365 * y + y / 4 - y / 100 + y / 400);\n        if (ddd < 0) {\n            y--;\n            ddd = g - (365 * y + y / 4 - y / 100 + y / 400);\n        }\n        long mi = (100 * ddd + 52) / 3060;\n        long mm = (mi + 2) % 12 + 1;\n        y = y + (mi + 2) / 12;\n        long dd = ddd - (mi * 306 + 5) / 10 + 1;\n        Solar solar = new Solar();\n        solar.solarYear = (int) y;\n        solar.solarMonth = (int) mm;\n        solar.solarDay = (int) dd;\n        return solar;\n    }\n\n    private static long solarToInt(int y, int m, final int d) {\n        m = (m + 9) % 12;\n        y = y - m / 10;\n        return 365 * y + y / 4 - y / 100 + y / 400 + (m * 306 + 5) / 10 + (d - 1);\n    }\n\n    public static class Lunar {\n\n        public int     lunarYear;\n        public int     lunarMonth;\n        public int     lunarDay;\n        public boolean isLeap;\n\n        Lunar() {\n        }\n\n        public Lunar(int lunarYear, int lunarMonth, int lunarDay, boolean isLeap) {\n            this.lunarYear = lunarYear;\n            this.lunarMonth = lunarMonth;\n            this.lunarDay = lunarDay;\n            this.isLeap = isLeap;\n        }\n\n        @Override\n        public String toString() {\n            return \"\" + lunarYear + \", \" + lunarMonth + \", \" + lunarDay + \", \" + isLeap;\n        }\n    }\n\n    public static class Solar {\n\n        public int solarYear;\n        public int solarMonth;\n        public int solarDay;\n\n        Solar() {\n        }\n\n        public Solar(int solarYear, int solarMonth, int solarDay) {\n            this.solarYear = solarYear;\n            this.solarMonth = solarMonth;\n            this.solarDay = solarDay;\n        }\n\n        @Override\n        public String toString() {\n            return \"\" + solarYear + \", \" + solarMonth + \", \" + solarDay;\n        }\n    }\n}\n\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/MapUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.util.Pair;\n\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.Hashtable;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.TreeMap;\n\n/**\n * <pre>\n *     author: blankj\n *     blog  : http://blankj.com\n *     time  : 2019/08/12\n *     desc  : utils about map\n * </pre>\n */\npublic class MapUtils {\n\n    private MapUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Returns a new read-only map with the specified contents, given as a list of pairs\n     * where the first value is the key and the second is the value.\n     *\n     * @param pairs a list of pairs\n     * @return a new read-only map with the specified contents\n     */\n    @SafeVarargs\n    public static <K, V> Map<K, V> newUnmodifiableMap(final Pair<K, V>... pairs) {\n        return Collections.unmodifiableMap(newHashMap(pairs));\n    }\n\n    @SafeVarargs\n    public static <K, V> HashMap<K, V> newHashMap(final Pair<K, V>... pairs) {\n        HashMap<K, V> map = new HashMap<>();\n        if (pairs == null || pairs.length == 0) {\n            return map;\n        }\n        for (Pair<K, V> pair : pairs) {\n            if (pair == null) continue;\n            map.put(pair.first, pair.second);\n        }\n        return map;\n    }\n\n    @SafeVarargs\n    public static <K, V> LinkedHashMap<K, V> newLinkedHashMap(final Pair<K, V>... pairs) {\n        LinkedHashMap<K, V> map = new LinkedHashMap<>();\n        if (pairs == null || pairs.length == 0) {\n            return map;\n        }\n        for (Pair<K, V> pair : pairs) {\n            if (pair == null) continue;\n            map.put(pair.first, pair.second);\n        }\n        return map;\n    }\n\n    @SafeVarargs\n    public static <K, V> TreeMap<K, V> newTreeMap(final Comparator<K> comparator,\n                                                  final Pair<K, V>... pairs) {\n        if (comparator == null) {\n            throw new IllegalArgumentException(\"comparator must not be null\");\n        }\n        TreeMap<K, V> map = new TreeMap<>(comparator);\n        if (pairs == null || pairs.length == 0) {\n            return map;\n        }\n        for (Pair<K, V> pair : pairs) {\n            if (pair == null) continue;\n            map.put(pair.first, pair.second);\n        }\n        return map;\n    }\n\n    @SafeVarargs\n    public static <K, V> Hashtable<K, V> newHashTable(final Pair<K, V>... pairs) {\n        Hashtable<K, V> map = new Hashtable<>();\n        if (pairs == null || pairs.length == 0) {\n            return map;\n        }\n        for (Pair<K, V> pair : pairs) {\n            if (pair == null) continue;\n            map.put(pair.first, pair.second);\n        }\n        return map;\n    }\n\n    /**\n     * Null-safe check if the specified map is empty.\n     * <p>\n     * Null returns true.\n     *\n     * @param map the map to check, may be null\n     * @return true if empty or null\n     */\n    public static boolean isEmpty(Map map) {\n        return map == null || map.size() == 0;\n    }\n\n    /**\n     * Null-safe check if the specified map is not empty.\n     * <p>\n     * Null returns false.\n     *\n     * @param map the map to check, may be null\n     * @return true if non-null and non-empty\n     */\n    public static boolean isNotEmpty(Map map) {\n        return !isEmpty(map);\n    }\n\n    /**\n     * Gets the size of the map specified.\n     *\n     * @param map The map.\n     * @return the size of the map specified\n     */\n    public static int size(Map map) {\n        if (map == null) return 0;\n        return map.size();\n    }\n\n    /**\n     * Executes the given closure on each element in the collection.\n     * <p>\n     * If the input collection or closure is null, there is no change made.\n     *\n     * @param map     the map to get the input from, may be null\n     * @param closure the closure to perform, may be null\n     */\n    public static <K, V> void forAllDo(Map<K, V> map, Closure<K, V> closure) {\n        if (map == null || closure == null) return;\n        for (Map.Entry<K, V> kvEntry : map.entrySet()) {\n            closure.execute(kvEntry.getKey(), kvEntry.getValue());\n        }\n    }\n\n    /**\n     * Transform the map by applying a Transformer to each element.\n     * <p>\n     * If the input map or transformer is null, there is no change made.\n     *\n     * @param map         the map to get the input from, may be null\n     * @param transformer the transformer to perform, may be null\n     */\n    public static <K1, V1, K2, V2> Map<K2, V2> transform(Map<K1, V1> map, final Transformer<K1, V1, K2, V2> transformer) {\n        if (map == null || transformer == null) return null;\n        try {\n            final Map<K2, V2> transMap = map.getClass().newInstance();\n            forAllDo(map, new Closure<K1, V1>() {\n                @Override\n                public void execute(K1 key, V1 value) {\n                    Pair<K2, V2> pair = transformer.transform(key, value);\n                    transMap.put(pair.first, pair.second);\n                }\n            });\n            return transMap;\n        } catch (IllegalAccessException e) {\n            e.printStackTrace();\n        } catch (InstantiationException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    /**\n     * Return the string of map.\n     *\n     * @param map The map.\n     * @return the string of map\n     */\n    public static String toString(Map map) {\n        if (map == null) return \"null\";\n        return map.toString();\n    }\n\n    public interface Closure<K, V> {\n        void execute(K key, V value);\n    }\n\n    public interface Transformer<K1, V1, K2, V2> {\n        Pair<K2, V2> transform(K1 k1, V1 v1);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/MessengerUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.annotation.SuppressLint;\nimport android.app.Notification;\nimport android.app.Service;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.Message;\nimport android.os.Messenger;\nimport android.os.RemoteException;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.Nullable;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/07/10\n *     desc  : utils about messenger\n * </pre>\n */\npublic class MessengerUtils {\n\n    private static ConcurrentHashMap<String, MessageCallback> subscribers = new ConcurrentHashMap<>();\n\n    private static Map<String, Client> sClientMap = new HashMap<>();\n    private static Client              sLocalClient;\n\n    private static final int    WHAT_SUBSCRIBE   = 0x00;\n    private static final int    WHAT_UNSUBSCRIBE = 0x01;\n    private static final int    WHAT_SEND        = 0x02;\n    private static final String KEY_STRING       = \"MESSENGER_UTILS\";\n\n    public static void register() {\n        if (UtilsBridge.isMainProcess()) {\n            if (UtilsBridge.isServiceRunning(ServerService.class.getName())) {\n                Log.i(\"MessengerUtils\", \"Server service is running.\");\n                return;\n            }\n            startServiceCompat(new Intent(Utils.getApp(), ServerService.class));\n            return;\n        }\n        if (sLocalClient == null) {\n            Client client = new Client(null);\n            if (client.bind()) {\n                sLocalClient = client;\n            } else {\n                Log.e(\"MessengerUtils\", \"Bind service failed.\");\n            }\n        } else {\n            Log.i(\"MessengerUtils\", \"The client have been bind.\");\n        }\n    }\n\n    public static void unregister() {\n        if (UtilsBridge.isMainProcess()) {\n            if (!UtilsBridge.isServiceRunning(ServerService.class.getName())) {\n                Log.i(\"MessengerUtils\", \"Server service isn't running.\");\n                return;\n            }\n            Intent intent = new Intent(Utils.getApp(), ServerService.class);\n            Utils.getApp().stopService(intent);\n        }\n        if (sLocalClient != null) {\n            sLocalClient.unbind();\n        }\n    }\n\n    public static void register(final String pkgName) {\n        if (sClientMap.containsKey(pkgName)) {\n            Log.i(\"MessengerUtils\", \"register: client registered: \" + pkgName);\n            return;\n        }\n        Client client = new Client(pkgName);\n        if (client.bind()) {\n            sClientMap.put(pkgName, client);\n        } else {\n            Log.e(\"MessengerUtils\", \"register: client bind failed: \" + pkgName);\n        }\n    }\n\n    public static void unregister(final String pkgName) {\n        if (!sClientMap.containsKey(pkgName)) {\n            Log.i(\"MessengerUtils\", \"unregister: client didn't register: \" + pkgName);\n            return;\n        }\n        Client client = sClientMap.get(pkgName);\n        sClientMap.remove(pkgName);\n        if (client != null) {\n            client.unbind();\n        }\n    }\n\n    public static void subscribe(@NonNull final String key, @NonNull final MessageCallback callback) {\n        subscribers.put(key, callback);\n    }\n\n    public static void unsubscribe(@NonNull final String key) {\n        subscribers.remove(key);\n    }\n\n    public static void post(@NonNull String key, @NonNull Bundle data) {\n        data.putString(KEY_STRING, key);\n        if (sLocalClient != null) {\n            sLocalClient.sendMsg2Server(data);\n        } else {\n            Intent intent = new Intent(Utils.getApp(), ServerService.class);\n            intent.putExtras(data);\n            startServiceCompat(intent);\n        }\n        for (Client client : sClientMap.values()) {\n            client.sendMsg2Server(data);\n        }\n    }\n\n    private static void startServiceCompat(Intent intent) {\n        try {\n            intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                Utils.getApp().startForegroundService(intent);\n            } else {\n                Utils.getApp().startService(intent);\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    static class Client {\n\n        String             mPkgName;\n        Messenger          mServer;\n        LinkedList<Bundle> mCached = new LinkedList<>();\n        @SuppressLint(\"HandlerLeak\")\n        Handler mReceiveServeMsgHandler = new Handler() {\n            @Override\n            public void handleMessage(Message msg) {\n                Bundle data = msg.getData();\n                data.setClassLoader(MessengerUtils.class.getClassLoader());\n                String key = data.getString(KEY_STRING);\n                if (key != null) {\n                    MessageCallback callback = subscribers.get(key);\n                    if (callback != null) {\n                        callback.messageCall(data);\n                    }\n                }\n            }\n        };\n        Messenger         mClient = new Messenger(mReceiveServeMsgHandler);\n        ServiceConnection mConn   = new ServiceConnection() {\n\n            @Override\n            public void onServiceConnected(ComponentName name, IBinder service) {\n                Log.d(\"MessengerUtils\", \"client service connected \" + name);\n                mServer = new Messenger(service);\n                int key = UtilsBridge.getCurrentProcessName().hashCode();\n                Message msg = Message.obtain(mReceiveServeMsgHandler, WHAT_SUBSCRIBE, key, 0);\n                msg.getData().setClassLoader(MessengerUtils.class.getClassLoader());\n                msg.replyTo = mClient;\n                try {\n                    mServer.send(msg);\n                } catch (RemoteException e) {\n                    e.printStackTrace();\n                }\n                sendCachedMsg2Server();\n            }\n\n            @Override\n            public void onServiceDisconnected(ComponentName name) {\n                Log.w(\"MessengerUtils\", \"client service disconnected:\" + name);\n                mServer = null;\n                if (!bind()) {\n                    Log.e(\"MessengerUtils\", \"client service rebind failed: \" + name);\n                }\n            }\n        };\n\n        Client(String pkgName) {\n            this.mPkgName = pkgName;\n        }\n\n        boolean bind() {\n            if (TextUtils.isEmpty(mPkgName)) {\n                Intent intent = new Intent(Utils.getApp(), ServerService.class);\n                return Utils.getApp().bindService(intent, mConn, Context.BIND_AUTO_CREATE);\n            }\n            if (UtilsBridge.isAppInstalled(mPkgName)) {\n                if (UtilsBridge.isAppRunning(mPkgName)) {\n                    Intent intent = new Intent(mPkgName + \".messenger\");\n                    intent.setPackage(mPkgName);\n                    return Utils.getApp().bindService(intent, mConn, Context.BIND_AUTO_CREATE);\n                } else {\n                    Log.e(\"MessengerUtils\", \"bind: the app is not running -> \" + mPkgName);\n                    return false;\n                }\n            } else {\n                Log.e(\"MessengerUtils\", \"bind: the app is not installed -> \" + mPkgName);\n                return false;\n            }\n        }\n\n        void unbind() {\n            int key = UtilsBridge.getCurrentProcessName().hashCode();\n            Message msg = Message.obtain(mReceiveServeMsgHandler, WHAT_UNSUBSCRIBE, key, 0);\n            msg.replyTo = mClient;\n            try {\n                mServer.send(msg);\n            } catch (RemoteException e) {\n                e.printStackTrace();\n            }\n            try {\n                Utils.getApp().unbindService(mConn);\n            } catch (Exception ignore) {/*ignore*/}\n        }\n\n        void sendMsg2Server(Bundle bundle) {\n            if (mServer == null) {\n                mCached.addFirst(bundle);\n                Log.i(\"MessengerUtils\", \"save the bundle \" + bundle);\n            } else {\n                sendCachedMsg2Server();\n                if (!send2Server(bundle)) {\n                    mCached.addFirst(bundle);\n                }\n            }\n        }\n\n        private void sendCachedMsg2Server() {\n            if (mCached.isEmpty()) return;\n            for (int i = mCached.size() - 1; i >= 0; --i) {\n                if (send2Server(mCached.get(i))) {\n                    mCached.remove(i);\n                }\n            }\n        }\n\n        private boolean send2Server(Bundle bundle) {\n            Message msg = Message.obtain(mReceiveServeMsgHandler, WHAT_SEND);\n            bundle.setClassLoader(MessengerUtils.class.getClassLoader());\n            msg.setData(bundle);\n            msg.replyTo = mClient;\n            try {\n                mServer.send(msg);\n                return true;\n            } catch (RemoteException e) {\n                e.printStackTrace();\n                return false;\n            }\n        }\n    }\n\n    public static class ServerService extends Service {\n\n        private final ConcurrentHashMap<Integer, Messenger> mClientMap = new ConcurrentHashMap<>();\n\n        @SuppressLint(\"HandlerLeak\")\n        private final Handler mReceiveClientMsgHandler = new Handler() {\n            @Override\n            public void handleMessage(Message msg) {\n                switch (msg.what) {\n                    case WHAT_SUBSCRIBE:\n                        mClientMap.put(msg.arg1, msg.replyTo);\n                        break;\n                    case WHAT_SEND:\n                        sendMsg2Client(msg);\n                        consumeServerProcessCallback(msg);\n                        break;\n                    case WHAT_UNSUBSCRIBE:\n                        mClientMap.remove(msg.arg1);\n                        break;\n                    default:\n                        super.handleMessage(msg);\n                }\n            }\n        };\n\n        private final Messenger messenger = new Messenger(mReceiveClientMsgHandler);\n\n        @Nullable\n        @Override\n        public IBinder onBind(Intent intent) {\n            return messenger.getBinder();\n        }\n\n        @Override\n        public int onStartCommand(Intent intent, int flags, int startId) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                Notification notification = UtilsBridge.getNotification(\n                        NotificationUtils.ChannelConfig.DEFAULT_CHANNEL_CONFIG, null\n                );\n                startForeground(1, notification);\n            }\n            if (intent != null) {\n                Bundle extras = intent.getExtras();\n                if (extras != null) {\n                    Message msg = Message.obtain(mReceiveClientMsgHandler, WHAT_SEND);\n                    msg.replyTo = messenger;\n                    msg.setData(extras);\n                    sendMsg2Client(msg);\n                    consumeServerProcessCallback(msg);\n                }\n            }\n            return START_NOT_STICKY;\n        }\n\n        private void sendMsg2Client(final Message msg) {\n            for (Messenger client : mClientMap.values()) {\n                try {\n                    if (client != null) {\n                        client.send(msg);\n                    }\n                } catch (RemoteException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        private void consumeServerProcessCallback(final Message msg) {\n            Bundle data = msg.getData();\n            if (data != null) {\n                String key = data.getString(KEY_STRING);\n                if (key != null) {\n                    MessageCallback callback = subscribers.get(key);\n                    if (callback != null) {\n                        callback.messageCall(data);\n                    }\n                }\n            }\n        }\n    }\n\n    public interface MessageCallback {\n        void messageCall(Bundle data);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/MetaDataUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.Activity;\nimport android.app.Service;\nimport android.content.BroadcastReceiver;\nimport android.content.ComponentName;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ServiceInfo;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/05/15\n *     desc  : utils about meta-data\n * </pre>\n */\npublic final class MetaDataUtils {\n\n    private MetaDataUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return the value of meta-data in application.\n     *\n     * @param key The key of meta-data.\n     * @return the value of meta-data in application\n     */\n    public static String getMetaDataInApp(@NonNull final String key) {\n        String value = \"\";\n        PackageManager pm = Utils.getApp().getPackageManager();\n        String packageName = Utils.getApp().getPackageName();\n        try {\n            ApplicationInfo ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);\n            value = String.valueOf(ai.metaData.get(key));\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n        }\n        return value;\n    }\n\n    /**\n     * Return the value of meta-data in activity.\n     *\n     * @param activity The activity.\n     * @param key      The key of meta-data.\n     * @return the value of meta-data in activity\n     */\n    public static String getMetaDataInActivity(@NonNull final Activity activity,\n                                               @NonNull final String key) {\n        return getMetaDataInActivity(activity.getClass(), key);\n    }\n\n    /**\n     * Return the value of meta-data in activity.\n     *\n     * @param clz The activity class.\n     * @param key The key of meta-data.\n     * @return the value of meta-data in activity\n     */\n    public static String getMetaDataInActivity(@NonNull final Class<? extends Activity> clz,\n                                               @NonNull final String key) {\n        String value = \"\";\n        PackageManager pm = Utils.getApp().getPackageManager();\n        ComponentName componentName = new ComponentName(Utils.getApp(), clz);\n        try {\n            ActivityInfo ai = pm.getActivityInfo(componentName, PackageManager.GET_META_DATA);\n            value = String.valueOf(ai.metaData.get(key));\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n        }\n        return value;\n    }\n\n    /**\n     * Return the value of meta-data in service.\n     *\n     * @param service The service.\n     * @param key     The key of meta-data.\n     * @return the value of meta-data in service\n     */\n    public static String getMetaDataInService(@NonNull final Service service,\n                                              @NonNull final String key) {\n        return getMetaDataInService(service.getClass(), key);\n    }\n\n    /**\n     * Return the value of meta-data in service.\n     *\n     * @param clz The service class.\n     * @param key The key of meta-data.\n     * @return the value of meta-data in service\n     */\n    public static String getMetaDataInService(@NonNull final Class<? extends Service> clz,\n                                              @NonNull final String key) {\n        String value = \"\";\n        PackageManager pm = Utils.getApp().getPackageManager();\n        ComponentName componentName = new ComponentName(Utils.getApp(), clz);\n        try {\n            ServiceInfo info = pm.getServiceInfo(componentName, PackageManager.GET_META_DATA);\n            value = String.valueOf(info.metaData.get(key));\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n        }\n        return value;\n    }\n\n    /**\n     * Return the value of meta-data in receiver.\n     *\n     * @param receiver The receiver.\n     * @param key      The key of meta-data.\n     * @return the value of meta-data in receiver\n     */\n    public static String getMetaDataInReceiver(@NonNull final BroadcastReceiver receiver,\n                                               @NonNull final String key) {\n        return getMetaDataInReceiver(receiver, key);\n    }\n\n    /**\n     * Return the value of meta-data in receiver.\n     *\n     * @param clz The receiver class.\n     * @param key The key of meta-data.\n     * @return the value of meta-data in receiver\n     */\n    public static String getMetaDataInReceiver(@NonNull final Class<? extends BroadcastReceiver> clz,\n                                               @NonNull final String key) {\n        String value = \"\";\n        PackageManager pm = Utils.getApp().getPackageManager();\n        ComponentName componentName = new ComponentName(Utils.getApp(), clz);\n        try {\n            ActivityInfo info = pm.getReceiverInfo(componentName, PackageManager.GET_META_DATA);\n            value = String.valueOf(info.metaData.get(key));\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n        }\n        return value;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/NetworkUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.annotation.SuppressLint;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.net.ConnectivityManager;\nimport android.net.NetworkInfo;\nimport android.net.wifi.ScanResult;\nimport android.net.wifi.WifiInfo;\nimport android.net.wifi.WifiManager;\nimport android.os.Build;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresPermission;\nimport android.telephony.TelephonyManager;\nimport android.text.TextUtils;\nimport android.text.format.Formatter;\n\nimport java.lang.reflect.Method;\nimport java.net.InetAddress;\nimport java.net.InterfaceAddress;\nimport java.net.NetworkInterface;\nimport java.net.SocketException;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.Enumeration;\nimport java.util.HashSet;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.Timer;\nimport java.util.TimerTask;\nimport java.util.concurrent.CopyOnWriteArraySet;\n\nimport static android.Manifest.permission.ACCESS_COARSE_LOCATION;\nimport static android.Manifest.permission.ACCESS_NETWORK_STATE;\nimport static android.Manifest.permission.ACCESS_WIFI_STATE;\nimport static android.Manifest.permission.CHANGE_WIFI_STATE;\nimport static android.Manifest.permission.INTERNET;\nimport static android.content.Context.WIFI_SERVICE;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/02\n *     desc  : utils about network\n * </pre>\n */\npublic final class NetworkUtils {\n\n    private NetworkUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    public enum NetworkType {\n        NETWORK_ETHERNET,\n        NETWORK_WIFI,\n        NETWORK_5G,\n        NETWORK_4G,\n        NETWORK_3G,\n        NETWORK_2G,\n        NETWORK_UNKNOWN,\n        NETWORK_NO\n    }\n\n    /**\n     * Open the settings of wireless.\n     */\n    public static void openWirelessSettings() {\n        Utils.getApp().startActivity(\n                new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS)\n                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n        );\n    }\n\n    /**\n     * Return whether network is connected.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />}</p>\n     *\n     * @return {@code true}: connected<br>{@code false}: disconnected\n     */\n    @RequiresPermission(ACCESS_NETWORK_STATE)\n    public static boolean isConnected() {\n        NetworkInfo info = getActiveNetworkInfo();\n        return info != null && info.isConnected();\n    }\n\n    /**\n     * Return whether network is available.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @param consumer The consumer.\n     * @return the task\n     */\n    @RequiresPermission(INTERNET)\n    public static Utils.Task<Boolean> isAvailableAsync(@NonNull final Utils.Consumer<Boolean> consumer) {\n        return UtilsBridge.doAsync(new Utils.Task<Boolean>(consumer) {\n            @RequiresPermission(INTERNET)\n            @Override\n            public Boolean doInBackground() {\n                return isAvailable();\n            }\n        });\n    }\n\n    /**\n     * Return whether network is available.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    @RequiresPermission(INTERNET)\n    public static boolean isAvailable() {\n        return isAvailableByDns() || isAvailableByPing(null);\n    }\n\n    /**\n     * Return whether network is available using ping.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     * <p>The default ping ip: 223.5.5.5</p>\n     *\n     * @param consumer The consumer.\n     */\n    @RequiresPermission(INTERNET)\n    public static void isAvailableByPingAsync(final Utils.Consumer<Boolean> consumer) {\n        isAvailableByPingAsync(\"\", consumer);\n    }\n\n    /**\n     * Return whether network is available using ping.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @param ip       The ip address.\n     * @param consumer The consumer.\n     * @return the task\n     */\n    @RequiresPermission(INTERNET)\n    public static Utils.Task<Boolean> isAvailableByPingAsync(final String ip,\n                                                             @NonNull final Utils.Consumer<Boolean> consumer) {\n        return UtilsBridge.doAsync(new Utils.Task<Boolean>(consumer) {\n            @RequiresPermission(INTERNET)\n            @Override\n            public Boolean doInBackground() {\n                return isAvailableByPing(ip);\n            }\n        });\n    }\n\n    /**\n     * Return whether network is available using ping.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     * <p>The default ping ip: 223.5.5.5</p>\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    @RequiresPermission(INTERNET)\n    public static boolean isAvailableByPing() {\n        return isAvailableByPing(\"\");\n    }\n\n    /**\n     * Return whether network is available using ping.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @param ip The ip address.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    @RequiresPermission(INTERNET)\n    public static boolean isAvailableByPing(final String ip) {\n        final String realIp = TextUtils.isEmpty(ip) ? \"223.5.5.5\" : ip;\n        ShellUtils.CommandResult result = ShellUtils.execCmd(String.format(\"ping -c 1 %s\", realIp), false);\n        return result.result == 0;\n    }\n\n    /**\n     * Return whether network is available using domain.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @param consumer The consumer.\n     */\n    @RequiresPermission(INTERNET)\n    public static void isAvailableByDnsAsync(final Utils.Consumer<Boolean> consumer) {\n        isAvailableByDnsAsync(\"\", consumer);\n    }\n\n    /**\n     * Return whether network is available using domain.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @param domain   The name of domain.\n     * @param consumer The consumer.\n     * @return the task\n     */\n    @RequiresPermission(INTERNET)\n    public static Utils.Task isAvailableByDnsAsync(final String domain,\n                                                   @NonNull final Utils.Consumer<Boolean> consumer) {\n        return UtilsBridge.doAsync(new Utils.Task<Boolean>(consumer) {\n            @RequiresPermission(INTERNET)\n            @Override\n            public Boolean doInBackground() {\n                return isAvailableByDns(domain);\n            }\n        });\n    }\n\n    /**\n     * Return whether network is available using domain.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    @RequiresPermission(INTERNET)\n    public static boolean isAvailableByDns() {\n        return isAvailableByDns(\"\");\n    }\n\n    /**\n     * Return whether network is available using domain.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @param domain The name of domain.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    @RequiresPermission(INTERNET)\n    public static boolean isAvailableByDns(final String domain) {\n        final String realDomain = TextUtils.isEmpty(domain) ? \"www.baidu.com\" : domain;\n        InetAddress inetAddress;\n        try {\n            inetAddress = InetAddress.getByName(realDomain);\n            return inetAddress != null;\n        } catch (UnknownHostException e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * Return whether mobile data is enabled.\n     *\n     * @return {@code true}: enabled<br>{@code false}: disabled\n     */\n    public static boolean getMobileDataEnabled() {\n        try {\n            TelephonyManager tm =\n                    (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);\n            if (tm == null) return false;\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                return tm.isDataEnabled();\n            }\n            @SuppressLint(\"PrivateApi\")\n            Method getMobileDataEnabledMethod =\n                    tm.getClass().getDeclaredMethod(\"getDataEnabled\");\n            if (null != getMobileDataEnabledMethod) {\n                return (boolean) getMobileDataEnabledMethod.invoke(tm);\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return false;\n    }\n\n    /**\n     * Return whether using mobile data.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />}</p>\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    @RequiresPermission(ACCESS_NETWORK_STATE)\n    public static boolean isMobileData() {\n        NetworkInfo info = getActiveNetworkInfo();\n        return null != info\n                && info.isAvailable()\n                && info.getType() == ConnectivityManager.TYPE_MOBILE;\n    }\n\n    /**\n     * Return whether using 4G.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />}</p>\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    @RequiresPermission(ACCESS_NETWORK_STATE)\n    public static boolean is4G() {\n        NetworkInfo info = getActiveNetworkInfo();\n        return info != null\n                && info.isAvailable()\n                && info.getSubtype() == TelephonyManager.NETWORK_TYPE_LTE;\n    }\n\n    /**\n     * Return whether using 4G.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />}</p>\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    @RequiresPermission(ACCESS_NETWORK_STATE)\n    public static boolean is5G() {\n        NetworkInfo info = getActiveNetworkInfo();\n        return info != null\n                && info.isAvailable()\n                && info.getSubtype() == TelephonyManager.NETWORK_TYPE_NR;\n    }\n\n    /**\n     * Return whether wifi is enabled.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />}</p>\n     *\n     * @return {@code true}: enabled<br>{@code false}: disabled\n     */\n    @RequiresPermission(ACCESS_WIFI_STATE)\n    public static boolean getWifiEnabled() {\n        @SuppressLint(\"WifiManagerLeak\")\n        WifiManager manager = (WifiManager) Utils.getApp().getSystemService(WIFI_SERVICE);\n        if (manager == null) return false;\n        return manager.isWifiEnabled();\n    }\n\n    /**\n     * Enable or disable wifi.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\" />}</p>\n     *\n     * @param enabled True to enabled, false otherwise.\n     */\n    @RequiresPermission(CHANGE_WIFI_STATE)\n    public static void setWifiEnabled(final boolean enabled) {\n        @SuppressLint(\"WifiManagerLeak\")\n        WifiManager manager = (WifiManager) Utils.getApp().getSystemService(WIFI_SERVICE);\n        if (manager == null) return;\n        if (enabled == manager.isWifiEnabled()) return;\n        manager.setWifiEnabled(enabled);\n    }\n\n    /**\n     * Return whether wifi is connected.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />}</p>\n     *\n     * @return {@code true}: connected<br>{@code false}: disconnected\n     */\n    @RequiresPermission(ACCESS_NETWORK_STATE)\n    public static boolean isWifiConnected() {\n        ConnectivityManager cm =\n                (ConnectivityManager) Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE);\n        if (cm == null) return false;\n        NetworkInfo ni = cm.getActiveNetworkInfo();\n        return ni != null && ni.getType() == ConnectivityManager.TYPE_WIFI;\n    }\n\n    /**\n     * Return whether wifi is available.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />},\n     * {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @return {@code true}: available<br>{@code false}: unavailable\n     */\n    @RequiresPermission(allOf = {ACCESS_WIFI_STATE, INTERNET})\n    public static boolean isWifiAvailable() {\n        return getWifiEnabled() && isAvailable();\n    }\n\n    /**\n     * Return whether wifi is available.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />},\n     * {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @param consumer The consumer.\n     * @return the task\n     */\n    @RequiresPermission(allOf = {ACCESS_WIFI_STATE, INTERNET})\n    public static Utils.Task<Boolean> isWifiAvailableAsync(@NonNull final Utils.Consumer<Boolean> consumer) {\n        return UtilsBridge.doAsync(new Utils.Task<Boolean>(consumer) {\n            @RequiresPermission(allOf = {ACCESS_WIFI_STATE, INTERNET})\n            @Override\n            public Boolean doInBackground() {\n                return isWifiAvailable();\n            }\n        });\n    }\n\n    /**\n     * Return the name of network operate.\n     *\n     * @return the name of network operate\n     */\n    public static String getNetworkOperatorName() {\n        TelephonyManager tm =\n                (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);\n        if (tm == null) return \"\";\n        return tm.getNetworkOperatorName();\n    }\n\n    /**\n     * Return type of network.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />}</p>\n     *\n     * @return type of network\n     * <ul>\n     * <li>{@link NetworkType#NETWORK_ETHERNET} </li>\n     * <li>{@link NetworkType#NETWORK_WIFI    } </li>\n     * <li>{@link NetworkType#NETWORK_4G      } </li>\n     * <li>{@link NetworkType#NETWORK_3G      } </li>\n     * <li>{@link NetworkType#NETWORK_2G      } </li>\n     * <li>{@link NetworkType#NETWORK_UNKNOWN } </li>\n     * <li>{@link NetworkType#NETWORK_NO      } </li>\n     * </ul>\n     */\n    @RequiresPermission(ACCESS_NETWORK_STATE)\n    public static NetworkType getNetworkType() {\n        if (isEthernet()) {\n            return NetworkType.NETWORK_ETHERNET;\n        }\n        NetworkInfo info = getActiveNetworkInfo();\n        if (info != null && info.isAvailable()) {\n            if (info.getType() == ConnectivityManager.TYPE_WIFI) {\n                return NetworkType.NETWORK_WIFI;\n            } else if (info.getType() == ConnectivityManager.TYPE_MOBILE) {\n                switch (info.getSubtype()) {\n                    case TelephonyManager.NETWORK_TYPE_GSM:\n                    case TelephonyManager.NETWORK_TYPE_GPRS:\n                    case TelephonyManager.NETWORK_TYPE_CDMA:\n                    case TelephonyManager.NETWORK_TYPE_EDGE:\n                    case TelephonyManager.NETWORK_TYPE_1xRTT:\n                    case TelephonyManager.NETWORK_TYPE_IDEN:\n                        return NetworkType.NETWORK_2G;\n\n                    case TelephonyManager.NETWORK_TYPE_TD_SCDMA:\n                    case TelephonyManager.NETWORK_TYPE_EVDO_A:\n                    case TelephonyManager.NETWORK_TYPE_UMTS:\n                    case TelephonyManager.NETWORK_TYPE_EVDO_0:\n                    case TelephonyManager.NETWORK_TYPE_HSDPA:\n                    case TelephonyManager.NETWORK_TYPE_HSUPA:\n                    case TelephonyManager.NETWORK_TYPE_HSPA:\n                    case TelephonyManager.NETWORK_TYPE_EVDO_B:\n                    case TelephonyManager.NETWORK_TYPE_EHRPD:\n                    case TelephonyManager.NETWORK_TYPE_HSPAP:\n                        return NetworkType.NETWORK_3G;\n\n                    case TelephonyManager.NETWORK_TYPE_IWLAN:\n                    case TelephonyManager.NETWORK_TYPE_LTE:\n                        return NetworkType.NETWORK_4G;\n\n                    case TelephonyManager.NETWORK_TYPE_NR:\n                        return NetworkType.NETWORK_5G;\n                    default:\n                        String subtypeName = info.getSubtypeName();\n                        if (subtypeName.equalsIgnoreCase(\"TD-SCDMA\")\n                                || subtypeName.equalsIgnoreCase(\"WCDMA\")\n                                || subtypeName.equalsIgnoreCase(\"CDMA2000\")) {\n                            return NetworkType.NETWORK_3G;\n                        } else {\n                            return NetworkType.NETWORK_UNKNOWN;\n                        }\n                }\n            } else {\n                return NetworkType.NETWORK_UNKNOWN;\n            }\n        }\n        return NetworkType.NETWORK_NO;\n    }\n\n    /**\n     * Return whether using ethernet.\n     * <p>Must hold\n     * {@code <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />}</p>\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    @RequiresPermission(ACCESS_NETWORK_STATE)\n    private static boolean isEthernet() {\n        final ConnectivityManager cm =\n                (ConnectivityManager) Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE);\n        if (cm == null) return false;\n        final NetworkInfo info = cm.getNetworkInfo(ConnectivityManager.TYPE_ETHERNET);\n        if (info == null) return false;\n        NetworkInfo.State state = info.getState();\n        if (null == state) return false;\n        return state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING;\n    }\n\n    @RequiresPermission(ACCESS_NETWORK_STATE)\n    private static NetworkInfo getActiveNetworkInfo() {\n        ConnectivityManager cm =\n                (ConnectivityManager) Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE);\n        if (cm == null) return null;\n        return cm.getActiveNetworkInfo();\n    }\n\n    /**\n     * Return the ip address.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @param useIPv4  True to use ipv4, false otherwise.\n     * @param consumer The consumer.\n     * @return the task\n     */\n    public static Utils.Task<String> getIPAddressAsync(final boolean useIPv4,\n                                                       @NonNull final Utils.Consumer<String> consumer) {\n        return UtilsBridge.doAsync(new Utils.Task<String>(consumer) {\n            @RequiresPermission(INTERNET)\n            @Override\n            public String doInBackground() {\n                return getIPAddress(useIPv4);\n            }\n        });\n    }\n\n    /**\n     * Return the ip address.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @param useIPv4 True to use ipv4, false otherwise.\n     * @return the ip address\n     */\n    @RequiresPermission(INTERNET)\n    public static String getIPAddress(final boolean useIPv4) {\n        try {\n            Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();\n            LinkedList<InetAddress> adds = new LinkedList<>();\n            while (nis.hasMoreElements()) {\n                NetworkInterface ni = nis.nextElement();\n                // To prevent phone of xiaomi return \"10.0.2.15\"\n                if (!ni.isUp() || ni.isLoopback()) continue;\n                Enumeration<InetAddress> addresses = ni.getInetAddresses();\n                while (addresses.hasMoreElements()) {\n                    adds.addFirst(addresses.nextElement());\n                }\n            }\n            for (InetAddress add : adds) {\n                if (!add.isLoopbackAddress()) {\n                    String hostAddress = add.getHostAddress();\n                    boolean isIPv4 = hostAddress.indexOf(':') < 0;\n                    if (useIPv4) {\n                        if (isIPv4) return hostAddress;\n                    } else {\n                        if (!isIPv4) {\n                            int index = hostAddress.indexOf('%');\n                            return index < 0\n                                    ? hostAddress.toUpperCase()\n                                    : hostAddress.substring(0, index).toUpperCase();\n                        }\n                    }\n                }\n            }\n        } catch (SocketException e) {\n            e.printStackTrace();\n        }\n        return \"\";\n    }\n\n    /**\n     * Return the ip address of broadcast.\n     *\n     * @return the ip address of broadcast\n     */\n    public static String getBroadcastIpAddress() {\n        try {\n            Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();\n            LinkedList<InetAddress> adds = new LinkedList<>();\n            while (nis.hasMoreElements()) {\n                NetworkInterface ni = nis.nextElement();\n                if (!ni.isUp() || ni.isLoopback()) continue;\n                List<InterfaceAddress> ias = ni.getInterfaceAddresses();\n                for (int i = 0, size = ias.size(); i < size; i++) {\n                    InterfaceAddress ia = ias.get(i);\n                    InetAddress broadcast = ia.getBroadcast();\n                    if (broadcast != null) {\n                        return broadcast.getHostAddress();\n                    }\n                }\n            }\n        } catch (SocketException e) {\n            e.printStackTrace();\n        }\n        return \"\";\n    }\n\n    /**\n     * Return the domain address.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @param domain   The name of domain.\n     * @param consumer The consumer.\n     * @return the task\n     */\n    @RequiresPermission(INTERNET)\n    public static Utils.Task<String> getDomainAddressAsync(final String domain,\n                                                           @NonNull final Utils.Consumer<String> consumer) {\n        return UtilsBridge.doAsync(new Utils.Task<String>(consumer) {\n            @RequiresPermission(INTERNET)\n            @Override\n            public String doInBackground() {\n                return getDomainAddress(domain);\n            }\n        });\n    }\n\n    /**\n     * Return the domain address.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.INTERNET\" />}</p>\n     *\n     * @param domain The name of domain.\n     * @return the domain address\n     */\n    @RequiresPermission(INTERNET)\n    public static String getDomainAddress(final String domain) {\n        InetAddress inetAddress;\n        try {\n            inetAddress = InetAddress.getByName(domain);\n            return inetAddress.getHostAddress();\n        } catch (UnknownHostException e) {\n            e.printStackTrace();\n            return \"\";\n        }\n    }\n\n    /**\n     * Return the ip address by wifi.\n     *\n     * @return the ip address by wifi\n     */\n    @RequiresPermission(ACCESS_WIFI_STATE)\n    public static String getIpAddressByWifi() {\n        @SuppressLint(\"WifiManagerLeak\")\n        WifiManager wm = (WifiManager) Utils.getApp().getSystemService(Context.WIFI_SERVICE);\n        if (wm == null) return \"\";\n        return Formatter.formatIpAddress(wm.getDhcpInfo().ipAddress);\n    }\n\n    /**\n     * Return the gate way by wifi.\n     *\n     * @return the gate way by wifi\n     */\n    @RequiresPermission(ACCESS_WIFI_STATE)\n    public static String getGatewayByWifi() {\n        @SuppressLint(\"WifiManagerLeak\")\n        WifiManager wm = (WifiManager) Utils.getApp().getSystemService(Context.WIFI_SERVICE);\n        if (wm == null) return \"\";\n        return Formatter.formatIpAddress(wm.getDhcpInfo().gateway);\n    }\n\n    /**\n     * Return the net mask by wifi.\n     *\n     * @return the net mask by wifi\n     */\n    @RequiresPermission(ACCESS_WIFI_STATE)\n    public static String getNetMaskByWifi() {\n        @SuppressLint(\"WifiManagerLeak\")\n        WifiManager wm = (WifiManager) Utils.getApp().getSystemService(Context.WIFI_SERVICE);\n        if (wm == null) return \"\";\n        return Formatter.formatIpAddress(wm.getDhcpInfo().netmask);\n    }\n\n    /**\n     * Return the server address by wifi.\n     *\n     * @return the server address by wifi\n     */\n    @RequiresPermission(ACCESS_WIFI_STATE)\n    public static String getServerAddressByWifi() {\n        @SuppressLint(\"WifiManagerLeak\")\n        WifiManager wm = (WifiManager) Utils.getApp().getSystemService(Context.WIFI_SERVICE);\n        if (wm == null) return \"\";\n        return Formatter.formatIpAddress(wm.getDhcpInfo().serverAddress);\n    }\n\n    /**\n     * Return the ssid.\n     *\n     * @return the ssid.\n     */\n    @RequiresPermission(ACCESS_WIFI_STATE)\n    public static String getSSID() {\n        WifiManager wm = (WifiManager) Utils.getApp().getApplicationContext().getSystemService(WIFI_SERVICE);\n        if (wm == null) return \"\";\n        WifiInfo wi = wm.getConnectionInfo();\n        if (wi == null) return \"\";\n        String ssid = wi.getSSID();\n        if (TextUtils.isEmpty(ssid)) {\n            return \"\";\n        }\n        if (ssid.length() > 2 && ssid.charAt(0) == '\"' && ssid.charAt(ssid.length() - 1) == '\"') {\n            return ssid.substring(1, ssid.length() - 1);\n        }\n        return ssid;\n    }\n\n    /**\n     * Register the status of network changed listener.\n     *\n     * @param listener The status of network changed listener\n     */\n    @RequiresPermission(ACCESS_NETWORK_STATE)\n    public static void registerNetworkStatusChangedListener(final OnNetworkStatusChangedListener listener) {\n        NetworkChangedReceiver.getInstance().registerListener(listener);\n    }\n\n    /**\n     * Return whether the status of network changed listener has been registered.\n     *\n     * @param listener The listener\n     * @return true to registered, false otherwise.\n     */\n    public static boolean isRegisteredNetworkStatusChangedListener(final OnNetworkStatusChangedListener listener) {\n        return NetworkChangedReceiver.getInstance().isRegistered(listener);\n    }\n\n    /**\n     * Unregister the status of network changed listener.\n     *\n     * @param listener The status of network changed listener.\n     */\n    public static void unregisterNetworkStatusChangedListener(final OnNetworkStatusChangedListener listener) {\n        NetworkChangedReceiver.getInstance().unregisterListener(listener);\n    }\n\n    @RequiresPermission(allOf = {ACCESS_WIFI_STATE, ACCESS_COARSE_LOCATION})\n    public static WifiScanResults getWifiScanResult() {\n        WifiScanResults result = new WifiScanResults();\n        if (!getWifiEnabled()) return result;\n        @SuppressLint(\"WifiManagerLeak\")\n        WifiManager wm = (WifiManager) Utils.getApp().getSystemService(WIFI_SERVICE);\n        //noinspection ConstantConditions\n        List<ScanResult> results = wm.getScanResults();\n        if (results != null) {\n            result.setAllResults(results);\n        }\n        return result;\n    }\n\n    private static final long                                 SCAN_PERIOD_MILLIS    = 3000;\n    private static final Set<Utils.Consumer<WifiScanResults>> SCAN_RESULT_CONSUMERS = new CopyOnWriteArraySet<>();\n    private static       Timer                                sScanWifiTimer;\n    private static       WifiScanResults                      sPreWifiScanResults;\n\n    @RequiresPermission(allOf = {ACCESS_WIFI_STATE, CHANGE_WIFI_STATE, ACCESS_COARSE_LOCATION})\n    public static void addOnWifiChangedConsumer(final Utils.Consumer<WifiScanResults> consumer) {\n        if (consumer == null) return;\n        UtilsBridge.runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                if (SCAN_RESULT_CONSUMERS.isEmpty()) {\n                    SCAN_RESULT_CONSUMERS.add(consumer);\n                    startScanWifi();\n                    return;\n                }\n                consumer.accept(sPreWifiScanResults);\n                SCAN_RESULT_CONSUMERS.add(consumer);\n            }\n        });\n    }\n\n    private static void startScanWifi() {\n        sPreWifiScanResults = new WifiScanResults();\n        sScanWifiTimer = new Timer();\n        sScanWifiTimer.schedule(new TimerTask() {\n            @RequiresPermission(allOf = {ACCESS_WIFI_STATE, CHANGE_WIFI_STATE, ACCESS_COARSE_LOCATION})\n            @Override\n            public void run() {\n                startScanWifiIfEnabled();\n                WifiScanResults scanResults = getWifiScanResult();\n                if (isSameScanResults(sPreWifiScanResults.allResults, scanResults.allResults)) {\n                    return;\n                }\n                sPreWifiScanResults = scanResults;\n                UtilsBridge.runOnUiThread(new Runnable() {\n                    @Override\n                    public void run() {\n                        for (Utils.Consumer<WifiScanResults> consumer : SCAN_RESULT_CONSUMERS) {\n                            consumer.accept(sPreWifiScanResults);\n                        }\n                    }\n                });\n            }\n        }, 0, SCAN_PERIOD_MILLIS);\n    }\n\n    @RequiresPermission(allOf = {ACCESS_WIFI_STATE, CHANGE_WIFI_STATE})\n    private static void startScanWifiIfEnabled() {\n        if (!getWifiEnabled()) return;\n        @SuppressLint(\"WifiManagerLeak\")\n        WifiManager wm = (WifiManager) Utils.getApp().getSystemService(WIFI_SERVICE);\n        //noinspection ConstantConditions\n        wm.startScan();\n    }\n\n    public static void removeOnWifiChangedConsumer(final Utils.Consumer<WifiScanResults> consumer) {\n        if (consumer == null) return;\n        UtilsBridge.runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                SCAN_RESULT_CONSUMERS.remove(consumer);\n                if (SCAN_RESULT_CONSUMERS.isEmpty()) {\n                    stopScanWifi();\n                }\n            }\n        });\n    }\n\n    private static void stopScanWifi() {\n        if (sScanWifiTimer != null) {\n            sScanWifiTimer.cancel();\n            sScanWifiTimer = null;\n        }\n    }\n\n    private static boolean isSameScanResults(List<ScanResult> l1, List<ScanResult> l2) {\n        if (l1 == null && l2 == null) {\n            return true;\n        }\n        if (l1 == null || l2 == null) {\n            return false;\n        }\n        if (l1.size() != l2.size()) {\n            return false;\n        }\n        for (int i = 0; i < l1.size(); i++) {\n            ScanResult r1 = l1.get(i);\n            ScanResult r2 = l2.get(i);\n            if (!isSameScanResultContent(r1, r2)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    private static boolean isSameScanResultContent(ScanResult r1, ScanResult r2) {\n        return r1 != null && r2 != null && UtilsBridge.equals(r1.BSSID, r2.BSSID)\n                && UtilsBridge.equals(r1.SSID, r2.SSID)\n                && UtilsBridge.equals(r1.capabilities, r2.capabilities)\n                && r1.level == r2.level;\n    }\n\n    public static final class NetworkChangedReceiver extends BroadcastReceiver {\n\n        private static NetworkChangedReceiver getInstance() {\n            return LazyHolder.INSTANCE;\n        }\n\n        private NetworkType                         mType;\n        private Set<OnNetworkStatusChangedListener> mListeners = new HashSet<>();\n\n        @RequiresPermission(ACCESS_NETWORK_STATE)\n        void registerListener(final OnNetworkStatusChangedListener listener) {\n            if (listener == null) return;\n            UtilsBridge.runOnUiThread(new Runnable() {\n                @Override\n                @RequiresPermission(ACCESS_NETWORK_STATE)\n                public void run() {\n                    int preSize = mListeners.size();\n                    mListeners.add(listener);\n                    if (preSize == 0 && mListeners.size() == 1) {\n                        mType = getNetworkType();\n                        IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);\n                        Utils.getApp().registerReceiver(NetworkChangedReceiver.getInstance(), intentFilter);\n                    }\n                }\n            });\n        }\n\n        boolean isRegistered(final OnNetworkStatusChangedListener listener) {\n            if (listener == null) return false;\n            return mListeners.contains(listener);\n        }\n\n        void unregisterListener(final OnNetworkStatusChangedListener listener) {\n            if (listener == null) return;\n            UtilsBridge.runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    int preSize = mListeners.size();\n                    mListeners.remove(listener);\n                    if (preSize == 1 && mListeners.size() == 0) {\n                        Utils.getApp().unregisterReceiver(NetworkChangedReceiver.getInstance());\n                    }\n                }\n            });\n        }\n\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {\n                // debouncing\n                UtilsBridge.runOnUiThreadDelayed(new Runnable() {\n                    @Override\n                    @RequiresPermission(ACCESS_NETWORK_STATE)\n                    public void run() {\n                        NetworkType networkType = NetworkUtils.getNetworkType();\n                        if (mType == networkType) return;\n                        mType = networkType;\n                        if (networkType == NetworkType.NETWORK_NO) {\n                            for (OnNetworkStatusChangedListener listener : mListeners) {\n                                listener.onDisconnected();\n                            }\n                        } else {\n                            for (OnNetworkStatusChangedListener listener : mListeners) {\n                                listener.onConnected(networkType);\n                            }\n                        }\n                    }\n                }, 1000);\n            }\n        }\n\n        private static class LazyHolder {\n            private static final NetworkChangedReceiver INSTANCE = new NetworkChangedReceiver();\n        }\n    }\n\n//    /**\n//     * Register the status of network changed listener.\n//     */\n//    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n//    @RequiresPermission(ACCESS_NETWORK_STATE)\n//    public static void registerNetworkStatusChangedListener() {\n//        ConnectivityManager cm = (ConnectivityManager) Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE);\n//        if (cm == null) return;\n//        NetworkCallbackImpl networkCallback = NetworkCallbackImpl.LazyHolder.INSTANCE;\n//        NetworkRequest.Builder builder = new NetworkRequest.Builder();\n//        NetworkRequest request = builder.build();\n//        cm.registerNetworkCallback(new NetworkRequest.Builder().build(), networkCallback);\n//    }\n//\n//\n//    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n//    public static final class NetworkCallbackImpl extends ConnectivityManager.NetworkCallback {\n//\n//        @Override\n//        public void onAvailable(@NonNull Network network) {\n//            super.onAvailable(network);\n//            LogUtils.d(TAG, \"onAvailable: \" + network);\n//        }\n//\n//        @Override\n//        public void onLosing(@NonNull Network network, int maxMsToLive) {\n//            super.onLosing(network, maxMsToLive);\n//            LogUtils.d(TAG, \"onLosing: \" + network);\n//        }\n//\n//        @Override\n//        public void onLost(@NonNull Network network) {\n//            super.onLost(network);\n//            LogUtils.e(TAG, \"onLost: \" + network);\n//        }\n//\n//        @Override\n//        public void onUnavailable() {\n//            super.onUnavailable();\n//            LogUtils.e(TAG, \"onUnavailable\");\n//        }\n//\n//        @Override\n//        public void onCapabilitiesChanged(@NonNull Network network, @NonNull NetworkCapabilities cap) {\n//            super.onCapabilitiesChanged(network, cap);\n//            if (cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {\n//                if (cap.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {\n//                    LogUtils.d(TAG, \"onCapabilitiesChanged: 网络类型为wifi\");\n//                } else if (cap.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {\n//                    LogUtils.d(TAG, \"onCapabilitiesChanged: 蜂窝网络\");\n//                } else {\n//                    LogUtils.d(TAG, \"onCapabilitiesChanged: 其他网络\");\n//                }\n//                LogUtils.d(TAG, \"onCapabilitiesChanged: \" + network + \", \" + cap);\n//            }\n//        }\n//\n//        @Override\n//        public void onLinkPropertiesChanged(@NonNull Network network, @NonNull LinkProperties lp) {\n//            super.onLinkPropertiesChanged(network, lp);\n//            LogUtils.d(TAG, \"onLinkPropertiesChanged: \" + network + \", \" + lp);\n//        }\n//\n//        @Override\n//        public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {\n//            super.onBlockedStatusChanged(network, blocked);\n//            LogUtils.d(TAG, \"onBlockedStatusChanged: \" + network + \", \" + blocked);\n//        }\n//\n//        private static class LazyHolder {\n//            private static final NetworkCallbackImpl INSTANCE = new NetworkCallbackImpl();\n//        }\n//    }\n\n    public interface OnNetworkStatusChangedListener {\n        void onDisconnected();\n\n        void onConnected(NetworkType networkType);\n    }\n\n    public static final class WifiScanResults {\n\n        private List<ScanResult> allResults    = new ArrayList<>();\n        private List<ScanResult> filterResults = new ArrayList<>();\n\n        public WifiScanResults() {\n        }\n\n        public List<ScanResult> getAllResults() {\n            return allResults;\n        }\n\n        public List<ScanResult> getFilterResults() {\n            return filterResults;\n        }\n\n        public void setAllResults(List<ScanResult> allResults) {\n            this.allResults = allResults;\n            filterResults = filterScanResult(allResults);\n        }\n\n        private static List<ScanResult> filterScanResult(final List<ScanResult> results) {\n            if (results == null || results.isEmpty()) {\n                return new ArrayList<>();\n            }\n            LinkedHashMap<String, ScanResult> map = new LinkedHashMap<>(results.size());\n            for (ScanResult result : results) {\n                if (TextUtils.isEmpty(result.SSID)) {\n                    continue;\n                }\n                ScanResult resultInMap = map.get(result.SSID);\n                if (resultInMap != null && resultInMap.level >= result.level) {\n                    continue;\n                }\n                map.put(result.SSID, result);\n            }\n            return new ArrayList<>(map.values());\n        }\n\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/NotificationUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.annotation.SuppressLint;\nimport android.app.Notification;\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.content.Context;\nimport android.media.AudioAttributes;\nimport android.net.Uri;\nimport android.os.Build;\nimport androidx.annotation.IntDef;\nimport androidx.annotation.RequiresPermission;\nimport androidx.core.app.NotificationCompat;\nimport androidx.core.app.NotificationManagerCompat;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.reflect.Method;\n\nimport static android.Manifest.permission.EXPAND_STATUS_BAR;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/10/20\n *     desc  : utils about notification\n * </pre>\n */\npublic class NotificationUtils {\n\n    public static final int IMPORTANCE_UNSPECIFIED = -1000;\n    public static final int IMPORTANCE_NONE        = 0;\n    public static final int IMPORTANCE_MIN         = 1;\n    public static final int IMPORTANCE_LOW         = 2;\n    public static final int IMPORTANCE_DEFAULT     = 3;\n    public static final int IMPORTANCE_HIGH        = 4;\n\n    @IntDef({IMPORTANCE_UNSPECIFIED, IMPORTANCE_NONE, IMPORTANCE_MIN, IMPORTANCE_LOW, IMPORTANCE_DEFAULT, IMPORTANCE_HIGH})\n    @Retention(RetentionPolicy.SOURCE)\n    public @interface Importance {\n    }\n\n    /**\n     * Return whether the notifications enabled.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean areNotificationsEnabled() {\n        return NotificationManagerCompat.from(Utils.getApp()).areNotificationsEnabled();\n    }\n\n    /**\n     * Post a notification to be shown in the status bar.\n     *\n     * @param id       An identifier for this notification.\n     * @param consumer The consumer of create the builder of notification.\n     */\n    public static void notify(int id, Utils.Consumer<NotificationCompat.Builder> consumer) {\n        notify(null, id, ChannelConfig.DEFAULT_CHANNEL_CONFIG, consumer);\n    }\n\n    /**\n     * Post a notification to be shown in the status bar.\n     *\n     * @param tag      A string identifier for this notification.  May be {@code null}.\n     * @param id       An identifier for this notification.\n     * @param consumer The consumer of create the builder of notification.\n     */\n    public static void notify(String tag, int id, Utils.Consumer<NotificationCompat.Builder> consumer) {\n        notify(tag, id, ChannelConfig.DEFAULT_CHANNEL_CONFIG, consumer);\n    }\n\n    /**\n     * Post a notification to be shown in the status bar.\n     *\n     * @param id            An identifier for this notification.\n     * @param channelConfig The notification channel of config.\n     * @param consumer      The consumer of create the builder of notification.\n     */\n    public static void notify(int id, ChannelConfig channelConfig, Utils.Consumer<NotificationCompat.Builder> consumer) {\n        notify(null, id, channelConfig, consumer);\n    }\n\n    /**\n     * Post a notification to be shown in the status bar.\n     *\n     * @param tag           A string identifier for this notification.  May be {@code null}.\n     * @param id            An identifier for this notification.\n     * @param channelConfig The notification channel of config.\n     * @param consumer      The consumer of create the builder of notification.\n     */\n    public static void notify(String tag, int id, ChannelConfig channelConfig, Utils.Consumer<NotificationCompat.Builder> consumer) {\n        NotificationManagerCompat.from(Utils.getApp()).notify(tag, id, getNotification(channelConfig, consumer));\n    }\n\n\n    public static Notification getNotification(ChannelConfig channelConfig, Utils.Consumer<NotificationCompat.Builder> consumer) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            NotificationManager nm = (NotificationManager) Utils.getApp().getSystemService(Context.NOTIFICATION_SERVICE);\n            //noinspection ConstantConditions\n            nm.createNotificationChannel(channelConfig.getNotificationChannel());\n        }\n\n        NotificationCompat.Builder builder = new NotificationCompat.Builder(Utils.getApp());\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            builder.setChannelId(channelConfig.mNotificationChannel.getId());\n        }\n        if (consumer != null) {\n            consumer.accept(builder);\n        }\n\n        return builder.build();\n    }\n\n    /**\n     * Cancel The notification.\n     *\n     * @param tag The tag for the notification will be cancelled.\n     * @param id  The identifier for the notification will be cancelled.\n     */\n    public static void cancel(String tag, final int id) {\n        NotificationManagerCompat.from(Utils.getApp()).cancel(tag, id);\n    }\n\n    /**\n     * Cancel The notification.\n     *\n     * @param id The identifier for the notification will be cancelled.\n     */\n    public static void cancel(final int id) {\n        NotificationManagerCompat.from(Utils.getApp()).cancel(id);\n    }\n\n    /**\n     * Cancel all of the notifications.\n     */\n    public static void cancelAll() {\n        NotificationManagerCompat.from(Utils.getApp()).cancelAll();\n    }\n\n    /**\n     * Set the notification bar's visibility.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.EXPAND_STATUS_BAR\" />}</p>\n     *\n     * @param isVisible True to set notification bar visible, false otherwise.\n     */\n    @RequiresPermission(EXPAND_STATUS_BAR)\n    public static void setNotificationBarVisibility(final boolean isVisible) {\n        String methodName;\n        if (isVisible) {\n            methodName = (Build.VERSION.SDK_INT <= 16) ? \"expand\" : \"expandNotificationsPanel\";\n        } else {\n            methodName = (Build.VERSION.SDK_INT <= 16) ? \"collapse\" : \"collapsePanels\";\n        }\n        invokePanels(methodName);\n    }\n\n    private static void invokePanels(final String methodName) {\n        try {\n            @SuppressLint(\"WrongConstant\")\n            Object service = Utils.getApp().getSystemService(\"statusbar\");\n            @SuppressLint(\"PrivateApi\")\n            Class<?> statusBarManager = Class.forName(\"android.app.StatusBarManager\");\n            Method expand = statusBarManager.getMethod(methodName);\n            expand.invoke(service);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static class ChannelConfig {\n\n        public static final ChannelConfig DEFAULT_CHANNEL_CONFIG = new ChannelConfig(\n                Utils.getApp().getPackageName(), Utils.getApp().getPackageName(), IMPORTANCE_DEFAULT\n        );\n\n        private NotificationChannel mNotificationChannel;\n\n        public ChannelConfig(String id, CharSequence name, @Importance int importance) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                mNotificationChannel = new NotificationChannel(id, name, importance);\n            }\n        }\n\n        public NotificationChannel getNotificationChannel() {\n            return mNotificationChannel;\n        }\n\n        /**\n         * Sets whether or not notifications posted to this channel can interrupt the user in\n         * {@link NotificationManager.Policy#INTERRUPTION_FILTER_PRIORITY} mode.\n         * <p>\n         * Only modifiable by the system and notification ranker.\n         */\n        public ChannelConfig setBypassDnd(boolean bypassDnd) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                mNotificationChannel.setBypassDnd(bypassDnd);\n            }\n            return this;\n        }\n\n        /**\n         * Sets the user visible description of this channel.\n         *\n         * <p>The recommended maximum length is 300 characters; the value may be truncated if it is too\n         * long.\n         */\n        public ChannelConfig setDescription(String description) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                mNotificationChannel.setDescription(description);\n            }\n            return this;\n        }\n\n        /**\n         * Sets what group this channel belongs to.\n         * <p>\n         * Group information is only used for presentation, not for behavior.\n         * <p>\n         * Only modifiable before the channel is submitted to\n         * {@link NotificationManager#createNotificationChannel(NotificationChannel)}, unless the\n         * channel is not currently part of a group.\n         *\n         * @param groupId the id of a group created by\n         *                {@link NotificationManager#createNotificationChannelGroup)}.\n         */\n        public ChannelConfig setGroup(String groupId) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                mNotificationChannel.setGroup(groupId);\n            }\n            return this;\n        }\n\n        /**\n         * Sets the level of interruption of this notification channel.\n         * <p>\n         * Only modifiable before the channel is submitted to\n         * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.\n         *\n         * @param importance the amount the user should be interrupted by\n         *                   notifications from this channel.\n         */\n        public ChannelConfig setImportance(@Importance int importance) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                mNotificationChannel.setImportance(importance);\n            }\n            return this;\n        }\n\n        /**\n         * Sets the notification light color for notifications posted to this channel, if lights are\n         * {@link NotificationChannel#enableLights(boolean) enabled} on this channel and the device supports that feature.\n         * <p>\n         * Only modifiable before the channel is submitted to\n         * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.\n         */\n        public ChannelConfig setLightColor(int argb) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                mNotificationChannel.setLightColor(argb);\n            }\n            return this;\n        }\n\n        /**\n         * Sets whether notifications posted to this channel appear on the lockscreen or not, and if so,\n         * whether they appear in a redacted form. See e.g. {@link Notification#VISIBILITY_SECRET}.\n         * <p>\n         * Only modifiable by the system and notification ranker.\n         */\n        public ChannelConfig setLockscreenVisibility(int lockscreenVisibility) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                mNotificationChannel.setLockscreenVisibility(lockscreenVisibility);\n            }\n            return this;\n        }\n\n        /**\n         * Sets the user visible name of this channel.\n         *\n         * <p>The recommended maximum length is 40 characters; the value may be truncated if it is too\n         * long.\n         */\n        public ChannelConfig setName(CharSequence name) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                mNotificationChannel.setName(name);\n            }\n            return this;\n        }\n\n        /**\n         * Sets whether notifications posted to this channel can appear as application icon badges\n         * in a Launcher.\n         * <p>\n         * Only modifiable before the channel is submitted to\n         * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.\n         *\n         * @param showBadge true if badges should be allowed to be shown.\n         */\n        public ChannelConfig setShowBadge(boolean showBadge) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                mNotificationChannel.setShowBadge(showBadge);\n            }\n            return this;\n        }\n\n        /**\n         * Sets the sound that should be played for notifications posted to this channel and its\n         * audio attributes. Notification channels with an {@link NotificationChannel#getImportance() importance} of at\n         * least {@link NotificationManager#IMPORTANCE_DEFAULT} should have a sound.\n         * <p>\n         * Only modifiable before the channel is submitted to\n         * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.\n         */\n        public ChannelConfig setSound(Uri sound, AudioAttributes audioAttributes) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                mNotificationChannel.setSound(sound, audioAttributes);\n            }\n            return this;\n        }\n\n        /**\n         * Sets the vibration pattern for notifications posted to this channel. If the provided\n         * pattern is valid (non-null, non-empty), will {@link NotificationChannel#enableVibration(boolean)} enable\n         * vibration} as well. Otherwise, vibration will be disabled.\n         * <p>\n         * Only modifiable before the channel is submitted to\n         * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.\n         */\n        public ChannelConfig setVibrationPattern(long[] vibrationPattern) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                mNotificationChannel.setVibrationPattern(vibrationPattern);\n            }\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/NumberUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.text.DecimalFormat;\nimport java.text.NumberFormat;\n\n/**\n * <pre>\n *     author: blankj\n *     blog  : http://blankj.com\n *     time  : 2020/04/12\n *     desc  : utils about number\n * </pre>\n */\npublic final class NumberUtils {\n\n    private static final ThreadLocal<DecimalFormat> DF_THREAD_LOCAL = new ThreadLocal<DecimalFormat>() {\n        @Override\n        protected DecimalFormat initialValue() {\n            return (DecimalFormat) NumberFormat.getInstance();\n        }\n    };\n\n    public static DecimalFormat getSafeDecimalFormat() {\n        return DF_THREAD_LOCAL.get();\n    }\n\n    private NumberUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Format the value.\n     *\n     * @param value          The value.\n     * @param fractionDigits The number of digits allowed in the fraction portion of value.\n     * @return the format value\n     */\n    public static String format(float value, int fractionDigits) {\n        return format(value, false, 1, fractionDigits, true);\n    }\n\n    /**\n     * Format the value.\n     *\n     * @param value          The value.\n     * @param fractionDigits The number of digits allowed in the fraction portion of value.\n     * @param isHalfUp       True to rounded towards the nearest neighbor.\n     * @return the format value\n     */\n    public static String format(float value, int fractionDigits, boolean isHalfUp) {\n        return format(value, false, 1, fractionDigits, isHalfUp);\n    }\n\n    /**\n     * Format the value.\n     *\n     * @param value            The value.\n     * @param minIntegerDigits The minimum number of digits allowed in the integer portion of value.\n     * @param fractionDigits   The number of digits allowed in the fraction portion of value.\n     * @param isHalfUp         True to rounded towards the nearest neighbor.\n     * @return the format value\n     */\n    public static String format(float value, int minIntegerDigits, int fractionDigits, boolean isHalfUp) {\n        return format(value, false, minIntegerDigits, fractionDigits, isHalfUp);\n    }\n\n    /**\n     * Format the value.\n     *\n     * @param value          The value.\n     * @param isGrouping     True to set grouping will be used in this format, false otherwise.\n     * @param fractionDigits The number of digits allowed in the fraction portion of value.\n     * @return the format value\n     */\n    public static String format(float value, boolean isGrouping, int fractionDigits) {\n        return format(value, isGrouping, 1, fractionDigits, true);\n    }\n\n    /**\n     * Format the value.\n     *\n     * @param value          The value.\n     * @param isGrouping     True to set grouping will be used in this format, false otherwise.\n     * @param fractionDigits The number of digits allowed in the fraction portion of value.\n     * @param isHalfUp       True to rounded towards the nearest neighbor.\n     * @return the format value\n     */\n    public static String format(float value, boolean isGrouping, int fractionDigits, boolean isHalfUp) {\n        return format(value, isGrouping, 1, fractionDigits, isHalfUp);\n    }\n\n    /**\n     * Format the value.\n     *\n     * @param value            The value.\n     * @param isGrouping       True to set grouping will be used in this format, false otherwise.\n     * @param minIntegerDigits The minimum number of digits allowed in the integer portion of value.\n     * @param fractionDigits   The number of digits allowed in the fraction portion of value.\n     * @param isHalfUp         True to rounded towards the nearest neighbor.\n     * @return the format value\n     */\n    public static String format(float value, boolean isGrouping, int minIntegerDigits, int fractionDigits, boolean isHalfUp) {\n        return format(float2Double(value), isGrouping, minIntegerDigits, fractionDigits, isHalfUp);\n    }\n\n    /**\n     * Format the value.\n     *\n     * @param value          The value.\n     * @param fractionDigits The number of digits allowed in the fraction portion of value.\n     * @return the format value\n     */\n    public static String format(double value, int fractionDigits) {\n        return format(value, false, 1, fractionDigits, true);\n    }\n\n    /**\n     * Format the value.\n     *\n     * @param value          The value.\n     * @param fractionDigits The number of digits allowed in the fraction portion of value.\n     * @param isHalfUp       True to rounded towards the nearest neighbor.\n     * @return the format value\n     */\n    public static String format(double value, int fractionDigits, boolean isHalfUp) {\n        return format(value, false, 1, fractionDigits, isHalfUp);\n    }\n\n    /**\n     * Format the value.\n     *\n     * @param value            The value.\n     * @param minIntegerDigits The minimum number of digits allowed in the integer portion of value.\n     * @param fractionDigits   The number of digits allowed in the fraction portion of value.\n     * @param isHalfUp         True to rounded towards the nearest neighbor.\n     * @return the format value\n     */\n    public static String format(double value, int minIntegerDigits, int fractionDigits, boolean isHalfUp) {\n        return format(value, false, minIntegerDigits, fractionDigits, isHalfUp);\n    }\n\n    /**\n     * Format the value.\n     *\n     * @param value          The value.\n     * @param isGrouping     True to set grouping will be used in this format, false otherwise.\n     * @param fractionDigits The number of digits allowed in the fraction portion of value.\n     * @return the format value\n     */\n    public static String format(double value, boolean isGrouping, int fractionDigits) {\n        return format(value, isGrouping, 1, fractionDigits, true);\n    }\n\n    /**\n     * Format the value.\n     *\n     * @param value          The value.\n     * @param isGrouping     True to set grouping will be used in this format, false otherwise.\n     * @param fractionDigits The number of digits allowed in the fraction portion of value.\n     * @param isHalfUp       True to rounded towards the nearest neighbor.\n     * @return the format value\n     */\n    public static String format(double value, boolean isGrouping, int fractionDigits, boolean isHalfUp) {\n        return format(value, isGrouping, 1, fractionDigits, isHalfUp);\n    }\n\n    /**\n     * Format the value.\n     *\n     * @param value            The value.\n     * @param isGrouping       True to set grouping will be used in this format, false otherwise.\n     * @param minIntegerDigits The minimum number of digits allowed in the integer portion of value.\n     * @param fractionDigits   The number of digits allowed in the fraction portion of value.\n     * @param isHalfUp         True to rounded towards the nearest neighbor.\n     * @return the format value\n     */\n    public static String format(double value, boolean isGrouping, int minIntegerDigits, int fractionDigits, boolean isHalfUp) {\n        DecimalFormat nf = getSafeDecimalFormat();\n        nf.setGroupingUsed(isGrouping);\n        nf.setRoundingMode(isHalfUp ? RoundingMode.HALF_UP : RoundingMode.DOWN);\n        nf.setMinimumIntegerDigits(minIntegerDigits);\n        nf.setMinimumFractionDigits(fractionDigits);\n        nf.setMaximumFractionDigits(fractionDigits);\n        return nf.format(value);\n    }\n\n    /**\n     * Float to double.\n     *\n     * @param value The value.\n     * @return the number of double\n     */\n    public static double float2Double(float value) {\n        return new BigDecimal(String.valueOf(value)).doubleValue();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ObjectUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.os.Build;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport androidx.collection.LongSparseArray;\nimport androidx.collection.SimpleArrayMap;\n\nimport android.util.SparseArray;\nimport android.util.SparseBooleanArray;\nimport android.util.SparseIntArray;\nimport android.util.SparseLongArray;\n\nimport java.lang.reflect.Array;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Comparator;\nimport java.util.Map;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2017/12/24\n *     desc  : utils about object\n * </pre>\n */\npublic final class ObjectUtils {\n\n    private ObjectUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return whether object is empty.\n     *\n     * @param obj The object.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isEmpty(final Object obj) {\n        if (obj == null) {\n            return true;\n        }\n        if (obj.getClass().isArray() && Array.getLength(obj) == 0) {\n            return true;\n        }\n        if (obj instanceof CharSequence && obj.toString().length() == 0) {\n            return true;\n        }\n        if (obj instanceof Collection && ((Collection) obj).isEmpty()) {\n            return true;\n        }\n        if (obj instanceof Map && ((Map) obj).isEmpty()) {\n            return true;\n        }\n        if (obj instanceof SimpleArrayMap && ((SimpleArrayMap) obj).isEmpty()) {\n            return true;\n        }\n        if (obj instanceof SparseArray && ((SparseArray) obj).size() == 0) {\n            return true;\n        }\n        if (obj instanceof SparseBooleanArray && ((SparseBooleanArray) obj).size() == 0) {\n            return true;\n        }\n        if (obj instanceof SparseIntArray && ((SparseIntArray) obj).size() == 0) {\n            return true;\n        }\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\n            if (obj instanceof SparseLongArray && ((SparseLongArray) obj).size() == 0) {\n                return true;\n            }\n        }\n        if (obj instanceof LongSparseArray && ((LongSparseArray) obj).size() == 0) {\n            return true;\n        }\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            if (obj instanceof android.util.LongSparseArray\n                    && ((android.util.LongSparseArray) obj).size() == 0) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    public static boolean isEmpty(final CharSequence obj) {\n        return obj == null || obj.toString().length() == 0;\n    }\n\n    public static boolean isEmpty(final Collection obj) {\n        return obj == null || obj.isEmpty();\n    }\n\n    public static boolean isEmpty(final Map obj) {\n        return obj == null || obj.isEmpty();\n    }\n\n    public static boolean isEmpty(final SimpleArrayMap obj) {\n        return obj == null || obj.isEmpty();\n    }\n\n    public static boolean isEmpty(final SparseArray obj) {\n        return obj == null || obj.size() == 0;\n    }\n\n    public static boolean isEmpty(final SparseBooleanArray obj) {\n        return obj == null || obj.size() == 0;\n    }\n\n    public static boolean isEmpty(final SparseIntArray obj) {\n        return obj == null || obj.size() == 0;\n    }\n\n    public static boolean isEmpty(final LongSparseArray obj) {\n        return obj == null || obj.size() == 0;\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)\n    public static boolean isEmpty(final SparseLongArray obj) {\n        return obj == null || obj.size() == 0;\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)\n    public static boolean isEmpty(final android.util.LongSparseArray obj) {\n        return obj == null || obj.size() == 0;\n    }\n\n    /**\n     * Return whether object is not empty.\n     *\n     * @param obj The object.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isNotEmpty(final Object obj) {\n        return !isEmpty(obj);\n    }\n\n\n    public static boolean isNotEmpty(final CharSequence obj) {\n        return !isEmpty(obj);\n    }\n\n    public static boolean isNotEmpty(final Collection obj) {\n        return !isEmpty(obj);\n    }\n\n    public static boolean isNotEmpty(final Map obj) {\n        return !isEmpty(obj);\n    }\n\n    public static boolean isNotEmpty(final SimpleArrayMap obj) {\n        return !isEmpty(obj);\n    }\n\n    public static boolean isNotEmpty(final SparseArray obj) {\n        return !isEmpty(obj);\n    }\n\n    public static boolean isNotEmpty(final SparseBooleanArray obj) {\n        return !isEmpty(obj);\n    }\n\n    public static boolean isNotEmpty(final SparseIntArray obj) {\n        return !isEmpty(obj);\n    }\n\n    public static boolean isNotEmpty(final LongSparseArray obj) {\n        return !isEmpty(obj);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)\n    public static boolean isNotEmpty(final SparseLongArray obj) {\n        return !isEmpty(obj);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)\n    public static boolean isNotEmpty(final android.util.LongSparseArray obj) {\n        return !isEmpty(obj);\n    }\n\n    /**\n     * Return whether object1 is equals to object2.\n     *\n     * @param o1 The first object.\n     * @param o2 The second object.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean equals(final Object o1, final Object o2) {\n        return o1 == o2 || (o1 != null && o1.equals(o2));\n    }\n\n    /**\n     * Returns 0 if the arguments are identical and {@code\n     * c.compare(a, b)} otherwise.\n     * Consequently, if both arguments are {@code null} 0\n     * is returned.\n     */\n    public static <T> int compare(T a, T b, @NonNull Comparator<? super T> c) {\n        return (a == b) ? 0 : c.compare(a, b);\n    }\n\n    /**\n     * Checks that the specified object reference is not {@code null}.\n     */\n    public static <T> T requireNonNull(T obj) {\n        if (obj == null) throw new NullPointerException();\n        return obj;\n    }\n\n    /**\n     * Checks that the specified object reference is not {@code null} and\n     * throws a customized {@link NullPointerException} if it is.\n     */\n    public static <T> T requireNonNull(T obj, String ifNullTip) {\n        if (obj == null) throw new NullPointerException(ifNullTip);\n        return obj;\n    }\n\n    /**\n     * Require the objects are not null.\n     *\n     * @param objects The object.\n     * @throws NullPointerException if any object is null in objects\n     */\n    public static void requireNonNulls(final Object... objects) {\n        if (objects == null) throw new NullPointerException();\n        for (Object object : objects) {\n            if (object == null) throw new NullPointerException();\n        }\n    }\n\n    /**\n     * Return the nonnull object or default object.\n     *\n     * @param object        The object.\n     * @param defaultObject The default object to use with the object is null.\n     * @param <T>           The value type.\n     * @return the nonnull object or default object\n     */\n    public static <T> T getOrDefault(final T object, final T defaultObject) {\n        if (object == null) {\n            return defaultObject;\n        }\n        return object;\n    }\n\n    /**\n     * Returns the result of calling {@code toString} for a non-{@code\n     * null} argument and {@code \"null\"} for a {@code null} argument.\n     */\n    public static String toString(Object obj) {\n        return String.valueOf(obj);\n    }\n\n    /**\n     * Returns the result of calling {@code toString} on the first\n     * argument if the first argument is not {@code null} and returns\n     * the second argument otherwise.\n     */\n    public static String toString(Object o, String nullDefault) {\n        return (o != null) ? o.toString() : nullDefault;\n    }\n\n    /**\n     * Return the hash code of object.\n     *\n     * @param o The object.\n     * @return the hash code of object\n     */\n    public static int hashCode(final Object o) {\n        return o != null ? o.hashCode() : 0;\n    }\n\n    /**\n     * Return the hash code of objects.\n     */\n    public static int hashCodes(Object... values) {\n        return Arrays.hashCode(values);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/PathUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.os.Build;\nimport android.os.Environment;\nimport android.text.TextUtils;\n\nimport java.io.File;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/04/15\n *     desc  : utils about path\n * </pre>\n */\npublic final class PathUtils {\n\n    private static final char SEP = File.separatorChar;\n\n    private PathUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Join the path.\n     *\n     * @param parent The parent of path.\n     * @param child  The child path.\n     * @return the path\n     */\n    public static String join(String parent, String child) {\n        if (TextUtils.isEmpty(child)) return parent;\n        if (parent == null) {\n            parent = \"\";\n        }\n        int len = parent.length();\n        String legalSegment = getLegalSegment(child);\n        String newPath;\n        if (len == 0) {\n            newPath = SEP + legalSegment;\n        } else if (parent.charAt(len - 1) == SEP) {\n            newPath = parent + legalSegment;\n        } else {\n            newPath = parent + SEP + legalSegment;\n        }\n        return newPath;\n    }\n\n    private static String getLegalSegment(String segment) {\n        int st = -1, end = -1;\n        char[] charArray = segment.toCharArray();\n        for (int i = 0; i < charArray.length; i++) {\n            char c = charArray[i];\n            if (c != SEP) {\n                if (st == -1) {\n                    st = i;\n                }\n                end = i;\n            }\n        }\n        if (st >= 0 && end >= st) {\n            return segment.substring(st, end + 1);\n        }\n        throw new IllegalArgumentException(\"segment of <\" + segment + \"> is illegal\");\n    }\n\n    /**\n     * Return the path of /system.\n     *\n     * @return the path of /system\n     */\n    public static String getRootPath() {\n        return getAbsolutePath(Environment.getRootDirectory());\n    }\n\n    /**\n     * Return the path of /data.\n     *\n     * @return the path of /data\n     */\n    public static String getDataPath() {\n        return getAbsolutePath(Environment.getDataDirectory());\n    }\n\n    /**\n     * Return the path of /cache.\n     *\n     * @return the path of /cache\n     */\n    public static String getDownloadCachePath() {\n        return getAbsolutePath(Environment.getDownloadCacheDirectory());\n    }\n\n    /**\n     * Return the path of /data/data/package.\n     *\n     * @return the path of /data/data/package\n     */\n    public static String getInternalAppDataPath() {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {\n            return Utils.getApp().getApplicationInfo().dataDir;\n        }\n        return getAbsolutePath(Utils.getApp().getDataDir());\n    }\n\n    /**\n     * Return the path of /data/data/package/code_cache.\n     *\n     * @return the path of /data/data/package/code_cache\n     */\n    public static String getInternalAppCodeCacheDir() {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {\n            return Utils.getApp().getApplicationInfo().dataDir + \"/code_cache\";\n        }\n        return getAbsolutePath(Utils.getApp().getCodeCacheDir());\n    }\n\n    /**\n     * Return the path of /data/data/package/cache.\n     *\n     * @return the path of /data/data/package/cache\n     */\n    public static String getInternalAppCachePath() {\n        return getAbsolutePath(Utils.getApp().getCacheDir());\n    }\n\n    /**\n     * Return the path of /data/data/package/databases.\n     *\n     * @return the path of /data/data/package/databases\n     */\n    public static String getInternalAppDbsPath() {\n        return Utils.getApp().getApplicationInfo().dataDir + \"/databases\";\n    }\n\n    /**\n     * Return the path of /data/data/package/databases/name.\n     *\n     * @param name The name of database.\n     * @return the path of /data/data/package/databases/name\n     */\n    public static String getInternalAppDbPath(String name) {\n        return getAbsolutePath(Utils.getApp().getDatabasePath(name));\n    }\n\n    /**\n     * Return the path of /data/data/package/files.\n     *\n     * @return the path of /data/data/package/files\n     */\n    public static String getInternalAppFilesPath() {\n        return getAbsolutePath(Utils.getApp().getFilesDir());\n    }\n\n    /**\n     * Return the path of /data/data/package/shared_prefs.\n     *\n     * @return the path of /data/data/package/shared_prefs\n     */\n    public static String getInternalAppSpPath() {\n        return Utils.getApp().getApplicationInfo().dataDir + \"/shared_prefs\";\n    }\n\n    /**\n     * Return the path of /data/data/package/no_backup.\n     *\n     * @return the path of /data/data/package/no_backup\n     */\n    public static String getInternalAppNoBackupFilesPath() {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {\n            return Utils.getApp().getApplicationInfo().dataDir + \"/no_backup\";\n        }\n        return getAbsolutePath(Utils.getApp().getNoBackupFilesDir());\n    }\n\n    /**\n     * Return the path of /storage/emulated/0.\n     *\n     * @return the path of /storage/emulated/0\n     */\n    public static String getExternalStoragePath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Environment.getExternalStorageDirectory());\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Music.\n     *\n     * @return the path of /storage/emulated/0/Music\n     */\n    public static String getExternalMusicPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Podcasts.\n     *\n     * @return the path of /storage/emulated/0/Podcasts\n     */\n    public static String getExternalPodcastsPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PODCASTS));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Ringtones.\n     *\n     * @return the path of /storage/emulated/0/Ringtones\n     */\n    public static String getExternalRingtonesPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Alarms.\n     *\n     * @return the path of /storage/emulated/0/Alarms\n     */\n    public static String getExternalAlarmsPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Notifications.\n     *\n     * @return the path of /storage/emulated/0/Notifications\n     */\n    public static String getExternalNotificationsPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_NOTIFICATIONS));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Pictures.\n     *\n     * @return the path of /storage/emulated/0/Pictures\n     */\n    public static String getExternalPicturesPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Movies.\n     *\n     * @return the path of /storage/emulated/0/Movies\n     */\n    public static String getExternalMoviesPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Download.\n     *\n     * @return the path of /storage/emulated/0/Download\n     */\n    public static String getExternalDownloadsPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/DCIM.\n     *\n     * @return the path of /storage/emulated/0/DCIM\n     */\n    public static String getExternalDcimPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Documents.\n     *\n     * @return the path of /storage/emulated/0/Documents\n     */\n    public static String getExternalDocumentsPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {\n            return getAbsolutePath(Environment.getExternalStorageDirectory()) + \"/Documents\";\n        }\n        return getAbsolutePath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Android/data/package.\n     *\n     * @return the path of /storage/emulated/0/Android/data/package\n     */\n    public static String getExternalAppDataPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        File externalCacheDir = Utils.getApp().getExternalCacheDir();\n        if (externalCacheDir == null) return \"\";\n        return getAbsolutePath(externalCacheDir.getParentFile());\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Android/data/package/cache.\n     *\n     * @return the path of /storage/emulated/0/Android/data/package/cache\n     */\n    public static String getExternalAppCachePath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Utils.getApp().getExternalCacheDir());\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Android/data/package/files.\n     *\n     * @return the path of /storage/emulated/0/Android/data/package/files\n     */\n    public static String getExternalAppFilesPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Utils.getApp().getExternalFilesDir(null));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Android/data/package/files/Music.\n     *\n     * @return the path of /storage/emulated/0/Android/data/package/files/Music\n     */\n    public static String getExternalAppMusicPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_MUSIC));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Android/data/package/files/Podcasts.\n     *\n     * @return the path of /storage/emulated/0/Android/data/package/files/Podcasts\n     */\n    public static String getExternalAppPodcastsPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_PODCASTS));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Android/data/package/files/Ringtones.\n     *\n     * @return the path of /storage/emulated/0/Android/data/package/files/Ringtones\n     */\n    public static String getExternalAppRingtonesPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_RINGTONES));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Android/data/package/files/Alarms.\n     *\n     * @return the path of /storage/emulated/0/Android/data/package/files/Alarms\n     */\n    public static String getExternalAppAlarmsPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_ALARMS));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Android/data/package/files/Notifications.\n     *\n     * @return the path of /storage/emulated/0/Android/data/package/files/Notifications\n     */\n    public static String getExternalAppNotificationsPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_NOTIFICATIONS));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Android/data/package/files/Pictures.\n     *\n     * @return path of /storage/emulated/0/Android/data/package/files/Pictures\n     */\n    public static String getExternalAppPicturesPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_PICTURES));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Android/data/package/files/Movies.\n     *\n     * @return the path of /storage/emulated/0/Android/data/package/files/Movies\n     */\n    public static String getExternalAppMoviesPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_MOVIES));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Android/data/package/files/Download.\n     *\n     * @return the path of /storage/emulated/0/Android/data/package/files/Download\n     */\n    public static String getExternalAppDownloadPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Android/data/package/files/DCIM.\n     *\n     * @return the path of /storage/emulated/0/Android/data/package/files/DCIM\n     */\n    public static String getExternalAppDcimPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_DCIM));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Android/data/package/files/Documents.\n     *\n     * @return the path of /storage/emulated/0/Android/data/package/files/Documents\n     */\n    public static String getExternalAppDocumentsPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {\n            return getAbsolutePath(Utils.getApp().getExternalFilesDir(null)) + \"/Documents\";\n        }\n        return getAbsolutePath(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS));\n    }\n\n    /**\n     * Return the path of /storage/emulated/0/Android/obb/package.\n     *\n     * @return the path of /storage/emulated/0/Android/obb/package\n     */\n    public static String getExternalAppObbPath() {\n        if (!UtilsBridge.isSDCardEnableByEnvironment()) return \"\";\n        return getAbsolutePath(Utils.getApp().getObbDir());\n    }\n\n    public static String getRootPathExternalFirst() {\n        String rootPath = getExternalStoragePath();\n        if (TextUtils.isEmpty(rootPath)) {\n            rootPath = getRootPath();\n        }\n        return rootPath;\n    }\n\n    public static String getAppDataPathExternalFirst() {\n        String appDataPath = getExternalAppDataPath();\n        if (TextUtils.isEmpty(appDataPath)) {\n            appDataPath = getInternalAppDataPath();\n        }\n        return appDataPath;\n    }\n\n    public static String getFilesPathExternalFirst() {\n        String filePath = getExternalAppFilesPath();\n        if (TextUtils.isEmpty(filePath)) {\n            filePath = getInternalAppFilesPath();\n        }\n        return filePath;\n    }\n\n    public static String getCachePathExternalFirst() {\n        String appPath = getExternalAppCachePath();\n        if (TextUtils.isEmpty(appPath)) {\n            appPath = getInternalAppCachePath();\n        }\n        return appPath;\n    }\n\n    private static String getAbsolutePath(final File file) {\n        if (file == null) return \"\";\n        return file.getAbsolutePath();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/PermissionUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.provider.Settings;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport androidx.core.content.ContextCompat;\n\nimport android.util.Log;\nimport android.util.Pair;\nimport android.view.MotionEvent;\nimport android.view.WindowManager;\nimport com.didichuxing.doraemonkit.constant.PermissionConstants;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Set;\n\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2017/12/29\n *     desc  : utils about permission\n * </pre>\n */\npublic final class PermissionUtils {\n\n    private static PermissionUtils sInstance;\n\n    private String[]            mPermissionsParam;\n    private OnExplainListener   mOnExplainListener;\n    private OnRationaleListener mOnRationaleListener;\n    private SingleCallback      mSingleCallback;\n    private SimpleCallback      mSimpleCallback;\n    private FullCallback        mFullCallback;\n    private ThemeCallback       mThemeCallback;\n    private Set<String>         mPermissions;\n    private List<String>        mPermissionsRequest;\n    private List<String>        mPermissionsGranted;\n    private List<String>        mPermissionsDenied;\n    private List<String>        mPermissionsDeniedForever;\n\n    private static SimpleCallback sSimpleCallback4WriteSettings;\n    private static SimpleCallback sSimpleCallback4DrawOverlays;\n\n    /**\n     * Return the permissions used in application.\n     *\n     * @return the permissions used in application\n     */\n    public static List<String> getPermissions() {\n        return getPermissions(Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the permissions used in application.\n     *\n     * @param packageName The name of the package.\n     * @return the permissions used in application\n     */\n    public static List<String> getPermissions(final String packageName) {\n        PackageManager pm = Utils.getApp().getPackageManager();\n        try {\n            String[] permissions = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS).requestedPermissions;\n            if (permissions == null) return Collections.emptyList();\n            return Arrays.asList(permissions);\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n            return Collections.emptyList();\n        }\n    }\n\n    /**\n     * Return whether <em>you</em> have been granted the permissions.\n     *\n     * @param permissions The permissions.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isGranted(final String... permissions) {\n        Pair<List<String>, List<String>> requestAndDeniedPermissions = getRequestAndDeniedPermissions(permissions);\n        List<String> deniedPermissions = requestAndDeniedPermissions.second;\n        if (!deniedPermissions.isEmpty()) {\n            return false;\n        }\n        List<String> requestPermissions = requestAndDeniedPermissions.first;\n        for (String permission : requestPermissions) {\n            if (!isGranted(permission)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    private static Pair<List<String>, List<String>> getRequestAndDeniedPermissions(final String... permissionsParam) {\n        List<String> requestPermissions = new ArrayList<>();\n        List<String> deniedPermissions = new ArrayList<>();\n        List<String> appPermissions = getPermissions();\n        for (String param : permissionsParam) {\n            boolean isIncludeInManifest = false;\n            String[] permissions = PermissionConstants.getPermissions(param);\n            for (String permission : permissions) {\n                if (appPermissions.contains(permission)) {\n                    requestPermissions.add(permission);\n                    isIncludeInManifest = true;\n                }\n            }\n            if (!isIncludeInManifest) {\n                deniedPermissions.add(param);\n                Log.e(\"PermissionUtils\", \"U should add the permission of \" + param + \" in manifest.\");\n            }\n        }\n        return Pair.create(requestPermissions, deniedPermissions);\n    }\n\n    private static boolean isGranted(final String permission) {\n        return Build.VERSION.SDK_INT < Build.VERSION_CODES.M\n                || PackageManager.PERMISSION_GRANTED\n                == ContextCompat.checkSelfPermission(Utils.getApp(), permission);\n    }\n\n    /**\n     * Return whether the app can modify system settings.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    public static boolean isGrantedWriteSettings() {\n        return Settings.System.canWrite(Utils.getApp());\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    public static void requestWriteSettings(final SimpleCallback callback) {\n        if (isGrantedWriteSettings()) {\n            if (callback != null) callback.onGranted();\n            return;\n        }\n        sSimpleCallback4WriteSettings = callback;\n        PermissionActivityImpl.start(PermissionActivityImpl.TYPE_WRITE_SETTINGS);\n    }\n\n    @TargetApi(Build.VERSION_CODES.M)\n    private static void startWriteSettingsActivity(final Activity activity, final int requestCode) {\n        Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);\n        intent.setData(Uri.parse(\"package:\" + Utils.getApp().getPackageName()));\n        if (!UtilsBridge.isIntentAvailable(intent)) {\n            launchAppDetailsSettings();\n            return;\n        }\n        activity.startActivityForResult(intent, requestCode);\n    }\n\n    /**\n     * Return whether the app can draw on top of other apps.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    public static boolean isGrantedDrawOverlays() {\n        return Settings.canDrawOverlays(Utils.getApp());\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    public static void requestDrawOverlays(final SimpleCallback callback) {\n        if (isGrantedDrawOverlays()) {\n            if (callback != null) callback.onGranted();\n            return;\n        }\n        sSimpleCallback4DrawOverlays = callback;\n        PermissionActivityImpl.start(PermissionActivityImpl.TYPE_DRAW_OVERLAYS);\n    }\n\n    @TargetApi(Build.VERSION_CODES.M)\n    private static void startOverlayPermissionActivity(final Activity activity, final int requestCode) {\n        Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);\n        intent.setData(Uri.parse(\"package:\" + Utils.getApp().getPackageName()));\n        if (!UtilsBridge.isIntentAvailable(intent)) {\n            launchAppDetailsSettings();\n            return;\n        }\n        activity.startActivityForResult(intent, requestCode);\n    }\n\n    /**\n     * Launch the application's details settings.\n     */\n    public static void launchAppDetailsSettings() {\n        Intent intent = UtilsBridge.getLaunchAppDetailsSettingsIntent(Utils.getApp().getPackageName(), true);\n        if (!UtilsBridge.isIntentAvailable(intent)) return;\n        Utils.getApp().startActivity(intent);\n    }\n\n    /**\n     * Set the permissions.\n     *\n     * @param permissions The permissions.\n     * @return the single {@link PermissionUtils} instance\n     */\n    public static PermissionUtils permissionGroup(@PermissionConstants.PermissionGroup final String... permissions) {\n        return permission(permissions);\n    }\n\n    /**\n     * Set the permissions.\n     *\n     * @param permissions The permissions.\n     * @return the single {@link PermissionUtils} instance\n     */\n    public static PermissionUtils permission(final String... permissions) {\n        return new PermissionUtils(permissions);\n    }\n\n    private PermissionUtils(final String... permissions) {\n        mPermissionsParam = permissions;\n        sInstance = this;\n    }\n\n    /**\n     * Set explain listener.\n     *\n     * @param listener The explain listener.\n     * @return the single {@link PermissionUtils} instance\n     */\n    public PermissionUtils explain(final OnExplainListener listener) {\n        mOnExplainListener = listener;\n        return this;\n    }\n\n    /**\n     * Set rationale listener.\n     *\n     * @param listener The rationale listener.\n     * @return the single {@link PermissionUtils} instance\n     */\n    public PermissionUtils rationale(final OnRationaleListener listener) {\n        mOnRationaleListener = listener;\n        return this;\n    }\n\n    /**\n     * Set the simple call back.\n     *\n     * @param callback the single call back\n     * @return the single {@link PermissionUtils} instance\n     */\n    public PermissionUtils callback(final SingleCallback callback) {\n        mSingleCallback = callback;\n        return this;\n    }\n\n    /**\n     * Set the simple call back.\n     *\n     * @param callback the simple call back\n     * @return the single {@link PermissionUtils} instance\n     */\n    public PermissionUtils callback(final SimpleCallback callback) {\n        mSimpleCallback = callback;\n        return this;\n    }\n\n    /**\n     * Set the full call back.\n     *\n     * @param callback the full call back\n     * @return the single {@link PermissionUtils} instance\n     */\n    public PermissionUtils callback(final FullCallback callback) {\n        mFullCallback = callback;\n        return this;\n    }\n\n    /**\n     * Set the theme callback.\n     *\n     * @param callback The theme callback.\n     * @return the single {@link PermissionUtils} instance\n     */\n    public PermissionUtils theme(final ThemeCallback callback) {\n        mThemeCallback = callback;\n        return this;\n    }\n\n    /**\n     * Start request.\n     */\n    public void request() {\n        if (mPermissionsParam == null || mPermissionsParam.length <= 0) {\n            Log.w(\"PermissionUtils\", \"No permissions to request.\");\n            return;\n        }\n\n        mPermissions = new LinkedHashSet<>();\n        mPermissionsRequest = new ArrayList<>();\n        mPermissionsGranted = new ArrayList<>();\n        mPermissionsDenied = new ArrayList<>();\n        mPermissionsDeniedForever = new ArrayList<>();\n\n        Pair<List<String>, List<String>> requestAndDeniedPermissions = getRequestAndDeniedPermissions(mPermissionsParam);\n        mPermissions.addAll(requestAndDeniedPermissions.first);\n        mPermissionsDenied.addAll(requestAndDeniedPermissions.second);\n\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            mPermissionsGranted.addAll(mPermissions);\n            requestCallback();\n        } else {\n            for (String permission : mPermissions) {\n                if (isGranted(permission)) {\n                    mPermissionsGranted.add(permission);\n                } else {\n                    mPermissionsRequest.add(permission);\n                }\n            }\n            if (mPermissionsRequest.isEmpty()) {\n                requestCallback();\n            } else {\n                startPermissionActivity();\n            }\n        }\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    private void startPermissionActivity() {\n        PermissionActivityImpl.start(PermissionActivityImpl.TYPE_RUNTIME);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    private boolean shouldRationale(final UtilsTransActivity activity, final Runnable againRunnable) {\n        boolean isRationale = false;\n        if (mOnRationaleListener != null) {\n            for (String permission : mPermissionsRequest) {\n                if (activity.shouldShowRequestPermissionRationale(permission)) {\n                    rationalInner(activity, againRunnable);\n                    isRationale = true;\n                    break;\n                }\n            }\n            mOnRationaleListener = null;\n        }\n        return isRationale;\n    }\n\n    private void rationalInner(final UtilsTransActivity activity, final Runnable againRunnable) {\n        getPermissionsStatus(activity);\n        mOnRationaleListener.rationale(activity, new OnRationaleListener.ShouldRequest() {\n            @Override\n            public void again(boolean again) {\n                if (again) {\n                    mPermissionsDenied = new ArrayList<>();\n                    mPermissionsDeniedForever = new ArrayList<>();\n                    againRunnable.run();\n                } else {\n                    activity.finish();\n                    requestCallback();\n                }\n            }\n        });\n    }\n\n    private void getPermissionsStatus(final Activity activity) {\n        for (String permission : mPermissionsRequest) {\n            if (isGranted(permission)) {\n                mPermissionsGranted.add(permission);\n            } else {\n                mPermissionsDenied.add(permission);\n                if (!activity.shouldShowRequestPermissionRationale(permission)) {\n                    mPermissionsDeniedForever.add(permission);\n                }\n            }\n        }\n    }\n\n    private void requestCallback() {\n        if (mSingleCallback != null) {\n            mSingleCallback.callback(mPermissionsDenied.isEmpty(),\n                    mPermissionsGranted, mPermissionsDeniedForever, mPermissionsDenied);\n            mSingleCallback = null;\n        }\n        if (mSimpleCallback != null) {\n            if (mPermissionsDenied.isEmpty()) {\n                mSimpleCallback.onGranted();\n            } else {\n                mSimpleCallback.onDenied();\n            }\n            mSimpleCallback = null;\n        }\n        if (mFullCallback != null) {\n            if (mPermissionsRequest.size() == 0\n                    || mPermissionsGranted.size() > 0) {\n                mFullCallback.onGranted(mPermissionsGranted);\n            }\n            if (!mPermissionsDenied.isEmpty()) {\n                mFullCallback.onDenied(mPermissionsDeniedForever, mPermissionsDenied);\n            }\n            mFullCallback = null;\n        }\n        mOnRationaleListener = null;\n        mThemeCallback = null;\n    }\n\n    private void onRequestPermissionsResult(final Activity activity) {\n        getPermissionsStatus(activity);\n        requestCallback();\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    static final class PermissionActivityImpl extends UtilsTransActivity.TransActivityDelegate {\n\n        private static final String TYPE                = \"TYPE\";\n        private static final int    TYPE_RUNTIME        = 0x01;\n        private static final int    TYPE_WRITE_SETTINGS = 0x02;\n        private static final int    TYPE_DRAW_OVERLAYS  = 0x03;\n\n        private static int currentRequestCode = -1;\n\n        private static PermissionActivityImpl INSTANCE = new PermissionActivityImpl();\n\n        public static void start(final int type) {\n            UtilsTransActivity.start(new Utils.Consumer<Intent>() {\n                @Override\n                public void accept(Intent data) {\n                    data.putExtra(TYPE, type);\n                }\n            }, INSTANCE);\n        }\n\n        @Override\n        public void onCreated(@NonNull final UtilsTransActivity activity, @Nullable Bundle savedInstanceState) {\n            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE\n                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);\n            int type = activity.getIntent().getIntExtra(TYPE, -1);\n            if (type == TYPE_RUNTIME) {\n                if (sInstance == null) {\n                    Log.e(\"PermissionUtils\", \"sInstance is null.\");\n                    activity.finish();\n                    return;\n                }\n                if (sInstance.mPermissionsRequest == null) {\n                    Log.e(\"PermissionUtils\", \"mPermissionsRequest is null.\");\n                    activity.finish();\n                    return;\n                }\n                if (sInstance.mPermissionsRequest.size() <= 0) {\n                    Log.e(\"PermissionUtils\", \"mPermissionsRequest's size is no more than 0.\");\n                    activity.finish();\n                    return;\n                }\n                if (sInstance.mThemeCallback != null) {\n                    sInstance.mThemeCallback.onActivityCreate(activity);\n                }\n                if (sInstance.mOnExplainListener != null) {\n                    sInstance.mOnExplainListener.explain(activity, sInstance.mPermissionsRequest, new OnExplainListener.ShouldRequest() {\n                        @Override\n                        public void start(boolean start) {\n                            if (!start) {\n                                activity.finish();\n                            } else {\n                                requestPermissions(activity);\n                            }\n                        }\n                    });\n                    sInstance.mOnExplainListener = null;\n                    return;\n                }\n                requestPermissions(activity);\n            } else if (type == TYPE_WRITE_SETTINGS) {\n                currentRequestCode = TYPE_WRITE_SETTINGS;\n                startWriteSettingsActivity(activity, TYPE_WRITE_SETTINGS);\n            } else if (type == TYPE_DRAW_OVERLAYS) {\n                currentRequestCode = TYPE_DRAW_OVERLAYS;\n                startOverlayPermissionActivity(activity, TYPE_DRAW_OVERLAYS);\n            } else {\n                activity.finish();\n                Log.e(\"PermissionUtils\", \"type is wrong.\");\n            }\n        }\n\n        private void requestPermissions(final UtilsTransActivity activity) {\n            if (sInstance.shouldRationale(activity, new Runnable() {\n                @Override\n                public void run() {\n                    activity.requestPermissions(sInstance.mPermissionsRequest.toArray(new String[0]), 1);\n                }\n            })) {\n                return;\n            }\n            activity.requestPermissions(sInstance.mPermissionsRequest.toArray(new String[0]), 1);\n        }\n\n        @Override\n        public void onRequestPermissionsResult(@NonNull UtilsTransActivity activity,\n                                               int requestCode,\n                                               @NonNull String[] permissions,\n                                               @NonNull int[] grantResults) {\n            activity.finish();\n            if (sInstance != null && sInstance.mPermissionsRequest != null) {\n                sInstance.onRequestPermissionsResult(activity);\n            }\n        }\n\n\n        @Override\n        public boolean dispatchTouchEvent(@NonNull UtilsTransActivity activity, MotionEvent ev) {\n            activity.finish();\n            return true;\n        }\n\n        @Override\n        public void onDestroy(@NonNull final UtilsTransActivity activity) {\n            if (currentRequestCode != -1) {\n                checkRequestCallback(currentRequestCode);\n                currentRequestCode = -1;\n            }\n            super.onDestroy(activity);\n        }\n\n        @Override\n        public void onActivityResult(@NonNull UtilsTransActivity activity, int requestCode, int resultCode, Intent data) {\n            activity.finish();\n        }\n\n        private void checkRequestCallback(int requestCode) {\n            if (requestCode == TYPE_WRITE_SETTINGS) {\n                if (sSimpleCallback4WriteSettings == null) return;\n                if (isGrantedWriteSettings()) {\n                    sSimpleCallback4WriteSettings.onGranted();\n                } else {\n                    sSimpleCallback4WriteSettings.onDenied();\n                }\n                sSimpleCallback4WriteSettings = null;\n            } else if (requestCode == TYPE_DRAW_OVERLAYS) {\n                if (sSimpleCallback4DrawOverlays == null) return;\n                if (isGrantedDrawOverlays()) {\n                    sSimpleCallback4DrawOverlays.onGranted();\n                } else {\n                    sSimpleCallback4DrawOverlays.onDenied();\n                }\n                sSimpleCallback4DrawOverlays = null;\n            }\n        }\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // interface\n    ///////////////////////////////////////////////////////////////////////////\n\n    public interface OnExplainListener {\n\n        void explain(@NonNull UtilsTransActivity activity, @NonNull List<String> denied, @NonNull ShouldRequest shouldRequest);\n\n        interface ShouldRequest {\n            void start(boolean start);\n        }\n    }\n\n    public interface OnRationaleListener {\n\n        void rationale(@NonNull UtilsTransActivity activity, @NonNull ShouldRequest shouldRequest);\n\n        interface ShouldRequest {\n            void again(boolean again);\n        }\n    }\n\n    public interface SingleCallback {\n        void callback(boolean isAllGranted, @NonNull List<String> granted,\n                      @NonNull List<String> deniedForever, @NonNull List<String> denied);\n    }\n\n\n    public interface SimpleCallback {\n        void onGranted();\n\n        void onDenied();\n    }\n\n    public interface FullCallback {\n        void onGranted(@NonNull List<String> granted);\n\n        void onDenied(@NonNull List<String> deniedForever, @NonNull List<String> denied);\n    }\n\n    public interface ThemeCallback {\n        void onActivityCreate(@NonNull Activity activity);\n    }\n}"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/PhoneUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.os.Build;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresPermission;\nimport android.telephony.TelephonyManager;\nimport android.text.TextUtils;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\n\nimport static android.Manifest.permission.CALL_PHONE;\nimport static android.Manifest.permission.READ_PHONE_STATE;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/02\n *     desc  : utils about phone\n * </pre>\n */\npublic final class PhoneUtils {\n\n    private PhoneUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return whether the device is phone.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isPhone() {\n        TelephonyManager tm = getTelephonyManager();\n        return tm.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE;\n    }\n\n    /**\n     * Return the unique device id.\n     * <p>If the version of SDK is greater than 28, it will return an empty string.</p>\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />}</p>\n     *\n     * @return the unique device id\n     */\n    @SuppressLint(\"HardwareIds\")\n    @RequiresPermission(READ_PHONE_STATE)\n    public static String getDeviceId() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            return \"\";\n        }\n        TelephonyManager tm = getTelephonyManager();\n        String deviceId = tm.getDeviceId();\n        if (!TextUtils.isEmpty(deviceId)) return deviceId;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            String imei = tm.getImei();\n            if (!TextUtils.isEmpty(imei)) return imei;\n            String meid = tm.getMeid();\n            return TextUtils.isEmpty(meid) ? \"\" : meid;\n        }\n        return \"\";\n    }\n\n    /**\n     * Return the serial of device.\n     *\n     * @return the serial of device\n     */\n    @SuppressLint(\"HardwareIds\")\n    @RequiresPermission(READ_PHONE_STATE)\n    public static String getSerial() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            try {\n                return Build.getSerial();\n            } catch (SecurityException e) {\n                e.printStackTrace();\n                return \"\";\n            }\n        }\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? Build.getSerial() : Build.SERIAL;\n    }\n\n    /**\n     * Return the IMEI.\n     * <p>If the version of SDK is greater than 28, it will return an empty string.</p>\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />}</p>\n     *\n     * @return the IMEI\n     */\n    @RequiresPermission(READ_PHONE_STATE)\n    public static String getIMEI() {\n        return getImeiOrMeid(true);\n    }\n\n    /**\n     * Return the MEID.\n     * <p>If the version of SDK is greater than 28, it will return an empty string.</p>\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />}</p>\n     *\n     * @return the MEID\n     */\n    @RequiresPermission(READ_PHONE_STATE)\n    public static String getMEID() {\n        return getImeiOrMeid(false);\n    }\n\n    @SuppressLint(\"HardwareIds\")\n    @RequiresPermission(READ_PHONE_STATE)\n    public static String getImeiOrMeid(boolean isImei) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            return \"\";\n        }\n        TelephonyManager tm = getTelephonyManager();\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            if (isImei) {\n                return getMinOne(tm.getImei(0), tm.getImei(1));\n            } else {\n                return getMinOne(tm.getMeid(0), tm.getMeid(1));\n            }\n        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            String ids = getSystemPropertyByReflect(isImei ? \"ril.gsm.imei\" : \"ril.cdma.meid\");\n            if (!TextUtils.isEmpty(ids)) {\n                String[] idArr = ids.split(\",\");\n                if (idArr.length == 2) {\n                    return getMinOne(idArr[0], idArr[1]);\n                } else {\n                    return idArr[0];\n                }\n            }\n\n            String id0 = tm.getDeviceId();\n            String id1 = \"\";\n            try {\n                Method method = tm.getClass().getMethod(\"getDeviceId\", int.class);\n                id1 = (String) method.invoke(tm,\n                        isImei ? TelephonyManager.PHONE_TYPE_GSM\n                                : TelephonyManager.PHONE_TYPE_CDMA);\n            } catch (NoSuchMethodException e) {\n                e.printStackTrace();\n            } catch (IllegalAccessException e) {\n                e.printStackTrace();\n            } catch (InvocationTargetException e) {\n                e.printStackTrace();\n            }\n            if (isImei) {\n                if (id0 != null && id0.length() < 15) {\n                    id0 = \"\";\n                }\n                if (id1 != null && id1.length() < 15) {\n                    id1 = \"\";\n                }\n            } else {\n                if (id0 != null && id0.length() == 14) {\n                    id0 = \"\";\n                }\n                if (id1 != null && id1.length() == 14) {\n                    id1 = \"\";\n                }\n            }\n            return getMinOne(id0, id1);\n        } else {\n            String deviceId = tm.getDeviceId();\n            if (isImei) {\n                if (deviceId != null && deviceId.length() >= 15) {\n                    return deviceId;\n                }\n            } else {\n                if (deviceId != null && deviceId.length() == 14) {\n                    return deviceId;\n                }\n            }\n        }\n        return \"\";\n    }\n\n    private static String getMinOne(String s0, String s1) {\n        boolean empty0 = TextUtils.isEmpty(s0);\n        boolean empty1 = TextUtils.isEmpty(s1);\n        if (empty0 && empty1) return \"\";\n        if (!empty0 && !empty1) {\n            if (s0.compareTo(s1) <= 0) {\n                return s0;\n            } else {\n                return s1;\n            }\n        }\n        if (!empty0) return s0;\n        return s1;\n    }\n\n    private static String getSystemPropertyByReflect(String key) {\n        try {\n            @SuppressLint(\"PrivateApi\")\n            Class<?> clz = Class.forName(\"android.os.SystemProperties\");\n            Method getMethod = clz.getMethod(\"get\", String.class, String.class);\n            return (String) getMethod.invoke(clz, key, \"\");\n        } catch (Exception e) {/**/}\n        return \"\";\n    }\n\n    /**\n     * Return the IMSI.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />}</p>\n     *\n     * @return the IMSI\n     */\n    @SuppressLint(\"HardwareIds\")\n    @RequiresPermission(READ_PHONE_STATE)\n    public static String getIMSI() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            try {\n                getTelephonyManager().getSubscriberId();\n            } catch (SecurityException e) {\n                e.printStackTrace();\n                return \"\";\n            }\n        }\n        return getTelephonyManager().getSubscriberId();\n    }\n\n    /**\n     * Returns the current phone type.\n     *\n     * @return the current phone type\n     * <ul>\n     * <li>{@link TelephonyManager#PHONE_TYPE_NONE}</li>\n     * <li>{@link TelephonyManager#PHONE_TYPE_GSM }</li>\n     * <li>{@link TelephonyManager#PHONE_TYPE_CDMA}</li>\n     * <li>{@link TelephonyManager#PHONE_TYPE_SIP }</li>\n     * </ul>\n     */\n    public static int getPhoneType() {\n        TelephonyManager tm = getTelephonyManager();\n        return tm.getPhoneType();\n    }\n\n    /**\n     * Return whether sim card state is ready.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isSimCardReady() {\n        TelephonyManager tm = getTelephonyManager();\n        return tm.getSimState() == TelephonyManager.SIM_STATE_READY;\n    }\n\n    /**\n     * Return the sim operator name.\n     *\n     * @return the sim operator name\n     */\n    public static String getSimOperatorName() {\n        TelephonyManager tm = getTelephonyManager();\n        return tm.getSimOperatorName();\n    }\n\n    /**\n     * Return the sim operator using mnc.\n     *\n     * @return the sim operator\n     */\n    public static String getSimOperatorByMnc() {\n        TelephonyManager tm = getTelephonyManager();\n        String operator = tm.getSimOperator();\n        if (operator == null) return \"\";\n        switch (operator) {\n            case \"46000\":\n            case \"46002\":\n            case \"46007\":\n            case \"46020\":\n                return \"中国移动\";\n            case \"46001\":\n            case \"46006\":\n            case \"46009\":\n                return \"中国联通\";\n            case \"46003\":\n            case \"46005\":\n            case \"46011\":\n                return \"中国电信\";\n            default:\n                return operator;\n        }\n    }\n\n    /**\n     * Skip to dial.\n     *\n     * @param phoneNumber The phone number.\n     */\n    public static void dial(@NonNull final String phoneNumber) {\n        Utils.getApp().startActivity(UtilsBridge.getDialIntent(phoneNumber));\n    }\n\n    /**\n     * Make a phone call.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.CALL_PHONE\" />}</p>\n     *\n     * @param phoneNumber The phone number.\n     */\n    @RequiresPermission(CALL_PHONE)\n    public static void call(@NonNull final String phoneNumber) {\n        Utils.getApp().startActivity(UtilsBridge.getCallIntent(phoneNumber));\n    }\n\n    /**\n     * Send sms.\n     *\n     * @param phoneNumber The phone number.\n     * @param content     The content.\n     */\n    public static void sendSms(@NonNull final String phoneNumber, final String content) {\n        Utils.getApp().startActivity(UtilsBridge.getSendSmsIntent(phoneNumber, content));\n    }\n\n    private static TelephonyManager getTelephonyManager() {\n        return (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/PinyinUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\n\nimport androidx.collection.SimpleArrayMap;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 16/11/16\n *     desc  : 拼音相关工具类\n * </pre>\n */\npublic final class PinyinUtils {\n\n    private PinyinUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * 汉字转拼音\n     *\n     * @param ccs 汉字字符串(Chinese characters)\n     * @return 拼音\n     */\n    public static String ccs2Pinyin(final CharSequence ccs) {\n        return ccs2Pinyin(ccs, \"\");\n    }\n\n    /**\n     * 汉字转拼音\n     *\n     * @param ccs   汉字字符串(Chinese characters)\n     * @param split 汉字拼音之间的分隔符\n     * @return 拼音\n     */\n    public static String ccs2Pinyin(final CharSequence ccs, final CharSequence split) {\n        if (ccs == null || ccs.length() == 0) return null;\n        StringBuilder sb = new StringBuilder();\n        for (int i = 0, len = ccs.length(); i < len; i++) {\n            char ch = ccs.charAt(i);\n            if (ch >= 0x4E00 && ch <= 0x9FA5) {\n                int sp = (ch - 0x4E00) * 6;\n                sb.append(pinyinTable.substring(sp, sp + 6).trim());\n            } else {\n                sb.append(ch);\n            }\n            sb.append(split);\n        }\n        return sb.toString();\n    }\n\n    /**\n     * 获取第一个汉字首字母\n     *\n     * @param ccs 汉字字符串(Chinese characters)\n     * @return 拼音\n     */\n    public static String getPinyinFirstLetter(final CharSequence ccs) {\n        if (ccs == null || ccs.length() == 0) return null;\n        return ccs2Pinyin(String.valueOf(ccs.charAt(0))).substring(0, 1);\n    }\n\n    /**\n     * 获取所有汉字的首字母\n     *\n     * @param ccs 汉字字符串(Chinese characters)\n     * @return 所有汉字的首字母\n     */\n    public static String getPinyinFirstLetters(final CharSequence ccs) {\n        return getPinyinFirstLetters(ccs, \"\");\n    }\n\n    /**\n     * 获取所有汉字的首字母\n     *\n     * @param ccs   汉字字符串(Chinese characters)\n     * @param split 首字母之间的分隔符\n     * @return 所有汉字的首字母\n     */\n    public static String getPinyinFirstLetters(final CharSequence ccs, final CharSequence split) {\n        if (ccs == null || ccs.length() == 0) return null;\n        int len = ccs.length();\n        StringBuilder sb = new StringBuilder(len);\n        for (int i = 0; i < len; i++) {\n            sb.append(ccs2Pinyin(String.valueOf(ccs.charAt(i))).substring(0, 1)).append(split);\n        }\n        return sb.toString();\n    }\n\n    /**\n     * 根据名字获取姓氏的拼音\n     *\n     * @param name 名字\n     * @return 姓氏的拼音\n     */\n    public static String getSurnamePinyin(final CharSequence name) {\n        if (name == null || name.length() == 0) return null;\n        if (name.length() >= 2) {\n            CharSequence str = name.subSequence(0, 2);\n            if (str.equals(\"澹台\")) return \"tantai\";\n            else if (str.equals(\"尉迟\")) return \"yuchi\";\n            else if (str.equals(\"万俟\")) return \"moqi\";\n            else if (str.equals(\"单于\")) return \"chanyu\";\n        }\n        char ch = name.charAt(0);\n        if (SURNAMES.containsKey(ch)) {\n            return SURNAMES.get(ch);\n        }\n        if (ch >= 0x4E00 && ch <= 0x9FA5) {\n            int sp = (ch - 0x4E00) * 6;\n            return pinyinTable.substring(sp, sp + 6).trim();\n        } else {\n            return String.valueOf(ch);\n        }\n    }\n\n    /**\n     * 根据名字获取姓氏的首字母\n     *\n     * @param name 名字\n     * @return 姓氏的首字母\n     */\n    public static String getSurnameFirstLetter(final CharSequence name) {\n        String surname = getSurnamePinyin(name);\n        if (surname == null || surname.length() == 0) return null;\n        return String.valueOf(surname.charAt(0));\n    }\n\n    // 多音字姓氏映射表\n    private static final SimpleArrayMap<Character, String> SURNAMES;\n\n    /**\n     * 获取拼音对照表，对比过pinyin4j和其他方式，这样查表设计的好处就是读取快\n     * <p>当该类加载后会一直占有123KB的内存</p>\n     * <p>如果你想存进文件，然后读取操作的话也是可以，但速度肯定没有这样空间换时间快，毕竟现在设备内存都很大</p>\n     * <p>如需更多用法可以用pinyin4j开源库</p>\n     */\n    private static final String pinyinTable;\n\n    static {\n        SURNAMES = new SimpleArrayMap<>(35);\n        SURNAMES.put('乐', \"yue\");\n        SURNAMES.put('乘', \"sheng\");\n        SURNAMES.put('乜', \"nie\");\n        SURNAMES.put('仇', \"qiu\");\n        SURNAMES.put('会', \"gui\");\n        SURNAMES.put('便', \"pian\");\n        SURNAMES.put('区', \"ou\");\n        SURNAMES.put('单', \"shan\");\n        SURNAMES.put('参', \"shen\");\n        SURNAMES.put('句', \"gou\");\n        SURNAMES.put('召', \"shao\");\n        SURNAMES.put('员', \"yun\");\n        SURNAMES.put('宓', \"fu\");\n        SURNAMES.put('弗', \"fei\");\n        SURNAMES.put('折', \"she\");\n        SURNAMES.put('曾', \"zeng\");\n        SURNAMES.put('朴', \"piao\");\n        SURNAMES.put('查', \"zha\");\n        SURNAMES.put('洗', \"xian\");\n        SURNAMES.put('盖', \"ge\");\n        SURNAMES.put('祭', \"zhai\");\n        SURNAMES.put('种', \"chong\");\n        SURNAMES.put('秘', \"bi\");\n        SURNAMES.put('繁', \"po\");\n        SURNAMES.put('缪', \"miao\");\n        SURNAMES.put('能', \"nai\");\n        SURNAMES.put('蕃', \"pi\");\n        SURNAMES.put('覃', \"qin\");\n        SURNAMES.put('解', \"xie\");\n        SURNAMES.put('谌', \"shan\");\n        SURNAMES.put('适', \"kuo\");\n        SURNAMES.put('都', \"du\");\n        SURNAMES.put('阿', \"e\");\n        SURNAMES.put('难', \"ning\");\n        SURNAMES.put('黑', \"he\");\n\n        //noinspection StringBufferReplaceableByString\n        pinyinTable = new StringBuilder(125412)\n                .append(\"yi    ding  kao   qi    shang xia   none  wan   zhang san   shang xia   ji    bu    yu    mian  gai   chou  chou  zhuan qie   pi    shi   shi   qiu   bing  ye    cong  dong  si    cheng diu   qiu   liang diu   you   liang yan   bing  sang  shu   jiu   ge    ya    qiang zhong ji    jie   feng  guan  chuan chan  lin   zhuo  zhu   none  wan   dan   wei   zhu   jing  li    ju    pie   fu    yi    yi    nai   none  jiu   jiu   tuo   me    yi    none  zhi   wu    zha   hu    fa    le    zhong ping  pang  qiao  hu    guai  cheng cheng yi    yin   none  mie   jiu   qi    ye    xi    xiang gai   diu   none  none  shu   none  shi   ji    nang  jia   none  shi   none  none  mai   luan  none  ru    xi    yan   fu    sha   na    gan   none  none  none  none  qian  zhi   gui   gan   luan  lin   yi    jue   le    none  yu    zheng shi   shi   er    chu   yu    kui   yu    yun   hu    qi    wu    jing  si    sui   gen   gen   ya    xie   ya    qi    ya    ji    tou   wang  kang  ta    jiao  hai   yi    chan  heng  mu    none  xiang jing  ting  liang heng  jing  ye    qin   bo    you   xie   dan   lian  duo   wei   ren   ren   ji    none  wang  yi    shen  ren   le    ding  ze    jin   pu    chou  ba    zhang jin   jie   bing  reng  cong  fo    san   lun   none  cang  zi    shi   ta    zhang fu    xian  xian  cha   hong  tong  ren   qian  gan   ge    di    dai   ling  yi    chao  chang sa    shang yi    mu    men   ren   jia   chao  yang  qian  zhong pi    wan   wu    jian  jia   yao   feng  cang  ren   wang  fen   di    fang  zhong qi    pei   yu    diao  dun   wen   yi    xin   kang  yi    ji    ai    wu    ji    fu    fa    xiu   jin   bei   chen  fu    tang  zhong you   huo   hui   yu    cui   yun   san   wei   chuan che   ya    xian  shang chang lun   cang  xun   xin   wei   zhu   chi   xuan  nao   bo    gu    ni    ni    xie   ban   xu    ling  zhou  shen  qu    si    beng  si    jia   pi    yi    si    ai    zheng dian  han   mai   dan   zhu   bu    qu    bi    shao  ci    wei   di    zhu   zuo   you   yang  ti    zhan  he    bi    tuo   she   yu    yi    fo    zuo   gou   ning  tong  ni    xuan  ju    yong  wa    qian  none  ka    none  pei   huai  he    lao   xiang ge    yang  bai   fa    ming  jia   nai   bing  ji    heng  huo   gui   quan  tiao  jiao  ci    yi    shi   xing  shen  tuo   kan   zhi   gai   lai   yi    chi   kua   guang li    yin   shi   mi    zhu   xu    you   an    lu    mou   er    lun   dong  cha   chi   xun   gong  zhou  yi    ru    jian  xia   jia   zai   lu:   none  jiao  zhen  ce    qiao  kuai  chai  ning  nong  jin   wu    hou   jiong cheng zhen  cuo   chou  qin   lu:   ju    shu   ting  shen  tuo   bo    nan   hao   bian  tui   yu    xi    cu    e     qiu   xu    kuang ku    wu    jun   yi    fu    lang  zu    qiao  li    yong  hun   jing  xian  san   pai   su    fu    xi    li    mian  ping  bao   yu    si    xia   xin   xiu   yu    ti    che   chou  none  yan   liang li    lai   si    jian  xiu   fu    he    ju    xiao  pai   jian  biao  ti    fei   feng  ya    an    bei   yu    xin   bi    chi   chang zhi   bing  zan   yao   cui   lia   wan   lai   cang  zong  ge    guan  bei   tian  shu   shu   men   dao   tan   jue   chui  xing  peng  tang  hou   yi    qi    ti    gan   jing  jie   xu    chang jie   fang  zhi   kong  juan  zong  ju    qian  ni    lun   zhuo  wo    luo   song  leng  hun   dong  zi    ben   wu    ju    nai   cai   jian  zhai  ye    zhi   sha   qing  none  ying  cheng qian  yan   nuan  zhong chun  jia   jie   wei   yu    bing  ruo   ti    wei   pian  yan   feng  tang  wo    e     xie   che   sheng kan   di    zuo   cha   ting  bei   ye    huang yao   zhan  qiu   yan   you   jian  xu    zha   chai  fu    bi    zhi   zong  mian  ji    yi    xie   xun   si    duan  ce    zhen  ou    tou   tou   bei   za    lou   jie   wei   fen   chang kui   sou   chi   su    xia   fu    yuan  rong  li    ru    yun   gou   ma    bang  dian  tang  hao   jie   xi    shan  qian  jue   cang  chu   san   bei   xiao  yong  yao   ta    suo   wang  fa    bing  jia   dai   zai   tang  none  bin   chu   nuo   zan   lei   cui   yong  zao   zong  peng  song  ao    chuan yu    zhai  zu    shang qian\")\n                .append(\"g qiang chi   sha   han   zhang qing  yan   di    xi    lou   bei   piao  jin   lian  lu    man   qian  xian  qiu   ying  dong  zhuan xiang shan  qiao  jiong tui   zun   pu    xi    lao   chang guang liao  qi    deng  chan  wei   zhang fan   hui   chuan tie   dan   jiao  jiu   seng  fen   xian  jue   e     jiao  jian  tong  lin   bo    gu    xian  su    xian  jiang min   ye    jin   jia   qiao  pi    feng  zhou  ai    sai   yi    jun   nong  shan  yi    dang  jing  xuan  kuai  jian  chu   dan   jiao  sha   zai   none  bin   an    ru    tai   chou  chai  lan   ni    jin   qian  meng  wu    neng  qiong ni    chang lie   lei   lu:   kuang bao   du    biao  zan   zhi   si    you   hao   qin   chen  li    teng  wei   long  chu   chan  rang  shu   hui   li    luo   zan   nuo   tang  yan   lei   nang  er    wu    yun   zan   yuan  xiong chong zhao  xiong xian  guang dui   ke    dui   mian  tu    chang er    dui   er    jin   tu    si    yan   yan   shi   shi   dang  qian  dou   fen   mao   xin   dou   bai   jing  li    kuang ru    wang  nei   quan  liang yu    ba    gong  liu   xi    none  lan   gong  tian  guan  xing  bing  qi    ju    dian  zi    none  yang  jian  shou  ji    yi    ji    chan  jiong mao   ran   nei   yuan  mao   gang  ran   ce    jiong ce    zai   gua   jiong mao   zhou  mao   gou   xu    mian  mi    rong  yin   xie   kan   jun   nong  yi    mi    shi   guan  meng  zhong zui   yuan  ming  kou   none  fu    xie   mi    bing  dong  tai   gang  feng  bing  hu    chong jue   hu    kuang ye    leng  pan   fu    min   dong  xian  lie   xia   jian  jing  shu   mei   shang qi    gu    zhun  song  jing  liang qing  diao  ling  dong  gan   jian  yin   cou   ai    li    cang  ming  zhun  cui   si    duo   jin   lin   lin   ning  xi    du    ji    fan   fan   fan   feng  ju    chu   none  feng  none  none  fu    feng  ping  feng  kai   huang kai   gan   deng  ping  qu    xiong kuai  tu    ao    chu   ji    dang  han   han   zao   dao   diao  dao   ren   ren   chuangfen   qie   yi    ji    kan   qian  cun   chu   wen   ji    dan   xing  hua   wan   jue   li    yue   lie   liu   ze    gang  chuangfu    chu   qu    ju    shan  min   ling  zhong pan   bie   jie   jie   bao   li    shan  bie   chan  jing  gua   gen   dao   chuangkui   ku    duo   er    zhi   shua  quan  cha   ci    ke    jie   gui   ci    gui   kai   duo   ji    ti    jing  lou   luo   ze    yuan  cuo   xue   ke    la    qian  cha   chuan gua   jian  cuo   li    ti    fei   pou   chan  qi    chuangzi    gang  wan   bo    ji    duo   qing  yan   zhuo  jian  ji    bo    yan   ju    huo   sheng jian  duo   duan  wu    gua   fu    sheng jian  ge    zha   kai   chuangjuan  chan  tuan  lu    li    fou   shan  piao  kou   jiao  gua   qiao  jue   hua   zha   zhuo  lian  ju    pi    liu   gui   jiao  gui   jian  jian  tang  huo   ji    jian  yi    jian  zhi   chan  cuan  mo    li    zhu   li    ya    quan  ban   gong  jia   wu    mai   lie   jing  keng  xie   zhi   dong  zhu   nu    jie   qu    shao  yi    zhu   mo    li    jing  lao   lao   juan  kou   yang  wa    xiao  mou   kuang jie   lie   he    shi   ke    jing  hao   bo    min   chi   lang  yong  yong  mian  ke    xun   juan  qing  lu    bu    meng  lai   le    kai   mian  dong  xu    xu    kan   wu    yi    xun   weng  sheng lao   mu    lu    piao  shi   ji    qin   qiang jiao  quan  xiang yi    qiao  fan   juan  tong  ju    dan   xie   mai   xun   xun   lu:   li    che   rang  quan  bao   shao  yun   jiu   bao   gou   wu    yun   none  none  gai   gai   bao   cong  none  xiong peng  ju    tao   ge    pu    an    pao   fu    gong  da    jiu   qiong bi    hua   bei   nao   chi   fang  jiu   yi    za    jiang kang  jiang kuang hu    xia   qu    fan   gui   qie   cang  kuang fei   hu    yu    gui   kui   hui   dan   kui   lian  lian  suan  du    jiu   qu    xi    pi    qu    yi    an    yan   bian  ni    qu    shi   xin   qian  nian  sa    zu    sheng wu    hui   ban   shi   xi    wan   hua   xie   wan   bei   zu    zhuo  xie   dan   mai   nan   dan   ji    bo    shuai bu    kuang bian  bu    zhan  ka    lu    you   lu    xi    gua   wo    xie   jie   jie   wei   ang   qiong zhi   mao   yin   we\")\n                .append(\"i   shao  ji    que   luan  shi   juan  xie   xu    jin   que   wu    ji    e     qing  xi    none  chang han   e     ting  li    zhe   an    li    ya    ya    yan   she   zhi   zha   pang  none  ke    ya    zhi   ce    pang  ti    li    she   hou   ting  zui   cuo   fei   yuan  ce    yuan  xiang yan   li    jue   sha   dian  chu   jiu   qin   ao    gui   yan   si    li    chang lan   li    yan   yan   yuan  si    si    lin   qiu   qu    qu    none  lei   du    xian  zhuan san   can   can   san   can   ai    dai   you   cha   ji    you   shuangfan   shou  guai  ba    fa    ruo   shi   shu   zhui  qu    shou  bian  xu    jia   pan   sou   ji    yu    sou   die   rui   cong  kou   gu    ju    ling  gua   tao   kou   zhi   jiao  zhao  ba    ding  ke    tai   chi   shi   you   qiu   po    ye    hao   si    tan   chi   le    diao  ji    none  hong  mie   yu    mang  chi   ge    xuan  yao   zi    he    ji    diao  cun   tong  ming  hou   li    tu    xiang zha   he    ye    lu:   a     ma    ou    xue   yi    jun   chou  lin   tun   yin   fei   bi    qin   qin   jie   pou   fou   ba    dun   fen   e     han   ting  hang  shun  qi    hu    zhi   yin   wu    wu    chao  na    chuo  xi    chui  dou   wen   hou   ou    wu    gao   ya    jun   lu:   e     ge    mei   dai   qi    cheng wu    gao   fu    jiao  hong  chi   sheng na    tun   m     yi    dai   ou    li    bei   yuan  guo   none  qiang wu    e     shi   quan  pen   wen   ni    mou   ling  ran   you   di    zhou  shi   zhou  zhan  ling  yi    qi    ping  zi    gua   ci    wei   xu    he    nao   xia   pei   yi    xiao  shen  hu    ming  da    qu    ju    gan   za    tuo   duo   pou   pao   bie   fu    bi    he    za    he    hai   jiu   yong  fu    da    zhou  wa    ka    gu    ka    zuo   bu    long  dong  ning  zha   si    xian  huo   qi    er    e     guang zha   xi    yi    lie   zi    mie   mi    zhi   yao   ji    zhou  ge    shuai zan   xiao  ke    hui   kua   huai  tao   xian  e     xuan  xiu   guo   yan   lao   yi    ai    pin   shen  tong  hong  xiong duo   wa    ha    zai   you   di    pai   xiang ai    gen   kuang ya    da    xiao  bi    hui   none  hua   none  kuai  duo   none  ji    nong  mou   yo    hao   yuan  long  pou   mang  ge    e     chi   shao  li    na    zu    he    ku    xiao  xian  lao   bei   zhe   zha   liang ba    mi    le    sui   fou   bu    han   heng  geng  shuo  ge    you   yan   gu    gu    bai   han   suo   chun  yi    ai    jia   tu    xian  guan  li    xi    tang  zuo   miu   che   wu    zao   ya    dou   qi    di    qin   ma    none  gong  dou   none  lao   liang suo   zao   huan  none  gou   ji    zuo   wo    feng  yin   hu    qi    shou  wei   shua  chang er    li    qiang an    jie   yo    nian  yu    tian  lai   sha   xi    tuo   hu    ai    zhou  nou   ken   zhuo  zhuo  shang di    heng  lin   a     xiao  xiang tun   wu    wen   cui   jie   hu    qi    qi    tao   dan   dan   wan   zi    bi    cui   chuo  he    ya    qi    zhe   fei   liang xian  pi    sha   la    ze    qing  gua   pa    zhe   se    zhuan nie   guo   luo   yan   di    quan  tan   bo    ding  lang  xiao  none  tang  chi   ti    an    jiu   dan   ka    yong  wei   nan   shan  yu    zhe   la    jie   hou   han   die   zhou  chai  kuai  re    yu    yin   zan   yao   wo    mian  hu    yun   chuan hui   huan  huan  xi    he    ji    kui   zhong wei   sha   xu    huang du    nie   xuan  liang yu    sang  chi   qiao  yan   dan   pen   shi   li    yo    zha   wei   miao  ying  pen   none  kui   xi    yu    jie   lou   ku    cao   huo   ti    yao   he    a     xiu   qiang se    yong  su    hong  xie   ai    suo   ma    cha   hai   ke    da    sang  chen  ru    sou   gong  ji    pang  wu    qian  shi   ge    zi    jie   luo   weng  wa    si    chi   hao   suo   jia   hai   suo   qin   nie   he    none  sai   ng    ge    na    dia   ai    none  tong  bi    ao    ao    lian  cui   zhe   mo    sou   sou   tan   di    qi    jiao  chong jiao  kai   tan   san   cao   jia   none  xiao  piao  lou   ga    gu    xiao  hu    hui   guo   ou    xian  ze    chang xu    po    de    ma    ma    hu    lei   du    ga    tang  ye    beng  ying  none  jiao  mi    xiao  hua   \")\n                .append(\"mai   ran   zuo   peng  lao   xiao  ji    zhu   chao  kui   zui   xiao  si    hao   fu    liao  qiao  xi    xu    chan  dan   hei   xun   wu    zun   pan   chi   kui   can   zan   cu    dan   yu    tun   cheng jiao  ye    xi    qi    hao   lian  xu    deng  hui   yin   pu    jue   qin   xun   nie   lu    si    yan   ying  da    zhan  o     zhou  jin   nong  hui   hui   qi    e     zao   yi    shi   jiao  yuan  ai    yong  xue   kuai  yu    pen   dao   ga    xin   dun   dang  none  sai   pi    pi    yin   zui   ning  di    han   ta    huo   ru    hao   xia   yan   duo   pi    chou  ji    jin   hao   ti    chang none  none  ca    ti    lu    hui   bao   you   nie   yin   hu    mo    huang zhe   li    liu   none  nang  xiao  mo    yan   li    lu    long  mo    dan   chen  pin   pi    xiang huo   mo    xi    duo   ku    yan   chan  ying  rang  dian  la    ta    xiao  jiao  chuo  huan  huo   zhuan nie   xiao  ca    li    chan  chai  li    yi    luo   nang  zan   su    xi    none  jian  za    zhu   lan   nie   nang  none  none  wei   hui   yin   qiu   si    nin   jian  hui   xin   yin   nan   tuan  tuan  dun   kang  yuan  jiong pian  yun   cong  hu    hui   yuan  e     guo   kun   cong  wei   tu    wei   lun   guo   jun   ri    ling  gu    guo   tai   guo   tu    you   guo   yin   hun   pu    yu    han   yuan  lun   quan  yu    qing  guo   chui  wei   yuan  quan  ku    pu    yuan  yuan  e     tu    tu    tu    tuan  lu:e  hui   yi    yuan  luan  luan  tu    ya    tu    ting  sheng yan   lu    none  ya    zai   wei   ge    yu    wu    gui   pi    yi    di    qian  qian  zhen  zhuo  dang  qia   none  none  kuang chang qi    nie   mo    ji    jia   zhi   zhi   ban   xun   tou   qin   fen   jun   keng  dun   fang  fen   ben   tan   kan   huai  zuo   keng  bi    xing  di    jing  ji    kuai  di    jing  jian  tan   li    ba    wu    fen   zhui  po    pan   tang  kun   qu    tan   zhi   tuo   gan   ping  dian  wa    ni    tai   pi    jiong yang  fo    ao    liu   qiu   mu    ke    gou   xue   ba    chi   che   ling  zhu   fu    hu    zhi   chui  la    long  long  lu    ao    none  pao   none  xing  tong  ji    ke    lu    ci    chi   lei   gai   yin   hou   dui   zhao  fu    guang yao   duo   duo   gui   cha   yang  yin   fa    gou   yuan  die   xie   ken   shang shou  e     none  dian  hong  ya    kua   da    none  dang  kai   none  nao   an    xing  xian  huan  bang  pei   ba    yi    yin   han   xu    chui  cen   geng  ai    peng  fang  que   yong  jun   jia   di    mai   lang  xuan  cheng shan  jin   zhe   lie   lie   pu    cheng none  bu    shi   xun   guo   jiong ye    nian  di    yu    bu    wu    juan  sui   pi    cheng wan   ju    lun   zheng kong  zhong dong  dai   tan   an    cai   shu   beng  kan   zhi   duo   yi    zhi   yi    pei   ji    zhun  qi    sao   ju    ni    ku    ke    tang  kun   ni    jian  dui   jin   gang  yu    e     peng  gu    tu    leng  none  ya    qian  none  an    chen  duo   nao   tu    cheng yin   hun   bi    lian  guo   die   zhuan hou   bao   bao   yu    di    mao   jie   ruan  e     geng  kan   zong  yu    huang e     yao   yan   bao   ji    mei   chang du    tuo   an    feng  zhong jie   zhen  heng  gang  chuan jian  none  lei   gang  huang leng  duan  wan   xuan  ji    ji    kuai  ying  ta    cheng yong  kai   su    su    shi   mi    ta    weng  cheng tu    tang  qiao  zhong li    peng  bang  sai   zang  dui   tian  wu    cheng xun   ge    zhen  ai    gong  yan   kan   tian  yuan  wen   xie   liu   none  lang  chang peng  beng  chen  lu    lu    ou    qian  mei   mo    zhuan shuangshu   lou   chi   man   biao  jing  ce    shu   di    zhang kan   yong  dian  chen  zhi   ji    guo   qiang jin   di    shang mu    cui   yan   ta    zeng  qi    qiang liang none  zhui  qiao  zeng  xu    shan  shan  ba    pu    kuai  dong  fan   que   mo    dun   dun   zun   zui   sheng duo   duo   tan   deng  mu    fen   huang tan   da    ye    chu   none  ao    qiang ji    qiao  ken   yi    pi    bi    dian  jiang ye    yong  xue   tan   lan   ju    huai  dang  rang  qian  xuan  lan   mi    he    kai   ya    dao   hao   ruan  none  lei   kuang lu    yan   tan   wei   huai  long  long  rui   li  \")\n                .append(\"  lin   rang  chan  xun   yan   lei   ba    none  shi   ren   none  zhuangzhuangsheng yi    mai   qiao  zhu   zhuanghu    hu    kun   yi    hu    xu    kun   shou  mang  zun   shou  yi    zhi   gu    chu   xiang feng  bei   none  bian  sui   qun   ling  fu    zuo   xia   xiong none  nao   xia   kui   xi    wai   yuan  mao   su    duo   duo   ye    qing  none  gou   gou   qi    meng  meng  yin   huo   chen  da    ze    tian  tai   fu    guai  yao   yang  hang  gao   shi   ben   tai   tou   yan   bi    yi    kua   jia   duo   none  kuang yun   jia   ba    en    lian  huan  di    yan   pao   juan  qi    nai   feng  xie   fen   dian  none  kui   zou   huan  qi    kai   she   ben   yi    jiang tao   zhuangben   xi    huang fei   diao  sui   beng  dian  ao    she   weng  pan   ao    wu    ao    jiang lian  duo   yun   jiang shi   fen   huo   bei   lian  che   nu:   nu    ding  nai   qian  jian  ta    jiu   nan   cha   hao   xian  fan   ji    shuo  ru    fei   wang  hong  zhuangfu    ma    dan   ren   fu    jing  yan   xie   wen   zhong pa    du    ji    keng  zhong yao   jin   yun   miao  pei   chi   yue   zhuangniu   yan   na    xin   fen   bi    yu    tuo   feng  yuan  fang  wu    yu    gui   du    ba    ni    zhou  zhou  zhao  da    nai   yuan  tou   xuan  zhi   e     mei   mo    qi    bi    shen  qie   e     he    xu    fa    zheng ni    ban   mu    fu    ling  zi    zi    shi   ran   shan  yang  qian  jie   gu    si    xing  wei   zi    ju    shan  pin   ren   yao   tong  jiang shu   ji    gai   shang kuo   juan  jiao  gou   lao   jian  jian  yi    nian  zhi   ji    ji    xian  heng  guang jun   kua   yan   ming  lie   pei   yan   you   yan   cha   xian  yin   chi   gui   quan  zi    song  wei   hong  wa    lou   ya    rao   jiao  luan  ping  xian  shao  li    cheng xie   mang  none  suo   mu    wei   ke    lai   chuo  ding  niang keng  nan   yu    na    pei   sui   juan  shen  zhi   han   di    zhuange     pin   tui   xian  mian  wu    yan   wu    xi    yan   yu    si    yu    wa    li    xian  ju    qu    chui  qi    xian  zhui  dong  chang lu    ai    e     e     lou   mian  cong  pou   ju    po    cai   ling  wan   biao  xiao  shu   qi    hui   fu    wo    rui   tan   fei   none  jie   tian  ni    quan  jing  hun   jing  qian  dian  xing  hu    wan   lai   bi    yin   chou  chuo  fu    jing  lun   yan   lan   kun   yin   ya    none  li    dian  xian  none  hua   ying  chan  shen  ting  yang  yao   wu    nan   chuo  jia   tou   xu    yu    wei   ti    rou   mei   dan   ruan  qin   none  wu    qian  chun  mao   fu    jie   duan  xi    zhong mei   huang mian  an    ying  xuan  none  wei   mei   yuan  zhen  qiu   ti    xie   tuo   lian  mao   ran   si    pian  wei   wa    jiu   hu    ao    none  bao   xu    tou   gui   zou   yao   pi    xi    yuan  ying  rong  ru    chi   liu   mei   pan   ao    ma    gou   kui   qin   jia   sao   zhen  yuan  cha   yong  ming  ying  ji    su    niao  xian  tao   pang  lang  niao  bao   ai    pi    pin   yi    piao  yu    lei   xuan  man   yi    zhang kang  yong  ni    li    di    gui   yan   jin   zhuan chang ce    han   nen   lao   mo    zhe   hu    hu    ao    nen   qiang none  bi    gu    wu    qiao  tuo   zhan  mao   xian  xian  mo    liao  lian  hua   gui   deng  zhi   xu    none  hua   xi    hui   rao   xi    yan   chan  jiao  mei   fan   fan   xian  yi    wei   chan  fan   shi   bi    shan  sui   qiang lian  huan  none  niao  dong  yi    can   ai    niang ning  ma    tiao  chou  jin   ci    yu    pin   none  xu    nai   yan   tai   ying  can   niao  none  ying  mian  none  ma    shen  xing  ni    du    liu   yuan  lan   yan   shuangling  jiao  niang lan   xian  ying  shuangshuai quan  mi    li    luan  yan   zhu   lan   zi    jie   jue   jue   kong  yun   zi    zi    cun   sun   fu    bei   zi    xiao  xin   meng  si    tai   bao   ji    gu    nu    xue   none  chan  hai   luan  sun   nao   mie   cong  jian  shu   chan  ya    zi    ni    fu    zi    li    xue   bo    ru    nai   nie   nie   ying  luan  mian  ning  rong  ta    gui   zhai  qiong yu    shou  an    tu    song  wan   rou   yao   hong  yi    jing  zhun  mi    guai  dang  hong  zong  guan  zhou  ding  wa\")\n                .append(\"n   yi    bao   shi   shi   chong shen  ke    xuan  shi   you   huan  yi    tiao  shi   xian  gong  cheng qun   gong  xiao  zai   zha   bao   hai   yan   xiao  jia   shen  chen  rong  huang mi    kou   kuan  bin   su    cai   zan   ji    yuan  ji    yin   mi    kou   qing  he    zhen  jian  fu    ning  bing  huan  mei   qin   han   yu    shi   ning  jin   ning  zhi   yu    bao   kuan  ning  qin   mo    cha   ju    gua   qin   hu    wu    liao  shi   ning  zhai  shen  wei   xie   kuan  hui   liao  jun   huan  yi    yi    bao   qin   chong bao   feng  cun   dui   si    xun   dao   lu:   dui   shou  po    feng  zhuan fu    she   ke    jiang jiang zhuan wei   zun   xun   shu   dui   dao   xiao  ji    shao  er    er    er    ga    jian  shu   chen  shang shang yuan  ga    chang liao  xian  xian  none  wang  wang  you   liao  liao  yao   mang  wang  wang  wang  ga    yao   duo   kui   zhong jiu   gan   gu    gan   gan   gan   gan   shi   yin   chi   kao   ni    jin   wei   niao  ju    pi    ceng  xi    bi    ju    jie   tian  qu    ti    jie   wu    diao  shi   shi   ping  ji    xie   chen  xi    ni    zhan  xi    none  man   e     lou   ping  ti    fei   shu   xie   tu    lu:   lu:   xi    ceng  lu:   ju    xie   ju    jue   liao  jue   shu   xi    che   tun   ni    shan  wa    xian  li    e     none  none  long  yi    qi    ren   wu    han   shen  yu    chu   sui   qi    none  yue   ban   yao   ang   ya    wu    jie   e     ji    qian  fen   wan   qi    cen   qian  qi    cha   jie   qu    gang  xian  ao    lan   dao   ba    zhai  zuo   yang  ju    gang  ke    gou   xue   bo    li    tiao  qu    yan   fu    xiu   jia   ling  tuo   pei   you   dai   kuang yue   qu    hu    po    min   an    tiao  ling  chi   none  dong  none  kui   xiu   mao   tong  xue   yi    none  he    ke    luo   e     fu    xun   die   lu    lang  er    gai   quan  tong  yi    mu    shi   an    wei   hu    zhi   mi    li    ji    tong  kui   you   none  xia   li    yao   jiao  zheng luan  jiao  e     e     yu    ye    bu    qiao  qun   feng  feng  nao   li    you   xian  hong  dao   shen  cheng tu    geng  jun   hao   xia   yin   wu    lang  kan   lao   lai   xian  que   kong  chong chong ta    none  hua   ju    lai   qi    min   kun   kun   zu    gu    cui   ya    ya    gang  lun   lun   leng  jue   duo   cheng guo   yin   dong  han   zheng wei   yao   pi    yan   song  jie   beng  zu    jue   dong  zhan  gu    yin   zi    ze    huang yu    wei   yang  feng  qiu   dun   ti    yi    zhi   shi   zai   yao   e     zhu   kan   lu:   yan   mei   gan   ji    ji    huan  ting  sheng mei   qian  wu    yu    zong  lan   jie   yan   yan   wei   zong  cha   sui   rong  ke    qin   yu    qi    lou   tu    dui   xi    weng  cang  dang  rong  jie   ai    liu   wu    song  qiao  zi    wei   beng  dian  cuo   qian  yong  nie   cuo   ji    none  none  song  zong  jiang liao  none  chan  di    cen   ding  tu    lou   zhang zhan  zhan  ao    cao   qu    qiang zui   zui   dao   dao   xi    yu    bo    long  xiang ceng  bo    qin   jiao  yan   lao   zhan  lin   liao  liao  jin   deng  duo   zun   jiao  gui   yao   qiao  yao   jue   zhan  yi    xue   nao   ye    ye    yi    e     xian  ji    xie   ke    sui   di    ao    zui   none  yi    rong  dao   ling  za    yu    yue   yin   none  jie   li    sui   long  long  dian  ying  xi    ju    chan  ying  kui   yan   wei   nao   quan  chao  cuan  luan  dian  dian  nie   yan   yan   yan   nao   yan   chuan gui   chuan zhou  huang jing  xun   chao  chao  lie   gong  zuo   qiao  ju    gong  none  wu    none  none  cha   qiu   qiu   ji    yi    si    ba    zhi   zhao  xiang yi    jin   xun   juan  none  xun   jin   fu    za    bi    shi   bu    ding  shuai fan   nie   shi   fen   pa    zhi   xi    hu    dan   wei   zhang tang  dai   ma    pei   pa    tie   fu    lian  zhi   zhou  bo    zhi   di    mo    yi    yi    ping  qia   juan  ru    shuai dai   zhen  shui  qiao  zhen  shi   qun   xi    bang  dai   gui   chou  ping  zhang sha   wan   dai   wei   chang sha   qi    ze    guo   mao   du    hou   zhen  xu    mi    wei   wo    fu    yi    bang  ping  none  gong  pan   huang dao   mi    jia   teng  hui   zhong sen   \")\n                .append(\"man   mu    biao  guo   ze    mu    bang  zhang jiong chan  fu    zhi   hu    fan   chuangbi    bi    none  mi    qiao  dan   fen   meng  bang  chou  mie   chu   jie   xian  lan   gan   ping  nian  jian  bing  bing  xing  gan   yao   huan  you   you   ji    guang pi    ting  ze    guang zhuangmo    qing  bi    qin   dun   chuanggui   ya    bai   jie   xu    lu    wu    none  ku    ying  di    pao   dian  ya    miao  geng  ci    fu    tong  pang  fei   xiang yi    zhi   tiao  zhi   xiu   du    zuo   xiao  tu    gui   ku    pang  ting  you   bu    bing  cheng lai   bi    ji    an    shu   kang  yong  tuo   song  shu   qing  yu    yu    miao  sou   ce    xiang fei   jiu   he    hui   liu   sha   lian  lang  sou   jian  pou   qing  jiu   jiu   qin   ao    kuo   lou   yin   liao  dai   lu    yi    chu   chan  tu    si    xin   miao  chang wu    fei   guang none  guai  bi    qiang xie   lin   lin   liao  lu    none  ying  xian  ting  yong  li    ting  yin   xun   yan   ting  di    po    jian  hui   nai   hui   gong  nian  kai   bian  yi    qi    nong  fen   ju    yan   yi    zang  bi    yi    yi    er    san   shi   er    shi   shi   gong  diao  yin   hu    fu    hong  wu    tui   chi   qiang ba    shen  di    zhang jue   tao   fu    di    mi    xian  hu    chao  nu    jing  zhen  yi    mi    quan  wan   shao  ruo   xuan  jing  diao  zhang jiang qiang beng  dan   qiang bi    bi    she   dan   jian  gou   none  fa    bi    kou   none  bie   xiao  dan   kuang qiang hong  mi    kuo   wan   jue   ji    ji    gui   dang  lu    lu    tuan  hui   zhi   hui   hui   yi    yi    yi    yi    huo   huo   shan  xing  zhang tong  yan   yan   yu    chi   cai   biao  diao  bin   peng  yong  piao  zhang ying  chi   chi   zhuo  tuo   ji    pang  zhong yi    wang  che   bi    di    ling  fu    wang  zheng cu    wang  jing  dai   xi    xun   hen   yang  huai  lu:   hou   wang  cheng zhi   xu    jing  tu    cong  none  lai   cong  de    pai   xi    none  qi    chang zhi   cong  zhou  lai   yu    xie   jie   jian  chi   jia   bian  huang fu    xun   wei   pang  yao   wei   xi    zheng piao  chi   de    zheng zhi   bie   de    chong che   jiao  wei   jiao  hui   mei   long  xiang bao   qu    xin   xin   bi    yi    le    ren   dao   ding  gai   ji    ren   ren   chan  tan   te    te    gan   qi    dai   cun   zhi   wang  mang  xi    fan   ying  tian  min   min   zhong chong wu    ji    wu    xi    ye    you   wan   zong  zhong kuai  yu    bian  zhi   chi   cui   chen  tai   tun   qian  nian  hun   xiong niu   wang  xian  xin   kang  hu    kai   fen   huai  tai   song  wu    ou    chang chuangju    yi    bao   chao  min   pi    zuo   zen   yang  kou   ban   nu    nao   zheng pa    bu    tie   hu    hu    ju    da    lian  si    zhou  di    dai   yi    tu    you   fu    ji    peng  xing  yuan  ni    guai  fu    xi    bi    you   qie   xuan  zong  bing  huang xu    chu   pi    xi    xi    tan   none  zong  dui   none  none  yi    chi   nen   xun   shi   xi    lao   heng  kuang mou   zhi   xie   lian  tiao  huang die   hao   kong  gui   heng  xi    xiao  shu   sai   hu    qiu   yang  hui   hui   chi   jia   yi    xiong guai  lin   hui   zi    xu    chi   xiang nu:   hen   en    ke    dong  tian  gong  quan  xi    qia   yue   peng  ken   de    hui   e     none  tong  yan   kai   ce    nao   yun   mang  yong  yong  juan  mang  kun   qiao  yue   yu    yu    jie   xi    zhe   lin   ti    han   hao   qie   ti    bu    yi    qian  hui   xi    bei   man   yi    heng  song  quan  cheng kui   wu    wu    you   li    liang huan  cong  yi    yue   li    nin   nao   e     que   xuan  qian  wu    min   cong  fei   bei   de    cui   chang men   li    ji    guan  guan  xing  dao   qi    kong  tian  lun   xi    kan   kun   ni    qing  chou  dun   guo   chan  jing  wan   yuan  jin   ji    lin   yu    huo   he    quan  yan   ti    ti    nie   wang  chuo  hu    hun   xi    chang xin   wei   hui   e     rui   zong  jian  yong  dian  ju    can   cheng de    bei   qie   can   dan   guan  duo   nao   yun   xiang zhui  die   huang chun  qiong re    xing  ce    bian  hun   zong  ti    qiao  chou  bei   xuan  wei   ge    qian  wei   yu    yu    bi    xuan  huan\")\n                .append(\"  min   bi    yi    mian  yong  kai   dang  yin   e     chen  mou   qia   ke    yu    ai    qie   yan   nuo   gan   yun   zong  sai   leng  fen   none  kui   kui   que   gong  yun   su    su    qi    yao   song  huang none  gu    ju    chuangta    xie   kai   zheng yong  cao   sun   shen  bo    kai   yuan  xie   hun   yong  yang  li    sao   tao   yin   ci    xu    qian  tai   huang yun   shen  ming  none  she   cong  piao  mo    mu    guo   chi   can   can   can   cui   min   ni    zhang tong  ao    shuangman   guan  que   zao   jiu   hui   kai   lian  ou    song  jin   yin   lu:   shang wei   tuan  man   qian  zhe   yong  qing  kang  di    zhi   lu:   juan  qi    qi    yu    ping  liao  zong  you   chuangzhi   tong  cheng qi    qu    peng  bei   bie   chun  jiao  zeng  chi   lian  ping  kui   hui   qiao  cheng yin   yin   xi    xi    dan   tan   duo   dui   dui   su    jue   ce    xiao  fan   fen   lao   lao   chong han   qi    xian  min   jing  liao  wu    can   jue   chou  xian  tan   sheng pi    yi    chu   xian  nao   dan   tan   jing  song  han   jiao  wei   huan  dong  qin   qin   qu    cao   ken   xie   ying  ao    mao   yi    lin   se    jun   huai  men   lan   ai    lin   yan   gua   xia   chi   yu    yin   dai   meng  ai    meng  dui   qi    mo    lan   men   chou  zhi   nuo   nuo   yan   yang  bo    zhi   xing  kuang you   fu    liu   mie   cheng none  chan  meng  lan   huai  xuan  rang  chan  ji    ju    huan  she   yi    lian  nan   mi    tang  jue   gang  gang  zhuangge    yue   wu    jian  xu    shu   rong  xi    cheng wo    jie   ge    jian  qiang huo   qiang zhan  dong  qi    jia   die   cai   jia   ji    shi   kan   ji    kui   gai   deng  zhan  chuangge    jian  jie   yu    jian  yan   lu    xi    zhan  xi    xi    chuo  dai   qu    hu    hu    hu    e     shi   li    mao   hu    li    fang  suo   bian  dian  jiong shang yi    yi    shan  hu    fei   yan   shou  shou  cai   zha   qiu   le    pu    ba    da    reng  fu    none  zai   tuo   zhang diao  kang  yu    ku    han   shen  cha   chi   gu    kou   wu    tuo   qian  zhi   cha   kuo   men   sao   yang  niu   ban   che   rao   xi    qian  ban   jia   yu    fu    ao    xi    pi    zhi   zi    e     dun   zhao  cheng ji    yan   kuang bian  chao  ju    wen   hu    yue   jue   ba    qin   zhen  zheng yun   wan   na    yi    shu   zhua  pou   tou   dou   kang  zhe   pou   fu    pao   ba    ao    ze    tuan  kou   lun   qiang none  hu    bao   bing  zhi   peng  tan   pu    pi    tai   yao   zhen  zha   yang  bao   he    ni    yi    di    chi   pi    za    mo    mo    chen  ya    chou  qu    min   chu   jia   fu    zha   zhu   dan   chai  mu    nian  la    fu    pao   ban   pai   lin   na    guai  qian  ju    tuo   ba    tuo   tuo   ao    ju    zhuo  pan   zhao  bai   bai   di    ni    ju    kuo   long  jian  qia   yong  lan   ning  bo    ze    qian  hen   kuo   shi   jie   zheng nin   gong  gong  quan  shuan tun   zan   kao   chi   xie   ce    hui   pin   zhuai shi   na    bo    chi   gua   zhi   kuo   duo   duo   zhi   qie   an    nong  zhen  ge    jiao  kua   dong  ru    tiao  lie   zha   lu:   die   wa    jue   none  ju    zhi   luan  ya    wo    ta    xie   nao   dang  jiao  zheng ji    hui   xian  none  ai    tuo   nuo   cuo   bo    geng  ti    zhen  cheng suo   suo   keng  mei   long  ju    peng  jian  yi    ting  shan  nuo   wan   xie   cha   feng  jiao  wu    jun   jiu   tong  kun   huo   tu    zhuo  pou   lu:   ba    han   shao  nie   juan  she   shu   ye    jue   bu    huan  bu    jun   yi    zhai  lu:   sou   tuo   lao   sun   bang  jian  huan  dao   none  wan   qin   peng  she   lie   min   men   fu    bai   ju    dao   wo    ai    juan  yue   zong  chen  chui  jie   tu    ben   na    nian  nuo   zu    wo    xi    xian  cheng dian  sao   lun   qing  gang  duo   shou  diao  pou   di    zhang gun   ji    tao   qia   qi    pai   shu   qian  ling  ye    ya    jue   zheng liang gua   yi    huo   shan  ding  lu:e  cai   tan   che   bing  jie   ti    kong  tui   yan   cuo   zou   ju    tian  qian  ken   bai   shou  jie   lu    guai  none  none  zhi   dan   none  chan  sao   guan  peng  yuan  nuo   jian  zheng jiu   jian  yu    ya\")\n                .append(\"n   kui   nan   hong  rou   pi    wei   sai   zou   xuan  miao  ti    nie   cha   shi   zong  zhen  yi    shun  heng  bian  yang  huan  yan   zan   an    xu    ya    wo    ke    chuai ji    ti    la    la    cheng kai   jiu   jiu   tu    jie   hui   geng  chong shuo  she   xie   yuan  qian  ye    cha   zha   bei   yao   none  none  lan   wen   qin   chan  ge    lou   zong  geng  jiao  gou   qin   yong  que   chou  chuai zhan  sun   sun   bo    chu   rong  bang  cuo   sao   ke    yao   dao   zhi   nu    xie   jian  sou   qiu   gao   xian  shuo  sang  jin   mie   e     chui  nuo   shan  ta    jie   tang  pan   ban   da    li    tao   hu    zhi   wa    xia   qian  wen   qiang chen  zhen  e     xie   nuo   quan  cha   zha   ge    wu    en    she   gong  she   shu   bai   yao   bin   sou   tan   sha   chan  suo   liao  chong chuangguo   bing  feng  shuai di    qi    none  zhai  lian  cheng chi   guan  lu    luo   lou   zong  gai   hu    zha   chuangtang  hua   cui   nai   mo    jiang gui   ying  zhi   ao    zhi   chi   man   shan  kou   shu   suo   tuan  zhao  mo    mo    zhe   chan  keng  biao  jiang yin   gou   qian  liao  ji    ying  jue   pie   pie   lao   dun   xian  ruan  kui   zan   yi    xian  cheng cheng sa    nao   heng  si    han   huang da    zun   nian  lin   zheng hui   zhuangjiao  ji    cao   dan   dan   che   bo    che   jue   xiao  liao  ben   fu    qiao  bo    cuo   zhuo  zhuan tuo   pu    qin   dun   nian  none  xie   lu    jiao  cuan  ta    han   qiao  zhua  jian  gan   yong  lei   kuo   lu    shan  zhuo  ze    pu    chuo  ji    dang  se    cao   qing  jing  huan  jie   qin   kuai  dan   xie   ge    pi    bo    ao    ju    ye    none  none  sou   mi    ji    tai   zhuo  dao   xing  lan   ca    ju    ye    ru    ye    ye    ni    huo   ji    bin   ning  ge    zhi   jie   kuo   mo    jian  xie   lie   tan   bai   sou   lu    lu:e  rao   zhi   pan   yang  lei   sa    shu   zan   nian  xian  jun   huo   lu:e  la    han   ying  lu    long  qian  qian  zan   qian  lan   san   ying  mei   rang  chan  none  cuan  xie   she   luo   jun   mi    li    zan   luan  tan   zuan  li    dian  wa    dang  jiao  jue   lan   li    nang  zhi   gui   gui   qi    xin   po    po    shou  kao   you   gai   gai   gong  gan   ban   fang  zheng bo    dian  kou   min   wu    gu    ge    ce    xiao  mi    chu   ge    di    xu    jiao  min   chen  jiu   shen  duo   yu    chi   ao    bai   xu    jiao  duo   lian  nie   bi    chang dian  duo   yi    gan   san   ke    yan   dun   qi    dou   xiao  duo   jiao  jing  yang  xia   hun   shu   ai    qiao  ai    zheng di    zhen  fu    shu   liao  qu    xiong xi    jiao  none  qiao  zhuo  yi    lian  bi    li    xue   xiao  wen   xue   qi    qi    zhai  bin   jue   zhai  lang  fei   ban   ban   lan   yu    lan   wei   dou   sheng liao  jia   hu    xie   jia   yu    zhen  jiao  wo    tiao  dou   jin   chi   yin   fu    qiang zhan  qu    zhuo  zhan  duan  zhuo  si    xin   zhuo  zhuo  qin   lin   zhuo  chu   duan  zhu   fang  xie   hang  wu    shi   pei   you   none  pang  qi    zhan  mao   lu:   pei   pi    liu   fu    fang  xuan  jing  jing  ni    zu    zhao  yi    liu   shao  jian  none  yi    qi    zhi   fan   piao  fan   zhan  guai  sui   yu    wu    ji    ji    ji    huo   ri    dan   jiu   zhi   zao   xie   tiao  xun   xu    ga    la    gan   han   tai   di    xu    chan  shi   kuang yang  shi   wang  min   min   tun   chun  wu    yun   bei   ang   ze    ban   jie   kun   sheng hu    fang  hao   gui   chang xuan  ming  hun   fen   qin   hu    yi    xi    xin   yan   ze    fang  tan   shen  ju    yang  zan   bing  xing  ying  xuan  pei   zhen  ling  chun  hao   mei   zuo   mo    bian  xu    hun   zhao  zong  shi   shi   yu    fei   die   mao   ni    chang wen   dong  ai    bing  ang   zhou  long  xian  kuang tiao  chao  shi   huang huang xuan  kui   xu    jiao  jin   zhi   jin   shang tong  hong  yan   gai   xiang shai  xiao  ye    yun   hui   han   han   jun   wan   xian  kun   zhou  xi    sheng sheng bu    zhe   zhe   wu    han   hui   hao   chen  wan   tian  zhuo  zui   zhou  pu    jing  xi    shan  yi    xi    qing  qi    jing  gui   zhen  yi    zhi   an    wan   lin   \")\n                .append(\"liang chang wang  xiao  zan   none  xuan  geng  yi    xia   yun   hui   fu    min   kui   he    ying  du    wei   shu   qing  mao   nan   jian  nuan  an    yang  chun  yao   suo   pu    ming  jiao  kai   gao   weng  chang qi    hao   yan   li    ai    ji    gui   men   zan   xie   hao   mu    mo    cong  ni    zhang hui   bao   han   xuan  chuan liao  xian  dan   jing  pie   lin   tun   xi    yi    ji    kuang dai   ye    ye    li    tan   tong  xiao  fei   qin   zhao  hao   yi    xiang xing  sen   jiao  bao   jing  none  ai    ye    ru    shu   meng  xun   yao   pu    li    chen  kuang die   none  yan   huo   lu    xi    rong  long  nang  luo   luan  shai  tang  yan   chu   yue   yue   qu    ye    geng  zhuai hu    he    shu   cao   cao   sheng man   ceng  ceng  ti    zui   can   xu    hui   yin   qie   fen   pi    yue   you   ruan  peng  ban   fu    ling  fei   qu    none  nu:   tiao  shuo  zhen  lang  lang  juan  ming  huang wang  tun   chao  ji    qi    ying  zong  wang  tong  lang  none  meng  long  mu    deng  wei   mo    ben   zha   zhu   shu   none  zhu   ren   ba    po    duo   duo   dao   li    qiu   ji    jiu   bi    xiu   ting  ci    sha   none  za    quan  qian  yu    gan   wu    cha   shan  xun   fan   wu    zi    li    xing  cai   cun   ren   shao  zhe   di    zhang mang  chi   yi    gu    gong  du    yi    qi    shu   gang  tiao  none  none  none  lai   shan  mang  yang  ma    miao  si    yuan  hang  fei   bei   jie   dong  gao   yao   xian  chu   chun  pa    shu   hua   xin   chou  zhu   chou  song  ban   song  ji    yue   yun   gou   ji    mao   pi    bi    wang  ang   fang  fen   yi    fu    nan   xi    hu    ya    dou   xun   zhen  yao   lin   rui   e     mei   zhao  guo   zhi   zong  yun   none  dou   shu   zao   none  li    lu    jian  cheng song  qiang feng  nan   xiao  xian  ku    ping  tai   xi    zhi   guai  xiao  jia   jia   gou   bao   mo    yi    ye    sang  shi   nie   bi    tuo   yi    ling  bing  ni    la    he    ban   fan   zhong dai   ci    yang  fu    bo    mou   gan   qi    ran   rou   mao   zhao  song  zhe   xia   you   shen  ju    tuo   zuo   nan   ning  yong  di    zhi   zha   cha   dan   gu    none  jiu   ao    fu    jian  bo    duo   ke    nai   zhu   bi    liu   chai  zha   si    zhu   pei   shi   guai  cha   yao   cheng jiu   shi   zhi   liu   mei   none  rong  zha   none  biao  zhan  zhi   long  dong  lu    none  li    lan   yong  shu   xun   shuan qi    zhen  qi    li    chi   xiang zhen  li    su    gua   kan   bing  ren   xiao  bo    ren   bing  zi    chou  yi    ci    xu    zhu   jian  zui   er    er    yu    fa    gong  kao   lao   zhan  li    none  yang  he    gen   zhi   chi   ge    zai   luan  fa    jie   heng  gui   tao   guang wei   kuang ru    an    an    juan  yi    zhuo  ku    zhi   qiong tong  sang  sang  huan  jie   jiu   xue   duo   zhui  yu    zan   none  ying  none  none  zhan  ya    rao   zhen  dang  qi    qiao  hua   gui   jiang zhuangxun   suo   suo   zhen  bei   ting  kuo   jing  bo    ben   fu    rui   tong  jue   xi    lang  liu   feng  qi    wen   jun   gan   cu    liang qiu   ting  you   mei   bang  long  peng  zhuangdi    xuan  tu    zao   ao    gu    bi    di    han   zi    zhi   ren   bei   geng  jian  huan  wan   nuo   jia   tiao  ji    xiao  lu:   kuan  shao  cen   fen   song  meng  wu    li    li    dou   cen   ying  suo   ju    ti    xie   kun   zhuo  shu   chan  fan   wei   jing  li    bing  none  none  tao   zhi   lai   lian  jian  zhuo  ling  li    qi    bing  lun   cong  qian  mian  qi    qi    cai   gun   chan  de    fei   pai   bang  pou   hun   zong  cheng zao   ji    li    peng  yu    yu    gu    hun   dong  tang  gang  wang  di    xi    fan   cheng zhan  qi    yuan  yan   yu    quan  yi    sen   ren   chui  leng  qi    zhuo  fu    ke    lai   zou   zou   zhao  guan  fen   fen   chen  qiong nie   wan   guo   lu    hao   jie   yi    chou  ju    ju    cheng zuo   liang qiang zhi   zhui  ya    ju    bei   jiao  zhuo  zi    bin   peng  ding  chu   shan  none  none  jian  gui   xi    du    qian  none  kui   none  luo   zhi   none  none  none  none  peng  shan  none  tuo   sen   duo   ye    fu    wei   wei   duan  jia   zong\")\n                .append(\"  jian  yi    shen  xi    yan   yan   chuan zhan  chun  yu    he    zha   wo    bian  bi    yao   huo   xu    ruo   yang  la    yan   ben   hun   kui   jie   kui   si    feng  xie   tuo   ji    jian  mu    mao   chu   hu    hu    lian  leng  ting  nan   yu    you   mei   song  xuan  xuan  ying  zhen  pian  die   ji    jie   ye    chu   shun  yu    cou   wei   mei   di    ji    jie   kai   qiu   ying  rou   heng  lou   le    none  gui   pin   none  gai   tan   lan   yun   yu    chen  lu:   ju    none  none  none  xie   jia   yi    zhan  fu    nuo   mi    lang  rong  gu    jian  ju    ta    yao   zhen  bang  sha   yuan  zi    ming  su    jia   yao   jie   huang gan   fei   zha   qian  ma    sun   yuan  xie   rong  shi   zhi   cui   yun   ting  liu   rong  tang  que   zhai  si    sheng ta    ke    xi    gu    qi    kao   gao   sun   pan   tao   ge    xun   dian  nou   ji    shuo  gou   chui  qiang cha   qian  huai  mei   xu    gang  gao   zhuo  tuo   qiao  yang  dian  jia   jian  zui   none  long  bin   zhu   none  xi    qi    lian  hui   yong  qian  guo   gai   gai   tuan  hua   qi    sen   cui   beng  you   hu    jiang hu    huan  kui   yi    yi    gao   kang  gui   gui   cao   man   jin   di    zhuangle    lang  chen  cong  li    xiu   qing  shuangfan   tong  guan  ji    suo   lei   lu    liang mi    lou   chao  su    ke    chu   tang  biao  lu    jiu   shu   zha   shu   zhang men   mo    niao  yang  tiao  peng  zhu   sha   xi    quan  heng  jian  cong  none  none  qiang none  ying  er    xin   zhi   qiao  zui   cong  pu    shu   hua   kui   zhen  zun   yue   zhan  xi    xun   dian  fa    gan   mo    wu    qiao  rao   lin   liu   qiao  xian  run   fan   zhan  tuo   lao   yun   shun  tui   cheng tang  meng  ju    cheng su    jue   jue   tan   hui   ji    nuo   xiang tuo   ning  rui   zhu   tong  zeng  fen   qiong ran   heng  cen   gu    liu   lao   gao   chu   none  none  none  none  ji    dou   none  lu    none  none  yuan  ta    shu   jiang tan   lin   nong  yin   xi    sui   shan  zui   xuan  cheng gan   ju    zui   yi    qin   pu    yan   lei   feng  hui   dang  ji    sui   bo    bi    ding  chu   zhua  gui   ji    jia   jia   qing  zhe   jian  qiang dao   yi    biao  song  she   lin   li    cha   meng  yin   tao   tai   mian  qi    none  bin   huo   ji    qian  mi    ning  yi    gao   jian  yin   er    qing  yan   qi    mi    zhao  gui   chun  ji    kui   po    deng  chu   none  mian  you   zhi   guang qian  lei   lei   sa    lu    none  cuan  lu:   mie   hui   ou    lu:   zhi   gao   du    yuan  li    fei   zhu   sou   lian  none  chu   none  zhu   lu    yan   li    zhu   chen  jie   e     su    huai  nie   yu    long  lai   none  xian  none  ju    xiao  ling  ying  jian  yin   you   ying  xiang nong  bo    chan  lan   ju    shuangshe   wei   cong  quan  qu    none  none  yu    luo   li    zan   luan  dang  jue   none  lan   lan   zhu   lei   li    ba    nang  yu    ling  none  qian  ci    huan  xin   yu    yu    qian  ou    xu    chao  chu   qi    kai   yi    jue   xi    xu    xia   yu    kuai  lang  kuan  shuo  xi    e     yi    qi    hu    chi   qin   kuan  kan   kuan  kan   chuan sha   none  yin   xin   xie   yu    qian  xiao  yi    ge    wu    tan   jin   ou    hu    ti    huan  xu    pen   xi    xiao  hu    she   none  lian  chu   yi    kan   yu    chuo  huan  zhi   zheng ci    bu    wu    qi    bu    bu    wai   ju    qian  chi   se    chi   se    zhong sui   sui   li    cuo   yu    li    gui   dai   dai   si    jian  zhe   mo    mo    yao   mo    cu    yang  tian  sheng dai   shang xu    xun   shu   can   jue   piao  qia   qiu   su    qing  yun   lian  yi    fou   zhi   ye    can   hun   dan   ji    ye    none  yun   wen   chou  bin   ti    jin   shang yin   diao  cu    hui   cuan  yi    dan   du    jiang lian  bin   du    jian  jian  shu   ou    duan  zhu   yin   qing  yi    sha   ke    ke    yao   xun   dian  hui   hui   gu    que   ji    yi    ou    hui   duan  yi    xiao  wu    guan  mu    mei   mei   ai    zuo   du    yu    bi    bi    bi    pi    pi    bi    chan  mao   none  none  pi    none  jia   zhan  sai   mu    tuo   xun   er    rong  xian  ju    mu    hao   qiu   dou   none  ta\")\n                .append(\"n   pei   ju    duo   cui   bi    san   none  mao   sui   shu   yu    tuo   he    jian  ta    san   lu:   mu    li    tong  rong  chang pu    lu    zhan  sao   zhan  meng  lu    qu    die   shi   di    min   jue   mang  qi    pie   nai   qi    dao   xian  chuan fen   ri    nei   none  fu    shen  dong  qing  qi    yin   xi    hai   yang  an    ya    ke    qing  ya    dong  dan   lu:   qing  yang  yun   yun   shui  shui  zheng bing  yong  dang  shui  le    ni    tun   fan   gui   ting  zhi   qiu   bin   ze    mian  cuan  hui   diao  han   cha   zhuo  chuan wan   fan   dai   xi    tuo   mang  qiu   qi    shan  pai   han   qian  wu    wu    xun   si    ru    gong  jiang chi   wu    none  none  tang  zhi   chi   qian  mi    gu    wang  qing  jing  rui   jun   hong  tai   quan  ji    bian  bian  gan   wen   zhong fang  xiong jue   hu    none  qi    fen   xu    xu    qin   yi    wo    yun   yuan  hang  yan   shen  chen  dan   you   dun   hu    huo   qi    mu    rou   mei   ta    mian  wu    chong tian  bi    sha   zhi   pei   pan   zhui  za    gou   liu   mei   ze    feng  ou    li    lun   cang  feng  wei   hu    mo    mei   shu   ju    zan   tuo   tuo   duo   he    li    mi    yi    fu    fei   you   tian  zhi   zhao  gu    zhan  yan   si    kuang jiong ju    xie   qiu   yi    jia   zhong quan  bo    hui   mi    ben   zhuo  chu   le    you   gu    hong  gan   fa    mao   si    hu    ping  ci    fan   zhi   su    ning  cheng ling  pao   bo    qi    si    ni    ju    yue   zhu   sheng lei   xuan  xue   fu    pan   min   tai   yang  ji    yong  guan  beng  xue   long  lu    dan   luo   xie   po    ze    jing  yin   zhou  jie   yi    hui   hui   zui   cheng yin   wei   hou   jian  yang  lie   si    ji    er    xing  fu    sa    zi    zhi   yin   wu    xi    kao   zhu   jiang luo   none  an    dong  yi    mou   lei   yi    mi    quan  jin   po    wei   xiao  xie   hong  xu    su    kuang tao   qie   ju    er    zhou  ru    ping  xun   xiong zhi   guang huan  ming  huo   wa    qia   pai   wu    qu    liu   yi    jia   jing  qian  jiang jiao  zhen  shi   zhuo  ce    none  hui   ji    liu   chan  hun   hu    nong  xun   jin   lie   qiu   wei   zhe   jun   han   bang  mang  zhuo  you   xi    bo    dou   huan  hong  yi    pu    ying  lan   hao   lang  han   li    geng  fu    wu    li    chun  feng  yi    yu    tong  lao   hai   jin   jia   chong weng  mei   sui   cheng pei   xian  shen  tu    kun   pin   nie   han   jing  xiao  she   nian  tu    yong  xiao  xian  ting  e     su    tun   juan  cen   ti    li    shui  si    lei   shui  tao   du    lao   lai   lian  wei   wo    yun   huan  di    none  run   jian  zhang se    fu    guan  xing  shou  shuan ya    chuo  zhang ye    kong  wan   han   tuo   dong  he    wo    ju    gan   liang hun   ta    zhuo  dian  qie   de    juan  zi    xi    xiao  qi    gu    guo   han   lin   tang  zhou  peng  hao   chang shu   qi    fang  chi   lu    nao   ju    tao   cong  lei   zhi   peng  fei   song  tian  pi    dan   yu    ni    yu    lu    gan   mi    jing  ling  lun   yin   cui   qu    huai  yu    nian  shen  piao  chun  hu    yuan  lai   hun   qing  yan   qian  tian  miao  zhi   yin   mi    ben   yuan  wen   re    fei   qing  yuan  ke    ji    she   yuan  se    lu    zi    du    none  jian  mian  pi    xi    yu    yuan  shen  shen  rou   huan  zhu   jian  nuan  yu    qiu   ting  qu    du    feng  zha   bo    wo    wo    di    wei   wen   ru    xie   ce    wei   ge    gang  yan   hong  xuan  mi    ke    mao   ying  yan   you   hong  miao  xing  mei   zai   hun   nai   kui   shi   e     pai   mei   lian  qi    qi    mei   tian  cou   wei   can   tuan  mian  xu    mo    xu    ji    pen   jian  jian  hu    feng  xiang yi    yin   zhan  shi   jie   zhen  huang tan   yu    bi    min   shi   tu    sheng yong  ju    zhong none  qiu   jiao  none  yin   tang  long  huo   yuan  nan   ban   you   quan  chui  liang chan  yan   chun  nie   zi    wan   shi   man   ying  la    kui   none  jian  xu    lou   gui   gai   none  none  po    jin   gui   tang  yuan  suo   yuan  lian  yao   meng  zhun  sheng ke    tai   ta    wa    liu   gou   sao   ming  zha   shi   yi    lun   ma    pu    wei   li    \")\n                .append(\"cai   wu    xi    wen   qiang ce    shi   su    yi    zhen  sou   yun   xiu   yin   rong  hun   su    su    ni    ta    shi   ru    wei   pan   chu   chu   pang  weng  cang  mie   he    dian  hao   huang xi    zi    di    zhi   ying  fu    jie   hua   ge    zi    tao   teng  sui   bi    jiao  hui   gun   yin   gao   long  zhi   yan   she   man   ying  chun  lu:   lan   luan  xiao  bin   tan   yu    xiu   hu    bi    biao  zhi   jiang kou   shen  shang di    mi    ao    lu    hu    hu    you   chan  fan   yong  gun   man   qing  yu    piao  ji    ya    jiao  qi    xi    ji    lu    lu:   long  jin   guo   cong  lou   zhi   gai   qiang li    yan   cao   jiao  cong  chun  tuan  ou    teng  ye    xi    mi    tang  mo    shang han   lian  lan   wa    li    qian  feng  xuan  yi    man   zi    mang  kang  luo   peng  shu   zhang zhang chong xu    huan  kuo   jian  yan   chuangliao  cui   ti    yang  jiang cong  ying  hong  xiu   shu   guan  ying  xiao  none  none  xu    lian  zhi   wei   pi    yu    jiao  po    xiang hui   jie   wu    pa    ji    pan   wei   xiao  qian  qian  xi    lu    xi    sun   dun   huang min   run   su    liao  zhen  zhong yi    di    wan   dan   tan   chao  xun   kui   none  shao  tu    zhu   sa    hei   bi    shan  chan  chan  shu   tong  pu    lin   wei   se    se    cheng jiong cheng hua   jiao  lao   che   gan   cun   heng  si    shu   peng  han   yun   liu   hong  fu    hao   he    xian  jian  shan  xi    ao    lu    lan   none  yu    lin   min   zao   dang  huan  ze    xie   yu    li    shi   xue   ling  man   zi    yong  kuai  can   lian  dian  ye    ao    huan  lian  chan  man   dan   dan   yi    sui   pi    ju    ta    qin   ji    zhuo  lian  nong  guo   jin   fen   se    ji    sui   hui   chu   ta    song  ding  se    zhu   lai   bin   lian  mi    shi   shu   mi    ning  ying  ying  meng  jin   qi    bi    ji    hao   ru    zui   wo    tao   yin   yin   dui   ci    huo   jing  lan   jun   ai    pu    zhuo  wei   bin   gu    qian  xing  bin   kuo   fei   none  bin   jian  dui   luo   luo   lu:   li    you   yang  lu    si    jie   ying  du    wang  hui   xie   pan   shen  biao  chan  mie   liu   jian  pu    se    cheng gu    bin   huo   xian  lu    qin   han   ying  rong  li    jing  xiao  ying  sui   wei   xie   huai  hao   zhu   long  lai   dui   fan   hu    lai   none  none  ying  mi    ji    lian  jian  ying  fen   lin   yi    jian  yue   chan  dai   rang  jian  lan   fan   shuangyuan  zhuo  feng  she   lei   lan   cong  qu    yong  qian  fa    guan  que   yan   hao   none  sa    zan   luan  yan   li    mi    dan   tan   dang  jiao  chan  none  hao   ba    zhu   lan   lan   nang  wan   luan  quan  xian  yan   gan   yan   yu    huo   biao  mie   guang deng  hui   xiao  xiao  none  hong  ling  zao   zhuan jiu   zha   xie   chi   zhuo  zai   zai   can   yang  qi    zhong fen   niu   gui   wen   po    yi    lu    chui  pi    kai   pan   yan   kai   pang  mu    chao  liao  gui   kang  dun   guang xin   zhi   guang xin   wei   qiang bian  da    xia   zheng zhu   ke    zhao  fu    ba    duo   duo   ling  zhuo  xuan  ju    tan   pao   jiong pao   tai   tai   bing  yang  tong  han   zhu   zha   dian  wei   shi   lian  chi   ping  none  hu    shuo  lan   ting  jiao  xu    xing  quan  lie   huan  yang  xiao  xiu   xian  yin   wu    zhou  yao   shi   wei   tong  tong  zai   kai   hong  luo   xia   zhu   xuan  zheng po    yan   hui   guang zhe   hui   kao   none  fan   shao  ye    hui   none  tang  jin   re    none  xi    fu    jiong che   pu    jing  zhuo  ting  wan   hai   peng  lang  shan  hu    feng  chi   rong  hu    none  shu   lang  xun   xun   jue   xiao  xi    yan   han   zhuangqu    di    xie   qi    wu    none  none  han   yan   huan  men   ju    dao   bei   fen   lin   kun   hun   chun  xi    cui   wu    hong  ju    fu    yue   jiao  cong  feng  ping  qiong cui   xi    qiong xin   zhuo  yan   yan   yi    jue   yu    gang  ran   pi    yan   none  sheng chang shao  none  none  none  none  chen  he    kui   zhong duan  ya    hui   feng  lian  xuan  xing  huang jiao  jian  bi    ying  zhu   wei   tuan  tian  xi    nuan  nuan  chan  yan   jiong jiong yu    mei   sha   wu    ye  \")\n                .append(\"  xin   qiong rou   mei   huan  xu    zhao  wei   fan   qiu   sui   yang  lie   zhu   none  gao   gua   bao   hu    yun   xia   none  none  bian  wei   tui   tang  chao  shan  yun   bo    huang xie   xi    wu    xi    yun   he    he    xi    yun   xiong nai   kao   none  yao   xun   ming  lian  ying  wen   rong  none  none  qiang liu   xi    bi    biao  cong  lu    jian  shu   yi    lou   feng  sui   yi    teng  jue   zong  yun   hu    yi    zhi   ao    wei   liao  han   ou    re    jiong man   none  shang cuan  zeng  jian  xi    xi    xi    yi    xiao  chi   huang chan  ye    qian  ran   yan   xian  qiao  zun   deng  dun   shen  jiao  fen   si    liao  yu    lin   tong  shao  fen   fan   yan   xun   lan   mei   tang  yi    jing  men   none  none  ying  yu    yi    xue   lan   tai   zao   can   sui   xi    que   cong  lian  hui   zhu   xie   ling  wei   yi    xie   zhao  hui   none  none  lan   ru    xian  kao   xun   jin   chou  dao   yao   he    lan   biao  rong  li    mo    bao   ruo   di    lu:   ao    xun   kuang shuo  none  li    lu    jue   liao  yan   xi    xie   long  yan   none  rang  yue   lan   cong  jue   tong  guan  none  che   mi    tang  lan   zhu   lan   ling  cuan  yu    zhua  lan   pa    zheng pao   zhao  yuan  ai    wei   none  jue   jue   fu    ye    ba    die   ye    yao   zu    shuanger    pan   chuan ke    zang  zang  qiang die   qiang pian  ban   pan   shao  jian  pai   du    yong  tou   tou   bian  die   bang  bo    bang  you   none  du    ya    cheng niu   cheng pin   jiu   mou   ta    mu    lao   ren   mang  fang  mao   mu    ren   wu    yan   fa    bei   si    jian  gu    you   gu    sheng mu    di    qian  quan  quan  zi    te    xi    mang  keng  qian  wu    gu    xi    li    li    pou   ji    gang  zhi   ben   quan  run   du    ju    jia   jian  feng  pian  ke    ju    kao   chu   xi    bei   luo   jie   ma    san   wei   li    dun   tong  se    jiang xi    li    du    lie   pi    piao  bao   xi    chou  wei   kui   chou  quan  quan  ba    fan   qiu   bo    chai  chuo  an    jie   zhuangguang ma    you   kang  bo    hou   ya    han   huan  zhuangyun   kuang niu   di    qing  zhong yun   bei   pi    ju    ni    sheng pao   xia   tuo   hu    ling  fei   pi    ni    sheng you   gou   yue   ju    dan   bo    gu    xian  ning  huan  hen   jiao  he    zhao  ji    huan  shan  ta    rong  shou  tong  lao   du    xia   shi   kuai  zheng yu    sun   yu    bi    mang  xi    juan  li    xia   yin   suan  lang  bei   zhi   yan   sha   li    zhi   xian  jing  han   fei   yao   ba    qi    ni    biao  yin   li    lie   jian  qiang kun   yan   guo   zong  mi    chang yi    zhi   zheng ya    meng  cai   cu    she   lie   none  luo   hu    zong  hu    wei   feng  wo    yuan  xing  zhu   mao   wei   yuan  xian  tuan  ya    nao   xie   jia   hou   bian  you   you   mei   cha   yao   sun   bo    ming  hua   yuan  sou   ma    yuan  dai   yu    shi   hao   none  yi    zhen  chuanghao   man   jing  jiang mo    zhang chan  ao    ao    hao   cui   ben   jue   bi    bi    huang bu    lin   yu    tong  yao   liao  shuo  xiao  shou  none  xi    ge    juan  du    hui   kuai  xian  xie   ta    xian  xun   ning  bian  huo   nou   meng  lie   nao   guang shou  lu    ta    xian  mi    rang  huan  nao   luo   xian  qi    qu    xuan  miao  zi    lu:   lu    yu    su    wang  qiu   ga    ding  le    ba    ji    hong  di    chuan gan   jiu   yu    qi    yu    yang  ma    hong  wu    fu    min   jie   ya    bin   bian  beng  yue   jue   yun   jue   wan   jian  mei   dan   pi    wei   huan  xian  qiang ling  dai   yi    an    ping  dian  fu    xuan  xi    bo    ci    gou   jia   shao  po    ci    ke    ran   sheng shen  yi    zu    jia   min   shan  liu   bi    zhen  zhen  jue   fa    long  jin   jiao  jian  li    guang xian  zhou  gong  yan   xiu   yang  xu    luo   su    zhu   qin   ken   xun   bao   er    xiang yao   xia   heng  gui   chong xu    ban   pei   none  dang  ying  hun   wen   e     cheng ti    wu    wu    cheng jun   mei   bei   ting  xian  chuo  han   xuan  yan   qiu   quan  lang  li    xiu   fu    liu   ya    xi    ling  li    jin   lian  suo   suo   none  wan   dian  bing  zhan  cui   min   yu\")\n                .append(\"    ju    chen  lai   wen   sheng wei   dian  chu   zhuo  pei   cheng hu    qi    e     kun   chang qi    beng  wan   lu    cong  guan  yan   diao  bei   lin   qin   pi    pa    qiang zhuo  qin   fa    none  qiong du    jie   hun   yu    mao   mei   chun  xuan  ti    xing  dai   rou   min   zhen  wei   ruan  huan  xie   chuan jian  zhuan yang  lian  quan  xia   duan  yuan  ye    nao   hu    ying  yu    huang rui   se    liu   none  rong  suo   yao   wen   wu    jin   jin   ying  ma    tao   liu   tang  li    lang  gui   tian  qiang cuo   jue   zhao  yao   ai    bin   tu    chang kun   zhuan cong  jin   yi    cui   cong  qi    li    ying  suo   qiu   xuan  ao    lian  man   zhang yin   none  ying  wei   lu    wu    deng  none  zeng  xun   qu    dang  lin   liao  qiong su    huang gui   pu    jing  fan   jin   liu   ji    none  jing  ai    bi    can   qu    zao   dang  jiao  gun   tan   hui   huan  se    sui   tian  none  yu    jin   fu    bin   shu   wen   zui   lan   xi    ji    xuan  ruan  huo   gai   lei   du    li    zhi   rou   li    zan   qiong zhe   gui   sui   la    long  lu    li    zan   lan   ying  mi    xiang xi    guan  dao   zan   huan  gua   bao   die   pao   hu    zhi   piao  ban   rang  li    wa    none  jiang qian  ban   pen   fang  dan   weng  ou    none  none  none  hu    ling  yi    ping  ci    none  juan  chang chi   none  dang  meng  bu    chui  ping  bian  zhou  zhen  none  ci    ying  qi    xian  lou   di    ou    meng  zhuan beng  lin   zeng  wu    pi    dan   weng  ying  yan   gan   dai   shen  tian  tian  han   chang sheng qing  shen  chan  chan  rui   sheng su    shen  yong  shuai lu    fu    yong  beng  none  ning  tian  you   jia   shen  zha   dian  fu    nan   dian  ping  ding  hua   ting  quan  zai   meng  bi    qi    liu   xun   liu   chang mu    yun   fan   fu    geng  tian  jie   jie   quan  wei   fu    tian  mu    none  pan   jiang wa    da    nan   liu   ben   zhen  chu   mu    mu    ce    none  gai   bi    da    zhi   lu:e  qi    lu:e  pan   none  fan   hua   yu    yu    mu    jun   yi    liu   she   die   chou  hua   dang  chuo  ji    wan   jiang cheng chang tun   lei   ji    cha   liu   die   tuan  lin   jiang jiang chou  bo    die   die   pi    nie   dan   shu   shu   zhi   yi    chuangnai   ding  bi    jie   liao  gong  ge    jiu   zhou  xia   shan  xu    nu:e  li    yang  chen  you   ba    jie   jue   xi    xia   cui   bi    yi    li    zong  chuangfeng  zhu   pao   pi    gan   ke    ci    xie   qi    dan   zhen  fa    zhi   teng  ju    ji    fei   ju    dian  jia   xuan  zha   bing  nie   zheng yong  jing  quan  chong tong  yi    jie   wei   hui   duo   yang  chi   zhi   hen   ya    mei   dou   jing  xiao  tong  tu    mang  pi    xiao  suan  pu    li    zhi   cuo   duo   wu    sha   lao   shou  huan  xian  yi    peng  zhang guan  tan   fei   ma    lin   chi   ji    tian  an    chi   bi    bi    min   gu    dui   e     wei   yu    cui   ya    zhu   xi    dan   shen  zhong ji    yu    hou   feng  la    yang  shen  tu    yu    gua   wen   huan  ku    jia   yin   yi    lou   sao   jue   chi   xi    guan  yi    wen   ji    chuangban   lei   liu   chai  shou  nu:e  dian  da    bie   tan   zhang biao  shen  cu    luo   yi    zong  chou  zhang zhai  sou   suo   que   diao  lou   lou   mo    jin   yin   ying  huang fu    liao  long  qiao  liu   lao   xian  fei   dan   yin   he    ai    ban   xian  guan  guai  nong  yu    wei   yi    yong  pi    lei   li    shu   dan   lin   dian  lin   lai   bie   ji    chi   yang  xuan  jie   zheng none  li    huo   lai   ji    dian  xian  ying  yin   qu    yong  tan   dian  luo   luan  luan  bo    none  gui   po    fa    deng  fa    bai   bai   qie   bi    zao   zao   mao   de    pa    jie   huang gui   ci    ling  gao   mo    ji    jiao  peng  gao   ai    e     hao   han   bi    wan   chou  qian  xi    ai    jiong hao   huang hao   ze    cui   hao   xiao  ye    po    hao   jiao  ai    xing  huang li    piao  he    jiao  pi    gan   pao   zhou  jun   qiu   cun   que   zha   gu    jun   jun   zhou  zha   gu    zhan  du    min   qi    ying  yu    bei   zhao  zhong pen   he    ying  he    yi    bo    wan   he    ang   zhan  yan   jian  \")\n                .append(\"he    yu    kui   fan   gai   dao   pan   fu    qiu   sheng dao   lu    zhan  meng  lu    jin   xu    jian  pan   guan  an    lu    xu    zhou  dang  an    gu    li    mu    ding  gan   xu    mang  mang  zhi   qi    wan   tian  xiang dun   xin   xi    pan   feng  dun   min   ming  sheng shi   yun   mian  pan   fang  miao  dan   mei   mao   kan   xian  kou   shi   yang  zheng yao   shen  huo   da    zhen  kuang ju    shen  yi    sheng mei   mo    zhu   zhen  zhen  mian  di    yuan  die   yi    zi    zi    chao  zha   xuan  bing  mi    long  sui   tong  mi    die   yi    er    ming  xuan  chi   kuang juan  mou   zhen  tiao  yang  yan   mo    zhong mai   zhe   zheng mei   suo   shao  han   huan  di    cheng cuo   juan  e     wan   xian  xi    kun   lai   jian  shan  tian  hun   wan   ling  shi   qiong lie   ya    jing  zheng li    lai   sui   juan  shui  sui   du    pi    pi    mu    hun   ni    lu    gao   jie   cai   zhou  yu    hun   ma    xia   xing  hui   gun   none  chun  jian  mei   du    hou   xuan  ti    kui   gao   rui   mao   xu    fa    wen   miao  chou  kui   mi    weng  kou   dang  chen  ke    sou   xia   qiong mao   ming  man   shui  ze    zhang yi    diao  kou   mo    shun  cong  lou   chi   man   piao  cheng ji    meng  huan  run   pie   xi    qiao  pu    zhu   deng  shen  shun  liao  che   xian  kan   ye    xu    tong  wu    lin   kui   jian  ye    ai    hui   zhan  jian  gu    zhao  ju    wei   chou  ji    ning  xun   yao   huo   meng  mian  bin   mian  li    guang jue   xuan  mian  huo   lu    meng  long  guan  man   xi    chu   tang  kan   zhu   mao   jin   lin   yu    shuo  ce    jue   shi   yi    shen  zhi   hou   shen  ying  ju    zhou  jiao  cuo   duan  ai    jiao  zeng  huo   bai   shi   ding  qi    ji    zi    gan   wu    tuo   ku    qiang xi    fan   kuang dang  ma    sha   dan   jue   li    fu    min   nuo   hua   kang  zhi   qi    kan   jie   fen   e     ya    pi    zhe   yan   sui   zhuan che   dun   pan   yan   none  feng  fa    mo    zha   qu    yu    ke    tuo   tuo   di    zhai  zhen  e     fu    mu    zhu   la    bian  nu    ping  peng  ling  pao   le    po    bo    po    shen  za    ai    li    long  tong  none  li    kuang chu   keng  quan  zhu   kuang gui   e     nao   jia   lu    wei   ai    luo   ken   xing  yan   dong  peng  xi    none  hong  shuo  xia   qiao  none  wei   qiao  none  keng  xiao  que   chan  lang  hong  yu    xiao  xia   mang  long  none  che   che   wo    liu   ying  mang  que   yan   cuo   kun   yu    none  none  lu    chen  jian  none  song  zhuo  keng  peng  yan   zhui  kong  ceng  qi    zong  qing  lin   jun   bo    ding  min   diao  jian  he    liu   ai    sui   que   ling  bei   yin   dui   wu    qi    lun   wan   dian  gang  bei   qi    chen  ruan  yan   die   ding  zhou  tuo   jie   ying  bian  ke    bi    wei   shuo  zhen  duan  xia   dang  ti    nao   peng  jian  di    tan   cha   none  qi    none  feng  xuan  que   que   ma    gong  nian  su    e     ci    liu   si    tang  bang  hua   pi    wei   sang  lei   cuo   tian  xia   xi    lian  pan   wei   yun   dui   zhe   ke    la    none  qing  gun   zhuan chan  qi    ao    peng  lu    lu    kan   qiang chen  yin   lei   biao  qi    mo    qi    cui   zong  qing  chuo  none  ji    shan  lao   qu    zeng  deng  jian  xi    lin   ding  dian  huang pan   za    qiao  di    li    jian  jiao  xi    zhang qiao  dun   jian  yu    zhui  he    huo   zhai  lei   ke    chu   ji    que   dang  wo    jiang pi    pi    yu    pin   qi    ai    ke    jian  yu    ruan  meng  pao   zi    bo    none  mie   ca    xian  kuang lei   lei   zhi   li    li    fan   que   pao   ying  li    long  long  mo    bo    shuangguan  lan   zan   yan   shi   shi   li    reng  she   yue   si    qi    ta    ma    xie   yao   xian  zhi   qi    zhi   beng  shu   chong none  yi    shi   you   zhi   tiao  fu    fu    mi    zu    zhi   suan  mei   zuo   qu    hu    zhu   shen  sui   ci    chai  mi    lu:   yu    xiang wu    tiao  piao  zhu   gui   xia   zhi   ji    gao   zhen  gao   shui  jin   zhen  gai   kun   di    dao   huo   tao   qi    gu    guan  zui   ling  lu    bing  jin   dao   zhi   lu    shan  bei   zhe   hui   you   xi  \")\n                .append(\"  yin   zi    huo   zhen  fu    yuan  wu    xian  yang  ti    yi    mei   si    di    none  zhuo  zhen  yong  ji    gao   tang  chi   ma    ta    none  xuan  qi    yu    xi    ji    si    chan  xuan  hui   sui   li    nong  ni    dao   li    rang  yue   ti    zan   lei   rou   yu    yu    li    xie   qin   he    tu    xiu   si    ren   tu    zi    cha   gan   yi    xian  bing  nian  qiu   qiu   zhong fen   hao   yun   ke    miao  zhi   jing  bi    zhi   yu    mi    ku    ban   pi    ni    li    you   zu    pi    ba    ling  mo    cheng nian  qin   yang  zuo   zhi   zhi   shu   ju    zi    tai   ji    cheng tong  zhi   huo   he    yin   zi    zhi   jie   ren   du    yi    zhu   hui   nong  fu    xi    kao   lang  fu    ze    shui  lu:   kun   gan   jing  ti    cheng tu    shao  shui  ya    lun   lu    gu    zuo   ren   zhun  bang  bai   ji    zhi   zhi   kun   leng  peng  ke    bing  chou  zui   yu    su    none  none  yi    xi    bian  ji    fu    bi    nuo   jie   zhong zong  xu    cheng dao   wen   lian  zi    yu    ji    xu    zhen  zhi   dao   jia   ji    gao   gao   gu    rong  sui   none  ji    kang  mu    shan  men   zhi   ji    lu    su    ji    ying  wen   qiu   se    none  yi    huang qie   ji    sui   xiao  pu    jiao  zhuo  tong  none  lu:   sui   nong  se    hui   rang  nuo   yu    none  ji    tui   wen   cheng huo   gong  lu:   biao  none  rang  jue   li    zan   xue   wa    jiu   qiong xi    qiong kong  yu    sen   jing  yao   chuan zhun  tu    lao   qie   zhai  yao   bian  bao   yao   bing  yu    zhu   jiao  qiao  diao  wu    gui   yao   zhi   chuan yao   tiao  jiao  chuangjiong xiao  cheng kou   cuan  wo    dan   ku    ke    zhui  xu    su    none  kui   dou   none  yin   wo    wa    ya    yu    ju    qiong yao   yao   tiao  liao  yu    tian  diao  ju    liao  xi    wu    kui   chuangju    none  kuan  long  cheng cui   piao  zao   cuan  qiao  qiong dou   zao   zao   qie   li    chu   shi   fu    qian  chu   hong  qi    qian  gong  shi   shu   miao  ju    zhan  zhu   ling  long  bing  jing  jing  zhang yi    si    jun   hong  tong  song  jing  diao  yi    shu   jing  qu    jie   ping  duan  shao  zhuan ceng  deng  cun   huai  jing  kan   jing  zhu   zhu   le    peng  yu    chi   gan   mang  zhu   none  du    ji    xiao  ba    suan  ji    zhen  zhao  sun   ya    zhui  yuan  hu    gang  xiao  cen   pi    bi    jian  yi    dong  shan  sheng xia   di    zhu   na    chi   gu    li    qie   min   bao   tiao  si    fu    ce    ben   fa    da    zi    di    ling  ze    nu    fu    gou   fan   jia   ge    fan   shi   mao   po    none  jian  qiong long  none  bian  luo   gui   qu    chi   yin   yao   xian  bi    qiong gua   deng  jiao  jin   quan  sun   ru    fa    kuang zhu   tong  ji    da    hang  ce    zhong kou   lai   bi    shai  dang  zheng ce    fu    yun   tu    pa    li    lang  ju    guan  jian  han   tong  xia   zhi   cheng suan  shi   zhu   zuo   xiao  shao  ting  jia   yan   gao   kuai  gan   chou  kuang gang  yun   none  qian  xiao  jian  pu    lai   zou   bi    bi    bi    ge    chi   guai  yu    jian  zhao  gu    chi   zheng qing  sha   zhou  lu    bo    ji    lin   suan  jun   fu    zha   gu    kong  qian  qian  jun   chui  guan  yuan  ce    ju    bo    ze    qie   tuo   luo   dan   xiao  ruo   jian  none  bian  sun   xiang xian  ping  zhen  sheng hu    shi   zhu   yue   chun  fu    wu    dong  shuo  ji    jie   huang xing  mei   fan   chuan zhuan pian  feng  zhu   hong  qie   hou   qiu   miao  qian  none  kui   none  lou   yun   he    tang  yue   chou  gao   fei   ruo   zheng gou   nie   qian  xiao  cuan  gong  pang  du    li    bi    zhuo  chu   shai  chi   zhu   qiang long  lan   jian  bu    li    hui   bi    di    cong  yan   peng  sen   cuan  pai   piao  dou   yu    mie   zhuan ze    xi    guo   yi    hu    chan  kou   cu    ping  zao   ji    gui   su    lou   zha   lu    nian  suo   cuan  none  suo   le    duan  liang xiao  bo    mi    shai  dang  liao  dan   dian  fu    jian  min   kui   dai   qiao  deng  huang sun   lao   zan   xiao  lu    shi   zan   none  pai   qi    pai   gan   ju    du    lu    yan   bo    dang  sai   ke    gou   qian  lian  bu    zhou  lai   none  la\")\n                .append(\"n   kui   yu    yue   hao   zhen  tai   ti    mi    chou  ji    none  qi    teng  zhuan zhou  fan   sou   zhou  qian  kuo   teng  lu    lu    jian  tuo   ying  yu    lai   long  none  lian  lan   qian  yue   zhong qu    lian  bian  duan  zuan  li    shai  luo   ying  yue   zhuo  xu    mi    di    fan   shen  zhe   shen  nu:   xie   lei   xian  zi    ni    cun   zhang qian  none  bi    ban   wu    sha   kang  rou   fen   bi    cui   yin   li    chi   tai   none  ba    li    gan   ju    po    mo    cu    zhan  zhou  li    su    tiao  li    xi    su    hong  tong  zi    ce    yue   zhou  lin   zhuangbai   none  fen   mian  qu    none  liang xian  fu    liang can   jing  li    yue   lu    ju    qi    cui   bai   chang lin   zong  jing  guo   none  san   san   tang  bian  rou   mian  hou   xu    zong  hu    jian  zan   ci    li    xie   fu    nuo   bei   gu    xiu   gao   tang  qiu   none  cao   zhuangtang  mi    san   fen   zao   kang  jiang mo    san   san   nuo   chi   liang jiang kuai  bo    huan  shu   zong  jian  nuo   tuan  nie   li    zuo   di    nie   tiao  lan   mi    mi    jiu   xi    gong  zheng jiu   you   ji    cha   zhou  xun   yue   hong  yu    he    wan   ren   wen   wen   qiu   na    zi    tou   niu   fou   jie   shu   chun  pi    yin   sha   hong  zhi   ji    fen   yun   ren   dan   jin   su    fang  suo   cui   jiu   zha   ba    jin   fu    zhi   qi    zi    chou  hong  zha   lei   xi    fu    xie   shen  bei   zhu   qu    ling  zhu   shao  gan   yang  fu    tuo   zhen  dai   chu   shi   zhong xian  zu    jiong ban   ju    pa    shu   zui   kuang jing  ren   heng  xie   jie   zhu   chou  gua   bai   jue   kuang hu    ci    geng  geng  tao   xie   ku    jiao  quan  gai   luo   xuan  beng  xian  fu    gei   tong  rong  tiao  yin   lei   xie   quan  xu    hai   die   tong  si    jiang xiang hui   jue   zhi   jian  juan  chi   mian  zhen  lu:   cheng qiu   shu   bang  tong  xiao  wan   qin   geng  xiu   ti    xiu   xie   hong  xi    fu    ting  sui   dui   kun   fu    jing  hu    zhi   yan   jiong feng  ji    xu    none  zong  lin   duo   li    lu:   liang chou  quan  shao  qi    qi    zhun  qi    wan   qian  xian  shou  wei   qi    tao   wan   gang  wang  beng  zhui  cai   guo   cui   lun   liu   qi    zhan  bei   chuo  ling  mian  qi    jie   tan   zong  gun   zou   yi    zi    xing  liang jin   fei   rui   min   yu    zong  fan   lu:   xu    none  shang none  xu    xiang jian  ke    xian  ruan  mian  ji    duan  zhong di    min   miao  yuan  xie   bao   si    qiu   bian  huan  geng  zong  mian  wei   fu    wei   yu    gou   miao  jie   lian  zong  bian  yun   yin   ti    gua   zhi   yun   cheng chan  dai   jia   yuan  zong  xu    sheng none  geng  none  ying  jin   yi    zhui  ni    bang  gu    pan   zhou  jian  cuo   quan  shuangyun   xia   shuai xi    rong  tao   fu    yun   zhen  gao   ru    hu    zai   teng  xian  su    zhen  zong  tao   huang cai   bi    feng  cu    li    suo   yin   xi    zong  lei   zhuan qian  man   zhi   lu:   mo    piao  lian  mi    xuan  zong  ji    shan  sui   fan   shuai beng  yi    sao   mou   zhou  qiang hun   xian  xi    none  xiu   ran   xuan  hui   qiao  zeng  zuo   zhi   shan  san   lin   yu    fan   liao  chuo  zun   jian  rao   chan  rui   xiu   hui   hua   zuan  xi    qiang none  da    sheng hui   xi    se    jian  jiang huan  qiao  cong  jie   jiao  bo    chan  yi    nao   sui   yi    shai  xu    ji    bin   qian  jian  pu    xun   zuan  qi    peng  li    mo    lei   xie   zuan  kuang you   xu    lei   xian  chan  none  lu    chan  ying  cai   xiang xian  zui   zuan  luo   xi    dao   lan   lei   lian  mi    jiu   yu    hong  zhou  xian  he    yue   ji    wan   kuang ji    ren   wei   yun   hong  chun  pi    sha   gang  na    ren   zong  lun   fen   zhi   wen   fang  zhu   zhen  niu   shu   xian  gan   xie   fu    lian  zu    shen  xi    zhi   zhong zhou  ban   fu    chu   shao  yi    jing  dai   bang  rong  jie   ku    rao   die   hang  hui   ji    xuan  jiang luo   jue   jiao  tong  geng  xiao  juan  xiu   xi    sui   tao   ji    ti    ji    xu    ling  yin   xu    qi    fei   chuo  shang gun   sheng wei   mian  shou  beng  chou  tao   liu   quan  \")\n                .append(\"zong  zhan  wan   lu:   zhui  zi    ke    xiang jian  mian  lan   ti    miao  ji    yun   hui   si    duo   duan  bian  xian  gou   zhui  huan  di    lu:   bian  min   yuan  jin   fu    ru    zhen  feng  cui   gao   chan  li    yi    jian  bin   piao  man   lei   ying  suo   mou   sao   xie   liao  shan  zeng  jiang qian  qiao  huan  jiao  zuan  fou   xie   gang  fou   que   fou   que   bo    ping  hou   none  gang  ying  ying  qing  xia   guan  zun   tan   none  qing  weng  ying  lei   tan   lu    guan  wang  gang  wang  wang  han   none  luo   fu    mi    fa    gu    zhu   ju    mao   gu    min   gang  ba    gua   ti    juan  fu    lin   yan   zhao  zui   gua   zhuo  yu    zhi   an    fa    lan   shu   si    pi    ma    liu   ba    fa    li    chao  wei   bi    ji    zeng  tong  liu   ji    juan  mi    zhao  luo   pi    ji    ji    luan  yang  mie   qiang ta    mei   yang  you   you   fen   ba    gao   yang  gu    qiang zang  gao   ling  yi    zhu   di    xiu   qiang yi    xian  rong  qun   qun   qian  huan  suo   xian  yi    yang  qiang xian  yu    geng  jie   tang  yuan  xi    fan   shan  fen   shan  lian  lei   geng  nou   qiang chan  yu    gong  yi    chong weng  fen   hong  chi   chi   cui   fu    xia   pen   yi    la    yi    pi    ling  liu   zhi   qu    xi    xie   xiang xi    xi    qi    qiao  hui   hui   shu   se    hong  jiang zhai  cui   fei   tao   sha   chi   zhu   jian  xuan  shi   pian  zong  wan   hui   hou   he    he    han   ao    piao  yi    lian  qu    none  lin   pen   qiao  ao    fan   yi    hui   xuan  dao   yao   lao   none  kao   mao   zhe   qi    gou   gou   gou   die   die   er    shua  ruan  er    nai   zhuan lei   ting  zi    geng  chao  hao   yun   pa    pi    chi   si    qu    jia   ju    huo   chu   lao   lun   ji    tang  ou    lou   nou   jiang pang  ze    lou   ji    lao   huo   you   mo    huai  er    zhe   ding  ye    da    song  qin   yun   chi   dan   dan   hong  geng  zhi   none  nie   dan   zhen  che   ling  zheng you   wa    liao  long  zhi   ning  tiao  er    ya    die   guo   none  lian  hao   sheng lie   pin   jing  ju    bi    di    guo   wen   xu    ping  cong  none  none  ting  yu    cong  kui   lian  kui   cong  lian  weng  kui   lian  lian  cong  ao    sheng song  ting  kui   nie   zhi   dan   ning  none  ji    ting  ting  long  yu    yu    zhao  si    su    yi    su    si    zhao  zhao  rou   yi    lei   ji    qiu   ken   cao   ge    di    huan  huang yi    ren   xiao  ru    zhou  yuan  du    gang  rong  gan   cha   wo    chang gu    zhi   qin   fu    fei   ban   pei   pang  jian  fang  zhun  you   na    ang   ken   ran   gong  yu    wen   yao   jin   pi    qian  xi    xi    fei   ken   jing  tai   shen  zhong zhang xie   shen  wei   zhou  die   dan   fei   ba    bo    qu    tian  bei   gua   tai   zi    ku    zhi   ni    ping  zi    fu    pang  zhen  xian  zuo   pei   jia   sheng zhi   bao   mu    qu    hu    ke    yi    yin   xu    yang  long  dong  ka    lu    jing  nu    yan   pang  kua   yi    guang hai   ge    dong  zhi   jiao  xiong xiong er    an    xing  pian  neng  zi    none  cheng tiao  zhi   cui   mei   xie   cui   xie   mo    mai   ji    xie   none  kuai  sa    zang  qi    nao   mi    nong  luan  wan   bo    wen   wan   qiu   jiao  jing  you   heng  cuo   lie   shan  ting  mei   chun  shen  xie   none  juan  cu    xiu   xin   tuo   pao   cheng nei   fu    dou   tuo   niao  nao   pi    gu    luo   li    lian  zhang cui   jie   liang shui  pi    biao  lun   pian  guo   juan  chui  dan   tian  nei   jing  jie   la    ye    a     ren   shen  chuo  fu    fu    ju    fei   qiang wan   dong  pi    guo   zong  ding  wu    mei   ruan  zhuan zhi   cou   gua   ou    di    an    xing  nao   shu   shuan nan   yun   zhong rou   e     sai   tu    yao   jian  wei   jiao  yu    jia   duan  bi    chang fu    xian  ni    mian  wa    teng  tui   bang  qian  lu:   wa    shou  tang  su    zhui  ge    yi    bo    liao  ji    pi    xie   gao   lu:   bin   none  chang lu    guo   pang  chuai biao  jiang fu    tang  mo    xi    zhuan lu:   jiao  ying  lu:   zhi   xue   chun  lin   tong  peng  ni    chuai liao  cui   gui   xiao  teng  fan   zhi   jiao  shan  hu  \")\n                .append(\"  cui   run   xin   sui   fen   ying  shan  gua   dan   kuai  nong  tun   lian  bei   yong  jue   chu   yi    juan  la    lian  sao   tun   gu    qi    cui   bin   xun   nao   huo   zang  xian  biao  xing  kuan  la    yan   lu    hu    za    luo   qu    zang  luan  ni    za    chen  qian  wo    guang zang  lin   guang zi    jiao  nie   chou  ji    gao   chou  mian  nie   zhi   zhi   ge    jian  die   zhi   xiu   tai   zhen  jiu   xian  yu    cha   yao   yu    chong xi    xi    jiu   yu    yu    xing  ju    jiu   xin   she   she   she   jiu   shi   tan   shu   shi   tian  dan   pu    pu    guan  hua   tian  chuan shun  xia   wu    zhou  dao   chuan shan  yi    none  pa    tai   fan   ban   chuan hang  fang  ban   bi    lu    zhong jian  cang  ling  zhu   ze    duo   bo    xian  ge    chuan jia   lu    hong  pang  xi    none  fu    zao   feng  li    shao  yu    lang  ting  none  wei   bo    meng  nian  ju    huang shou  zong  bian  mao   die   none  bang  cha   yi    sou   cang  cao   lou   dai   none  yao   chong none  dang  qiang lu    yi    jie   jian  huo   meng  qi    lu    lu    chan  shuanggen   liang jian  jian  se    yan   fu    ping  yan   yan   cao   cao   yi    le    ting  jiao  ai    nai   tiao  jiao  jie   peng  wan   yi    chai  mian  mi    gan   qian  yu    yu    shao  xiong du    xia   qi    mang  zi    hui   sui   zhi   xiang bi    fu    tun   wei   wu    zhi   qi    shan  wen   qian  ren   fou   kou   jie   lu    zhu   ji    qin   qi    yan   fen   ba    rui   xin   ji    hua   hua   fang  wu    jue   gou   zhi   yun   qin   ao    chu   mao   ya    fei   reng  hang  cong  yin   you   bian  yi    none  wei   li    pi    e     xian  chang cang  zhu   su    yi    yuan  ran   ling  tai   tiao  di    miao  qing  li    rao   ke    mu    pei   bao   gou   min   yi    yi    ju    pie   ruo   ku    zhu   ni    bo    bing  shan  qiu   yao   xian  ben   hong  ying  zha   dong  ju    die   nie   gan   hu    ping  mei   fu    sheng gu    bi    wei   fu    zhuo  mao   fan   qie   mao   mao   ba    zi    mo    zi    di    chi   gou   jing  long  none  niao  none  xue   ying  qiong ge    ming  li    rong  yin   gen   qian  chai  chen  yu    xiu   zi    lie   wu    duo   kui   ce    jian  ci    gou   guang mang  cha   jiao  jiao  fu    yu    zhu   zi    jiang hui   yin   cha   fa    rong  ru    chong mang  tong  zhong none  zhu   xun   huan  kua   quan  gai   da    jing  xing  chuan cao   jing  er    an    shou  chi   ren   jian  ti    huang ping  li    jin   lao   rong  zhuangda    jia   rao   bi    ce    qiao  hui   ji    dang  none  rong  hun   ying  luo   ying  qian  jin   sun   yin   mai   hong  zhou  yao   du    wei   chu   dou   fu    ren   yin   he    bi    bu    yun   di    tu    sui   sui   cheng chen  wu    bie   xi    geng  li    pu    zhu   mo    li    zhuangji    duo   qiu   sha   suo   chen  feng  ju    mei   meng  xing  jing  che   xin   jun   yan   ting  you   cuo   guan  han   you   cuo   jia   wang  you   niu   shao  xian  lang  fu    e     mo    wen   jie   nan   mu    kan   lai   lian  shi   wo    tu    xian  huo   you   ying  ying  none  chun  mang  mang  ci    yu    jing  di    qu    dong  jian  zou   gu    la    lu    ju    wei   jun   nie   kun   he    pu    zai   gao   guo   fu    lun   chang chou  song  chui  zhan  men   cai   ba    li    tu    bo    han   bao   qin   juan  xi    qin   di    jie   pu    dang  jin   zhao  tai   geng  hua   gu    ling  fei   jin   an    wang  beng  zhou  yan   zu    jian  lin   tan   shu   tian  dao   hu    ji    he    cui   tao   chun  bei   chang huan  fei   lai   qi    meng  ping  wei   dan   sha   huan  yan   yi    tiao  qi    wan   ce    nai   none  tuo   jiu   tie   luo   none  none  meng  none  none  ding  ying  ying  ying  xiao  sa    qiu   ke    xiang wan   yu    yu    fu    lian  xuan  xuan  nan   ze    wo    chun  xiao  yu    pian  mao   an    e     luo   ying  huo   gua   jiang wan   zuo   zuo   ju    bao   rou   xi    xie   an    qu    jian  fu    lu:   lu:   pen   feng  hong  hong  hou   yan   tu    zhu   zi    xiang shen  ge    qia   jing  mi    huang shen  pu    ge    dong  zhou  qian  wei   bo    wei   pa    ji    hu    zang  ji\")\n                .append(\"a   duan  yao   jun   cong  quan  wei   xian  kui   ting  hun   xi    shi   qi    lan   zong  yao   yuan  mei   yun   shu   di    zhuan guan  none  qiong chan  kai   kui   none  jiang lou   wei   pai   none  sou   yin   shi   chun  shi   yun   zhen  lang  nu    meng  he    que   suan  yuan  li    ju    xi    bang  chu   xu    tu    liu   huo   zhen  qian  zu    po    cuo   yuan  chu   yu    kuai  pan   pu    pu    na    shuo  xi    fen   yun   zheng jian  ji    ruo   cang  en    mi    hao   sun   zhen  ming  none  xu    liu   xi    gu    lang  rong  weng  gai   cuo   shi   tang  luo   ru    suo   xian  bei   yao   gui   bi    zong  gun   none  xiu   ce    none  lan   none  ji    li    can   lang  yu    none  ying  mo    diao  xiu   wu    tong  zhu   peng  an    lian  cong  xi    ping  qiu   jin   chun  jie   wei   tui   cao   yu    yi    ji    liao  bi    lu    xu    bu    zhang luo   qiang man   yan   leng  ji    biao  gun   han   di    su    lu    she   shang di    mie   xun   man   bo    di    cuo   zhe   sen   xuan  yu    hu    ao    mi    lou   cu    zhong cai   po    jiang mi    cong  niao  hui   jun   yin   jian  nian  shu   yin   kui   chen  hu    sha   kou   qian  ma    cang  ze    qiang dou   lian  lin   kou   ai    bi    li    wei   ji    qian  sheng fan   meng  ou    chan  dian  xun   jiao  rui   rui   lei   yu    qiao  chu   hua   jian  mai   yun   bao   you   qu    lu    rao   hui   e     ti    fei   jue   zui   fa    ru    fen   kui   shun  rui   ya    xu    fu    jue   dang  wu    tong  si    xiao  xi    yong  wen   shao  qi    jian  yun   sun   ling  yu    xia   weng  ji    hong  si    deng  lei   xuan  yun   yu    xi    hao   bo    hao   ai    wei   hui   wei   ji    ci    xiang luan  mie   yi    leng  jiang can   shen  qiang lian  ke    yuan  da    ti    tang  xue   bi    zhan  sun   lian  fan   ding  xiao  gu    xie   shu   jian  kao   hong  sa    xin   xun   yao   bai   sou   shu   xun   dui   pin   wei   neng  chou  mai   ru    piao  tai   qi    zao   chen  zhen  er    ni    ying  gao   cong  xiao  qi    fa    jian  xu    kui   jie   bian  di    mi    lan   jin   zang  miao  qiong qie   xian  none  ou    xian  su    lu:   yi    xu    xie   li    yi    la    lei   jiao  di    zhi   pi    teng  yao   mo    huan  biao  fan   sou   tan   tui   qiong qiao  wei   liu   hui   shu   gao   yun   none  li    zhu   zhu   ai    lin   zao   xuan  chen  lai   huo   tuo   wu    rui   rui   qi    heng  lu    su    tui   mang  yun   pin   yu    xun   ji    jiong xuan  mo    none  su    jiong none  nie   bo    rang  yi    xian  yu    ju    lian  lian  yin   qiang ying  long  tou   wei   yue   ling  qu    yao   fan   mi    lan   kui   lan   ji    dang  none  lei   lei   tong  feng  zhi   wei   kui   zhan  huai  li    ji    mi    lei   huai  luo   ji    nao   lu    jian  none  none  lei   quan  xiao  yi    luan  men   bie   hu    hu    lu    nu:e  lu:   zhi   xiao  qian  chu   hu    xu    cuo   fu    xu    xu    lu    hu    yu    hao   jiao  ju    guo   bao   yan   zhan  zhan  kui   ban   xi    shu   chong qiu   diao  ji    qiu   ding  shi   none  di    zhe   she   yu    gan   zi    hong  hui   meng  ge    sui   xia   chai  shi   yi    ma    xiang fang  e     pa    chi   qian  wen   wen   rui   bang  pi    yue   yue   jun   qi    ran   yin   qi    can   yuan  jue   hui   qian  qi    zhong ya    hao   mu    wang  fen   fen   hang  gong  zao   fu    ran   jie   fu    chi   dou   bao   xian  ni    te    qiu   you   zha   ping  chi   you   he    han   ju    li    fu    ran   zha   gou   pi    bo    xian  zhu   diao  bie   bing  gu    ran   qu    she   tie   ling  gu    dan   gu    ying  li    cheng qu    mou   ge    ci    hui   hui   mang  fu    yang  wa    lie   zhu   yi    xian  kuo   jiao  li    yi    ping  ji    ha    she   yi    wang  mo    qiong qie   gui   gong  zhi   man   none  zhe   jia   nao   si    qi    xing  lie   qiu   shao  yong  jia   tui   che   bai   e     han   shu   xuan  feng  shen  zhen  fu    xian  zhe   wu    fu    li    lang  bi    chu   yuan  you   jie   dan   yan   ting  dian  tui   hui   wo    zhi   song  fei   ju    mi    qi    qi    yu    jun   la    meng  qiang si    xi    \")\n                .append(\"lun   li    die   tiao  tao   kun   gan   han   yu    bang  fei   pi    wei   dun   yi    yuan  su    quan  qian  rui   ni    qing  wei   liang guo   wan   dong  e     ban   zhuo  wang  can   yang  ying  guo   chan  none  la    ke    ji    xie   ting  mai   xu    mian  yu    jie   shi   xuan  huang yan   bian  rou   wei   fu    yuan  mei   wei   fu    ruan  xie   you   you   mao   xia   ying  shi   chong tang  zhu   zong  ti    fu    yuan  kui   meng  la    du    hu    qiu   die   li    gua   yun   ju    nan   lou   chun  rong  ying  jiang tun   lang  pang  si    xi    xi    xi    yuan  weng  lian  sou   ban   rong  rong  ji    wu    xiu   han   qin   yi    bi    hua   tang  yi    du    nai   he    hu    xi    ma    ming  yi    wen   ying  teng  yu    cang  none  none  man   none  shang shi   cao   chi   di    ao    lu    wei   zhi   tang  chen  piao  qu    pi    yu    jian  luo   lou   qin   zhong yin   jiang shuai wen   jiao  wan   zhe   zhe   ma    ma    guo   liao  mao   xi    cong  li    man   xiao  none  zhang mang  xiang mo    zi    si    qiu   te    zhi   peng  peng  jiao  qu    bie   liao  pan   gui   xi    ji    zhuan huang fei   lao   jue   jue   hui   yin   chan  jiao  shan  rao   xiao  wu    chong xun   si    none  cheng dang  li    xie   shan  yi    jing  da    chan  qi    zi    xiang she   luo   qin   ying  chai  li    ze    xuan  lian  zhu   ze    xie   mang  xie   qi    rong  jian  meng  hao   ru    huo   zhuo  jie   bin   he    mie   fan   lei   jie   la    mi    li    chun  li    qiu   nie   lu    du    xiao  zhu   long  li    long  feng  ye    pi    rang  gu    juan  ying  none  xi    can   qu    quan  du    can   man   qu    jie   zhu   zha   xue   huang nu:   pei   nu:   xin   zhong mo    er    mie   mie   shi   xing  yan   kan   yuan  none  ling  xuan  shu   xian  tong  long  jie   xian  ya    hu    wei   dao   chong wei   dao   zhun  heng  qu    yi    yi    bu    gan   yu    biao  cha   yi    shan  chen  fu    gun   fen   shuai jie   na    zhong dan   ri    zhong zhong xie   qi    xie   ran   zhi   ren   qin   jin   jun   yuan  mei   chai  ao    niao  hui   ran   jia   tuo   ling  dai   bao   pao   yao   zuo   bi    shao  tan   ju    he    xue   xiu   zhen  yi    pa    bo    di    wa    fu    gun   zhi   zhi   ran   pan   yi    mao   none  na    kou   xuan  chan  qu    bei   yu    xi    none  bo    none  fu    yi    chi   ku    ren   jiang jia   cun   mo    jie   er    ge    ru    zhu   gui   yin   cai   lie   none  none  zhuangdang  none  kun   ken   niao  shu   jia   kun   cheng li    juan  shen  pou   ge    yi    yu    chen  liu   qiu   qun   ji    yi    bu    zhuangshui  sha   qun   li    lian  lian  ku    jian  fou   tan   bi    gun   tao   yuan  ling  chi   chang chou  duo   biao  liang shang pei   pei   fei   yuan  luo   guo   yan   du    ti    zhi   ju    qi    ji    zhi   gua   ken   none  ti    shi   fu    chong xie   bian  die   kun   duan  xiu   xiu   he    yuan  bao   bao   fu    yu    tuan  yan   hui   bei   chu   lu:   none  none  yun   ta    gou   da    huai  rong  yuan  ru    nai   jiong suo   ban   tun   chi   sang  niao  ying  jie   qian  huai  ku    lian  lan   li    zhe   shi   lu:   yi    die   xie   xian  wei   biao  cao   ji    qiang sen   bao   xiang none  pu    jian  zhuan jian  zui   ji    dan   za    fan   bo    xiang xin   bie   rao   man   lan   ao    duo   hui   cao   sui   nong  chan  lian  bi    jin   dang  shu   tan   bi    lan   pu    ru    zhi   none  shu   wa    shi   bai   xie   bo    chen  lai   long  xi    xian  lan   zhe   dai   none  zan   shi   jian  pan   yi    none  ya    xi    xi    yao   feng  tan   none  none  fu    ba    he    ji    ji    jian  guan  bian  yan   gui   jue   pian  mao   mi    mi    mie   shi   si    zhan  luo   jue   mo    tiao  lian  yao   zhi   jun   xi    shan  wei   xi    tian  yu    lan   e     du    qin   pang  ji    ming  ping  gou   qu    zhan  jin   guan  deng  jian  luo   qu    jian  wei   jue   qu    luo   lan   shen  di    guan  jian  guan  yan   gui   mi    shi   chan  lan   jue   ji    xi    di    tian  yu    gou   jin   qu    jiao  jiu   jin   cu    jue   zhi   chao  ji    gu    dan   zui   di    shan\")\n                .append(\"g hua   quan  ge    zhi   jie   gui   gong  chu   jie   huan  qiu   xing  su    ni    ji    lu    zhi   zhu   bi    xing  hu    shang gong  zhi   xue   chu   xi    yi    li    jue   xi    yan   xi    yan   yan   ding  fu    qiu   qiu   jiao  hong  ji    fan   xun   diao  hong  cha   tao   xu    jie   yi    ren   xun   yin   shan  qi    tuo   ji    xun   yin   e     fen   ya    yao   song  shen  yin   xin   jue   xiao  ne    chen  you   zhi   xiong fang  xin   chao  she   xian  sa    zhun  xu    yi    yi    su    chi   he    shen  he    xu    zhen  zhu   zheng gou   zi    zi    zhan  gu    fu    jian  die   ling  di    yang  li    nao   pan   zhou  gan   shi   ju    ao    zha   tuo   yi    qu    zhao  ping  bi    xiong chu   ba    da    zu    tao   zhu   ci    zhe   yong  xu    xun   yi    huang he    shi   cha   jiao  shi   hen   cha   gou   gui   quan  hui   jie   hua   gai   xiang hui   shen  chou  tong  mi    zhan  ming  e     hui   yan   xiong gua   er    beng  tiao  chi   lei   zhu   kuang kua   wu    yu    teng  ji    zhi   ren   su    lang  e     kuang e^    shi   ting  dan   bei   chan  you   heng  qiao  qin   shua  an    yu    xiao  cheng jie   xian  wu    wu    gao   song  pu    hui   jing  shuo  zhen  shuo  du    none  chang shui  jie   ke    qu    cong  xiao  sui   wang  xuan  fei   chi   ta    yi    na    yin   diao  pi    chuo  chan  chen  zhun  ji    qi    tan   chui  wei   ju    qing  jian  zheng ze    zou   qian  zhuo  liang jian  zhu   hao   lun   shen  biao  huai  pian  yu    die   xu    pian  shi   xuan  shi   hun   hua   e     zhong di    xie   fu    pu    ting  jian  qi    yu    zi    chuan xi    hui   yin   an    xian  nan   chen  feng  zhu   yang  yan   heng  xuan  ge    nuo   qi    mou   ye    wei   none  teng  zou   shan  jian  bo    none  huang huo   ge    ying  mi    xiao  mi    xi    qiang chen  nu:e  si    su    bang  chi   qian  shi   jiang yuan  xie   xue   tao   yao   yao   hu    yu    biao  cong  qing  li    mo    mo    shang zhe   miu   jian  ze    zha   lian  lou   can   ou    guan  xi    zhuo  ao    ao    jin   zhe   yi    hu    jiang man   chao  han   hua   chan  xu    zeng  se    xi    she   dui   zheng nao   lan   e     ying  jue   ji    zun   jiao  bo    hui   zhuan wu    jian  zha   shi   qiao  tan   zen   pu    sheng xuan  zao   zhan  dang  sui   qian  ji    jiao  jing  lian  nou   yi    ai    zhan  pi    hui   hua   yi    yi    shan  rang  nou   qian  zhui  ta    hu    zhou  hao   ni    ying  jian  yu    jian  hui   du    zhe   xuan  zan   lei   shen  wei   chan  li    yi    bian  zhe   yan   e     chou  wei   chou  yao   chan  rang  yin   lan   chen  huo   zhe   huan  zan   yi    dang  zhan  yan   du    yan   ji    ding  fu    ren   ji    jie   hong  tao   rang  shan  qi    tuo   xun   yi    xun   ji    ren   jiang hui   ou    ju    ya    ne    xu    e     lun   xiong song  feng  she   fang  jue   zheng gu    he    ping  zu    shi   xiong zha   su    zhen  di    zhou  ci    qu    zhao  bi    yi    yi    kuang lei   shi   gua   shi   jie   hui   cheng zhu   shen  hua   dan   gou   quan  gui   xun   yi    zheng gai   xiang cha   hun   xu    zhou  jie   wu    yu    qiao  wu    gao   you   hui   kuang shuo  song  ei    qing  zhu   zou   nuo   du    zhuo  fei   ke    wei   yu    shei  shen  diao  chan  liang zhun  sui   tan   shen  yi    mou   chen  die   huang jian  xie   xue   ye    wei   e     yu    xuan  chan  zi    an    yan   di    mi    pian  xu    mo    dang  su    xie   yao   bang  shi   qian  mi    jin   man   zhe   jian  miu   tan   jian  qiao  lan   pu    jue   yan   qian  zhan  chen  gu    qian  hong  ya    jue   hong  han   hong  qi    xi    huo   liao  han   du    long  dou   jiang qi    chi   feng  deng  wan   bi    shu   xian  feng  zhi   zhi   yan   yan   shi   chu   hui   tun   yi    tun   yi    jian  ba    hou   e     cu    xiang huan  jian  ken   gai   qu    fu    xi    bin   hao   yu    zhu   jia   fen   xi    hu    wen   huan  bin   di    zong  fen   yi    zhi   bao   chai  han   pi    na    pi    gou   duo   you   diao  mo    si    xiu   huan  kun   he    he    mo    an    mao   li    ni    bi    yu    jia   tuan  mao   pi    xi    e     ju\")\n                .append(\"    mo    chu   tan   huan  qu    bei   zhen  yuan  fu    cai   gong  te    yi    hang  wan   pin   huo   fan   tan   guan  ze    zhi   er    zhu   shi   bi    zi    er    gui   pian  bian  mai   dai   sheng kuang fei   tie   yi    chi   mao   he    bi    lu    lin   hui   gai   pian  zi    jia   xu    zei   jiao  gai   zang  jian  ying  xun   zhen  she   bin   bin   qiu   she   chuan zang  zhou  lai   zan   si    chen  shang tian  pei   geng  xian  mai   jian  sui   fu    dan   cong  cong  zhi   ji    zhang du    jin   xiong shun  yun   bao   zai   lai   feng  cang  ji    sheng ai    zhuan fu    gou   sai   ze    liao  wei   bai   chen  zhuan zhi   zhui  biao  yun   zeng  tan   zan   yan   none  shan  wan   ying  jin   gan   xian  zang  bi    du    shu   yan   none  xuan  long  gan   zang  bei   zhen  fu    yuan  gong  cai   ze    xian  bai   zhang huo   zhi   fan   tan   pin   bian  gou   zhu   guan  er    jian  bi    shi   tie   gui   kuang dai   mao   fei   he    yi    zei   zhi   jia   hui   zi    lin   lu    zang  zi    gai   jin   qiu   zhen  lai   she   fu    du    ji    shu   shang ci    bi    zhou  geng  pei   dan   lai   feng  zhui  fu    zhuan sai   ze    yan   zan   yun   zeng  shan  ying  gan   chi   xi    she   nan   xiong xi    cheng he    cheng zhe   xia   tang  zou   zou   li    jiu   fu    zhao  gan   qi    shan  qiong qin   xian  ci    jue   qin   chi   ci    chen  chen  die   ju    chao  di    se    zhan  zhu   yue   qu    jie   chi   chu   gua   xue   zi    tiao  duo   lie   gan   suo   cu    xi    zhao  su    yin   ju    jian  que   tang  chuo  cui   lu    qu    dang  qiu   zi    ti    qu    chi   huang qiao  qiao  yao   zao   yue   none  zan   zan   zu    pa    bao   ku    he    dun   jue   fu    chen  jian  fang  zhi   ta    yue   pa    qi    yue   qiang tuo   tai   yi    nian  ling  mei   ba    die   ku    tuo   jia   ci    pao   qia   zhu   ju    die   zhi   fu    pan   ju    shan  bo    ni    ju    li    gen   yi    ji    dai   xian  jiao  duo   chu   quan  kua   zhuai gui   qiong kui   xiang chi   lu    beng  zhi   jia   tiao  cai   jian  da    qiao  bi    xian  duo   ji    ju    ji    shu   tu    chu   xing  nie   xiao  bo    xue   qun   mou   shu   liang yong  jiao  chou  xiao  none  ta    jian  qi    wo    wei   chuo  jie   ji    nie   ju    ju    lun   lu    leng  huai  ju    chi   wan   quan  ti    bo    zu    qie   qi    cu    zong  cai   zong  pan   zhi   zheng dian  zhi   yu    duo   dun   chun  yong  zhong di    zha   chen  chuai jian  gua   tang  ju    fu    zu    die   pian  rou   nuo   ti    cha   tui   jian  dao   cuo   xi    ta    qiang zhan  dian  ti    ji    nie   pan   liu   zhan  bi    chong lu    liao  cu    tang  dai   su    xi    kui   ji    zhi   qiang di    man   zong  lian  beng  zao   nian  bie   tui   ju    deng  ceng  xian  fan   chu   zhong dun   bo    cu    zu    jue   jue   lin   ta    qiao  qiao  pu    liao  dun   cuan  kuang zao   ta    bi    bi    zhu   ju    chu   qiao  dun   chou  ji    wu    yue   nian  lin   lie   zhi   li    zhi   chan  chu   duan  wei   long  lin   xian  wei   zuan  lan   xie   rang  xie   nie   ta    qu    jie   cuan  zuan  xi    kui   jue   lin   shen  gong  dan   none  qu    ti    duo   duo   gong  lang  none  luo   ai    ji    ju    tang  none  none  yan   none  kang  qu    lou   lao   duo   zhi   none  ti    dao   none  yu    che   ya    gui   jun   wei   yue   xin   di    xuan  fan   ren   shan  qiang shu   tun   chen  dai   e     na    qi    mao   ruan  ren   qian  zhuan hong  hu    qu    huang di    ling  dai   ao    zhen  fan   kuang ang   peng  bei   gu    gu    pao   zhu   rong  e     ba    zhou  zhi   yao   ke    yi    qing  shi   ping  er    qiong ju    jiao  guang lu    kai   quan  zhou  zai   zhi   ju    liang yu    shao  you   huan  yun   zhe   wan   fu    qing  zhou  ni    ling  zhe   zhan  liang zi    hui   wang  chuo  guo   kan   yi    peng  qian  gun   nian  ping  guan  bei   lun   pai   liang ruan  rou   ji    yang  xian  chuan cou   chun  ge    you   hong  shu   fu    zi    fu    wen   ben   zhan  yu    wen   tao   gu    zhen  xia   yuan  lu    jiu   chao  zhuan wei   hun   none  che   jiao  zhan  \")\n                .append(\"pu    lao   fen   fan   lin   ge    se    kan   huan  yi    ji    dui   er    yu    xian  hong  lei   pei   li    li    lu    lin   che   ya    gui   xuan  dai   ren   zhuan e     lun   ruan  hong  gu    ke    lu    zhou  zhi   yi    hu    zhen  li    yao   qing  shi   zai   zhi   jiao  zhou  quan  lu    jiao  zhe   fu    liang nian  bei   hui   gun   wang  liang chuo  zi    cou   fu    ji    wen   shu   pei   yuan  xia   zhan  lu    zhe   lin   xin   gu    ci    ci    pi    zui   bian  la    la    ci    xue   ban   bian  bian  bian  none  bian  ban   ci    bian  bian  chen  ru    nong  nong  zhen  chuo  chuo  none  reng  bian  bian  none  none  liao  da    chan  gan   qian  yu    yu    qi    xun   yi    guo   mai   qi    za    wang  none  zhun  ying  ti    yun   jin   hang  ya    fan   wu    ta    e     hai   zhei  none  jin   yuan  wei   lian  chi   che   ni    tiao  zhi   yi    jiong jia   chen  dai   er    di    po    wang  die   ze    tao   shu   tuo   none  jing  hui   tong  you   mi    beng  ji    nai   yi    jie   zhui  lie   xun   tui   song  shi   tao   pang  hou   ni    dun   jiong xuan  xun   bu    you   xiao  qiu   tou   zhu   qiu   di    di    tu    jing  ti    dou   yi    zhe   tong  guang wu    shi   cheng su    zao   qun   feng  lian  suo   hui   li    none  zui   ben   cuo   jue   beng  huan  dai   lu    you   zhou  jin   yu    chuo  kui   wei   ti    yi    da    yuan  luo   bi    nuo   yu    dang  sui   dun   sui   yan   chuan chi   ti    yu    shi   zhen  you   yun   e     bian  guo   e     xia   huang qiu   dao   da    wei   none  yi    gou   yao   chu   liu   xun   ta    di    chi   yuan  su    ta    qian  none  yao   guan  zhang ao    shi   ce    su    su    zao   zhe   dun   zhi   lou   chi   cuo   lin   zun   rao   qian  xuan  yu    yi    wu    liao  ju    shi   bi    yao   mai   xie   sui   huan  zhan  deng  er    miao  bian  bian  la    li    yuan  you   luo   li    yi    ting  deng  qi    yong  shan  han   yu    mang  ru    qiong none  kuang fu    kang  bin   fang  xing  nei   none  shen  bang  yuan  cun   huo   xie   bang  wu    ju    you   han   tai   qiu   bi    pi    bing  shao  bei   wa    di    zou   ye    lin   kuang gui   zhu   shi   ku    yu    gai   he    qie   zhi   ji    xun   hou   xing  jiao  xi    gui   nuo   lang  jia   kuai  zheng lang  yun   yan   cheng dou   xi    lu:   fu    wu    fu    gao   hao   lang  jia   geng  jun   ying  bo    xi    bei   li    yun   bu    xiao  qi    pi    qing  guo   none  tan   zou   ping  lai   ni    chen  you   bu    xiang dan   ju    yong  qiao  yi    dou   yan   mei   ruo   bei   e     yu    juan  yu    yun   hou   kui   xiang xiang sou   tang  ming  xi    ru    chu   zi    zou   ju    wu    xiang yun   hao   yong  bi    mao   chao  fu    liao  yin   zhuan hu    qiao  yan   zhang fan   wu    xu    deng  bi    xin   bi    ceng  wei   zheng mao   shan  lin   po    dan   meng  ye    cao   kuai  feng  meng  zou   kuang lian  zan   chan  you   qi    yan   chan  cuo   ling  huan  xi    feng  zan   li    you   ding  qiu   zhuo  pei   zhou  yi    gan   yu    jiu   yan   zui   mao   dan   xu    tou   zhen  fen   none  none  yun   tai   tian  qia   tuo   zuo   han   gu    su    fa    chou  dai   ming  lao   chuo  chou  you   tong  zhi   xian  jiang cheng yin   tu    jiao  mei   ku    suan  lei   pu    zui   hai   yan   shi   niang wei   lu    lan   yan   tao   pei   zhan  chun  tan   zui   chuo  cu    kun   ti    xian  du    hu    xu    xing  tan   qiu   chun  yun   fa    ke    sou   mi    quan  chou  cuo   yun   yong  ang   zha   hai   tang  jiang piao  lao   yu    li    zao   lao   yi    jiang bu    jiao  xi    tan   fa    nong  yi    li    ju    yan   yi    niang ru    xun   chou  yan   ling  mi    mi    niang xin   jiao  shi   mi    yan   bian  cai   shi   you   shi   shi   li    zhong ye    liang li    jin   jin   ga    yi    liao  dao   zhao  ding  li    qiu   he    fu    zhen  zhi   ba    luan  fu    nai   diao  shan  qiao  kou   chuan zi    fan   yu    hua   han   gong  qi    mang  jian  di    si    xi    yi    chai  ta    tu    xi    nu:   qian  none  jian  pi    ye    yin   ba    fang  chen  jian  tou   yue   yan   fu    bu  \")\n                .append(\"  na    xin   e     jue   dun   gou   yin   qian  ban   ji    ren   chao  niu   fen   yun   yi    qin   pi    guo   hong  yin   jun   shi   yi    zhong nie   gai   ri    huo   tai   kang  none  lu    none  none  duo   zi    ni    tu    shi   min   gu    ke    ling  bing  yi    gu    ba    pi    yu    si    zuo   bu    you   dian  jia   zhen  shi   shi   tie   ju    zhan  ta    she   xuan  zhao  bao   he    bi    sheng chu   shi   bo    zhu   chi   za    po    tong  qian  fu    zhai  liu   qian  fu    li    yue   pi    yang  ban   bo    jie   gou   shu   zheng mu    ni    xi    di    jia   mu    tan   shen  yi    si    kuang ka    bei   jian  tong  xing  hong  jiao  chi   er    ge    bing  shi   mou   jia   yin   jun   zhou  chong shang tong  mo    lei   ji    yu    xu    ren   cun   zhi   qiong shan  chi   xian  xing  quan  pi    yi    zhu   hou   ming  kua   yao   xian  xian  xiu   jun   cha   lao   ji    yong  ru    mi    yi    yin   guang an    diu   you   se    kao   qian  luan  none  ai    diao  han   rui   shi   keng  qiu   xiao  zhe   xiu   zang  ti    cuo   gua   gong  zhong dou   lu:   mei   lang  wan   xin   yun   bei   wu    su    yu    chan  ting  bo    han   jia   hong  cuan  feng  chan  wan   zhi   si    xuan  wu    wu    tiao  gong  zhuo  lu:e  xing  qin   shen  han   none  ye    chu   zeng  ju    xian  e     mang  pu    li    shi   rui   cheng gao   li    te    none  zhu   none  tu    liu   zui   ju    chang yuan  jian  gang  diao  tao   chang lun   guo   ling  bei   lu    li    qing  pei   juan  min   zui   peng  an    pi    xian  ya    zhui  lei   a     kong  ta    kun   du    wei   chui  zi    zheng ben   nie   cong  chun  tan   ding  qi    qian  zhuo  qi    yu    jin   guan  mao   chang dian  xi    lian  tao   gu    cuo   shu   zhen  lu    meng  lu    hua   biao  ga    lai   ken   zhui  none  nai   wan   zan   none  de    xian  none  huo   liang none  men   kai   ying  di    lian  guo   xian  du    tu    wei   cong  fu    rou   ji    e     rou   chen  ti    zha   hong  yang  duan  xia   yu    keng  xing  huang wei   fu    zhao  cha   qie   she   hong  kui   nuo   mou   qiao  qiao  hou   zhen  huo   huan  ye    min   jian  duan  jian  si    kui   hu    xuan  zang  jie   zhen  bian  zhong zi    xiu   ye    mei   pai   ai    jie   none  mei   cha   ta    bang  xia   lian  suo   xi    liu   zu    ye    nou   weng  rong  tang  suo   qiang ge    shuo  chui  bo    pan   ta    bi    sang  gang  zi    wu    ying  huang tiao  liu   kai   sun   sha   sou   wan   hao   zhen  zhen  luo   yi    yuan  tang  nie   xi    jia   ge    ma    juan  rong  none  suo   none  none  none  na    lu    suo   kou   zu    tuan  xiu   guan  xuan  lian  shou  ao    man   mo    luo   bi    wei   liu   di    qiao  huo   yin   lu    ao    keng  qiang cui   qi    chang tang  man   yong  chan  feng  jing  biao  shu   lou   xiu   cong  long  zan   jian  cao   li    xia   xi    kang  none  beng  none  none  zheng lu    hua   ji    pu    hui   qiang po    lin   suo   xiu   san   cheng kui   san   liu   nao   huang pie   sui   fan   qiao  chuan yang  tang  xiang jue   jiao  zun   liao  jie   lao   dui   tan   zan   ji    jian  zhong deng  lou   ying  dui   jue   nou   ti    pu    tie   none  none  ding  shan  kai   jian  fei   sui   lu    juan  hui   yu    lian  zhuo  qiao  qian  zhuo  lei   bi    tie   huan  ye    duo   guo   dang  ju    fen   da    bei   yi    ai    dang  xun   diao  zhu   heng  zhui  ji    nie   ta    huo   qing  bin   ying  kui   ning  xu    jian  jian  qiang cha   zhi   mie   li    lei   ji    zuan  kuang shang peng  la    du    shuo  chuo  lu:   biao  bao   lu    none  none  long  e     lu    xin   jian  lan   bo    jian  yao   chan  xiang jian  xi    guan  cang  nie   lei   cuan  qu    pan   luo   zuan  luan  zao   nie   jue   tang  shu   lan   jin   ga    yi    zhen  ding  zhao  po    liao  tu    qian  chuan shan  sa    fan   diao  men   nu:   yang  chai  xing  gai   bu    tai   ju    dun   chao  zhong na    bei   gang  ban   qian  yao   qin   jun   wu    gou   kang  fang  huo   tou   niu   ba    yu    qian  zheng qian  gu    bo    ke    po    bu    bo    yue   zuan  mu    tan   jia   dian  you   ti\")\n                .append(\"e   bo    ling  shuo  qian  mao   bao   shi   xuan  tuo   bi    ni    pi    duo   xing  kao   lao   er    mang  ya    you   cheng jia   ye    nao   zhi   dang  tong  lu:   diao  yin   kai   zha   zhu   xian  ting  diu   xian  hua   quan  sha   ha    yao   ge    ming  zheng se    jiao  yi    chan  chong tang  an    yin   ru    zhu   lao   pu    wu    lai   te    lian  keng  xiao  suo   li    zeng  chu   guo   gao   e     xiu   cuo   lu:e  feng  xin   liu   kai   jian  rui   ti    lang  qin   ju    a     qing  zhe   nuo   cuo   mao   ben   qi    de    ke    kun   chang xi    gu    luo   chui  zhui  jin   zhi   xian  juan  huo   pei   tan   ding  jian  ju    meng  zi    qie   ying  kai   qiang si    e     cha   qiao  zhong duan  sou   huang huan  ai    du    mei   lou   zi    fei   mei   mo    zhen  bo    ge    nie   tang  juan  nie   na    liu   hao   bang  yi    jia   bin   rong  biao  tang  man   luo   beng  yong  jing  di    zu    xuan  liu   chan  jue   liao  pu    lu    dun   lan   pu    cuan  qiang deng  huo   lei   huan  zhuo  lian  yi    cha   biao  la    chan  xiang chang chang jiu   ao    die   qu    liao  mi    zhang men   ma    shuan shan  huo   men   yan   bi    han   bi    none  kai   kang  beng  hong  run   san   xian  xian  jian  min   xia   min   dou   zha   nao   none  peng  ke    ling  bian  bi    run   he    guan  ge    he    fa    chu   hong  gui   min   none  kun   lang  lu:   ting  sha   yan   yue   yue   chan  qu    lin   chang shai  kun   yan   min   yan   e     hun   yu    wen   xiang none  xiang qu    yao   wen   ban   an    wei   yin   kuo   que   lan   du    none  none  tian  nie   da    kai   he    que   chuangguan  dou   qi    kui   tang  guan  piao  kan   xi    hui   chan  pi    dang  huan  ta    wen   none  men   shuan shan  yan   han   bi    wen   chuangrun   wei   xian  hong  jian  min   kang  men   zha   nao   gui   wen   ta    min   lu:   kai   fa    ge    he    kun   jiu   yue   lang  du    yu    yan   chang xi    wen   hun   yan   yan   chan  lan   qu    hui   kuo   que   he    tian  da    que   kan   huan  fu    fu    le    dui   xin   qian  wu    yi    tuo   yin   yang  dou   e     sheng ban   pei   keng  yun   ruan  zhi   pi    jing  fang  yang  yin   zhen  jie   cheng e     qu    di    zu    zuo   dian  ling  a     tuo   tuo   po    bing  fu    ji    lu    long  chen  xing  duo   lou   mo    jiang shu   duo   xian  er    gui   wu    gai   shan  jun   qiao  xing  chun  fu    bi    shan  shan  sheng zhi   pu    dou   yuan  zhen  chu   xian  zhi   nie   yun   xian  pei   pei   zou   yi    dui   lun   yin   ju    chui  chen  pi    ling  tao   xian  lu    none  xian  yin   zhu   yang  reng  shan  chong yan   yin   yu    ti    yu    long  wei   wei   nie   dui   sui   an    huang jie   sui   yin   gai   yan   hui   ge    yun   wu    wei   ai    xi    tang  ji    zhang dao   ao    xi    yin   sa    rao   lin   tui   deng  pi    sui   sui   yu    xian  fen   ni    er    ji    dao   xi    yin   zhi   hui   long  xi    li    li    li    zhui  he    zhi   sun   juan  nan   yi    que   yan   qin   ya    xiong ya    ji    gu    huan  zhi   gou   jun   ci    yong  ju    chu   hu    za    luo   yu    chou  diao  sui   han   huo   shuangguan  chu   za    yong  ji    sui   chou  liu   li    nan   xue   za    ji    ji    yu    yu    xue   na    fou   se    mu    wen   fen   pang  yun   li    li    yang  ling  lei   an    bao   meng  dian  dang  hang  wu    zhao  xu    ji    mu    chen  xiao  zha   ting  zhen  pei   mei   ling  qi    chou  huo   sha   fei   weng  zhan  ying  ni    chou  tun   lin   none  dong  ying  wu    ling  shuangling  xia   hong  yin   mai   mo    yun   liu   meng  bin   wu    wei   kuo   yin   xi    yi    ai    dan   deng  xian  yu    lu    long  dai   ji    pang  yang  ba    pi    wei   none  xi    ji    mai   meng  meng  lei   li    huo   ai    fei   dai   long  ling  ai    feng  li    bao   none  he    he    bing  qing  qing  jing  qi    zhen  jing  cheng qing  jing  jing  dian  jing  tian  fei   fei   kao   mi    mian  mian  pao   ye    tian  hui   ye    ge    ding  ren   jian  ren   di    du    wu    ren   qin   jin   xue   niu   ba    yin   sa    ren   \")\n                .append(\"mo    zu    da    ban   yi    yao   tao   bei   jia   hong  pao   yang  mo    yin   jia   tao   ji    xie   an    an    hen   gong  gong  da    qiao  ting  man   ying  sui   tiao  qiao  xuan  kong  beng  ta    zhang bing  kuo   ju    la    xie   rou   bang  yi    qiu   qiu   he    xiao  mu    ju    jian  bian  di    jian  none  tao   gou   ta    bei   xie   pan   ge    bi    kuo   tang  lou   gui   qiao  xue   ji    jian  jiang chan  da    huo   xian  qian  du    wa    jian  lan   wei   ren   fu    mei   juan  ge    wei   qiao  han   chang none  rou   xun   she   wei   ge    bei   tao   gou   yun   gao   bi    wei   hui   shu   wa    du    wei   ren   fu    han   wei   yun   tao   jiu   jiu   xian  xie   xian  ji    yin   za    yun   shao  luo   peng  huang ying  yun   peng  yin   yin   xiang hu    ye    ding  qing  pan   xiang shun  han   xu    yi    xu    gu    song  kui   qi    hang  yu    wan   ban   dun   di    dan   pan   po    ling  cheng jing  lei   he    qiao  e     e     wei   jie   gua   shen  yi    yi    ke    dui   pian  ping  lei   fu    jia   tou   hui   kui   jia   le    ting  cheng ying  jun   hu    han   jing  tui   tui   pin   lai   tui   zi    zi    chui  ding  lai   yan   han   qian  ke    cui   jiong qin   yi    sai   ti    e     e     yan   hun   kan   yong  zhuan yan   xian  xin   yi    yuan  sang  dian  dian  jiang ku    lei   liao  piao  yi    man   qi    yao   hao   qiao  gu    xun   qian  hui   zhan  ru    hong  bin   xian  pin   lu    lan   nie   quan  ye    ding  qing  han   xiang shun  xu    xu    wan   gu    dun   qi    ban   song  hang  yu    lu    ling  po    jing  jie   jia   ting  he    ying  jiong ke    yi    pin   hui   tui   han   ying  ying  ke    ti    yong  e     zhuan yan   e     nie   man   dian  sang  hao   lei   zhan  ru    pin   quan  feng  biao  none  fu    xia   zhan  biao  sa    fa    tai   lie   gua   xuan  shao  ju    biao  si    wei   yang  yao   sou   kai   sao   fan   liu   xi    liao  piao  piao  liu   biao  biao  biao  liao  none  se    feng  biao  feng  yang  zhan  biao  sa    ju    si    sou   yao   liu   piao  biao  biao  fei   fan   fei   fei   shi   shi   can   ji    ding  si    tuo   jian  sun   xiang tun   ren   yu    juan  chi   yin   fan   fan   sun   yin   zhu   yi    zhai  bi    jie   tao   liu   ci    tie   si    bao   shi   duo   hai   ren   tian  jiao  jia   bing  yao   tong  ci    xiang yang  yang  er    yan   le    yi    can   bo    nei   e     bu    jun   dou   su    yu    shi   yao   hun   guo   shi   jian  zhui  bing  xian  bu    ye    tan   fei   zhang wei   guan  e     nuan  hun   hu    huang tie   hui   jian  hou   he    xing  fen   wei   gu    cha   song  tang  bo    gao   xi    kui   liu   sou   tao   ye    yun   mo    tang  man   bi    yu    xiu   jin   san   kui   zhuan shan  chi   dan   yi    ji    rao   cheng yong  tao   hui   xiang zhan  fen   hai   meng  yan   mo    chan  xiang luo   zuan  nang  shi   ding  ji    tuo   xing  tun   xi    ren   yu    chi   fan   yin   jian  shi   bao   si    duo   yi    er    rao   xiang he    le    jiao  xi    bing  bo    dou   e     yu    nei   jun   guo   hun   xian  guan  cha   kui   gu    sou   chan  ye    mo    bo    liu   xiu   jin   man   san   zhuan nang  shou  kui   guo   xiang fen   ba    ni    bi    bo    tu    han   fei   jian  yan   ai    fu    xian  wen   xin   fen   bin   xing  ma    yu    feng  han   di    tuo   tuo   chi   xun   zhu   zhi   pei   xin   ri    sa    yin   wen   zhi   dan   lu:   you   bo    bao   kuai  tuo   yi    qu    wen   qu    jiong bo    zhao  yuan  peng  zhou  ju    zhu   nu    ju    pi    zang  jia   ling  zhen  tai   fu    yang  shi   bi    tuo   tuo   si    liu   ma    pian  tao   zhi   rong  teng  dong  xun   quan  shen  jiong er    hai   bo    none  yin   luo   none  dan   xie   liu   ju    song  qin   mang  liang han   tu    xuan  tui   jun   e     cheng xing  ai    lu    zhui  zhou  she   pian  kun   tao   lai   zong  ke    qi    qi    yan   fei   sao   yan   jie   yao   wu    pian  cong  pian  qian  fei   huang jian  huo   yu    ti    quan  xia   zong  kui   rou   si    gua   tuo   kui   sou   qian  cheng zhi   liu   pang  teng  xi    cao \")\n                .append(\"  du    yan   yuan  zou   sao   shan  li    zhi   shuanglu    xi    luo   zhang mo    ao    can   piao  cong  qu    bi    zhi   yu    xu    hua   bo    su    xiao  lin   zhan  dun   liu   tuo   zeng  tan   jiao  tie   yan   luo   zhan  jing  yi    ye    tuo   bin   zou   yan   peng  lu:   teng  xiang ji    shuangju    xi    huan  li    biao  ma    yu    tuo   xun   chi   qu    ri    bo    lu:   zang  shi   si    fu    ju    zou   zhu   tuo   nu    jia   yi    tai   xiao  ma    yin   jiao  hua   luo   hai   pian  biao  li    cheng yan   xing  qin   jun   qi    qi    ke    zhui  zong  su    can   pian  zhi   kui   sao   wu    ao    liu   qian  shan  piao  luo   cong  zhan  zhou  ji    shuangxiang gu    wei   wei   wei   yu    gan   yi    ang   tou   jie   bo    bi    ci    ti    di    ku    hai   qiao  hou   kua   ge    tui   geng  pian  bi    ke    qia   yu    sui   lou   bo    xiao  bang  bo    cuo   kuan  bin   mo    liao  lou   nao   du    zang  sui   ti    bin   kuan  lu    gao   gao   qiao  kao   qiao  lao   zao   biao  kun   kun   ti    fang  xiu   ran   mao   dan   kun   bin   fa    tiao  pi    zi    fa    ran   ti    pao   pi    mao   fu    er    rong  qu    none  xiu   gua   ji    peng  zhua  shao  sha   ti    li    bin   zong  ti    peng  song  zheng quan  zong  shun  jian  duo   hu    la    jiu   qi    lian  zhen  bin   peng  mo    san   man   man   seng  xu    lie   qian  qian  nong  huan  kuai  ning  bin   lie   rang  dou   dou   nao   hong  xi    dou   kan   dou   dou   jiu   chang yu    yu    li    juan  fu    qian  gui   zong  liu   gui   shang yu    gui   mei   ji    qi    jie   kui   hun   ba    po    mei   xu    yan   xiao  liang yu    tui   qi    wang  liang wei   jian  chi   piao  bi    mo    ji    xu    chou  yan   zhan  yu    dao   ren   ji    ba    hong  tuo   diao  ji    yu    e     que   sha   hang  tun   mo    gai   shen  fan   yuan  pi    lu    wen   hu    lu    za    fang  fen   na    you   none  none  he    xia   qu    han   pi    ling  tuo   ba    qiu   ping  fu    bi    ji    wei   ju    diao  ba    you   gun   pi    nian  xing  tai   bao   fu    zha   ju    gu    none  none  none  ta    jie   shua  hou   xiang er    an    wei   tiao  zhu   yin   lie   luo   tong  yi    qi    bing  wei   jiao  pu    gui   xian  ge    hui   none  none  kao   none  duo   jun   ti    mian  shao  za    suo   qin   yu    nei   zhe   gun   geng  none  wu    qiu   ting  fu    huan  chou  li    sha   sha   gao   meng  none  none  none  none  yong  ni    zi    qi    qing  xiang nei   chun  ji    diao  qie   gu    zhou  dong  lai   fei   ni    yi    kun   lu    jiu   chang jing  lun   ling  zou   li    meng  zong  zhi   nian  none  none  none  shi   sao   hun   ti    hou   xing  ju    la    zong  ji    bian  bian  huan  quan  ji    wei   wei   yu    chun  rou   die   huang lian  yan   qiu   qiu   jian  bi    e     yang  fu    sai   jian  ha    tuo   hu    none  ruo   none  wen   jian  hao   wu    pang  sao   liu   ma    shi   shi   guan  zi    teng  ta    yao   ge    rong  qian  qi    wen   ruo   none  lian  ao    le    hui   min   ji    tiao  qu    jian  sao   man   xi    qiu   biao  ji    ji    zhu   jiang qiu   zhuan yong  zhang kang  xue   bie   jue   qu    xiang bo    jiao  xun   su    huang zun   shan  shan  fan   gui   lin   xun   miao  xi    none  xiang fen   guan  hou   kuai  zei   sao   zhan  gan   gui   sheng li    chang none  none  ai    ru    ji    xu    huo   none  li    lie   li    mie   zhen  xiang e     lu    guan  li    xian  yu    dao   ji    you   tun   lu    fang  ba    ke    ba    ping  nian  lu    you   zha   fu    ba    bao   hou   pi    tai   gui   jie   kao   wei   er    tong  zei   hou   kuai  ji    jiao  xian  zha   xiang xun   geng  li    lian  jian  li    shi   tiao  gun   sha   huan  jun   ji    yong  qing  ling  qi    zou   fei   kun   chang gu    ni    nian  diao  jing  shen  shi   zi    fen   die   bi    chang ti    wen   wei   sai   e     qiu   fu    huang quan  jiang bian  sao   ao    qi    ta    guan  yao   pang  jian  le    biao  xue   bie   man   min   yong  wei   xi    gui   shan  lin   zun   hu    gan   li    shan  guan  niao  yi    fu    li    jiu   bu    ya\")\n                .append(\"n   fu    diao  ji    feng  none  gan   shi   feng  ming  bao   yuan  zhi   hu    qian  fu    fen   wen   jian  shi   yu    fou   yiao  ju    jue   pi    huan  zhen  bao   yan   ya    zheng fang  feng  wen   ou    te    jia   nu    ling  mie   fu    tuo   wen   li    bian  zhi   ge    yuan  zi    qu    xiao  chi   dan   ju    you   gu    zhong yu    yang  rong  ya    zhi   yu    none  ying  zhui  wu    er    gua   ai    zhi   yan   heng  jiao  ji    lie   zhu   ren   ti    hong  luo   ru    mou   ge    ren   jiao  xiu   zhou  chi   luo   none  none  none  luan  jia   ji    yu    huan  tuo   bu    wu    juan  yu    bo    xun   xun   bi    xi    jun   ju    tu    jing  ti    e     e     kuang hu    wu    shen  la    none  none  lu    bing  shu   fu    an    zhao  peng  qin   qian  bei   diao  lu    que   jian  ju    tu    ya    yuan  qi    li    ye    zhui  kong  duo   kun   sheng qi    jing  ni    e     jing  zi    lai   dong  qi    chun  geng  ju    qu    none  none  ji    shu   none  chi   miao  rou   fu    qiu   ti    hu    ti    e     jie   mao   fu    chun  tu    yan   he    yuan  pian  yun   mei   hu    ying  dun   mu    ju    none  cang  fang  ge    ying  yuan  xuan  weng  shi   he    chu   tang  xia   ruo   liu   ji    gu    jian  zhun  han   zi    ci    yi    yao   yan   ji    li    tian  kou   ti    ti    ni    tu    ma    jiao  liu   zhen  chen  li    zhuan zhe   ao    yao   yi    ou    chi   zhi   liao  rong  lou   bi    shuangzhuo  yu    wu    jue   yin   tan   si    jiao  yi    hua   bi    ying  su    huang fan   jiao  liao  yan   kao   jiu   xian  xian  tu    mai   zun   yu    ying  lu    tuan  xian  xue   yi    pi    shu   luo   qi    yi    ji    zhe   yu    zhan  ye    yang  pi    ning  hu    mi    ying  meng  di    yue   yu    lei   bo    lu    he    long  shuangyue   ying  guan  qu    li    luan  niao  jiu   ji    yuan  ming  shi   ou    ya    cang  bao   zhen  gu    dong  lu    ya    xiao  yang  ling  chi   qu    yuan  xue   tuo   si    zhi   er    gua   xiu   heng  zhou  ge    luan  hong  wu    bo    li    juan  hu    e     yu    xian  ti    wu    que   miao  an    kun   bei   peng  qian  chun  geng  yuan  su    hu    he    e     gu    qiu   ci    mei   wu    yi    yao   weng  liu   ji    yi    jian  he    yi    ying  zhe   liu   liao  jiao  jiu   yu    lu    huan  zhan  ying  hu    meng  guan  shuanglu    jin   ling  jian  xian  cuo   jian  jian  yan   cuo   lu    you   cu    ji    biao  cu    pao   zhu   jun   zhu   jian  mi    mi    wu    liu   chen  jun   lin   ni    qi    lu    jiu   jun   jing  li    xiang yan   jia   mi    li    she   zhang lin   jing  qi    ling  yan   cu    mai   mai   ge    chao  fu    mian  mian  fu    pao   qu    qu    mou   fu    xian  lai   qu    mian  chi   feng  fu    qu    mian  ma    ma    mo    hui   none  zou   nen   fen   huang huang jin   guang tian  tou   hong  xi    kuang hong  shu   li    nian  chi   hei   hei   yi    qian  zhen  xi    tuan  mo    mo    qian  dai   chu   you   dian  yi    xia   yan   qu    mei   yan   qing  yu    li    dang  du    can   yin   an    yan   tan   an    zhen  dai   can   yi    mei   dan   yan   du    lu    zhi   fen   fu    fu    min   min   yuan  cu    qu    chao  wa    zhu   zhi   mang  ao    bie   tuo   bi    yuan  chao  tuo   ding  mi    nai   ding  zi    gu    gu    dong  fen   tao   yuan  pi    chang gao   qi    yuan  tang  teng  shu   shu   fen   fei   wen   ba    diao  tuo   tong  qu    sheng shi   you   shi   ting  wu    nian  jing  hun   ju    yan   tu    si    xi    xian  yan   lei   bi    yao   yan   han   hui   wu    hou   xi    ge    zha   xiu   weng  zha   nong  nang  qi    zhai  ji    zi    ji    ji    qi    ji    chi   chen  chen  he    ya    ken   xie   bao   ze    shi   zi    chi   nian  ju    tiao  ling  ling  chu   quan  xie   yin   nie   jiu   nie   chuo  kun   yu    chu   yi    ni    cuo   chuo  qu    nian  xian  yu    e     wo    yi    chi   zou   dian  chu   jin   ya    chi   chen  he    yin   ju    ling  bao   tiao  zi    yin   yu    chuo  qu    wo    long  pang  gong  pang  yan   long  long  gong  kan   ta    ling  ta    long  gong  kan   gui   qiu   bie   gui   yue   chui  he    jue   \")\n                .append(\"xie   yue   \").toString();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ProcessUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.ActivityManager;\nimport android.app.AppOpsManager;\nimport android.app.Application;\nimport android.app.usage.UsageStats;\nimport android.app.usage.UsageStatsManager;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.provider.Settings;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresPermission;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileReader;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport static android.Manifest.permission.KILL_BACKGROUND_PROCESSES;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/10/18\n *     desc  : utils about process\n * </pre>\n */\npublic final class ProcessUtils {\n\n    private ProcessUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return the foreground process name.\n     * <p>Target APIs greater than 21 must hold\n     * {@code <uses-permission android:name=\"android.permission.PACKAGE_USAGE_STATS\" />}</p>\n     *\n     * @return the foreground process name\n     */\n    public static String getForegroundProcessName() {\n        ActivityManager am =\n                (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);\n        //noinspection ConstantConditions\n        List<ActivityManager.RunningAppProcessInfo> pInfo = am.getRunningAppProcesses();\n        if (pInfo != null && pInfo.size() > 0) {\n            for (ActivityManager.RunningAppProcessInfo aInfo : pInfo) {\n                if (aInfo.importance\n                        == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {\n                    return aInfo.processName;\n                }\n            }\n        }\n        if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.LOLLIPOP) {\n            PackageManager pm = Utils.getApp().getPackageManager();\n            Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);\n            List<ResolveInfo> list =\n                    pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);\n            Log.i(\"ProcessUtils\", list.toString());\n            if (list.size() <= 0) {\n                Log.i(\"ProcessUtils\",\n                        \"getForegroundProcessName: noun of access to usage information.\");\n                return \"\";\n            }\n            try {// Access to usage information.\n                ApplicationInfo info =\n                        pm.getApplicationInfo(Utils.getApp().getPackageName(), 0);\n                AppOpsManager aom =\n                        (AppOpsManager) Utils.getApp().getSystemService(Context.APP_OPS_SERVICE);\n                if (aom.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,\n                        info.uid,\n                        info.packageName) != AppOpsManager.MODE_ALLOWED) {\n                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n                    Utils.getApp().startActivity(intent);\n                }\n                if (aom.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,\n                        info.uid,\n                        info.packageName) != AppOpsManager.MODE_ALLOWED) {\n                    Log.i(\"ProcessUtils\",\n                            \"getForegroundProcessName: refuse to device usage stats.\");\n                    return \"\";\n                }\n                UsageStatsManager usageStatsManager = (UsageStatsManager) Utils.getApp()\n                        .getSystemService(Context.USAGE_STATS_SERVICE);\n                List<UsageStats> usageStatsList = null;\n                if (usageStatsManager != null) {\n                    long endTime = System.currentTimeMillis();\n                    long beginTime = endTime - 86400000 * 7;\n                    usageStatsList = usageStatsManager\n                            .queryUsageStats(UsageStatsManager.INTERVAL_BEST,\n                                    beginTime, endTime);\n                }\n                if (usageStatsList == null || usageStatsList.isEmpty()) return \"\";\n                UsageStats recentStats = null;\n                for (UsageStats usageStats : usageStatsList) {\n                    if (recentStats == null\n                            || usageStats.getLastTimeUsed() > recentStats.getLastTimeUsed()) {\n                        recentStats = usageStats;\n                    }\n                }\n                return recentStats == null ? null : recentStats.getPackageName();\n            } catch (PackageManager.NameNotFoundException e) {\n                e.printStackTrace();\n            }\n        }\n        return \"\";\n    }\n\n    /**\n     * Return all background processes.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.KILL_BACKGROUND_PROCESSES\" />}</p>\n     *\n     * @return all background processes\n     */\n    @RequiresPermission(KILL_BACKGROUND_PROCESSES)\n    public static Set<String> getAllBackgroundProcesses() {\n        ActivityManager am =\n                (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);\n        List<ActivityManager.RunningAppProcessInfo> info = am.getRunningAppProcesses();\n        Set<String> set = new HashSet<>();\n        if (info != null) {\n            for (ActivityManager.RunningAppProcessInfo aInfo : info) {\n                Collections.addAll(set, aInfo.pkgList);\n            }\n        }\n        return set;\n    }\n\n    /**\n     * Kill all background processes.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.KILL_BACKGROUND_PROCESSES\" />}</p>\n     *\n     * @return background processes were killed\n     */\n    @RequiresPermission(KILL_BACKGROUND_PROCESSES)\n    public static Set<String> killAllBackgroundProcesses() {\n        ActivityManager am =\n                (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);\n        List<ActivityManager.RunningAppProcessInfo> info = am.getRunningAppProcesses();\n        Set<String> set = new HashSet<>();\n        if (info == null) return set;\n        for (ActivityManager.RunningAppProcessInfo aInfo : info) {\n            for (String pkg : aInfo.pkgList) {\n                am.killBackgroundProcesses(pkg);\n                set.add(pkg);\n            }\n        }\n        info = am.getRunningAppProcesses();\n        for (ActivityManager.RunningAppProcessInfo aInfo : info) {\n            for (String pkg : aInfo.pkgList) {\n                set.remove(pkg);\n            }\n        }\n        return set;\n    }\n\n    /**\n     * Kill background processes.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.KILL_BACKGROUND_PROCESSES\" />}</p>\n     *\n     * @param packageName The name of the package.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    @RequiresPermission(KILL_BACKGROUND_PROCESSES)\n    public static boolean killBackgroundProcesses(@NonNull final String packageName) {\n        ActivityManager am =\n                (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);\n        List<ActivityManager.RunningAppProcessInfo> info = am.getRunningAppProcesses();\n        if (info == null || info.size() == 0) return true;\n        for (ActivityManager.RunningAppProcessInfo aInfo : info) {\n            if (Arrays.asList(aInfo.pkgList).contains(packageName)) {\n                am.killBackgroundProcesses(packageName);\n            }\n        }\n        info = am.getRunningAppProcesses();\n        if (info == null || info.size() == 0) return true;\n        for (ActivityManager.RunningAppProcessInfo aInfo : info) {\n            if (Arrays.asList(aInfo.pkgList).contains(packageName)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Return whether app running in the main process.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isMainProcess() {\n        return Utils.getApp().getPackageName().equals(getCurrentProcessName());\n    }\n\n    /**\n     * Return the name of current process.\n     *\n     * @return the name of current process\n     */\n    public static String getCurrentProcessName() {\n        String name = getCurrentProcessNameByFile();\n        if (!TextUtils.isEmpty(name)) return name;\n        name = getCurrentProcessNameByAms();\n        if (!TextUtils.isEmpty(name)) return name;\n        name = getCurrentProcessNameByReflect();\n        return name;\n    }\n\n    private static String getCurrentProcessNameByFile() {\n        try {\n            File file = new File(\"/proc/\" + android.os.Process.myPid() + \"/\" + \"cmdline\");\n            BufferedReader mBufferedReader = new BufferedReader(new FileReader(file));\n            String processName = mBufferedReader.readLine().trim();\n            mBufferedReader.close();\n            return processName;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return \"\";\n        }\n    }\n\n    private static String getCurrentProcessNameByAms() {\n        try {\n            ActivityManager am = (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);\n            if (am == null) return \"\";\n            List<ActivityManager.RunningAppProcessInfo> info = am.getRunningAppProcesses();\n            if (info == null || info.size() == 0) return \"\";\n            int pid = android.os.Process.myPid();\n            for (ActivityManager.RunningAppProcessInfo aInfo : info) {\n                if (aInfo.pid == pid) {\n                    if (aInfo.processName != null) {\n                        return aInfo.processName;\n                    }\n                }\n            }\n        } catch (Exception e) {\n            return \"\";\n        }\n        return \"\";\n    }\n\n    private static String getCurrentProcessNameByReflect() {\n        String processName = \"\";\n        try {\n            Application app = Utils.getApp();\n            Field loadedApkField = app.getClass().getField(\"mLoadedApk\");\n            loadedApkField.setAccessible(true);\n            Object loadedApk = loadedApkField.get(app);\n\n            Field activityThreadField = loadedApk.getClass().getDeclaredField(\"mActivityThread\");\n            activityThreadField.setAccessible(true);\n            Object activityThread = activityThreadField.get(loadedApk);\n\n            Method getProcessName = activityThread.getClass().getDeclaredMethod(\"getProcessName\");\n            processName = (String) getProcessName.invoke(activityThread);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return processName;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/RandomUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport java.util.Random;\n\n/**\n * didi Create on 2022/4/1 .\n * <p>\n * Copyright (c) 2022/4/1 by didiglobal.com.\n *\n * @author <a href=\"realonlyone@126.com\">zhangjun</a>\n * @version 1.0\n * @Date 2022/4/1 4:59 下午\n * @Description 用一句话说明文件功能\n */\n\npublic class RandomUtils {\n\n    public static final int RANDOM_MIN_LENGTH = 4;\n\n    private RandomUtils() {\n    }\n\n    public static String random16HexString() {\n        return randomHexString(16);\n    }\n\n    public static String random32HexString() {\n        return randomHexString(32);\n    }\n\n    public static String random64HexString() {\n        return randomHexString(64);\n    }\n\n    public static String random128HexString() {\n        return randomHexString(128);\n    }\n\n    public static String random256HexString() {\n        return randomHexString(256);\n    }\n\n    public static String randomHexString(int length) {\n        Random random = new Random();\n        if (length < RANDOM_MIN_LENGTH) {\n            length = RANDOM_MIN_LENGTH;\n        }\n        byte[] buffer = new byte[length / 2];\n        random.nextBytes(buffer);\n        return ConvertUtils.bytes2HexString(buffer);\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ReflectUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport java.lang.reflect.AccessibleObject;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.Member;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.lang.reflect.Proxy;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2017/12/15\n *     desc  : utils about reflect\n * </pre>\n */\npublic final class ReflectUtils {\n\n    private final Class<?> type;\n\n    private final Object object;\n\n    private ReflectUtils(final Class<?> type) {\n        this(type, type);\n    }\n\n    private ReflectUtils(final Class<?> type, Object object) {\n        this.type = type;\n        this.object = object;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // reflect\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Reflect the class.\n     *\n     * @param className The name of class.\n     * @return the single {@link ReflectUtils} instance\n     * @throws ReflectException if reflect unsuccessfully\n     */\n    public static ReflectUtils reflect(final String className)\n            throws ReflectException {\n        return reflect(forName(className));\n    }\n\n    /**\n     * Reflect the class.\n     *\n     * @param className   The name of class.\n     * @param classLoader The loader of class.\n     * @return the single {@link ReflectUtils} instance\n     * @throws ReflectException if reflect unsuccessfully\n     */\n    public static ReflectUtils reflect(final String className, final ClassLoader classLoader)\n            throws ReflectException {\n        return reflect(forName(className, classLoader));\n    }\n\n    /**\n     * Reflect the class.\n     *\n     * @param clazz The class.\n     * @return the single {@link ReflectUtils} instance\n     * @throws ReflectException if reflect unsuccessfully\n     */\n    public static ReflectUtils reflect(final Class<?> clazz)\n            throws ReflectException {\n        return new ReflectUtils(clazz);\n    }\n\n    /**\n     * Reflect the class.\n     *\n     * @param object The object.\n     * @return the single {@link ReflectUtils} instance\n     * @throws ReflectException if reflect unsuccessfully\n     */\n    public static ReflectUtils reflect(final Object object)\n            throws ReflectException {\n        return new ReflectUtils(object == null ? Object.class : object.getClass(), object);\n    }\n\n    private static Class<?> forName(String className) {\n        try {\n            return Class.forName(className);\n        } catch (ClassNotFoundException e) {\n            throw new ReflectException(e);\n        }\n    }\n\n    private static Class<?> forName(String name, ClassLoader classLoader) {\n        try {\n            return Class.forName(name, true, classLoader);\n        } catch (ClassNotFoundException e) {\n            throw new ReflectException(e);\n        }\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // newInstance\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Create and initialize a new instance.\n     *\n     * @return the single {@link ReflectUtils} instance\n     */\n    public ReflectUtils newInstance() {\n        return newInstance(new Object[0]);\n    }\n\n    /**\n     * Create and initialize a new instance.\n     *\n     * @param args The args.\n     * @return the single {@link ReflectUtils} instance\n     */\n    public ReflectUtils newInstance(Object... args) {\n        Class<?>[] types = getArgsType(args);\n        try {\n            Constructor<?> constructor = type().getDeclaredConstructor(types);\n            return newInstance(constructor, args);\n        } catch (NoSuchMethodException e) {\n            List<Constructor<?>> list = new ArrayList<>();\n            for (Constructor<?> constructor : type().getDeclaredConstructors()) {\n                if (match(constructor.getParameterTypes(), types)) {\n                    list.add(constructor);\n                }\n            }\n            if (list.isEmpty()) {\n                throw new ReflectException(e);\n            } else {\n                sortConstructors(list);\n                return newInstance(list.get(0), args);\n            }\n        }\n    }\n\n    private Class<?>[] getArgsType(final Object... args) {\n        if (args == null) return new Class[0];\n        Class<?>[] result = new Class[args.length];\n        for (int i = 0; i < args.length; i++) {\n            Object value = args[i];\n            result[i] = value == null ? NULL.class : value.getClass();\n        }\n        return result;\n    }\n\n    private void sortConstructors(List<Constructor<?>> list) {\n        Collections.sort(list, new Comparator<Constructor<?>>() {\n            @Override\n            public int compare(Constructor<?> o1, Constructor<?> o2) {\n                Class<?>[] types1 = o1.getParameterTypes();\n                Class<?>[] types2 = o2.getParameterTypes();\n                int len = types1.length;\n                for (int i = 0; i < len; i++) {\n                    if (!types1[i].equals(types2[i])) {\n                        if (wrapper(types1[i]).isAssignableFrom(wrapper(types2[i]))) {\n                            return 1;\n                        } else {\n                            return -1;\n                        }\n                    }\n                }\n                return 0;\n            }\n        });\n    }\n\n    private ReflectUtils newInstance(final Constructor<?> constructor, final Object... args) {\n        try {\n            return new ReflectUtils(\n                    constructor.getDeclaringClass(),\n                    accessible(constructor).newInstance(args)\n            );\n        } catch (Exception e) {\n            throw new ReflectException(e);\n        }\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // field\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Get the field.\n     *\n     * @param name The name of field.\n     * @return the single {@link ReflectUtils} instance\n     */\n    public ReflectUtils field(final String name) {\n        try {\n            Field field = getField(name);\n            return new ReflectUtils(field.getType(), field.get(object));\n        } catch (IllegalAccessException e) {\n            throw new ReflectException(e);\n        }\n    }\n\n    /**\n     * Set the field.\n     *\n     * @param name  The name of field.\n     * @param value The value.\n     * @return the single {@link ReflectUtils} instance\n     */\n    public ReflectUtils field(String name, Object value) {\n        try {\n            Field field = getField(name);\n            field.set(object, unwrap(value));\n            return this;\n        } catch (Exception e) {\n            throw new ReflectException(e);\n        }\n    }\n\n    private Field getField(String name) throws IllegalAccessException {\n        Field field = getAccessibleField(name);\n        if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {\n            try {\n                Field modifiersField = Field.class.getDeclaredField(\"modifiers\");\n                modifiersField.setAccessible(true);\n                modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);\n            } catch (NoSuchFieldException ignore) {\n                // runs in android will happen\n                field.setAccessible(true);\n            }\n        }\n        return field;\n    }\n\n    private Field getAccessibleField(String name) {\n        Class<?> type = type();\n        try {\n            return accessible(type.getField(name));\n        } catch (NoSuchFieldException e) {\n            do {\n                try {\n                    return accessible(type.getDeclaredField(name));\n                } catch (NoSuchFieldException ignore) {\n                }\n                type = type.getSuperclass();\n            } while (type != null);\n            throw new ReflectException(e);\n        }\n    }\n\n    private Object unwrap(Object object) {\n        if (object instanceof ReflectUtils) {\n            return ((ReflectUtils) object).get();\n        }\n        return object;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // method\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Invoke the method.\n     *\n     * @param name The name of method.\n     * @return the single {@link ReflectUtils} instance\n     * @throws ReflectException if reflect unsuccessfully\n     */\n    public ReflectUtils method(final String name) throws ReflectException {\n        return method(name, new Object[0]);\n    }\n\n    /**\n     * Invoke the method.\n     *\n     * @param name The name of method.\n     * @param args The args.\n     * @return the single {@link ReflectUtils} instance\n     * @throws ReflectException if reflect unsuccessfully\n     */\n    public ReflectUtils method(final String name, final Object... args) throws ReflectException {\n        Class<?>[] types = getArgsType(args);\n        try {\n            Method method = exactMethod(name, types);\n            return method(method, object, args);\n        } catch (NoSuchMethodException e) {\n            try {\n                Method method = similarMethod(name, types);\n                return method(method, object, args);\n            } catch (NoSuchMethodException e1) {\n                throw new ReflectException(e1);\n            }\n        }\n    }\n\n    private ReflectUtils method(final Method method, final Object obj, final Object... args) {\n        try {\n            accessible(method);\n            if (method.getReturnType() == void.class) {\n                method.invoke(obj, args);\n                return reflect(obj);\n            } else {\n                return reflect(method.invoke(obj, args));\n            }\n        } catch (Exception e) {\n            throw new ReflectException(e);\n        }\n    }\n\n    private Method exactMethod(final String name, final Class<?>[] types)\n            throws NoSuchMethodException {\n        Class<?> type = type();\n        try {\n            return type.getMethod(name, types);\n        } catch (NoSuchMethodException e) {\n            do {\n                try {\n                    return type.getDeclaredMethod(name, types);\n                } catch (NoSuchMethodException ignore) {\n                }\n                type = type.getSuperclass();\n            } while (type != null);\n            throw new NoSuchMethodException();\n        }\n    }\n\n    private Method similarMethod(final String name, final Class<?>[] types)\n            throws NoSuchMethodException {\n        Class<?> type = type();\n        List<Method> methods = new ArrayList<>();\n        for (Method method : type.getMethods()) {\n            if (isSimilarSignature(method, name, types)) {\n                methods.add(method);\n            }\n        }\n        if (!methods.isEmpty()) {\n            sortMethods(methods);\n            return methods.get(0);\n        }\n        do {\n            for (Method method : type.getDeclaredMethods()) {\n                if (isSimilarSignature(method, name, types)) {\n                    methods.add(method);\n                }\n            }\n            if (!methods.isEmpty()) {\n                sortMethods(methods);\n                return methods.get(0);\n            }\n            type = type.getSuperclass();\n        } while (type != null);\n\n        throw new NoSuchMethodException(\"No similar method \" + name + \" with params \"\n                + Arrays.toString(types) + \" could be found on type \" + type() + \".\");\n    }\n\n    private void sortMethods(final List<Method> methods) {\n        Collections.sort(methods, new Comparator<Method>() {\n            @Override\n            public int compare(Method o1, Method o2) {\n                Class<?>[] types1 = o1.getParameterTypes();\n                Class<?>[] types2 = o2.getParameterTypes();\n                int len = types1.length;\n                for (int i = 0; i < len; i++) {\n                    if (!types1[i].equals(types2[i])) {\n                        if (wrapper(types1[i]).isAssignableFrom(wrapper(types2[i]))) {\n                            return 1;\n                        } else {\n                            return -1;\n                        }\n                    }\n                }\n                return 0;\n            }\n        });\n    }\n\n    private boolean isSimilarSignature(final Method possiblyMatchingMethod,\n                                       final String desiredMethodName,\n                                       final Class<?>[] desiredParamTypes) {\n        return possiblyMatchingMethod.getName().equals(desiredMethodName)\n                && match(possiblyMatchingMethod.getParameterTypes(), desiredParamTypes);\n    }\n\n    private boolean match(final Class<?>[] declaredTypes, final Class<?>[] actualTypes) {\n        if (declaredTypes.length == actualTypes.length) {\n            for (int i = 0; i < actualTypes.length; i++) {\n                if (actualTypes[i] == NULL.class\n                        || wrapper(declaredTypes[i]).isAssignableFrom(wrapper(actualTypes[i]))) {\n                    continue;\n                }\n                return false;\n            }\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    private <T extends AccessibleObject> T accessible(T accessible) {\n        if (accessible == null) return null;\n        if (accessible instanceof Member) {\n            Member member = (Member) accessible;\n            if (Modifier.isPublic(member.getModifiers())\n                    && Modifier.isPublic(member.getDeclaringClass().getModifiers())) {\n                return accessible;\n            }\n        }\n        if (!accessible.isAccessible()) accessible.setAccessible(true);\n        return accessible;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // proxy\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Create a proxy for the wrapped object allowing to typesafely invoke\n     * methods on it using a custom interface.\n     *\n     * @param proxyType The interface type that is implemented by the proxy.\n     * @return a proxy for the wrapped object\n     */\n    @SuppressWarnings(\"unchecked\")\n    public <P> P proxy(final Class<P> proxyType) {\n        final boolean isMap = (object instanceof Map);\n        final InvocationHandler handler = new InvocationHandler() {\n            @Override\n            @SuppressWarnings(\"null\")\n            public Object invoke(Object proxy, Method method, Object[] args) {\n                String name = method.getName();\n                try {\n                    return reflect(object).method(name, args).get();\n                } catch (ReflectException e) {\n                    if (isMap) {\n                        Map<String, Object> map = (Map<String, Object>) object;\n                        int length = (args == null ? 0 : args.length);\n\n                        if (length == 0 && name.startsWith(\"get\")) {\n                            return map.get(property(name.substring(3)));\n                        } else if (length == 0 && name.startsWith(\"is\")) {\n                            return map.get(property(name.substring(2)));\n                        } else if (length == 1 && name.startsWith(\"set\")) {\n                            map.put(property(name.substring(3)), args[0]);\n                            return null;\n                        }\n                    }\n                    throw e;\n                }\n            }\n        };\n        return (P) Proxy.newProxyInstance(proxyType.getClassLoader(),\n                new Class[]{proxyType},\n                handler);\n    }\n\n    /**\n     * Get the POJO property name of an getter/setter\n     */\n    private static String property(String string) {\n        int length = string.length();\n\n        if (length == 0) {\n            return \"\";\n        } else if (length == 1) {\n            return string.toLowerCase();\n        } else {\n            return string.substring(0, 1).toLowerCase() + string.substring(1);\n        }\n    }\n\n    private Class<?> type() {\n        return type;\n    }\n\n    private Class<?> wrapper(final Class<?> type) {\n        if (type == null) {\n            return null;\n        } else if (type.isPrimitive()) {\n            if (boolean.class == type) {\n                return Boolean.class;\n            } else if (int.class == type) {\n                return Integer.class;\n            } else if (long.class == type) {\n                return Long.class;\n            } else if (short.class == type) {\n                return Short.class;\n            } else if (byte.class == type) {\n                return Byte.class;\n            } else if (double.class == type) {\n                return Double.class;\n            } else if (float.class == type) {\n                return Float.class;\n            } else if (char.class == type) {\n                return Character.class;\n            } else if (void.class == type) {\n                return Void.class;\n            }\n        }\n        return type;\n    }\n\n    /**\n     * Get the result.\n     *\n     * @param <T> The value type.\n     * @return the result\n     */\n    @SuppressWarnings(\"unchecked\")\n    public <T> T get() {\n        return (T) object;\n    }\n\n    @Override\n    public int hashCode() {\n        return object.hashCode();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        return obj instanceof ReflectUtils && object.equals(((ReflectUtils) obj).get());\n    }\n\n    @Override\n    public String toString() {\n        return object.toString();\n    }\n\n    private static class NULL {\n    }\n\n    public static class ReflectException extends RuntimeException {\n\n        private static final long serialVersionUID = 858774075258496016L;\n\n        public ReflectException(String message) {\n            super(message);\n        }\n\n        public ReflectException(String message, Throwable cause) {\n            super(message, cause);\n        }\n\n        public ReflectException(Throwable cause) {\n            super(cause);\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/RegexUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\n\n\nimport androidx.collection.SimpleArrayMap;\n\nimport com.didichuxing.doraemonkit.constant.RegexConstants;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/02\n *     desc  : utils about regex\n * </pre>\n */\npublic final class RegexUtils {\n\n    private final static SimpleArrayMap<String, String> CITY_MAP = new SimpleArrayMap<>();\n\n    private RegexUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // If u want more please visit http://toutiao.com/i6231678548520731137\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Return whether input matches regex of simple mobile.\n     *\n     * @param input The input.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isMobileSimple(final CharSequence input) {\n        return isMatch(RegexConstants.REGEX_MOBILE_SIMPLE, input);\n    }\n\n    /**\n     * Return whether input matches regex of exact mobile.\n     *\n     * @param input The input.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isMobileExact(final CharSequence input) {\n        return isMobileExact(input, null);\n    }\n\n    /**\n     * Return whether input matches regex of exact mobile.\n     *\n     * @param input       The input.\n     * @param newSegments The new segments of mobile number.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isMobileExact(final CharSequence input, List<String> newSegments) {\n        boolean match = isMatch(RegexConstants.REGEX_MOBILE_EXACT, input);\n        if (match) return true;\n        if (newSegments == null) return false;\n        if (input == null || input.length() != 11) return false;\n        String content = input.toString();\n        for (char c : content.toCharArray()) {\n            if (!Character.isDigit(c)) {\n                return false;\n            }\n        }\n        for (String newSegment : newSegments) {\n            if (content.startsWith(newSegment)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Return whether input matches regex of telephone number.\n     *\n     * @param input The input.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isTel(final CharSequence input) {\n        return isMatch(RegexConstants.REGEX_TEL, input);\n    }\n\n    /**\n     * Return whether input matches regex of id card number which length is 15.\n     *\n     * @param input The input.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isIDCard15(final CharSequence input) {\n        return isMatch(RegexConstants.REGEX_ID_CARD15, input);\n    }\n\n    /**\n     * Return whether input matches regex of id card number which length is 18.\n     *\n     * @param input The input.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isIDCard18(final CharSequence input) {\n        return isMatch(RegexConstants.REGEX_ID_CARD18, input);\n    }\n\n    /**\n     * Return whether input matches regex of exact id card number which length is 18.\n     *\n     * @param input The input.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isIDCard18Exact(final CharSequence input) {\n        if (isIDCard18(input)) {\n            int[] factor = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};\n            char[] suffix = new char[]{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};\n            if (CITY_MAP.isEmpty()) {\n                CITY_MAP.put(\"11\", \"北京\");\n                CITY_MAP.put(\"12\", \"天津\");\n                CITY_MAP.put(\"13\", \"河北\");\n                CITY_MAP.put(\"14\", \"山西\");\n                CITY_MAP.put(\"15\", \"内蒙古\");\n\n                CITY_MAP.put(\"21\", \"辽宁\");\n                CITY_MAP.put(\"22\", \"吉林\");\n                CITY_MAP.put(\"23\", \"黑龙江\");\n\n                CITY_MAP.put(\"31\", \"上海\");\n                CITY_MAP.put(\"32\", \"江苏\");\n                CITY_MAP.put(\"33\", \"浙江\");\n                CITY_MAP.put(\"34\", \"安徽\");\n                CITY_MAP.put(\"35\", \"福建\");\n                CITY_MAP.put(\"36\", \"江西\");\n                CITY_MAP.put(\"37\", \"山东\");\n\n                CITY_MAP.put(\"41\", \"河南\");\n                CITY_MAP.put(\"42\", \"湖北\");\n                CITY_MAP.put(\"43\", \"湖南\");\n                CITY_MAP.put(\"44\", \"广东\");\n                CITY_MAP.put(\"45\", \"广西\");\n                CITY_MAP.put(\"46\", \"海南\");\n\n                CITY_MAP.put(\"50\", \"重庆\");\n                CITY_MAP.put(\"51\", \"四川\");\n                CITY_MAP.put(\"52\", \"贵州\");\n                CITY_MAP.put(\"53\", \"云南\");\n                CITY_MAP.put(\"54\", \"西藏\");\n\n                CITY_MAP.put(\"61\", \"陕西\");\n                CITY_MAP.put(\"62\", \"甘肃\");\n                CITY_MAP.put(\"63\", \"青海\");\n                CITY_MAP.put(\"64\", \"宁夏\");\n                CITY_MAP.put(\"65\", \"新疆\");\n\n                CITY_MAP.put(\"71\", \"台湾老\");\n                CITY_MAP.put(\"81\", \"香港\");\n                CITY_MAP.put(\"82\", \"澳门\");\n                CITY_MAP.put(\"83\", \"台湾新\");\n                CITY_MAP.put(\"91\", \"国外\");\n            }\n            if (CITY_MAP.get(input.subSequence(0, 2).toString()) != null) {\n                int weightSum = 0;\n                for (int i = 0; i < 17; ++i) {\n                    weightSum += (input.charAt(i) - '0') * factor[i];\n                }\n                int idCardMod = weightSum % 11;\n                char idCardLast = input.charAt(17);\n                return idCardLast == suffix[idCardMod];\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Return whether input matches regex of email.\n     *\n     * @param input The input.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isEmail(final CharSequence input) {\n        return isMatch(RegexConstants.REGEX_EMAIL, input);\n    }\n\n    /**\n     * Return whether input matches regex of url.\n     *\n     * @param input The input.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isURL(final CharSequence input) {\n        return isMatch(RegexConstants.REGEX_URL, input);\n    }\n\n    /**\n     * Return whether input matches regex of Chinese character.\n     *\n     * @param input The input.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isZh(final CharSequence input) {\n        return isMatch(RegexConstants.REGEX_ZH, input);\n    }\n\n    /**\n     * Return whether input matches regex of username.\n     * <p>scope for \"a-z\", \"A-Z\", \"0-9\", \"_\", \"Chinese character\"</p>\n     * <p>can't end with \"_\"</p>\n     * <p>length is between 6 to 20</p>.\n     *\n     * @param input The input.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isUsername(final CharSequence input) {\n        return isMatch(RegexConstants.REGEX_USERNAME, input);\n    }\n\n    /**\n     * Return whether input matches regex of date which pattern is \"yyyy-MM-dd\".\n     *\n     * @param input The input.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isDate(final CharSequence input) {\n        return isMatch(RegexConstants.REGEX_DATE, input);\n    }\n\n    /**\n     * Return whether input matches regex of ip address.\n     *\n     * @param input The input.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isIP(final CharSequence input) {\n        return isMatch(RegexConstants.REGEX_IP, input);\n    }\n\n    /**\n     * Return whether input matches the regex.\n     *\n     * @param regex The regex.\n     * @param input The input.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isMatch(final String regex, final CharSequence input) {\n        return input != null && input.length() > 0 && Pattern.matches(regex, input);\n    }\n\n    /**\n     * Return the list of input matches the regex.\n     *\n     * @param regex The regex.\n     * @param input The input.\n     * @return the list of input matches the regex\n     */\n    public static List<String> getMatches(final String regex, final CharSequence input) {\n        if (input == null) return Collections.emptyList();\n        List<String> matches = new ArrayList<>();\n        Pattern pattern = Pattern.compile(regex);\n        Matcher matcher = pattern.matcher(input);\n        while (matcher.find()) {\n            matches.add(matcher.group());\n        }\n        return matches;\n    }\n\n    /**\n     * Splits input around matches of the regex.\n     *\n     * @param input The input.\n     * @param regex The regex.\n     * @return the array of strings computed by splitting input around matches of regex\n     */\n    public static String[] getSplits(final String input, final String regex) {\n        if (input == null) return new String[0];\n        return input.split(regex);\n    }\n\n    /**\n     * Replace the first subsequence of the input sequence that matches the\n     * regex with the given replacement string.\n     *\n     * @param input       The input.\n     * @param regex       The regex.\n     * @param replacement The replacement string.\n     * @return the string constructed by replacing the first matching\n     * subsequence by the replacement string, substituting captured\n     * subsequences as needed\n     */\n    public static String getReplaceFirst(final String input,\n                                         final String regex,\n                                         final String replacement) {\n        if (input == null) return \"\";\n        return Pattern.compile(regex).matcher(input).replaceFirst(replacement);\n    }\n\n    /**\n     * Replace every subsequence of the input sequence that matches the\n     * pattern with the given replacement string.\n     *\n     * @param input       The input.\n     * @param regex       The regex.\n     * @param replacement The replacement string.\n     * @return the string constructed by replacing each matching subsequence\n     * by the replacement string, substituting captured subsequences\n     * as needed\n     */\n    public static String getReplaceAll(final String input,\n                                       final String regex,\n                                       final String replacement) {\n        if (input == null) return \"\";\n        return Pattern.compile(regex).matcher(input).replaceAll(replacement);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ResourceUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.DrawableRes;\n\nimport androidx.annotation.RawRes;\nimport androidx.core.content.ContextCompat;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.UnsupportedEncodingException;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/05/07\n *     desc  : utils about resource\n * </pre>\n */\npublic final class ResourceUtils {\n\n    private static final int BUFFER_SIZE = 8192;\n\n    private ResourceUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return the drawable by identifier.\n     *\n     * @param id The identifier.\n     * @return the drawable by identifier\n     */\n    public static Drawable getDrawable(@DrawableRes int id) {\n        return ContextCompat.getDrawable(Utils.getApp(), id);\n    }\n\n    /**\n     * Return the id identifier by name.\n     *\n     * @param name The name of id.\n     * @return the id identifier by name\n     */\n    public static int getIdByName(String name) {\n        return Utils.getApp().getResources().getIdentifier(name, \"id\", Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the string identifier by name.\n     *\n     * @param name The name of string.\n     * @return the string identifier by name\n     */\n    public static int getStringIdByName(String name) {\n        return Utils.getApp().getResources().getIdentifier(name, \"string\", Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the color identifier by name.\n     *\n     * @param name The name of color.\n     * @return the color identifier by name\n     */\n    public static int getColorIdByName(String name) {\n        return Utils.getApp().getResources().getIdentifier(name, \"color\", Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the dimen identifier by name.\n     *\n     * @param name The name of dimen.\n     * @return the dimen identifier by name\n     */\n    public static int getDimenIdByName(String name) {\n        return Utils.getApp().getResources().getIdentifier(name, \"dimen\", Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the drawable identifier by name.\n     *\n     * @param name The name of drawable.\n     * @return the drawable identifier by name\n     */\n    public static int getDrawableIdByName(String name) {\n        return Utils.getApp().getResources().getIdentifier(name, \"drawable\", Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the mipmap identifier by name.\n     *\n     * @param name The name of mipmap.\n     * @return the mipmap identifier by name\n     */\n    public static int getMipmapIdByName(String name) {\n        return Utils.getApp().getResources().getIdentifier(name, \"mipmap\", Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the layout identifier by name.\n     *\n     * @param name The name of layout.\n     * @return the layout identifier by name\n     */\n    public static int getLayoutIdByName(String name) {\n        return Utils.getApp().getResources().getIdentifier(name, \"layout\", Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the style identifier by name.\n     *\n     * @param name The name of style.\n     * @return the style identifier by name\n     */\n    public static int getStyleIdByName(String name) {\n        return Utils.getApp().getResources().getIdentifier(name, \"style\", Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the anim identifier by name.\n     *\n     * @param name The name of anim.\n     * @return the anim identifier by name\n     */\n    public static int getAnimIdByName(String name) {\n        return Utils.getApp().getResources().getIdentifier(name, \"anim\", Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Return the menu identifier by name.\n     *\n     * @param name The name of menu.\n     * @return the menu identifier by name\n     */\n    public static int getMenuIdByName(String name) {\n        return Utils.getApp().getResources().getIdentifier(name, \"menu\", Utils.getApp().getPackageName());\n    }\n\n    /**\n     * Copy the file from assets.\n     *\n     * @param assetsFilePath The path of file in assets.\n     * @param destFilePath   The path of destination file.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean copyFileFromAssets(final String assetsFilePath, final String destFilePath) {\n        boolean res = true;\n        try {\n            String[] assets = Utils.getApp().getAssets().list(assetsFilePath);\n            if (assets != null && assets.length > 0) {\n                for (String asset : assets) {\n                    res &= copyFileFromAssets(assetsFilePath + \"/\" + asset, destFilePath + \"/\" + asset);\n                }\n            } else {\n                res = UtilsBridge.writeFileFromIS(\n                        destFilePath,\n                        Utils.getApp().getAssets().open(assetsFilePath)\n                );\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n            res = false;\n        }\n        return res;\n    }\n\n    /**\n     * Return the content of assets.\n     *\n     * @param assetsFilePath The path of file in assets.\n     * @return the content of assets\n     */\n    public static String readAssets2String(final String assetsFilePath) {\n        return readAssets2String(assetsFilePath, null);\n    }\n\n    /**\n     * Return the content of assets.\n     *\n     * @param assetsFilePath The path of file in assets.\n     * @param charsetName    The name of charset.\n     * @return the content of assets\n     */\n    public static String readAssets2String(final String assetsFilePath, final String charsetName) {\n        try {\n            InputStream is = Utils.getApp().getAssets().open(assetsFilePath);\n            byte[] bytes = UtilsBridge.inputStream2Bytes(is);\n            if (bytes == null) return \"\";\n            if (UtilsBridge.isSpace(charsetName)) {\n                return new String(bytes);\n            } else {\n                try {\n                    return new String(bytes, charsetName);\n                } catch (UnsupportedEncodingException e) {\n                    e.printStackTrace();\n                    return \"\";\n                }\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n            return \"\";\n        }\n    }\n\n    /**\n     * Return the content of file in assets.\n     *\n     * @param assetsPath The path of file in assets.\n     * @return the content of file in assets\n     */\n    public static List<String> readAssets2List(final String assetsPath) {\n        return readAssets2List(assetsPath, \"\");\n    }\n\n    /**\n     * Return the content of file in assets.\n     *\n     * @param assetsPath  The path of file in assets.\n     * @param charsetName The name of charset.\n     * @return the content of file in assets\n     */\n    public static List<String> readAssets2List(final String assetsPath,\n                                               final String charsetName) {\n        try {\n            return UtilsBridge.inputStream2Lines(Utils.getApp().getResources().getAssets().open(assetsPath), charsetName);\n        } catch (IOException e) {\n            e.printStackTrace();\n            return Collections.emptyList();\n        }\n    }\n\n\n    /**\n     * Copy the file from raw.\n     *\n     * @param resId        The resource id.\n     * @param destFilePath The path of destination file.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean copyFileFromRaw(@RawRes final int resId, final String destFilePath) {\n        return UtilsBridge.writeFileFromIS(\n                destFilePath,\n                Utils.getApp().getResources().openRawResource(resId)\n        );\n    }\n\n    /**\n     * Return the content of resource in raw.\n     *\n     * @param resId The resource id.\n     * @return the content of resource in raw\n     */\n    public static String readRaw2String(@RawRes final int resId) {\n        return readRaw2String(resId, null);\n    }\n\n    /**\n     * Return the content of resource in raw.\n     *\n     * @param resId       The resource id.\n     * @param charsetName The name of charset.\n     * @return the content of resource in raw\n     */\n    public static String readRaw2String(@RawRes final int resId, final String charsetName) {\n        InputStream is = Utils.getApp().getResources().openRawResource(resId);\n        byte[] bytes = UtilsBridge.inputStream2Bytes(is);\n        if (bytes == null) return null;\n        if (UtilsBridge.isSpace(charsetName)) {\n            return new String(bytes);\n        } else {\n            try {\n                return new String(bytes, charsetName);\n            } catch (UnsupportedEncodingException e) {\n                e.printStackTrace();\n                return \"\";\n            }\n        }\n    }\n\n    /**\n     * Return the content of resource in raw.\n     *\n     * @param resId The resource id.\n     * @return the content of file in assets\n     */\n    public static List<String> readRaw2List(@RawRes final int resId) {\n        return readRaw2List(resId, \"\");\n    }\n\n    /**\n     * Return the content of resource in raw.\n     *\n     * @param resId       The resource id.\n     * @param charsetName The name of charset.\n     * @return the content of file in assets\n     */\n    public static List<String> readRaw2List(@RawRes final int resId,\n                                            final String charsetName) {\n        return UtilsBridge.inputStream2Lines(Utils.getApp().getResources().openRawResource(resId), charsetName);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/RetrofitUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/08/25\n *     desc  : utils about retrofit\n * </pre>\n */\npublic final class RetrofitUtils {\n\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/RomUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.annotation.SuppressLint;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.text.TextUtils;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.lang.reflect.Method;\nimport java.util.Properties;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/07/04\n *     desc  : utils about rom\n * </pre>\n */\npublic final class RomUtils {\n\n    private static final String[] ROM_HUAWEI    = {\"huawei\"};\n    private static final String[] ROM_VIVO      = {\"vivo\"};\n    private static final String[] ROM_XIAOMI    = {\"xiaomi\"};\n    private static final String[] ROM_OPPO      = {\"oppo\"};\n    private static final String[] ROM_LEECO     = {\"leeco\", \"letv\"};\n    private static final String[] ROM_360       = {\"360\", \"qiku\"};\n    private static final String[] ROM_ZTE       = {\"zte\"};\n    private static final String[] ROM_ONEPLUS   = {\"oneplus\"};\n    private static final String[] ROM_NUBIA     = {\"nubia\"};\n    private static final String[] ROM_COOLPAD   = {\"coolpad\", \"yulong\"};\n    private static final String[] ROM_LG        = {\"lg\", \"lge\"};\n    private static final String[] ROM_GOOGLE    = {\"google\"};\n    private static final String[] ROM_SAMSUNG   = {\"samsung\"};\n    private static final String[] ROM_MEIZU     = {\"meizu\"};\n    private static final String[] ROM_LENOVO    = {\"lenovo\"};\n    private static final String[] ROM_SMARTISAN = {\"smartisan\", \"deltainno\"};\n    private static final String[] ROM_HTC       = {\"htc\"};\n    private static final String[] ROM_SONY      = {\"sony\"};\n    private static final String[] ROM_GIONEE    = {\"gionee\", \"amigo\"};\n    private static final String[] ROM_MOTOROLA  = {\"motorola\"};\n\n    private static final String VERSION_PROPERTY_HUAWEI  = \"ro.build.version.emui\";\n    private static final String VERSION_PROPERTY_VIVO    = \"ro.vivo.os.build.display.id\";\n    private static final String VERSION_PROPERTY_XIAOMI  = \"ro.build.version.incremental\";\n    private static final String VERSION_PROPERTY_OPPO    = \"ro.build.version.opporom\";\n    private static final String VERSION_PROPERTY_LEECO   = \"ro.letv.release.version\";\n    private static final String VERSION_PROPERTY_360     = \"ro.build.uiversion\";\n    private static final String VERSION_PROPERTY_ZTE     = \"ro.build.MiFavor_version\";\n    private static final String VERSION_PROPERTY_ONEPLUS = \"ro.rom.version\";\n    private static final String VERSION_PROPERTY_NUBIA   = \"ro.build.rom.id\";\n    private final static String UNKNOWN                  = \"unknown\";\n\n    private static RomInfo bean = null;\n\n    private RomUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return whether the rom is made by huawei.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isHuawei() {\n        return ROM_HUAWEI[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by vivo.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isVivo() {\n        return ROM_VIVO[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by xiaomi.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isXiaomi() {\n        return ROM_XIAOMI[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by oppo.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isOppo() {\n        return ROM_OPPO[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by leeco.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isLeeco() {\n        return ROM_LEECO[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by 360.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean is360() {\n        return ROM_360[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by zte.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isZte() {\n        return ROM_ZTE[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by oneplus.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isOneplus() {\n        return ROM_ONEPLUS[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by nubia.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isNubia() {\n        return ROM_NUBIA[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by coolpad.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isCoolpad() {\n        return ROM_COOLPAD[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by lg.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isLg() {\n        return ROM_LG[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by google.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isGoogle() {\n        return ROM_GOOGLE[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by samsung.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isSamsung() {\n        return ROM_SAMSUNG[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by meizu.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isMeizu() {\n        return ROM_MEIZU[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by lenovo.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isLenovo() {\n        return ROM_LENOVO[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by smartisan.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isSmartisan() {\n        return ROM_SMARTISAN[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by htc.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isHtc() {\n        return ROM_HTC[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by sony.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isSony() {\n        return ROM_SONY[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by gionee.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isGionee() {\n        return ROM_GIONEE[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return whether the rom is made by motorola.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isMotorola() {\n        return ROM_MOTOROLA[0].equals(getRomInfo().name);\n    }\n\n    /**\n     * Return the rom's information.\n     *\n     * @return the rom's information\n     */\n    public static RomInfo getRomInfo() {\n        if (bean != null) return bean;\n        bean = new RomInfo();\n        final String brand = getBrand();\n        final String manufacturer = getManufacturer();\n        if (isRightRom(brand, manufacturer, ROM_HUAWEI)) {\n            bean.name = ROM_HUAWEI[0];\n            String version = getRomVersion(VERSION_PROPERTY_HUAWEI);\n            String[] temp = version.split(\"_\");\n            if (temp.length > 1) {\n                bean.version = temp[1];\n            } else {\n                bean.version = version;\n            }\n            return bean;\n        }\n        if (isRightRom(brand, manufacturer, ROM_VIVO)) {\n            bean.name = ROM_VIVO[0];\n            bean.version = getRomVersion(VERSION_PROPERTY_VIVO);\n            return bean;\n        }\n        if (isRightRom(brand, manufacturer, ROM_XIAOMI)) {\n            bean.name = ROM_XIAOMI[0];\n            bean.version = getRomVersion(VERSION_PROPERTY_XIAOMI);\n            return bean;\n        }\n        if (isRightRom(brand, manufacturer, ROM_OPPO)) {\n            bean.name = ROM_OPPO[0];\n            bean.version = getRomVersion(VERSION_PROPERTY_OPPO);\n            return bean;\n        }\n        if (isRightRom(brand, manufacturer, ROM_LEECO)) {\n            bean.name = ROM_LEECO[0];\n            bean.version = getRomVersion(VERSION_PROPERTY_LEECO);\n            return bean;\n        }\n\n        if (isRightRom(brand, manufacturer, ROM_360)) {\n            bean.name = ROM_360[0];\n            bean.version = getRomVersion(VERSION_PROPERTY_360);\n            return bean;\n        }\n        if (isRightRom(brand, manufacturer, ROM_ZTE)) {\n            bean.name = ROM_ZTE[0];\n            bean.version = getRomVersion(VERSION_PROPERTY_ZTE);\n            return bean;\n        }\n        if (isRightRom(brand, manufacturer, ROM_ONEPLUS)) {\n            bean.name = ROM_ONEPLUS[0];\n            bean.version = getRomVersion(VERSION_PROPERTY_ONEPLUS);\n            return bean;\n        }\n        if (isRightRom(brand, manufacturer, ROM_NUBIA)) {\n            bean.name = ROM_NUBIA[0];\n            bean.version = getRomVersion(VERSION_PROPERTY_NUBIA);\n            return bean;\n        }\n\n        if (isRightRom(brand, manufacturer, ROM_COOLPAD)) {\n            bean.name = ROM_COOLPAD[0];\n        } else if (isRightRom(brand, manufacturer, ROM_LG)) {\n            bean.name = ROM_LG[0];\n        } else if (isRightRom(brand, manufacturer, ROM_GOOGLE)) {\n            bean.name = ROM_GOOGLE[0];\n        } else if (isRightRom(brand, manufacturer, ROM_SAMSUNG)) {\n            bean.name = ROM_SAMSUNG[0];\n        } else if (isRightRom(brand, manufacturer, ROM_MEIZU)) {\n            bean.name = ROM_MEIZU[0];\n        } else if (isRightRom(brand, manufacturer, ROM_LENOVO)) {\n            bean.name = ROM_LENOVO[0];\n        } else if (isRightRom(brand, manufacturer, ROM_SMARTISAN)) {\n            bean.name = ROM_SMARTISAN[0];\n        } else if (isRightRom(brand, manufacturer, ROM_HTC)) {\n            bean.name = ROM_HTC[0];\n        } else if (isRightRom(brand, manufacturer, ROM_SONY)) {\n            bean.name = ROM_SONY[0];\n        } else if (isRightRom(brand, manufacturer, ROM_GIONEE)) {\n            bean.name = ROM_GIONEE[0];\n        } else if (isRightRom(brand, manufacturer, ROM_MOTOROLA)) {\n            bean.name = ROM_MOTOROLA[0];\n        } else {\n            bean.name = manufacturer;\n        }\n        bean.version = getRomVersion(\"\");\n        return bean;\n    }\n\n    private static boolean isRightRom(final String brand, final String manufacturer, final String... names) {\n        for (String name : names) {\n            if (brand.contains(name) || manufacturer.contains(name)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private static String getManufacturer() {\n        try {\n            String manufacturer = Build.MANUFACTURER;\n            if (!TextUtils.isEmpty(manufacturer)) {\n                return manufacturer.toLowerCase();\n            }\n        } catch (Throwable ignore) {/**/}\n        return UNKNOWN;\n    }\n\n    private static String getBrand() {\n        try {\n            String brand = Build.BRAND;\n            if (!TextUtils.isEmpty(brand)) {\n                return brand.toLowerCase();\n            }\n        } catch (Throwable ignore) {/**/}\n        return UNKNOWN;\n    }\n\n    private static String getRomVersion(final String propertyName) {\n        String ret = \"\";\n        if (!TextUtils.isEmpty(propertyName)) {\n            ret = getSystemProperty(propertyName);\n        }\n        if (TextUtils.isEmpty(ret) || ret.equals(UNKNOWN)) {\n            try {\n                String display = Build.DISPLAY;\n                if (!TextUtils.isEmpty(display)) {\n                    ret = display.toLowerCase();\n                }\n            } catch (Throwable ignore) {/**/}\n        }\n        if (TextUtils.isEmpty(ret)) {\n            return UNKNOWN;\n        }\n        return ret;\n    }\n\n    private static String getSystemProperty(final String name) {\n        String prop = getSystemPropertyByShell(name);\n        if (!TextUtils.isEmpty(prop)) return prop;\n        prop = getSystemPropertyByStream(name);\n        if (!TextUtils.isEmpty(prop)) return prop;\n        if (Build.VERSION.SDK_INT < 28) {\n            return getSystemPropertyByReflect(name);\n        }\n        return prop;\n    }\n\n    private static String getSystemPropertyByShell(final String propName) {\n        String line;\n        BufferedReader input = null;\n        try {\n            Process p = Runtime.getRuntime().exec(\"getprop \" + propName);\n            input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);\n            String ret = input.readLine();\n            if (ret != null) {\n                return ret;\n            }\n        } catch (IOException ignore) {\n        } finally {\n            if (input != null) {\n                try {\n                    input.close();\n                } catch (IOException ignore) {/**/}\n            }\n        }\n        return \"\";\n    }\n\n    private static String getSystemPropertyByStream(final String key) {\n        try {\n            Properties prop = new Properties();\n            FileInputStream is = new FileInputStream(\n                    new File(Environment.getRootDirectory(), \"build.prop\")\n            );\n            prop.load(is);\n            return prop.getProperty(key, \"\");\n        } catch (Exception ignore) {/**/}\n        return \"\";\n    }\n\n    private static String getSystemPropertyByReflect(String key) {\n        try {\n            @SuppressLint(\"PrivateApi\")\n            Class<?> clz = Class.forName(\"android.os.SystemProperties\");\n            Method getMethod = clz.getMethod(\"get\", String.class, String.class);\n            return (String) getMethod.invoke(clz, key, \"\");\n        } catch (Exception e) {/**/}\n        return \"\";\n    }\n\n    public static class RomInfo {\n        private String name;\n        private String version;\n\n        public String getName() {\n            return name;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n\n        @Override\n        public String toString() {\n            return \"RomInfo{name=\" + name +\n                    \", version=\" + version + \"}\";\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/SDCardUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.Context;\nimport android.os.Environment;\nimport android.os.storage.StorageManager;\nimport android.os.storage.StorageVolume;\nimport android.text.format.Formatter;\n\nimport java.lang.reflect.Array;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/11\n *     desc  : utils about sdcard\n * </pre>\n */\npublic final class SDCardUtils {\n\n    private SDCardUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return whether sdcard is enabled by environment.\n     *\n     * @return {@code true}: enabled<br>{@code false}: disabled\n     */\n    public static boolean isSDCardEnableByEnvironment() {\n        return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());\n    }\n\n    /**\n     * Return the path of sdcard by environment.\n     *\n     * @return the path of sdcard by environment\n     */\n    public static String getSDCardPathByEnvironment() {\n        if (isSDCardEnableByEnvironment()) {\n            return Environment.getExternalStorageDirectory().getAbsolutePath();\n        }\n        return \"\";\n    }\n\n    /**\n     * Return the information of sdcard.\n     *\n     * @return the information of sdcard\n     */\n    public static List<SDCardInfo> getSDCardInfo() {\n        List<SDCardInfo> paths = new ArrayList<>();\n        StorageManager sm = (StorageManager) Utils.getApp().getSystemService(Context.STORAGE_SERVICE);\n        if (sm == null) return paths;\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {\n            List<StorageVolume> storageVolumes = sm.getStorageVolumes();\n            try {\n                //noinspection JavaReflectionMemberAccess\n                Method getPathMethod = StorageVolume.class.getMethod(\"getPath\");\n                for (StorageVolume storageVolume : storageVolumes) {\n                    boolean isRemovable = storageVolume.isRemovable();\n                    String state = storageVolume.getState();\n                    String path = (String) getPathMethod.invoke(storageVolume);\n                    paths.add(new SDCardInfo(path, state, isRemovable));\n                }\n            } catch (NoSuchMethodException e) {\n                e.printStackTrace();\n            } catch (IllegalAccessException e) {\n                e.printStackTrace();\n            } catch (InvocationTargetException e) {\n                e.printStackTrace();\n            }\n        } else {\n            try {\n                Class<?> storageVolumeClazz = Class.forName(\"android.os.storage.StorageVolume\");\n                //noinspection JavaReflectionMemberAccess\n                Method getPathMethod = storageVolumeClazz.getMethod(\"getPath\");\n                Method isRemovableMethod = storageVolumeClazz.getMethod(\"isRemovable\");\n                //noinspection JavaReflectionMemberAccess\n                Method getVolumeStateMethod = StorageManager.class.getMethod(\"getVolumeState\", String.class);\n                //noinspection JavaReflectionMemberAccess\n                Method getVolumeListMethod = StorageManager.class.getMethod(\"getVolumeList\");\n                Object result = getVolumeListMethod.invoke(sm);\n                final int length = Array.getLength(result);\n                for (int i = 0; i < length; i++) {\n                    Object storageVolumeElement = Array.get(result, i);\n                    String path = (String) getPathMethod.invoke(storageVolumeElement);\n                    boolean isRemovable = (Boolean) isRemovableMethod.invoke(storageVolumeElement);\n                    String state = (String) getVolumeStateMethod.invoke(sm, path);\n                    paths.add(new SDCardInfo(path, state, isRemovable));\n                }\n            } catch (ClassNotFoundException e) {\n                e.printStackTrace();\n            } catch (InvocationTargetException e) {\n                e.printStackTrace();\n            } catch (NoSuchMethodException e) {\n                e.printStackTrace();\n            } catch (IllegalAccessException e) {\n                e.printStackTrace();\n            }\n        }\n        return paths;\n    }\n\n    /**\n     * Return the ptah of mounted sdcard.\n     *\n     * @return the ptah of mounted sdcard.\n     */\n    public static List<String> getMountedSDCardPath() {\n        List<String> path = new ArrayList<>();\n        List<SDCardInfo> sdCardInfo = getSDCardInfo();\n        if (sdCardInfo == null || sdCardInfo.isEmpty()) return path;\n        for (SDCardInfo cardInfo : sdCardInfo) {\n            String state = cardInfo.state;\n            if (state == null) continue;\n            if (\"mounted\".equals(state.toLowerCase())) {\n                path.add(cardInfo.path);\n            }\n        }\n        return path;\n    }\n\n\n    /**\n     * Return the total size of external storage\n     *\n     * @return the total size of external storage\n     */\n    public static long getExternalTotalSize() {\n        return UtilsBridge.getFsTotalSize(getSDCardPathByEnvironment());\n    }\n\n    /**\n     * Return the available size of external storage.\n     *\n     * @return the available size of external storage\n     */\n    public static long getExternalAvailableSize() {\n        return UtilsBridge.getFsAvailableSize(getSDCardPathByEnvironment());\n    }\n\n    /**\n     * Return the total size of internal storage\n     *\n     * @return the total size of internal storage\n     */\n    public static long getInternalTotalSize() {\n        return UtilsBridge.getFsTotalSize(Environment.getDataDirectory().getAbsolutePath());\n    }\n\n    /**\n     * Return the available size of internal storage.\n     *\n     * @return the available size of internal storage\n     */\n    public static long getInternalAvailableSize() {\n        return UtilsBridge.getFsAvailableSize(Environment.getDataDirectory().getAbsolutePath());\n    }\n\n    public static class SDCardInfo {\n\n        private String  path;\n        private String  state;\n        private boolean isRemovable;\n        private long    totalSize;\n        private long    availableSize;\n\n        SDCardInfo(String path, String state, boolean isRemovable) {\n            this.path = path;\n            this.state = state;\n            this.isRemovable = isRemovable;\n            this.totalSize = UtilsBridge.getFsTotalSize(path);\n            this.availableSize = UtilsBridge.getFsAvailableSize(path);\n        }\n\n        public String getPath() {\n            return path;\n        }\n\n        public String getState() {\n            return state;\n        }\n\n        public boolean isRemovable() {\n            return isRemovable;\n        }\n\n        public long getTotalSize() {\n            return totalSize;\n        }\n\n        public long getAvailableSize() {\n            return availableSize;\n        }\n\n        @Override\n        public String toString() {\n            return \"SDCardInfo {\" +\n                    \"path = \" + path +\n                    \", state = \" + state +\n                    \", isRemovable = \" + isRemovable +\n                    \", totalSize = \" + Formatter.formatFileSize(Utils.getApp(), totalSize) +\n                    \", availableSize = \" + Formatter.formatFileSize(Utils.getApp(), availableSize) +\n                    '}';\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/SPStaticUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.SharedPreferences;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/01/04\n *     desc  : utils about shared preference\n * </pre>\n */\npublic final class SPStaticUtils {\n\n    private static SPUtils sDefaultSPUtils;\n\n    /**\n     * Set the default instance of {@link SPUtils}.\n     *\n     * @param spUtils The default instance of {@link SPUtils}.\n     */\n    public static void setDefaultSPUtils(final SPUtils spUtils) {\n        sDefaultSPUtils = spUtils;\n    }\n\n    /**\n     * Put the string value in sp.\n     *\n     * @param key   The key of sp.\n     * @param value The value of sp.\n     */\n    public static void put(@NonNull final String key, final String value) {\n        put(key, value, getDefaultSPUtils());\n    }\n\n    /**\n     * Put the string value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public static void put(@NonNull final String key, final String value, final boolean isCommit) {\n        put(key, value, isCommit, getDefaultSPUtils());\n    }\n\n\n    /**\n     * Return the string value in sp.\n     *\n     * @param key The key of sp.\n     * @return the string value if sp exists or {@code \"\"} otherwise\n     */\n    public static String getString(@NonNull final String key) {\n        return getString(key, getDefaultSPUtils());\n    }\n\n    /**\n     * Return the string value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @return the string value if sp exists or {@code defaultValue} otherwise\n     */\n    public static String getString(@NonNull final String key, final String defaultValue) {\n        return getString(key, defaultValue, getDefaultSPUtils());\n    }\n\n\n    /**\n     * Put the int value in sp.\n     *\n     * @param key   The key of sp.\n     * @param value The value of sp.\n     */\n    public static void put(@NonNull final String key, final int value) {\n        put(key, value, getDefaultSPUtils());\n    }\n\n    /**\n     * Put the int value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public static void put(@NonNull final String key, final int value, final boolean isCommit) {\n        put(key, value, isCommit, getDefaultSPUtils());\n    }\n\n    /**\n     * Return the int value in sp.\n     *\n     * @param key The key of sp.\n     * @return the int value if sp exists or {@code -1} otherwise\n     */\n    public static int getInt(@NonNull final String key) {\n        return getInt(key, getDefaultSPUtils());\n    }\n\n    /**\n     * Return the int value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @return the int value if sp exists or {@code defaultValue} otherwise\n     */\n    public static int getInt(@NonNull final String key, final int defaultValue) {\n        return getInt(key, defaultValue, getDefaultSPUtils());\n    }\n\n    /**\n     * Put the long value in sp.\n     *\n     * @param key   The key of sp.\n     * @param value The value of sp.\n     */\n    public static void put(@NonNull final String key, final long value) {\n        put(key, value, getDefaultSPUtils());\n    }\n\n    /**\n     * Put the long value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public static void put(@NonNull final String key, final long value, final boolean isCommit) {\n        put(key, value, isCommit, getDefaultSPUtils());\n    }\n\n    /**\n     * Return the long value in sp.\n     *\n     * @param key The key of sp.\n     * @return the long value if sp exists or {@code -1} otherwise\n     */\n    public static long getLong(@NonNull final String key) {\n        return getLong(key, getDefaultSPUtils());\n    }\n\n    /**\n     * Return the long value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @return the long value if sp exists or {@code defaultValue} otherwise\n     */\n    public static long getLong(@NonNull final String key, final long defaultValue) {\n        return getLong(key, defaultValue, getDefaultSPUtils());\n    }\n\n    /**\n     * Put the float value in sp.\n     *\n     * @param key   The key of sp.\n     * @param value The value of sp.\n     */\n    public static void put(@NonNull final String key, final float value) {\n        put(key, value, getDefaultSPUtils());\n    }\n\n    /**\n     * Put the float value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public static void put(@NonNull final String key, final float value, final boolean isCommit) {\n        put(key, value, isCommit, getDefaultSPUtils());\n    }\n\n    /**\n     * Return the float value in sp.\n     *\n     * @param key The key of sp.\n     * @return the float value if sp exists or {@code -1f} otherwise\n     */\n    public static float getFloat(@NonNull final String key) {\n        return getFloat(key, getDefaultSPUtils());\n    }\n\n    /**\n     * Return the float value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @return the float value if sp exists or {@code defaultValue} otherwise\n     */\n    public static float getFloat(@NonNull final String key, final float defaultValue) {\n        return getFloat(key, defaultValue, getDefaultSPUtils());\n    }\n\n    /**\n     * Put the boolean value in sp.\n     *\n     * @param key   The key of sp.\n     * @param value The value of sp.\n     */\n    public static void put(@NonNull final String key, final boolean value) {\n        put(key, value, getDefaultSPUtils());\n    }\n\n    /**\n     * Put the boolean value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public static void put(@NonNull final String key, final boolean value, final boolean isCommit) {\n        put(key, value, isCommit, getDefaultSPUtils());\n    }\n\n    /**\n     * Return the boolean value in sp.\n     *\n     * @param key The key of sp.\n     * @return the boolean value if sp exists or {@code false} otherwise\n     */\n    public static boolean getBoolean(@NonNull final String key) {\n        return getBoolean(key, getDefaultSPUtils());\n    }\n\n    /**\n     * Return the boolean value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @return the boolean value if sp exists or {@code defaultValue} otherwise\n     */\n    public static boolean getBoolean(@NonNull final String key, final boolean defaultValue) {\n        return getBoolean(key, defaultValue, getDefaultSPUtils());\n    }\n\n    /**\n     * Put the set of string value in sp.\n     *\n     * @param key   The key of sp.\n     * @param value The value of sp.\n     */\n    public static void put(@NonNull final String key, final Set<String> value) {\n        put(key, value, getDefaultSPUtils());\n    }\n\n    /**\n     * Put the set of string value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public static void put(@NonNull final String key,\n                           final Set<String> value,\n                           final boolean isCommit) {\n        put(key, value, isCommit, getDefaultSPUtils());\n    }\n\n    /**\n     * Return the set of string value in sp.\n     *\n     * @param key The key of sp.\n     * @return the set of string value if sp exists\n     * or {@code Collections.<String>emptySet()} otherwise\n     */\n    public static Set<String> getStringSet(@NonNull final String key) {\n        return getStringSet(key, getDefaultSPUtils());\n    }\n\n    /**\n     * Return the set of string value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @return the set of string value if sp exists or {@code defaultValue} otherwise\n     */\n    public static Set<String> getStringSet(@NonNull final String key,\n                                           final Set<String> defaultValue) {\n        return getStringSet(key, defaultValue, getDefaultSPUtils());\n    }\n\n    /**\n     * Return all values in sp.\n     *\n     * @return all values in sp\n     */\n    public static Map<String, ?> getAll() {\n        return getAll(getDefaultSPUtils());\n    }\n\n    /**\n     * Return whether the sp contains the preference.\n     *\n     * @param key The key of sp.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean contains(@NonNull final String key) {\n        return contains(key, getDefaultSPUtils());\n    }\n\n    /**\n     * Remove the preference in sp.\n     *\n     * @param key The key of sp.\n     */\n    public static void remove(@NonNull final String key) {\n        remove(key, getDefaultSPUtils());\n    }\n\n    /**\n     * Remove the preference in sp.\n     *\n     * @param key      The key of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public static void remove(@NonNull final String key, final boolean isCommit) {\n        remove(key, isCommit, getDefaultSPUtils());\n    }\n\n    /**\n     * Remove all preferences in sp.\n     */\n    public static void clear() {\n        clear(getDefaultSPUtils());\n    }\n\n    /**\n     * Remove all preferences in sp.\n     *\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public static void clear(final boolean isCommit) {\n        clear(isCommit, getDefaultSPUtils());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // dividing line\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Put the string value in sp.\n     *\n     * @param key     The key of sp.\n     * @param value   The value of sp.\n     * @param spUtils The instance of {@link SPUtils}.\n     */\n    public static void put(@NonNull final String key, final String value, @NonNull final SPUtils spUtils) {\n        spUtils.put(key, value);\n    }\n\n    /**\n     * Put the string value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     * @param spUtils  The instance of {@link SPUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final String value,\n                           final boolean isCommit,\n                           @NonNull final SPUtils spUtils) {\n        spUtils.put(key, value, isCommit);\n    }\n\n\n    /**\n     * Return the string value in sp.\n     *\n     * @param key     The key of sp.\n     * @param spUtils The instance of {@link SPUtils}.\n     * @return the string value if sp exists or {@code \"\"} otherwise\n     */\n    public static String getString(@NonNull final String key, @NonNull final SPUtils spUtils) {\n        return spUtils.getString(key);\n    }\n\n    /**\n     * Return the string value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @param spUtils      The instance of {@link SPUtils}.\n     * @return the string value if sp exists or {@code defaultValue} otherwise\n     */\n    public static String getString(@NonNull final String key,\n                                   final String defaultValue,\n                                   @NonNull final SPUtils spUtils) {\n        return spUtils.getString(key, defaultValue);\n    }\n\n\n    /**\n     * Put the int value in sp.\n     *\n     * @param key     The key of sp.\n     * @param value   The value of sp.\n     * @param spUtils The instance of {@link SPUtils}.\n     */\n    public static void put(@NonNull final String key, final int value, @NonNull final SPUtils spUtils) {\n        spUtils.put(key, value);\n    }\n\n    /**\n     * Put the int value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     * @param spUtils  The instance of {@link SPUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final int value,\n                           final boolean isCommit,\n                           @NonNull final SPUtils spUtils) {\n        spUtils.put(key, value, isCommit);\n    }\n\n    /**\n     * Return the int value in sp.\n     *\n     * @param key     The key of sp.\n     * @param spUtils The instance of {@link SPUtils}.\n     * @return the int value if sp exists or {@code -1} otherwise\n     */\n    public static int getInt(@NonNull final String key, @NonNull final SPUtils spUtils) {\n        return spUtils.getInt(key);\n    }\n\n    /**\n     * Return the int value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @param spUtils      The instance of {@link SPUtils}.\n     * @return the int value if sp exists or {@code defaultValue} otherwise\n     */\n    public static int getInt(@NonNull final String key, final int defaultValue, @NonNull final SPUtils spUtils) {\n        return spUtils.getInt(key, defaultValue);\n    }\n\n    /**\n     * Put the long value in sp.\n     *\n     * @param key     The key of sp.\n     * @param value   The value of sp.\n     * @param spUtils The instance of {@link SPUtils}.\n     */\n    public static void put(@NonNull final String key, final long value, @NonNull final SPUtils spUtils) {\n        spUtils.put(key, value);\n    }\n\n    /**\n     * Put the long value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     * @param spUtils  The instance of {@link SPUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final long value,\n                           final boolean isCommit,\n                           @NonNull final SPUtils spUtils) {\n        spUtils.put(key, value, isCommit);\n    }\n\n    /**\n     * Return the long value in sp.\n     *\n     * @param key     The key of sp.\n     * @param spUtils The instance of {@link SPUtils}.\n     * @return the long value if sp exists or {@code -1} otherwise\n     */\n    public static long getLong(@NonNull final String key, @NonNull final SPUtils spUtils) {\n        return spUtils.getLong(key);\n    }\n\n    /**\n     * Return the long value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @param spUtils      The instance of {@link SPUtils}.\n     * @return the long value if sp exists or {@code defaultValue} otherwise\n     */\n    public static long getLong(@NonNull final String key, final long defaultValue, @NonNull final SPUtils spUtils) {\n        return spUtils.getLong(key, defaultValue);\n    }\n\n    /**\n     * Put the float value in sp.\n     *\n     * @param key     The key of sp.\n     * @param value   The value of sp.\n     * @param spUtils The instance of {@link SPUtils}.\n     */\n    public static void put(@NonNull final String key, final float value, @NonNull final SPUtils spUtils) {\n        spUtils.put(key, value);\n    }\n\n    /**\n     * Put the float value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     * @param spUtils  The instance of {@link SPUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final float value,\n                           final boolean isCommit,\n                           @NonNull final SPUtils spUtils) {\n        spUtils.put(key, value, isCommit);\n    }\n\n    /**\n     * Return the float value in sp.\n     *\n     * @param key     The key of sp.\n     * @param spUtils The instance of {@link SPUtils}.\n     * @return the float value if sp exists or {@code -1f} otherwise\n     */\n    public static float getFloat(@NonNull final String key, @NonNull final SPUtils spUtils) {\n        return spUtils.getFloat(key);\n    }\n\n    /**\n     * Return the float value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @param spUtils      The instance of {@link SPUtils}.\n     * @return the float value if sp exists or {@code defaultValue} otherwise\n     */\n    public static float getFloat(@NonNull final String key, final float defaultValue, @NonNull final SPUtils spUtils) {\n        return spUtils.getFloat(key, defaultValue);\n    }\n\n    /**\n     * Put the boolean value in sp.\n     *\n     * @param key     The key of sp.\n     * @param value   The value of sp.\n     * @param spUtils The instance of {@link SPUtils}.\n     */\n    public static void put(@NonNull final String key, final boolean value, @NonNull final SPUtils spUtils) {\n        spUtils.put(key, value);\n    }\n\n    /**\n     * Put the boolean value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     * @param spUtils  The instance of {@link SPUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final boolean value,\n                           final boolean isCommit,\n                           @NonNull final SPUtils spUtils) {\n        spUtils.put(key, value, isCommit);\n    }\n\n    /**\n     * Return the boolean value in sp.\n     *\n     * @param key     The key of sp.\n     * @param spUtils The instance of {@link SPUtils}.\n     * @return the boolean value if sp exists or {@code false} otherwise\n     */\n    public static boolean getBoolean(@NonNull final String key, @NonNull final SPUtils spUtils) {\n        return spUtils.getBoolean(key);\n    }\n\n    /**\n     * Return the boolean value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @param spUtils      The instance of {@link SPUtils}.\n     * @return the boolean value if sp exists or {@code defaultValue} otherwise\n     */\n    public static boolean getBoolean(@NonNull final String key,\n                                     final boolean defaultValue,\n                                     @NonNull final SPUtils spUtils) {\n        return spUtils.getBoolean(key, defaultValue);\n    }\n\n    /**\n     * Put the set of string value in sp.\n     *\n     * @param key     The key of sp.\n     * @param value   The value of sp.\n     * @param spUtils The instance of {@link SPUtils}.\n     */\n    public static void put(@NonNull final String key, final Set<String> value, @NonNull final SPUtils spUtils) {\n        spUtils.put(key, value);\n    }\n\n    /**\n     * Put the set of string value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     * @param spUtils  The instance of {@link SPUtils}.\n     */\n    public static void put(@NonNull final String key,\n                           final Set<String> value,\n                           final boolean isCommit,\n                           @NonNull final SPUtils spUtils) {\n        spUtils.put(key, value, isCommit);\n    }\n\n    /**\n     * Return the set of string value in sp.\n     *\n     * @param key     The key of sp.\n     * @param spUtils The instance of {@link SPUtils}.\n     * @return the set of string value if sp exists\n     * or {@code Collections.<String>emptySet()} otherwise\n     */\n    public static Set<String> getStringSet(@NonNull final String key, @NonNull final SPUtils spUtils) {\n        return spUtils.getStringSet(key);\n    }\n\n    /**\n     * Return the set of string value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @param spUtils      The instance of {@link SPUtils}.\n     * @return the set of string value if sp exists or {@code defaultValue} otherwise\n     */\n    public static Set<String> getStringSet(@NonNull final String key,\n                                           final Set<String> defaultValue,\n                                           @NonNull final SPUtils spUtils) {\n        return spUtils.getStringSet(key, defaultValue);\n    }\n\n    /**\n     * Return all values in sp.\n     *\n     * @param spUtils The instance of {@link SPUtils}.\n     * @return all values in sp\n     */\n    public static Map<String, ?> getAll(@NonNull final SPUtils spUtils) {\n        return spUtils.getAll();\n    }\n\n    /**\n     * Return whether the sp contains the preference.\n     *\n     * @param key     The key of sp.\n     * @param spUtils The instance of {@link SPUtils}.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean contains(@NonNull final String key, @NonNull final SPUtils spUtils) {\n        return spUtils.contains(key);\n    }\n\n    /**\n     * Remove the preference in sp.\n     *\n     * @param key     The key of sp.\n     * @param spUtils The instance of {@link SPUtils}.\n     */\n    public static void remove(@NonNull final String key, @NonNull final SPUtils spUtils) {\n        spUtils.remove(key);\n    }\n\n    /**\n     * Remove the preference in sp.\n     *\n     * @param key      The key of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     * @param spUtils  The instance of {@link SPUtils}.\n     */\n    public static void remove(@NonNull final String key, final boolean isCommit, @NonNull final SPUtils spUtils) {\n        spUtils.remove(key, isCommit);\n    }\n\n    /**\n     * Remove all preferences in sp.\n     *\n     * @param spUtils The instance of {@link SPUtils}.\n     */\n    public static void clear(@NonNull final SPUtils spUtils) {\n        spUtils.clear();\n    }\n\n    /**\n     * Remove all preferences in sp.\n     *\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     * @param spUtils  The instance of {@link SPUtils}.\n     */\n    public static void clear(final boolean isCommit, @NonNull final SPUtils spUtils) {\n        spUtils.clear(isCommit);\n    }\n\n    private static SPUtils getDefaultSPUtils() {\n        return sDefaultSPUtils != null ? sDefaultSPUtils : SPUtils.getInstance();\n    }\n}"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/SPUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.SharedPreferences;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/02\n *     desc  : utils about shared preference\n * </pre>\n */\n@SuppressLint(\"ApplySharedPref\")\npublic final class SPUtils {\n\n    private static final Map<String, SPUtils> SP_UTILS_MAP = new HashMap<>();\n\n    private SharedPreferences sp;\n\n    /**\n     * Return the single {@link SPUtils} instance\n     *\n     * @return the single {@link SPUtils} instance\n     */\n    public static SPUtils getInstance() {\n        return getInstance(\"\", Context.MODE_PRIVATE);\n    }\n\n    /**\n     * Return the single {@link SPUtils} instance\n     *\n     * @param mode Operating mode.\n     * @return the single {@link SPUtils} instance\n     */\n    public static SPUtils getInstance(final int mode) {\n        return getInstance(\"\", mode);\n    }\n\n    /**\n     * Return the single {@link SPUtils} instance\n     *\n     * @param spName The name of sp.\n     * @return the single {@link SPUtils} instance\n     */\n    public static SPUtils getInstance(String spName) {\n        return getInstance(spName, Context.MODE_PRIVATE);\n    }\n\n    /**\n     * Return the single {@link SPUtils} instance\n     *\n     * @param spName The name of sp.\n     * @param mode   Operating mode.\n     * @return the single {@link SPUtils} instance\n     */\n    public static SPUtils getInstance(String spName, final int mode) {\n        if (isSpace(spName)) spName = \"spUtils\";\n        SPUtils spUtils = SP_UTILS_MAP.get(spName);\n        if (spUtils == null) {\n            synchronized (SPUtils.class) {\n                spUtils = SP_UTILS_MAP.get(spName);\n                if (spUtils == null) {\n                    spUtils = new SPUtils(spName, mode);\n                    SP_UTILS_MAP.put(spName, spUtils);\n                }\n            }\n        }\n        return spUtils;\n    }\n\n    private SPUtils(final String spName) {\n        sp = Utils.getApp().getSharedPreferences(spName, Context.MODE_PRIVATE);\n    }\n\n    private SPUtils(final String spName, final int mode) {\n        sp = Utils.getApp().getSharedPreferences(spName, mode);\n    }\n\n    /**\n     * Put the string value in sp.\n     *\n     * @param key   The key of sp.\n     * @param value The value of sp.\n     */\n    public void put(@NonNull final String key, final String value) {\n        put(key, value, false);\n    }\n\n    /**\n     * Put the string value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public void put(@NonNull final String key, final String value, final boolean isCommit) {\n        if (isCommit) {\n            sp.edit().putString(key, value).commit();\n        } else {\n            sp.edit().putString(key, value).apply();\n        }\n    }\n\n    /**\n     * Return the string value in sp.\n     *\n     * @param key The key of sp.\n     * @return the string value if sp exists or {@code \"\"} otherwise\n     */\n    public String getString(@NonNull final String key) {\n        return getString(key, \"\");\n    }\n\n    /**\n     * Return the string value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @return the string value if sp exists or {@code defaultValue} otherwise\n     */\n    public String getString(@NonNull final String key, final String defaultValue) {\n        return sp.getString(key, defaultValue);\n    }\n\n    /**\n     * Put the int value in sp.\n     *\n     * @param key   The key of sp.\n     * @param value The value of sp.\n     */\n    public void put(@NonNull final String key, final int value) {\n        put(key, value, false);\n    }\n\n    /**\n     * Put the int value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public void put(@NonNull final String key, final int value, final boolean isCommit) {\n        if (isCommit) {\n            sp.edit().putInt(key, value).commit();\n        } else {\n            sp.edit().putInt(key, value).apply();\n        }\n    }\n\n    /**\n     * Return the int value in sp.\n     *\n     * @param key The key of sp.\n     * @return the int value if sp exists or {@code -1} otherwise\n     */\n    public int getInt(@NonNull final String key) {\n        return getInt(key, -1);\n    }\n\n    /**\n     * Return the int value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @return the int value if sp exists or {@code defaultValue} otherwise\n     */\n    public int getInt(@NonNull final String key, final int defaultValue) {\n        return sp.getInt(key, defaultValue);\n    }\n\n    /**\n     * Put the long value in sp.\n     *\n     * @param key   The key of sp.\n     * @param value The value of sp.\n     */\n    public void put(@NonNull final String key, final long value) {\n        put(key, value, false);\n    }\n\n    /**\n     * Put the long value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public void put(@NonNull final String key, final long value, final boolean isCommit) {\n        if (isCommit) {\n            sp.edit().putLong(key, value).commit();\n        } else {\n            sp.edit().putLong(key, value).apply();\n        }\n    }\n\n    /**\n     * Return the long value in sp.\n     *\n     * @param key The key of sp.\n     * @return the long value if sp exists or {@code -1} otherwise\n     */\n    public long getLong(@NonNull final String key) {\n        return getLong(key, -1L);\n    }\n\n    /**\n     * Return the long value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @return the long value if sp exists or {@code defaultValue} otherwise\n     */\n    public long getLong(@NonNull final String key, final long defaultValue) {\n        return sp.getLong(key, defaultValue);\n    }\n\n    /**\n     * Put the float value in sp.\n     *\n     * @param key   The key of sp.\n     * @param value The value of sp.\n     */\n    public void put(@NonNull final String key, final float value) {\n        put(key, value, false);\n    }\n\n    /**\n     * Put the float value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public void put(@NonNull final String key, final float value, final boolean isCommit) {\n        if (isCommit) {\n            sp.edit().putFloat(key, value).commit();\n        } else {\n            sp.edit().putFloat(key, value).apply();\n        }\n    }\n\n    /**\n     * Return the float value in sp.\n     *\n     * @param key The key of sp.\n     * @return the float value if sp exists or {@code -1f} otherwise\n     */\n    public float getFloat(@NonNull final String key) {\n        return getFloat(key, -1f);\n    }\n\n    /**\n     * Return the float value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @return the float value if sp exists or {@code defaultValue} otherwise\n     */\n    public float getFloat(@NonNull final String key, final float defaultValue) {\n        return sp.getFloat(key, defaultValue);\n    }\n\n    /**\n     * Put the boolean value in sp.\n     *\n     * @param key   The key of sp.\n     * @param value The value of sp.\n     */\n    public void put(@NonNull final String key, final boolean value) {\n        put(key, value, false);\n    }\n\n    /**\n     * Put the boolean value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public void put(@NonNull final String key, final boolean value, final boolean isCommit) {\n        if (isCommit) {\n            sp.edit().putBoolean(key, value).commit();\n        } else {\n            sp.edit().putBoolean(key, value).apply();\n        }\n    }\n\n    /**\n     * Return the boolean value in sp.\n     *\n     * @param key The key of sp.\n     * @return the boolean value if sp exists or {@code false} otherwise\n     */\n    public boolean getBoolean(@NonNull final String key) {\n        return getBoolean(key, false);\n    }\n\n    /**\n     * Return the boolean value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @return the boolean value if sp exists or {@code defaultValue} otherwise\n     */\n    public boolean getBoolean(@NonNull final String key, final boolean defaultValue) {\n        return sp.getBoolean(key, defaultValue);\n    }\n\n    /**\n     * Put the set of string value in sp.\n     *\n     * @param key   The key of sp.\n     * @param value The value of sp.\n     */\n    public void put(@NonNull final String key, final Set<String> value) {\n        put(key, value, false);\n    }\n\n    /**\n     * Put the set of string value in sp.\n     *\n     * @param key      The key of sp.\n     * @param value    The value of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public void put(@NonNull final String key,\n                    final Set<String> value,\n                    final boolean isCommit) {\n        if (isCommit) {\n            sp.edit().putStringSet(key, value).commit();\n        } else {\n            sp.edit().putStringSet(key, value).apply();\n        }\n    }\n\n    /**\n     * Return the set of string value in sp.\n     *\n     * @param key The key of sp.\n     * @return the set of string value if sp exists\n     * or {@code Collections.<String>emptySet()} otherwise\n     */\n    public Set<String> getStringSet(@NonNull final String key) {\n        return getStringSet(key, Collections.<String>emptySet());\n    }\n\n    /**\n     * Return the set of string value in sp.\n     *\n     * @param key          The key of sp.\n     * @param defaultValue The default value if the sp doesn't exist.\n     * @return the set of string value if sp exists or {@code defaultValue} otherwise\n     */\n    public Set<String> getStringSet(@NonNull final String key,\n                                    final Set<String> defaultValue) {\n        return sp.getStringSet(key, defaultValue);\n    }\n\n    /**\n     * Return all values in sp.\n     *\n     * @return all values in sp\n     */\n    public Map<String, ?> getAll() {\n        return sp.getAll();\n    }\n\n    /**\n     * Return whether the sp contains the preference.\n     *\n     * @param key The key of sp.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public boolean contains(@NonNull final String key) {\n        return sp.contains(key);\n    }\n\n    /**\n     * Remove the preference in sp.\n     *\n     * @param key The key of sp.\n     */\n    public void remove(@NonNull final String key) {\n        remove(key, false);\n    }\n\n    /**\n     * Remove the preference in sp.\n     *\n     * @param key      The key of sp.\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public void remove(@NonNull final String key, final boolean isCommit) {\n        if (isCommit) {\n            sp.edit().remove(key).commit();\n        } else {\n            sp.edit().remove(key).apply();\n        }\n    }\n\n    /**\n     * Remove all preferences in sp.\n     */\n    public void clear() {\n        clear(false);\n    }\n\n    /**\n     * Remove all preferences in sp.\n     *\n     * @param isCommit True to use {@link SharedPreferences.Editor#commit()},\n     *                 false to use {@link SharedPreferences.Editor#apply()}\n     */\n    public void clear(final boolean isCommit) {\n        if (isCommit) {\n            sp.edit().clear().commit();\n        } else {\n            sp.edit().clear().apply();\n        }\n    }\n\n    private static boolean isSpace(final String s) {\n        if (s == null) return true;\n        for (int i = 0, len = s.length(); i < len; ++i) {\n            if (!Character.isWhitespace(s.charAt(i))) {\n                return false;\n            }\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ScreenUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.app.KeyguardManager;\nimport android.content.Context;\nimport android.content.pm.ActivityInfo;\nimport android.content.res.Configuration;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.Point;\nimport android.os.Build;\nimport android.provider.Settings;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresPermission;\nimport android.util.DisplayMetrics;\nimport android.view.Surface;\nimport android.view.View;\nimport android.view.Window;\nimport android.view.WindowManager;\n\nimport static android.Manifest.permission.WRITE_SETTINGS;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/02\n *     desc  : utils about screen\n * </pre>\n */\npublic final class ScreenUtils {\n\n    private ScreenUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return the width of screen, in pixel.\n     *\n     * @return the width of screen, in pixel\n     */\n    public static int getScreenWidth() {\n        WindowManager wm = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE);\n        if (wm == null) return -1;\n        Point point = new Point();\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            wm.getDefaultDisplay().getRealSize(point);\n        } else {\n            wm.getDefaultDisplay().getSize(point);\n        }\n        return point.x;\n    }\n\n    /**\n     * Return the height of screen, in pixel.\n     *\n     * @return the height of screen, in pixel\n     */\n    public static int getScreenHeight() {\n        WindowManager wm = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE);\n        if (wm == null) return -1;\n        Point point = new Point();\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            wm.getDefaultDisplay().getRealSize(point);\n        } else {\n            wm.getDefaultDisplay().getSize(point);\n        }\n        return point.y;\n    }\n\n    /**\n     * Return the application's width of screen, in pixel.\n     *\n     * @return the application's width of screen, in pixel\n     */\n    public static int getAppScreenWidth() {\n        WindowManager wm = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE);\n        if (wm == null) return -1;\n        Point point = new Point();\n        wm.getDefaultDisplay().getSize(point);\n        return point.x;\n    }\n\n    /**\n     * Return the application's height of screen, in pixel.\n     *\n     * @return the application's height of screen, in pixel\n     */\n    public static int getAppScreenHeight() {\n        WindowManager wm = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE);\n        if (wm == null) return -1;\n        Point point = new Point();\n        wm.getDefaultDisplay().getSize(point);\n        return point.y;\n    }\n\n    /**\n     * Return the density of screen.\n     *\n     * @return the density of screen\n     */\n    public static float getScreenDensity() {\n        return Resources.getSystem().getDisplayMetrics().density;\n    }\n\n    /**\n     * Return the screen density expressed as dots-per-inch.\n     *\n     * @return the screen density expressed as dots-per-inch\n     */\n    public static int getScreenDensityDpi() {\n        return Resources.getSystem().getDisplayMetrics().densityDpi;\n    }\n\n    /**\n     * Return the exact physical pixels per inch of the screen in the Y dimension.\n     *\n     * @return the exact physical pixels per inch of the screen in the Y dimension\n     */\n    public static float getScreenXDpi() {\n        return Resources.getSystem().getDisplayMetrics().xdpi;\n    }\n\n    /**\n     * Return the exact physical pixels per inch of the screen in the Y dimension.\n     *\n     * @return the exact physical pixels per inch of the screen in the Y dimension\n     */\n    public static float getScreenYDpi() {\n        return Resources.getSystem().getDisplayMetrics().ydpi;\n    }\n\n    /**\n     * Return the distance between the given View's X (start point of View's width) and the screen width.\n     *\n     * @return the distance between the given View's X (start point of View's width) and the screen width.\n     */\n    public int calculateDistanceByX(View view) {\n        int[] point = new int[2];\n        view.getLocationOnScreen(point);\n        return getScreenWidth() - point[0];\n    }\n\n    /**\n     * Return the distance between the given View's Y (start point of View's height) and the screen height.\n     *\n     * @return the distance between the given View's Y (start point of View's height) and the screen height.\n     */\n    public int calculateDistanceByY(View view) {\n        int[] point = new int[2];\n        view.getLocationOnScreen(point);\n        return getScreenHeight() - point[1];\n    }\n\n    /**\n     * Return the X coordinate of the given View on the screen.\n     *\n     * @return X coordinate of the given View on the screen.\n     */\n    public int getViewX(View view) {\n        int[] point = new int[2];\n        view.getLocationOnScreen(point);\n        return point[0];\n    }\n\n    /**\n     * Return the Y coordinate of the given View on the screen.\n     *\n     * @return Y coordinate of the given View on the screen.\n     */\n    public int getViewY(View view) {\n        int[] point = new int[2];\n        view.getLocationOnScreen(point);\n        return point[1];\n    }\n\n    /**\n     * Set full screen.\n     *\n     * @param activity The activity.\n     */\n    public static void setFullScreen(@NonNull final Activity activity) {\n        activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n    }\n\n    /**\n     * Set non full screen.\n     *\n     * @param activity The activity.\n     */\n    public static void setNonFullScreen(@NonNull final Activity activity) {\n        activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n    }\n\n    /**\n     * Toggle full screen.\n     *\n     * @param activity The activity.\n     */\n    public static void toggleFullScreen(@NonNull final Activity activity) {\n        boolean isFullScreen = isFullScreen(activity);\n        Window window = activity.getWindow();\n        if (isFullScreen) {\n            window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n        } else {\n            window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n        }\n    }\n\n    /**\n     * Return whether screen is full.\n     *\n     * @param activity The activity.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isFullScreen(@NonNull final Activity activity) {\n        int fullScreenFlag = WindowManager.LayoutParams.FLAG_FULLSCREEN;\n        return (activity.getWindow().getAttributes().flags & fullScreenFlag) == fullScreenFlag;\n    }\n\n    /**\n     * Set the screen to landscape.\n     *\n     * @param activity The activity.\n     */\n    @SuppressLint(\"SourceLockedOrientationActivity\")\n    public static void setLandscape(@NonNull final Activity activity) {\n        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);\n    }\n\n    /**\n     * Set the screen to portrait.\n     *\n     * @param activity The activity.\n     */\n    @SuppressLint(\"SourceLockedOrientationActivity\")\n    public static void setPortrait(@NonNull final Activity activity) {\n        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\n    }\n\n    /**\n     * Return whether screen is landscape.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isLandscape() {\n        return Utils.getApp().getResources().getConfiguration().orientation\n                == Configuration.ORIENTATION_LANDSCAPE;\n    }\n\n    /**\n     * Return whether screen is portrait.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isPortrait() {\n        return Utils.getApp().getResources().getConfiguration().orientation\n                == Configuration.ORIENTATION_PORTRAIT;\n    }\n\n    /**\n     * Return the rotation of screen.\n     *\n     * @param activity The activity.\n     * @return the rotation of screen\n     */\n    public static int getScreenRotation(@NonNull final Activity activity) {\n        switch (activity.getWindowManager().getDefaultDisplay().getRotation()) {\n            case Surface.ROTATION_0:\n                return 0;\n            case Surface.ROTATION_90:\n                return 90;\n            case Surface.ROTATION_180:\n                return 180;\n            case Surface.ROTATION_270:\n                return 270;\n            default:\n                return 0;\n        }\n    }\n\n    /**\n     * Return the bitmap of screen.\n     *\n     * @param activity The activity.\n     * @return the bitmap of screen\n     */\n    public static Bitmap screenShot(@NonNull final Activity activity) {\n        return screenShot(activity, false);\n    }\n\n    /**\n     * Return the bitmap of screen.\n     *\n     * @param activity          The activity.\n     * @param isDeleteStatusBar True to delete status bar, false otherwise.\n     * @return the bitmap of screen\n     */\n    public static Bitmap screenShot(@NonNull final Activity activity, boolean isDeleteStatusBar) {\n        View decorView = activity.getWindow().getDecorView();\n        Bitmap bmp = UtilsBridge.view2Bitmap(decorView);\n        DisplayMetrics dm = new DisplayMetrics();\n        activity.getWindowManager().getDefaultDisplay().getMetrics(dm);\n        if (isDeleteStatusBar) {\n            int statusBarHeight = UtilsBridge.getStatusBarHeight();\n            return Bitmap.createBitmap(\n                    bmp,\n                    0,\n                    statusBarHeight,\n                    dm.widthPixels,\n                    dm.heightPixels - statusBarHeight\n            );\n        } else {\n            return Bitmap.createBitmap(bmp, 0, 0, dm.widthPixels, dm.heightPixels);\n        }\n    }\n\n    /**\n     * Return whether screen is locked.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isScreenLock() {\n        KeyguardManager km =\n                (KeyguardManager) Utils.getApp().getSystemService(Context.KEYGUARD_SERVICE);\n        if (km == null) return false;\n        return km.inKeyguardRestrictedInputMode();\n    }\n\n    /**\n     * Set the duration of sleep.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.WRITE_SETTINGS\" />}</p>\n     *\n     * @param duration The duration.\n     */\n    @RequiresPermission(WRITE_SETTINGS)\n    public static void setSleepDuration(final int duration) {\n        Settings.System.putInt(\n                Utils.getApp().getContentResolver(),\n                Settings.System.SCREEN_OFF_TIMEOUT,\n                duration\n        );\n    }\n\n    /**\n     * Return the duration of sleep.\n     *\n     * @return the duration of sleep.\n     */\n    public static int getSleepDuration() {\n        try {\n            return Settings.System.getInt(\n                    Utils.getApp().getContentResolver(),\n                    Settings.System.SCREEN_OFF_TIMEOUT\n            );\n        } catch (Settings.SettingNotFoundException e) {\n            e.printStackTrace();\n            return -123;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ServiceUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.ActivityManager;\nimport android.app.ActivityManager.RunningServiceInfo;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.os.Build;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/02\n *     desc  : utils about service\n * </pre>\n */\npublic final class ServiceUtils {\n\n    private ServiceUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return all of the services are running.\n     *\n     * @return all of the services are running\n     */\n    public static Set<String> getAllRunningServices() {\n        ActivityManager am = (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);\n        List<RunningServiceInfo> info = am.getRunningServices(0x7FFFFFFF);\n        Set<String> names = new HashSet<>();\n        if (info == null || info.size() == 0) return null;\n        for (RunningServiceInfo aInfo : info) {\n            names.add(aInfo.service.getClassName());\n        }\n        return names;\n    }\n\n    /**\n     * Start the service.\n     *\n     * @param className The name of class.\n     */\n    public static void startService(@NonNull final String className) {\n        try {\n            startService(Class.forName(className));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * Start the service.\n     *\n     * @param cls The service class.\n     */\n    public static void startService(@NonNull final Class<?> cls) {\n        startService(new Intent(Utils.getApp(), cls));\n    }\n\n    /**\n     * Start the service.\n     *\n     * @param intent The intent.\n     */\n    public static void startService(Intent intent) {\n        try {\n            intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                Utils.getApp().startForegroundService(intent);\n            } else {\n                Utils.getApp().startService(intent);\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * Stop the service.\n     *\n     * @param className The name of class.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean stopService(@NonNull final String className) {\n        try {\n            return stopService(Class.forName(className));\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * Stop the service.\n     *\n     * @param cls The name of class.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean stopService(@NonNull final Class<?> cls) {\n        return stopService(new Intent(Utils.getApp(), cls));\n    }\n\n    /**\n     * Stop the service.\n     *\n     * @param intent The intent.\n     * @return {@code true}: success<br>{@code false}: fail\n     */\n    public static boolean stopService(@NonNull Intent intent) {\n        try {\n            return Utils.getApp().stopService(intent);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * Bind the service.\n     *\n     * @param className The name of class.\n     * @param conn      The ServiceConnection object.\n     * @param flags     Operation options for the binding.\n     *                  <ul>\n     *                  <li>0</li>\n     *                  <li>{@link Context#BIND_AUTO_CREATE}</li>\n     *                  <li>{@link Context#BIND_DEBUG_UNBIND}</li>\n     *                  <li>{@link Context#BIND_NOT_FOREGROUND}</li>\n     *                  <li>{@link Context#BIND_ABOVE_CLIENT}</li>\n     *                  <li>{@link Context#BIND_ALLOW_OOM_MANAGEMENT}</li>\n     *                  <li>{@link Context#BIND_WAIVE_PRIORITY}</li>\n     *                  </ul>\n     */\n    public static void bindService(@NonNull final String className,\n                                   @NonNull final ServiceConnection conn,\n                                   final int flags) {\n        try {\n            bindService(Class.forName(className), conn, flags);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * Bind the service.\n     *\n     * @param cls   The service class.\n     * @param conn  The ServiceConnection object.\n     * @param flags Operation options for the binding.\n     *              <ul>\n     *              <li>0</li>\n     *              <li>{@link Context#BIND_AUTO_CREATE}</li>\n     *              <li>{@link Context#BIND_DEBUG_UNBIND}</li>\n     *              <li>{@link Context#BIND_NOT_FOREGROUND}</li>\n     *              <li>{@link Context#BIND_ABOVE_CLIENT}</li>\n     *              <li>{@link Context#BIND_ALLOW_OOM_MANAGEMENT}</li>\n     *              <li>{@link Context#BIND_WAIVE_PRIORITY}</li>\n     *              </ul>\n     */\n    public static void bindService(@NonNull final Class<?> cls,\n                                   @NonNull final ServiceConnection conn,\n                                   final int flags) {\n        bindService(new Intent(Utils.getApp(), cls), conn, flags);\n    }\n\n    /**\n     * Bind the service.\n     *\n     * @param intent The intent.\n     * @param conn   The ServiceConnection object.\n     * @param flags  Operation options for the binding.\n     *               <ul>\n     *               <li>0</li>\n     *               <li>{@link Context#BIND_AUTO_CREATE}</li>\n     *               <li>{@link Context#BIND_DEBUG_UNBIND}</li>\n     *               <li>{@link Context#BIND_NOT_FOREGROUND}</li>\n     *               <li>{@link Context#BIND_ABOVE_CLIENT}</li>\n     *               <li>{@link Context#BIND_ALLOW_OOM_MANAGEMENT}</li>\n     *               <li>{@link Context#BIND_WAIVE_PRIORITY}</li>\n     *               </ul>\n     */\n    public static void bindService(@NonNull final Intent intent,\n                                   @NonNull final ServiceConnection conn,\n                                   final int flags) {\n        try {\n            Utils.getApp().bindService(intent, conn, flags);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * Unbind the service.\n     *\n     * @param conn The ServiceConnection object.\n     */\n    public static void unbindService(@NonNull final ServiceConnection conn) {\n        Utils.getApp().unbindService(conn);\n    }\n\n    /**\n     * Return whether service is running.\n     *\n     * @param cls The service class.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isServiceRunning(@NonNull final Class<?> cls) {\n        return isServiceRunning(cls.getName());\n    }\n\n    /**\n     * Return whether service is running.\n     *\n     * @param className The name of class.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isServiceRunning(@NonNull final String className) {\n        try {\n            ActivityManager am = (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);\n            List<RunningServiceInfo> info = am.getRunningServices(0x7FFFFFFF);\n            if (info == null || info.size() == 0) return false;\n            for (RunningServiceInfo aInfo : info) {\n                if (className.equals(aInfo.service.getClassName())) return true;\n            }\n            return false;\n        } catch (Exception ignore) {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ShadowUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.res.ColorStateList;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.ColorFilter;\nimport android.graphics.LinearGradient;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.PixelFormat;\nimport android.graphics.PorterDuff.Mode;\nimport android.graphics.RadialGradient;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.graphics.Region;\nimport android.graphics.Shader;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.StateListDrawable;\n\nimport androidx.core.graphics.drawable.DrawableCompat;\nimport androidx.core.view.ViewCompat;\nimport android.util.StateSet;\nimport android.view.View;\n\n/**\n * <pre>\n *     author: blankj\n *     blog  : http://blankj.com\n *     time  : 2019/09/13\n *     desc  : utils about shadow\n * </pre>\n */\npublic class ShadowUtils {\n\n    private static final int SHADOW_TAG = -16;\n\n    public static void apply(View... views) {\n        if (views == null) return;\n        for (View view : views) {\n            apply(view, new Config());\n        }\n    }\n\n    public static void apply(View view, Config builder) {\n        if (view == null || builder == null) return;\n        Drawable background = view.getBackground();\n        Object tag = view.getTag(SHADOW_TAG);\n        if (tag instanceof Drawable) {\n            ViewCompat.setBackground(view, (Drawable) tag);\n        } else {\n            background = builder.apply(background);\n            ViewCompat.setBackground(view, background);\n            view.setTag(SHADOW_TAG, background);\n        }\n    }\n\n    public static class Config {\n\n        private static final int SHADOW_COLOR_DEFAULT = 0x44000000;\n        private static final int SHADOW_SIZE          = UtilsBridge.dp2px(8);\n\n        private float   mShadowRadius         = -1;\n        private float   mShadowSizeNormal     = -1;\n        private float   mShadowSizePressed    = -1;\n        private float   mShadowMaxSizeNormal  = -1;\n        private float   mShadowMaxSizePressed = -1;\n        private int     mShadowColorNormal    = SHADOW_COLOR_DEFAULT;\n        private int     mShadowColorPressed   = SHADOW_COLOR_DEFAULT;\n        private boolean isCircle              = false;\n\n        public Config() {\n        }\n\n        public Config setShadowRadius(float radius) {\n            this.mShadowRadius = radius;\n            if (isCircle) {\n                throw new IllegalArgumentException(\"Set circle needn't set radius.\");\n            }\n            return this;\n        }\n\n        public Config setCircle() {\n            isCircle = true;\n            if (mShadowRadius != -1) {\n                throw new IllegalArgumentException(\"Set circle needn't set radius.\");\n            }\n            return this;\n        }\n\n        public Config setShadowSize(int size) {\n            return setShadowSize(size, size);\n        }\n\n        public Config setShadowSize(int sizeNormal, int sizePressed) {\n            this.mShadowSizeNormal = sizeNormal;\n            this.mShadowSizePressed = sizePressed;\n            return this;\n        }\n\n        public Config setShadowMaxSize(int maxSize) {\n            return setShadowMaxSize(maxSize, maxSize);\n        }\n\n        public Config setShadowMaxSize(int maxSizeNormal, int maxSizePressed) {\n            this.mShadowMaxSizeNormal = maxSizeNormal;\n            this.mShadowMaxSizePressed = maxSizePressed;\n            return this;\n        }\n\n        public Config setShadowColor(int color) {\n            return setShadowColor(color, color);\n        }\n\n        public Config setShadowColor(int colorNormal, int colorPressed) {\n            this.mShadowColorNormal = colorNormal;\n            this.mShadowColorPressed = colorPressed;\n            return this;\n        }\n\n        Drawable apply(Drawable src) {\n            if (src == null) {\n                src = new ColorDrawable(Color.TRANSPARENT);\n            }\n            StateListDrawable drawable = new StateListDrawable();\n            drawable.addState(\n                    new int[]{android.R.attr.state_pressed},\n                    new ShadowDrawable(src, getShadowRadius(), getShadowSizeNormal(),\n                            getShadowMaxSizeNormal(), mShadowColorPressed, isCircle)\n            );\n            drawable.addState(\n                    StateSet.WILD_CARD,\n                    new ShadowDrawable(src, getShadowRadius(), getShadowSizePressed(),\n                            getShadowMaxSizePressed(), mShadowColorNormal, isCircle)\n            );\n            return drawable;\n        }\n\n        private float getShadowRadius() {\n            if (mShadowRadius < 0) {\n                mShadowRadius = 0;\n            }\n            return mShadowRadius;\n        }\n\n        private float getShadowSizeNormal() {\n            if (mShadowSizeNormal == -1) {\n                mShadowSizeNormal = SHADOW_SIZE;\n            }\n            return mShadowSizeNormal;\n        }\n\n        private float getShadowSizePressed() {\n            if (mShadowSizePressed == -1) {\n                mShadowSizePressed = getShadowSizeNormal();\n            }\n            return mShadowSizePressed;\n        }\n\n        private float getShadowMaxSizeNormal() {\n            if (mShadowMaxSizeNormal == -1) {\n                mShadowMaxSizeNormal = getShadowSizeNormal();\n            }\n            return mShadowMaxSizeNormal;\n        }\n\n        private float getShadowMaxSizePressed() {\n            if (mShadowMaxSizePressed == -1) {\n                mShadowMaxSizePressed = getShadowSizePressed();\n            }\n            return mShadowMaxSizePressed;\n        }\n    }\n\n    public static class ShadowDrawable extends DrawableWrapper {\n        // used to calculate content padding\n        private static final double COS_45 = Math.cos(Math.toRadians(45));\n\n        private float mShadowMultiplier = 1f;\n\n        private float mShadowTopScale    = 1f;\n        private float mShadowHorizScale  = 1f;\n        private float mShadowBottomScale = 1f;\n\n        private Paint mCornerShadowPaint;\n        private Paint mEdgeShadowPaint;\n\n        private RectF mContentBounds;\n\n        private float mCornerRadius;\n\n        private Path mCornerShadowPath;\n\n        // updated value with inset\n        private float mMaxShadowSize;\n        // actual value set by developer\n        private float mRawMaxShadowSize;\n\n        // multiplied value to account for shadow offset\n        private float mShadowSize;\n        // actual value set by developer\n        private float mRawShadowSize;\n\n        private boolean mDirty = true;\n\n        private final int mShadowStartColor;\n        private final int mShadowEndColor;\n\n        private boolean mAddPaddingForCorners = false;\n\n        private float mRotation;\n\n        private boolean isCircle;\n\n        public ShadowDrawable(Drawable content, float radius,\n                              float shadowSize, float maxShadowSize, int shadowColor, boolean isCircle) {\n            super(content);\n            mShadowStartColor = shadowColor;\n            mShadowEndColor = mShadowStartColor & 0x00ffffff;\n            this.isCircle = isCircle;\n            if (isCircle) {\n                mShadowMultiplier = 1;\n                mShadowTopScale = 1;\n                mShadowHorizScale = 1;\n                mShadowBottomScale = 1;\n            }\n\n            mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);\n            mCornerShadowPaint.setStyle(Paint.Style.FILL);\n            mCornerRadius = Math.round(radius);\n            mContentBounds = new RectF();\n            mEdgeShadowPaint = new Paint(mCornerShadowPaint);\n            mEdgeShadowPaint.setAntiAlias(false);\n            setShadowSize(shadowSize, maxShadowSize);\n        }\n\n        /**\n         * Casts the value to an even integer.\n         */\n        private static int toEven(float value) {\n            int i = Math.round(value);\n            return (i % 2 == 1) ? i - 1 : i;\n        }\n\n        public void setAddPaddingForCorners(boolean addPaddingForCorners) {\n            mAddPaddingForCorners = addPaddingForCorners;\n            invalidateSelf();\n        }\n\n        @Override\n        public void setAlpha(int alpha) {\n            super.setAlpha(alpha);\n            mCornerShadowPaint.setAlpha(alpha);\n            mEdgeShadowPaint.setAlpha(alpha);\n        }\n\n        @Override\n        protected void onBoundsChange(Rect bounds) {\n            mDirty = true;\n        }\n\n        void setShadowSize(float shadowSize, float maxShadowSize) {\n            if (shadowSize < 0 || maxShadowSize < 0) {\n                throw new IllegalArgumentException(\"invalid shadow size\");\n            }\n            shadowSize = toEven(shadowSize);\n            maxShadowSize = toEven(maxShadowSize);\n            if (shadowSize > maxShadowSize) {\n                shadowSize = maxShadowSize;\n            }\n            if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {\n                return;\n            }\n            mRawShadowSize = shadowSize;\n            mRawMaxShadowSize = maxShadowSize;\n            mShadowSize = Math.round(shadowSize * mShadowMultiplier);\n            mMaxShadowSize = maxShadowSize;\n            mDirty = true;\n            invalidateSelf();\n        }\n\n        @Override\n        public boolean getPadding(Rect padding) {\n            int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,\n                    mAddPaddingForCorners));\n            int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,\n                    mAddPaddingForCorners));\n            padding.set(hOffset, vOffset, hOffset, vOffset);\n            return true;\n        }\n\n        private float calculateVerticalPadding(float maxShadowSize, float cornerRadius,\n                                               boolean addPaddingForCorners) {\n            if (addPaddingForCorners) {\n                return (float) (maxShadowSize * mShadowMultiplier + (1 - COS_45) * cornerRadius);\n            } else {\n                return maxShadowSize * mShadowMultiplier;\n            }\n        }\n\n        private static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,\n                                                        boolean addPaddingForCorners) {\n            if (addPaddingForCorners) {\n                return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);\n            } else {\n                return maxShadowSize;\n            }\n        }\n\n        @Override\n        public int getOpacity() {\n            return PixelFormat.TRANSLUCENT;\n        }\n\n        public void setCornerRadius(float radius) {\n            radius = Math.round(radius);\n            if (mCornerRadius == radius) {\n                return;\n            }\n            mCornerRadius = radius;\n            mDirty = true;\n            invalidateSelf();\n        }\n\n        @Override\n        public void draw(Canvas canvas) {\n            if (mDirty) {\n                buildComponents(getBounds());\n                mDirty = false;\n            }\n            drawShadow(canvas);\n\n            super.draw(canvas);\n        }\n\n        final void setRotation(float rotation) {\n            if (mRotation != rotation) {\n                mRotation = rotation;\n                invalidateSelf();\n            }\n        }\n\n        private void drawShadow(Canvas canvas) {\n            if (isCircle) {\n                int saved = canvas.save();\n                canvas.translate(mContentBounds.centerX(), mContentBounds.centerY());\n                canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);\n                canvas.restoreToCount(saved);\n                return;\n            }\n\n            final int rotateSaved = canvas.save();\n            canvas.rotate(mRotation, mContentBounds.centerX(), mContentBounds.centerY());\n\n            final float edgeShadowTop = -mCornerRadius - mShadowSize;\n            final float shadowOffset = mCornerRadius;\n            final boolean drawHorizontalEdges = mContentBounds.width() - 2 * shadowOffset > 0;\n            final boolean drawVerticalEdges = mContentBounds.height() - 2 * shadowOffset > 0;\n\n            final float shadowOffsetTop = mRawShadowSize - (mRawShadowSize * mShadowTopScale);\n            final float shadowOffsetHorizontal = mRawShadowSize - (mRawShadowSize * mShadowHorizScale);\n            final float shadowOffsetBottom = mRawShadowSize - (mRawShadowSize * mShadowBottomScale);\n\n            final float shadowScaleHorizontal = shadowOffset == 0 ? 1 : shadowOffset / (shadowOffset + shadowOffsetHorizontal);\n            final float shadowScaleTop = shadowOffset == 0 ? 1 : shadowOffset / (shadowOffset + shadowOffsetTop);\n            final float shadowScaleBottom = shadowOffset == 0 ? 1 : shadowOffset / (shadowOffset + shadowOffsetBottom);\n\n            // LT\n            int saved = canvas.save();\n            canvas.translate(mContentBounds.left + shadowOffset, mContentBounds.top + shadowOffset);\n            canvas.scale(shadowScaleHorizontal, shadowScaleTop);\n            canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);\n            if (drawHorizontalEdges) {\n                // TE\n                canvas.scale(1f / shadowScaleHorizontal, 1f);\n                canvas.drawRect(0, edgeShadowTop,\n                        mContentBounds.width() - 2 * shadowOffset, -mCornerRadius,\n                        mEdgeShadowPaint);\n            }\n            canvas.restoreToCount(saved);\n            // RB\n            saved = canvas.save();\n            canvas.translate(mContentBounds.right - shadowOffset, mContentBounds.bottom - shadowOffset);\n            canvas.scale(shadowScaleHorizontal, shadowScaleBottom);\n            canvas.rotate(180f);\n            canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);\n            if (drawHorizontalEdges) {\n                // BE\n                canvas.scale(1f / shadowScaleHorizontal, 1f);\n                canvas.drawRect(0, edgeShadowTop,\n                        mContentBounds.width() - 2 * shadowOffset, -mCornerRadius,\n                        mEdgeShadowPaint);\n            }\n            canvas.restoreToCount(saved);\n            // LB\n            saved = canvas.save();\n            canvas.translate(mContentBounds.left + shadowOffset, mContentBounds.bottom - shadowOffset);\n            canvas.scale(shadowScaleHorizontal, shadowScaleBottom);\n            canvas.rotate(270f);\n            canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);\n            if (drawVerticalEdges) {\n                // LE\n                canvas.scale(1f / shadowScaleBottom, 1f);\n                canvas.drawRect(0, edgeShadowTop,\n                        mContentBounds.height() - 2 * shadowOffset, -mCornerRadius, mEdgeShadowPaint);\n            }\n            canvas.restoreToCount(saved);\n            // RT\n            saved = canvas.save();\n            canvas.translate(mContentBounds.right - shadowOffset, mContentBounds.top + shadowOffset);\n            canvas.scale(shadowScaleHorizontal, shadowScaleTop);\n            canvas.rotate(90f);\n            canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);\n            if (drawVerticalEdges) {\n                // RE\n                canvas.scale(1f / shadowScaleTop, 1f);\n                canvas.drawRect(0, edgeShadowTop,\n                        mContentBounds.height() - 2 * shadowOffset, -mCornerRadius, mEdgeShadowPaint);\n            }\n            canvas.restoreToCount(saved);\n\n            canvas.restoreToCount(rotateSaved);\n        }\n\n        private void buildShadowCorners() {\n            if (isCircle) {\n                float size = mContentBounds.width() / 2 - 1f;\n                RectF innerBounds = new RectF(-size, -size, size, size);\n                RectF outerBounds = new RectF(innerBounds);\n                outerBounds.inset(-mShadowSize, -mShadowSize);\n\n                if (mCornerShadowPath == null) {\n                    mCornerShadowPath = new Path();\n                } else {\n                    mCornerShadowPath.reset();\n                }\n                mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);\n                mCornerShadowPath.moveTo(-size, 0);\n                mCornerShadowPath.rLineTo(-mShadowSize, 0);\n                // outer arc\n                mCornerShadowPath.arcTo(outerBounds, 180f, 180f, false);\n                mCornerShadowPath.arcTo(outerBounds, 0f, 180f, false);\n                // inner arc\n                mCornerShadowPath.arcTo(innerBounds, 180f, 180f, false);\n                mCornerShadowPath.arcTo(innerBounds, 0f, 180f, false);\n                mCornerShadowPath.close();\n\n                float shadowRadius = -outerBounds.top;\n                if (shadowRadius > 0f) {\n                    float startRatio = size / shadowRadius;\n                    mCornerShadowPaint.setShader(new RadialGradient(0, 0, shadowRadius,\n                            new int[]{0, mShadowStartColor, mShadowEndColor}, new float[]{0.0F, startRatio, 1.0F},\n                            Shader.TileMode.CLAMP));\n                }\n                return;\n            }\n\n            RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);\n            RectF outerBounds = new RectF(innerBounds);\n            outerBounds.inset(-mShadowSize, -mShadowSize);\n\n            if (mCornerShadowPath == null) {\n                mCornerShadowPath = new Path();\n            } else {\n                mCornerShadowPath.reset();\n            }\n            mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);\n            mCornerShadowPath.moveTo(-mCornerRadius, 0);\n            mCornerShadowPath.rLineTo(-mShadowSize, 0);\n            // outer arc\n            mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);\n            // inner arc\n            mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);\n            mCornerShadowPath.close();\n\n            float shadowRadius = -outerBounds.top;\n            if (shadowRadius > 0f) {\n                float startRatio = mCornerRadius / shadowRadius;\n                mCornerShadowPaint.setShader(new RadialGradient(0, 0, shadowRadius,\n                        new int[]{0, mShadowStartColor, mShadowEndColor}, new float[]{0F, startRatio, 1F},\n                        Shader.TileMode.CLAMP));\n            }\n\n            // we offset the content shadowSize/2 pixels up to make it more realistic.\n            // this is why edge shadow shader has some extra space\n            // When drawing bottom edge shadow, we use that extra space.\n            mEdgeShadowPaint.setShader(new LinearGradient(0, innerBounds.top, 0, outerBounds.top,\n                    mShadowStartColor, mShadowEndColor, Shader.TileMode.CLAMP));\n            mEdgeShadowPaint.setAntiAlias(false);\n        }\n\n        private void buildComponents(Rect bounds) {\n            // Card is offset mShadowMultiplier * maxShadowSize to account for the shadow shift.\n            // We could have different top-bottom offsets to avoid extra gap above but in that case\n            // center aligning Views inside the CardView would be problematic.\n            if (isCircle) {\n                mCornerRadius = bounds.width() / 2;\n            }\n            final float verticalOffset = mRawMaxShadowSize * mShadowMultiplier;\n            mContentBounds.set(bounds.left + mRawMaxShadowSize, bounds.top + verticalOffset,\n                    bounds.right - mRawMaxShadowSize, bounds.bottom - verticalOffset);\n\n            getWrappedDrawable().setBounds((int) mContentBounds.left, (int) mContentBounds.top,\n                    (int) mContentBounds.right, (int) mContentBounds.bottom);\n            buildShadowCorners();\n        }\n\n        public float getCornerRadius() {\n            return mCornerRadius;\n        }\n\n        public void setShadowSize(float size) {\n            setShadowSize(size, mRawMaxShadowSize);\n        }\n\n        public void setMaxShadowSize(float size) {\n            setShadowSize(mRawShadowSize, size);\n        }\n\n        public float getShadowSize() {\n            return mRawShadowSize;\n        }\n\n        public float getMaxShadowSize() {\n            return mRawMaxShadowSize;\n        }\n\n        public float getMinWidth() {\n            final float content = 2 *\n                    Math.max(mRawMaxShadowSize, mCornerRadius + mRawMaxShadowSize / 2);\n            return content + mRawMaxShadowSize * 2;\n        }\n\n        public float getMinHeight() {\n            final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius\n                    + mRawMaxShadowSize * mShadowMultiplier / 2);\n            return content + (mRawMaxShadowSize * mShadowMultiplier) * 2;\n        }\n    }\n\n    static class DrawableWrapper extends Drawable implements Drawable.Callback {\n        private Drawable mDrawable;\n\n        public DrawableWrapper(Drawable drawable) {\n            this.setWrappedDrawable(drawable);\n        }\n\n        public void draw(Canvas canvas) {\n            this.mDrawable.draw(canvas);\n        }\n\n        protected void onBoundsChange(Rect bounds) {\n            this.mDrawable.setBounds(bounds);\n        }\n\n        public void setChangingConfigurations(int configs) {\n            this.mDrawable.setChangingConfigurations(configs);\n        }\n\n        public int getChangingConfigurations() {\n            return this.mDrawable.getChangingConfigurations();\n        }\n\n        public void setDither(boolean dither) {\n            this.mDrawable.setDither(dither);\n        }\n\n        public void setFilterBitmap(boolean filter) {\n            this.mDrawable.setFilterBitmap(filter);\n        }\n\n        public void setAlpha(int alpha) {\n            this.mDrawable.setAlpha(alpha);\n        }\n\n        public void setColorFilter(ColorFilter cf) {\n            this.mDrawable.setColorFilter(cf);\n        }\n\n        public boolean isStateful() {\n            return this.mDrawable.isStateful();\n        }\n\n        public boolean setState(int[] stateSet) {\n            return this.mDrawable.setState(stateSet);\n        }\n\n        public int[] getState() {\n            return this.mDrawable.getState();\n        }\n\n        public void jumpToCurrentState() {\n            DrawableCompat.jumpToCurrentState(this.mDrawable);\n        }\n\n        public Drawable getCurrent() {\n            return this.mDrawable.getCurrent();\n        }\n\n        public boolean setVisible(boolean visible, boolean restart) {\n            return super.setVisible(visible, restart) || this.mDrawable.setVisible(visible, restart);\n        }\n\n        public int getOpacity() {\n            return this.mDrawable.getOpacity();\n        }\n\n        public Region getTransparentRegion() {\n            return this.mDrawable.getTransparentRegion();\n        }\n\n        public int getIntrinsicWidth() {\n            return this.mDrawable.getIntrinsicWidth();\n        }\n\n        public int getIntrinsicHeight() {\n            return this.mDrawable.getIntrinsicHeight();\n        }\n\n        public int getMinimumWidth() {\n            return this.mDrawable.getMinimumWidth();\n        }\n\n        public int getMinimumHeight() {\n            return this.mDrawable.getMinimumHeight();\n        }\n\n        public boolean getPadding(Rect padding) {\n            return this.mDrawable.getPadding(padding);\n        }\n\n        public void invalidateDrawable(Drawable who) {\n            this.invalidateSelf();\n        }\n\n        public void scheduleDrawable(Drawable who, Runnable what, long when) {\n            this.scheduleSelf(what, when);\n        }\n\n        public void unscheduleDrawable(Drawable who, Runnable what) {\n            this.unscheduleSelf(what);\n        }\n\n        protected boolean onLevelChange(int level) {\n            return this.mDrawable.setLevel(level);\n        }\n\n        public void setAutoMirrored(boolean mirrored) {\n            DrawableCompat.setAutoMirrored(this.mDrawable, mirrored);\n        }\n\n        public boolean isAutoMirrored() {\n            return DrawableCompat.isAutoMirrored(this.mDrawable);\n        }\n\n        public void setTint(int tint) {\n            DrawableCompat.setTint(this.mDrawable, tint);\n        }\n\n        public void setTintList(ColorStateList tint) {\n            DrawableCompat.setTintList(this.mDrawable, tint);\n        }\n\n        public void setTintMode(Mode tintMode) {\n            DrawableCompat.setTintMode(this.mDrawable, tintMode);\n        }\n\n        public void setHotspot(float x, float y) {\n            DrawableCompat.setHotspot(this.mDrawable, x, y);\n        }\n\n        public void setHotspotBounds(int left, int top, int right, int bottom) {\n            DrawableCompat.setHotspotBounds(this.mDrawable, left, top, right, bottom);\n        }\n\n        public Drawable getWrappedDrawable() {\n            return this.mDrawable;\n        }\n\n        public void setWrappedDrawable(Drawable drawable) {\n            if (this.mDrawable != null) {\n                this.mDrawable.setCallback((Callback) null);\n            }\n\n            this.mDrawable = drawable;\n            if (drawable != null) {\n                drawable.setCallback(this);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ShellUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport androidx.annotation.NonNull;\n\nimport java.io.BufferedReader;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.List;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/07\n *     desc  : utils about shell\n * </pre>\n */\npublic final class ShellUtils {\n\n    private static final String LINE_SEP = System.getProperty(\"line.separator\");\n\n    private ShellUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Execute the command asynchronously.\n     *\n     * @param command  The command.\n     * @param isRooted True to use root, false otherwise.\n     * @param consumer The consumer.\n     * @return the task\n     */\n    public static Utils.Task<CommandResult> execCmdAsync(final String command,\n                                                         final boolean isRooted,\n                                                         final Utils.Consumer<CommandResult> consumer) {\n        return execCmdAsync(new String[]{command}, isRooted, true, consumer);\n    }\n\n    /**\n     * Execute the command asynchronously.\n     *\n     * @param commands The commands.\n     * @param isRooted True to use root, false otherwise.\n     * @param consumer The consumer.\n     * @return the task\n     */\n    public static Utils.Task<CommandResult> execCmdAsync(final List<String> commands,\n                                                         final boolean isRooted,\n                                                         final Utils.Consumer<CommandResult> consumer) {\n        return execCmdAsync(commands == null ? null : commands.toArray(new String[]{}), isRooted, true, consumer);\n    }\n\n    /**\n     * Execute the command asynchronously.\n     *\n     * @param commands The commands.\n     * @param isRooted True to use root, false otherwise.\n     * @param consumer The consumer.\n     * @return the task\n     */\n    public static Utils.Task<CommandResult> execCmdAsync(final String[] commands,\n                                                         final boolean isRooted,\n                                                         final Utils.Consumer<CommandResult> consumer) {\n        return execCmdAsync(commands, isRooted, true, consumer);\n    }\n\n    /**\n     * Execute the command asynchronously.\n     *\n     * @param command         The command.\n     * @param isRooted        True to use root, false otherwise.\n     * @param isNeedResultMsg True to return the message of result, false otherwise.\n     * @param consumer        The consumer.\n     * @return the task\n     */\n    public static Utils.Task<CommandResult> execCmdAsync(final String command,\n                                                         final boolean isRooted,\n                                                         final boolean isNeedResultMsg,\n                                                         final Utils.Consumer<CommandResult> consumer) {\n        return execCmdAsync(new String[]{command}, isRooted, isNeedResultMsg, consumer);\n    }\n\n    /**\n     * Execute the command asynchronously.\n     *\n     * @param commands        The commands.\n     * @param isRooted        True to use root, false otherwise.\n     * @param isNeedResultMsg True to return the message of result, false otherwise.\n     * @param consumer        The consumer.\n     * @return the task\n     */\n    public static Utils.Task<CommandResult> execCmdAsync(final List<String> commands,\n                                                         final boolean isRooted,\n                                                         final boolean isNeedResultMsg,\n                                                         final Utils.Consumer<CommandResult> consumer) {\n        return execCmdAsync(commands == null ? null : commands.toArray(new String[]{}),\n                isRooted,\n                isNeedResultMsg,\n                consumer);\n    }\n\n    /**\n     * Execute the command asynchronously.\n     *\n     * @param commands        The commands.\n     * @param isRooted        True to use root, false otherwise.\n     * @param isNeedResultMsg True to return the message of result, false otherwise.\n     * @param consumer        The consumer.\n     * @return the task\n     */\n    public static Utils.Task<CommandResult> execCmdAsync(final String[] commands,\n                                                         final boolean isRooted,\n                                                         final boolean isNeedResultMsg,\n                                                         @NonNull final Utils.Consumer<CommandResult> consumer) {\n        return UtilsBridge.doAsync(new Utils.Task<CommandResult>(consumer) {\n            @Override\n            public CommandResult doInBackground() {\n                return execCmd(commands, isRooted, isNeedResultMsg);\n            }\n        });\n    }\n\n    /**\n     * Execute the command.\n     *\n     * @param command  The command.\n     * @param isRooted True to use root, false otherwise.\n     * @return the single {@link CommandResult} instance\n     */\n    public static CommandResult execCmd(final String command, final boolean isRooted) {\n        return execCmd(new String[]{command}, isRooted, true);\n    }\n\n    /**\n     * Execute the command.\n     *\n     * @param command  The command.\n     * @param envp     The environment variable settings.\n     * @param isRooted True to use root, false otherwise.\n     * @return the single {@link CommandResult} instance\n     */\n    public static CommandResult execCmd(final String command, final List<String> envp, final boolean isRooted) {\n        return execCmd(new String[]{command},\n                envp == null ? null : envp.toArray(new String[]{}),\n                isRooted,\n                true);\n    }\n\n    /**\n     * Execute the command.\n     *\n     * @param commands The commands.\n     * @param isRooted True to use root, false otherwise.\n     * @return the single {@link CommandResult} instance\n     */\n    public static CommandResult execCmd(final List<String> commands, final boolean isRooted) {\n        return execCmd(commands == null ? null : commands.toArray(new String[]{}), isRooted, true);\n    }\n\n    /**\n     * Execute the command.\n     *\n     * @param commands The commands.\n     * @param envp     The environment variable settings.\n     * @param isRooted True to use root, false otherwise.\n     * @return the single {@link CommandResult} instance\n     */\n    public static CommandResult execCmd(final List<String> commands,\n                                        final List<String> envp,\n                                        final boolean isRooted) {\n        return execCmd(commands == null ? null : commands.toArray(new String[]{}),\n                envp == null ? null : envp.toArray(new String[]{}),\n                isRooted,\n                true);\n    }\n\n    /**\n     * Execute the command.\n     *\n     * @param commands The commands.\n     * @param isRooted True to use root, false otherwise.\n     * @return the single {@link CommandResult} instance\n     */\n    public static CommandResult execCmd(final String[] commands, final boolean isRooted) {\n        return execCmd(commands, isRooted, true);\n    }\n\n    /**\n     * Execute the command.\n     *\n     * @param command         The command.\n     * @param isRooted        True to use root, false otherwise.\n     * @param isNeedResultMsg True to return the message of result, false otherwise.\n     * @return the single {@link CommandResult} instance\n     */\n    public static CommandResult execCmd(final String command,\n                                        final boolean isRooted,\n                                        final boolean isNeedResultMsg) {\n        return execCmd(new String[]{command}, isRooted, isNeedResultMsg);\n    }\n\n    /**\n     * Execute the command.\n     *\n     * @param command         The command.\n     * @param envp            The environment variable settings.\n     * @param isRooted        True to use root, false otherwise.\n     * @param isNeedResultMsg True to return the message of result, false otherwise.\n     * @return the single {@link CommandResult} instance\n     */\n    public static CommandResult execCmd(final String command,\n                                        final List<String> envp,\n                                        final boolean isRooted,\n                                        final boolean isNeedResultMsg) {\n        return execCmd(new String[]{command}, envp == null ? null : envp.toArray(new String[]{}),\n                isRooted,\n                isNeedResultMsg);\n    }\n\n    /**\n     * Execute the command.\n     *\n     * @param command         The command.\n     * @param envp            The environment variable settings array.\n     * @param isRooted        True to use root, false otherwise.\n     * @param isNeedResultMsg True to return the message of result, false otherwise.\n     * @return the single {@link CommandResult} instance\n     */\n    public static CommandResult execCmd(final String command,\n                                        final String[] envp,\n                                        final boolean isRooted,\n                                        final boolean isNeedResultMsg) {\n        return execCmd(new String[]{command}, envp, isRooted, isNeedResultMsg);\n    }\n\n    /**\n     * Execute the command.\n     *\n     * @param commands        The commands.\n     * @param isRooted        True to use root, false otherwise.\n     * @param isNeedResultMsg True to return the message of result, false otherwise.\n     * @return the single {@link CommandResult} instance\n     */\n    public static CommandResult execCmd(final List<String> commands,\n                                        final boolean isRooted,\n                                        final boolean isNeedResultMsg) {\n        return execCmd(commands == null ? null : commands.toArray(new String[]{}),\n                isRooted,\n                isNeedResultMsg);\n    }\n\n    /**\n     * Execute the command.\n     *\n     * @param commands        The commands.\n     * @param isRooted        True to use root, false otherwise.\n     * @param isNeedResultMsg True to return the message of result, false otherwise.\n     * @return the single {@link CommandResult} instance\n     */\n    public static CommandResult execCmd(final String[] commands,\n                                        final boolean isRooted,\n                                        final boolean isNeedResultMsg) {\n        return execCmd(commands, null, isRooted, isNeedResultMsg);\n    }\n\n    /**\n     * Execute the command.\n     *\n     * @param commands        The commands.\n     * @param envp            Array of strings, each element of which\n     *                        has environment variable settings in the format\n     *                        <i>name</i>=<i>value</i>, or\n     *                        <tt>null</tt> if the subprocess should inherit\n     *                        the environment of the current process.\n     * @param isRooted        True to use root, false otherwise.\n     * @param isNeedResultMsg True to return the message of result, false otherwise.\n     * @return the single {@link CommandResult} instance\n     */\n    public static CommandResult execCmd(final String[] commands,\n                                        final String[] envp,\n                                        final boolean isRooted,\n                                        final boolean isNeedResultMsg) {\n        int result = -1;\n        if (commands == null || commands.length == 0) {\n            return new CommandResult(result, \"\", \"\");\n        }\n        Process process = null;\n        BufferedReader successResult = null;\n        BufferedReader errorResult = null;\n        StringBuilder successMsg = null;\n        StringBuilder errorMsg = null;\n        DataOutputStream os = null;\n        try {\n            process = Runtime.getRuntime().exec(isRooted ? \"su\" : \"sh\", envp, null);\n            os = new DataOutputStream(process.getOutputStream());\n            for (String command : commands) {\n                if (command == null) continue;\n                os.write(command.getBytes());\n                os.writeBytes(LINE_SEP);\n                os.flush();\n            }\n            os.writeBytes(\"exit\" + LINE_SEP);\n            os.flush();\n            result = process.waitFor();\n            if (isNeedResultMsg) {\n                successMsg = new StringBuilder();\n                errorMsg = new StringBuilder();\n                successResult = new BufferedReader(\n                        new InputStreamReader(process.getInputStream(), \"UTF-8\")\n                );\n                errorResult = new BufferedReader(\n                        new InputStreamReader(process.getErrorStream(), \"UTF-8\")\n                );\n                String line;\n                if ((line = successResult.readLine()) != null) {\n                    successMsg.append(line);\n                    while ((line = successResult.readLine()) != null) {\n                        successMsg.append(LINE_SEP).append(line);\n                    }\n                }\n                if ((line = errorResult.readLine()) != null) {\n                    errorMsg.append(line);\n                    while ((line = errorResult.readLine()) != null) {\n                        errorMsg.append(LINE_SEP).append(line);\n                    }\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            try {\n                if (os != null) {\n                    os.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n            try {\n                if (successResult != null) {\n                    successResult.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n            try {\n                if (errorResult != null) {\n                    errorResult.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n            if (process != null) {\n                process.destroy();\n            }\n        }\n        return new CommandResult(\n                result,\n                successMsg == null ? \"\" : successMsg.toString(),\n                errorMsg == null ? \"\" : errorMsg.toString()\n        );\n    }\n\n    /**\n     * The result of command.\n     */\n    public static class CommandResult {\n        public int    result;\n        public String successMsg;\n        public String errorMsg;\n\n        public CommandResult(final int result, final String successMsg, final String errorMsg) {\n            this.result = result;\n            this.successMsg = successMsg;\n            this.errorMsg = errorMsg;\n        }\n\n        @Override\n        public String toString() {\n            return \"result: \" + result + \"\\n\" +\n                    \"successMsg: \" + successMsg + \"\\n\" +\n                    \"errorMsg: \" + errorMsg;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/SizeUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.res.Resources;\nimport android.util.DisplayMetrics;\nimport android.util.TypedValue;\nimport android.view.View;\nimport android.view.ViewGroup;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/02\n *     desc  : utils about size\n * </pre>\n */\npublic final class SizeUtils {\n\n    private SizeUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Value of dp to value of px.\n     *\n     * @param dpValue The value of dp.\n     * @return value of px\n     */\n    public static int dp2px(final float dpValue) {\n        final float scale = Resources.getSystem().getDisplayMetrics().density;\n        return (int) (dpValue * scale + 0.5f);\n    }\n\n    /**\n     * Value of px to value of dp.\n     *\n     * @param pxValue The value of px.\n     * @return value of dp\n     */\n    public static int px2dp(final float pxValue) {\n        final float scale = Resources.getSystem().getDisplayMetrics().density;\n        return (int) (pxValue / scale + 0.5f);\n    }\n\n    /**\n     * Value of sp to value of px.\n     *\n     * @param spValue The value of sp.\n     * @return value of px\n     */\n    public static int sp2px(final float spValue) {\n        final float fontScale = Resources.getSystem().getDisplayMetrics().scaledDensity;\n        return (int) (spValue * fontScale + 0.5f);\n    }\n\n    /**\n     * Value of px to value of sp.\n     *\n     * @param pxValue The value of px.\n     * @return value of sp\n     */\n    public static int px2sp(final float pxValue) {\n        final float fontScale = Resources.getSystem().getDisplayMetrics().scaledDensity;\n        return (int) (pxValue / fontScale + 0.5f);\n    }\n\n    /**\n     * Converts an unpacked complex data value holding a dimension to its final floating\n     * point value. The two parameters <var>unit</var> and <var>value</var>\n     * are as in {@link TypedValue#TYPE_DIMENSION}.\n     *\n     * @param value The value to apply the unit to.\n     * @param unit  The unit to convert from.\n     * @return The complex floating point value multiplied by the appropriate\n     * metrics depending on its unit.\n     */\n    public static float applyDimension(final float value, final int unit) {\n        DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();\n        switch (unit) {\n            case TypedValue.COMPLEX_UNIT_PX:\n                return value;\n            case TypedValue.COMPLEX_UNIT_DIP:\n                return value * metrics.density;\n            case TypedValue.COMPLEX_UNIT_SP:\n                return value * metrics.scaledDensity;\n            case TypedValue.COMPLEX_UNIT_PT:\n                return value * metrics.xdpi * (1.0f / 72);\n            case TypedValue.COMPLEX_UNIT_IN:\n                return value * metrics.xdpi;\n            case TypedValue.COMPLEX_UNIT_MM:\n                return value * metrics.xdpi * (1.0f / 25.4f);\n        }\n        return 0;\n    }\n\n    /**\n     * Force get the size of view.\n     * <p>e.g.</p>\n     * <pre>\n     * SizeUtils.forceGetViewSize(view, new SizeUtils.OnGetSizeListener() {\n     *     Override\n     *     public void onGetSize(final View view) {\n     *         view.getWidth();\n     *     }\n     * });\n     * </pre>\n     *\n     * @param view     The view.\n     * @param listener The get size listener.\n     */\n    public static void forceGetViewSize(final View view, final OnGetSizeListener listener) {\n        view.post(new Runnable() {\n            @Override\n            public void run() {\n                if (listener != null) {\n                    listener.onGetSize(view);\n                }\n            }\n        });\n    }\n\n    /**\n     * Return the width of view.\n     *\n     * @param view The view.\n     * @return the width of view\n     */\n    public static int getMeasuredWidth(final View view) {\n        return measureView(view)[0];\n    }\n\n    /**\n     * Return the height of view.\n     *\n     * @param view The view.\n     * @return the height of view\n     */\n    public static int getMeasuredHeight(final View view) {\n        return measureView(view)[1];\n    }\n\n    /**\n     * Measure the view.\n     *\n     * @param view The view.\n     * @return arr[0]: view's width, arr[1]: view's height\n     */\n    public static int[] measureView(final View view) {\n        ViewGroup.LayoutParams lp = view.getLayoutParams();\n        if (lp == null) {\n            lp = new ViewGroup.LayoutParams(\n                    ViewGroup.LayoutParams.MATCH_PARENT,\n                    ViewGroup.LayoutParams.WRAP_CONTENT\n            );\n        }\n        int widthSpec = ViewGroup.getChildMeasureSpec(0, 0, lp.width);\n        int lpHeight = lp.height;\n        int heightSpec;\n        if (lpHeight > 0) {\n            heightSpec = View.MeasureSpec.makeMeasureSpec(lpHeight, View.MeasureSpec.EXACTLY);\n        } else {\n            heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);\n        }\n        view.measure(widthSpec, heightSpec);\n        return new int[]{view.getMeasuredWidth(), view.getMeasuredHeight()};\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // interface\n    ///////////////////////////////////////////////////////////////////////////\n\n    public interface OnGetSizeListener {\n        void onGetSize(View view);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/SnackbarUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.os.Build;\n\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.IntDef;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.LayoutRes;\nimport androidx.annotation.NonNull;\nimport androidx.coordinatorlayout.widget.CoordinatorLayout;\n\nimport android.text.SpannableString;\nimport android.text.Spanned;\nimport android.text.style.ForegroundColorSpan;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.ViewParent;\nimport android.widget.FrameLayout;\n\nimport com.google.android.material.snackbar.Snackbar;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.ref.WeakReference;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/10/16\n *     desc  : utils about snackbar\n * </pre>\n */\npublic final class SnackbarUtils {\n\n    public static final int LENGTH_INDEFINITE = -2;\n    public static final int LENGTH_SHORT = -1;\n    public static final int LENGTH_LONG = 0;\n\n    @IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})\n    @Retention(RetentionPolicy.SOURCE)\n    public @interface Duration {\n    }\n\n    private static final int COLOR_DEFAULT = 0xFEFFFFFF;\n    private static final int COLOR_SUCCESS = 0xFF2BB600;\n    private static final int COLOR_WARNING = 0xFFFFC100;\n    private static final int COLOR_ERROR = 0xFFFF0000;\n    private static final int COLOR_MESSAGE = 0xFFFFFFFF;\n\n    private static WeakReference<Snackbar> sWeakSnackbar;\n\n    private View view;\n    private CharSequence message;\n    private int messageColor;\n    private int bgColor;\n    private int bgResource;\n    private int duration;\n    private CharSequence actionText;\n    private int actionTextColor;\n    private View.OnClickListener actionListener;\n    private int bottomMargin;\n\n    private SnackbarUtils(final View parent) {\n        setDefault();\n        this.view = parent;\n    }\n\n    private void setDefault() {\n        message = \"\";\n        messageColor = COLOR_DEFAULT;\n        bgColor = COLOR_DEFAULT;\n        bgResource = -1;\n        duration = LENGTH_SHORT;\n        actionText = \"\";\n        actionTextColor = COLOR_DEFAULT;\n        bottomMargin = 0;\n    }\n\n    /**\n     * Set the view to find a parent from.\n     *\n     * @param view The view to find a parent from.\n     * @return the single {@link SnackbarUtils} instance\n     */\n    public static SnackbarUtils with(@NonNull final View view) {\n        return new SnackbarUtils(view);\n    }\n\n    /**\n     * Set the message.\n     *\n     * @param msg The message.\n     * @return the single {@link SnackbarUtils} instance\n     */\n    public SnackbarUtils setMessage(@NonNull final CharSequence msg) {\n        this.message = msg;\n        return this;\n    }\n\n    /**\n     * Set the color of message.\n     *\n     * @param color The color of message.\n     * @return the single {@link SnackbarUtils} instance\n     */\n    public SnackbarUtils setMessageColor(@ColorInt final int color) {\n        this.messageColor = color;\n        return this;\n    }\n\n    /**\n     * Set the color of background.\n     *\n     * @param color The color of background.\n     * @return the single {@link SnackbarUtils} instance\n     */\n    public SnackbarUtils setBgColor(@ColorInt final int color) {\n        this.bgColor = color;\n        return this;\n    }\n\n    /**\n     * Set the resource of background.\n     *\n     * @param bgResource The resource of background.\n     * @return the single {@link SnackbarUtils} instance\n     */\n    public SnackbarUtils setBgResource(@DrawableRes final int bgResource) {\n        this.bgResource = bgResource;\n        return this;\n    }\n\n    /**\n     * Set the duration.\n     *\n     * @param duration The duration.\n     *                 <ul>\n     *                 <li>{@link Duration#LENGTH_INDEFINITE}</li>\n     *                 <li>{@link Duration#LENGTH_SHORT     }</li>\n     *                 <li>{@link Duration#LENGTH_LONG      }</li>\n     *                 </ul>\n     * @return the single {@link SnackbarUtils} instance\n     */\n    public SnackbarUtils setDuration(@Duration final int duration) {\n        this.duration = duration;\n        return this;\n    }\n\n    /**\n     * Set the action.\n     *\n     * @param text     The text.\n     * @param listener The click listener.\n     * @return the single {@link SnackbarUtils} instance\n     */\n    public SnackbarUtils setAction(@NonNull final CharSequence text,\n                                   @NonNull final View.OnClickListener listener) {\n        return setAction(text, COLOR_DEFAULT, listener);\n    }\n\n    /**\n     * Set the action.\n     *\n     * @param text     The text.\n     * @param color    The color of text.\n     * @param listener The click listener.\n     * @return the single {@link SnackbarUtils} instance\n     */\n\n    public SnackbarUtils setAction(@NonNull final CharSequence text,\n                                   @ColorInt final int color,\n                                   @NonNull final View.OnClickListener listener) {\n        this.actionText = text;\n        this.actionTextColor = color;\n        this.actionListener = listener;\n        return this;\n    }\n\n    /**\n     * Set the bottom margin.\n     *\n     * @param bottomMargin The size of bottom margin, in pixel.\n     */\n    public SnackbarUtils setBottomMargin(@IntRange(from = 1) final int bottomMargin) {\n        this.bottomMargin = bottomMargin;\n        return this;\n    }\n\n    /**\n     * Show the snackbar.\n     */\n    public Snackbar show() {\n        return show(false);\n    }\n\n    /**\n     * Show the snackbar.\n     *\n     * @param isShowTop True to show the snack bar on the top, false otherwise.\n     */\n    public Snackbar show(boolean isShowTop) {\n        View view = this.view;\n        if (view == null) return null;\n        if (isShowTop) {\n            ViewGroup suitableParent = findSuitableParentCopyFromSnackbar(view);\n            View topSnackBarContainer = suitableParent.findViewWithTag(\"topSnackBarCoordinatorLayout\");\n            if (topSnackBarContainer == null) {\n                CoordinatorLayout topSnackBarCoordinatorLayout = new CoordinatorLayout(view.getContext());\n                topSnackBarCoordinatorLayout.setTag(\"topSnackBarCoordinatorLayout\");\n                topSnackBarCoordinatorLayout.setRotation(180);\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                    // bring to front\n                    topSnackBarCoordinatorLayout.setElevation(100);\n                }\n                suitableParent.addView(topSnackBarCoordinatorLayout, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);\n                topSnackBarContainer = topSnackBarCoordinatorLayout;\n            }\n            view = topSnackBarContainer;\n        }\n        if (messageColor != COLOR_DEFAULT) {\n            SpannableString spannableString = new SpannableString(message);\n            ForegroundColorSpan colorSpan = new ForegroundColorSpan(messageColor);\n            spannableString.setSpan(\n                    colorSpan, 0, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n            );\n            sWeakSnackbar = new WeakReference<>(Snackbar.make(view, spannableString, duration));\n        } else {\n            sWeakSnackbar = new WeakReference<>(Snackbar.make(view, message, duration));\n        }\n        final Snackbar snackbar = sWeakSnackbar.get();\n        final Snackbar.SnackbarLayout snackbarView = (Snackbar.SnackbarLayout) snackbar.getView();\n        if (isShowTop) {\n            for (int i = 0; i < snackbarView.getChildCount(); i++) {\n                View child = snackbarView.getChildAt(i);\n                child.setRotation(180);\n            }\n        }\n        if (bgResource != -1) {\n            snackbarView.setBackgroundResource(bgResource);\n        } else if (bgColor != COLOR_DEFAULT) {\n            snackbarView.setBackgroundColor(bgColor);\n        }\n        if (bottomMargin != 0) {\n            ViewGroup.MarginLayoutParams params =\n                    (ViewGroup.MarginLayoutParams) snackbarView.getLayoutParams();\n            params.bottomMargin = bottomMargin;\n        }\n        if (actionText.length() > 0 && actionListener != null) {\n            if (actionTextColor != COLOR_DEFAULT) {\n                snackbar.setActionTextColor(actionTextColor);\n            }\n            snackbar.setAction(actionText, actionListener);\n        }\n        snackbar.show();\n        return snackbar;\n    }\n\n    /**\n     * Show the snackbar with success style.\n     */\n    public void showSuccess() {\n        showSuccess(false);\n    }\n\n    /**\n     * Show the snackbar with success style.\n     *\n     * @param isShowTop True to show the snack bar on the top, false otherwise.\n     */\n    public void showSuccess(boolean isShowTop) {\n        bgColor = COLOR_SUCCESS;\n        messageColor = COLOR_MESSAGE;\n        actionTextColor = COLOR_MESSAGE;\n        show(isShowTop);\n    }\n\n    /**\n     * Show the snackbar with warning style.\n     */\n    public void showWarning() {\n        showWarning(false);\n    }\n\n    /**\n     * Show the snackbar with warning style.\n     *\n     * @param isShowTop True to show the snackbar on the top, false otherwise.\n     */\n    public void showWarning(boolean isShowTop) {\n        bgColor = COLOR_WARNING;\n        messageColor = COLOR_MESSAGE;\n        actionTextColor = COLOR_MESSAGE;\n        show(isShowTop);\n    }\n\n    /**\n     * Show the snackbar with error style.\n     */\n    public void showError() {\n        showError(false);\n    }\n\n    /**\n     * Show the snackbar with error style.\n     *\n     * @param isShowTop True to show the snackbar on the top, false otherwise.\n     */\n    public void showError(boolean isShowTop) {\n        bgColor = COLOR_ERROR;\n        messageColor = COLOR_MESSAGE;\n        actionTextColor = COLOR_MESSAGE;\n        show(isShowTop);\n    }\n\n    /**\n     * Dismiss the snackbar.\n     */\n    public static void dismiss() {\n        if (sWeakSnackbar != null && sWeakSnackbar.get() != null) {\n            sWeakSnackbar.get().dismiss();\n            sWeakSnackbar = null;\n        }\n    }\n\n    /**\n     * Return the view of snackbar.\n     *\n     * @return the view of snackbar\n     */\n    public static View getView() {\n        Snackbar snackbar = sWeakSnackbar.get();\n        if (snackbar == null) return null;\n        return snackbar.getView();\n    }\n\n    /**\n     * Add view to the snackbar.\n     * <p>Call it after {@link #show()}</p>\n     *\n     * @param layoutId The id of layout.\n     * @param params   The params.\n     */\n    public static void addView(@LayoutRes final int layoutId,\n                               @NonNull final ViewGroup.LayoutParams params) {\n        final View view = getView();\n        if (view != null) {\n            view.setPadding(0, 0, 0, 0);\n            Snackbar.SnackbarLayout layout = (Snackbar.SnackbarLayout) view;\n            View child = LayoutInflater.from(view.getContext()).inflate(layoutId, null);\n            layout.addView(child, -1, params);\n        }\n    }\n\n    /**\n     * Add view to the snackbar.\n     * <p>Call it after {@link #show()}</p>\n     *\n     * @param child  The child view.\n     * @param params The params.\n     */\n    public static void addView(@NonNull final View child,\n                               @NonNull final ViewGroup.LayoutParams params) {\n        final View view = getView();\n        if (view != null) {\n            view.setPadding(0, 0, 0, 0);\n            Snackbar.SnackbarLayout layout = (Snackbar.SnackbarLayout) view;\n            layout.addView(child, params);\n        }\n    }\n\n    private static ViewGroup findSuitableParentCopyFromSnackbar(View view) {\n        ViewGroup fallback = null;\n\n        do {\n            if (view instanceof CoordinatorLayout) {\n                return (ViewGroup) view;\n            }\n\n            if (view instanceof FrameLayout) {\n                if (view.getId() == android.R.id.content) {\n                    return (ViewGroup) view;\n                }\n\n                fallback = (ViewGroup) view;\n            }\n\n            if (view != null) {\n                ViewParent parent = view.getParent();\n                view = parent instanceof View ? (View) parent : null;\n            }\n        } while (view != null);\n\n        return fallback;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/SpanUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.annotation.SuppressLint;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.BlurMaskFilter;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.Rect;\nimport android.graphics.Shader;\nimport android.graphics.Typeface;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntDef;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.Nullable;\nimport androidx.core.content.ContextCompat;\nimport android.text.Layout;\nimport android.text.Layout.Alignment;\nimport android.text.SpannableStringBuilder;\nimport android.text.Spanned;\nimport android.text.TextPaint;\nimport android.text.method.LinkMovementMethod;\nimport android.text.style.AbsoluteSizeSpan;\nimport android.text.style.AlignmentSpan;\nimport android.text.style.BackgroundColorSpan;\nimport android.text.style.CharacterStyle;\nimport android.text.style.ClickableSpan;\nimport android.text.style.ForegroundColorSpan;\nimport android.text.style.LeadingMarginSpan;\nimport android.text.style.LineHeightSpan;\nimport android.text.style.MaskFilterSpan;\nimport android.text.style.RelativeSizeSpan;\nimport android.text.style.ReplacementSpan;\nimport android.text.style.ScaleXSpan;\nimport android.text.style.StrikethroughSpan;\nimport android.text.style.StyleSpan;\nimport android.text.style.SubscriptSpan;\nimport android.text.style.SuperscriptSpan;\nimport android.text.style.TypefaceSpan;\nimport android.text.style.URLSpan;\nimport android.text.style.UnderlineSpan;\nimport android.text.style.UpdateAppearance;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport java.io.InputStream;\nimport java.io.Serializable;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.ref.WeakReference;\n\nimport static android.graphics.BlurMaskFilter.Blur;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 16/12/13\n *     desc  : utils about span\n * </pre>\n */\npublic final class SpanUtils {\n\n    private static final int COLOR_DEFAULT = 0xFEFFFFFF;\n\n    public static final int ALIGN_BOTTOM   = 0;\n    public static final int ALIGN_BASELINE = 1;\n    public static final int ALIGN_CENTER   = 2;\n    public static final int ALIGN_TOP      = 3;\n\n    @IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER, ALIGN_TOP})\n    @Retention(RetentionPolicy.SOURCE)\n    public @interface Align {\n    }\n\n    private static final String LINE_SEPARATOR = System.getProperty(\"line.separator\");\n\n    public static SpanUtils with(final TextView textView) {\n        return new SpanUtils(textView);\n    }\n\n    private TextView      mTextView;\n    private CharSequence  mText;\n    private int           flag;\n    private int           foregroundColor;\n    private int           backgroundColor;\n    private int           lineHeight;\n    private int           alignLine;\n    private int           quoteColor;\n    private int           stripeWidth;\n    private int           quoteGapWidth;\n    private int           first;\n    private int           rest;\n    private int           bulletColor;\n    private int           bulletRadius;\n    private int           bulletGapWidth;\n    private int           fontSize;\n    private boolean       fontSizeIsDp;\n    private float         proportion;\n    private float         xProportion;\n    private boolean       isStrikethrough;\n    private boolean       isUnderline;\n    private boolean       isSuperscript;\n    private boolean       isSubscript;\n    private boolean       isBold;\n    private boolean       isItalic;\n    private boolean       isBoldItalic;\n    private String        fontFamily;\n    private Typeface      typeface;\n    private Alignment     alignment;\n    private int           verticalAlign;\n    private ClickableSpan clickSpan;\n    private String        url;\n    private float         blurRadius;\n    private Blur          style;\n    private Shader        shader;\n    private float         shadowRadius;\n    private float         shadowDx;\n    private float         shadowDy;\n    private int           shadowColor;\n    private Object[]      spans;\n\n    private Bitmap   imageBitmap;\n    private Drawable imageDrawable;\n    private Uri      imageUri;\n    private int      imageResourceId;\n    private int      alignImage;\n\n    private int spaceSize;\n    private int spaceColor;\n\n    private SerializableSpannableStringBuilder mBuilder;\n    private boolean isCreated;\n\n    private       int mType;\n    private final int mTypeCharSequence = 0;\n    private final int mTypeImage        = 1;\n    private final int mTypeSpace        = 2;\n\n    private SpanUtils(TextView textView) {\n        this();\n        mTextView = textView;\n    }\n\n    public SpanUtils() {\n        mBuilder = new SerializableSpannableStringBuilder();\n        mText = \"\";\n        mType = -1;\n        setDefault();\n    }\n\n    private void setDefault() {\n        flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;\n        foregroundColor = COLOR_DEFAULT;\n        backgroundColor = COLOR_DEFAULT;\n        lineHeight = -1;\n        quoteColor = COLOR_DEFAULT;\n        first = -1;\n        bulletColor = COLOR_DEFAULT;\n        fontSize = -1;\n        proportion = -1;\n        xProportion = -1;\n        isStrikethrough = false;\n        isUnderline = false;\n        isSuperscript = false;\n        isSubscript = false;\n        isBold = false;\n        isItalic = false;\n        isBoldItalic = false;\n        fontFamily = null;\n        typeface = null;\n        alignment = null;\n        verticalAlign = -1;\n        clickSpan = null;\n        url = null;\n        blurRadius = -1;\n        shader = null;\n        shadowRadius = -1;\n        spans = null;\n\n        imageBitmap = null;\n        imageDrawable = null;\n        imageUri = null;\n        imageResourceId = -1;\n\n        spaceSize = -1;\n    }\n\n    /**\n     * Set the span of flag.\n     *\n     * @param flag The flag.\n     *             <ul>\n     *             <li>{@link Spanned#SPAN_INCLUSIVE_EXCLUSIVE}</li>\n     *             <li>{@link Spanned#SPAN_INCLUSIVE_INCLUSIVE}</li>\n     *             <li>{@link Spanned#SPAN_EXCLUSIVE_EXCLUSIVE}</li>\n     *             <li>{@link Spanned#SPAN_EXCLUSIVE_INCLUSIVE}</li>\n     *             </ul>\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setFlag(final int flag) {\n        this.flag = flag;\n        return this;\n    }\n\n    /**\n     * Set the span of foreground's color.\n     *\n     * @param color The color of foreground\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setForegroundColor(@ColorInt final int color) {\n        this.foregroundColor = color;\n        return this;\n    }\n\n    /**\n     * Set the span of background's color.\n     *\n     * @param color The color of background\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setBackgroundColor(@ColorInt final int color) {\n        this.backgroundColor = color;\n        return this;\n    }\n\n    /**\n     * Set the span of line height.\n     *\n     * @param lineHeight The line height, in pixel.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setLineHeight(@IntRange(from = 0) final int lineHeight) {\n        return setLineHeight(lineHeight, ALIGN_CENTER);\n    }\n\n    /**\n     * Set the span of line height.\n     *\n     * @param lineHeight The line height, in pixel.\n     * @param align      The alignment.\n     *                   <ul>\n     *                   <li>{@link Align#ALIGN_TOP   }</li>\n     *                   <li>{@link Align#ALIGN_CENTER}</li>\n     *                   <li>{@link Align#ALIGN_BOTTOM}</li>\n     *                   </ul>\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setLineHeight(@IntRange(from = 0) final int lineHeight,\n                                   @Align final int align) {\n        this.lineHeight = lineHeight;\n        this.alignLine = align;\n        return this;\n    }\n\n    /**\n     * Set the span of quote's color.\n     *\n     * @param color The color of quote\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setQuoteColor(@ColorInt final int color) {\n        return setQuoteColor(color, 2, 2);\n    }\n\n    /**\n     * Set the span of quote's color.\n     *\n     * @param color       The color of quote.\n     * @param stripeWidth The width of stripe, in pixel.\n     * @param gapWidth    The width of gap, in pixel.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setQuoteColor(@ColorInt final int color,\n                                   @IntRange(from = 1) final int stripeWidth,\n                                   @IntRange(from = 0) final int gapWidth) {\n        this.quoteColor = color;\n        this.stripeWidth = stripeWidth;\n        this.quoteGapWidth = gapWidth;\n        return this;\n    }\n\n    /**\n     * Set the span of leading margin.\n     *\n     * @param first The indent for the first line of the paragraph.\n     * @param rest  The indent for the remaining lines of the paragraph.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setLeadingMargin(@IntRange(from = 0) final int first,\n                                      @IntRange(from = 0) final int rest) {\n        this.first = first;\n        this.rest = rest;\n        return this;\n    }\n\n    /**\n     * Set the span of bullet.\n     *\n     * @param gapWidth The width of gap, in pixel.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setBullet(@IntRange(from = 0) final int gapWidth) {\n        return setBullet(0, 3, gapWidth);\n    }\n\n    /**\n     * Set the span of bullet.\n     *\n     * @param color    The color of bullet.\n     * @param radius   The radius of bullet, in pixel.\n     * @param gapWidth The width of gap, in pixel.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setBullet(@ColorInt final int color,\n                               @IntRange(from = 0) final int radius,\n                               @IntRange(from = 0) final int gapWidth) {\n        this.bulletColor = color;\n        this.bulletRadius = radius;\n        this.bulletGapWidth = gapWidth;\n        return this;\n    }\n\n    /**\n     * Set the span of font's size.\n     *\n     * @param size The size of font.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setFontSize(@IntRange(from = 0) final int size) {\n        return setFontSize(size, false);\n    }\n\n    /**\n     * Set the span of size of font.\n     *\n     * @param size The size of font.\n     * @param isSp True to use sp, false to use pixel.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setFontSize(@IntRange(from = 0) final int size, final boolean isSp) {\n        this.fontSize = size;\n        this.fontSizeIsDp = isSp;\n        return this;\n    }\n\n    /**\n     * Set the span of proportion of font.\n     *\n     * @param proportion The proportion of font.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setFontProportion(final float proportion) {\n        this.proportion = proportion;\n        return this;\n    }\n\n    /**\n     * Set the span of transverse proportion of font.\n     *\n     * @param proportion The transverse proportion of font.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setFontXProportion(final float proportion) {\n        this.xProportion = proportion;\n        return this;\n    }\n\n    /**\n     * Set the span of strikethrough.\n     *\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setStrikethrough() {\n        this.isStrikethrough = true;\n        return this;\n    }\n\n    /**\n     * Set the span of underline.\n     *\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setUnderline() {\n        this.isUnderline = true;\n        return this;\n    }\n\n    /**\n     * Set the span of superscript.\n     *\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setSuperscript() {\n        this.isSuperscript = true;\n        return this;\n    }\n\n    /**\n     * Set the span of subscript.\n     *\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setSubscript() {\n        this.isSubscript = true;\n        return this;\n    }\n\n    /**\n     * Set the span of bold.\n     *\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setBold() {\n        isBold = true;\n        return this;\n    }\n\n    /**\n     * Set the span of italic.\n     *\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setItalic() {\n        isItalic = true;\n        return this;\n    }\n\n    /**\n     * Set the span of bold italic.\n     *\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setBoldItalic() {\n        isBoldItalic = true;\n        return this;\n    }\n\n    /**\n     * Set the span of font family.\n     *\n     * @param fontFamily The font family.\n     *                   <ul>\n     *                   <li>monospace</li>\n     *                   <li>serif</li>\n     *                   <li>sans-serif</li>\n     *                   </ul>\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setFontFamily(@NonNull final String fontFamily) {\n        this.fontFamily = fontFamily;\n        return this;\n    }\n\n    /**\n     * Set the span of typeface.\n     *\n     * @param typeface The typeface.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setTypeface(@NonNull final Typeface typeface) {\n        this.typeface = typeface;\n        return this;\n    }\n\n    /**\n     * Set the span of horizontal alignment.\n     *\n     * @param alignment The alignment.\n     *                  <ul>\n     *                  <li>{@link Alignment#ALIGN_NORMAL  }</li>\n     *                  <li>{@link Alignment#ALIGN_OPPOSITE}</li>\n     *                  <li>{@link Alignment#ALIGN_CENTER  }</li>\n     *                  </ul>\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setHorizontalAlign(@NonNull final Alignment alignment) {\n        this.alignment = alignment;\n        return this;\n    }\n\n    /**\n     * Set the span of vertical alignment.\n     *\n     * @param align The alignment.\n     *              <ul>\n     *              <li>{@link Align#ALIGN_TOP     }</li>\n     *              <li>{@link Align#ALIGN_CENTER  }</li>\n     *              <li>{@link Align#ALIGN_BASELINE}</li>\n     *              <li>{@link Align#ALIGN_BOTTOM  }</li>\n     *              </ul>\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setVerticalAlign(@Align final int align) {\n        this.verticalAlign = align;\n        return this;\n    }\n\n    /**\n     * Set the span of click.\n     * <p>Must set {@code view.setMovementMethod(LinkMovementMethod.getInstance())}</p>\n     *\n     * @param clickSpan The span of click.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setClickSpan(@NonNull final ClickableSpan clickSpan) {\n        setMovementMethodIfNeed();\n        this.clickSpan = clickSpan;\n        return this;\n    }\n\n    /**\n     * Set the span of click.\n     * <p>Must set {@code view.setMovementMethod(LinkMovementMethod.getInstance())}</p>\n     *\n     * @param color         The color of click span.\n     * @param underlineText True to support underline, false otherwise.\n     * @param listener      The listener of click span.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setClickSpan(@ColorInt final int color,\n                                  final boolean underlineText,\n                                  final View.OnClickListener listener) {\n        setMovementMethodIfNeed();\n        this.clickSpan = new ClickableSpan() {\n\n            @Override\n            public void updateDrawState(@NonNull TextPaint paint) {\n                paint.setColor(color);\n                paint.setUnderlineText(underlineText);\n            }\n\n            @Override\n            public void onClick(@NonNull View widget) {\n                if (listener != null) {\n                    listener.onClick(widget);\n                }\n            }\n        };\n        return this;\n    }\n\n    /**\n     * Set the span of url.\n     * <p>Must set {@code view.setMovementMethod(LinkMovementMethod.getInstance())}</p>\n     *\n     * @param url The url.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setUrl(@NonNull final String url) {\n        setMovementMethodIfNeed();\n        this.url = url;\n        return this;\n    }\n\n    private void setMovementMethodIfNeed() {\n        if (mTextView != null && mTextView.getMovementMethod() == null) {\n            mTextView.setMovementMethod(LinkMovementMethod.getInstance());\n        }\n    }\n\n    /**\n     * Set the span of blur.\n     *\n     * @param radius The radius of blur.\n     * @param style  The style.\n     *               <ul>\n     *               <li>{@link Blur#NORMAL}</li>\n     *               <li>{@link Blur#SOLID}</li>\n     *               <li>{@link Blur#OUTER}</li>\n     *               <li>{@link Blur#INNER}</li>\n     *               </ul>\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setBlur(@FloatRange(from = 0, fromInclusive = false) final float radius,\n                             final Blur style) {\n        this.blurRadius = radius;\n        this.style = style;\n        return this;\n    }\n\n    /**\n     * Set the span of shader.\n     *\n     * @param shader The shader.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setShader(@NonNull final Shader shader) {\n        this.shader = shader;\n        return this;\n    }\n\n    /**\n     * Set the span of shadow.\n     *\n     * @param radius      The radius of shadow.\n     * @param dx          X-axis offset, in pixel.\n     * @param dy          Y-axis offset, in pixel.\n     * @param shadowColor The color of shadow.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setShadow(@FloatRange(from = 0, fromInclusive = false) final float radius,\n                               final float dx,\n                               final float dy,\n                               final int shadowColor) {\n        this.shadowRadius = radius;\n        this.shadowDx = dx;\n        this.shadowDy = dy;\n        this.shadowColor = shadowColor;\n        return this;\n    }\n\n\n    /**\n     * Set the spans.\n     *\n     * @param spans The spans.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils setSpans(@NonNull final Object... spans) {\n        if (spans.length > 0) {\n            this.spans = spans;\n        }\n        return this;\n    }\n\n    /**\n     * Append the text text.\n     *\n     * @param text The text.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils append(@NonNull final CharSequence text) {\n        apply(mTypeCharSequence);\n        mText = text;\n        return this;\n    }\n\n    /**\n     * Append one line.\n     *\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils appendLine() {\n        apply(mTypeCharSequence);\n        mText = LINE_SEPARATOR;\n        return this;\n    }\n\n    /**\n     * Append text and one line.\n     *\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils appendLine(@NonNull final CharSequence text) {\n        apply(mTypeCharSequence);\n        mText = text + LINE_SEPARATOR;\n        return this;\n    }\n\n    /**\n     * Append one image.\n     *\n     * @param bitmap The bitmap of image.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils appendImage(@NonNull final Bitmap bitmap) {\n        return appendImage(bitmap, ALIGN_BOTTOM);\n    }\n\n    /**\n     * Append one image.\n     *\n     * @param bitmap The bitmap.\n     * @param align  The alignment.\n     *               <ul>\n     *               <li>{@link Align#ALIGN_TOP     }</li>\n     *               <li>{@link Align#ALIGN_CENTER  }</li>\n     *               <li>{@link Align#ALIGN_BASELINE}</li>\n     *               <li>{@link Align#ALIGN_BOTTOM  }</li>\n     *               </ul>\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils appendImage(@NonNull final Bitmap bitmap, @Align final int align) {\n        apply(mTypeImage);\n        this.imageBitmap = bitmap;\n        this.alignImage = align;\n        return this;\n    }\n\n    /**\n     * Append one image.\n     *\n     * @param drawable The drawable of image.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils appendImage(@NonNull final Drawable drawable) {\n        return appendImage(drawable, ALIGN_BOTTOM);\n    }\n\n    /**\n     * Append one image.\n     *\n     * @param drawable The drawable of image.\n     * @param align    The alignment.\n     *                 <ul>\n     *                 <li>{@link Align#ALIGN_TOP     }</li>\n     *                 <li>{@link Align#ALIGN_CENTER  }</li>\n     *                 <li>{@link Align#ALIGN_BASELINE}</li>\n     *                 <li>{@link Align#ALIGN_BOTTOM  }</li>\n     *                 </ul>\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils appendImage(@NonNull final Drawable drawable, @Align final int align) {\n        apply(mTypeImage);\n        this.imageDrawable = drawable;\n        this.alignImage = align;\n        return this;\n    }\n\n    /**\n     * Append one image.\n     *\n     * @param uri The uri of image.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils appendImage(@NonNull final Uri uri) {\n        return appendImage(uri, ALIGN_BOTTOM);\n    }\n\n    /**\n     * Append one image.\n     *\n     * @param uri   The uri of image.\n     * @param align The alignment.\n     *              <ul>\n     *              <li>{@link Align#ALIGN_TOP     }</li>\n     *              <li>{@link Align#ALIGN_CENTER  }</li>\n     *              <li>{@link Align#ALIGN_BASELINE}</li>\n     *              <li>{@link Align#ALIGN_BOTTOM  }</li>\n     *              </ul>\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils appendImage(@NonNull final Uri uri, @Align final int align) {\n        apply(mTypeImage);\n        this.imageUri = uri;\n        this.alignImage = align;\n        return this;\n    }\n\n    /**\n     * Append one image.\n     *\n     * @param resourceId The resource id of image.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils appendImage(@DrawableRes final int resourceId) {\n        return appendImage(resourceId, ALIGN_BOTTOM);\n    }\n\n    /**\n     * Append one image.\n     *\n     * @param resourceId The resource id of image.\n     * @param align      The alignment.\n     *                   <ul>\n     *                   <li>{@link Align#ALIGN_TOP     }</li>\n     *                   <li>{@link Align#ALIGN_CENTER  }</li>\n     *                   <li>{@link Align#ALIGN_BASELINE}</li>\n     *                   <li>{@link Align#ALIGN_BOTTOM  }</li>\n     *                   </ul>\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils appendImage(@DrawableRes final int resourceId, @Align final int align) {\n        apply(mTypeImage);\n        this.imageResourceId = resourceId;\n        this.alignImage = align;\n        return this;\n    }\n\n    /**\n     * Append space.\n     *\n     * @param size The size of space.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils appendSpace(@IntRange(from = 0) final int size) {\n        return appendSpace(size, Color.TRANSPARENT);\n    }\n\n    /**\n     * Append space.\n     *\n     * @param size  The size of space.\n     * @param color The color of space.\n     * @return the single {@link SpanUtils} instance\n     */\n    public SpanUtils appendSpace(@IntRange(from = 0) final int size, @ColorInt final int color) {\n        apply(mTypeSpace);\n        spaceSize = size;\n        spaceColor = color;\n        return this;\n    }\n\n    private void apply(final int type) {\n        applyLast();\n        mType = type;\n    }\n\n    public SpannableStringBuilder get() {\n        return mBuilder;\n    }\n\n    /**\n     * Create the span string.\n     *\n     * @return the span string\n     */\n    public SpannableStringBuilder create() {\n        applyLast();\n        if (mTextView != null) {\n            mTextView.setText(mBuilder);\n        }\n        isCreated = true;\n        return mBuilder;\n    }\n\n    private void applyLast() {\n        if (isCreated) {\n            return;\n        }\n        if (mType == mTypeCharSequence) {\n            updateCharCharSequence();\n        } else if (mType == mTypeImage) {\n            updateImage();\n        } else if (mType == mTypeSpace) {\n            updateSpace();\n        }\n        setDefault();\n    }\n\n    private void updateCharCharSequence() {\n        if (mText.length() == 0) return;\n        int start = mBuilder.length();\n        if (start == 0 && lineHeight != -1) {// bug of LineHeightSpan when first line\n            mBuilder.append(Character.toString((char) 2))\n                    .append(\"\\n\")\n                    .setSpan(new AbsoluteSizeSpan(0), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n            start = 2;\n        }\n        mBuilder.append(mText);\n        int end = mBuilder.length();\n        if (verticalAlign != -1) {\n            mBuilder.setSpan(new VerticalAlignSpan(verticalAlign), start, end, flag);\n        }\n        if (foregroundColor != COLOR_DEFAULT) {\n            mBuilder.setSpan(new ForegroundColorSpan(foregroundColor), start, end, flag);\n        }\n        if (backgroundColor != COLOR_DEFAULT) {\n            mBuilder.setSpan(new BackgroundColorSpan(backgroundColor), start, end, flag);\n        }\n        if (first != -1) {\n            mBuilder.setSpan(new LeadingMarginSpan.Standard(first, rest), start, end, flag);\n        }\n        if (quoteColor != COLOR_DEFAULT) {\n            mBuilder.setSpan(\n                    new CustomQuoteSpan(quoteColor, stripeWidth, quoteGapWidth),\n                    start,\n                    end,\n                    flag\n            );\n        }\n        if (bulletColor != COLOR_DEFAULT) {\n            mBuilder.setSpan(\n                    new CustomBulletSpan(bulletColor, bulletRadius, bulletGapWidth),\n                    start,\n                    end,\n                    flag\n            );\n        }\n        if (fontSize != -1) {\n            mBuilder.setSpan(new AbsoluteSizeSpan(fontSize, fontSizeIsDp), start, end, flag);\n        }\n        if (proportion != -1) {\n            mBuilder.setSpan(new RelativeSizeSpan(proportion), start, end, flag);\n        }\n        if (xProportion != -1) {\n            mBuilder.setSpan(new ScaleXSpan(xProportion), start, end, flag);\n        }\n        if (lineHeight != -1) {\n            mBuilder.setSpan(new CustomLineHeightSpan(lineHeight, alignLine), start, end, flag);\n        }\n        if (isStrikethrough) {\n            mBuilder.setSpan(new StrikethroughSpan(), start, end, flag);\n        }\n        if (isUnderline) {\n            mBuilder.setSpan(new UnderlineSpan(), start, end, flag);\n        }\n        if (isSuperscript) {\n            mBuilder.setSpan(new SuperscriptSpan(), start, end, flag);\n        }\n        if (isSubscript) {\n            mBuilder.setSpan(new SubscriptSpan(), start, end, flag);\n        }\n        if (isBold) {\n            mBuilder.setSpan(new StyleSpan(Typeface.BOLD), start, end, flag);\n        }\n        if (isItalic) {\n            mBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, flag);\n        }\n        if (isBoldItalic) {\n            mBuilder.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, flag);\n        }\n        if (fontFamily != null) {\n            mBuilder.setSpan(new TypefaceSpan(fontFamily), start, end, flag);\n        }\n        if (typeface != null) {\n            mBuilder.setSpan(new CustomTypefaceSpan(typeface), start, end, flag);\n        }\n        if (alignment != null) {\n            mBuilder.setSpan(new AlignmentSpan.Standard(alignment), start, end, flag);\n        }\n        if (clickSpan != null) {\n            mBuilder.setSpan(clickSpan, start, end, flag);\n        }\n        if (url != null) {\n            mBuilder.setSpan(new URLSpan(url), start, end, flag);\n        }\n        if (blurRadius != -1) {\n            mBuilder.setSpan(\n                    new MaskFilterSpan(new BlurMaskFilter(blurRadius, style)),\n                    start,\n                    end,\n                    flag\n            );\n        }\n        if (shader != null) {\n            mBuilder.setSpan(new ShaderSpan(shader), start, end, flag);\n        }\n        if (shadowRadius != -1) {\n            mBuilder.setSpan(\n                    new ShadowSpan(shadowRadius, shadowDx, shadowDy, shadowColor),\n                    start,\n                    end,\n                    flag\n            );\n        }\n        if (spans != null) {\n            for (Object span : spans) {\n                mBuilder.setSpan(span, start, end, flag);\n            }\n        }\n    }\n\n    private void updateImage() {\n        int start = mBuilder.length();\n        mText = \"<img>\";\n        updateCharCharSequence();\n        int end = mBuilder.length();\n        if (imageBitmap != null) {\n            mBuilder.setSpan(new CustomImageSpan(imageBitmap, alignImage), start, end, flag);\n        } else if (imageDrawable != null) {\n            mBuilder.setSpan(new CustomImageSpan(imageDrawable, alignImage), start, end, flag);\n        } else if (imageUri != null) {\n            mBuilder.setSpan(new CustomImageSpan(imageUri, alignImage), start, end, flag);\n        } else if (imageResourceId != -1) {\n            mBuilder.setSpan(new CustomImageSpan(imageResourceId, alignImage), start, end, flag);\n        }\n    }\n\n    private void updateSpace() {\n        int start = mBuilder.length();\n        mText = \"< >\";\n        updateCharCharSequence();\n        int end = mBuilder.length();\n        mBuilder.setSpan(new SpaceSpan(spaceSize, spaceColor), start, end, flag);\n    }\n\n    static class VerticalAlignSpan extends ReplacementSpan {\n\n        static final int ALIGN_CENTER = 2;\n        static final int ALIGN_TOP    = 3;\n\n        final int mVerticalAlignment;\n\n        VerticalAlignSpan(int verticalAlignment) {\n            mVerticalAlignment = verticalAlignment;\n        }\n\n        @Override\n        public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm) {\n            text = text.subSequence(start, end);\n            return (int) paint.measureText(text.toString());\n        }\n\n        @Override\n        public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {\n            text = text.subSequence(start, end);\n            Paint.FontMetricsInt fm = paint.getFontMetricsInt();\n//            int need = height - (v + fm.descent - fm.ascent - spanstartv);\n//            if (need > 0) {\n//                if (mVerticalAlignment == ALIGN_TOP) {\n//                    fm.descent += need;\n//                } else if (mVerticalAlignment == ALIGN_CENTER) {\n//                    fm.descent += need / 2;\n//                    fm.ascent -= need / 2;\n//                } else {\n//                    fm.ascent -= need;\n//                }\n//            }\n//            need = height - (v + fm.bottom - fm.top - spanstartv);\n//            if (need > 0) {\n//                if (mVerticalAlignment == ALIGN_TOP) {\n//                    fm.bottom += need;\n//                } else if (mVerticalAlignment == ALIGN_CENTER) {\n//                    fm.bottom += need / 2;\n//                    fm.top -= need / 2;\n//                } else {\n//                    fm.top -= need;\n//                }\n//            }\n\n            canvas.drawText(text.toString(), x, y - ((y + fm.descent + y + fm.ascent) / 2 - (bottom + top) / 2), paint);\n        }\n    }\n\n    static class CustomLineHeightSpan implements LineHeightSpan {\n\n        private final int height;\n\n        static final int ALIGN_CENTER = 2;\n        static final int ALIGN_TOP    = 3;\n\n        final  int                  mVerticalAlignment;\n        static Paint.FontMetricsInt sfm;\n\n        CustomLineHeightSpan(int height, int verticalAlignment) {\n            this.height = height;\n            mVerticalAlignment = verticalAlignment;\n        }\n\n        @Override\n        public void chooseHeight(final CharSequence text, final int start, final int end,\n                                 final int spanstartv, final int v, final Paint.FontMetricsInt fm) {\n//            LogUtils.e(fm, sfm);\n            if (sfm == null) {\n                sfm = new Paint.FontMetricsInt();\n                sfm.top = fm.top;\n                sfm.ascent = fm.ascent;\n                sfm.descent = fm.descent;\n                sfm.bottom = fm.bottom;\n                sfm.leading = fm.leading;\n            } else {\n                fm.top = sfm.top;\n                fm.ascent = sfm.ascent;\n                fm.descent = sfm.descent;\n                fm.bottom = sfm.bottom;\n                fm.leading = sfm.leading;\n            }\n            int need = height - (v + fm.descent - fm.ascent - spanstartv);\n            if (need > 0) {\n                if (mVerticalAlignment == ALIGN_TOP) {\n                    fm.descent += need;\n                } else if (mVerticalAlignment == ALIGN_CENTER) {\n                    fm.descent += need / 2;\n                    fm.ascent -= need / 2;\n                } else {\n                    fm.ascent -= need;\n                }\n            }\n            need = height - (v + fm.bottom - fm.top - spanstartv);\n            if (need > 0) {\n                if (mVerticalAlignment == ALIGN_TOP) {\n                    fm.bottom += need;\n                } else if (mVerticalAlignment == ALIGN_CENTER) {\n                    fm.bottom += need / 2;\n                    fm.top -= need / 2;\n                } else {\n                    fm.top -= need;\n                }\n            }\n            if (end == ((Spanned) text).getSpanEnd(this)) {\n                sfm = null;\n            }\n//            LogUtils.e(fm, sfm);\n        }\n    }\n\n    static class SpaceSpan extends ReplacementSpan {\n\n        private final int   width;\n        private final Paint paint = new Paint();\n\n        private SpaceSpan(final int width) {\n            this(width, Color.TRANSPARENT);\n        }\n\n        private SpaceSpan(final int width, final int color) {\n            super();\n            this.width = width;\n            paint.setColor(color);\n            paint.setStyle(Paint.Style.FILL);\n        }\n\n        @Override\n        public int getSize(@NonNull final Paint paint, final CharSequence text,\n                           @IntRange(from = 0) final int start,\n                           @IntRange(from = 0) final int end,\n                           @Nullable final Paint.FontMetricsInt fm) {\n            return width;\n        }\n\n        @Override\n        public void draw(@NonNull final Canvas canvas, final CharSequence text,\n                         @IntRange(from = 0) final int start,\n                         @IntRange(from = 0) final int end,\n                         final float x, final int top, final int y, final int bottom,\n                         @NonNull final Paint paint) {\n            canvas.drawRect(x, top, x + width, bottom, this.paint);\n        }\n    }\n\n    static class CustomQuoteSpan implements LeadingMarginSpan {\n\n        private final int color;\n        private final int stripeWidth;\n        private final int gapWidth;\n\n        private CustomQuoteSpan(final int color, final int stripeWidth, final int gapWidth) {\n            super();\n            this.color = color;\n            this.stripeWidth = stripeWidth;\n            this.gapWidth = gapWidth;\n        }\n\n        public int getLeadingMargin(final boolean first) {\n            return stripeWidth + gapWidth;\n        }\n\n        public void drawLeadingMargin(final Canvas c, final Paint p, final int x, final int dir,\n                                      final int top, final int baseline, final int bottom,\n                                      final CharSequence text, final int start, final int end,\n                                      final boolean first, final Layout layout) {\n            Paint.Style style = p.getStyle();\n            int color = p.getColor();\n\n            p.setStyle(Paint.Style.FILL);\n            p.setColor(this.color);\n\n            c.drawRect(x, top, x + dir * stripeWidth, bottom, p);\n\n            p.setStyle(style);\n            p.setColor(color);\n        }\n    }\n\n    static class CustomBulletSpan implements LeadingMarginSpan {\n\n        private final int color;\n        private final int radius;\n        private final int gapWidth;\n\n        private Path sBulletPath = null;\n\n        private CustomBulletSpan(final int color, final int radius, final int gapWidth) {\n            this.color = color;\n            this.radius = radius;\n            this.gapWidth = gapWidth;\n        }\n\n        public int getLeadingMargin(final boolean first) {\n            return 2 * radius + gapWidth;\n        }\n\n        public void drawLeadingMargin(final Canvas c, final Paint p, final int x, final int dir,\n                                      final int top, final int baseline, final int bottom,\n                                      final CharSequence text, final int start, final int end,\n                                      final boolean first, final Layout l) {\n            if (((Spanned) text).getSpanStart(this) == start) {\n                Paint.Style style = p.getStyle();\n                int oldColor = 0;\n                oldColor = p.getColor();\n                p.setColor(color);\n                p.setStyle(Paint.Style.FILL);\n                if (c.isHardwareAccelerated()) {\n                    if (sBulletPath == null) {\n                        sBulletPath = new Path();\n                        // Bullet is slightly better to avoid aliasing artifacts on mdpi devices.\n                        sBulletPath.addCircle(0.0f, 0.0f, radius, Path.Direction.CW);\n                    }\n                    c.save();\n                    c.translate(x + dir * radius, (top + bottom) / 2.0f);\n                    c.drawPath(sBulletPath, p);\n                    c.restore();\n                } else {\n                    c.drawCircle(x + dir * radius, (top + bottom) / 2.0f, radius, p);\n                }\n                p.setColor(oldColor);\n                p.setStyle(style);\n            }\n        }\n    }\n\n    @SuppressLint(\"ParcelCreator\")\n    static class CustomTypefaceSpan extends TypefaceSpan {\n\n        private final Typeface newType;\n\n        private CustomTypefaceSpan(final Typeface type) {\n            super(\"\");\n            newType = type;\n        }\n\n        @Override\n        public void updateDrawState(final TextPaint textPaint) {\n            apply(textPaint, newType);\n        }\n\n        @Override\n        public void updateMeasureState(final TextPaint paint) {\n            apply(paint, newType);\n        }\n\n        private void apply(final Paint paint, final Typeface tf) {\n            int oldStyle;\n            Typeface old = paint.getTypeface();\n            if (old == null) {\n                oldStyle = 0;\n            } else {\n                oldStyle = old.getStyle();\n            }\n\n            int fake = oldStyle & ~tf.getStyle();\n            if ((fake & Typeface.BOLD) != 0) {\n                paint.setFakeBoldText(true);\n            }\n\n            if ((fake & Typeface.ITALIC) != 0) {\n                paint.setTextSkewX(-0.25f);\n            }\n\n            paint.getShader();\n\n            paint.setTypeface(tf);\n        }\n    }\n\n    static class CustomImageSpan extends CustomDynamicDrawableSpan {\n        private Drawable mDrawable;\n        private Uri      mContentUri;\n        private int      mResourceId;\n\n        private CustomImageSpan(final Bitmap b, final int verticalAlignment) {\n            super(verticalAlignment);\n            mDrawable = new BitmapDrawable(Utils.getApp().getResources(), b);\n            mDrawable.setBounds(\n                    0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()\n            );\n        }\n\n        private CustomImageSpan(final Drawable d, final int verticalAlignment) {\n            super(verticalAlignment);\n            mDrawable = d;\n            mDrawable.setBounds(\n                    0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()\n            );\n        }\n\n        private CustomImageSpan(final Uri uri, final int verticalAlignment) {\n            super(verticalAlignment);\n            mContentUri = uri;\n        }\n\n        private CustomImageSpan(@DrawableRes final int resourceId, final int verticalAlignment) {\n            super(verticalAlignment);\n            mResourceId = resourceId;\n        }\n\n        @Override\n        public Drawable getDrawable() {\n            Drawable drawable = null;\n            if (mDrawable != null) {\n                drawable = mDrawable;\n            } else if (mContentUri != null) {\n                Bitmap bitmap;\n                try {\n                    InputStream is =\n                            Utils.getApp().getContentResolver().openInputStream(mContentUri);\n                    bitmap = BitmapFactory.decodeStream(is);\n                    drawable = new BitmapDrawable(Utils.getApp().getResources(), bitmap);\n                    drawable.setBounds(\n                            0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()\n                    );\n                    if (is != null) {\n                        is.close();\n                    }\n                } catch (Exception e) {\n                    Log.e(\"sms\", \"Failed to loaded content \" + mContentUri, e);\n                }\n            } else {\n                try {\n                    drawable = ContextCompat.getDrawable(Utils.getApp(), mResourceId);\n                    drawable.setBounds(\n                            0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()\n                    );\n                } catch (Exception e) {\n                    Log.e(\"sms\", \"Unable to find resource: \" + mResourceId);\n                }\n            }\n            return drawable;\n        }\n    }\n\n    static abstract class CustomDynamicDrawableSpan extends ReplacementSpan {\n\n        static final int ALIGN_BOTTOM = 0;\n\n        static final int ALIGN_BASELINE = 1;\n\n        static final int ALIGN_CENTER = 2;\n\n        static final int ALIGN_TOP = 3;\n\n        final int mVerticalAlignment;\n\n        private CustomDynamicDrawableSpan() {\n            mVerticalAlignment = ALIGN_BOTTOM;\n        }\n\n        private CustomDynamicDrawableSpan(final int verticalAlignment) {\n            mVerticalAlignment = verticalAlignment;\n        }\n\n        public abstract Drawable getDrawable();\n\n        @Override\n        public int getSize(@NonNull final Paint paint, final CharSequence text,\n                           final int start, final int end, final Paint.FontMetricsInt fm) {\n            Drawable d = getCachedDrawable();\n            Rect rect = d.getBounds();\n            if (fm != null) {\n//                LogUtils.d(\"fm.top: \" + fm.top,\n//                        \"fm.ascent: \" + fm.ascent,\n//                        \"fm.descent: \" + fm.descent,\n//                        \"fm.bottom: \" + fm.bottom,\n//                        \"lineHeight: \" + (fm.bottom - fm.top));\n                int lineHeight = fm.bottom - fm.top;\n                if (lineHeight < rect.height()) {\n                    if (mVerticalAlignment == ALIGN_TOP) {\n                        fm.top = fm.top;\n                        fm.bottom = rect.height() + fm.top;\n                    } else if (mVerticalAlignment == ALIGN_CENTER) {\n                        fm.top = -rect.height() / 2 - lineHeight / 4;\n                        fm.bottom = rect.height() / 2 - lineHeight / 4;\n                    } else {\n                        fm.top = -rect.height() + fm.bottom;\n                        fm.bottom = fm.bottom;\n                    }\n                    fm.ascent = fm.top;\n                    fm.descent = fm.bottom;\n                }\n            }\n            return rect.right;\n        }\n\n        @Override\n        public void draw(@NonNull final Canvas canvas, final CharSequence text,\n                         final int start, final int end, final float x,\n                         final int top, final int y, final int bottom, @NonNull final Paint paint) {\n            Drawable d = getCachedDrawable();\n            Rect rect = d.getBounds();\n            canvas.save();\n            float transY;\n            int lineHeight = bottom - top;\n//            LogUtils.d(\"rectHeight: \" + rect.height(),\n//                    \"lineHeight: \" + (bottom - top));\n            if (rect.height() < lineHeight) {\n                if (mVerticalAlignment == ALIGN_TOP) {\n                    transY = top;\n                } else if (mVerticalAlignment == ALIGN_CENTER) {\n                    transY = (bottom + top - rect.height()) / 2;\n                } else if (mVerticalAlignment == ALIGN_BASELINE) {\n                    transY = y - rect.height();\n                } else {\n                    transY = bottom - rect.height();\n                }\n                canvas.translate(x, transY);\n            } else {\n                canvas.translate(x, top);\n            }\n            d.draw(canvas);\n            canvas.restore();\n        }\n\n        private Drawable getCachedDrawable() {\n            WeakReference<Drawable> wr = mDrawableRef;\n            Drawable d = null;\n            if (wr != null) {\n                d = wr.get();\n            }\n            if (d == null) {\n                d = getDrawable();\n                mDrawableRef = new WeakReference<>(d);\n            }\n            return d;\n        }\n\n        private WeakReference<Drawable> mDrawableRef;\n    }\n\n    static class ShaderSpan extends CharacterStyle implements UpdateAppearance {\n        private Shader mShader;\n\n        private ShaderSpan(final Shader shader) {\n            this.mShader = shader;\n        }\n\n        @Override\n        public void updateDrawState(final TextPaint tp) {\n            tp.setShader(mShader);\n        }\n    }\n\n    static class ShadowSpan extends CharacterStyle implements UpdateAppearance {\n        private float radius;\n        private float dx, dy;\n        private int shadowColor;\n\n        private ShadowSpan(final float radius,\n                           final float dx,\n                           final float dy,\n                           final int shadowColor) {\n            this.radius = radius;\n            this.dx = dx;\n            this.dy = dy;\n            this.shadowColor = shadowColor;\n        }\n\n        @Override\n        public void updateDrawState(final TextPaint tp) {\n            tp.setShadowLayer(radius, dx, dy, shadowColor);\n        }\n    }\n\n    private static class SerializableSpannableStringBuilder extends SpannableStringBuilder\n            implements Serializable {\n\n        private static final long serialVersionUID = 4909567650765875771L;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/StringUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.res.Resources;\nimport androidx.annotation.ArrayRes;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\n\nimport java.util.IllegalFormatException;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/16\n *     desc  : utils about string\n * </pre>\n */\npublic final class StringUtils {\n\n    private StringUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Return whether the string is null or 0-length.\n     *\n     * @param s The string.\n     * @return {@code true}: yes<br> {@code false}: no\n     */\n    public static boolean isEmpty(final CharSequence s) {\n        return s == null || s.length() == 0;\n    }\n\n    /**\n     * Return whether the string is null or whitespace.\n     *\n     * @param s The string.\n     * @return {@code true}: yes<br> {@code false}: no\n     */\n    public static boolean isTrimEmpty(final String s) {\n        return (s == null || s.trim().length() == 0);\n    }\n\n    /**\n     * Return whether the string is null or white space.\n     *\n     * @param s The string.\n     * @return {@code true}: yes<br> {@code false}: no\n     */\n    public static boolean isSpace(final String s) {\n        if (s == null) return true;\n        for (int i = 0, len = s.length(); i < len; ++i) {\n            if (!Character.isWhitespace(s.charAt(i))) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Return whether string1 is equals to string2.\n     *\n     * @param s1 The first string.\n     * @param s2 The second string.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean equals(final CharSequence s1, final CharSequence s2) {\n        if (s1 == s2) return true;\n        int length;\n        if (s1 != null && s2 != null && (length = s1.length()) == s2.length()) {\n            if (s1 instanceof String && s2 instanceof String) {\n                return s1.equals(s2);\n            } else {\n                for (int i = 0; i < length; i++) {\n                    if (s1.charAt(i) != s2.charAt(i)) return false;\n                }\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Return whether string1 is equals to string2, ignoring case considerations..\n     *\n     * @param s1 The first string.\n     * @param s2 The second string.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean equalsIgnoreCase(final String s1, final String s2) {\n        return s1 == null ? s2 == null : s1.equalsIgnoreCase(s2);\n    }\n\n    /**\n     * Return {@code \"\"} if string equals null.\n     *\n     * @param s The string.\n     * @return {@code \"\"} if string equals null\n     */\n    public static String null2Length0(final String s) {\n        return s == null ? \"\" : s;\n    }\n\n    /**\n     * Return the length of string.\n     *\n     * @param s The string.\n     * @return the length of string\n     */\n    public static int length(final CharSequence s) {\n        return s == null ? 0 : s.length();\n    }\n\n    /**\n     * Set the first letter of string upper.\n     *\n     * @param s The string.\n     * @return the string with first letter upper.\n     */\n    public static String upperFirstLetter(final String s) {\n        if (s == null || s.length() == 0) return \"\";\n        if (!Character.isLowerCase(s.charAt(0))) return s;\n        return (char) (s.charAt(0) - 32) + s.substring(1);\n    }\n\n    /**\n     * Set the first letter of string lower.\n     *\n     * @param s The string.\n     * @return the string with first letter lower.\n     */\n    public static String lowerFirstLetter(final String s) {\n        if (s == null || s.length() == 0) return \"\";\n        if (!Character.isUpperCase(s.charAt(0))) return s;\n        return String.valueOf((char) (s.charAt(0) + 32)) + s.substring(1);\n    }\n\n    /**\n     * Reverse the string.\n     *\n     * @param s The string.\n     * @return the reverse string.\n     */\n    public static String reverse(final String s) {\n        if (s == null) return \"\";\n        int len = s.length();\n        if (len <= 1) return s;\n        int mid = len >> 1;\n        char[] chars = s.toCharArray();\n        char c;\n        for (int i = 0; i < mid; ++i) {\n            c = chars[i];\n            chars[i] = chars[len - i - 1];\n            chars[len - i - 1] = c;\n        }\n        return new String(chars);\n    }\n\n    /**\n     * Convert string to DBC.\n     *\n     * @param s The string.\n     * @return the DBC string\n     */\n    public static String toDBC(final String s) {\n        if (s == null || s.length() == 0) return \"\";\n        char[] chars = s.toCharArray();\n        for (int i = 0, len = chars.length; i < len; i++) {\n            if (chars[i] == 12288) {\n                chars[i] = ' ';\n            } else if (65281 <= chars[i] && chars[i] <= 65374) {\n                chars[i] = (char) (chars[i] - 65248);\n            } else {\n                chars[i] = chars[i];\n            }\n        }\n        return new String(chars);\n    }\n\n    /**\n     * Convert string to SBC.\n     *\n     * @param s The string.\n     * @return the SBC string\n     */\n    public static String toSBC(final String s) {\n        if (s == null || s.length() == 0) return \"\";\n        char[] chars = s.toCharArray();\n        for (int i = 0, len = chars.length; i < len; i++) {\n            if (chars[i] == ' ') {\n                chars[i] = (char) 12288;\n            } else if (33 <= chars[i] && chars[i] <= 126) {\n                chars[i] = (char) (chars[i] + 65248);\n            } else {\n                chars[i] = chars[i];\n            }\n        }\n        return new String(chars);\n    }\n\n    /**\n     * Return the string value associated with a particular resource ID.\n     *\n     * @param id The desired resource identifier.\n     * @return the string value associated with a particular resource ID.\n     */\n    public static String getString(@StringRes int id) {\n        return getString(id, (Object[]) null);\n    }\n\n    /**\n     * Return the string value associated with a particular resource ID.\n     *\n     * @param id         The desired resource identifier.\n     * @param formatArgs The format arguments that will be used for substitution.\n     * @return the string value associated with a particular resource ID.\n     */\n    public static String getString(@StringRes int id, Object... formatArgs) {\n        try {\n            return format(Utils.getApp().getString(id), formatArgs);\n        } catch (Resources.NotFoundException e) {\n            e.printStackTrace();\n            return String.valueOf(id);\n        }\n    }\n\n    /**\n     * Return the string array associated with a particular resource ID.\n     *\n     * @param id The desired resource identifier.\n     * @return The string array associated with the resource.\n     */\n    public static String[] getStringArray(@ArrayRes int id) {\n        try {\n            return Utils.getApp().getResources().getStringArray(id);\n        } catch (Resources.NotFoundException e) {\n            e.printStackTrace();\n            return new String[]{String.valueOf(id)};\n        }\n    }\n\n    /**\n     * Format the string.\n     *\n     * @param str  The string.\n     * @param args The args.\n     * @return a formatted string.\n     */\n    public static String format(@Nullable String str, Object... args) {\n        String text = str;\n        if (text != null) {\n            if (args != null && args.length > 0) {\n                try {\n                    text = String.format(str, args);\n                } catch (IllegalFormatException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n        return text;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/TemperatureUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\n/**\n * <pre>\n *     author: Faramarz Afzali\n *     time  : 2020/09/05\n *     desc  : This class is intended for converting temperatures into different units.\n *             C refers to the Celsius unit\n *             F refers to the Fahrenheit unit\n *             K refers to the Kelvin unit\n * </pre>\n */\npublic final class TemperatureUtils {\n\n    public static float cToF(float temp) {\n        return (temp * 9) / 5 + 32;\n    }\n\n    public static float cToK(float temp) {\n        return temp + 273.15f;\n    }\n\n\n    public static float fToC(float temp) {\n        return (temp - 32) * 5 / 9;\n    }\n\n    public static float fToK(float temp) {\n        return temp + 255.3722222222f;\n    }\n\n\n    public static float kToC(float temp) {\n        return temp - 273.15f;\n    }\n\n    public static float kToF(float temp) {\n        return temp - 459.67f;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ThreadUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.os.Handler;\nimport android.os.Looper;\nimport androidx.annotation.CallSuper;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.util.Log;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Timer;\nimport java.util.TimerTask;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.RejectedExecutionException;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/05/08\n *     desc  : utils about thread\n * </pre>\n */\npublic final class ThreadUtils {\n\n    private static final Handler HANDLER = new Handler(Looper.getMainLooper());\n\n    private static final Map<Integer, Map<Integer, ExecutorService>> TYPE_PRIORITY_POOLS = new HashMap<>();\n\n    private static final Map<Task, ExecutorService> TASK_POOL_MAP = new ConcurrentHashMap<>();\n\n    private static final int   CPU_COUNT = Runtime.getRuntime().availableProcessors();\n    private static final Timer TIMER     = new Timer();\n\n    private static final byte TYPE_SINGLE = -1;\n    private static final byte TYPE_CACHED = -2;\n    private static final byte TYPE_IO     = -4;\n    private static final byte TYPE_CPU    = -8;\n\n    private static Executor sDeliver;\n\n    /**\n     * Return whether the thread is the main thread.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isMainThread() {\n        return Looper.myLooper() == Looper.getMainLooper();\n    }\n\n    public static Handler getMainHandler() {\n        return HANDLER;\n    }\n\n    public static void runOnUiThread(final Runnable runnable) {\n        if (Looper.myLooper() == Looper.getMainLooper()) {\n            runnable.run();\n        } else {\n            HANDLER.post(runnable);\n        }\n    }\n\n    public static void runOnUiThreadDelayed(final Runnable runnable, long delayMillis) {\n        HANDLER.postDelayed(runnable, delayMillis);\n    }\n\n    /**\n     * Return a thread pool that reuses a fixed number of threads\n     * operating off a shared unbounded queue, using the provided\n     * ThreadFactory to create new threads when needed.\n     *\n     * @param size The size of thread in the pool.\n     * @return a fixed thread pool\n     */\n    public static ExecutorService getFixedPool(@IntRange(from = 1) final int size) {\n        return getPoolByTypeAndPriority(size);\n    }\n\n    /**\n     * Return a thread pool that reuses a fixed number of threads\n     * operating off a shared unbounded queue, using the provided\n     * ThreadFactory to create new threads when needed.\n     *\n     * @param size     The size of thread in the pool.\n     * @param priority The priority of thread in the poll.\n     * @return a fixed thread pool\n     */\n    public static ExecutorService getFixedPool(@IntRange(from = 1) final int size,\n                                               @IntRange(from = 1, to = 10) final int priority) {\n        return getPoolByTypeAndPriority(size, priority);\n    }\n\n    /**\n     * Return a thread pool that uses a single worker thread operating\n     * off an unbounded queue, and uses the provided ThreadFactory to\n     * create a new thread when needed.\n     *\n     * @return a single thread pool\n     */\n    public static ExecutorService getSinglePool() {\n        return getPoolByTypeAndPriority(TYPE_SINGLE);\n    }\n\n    /**\n     * Return a thread pool that uses a single worker thread operating\n     * off an unbounded queue, and uses the provided ThreadFactory to\n     * create a new thread when needed.\n     *\n     * @param priority The priority of thread in the poll.\n     * @return a single thread pool\n     */\n    public static ExecutorService getSinglePool(@IntRange(from = 1, to = 10) final int priority) {\n        return getPoolByTypeAndPriority(TYPE_SINGLE, priority);\n    }\n\n    /**\n     * Return a thread pool that creates new threads as needed, but\n     * will reuse previously constructed threads when they are\n     * available.\n     *\n     * @return a cached thread pool\n     */\n    public static ExecutorService getCachedPool() {\n        return getPoolByTypeAndPriority(TYPE_CACHED);\n    }\n\n    /**\n     * Return a thread pool that creates new threads as needed, but\n     * will reuse previously constructed threads when they are\n     * available.\n     *\n     * @param priority The priority of thread in the poll.\n     * @return a cached thread pool\n     */\n    public static ExecutorService getCachedPool(@IntRange(from = 1, to = 10) final int priority) {\n        return getPoolByTypeAndPriority(TYPE_CACHED, priority);\n    }\n\n    /**\n     * Return a thread pool that creates (2 * CPU_COUNT + 1) threads\n     * operating off a queue which size is 128.\n     *\n     * @return a IO thread pool\n     */\n    public static ExecutorService getIoPool() {\n        return getPoolByTypeAndPriority(TYPE_IO);\n    }\n\n    /**\n     * Return a thread pool that creates (2 * CPU_COUNT + 1) threads\n     * operating off a queue which size is 128.\n     *\n     * @param priority The priority of thread in the poll.\n     * @return a IO thread pool\n     */\n    public static ExecutorService getIoPool(@IntRange(from = 1, to = 10) final int priority) {\n        return getPoolByTypeAndPriority(TYPE_IO, priority);\n    }\n\n    /**\n     * Return a thread pool that creates (CPU_COUNT + 1) threads\n     * operating off a queue which size is 128 and the maximum\n     * number of threads equals (2 * CPU_COUNT + 1).\n     *\n     * @return a cpu thread pool for\n     */\n    public static ExecutorService getCpuPool() {\n        return getPoolByTypeAndPriority(TYPE_CPU);\n    }\n\n    /**\n     * Return a thread pool that creates (CPU_COUNT + 1) threads\n     * operating off a queue which size is 128 and the maximum\n     * number of threads equals (2 * CPU_COUNT + 1).\n     *\n     * @param priority The priority of thread in the poll.\n     * @return a cpu thread pool for\n     */\n    public static ExecutorService getCpuPool(@IntRange(from = 1, to = 10) final int priority) {\n        return getPoolByTypeAndPriority(TYPE_CPU, priority);\n    }\n\n    /**\n     * Executes the given task in a fixed thread pool.\n     *\n     * @param size The size of thread in the fixed thread pool.\n     * @param task The task to execute.\n     * @param <T>  The type of the task's result.\n     */\n    public static <T> void executeByFixed(@IntRange(from = 1) final int size, final Task<T> task) {\n        execute(getPoolByTypeAndPriority(size), task);\n    }\n\n    /**\n     * Executes the given task in a fixed thread pool.\n     *\n     * @param size     The size of thread in the fixed thread pool.\n     * @param task     The task to execute.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeByFixed(@IntRange(from = 1) final int size,\n                                          final Task<T> task,\n                                          @IntRange(from = 1, to = 10) final int priority) {\n        execute(getPoolByTypeAndPriority(size, priority), task);\n    }\n\n    /**\n     * Executes the given task in a fixed thread pool after the given delay.\n     *\n     * @param size  The size of thread in the fixed thread pool.\n     * @param task  The task to execute.\n     * @param delay The time from now to delay execution.\n     * @param unit  The time unit of the delay parameter.\n     * @param <T>   The type of the task's result.\n     */\n    public static <T> void executeByFixedWithDelay(@IntRange(from = 1) final int size,\n                                                   final Task<T> task,\n                                                   final long delay,\n                                                   final TimeUnit unit) {\n        executeWithDelay(getPoolByTypeAndPriority(size), task, delay, unit);\n    }\n\n    /**\n     * Executes the given task in a fixed thread pool after the given delay.\n     *\n     * @param size     The size of thread in the fixed thread pool.\n     * @param task     The task to execute.\n     * @param delay    The time from now to delay execution.\n     * @param unit     The time unit of the delay parameter.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeByFixedWithDelay(@IntRange(from = 1) final int size,\n                                                   final Task<T> task,\n                                                   final long delay,\n                                                   final TimeUnit unit,\n                                                   @IntRange(from = 1, to = 10) final int priority) {\n        executeWithDelay(getPoolByTypeAndPriority(size, priority), task, delay, unit);\n    }\n\n    /**\n     * Executes the given task in a fixed thread pool at fix rate.\n     *\n     * @param size   The size of thread in the fixed thread pool.\n     * @param task   The task to execute.\n     * @param period The period between successive executions.\n     * @param unit   The time unit of the period parameter.\n     * @param <T>    The type of the task's result.\n     */\n    public static <T> void executeByFixedAtFixRate(@IntRange(from = 1) final int size,\n                                                   final Task<T> task,\n                                                   final long period,\n                                                   final TimeUnit unit) {\n        executeAtFixedRate(getPoolByTypeAndPriority(size), task, 0, period, unit);\n    }\n\n    /**\n     * Executes the given task in a fixed thread pool at fix rate.\n     *\n     * @param size     The size of thread in the fixed thread pool.\n     * @param task     The task to execute.\n     * @param period   The period between successive executions.\n     * @param unit     The time unit of the period parameter.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeByFixedAtFixRate(@IntRange(from = 1) final int size,\n                                                   final Task<T> task,\n                                                   final long period,\n                                                   final TimeUnit unit,\n                                                   @IntRange(from = 1, to = 10) final int priority) {\n        executeAtFixedRate(getPoolByTypeAndPriority(size, priority), task, 0, period, unit);\n    }\n\n    /**\n     * Executes the given task in a fixed thread pool at fix rate.\n     *\n     * @param size         The size of thread in the fixed thread pool.\n     * @param task         The task to execute.\n     * @param initialDelay The time to delay first execution.\n     * @param period       The period between successive executions.\n     * @param unit         The time unit of the initialDelay and period parameters.\n     * @param <T>          The type of the task's result.\n     */\n    public static <T> void executeByFixedAtFixRate(@IntRange(from = 1) final int size,\n                                                   final Task<T> task,\n                                                   long initialDelay,\n                                                   final long period,\n                                                   final TimeUnit unit) {\n        executeAtFixedRate(getPoolByTypeAndPriority(size), task, initialDelay, period, unit);\n    }\n\n    /**\n     * Executes the given task in a fixed thread pool at fix rate.\n     *\n     * @param size         The size of thread in the fixed thread pool.\n     * @param task         The task to execute.\n     * @param initialDelay The time to delay first execution.\n     * @param period       The period between successive executions.\n     * @param unit         The time unit of the initialDelay and period parameters.\n     * @param priority     The priority of thread in the poll.\n     * @param <T>          The type of the task's result.\n     */\n    public static <T> void executeByFixedAtFixRate(@IntRange(from = 1) final int size,\n                                                   final Task<T> task,\n                                                   long initialDelay,\n                                                   final long period,\n                                                   final TimeUnit unit,\n                                                   @IntRange(from = 1, to = 10) final int priority) {\n        executeAtFixedRate(getPoolByTypeAndPriority(size, priority), task, initialDelay, period, unit);\n    }\n\n    /**\n     * Executes the given task in a single thread pool.\n     *\n     * @param task The task to execute.\n     * @param <T>  The type of the task's result.\n     */\n    public static <T> void executeBySingle(final Task<T> task) {\n        execute(getPoolByTypeAndPriority(TYPE_SINGLE), task);\n    }\n\n    /**\n     * Executes the given task in a single thread pool.\n     *\n     * @param task     The task to execute.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeBySingle(final Task<T> task,\n                                           @IntRange(from = 1, to = 10) final int priority) {\n        execute(getPoolByTypeAndPriority(TYPE_SINGLE, priority), task);\n    }\n\n    /**\n     * Executes the given task in a single thread pool after the given delay.\n     *\n     * @param task  The task to execute.\n     * @param delay The time from now to delay execution.\n     * @param unit  The time unit of the delay parameter.\n     * @param <T>   The type of the task's result.\n     */\n    public static <T> void executeBySingleWithDelay(final Task<T> task,\n                                                    final long delay,\n                                                    final TimeUnit unit) {\n        executeWithDelay(getPoolByTypeAndPriority(TYPE_SINGLE), task, delay, unit);\n    }\n\n    /**\n     * Executes the given task in a single thread pool after the given delay.\n     *\n     * @param task     The task to execute.\n     * @param delay    The time from now to delay execution.\n     * @param unit     The time unit of the delay parameter.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeBySingleWithDelay(final Task<T> task,\n                                                    final long delay,\n                                                    final TimeUnit unit,\n                                                    @IntRange(from = 1, to = 10) final int priority) {\n        executeWithDelay(getPoolByTypeAndPriority(TYPE_SINGLE, priority), task, delay, unit);\n    }\n\n    /**\n     * Executes the given task in a single thread pool at fix rate.\n     *\n     * @param task   The task to execute.\n     * @param period The period between successive executions.\n     * @param unit   The time unit of the period parameter.\n     * @param <T>    The type of the task's result.\n     */\n    public static <T> void executeBySingleAtFixRate(final Task<T> task,\n                                                    final long period,\n                                                    final TimeUnit unit) {\n        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_SINGLE), task, 0, period, unit);\n    }\n\n    /**\n     * Executes the given task in a single thread pool at fix rate.\n     *\n     * @param task     The task to execute.\n     * @param period   The period between successive executions.\n     * @param unit     The time unit of the period parameter.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeBySingleAtFixRate(final Task<T> task,\n                                                    final long period,\n                                                    final TimeUnit unit,\n                                                    @IntRange(from = 1, to = 10) final int priority) {\n        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_SINGLE, priority), task, 0, period, unit);\n    }\n\n    /**\n     * Executes the given task in a single thread pool at fix rate.\n     *\n     * @param task         The task to execute.\n     * @param initialDelay The time to delay first execution.\n     * @param period       The period between successive executions.\n     * @param unit         The time unit of the initialDelay and period parameters.\n     * @param <T>          The type of the task's result.\n     */\n    public static <T> void executeBySingleAtFixRate(final Task<T> task,\n                                                    long initialDelay,\n                                                    final long period,\n                                                    final TimeUnit unit) {\n        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_SINGLE), task, initialDelay, period, unit);\n    }\n\n    /**\n     * Executes the given task in a single thread pool at fix rate.\n     *\n     * @param task         The task to execute.\n     * @param initialDelay The time to delay first execution.\n     * @param period       The period between successive executions.\n     * @param unit         The time unit of the initialDelay and period parameters.\n     * @param priority     The priority of thread in the poll.\n     * @param <T>          The type of the task's result.\n     */\n    public static <T> void executeBySingleAtFixRate(final Task<T> task,\n                                                    long initialDelay,\n                                                    final long period,\n                                                    final TimeUnit unit,\n                                                    @IntRange(from = 1, to = 10) final int priority) {\n        executeAtFixedRate(\n                getPoolByTypeAndPriority(TYPE_SINGLE, priority), task, initialDelay, period, unit\n        );\n    }\n\n    /**\n     * Executes the given task in a cached thread pool.\n     *\n     * @param task The task to execute.\n     * @param <T>  The type of the task's result.\n     */\n    public static <T> void executeByCached(final Task<T> task) {\n        execute(getPoolByTypeAndPriority(TYPE_CACHED), task);\n    }\n\n    /**\n     * Executes the given task in a cached thread pool.\n     *\n     * @param task     The task to execute.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeByCached(final Task<T> task,\n                                           @IntRange(from = 1, to = 10) final int priority) {\n        execute(getPoolByTypeAndPriority(TYPE_CACHED, priority), task);\n    }\n\n    /**\n     * Executes the given task in a cached thread pool after the given delay.\n     *\n     * @param task  The task to execute.\n     * @param delay The time from now to delay execution.\n     * @param unit  The time unit of the delay parameter.\n     * @param <T>   The type of the task's result.\n     */\n    public static <T> void executeByCachedWithDelay(final Task<T> task,\n                                                    final long delay,\n                                                    final TimeUnit unit) {\n        executeWithDelay(getPoolByTypeAndPriority(TYPE_CACHED), task, delay, unit);\n    }\n\n    /**\n     * Executes the given task in a cached thread pool after the given delay.\n     *\n     * @param task     The task to execute.\n     * @param delay    The time from now to delay execution.\n     * @param unit     The time unit of the delay parameter.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeByCachedWithDelay(final Task<T> task,\n                                                    final long delay,\n                                                    final TimeUnit unit,\n                                                    @IntRange(from = 1, to = 10) final int priority) {\n        executeWithDelay(getPoolByTypeAndPriority(TYPE_CACHED, priority), task, delay, unit);\n    }\n\n    /**\n     * Executes the given task in a cached thread pool at fix rate.\n     *\n     * @param task   The task to execute.\n     * @param period The period between successive executions.\n     * @param unit   The time unit of the period parameter.\n     * @param <T>    The type of the task's result.\n     */\n    public static <T> void executeByCachedAtFixRate(final Task<T> task,\n                                                    final long period,\n                                                    final TimeUnit unit) {\n        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CACHED), task, 0, period, unit);\n    }\n\n    /**\n     * Executes the given task in a cached thread pool at fix rate.\n     *\n     * @param task     The task to execute.\n     * @param period   The period between successive executions.\n     * @param unit     The time unit of the period parameter.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeByCachedAtFixRate(final Task<T> task,\n                                                    final long period,\n                                                    final TimeUnit unit,\n                                                    @IntRange(from = 1, to = 10) final int priority) {\n        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CACHED, priority), task, 0, period, unit);\n    }\n\n    /**\n     * Executes the given task in a cached thread pool at fix rate.\n     *\n     * @param task         The task to execute.\n     * @param initialDelay The time to delay first execution.\n     * @param period       The period between successive executions.\n     * @param unit         The time unit of the initialDelay and period parameters.\n     * @param <T>          The type of the task's result.\n     */\n    public static <T> void executeByCachedAtFixRate(final Task<T> task,\n                                                    long initialDelay,\n                                                    final long period,\n                                                    final TimeUnit unit) {\n        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CACHED), task, initialDelay, period, unit);\n    }\n\n    /**\n     * Executes the given task in a cached thread pool at fix rate.\n     *\n     * @param task         The task to execute.\n     * @param initialDelay The time to delay first execution.\n     * @param period       The period between successive executions.\n     * @param unit         The time unit of the initialDelay and period parameters.\n     * @param priority     The priority of thread in the poll.\n     * @param <T>          The type of the task's result.\n     */\n    public static <T> void executeByCachedAtFixRate(final Task<T> task,\n                                                    long initialDelay,\n                                                    final long period,\n                                                    final TimeUnit unit,\n                                                    @IntRange(from = 1, to = 10) final int priority) {\n        executeAtFixedRate(\n                getPoolByTypeAndPriority(TYPE_CACHED, priority), task, initialDelay, period, unit\n        );\n    }\n\n    /**\n     * Executes the given task in an IO thread pool.\n     *\n     * @param task The task to execute.\n     * @param <T>  The type of the task's result.\n     */\n    public static <T> void executeByIo(final Task<T> task) {\n        execute(getPoolByTypeAndPriority(TYPE_IO), task);\n    }\n\n    /**\n     * Executes the given task in an IO thread pool.\n     *\n     * @param task     The task to execute.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeByIo(final Task<T> task,\n                                       @IntRange(from = 1, to = 10) final int priority) {\n        execute(getPoolByTypeAndPriority(TYPE_IO, priority), task);\n    }\n\n    /**\n     * Executes the given task in an IO thread pool after the given delay.\n     *\n     * @param task  The task to execute.\n     * @param delay The time from now to delay execution.\n     * @param unit  The time unit of the delay parameter.\n     * @param <T>   The type of the task's result.\n     */\n    public static <T> void executeByIoWithDelay(final Task<T> task,\n                                                final long delay,\n                                                final TimeUnit unit) {\n        executeWithDelay(getPoolByTypeAndPriority(TYPE_IO), task, delay, unit);\n    }\n\n    /**\n     * Executes the given task in an IO thread pool after the given delay.\n     *\n     * @param task     The task to execute.\n     * @param delay    The time from now to delay execution.\n     * @param unit     The time unit of the delay parameter.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeByIoWithDelay(final Task<T> task,\n                                                final long delay,\n                                                final TimeUnit unit,\n                                                @IntRange(from = 1, to = 10) final int priority) {\n        executeWithDelay(getPoolByTypeAndPriority(TYPE_IO, priority), task, delay, unit);\n    }\n\n    /**\n     * Executes the given task in an IO thread pool at fix rate.\n     *\n     * @param task   The task to execute.\n     * @param period The period between successive executions.\n     * @param unit   The time unit of the period parameter.\n     * @param <T>    The type of the task's result.\n     */\n    public static <T> void executeByIoAtFixRate(final Task<T> task,\n                                                final long period,\n                                                final TimeUnit unit) {\n        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_IO), task, 0, period, unit);\n    }\n\n    /**\n     * Executes the given task in an IO thread pool at fix rate.\n     *\n     * @param task     The task to execute.\n     * @param period   The period between successive executions.\n     * @param unit     The time unit of the period parameter.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeByIoAtFixRate(final Task<T> task,\n                                                final long period,\n                                                final TimeUnit unit,\n                                                @IntRange(from = 1, to = 10) final int priority) {\n        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_IO, priority), task, 0, period, unit);\n    }\n\n    /**\n     * Executes the given task in an IO thread pool at fix rate.\n     *\n     * @param task         The task to execute.\n     * @param initialDelay The time to delay first execution.\n     * @param period       The period between successive executions.\n     * @param unit         The time unit of the initialDelay and period parameters.\n     * @param <T>          The type of the task's result.\n     */\n    public static <T> void executeByIoAtFixRate(final Task<T> task,\n                                                long initialDelay,\n                                                final long period,\n                                                final TimeUnit unit) {\n        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_IO), task, initialDelay, period, unit);\n    }\n\n    /**\n     * Executes the given task in an IO thread pool at fix rate.\n     *\n     * @param task         The task to execute.\n     * @param initialDelay The time to delay first execution.\n     * @param period       The period between successive executions.\n     * @param unit         The time unit of the initialDelay and period parameters.\n     * @param priority     The priority of thread in the poll.\n     * @param <T>          The type of the task's result.\n     */\n    public static <T> void executeByIoAtFixRate(final Task<T> task,\n                                                long initialDelay,\n                                                final long period,\n                                                final TimeUnit unit,\n                                                @IntRange(from = 1, to = 10) final int priority) {\n        executeAtFixedRate(\n                getPoolByTypeAndPriority(TYPE_IO, priority), task, initialDelay, period, unit\n        );\n    }\n\n    /**\n     * Executes the given task in a cpu thread pool.\n     *\n     * @param task The task to execute.\n     * @param <T>  The type of the task's result.\n     */\n    public static <T> void executeByCpu(final Task<T> task) {\n        execute(getPoolByTypeAndPriority(TYPE_CPU), task);\n    }\n\n    /**\n     * Executes the given task in a cpu thread pool.\n     *\n     * @param task     The task to execute.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeByCpu(final Task<T> task,\n                                        @IntRange(from = 1, to = 10) final int priority) {\n        execute(getPoolByTypeAndPriority(TYPE_CPU, priority), task);\n    }\n\n    /**\n     * Executes the given task in a cpu thread pool after the given delay.\n     *\n     * @param task  The task to execute.\n     * @param delay The time from now to delay execution.\n     * @param unit  The time unit of the delay parameter.\n     * @param <T>   The type of the task's result.\n     */\n    public static <T> void executeByCpuWithDelay(final Task<T> task,\n                                                 final long delay,\n                                                 final TimeUnit unit) {\n        executeWithDelay(getPoolByTypeAndPriority(TYPE_CPU), task, delay, unit);\n    }\n\n    /**\n     * Executes the given task in a cpu thread pool after the given delay.\n     *\n     * @param task     The task to execute.\n     * @param delay    The time from now to delay execution.\n     * @param unit     The time unit of the delay parameter.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeByCpuWithDelay(final Task<T> task,\n                                                 final long delay,\n                                                 final TimeUnit unit,\n                                                 @IntRange(from = 1, to = 10) final int priority) {\n        executeWithDelay(getPoolByTypeAndPriority(TYPE_CPU, priority), task, delay, unit);\n    }\n\n    /**\n     * Executes the given task in a cpu thread pool at fix rate.\n     *\n     * @param task   The task to execute.\n     * @param period The period between successive executions.\n     * @param unit   The time unit of the period parameter.\n     * @param <T>    The type of the task's result.\n     */\n    public static <T> void executeByCpuAtFixRate(final Task<T> task,\n                                                 final long period,\n                                                 final TimeUnit unit) {\n        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CPU), task, 0, period, unit);\n    }\n\n    /**\n     * Executes the given task in a cpu thread pool at fix rate.\n     *\n     * @param task     The task to execute.\n     * @param period   The period between successive executions.\n     * @param unit     The time unit of the period parameter.\n     * @param priority The priority of thread in the poll.\n     * @param <T>      The type of the task's result.\n     */\n    public static <T> void executeByCpuAtFixRate(final Task<T> task,\n                                                 final long period,\n                                                 final TimeUnit unit,\n                                                 @IntRange(from = 1, to = 10) final int priority) {\n        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CPU, priority), task, 0, period, unit);\n    }\n\n    /**\n     * Executes the given task in a cpu thread pool at fix rate.\n     *\n     * @param task         The task to execute.\n     * @param initialDelay The time to delay first execution.\n     * @param period       The period between successive executions.\n     * @param unit         The time unit of the initialDelay and period parameters.\n     * @param <T>          The type of the task's result.\n     */\n    public static <T> void executeByCpuAtFixRate(final Task<T> task,\n                                                 long initialDelay,\n                                                 final long period,\n                                                 final TimeUnit unit) {\n        executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CPU), task, initialDelay, period, unit);\n    }\n\n    /**\n     * Executes the given task in a cpu thread pool at fix rate.\n     *\n     * @param task         The task to execute.\n     * @param initialDelay The time to delay first execution.\n     * @param period       The period between successive executions.\n     * @param unit         The time unit of the initialDelay and period parameters.\n     * @param priority     The priority of thread in the poll.\n     * @param <T>          The type of the task's result.\n     */\n    public static <T> void executeByCpuAtFixRate(final Task<T> task,\n                                                 long initialDelay,\n                                                 final long period,\n                                                 final TimeUnit unit,\n                                                 @IntRange(from = 1, to = 10) final int priority) {\n        executeAtFixedRate(\n                getPoolByTypeAndPriority(TYPE_CPU, priority), task, initialDelay, period, unit\n        );\n    }\n\n    /**\n     * Executes the given task in a custom thread pool.\n     *\n     * @param pool The custom thread pool.\n     * @param task The task to execute.\n     * @param <T>  The type of the task's result.\n     */\n    public static <T> void executeByCustom(final ExecutorService pool, final Task<T> task) {\n        execute(pool, task);\n    }\n\n    /**\n     * Executes the given task in a custom thread pool after the given delay.\n     *\n     * @param pool  The custom thread pool.\n     * @param task  The task to execute.\n     * @param delay The time from now to delay execution.\n     * @param unit  The time unit of the delay parameter.\n     * @param <T>   The type of the task's result.\n     */\n    public static <T> void executeByCustomWithDelay(final ExecutorService pool,\n                                                    final Task<T> task,\n                                                    final long delay,\n                                                    final TimeUnit unit) {\n        executeWithDelay(pool, task, delay, unit);\n    }\n\n    /**\n     * Executes the given task in a custom thread pool at fix rate.\n     *\n     * @param pool   The custom thread pool.\n     * @param task   The task to execute.\n     * @param period The period between successive executions.\n     * @param unit   The time unit of the period parameter.\n     * @param <T>    The type of the task's result.\n     */\n    public static <T> void executeByCustomAtFixRate(final ExecutorService pool,\n                                                    final Task<T> task,\n                                                    final long period,\n                                                    final TimeUnit unit) {\n        executeAtFixedRate(pool, task, 0, period, unit);\n    }\n\n    /**\n     * Executes the given task in a custom thread pool at fix rate.\n     *\n     * @param pool         The custom thread pool.\n     * @param task         The task to execute.\n     * @param initialDelay The time to delay first execution.\n     * @param period       The period between successive executions.\n     * @param unit         The time unit of the initialDelay and period parameters.\n     * @param <T>          The type of the task's result.\n     */\n    public static <T> void executeByCustomAtFixRate(final ExecutorService pool,\n                                                    final Task<T> task,\n                                                    long initialDelay,\n                                                    final long period,\n                                                    final TimeUnit unit) {\n        executeAtFixedRate(pool, task, initialDelay, period, unit);\n    }\n\n    /**\n     * Cancel the given task.\n     *\n     * @param task The task to cancel.\n     */\n    public static void cancel(final Task task) {\n        if (task == null) return;\n        task.cancel();\n    }\n\n    /**\n     * Cancel the given tasks.\n     *\n     * @param tasks The tasks to cancel.\n     */\n    public static void cancel(final Task... tasks) {\n        if (tasks == null || tasks.length == 0) return;\n        for (Task task : tasks) {\n            if (task == null) continue;\n            task.cancel();\n        }\n    }\n\n    /**\n     * Cancel the given tasks.\n     *\n     * @param tasks The tasks to cancel.\n     */\n    public static void cancel(final List<Task> tasks) {\n        if (tasks == null || tasks.size() == 0) return;\n        for (Task task : tasks) {\n            if (task == null) continue;\n            task.cancel();\n        }\n    }\n\n    /**\n     * Cancel the tasks in pool.\n     *\n     * @param executorService The pool.\n     */\n    public static void cancel(ExecutorService executorService) {\n        if (executorService instanceof ThreadPoolExecutor4Util) {\n            for (Map.Entry<Task, ExecutorService> taskTaskInfoEntry : TASK_POOL_MAP.entrySet()) {\n                if (taskTaskInfoEntry.getValue() == executorService) {\n                    cancel(taskTaskInfoEntry.getKey());\n                }\n            }\n        } else {\n            Log.e(\"ThreadUtils\", \"The executorService is not ThreadUtils's pool.\");\n        }\n    }\n\n    /**\n     * Set the deliver.\n     *\n     * @param deliver The deliver.\n     */\n    public static void setDeliver(final Executor deliver) {\n        sDeliver = deliver;\n    }\n\n    private static <T> void execute(final ExecutorService pool, final Task<T> task) {\n        execute(pool, task, 0, 0, null);\n    }\n\n    private static <T> void executeWithDelay(final ExecutorService pool,\n                                             final Task<T> task,\n                                             final long delay,\n                                             final TimeUnit unit) {\n        execute(pool, task, delay, 0, unit);\n    }\n\n    private static <T> void executeAtFixedRate(final ExecutorService pool,\n                                               final Task<T> task,\n                                               long delay,\n                                               final long period,\n                                               final TimeUnit unit) {\n        execute(pool, task, delay, period, unit);\n    }\n\n    private static <T> void execute(final ExecutorService pool, final Task<T> task,\n                                    long delay, final long period, final TimeUnit unit) {\n        synchronized (TASK_POOL_MAP) {\n            if (TASK_POOL_MAP.get(task) != null) {\n                Log.e(\"ThreadUtils\", \"Task can only be executed once.\");\n                return;\n            }\n            TASK_POOL_MAP.put(task, pool);\n        }\n        if (period == 0) {\n            if (delay == 0) {\n                pool.execute(task);\n            } else {\n                TimerTask timerTask = new TimerTask() {\n                    @Override\n                    public void run() {\n                        pool.execute(task);\n                    }\n                };\n                TIMER.schedule(timerTask, unit.toMillis(delay));\n            }\n        } else {\n            task.setSchedule(true);\n            TimerTask timerTask = new TimerTask() {\n                @Override\n                public void run() {\n                    pool.execute(task);\n                }\n            };\n            TIMER.scheduleAtFixedRate(timerTask, unit.toMillis(delay), unit.toMillis(period));\n        }\n    }\n\n    private static ExecutorService getPoolByTypeAndPriority(final int type) {\n        return getPoolByTypeAndPriority(type, Thread.NORM_PRIORITY);\n    }\n\n    private static ExecutorService getPoolByTypeAndPriority(final int type, final int priority) {\n        synchronized (TYPE_PRIORITY_POOLS) {\n            ExecutorService pool;\n            Map<Integer, ExecutorService> priorityPools = TYPE_PRIORITY_POOLS.get(type);\n            if (priorityPools == null) {\n                priorityPools = new ConcurrentHashMap<>();\n                pool = ThreadPoolExecutor4Util.createPool(type, priority);\n                priorityPools.put(priority, pool);\n                TYPE_PRIORITY_POOLS.put(type, priorityPools);\n            } else {\n                pool = priorityPools.get(priority);\n                if (pool == null) {\n                    pool = ThreadPoolExecutor4Util.createPool(type, priority);\n                    priorityPools.put(priority, pool);\n                }\n            }\n            return pool;\n        }\n    }\n\n    static final class ThreadPoolExecutor4Util extends ThreadPoolExecutor {\n\n        private static ExecutorService createPool(final int type, final int priority) {\n            switch (type) {\n                case TYPE_SINGLE:\n                    return new ThreadPoolExecutor4Util(1, 1,\n                            0L, TimeUnit.MILLISECONDS,\n                            new LinkedBlockingQueue4Util(),\n                            new UtilsThreadFactory(\"single\", priority)\n                    );\n                case TYPE_CACHED:\n                    return new ThreadPoolExecutor4Util(0, 128,\n                            60L, TimeUnit.SECONDS,\n                            new LinkedBlockingQueue4Util(true),\n                            new UtilsThreadFactory(\"cached\", priority)\n                    );\n                case TYPE_IO:\n                    return new ThreadPoolExecutor4Util(2 * CPU_COUNT + 1, 2 * CPU_COUNT + 1,\n                            30, TimeUnit.SECONDS,\n                            new LinkedBlockingQueue4Util(),\n                            new UtilsThreadFactory(\"io\", priority)\n                    );\n                case TYPE_CPU:\n                    return new ThreadPoolExecutor4Util(CPU_COUNT + 1, 2 * CPU_COUNT + 1,\n                            30, TimeUnit.SECONDS,\n                            new LinkedBlockingQueue4Util(true),\n                            new UtilsThreadFactory(\"cpu\", priority)\n                    );\n                default:\n                    return new ThreadPoolExecutor4Util(type, type,\n                            0L, TimeUnit.MILLISECONDS,\n                            new LinkedBlockingQueue4Util(),\n                            new UtilsThreadFactory(\"fixed(\" + type + \")\", priority)\n                    );\n            }\n        }\n\n        private final AtomicInteger mSubmittedCount = new AtomicInteger();\n\n        private LinkedBlockingQueue4Util mWorkQueue;\n\n        ThreadPoolExecutor4Util(int corePoolSize, int maximumPoolSize,\n                                long keepAliveTime, TimeUnit unit,\n                                LinkedBlockingQueue4Util workQueue,\n                                ThreadFactory threadFactory) {\n            super(corePoolSize, maximumPoolSize,\n                    keepAliveTime, unit,\n                    workQueue,\n                    threadFactory\n            );\n            workQueue.mPool = this;\n            mWorkQueue = workQueue;\n        }\n\n        private int getSubmittedCount() {\n            return mSubmittedCount.get();\n        }\n\n        @Override\n        protected void afterExecute(Runnable r, Throwable t) {\n            mSubmittedCount.decrementAndGet();\n            super.afterExecute(r, t);\n        }\n\n        @Override\n        public void execute(@NonNull Runnable command) {\n            if (this.isShutdown()) return;\n            mSubmittedCount.incrementAndGet();\n            try {\n                super.execute(command);\n            } catch (RejectedExecutionException ignore) {\n                Log.e(\"ThreadUtils\", \"This will not happen!\");\n                mWorkQueue.offer(command);\n            } catch (Throwable t) {\n                mSubmittedCount.decrementAndGet();\n            }\n        }\n    }\n\n    private static final class LinkedBlockingQueue4Util extends LinkedBlockingQueue<Runnable> {\n\n        private volatile ThreadPoolExecutor4Util mPool;\n\n        private int mCapacity = Integer.MAX_VALUE;\n\n        LinkedBlockingQueue4Util() {\n            super();\n        }\n\n        LinkedBlockingQueue4Util(boolean isAddSubThreadFirstThenAddQueue) {\n            super();\n            if (isAddSubThreadFirstThenAddQueue) {\n                mCapacity = 0;\n            }\n        }\n\n        LinkedBlockingQueue4Util(int capacity) {\n            super();\n            mCapacity = capacity;\n        }\n\n        @Override\n        public boolean offer(@NonNull Runnable runnable) {\n            if (mCapacity <= size() &&\n                    mPool != null && mPool.getPoolSize() < mPool.getMaximumPoolSize()) {\n                // create a non-core thread\n                return false;\n            }\n            return super.offer(runnable);\n        }\n    }\n\n    static final class UtilsThreadFactory extends AtomicLong\n            implements ThreadFactory {\n        private static final AtomicInteger POOL_NUMBER      = new AtomicInteger(1);\n        private static final long          serialVersionUID = -9209200509960368598L;\n        private final        String        namePrefix;\n        private final        int           priority;\n        private final        boolean       isDaemon;\n\n        UtilsThreadFactory(String prefix, int priority) {\n            this(prefix, priority, false);\n        }\n\n        UtilsThreadFactory(String prefix, int priority, boolean isDaemon) {\n            namePrefix = prefix + \"-pool-\" +\n                    POOL_NUMBER.getAndIncrement() +\n                    \"-thread-\";\n            this.priority = priority;\n            this.isDaemon = isDaemon;\n        }\n\n        @Override\n        public Thread newThread(@NonNull Runnable r) {\n            Thread t = new Thread(r, namePrefix + getAndIncrement()) {\n                @Override\n                public void run() {\n                    try {\n                        super.run();\n                    } catch (Throwable t) {\n                        Log.e(\"ThreadUtils\", \"Request threw uncaught throwable\", t);\n                    }\n                }\n            };\n            t.setDaemon(isDaemon);\n            t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {\n                @Override\n                public void uncaughtException(Thread t, Throwable e) {\n                    System.out.println(e);\n                }\n            });\n            t.setPriority(priority);\n            return t;\n        }\n    }\n\n    public abstract static class SimpleTask<T> extends Task<T> {\n\n        @Override\n        public void onCancel() {\n            Log.e(\"ThreadUtils\", \"onCancel: \" + Thread.currentThread());\n        }\n\n        @Override\n        public void onFail(Throwable t) {\n            Log.e(\"ThreadUtils\", \"onFail: \", t);\n        }\n\n    }\n\n    public abstract static class Task<T> implements Runnable {\n\n        private static final int NEW         = 0;\n        private static final int RUNNING     = 1;\n        private static final int EXCEPTIONAL = 2;\n        private static final int COMPLETING  = 3;\n        private static final int CANCELLED   = 4;\n        private static final int INTERRUPTED = 5;\n        private static final int TIMEOUT     = 6;\n\n        private final AtomicInteger state = new AtomicInteger(NEW);\n\n        private volatile boolean isSchedule;\n        private volatile Thread  runner;\n\n        private Timer             mTimer;\n        private long              mTimeoutMillis;\n        private OnTimeoutListener mTimeoutListener;\n\n        private Executor deliver;\n\n        public abstract T doInBackground() throws Throwable;\n\n        public abstract void onSuccess(T result);\n\n        public abstract void onCancel();\n\n        public abstract void onFail(Throwable t);\n\n        @Override\n        public void run() {\n            if (isSchedule) {\n                if (runner == null) {\n                    if (!state.compareAndSet(NEW, RUNNING)) return;\n                    runner = Thread.currentThread();\n                    if (mTimeoutListener != null) {\n                        Log.w(\"ThreadUtils\", \"Scheduled task doesn't support timeout.\");\n                    }\n                } else {\n                    if (state.get() != RUNNING) return;\n                }\n            } else {\n                if (!state.compareAndSet(NEW, RUNNING)) return;\n                runner = Thread.currentThread();\n                if (mTimeoutListener != null) {\n                    mTimer = new Timer();\n                    mTimer.schedule(new TimerTask() {\n                        @Override\n                        public void run() {\n                            if (!isDone() && mTimeoutListener != null) {\n                                timeout();\n                                mTimeoutListener.onTimeout();\n                                onDone();\n                            }\n                        }\n                    }, mTimeoutMillis);\n                }\n            }\n            try {\n                final T result = doInBackground();\n                if (isSchedule) {\n                    if (state.get() != RUNNING) return;\n                    getDeliver().execute(new Runnable() {\n                        @Override\n                        public void run() {\n                            onSuccess(result);\n                        }\n                    });\n                } else {\n                    if (!state.compareAndSet(RUNNING, COMPLETING)) return;\n                    getDeliver().execute(new Runnable() {\n                        @Override\n                        public void run() {\n                            onSuccess(result);\n                            onDone();\n                        }\n                    });\n                }\n            } catch (InterruptedException ignore) {\n                state.compareAndSet(CANCELLED, INTERRUPTED);\n            } catch (final Throwable throwable) {\n                if (!state.compareAndSet(RUNNING, EXCEPTIONAL)) return;\n                getDeliver().execute(new Runnable() {\n                    @Override\n                    public void run() {\n                        onFail(throwable);\n                        onDone();\n                    }\n                });\n            }\n        }\n\n        public void cancel() {\n            cancel(true);\n        }\n\n        public void cancel(boolean mayInterruptIfRunning) {\n            synchronized (state) {\n                if (state.get() > RUNNING) return;\n                state.set(CANCELLED);\n            }\n            if (mayInterruptIfRunning) {\n                if (runner != null) {\n                    runner.interrupt();\n                }\n            }\n\n            getDeliver().execute(new Runnable() {\n                @Override\n                public void run() {\n                    onCancel();\n                    onDone();\n                }\n            });\n        }\n\n        private void timeout() {\n            synchronized (state) {\n                if (state.get() > RUNNING) return;\n                state.set(TIMEOUT);\n            }\n            if (runner != null) {\n                runner.interrupt();\n            }\n        }\n\n\n        public boolean isCanceled() {\n            return state.get() >= CANCELLED;\n        }\n\n        public boolean isDone() {\n            return state.get() > RUNNING;\n        }\n\n        public Task<T> setDeliver(Executor deliver) {\n            this.deliver = deliver;\n            return this;\n        }\n\n        /**\n         * Scheduled task doesn't support timeout.\n         */\n        public Task<T> setTimeout(final long timeoutMillis, final OnTimeoutListener listener) {\n            mTimeoutMillis = timeoutMillis;\n            mTimeoutListener = listener;\n            return this;\n        }\n\n        private void setSchedule(boolean isSchedule) {\n            this.isSchedule = isSchedule;\n        }\n\n        private Executor getDeliver() {\n            if (deliver == null) {\n                return getGlobalDeliver();\n            }\n            return deliver;\n        }\n\n        @CallSuper\n        protected void onDone() {\n            TASK_POOL_MAP.remove(this);\n            if (mTimer != null) {\n                mTimer.cancel();\n                mTimer = null;\n                mTimeoutListener = null;\n            }\n        }\n\n        public interface OnTimeoutListener {\n            void onTimeout();\n        }\n    }\n\n    public static class SyncValue<T> {\n\n        private CountDownLatch mLatch = new CountDownLatch(1);\n        private AtomicBoolean  mFlag  = new AtomicBoolean();\n        private T              mValue;\n\n        public void setValue(T value) {\n            if (mFlag.compareAndSet(false, true)) {\n                mValue = value;\n                mLatch.countDown();\n            }\n        }\n\n        public T getValue() {\n            if (!mFlag.get()) {\n                try {\n                    mLatch.await();\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n            }\n            return mValue;\n        }\n\n        public T getValue(long timeout, TimeUnit unit, T defaultValue) {\n            if (!mFlag.get()) {\n                try {\n                    mLatch.await(timeout, unit);\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                    return defaultValue;\n                }\n            }\n            return mValue;\n        }\n    }\n\n    private static Executor getGlobalDeliver() {\n        if (sDeliver == null) {\n            sDeliver = new Executor() {\n                @Override\n                public void execute(@NonNull Runnable command) {\n                    runOnUiThread(command);\n                }\n            };\n        }\n        return sDeliver;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ThrowableUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.StringTokenizer;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/02/12\n *     desc  : utils about exception\n * </pre>\n */\npublic class ThrowableUtils {\n\n    private static final String LINE_SEP = System.getProperty(\"line.separator\");\n\n    private ThrowableUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    public static String getFullStackTrace(Throwable throwable) {\n        final List<Throwable> throwableList = new ArrayList<>();\n        while (throwable != null && !throwableList.contains(throwable)) {\n            throwableList.add(throwable);\n            throwable = throwable.getCause();\n        }\n        final int size = throwableList.size();\n        final List<String> frames = new ArrayList<>();\n        List<String> nextTrace = getStackFrameList(throwableList.get(size - 1));\n        for (int i = size; --i >= 0; ) {\n            final List<String> trace = nextTrace;\n            if (i != 0) {\n                nextTrace = getStackFrameList(throwableList.get(i - 1));\n                removeCommonFrames(trace, nextTrace);\n            }\n            if (i == size - 1) {\n                frames.add(throwableList.get(i).toString());\n            } else {\n                frames.add(\" Caused by: \" + throwableList.get(i).toString());\n            }\n            frames.addAll(trace);\n        }\n        StringBuilder sb = new StringBuilder();\n        for (final String element : frames) {\n            sb.append(element).append(LINE_SEP);\n        }\n        return sb.toString();\n    }\n\n    private static List<String> getStackFrameList(final Throwable throwable) {\n        final StringWriter sw = new StringWriter();\n        final PrintWriter pw = new PrintWriter(sw, true);\n        throwable.printStackTrace(pw);\n        final String stackTrace = sw.toString();\n        final StringTokenizer frames = new StringTokenizer(stackTrace, LINE_SEP);\n        final List<String> list = new ArrayList<>();\n        boolean traceStarted = false;\n        while (frames.hasMoreTokens()) {\n            final String token = frames.nextToken();\n            // Determine if the line starts with <whitespace>at\n            final int at = token.indexOf(\"at\");\n            if (at != -1 && token.substring(0, at).trim().isEmpty()) {\n                traceStarted = true;\n                list.add(token);\n            } else if (traceStarted) {\n                break;\n            }\n        }\n        return list;\n    }\n\n    private static void removeCommonFrames(final List<String> causeFrames, final List<String> wrapperFrames) {\n        int causeFrameIndex = causeFrames.size() - 1;\n        int wrapperFrameIndex = wrapperFrames.size() - 1;\n        while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {\n            // Remove the frame from the cause trace if it is the same\n            // as in the wrapper trace\n            final String causeFrame = causeFrames.get(causeFrameIndex);\n            final String wrapperFrame = wrapperFrames.get(wrapperFrameIndex);\n            if (causeFrame.equals(wrapperFrame)) {\n                causeFrames.remove(causeFrameIndex);\n            }\n            causeFrameIndex--;\n            wrapperFrameIndex--;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/TimeUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.annotation.SuppressLint;\n\nimport androidx.annotation.NonNull;\n\n\nimport com.didichuxing.doraemonkit.constant.TimeConstants;\n\nimport java.text.DateFormat;\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.util.Calendar;\nimport java.util.Date;\nimport java.util.GregorianCalendar;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/02\n *     desc  : utils about time\n * </pre>\n */\npublic final class TimeUtils {\n\n    private static final ThreadLocal<Map<String, SimpleDateFormat>> SDF_THREAD_LOCAL\n            = new ThreadLocal<Map<String, SimpleDateFormat>>() {\n        @Override\n        protected Map<String, SimpleDateFormat> initialValue() {\n            return new HashMap<>();\n        }\n    };\n\n    private static SimpleDateFormat getDefaultFormat() {\n        return getSafeDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n    }\n\n    @SuppressLint(\"SimpleDateFormat\")\n    public static SimpleDateFormat getSafeDateFormat(String pattern) {\n        Map<String, SimpleDateFormat> sdfMap = SDF_THREAD_LOCAL.get();\n        //noinspection ConstantConditions\n        SimpleDateFormat simpleDateFormat = sdfMap.get(pattern);\n        if (simpleDateFormat == null) {\n            simpleDateFormat = new SimpleDateFormat(pattern);\n            sdfMap.put(pattern, simpleDateFormat);\n        }\n        return simpleDateFormat;\n    }\n\n    private TimeUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Milliseconds to the formatted time string.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param millis The milliseconds.\n     * @return the formatted time string\n     */\n    public static String millis2String(final long millis) {\n        return millis2String(millis, getDefaultFormat());\n    }\n\n    /**\n     * Milliseconds to the formatted time string.\n     *\n     * @param millis  The milliseconds.\n     * @param pattern The pattern of date format, such as yyyy/MM/dd HH:mm\n     * @return the formatted time string\n     */\n    public static String millis2String(long millis, @NonNull final String pattern) {\n        return millis2String(millis, getSafeDateFormat(pattern));\n    }\n\n    /**\n     * Milliseconds to the formatted time string.\n     *\n     * @param millis The milliseconds.\n     * @param format The format.\n     * @return the formatted time string\n     */\n    public static String millis2String(final long millis, @NonNull final DateFormat format) {\n        return format.format(new Date(millis));\n    }\n\n    /**\n     * Formatted time string to the milliseconds.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time The formatted time string.\n     * @return the milliseconds\n     */\n    public static long string2Millis(final String time) {\n        return string2Millis(time, getDefaultFormat());\n    }\n\n    /**\n     * Formatted time string to the milliseconds.\n     *\n     * @param time    The formatted time string.\n     * @param pattern The pattern of date format, such as yyyy/MM/dd HH:mm\n     * @return the milliseconds\n     */\n    public static long string2Millis(final String time, @NonNull final String pattern) {\n        return string2Millis(time, getSafeDateFormat(pattern));\n    }\n\n    /**\n     * Formatted time string to the milliseconds.\n     *\n     * @param time   The formatted time string.\n     * @param format The format.\n     * @return the milliseconds\n     */\n    public static long string2Millis(final String time, @NonNull final DateFormat format) {\n        try {\n            return format.parse(time).getTime();\n        } catch (ParseException e) {\n            e.printStackTrace();\n        }\n        return -1;\n    }\n\n    /**\n     * Formatted time string to the date.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time The formatted time string.\n     * @return the date\n     */\n    public static Date string2Date(final String time) {\n        return string2Date(time, getDefaultFormat());\n    }\n\n    /**\n     * Formatted time string to the date.\n     *\n     * @param time    The formatted time string.\n     * @param pattern The pattern of date format, such as yyyy/MM/dd HH:mm\n     * @return the date\n     */\n    public static Date string2Date(final String time, @NonNull final String pattern) {\n        return string2Date(time, getSafeDateFormat(pattern));\n    }\n\n    /**\n     * Formatted time string to the date.\n     *\n     * @param time   The formatted time string.\n     * @param format The format.\n     * @return the date\n     */\n    public static Date string2Date(final String time, @NonNull final DateFormat format) {\n        try {\n            return format.parse(time);\n        } catch (ParseException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    /**\n     * Date to the formatted time string.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param date The date.\n     * @return the formatted time string\n     */\n    public static String date2String(final Date date) {\n        return date2String(date, getDefaultFormat());\n    }\n\n    /**\n     * Date to the formatted time string.\n     *\n     * @param date    The date.\n     * @param pattern The pattern of date format, such as yyyy/MM/dd HH:mm\n     * @return the formatted time string\n     */\n    public static String date2String(final Date date, @NonNull final String pattern) {\n        return getSafeDateFormat(pattern).format(date);\n    }\n\n    /**\n     * Date to the formatted time string.\n     *\n     * @param date   The date.\n     * @param format The format.\n     * @return the formatted time string\n     */\n    public static String date2String(final Date date, @NonNull final DateFormat format) {\n        return format.format(date);\n    }\n\n    /**\n     * Date to the milliseconds.\n     *\n     * @param date The date.\n     * @return the milliseconds\n     */\n    public static long date2Millis(final Date date) {\n        return date.getTime();\n    }\n\n    /**\n     * Milliseconds to the date.\n     *\n     * @param millis The milliseconds.\n     * @return the date\n     */\n    public static Date millis2Date(final long millis) {\n        return new Date(millis);\n    }\n\n    /**\n     * Return the time span, in unit.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time1 The first formatted time string.\n     * @param time2 The second formatted time string.\n     * @param unit  The unit of time span.\n     *              <ul>\n     *              <li>{@link TimeConstants#MSEC}</li>\n     *              <li>{@link TimeConstants#SEC }</li>\n     *              <li>{@link TimeConstants#MIN }</li>\n     *              <li>{@link TimeConstants#HOUR}</li>\n     *              <li>{@link TimeConstants#DAY }</li>\n     *              </ul>\n     * @return the time span, in unit\n     */\n    public static long getTimeSpan(final String time1,\n                                   final String time2,\n                                   @TimeConstants.Unit final int unit) {\n        return getTimeSpan(time1, time2, getDefaultFormat(), unit);\n    }\n\n    /**\n     * Return the time span, in unit.\n     *\n     * @param time1  The first formatted time string.\n     * @param time2  The second formatted time string.\n     * @param format The format.\n     * @param unit   The unit of time span.\n     *               <ul>\n     *               <li>{@link TimeConstants#MSEC}</li>\n     *               <li>{@link TimeConstants#SEC }</li>\n     *               <li>{@link TimeConstants#MIN }</li>\n     *               <li>{@link TimeConstants#HOUR}</li>\n     *               <li>{@link TimeConstants#DAY }</li>\n     *               </ul>\n     * @return the time span, in unit\n     */\n    public static long getTimeSpan(final String time1,\n                                   final String time2,\n                                   @NonNull final DateFormat format,\n                                   @TimeConstants.Unit final int unit) {\n        return millis2TimeSpan(string2Millis(time1, format) - string2Millis(time2, format), unit);\n    }\n\n    /**\n     * Return the time span, in unit.\n     *\n     * @param date1 The first date.\n     * @param date2 The second date.\n     * @param unit  The unit of time span.\n     *              <ul>\n     *              <li>{@link TimeConstants#MSEC}</li>\n     *              <li>{@link TimeConstants#SEC }</li>\n     *              <li>{@link TimeConstants#MIN }</li>\n     *              <li>{@link TimeConstants#HOUR}</li>\n     *              <li>{@link TimeConstants#DAY }</li>\n     *              </ul>\n     * @return the time span, in unit\n     */\n    public static long getTimeSpan(final Date date1,\n                                   final Date date2,\n                                   @TimeConstants.Unit final int unit) {\n        return millis2TimeSpan(date2Millis(date1) - date2Millis(date2), unit);\n    }\n\n    /**\n     * Return the time span, in unit.\n     *\n     * @param millis1 The first milliseconds.\n     * @param millis2 The second milliseconds.\n     * @param unit    The unit of time span.\n     *                <ul>\n     *                <li>{@link TimeConstants#MSEC}</li>\n     *                <li>{@link TimeConstants#SEC }</li>\n     *                <li>{@link TimeConstants#MIN }</li>\n     *                <li>{@link TimeConstants#HOUR}</li>\n     *                <li>{@link TimeConstants#DAY }</li>\n     *                </ul>\n     * @return the time span, in unit\n     */\n    public static long getTimeSpan(final long millis1,\n                                   final long millis2,\n                                   @TimeConstants.Unit final int unit) {\n        return millis2TimeSpan(millis1 - millis2, unit);\n    }\n\n    /**\n     * Return the fit time span.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time1     The first formatted time string.\n     * @param time2     The second formatted time string.\n     * @param precision The precision of time span.\n     *                  <ul>\n     *                  <li>precision = 0, return null</li>\n     *                  <li>precision = 1, return 天</li>\n     *                  <li>precision = 2, return 天, 小时</li>\n     *                  <li>precision = 3, return 天, 小时, 分钟</li>\n     *                  <li>precision = 4, return 天, 小时, 分钟, 秒</li>\n     *                  <li>precision &gt;= 5，return 天, 小时, 分钟, 秒, 毫秒</li>\n     *                  </ul>\n     * @return the fit time span\n     */\n    public static String getFitTimeSpan(final String time1,\n                                        final String time2,\n                                        final int precision) {\n        long delta = string2Millis(time1, getDefaultFormat()) - string2Millis(time2, getDefaultFormat());\n        return millis2FitTimeSpan(delta, precision);\n    }\n\n    /**\n     * Return the fit time span.\n     *\n     * @param time1     The first formatted time string.\n     * @param time2     The second formatted time string.\n     * @param format    The format.\n     * @param precision The precision of time span.\n     *                  <ul>\n     *                  <li>precision = 0, return null</li>\n     *                  <li>precision = 1, return 天</li>\n     *                  <li>precision = 2, return 天, 小时</li>\n     *                  <li>precision = 3, return 天, 小时, 分钟</li>\n     *                  <li>precision = 4, return 天, 小时, 分钟, 秒</li>\n     *                  <li>precision &gt;= 5，return 天, 小时, 分钟, 秒, 毫秒</li>\n     *                  </ul>\n     * @return the fit time span\n     */\n    public static String getFitTimeSpan(final String time1,\n                                        final String time2,\n                                        @NonNull final DateFormat format,\n                                        final int precision) {\n        long delta = string2Millis(time1, format) - string2Millis(time2, format);\n        return millis2FitTimeSpan(delta, precision);\n    }\n\n    /**\n     * Return the fit time span.\n     *\n     * @param date1     The first date.\n     * @param date2     The second date.\n     * @param precision The precision of time span.\n     *                  <ul>\n     *                  <li>precision = 0, return null</li>\n     *                  <li>precision = 1, return 天</li>\n     *                  <li>precision = 2, return 天, 小时</li>\n     *                  <li>precision = 3, return 天, 小时, 分钟</li>\n     *                  <li>precision = 4, return 天, 小时, 分钟, 秒</li>\n     *                  <li>precision &gt;= 5，return 天, 小时, 分钟, 秒, 毫秒</li>\n     *                  </ul>\n     * @return the fit time span\n     */\n    public static String getFitTimeSpan(final Date date1, final Date date2, final int precision) {\n        return millis2FitTimeSpan(date2Millis(date1) - date2Millis(date2), precision);\n    }\n\n    /**\n     * Return the fit time span.\n     *\n     * @param millis1   The first milliseconds.\n     * @param millis2   The second milliseconds.\n     * @param precision The precision of time span.\n     *                  <ul>\n     *                  <li>precision = 0, return null</li>\n     *                  <li>precision = 1, return 天</li>\n     *                  <li>precision = 2, return 天, 小时</li>\n     *                  <li>precision = 3, return 天, 小时, 分钟</li>\n     *                  <li>precision = 4, return 天, 小时, 分钟, 秒</li>\n     *                  <li>precision &gt;= 5，return 天, 小时, 分钟, 秒, 毫秒</li>\n     *                  </ul>\n     * @return the fit time span\n     */\n    public static String getFitTimeSpan(final long millis1,\n                                        final long millis2,\n                                        final int precision) {\n        return millis2FitTimeSpan(millis1 - millis2, precision);\n    }\n\n    /**\n     * Return the current time in milliseconds.\n     *\n     * @return the current time in milliseconds\n     */\n    public static long getNowMills() {\n        return System.currentTimeMillis();\n    }\n\n    /**\n     * Return the current formatted time string.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @return the current formatted time string\n     */\n    public static String getNowString() {\n        return millis2String(System.currentTimeMillis(), getDefaultFormat());\n    }\n\n    /**\n     * Return the current formatted time string.\n     *\n     * @param format The format.\n     * @return the current formatted time string\n     */\n    public static String getNowString(@NonNull final DateFormat format) {\n        return millis2String(System.currentTimeMillis(), format);\n    }\n\n    /**\n     * Return the current date.\n     *\n     * @return the current date\n     */\n    public static Date getNowDate() {\n        return new Date();\n    }\n\n    /**\n     * Return the time span by now, in unit.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time The formatted time string.\n     * @param unit The unit of time span.\n     *             <ul>\n     *             <li>{@link TimeConstants#MSEC}</li>\n     *             <li>{@link TimeConstants#SEC }</li>\n     *             <li>{@link TimeConstants#MIN }</li>\n     *             <li>{@link TimeConstants#HOUR}</li>\n     *             <li>{@link TimeConstants#DAY }</li>\n     *             </ul>\n     * @return the time span by now, in unit\n     */\n    public static long getTimeSpanByNow(final String time, @TimeConstants.Unit final int unit) {\n        return getTimeSpan(time, getNowString(), getDefaultFormat(), unit);\n    }\n\n    /**\n     * Return the time span by now, in unit.\n     *\n     * @param time   The formatted time string.\n     * @param format The format.\n     * @param unit   The unit of time span.\n     *               <ul>\n     *               <li>{@link TimeConstants#MSEC}</li>\n     *               <li>{@link TimeConstants#SEC }</li>\n     *               <li>{@link TimeConstants#MIN }</li>\n     *               <li>{@link TimeConstants#HOUR}</li>\n     *               <li>{@link TimeConstants#DAY }</li>\n     *               </ul>\n     * @return the time span by now, in unit\n     */\n    public static long getTimeSpanByNow(final String time,\n                                        @NonNull final DateFormat format,\n                                        @TimeConstants.Unit final int unit) {\n        return getTimeSpan(time, getNowString(format), format, unit);\n    }\n\n    /**\n     * Return the time span by now, in unit.\n     *\n     * @param date The date.\n     * @param unit The unit of time span.\n     *             <ul>\n     *             <li>{@link TimeConstants#MSEC}</li>\n     *             <li>{@link TimeConstants#SEC }</li>\n     *             <li>{@link TimeConstants#MIN }</li>\n     *             <li>{@link TimeConstants#HOUR}</li>\n     *             <li>{@link TimeConstants#DAY }</li>\n     *             </ul>\n     * @return the time span by now, in unit\n     */\n    public static long getTimeSpanByNow(final Date date, @TimeConstants.Unit final int unit) {\n        return getTimeSpan(date, new Date(), unit);\n    }\n\n    /**\n     * Return the time span by now, in unit.\n     *\n     * @param millis The milliseconds.\n     * @param unit   The unit of time span.\n     *               <ul>\n     *               <li>{@link TimeConstants#MSEC}</li>\n     *               <li>{@link TimeConstants#SEC }</li>\n     *               <li>{@link TimeConstants#MIN }</li>\n     *               <li>{@link TimeConstants#HOUR}</li>\n     *               <li>{@link TimeConstants#DAY }</li>\n     *               </ul>\n     * @return the time span by now, in unit\n     */\n    public static long getTimeSpanByNow(final long millis, @TimeConstants.Unit final int unit) {\n        return getTimeSpan(millis, System.currentTimeMillis(), unit);\n    }\n\n    /**\n     * Return the fit time span by now.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time      The formatted time string.\n     * @param precision The precision of time span.\n     *                  <ul>\n     *                  <li>precision = 0，返回 null</li>\n     *                  <li>precision = 1，返回天</li>\n     *                  <li>precision = 2，返回天和小时</li>\n     *                  <li>precision = 3，返回天、小时和分钟</li>\n     *                  <li>precision = 4，返回天、小时、分钟和秒</li>\n     *                  <li>precision &gt;= 5，返回天、小时、分钟、秒和毫秒</li>\n     *                  </ul>\n     * @return the fit time span by now\n     */\n    public static String getFitTimeSpanByNow(final String time, final int precision) {\n        return getFitTimeSpan(time, getNowString(), getDefaultFormat(), precision);\n    }\n\n    /**\n     * Return the fit time span by now.\n     *\n     * @param time      The formatted time string.\n     * @param format    The format.\n     * @param precision The precision of time span.\n     *                  <ul>\n     *                  <li>precision = 0，返回 null</li>\n     *                  <li>precision = 1，返回天</li>\n     *                  <li>precision = 2，返回天和小时</li>\n     *                  <li>precision = 3，返回天、小时和分钟</li>\n     *                  <li>precision = 4，返回天、小时、分钟和秒</li>\n     *                  <li>precision &gt;= 5，返回天、小时、分钟、秒和毫秒</li>\n     *                  </ul>\n     * @return the fit time span by now\n     */\n    public static String getFitTimeSpanByNow(final String time,\n                                             @NonNull final DateFormat format,\n                                             final int precision) {\n        return getFitTimeSpan(time, getNowString(format), format, precision);\n    }\n\n    /**\n     * Return the fit time span by now.\n     *\n     * @param date      The date.\n     * @param precision The precision of time span.\n     *                  <ul>\n     *                  <li>precision = 0，返回 null</li>\n     *                  <li>precision = 1，返回天</li>\n     *                  <li>precision = 2，返回天和小时</li>\n     *                  <li>precision = 3，返回天、小时和分钟</li>\n     *                  <li>precision = 4，返回天、小时、分钟和秒</li>\n     *                  <li>precision &gt;= 5，返回天、小时、分钟、秒和毫秒</li>\n     *                  </ul>\n     * @return the fit time span by now\n     */\n    public static String getFitTimeSpanByNow(final Date date, final int precision) {\n        return getFitTimeSpan(date, getNowDate(), precision);\n    }\n\n    /**\n     * Return the fit time span by now.\n     *\n     * @param millis    The milliseconds.\n     * @param precision The precision of time span.\n     *                  <ul>\n     *                  <li>precision = 0，返回 null</li>\n     *                  <li>precision = 1，返回天</li>\n     *                  <li>precision = 2，返回天和小时</li>\n     *                  <li>precision = 3，返回天、小时和分钟</li>\n     *                  <li>precision = 4，返回天、小时、分钟和秒</li>\n     *                  <li>precision &gt;= 5，返回天、小时、分钟、秒和毫秒</li>\n     *                  </ul>\n     * @return the fit time span by now\n     */\n    public static String getFitTimeSpanByNow(final long millis, final int precision) {\n        return getFitTimeSpan(millis, System.currentTimeMillis(), precision);\n    }\n\n    /**\n     * Return the friendly time span by now.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time The formatted time string.\n     * @return the friendly time span by now\n     * <ul>\n     * <li>如果小于 1 秒钟内，显示刚刚</li>\n     * <li>如果在 1 分钟内，显示 XXX秒前</li>\n     * <li>如果在 1 小时内，显示 XXX分钟前</li>\n     * <li>如果在 1 小时外的今天内，显示今天15:32</li>\n     * <li>如果是昨天的，显示昨天15:32</li>\n     * <li>其余显示，2016-10-15</li>\n     * <li>时间不合法的情况全部日期和时间信息，如星期六 十月 27 14:21:20 CST 2007</li>\n     * </ul>\n     */\n    public static String getFriendlyTimeSpanByNow(final String time) {\n        return getFriendlyTimeSpanByNow(time, getDefaultFormat());\n    }\n\n    /**\n     * Return the friendly time span by now.\n     *\n     * @param time   The formatted time string.\n     * @param format The format.\n     * @return the friendly time span by now\n     * <ul>\n     * <li>如果小于 1 秒钟内，显示刚刚</li>\n     * <li>如果在 1 分钟内，显示 XXX秒前</li>\n     * <li>如果在 1 小时内，显示 XXX分钟前</li>\n     * <li>如果在 1 小时外的今天内，显示今天15:32</li>\n     * <li>如果是昨天的，显示昨天15:32</li>\n     * <li>其余显示，2016-10-15</li>\n     * <li>时间不合法的情况全部日期和时间信息，如星期六 十月 27 14:21:20 CST 2007</li>\n     * </ul>\n     */\n    public static String getFriendlyTimeSpanByNow(final String time,\n                                                  @NonNull final DateFormat format) {\n        return getFriendlyTimeSpanByNow(string2Millis(time, format));\n    }\n\n    /**\n     * Return the friendly time span by now.\n     *\n     * @param date The date.\n     * @return the friendly time span by now\n     * <ul>\n     * <li>如果小于 1 秒钟内，显示刚刚</li>\n     * <li>如果在 1 分钟内，显示 XXX秒前</li>\n     * <li>如果在 1 小时内，显示 XXX分钟前</li>\n     * <li>如果在 1 小时外的今天内，显示今天15:32</li>\n     * <li>如果是昨天的，显示昨天15:32</li>\n     * <li>其余显示，2016-10-15</li>\n     * <li>时间不合法的情况全部日期和时间信息，如星期六 十月 27 14:21:20 CST 2007</li>\n     * </ul>\n     */\n    public static String getFriendlyTimeSpanByNow(final Date date) {\n        return getFriendlyTimeSpanByNow(date.getTime());\n    }\n\n    /**\n     * Return the friendly time span by now.\n     *\n     * @param millis The milliseconds.\n     * @return the friendly time span by now\n     * <ul>\n     * <li>如果小于 1 秒钟内，显示刚刚</li>\n     * <li>如果在 1 分钟内，显示 XXX秒前</li>\n     * <li>如果在 1 小时内，显示 XXX分钟前</li>\n     * <li>如果在 1 小时外的今天内，显示今天15:32</li>\n     * <li>如果是昨天的，显示昨天15:32</li>\n     * <li>其余显示，2016-10-15</li>\n     * <li>时间不合法的情况全部日期和时间信息，如星期六 十月 27 14:21:20 CST 2007</li>\n     * </ul>\n     */\n    public static String getFriendlyTimeSpanByNow(final long millis) {\n        long now = System.currentTimeMillis();\n        long span = now - millis;\n        if (span < 0)\n            // U can read http://www.apihome.cn/api/java/Formatter.html to understand it.\n            return String.format(\"%tc\", millis);\n        if (span < 1000) {\n            return \"刚刚\";\n        } else if (span < TimeConstants.MIN) {\n            return String.format(Locale.getDefault(), \"%d秒前\", span / TimeConstants.SEC);\n        } else if (span < TimeConstants.HOUR) {\n            return String.format(Locale.getDefault(), \"%d分钟前\", span / TimeConstants.MIN);\n        }\n        // 获取当天 00:00\n        long wee = getWeeOfToday();\n        if (millis >= wee) {\n            return String.format(\"今天%tR\", millis);\n        } else if (millis >= wee - TimeConstants.DAY) {\n            return String.format(\"昨天%tR\", millis);\n        } else {\n            return String.format(\"%tF\", millis);\n        }\n    }\n\n    private static long getWeeOfToday() {\n        Calendar cal = Calendar.getInstance();\n        cal.set(Calendar.HOUR_OF_DAY, 0);\n        cal.set(Calendar.SECOND, 0);\n        cal.set(Calendar.MINUTE, 0);\n        cal.set(Calendar.MILLISECOND, 0);\n        return cal.getTimeInMillis();\n    }\n\n    /**\n     * Return the milliseconds differ time span.\n     *\n     * @param millis   The milliseconds.\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the milliseconds differ time span\n     */\n    public static long getMillis(final long millis,\n                                 final long timeSpan,\n                                 @TimeConstants.Unit final int unit) {\n        return millis + timeSpan2Millis(timeSpan, unit);\n    }\n\n    /**\n     * Return the milliseconds differ time span.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time     The formatted time string.\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the milliseconds differ time span\n     */\n    public static long getMillis(final String time,\n                                 final long timeSpan,\n                                 @TimeConstants.Unit final int unit) {\n        return getMillis(time, getDefaultFormat(), timeSpan, unit);\n    }\n\n    /**\n     * Return the milliseconds differ time span.\n     *\n     * @param time     The formatted time string.\n     * @param format   The format.\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the milliseconds differ time span.\n     */\n    public static long getMillis(final String time,\n                                 @NonNull final DateFormat format,\n                                 final long timeSpan,\n                                 @TimeConstants.Unit final int unit) {\n        return string2Millis(time, format) + timeSpan2Millis(timeSpan, unit);\n    }\n\n    /**\n     * Return the milliseconds differ time span.\n     *\n     * @param date     The date.\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the milliseconds differ time span.\n     */\n    public static long getMillis(final Date date,\n                                 final long timeSpan,\n                                 @TimeConstants.Unit final int unit) {\n        return date2Millis(date) + timeSpan2Millis(timeSpan, unit);\n    }\n\n    /**\n     * Return the formatted time string differ time span.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param millis   The milliseconds.\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the formatted time string differ time span\n     */\n    public static String getString(final long millis,\n                                   final long timeSpan,\n                                   @TimeConstants.Unit final int unit) {\n        return getString(millis, getDefaultFormat(), timeSpan, unit);\n    }\n\n    /**\n     * Return the formatted time string differ time span.\n     *\n     * @param millis   The milliseconds.\n     * @param format   The format.\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the formatted time string differ time span\n     */\n    public static String getString(final long millis,\n                                   @NonNull final DateFormat format,\n                                   final long timeSpan,\n                                   @TimeConstants.Unit final int unit) {\n        return millis2String(millis + timeSpan2Millis(timeSpan, unit), format);\n    }\n\n    /**\n     * Return the formatted time string differ time span.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time     The formatted time string.\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the formatted time string differ time span\n     */\n    public static String getString(final String time,\n                                   final long timeSpan,\n                                   @TimeConstants.Unit final int unit) {\n        return getString(time, getDefaultFormat(), timeSpan, unit);\n    }\n\n    /**\n     * Return the formatted time string differ time span.\n     *\n     * @param time     The formatted time string.\n     * @param format   The format.\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the formatted time string differ time span\n     */\n    public static String getString(final String time,\n                                   @NonNull final DateFormat format,\n                                   final long timeSpan,\n                                   @TimeConstants.Unit final int unit) {\n        return millis2String(string2Millis(time, format) + timeSpan2Millis(timeSpan, unit), format);\n    }\n\n    /**\n     * Return the formatted time string differ time span.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param date     The date.\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the formatted time string differ time span\n     */\n    public static String getString(final Date date,\n                                   final long timeSpan,\n                                   @TimeConstants.Unit final int unit) {\n        return getString(date, getDefaultFormat(), timeSpan, unit);\n    }\n\n    /**\n     * Return the formatted time string differ time span.\n     *\n     * @param date     The date.\n     * @param format   The format.\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the formatted time string differ time span\n     */\n    public static String getString(final Date date,\n                                   @NonNull final DateFormat format,\n                                   final long timeSpan,\n                                   @TimeConstants.Unit final int unit) {\n        return millis2String(date2Millis(date) + timeSpan2Millis(timeSpan, unit), format);\n    }\n\n    /**\n     * Return the date differ time span.\n     *\n     * @param millis   The milliseconds.\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the date differ time span\n     */\n    public static Date getDate(final long millis,\n                               final long timeSpan,\n                               @TimeConstants.Unit final int unit) {\n        return millis2Date(millis + timeSpan2Millis(timeSpan, unit));\n    }\n\n    /**\n     * Return the date differ time span.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time     The formatted time string.\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the date differ time span\n     */\n    public static Date getDate(final String time,\n                               final long timeSpan,\n                               @TimeConstants.Unit final int unit) {\n        return getDate(time, getDefaultFormat(), timeSpan, unit);\n    }\n\n    /**\n     * Return the date differ time span.\n     *\n     * @param time     The formatted time string.\n     * @param format   The format.\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the date differ time span\n     */\n    public static Date getDate(final String time,\n                               @NonNull final DateFormat format,\n                               final long timeSpan,\n                               @TimeConstants.Unit final int unit) {\n        return millis2Date(string2Millis(time, format) + timeSpan2Millis(timeSpan, unit));\n    }\n\n    /**\n     * Return the date differ time span.\n     *\n     * @param date     The date.\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the date differ time span\n     */\n    public static Date getDate(final Date date,\n                               final long timeSpan,\n                               @TimeConstants.Unit final int unit) {\n        return millis2Date(date2Millis(date) + timeSpan2Millis(timeSpan, unit));\n    }\n\n    /**\n     * Return the milliseconds differ time span by now.\n     *\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the milliseconds differ time span by now\n     */\n    public static long getMillisByNow(final long timeSpan, @TimeConstants.Unit final int unit) {\n        return getMillis(getNowMills(), timeSpan, unit);\n    }\n\n    /**\n     * Return the formatted time string differ time span by now.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the formatted time string differ time span by now\n     */\n    public static String getStringByNow(final long timeSpan, @TimeConstants.Unit final int unit) {\n        return getStringByNow(timeSpan, getDefaultFormat(), unit);\n    }\n\n    /**\n     * Return the formatted time string differ time span by now.\n     *\n     * @param timeSpan The time span.\n     * @param format   The format.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the formatted time string differ time span by now\n     */\n    public static String getStringByNow(final long timeSpan,\n                                        @NonNull final DateFormat format,\n                                        @TimeConstants.Unit final int unit) {\n        return getString(getNowMills(), format, timeSpan, unit);\n    }\n\n    /**\n     * Return the date differ time span by now.\n     *\n     * @param timeSpan The time span.\n     * @param unit     The unit of time span.\n     *                 <ul>\n     *                 <li>{@link TimeConstants#MSEC}</li>\n     *                 <li>{@link TimeConstants#SEC }</li>\n     *                 <li>{@link TimeConstants#MIN }</li>\n     *                 <li>{@link TimeConstants#HOUR}</li>\n     *                 <li>{@link TimeConstants#DAY }</li>\n     *                 </ul>\n     * @return the date differ time span by now\n     */\n    public static Date getDateByNow(final long timeSpan, @TimeConstants.Unit final int unit) {\n        return getDate(getNowMills(), timeSpan, unit);\n    }\n\n    /**\n     * Return whether it is today.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time The formatted time string.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isToday(final String time) {\n        return isToday(string2Millis(time, getDefaultFormat()));\n    }\n\n    /**\n     * Return whether it is today.\n     *\n     * @param time   The formatted time string.\n     * @param format The format.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isToday(final String time, @NonNull final DateFormat format) {\n        return isToday(string2Millis(time, format));\n    }\n\n    /**\n     * Return whether it is today.\n     *\n     * @param date The date.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isToday(final Date date) {\n        return isToday(date.getTime());\n    }\n\n    /**\n     * Return whether it is today.\n     *\n     * @param millis The milliseconds.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isToday(final long millis) {\n        long wee = getWeeOfToday();\n        return millis >= wee && millis < wee + TimeConstants.DAY;\n    }\n\n    /**\n     * Return whether it is leap year.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time The formatted time string.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isLeapYear(final String time) {\n        return isLeapYear(string2Date(time, getDefaultFormat()));\n    }\n\n    /**\n     * Return whether it is leap year.\n     *\n     * @param time   The formatted time string.\n     * @param format The format.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isLeapYear(final String time, @NonNull final DateFormat format) {\n        return isLeapYear(string2Date(time, format));\n    }\n\n    /**\n     * Return whether it is leap year.\n     *\n     * @param date The date.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isLeapYear(final Date date) {\n        Calendar cal = Calendar.getInstance();\n        cal.setTime(date);\n        int year = cal.get(Calendar.YEAR);\n        return isLeapYear(year);\n    }\n\n    /**\n     * Return whether it is leap year.\n     *\n     * @param millis The milliseconds.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isLeapYear(final long millis) {\n        return isLeapYear(millis2Date(millis));\n    }\n\n    /**\n     * Return whether it is leap year.\n     *\n     * @param year The year.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isLeapYear(final int year) {\n        return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;\n    }\n\n    /**\n     * Return the day of week in Chinese.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time The formatted time string.\n     * @return the day of week in Chinese\n     */\n    public static String getChineseWeek(final String time) {\n        return getChineseWeek(string2Date(time, getDefaultFormat()));\n    }\n\n    /**\n     * Return the day of week in Chinese.\n     *\n     * @param time   The formatted time string.\n     * @param format The format.\n     * @return the day of week in Chinese\n     */\n    public static String getChineseWeek(final String time, @NonNull final DateFormat format) {\n        return getChineseWeek(string2Date(time, format));\n    }\n\n    /**\n     * Return the day of week in Chinese.\n     *\n     * @param date The date.\n     * @return the day of week in Chinese\n     */\n    public static String getChineseWeek(final Date date) {\n        return new SimpleDateFormat(\"E\", Locale.CHINA).format(date);\n    }\n\n    /**\n     * Return the day of week in Chinese.\n     *\n     * @param millis The milliseconds.\n     * @return the day of week in Chinese\n     */\n    public static String getChineseWeek(final long millis) {\n        return getChineseWeek(new Date(millis));\n    }\n\n    /**\n     * Return the day of week in US.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time The formatted time string.\n     * @return the day of week in US\n     */\n    public static String getUSWeek(final String time) {\n        return getUSWeek(string2Date(time, getDefaultFormat()));\n    }\n\n    /**\n     * Return the day of week in US.\n     *\n     * @param time   The formatted time string.\n     * @param format The format.\n     * @return the day of week in US\n     */\n    public static String getUSWeek(final String time, @NonNull final DateFormat format) {\n        return getUSWeek(string2Date(time, format));\n    }\n\n    /**\n     * Return the day of week in US.\n     *\n     * @param date The date.\n     * @return the day of week in US\n     */\n    public static String getUSWeek(final Date date) {\n        return new SimpleDateFormat(\"EEEE\", Locale.US).format(date);\n    }\n\n    /**\n     * Return the day of week in US.\n     *\n     * @param millis The milliseconds.\n     * @return the day of week in US\n     */\n    public static String getUSWeek(final long millis) {\n        return getUSWeek(new Date(millis));\n    }\n\n    /**\n     * Return whether it is am.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAm() {\n        Calendar cal = Calendar.getInstance();\n        return cal.get(GregorianCalendar.AM_PM) == 0;\n    }\n\n    /**\n     * Return whether it is am.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time The formatted time string.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAm(final String time) {\n        return getValueByCalendarField(time, getDefaultFormat(), GregorianCalendar.AM_PM) == 0;\n    }\n\n    /**\n     * Return whether it is am.\n     *\n     * @param time   The formatted time string.\n     * @param format The format.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAm(final String time,\n                               @NonNull final DateFormat format) {\n        return getValueByCalendarField(time, format, GregorianCalendar.AM_PM) == 0;\n    }\n\n    /**\n     * Return whether it is am.\n     *\n     * @param date The date.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAm(final Date date) {\n        return getValueByCalendarField(date, GregorianCalendar.AM_PM) == 0;\n    }\n\n    /**\n     * Return whether it is am.\n     *\n     * @param millis The milliseconds.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isAm(final long millis) {\n        return getValueByCalendarField(millis, GregorianCalendar.AM_PM) == 0;\n    }\n\n    /**\n     * Return whether it is am.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isPm() {\n        return !isAm();\n    }\n\n    /**\n     * Return whether it is am.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time The formatted time string.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isPm(final String time) {\n        return !isAm(time);\n    }\n\n    /**\n     * Return whether it is am.\n     *\n     * @param time   The formatted time string.\n     * @param format The format.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isPm(final String time,\n                               @NonNull final DateFormat format) {\n        return !isAm(time, format);\n    }\n\n    /**\n     * Return whether it is am.\n     *\n     * @param date The date.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isPm(final Date date) {\n        return !isAm(date);\n    }\n\n    /**\n     * Return whether it is am.\n     *\n     * @param millis The milliseconds.\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isPm(final long millis) {\n        return !isAm(millis);\n    }\n\n    /**\n     * Returns the value of the given calendar field.\n     *\n     * @param field The given calendar field.\n     *              <ul>\n     *              <li>{@link Calendar#ERA}</li>\n     *              <li>{@link Calendar#YEAR}</li>\n     *              <li>{@link Calendar#MONTH}</li>\n     *              <li>...</li>\n     *              <li>{@link Calendar#DST_OFFSET}</li>\n     *              </ul>\n     * @return the value of the given calendar field\n     */\n    public static int getValueByCalendarField(final int field) {\n        Calendar cal = Calendar.getInstance();\n        return cal.get(field);\n    }\n\n    /**\n     * Returns the value of the given calendar field.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time  The formatted time string.\n     * @param field The given calendar field.\n     *              <ul>\n     *              <li>{@link Calendar#ERA}</li>\n     *              <li>{@link Calendar#YEAR}</li>\n     *              <li>{@link Calendar#MONTH}</li>\n     *              <li>...</li>\n     *              <li>{@link Calendar#DST_OFFSET}</li>\n     *              </ul>\n     * @return the value of the given calendar field\n     */\n    public static int getValueByCalendarField(final String time, final int field) {\n        return getValueByCalendarField(string2Date(time, getDefaultFormat()), field);\n    }\n\n    /**\n     * Returns the value of the given calendar field.\n     *\n     * @param time   The formatted time string.\n     * @param format The format.\n     * @param field  The given calendar field.\n     *               <ul>\n     *               <li>{@link Calendar#ERA}</li>\n     *               <li>{@link Calendar#YEAR}</li>\n     *               <li>{@link Calendar#MONTH}</li>\n     *               <li>...</li>\n     *               <li>{@link Calendar#DST_OFFSET}</li>\n     *               </ul>\n     * @return the value of the given calendar field\n     */\n    public static int getValueByCalendarField(final String time,\n                                              @NonNull final DateFormat format,\n                                              final int field) {\n        return getValueByCalendarField(string2Date(time, format), field);\n    }\n\n    /**\n     * Returns the value of the given calendar field.\n     *\n     * @param date  The date.\n     * @param field The given calendar field.\n     *              <ul>\n     *              <li>{@link Calendar#ERA}</li>\n     *              <li>{@link Calendar#YEAR}</li>\n     *              <li>{@link Calendar#MONTH}</li>\n     *              <li>...</li>\n     *              <li>{@link Calendar#DST_OFFSET}</li>\n     *              </ul>\n     * @return the value of the given calendar field\n     */\n    public static int getValueByCalendarField(final Date date, final int field) {\n        Calendar cal = Calendar.getInstance();\n        cal.setTime(date);\n        return cal.get(field);\n    }\n\n    /**\n     * Returns the value of the given calendar field.\n     *\n     * @param millis The milliseconds.\n     * @param field  The given calendar field.\n     *               <ul>\n     *               <li>{@link Calendar#ERA}</li>\n     *               <li>{@link Calendar#YEAR}</li>\n     *               <li>{@link Calendar#MONTH}</li>\n     *               <li>...</li>\n     *               <li>{@link Calendar#DST_OFFSET}</li>\n     *               </ul>\n     * @return the value of the given calendar field\n     */\n    public static int getValueByCalendarField(final long millis, final int field) {\n        Calendar cal = Calendar.getInstance();\n        cal.setTimeInMillis(millis);\n        return cal.get(field);\n    }\n\n    private static final String[] CHINESE_ZODIAC =\n            {\"猴\", \"鸡\", \"狗\", \"猪\", \"鼠\", \"牛\", \"虎\", \"兔\", \"龙\", \"蛇\", \"马\", \"羊\"};\n\n    /**\n     * Return the Chinese zodiac.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time The formatted time string.\n     * @return the Chinese zodiac\n     */\n    public static String getChineseZodiac(final String time) {\n        return getChineseZodiac(string2Date(time, getDefaultFormat()));\n    }\n\n    /**\n     * Return the Chinese zodiac.\n     *\n     * @param time   The formatted time string.\n     * @param format The format.\n     * @return the Chinese zodiac\n     */\n    public static String getChineseZodiac(final String time, @NonNull final DateFormat format) {\n        return getChineseZodiac(string2Date(time, format));\n    }\n\n    /**\n     * Return the Chinese zodiac.\n     *\n     * @param date The date.\n     * @return the Chinese zodiac\n     */\n    public static String getChineseZodiac(final Date date) {\n        Calendar cal = Calendar.getInstance();\n        cal.setTime(date);\n        return CHINESE_ZODIAC[cal.get(Calendar.YEAR) % 12];\n    }\n\n    /**\n     * Return the Chinese zodiac.\n     *\n     * @param millis The milliseconds.\n     * @return the Chinese zodiac\n     */\n    public static String getChineseZodiac(final long millis) {\n        return getChineseZodiac(millis2Date(millis));\n    }\n\n    /**\n     * Return the Chinese zodiac.\n     *\n     * @param year The year.\n     * @return the Chinese zodiac\n     */\n    public static String getChineseZodiac(final int year) {\n        return CHINESE_ZODIAC[year % 12];\n    }\n\n    private static final int[]    ZODIAC_FLAGS = {20, 19, 21, 21, 21, 22, 23, 23, 23, 24, 23, 22};\n    private static final String[] ZODIAC       = {\n            \"水瓶座\", \"双鱼座\", \"白羊座\", \"金牛座\", \"双子座\", \"巨蟹座\",\n            \"狮子座\", \"处女座\", \"天秤座\", \"天蝎座\", \"射手座\", \"摩羯座\"\n    };\n\n    /**\n     * Return the zodiac.\n     * <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>\n     *\n     * @param time The formatted time string.\n     * @return the zodiac\n     */\n    public static String getZodiac(final String time) {\n        return getZodiac(string2Date(time, getDefaultFormat()));\n    }\n\n    /**\n     * Return the zodiac.\n     *\n     * @param time   The formatted time string.\n     * @param format The format.\n     * @return the zodiac\n     */\n    public static String getZodiac(final String time, @NonNull final DateFormat format) {\n        return getZodiac(string2Date(time, format));\n    }\n\n    /**\n     * Return the zodiac.\n     *\n     * @param date The date.\n     * @return the zodiac\n     */\n    public static String getZodiac(final Date date) {\n        Calendar cal = Calendar.getInstance();\n        cal.setTime(date);\n        int month = cal.get(Calendar.MONTH) + 1;\n        int day = cal.get(Calendar.DAY_OF_MONTH);\n        return getZodiac(month, day);\n    }\n\n    /**\n     * Return the zodiac.\n     *\n     * @param millis The milliseconds.\n     * @return the zodiac\n     */\n    public static String getZodiac(final long millis) {\n        return getZodiac(millis2Date(millis));\n    }\n\n    /**\n     * Return the zodiac.\n     *\n     * @param month The month.\n     * @param day   The day.\n     * @return the zodiac\n     */\n    public static String getZodiac(final int month, final int day) {\n        return ZODIAC[day >= ZODIAC_FLAGS[month - 1]\n                ? month - 1\n                : (month + 10) % 12];\n    }\n\n    private static long timeSpan2Millis(final long timeSpan, @TimeConstants.Unit final int unit) {\n        return timeSpan * unit;\n    }\n\n    private static long millis2TimeSpan(final long millis, @TimeConstants.Unit final int unit) {\n        return millis / unit;\n    }\n\n    static String millis2FitTimeSpan(long millis, int precision) {\n        if (precision <= 0) return null;\n        precision = Math.min(precision, 5);\n        String[] units = {\"天\", \"小时\", \"分钟\", \"秒\", \"毫秒\"};\n        if (millis == 0) return 0 + units[precision - 1];\n        StringBuilder sb = new StringBuilder();\n        if (millis < 0) {\n            sb.append(\"-\");\n            millis = -millis;\n        }\n        int[] unitLen = {86400000, 3600000, 60000, 1000, 1};\n        for (int i = 0; i < precision; i++) {\n            if (millis >= unitLen[i]) {\n                long mode = millis / unitLen[i];\n                millis -= mode * unitLen[i];\n                sb.append(mode).append(units[i]);\n            }\n        }\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ToastUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.PixelFormat;\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffColorFilter;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.GradientDrawable;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.os.Message;\n\nimport androidx.annotation.CallSuper;\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringDef;\nimport androidx.annotation.StringRes;\nimport androidx.core.app.NotificationManagerCompat;\nimport androidx.core.content.ContextCompat;\nimport androidx.core.view.ViewCompat;\n\nimport android.util.AttributeSet;\nimport android.view.Gravity;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.RelativeLayout;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.ref.WeakReference;\nimport java.lang.reflect.Field;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/09/29\n *     desc  : utils about toast\n * </pre>\n */\npublic final class ToastUtils {\n\n    @StringDef({MODE.LIGHT, MODE.DARK})\n    @Retention(RetentionPolicy.SOURCE)\n    public @interface MODE {\n        String LIGHT = \"light\";\n        String DARK = \"dark\";\n    }\n\n    private static final String TAG_TOAST = \"TAG_TOAST\";\n    private static final int COLOR_DEFAULT = 0xFEFFFFFF;\n    private static final String NULL = \"toast null\";\n    private static final String NOTHING = \"toast nothing\";\n    private static final ToastUtils DEFAULT_MAKER = make();\n\n    private static WeakReference<IToast> sWeakToast;\n\n    private String mMode;\n    private int mGravity = -1;\n    private int mXOffset = -1;\n    private int mYOffset = -1;\n    private int mBgColor = COLOR_DEFAULT;\n    private int mBgResource = -1;\n    private int mTextColor = COLOR_DEFAULT;\n    private int mTextSize = -1;\n    private boolean isLong = false;\n    private Drawable[] mIcons = new Drawable[4];\n    private boolean isNotUseSystemToast = false;\n\n    /**\n     * Make a toast.\n     *\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public static ToastUtils make() {\n        return new ToastUtils();\n    }\n\n    /**\n     * @param mode The mode.\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setMode(@MODE String mode) {\n        mMode = mode;\n        return this;\n    }\n\n    /**\n     * Set the gravity.\n     *\n     * @param gravity The gravity.\n     * @param xOffset X-axis offset, in pixel.\n     * @param yOffset Y-axis offset, in pixel.\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setGravity(final int gravity, final int xOffset, final int yOffset) {\n        mGravity = gravity;\n        mXOffset = xOffset;\n        mYOffset = yOffset;\n        return this;\n    }\n\n    /**\n     * Set the color of background.\n     *\n     * @param backgroundColor The color of background.\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setBgColor(@ColorInt final int backgroundColor) {\n        mBgColor = backgroundColor;\n        return this;\n    }\n\n    /**\n     * Set the resource of background.\n     *\n     * @param bgResource The resource of background.\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setBgResource(@DrawableRes final int bgResource) {\n        mBgResource = bgResource;\n        return this;\n    }\n\n    /**\n     * Set the text color of toast.\n     *\n     * @param msgColor The text color of toast.\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setTextColor(@ColorInt final int msgColor) {\n        mTextColor = msgColor;\n        return this;\n    }\n\n    /**\n     * Set the text size of toast.\n     *\n     * @param textSize The text size of toast.\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setTextSize(final int textSize) {\n        mTextSize = textSize;\n        return this;\n    }\n\n    /**\n     * Set the toast for a long period of time.\n     *\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setDurationIsLong(boolean isLong) {\n        this.isLong = isLong;\n        return this;\n    }\n\n    /**\n     * Set the left icon of toast.\n     *\n     * @param resId The left icon resource identifier.\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setLeftIcon(@DrawableRes int resId) {\n        return setLeftIcon(ContextCompat.getDrawable(Utils.getApp(), resId));\n    }\n\n    /**\n     * Set the left icon of toast.\n     *\n     * @param drawable The left icon drawable.\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setLeftIcon(@Nullable Drawable drawable) {\n        mIcons[0] = drawable;\n        return this;\n    }\n\n    /**\n     * Set the top icon of toast.\n     *\n     * @param resId The top icon resource identifier.\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setTopIcon(@DrawableRes int resId) {\n        return setTopIcon(ContextCompat.getDrawable(Utils.getApp(), resId));\n    }\n\n    /**\n     * Set the top icon of toast.\n     *\n     * @param drawable The top icon drawable.\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setTopIcon(@Nullable Drawable drawable) {\n        mIcons[1] = drawable;\n        return this;\n    }\n\n    /**\n     * Set the right icon of toast.\n     *\n     * @param resId The right icon resource identifier.\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setRightIcon(@DrawableRes int resId) {\n        return setRightIcon(ContextCompat.getDrawable(Utils.getApp(), resId));\n    }\n\n    /**\n     * Set the right icon of toast.\n     *\n     * @param drawable The right icon drawable.\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setRightIcon(@Nullable Drawable drawable) {\n        mIcons[2] = drawable;\n        return this;\n    }\n\n    /**\n     * Set the left bottom of toast.\n     *\n     * @param resId The bottom icon resource identifier.\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setBottomIcon(int resId) {\n        return setBottomIcon(ContextCompat.getDrawable(Utils.getApp(), resId));\n    }\n\n    /**\n     * Set the bottom icon of toast.\n     *\n     * @param drawable The bottom icon drawable.\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setBottomIcon(@Nullable Drawable drawable) {\n        mIcons[3] = drawable;\n        return this;\n    }\n\n    /**\n     * Set not use system toast.\n     *\n     * @return the single {@link ToastUtils} instance\n     */\n    @NonNull\n    public final ToastUtils setNotUseSystemToast() {\n        isNotUseSystemToast = true;\n        return this;\n    }\n\n    /**\n     * Return the default {@link ToastUtils} instance.\n     *\n     * @return the default {@link ToastUtils} instance\n     */\n    @NonNull\n    public static ToastUtils getDefaultMaker() {\n        return DEFAULT_MAKER;\n    }\n\n    /**\n     * Show the toast for a short period of time.\n     *\n     * @param text The text.\n     */\n    public final void show(@Nullable final CharSequence text) {\n        show(text, getDuration(), this);\n    }\n\n    /**\n     * Show the toast for a short period of time.\n     *\n     * @param resId The resource id for text.\n     */\n    public final void show(@StringRes final int resId) {\n        show(UtilsBridge.getString(resId), getDuration(), this);\n    }\n\n    /**\n     * Show the toast for a short period of time.\n     *\n     * @param resId The resource id for text.\n     * @param args  The args.\n     */\n    public final void show(@StringRes final int resId, final Object... args) {\n        show(UtilsBridge.getString(resId, args), getDuration(), this);\n    }\n\n    /**\n     * Show the toast for a short period of time.\n     *\n     * @param format The format.\n     * @param args   The args.\n     */\n    public final void show(@Nullable final String format, final Object... args) {\n        show(UtilsBridge.format(format, args), getDuration(), this);\n    }\n\n    /**\n     * Show custom toast.\n     */\n    public final void show(@NonNull final View view) {\n        show(view, getDuration(), this);\n    }\n\n    private int getDuration() {\n        return isLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT;\n    }\n\n    private View tryApplyUtilsToastView(final CharSequence text) {\n        if (!MODE.DARK.equals(mMode) && !MODE.LIGHT.equals(mMode)\n                && mIcons[0] == null && mIcons[1] == null && mIcons[2] == null && mIcons[3] == null) {\n            return null;\n        }\n\n        View toastView = UtilsBridge.layoutId2View(R.layout.utils_toast_view);\n        TextView messageTv = toastView.findViewById(android.R.id.message);\n        if (MODE.DARK.equals(mMode)) {\n            GradientDrawable bg = (GradientDrawable) toastView.getBackground().mutate();\n            bg.setColor(Color.parseColor(\"#BB000000\"));\n            messageTv.setTextColor(Color.WHITE);\n        }\n        messageTv.setText(text);\n        if (mIcons[0] != null) {\n            View leftIconView = toastView.findViewById(R.id.utvLeftIconView);\n            ViewCompat.setBackground(leftIconView, mIcons[0]);\n            leftIconView.setVisibility(View.VISIBLE);\n        }\n        if (mIcons[1] != null) {\n            View topIconView = toastView.findViewById(R.id.utvTopIconView);\n            ViewCompat.setBackground(topIconView, mIcons[1]);\n            topIconView.setVisibility(View.VISIBLE);\n        }\n        if (mIcons[2] != null) {\n            View rightIconView = toastView.findViewById(R.id.utvRightIconView);\n            ViewCompat.setBackground(rightIconView, mIcons[2]);\n            rightIconView.setVisibility(View.VISIBLE);\n        }\n        if (mIcons[3] != null) {\n            View bottomIconView = toastView.findViewById(R.id.utvBottomIconView);\n            ViewCompat.setBackground(bottomIconView, mIcons[3]);\n            bottomIconView.setVisibility(View.VISIBLE);\n        }\n        return toastView;\n    }\n\n\n    /**\n     * Show the toast for a short period of time.\n     *\n     * @param text The text.\n     */\n    public static void showShort(@Nullable final CharSequence text) {\n        show(text, Toast.LENGTH_SHORT, DEFAULT_MAKER);\n    }\n\n    /**\n     * Show the toast for a short period of time.\n     *\n     * @param resId The resource id for text.\n     */\n    public static void showShort(@StringRes final int resId) {\n        show(UtilsBridge.getString(resId), Toast.LENGTH_SHORT, DEFAULT_MAKER);\n    }\n\n    /**\n     * Show the toast for a short period of time.\n     *\n     * @param resId The resource id for text.\n     * @param args  The args.\n     */\n    public static void showShort(@StringRes final int resId, final Object... args) {\n        show(UtilsBridge.getString(resId, args), Toast.LENGTH_SHORT, DEFAULT_MAKER);\n    }\n\n    /**\n     * Show the toast for a short period of time.\n     *\n     * @param format The format.\n     * @param args   The args.\n     */\n    public static void showShort(@Nullable final String format, final Object... args) {\n        show(UtilsBridge.format(format, args), Toast.LENGTH_SHORT, DEFAULT_MAKER);\n    }\n\n    /**\n     * Show the toast for a long period of time.\n     *\n     * @param text The text.\n     */\n    public static void showLong(@Nullable final CharSequence text) {\n        show(text, Toast.LENGTH_LONG, DEFAULT_MAKER);\n    }\n\n    /**\n     * Show the toast for a long period of time.\n     *\n     * @param resId The resource id for text.\n     */\n    public static void showLong(@StringRes final int resId) {\n        show(UtilsBridge.getString(resId), Toast.LENGTH_LONG, DEFAULT_MAKER);\n    }\n\n    /**\n     * Show the toast for a long period of time.\n     *\n     * @param resId The resource id for text.\n     * @param args  The args.\n     */\n    public static void showLong(@StringRes final int resId, final Object... args) {\n        show(UtilsBridge.getString(resId), Toast.LENGTH_LONG, DEFAULT_MAKER);\n    }\n\n    /**\n     * Show the toast for a long period of time.\n     *\n     * @param format The format.\n     * @param args   The args.\n     */\n    public static void showLong(@Nullable final String format, final Object... args) {\n        show(UtilsBridge.format(format, args), Toast.LENGTH_LONG, DEFAULT_MAKER);\n    }\n\n    /**\n     * Cancel the toast.\n     */\n    public static void cancel() {\n        UtilsBridge.runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                if (sWeakToast != null) {\n                    final IToast iToast = ToastUtils.sWeakToast.get();\n                    if (iToast != null) {\n                        iToast.cancel();\n                    }\n                    sWeakToast = null;\n                }\n            }\n        });\n    }\n\n    private static void show(@Nullable final CharSequence text, final int duration, final ToastUtils utils) {\n        show(null, getToastFriendlyText(text), duration, utils);\n    }\n\n    private static void show(@NonNull final View view, final int duration, final ToastUtils utils) {\n        show(view, null, duration, utils);\n    }\n\n    private static void show(@Nullable final View view,\n                             @Nullable final CharSequence text,\n                             final int duration,\n                             @NonNull final ToastUtils utils) {\n        UtilsBridge.runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                cancel();\n                IToast iToast = newToast(utils);\n                ToastUtils.sWeakToast = new WeakReference<>(iToast);\n                if (view != null) {\n                    iToast.setToastView(view);\n                } else {\n                    iToast.setToastView(text);\n                }\n                iToast.show(duration);\n            }\n        });\n    }\n\n    private static CharSequence getToastFriendlyText(CharSequence src) {\n        CharSequence text = src;\n        if (text == null) {\n            text = NULL;\n        } else if (text.length() == 0) {\n            text = NOTHING;\n        }\n        return text;\n    }\n\n    private static IToast newToast(ToastUtils toastUtils) {\n        if (!toastUtils.isNotUseSystemToast) {\n            if (NotificationManagerCompat.from(Utils.getApp()).areNotificationsEnabled()) {\n                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n                    return new SystemToast(toastUtils);\n                }\n                if (!UtilsBridge.isGrantedDrawOverlays()) {\n                    return new SystemToast(toastUtils);\n                }\n            }\n        }\n\n        // not use system or notification disable\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {\n            return new WindowManagerToast(toastUtils, WindowManager.LayoutParams.TYPE_TOAST);\n        } else if (UtilsBridge.isGrantedDrawOverlays()) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                return new WindowManagerToast(toastUtils, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);\n            } else {\n                return new WindowManagerToast(toastUtils, WindowManager.LayoutParams.TYPE_PHONE);\n            }\n        }\n        return new ActivityToast(toastUtils);\n    }\n\n    static final class SystemToast extends AbsToast {\n\n        SystemToast(ToastUtils toastUtils) {\n            super(toastUtils);\n            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {\n                try {\n                    //noinspection JavaReflectionMemberAccess\n                    Field mTNField = Toast.class.getDeclaredField(\"mTN\");\n                    mTNField.setAccessible(true);\n                    Object mTN = mTNField.get(mToast);\n                    Field mTNmHandlerField = mTNField.getType().getDeclaredField(\"mHandler\");\n                    mTNmHandlerField.setAccessible(true);\n                    Handler tnHandler = (Handler) mTNmHandlerField.get(mTN);\n                    mTNmHandlerField.set(mTN, new SafeHandler(tnHandler));\n                } catch (Exception ignored) {/**/}\n            }\n        }\n\n        @Override\n        public void show(int duration) {\n            if (mToast == null) return;\n            mToast.setDuration(duration);\n            mToast.show();\n        }\n\n        static class SafeHandler extends Handler {\n            private Handler impl;\n\n            SafeHandler(Handler impl) {\n                this.impl = impl;\n            }\n\n            @Override\n            public void handleMessage(@NonNull Message msg) {\n                impl.handleMessage(msg);\n            }\n\n            @Override\n            public void dispatchMessage(@NonNull Message msg) {\n                try {\n                    impl.dispatchMessage(msg);\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    static final class WindowManagerToast extends AbsToast {\n\n        private WindowManager mWM;\n\n        private WindowManager.LayoutParams mParams;\n\n        WindowManagerToast(ToastUtils toastUtils, int type) {\n            super(toastUtils);\n            mParams = new WindowManager.LayoutParams();\n            mWM = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE);\n            mParams.type = type;\n        }\n\n        WindowManagerToast(ToastUtils toastUtils, WindowManager wm, int type) {\n            super(toastUtils);\n            mParams = new WindowManager.LayoutParams();\n            mWM = wm;\n            mParams.type = type;\n        }\n\n        @Override\n        public void show(final int duration) {\n            if (mToast == null) return;\n            mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;\n            mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;\n            mParams.format = PixelFormat.TRANSLUCENT;\n            mParams.windowAnimations = android.R.style.Animation_Toast;\n            mParams.setTitle(\"ToastWithoutNotification\");\n            mParams.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON\n                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE\n                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;\n            mParams.packageName = Utils.getApp().getPackageName();\n\n            mParams.gravity = mToast.getGravity();\n            if ((mParams.gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {\n                mParams.horizontalWeight = 1.0f;\n            }\n            if ((mParams.gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {\n                mParams.verticalWeight = 1.0f;\n            }\n\n            mParams.x = mToast.getXOffset();\n            mParams.y = mToast.getYOffset();\n            mParams.horizontalMargin = mToast.getHorizontalMargin();\n            mParams.verticalMargin = mToast.getVerticalMargin();\n\n            try {\n                if (mWM != null) {\n                    mWM.addView(mToastView, mParams);\n                }\n            } catch (Exception ignored) {/**/}\n\n            UtilsBridge.runOnUiThreadDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    cancel();\n                }\n            }, duration == Toast.LENGTH_SHORT ? 2000 : 3500);\n        }\n\n        @Override\n        public void cancel() {\n            try {\n                if (mWM != null) {\n                    mWM.removeViewImmediate(mToastView);\n                    mWM = null;\n                }\n            } catch (Exception ignored) {/**/}\n            super.cancel();\n        }\n    }\n\n    static final class ActivityToast extends AbsToast {\n\n        private static int sShowingIndex = 0;\n\n        private Utils.ActivityLifecycleCallbacks mActivityLifecycleCallbacks;\n        private IToast iToast;\n\n        ActivityToast(ToastUtils toastUtils) {\n            super(toastUtils);\n        }\n\n        @Override\n        public void show(int duration) {\n            if (mToast == null) return;\n            if (!UtilsBridge.isAppForeground()) {\n                // try to use system toast\n                iToast = showSystemToast(duration);\n                return;\n            }\n            boolean hasAliveActivity = false;\n            for (final Activity activity : UtilsBridge.getActivityList()) {\n                if (!UtilsBridge.isActivityAlive(activity)) {\n                    continue;\n                }\n                if (!hasAliveActivity) {\n                    hasAliveActivity = true;\n                    iToast = showWithActivityWindow(activity, duration);\n                } else {\n                    showWithActivityView(activity, sShowingIndex, true);\n                }\n            }\n            if (hasAliveActivity) {\n                registerLifecycleCallback();\n                UtilsBridge.runOnUiThreadDelayed(new Runnable() {\n                    @Override\n                    public void run() {\n                        cancel();\n                    }\n                }, duration == Toast.LENGTH_SHORT ? 2000 : 3500);\n\n                ++sShowingIndex;\n            } else {\n                // try to use system toast\n                iToast = showSystemToast(duration);\n            }\n        }\n\n        @Override\n        public void cancel() {\n            if (isShowing()) {\n                unregisterLifecycleCallback();\n                for (Activity activity : UtilsBridge.getActivityList()) {\n                    if (!UtilsBridge.isActivityAlive(activity)) {\n                        continue;\n                    }\n                    final Window window = activity.getWindow();\n                    if (window != null) {\n                        ViewGroup decorView = (ViewGroup) window.getDecorView();\n                        View toastView = decorView.findViewWithTag(TAG_TOAST + (sShowingIndex - 1));\n                        if (toastView != null) {\n                            try {\n                                decorView.removeView(toastView);\n                            } catch (Exception ignored) {/**/}\n                        }\n                    }\n                }\n            }\n            if (iToast != null) {\n                iToast.cancel();\n                iToast = null;\n            }\n            super.cancel();\n        }\n\n        private IToast showSystemToast(int duration) {\n            SystemToast systemToast = new SystemToast(mToastUtils);\n            systemToast.mToast = mToast;\n            systemToast.show(duration);\n            return systemToast;\n        }\n\n        private IToast showWithActivityWindow(Activity activity, int duration) {\n            WindowManagerToast wmToast = new WindowManagerToast(mToastUtils, activity.getWindowManager(), WindowManager.LayoutParams.LAST_APPLICATION_WINDOW);\n            wmToast.mToastView = getToastViewSnapshot(-1);\n            wmToast.mToast = mToast;\n            wmToast.show(duration);\n            return wmToast;\n        }\n\n        private void showWithActivityView(final Activity activity, final int index, boolean useAnim) {\n            final Window window = activity.getWindow();\n            if (window != null) {\n                final ViewGroup decorView = (ViewGroup) window.getDecorView();\n                FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(\n                        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT\n                );\n                lp.gravity = mToast.getGravity();\n                lp.bottomMargin = mToast.getYOffset() + UtilsBridge.getNavBarHeight();\n                lp.topMargin = mToast.getYOffset() + UtilsBridge.getStatusBarHeight();\n                lp.leftMargin = mToast.getXOffset();\n                View toastViewSnapshot = getToastViewSnapshot(index);\n                if (useAnim) {\n                    toastViewSnapshot.setAlpha(0);\n                    toastViewSnapshot.animate().alpha(1).setDuration(200).start();\n                }\n                decorView.addView(toastViewSnapshot, lp);\n            }\n        }\n\n        private void registerLifecycleCallback() {\n            final int index = sShowingIndex;\n            mActivityLifecycleCallbacks = new Utils.ActivityLifecycleCallbacks() {\n                @Override\n                public void onActivityCreated(@NonNull Activity activity) {\n                    if (isShowing()) {\n                        showWithActivityView(activity, index, false);\n                    }\n                }\n            };\n            UtilsBridge.addActivityLifecycleCallbacks(mActivityLifecycleCallbacks);\n        }\n\n        private void unregisterLifecycleCallback() {\n            UtilsBridge.removeActivityLifecycleCallbacks(mActivityLifecycleCallbacks);\n            mActivityLifecycleCallbacks = null;\n        }\n\n        private boolean isShowing() {\n            return mActivityLifecycleCallbacks != null;\n        }\n    }\n\n    static abstract class AbsToast implements IToast {\n\n        protected Toast mToast;\n        protected ToastUtils mToastUtils;\n        protected View mToastView;\n\n        AbsToast(ToastUtils toastUtils) {\n            mToast = new Toast(Utils.getApp());\n            mToastUtils = toastUtils;\n\n            if (mToastUtils.mGravity != -1 || mToastUtils.mXOffset != -1 || mToastUtils.mYOffset != -1) {\n                mToast.setGravity(mToastUtils.mGravity, mToastUtils.mXOffset, mToastUtils.mYOffset);\n            }\n        }\n\n        @Override\n        public void setToastView(View view) {\n            mToastView = view;\n            mToast.setView(mToastView);\n        }\n\n        @Override\n        public void setToastView(CharSequence text) {\n            View utilsToastView = mToastUtils.tryApplyUtilsToastView(text);\n            if (utilsToastView != null) {\n                setToastView(utilsToastView);\n                processRtlIfNeed();\n                return;\n            }\n\n            mToastView = mToast.getView();\n            if (mToastView == null || mToastView.findViewById(android.R.id.message) == null) {\n                setToastView(UtilsBridge.layoutId2View(R.layout.utils_toast_view));\n            }\n\n            TextView messageTv = mToastView.findViewById(android.R.id.message);\n            messageTv.setText(text);\n            if (mToastUtils.mTextColor != COLOR_DEFAULT) {\n                messageTv.setTextColor(mToastUtils.mTextColor);\n            }\n            if (mToastUtils.mTextSize != -1) {\n                messageTv.setTextSize(mToastUtils.mTextSize);\n            }\n            setBg(messageTv);\n            processRtlIfNeed();\n        }\n\n        private void processRtlIfNeed() {\n            if (UtilsBridge.isLayoutRtl()) {\n                setToastView(getToastViewSnapshot(-1));\n            }\n        }\n\n        private void setBg(final TextView msgTv) {\n            if (mToastUtils.mBgResource != -1) {\n                mToastView.setBackgroundResource(mToastUtils.mBgResource);\n                msgTv.setBackgroundColor(Color.TRANSPARENT);\n            } else if (mToastUtils.mBgColor != COLOR_DEFAULT) {\n                Drawable toastBg = mToastView.getBackground();\n                Drawable msgBg = msgTv.getBackground();\n                if (toastBg != null && msgBg != null) {\n                    toastBg.mutate().setColorFilter(new PorterDuffColorFilter(mToastUtils.mBgColor, PorterDuff.Mode.SRC_IN));\n                    msgTv.setBackgroundColor(Color.TRANSPARENT);\n                } else if (toastBg != null) {\n                    toastBg.mutate().setColorFilter(new PorterDuffColorFilter(mToastUtils.mBgColor, PorterDuff.Mode.SRC_IN));\n                } else if (msgBg != null) {\n                    msgBg.mutate().setColorFilter(new PorterDuffColorFilter(mToastUtils.mBgColor, PorterDuff.Mode.SRC_IN));\n                } else {\n                    mToastView.setBackgroundColor(mToastUtils.mBgColor);\n                }\n            }\n        }\n\n        @Override\n        @CallSuper\n        public void cancel() {\n            if (mToast != null) {\n                mToast.cancel();\n            }\n            mToast = null;\n            mToastView = null;\n        }\n\n        View getToastViewSnapshot(final int index) {\n            Bitmap bitmap = UtilsBridge.view2Bitmap(mToastView);\n            ImageView toastIv = new ImageView(Utils.getApp());\n            toastIv.setTag(TAG_TOAST + index);\n            toastIv.setImageBitmap(bitmap);\n            return toastIv;\n        }\n    }\n\n    interface IToast {\n\n        void setToastView(View view);\n\n        void setToastView(CharSequence text);\n\n        void show(int duration);\n\n        void cancel();\n    }\n\n    public static final class UtilsMaxWidthRelativeLayout extends RelativeLayout {\n\n        private static final int SPACING = UtilsBridge.dp2px(80);\n\n        public UtilsMaxWidthRelativeLayout(Context context) {\n            super(context);\n        }\n\n        public UtilsMaxWidthRelativeLayout(Context context, AttributeSet attrs) {\n            super(context, attrs);\n        }\n\n        public UtilsMaxWidthRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {\n            super(context, attrs, defStyleAttr);\n        }\n\n        @Override\n        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n            int widthMaxSpec = MeasureSpec.makeMeasureSpec(UtilsBridge.getAppScreenWidth() - SPACING, MeasureSpec.AT_MOST);\n            super.onMeasure(widthMaxSpec, heightMeasureSpec);\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/TouchUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport androidx.annotation.IntDef;\nimport android.view.MotionEvent;\nimport android.view.VelocityTracker;\nimport android.view.View;\nimport android.view.ViewConfiguration;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/08/26\n *     desc  : utils about touch\n * </pre>\n */\npublic class TouchUtils {\n\n    public static final int UNKNOWN = 0;\n    public static final int LEFT    = 1;\n    public static final int UP      = 2;\n    public static final int RIGHT   = 4;\n    public static final int DOWN    = 8;\n\n    @IntDef({LEFT, UP, RIGHT, DOWN})\n    @Retention(RetentionPolicy.SOURCE)\n    public @interface Direction {\n    }\n\n    private TouchUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    public static void setOnTouchListener(final View v, final OnTouchUtilsListener listener) {\n        if (v == null || listener == null) {\n            return;\n        }\n        v.setOnTouchListener(listener);\n    }\n\n    public static abstract class OnTouchUtilsListener implements View.OnTouchListener {\n\n        private static final int STATE_DOWN = 0;\n        private static final int STATE_MOVE = 1;\n        private static final int STATE_STOP = 2;\n\n        private static final int MIN_TAP_TIME = 1000;\n\n        private int touchSlop;\n        private int downX, downY, lastX, lastY;\n        private int             state;\n        private int             direction;\n        private VelocityTracker velocityTracker;\n        private int             maximumFlingVelocity;\n        private int             minimumFlingVelocity;\n\n        public OnTouchUtilsListener() {\n            resetTouch(-1, -1);\n        }\n\n        private void resetTouch(int x, int y) {\n            downX = x;\n            downY = y;\n            lastX = x;\n            lastY = y;\n            state = STATE_DOWN;\n            direction = UNKNOWN;\n            if (velocityTracker != null) {\n                velocityTracker.clear();\n            }\n        }\n\n        public abstract boolean onDown(View view, int x, int y, MotionEvent event);\n\n        public abstract boolean onMove(View view,\n                                       @Direction int direction,\n                                       int x,\n                                       int y,\n                                       int dx,\n                                       int dy,\n                                       int totalX,\n                                       int totalY,\n                                       MotionEvent event);\n\n        public abstract boolean onStop(View view,\n                                       @Direction int direction,\n                                       int x,\n                                       int y,\n                                       int totalX,\n                                       int totalY,\n                                       int vx,\n                                       int vy,\n                                       MotionEvent event);\n\n        @Override\n        public boolean onTouch(View v, MotionEvent event) {\n            if (touchSlop == 0) {\n                touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();\n            }\n            if (maximumFlingVelocity == 0) {\n                maximumFlingVelocity =\n                        ViewConfiguration.get(v.getContext()).getScaledMaximumFlingVelocity();\n            }\n            if (minimumFlingVelocity == 0) {\n                minimumFlingVelocity =\n                        ViewConfiguration.get(v.getContext()).getScaledMinimumFlingVelocity();\n            }\n            if (velocityTracker == null) {\n                velocityTracker = VelocityTracker.obtain();\n            }\n            velocityTracker.addMovement(event);\n\n            switch (event.getAction()) {\n                case MotionEvent.ACTION_DOWN:\n                    return onUtilsDown(v, event);\n                case MotionEvent.ACTION_MOVE:\n                    return onUtilsMove(v, event);\n                case MotionEvent.ACTION_UP:\n                case MotionEvent.ACTION_CANCEL:\n                    return onUtilsStop(v, event);\n                default:\n                    break;\n            }\n            return false;\n        }\n\n        public boolean onUtilsDown(View view, MotionEvent event) {\n            int x = (int) event.getRawX();\n            int y = (int) event.getRawY();\n\n            resetTouch(x, y);\n            view.setPressed(true);\n            return onDown(view, x, y, event);\n        }\n\n        public boolean onUtilsMove(View view, MotionEvent event) {\n            int x = (int) event.getRawX();\n            int y = (int) event.getRawY();\n\n            if (downX == -1) {\n                // not receive down should reset\n                resetTouch(x, y);\n                view.setPressed(true);\n            }\n\n            if (state != STATE_MOVE) {\n                if (Math.abs(x - lastX) < touchSlop && Math.abs(y - lastY) < touchSlop) {\n                    return true;\n                }\n                state = STATE_MOVE;\n                if (Math.abs(x - lastX) >= Math.abs(y - lastY)) {\n                    if (x - lastX < 0) {\n                        direction = LEFT;\n                    } else {\n                        direction = RIGHT;\n                    }\n                } else {\n                    if (y - lastY < 0) {\n                        direction = UP;\n                    } else {\n                        direction = DOWN;\n                    }\n                }\n            }\n\n            boolean consumeMove =\n                    onMove(view, direction, x, y, x - lastX, y - lastY, x - downX, y - downY, event);\n            lastX = x;\n            lastY = y;\n            return consumeMove;\n        }\n\n        public boolean onUtilsStop(View view, MotionEvent event) {\n            int x = (int) event.getRawX();\n            int y = (int) event.getRawY();\n\n            int vx = 0, vy = 0;\n\n            if (velocityTracker != null) {\n                velocityTracker.computeCurrentVelocity(1000, maximumFlingVelocity);\n                vx = (int) velocityTracker.getXVelocity();\n                vy = (int) velocityTracker.getYVelocity();\n                velocityTracker.recycle();\n                if (Math.abs(vx) < minimumFlingVelocity) {\n                    vx = 0;\n                }\n                if (Math.abs(vy) < minimumFlingVelocity) {\n                    vy = 0;\n                }\n                velocityTracker = null;\n            }\n\n            view.setPressed(false);\n            boolean consumeStop = onStop(view, direction, x, y, x - downX, y - downY, vx, vy, event);\n\n            if (event.getAction() == MotionEvent.ACTION_UP) {\n                if (state == STATE_DOWN) {\n                    if (event.getEventTime() - event.getDownTime() <= MIN_TAP_TIME) {\n                        view.performClick();\n                    } else {\n                        view.performLongClick();\n                    }\n                }\n            }\n\n            resetTouch(-1, -1);\n\n            return consumeStop;\n        }\n    }\n}"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/UiMessageUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.util.Log;\nimport android.util.SparseArray;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * <pre>\n *     author: blankj\n *     blog  : http://blankj.com\n *     time  : 2019/10/20\n *     desc  : utils about ui message can replace LocalBroadcastManager\n * </pre>\n */\npublic final class UiMessageUtils implements Handler.Callback {\n\n    private static final String  TAG   = \"UiMessageUtils\";\n    private static final boolean DEBUG = UtilsBridge.isAppDebug();\n\n    private final Handler   mHandler = new Handler(Looper.getMainLooper(), this);\n    private final UiMessage mMessage = new UiMessage(null);\n\n    private final SparseArray<List<UiMessageCallback>> mListenersSpecific  = new SparseArray<>();\n    private final List<UiMessageCallback>              mListenersUniversal = new ArrayList<>();\n    private final List<UiMessageCallback>              mDefensiveCopyList  = new ArrayList<>();\n\n    public static UiMessageUtils getInstance() {\n        return LazyHolder.INSTANCE;\n    }\n\n    private UiMessageUtils() {\n    }\n\n    /**\n     * Sends an empty Message containing only the message ID.\n     *\n     * @param id The message ID.\n     */\n    public final void send(final int id) {\n        mHandler.sendEmptyMessage(id);\n    }\n\n    /**\n     * Sends a message containing the ID and an arbitrary object.\n     *\n     * @param id  The message ID.\n     * @param obj The object.\n     */\n    public final void send(final int id, @NonNull final Object obj) {\n        mHandler.sendMessage(mHandler.obtainMessage(id, obj));\n    }\n\n    /**\n     * Add listener for specific type of message by its ID.\n     * Don't forget to call {@link #removeListener(UiMessageCallback)} or\n     * {@link #removeListeners(int)}\n     *\n     * @param id       The ID of message that will be only notified to listener.\n     * @param listener The listener.\n     */\n    public void addListener(int id, @NonNull final UiMessageCallback listener) {\n        synchronized (mListenersSpecific) {\n            List<UiMessageCallback> idListeners = mListenersSpecific.get(id);\n            if (idListeners == null) {\n                idListeners = new ArrayList<>();\n                mListenersSpecific.put(id, idListeners);\n            }\n            if (!idListeners.contains(listener)) {\n                idListeners.add(listener);\n            }\n        }\n    }\n\n    /**\n     * Add listener for all messages.\n     *\n     * @param listener The listener.\n     */\n    public void addListener(@NonNull final UiMessageCallback listener) {\n        synchronized (mListenersUniversal) {\n            if (!mListenersUniversal.contains(listener)) {\n                mListenersUniversal.add(listener);\n            } else {\n                if (DEBUG) {\n                    Log.w(TAG, \"Listener is already added. \" + listener.toString());\n                }\n            }\n        }\n    }\n\n    /**\n     * Remove listener for all messages.\n     *\n     * @param listener The listener to remove.\n     */\n    public void removeListener(@NonNull final UiMessageCallback listener) {\n        synchronized (mListenersUniversal) {\n            if (DEBUG && !mListenersUniversal.contains(listener)) {\n                Log.w(TAG, \"Trying to remove a listener that is not registered. \" + listener.toString());\n            }\n            mListenersUniversal.remove(listener);\n        }\n    }\n\n    /**\n     * Remove all listeners for desired message ID.\n     *\n     * @param id The id of the message to stop listening to.\n     */\n    public void removeListeners(final int id) {\n        if (DEBUG) {\n            final List<UiMessageCallback> callbacks = mListenersSpecific.get(id);\n            if (callbacks == null || callbacks.size() == 0) {\n                Log.w(TAG, \"Trying to remove specific listeners that are not registered. ID \" + id);\n            }\n        }\n        synchronized (mListenersSpecific) {\n            mListenersSpecific.delete(id);\n        }\n    }\n\n    /**\n     * Remove the specific listener for desired message ID.\n     *\n     * @param id       The id of the message to stop listening to.\n     * @param listener The listener which should be removed.\n     */\n    public void removeListener(final int id, @NonNull final UiMessageCallback listener) {\n        synchronized (mListenersSpecific) {\n            final List<UiMessageCallback> callbacks = this.mListenersSpecific.get(id);\n            if (callbacks != null && !callbacks.isEmpty()) {\n                if (DEBUG) {\n                    if (!callbacks.contains(listener)) {\n                        Log.w(TAG, \"Trying to remove specific listener that is not registered. ID \" + id + \", \" + listener);\n                        return;\n                    }\n                }\n                callbacks.remove(listener);\n            } else {\n                if (DEBUG) {\n                    Log.w(TAG, \"Trying to remove specific listener that is not registered. ID \" + id + \", \" + listener);\n                }\n            }\n        }\n    }\n\n    @Override\n    public boolean handleMessage(Message msg) {\n        mMessage.setMessage(msg);\n        if (DEBUG) {\n            logMessageHandling(mMessage);\n        }\n\n        // process listeners for specified type of message what\n        synchronized (mListenersSpecific) {\n            final List<UiMessageCallback> idListeners = mListenersSpecific.get(msg.what);\n            if (idListeners != null) {\n                if (idListeners.size() == 0) {\n                    mListenersSpecific.remove(msg.what);\n                } else {\n                    mDefensiveCopyList.addAll(idListeners);\n                    for (final UiMessageCallback callback : mDefensiveCopyList) {\n                        callback.handleMessage(mMessage);\n                    }\n                    mDefensiveCopyList.clear();\n                }\n            }\n        }\n\n        // process universal listeners\n        synchronized (mListenersUniversal) {\n            if (mListenersUniversal.size() > 0) {\n                mDefensiveCopyList.addAll(mListenersUniversal);\n                for (final UiMessageCallback callback : mDefensiveCopyList) {\n                    callback.handleMessage(mMessage);\n                }\n                mDefensiveCopyList.clear();\n            }\n        }\n\n        mMessage.setMessage(null);\n\n        return true;\n    }\n\n    private void logMessageHandling(@NonNull final UiMessage msg) {\n        final List<UiMessageCallback> idListeners = mListenersSpecific.get(msg.getId());\n\n        if ((idListeners == null || idListeners.size() == 0) && mListenersUniversal.size() == 0) {\n            Log.w(TAG, \"Delivering FAILED for message ID \" + msg.getId() + \". No listeners. \" + msg.toString());\n        } else {\n            final StringBuilder stringBuilder = new StringBuilder();\n            stringBuilder.append(\"Delivering message ID \");\n            stringBuilder.append(msg.getId());\n            stringBuilder.append(\", Specific listeners: \");\n            if (idListeners == null || idListeners.size() == 0) {\n                stringBuilder.append(0);\n            } else {\n                stringBuilder.append(idListeners.size());\n                stringBuilder.append(\" [\");\n                for (int i = 0; i < idListeners.size(); i++) {\n                    stringBuilder.append(idListeners.get(i).getClass().getSimpleName());\n                    if (i < idListeners.size() - 1) {\n                        stringBuilder.append(\",\");\n                    }\n                }\n                stringBuilder.append(\"]\");\n            }\n\n            stringBuilder.append(\", Universal listeners: \");\n            synchronized (mListenersUniversal) {\n                if (mListenersUniversal.size() == 0) {\n                    stringBuilder.append(0);\n                } else {\n                    stringBuilder.append(mListenersUniversal.size());\n                    stringBuilder.append(\" [\");\n                    for (int i = 0; i < mListenersUniversal.size(); i++) {\n                        stringBuilder.append(mListenersUniversal.get(i).getClass().getSimpleName());\n                        if (i < mListenersUniversal.size() - 1) {\n                            stringBuilder.append(\",\");\n                        }\n                    }\n                    stringBuilder.append(\"], Message: \");\n                }\n            }\n            stringBuilder.append(msg.toString());\n\n            Log.v(TAG, stringBuilder.toString());\n        }\n    }\n\n    public static final class UiMessage {\n\n        private Message mMessage;\n\n        private UiMessage(final Message message) {\n            this.mMessage = message;\n        }\n\n        private void setMessage(final Message message) {\n            mMessage = message;\n        }\n\n        public int getId() {\n            return mMessage.what;\n        }\n\n        public Object getObject() {\n            return mMessage.obj;\n        }\n\n        @Override\n        public String toString() {\n            return \"{ \" +\n                    \"id=\" + getId() +\n                    \", obj=\" + getObject() +\n                    \" }\";\n        }\n    }\n\n    public interface UiMessageCallback {\n        void handleMessage(@NonNull UiMessage localMessage);\n    }\n\n    private static final class LazyHolder {\n        private static final UiMessageUtils INSTANCE = new UiMessageUtils();\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/UriUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.ContentResolver;\nimport android.content.ContentUris;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.os.storage.StorageManager;\nimport android.provider.DocumentsContract;\nimport android.provider.MediaStore;\nimport androidx.core.content.FileProvider;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.reflect.Array;\nimport java.lang.reflect.Method;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2018/04/20\n *     desc  : utils about uri\n * </pre>\n */\npublic final class UriUtils {\n\n    private UriUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Resource to uri.\n     * <p>res2Uri([res type]/[res name]) -> res2Uri(drawable/icon), res2Uri(raw/icon)</p>\n     * <p>res2Uri([resource_id]) -> res2Uri(R.drawable.icon)</p>\n     *\n     * @param resPath The path of res.\n     * @return uri\n     */\n    public static Uri res2Uri(String resPath) {\n        return Uri.parse(\"android.resource://\" + Utils.getApp().getPackageName() + \"/\" + resPath);\n    }\n\n    /**\n     * File to uri.\n     *\n     * @param file The file.\n     * @return uri\n     */\n    public static Uri file2Uri(final File file) {\n        if (!UtilsBridge.isFileExists(file)) return null;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            String authority = Utils.getApp().getPackageName() + \".utilcode.provider\";\n            return FileProvider.getUriForFile(Utils.getApp(), authority, file);\n        } else {\n            return Uri.fromFile(file);\n        }\n    }\n\n    /**\n     * Uri to file.\n     *\n     * @param uri The uri.\n     * @return file\n     */\n    public static File uri2File(final Uri uri) {\n        if (uri == null) return null;\n        File file = uri2FileReal(uri);\n        if (file != null) return file;\n        return copyUri2Cache(uri);\n    }\n\n    /**\n     * Uri to file.\n     *\n     * @param uri The uri.\n     * @return file\n     */\n    private static File uri2FileReal(final Uri uri) {\n        Log.d(\"UriUtils\", uri.toString());\n        String authority = uri.getAuthority();\n        String scheme = uri.getScheme();\n        String path = uri.getPath();\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && path != null) {\n            String[] externals = new String[]{\"/external/\", \"/external_path/\"};\n            File file = null;\n            for (String external : externals) {\n                if (path.startsWith(external)) {\n                    file = new File(Environment.getExternalStorageDirectory().getAbsolutePath()\n                            + path.replace(external, \"/\"));\n                    if (file.exists()) {\n                        Log.d(\"UriUtils\", uri.toString() + \" -> \" + external);\n                        return file;\n                    }\n                }\n            }\n            file = null;\n            if (path.startsWith(\"/files_path/\")) {\n                file = new File(Utils.getApp().getFilesDir().getAbsolutePath()\n                        + path.replace(\"/files_path/\", \"/\"));\n            } else if (path.startsWith(\"/cache_path/\")) {\n                file = new File(Utils.getApp().getCacheDir().getAbsolutePath()\n                        + path.replace(\"/cache_path/\", \"/\"));\n            } else if (path.startsWith(\"/external_files_path/\")) {\n                file = new File(Utils.getApp().getExternalFilesDir(null).getAbsolutePath()\n                        + path.replace(\"/external_files_path/\", \"/\"));\n            } else if (path.startsWith(\"/external_cache_path/\")) {\n                file = new File(Utils.getApp().getExternalCacheDir().getAbsolutePath()\n                        + path.replace(\"/external_cache_path/\", \"/\"));\n            }\n            if (file != null && file.exists()) {\n                Log.d(\"UriUtils\", uri.toString() + \" -> \" + path);\n                return file;\n            }\n        }\n        if (ContentResolver.SCHEME_FILE.equals(scheme)) {\n            if (path != null) return new File(path);\n            Log.d(\"UriUtils\", uri.toString() + \" parse failed. -> 0\");\n            return null;\n        }// end 0\n        else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT\n                && DocumentsContract.isDocumentUri(Utils.getApp(), uri)) {\n            if (\"com.android.externalstorage.documents\".equals(authority)) {\n                final String docId = DocumentsContract.getDocumentId(uri);\n                final String[] split = docId.split(\":\");\n                final String type = split[0];\n                if (\"primary\".equalsIgnoreCase(type)) {\n                    return new File(Environment.getExternalStorageDirectory() + \"/\" + split[1]);\n                } else {\n                    // Below logic is how External Storage provider build URI for documents\n                    // http://stackoverflow.com/questions/28605278/android-5-sd-card-label\n                    StorageManager mStorageManager = (StorageManager) Utils.getApp().getSystemService(Context.STORAGE_SERVICE);\n                    try {\n                        Class<?> storageVolumeClazz = Class.forName(\"android.os.storage.StorageVolume\");\n                        Method getVolumeList = mStorageManager.getClass().getMethod(\"getVolumeList\");\n                        Method getUuid = storageVolumeClazz.getMethod(\"getUuid\");\n                        Method getState = storageVolumeClazz.getMethod(\"getState\");\n                        Method getPath = storageVolumeClazz.getMethod(\"getPath\");\n                        Method isPrimary = storageVolumeClazz.getMethod(\"isPrimary\");\n                        Method isEmulated = storageVolumeClazz.getMethod(\"isEmulated\");\n\n                        Object result = getVolumeList.invoke(mStorageManager);\n\n                        final int length = Array.getLength(result);\n                        for (int i = 0; i < length; i++) {\n                            Object storageVolumeElement = Array.get(result, i);\n                            //String uuid = (String) getUuid.invoke(storageVolumeElement);\n\n                            final boolean mounted = Environment.MEDIA_MOUNTED.equals(getState.invoke(storageVolumeElement))\n                                    || Environment.MEDIA_MOUNTED_READ_ONLY.equals(getState.invoke(storageVolumeElement));\n\n                            //if the media is not mounted, we need not get the volume details\n                            if (!mounted) continue;\n\n                            //Primary storage is already handled.\n                            if ((Boolean) isPrimary.invoke(storageVolumeElement)\n                                    && (Boolean) isEmulated.invoke(storageVolumeElement)) {\n                                continue;\n                            }\n\n                            String uuid = (String) getUuid.invoke(storageVolumeElement);\n\n                            if (uuid != null && uuid.equals(type)) {\n                                return new File(getPath.invoke(storageVolumeElement) + \"/\" + split[1]);\n                            }\n                        }\n                    } catch (Exception ex) {\n                        Log.d(\"UriUtils\", uri.toString() + \" parse failed. \" + ex.toString() + \" -> 1_0\");\n                    }\n                }\n                Log.d(\"UriUtils\", uri.toString() + \" parse failed. -> 1_0\");\n                return null;\n            }// end 1_0\n            else if (\"com.android.providers.downloads.documents\".equals(authority)) {\n                String id = DocumentsContract.getDocumentId(uri);\n                if (TextUtils.isEmpty(id)) {\n                    Log.d(\"UriUtils\", uri.toString() + \" parse failed(id is null). -> 1_1\");\n                    return null;\n                }\n                if (id.startsWith(\"raw:\")) {\n                    return new File(id.substring(4));\n                } else if (id.startsWith(\"msf:\")) {\n                    id = id.split(\":\")[1];\n                }\n\n                long availableId = 0;\n                try {\n                    availableId = Long.parseLong(id);\n                } catch (Exception e) {\n                    return null;\n                }\n\n                String[] contentUriPrefixesToTry = new String[]{\n                        \"content://downloads/public_downloads\",\n                        \"content://downloads/all_downloads\",\n                        \"content://downloads/my_downloads\"\n                };\n\n                for (String contentUriPrefix : contentUriPrefixesToTry) {\n                    Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), availableId);\n                    try {\n                        File file = getFileFromUri(contentUri, \"1_1\");\n                        if (file != null) {\n                            return file;\n                        }\n                    } catch (Exception ignore) {\n                    }\n                }\n\n                Log.d(\"UriUtils\", uri.toString() + \" parse failed. -> 1_1\");\n                return null;\n            }// end 1_1\n            else if (\"com.android.providers.media.documents\".equals(authority)) {\n                final String docId = DocumentsContract.getDocumentId(uri);\n                final String[] split = docId.split(\":\");\n                final String type = split[0];\n                Uri contentUri;\n                if (\"image\".equals(type)) {\n                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;\n                } else if (\"video\".equals(type)) {\n                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;\n                } else if (\"audio\".equals(type)) {\n                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;\n                } else {\n                    Log.d(\"UriUtils\", uri.toString() + \" parse failed. -> 1_2\");\n                    return null;\n                }\n                final String selection = \"_id=?\";\n                final String[] selectionArgs = new String[]{split[1]};\n                return getFileFromUri(contentUri, selection, selectionArgs, \"1_2\");\n            }// end 1_2\n            else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {\n                return getFileFromUri(uri, \"1_3\");\n            }// end 1_3\n            else {\n                Log.d(\"UriUtils\", uri.toString() + \" parse failed. -> 1_4\");\n                return null;\n            }// end 1_4\n        }// end 1\n        else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {\n            return getFileFromUri(uri, \"2\");\n        }// end 2\n        else {\n            Log.d(\"UriUtils\", uri.toString() + \" parse failed. -> 3\");\n            return null;\n        }// end 3\n    }\n\n    private static File getFileFromUri(final Uri uri, final String code) {\n        return getFileFromUri(uri, null, null, code);\n    }\n\n    private static File getFileFromUri(final Uri uri,\n                                       final String selection,\n                                       final String[] selectionArgs,\n                                       final String code) {\n        if (\"com.google.android.apps.photos.content\".equals(uri.getAuthority())) {\n            if (!TextUtils.isEmpty(uri.getLastPathSegment())) {\n                return new File(uri.getLastPathSegment());\n            }\n        } else if (\"com.tencent.mtt.fileprovider\".equals(uri.getAuthority())) {\n            String path = uri.getPath();\n            if (!TextUtils.isEmpty(path)) {\n                File fileDir = Environment.getExternalStorageDirectory();\n                return new File(fileDir, path.substring(\"/QQBrowser\".length(), path.length()));\n            }\n        } else if (\"com.huawei.hidisk.fileprovider\".equals(uri.getAuthority())) {\n            String path = uri.getPath();\n            if (!TextUtils.isEmpty(path)) {\n                return new File(path.replace(\"/root\", \"\"));\n            }\n        }\n\n        final Cursor cursor = Utils.getApp().getContentResolver().query(\n                uri, new String[]{\"_data\"}, selection, selectionArgs, null);\n        if (cursor == null) {\n            Log.d(\"UriUtils\", uri.toString() + \" parse failed(cursor is null). -> \" + code);\n            return null;\n        }\n        try {\n            if (cursor.moveToFirst()) {\n                final int columnIndex = cursor.getColumnIndex(\"_data\");\n                if (columnIndex > -1) {\n                    return new File(cursor.getString(columnIndex));\n                } else {\n                    Log.d(\"UriUtils\", uri.toString() + \" parse failed(columnIndex: \" + columnIndex + \" is wrong). -> \" + code);\n                    return null;\n                }\n            } else {\n                Log.d(\"UriUtils\", uri.toString() + \" parse failed(moveToFirst return false). -> \" + code);\n                return null;\n            }\n        } catch (Exception e) {\n            Log.d(\"UriUtils\", uri.toString() + \" parse failed. -> \" + code);\n            return null;\n        } finally {\n            cursor.close();\n        }\n    }\n\n    private static File copyUri2Cache(Uri uri) {\n        Log.d(\"UriUtils\", \"copyUri2Cache() called\");\n        InputStream is = null;\n        try {\n            is = Utils.getApp().getContentResolver().openInputStream(uri);\n            File file = new File(Utils.getApp().getCacheDir(), \"\" + System.currentTimeMillis());\n            UtilsBridge.writeFileFromIS(file.getAbsolutePath(), is);\n            return file;\n        } catch (FileNotFoundException e) {\n            e.printStackTrace();\n            return null;\n        } finally {\n            if (is != null) {\n                try {\n                    is.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * uri to input stream.\n     *\n     * @param uri The uri.\n     * @return the input stream\n     */\n    public static byte[] uri2Bytes(Uri uri) {\n        if (uri == null) return null;\n        InputStream is = null;\n        try {\n            is = Utils.getApp().getContentResolver().openInputStream(uri);\n            return UtilsBridge.inputStream2Bytes(is);\n        } catch (FileNotFoundException e) {\n            e.printStackTrace();\n            Log.d(\"UriUtils\", \"uri to bytes failed.\");\n            return null;\n        } finally {\n            if (is != null) {\n                try {\n                    is.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/Utils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.app.Application;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.lifecycle.Lifecycle;\n\n/**\n * <pre>\n *     author:\n *                                      ___           ___           ___         ___\n *         _____                       /  /\\         /__/\\         /__/|       /  /\\\n *        /  /::\\                     /  /::\\        \\  \\:\\       |  |:|      /  /:/\n *       /  /:/\\:\\    ___     ___    /  /:/\\:\\        \\  \\:\\      |  |:|     /__/::\\\n *      /  /:/~/::\\  /__/\\   /  /\\  /  /:/~/::\\   _____\\__\\:\\   __|  |:|     \\__\\/\\:\\\n *     /__/:/ /:/\\:| \\  \\:\\ /  /:/ /__/:/ /:/\\:\\ /__/::::::::\\ /__/\\_|:|____    \\  \\:\\\n *     \\  \\:\\/:/~/:/  \\  \\:\\  /:/  \\  \\:\\/:/__\\/ \\  \\:\\~~\\~~\\/ \\  \\:\\/:::::/     \\__\\:\\\n *      \\  \\::/ /:/    \\  \\:\\/:/    \\  \\::/       \\  \\:\\  ~~~   \\  \\::/~~~~      /  /:/\n *       \\  \\:\\/:/      \\  \\::/      \\  \\:\\        \\  \\:\\        \\  \\:\\         /__/:/\n *        \\  \\::/        \\__\\/        \\  \\:\\        \\  \\:\\        \\  \\:\\        \\__\\/\n *         \\__\\/                       \\__\\/         \\__\\/         \\__\\/\n *     blog  : http://blankj.com\n *     time  : 16/12/08\n *     desc  : utils about initialization\n * </pre>\n */\npublic final class Utils {\n\n    @SuppressLint(\"StaticFieldLeak\")\n    private static Application sApp;\n\n    private Utils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Init utils.\n     * <p>Init it in the class of UtilsFileProvider.</p>\n     *\n     * @param app application\n     */\n    public static void init(final Application app) {\n        if (app == null) {\n            Log.e(\"Utils\", \"app is null.\");\n            return;\n        }\n        if (sApp == null) {\n            sApp = app;\n            UtilsBridge.init(sApp);\n            UtilsBridge.preLoad();\n            return;\n        }\n        if (sApp.equals(app)) return;\n        UtilsBridge.unInit(sApp);\n        sApp = app;\n        UtilsBridge.init(sApp);\n    }\n\n    /**\n     * Return the Application object.\n     * <p>Main process get app by UtilsFileProvider,\n     * and other process get app by reflect.</p>\n     *\n     * @return the Application object\n     */\n    public static Application getApp() {\n        if (sApp != null) return sApp;\n        init(UtilsBridge.getApplicationByReflect());\n        if (sApp == null) throw new NullPointerException(\"reflect failed.\");\n        Log.i(\"Utils\", UtilsBridge.getCurrentProcessName() + \" reflect app success.\");\n        return sApp;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // interface\n    ///////////////////////////////////////////////////////////////////////////\n\n    public abstract static class Task<Result> extends ThreadUtils.SimpleTask<Result> {\n\n        private Consumer<Result> mConsumer;\n\n        public Task(final Consumer<Result> consumer) {\n            mConsumer = consumer;\n        }\n\n        @Override\n        public void onSuccess(Result result) {\n            if (mConsumer != null) {\n                mConsumer.accept(result);\n            }\n        }\n    }\n\n    public interface OnAppStatusChangedListener {\n        void onForeground(Activity activity);\n\n        void onBackground(Activity activity);\n    }\n\n    public static class ActivityLifecycleCallbacks {\n\n        public void onActivityCreated(@NonNull Activity activity) {/**/}\n\n        public void onActivityStarted(@NonNull Activity activity) {/**/}\n\n        public void onActivityResumed(@NonNull Activity activity) {/**/}\n\n        public void onActivityPaused(@NonNull Activity activity) {/**/}\n\n        public void onActivityStopped(@NonNull Activity activity) {/**/}\n\n        public void onActivityDestroyed(@NonNull Activity activity) {/**/}\n\n        public void onLifecycleChanged(@NonNull Activity activity, Lifecycle.Event event) {/**/}\n    }\n\n    public interface Consumer<T> {\n        void accept(T t);\n    }\n\n    public interface Supplier<T> {\n        T get();\n    }\n\n    public interface Func1<Ret, Par> {\n        Ret call(Par param);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/UtilsActivityLifecycleImpl.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.animation.ValueAnimator;\nimport android.app.Activity;\nimport android.app.Application;\n\nimport androidx.annotation.NonNull;\nimport androidx.lifecycle.Lifecycle;\nimport android.os.Build;\nimport android.os.Bundle;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.Nullable;\nimport android.util.Log;\nimport android.view.Window;\nimport android.view.WindowManager;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * <pre>\n *     author: blankj\n *     blog  : http://blankj.com\n *     time  : 2020/03/19\n *     desc  :\n * </pre>\n */\nfinal class UtilsActivityLifecycleImpl implements Application.ActivityLifecycleCallbacks {\n\n    static final UtilsActivityLifecycleImpl INSTANCE = new UtilsActivityLifecycleImpl();\n\n    private final LinkedList<Activity> mActivityList = new LinkedList<>();\n\n    private final List<Utils.OnAppStatusChangedListener>                mStatusListeners               = new CopyOnWriteArrayList<>();\n    private final Map<Activity, List<Utils.ActivityLifecycleCallbacks>> mActivityLifecycleCallbacksMap = new ConcurrentHashMap<>();\n\n    private static final Activity STUB = new Activity();\n\n    private int     mForegroundCount = 0;\n    private int     mConfigCount     = 0;\n    private boolean mIsBackground    = false;\n\n    void init(Application app) {\n        app.registerActivityLifecycleCallbacks(this);\n    }\n\n    void unInit(Application app) {\n        mActivityList.clear();\n        app.unregisterActivityLifecycleCallbacks(this);\n    }\n\n    Activity getTopActivity() {\n        List<Activity> activityList = getActivityList();\n        for (Activity activity : activityList) {\n            if (!UtilsBridge.isActivityAlive(activity)) {\n                continue;\n            }\n            return activity;\n        }\n        return null;\n    }\n\n    List<Activity> getActivityList() {\n        if (!mActivityList.isEmpty()) {\n            return new LinkedList<>(mActivityList);\n        }\n        List<Activity> reflectActivities = getActivitiesByReflect();\n        mActivityList.addAll(reflectActivities);\n        return new LinkedList<>(mActivityList);\n    }\n\n    void addOnAppStatusChangedListener(final Utils.OnAppStatusChangedListener listener) {\n        mStatusListeners.add(listener);\n    }\n\n    void removeOnAppStatusChangedListener(final Utils.OnAppStatusChangedListener listener) {\n        mStatusListeners.remove(listener);\n    }\n\n    void addActivityLifecycleCallbacks(final Utils.ActivityLifecycleCallbacks listener) {\n        addActivityLifecycleCallbacks(STUB, listener);\n    }\n\n    void addActivityLifecycleCallbacks(final Activity activity,\n                                       final Utils.ActivityLifecycleCallbacks listener) {\n        if (activity == null || listener == null) return;\n        UtilsBridge.runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                addActivityLifecycleCallbacksInner(activity, listener);\n            }\n        });\n    }\n\n    boolean isAppForeground() {\n        return !mIsBackground;\n    }\n\n    private void addActivityLifecycleCallbacksInner(final Activity activity,\n                                                    final Utils.ActivityLifecycleCallbacks callbacks) {\n        List<Utils.ActivityLifecycleCallbacks> callbacksList = mActivityLifecycleCallbacksMap.get(activity);\n        if (callbacksList == null) {\n            callbacksList = new CopyOnWriteArrayList<>();\n            mActivityLifecycleCallbacksMap.put(activity, callbacksList);\n        } else {\n            if (callbacksList.contains(callbacks)) return;\n        }\n        callbacksList.add(callbacks);\n    }\n\n    void removeActivityLifecycleCallbacks(final Utils.ActivityLifecycleCallbacks callbacks) {\n        removeActivityLifecycleCallbacks(STUB, callbacks);\n    }\n\n    void removeActivityLifecycleCallbacks(final Activity activity) {\n        if (activity == null) return;\n        UtilsBridge.runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                mActivityLifecycleCallbacksMap.remove(activity);\n            }\n        });\n    }\n\n    void removeActivityLifecycleCallbacks(final Activity activity,\n                                          final Utils.ActivityLifecycleCallbacks callbacks) {\n        if (activity == null || callbacks == null) return;\n        UtilsBridge.runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                removeActivityLifecycleCallbacksInner(activity, callbacks);\n            }\n        });\n    }\n\n    private void removeActivityLifecycleCallbacksInner(final Activity activity,\n                                                       final Utils.ActivityLifecycleCallbacks callbacks) {\n        List<Utils.ActivityLifecycleCallbacks> callbacksList = mActivityLifecycleCallbacksMap.get(activity);\n        if (callbacksList != null && !callbacksList.isEmpty()) {\n            callbacksList.remove(callbacks);\n        }\n    }\n\n    private void consumeActivityLifecycleCallbacks(Activity activity, Lifecycle.Event event) {\n        consumeLifecycle(activity, event, mActivityLifecycleCallbacksMap.get(activity));\n        consumeLifecycle(activity, event, mActivityLifecycleCallbacksMap.get(STUB));\n    }\n\n    private void consumeLifecycle(Activity activity, Lifecycle.Event event, List<Utils.ActivityLifecycleCallbacks> listeners) {\n        if (listeners == null) return;\n        for (Utils.ActivityLifecycleCallbacks listener : listeners) {\n            listener.onLifecycleChanged(activity, event);\n            if (event.equals(Lifecycle.Event.ON_CREATE)) {\n                listener.onActivityCreated(activity);\n            } else if (event.equals(Lifecycle.Event.ON_START)) {\n                listener.onActivityStarted(activity);\n            } else if (event.equals(Lifecycle.Event.ON_RESUME)) {\n                listener.onActivityResumed(activity);\n            } else if (event.equals(Lifecycle.Event.ON_PAUSE)) {\n                listener.onActivityPaused(activity);\n            } else if (event.equals(Lifecycle.Event.ON_STOP)) {\n                listener.onActivityStopped(activity);\n            } else if (event.equals(Lifecycle.Event.ON_DESTROY)) {\n                listener.onActivityDestroyed(activity);\n            }\n        }\n        if (event.equals(Lifecycle.Event.ON_DESTROY)) {\n            mActivityLifecycleCallbacksMap.remove(activity);\n        }\n    }\n\n    Application getApplicationByReflect() {\n        try {\n            Class activityThreadClass = Class.forName(\"android.app.ActivityThread\");\n            Object thread = getActivityThread();\n            Object app = activityThreadClass.getMethod(\"getApplication\").invoke(thread);\n            if (app == null) {\n                return null;\n            }\n            return (Application) app;\n        } catch (InvocationTargetException e) {\n            e.printStackTrace();\n        } catch (NoSuchMethodException e) {\n            e.printStackTrace();\n        } catch (IllegalAccessException e) {\n            e.printStackTrace();\n        } catch (ClassNotFoundException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // lifecycle start\n    ///////////////////////////////////////////////////////////////////////////\n    @Override\n    public void onActivityPreCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {/**/}\n\n    @Override\n    public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) {\n        if (mActivityList.size() == 0) {\n            postStatus(activity, true);\n        }\n        LanguageUtils.applyLanguage(activity);\n        setAnimatorsEnabled();\n        setTopActivity(activity);\n        consumeActivityLifecycleCallbacks(activity, Lifecycle.Event.ON_CREATE);\n    }\n\n    @Override\n    public void onActivityPostCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {/**/}\n\n    @Override\n    public void onActivityPreStarted(@NonNull Activity activity) {/**/}\n\n    @Override\n    public void onActivityStarted(@NonNull Activity activity) {\n        if (!mIsBackground) {\n            setTopActivity(activity);\n        }\n        if (mConfigCount < 0) {\n            ++mConfigCount;\n        } else {\n            ++mForegroundCount;\n        }\n        consumeActivityLifecycleCallbacks(activity, Lifecycle.Event.ON_START);\n    }\n\n    @Override\n    public void onActivityPostStarted(@NonNull Activity activity) {/**/}\n\n    @Override\n    public void onActivityPreResumed(@NonNull Activity activity) {/**/}\n\n    @Override\n    public void onActivityResumed(@NonNull final Activity activity) {\n        setTopActivity(activity);\n        if (mIsBackground) {\n            mIsBackground = false;\n            postStatus(activity, true);\n        }\n        processHideSoftInputOnActivityDestroy(activity, false);\n        consumeActivityLifecycleCallbacks(activity, Lifecycle.Event.ON_RESUME);\n    }\n\n    @Override\n    public void onActivityPostResumed(@NonNull Activity activity) {/**/}\n\n    @Override\n    public void onActivityPrePaused(@NonNull Activity activity) {/**/}\n\n    @Override\n    public void onActivityPaused(@NonNull Activity activity) {\n        consumeActivityLifecycleCallbacks(activity, Lifecycle.Event.ON_PAUSE);\n    }\n\n    @Override\n    public void onActivityPostPaused(@NonNull Activity activity) {/**/}\n\n    @Override\n    public void onActivityPreStopped(@NonNull Activity activity) {/**/}\n\n    @Override\n    public void onActivityStopped(Activity activity) {\n        if (activity.isChangingConfigurations()) {\n            --mConfigCount;\n        } else {\n            --mForegroundCount;\n            if (mForegroundCount <= 0) {\n                mIsBackground = true;\n                postStatus(activity, false);\n            }\n        }\n        processHideSoftInputOnActivityDestroy(activity, true);\n        consumeActivityLifecycleCallbacks(activity, Lifecycle.Event.ON_STOP);\n    }\n\n    @Override\n    public void onActivityPostStopped(@NonNull Activity activity) {/**/}\n\n    @Override\n    public void onActivityPreSaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {/**/}\n\n    @Override\n    public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {/**/}\n\n    @Override\n    public void onActivityPostSaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {/**/}\n\n    @Override\n    public void onActivityPreDestroyed(@NonNull Activity activity) {/**/}\n\n    @Override\n    public void onActivityDestroyed(@NonNull Activity activity) {\n        mActivityList.remove(activity);\n        UtilsBridge.fixSoftInputLeaks(activity);\n        consumeActivityLifecycleCallbacks(activity, Lifecycle.Event.ON_DESTROY);\n    }\n\n    @Override\n    public void onActivityPostDestroyed(@NonNull Activity activity) {/**/}\n    ///////////////////////////////////////////////////////////////////////////\n    // lifecycle end\n    ///////////////////////////////////////////////////////////////////////////\n\n    /**\n     * To solve close keyboard when activity onDestroy.\n     * The preActivity set windowSoftInputMode will prevent\n     * the keyboard from closing when curActivity onDestroy.\n     */\n    private void processHideSoftInputOnActivityDestroy(final Activity activity, boolean isSave) {\n//        try {\n//            if (isSave) {\n//                Window window = activity.getWindow();\n//                final WindowManager.LayoutParams attrs = window.getAttributes();\n//                final int softInputMode = attrs.softInputMode;\n//                window.getDecorView().setTag(-123, softInputMode);\n//                window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);\n//            } else {\n//                final Object tag = activity.getWindow().getDecorView().getTag(-123);\n//                if (!(tag instanceof Integer)) return;\n//                UtilsBridge.runOnUiThreadDelayed(new Runnable() {\n//                    @Override\n//                    public void run() {\n//                        try {\n//                            Window window = activity.getWindow();\n//                            if (window != null) {\n//                                window.setSoftInputMode(((Integer) tag));\n//                            }\n//                        } catch (Exception ignore) {\n//                        }\n//                    }\n//                }, 100);\n//            }\n//        } catch (Exception ignore) {\n//        }\n    }\n\n    private void postStatus(final Activity activity, final boolean isForeground) {\n        if (mStatusListeners.isEmpty()) return;\n        for (Utils.OnAppStatusChangedListener statusListener : mStatusListeners) {\n            if (isForeground) {\n                statusListener.onForeground(activity);\n            } else {\n                statusListener.onBackground(activity);\n            }\n        }\n    }\n\n    private void setTopActivity(final Activity activity) {\n        if (mActivityList.contains(activity)) {\n            if (!mActivityList.getFirst().equals(activity)) {\n                mActivityList.remove(activity);\n                mActivityList.addFirst(activity);\n            }\n        } else {\n            mActivityList.addFirst(activity);\n        }\n    }\n\n    /**\n     * @return the activities which topActivity is first position\n     */\n    private List<Activity> getActivitiesByReflect() {\n        LinkedList<Activity> list = new LinkedList<>();\n        Activity topActivity = null;\n        try {\n            Object activityThread = getActivityThread();\n            Field mActivitiesField = activityThread.getClass().getDeclaredField(\"mActivities\");\n            mActivitiesField.setAccessible(true);\n            Object mActivities = mActivitiesField.get(activityThread);\n            if (!(mActivities instanceof Map)) {\n                return list;\n            }\n            Map<Object, Object> binder_activityClientRecord_map = (Map<Object, Object>) mActivities;\n            for (Object activityRecord : binder_activityClientRecord_map.values()) {\n                Class activityClientRecordClass = activityRecord.getClass();\n                Field activityField = activityClientRecordClass.getDeclaredField(\"activity\");\n                activityField.setAccessible(true);\n                Activity activity = (Activity) activityField.get(activityRecord);\n                if (topActivity == null) {\n                    Field pausedField = activityClientRecordClass.getDeclaredField(\"paused\");\n                    pausedField.setAccessible(true);\n                    if (!pausedField.getBoolean(activityRecord)) {\n                        topActivity = activity;\n                    } else {\n                        list.add(activity);\n                    }\n                } else {\n                    list.add(activity);\n                }\n            }\n        } catch (Exception e) {\n            Log.e(\"UtilsActivityLifecycle\", \"getActivitiesByReflect: \" + e.getMessage());\n        }\n        if (topActivity != null) {\n            list.addFirst(topActivity);\n        }\n        return list;\n    }\n\n    private Object getActivityThread() {\n        Object activityThread = getActivityThreadInActivityThreadStaticField();\n        if (activityThread != null) return activityThread;\n        return getActivityThreadInActivityThreadStaticMethod();\n    }\n\n    private Object getActivityThreadInActivityThreadStaticField() {\n        try {\n            Class activityThreadClass = Class.forName(\"android.app.ActivityThread\");\n            Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField(\"sCurrentActivityThread\");\n            sCurrentActivityThreadField.setAccessible(true);\n            return sCurrentActivityThreadField.get(null);\n        } catch (Exception e) {\n            Log.e(\"UtilsActivityLifecycle\", \"getActivityThreadInActivityThreadStaticField: \" + e.getMessage());\n            return null;\n        }\n    }\n\n    private Object getActivityThreadInActivityThreadStaticMethod() {\n        try {\n            Class activityThreadClass = Class.forName(\"android.app.ActivityThread\");\n            return activityThreadClass.getMethod(\"currentActivityThread\").invoke(null);\n        } catch (Exception e) {\n            Log.e(\"UtilsActivityLifecycle\", \"getActivityThreadInActivityThreadStaticMethod: \" + e.getMessage());\n            return null;\n        }\n    }\n\n    /**\n     * Set animators enabled.\n     */\n    private static void setAnimatorsEnabled() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && ValueAnimator.areAnimatorsEnabled()) {\n            return;\n        }\n        try {\n            //noinspection JavaReflectionMemberAccess\n            Field sDurationScaleField = ValueAnimator.class.getDeclaredField(\"sDurationScale\");\n            sDurationScaleField.setAccessible(true);\n            //noinspection ConstantConditions\n            float sDurationScale = (Float) sDurationScaleField.get(null);\n            if (sDurationScale == 0f) {\n                sDurationScaleField.set(null, 1f);\n                Log.i(\"UtilsActivityLifecycle\", \"setAnimatorsEnabled: Animators are enabled now!\");\n            }\n        } catch (NoSuchFieldException e) {\n            e.printStackTrace();\n        } catch (IllegalAccessException e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/UtilsBridge.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.app.Notification;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Parcelable;\nimport androidx.annotation.LayoutRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport androidx.annotation.RequiresPermission;\nimport androidx.annotation.StringRes;\nimport androidx.core.app.NotificationCompat;\nimport android.text.TextUtils;\nimport android.view.View;\n\n\nimport com.google.gson.Gson;\n\nimport org.json.JSONArray;\nimport org.json.JSONObject;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.InputStream;\nimport java.io.Serializable;\nimport java.lang.reflect.Type;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static android.Manifest.permission.CALL_PHONE;\n\n/**\n * <pre>\n *     author: blankj\n *     blog  : http://blankj.com\n *     time  : 2020/03/19\n *     desc  :\n * </pre>\n */\nclass UtilsBridge {\n\n    static void init(Application app) {\n        UtilsActivityLifecycleImpl.INSTANCE.init(app);\n    }\n\n    static void unInit(Application app) {\n        UtilsActivityLifecycleImpl.INSTANCE.unInit(app);\n    }\n\n    static void preLoad() {\n        preLoad(AdaptScreenUtils.getPreLoadRunnable());\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // UtilsActivityLifecycleImpl\n    ///////////////////////////////////////////////////////////////////////////\n    static Activity getTopActivity() {\n        return UtilsActivityLifecycleImpl.INSTANCE.getTopActivity();\n    }\n\n    static void addOnAppStatusChangedListener(final Utils.OnAppStatusChangedListener listener) {\n        UtilsActivityLifecycleImpl.INSTANCE.addOnAppStatusChangedListener(listener);\n    }\n\n    static void removeOnAppStatusChangedListener(final Utils.OnAppStatusChangedListener listener) {\n        UtilsActivityLifecycleImpl.INSTANCE.removeOnAppStatusChangedListener(listener);\n    }\n\n    static void addActivityLifecycleCallbacks(final Utils.ActivityLifecycleCallbacks callbacks) {\n        UtilsActivityLifecycleImpl.INSTANCE.addActivityLifecycleCallbacks(callbacks);\n    }\n\n    static void removeActivityLifecycleCallbacks(final Utils.ActivityLifecycleCallbacks callbacks) {\n        UtilsActivityLifecycleImpl.INSTANCE.removeActivityLifecycleCallbacks(callbacks);\n    }\n\n    static void addActivityLifecycleCallbacks(final Activity activity,\n                                              final Utils.ActivityLifecycleCallbacks callbacks) {\n        UtilsActivityLifecycleImpl.INSTANCE.addActivityLifecycleCallbacks(activity, callbacks);\n    }\n\n    static void removeActivityLifecycleCallbacks(final Activity activity) {\n        UtilsActivityLifecycleImpl.INSTANCE.removeActivityLifecycleCallbacks(activity);\n    }\n\n    static void removeActivityLifecycleCallbacks(final Activity activity,\n                                                 final Utils.ActivityLifecycleCallbacks callbacks) {\n        UtilsActivityLifecycleImpl.INSTANCE.removeActivityLifecycleCallbacks(activity, callbacks);\n    }\n\n    static List<Activity> getActivityList() {\n        return UtilsActivityLifecycleImpl.INSTANCE.getActivityList();\n    }\n\n    static Application getApplicationByReflect() {\n        return UtilsActivityLifecycleImpl.INSTANCE.getApplicationByReflect();\n    }\n\n    static boolean isAppForeground() {\n        return UtilsActivityLifecycleImpl.INSTANCE.isAppForeground();\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // ActivityUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static boolean isActivityAlive(final Activity activity) {\n        return ActivityUtils.isActivityAlive(activity);\n    }\n\n    static String getLauncherActivity(final String pkg) {\n        return ActivityUtils.getLauncherActivity(pkg);\n    }\n\n    static Activity getActivityByContext(Context context) {\n        return ActivityUtils.getActivityByContext(context);\n    }\n\n    static void startHomeActivity() {\n        ActivityUtils.startHomeActivity();\n    }\n\n    static void finishAllActivities() {\n        ActivityUtils.finishAllActivities();\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // AppUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static boolean isAppRunning(@NonNull final String pkgName) {\n        return AppUtils.isAppRunning(pkgName);\n    }\n\n    static boolean isAppInstalled(final String pkgName) {\n        return AppUtils.isAppInstalled(pkgName);\n    }\n\n    static boolean isAppDebug() {\n        return AppUtils.isAppDebug();\n    }\n\n    static void relaunchApp() {\n        AppUtils.relaunchApp();\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // BarUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static int getStatusBarHeight() {\n        return BarUtils.getStatusBarHeight();\n    }\n\n    static int getNavBarHeight() {\n        return BarUtils.getNavBarHeight();\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // ConvertUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static String bytes2HexString(final byte[] bytes) {\n        return ConvertUtils.bytes2HexString(bytes);\n    }\n\n    static byte[] hexString2Bytes(String hexString) {\n        return ConvertUtils.hexString2Bytes(hexString);\n    }\n\n    static byte[] string2Bytes(final String string) {\n        return ConvertUtils.string2Bytes(string);\n    }\n\n    static String bytes2String(final byte[] bytes) {\n        return ConvertUtils.bytes2String(bytes);\n    }\n\n    static byte[] jsonObject2Bytes(final JSONObject jsonObject) {\n        return ConvertUtils.jsonObject2Bytes(jsonObject);\n    }\n\n    static JSONObject bytes2JSONObject(final byte[] bytes) {\n        return ConvertUtils.bytes2JSONObject(bytes);\n    }\n\n    static byte[] jsonArray2Bytes(final JSONArray jsonArray) {\n        return ConvertUtils.jsonArray2Bytes(jsonArray);\n    }\n\n    static JSONArray bytes2JSONArray(final byte[] bytes) {\n        return ConvertUtils.bytes2JSONArray(bytes);\n    }\n\n    static byte[] parcelable2Bytes(final Parcelable parcelable) {\n        return ConvertUtils.parcelable2Bytes(parcelable);\n    }\n\n    static <T> T bytes2Parcelable(final byte[] bytes,\n                                  final Parcelable.Creator<T> creator) {\n        return ConvertUtils.bytes2Parcelable(bytes, creator);\n    }\n\n    static byte[] serializable2Bytes(final Serializable serializable) {\n        return ConvertUtils.serializable2Bytes(serializable);\n    }\n\n    static Object bytes2Object(final byte[] bytes) {\n        return ConvertUtils.bytes2Object(bytes);\n    }\n\n    static String byte2FitMemorySize(final long byteSize) {\n        return ConvertUtils.byte2FitMemorySize(byteSize);\n    }\n\n    static byte[] inputStream2Bytes(final InputStream is) {\n        return ConvertUtils.inputStream2Bytes(is);\n    }\n\n    static ByteArrayOutputStream input2OutputStream(final InputStream is) {\n        return ConvertUtils.input2OutputStream(is);\n    }\n\n    static List<String> inputStream2Lines(final InputStream is, final String charsetName) {\n        return ConvertUtils.inputStream2Lines(is, charsetName);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // DebouncingUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static boolean isValid(@NonNull final View view, final long duration) {\n        return DebouncingUtils.isValid(view, duration);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // EncodeUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static byte[] base64Encode(final byte[] input) {\n        return EncodeUtils.base64Encode(input);\n    }\n\n    static byte[] base64Decode(final byte[] input) {\n        return EncodeUtils.base64Decode(input);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // EncryptUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static byte[] hashTemplate(final byte[] data, final String algorithm) {\n        return EncryptUtils.hashTemplate(data, algorithm);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // FileIOUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static boolean writeFileFromBytes(final File file,\n                                      final byte[] bytes) {\n        return FileIOUtils.writeFileFromBytesByChannel(file, bytes, true);\n    }\n\n    static byte[] readFile2Bytes(final File file) {\n        return FileIOUtils.readFile2BytesByChannel(file);\n    }\n\n    static boolean writeFileFromString(final String filePath, final String content, final boolean append) {\n        return FileIOUtils.writeFileFromString(filePath, content, append);\n    }\n\n    static boolean writeFileFromIS(final String filePath, final InputStream is) {\n        return FileIOUtils.writeFileFromIS(filePath, is);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // FileUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static boolean isFileExists(final File file) {\n        return FileUtils.isFileExists(file);\n    }\n\n    static File getFileByPath(final String filePath) {\n        return FileUtils.getFileByPath(filePath);\n    }\n\n    static boolean deleteAllInDir(final File dir) {\n        return FileUtils.deleteAllInDir(dir);\n    }\n\n    static boolean createOrExistsFile(final File file) {\n        return FileUtils.createOrExistsFile(file);\n    }\n\n    static boolean createOrExistsDir(final File file) {\n        return FileUtils.createOrExistsDir(file);\n    }\n\n    static boolean createFileByDeleteOldFile(final File file) {\n        return FileUtils.createFileByDeleteOldFile(file);\n    }\n\n    static long getFsTotalSize(String path) {\n        return FileUtils.getFsTotalSize(path);\n    }\n\n    static long getFsAvailableSize(String path) {\n        return FileUtils.getFsAvailableSize(path);\n    }\n\n    static void notifySystemToScan(File file) {\n        FileUtils.notifySystemToScan(file);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // GsonUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static String toJson(final Object object) {\n        return GsonUtils.toJson(object);\n    }\n\n    static <T> T fromJson(final String json, final Type type) {\n        return GsonUtils.fromJson(json, type);\n    }\n\n    static Gson getGson4LogUtils() {\n        return GsonUtils.getGson4LogUtils();\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // ImageUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static byte[] bitmap2Bytes(final Bitmap bitmap) {\n        return ImageUtils.bitmap2Bytes(bitmap);\n    }\n\n    static byte[] bitmap2Bytes(final Bitmap bitmap, final Bitmap.CompressFormat format, int quality) {\n        return ImageUtils.bitmap2Bytes(bitmap, format, quality);\n    }\n\n    static Bitmap bytes2Bitmap(final byte[] bytes) {\n        return ImageUtils.bytes2Bitmap(bytes);\n    }\n\n    static byte[] drawable2Bytes(final Drawable drawable) {\n        return ImageUtils.drawable2Bytes(drawable);\n    }\n\n    static byte[] drawable2Bytes(final Drawable drawable, final Bitmap.CompressFormat format, int quality) {\n        return ImageUtils.drawable2Bytes(drawable, format, quality);\n    }\n\n    static Drawable bytes2Drawable(final byte[] bytes) {\n        return ImageUtils.bytes2Drawable(bytes);\n    }\n\n    static Bitmap view2Bitmap(final View view) {\n        return ImageUtils.view2Bitmap(view);\n    }\n\n    static Bitmap drawable2Bitmap(final Drawable drawable) {\n        return ImageUtils.drawable2Bitmap(drawable);\n    }\n\n    static Drawable bitmap2Drawable(final Bitmap bitmap) {\n        return ImageUtils.bitmap2Drawable(bitmap);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // IntentUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static boolean isIntentAvailable(final Intent intent) {\n        return IntentUtils.isIntentAvailable(intent);\n    }\n\n    static Intent getLaunchAppIntent(final String pkgName) {\n        return IntentUtils.getLaunchAppIntent(pkgName);\n    }\n\n    static Intent getInstallAppIntent(final File file) {\n        return IntentUtils.getInstallAppIntent(file);\n    }\n\n    static Intent getInstallAppIntent(final Uri uri) {\n        return IntentUtils.getInstallAppIntent(uri);\n    }\n\n    static Intent getUninstallAppIntent(final String pkgName) {\n        return IntentUtils.getUninstallAppIntent(pkgName);\n    }\n\n    static Intent getDialIntent(final String phoneNumber) {\n        return IntentUtils.getDialIntent(phoneNumber);\n    }\n\n    @RequiresPermission(CALL_PHONE)\n    static Intent getCallIntent(final String phoneNumber) {\n        return IntentUtils.getCallIntent(phoneNumber);\n    }\n\n    static Intent getSendSmsIntent(final String phoneNumber, final String content) {\n        return IntentUtils.getSendSmsIntent(phoneNumber, content);\n    }\n\n    static Intent getLaunchAppDetailsSettingsIntent(final String pkgName, final boolean isNewTask) {\n        return IntentUtils.getLaunchAppDetailsSettingsIntent(pkgName, isNewTask);\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // JsonUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static String formatJson(String json) {\n        return JsonUtils.formatJson(json);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // KeyboardUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static void fixSoftInputLeaks(final Activity activity) {\n        KeyboardUtils.fixSoftInputLeaks(activity);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // NotificationUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static Notification getNotification(NotificationUtils.ChannelConfig channelConfig,\n                                        Utils.Consumer<NotificationCompat.Builder> consumer) {\n        return NotificationUtils.getNotification(channelConfig, consumer);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // PermissionUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static boolean isGranted(final String... permissions) {\n        return PermissionUtils.isGranted(permissions);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    static boolean isGrantedDrawOverlays() {\n        return PermissionUtils.isGrantedDrawOverlays();\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // ProcessUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static boolean isMainProcess() {\n        return ProcessUtils.isMainProcess();\n    }\n\n    static String getForegroundProcessName() {\n        return ProcessUtils.getForegroundProcessName();\n    }\n\n    static String getCurrentProcessName() {\n        return ProcessUtils.getCurrentProcessName();\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // RomUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static boolean isSamsung() {\n        return RomUtils.isSamsung();\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // ScreenUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static int getAppScreenWidth() {\n        return ScreenUtils.getAppScreenWidth();\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // SDCardUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static boolean isSDCardEnableByEnvironment() {\n        return SDCardUtils.isSDCardEnableByEnvironment();\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // ServiceUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static boolean isServiceRunning(final String className) {\n        return ServiceUtils.isServiceRunning(className);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // ShellUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static ShellUtils.CommandResult execCmd(final String command, final boolean isRooted) {\n        return ShellUtils.execCmd(command, isRooted);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // SizeUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static int dp2px(final float dpValue) {\n        return SizeUtils.dp2px(dpValue);\n    }\n\n    static int px2dp(final float pxValue) {\n        return SizeUtils.px2dp(pxValue);\n    }\n\n    static int sp2px(final float spValue) {\n        return SizeUtils.sp2px(spValue);\n    }\n\n    static int px2sp(final float pxValue) {\n        return SizeUtils.px2sp(pxValue);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // SpUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static SPUtils getSpUtils4Utils() {\n        return SPUtils.getInstance(\"Utils\");\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // StringUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static boolean isSpace(final String s) {\n        return StringUtils.isSpace(s);\n    }\n\n    static boolean equals(final CharSequence s1, final CharSequence s2) {\n        return StringUtils.equals(s1, s2);\n    }\n\n    static String getString(@StringRes int id) {\n        return StringUtils.getString(id);\n    }\n\n    static String getString(@StringRes int id, Object... formatArgs) {\n        return StringUtils.getString(id, formatArgs);\n    }\n\n    static String format(@Nullable String str, Object... args) {\n        return StringUtils.format(str, args);\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // ThreadUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static <T> Utils.Task<T> doAsync(final Utils.Task<T> task) {\n        ThreadUtils.getCachedPool().execute(task);\n        return task;\n    }\n\n    static void runOnUiThread(final Runnable runnable) {\n        ThreadUtils.runOnUiThread(runnable);\n    }\n\n    static void runOnUiThreadDelayed(final Runnable runnable, long delayMillis) {\n        ThreadUtils.runOnUiThreadDelayed(runnable, delayMillis);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // ThrowableUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static String getFullStackTrace(Throwable throwable) {\n        return ThrowableUtils.getFullStackTrace(throwable);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // TimeUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static String millis2FitTimeSpan(long millis, int precision) {\n        return TimeUtils.millis2FitTimeSpan(millis, precision);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // ToastUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static void toastShowShort(final CharSequence text) {\n        ToastUtils.showShort(text);\n    }\n\n    static void toastCancel() {\n        ToastUtils.cancel();\n    }\n\n    private static void preLoad(final Runnable... runs) {\n        for (final Runnable r : runs) {\n            ThreadUtils.getCachedPool().execute(r);\n        }\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // UriUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static Uri file2Uri(final File file) {\n        return UriUtils.file2Uri(file);\n    }\n\n    static File uri2File(final Uri uri) {\n        return UriUtils.uri2File(uri);\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // ViewUtils\n    ///////////////////////////////////////////////////////////////////////////\n    static View layoutId2View(@LayoutRes final int layoutId) {\n        return ViewUtils.layoutId2View(layoutId);\n    }\n\n    static boolean isLayoutRtl() {\n        return ViewUtils.isLayoutRtl();\n    }\n\n\n    ///////////////////////////////////////////////////////////////////////////\n    // Common\n    ///////////////////////////////////////////////////////////////////////////\n    static final class FileHead {\n\n        private String                        mName;\n        private LinkedHashMap<String, String> mFirst = new LinkedHashMap<>();\n        private LinkedHashMap<String, String> mLast  = new LinkedHashMap<>();\n\n        FileHead(String name) {\n            mName = name;\n        }\n\n        void addFirst(String key, String value) {\n            append2Host(mFirst, key, value);\n        }\n\n        void append(Map<String, String> extra) {\n            append2Host(mLast, extra);\n        }\n\n        void append(String key, String value) {\n            append2Host(mLast, key, value);\n        }\n\n        private void append2Host(Map<String, String> host, Map<String, String> extra) {\n            if (extra == null || extra.isEmpty()) {\n                return;\n            }\n            for (Map.Entry<String, String> entry : extra.entrySet()) {\n                append2Host(host, entry.getKey(), entry.getValue());\n            }\n        }\n\n        private void append2Host(Map<String, String> host, String key, String value) {\n            if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) {\n                return;\n            }\n            int delta = 19 - key.length(); // 19 is length of \"Device Manufacturer\"\n            if (delta > 0) {\n                key = key + \"                   \".substring(0, delta);\n            }\n            host.put(key, value);\n        }\n\n        public String getAppended() {\n            StringBuilder sb = new StringBuilder();\n            for (Map.Entry<String, String> entry : mLast.entrySet()) {\n                sb.append(entry.getKey()).append(\": \").append(entry.getValue()).append(\"\\n\");\n            }\n            return sb.toString();\n        }\n\n        @Override\n        public String toString() {\n            StringBuilder sb = new StringBuilder();\n            String border = \"************* \" + mName + \" Head ****************\\n\";\n            sb.append(border);\n            for (Map.Entry<String, String> entry : mFirst.entrySet()) {\n                sb.append(entry.getKey()).append(\": \").append(entry.getValue()).append(\"\\n\");\n            }\n\n            sb.append(\"Rom Info           : \").append(RomUtils.getRomInfo()).append(\"\\n\");\n            sb.append(\"Device Manufacturer: \").append(Build.MANUFACTURER).append(\"\\n\");\n            sb.append(\"Device Model       : \").append(Build.MODEL).append(\"\\n\");\n            sb.append(\"Android Version    : \").append(Build.VERSION.RELEASE).append(\"\\n\");\n            sb.append(\"Android SDK        : \").append(Build.VERSION.SDK_INT).append(\"\\n\");\n            sb.append(\"App VersionName    : \").append(AppUtils.getAppVersionName()).append(\"\\n\");\n            sb.append(\"App VersionCode    : \").append(AppUtils.getAppVersionCode()).append(\"\\n\");\n\n            sb.append(getAppended());\n            return sb.append(border).append(\"\\n\").toString();\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/UtilsFileProvider.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.Application;\nimport androidx.core.content.FileProvider;\n\n/**\n * <pre>\n *     author: blankj\n *     blog  : http://blankj.com\n *     time  : 2020/03/19\n *     desc  :\n * </pre>\n */\npublic class UtilsFileProvider extends FileProvider {\n\n    @Override\n    public boolean onCreate() {\n        //noinspection ConstantConditions\n        Utils.init((Application) getContext().getApplicationContext());\n        return true;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/UtilsTransActivity.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\nimport android.view.MotionEvent;\n\nimport java.io.Serializable;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * <pre>\n *     author: blankj\n *     blog  : http://blankj.com\n *     time  : 2020/03/19\n *     desc  :\n * </pre>\n */\npublic class UtilsTransActivity extends AppCompatActivity {\n\n    private static final Map<UtilsTransActivity, TransActivityDelegate> CALLBACK_MAP = new HashMap<>();\n\n    protected static final String EXTRA_DELEGATE = \"extra_delegate\";\n\n    public static void start(final TransActivityDelegate delegate) {\n        start(null, null, delegate, UtilsTransActivity.class);\n    }\n\n    public static void start(final Utils.Consumer<Intent> consumer,\n                             final TransActivityDelegate delegate) {\n        start(null, consumer, delegate, UtilsTransActivity.class);\n    }\n\n    public static void start(final Activity activity,\n                             final TransActivityDelegate delegate) {\n        start(activity, null, delegate, UtilsTransActivity.class);\n    }\n\n    public static void start(final Activity activity,\n                             final Utils.Consumer<Intent> consumer,\n                             final TransActivityDelegate delegate) {\n        start(activity, consumer, delegate, UtilsTransActivity.class);\n    }\n\n    protected static void start(final Activity activity,\n                                final Utils.Consumer<Intent> consumer,\n                                final TransActivityDelegate delegate,\n                                final Class<?> cls) {\n        if (delegate == null) return;\n        Intent starter = new Intent(Utils.getApp(), cls);\n        starter.putExtra(EXTRA_DELEGATE, delegate);\n        if (consumer != null) {\n            consumer.accept(starter);\n        }\n        if (activity == null) {\n            starter.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            Utils.getApp().startActivity(starter);\n        } else {\n            activity.startActivity(starter);\n        }\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        overridePendingTransition(0, 0);\n        Serializable extra = getIntent().getSerializableExtra(EXTRA_DELEGATE);\n        if (!(extra instanceof TransActivityDelegate)) {\n            super.onCreate(savedInstanceState);\n            finish();\n            return;\n        }\n        TransActivityDelegate delegate = (TransActivityDelegate) extra;\n        CALLBACK_MAP.put(this, delegate);\n        delegate.onCreateBefore(this, savedInstanceState);\n        super.onCreate(savedInstanceState);\n        delegate.onCreated(this, savedInstanceState);\n    }\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n        TransActivityDelegate callback = CALLBACK_MAP.get(this);\n        if (callback == null) return;\n        callback.onStarted(this);\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        TransActivityDelegate callback = CALLBACK_MAP.get(this);\n        if (callback == null) return;\n        callback.onResumed(this);\n    }\n\n    @Override\n    protected void onPause() {\n        overridePendingTransition(0, 0);\n        super.onPause();\n        TransActivityDelegate callback = CALLBACK_MAP.get(this);\n        if (callback == null) return;\n        callback.onPaused(this);\n    }\n\n    @Override\n    protected void onStop() {\n        super.onStop();\n        TransActivityDelegate callback = CALLBACK_MAP.get(this);\n        if (callback == null) return;\n        callback.onStopped(this);\n    }\n\n    @Override\n    protected void onSaveInstanceState(Bundle outState) {\n        super.onSaveInstanceState(outState);\n        TransActivityDelegate callback = CALLBACK_MAP.get(this);\n        if (callback == null) return;\n        callback.onSaveInstanceState(this, outState);\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        TransActivityDelegate callback = CALLBACK_MAP.get(this);\n        if (callback == null) return;\n        callback.onDestroy(this);\n        CALLBACK_MAP.remove(this);\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n        TransActivityDelegate callback = CALLBACK_MAP.get(this);\n        if (callback == null) return;\n        callback.onRequestPermissionsResult(this, requestCode, permissions, grantResults);\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        TransActivityDelegate callback = CALLBACK_MAP.get(this);\n        if (callback == null) return;\n        callback.onActivityResult(this, requestCode, resultCode, data);\n    }\n\n    @Override\n    public boolean dispatchTouchEvent(MotionEvent ev) {\n        TransActivityDelegate callback = CALLBACK_MAP.get(this);\n        if (callback == null) return super.dispatchTouchEvent(ev);\n        if (callback.dispatchTouchEvent(this, ev)) {\n            return true;\n        }\n        return super.dispatchTouchEvent(ev);\n    }\n\n    public abstract static class TransActivityDelegate implements Serializable {\n        public void onCreateBefore(@NonNull UtilsTransActivity activity, @Nullable Bundle savedInstanceState) {/**/}\n\n        public void onCreated(@NonNull UtilsTransActivity activity, @Nullable Bundle savedInstanceState) {/**/}\n\n        public void onStarted(@NonNull UtilsTransActivity activity) {/**/}\n\n        public void onDestroy(@NonNull UtilsTransActivity activity) {/**/}\n\n        public void onResumed(@NonNull UtilsTransActivity activity) {/**/}\n\n        public void onPaused(@NonNull UtilsTransActivity activity) {/**/}\n\n        public void onStopped(@NonNull UtilsTransActivity activity) {/**/}\n\n        public void onSaveInstanceState(@NonNull UtilsTransActivity activity, Bundle outState) {/**/}\n\n        public void onRequestPermissionsResult(@NonNull UtilsTransActivity activity, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {/**/}\n\n        public void onActivityResult(@NonNull UtilsTransActivity activity, int requestCode, int resultCode, Intent data) {/**/}\n\n        public boolean dispatchTouchEvent(@NonNull UtilsTransActivity activity, MotionEvent ev) {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/UtilsTransActivity4MainProcess.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.app.Activity;\nimport android.content.Intent;\n\n/**\n * <pre>\n *     author: blankj\n *     blog  : http://blankj.com\n *     time  : 2020/03/19\n *     desc  :\n * </pre>\n */\npublic class UtilsTransActivity4MainProcess extends UtilsTransActivity {\n\n    public static void start(final TransActivityDelegate delegate) {\n        start(null, null, delegate, UtilsTransActivity4MainProcess.class);\n    }\n\n    public static void start(final Utils.Consumer<Intent> consumer,\n                             final TransActivityDelegate delegate) {\n        start(null, consumer, delegate, UtilsTransActivity4MainProcess.class);\n    }\n\n    public static void start(final Activity activity,\n                             final TransActivityDelegate delegate) {\n        start(activity, null, delegate, UtilsTransActivity4MainProcess.class);\n    }\n\n    public static void start(final Activity activity,\n                             final Utils.Consumer<Intent> consumer,\n                             final TransActivityDelegate delegate) {\n        start(activity, consumer, delegate, UtilsTransActivity4MainProcess.class);\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/VibrateUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.Context;\nimport android.os.Vibrator;\nimport androidx.annotation.RequiresPermission;\n\nimport static android.Manifest.permission.VIBRATE;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/09/29\n *     desc  : utils about vibrate\n * </pre>\n */\npublic final class VibrateUtils {\n\n    private static Vibrator vibrator;\n\n    private VibrateUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Vibrate.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.VIBRATE\" />}</p>\n     *\n     * @param milliseconds The number of milliseconds to vibrate.\n     */\n    @RequiresPermission(VIBRATE)\n    public static void vibrate(final long milliseconds) {\n        Vibrator vibrator = getVibrator();\n        if (vibrator == null) return;\n        vibrator.vibrate(milliseconds);\n    }\n\n    /**\n     * Vibrate.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.VIBRATE\" />}</p>\n     *\n     * @param pattern An array of longs of times for which to turn the vibrator on or off.\n     * @param repeat  The index into pattern at which to repeat, or -1 if you don't want to repeat.\n     */\n    @RequiresPermission(VIBRATE)\n    public static void vibrate(final long[] pattern, final int repeat) {\n        Vibrator vibrator = getVibrator();\n        if (vibrator == null) return;\n        vibrator.vibrate(pattern, repeat);\n    }\n\n    /**\n     * Cancel vibrate.\n     * <p>Must hold {@code <uses-permission android:name=\"android.permission.VIBRATE\" />}</p>\n     */\n    @RequiresPermission(VIBRATE)\n    public static void cancel() {\n        Vibrator vibrator = getVibrator();\n        if (vibrator == null) return;\n        vibrator.cancel();\n    }\n\n    private static Vibrator getVibrator() {\n        if (vibrator == null) {\n            vibrator = (Vibrator) Utils.getApp().getSystemService(Context.VIBRATOR_SERVICE);\n        }\n        return vibrator;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ViewUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.Context;\nimport android.os.Build;\nimport androidx.annotation.LayoutRes;\nimport android.text.TextUtils;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport java.util.Locale;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2019/06/18\n *     desc  : utils about view\n * </pre>\n */\npublic class ViewUtils {\n\n    /**\n     * Set the enabled state of this view.\n     *\n     * @param view    The view.\n     * @param enabled True to enabled, false otherwise.\n     */\n    public static void setViewEnabled(View view, boolean enabled) {\n        setViewEnabled(view, enabled, (View) null);\n    }\n\n    /**\n     * Set the enabled state of this view.\n     *\n     * @param view     The view.\n     * @param enabled  True to enabled, false otherwise.\n     * @param excludes The excludes.\n     */\n    public static void setViewEnabled(View view, boolean enabled, View... excludes) {\n        if (view == null) return;\n        if (excludes != null) {\n            for (View exclude : excludes) {\n                if (view == exclude) return;\n            }\n        }\n        if (view instanceof ViewGroup) {\n            ViewGroup viewGroup = (ViewGroup) view;\n            int childCount = viewGroup.getChildCount();\n            for (int i = 0; i < childCount; i++) {\n                setViewEnabled(viewGroup.getChildAt(i), enabled, excludes);\n            }\n        }\n        view.setEnabled(enabled);\n    }\n\n    /**\n     * @param runnable The runnable\n     */\n    public static void runOnUiThread(final Runnable runnable) {\n        UtilsBridge.runOnUiThread(runnable);\n    }\n\n    /**\n     * @param runnable    The runnable.\n     * @param delayMillis The delay (in milliseconds) until the Runnable will be executed.\n     */\n    public static void runOnUiThreadDelayed(final Runnable runnable, long delayMillis) {\n        UtilsBridge.runOnUiThreadDelayed(runnable, delayMillis);\n    }\n\n    /**\n     * Return whether horizontal layout direction of views are from Right to Left.\n     *\n     * @return {@code true}: yes<br>{@code false}: no\n     */\n    public static boolean isLayoutRtl() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            Locale primaryLocale;\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                primaryLocale = Utils.getApp().getResources().getConfiguration().getLocales().get(0);\n            } else {\n                primaryLocale = Utils.getApp().getResources().getConfiguration().locale;\n            }\n            return TextUtils.getLayoutDirectionFromLocale(primaryLocale) == View.LAYOUT_DIRECTION_RTL;\n        }\n        return false;\n    }\n\n    /**\n     * Fix the problem of topping the ScrollView nested ListView/GridView/WebView/RecyclerView.\n     *\n     * @param view The root view inner of ScrollView.\n     */\n    public static void fixScrollViewTopping(View view) {\n        view.setFocusable(false);\n        ViewGroup viewGroup = null;\n        if (view instanceof ViewGroup) {\n            viewGroup = (ViewGroup) view;\n        }\n        if (viewGroup == null) {\n            return;\n        }\n        for (int i = 0, n = viewGroup.getChildCount(); i < n; i++) {\n            View childAt = viewGroup.getChildAt(i);\n            childAt.setFocusable(false);\n            if (childAt instanceof ViewGroup) {\n                fixScrollViewTopping(childAt);\n            }\n        }\n    }\n\n    public static View layoutId2View(@LayoutRes final int layoutId) {\n        LayoutInflater inflate =\n                (LayoutInflater) Utils.getApp().getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n        return inflate.inflate(layoutId, null);\n    }\n}"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/VolumeUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.content.Context;\nimport android.media.AudioManager;\nimport android.os.Build;\n\n/**\n * <pre>\n *     author: blankj\n *     blog  : http://blankj.com\n *     time  : 2020/09/08\n *     desc  : utils about volume\n * </pre>\n */\npublic class VolumeUtils {\n\n    /**\n     * Return the volume.\n     *\n     * @param streamType The stream type.\n     *                   <ul>\n     *                   <li>{@link AudioManager#STREAM_VOICE_CALL}</li>\n     *                   <li>{@link AudioManager#STREAM_SYSTEM}</li>\n     *                   <li>{@link AudioManager#STREAM_RING}</li>\n     *                   <li>{@link AudioManager#STREAM_MUSIC}</li>\n     *                   <li>{@link AudioManager#STREAM_ALARM}</li>\n     *                   <li>{@link AudioManager#STREAM_NOTIFICATION}</li>\n     *                   <li>{@link AudioManager#STREAM_DTMF}</li>\n     *                   <li>{@link AudioManager#STREAM_ACCESSIBILITY}</li>\n     *                   </ul>\n     * @return the volume\n     */\n    public static int getVolume(int streamType) {\n        AudioManager am = (AudioManager) Utils.getApp().getSystemService(Context.AUDIO_SERVICE);\n        //noinspection ConstantConditions\n        return am.getStreamVolume(streamType);\n    }\n\n    /**\n     * Sets media volume.<br>\n     * When setting the value of parameter 'volume' greater than the maximum value of the media volume will not either cause error or throw exception but maximize the media volume.<br>\n     * Setting the value of volume lower than 0 will minimize the media volume.\n     *\n     * @param streamType The stream type.\n     *                   <ul>\n     *                   <li>{@link AudioManager#STREAM_VOICE_CALL}</li>\n     *                   <li>{@link AudioManager#STREAM_SYSTEM}</li>\n     *                   <li>{@link AudioManager#STREAM_RING}</li>\n     *                   <li>{@link AudioManager#STREAM_MUSIC}</li>\n     *                   <li>{@link AudioManager#STREAM_ALARM}</li>\n     *                   <li>{@link AudioManager#STREAM_NOTIFICATION}</li>\n     *                   <li>{@link AudioManager#STREAM_DTMF}</li>\n     *                   <li>{@link AudioManager#STREAM_ACCESSIBILITY}</li>\n     *                   </ul>\n     * @param volume     The volume.\n     * @param flags      The flags.\n     *                   <ul>\n     *                   <li>{@link AudioManager#FLAG_SHOW_UI}</li>\n     *                   <li>{@link AudioManager#FLAG_ALLOW_RINGER_MODES}</li>\n     *                   <li>{@link AudioManager#FLAG_PLAY_SOUND}</li>\n     *                   <li>{@link AudioManager#FLAG_REMOVE_SOUND_AND_VIBRATE}</li>\n     *                   <li>{@link AudioManager#FLAG_VIBRATE}</li>\n     *                   </ul>\n     */\n    public static void setVolume(int streamType, int volume, int flags) {\n        AudioManager am = (AudioManager) Utils.getApp().getSystemService(Context.AUDIO_SERVICE);\n        try {\n            //noinspection ConstantConditions\n            am.setStreamVolume(streamType, volume, flags);\n        } catch (SecurityException ignore) {\n        }\n    }\n\n    /**\n     * Return the maximum volume.\n     *\n     * @param streamType The stream type.\n     *                   <ul>\n     *                   <li>{@link AudioManager#STREAM_VOICE_CALL}</li>\n     *                   <li>{@link AudioManager#STREAM_SYSTEM}</li>\n     *                   <li>{@link AudioManager#STREAM_RING}</li>\n     *                   <li>{@link AudioManager#STREAM_MUSIC}</li>\n     *                   <li>{@link AudioManager#STREAM_ALARM}</li>\n     *                   <li>{@link AudioManager#STREAM_NOTIFICATION}</li>\n     *                   <li>{@link AudioManager#STREAM_DTMF}</li>\n     *                   <li>{@link AudioManager#STREAM_ACCESSIBILITY}</li>\n     *                   </ul>\n     * @return the maximum volume\n     */\n    public static int getMaxVolume(int streamType) {\n        AudioManager am = (AudioManager) Utils.getApp().getSystemService(Context.AUDIO_SERVICE);\n        //noinspection ConstantConditions\n        return am.getStreamMaxVolume(streamType);\n    }\n\n    /**\n     * Return the minimum volume.\n     *\n     * @param streamType The stream type.\n     *                   <ul>\n     *                   <li>{@link AudioManager#STREAM_VOICE_CALL}</li>\n     *                   <li>{@link AudioManager#STREAM_SYSTEM}</li>\n     *                   <li>{@link AudioManager#STREAM_RING}</li>\n     *                   <li>{@link AudioManager#STREAM_MUSIC}</li>\n     *                   <li>{@link AudioManager#STREAM_ALARM}</li>\n     *                   <li>{@link AudioManager#STREAM_NOTIFICATION}</li>\n     *                   <li>{@link AudioManager#STREAM_DTMF}</li>\n     *                   <li>{@link AudioManager#STREAM_ACCESSIBILITY}</li>\n     *                   </ul>\n     * @return the minimum volume\n     */\n    public static int getMinVolume(int streamType) {\n        AudioManager am = (AudioManager) Utils.getApp().getSystemService(Context.AUDIO_SERVICE);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n            //noinspection ConstantConditions\n            return am.getStreamMinVolume(streamType);\n        }\n        return 0;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/ZipUtils.java",
    "content": "package com.didichuxing.doraemonkit.util;\n\nimport android.util.Log;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Enumeration;\nimport java.util.List;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\nimport java.util.zip.ZipOutputStream;\n\n/**\n * <pre>\n *     author: Blankj\n *     blog  : http://blankj.com\n *     time  : 2016/08/27\n *     desc  : utils about zip\n * </pre>\n */\npublic final class ZipUtils {\n\n    private static final int BUFFER_LEN = 8192;\n\n    private ZipUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * Zip the files.\n     *\n     * @param srcFiles    The source of files.\n     * @param zipFilePath The path of ZIP file.\n     * @return {@code true}: success<br>{@code false}: fail\n     * @throws IOException if an I/O error has occurred\n     */\n    public static boolean zipFiles(final Collection<String> srcFiles,\n                                   final String zipFilePath)\n            throws IOException {\n        return zipFiles(srcFiles, zipFilePath, null);\n    }\n\n    /**\n     * Zip the files.\n     *\n     * @param srcFilePaths The paths of source files.\n     * @param zipFilePath  The path of ZIP file.\n     * @param comment      The comment.\n     * @return {@code true}: success<br>{@code false}: fail\n     * @throws IOException if an I/O error has occurred\n     */\n    public static boolean zipFiles(final Collection<String> srcFilePaths,\n                                   final String zipFilePath,\n                                   final String comment)\n            throws IOException {\n        if (srcFilePaths == null || zipFilePath == null) return false;\n        ZipOutputStream zos = null;\n        try {\n            zos = new ZipOutputStream(new FileOutputStream(zipFilePath));\n            for (String srcFile : srcFilePaths) {\n                if (!zipFile(UtilsBridge.getFileByPath(srcFile), \"\", zos, comment)) return false;\n            }\n            return true;\n        } finally {\n            if (zos != null) {\n                zos.finish();\n                zos.close();\n            }\n        }\n    }\n\n    /**\n     * Zip the files.\n     *\n     * @param srcFiles The source of files.\n     * @param zipFile  The ZIP file.\n     * @return {@code true}: success<br>{@code false}: fail\n     * @throws IOException if an I/O error has occurred\n     */\n    public static boolean zipFiles(final Collection<File> srcFiles, final File zipFile)\n            throws IOException {\n        return zipFiles(srcFiles, zipFile, null);\n    }\n\n    /**\n     * Zip the files.\n     *\n     * @param srcFiles The source of files.\n     * @param zipFile  The ZIP file.\n     * @param comment  The comment.\n     * @return {@code true}: success<br>{@code false}: fail\n     * @throws IOException if an I/O error has occurred\n     */\n    public static boolean zipFiles(final Collection<File> srcFiles,\n                                   final File zipFile,\n                                   final String comment)\n            throws IOException {\n        if (srcFiles == null || zipFile == null) return false;\n        ZipOutputStream zos = null;\n        try {\n            zos = new ZipOutputStream(new FileOutputStream(zipFile));\n            for (File srcFile : srcFiles) {\n                if (!zipFile(srcFile, \"\", zos, comment)) return false;\n            }\n            return true;\n        } finally {\n            if (zos != null) {\n                zos.finish();\n                zos.close();\n            }\n        }\n    }\n\n    /**\n     * Zip the file.\n     *\n     * @param srcFilePath The path of source file.\n     * @param zipFilePath The path of ZIP file.\n     * @return {@code true}: success<br>{@code false}: fail\n     * @throws IOException if an I/O error has occurred\n     */\n    public static boolean zipFile(final String srcFilePath,\n                                  final String zipFilePath)\n            throws IOException {\n        return zipFile(UtilsBridge.getFileByPath(srcFilePath), UtilsBridge.getFileByPath(zipFilePath), null);\n    }\n\n    /**\n     * Zip the file.\n     *\n     * @param srcFilePath The path of source file.\n     * @param zipFilePath The path of ZIP file.\n     * @param comment     The comment.\n     * @return {@code true}: success<br>{@code false}: fail\n     * @throws IOException if an I/O error has occurred\n     */\n    public static boolean zipFile(final String srcFilePath,\n                                  final String zipFilePath,\n                                  final String comment)\n            throws IOException {\n        return zipFile(UtilsBridge.getFileByPath(srcFilePath), UtilsBridge.getFileByPath(zipFilePath), comment);\n    }\n\n    /**\n     * Zip the file.\n     *\n     * @param srcFile The source of file.\n     * @param zipFile The ZIP file.\n     * @return {@code true}: success<br>{@code false}: fail\n     * @throws IOException if an I/O error has occurred\n     */\n    public static boolean zipFile(final File srcFile,\n                                  final File zipFile)\n            throws IOException {\n        return zipFile(srcFile, zipFile, null);\n    }\n\n    /**\n     * Zip the file.\n     *\n     * @param srcFile The source of file.\n     * @param zipFile The ZIP file.\n     * @param comment The comment.\n     * @return {@code true}: success<br>{@code false}: fail\n     * @throws IOException if an I/O error has occurred\n     */\n    public static boolean zipFile(final File srcFile,\n                                  final File zipFile,\n                                  final String comment)\n            throws IOException {\n        if (srcFile == null || zipFile == null) return false;\n        ZipOutputStream zos = null;\n        try {\n            zos = new ZipOutputStream(new FileOutputStream(zipFile));\n            return zipFile(srcFile, \"\", zos, comment);\n        } finally {\n            if (zos != null) {\n                zos.close();\n            }\n        }\n    }\n\n    private static boolean zipFile(final File srcFile,\n                                   String rootPath,\n                                   final ZipOutputStream zos,\n                                   final String comment)\n            throws IOException {\n        rootPath = rootPath + (UtilsBridge.isSpace(rootPath) ? \"\" : File.separator) + srcFile.getName();\n        if (srcFile.isDirectory()) {\n            File[] fileList = srcFile.listFiles();\n            if (fileList == null || fileList.length <= 0) {\n                ZipEntry entry = new ZipEntry(rootPath + '/');\n                entry.setComment(comment);\n                zos.putNextEntry(entry);\n                zos.closeEntry();\n            } else {\n                for (File file : fileList) {\n                    if (!zipFile(file, rootPath, zos, comment)) return false;\n                }\n            }\n        } else {\n            InputStream is = null;\n            try {\n                is = new BufferedInputStream(new FileInputStream(srcFile));\n                ZipEntry entry = new ZipEntry(rootPath);\n                entry.setComment(comment);\n                zos.putNextEntry(entry);\n                byte buffer[] = new byte[BUFFER_LEN];\n                int len;\n                while ((len = is.read(buffer, 0, BUFFER_LEN)) != -1) {\n                    zos.write(buffer, 0, len);\n                }\n                zos.closeEntry();\n            } finally {\n                if (is != null) {\n                    is.close();\n                }\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Unzip the file.\n     *\n     * @param zipFilePath The path of ZIP file.\n     * @param destDirPath The path of destination directory.\n     * @return the unzipped files\n     * @throws IOException if unzip unsuccessfully\n     */\n    public static List<File> unzipFile(final String zipFilePath,\n                                       final String destDirPath)\n            throws IOException {\n        return unzipFileByKeyword(zipFilePath, destDirPath, null);\n    }\n\n    /**\n     * Unzip the file.\n     *\n     * @param zipFile The ZIP file.\n     * @param destDir The destination directory.\n     * @return the unzipped files\n     * @throws IOException if unzip unsuccessfully\n     */\n    public static List<File> unzipFile(final File zipFile,\n                                       final File destDir)\n            throws IOException {\n        return unzipFileByKeyword(zipFile, destDir, null);\n    }\n\n    /**\n     * Unzip the file by keyword.\n     *\n     * @param zipFilePath The path of ZIP file.\n     * @param destDirPath The path of destination directory.\n     * @param keyword     The keyboard.\n     * @return the unzipped files\n     * @throws IOException if unzip unsuccessfully\n     */\n    public static List<File> unzipFileByKeyword(final String zipFilePath,\n                                                final String destDirPath,\n                                                final String keyword)\n            throws IOException {\n        return unzipFileByKeyword(UtilsBridge.getFileByPath(zipFilePath), UtilsBridge.getFileByPath(destDirPath), keyword);\n    }\n\n    /**\n     * Unzip the file by keyword.\n     *\n     * @param zipFile The ZIP file.\n     * @param destDir The destination directory.\n     * @param keyword The keyboard.\n     * @return the unzipped files\n     * @throws IOException if unzip unsuccessfully\n     */\n    public static List<File> unzipFileByKeyword(final File zipFile,\n                                                final File destDir,\n                                                final String keyword)\n            throws IOException {\n        if (zipFile == null || destDir == null) return null;\n        List<File> files = new ArrayList<>();\n        ZipFile zip = new ZipFile(zipFile);\n        Enumeration<?> entries = zip.entries();\n        try {\n            if (UtilsBridge.isSpace(keyword)) {\n                while (entries.hasMoreElements()) {\n                    ZipEntry entry = ((ZipEntry) entries.nextElement());\n                    String entryName = entry.getName().replace(\"\\\\\", \"/\");\n                    if (entryName.contains(\"../\")) {\n                        Log.e(\"ZipUtils\", \"entryName: \" + entryName + \" is dangerous!\");\n                        continue;\n                    }\n                    if (!unzipChildFile(destDir, files, zip, entry, entryName)) return files;\n                }\n            } else {\n                while (entries.hasMoreElements()) {\n                    ZipEntry entry = ((ZipEntry) entries.nextElement());\n                    String entryName = entry.getName().replace(\"\\\\\", \"/\");\n                    if (entryName.contains(\"../\")) {\n                        Log.e(\"ZipUtils\", \"entryName: \" + entryName + \" is dangerous!\");\n                        continue;\n                    }\n                    if (entryName.contains(keyword)) {\n                        if (!unzipChildFile(destDir, files, zip, entry, entryName)) return files;\n                    }\n                }\n            }\n        } finally {\n            zip.close();\n        }\n        return files;\n    }\n\n    private static boolean unzipChildFile(final File destDir,\n                                          final List<File> files,\n                                          final ZipFile zip,\n                                          final ZipEntry entry,\n                                          final String name) throws IOException {\n        File file = new File(destDir, name);\n        files.add(file);\n        if (entry.isDirectory()) {\n            return UtilsBridge.createOrExistsDir(file);\n        } else {\n            if (!UtilsBridge.createOrExistsFile(file)) return false;\n            InputStream in = null;\n            OutputStream out = null;\n            try {\n                in = new BufferedInputStream(zip.getInputStream(entry));\n                out = new BufferedOutputStream(new FileOutputStream(file));\n                byte buffer[] = new byte[BUFFER_LEN];\n                int len;\n                while ((len = in.read(buffer)) != -1) {\n                    out.write(buffer, 0, len);\n                }\n            } finally {\n                if (in != null) {\n                    in.close();\n                }\n                if (out != null) {\n                    out.close();\n                }\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Return the files' path in ZIP file.\n     *\n     * @param zipFilePath The path of ZIP file.\n     * @return the files' path in ZIP file\n     * @throws IOException if an I/O error has occurred\n     */\n    public static List<String> getFilesPath(final String zipFilePath)\n            throws IOException {\n        return getFilesPath(UtilsBridge.getFileByPath(zipFilePath));\n    }\n\n    /**\n     * Return the files' path in ZIP file.\n     *\n     * @param zipFile The ZIP file.\n     * @return the files' path in ZIP file\n     * @throws IOException if an I/O error has occurred\n     */\n    public static List<String> getFilesPath(final File zipFile)\n            throws IOException {\n        if (zipFile == null) return null;\n        List<String> paths = new ArrayList<>();\n        ZipFile zip = new ZipFile(zipFile);\n        Enumeration<?> entries = zip.entries();\n        while (entries.hasMoreElements()) {\n            String entryName = ((ZipEntry) entries.nextElement()).getName().replace(\"\\\\\", \"/\");\n            if (entryName.contains(\"../\")) {\n                Log.e(\"ZipUtils\", \"entryName: \" + entryName + \" is dangerous!\");\n                paths.add(entryName);\n            } else {\n                paths.add(entryName);\n            }\n        }\n        zip.close();\n        return paths;\n    }\n\n    /**\n     * Return the files' comment in ZIP file.\n     *\n     * @param zipFilePath The path of ZIP file.\n     * @return the files' comment in ZIP file\n     * @throws IOException if an I/O error has occurred\n     */\n    public static List<String> getComments(final String zipFilePath)\n            throws IOException {\n        return getComments(UtilsBridge.getFileByPath(zipFilePath));\n    }\n\n    /**\n     * Return the files' comment in ZIP file.\n     *\n     * @param zipFile The ZIP file.\n     * @return the files' comment in ZIP file\n     * @throws IOException if an I/O error has occurred\n     */\n    public static List<String> getComments(final File zipFile)\n            throws IOException {\n        if (zipFile == null) return null;\n        List<String> comments = new ArrayList<>();\n        ZipFile zip = new ZipFile(zipFile);\n        Enumeration<?> entries = zip.entries();\n        while (entries.hasMoreElements()) {\n            ZipEntry entry = ((ZipEntry) entries.nextElement());\n            comments.add(entry.getComment());\n        }\n        zip.close();\n        return comments;\n    }\n}\n"
  },
  {
    "path": "Android/dokit-util/src/main/res/drawable/utils_toast_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"#E6EEEEEE\" />\n    <corners android:radius=\"16dp\" />\n</shape>\n\n"
  },
  {
    "path": "Android/dokit-util/src/main/res/layout/utils_toast_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<view xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    class=\"com.didichuxing.doraemonkit.util.ToastUtils$UtilsMaxWidthRelativeLayout\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/utils_toast_bg\"\n    android:paddingLeft=\"16dp\"\n    android:paddingTop=\"12dp\"\n    android:paddingRight=\"16dp\"\n    android:paddingBottom=\"12dp\">\n\n    <View\n        android:id=\"@+id/utvLeftIconView\"\n        android:layout_width=\"22dp\"\n        android:layout_height=\"22dp\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:layout_marginRight=\"8dp\"\n        android:visibility=\"gone\"\n        tools:background=\"#00ff00\"\n        tools:visibility=\"gone\" />\n\n    <View\n        android:id=\"@+id/utvTopIconView\"\n        android:layout_width=\"22dp\"\n        android:layout_height=\"22dp\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginBottom=\"4dp\"\n        android:visibility=\"gone\"\n        tools:background=\"#00ff00\"\n        tools:visibility=\"gone\" />\n\n    <View\n        android:id=\"@+id/utvRightIconView\"\n        android:layout_width=\"22dp\"\n        android:layout_height=\"22dp\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:layout_marginLeft=\"8dp\"\n        android:visibility=\"gone\"\n        tools:background=\"#00ff00\"\n        tools:visibility=\"gone\" />\n\n    <View\n        android:id=\"@+id/utvBottomIconView\"\n        android:layout_width=\"22dp\"\n        android:layout_height=\"22dp\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginTop=\"4dp\"\n        android:visibility=\"gone\"\n        tools:background=\"#00ff00\"\n        tools:visibility=\"gone\" />\n\n    <TextView\n        android:id=\"@android:id/message\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_above=\"@id/utvBottomIconView\"\n        android:layout_below=\"@id/utvTopIconView\"\n        android:layout_centerInParent=\"true\"\n        android:layout_toLeftOf=\"@id/utvRightIconView\"\n        android:layout_toRightOf=\"@id/utvLeftIconView\"\n        android:fontFamily=\"sans-serif\"\n        android:gravity=\"center\"\n        android:lineSpacingExtra=\"2dp\"\n        android:textColor=\"#DE000000\"\n        android:textSize=\"14sp\"\n        tools:text=\"This is long text.\" />\n</view>"
  },
  {
    "path": "Android/dokit-util/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"ActivityTranslucent\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <item name=\"android:background\">@android:color/transparent</item>\n        <item name=\"android:colorBackgroundCacheHint\">@null</item>\n        <item name=\"android:windowActionBar\">false</item>\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"android:backgroundDimEnabled\">false</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:windowAnimationStyle\">@null</item>\n        <item name=\"android:windowDisablePreview\">true</item>\n    </style>\n\n</resources>"
  },
  {
    "path": "Android/dokit-util/src/main/res/values-v21/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"ActivityTranslucent\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <item name=\"android:background\">@android:color/transparent</item>\n        <item name=\"android:colorBackgroundCacheHint\">@null</item>\n        <item name=\"android:windowActionBar\">false</item>\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"android:statusBarColor\">@android:color/transparent</item>\n        <item name=\"android:backgroundDimEnabled\">false</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:windowAnimationStyle\">@null</item>\n        <item name=\"android:windowDisablePreview\">true</item>\n    </style>\n\n</resources>"
  },
  {
    "path": "Android/dokit-util/src/main/res/xml/util_code_provider_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n    <files-path\n        name=\"files_path\"\n        path=\".\" />\n\n    <cache-path\n        name=\"cache_path\"\n        path=\".\" />\n\n    <external-path\n        name=\"external_path\"\n        path=\".\" />\n\n    <external-files-path\n        name=\"external_files_path\"\n        path=\".\" />\n\n    <external-cache-path\n        name=\"external_cache_path\"\n        path=\".\" />\n\n    <external-media-path\n        name=\"external_media_path\"\n        path=\".\" />\n</paths>"
  },
  {
    "path": "Android/dokit-util/upload.sh",
    "content": "#!/usr/bin/env bash\necho -n  \"please enter bintray userid ->\"\nread  userid_bintray\necho -n  \"please enter bintray apikey ->\"\nread  apikey_bintray\n../gradlew clean build --stacktrace  --info   bintrayUpload -PbintrayUser=${userid_bintray} -PbintrayKey=${apikey_bintray} -PdryRun=false"
  },
  {
    "path": "Android/dokit-weex/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "Android/dokit-weex/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-kapt'\napply from: '../upload.gradle'\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion_16\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode rootProject.ext.android[\"versionCode\"]\n        versionName rootProject.ext.android[\"versionName\"]\n\n        lintOptions {\n            abortOnError false\n        }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n    /**\n     * 支持ViewBinding\n     */\n    buildFeatures {\n        //viewBinding = true\n    }\n\n\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n\n}\n\n\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    if (needKotlinV14()) {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v14\"]\n    } else {\n        compileOnly rootProject.ext.dependencies[\"kotlin_v13\"]\n    }\n    implementation rootProject.ext.dependencies[\"appcompat\"]\n    implementation rootProject.ext.dependencies[\"constraintLayout\"]\n    implementation rootProject.ext.dependencies[\"material\"]\n    compileOnly project(':dokit-util')\n    api project(':dokit')\n    compileOnly rootProject.ext.dependencies[\"weex_inspector\"]\n    compileOnly rootProject.ext.dependencies[\"weex_sdk\"]\n    //auto-service\n    implementation rootProject.ext.dependencies[\"auto_service\"]\n    kapt rootProject.ext.dependencies[\"auto_service\"]\n}\nrepositories {\n    mavenCentral()\n}\n\n\n//def PUBLISH_ARCHIVES_TYPE = rootProject.ext.publish_config[\"archives_type\"]\n//if (PUBLISH_ARCHIVES_TYPE == 0 || PUBLISH_ARCHIVES_TYPE == 1) {\n//\n//    publish.mustRunAfter {\n//\n//    }\n//\n//    publish.dependsOn([\n//            ':dokit-no-op:publish',\n//            ':dokit-util:publish',\n//            ':dokit-plugin:publish',\n//            ':dokit-okhttp-v3:publish',\n//            ':dokit-okhttp-v4:publish',\n//            ':dokit-okhttp-api:publish',\n//            ':dokit:publish',\n//            ':dokit-mc:publish',\n//            ':dokit-ft:publish',\n//            ':dokit-rpc:publish',\n//            ':dokit-rpc-mc:publish'\n//    ])\n//}\n\n\n"
  },
  {
    "path": "Android/dokit-weex/gradle.properties",
    "content": "ARTIFACT_ID=dokitx-weex"
  },
  {
    "path": "Android/dokit-weex/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original from file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "Android/dokit-weex/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.didichuxing.doraemonkit.weex\">\n\n    <uses-permission android:name=\"android.permission.VIBRATE\" />\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n\n    <uses-feature android:name=\"android.hardware.camera.autofocus\" />\n\n    <application>\n        <activity android:name=\".devtool.DevToolActivity\"\n            android:theme=\"@style/Theme.AppCompat.Light.NoActionBar\"/>\n\n        <activity android:name=\".devtool.DevToolScanActivity\"\n            android:theme=\"@style/Theme.AppCompat.Light.NoActionBar\"/>\n\n        <activity android:name=\".common.DKCommonActivity\"\n            android:theme=\"@style/Theme.AppCompat.Light.NoActionBar\"/>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/common/DKCommonActivity.java",
    "content": "package com.didichuxing.doraemonkit.weex.common;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport androidx.annotation.Nullable;\n\nimport com.didichuxing.doraemonkit.kit.core.BaseActivity;\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\n\n/**\n * @author haojianglong\n * @date 2019-06-18\n */\npublic class DKCommonActivity extends BaseActivity {\n\n    private static final String CLASSNAME = \"className\";\n\n    public static void startWith(Context context, Class<? extends BaseFragment> clazz) {\n        Intent intent = new Intent(context, DKCommonActivity.class);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        intent.putExtra(CLASSNAME, clazz);\n        context.startActivity(intent);\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        Bundle bundle = getIntent().getExtras();\n        if (bundle == null) {\n            finish();\n            return;\n        }\n        Class<BaseFragment> clazz = (Class<BaseFragment>) getIntent().getSerializableExtra(CLASSNAME);\n        showContent(clazz);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/devtool/DevToolActivity.java",
    "content": "package com.didichuxing.doraemonkit.weex.devtool;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport android.text.TextUtils;\nimport android.widget.Toast;\n\nimport com.didichuxing.doraemonkit.zxing.activity.CaptureActivity;\n\nimport org.apache.weex.WXEnvironment;\nimport org.apache.weex.WXSDKEngine;\n\n/**\n * @author haojianglong\n * @date 2019-06-25\n */\npublic class DevToolActivity extends AppCompatActivity {\n\n    private final int REQUEST_CODE_CAMERA = 0x100;\n    private final int REQUEST_CODE_SCAN = 0x101;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED) {\n                String[] permissions = {Manifest.permission.CAMERA};\n                requestPermissions(permissions, REQUEST_CODE_CAMERA);\n            } else {\n                startScan();\n            }\n        } else {\n            startScan();\n        }\n    }\n\n    private void startScan() {\n        Intent intent = new Intent(this, DevToolScanActivity.class);\n        startActivityForResult(intent, REQUEST_CODE_SCAN);\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n        if (requestCode == REQUEST_CODE_CAMERA) {\n            if (permissions.length > 0) {\n                for (int i = 0; i < permissions.length; i++) {\n                    if (Manifest.permission.CAMERA.equals(permissions[i]) &&\n                            grantResults[i] == PackageManager.PERMISSION_GRANTED) {\n                        startScan();\n                        return;\n                    }\n                }\n            }\n            finish();\n        }\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        if (requestCode == REQUEST_CODE_SCAN && resultCode == Activity.RESULT_OK) {\n            if (data != null && data.hasExtra(CaptureActivity.INTENT_EXTRA_KEY_QR_SCAN)) {\n                String code = data.getStringExtra(CaptureActivity.INTENT_EXTRA_KEY_QR_SCAN);\n                if (!TextUtils.isEmpty(code)) {\n                    try {\n                        Uri uri = Uri.parse(code);\n                        handleScanResult(uri);\n                    } catch (Exception e) {\n                        e.printStackTrace();\n                    } finally {\n                        finish();\n                    }\n                } else {\n                    handleNoResult();\n                }\n            } else {\n                handleNoResult();\n            }\n        } else {\n            handleNoResult();\n        }\n    }\n\n    private void handleNoResult() {\n        Toast.makeText(getApplicationContext(), \"没有扫描到任何内容>_<\", Toast.LENGTH_SHORT);\n        finish();\n    }\n\n    private void handleScanResult(Uri uri) {\n        if (WXEnvironment.isApkDebugable()) {\n            String devToolUrl = uri.getQueryParameter(\"_wx_devtool\");\n            if (!TextUtils.isEmpty(devToolUrl)) {\n                WXEnvironment.sRemoteDebugProxyUrl = devToolUrl;\n                WXEnvironment.sDebugServerConnectable = true;\n                WXSDKEngine.reload(getApplicationContext(), false);\n            }\n        }\n        finish();\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/devtool/DevToolScanActivity.java",
    "content": "package com.didichuxing.doraemonkit.weex.devtool;\n\nimport androidx.annotation.IdRes;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.FrameLayout;\n\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\nimport com.didichuxing.doraemonkit.weex.R;\nimport com.didichuxing.doraemonkit.zxing.activity.CaptureActivity;\n\n/**\n * @author haojianglong\n * @date 2019-06-18\n */\npublic class DevToolScanActivity extends CaptureActivity {\n\n    @Override\n    public void setContentView(View view) {\n        super.setContentView(view);\n        initTitleBar();\n    }\n\n    @Override\n    public void setContentView(@IdRes int layoutResID) {\n        super.setContentView(layoutResID);\n        initTitleBar();\n    }\n\n    private void initTitleBar() {\n        HomeTitleBar homeTitleBar = new HomeTitleBar(this);\n        homeTitleBar.setBackgroundColor(getResources().getColor(R.color.foreground_wtf));\n        homeTitleBar.setTitle(getResources().getString(R.string.dk_dev_tool_title));\n        homeTitleBar.setIcon(R.mipmap.dk_close_icon);\n        homeTitleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                finish();\n            }\n        });\n        FrameLayout.LayoutParams params =\n                new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,\n                        (int) getResources().getDimension(R.dimen.dk_home_title_height));\n        ((FrameLayout) findViewById(android.R.id.content)).addView(homeTitleBar, params);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/devtool/WeexDevToolKit.kt",
    "content": "package com.didichuxing.doraemonkit.weex.devtool\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content.Intent\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.weex.R\nimport com.google.auto.service.AutoService\n\n/**\n * @author haojianglong\n * @date 2019-06-11\n */\n@AutoService(AbstractKit::class)\nclass WeexDevToolKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_dev_tool_name\n    override val icon: Int\n        get() = R.mipmap.dk_custom\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        val intent = Intent(activity, DevToolActivity::class.java)\n        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK\n        activity.startActivity(intent)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_weex_ck_devtool\"\n    }\n}"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/info/WeexInfo.java",
    "content": "package com.didichuxing.doraemonkit.weex.info;\n\n/**\n * @author haojianglong\n * @date 2019-06-25\n */\npublic class WeexInfo {\n\n    public String key;\n\n    public String value;\n\n    public WeexInfo(String key, String value) {\n        this.key = key;\n        this.value = value;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/info/WeexInfoAdapter.java",
    "content": "package com.didichuxing.doraemonkit.weex.info;\n\nimport android.content.Context;\nimport android.text.TextUtils;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.BaseAdapter;\nimport android.widget.RelativeLayout;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.weex.R;\n\nimport java.util.List;\n\n/**\n * @author haojianglong\n * @date 2019-06-25\n */\npublic class WeexInfoAdapter extends BaseAdapter {\n\n    private Context mContext;\n\n    private List<WeexInfo> mWeexInfos;\n\n    public void setWeexInfos(List<WeexInfo> infos) {\n        this.mWeexInfos = infos;\n    }\n\n    public WeexInfoAdapter(Context context) {\n        this.mContext = context;\n    }\n\n    @Override\n    public int getCount() {\n        return mWeexInfos.size();\n    }\n\n    @Override\n    public Object getItem(int position) {\n        return mWeexInfos.get(position);\n    }\n\n    @Override\n    public long getItemId(int position) {\n        return 0;\n    }\n\n    @Override\n    public View getView(int position, View convertView, ViewGroup parent) {\n        if (convertView == null) {\n            convertView = LayoutInflater.from(mContext)\n                    .inflate(R.layout.dk_fragment_info_item, null);\n        }\n        ViewHolder viewHolder;\n        if (convertView.getTag() == null) {\n            viewHolder = new ViewHolder();\n            viewHolder.keyText = convertView.findViewById(R.id.info_item_key);\n            viewHolder.valueText = convertView.findViewById(R.id.info_item_value);\n            convertView.setTag(viewHolder);\n        } else {\n            viewHolder = (ViewHolder) convertView.getTag();\n        }\n        setText(viewHolder.keyText, mWeexInfos.get(position).key);\n        setText(viewHolder.valueText, mWeexInfos.get(position).value);\n        convertView.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,\n                (int) mContext.getResources().getDimension(R.dimen.dk_title_height)));\n        return convertView;\n    }\n\n    private void setText(TextView textView, String text) {\n        if (TextUtils.isEmpty(text)) {\n            textView.setVisibility(View.GONE);\n        } else {\n            textView.setVisibility(View.VISIBLE);\n            textView.setText(text);\n        }\n    }\n\n    class ViewHolder {\n        TextView keyText;\n        TextView valueText;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/info/WeexInfoFragment.java",
    "content": "package com.didichuxing.doraemonkit.weex.info;\n\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.view.View;\nimport android.widget.ListView;\n\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\nimport com.didichuxing.doraemonkit.weex.R;\n\n/**\n * @author haojianglong\n * @date 2019-06-18\n */\npublic class WeexInfoFragment extends BaseFragment {\n\n    private WeexInfoAdapter mAdapter;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_info;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n    }\n\n    private void initView() {\n        HomeTitleBar titleBar = findViewById(R.id.title_bar);\n        titleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                getActivity().finish();\n            }\n        });\n        ListView listView = findViewById(R.id.info_list);\n        mAdapter = new WeexInfoAdapter(getContext());\n        mAdapter.setWeexInfos(WeexInfoHacker.getWeexInfos());\n        listView.setAdapter(mAdapter);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/info/WeexInfoHacker.java",
    "content": "package com.didichuxing.doraemonkit.weex.info;\n\n\nimport org.apache.weex.WXEnvironment;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * @author haojianglong\n * @date 2019-06-25\n */\npublic class WeexInfoHacker {\n\n    public static List<WeexInfo> getWeexInfos() {\n        List<WeexInfo> infos = new ArrayList<>();\n        Set<Map.Entry<String, String>> entrySet = WXEnvironment.getConfig().entrySet();\n        for (Map.Entry<String, String> entry : entrySet) {\n            infos.add(new WeexInfo(entry.getKey(), entry.getValue()));\n        }\n        return infos;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/info/WeexInfoKit.kt",
    "content": "package com.didichuxing.doraemonkit.weex.info\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.parameter.cpu.CpuMainPageFragment\nimport com.didichuxing.doraemonkit.weex.R\nimport com.google.auto.service.AutoService\n\n/**\n * @author haojianglong\n * @date 2019-06-11\n */\n@AutoService(AbstractKit::class)\nclass WeexInfoKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_weex_info_name\n    override val icon: Int\n        get() = R.mipmap.dk_sys_info\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        startUniversalActivity(CpuMainPageFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_weex_ck_info\"\n    }\n}"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/log/WeexLogInfoDoKitView.java",
    "content": "package com.didichuxing.doraemonkit.weex.log;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.FrameLayout;\n\nimport com.didichuxing.doraemonkit.kit.loginfo.LogInfoDoKitView;\nimport com.didichuxing.doraemonkit.kit.loginfo.LogInfoManager;\nimport com.didichuxing.doraemonkit.kit.loginfo.LogLine;\nimport com.didichuxing.doraemonkit.weex.R;\nimport com.didichuxing.doraemonkit.widget.titlebar.LogTitleBar;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author haojianglong\n * @date 2019-06-25\n */\npublic class WeexLogInfoDoKitView extends LogInfoDoKitView {\n\n    private final String WEEX_TAG = \"weex\";\n\n    @Override\n    public View onCreateView(Context context, FrameLayout view) {\n        return LayoutInflater.from(context).inflate(R.layout.dk_weex_float_log_info, null);\n    }\n\n\n    @Override\n    public void initView() {\n        super.initView();\n\n        LogTitleBar logTitleBar = findViewById(R.id.dokit_title_bar);\n        logTitleBar.setListener(new LogTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                //关闭日志服务\n                LogInfoManager.getInstance().stop();\n                //清空回调\n                LogInfoManager.getInstance().removeListener();\n                detach();\n            }\n\n            @Override\n            public void onLeftClick() {\n                minimize();\n            }\n        });\n\n\n    }\n\n    @Override\n    public void onLogCatch(List<LogLine> logLines) {\n        if (logLines == null || logLines.size() <= 0) {\n            return;\n        }\n        //过滤出weex日志\n        List<LogLine> newLines = new ArrayList<>();\n        for (LogLine logLine : logLines) {\n            if (logLine.getTag().contains(WEEX_TAG)) {\n                newLines.add(logLine);\n            }\n        }\n        super.onLogCatch(newLines);\n    }\n\n\n}\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/log/WeexLogKit.kt",
    "content": "package com.didichuxing.doraemonkit.weex.log\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.DoKit\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.kit.loginfo.LogInfoManager\nimport com.didichuxing.doraemonkit.weex.R\nimport com.google.auto.service.AutoService\n\n/**\n * @author haojianglong\n * @date 2019-06-11\n */\n@AutoService(AbstractKit::class)\nclass WeexLogKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_console_log_name\n    override val icon: Int\n        get() = R.mipmap.dk_log_info\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        DoKit.launchFloating<WeexLogInfoDoKitView>()\n\n        //开启日志服务\n        LogInfoManager.getInstance().start()\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_weex_ck_log\"\n    }\n}\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/storage/StorageAdapter.java",
    "content": "package com.didichuxing.doraemonkit.weex.storage;\n\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsRecyclerAdapter;\nimport com.didichuxing.doraemonkit.widget.recyclerview.AbsViewBinder;\nimport com.didichuxing.doraemonkit.weex.R;\n\n/**\n * @author haojianglong\n * @date 2019-06-18\n */\n\npublic class StorageAdapter extends AbsRecyclerAdapter<StorageAdapter.ViewHolder, StorageInfo> {\n\n    private OnItemClickListener onItemClickListener;\n    private final String KEY = \"key\";\n    private final String VALUE = \"value\";\n\n    private final int TITLE_BACKGROUND_COLOR = Color.parseColor(\"#BBBBBB\");\n    private final int NORMAL_BACKGROUND_COLOR = Color.parseColor(\"#CDCDCD\");\n\n    public StorageAdapter(Context context) {\n        super(context);\n        append(new StorageInfo(KEY, VALUE));\n    }\n\n    @Override\n    protected ViewHolder createViewHolder(View view, int viewType) {\n        return new ViewHolder(view);\n    }\n\n    @Override\n    protected View createView(LayoutInflater inflater, ViewGroup parent, int viewType) {\n        return inflater.inflate(R.layout.dk_item_storage_watch, parent, false);\n    }\n\n    public class ViewHolder extends AbsViewBinder<StorageInfo> {\n\n        private TextView key;\n        private TextView value;\n\n        public ViewHolder(View view) {\n            super(view);\n        }\n\n        @Override\n        protected void getViews() {\n            key = getView(R.id.tv_tip_key);\n            value = getView(R.id.tv_tip_value);\n        }\n\n        @Override\n        public void bind(final StorageInfo bean) {\n            if (getAdapterPosition() == 0) {\n                key.setBackgroundColor(TITLE_BACKGROUND_COLOR);\n                value.setBackgroundColor(TITLE_BACKGROUND_COLOR);\n            } else {\n                key.setBackgroundColor(NORMAL_BACKGROUND_COLOR);\n                value.setBackgroundColor(NORMAL_BACKGROUND_COLOR);\n            }\n            key.setText(bean.key);\n            value.setText(bean.value);\n            getView().setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    if (getAdapterPosition() != 0 && onItemClickListener != null) {\n                        onItemClickListener.onItemClick(bean);\n                    }\n                }\n            });\n        }\n\n    }\n\n    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {\n        this.onItemClickListener = onItemClickListener;\n    }\n\n    public interface OnItemClickListener {\n        /**\n         * 点击事件\n         *\n         * @param info\n         */\n        void onItemClick(StorageInfo info);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/storage/StorageDialogFragment.java",
    "content": "package com.didichuxing.doraemonkit.weex.storage;\n\nimport android.app.Dialog;\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.DialogFragment;\n\nimport android.util.DisplayMetrics;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.didichuxing.doraemonkit.weex.R;\n\n\npublic class StorageDialogFragment extends DialogFragment {\n\n    public static final String KEY_STORAGE_INFO = \"key_storage_info\";\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        View view = inflater.inflate(R.layout.dk_item_storage_dialog, container);\n        return view;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        if (getArguments() != null) {\n            StorageInfo info = (StorageInfo) getArguments().getSerializable(KEY_STORAGE_INFO);\n            if (info != null) {\n                TextView tv_key = view.findViewById(R.id.tv_name);\n                TextView tv_value = view.findViewById(R.id.tv_value);\n                tv_key.setText(info.key);\n                tv_value.setText(info.value);\n            }\n        }\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        Dialog dialog = getDialog();\n        if (dialog != null) {\n            DisplayMetrics dm = new DisplayMetrics();\n            getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);\n            dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);\n        }\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/storage/StorageFragment.java",
    "content": "package com.didichuxing.doraemonkit.weex.storage;\n\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.view.View;\n\nimport com.didichuxing.doraemonkit.kit.core.BaseFragment;\nimport com.didichuxing.doraemonkit.widget.recyclerview.DividerItemDecoration;\nimport com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar;\nimport com.didichuxing.doraemonkit.weex.R;\n\nimport java.util.List;\n\n/**\n * @author haojianglong\n * @date 2019-06-18\n */\npublic class StorageFragment extends BaseFragment {\n\n    private StorageHacker mStorageHacker;\n\n    @Override\n    protected int onRequestLayout() {\n        return R.layout.dk_fragment_storage;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initView();\n    }\n\n    private void initView() {\n        mStorageHacker = new StorageHacker(getContext(), true);\n\n        HomeTitleBar titleBar = findViewById(R.id.title_bar);\n        titleBar.setListener(new HomeTitleBar.OnTitleBarClickListener() {\n            @Override\n            public void onRightClick() {\n                getActivity().finish();\n            }\n        });\n        RecyclerView recycler = findViewById(R.id.info_list);\n        recycler.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));\n        recycler.addItemDecoration(new DividerItemDecoration(DividerItemDecoration.VERTICAL));\n        final StorageAdapter storageItemAdapter = new StorageAdapter(getActivity());\n        mStorageHacker.fetch(new StorageHacker.OnLoadListener() {\n            @Override\n            public void onLoad(List<StorageInfo> list) {\n                storageItemAdapter.append(list);\n            }\n        });\n\n        storageItemAdapter.setOnItemClickListener(new StorageAdapter.OnItemClickListener() {\n            @Override\n            public void onItemClick(StorageInfo info) {\n                StorageDialogFragment fragment = new StorageDialogFragment();\n                Bundle bundle = new Bundle();\n                bundle.putSerializable(StorageDialogFragment.KEY_STORAGE_INFO, info);\n                fragment.setArguments(bundle);\n                fragment.show(getFragmentManager(), \"dialog\");\n            }\n        });\n        recycler.setAdapter(storageItemAdapter);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/storage/StorageHacker.java",
    "content": "package com.didichuxing.doraemonkit.weex.storage;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.os.Handler;\nimport android.os.Looper;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport org.apache.weex.WXSDKEngine;\nimport org.apache.weex.appfram.storage.DefaultWXStorage;\nimport org.apache.weex.appfram.storage.IWXStorageAdapter;\nimport org.apache.weex.appfram.storage.WXSQLiteOpenHelper;\n\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ThreadFactory;\n\n/**\n * @author haojianglong\n * @date 2019-06-18\n */\n\npublic final class StorageHacker {\n\n    private IWXStorageAdapter mStorageAdapter;\n    private Context mContext;\n    private final boolean isDebug;\n\n    private Handler mHandler = new Handler(Looper.getMainLooper());\n    private ExecutorService mExecutor = Executors.newCachedThreadPool(new ThreadFactory() {\n        @Override\n        public Thread newThread(@NonNull Runnable r) {\n            return new Thread(r, \"wx_analyzer_storage_dumper\");\n        }\n    });\n\n    public StorageHacker(@NonNull Context context, boolean isDebug) {\n        mStorageAdapter = WXSDKEngine.getIWXStorageAdapter();\n        if (!(context instanceof Application)) {\n            context = context.getApplicationContext();\n        }\n        this.mContext = context;\n        this.isDebug = isDebug;\n    }\n\n    public void destroy() {\n        if (mHandler != null) {\n            mHandler.removeCallbacksAndMessages(null);\n            mHandler = null;\n        }\n\n        if (mExecutor != null) {\n            mExecutor.shutdown();\n            mExecutor = null;\n        }\n    }\n\n    public boolean isDestroy() {\n        return mHandler == null || mExecutor == null || mExecutor.isShutdown();\n    }\n\n\n    @SuppressWarnings(\"unchecked\")\n    public void fetch(@Nullable final OnLoadListener listener) {\n        if (listener == null) {\n            return;\n        }\n\n        if (mStorageAdapter == null) {\n            listener.onLoad(Collections.<StorageInfo>emptyList());\n            return;\n        }\n\n        if (!(mStorageAdapter instanceof DefaultWXStorage)) {\n            listener.onLoad(Collections.<StorageInfo>emptyList());\n            return;\n        }\n\n        if (isDestroy()) {\n            listener.onLoad(Collections.<StorageInfo>emptyList());\n            return;\n        }\n\n        mExecutor.execute(new Runnable() {\n            @Override\n            public void run() {\n                final List<StorageInfo> resultList = new ArrayList<>();\n                WXSQLiteOpenHelper helper = null;\n                Cursor c = null;\n                try {\n                    Constructor<WXSQLiteOpenHelper> constructor = WXSQLiteOpenHelper.class.getDeclaredConstructor(Context.class);\n                    constructor.setAccessible(true);\n                    helper = constructor.newInstance(mContext);\n\n                    Method method = WXSQLiteOpenHelper.class.getDeclaredMethod(\"getDatabase\");\n                    method.setAccessible(true);\n                    SQLiteDatabase db = (SQLiteDatabase) method.invoke(helper);\n\n                    c = db.query(\"default_wx_storage\", new String[]{\"key\", \"value\", \"timestamp\"}, null, null, null, null, null);\n\n                    if (isDebug) {\n                        Log.d(\"weex-analyzer\", \"start dump weex storage\");\n                    }\n\n                    while (c.moveToNext()) {\n                        StorageInfo info = new StorageInfo();\n                        info.key = c.getString(c.getColumnIndex(\"key\"));\n                        info.value = c.getString(c.getColumnIndex(\"value\"));\n                        info.timestamp = c.getString(c.getColumnIndex(\"timestamp\"));\n                        if (isDebug) {\n                            Log.d(\"weex-analyzer\", \"weex storage[\" + info.key + \" | \" + info.value + \"]\");\n                        }\n                        resultList.add(info);\n                    }\n\n                    if (isDebug) {\n                        Log.d(\"weex-analyzer\", \"end dump weex storage\");\n                    }\n\n                    if (mHandler != null) {\n                        mHandler.post(new Runnable() {\n                            @Override\n                            public void run() {\n                                listener.onLoad(resultList);\n                            }\n                        });\n                    }\n\n                } catch (Exception e) {\n                    e.printStackTrace();\n                } finally {\n                    if (c != null) {\n                        c.close();\n                    }\n                    if (helper != null) {\n                        helper.closeDatabase();\n                    }\n                }\n            }\n        });\n    }\n\n    public void remove(@Nullable final String key, @Nullable final OnRemoveListener listener) {\n        if (listener == null || TextUtils.isEmpty(key)) {\n            return;\n        }\n\n        if (mStorageAdapter == null) {\n            listener.onRemoved(false);\n            return;\n        }\n\n        if (!(mStorageAdapter instanceof DefaultWXStorage)) {\n            listener.onRemoved(false);\n            return;\n        }\n\n        if (isDestroy()) {\n            listener.onRemoved(false);\n            return;\n        }\n\n        mExecutor.execute(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    DefaultWXStorage storage = (DefaultWXStorage) mStorageAdapter;\n                    Method method = storage.getClass().getDeclaredMethod(\"performRemoveItem\", String.class);\n                    if (method != null) {\n                        method.setAccessible(true);\n                        final boolean result = (boolean) method.invoke(storage, key);\n                        if (mHandler != null) {\n                            mHandler.post(new Runnable() {\n                                @Override\n                                public void run() {\n                                    listener.onRemoved(result);\n                                }\n                            });\n                            method.setAccessible(false);\n                        }\n                    }\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n            }\n        });\n    }\n\n    interface OnLoadListener {\n        void onLoad(List<StorageInfo> list);\n    }\n\n    interface OnRemoveListener {\n        void onRemoved(boolean status);\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/storage/StorageInfo.java",
    "content": "package com.didichuxing.doraemonkit.weex.storage;\n\nimport java.io.Serializable;\n\n/**\n * @author haojianglong\n * @date 2019-06-18\n */\npublic class StorageInfo implements Serializable {\n\n    public String key;\n    public String value;\n    public String timestamp;\n\n    public StorageInfo() {\n    }\n\n    public StorageInfo(String key, String value) {\n        this.key = key;\n        this.value = value;\n    }\n\n}\n"
  },
  {
    "path": "Android/dokit-weex/src/main/java/com/didichuxing/doraemonkit/weex/storage/WeexStorageKit.kt",
    "content": "package com.didichuxing.doraemonkit.weex.storage\n\nimport android.app.Activity\nimport android.content.Context\nimport com.didichuxing.doraemonkit.kit.AbstractKit\nimport com.didichuxing.doraemonkit.weex.R\nimport com.google.auto.service.AutoService\n\n/**\n * @author haojianglong\n * @date 2019-06-11\n */\n@AutoService(AbstractKit::class)\nclass WeexStorageKit : AbstractKit() {\n    override val name: Int\n        get() = R.string.dk_storage_cache_name\n    override val icon: Int\n        get() = R.mipmap.dk_file_explorer\n\n    override fun onClickWithReturn(activity: Activity): Boolean {\n        startUniversalActivity(StorageFragment::class.java, activity, null, true)\n        return true\n    }\n\n    override fun onAppInit(context: Context?) {}\n    override val isInnerKit: Boolean\n        get() = true\n\n    override fun innerKitId(): String {\n        return \"dokit_sdk_weex_ck_storage\"\n    }\n}"
  },
  {
    "path": "Android/dokit-weex/src/main/res/layout/dk_fragment_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        android:layout_height=\"@dimen/dk_home_title_height\"\n        app:dkTitle=\"@string/dk_weex_info_title\"/>\n\n    <ListView\n        android:id=\"@+id/info_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scrollbars=\"none\"\n        android:paddingLeft=\"@dimen/dk_dp_15\"\n        android:divider=\"@color/dk_color_CCCCCC\"\n        android:dividerHeight=\"1px\"/>\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit-weex/src/main/res/layout/dk_fragment_info_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/dk_title_height\">\n\n    <TextView\n        android:id=\"@+id/info_item_key\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerVertical=\"true\"\n        android:textColor=\"@color/dk_color_333333\"\n        android:textSize=\"@dimen/dk_font_size_16\"/>\n\n    <TextView\n        android:id=\"@+id/info_item_value\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"right\"\n        android:layout_centerVertical=\"true\"\n        android:layout_toRightOf=\"@id/info_item_key\"\n        android:layout_marginLeft=\"@dimen/dk_dp_40\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_marginRight=\"@dimen/dk_dp_15\"\n        android:textColor=\"@color/dk_color_666666\"\n        android:textSize=\"@dimen/dk_font_size_14\"/>\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit-weex/src/main/res/layout/dk_fragment_storage.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\"\n    android:background=\"@color/dk_color_FFFFFF\">\n\n    <com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        app:dkIcon=\"@mipmap/dk_close_icon_big\"\n        android:layout_height=\"@dimen/dk_home_title_height\"\n        app:dkTitle=\"@string/dk_storage_cache_title\"/>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/info_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:overScrollMode=\"never\"\n        android:scrollbars=\"none\" />\n\n</LinearLayout>"
  },
  {
    "path": "Android/dokit-weex/src/main/res/layout/dk_item_storage_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingLeft=\"@dimen/dk_dp_20\"\n    android:paddingRight=\"@dimen/dk_dp_20\"\n    android:paddingTop=\"@dimen/dk_dp_40\"\n    android:paddingBottom=\"@dimen/dk_dp_40\"\n    android:orientation=\"vertical\">\n\n    <TextView\n        android:id=\"@+id/tv_key_tip\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textSize=\"@dimen/dk_font_size_16\"\n        android:textStyle=\"bold\"\n        android:text=\"@string/dk_storage_tip_key\" />\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/tv_key_tip\"\n        android:textSize=\"@dimen/dk_font_size_18\" />\n\n    <TextView\n        android:id=\"@+id/tv_value_tip\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/tv_name\"\n        android:layout_marginTop=\"10dp\"\n        android:textSize=\"@dimen/dk_font_size_16\"\n        android:textStyle=\"bold\"\n        android:text=\"@string/dk_storage_tip_value\" />\n\n    <TextView\n        android:id=\"@+id/tv_value\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/tv_value_tip\"\n        android:textSize=\"@dimen/dk_font_size_18\" />\n\n</RelativeLayout>"
  },
  {
    "path": "Android/dokit-weex/src/main/res/layout/dk_item_storage_watch.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/dk_dp_40\"\n    android:orientation=\"vertical\">\n\n    <TextView\n        android:id=\"@+id/tv_tip_key\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"match_parent\"\n        android:ellipsize=\"end\"\n        android:gravity=\"center\"\n        android:singleLine=\"true\"\n        android:textSize=\"@dimen/dk_font_size_18\"\n        app:layout_constraintHorizontal_weight=\"1\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/tv_tip_value\" />\n\n    <TextView\n        android:id=\"@+id/tv_tip_value\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"match_parent\"\n        android:ellipsize=\"end\"\n        android:gravity=\"center\"\n        android:singleLine=\"true\"\n        android:textSize=\"@dimen/dk_font_size_18\"\n        app:layout_constraintHorizontal_weight=\"1\"\n        app:layout_constraintLeft_toRightOf=\"@+id/tv_tip_key\"\n        app:layout_constraintRight_toRightOf=\"parent\" />\n\n    <View\n        android:layout_width=\"1px\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@color/dk_color_999999\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"@+id/tv_tip_value\"\n        app:layout_constraintStart_toStartOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "Android/dokit-weex/src/main/res/layout/dk_weex_float_log_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <RelativeLayout\n        android:id=\"@+id/log_page\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@color/dk_color_FFFFFF\"\n        android:orientation=\"vertical\">\n\n        <com.didichuxing.doraemonkit.widget.titlebar.LogTitleBar\n            android:id=\"@id/dokit_title_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"68dp\"\n            android:layout_alignParentTop=\"true\"\n            android:background=\"@color/foreground_wtf\"\n            app:dkBack=\"@string/dk_kit_log_min\"\n            app:dkIcon=\"@mipmap/dk_close_icon\"\n            app:dkTitle=\"@string/dk_console_log_title\" />\n\n        <View\n            android:id=\"@+id/view_divider\"\n            style=\"@style/DK.Shadow.Bottom\"\n            android:layout_below=\"@id/dokit_title_bar\" />\n\n        <EditText\n            android:id=\"@+id/log_filter\"\n            style=\"@style/DK.Input\"\n            android:layout_height=\"50dp\"\n            android:layout_below=\"@id/view_divider\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"15dp\"\n            android:layout_marginRight=\"16dp\"\n            android:background=\"@drawable/dk_log_filter_background\"\n            android:elevation=\"1dp\"\n            android:hint=\"@string/dk_log_info_edt_hint\"\n            android:inputType=\"text\"\n            android:paddingLeft=\"15dp\"\n            android:paddingRight=\"15dp\" />\n\n        <LinearLayout\n            android:id=\"@+id/button_wrap\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/log_filter\"\n            android:layout_marginTop=\"16dp\"\n            android:layout_marginBottom=\"6dp\"\n            android:orientation=\"vertical\">\n\n\n            <RadioGroup\n                android:id=\"@+id/radio_group\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n\n                android:orientation=\"horizontal\">\n\n                <RadioButton\n                    android:id=\"@+id/verbose\"\n                    style=\"@style/DK.RadioButton.Left\"\n                    android:text=\"@string/dk_log_info_verbose\" />\n\n                <RadioButton\n                    android:id=\"@+id/debug\"\n                    style=\"@style/DK.RadioButton\"\n                    android:text=\"@string/dk_log_info_debug\" />\n\n                <RadioButton\n                    android:id=\"@+id/info\"\n                    style=\"@style/DK.RadioButton\"\n                    android:text=\"@string/dk_log_info_info\" />\n\n                <RadioButton\n                    android:id=\"@+id/warn\"\n                    style=\"@style/DK.RadioButton\"\n                    android:text=\"@string/dk_log_info_warn\" />\n\n                <RadioButton\n                    android:id=\"@+id/error\"\n                    style=\"@style/DK.RadioButton.Right\"\n                    android:text=\"@string/dk_log_info_error\" />\n\n            </RadioGroup>\n\n            <LinearLayout\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"5dp\"\n                android:orientation=\"horizontal\">\n\n                <Button\n                    android:id=\"@+id/btn_clean\"\n                    style=\"@style/DK.RadioButton.Left\"\n                    android:text=\"@string/dk_log_btn_clean\" />\n\n                <Button\n                    android:id=\"@+id/btn_export\"\n                    style=\"@style/DK.RadioButton.middle\"\n                    android:text=\"@string/dk_log_btn_export\" />\n\n                <Button\n                    android:id=\"@+id/btn_top\"\n                    style=\"@style/DK.RadioButton.middle\"\n                    android:text=\"@string/dk_log_btn_back_top\" />\n\n                <Button\n                    android:id=\"@+id/btn_bottom\"\n                    style=\"@style/DK.RadioButton.Right\"\n                    android:text=\"@string/dk_log_btn_to_bottom\" />\n            </LinearLayout>\n\n        </LinearLayout>\n\n\n        <LinearLayout\n            android:id=\"@+id/ll_loading\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentBottom=\"true\"\n            android:gravity=\"center\"\n            android:orientation=\"horizontal\">\n\n            <ProgressBar\n                style=\"@android:style/Widget.ProgressBar.Small\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"3dp\"\n                android:text=\"@string/dk_log_text_loading\"\n                android:textColor=\"@color/dk_color_666666\"\n                android:textSize=\"@dimen/dk_font_size_14\" />\n        </LinearLayout>\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/log_list\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_above=\"@id/ll_loading\"\n            android:layout_below=\"@id/button_wrap\"\n            android:scrollbars=\"vertical\"\n            android:visibility=\"gone\" />\n    </RelativeLayout>\n\n    <TextView\n        android:id=\"@+id/log_hint\"\n        style=\"@style/DK.Text.White\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"30dp\"\n        android:background=\"@color/dk_color_337CC4\"\n        android:singleLine=\"true\"\n        android:text=\"@string/dk_kit_log_info\"\n        android:visibility=\"gone\" />\n\n</FrameLayout>"
  },
  {
    "path": "Android/dokit-weex/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"dk_color_333333\">#333333</color>\n    <color name=\"dk_color_666666\">#666666</color>\n    <color name=\"dk_color_999999\">#999999</color>\n    <color name=\"dk_color_33999999\">#33999999</color>\n    <color name=\"dk_color_CCCCCC\">#CCCCCC</color>\n    <color name=\"dk_color_E5E5E5\">#E5E5E5</color>\n    <color name=\"dk_color_F3F4F5\">#F3F4F5</color>\n    <color name=\"dk_color_FFFFFF\">#FFFFFF</color>\n    <color name=\"dk_color_DDDDDD\">#DDDDDD</color>\n    <color name=\"dk_color_33FFFFFF\">#33FFFFFF</color>\n    <color name=\"dk_color_aa000000\">#aa000000</color>\n    <color name=\"dk_color_000000\">#000000</color>\n    <color name=\"dk_color_151515\">#151515</color>\n    <color name=\"dk_color_2E2E3A\">#2E2E3A</color>\n    <color name=\"dk_color_7AE5E5E5\">#7AE5E5E5</color>\n    <color name=\"dk_color_FAD337\">#FAD337</color>\n    <color name=\"dk_color_55A8FD\">#55A8FD</color>\n    <color name=\"dk_color_BBBBBB\">#BBBBBB</color>\n    <color name=\"dk_color_0070BB\">#0070BB</color>\n    <color name=\"dk_color_000a7a\">#000a7a</color>\n    <color name=\"dk_color_0099dd\">#0099dd</color>\n    <color name=\"dk_color_48BB31\">#48BB31</color>\n    <color name=\"dk_color_BBBB23\">#BBBB23</color>\n    <color name=\"dk_color_FF0006\">#FF0006</color>\n    <color name=\"dk_color_8F0005\">#8F0005</color>\n    <color name=\"dk_color_CC3A4B\">#CC3A4B</color>\n    <color name=\"dk_color_30CC3A4B\">#30CC3A4B</color>\n    <color name=\"dk_color_F5F6F7\">#F5F6F7</color>\n    <color name=\"dk_color_324456\">#324456</color>\n    <color name=\"dk_color_D26282\">#D26282</color>\n    <color name=\"dk_color_F4F5F6\">#F4F5F6</color>\n    <color name=\"dk_color_337CC4\">#337CC4</color>\n    <color name=\"dk_color_3300E0DC\">#3300E0DC</color>\n    <color name=\"dk_color_3300BFFF\">#3300BFFF</color>\n    <color name=\"dk_color_33434352\">#33434352</color>\n    <color name=\"dk_color_79DE79\">#79DE79</color>\n    <color name=\"dk_color_7FFFFFFF\">#7FFFFFFF</color>\n    <color name=\"dk_color_3f3f46\">#3f3f46</color>\n    <color name=\"dk_color_333339\">#333339</color>\n    <color name=\"dk_color_4c00C9F4\">#4c00C9F4</color>\n    <color name=\"dk_color_ff00C9F4\">#ff00C9F4</color>\n    <color name=\"dk_color_60000000\">#60000000</color>\n    <color name=\"dk_color_90FFFFFF\">#90FFFFFF</color>\n\n    <color name=\"background_wtf\">#FF999900</color>\n    <color name=\"background_error\">#FFCC0000</color>\n    <color name=\"background_verbose\">#FF666666</color>\n    <color name=\"background_debug\">#FFFFFF00</color>\n    <color name=\"background_info\">#FF00CC00</color>\n    <color name=\"background_warn\">#FF0066CC</color>\n\n    <color name=\"foreground_wtf\">#FFFFFFFF</color>\n    <color name=\"foreground_error\">#FFFFFFFF</color>\n    <color name=\"foreground_verbose\">#FFCCCCCC</color>\n    <color name=\"foreground_debug\">#FF333333</color>\n    <color name=\"foreground_info\">#FF333333</color>\n    <color name=\"foreground_warn\">#FFCCCCCC</color>\n</resources>"
  },
  {
    "path": "Android/dokit-weex/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <dimen name=\"dk_dp_15\">15dp</dimen>\n    <dimen name=\"dk_dp_5\">5dp</dimen>\n    <dimen name=\"dk_dp_40\">40dp</dimen>\n    <dimen name=\"dk_dp_20\">20dp</dimen>\n    <dimen name=\"dk_dp_16\">16dp</dimen>\n    <dimen name=\"dk_title_height\">46.5dp</dimen>\n    <dimen name=\"dk_home_title_height\">89dp</dimen>\n</resources>"
  },
  {
    "path": "Android/dokit-weex/src/main/res/values/strings.xml",
    "content": "<resources>\n<!--    <string name=\"app_name\">weexkit</string>-->\n    \n    <string name=\"dk_console_log_name\">日志</string>\n    <string name=\"dk_console_log_title\">Weex日志记录</string>\n    <string name=\"dk_storage_cache_name\">缓存</string>\n    <string name=\"dk_storage_cache_title\">Storage信息查看</string>\n    <string name=\"dk_weex_info_name\">信息</string>\n    <string name=\"dk_weex_info_title\">Weex信息查看</string>\n    <string name=\"dk_dev_tool_name\">DevTool</string>\n    <string name=\"dk_dev_tool_title\">DevTool</string>\n\n    <string name=\"dk_storage_tip_key\">key:</string>\n    <string name=\"dk_storage_tip_value\">value:</string>\n\n    <!--日志-->\n    <string name=\"dk_log_btn_clean\">清空日志</string>\n    <string name=\"dk_log_btn_export\">导出</string>\n    <string name=\"dk_log_btn_back_top\">回到顶部</string>\n    <string name=\"dk_log_btn_to_bottom\">滚至底部</string>\n</resources>\n"
  },
  {
    "path": "Android/dokit-weex/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <style name=\"AppTheme.AppBarOverlay\" parent=\"ThemeOverlay.AppCompat.Dark.ActionBar\"/>\n    <style name=\"AppTheme.PopupOverlay\" parent=\"ThemeOverlay.AppCompat.Light\"/>\n</resources>"
  },
  {
    "path": "Android/dokit-weex/src/main/res/values-en-rUS/strings.xml",
    "content": "<resources>\n<!--    <string name=\"app_name\">weexkit</string>-->\n    \n    <string name=\"dk_console_log_name\">Log</string>\n    <string name=\"dk_console_log_title\">Weex Logging</string>\n    <string name=\"dk_storage_cache_name\">Cache</string>\n    <string name=\"dk_storage_cache_title\">Storage Information</string>\n    <string name=\"dk_weex_info_name\">Info</string>\n    <string name=\"dk_weex_info_title\">Weex Information</string>\n    <string name=\"dk_dev_tool_name\">DevTool</string>\n    <string name=\"dk_dev_tool_title\">DevTool</string>\n\n    <string name=\"dk_storage_tip_key\">key:</string>\n    <string name=\"dk_storage_tip_value\">value:</string>\n\n    <string name=\"dk_log_btn_clean\">Clear</string>\n    <string name=\"dk_log_btn_export\">Export</string>\n    <string name=\"dk_log_btn_back_top\">Top</string>\n    <string name=\"dk_log_btn_to_bottom\">Bottom</string>\n</resources>\n"
  },
  {
    "path": "Android/dokit-weex/src/main/res/values-zh-rCN/strings.xml",
    "content": "<resources>\n<!--    <string name=\"app_name\">weexkit</string>-->\n    \n    <string name=\"dk_console_log_name\">日志</string>\n    <string name=\"dk_console_log_title\">Weex日志记录</string>\n    <string name=\"dk_storage_cache_name\">缓存</string>\n    <string name=\"dk_storage_cache_title\">Storage信息查看</string>\n    <string name=\"dk_weex_info_name\">信息</string>\n    <string name=\"dk_weex_info_title\">Weex信息查看</string>\n    <string name=\"dk_dev_tool_name\">DevTool</string>\n    <string name=\"dk_dev_tool_title\">DevTool</string>\n\n    <string name=\"dk_storage_tip_key\">key:</string>\n    <string name=\"dk_storage_tip_value\">value:</string>\n    <string name=\"dk_log_btn_clean\">清空日志</string>\n    <string name=\"dk_log_btn_export\">导出</string>\n    <string name=\"dk_log_btn_back_top\">回到顶部</string>\n    <string name=\"dk_log_btn_to_bottom\">滚至底部</string>\n</resources>\n"
  },
  {
    "path": "Android/dokit-weex/src/main/res/values-zh-rTW/strings.xml",
    "content": "<resources>\n<!--    <string name=\"app_name\">weexkit</string>-->\n    \n    <string name=\"dk_console_log_name\">日誌</string>\n    <string name=\"dk_console_log_title\">Weex日誌記錄</string>\n    <string name=\"dk_storage_cache_name\">緩存</string>\n    <string name=\"dk_storage_cache_title\">Storage信息查看</string>\n    <string name=\"dk_weex_info_name\">信息</string>\n    <string name=\"dk_weex_info_title\">Weex信息查看</string>\n    <string name=\"dk_dev_tool_name\">DevTool</string>\n    <string name=\"dk_dev_tool_title\">DevTool</string>\n\n    <string name=\"dk_storage_tip_key\">key:</string>\n    <string name=\"dk_storage_tip_value\">value:</string>\n    <string name=\"dk_log_btn_clean\">清空日誌</string>\n    <string name=\"dk_log_btn_export\">導出</string>\n    <string name=\"dk_log_btn_back_top\">回到頂部</string>\n    <string name=\"dk_log_btn_to_bottom\">滾至底部</string>\n</resources>\n"
  },
  {
    "path": "Android/dokit-weex/upload.sh",
    "content": "#!/usr/bin/env bash\necho -n  \"please enter bintray userid ->\"\nread  userid_bintray\necho -n  \"please enter bintray apikey ->\"\nread  apikey_bintray\n../gradlew clean build --stacktrace --info   bintrayUpload -PbintrayUser=${userid_bintray} -PbintrayKey=${apikey_bintray} -PdryRun=false"
  },
  {
    "path": "Android/dokit_module.json",
    "content": "{\n    \"dokit\": \"dokitx\",\n    \"dokit-ft\": \"dokitx-ft\",\n    \"dokit-leakcanary\": \"dokitx-leakcanary\",\n    \"dokit-mc\": \"dokitx-mc\",\n    \"dokit-no-op\": \"dokitx-no-op\",\n    \"dokit-okhttp-api\": \"dokitx-okhttp-api\",\n    \"dokit-okhttp-v3\": \"dokitx-okhttp-v3\",\n    \"dokit-okhttp-v4\": \"dokitx-okhttp-v4\",\n    \"dokit-plugin\": \"dokitx-plugin\",\n    \"dokit-util\": \"dokitx-util\",\n    \"dokit-weex\": \"dokitx-weex\",\n    \"dokit-hook\": \"dokitx-hook\",\n    \"dokit-rpc\": \"dokitx-rpc\",\n    \"dokit-rpc-mc\": \"dokitx-rpc-mc\",\n    \"dokit-test\": \"dokitx-test\",\n    \"dokit-autotest\": \"dokitx-autotest\",\n    \"dokit-gps-mock\": \"dokitx-gps-mock\",\n    \"dokit-pthread-hook\": \"dokitx-pthread-hook\"\n}\n"
  },
  {
    "path": "Android/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Thu Dec 10 11:46:46 CST 2020\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-7.0.2-bin.zip\n#distributionUrl=https\\://services.gradle.org/distributions/gradle-5.4.1-all.zip\n"
  },
  {
    "path": "Android/gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx4096m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\norg.gradle.parallel=true\nandroid.enableD8=true\nandroid.debug.obsoleteApi=true\nandroid.useAndroidX=true\nandroid.enableJetifier=true\nandroid.injected.testOnly=false\nkotlin.code.style=official\n#dokit全局配置\n## 插件开关\nDOKIT_PLUGIN_SWITCH=true\n## 插件日志\nDOKIT_LOG_SWITCH=true\n## webview 的全限定名\nDOKIT_WEBVIEW_CLASS_NAME=com/didichuxing/doraemonkit/widget/webview/MyWebView\n## dokit 慢函数开关\nDOKIT_METHOD_SWITCH=true\n## dokit 函数调用栈层级\nDOKIT_METHOD_STACK_LEVEL=4\n## 0:调用栈模式 打印函数调用栈 需添加指定入口  默认为application onCreate 和attachBaseContext\n## 1:普通模式 运行时打印某个函数的耗时 全局业务代码函数插入\nDOKIT_METHOD_STRATEGY=0\n\n"
  },
  {
    "path": "Android/gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "Android/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windowz variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\ngoto execute\n\n:4NT_args\n@rem Get arguments from the 4NT Shell from JP Software\nset CMD_LINE_ARGS=%$\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "Android/settings.gradle",
    "content": "rootProject.name = \"Dokit-Android\"\ninclude ':app'\ninclude ':dokit-util'\ninclude ':dokit-ft'\ninclude ':dokit'\ninclude ':dokit-mc'\ninclude ':dokit-okhttp-v3'\ninclude ':dokit-okhttp-v4'\ninclude ':dokit-okhttp-api'\ninclude ':dokit-weex'\ninclude ':dokit-no-op'\ninclude ':dokit-plugin'\ninclude ':dokit-pthread-hook'\ninclude ':dokit-autotest'\ninclude ':dokit-test'\ninclude ':dokit-gps-mock'\n"
  },
  {
    "path": "Android/upload.gradle",
    "content": "def PUBLISH_ARCHIVES_TYPE = rootProject.ext.publish_config[\"archives_type\"]\n\nif (PUBLISH_ARCHIVES_TYPE == 0 || PUBLISH_ARCHIVES_TYPE == 1) {\n    apply from: '../upload_private.gradle'\n} else if (PUBLISH_ARCHIVES_TYPE == 2) {\n    apply from: '../upload_maven_central.gradle'\n}"
  },
  {
    "path": "Android/upload_didi.sh",
    "content": "#!/bin/bash\n\n#定义颜色的变量\nRED_COLOR=\"\\033[1;31m\"  #红\nGREEN_COLOR=\"\\033[1;32m\" #绿\nYELOW_COLOR=\"\\033[1;33m\" #黄\nBLUE_COLOR=\"\\033[1;34m\"  #蓝\nPINK=\"\\033[1;35m\"    #粉红\nRES=\"\\033[0m\"\n\n#./gradlew checkUploadConfig4Didi || ! echo -e \"${RED_COLOR}未通过打包的配置检测！！！ ${RES}\" || exit\n./gradlew copyPluginSource\n./gradlew clean\n#./gradlew assembleRelease\n./gradlew :dokit-plugin:assemble --stacktrace\n./gradlew :dokit:assembleRelease --stacktrace\n./gradlew :dokit-no-op:assembleRelease --stacktrace\n./gradlew :dokit-okhttp-api:assembleRelease --stacktrace\n./gradlew :dokit-okhttp-v3:assembleRelease --stacktrace\n./gradlew :dokit-okhttp-v4:assembleRelease --stacktrace\n./gradlew :dokit-ft:assembleRelease --stacktrace\n./gradlew :dokit-test:assembleRelease --stacktrace\n./gradlew :dokit-autotest:assembleRelease --stacktrace\n./gradlew :dokit-mc:assembleRelease --stacktrace\n./gradlew :dokit-util:assembleRelease --stacktrace\n./gradlew :dokit-weex:assembleRelease --stacktrace\n./gradlew :dokit-gps-mock:assembleRelease --stacktrace\n./gradlew :dokit-pthread-hook:assembleRelease --stacktrace\n#publish\n./gradlew :dokit-plugin:publish\n./gradlew :dokit:publish\n./gradlew :dokit-no-op:publish\n./gradlew :dokit-okhttp-api:publish\n./gradlew :dokit-okhttp-v3:publish\n./gradlew :dokit-okhttp-v4:publish\n./gradlew :dokit-ft:publish\n./gradlew :dokit-test:publish\n./gradlew :dokit-autotest:publish\n./gradlew :dokit-mc:publish\n./gradlew :dokit-util:publish\n./gradlew :dokit-weex:publish\n./gradlew :dokit-pthread-hook:publish\n./gradlew :dokit-gps-mock:publish\necho -e  \"${GREEN_COLOR} 打包上传到滴滴内部仓库完成！！！${RES}\"\n"
  },
  {
    "path": "Android/upload_local.sh",
    "content": "#!/bin/bash\n\n#定义颜色的变量\nRED_COLOR=\"\\033[1;31m\"  #红\nGREEN_COLOR=\"\\033[1;32m\" #绿\nYELOW_COLOR=\"\\033[1;33m\" #黄\nBLUE_COLOR=\"\\033[1;34m\"  #蓝\nPINK=\"\\033[1;35m\"    #粉红\nRES=\"\\033[0m\"\n\n./gradlew checkUploadConfig4Local || ! echo -e  \"${RED_COLOR}未通过打包的配置检测！！！ ${RES}\" || exit\n./gradlew copyPluginSource\n./gradlew clean\n#./gradlew assembleRelease --stacktrace\n./gradlew :dokit-plugin:assemble --stacktrace\n./gradlew :dokit:assembleRelease --stacktrace\n./gradlew :dokit-no-op:assembleRelease --stacktrace\n./gradlew :dokit-okhttp-api:assembleRelease --stacktrace\n./gradlew :dokit-okhttp-v3:assembleRelease --stacktrace\n./gradlew :dokit-okhttp-v4:assembleRelease --stacktrace\n./gradlew :dokit-ft:assembleRelease --stacktrace\n./gradlew :dokit-test:assembleRelease --stacktrace\n./gradlew :dokit-autotest:assembleRelease --stacktrace\n./gradlew :dokit-mc:assembleRelease --stacktrace\n./gradlew :dokit-util:assembleRelease --stacktrace\n./gradlew :dokit-weex:assembleRelease --stacktrace\n./gradlew :dokit-pthread-hook:assembleRelease --stacktrace\n./gradlew :dokit-gps-mock:assembleRelease --stacktrace\n#publish\n./gradlew :dokit-plugin:publish --stacktrace\n./gradlew :dokit:publish --stacktrace\n./gradlew :dokit-no-op:publish --stacktrace\n./gradlew :dokit-okhttp-api:publish --stacktrace\n./gradlew :dokit-okhttp-v3:publish --stacktrace\n./gradlew :dokit-okhttp-v4:publish --stacktrace\n./gradlew :dokit-ft:publish --stacktrace\n./gradlew :dokit-test:publish --stacktrace\n./gradlew :dokit-autotest:publish --stacktrace\n./gradlew :dokit-mc:publish --stacktrace\n./gradlew :dokit-util:publish --stacktrace\n./gradlew :dokit-weex:publish --stacktrace\n./gradlew :dokit-pthread-hook:publish --stacktrace\n./gradlew :dokit-gps-mock:publish --stacktrace\necho -e \"${GREEN_COLOR}本地打包完成！！！${RES}\"\n"
  },
  {
    "path": "Android/upload_maven.sh",
    "content": "#!/bin/bash\n\n#定义颜色的变量\nRED_COLOR=\"\\033[1;31m\"  #红\nGREEN_COLOR=\"\\033[1;32m\" #绿\nYELOW_COLOR=\"\\033[1;33m\" #黄\nBLUE_COLOR=\"\\033[1;34m\"  #蓝\nPINK=\"\\033[1;35m\"    #粉红\nRES=\"\\033[0m\"\n\n./gradlew checkUploadConfig4Maven || ! echo -e  \"${RED_COLOR}未通过打包的配置检测！！！ ${RES}\" || exit\n./gradlew copyPluginSource\n./gradlew clean\n#./gradlew assembleRelease\n./gradlew :dokit-plugin:assemble --stacktrace\n./gradlew :dokit:assembleRelease --stacktrace\n./gradlew :dokit-no-op:assembleRelease --stacktrace\n./gradlew :dokit-okhttp-api:assembleRelease --stacktrace\n./gradlew :dokit-okhttp-v3:assembleRelease --stacktrace\n./gradlew :dokit-okhttp-v4:assembleRelease --stacktrace\n./gradlew :dokit-ft:assembleRelease --stacktrace\n./gradlew :dokit-test:assembleRelease --stacktrace\n./gradlew :dokit-autotest:assembleRelease --stacktrace\n./gradlew :dokit-mc:assembleRelease --stacktrace\n./gradlew :dokit-util:assembleRelease --stacktrace\n./gradlew :dokit-weex:assembleRelease --stacktrace\n./gradlew :dokit-pthread-hook:assembleRelease --stacktrace\n./gradlew :dokit-gps-mock:assembleRelease --stacktrace\n#publish\n./gradlew :dokit-plugin:publishReleasePublicationToMavenCentralRepository\n./gradlew :dokit:publishReleasePublicationToMavenCentralRepository\n./gradlew :dokit-no-op:publishReleasePublicationToMavenCentralRepository\n./gradlew :dokit-okhttp-api:publishReleasePublicationToMavenCentralRepository\n./gradlew :dokit-okhttp-v3:publishReleasePublicationToMavenCentralRepository\n./gradlew :dokit-okhttp-v4:publishReleasePublicationToMavenCentralRepository\n./gradlew :dokit-ft:publishReleasePublicationToMavenCentralRepository\n./gradlew :dokit-test:publishReleasePublicationToMavenCentralRepository\n./gradlew :dokit-autotest:publishReleasePublicationToMavenCentralRepository\n./gradlew :dokit-mc:publishReleasePublicationToMavenCentralRepository\n./gradlew :dokit-weex:publishReleasePublicationToMavenCentralRepository\n./gradlew :dokit-util:publishReleasePublicationToMavenCentralRepository\n./gradlew :dokit-pthread-hook:publishReleasePublicationToMavenCentralRepository\n./gradlew :dokit-gps-mock:publishReleasePublicationToMavenCentralRepository\necho -e  \"${GREEN_COLOR} 打包上传到MavenCenter()仓库完成！！！ ${RES}\"\n"
  },
  {
    "path": "Android/upload_maven_central.gradle",
    "content": "import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency\nimport org.json.JSONObject\n// mavenCentral 打包脚本\napply plugin: 'maven-publish'\napply plugin: 'signing'\n\ndef DOKIT_VERSION = rootProject.ext.publish_config[\"version\"]\ndef DOKIT_GROUP_ID = rootProject.ext.publish_config[\"group_id\"]\n\n//生成源码注释\ntask packageSourcesJar(type: Jar) {\n    classifier 'sources'\n    if (project.ARTIFACT_ID == \"dokitx-plugin\") {\n        from project.kotlin.sourceSets.main.kotlin.getSrcDirs()\n    } else {\n        from project.android.sourceSets.main.java.getSrcDirs()\n    }\n}\nif (project.ARTIFACT_ID == \"dokitx-plugin\") {\n    task packageJavadoc(type: Jar) {\n        classifier = 'javadoc'\n        from javadoc\n    }\n}\n\n\next[\"signing.keyId\"] = ''\next[\"signing.password\"] = ''\next[\"signing.secretKeyRingFile\"] = ''\next[\"ossrhUsername\"] = ''\next[\"ossrhPassword\"] = ''\n\n\nFile secretPropsFile = project.rootProject.file('local.properties')\nif (secretPropsFile.exists()) {\n    println \"Found secret props file, loading props\"\n    Properties p = new Properties()\n    p.load(new FileInputStream(secretPropsFile))\n    p.each { name, value ->\n        ext[name] = value\n    }\n    println \"Found secret props file, loading props\"\n} else {\n    throw NullPointerException(\"no local.properties exists\")\n}\n\nProperties properties = new Properties()\nproperties.load(project.rootProject.file('local.properties').newDataInputStream())\ndef USERNAME = properties.getProperty('MAVEN_CENTRAL_USERNAME', \"\")\ndef PASSWORD = properties.getProperty('MAVEN_CENTRAL_PASSWORD', \"\")\n\n//读取module和ARTIFACT_ID的映射文件\ndef MODULE_JSONOBJECT = new JSONObject(new File(\"${rootProject.rootDir}/dokit_module.json\").text)\n//println(\"jsonObject==>${MODULE_JSONOBJECT.getString(\"doraemonkit\")}\")\n\nafterEvaluate {\n    publishing {\n        publications {\n            release(MavenPublication) {\n                // The coordinates of the library, being set from variables that\n                // we'll set up in a moment\n                groupId DOKIT_GROUP_ID\n                artifactId project.ARTIFACT_ID\n                version DOKIT_VERSION\n                artifact packageSourcesJar\n                // Two artifacts, the `aar` and the sources\n                if (project.ARTIFACT_ID == \"dokitx-plugin\") {\n                    artifact packageJavadoc\n                    artifact(\"$buildDir/libs/${project.getName()}.jar\")\n                } else {\n                    artifact(\"$buildDir/outputs/aar/${project.getName()}-release.aar\")\n                }\n\n\n                // Self-explanatory metadata for the most part\n                pom {\n                    name = project.ARTIFACT_ID\n                    description = 'DoKit is an efficiency platform for the entire life cycle of general front-end product research and development.'\n                    // If your project has a dedicated site, use its URL here\n                    url = 'https://github.com/didi/DoraemonKit'\n                    licenses {\n                        license {\n                            name = 'The Apache License, Version 2.0'\n                            url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'\n                        }\n                    }\n                    developers {\n                        developer {\n                            id = 'OrangeLab'\n                            name = 'Orange Lab'\n                            email = 'orange-lab@didiglobal.com'\n                        }\n                    }\n                    // Version control info, if you're using GitHub, follow the format as seen here\n                    scm {\n                        connection = 'scm:git:git://github.com/didi/DoraemonKit.git'\n                        developerConnection = 'scm:git:ssh://github.com/didi/DoraemonKit.git'\n                        url = 'https://github.com/didi/DoraemonKit/tree/master'\n                    }\n                    // A slightly hacky fix so that your POM will include any transitive dependencies\n                    // that your library builds upon\n                    withXml {\n                        def dependenciesNode = asNode().appendNode('dependencies')\n                        project.configurations.implementation.allDependencies.each { dependency ->\n                            if (!dependency.name.isEmpty() && dependency.name != \"unspecified\") {\n                                def dependencyNode = dependenciesNode.appendNode('dependency')\n                                if (dependency instanceof DefaultProjectDependency) {\n                                    //统一project 前缀\n                                    if (dependency.name.startsWith(\"dokit\")) {\n                                        dependencyNode.appendNode('groupId', DOKIT_GROUP_ID)\n                                        dependencyNode.appendNode('artifactId', MODULE_JSONOBJECT.getString(dependency.name))\n                                        dependencyNode.appendNode('version', DOKIT_VERSION)\n                                    }\n                                } else {\n                                    dependencyNode.appendNode('groupId', dependency.group)\n                                    dependencyNode.appendNode('artifactId', dependency.name)\n                                    dependencyNode.appendNode('version', dependency.version)\n                                }\n                            }\n\n                        }\n                    }\n                }\n            }\n        }\n        repositories {\n            // The repository to publish to, Sonatype/MavenCentral\n            maven {\n                // This is an arbitrary name, you may also use \"mavencentral\" or\n                // any other name that's descriptive for you\n                name = \"mavenCentral\"\n\n                def releasesRepoUrl = \"https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/\"\n                def snapshotsRepoUrl = \"https://s01.oss.sonatype.org/content/repositories/snapshots/\"\n                // You only need this if you want to publish snapshots, otherwise just set the URL\n                // to the release repo directly\n                url = DOKIT_VERSION.endsWith('-SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl\n\n                // 用于打印url，方便查看aar是否存在\n                def searchUrl = \"https://search.maven.org/artifact/\"\n                System.out.println(\"aar url：\" + searchUrl + DOKIT_GROUP_ID + \"/\" + project.ARTIFACT_ID + \"/\" + DOKIT_VERSION + \"/aar\")\n\n                // The username and password we've fetched earlier\n                credentials {\n                    username USERNAME\n                    password PASSWORD\n                }\n            }\n        }\n    }\n}\n\nsigning {\n    sign publishing.publications\n}\n"
  },
  {
    "path": "Android/upload_private.gradle",
    "content": "import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency\nimport org.json.JSONObject\n\n//本地或者滴滴内部仓库打包脚本(插件需要单独区分)\napply plugin: 'maven-publish'\ndef PUBLISH_ARCHIVES_TYPE = rootProject.ext.publish_config[\"archives_type\"]\ndef DOKIT_VERSION = rootProject.ext.publish_config[\"version\"]\ndef DOKIT_GROUP_ID = rootProject.ext.publish_config[\"group_id\"]\n\nProperties properties = new Properties()\nproperties.load(project.rootProject.file('local.properties').newDataInputStream())\ndef LOCAL_REPOSITORY_URL = properties.getProperty('LOCAL_REPOSITORY_URL', \"\")\ndef DIDI_REPOSITORY_URL = properties.getProperty('DIDI_REPOSITORY_URL', \"\")\ndef DIDI_SNAPSHOT_REPOSITORY_URL = properties.getProperty('DIDI_SNAPSHOT_REPOSITORY_URL', \"\")\ndef USERNAME = properties.getProperty('DIDI_USERNAME', \"\")\ndef PASSWORD = properties.getProperty('DIDI_PASSWORD', \"\")\n\n//读取module和ARTIFACT_ID的映射文件\ndef MODULE_JSONOBJECT = new JSONObject(new File(\"${rootProject.rootDir}/dokit_module.json\").text)\n\ntask packageSourcesJar(type: Jar) {\n    classifier 'sources'\n    if (project.ARTIFACT_ID == \"dokitx-plugin\") {\n        from project.kotlin.sourceSets.main.kotlin.getSrcDirs()\n    } else {\n        from project.android.sourceSets.main.java.getSrcDirs()\n    }\n\n}\n\nif (project.ARTIFACT_ID == \"dokitx-plugin\") {\n    task packageJavadoc(type: Jar) {\n        classifier = 'javadoc'\n        from javadoc\n    }\n}\n\nafterEvaluate {\n    publishing {\n        publications {\n            release(MavenPublication) {\n                // The coordinates of the library, being set from variables that\n                // we'll set up in a moment\n                groupId DOKIT_GROUP_ID\n                artifactId project.ARTIFACT_ID\n                version DOKIT_VERSION\n                artifact packageSourcesJar\n                // Two artifacts, the `aar` and the sources\n                if (project.ARTIFACT_ID == \"dokitx-plugin\") {\n                    artifact packageJavadoc\n                    artifact(\"$buildDir/libs/${project.getName()}.jar\")\n                } else {\n                    artifact(\"$buildDir/outputs/aar/${project.getName()}-release.aar\")\n                }\n\n                // Self-explanatory metadata for the most part\n                pom {\n                    name = project.ARTIFACT_ID\n                    description = 'DoKit is an efficiency platform for the entire life cycle of general front-end product research and development.'\n                    // If your project has a dedicated site, use its URL here\n                    url = 'https://github.com/didi/DoraemonKit'\n                    licenses {\n                        license {\n                            name = 'The Apache License, Version 2.0'\n                            url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'\n                        }\n                    }\n                    developers {\n                        developer {\n                            id = 'OrangeLab'\n                            name = 'Orange Lab'\n                            email = 'orange-lab@didiglobal.com'\n                        }\n                    }\n                    // Version control info, if you're using GitHub, follow the format as seen here\n                    scm {\n                        connection = 'scm:git:git://github.com/didi/DoraemonKit.git'\n                        developerConnection = 'scm:git:ssh://github.com/didi/DoraemonKit.git'\n                        url = 'https://github.com/didi/DoraemonKit/tree/master'\n                    }\n                    // A slightly hacky fix so that your POM will include any transitive dependencies\n                    // that your library builds upon\n                    withXml {\n                        def dependenciesNode = asNode().appendNode('dependencies')\n                        project.configurations.implementation.allDependencies.each { dependency ->\n                            if (!dependency.name.isEmpty() && dependency.name != \"unspecified\") {\n                                def dependencyNode = dependenciesNode.appendNode('dependency')\n                                if (dependency instanceof DefaultProjectDependency) {\n                                    //统一project 前缀\n                                    if (dependency.name.startsWith(\"dokit\")) {\n                                        dependencyNode.appendNode('groupId', DOKIT_GROUP_ID)\n                                        dependencyNode.appendNode('artifactId', MODULE_JSONOBJECT.getString(dependency.name))\n                                        dependencyNode.appendNode('version', DOKIT_VERSION)\n                                    }\n                                } else {\n                                    dependencyNode.appendNode('groupId', dependency.group)\n                                    dependencyNode.appendNode('artifactId', dependency.name)\n                                    dependencyNode.appendNode('version', dependency.version)\n                                }\n                            }\n\n                        }\n                    }\n                }\n            }\n        }\n        repositories {\n            // The repository to publish to, Sonatype/MavenCentral\n            maven {\n                // This is an arbitrary name, you may also use \"mavencentral\" or\n                // any other name that's descriptive for you\n                name = \"mavenCentral\"\n\n                // You only need this if you want to publish snapshots, otherwise just set the URL\n                // to the release repo directly\n                if(PUBLISH_ARCHIVES_TYPE == 0){\n                    url = LOCAL_REPOSITORY_URL\n                }else  if(PUBLISH_ARCHIVES_TYPE ==1 ){\n                    url = DIDI_REPOSITORY_URL\n                }\n\n\n                // The username and password we've fetched earlier\n                if (PUBLISH_ARCHIVES_TYPE == 1) {\n                    credentials {\n                        username USERNAME\n                        password PASSWORD\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "\n# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, caste, color, religion, or sexual\nidentity and orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the overall\n  community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or advances of\n  any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email address,\n  without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\norange-lab@didiglobal.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series of\nactions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or permanent\nban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within the\ncommunity.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.1, available at\n[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].\n\nCommunity Impact Guidelines were inspired by\n[Mozilla's code of conduct enforcement ladder][Mozilla CoC].\n\nFor answers to common questions about this code of conduct, see the FAQ at\n[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at\n[https://www.contributor-covenant.org/translations][translations].\n\n[homepage]: https://www.contributor-covenant.org\n[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html\n[Mozilla CoC]: https://github.com/mozilla/diversity\n[FAQ]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\n"
  },
  {
    "path": "CODE_OF_CONDUCT.zh-cn.md",
    "content": "\n# 贡献者公约\n\n## 我们的承诺\n\n身为项目成员、贡献者、负责人，我们保证参与此社区的每个人都不受骚扰，不论其年龄、体型、身体条件、民族、性征、性别认同与表现、经验水平、教育程度、社会地位、国籍、相貌、种族、宗教信仰及性取向如何。\n\n我们承诺致力于建设开放、友善、多元、包容、健康的社区环境。\n\n## 我们的准则\n\n有助于促进本社区积极环境的行为包括但不限于：\n\n* 与人为善、推己及人\n* 尊重不同的主张、观点和经历\n* 积极提出、耐心接受有益批评\n* 面对过失，承担责任、认真道歉、从中学习\n* 关注社区共同诉求，而非一己私利\n\n不当行为包括但不限于：\n\n* 发布与性有关的言论或图像，以及任何形式的献殷勤或勾引\n* 挑衅行为、侮辱或贬损的言论、人身及政治攻击\n* 公开或私下骚扰\n* 未获明确授权擅自发布他人的资料，如地址、电子邮箱等\n* 其他有理由认定为违反职业操守的不当行为\n\n## 落实之义务\n\n社区负责人有责任诠释何谓“妥当行为”，并据此准则，妥善公正地认定与处置不当、威胁、冒犯及有害的行为。\n\n社区负责人有权利和义务删除、编辑、拒绝违背本公约的评论（comment）、提交（commit）、代码、维基（wiki）编辑、问题（issue）等贡献。如有必要，需告知采取措施之理由。\n\n## 适用范围\n\n此行为标准适用于本社区全部场合，以及在其他场合代表本社区的个人。\n\n代表本社区的情形包括但不限于：使用官方电子邮件与社交平台、作为指定代表参与在线或线下活动。\n\n## 贯彻落实\n\n如遇滥用、骚扰等不当行为，请通过［orange-lab@didiglobal.com］向纪律检查委员举报。\n纪委将迅速审议并调查全部投诉。\n\n社区全体负责人有义务保密举报者信息。\n\n## 指导方针\n\n社区负责人将依据下列方案判断并处置违纪行为：\n\n### 一、督促\n\n**社区影响**：用语不当、举止不符合职业道德或不受社区欢迎。\n\n**处理意见**：由社区负责人予以非公开的书面警告，阐明违纪事由、解释举止如何不妥。或将要求公开道歉。\n\n### 二、警告\n\n**社区影响**：一起或多起事件中的违纪行为。\n\n**处理意见**：警告继续违纪之后果、违纪者在特定时间内禁止与当事人往来、不得擅自与社区执法者往来，禁令涵盖社区内外、社交网络在内的一切联络。如有违反，可致封禁乃至开除。\n\n### 三、封禁\n\n**社区影响**：严重违纪行为，包括屡教不改。\n\n**处理意见**：违纪者在特定时间内禁止与社区的任何往来或公开联络，禁止任何与当事人公开或私下往来，不得擅自与社区执法者往来。如有违反，可致开除。\n\n### 四、开除\n\n**社区影响**：典型违纪行为，例如屡教不改、骚扰某个人、敌对或贬低某个群体。\n\n**处理意见**：无限期禁止违纪者与项目社区的一切公开往来。\n\n## 来源\n\n本行为标准改编自[参与者公约][homepage]2.0版，可在此查阅：[https://www.contributor-covenant.org/zh-cn/version/2/0/code_of_conduct.html][v2.0]\n\n指导方针借鉴自[Mozilla纪检分级][Mozilla CoC]。\n\n此行为标准常见问题请洽：[https://www.contributor-covenant.org/faq][FAQ]。\n另有诸译本：[https://www.contributor-covenant.org/translations][translations]。\n\n[homepage]:https://www.contributor-covenant.org\n[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html\n[Mozilla CoC]: https://github.com/mozilla/diversity\n[FAQ]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contribution Guidelines\n\nThanks for your interest in contributing this project. All issues and pull requests are highly appreciated!\n\n## Pull Requests\n\nBefore sending a pull request to this project, please read and follow guidelines:\n\n1. Branch: We only accept pull requests on the `master` branch\n2. Coding style: Follow the coding style used in DoraemonKit\n3. Commit message: Use English and check your spelling\n4. Test: Make sure to test your code\n\nAdd device mode, API version, related log, screenshots and other related information in your pull request if possible.\n\nNOTE: We assume all contributions can be licensed under the [Apache License 2.0](https://github.com/didi/DoraemonKit/blob/master/LICENSE).\n\n## Issues\n\nWe love clearly described issues. :)\n\nThe following information can help us resolve the issue faster:\n\n* Device mode and hardware information\n* API version\n* Logs\n* Screenshots\n* Steps to reproduce the issue\n"
  },
  {
    "path": "Doc/android-ReleaseNotes.md",
    "content": "DoraemonKit-Android releases\n===\n\n## 3.5.0.1\n\n1、bug fixed\n\n2、DoKit For Android Kotlin 编译插件版本为1.3.72，支持Gradle 6.8及以下版本\n\n3、相关功能对应3.5.0\n\n## 3.5.0\n\n1、bug fixed\n\n2、DoKit For Android Kotlin 编译插件版本为1.4.32，支持Gradle 6.8及以上版本\n\n\n## 3.5.0.1-beta01\n\n1、Support AGP 7.0\n\n2、DoKit For Android Kotlin 编译插件版本为1.3.72，支持Gradle 6.8及以下版本\n\n3、相关功能对应3.5.0-beta01\n\n## 3.5.0-beta01\n\n1、Support AGP 7.0\n\n2、DoKit For Android Kotlin 编译插件版本为1.4.32，支持Gradle 6.8及以上版本\n\n\n## 3.4.3.1\n\n1、DoKit For Android Kotlin 编译插件版本为1.3.72，支持Gradle 6.8及以下版本\n\n2、相关功能对应3.4.3\n\n## 3.4.3\n\n1、DoKit For Android Kotlin 编译插件版本为1.4.32，支持Gradle 6.8及以上版本\n\n2、Bug Fixed\n\n\n## 3.4.2.1\n\n1、DoKit For Android Kotlin 编译插件版本为1.3.72，支持Gradle 6.8及以下版本\n\n2、相关功能对应3.4.2\n\n## 3.4.2\n\n1、DoKit For Android Kotlin 编译插件版本为1.4.32，支持Gradle 6.8及以上版本\n\n2、一机多控新增数据录制功能，主要解决页面一致性的问题\n\n3、一机多控新增自定义事件接口和Client端统一回调，主要解决特殊控件手势等自定义长连接事件。\n\n4、DoKit 对外API收敛\n\n5、Bug Fixed\n\n\n## 3.4.0-alpha03\n\n1、去掉kotlin-android-exensions插件\n\n2、开放dokit性能数据(cpu、fps、内存、网络)，通过入口函数callback接口返回，具体参考android接入文档\n\n3、新增debug环境okhttp 扩展拦截器，通过入口函数netExtInterceptor设置，具体参考android接入文档\n\n\n## 3.4.0-alpha02\n\n由于jcenter事件的影响，我们需要将DoKit For Android迁移到mavenCentral，但是需要更改groupId.所以大家要注意一下，具体的更新信息如下：\n\n1、变更GroupId(io.github.didi.dokit)\n\n //核心模块\n\ndebugImplementation \"io.github.didi.dokit:dokitx:3.4.0-alpha02\"\n\n//文件同步模块\n\ndebugImplementation \"io.github.didi.dokit:dokitx-ft:3.4.0-alpha02\"\n\n//一机多控模块\n\ndebugImplementation \"io.github.didi.dokit:dokitx-mc:3.4.0-alpha02\"\n\n//weex模块\n\ndebugImplementation \"io.github.didi.dokit:dokitx-weex:3.4.0-alpha02\"\n\n//no-op 模块\n\nreleaseImplementation \"io.github.didi.dokit:dokitx-no-op:3.4.0-alpha02\"\n\n2、适配AS4.2\n\n3、去掉AndroidUtil库，减少编译冲突\n\n4、新增LBS专区\n\n5、DoKit 悬浮主Icon更新\n\n6、其他github issuebug修复\n\n**tips:**\n\n**1、v.3.3.5及以前的版本还是在jcenter维护，所以无需变更GroupId，3.4.0及以后的版本需要将GroupId变更为：io.github.didi.dokit**\n\n**2、需要添加mavenCentral()仓库**\n\n**3、由于本次内部框架变更比较多，所以当前是alpha02版本，欢迎大家给我提Bug**\n\n\n## 3.3.5（dokitx 对应androidx，dokit对应Android Support）\n\n1.为了更好的支持android官方androidx和support，dokit从3.3.1版本开始正式对sdk名字进行更新。具体如下：\n\nandroidx===>com.didichuxing.doraemonkit:dokitx:3.3.5;\n\nsupport===>com.didichuxing.doraemonkit:dokit:3.3.5;\n\n2.修复与booster的冲突。相关issues:https://github.com/didi/booster/issues/202\n\n\n## 3.3.4（dokitx 对应androidx，dokit对应Android Support）\n\n1.为了更好的支持android官方androidx和support，dokit从3.3.1版本开始正式对sdk名字进行更新。具体如下：\n\nandroidx===>com.didichuxing.doraemonkit:dokitx:3.3.4;\n\nsupport===>com.didichuxing.doraemonkit:dokit:3.3.4;\n\n2.兼容AS Gradle v4.1.0\n\n3.dokit v3.3.4插件底层依赖库Booster升级到 v3.0.0版本\n\n\n## 3.3.3（dokitx 对应androidx，dokit对应Android Support）\n\n1.为了更好的支持android官方androidx和support，dokit从3.3.1版本开始正式对sdk名字进行更新。具体如下：\n\nandroidx===>com.didichuxing.doraemonkit:dokitx:3.3.3;\n\nsupport===>com.didichuxing.doraemonkit:dokit:3.3.3;\n\n2.新增三方依赖库搜索\n\n3.解决H5助手崩溃的问题\n\n4.兼容booster，dokit v3.3.3插件底层依赖booster v2.3.0版本\n\n\n\n## 3.3.1（dokitx 对应androidx，dokit对应Android Support）\n1.为了更好的支持android官方androidx和support，dokit从3.3.1版本开始正式对sdk名字进行更新。具体如下：\n\nandroidx===>com.didichuxing.doraemonkit:dokitx:3.3.1;\n\nsupport===>com.didichuxing.doraemonkit:dokit:3.3.1;\n\n2.新增H5助手功能:针对webview中的js请求进行数据Mock和抓包\n\n3.新增三方库信息:将项目中依赖的三方库进行统一收集展现。\n\n4.兼容okhttp v3.+ 以及 v4.+\n\n5.大量github issues 问题修复。\n\n## 3.2.0 & 3.0.8.0\n1.文件同步助手功能已推出。\n详细文档参考：\nhttp://xingyun.xiaojukeji.com/docs/dokit/#/FileList\n\n2.github issues fixed\n\n## 3.1.8 & 3.0.7.2\n1.适配AGP 4.0\n\n2.github issues fixed\n\n## 3.1.6\n1.更换dokit的底层插件框架为[booster](https://github.com/didi/booster),兼容性和编译速度得到显著提升\n\n2.github issues fixed\n\n## 3.0.6\n1.对齐androidx dokit v3.1.5 版本功能\n\n\n## 3.1.5\n1.更加精细化的插件控制(具体的插件配置信息可参考www.dokit.cn Android接入指南)\n\n2.新增用户自定义管理kit和自定义业务专区分组api(具体的插件配置信息可参考www.dokit.cn Android接入指南)\n\n3.核心Api升级成kotlin，同时欢迎大家和我一起参与dokit kotlin改造\n\n4.github issues fixed\n\n## 3.1.4\n1.DoKit项目结构调整,对外暴露的api已全部移到com.didichuxing.doraemonkit.kit.core包名下,如果你有自定义Kit，请重新引入一下包名\n\n2.修复okhttp拦截器被重复调用的问题\n\n3.github issues fixed\n\n## 3.1.3\n1.DoKit项目结构调整,对外暴露的api已全部移到com.didichuxing.doraemonkit.kit.core包名下,如果你有自定义Kit，请重新引入一下包名\n\n2.DoKit数据Mock模块支持post模式匹配\n\n3.github issues fixed\n\n## 3.1.2\n1.安卓版本DoKit从3.1.0版本开始全面拥抱Androidx,假如你的项目还没有升级到androidx你可以选择依赖3.0.3版本\n\n2.修复大图检测圆角失效的问题\n\n## 3.1.1\n1.修复网络工具里由于androidx的ViewPage包名不一致导致的进入页面崩溃的bug\n\n2.修复其他github issues\n\n## 3.1.0\n1.安卓版本DoKit从3.1.0版本开始全面拥抱Androidx,假如你的项目还没有升级到androidx你可以选择依赖3.0.2版本\n\n## 3.0.5\n1.该版本为支持Android support的最后一个版本，后期也将不在提供维护，请大家尽快升级和适配Androidx\n\n2.DoKit数据Mock模块支持post模式匹配\n\n3.github issues fixed\n\n## 3.0.3\n1.修复大图检测圆角失效的问题\n\n## 3.0.2\n1.安卓版DoKit从3.0.2版本开始将逐渐放弃对Android Support版本的支持，请大家全面拥抱androidx吧\n\n## 3.0.0\n1.优化dokit 慢函数代码插件性能\n\n2.优化大图检测实现方式，不再需要手动注入代码(支持Glide4.0+、Fresco、Picasso、ImageLoader)\n\n3.优化安卓端启动性能\n\n4.其他功能优化\n\n## 3.0_beta3\n1.优化dokit插件\n\n## 3.0_beta2\n1.新增日志一键导出功能\n\n2.bug fix\n\n## 3.0_beta1\n1.新增2个平台端功能 接口mock和健康体检\n\n2.优化内部架构，提升性能和代码稳定性\n\n\n## 2.2.2\n\n1. 更新视图检查工具，展示符合当前页面位置条件的所有视图\n\n\n## 2.2.1\n\n1. 升级 AOP 方案，原先采用Aspectj，现已升级为ASM方案，提升编译性能和兼容性\n   \n2. 新增高德、腾讯、百度的经纬度模拟功能\n   \n3. 合并pr以及大量issue解决\n\n\n## 2.0.1\n\n1. 修复 Dokit 在 EMUI9.1 等少数系统上存在的兼容性问题\n\n## 2.0.0\n\n1. 修复 V2.0.0 前置试用版 V1.2.8 的相关 issues, 先正是推出 V2.0.0 版本，请大家升级使用\n\n## 1.2.8\n\n1. 解决 app_name 被覆盖的问题\n\n## 1.2.7\n\n1. 新增 `LeakCanary`\n2. 新增 DBView\n3. 修改悬浮窗模式\n4. 优化性能\n\n## 1.2.1\n\n1. 位置模拟某些场景不可用修复\n2. 性能监测 Crash 修复\n\n## 1.2.0\n\n1. Crash 记录支持分享导出\n2. 重启 APP 后模拟定位依然生效\n3. 网络库代码重构\n4. RN 页面布局层级功能 Crash 修复\n5. 提供设置手动申请悬浮窗权限的能力\n\n## 1.1.8\n\n1. Crash 捕获功能重构，改为私有存储，交互体验优化\n2. 页面启动耗时兼容 __Android P__\n3. 问题修复，稳定性优化\n\n## 1.1.7\n\n1. 新增模拟弱网功能\n2. 帧率统计改为固定时间帧数法，结果更稳定平滑\n3. 问题修复，稳定性优化\n\n## 1.1.6\n\n1. 日志查看功能优化，更好的显示体验\n2. 沙盒浏览支持修改 sharedprefs 文件\n3. 反馈问题修复，包括文件删除后列表无刷新等\n4. 稳定性优化\n\n## 1.1.5\n\n1. 沙盒浏览支持视频格式，支持文件删除\n2. 位置模拟支持地图选取经纬度\n3. 缓存清理增加缓存大小显示，交互优化\n4. H5 任意门增加默认实现\n5. 其他反馈的问题修复，稳定性优化\n\n## 1.1.3\n\n1. 优化数据库预览的用户体验\n2. 修复内存实时图表显示异常\n3. 优化位置模拟功能的交互体验\n4. 其他反馈的问题修复\n\n## 1.1.2\n\n1. no-op 版本增加悬浮窗打开、关闭方法\n2. 增加了 App 启动耗时、页面跳转耗时的统计功能\n3. 增加了数据库文件的可视化查看功能\n4. 其他 bug 修复\n\n## 1.1.1\n\n1. 体验优化\n2. bug fix\n\n## 1.1.0\n\n1. 优化 CPU 和 RAM 数据读取，支持 8.0 以上系统\n2. 优化取色器功能，取色功能更准确\n3. mockGPS 兼容高精度定位模式\n4. 视觉工具新增布局边框、布局层级\n5. 体验细节优化\n\n## 1.0.5\n\n1. 提供 no-op 版本\n2. 体验优化\n\n## 1.0.1\n\n1. 修复资源冲突与覆盖导致的编译问题\n\n## 1.0.0\n\n1. 常用工具：App 信息、文件浏览、位置模拟、H5 任意门、日志显示、Crash 查看、缓存清理\n2. 性能监控：帧率、CPU、RAM、网络流量、卡顿检测\n3. 视觉工具：取色器、控件检查、对齐标尺\n\n"
  },
  {
    "path": "Doc/iOS-ReleaseNotes.md",
    "content": "# Release Notes\n\n### 3.0.7\n1、DoKit iOS github issues bug fixed\n\n\n### 3.0.4\n1、DoKit iOS端文件同步助手正式上线\n\n\n### 3.0.2\n1、支持每一个内置Kit，进行位置重排，添加删除某一个Kit\n\n2、去掉UIWebView相关代码\n\n3、其他用户反馈的问题（issues或者QQ群）解决\n\n### 3.0.1\n1、SDK以及演示Demo支持中英双语\n\n2、解决VCProfiler在某一些情况下会死锁crash的问题 iss416，先加一个黑名单规避掉\n\n3、修复 H5 任意门键盘没有退出时遮挡【点击跳转】按钮的问题；H5 任意门历史记录倒序显示（最近地址显示前面）；H5 任意门历史记录支持单条删除（左滑删除）\n\n4、 iOS组件检查功能希望能展示出对应的属性名\n\n5、启动耗时新增启动函数耗时统计\n\n6、MockGPS支持通过body字段区分请求\n\n7、支持浮窗自动默认停靠和记录上次坐标\n\n### 3.0.0\n1、新增两款平台工具，\"健康体检\"和\"数据Mock\"\n2.提升DoKit SDK稳定性以及性能优化\n\n### 3.0_beta1\n1、新增两款平台工具，\"健康体检\"和\"数据Mock\"\n\n2、新增\"应用设置\"、\"NSUserDefault\"、\"模拟弱网\"、\"UI层级检查\"\n\"UI结构\"、\"启动耗时\"等工具\n\n3、解决社区和用户反馈的一些问题，用户体验优化\n\n### 2.0.0\n1、支持旧版本Xcdoe编译SDK无法通过的问题\n2、解决2.0前置版本1.2.4的相关问题\n\n### 1.2.4\n1、新增函数耗时功能\n\n2、新增UI层级检查功能\n\n3、新增内存泄漏功能\n\n4、支持iOS13暗黑模式\n\n5、支持Carthage接入方式\n\n6、其他的一些优化，详见代码提交记录\n\n### 1.2.3\n1、为了解决调用空的NSArray和NSSet的description，会crash的问题。\n\n### 1.2.2\n1、App启动耗时统计\n\n2、Xcode控制台打印字典、数组等集合中的中文显示为unicode的问题\n\n3、组件检查或者对齐标尺组件打开后切换rootviewcontroller后失去响应\n\n4、重启APP mock的定位数据仍然生效\n\n5、消除SDK的warning\n\n6、其他的一些优化，详见代码提交记录\n\n### 1.2.1\n1、大图检测功能[0xd-cc](https://github.com/0xd-cc)\n\n2、修复元素边框线crash的问题\n\n3、添加应用内数据库调试功能 [y500](https://github.com/y500)\n\n4、横屏适配bug fix\n\n5、支持下CocoaPods1.7 的generate_multiple_pod_projects新特性\n\n6、多次执行install方法保护，避免出现多次初始化UI界面出错\n\n### 1.2.0\n1. 加入Weex专项工具，包括：\n2. **【console日志查看】** 方便在端上查看每一个Weex文件中的console日志，提供分级和搜索功能\n3. **【storage缓存查看】** 将Weex中的storage模块的本地缓存数据可视化展示\n4. **【容器信息】** 查看每一个打开的Weex页面的基本信息和性能数据\n5. **【DevTool】** 快速开启Weex DevTool的扫码入口\n\n### 1.1.9\n1、H5任意门支持扫码和历史浏览记录功能 [feng562925462](https://github.com/feng562925462)\n\n2、支持使用block的方式接入自定义测试模块 [csc-EricWu](https://github.com/csc-EricWu/)\n\n3、卡顿监控支持查看卡顿时长 [k373379320](https://github.com/k373379320)\n\n4、沙盒浏览，除了数据库文件之外，其他文件修改成 QLPreviewController 打开 [HuginChen](https://github.com/HuginChen)\n\n5、支持framework直接接入，方便非pod管理的项目\n\n6、性能曲线图代码重构,支持同时查看fps、内存、cpu和流量的信息。\n\n7、解决toast在某一些情况无法显示的问题,改善用户体验\n\n8、其他一些优化和代码整理，详见代码提交记录\n\n\n### 1.1.8\n1、支持国际化，中英文\n\n2、适配横屏 @ [Hugin](https://github.com/HuginChen)\n\n3、修复强持有 CLLocationManagerDelegate 不释放的问题 @[li6185377](https://github.com/li6185377)\n\n4、iOS沙盒浏览器优化 @[csc-EricWu](https://github.com/csc-EricWu/)\n\n5、支持主动控制隐藏显示DoKit入口\n\n6、app信息页面 权限cell可以直接跳转到权限设置页面进行设置\n\n7、其他一些优化和代码整理，详见代码提交记录\n\n\n## 1.1.7\n1、支持数据库文件本地预览功能\n\n2、支持word、pdf、html文件本地预览功能\n\n3、解决ipad上crash问题\n\n4、解决低版本手机适配问题\n\n5、mockGPS更加通用\n\n6、其他的一些issues解决和PR合并，详见代码提交记录\n\n\n## 1.1.6\n1、去掉对于AFNetworking的多余依赖\n\n2、颜色拾取器优化，方便更加准确的选取字体的颜色\n\n3、大文本显示的时候，UIlabel在模拟器上会显示空白，使用TextView代替\n\n4、pluginDidLoad回调，将itemData返回\n\n5、解决和JGProgressHUD的layoutSubviews 循环调用的问题\n\n6、适配iOS9状态栏不显示的问题\n\n7、DoraemonLoadAnalyze改成可选的pod依赖\n\n## 1.1.5\n1、更新DoraemonLoadAnalyze.framework支持bitcode\n\n2、修复 iOS11以下版本点击沙盒浏览功能崩溃\n\n3、修复 NSLog 未适配iPhone X以上机型\n\n4、流量详情的每一个cell中的数据支持复制\n\n5、其他一些优化和bug fix，详见代码提交记录\n\n## 1.1.4\n1、新增Load函数耗时检测功能\n\n2、新增元素边框线功能\n\n3、H5任意门添加默认跳转实现\n\n4、适配XR、XS、XS Max\n\n5、其他一些优化和bug fix，详见代码提交记录\n\n\n## 1.1.3\n1、DorameonKit界面不占用keyWindow\n\n2、https强制信任，解决某一些情况下https的请求无法拦截的问题\n\n3、点击空白处收起键盘\n\n4、流量监控支持WKWebView\n\n## 1.1.2\n1、沙盒浏览器支持多媒体资源预览\n\n2、修复沙盒浏览器文件过长显示不全的问题\n\n3、重建文件目录，把Demo和仓库都放在iOS目录下面。兼容andorid版本的到来\n\n## 1.1.1\n1、解决swift项目，编译不过的问题。\n\n## 1.1.0\n1、体验优化、UI大改版。\n\n2、沙盒浏览器支持文件大小查看。\n\n3、支持crash本地查看\n\n4、新增清楚本地数据功能\n\n5、新增NSLog客户端显示\n\n6、新增卡顿检测\n\n## 1.0.4\n1、UIView_Positioning重命名\n\n2、解决获取图片方法错误\n\n\n## 1.0.3\n1、BSBacktraceLogger改为pod依赖\n\n\n## 1.0.2\n1、UIView+Positioning分类加前缀\n\n\n## 1.0.1\n1、新增子线程UI检查操作\n\n2、权限信息查看更多\n\n3、完善网络测试Demo\n\n4、沙盒浏览器支持文件删除\n\n\n## 1.0.0\n\n1、第一个版本上线。\n\n2、通用工具：App信息、沙盒浏览、MockGPS、H5任意门、日志显示\n\n3、性能工具：帧率、CPU、内存、流量、自定义\n\n4、视觉工具：颜色吸管、组件检查、对齐标尺\n"
  },
  {
    "path": "Doc/iOS_cn_guide.md",
    "content": "## DoraemonKit如何使用\n\n### 一、集成方式\n\n#### 1.1: cocoapods依赖\n\n```\n    pod 'DoraemonKit/Core', '~> 3.0.4', :configurations => ['Debug'] //必选\n    pod 'DoraemonKit/WithLogger', '~> 3.0.4', :configurations => ['Debug'] //可选\n    pod 'DoraemonKit/WithGPS', '~> 3.0.4', :configurations => ['Debug'] //可选\n    pod 'DoraemonKit/WithLoad', '~> 3.0.4', :configurations => ['Debug'] //可选\n    pod 'DoraemonKit/WithWeex', '~> 3.0.4', :configurations => ['Debug'] //可选\n    pod 'DoraemonKit/WithDatabase', '~> 3.0.4', :configurations => ['Debug'] //可选\n    pod 'DoraemonKit/WithMLeaksFinder', '3.0.4', :configurations => ['Debug'] //可选\n```\nCore subspec作为核心，必须引入。\n\n如果你的日志是基于CocoaLumberjack，那你也可以引入WithLogger subspec。\n\n如果你想使用模拟定位的功能，请单独接入WithGPS subspec。\n\n如果你要集成Load耗时检测的话，那就请接入WithLoad subspec。\n\n如果你要集成Weex的相关专项工具的话，那就请接入WithWeex subspec。\n\n如果你要使用[YYDebugDatabase](https://github.com/y500/iOSDebugDatabase)在网页端调式数据库的话，那就请接入WithDatabase subspec。\n\n如果你要使用[MLeaksFinder](https://github.com/Tencent/MLeaksFinder)查找内存泄漏的问题的话，那就请接入WithMLeaksFinder subspec。\n\n#### 1.2: Carthage依赖\n\n```\ngit \"https://github.com/didi/DoraemonKit.git\"  \"c3.0.4\"\n    或者\n    github \"didi/DoraemonKit\"\n```\n**tip**：只在Debug环境中进行集成，不要带到线上。有一些hook操作会污染线上代码。\n\n\n### 二、使用DoraemonKit内置工具集的接入方式\n在App启动的时候添加一下代码\n\n```objective-c\n#ifdef DEBUG\n#import <DoraemonKit/DoraemonManager.h>\n#endif\n\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {\n    #ifdef DEBUG\n        //默认\n        [[DoraemonManager shareInstance] install];\n        // 或者使用传入位置,解决遮挡关键区域,减少频繁移动\n        //[[DoraemonManager shareInstance] installWithStartingPosition:CGPointMake(66, 66)];\n    #endif\n}\n```\n\n 通过以上步骤你就可以使用DorameonKit所有的内置工具集合。如果你想把自己与业务相关的一些工具代码加入到DoraemonKit中做统一管理的话，你可以按照3的步骤来做。\n\n### 三、添加自定义测试模块到Doraemon面板中（非必要）\n比如我们要在Doraemon面板中添加一个环境切换的功能。\n\n第一步：新建一个类，实现DoraemonPluginProtocol协议中的pluginDidLoad方法，该方法就是以后点击Doraemon工具面板中\"环境切换\"按钮触发的事件。\n\n比如以代驾司机端为例，点击按钮之后会进入环境切换页面。\n\n```\n@implementation KDDoraemonEnvPlugin\n- (void)pluginDidLoad{\n    [APP_INTERACOTR.rootNav openURL:@\"KDSJ://KDDoraemonSFViewController\"];\n    [[DoraemonManager shareInstance] hiddenHomeWindow];\n}\n @end\n```\n\n\n第二步：在Doraemon初始化的地方添加第一步中添加的\"环境切换\"插件\n\n调用DoraemonManager的以下方法：\n\n```\n[[DoraemonManager shareInstance] addPluginWithTitle:@\"环境切换\" icon:@\"doraemon_default\" desc:@\"用于app内部环境切换功能\" pluginName:@\"KDDoraemonEnvPlugin\" atModule:@\"业务专区\"];\n```\n\n依次代表 集成到DoraemonKit面板中的标题，图标，描述，插件名称，和所属于的模块。\n\n比如以代驾司机端为例：\n\n```\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {\n    #ifdef DEBUG\n       [self configDoraemonKit];\n    #endif\n}\n//配置Doraemon工具集\n- (void)configDoraemonKit{\n    [[DoraemonManager shareInstance] addPluginWithTitle:@\"环境切换\" icon:@\"doraemon_default\" desc:@\"用于app内部环境切换功能\" pluginName:@\"KDDoraemonEnvPlugin\" atModule:@\"业务专区\"];\n    [[DoraemonManager shareInstance] addH5DoorBlock:^(NSString *h5Url) {\n        [APP_INTERACOTR.rootNav openURL:@\"KDSJ://KDWebViewController\" withQuery:@{@\"urlString\":h5Url}];\n    }];\n    [[DoraemonManager shareInstance] install];\n}\n```\n\n**tips**:目前也支持使用block方式接入自定义测试模块，使用方式如下：\n\n```\n\n    [[DoraemonManager shareInstance] addPluginWithTitle:@\"标题\" icon:@\"doraemon_default\" desc:@\"测试插件\" pluginName:@\"TestPlugin(可以为空)\" atModule:DoraemonLocalizedString(@\"业务工具\") handle:^(NSDictionary *itemData) {\n        NSLog(@\"handle block plugin\");\n    }];\n    \n```\n\n### 四、swift 接入方式\npod 同 OC 一样\n\n#### swift 4.0 4.2 5.0 接入方式都一样\n\n```\nimport UIKit\n\n#if DEBUG\n    import DoraemonKit\n#endif\n\n@UIApplicationMain\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\n    var window: UIWindow?\n\n\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n        // Override point for customization after application launch.\n        \n    #if DEBUG\n        DoraemonManager.shareInstance().install()\n    #endif\n        return true\n    }\n    \n}\n```\n\n"
  },
  {
    "path": "Doc/iOS_en_guide.md",
    "content": "## How To Use\n### 1： Use Cocoapods to Get latest version of DoraemonKit\n\n```\npod 'DoraemonKit/Core', :git => \"https://github.com/didi/DoraemonKit.git\", :tag => '3.0.4', :configurations => ['Debug']\npod 'DoraemonKit/WithGPS', :git => \"https://github.com/didi/DoraemonKit.git\", :tag => '3.0.4', :configurations => ['Debug']\n```\n\n### 2： Access method using DoraemonKit's built-in toolset\nAdd code when the app starts.\n\n```objective-c\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {\n    #ifdef DEBUG\n        [[DoraemonManager shareInstance] install];\n        // or custom position\n        // [DoraemonManager shareInstance] installWithStartingPosition:CGPointMake(66, 66)];\n    #endif\n}\n```\n\n  Through the above steps you can use all of the built-in tools of DorameonKit. If you want to add some of your customized tools, see chapter 3.\n\n### 3: Add a custom test module to the Doraemon panel (non-essential)\nFor example, we want to add an environment switch module to the Doraemon panel.\n\nStep 1: create a new class, implement the pluginDidLoad method in the KDDoraemonPluginProtocol protocol, this method is to be called when the \"Environment Switch\" button is clicked.\n\nTaking our app as an example, after clicking the button, it will enter the environment switching page.\n\n```\n@implementation KDDoraemonEnvPlugin\n- (void)pluginDidLoad{\n    [APP_INTERACOTR.rootNav openURL:@\"KDSJ://KDDoraemonSFViewController\"];\n    [[DoraemonManager shareInstance] hiddenHomeWindow];\n}\n @end\n```\n\nStep 2: Add the \"Environment Switching\" plugin added in the first step where Doraemon is initialized.\n\n\n```\n[[DoraemonManager shareInstance] addPluginWithTitle:@\"环境切换\" icon:@\"qiehuang\" desc:@\"用于app内部环境切换功能\" pluginName:@\"KDDoraemonEnvPlugin\" atModule:@\"业务专区\"];\n```\n\nIt in turn shows the title, icon, description, plugin name, and the module it belongs to.\n\nTake our App as an example:\n\n```\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {\n    #ifdef DEBUG\n       [self configDoraemonKit];\n    #endif\n}\n//Initialize the Doraemon toolset\n- (void)configDoraemonKit{\n    [[DoraemonManager shareInstance] addPluginWithTitle:@\"环境切换\" icon:@\"qiehuang\" desc:@\"用于app内部环境切换功能\" pluginName:@\"KDDoraemonEnvPlugin\" atModule:@\"业务专区\"];\n    [[DoraemonManager shareInstance] addH5DoorBlock:^(NSString *h5Url) {\n        [APP_INTERACOTR.rootNav openURL:@\"KDSJ://KDWebViewController\" withQuery:@{@\"urlString\":h5Url}];\n    }];\n    [[DoraemonManager shareInstance] install];\n}\n```\n\n\n\n\n"
  },
  {
    "path": "Doc/miniapp-ReleaseNotes.md",
    "content": "## 0.0.1\n\n1. 常用工具：App信息、位置模拟、缓存管理、H5任意门、更新版本\n\n\n## 0.0.2\n\n2. 常用工具：添加api mock功能"
  },
  {
    "path": "Doc/miniapp_cn_guide.md",
    "content": "# DoKit微信小程序研发助手SDK升级\n\n## Doraemon mini program debugger\n\n一个支持小程序端的调试工具\n\n# 背景\n\n对于小程序开发者和测试同学来说，很多临时性的调试功能需要单独开发去支持，比如查看小程序信息，手机信息以及用户信息，扫码打开页面等。这些功能对于每个小程序都是相似的，而且遇到类似的需求时都需要进行单独开发。DoKit在移动端发展，获得了众多开发者的好评，其中不乏很多一线大厂(阿里，字节，腾讯，百度...)的身影，同时给我们带来了很多宝贵的经验。在广大开发者的要求下，我们重新启动了小程序端sdk的升级.\n\n此次版本升级主要提供了数据模拟功能，优化接入流程，降低用户接入成本，更好配合使用原生小程序以及第三方框架开发，提升开发同学的幸福感。\n\n# 简单总结\n\nDoKit小程序端调试工具，内置很多常用的工具，避免重复实现，一次接入，你将会拥有强大的工具集合。\n\n# 新增功能演示\n\n哆啦A梦小程序端apimock功能演示\n\n<div align=center>\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/4yXktFAckF1589275220286.png\">\n</div>\n\n> 在我们的[平台端](https://www.dokit.cn/)注册，即可使用该功能，在sdk接入部分会有详细介绍。\n\n<div align=center>\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/YfLpeuu2MH1590650509070.png\">\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/HDibtAjCqq1590651365296.png\">\n</div>\n\n点击工具中的数据模拟即可进入详情页，其中详情页分为mock数据和上传模板两块功能。\n\n> \u0001**mock数据:** 你可以通过打开指定接口的开关并选择相应的场景,此时你无需改变你的网络请求代码即可对你的指定接口进行拦截并返回你在平台创建的接口数据。\n\n> **上传模板:** 上传模板功能的适用场景是当你已经有了一个真实的接口，需要针对不同的用户场景进行测试但是同时接口返回的数据量比较庞大，所以我们为你提供了上传模板的功能。当你打开上传模板中指定接口的开关时，我们会拦截并保存你真实接口返回的数据并提供json预览功能。点击上传即可上传你的模板数据到Dokit平台端。\n\n> **实现原理:** 使用Object.defineProperty()劫持小程序的wx.request(),然后重写次方法，添加上url匹配拦截逻辑和上传模板逻辑。在平台端更新接口时，会和小程序本地数据合并，即还原原先在本地操作的记录。\n\n# 其他功能\n- app信息\n\n<div align=center>\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/Eu9DFjS6cf1590657590624.png\">\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/hHlPfUAPgu1590657697205.png\">\n</div>\n\n> 用于快速查看手机系统信息，小程序基本信息，用户信息，授权信息等基础信息，避免反复打开手机设置或者调用小程序原生api进行查看。\n\n- 位置模拟\n\n<div align=center>\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/zFDfYgfTFJ1590659245437.png\">\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/jYGvr0fsyI1590659269350.png\">\n</div>\n\n> 用于小程序端位置模拟，包括位置授权，位置查看，位置模拟，恢复位置设置等几大功能，可以通过简单的点击操作实现任意位置模拟和位置还原，该功能的实现原理是通过对wx.getLocation进行方法重写，进而进行位置模拟，位置模拟后，在小程序内所有调用位置查询的方法内都将返回你设定的位置，还原后将恢复原生方法\n\n- 缓存管理\n\n\n<div align=center>\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/MUMXoRhSFl1590667273847.png\">\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/GflYwbHHXO1590667273704.png\">\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/MM53oF1Wtj1590667274519.png\">\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/21JTvLDLp71590667274523.png\">\n</div>\n\n> 一个强大的缓存管理面板，集成了对缓存的所有操作功能，包括设置缓存，清除缓存，更新缓存值等，可以在小程序非常便利的进行缓存管理\n\n- H5任意门\n<div align=center>\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/ZWGSKwm2ZJ1590667512289.png\">\n</div>\n\n> 可以通过扫码和粘贴链接的方式在小程序中打开h5页面，操作简单方便\n\n- 更新版本\n\n> 当你的小程序进行代码更新时，为了获取最新的线上包需要重启小程序，该功能可以在你的小程序上通过点击更新操作，直接获取到最新的远程代码资源\n\n\n# 快速上手\n\n#### 如何接入\n\n如果您是使用原生小程序的开发方式，请安如下方式接入\n\n```\n1. 通过npm安装 npm install dokit-miniapp, 然后从node_modules中将dokit-miniapp文件夹拷贝到自己的项目中，然后按如下方式进行使用\n```\n\n```\n2. 在需要引用工具的页面 page.json 中引入组件(注意引用的路径)\n```\n\n```json\n\"usingComponents\": {\n  \"dokit\": \"../../components/dokit-miniapp/dist/index/index\"\n}\n```\n\n```html\n3. 在需要引用工具的页面 page.wxml 中引入使用组件\n<dokit projectId=\"your projectId\"></dokit>\n```\n\n如果您是使用小程序第三方框架的开发方式，可以做如下优化，\n\n```js\n在所需引入页面的js中，添加变量声明，例如\nconst isProd = process.env.NODE_ENV === '\"production\"'\n```\n\n```html\n在<template></template>或者是render函数中，可以使用\n\nisProd ? '' : <dokit projectId=\"your projectId\"></dokit>\n```\n\n```js\n如果框架暴露了webpack的相关打包配置，可以按照这样的配置，优化资源打包\ncompile: {\n      exclude: [\n        path.resolve(__dirname, '..', 'src/components/dokit-miniapp')\n      ]\n    }\n```\n\n> Tip: 1.由于微信小程序暂不支持开发环境和生产环境判断，请在生产环境手动删掉引用\n\n> Tip: 2.第三方框架开始，要注意框架是否将process.env.NODE_ENV注入到的全局变量中，此外有的框架的兼容性并不友好，有些打包配置并没有支持，开发者要视情况而定\n\n\n# 后续规划\n1. 取色器\n2. 接口请求性能分析\n3. 授权开启管理工具\n\n\n# 贡献\n\n有任何意见或建议都欢迎提 issue\n\n# github地址\n\nhttps://github.com/didi/DoraemonKit/tree/master/miniapp 欢迎star\n\n# 欢迎加入DoKit交流QQ群\n\n<div align=center>\n  <img width=\"190\" height=\"260\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/MDnNgukM511590736490933.jpg\">\n</div>\n\n# 欢迎关注我们的公众号\n\n<div align=center>\n  <img width=\"190\" height=\"200\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/pYXiYtMEOl1591177821127.jpeg\">\n</div>"
  },
  {
    "path": "DoraemonKit.podspec",
    "content": "# Be sure to run `pod lib lint DoraemonKit.podspec' to ensure this is a\n# valid spec before submitting.\n#\n# Any lines starting with a # are optional, but their use is encouraged\n# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html\n#\n\nPod::Spec.new do |s|\n  s.name             = 'DoraemonKit'\n  s.version          = '3.1.7'\n  s.summary          = 'iOS 各式各样的工具集合'\n\n# This description is used to generate tags and improve search results.\n#   * Think: What does it do? Why did you write it? What is the focus?\n#   * Try to keep it short, snappy and to the point.\n#   * Write the description between the DESC delimiters below.\n#   * Finally, don't worry about the indent, CocoaPods strips it!\n\n  s.description      = <<-DESC\niOS各式各样的工具集合\n                       DESC\n\n  s.homepage         = 'https://www.dokit.cn'\n  # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'\n  s.license          = { :type => 'Apache-2.0', :file => 'LICENSE' }\n  s.author           = { 'OrangeLab' => 'orange-lab@didiglobal.com' }\n  s.source           = { :git => 'https://github.com/didi/DoraemonKit.git', :tag => s.version.to_s }\n  # s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'\n\n  s.ios.deployment_target = '9.0'\n\n  s.default_subspec = 'Core'\n  s.pod_target_xcconfig = {\n    'DEFINES_MODULE' => 'YES'\n  }\n  \n  s.subspec 'CFoundation' do |ss|\n    ss.source_files = 'iOS/DoKit/Classes/CFoundation/*.{h,c}'\n    ss.compiler_flags = '-Wall', '-Wextra', '-Wpedantic', '-Werror', '-fvisibility=hidden'\n  end\n  \n  s.subspec 'Foundation' do |ss|\n    ss.source_files = 'iOS/DoKit/Classes/Foundation/**/*.{h,m}'\n    # language-extension-token warning be used to implement Objective-C typeof().\n    # ?: grammar\n    ss.compiler_flags = '-Wall', '-Wextra', '-Werror'\n    ss.dependency 'SocketRocket', '~> 0.6'\n    ss.dependency 'Mantle', '~> 2.2'\n  end\n  \n#  s.subspec 'CoreNG' do |ss|\n#    ss.dependency 'DoraemonKit/Foundation'\n#    ss.source_files = 'iOS/DoKit/Classes/Core/**/*.{h,m}'\n#    # language-extension-token warning be used to implement Objective-C typeof().\n#    # ?: grammar\n#    ss.compiler_flags = '-Wall', '-Wextra', '-Wpedantic', '-Werror', '-Wno-language-extension-token', '-Wno-gnu-conditional-omitted-operand'\n#    ss.resource_bundle = {\n#      'DoKitResource' => [\n#        'iOS/DoKit/Assets/Assets.xcassets',\n#        'iOS/DoKit/Assets/*.xib'\n#      ]\n#    }\n#    ss.dependency 'SocketRocket', '~> 0.6'\n#    ss.dependency 'Mantle', '~> 2.2'\n#  end\n\n  s.subspec 'EventSynthesize' do |ss|\n    ss.source_files = 'iOS/DoKit/Classes/EventSynthesize/*.{h,m}'\n    ss.compiler_flags = '-Wall', '-Wextra', '-Wpedantic', '-Werror', '-fvisibility=hidden', '-Wno-gnu-conditional-omitted-operand', '-Wno-pointer-arith','-Wno-gnu-zero-variadic-macro-arguments', '-Wno-unused-variable'\n    ss.framework = 'IOKit'\n    ss.dependency 'DoraemonKit/Foundation'\n    ss.dependency 'DoraemonKit/CFoundation'\n  end\n\n  s.subspec 'Core' do |ss| \n    ss.source_files = 'iOS/DoraemonKit/Src/Core/**/*.{h,m,c,mm}'\n    ss.resource_bundle = {\n      'DoraemonKit' => 'iOS/DoraemonKit/Resource/**/*'\n    }\n    ss.dependency 'GCDWebServer'\n    ss.dependency 'GCDWebServer/WebUploader'\n    ss.dependency 'GCDWebServer/WebDAV'\n    ss.dependency 'FMDB'\n  end\n\n  s.subspec 'WithLogger' do |ss| \n    ss.source_files = 'iOS/DoraemonKit/Src/Logger/**/*{.h,.m}'\n    ss.pod_target_xcconfig = {\n      'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) DoraemonWithLogger'\n    }\n    ss.dependency 'DoraemonKit/Core'\n    ss.dependency 'CocoaLumberjack'\n  end\n\n  s.subspec 'WithGPS' do |ss| \n    ss.source_files = 'iOS/DoraemonKit/Src/GPS/**/*{.h,.m}'\n    ss.pod_target_xcconfig = {\n      'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) DoraemonWithGPS'\n    }\n    ss.dependency 'DoraemonKit/Core'\n  end\n\n  s.subspec 'WithLoad' do |ss| \n    ss.source_files = 'iOS/DoraemonKit/Src/MethodUseTime/**/*{.h,.m}'\n    ss.pod_target_xcconfig = {\n      'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) DoraemonWithLoad'\n    }\n    ss.dependency 'DoraemonKit/Core'\n    # https://guides.cocoapods.org/syntax/podspec.html#vendored_frameworks\n    # TODO(ChasonTang): Should change to vendored_framework?\n    ss.vendored_frameworks = 'iOS/DoraemonKit/Framework/*.framework'\n  end\n\n  s.subspec 'WithWeex' do |ss| \n    ss.source_files = 'iOS/DoraemonKit/Src/Weex/**/*{.h,.m}'\n    ss.pod_target_xcconfig = {\n      'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) DoraemonWithWeex'\n    }\n    ss.dependency 'DoraemonKit/Core'\n    ss.dependency 'WeexSDK'\n    ss.dependency 'WXDevtool'\n  end\n\n  s.subspec 'WithDatabase' do |ss|\n    ss.source_files = 'iOS/DoraemonKit/Src/Database/**/*{.h,.m}'\n    ss.pod_target_xcconfig = {\n        'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) DoraemonWithDatabase'\n    }\n    ss.dependency 'DoraemonKit/Core'\n    ss.dependency 'YYDebugDatabase'\n  end\n\n  s.subspec 'WithMLeaksFinder' do |ss|\n    ss.source_files = 'iOS/DoraemonKit/Src/MLeaksFinder/**/*{.h,.m}'\n    ss.pod_target_xcconfig = {\n      'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) DoraemonWithMLeaksFinder'\n    }\n    ss.dependency 'DoraemonKit/Core'\n    # FBRetainCycleDetector 存在编译问题，使用pod发布时lint不通过，因此采用壳工程配置方式引入\n    #ss.dependency 'FBRetainCycleDetector'\n  end\n\n  s.subspec 'WithMultiControl' do |ss|\n    ss.source_files = 'iOS/DoraemonKit/Src/MultiControl/**/*{.h,.m}'\n    ss.pod_target_xcconfig = {\n      'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) DoraemonWithMultiControl'\n    }\n    ss.dependency 'DoraemonKit/Core'\n    ss.dependency 'DoraemonKit/Foundation'\n  end\nend\n"
  },
  {
    "path": "Flutter/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.packages\n.pub-cache/\n.pub/\n/build/\n\n# Web related\nlib/generated_plugin_registrant.dart\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Exceptions to above rules.\n!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages\n"
  },
  {
    "path": "Flutter/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: 8af6b2f038c1172e61d418869363a28dffec3cb4\n  channel: stable\n\nproject_type: app\n"
  },
  {
    "path": "Flutter/CHANGELOG.md",
    "content": "## [0.8.1-nullsafety.0] - 支持在主面板添加自定义入口kit;增加悬浮按钮点击事件回调能力\n## [0.8.0-nullsafety.0] - 支持Flutter2.0，支持nullsafety\n## [0.6.0] - 增加widget层级、颜色拾取、查看页面源码、页面启动耗时以及对齐标尺等功能\n## [0.2.15] - 引入Flutter 1.70.0版本的analysis_options.yaml代码规范\n## [0.2.14] - methodchannel增加黑名单过滤\n## [0.2.13] - 修复网络监控会影响非文本请求内容响应的问题\n## [0.2.12] - 修改页面层级，现在DoKitApp将以Stack存放用户传入的Widget,防止各种InheritedWidget影响到用户Widget\n## [0.2.11] - 修复一些UI异常问题；日志模块增加错误信息过滤，防止被错误日志冲刷掉；\n## [0.2.10] - 修复DoKitApp在某些case下获取高度为0的异常.\n## [0.2.9] - 修改DoKitApp类型，自定义Overlay容器防止各种InheritedWidget使用异常.\n## [0.2.8] - 修复网络请求返回结果乱码问题；增加method—channel耗时统计；日志/方法通道/网络请求增加清空按钮.\n## [0.2.7] - 网络请求增加RequFestHeaders信息展示，增加非文本类型的返回结果size展示.\n## [0.2.6] - 日志功能优化与bug修复.\n## [0.2.5] - 修改demo包名为example.\n## [0.2.4] - 发布pub包去除demo.\n## [0.2.3] - 修改目录结构，横屏适配.\n## [0.2.2] - 修改文档和logo.\n## [0.2.0] - 开源第一版.\n"
  },
  {
    "path": "Flutter/LICENSE",
    "content": "Apache License\n \n                        Version 2.0, January 2004\n \n                     http://www.apache.org/licenses/\n \n \n \n \nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n \n \n \n \n1. Definitions.\n \n \n \n \n   \"License\" shall mean the terms and conditions for use, reproduction,\n \n   and distribution as defined by Sections 1 through 9 of this document.\n \n \n \n \n   \"Licensor\" shall mean the copyright owner or entity authorized by\n \n   the copyright owner that is granting the License.\n \n \n \n \n   \"Legal Entity\" shall mean the union of the acting entity and all\n \n   other entities that control, are controlled by, or are under common\n \n   control with that entity. For the purposes of this definition,\n \n   \"control\" means (i) the power, direct or indirect, to cause the\n \n   direction or management of such entity, whether by contract or\n \n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n \n   outstanding shares, or (iii) beneficial ownership of such entity.\n \n \n \n \n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n \n   exercising permissions granted by this License.\n \n \n \n \n   \"Source\" form shall mean the preferred form for making modifications,\n \n   including but not limited to software source code, documentation\n \n   source, and configuration files.\n \n \n \n \n   \"Object\" form shall mean any form resulting from mechanical\n \n   transformation or translation of a Source form, including but\n \n   not limited to compiled object code, generated documentation,\n \n   and conversions to other media types.\n \n \n \n \n   \"Work\" shall mean the work of authorship, whether in Source or\n \n   Object form, made available under the License, as indicated by a\n \n   copyright notice that is included in or attached to the work\n \n   (an example is provided in the Appendix below).\n \n \n \n \n   \"Derivative Works\" shall mean any work, whether in Source or Object\n \n   form, that is based on (or derived from) the Work and for which the\n \n   editorial revisions, annotations, elaborations, or other modifications\n \n   represent, as a whole, an original work of authorship. For the purposes\n \n   of this License, Derivative Works shall not include works that remain\n \n   separable from, or merely link (or bind by name) to the interfaces of,\n \n   the Work and Derivative Works thereof.\n \n \n \n \n   \"Contribution\" shall mean any work of authorship, including\n \n   the original version of the Work and any modifications or additions\n \n   to that Work or Derivative Works thereof, that is intentionally\n \n   submitted to Licensor for inclusion in the Work by the copyright owner\n \n   or by an individual or Legal Entity authorized to submit on behalf of\n \n   the copyright owner. For the purposes of this definition, \"submitted\"\n \n   means any form of electronic, verbal, or written communication sent\n \n   to the Licensor or its representatives, including but not limited to\n \n   communication on electronic mailing lists, source code control systems,\n \n   and issue tracking systems that are managed by, or on behalf of, the\n \n   Licensor for the purpose of discussing and improving the Work, but\n \n   excluding communication that is conspicuously marked or otherwise\n \n   designated in writing by the copyright owner as \"Not a Contribution.\"\n \n \n \n \n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n \n   on behalf of whom a Contribution has been received by Licensor and\n \n   subsequently incorporated within the Work.\n \n \n \n \n2. Grant of Copyright License. Subject to the terms and conditions of\n \n   this License, each Contributor hereby grants to You a perpetual,\n \n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n \n   copyright license to reproduce, prepare Derivative Works of,\n \n   publicly display, publicly perform, sublicense, and distribute the\n \n   Work and such Derivative Works in Source or Object form.\n \n \n \n \n3. Grant of Patent License. Subject to the terms and conditions of\n \n   this License, each Contributor hereby grants to You a perpetual,\n \n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n \n   (except as stated in this section) patent license to make, have made,\n \n   use, offer to sell, sell, import, and otherwise transfer the Work,\n \n   where such license applies only to those patent claims licensable\n \n   by such Contributor that are necessarily infringed by their\n \n   Contribution(s) alone or by combination of their Contribution(s)\n \n   with the Work to which such Contribution(s) was submitted. If You\n \n   institute patent litigation against any entity (including a\n \n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n \n   or a Contribution incorporated within the Work constitutes direct\n \n   or contributory patent infringement, then any patent licenses\n \n   granted to You under this License for that Work shall terminate\n \n   as of the date such litigation is filed.\n \n \n \n \n4. Redistribution. You may reproduce and distribute copies of the\n \n   Work or Derivative Works thereof in any medium, with or without\n \n   modifications, and in Source or Object form, provided that You\n \n   meet the following conditions:\n \n \n \n \n   (a) You must give any other recipients of the Work or\n \n       Derivative Works a copy of this License; and\n \n \n \n \n   (b) You must cause any modified files to carry prominent notices\n \n       stating that You changed the files; and\n \n \n \n \n   (c) You must retain, in the Source form of any Derivative Works\n \n       that You distribute, all copyright, patent, trademark, and\n \n       attribution notices from the Source form of the Work,\n \n       excluding those notices that do not pertain to any part of\n \n       the Derivative Works; and\n \n \n \n \n   (d) If the Work includes a \"NOTICE\" text file as part of its\n \n       distribution, then any Derivative Works that You distribute must\n \n       include a readable copy of the attribution notices contained\n \n       within such NOTICE file, excluding those notices that do not\n \n       pertain to any part of the Derivative Works, in at least one\n \n       of the following places: within a NOTICE text file distributed\n \n       as part of the Derivative Works; within the Source form or\n \n       documentation, if provided along with the Derivative Works; or,\n \n       within a display generated by the Derivative Works, if and\n \n       wherever such third-party notices normally appear. The contents\n \n       of the NOTICE file are for informational purposes only and\n \n       do not modify the License. You may add Your own attribution\n \n       notices within Derivative Works that You distribute, alongside\n \n       or as an addendum to the NOTICE text from the Work, provided\n \n       that such additional attribution notices cannot be construed\n \n       as modifying the License.\n \n \n \n \n   You may add Your own copyright statement to Your modifications and\n \n   may provide additional or different license terms and conditions\n \n   for use, reproduction, or distribution of Your modifications, or\n \n   for any such Derivative Works as a whole, provided Your use,\n \n   reproduction, and distribution of the Work otherwise complies with\n \n   the conditions stated in this License.\n \n \n \n \n5. Submission of Contributions. Unless You explicitly state otherwise,\n \n   any Contribution intentionally submitted for inclusion in the Work\n \n   by You to the Licensor shall be under the terms and conditions of\n \n   this License, without any additional terms or conditions.\n \n   Notwithstanding the above, nothing herein shall supersede or modify\n \n   the terms of any separate license agreement you may have executed\n \n   with Licensor regarding such Contributions.\n \n \n \n \n6. Trademarks. This License does not grant permission to use the trade\n \n   names, trademarks, service marks, or product names of the Licensor,\n \n   except as required for reasonable and customary use in describing the\n \n   origin of the Work and reproducing the content of the NOTICE file.\n \n \n \n \n7. Disclaimer of Warranty. Unless required by applicable law or\n \n   agreed to in writing, Licensor provides the Work (and each\n \n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n \n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n \n   implied, including, without limitation, any warranties or conditions\n \n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n \n   PARTICULAR PURPOSE. You are solely responsible for determining the\n \n   appropriateness of using or redistributing the Work and assume any\n \n   risks associated with Your exercise of permissions under this License.\n \n \n \n \n8. Limitation of Liability. In no event and under no legal theory,\n \n   whether in tort (including negligence), contract, or otherwise,\n \n   unless required by applicable law (such as deliberate and grossly\n \n   negligent acts) or agreed to in writing, shall any Contributor be\n \n   liable to You for damages, including any direct, indirect, special,\n \n   incidental, or consequential damages of any character arising as a\n \n   result of this License or out of the use or inability to use the\n \n   Work (including but not limited to damages for loss of goodwill,\n \n   work stoppage, computer failure or malfunction, or any and all\n \n   other commercial damages or losses), even if such Contributor\n \n   has been advised of the possibility of such damages.\n \n \n \n \n9. Accepting Warranty or Additional Liability. While redistributing\n \n   the Work or Derivative Works thereof, You may choose to offer,\n \n   and charge a fee for, acceptance of support, warranty, indemnity,\n \n   or other liability obligations and/or rights consistent with this\n \n   License. However, in accepting such obligations, You may act only\n \n   on Your own behalf and on Your sole responsibility, not on behalf\n \n   of any other Contributor, and only if You agree to indemnify,\n \n   defend, and hold each Contributor harmless for any liability\n \n   incurred by, or claims asserted against, such Contributor by reason\n \n   of your accepting any such warranty or additional liability.\n \n \n \n \nEND OF TERMS AND CONDITIONS\n \n \n \n \nAPPENDIX: How to apply the Apache License to your work.\n \n \n \n \n   To apply the Apache License to your work, attach the following\n \n   boilerplate notice, with the fields enclosed by brackets \"{}\"\n \n   replaced with your own identifying information. (Don't include\n \n   the brackets!)  The text should be enclosed in the appropriate\n \n   comment syntax for the file format. We also recommend that a\n \n   file or class name and description of purpose be included on the\n \n   same \"printed page\" as the copyright notice for easier\n \n   identification within third-party archives.\n \n \n \n \nCopyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n \n \n \n \nLicensed under the Apache License, Version 2.0 (the \"License\");\n \nyou may not use this file except in compliance with the License.\n \nYou may obtain a copy of the License at\n \n \n \n \n    http://www.apache.org/licenses/LICENSE-2.0\n \n \n \n \nUnless required by applicable law or agreed to in writing, software\n \ndistributed under the License is distributed on an \"AS IS\" BASIS,\n \nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n \nSee the License for the specific language governing permissions and\n \nlimitations under the License."
  },
  {
    "path": "Flutter/README.md",
    "content": "**目录**\n- [DoKit Flutter版](#dokit-flutter版)\n- [支持 Flutter 版本](#支持flutter版本)\n- [Pub地址](#pub地址)\n- [接入](#接入)\n- [功能简介](#功能简介)\n  - [全部组件](#全部组件)\n  - [日志查看](#日志查看)\n  - [网络请求](#网络请求)\n  - [Method Channel信息](#method-channel信息)\n  - [路由信息](#路由信息)\n  - [帧率](#帧率)\n  - [内存](#内存)\n  - [基本信息](#基本信息)\n  - [控件检查](#控件检查)\n  - [颜色拾取](#颜色拾取)\n  - [Widget层级](#widget层级)\n  - [页面源码查看](#页面源码查看)\n  - [页面启动耗时](#页面启动耗时)\n  - [自定义入口](#自定义入口)\n\n# DoKit Flutter版\n\n内测版本，目前提供了日志、method channel 信息、路由信息、网络抓包、帧率、设备与内存信息查看、控件信息查看、颜色拾取、启动耗时、查看源码、查看 widget 的 build 链以及对齐标尺的功能.\n\n## 支持Flutter版本\n\nversion>=1.17.5，其余版本未做过兼容性测试。支持 flutter2.0 的分支为`0.8.0-nullsafety.0`，后期维护主要会基于该版本进行。\n\n## Pub地址\n\n[DoKit For Flutter](https://pub.dev/packages/dokit)\n\n## 接入\n1.在 `pubspect.yaml` 文件的 dependencies 节点添加 pub 依赖\n\n```yaml\ndependencies:\n  dokit: ^0.8.0-nullsafety.0\n```\n\n2.在 main 函数入口初始化。 DoKit 使用 runZone 的方式进行日志捕获，方法通道的捕获，如果你的 app 需要使用同样的方式会有冲突。\n\n```dart\nvoid main() => {\n  DoKit.runApp(\n    app: DoKitApp(const MyApp()),\n    // 是否在release包内使用，默认release包会禁用\n    useInRelease: true,\n    // 选择性控制是否使用dokit中的runZonedGuarded,false: 禁用；true: 启用\n    useRunZoned: false,\n    releaseAction: () => {\n      // release模式下执行该函数，一些用到runZone之类实现的可以放到这里，该值为空则会直接调用系统的runApp(MyApp())，\n    },\n  );\n}\n\n```\n\n**注：谷歌提供的 DevTool 会折叠非主工程内实例化的 widget（根据 source file 是否属于当前工程），DoKit 需要实例化一个 wrapper widget 用以展示各种 overlay，\n如果在 package 内去声明这个 wrapper，会导致左边树全部被折叠。故这里要求在 main 文件内使用 `DoKitApp(MyApp())` 的方式来初始化入口**\n另外提供了一个异步创建入口 Widget的方式，需要异步构建widget的情况。(有些库会在异步构建 Widget 的时候调用 WidgetFlutterBinding.ensureInitialized()，影响 DoKit的 method channel 监控和日志监控，需要延迟到 runZone 内执行)\n\n```dart\nvoid main() {\n  DoKit.runApp(\n    appCreator: () async => DoKitApp(\n      await crateApp(),\n    ),\n  );\n}\n\nFuture<Widget> crateApp() async {\n  // 一些初始化操作\n  return MyApp();\n}\n```\n\n\n**参数说明**\n\n\n参数 | 返回类型 | 说明 | 是否必须\n---|---|---|---\napp | DoKitApp | 返回被DoKitApp类包装的根布局 | app和appCreator至少需要设置一个同时设置时app参数生效\nappCreator | DoKitAppCreator | 异步返回根布局 | 同上\nuseInRelease | bool |是否在release模式下显示DoKit | x\nlogCallback | LogCallback | 调用print方法打印日志时被回调 | x\nexceptionCallback | ExceptionCallback | 异常回调 | x\nmethodChannelBlackList | List<String> | 过滤方法通道的黑名单 | x\nreleaseAction | Function | release模式下执行该函数，该值为空则会直接调用系统的runApp |x\n\n\n## 功能简介\n\n### 全部组件\n\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/AuETMp2dp11619684586454.png\"  width=\"300px\"  />\n\n当前版本 DoKit 支持的所有功能全览。常驻工具为显示在底部 tab 栏的组件，可通过拖动将组件放置或移出常驻工具。\n\n\n### 日志查看\n\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/apwIxs7A341609765573351.jpg\"  width=\"300px\"  />\n\n\n查看使用 print 方式打印出来的日志，捕获的异常会以红色显示。超过7行的日志会自动折叠，点击可展开。长按复制日志到剪贴板。\n\n\n### 网络请求\n\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/nEN7uos9OV1609765604202.jpg\"  width=\"300px\"  />\n\n\n可以捕获通过 flutter httpclient 发出的网络请求，主流的 http、dio 库底层也是通过 httpclient 实现的，也能捕获。\n\n\n### Method Channel信息\n\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/qH6jtyNvqp1609765652146.jpg\"  width=\"300px\"  />\n\n\n可以展示从 dart 端到 native 和从 native 端到 dart 端的方法调用、参数、返回结果。\n\n### 路由信息\n\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/VLyiReklD41609765682140.jpg\"  width=\"300px\"  />\n\n\n展示当前页面的路由信息，当存在多层 Navigator 组件嵌套时，会展示多层的路由信息。\n\n**注：当前查找栈顶widget是通过遍历整棵widget tree的方式，如果添加了overlay，栈顶widget会始终指向overlay，导致该功能读取数据异常。**\n\n\n### 帧率\n\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/Xno9FVbweg1609765703740.jpg\"  width=\"300px\"  />\n\n\n展示最近240帧的耗时情况，每次进入该页面刷新。debug 模式下帧率会普遍偏高，profile 和 release 模式下会比较正常。\n\n### 内存\n\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/9ESwEmODlR1609765729941.jpg\"  width=\"300px\"  />\n\n\n当前已使用的内存和最大内存；底部搜索栏可以显示指定的类的对象数量和占用内存。\n\n**注：该功能通过VMService获取数据，release模式下无法使用**\n\n### 基本信息\n\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/8brZZzWijZ1609765750681.jpg\"  width=\"300px\"  />\n\n\n展示当前 dart 虚拟机进程、cpu、版本信息；当前 app 包名和 dart 工程构建版本信息；\n\n**注：该功能通过VMService获取数据，release模式下无法使用。flutter版本号需要flutter attach后才可获取**\n\n### 控件检查\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/qXec9UMCfi1619673904927.png\"  width=\"300px\"  />\n\n查看当前页面上的控件信息，包含位置、大小、源码信息、对齐标尺和查看build链等。\n\n**注：源码信息只有在debug模式下才可获取到。同路由功能，在存在Overlay的情况下功能会异常**\n\n### 颜色拾取\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/4MYRNqqcZh1619673900891.png\"  width=\"300px\"  />\n\n查看当前页面任何位置对应的像素点的 RGBA 颜色值，方便UI的调试和获取像素点的颜色\n\n### Widget层级\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/GmjvVDp4Ye1619673908393.png\"  width=\"300px\"  />\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/sGd73y7uoc1619673910771.png\"  width=\"300px\"  />\n\n查看当前选中 widget 的树层级，以及它 renderObject 的详细 build 链等信息\n\n### 页面源码查看\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/e7Pbo95nJ71619665430550.jpg\"  width=\"300px\"  />\n\n查看当前所在页面的源代码，支持语法高亮显示\n\n**注：源码信息只有在debug模式下才可获取到。同路由功能，在存在Overlay的情况下功能会异常**\n\n### 页面启动耗时\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/gDkBh4a87P1619673916288.png\"  width=\"300px\"  />\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/z1wWlYqZDg1619674872051.png\"  width=\"300px\"  />\n\n获取页面的启动耗时, \n框架已做无侵入的注入 NavigatorObserver。但是在较复杂的 App 构建时可能失效，需要手动添加`DokitNavigatorObserver`\n\n**注：页面启动耗时信息只有在profile或release模式下才有意义**\n\n### 自定义入口\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/0b2jdWmxbK1637739570393.png\" width=\"300px\">\n\n业务方可以依照自己需要，依托自定义入口的功能（支持添加多组），在dokit中添加自己的功能启动入口。比如上图中的“业务专区”。\n\n\n\n"
  },
  {
    "path": "Flutter/analysis_options.yaml",
    "content": "#  default Google rule(pedantic)\ninclude: package:pedantic/analysis_options.yaml\n\nanalyzer:\n  enable-experiment:\n    - extension-methods"
  },
  {
    "path": "Flutter/example/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.packages\n.pub-cache/\n.pub/\nbuild/\nlib/generated_plugin_registrant.dart\n\n\n# Android related\n**/android/**/gradle-wrapper.jar\n**/android/.gradle\n**/android/captures/\n**/android/gradlew\n**/android/gradlew.bat\n**/android/local.properties\n**/android/**/GeneratedPluginRegistrant.java\n\n# iOS/XCode related\n**/ios/**/*.mode1v3\n**/ios/**/*.mode2v3\n**/ios/**/*.moved-aside\n**/ios/**/*.pbxuser\n**/ios/**/*.perspectivev3\n**/ios/**/*sync/\n**/ios/**/.sconsign.dblite\n**/ios/**/.tags*\n**/ios/**/.vagrant/\n**/ios/**/DerivedData/\n**/ios/**/Icon?\n**/ios/**/Pods/\n**/ios/**/.symlinks/\n**/ios/**/profile\n**/ios/**/xcuserdata\n**/ios/.generated/\n**/ios/Flutter/App.framework\n**/ios/Flutter/Flutter.framework\n**/ios/Flutter/Flutter.podspec\n**/ios/Flutter/Generated.xcconfig\n**/ios/Flutter/app.flx\n**/ios/Flutter/app.zip\n**/ios/Flutter/flutter_assets/\n**/ios/Flutter/flutter_export_environment.sh\n**/ios/ServiceDefinitions.json\n**/ios/Runner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!**/ios/**/default.mode1v3\n!**/ios/**/default.mode2v3\n!**/ios/**/default.pbxuser\n!**/ios/**/default.perspectivev3\n!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages\n"
  },
  {
    "path": "Flutter/example/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: fba99f6cf9a14512e461e3122c8ddfaa25394e89\n  channel: stable\n\nproject_type: package\n"
  },
  {
    "path": "Flutter/example/LICENSE",
    "content": "Apache License\n \n                        Version 2.0, January 2004\n \n                     http://www.apache.org/licenses/\n \n \n \n \nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n \n \n \n \n1. Definitions.\n \n \n \n \n   \"License\" shall mean the terms and conditions for use, reproduction,\n \n   and distribution as defined by Sections 1 through 9 of this document.\n \n \n \n \n   \"Licensor\" shall mean the copyright owner or entity authorized by\n \n   the copyright owner that is granting the License.\n \n \n \n \n   \"Legal Entity\" shall mean the union of the acting entity and all\n \n   other entities that control, are controlled by, or are under common\n \n   control with that entity. For the purposes of this definition,\n \n   \"control\" means (i) the power, direct or indirect, to cause the\n \n   direction or management of such entity, whether by contract or\n \n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n \n   outstanding shares, or (iii) beneficial ownership of such entity.\n \n \n \n \n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n \n   exercising permissions granted by this License.\n \n \n \n \n   \"Source\" form shall mean the preferred form for making modifications,\n \n   including but not limited to software source code, documentation\n \n   source, and configuration files.\n \n \n \n \n   \"Object\" form shall mean any form resulting from mechanical\n \n   transformation or translation of a Source form, including but\n \n   not limited to compiled object code, generated documentation,\n \n   and conversions to other media types.\n \n \n \n \n   \"Work\" shall mean the work of authorship, whether in Source or\n \n   Object form, made available under the License, as indicated by a\n \n   copyright notice that is included in or attached to the work\n \n   (an example is provided in the Appendix below).\n \n \n \n \n   \"Derivative Works\" shall mean any work, whether in Source or Object\n \n   form, that is based on (or derived from) the Work and for which the\n \n   editorial revisions, annotations, elaborations, or other modifications\n \n   represent, as a whole, an original work of authorship. For the purposes\n \n   of this License, Derivative Works shall not include works that remain\n \n   separable from, or merely link (or bind by name) to the interfaces of,\n \n   the Work and Derivative Works thereof.\n \n \n \n \n   \"Contribution\" shall mean any work of authorship, including\n \n   the original version of the Work and any modifications or additions\n \n   to that Work or Derivative Works thereof, that is intentionally\n \n   submitted to Licensor for inclusion in the Work by the copyright owner\n \n   or by an individual or Legal Entity authorized to submit on behalf of\n \n   the copyright owner. For the purposes of this definition, \"submitted\"\n \n   means any form of electronic, verbal, or written communication sent\n \n   to the Licensor or its representatives, including but not limited to\n \n   communication on electronic mailing lists, source code control systems,\n \n   and issue tracking systems that are managed by, or on behalf of, the\n \n   Licensor for the purpose of discussing and improving the Work, but\n \n   excluding communication that is conspicuously marked or otherwise\n \n   designated in writing by the copyright owner as \"Not a Contribution.\"\n \n \n \n \n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n \n   on behalf of whom a Contribution has been received by Licensor and\n \n   subsequently incorporated within the Work.\n \n \n \n \n2. Grant of Copyright License. Subject to the terms and conditions of\n \n   this License, each Contributor hereby grants to You a perpetual,\n \n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n \n   copyright license to reproduce, prepare Derivative Works of,\n \n   publicly display, publicly perform, sublicense, and distribute the\n \n   Work and such Derivative Works in Source or Object form.\n \n \n \n \n3. Grant of Patent License. Subject to the terms and conditions of\n \n   this License, each Contributor hereby grants to You a perpetual,\n \n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n \n   (except as stated in this section) patent license to make, have made,\n \n   use, offer to sell, sell, import, and otherwise transfer the Work,\n \n   where such license applies only to those patent claims licensable\n \n   by such Contributor that are necessarily infringed by their\n \n   Contribution(s) alone or by combination of their Contribution(s)\n \n   with the Work to which such Contribution(s) was submitted. If You\n \n   institute patent litigation against any entity (including a\n \n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n \n   or a Contribution incorporated within the Work constitutes direct\n \n   or contributory patent infringement, then any patent licenses\n \n   granted to You under this License for that Work shall terminate\n \n   as of the date such litigation is filed.\n \n \n \n \n4. Redistribution. You may reproduce and distribute copies of the\n \n   Work or Derivative Works thereof in any medium, with or without\n \n   modifications, and in Source or Object form, provided that You\n \n   meet the following conditions:\n \n \n \n \n   (a) You must give any other recipients of the Work or\n \n       Derivative Works a copy of this License; and\n \n \n \n \n   (b) You must cause any modified files to carry prominent notices\n \n       stating that You changed the files; and\n \n \n \n \n   (c) You must retain, in the Source form of any Derivative Works\n \n       that You distribute, all copyright, patent, trademark, and\n \n       attribution notices from the Source form of the Work,\n \n       excluding those notices that do not pertain to any part of\n \n       the Derivative Works; and\n \n \n \n \n   (d) If the Work includes a \"NOTICE\" text file as part of its\n \n       distribution, then any Derivative Works that You distribute must\n \n       include a readable copy of the attribution notices contained\n \n       within such NOTICE file, excluding those notices that do not\n \n       pertain to any part of the Derivative Works, in at least one\n \n       of the following places: within a NOTICE text file distributed\n \n       as part of the Derivative Works; within the Source form or\n \n       documentation, if provided along with the Derivative Works; or,\n \n       within a display generated by the Derivative Works, if and\n \n       wherever such third-party notices normally appear. The contents\n \n       of the NOTICE file are for informational purposes only and\n \n       do not modify the License. You may add Your own attribution\n \n       notices within Derivative Works that You distribute, alongside\n \n       or as an addendum to the NOTICE text from the Work, provided\n \n       that such additional attribution notices cannot be construed\n \n       as modifying the License.\n \n \n \n \n   You may add Your own copyright statement to Your modifications and\n \n   may provide additional or different license terms and conditions\n \n   for use, reproduction, or distribution of Your modifications, or\n \n   for any such Derivative Works as a whole, provided Your use,\n \n   reproduction, and distribution of the Work otherwise complies with\n \n   the conditions stated in this License.\n \n \n \n \n5. Submission of Contributions. Unless You explicitly state otherwise,\n \n   any Contribution intentionally submitted for inclusion in the Work\n \n   by You to the Licensor shall be under the terms and conditions of\n \n   this License, without any additional terms or conditions.\n \n   Notwithstanding the above, nothing herein shall supersede or modify\n \n   the terms of any separate license agreement you may have executed\n \n   with Licensor regarding such Contributions.\n \n \n \n \n6. Trademarks. This License does not grant permission to use the trade\n \n   names, trademarks, service marks, or product names of the Licensor,\n \n   except as required for reasonable and customary use in describing the\n \n   origin of the Work and reproducing the content of the NOTICE file.\n \n \n \n \n7. Disclaimer of Warranty. Unless required by applicable law or\n \n   agreed to in writing, Licensor provides the Work (and each\n \n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n \n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n \n   implied, including, without limitation, any warranties or conditions\n \n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n \n   PARTICULAR PURPOSE. You are solely responsible for determining the\n \n   appropriateness of using or redistributing the Work and assume any\n \n   risks associated with Your exercise of permissions under this License.\n \n \n \n \n8. Limitation of Liability. In no event and under no legal theory,\n \n   whether in tort (including negligence), contract, or otherwise,\n \n   unless required by applicable law (such as deliberate and grossly\n \n   negligent acts) or agreed to in writing, shall any Contributor be\n \n   liable to You for damages, including any direct, indirect, special,\n \n   incidental, or consequential damages of any character arising as a\n \n   result of this License or out of the use or inability to use the\n \n   Work (including but not limited to damages for loss of goodwill,\n \n   work stoppage, computer failure or malfunction, or any and all\n \n   other commercial damages or losses), even if such Contributor\n \n   has been advised of the possibility of such damages.\n \n \n \n \n9. Accepting Warranty or Additional Liability. While redistributing\n \n   the Work or Derivative Works thereof, You may choose to offer,\n \n   and charge a fee for, acceptance of support, warranty, indemnity,\n \n   or other liability obligations and/or rights consistent with this\n \n   License. However, in accepting such obligations, You may act only\n \n   on Your own behalf and on Your sole responsibility, not on behalf\n \n   of any other Contributor, and only if You agree to indemnify,\n \n   defend, and hold each Contributor harmless for any liability\n \n   incurred by, or claims asserted against, such Contributor by reason\n \n   of your accepting any such warranty or additional liability.\n \n \n \n \nEND OF TERMS AND CONDITIONS\n \n \n \n \nAPPENDIX: How to apply the Apache License to your work.\n \n \n \n \n   To apply the Apache License to your work, attach the following\n \n   boilerplate notice, with the fields enclosed by brackets \"{}\"\n \n   replaced with your own identifying information. (Don't include\n \n   the brackets!)  The text should be enclosed in the appropriate\n \n   comment syntax for the file format. We also recommend that a\n \n   file or class name and description of purpose be included on the\n \n   same \"printed page\" as the copyright notice for easier\n \n   identification within third-party archives.\n \n \n \n \nCopyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n \n \n \n \nLicensed under the Apache License, Version 2.0 (the \"License\");\n \nyou may not use this file except in compliance with the License.\n \nYou may obtain a copy of the License at\n \n \n \n \n    http://www.apache.org/licenses/LICENSE-2.0\n \n \n \n \nUnless required by applicable law or agreed to in writing, software\n \ndistributed under the License is distributed on an \"AS IS\" BASIS,\n \nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n \nSee the License for the specific language governing permissions and\n \nlimitations under the License."
  },
  {
    "path": "Flutter/example/android/.gitignore",
    "content": "gradle-wrapper.jar\n/.gradle\n/captures/\n/gradlew\n/gradlew.bat\n/local.properties\nGeneratedPluginRegistrant.java\n"
  },
  {
    "path": "Flutter/example/android/app/build.gradle",
    "content": "def localProperties = new Properties()\ndef localPropertiesFile = rootProject.file('local.properties')\nif (localPropertiesFile.exists()) {\n    localPropertiesFile.withReader('UTF-8') { reader ->\n        localProperties.load(reader)\n    }\n}\n\ndef flutterRoot = localProperties.getProperty('flutter.sdk')\nif (flutterRoot == null) {\n    throw new GradleException(\"Flutter SDK not found. Define location with flutter.sdk in the local.properties file.\")\n}\n\ndef flutterVersionCode = localProperties.getProperty('flutter.versionCode')\nif (flutterVersionCode == null) {\n    flutterVersionCode = '1'\n}\n\ndef flutterVersionName = localProperties.getProperty('flutter.versionName')\nif (flutterVersionName == null) {\n    flutterVersionName = '1.0'\n}\n\napply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply from: \"$flutterRoot/packages/flutter_tools/gradle/flutter.gradle\"\n\nandroid {\n    compileSdkVersion 28\n\n    sourceSets {\n        main.java.srcDirs += 'src/main/kotlin'\n    }\n\n    lintOptions {\n        disable 'InvalidPackage'\n    }\n\n    defaultConfig {\n        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).\n        applicationId \"com.linjizong.flutter_app\"\n        minSdkVersion 16\n        targetSdkVersion 28\n        versionCode flutterVersionCode.toInteger()\n        versionName flutterVersionName\n    }\n\n    buildTypes {\n        release {\n            // TODO: Add your own signing config for the release build.\n            // Signing with the debug keys for now, so `flutter run --release` works.\n            signingConfig signingConfigs.debug\n        }\n    }\n}\n\nflutter {\n    source '../..'\n}\n\ndependencies {\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n}\n"
  },
  {
    "path": "Flutter/example/android/app/src/debug/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.linjizong.flutter_app\">\n    <!-- Flutter needs it to communicate with the running application\n         to allow setting breakpoints, to provide hot reload, etc.\n    -->\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n</manifest>\n"
  },
  {
    "path": "Flutter/example/android/app/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.linjizong.flutter_app\">\n    <!-- io.flutter.app.FlutterApplication is an android.app.Application that\n         calls FlutterMain.startInitialization(this); in its onCreate method.\n         In most cases you can leave this as-is, but you if you want to provide\n         additional functionality it is fine to subclass or reimplement\n         FlutterApplication and put your custom class here. -->\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <application\n        android:name=\"io.flutter.app.FlutterApplication\"\n        android:label=\"flutter_app\"\n        android:icon=\"@mipmap/ic_launcher\">\n        <activity\n            android:name=\".MainActivity\"\n            android:launchMode=\"singleTop\"\n            android:theme=\"@style/LaunchTheme\"\n            android:configChanges=\"orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode\"\n            android:hardwareAccelerated=\"true\"\n            android:windowSoftInputMode=\"adjustResize\">\n            <!-- Specifies an Android theme to apply to this Activity as soon as\n                 the Android process has started. This theme is visible to the user\n                 while the Flutter UI initializes. After that, this theme continues\n                 to determine the Window background behind the Flutter UI. -->\n            <meta-data\n              android:name=\"io.flutter.embedding.android.NormalTheme\"\n              android:resource=\"@style/NormalTheme\"\n              />\n            <!-- Displays an Android View that continues showing the launch screen\n                 Drawable until Flutter paints its first frame, then this splash\n                 screen fades out. A splash screen is useful to avoid any kit.visual\n                 gap between the end of Android's launch screen and the painting of\n                 Flutter's first frame. -->\n            <meta-data\n              android:name=\"io.flutter.embedding.android.SplashScreenDrawable\"\n              android:resource=\"@drawable/launch_background\"\n              />\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n        <!-- Don't delete the meta-data below.\n             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->\n        <meta-data\n            android:name=\"flutterEmbedding\"\n            android:value=\"2\" />\n    </application>\n</manifest>\n"
  },
  {
    "path": "Flutter/example/android/app/src/main/kotlin/com/linjizong/flutter_app/MainActivity.kt",
    "content": "package com.linjizong.flutter_app\n\nimport io.flutter.embedding.android.FlutterActivity\n\nclass MainActivity: FlutterActivity() {\n}\n"
  },
  {
    "path": "Flutter/example/android/app/src/main/res/drawable/launch_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Modify this file to customize your launch splash screen -->\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@android:color/white\" />\n\n    <!-- You can insert your own image assets here -->\n    <!-- <item>\n        <bitmap\n            android:gravity=\"center\"\n            android:src=\"@mipmap/launch_image\" />\n    </item> -->\n</layer-list>\n"
  },
  {
    "path": "Flutter/example/android/app/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- Theme applied to the Android Window while the process is starting -->\n    <style name=\"LaunchTheme\" parent=\"@android:style/Theme.Black.NoTitleBar\">\n        <!-- Show a splash screen on the activity. Automatically removed when\n             Flutter draws its first frame -->\n        <item name=\"android:windowBackground\">@drawable/launch_background</item>\n    </style>\n    <!-- Theme applied to the Android Window as soon as the process has started.\n         This theme determines the color of the Android Window while your\n         Flutter UI initializes, as well as behind your Flutter UI while its\n         running.\n         \n         This Theme is only used starting with V2 of Flutter's Android embedding. -->\n    <style name=\"NormalTheme\" parent=\"@android:style/Theme.Black.NoTitleBar\">\n        <item name=\"android:windowBackground\">@android:color/white</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "Flutter/example/android/app/src/profile/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.linjizong.flutter_app\">\n    <!-- Flutter needs it to communicate with the running application\n         to allow setting breakpoints, to provide hot reload, etc.\n    -->\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n</manifest>\n"
  },
  {
    "path": "Flutter/example/android/build.gradle",
    "content": "buildscript {\n    ext.kotlin_version = '1.3.72'\n    repositories {\n        maven {\n            url 'http://artifactory.intra.xiaojukeji.com/artifactory/public/'\n        }\n        maven {\n            url 'http://maven.aliyun.com/nexus/content/groups/public/'\n        }\n        maven {\n            url \"https://storage.googleapis.com/download.flutter.io\"\n        }\n        mavenCentral()\n        mavenLocal()\n//        maven {\n//            // 引入rn本地aar\n//            url '/Users/didi/AndroidStudioProjects/BeetlesSDK/lib/local'\n//        }\n//        maven {\n//            url \"$rootDir/component/local\" // 引入rn本地aar\n//        }\n        maven { url \"https://jitpack.io\" }\n        google()\n    }\n\n    dependencies {\n        classpath 'com.android.tools.build:gradle:4.2.1'\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n    }\n}\n\nallprojects {\n    repositories {\n        maven {\n            url 'http://artifactory.intra.xiaojukeji.com/artifactory/public/'\n        }\n        maven {\n            url 'http://maven.aliyun.com/nexus/content/groups/public/'\n        }\n        maven {\n            url \"https://storage.googleapis.com/download.flutter.io\"\n        }\n        mavenCentral()\n        mavenLocal()\n//        maven {\n//            // 引入rn本地aar\n//            url '/Users/didi/AndroidStudioProjects/BeetlesSDK/lib/local'\n//        }\n//        maven {\n//            url \"$rootDir/component/local\" // 引入rn本地aar\n//        }\n        maven { url \"https://jitpack.io\" }\n        google()\n    }\n}\n\nrootProject.buildDir = '../build'\nsubprojects {\n    project.buildDir = \"${rootProject.buildDir}/${project.name}\"\n}\nsubprojects {\n    project.evaluationDependsOn(':app')\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "Flutter/example/android/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Fri Jun 23 08:50:38 CEST 2017\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.7.1-bin.zip\n"
  },
  {
    "path": "Flutter/example/android/gradle.properties",
    "content": "org.gradle.jvmargs=-Xmx1536M\nandroid.enableR8=true\nandroid.useAndroidX=true\nandroid.enableJetifier=true\nandroid.injected.testOnly=false\n\n"
  },
  {
    "path": "Flutter/example/android/settings.gradle",
    "content": "// Copyright 2014 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\ninclude ':app'\n\ndef localPropertiesFile = new File(rootProject.projectDir, \"local.properties\")\ndef properties = new Properties()\n\nassert localPropertiesFile.exists()\nlocalPropertiesFile.withReader(\"UTF-8\") { reader -> properties.load(reader) }\n\ndef flutterSdkPath = properties.getProperty(\"flutter.sdk\")\nassert flutterSdkPath != null, \"flutter.sdk not set in local.properties\"\napply from: \"$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle\"\n"
  },
  {
    "path": "Flutter/example/ios/.gitignore",
    "content": "*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3\n"
  },
  {
    "path": "Flutter/example/ios/Flutter/.last_build_id",
    "content": "29db02f4017b5d46dbbbd5412d6a52d4"
  },
  {
    "path": "Flutter/example/ios/Flutter/AppFrameworkInfo.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n  <key>CFBundleDevelopmentRegion</key>\n  <string>$(DEVELOPMENT_LANGUAGE)</string>\n  <key>CFBundleExecutable</key>\n  <string>App</string>\n  <key>CFBundleIdentifier</key>\n  <string>io.flutter.flutter.app</string>\n  <key>CFBundleInfoDictionaryVersion</key>\n  <string>6.0</string>\n  <key>CFBundleName</key>\n  <string>App</string>\n  <key>CFBundlePackageType</key>\n  <string>FMWK</string>\n  <key>CFBundleShortVersionString</key>\n  <string>1.0</string>\n  <key>CFBundleSignature</key>\n  <string>????</string>\n  <key>CFBundleVersion</key>\n  <string>1.0</string>\n  <key>MinimumOSVersion</key>\n  <string>8.0</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Flutter/example/ios/Flutter/Debug.xcconfig",
    "content": "#include \"Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig\"\n#include \"Generated.xcconfig\"\n"
  },
  {
    "path": "Flutter/example/ios/Flutter/Release.xcconfig",
    "content": "#include \"Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig\"\n#include \"Generated.xcconfig\"\n"
  },
  {
    "path": "Flutter/example/ios/Podfile",
    "content": "# Uncomment this line to define a global platform for your project\n# platform :ios, '9.0'\n\n# CocoaPods analytics sends network stats synchronously affecting flutter build latency.\nENV['COCOAPODS_DISABLE_STATS'] = 'true'\n\nproject 'Runner', {\n  'Debug' => :debug,\n  'Profile' => :release,\n  'Release' => :release,\n}\n\ndef flutter_root\n  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)\n  unless File.exist?(generated_xcode_build_settings_path)\n    raise \"#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first\"\n  end\n\n  File.foreach(generated_xcode_build_settings_path) do |line|\n    matches = line.match(/FLUTTER_ROOT\\=(.*)/)\n    return matches[1].strip if matches\n  end\n  raise \"FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get\"\nend\n\nrequire File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)\n\nflutter_ios_podfile_setup\n\ntarget 'Runner' do\n  use_frameworks!\n  use_modular_headers!\n\n  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))\nend\n\npost_install do |installer|\n  installer.pods_project.targets.each do |target|\n    flutter_additional_ios_build_settings(target)\n  end\nend\n"
  },
  {
    "path": "Flutter/example/ios/Runner/AppDelegate.h",
    "content": "#import <Flutter/Flutter.h>\n#import <UIKit/UIKit.h>\n\n@interface AppDelegate : FlutterAppDelegate\n\n@end\n"
  },
  {
    "path": "Flutter/example/ios/Runner/AppDelegate.m",
    "content": "#import \"AppDelegate.h\"\n#import \"GeneratedPluginRegistrant.h\"\n\n@implementation AppDelegate\n\n- (BOOL)application:(UIApplication *)application\n    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {\n  [GeneratedPluginRegistrant registerWithRegistry:self];\n  // Override point for customization after application launch.\n  return [super application:application didFinishLaunchingWithOptions:launchOptions];\n}\n\n@end\n"
  },
  {
    "path": "Flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-20x20@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-20x20@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-29x29@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-29x29@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-29x29@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-40x40@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-40x40@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"60x60\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-60x60@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"60x60\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-60x60@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-20x20@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-20x20@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-29x29@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-29x29@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-40x40@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-40x40@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"76x76\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-76x76@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"76x76\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-76x76@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"83.5x83.5\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-83.5x83.5@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"1024x1024\",\n      \"idiom\" : \"ios-marketing\",\n      \"filename\" : \"Icon-App-1024x1024@1x.png\",\n      \"scale\" : \"1x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "Flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage@3x.png\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "Flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md",
    "content": "# Launch Screen Assets\n\nYou can customize the launch screen with your own desired assets by replacing the image files in this directory.\n\nYou can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images."
  },
  {
    "path": "Flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"12121\" systemVersion=\"16G29\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" launchScreen=\"YES\" colorMatched=\"YES\" initialViewController=\"01J-lp-oVM\">\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"12089\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"EHf-IW-A2E\">\n            <objects>\n                <viewController id=\"01J-lp-oVM\" sceneMemberID=\"viewController\">\n                    <layoutGuides>\n                        <viewControllerLayoutGuide type=\"top\" id=\"Ydg-fD-yQy\"/>\n                        <viewControllerLayoutGuide type=\"bottom\" id=\"xbc-2k-c8Z\"/>\n                    </layoutGuides>\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"Ze5-6b-2t3\">\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <subviews>\n                            <imageView opaque=\"NO\" clipsSubviews=\"YES\" multipleTouchEnabled=\"YES\" contentMode=\"center\" image=\"LaunchImage\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"YRO-k0-Ey4\">\n                            </imageView>\n                        </subviews>\n                        <color key=\"backgroundColor\" red=\"1\" green=\"1\" blue=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"sRGB\"/>\n                        <constraints>\n                            <constraint firstItem=\"YRO-k0-Ey4\" firstAttribute=\"centerX\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerX\" id=\"1a2-6s-vTC\"/>\n                            <constraint firstItem=\"YRO-k0-Ey4\" firstAttribute=\"centerY\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerY\" id=\"4X2-HB-R7a\"/>\n                        </constraints>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"iYj-Kq-Ea1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"53\" y=\"375\"/>\n        </scene>\n    </scenes>\n    <resources>\n        <image name=\"LaunchImage\" width=\"168\" height=\"185\"/>\n    </resources>\n</document>\n"
  },
  {
    "path": "Flutter/example/ios/Runner/Base.lproj/Main.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"10117\" systemVersion=\"15F34\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" useTraitCollections=\"YES\" initialViewController=\"BYZ-38-t0r\">\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"10085\"/>\n    </dependencies>\n    <scenes>\n        <!--Flutter View Controller-->\n        <scene sceneID=\"tne-QT-ifu\">\n            <objects>\n                <viewController id=\"BYZ-38-t0r\" customClass=\"FlutterViewController\" sceneMemberID=\"viewController\">\n                    <layoutGuides>\n                        <viewControllerLayoutGuide type=\"top\" id=\"y3c-jy-aDJ\"/>\n                        <viewControllerLayoutGuide type=\"bottom\" id=\"wfy-db-euE\"/>\n                    </layoutGuides>\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"8bC-Xf-vdC\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"600\" height=\"600\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <color key=\"backgroundColor\" white=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"calibratedWhite\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"dkx-z0-nzr\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n        </scene>\n    </scenes>\n</document>\n"
  },
  {
    "path": "Flutter/example/ios/Runner/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>flutter_app</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>$(MARKETING_VERSION)</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>NSPhotoLibraryAddUsageDescription</key>\n\t<string>需要存储照片到本地</string>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIMainStoryboardFile</key>\n\t<string>Main</string>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UIViewControllerBasedStatusBarAppearance</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Flutter/example/ios/Runner/Runner-Bridging-Header.h",
    "content": "//\n//  Use this file to import your target's public headers that you would like to expose to Swift.\n//\n\n"
  },
  {
    "path": "Flutter/example/ios/Runner/main.m",
    "content": "#import <Flutter/Flutter.h>\n#import <UIKit/UIKit.h>\n#import \"AppDelegate.h\"\n\nint main(int argc, char* argv[]) {\n  @autoreleasepool {\n    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));\n  }\n}\n"
  },
  {
    "path": "Flutter/example/ios/Runner.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 51;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };\n\t\t1EDC4DDDCD0644A1FAAC0AA0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0294BF8CF31FCFA9383A0A16 /* Pods_Runner.framework */; };\n\t\t3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };\n\t\t978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };\n\t\t97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };\n\t\t97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };\n\t\t97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };\n\t\t97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXCopyFilesBuildPhase section */\n\t\t9705A1C41CF9048500538489 /* Embed Frameworks */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 10;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tname = \"Embed Frameworks\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXCopyFilesBuildPhase section */\n\n/* Begin PBXFileReference section */\n\t\t0294BF8CF31FCFA9383A0A16 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t0A9146CC25F0E16B002A2AA6 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = \"Runner-Bridging-Header.h\"; sourceTree = \"<group>\"; };\n\t\t1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = \"<group>\"; };\n\t\t1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = \"<group>\"; };\n\t\t30366B0B141CD28AA834F32C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-Runner.release.xcconfig\"; path = \"Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = \"<group>\"; };\n\t\t7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = \"<group>\"; };\n\t\t7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = \"<group>\"; };\n\t\t7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = \"<group>\"; };\n\t\t8E189437D4AA54EFA5F1E371 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-Runner.debug.xcconfig\"; path = \"Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = \"<group>\"; };\n\t\t9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = \"<group>\"; };\n\t\t97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = \"<group>\"; };\n\t\t97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = \"<group>\"; };\n\t\t97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\t97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t9A263ABF5AC5C0782B4B8852 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-Runner.profile.xcconfig\"; path = \"Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig\"; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t97C146EB1CF9000F007C117D /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1EDC4DDDCD0644A1FAAC0AA0 /* Pods_Runner.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t044F0ED54FD0C5AAAC12E6C1 /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t8E189437D4AA54EFA5F1E371 /* Pods-Runner.debug.xcconfig */,\n\t\t\t\t30366B0B141CD28AA834F32C /* Pods-Runner.release.xcconfig */,\n\t\t\t\t9A263ABF5AC5C0782B4B8852 /* Pods-Runner.profile.xcconfig */,\n\t\t\t);\n\t\t\tpath = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t9740EEB11CF90186004384FC /* Flutter */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,\n\t\t\t\t9740EEB21CF90195004384FC /* Debug.xcconfig */,\n\t\t\t\t7AFA3C8E1D35360C0083082E /* Release.xcconfig */,\n\t\t\t\t9740EEB31CF90195004384FC /* Generated.xcconfig */,\n\t\t\t);\n\t\t\tname = Flutter;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146E51CF9000F007C117D = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t9740EEB11CF90186004384FC /* Flutter */,\n\t\t\t\t97C146F01CF9000F007C117D /* Runner */,\n\t\t\t\t97C146EF1CF9000F007C117D /* Products */,\n\t\t\t\t044F0ED54FD0C5AAAC12E6C1 /* Pods */,\n\t\t\t\tDE4D1CE6BE2A976A8FDEA6FC /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146EF1CF9000F007C117D /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t97C146EE1CF9000F007C117D /* Runner.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146F01CF9000F007C117D /* Runner */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,\n\t\t\t\t7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,\n\t\t\t\t97C146FA1CF9000F007C117D /* Main.storyboard */,\n\t\t\t\t97C146FD1CF9000F007C117D /* Assets.xcassets */,\n\t\t\t\t97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,\n\t\t\t\t97C147021CF9000F007C117D /* Info.plist */,\n\t\t\t\t97C146F11CF9000F007C117D /* Supporting Files */,\n\t\t\t\t1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,\n\t\t\t\t1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,\n\t\t\t\t0A9146CC25F0E16B002A2AA6 /* Runner-Bridging-Header.h */,\n\t\t\t);\n\t\t\tpath = Runner;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146F11CF9000F007C117D /* Supporting Files */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t97C146F21CF9000F007C117D /* main.m */,\n\t\t\t);\n\t\t\tname = \"Supporting Files\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDE4D1CE6BE2A976A8FDEA6FC /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0294BF8CF31FCFA9383A0A16 /* Pods_Runner.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t97C146ED1CF9000F007C117D /* Runner */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget \"Runner\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tFDF843932B8DA211CF1381BA /* [CP] Check Pods Manifest.lock */,\n\t\t\t\t9740EEB61CF901F6004384FC /* Run Script */,\n\t\t\t\t97C146EA1CF9000F007C117D /* Sources */,\n\t\t\t\t97C146EB1CF9000F007C117D /* Frameworks */,\n\t\t\t\t97C146EC1CF9000F007C117D /* Resources */,\n\t\t\t\t9705A1C41CF9048500538489 /* Embed Frameworks */,\n\t\t\t\t3B06AD1E1E4923F5004D2608 /* Thin Binary */,\n\t\t\t\tF9570C582DB1C0C5BAF817F5 /* [CP] Embed Pods Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = Runner;\n\t\t\tproductName = Runner;\n\t\t\tproductReference = 97C146EE1CF9000F007C117D /* Runner.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t97C146E61CF9000F007C117D /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 1020;\n\t\t\t\tORGANIZATIONNAME = \"\";\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t97C146ED1CF9000F007C117D = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 7.3.1;\n\t\t\t\t\t\tLastSwiftMigration = 1170;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject \"Runner\" */;\n\t\t\tcompatibilityVersion = \"Xcode 9.3\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 97C146E51CF9000F007C117D;\n\t\t\tproductRefGroup = 97C146EF1CF9000F007C117D /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t97C146ED1CF9000F007C117D /* Runner */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t97C146EC1CF9000F007C117D /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,\n\t\t\t\t3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,\n\t\t\t\t97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,\n\t\t\t\t97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Thin Binary\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"/bin/sh \\\"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\\\" embed_and_thin\";\n\t\t};\n\t\t9740EEB61CF901F6004384FC /* Run Script */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Run Script\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"/bin/sh \\\"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\\\" build\";\n\t\t};\n\t\tF9570C582DB1C0C5BAF817F5 /* [CP] Embed Pods Frameworks */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist\",\n\t\t\t);\n\t\t\tname = \"[CP] Embed Pods Frameworks\";\n\t\t\toutputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\tFDF843932B8DA211CF1381BA /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"diff \\\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\\\" \\\"${PODS_ROOT}/Manifest.lock\\\" > /dev/null\\nif [ $? != 0 ] ; then\\n    # print error to STDERR\\n    echo \\\"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\\\" >&2\\n    exit 1\\nfi\\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\\necho \\\"SUCCESS\\\" > \\\"${SCRIPT_OUTPUT_FILE_0}\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t97C146EA1CF9000F007C117D /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,\n\t\t\t\t97C146F31CF9000F007C117D /* main.m in Sources */,\n\t\t\t\t1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXVariantGroup section */\n\t\t97C146FA1CF9000F007C117D /* Main.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t97C146FB1CF9000F007C117D /* Base */,\n\t\t\t);\n\t\t\tname = Main.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t97C147001CF9000F007C117D /* Base */,\n\t\t\t);\n\t\t\tname = LaunchScreen.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXVariantGroup section */\n\n/* Begin XCBuildConfiguration section */\n\t\t249021D3217E4FDB00AE95B9 /* Profile */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSUPPORTED_PLATFORMS = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Profile;\n\t\t};\n\t\t249021D4217E4FDB00AE95B9 /* Profile */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 0.8.0;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/Flutter\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/Flutter\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 0.8.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.linusflow.dokitApp;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"Runner/Runner-Bridging-Header.h\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Profile;\n\t\t};\n\t\t97C147031CF9000F007C117D /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t97C147041CF9000F007C117D /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSUPPORTED_PLATFORMS = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t97C147061CF9000F007C117D /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 0.8.0;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/Flutter\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/Flutter\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 0.8.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.linusflow.dokitApp;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"Runner/Runner-Bridging-Header.h\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t97C147071CF9000F007C117D /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 0.8.0;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/Flutter\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/Flutter\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 0.8.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.linusflow.dokitApp;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"Runner/Runner-Bridging-Header.h\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t97C146E91CF9000F007C117D /* Build configuration list for PBXProject \"Runner\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t97C147031CF9000F007C117D /* Debug */,\n\t\t\t\t97C147041CF9000F007C117D /* Release */,\n\t\t\t\t249021D3217E4FDB00AE95B9 /* Profile */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget \"Runner\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t97C147061CF9000F007C117D /* Debug */,\n\t\t\t\t97C147071CF9000F007C117D /* Release */,\n\t\t\t\t249021D4217E4FDB00AE95B9 /* Profile */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 97C146E61CF9000F007C117D /* Project object */;\n}\n"
  },
  {
    "path": "Flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>PreviewsEnabled</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1020\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n               BuildableName = \"Runner.app\"\n               BlueprintName = \"Runner\"\n               ReferencedContainer = \"container:Runner.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n      </Testables>\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n            BuildableName = \"Runner.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n      <AdditionalOptions>\n      </AdditionalOptions>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n            BuildableName = \"Runner.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n      <AdditionalOptions>\n      </AdditionalOptions>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Profile\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n            BuildableName = \"Runner.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "Flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>PreviewsEnabled</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Flutter/example/lib/main.dart",
    "content": "// This is a basic Flutter widget test.\n//\n// To perform an interaction with a widget in your test, use the WidgetTester\n// utility that Flutter provides. For example, you can send tap and scroll\n// gestures. You can also use WidgetTester to find child widgets in the widget\n// tree, read text, and verify that the values of widget properties are correct.\n\nimport 'dart:async';\nimport 'dart:convert';\nimport 'dart:io';\n\nimport 'package:dio/dio.dart';\nimport 'package:dokit/dokit.dart';\nimport 'package:dokit/kit/apm/vm/vm_helper.dart';\nimport 'package:dokit/kit/biz/biz.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:path_provider/path_provider.dart';\n\nimport 'page2.dart';\n\nvoid main() {\n  List<String> blackList = [\n    'plugins.flutter.io/sensors/gyroscope',\n    'plugins.flutter.io/sensors/user_accel',\n    'plugins.flutter.io/sensors/accelerometer'\n  ];\n\n  DoKit.runApp(\n      app: DoKitApp(MyApp()),\n      useInRelease: true,\n      useRunZoned: false,\n      logCallback: (log) {\n//        String i = log;\n      },\n      methodChannelBlackList: blackList,\n      exceptionCallback: (dynamic obj, StackTrace trace) {\n        print('ttt$obj');\n      });\n  // runApp(MyApp());\n\n  DoKit.i.isDoKitPageShow((bool isShow) {\n    print('isShow = $isShow');\n  });\n\n  // 业务方接入自定义能力\n  DoKit.i.buildBizKit(\n      name: 'toB',\n      group: '业务专区',\n      desc: '[提供自动化测试能力]',\n      action: () => {print('isShow = 业务专区 toB')});\n\n  DoKit.i.buildBizKit(\n      name: 'toC',\n      group: '业务专区',\n      desc: '[提供自动化测试能力]',\n      action: () => {print('isShow = 业务专区 toB')});\n\n  DoKit.i.buildBizKit(\n      name: 'toC1',\n      group: '业务专区1',\n      desc: '[提供自动化测试能力1]',\n      action: () => {print('isShow = 业务专区 toB')});\n\n  DoKit.i.buildBizKit(\n      name: 'toC2',\n      group: '业务专区1',\n      desc: '[提供自动化测试能力1]',\n      kitBuilder: () => BizKitTestPage());\n\n  var bizKit0 = DoKit.i.newBizKit(name: '1111', group: '业务专区2');\n  DoKit.i.addKit(kit: bizKit0);\n\n  var bizKit1 = DoKit.i.newBizKit(name: '2222', group: '业务专区3');\n  var bizKit2 = DoKit.i.newBizKit(name: '3333', group: '业务专区3');\n  var bizKit3 = DoKit.i.newBizKit(name: '4444', group: '业务专区3');\n  DoKit.i.addBizKits([bizKit1, bizKit2, bizKit3]);\n\n  // DoKit.i.addKit({kit: BizKit(name: '1111', group: '业务专区2')});\n}\n\n/// ===自定义BizKit Test===\nclass BizKitTestPage extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() {\n    return BizKitTestPageState();\n  }\n}\n\nclass BizKitTestPageState extends State<BizKitTestPage> {\n  @override\n  Widget build(BuildContext context) {\n    return Container(\n      width: MediaQuery.of(context).size.width,\n      child: Column(\n        mainAxisAlignment: MainAxisAlignment.start,\n        children: <Widget>[\n          Container(\n              height: 44,\n              child: Row(\n                children: [\n                  Text('自定义BizKit Test Page',\n                      style: TextStyle(\n                          color: Color(0xff333333),\n                          fontWeight: FontWeight.normal,\n                          fontFamily: 'PingFang SC',\n                          fontSize: 14))\n                ],\n              )),\n          Divider(\n            height: 0.5,\n            color: Color(0xffdddddd),\n            indent: 16,\n            endIndent: 16,\n          )\n        ],\n      ),\n    );\n  }\n}\n\n/// === end ===\n\nclass MyApp extends StatelessWidget {\n  // This widget is the root of your application.\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      title: 'DoKit Test',\n      theme: ThemeData(\n        // This is the theme of your application.\n        //\n        // Try running your application with \"flutter run\". You'll see the\n        // application has a blue toolbar. Then, without quitting the app, try\n        // changing the primarySwatch below to Colors.green and then invoke\n        // \"hot reload\" (press \"r\" in the console where you ran \"flutter run\",\n        // or simply save your changes to \"hot reload\" in a Flutter IDE).\n        // Notice that the counter didn't reset back to zero; the application\n        // is not restarted.\n        primarySwatch: Colors.blue,\n        // This makes the kit.visual density adapt to the platform that you run\n        // the app on. For desktop platforms, the controls will be smaller and\n        // closer together (more dense) than on mobile platforms.\n        visualDensity: VisualDensity.adaptivePlatformDensity,\n      ),\n      navigatorObservers: [],\n      home: DoKitTestPage(),\n    );\n  }\n}\n\nclass DoKitTestPage extends StatefulWidget {\n  // This widget is the home page of your application. It is stateful, meaning\n  // that it has a State object (defined below) that contains fields that affect\n  // how it looks.\n\n  // This class is the configuration for the state. It holds the values (in this\n  // case the title) provided by the parent (in this case the App widget) and\n  // used by the build method of the State. Fields in a Widget subclass are\n  // always marked \"final\".\n\n  @override\n  _DoKitTestPageState createState() => _DoKitTestPageState();\n}\n\nclass _DoKitTestPageState extends State<DoKitTestPage> {\n  @override\n  void initState() {\n    super.initState();\n  }\n\n  @override\n  void dispose() {\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Container(\n      alignment: Alignment.center,\n      color: Colors.white,\n      child: SingleChildScrollView(\n        child: Column(\n          mainAxisAlignment: MainAxisAlignment.center,\n          crossAxisAlignment: CrossAxisAlignment.center,\n          children: <Widget>[\n            Container(\n              decoration: BoxDecoration(\n                  borderRadius: BorderRadius.all(Radius.circular(4)),\n                  color: Color(0xffcccccc)),\n              margin: EdgeInsets.only(bottom: 30),\n              child: TextButton(\n                child: Text('Mock Http Post',\n                    style: TextStyle(\n                      color: Color(0xff000000),\n                      fontSize: 18,\n                    )),\n                onPressed: mockHttpPost,\n              ),\n            ),\n            Container(\n              decoration: BoxDecoration(\n                  borderRadius: BorderRadius.all(Radius.circular(4)),\n                  color: Color(0xffcccccc)),\n              margin: EdgeInsets.only(bottom: 30),\n              child: TextButton(\n                style: ButtonStyle(\n                  padding: MaterialStateProperty.all(EdgeInsets.all(0)),\n                ),\n                child: Text('Mock Http Get',\n                    style: TextStyle(\n                      color: Color(0xff000000),\n                      fontSize: 18,\n                    )),\n                onPressed: mockHttpGet,\n              ),\n            ),\n            Container(\n              decoration: BoxDecoration(\n                  borderRadius: BorderRadius.all(Radius.circular(4)),\n                  color: Color(0xffcccccc)),\n              margin: EdgeInsets.only(bottom: 30),\n              child: TextButton(\n                style: ButtonStyle(\n                  padding: MaterialStateProperty.all(EdgeInsets.all(0)),\n                ),\n                child: Text('Test Download',\n                    style: TextStyle(\n                      color: Color(0xff000000),\n                      fontSize: 18,\n                    )),\n                onPressed: testDownload,\n              ),\n            ),\n            Container(\n              decoration: BoxDecoration(\n                  borderRadius: BorderRadius.all(Radius.circular(4)),\n                  color: Color(0xffcccccc)),\n              margin: EdgeInsets.only(bottom: 30),\n              child: TextButton(\n                style: ButtonStyle(\n                  padding: MaterialStateProperty.all(EdgeInsets.all(0)),\n                ),\n                child: Text('Test Method Channel',\n                    style: TextStyle(\n                      color: Color(0xff000000),\n                      fontSize: 18,\n                    )),\n                onPressed: () {\n                  testMethodChannel();\n                },\n              ),\n            ),\n            Container(\n              decoration: BoxDecoration(\n                  borderRadius: BorderRadius.all(Radius.circular(4)),\n                  color: Color(0xffcccccc)),\n              margin: EdgeInsets.only(bottom: 30),\n              child: TextButton(\n                style: ButtonStyle(\n                  padding: MaterialStateProperty.all(EdgeInsets.all(0)),\n                ),\n                child: Text('Open Route Page',\n                    style: TextStyle(\n                      color: Color(0xff000000),\n                      fontSize: 18,\n                    )),\n                onPressed: () {\n                  Navigator.of(context, rootNavigator: false).push<void>(\n                      MaterialPageRoute(\n                          builder: (context) {\n                            //指定跳转的页面\n                            return TestPage2();\n                          },\n                          settings: RouteSettings(\n                              name: 'page1', arguments: ['test', '111'])));\n                },\n              ),\n            ),\n            Container(\n              decoration: BoxDecoration(\n                  borderRadius: BorderRadius.all(Radius.circular(4)),\n                  color: Color(0xffcccccc)),\n              margin: EdgeInsets.only(bottom: 30),\n              child: TextButton(\n                style: ButtonStyle(\n                  padding: MaterialStateProperty.all(EdgeInsets.all(0)),\n                ),\n                child: Text('Test Get Page Script',\n                    style: TextStyle(\n                      color: Color(0xff000000),\n                      fontSize: 18,\n                    )),\n                onPressed: () {\n                  VmHelper.instance.testPrintScript();\n                },\n              ),\n            ),\n            Container(\n              decoration: BoxDecoration(\n                  borderRadius: BorderRadius.all(Radius.circular(4)),\n                  color: Color(0xffcccccc)),\n              margin: EdgeInsets.only(bottom: 30),\n              child: TextButton(\n                style: ButtonStyle(\n                  padding: MaterialStateProperty.all(EdgeInsets.all(0)),\n                ),\n                child: Text('Stop Timer',\n                    style: TextStyle(\n                      color: Color(0xff000000),\n                      fontSize: 18,\n                    )),\n                onPressed: stopAll,\n              ),\n            ),\n          ],\n        ),\n      ),\n    );\n  }\n\n  Timer? timer;\n\n  void testDownload() async {\n    String url =\n        'https://pt-starfile.didistatic.com/static/starfile/node20210220/895f1e95e30aba5dd56d6f2ccf768b57/GjzGU0Pvv11613804530384.zip';\n    String? savePath = await getPhoneLocalPath();\n    String zipName = 'test.zip';\n    Dio dio = Dio();\n    print(\"$savePath/$zipName\");\n    Response response = await dio.download(url, \"$savePath/$zipName\",\n        onReceiveProgress: (received, total) {\n      if (total != -1) {\n        // 当前下载的百分比\n        // print((received / total * 100).toStringAsFixed(0) + \"%\");\n        // print(\"received=$received total=$total\");\n        if (received == total) {\n          print(\"下载完成 ✅ \");\n        }\n      } else {}\n    });\n  }\n\n  ///获取手机的存储目录路径\n  ///getExternalStorageDirectory() 获取的是  android 的外部存储 （External Storage）\n  ///  getApplicationDocumentsDirectory 获取的是 ios 的Documents` or `Downloads` 目录\n  Future<String?> getPhoneLocalPath() async {\n    final directory = Theme.of(context).platform == TargetPlatform.android\n        ? await getExternalStorageDirectory()\n        : await getApplicationDocumentsDirectory();\n    return directory?.path;\n  }\n\n  void testMethodChannel() {\n    timer?.cancel();\n    timer = Timer.periodic(Duration(seconds: 2), (timer) async {\n      const MethodChannel _kChannel =\n          MethodChannel('plugins.flutter.io/package_info');\n      final Map<String, dynamic>? map =\n          await _kChannel.invokeMapMethod<String, dynamic>('getAll');\n    });\n  }\n\n  void stopAll() {\n    print('stopAll');\n    timer?.cancel();\n    timer = null;\n  }\n\n  void request() async {\n    Image.network(\n      //图片地址\n      'https://img04.sogoucdn.com/app/a/100520093/ac75323d6b6de243-0bd502b2bdc1100a-92cef3b2299cfc6875afe7d5d0b83a7b.jpg',\n      //填充模式\n      fit: BoxFit.fitWidth,\n    );\n  }\n\n  void mockHttpPost() async {\n    timer?.cancel();\n    timer = Timer.periodic(Duration(seconds: 2), (timer) async {\n      HttpClient client = HttpClient();\n      String url = 'https://pinzhi.didichuxing.com/kop_stable/gateway?api=hhh';\n      HttpClientRequest request = await client.postUrl(Uri.parse(url));\n      Map<String, String> map1 = Map();\n      map1[\"v\"] = \"1.0\";\n      map1[\"month\"] = \"7\";\n      map1[\"day\"] = \"25\";\n      map1[\"key\"] = \"bd6e35a2691ae5bb8425c8631e475c2a\";\n      request.add(utf8.encode(json.encode(map1)));\n      request.add(utf8.encode(json.encode(map1)));\n      HttpClientResponse response = await request.close();\n      String responseBody = await response.transform(utf8.decoder).join();\n    });\n  }\n\n  void mockHttpGet() async {\n    timer?.cancel();\n    timer = Timer.periodic(Duration(seconds: 2), (timer) async {\n      HttpClient client = HttpClient();\n      String url = 'https://www.baidu.com';\n      HttpClientRequest request = await client.postUrl(Uri.parse(url));\n      HttpClientResponse response = await request.close();\n      String responseBody = await response.transform(utf8.decoder).join();\n    });\n  }\n}\n\nclass TestPage extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() {\n    return TestPageState();\n  }\n}\n\nclass TestPageState extends State<TestPage> {\n  int index = 0;\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(),\n      body: Center(\n        // Center is a layout widget. It takes a single child and positions it\n        // in the middle of the parent.\n        child: Column(\n          // Column is also a layout widget. It takes a list of children and\n          // arranges them vertically. By default, it sizes itself to fit its\n          // children horizontally, and tries to be as tall as its parent.\n          //\n          // Invoke \"debug painting\" (press \"p\" in the console, choose the\n          // \"Toggle Debug Paint\" action from the Flutter Inspector in Android\n          // Studio, or the \"Toggle Debug Paint\" command in Visual Studio Code)\n          // to see the wireframe for each widget.\n          //\n          // Column has various properties to control how it sizes itself and\n          // how it positions its children. Here we use mainAxisAlignment to\n          // center the children vertically; the main axis here is the vertical\n          // axis because Columns are vertical (the cross axis would be\n          // horizontal).\n          mainAxisAlignment: MainAxisAlignment.center,\n          children: <Widget>[\n            GestureDetector(\n              onTap: () => {\n                Navigator.of(context, rootNavigator: false).push<void>(\n                    MaterialPageRoute(\n                        builder: (context) {\n                          //指定跳转的页面\n                          return TestPage2();\n                        },\n                        settings: RouteSettings(\n                            name: 'page1', arguments: ['test', '111'])))\n              },\n              child: Text(\n                'page1:',\n              ),\n            ),\n            Text(\n              '0',\n              style: Theme.of(context).textTheme.headline4,\n            ),\n            Container(height: 100, width: 300)\n          ],\n        ),\n      ),\n    );\n  }\n}\n\nclass TestPage3 extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() {\n    return TestPageState3();\n  }\n}\n\nclass TestPageState3 extends State<TestPage3> {\n  int index = 0;\n\n  @override\n  Widget build(BuildContext context) {\n    // TODO: implement build\n    return Scaffold(\n      appBar: AppBar(),\n      body: Center(\n        // Center is a layout widget. It takes a single child and positions it\n        // in the middle of the parent.\n        child: Column(\n          // Column is also a layout widget. It takes a list of children and\n          // arranges them vertically. By default, it sizes itself to fit its\n          // children horizontally, and tries to be as tall as its parent.\n          //\n          // Invoke \"debug painting\" (press \"p\" in the console, choose the\n          // \"Toggle Debug Paint\" action from the Flutter Inspector in Android\n          // Studio, or the \"Toggle Debug Paint\" command in Visual Studio Code)\n          // to see the wireframe for each widget.\n          //\n          // Column has various properties to control how it sizes itself and\n          // how it positions its children. Here we use mainAxisAlignment to\n          // center the children vertically; the main axis here is the vertical\n          // axis because Columns are vertical (the cross axis would be\n          // horizontal).\n          mainAxisAlignment: MainAxisAlignment.center,\n          children: <Widget>[\n            GestureDetector(\n              onTap: () => {\n                Navigator.of(context, rootNavigator: false)\n                    .push<void>(MaterialPageRoute(\n                        builder: (context) {\n                          //指定跳转的页面\n                          return MyApp();\n                        },\n                        settings: RouteSettings(name: 'page3')))\n              },\n              child: Text(\n                'page3:',\n              ),\n            ),\n            Text(\n              '0',\n              style: Theme.of(context).textTheme.headline4,\n            )\n          ],\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "Flutter/example/lib/page2.dart",
    "content": "import 'package:flutter/material.dart';\n\nclass TestPage2 extends StatefulWidget {\n  @override\n  StatefulElement createElement() {\n    StatefulElement element = super.createElement();\n\n    // Future.delayed(Duration(hours: 1), () {\n    //   print(element);\n    // });\n    return element;\n    // return super.createElement();\n  }\n\n  @override\n  State<StatefulWidget> createState() {\n    return TestPageState2();\n  }\n}\n\nclass TestPageState2 extends State<TestPage2> {\n  int index = 0;\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      home: Scaffold(\n        appBar: AppBar(),\n        body: Center(\n          child: Column(\n            mainAxisAlignment: MainAxisAlignment.center,\n            children: <Widget>[\n              Text(\n                'page2:',\n              ),\n              Text(\n                '0',\n                style: Theme.of(context).textTheme.headline4,\n              )\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "Flutter/example/local.properties",
    "content": "## This file must *NOT* be checked into Version Control Systems,\n# as it contains information specific to your local configuration.\n#\n# Location of the SDK. This is only used by Gradle.\n# For customization when using a Version Control System, please read the\n# header note.\n#Thu Mar 25 22:20:13 CST 2021\nsdk.dir=/Users/didi/Library/Android/sdk\n"
  },
  {
    "path": "Flutter/example/pubspec.yaml",
    "content": "name: dokit_demo\ndescription: dokit测试工程\nversion: 0.8.0\nhomepage: http://flutter.intra.xiaojukeji.com/index.php\n\nenvironment:\n  sdk: \">=2.12.0 <3.0.0\"\n\ndependencies:\n  flutter:\n    sdk: flutter\n  dokit:\n    path: ..\n  dio: 4.0.0\n  path_provider: ^2.0.1\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n\n\nflutter:\n  uses-material-design: true"
  },
  {
    "path": "Flutter/example/test/dokit_library_test.dart",
    "content": "import 'package:flutter_test/flutter_test.dart';\n\n\nvoid main() {\n  test('adds one to input values', () {\n    // final calculator = Calculator();\n    // expect(calculator.addOne(2), 3);\n    // expect(calculator.addOne(-7), -6);\n    // expect(calculator.addOne(0), 1);\n    // expect(() => calculator.addOne(null), throwsNoSuchMethodError);\n  });\n}\n"
  },
  {
    "path": "Flutter/flutterw",
    "content": "#!/usr/bin/env bash\nset -e\nZIP_DIST=\"$HOME/.taco/flutter/2.0.6/2.0.6.zip\"\nZIP_URL=\"https://storage.flutter-io.cn/flutter_infra/releases/stable/macos/flutter_macos_2.0.6-stable.zip\"\nFLUTTER_HOME=\"$HOME/.taco/flutter/2.0.6/flutter\"\nFLUTTER_DEST=\"$HOME/.taco/flutter/2.0.6\"\nFLUTTER_BIN=\"$HOME/.taco/flutter/2.0.6/flutter/bin/flutter\"\nVERSION=\"2.0.6\"\n\necho \"Using Flutter Wrapper: $VERSION\"\n#\nif [[ ! -e $FLUTTER_BIN ]]; then\n    echo \"Flutter Wrapper: $VERSION, is not installed.\"\n    rm -rf $FLUTTER_HOME\n    mkdir -p $FLUTTER_HOME\n    # if [[ ! -e $ZIP_DIST ]]; then\n    \techo \"Downloading Flutter $VERSION...\"\n        curl -C - -L -o $ZIP_DIST $ZIP_URL\n    # fi\n    echo \"Unzipping Flutter...\"\n    unzip -qo $ZIP_DIST -d $FLUTTER_DEST\n    echo \"Flutter Wrapper: $VERSION is installed.\"\nfi\n# shellcheck disable=SC2068\n$FLUTTER_BIN $@"
  },
  {
    "path": "Flutter/lib/dokit.dart",
    "content": "// Copyright© Dokit for Flutter. All rights reserved.\n//\n// dokit.dart\n// Flutter\n//\n// Created by linusflow on 2021/3/05\n// Modified by linusflow on 2021/5/12 下午3:47\n//\n\nimport 'dart:async';\nimport 'dart:convert';\nimport 'dart:core';\nimport 'dart:io';\n\nimport 'package:dokit/engine/dokit_binding.dart';\nimport 'package:dokit/kit/apm/log_kit.dart';\nimport 'package:dokit/kit/apm/vm/version.dart';\nimport 'package:dokit/ui/dokit_app.dart';\nimport 'package:dokit/ui/dokit_btn.dart';\nimport 'package:dokit/ui/kit_page.dart';\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/widgets.dart' as dart;\nimport 'package:package_info/package_info.dart';\n\nimport 'kit/apm/vm/vm_service_wrapper.dart';\nimport 'kit/biz/biz.dart';\n\nexport 'package:dokit/ui/dokit_app.dart';\n\ntypedef DoKitAppCreator = Future<IDoKitApp> Function();\ntypedef LogCallback = void Function(String);\ntypedef ExceptionCallback = void Function(dynamic, StackTrace);\n\nconst String DK_PACKAGE_NAME = 'dokit';\nconst String DK_PACKAGE_VERSION = '0.8.0-nullsafety.0';\n\n//默认release模式不开启该功能\nconst bool release = kReleaseMode;\n\n//记录当前zone\nZone? _zone;\n\n// ignore: avoid_classes_with_only_static_members\nclass DoKit {\n  // 初始化方法,app或者appCreator必须设置一个\n  static Future<void> runApp(\n      {DoKitApp? app,\n      bool useRunZoned = true,\n      DoKitAppCreator? appCreator,\n      bool useInRelease = false,\n      LogCallback? logCallback,\n      ExceptionCallback? exceptionCallback,\n      List<String> methodChannelBlackList = const <String>[],\n      Function? releaseAction}) async {\n    // 统计用户信息，便于了解该开源产品的使用量 (请大家放心，我们不用于任何恶意行为)\n    upLoadUserInfo();\n\n    assert(\n        app != null || appCreator != null, 'app and appCreator are both null');\n    if (release && !useInRelease) {\n      if (releaseAction != null) {\n        releaseAction.call();\n      } else {\n        if (app != null) {\n          dart.runApp(app.origin);\n        } else {\n          dart.runApp((await appCreator!()).origin);\n        }\n      }\n      return;\n    }\n    blackList = methodChannelBlackList;\n\n    if (useRunZoned != true) {\n      var f = () async => <void>{\n            _ensureDoKitBinding(useInRelease: useInRelease),\n            _runWrapperApp(app != null ? app : await appCreator!()),\n            _zone = Zone.current\n          };\n      await f();\n      return;\n    }\n    await runZonedGuarded(\n      () async => <void>{\n        _ensureDoKitBinding(useInRelease: useInRelease),\n        _runWrapperApp(app != null ? app : await appCreator!()),\n        _zone = Zone.current\n      },\n      (Object obj, StackTrace stack) {\n        _collectError(obj, stack);\n        if (exceptionCallback != null) {\n          _zone?.runBinary(exceptionCallback, obj, stack);\n        }\n      },\n      zoneSpecification: ZoneSpecification(\n        print: (Zone self, ZoneDelegate parent, Zone zone, String line) {\n          _collectLog(line); //手机日志\n          parent.print(zone, line);\n          if (logCallback != null) {\n            _zone?.runUnary(logCallback, line);\n          }\n        },\n      ),\n    );\n  }\n\n  /// 暴露出来的除[runApp]外的所有接口\n  static final i = _DoKitInterfaces._instance;\n}\n\nabstract class IDoKit {/* Just empty. */}\n\nclass _DoKitInterfaces extends IDoKit with _BizKitMixin {\n  _DoKitInterfaces._();\n\n  static final _DoKitInterfaces _instance = _DoKitInterfaces._();\n\n  late DoKitBtnClickedCallback callback = (b) => {};\n\n  /// doKit是否打开了页面（只要是通过doKit打开的页面）\n  void isDoKitPageShow(DoKitBtnClickedCallback callback) {\n    this.callback = callback;\n  }\n}\n\nmixin _BizKitMixin on IDoKit {\n  /// 更新group信息，详见[addKitGroupTip]\n  void updateKitGroupTip(String name, String tip) {\n    BizKitManager.instance.updateBizKitGroupTip(name, tip);\n  }\n\n  /// 详见[addBizKit]\n  void addKit<S extends BizKit>({String? key, required S kit}) {\n    BizKitManager.instance.addBizKit<S>(key, kit);\n  }\n\n  /// 详见[addBizKits]\n  void addBizKits(List<BizKit> bizKits) {\n    BizKitManager.instance.addBizKits(bizKits);\n  }\n\n  /// 创建BizKit对象\n  T newBizKit<T extends BizKit>(\n      {String? key,\n      required String name,\n      String? icon,\n      required String group,\n      String? desc,\n      KitPageBuilder? kitBuilder,\n      Function? action}) {\n    return BizKitManager.instance.createBizKit(\n        name: name,\n        group: group,\n        key: key,\n        icon: icon,\n        desc: desc,\n        action: action,\n        kitBuilder: kitBuilder);\n  }\n\n  /// [key] kit的唯一标识，全局不可重复，不传则默认使用[BizKit._defaultKey];\n  /// [name] kit显示的名字;\n  /// [icon] kit的显示的图标，不传则使用默认图标;\n  /// [group] kit归属的组，如果该组不存在，则会自动创建;\n  /// [desc] kit的描述信息，不会以任何形式显示出来;\n  /// [kitBuilder] kit对应的页面的WidgetBuilder，点击该kit的图标后跳转到的Widget页面，不要求有Navigator，详见[BizKit.tapAction].\n  void buildBizKit(\n      {String? key,\n      required String name,\n      String? icon,\n      required String group,\n      String? desc,\n      KitPageBuilder? kitBuilder,\n      Function? action}) {\n    BizKitManager.instance.buildBizKit(\n        key: key,\n        name: name,\n        icon: icon,\n        group: group,\n        desc: desc,\n        kitBuilder: kitBuilder,\n        action: action);\n  }\n}\n\n// 如果在runApp之前执行了WidgetsFlutterBinding.ensureInitialized，会导致methodchannel功能不可用，可以在runApp前先调用一下ensureDoKitBinding\nvoid _ensureDoKitBinding({bool useInRelease = false}) {\n  if (!release || useInRelease) {\n    DoKitWidgetsFlutterBinding.ensureInitialized();\n  }\n}\n\nvoid _runWrapperApp(IDoKitApp wrapper) {\n  DoKitWidgetsFlutterBinding.ensureInitialized()\n// ignore: invalid_use_of_protected_member\n    ?..scheduleAttachRootWidget(wrapper)\n    ..scheduleWarmUpFrame();\n  addEntrance();\n}\n\nvoid _collectLog(String line) {\n  LogManager.instance.addLog(LogBean.TYPE_INFO, line);\n}\n\nvoid _collectError(Object? details, Object? stack) {\n  LogManager.instance.addLog(\n      LogBean.TYPE_ERROR, '${details?.toString()}\\n${stack?.toString()}');\n}\n\nvoid addEntrance() {\n  WidgetsBinding.instance?.addPostFrameCallback((_) {\n    final DoKitBtn floatBtn = DoKitBtn();\n    floatBtn.addToOverlay();\n    floatBtn.btnClickCallback = DoKit.i.callback;\n    KitPageManager.instance.loadCache();\n  });\n}\n\nvoid dispose({required BuildContext context}) {\n  doKitOverlayKey.currentState?.widget.initialEntries.forEach((element) {\n// element.remove();\n  });\n}\n\nvoid upLoadUserInfo() async {\n  final client = HttpClient();\n  const url = 'https://doraemon.xiaojukeji.com/uploadAppData';\n  final request = await client.postUrl(Uri.parse(url));\n  final packageInfo = await PackageInfo.fromPlatform();\n\n  Locale? locale;\n  void finder(Element element) {\n    if (element.widget is Localizations) {\n      locale ??= (element.widget as Localizations).locale;\n    } else {\n      element.visitChildren(finder);\n    }\n  }\n\n  DoKitApp.appKey.currentContext?.visitChildElements(finder);\n\n  final appId = packageInfo.packageName;\n  // 在iOS上可能获取不到appName\n  // https://github.com/flutter/flutter/issues/42510\n  // 当info.plist文件中只有CFBundleName，没有CFBundleDisplayName时，则无法获取\n  final appName =\n      packageInfo.appName.isEmpty ? 'DoKitFlutterDefault' : packageInfo.appName;\n  final appVersion = packageInfo.version;\n  final version = DK_PACKAGE_VERSION;\n  final from = '1';\n  var type = 'flutter_';\n  if (Platform.isIOS) {\n    type += 'iOS';\n  } else if (Platform.isAndroid) {\n    type += 'android';\n  } else {\n    type += 'other';\n  }\n  final language = locale?.toString() ?? '';\n  final playload = <String, dynamic>{};\n  await VMServiceWrapper.instance\n      .callExtensionService('flutterVersion')\n      .then((value) {\n    if (value != null) {\n      final flutter = FlutterVersion.parse(value.json);\n      playload['flutter_version'] = flutter.version;\n      playload['dart_sdk_version'] = flutter.dartSdkVersion;\n      type +=\n          '-flutter_version_${flutter.version}-dart_sdk_version_${flutter.dartSdkVersion}';\n    }\n  });\n\n  final params = <String, dynamic>{};\n  params['appId'] = appId;\n  params['appName'] = appName;\n  params['appVersion'] = appVersion;\n  params['version'] = version;\n  params['from'] = from;\n  params['type'] = type;\n  params['language'] = language;\n  params['playload'] = playload;\n\n  request.headers\n    ..add('Content-Type', 'application/json')\n    ..add('Accept', 'application/json');\n  request.add(utf8.encode(json.encode(params)));\n\n  final response = await request.close();\n//  final responseBody = await response.transform(utf8.decoder).join();\n  if (response.statusCode == HttpStatus.ok) {\n//    print('用户统计数据上报成功！');\n  }\n  client.close();\n}\n"
  },
  {
    "path": "Flutter/lib/engine/dokit_binding.dart",
    "content": "import 'dart:async';\nimport 'dart:convert';\nimport 'dart:ui';\n\nimport 'package:dokit/kit/apm/apm.dart';\nimport 'package:dokit/kit/apm/method_channel_kit.dart';\nimport 'package:dokit/kit/kit.dart';\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flutter/widgets.dart';\n\nList<String> blackList = <String>[];\n\nclass DoKitWidgetsFlutterBinding extends WidgetsFlutterBinding\n    with DoKitServicesBinding {\n  static WidgetsBinding? ensureInitialized() {\n    if (WidgetsBinding.instance == null) {\n      DoKitWidgetsFlutterBinding();\n    }\n    return WidgetsBinding.instance;\n  }\n}\n\nmixin DoKitServicesBinding on BindingBase, ServicesBinding {\n  @override\n  BinaryMessenger createBinaryMessenger() {\n    return DoKitBinaryMessenger(super.createBinaryMessenger());\n  }\n}\n\n// 扩展1.20版本新增的两个方法，解决低版本编译问题\n// 扩展的方法直接调用this.同名方法，在高版本上会调用原方法，低版本上虽然会造成递归调用，但实际不会用到这个方法\nextension _BinaryMessengerExt on BinaryMessenger {\n  // ignore: unused_element\n  bool checkMessageHandler(String channel, MessageHandler handler) {\n    return this.checkMessageHandler(channel, handler);\n  }\n\n  // ignore: unused_element\n  bool checkMockMessageHandler(String channel, MessageHandler handler) {\n    return this.checkMockMessageHandler(channel, handler);\n  }\n}\n\nclass DoKitBinaryMessenger extends BinaryMessenger {\n  DoKitBinaryMessenger(this.origin);\n\n  final MethodCodec codec = const StandardMethodCodec();\n  final BinaryMessenger origin;\n\n  @override\n  Future<void> handlePlatformMessage(String channel, ByteData? data, callback) {\n    final ChannelInfo? info = saveMessage(channel, data, false);\n    if (info == null) {\n      return origin.handlePlatformMessage(channel, data, callback);\n    }\n    final PlatformMessageResponseCallback wrapper = (ByteData? data) {\n      resolveResult(info, data);\n      callback?.call(data);\n    };\n    return origin.handlePlatformMessage(channel, data, wrapper);\n  }\n\n  @override\n  Future<ByteData?>? send(String channel, ByteData? message) async {\n    final ChannelInfo? info = saveMessage(channel, message, true);\n    if (info == null) {\n      return origin.send(channel, message);\n    }\n    final ByteData? result = await origin.send(channel, message);\n    resolveResult(info, result);\n    return result;\n  }\n\n  void resolveResult(ChannelInfo? info, ByteData? result) {\n    try {\n      if (info != null && result != null) {\n        if (info.methodCodec != null) {\n          info.results = info.methodCodec?.decodeEnvelope(result);\n          info.endTimestamp = DateTime.now().millisecondsSinceEpoch;\n        } else if (info.messageCodec != null) {\n          info.results = info.messageCodec?.decodeMessage(result);\n          info.endTimestamp = DateTime.now().millisecondsSinceEpoch;\n        } else {\n          info.endTimestamp = DateTime.now().millisecondsSinceEpoch;\n        }\n      }\n    } catch (e) {\n      print(e);\n    }\n    info?.methodCodec = null;\n    info?.messageCodec = null;\n  }\n\n  ChannelInfo? saveMessage(String name, ByteData? data, bool send) {\n    final MethodChannelKit? kit =\n        ApmKitManager.instance.getKit<MethodChannelKit>(ApmKitName.KIT_CHANNEL);\n    if (kit == null) {\n      return null;\n    }\n    if (blackList.contains(name)) {\n      return null;\n    }\n    ChannelInfo? info;\n    try {\n      info = filterSystemChannel(name, data, send) as ChannelInfo?;\n      if (info == null) {\n        final MethodCall call = codec.decodeMethodCall(data);\n        info = ChannelInfo(name, call.method, call.arguments,\n            send ? ChannelInfo.TYPE_USER_SEND : ChannelInfo.TYPE_USER_RECEIVE);\n        info.methodCodec = codec;\n      }\n    } catch (e) {\n      info = ChannelInfo.error(name,\n          send ? ChannelInfo.TYPE_USER_SEND : ChannelInfo.TYPE_USER_RECEIVE);\n    }\n    kit.save(info);\n    return info;\n  }\n\n  @override\n  void setMessageHandler(String channel, handler) {\n    origin.setMessageHandler(channel, handler);\n  }\n\n  @override\n  void setMockMessageHandler(String channel, handler) {\n    // origin.setMockMessageHandler(channel, handler);\n  }\n\n  @override\n  bool checkMessageHandler(String channel, handler) {\n    return origin.checkMessageHandler(channel, handler);\n  }\n\n  @override\n  bool checkMockMessageHandler(String channel, MessageHandler? handler) {\n    return origin.checkMockMessageHandler(channel, handler!);\n  }\n\n  IInfo? filterSystemChannel(String name, ByteData? data, bool send) {\n    if (name == SystemChannels.lifecycle.name) {\n      return decodeMessage(name, data, SystemChannels.lifecycle.codec, send);\n    }\n    if (name == SystemChannels.accessibility.name) {\n      return decodeMessage(\n          name, data, SystemChannels.accessibility.codec, send);\n    }\n    if (name == SystemChannels.keyEvent.name) {\n      return decodeMessage(name, data, SystemChannels.keyEvent.codec, send);\n    }\n    if (name == SystemChannels.navigation.name) {\n      return decodeMethod(name, data, SystemChannels.navigation.codec, send);\n    }\n    if (name == SystemChannels.platform.name) {\n      return decodeMethod(name, data, SystemChannels.platform.codec, send);\n    }\n    if (name == SystemChannels.platform_views.name) {\n      return decodeMethod(\n          name, data, SystemChannels.platform_views.codec, send);\n    }\n    if (name == SystemChannels.skia.name) {\n      return decodeMethod(name, data, SystemChannels.skia.codec, send);\n    }\n    if (name == SystemChannels.system.name) {\n      return decodeMessage(name, data, SystemChannels.system.codec, send);\n    }\n    if (name == SystemChannels.textInput.name) {\n      return decodeMethod(name, data, SystemChannels.textInput.codec, send);\n    }\n    if (name == 'flutter/assets') {\n      return decodeByteMessage(name, data, send);\n    }\n    return null;\n  }\n\n  IInfo? decodeByteMessage(String name, ByteData? data, bool send) {\n    var arguments =\n        data == null ? null : utf8.decode(data.buffer.asUint8List());\n    return ChannelInfo(name, null, arguments,\n        send ? ChannelInfo.TYPE_SYSTEM_SEND : ChannelInfo.TYPE_SYSTEM_RECEIVE);\n  }\n\n  IInfo decodeMessage(\n      String name, ByteData? data, MessageCodec<dynamic> codec, bool send) {\n    final dynamic call = codec.decodeMessage(data);\n    return ChannelInfo(name, call.toString(), null,\n        send ? ChannelInfo.TYPE_SYSTEM_SEND : ChannelInfo.TYPE_SYSTEM_RECEIVE)\n      ..messageCodec = codec;\n  }\n\n  IInfo decodeMethod(\n      String name, ByteData? data, MethodCodec codec, bool send) {\n    final MethodCall call = codec.decodeMethodCall(data);\n    return ChannelInfo(name, call.method, call.arguments,\n        send ? ChannelInfo.TYPE_SYSTEM_SEND : ChannelInfo.TYPE_SYSTEM_RECEIVE)\n      ..methodCodec = codec;\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/engine/dokit_http.dart",
    "content": "import 'dart:async';\nimport 'dart:convert';\nimport 'dart:io';\nimport 'dart:typed_data';\n\nimport 'package:dokit/kit/apm/apm.dart';\nimport 'package:dokit/kit/apm/http_kit.dart';\n\nclass DoKitHttpOverrides extends HttpOverrides {\n  DoKitHttpOverrides(this.origin);\n\n  final HttpOverrides? origin;\n\n  @override\n  HttpClient createHttpClient(SecurityContext? context) {\n    if (origin != null) {\n      return DoKitHttpClient(origin!.createHttpClient(context));\n    }\n    HttpOverrides.global = null;\n    final HttpClient client = DoKitHttpClient(HttpClient(context: context));\n    HttpOverrides.global = this;\n    return client;\n  }\n}\n\nclass DoKitHttpClient implements HttpClient {\n  DoKitHttpClient(this.origin);\n\n  final HttpClient origin;\n  HttpInfo? httpInfo;\n\n  @override\n  set autoUncompress(bool value) => origin.autoUncompress = value;\n\n  @override\n  bool get autoUncompress => origin.autoUncompress;\n\n  @override\n  set idleTimeout(Duration value) => origin.idleTimeout = value;\n\n  @override\n  Duration get idleTimeout => origin.idleTimeout;\n\n  @override\n  set connectionTimeout(Duration? value) => origin.connectionTimeout = value;\n\n  @override\n  Duration? get connectionTimeout => origin.connectionTimeout;\n\n  @override\n  set maxConnectionsPerHost(int? value) => origin.maxConnectionsPerHost = value;\n\n  @override\n  int? get maxConnectionsPerHost => origin.maxConnectionsPerHost;\n\n  @override\n  set userAgent(String? value) => origin.userAgent = value;\n\n  @override\n  String? get userAgent => origin.userAgent;\n\n  @override\n  void addCredentials(\n      Uri url, String realm, HttpClientCredentials credentials) {\n    origin.addCredentials(url, realm, credentials);\n  }\n\n  @override\n  void addProxyCredentials(\n      String host, int port, String realm, HttpClientCredentials credentials) {\n    origin.addProxyCredentials(host, port, realm, credentials);\n  }\n\n  @override\n  set authenticate(\n      Future<bool> Function(Uri url, String scheme, String realm)? f) {\n    origin.authenticate = f as Future<bool> Function(Uri url, String scheme, String? realm);\n  }\n\n  @override\n  set authenticateProxy(\n      Future<bool> Function(String host, int port, String scheme, String realm)?\n          f) {\n    origin.authenticateProxy = f as Future<bool> Function(String host, int port, String scheme, String? realm);\n  }\n\n  @override\n  set badCertificateCallback(\n      bool Function(X509Certificate cert, String host, int port)? callback) {\n    origin.badCertificateCallback = callback;\n  }\n\n  @override\n  void close({bool force = false}) {\n    origin.close(force: force);\n  }\n\n  @override\n  set findProxy(String Function(Uri url)? f) {\n    origin.findProxy = f;\n  }\n\n  Future<HttpClientRequest> monitor(Future<HttpClientRequest> future) async {\n    future = future.catchError((dynamic error, [StackTrace? stackTrace]) {\n      if (httpInfo == null) {\n        httpInfo = HttpInfo.error(error.toString());\n        final HttpKit? kit = ApmKitManager.instance.getKit(ApmKitName.KIT_HTTP);\n        kit?.save(httpInfo);\n      }\n    });\n    final HttpClientRequest request = await future;\n    httpInfo ??= HttpInfo(request.uri, request.method);\n    final HttpKit? kit = ApmKitManager.instance.getKit(ApmKitName.KIT_HTTP);\n    kit?.save(httpInfo);\n    return DoKitHttpClientRequest(request, httpInfo);\n  }\n\n  void addRequestBody(HttpClientRequest request) {\n    if (request.method.toUpperCase() != 'GET') {}\n  }\n\n  @override\n  Future<HttpClientRequest> delete(String host, int port, String path) {\n    return monitor(origin.delete(host, port, path));\n  }\n\n  @override\n  Future<HttpClientRequest> deleteUrl(Uri url) {\n    return monitor(origin.deleteUrl(url));\n  }\n\n  @override\n  Future<HttpClientRequest> get(String host, int port, String path) {\n    return monitor(origin.get(host, port, path));\n  }\n\n  @override\n  Future<HttpClientRequest> getUrl(Uri url) {\n    return monitor(origin.getUrl(url));\n  }\n\n  @override\n  Future<HttpClientRequest> head(String host, int port, String path) {\n    return monitor(origin.head(host, port, path));\n  }\n\n  @override\n  Future<HttpClientRequest> headUrl(Uri url) {\n    return monitor(origin.headUrl(url));\n  }\n\n  @override\n  Future<HttpClientRequest> open(\n      String method, String host, int port, String path) {\n    return monitor(origin.open(method, host, port, path));\n  }\n\n  @override\n  Future<HttpClientRequest> openUrl(String method, Uri url) {\n    return monitor(origin.openUrl(method, url));\n  }\n\n  @override\n  Future<HttpClientRequest> patch(String host, int port, String path) {\n    return monitor(origin.patch(host, port, path));\n  }\n\n  @override\n  Future<HttpClientRequest> patchUrl(Uri url) {\n    return monitor(origin.patchUrl(url));\n  }\n\n  @override\n  Future<HttpClientRequest> post(String host, int port, String path) {\n    return monitor(origin.post(host, port, path));\n  }\n\n  @override\n  Future<HttpClientRequest> postUrl(Uri url) {\n    return monitor(origin.postUrl(url));\n  }\n\n  @override\n  Future<HttpClientRequest> put(String host, int port, String path) {\n    return monitor(origin.put(host, port, path));\n  }\n\n  @override\n  Future<HttpClientRequest> putUrl(Uri url) {\n    return monitor(origin.postUrl(url));\n  }\n}\n\nclass DoKitHttpClientRequest implements HttpClientRequest {\n  DoKitHttpClientRequest(this.origin, this.httpInfo);\n\n  final HttpClientRequest origin;\n  final HttpInfo? httpInfo;\n\n  @override\n  bool get bufferOutput => origin.bufferOutput;\n\n  @override\n  set bufferOutput(bool value) => origin.bufferOutput = value;\n\n  @override\n  int get contentLength => origin.contentLength;\n\n  @override\n  set contentLength(int value) => origin.contentLength = value;\n\n  @override\n  Encoding get encoding => origin.encoding;\n\n  @override\n  set encoding(Encoding value) => origin.encoding = value;\n\n  @override\n  bool get followRedirects => origin.followRedirects;\n\n  @override\n  set followRedirects(bool value) => origin.followRedirects = value;\n\n  @override\n  int get maxRedirects => origin.maxRedirects;\n\n  @override\n  set maxRedirects(int value) => origin.maxRedirects = value;\n\n  @override\n  set persistentConnection(bool value) => origin.persistentConnection = value;\n\n  @override\n  bool get persistentConnection => origin.persistentConnection;\n\n  @override\n  HttpHeaders get headers => origin.headers;\n\n  @override\n  String get method => origin.method;\n\n  @override\n  Uri get uri => origin.uri;\n\n  @override\n  HttpConnectionInfo? get connectionInfo => origin.connectionInfo;\n\n  @override\n  List<Cookie> get cookies => origin.cookies;\n\n  @override\n  Future<HttpClientResponse> get done => origin.done;\n\n  @override\n  void write(Object? obj) {\n    origin.write(obj);\n  }\n\n  @override\n  void writeAll(Iterable<dynamic> objects, [String separator = '']) {\n    origin.writeAll(objects, separator);\n  }\n\n  @override\n  void writeCharCode(int charCode) {\n    origin.writeCharCode(charCode);\n  }\n\n  @override\n  void writeln([dynamic obj = '']) {\n    origin.writeln(obj);\n  }\n\n  @override\n  void add(List<int> data) {\n    origin.add(data);\n    recordParameter(data);\n  }\n\n  void recordParameter(List<int> data) {\n    try {\n      httpInfo?.request.header = headers.toString();\n      httpInfo?.request.add(encoding.decode(data));\n    } catch (e) {\n      print(e);\n    }\n  }\n\n  @override\n  void addError(Object error, [StackTrace? stackTrace]) {\n    origin.addError(error, stackTrace);\n  }\n\n  @override\n  Future<dynamic> addStream(Stream<List<int>> stream) {\n    stream = stream.asBroadcastStream();\n    stream.listen((List<int> event) {\n      recordParameter(event);\n    });\n    return origin.addStream(stream);\n  }\n\n  @override\n  Future<HttpClientResponse> close() {\n    return monitor(origin.close());\n  }\n\n  Future<HttpClientResponse> monitor(Future<HttpClientResponse> future) async {\n    final HttpClientResponse response = await future;\n\n    return DoKitHttpClientResponse(response, recordResponse);\n  }\n\n  void recordResponse(int code, String result, String header, int size) {\n    httpInfo?.response.update(code, result, header, size);\n  }\n\n  @override\n  Future<dynamic> flush() {\n    return origin.flush();\n  }\n\n  @override\n  void abort([Object? exception, StackTrace? stackTrace]) {\n    return origin.abort(exception, stackTrace);\n  }\n}\n\nextension HttpClientRequestExt on HttpClientRequest {\n  void abort([Object? exception, StackTrace? stackTrace]) {\n    this.abort(exception, stackTrace);\n  }\n}\n\nclass DoKitHttpClientResponse implements HttpClientResponse {\n  DoKitHttpClientResponse(this.origin, this.recordResponse);\n\n  final HttpClientResponse origin;\n  final Function(int, String, String, int) recordResponse;\n\n  @override\n  Future<bool> any(bool Function(List<int> element) test) {\n    return origin.any(test);\n  }\n\n  @override\n  Stream<List<int>> asBroadcastStream(\n      {void Function(StreamSubscription<List<int>> subscription)? onListen,\n      void Function(StreamSubscription<List<int>> subscription)? onCancel}) {\n    return origin.asBroadcastStream(onListen: onListen, onCancel: onCancel);\n  }\n\n  @override\n  Stream<E> asyncExpand<E>(Stream<E>? Function(List<int> event) convert) {\n    return asyncExpand(convert);\n  }\n\n  @override\n  Stream<E> asyncMap<E>(FutureOr<E> Function(List<int> event) convert) {\n    return asyncMap(convert);\n  }\n\n  @override\n  Stream<R> cast<R>() {\n    return origin.cast();\n  }\n\n  @override\n  X509Certificate? get certificate => origin.certificate;\n\n  @override\n  HttpClientResponseCompressionState get compressionState =>\n      origin.compressionState;\n\n  @override\n  HttpConnectionInfo? get connectionInfo => origin.connectionInfo;\n\n  @override\n  Future<bool> contains(dynamic needle) {\n    return origin.contains(needle);\n  }\n\n  @override\n  int get contentLength => origin.contentLength;\n\n  @override\n  List<Cookie> get cookies => origin.cookies;\n\n  @override\n  Future<Socket> detachSocket() {\n    return origin.detachSocket();\n  }\n\n  @override\n  Stream<List<int>> distinct(\n      [bool Function(List<int> previous, List<int> next)? equals]) {\n    return origin.distinct(equals);\n  }\n\n  @override\n  Future<E> drain<E>([E? futureValue]) {\n    return origin.drain(futureValue);\n  }\n\n  @override\n  Future<List<int>> elementAt(int index) {\n    return elementAt(index);\n  }\n\n  @override\n  Future<bool> every(bool Function(List<int> element) test) {\n    return origin.every(test);\n  }\n\n  @override\n  Stream<S> expand<S>(Iterable<S> Function(List<int> element) convert) {\n    return origin.expand(convert);\n  }\n\n  @override\n  Future<List<int>> get first => origin.first;\n\n  @override\n  Future<List<int>> firstWhere(bool Function(List<int> element) test,\n      {List<int> Function()? orElse}) {\n    return origin.firstWhere(test, orElse: orElse);\n  }\n\n  @override\n  Future<S> fold<S>(\n      S initialValue, S Function(S previous, List<int> element) combine) {\n    return origin.fold(initialValue, combine);\n  }\n\n  @override\n  Future<dynamic> forEach(void Function(List<int> element) action) {\n    return origin.forEach(action);\n  }\n\n  @override\n  Stream<List<int>> handleError(Function onError,\n      {bool Function(dynamic error)? test}) {\n    return origin.handleError(onError, test: test);\n  }\n\n  @override\n  HttpHeaders get headers => origin.headers;\n\n  @override\n  bool get isBroadcast => origin.isBroadcast;\n\n  @override\n  Future<bool> get isEmpty => origin.isEmpty;\n\n  @override\n  bool get isRedirect => origin.isRedirect;\n\n  @override\n  Future<String> join([String separator = '']) {\n    return origin.join(separator);\n  }\n\n  @override\n  Future<List<int>> get last => origin.last;\n\n  @override\n  Future<List<int>> lastWhere(bool Function(List<int> element) test,\n      {List<int> Function()? orElse}) {\n    return origin.lastWhere(test, orElse: orElse);\n  }\n\n  @override\n  Future<int> get length => origin.length;\n\n  bool isTextResponse() {\n    return headers['content-type'] != null &&\n        (headers['content-type'].toString().contains('json') ||\n            headers['content-type'].toString().contains('text') ||\n            headers['content-type'].toString().contains('xml'));\n  }\n\n  Encoding? getEncoding() {\n    String charset;\n    if (headers.contentType != null && headers.contentType?.charset != null) {\n      charset = headers.contentType!.charset!;\n    } else {\n      charset = 'utf-8';\n    }\n    return Encoding?.getByName(charset);\n  }\n\n  @override\n  StreamSubscription<List<int>> listen(void Function(List<int> event)? onData,\n      {Function? onError, void Function()? onDone, bool? cancelOnError}) {\n    if (!isTextResponse()) {\n      recordResponse(\n          statusCode, '返回结果不支持解析', headers.toString(), contentLength);\n      return origin.listen(onData,\n          onError: onError, onDone: onDone, cancelOnError: cancelOnError);\n    }\n    void onDataWrapper(List<int> result) {\n      onData?.call(result);\n      try {\n        final encoding = getEncoding();\n        if (encoding != null) {\n          recordResponse(statusCode, encoding.decode(result),\n              headers.toString(), contentLength);\n        } else {\n          recordResponse(\n              statusCode, '返回结果解析失败', headers.toString(), contentLength);\n        }\n      } catch (e) {\n        recordResponse(\n            statusCode, '返回结果解析失败', headers.toString(), contentLength);\n      }\n    }\n\n    return origin.listen(onDataWrapper,\n        onError: onError, onDone: onDone, cancelOnError: cancelOnError);\n  }\n\n  @override\n  Stream<S> map<S>(S Function(List<int> event) convert) {\n    return origin.map(convert);\n  }\n\n  @override\n  bool get persistentConnection => origin.persistentConnection;\n\n  @override\n  Future<dynamic> pipe(StreamConsumer<List<int>> streamConsumer) {\n    return origin.pipe(streamConsumer);\n  }\n\n  @override\n  String get reasonPhrase => origin.reasonPhrase;\n\n  @override\n  Future<HttpClientResponse> redirect(\n      [String? method, Uri? url, bool? followLoops]) {\n    return origin.redirect(method, url, followLoops);\n  }\n\n  @override\n  List<RedirectInfo> get redirects => origin.redirects;\n\n  @override\n  Future<List<int>> reduce(\n      List<int> Function(List<int> previous, List<int> element) combine) {\n    return origin.reduce(combine);\n  }\n\n  @override\n  Future<List<int>> get single => origin.single;\n\n  @override\n  Future<List<int>> singleWhere(bool Function(List<int> element) test,\n      {List<int> Function()? orElse}) {\n    return origin.singleWhere(test, orElse: orElse);\n  }\n\n  @override\n  Stream<List<int>> skip(int count) {\n    return origin.skip(count);\n  }\n\n  @override\n  Stream<List<int>> skipWhile(bool Function(List<int> element) test) {\n    return origin.skipWhile(test);\n  }\n\n  @override\n  int get statusCode => origin.statusCode;\n\n  @override\n  Stream<List<int>> take(int count) {\n    return origin.take(count);\n  }\n\n  @override\n  Stream<List<int>> takeWhile(bool Function(List<int> element) test) {\n    return origin.takeWhile(test);\n  }\n\n  @override\n  Stream<List<int>> timeout(Duration timeLimit,\n      {void Function(EventSink<List<int>> sink)? onTimeout}) {\n    return origin.timeout(timeLimit, onTimeout: onTimeout);\n  }\n\n  @override\n  Future<List<List<int>>> toList() {\n    return origin.toList();\n  }\n\n  @override\n  Future<Set<List<int>>> toSet() {\n    return origin.toSet();\n  }\n\n  @override\n  Stream<S> transform<S>(StreamTransformer<List<int>, S> streamTransformer) {\n    Stream<S> s = origin.transform<S>(streamTransformer);\n    if (!isTextResponse()) {\n      recordResponse(\n          statusCode, '返回结果不支持解析', headers.toString(), contentLength);\n      return s;\n    }\n    s = s.asBroadcastStream();\n    s.listen((S event) {\n      if (event is Uint8List) {\n        final Uint8List result = event;\n        var encoding = getEncoding();\n        if (encoding != null) {\n          recordResponse(statusCode, encoding.decode(result.toList()),\n              headers.toString(), event.length);\n        } else {\n          recordResponse(\n              statusCode, '返回结果解析失败', headers.toString(), contentLength);\n        }\n      } else if (event is String) {\n        recordResponse(statusCode, event, headers.toString(), contentLength);\n      } else {\n        recordResponse(statusCode, 'unknown type:${event.runtimeType}',\n            headers.toString(), contentLength);\n      }\n    });\n    return s;\n  }\n\n  @override\n  Stream<List<int>> where(bool Function(List<int> event) test) {\n    return origin.where(test);\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/apm/apm.dart",
    "content": "import 'package:dokit/kit/apm/fps_kit.dart';\nimport 'package:dokit/kit/apm/http_kit.dart';\nimport 'package:dokit/kit/apm/launch/page_launch_kit.dart';\nimport 'package:dokit/kit/apm/log_kit.dart';\nimport 'package:dokit/kit/apm/memory_kit.dart';\nimport 'package:dokit/kit/apm/method_channel_kit.dart';\nimport 'package:dokit/kit/apm/route_kit.dart';\nimport 'package:dokit/kit/apm/source_code_kit.dart';\nimport 'package:dokit/kit/kit.dart';\nimport 'package:dokit/ui/resident_page.dart';\nimport 'package:flutter/material.dart';\n\nclass ApmKitManager {\n  Map<String, ApmKit> kitMap = {\n    ApmKitName.KIT_LOG: LogKit(),\n    ApmKitName.KIT_CHANNEL: MethodChannelKit(),\n    ApmKitName.KIT_ROUTE: RouteKit(),\n    ApmKitName.KIT_FPS: FpsKit(),\n    ApmKitName.KIT_MEMORY: MemoryKit(),\n    ApmKitName.KIT_HTTP: HttpKit(),\n    ApmKitName.KIT_SOURCE_CODE: SourceCodeKit(),\n    ApmKitName.KIT_PAGE_LAUNCH: PageLaunchKit()\n  };\n\n  ApmKitManager._privateConstructor();\n\n  static final ApmKitManager _instance = ApmKitManager._privateConstructor();\n\n  static ApmKitManager get instance => _instance;\n\n  // 如果想要自定义实现，可以用这个方式进行覆盖。后续扩展入口\n  void addKit(String tag, ApmKit kit) {\n    kitMap[tag] = kit;\n  }\n\n  T? getKit<T extends ApmKit>(String name) {\n    if (kitMap.containsKey(name)) {\n      return kitMap[name] as T;\n    }\n    return null;\n  }\n\n  void startUp() {\n    kitMap.forEach((key, kit) {\n      kit.start();\n    });\n  }\n}\n\nabstract class ApmKit implements IKit {\n  late IStorage storage;\n\n  void start();\n\n  void stop();\n\n  IStorage createStorage();\n\n  Widget createDisplayPage();\n\n  ApmKit() {\n    storage = createStorage();\n  }\n\n  @override\n  void tabAction() {\n    // ignore: invalid_use_of_protected_member\n    ResidentPage.residentPageKey.currentState?.setState(() {\n      ResidentPage.tag = getKitName();\n    });\n  }\n\n  bool save(IInfo? info) {\n    return info != null && !storage.contains(info) && storage.save(info);\n  }\n\n  IStorage getStorage() {\n    return storage;\n  }\n}\n\nclass ApmKitName {\n  static const String KIT_FPS = '帧率';\n  static const String KIT_MEMORY = '内存';\n  static const String KIT_LOG = '日志查看';\n  static const String KIT_ROUTE = '路由信息';\n  static const String KIT_CHANNEL = '方法通道';\n  static const String KIT_HTTP = '网络请求';\n  static const String KIT_SOURCE_CODE = '查看源码';\n  static const String KIT_PAGE_LAUNCH = '启动耗时';\n}\n"
  },
  {
    "path": "Flutter/lib/kit/apm/fps_kit.dart",
    "content": "import 'dart:ui';\n\nimport 'package:dokit/dokit.dart';\nimport 'package:dokit/kit/apm/apm.dart';\nimport 'package:dokit/kit/kit.dart';\nimport 'package:dokit/widget/fps_chart.dart';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\n\nclass FpsInfo implements IInfo {\n  int? fps;\n  String? pageName;\n\n  @override\n  int? getValue() {\n    return fps;\n  }\n}\n\nclass FpsKit extends ApmKit {\n  int lastFrame = 0;\n\n  @override\n  String getKitName() {\n    return ApmKitName.KIT_FPS;\n  }\n\n  @override\n  void start() {\n    WidgetsBinding.instance?.addTimingsCallback((timings) {\n      int fps = 0;\n      timings.forEach((element) {\n        FrameTiming frameTiming = element;\n        fps = frameTiming.totalSpan.inMilliseconds;\n        if (checkValid(fps)) {\n          FpsInfo fpsInfo = FpsInfo();\n          fpsInfo.fps = fps;\n          save(fpsInfo);\n        }\n      });\n    });\n  }\n\n  bool checkValid(int fps) {\n    return fps >= 0 && fps < 500;\n  }\n\n  @override\n  void stop() {}\n\n  @override\n  IStorage createStorage() {\n    return CommonStorage(maxCount: 240);\n  }\n\n  @override\n  Widget createDisplayPage() {\n    return FpsPage();\n  }\n\n  @override\n  String getIcon() {\n    return 'images/dk_frame_hist.png';\n  }\n}\n\nclass FpsPage extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() {\n    return FpsPageState();\n  }\n}\n\nclass FpsPageState extends State<FpsPage> {\n  @override\n  Widget build(BuildContext context) {\n    FpsKit? kit = ApmKitManager.instance.getKit<FpsKit>(ApmKitName.KIT_FPS);\n    List<IInfo> list = [];\n    if (kit != null) {\n      list = kit.storage.getAll();\n    }\n    return Container(\n      width: MediaQuery.of(context).size.width,\n      child: Column(\n        mainAxisAlignment: MainAxisAlignment.start,\n        children: <Widget>[\n          Container(\n              height: 44,\n              child: Row(\n                children: [\n                  Container(\n                    child: Image.asset('images/dk_fps_chart.png',\n                        package: DK_PACKAGE_NAME, height: 16, width: 16),\n                    margin: EdgeInsets.only(left: 22, right: 6),\n                  ),\n                  Text('最近240帧耗时',\n                      style: TextStyle(\n                          color: Color(0xff333333),\n                          fontWeight: FontWeight.normal,\n                          fontFamily: 'PingFang SC',\n                          fontSize: 14))\n                ],\n              )),\n          Divider(\n            height: 0.5,\n            color: Color(0xffdddddd),\n            indent: 16,\n            endIndent: 16,\n          ),\n          FpsBarChart(data: list)\n        ],\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/apm/http_kit.dart",
    "content": "import 'dart:io';\nimport 'dart:ui';\n\nimport 'package:dokit/dokit.dart';\nimport 'package:dokit/engine/dokit_http.dart';\nimport 'package:dokit/kit/apm/apm.dart';\nimport 'package:dokit/kit/kit.dart';\nimport 'package:dokit/util/byte_util.dart';\nimport 'package:dokit/util/time_util.dart';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/scheduler.dart';\nimport 'package:flutter/services.dart';\n\nclass HttpInfo implements IInfo {\n  HttpInfo(this.uri, this.method)\n      : startTimestamp = DateTime.now().millisecondsSinceEpoch;\n\n  factory HttpInfo.error(String error) {\n    return HttpInfo(null, '')\n      ..error = error\n      ..response.update(-1, '', '', 0);\n  }\n\n  final Uri? uri;\n\n  final String method;\n\n  final int startTimestamp;\n\n  String? error;\n  HttpRequest request = HttpRequest();\n  HttpResponse response = HttpResponse();\n\n  bool expand = false;\n\n  @override\n  String getValue() {\n    if (error != null) {\n      return 'Error:$error';\n    }\n    return 'Uri:$uri\\nMethod:$method\\nParameters:${request.parameters}\\nResponse:$response';\n  }\n}\n\nclass HttpRequest {\n  List<String> parameters = <String>[];\n  String? header;\n\n  void add(String parameter) {\n    parameters.add(parameter);\n    final HttpKit? kit = ApmKitManager.instance.getKit(ApmKitName.KIT_HTTP);\n    kit?.listener?.call();\n  }\n}\n\nclass HttpResponse {\n  String? _result;\n\n  String? get result => _result;\n  int _code = 0;\n\n  int get code => _code;\n\n  String? _header;\n\n  String? get header => _header;\n  int endTimestamp = 0;\n  int size = 0;\n\n  void update(int code, String result, String header, int size) {\n    _code = code;\n    _result = result;\n    _header = header;\n    this.size = size;\n    endTimestamp = DateTime.now().millisecondsSinceEpoch;\n    final HttpKit? kit = ApmKitManager.instance.getKit(ApmKitName.KIT_HTTP);\n    kit?.listener?.call();\n  }\n\n  @override\n  String toString() {\n    return _code > 0 ? 'code $_code,result $_result' : '';\n  }\n}\n\nclass HttpKit extends ApmKit {\n  @override\n  Widget createDisplayPage() {\n    return HttpPage();\n  }\n\n  Function? listener;\n\n  @override\n  String getIcon() {\n    return 'images/dk_net_monitor.png';\n  }\n\n  @override\n  IStorage createStorage() {\n    return CommonStorage(maxCount: 60);\n  }\n\n  @override\n  String getKitName() {\n    return ApmKitName.KIT_HTTP;\n  }\n\n  @override\n  void start() {\n    final HttpOverrides? origin = HttpOverrides.current;\n    HttpOverrides.global = DoKitHttpOverrides(origin);\n  }\n\n  @override\n  void stop() {}\n\n  void registerListener(Function listener) {\n    this.listener = listener;\n  }\n\n  void unregisterListener() {\n    listener = null;\n  }\n}\n\nclass HttpPage extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() {\n    return HttpPageState();\n  }\n}\n\nclass HttpPageState extends State<HttpPage> {\n  final ScrollController _offsetController =\n      ScrollController(); //定义ListView的controller\n  static bool showSystemChannel = true;\n\n  Future<void> _listener() async {\n    if (!mounted) {\n      return;\n    }\n    if (SchedulerBinding.instance?.schedulerPhase != SchedulerPhase.idle) {\n      await SchedulerBinding.instance?.endOfFrame;\n      if (!mounted) {\n        return;\n      }\n    }\n    setState(() {\n      // 如果正在查看，就不自动滑动到底部\n      if (_offsetController.offset < 10) {\n        _offsetController.jumpTo(0);\n      }\n    });\n  }\n\n  @override\n  void initState() {\n    super.initState();\n    ApmKitManager.instance\n        .getKit<HttpKit>(ApmKitName.KIT_HTTP)\n        ?.registerListener(_listener);\n  }\n\n  @override\n  void dispose() {\n    super.dispose();\n    ApmKitManager.instance\n        .getKit<HttpKit>(ApmKitName.KIT_HTTP)\n        ?.unregisterListener();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final List<IInfo> items = ApmKitManager.instance\n            .getKit<HttpKit>(ApmKitName.KIT_HTTP)\n            ?.getStorage()\n            .getAll()\n            .reversed\n            .toList() ??\n        [];\n    return Column(\n      children: <Widget>[\n        Row(\n          mainAxisAlignment: MainAxisAlignment.start,\n          children: <Widget>[\n            Container(\n              decoration: BoxDecoration(\n                border: Border.all(color: const Color(0xff337cc4), width: 1),\n                borderRadius: BorderRadius.circular(2), // 也可控件一边圆角大小\n              ),\n              margin: const EdgeInsets.only(left: 16, top: 8, bottom: 8),\n              padding: const EdgeInsets.all(2),\n              alignment: Alignment.centerLeft,\n              child: GestureDetector(\n                  behavior: HitTestBehavior.opaque,\n                  onTap: () {\n                    setState(() {\n                      ApmKitManager.instance\n                          .getKit<HttpKit>(ApmKitName.KIT_HTTP)\n                          ?.getStorage()\n                          .clear();\n                    });\n                  },\n                  child: const Text('清除本页数据',\n                      style:\n                          TextStyle(color: Color(0xff333333), fontSize: 12))),\n            ),\n            Container(\n              decoration: BoxDecoration(\n                border: Border.all(color: const Color(0xff337cc4), width: 1),\n                borderRadius: BorderRadius.circular(2), // 也可控件一边圆角大小\n              ),\n              margin: const EdgeInsets.only(left: 10, top: 8, bottom: 8),\n              padding: const EdgeInsets.all(2),\n              alignment: Alignment.centerLeft,\n              child: GestureDetector(\n                behavior: HitTestBehavior.opaque,\n                onTap: () {\n                  _offsetController.jumpTo(0);\n                },\n                child: const Text(\n                  '滑动到底部',\n                  style: TextStyle(color: Color(0xff333333), fontSize: 12),\n                ),\n              ),\n            ),\n          ],\n        ),\n        Expanded(\n          child: Container(\n              alignment: Alignment.topCenter,\n              color: const Color(0xfff5f6f7),\n              child: ListView.builder(\n                  controller: _offsetController,\n                  itemCount: items.length,\n                  reverse: true,\n                  padding: const EdgeInsets.only(\n                      left: 4, right: 4, bottom: 0, top: 8),\n                  shrinkWrap: true,\n                  itemBuilder: (BuildContext context, int index) {\n                    return HttpItemWidget(\n                      item: items[index] as HttpInfo,\n                      index: index,\n                      isLast: index == items.length - 1,\n                    );\n                  })),\n        ),\n      ],\n    );\n  }\n}\n\nclass HttpItemWidget extends StatefulWidget {\n  const HttpItemWidget(\n      {Key? key, required this.item, required this.index, required this.isLast})\n      : super(key: key);\n\n  final HttpInfo item;\n  final int index;\n  final bool isLast;\n\n  @override\n  State<StatefulWidget> createState() {\n    return _HttpItemWidgetState();\n  }\n}\n\nclass _HttpItemWidgetState extends State<HttpItemWidget> {\n  String getCode() {\n    return widget.item.response.code > 0 ? '${widget.item.response.code}' : '-';\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return GestureDetector(\n      onLongPress: () {\n        if (widget.item.response.result != null) {\n          Clipboard.setData(ClipboardData(text: widget.item.response.result));\n          ScaffoldMessenger.of(context).showSnackBar(const SnackBar(\n            duration: Duration(milliseconds: 500),\n            content: Text('请求返回已拷贝至剪贴板'),\n          ));\n        }\n      },\n      onTap: () {\n        setState(() {\n          widget.item.expand = !widget.item.expand;\n        });\n      },\n      child: Card(\n        color: Colors.white,\n        child: Row(\n          mainAxisAlignment: MainAxisAlignment.start,\n          children: <Widget>[\n            Container(\n              width: MediaQuery.of(context).size.width - 80,\n              margin: const EdgeInsets.only(\n                  left: 16, right: 16, top: 12, bottom: 12),\n              child: RichText(\n                maxLines: widget.item.expand ? 9999 : 7,\n                overflow: TextOverflow.ellipsis,\n                text: TextSpan(\n                  children: <InlineSpan>[\n                    TextSpan(\n                        text: '[${toTimeString(widget.item.startTimestamp)}]',\n                        style: const TextStyle(\n                            fontSize: 9,\n                            color: Color(0xff333333),\n                            height: 1.2)),\n                    WidgetSpan(\n                        child: Container(\n                            child: Text(getCode(),\n                                style: const TextStyle(\n                                    fontSize: 8,\n                                    color: Color(0xffffffff),\n                                    height: 1.2)),\n                            height: 11,\n                            margin: const EdgeInsets.only(left: 4),\n                            padding: const EdgeInsets.only(left: 6, right: 6),\n                            decoration: BoxDecoration(\n                                borderRadius:\n                                    const BorderRadius.all(Radius.circular(2)),\n                                color: (widget.item.response.code == 200 ||\n                                        widget.item.response.code == 0)\n                                    ? const Color(0xff337cc4)\n                                    : const Color(0xffd0607e)))),\n                    TextSpan(\n                        text: '  ${widget.item.method}'\n                            '  Cost:${widget.item.response.endTimestamp > 0 ? ((widget.item.response.endTimestamp - widget.item.startTimestamp).toString() + 'ms') : '-'} '\n                            '  Size:${widget.item.response.size > 0 ? (toByteString(widget.item.response.size)) : '-'}',\n                        style: const TextStyle(\n                          fontSize: 9,\n                          color: Color(0xff666666),\n                          height: 1.5,\n                        )),\n                    const TextSpan(\n                        text: '\\nUri: ',\n                        style: TextStyle(\n                            fontSize: 10,\n                            color: Color(0xff333333),\n                            height: 1.5,\n                            fontWeight: FontWeight.bold)),\n                    TextSpan(\n                        text: widget.item.uri.toString(),\n                        style: const TextStyle(\n                            fontSize: 10,\n                            height: 1.5,\n                            color: Color(0xff666666))),\n                    const TextSpan(\n                        text: '\\nRequestHeader: ',\n                        style: TextStyle(\n                            height: 1.5,\n                            fontSize: 10,\n                            color: Color(0xff333333),\n                            fontWeight: FontWeight.bold)),\n                    TextSpan(\n                        text: widget.item.request.header,\n                        style: const TextStyle(\n                            fontSize: 10,\n                            height: 1.5,\n                            color: Color(0xff666666))),\n                    const TextSpan(\n                        text: '\\nResponseHeader: ',\n                        style: TextStyle(\n                            height: 1.5,\n                            fontSize: 10,\n                            color: Color(0xff333333),\n                            fontWeight: FontWeight.bold)),\n                    TextSpan(\n                        text: widget.item.response.header,\n                        style: const TextStyle(\n                            fontSize: 10,\n                            height: 1.5,\n                            color: Color(0xff666666))),\n                    const TextSpan(\n                        text: '\\nParameters: ',\n                        style: TextStyle(\n                            fontSize: 10,\n                            height: 1.5,\n                            color: Color(0xff333333),\n                            fontWeight: FontWeight.bold)),\n                    TextSpan(\n                        text: widget.item.request.parameters.toString(),\n                        style: const TextStyle(\n                            fontSize: 10,\n                            height: 1.5,\n                            color: Color(0xff666666))),\n                    const TextSpan(\n                        text: '\\nResponse: ',\n                        style: TextStyle(\n                            fontSize: 10,\n                            height: 1.5,\n                            color: Color(0xff333333),\n                            fontWeight: FontWeight.bold)),\n                    TextSpan(\n                        text: widget.item.response.result,\n                        style: const TextStyle(\n                            fontSize: 10,\n                            height: 1.5,\n                            color: Color(0xff666666))),\n                  ],\n                ),\n              ),\n            ),\n            Image.asset(\n                widget.item.expand\n                    ? 'images/dk_channel_expand_h.png'\n                    : 'images/dk_channel_expand_n.png',\n                package: DK_PACKAGE_NAME,\n                height: 14,\n                width: 9),\n          ],\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/apm/launch/model.dart",
    "content": "\nclass LaunchInfo{\n  final int costTime;\n  final String? previousPage;\n  final String? newPage;\n\n  LaunchInfo(this.costTime, this.previousPage, this.newPage);\n}"
  },
  {
    "path": "Flutter/lib/kit/apm/launch/page_launch_kit.dart",
    "content": "import 'package:dokit/kit/apm/apm.dart';\nimport 'package:dokit/kit/apm/launch/model.dart';\nimport 'package:dokit/kit/apm/launch/route_observer.dart';\nimport 'package:dokit/kit/kit.dart';\nimport 'package:dokit/ui/dokit_app.dart';\nimport 'package:dokit/util/screen_util.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/src/widgets/framework.dart';\n\nclass PageLaunchKit extends ApmKit {\n  static bool _open = false;\n\n  static OverlayEntry _overlayEntry = OverlayEntry(builder: (context) {\n    return TimeCounter(notifier.value);\n  });\n\n  static VoidCallback? callback;\n\n  static double left = ScreenUtil.instance.screenWidth / 2;\n  static double top = ScreenUtil.instance.screenHeight / 2;\n\n  static void closeCounter() {\n    enabled = false;\n    _open = false;\n    if (callback != null) {\n      notifier.removeListener(PageLaunchKit.callback!);\n    }\n    _overlayEntry.remove();\n  }\n\n  @override\n  Widget createDisplayPage() {\n    return PageLaunchPage();\n  }\n\n  @override\n  IStorage createStorage() {\n    return CommonStorage(maxCount: 120);\n  }\n\n  @override\n  String getIcon() {\n    return 'images/dk_time_counter.png';\n  }\n\n  @override\n  String getKitName() {\n    return ApmKitName.KIT_PAGE_LAUNCH;\n  }\n\n  @override\n  void start() {}\n\n  @override\n  void stop() {}\n}\n\nclass PageLaunchPage extends StatefulWidget {\n  @override\n  _PageLaunchPageState createState() => _PageLaunchPageState();\n}\n\nclass _PageLaunchPageState extends State<PageLaunchPage> {\n  @override\n  Widget build(BuildContext context) {\n    return Container(\n      constraints: BoxConstraints.expand(),\n      child: Padding(\n        padding: const EdgeInsets.all(10.0),\n        child: Column(\n          children: [\n            Text('请在Profile模式下检测性能'),\n            Row(\n              mainAxisAlignment: MainAxisAlignment.spaceBetween,\n              children: [\n                Text('页面启动耗时'),\n                Switch(\n                    value: PageLaunchKit._open,\n                    onChanged: (newValue) {\n                      setState(() {\n                        PageLaunchKit._open = newValue;\n                        if (PageLaunchKit._open) {\n                          openCounter();\n                        } else {\n                          PageLaunchKit.closeCounter();\n                        }\n                      });\n                    })\n              ],\n            )\n          ],\n        ),\n      ),\n    );\n  }\n\n  void openCounter() {\n    enabled = true;\n    PageLaunchKit.callback = () {\n      PageLaunchKit._overlayEntry.markNeedsBuild();\n    };\n    notifier.addListener(PageLaunchKit.callback!);\n    doKitOverlayKey.currentState?.insert(PageLaunchKit._overlayEntry);\n  }\n}\n\nclass TimeCounter extends StatefulWidget {\n  final LaunchInfo info;\n\n  TimeCounter(this.info);\n\n  @override\n  _TimeCounterState createState() => _TimeCounterState();\n}\n\nclass _TimeCounterState extends State<TimeCounter> {\n  @override\n  Widget build(BuildContext context) {\n    return Positioned(\n      left: PageLaunchKit.left,\n      top: PageLaunchKit.top,\n      child: Draggable(\n        childWhenDragging: Container(),\n        onDragEnd: (details) {\n          setState(() {\n            PageLaunchKit.left = details.offset.dx;\n            PageLaunchKit.top = details.offset.dy;\n          });\n        },\n        feedback: buildRealCounter(),\n        child: buildRealCounter(),\n      ),\n    );\n  }\n\n  Container buildRealCounter() {\n    return Container(\n      decoration: BoxDecoration(\n        color: Color(0xffcccccc),\n        borderRadius: BorderRadius.all(Radius.circular(4)),\n      ),\n      child: Padding(\n        padding: const EdgeInsets.all(8.0),\n        child: Stack(\n          children: [\n            Padding(\n              padding: const EdgeInsets.only(right: 18.0),\n              child: Column(\n                children: [\n                  Text(\n                    '${widget.info.previousPage} -> ${widget.info.newPage}',\n                    style: TextStyle(color: Color(0xff333333)),\n                  ),\n                  Text(\n                    'Cost Time: ${widget.info.costTime} ms',\n                    style: TextStyle(color: Color(0xff333333)),\n                  )\n                ],\n              ),\n            ),\n            Positioned(\n              right: 0,\n              child: GestureDetector(\n                child: Image.asset(\n                  'images/dokit_ic_close.png',\n                  package: 'dokit',\n                  height: 22,\n                  width: 22,\n                ),\n                onTap: () {\n                  PageLaunchKit.closeCounter();\n                },\n              ),\n            )\n          ],\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/apm/launch/route_observer.dart",
    "content": "import 'package:dokit/kit/apm/launch/model.dart';\nimport 'package:flutter/material.dart';\n\nValueNotifier<LaunchInfo> notifier = ValueNotifier(LaunchInfo(0, '', ''));\n\nbool enabled = false;\n\nclass LaunchObserver extends NavigatorObserver {\n  @override\n  void didPush(Route route, Route? previousRoute) {\n    super.didPush(route, previousRoute);\n    if (enabled) {\n      int before = DateTime.now().millisecondsSinceEpoch;\n      WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {\n        int now = DateTime.now().millisecondsSinceEpoch;\n        notifier.value = LaunchInfo(\n            now - before, previousRoute?.settings.name, route.settings.name);\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/apm/log_kit.dart",
    "content": "import 'dart:async';\n\nimport 'package:dokit/dokit.dart';\nimport 'package:dokit/kit/apm/apm.dart';\nimport 'package:dokit/kit/kit.dart';\nimport 'package:dokit/util/time_util.dart';\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/scheduler.dart';\nimport 'package:flutter/services.dart';\nimport 'package:shared_preferences/shared_preferences.dart';\n\nclass LogKit extends ApmKit {\n  FlutterExceptionHandler? originOnError;\n\n  final CommonStorage _error = CommonStorage();\n\n  CommonStorage get error => _error;\n\n  @override\n  Widget createDisplayPage() {\n    return LogPage();\n  }\n\n  @override\n  IStorage createStorage() {\n    return CommonStorage(maxCount: 120);\n  }\n\n  @override\n  String getKitName() {\n    return ApmKitName.KIT_LOG;\n  }\n\n  @override\n  String getIcon() {\n    return 'images/dk_log_info.png';\n  }\n\n  @override\n  bool save(IInfo? info) {\n    if ((info as LogBean).type == LogBean.TYPE_ERROR) {\n      _error.save(info);\n    }\n    return super.save(info);\n  }\n\n  @override\n  void start() {\n    resetOnErrorInstance();\n  }\n\n  // dart vm会通过widget_inspector.dart structuredErrors服务替换掉FlutterError.onError，不清楚具体执行时机是什么时候,会重复执行。这里启动定时器每1秒将其替换回来。\n  // 需要保留widget_inspector注入的onError调用，否则会影响android studio的输出\n  void resetOnErrorInstance() {\n    Timer.periodic(const Duration(seconds: 1), (Timer timer) {\n      if (FlutterError.onError != _doKitOnError) {\n        originOnError = FlutterError.onError;\n        FlutterError.onError = _doKitOnError;\n      }\n    });\n  }\n\n  void _doKitOnError(FlutterErrorDetails details) {\n    // 委托给runZone内的onError\n    var stack = details.stack;\n    if (stack != null) {\n      Zone.current.handleUncaughtError(details.exception, stack);\n    }\n    if (originOnError != null) {\n      originOnError?.call(details);\n    }\n  }\n\n  @override\n  void stop() {}\n}\n\nclass LogManager {\n  LogManager._privateConstructor();\n\n  static final LogManager _instance = LogManager._privateConstructor();\n\n  Function? listener;\n\n  void registerListener(Function listener) {\n    this.listener = listener;\n  }\n\n  void unregisterListener() {\n    listener = null;\n  }\n\n  static LogManager get instance {\n    return _instance;\n  }\n\n  List<IInfo>? getLogs() {\n    return ApmKitManager.instance\n        .getKit(ApmKitName.KIT_LOG)\n        ?.getStorage()\n        .getAll();\n  }\n\n  List<IInfo>? getErrors() {\n    return ApmKitManager.instance\n        .getKit<LogKit>(ApmKitName.KIT_LOG)\n        ?.error\n        .getAll();\n  }\n\n  void addLog(int type, String msg) {\n    if (ApmKitManager.instance.getKit(ApmKitName.KIT_LOG) != null) {\n      final LogBean log = LogBean(type, msg);\n      final LogKit? kit = ApmKitManager.instance.getKit(ApmKitName.KIT_LOG);\n      kit?.save(log);\n      if (type != LogBean.TYPE_ERROR || LogPageState._showError) {\n        listener?.call(log);\n      }\n    }\n  }\n\n  void addException(String exception) {\n    addLog(LogBean.TYPE_ERROR, exception);\n  }\n}\n\nclass LogBean implements IInfo {\n  LogBean(this.type, this.msg) {\n    timestamp = DateTime.now().millisecondsSinceEpoch;\n    expand = false;\n  }\n\n  static const int TYPE_LOG = 1;\n  static const int TYPE_DEBUG = 2;\n  static const int TYPE_INFO = 3;\n  static const int TYPE_WARN = 4;\n  static const int TYPE_ERROR = 5;\n\n  final int type;\n  final String msg;\n  late int timestamp;\n  late bool expand;\n\n  @override\n  int getValue() {\n    return 0;\n  }\n}\n\nclass LogPage extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() {\n    return LogPageState();\n  }\n}\n\nclass LogPageState extends State<LogPage> {\n  final ScrollController _offsetController =\n      ScrollController(); //定义ListView的controller\n  static bool _showError = false;\n\n  Future<void> _listener(LogBean logBean) async {\n    if (!mounted) {\n      return Future<void>.value();\n    }\n    // if there's a current frame,\n    if (SchedulerBinding.instance?.schedulerPhase != SchedulerPhase.idle) {\n      // wait for the end of that frame.\n      await SchedulerBinding.instance?.endOfFrame;\n      if (!mounted) {\n        return Future<void>.value();\n      }\n    }\n    setState(() {\n      // 如果正在查看，就不自动滑动到底部\n      if (_offsetController.offset < 10) {\n        _offsetController.jumpTo(0);\n      }\n    });\n\n    return Future<void>.value();\n  }\n\n  @override\n  void initState() {\n    super.initState();\n    LogManager.instance.registerListener(_listener);\n  }\n\n  @override\n  void dispose() {\n    super.dispose();\n    LogManager.instance.unregisterListener();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final List<IInfo> items = (LogPageState._showError\n            ? LogManager.instance.getErrors()?.reversed.toList()\n            : LogManager.instance.getLogs()?.reversed.toList()) ??\n        [];\n    return Column(\n      children: <Widget>[\n        Row(\n          mainAxisAlignment: MainAxisAlignment.start,\n          children: <Widget>[\n            GestureDetector(\n              behavior: HitTestBehavior.opaque,\n              onTap: () {\n                setState(() {\n                  _showError = !_showError;\n                });\n              },\n              child: Container(\n                height: 44,\n                width: 44,\n                padding: const EdgeInsets.only(left: 16),\n                child: Image.asset(\n                    _showError\n                        ? 'images/dk_channel_check_h.png'\n                        : 'images/dk_channel_check_n.png',\n                    package: DK_PACKAGE_NAME,\n                    height: 13,\n                    width: 13),\n              ),\n            ),\n            GestureDetector(\n                behavior: HitTestBehavior.opaque,\n                onTap: () {\n                  setState(() {\n                    _showError = !_showError;\n                  });\n                },\n                child: Text('只显示异常',\n                    style: TextStyle(\n                        color: _showError\n                            ? const Color(0xff337cc4)\n                            : const Color(0xff333333),\n                        fontSize: 12))),\n            Container(\n              decoration: BoxDecoration(\n                border: Border.all(color: const Color(0xff337cc4), width: 1),\n                borderRadius: BorderRadius.circular(2), // 也可控件一边圆角大小\n              ),\n              margin: const EdgeInsets.only(left: 16, top: 8, bottom: 8),\n              padding: const EdgeInsets.all(2),\n              alignment: Alignment.centerLeft,\n              child: GestureDetector(\n                  behavior: HitTestBehavior.opaque,\n                  onTap: () {\n                    setState(() {\n                      ApmKitManager.instance\n                          .getKit<LogKit>(ApmKitName.KIT_LOG)\n                          ?.getStorage()\n                          .clear();\n                      ApmKitManager.instance\n                          .getKit<LogKit>(ApmKitName.KIT_LOG)\n                          ?.error\n                          .clear();\n                    });\n                  },\n                  child: const Text('清除本页数据',\n                      style:\n                          TextStyle(color: Color(0xff333333), fontSize: 12))),\n            ),\n            Container(\n              decoration: BoxDecoration(\n                border: Border.all(color: const Color(0xff337cc4), width: 1),\n                borderRadius: BorderRadius.circular(2), // 也可控件一边圆角大小\n              ),\n              margin: const EdgeInsets.only(left: 10, top: 8, bottom: 8),\n              padding: const EdgeInsets.all(2),\n              alignment: Alignment.centerLeft,\n              child: GestureDetector(\n                  behavior: HitTestBehavior.opaque,\n                  onTap: () {\n                    _offsetController.jumpTo(0);\n                  },\n                  child: const Text('滑动到底部',\n                      style:\n                          TextStyle(color: Color(0xff333333), fontSize: 12))),\n            ),\n          ],\n        ),\n        Expanded(\n          child: Container(\n            alignment: Alignment.topLeft,\n            child: ListView.builder(\n                controller: _offsetController,\n                itemCount: items.length,\n                reverse: true,\n                shrinkWrap: true,\n                padding:\n                    const EdgeInsets.only(left: 0, right: 0, bottom: 0, top: 0),\n                itemBuilder: (BuildContext context, int index) {\n                  return LogItemWidget(\n                    item: items[index] as LogBean,\n                    index: index,\n                    isLast: index == items.length - 1,\n                  );\n                }),\n          ),\n        ),\n      ],\n    );\n  }\n}\n\nclass LogItemWidget extends StatefulWidget {\n  const LogItemWidget(\n      {Key? key, required this.item, required this.index, required this.isLast})\n      : super(key: key);\n\n  final LogBean item;\n  final int index;\n  final bool isLast;\n\n  @override\n  State<StatefulWidget> createState() {\n    return _LogItemWidgetState();\n  }\n}\n\nclass _LogItemWidgetState extends State<LogItemWidget> {\n  static const String KEY_SHOW_LOG_EXPAND_TIPS = 'key_show_log_expand_tips';\n\n  @override\n  Widget build(BuildContext context) {\n    return GestureDetector(\n      onLongPress: () {\n        Clipboard.setData(ClipboardData(text: widget.item.msg));\n        ScaffoldMessenger.of(context).showSnackBar(const SnackBar(\n          duration: Duration(milliseconds: 500),\n          content: Text('已拷贝至剪贴板'),\n        ));\n      },\n      onTap: () {\n        setState(() {\n          widget.item.expand = !widget.item.expand;\n          SharedPreferences.getInstance().then<dynamic>(\n            (SharedPreferences prefs) {\n              if (!prefs.containsKey(KEY_SHOW_LOG_EXPAND_TIPS)) {\n                prefs.setBool(KEY_SHOW_LOG_EXPAND_TIPS, true);\n                ScaffoldMessenger.of(context).showSnackBar(const SnackBar(\n                  duration: Duration(milliseconds: 2000),\n                  content: Text('日志超过7行时，点击可展开日志详情'),\n                ));\n              }\n            },\n          );\n        });\n      },\n      child: Container(\n        padding: const EdgeInsets.only(left: 16, right: 16),\n        decoration: BoxDecoration(\n            color: widget.item.expand ? Colors.black : Colors.white,\n            border: const Border(\n                bottom: BorderSide(width: 0.5, color: Color(0xffeeeeee)))),\n        child: Container(\n          margin: const EdgeInsets.only(top: 10, bottom: 10),\n          child: RichText(\n            maxLines: widget.item.expand ? 9999 : 7,\n            overflow: TextOverflow.ellipsis,\n            text: TextSpan(children: <TextSpan>[\n              TextSpan(\n                  text: '[${toTimeString(widget.item.timestamp)}] ',\n                  style: TextStyle(\n                      color: widget.item.type == LogBean.TYPE_ERROR\n                          ? Colors.red\n                          : (widget.item.expand\n                              ? Colors.white\n                              : const Color(0xff333333)),\n                      height: 1.4,\n                      fontSize: 10)),\n              TextSpan(\n                  text: widget.item.msg,\n                  style: TextStyle(\n                      color: widget.item.type == LogBean.TYPE_ERROR\n                          ? Colors.red\n                          : (widget.item.expand\n                              ? Colors.white\n                              : const Color(0xff333333)),\n                      height: 1.4,\n                      fontSize: 10))\n            ]),\n          ),\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/apm/memory_kit.dart",
    "content": "import 'dart:ui';\n\nimport 'package:dokit/dokit.dart';\nimport 'package:dokit/kit/apm/apm.dart';\nimport 'package:dokit/kit/apm/vm/vm_helper.dart';\nimport 'package:dokit/kit/apm/vm/vm_service_wrapper.dart';\nimport 'package:dokit/kit/kit.dart';\nimport 'package:dokit/util/byte_util.dart';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:vm_service/vm_service.dart';\n\nclass MemoryInfo implements IInfo {\n  int? fps;\n  String? pageName;\n\n  @override\n  int? getValue() {\n    return fps;\n  }\n}\n\nclass MemoryKit extends ApmKit {\n  int lastFrame = 0;\n\n  @override\n  String getKitName() {\n    return ApmKitName.KIT_MEMORY;\n  }\n\n  @override\n  String getIcon() {\n    return 'images/dk_ram.png';\n  }\n\n  @override\n  void start() {\n    VMServiceWrapper.instance.connect();\n    VmHelper vmHelper = VmHelper.instance;\n    VMServiceWrapper.instance\n        .connect()\n        .then((value) => vmHelper.resolveVMInfo());\n  }\n\n  void update() {\n    VmHelper.instance.updateAllocationProfile();\n    VmHelper.instance.updateFlutterVersion();\n    VmHelper.instance.updateMemoryUsage();\n  }\n\n  AllocationProfile? getAllocationProfile() {\n    return VmHelper.instance.allocationProfile;\n  }\n\n  @override\n  void stop() {\n    VMServiceWrapper.instance.disConnect();\n  }\n\n  @override\n  IStorage createStorage() {\n    return CommonStorage(maxCount: 120);\n  }\n\n  @override\n  Widget createDisplayPage() {\n    return MemoryPage();\n  }\n}\n\nclass MemoryPage extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() {\n    return MemoryPageState();\n  }\n}\n\nclass MemoryPageState extends State<MemoryPage> {\n  MemoryKit? kit =\n      ApmKitManager.instance.getKit<MemoryKit>(ApmKitName.KIT_MEMORY);\n  List<ClassHeapStats> heaps = [];\n  TextEditingController editingController = TextEditingController();\n\n  @override\n  void initState() {\n    super.initState();\n    kit?.update();\n    initHeaps();\n  }\n\n  void initHeaps() {\n    if (kit?.getAllocationProfile() != null) {\n      kit!.getAllocationProfile()?.members?.sort((left, right) =>\n          right.bytesCurrent?.compareTo(left.bytesCurrent ?? 0) ?? 0);\n      kit?.getAllocationProfile()?.members?.forEach((element) {\n        if (heaps.length < 32) {\n          heaps.add(element);\n        }\n      });\n    }\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return SingleChildScrollView(\n        child: Container(\n            margin: EdgeInsets.all(16),\n            child: Column(\n              mainAxisAlignment: MainAxisAlignment.start,\n              crossAxisAlignment: CrossAxisAlignment.start,\n              children: <Widget>[\n                Column(\n                    crossAxisAlignment: CrossAxisAlignment.start,\n                    children: <Widget>[\n                      Text('Memory Info',\n                          style: TextStyle(\n                              color: Color(0xff333333),\n                              fontWeight: FontWeight.bold,\n                              fontSize: 16)),\n                      StreamBuilder(\n                        stream: Stream.periodic(Duration(seconds: 2), (value) {\n                          VmHelper.instance.updateAllocationProfile();\n                          VmHelper.instance.updateMemoryUsage();\n                        }),\n                        builder: (context, snapshot) {\n                          return Container(\n                            margin: EdgeInsets.only(top: 3),\n                            alignment: Alignment.topLeft,\n                            child: VmHelper.instance.memoryInfo.isNotEmpty\n                                ? Column(\n                                    children: getMemoryInfo(\n                                        VmHelper.instance.memoryInfo))\n                                : Text('获取Memory数据失败(release模式下无法获取数据)',\n                                    style: TextStyle(\n                                        color: Color(0xff999999),\n                                        fontSize: 12)),\n                          );\n                        },\n                      )\n                    ]),\n                Container(\n                  margin: EdgeInsets.only(top: 10),\n                  alignment: Alignment.centerLeft,\n                  padding: EdgeInsets.only(left: 13),\n                  height: 50,\n                  decoration: BoxDecoration(\n                    border: Border.all(\n                        color: Color(0xff337cc4),\n                        width: 0.5,\n                        style: BorderStyle.solid),\n                    borderRadius: BorderRadius.all(Radius.circular(4)),\n                  ),\n                  child: Row(\n                    mainAxisAlignment: MainAxisAlignment.spaceBetween,\n                    children: <Widget>[\n                      Container(\n                        child: TextField(\n                          controller: editingController,\n                          style:\n                              TextStyle(color: Color(0xff333333), fontSize: 16),\n                          inputFormatters: [\n                            FilteringTextInputFormatter.deny(RegExp(\n                                '[^\\\\u0020-\\\\u007E\\\\u00A0-\\\\u00BE\\\\u2E80-\\\\uA4CF\\\\uF900-\\\\uFAFF\\\\uFE30-\\\\uFE4F\\\\uFF00-\\\\uFFEF\\\\u0080-\\\\u009F\\\\u2000-\\\\u201f\\r\\n]'))\n                          ],\n                          onSubmitted: (value) => {filterAllocations()},\n                          decoration: InputDecoration(\n                            border: InputBorder.none,\n                            hintStyle: TextStyle(\n                                color: Color(0xffbebebe), fontSize: 16),\n                            hintText: '输入类名，查看内存占用',\n                          ),\n                        ),\n                        width: MediaQuery.of(context).size.width - 150,\n                      ),\n                      Container(\n                        width: 60,\n                        child: TextButton(\n                          style: ButtonStyle(\n                            padding: MaterialStateProperty.all(EdgeInsets.only(\n                                left: 15, right: 0, top: 15, bottom: 15)),\n                          ),\n                          child: Image.asset('images/dk_memory_search.png',\n                              package: DK_PACKAGE_NAME, height: 16, width: 16),\n                          onPressed: filterAllocations,\n                        ),\n                      )\n                    ],\n                  ),\n                ),\n                Container(\n                  margin: EdgeInsets.only(top: 12),\n                  height: 34,\n                  child: Row(\n                    mainAxisAlignment: MainAxisAlignment.start,\n                    children: <Widget>[\n                      Container(\n                        width: 80,\n                        decoration: BoxDecoration(\n                            color: Color(0xff337cc4),\n                            borderRadius: BorderRadius.only(\n                                topLeft: Radius.circular(4),\n                                bottomLeft: Radius.circular(4))),\n                        alignment: Alignment.center,\n                        child: Text('Size',\n                            style: TextStyle(\n                                color: Color(0xffffffff), fontSize: 14)),\n                      ),\n                      VerticalDivider(\n                        width: 0.5,\n                        color: Color(0xffffffff),\n                      ),\n                      Container(\n                        width: 80,\n                        decoration: BoxDecoration(\n                          color: Color(0xff337cc4),\n                        ),\n                        alignment: Alignment.center,\n                        child: Text('Count',\n                            style: TextStyle(\n                                color: Color(0xffffffff), fontSize: 14)),\n                      ),\n                      VerticalDivider(\n                        width: 0.5,\n                        color: Color(0xffffffff),\n                      ),\n                      Container(\n                          decoration: BoxDecoration(\n                              color: Color(0xff337cc4),\n                              borderRadius: BorderRadius.only(\n                                  topRight: Radius.circular(4),\n                                  bottomRight: Radius.circular(4))),\n                          width: MediaQuery.of(context).size.width - 193,\n                          alignment: Alignment.center,\n                          child: Text('ClassName',\n                              style: TextStyle(\n                                  color: Color(0xffffffff), fontSize: 14))),\n                    ],\n                  ),\n                ),\n                Container(\n                  height: MediaQuery.of(context).size.height - 200 - 210,\n                  child: ListView.builder(\n                      padding: EdgeInsets.all(0),\n                      itemCount: heaps.length,\n                      itemBuilder: (context, index) {\n                        return HeapItemWidget(\n                          item: heaps[index],\n                          index: index,\n                        );\n                      }),\n                ),\n              ],\n            )));\n  }\n\n  void filterAllocations() {\n    String className = editingController.text;\n    heaps.clear();\n    if (className.length >= 3 && kit?.getAllocationProfile() != null) {\n      kit?.getAllocationProfile()?.members?.forEach((element) {\n        if (element.classRef?.name\n                ?.toLowerCase()\n                .contains(className.toLowerCase()) ==\n            true) {\n          heaps.add(element);\n        }\n      });\n      heaps.sort((left, right) =>\n          right.bytesCurrent?.compareTo(left.bytesCurrent ?? 0) ?? 0);\n    }\n    setState(() {});\n  }\n\n  List<Widget> getMemoryInfo(Map<IsolateRef, MemoryUsage> map) {\n    List<Widget> widgets = <Widget>[];\n    map.forEach((key, value) {\n      widgets.add(RichText(\n          text: TextSpan(children: [\n        TextSpan(\n            text: 'IsolateName: ',\n            style:\n                TextStyle(fontSize: 10, color: Color(0xff333333), height: 1.5)),\n        TextSpan(\n            text: '${key.name}',\n            style:\n                TextStyle(fontSize: 10, height: 1.5, color: Color(0xff666666))),\n        TextSpan(\n            text: '\\nHeapUsage: ',\n            style:\n                TextStyle(height: 1.5, fontSize: 10, color: Color(0xff333333))),\n        TextSpan(\n            text: '${toByteString(value.heapUsage)}',\n            style:\n                TextStyle(fontSize: 10, height: 1.5, color: Color(0xff666666))),\n        TextSpan(\n            text: '\\nHeapCapacity: ',\n            style:\n                TextStyle(fontSize: 10, height: 1.5, color: Color(0xff333333))),\n        TextSpan(\n            text: '${toByteString(value.heapCapacity)}',\n            style:\n                TextStyle(fontSize: 10, height: 1.5, color: Color(0xff666666))),\n        TextSpan(\n            text: '\\nExternalUsage: ',\n            style:\n                TextStyle(fontSize: 10, height: 1.5, color: Color(0xff333333))),\n        TextSpan(\n            text: '${toByteString(value.externalUsage)}',\n            style:\n                TextStyle(fontSize: 10, height: 1.5, color: Color(0xff666666))),\n      ])));\n    });\n    return widgets;\n  }\n}\n\nclass HeapItemWidget extends StatelessWidget {\n  final ClassHeapStats item;\n  final int index;\n\n  HeapItemWidget({Key? key, required this.item, required this.index})\n      : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return Container(\n      height: 40,\n      color: index % 2 == 1 ? Color(0xfffafafa) : Colors.white,\n      child: Row(\n        mainAxisAlignment: MainAxisAlignment.start,\n        children: <Widget>[\n          Container(\n            width: 80,\n            alignment: Alignment.center,\n            child: Text('${toByteString(item.bytesCurrent)}',\n                style: TextStyle(color: Color(0xff333333), fontSize: 12)),\n          ),\n          Container(\n            width: 80,\n            alignment: Alignment.center,\n            child: Text('${item.instancesCurrent}',\n                style: TextStyle(color: Color(0xff333333), fontSize: 12)),\n          ),\n          Container(\n              width: MediaQuery.of(context).size.width - 193,\n              alignment: Alignment.center,\n              child: Text('${item.classRef?.name}',\n                  style: TextStyle(color: Color(0xff333333), fontSize: 12))),\n        ],\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/apm/method_channel_kit.dart",
    "content": "import 'dart:ui';\n\nimport 'package:dokit/dokit.dart';\nimport 'package:dokit/kit/apm/apm.dart';\nimport 'package:dokit/kit/kit.dart';\nimport 'package:dokit/util/time_util.dart';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/scheduler.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flutter/src/widgets/framework.dart';\n\nclass ChannelInfo implements IInfo {\n  static const int TYPE_USER_SEND = 0;\n  static const int TYPE_USER_RECEIVE = 1;\n  static const int TYPE_SYSTEM_SEND = 2;\n  static const int TYPE_SYSTEM_RECEIVE = 3;\n  final String channelName;\n\n  final String? method;\n\n  final dynamic arguments;\n  final int startTimestamp;\n  int endTimestamp = 0;\n\n  final int type;\n  dynamic results;\n  bool expand = false;\n  MethodCodec? methodCodec;\n  MessageCodec? messageCodec;\n\n  ChannelInfo(this.channelName, this.method, this.arguments, this.type)\n      : this.startTimestamp = DateTime.now().millisecondsSinceEpoch;\n\n  @override\n  String getValue() {\n    return (type == TYPE_USER_SEND\n            ? 'dart端调用方法\\n'\n            : type == TYPE_USER_RECEIVE\n                ? 'native端调用方法\\n'\n                : type == TYPE_SYSTEM_SEND\n                    ? 'dart端调用方法[系统]\\n'\n                    : 'native端调用方法[系统]\\n') +\n        'channelName:${channelName}\\n' +\n        'method:${method}\\n' +\n        'arguments:${arguments}\\n' +\n        'results:${results}';\n  }\n\n  factory ChannelInfo.error(String channelName, int type) {\n    return ChannelInfo(channelName, '', null, type);\n  }\n}\n\nclass MethodChannelKit extends ApmKit {\n  Function? listener;\n\n  @override\n  Widget createDisplayPage() {\n    return ChannelPage();\n  }\n\n  @override\n  String getIcon() {\n    return 'images/dk_method_channel.png';\n  }\n\n  @override\n  IStorage createStorage() {\n    return CommonStorage(maxCount: 240);\n  }\n\n  @override\n  String getKitName() {\n    return ApmKitName.KIT_CHANNEL;\n  }\n\n  @override\n  bool save(IInfo? info) {\n    if (!ChannelPageState.showSystemChannel &&\n        ((info as ChannelInfo).type == ChannelInfo.TYPE_SYSTEM_RECEIVE ||\n            info.type == ChannelInfo.TYPE_SYSTEM_SEND)) {\n      super.save(info);\n      return false;\n    }\n    bool result = super.save(info);\n    ApmKitManager.instance\n        .getKit<MethodChannelKit>(ApmKitName.KIT_CHANNEL)\n        ?.listener\n        ?.call();\n    return result;\n  }\n\n  @override\n  void start() {}\n\n  @override\n  void stop() {}\n\n  void registerListener(Function listener) {\n    this.listener = listener;\n  }\n\n  void unregisterListener() {\n    this.listener = null;\n  }\n}\n\nclass ChannelPage extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() {\n    return ChannelPageState();\n  }\n}\n\nclass ChannelPageState extends State<ChannelPage> {\n  // 定义ListView的controller\n  ScrollController _offsetController = ScrollController();\n  static bool showSystemChannel = false;\n\n  Future<void> _listener() async {\n    if (!mounted) return;\n    if (SchedulerBinding.instance?.schedulerPhase != SchedulerPhase.idle) {\n      await SchedulerBinding.instance?.endOfFrame;\n      if (!mounted) return;\n    }\n    setState(() {\n      // 如果正在查看，就不自动滑动到底部\n      if (_offsetController.offset < 10) {\n        _offsetController.jumpTo(0);\n      }\n    });\n  }\n\n  @override\n  void initState() {\n    super.initState();\n    ApmKitManager.instance\n        .getKit<MethodChannelKit>(ApmKitName.KIT_CHANNEL)\n        ?.registerListener(_listener);\n  }\n\n  @override\n  void dispose() {\n    super.dispose();\n    ApmKitManager.instance\n        .getKit<MethodChannelKit>(ApmKitName.KIT_CHANNEL)\n        ?.unregisterListener();\n    _offsetController.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    List<IInfo>? items = ApmKitManager.instance\n        .getKit<MethodChannelKit>(ApmKitName.KIT_CHANNEL)\n        ?.getStorage()\n        .getAll()\n        .reversed\n        .where((element) => showSystemChannel\n            ? true\n            : ((element as ChannelInfo).type == ChannelInfo.TYPE_USER_SEND ||\n                (element).type == ChannelInfo.TYPE_USER_RECEIVE))\n        .toList();\n    return Column(\n      children: <Widget>[\n        Container(\n            color: Color(0xfff5f6f7),\n            alignment: Alignment.topLeft,\n            child: Row(\n              mainAxisAlignment: MainAxisAlignment.start,\n              children: <Widget>[\n                GestureDetector(\n                  behavior: HitTestBehavior.opaque,\n                  onTap: () {\n                    this.setState(() {\n                      showSystemChannel = !showSystemChannel;\n                    });\n                  },\n                  child: Container(\n                    height: 44,\n                    width: 44,\n                    padding: EdgeInsets.only(left: 16),\n                    child: Image.asset(\n                        showSystemChannel\n                            ? 'images/dk_channel_check_h.png'\n                            : 'images/dk_channel_check_n.png',\n                        package: DK_PACKAGE_NAME,\n                        height: 13,\n                        width: 13),\n                  ),\n                ),\n                GestureDetector(\n                    behavior: HitTestBehavior.opaque,\n                    onTap: () {\n                      this.setState(() {\n                        showSystemChannel = !showSystemChannel;\n                      });\n                    },\n                    child: Text('显示系统Channel',\n                        style: TextStyle(\n                            color: showSystemChannel\n                                ? Color(0xff337cc4)\n                                : Color(0xff333333),\n                            fontSize: 12))),\n                GestureDetector(\n                    behavior: HitTestBehavior.opaque,\n                    onTap: () {\n                      this.setState(() {\n                        ApmKitManager.instance\n                            .getKit<MethodChannelKit>(ApmKitName.KIT_CHANNEL)\n                            ?.getStorage()\n                            .clear();\n                      });\n                    },\n                    child: Container(\n                      decoration: BoxDecoration(\n                        border: Border.all(color: Color(0xff337cc4), width: 1),\n                        borderRadius: BorderRadius.circular(2), // 也可控件一边圆角大小\n                      ),\n                      margin: EdgeInsets.only(left: 10),\n                      padding: EdgeInsets.all(2),\n                      child: Text('清除本页数据',\n                          style: TextStyle(\n                              color: showSystemChannel\n                                  ? Color(0xff337cc4)\n                                  : Color(0xff333333),\n                              fontSize: 12)),\n                    )),\n                GestureDetector(\n                    behavior: HitTestBehavior.opaque,\n                    onTap: () {\n                      _offsetController.jumpTo(0);\n                    },\n                    child: Container(\n                      decoration: BoxDecoration(\n                        border: Border.all(color: Color(0xff337cc4), width: 1),\n                        borderRadius: BorderRadius.circular(2), // 也可控件一边圆角大小\n                      ),\n                      margin: EdgeInsets.only(left: 10),\n                      padding: EdgeInsets.all(2),\n                      child: Text('滑动到底部',\n                          style: TextStyle(\n                              color: showSystemChannel\n                                  ? Color(0xff337cc4)\n                                  : Color(0xff333333),\n                              fontSize: 12)),\n                    )),\n              ],\n            )),\n        Expanded(\n          child: Container(\n              alignment: Alignment.topCenter,\n              color: Color(0xfff5f6f7),\n              child: items == null\n                  ? null\n                  : ListView.builder(\n                      controller: _offsetController,\n                      itemCount: items.length,\n                      padding:\n                          EdgeInsets.only(left: 4, right: 4, bottom: 0, top: 0),\n                      reverse: true,\n                      shrinkWrap: true,\n                      itemBuilder: (context, index) {\n                        return ChannelItemWidget(\n                          item: items[index] as ChannelInfo,\n                          index: index,\n                          isLast: index == items.length - 1,\n                        );\n                      })),\n        )\n      ],\n    );\n  }\n}\n\nclass ChannelItemWidget extends StatefulWidget {\n  final ChannelInfo item;\n  final int index;\n  final bool isLast;\n\n  ChannelItemWidget(\n      {Key? key, required this.item, required this.index, required this.isLast})\n      : super(key: key);\n\n  @override\n  State<StatefulWidget> createState() {\n    return _ChannelItemWidgetState();\n  }\n}\n\nclass _ChannelItemWidgetState extends State<ChannelItemWidget> {\n  String getChannelType() {\n    switch (widget.item.type) {\n      case ChannelInfo.TYPE_USER_SEND:\n        return 'Flutter > 终端';\n      case ChannelInfo.TYPE_USER_RECEIVE:\n        return '终端 > Flutter';\n      case ChannelInfo.TYPE_SYSTEM_SEND:\n        return 'Flutter > 终端 [系统调用]';\n      case ChannelInfo.TYPE_SYSTEM_RECEIVE:\n        return '终端 > Flutter [系统调用]';\n      default:\n        return 'Flutter > 终端';\n    }\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return GestureDetector(\n      onTap: () {\n        setState(() {\n          widget.item.expand = !widget.item.expand;\n        });\n      },\n      child: Card(\n        color: Colors.white,\n        child: Row(\n          mainAxisAlignment: MainAxisAlignment.start,\n          children: <Widget>[\n            Container(\n                width: MediaQuery.of(context).size.width - 80,\n                margin:\n                    EdgeInsets.only(left: 16, right: 16, top: 12, bottom: 12),\n                child: RichText(\n                    maxLines: widget.item.expand ? 9999 : 7,\n                    overflow: TextOverflow.ellipsis,\n                    text: TextSpan(children: [\n                      TextSpan(\n                          text: '[${toTimeString(widget.item.startTimestamp)}]',\n                          style: TextStyle(\n                              fontSize: 9,\n                              color: Color(0xff333333),\n                              height: 1.2)),\n                      WidgetSpan(\n                          child: Container(\n                              child: Text('${getChannelType()}',\n                                  style: TextStyle(\n                                      fontSize: 8,\n                                      color: Color(0xffffffff),\n                                      height: 1.2)),\n                              height: 11,\n                              margin: EdgeInsets.only(left: 4),\n                              padding: EdgeInsets.only(left: 6, right: 6),\n                              decoration: BoxDecoration(\n                                  borderRadius:\n                                      BorderRadius.all(Radius.circular(2)),\n                                  color: (widget.item.type % 2 != 0\n                                      ? Color(0xffd0607e)\n                                      : Color(0xff337cc4))))),\n                      TextSpan(\n                          text:\n                              '  Cost:${widget.item.endTimestamp > 0 ? ((widget.item.endTimestamp - widget.item.startTimestamp).toString() + 'ms') : '-'} ',\n                          style: TextStyle(\n                            fontSize: 9,\n                            color: Color(0xff666666),\n                            height: 1.5,\n                          )),\n                      TextSpan(\n                          text: '\\nChannelName: ',\n                          style: TextStyle(\n                              fontSize: 10,\n                              color: Color(0xff333333),\n                              height: 1.5,\n                              fontWeight: FontWeight.bold)),\n                      TextSpan(\n                          text: '${widget.item.channelName}',\n                          style: TextStyle(\n                              fontSize: 10,\n                              height: 1.5,\n                              color: Color(0xff666666))),\n                      TextSpan(\n                          text: '\\nMethod: ',\n                          style: TextStyle(\n                              height: 1.5,\n                              fontSize: 10,\n                              color: Color(0xff333333),\n                              fontWeight: FontWeight.bold)),\n                      TextSpan(\n                          text: '${widget.item.method}',\n                          style: TextStyle(\n                              fontSize: 10,\n                              height: 1.5,\n                              color: Color(0xff666666))),\n                      TextSpan(\n                          text: '\\nArguments: ',\n                          style: TextStyle(\n                              fontSize: 10,\n                              height: 1.5,\n                              color: Color(0xff333333),\n                              fontWeight: FontWeight.bold)),\n                      TextSpan(\n                          text: '${widget.item.arguments}',\n                          style: TextStyle(\n                              fontSize: 10,\n                              height: 1.5,\n                              color: Color(0xff666666))),\n                      TextSpan(\n                          text: '\\nResult: ',\n                          style: TextStyle(\n                              fontSize: 10,\n                              height: 1.5,\n                              color: Color(0xff333333),\n                              fontWeight: FontWeight.bold)),\n                      TextSpan(\n                          text: '${widget.item.results}',\n                          style: TextStyle(\n                              fontSize: 10,\n                              height: 1.5,\n                              color: Color(0xff666666))),\n                    ]))),\n            Image.asset(\n                widget.item.expand\n                    ? 'images/dk_channel_expand_h.png'\n                    : 'images/dk_channel_expand_n.png',\n                package: DK_PACKAGE_NAME,\n                height: 14,\n                width: 9)\n          ],\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/apm/route_kit.dart",
    "content": "import 'package:dokit/dokit.dart';\nimport 'package:dokit/kit/apm/apm.dart';\nimport 'package:dokit/kit/kit.dart';\nimport 'package:dokit/ui/dokit_app.dart';\nimport 'package:flutter/material.dart';\n\nclass RouteKit extends ApmKit {\n  @override\n  Widget createDisplayPage() {\n    return RouteInfoPage();\n  }\n\n  @override\n  IStorage createStorage() {\n    return CommonStorage(maxCount: 120);\n  }\n\n  @override\n  String getKitName() {\n    return ApmKitName.KIT_ROUTE;\n  }\n\n  @override\n  String getIcon() {\n    return 'images/dk_view_route.png';\n  }\n\n  @override\n  void start() {}\n\n  @override\n  void stop() {}\n}\n\nclass RouteInfoPage extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() {\n    return RouteInfoPageState();\n  }\n}\n\nclass RouteInfoPageState extends State<RouteInfoPage> {\n  RouteInfo? route;\n\n  @override\n  void initState() {\n    super.initState();\n    WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) {\n      final RouteInfo? route = findRoute();\n      if (route != null && (route.current != null)) {\n        setState(() {\n          this.route = route;\n        });\n      }\n    });\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return SingleChildScrollView(\n      child: Container(\n          margin: const EdgeInsets.all(16),\n          child: Column(\n            children: <Widget>[\n              Container(\n                  alignment: Alignment.topLeft,\n                  width: MediaQuery.of(context).size.width,\n                  child: Column(\n                    mainAxisAlignment: MainAxisAlignment.start,\n                    crossAxisAlignment: CrossAxisAlignment.start,\n                    children: <Widget>[\n                      const Text('当前页面路由树',\n                          style: TextStyle(\n                              color: Color(0xff333333),\n                              fontSize: 16,\n                              fontWeight: FontWeight.bold)),\n                      Container(\n                          alignment: Alignment.center,\n                          margin: const EdgeInsets.only(top: 3),\n                          child: const Text(\n                              'Navigator存在嵌套情况，打印当前页面在每层Navigator内的路由信息',\n                              style: TextStyle(\n                                  color: Color(0xff999999), fontSize: 12))),\n                      Container(\n                          margin: const EdgeInsets.only(top: 10),\n                          alignment: Alignment.topLeft,\n                          child: Column(\n                              crossAxisAlignment: CrossAxisAlignment.start,\n                              children: buildRouteInfoWidget()))\n                    ],\n                  )),\n            ],\n          )),\n    );\n  }\n\n  List<Widget> buildRouteInfoWidget() {\n    final List<Widget> widgets = <Widget>[];\n    RouteInfo? route = this.route;\n    if (route == null) {\n      return widgets;\n    }\n    do {\n      if (route?.current != null) {\n        widgets.add(\n          Container(\n            padding: const EdgeInsets.all(12),\n            width: MediaQuery.of(context).size.width,\n            decoration: const BoxDecoration(\n                color: Color(0xfff5f6f7),\n                borderRadius: BorderRadius.all(Radius.circular(4.0))),\n            alignment: Alignment.topLeft,\n            child: RichText(\n              text: TextSpan(\n                children: <TextSpan>[\n                  const TextSpan(\n                      text: '路由名称: ',\n                      style: TextStyle(\n                          fontSize: 10,\n                          color: Color(0xff333333),\n                          height: 1.5,\n                          fontWeight: FontWeight.bold)),\n                  TextSpan(\n                      text: route?.current?.settings.name,\n                      style: const TextStyle(\n                          fontSize: 10, height: 1.5, color: Color(0xff666666))),\n                  const TextSpan(\n                      text: '\\n路由参数: ',\n                      style: TextStyle(\n                          height: 1.5,\n                          fontSize: 10,\n                          color: Color(0xff333333),\n                          fontWeight: FontWeight.bold)),\n                  TextSpan(\n                      text: '${route?.current?.settings.arguments}',\n                      style: const TextStyle(\n                          fontSize: 10, height: 1.5, color: Color(0xff666666))),\n                  const TextSpan(\n                      text: '\\n所在Navigator: ',\n                      style: TextStyle(\n                          fontSize: 10,\n                          height: 1.5,\n                          color: Color(0xff333333),\n                          fontWeight: FontWeight.bold)),\n                  TextSpan(\n                      text: route?.parentNavigator != null\n                          ? route?.parentNavigator.toString()\n                          : '未知',\n                      style: const TextStyle(\n                          fontSize: 10, height: 1.5, color: Color(0xff666666))),\n                  const TextSpan(\n                      text: '\\n所有信息: ',\n                      style: TextStyle(\n                          fontSize: 10,\n                          height: 1.5,\n                          color: Color(0xff333333),\n                          fontWeight: FontWeight.bold)),\n                  TextSpan(\n                      text: route?.current.toString(),\n                      style: const TextStyle(\n                          fontSize: 10, height: 1.5, color: Color(0xff666666))),\n                ],\n              ),\n            ),\n          ),\n        );\n      }\n      route = route?.parent;\n      if (route != null && route.parent != null) {\n        widgets.add(\n          Container(\n            margin: const EdgeInsets.only(top: 10, bottom: 10),\n            alignment: Alignment.center,\n            child: Image.asset('images/dk_route_arrow.png',\n                package: DK_PACKAGE_NAME, height: 13, width: 12),\n          ),\n        );\n      }\n      // 过滤掉dokit自带的navigator\n    } while (route != null);\n    return widgets;\n  }\n\n  RouteInfo? findRoute() {\n    Element? topElement;\n    var context = DoKitApp.appKey.currentContext;\n    if (context == null) return null;\n    final ModalRoute<dynamic>? rootRoute = ModalRoute.of(context);\n    void listTopView(Element element) {\n      if (element.widget is! PositionedDirectional) {\n        if (element is RenderObjectElement &&\n            element.renderObject is RenderBox) {\n          final ModalRoute<dynamic>? route = ModalRoute.of(element);\n          if (route != null && route != rootRoute) {\n            topElement = element;\n          }\n        }\n        element.visitChildren(listTopView);\n      }\n    }\n\n    context.visitChildElements(listTopView);\n    if (topElement != null) {\n      final RouteInfo routeInfo = RouteInfo();\n      routeInfo.current = ModalRoute.of(topElement!);\n      buildNavigatorTree(topElement!, routeInfo);\n      return routeInfo;\n    }\n    return null;\n  }\n\n  /// 反向遍历生成路由树\n  void buildNavigatorTree(Element element, RouteInfo routeInfo) {\n    final NavigatorState? navigatorState =\n        element.findAncestorStateOfType<NavigatorState>();\n\n    if (navigatorState != null) {\n      final RouteInfo parent = RouteInfo();\n      parent.current = ModalRoute.of(navigatorState.context);\n      routeInfo.parent = parent;\n      routeInfo.parentNavigator = navigatorState.widget;\n      return buildNavigatorTree(navigatorState.context as Element, parent);\n    }\n  }\n}\n\nclass RouteInfo extends IInfo {\n  ModalRoute<dynamic>? current;\n  Widget? parentNavigator;\n  RouteInfo? parent;\n\n  @override\n  int getValue() {\n    return 0;\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/apm/source_code_kit.dart",
    "content": "import 'dart:convert';\n\nimport 'package:dokit/dokit.dart';\nimport 'package:dokit/kit/apm/apm.dart';\nimport 'package:dokit/kit/apm/vm/vm_helper.dart';\nimport 'package:dokit/kit/kit.dart';\nimport 'package:dokit/widget/source_code/source_code_view.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/src/widgets/framework.dart';\n\nclass SourceCodeKit extends ApmKit {\n  @override\n  Widget createDisplayPage() {\n    return SourceCodePage();\n  }\n\n  @override\n  IStorage createStorage() {\n    return CommonStorage(maxCount: 120);\n  }\n\n  @override\n  String getIcon() {\n    return 'images/dk_source_code.png';\n  }\n\n  @override\n  String getKitName() {\n    return ApmKitName.KIT_SOURCE_CODE;\n  }\n\n  @override\n  void start() {}\n\n  @override\n  void stop() {}\n}\n\nclass SourceCodePage extends StatefulWidget {\n  @override\n  _SourceCodePageState createState() => _SourceCodePageState();\n}\n\nclass _SourceCodePageState extends State<SourceCodePage> {\n  String? sourceCode;\n\n  static const String _dokitSourceCodeGroup = 'dokit_source_code-group';\n\n  @override\n  void dispose() {\n    // ignore: invalid_use_of_protected_member\n    WidgetInspectorService.instance.disposeGroup(_dokitSourceCodeGroup);\n    super.dispose();\n  }\n\n  @override\n  void initState() {\n    super.initState();\n    WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {\n      var renderObject = findTopRenderObject();\n      if (renderObject == null) {\n        return;\n      }\n      WidgetInspectorService.instance.selection.current = renderObject;\n      final id = WidgetInspectorService.instance\n          // ignore: invalid_use_of_protected_member\n          .toId(renderObject.toDiagnosticsNode(), _dokitSourceCodeGroup);\n      if (id == null) {\n        return;\n      }\n      final String? nodeDesc = WidgetInspectorService.instance\n          .getSelectedSummaryWidget(id, _dokitSourceCodeGroup);\n      if (nodeDesc != null) {\n        final Map<String, dynamic>? map =\n            json.decode(nodeDesc) as Map<String, dynamic>?;\n        if (map != null) {\n          final Map<String, dynamic>? location =\n              map['creationLocation'] as Map<String, dynamic>?;\n          if (location != null) {\n            final fileLocation = location['file'] as String;\n            final fileName = fileLocation.split(\"/\").last;\n            getScriptList(fileName).then((value) => {\n                  setState(() {\n                    sourceCode = value!.source;\n                  })\n                });\n          }\n        }\n      }\n    });\n  }\n\n  RenderObject? findTopRenderObject() {\n    Element? topElement;\n    var context = DoKitApp.appKey.currentContext;\n    if (context == null) {\n      return null;\n    }\n    final ModalRoute<dynamic>? rootRoute = ModalRoute.of(context);\n    void listTopView(Element element) {\n      if (element.widget is! PositionedDirectional) {\n        if (element is RenderObjectElement &&\n            element.renderObject is RenderBox) {\n          final ModalRoute<dynamic>? route = ModalRoute.of(element);\n          if (route != null && route != rootRoute) {\n            topElement = element;\n          }\n        }\n        element.visitChildren(listTopView);\n      }\n    }\n\n    context.visitChildElements(listTopView);\n    if (topElement != null) {\n      return topElement!.renderObject;\n    }\n    return null;\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Container(\n      constraints: BoxConstraints.expand(),\n      child: sourceCode == null\n          ? Center(\n              child: Text(\"加载中\"),\n            )\n          : Padding(\n              padding: const EdgeInsets.all(8.0),\n              child: SourceCodeView(sourceCode: sourceCode ?? ''),\n            ),\n    );\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/apm/vm/version.dart",
    "content": "import 'package:flutter/material.dart';\n\nclass FlutterVersion extends SemanticVersion {\n  FlutterVersion._({\n    required this.version,\n    required this.channel,\n    required this.repositoryUrl,\n    required this.frameworkRevision,\n    required this.frameworkCommitDate,\n    required this.engineRevision,\n    required this.dartSdkVersion,\n  }) {\n    // Flutter versions can come in as '1.10.7-pre.42', so we strip out any\n    // characters that are not digits. We do not currently have a need to know\n    // more version parts than major, minor, and patch. If this changes, we can\n    // add support for the extra values.\n    final List<String> _versionParts = version\n        .split('.')\n        .map((String part) => RegExp(r'\\d+').stringMatch(part) ?? '0')\n        .toList();\n    major =\n        _versionParts.isNotEmpty ? int.tryParse(_versionParts.first) ?? 0 : 0;\n    minor = _versionParts.length > 1 ? int.tryParse(_versionParts[1]) ?? 0 : 0;\n    patch = _versionParts.length > 2 ? int.tryParse(_versionParts[2]) ?? 0 : 0;\n  }\n\n  factory FlutterVersion.parse(Map<String, dynamic>? json) {\n    if (json == null) {\n      json={};\n    }\n    return FlutterVersion._(\n      version: json['frameworkVersion'] as String,\n      channel: json['channel'] as String,\n      repositoryUrl: json['repositoryUrl'] as String? ?? 'unknown source',\n      frameworkRevision: json['frameworkRevisionShort'] as String,\n      frameworkCommitDate: json['frameworkCommitDate'] as String,\n      engineRevision: json['engineRevisionShort'] as String,\n      dartSdkVersion: json['dartSdkVersion'] as String,\n    );\n  }\n\n  final String version;\n\n  final String channel;\n\n  final String repositoryUrl;\n\n  final String frameworkRevision;\n\n  final String frameworkCommitDate;\n\n  final String engineRevision;\n\n  final String dartSdkVersion;\n\n  String get flutterVersionSummary => <String>[\n        if (version != 'unknown') version,\n        'channel $channel',\n        repositoryUrl,\n      ].join(' • ');\n\n  String get frameworkVersionSummary =>\n      'revision $frameworkRevision • $frameworkCommitDate';\n\n  String get engineVersionSummary => 'revision $engineRevision';\n\n  @override\n  bool operator ==(Object other) {\n    return other is FlutterVersion &&\n        version == other.version &&\n        channel == other.channel &&\n        repositoryUrl == other.repositoryUrl &&\n        frameworkRevision == other.frameworkRevision &&\n        frameworkCommitDate == other.frameworkCommitDate &&\n        engineRevision == other.engineRevision &&\n        dartSdkVersion == other.dartSdkVersion;\n  }\n\n  @override\n  int get hashCode => hashValues(\n        version,\n        channel,\n        repositoryUrl,\n        frameworkRevision,\n        frameworkCommitDate,\n        engineRevision,\n        dartSdkVersion,\n      );\n}\n\nclass SemanticVersion implements Comparable<SemanticVersion> {\n  SemanticVersion({this.major = 0, this.minor = 0, this.patch = 0});\n\n  int major;\n\n  int minor;\n\n  int patch;\n\n  bool isSupported({required SemanticVersion supportedVersion}) =>\n      compareTo(supportedVersion) >= 0;\n\n  @override\n  int compareTo(SemanticVersion other) {\n    if (major == other.major && minor == other.minor && patch == other.patch) {\n      return 0;\n    }\n    if (major > other.major ||\n        (major == other.major && minor > other.minor) ||\n        (major == other.major && minor == other.minor && patch > other.patch)) {\n      return 1;\n    }\n    return -1;\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/apm/vm/vm_helper.dart",
    "content": "import 'dart:async';\n\nimport 'package:dokit/kit/apm/vm/version.dart';\nimport 'package:dokit/kit/apm/vm/vm_service_wrapper.dart';\nimport 'package:package_info/package_info.dart';\nimport 'package:vm_service/vm_service.dart';\n\nclass VmHelper {\n  VmHelper._privateConstructor();\n\n  static final VmHelper _instance = VmHelper._privateConstructor();\n\n  static VmHelper get instance => _instance;\n\n  // 各Isolate内存使用情况\n  final memoryInfo = <IsolateRef, MemoryUsage>{};\n  AllocationProfile? allocationProfile;\n  PackageInfo? packageInfo;\n\n  // flutter版本\n  String _flutterVersion = '';\n\n  VM? get vm => VMServiceWrapper.instance.vm;\n\n  Future<void> resolveVMInfo() async {\n    if (!VMServiceWrapper.instance.connected) {\n      return;\n    }\n    await PackageInfo.fromPlatform().then((value) => packageInfo = value);\n    updateMemoryUsage();\n    updateFlutterVersion();\n    updateAllocationProfile();\n  }\n\n  String get flutterVersion {\n    if (!VMServiceWrapper.instance.connected) {\n      return 'Flutter Attach后可获取版本号';\n    }\n    if (_flutterVersion != '') {\n      return _flutterVersion;\n    } else {\n      return 'Flutter Attach后可获取版本号';\n    }\n  }\n\n  updateMemoryUsage() {\n    var mainId = VMServiceWrapper.instance.main?.id;\n    if (!VMServiceWrapper.instance.connected || mainId == null) {\n      return;\n    }\n    VMServiceWrapper.instance.service\n        ?.getMemoryUsage(mainId)\n        .then((value) => memoryInfo[VMServiceWrapper.instance.main!] = value);\n  }\n\n  updateFlutterVersion() {\n    if (!VMServiceWrapper.instance.connected) {\n      return;\n    }\n    VMServiceWrapper.instance\n        .callExtensionService('flutterVersion')\n        .then((value) => {\n              if (value != null)\n                {_flutterVersion = FlutterVersion.parse(value.json).version}\n            });\n  }\n\n  updateAllocationProfile() {\n    var mainId = VMServiceWrapper.instance.main?.id;\n    if (!VMServiceWrapper.instance.connected || mainId == null) {\n      return;\n    }\n    VMServiceWrapper.instance.service\n        ?.getAllocationProfile(mainId)\n        .then((value) => allocationProfile = value);\n  }\n\n  testPrintScript() async {\n    // Script script = await compute(getScriptList, 'main.dart');\n    Script? script = await getScriptList('main.dart');\n    print(script?.source ?? \"null\");\n  }\n}\n\nFuture<Script?> getScriptList(String fileName) async {\n  if (!VMServiceWrapper.instance.connected) {\n    await VMServiceWrapper.instance.connect();\n  }\n  var mainId = VMServiceWrapper.instance.main?.id;\n  if (VMServiceWrapper.instance.connected && mainId != null) {\n    return VMServiceWrapper.instance.service\n        ?.getScripts(mainId)\n        .then<Script>((scriptList) async {\n      String? id = scriptList.scripts\n          ?.firstWhere((element) => element.id?.contains(fileName) == true)\n          .id;\n      if (id == null) {\n        return Future.value(null);\n      }\n      return await VMServiceWrapper.instance.service?.getObject(mainId, id)\n          as Script;\n    });\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "Flutter/lib/kit/apm/vm/vm_service_wrapper.dart",
    "content": "import 'dart:developer';\n\nimport 'package:dokit/kit/apm/vm/version.dart';\nimport 'package:vm_service/utils.dart';\nimport 'package:vm_service/vm_service.dart';\nimport 'package:vm_service/vm_service_io.dart';\n\nclass VMServiceWrapper {\n  VMServiceWrapper._privateConstructor();\n\n  static final VMServiceWrapper _instance =\n      VMServiceWrapper._privateConstructor();\n\n  static VMServiceWrapper get instance => _instance;\n\n  VmService? service;\n\n  IsolateRef? main;\n\n  VM? vm;\n\n  ExtensionService? _extensionService;\n  bool connected = false;\n\n  Future<VmService> getService(info) async {\n    Uri uri = convertToWebSocketUrl(serviceProtocolUrl: info.serverUri);\n    return await vmServiceConnectUri(uri.toString(), log: StdoutLog());\n  }\n\n  Future<void> connect() async {\n    ServiceProtocolInfo info = await Service.getInfo();\n    if (info.serverUri == null) {\n      print(\"service  protocol url is null,start vm service fail\");\n      return;\n    }\n    service = await getService(info);\n    print('socket connected in service $info');\n    vm = await service?.getVM();\n    List<IsolateRef>? isolates = vm?.isolates;\n    main = isolates?.firstWhere((ref) => ref.name?.contains('main') == true);\n    main ??= isolates?.first;\n    connected = true;\n  }\n\n  Future<Response?> callExtensionService(String method) async {\n    if (_extensionService == null && service != null && main != null) {\n      _extensionService = ExtensionService(service!, main!);\n      await _extensionService?.loadExtensionService();\n    }\n    return _extensionService!.callMethod(method);\n  }\n\n  gc() {\n    var isolateId = main?.id;\n    if (connected && isolateId != null) {\n      service?.getAllocationProfile(isolateId, gc: true);\n    }\n  }\n\n  disConnect() async {\n    if (service != null) {\n      print('waiting for client to shut down...');\n      await service?.dispose();\n\n      await service?.onDone;\n      connected = false;\n      service = null;\n      print('service client shut down');\n    }\n  }\n}\n\nclass ExtensionService {\n  final VmService serviceClient;\n  final IsolateRef main;\n  Version? _protocolVersion;\n  Version? _dartIoVersion;\n\n  Map<String, List<String>> get registeredMethodsForService =>\n      _registeredMethodsForService;\n  final Map<String, List<String>> _registeredMethodsForService = {};\n\n  ExtensionService(this.serviceClient, this.main);\n\n  // 获取flutter版本，目前比较鸡肋，需要借助devtools向vmservice注册的服务来获取,flutter 未 attach的情况下无法使用。\n  Future<void> loadExtensionService() async {\n    final serviceStreamName = await this.serviceStreamName;\n    serviceClient.onEvent(serviceStreamName).listen(handleServiceEvent);\n    final streamIds = [\n      EventStreams.kDebug,\n      EventStreams.kExtension,\n      EventStreams.kGC,\n      EventStreams.kIsolate,\n      EventStreams.kLogging,\n      EventStreams.kStderr,\n      EventStreams.kStdout,\n      EventStreams.kTimeline,\n      EventStreams.kVM,\n      serviceStreamName,\n    ];\n\n    await Future.wait(streamIds.map((String id) async {\n      try {\n        await serviceClient.streamListen(id);\n      } catch (e) {\n        print(e);\n      }\n    }));\n  }\n\n  Future<String> get serviceStreamName async =>\n      (await isProtocolVersionSupported(\n              supportedVersion: SemanticVersion(major: 3, minor: 22)))\n          ? 'Service'\n          : '_Service';\n\n  Future<bool> isProtocolVersionSupported({\n    required SemanticVersion supportedVersion,\n  }) async {\n    _protocolVersion ??= await serviceClient.getVersion();\n    return isProtocolVersionSupportedNow(supportedVersion: supportedVersion);\n  }\n\n  bool isProtocolVersionSupportedNow({\n    required SemanticVersion supportedVersion,\n  }) {\n    if (_protocolVersion == null) {\n      return false;\n    }\n    return _versionSupported(\n      version: _protocolVersion!,\n      supportedVersion: supportedVersion,\n    );\n  }\n\n  bool _versionSupported({\n    required Version version,\n    required SemanticVersion supportedVersion,\n  }) {\n    return SemanticVersion(\n      major: version.major ?? 0,\n      minor: version.minor ?? 0,\n    ).isSupported(supportedVersion: supportedVersion);\n  }\n\n  Future<bool> isDartIoVersionSupported({\n    required SemanticVersion supportedVersion,\n    required String isolateId,\n  }) async {\n    _dartIoVersion ??= await getDartIOVersion(isolateId);\n    return _versionSupported(\n      version: _dartIoVersion!,\n      supportedVersion: supportedVersion,\n    );\n  }\n\n  Future<Version> getDartIOVersion(String isolateId) =>\n      serviceClient.getDartIOVersion(isolateId);\n\n  void handleServiceEvent(Event e) {\n    if (e.kind == EventKind.kServiceRegistered && e.method != null) {\n      final serviceName = e.service ?? '';\n      _registeredMethodsForService\n          .putIfAbsent(serviceName, () => [])\n          .add(e.method!);\n    }\n\n    if (e.kind == EventKind.kServiceUnregistered) {\n      final serviceName = e.service;\n      _registeredMethodsForService.remove(serviceName);\n    }\n  }\n\n  Future<Response?> callMethod(String method) {\n    if (registeredMethodsForService.containsKey(method)) {\n      return (serviceClient.callMethod(\n          registeredMethodsForService[method]!.last,\n          isolateId: main.id));\n    }\n    return Future.value(null);\n  }\n}\n\nclass StdoutLog extends Log {\n  void warning(String message) => print(message);\n\n  void severe(String message) => print(message);\n}\n"
  },
  {
    "path": "Flutter/lib/kit/biz/biz.dart",
    "content": "// ignore_for_file: unnecessary_null_comparison\n\nimport 'dart:collection';\nimport 'package:dokit/ui/resident_page.dart';\nimport 'package:flutter/material.dart';\n\nimport '../kit.dart';\n\ntypedef KitPageBuilder = Widget Function();\n\nclass BizKitManager {\n  BizKitManager._();\n\n  static final BizKitManager _instance = BizKitManager._();\n\n  static BizKitManager get instance => _instance;\n\n  /// 存储BizKit分组的映射数据\n  final Map<String, List<BizKit>> _kitGroupMap = <String, List<BizKit>>{};\n\n  /// 存储分组的提示信息\n  final Map<String, String> _kitGroupTips = <String, String>{};\n\n  void _addBizKit2Group(String groupName, String kitName, String? icon,\n      String? desc, KitPageBuilder? kitBuilder, String? key, Function? action) {\n    _registerBizKitGroup(groupName, desc);\n\n    final kit = BizKit(icon, kitName, groupName, desc, kitBuilder, key, action);\n    final kitList = _kitGroupMap[groupName];\n    kitList!.add(kit);\n  }\n\n  void _registerBizKitGroup(String groupName, String? tip) {\n    final kitList = <BizKit>[];\n    _kitGroupMap.putIfAbsent(groupName, () => kitList);\n    _addBizKitGroupTip(groupName, tip);\n  }\n\n  /// Getter\n  Map<String, List<BizKit>> get kitGroupMap => _kitGroupMap;\n\n  Map<String, String> get kitGroupTips => _kitGroupTips;\n\n  /// 构建BizKit key值和BizKit对象间的映射关系\n  Map<String, BizKit> get kitMap {\n    final allKitList = kitGroupMap.values.expand((e) => e).toList();\n    final _kitMap = <String, BizKit>{};\n    allKitList.forEach((e) {\n      _kitMap.putIfAbsent(e.key, () => e);\n    });\n\n    return UnmodifiableMapView(_kitMap);\n  }\n\n  /// 获取分组键值列表\n  List<String> groupKeys() => BizKitManager.instance.kitGroupMap.keys.toList();\n\n  /// 获取分组个数\n  int get groupCounts => kitGroupMap.length;\n\n  /// 添加group的tip信息，不可在添加group前操作\n  void _addBizKitGroupTip(String name, String? tip) {\n    assert(name != null);\n    final groupExist = kitGroupMap.keys.contains(name);\n    assert(groupExist,\n        'Can not add [$name] group tip before adding [$name] group.');\n\n    if (tip == null) {\n      _kitGroupTips.remove(name);\n      return;\n    }\n    _kitGroupTips[name] = tip;\n  }\n\n  /// 根据key获取BizKit\n  T? getKit<T extends BizKit>(String key) {\n    if (kitMap.containsKey(key)) {\n      return kitMap[key] as T;\n    }\n    return null;\n  }\n\n  /// 创建BizKit对象\n  T createBizKit<T extends BizKit>(\n      {String? key,\n      required String name,\n      String? icon,\n      required String group,\n      String? desc,\n      KitPageBuilder? kitBuilder,\n      Function? action}) {\n    assert(name != null && group != null);\n\n    final kit = BizKit(icon, name, group, desc, kitBuilder, key, action);\n    return kit as T;\n  }\n\n  /// 更新group信息，详见[_addKitGroupTip]\n  /// [group] kit归属的组\n  /// [desc] kit的描述信息\n  void updateBizKitGroupTip(String group, String desc) {\n    _addBizKitGroupTip(group, desc);\n  }\n\n  /// 向group中添加一组BizKit\n  /// [group] kit归属的组\n  /// [desc] kit的描述信息\n  /// [bizKits] 一组bizKit集合\n  void addBizKits(List<BizKit> bizKits) {\n    if (bizKits.isEmpty) {\n      return;\n    }\n    \n    var group = bizKits.first.group;\n    var desc = bizKits.first.desc;\n    _registerBizKitGroup(group, desc);\n    final kitList = _kitGroupMap[group];\n    kitList!.addAll(bizKits);\n  }\n\n  /// 详见[buildBizKit]\n  void addBizKit<S extends BizKit>(String? key, S kit) {\n    BizKit ikit = kit;\n    buildBizKit(\n        key: key,\n        name: ikit.name,\n        group: ikit.group,\n        icon: ikit.icon,\n        desc: ikit.desc,\n        kitBuilder: ikit.kitPageBuilder,\n        action: ikit.action());\n  }\n\n  /// [key] kit的唯一标识，全局不可重复，不传则默认使用[BizKit._defaultKey];\n  /// [name] kit显示的名字;\n  /// [icon] kit的显示的图标，不传则使用默认图标;\n  /// [group] kit归属的组，如果该组不存在，则会自动创建;\n  /// [desc] kit的描述信息，不会以任何形式显示出来;\n  /// [kitBuilder] kit对应的页面的WidgetBuilder，点击该kit的图标后跳转到的Widget页面.\n  /// [action] 点击该kit的图标后响应事件, 用于不需要跳转widget页面的情况.\n  void buildBizKit(\n      {String? key,\n      required String name,\n      String? icon,\n      required String group,\n      String? desc,\n      KitPageBuilder? kitBuilder,\n      Function? action}) {\n    assert(name != null && group != null);\n\n    if (!_kitGroupMap.containsKey(group)) {\n      _addBizKit2Group(group, name, icon, desc, kitBuilder, key, action);\n    } else {\n      final keyList = kitMap.keys;\n      final kit = BizKit(icon, name, group, desc, kitBuilder, key, action);\n      final exist = keyList.contains(kit.key);\n      assert(!exist, 'The ${kit.toString()} kit already exists.');\n      _kitGroupMap[group]!.add(kit);\n    }\n  }\n}\n\nclass BizKit extends IKit {\n  final String _name;\n  final String _group;\n\n  final String? _key;\n  final String? _icon;\n  final String? _desc;\n  final KitPageBuilder? _kitBuilder;\n  final Function? _action;\n\n  /// [_name]不能为空\n  /// [_group]不能为空\n  BizKit(this._icon, this._name, this._group, this._desc, this._kitBuilder,\n      this._key, this._action)\n      : assert(_name != null);\n\n  String get name => _name;\n  String get key => _key ?? _defaultKey;\n  String get group => _group;\n  String? get desc => _desc ?? '';\n  String? get icon => _icon ?? getIcon();\n  Function? action() => _action;\n\n  String get _defaultKey =>\n      '$group' + '_' + '$name' + '_' + '$icon' + '_' + '$desc';\n\n  KitPageBuilder? get kitPageBuilder => _kitBuilder;\n\n  @override\n  String toString() {\n    final descToString = _desc == null ? 'null' : '\"$_desc\"';\n    return '[group:\"$group\", name:\"$_name\", icon:\"$_icon\", desc:$descToString]';\n  }\n\n  @override\n  String getIcon() {\n    return 'images/dk_frame_hist.png';\n  }\n\n  @override\n  String getKitName() {\n    return _name;\n  }\n\n  @override\n  void tabAction() {\n    if (kitPageBuilder != null) {\n      ResidentPage.residentPageKey.currentState?.setState(() {\n        ResidentPage.tag = key;\n      });\n    }\n\n    if (_action != null) {\n      _action!();\n    }\n  }\n\n  /// 返回BizKit 绑定的widget页面\n  /// 如有的话，否则返回null\n  Widget? displayPage() {\n    if (kitPageBuilder == null) {\n      return null;\n    }\n    final kitPage = kitPageBuilder!();\n    return kitPage;\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/common/basic_info.dart",
    "content": "import 'package:dokit/kit/apm/vm/vm_helper.dart';\nimport 'package:dokit/kit/apm/vm/vm_service_wrapper.dart';\nimport 'package:dokit/kit/common/common.dart';\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/src/widgets/framework.dart';\nimport 'package:vm_service/vm_service.dart';\n\nclass BasicInfoKit extends CommonKit {\n  @override\n  String getIcon() {\n    return 'images/dk_sys_info.png';\n  }\n\n  @override\n  String getKitName() {\n    return CommonKitName.KIT_BASIC_INFO;\n  }\n\n  @override\n  Widget createDisplayPage() {\n    return BasicInfoPage();\n  }\n}\n\nclass BasicInfoPage extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return SingleChildScrollView(\n        child: Container(\n            padding: EdgeInsets.only(left: 16, right: 16),\n            color: Color(0xffffff),\n            child: Column(\n              children: buildAppInfo(),\n            )));\n  }\n\n  List<Widget> buildAppInfo() {\n    List<Widget> list = [];\n    list.add(Container(\n        height: 56,\n        alignment: Alignment.centerLeft,\n        child: Text('VM信息 ' + (!kReleaseMode ? '' : '[release模式下不可用]'),\n            style: TextStyle(fontSize: 14, color: Color(0xff999999)))));\n    list.add(InfoItem('CPU', VmHelper.instance.vm?.hostCPU));\n    list.add(Divider(height: 0.5, color: Color(0xffeeeeee)));\n    list.add(InfoItem('Dart虚拟机', VmHelper.instance.vm?.version));\n    list.add(Divider(height: 0.5, color: Color(0xffeeeeee)));\n    list.add(InfoItem('Flutter版本', VmHelper.instance.flutterVersion));\n    list.add(Divider(height: 0.5, color: Color(0xffeeeeee)));\n    list.add(IsolateItem());\n    list.add(Divider(height: 0.5, color: Color(0xffeeeeee)));\n\n    list.add(Container(\n        height: 56,\n        alignment: Alignment.centerLeft,\n        child: Text('Package信息',\n            style: TextStyle(fontSize: 14, color: Color(0xff999999)))));\n    list.add(InfoItem('App包名', VmHelper.instance.packageInfo?.packageName));\n    list.add(Divider(height: 0.5, color: Color(0xffeeeeee)));\n    list.add(InfoItem(\n        'Module版本',\n        (VmHelper.instance.packageInfo == null)\n            ? '-'\n            : '${VmHelper.instance.packageInfo?.version}+${VmHelper.instance.packageInfo?.buildNumber}'));\n    list.add(Divider(height: 0.5, color: Color(0xffeeeeee)));\n    return list;\n  }\n}\n\nclass IsolateItem extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() {\n    return _IsolateItemState();\n  }\n}\n\nclass _IsolateItemState extends State<IsolateItem> {\n  VM? vm;\n\n  @override\n  void initState() {\n    super.initState();\n\n    VMServiceWrapper.instance.service?.getVM().then((value) => setState(() {\n          vm = value;\n        }));\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    String? isolate;\n    int index = 1;\n    vm?.isolates?.forEach((element) {\n      if (isolate == null) {\n        isolate = '[isolate$index]: ${element.name} ${element.type}\\n';\n      } else {\n        isolate =\n            isolate! + '[isolate$index]: ${element.name} ${element.type}\\n';\n      }\n      index++;\n    });\n    if (isolate != null && (isolate?.length ?? 0) > 1) {\n      isolate = isolate!.substring(0, isolate!.length - 1);\n    }\n    isolate ??= '-';\n    return InfoItem('Isolates', isolate);\n  }\n}\n\nclass InfoItem extends StatelessWidget {\n  final String label;\n  final String? text;\n\n  InfoItem(this.label, this.text);\n\n  @override\n  Widget build(BuildContext context) {\n    return Container(\n        padding: EdgeInsets.only(top: 14, bottom: 14),\n        child: Row(\n          mainAxisAlignment: MainAxisAlignment.spaceBetween,\n          crossAxisAlignment: CrossAxisAlignment.start,\n          children: <Widget>[\n            Text(\n              label,\n              style: TextStyle(\n                fontSize: 16,\n                color: Color(0xff333333),\n              ),\n            ),\n            Expanded(\n              child: Container(\n                  margin: EdgeInsets.only(left: 10),\n                  child: Text(\n                    text ?? '-',\n                    textAlign: TextAlign.end,\n                    style: TextStyle(\n                      fontSize: 16,\n                      color: Color(0xff666666),\n                    ),\n                  )),\n            )\n          ],\n        ));\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/common/common.dart",
    "content": "// 视觉功能\nimport 'package:dokit/kit/common/basic_info.dart';\nimport 'package:dokit/kit/kit.dart';\nimport 'package:dokit/ui/resident_page.dart';\nimport 'package:flutter/material.dart';\n\nabstract class CommonKit implements IKit {\n  @override\n  void tabAction() {\n    // ignore: invalid_use_of_protected_member\n    ResidentPage.residentPageKey.currentState?.setState(() {\n      ResidentPage.tag = getKitName();\n    });\n  }\n\n  Widget createDisplayPage();\n}\n\nclass CommonKitManager {\n  Map<String, CommonKit> kitMap = {\n    CommonKitName.KIT_BASIC_INFO: BasicInfoKit(),\n  };\n\n  CommonKitManager._privateConstructor();\n\n  static final CommonKitManager _instance =\n      CommonKitManager._privateConstructor();\n\n  static CommonKitManager get instance => _instance;\n\n  // 如果想要自定义实现，可以用这个方式进行覆盖。后续扩展入口\n  void addKit(String tag, CommonKit kit) {\n    kitMap[tag] = kit;\n  }\n\n  T? getKit<T extends CommonKit?>(String name) {\n    if (kitMap.containsKey(name)) {\n      return kitMap[name] as T;\n    }\n    return null;\n  }\n}\n\nclass CommonKitName {\n  static const String KIT_BASIC_INFO = '基本信息';\n}\n"
  },
  {
    "path": "Flutter/lib/kit/kit.dart",
    "content": "// Copyright© Dokit for Flutter.\n//\n// kit.dart\n// Flutter\n//\n// Created by linusflow on 2021/3/05\n// Modified by linusflow on 2021/5/11 下午7:40\n//\n\nimport 'dart:collection';\n\nabstract class IInfo {\n  dynamic getValue();\n}\n\nabstract class IStorage {\n  bool save(IInfo info);\n\n  bool contains(IInfo info);\n\n  List<IInfo> getAll();\n\n  void clear();\n}\n\nabstract class IKit {\n  String getKitName();\n\n  String getIcon();\n\n  void tabAction();\n}\n\nclass CommonStorage implements IStorage {\n  final int maxCount;\n  Queue<IInfo> items = Queue();\n\n  CommonStorage({this.maxCount = 100});\n\n  @override\n  List<IInfo> getAll() {\n    return items.toList();\n  }\n\n  @override\n  bool save(IInfo info) {\n    if (items.length >= maxCount) {\n      items.removeFirst();\n    }\n    items.add(info);\n    return true;\n  }\n\n  @override\n  bool contains(IInfo info) {\n    return items.contains(info);\n  }\n\n  @override\n  void clear() {\n    return items.clear();\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/observer.dart",
    "content": "import 'package:dokit/kit/apm/launch/route_observer.dart';\nimport 'package:flutter/material.dart';\n\n/// Dokit对外的路由观察者\n/// 向内转发各个路由事件\nclass DokitNavigatorObserver extends NavigatorObserver {\n  List<NavigatorObserver> _observers = [LaunchObserver()];\n\n\n  @override\n  void didPush(Route route, Route? previousRoute) {\n    super.didPush(route, previousRoute);\n    _observers.forEach((element) {\n      element.didPush(route, previousRoute);\n    });\n  }\n\n  @override\n  void didPop(Route route, Route? previousRoute) {\n    super.didPop(route, previousRoute);\n    _observers.forEach((element) {\n      element.didPop(route, previousRoute);\n    });\n  }\n\n  @override\n  void didRemove(Route route, Route? previousRoute) {\n    super.didRemove(route, previousRoute);\n    _observers.forEach((element) {\n      element.didRemove(route, previousRoute);\n    });\n  }\n\n  @override\n  void didReplace({Route? newRoute, Route? oldRoute}) {\n    super.didReplace(newRoute: newRoute, oldRoute: oldRoute);\n    _observers.forEach((element) {\n      element.didReplace(newRoute: newRoute, oldRoute: oldRoute);\n    });\n  }\n\n  @override\n  void didStartUserGesture(Route route, Route? previousRoute) {\n    super.didStartUserGesture(route, previousRoute);\n    _observers.forEach((element) {\n      element.didStartUserGesture(route, previousRoute);\n    });\n  }\n\n  @override\n  void didStopUserGesture() {\n    super.didStopUserGesture();\n    _observers.forEach((element) {\n      element.didStopUserGesture();\n    });\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/visual/color_pick.dart",
    "content": "// Copyright© Dokit for Flutter.\n//\n// color_pick.dart\n// Flutter\n//\n// Created by linusflow on 2021/5/12\n// Modified by linusflow on 2021/5/12 下午2:28\n//\n\nimport 'dart:typed_data';\nimport 'dart:ui' as ui;\n\nimport 'package:dokit/dokit.dart';\nimport 'package:dokit/kit/visual/visual.dart';\nimport 'package:dokit/ui/dokit_btn.dart';\nimport 'package:dokit/util/screen_util.dart';\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/rendering.dart';\nimport 'package:flutter/services.dart';\nimport 'package:image/image.dart' as images;\n\n/// 无法获取到overlay的截图信息，如悬浮窗\nclass ColorPickerKit extends VisualKit {\n  ColorPickerKit._privateConstructor() {\n    _initPosition =\n        ScreenUtil.instance.screenCenter - Offset(diameter / 2, diameter / 2);\n    _position = ValueNotifier<Offset>(initPosition);\n    _focusEntry = OverlayEntry(builder: (BuildContext context) {\n      return ColorPickerWidget();\n    });\n    _infoEntry = OverlayEntry(builder: (BuildContext context) {\n      return ColorPickerInfoWidget();\n    });\n    isShown = false;\n  }\n  static final ColorPickerKit _instance = ColorPickerKit._privateConstructor();\n  static ColorPickerKit get instance => _instance;\n\n  late bool isShown;\n  // 选中的颜色\n  final ValueNotifier<Color> color = ValueNotifier<Color>(Colors.white);\n\n  // 当前屏幕的截图快照\n  final ValueNotifier<ui.Image?> snapshot = ValueNotifier<ui.Image?>(null);\n\n  // 放大镜当前位置（左上角）\n  late ValueNotifier<Offset> _position;\n  ValueNotifier<Offset> get position => _position;\n\n  // 放大镜的直径\n  final double diameter = 170;\n  // 像素点放大的倍数\n  final double scale = 8;\n  late Offset _initPosition;\n  Offset get initPosition => _initPosition;\n\n  late OverlayEntry _focusEntry;\n  late OverlayEntry _infoEntry;\n\n  @override\n  String getIcon() {\n    return 'images/dk_color_pick.png';\n  }\n\n  @override\n  String getKitName() {\n    return VisualKitName.KIT_COLOR_PICK;\n  }\n\n  @override\n  void tabAction() {\n    final DoKitBtnState? state = DoKitBtn.doKitBtnKey.currentState;\n    state?.closeDebugPage();\n    show(DoKitBtn.doKitBtnKey.currentContext, state?.owner);\n  }\n\n  static void show(BuildContext? context, OverlayEntry? entrance) {\n    _instance._show(context, entrance);\n  }\n\n  void _show(BuildContext? context, OverlayEntry? entrance) {\n    if (isShown) {\n      return;\n    }\n    isShown = true;\n    doKitOverlayKey.currentState?.insert(_focusEntry, below: entrance);\n    doKitOverlayKey.currentState?.insert(_infoEntry, below: _focusEntry);\n  }\n\n  static bool hide(BuildContext context) {\n    return _instance._hide(context);\n  }\n\n  bool _hide(BuildContext context) {\n    if (!isShown) {\n      return false;\n    }\n    isShown = false;\n    _focusEntry.remove();\n    _infoEntry.remove();\n    return true;\n  }\n}\n\nclass ColorPickerWidget extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() => ColorPickerWidgetState();\n}\n\nclass ColorPickerWidgetState extends State<ColorPickerWidget> {\n  // 当前页面的截图快照\n  images.Image? _image;\n  Future<images.Image?> get image async {\n    if (_image != null) {\n      return _image!;\n    }\n    await _updateImage();\n\n    return _image;\n  }\n\n  // 是否做好显示颜色拾取器的准备\n  bool _ready = false;\n\n  Uint8List? _imageUint8List;\n  Future<Uint8List?> get imageUint8List async {\n    if (_imageUint8List != null) {\n      return _imageUint8List!;\n    }\n\n    final RenderRepaintBoundary? boundary =\n        _findCurrentPageRepaintBoundaryRenderObject();\n    final Uint8List? imageData = await _boundaryToImageUint8List(boundary);\n    _imageUint8List = imageData;\n\n    return _imageUint8List;\n  }\n\n  // 放大镜左上角的位置\n  Offset get position => ColorPickerKit.instance.position.value;\n  set position(Offset point) => ColorPickerKit.instance.position.value = point;\n\n  // 手指和屏幕接触的初始位置\n  Offset? _touchPoint;\n  // 手指和屏幕接触时，放大镜左上角的位置\n  Offset _lastPosition = ColorPickerKit.instance.position.value;\n  Offset get deltaOffset {\n    if (_touchPoint == null) {\n      return Offset.zero;\n    }\n    return _touchPoint! - _lastPosition;\n  }\n\n  // 放大镜的半径\n  double get _radius => ColorPickerKit.instance.diameter / 2;\n\n  @override\n  initState() {\n    super.initState();\n    WidgetsBinding.instance?.addPostFrameCallback((_) {\n      // 第一次内部会调用_updateImage方法\n      _updateColor();\n    });\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Positioned(\n      top: position.dy,\n      left: position.dx,\n      child: Listener(\n        onPointerDown: (PointerDownEvent event) {\n          _touchPoint = event.position;\n          _lastPosition = position;\n        },\n        onPointerMove: (PointerMoveEvent event) {\n          position = event.position - deltaOffset;\n          _updateColor();\n        },\n        child: Draggable(\n          child: _buildMagnifier(context),\n          feedback: _buildMagnifier(context),\n          childWhenDragging: Container(),\n          onDragStarted: () {\n            // 开始拖动时，重新获取截图快照\n            _updateImage();\n          },\n          onDragEnd: (DraggableDetails detail) {\n            final Offset point = detail.offset;\n            double x = point.dx;\n            double y = point.dy;\n\n            final width = ScreenUtil.instance.screenWidth;\n            final height = ScreenUtil.instance.screenHeight;\n\n            bool refresh = false;\n            if (x < -_radius) {\n              x = -_radius;\n              refresh = true;\n            }\n            if (x > width - _radius) {\n              x = width - _radius;\n              refresh = true;\n            }\n            if (y < -_radius) {\n              y = -_radius;\n              refresh = true;\n            }\n            if (y > height - _radius) {\n              y = height - _radius;\n              refresh = true;\n            }\n\n            if (refresh) {\n              setState(() {\n                position = Offset(x, y);\n                _updateColor();\n              });\n            }\n          },\n        ),\n      ),\n    );\n  }\n\n  Widget _buildMagnifier(BuildContext context) {\n    return Offstage(\n      offstage: !_ready,\n      child: RepaintBoundary(\n        child: ClipOval(\n          child: Stack(\n            alignment: Alignment.center,\n            children: [\n              ValueListenableBuilder<Offset>(\n                valueListenable: ColorPickerKit.instance.position,\n                builder: (BuildContext context, Offset value, Widget? child) {\n                  if (ColorPickerKit.instance.snapshot.value == null) {\n                    return Container();\n                  }\n                  return ValueListenableBuilder<ui.Image?>(\n                    valueListenable: ColorPickerKit.instance.snapshot,\n                    builder:\n                        (BuildContext context, ui.Image? value, Widget? child) {\n                      return CustomPaint(\n                        painter: GridsPainter(),\n                        size: Size.fromRadius(_radius),\n                      );\n                    },\n                  );\n                },\n              ),\n              CustomPaint(\n                painter: MagnifierPainter(),\n                size: Size.fromRadius(_radius),\n              ),\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n\n  Future<void> _updateImage() async {\n    _imageUint8List = null;\n    final imageData = await imageUint8List;\n    if (imageData == null) {\n      return;\n    }\n    _image = images.decodeImage(imageData);\n    final codec = await ui.instantiateImageCodec(imageData);\n    final info = await codec.getNextFrame();\n    ColorPickerKit.instance.snapshot.value = info.image;\n    if (!_ready && mounted) {\n      // 第一次获取Image会有耗时\n      // 保证MagnifierPainter和GridsPainter同步出现\n      setState(() {\n        _ready = true;\n      });\n    }\n  }\n\n  Future<void> _updateColor() async {\n    final double dpr = ui.window.devicePixelRatio;\n    final center = (position + Offset(_radius, _radius)) * dpr;\n    final images.Image? image = await this.image;\n    final abgrPixel = image?.getPixelSafe(center.dx.toInt(), center.dy.toInt());\n    if (abgrPixel == null) {\n      return;\n    }\n    final int argbPixel = _abgrToArgb(abgrPixel);\n    ColorPickerKit.instance.color.value = Color(argbPixel);\n  }\n\n  RenderRepaintBoundary? _findCurrentPageRepaintBoundaryRenderObject() {\n    final owner = context.findRenderObject()?.owner;\n    assert(owner != null, '当前正在build，无法获取当前页面的RepaintBoundary！');\n\n    bool isRepaintBoundaryTo_ModalScopeStatus(String? desc) {\n      if (desc?.isEmpty ?? true) {\n        return false;\n      }\n      final creators = desc!.split(' ← ');\n      const sampleCreators = [\n        'RepaintBoundary',\n        '_FocusMarker',\n        'Semantics',\n        'FocusScope',\n        'PrimaryScrollController', // flutter2.0+特有\n        '_ActionsMarker',\n        'Actions',\n        'Builder', // flutter2.0+特有\n        'PageStorage',\n        'Offstage',\n        '_ModalScopeStatus',\n        'UnmanagedRestorationScope', // flutter2.0+特有\n      ];\n      for (int i = 0; i < sampleCreators.length; i++) {\n        if (creators.length < i + 1) {\n          return false;\n        }\n        if (creators[i] != sampleCreators[i]) {\n          return false;\n        }\n      }\n\n      return true;\n    }\n\n    final ModalRoute<dynamic>? rootRoute =\n        ModalRoute.of<dynamic>(DoKitApp.appKey.currentContext!);\n\n    // 当前页面的_ModalScopeStatus下的RepaintBoundary的RenderObject\n    RenderRepaintBoundary? currentPageRepaintBoundary;\n    void filter(Element element) {\n      if (element is RenderObjectElement && element.renderObject is RenderBox) {\n        final ModalRoute<dynamic>? route = ModalRoute.of<dynamic>(element);\n        if (route != null && route != rootRoute) {\n          final RenderBox renderBox = element.renderObject as RenderBox;\n          if (renderBox.hasSize &&\n              renderBox.attached &&\n              renderBox.isRepaintBoundary) {\n            String? desc;\n            if (!kReleaseMode) {\n              desc = element.renderObject.debugCreator.toString();\n            }\n            if (isRepaintBoundaryTo_ModalScopeStatus(desc)) {\n              currentPageRepaintBoundary =\n                  element.renderObject as RenderRepaintBoundary;\n            }\n          }\n        }\n      }\n      element.visitChildren(filter);\n    }\n\n    DoKitApp.appKey.currentContext?.visitChildElements(filter);\n\n    return currentPageRepaintBoundary;\n  }\n\n  Future<Uint8List?> _boundaryToImageUint8List(\n      RenderRepaintBoundary? boundary) async {\n    if (boundary == null) {\n      return null;\n    }\n    final double dpr = ui.window.devicePixelRatio;\n    final ui.Image image = await boundary.toImage(pixelRatio: dpr);\n    final ByteData? byteData =\n        await image.toByteData(format: ui.ImageByteFormat.png);\n    final Uint8List? pngBytes = byteData?.buffer.asUint8List();\n\n    return pngBytes;\n  }\n\n  /// Uint32 编码过的像素颜色值（#AABBGGRR)转为（#AARRGGBB）\n  int _abgrToArgb(int argbColor) {\n    int r = (argbColor >> 16) & 0xFF;\n    int b = argbColor & 0xFF;\n    return (argbColor & 0xFF00FF00) | (b << 16) | r;\n  }\n}\n\nclass GridsPainter extends CustomPainter {\n  GridsPainter();\n\n  double get scale => ColorPickerKit.instance.scale;\n  ui.Image? get image => ColorPickerKit.instance.snapshot.value;\n  Offset get position => ColorPickerKit.instance.position.value;\n  double get radius => ColorPickerKit.instance.diameter / 2;\n\n  // 水平方向上显示多少个颜色格子\n  int get width => radius * 2 ~/ scale;\n\n  // 竖直方向上显示多少个颜色格子\n  int get height => radius * 2 ~/ scale;\n\n  @override\n  void paint(Canvas canvas, Size size) {\n    if (image == null) {\n      return;\n    }\n\n    final paint = Paint();\n    final double dpr = ui.window.devicePixelRatio;\n\n    final center = (position + Offset(radius, radius)) * dpr;\n    final left = center.dx - (width - 1) / 2;\n    final top = center.dy - (height - 1) / 2;\n    final srcRect =\n        Rect.fromLTWH(left, top, width.toDouble(), height.toDouble());\n    final distRect = Rect.fromLTWH(0, 0, size.width, size.height);\n    canvas.drawImageRect(image!, srcRect, distRect, paint);\n  }\n\n  @override\n  bool shouldRepaint(GridsPainter old) => true;\n\n  @override\n  bool hitTest(ui.Offset position) => true;\n}\n\nclass MagnifierPainter extends CustomPainter {\n  MagnifierPainter();\n\n  final double strokeWidth = 1;\n  final Color color = Colors.black;\n  double gridSide = ColorPickerKit.instance.scale;\n\n  @override\n  void paint(Canvas canvas, Size size) {\n    assert(size.height == size.width);\n    _drawCircle(canvas, size);\n    _drawGrid(canvas, size);\n  }\n\n  void _drawCircle(Canvas canvas, Size size) {\n    final Paint paint = Paint()\n      ..style = PaintingStyle.stroke\n      ..strokeWidth = strokeWidth\n      ..color = color;\n    canvas.drawCircle(\n        size.center(const Offset(0, 0)), size.longestSide / 2, paint);\n  }\n\n  void _drawGrid(Canvas canvas, Size size) {\n    final Paint crossPaint = Paint()\n      ..strokeWidth = 1\n      ..color = color;\n\n    final List<Offset> points = [\n      size.center(Offset(-gridSide / 2, -gridSide / 2)),\n      size.center(Offset(gridSide / 2, -gridSide / 2)),\n      size.center(Offset(gridSide / 2, gridSide / 2)),\n      size.center(Offset(-gridSide / 2, gridSide / 2)),\n      size.center(Offset(-gridSide / 2, -gridSide / 2)),\n    ];\n    canvas.drawPoints(ui.PointMode.polygon, points, crossPaint);\n  }\n\n  @override\n  bool shouldRepaint(CustomPainter oldDelegate) {\n    if (ColorPickerKit.instance.scale != gridSide) {\n      gridSide = ColorPickerKit.instance.scale;\n      return true;\n    }\n    return false;\n  }\n\n  @override\n  bool hitTest(ui.Offset position) => true;\n}\n\nclass ColorPickerInfoWidget extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() => _ColorPickerInfoWidgetState();\n}\n\nclass _ColorPickerInfoWidgetState extends State<ColorPickerInfoWidget> {\n  double top = infoWidgetTopMargin;\n  Color? color;\n  String get colorDesc =>\n      '#${color?.value.toRadixString(16).padLeft(8, '0').toUpperCase() ?? ''}';\n\n  @override\n  Widget build(BuildContext context) {\n    return Positioned(\n      left: infoWidgetHorizontalMargin,\n      top: top,\n      child: Draggable(\n          child: _buildInfoView(),\n          feedback: _buildInfoView(),\n          childWhenDragging: Container(),\n          onDragEnd: (DraggableDetails detail) {\n            final Offset offset = detail.offset;\n            setState(() {\n              top = offset.dy;\n              if (top < 0) {\n                top = 0;\n              }\n            });\n          },\n          onDraggableCanceled: (Velocity velocity, Offset offset) {}),\n    );\n  }\n\n  Widget _buildInfoView() {\n    final Size size = MediaQuery.of(context).size;\n    return Container(\n        width: size.width - 40,\n        padding:\n            const EdgeInsets.only(left: 16, right: 16, top: 20, bottom: 20),\n        decoration: BoxDecoration(\n            border: Border.all(color: const Color(0xffeeeeee), width: 0.5),\n            borderRadius: const BorderRadius.all(Radius.circular(4)),\n            color: Colors.white,\n            boxShadow: [\n              BoxShadow(\n                color: Colors.black12,\n                offset: Offset(4, 4),\n                blurRadius: 15,\n                spreadRadius: 1,\n              )\n            ]),\n        alignment: Alignment.centerLeft,\n        child: Row(\n          mainAxisAlignment: MainAxisAlignment.spaceBetween,\n          crossAxisAlignment: CrossAxisAlignment.center,\n          children: <Widget>[\n            ValueListenableBuilder<Color>(\n              valueListenable: ColorPickerKit.instance.color,\n              builder: (BuildContext context, Color value, Widget? child) {\n                color = value;\n                return Row(\n                  crossAxisAlignment: CrossAxisAlignment.center,\n                  children: [\n                    Container(\n                      child: const SizedBox(height: 20, width: 20),\n                      decoration: BoxDecoration(\n                          border: Border.all(\n                              color: const Color(0xffeeeeee), width: 0.5),\n                          borderRadius:\n                              const BorderRadius.all(Radius.circular(4)),\n                          color: color),\n                    ),\n                    const SizedBox(width: 26),\n                    Text(\n                      colorDesc,\n                      style: const TextStyle(\n                        color: Color(0xff333333),\n                        fontSize: 16,\n                      ),\n                    )\n                  ],\n                );\n              },\n            ),\n            GestureDetector(\n              child: Image.asset(\n                'images/dokit_ic_close.png',\n                package: DK_PACKAGE_NAME,\n                height: 22,\n                width: 22,\n              ),\n              onTap: () {\n                ColorPickerKit.hide(context);\n              },\n            )\n          ],\n        ));\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/kit/visual/view_check.dart",
    "content": "// Copyright© Dokit for Flutter.\n//\n// view_check.dart\n// Flutter\n//\n// Created by linjizong on 2021/3/05\n// Modified by linusflow on 2021/5/12 下午2:51\n//\n\nimport 'dart:convert';\nimport 'dart:math';\n\nimport 'package:dokit/dokit.dart';\nimport 'package:dokit/kit/visual/visual.dart';\nimport 'package:dokit/ui/dokit_app.dart';\nimport 'package:dokit/ui/dokit_btn.dart';\nimport 'package:dokit/util/screen_util.dart';\nimport 'package:dokit/widget/dash_decoration.dart';\nimport 'package:dokit/widget/widget_build_chain/widget_build_chain_page.dart';\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/widgets.dart';\n\nclass ViewCheckerKit extends VisualKit {\n  ViewCheckerKit._privateConstructor() {\n    final ViewCheckerWidget widget = ViewCheckerWidget();\n\n    focusEntry = OverlayEntry(builder: (BuildContext context) {\n      return widget;\n    });\n\n    rectEntry = OverlayEntry(builder: (BuildContext context) {\n      return Positioned(\n        left: area.left,\n        top: area.top,\n        child: RectWidget(),\n      );\n    });\n    infoEntry = OverlayEntry(builder: (BuildContext context) {\n      return InfoWidget();\n    });\n    alignEntry = OverlayEntry(builder: (BuildContext context) {\n      return const AlignRulerWidget();\n    });\n    isShown = false;\n  }\n\n  late bool isShown;\n  late OverlayEntry focusEntry;\n  late OverlayEntry rectEntry;\n  late OverlayEntry infoEntry;\n  late OverlayEntry alignEntry;\n\n  Rect area = const Rect.fromLTWH(0, 0, 0, 0);\n  String info = '移动屏幕中心焦点聚焦控件，查看控件信息';\n  RenderObjectElement? selectedElement;\n\n  final ValueNotifier<Offset> viewCheckerWidgetCenterNotifier =\n      ValueNotifier<Offset>(ScreenUtil.instance.screenCenter);\n\n  static final ViewCheckerKit _instance = ViewCheckerKit._privateConstructor();\n\n  static ViewCheckerKit get instance => _instance;\n\n  static void show(BuildContext? context, OverlayEntry? entrance) {\n    _instance._show(context, entrance);\n  }\n\n  void _show(BuildContext? context, OverlayEntry? entrance) {\n    if (isShown) {\n      return;\n    }\n    isShown = true;\n    doKitOverlayKey.currentState?.insert(focusEntry, below: entrance);\n    doKitOverlayKey.currentState?.insert(infoEntry, below: focusEntry);\n    doKitOverlayKey.currentState?.insert(rectEntry, below: infoEntry);\n    doKitOverlayKey.currentState?.insert(alignEntry, below: rectEntry);\n  }\n\n  static bool hide(BuildContext context) {\n    return _instance._hide(context);\n  }\n\n  bool _hide(BuildContext context) {\n    if (!isShown) {\n      return false;\n    }\n    isShown = false;\n    focusEntry.remove();\n    rectEntry.remove();\n    infoEntry.remove();\n    alignEntry.remove();\n    viewCheckerWidgetCenterNotifier.value = ScreenUtil.instance.screenCenter;\n    area = const Rect.fromLTWH(0, 0, 0, 0);\n    info = '移动屏幕中心焦点聚焦控件，查看控件信息';\n    return true;\n  }\n\n  @override\n  String getIcon() {\n    return 'images/dk_view_check.png';\n  }\n\n  @override\n  String getKitName() {\n    return VisualKitName.KIT_VIEW_CHECK;\n  }\n\n  @override\n  void tabAction() {\n    final DoKitBtnState? state = DoKitBtn.doKitBtnKey.currentState;\n    state?.closeDebugPage();\n    show(DoKitBtn.doKitBtnKey.currentContext, state?.owner);\n  }\n}\n\nclass RectWidget extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() {\n    return _RectWidgetState();\n  }\n}\n\nclass _RectWidgetState extends State<RectWidget> {\n  @override\n  Widget build(BuildContext context) {\n    return Container(\n        width: ViewCheckerKit._instance.area.width,\n        height: ViewCheckerKit._instance.area.height,\n        decoration: const DashedDecoration(\n            dashedColor: Color(0xffCC3000),\n            color: Color(0x183ca0e6),\n            borderRadius: BorderRadius.all(Radius.circular(1))));\n  }\n}\n\nclass InfoWidget extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() {\n    return _InfoWidgetState();\n  }\n}\n\nclass _InfoWidgetState extends State<InfoWidget> {\n  late double top;\n  late WidgetBuildChainController controller;\n\n  @override\n  Widget build(BuildContext context) {\n    top = MediaQuery.of(context).size.height - 250;\n    return Positioned(\n      left: 20,\n      top: top,\n      child: Draggable(\n          child: _buildInfoView(),\n          feedback: _buildInfoView(),\n          childWhenDragging: Container(),\n          onDragEnd: (DraggableDetails detail) {\n            final Offset offset = detail.offset;\n            setState(() {\n              top = offset.dy;\n              if (top < 0) {\n                top = 0;\n              }\n            });\n          },\n          onDraggableCanceled: (Velocity velocity, Offset offset) {}),\n    );\n  }\n\n  Widget _buildInfoView() {\n    final Size size = MediaQuery.of(context).size;\n    return Container(\n        width: size.width - 40,\n        padding: const EdgeInsets.only(left: 10, right: 16, top: 9, bottom: 24),\n        decoration: BoxDecoration(\n            border: Border.all(color: const Color(0xffeeeeee), width: 0.5),\n            borderRadius: const BorderRadius.all(Radius.circular(4)),\n            color: Colors.white),\n        alignment: Alignment.centerLeft,\n        child: Column(\n          crossAxisAlignment: CrossAxisAlignment.start,\n          children: <Widget>[\n            Container(\n                alignment: Alignment.centerRight,\n                width: size.width - 40,\n                child: GestureDetector(\n                  child: Image.asset('images/dokit_ic_close.png',\n                      package: DK_PACKAGE_NAME, height: 22, width: 22),\n                  onTap: () {\n                    ViewCheckerKit.hide(context);\n                  },\n                )),\n            Text(\n              ViewCheckerKit._instance.info,\n              style: const TextStyle(\n                  color: Color(0xff333333),\n                  fontFamily: 'PingFang SC',\n                  fontWeight: FontWeight.normal,\n                  decoration: TextDecoration.none,\n                  fontSize: 12),\n            ),\n            Offstage(\n              offstage: ViewCheckerKit._instance.selectedElement == null,\n              child: GestureDetector(\n                onTap: _openWidgetBuildChainPage,\n                child: Padding(\n                  padding: EdgeInsets.only(top: 6),\n                  child: Text(\n                    '查看控件的build链',\n                    style: TextStyle(\n                      color: Colors.blue,\n                      fontSize: 10,\n                      decoration: TextDecoration.underline,\n                    ),\n                  ),\n                ),\n              ),\n            )\n          ],\n        ));\n  }\n\n  void _openWidgetBuildChainPage() {\n    if (ViewCheckerKit._instance.selectedElement == null) {\n      return;\n    }\n    controller =\n        WidgetBuildChainController(ViewCheckerKit._instance.selectedElement!);\n    controller.show();\n  }\n}\n\n// ignore: must_be_immutable\nclass ViewCheckerWidget extends StatefulWidget {\n  ViewCheckerWidget({Key? key, this.diameter = 40}) : super(key: key);\n\n  late OverlayEntry owner;\n  late OverlayEntry debugPage;\n  bool showDebugPage = false;\n\n  final double diameter;\n\n  @override\n  _ViewCheckerWidgetState createState() => _ViewCheckerWidgetState();\n}\n\nclass _ViewCheckerWidgetState extends State<ViewCheckerWidget> {\n  Offset? buttonOffset; // 按钮的初始位置\n  Offset? touchOffset; // 手指和屏幕接触的初始位置\n  Offset get deltaOffset {\n    if (touchOffset == null || buttonOffset == null) {\n      return Offset.zero;\n    }\n    return touchOffset! - buttonOffset!;\n  }\n\n  static const String _dokitViewCheckGroup = 'dokit_view_check-group';\n\n  @override\n  void dispose() {\n    // ignore: invalid_use_of_protected_member\n    WidgetInspectorService.instance.disposeGroup(_dokitViewCheckGroup);\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final Size size = MediaQuery.of(context).size;\n    final double width = size.width;\n    final double height = size.height;\n    buttonOffset ??= ScreenUtil.instance.screenCenter -\n        Offset(widget.diameter / 2, widget.diameter / 2);\n    return Positioned(\n      left: buttonOffset!.dx,\n      top: buttonOffset!.dy,\n      child: Listener(\n        behavior: HitTestBehavior.opaque,\n        onPointerDown: (PointerDownEvent event) {\n          touchOffset = event.position;\n        },\n        onPointerMove: (PointerMoveEvent event) {\n          ViewCheckerKit._instance.viewCheckerWidgetCenterNotifier.value =\n              event.position -\n                  deltaOffset +\n                  Offset(widget.diameter / 2, widget.diameter / 2);\n        },\n        child: Draggable(\n            child: FocusWidget(widget.diameter),\n            feedback: FocusWidget(widget.diameter),\n            childWhenDragging: Container(),\n            onDragEnd: (DraggableDetails detail) {\n              final Offset offset = detail.offset;\n              bool needCorrect = false;\n              setState(() {\n                double x = offset.dx;\n                double y = offset.dy;\n                // 避免在iOS中，按钮移动到statusBar所在区域出现无法再拖动按钮的问题\n                final double statusBarHeight =\n                    ScreenUtil.instance.statusBarHeight;\n                if (x < -widget.diameter / 2) {\n                  x = -widget.diameter / 2;\n                  needCorrect = true;\n                }\n                if (x > width - widget.diameter / 2) {\n                  x = width - widget.diameter / 2;\n                  needCorrect = true;\n                }\n                if (y < statusBarHeight - widget.diameter / 2) {\n                  y = statusBarHeight - widget.diameter / 2;\n                  needCorrect = true;\n                }\n                if (y > height - widget.diameter / 2) {\n                  y = height - widget.diameter / 2;\n                  needCorrect = true;\n                }\n                buttonOffset = Offset(x, y);\n                if (needCorrect) {\n                  ViewCheckerKit\n                          ._instance.viewCheckerWidgetCenterNotifier.value =\n                      buttonOffset! +\n                          Offset(widget.diameter / 2, widget.diameter / 2);\n                }\n                findFocusView();\n              });\n            },\n            onDraggableCanceled: (Velocity velocity, Offset offset) {}),\n      ),\n    );\n  }\n\n  // 找到当前聚焦控件的方式：\n  // 1.从根节点开始向下遍历，找到当前处于顶部的控件，记录这个控件的路由信息，作为当前页面路由\n  // 2.遍历过程中，记录所有和焦点悬浮窗有交集控件。\n  // 3.遍历上面记录的控件，获取控件所在路由信息和当前页面路由一致的控件，再计算(焦点悬浮窗和在控件重叠区)/(组件面积)，占比更高者作为当前聚焦组件\n  // 4.debug模式下，将聚焦控件设置为选中控件，可以获取到源码信息\n  void findFocusView() {\n    final RenderObjectElement? element = resolveTree();\n    ViewCheckerKit._instance.selectedElement = element;\n\n    if (element != null && element.size != null) {\n      final Offset offset =\n          (element.renderObject as RenderBox).localToGlobal(Offset.zero);\n      ViewCheckerKit._instance.area = Rect.fromLTWH(\n          offset.dx, offset.dy, element.size!.width, element.size!.height);\n      ViewCheckerKit._instance.info = toInfoString(element);\n    } else {\n      ViewCheckerKit._instance.area = const Rect.fromLTWH(0, 0, 0, 0);\n    }\n  }\n\n  String toInfoString(RenderObjectElement element) {\n    String? fileLocation;\n    int? line;\n    int? column;\n    if (kDebugMode) {\n      WidgetInspectorService.instance.selection.current = element.renderObject;\n      final id = WidgetInspectorService.instance\n          // ignore: invalid_use_of_protected_member\n          .toId(element.renderObject.toDiagnosticsNode(), _dokitViewCheckGroup);\n      if (id == null) {\n        return '';\n      }\n      final String? nodeDesc = WidgetInspectorService.instance\n          .getSelectedSummaryWidget(id, _dokitViewCheckGroup);\n\n      if (nodeDesc != null) {\n        final Map<String, dynamic>? map =\n            json.decode(nodeDesc) as Map<String, dynamic>;\n        if (map != null) {\n          final Map<String, dynamic>? location =\n              map['creationLocation'] as Map<String, dynamic>;\n          if (location != null) {\n            fileLocation = location['file'] as String;\n            line = location['line'] as int;\n            column = location['column'] as int;\n          }\n        }\n      }\n    }\n\n    final Offset offset =\n        (element.renderObject as RenderBox).localToGlobal(Offset.zero);\n    String info = '控件名称: ${element.widget.toString()}\\n'\n        '控件位置: 左${offset.dx} 上${offset.dy} 宽${element.size!.width} 高${element.size!.height}';\n    if (fileLocation != null) {\n      info += '\\n源码位置: $fileLocation' '【行 ${line!} 列 ${column!}】';\n    }\n    return info;\n  }\n\n  RenderObjectElement? resolveTree() {\n    Element? currentPage;\n    final List<RenderObjectElement> inBounds = <RenderObjectElement>[];\n    final Rect focus = Rect.fromLTWH(\n        buttonOffset!.dx, buttonOffset!.dy, widget.diameter, widget.diameter);\n    // 记录根路由，用以过滤overlay\n    final ModalRoute<dynamic>? rootRoute =\n        ModalRoute.of<dynamic>(DoKitApp.appKey.currentContext!);\n\n    void filter(Element element) {\n      // 兼容IOS，IOS的MaterialApp会在Navigator后再插入一个PositionedDirectional控件，用以处理右滑关闭手势，遍历的时候跳过该控件\n      // ignore:\n      if (element.widget is! PositionedDirectional) {\n        if (element is RenderObjectElement &&\n            element.renderObject is RenderBox) {\n          final ModalRoute<dynamic>? route = ModalRoute.of<dynamic>(element);\n          // overlay不包含route信息，通过ModalRoute.of会获取到当前所在materialapp在其父节点内的route,此处对overlay做过滤。只能过滤掉直接添加在根MaterialApp的overlay,\n          // 并且该overlay的子widget不能包含materialApp或navigator\n          if (route != null && route != rootRoute) {\n            currentPage = element;\n            final RenderBox renderBox = element.renderObject as RenderBox;\n            if (renderBox.hasSize && renderBox.attached) {\n              final Offset offset = renderBox.localToGlobal(Offset.zero);\n              if (isOverlap(\n                  focus,\n                  Rect.fromLTWH(offset.dx, offset.dy, element.size!.width,\n                      element.size!.height))) {\n                inBounds.add(element);\n              }\n            }\n          }\n        }\n        element.visitChildren(filter);\n      }\n    }\n\n    DoKitApp.appKey.currentContext?.visitChildElements(filter);\n    RenderObjectElement? topElement;\n    final ModalRoute<dynamic>? route =\n        currentPage != null ? ModalRoute.of<dynamic>(currentPage!) : null;\n    for (final RenderObjectElement element in inBounds) {\n      if ((route == null || ModalRoute.of(element) == route) &&\n          checkSelected(topElement, element)) {\n        topElement = element;\n      }\n    }\n    return topElement;\n  }\n\n  bool checkSelected(RenderObjectElement? last, RenderObjectElement current) {\n    if (last == null) {\n      return true;\n    } else {\n      return getOverlayPercent(current) > getOverlayPercent(last) &&\n          current.depth >= last.depth;\n    }\n  }\n\n  double getOverlayPercent(RenderObjectElement element) {\n    if (element.size == null) {\n      return 0;\n    }\n    final double size = element.size!.width * element.size!.height;\n    final Offset offset =\n        (element.renderObject as RenderBox).localToGlobal(Offset.zero);\n    final Rect rect = Rect.fromLTWH(\n        offset.dx, offset.dy, element.size!.width, element.size!.height);\n    final double xc1 = max(rect.left, buttonOffset!.dx);\n    final double yc1 = max(rect.top, buttonOffset!.dy);\n    final double xc2 = min(rect.right, buttonOffset!.dx + widget.diameter);\n    final double yc2 = min(rect.bottom, buttonOffset!.dy + widget.diameter);\n    return ((xc2 - xc1) * (yc2 - yc1)) / size;\n  }\n\n  bool isOverlap(Rect rc1, Rect rc2) {\n    return rc1.left + rc1.width > rc2.left &&\n        rc2.left + rc2.width > rc1.left &&\n        rc1.top + rc1.height > rc2.top &&\n        rc2.top + rc2.height > rc1.top;\n  }\n}\n\nclass FocusWidget extends StatelessWidget {\n  const FocusWidget(this.diameter, {Key? key}) : super(key: key);\n\n  final double diameter;\n\n  @override\n  Widget build(BuildContext context) {\n    return Container(\n      width: diameter,\n      height: diameter,\n      decoration: BoxDecoration(\n        shape: BoxShape.circle,\n        border: Border.all(color: const Color(0xff337CC4), width: 1),\n        color: const Color(0x00000000),\n      ),\n      alignment: Alignment.center,\n      child: Container(\n        width: diameter * 1 / 3,\n        height: diameter * 1 / 3,\n        decoration: const BoxDecoration(\n          shape: BoxShape.circle,\n          color: Color(0x30CC3A4B),\n        ),\n        alignment: Alignment.center,\n        child: Container(\n          width: diameter * 1 / 6,\n          height: diameter * 1 / 6,\n          decoration: const BoxDecoration(\n            shape: BoxShape.circle,\n            color: Color(0xffCC3A4B),\n          ),\n        ),\n      ),\n    );\n  }\n}\n\nclass AlignRulerWidget extends StatelessWidget {\n  const AlignRulerWidget({\n    Key? key,\n  }) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    final width = MediaQuery.of(context).size.width;\n    final height = MediaQuery.of(context).size.height;\n\n    return ValueListenableBuilder<Offset>(\n      builder: (BuildContext context, Offset value, Widget? child) {\n        return Container(\n          height: height,\n          width: width,\n          child: CustomPaint(\n            painter: AlignRulerPainter(value),\n          ),\n        );\n      },\n      valueListenable: ViewCheckerKit._instance.viewCheckerWidgetCenterNotifier,\n    );\n  }\n}\n\nclass AlignRulerPainter extends CustomPainter {\n  AlignRulerPainter(this.center);\n\n  final Offset center;\n\n  @override\n  void paint(Canvas canvas, Size size) {\n    _paintVertical(canvas, size);\n    _paintHorizontal(canvas, size);\n  }\n\n  void _paintVertical(Canvas canvas, Size size) {\n    final double sh = size.height;\n\n    final Paint paint = Paint()\n      ..color = Colors.red\n      ..style = PaintingStyle.stroke\n      ..strokeWidth = 1;\n\n    final Path path = Path()\n      ..moveTo(center.dx, 0)\n      ..lineTo(center.dx, sh);\n\n    canvas.drawPath(path, paint);\n    path.close();\n\n    final double dx = center.dx;\n    final double dy = center.dy;\n    final double top = dy;\n    final double bottom = sh - dy;\n\n    TextPainter(\n      text: TextSpan(\n        text: top.toStringAsFixed(1),\n        style: const TextStyle(fontSize: 12, color: Colors.red),\n      ),\n      textAlign: TextAlign.right,\n      textDirection: TextDirection.ltr,\n      textWidthBasis: TextWidthBasis.longestLine,\n    )\n      ..layout(minWidth: 0)\n      ..paint(canvas, Offset(dx, dy / 2));\n\n    TextPainter(\n      text: TextSpan(\n        text: bottom.toStringAsFixed(1),\n        style: const TextStyle(fontSize: 12, color: Colors.red),\n      ),\n      textAlign: TextAlign.right,\n      textDirection: TextDirection.ltr,\n      textWidthBasis: TextWidthBasis.longestLine,\n    )\n      ..layout(minWidth: 0)\n      ..paint(canvas, Offset(dx, sh - bottom / 2));\n  }\n\n  void _paintHorizontal(Canvas canvas, Size size) {\n    final double sw = size.width;\n\n    final Paint paint = Paint()\n      ..color = Colors.red\n      ..style = PaintingStyle.stroke\n      ..strokeWidth = 1;\n\n    final Path path = Path()\n      ..moveTo(0, center.dy)\n      ..lineTo(sw, center.dy);\n\n    canvas.drawPath(path, paint);\n    path.close();\n\n    final double dx = center.dx;\n    final double dy = center.dy;\n    final double right = sw - dx;\n    final double left = dx;\n\n    TextPainter(\n      text: TextSpan(\n        text: right.toStringAsFixed(1),\n        style: const TextStyle(fontSize: 12, color: Colors.red),\n      ),\n      textAlign: TextAlign.right,\n      textDirection: TextDirection.ltr,\n      textWidthBasis: TextWidthBasis.longestLine,\n    )\n      ..layout(minWidth: 0)\n      ..paint(canvas, Offset(sw - right / 2, dy));\n\n    TextPainter(\n      text: TextSpan(\n        text: left.toStringAsFixed(1),\n        style: const TextStyle(fontSize: 12, color: Colors.red),\n      ),\n      textAlign: TextAlign.right,\n      textDirection: TextDirection.ltr,\n      textWidthBasis: TextWidthBasis.longestLine,\n    )\n      ..layout(minWidth: 0)\n      ..paint(canvas, Offset(dx / 2, dy));\n  }\n\n  @override\n  bool shouldRepaint(AlignRulerPainter oldDelegate) => true;\n\n  @override\n  bool hitTest(Offset position) => false;\n}\n"
  },
  {
    "path": "Flutter/lib/kit/visual/visual.dart",
    "content": "// Copyright© Dokit for Flutter.\n//\n// visual.dart\n// Flutter\n//\n// Created by linusflow on 2021/3/05\n// Modified by linusflow on 2021/5/11 下午4:35\n//\n\n// 视觉功能\nimport 'package:dokit/kit/kit.dart';\nimport 'package:dokit/kit/visual/color_pick.dart';\nimport 'package:dokit/kit/visual/view_check.dart';\nimport 'package:dokit/util/screen_util.dart';\n\n/// 可视化Kit的信息展示widget的margin\nconst double infoWidgetHorizontalMargin = 20;\n\n/// 可视化Kit的信息展示widget的bottom margin\nfinal double infoWidgetTopMargin = ScreenUtil.instance.statusBarHeight + 20;\n\nabstract class VisualKit implements IKit {}\n\nclass VisualKitManager {\n  VisualKitManager._privateConstructor();\n\n  Map<String, IKit> kitMap = <String, IKit>{\n    VisualKitName.KIT_VIEW_CHECK: ViewCheckerKit.instance,\n    VisualKitName.KIT_COLOR_PICK: ColorPickerKit.instance,\n  };\n\n  static final VisualKitManager _instance =\n      VisualKitManager._privateConstructor();\n\n  static VisualKitManager get instance => _instance;\n\n  // 如果想要自定义实现，可以用这个方式进行覆盖。后续扩展入口\n  void addKit(String tag, IKit kit) {\n    kitMap[tag] = kit;\n  }\n\n  T? getKit<T extends IKit>(String name) {\n    if (kitMap.containsKey(name)) {\n      return kitMap[name] as T;\n    }\n    return null;\n  }\n}\n\nclass VisualKitName {\n  static const String KIT_VIEW_CHECK = '控件检查';\n  static const String KIT_COLOR_PICK = '颜色拾取';\n}\n"
  },
  {
    "path": "Flutter/lib/ui/dokit_app.dart",
    "content": "// Copyright© Dokit for Flutter. All rights reserved.\n//\n// dokit_app.dart\n// Flutter\n//\n// Created by linusflow on 2021/3/05\n// Modified by linusflow on 2021/5/11 下午8:08\n//\n\nimport 'package:dokit/kit/observer.dart';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\n\nfinal GlobalKey<OverlayState> doKitOverlayKey = GlobalKey<OverlayState>();\n\nabstract class IDoKitApp extends Widget {\n  Widget get origin;\n}\n\n// 谷歌提供的DevTool会判断入口widget是否在主工程内申明(runApp(MyApp())，MyApp必须在主工程内定义，估计是根据source file来判断的)，\n// 如果在package内去申明这个入口widget，则在Flutter Inspector上的左边树会被折叠，影响开发使用。故这里要求在main文件内使用DoKitApp(MyApp())的形式来初始化入口\nclass DoKitApp extends StatefulWidget implements IDoKitApp {\n  DoKitApp(Widget widget)\n      : _origin = _DoKitWrapper(widget),\n        super(key: rootKey);\n\n  // 放置dokit悬浮窗的容器\n  static GlobalKey rootKey = GlobalKey();\n\n  // 放置应用真实widget的容器\n  static GlobalKey appKey = GlobalKey();\n\n  @override\n  Widget get origin => _origin;\n  final Widget _origin;\n\n  @override\n  State<StatefulWidget> createState() {\n    return _DoKitAppState();\n  }\n}\n\nclass _DoKitWrapper extends StatelessWidget {\n  _DoKitWrapper(this._origin) : super(key: DoKitApp.appKey);\n\n  final Widget _origin;\n\n  @override\n  Widget build(BuildContext context) {\n    if (_origin is StatelessWidget) {\n      debugPrint(_origin.toStringShort());\n      Widget widget = (_origin as StatelessWidget).build(context);\n      debugPrint(widget.toStringShort());\n      if (widget is MaterialApp) {\n        final navigatorObservers = widget.navigatorObservers;\n        if (navigatorObservers != null) {\n          ensureDokitObserver(navigatorObservers);\n          return widget;\n        }\n      }\n      if (widget is CupertinoApp) {\n        final navigatorObservers = widget.navigatorObservers;\n        if (navigatorObservers != null) {\n          ensureDokitObserver(navigatorObservers);\n          return widget;\n        }\n      }\n    }\n    return _origin;\n  }\n\n  void ensureDokitObserver(List<NavigatorObserver> navigatorObservers) {\n    if (!navigatorObservers\n        .any((element) => element is DokitNavigatorObserver)) {\n      navigatorObservers.add(DokitNavigatorObserver());\n    }\n  }\n}\n\nclass _DoKitAppState extends State<DoKitApp> {\n  final List<OverlayEntry> entries = <OverlayEntry>[];\n  final List<Locale> supportedLocales = const <Locale>[Locale('en', 'US')];\n\n  @override\n  void initState() {\n    super.initState();\n    entries.clear();\n    entries.add(OverlayEntry(builder: (BuildContext context) {\n      return widget.origin;\n    }));\n  }\n\n  Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {\n    yield DefaultMaterialLocalizations.delegate;\n    yield DefaultCupertinoLocalizations.delegate;\n    yield DefaultWidgetsLocalizations.delegate;\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Directionality(\n      textDirection: TextDirection.ltr,\n      child: Stack(\n        children: <Widget>[\n          widget.origin,\n          _MediaQueryFromWindow(\n            child: Localizations(\n              locale: supportedLocales.first,\n              delegates: _localizationsDelegates.toList(),\n              child: ScaffoldMessenger(\n                child: Overlay(\n                  key: doKitOverlayKey,\n                ),\n              ),\n            ),\n          )\n        ],\n      ),\n    );\n  }\n}\n\nclass _MediaQueryFromWindow extends StatefulWidget {\n  const _MediaQueryFromWindow({Key? key, required this.child})\n      : super(key: key);\n\n  final Widget child;\n\n  @override\n  _MediaQueryFromWindowsState createState() => _MediaQueryFromWindowsState();\n}\n\nclass _MediaQueryFromWindowsState extends State<_MediaQueryFromWindow>\n    with WidgetsBindingObserver {\n  @override\n  void initState() {\n    super.initState();\n    WidgetsBinding.instance?.addObserver(this);\n  }\n\n  // ACCESSIBILITY\n\n  @override\n  void didChangeAccessibilityFeatures() {\n    setState(() {\n      // The properties of window have changed. We use them in our build\n      // function, so we need setState(), but we don't cache anything locally.\n    });\n  }\n\n  // METRICS\n\n  @override\n  void didChangeMetrics() {\n    setState(() {\n      // The properties of window have changed. We use them in our build\n      // function, so we need setState(), but we don't cache anything locally.\n    });\n  }\n\n  @override\n  void didChangeTextScaleFactor() {\n    setState(() {\n      // The textScaleFactor property of window has changed. We reference\n      // window in our build function, so we need to call setState(), but\n      // we don't need to cache anything locally.\n    });\n  }\n\n  // RENDERING\n  @override\n  void didChangePlatformBrightness() {\n    setState(() {\n      // The platformBrightness property of window has changed. We reference\n      // window in our build function, so we need to call setState(), but\n      // we don't need to cache anything locally.\n    });\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MediaQuery(\n      data: MediaQueryData.fromWindow(WidgetsBinding.instance!.window),\n      child: widget.child,\n    );\n  }\n\n  @override\n  void dispose() {\n    WidgetsBinding.instance?.removeObserver(this);\n    super.dispose();\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/ui/dokit_btn.dart",
    "content": "import 'package:dokit/dokit.dart';\nimport 'package:dokit/kit/apm/apm.dart';\nimport 'package:dokit/ui/dokit_app.dart';\nimport 'package:dokit/ui/resident_page.dart';\nimport 'package:flutter/material.dart';\n\n// DoKitBtn 点击事件回调\n// 参数说明：\n// true : dokit面板展开\n// false: dokit面板收起\ntypedef DoKitBtnClickedCallback = void Function(bool);\n\n// 入口btn\n// ignore: must_be_immutable\nclass DoKitBtn extends StatefulWidget {\n  DoKitBtn() : super(key: doKitBtnKey);\n\n  static GlobalKey<DoKitBtnState> doKitBtnKey = GlobalKey<DoKitBtnState>();\n  OverlayEntry? overlayEntry;\n  DoKitBtnClickedCallback? btnClickCallback;\n\n  @override\n  DoKitBtnState createState() => DoKitBtnState(overlayEntry!);\n\n  void addToOverlay() {\n    assert(overlayEntry == null);\n    overlayEntry = OverlayEntry(builder: (BuildContext context) {\n      return this;\n    });\n    final OverlayState? rootOverlay = doKitOverlayKey.currentState;\n    assert(rootOverlay != null);\n    rootOverlay?.insert(overlayEntry!);\n    ApmKitManager.instance.startUp();\n  }\n}\n\nclass DoKitBtnState extends State<DoKitBtn> {\n  DoKitBtnState(this.owner);\n\n  Offset? offsetA; //按钮的初始位置\n  final OverlayEntry owner;\n  OverlayEntry? debugPage;\n  bool showDebugPage = false;\n\n  @override\n  Widget build(BuildContext context) {\n    return Positioned(\n        left: offsetA?.dx,\n        top: offsetA?.dy,\n        right: offsetA == null ? 20 : null,\n        bottom: offsetA == null ? 120 : null,\n        child: Draggable(\n            child: Container(\n              width: 70,\n              height: 70,\n              alignment: Alignment.center,\n              child: TextButton(\n                style: ButtonStyle(\n                  padding: MaterialStateProperty.all(EdgeInsets.all(0)),\n                ),\n                child: Image.asset('images/dokit_flutter_btn.png',\n                    package: DK_PACKAGE_NAME, height: 70, width: 70),\n                onPressed: openDebugPage,\n              ),\n            ),\n            feedback: Container(\n              width: 70,\n              height: 70,\n              alignment: Alignment.center,\n              child: TextButton(\n                style: ButtonStyle(\n                  padding: MaterialStateProperty.all(EdgeInsets.all(0)),\n                ),\n                child: Image.asset('images/dokit_flutter_btn.png',\n                    package: DK_PACKAGE_NAME, height: 70, width: 70),\n                onPressed: openDebugPage,\n              ),\n            ),\n            childWhenDragging: Container(),\n            onDragEnd: (DraggableDetails detail) {\n              final Offset offset = detail.offset;\n              setState(() {\n                final Size size = MediaQuery.of(context).size;\n                final double width = size.width;\n                final double height = size.height;\n                double x = offset.dx;\n                double y = offset.dy;\n                if (x < 0) {\n                  x = 0;\n                }\n                if (x > width - 80) {\n                  x = width - 80;\n                }\n                if (y < 0) {\n                  y = 0;\n                }\n                if (y > height - 26) {\n                  y = height - 26;\n                }\n                offsetA = Offset(x, y);\n              });\n            },\n            onDraggableCanceled: (Velocity velocity, Offset offset) {}));\n  }\n\n  void openDebugPage() {\n    debugPage ??= OverlayEntry(builder: (BuildContext context) {\n      return ResidentPage();\n    });\n    if (showDebugPage) {\n      closeDebugPage();\n    } else {\n      doKitOverlayKey.currentState?.insert(debugPage!, below: owner);\n      showDebugPage = true;\n    }\n\n    widget.btnClickCallback!(showDebugPage);\n  }\n\n  void closeDebugPage() {\n    if (showDebugPage && debugPage != null) {\n      debugPage!.remove();\n      showDebugPage = false;\n    }\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/ui/kit_page.dart",
    "content": "// ignore_for_file: sort_child_properties_last\n\nimport 'package:dokit/dokit.dart';\nimport 'package:dokit/kit/apm/apm.dart';\nimport 'package:dokit/kit/biz/biz.dart';\nimport 'package:dokit/kit/common/common.dart';\nimport 'package:dokit/kit/kit.dart';\nimport 'package:dokit/kit/visual/visual.dart';\nimport 'package:dokit/ui/resident_page.dart';\nimport 'package:dokit/widget/dash_decoration.dart';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:shared_preferences/shared_preferences.dart';\n\nclass KitPage extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() {\n    return _KitPage();\n  }\n}\n\nclass _KitPage extends State<KitPage> {\n  bool onDrag = false;\n  final GlobalKey _residentContainerKey = GlobalKey();\n\n  @override\n  void initState() {\n    super.initState();\n    onDrag = false;\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final double width = MediaQuery.of(context).size.width;\n\n    return SingleChildScrollView(\n        child: Container(\n            color: const Color(0xffffffff),\n            child: Column(\n              mainAxisAlignment: MainAxisAlignment.start,\n              crossAxisAlignment: CrossAxisAlignment.start,\n              children: <Widget>[\n                Container(\n                  margin: const EdgeInsets.only(\n                      left: 10, right: 10, top: 15, bottom: 10),\n                  child: Container(\n                    key: _residentContainerKey,\n                    decoration: onDrag\n                        ? const DashedDecoration(\n                            dashedColor: Colors.red,\n                            borderRadius:\n                                BorderRadius.all(Radius.circular(8.0)))\n                        : null,\n                    padding:\n                        const EdgeInsets.only(left: 5, right: 5, bottom: 20),\n                    child: Column(\n                      crossAxisAlignment: CrossAxisAlignment.start,\n                      children: <Widget>[\n                        Container(\n                          alignment: Alignment.topLeft,\n                          margin: const EdgeInsets.only(\n                              left: 10, top: 10, bottom: 15),\n                          child: RichText(\n                              text: const TextSpan(children: <TextSpan>[\n                            TextSpan(\n                                text: '常驻工具',\n                                style: TextStyle(\n                                    fontSize: 16,\n                                    color: Color(0xff333333),\n                                    fontWeight: FontWeight.bold)),\n                            TextSpan(\n                                text: '  [最多放置4个]',\n                                style: TextStyle(\n                                  fontSize: 12,\n                                  color: Color(0xff333333),\n                                )),\n                          ])),\n                        ),\n                        buildResidentView(context)\n                      ],\n                    ),\n                    alignment: Alignment.center,\n                  ),\n                ),\n                buildBizGroupView(context), // 自定义工具\n                Container(\n                    width: width, height: 12, color: const Color(0xfff5f6f7)),\n                Container(\n                  margin: const EdgeInsets.only(\n                      left: 10, right: 10, top: 15, bottom: 10),\n                  child: Container(\n                    child: Column(\n                      crossAxisAlignment: CrossAxisAlignment.start,\n                      children: <Widget>[\n                        Container(\n                          alignment: Alignment.topLeft,\n                          margin: const EdgeInsets.only(\n                              left: 10, top: 10, bottom: 15),\n                          child: RichText(\n                              text: const TextSpan(children: <TextSpan>[\n                            TextSpan(\n                                text: '其他工具',\n                                style: TextStyle(\n                                    fontSize: 16,\n                                    color: Color(0xff333333),\n                                    fontWeight: FontWeight.bold)),\n                            TextSpan(\n                                text: '  [拖动图标放入常驻工具]',\n                                style: TextStyle(\n                                  fontSize: 12,\n                                  color: Color(0xff333333),\n                                )),\n                          ])),\n                        ),\n                        buildOtherView(context)\n                      ],\n                    ),\n                    alignment: Alignment.center,\n                  ),\n                ),\n              ],\n            )));\n  }\n\n  bool inResidentContainerEdge(Offset? offset) {\n    final Size? size = _residentContainerKey.currentContext?.size;\n    if (offset == null || size == null) {\n      return false;\n    }\n\n    final Offset position =\n        (_residentContainerKey.currentContext?.findRenderObject() as RenderBox)\n            .localToGlobal(Offset.zero);\n    final Rect rc1 = Rect.fromLTWH(offset.dx, offset.dy, 80, 80);\n    final Rect rc2 =\n        Rect.fromLTWH(position.dx, position.dy, size.width, size.height);\n\n    return rc1.left + rc1.width > rc2.left &&\n        rc2.left + rc2.width > rc1.left &&\n        rc1.top + rc1.height > rc2.top &&\n        rc2.top + rc2.height > rc1.top;\n  }\n\n  Widget buildBizGroupView(BuildContext context) {\n    final widgets = <Widget>[];\n    final width = MediaQuery.of(context).size.width;\n    var groupKeys = BizKitManager.instance.groupKeys();\n    var counts = groupKeys.length;\n\n    if (counts == 0) {\n      return SizedBox();\n    }\n\n    for (var i = 0; i < counts; i++) {\n      var key = groupKeys[i];\n      var tip = BizKitManager.instance.kitGroupTips[key];\n      widgets.add(\n          Container(width: width, height: 12, color: const Color(0xfff5f6f7)));\n\n      widgets.add(Container(\n        margin: const EdgeInsets.only(left: 10, right: 10),\n        child: Container(\n          alignment: Alignment.center,\n          child: Column(\n            crossAxisAlignment: CrossAxisAlignment.start,\n            children: <Widget>[\n              Container(\n                alignment: Alignment.topLeft,\n                margin: const EdgeInsets.only(left: 10, top: 10, bottom: 15),\n                child: RichText(\n                    text: TextSpan(children: <TextSpan>[\n                  TextSpan(\n                      text: key, // 这块也是外部获取\n                      style: TextStyle(\n                          fontSize: 16,\n                          color: Color(0xff333333),\n                          fontWeight: FontWeight.bold)),\n                  TextSpan(\n                      text: '  $tip', // 外部获取\n                      style: TextStyle(\n                        fontSize: 12,\n                        color: Color(0xff333333),\n                      )),\n                ])),\n              ),\n              // 这里的数据从BizMananger中获取\n              buildBizKitView(context, key)\n            ],\n          ),\n        ),\n      ));\n\n      widgets.add(Container(\n          width: width,\n          height: 12,\n          color: Colors.white));\n    }\n\n    final wrap = Wrap(\n      children: widgets,\n    );\n    return wrap;\n  }\n\n  Widget buildBizKitView(BuildContext context, String key) {\n    final List<Widget> widgets = <Widget>[];\n    final double round = (MediaQuery.of(context).size.width - 80 * 4 - 30) / 3;\n    BizKitManager.instance.kitGroupMap[key]!.forEach((IKit value) {\n      widgets.add(\n        MaterialButton(\n            child: KitItem(value),\n            onPressed: () {\n              setState(() {\n                value.tabAction();\n              });\n            },\n            padding: const EdgeInsets.all(0),\n            minWidth: 40),\n      );\n    });\n    final wrap = Wrap(\n      spacing: round,\n      runSpacing: 15,\n      children: widgets,\n    );\n    return wrap;\n  }\n\n  Widget buildResidentView(BuildContext context) {\n    final List<Widget> widgets = <Widget>[];\n    final double round = (MediaQuery.of(context).size.width - 80 * 4 - 30) / 3;\n    KitPageManager.instance.getResidentKit().forEach((String key, IKit value) {\n      widgets.add(\n        Draggable(\n          child: MaterialButton(\n              child: KitItem(value),\n              onPressed: () {\n                setState(() {\n                  value.tabAction();\n                });\n              },\n              padding: const EdgeInsets.all(0),\n              minWidth: 40),\n          feedback: KitItem(value),\n          onDragStarted: () {\n            setState(() {\n              onDrag = true;\n            });\n          },\n          onDraggableCanceled: (Velocity velocity, Offset offset) {\n            setState(() {\n              onDrag = false;\n              if (!inResidentContainerEdge(offset)) {\n                KitPageManager.instance.removeResidentKit(key);\n              }\n            });\n          },\n          onDragEnd: (DraggableDetails detail) {\n            setState(() {\n              onDrag = false;\n              if (!inResidentContainerEdge(detail.offset)) {\n                KitPageManager.instance.removeResidentKit(key);\n              }\n            });\n          },\n        ),\n      );\n    });\n    final Wrap wrap = Wrap(\n      spacing: round,\n      runSpacing: 15,\n      children: widgets,\n    );\n    return wrap;\n  }\n\n  Widget buildOtherView(BuildContext context) {\n    final List<Widget> widgets = <Widget>[];\n    final double round = (MediaQuery.of(context).size.width - 80 * 4 - 30) / 3;\n    KitPageManager.instance.getOtherKit().forEach((String key, IKit value) {\n      widgets.add(\n        Draggable(\n          child: MaterialButton(\n              child: KitItem(value),\n              onPressed: () {\n                setState(() {\n                  value.tabAction();\n                });\n              },\n              padding: const EdgeInsets.all(0),\n              minWidth: 40),\n          feedback: KitItem(value),\n          onDragStarted: () {\n            setState(() {\n              onDrag = true;\n            });\n          },\n          onDragEnd: (DraggableDetails detail) {\n            setState(() {\n              if (inResidentContainerEdge(detail.offset)) {\n                KitPageManager.instance.addResidentKit(key);\n              }\n              onDrag = false;\n            });\n          },\n          onDraggableCanceled: (Velocity v, Offset offset) {\n            setState(() {\n              if (inResidentContainerEdge(offset)) {\n                KitPageManager.instance.addResidentKit(key);\n              }\n              onDrag = false;\n            });\n          },\n        ),\n      );\n    });\n    final wrap = Wrap(\n      spacing: round,\n      runSpacing: 15,\n      children: widgets,\n    );\n    return wrap;\n  }\n}\n\nclass KitItem extends StatelessWidget {\n  const KitItem(this.kit);\n\n  final IKit kit;\n\n  @override\n  Widget build(BuildContext context) {\n    return Container(\n      width: 80,\n      alignment: Alignment.center,\n      child: Column(\n        children: <Widget>[\n          Image.asset(\n            kit.getIcon(),\n            width: 34,\n            height: 34,\n            fit: BoxFit.fitWidth,\n            package: DK_PACKAGE_NAME,\n          ),\n          Container(\n            margin: const EdgeInsets.only(top: 6),\n            child: Text(kit.getKitName(),\n                style: const TextStyle(\n                    fontFamily: 'PingFang SC',\n                    fontSize: 12,\n                    fontWeight: FontWeight.normal,\n                    decoration: TextDecoration.none,\n                    color: Color(0xff666666))),\n          ),\n        ],\n      ),\n    );\n  }\n}\n\nclass KitPageManager {\n  KitPageManager._privateConstructor();\n\n  static const String KIT_ALL = '全部';\n  static const String KEY_KIT_PAGE_CACHE = 'key_kit_page_cache';\n  List<String> residentList = <String>[\n    ApmKitName.KIT_LOG,\n    ApmKitName.KIT_CHANNEL\n  ];\n\n  static final KitPageManager _instance = KitPageManager._privateConstructor();\n\n  static KitPageManager get instance => _instance;\n\n  String listToString(List<String>? list) {\n    if (list == null || list.isEmpty) {\n      return '';\n    }\n    String? result;\n    for (final String item in list) {\n      if (result == null) {\n        result = item;\n      } else {\n        result = '${result},${item}';\n      }\n    }\n\n    return result.toString();\n  }\n\n  bool addResidentKit(String? tag) {\n    assert(tag != null);\n    if (!residentList.contains(tag)) {\n      if (residentList.length >= 4) {\n        return false;\n      }\n      residentList.add(tag!);\n      SharedPreferences.getInstance().then((SharedPreferences prefs) =>\n          prefs.setString(KEY_KIT_PAGE_CACHE, listToString(residentList)));\n      return true;\n    }\n    return false;\n  }\n\n  bool removeResidentKit(String tag) {\n    if (residentList.contains(tag)) {\n      residentList.remove(tag);\n      SharedPreferences.getInstance().then((SharedPreferences prefs) =>\n          prefs.setString(KEY_KIT_PAGE_CACHE, listToString(residentList)));\n      return true;\n    }\n    return false;\n  }\n\n  Map<String, IKit> getOtherKit() {\n    final Map<String, IKit> kits = <String, IKit>{};\n    CommonKitManager.instance.kitMap.forEach((String key, CommonKit value) {\n      if (!residentList.contains(key)) {\n        kits[key] = value;\n      }\n    });\n    ApmKitManager.instance.kitMap.forEach((String key, ApmKit value) {\n      if (!residentList.contains(key)) {\n        kits[key] = value;\n      }\n    });\n    VisualKitManager.instance.kitMap.forEach((String key, IKit value) {\n      if (!residentList.contains(key)) {\n        kits[key] = value;\n      }\n    });\n    return kits;\n  }\n\n  Map<String, IKit> getResidentKit() {\n    final Map<String, IKit> kits = <String, IKit>{};\n\n    for (final String element in residentList) {\n      if (ApmKitManager.instance.getKit(element) != null) {\n        kits[element] = ApmKitManager.instance.getKit(element)!;\n      } else if (VisualKitManager.instance.getKit(element) != null) {\n        kits[element] = VisualKitManager.instance.getKit(element)!;\n      } else if (CommonKitManager.instance.getKit(element) != null) {\n        kits[element] = CommonKitManager.instance.getKit(element)!;\n      }\n    }\n    return kits;\n  }\n\n  void loadCache() {\n    SharedPreferences.getInstance().then<dynamic>((SharedPreferences prefs) {\n      if (prefs.getString(KitPageManager.KEY_KIT_PAGE_CACHE) != null) {\n        if (prefs.getString(KitPageManager.KEY_KIT_PAGE_CACHE) == '') {\n          KitPageManager.instance.residentList = <String>[];\n        } else {\n          KitPageManager.instance.residentList =\n              prefs.getString(KitPageManager.KEY_KIT_PAGE_CACHE)?.split(',') ??\n                  [];\n        }\n      }\n      if (KitPageManager.instance.residentList.isNotEmpty) {\n        ResidentPage.tag = KitPageManager.instance.residentList.first;\n      } else {\n        ResidentPage.tag = KitPageManager.KIT_ALL;\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/ui/resident_page.dart",
    "content": "import 'package:dokit/kit/apm/apm.dart';\nimport 'package:dokit/kit/biz/biz.dart';\nimport 'package:dokit/kit/common/common.dart';\nimport 'package:dokit/kit/kit.dart';\nimport 'package:dokit/ui/kit_page.dart';\nimport 'package:flutter/material.dart';\n\nclass ResidentPage extends StatefulWidget {\n  ResidentPage() : super(key: residentPageKey);\n\n  static String tag = KitPageManager.KIT_ALL;\n  static final GlobalKey<ResidentPageState> residentPageKey =\n      GlobalKey<ResidentPageState>();\n\n  @override\n  State<StatefulWidget> createState() {\n    return ResidentPageState();\n  }\n}\n\nclass ResidentPageState extends State<ResidentPage> {\n  Widget getPage() {\n    Widget? page;\n    page ??=\n        ApmKitManager.instance.getKit(ResidentPage.tag)?.createDisplayPage();\n    page ??=\n        CommonKitManager.instance.getKit(ResidentPage.tag)?.createDisplayPage();\n    page ??=\n        BizKitManager.instance.getKit(ResidentPage.tag)?.displayPage();\n    page ??= KitPage();\n    return page;\n  }\n\n  String getTitle() {\n    String? title;\n    title ??= ApmKitManager.instance.getKit(ResidentPage.tag)?.getKitName();\n    title ??= CommonKitManager.instance.getKit(ResidentPage.tag)?.getKitName();\n    title ??= BizKitManager.instance.getKit(ResidentPage.tag)?.getKitName();\n    title ??= 'DoKit';\n    return title;\n  }\n\n  void _tapListener(String current) {\n    setState(() {\n      ResidentPage.tag = current;\n    });\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final Size size = MediaQuery.of(context).size;\n    final double width = size.width;\n    final double height = size.height;\n    final int topMargin =\n        MediaQuery.of(context).orientation == Orientation.portrait ? 100 : 0;\n    if (height == 0) {\n      return Container();\n    }\n    return Positioned(\n      child: Container(\n        width: width,\n        color: const Color(0x88000000),\n        height: height - topMargin,\n        child: Column(\n          mainAxisAlignment: MainAxisAlignment.end,\n          children: <Widget>[\n            Container(\n                alignment: Alignment.topCenter,\n                width: width,\n                height: height - topMargin - 50,\n                //todo Scaffold在ios 11上会出现顶部组件点击事件失效问题（应该是在计算触摸事件的时候计算了刘海屏的高度，在这个高度内的控件不响应事件），这里修改了层级把标题放到了Scaffold内部\n                child: Scaffold(\n                    backgroundColor: Colors.transparent,\n                    body: Stack(\n                      children: <Widget>[\n                        Container(\n                          height: 50,\n                          width: width,\n                          alignment: Alignment.center,\n                          decoration: const BoxDecoration(\n                            color: Color(0xfff6f6f7),\n                            borderRadius: BorderRadius.only(\n                                topLeft: Radius.circular(12),\n                                topRight: Radius.circular(12)),\n                          ),\n                          child: Text(getTitle(),\n                              style: const TextStyle(\n                                  fontFamily: 'PingFang SC',\n                                  color: Color(0xff000000),\n                                  fontSize: 18,\n                                  fontWeight: FontWeight.bold,\n                                  decoration: TextDecoration.none)),\n                        ),\n                        Container(\n                          margin: const EdgeInsets.only(top: 50),\n                          height: height - topMargin - 100,\n                          color: Colors.white,\n                          child: getPage(),\n                        ),\n                        Container(\n                          height: 0.5,\n                          color: const Color(0xffeae6ea),\n                          width: MediaQuery.of(context).size.width,\n                          margin: const EdgeInsets.only(top: 50),\n                        )\n                      ],\n                    ))),\n            const Divider(height: 0.5, color: Color(0xffeae6ea)),\n            Container(\n              height: 49,\n              width: width,\n              color: const Color(0xfff6f6f7),\n              child: Row(\n                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n                  children: buildBottomWidgets()),\n            )\n          ],\n        ),\n      ),\n    );\n  }\n\n  List<Widget> buildBottomWidgets() {\n    final List<Widget> list = <Widget>[];\n    KitPageManager.instance.getResidentKit().forEach((String key, IKit kit) {\n      list.add(Expanded(\n        child: GestureDetector(\n          child: Container(\n            alignment: Alignment.center,\n            height: 29,\n            child: Text(key,\n                style: TextStyle(\n                    color: ResidentPage.tag == key\n                        ? const Color(0xFF337CC4)\n                        : const Color(0xff333333),\n                    fontWeight: FontWeight.normal,\n                    fontFamily: 'PingFang SC',\n                    decoration: TextDecoration.none,\n                    fontSize: 13)),\n          ),\n          onTap: () {\n            kit.tabAction();\n          },\n          behavior: HitTestBehavior.opaque,\n        ),\n        flex: 1,\n      ));\n      list.add(Container(\n          width: 0.5,\n          height: 18,\n          decoration: const BoxDecoration(color: Color(0xffe5e5e6))));\n    });\n    list.add(Expanded(\n      child: GestureDetector(\n        child: Container(\n          alignment: Alignment.center,\n          height: 29,\n          child: Text(\n            KitPageManager.KIT_ALL,\n            style: TextStyle(\n                color: ResidentPage.tag == KitPageManager.KIT_ALL\n                    ? const Color(0xFF337CC4)\n                    : const Color(0xff333333),\n                fontWeight: FontWeight.normal,\n                decoration: TextDecoration.none,\n                fontSize: 13),\n          ),\n        ),\n        onTap: () {\n          _tapListener(KitPageManager.KIT_ALL);\n        },\n        behavior: HitTestBehavior.opaque,\n      ),\n      flex: 1,\n    ));\n    return list;\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/util/byte_util.dart",
    "content": "// Copyright© Dokit for Flutter. All rights reserved.\n//\n// byte_util.dart\n// Flutter\n//\n// Created by linusflow on 2021/3/05\n// Modified by linusflow on 2021/5/11 上午10:39\n//\n\nString toByteString(int? bytes) {\n  if (bytes == null) {\n    return '0';\n  }\n  if (bytes <= (1 << 10)) {\n    return '${bytes}B';\n  } else if (bytes <= (1 << 20)) {\n    return '${(bytes >> 10).toStringAsFixed(2)}K';\n  } else {\n    return '${(bytes >> 20).toStringAsFixed(2)}M';\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/util/screen_util.dart",
    "content": "import 'dart:ui' as ui show window;\n\nimport 'package:flutter/material.dart';\n\nclass ScreenUtil {\n  ScreenUtil._privateConstructor() {\n    final MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window);\n    if (_mediaQueryData != mediaQuery) {\n      _mediaQueryData = mediaQuery;\n      _screenWidth = mediaQuery.size.width;\n      _screenHeight = mediaQuery.size.height;\n      _screenDensity = mediaQuery.devicePixelRatio;\n      _statusBarHeight = mediaQuery.padding.top;\n      _bottomBarHeight = mediaQuery.padding.bottom;\n      _appBarHeight = kToolbarHeight;\n    }\n  }\n\n  static final ScreenUtil _instance = ScreenUtil._privateConstructor();\n  static ScreenUtil get instance => _instance;\n\n  double _screenWidth = 0.0;\n  double _screenHeight = 0.0;\n  double _screenDensity = 0.0;\n  double _statusBarHeight = 0.0;\n  double _bottomBarHeight = 0.0;\n  double _appBarHeight = 0.0;\n  MediaQueryData? _mediaQueryData;\n\n  Offset? _screenCenter;\n  Offset get screenCenter {\n    if (_screenCenter == null) {\n      final Size size = _mediaQueryData!.size;\n      final double width = size.width;\n      final double height = size.height;\n      final double x = width / 2;\n      final double y = height / 2;\n      _screenCenter = Offset(x, y);\n    }\n    return _screenCenter!;\n  }\n\n  /// 屏幕的宽度\n  double get screenWidth => _screenWidth;\n\n  /// 屏幕的高度\n  double get screenHeight => _screenHeight;\n\n  /// appBar的高度\n  double get appBarHeight => _appBarHeight;\n\n  /// 屏幕的像素密度\n  double get screenDensity => _screenDensity;\n\n  /// 状态栏的高度\n  double get statusBarHeight => _statusBarHeight;\n\n  /// bottom bar的高度\n  double get bottomBarHeight => _bottomBarHeight;\n}\n"
  },
  {
    "path": "Flutter/lib/util/time_util.dart",
    "content": "import 'package:date_format/date_format.dart';\n\nString toTimeString(int time) {\n  return formatDate(DateTime.fromMillisecondsSinceEpoch(time),\n      <String>[HH, \":\", nn, \":\", ss, \".\", S]);\n}"
  },
  {
    "path": "Flutter/lib/widget/dash_decoration.dart",
    "content": "import 'dart:math' as math;\n\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\n\nclass DashedDecoration extends Decoration {\n  /// Creates a box decoration.\n  ///\n  /// * If [color] is null, this decoration does not paint a background color.\n  /// * If [image] is null, this decoration does not paint a background image.\n  /// * If [border] is null, this decoration does not paint a border.\n  /// * If [borderRadius] is null, this decoration uses more efficient background\n  ///   painting commands. The [borderRadius] argument must be null if [shape] is\n  ///   [BoxShape.circle].\n  /// * If [boxShadow] is null, this decoration does not paint a shadow.\n  /// * If [gradient] is null, this decoration does not paint gradients.\n  /// * If [backgroundBlendMode] is null, this decoration paints with [BlendMode.srcOver]\n  ///\n  /// The [shape] argument must not be null.\n  const DashedDecoration({\n    this.color,\n    this.image,\n    this.border,\n    this.borderRadius,\n    this.boxShadow,\n    this.gradient,\n    this.backgroundBlendMode,\n    this.shape = BoxShape.rectangle,\n    this.strokeHeight = 1.0,\n    this.gap = 2.0,\n    this.dashedColor,\n    this.dawDashed = true,\n  });\n\n  /// Creates a copy of this object but with the given fields replaced with the\n  /// values.\n  DashedDecoration copyWith({\n    Color? color,\n    DecorationImage? image,\n    BoxBorder? border,\n    BorderRadiusGeometry? borderRadius,\n    List<BoxShadow>? boxShadow,\n    Gradient? gradient,\n    BlendMode? backgroundBlendMode,\n    BoxShape? shape,\n  }) {\n    return DashedDecoration(\n      color: color ?? this.color,\n      image: image ?? this.image,\n      border: border ?? this.border,\n      borderRadius: borderRadius ?? this.borderRadius,\n      boxShadow: boxShadow ?? this.boxShadow,\n      gradient: gradient ?? this.gradient,\n      backgroundBlendMode: backgroundBlendMode ?? this.backgroundBlendMode,\n      shape: shape ?? this.shape,\n      dashedColor: this.dashedColor,\n    );\n  }\n\n  @override\n  bool debugAssertIsValid() {\n    assert(shape != BoxShape.circle ||\n        borderRadius == null); // Can't have a border radius if you're a circle.\n    return super.debugAssertIsValid();\n  }\n\n  /// The color to fill in the background of the box.\n  ///\n  /// The color is filled into the [shape] of the box (e.g., either a rectangle,\n  /// potentially with a [borderRadius], or a circle).\n  ///\n  /// This is ignored if [gradient] is non-null.\n  ///\n  /// The [color] is drawn under the [image].\n  final Color? color;\n\n  /// An image to paint above the background [color] or [gradient].\n  ///\n  /// If [shape] is [BoxShape.circle] then the image is clipped to the circle's\n  /// boundary; if [borderRadius] is non-null then the image is clipped to the\n  /// given radii.\n  final DecorationImage? image;\n\n  /// A border to draw above the background [color], [gradient], or [image].\n  ///\n  /// Follows the [shape] and [borderRadius].\n  ///\n  /// Use [Border] objects to describe borders that do not depend on the reading\n  /// direction.\n  ///\n  /// Use [BoxBorder] objects to describe borders that should flip their left\n  /// and right edges based on whether the text is being read left-to-right or\n  /// right-to-left.\n  final BoxBorder? border;\n\n  /// If non-null, the corners of this box are rounded by this [BorderRadius].\n  ///\n  /// Applies only to boxes with rectangular shapes; ignored if [shape] is not\n  /// [BoxShape.rectangle].\n  ///\n  /// {@macro flutter.painting.boxDecoration.clip}\n  final BorderRadiusGeometry? borderRadius;\n\n  /// A list of shadows cast by this box behind the box.\n  ///\n  /// The shadow follows the [shape] of the box.\n  ///\n  /// See also:\n  ///\n  ///  * [kElevationToShadow], for some predefined shadows used in Material\n  ///    Design.\n  ///  * [PhysicalModel], a widget for showing shadows.\n  final List<BoxShadow>? boxShadow;\n\n  /// A gradient to use when filling the box.\n  ///\n  /// If this is specified, [color] has no effect.\n  ///\n  /// The [gradient] is drawn under the [image].\n  final Gradient? gradient;\n\n  /// The blend mode applied to the [color] or [gradient] background of the box.\n  ///\n  /// If no [backgroundBlendMode] is provided then the default painting blend\n  /// mode is used.\n  ///\n  /// If no [color] or [gradient] is provided then the blend mode has no impact.\n  final BlendMode? backgroundBlendMode;\n\n  /// The shape to fill the background [color], [gradient], and [image] into and\n  /// to cast as the [boxShadow].\n  ///\n  /// If this is [BoxShape.circle] then [borderRadius] is ignored.\n  ///\n  /// The [shape] cannot be interpolated; animating between two [DashedDecoration]s\n  /// with different [shape]s will result in a discontinuity in the rendering.\n  /// To interpolate between two shapes, consider using [ShapeDecoration] and\n  /// different [ShapeBorder]s; in particular, [CircleBorder] instead of\n  /// [BoxShape.circle] and [RoundedRectangleBorder] instead of\n  /// [BoxShape.rectangle].\n  ///\n  /// {@macro flutter.painting.boxDecoration.clip}\n  final BoxShape? shape;\n\n  final double strokeHeight;\n  final double gap;\n  final Color? dashedColor;\n  final bool? dawDashed;\n\n  @override\n  EdgeInsetsGeometry? get padding => border?.dimensions;\n\n  @override\n  Path getClipPath(Rect rect, TextDirection textDirection) {\n    late Path clipPath;\n    switch (shape) {\n      case BoxShape.circle:\n        clipPath = Path()..addOval(rect);\n        break;\n      case BoxShape.rectangle:\n        if (borderRadius != null) {\n          clipPath = Path()\n            ..addRRect(borderRadius!.resolve(textDirection).toRRect(rect));\n        }\n        break;\n      default:\n        clipPath = Path()..addOval(rect);\n    }\n    return clipPath;\n  }\n\n  /// Returns a box decoration that is scaled by the given factor.\n  DashedDecoration scale(double factor) {\n    return DashedDecoration(\n      color: Color.lerp(null, color, factor)!,\n      image: image,\n// TODO(ianh): fade the image from transparent\n      border: BoxBorder.lerp(null, border, factor),\n      borderRadius: BorderRadiusGeometry.lerp(null, borderRadius, factor),\n      boxShadow: BoxShadow.lerpList(null, boxShadow, factor),\n      gradient: gradient?.scale(factor),\n      shape: shape,\n    );\n  }\n\n  @override\n  bool get isComplex => boxShadow != null;\n\n  @override\n  DashedDecoration? lerpFrom(Decoration? a, double t) {\n    if (a == null) {\n      return scale(t);\n    }\n    if (a is DashedDecoration) {\n      return DashedDecoration.lerp(a, this, t);\n    }\n    return super.lerpFrom(a, t) as DashedDecoration;\n  }\n\n  @override\n  DashedDecoration? lerpTo(Decoration? b, double t) {\n    if (b == null) {\n      return scale(1.0 - t);\n    }\n\n    if (b is DashedDecoration) {\n      return DashedDecoration.lerp(this, b, t);\n    }\n    return super.lerpTo(b, t) as DashedDecoration;\n  }\n\n  /// Linearly interpolate between two box decorations.\n  ///\n  /// Interpolates each parameter of the box decoration separately.\n  ///\n  /// The [shape] is not interpolated. To interpolate the shape, consider using\n  /// a [ShapeDecoration] with different border shapes.\n  ///\n  /// If both values are null, this returns null. Otherwise, it returns a\n  /// non-null value. If one of the values is null, then the result is obtained\n  /// by applying [scale] to the other value. If neither value is null and `t ==\n  /// 0.0`, then `a` is returned unmodified; if `t == 1.0` then `b` is returned\n  /// unmodified. Otherwise, the values are computed by interpolating the\n  /// properties appropriately.\n  ///\n  /// {@macro dart.ui.shadow.lerp}\n  ///\n  /// See also:\n  ///\n  ///  * [Decoration.lerp], which can interpolate between any two types of\n  ///    [Decoration]s, not just [DashedDecoration]s.\n  ///  * [lerpFrom] and [lerpTo], which are used to implement [Decoration.lerp]\n  ///    and which use [DashedDecoration.lerp] when interpolating two\n  ///    [DashedDecoration]s or a [DashedDecoration] to or from null.\n  static DashedDecoration? lerp(\n      DashedDecoration? a, DashedDecoration? b, double t) {\n    if (a == null && b == null) {\n      return null;\n    }\n    if (a == null) {\n      return b!.scale(t);\n    }\n    if (b == null) {\n      return a.scale(1.0 - t);\n    }\n    if (t == 0.0) {\n      return a;\n    }\n    if (t == 1.0) {\n      return b;\n    }\n    return DashedDecoration(\n      color: Color.lerp(a.color, b.color, t)!,\n      image: t < 0.5 ? a.image : b.image,\n// TODO(ianh): cross-fade the image\n      border: BoxBorder.lerp(a.border, b.border, t),\n      borderRadius:\n          BorderRadiusGeometry.lerp(a.borderRadius, b.borderRadius, t),\n      boxShadow: BoxShadow.lerpList(a.boxShadow, b.boxShadow, t),\n      gradient: Gradient.lerp(a.gradient, b.gradient, t),\n      shape: t < 0.5 ? a.shape : b.shape,\n    );\n  }\n\n  @override\n  bool operator ==(Object other) {\n    if (identical(this, other)) {\n      return true;\n    }\n    if (other.runtimeType != runtimeType) {\n      return false;\n    }\n    return other is DashedDecoration &&\n        other.color == color &&\n        other.image == image &&\n        other.border == border &&\n        other.borderRadius == borderRadius &&\n        other.boxShadow == boxShadow &&\n        other.gradient == gradient &&\n        other.shape == shape;\n  }\n\n  @override\n  int get hashCode {\n    return hashValues(\n      color,\n      image,\n      border,\n      borderRadius,\n      boxShadow,\n      gradient,\n      shape,\n    );\n  }\n\n  @override\n  void debugFillProperties(DiagnosticPropertiesBuilder properties) {\n    super.debugFillProperties(properties);\n    properties\n      ..defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.whitespace\n      ..emptyBodyDescription = '<no decorations specified>';\n\n    properties.add(ColorProperty('color', color, defaultValue: null));\n    properties.add(DiagnosticsProperty<DecorationImage>('image', image,\n        defaultValue: null));\n    properties.add(\n        DiagnosticsProperty<BoxBorder>('border', border, defaultValue: null));\n    properties.add(DiagnosticsProperty<BorderRadiusGeometry>(\n        'borderRadius', borderRadius,\n        defaultValue: null));\n    properties.add(IterableProperty<BoxShadow>('boxShadow', boxShadow,\n        defaultValue: null, style: DiagnosticsTreeStyle.whitespace));\n    properties.add(DiagnosticsProperty<Gradient>('gradient', gradient,\n        defaultValue: null));\n    properties.add(EnumProperty<BoxShape>('shape', shape,\n        defaultValue: BoxShape.rectangle));\n  }\n\n  @override\n  bool hitTest(Size size, Offset position, {TextDirection? textDirection}) {\n    assert(shape != null);\n    assert((Offset.zero & size).contains(position));\n    switch (shape) {\n      case BoxShape.rectangle:\n        if (borderRadius != null) {\n          final RRect bounds =\n              borderRadius!.resolve(textDirection).toRRect(Offset.zero & size);\n          return bounds.contains(position);\n        }\n        return true;\n      case BoxShape.circle:\n// Circles are inscribed into our smallest dimension.\n        final Offset center = size.center(Offset.zero);\n        final double distance = (position - center).distance;\n        return distance <= math.min(size.width, size.height) / 2.0;\n      default:\n        return false;\n    }\n  }\n\n  @override\n  _BoxDecorationPainter createBoxPainter([VoidCallback? onChanged]) {\n    assert(onChanged != null || image == null);\n    return _BoxDecorationPainter(this, onChanged);\n  }\n}\n\n/// An object that paints a [DashedDecoration] into a canvas.\nclass _BoxDecorationPainter extends BoxPainter {\n  _BoxDecorationPainter(this._decoration, VoidCallback? onChanged)\n      : super(onChanged);\n\n  final DashedDecoration _decoration;\n\n  Paint? _cachedBackgroundPaint;\n  Rect? _rectForCachedBackgroundPaint;\n\n  Paint _getBackgroundPaint(Rect rect, TextDirection textDirection) {\n    assert(\n        _decoration.gradient != null || _rectForCachedBackgroundPaint == null);\n\n    if (_cachedBackgroundPaint == null ||\n        (_decoration.gradient != null &&\n            _rectForCachedBackgroundPaint != rect)) {\n      final Paint paint = Paint();\n      if (_decoration.backgroundBlendMode != null) {\n        paint.blendMode = _decoration.backgroundBlendMode!;\n      }\n      if (_decoration.color != null) {\n        paint.color = _decoration.color!;\n      }\n      if (_decoration.gradient != null) {\n        paint.shader = _decoration.gradient!\n            .createShader(rect, textDirection: textDirection);\n        _rectForCachedBackgroundPaint = rect;\n      }\n      _cachedBackgroundPaint = paint;\n    }\n\n    return _cachedBackgroundPaint!;\n  }\n\n  void _paintBox(\n      Canvas canvas, Rect rect, Paint paint, TextDirection textDirection) {\n    switch (_decoration.shape) {\n      case BoxShape.circle:\n        assert(_decoration.borderRadius == null);\n        final Offset center = rect.center;\n        final double radius = rect.shortestSide / 2.0;\n        canvas.drawCircle(center, radius, paint);\n        break;\n      case BoxShape.rectangle:\n        if (_decoration.borderRadius == null) {\n          canvas.drawRect(rect, paint);\n        } else {\n          canvas.drawRRect(\n              _decoration.borderRadius!.resolve(textDirection).toRRect(rect),\n              paint);\n        }\n        break;\n      default:\n        return;\n    }\n  }\n\n  void _paintShadows(Canvas canvas, Rect rect, TextDirection textDirection) {\n    if (_decoration.boxShadow == null) {\n      return;\n    }\n    for (final BoxShadow boxShadow in _decoration.boxShadow!) {\n      final Paint paint = boxShadow.toPaint();\n      final Rect bounds =\n          rect.shift(boxShadow.offset).inflate(boxShadow.spreadRadius);\n      _paintBox(canvas, bounds, paint, textDirection);\n    }\n  }\n\n  void _paintBackgroundColor(\n      Canvas canvas, Rect rect, TextDirection textDirection) {\n    if (_decoration.color != null || _decoration.gradient != null) {\n      _paintBox(canvas, rect, _getBackgroundPaint(rect, textDirection),\n          textDirection);\n    }\n  }\n\n  DecorationImagePainter? _imagePainter;\n\n  void _paintBackgroundImage(\n      Canvas canvas, Rect rect, ImageConfiguration configuration) {\n    if (_decoration.image == null) {\n      return;\n    }\n    _imagePainter ??= _decoration.image!.createPainter(onChanged!);\n    Path? clipPath;\n    switch (_decoration.shape) {\n      case BoxShape.circle:\n        clipPath = Path()..addOval(rect);\n        break;\n      case BoxShape.rectangle:\n        if (_decoration.borderRadius != null) {\n          clipPath = Path()\n            ..addRRect(_decoration.borderRadius!\n                .resolve(configuration.textDirection)\n                .toRRect(rect));\n        }\n        break;\n      default:\n        return;\n    }\n    _imagePainter!.paint(canvas, rect, clipPath, configuration);\n  }\n\n  @override\n  void dispose() {\n    _imagePainter?.dispose();\n    super.dispose();\n  }\n\n  /// Paint the box decoration into the given location on the given canvas\n  @override\n  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {\n    assert(configuration.size != null);\n    final Rect rect = offset & configuration.size!;\n    final TextDirection textDirection = configuration.textDirection!;\n    _paintShadows(canvas, rect, textDirection);\n    _paintBackgroundColor(canvas, rect, textDirection);\n    _paintBackgroundImage(canvas, rect, configuration);\n    if (_decoration.dawDashed != null && !(_decoration.dawDashed ?? true)) {\n      _decoration.border?.paint(\n        canvas,\n        rect,\n        shape: _decoration.shape!,\n        borderRadius: _decoration.borderRadius as BorderRadius,\n        textDirection: configuration.textDirection,\n      );\n      return;\n    }\n    final Paint dashedPaint = Paint()\n      ..color = _decoration.dashedColor!\n      ..strokeWidth = _decoration.strokeHeight\n      ..style = PaintingStyle.stroke;\n\n    final Path _topPath = getDashedPath(\n      a: math.Point<double>(rect.topLeft.dx, rect.topLeft.dy),\n      b: math.Point<double>(rect.topRight.dx, rect.topRight.dy),\n      gap: _decoration.gap,\n    );\n\n    final Path _rightPath = getDashedPath(\n      a: math.Point<double>(rect.topRight.dx, rect.topRight.dy),\n      b: math.Point<double>(rect.bottomRight.dx, rect.bottomRight.dy),\n      gap: _decoration.gap,\n    );\n\n    final Path _bottomPath = getDashedPath(\n      a: math.Point<double>(rect.bottomLeft.dx, rect.bottomLeft.dy),\n      b: math.Point<double>(rect.bottomRight.dx, rect.bottomRight.dy),\n      gap: _decoration.gap,\n    );\n\n    final Path _leftPath = getDashedPath(\n      a: math.Point<double>(rect.topLeft.dx, rect.topLeft.dy),\n      b: math.Point<double>(rect.bottomLeft.dx, rect.bottomLeft.dy),\n      gap: _decoration.gap,\n    );\n\n    canvas.drawPath(_topPath, dashedPaint);\n    canvas.drawPath(_rightPath, dashedPaint);\n    canvas.drawPath(_bottomPath, dashedPaint);\n    canvas.drawPath(_leftPath, dashedPaint);\n//    }\n  }\n\n  Path getDashedPath({\n    required math.Point<double> a,\n    required math.Point<double> b,\n    required num gap,\n  }) {\n    final Size size = Size(b.x - a.x, b.y - a.y);\n    final Path path = Path();\n    path.moveTo(a.x, a.y);\n    bool shouldDraw = true;\n    math.Point<double> currentPoint = math.Point<double>(a.x, a.y);\n\n    final num radians = math.atan(size.height / size.width);\n\n    final num dx = math.cos(radians) * gap < 0\n        ? math.cos(radians) * gap * -1\n        : math.cos(radians) * gap;\n\n    final num dy = math.sin(radians) * gap < 0\n        ? math.sin(radians) * gap * -1\n        : math.sin(radians) * gap;\n\n    while (currentPoint.x <= b.x && currentPoint.y <= b.y) {\n      shouldDraw\n          ? path.lineTo(currentPoint.x, currentPoint.y)\n          : path.moveTo(currentPoint.x, currentPoint.y);\n      shouldDraw = !shouldDraw;\n      currentPoint = math.Point<double>(\n        currentPoint.x + dx,\n        currentPoint.y + dy,\n      );\n    }\n    return path;\n  }\n\n  bool shouldRepaint(CustomPainter oldDelegate) {\n    return true;\n  }\n\n  @override\n  String toString() {\n    return 'BoxPainter for $_decoration';\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/widget/fps_chart.dart",
    "content": "import 'dart:math';\n\nimport 'package:dokit/kit/kit.dart';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\n\nclass BarChartPainter extends CustomPainter {\n  BarChartPainter({required this.datas});\n\n  List<IInfo> datas;\n\n  @override\n  bool shouldRepaint(BarChartPainter oldDelegate) => true;\n\n  @override\n  bool shouldRebuildSemantics(BarChartPainter oldDelegate) => false;\n\n  void _drawAxis(Canvas canvas, Size size) {\n    final double sw = size.width;\n    final double sh = size.height;\n\n    // 使用 Paint 定义路径的样式\n    final Paint paint = Paint()\n      ..color = const Color(0xffdddddd)\n      ..style = PaintingStyle.stroke\n      ..strokeWidth = 0.5;\n\n    // 使用 Path 定义绘制的路径，从画布的左上角到左下角在到右下角\n    final Path path = Path()\n      ..moveTo(0, 0)\n      ..lineTo(0, sh)\n      ..lineTo(sw, sh);\n\n    // 使用 drawPath 方法绘制路径\n    canvas.drawPath(path, paint);\n  }\n\n  void _drawLabels(Canvas canvas, Size size) {\n    const double labelFontSize = 10;\n    final double sh = size.height;\n    final List<double> yAxisLabels = <double>[];\n\n    yAxisLabels.add(16);\n    yAxisLabels.add(32);\n    yAxisLabels.add(50);\n    yAxisLabels.add(100);\n\n    yAxisLabels.asMap().forEach(\n      (int index, double label) {\n        // 标识的高度为画布高度减去标识的值\n        final double top = sh - label * 2.5;\n        // ignore: unused_local_variable\n        final Rect rect = Rect.fromLTWH(0, top, 4, 1);\n        final Offset textOffset = Offset(\n          0 - (label.toInt().toString().length == 3 ? 24 : 20).toDouble(),\n          top - labelFontSize / 2,\n        );\n\n        // 绘制文字需要用 `TextPainter`，最后调用 paint 方法绘制文字\n        TextPainter(\n          text: TextSpan(\n            text: label.toStringAsFixed(0),\n            style: const TextStyle(\n                fontSize: labelFontSize, color: Color(0xff4a4b5b)),\n          ),\n          textAlign: TextAlign.right,\n          textDirection: TextDirection.ltr,\n          textWidthBasis: TextWidthBasis.longestLine,\n        )\n          ..layout(minWidth: 0, maxWidth: 24)\n          ..paint(canvas, textOffset);\n      },\n    );\n  }\n\n  void _drawBars(Canvas canvas, Size size) {\n    final double sh = size.height;\n    final Paint paint = Paint()..style = PaintingStyle.fill;\n    const double marginLeft = 7.5;\n    const double _barWidth = 2.5;\n    final double maxVisibleSize = (size.width - marginLeft) / 2.5;\n    if (datas.length > maxVisibleSize.toInt()) {\n      datas = datas.sublist(datas.length - maxVisibleSize.toInt());\n    }\n    const double _barGap = 0;\n    for (int i = 0; i < datas.length; i++) {\n      int value = datas[i].getValue() as int;\n      value = min(value, 110);\n      paint.color = value <= 16\n          ? const Color(0xff55a8fd)\n          : value < 50\n              ? const Color(0xfffad337)\n              : const Color(0xffd0607e);\n      // 矩形的上边缘为画布高度减去数据值\n      final double top = sh - value * 2.5;\n      // 矩形的左边缘为当前索引值乘以矩形宽度加上矩形之间的间距\n      final double left = marginLeft + i * _barWidth + (i * _barGap) + _barGap;\n\n      // 使用 Rect.fromLTWH 方法创建要绘制的矩形\n      final Rect rect =\n          Rect.fromLTWH(left, top, _barWidth, value * 2.5.toDouble());\n      // 使用 drawRect 方法绘制矩形\n      canvas.drawRect(rect, paint);\n    }\n  }\n\n  @override\n  void paint(Canvas canvas, Size size) {\n    _drawAxis(canvas, size);\n    _drawLabels(canvas, size);\n    _drawBars(canvas, size);\n  }\n}\n\nclass FpsBarChart extends StatefulWidget {\n  const FpsBarChart({\n    required this.data,\n  });\n\n  final List<IInfo> data;\n\n  @override\n  _FpsBarChartState createState() => _FpsBarChartState();\n}\n\nclass _FpsBarChartState extends State<FpsBarChart>\n    with TickerProviderStateMixin {\n  @override\n  Widget build(BuildContext context) {\n    final double width =\n        MediaQuery.of(context).orientation == Orientation.portrait\n            ? MediaQuery.of(context).size.width - 56\n            : MediaQuery.of(context).size.width - 30;\n    final double height =\n        MediaQuery.of(context).orientation == Orientation.portrait\n            ? MediaQuery.of(context).size.height - 200 - 140\n            : MediaQuery.of(context).size.height - 200;\n    return Column(\n      mainAxisAlignment: MainAxisAlignment.center,\n      children: <Widget>[\n        Container(\n          margin: const EdgeInsets.only(top: 40, left: 24),\n          child: CustomPaint(\n            painter: BarChartPainter(datas: widget.data),\n            child: Container(\n              width: width,\n              height: height,\n            ),\n          ),\n        )\n      ],\n    );\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/widget/source_code/source_code_view.dart",
    "content": "import 'package:dokit/widget/source_code/syntax_highlighter.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:google_fonts/google_fonts.dart';\n\nclass SourceCodeView extends StatelessWidget {\n  final String sourceCode;\n  final double _textScaleFactor = 1.0;\n\n  const SourceCodeView({Key? key, required this.sourceCode}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return _getCodeView(sourceCode, context);\n  }\n\n  Widget _getCodeView(String codeContent, BuildContext context) {\n    codeContent = codeContent.replaceAll('\\r\\n', '\\n');\n    final SyntaxHighlighterStyle style =\n        Theme.of(context).brightness == Brightness.dark\n            ? SyntaxHighlighterStyle.darkThemeStyle()\n            : SyntaxHighlighterStyle.lightThemeStyle();\n    return Container(\n      constraints: BoxConstraints.expand(),\n      child: Scrollbar(\n        child: SingleChildScrollView(\n          scrollDirection: Axis.horizontal,\n          child: SelectableText.rich(\n            TextSpan(\n              style: GoogleFonts.droidSansMono(fontSize: 12)\n                  .apply(fontSizeFactor: this._textScaleFactor),\n              children: <TextSpan>[\n                DartSyntaxHighlighter(style).format(codeContent)\n              ],\n            ),\n            style: DefaultTextStyle.of(context)\n                .style\n                .apply(fontSizeFactor: this._textScaleFactor),\n          ),\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/widget/source_code/syntax_highlighter.dart",
    "content": "// Copyright 2016 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport 'package:flutter/material.dart';\nimport 'package:string_scanner/string_scanner.dart';\n\nclass SyntaxHighlighterStyle {\n  SyntaxHighlighterStyle(\n      {required this.baseStyle,\n      required this.numberStyle,\n      required this.commentStyle,\n      required this.keywordStyle,\n      required this.stringStyle,\n      required this.punctuationStyle,\n      required this.classStyle,\n      required this.constantStyle});\n\n  static SyntaxHighlighterStyle lightThemeStyle() {\n    return SyntaxHighlighterStyle(\n        baseStyle: const TextStyle(color: const Color(0xFF000000)),\n        numberStyle: const TextStyle(color: const Color(0xFF1565C0)),\n        commentStyle: const TextStyle(color: const Color(0xFF9E9E9E)),\n        keywordStyle: const TextStyle(color: const Color(0xFF9C27B0)),\n        stringStyle: const TextStyle(color: const Color(0xFF43A047)),\n        punctuationStyle: const TextStyle(color: const Color(0xFF000000)),\n        classStyle: const TextStyle(color: const Color(0xFF512DA8)),\n        constantStyle: const TextStyle(color: const Color(0xFF795548)));\n  }\n\n  static SyntaxHighlighterStyle darkThemeStyle() {\n    return SyntaxHighlighterStyle(\n        baseStyle: const TextStyle(color: const Color(0xFFFFFFFF)),\n        numberStyle: const TextStyle(color: const Color(0xFF1565C0)),\n        commentStyle: const TextStyle(color: const Color(0xFF9E9E9E)),\n        keywordStyle: const TextStyle(color: const Color(0xFF80CBC4)),\n        stringStyle: const TextStyle(color: const Color(0xFF009688)),\n        punctuationStyle: const TextStyle(color: const Color(0xFFFFFFFF)),\n        classStyle: const TextStyle(color: const Color(0xFF009688)),\n        constantStyle: const TextStyle(color: const Color(0xFF795548)));\n  }\n\n  final TextStyle baseStyle;\n  final TextStyle numberStyle;\n  final TextStyle commentStyle;\n  final TextStyle keywordStyle;\n  final TextStyle stringStyle;\n  final TextStyle punctuationStyle;\n  final TextStyle classStyle;\n  final TextStyle constantStyle;\n}\n\nabstract class SyntaxHighlighter {\n  // ignore: one_member_abstracts\n  TextSpan format(String src);\n}\n\nclass DartSyntaxHighlighter extends SyntaxHighlighter {\n  DartSyntaxHighlighter([this._style]) {\n    _spans = <_HighlightSpan>[];\n    _style ??= SyntaxHighlighterStyle.darkThemeStyle();\n  }\n\n  SyntaxHighlighterStyle? _style;\n\n  static const List<String> _keywords = const <String>[\n    'abstract',\n    'as',\n    'assert',\n    'async',\n    'await',\n    'break',\n    'case',\n    'catch',\n    'class',\n    'const',\n    'continue',\n    'default',\n    'deferred',\n    'do',\n    'dynamic',\n    'else',\n    'enum',\n    'export',\n    'external',\n    'extends',\n    'factory',\n    'false',\n    'final',\n    'finally',\n    'for',\n    'get',\n    'if',\n    'implements',\n    'import',\n    'in',\n    'is',\n    'library',\n    'new',\n    'null',\n    'operator',\n    'part',\n    'rethrow',\n    'return',\n    'set',\n    'static',\n    'super',\n    'switch',\n    'sync',\n    'this',\n    'throw',\n    'true',\n    'try',\n    'typedef',\n    'var',\n    'void',\n    'while',\n    'with',\n    'yield'\n  ];\n\n  static const List<String> _builtInTypes = const <String>[\n    'int',\n    'double',\n    'num',\n    'bool'\n  ];\n\n  late String _src;\n  late StringScanner _scanner;\n\n  late List<_HighlightSpan> _spans;\n\n  @override\n  TextSpan format(String src) {\n    _src = src;\n    _scanner = StringScanner(_src);\n\n    if (_generateSpans()) {\n      // Successfully parsed the code\n      final List<TextSpan> formattedText = <TextSpan>[];\n      int currentPosition = 0;\n\n      for (_HighlightSpan span in _spans) {\n        if (currentPosition != span.start) {\n          formattedText\n              .add(TextSpan(text: _src.substring(currentPosition, span.start)));\n        }\n\n        formattedText.add(TextSpan(\n            style: span.textStyle(_style!), text: span.textForSpan(_src)));\n\n        currentPosition = span.end;\n      }\n\n      if (currentPosition != _src.length) {\n        formattedText\n            .add(TextSpan(text: _src.substring(currentPosition, _src.length)));\n      }\n\n      return TextSpan(style: _style!.baseStyle, children: formattedText);\n    } else {\n      // Parsing failed, return with only basic formatting\n      return TextSpan(style: _style!.baseStyle, text: src);\n    }\n  }\n\n  bool _generateSpans() {\n    int lastLoopPosition = _scanner.position;\n\n    while (!_scanner.isDone) {\n      // Skip White space\n      _scanner.scan(RegExp(r'\\s+'));\n\n      // Block comments\n      if (_scanner.scan(RegExp(r'/\\*(.|\\n)*\\*/'))) {\n        _spans.add(_HighlightSpan(_HighlightType.comment,\n            _scanner.lastMatch!.start, _scanner.lastMatch!.end));\n        continue;\n      }\n\n      // Line comments\n      if (_scanner.scan('//')) {\n        final int startComment = _scanner.lastMatch!.start;\n\n        bool eof = false;\n        int endComment;\n        if (_scanner.scan(RegExp(r'.*\\n'))) {\n          endComment = _scanner.lastMatch!.end - 1;\n        } else {\n          eof = true;\n          endComment = _src.length;\n        }\n\n        _spans.add(\n            _HighlightSpan(_HighlightType.comment, startComment, endComment));\n\n        if (eof) break;\n\n        continue;\n      }\n\n      // Raw r\"String\"\n      if (_scanner.scan(RegExp(r'r\".*\"'))) {\n        _spans.add(_HighlightSpan(_HighlightType.string,\n            _scanner.lastMatch!.start, _scanner.lastMatch!.end));\n        continue;\n      }\n\n      // Raw r'String'\n      if (_scanner.scan(RegExp(r\"r'.*'\"))) {\n        _spans.add(_HighlightSpan(_HighlightType.string,\n            _scanner.lastMatch!.start, _scanner.lastMatch!.end));\n        continue;\n      }\n\n      // Multiline \"\"\"String\"\"\"\n      if (_scanner.scan(RegExp(r'\"\"\"(?:[^\"\\\\]|\\\\(.|\\n))*\"\"\"'))) {\n        _spans.add(_HighlightSpan(_HighlightType.string,\n            _scanner.lastMatch!.start, _scanner.lastMatch!.end));\n        continue;\n      }\n\n      // Multiline '''String'''\n      if (_scanner.scan(RegExp(r\"'''(?:[^'\\\\]|\\\\(.|\\n))*'''\"))) {\n        _spans.add(_HighlightSpan(_HighlightType.string,\n            _scanner.lastMatch!.start, _scanner.lastMatch!.end));\n        continue;\n      }\n\n      // \"String\"\n      if (_scanner.scan(RegExp(r'\"(?:[^\"\\\\]|\\\\.)*\"'))) {\n        _spans.add(_HighlightSpan(_HighlightType.string,\n            _scanner.lastMatch!.start, _scanner.lastMatch!.end));\n        continue;\n      }\n\n      // 'String'\n      if (_scanner.scan(RegExp(r\"'(?:[^'\\\\]|\\\\.)*'\"))) {\n        _spans.add(_HighlightSpan(_HighlightType.string,\n            _scanner.lastMatch!.start, _scanner.lastMatch!.end));\n        continue;\n      }\n\n      // Double\n      if (_scanner.scan(RegExp(r'\\d+\\.\\d+'))) {\n        _spans.add(_HighlightSpan(_HighlightType.number,\n            _scanner.lastMatch!.start, _scanner.lastMatch!.end));\n        continue;\n      }\n\n      // Integer\n      if (_scanner.scan(RegExp(r'\\d+'))) {\n        _spans.add(_HighlightSpan(_HighlightType.number,\n            _scanner.lastMatch!.start, _scanner.lastMatch!.end));\n        continue;\n      }\n\n      // Punctuation\n      if (_scanner.scan(RegExp(r'[\\[\\]{}().!=<>&\\|\\?\\+\\-\\*/%\\^~;:,]'))) {\n        _spans.add(_HighlightSpan(_HighlightType.punctuation,\n            _scanner.lastMatch!.start, _scanner.lastMatch!.end));\n        continue;\n      }\n\n      // Meta data\n      if (_scanner.scan(RegExp(r'@\\w+'))) {\n        _spans.add(_HighlightSpan(_HighlightType.keyword,\n            _scanner.lastMatch!.start, _scanner.lastMatch!.end));\n        continue;\n      }\n\n      // Words\n      if (_scanner.scan(RegExp(r'\\w+'))) {\n        _HighlightType? type;\n\n        String word = _scanner.lastMatch![0]!;\n        if (word.startsWith('_')) word = word.substring(1);\n\n        if (_keywords.contains(word)) {\n          type = _HighlightType.keyword;\n        } else if (_builtInTypes.contains(word)) {\n          type = _HighlightType.keyword;\n        } else if (_firstLetterIsUpperCase(word)) {\n          type = _HighlightType.klass;\n        } else if (word.length >= 2 &&\n            word.startsWith('k') &&\n            _firstLetterIsUpperCase(word.substring(1))) {\n          type = _HighlightType.constant;\n        }\n\n        if (type != null) {\n          _spans.add(_HighlightSpan(\n              type, _scanner.lastMatch!.start, _scanner.lastMatch!.end));\n        }\n      }\n\n      // Check if this loop did anything\n      if (lastLoopPosition == _scanner.position) {\n        // Failed to parse this file, abort gracefully\n        return false;\n      }\n      lastLoopPosition = _scanner.position;\n    }\n\n    _simplify();\n    return true;\n  }\n\n  void _simplify() {\n    for (int i = _spans.length - 2; i >= 0; i -= 1) {\n      if (_spans[i].type == _spans[i + 1].type &&\n          _spans[i].end == _spans[i + 1].start) {\n        _spans[i] =\n            _HighlightSpan(_spans[i].type, _spans[i].start, _spans[i + 1].end);\n        _spans.removeAt(i + 1);\n      }\n    }\n  }\n\n  bool _firstLetterIsUpperCase(String str) {\n    if (str.isNotEmpty) {\n      final String first = str.substring(0, 1);\n      return first == first.toUpperCase();\n    }\n    return false;\n  }\n}\n\nenum _HighlightType {\n  number,\n  comment,\n  keyword,\n  string,\n  punctuation,\n  klass,\n  constant\n}\n\nclass _HighlightSpan {\n  _HighlightSpan(this.type, this.start, this.end);\n  final _HighlightType type;\n  final int start;\n  final int end;\n\n  String textForSpan(String src) {\n    return src.substring(start, end);\n  }\n\n  TextStyle textStyle(SyntaxHighlighterStyle style) {\n    if (type == _HighlightType.number) {\n      return style.numberStyle;\n    } else if (type == _HighlightType.comment) {\n      return style.commentStyle;\n    } else if (type == _HighlightType.keyword) {\n      return style.keywordStyle;\n    } else if (type == _HighlightType.string) {\n      return style.stringStyle;\n    } else if (type == _HighlightType.punctuation) {\n      return style.punctuationStyle;\n    } else if (type == _HighlightType.klass) {\n      return style.classStyle;\n    } else if (type == _HighlightType.constant) {\n      return style.constantStyle;\n    } else {\n      return style.baseStyle;\n    }\n  }\n}\n"
  },
  {
    "path": "Flutter/lib/widget/widget_build_chain/widget_build_chain_page.dart",
    "content": "// Copyright© Dokit for Flutter.\n//\n// widget_build_chain_page.dart\n// Flutter\n//\n// Created by linusflow on 2021/4/30\n// Modified by linusflow on 2021/5/11 下午4:50\n//\n\nimport 'dart:math';\n\nimport 'package:dokit/ui/dokit_app.dart';\nimport 'package:dokit/widget/widget_build_chain/widget_details_page.dart';\nimport 'package:flutter/material.dart';\n\nclass WidgetBuildChainController {\n  OverlayEntry? _overlayEntry;\n  final Element element;\n\n  WidgetBuildChainController(this.element);\n\n  void show() {\n    if (_overlayEntry != null) {\n      return;\n    }\n    _overlayEntry = OverlayEntry(builder: (BuildContext context) {\n      return WidgetBuildChainPage(element: element, controller: this);\n    });\n    final rootOverlay = doKitOverlayKey.currentState;\n    assert(rootOverlay != null);\n    rootOverlay!.insert(_overlayEntry!);\n  }\n\n  void remove() {\n    _overlayEntry?.remove();\n    _overlayEntry = null;\n  }\n}\n\nclass WidgetBuildChainPage extends StatefulWidget {\n  final Element element;\n  final WidgetBuildChainController controller;\n\n  WidgetBuildChainPage(\n      {Key? key, required this.element, required this.controller})\n      : super(key: key);\n\n  @override\n  State<StatefulWidget> createState() => _WidgetBuildChainPageState();\n}\n\nclass _WidgetBuildChainPageState extends State<WidgetBuildChainPage> {\n  late FocusNode _focusNode;\n  late TextEditingController _queryTextController;\n  List<Element>? _buildChainWidgets;\n  List<Element> get buildChainWidgets {\n    _buildChainWidgets ??= widget.element.debugGetDiagnosticChain();\n    return _buildChainWidgets!;\n  }\n\n  late List<CellBean> cells;\n  String? queryString;\n  List<CellBean> get filterCells => cells\n      .where((e) =>\n          e.title.toLowerCase().contains(queryString?.toLowerCase() ?? ''))\n      .toList();\n\n  final cacheExtent = 44.0;\n\n  final cellColors = const [\n    Colors.blue,\n    Colors.red,\n    Colors.amber,\n    Colors.brown,\n    Colors.cyanAccent,\n    Colors.green,\n    Colors.purple,\n    Colors.yellow,\n  ];\n  Color? _lastCellLeadingColor;\n  Color get cellLeadingColor {\n    Color diffColor;\n    do {\n      final index = Random().nextInt(cellColors.length - 1);\n      diffColor = cellColors[index];\n    } while (diffColor == _lastCellLeadingColor);\n    _lastCellLeadingColor = diffColor;\n\n    return diffColor;\n  }\n\n  @override\n  void initState() {\n    super.initState();\n\n    _focusNode = FocusNode();\n    _queryTextController = TextEditingController();\n    cells = buildChainWidgets\n        .map((e) =>\n            CellBean(cellLeadingColor, e.toStringShort().split('-').first, e))\n        .toList();\n  }\n\n  @override\n  void dispose() {\n    _focusNode.dispose();\n    _queryTextController.dispose();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      home: GestureDetector(\n        onTap: () => _focusNode.unfocus(),\n        child: Scaffold(\n          appBar: AppBar(\n            title: RichText(\n              text: TextSpan(children: [\n                TextSpan(\n                    text: 'Widget Build Chain',\n                    style:\n                        TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),\n                WidgetSpan(child: SizedBox(width: 10)),\n                TextSpan(\n                    text: 'depth:${widget.element.depth}',\n                    style:\n                        TextStyle(fontSize: 12, fontWeight: FontWeight.bold)),\n              ]),\n            ),\n            leading: GestureDetector(\n              onTap: () => _onBack(context),\n              child: Icon(\n                Icons.chevron_left,\n                size: 28,\n              ),\n            ),\n          ),\n          body: Container(\n            padding: EdgeInsets.all(12),\n            child: Column(\n              crossAxisAlignment: CrossAxisAlignment.start,\n              children: [\n                TextField(\n                  textInputAction: TextInputAction.done,\n                  keyboardType: TextInputType.text,\n                  controller: _queryTextController,\n                  focusNode: _focusNode,\n                  decoration: InputDecoration(\n                    hintText: '输入需要查看的Widget名称',\n                    hintStyle: TextStyle(color: Colors.grey, fontSize: 14),\n                    prefixIcon: Icon(\n                      Icons.search,\n                      size: 24,\n                    ),\n                  ),\n                  style: TextStyle(\n                    textBaseline: TextBaseline.alphabetic,\n                    fontSize: 14,\n                    color: Colors.black,\n                  ),\n                  onSubmitted: (_) {},\n                  onChanged: _onTextChange,\n                ),\n                SizedBox(height: 8),\n                Text(\n                  '共${filterCells.length}条数据',\n                  style: TextStyle(\n                    fontSize: 10,\n                    color: Colors.grey,\n                  ),\n                ),\n                SizedBox(height: 2),\n                Expanded(\n                  child: ListView.builder(\n                    itemCount: filterCells.length,\n                    cacheExtent: cacheExtent,\n                    itemBuilder: (BuildContext context, int index) {\n                      return GestureDetector(\n                        onTap: () =>\n                            _onCellTap(filterCells[index].element, context),\n                        child: Container(\n                          height: cacheExtent,\n                          child: Row(\n                            crossAxisAlignment: CrossAxisAlignment.center,\n                            children: [\n                              Container(\n                                height: cacheExtent * 0.4,\n                                width: cacheExtent * 0.4,\n                                decoration: BoxDecoration(\n                                  color: filterCells[index].leadingColor,\n                                  borderRadius: BorderRadius.all(\n                                    Radius.circular(cacheExtent * 0.2),\n                                  ),\n                                ),\n                              ),\n                              SizedBox(width: 18),\n                              Expanded(\n                                child: Text(\n                                  '${filterCells[index].title}',\n                                  maxLines: 1,\n                                  overflow: TextOverflow.ellipsis,\n                                ),\n                              ),\n                            ],\n                          ),\n                          decoration: BoxDecoration(\n                            border: Border(\n                              bottom:\n                                  BorderSide(width: 0.5, color: Colors.black12),\n                            ),\n                          ),\n                        ),\n                      );\n                    },\n                  ),\n                ),\n              ],\n            ),\n          ),\n        ),\n      ),\n    );\n  }\n\n  void _onCellTap(Element e, BuildContext context) {\n    Navigator.of(context, rootNavigator: false).push<void>(\n      MaterialPageRoute(\n        builder: (context) {\n          return WidgetDetailsPage(element: e);\n        },\n      ),\n    );\n  }\n\n  void _onTextChange(String query) {\n    setState(() {\n      queryString = query;\n    });\n  }\n\n  void _onBack(BuildContext context) {\n    widget.controller.remove();\n  }\n}\n\nclass CellBean {\n  final Color leadingColor;\n  final String title;\n  final Element element;\n\n  CellBean(this.leadingColor, this.title, this.element);\n}\n"
  },
  {
    "path": "Flutter/lib/widget/widget_build_chain/widget_details_page.dart",
    "content": "// Copyright© Dokit for Flutter.\n//\n// widget_details_page.dart\n// Flutter\n//\n// Created by linusflow on 2021/4/30\n// Modified by linusflow on 2021/5/12 下午3:09\n//\n\nimport 'package:flutter/material.dart';\n\nclass WidgetDetailsPage extends StatelessWidget {\n  final Element element;\n\n  const WidgetDetailsPage({Key? key, required this.element}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        title: RichText(\n          text: TextSpan(children: [\n            TextSpan(\n                text: 'Widget Details',\n                style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),\n            WidgetSpan(child: SizedBox(width: 10)),\n            TextSpan(\n                text: 'depth:${element.depth}',\n                style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold)),\n          ]),\n        ),\n        leading: GestureDetector(\n          onTap: () => _onBack(context),\n          child: Icon(\n            Icons.chevron_left,\n            size: 28,\n          ),\n        ),\n      ),\n      body: Scrollbar(\n        child: SingleChildScrollView(\n          child: SingleChildScrollView(\n            scrollDirection: Axis.horizontal,\n            child: Container(\n              padding: EdgeInsets.all(12),\n              child: Column(\n                crossAxisAlignment: CrossAxisAlignment.start,\n                children: [\n                  Text(\n                    'Widget Overview',\n                    style: TextStyle(\n                      fontSize: 20,\n                      fontWeight: FontWeight.bold,\n                    ),\n                  ),\n                  SizedBox(height: 8),\n                  Divider(\n                    height: 0.5,\n                  ),\n                  SizedBox(height: 10),\n                  Text(\n                    '${element.widget.toStringDeep()}',\n                    style: TextStyle(\n                      fontSize: 12,\n                    ),\n                  ),\n                  SizedBox(height: 16),\n                  Text(\n                    'RenderObject Full Details',\n                    style: TextStyle(\n                      fontSize: 20,\n                      fontWeight: FontWeight.bold,\n                    ),\n                  ),\n                  SizedBox(height: 8),\n                  Divider(\n                    height: 0.5,\n                  ),\n                  SizedBox(height: 16),\n                  Text(\n                    '${element.renderObject?.toStringDeep() ?? '-'}',\n                    style: TextStyle(\n                      fontSize: 12,\n                    ),\n                  ),\n                ],\n              ),\n            ),\n          ),\n        ),\n      ),\n    );\n  }\n\n  void _onBack(BuildContext context) {\n    Navigator.of(context).pop();\n  }\n}\n"
  },
  {
    "path": "Flutter/pubspec.yaml",
    "content": "name: dokit\ndescription: 开发工具集\nversion: 0.8.1-nullsafety.0\nhomepage: https://www.dokit.cn/#/index/home\n\n\nenvironment:\n  sdk: \">=2.12.0 <3.0.0\"\n  flutter: \">=2.0.0\"\n\ndependencies:\n  flutter:\n    sdk: flutter\n  date_format: ^2.0.2\n  vm_service: ^6.2.0\n  package_info: ^2.0.0\n  shared_preferences: ^2.0.5\n  image: ^3.0.2\n  google_fonts: ^2.0.0\n  string_scanner: ^1.0.5\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  pedantic: ^1.9.2\n\n\nflutter:\n  uses-material-design: true\n\n  assets:\n    - images/dokit_ic_close.png\n    - images/dk_frame_hist.png\n    - images/dk_method_channel.png\n    - images/dk_sys_info.png\n    - images/dk_view_check.png\n    - images/dk_log_info.png\n    - images/dk_ram.png\n    - images/dk_view_route.png\n    - images/dk_net_monitor.png\n    - images/dokit_flutter_btn.png\n    - images/dk_channel_check_h.png\n    - images/dk_channel_check_n.png\n    - images/dk_channel_expand_h.png\n    - images/dk_channel_expand_n.png\n    - images/dk_fps_chart.png\n    - images/dk_route_arrow.png\n    - images/dk_memory_search.png\n    - images/dk_color_pick.png\n    - images/dk_source_code.png\n    - images/dk_time_counter.png"
  },
  {
    "path": "Flutter/test/widget_test.dart",
    "content": "// This is a basic Flutter widget test.\n//\n// To perform an interaction with a widget in your test, use the WidgetTester\n// utility that Flutter provides. For example, you can send tap and scroll\n// gestures. You can also use WidgetTester to find child widgets in the widget\n// tree, read text, and verify that the values of widget properties are correct.\n\nimport 'package:flutter/material.dart'; // ignore: unused_import\n\nvoid main() {}\n"
  },
  {
    "path": "LICENSE",
    "content": "                              Apache License\n \n                        Version 2.0, January 2004\n \n                     http://www.apache.org/licenses/\n \n \n \n \nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n \n \n \n \n1. Definitions.\n \n \n \n \n   \"License\" shall mean the terms and conditions for use, reproduction,\n \n   and distribution as defined by Sections 1 through 9 of this document.\n \n \n \n \n   \"Licensor\" shall mean the copyright owner or entity authorized by\n \n   the copyright owner that is granting the License.\n \n \n \n \n   \"Legal Entity\" shall mean the union of the acting entity and all\n \n   other entities that control, are controlled by, or are under common\n \n   control with that entity. For the purposes of this definition,\n \n   \"control\" means (i) the power, direct or indirect, to cause the\n \n   direction or management of such entity, whether by contract or\n \n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n \n   outstanding shares, or (iii) beneficial ownership of such entity.\n \n \n \n \n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n \n   exercising permissions granted by this License.\n \n \n \n \n   \"Source\" form shall mean the preferred form for making modifications,\n \n   including but not limited to software source code, documentation\n \n   source, and configuration files.\n \n \n \n \n   \"Object\" form shall mean any form resulting from mechanical\n \n   transformation or translation of a Source form, including but\n \n   not limited to compiled object code, generated documentation,\n \n   and conversions to other media types.\n \n \n \n \n   \"Work\" shall mean the work of authorship, whether in Source or\n \n   Object form, made available under the License, as indicated by a\n \n   copyright notice that is included in or attached to the work\n \n   (an example is provided in the Appendix below).\n \n \n \n \n   \"Derivative Works\" shall mean any work, whether in Source or Object\n \n   form, that is based on (or derived from) the Work and for which the\n \n   editorial revisions, annotations, elaborations, or other modifications\n \n   represent, as a whole, an original work of authorship. For the purposes\n \n   of this License, Derivative Works shall not include works that remain\n \n   separable from, or merely link (or bind by name) to the interfaces of,\n \n   the Work and Derivative Works thereof.\n \n \n \n \n   \"Contribution\" shall mean any work of authorship, including\n \n   the original version of the Work and any modifications or additions\n \n   to that Work or Derivative Works thereof, that is intentionally\n \n   submitted to Licensor for inclusion in the Work by the copyright owner\n \n   or by an individual or Legal Entity authorized to submit on behalf of\n \n   the copyright owner. For the purposes of this definition, \"submitted\"\n \n   means any form of electronic, verbal, or written communication sent\n \n   to the Licensor or its representatives, including but not limited to\n \n   communication on electronic mailing lists, source code control systems,\n \n   and issue tracking systems that are managed by, or on behalf of, the\n \n   Licensor for the purpose of discussing and improving the Work, but\n \n   excluding communication that is conspicuously marked or otherwise\n \n   designated in writing by the copyright owner as \"Not a Contribution.\"\n \n \n \n \n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n \n   on behalf of whom a Contribution has been received by Licensor and\n \n   subsequently incorporated within the Work.\n \n \n \n \n2. Grant of Copyright License. Subject to the terms and conditions of\n \n   this License, each Contributor hereby grants to You a perpetual,\n \n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n \n   copyright license to reproduce, prepare Derivative Works of,\n \n   publicly display, publicly perform, sublicense, and distribute the\n \n   Work and such Derivative Works in Source or Object form.\n \n \n \n \n3. Grant of Patent License. Subject to the terms and conditions of\n \n   this License, each Contributor hereby grants to You a perpetual,\n \n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n \n   (except as stated in this section) patent license to make, have made,\n \n   use, offer to sell, sell, import, and otherwise transfer the Work,\n \n   where such license applies only to those patent claims licensable\n \n   by such Contributor that are necessarily infringed by their\n \n   Contribution(s) alone or by combination of their Contribution(s)\n \n   with the Work to which such Contribution(s) was submitted. If You\n \n   institute patent litigation against any entity (including a\n \n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n \n   or a Contribution incorporated within the Work constitutes direct\n \n   or contributory patent infringement, then any patent licenses\n \n   granted to You under this License for that Work shall terminate\n \n   as of the date such litigation is filed.\n \n \n \n \n4. Redistribution. You may reproduce and distribute copies of the\n \n   Work or Derivative Works thereof in any medium, with or without\n \n   modifications, and in Source or Object form, provided that You\n \n   meet the following conditions:\n \n \n \n \n   (a) You must give any other recipients of the Work or\n \n       Derivative Works a copy of this License; and\n \n \n \n \n   (b) You must cause any modified files to carry prominent notices\n \n       stating that You changed the files; and\n \n \n \n \n   (c) You must retain, in the Source form of any Derivative Works\n \n       that You distribute, all copyright, patent, trademark, and\n \n       attribution notices from the Source form of the Work,\n \n       excluding those notices that do not pertain to any part of\n \n       the Derivative Works; and\n \n \n \n \n   (d) If the Work includes a \"NOTICE\" text file as part of its\n \n       distribution, then any Derivative Works that You distribute must\n \n       include a readable copy of the attribution notices contained\n \n       within such NOTICE file, excluding those notices that do not\n \n       pertain to any part of the Derivative Works, in at least one\n \n       of the following places: within a NOTICE text file distributed\n \n       as part of the Derivative Works; within the Source form or\n \n       documentation, if provided along with the Derivative Works; or,\n \n       within a display generated by the Derivative Works, if and\n \n       wherever such third-party notices normally appear. The contents\n \n       of the NOTICE file are for informational purposes only and\n \n       do not modify the License. You may add Your own attribution\n \n       notices within Derivative Works that You distribute, alongside\n \n       or as an addendum to the NOTICE text from the Work, provided\n \n       that such additional attribution notices cannot be construed\n \n       as modifying the License.\n \n \n \n \n   You may add Your own copyright statement to Your modifications and\n \n   may provide additional or different license terms and conditions\n \n   for use, reproduction, or distribution of Your modifications, or\n \n   for any such Derivative Works as a whole, provided Your use,\n \n   reproduction, and distribution of the Work otherwise complies with\n \n   the conditions stated in this License.\n \n \n \n \n5. Submission of Contributions. Unless You explicitly state otherwise,\n \n   any Contribution intentionally submitted for inclusion in the Work\n \n   by You to the Licensor shall be under the terms and conditions of\n \n   this License, without any additional terms or conditions.\n \n   Notwithstanding the above, nothing herein shall supersede or modify\n \n   the terms of any separate license agreement you may have executed\n \n   with Licensor regarding such Contributions.\n \n \n \n \n6. Trademarks. This License does not grant permission to use the trade\n \n   names, trademarks, service marks, or product names of the Licensor,\n \n   except as required for reasonable and customary use in describing the\n \n   origin of the Work and reproducing the content of the NOTICE file.\n \n \n \n \n7. Disclaimer of Warranty. Unless required by applicable law or\n \n   agreed to in writing, Licensor provides the Work (and each\n \n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n \n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n \n   implied, including, without limitation, any warranties or conditions\n \n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n \n   PARTICULAR PURPOSE. You are solely responsible for determining the\n \n   appropriateness of using or redistributing the Work and assume any\n \n   risks associated with Your exercise of permissions under this License.\n \n \n \n \n8. Limitation of Liability. In no event and under no legal theory,\n \n   whether in tort (including negligence), contract, or otherwise,\n \n   unless required by applicable law (such as deliberate and grossly\n \n   negligent acts) or agreed to in writing, shall any Contributor be\n \n   liable to You for damages, including any direct, indirect, special,\n \n   incidental, or consequential damages of any character arising as a\n \n   result of this License or out of the use or inability to use the\n \n   Work (including but not limited to damages for loss of goodwill,\n \n   work stoppage, computer failure or malfunction, or any and all\n \n   other commercial damages or losses), even if such Contributor\n \n   has been advised of the possibility of such damages.\n \n \n \n \n9. Accepting Warranty or Additional Liability. While redistributing\n \n   the Work or Derivative Works thereof, You may choose to offer,\n \n   and charge a fee for, acceptance of support, warranty, indemnity,\n \n   or other liability obligations and/or rights consistent with this\n \n   License. However, in accepting such obligations, You may act only\n \n   on Your own behalf and on Your sole responsibility, not on behalf\n \n   of any other Contributor, and only if You agree to indemnify,\n \n   defend, and hold each Contributor harmless for any liability\n \n   incurred by, or claims asserted against, such Contributor by reason\n \n   of your accepting any such warranty or additional liability.\n \n \n \n \nEND OF TERMS AND CONDITIONS\n \n \n \n \nAPPENDIX: How to apply the Apache License to your work.\n \n \n \n \n   To apply the Apache License to your work, attach the following\n \n   boilerplate notice, with the fields enclosed by brackets \"{}\"\n \n   replaced with your own identifying information. (Don't include\n \n   the brackets!)  The text should be enclosed in the appropriate\n \n   comment syntax for the file format. We also recommend that a\n \n   file or class name and description of purpose be included on the\n \n   same \"printed page\" as the copyright notice for easier\n \n   identification within third-party archives.\n \n \n \n \nCopyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n \n \n \n \nLicensed under the Apache License, Version 2.0 (the \"License\");\n \nyou may not use this file except in compliance with the License.\n \nYou may obtain a copy of the License at\n \n \n \n \n    http://www.apache.org/licenses/LICENSE-2.0\n \n \n \n \nUnless required by applicable law or agreed to in writing, software\n \ndistributed under the License is distributed on an \"AS IS\" BASIS,\n \nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n \nSee the License for the specific language governing permissions and\n \nlimitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "- [简介](#简介)\n- [领域生态](#领域生态)\n- [使用手册](#使用手册)\n- [更新日志](#更新日志)\n- [社区活动](#社区活动)\n- [开发背景](#开发背景)\n- [功能模块](#功能模块)\n  - [一、平台工具(www.dokit.cn)](#一平台工具wwwdokitcn)\n  - [二、常用工具](#二常用工具)\n  - [三、性能检测](#三性能检测)\n  - [四、视觉工具](#四视觉工具)\n  - [五、Weex专项工具（CML专项工具）](#五weex专项工具cml专项工具)\n  - [六、支持自定义的业务工具集成到面板中](#六支持自定义的业务工具集成到面板中)\n  - [七、微信小程序专项工具](#七微信小程序专项工具)\n- [相关文档](#相关文档)\n- [微信交流群(一群满员，已开二群)](#一群满员，已开二群)\n- [QQ 交流群](#qq-交流群)\n- [微信公众号](#微信公众号)\n- [项目成员](#项目成员)\n- [使用提醒](#使用提醒)\n- [友情链接](#友情链接)\n- [协议](#协议)\n\n## 简介\n<div align=\"center\">    \n <img src=\"https://pt-starimg.didistatic.com/static/starimg/img/J0WO7tUi9U1616143930629.png\" width = \"250\"  alt=\"DoraemonKit\" align=left />\n <img src=\"https://img.shields.io/github/license/didi/DoraemonKit.svg\" align=left />\n <img src=\"https://img.shields.io/badge/Android-3.5.0-blue.svg\" align=left />\n <img src=\"https://img.shields.io/badge/iOS-3.0.7-yellow.svg\" align=left />\n <img src=\"https://img.shields.io/badge/Flutter-0.6.0-blue.svg\" align=left />\n <img src=\"https://img.shields.io/badge/miniapp-0.0.1-red.svg\" align=left />\n <img src=\"https://img.shields.io/badge/PRs-welcome-brightgreen.svg\" align=left />\n</div>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n\nDoKit诞生于滴滴城运服体验技术部，是一款面向泛前端产品研发全生命周期的效率平台。经过两年的发展，当前DoKit已经发展成了一个相对完整的生态，比如DoKit For Android、DoKit For iOS、DoKit For 小程序、DoKit For Flutter、DoKit For Web。同时我们的项目被BAT以及滴滴、字节、快手、京东等等头部独角兽企业广泛使用并获得良好的口碑。随着dokit.cn平台端的推出，标志着DoKit已经从单纯的效率工具正式进入了效率工具平台的阶段。与此同时我们一直都未停下持续探索的精神，积极地在更多平台领域进行尝试，不给自己设限。我们相信DoKit的未来充满无限可能。\n\n> [English Readme](README_EN.md)\n\n## 领域生态\n\n<div align=\"center\">    \n  <img src=\"https://pt-starimg.didistatic.com/static/starimg/img/WRUdiWDsw71626696344680.jpg\" width = \"900\" alt=\"DoKit 首页效果演示\" align=center />\n</div>\n\n\n## 使用手册\n访问[DoKit官网](https://www.dokit.cn/)，点击\"[使用中心](http://xingyun.xiaojukeji.com/docs/dokit/#/intro)\"。\n\n**温馨提示：当前DoKit的所有功能都只针对Debug环境，Release环境未经过实际验证，所以请大家严格按照官方文档来集成，也不建议大家在Release环境上使用DoKit的任何功能。如果大家一定要在Release环境上使用，请自行进行充分的测试和验证，DoKit官方将不承担任何责任和损失。** \n## 更新日志\n\n- [Android-ReleaseNotes](http://xingyun.xiaojukeji.com/docs/dokit/#/AndroidReleaseNote)\n- [iOS-ReleaseNotes](Doc/iOS-ReleaseNotes.md)\n- [微信小程序-ReleaseNotes](Doc/miniapp-ReleaseNotes.md)\n- [DoKit For Flutter-ReleaseNotes](Flutter/README.md)\n\n## 社区活动\n\n**DoKit调研问卷**\n亲爱的DoKit用户,动动你的小手指参与一下我们的官方调研活动吧。我们极度渴望听到你们的声音:\n\n链接:https://page.juyanwenjuan.com/jy_0CMpJzlu.html\n\n<div align=\"center\">    \n  <img src=\"https://pt-starimg.didistatic.com/static/starimg/img/INLjGkp9wN1597062733948.jpg\" width = \"250\" alt=\"DoKit 首页效果演示\" align=center />\n</div>\n\n\n\n## 开发背景\n\n每一个稍微有点规模的 App，总会自带一些线下的测试功能代码，比如环境切换功能、帧率查看功能等等，这些功能的切换入口往往放在各式各样的入口中，比如一些特殊的手势，双击 statusBar，双击某一个功能区块，或者新建一个 keyWindow 始终至于 App 最上方等等，而且每一个 App 里面的线下附带功能模块很多是相似的，比如帧率查看、内存和 CPU 监控等等，但是现在基本上都是每个 App 都是自己实现了一份，经历了以上的问题之后，DoKit 就有了它存在的意义。\n\nDoKit 是一个功能平台，能够让每一个 App 快速接入一些常用的或者你没有实现的一些辅助开发工具、测试效率工具、视觉辅助工具，而且能够完美在 Doraemon 面板中接入你已经实现的与业务紧密耦合的一些非通有的辅助工具，并搭配我们的[dokit](https://www.dokit.cn)平台，让功能得到延伸，接入方便，便于扩展。\n\n**简单总结**\n\n1、DoKit 能够快速让你的业务测试代码能够在这里统一管理，统一收口；  \n\n2、DoKit 内置很多常用的工具，避免重复实现，一次接入，你将会拥有强大的工具集合；\n\n3、搭配dokit平台，借助[接口Mock](https://www.dokit.cn/#/index/dataMockPage)、[健康体检](https://www.dokit.cn/#/index/checkDataPage)、[文件同步助手](https://www.dokit.cn/#/index/fileSyncPage)、[一机多控]()让你方便和他人协同，极大的提升研发过程中的效率。\n\n\n\n## 功能模块\n\n### 一、平台工具(www.dokit.cn)\n1. **【数据Mock】** App接口Mock解决方案，提供一套基于App网络拦截的接口Mock方案，无需修改代码即可完成对于接口数据的Mock。\n2. **【健康体检】** 一键式操作，整合DoKit多项工具，数据可视化，快速准确定位问题，让你对app的性能了如指掌。\n3. **【文件同步助手】** 通过终端服务，让你的终端空间在平台端完整的展现并提供强大的文件以及数据库操作能力。\n4. **【一机多控】** 主从同步，释放人力，让研发测试效率提升看得见\n\n### 二、常用工具\n\n1. **【App 信息查看】** 快速查看手机信息，App 基础信息、签名相关、权限信息的渠道，避免去手机设置查找或者查看项目源代码的麻烦；\n2. **【开发者选项 Android特有】** 一键跳转开发者选项，避免安卓由于平台差异导致的入口不一致\n3. **【本地语言】** 一键跳转本地语言，避免安卓由于平台差异导致的入口不一致\n4. **【沙盒浏览】** App 内部文件浏览的功能，支持删除和预览, 并且能通过 AirDrop 或者其他分享方式上传到 PC 中，进行更加细致的操作；\n5. **【MockGPS】** App 能定位到全国各地，支持地图地位和手动输入经纬度；\n6. **【H5任意门】** 开发测试同学可以快速输入 H5 页面地址，查看该页面效果；\n7. **【Crash查看】** 方便本地打印出出现 Crash 的堆栈；\n8. **【子线程UI】** 快速定位哪一些 UI 操作在非主线程中进行渲染，避免不必要的问题；（iOS独有）\n9. **【清除本地数据】** 一键删除沙盒中所有数据；\n10. **【NSLog】** 把所有 NSLog 信息打印到UI界面，避免没有开发证书无法调试的尴尬；\n11. **【Lumberjack】** 每一条 CocoaLumberjack 的日志信息，都在在 App 的界面中显示出来，再也不需要导出日志这么麻烦;（iOS独有）\n12. **【DBView】** 通过网页方便快捷的操作应用内数据库，让数据库的调试变得非常优雅;\n13. **【模拟弱网】** 限制网速，模拟弱网环境下App的运行情况;（android独有）\n14. **【JS脚本】** 在指定WebView运行JS脚本。（iOS独有）\n\n### 三、性能检测\n\n1. **【帧率】** App 帧率信息提供波形图查看功能，让帧率监控的趋势更加明显；\n2. **【CPU】** App CPU 使用率信息提供波形图查看功能，让 CPU 监控的趋势更加形象；\n3. **【内存】** App 内存使用量信息提供波形图查看功能，让内存监控的趋势更加鲜明；\n4. **【流量监控】** 拦截 App 内部流量信息，提供波形图展示、流量概要展示、流量列表展示、流量筛选、流量详情，对流量信息统一拦截，成为我们 App 中自带的 \"Charles\"；\n5. **【卡顿】** 锁定 App 出现卡顿的时刻，打印出对应的代码调用堆栈；\n6. **【大图检测】** 通过流量监测，找出所有的大小超标的图片，避免下载大图造成的流量浪费和渲染大图带来的CPU消耗。\n7. **【启动耗时】** 无侵入的统计出App启动过程的总共耗时；\n8. **【UI层级检查】** 检查出每一个页面中层级最深的元素；\n9. **【函数耗时】** 从函数级别分析app性能瓶颈；\n10. **【Load】** 找出所有的Load方法，并给出耗时分析；（iOS独有）\n11. **【内存泄漏】** 找出App中所有的内存泄漏的问题。\n\n### 四、视觉工具\n\n1. **【颜色吸管】** 方便设计师 UI 捉虫的时候，查看每一个组件的颜色值是否设置正确；\n2. **【组件检查】** 可以抓取任意一个UI控件，查看它们的详细信息，包括控件名称、控件位置、背景色、字体颜色、字体大小；\n3. **【对齐标尺】** 参考 Android 系统自带测试工具，能够实时捕获屏幕坐标，并且可以查看组件是否对齐；\n4. **【元素边框线】** 绘制出每一个 UI 组件的边框，对于组件布局有一定的参考意义。\n\n### 五、Weex专项工具（CML专项工具）\n\n1. **【console日志查看】** 方便在端上查看每一个Weex文件中的console日志，提供分级和搜索功能；\n2. **【storage缓存查看】** 将Weex中的storage模块的本地缓存数据可视化展示；\n3. **【容器信息】** 查看每一个打开的Weex页面的基本信息和性能数据；\n4. **【DevTool】** 快速开启Weex DevTool的扫码入口。\n\ntips ： 如果使用我们滴滴优秀的开源跨端方案 [chameleon](https://github.com/didi/chameleon) 也可以集成该工具集合\n\n### 六、支持自定义的业务工具集成到面板中\n\n统一维护和管理所有的测试模块，详见接入手册\n\n### 七、微信小程序专项工具\n\n详见 [Doraemon mini program debugger](https://github.com/didi/DoraemonKit/tree/master/miniapp)\n\n\n\n## 相关文档\n- [iOS 研发助手 DoKit 技术实现（一）](https://www.jianshu.com/p/00763123dbc4)\n- [iOS 研发助手 DoKit 技术实现（二）](https://blog.csdn.net/weixin_33847182/article/details/91472599)\n- [DoKit支持iOS本地crash查看功能](https://juejin.im/post/5d76184ce51d4561d106cc65)\n- [开源组件 DoKit 之 Android 版本技术实现（一）](https://blog.csdn.net/weixin_33711647/article/details/88004429)\n- [开源组件 DoKit 之 Android 版本技术实现（二）](https://blog.csdn.net/weixin_34148456/article/details/88004414)\n- [DoKit支持Activity启动耗时统计方案](https://juejin.im/post/5d70bc3051882571ed61e407)\n- [DoKit 微信小程序SDK对外发布](https://juejin.im/post/5d9bf252518825095c3c5e32)\n- [滴滴DoKit2.0 - 泛前端开发者的百宝箱](https://juejin.im/post/5dc3cdfa51882538d22d5948)\n- [滴滴正式发布开源客户端研发助手 DoKit 3.0，新特性解读](https://juejin.im/post/5e818e29f265da4804697d25)\n- [滴滴DoKit Android核心原理揭秘之函数耗时](https://juejin.im/post/5eba5ce15188256d6f268c98)\n- [滴滴DoKit Android核心原理揭秘之AOP字节码实现](https://juejin.im/post/6891887877613944840)\n\n\n\n## QQ 技术交流群\n\n<div align=\"center\">    \n <img src=\"https://dpubstatic.udache.com/static/dpubimg/z73LRuuFdxTmFLJq8c2qt_WechatIMG483.jpeg\" width = \"225\" height = \"400\" alt=\"QQ 交流群\" align=left />\n</div>\n\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n\n\n## 项目成员\n\n**创始人**\n[yixiangboy(易翔)](https://github.com/yixiangboy)\n**负责人**\n[小枫](https://github.com/bbssyyuui) \n\n**内部核心成员**\n[小枫](https://github.com/bbssyyuui) 、\n[ChasonTang](https://github.com/ChasonTang) 、\n[AdamCaoQAQ](https://github.com/AdamCaoQAQ) 、\n[fangyeqing123](https://github.com/fangyeqing123) 、\n[RealOnlyone](https://github.com/RealOnlyone) 、\n[HeyCFarmer](https://github.com/HeyCFarmer) 、\n[yFeii](https://github.com/yFeii) 、\n[卡布达](https://github.com/kabda) 、\n[maxiee](https://github.com/maxiee) 、\n[zhugeafanti](https://github.com/zhugeafanti)\n\n**贡献者榜单**\n[yixiangboy(易翔)](https://github.com/yixiangboy)、\n[jtsky(金台)](https://github.com/jtsky) 、\n[LinJZong](https://github.com/LinJZong) 、\n[wanglikun7342](https://github.com/wanglikun7342) 、\n[jayconscious](https://github.com/jayconscious) 、\n[jellybean](https://github.com/excitedhaha) 、\n[linusflow](https://github.com/xu984386604) 、\n[wangzhipeng](https://github.com/WangLao100)、\n[BzCoder](https://github.com/BzCoder) 、 \n[changzuozhen](https://github.com/changzuozhen)、\n[momoxiangbei](https://github.com/momoxiangbei)、\n[wenquanlebao](https://github.com/wenquanlebao) 、 \n[hiXgb](https://github.com/hiXgb) 、 \n[Chinnko](https://github.com/Chinnko) 、 \n[y644938647](https://github.com/y644938647) 、\n[wm219](https://github.com/wm219)、 \n[goolong](https://github.com/goolong) 、\n[miracle9312](https://github.com/miracle9312) 、\n[lwhsgz123](https://github.com/lwhsgz123)、\n[huakucha](https://github.com/huakucha) 、\n[HuginChen](https://github.com/HuginChen) 、\n[feng562925462](https://github.com/feng562925462) 、\n[azhon](https://github.com/azhon) 、\n[rex26](https://github.com/rex26) 、\n[csc-EricWu](https://github.com/csc-EricWu) 、\n[xiandanin](https://github.com/xiandanin) 、\n[0xd-cc](https://github.com/0xd-cc) 、\n[k373379320](https://github.com/k373379320) 、\n[fabcz](https://github.com/fabcz) 、\n[y500](https://github.com/y500) 、\n[Knight-ZXW](https://github.com/Knight-ZXW) 、\n[boai](https://github.com/boai) 、\n[klone1127](https://github.com/klone1127) 、\n[DeveloperLY](https://github.com/DeveloperLY) 、\n[sagdragon](https://github.com/sagdragon) 、\n[ccworld1000](https://github.com/ccworld1000) 、\n[HDB-Li](https://github.com/HDB-Li)、\n[yu-jianfeng](https://github.com/yu-jianfeng)、\n[ydlsl](https://github.com/ydlsl)\n\n如何成为外部贡献者？ 提交有意义的PR，并被采纳。\n\n\n## 使用提醒\n因为SDK目前会配合[dokit.cn](http://www.dokit.cn/)平台, 会产生一些网络数据，这些信息我们收集绝不用于任何恶意用途。\n\n**以下为所有涉及到网络请求的部分**\n\n1. 统计有多少用户集成了dokit\n\n    Android : DoraemonStatisticsUtil#uploadUserInfo\n\n    iOS : DoraemonStatisticsUtil#upLoadUserInfo\n\n2. 统计每个内置kit的使用情况\n\n    Android : DataPickManager#realPost \n\n    iOS : DoraemonBuriedPointManager#uploadData\n\n3. 上传健康体检的相关数据\n\n    Android : AppHealthInfoUtil#post\n\n    iOS : DoraemonHealthManager#upLoadData\n\n4. 数据mock的相关网络请求\n\n    Android : NetWorkMockFragment 里涉及到接口mock的相关网络请求\n    \n    iOS : DoraemonMockManager#queryMockData&uploadSaveData\n\n\n敬请各位用户知晓。\n\n\n\n## 友情链接\n1. [Hummer](https://github.com/didi/Hummer)，Hummer 是一套高性能高可用的跨端开发框架，一套代码可以同时支持开发 Android 和 iOS 应用。现已经支持 Vue/TypeScript/JavaScript 三种语法，面向大前端开发人员，总有一款适合你。\n\n2. [Chameleon]( https://github.com/didi/chameleon)，简写CML，中文意思变色龙，意味着就像变色龙一样能适应不同环境的跨端整体解决方案，达到真正意义上\"一套代码，多端运行\"的终极目标\n   \n3. [Booster](https://github.com/didi/booster) 是一款专门为移动应用设计的易用、轻量级且可扩展的质量优化框架，其目标主要是为了解决随着 APP 复杂度的提升而带来的性能、稳定性、包体积等一系列质量问题。Booster 提供了性能检测、多线程优化、资源索引内联、资源去冗余、资源压缩、系统 Bug 修复等一系列功能模块，可以使得稳定性能够提升 15% ~ 25%，包体积可以减小 1MB ~ 10MB。**同时DoKit插件的底层也是基于Booster进行开发的。**\n\n4. [AoE](https://github.com/didi/AoE)，一个终端侧AI集成运行时环境 \n   \n5. [Mand Mobile](https://github.com/didi/mand-mobile) 一款优秀的面向金融场景的 移动端UI组件库\n   \n\n6. 我们团队的技术公众号【滴滴OrangeLab】，欢迎关注，我们会在这里持续输出团队内部比较有深度的技术沉淀和经验分享，欢迎一起交流。\n<img src=\"https://pt-starimg.didistatic.com/static/starimg/img/cPlaMme4g81616682162304.jpg\" width = \"250\" />\n\n## 协议\n\n<img alt=\"Apache-2.0 license\" src=\"https://www.apache.org/img/ASF20thAnniversary.jpg\" width=\"128\">\n\nDoKit 基于 Apache-2.0 协议进行分发和使用，更多信息参见 [协议文件](LICENSE)。\n"
  },
  {
    "path": "README_EN.md",
    "content": "<div align=\"center\">    \n <img src=\"https://javer.oss-cn-shanghai.aliyuncs.com/doraemon/github/DoraemonKit_github.png\" width = \"150\" height = \"150\" alt=\"DoraemonKit\" align=left />\n <img src=\"https://img.shields.io/github/license/didi/DoraemonKit.svg\" align=left />\n <img src=\"https://img.shields.io/badge/Android-3.5.0.svg\" align=left />\n <img src=\"https://img.shields.io/badge/iOS-3.0.4-yellow.svg\" align=left />\n <img src=\"https://img.shields.io/badge/PRs-welcome-brightgreen.svg\" align=left />\n</div>\n\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n<br/>\n\nA full-featured App (iOS & Android) development assistant. You deserve it.\n\n> [中文文档](README.md)\n\n**community activity**\n\n>DoKit official community satisfaction survey\n\nlink:https://page.juyanwenjuan.com/jy_0CMpJzlu.html\n\n<div align=\"center\">    \n  <img src=\"https://pt-starimg.didistatic.com/static/starimg/img/INLjGkp9wN1597062733948.jpg\" width = \"250\" alt=\"DoKit 首页效果演示\" align=center />\n</div>\n \n## Introduction\n\nIn the development stage of the App, in order to improve the efficiency of the developer and tester， we have developed a collection of tools with full-featured functions. I can use it to simulate the positioning of the App; preview the content of the sandbox file; view the information and logs of the App; test the performance of the App and view the detailed information of the view, etc. Each tool solves every problem in our app development. \nAnd our UI interface is simple and beautiful, and the user experience is good.\n\nAt present, we provide a total of more than 30+ built-in tools, including 2 platform tools; 10 common tools; 12 performance tools; and 5 ui tools. At the same time, you can also add your own tools in our DoKit panel for unified management.\n\nDoKit is rich in functions, easy to access, and easy to expand. Everyone is welcome to try and feedback.\n\n\n## SDK Show\n<div align=\"center\">    \n <img src=\"https://javer.oss-cn-shanghai.aliyuncs.com/2020/dokit/dokiten1.png\" width = \"250\" alt=\"Demonstration\" align=center />\n</div>\n\n## Feature List\n### Common Tools\n* App Settings： quickly open the setting page of the specific app\n* App Info：view mobile phone information, device information, permission information of the current App\n* Sanbox：support sandbox files for viewing, previewing, deleting, sharing and other operations\n* Mock GPS：You can uniformly modify the latitude and longitude callbacks inside the App\n* Browser：quickly enter the html address to view the effect of the page, and support scan code;\n* Clear Sanbox： delete all data in the sandbox\n* Log：print all logs to the UI interface for easy viewing\n* UserDefaults（iOS）: add, delete, and modify the NSUserDefaults file\n* DBView：perform more detailed operations on the DB file on the web\n* JavaScript（iOS）：execute scripts in the web view\n\n\n### Performance Tools\n* FPS：view the real-time fps of the app through floating window \n* CPU：view the real-time cpu of the app through floating window \n* Memory：view the real-time memory of the app through floating window \n* Network：view the real-time network of the app through floating window，and analysis of all network data\n* Crash：convenient to print out the code stack where Crash appears\n* Sub Thread UI：quickly locate UI operations in some sub-threads\n* ANR：when the app appears anr, print out the corresponding code call stack\n* BigImg：Through network monitoring, find out all the images with oversized size, to avoid the waste of network caused by downloading large images and the CPU consumption caused by rendering large images\n* Weak Network：view the running status of the App when the network is not good\n* Launch Time：show app launch time\n* UI Hierrachy：find the deepest element in each page\n* Time Profiler：analyze app performance bottlenecks at the function level\n* Memory Leak：quickly locate App memory leaks\n* Load（iOS）：check out all \"+load\" functions in iOS, and time-consuming statistics\n\n\n### UI Tools\n\n* Color Picker：capture the color value of every point in the app in real time\n* View Check：you can touch any view and view their detailed information, including view name, view position, background color, font color, font size\n* Align Ruler：ability to capture screen coordinates in real time and see if views are aligned\n* View Border：draw the border of each view\n\n### Platform Tools\n\n* Mock Data： App network mock solution, provides a set of network mock solutions based on App network interception, and can complete the mock for network data without modifying the code\n* Health Check： integration of multiple DoKit tools, data visualization, quick and accurate positioning of problems, let you know the performance of the app\n\n**tip：** Platform tools need to be used in conjunction with [https://www.dokit.cn/](https://www.dokit.cn/)\n\n## Installation\n\n### iOS\n#### Cocoapods\n```\n    pod 'DoraemonKit/Core', '~> 3.0.2', :configurations => ['Debug'] #Required\n    pod 'DoraemonKit/WithGPS', '~> 3.0.2', :configurations => ['Debug'] #Optional\n    pod 'DoraemonKit/WithLoad', '~> 3.0.2', :configurations => ['Debug'] #Optional\n```\n#### Example Usage\n\n```\n#ifdef DEBUG\n#import <DoraemonKit/DoraemonManager.h>\n#endif\n\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {\n    #ifdef DEBUG\n    [[DoraemonManager shareInstance] install];\n    #endif\n}\n\n```\n\n### Android\n#### 1、Download\nTo use DoKit , add the plugin to your buildscript:\n```\nbuildscript {\n    repositories {\n        google()\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.6.1'\n        classpath 'com.didichuxing.doraemonkit:dokitx-plugin:3.3.5'\n    }\n}\n\n```\nand then apply it in your app module\n\n```\napply plugin: 'com.didi.dokit'\n```\n\nand then implementation DoKit SDK\n```\ndebugImplementation \"com.didichuxing.doraemonkit:dokitx:3.3.5\"\nreleaseImplementation \"com.didichuxing.doraemonkit:dokitx-no-op:3.3.5\"\n```\n\n#### 2、SDK Init\n```\npublic class App extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        DoraemonKit.install(this);\n    }\n}\n```\n\n##  Product Manual\n\nIf you want to know more details about DoKit, please visit \n**[https://www.dokit.cn/](https://www.dokit.cn/)**.\n\n\n\n## License\n\n<img alt=\"Apache-2.0 license\" src=\"https://www.apache.org/img/ASF20thAnniversary.jpg\" width=\"128\">\n\nDoraemonKit is available under the Apache-2.0 license. See the [LICENSE](LICENSE) file for more info.\n"
  },
  {
    "path": "Web/.gitignore",
    "content": "node_modules/\ndist/\npackages/*/dist/\npackage-lock.json"
  },
  {
    "path": "Web/DEVELOPMENT.md",
    "content": "### 开发\n\n#### 安装\n1. 执行 `npm install` 安装项目依赖\n2. 执行 `npm run bootstrap`安装内部包依赖\n#### Dev 开发\n1. `npm run dev:playground` 开启 Dev 服务\n2. 浏览器访问 `http://localhost:3000/playground` 即可\n\n#### Build\n1. 执行 `npm run build`即可\n\n### 发包\nnpm publish，使用npm提供的cdn资源可远程引入资源\n### 开发规范\n\n#### 组件篇\n##### 样式\n- 所有的组件编写的时候，样式必须设定 scoped\n"
  },
  {
    "path": "Web/LICENSE",
    "content": "                              Apache License\n \n                        Version 2.0, January 2004\n \n                     http://www.apache.org/licenses/\n \n \n \n \nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n \n \n \n \n1. Definitions.\n \n \n \n \n   \"License\" shall mean the terms and conditions for use, reproduction,\n \n   and distribution as defined by Sections 1 through 9 of this document.\n \n \n \n \n   \"Licensor\" shall mean the copyright owner or entity authorized by\n \n   the copyright owner that is granting the License.\n \n \n \n \n   \"Legal Entity\" shall mean the union of the acting entity and all\n \n   other entities that control, are controlled by, or are under common\n \n   control with that entity. For the purposes of this definition,\n \n   \"control\" means (i) the power, direct or indirect, to cause the\n \n   direction or management of such entity, whether by contract or\n \n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n \n   outstanding shares, or (iii) beneficial ownership of such entity.\n \n \n \n \n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n \n   exercising permissions granted by this License.\n \n \n \n \n   \"Source\" form shall mean the preferred form for making modifications,\n \n   including but not limited to software source code, documentation\n \n   source, and configuration files.\n \n \n \n \n   \"Object\" form shall mean any form resulting from mechanical\n \n   transformation or translation of a Source form, including but\n \n   not limited to compiled object code, generated documentation,\n \n   and conversions to other media types.\n \n \n \n \n   \"Work\" shall mean the work of authorship, whether in Source or\n \n   Object form, made available under the License, as indicated by a\n \n   copyright notice that is included in or attached to the work\n \n   (an example is provided in the Appendix below).\n \n \n \n \n   \"Derivative Works\" shall mean any work, whether in Source or Object\n \n   form, that is based on (or derived from) the Work and for which the\n \n   editorial revisions, annotations, elaborations, or other modifications\n \n   represent, as a whole, an original work of authorship. For the purposes\n \n   of this License, Derivative Works shall not include works that remain\n \n   separable from, or merely link (or bind by name) to the interfaces of,\n \n   the Work and Derivative Works thereof.\n \n \n \n \n   \"Contribution\" shall mean any work of authorship, including\n \n   the original version of the Work and any modifications or additions\n \n   to that Work or Derivative Works thereof, that is intentionally\n \n   submitted to Licensor for inclusion in the Work by the copyright owner\n \n   or by an individual or Legal Entity authorized to submit on behalf of\n \n   the copyright owner. For the purposes of this definition, \"submitted\"\n \n   means any form of electronic, verbal, or written communication sent\n \n   to the Licensor or its representatives, including but not limited to\n \n   communication on electronic mailing lists, source code control systems,\n \n   and issue tracking systems that are managed by, or on behalf of, the\n \n   Licensor for the purpose of discussing and improving the Work, but\n \n   excluding communication that is conspicuously marked or otherwise\n \n   designated in writing by the copyright owner as \"Not a Contribution.\"\n \n \n \n \n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n \n   on behalf of whom a Contribution has been received by Licensor and\n \n   subsequently incorporated within the Work.\n \n \n \n \n2. Grant of Copyright License. Subject to the terms and conditions of\n \n   this License, each Contributor hereby grants to You a perpetual,\n \n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n \n   copyright license to reproduce, prepare Derivative Works of,\n \n   publicly display, publicly perform, sublicense, and distribute the\n \n   Work and such Derivative Works in Source or Object form.\n \n \n \n \n3. Grant of Patent License. Subject to the terms and conditions of\n \n   this License, each Contributor hereby grants to You a perpetual,\n \n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n \n   (except as stated in this section) patent license to make, have made,\n \n   use, offer to sell, sell, import, and otherwise transfer the Work,\n \n   where such license applies only to those patent claims licensable\n \n   by such Contributor that are necessarily infringed by their\n \n   Contribution(s) alone or by combination of their Contribution(s)\n \n   with the Work to which such Contribution(s) was submitted. If You\n \n   institute patent litigation against any entity (including a\n \n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n \n   or a Contribution incorporated within the Work constitutes direct\n \n   or contributory patent infringement, then any patent licenses\n \n   granted to You under this License for that Work shall terminate\n \n   as of the date such litigation is filed.\n \n \n \n \n4. Redistribution. You may reproduce and distribute copies of the\n \n   Work or Derivative Works thereof in any medium, with or without\n \n   modifications, and in Source or Object form, provided that You\n \n   meet the following conditions:\n \n \n \n \n   (a) You must give any other recipients of the Work or\n \n       Derivative Works a copy of this License; and\n \n \n \n \n   (b) You must cause any modified files to carry prominent notices\n \n       stating that You changed the files; and\n \n \n \n \n   (c) You must retain, in the Source form of any Derivative Works\n \n       that You distribute, all copyright, patent, trademark, and\n \n       attribution notices from the Source form of the Work,\n \n       excluding those notices that do not pertain to any part of\n \n       the Derivative Works; and\n \n \n \n \n   (d) If the Work includes a \"NOTICE\" text file as part of its\n \n       distribution, then any Derivative Works that You distribute must\n \n       include a readable copy of the attribution notices contained\n \n       within such NOTICE file, excluding those notices that do not\n \n       pertain to any part of the Derivative Works, in at least one\n \n       of the following places: within a NOTICE text file distributed\n \n       as part of the Derivative Works; within the Source form or\n \n       documentation, if provided along with the Derivative Works; or,\n \n       within a display generated by the Derivative Works, if and\n \n       wherever such third-party notices normally appear. The contents\n \n       of the NOTICE file are for informational purposes only and\n \n       do not modify the License. You may add Your own attribution\n \n       notices within Derivative Works that You distribute, alongside\n \n       or as an addendum to the NOTICE text from the Work, provided\n \n       that such additional attribution notices cannot be construed\n \n       as modifying the License.\n \n \n \n \n   You may add Your own copyright statement to Your modifications and\n \n   may provide additional or different license terms and conditions\n \n   for use, reproduction, or distribution of Your modifications, or\n \n   for any such Derivative Works as a whole, provided Your use,\n \n   reproduction, and distribution of the Work otherwise complies with\n \n   the conditions stated in this License.\n \n \n \n \n5. Submission of Contributions. Unless You explicitly state otherwise,\n \n   any Contribution intentionally submitted for inclusion in the Work\n \n   by You to the Licensor shall be under the terms and conditions of\n \n   this License, without any additional terms or conditions.\n \n   Notwithstanding the above, nothing herein shall supersede or modify\n \n   the terms of any separate license agreement you may have executed\n \n   with Licensor regarding such Contributions.\n \n \n \n \n6. Trademarks. This License does not grant permission to use the trade\n \n   names, trademarks, service marks, or product names of the Licensor,\n \n   except as required for reasonable and customary use in describing the\n \n   origin of the Work and reproducing the content of the NOTICE file.\n \n \n \n \n7. Disclaimer of Warranty. Unless required by applicable law or\n \n   agreed to in writing, Licensor provides the Work (and each\n \n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n \n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n \n   implied, including, without limitation, any warranties or conditions\n \n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n \n   PARTICULAR PURPOSE. You are solely responsible for determining the\n \n   appropriateness of using or redistributing the Work and assume any\n \n   risks associated with Your exercise of permissions under this License.\n \n \n \n \n8. Limitation of Liability. In no event and under no legal theory,\n \n   whether in tort (including negligence), contract, or otherwise,\n \n   unless required by applicable law (such as deliberate and grossly\n \n   negligent acts) or agreed to in writing, shall any Contributor be\n \n   liable to You for damages, including any direct, indirect, special,\n \n   incidental, or consequential damages of any character arising as a\n \n   result of this License or out of the use or inability to use the\n \n   Work (including but not limited to damages for loss of goodwill,\n \n   work stoppage, computer failure or malfunction, or any and all\n \n   other commercial damages or losses), even if such Contributor\n \n   has been advised of the possibility of such damages.\n \n \n \n \n9. Accepting Warranty or Additional Liability. While redistributing\n \n   the Work or Derivative Works thereof, You may choose to offer,\n \n   and charge a fee for, acceptance of support, warranty, indemnity,\n \n   or other liability obligations and/or rights consistent with this\n \n   License. However, in accepting such obligations, You may act only\n \n   on Your own behalf and on Your sole responsibility, not on behalf\n \n   of any other Contributor, and only if You agree to indemnify,\n \n   defend, and hold each Contributor harmless for any liability\n \n   incurred by, or claims asserted against, such Contributor by reason\n \n   of your accepting any such warranty or additional liability.\n \n \n \n \nEND OF TERMS AND CONDITIONS\n \n \n \n \nAPPENDIX: How to apply the Apache License to your work.\n \n \n \n \n   To apply the Apache License to your work, attach the following\n \n   boilerplate notice, with the fields enclosed by brackets \"{}\"\n \n   replaced with your own identifying information. (Don't include\n \n   the brackets!)  The text should be enclosed in the appropriate\n \n   comment syntax for the file format. We also recommend that a\n \n   file or class name and description of purpose be included on the\n \n   same \"printed page\" as the copyright notice for easier\n \n   identification within third-party archives.\n \n \n \n \nCopyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n \n \n \n \nLicensed under the Apache License, Version 2.0 (the \"License\");\n \nyou may not use this file except in compliance with the License.\n \nYou may obtain a copy of the License at\n \n \n \n \n    http://www.apache.org/licenses/LICENSE-2.0\n \n \n \n \nUnless required by applicable law or agreed to in writing, software\n \ndistributed under the License is distributed on an \"AS IS\" BASIS,\n \nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n \nSee the License for the specific language governing permissions and\n \nlimitations under the License.\n"
  },
  {
    "path": "Web/README.md",
    "content": "## 介绍\n一款面向前端生态的开发工具\n### 愿景\n让前端开发更美好\n\n### 功能\n\n#### 功能一览\n<div align=center>\n  <img width=\"200\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/rxidkzyycP1628441881971.png\">\n</div>\n\n#### 日志\n\n展示 `Console` 控制台的日志\n<div align=center>\n  <img width=\"200\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/eav7Ts12nH1628441945429.png\">\n</div>\n\n##### 基础功能\n- 支持所有的 `console` 方法\n- 支持日志的筛选和清除\n- 支持执行简易的 `javascript`命令\n#### 接口抓取\n展示所有的数据请求\n<div align=center>\n  <img width=\"200\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/ROL9A3CvOH1628442010241.png\">\n</div>\n\n##### 基础功能\n- 支持抓取常用的接口类型 `xhr` 和 `fetch`\n- 展示请求的详细内容\n- 展示响应的详细内容\n#### 资源查看\n展示页面的所有资源请求\n<div align=center>\n  <img width=\"200\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/KpRqRxDbBb1628442052029.png\">\n</div>\n\n##### 基础功能\n- 展示`javascript`资源\n- 展示`css`资源\n- 展示`image`等媒体资源\n\n#### 应用存储\n展示当前页面的存储信息\n\n<div align=center>\n  <img width=\"200\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/60jC26gBJI1628442340277.png\">\n</div>\n\n##### 基础功能\n- 支持查看并修改 `LocalStorage`\n- 支持查看并修改 `SessionStorage`\n- 支持查看 `Cookie`\n\n#### 接口Mock\n无侵入式 Mock 数据\n<div align=center>\n  <img width=\"200\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/t1qIRaZYeb1628443351551.png\">\n</div>\n\n> 在我们的[平台端](https://www.dokit.cn/)注册，即可使用该功能，在sdk接入部分会有详细介绍。\n点击工具中的数据模拟即可进入详情页，其中详情页分为mock数据和上传模板(开发中)两块功能。\n\n> \u0001**mock数据:** 你可以通过打开指定接口的开关并选择相应的场景,此时你无需改变你的网络请求代码即可对你的指定接口进行拦截并返回你在平台创建的接口数据。\n\n> **上传模板:** 上传模板功能的适用场景是当你已经有了一个真实的接口，需要针对不同的用户场景进行测试但是同时接口返回的数据量比较庞大，所以我们为你提供了上传模板的功能。当你打开上传模板中指定接口的开关时，我们会拦截并保存你真实接口返回的数据并提供json预览功能。点击上传即可上传你的模板数据到Dokit平台端。\n##### 基础功能\n- 支持无侵入式一键 Mock\n- 支持自动同步真实请求数据到平台侧，并与快速创建\n\n\n#### H5任意门\n通过输入连接的方式从当前页面跳往任意页面，可快速添加和编辑页面query参数，同时可以缓存跳转历史\n<div align=center>\n  <img width=\"200\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/GjeLt3ykHO1628444700515.png\">\n</div>\n\n#### 应用信息\n查看与当前页面有关的应用信息\n<div align=center>\n  <img width=\"200\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/EnksDhWPtb1628443757848.png\">\n</div>\n\n#### 页面标尺\n利用可移动标尺查看元素位置及元素对齐情况\n<div align=center>\n  <img width=\"200\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/TNCHHhBWky1628443923215.png\">\n</div>\n\n#### 屏幕录制 & 回放\n> 功能设计中\n\n#### 自动化测试\n> 功能设计中\n\n#### 如何接入DoKit For Web\n\n在项目中引入vue3和dokit的cdn即可使用dokit相关基础功能，我们会自动实例化DoKit实例并挂载在全局上\n如需使用平台侧增强功能如数据mock等功能则需要调用setProductId这个方法来设置productId\n```html\n<script src=\"https://unpkg.com/vue@next\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/@dokit/web@0.0.1-alpha.1/dist/dokit.min.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n\n<script>\n  Dokit.setProductId('你的product id')\n</script>\n```\n\n\n#### 自定义业务专区\n\n\n通用的开发工具肯定无法满足各种各样的业务开发需要\n\n所以我们提供了较为便捷的方式能够让前端同学能够定制属于自己的Dokit开发工具\n你可以在本地clone DoKit项目，在Web模块下进行定制化开发，在DoKit For Web\n中我们将整个项目分成三个模块：core/utils/web,利用lerna进行开发管理\n\n\n- core主要提供了框架初始化能力，集成了简单的路由和状态管理功能\n- utils中主要是一个工具方法和公共能力支持，如事件订阅发布、网络、拖拽等\n- web中主要是对具体功能组件的实现，我们提供了两种组件/插件，第一种是基于自定义路由的依托于DoKit主容器的类路由view插件（日志、网络请求等），另一种是脱离DoKit主容器的独立插件（标尺对齐）\n\n当你需要自定义一个业务工具是，你只需要进行三步即可构建属于你自己的Dokit\n1. 在web文件夹的plugin中穿件一个新的plugin,然后实现你自己的vue组件\n2. 在文件夹中添加一个index.js，用于将vue组件导出为路由插件/独立插件\n3. 在web文件夹下的feature.js中添加你的插件\n经过上面三步你就可以将你的插件集成进DoKit,利用我们提供的构建工具，可以在本地预览和调试，然后进行项目打包，将最终dist产物发布到npm后你就可以利用npm提供的cdn能力引入你的专属DoKit了\n\n我们来实战一下，分别添加一个RouterPlugin和一个IndependPlugin，\n\n我们写一个简单的vue组件只包含简单的视图和文字\n<div align=center>\n  <img src=\"https://pt-starimg.didistatic.com/static/starimg/img/Q3bQDwVGYX1628761222487.png\">\n</div>\n然后将组件导出为路由插件\n<div align=center>\n  <img src=\"https://pt-starimg.didistatic.com/static/starimg/img/qluMSP6wlO1628761222376.png\">\n</div>\n\n再写一个支持拖拽的可移动视图\n<div align=center>\n  <img src=\"https://pt-starimg.didistatic.com/static/starimg/img/bvka6ncI781628761228269.png\">\n</div>\n将其导出为独立插件\n<div align=center>\n  <img src=\"https://pt-starimg.didistatic.com/static/starimg/img/QnBm6hKD6s1628761225271.png\">\n</div>\n然后我们在feature.js中引入并声明这两个插件\n<div align=center>\n  <img src=\"https://pt-starimg.didistatic.com/static/starimg/img/ZFGI8KVTFG1628761226052.png\">\n</div>\n启动dev服务，我们可以看到预览页面的DoKit中多了两个工具\n\n\n<div align=center>\n  <img width=\"200\"  src=\"https://pt-starimg.didistatic.com/static/starimg/img/HnvW7LUw8u1628761227192.png\">\n</div>\n第一个就是我们写的简单的仅包含文字的组件\n<div align=center>\n  <img width=\"200\"  src=\"https://pt-starimg.didistatic.com/static/starimg/img/kG9oNBEaTS1628761227928.png\">\n</div>\n第二个则是一个脱离了DoKit主容器的独立可拖拽插件\n<div align=center>\n  <img width=\"200\"  src=\"https://pt-starimg.didistatic.com/static/starimg/img/eeugDnQgjw1628761960638.png\">\n</div>\n\n\n\n\n\n# 贡献\n\n有任何意见或建议都欢迎提 issue\n\n# github地址\n\nhttps://github.com/didi/DoraemonKit 欢迎star\n\n"
  },
  {
    "path": "Web/jest.config.js",
    "content": "module.exports = {\n  globals: {},\n  coverageDirectory: 'coverage',\n  coverageReporters: ['html', 'lcov', 'text'],\n  collectCoverageFrom: [\n    'packages/*/src/**/*.js'\n  ],\n  watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'],\n  moduleFileExtensions: ['vue', 'js', 'json'],\n  rootDir: __dirname,\n  testMatch: ['<rootDir>/packages/**/__tests__/**/*test.[jt]s?(x)'],\n  testPathIgnorePatterns: process.env.SKIP_E2E\n    ? // ignore example tests on netlify builds since they don't contribute\n      // to coverage and can cause netlify builds to fail\n      ['/node_modules/', '/examples/__tests__']\n    : ['/node_modules/']\n}\n"
  },
  {
    "path": "Web/lerna-debug.log",
    "content": "0 silly argv { _: [ 'bootstrap' ], lernaVersion: '4.0.0', '$0': 'lerna' }\n1 notice cli v4.0.0\n2 verbose rootPath /Users/didi/Desktop/DoKit/DoraemonKit/Web\n3 error JSONError: Unexpected token \"<\" (0x3C) in JSON at position 745 while parsing near \"...  \\\"dependencies\\\": {\\n<<<<<<< HEAD\\n<<<<<<<...\" in packages/web/package.json\n3 error\n3 error \u001b[0m \u001b[90m 31 |\u001b[39m   }\u001b[33m,\u001b[39m\u001b[0m\n3 error \u001b[0m \u001b[90m 32 |\u001b[39m   \u001b[32m\"dependencies\"\u001b[39m\u001b[33m:\u001b[39m {\u001b[0m\n3 error \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 33 |\u001b[39m \u001b[33m<<\u001b[39m\u001b[33m<<\u001b[39m\u001b[33m<<\u001b[39m\u001b[33m<\u001b[39m \u001b[33mHEAD\u001b[39m\u001b[0m\n3 error \u001b[0m \u001b[90m    |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n3 error \u001b[0m \u001b[90m 34 |\u001b[39m \u001b[33m<<\u001b[39m\u001b[33m<<\u001b[39m\u001b[33m<<\u001b[39m\u001b[33m<\u001b[39m \u001b[33mHEAD\u001b[39m\u001b[0m\n3 error \u001b[0m \u001b[90m 35 |\u001b[39m \u001b[33m===\u001b[39m\u001b[33m===\u001b[39m\u001b[33m=\u001b[39m\u001b[0m\n3 error \u001b[0m \u001b[90m 36 |\u001b[39m \u001b[33m>>>\u001b[39m\u001b[33m>>>\u001b[39m\u001b[33m>\u001b[39m \u001b[35m4219\u001b[39mbdfe (feat\u001b[33m:\u001b[39m 修改部分一机多控逻辑)\u001b[0m\n3 error\n3 error     at parseJson (/Users/didi/Desktop/DoKit/DoraemonKit/Web/node_modules/parse-json/index.js:29:21)\n3 error     at parse (/Users/didi/Desktop/DoKit/DoraemonKit/Web/node_modules/load-json-file/index.js:15:9)\n3 error     at module.exports (/Users/didi/Desktop/DoKit/DoraemonKit/Web/node_modules/load-json-file/index.js:18:47)\n"
  },
  {
    "path": "Web/lerna.json",
    "content": "{\n  \"tagVersionPrefix\": \"web_\",\n  \"packages\": [\n    \"packages/*\"\n  ],\n  \"version\": \"0.0.3\",\n  \"npmClient\": \"npm\",\n  \"command\": {\n    \"bootstrap\": {\n      \"npmClientArgs\": [\n        \"--no-package-lock\"\n      ]\n    },\n    \"publish\": {\n      \"allowBranch\": [\n        \"master\",\n        \"release\"\n      ],\n      \"ignoreChanges\": [\n        \"*.md\",\n        \"lerna.json\"\n      ],\n      \"registry\": \"https://registry.npmjs.org/\"\n    },\n    \"npmClientArgs\": [\n      \"--no-lockfile\"\n    ]\n  }\n}\n"
  },
  {
    "path": "Web/package.json",
    "content": "{\n  \"name\": \"dokit-web\",\n  \"version\": \"0.0.0\",\n  \"description\": \"a web toolkit powered by DoKit\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"jest test\",\n    \"bootstrap\": \"lerna bootstrap\",\n    \"build\": \"lerna run build\",\n    \"dev\": \"lerna run dev --parallel\",\n    \"dev:playground\": \"run-p serve:playground dev\",\n    \"serve:playground\": \"node scripts/dev-playground.js\",\n    \"clean\": \"run-s clean:lerna clean:lock\",\n    \"clean:lerna\": \"lerna clean --yes\",\n    \"clean:lock\": \"run-s clean:lock-package clean:lock-subpackage\",\n    \"clean:lock-package\": \"rm -rf ./package-lock.json\",\n    \"clean:lock-subpackage\": \"rm -rf ./packages/**/package-lock.json\"\n  },\n  \"author\": \"caoenze\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"@rollup/plugin-alias\": \"^3.1.2\",\n    \"@vue/compiler-sfc\": \"^3.0.11\",\n    \"cross-env\": \"^7.0.3\",\n    \"execa\": \"^5.0.0\",\n    \"jest\": \"^26.6.3\",\n    \"lerna\": \"^4.0.0\",\n    \"less\": \"^4.1.1\",\n    \"livereload\": \"^0.9.3\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"open\": \"^8.0.6\",\n    \"postcss\": \"^8.2.12\",\n    \"rollup\": \"^2.41.1\",\n    \"rollup-plugin-commonjs\": \"^10.1.0\",\n    \"rollup-plugin-node-resolve\": \"^5.2.0\",\n    \"rollup-plugin-postcss\": \"^4.0.0\",\n    \"rollup-plugin-replace\": \"^2.2.0\",\n    \"rollup-plugin-terser\": \"^7.0.2\",\n    \"rollup-plugin-vue\": \"^6.0.0\",\n    \"serve-handler\": \"^6.1.3\"\n  },\n  \"dependencies\": {\n    \"@dokit/web\": \"file:packages/web\",\n    \"@dokit/web-core\": \"file:packages/core\",\n    \"@dokit/web-independent\": \"file:packages/web-independent\",\n    \"@dokit/web-utils\": \"file:packages/utils\",\n    \"qrcodejs2\": \"0.0.2\",\n    \"qrcodejs2-fix\": \"0.0.1\",\n    \"vant\": \"^3.1.0-beta.0\",\n    \"vue\": \"^3.2.21\"\n  }\n}\n"
  },
  {
    "path": "Web/packages/core/README.md",
    "content": "# `dokit-core`\n提供 Dokit 无侵入式容器，供 Dokit Web 定制化。\n\n支持外部注入功能的配置，来自定义 Dokit 容器。\n\n提供插件注册的能力、提供基础的 runtime 能力。\n\n> Dokit Web Core，负责提供容器，供Dokit Web定制化能力。\n\n## Usage\n\n```javascript\nimport dokit from '@dokit/web-core';\ndokit({\n  routes: routes\n});\n\n```\n"
  },
  {
    "path": "Web/packages/core/__tests__/index.test.js",
    "content": "'use strict';\n\ndescribe('index', () => {\n    it('it needs tests', () => {})\n});\n"
  },
  {
    "path": "Web/packages/core/package.json",
    "content": "{\n  \"name\": \"@dokit/web-core\",\n  \"version\": \"0.0.3\",\n  \"description\": \"Dokit\",\n  \"keywords\": [\n    \"Dokit\"\n  ],\n  \"author\": \"duanlikang <duanlikang@didichuxing.com>\",\n  \"homepage\": \"https://github.com/didi/DoraemonKit#readme\",\n  \"license\": \"ISC\",\n  \"main\": \"dist/index.js\",\n  \"module\": \"dist/index.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"publishConfig\": {\n    \"registry\": \"https://registry.npmjs.org/\",\n    \"access\": \"public\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/didi/DoraemonKit.git\"\n  },\n  \"scripts\": {\n    \"build\": \"npx cross-env NODE_ENV=production npx rollup -c\",\n    \"dev\": \"npx rollup -wc\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/didi/DoraemonKit/issues\"\n  },\n  \"dependencies\": {\n    \"@dokit/web-utils\": \"file:../utils\",\n    \"@medv/finder\": \"^1.1.3\",\n    \"cssesc\": \"^3.0.0\",\n    \"moment\": \"^2.29.1\",\n    \"vue\": \"^3.2.21\"\n  },\n  \"gitHead\": \"886ea7c19806526668e5da0179da335e7df9d012\"\n}\n"
  },
  {
    "path": "Web/packages/core/rollup.config.js",
    "content": "import vuePlugin from 'rollup-plugin-vue'\nimport postcssPlugin from 'rollup-plugin-postcss'\nimport resolve from 'rollup-plugin-node-resolve'\nimport commonjs from 'rollup-plugin-commonjs'\nimport replace from 'rollup-plugin-replace'\nimport rAlias from '@rollup/plugin-alias'\nimport {terser} from 'rollup-plugin-terser'\nconst path = require('path')\n\nconst extendPlugins = []\nif(process.env.NODE_ENV === 'production'){\n  extendPlugins.push(terser())\n}\n\nexport default {\n  input: 'src/index.js',\n  output: [\n    {\n      file: 'dist/index.js',\n      format: 'es'\n    }\n  ],\n  external: ['vue'],\n  plugins: [\n    rAlias({\n      entries:{\n        \"@common\": path.join(__dirname, './src/common'),\n        \"@store\": path.join(__dirname, './src/store'),\n        \"@router\": path.join(__dirname, './src/route')\n      }\n    }),\n    vuePlugin({\n      preprocessStyles: true\n    }),\n    replace({\n      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),\n      'process.env.VUE_ENV': JSON.stringify('browser')\n    }),\n    postcssPlugin(),\n    resolve({ extensions: ['.vue'] }),\n    commonjs(),\n    ...extendPlugins\n  ]\n}"
  },
  {
    "path": "Web/packages/core/src/common/components/card.vue",
    "content": "<template>\n  <div class=\"dokit-card\">\n    <div class=\"dokit-card-title\">\n      <span class=\"dokit-card-title-text\"> {{title}} </span>\n    </div>\n    <div class=\"dokit-item-list\">\n      <div class=\"item\" v-for=\"(item,index) in list\" :key=\"index\" @click=\"handleClickItem(item)\">\n        <div class=\"item-icon\">\n          <img\n            class=\"item-icon-image\"\n            :src=\"item.icon || defaultIcon\"\n          />\n        </div>\n        <div class=\"item-title\">{{item.nameZh || '默认功能'}}</div>\n      </div>\n    </div>\n  </div>\n</template>\n<script>\nimport {DefaultItemIcon} from '../js/icon'\nexport default {\n  props: {\n    title: {\n      default: '专区'\n    },\n    list: {\n      default: []\n    }\n  },\n  data(){\n    return {\n      defaultIcon: DefaultItemIcon\n    }\n  },\n  methods: {\n    handleClickItem(item){\n      this.$emit('handleClickItem', item)\n    }\n  }\n};\n</script>\n<style lang=\"less\" scoped>\n.dokit-card {\n  margin-bottom: 10px;\n  padding: 10px;\n  background-color: white;\n}\n.dokit-card-title-text {\n  font-size: 16px;\n  color: #333333;\n}\n.dokit-item-list {\n  display: flex;\n  flex-wrap: wrap;\n  margin-top: 5px;\n}\n.item {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  width: 25%;\n  margin-top: 5px;\n  .item-icon-image {\n    width: 30px;\n  }\n  .item-title {\n    font-size: 14px;\n    margin-top: 5px;\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/core/src/common/components/dokit-ui/index.js",
    "content": "\nimport { Col, Row } from \"./layout/index\"\n\nimport './layout/col.css'\nimport './layout/row.css'\n\nexport default {\n  install: app => {\n    app.component('DoCol', Col)\n    app.component('DoRow', Row)\n  }\n}"
  },
  {
    "path": "Web/packages/core/src/common/components/dokit-ui/layout/col.css",
    "content": ".do-col-pull-0,.do-col-pull-1,.do-col-pull-10,.do-col-pull-11,.do-col-pull-12,.do-col-pull-13,.do-col-pull-14,.do-col-pull-15,.do-col-pull-16,.do-col-pull-17,.do-col-pull-18,.do-col-pull-19,.do-col-pull-2,.do-col-pull-20,.do-col-pull-21,.do-col-pull-22,.do-col-pull-23,.do-col-pull-24,.do-col-pull-3,.do-col-pull-4,.do-col-pull-5,.do-col-pull-6,.do-col-pull-7,.do-col-pull-8,.do-col-pull-9,.do-col-push-0,.do-col-push-1,.do-col-push-10,.do-col-push-11,.do-col-push-12,.do-col-push-13,.do-col-push-14,.do-col-push-15,.do-col-push-16,.do-col-push-17,.do-col-push-18,.do-col-push-19,.do-col-push-2,.do-col-push-20,.do-col-push-21,.do-col-push-22,.do-col-push-23,.do-col-push-24,.do-col-push-3,.do-col-push-4,.do-col-push-5,.do-col-push-6,.do-col-push-7,.do-col-push-8,.do-col-push-9{position:relative}[class*=do-col-]{float:left;-webkit-box-sizing:border-box;box-sizing:border-box}.do-col-0{display:none;width:0%}.do-col-offset-0{margin-left:0}.do-col-pull-0{right:0}.do-col-push-0{left:0}.do-col-1{width:4.16667%}.do-col-offset-1{margin-left:4.16667%}.do-col-pull-1{right:4.16667%}.do-col-push-1{left:4.16667%}.do-col-2{width:8.33333%}.do-col-offset-2{margin-left:8.33333%}.do-col-pull-2{right:8.33333%}.do-col-push-2{left:8.33333%}.do-col-3{width:12.5%}.do-col-offset-3{margin-left:12.5%}.do-col-pull-3{right:12.5%}.do-col-push-3{left:12.5%}.do-col-4{width:16.66667%}.do-col-offset-4{margin-left:16.66667%}.do-col-pull-4{right:16.66667%}.do-col-push-4{left:16.66667%}.do-col-5{width:20.83333%}.do-col-offset-5{margin-left:20.83333%}.do-col-pull-5{right:20.83333%}.do-col-push-5{left:20.83333%}.do-col-6{width:25%}.do-col-offset-6{margin-left:25%}.do-col-pull-6{right:25%}.do-col-push-6{left:25%}.do-col-7{width:29.16667%}.do-col-offset-7{margin-left:29.16667%}.do-col-pull-7{right:29.16667%}.do-col-push-7{left:29.16667%}.do-col-8{width:33.33333%}.do-col-offset-8{margin-left:33.33333%}.do-col-pull-8{right:33.33333%}.do-col-push-8{left:33.33333%}.do-col-9{width:37.5%}.do-col-offset-9{margin-left:37.5%}.do-col-pull-9{right:37.5%}.do-col-push-9{left:37.5%}.do-col-10{width:41.66667%}.do-col-offset-10{margin-left:41.66667%}.do-col-pull-10{right:41.66667%}.do-col-push-10{left:41.66667%}.do-col-11{width:45.83333%}.do-col-offset-11{margin-left:45.83333%}.do-col-pull-11{right:45.83333%}.do-col-push-11{left:45.83333%}.do-col-12{width:50%}.do-col-offset-12{margin-left:50%}.do-col-pull-12{right:50%}.do-col-push-12{left:50%}.do-col-13{width:54.16667%}.do-col-offset-13{margin-left:54.16667%}.do-col-pull-13{right:54.16667%}.do-col-push-13{left:54.16667%}.do-col-14{width:58.33333%}.do-col-offset-14{margin-left:58.33333%}.do-col-pull-14{right:58.33333%}.do-col-push-14{left:58.33333%}.do-col-15{width:62.5%}.do-col-offset-15{margin-left:62.5%}.do-col-pull-15{right:62.5%}.do-col-push-15{left:62.5%}.do-col-16{width:66.66667%}.do-col-offset-16{margin-left:66.66667%}.do-col-pull-16{right:66.66667%}.do-col-push-16{left:66.66667%}.do-col-17{width:70.83333%}.do-col-offset-17{margin-left:70.83333%}.do-col-pull-17{right:70.83333%}.do-col-push-17{left:70.83333%}.do-col-18{width:75%}.do-col-offset-18{margin-left:75%}.do-col-pull-18{right:75%}.do-col-push-18{left:75%}.do-col-19{width:79.16667%}.do-col-offset-19{margin-left:79.16667%}.do-col-pull-19{right:79.16667%}.do-col-push-19{left:79.16667%}.do-col-20{width:83.33333%}.do-col-offset-20{margin-left:83.33333%}.do-col-pull-20{right:83.33333%}.do-col-push-20{left:83.33333%}.do-col-21{width:87.5%}.do-col-offset-21{margin-left:87.5%}.do-col-pull-21{right:87.5%}.do-col-push-21{left:87.5%}.do-col-22{width:91.66667%}.do-col-offset-22{margin-left:91.66667%}.do-col-pull-22{right:91.66667%}.do-col-push-22{left:91.66667%}.do-col-23{width:95.83333%}.do-col-offset-23{margin-left:95.83333%}.do-col-pull-23{right:95.83333%}.do-col-push-23{left:95.83333%}.do-col-24{width:100%}.do-col-offset-24{margin-left:100%}.do-col-pull-24{right:100%}.do-col-push-24{left:100%}@media only screen and (max-width:767px){.do-col-xs-0{display:none;width:0%}.do-col-xs-offset-0{margin-left:0}.do-col-xs-pull-0{position:relative;right:0}.do-col-xs-push-0{position:relative;left:0}.do-col-xs-1{width:4.16667%}.do-col-xs-offset-1{margin-left:4.16667%}.do-col-xs-pull-1{position:relative;right:4.16667%}.do-col-xs-push-1{position:relative;left:4.16667%}.do-col-xs-2{width:8.33333%}.do-col-xs-offset-2{margin-left:8.33333%}.do-col-xs-pull-2{position:relative;right:8.33333%}.do-col-xs-push-2{position:relative;left:8.33333%}.do-col-xs-3{width:12.5%}.do-col-xs-offset-3{margin-left:12.5%}.do-col-xs-pull-3{position:relative;right:12.5%}.do-col-xs-push-3{position:relative;left:12.5%}.do-col-xs-4{width:16.66667%}.do-col-xs-offset-4{margin-left:16.66667%}.do-col-xs-pull-4{position:relative;right:16.66667%}.do-col-xs-push-4{position:relative;left:16.66667%}.do-col-xs-5{width:20.83333%}.do-col-xs-offset-5{margin-left:20.83333%}.do-col-xs-pull-5{position:relative;right:20.83333%}.do-col-xs-push-5{position:relative;left:20.83333%}.do-col-xs-6{width:25%}.do-col-xs-offset-6{margin-left:25%}.do-col-xs-pull-6{position:relative;right:25%}.do-col-xs-push-6{position:relative;left:25%}.do-col-xs-7{width:29.16667%}.do-col-xs-offset-7{margin-left:29.16667%}.do-col-xs-pull-7{position:relative;right:29.16667%}.do-col-xs-push-7{position:relative;left:29.16667%}.do-col-xs-8{width:33.33333%}.do-col-xs-offset-8{margin-left:33.33333%}.do-col-xs-pull-8{position:relative;right:33.33333%}.do-col-xs-push-8{position:relative;left:33.33333%}.do-col-xs-9{width:37.5%}.do-col-xs-offset-9{margin-left:37.5%}.do-col-xs-pull-9{position:relative;right:37.5%}.do-col-xs-push-9{position:relative;left:37.5%}.do-col-xs-10{width:41.66667%}.do-col-xs-offset-10{margin-left:41.66667%}.do-col-xs-pull-10{position:relative;right:41.66667%}.do-col-xs-push-10{position:relative;left:41.66667%}.do-col-xs-11{width:45.83333%}.do-col-xs-offset-11{margin-left:45.83333%}.do-col-xs-pull-11{position:relative;right:45.83333%}.do-col-xs-push-11{position:relative;left:45.83333%}.do-col-xs-12{width:50%}.do-col-xs-offset-12{margin-left:50%}.do-col-xs-pull-12{position:relative;right:50%}.do-col-xs-push-12{position:relative;left:50%}.do-col-xs-13{width:54.16667%}.do-col-xs-offset-13{margin-left:54.16667%}.do-col-xs-pull-13{position:relative;right:54.16667%}.do-col-xs-push-13{position:relative;left:54.16667%}.do-col-xs-14{width:58.33333%}.do-col-xs-offset-14{margin-left:58.33333%}.do-col-xs-pull-14{position:relative;right:58.33333%}.do-col-xs-push-14{position:relative;left:58.33333%}.do-col-xs-15{width:62.5%}.do-col-xs-offset-15{margin-left:62.5%}.do-col-xs-pull-15{position:relative;right:62.5%}.do-col-xs-push-15{position:relative;left:62.5%}.do-col-xs-16{width:66.66667%}.do-col-xs-offset-16{margin-left:66.66667%}.do-col-xs-pull-16{position:relative;right:66.66667%}.do-col-xs-push-16{position:relative;left:66.66667%}.do-col-xs-17{width:70.83333%}.do-col-xs-offset-17{margin-left:70.83333%}.do-col-xs-pull-17{position:relative;right:70.83333%}.do-col-xs-push-17{position:relative;left:70.83333%}.do-col-xs-18{width:75%}.do-col-xs-offset-18{margin-left:75%}.do-col-xs-pull-18{position:relative;right:75%}.do-col-xs-push-18{position:relative;left:75%}.do-col-xs-19{width:79.16667%}.do-col-xs-offset-19{margin-left:79.16667%}.do-col-xs-pull-19{position:relative;right:79.16667%}.do-col-xs-push-19{position:relative;left:79.16667%}.do-col-xs-20{width:83.33333%}.do-col-xs-offset-20{margin-left:83.33333%}.do-col-xs-pull-20{position:relative;right:83.33333%}.do-col-xs-push-20{position:relative;left:83.33333%}.do-col-xs-21{width:87.5%}.do-col-xs-offset-21{margin-left:87.5%}.do-col-xs-pull-21{position:relative;right:87.5%}.do-col-xs-push-21{position:relative;left:87.5%}.do-col-xs-22{width:91.66667%}.do-col-xs-offset-22{margin-left:91.66667%}.do-col-xs-pull-22{position:relative;right:91.66667%}.do-col-xs-push-22{position:relative;left:91.66667%}.do-col-xs-23{width:95.83333%}.do-col-xs-offset-23{margin-left:95.83333%}.do-col-xs-pull-23{position:relative;right:95.83333%}.do-col-xs-push-23{position:relative;left:95.83333%}.do-col-xs-24{width:100%}.do-col-xs-offset-24{margin-left:100%}.do-col-xs-pull-24{position:relative;right:100%}.do-col-xs-push-24{position:relative;left:100%}}@media only screen and (min-width:768px){.do-col-sm-0{display:none;width:0%}.do-col-sm-offset-0{margin-left:0}.do-col-sm-pull-0{position:relative;right:0}.do-col-sm-push-0{position:relative;left:0}.do-col-sm-1{width:4.16667%}.do-col-sm-offset-1{margin-left:4.16667%}.do-col-sm-pull-1{position:relative;right:4.16667%}.do-col-sm-push-1{position:relative;left:4.16667%}.do-col-sm-2{width:8.33333%}.do-col-sm-offset-2{margin-left:8.33333%}.do-col-sm-pull-2{position:relative;right:8.33333%}.do-col-sm-push-2{position:relative;left:8.33333%}.do-col-sm-3{width:12.5%}.do-col-sm-offset-3{margin-left:12.5%}.do-col-sm-pull-3{position:relative;right:12.5%}.do-col-sm-push-3{position:relative;left:12.5%}.do-col-sm-4{width:16.66667%}.do-col-sm-offset-4{margin-left:16.66667%}.do-col-sm-pull-4{position:relative;right:16.66667%}.do-col-sm-push-4{position:relative;left:16.66667%}.do-col-sm-5{width:20.83333%}.do-col-sm-offset-5{margin-left:20.83333%}.do-col-sm-pull-5{position:relative;right:20.83333%}.do-col-sm-push-5{position:relative;left:20.83333%}.do-col-sm-6{width:25%}.do-col-sm-offset-6{margin-left:25%}.do-col-sm-pull-6{position:relative;right:25%}.do-col-sm-push-6{position:relative;left:25%}.do-col-sm-7{width:29.16667%}.do-col-sm-offset-7{margin-left:29.16667%}.do-col-sm-pull-7{position:relative;right:29.16667%}.do-col-sm-push-7{position:relative;left:29.16667%}.do-col-sm-8{width:33.33333%}.do-col-sm-offset-8{margin-left:33.33333%}.do-col-sm-pull-8{position:relative;right:33.33333%}.do-col-sm-push-8{position:relative;left:33.33333%}.do-col-sm-9{width:37.5%}.do-col-sm-offset-9{margin-left:37.5%}.do-col-sm-pull-9{position:relative;right:37.5%}.do-col-sm-push-9{position:relative;left:37.5%}.do-col-sm-10{width:41.66667%}.do-col-sm-offset-10{margin-left:41.66667%}.do-col-sm-pull-10{position:relative;right:41.66667%}.do-col-sm-push-10{position:relative;left:41.66667%}.do-col-sm-11{width:45.83333%}.do-col-sm-offset-11{margin-left:45.83333%}.do-col-sm-pull-11{position:relative;right:45.83333%}.do-col-sm-push-11{position:relative;left:45.83333%}.do-col-sm-12{width:50%}.do-col-sm-offset-12{margin-left:50%}.do-col-sm-pull-12{position:relative;right:50%}.do-col-sm-push-12{position:relative;left:50%}.do-col-sm-13{width:54.16667%}.do-col-sm-offset-13{margin-left:54.16667%}.do-col-sm-pull-13{position:relative;right:54.16667%}.do-col-sm-push-13{position:relative;left:54.16667%}.do-col-sm-14{width:58.33333%}.do-col-sm-offset-14{margin-left:58.33333%}.do-col-sm-pull-14{position:relative;right:58.33333%}.do-col-sm-push-14{position:relative;left:58.33333%}.do-col-sm-15{width:62.5%}.do-col-sm-offset-15{margin-left:62.5%}.do-col-sm-pull-15{position:relative;right:62.5%}.do-col-sm-push-15{position:relative;left:62.5%}.do-col-sm-16{width:66.66667%}.do-col-sm-offset-16{margin-left:66.66667%}.do-col-sm-pull-16{position:relative;right:66.66667%}.do-col-sm-push-16{position:relative;left:66.66667%}.do-col-sm-17{width:70.83333%}.do-col-sm-offset-17{margin-left:70.83333%}.do-col-sm-pull-17{position:relative;right:70.83333%}.do-col-sm-push-17{position:relative;left:70.83333%}.do-col-sm-18{width:75%}.do-col-sm-offset-18{margin-left:75%}.do-col-sm-pull-18{position:relative;right:75%}.do-col-sm-push-18{position:relative;left:75%}.do-col-sm-19{width:79.16667%}.do-col-sm-offset-19{margin-left:79.16667%}.do-col-sm-pull-19{position:relative;right:79.16667%}.do-col-sm-push-19{position:relative;left:79.16667%}.do-col-sm-20{width:83.33333%}.do-col-sm-offset-20{margin-left:83.33333%}.do-col-sm-pull-20{position:relative;right:83.33333%}.do-col-sm-push-20{position:relative;left:83.33333%}.do-col-sm-21{width:87.5%}.do-col-sm-offset-21{margin-left:87.5%}.do-col-sm-pull-21{position:relative;right:87.5%}.do-col-sm-push-21{position:relative;left:87.5%}.do-col-sm-22{width:91.66667%}.do-col-sm-offset-22{margin-left:91.66667%}.do-col-sm-pull-22{position:relative;right:91.66667%}.do-col-sm-push-22{position:relative;left:91.66667%}.do-col-sm-23{width:95.83333%}.do-col-sm-offset-23{margin-left:95.83333%}.do-col-sm-pull-23{position:relative;right:95.83333%}.do-col-sm-push-23{position:relative;left:95.83333%}.do-col-sm-24{width:100%}.do-col-sm-offset-24{margin-left:100%}.do-col-sm-pull-24{position:relative;right:100%}.do-col-sm-push-24{position:relative;left:100%}}@media only screen and (min-width:992px){.do-col-md-0{display:none;width:0%}.do-col-md-offset-0{margin-left:0}.do-col-md-pull-0{position:relative;right:0}.do-col-md-push-0{position:relative;left:0}.do-col-md-1{width:4.16667%}.do-col-md-offset-1{margin-left:4.16667%}.do-col-md-pull-1{position:relative;right:4.16667%}.do-col-md-push-1{position:relative;left:4.16667%}.do-col-md-2{width:8.33333%}.do-col-md-offset-2{margin-left:8.33333%}.do-col-md-pull-2{position:relative;right:8.33333%}.do-col-md-push-2{position:relative;left:8.33333%}.do-col-md-3{width:12.5%}.do-col-md-offset-3{margin-left:12.5%}.do-col-md-pull-3{position:relative;right:12.5%}.do-col-md-push-3{position:relative;left:12.5%}.do-col-md-4{width:16.66667%}.do-col-md-offset-4{margin-left:16.66667%}.do-col-md-pull-4{position:relative;right:16.66667%}.do-col-md-push-4{position:relative;left:16.66667%}.do-col-md-5{width:20.83333%}.do-col-md-offset-5{margin-left:20.83333%}.do-col-md-pull-5{position:relative;right:20.83333%}.do-col-md-push-5{position:relative;left:20.83333%}.do-col-md-6{width:25%}.do-col-md-offset-6{margin-left:25%}.do-col-md-pull-6{position:relative;right:25%}.do-col-md-push-6{position:relative;left:25%}.do-col-md-7{width:29.16667%}.do-col-md-offset-7{margin-left:29.16667%}.do-col-md-pull-7{position:relative;right:29.16667%}.do-col-md-push-7{position:relative;left:29.16667%}.do-col-md-8{width:33.33333%}.do-col-md-offset-8{margin-left:33.33333%}.do-col-md-pull-8{position:relative;right:33.33333%}.do-col-md-push-8{position:relative;left:33.33333%}.do-col-md-9{width:37.5%}.do-col-md-offset-9{margin-left:37.5%}.do-col-md-pull-9{position:relative;right:37.5%}.do-col-md-push-9{position:relative;left:37.5%}.do-col-md-10{width:41.66667%}.do-col-md-offset-10{margin-left:41.66667%}.do-col-md-pull-10{position:relative;right:41.66667%}.do-col-md-push-10{position:relative;left:41.66667%}.do-col-md-11{width:45.83333%}.do-col-md-offset-11{margin-left:45.83333%}.do-col-md-pull-11{position:relative;right:45.83333%}.do-col-md-push-11{position:relative;left:45.83333%}.do-col-md-12{width:50%}.do-col-md-offset-12{margin-left:50%}.do-col-md-pull-12{position:relative;right:50%}.do-col-md-push-12{position:relative;left:50%}.do-col-md-13{width:54.16667%}.do-col-md-offset-13{margin-left:54.16667%}.do-col-md-pull-13{position:relative;right:54.16667%}.do-col-md-push-13{position:relative;left:54.16667%}.do-col-md-14{width:58.33333%}.do-col-md-offset-14{margin-left:58.33333%}.do-col-md-pull-14{position:relative;right:58.33333%}.do-col-md-push-14{position:relative;left:58.33333%}.do-col-md-15{width:62.5%}.do-col-md-offset-15{margin-left:62.5%}.do-col-md-pull-15{position:relative;right:62.5%}.do-col-md-push-15{position:relative;left:62.5%}.do-col-md-16{width:66.66667%}.do-col-md-offset-16{margin-left:66.66667%}.do-col-md-pull-16{position:relative;right:66.66667%}.do-col-md-push-16{position:relative;left:66.66667%}.do-col-md-17{width:70.83333%}.do-col-md-offset-17{margin-left:70.83333%}.do-col-md-pull-17{position:relative;right:70.83333%}.do-col-md-push-17{position:relative;left:70.83333%}.do-col-md-18{width:75%}.do-col-md-offset-18{margin-left:75%}.do-col-md-pull-18{position:relative;right:75%}.do-col-md-push-18{position:relative;left:75%}.do-col-md-19{width:79.16667%}.do-col-md-offset-19{margin-left:79.16667%}.do-col-md-pull-19{position:relative;right:79.16667%}.do-col-md-push-19{position:relative;left:79.16667%}.do-col-md-20{width:83.33333%}.do-col-md-offset-20{margin-left:83.33333%}.do-col-md-pull-20{position:relative;right:83.33333%}.do-col-md-push-20{position:relative;left:83.33333%}.do-col-md-21{width:87.5%}.do-col-md-offset-21{margin-left:87.5%}.do-col-md-pull-21{position:relative;right:87.5%}.do-col-md-push-21{position:relative;left:87.5%}.do-col-md-22{width:91.66667%}.do-col-md-offset-22{margin-left:91.66667%}.do-col-md-pull-22{position:relative;right:91.66667%}.do-col-md-push-22{position:relative;left:91.66667%}.do-col-md-23{width:95.83333%}.do-col-md-offset-23{margin-left:95.83333%}.do-col-md-pull-23{position:relative;right:95.83333%}.do-col-md-push-23{position:relative;left:95.83333%}.do-col-md-24{width:100%}.do-col-md-offset-24{margin-left:100%}.do-col-md-pull-24{position:relative;right:100%}.do-col-md-push-24{position:relative;left:100%}}@media only screen and (min-width:1200px){.do-col-lg-0{display:none;width:0%}.do-col-lg-offset-0{margin-left:0}.do-col-lg-pull-0{position:relative;right:0}.do-col-lg-push-0{position:relative;left:0}.do-col-lg-1{width:4.16667%}.do-col-lg-offset-1{margin-left:4.16667%}.do-col-lg-pull-1{position:relative;right:4.16667%}.do-col-lg-push-1{position:relative;left:4.16667%}.do-col-lg-2{width:8.33333%}.do-col-lg-offset-2{margin-left:8.33333%}.do-col-lg-pull-2{position:relative;right:8.33333%}.do-col-lg-push-2{position:relative;left:8.33333%}.do-col-lg-3{width:12.5%}.do-col-lg-offset-3{margin-left:12.5%}.do-col-lg-pull-3{position:relative;right:12.5%}.do-col-lg-push-3{position:relative;left:12.5%}.do-col-lg-4{width:16.66667%}.do-col-lg-offset-4{margin-left:16.66667%}.do-col-lg-pull-4{position:relative;right:16.66667%}.do-col-lg-push-4{position:relative;left:16.66667%}.do-col-lg-5{width:20.83333%}.do-col-lg-offset-5{margin-left:20.83333%}.do-col-lg-pull-5{position:relative;right:20.83333%}.do-col-lg-push-5{position:relative;left:20.83333%}.do-col-lg-6{width:25%}.do-col-lg-offset-6{margin-left:25%}.do-col-lg-pull-6{position:relative;right:25%}.do-col-lg-push-6{position:relative;left:25%}.do-col-lg-7{width:29.16667%}.do-col-lg-offset-7{margin-left:29.16667%}.do-col-lg-pull-7{position:relative;right:29.16667%}.do-col-lg-push-7{position:relative;left:29.16667%}.do-col-lg-8{width:33.33333%}.do-col-lg-offset-8{margin-left:33.33333%}.do-col-lg-pull-8{position:relative;right:33.33333%}.do-col-lg-push-8{position:relative;left:33.33333%}.do-col-lg-9{width:37.5%}.do-col-lg-offset-9{margin-left:37.5%}.do-col-lg-pull-9{position:relative;right:37.5%}.do-col-lg-push-9{position:relative;left:37.5%}.do-col-lg-10{width:41.66667%}.do-col-lg-offset-10{margin-left:41.66667%}.do-col-lg-pull-10{position:relative;right:41.66667%}.do-col-lg-push-10{position:relative;left:41.66667%}.do-col-lg-11{width:45.83333%}.do-col-lg-offset-11{margin-left:45.83333%}.do-col-lg-pull-11{position:relative;right:45.83333%}.do-col-lg-push-11{position:relative;left:45.83333%}.do-col-lg-12{width:50%}.do-col-lg-offset-12{margin-left:50%}.do-col-lg-pull-12{position:relative;right:50%}.do-col-lg-push-12{position:relative;left:50%}.do-col-lg-13{width:54.16667%}.do-col-lg-offset-13{margin-left:54.16667%}.do-col-lg-pull-13{position:relative;right:54.16667%}.do-col-lg-push-13{position:relative;left:54.16667%}.do-col-lg-14{width:58.33333%}.do-col-lg-offset-14{margin-left:58.33333%}.do-col-lg-pull-14{position:relative;right:58.33333%}.do-col-lg-push-14{position:relative;left:58.33333%}.do-col-lg-15{width:62.5%}.do-col-lg-offset-15{margin-left:62.5%}.do-col-lg-pull-15{position:relative;right:62.5%}.do-col-lg-push-15{position:relative;left:62.5%}.do-col-lg-16{width:66.66667%}.do-col-lg-offset-16{margin-left:66.66667%}.do-col-lg-pull-16{position:relative;right:66.66667%}.do-col-lg-push-16{position:relative;left:66.66667%}.do-col-lg-17{width:70.83333%}.do-col-lg-offset-17{margin-left:70.83333%}.do-col-lg-pull-17{position:relative;right:70.83333%}.do-col-lg-push-17{position:relative;left:70.83333%}.do-col-lg-18{width:75%}.do-col-lg-offset-18{margin-left:75%}.do-col-lg-pull-18{position:relative;right:75%}.do-col-lg-push-18{position:relative;left:75%}.do-col-lg-19{width:79.16667%}.do-col-lg-offset-19{margin-left:79.16667%}.do-col-lg-pull-19{position:relative;right:79.16667%}.do-col-lg-push-19{position:relative;left:79.16667%}.do-col-lg-20{width:83.33333%}.do-col-lg-offset-20{margin-left:83.33333%}.do-col-lg-pull-20{position:relative;right:83.33333%}.do-col-lg-push-20{position:relative;left:83.33333%}.do-col-lg-21{width:87.5%}.do-col-lg-offset-21{margin-left:87.5%}.do-col-lg-pull-21{position:relative;right:87.5%}.do-col-lg-push-21{position:relative;left:87.5%}.do-col-lg-22{width:91.66667%}.do-col-lg-offset-22{margin-left:91.66667%}.do-col-lg-pull-22{position:relative;right:91.66667%}.do-col-lg-push-22{position:relative;left:91.66667%}.do-col-lg-23{width:95.83333%}.do-col-lg-offset-23{margin-left:95.83333%}.do-col-lg-pull-23{position:relative;right:95.83333%}.do-col-lg-push-23{position:relative;left:95.83333%}.do-col-lg-24{width:100%}.do-col-lg-offset-24{margin-left:100%}.do-col-lg-pull-24{position:relative;right:100%}.do-col-lg-push-24{position:relative;left:100%}}@media only screen and (min-width:1920px){.do-col-xl-0{display:none;width:0%}.do-col-xl-offset-0{margin-left:0}.do-col-xl-pull-0{position:relative;right:0}.do-col-xl-push-0{position:relative;left:0}.do-col-xl-1{width:4.16667%}.do-col-xl-offset-1{margin-left:4.16667%}.do-col-xl-pull-1{position:relative;right:4.16667%}.do-col-xl-push-1{position:relative;left:4.16667%}.do-col-xl-2{width:8.33333%}.do-col-xl-offset-2{margin-left:8.33333%}.do-col-xl-pull-2{position:relative;right:8.33333%}.do-col-xl-push-2{position:relative;left:8.33333%}.do-col-xl-3{width:12.5%}.do-col-xl-offset-3{margin-left:12.5%}.do-col-xl-pull-3{position:relative;right:12.5%}.do-col-xl-push-3{position:relative;left:12.5%}.do-col-xl-4{width:16.66667%}.do-col-xl-offset-4{margin-left:16.66667%}.do-col-xl-pull-4{position:relative;right:16.66667%}.do-col-xl-push-4{position:relative;left:16.66667%}.do-col-xl-5{width:20.83333%}.do-col-xl-offset-5{margin-left:20.83333%}.do-col-xl-pull-5{position:relative;right:20.83333%}.do-col-xl-push-5{position:relative;left:20.83333%}.do-col-xl-6{width:25%}.do-col-xl-offset-6{margin-left:25%}.do-col-xl-pull-6{position:relative;right:25%}.do-col-xl-push-6{position:relative;left:25%}.do-col-xl-7{width:29.16667%}.do-col-xl-offset-7{margin-left:29.16667%}.do-col-xl-pull-7{position:relative;right:29.16667%}.do-col-xl-push-7{position:relative;left:29.16667%}.do-col-xl-8{width:33.33333%}.do-col-xl-offset-8{margin-left:33.33333%}.do-col-xl-pull-8{position:relative;right:33.33333%}.do-col-xl-push-8{position:relative;left:33.33333%}.do-col-xl-9{width:37.5%}.do-col-xl-offset-9{margin-left:37.5%}.do-col-xl-pull-9{position:relative;right:37.5%}.do-col-xl-push-9{position:relative;left:37.5%}.do-col-xl-10{width:41.66667%}.do-col-xl-offset-10{margin-left:41.66667%}.do-col-xl-pull-10{position:relative;right:41.66667%}.do-col-xl-push-10{position:relative;left:41.66667%}.do-col-xl-11{width:45.83333%}.do-col-xl-offset-11{margin-left:45.83333%}.do-col-xl-pull-11{position:relative;right:45.83333%}.do-col-xl-push-11{position:relative;left:45.83333%}.do-col-xl-12{width:50%}.do-col-xl-offset-12{margin-left:50%}.do-col-xl-pull-12{position:relative;right:50%}.do-col-xl-push-12{position:relative;left:50%}.do-col-xl-13{width:54.16667%}.do-col-xl-offset-13{margin-left:54.16667%}.do-col-xl-pull-13{position:relative;right:54.16667%}.do-col-xl-push-13{position:relative;left:54.16667%}.do-col-xl-14{width:58.33333%}.do-col-xl-offset-14{margin-left:58.33333%}.do-col-xl-pull-14{position:relative;right:58.33333%}.do-col-xl-push-14{position:relative;left:58.33333%}.do-col-xl-15{width:62.5%}.do-col-xl-offset-15{margin-left:62.5%}.do-col-xl-pull-15{position:relative;right:62.5%}.do-col-xl-push-15{position:relative;left:62.5%}.do-col-xl-16{width:66.66667%}.do-col-xl-offset-16{margin-left:66.66667%}.do-col-xl-pull-16{position:relative;right:66.66667%}.do-col-xl-push-16{position:relative;left:66.66667%}.do-col-xl-17{width:70.83333%}.do-col-xl-offset-17{margin-left:70.83333%}.do-col-xl-pull-17{position:relative;right:70.83333%}.do-col-xl-push-17{position:relative;left:70.83333%}.do-col-xl-18{width:75%}.do-col-xl-offset-18{margin-left:75%}.do-col-xl-pull-18{position:relative;right:75%}.do-col-xl-push-18{position:relative;left:75%}.do-col-xl-19{width:79.16667%}.do-col-xl-offset-19{margin-left:79.16667%}.do-col-xl-pull-19{position:relative;right:79.16667%}.do-col-xl-push-19{position:relative;left:79.16667%}.do-col-xl-20{width:83.33333%}.do-col-xl-offset-20{margin-left:83.33333%}.do-col-xl-pull-20{position:relative;right:83.33333%}.do-col-xl-push-20{position:relative;left:83.33333%}.do-col-xl-21{width:87.5%}.do-col-xl-offset-21{margin-left:87.5%}.do-col-xl-pull-21{position:relative;right:87.5%}.do-col-xl-push-21{position:relative;left:87.5%}.do-col-xl-22{width:91.66667%}.do-col-xl-offset-22{margin-left:91.66667%}.do-col-xl-pull-22{position:relative;right:91.66667%}.do-col-xl-push-22{position:relative;left:91.66667%}.do-col-xl-23{width:95.83333%}.do-col-xl-offset-23{margin-left:95.83333%}.do-col-xl-pull-23{position:relative;right:95.83333%}.do-col-xl-push-23{position:relative;left:95.83333%}.do-col-xl-24{width:100%}.do-col-xl-offset-24{margin-left:100%}.do-col-xl-pull-24{position:relative;right:100%}.do-col-xl-push-24{position:relative;left:100%}}\n"
  },
  {
    "path": "Web/packages/core/src/common/components/dokit-ui/layout/col.js",
    "content": "/* eslint-disable */\nimport { defineComponent, h, computed, inject } from 'vue';\nexport default defineComponent({\n  name: 'DoCol',\n  componentName: 'DoCol',\n  props: {\n    /**\n     * 网格跨度的列数\n     * 默认值：24\n     */\n    span: {\n      type: Number,\n      default: 24\n    },\n    /**\n     * 自定义元素标签\n     * 默认值：div\n     */\n    tag: {\n      type: String,\n      default: 'div'\n    },\n    /**\n     * 网格左侧的间距数\n     * 默认值：0\n     */\n    offset: Number,\n    /**\n     * 网格向左移动的列数\n     * 默认值：0\n     */\n    pull: Number,\n    /**\n     * 网格向右移动的列数\n     * 默认值：0\n     */\n    push: Number,\n    /**\n     * <768px 响应列 | {span, offset}对象\n     */\n    // @ts-ignore\n    xs: [Number, Object],\n    /**\n     * ≥768px 响应列 | {span, offset}对象\n     */\n    // @ts-ignore\n    sm: [Number, Object],\n    /**\n     * ≥992px 响应列 | {span, offset}对象\n     */\n    // @ts-ignore\n    md: [Number, Object],\n    /**\n     * ≥1200px 响应列 | {span, offset}对象\n     */\n    // @ts-ignore\n    lg: [Number, Object],\n    /**\n     * ≥1920px 响应列 | {span, offset}对象\n     */\n    // @ts-ignore\n    xl: [Number, Object]\n  },\n\n  setup(props, { slots }) {\n    let gutter = computed(() => {\n      return inject('gutter', 0);\n    });\n\n    let classList = [];\n    let style = {\n      paddingLeft: '',\n      paddingRight: ''\n    };\n    if (gutter) {\n      style.paddingLeft = gutter.value / 2 + 'px';\n      style.paddingRight = style.paddingLeft;\n    }\n    ['span', 'offset', 'pull', 'push'].forEach(prop => {\n      if (props[prop] || props[prop] === 0) {\n        classList.push(prop !== 'span' ? `do-col-${prop}-${props[prop]}` : `do-col-${props[prop]}`);\n      }\n    });\n    ['xs', 'sm', 'md', 'lg', 'xl'].forEach(size => {\n      if (typeof props[size] === 'number') {\n        classList.push(`do-col-${size}-${props[size]}`);\n      } else if (typeof props[size] === 'object') {\n        let iProps = props[size];\n        Object.keys(iProps).forEach(prop => {\n          classList.push(\n            prop !== 'span'\n              ? `do-col-${size}-${prop}-${iProps[prop]}`\n              : `do-col-${size}-${iProps[prop]}`\n          );\n        });\n      }\n    });\n    return () =>\n      h(\n        props.tag,\n        {\n          class: ['do-col', classList],\n          style\n        },\n        slots\n      );\n  }\n});"
  },
  {
    "path": "Web/packages/core/src/common/components/dokit-ui/layout/index.js",
    "content": "// respect!!!     https://github.com/lgd8981289/Ale\n\nimport Col from './col'\nimport Row from './row'\n\nexport {\n  Col,\n  Row\n}"
  },
  {
    "path": "Web/packages/core/src/common/components/dokit-ui/layout/row.css",
    "content": ".do-row{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box}.do-row::after,.do-row::before{display:table;content:\"\"}.do-row::after{clear:both}.do-row--flex{display:-webkit-box;display:-ms-flexbox;display:flex}.do-row--flex:after,.do-row--flex:before{display:none}.do-row--flex.is-justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.do-row--flex.is-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.do-row--flex.is-justify-space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.do-row--flex.is-justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.do-row--flex.is-align-middle{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.do-row--flex.is-align-bottom{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}\n"
  },
  {
    "path": "Web/packages/core/src/common/components/dokit-ui/layout/row.js",
    "content": "/* eslint-disable */\nimport { defineComponent, h, computed, provide } from 'vue';\n\nexport default defineComponent({\n  name: 'DoRow',\n  componentName: 'DoRow',\n  props: {\n    /**\n     * 自定义元素标签\n     * 默认值：div\n     */\n    tag: {\n      type: String,\n      default: 'div'\n    },\n    /**\n     * 栅格间距\n     */\n    gutter: Number,\n    /**\n     * 布局模式，你可以使用 flex 布局\n     */\n    type: {\n      type: String,\n      default: 'flex'\n    },\n    /**\n     * flex 布局的水平对齐\n     * start（默认值）\n     * end\n     * center\n     * space-around\n     * space-between\n     */\n    justify: {\n      type: String,\n      default: 'start'\n    },\n\n    /**\n     * flex 布局的垂直对齐\n     * top（默认值）\n     * middle\n     * bottom\n     */\n    align: {\n      type: String,\n      default: 'top'\n    }\n  },\n  setup(props, { slots }) {\n    let style = computed(() => {\n      const ret = {};\n\n      if (props.gutter) {\n        ret.marginLeft = `-${props.gutter / 2}px`;\n        ret.marginRight = ret.marginLeft;\n      }\n\n      return ret;\n    });\n\n    provide('gutter', props.gutter);\n\n    return () =>\n      h(\n        props.tag,\n        {\n          class: [\n            'do-row',\n            props.justify !== 'start' ? `is-justify-${props.justify}` : '',\n            props.align !== 'top' ? `is-align-${props.align}` : '',\n            { 'do-row--flex': props.type === 'flex' }\n          ],\n          style\n        },\n        slots\n      );\n  }\n});"
  },
  {
    "path": "Web/packages/core/src/common/components/toast/index.js",
    "content": "import toastcom from './toast.vue'\nimport { createApp } from \"vue\"\nconst toast = {\n}\ntoast.install = vue => {\n  const ins = createApp(toastcom)\n  let parent = document.createElement(\"div\")\n  ins.mount(parent)\n  document.body.appendChild(parent)\n  vue.config.globalProperties.$toast = (msg, duration = 3000) => {\n    ins._instance.ctx.setMessage(msg)\n    ins._instance.ctx.showToast()\n    setTimeout(() => {\n      ins._instance.ctx.closeToast()\n    }, duration)\n  }\n}\nexport default toast\n"
  },
  {
    "path": "Web/packages/core/src/common/components/toast/toast.vue",
    "content": "<template>\n  <transition name=\"alert-fade\">\n    <div id=\"dokit-toast\"\n         v-show=\"visible\"\n         class=\"dokit-dialog-tips dokit-dialog-center\">\n      {{message}}\n    </div>\n  </transition>\n</template>\n<script>\nexport default {\n  data () {\n    return {\n      visible: false,\n      message: ''\n    }\n  },\n  methods: {\n     showToast(){\n        this.visible = true\n     },\n     closeToast(){\n      this.visible = false\n     },\n     setMessage(val){\n       this.message = val\n     }\n  },\n}\n</script>\n<style lang=\"less\" scoped>\n.alert-fade-enter-active,\n.alert-fade-leave-active {\n  transition: opacity 0.3s;\n}\n.alert-fade-enter,\n.alert-fade-leave-to {\n  opacity: 0;\n}\n.dokit-dialog-tips {\n  position: fixed;\n  z-index: 100001;\n  max-width: 70%;\n  padding: 8px 12px;\n  border-radius: 15px;\n  white-space: nowrap;\n  background-color: rgba(0, 0, 0, 0.5);\n  box-shadow: 0px 8px 30px 0 rgba(0, 0, 0, 0.363);\n  text-align: center;\n  color: #ffffff;\n}\n.dokit-dialog-center {\n  top: 20%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n}\n</style>"
  },
  {
    "path": "Web/packages/core/src/common/components/top-bar.vue",
    "content": "<template>\n  <div class=\"dokit-bar\">\n    <div class=\"dokit-bar-back\" @click=\"handleBackRoute\" v-show=\"canBack\">\n      <img class=\"dokit-bar-back-icon\" :src=\"icon\"/>\n      <span class=\"dokit-bar-back-btn\">返回</span>\n    </div>\n    <div class=\"dokit-bar-title\">\n      <span class=\"dokit-bar-title-text\">{{title}}</span>\n    </div>\n    <!-- TODO 支持切换模式 -->\n    <!-- <div class=\"bar-other\">\n      <span class=\"bar-other-text\">更多</span>\n    </div> -->\n  </div>\n</template>\n<script>\nimport {IconBack} from '../js/icon';\nexport default {\n  props: {\n    title: {\n      default: 'DoKit'\n    },\n    canBack:{\n      default: true\n    }\n  },\n  data(){\n    return {\n      icon: IconBack\n    }\n  },\n  methods: {\n    handleBackRoute(){\n      if(this.$emit('back')){\n        // 支持拦截 back\n      }else {\n        this.$router.back()\n      }\n    }\n  }\n}\n</script>\n<style lang=\"less\" scoped>\n  .dokit-bar{\n    background-color: white;\n    height: 50px;\n    width: 100%;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    padding: 0 10px;\n    box-sizing: border-box;\n    position: relative;\n    border-radius: 10px 10px 0 0;\n  }\n  .dokit-bar-back{\n    position: absolute;\n    left: 10px;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n  }\n  .dokit-bar-back-icon{\n    display: inline-block;\n    height: 18px;\n  }\n  .dokit-bar-back-btn{\n    color: #337CC4;\n    font-size: 16px;\n    margin-left: 5px;\n  }\n  .dokit-bar-title-text{\n    color: #333333;\n    font-size: 20px;\n    font-weight: bold;\n  }\n  .dokit-bar-other-text{\n    color:  #666666;\n    font-size: 16px;\n  }\n</style>"
  },
  {
    "path": "Web/packages/core/src/common/components/version.vue",
    "content": "<template>\n  <div class=\"dokit-card dokit-version\">\n    <div>\n      <span class=\"dokit-version-text\">当前版本：V{{version}}</span>\n    </div>\n    <div class=\"dokit-version-image\">\n      <img class=\"dokit-icon\" :src=\"dokitIcon\"/>\n    </div>\n  </div>\n</template>\n<script>\nimport {dokitIcon} from '../js/icon'\nexport default {\n  props: {\n    version: String\n  },\n  data(){\n    return {\n      dokitIcon: dokitIcon\n    }\n  }\n}\n</script>\n<style lang=\"less\" scoped>\n.dokit-card {\n  padding: 10px;\n  background-color: white;\n}\n.dokit-version{\n  padding: 20px 0;\n  text-align: center;\n  .dokit-version-text{\n    font-size: 16px;\n    color: #999999;\n  }\n  .dokit-version-image{\n    margin-top: 20px;\n  }\n  .dokit-icon{\n    width: 150px;\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/core/src/common/js/EventPlayback.js",
    "content": "import {\n    getGlobalData\n} from '../../store'\nimport mySocket from './socket'\nexport default class EventPlayback {\n    constructor(url) {\n        this.socketUrl = url\n        this.state = getGlobalData();\n        this.init()\n        this.isAutoTest = false\n    }\n    init() {\n        !this.state.mySocket && (this.state.mySocket = new mySocket(this.socketUrl))\n        if (this.state.mySocket && this.state.mySocket.socket) {\n            this.state.startPlayback = true\n            this.state.mySocket.onmessage((e) => {\n                try {\n                    let data, msg = JSON.parse(e.data);\n                    if (msg.type === \"LOGIN\") {\n                        data = JSON.parse(msg.data)\n                        this.state.connectSerial = data.connectSerial\n                        if(this.state.isNative){\n                            localStorage.setItem('nativeConnectSerial', data.connectSerial)\n                        }\n                    } else if (msg.type === \"BROADCAST\") {\n                        data = JSON.parse(msg.data)\n\n                        console.log(data)\n                        if (msg.contentType === 'action') {\n                            this.state.aid = data.eventId\n                            if (this.state.startPlayback) {\n                                const nodeOps = () => {\n                                    let node = document.querySelector(data.viewC12c.viewPath)\n                                    let event;\n                                    switch (data.viewC12c.actionType) {\n                                        // case \"navigatorBack\":\n                                        //     window.history.back()\n                                        //     break;\n                                        case \"ON_CLICK\":\n                                            node.click();\n                                            break;\n                                        case \"ON_TOUCH_START\":\n                                            event = document.createEvent('MouseEvents');\n                                            event.initMouseEvent('touchstart', true, true, window, 0, data.viewC12c.cloneEvent.targetTouches.screenX, data.viewC12c.cloneEvent.targetTouches.screenY, data.viewC12c.cloneEvent.targetTouches.clientX, data.viewC12c.cloneEvent.targetTouches.clientY, data.viewC12c.cloneEvent.ctrlKey, data.viewC12c.cloneEvent.altKey, data.viewC12c.cloneEvent.shiftKey, data.viewC12c.cloneEvent.metaKey);\n                                            event.changedTouches = [{\n                                                clientX: data.viewC12c.cloneEvent.targetTouches.clientX,\n                                                clientY: data.viewC12c.cloneEvent.targetTouches.clientY,\n                                                force: data.viewC12c.cloneEvent.targetTouches.force,\n                                                identifier: data.viewC12c.cloneEvent.targetTouches.identifier,\n                                                pageX: data.viewC12c.cloneEvent.targetTouches.pageX,\n                                                pageY: data.viewC12c.cloneEvent.targetTouches.pageY,\n                                                radiusX: data.viewC12c.cloneEvent.targetTouches.radiusX,\n                                                radiusY: data.viewC12c.cloneEvent.targetTouches.radiusY,\n                                                rotationAngle: data.viewC12c.cloneEvent.targetTouches.rotationAngle,\n                                                screenX: data.viewC12c.cloneEvent.targetTouches.screenX,\n                                                screenY: data.viewC12c.cloneEvent.targetTouches.screenY\n                                            }]\n                                            event.touches = event.changedTouches\n                                            event.targetTouches = event.changedTouches\n                                            console.log('fyq', event)\n                                            node.dispatchEvent(event);\n                                            break;\n                                        case \"ON_TOUCH_MOVE\":\n                                            event = document.createEvent('MouseEvents');\n                                            event.initMouseEvent('touchmove', true, true, window, 0, data.viewC12c.cloneEvent.targetTouches.screenX, data.viewC12c.cloneEvent.targetTouches.screenY, data.viewC12c.cloneEvent.targetTouches.clientX, data.viewC12c.cloneEvent.targetTouches.clientY, data.viewC12c.cloneEvent.ctrlKey, data.viewC12c.cloneEvent.altKey, data.viewC12c.cloneEvent.shiftKey, data.viewC12c.cloneEvent.metaKey);\n                                            event.changedTouches = [{\n                                                clientX: data.viewC12c.cloneEvent.targetTouches.clientX,\n                                                clientY: data.viewC12c.cloneEvent.targetTouches.clientY,\n                                                force: data.viewC12c.cloneEvent.targetTouches.force,\n                                                identifier: data.viewC12c.cloneEvent.targetTouches.identifier,\n                                                pageX: data.viewC12c.cloneEvent.targetTouches.pageX,\n                                                pageY: data.viewC12c.cloneEvent.targetTouches.pageY,\n                                                radiusX: data.viewC12c.cloneEvent.targetTouches.radiusX,\n                                                radiusY: data.viewC12c.cloneEvent.targetTouches.radiusY,\n                                                rotationAngle: data.viewC12c.cloneEvent.targetTouches.rotationAngle,\n                                                screenX: data.viewC12c.cloneEvent.targetTouches.screenX,\n                                                screenY: data.viewC12c.cloneEvent.targetTouches.screenY\n                                            }]\n                                            event.touches = event.changedTouches\n                                            event.targetTouches = event.changedTouches\n                                            node.dispatchEvent(event);\n                                            break;\n                                        case \"ON_TOUCH_END\":\n                                            event = document.createEvent('MouseEvents');\n                                            event.initMouseEvent('touchend', true, true, window, 0, data.viewC12c.cloneEvent.changedTouches.screenX, data.viewC12c.cloneEvent.changedTouches.screenY, data.viewC12c.cloneEvent.changedTouches.clientX, data.viewC12c.cloneEvent.changedTouches.clientY, data.viewC12c.cloneEvent.ctrlKey, data.viewC12c.cloneEvent.altKey, data.viewC12c.cloneEvent.shiftKey, data.viewC12c.cloneEvent.metaKey);\n                                            event.changedTouches = [{\n                                                clientX: data.viewC12c.cloneEvent.targetTouches.clientX,\n                                                clientY: data.viewC12c.cloneEvent.targetTouches.clientY,\n                                                force: data.viewC12c.cloneEvent.targetTouches.force,\n                                                identifier: data.viewC12c.cloneEvent.targetTouches.identifier,\n                                                pageX: data.viewC12c.cloneEvent.targetTouches.pageX,\n                                                pageY: data.viewC12c.cloneEvent.targetTouches.pageY,\n                                                radiusX: data.viewC12c.cloneEvent.targetTouches.radiusX,\n                                                radiusY: data.viewC12c.cloneEvent.targetTouches.radiusY,\n                                                rotationAngle: data.viewC12c.cloneEvent.targetTouches.rotationAngle,\n                                                screenX: data.viewC12c.cloneEvent.targetTouches.screenX,\n                                                screenY: data.viewC12c.cloneEvent.targetTouches.screenY\n                                            }]\n                                            event.touches = event.changedTouches\n                                            event.targetTouches = event.changedTouches\n                                            node.dispatchEvent(event);\n                                            break;\n                                        case \"ON_INPUT_CHANGE\":\n                                            event = document.createEvent('Events');\n                                            event.initEvent('input', true, true);\n                                            node.value = data.viewC12c.inputValue;\n                                            node.dispatchEvent(event);\n                                            break;\n                                        case \"ON_SCROLL\":\n                                            node.scrollTop = data.viewC12c.scrollX;\n                                            node.scrollLeft = data.viewC12c.scrollY;\n                                            break;\n                                        default:\n                                            break;\n                                    }\n                                }\n                                if (this.isAutoTest) {\n                                    setTimeout(() => {\n                                        nodeOps()\n                                        this.state.mySocket.send({\n                                            type: 'AUTOTEST',\n                                            contentType:'action_response',\n                                            channelSerial: this.state.channelSerial,\n                                            data: JSON.stringify({ command: 'action_response',message: 'success', params: {eventId: data.eventId}})\n                                        })\n                                    }, data.diffTime);\n                                } else {\n                                    nodeOps()\n                                }\n                            }\n                        } else if (msg.contentType === 'mc_host') {\n                            this.state.isHost = false;\n                            this.state.startPlayback = true\n                        }\n                    } else if (msg.type === \"AUTOTEST\") {\n                        console.log('message', msg)\n                        data = JSON.parse(msg.data)\n                        if (msg.contentType === 'auto_test_control') {\n                            if (data.command === 'startAutoTest' || data.command === 'stopAutoTest') {\n                                this.isAutoTest = data.command === 'startAutoTest' ? true : false\n                                this.state.mySocket.send({\n                                    type: 'AUTOTEST',\n                                    contentType:'control_response',\n                                    channelSerial: this.state.channelSerial,\n                                    data: JSON.stringify({...data, command: 'control_response',message: 'success', params: {command:data.command}})\n                                })\n                            }\n                        }\n                    }\n                } catch (error) {\n                    console.error(error);\n                }\n            })\n        }\n    }\n    close() {\n        this.state.mySocket && (this.state.mySocket.close(), this.state.mySocket = null)\n        this.state.startPlayback = false\n    }\n}"
  },
  {
    "path": "Web/packages/core/src/common/js/EventRecorder.js",
    "content": "import {\n  throttle,\n  debounce,\n  uuid,\n  ActionObject,\n} from './util'\nimport xpath from './xpath/index';\nimport cssesc from 'cssesc';\nimport finder from './finder';\nimport eventsToRecord from './dom-events-to-record';\nimport UIController from './UIController';\nimport getNodeText from './node';\nimport moment from 'moment'\nimport {\n  getGlobalData\n} from '../../store'\nvar xpathFinder;\nif (document.readyState === \"loading\") {\n  // window.addEventListener(\"load\", function (_event) {\n  //   xpathFinder = xpath()\n  // });\n  document.addEventListener(\"readystatechange\", function () {\n    if (document.readyState === \"interactive\") {\n      xpathFinder = xpath()\n    }\n  });\n}else{\n  xpathFinder = xpath()\n}\nexport default class EventRecorder {\n  constructor(socketUrl) {\n    this.state = getGlobalData();\n    this._boundedMessageListener = null;\n    this._eventErrLog = [];\n    this._eventLog = [];\n    this._eventSendLog = [];\n    this._previousEvent = null;\n    this._dataAttribute = null;\n    this._uiController = null;\n    this._screenShotMode = false;\n    this._isTopFrame = (window.location === window.parent.location);\n    this._isRecordingClicks = true;\n    this.socketUrl = socketUrl;\n    this.observer = null;\n    this.inputEventListenerFun = debounce(this._recordEvent.bind(this), 200);\n  }\n\n  boot() {\n    this.state.startRecorder = true\n    if (document.readyState === \"loading\") {\n      document.addEventListener(\"readystatechange\", ()=>{\n        if (document.readyState === \"complete\") {\n          this._initializeRecorder();\n          this.ovserverDom();\n        }\n      });\n    }else{\n      this._initializeRecorder();\n      this.ovserverDom()\n    }\n  }\n\n  off() {\n    this.state.startRecorder = false\n    this.observer.disconnect()\n  }\n  ovserverDom(){\n    this.observer = new MutationObserver((mutations) => {\n      for (let i = 0; i < mutations.length; i++) {\n        let mutation = mutations[i];\n        if(mutation.addedNodes.length>0){\n          let inputList = document.getElementsByTagName(\"input\");\n          let textareaList = document.getElementsByTagName(\"textarea\");\n          let list = [...inputList, ...textareaList];\n          list.forEach((item) => {\n            switch (item.type) {\n              case 'text':\n              case 'number':\n              case 'password':\n              case 'textarea':\n                item.addEventListener('input', this.inputEventListenerFun, true);\n              default:\n                break;\n            }\n          })\n        }\n      }\n    });\n    this.observer.observe(document.body, {\n      childList: true,\n      subtree: true,\n    });\n  }\n\n  _initializeRecorder() {\n    // console.debug('event recorder init:', this._isTopFrame);\n    const events = Object.values(eventsToRecord);\n    const popstateCallback = (e) =>{\n      console.log('页面返回:',e);\n      const msg = {\n        type: 'action',\n        action: 'navigatorBack',\n        timestamp: new Date().getTime(),\n        trudid: uuid(),\n        location: window.location ? window.location : null,\n      };\n      this._eventLog.push(msg);\n      this._sendMessage(msg);\n    }\n    if (!window.screenRecorderAddedControlListeners) {\n      console.log('_addAllListeners:', events);\n      this._addAllListeners(events);\n      // window.addEventListener('popstate', popstateCallback);\n      // this._boundedMessageListener = this._boundedMessageListener || this._handleBackgroundMessage.bind(this);\n      // chrome.runtime.onMessage.addListener(this._boundedMessageListener);\n      window.screenRecorderAddedControlListeners = true;\n    }\n\n    if (!window.document.screenRecorderAddedControlListeners) {\n      window.document.screenRecorderAddedControlListeners = true;\n    }\n\n    // if (this._isTopFrame) {\n    //   this._sendMessage({\n    //     control: ctrl.EVENT_RECORDER_STARTED\n    //   });\n    //   this._sendMessage({\n    //     control: ctrl.GET_CURRENT_URL,\n    //     href: window.location.href\n    //   });\n    //   this._sendMessage({\n    //     control: ctrl.GET_VIEWPORT_SIZE,\n    //     coordinates: {\n    //       width: window.innerWidth,\n    //       height: window.innerHeight\n    //     }\n    //   });\n    // }\n  }\n\n  // _handleBackgroundMessage(msg, sender, sendResponse) {\n  //   console.debug('content-script 22: message from background', msg);\n  //   if (msg && msg.action) {\n  //     switch (msg.action) {\n  //       case actions.TOGGLE_SCREENSHOT_MODE:\n  //         this._handleScreenshotMode(false);\n  //         break;\n  //       case actions.TOGGLE_SCREENSHOT_CLIPPED_MODE:\n  //         this._handleScreenshotMode(true);\n  //         break;\n  //       default:\n  //     }\n  //   }\n  // }\n\n  _addAllListeners(events) {\n    events.forEach((type) => {\n      let boundedRecordEvent = this._recordEvent.bind(this);\n      console.log('type:',type)\n      if (type === 'input') {\n        let inputList = document.getElementsByTagName(\"input\");\n        let textareaList = document.getElementsByTagName(\"textarea\");\n        let list = [...inputList, ...textareaList];\n        console.log('inputList:',list)\n        list.forEach((item) => {\n          console.log(item.type)\n          switch (item.type) {\n            case 'text':\n            case 'number':\n            case 'password':\n            case 'textarea':\n              item.addEventListener('input', this.inputEventListenerFun, true);\n            default:\n              break;\n          }\n        })\n        return;\n      }\n      if (type === 'touchmove') {\n        boundedRecordEvent = throttle(boundedRecordEvent, 50);\n      }\n      if (type === 'scroll') {\n        boundedRecordEvent = throttle(boundedRecordEvent, 50);\n      }\n      window.addEventListener(type, boundedRecordEvent, true);\n    })\n  }\n\n  _sendMessage(msg) {\n    if (this.state.mySocket && this.state.mySocket.webSocketState) {\n      console.log('send message:', msg, window.eventRecorder);\n      this.state.mySocket.send({\n        type: 'BROADCAST',\n        contentType:'action',\n        channelSerial: this.state.channelSerial,\n        data: JSON.stringify({\n          ...msg,\n          connectSerial: this.state.connectSerial\n        })\n      })\n      this._eventSendLog.push(msg);\n    }\n  }\n\n  _recordEvent(e) {\n    this.state.aid = uuid()\n    // console.log('startRecorder:',this.state.startRecorder)\n    if (!this.state.startRecorder) {\n      return;\n    }\n    try {\n      // console.log(e);\n      let selector = '';\n      // let dangerSelector = '';\n      let el = e.target;\n      if (e.target === document) {\n        selector = 'html';\n        el = e.target.scrollingElement;\n      } else {\n        const optimizedMinLength = (e.target.id) ? 2 : 10;\n        selector = this._dataAttribute && e.target.hasAttribute && e.target.hasAttribute(this._dataAttribute) ?\n          EventRecorder._formatDataSelector(e.target, this._dataAttribute) :\n          xpathFinder.find(e.target);\n        // dangerSelector = finder(e.target, {\n        //   seedMinLength: 5,\n        //   optimizedMinLength,\n        //   className: (className, input) => {\n        //     if (input.tagName.toLowerCase() === 'body') {\n        //       return false;\n        //     }\n        //     return true;\n        //   },\n        // });\n        // console.log('selector:',selector)\n      }\n      if (selector.indexOf('body')<0&&selector!=='html') {\n        return;\n      }\n      let attrs = {};\n      if (e.type === 'scroll') {\n        attrs = {\n          scrollLeft: el.scrollLeft,\n          scrollTop: el.scrollTop,\n        };\n      }\n      const names = Array.from(e.target.classList || [])\n        .map(cName => `.${cssesc(cName, { isIdentifier: true })}`);\n      const cloneEvent = {}\n      switch (e.type) {\n        case 'click':\n        case 'click':\n        case 'touchstart':\n        case 'touchmove':\n        case 'touchend':\n          cloneEvent.altKey = e?.altKey\n          cloneEvent.metaKey = e?.metaKey\n          cloneEvent.ctrlKey = e?.ctrlKey\n          e?.targetTouches&&(cloneEvent.targetTouches={\n            clientX: e?.targetTouches[0]?.clientX,\n            clientY: e?.targetTouches[0]?.clientY,\n            force: e?.targetTouches[0]?.force,\n            identifier: e?.targetTouches[0]?.identifier,\n            pageX: e?.targetTouches[0]?.pageX,\n            pageY: e?.targetTouches[0]?.pageY,\n            radiusX: e?.targetTouches[0]?.radiusX,\n            radiusY: e?.targetTouches[0]?.radiusY,\n            rotationAngle: e?.targetTouches[0]?.rotationAngle,\n            screenX: e?.targetTouches[0]?.screenX,\n            screenY: e?.targetTouches[0]?.screenY\n          })\n          e?.changedTouches&&(cloneEvent.changedTouches={\n            clientX: e?.changedTouches[0]?.clientX,\n            clientY: e?.changedTouches[0]?.clientY,\n            force: e?.changedTouches[0]?.force,\n            identifier: e?.changedTouches[0]?.identifier,\n            pageX: e?.changedTouches[0]?.pageX,\n            pageY: e?.changedTouches[0]?.pageY,\n            radiusX: e?.changedTouches[0]?.radiusX,\n            radiusY: e?.changedTouches[0]?.radiusY,\n            rotationAngle: e?.changedTouches[0]?.rotationAngle,\n            screenX: e?.changedTouches[0]?.screenX,\n            screenY: e?.changedTouches[0]?.screenY\n          })\n          break;\n        default:\n          break;\n      }\n      let dateTime = new Date().getTime();\n      let coordinates = EventRecorder._getCoordinates(e);\n      const msg = {\n        eventId: uuid(),\n        eventType:'VIEW_COMMON_EVENT',\n        dateTime: moment(dateTime).format(\"YYYY-MM-DD HH:mm:ss.SSS\"),\n        diffTime:this.state.actionTime?(dateTime - this.state.actionTime):0,\n        params:{},\n        viewC12c:{\n          actionType: ActionObject[e.type].actionType,\n          actionName: ActionObject[e.type].actionName,\n          params:{},\n          viewPath:selector,\n          viewPathDetail:selector,\n          text:getNodeText(el),\n          touchX:coordinates?.x,\n          touchY:coordinates?.y,\n          scrollX:attrs?.scrollTop,\n          scrollY:attrs?.scrollLeft,\n          inputValue:el?.value,\n          position:this.getPositon(el),\n          cloneEvent,\n        },\n        // type: 'action',\n        // e: cloneEvent,\n        // selector,\n        // aid: this.state.aid,\n        // className: names.join(''),\n        // value: el.value,\n        // innerHTML: getNodeText(el),\n        // tagName: el.tagName,\n        // action: e.type,\n        // keyCode: e.keyCode ? e.keyCode : null,\n        // href: el.href ? el.href : null,\n        // coordinates: EventRecorder._getCoordinates(e),\n        // position: this.getPositon(el),\n        // location: window.location ? window.location : null,\n        // ...attrs,\n      };\n      this.state.actionTime = dateTime\n      this._eventLog.push(msg);\n      this._sendMessage(msg);\n    } catch (event) {\n      this._eventErrLog.push(event);\n      console.log('[eventErrLog]:', event);\n    }\n  }\n\n  getPositon(elem) {\n    let top = -window.scrollY;\n    let left = -window.scrollX;\n    const width = elem.offsetWidth;\n    const height = elem.offsetHeight;\n\n    while (elem && elem !== document.body) {\n      top += elem.offsetTop;\n      left += elem.offsetLeft;\n      elem = elem.offsetParent;\n    }\n    return {\n      width: `${width}px`,\n      height: `${height}px`,\n      top: `${top - 1}px`,\n      left: `${left - 1}px`,\n    };\n  }\n\n  _getEventLog() {\n    return this._eventLog;\n  }\n\n  _clearEventLog() {\n    this._eventLog = [];\n  }\n\n  // _handleScreenshotMode(isClipped) {\n  //   this._disableClickRecording();\n  //   this._uiController = new UIController({\n  //     showSelector: isClipped\n  //   });\n  //   this._screenShotMode = !this._screenShotMode;\n  //   document.body.style.cursor = 'crosshair';\n\n  //   console.debug('screenshot mode:', this._screenShotMode);\n\n  //   if (this._screenShotMode) {\n  //     this._uiController.showSelector();\n  //   } else {\n  //     this._uiController.hideSelector();\n  //   }\n\n  //   this._uiController.on('click', (event) => {\n  //     this._screenShotMode = false;\n  //     document.body.style.cursor = DEFAULT_MOUSE_CURSOR;\n  //     this._sendMessage({\n  //       control: ctrl.GET_SCREENSHOT,\n  //       value: event.clip\n  //     });\n  //     this._enableClickRecording();\n  //   });\n  // }\n\n  // _disableClickRecording() {\n  //   this._isRecordingClicks = false;\n  // }\n\n  // _enableClickRecording() {\n  //   this._isRecordingClicks = true;\n  // }\n\n  sendEventRight() {\n    return this._eventLog.length === this._eventSendLog.length;\n  }\n\n  static _getCoordinates(evt) {\n    const eventsWithCoordinates = {\n      mouseup: true,\n      mousedown: true,\n      mousemove: true,\n      mouseover: true,\n      touchstart: true,\n      touchend: true,\n      touchmove: true,\n    };\n    return eventsWithCoordinates[evt.type] ? {\n      x: evt.clientX || evt.changedTouches[0].clientX,\n      y: evt.clientY || evt.changedTouches[0].clientY\n    } : null;\n  }\n\n  static _formatDataSelector(element, attribute) {\n    return `[${attribute}=\"${element.getAttribute(attribute)}\"]`;\n  }\n}"
  },
  {
    "path": "Web/packages/core/src/common/js/UIController.js",
    "content": "// import {\n//   EventEmitter\n// } from 'events';\nimport {\n  EventEmitter\n} from \"@dokit/web-utils\";\n\nconst BORDER_THICKNESS = 3;\n\nconst defaults = {\n  showSelector: false,\n};\n\nclass UIController extends EventEmitter {\n  constructor(options) {\n    options = Object.assign({}, defaults, options);\n\n    super();\n    this._overlay = null;\n    this._selector = null;\n    this._element = null;\n    this._dimensions = {};\n    this._showSelector = options.showSelector;\n\n    this._boundeMouseMove = this._mousemove.bind(this);\n    this._boundeMouseUp = this._mouseup.bind(this);\n  }\n\n  showSelector() {\n    console.debug('UIController:show');\n    if (!this._overlay) {\n      this._overlay = document.createElement('div');\n      this._overlay.className = 'pptrRecorderOverlay';\n      this._overlay.style.position = 'fixed';\n      this._overlay.style.top = '0px';\n      this._overlay.style.left = '0px';\n      this._overlay.style.width = '100%';\n      this._overlay.style.height = '100%';\n      this._overlay.style.pointerEvents = 'none';\n\n      if (this._showSelector) {\n        this._selector = document.createElement('div');\n        this._selector.className = 'pptrRecorderOutline';\n        this._selector.style.position = 'fixed';\n        this._selector.style.border = `${BORDER_THICKNESS}px solid rgba(69,200,241,0.8)`;\n        this._selector.style.borderRadius = '3px';\n        this._overlay.appendChild(this._selector);\n      }\n    }\n    if (!this._overlay.parentNode) {\n      document.body.appendChild(this._overlay);\n      document.body.addEventListener('mousemove', this._boundeMouseMove, false);\n      document.body.addEventListener('click', this._boundeMouseUp, false);\n    }\n  }\n\n  hideSelector() {\n    console.debug('UIController:hide');\n    if (this._overlay) {\n      document.body.removeChild(this._overlay);\n    }\n    this._overlay = this._selector = this._element = null;\n    this._dimensions = {};\n  }\n\n  _mousemove(e) {\n    if (this._element !== e.target) {\n      this._element = e.target;\n\n      this._dimensions.top = -window.scrollY;\n      this._dimensions.left = -window.scrollX;\n\n      let elem = e.target;\n\n      while (elem && elem !== document.body) {\n        this._dimensions.top += elem.offsetTop;\n        this._dimensions.left += elem.offsetLeft;\n        elem = elem.offsetParent;\n      }\n      this._dimensions.width = this._element.offsetWidth;\n      this._dimensions.height = this._element.offsetHeight;\n\n      if (this._selector) {\n        this._selector.style.top = `${this._dimensions.top - BORDER_THICKNESS}px`;\n        this._selector.style.left = `${this._dimensions.left - BORDER_THICKNESS}px`;\n        this._selector.style.width = `${this._dimensions.width}px`;\n        this._selector.style.height = `${this._dimensions.height}px`;\n        console.debug(`top: ${this._selector.style.top}, left: ${this._selector.style.left}, width: ${this._selector.style.width}, height: ${this._selector.style.height}`);\n      }\n    }\n  }\n\n  _mouseup(e) {\n    this._overlay.style.backgroundColor = 'white';\n    setTimeout(() => {\n      this._overlay.style.backgroundColor = 'none';\n      this._cleanup();\n\n      let clip = null;\n\n      if (this._selector) {\n        clip = {\n          x: this._selector.style.left,\n          y: this._selector.style.top,\n          width: this._selector.style.width,\n          height: this._selector.style.height,\n        };\n      }\n\n      this.emit('click', {\n        clip,\n        raw: e\n      });\n    }, 100);\n  }\n\n  _cleanup() {\n    document.body.removeEventListener('mousemove', this._boundeMouseMove, false);\n    document.body.removeEventListener('mouseup', this._boundeMouseUp, false);\n    document.body.removeChild(this._overlay);\n  }\n}\n\nexport default UIController"
  },
  {
    "path": "Web/packages/core/src/common/js/dom-events-to-record.js",
    "content": "export default {\n    // MOUSEOVER: 'mouseover',\n    // FOCUS: 'focus',\n    CLICK: 'click',\n    DBLCLICK: 'dblclick',\n    // CHANGE: 'change',\n    // KEYDOWN: 'keydown',\n    // SELECT: 'select',\n    // SUBMIT: 'submit',\n    TOUCHSTART:'touchstart',\n    TOUCHMOVE:'touchmove',\n    TOUCHEND:'touchend',\n    INPUT: 'input',\n    SCROLL: 'scroll',\n    // LOAD: 'load',\n    // UNLOAD: 'unload',\n  }"
  },
  {
    "path": "Web/packages/core/src/common/js/feature.js",
    "content": "export const noop = () => {}\n\nexport class BasePlugin{\n  type = ''\n  name = ''\n  nameZh = ''\n  icon = ''\n  component = null\n  _onLoad = noop\n  _onUnload = noop\n  _onProductReady = noop\n  constructor(options){\n    let {name, nameZh, icon, component, onLoad, onUnload, onProductReady} = options;\n    this.name = name;\n    this.nameZh = nameZh;\n    this.icon = icon;\n    this.component = component;\n    this._onLoad = onLoad || noop;\n    this._onUnload = onUnload || noop;\n    this._onProductReady = onProductReady || noop\n  }\n  load(){\n    this._onLoad.call(this)\n  }\n  unload(){\n    this._onUnload.call(this)\n  }\n  productReady(){\n    this._onProductReady.call(this)\n  }\n}\n\n/**\n * 基于路由容器的插件\n */\nexport class RouterPlugin extends BasePlugin{\n  type = \"RouterPlugin\"\n  constructor(options){\n    super(options)\n  }\n}\n\n/**\n * 独立容器的插件\n */\nexport class IndependPlugin extends BasePlugin{\n  type = \"IndependPlugin\"\n  constructor(options){\n    super(options)\n  }\n}\n\nexport const isRouterPlugin =  function(plugin){\n  return plugin instanceof RouterPlugin\n}\n\nexport const isIndependPlugin =  function(plugin){\n  return plugin instanceof IndependPlugin\n}"
  },
  {
    "path": "Web/packages/core/src/common/js/finder/index.js",
    "content": "import cssesc from 'cssesc';\n\nlet Limit;\n(function (Limit) {\n  Limit[Limit.All = 0] = 'All';\n  Limit[Limit.Two = 1] = 'Two';\n  Limit[Limit.One = 2] = 'One';\n}(Limit || (Limit = {})));\nlet config;\nlet rootDocument;\nexport default function (input, options) {\n  if (input.nodeType !== Node.ELEMENT_NODE) {\n    throw new Error('Can\\'t generate CSS selector for non-element node type.');\n  }\n  if (input.tagName.toLowerCase() === 'html') {\n    return 'html';\n  }\n  const defaults = {\n    root: document.body,\n    idName: name => true,\n    className: (name, input) => true,\n    tagName: name => true,\n    attr: (name, value) => false,\n    seedMinLength: 1,\n    optimizedMinLength: 2,\n    threshold: 1000,\n  };\n  config = Object.assign({}, defaults, options);\n  rootDocument = findRootDocument(config.root, defaults);\n  let path = bottomUpSearch(input, Limit.All, () => bottomUpSearch(input, Limit.Two, () => bottomUpSearch(input, Limit.One)));\n  // console.log('bottomUpSearch:', path)\n  if (path) {\n    const optimized = sort(optimize(path, input));\n    // console.log('optimized:', optimized)\n    if (optimized.length > 0) {\n      path = optimized[0];\n    }\n    // console.log('last path:', path)\n    return selector(path);\n  }\n\n  throw new Error('Selector was not found.');\n}\nfunction findRootDocument(rootNode, defaults) {\n  if (rootNode.nodeType === Node.DOCUMENT_NODE) {\n    return rootNode;\n  }\n  if (rootNode === defaults.root) {\n    return rootNode.ownerDocument;\n  }\n  return rootNode;\n}\nfunction bottomUpSearch(input, limit, fallback) {\n  let path = null;\n  const stack = [];\n  let current = input;\n  let i = 0;\n  while (current && current !== config.root.parentElement) {\n    let level = maybe(id(current)) || maybe(...attr(current)) || maybe(...classNames(current)) || maybe(tagName(current)) || [any()];\n    const nth = index(current, level);\n    // console.log('nth:', nth, 'level:', level)\n    if (limit === Limit.All) {\n      if (nth) {\n        level = level.concat(level.filter(dispensableNth).map(node => nthChild(node, nth)));\n      }\n      // console.log('[limit all] after level:', level)\n    } else if (limit === Limit.Two) {\n      level = level.slice(0, 1);\n      if (nth) {\n        level = level.concat(level.filter(dispensableNth).map(node => nthChild(node, nth)));\n      }\n      // console.log('[limit Two] after level:', level)\n    } else if (limit === Limit.One) {\n      const [node] = level = level.slice(0, 1);\n      if (nth && dispensableNth(node)) {\n        level = [nthChild(node, nth)];\n      }\n      // console.log('[limit One] after level:', level)\n    }\n    for (const node of level) {\n      node.level = i;\n    }\n    stack.push(level);\n    if (stack.length >= config.seedMinLength) {\n      path = findUniquePath(stack, fallback);\n      if (path) {\n        break;\n      }\n    }\n    // console.log('stack:', stack)\n    current = current.parentElement;\n    i++;\n  }\n  if (!path) {\n    path = findUniquePath(stack, fallback);\n  }\n  return path;\n}\nfunction findUniquePath(stack, fallback) {\n  const paths = sort(combinations(stack));\n\n  // console.log('findUniquePath:', paths)\n  if (paths.length > config.threshold) {\n    return fallback ? fallback() : null;\n  }\n  for (const candidate of paths) {\n    if (unique(candidate)) {\n      return candidate;\n    }\n  }\n  return null;\n}\nfunction selector(path) {\n  let node = path[0];\n  let query = node.name;\n  // console.log('selector:', node, path.slice(1))\n  for (let i = 1; i < path.length; i++) {\n    const level = path[i].level || 0;\n    if (node.level === level - 1) {\n      query = `${path[i].name} > ${query}`;\n    } else {\n      query = `${path[i].name} ${query}`;\n    }\n    node = path[i];\n  }\n  return query;\n}\nfunction penalty(path) {\n  return path.map(node => node.penalty).reduce((acc, i) => acc + i, 0);\n}\nfunction unique(path) {\n  const s = selector(path)\n  const len = rootDocument.querySelectorAll(s).length\n  // console.log('[unique] selector:', s, 'len:', len)\n  switch (len) {\n    case 0:\n      throw new Error(`Can't select any node with this selector: ${selector(path)}`);\n    case 1:\n      return true;\n    default:\n      return false;\n  }\n}\nfunction id(input) {\n  const elementId = input.getAttribute('id');\n  if (elementId && config.idName(elementId)) {\n    return {\n      name: `#${cssesc(elementId, { isIdentifier: true })}`,\n      penalty: 0,\n      type: 'id',\n    };\n  }\n  return null;\n}\nfunction attr(input) {\n  const attrs = Array.from(input.attributes).filter(attr => config.attr(attr.name, attr.value));\n  return attrs.map(attr => ({\n    name: `[${cssesc(attr.name, { isIdentifier: true })}=\"${cssesc(attr.value)}\"]`,\n    penalty: 0.5,\n    type: 'attr',\n  }));\n}\nfunction classNames(input) {\n  const names = Array.from(input.classList)\n    .filter((cName)=> config.className(cName, input));\n  return names.map(name => ({\n    name: `.${cssesc(name, { isIdentifier: true })}`,\n    penalty: 1,\n    type: 'className',\n  }));\n}\nfunction tagName(input) {\n  const name = input.tagName.toLowerCase();\n  if (config.tagName(name)) {\n    return {\n      name,\n      penalty: 2,\n      type: 'tag',\n    };\n  }\n  return null;\n}\nfunction any() {\n  return {\n    name: '*',\n    penalty: 3,\n    type: 'any',\n  };\n}\nfunction index(input, level) {\n  const parent = input.parentNode;\n  if (!parent) {\n    return null;\n  }\n  let child = parent.firstChild;\n  if (!child) {\n    return null;\n  }\n  let i = 0;\n  while (child) {\n    // hasSimilar(input, child, level[0])\n    if (child.nodeType === Node.ELEMENT_NODE) {\n      i++;\n    }\n    if (child === input) {\n      break;\n    }\n    child = child.nextSibling;\n  }\n  return i;\n}\nfunction hasSimilar(input, compareNode, { type, name }) {\n  if (type === 'className') {\n    return Array.from(compareNode.classList).map(cName => cssesc(cName, { isIdentifier: true })).includes(name.slice(1));\n  }\n  if (type === 'tag') {\n    return name === cssesc(compareNode.tagName.toLowerCase(), { isIdentifier: true });\n  }\n  return true;\n}\nfunction nthChild(node, i) {\n  return {\n    name: `${node.name}:nth-child(${i})`,\n    penalty: node.penalty + 1,\n    type: 'nth',\n  };\n}\nfunction dispensableNth(node) {\n  return node.name !== 'html' && !node.name.startsWith('#');\n}\nfunction maybe(...level) {\n  const list = level.filter(notEmpty);\n  if (list.length > 0) {\n    return list;\n  }\n  return null;\n}\nfunction notEmpty(value) {\n  return value !== null && value !== undefined;\n}\nfunction* combinations(stack, path = []) {\n  if (stack.length > 0) {\n    for (const node of stack[0]) {\n      yield* combinations(stack.slice(1, stack.length), path.concat(node));\n    }\n  } else {\n    yield path;\n  }\n}\nfunction sort(paths) {\n  return Array.from(paths).sort((a, b) => penalty(a) - penalty(b));\n}\nfunction* optimize(path, input) {\n  // console.log('optimize start:', path.length > 2 && path.length > config.optimizedMinLength, path, input)\n  if (path.length > 2 && path.length > config.optimizedMinLength) {\n    // console.log('optimize start')\n    for (let i = 1; i < path.length - 1; i++) {\n      const newPath = [...path];\n      newPath.splice(i, 1);\n      // console.log('optimize newPath:', newPath)\n      if (unique(newPath) && same(newPath, input)) {\n        yield newPath;\n        yield* optimize(newPath, input);\n      }\n    }\n  }\n}\nfunction same(path, input) {\n  return rootDocument.querySelector(selector(path)) === input;\n}\n"
  },
  {
    "path": "Web/packages/core/src/common/js/icon.js",
    "content": "export const IconBack = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAzCAMAAADIDVqJAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAXFQTFRFAAAANX7GNHzFM3zFM3zFNn3ENYDHNHzFM3zENIDHNH7INH3ENX3GNn7GM33ENYDFNYDGM4DINH7HM3zENH3HNHzENYDGM37FM33FNH3FNn3FNX7HN4DINHzEM33HNH7FNIDGNn7GM33ENX3FNXzHNoDENH7FNH3EM3zEN33INHzFNH3FM4DHNX3HNH3GNX7ENHzENHzFNYDFNH3FM33ENn3JM33GN4DINX7EM3zEM33GNH7FM33FM33FNIDFNH3GNX3KM37HNHzFN4DINX7FNH3EOHzHNH3GM3zFM4DGM33EM3zENHzENH3EM33FOoTFgID/NHzFOIDHM3zEM3zFNoPJM33FM33ENH3FNHzENYDL////NHzENH3FM4PFM3zFOYDGNH3FM3zFN3zINHzEM33FNoDJM3zFM33FNH3ENHzFM37FNH3ENH3FM33ENH7GNX7FM33ENH3FNH7FM37ENH7FM3zFNH3EM4jMM3zE////TdHL6gAAAHl0Uk5TAEPy+vA9RPb0QEX3P0fzPkg8Sfg7SjpL+fE5TThON082Ue81UjRT++4zVO0yVjFX/OwwWOsvWi5b6i1d/eksXitf6Cph5yli/ihk5ieh3x8CoiCg4CGf4Z3iIgGc4yOaJJnkJZjlJpaVk5KQj42LioiHhYSCgHOyD3AmCqsAAAABYktHRFt0vJU0AAAACXBIWXMAAABIAAAASABGyWs+AAABPUlEQVQ4y43UxVoCUBQE4Ctgd3cXKAYoKqioINiBrdjdXby9C+6c3ZzPWf+rE2MMS4bDmY4rk5qs7FQ6ObnU5OXDFFBTCFNUTE2JmFJqysqtcVRQUymmiprqGmtq66ipF9NATSOMq4maZidMCzWtMG3t1HSI6aSmq9sat4eaHpheLzV9YvqpGRi0xuenZghmOEDNiJhRasZggiFqxiesmQxTMyVmmpoZmEiUmlmYWJyauXmYBWoWYZaWqTEr1qRW1zhaT4jaUNQm1Na2onagdjW1B7V/wFXyEOroWFEnok4VdQZ1fqGoS6ira0XdpP4x+uStKL5EY+6gYpq6h4rEFfUgKqqoRyjlkI15ggqGFfUsKqSoFyjlTY15hfIFFPUmyq+odyilhIz5gHJ7FfUpyqOoLyilYo35/rFJ/Jo/DZ3bT7fEcIgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDQtMjFUMTc6MzI6MjgrMDg6MDBBnT5hAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA0LTIxVDE3OjMyOjI4KzA4OjAwMMCG3QAAAABJRU5ErkJggg=='\n\nexport const DefaultItemIcon = 'https://pt-starimg.didistatic.com/static/starimg/img/FHqpI3InaS1618997548865.png'\n\nexport const dokitIcon = 'https://pt-starimg.didistatic.com/static/starimg/img/eM7MJKDqVG1618998466986.png'"
  },
  {
    "path": "Web/packages/core/src/common/js/lifecycle.js",
    "content": "export const LifecycleHooks = {\n  LOAD: 'load',\n  UNLOAD: 'unload',\n  PRODUCT_READY: 'productReady'\n}\n\nexport const applyLifecyle = function(features, lifecycle){\n  features.forEach(feature => {\n    let {list} = feature\n    list.forEach(item => {\n      if(isFunction(item[lifecycle])){\n        item[lifecycle]()\n      }\n    });\n  });\n}\n\nexport const isFunction = function(ob){\n  return typeof ob === 'function'\n}"
  },
  {
    "path": "Web/packages/core/src/common/js/node.js",
    "content": "const getNodeLevel = (node, treeNum = 0, maxDepth = 3) => {\n  if (treeNum > maxDepth) {\n    return treeNum;\n  }\n  const children = Array.from(node.childNodes || []);\n  if (node.nodeType !== Node.ELEMENT_NODE) {\n    return treeNum;\n  }\n  if (children.length === 0) {\n    return treeNum;\n  }\n  const levels = children.map(cNode => getNodeLevel(cNode, treeNum + 1, maxDepth));\n  return Math.max(...levels);\n};\n\nconst getText = (el) => {\n  switch (el.nodeType) {\n    case Node.ELEMENT_NODE: // element\n      return el.innerHTML;\n    case Node.TEXT_NODE: // text\n      return el.nodeValue;\n    default:\n      return '';\n  }\n};\n\nconst getNodeText = (node, treeNum = 3) => {\n  if (getNodeLevel(node) > treeNum) {\n    return '';\n  }\n\n  return getText(node);\n};\n\nexport default getNodeText;"
  },
  {
    "path": "Web/packages/core/src/common/js/socket.js",
    "content": "import {\n    getGlobalData\n} from '../../store'\nimport {\n    getCurrentInstance\n} from 'vue';\nimport {\n    $bus,\n  } from '@dokit/web-utils'\nexport default class Socket {\n    constructor(url) {\n        this.instance = getCurrentInstance();\n        this.state = getGlobalData();\n        this.socket = null;\n        this.webSocketState = null;\n        this.reconnectTimer = null;\n        this.waitingServerTime = null;\n        this.startHeartBeatTimee = null;\n        this.heartBeat = {\n            time: 30 * 1000,\n            timeout: 3 * 1000,\n            reconnect: 10 * 1000,\n        }\n        this.isReconnect = true;\n        this.wsUrl = url;\n        this.init();\n        if (this.socket) {\n            this.onopen(() => {\n                this.state = getGlobalData();\n                this.webSocketState = true\n                this.send({\n                    type: 'LOGIN',\n                    channelSerial: this.state.channelSerial,\n                    data: JSON.stringify({\n                        manufacturer: window.location.host,\n                        connectSerial: this.state.connectSerial || undefined\n                    })\n                })\n                $bus.emit(\"webSocketState\")\n                this.startHeartBeat(this.heartBeat.time) // 心跳机制\n            })\n            this.onerror((e) => {\n                console.log(e);\n                this.webSocketState = false\n                this.socket = null\n                this.state.socketConnect = false\n            })\n            this.onclose((e) => {\n                console.log(e)\n                this.socket = null\n                this.state.socketConnect = false\n                this.webSocketState = false\n                this.reconnectTimer && clearTimeout(this.reconnectTimer);\n                this.waitingServerTime && clearTimeout(this.waitingServerTime);\n                this.startHeartBeatTime && clearTimeout(this.startHeartBeatTime);\n            })\n            this.onmessage((e) => {\n                let msg = JSON.parse(e.data);\n                if (msg.type === \"HEART_BEAT\") {\n                    this.webSocketState = true\n                }\n            })\n        }\n    }\n    init() {\n        try {\n            if (!this.socket) {\n                this.socket = new WebSocket(this.wsUrl)\n            }\n            if (this.socket && this.reconnectTimer) {\n                clearTimeout(this.reconnectTimer)\n                this.reconnectTimer = null\n                this.socket = new WebSocket(this.wsUrl)\n            }\n        } catch (error) {\n            console.error(error)\n        }\n    }\n    onopen(callBack) {\n        this.socket.addEventListener('open', callBack)\n    }\n    onclose(callBack) {\n        this.socket.addEventListener('close', callBack)\n    }\n    onmessage(callBack) {\n        this.socket.addEventListener('message', callBack)\n    }\n    onerror(callBack) {\n        this.socket.addEventListener('error', callBack)\n    }\n    close() {\n        this.socket && this.socket?.close()\n        this.socket = null\n        this.reconnectTimer && clearTimeout(this.reconnectTimer);\n        this.waitingServerTime && clearTimeout(this.waitingServerTime);\n        this.startHeartBeatTime && clearTimeout(this.startHeartBeatTime);\n    }\n    send(msg) {\n        this.socket && this.socket.send(JSON.stringify(msg))\n    }\n    startHeartBeat(time) {\n        this.startHeartBeatTime = setTimeout(() => {\n            this.send({\n                type: 'HEART_BEAT',\n                data: JSON.stringify({\n                    time: new Date(),\n                    connectSerial: this.state.connectSerial\n                })\n            })\n            this.waitingServer()\n        }, time)\n    }\n    //延时等待服务端响应，通过webSocketState判断是否连线成功\n    waitingServer() {\n        this.webSocketState = false\n        this.waitingServerTime = setTimeout(() => {\n            if (this.webSocketState) {\n                this.startHeartBeat(this.heartBeat.time)\n                return\n            }\n            console.log('心跳无响应，已断线')\n            try {\n                this.close()\n                this.instance.proxy.$toast(\"连接已经断开\", 2000);\n            } catch (e) {\n                console.log('连接已关闭，无需关闭')\n            }\n            this.reconnectWebSocket()\n        }, this.heartBeat.timeout)\n    }\n    reconnectWebSocket() {\n        if (!this.isReconnect) {\n            return;\n        }\n        this.reconnectTimer = setTimeout(() => {\n            this.init()\n        }, this.heartBeat.reconnect)\n    }\n}"
  },
  {
    "path": "Web/packages/core/src/common/js/store.js",
    "content": "import {reactive} from 'vue'\nconst storeKey = 'store'\n/**\n * 简易版 Store 实现\n * 支持直接修改 Store 数据\n */\nexport class Store{\n  constructor(options){\n    let {state} = options\n    this.initData(state)\n  }\n\n  initData(data = {}){\n    this._state = reactive({\n      data: data\n    })\n  }\n\n  get state(){\n    return this._state.data\n  }\n\n  install(app){\n    app.provide(storeKey, this)\n    app.config.globalProperties.$store = this\n  }\n\n}\n"
  },
  {
    "path": "Web/packages/core/src/common/js/util.js",
    "content": "import {\n  EventEmitter\n} from \"@dokit/web-utils\";\n\nexport const getDataType = function (arg) {\n  if (arg === null) {\n    return 'Null'\n  }\n  if (arg === undefined) {\n    return 'Undefined'\n  }\n  return arg.constructor && arg.constructor.name || 'Object'\n}\n\nconst MAX_DISPLAY_PROPERTY_NUM = 5\n\nexport const getDataStructureStr = function (arg, isFirstLevel) {\n  let dataType = getDataType(arg)\n  let str = ''\n  switch (dataType) {\n    case 'Number':\n    case 'String':\n    case 'Boolean':\n    case 'RegExp':\n    case 'Symbol':\n    case 'Function':\n      str = arg.toString()\n      break;\n\n    case 'Null':\n    case 'Undefined':\n      str = arg + ''\n      break;\n    case 'Array':\n      break;\n    case 'Object':\n      str += '{'\n      if (isFirstLevel) {\n        let propertyNames = Object.getOwnPropertyNames(arg)\n        let propertyNameStrs = propertyNames.map(key =>\n          str += `${key}: ${getDataStructureStr(arg[key], false)}`\n        )\n        propertyNameStrs.join(',')\n        if (propertyNameStrs.length > MAX_DISPLAY_PROPERTY_NUM) {\n          str += ',...'\n        }\n      } else {\n        str += '...'\n      }\n\n      str += '}'\n\n      break;\n    default:\n      break;\n  }\n  return str\n}\n\nexport const guid = function () {\n  function S4() {\n    return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)\n  }\n  return (S4() + S4() + \"-\" + S4() + \"-\" + S4() + \"-\" + S4() + \"-\" + S4() + S4() + S4())\n}\n\nexport const $bus = new EventEmitter()\n\nexport const uuid = () => {\n  var temp_url = URL.createObjectURL(new Blob());\n  var uuid = temp_url.toString(); // blob:https://xxx.com/b250d159-e1b6-4a87-9002-885d90033be3\n  URL.revokeObjectURL(temp_url);\n  return uuid.substr(uuid.lastIndexOf(\"/\") + 1);\n}\n\nexport const debounce = (fn, wait, time) => {\n  var previous = null\n  var timer = null\n  return function(...args){\n    var now = +new Date()\n    var that = this\n    if (!previous) previous = now\n    if (now - previous > time) {\n      clearTimeout(timer)\n      fn.apply(that, args)\n      previous = now\n    } else {\n      clearTimeout(timer)\n      timer = setTimeout(function () {\n        fn.apply(that, args)\n      }, wait)\n    }\n  }\n}\n\nexport function throttle(fn, wait) {\n  let prev = 0\n  return (...args) => {\n    let now = +new Date()\n    if (now - prev > wait) {\n      fn.apply(this, args)\n      prev = now\n    }\n  }\n}\nexport function getDeviceType() {\n  let u = navigator.userAgent.toLowerCase();\n  let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; //判断是否是 android终端\n  let isIOS = !!u.match(/\\(i[^;]+;( U;)? CPU.+Mac OS X/); //判断是否是 iOS终端\n  if (isAndroid) {\n    return 'android';\n  } else if (isIOS) {\n    return 'ios';\n  } else {\n    return 'pc';\n  }\n}\nexport function getOsVersion() {\n  let version, ua = navigator.userAgent.toLowerCase();\n  if (getDeviceType() === 'android') {\n    version = /Android(.+?);/.exec(ua)\n    return version[1].trim()\n  } else if (getDeviceType() === 'ios') {\n    if (ua.indexOf(\"like mac os x\") > 0) {\n      let reg = /os [\\d._]*/gi;\n      let verinfo = ua.match(reg);\n      version = (verinfo + \"\").replace(/[^0-9|_.]/ig, \"\").replace(/_/ig, \".\");\n      return version\n    }\n  }\n}\n\nexport const ActionObject = {\n  'click':{\n    actionType:'ON_CLICK',\n    actionName:'点击',\n  },\n  'dblclick':{\n    actionType:'ON_DBL_CLICK',\n    actionName:'双击',\n  },\n  'touchstart':{\n    actionType:'ON_TOUCH_START',\n    actionName:'触摸开始',\n  },\n  'touchmove':{\n    actionType:'ON_TOUCH_MOVE',\n    actionName:'触摸移动',\n  },\n  'touchend':{\n    actionType:'ON_TOUCH_END',\n    actionName:'触摸结束',\n  },\n  'input':{\n    actionType:'ON_INPUT_CHANGE',\n    actionName:'输入',\n  },\n  'scroll':{\n    actionType:'ON_SCROLL',\n    actionName:'滚动',\n  },\n}"
  },
  {
    "path": "Web/packages/core/src/common/js/xpath/index.js",
    "content": "class XPath {\n  static _findRootDocument(rootNode, defaultRoot) {\n    if (rootNode.nodeType === Node.DOCUMENT_NODE) {\n      return rootNode;\n    }\n    if (rootNode === defaultRoot) {\n      return rootNode.ownerDocument;\n    }\n    return rootNode;\n  }\n\n  static index(input) {\n    const parent = input.parentNode;\n    if (!parent) {\n      return null;\n    }\n    let child = parent.firstChild;\n    if (!child) {\n      return null;\n    }\n    let i = 0;\n    while (child) {\n      if (child.nodeType === Node.ELEMENT_NODE) {\n        i += 1;\n      }\n      if (child === input) {\n        break;\n      }\n      child = child.nextSibling;\n    }\n    return i;\n  }\n\n  static any() {\n    return {\n      name: '*',\n      weight: 1,\n      type: 'any',\n    };\n  }\n\n  static nthChild(node, i) {\n    return {\n      name: `${node.name}:nth-child(${i})`,\n      penalty: node.weight + 1,\n      type: 'nth',\n    };\n  }\n\n  static filterNth(node) {\n    return node.name !== 'html' && !node.name.startsWith('#');\n  }\n\n  constructor(opts) {\n    const defaultOpts = {\n      root: document.body,\n      tagName: name => true,\n    };\n    this._opts = Object.assign({}, defaultOpts, opts);\n    this._rootDoc = XPath._findRootDocument(this._opts.root, defaultOpts.root);\n  }\n\n  bubbleUp(input) {\n    const { root } = this._opts;\n    const stack = [];\n    let current = input;\n    let i = 0;\n    while (current && current !== root.parentElement) {\n      let node = this.tagName(current) || XPath.any();\n      const nth = XPath.index(current);\n      if (nth && node.name !== 'html') {\n        node = XPath.nthChild(node, nth);\n      }\n\n      node.level = i;\n      stack.push(node);\n      current = current.parentElement;\n      i += 1;\n    }\n    return stack;\n  }\n\n  tagName(input) {\n    const name = input.tagName.toLowerCase();\n    const { tagName } = this._opts;\n    if (tagName(name)) {\n      return {\n        name,\n        weight: 2,\n        type: 'tag',\n      };\n    }\n    return null;\n  }\n\n  find(input) {\n    const stack = this.bubbleUp(input);\n    const selector = stack.sort((a, b) => b.level - a.level).reduce((selector, node) => {\n      selector += `${node.name}>`;\n      return selector;\n    }, '');\n    return selector.length ? selector.slice(0, selector.length - 1) : selector;\n  }\n}\n\nexport default function xpath(opts) {\n  return new XPath(opts);\n}\n"
  },
  {
    "path": "Web/packages/core/src/components/app.vue",
    "content": "<template>\n  <div class=\"dokit-app\">\n    <div\n      class=\"dokit-entry-btn\"\n      style=\"z-index: 10000\"\n      v-dragable=\"btnConfig\"\n      @click=\"toggleShowContainer\"\n    ></div>\n    <div class=\"dokit-mask\" v-show=\"showContainer\" @click=\"toggleContainer\"></div>\n    <router-container v-show=\"showContainer\"></router-container>\n    <independ-container v-show=\"independPlugins.length\"></independ-container>\n    <elements-highlight\n      v-if=\"showHighlightElement && highlightElement\"\n      :element=\"highlightElement\"\n    ></elements-highlight>\n    <host-suspendedBall v-if=\"socketConnect\"></host-suspendedBall>\n  </div>\n</template>\n\n<script>\nimport { dragable } from \"@dokit/web-utils\";\nimport { uuid } from '../common/js/util.js'\nimport RouterContainer from \"./router-container\";\nimport IndependContainer from \"./independ-container\";\nimport ElementsHighlight from \"./elements-highlight.vue\";\nimport HostSuspendedBall from \"./hostSuspendedBall.vue\";\nimport { toggleContainer } from \"@store/index\";\nimport EventRecorder from \"../common/js/EventRecorder\";\nimport EventPlayback from \"../common/js/EventPlayback\";\nexport default {\n  components: {\n    RouterContainer,\n    IndependContainer,\n    ElementsHighlight,\n    HostSuspendedBall,\n  },\n  directives: {\n    dragable,\n  },\n  data() {\n    return {\n      btnConfig: {\n        name: \"dokit_entry\",\n        opacity: 0.5,\n        left: window.innerWidth - 50,\n        top: window.innerHeight - 100,\n        safeBottom: 50,\n        eventPlayback: null,\n      },\n    };\n  },\n  created() {\n    this.$store.state.aid = uuid()\n  },\n  watch: {\n    socketConnect: {\n      handler(newVal, oldVal) {\n        if (newVal) {\n          this.eventPlayback = new EventPlayback(this.socketUrl);\n          this.$store.state.socketHistoryList.set(this.socketUrl, \"connect\");\n        } else {\n          if (this.eventPlayback) {\n            this.$store.state.socketHistoryList.set(this.socketUrl, \"close\");\n            this.eventPlayback.close();\n            this.eventPlayback = null;\n          }\n        }\n      },\n      immediate: true,\n    },\n    socketHistoryList: {\n      handler(newVal, oldVal) {\n        localStorage.setItem(\n            \"dokit-socket-history-list\",\n            JSON.stringify([...newVal])\n          );\n      },\n      deep: true,\n    },\n    isHost: {\n      handler(newVal, oldVal) {\n        if (newVal) {\n          this.eventPlayback?.state?.mySocket?.webSocketState&&this.eventPlayback.state.mySocket.send({\n            type: \"BROADCAST\",\n            contentType:'mc_host',\n            channelSerial: this.channelSerial,\n            data: JSON.stringify({\n              connectSerial: this.eventPlayback.state.connectSerial,\n            }),\n          });\n          if (!window.eventRecorder) {\n            window.eventRecorder = new EventRecorder(this.socketUrl);\n          }\n          this.$store.state.startPlayback = false;\n          window?.eventRecorder?.boot();\n        } else {\n          window?.eventRecorder?.off();\n        }\n      },\n      immediate: true,\n    },\n  },\n  computed: {\n    channelSerial(){\n      return this.state.channelSerial;\n    },\n    socketHistoryList(){\n      return this.state.socketHistoryList;\n    },\n    highlightElement() {\n      return this.state.highlightElement;\n    },\n    showHighlightElement() {\n      return this.state.showHighlightElement;\n    },\n    state() {\n      return this.$store.state;\n    },\n    showContainer() {\n      return this.state.showContainer;\n    },\n    independPlugins() {\n      return this.$store.state.independPlugins;\n    },\n    socketConnect() {\n      return this.state.socketConnect;\n    },\n    socketUrl() {\n      return this.state.socketUrl;\n    },\n    isHost() {\n      return this.state.isHost;\n    },\n  },\n  methods: {\n    toggleShowContainer() {\n      toggleContainer();\n    },\n  },\n};\n</script>\n\n<style lang=\"less\" scoped>\n.dokit-app {\n  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;\n  pointer-events: none;\n  position: fixed;\n  left: 0;\n  top: 0;\n  width: 100%;\n  height: 100%;\n  z-index: 100000;\n  & > * {\n    pointer-events: all;\n    font-size: 16px;\n  }\n}\n.dokit-entry-btn {\n  width: 50px;\n  height: 50px;\n  padding: 10px;\n  box-sizing: border-box;\n  background-image: url(//pt-starimg.didistatic.com/static/starimg/img/OzaetKDzHr1618905183992.png);\n  background-size: 50px;\n  background-position: center;\n  background-repeat: no-repeat;\n}\n.dokit-mask {\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  z-index: 3;\n  background-color: #333333;\n  opacity: 0.3;\n}\n</style>"
  },
  {
    "path": "Web/packages/core/src/components/elements-highlight.vue",
    "content": "<template>\n  <div\n    class=\"dokit-elements-highlight\"\n    :style=\"`\n          top: ${element.getBoundingClientRect().top}px;\n          left: ${element.getBoundingClientRect().left}px;\n        `\"\n  >\n    <div class=\"dokit-elements-indicator\">\n      <div\n        class=\"dokit-margin\"\n        :style=\"`\n          left: -${highlightnNode.boxStyle.marginLeft}px;\n          top: -${highlightnNode.boxStyle.marginTop}px;\n          width: ${element.offsetWidth}px;\n          height: ${element.offsetHeight}px;\n          border-width: ${highlightnNode.boxStyle.marginTop}px ${highlightnNode.boxStyle.marginRight}px ${highlightnNode.boxStyle.marginBottom}px ${highlightnNode.boxStyle.marginLeft}px;\n          border-style: solid;\n          border-color: rgba(246, 178, 107, 0.66);\n        `\"\n      ></div>\n      <div\n        class=\"dokit-border\"\n        :style=\"`\n          left: 0px;\n          top:0px;\n          width: ${element.offsetWidth}px;\n          height: ${element.offsetHeight}px;\n          border-width: ${highlightnNode.boxStyle.borderTopWidth}px ${highlightnNode.boxStyle.borderRightWidth}px ${highlightnNode.boxStyle.borderBottomWidth}px ${highlightnNode.boxStyle.borderLeftWidth}px;\n          border-style: solid;\n          border-color: rgba(255, 229, 153, 0.66);\n        `\"\n      ></div>\n      <div\n        class=\"dokit-padding\"\n        :style=\"`\n          left: ${highlightnNode.boxStyle.borderLeftWidth}px;\n          top: ${highlightnNode.boxStyle.borderTopWidth}px;\n          width: ${highlightnNode.boxStyle.contentWidth}px;\n          height: ${highlightnNode.boxStyle.contentHeight}px;\n          border-width: ${highlightnNode.boxStyle.paddingTop}px ${highlightnNode.boxStyle.paddingRight}px ${highlightnNode.boxStyle.paddingBottom}px ${highlightnNode.boxStyle.paddingLeft}px;\n          border-style: solid;\n          border-color: rgba(147, 196, 125, 0.55);\n        `\"\n      ></div>\n      <div\n        class=\"dokit-content\"\n        :style=\"`\n          left: ${contentLeft};\n          top: ${contentTop};\n          width: ${highlightnNode.boxStyle.contentWidth}px;\n          height: ${highlightnNode.boxStyle.contentHeight}px;\n          background: rgba(111, 168, 220, 0.66);\n        `\"\n      ></div>\n    </div>\n    <div class=\"dokit-elements-size\" :style=\"isOverfllow\">\n      <span class=\"nodeName\">{{ element.nodeName.toLowerCase() }}</span>\n      <span v-if=\"element.id !== ''\" class=\"nodeaId\">{{\n        `#${element.id}`\n      }}</span>\n      <span v-if=\"element.className !== ''\" class=\"nodeaClass\">{{\n        `.${element.className}`\n      }}</span>\n      <span> | {{ element.offsetWidth }} × {{ element.offsetHeight }}</span>\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  props: {\n    element: Object,\n  },\n  data() {\n    return {};\n  },\n  computed: {\n    highlightnNode() {\n      return this.element?.__dokitForWeb_node;\n    },\n    isOverfllow() {\n      let offsetTop =\n        this.element.getBoundingClientRect().top +\n        this.highlightnNode.boxStyle.marginTop;\n      if (offsetTop < 25) {\n        return `\n          top: 0px;\n          left: 0px;\n        `;\n      } else {\n        return `\n          top: ${-25 - this.highlightnNode.boxStyle.marginTop}px;\n          left: ${-this.highlightnNode.boxStyle.marginLeft}px;\n        `;\n      }\n    },\n    contentLeft() {\n      let elPaddingLeft = parseInt(this.highlightnNode.boxStyle.paddingLeft);\n      let elBorderLeftWidth = parseInt(this.highlightnNode.boxStyle.borderLeftWidth);\n      return `${elPaddingLeft + elBorderLeftWidth}px`;\n    },\n    contentTop() {\n      let elPaddingTop = parseInt(this.highlightnNode.boxStyle.paddingTop);\n      let elBorderTopWidth = parseInt(this.highlightnNode.boxStyle.borderTopWidth);\n      return `${elPaddingTop + elBorderTopWidth}px`;\n    },\n  },\n};\n</script>\n\n<style lang=\"less\" scoped>\n.dokit-elements-highlight {\n  position: absolute;\n  z-index: -100;\n  pointer-events: none !important;\n  .dokit-elements-indicator {\n    position: absolute;\n    left: 0;\n    right: 0;\n    width: 100%;\n    height: 100%;\n    & > * {\n      pointer-events: none !important;\n    }\n    .dokit-margin,\n    .dokit-border,\n    .dokit-padding,\n    .dokit-content {\n      position: absolute;\n    }\n    .dokit-border {\n      box-sizing: border-box;\n    }\n  }\n  .dokit-elements-size {\n    position: absolute;\n    background: #fff;\n    color: #222;\n    font-size: 12px;\n    height: 25px;\n    line-height: 25px;\n    text-align: center;\n    padding: 0 5px;\n    white-space: nowrap;\n    overflow-x: hidden;\n    box-shadow: 0 2px 2px 0 rgb(0 0 0 / 5%), 0 1px 4px 0 rgb(0 0 0 / 8%),\n      0 3px 1px -2px rgb(0 0 0 / 20%);\n    .nodeName {\n      color: #881280;\n    }\n    .nodeaId {\n      color: #1a1aa8;\n    }\n    .nodeaClass {\n      color: rgb(143, 73, 25);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "Web/packages/core/src/components/home.vue",
    "content": "<template>\n  <div class=\"dokit-index-container\">\n    <card\n      v-for=\"(item, index) in features\"\n      :key=\"index\"\n      :title=\"item.title\"\n      :list=\"item.list\"\n      @handleClickItem=\"handleClickItem\"\n    ></card>\n    <version-card :version=\"version\"></version-card>\n  </div>\n</template>\n<script>\nimport TopBar from \"@common/components/top-bar\";\nimport Card from \"@common/components/card\";\nimport VersionCard from \"@common/components/version\";\nimport {addIndependPlugin} from '@store/index';\nexport default {\n  components: {\n    TopBar,\n    Card,\n    VersionCard\n  },\n  data(){\n    return {\n      version: '0.0.3-alpha.3'\n    }\n  },\n  mounted(){\n  },\n  computed: {\n    features(){\n      return this.$store.state.features\n    }\n  },\n  methods: {\n    handleClickItem(item){\n      switch(item.type){\n        case \"RouterPlugin\":\n          this.$router.push({\n            name: item.name\n          })\n          break;\n        case \"IndependPlugin\":\n          addIndependPlugin(item)\n          this.$store.state.showContainer = false\n          break;\n        default:\n          break;\n      }\n    }\n  }\n};\n</script>\n<style lang=\"less\" scoped>\n.dokit-index-container {\n  background-color: #f5f6f7;\n}\n</style>"
  },
  {
    "path": "Web/packages/core/src/components/hostSuspendedBall.vue",
    "content": "<template>\n  <div>\n    <div class=\"dokit-masterSuspendedBall\" @click=\"change\" v-dragable=\"btnConfig\">\n      {{ isHost ? \"主机\" : \"从机\" }}\n    </div>\n  </div>\n</template>\n\n<script>\nimport { dragable } from \"@dokit/web-utils\";\nexport default {\n  directives: {\n    dragable,\n  },\n  data() {\n    return {\n      isHost: false,\n      btnConfig: {\n        name: \"dokit_multi_control\",\n        opacity: 0.5,\n        left: window.innerWidth - 50,\n        top: window.innerHeight * 0.2,\n        safeBottom: 50,\n        eventPlayback: null,\n      },\n    };\n  },\n  computed: {\n    isHost() {\n      return this.$store.state.isHost;\n    },\n  },\n  methods: {\n    change() {\n      this.$store.state.isHost = !this.$store.state.isHost;\n    },\n  },\n};\n</script>\n\n<style lang=\"less\" scoped>\n.dokit-masterSuspendedBall {\n  // position: fixed;\n  z-index: 10000;\n  // opacity: 0.5;\n  // right: 50px;\n  // top: 20%;\n  font-size: 16px;\n  width: 50px;\n  height: 50px;\n  border-radius: 50%;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background-color: #ccc;\n}\n</style>"
  },
  {
    "path": "Web/packages/core/src/components/independ-container.vue",
    "content": "<template>\n  <div class=\"dokit-container\" style=\"z-index: 998;\">\n    <component :is=\"toRaw(plugin.component)\" v-for=\"plugin in independPlugins\" :key=\"plugin.name\"/>\n  </div>\n</template>\n<script>\n// 所有独立插件共用一个容器\nimport { toRaw } from 'vue';\n\nexport default {\n  components: {},\n  data(){\n    return {}\n  },\n  computed:{\n    independPlugins(){\n      return this.$store.state.independPlugins\n    }\n  },\n  methods: {\n    toRaw: toRaw\n  }\n}\n</script>\n<style lang=\"less\" scoped>\n.dokit-container{\n  position: absolute;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  top: 0;\n}\n</style>"
  },
  {
    "path": "Web/packages/core/src/components/router-container.vue",
    "content": "<template>\n  <div class=\"dokit-container\">\n    <top-bar :title=\"title\" :canBack=\"canBack\"></top-bar>\n    <div class=\"dokit-router-container\">\n      <keep-alive>\n        <component :is=\"component\" />\n      </keep-alive>\n    </div>\n  </div>\n</template>\n<script>\nimport TopBar from \"@common/components/top-bar\";\n\nexport default {\n  components: {\n   TopBar \n  },\n  data(){\n    return {}\n  },\n  computed:{\n    component(){\n      \n      return this.$route.component\n    },\n    title(){\n      return this.$route.meta && this.$route.meta.title || 'DoKit'\n    },\n    canBack(){\n      return this.$route.name !== 'home'\n    }\n  }\n}\n</script>\n<style lang=\"less\" scoped>\n.dokit-container{\n  position: fixed;\n  left: 0;\n  right: 0;\n  top:100px;\n  bottom: 0;\n  background-color: #f5f6f7;\n  display: flex;\n  flex-direction: column;\n  z-index: 999;\n  border-radius: 10px 10px 0 0;\n}\n.dokit-router-container{\n  margin-top: 5px;\n  background-color: white;\n  flex: 1;\n  overflow-y: scroll;\n}\n</style>"
  },
  {
    "path": "Web/packages/core/src/index.js",
    "content": "import {\n  createApp\n} from 'vue'\nimport App from './components/app'\nimport DokitUi from './common/components/dokit-ui'\nimport Store from './store'\nimport {\n  applyLifecyle,\n  LifecycleHooks\n} from './common/js/lifecycle'\nimport {\n  getRouter\n} from './router'\nimport toast from './common/components/toast/index'\nexport class Dokit {\n  options = null\n  constructor(options) {\n    this.options = options\n    let app = createApp(App);\n    let {\n      features\n    } = options;\n    app.use(DokitUi);\n    app.use(getRouter(features));\n    app.use(Store);\n    if (document.readyState === \"loading\") {\n      // window.addEventListener(\"load\", function (_event) {\n      //   app.use(toast);\n      // });\n      document.addEventListener(\"readystatechange\", function () {\n        if (document.readyState === \"interactive\") {\n          app.use(toast);\n        }\n      });\n    }else{\n      app.use(toast);\n    }\n    Store.state.features = features;\n    this.app = app;\n    this.init();\n    this.onLoad();\n  }\n\n  onLoad() {\n    // Lifecycle Load\n    applyLifecyle(this.options.features, LifecycleHooks.LOAD)\n  }\n\n  onUnload() {\n    // Lifecycle UnLoad\n    applyLifecyle(this.options.features, LifecycleHooks.UNLOAD)\n  }\n\n  onProductReady() {\n    applyLifecyle(this.options.features, LifecycleHooks.PRODUCT_READY)\n  }\n\n  init() {\n    let dokitRoot = document.createElement('div')\n    dokitRoot.id = \"dokit-root\"\n    document.documentElement.appendChild(dokitRoot);\n    // dokit 容器\n    let el = document.createElement('div')\n    el.id = \"dokit-container\"\n    this.app.mount(el)\n    dokitRoot.appendChild(el)\n  }\n\n  setProductId(productId) {\n    this.productId = productId\n    Store.state.productId = productId\n    this.onProductReady()\n  }\n\n  startMultiControl(url, role) {\n    if(role==='client'||role==='host') {\n      role === 'host'&&(Store.state.isHost = true);\n      Store.state.socketUrl = url;\n      Store.state.socketConnect = true;\n    }\n  }\n\n  stopMultiControl(){\n    Store.state.socketConnect = false;\n  }\n\n  isNativeContainer(){\n    if(!Store.state.socketConnect){\n      Store.state.isNative = true;\n      let nativeConnectSerial = localStorage.getItem('nativeConnectSerial')\n      nativeConnectSerial&&(Store.state.connectSerial = nativeConnectSerial)\n    }\n  }\n}\n\nexport * from './store'\nexport * from './common/js/feature'\n\n//其他模块加载该模块时，import命令可以为该匿名函数指定任意名字\nexport default {\n  Dokit\n}"
  },
  {
    "path": "Web/packages/core/src/router/index.js",
    "content": "import {getRoutes} from './routes'\nimport {createRouter} from './router'\n\nexport function getRouter(features){\n  return createRouter({\n    routes: [...getRoutes(features)],\n  })\n}"
  },
  {
    "path": "Web/packages/core/src/router/router.js",
    "content": "import {shallowRef, unref} from 'vue'\n\n/**\n * DoKit 专用 Router\n * 1. 处理普通的 Router Container\n */\nexport const createRouter = function({routes:mainRoutes}){\n  const routes = mainRoutes;\n  const history = [];\n  const defaultRoute = 'home'\n  const homeRoute = matchRoute(defaultRoute)\n  const currentRoute = shallowRef(homeRoute)\n  /* Route Operation Start */\n  function addRoute(route){\n    routes.push(route)\n  }\n  function removeRoute(name){\n    let index = routes.findIndex((item)=> item.name === name)\n    if(index != -1){\n      return routes.splice(index, 1)\n    }else{\n      return null\n    }\n  }\n  function hasRoute(name){\n    let index = routes.findIndex((item)=> item.name === name)\n    return index !== -1\n  }\n  function getRoutes(){\n    return routes\n  }\n  /* Route Operation End */\n  /* Router Operation Start */\n  function push({name}){\n    history.push(name)\n    updateCurrentRoute({name})\n  }\n\n  function replace(name){\n    history.pop()\n    history.push(name)\n    updateCurrentRoute({name})\n  }\n\n  function back(){\n    history.pop();\n    let index = history.length - 1\n    updateCurrentRoute({\n      name: history[index]\n    })\n  }\n\n  /* Router Operation End */\n\n  function install(app){\n    // Install To Vue\n    const router = this\n    app.config.globalProperties.$router = router\n    Object.defineProperty(app.config.globalProperties, '$route', {\n      get: () => unref(currentRoute)\n    })\n  }\n\n  function matchRoute(name){\n    let route = routes.find((item) => {\n      return item.name === name\n    })\n    return route\n  }\n  \n  function updateCurrentRoute({name}){\n    let route = matchRoute(name) || homeRoute\n    currentRoute.value = route\n  }\n  return {\n    currentRoute,\n    addRoute,\n    removeRoute,\n    hasRoute,\n    getRoutes,\n    push,\n    replace,\n    back,\n    install\n  }\n}\n"
  },
  {
    "path": "Web/packages/core/src/router/routes.js",
    "content": "import Home from '../components/home'\nconst defaultRoute = [{\n  name: 'home',\n  component: Home\n}]\n\nexport function getRoutes(features){\n  let routes = []\n  features.forEach(feature => {\n    let {list, title:zoneTitle} = feature\n    list.forEach(item => {\n      let {name, nameZh, component} = item\n      routes.push({\n        name: name,\n        component: component,\n        meta: {\n          title: nameZh,\n          zone: zoneTitle\n        }\n      })\n    })\n  })\n  return [...defaultRoute, ...routes]\n}\n"
  },
  {
    "path": "Web/packages/core/src/store/index.js",
    "content": "import { Store } from \"../common/js/store\";\n\nconst store = new Store({\n  state: {\n    showContainer: false,\n    showHighlightElement:false,\n    highlightElement:null,\n    independPlugins: [],\n    interfaceList:[],\n    features: [],\n    socketUrl:'',\n    isHost:false,\n    socketConnect: false,\n    socketHistoryList:new Map(),\n    mcClientWaitRequestQueue:[],\n    mcHostWaitRequestQueue:[],\n    mcHostWaitResponseQueue:[],\n    mcClientWaitFetchRequestQueue:[],\n    mcHostWaitFetchRequestQueue:[],\n    mcHostWaitFetchResponseQueue:[],\n    isNative:false,\n  }\n})\n\n// 更新全局 Store 数据\nexport function updateGlobalData(key, value){\n  store.state[key] = value\n}\n\n// 获取当前 Store 数据的状态\nexport function getGlobalData(){\n  return store.state\n}\n\nexport function toggleContainer(flag){\n  if(flag){\n    store.state.showContainer = flag;\n  }else{\n    store.state.showContainer = !store.state.showContainer;\n  }\n}\n\nexport function toggleHighlight(flag){\n  if(flag){\n    store.state.showHighlightElement = flag;\n  }else{\n    store.state.showHighlightElement = !store.state.showHighlightElement;\n  }\n}\nexport function toggleElement(element){\n    store.state.highlightElement = element;\n}\n\nexport function addIndependPlugin(plugin){\n  // Unique Container\n  let index = store.state.independPlugins.findIndex(ele => {\n    return ele.name === plugin.name\n  })\n  if (index > -1) return\n  store.state.independPlugins.push(plugin)\n}\n\nexport function removeIndependPlugin(name){\n  let index = store.state.independPlugins.findIndex(ele => {\n    return ele.name === name\n  })\n  if (index === -1) return\n  store.state.independPlugins.splice(index, 1)\n}\n\nexport default store"
  },
  {
    "path": "Web/packages/utils/README.md",
    "content": "# `utils`\n\n> TODO: description\n\n## Usage\n\n```\nconst utils = require('utils');\n\n// TODO: DEMONSTRATE API\n```\n"
  },
  {
    "path": "Web/packages/utils/__tests__/index.test.js",
    "content": "'use strict';\n\ndescribe('utils', () => {\n    it('it needs tests', () => {})\n});\n"
  },
  {
    "path": "Web/packages/utils/package.json",
    "content": "{\n  \"name\": \"@dokit/web-utils\",\n  \"version\": \"0.0.3\",\n  \"description\": \"Dokit\",\n  \"keywords\": [\n    \"Dokit\"\n  ],\n  \"author\": \"duanlikang <duanlikang@didichuxing.com>\",\n  \"homepage\": \"https://github.com/didi/DoraemonKit#readme\",\n  \"license\": \"ISC\",\n  \"main\": \"dist/index.js\",\n  \"directories\": {\n    \"lib\": \"lib\",\n    \"test\": \"__tests__\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"publishConfig\": {\n    \"registry\": \"https://registry.npmjs.org/\",\n    \"access\": \"public\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/didi/DoraemonKit.git\"\n  },\n  \"scripts\": {\n    \"build\": \"npx cross-env NODE_ENV=production npx rollup -c\",\n    \"dev\": \"npx rollup -wc\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/didi/DoraemonKit/issues\"\n  },\n  \"gitHead\": \"886ea7c19806526668e5da0179da335e7df9d012\",\n  \"dependencies\": {\n    \"@dokit/web-core\": \"file:../core\"\n  }\n}\n"
  },
  {
    "path": "Web/packages/utils/rollup.config.js",
    "content": "import {terser} from 'rollup-plugin-terser'\nimport commonjs from 'rollup-plugin-commonjs'\nconst extendPlugins = []\nif(process.env.NODE_ENV === 'production'){\n  extendPlugins.push(terser())\n}\n\nexport default {\n  input: 'src/index.js',\n  output: {\n    name: 'index.js',\n    file: 'dist/index.js',\n    format: 'es'\n  },\n\n  plugins: [\n    commonjs(),\n    ...extendPlugins\n  ]\n}"
  },
  {
    "path": "Web/packages/utils/src/deepClone.js",
    "content": "const mapTag = '[object Map]';\nconst setTag = '[object Set]';\nconst arrayTag = '[object Array]';\nconst objectTag = '[object Object]';\nconst argsTag = '[object Arguments]';\n\nconst boolTag = '[object Boolean]';\nconst dateTag = '[object Date]';\nconst numberTag = '[object Number]';\nconst stringTag = '[object String]';\nconst symbolTag = '[object Symbol]';\nconst errorTag = '[object Error]';\nconst regexpTag = '[object RegExp]';\nconst funcTag = '[object Function]';\n\nconst deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];\n\n\nfunction forEach(array, iteratee) {\n    let index = -1;\n    const length = array.length;\n    while (++index < length) {\n        iteratee(array[index], index);\n    }\n    return array;\n}\n\nfunction isObject(target) {\n    const type = typeof target;\n    return target !== null && (type === 'object' || type === 'function');\n}\n\nfunction getType(target) {\n    return Object.prototype.toString.call(target);\n}\n\nfunction cloneSymbol(targe) {\n    return Object(Symbol.prototype.valueOf.call(targe));\n}\n\nfunction cloneReg(targe) {\n    const reFlags = /\\w*$/;\n    const result = new targe.constructor(targe.source, reFlags.exec(targe));\n    result.lastIndex = targe.lastIndex;\n    return result;\n}\n\nfunction cloneFunction(func) {\n    const bodyReg = /(?<={)(.|\\n)+(?=})/m;\n    const paramReg = /(?<=\\().+(?=\\)\\s+{)/;\n    const funcString = func.toString();\n    if (func.prototype) {\n        const param = paramReg.exec(funcString);\n        const body = bodyReg.exec(funcString);\n        if (body) {\n            if (param) {\n                const paramArr = param[0].split(',');\n                return new Function(...paramArr, body[0]);\n            } else {\n                return new Function(body[0]);\n            }\n        } else {\n            return null;\n        }\n    } else {\n        // \n        return eval(funcString);\n    }\n}\n\nfunction cloneOtherType(targe, type) {\n    const Ctor = targe.constructor;\n    switch (type) {\n        case boolTag:\n        case numberTag:\n        case stringTag:\n        case errorTag:\n        case dateTag:\n            return new Ctor(targe);\n        case regexpTag:\n            return cloneReg(targe);\n        case symbolTag:\n            return cloneSymbol(targe);\n        case funcTag:\n            return cloneFunction(targe);\n        default:\n            return null;\n    }\n}\n\nfunction allKey(obj, newobj) {\n    let ret = [];\n    for (const key in obj) {\n        ret.push(key);\n        obj.propertyIsEnumerable(key)||defineReactive(newobj, key, obj[key])\n    };\n    function defineReactive(data, key, value) {\n        Object.defineProperty(data, key, {\n            enumerable:true,\n            get() {\n                return value\n            },\n            set(newValue) {\n                value = newValue\n            }\n        })\n    }\n    return ret\n}\n\nexport const clone = (target, map = new WeakMap()) => {\n    // 克隆原始类型\n    if (!isObject(target)) {\n        return target;\n    }\n\n    // 初始化\n    const type = getType(target);\n    let cloneTarget;\n    if (deepTag.includes(type) || Object.prototype.isPrototypeOf(target) || Array.prototype.isPrototypeOf(target)) {\n        cloneTarget = Object.create(target.constructor.prototype);\n    } else {\n        return cloneOtherType(target, type);\n    }\n\n    // 防止循环引用\n    if (map.get(target)) {\n        return map.get(target);\n    }\n    map.set(target, cloneTarget);\n\n    // 克隆set\n    if (type === setTag) {\n        target.forEach(value => {\n            cloneTarget.add(clone(value, map));\n        });\n        return cloneTarget;\n    }\n\n    // 克隆map\n    if (type === mapTag) {\n        target.forEach((value, key) => {\n            cloneTarget.set(key, clone(value, map));\n        });\n        return cloneTarget;\n    }\n\n    // 克隆对象和数组\n    const keys = Array.prototype.isPrototypeOf(cloneTarget) ? undefined : allKey(target, cloneTarget);\n    forEach(keys || target, (value, key) => {\n        if (keys) {\n            key = value;\n        }\n        if(Array.prototype.isPrototypeOf(cloneTarget)){\n            cloneTarget.splice(key,1,clone(target[key], map));\n        }else{\n            cloneTarget[key] = clone(target[key], map);\n        }\n    });\n\n    return cloneTarget;\n}"
  },
  {
    "path": "Web/packages/utils/src/dom.js",
    "content": "export const inBrowser = typeof window !== 'undefined'\n"
  },
  {
    "path": "Web/packages/utils/src/dragable.js",
    "content": "/**\n * 拖拽指令 v-dragable\n * 减少外部依赖\n * 默认使用v-dragable\n * 也接受传入一个config对象 v-dragable=\"config\"\n * config支持 name opacity left top safeBottom 等属性\n*/\nconst INIT_VALUE = 9999\n// const SAFE_BOTTOM = 50 // 底部防误触\n\nlet MOUSE_DOWN_FLAG = false\n\nconst DEFAULT_EL_CONF = {\n  name: '',         // 名称 用于存储位置storage的标识，没有则不存储\n  opacity: 1,       // 默认透明度\n  left: '',         // 初始位置, 没有则居中\n  top: '',          // 初始位置, 没有则居中\n  safeBottom: 0\n}\n\n// TODO 拖拽事件兼容 Pc处理\n// TODO 默认初始位置为右下角\nexport const dragable = {\n  mounted (el, binding) {\n    el.config = {\n      ...DEFAULT_EL_CONF,\n      ...binding.value\n    }\n    // 初始化变量\n    el.dokitEntryLastX = INIT_VALUE\n    el.dokitEntryLastY = INIT_VALUE\n    // 初始化样式\n    el.style.position = 'fixed'\n    el.style.opacity = el.config.opacity\n    el.dokitPositionLeft = getDefaultX(el)\n    el.dokitPositionTop = getDefaultY(el)\n    el.style.top = `${el.dokitPositionTop}px`\n    el.style.left = `${el.dokitPositionLeft}px`\n\n    adjustPosition(el);\n\n    // 触摸事件监听\n    el.ontouchstart = () => {\n      moveStart(el)\n    }\n    el.ontouchmove = (e) => {\n      e.preventDefault()\n      moving(el, e)\n    }\n    el.ontouchend = (e) => {\n      moveEnd(el, e)\n    }\n    // PC鼠标事件\n    el.onmousedown = (e) => {\n      e.preventDefault()\n      moveStart(el)\n      MOUSE_DOWN_FLAG = true\n    }\n\n    window.addEventListener('mousemove', (e)=> {\n      if (MOUSE_DOWN_FLAG) moving(el, e)\n    })\n\n    window.addEventListener('mouseup', (e)=> {\n      if (MOUSE_DOWN_FLAG) {\n        moveEnd(el, e)\n        MOUSE_DOWN_FLAG = false\n      }\n    })\n\n    window.addEventListener('resize', ()=> {\n      adjustPosition(el)\n    })\n  }\n}\n\nfunction moveStart(el) {\n  el.style.opacity = 1\n}\n\nfunction moving(el, e) {\n  let target = e.touches ? e.touches[0] : e\n  if (el.dokitEntryLastX === INIT_VALUE) {\n    el.dokitEntryLastX = target.clientX\n    el.dokitEntryLastY = target.clientY\n    return\n  }\n\n  el.dokitPositionTop += (target.clientY - el.dokitEntryLastY)\n  el.dokitPositionLeft += (target.clientX - el.dokitEntryLastX)\n  el.dokitEntryLastX = target.clientX\n  el.dokitEntryLastY = target.clientY\n\n  // el.style.top = `${getAvailableTop(el)}px`\n  // el.style.left = `${getAvailableLeft(el)}px`\n  el.style.top = `${el.dokitPositionTop}px`\n  el.style.left = `${el.dokitPositionLeft}px`\n}\n\nfunction moveEnd(el, e) {\n  setTimeout(() => {\n    adjustPosition(el)\n    el.config.name && localStorage.setItem(`dokitPositionTop_${el.config.name}`, el.dokitPositionTop);\n    el.config.name && localStorage.setItem(`dokitPositionLeft_${el.config.name}`, el.dokitPositionLeft);\n  }, 100)\n  el.dokitEntryLastX = INIT_VALUE\n  el.dokitEntryLastY = INIT_VALUE\n  el.style.opacity = el.config.opacity\n}\n\nfunction getDefaultX(el){\n  let defaultX = el.config.left || Math.round(window.innerWidth/2)\n  return localStorage.getItem(`dokitPositionLeft_${el.config.name}`) ? parseInt(localStorage.getItem(`dokitPositionLeft_${el.config.name}`)) : defaultX\n}\nfunction getDefaultY(el){\n  let defaultY = el.config.top || Math.round(window.innerHeight/2)\n  return localStorage.getItem(`dokitPositionTop_${el.config.name}`) ? parseInt(localStorage.getItem(`dokitPositionTop_${el.config.name}`)) : defaultY\n}\n\n// function getAvailableLeft(el){\n//   return standardNumber(el.dokitPositionLeft, window.innerWidth - el.clientWidth)\n// }\n// function getAvailableTop(el){\n//   return standardNumber(el.dokitPositionTop, window.innerHeight - el.clientHeight)\n// }\n// function standardNumber(number, max){\n//   if(number < 0){\n//     return 0\n//   }\n//   if(number >= max){\n//     return max\n//   }\n//   return number\n// }\n\nfunction adjustPosition(el) {\n  if (el.dokitPositionLeft < 0) {\n    el.dokitPositionLeft = 0\n    el.style.left = `${el.dokitPositionLeft}px`\n  } else if (el.dokitPositionLeft + el.getBoundingClientRect().width > window.innerWidth) {\n    el.dokitPositionLeft = window.innerWidth - el.getBoundingClientRect().width\n    el.style.left = `${el.dokitPositionLeft}px`\n  }\n  \n  if (el.dokitPositionTop < 0) {\n    el.dokitPositionTop = 0\n    el.style.top = `${el.dokitPositionTop}px`\n\n  } else if (el.dokitPositionTop + el.getBoundingClientRect().height + el.config.safeBottom > window.innerHeight) {\n    el.dokitPositionTop = window.innerHeight - el.getBoundingClientRect().height - el.config.safeBottom\n    el.style.top = `${el.dokitPositionTop}px`\n  }\n}"
  },
  {
    "path": "Web/packages/utils/src/eventEmiter.js",
    "content": "export class EventEmitter{\n  constructor(){\n      this._events = {};\n  }\n\n  on(eventName, callback){\n      // if(this._events[eventName]){\n      //     if(this.eventName !== \"newListener\"){\n      //         this.emit(\"newListener\", eventName)\n      //     }\n      // }\n      const callbacks = this._events[eventName] || [];\n      callbacks.push(callback);\n      this._events[eventName] = callbacks\n  }\n\n  emit(eventName, ...args){\n      const callbacks = this._events[eventName] || [];\n      callbacks.forEach(cb => cb(...args))\n  }\n\n  once(eventName, callback){\n      const one = (...args)=>{\n          callback(...args)\n          this.off(eventName, one)\n      }\n      one.initialCallback = callback;\n      this.on(eventName, one)\n  }\n\n   off(eventName, callback){\n      const callbacks = this._events[eventName] || []\n      const newCallbacks = callbacks.filter(fn => fn != callback && fn.initialCallback != callback /* 用于once的取消订阅 */)\n      this._events[eventName] = newCallbacks;\n  }\n}\n\n"
  },
  {
    "path": "Web/packages/utils/src/index.js",
    "content": "export * from './utils'\nexport * from './dom'\nexport * from './dragable'\nexport * from './eventEmiter'\nexport * from './deepClone'\nexport * from './md5'\n\n// import request from './network'\nexport * from './network'\n"
  },
  {
    "path": "Web/packages/utils/src/md5.js",
    "content": "/*\n * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message\n * Digest Algorithm, as defined in RFC 1321.\n * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.\n * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet\n * Distributed under the BSD License\n * See http://pajhome.org.uk/crypt/md5 for more info.\n */\n\n/*\n * Configurable variables. You may need to tweak these to be compatible with\n * the server-side, but the defaults work in most cases.\n */\nvar hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase        */\nvar b64pad = \"\"; /* base-64 pad character. \"=\" for strict RFC compliance   */\nvar chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode      */\n\n/*\n * These are the functions you'll usually want to call\n * They take string arguments and return either hex or base-64 encoded strings\n */\nfunction hex_md5(s) {\n  return binl2hex(core_md5(str2binl(s), s.length * chrsz));\n}\n\nfunction b64_md5(s) {\n  return binl2b64(core_md5(str2binl(s), s.length * chrsz));\n}\n\nfunction str_md5(s) {\n  return binl2str(core_md5(str2binl(s), s.length * chrsz));\n}\n\nfunction hex_hmac_md5(key, data) {\n  return binl2hex(core_hmac_md5(key, data));\n}\n\nfunction b64_hmac_md5(key, data) {\n  return binl2b64(core_hmac_md5(key, data));\n}\n\nfunction str_hmac_md5(key, data) {\n  return binl2str(core_hmac_md5(key, data));\n}\n\n/*\n * Perform a simple self-test to see if the VM is working\n */\nfunction md5_vm_test() {\n  return hex_md5(\"abc\") == \"900150983cd24fb0d6963f7d28e17f72\";\n}\n\n/*\n * Calculate the MD5 of an array of little-endian words, and a bit length\n */\nfunction core_md5(x, len) {\n  /* append padding */\n  x[len >> 5] |= 0x80 << ((len) % 32);\n  x[(((len + 64) >>> 9) << 4) + 14] = len;\n\n  var a = 1732584193;\n  var b = -271733879;\n  var c = -1732584194;\n  var d = 271733878;\n\n  for (var i = 0; i < x.length; i += 16) {\n    var olda = a;\n    var oldb = b;\n    var oldc = c;\n    var oldd = d;\n\n    a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936);\n    d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);\n    c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);\n    b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);\n    a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);\n    d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);\n    c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);\n    b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);\n    a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);\n    d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);\n    c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);\n    b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);\n    a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);\n    d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);\n    c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);\n    b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);\n\n    a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);\n    d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);\n    c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);\n    b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302);\n    a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);\n    d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);\n    c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);\n    b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);\n    a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);\n    d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);\n    c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);\n    b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);\n    a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);\n    d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);\n    c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);\n    b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);\n\n    a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);\n    d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);\n    c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);\n    b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);\n    a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);\n    d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);\n    c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);\n    b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);\n    a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);\n    d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222);\n    c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);\n    b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);\n    a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);\n    d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);\n    c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);\n    b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);\n\n    a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844);\n    d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);\n    c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);\n    b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);\n    a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);\n    d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);\n    c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);\n    b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);\n    a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);\n    d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);\n    c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);\n    b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);\n    a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);\n    d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);\n    c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);\n    b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);\n\n    a = safe_add(a, olda);\n    b = safe_add(b, oldb);\n    c = safe_add(c, oldc);\n    d = safe_add(d, oldd);\n  }\n  return Array(a, b, c, d);\n\n}\n\n/*\n * These functions implement the four basic operations the algorithm uses.\n */\nfunction md5_cmn(q, a, b, x, s, t) {\n  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);\n}\n\nfunction md5_ff(a, b, c, d, x, s, t) {\n  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);\n}\n\nfunction md5_gg(a, b, c, d, x, s, t) {\n  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);\n}\n\nfunction md5_hh(a, b, c, d, x, s, t) {\n  return md5_cmn(b ^ c ^ d, a, b, x, s, t);\n}\n\nfunction md5_ii(a, b, c, d, x, s, t) {\n  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);\n}\n\n/*\n * Calculate the HMAC-MD5, of a key and some data\n */\nfunction core_hmac_md5(key, data) {\n  var bkey = str2binl(key);\n  if (bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);\n\n  var ipad = Array(16),\n    opad = Array(16);\n  for (var i = 0; i < 16; i++) {\n    ipad[i] = bkey[i] ^ 0x36363636;\n    opad[i] = bkey[i] ^ 0x5C5C5C5C;\n  }\n\n  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);\n  return core_md5(opad.concat(hash), 512 + 128);\n}\n\n/*\n * Add integers, wrapping at 2^32. This uses 16-bit operations internally\n * to work around bugs in some JS interpreters.\n */\nfunction safe_add(x, y) {\n  var lsw = (x & 0xFFFF) + (y & 0xFFFF);\n  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n  return (msw << 16) | (lsw & 0xFFFF);\n}\n\n/*\n * Bitwise rotate a 32-bit number to the left.\n */\nfunction bit_rol(num, cnt) {\n  return (num << cnt) | (num >>> (32 - cnt));\n}\n\n/*\n * Convert a string to an array of little-endian words\n * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.\n */\nfunction str2binl(str) {\n  var bin = Array();\n  var mask = (1 << chrsz) - 1;\n  for (var i = 0; i < str.length * chrsz; i += chrsz)\n    bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (i % 32);\n  return bin;\n}\n\n/*\n * Convert an array of little-endian words to a string\n */\nfunction binl2str(bin) {\n  var str = \"\";\n  var mask = (1 << chrsz) - 1;\n  for (var i = 0; i < bin.length * 32; i += chrsz)\n    str += String.fromCharCode((bin[i >> 5] >>> (i % 32)) & mask);\n  return str;\n}\n\n/*\n * Convert an array of little-endian words to a hex string.\n */\nfunction binl2hex(binarray) {\n  var hex_tab = hexcase ? \"0123456789ABCDEF\" : \"0123456789abcdef\";\n  var str = \"\";\n  for (var i = 0; i < binarray.length * 4; i++) {\n    str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xF) +\n      hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xF);\n  }\n  return str;\n}\n\n/*\n * Convert an array of little-endian words to a base-64 string\n */\nfunction binl2b64(binarray) {\n  var tab = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n  var str = \"\";\n  for (var i = 0; i < binarray.length * 4; i += 3) {\n    var triplet = (((binarray[i >> 2] >> 8 * (i % 4)) & 0xFF) << 16) |\n      (((binarray[i + 1 >> 2] >> 8 * ((i + 1) % 4)) & 0xFF) << 8) |\n      ((binarray[i + 2 >> 2] >> 8 * ((i + 2) % 4)) & 0xFF);\n    for (var j = 0; j < 4; j++) {\n      if (i * 8 + j * 6 > binarray.length * 32) str += b64pad;\n      else str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3F);\n    }\n  }\n  return str;\n}\nexport {\n  hex_md5,\n  b64_md5,\n  str_md5,\n  hex_hmac_md5,\n  b64_hmac_md5,\n  str_hmac_md5,\n}"
  },
  {
    "path": "Web/packages/utils/src/network.js",
    "content": "import {\n  EventEmitter\n} from './eventEmiter'\nimport {\n  guid,completionUrlProtocol\n} from './utils'\nconst getAllResponseHeadersMap = function (xhr) {\n  let headers = xhr.getAllResponseHeaders();\n  let arr = headers.trim().split(/[\\r\\n]+/);\n  let headerMap = {};\n\n  arr.forEach(line => {\n    let parts = line.split(': ');\n    let header = parts.shift();\n    let value = parts.join(': ');\n    headerMap[header] = value;\n  });\n}\n\nexport class Request extends EventEmitter {\n  constructor() {\n    super()\n    this.hookFetchConfig = {}\n    this.hookXhrConfig = {}\n    this.multiControlhookConfig = {}\n    this.initialize()\n  }\n  initialize() {\n    let Req = this\n    // const {send:originSend, open:originOpen, setRequestHeader: originSetRequestHeader} = window.XMLHttpRequest.prototype;\n\n    const winXhrProto = window.XMLHttpRequest.prototype;\n\n    const originSend = winXhrProto.send;\n    const originOpen = winXhrProto.open;\n    const originSetRequestHeader = winXhrProto.setRequestHeader;\n    const getResponseHeader = winXhrProto.getResponseHeader;\n    const getAllResponseHeaders = winXhrProto.getAllResponseHeaders;\n    // XMLHttp\n    window.XMLHttpRequest.prototype.setRequestHeader = function (...args) {\n      this.reqConf.requestHeaders||(this.reqConf.requestHeaders = {})\n      this.reqConf.requestHeaders[args[0]] = args[1]\n      if (Req.hookXhrConfig.onBeforeSetRequestHeader) {\n        args = Req.hookXhrConfig.onBeforeSetRequestHeader(args, this.reqConf);\n        // 返回false则取消设置请求头 （api-mock拦截接口，会将post改为get 此时设置请求头Content-Type会报跨域错误）\n        args && originSetRequestHeader.apply(this, args);\n      } else {\n        originSetRequestHeader.apply(this, args);\n      }\n    }\n    window.addEventListener('xhrSendStart', function (e) {\n      console.log('xhrSendStart', e);\n    });\n    window.XMLHttpRequest.prototype.open = function (...args) {\n      console.log('open', args);\n      let originArgs = {\n        ...args\n      }\n      args = Req.hookXhrConfig.onBeforeOpen && Req.hookXhrConfig.onBeforeOpen(args) || args\n      const xhr = this;\n      \n      this.reqConf = {\n        id: guid(),\n        type: 'xhr',\n        requestInfo: {\n          method: args[0].toUpperCase(),\n          url: completionUrlProtocol(args[1])\n        },\n        originRequestInfo: {\n          method: originArgs[0].toUpperCase(),\n          url: completionUrlProtocol(originArgs[1])\n        }\n      }\n\n      xhr.addEventListener('readystatechange', function (e) {\n        try {\n          switch (xhr.readyState) {\n            case 2:\n              // this.headersReceived();\n              Req.multiControlhookConfig?.xhrHostRequest?.call(Req,xhr);\n              break;\n            case 4:\n              Req.handleDone(xhr);\n              Req.multiControlhookConfig?.xhrHostResponse?.call(Req,xhr);\n              break;\n          }         \n        } catch (error) {\n            console.log(error)\n        }\n      });\n\n      xhr.addEventListener('error', function (e) {\n        Req.emit('REQUEST.ERROR', {\n          id: this.reqConf.id,\n          responseInfo: {\n            type: 'error'\n          }\n        })\n      });\n      xhr.getResponseHeader = (name) => {\n        if(Req.multiControlhookConfig?.getResponseHeader){\n          return Req.multiControlhookConfig?.getResponseHeader.call(this, getResponseHeader,name);\n        }else{\n          return getResponseHeader.call(this, name);\n        }\n      }\n      xhr.getAllResponseHeaders = ()=> {\n        if(Req.multiControlhookConfig?.getAllResponseHeaders){\n          return Req.multiControlhookConfig?.getAllResponseHeaders.apply(this, [getAllResponseHeaders,...arguments]);\n        }else{\n          return getAllResponseHeaders.call(this, args);\n        }\n      }\n      originOpen.apply(this, args);\n    };\n\n    window.XMLHttpRequest.prototype.send = function () {\n      try {\n        if (arguments.length) {\n          this.reqConf.requestInfo.body = arguments[0]\n        }\n        Req.multiControlhookConfig?.xhrClientQuery?.apply(this, [originSend,...arguments]);\n        Req.emit('REQUEST.SEND', this.reqConf)\n        if(Req.multiControlhookConfig?.isOriginSend){\n          Req.multiControlhookConfig?.isOriginSend.apply(this, [originSend,...arguments]);\n        }else{\n          originSend.apply(this, arguments);\n        }\n      } catch (error) {\n        console.log(error)\n      }\n    }\n\n    // fetch\n    const origFetch = window.fetch;\n    window.fetch = async function (...args) {\n      try {\n        let did = guid();\n        let pid = guid();\n        let reqId = guid()\n        let fetchResult = null;\n        console.log('fetchHostRequest:',Req.multiControlhookConfig?.fetchHostRequest)\n        Req.multiControlhookConfig?.fetchHostRequest?.apply(Req, [did,pid,...arguments]);\n        fetchResult = Req.multiControlhookConfig?.fetchResult?.apply(Req, [pid,reqId,origFetch,...arguments]);\n        Req.multiControlhookConfig?.fetchClientQuery?.apply(Req, [pid,...arguments]);\n        args = Req.hookFetchConfig.onBeforeFetch && Req.hookFetchConfig.onBeforeFetch(args) || args\n        Req.emit('REQUEST.SEND', {\n          id: reqId,\n          type: 'fetch',\n          requestInfo: {\n            url: args[0],\n            method: (args.length > 1 ? (args[1].method || 'get') : 'get').toUpperCase(),\n            headers: args.length > 1 ? (args[1].headers || {}) : {},\n            body: args.length > 1 ? (args[1].body || '') : ''\n          }\n        })\n        if (!fetchResult) {\n          fetchResult = origFetch(...args);\n          const res = await fetchResult\n          const r = await res.clone().text()\n          Req.multiControlhookConfig?.fetchHostResponse?.apply(Req, [did,res,r]);\n          Req.emit('REQUEST.DONE', {\n            id: reqId,\n            responseInfo: {\n              contentType: res.headers.get('Content-Type'),\n              status: res.status,\n              resRaw: r\n            }\n          })     \n        }\n        return fetchResult;\n      } catch (error) {\n        console.error(error)\n      }\n    };\n  }\n\n  hookXhr(hookXhrConfig) {\n    this.hookXhrConfig = hookXhrConfig\n  }\n\n  hookFetch(hookFetchConfig) {\n    this.hookFetchConfig = hookFetchConfig\n  }\n\n  getType(contentType) {\n    if (!contentType)\n      return {\n        type: 'unknown',\n        subType: 'unknown',\n      };\n\n    const type = contentType.split(';')[0].split('/');\n\n    return {\n      type: type[0],\n      subType: type[type.length - 1],\n    };\n  }\n\n  readBlobAsText(blob, callback) {\n    const reader = new FileReader();\n    reader.onload = () => {\n      callback(null, reader.result);\n    };\n    reader.onerror = err => {\n      callback(err);\n    };\n    reader.readAsText(blob);\n  }\n\n  handleDone(xhr) {\n    const resType = xhr.responseType;\n    let resTxt = '';\n    const update = () => {\n      this.emit('REQUEST.DONE', {\n        id: xhr.reqConf.id,\n        responseInfo: {\n          resRaw: resTxt,\n          contentType: xhr.getResponseHeader('Content-Type'),\n          status: xhr.status,\n        }\n      })\n    };\n\n    const type = this.getType(xhr.getResponseHeader('Content-Type') || '');\n    if (\n      resType === 'blob' &&\n      (type.type === 'text' ||\n        type.subType === 'javascript' ||\n        type.subType === 'json')\n    ) {\n      this.readBlobAsText(xhr.response, (err, result) => {\n        if (result) resTxt = result;\n        update();\n      });\n    } else {\n      if (resType === '' || resType === 'text') resTxt = xhr.responseText;\n      if (resType === 'json') resTxt = JSON.stringify(xhr.response);\n\n      update();\n    }\n  }\n  multiControlhook(multiControlhookConfig) {\n    this.multiControlhookConfig = multiControlhookConfig\n  }\n}"
  },
  {
    "path": "Web/packages/utils/src/utils.js",
    "content": "\nimport {EventEmitter} from './eventEmiter'\nexport const isObject = function (obj) {\n  return Object.prototype.toString.call(obj) === '[object Object]'\n}\n\nexport const isFunction = function (obj) {\n  return typeof obj === 'function'\n}\n\nexport const isMobile = function () {\n  if (window.navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)) {\n    return true\n  }\n  return false\n}\n\nexport const getPartUrlByParam = (url, param) => {\n  const reg = /^(?:([A-Za-z]+):)?(\\/{0,3})([0-9.\\-A-Za-z]+)(?::(\\d+))?(?:\\/([^?#]*))?(?:\\?([^#]*))?(?:#(.*))?$/;\n  const res = reg.exec(url)\n  const fields = ['url', 'scheme', 'slash', 'host', 'port', 'path', 'query', 'hash'];\n  return res[fields.indexOf(param)]\n}\n\nexport const getQueryMap = (queryStr) => {\n  if (!queryStr) {\n    return null\n  }\n  let queryMap = {}\n  let queryList = queryStr.split('&')\n  queryList.forEach(query => {\n    if (query) {\n      queryMap[query.split('=')[0]] = query.split('=')[1]\n    }\n  });\n  return queryMap\n}\n\nexport const $bus = new EventEmitter()\n\nexport const guid = function () {\n  function S4() {\n    return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)\n  }\n  return (S4() + S4() + \"-\" + S4() + \"-\" + S4() + \"-\" + S4() + \"-\" + S4() + S4() + S4())\n}\n\nexport const HTTP_STATUS_CODES = {\n  100: \"Continue\",\n  101: \"Switching Protocols\",\n  200: \"OK\",\n  201: \"Created\",\n  202: \"Accepted\",\n  203: \"Non-Authoritative Information\",\n  204: \"No Content\",\n  205: \"Reset Content\",\n  206: \"Partial Content\",\n  300: \"Multiple Choice\",\n  301: \"Moved Permanently\",\n  302: \"Found\",\n  303: \"See Other\",\n  304: \"Not Modified\",\n  305: \"Use Proxy\",\n  307: \"Temporary Redirect\",\n  400: \"Bad Request\",\n  401: \"Unauthorized\",\n  402: \"Payment Required\",\n  403: \"Forbidden\",\n  404: \"Not Found\",\n  405: \"Method Not Allowed\",\n  406: \"Not Acceptable\",\n  407: \"Proxy Authentication Required\",\n  408: \"Request Timeout\",\n  409: \"Conflict\",\n  410: \"Gone\",\n  411: \"Length Required\",\n  412: \"Precondition Failed\",\n  413: \"Request Entity Too Large\",\n  414: \"Request-URI Too Long\",\n  415: \"Unsupported Media Type\",\n  416: \"Requested Range Not Satisfiable\",\n  417: \"Expectation Failed\",\n  422: \"Unprocessable Entity\",\n  500: \"Internal Server Error\",\n  501: \"Not Implemented\",\n  502: \"Bad Gateway\",\n  503: \"Service Unavailable\",\n  504: \"Gateway Timeout\",\n  505: \"HTTP Version Not Supported\"\n}\n\n/**\n * 获取url参数\n * @param key 参数的key\n */\nexport const getQueryVariable = (key, url) => {\n  if (url.indexOf('?') >= 0) {\n    var query = url ? url.split('?')[1] : window.location.search.substring(1);\n    var vars = query.split(\"&\");\n    for (var i = 0; i < vars.length; i++) {\n      var pair = vars[i].split(\"=\");\n      if (pair[0] == key) {\n        return pair[1];\n      }\n    }\n  }\n  return (false);\n}\n\nexport const strMapToObj = (strMap) => {\n  let obj = Object.create(null);\n  for (let [k, v] of strMap) {\n    obj[k] = v;\n  }\n  return obj;\n}\n\nexport const completionUrlProtocol = (url) => {\n  if (url.substr(0, 5).toLowerCase() == \"http:\" || url.substr(0, 6).toLowerCase() == \"https:\") {\n    return url\n  } else {\n    return `${location.protocol}` + url;\n  }\n}"
  },
  {
    "path": "Web/packages/web/README.md",
    "content": "# `Dokit For Web`\n\n\n\n### 基础插件\n#### Console\n基于 Web 的日志组件\n##### TODO\n- [ ] 支持多种类型的日志\n\n- [ ] 支持对象类型展开\n\n"
  },
  {
    "path": "Web/packages/web/__tests__/index.test.js",
    "content": "'use strict';\n\ndescribe('web', () => {\n    it('it needs tests', () => {})\n});\n"
  },
  {
    "path": "Web/packages/web/package.json",
    "content": "{\n  \"name\": \"@dokit/web\",\n  \"version\": \"0.0.3\",\n  \"description\": \"Dokit Web Main Entry\",\n  \"keywords\": [\n    \"Dokit\"\n  ],\n  \"author\": \"duanlikang <duanlikang@didichuxing.com>\",\n  \"homepage\": \"http://dokit.cn\",\n  \"license\": \"ISC\",\n  \"main\": \"dist/dokit.js\",\n  \"unpkg\": \"dist/dokit.js\",\n  \"jsdelivr\": \"dist/dokit.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"publishConfig\": {\n    \"registry\": \"https://registry.npmjs.org/\",\n    \"access\": \"public\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/didi/DoraemonKit.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/didi/DoraemonKit/issues\"\n  },\n  \"scripts\": {\n    \"build\": \"npx cross-env NODE_ENV=production npx rollup -c\",\n    \"dev\": \"npx rollup -wc\"\n  },\n  \"dependencies\": {\n    \"@dokit/web-core\": \"file:../core\",\n    \"@dokit/web-utils\": \"file:../utils\",\n    \"jsqr\": \"^1.4.0\",\n    \"web-vitals\": \"^2.1.2\",\n    \"moment\": \"^2.29.1\"\n  },\n  \"gitHead\": \"886ea7c19806526668e5da0179da335e7df9d012\"\n}\n"
  },
  {
    "path": "Web/packages/web/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Dokit For Web</title>\n  <style>\n    .asd4 .asd {\n      color: rgba(241, 9, 9, 0.3);\n      background: url(https://pt-starimg.didistatic.com/static/starimg/img/UioDkptMhY1635838350336.png);\n      box-shadow: 10px 10px 5px rgb(122, 222, 222, 0.7);\n    }\n\n    body {\n      top: 10px;\n    }\n\n    .asd1 {\n      position: relative;\n    }\n\n    .weatherInfo {\n      font-size: 13px;\n      overflow: hidden;\n    }\n  </style>\n  <script src=\"https://unpkg.com/axios/dist/axios.min.js\"></script>\n  <script src=\"https://unpkg.com/vue@next\"></script>\n  <script src=\"dokit.js\"></script>\n  <script>\n    Dokit.startMultiControl('ws://172.23.165.79:8000/proxy/multicontrol/OYZGTSEL', window.location.href.split(\"?asd=\")[1])\n    // axios({\n    //   url: 'https://thor.weidian.com/skittles/share.getConfig/1.0?wdtoken=4000fcea&_=1649759524263',\n    // }).then((res) => {\n    //   console.log('axios:',res)\n    //   // changeNode.style.color = \"blue\"\n    //   weatherInfoNode.innerText = `接口数据:${JSON.stringify(res)}`\n    // }).catch((err) => {\n    //   console.log(err)\n    // });\n    Dokit.setProductId('749a0600b5e48dd77cf8ee680be7b1b7')\n  </script>\n</head>\n\n<body>\n  <input calss=\"inputDemo\" />\n  <div>DoKit For Web</div>\n  <div class=\"asd1\">\n    <div class=\"asd2\">\n      <div class=\"asd3\">\n        <div class=\"asd4\">\n          <span class=\"asd\" id=\"qwe\" style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</span>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div style=\"height:300px;overflow: scroll;\">\n    <a href=\"https://hummer.didi.cn/home#/\">Hummer</a>\n    <img src=\"https://pt-starimg.didistatic.com/static/starimg/img/UioDkptMhY1635838350336.png\" />\n    <h2 class=\"asd\" id=\"qwe\" style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <input>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  </div>\n  <h2 class=\"weatherInfo\">接口数据：</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <div class=\"asdasd\">zepto测试</div>\n</body>\n<script src=\"https://cdnjs.gtimg.com/cdnjs/libs/zepto/1.1.4/zepto.js\"></script>\n<script>\n  let eventNode = document.getElementsByClassName('asd1')[0]\n  let changeNode = document.getElementById('qwe')\n  let weatherInfoNode = document.getElementsByClassName('weatherInfo')[0]\n  // axios({\n  //     url: 'https://thor.weidian.com/skittles/share.getConfig/1.0?wdtoken=4000fcea&_=1649759524263',\n  //   }).then((res) => {\n  //     console.log('axios:',res)\n  //     // changeNode.style.color = \"blue\"\n  //     weatherInfoNode.innerText = `接口数据:${JSON.stringify(res)}`\n  //   }).catch((err) => {\n  //     console.log(err)\n  //   });\n  eventNode.onclick = (e) => {\n    fetch('https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7', {})\n      .then((response) => {\n        let weatherInfo = response.json();\n        return weatherInfo;\n      })\n      .then((info) => {\n        console.log('weatherInfo', info)\n        changeNode.style.color = \"blue\"\n        weatherInfoNode.innerText = `接口数据:${JSON.stringify(info)}`\n        // axios({\n        //   url: 'https://daijia.kuaidadi.com/gateway?api=lj.assign.d.regions&apiVersion=1.0.0&appKey=b4f945fe780140d8a0d19d1f2d021db7&appVersion=6.8.9&mobileType=vivo+NEX+S&osType=2&osVersion=10&sign=f2d04df3510670528a067ae52ded2c49&timestamp=1648180651319&token=Mk_G1OHDSNax7QIHN8j_BmFarMeZA_ORR7yKj831WPYkyDvKw0AMAOG7TC2MtCtrbbV__98hD-fRbCAhlfHdg_E0A99KV5I66KAI3UgTeiGLqo5Cr6QK3Y-NJH__CCcShDNZJvPmc4tWprm6C9cdQ1jIlc_r-74sZGhR24QbaeGtVm9RhTuJmelRIDx2QXiSuv0CAAD__w%3D%3D&ttid=-1&userId=182784&userRole=2',\n        // }).then((res) => {\n        //   console.log(res)\n        // }).catch((err) => {\n        //   console.log(err)\n        // });\n      });\n    // for (let index = 0; index < 10; index++) {\n    // axios({\n    //   url: 'https://daijia.kuaidadi.com/gateway?api=lj.assign.d.regions&apiVersion=1.0.0&appKey=b4f945fe780140d8a0d19d1f2d021db7&appVersion=6.8.9&mobileType=vivo+NEX+S&osType=2&osVersion=10&sign=f2d04df3510670528a067ae52ded2c49&timestamp=1648180651319&token=Mk_G1OHDSNax7QIHN8j_BmFarMeZA_ORR7yKj831WPYkyDvKw0AMAOG7TC2MtCtrbbV__98hD-fRbCAhlfHdg_E0A99KV5I66KAI3UgTeiGLqo5Cr6QK3Y-NJH__CCcShDNZJvPmc4tWprm6C9cdQ1jIlc_r-74sZGhR24QbaeGtVm9RhTuJmelRIDx2QXiSuv0CAAD__w%3D%3D&ttid=-1&userId=182784&userRole=2',\n    // }).then((res) => {\n    //   console.log(res)\n    //   // alert(res)\n    // }).catch((err) => {\n    //   console.log(err)\n    // });\n    // }\n\n    // function reqListener() {\n    //   console.log('asasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsd', this\n    //     .responseText);\n    // }\n    // var oReq = new XMLHttpRequest();\n    // oReq.addEventListener(\"load\", reqListener);\n    // oReq.open(\"post\", \"https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7\");\n    // oReq.setRequestHeader('Content-Type', 'text/html; charset=utf-8')\n    // oReq.setRequestHeader('Access-Control-Allow-Origin', 'http://localhost:3000/playground')\n    // oReq.setRequestHeader('Access-Control-Allow-Credentials', 'true')\n    // oReq.send(JSON.stringify({\n    //   a: 1\n    // }));\n\n    // axios({\n    //   url: 'https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7',\n    //   method: \"POST\",\n    //   data: {\n    //     firstName: \"Fred\",\n    //     lastName: \"Flintstone\"\n    //   }\n    // }).then((res) => {\n    //   console.log(res)\n    // }).catch((err) => {\n    //   console.log(err)\n    // });\n  }\n  eventNode.addEventListener('touchstart', (e) => {\n    console.log(e)\n    console.log('----------touchstart--------')\n  }, false)\n  eventNode.addEventListener('touchmove', () => {\n    console.log('----------touchmove--------')\n  }, false)\n  eventNode.addEventListener('touchend', () => {\n    console.log('----------touchend--------')\n  }, false)\n  $('.asdasd').on('click', function (event) {\n    console.log('zepto', event);\n  });\n  setTimeout(() => {\n    document.body.appendChild(document.createElement('textarea'))\n  }, 5000)\n  // setTimeout(() => {\n  //   var a = {\n  //     b: 1,\n  //     c: 3,\n  //   }\n  //   console.log(a);\n\n  // function reqListener() {\n  //   console.log('asasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsd', this\n  //     .responseText);\n  // }\n  // var oReq = new XMLHttpRequest();\n  // oReq.addEventListener(\"load\", reqListener);\n  // oReq.open(\"post\", \"https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7\");\n  // oReq.setRequestHeader('Content-Type', 'text/html; charset=utf-8')\n  // oReq.send(JSON.stringify({\n  //   a: 1\n  // }));\n  // }, 5000);\n</script>\n\n</html>"
  },
  {
    "path": "Web/packages/web/rollup.config.js",
    "content": "import vuePlugin from 'rollup-plugin-vue'\nimport postcssPlugin from 'rollup-plugin-postcss'\nimport resolve from 'rollup-plugin-node-resolve'\nimport commonjs from 'rollup-plugin-commonjs'\nimport replace from 'rollup-plugin-replace'\n\nimport {\n  terser\n} from 'rollup-plugin-terser'\nimport rAlias from '@rollup/plugin-alias'\nconst path = require('path')\n\nconst extendPlugins = []\nif (process.env.NODE_ENV === 'production') {\n  extendPlugins.push(terser())\n}\n\nexport default {\n  input: 'src/index.js',\n  output: {\n    name: 'dokit',\n    file: 'dist/dokit.js',\n    globals: {\n      vue: 'Vue',\n    },\n    format: 'iife'\n  },\n  external: [\"vue\"],\n  plugins: [\n    rAlias({\n      entries: {\n        \"@common\": path.join(__dirname, './src/common')\n      }\n    }),\n    vuePlugin(),\n    replace({\n      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),\n      'process.env.VUE_ENV': JSON.stringify('browser')\n    }),\n    postcssPlugin(),\n    resolve({\n      extensions: ['.vue']\n    }),\n    commonjs(),\n    ...extendPlugins\n  ]\n}"
  },
  {
    "path": "Web/packages/web/src/assets/CssStore.js",
    "content": "function formatStyle(style) {\n  const ret = {}\n  for (let i = 0, len = style.length; i < len; i++) {\n    const name = style[i]\n    if (style[name] === 'initial') continue\n    ret[name] = style[name]\n  }\n  return ret\n}\n\nconst elProto = Element.prototype\n\nlet matchesSel = function () {\n  return false\n}\n\nif (elProto.webkitMatchesSelector) {\n  matchesSel = (el, selText) => el.webkitMatchesSelector(selText)\n} else if (elProto.mozMatchesSelector) {\n  matchesSel = (el, selText) => el.mozMatchesSelector(selText)\n}\n\nexport default class CssStore {\n  constructor(el) {\n    this._el = el\n  }\n  getComputedStyle() {\n    const computedStyle = window.getComputedStyle(this._el)\n\n    return formatStyle(computedStyle)\n  }\n  getMatchedCSSRules() {\n    const ret = [];\n    [].slice.call(document.styleSheets).forEach(styleSheet => {\n      try {\n        // Started with version 64, Chrome does not allow cross origin script to access this property.\n        if (!styleSheet.cssRules) return\n      } catch (e) {\n        return\n      }\n      for (let key in styleSheet.cssRules) {\n        let matchesEl = false\n        let cssRule = styleSheet.cssRules[key]\n        // Mobile safari will throw DOM Exception 12 error, need to try catch it.\n        try {\n          matchesEl = this._elMatchesSel(cssRule.selectorText)\n          /* eslint-disable no-empty */\n        } catch (e) {}\n\n        if (!matchesEl) return\n\n        ret.push({\n          selectorText: cssRule.selectorText,\n          style: formatStyle(cssRule.style),\n        })\n      }\n    })\n\n    return ret\n  }\n  _elMatchesSel(selText) {\n    return matchesSel(this._el, selText)\n  }\n}"
  },
  {
    "path": "Web/packages/web/src/assets/deepClone.js",
    "content": "const mapTag = '[object Map]';\nconst setTag = '[object Set]';\nconst arrayTag = '[object Array]';\nconst objectTag = '[object Object]';\nconst argsTag = '[object Arguments]';\n\nconst boolTag = '[object Boolean]';\nconst dateTag = '[object Date]';\nconst numberTag = '[object Number]';\nconst stringTag = '[object String]';\nconst symbolTag = '[object Symbol]';\nconst errorTag = '[object Error]';\nconst regexpTag = '[object RegExp]';\nconst funcTag = '[object Function]';\n\nconst deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];\n\n\nfunction forEach(array, iteratee) {\n    let index = -1;\n    const length = array.length;\n    while (++index < length) {\n        iteratee(array[index], index);\n    }\n    return array;\n}\n\nfunction isObject(target) {\n    const type = typeof target;\n    return target !== null && (type === 'object' || type === 'function');\n}\n\nfunction getType(target) {\n    return Object.prototype.toString.call(target);\n}\n\nfunction cloneSymbol(targe) {\n    return Object(Symbol.prototype.valueOf.call(targe));\n}\n\nfunction cloneReg(targe) {\n    const reFlags = /\\w*$/;\n    const result = new targe.constructor(targe.source, reFlags.exec(targe));\n    result.lastIndex = targe.lastIndex;\n    return result;\n}\n\nfunction cloneFunction(func) {\n    const bodyReg = /(?<={)(.|\\n)+(?=})/m;\n    const paramReg = /(?<=\\().+(?=\\)\\s+{)/;\n    const funcString = func.toString();\n    if (func.prototype) {\n        const param = paramReg.exec(funcString);\n        const body = bodyReg.exec(funcString);\n        if (body) {\n            if (param) {\n                const paramArr = param[0].split(',');\n                return new Function(...paramArr, body[0]);\n            } else {\n                return new Function(body[0]);\n            }\n        } else {\n            return null;\n        }\n    } else {\n        // \n        return eval(funcString);\n    }\n}\n\nfunction cloneOtherType(targe, type) {\n    const Ctor = targe.constructor;\n    switch (type) {\n        case boolTag:\n        case numberTag:\n        case stringTag:\n        case errorTag:\n        case dateTag:\n            return new Ctor(targe);\n        case regexpTag:\n            return cloneReg(targe);\n        case symbolTag:\n            return cloneSymbol(targe);\n        case funcTag:\n            return cloneFunction(targe);\n        default:\n            return null;\n    }\n}\n\nfunction allKey(obj, newobj) {\n    let ret = [];\n    for (const key in obj) {\n        ret.push(key);\n        obj.propertyIsEnumerable(key)||defineReactive(newobj, key, obj[key])\n    };\n    function defineReactive(data, key, value) {\n        Object.defineProperty(data, key, {\n            enumerable:true,\n            get() {\n                return value\n            },\n            set(newValue) {\n                value = newValue\n            }\n        })\n    }\n    return ret\n}\n\nexport const clone = (target, map = new WeakMap()) => {\n    // 克隆原始类型\n    if (!isObject(target)) {\n        return target;\n    }\n\n    // 初始化\n    const type = getType(target);\n    let cloneTarget;\n    if (deepTag.includes(type) || Object.prototype.isPrototypeOf(target) || Array.prototype.isPrototypeOf(target)) {\n        cloneTarget = Object.create(target.constructor.prototype);\n    } else {\n        return cloneOtherType(target, type);\n    }\n\n    // 防止循环引用\n    if (map.get(target)) {\n        return map.get(target);\n    }\n    map.set(target, cloneTarget);\n\n    // 克隆set\n    if (type === setTag) {\n        target.forEach(value => {\n            cloneTarget.add(clone(value, map));\n        });\n        return cloneTarget;\n    }\n\n    // 克隆map\n    if (type === mapTag) {\n        target.forEach((value, key) => {\n            cloneTarget.set(key, clone(value, map));\n        });\n        return cloneTarget;\n    }\n\n    // 克隆对象和数组\n    const keys = Array.prototype.isPrototypeOf(cloneTarget) ? undefined : allKey(target, cloneTarget);\n    forEach(keys || target, (value, key) => {\n        if (keys) {\n            key = value;\n        }\n        if(Array.prototype.isPrototypeOf(cloneTarget)){\n            cloneTarget.splice(key,1,clone(target[key], map));\n        }else{\n            cloneTarget[key] = clone(target[key], map);\n        }\n    });\n\n    return cloneTarget;\n}"
  },
  {
    "path": "Web/packages/web/src/assets/util.js",
    "content": "import {\n  Request,\n  EventEmitter\n} from \"@dokit/web-utils\";\nimport { ConsoleLogMap } from \"../plugins/console/js/console\";\n\n\nexport const request = new Request()\n\nexport const getDataType = function (arg) {\n  if (arg === null) {\n    return 'Null'\n  }\n  if (arg === undefined) {\n    return 'Undefined'\n  }\n  return arg.constructor && arg.constructor.name || 'Object'\n}\n\nconst MAX_DISPLAY_PROPERTY_NUM = 5\n\nexport const getDataStructureStr = function (arg, isFirstLevel) {\n  let dataType = getDataType(arg)\n  let str = ''\n  switch (dataType) {\n    case 'Number':\n    case 'String':\n    case 'Boolean':\n    case 'RegExp':\n    case 'Symbol':\n    case 'Function':\n      str = arg.toString()\n      break;\n\n    case 'Null':\n    case 'Undefined':\n      str = arg + ''\n      break;\n    case 'Array':\n      break;\n    case 'Object':\n      try {\n        str += '{'\n        if (isFirstLevel) {\n          let propertyNames = Object.getOwnPropertyNames(arg)\n          let propertyNameStrs = propertyNames.map(key =>\n            str += `${key}: ${getDataStructureStr(arg[key], false)}`\n          )\n          propertyNameStrs.join(',')\n          if (propertyNameStrs.length > MAX_DISPLAY_PROPERTY_NUM) {\n            str += ',...'\n          }\n        } else {\n          str += '...'\n        }\n        str += '}'\n      } catch (error) {\n        console.log(error)\n      }\n      break;\n    default:\n      break;\n  }\n  return str\n}\n\nexport const guid = function () {\n  function S4() {\n    return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)\n  }\n  return (S4() + S4() + \"-\" + S4() + \"-\" + S4() + \"-\" + S4() + \"-\" + S4() + S4() + S4())\n}\n\nexport const $bus = new EventEmitter()\n\nexport const uuid = () => {\n  var temp_url = URL.createObjectURL(new Blob());\n  var uuid = temp_url.toString(); // blob:https://xxx.com/b250d159-e1b6-4a87-9002-885d90033be3\n  URL.revokeObjectURL(temp_url);\n  return uuid.substr(uuid.lastIndexOf(\"/\") + 1);\n}\n\nexport const debounce = (fn, wait, time) => {\n  var previous = null\n  var timer = null\n  return function (...args) {\n    var now = +new Date()\n    var that = this\n    if (!previous) previous = now\n    if (now - previous > time) {\n      clearTimeout(timer)\n      fn.apply(that, args)\n      previous = now\n    } else {\n      clearTimeout(timer)\n      timer = setTimeout(function () {\n        fn.apply(that, args)\n      }, wait)\n    }\n  }\n}\n\nexport function throttle(fn, wait) {\n  let prev = 0\n  return (...args) => {\n    let now = +new Date()\n    if (now - prev > wait) {\n      fn.apply(this, args)\n      prev = now\n    }\n  }\n}\nexport function getDeviceType() {\n  let u = navigator.userAgent.toLowerCase();\n  let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; //判断是否是 android终端\n  let isIOS = !!u.match(/\\(i[^;]+;( U;)? CPU.+Mac OS X/); //判断是否是 iOS终端\n  if (isAndroid) {\n    return 'android';\n  } else if (isIOS) {\n    return 'ios';\n  } else {\n    return 'pc';\n  }\n}\nexport function getOsVersion() {\n  let version, ua = navigator.userAgent.toLowerCase();\n  if (getDeviceType() === 'android') {\n    version = /Android(.+?);/.exec(ua)\n    return version[1].trim()\n  } else if (getDeviceType() === 'ios') {\n    if (ua.indexOf(\"like mac os x\") > 0) {\n      let reg = /os [\\d._]*/gi;\n      let verinfo = ua.match(reg);\n      version = (verinfo + \"\").replace(/[^0-9|_.]/ig, \"\").replace(/_/ig, \".\");\n      return version\n    }\n  }\n}"
  },
  {
    "path": "Web/packages/web/src/common/Card.vue",
    "content": "<template>\n  <div class=\"dokit-common-card\">\n    <div class=\"dokit-common-card-header\">\n      <div class=\"dokit-common-card-header__title\">{{ title }}</div>\n      <div class=\"dokit-common-card-header__extra-info\">\n        <slot name=\"extra\"></slot>\n      </div>\n    </div>\n    <div class=\"dokit-common-card-body\">\n      <slot name=\"body\"></slot>\n    </div>\n  </div>\n</template>\n<script>\nexport default {\n  props: {\n    title: {\n      default: \"\"\n    }\n  }\n};\n</script>\n<style lang=\"less\">\n.dokit-common-card {\n  border-radius: 5px;\n  overflow: hidden;\n  border: 1px solid #d6e4ef;\n\n}\n\n.dokit-common-card-header {\n  border-bottom: 1px solid #d6e4ef;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  .dokit-common-card-header__title {\n    padding: 8px;\n    font-size: 18px;\n    color:#2c405a;\n  }\n  \n}\n\n.dokit-common-card-body {\n  padding: 8px;\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/common/info-card.vue",
    "content": "<template>\n  <div class=\"dokit-info-card\">\n    <div class=\"dokit-info-card-header\">\n      <div class=\"dokit-info-card-header__title\">{{ title }}</div>\n      <div class=\"dokit-info-card-header__opt\">\n        <div class=\"dokit-filter-box\" :class=\"keyword ? 'dokit-filter-box-actived' : ''\">\n          <span class=\"dokit-filter-text\">{{ keyword }}</span>\n          <span class=\"dokit-filter dokit-opt-icon\" @click=\"openPrompt\"></span>\n        </div>\n        <span class=\"dokit-clear-all dokit-opt-icon\" @click=\"clearAll\"></span>\n        <span class=\"dokit-refresh dokit-opt-icon\" @click=\"refresh\"></span>\n      </div>\n    </div>\n    <div class=\"dokit-info-card-body\">\n      <table v-show=\"Object.keys(filteredMap).length\">\n        <tbody>\n          <tr class=\"\" v-for=\"(value, key) in filteredMap\" :key=\"key\">\n            <td class=\"dokit-info-key\">{{ key }}</td>\n            <td class=\"dokit-info-value\">{{ value }}</td>\n            <td class=\"dokit-info-opt\">\n              <span class=\"dokit-info-delete\" @click=\"removeItem(key)\"></span>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n      <div class=\"dokit-empty\" v-show=\"Object.keys(filteredMap).length === 0\">\n        <span>empty</span>\n      </div>\n    </div>\n  </div>\n</template>\n<script>\nexport default {\n  props: {\n    title: {\n      default: \"\",\n    },\n    infoMap: {\n      default: {},\n    },\n  },\n  data() {\n    return {\n      keyword: \"\",\n    };\n  },\n  computed: {\n    // 过滤地图\n    filteredMap() {\n      if (this.keyword) {\n        let map = Object.create({})\n        for (const key in this.infoMap) {\n          if (Object.hasOwnProperty.call(this.infoMap, key)) {\n            if(this.infoMap[key].indexOf(this.keyword) > -1 || key.indexOf(this.keyword) > -1) {\n              map[key] = this.infoMap[key]\n            }\n          }\n        }\n        return map\n      } else {\n        return this.infoMap\n      }\n      \n    }\n  },\n  methods: {\n    /**\n     * \n     * @param {关键字} key \n     */\n    removeItem(key) {\n      this.$emit(\"removeItem\", key);\n    },\n    openPrompt() {\n      this.keyword = window.prompt(\n        \"请输入过滤关键词\",\n        this.keyword ? this.keyword : \"\"\n      );\n    },\n    clearAll() {\n      if (window.confirm(`将清空所有${this.title}数据，是否确认清空？`)) {\n        this.$emit(\"clear\");\n      }\n    },\n    /**\n     * 刷新\n     */\n    refresh() {\n      this.$emit(\"refresh\");\n    }\n  },\n};\n</script>\n<style lang=\"less\">\n.dokit-info-card {\n  border-radius: 5px;\n  background-color: #d9e1e8;\n  overflow: hidden;\n}\n\n.dokit-info-card-header {\n  border-bottom: 1px solid #eeeeee;\n  background-color: #2b90d9;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  color: white;\n\n  .dokit-info-card-header__title {\n    padding: 5px;\n    font-size: 14px;\n    font-weight: bold;\n    font-style: italic;\n  }\n  .dokit-info-card-header__opt {\n    display: flex;\n    .dokit-opt-icon {\n      display: inline-block;\n      width: 15px;\n      height: 15px;\n      background-size: 15px;\n      background-repeat: no-repeat;\n      margin: 0 10px;\n    }\n    .dokit-refresh {\n      background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAr5QTFRFAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////0dICkAAAAOl0Uk5TAAcuVXuiwcjQ2N/n7/b+8eXLv7KlmYx/USMgYJO54PXHaz4QndnhqghEjsqpWRR9zGUYZ7azVDbwxGY9oNZ4GajoiStMr/qSHCqeaQV67kBWFj+mYxut5OKwKVtih/vVeXBcU0lPX25+jay740sm6m1HInSj03b06xO+gjsENHehrl4aF8bJKJf3Eo9DJEGGJw9siNR1V7QB5pWc3YrFgIFKalKaEU0vcUUwYSXtDYv8uGSkzQs3/TjOaCG32tvRDpuULQmQXU58w28Ctbz5MQOrg0gzDLofmKfPvVoVkezz3LHClp85BtIRZQRLAAAAAWJLR0QB/wIt3gAAAAlwSFlzAAAASAAAAEgARslrPgAACSlJREFUeNrtnflfFkUcx9cHUUARJBBT8CCVy1S8RQ1FQDEQQREQDwQPvPDIC1BDDTVPIswrJRPkyLSDrIAOLU3TDq0su7W09r/oeRSV/c7s88zszuzs69V+fn2e7/F+9tlr5jvfkSRLlixZsmTJkiVLlixZsmSJo9rY3Nq6t2vv4enVoaN3Jx9fz85+T/gHdAns+qTozMjVrXtQcA9ZVT17BfQOeUp0kq4h+vSVSdQv1C1MdLKqCo+IJIJoUaf+Tw8QnTJGtoGDaChaWEKjuolOXKHBQ4bSU7SwDBsuOvtHGhEwUivGfY2KbiMawaHRY3RR3NfYZ2JEY4SP04/h0PjYCSIx4uLZYDiUMHGSKIzEyewwHPJ6VghGUvIUthx2DU0xnmMq1c2PWF1SnQUdnJZKmh+hbNO4YNjVd7pazPQZGfbPM7NmMuTI7sCLw64Z+Jt9yqMvzGLGMZsooTlz42Nz5kXn5s1PWbBwUf7iPkuWLiMy9FyOidm91RdGs8EoGOYylRX+K1etxtkOCH9uzVrXKOsQw27rW328oZAFR56XiwNRVLzRuYdNm59v54KkBKa6WPFxBAOOQOcZbNm6jcjNC7klG5z5GRSi/H6p4tPO+jmcnh7bc3ZQuAqL29lT3deLirvjLvCp7v/WbvXI3jv3ULvbW+zkf1rc6ov7wGdpOjnU7x6++8s0eXypXP3G+jI3ENWYFQd03HFfOajmdjcfkENqz1aHFyXp+4EWqZ33R7iArFeJdlT/UNWE/iq+gziAvIoPFdlVN4ZD0Rl498eYgxzHB6pk9bq9twQf4DXGIFuwUTxOMMJw6HVvbIyTTEF2YmNUsR1gC8FfFcsYgmCfSzZEMcWwqxr77pxQyAzkFPYsr2HNYVdtJ0wkP1Yg6WNx3vmMRNXhYsUyAlmD4+A1OGjDXeYPMAGpxR9tXqr3xcR7gwFIWoKhHJJ0+k00oI9+kDO9ULcVPDkkKUbtYUgXyFnDOew/3lvsQVIwHPwnAQ6xB3kb5TjNnUOSElmDrERcrLAZwCFJJ9mCrEYvhe8YwiFJbZmCVCEeqgzikKR3GYLEIQ48DJxQbmAHgs5HsXz/gFqQU+KnkDcrkDrEvpIjR4RMIToQZJ7T4z1+HIdpOOhAQhDzXH4cx6k46ECQ0YAZ/DjW0XFQgZyDxu9/wA/kQ44gyD0klh9HUgI/kBhYX7KMY5VFIiUHDUgjtM3hx0EP0kTuuxmYfsSzeo/2r/UxuesaaDuPIwf1yd6H3HM+MP2E79vUPDoQiusnfGJr5MqhNrasIopXiXBgmvEpZxDpPDlHKIVb+AR3gTeHJM0ixOhXTuO1FFjTT9jSa8DGWZUudfKzz2l8TgIc2w3g4CI3AJIjOiGtughAaAoaTKUKJUd70floFTxFJopOSKsuAZBs0QlpVRAA2Ss6Ia0KVnIsE52PZoH1ODSPBKZSGPhnrdPvUoxsAOQL0QlpFbyvl4lOSKsuAxDR+WiWu5LDU3Q+mnVFCZIlOh/NAqXSHAfm6BQYWiF7rAkkN8hUgjwjGuCBdj2crWkgXvkDygy+FI1wXztaZUQ45nkVXLSiRTPcV+uVs0VkJtUAhOOsCLkqFSmRnSdNACRPNIRD1xQpfUVkUw9A5ouGkJDB4a83kRjB8hMBK9Fc5kQ0rfCNCY/ICZBTPYnRahOeI8NBTkTzI4UmvGrBUYRdJEabgBHz8l4NigY5fUtkBSrxzHBnB/Mn68msQG2TGZ61cpQp+ZJZgRpiMzz9HlOmNJbMCtSVxoumsCtLmVIvMqvrSivuRaUEmqtM6TqZ1Q2lVYJoCrvmKFO6QWaVq+Xuw1XwHk04GL0ZmK0SzSF9BzL6nszsB2C2UjSHtAhkRFoAB5adU9QZcBJYkZpJardUaSd+YGuFMqGbpHZwfkRYw5IWbQT5EM+gwWdNN8EgxSCfhaSGcA6RxYJ+PfID+fxIbAlmdUvFcqSD5SR9yU3NNc++EGRDcRU1V+UDrGwnPkVMVosyEyRDcYqIqQ5SUzLIJZLGWEC9lqrgUmSqa2g4MM44I4wDpkL574A1jeKG5H8CmQyiM8/XZ85OO+BymIF09kjdr6hDsh8mQrvqrtkchyQVrrsbSuuh0RyH5ABMYwithxgfMxySJPDUJ48cTO0DWT/SWwBIOUwigN4HsqKnR7rhHJOQBsgjNHhB1ljt1uBEn+BTuDxGixd01Zverla0SkMy0NZGD1mHOMxgEKRf1S1tfuqQH4Rdh0QSTUfih2v0hKzVHW/MYvYHqhkPw2/R6gpdPR1sHMehm0j05Zqdob18zhoG0gWJPVm7s8SOiDej5ngvIZGn6OnGmoy462DMaYKeIHKyHn9JaMurn43ow12NrqaO1Nexbyri0JDhulA0rN5xW0yPyV+4cxxFg07T69OG6VFMOBepWZiuCQxOzWzUK9feD7Ba7oF+ZeAX11B2MUeOJZh4s5l4xnVcPs+NowgTjdHTahmul23/ai4Y2zDtvGSvAkbe8zDO5VE8ugLWeOBCsSt9M6hPoxSFbTNLUUTuUoZ0zvwNv4KazYn+UAb0Mj2B/Vsxn8BU6y7Lyn9SJT6AP2MO1X6/fmzKgpeX4t3zmCtTazrI4KCk/q7i+w8OHOo9sXUflFxPFc9/8uCQnHQpH7NPh9e8a2pub3PikKRbaiHlOyc1uswtUvU5ihuH007+f02nn2c8E9VL3SHHLliS870VMoLo5in3XMhw4u1vrhz4l4XHIt8mYmaO801+WD6X4JXXw2kCcuSRha42ojudPbG9cydeRiyRKDsou9Ltu+Wr8LWpZQvWhbre4GYYq+d2FyLboyehIj62duvyewWFUmHBvbittbF3PIkM5f3GYEicd006zOL9nFT89rGS/Y0c8Ze47SwWaXz95D/JHfXnDTQlWedOJtqUeJcxx2SWu59R6VQWQ4z4OFEYDtUx2qFSHlcnEsOhkBL9FHJJiP5E9OtclY8uipFV50QjPFRMY7NmjOZG4Tu4KlST36CBoiGfx3ilXlHuPS2XRmgtAOAv4t3AKy66iV7MQQDjdH92uUdw0CXTQzxSmM3tsvuVtZmPr2c+mf9ecW/rZjPvtuzOdbW6qT6lvqn6quhELFmyZMmSJUuWLFmyZMnS/0X/AVYjDmTSExexAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTA1LTI4VDE3OjI4OjM4KzA4OjAwUQL/cAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wNS0yOFQxNzoyODozOCswODowMCBfR8wAAAAASUVORK5CYII=');\n    }\n    .dokit-clear-all {\n      background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAvRQTFRFAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////ScVxIwAAAPt0Uk5TAB41BcjfTK71YpV5fJCmE0m9Ki/y00AW2epXv26EjJsEc7IdWifoDdC3+p2+UOQUOs5r8yS4QVEOozj2ZI3ef3fHmWG04BfPCjbKMXvmJSBK/T8Gn2OJ7HXVj14HqkjcIcPG1DvaGxixVPBZhXDi7hHLoELYK7MpwkSczQ+t45d4akaBbAE3U5M0wU6pwGiS1+08fvwL/kuH0T4Vu1ilcYuI4TL3TVz4LvR6JpHFp4aWKG2v62fJUgOk8dLbXVuojpqCtQwQuWWU1hrEclbpdoOsArAjQxwImBkwzN2AqzNpdCLlnkcftqJFYPtVCe9fuj1P+aEtLLw5Zn11U/nBAAAAAWJLR0QB/wIt3gAAAAlwSFlzAAAASAAAAEgARslrPgAAC9lJREFUeNrtXXt8FsUV/dIkvISYkASSRgUDJKJSIwpKQoMICoo8VCoEEWxTCQ+FRKBQEQhqjSJVoWCsQaTF8ihFa6GligqoVGpq8FERilQULPXRWm2rbfeffjEN7J57d3dmdnbnw+b8mZ1755789jFz7z3zxWIt+H9E0ldMR6AHySmprUzHoAWtLatNW9NBaEA7qxEnmQ4jMNpbTehgOpCAaGs1I+1k07EEQbp1HBkdTUejjswsGxEru5PpeFTROcdyIPerpiNSRJ6FOMV0SEpIsyhONR2UAk6zOHTpajouWZxu8cjvZjoyOXS33JByQq0hO1nuOJHWkD0KPIicQGvIwjMsb5woa8ieEPeZhMmJsYY8C6I+u9fXCJO0QtNR+uNUiDnnnFjRuYRJRg/TcfqhN0R8Xmb8j+f3IUyy+5qO1BsXYMDpX/z5wn6ESWKvIYtLINz+zVe+Tl9eCbyGTCqFWAccv3YRZZKwa8jkgRDpxfarg0oIky6mI3ZBa4hzsPNyq0sIk/xLTcfMoR1EOWQoDLjscsIkZZjpqCmugBiHjyBDRg4mTFJHmY4b0RYivPIqZtDVowmTNt8wHbkT6Rhgd37cNfTllVBryMwxEN1Yt5HM3jGB1pCdyyC2ce5jr80iTNLGmybQjAyI7DqvwRMmEiYZ15tm0ARM/XzTe/iwbxEm5d82zaERmPq5YZKPQcVkwiR3imkW9PGdKpAo6UIfeeNryGkYUS8RqxspE8NryE64Je8tZncTZWJ0DdljOkRzo6hl9xmESX6lMR6FVRDLzeK2M2cRJimzTRH5DkTSU8Z4zlzCJPW7ZnjcAnHMq5Qyv3U+YVJyrQkemPpZIH1nLKSPfHX0PBZBCGfeJu/jdsok8jUkSf0MUvFyB2WS9r1Iedw5Fea/Qs1PTS5hkuG3xtGJJHx53qXqqaicMCm/OzIeyYth7uvUfY3MI0xy74mKyBKY+ftB9kbjlxAm1r3R8LgPpr3/+mD+7qJMbo+CB6Z+li4L6vEHlEkEa0hM/VgaMuv9lxMm+StC5jHhAZjxJh1ea4cTJinnh8ojE2d8UI/fYT8kTFIfCpFH3RCY7SxdnitWEiYlD4dHBIu2g4O7PIZV9JF/JCweN8NEl2utDIyjTFaHw+NHMM2P1+j1P5YyWRUGj0dgkkd/onuGtesIk4w67Tym4Rz9g/tEzJxOmJSv1zxHpw0ww0/184hv5ecRJrkTtM6wEbPPPwuDR3wr35MwsXS2FBVuAuePhcMjRl+NlvK2jQOmPM4OsTPmccqknS7fP8cn8InweMRiv6BMNidr8TwA3G4JuSumZgNhkpKkwe8v0WvoPTFFqYRJanFgr73QZwTVjJG/IkxKtgb0eSembCKpZYynhS3RooULkrLBXVSVDCajKly2YNAV9wmTfx0REW4rP1rd25PgamCEfeH9KZM+Fyr6wqJtaXRpwDhqnyJMtql9wXCHsGFUlDziW/mnCZPcmQp+nkEvSjn3IKighS3rWWkvt+E+56KoecSxnTIZK+liPbbv7TDAg93Kj5NyUIcf1yeN8GC38ttl7OmnNaIkOcFaymRuhbD1TmptPWeIycwxJJSJohmc5ywOtxhiMiebhNKmVsiyt8Xj+RfMMNn1GxrLiwJ2NZYbhowQMA8DzFZ+gK/RshmuRKyCMJPkXiBb+d2+JiN+a3khxCS5B16qhzD8K7CFeZY3fmeAR1dsvs/r7GvzGJhQwdfq6IngNqvMv/WlA1JnBF+rom20oG0rAnmck8Akp44TfOVFq5kgX4OXfU0GgcXUxi8oI/iapTtJ7oUpOPsdviajsFDc1E7BCL6y9CbJvfBQA8zt30+wBh+HRc1XGMFXVLrbHljxXehvswdMXjl+iRF8aUySe6AS8zivvuZvBHmLnfZrjOBLW5LcC5jHEVrBb3WYvO68yAi+Noevu92h9JZxqOoHYh2SE3z9PmQe1fiOeUPIbL3dhuZdGMHX9KJQeeDXwJomZrfXblNDrzOCr+VBk+ReqMWvgWgh4AW7EVt+ZgRfwZLkXrh7AUwlXgiwW+1hRzCCryBJci8k4TN5sbjtUpuZSwcTI/gaLfBml8ebmMeRKSTac637XMYwgq8++0Mggsqf+pckjO1JuSVugxjB17ZM7TxQ91P6Bxlre9/BStdRjOBri0JvvCdOwbej3AT27q8DHuMYwdczWnk8jO4lc/D2nt63vAYygi/ZJLkX+paAc9lmuoM22xmeIxnB1w5tPGa/Ba4fl/XguDO9hzKCr+2qFT5AXT04lk/VOmpUu7zHMoKvuXqadf8IbifLN9+3stuP9BnMCL6e1qG7xeb7KoVK8jK7A98zGRjBV5vga0hsvi94W8HJbLsHgfop055wKCCPF9GhUpZjkt2DyD+XEXz5J8m9cA+6U9P0DbW7EDpaghF87QvQ5VGMPQKnKzp61ObjHSELRvCVL7O8c+BdfBNeo+rpsM2J4DaGEXxVSS3wjmNFHjg6osojZi/WiWZ7GMHX4feUZsdG/55Dldw0wi5oE25M5wRfKs8otvCU/UmZh+Mwo6PiZozgS+wJswN30VlBDqg8YnM0UMKO6RJbLTn1s+hArADtAnuyepaMISP4+vOtMg7eR/MPgvA4diR3I3KlLBnB10qJWnbmFjDeI27Lwf59+1Duw8YIvsqENYoV2wLemAjHjXqOnC0j+JpRI2b6GnY29AvII1Zr9ya9u2AEX4uEDLGvYXHgVpEiuzv51x/TJXZQwOxBsCl9NyiPWEe7P4VcKNMltrCrnxFJ/QTvg4/td3hU0Esygq/WPocGfIQGOlRof3G6fF7yeY+xgq9NnnvNZThcz8l0f3U6nSd/fgwj+Cr12OWNOAyDq7XwiOFJnofFal12MIKvdR+7Dj4AQ3WdKELaNa6UbxjmusTc8pB4cM98TTy4hmGF3SYj+DqNHYg9oFVS6zNPdKAxrJb3wgi++jEJP8xdFOiU41czMciLzhjB19824qB7cUiRRh6x2AeUyUr582MYwdcZnziHfIoDhE5+k8CEBhJDtrzkmxF8FTjO/7vs73BZpAFWDpl0Rb5llLQXH8FXN2wJaa+dRyx2KdMwrNDWxGRUj/VakTObd8r7F8E/aAwKwm9mK7/vfy8ObBuVOvlNBkyPg4L0mxF8bd7beGE3/LWsMiwi5GiEOJbIl6QYwdemNbR2kBXm8f7TPiQxLJb/YDGCr4np5OCewAcQeaLvAhJDTpG0F0bwdR7+IcwWo0ZsrCIxPHWBvBtm/ebEoZB5xPdZ+XRWBYXSOG8e0sVnFTASJZF0AmCsF4+IVGiMSGmfvJe17jwWR8ODni0Zx+v+mgcEI/hqQqp8WkAVW5kjGP4p7YURfDVi+ZzIeMSXqbTV7BL5rTwn+LKsq6T9BMH+ehLAOsHGVTuYrfxHkfKI41Uag8Lpc2Qrb0DltJsykX79E8GXkR8kaE+ZHHlTygMRfGlL/cihLWVyVKrsitusqqiVWs14fylhcq5E5eEg2DbIf4x04YkywmS4cOmVfFc/M8YjFluRQZg8ILiVJ/Jfwz8vxhTXqkXsPsHb8mMRqzDBqMZ2+FttxJsyjNSPJJit/KpKH5tdeEuGlPqRwxTK5HOf1AHp+jHNoQnFtMeh3FPljrdjWWQHKflgEhXvjvH4VRdUN2aFIdJQxFF6e7l2BpDkfpQyX18coUxcFLTk57oCdS/pB9Mnu5C79d/GD8gh05EjmKzufNpduhc/IJGkfuTwHu1xqMfu0mT8gJg6gMgTHal49wYQEOEHJLLUjxwKUwiTLIdCCdtGU682HbIbqFzBXpV/By4tj/InOSTBVOWPdYGT7KKG7qXwwEheRze1v72Bf4889SOHlymTo40NcLPxA6KqO4gM/6LFtQPFsZPxA5JAv0Xphm45hMn0T/ED0tp0lEL4nDDBpoYq0yEKgp484ESDXCrPIF7xJhLuj4loxb+9eCT4L8s7ke7Ow3jqRw6fNbjw+I/pyGRBGkabEG7qZ0Ao2Mzw6HNQDnIzxqwvCVqIJBpaiCQaWogkGlqIJBpaiCQavjRE/gvAWs6hud5ybQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wNS0yOFQxNzoyODozOCswODowMFEC/3AAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDUtMjhUMTc6Mjg6MzgrMDg6MDAgX0fMAAAAAElFTkSuQmCC');\n    }\n    .dokit-filter-box {\n      padding-left: 10px;\n      display: inline-flex;\n      justify-content: space-between;\n      align-items: center;\n      .dokit-filter-text {\n        font-size: 12px;\n      }\n    }\n    .dokit-filter-box-actived {\n      border-radius: 10px;\n      border: 1px solid #d9e1e8;\n    }\n    .dokit-filter {\n      background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAspQTFRFAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////17LOWQAAAO10Uk5TADFiiK2/n3ZVFBCE99pmUta5KkH11DDhKbWsWTPFjlHoC387avCa8sDVtqYv4JfQozrroEVUXZ1P6hsS+plai6SWZSvzk2+GaJB6sowPAYmPPgWwMgizgiUMt68Yu3wJuvwTvnnEF8J1Hs7pGnLY38k04tIhzWtJUyxe22lbc4c3WH7mbflhQu1NRvFKp0jvArH4Qw0uPyLlPBU4Ld41X+zKY1fRJ24kbHHLIMcdgHgZhcEWgweKEfscRJIm55Wp3R+i9gabmKuUA5GNCg57vWfIzM9A0zac1+Ou5GT0XEzGTv2l3MO4YHf+qks5NtOChAAAAAFiS0dEAf8CLd4AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAdRSURBVHja7Z33X1ZVHMcPCqJZmiUOcqC4NffARClXJimiaWI4ADUynGWuTI1KwSxHqTkyK1emqallQTmyaUvNpqntrOd/SJDwuZ87zz33jIfXef98zvd83nDvc8+5k5CrRFWqHB2jMFViq1Yj7lxX/fqQ8txQw9Wj5o2yQ3qj1k3OHjfXlp3QK3GOJnXqys5HYVLPQaS+7HQ0xNt73CI7Gx0NbEUayo5GRyM7j8ayk1GS0MRGpKnsZLQkVoRdvYRmNiLNZQejpYWNSMvyFq1UpnV5zDY2ImFHw7ZEVdrdei1le5s2HcL+ax1lB7ahU+ewkLE2jWLCt78usiNb0tUwF+xm06q7YU/qITu0BUmGhD1vs2nWK9nQrrfs2Cb6GH+0Umwb3m5seIfs4EBfY7zkfrYtO/U3Nh0gO7qBgXAUudOh7SBoe5fs8GEMhmypjq3bQ+u7Zccvpzoe1oc4Nh8aD83TZAuUMQw90l06DB9BsSWKowV63OPaZeQo6HKvbAli+jkNhUZ76JQxBjo1la1B7kOPTE/dhoyFbuPkaoyfAHmyMjz2zM6BnhNlelSbBGlaeu87+X7omyvP44EpkOVBmt55DaH3VFke9aZBkumUf4cZ0H+gHI+ZsyDHQ7QVHp4NFR6R4ZHeGVI0pK8xugPUGCTeYw6eUq/lp0q9OKjSV7THXDx8xPurMw/3s0piPeajh+/Vd50FUGm+SI9H0YNhdTRzOtRaKM7jMfRYxFJtMf76LRHl8Th6DGarl5hvLJfVja2eVyqjxxOsFdN7Ggs+2ViExwD0eIq95tJlxpK1C/h79ECPQLaDwuXGoi2f5u2xAj2eCaYuHl+fLeSqsXIVeiSyF71KwWpj4c5rOHo8Z1pGPR9c8cZZxtoJa7l5ZJqWUeuCLN8Nqq9/gZPHOlxGJWwIdoAlUH/WRi4em3B6t3l80EMsxBFe5OCxEScScRwG2QJjLHA/RUbL0pdgjK0cPAh5GUaZFuCvSSkFuIxqxcWDkBr4f+/HXjOMV/DwMYaTByHNYKRXtwVYvBJ6RHPzIGQ7jDVlR2Cld6JHR44ehOyC0V7bHVDhWPTgfQEzF8Z7fU8gZfeixzDOHoS8ASNO2hdA0TT02M/dg5BxMOaBN5lLmpZRYk7/42aQk81Y0LSMEnWi+SCMO2EIU7kV6CHulOYiPHJ5vfBiwSHTMkrkybPDMPaokX4rvTUWSr0t5ORGOY1g+BHD/dU5gsuo5e8I9SDkXQgQP9RPlSJcRhXzWeY48R5EeN9HjShcRh0Ndhrqjd4Qgv7mwUTTMuqYBA/zz/9xyv5dTcuoXlI8COkCQapQ9U46Ad1bfSDJw3xjxUmKvng2w/bWVyHgQflDzz1NyyifV9WC4iOI87HHfqZlFO0OFjSf4O0un3rqZlpGef9X8uJQKkT6zEMnCcsod061gVCHXbvgTE3IMsqdz/Hsudstd6Zl1BeyFcro9SUEO+jY3LSM2iVboJwNByDaXofG+Isd+kp2/DD2wWQ8x6EtenSXHd7Aka99iiTPlR0d2H3al0hz7tdVqdlxxodI/lnZsS3Y9g21yNFzskNbcpZaZJ7syEGJyE6sRbSIFlFJpEGNpjSsZTgNzVMkDx9mcWUW8612PESKaDVKYL36yENkNrVFCQ3cCwsWmePLg/VJPQ4iW6gdSolRTmSiPxGKJ34EidT0J8J4bxQHkR3+RL5VToRQH0VKYbx1lIdIgR+PFPe6wkVI92XUHowbFq+5VrtF31FptGF/jFXPflVDi6iGFlENLaIaWkQ1tIhqaBHV0CKqoUVUQ4uohhZRDS2iGrxEis5SsDaAu7n5iOz/PkTFiR+UFBnahU6jhB9VFJlK7+Hz1VV8RZpQbldlMJ7H5iByzpeHy9taZYj85E9kvRbhJVJhNq2MfGqJEva6FhYtYn4+w9OWtU89kbwZ1BrsL8PlM0VJoZ2irGK+mVhPGlVDi6iGFlENLaIaWkQ1tIhqaBHV0CKqoUVUQ4uohhZRDS2iGlrEkU272sbQMJH9nfZcRArPh2g5zvreNB4iP1NrhJgvj/AQyfZ3feSCciJJ1A6lMD51zEGkL7VDKaeVE/F1wcpLYdEihf48LionkulPpJFyIuYvhXohn/Ed8FFhtWZ3DUZkph8R1pc+7DFUiy4KQoSsmU7lEIQHOQUFbd5zSCdC5omfa5GtYFJ7ewAiMjDfAtP6UkSKTD5v3mDrm76HEQEipjd+l4LfeI8EEXLRyuQX46dsI0KEhCzZfMm6iey0TvxqrZJzbVeJEBHz5+DLKH8TQ6SIkPRoG5XcCBMhpOoka5P8SxEmQsjO36xVft8WYSKkWvX+drtKZIlcmeydDLkiO6NH1qRWEBFC+mytICJk5dS6Dh7NZcejIS9tta0I66lNwUTZPpSTy15cLHP+sPRIjpIdjJ4/z1iIML6EUA4j/ypGj2I5n7Fh5tiwv40igr4qzoHFbSuGxxUuXP5f4/I/srMw8m/KhIT1qWm8v7r1H8NPFXuGGAleAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTA1LTI4VDE3OjI4OjM4KzA4OjAwUQL/cAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wNS0yOFQxNzoyODozOCswODowMCBfR8wAAAAASUVORK5CYII=');\n    }\n  }\n}\n.dokit-info-card-body {\n  table {\n    width: 100%;\n  }\n  td {\n    padding: 5px;\n    font-size: 12px;\n  }\n  .dokit-empty{\n    padding: 10px;\n    text-align: center;\n    line-height: 20px;\n    color: #9baec8;\n    font-weight: bold;\n  }\n  .dokit-info-key {\n    font-weight: bolder;\n    max-width: 150px;\n    overflow-x: scroll;\n  }\n  .dokit-info-value {\n  }\n  .dokit-info-opt {\n    width: 15px;\n    span {\n      display: inline-block;\n      width: 15px;\n      height: 15px;\n      background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAvFQTFRFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////A6m1bgAAAPl0Uk5TAB1FbIaTn6y5xtLf7Pn4697RxLeqnZCDYjsTCjFYgKfO9XZOJwhCy7p1NAI8tvvqpGctG2vm1ZtXCQNSovLhlEY6itrQghxywr5wLo3piD+e/jNRsKDBS7WXDIX99u/n4NnKw+jwZlTYsoxBJnGWvK81JN1jqe5+BW30LH3Ax1APIGCB1qEHaLMrf/wVKeVcKLF8IzaVjgZa489vDoc3hGEZebRTC9tkvRe4X6gSiUe/MlZ4AWqS7V7i3C+LXUNZaQTxq6bNETDFkXpMGK1ApUjkyZkeFER0zI+jWyUa07tNrnNle/MfIhBPPpwWbppV+iGYSUo9yNd3C18G8gAAAAFiS0dE+tVtBkoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAw7SURBVHja7V17XBXHFb4KiIjiEzWC8tAkGlFE8REVQVHjOxrxASQRQQWNJho1CWjwEW3UKD4qiqaGh2ISfBFTMGLU+GgTq+KLVmtsaJJGLKk1Tart/a9cNpe7Z2Z2d2Z3dufya74/Z2fO+b4Luzs7c84Zm80MNGjo4enVyLuxTxPfps38mrdo2ap1G/+27do/1sEUdyYgILBjp6DgELsimrfyD+3cRTRNdTz+hOeTdkp07faUaLpkdA8L7dGTVoWE8F4RvUXTRtAnsk1fNhFO9Os/4GnR7J0YOGhwlD4VEoZEx4iW4MDQYbFGVEgYPkLwDTPymVHGVUgYPaaPMBljxzXlJcOB8RPEPJSfnTiJpwwHQqKfs1zG5DjeKiRMedZSGe2nmiPDAe9plskIm26eDAfiG1giIyGR9t54/oUXZyTNbJmckpLcJHbIrNl+tEp851igIzWYQkHa3HkRL43EB8+ftuDlV4ZQSFm4yGQZr/prUVgcP+hVDSOPj12y9DUNM36vv2GmjnT1GVXGQo9ltKbClr+pLiWzs2kyRvZSc5zktWIlm71Vq4My1Cy+ZZKOzmuUfb62NvVXemz2eVvtCbhuvRk6Nij6e2dh+4367W4KVZ6vZW3mLmPLVsW7e9uvjRrfnq34abxjJ18dObsUHO1+9zc87M/fM0vBwZvv8dSRq+Blah43F/kFCt81URz/veLJLvbu4/lj2Qo9dpP9vMvLQxui+f3vc5XhQMKIZkRXI/iY/4B8F37IXUcNFnkTnWXzsD2cZLnoJTNkOHCA+LY6aNww6XE1JNIsGTU4dJikZK5Rsy8QjG41/OJQx5F+BKevGLM5m2DS01wZNWhAerx4G7HYBLcXzn/WQEAn0uNFv7m9uLXij6zQYbMdJdybY/Qae5nwHNQ1x9WD9Y1x7xH6TM3DLX1slYwa/HYt7j9PjyFP3E6JhTpqUIozyGG3gn9+9JxsrQ7iXJWZQypm4pjJbw8SPsFYtJjPZmHTcdRCmfUyanACU7KOzUBrdHymEB01tzymZALL8E+xv6ggHTbbh5iSk/SDV6NjM1YKE2LrjZIJp34nL8KWAYVuvp5C2RRTLkgEYMsz1i30E3Ea5RNNNw77HvhMrA6bbQzKqCPNqDB01DDROmy2cQil4EKKQeuQQdNFq6jBmWKEFMWy8BxkiK9bxL+8dxay8tPcl89fjAixfIJFxmcIrXNaA9oiA7aJVuBEf4TYJ+rd0Wf2edH869Dhd5BZv9+rdkc+y1rmi+bvQkPkN96j1vl9pPMA0ezlQN5vkz5X6fsF7NtLNHeAQ8ga5AXlrh1hz7NuFt92ANKLUt49Ru6QP4hmjgJZ4U5U6ncR9sscKJo4ikVw16HvJYV+l6GQFaJ54xgBGRaQe5XDXv6iWRNQCPe0YslzR2RBjOsmJC94QI7ppD5PwT5XRHMmohDumAaT+iCrYQ1FcyajALIcROgCtxAYl48sQ/4srf+bq1Aq351njtgDaDbDlz/hHsJU0XwV0QVGe4Si1xOugeurRfNVRjYgeh29nAMuzxTNVgXb4T0Qg1w+CK7OE81WDXBGeANerIDb0NZGETMCLo80hREY8I35pGiuqvjjeEAWbqPB+eLrormqAz5gQXTHwHBwLVA0VXX8CZD1Vb70gWimWvABdOXf7nCer3tj3ircBHTlET5waXWTaKJaWAboDnZdCHhHfuGWaJ7aSJPzjXW1TwMK/yyapjbgN6ArmwAGa5gXk84NJYCwa9sHZFKEnBFNUxtbgBAvZ3MAaC4SzZIGvnLGdTPgj4AQblG2ZuK2nPFZZ+uXQMgd0SRpkAco/+Xn1nbyxqaiOVIBRhE88XPrOXnjF6I5UmEnEOKcAH8lb2wkmiMdwN3ufGyB97qV0X4GUCTnXCm13QF/JjfZxdXCBTnnXVLbX4EQU9Ka+CMSkE6obesG2urBe90B+AUlpV+CTUYxEX/sqABCrta2gRUvg7H01gE8oVJrmwbLmy6LJkgLEByXV9v0tbxpomiCtAAvEmnDp1Le1FY0QVq0lLOW9p8z5U3s+bEVm9sU80Ak264+iFeUUlrADOUbVh2Jdm64zeIXxMdJc5QkeRNrTl4lG1d1sMy8r8gHShNEUNzkKJuOEjamWmDYgQXrplLaEggnOMKkI4avDrt9A7XrCfJhRUaF3GSjqQ361xhBiIF/rUa8heyndh0vHyZFOIJXy7dMQrYysaTAbGrX5+XDJtY2gQ32m0xC/sZbSGtq16DEQmltk4+8qR2TkBW8hRymdt1KPkyK3AKbi6VMQmzcymtJ+O4utWfwGpe+z0Fc2lo2IZf4CjlA7/m4fJy0RQIePXFsQmxVaUxM1XGVwTGIMZd+AC95UzGjEFvhvGJDpfTqcOsGy9YlzL2SgoRA5mRfViGCANfjpZgsuMfOpTqI+egMSEtLPzBW200Lc6LoBkhLSz8NQNuXoinSASQvOJd+QASUWSWUOAMUwXMmHYGdNw71LqwAiAh01h4IkjfuFk2RCvfA7eAsWQcyvkNEc6QCjD5zRmXBJAXri1fqQDqg7IyRDwStoYY8WISlcsZ1HzFwe7qraJI0yJIzdgVoJsubn08QzVIb68FP74o5Adu6olOMabAEEP57XXs1aHeD3FwtfA8I/6OufSAol3jdgAeLANLG5G8+GJu5RTRPLcBbRF5T6D64Us9CAeXJ7TCspp4FZ1bJL8Ev73oVLjscXIPVb908gDkekP0nuAanW/UqpPwBuBhwHFysR0H+ad3h1WhwtR6lXaB5rnDLph4lwmBla2BdySWi6SoD3up7seswPN5HNF1F9P4BEM3DOiCJoZYUMdQDWF3jGmE9cTToUSyasALmfwdokqoMIAV6HjD7sARI4YdqQpc+MKvHPQOy78ITT3rQiHXLWH8kMz+V2KkLTOGrFE2agE3wDzJKoRtMHrXfF00bxxTIUCkRF3kCZ/1LNG8U+yDBYMWOSLGEH0UTR7AzGfLzUOyJlK9wt4Wh5ZBdrErZM6SgyCh6Jxbgc/guVComUgukxIu9m2jycgRBbkmq9d7hypc9I0w0excikB9ZvWbvHfgZaU8+JJq/E4uQOuNa4TfPILrdJZ1kIHqeQLnWCLSQoJvcJmiZ2HGaI35CRtj/LVqDA2gV0P0UJzugtcnTVolWUfMVgp59RRPVe3cXMsgN4v4XIpS2Uo1CKia5QYEq9NipSY/RjZuCKjFwGAAPYKeZLaAceAg7HJc+rtgEYAcKfE899CF2WIfAXDjsvIjMlfSD30YH2wUUjZdwAGUSNZRluBemxLLTISCwgtiMIdY7R2MGhGRV52A0WFOOAsdjJk5ZrwMrhm2v7M5qAysNrvPQDCPYg1FoQX3eogvf4Eo6WSrj3hWMgF+MHkOEg1T8aaqDc8KJFNw/Q6S2HOdxSylmH0pah9MzcO8Feo0V47aOWVSdrh3umuIbRBEzCeasiLlZ9TXB8RQjFklh79NNryQ/jfQDGiyB2Zxg0tfkmVc3gk/mNAoMrUhW+5v4mj/VleSRPtVHEcSzjWfRfhKwIr8tyZ29PQ/b24imR181Q8ecxURnqcYtOzCGaNweXcFbRtg6oqNJbFmeKlhCVjLDk/FQKXVUK6RnllF+odNggMLh2SHR3NJNLl4mu7CnjOX5ay3bq+DGPpjL6aE5XZXslz7NU4cN20+VIe60wZDnVemPlGyPN2FGNDlWUUrWj/r3tg4dOOenaLg1138rJ+4etCvjP8Me6jB5ZrJ/uIrRRB0mqRBqV8OjwxeZju7O33yjTM1emYl1uR/2UJVif3HuAsoZ5dBcrTTfeO6vKYD7MzT82/eXLqm+pGai6kj6leZaVh6ZHkFyZ6IWBweibgW9tTms9917dYseZzpUBZZ/+2ncmp4Uw/02cD5fnogHwRRUXJKu9fPJXJOVwTImbrsFMmqQ4MnCihlJOk+d1YMTO46ZJSO24JJxfgyoWF5mnDSO4HTrE6MSIpONE4cYlWe5Cgmni4yTd6GHyDDK8uxw4wocuHah2jgbQ9hYUmxcxvQ8t6gHYPDPkpZo0VHpFNhYsjRJn4om/Y+623FZgR63Y9lE3Mo+WWXcrymo6Oh1fTaNhr7FuT+5xW2hhvzyiB3TFyso+OGrc7knt68UzZEBbyyL2bfao8DTq5F348bejS7k/rfk5IpT7vqv9At+wf8D/geF3QB8rZaJCgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wNS0yNVQxNzowNTozMiswODowMFGq0BYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDUtMjVUMTc6MDU6MzIrMDg6MDAg92iqAAAAAElFTkSuQmCC\");\n      background-size: 15px;\n      background-repeat: no-repeat;\n    }\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/components/ScanerComponent.vue",
    "content": "<template>\n  <div class=\"dokit-scaner\" ref=\"scaner\">\n    <div class=\"dokit-banner\" v-if=\"showBanner\">\n      <i class=\"dokit-close_icon\" @click=\"() => (showBanner = false)\"></i>\n      <p class=\"dokit-text\">若当前浏览器无法扫码，请切换其他浏览器尝试，目前支持的url:localhost、file和https</p>\n    </div>\n    <div class=\"dokit-cover\">\n      <p class=\"dokit-line\"></p>\n      <span class=\"dokit-square top left\"></span>\n      <span class=\"dokit-square top right\"></span>\n      <span class=\"dokit-square bottom right\"></span>\n      <span class=\"dokit-square bottom left\"></span>\n      <p class=\"dokit-tips\">将二维码放入框内，即可自动扫描</p>\n    </div>\n    <video\n      v-show=\"showPlay\"\n      class=\"dokit-source\"\n      ref=\"video\"\n      :width=\"videoWH.width\"\n      :height=\"videoWH.height\"\n      controls\n    ></video>\n    <canvas v-show=\"!showPlay\" ref=\"canvas\" />\n    <button v-show=\"showPlay\" @click=\"run\">开始</button>\n  </div>\n</template>\n\n<script>\n// eslint-disable-next-line no-unused-vars\n// import adapter from \"webrtc-adapter\";\nimport jsQR from \"jsqr\";\nexport default {\n  name: \"Scaner\",\n  props: {\n    // 使用后置相机\n    useBackCamera: {\n      type: Boolean,\n      default: true,\n    },\n    // 扫描识别后停止\n    stopOnScaned: {\n      type: Boolean,\n      default: true,\n    },\n    drawOnfound: {\n      type: Boolean,\n      default: true,\n    },\n    // 线条颜色\n    lineColor: {\n      type: String,\n      default: \"#03C03C\",\n    },\n    // 线条宽度\n    lineWidth: {\n      type: Number,\n      default: 2,\n    },\n    // 视频宽度\n    videoWidth: {\n      type: Number,\n      default:\n        document.documentElement.clientWidth || document.body.clientWidth,\n    },\n    // 视频高度\n    videoHeight: {\n      type: Number,\n      default:\n        document.documentElement.clientHeight - 48 ||\n        document.body.clientHeight - 48,\n    },\n    responsive: {\n      type: Boolean,\n      default: false,\n    },\n  },\n  data() {\n    return {\n      showPlay: false,\n      showBanner: true,\n      containerWidth: null,\n      active: false,\n      timeIndex: null,\n    };\n  },\n  computed: {\n    videoWH() {\n      if (this.containerWidth) {\n        const width = this.containerWidth;\n        const height = width * 0.75;\n        return { width, height };\n      }\n      return { width: this.videoWidth, height: this.videoHeight };\n    },\n  },\n  watch: {\n    active: {\n      immediate: true,\n      handler(active) {\n        if (!active) {\n          this.fullStop();\n        }\n      },\n    },\n  },\n  methods: {\n    /**\n     * \n     * @param {开始坐标} begin \n     * @param {结束坐标} end \n     */\n    drawLine(begin, end) {\n      this.canvas.beginPath();\n      this.canvas.moveTo(begin.x, begin.y);\n      this.canvas.lineTo(end.x, end.y);\n      this.canvas.lineWidth = this.lineWidth;\n      this.canvas.strokeStyle = this.lineColor;\n      this.canvas.stroke();\n    },\n    /**\n     * 画框\n     * @param location \n     */\n    drawBox(location) {\n      if (this.drawOnfound) {\n        this.drawLine(location.topLeftCorner, location.topRightCorner);\n        this.drawLine(location.topRightCorner, location.bottomRightCorner);\n        this.drawLine(location.bottomRightCorner, location.bottomLeftCorner);\n        this.drawLine(location.bottomLeftCorner, location.topLeftCorner);\n      }\n    },\n    tick() {\n      if (\n        this.$refs.video &&\n        this.$refs.video.readyState === this.$refs.video.HAVE_ENOUGH_DATA\n      ) {\n        this.$refs.canvas.height = this.videoWH.height;\n        this.$refs.canvas.width = this.videoWH.width;\n        this.canvas.drawImage(\n          this.$refs.video,\n          0,\n          0,\n          this.$refs.canvas.width,\n          this.$refs.canvas.height\n        );\n        const imageData = this.canvas.getImageData(\n          0,\n          0,\n          this.$refs.canvas.width,\n          this.$refs.canvas.height\n        );\n        let code = false;\n        try {\n          code = jsQR(imageData.data, imageData.width, imageData.height);\n        } catch (e) {\n          console.error(e);\n        }\n        if (code) {\n          this.drawBox(code.location);\n          this.found(code.data);\n        }\n      }\n      this.run();\n    },\n    // 初始化\n    setup() {\n      if (this.responsive) {\n        this.$nextTick(() => {\n          this.containerWidth = this.$refs.scaner.clientWidth;\n        });\n      }\n      console.log(navigator.mediaDevices)\n      if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {\n        this.previousCode = null;\n        this.parity = 0;\n        this.active = true;\n        this.canvas = this.$refs.canvas.getContext(\"2d\");\n        const facingMode = this.useBackCamera\n          ? { exact: \"environment\" }\n          : \"user\";\n        const handleSuccess = (stream) => {\n          if (this.$refs.video.srcObject !== undefined) {\n            this.$refs.video.srcObject = stream;\n          } else if (window.videoEl.mozSrcObject !== undefined) {\n            this.$refs.video.mozSrcObject = stream;\n          } else if (window.URL.createObjectURL) {\n            this.$refs.video.src = window.URL.createObjectURL(stream);\n          } else if (window.webkitURL) {\n            this.$refs.video.src = window.webkitURL.createObjectURL(stream);\n          } else {\n            this.$refs.video.src = stream;\n          }\n          this.$refs.video.playsInline = true;\n          const playPromise = this.$refs.video.play();\n          playPromise.catch(() => (this.showPlay = true));\n          playPromise.then(this.run);\n        };\n        navigator.mediaDevices\n          .getUserMedia({ video: { facingMode } })\n          .then(handleSuccess)\n          .catch(() => {\n            navigator.mediaDevices\n              .getUserMedia({ video: true })\n              .then(handleSuccess)\n              .catch((error) => {\n                this.$emit(\"error-captured\", error);\n              });\n          });\n      }\n    },\n    run() {\n      if (this.active) {\n        this.timeIndex = requestAnimationFrame(this.tick);\n      }\n    },\n    found(code) {\n      if (this.previousCode !== code) {\n        this.previousCode = code;\n      } else if (this.previousCode === code) {\n        this.parity += 1;\n      }\n      if (this.parity > 2) {\n        this.active = this.stopOnScanned ? false : true;\n        this.parity = 0;\n        this.$emit(\"code-scanned\", code);\n      }\n    },\n    // 完全停止\n    fullStop() {\n      if (this.$refs.video && this.$refs.video.srcObject) {\n        this.$refs.video.srcObject.getTracks().forEach((t) => t.stop());\n      }\n    },\n  },\n  activated() {\n    this.setup();\n  },\n  deactivated() {\n    this.fullStop();\n    cancelAnimationFrame(this.timeIndex);\n    this.timeIndex = null;\n  },\n};\n</script>\n\n<style lang=\"less\" scoped>\n.dokit-scaner {\n  background: #000000;\n  position: fixed;\n  top: 48px;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  height: -webkit-calc(100% - 48px);\n  height: -moz-calc(100% - 48px);\n  height: -ms-calc(100% - 48px);\n  height: -o-calc(100% - 48px);\n  height: calc(100% - 48px);\n  .dokit-banner {\n    width: 340px;\n    position: absolute;\n    top: 16px;\n    left: 50%;\n    margin-left: -170px;\n    background: #fa74a2;\n    border-radius: 8px;\n    box-sizing: border-box;\n    padding: 12px;\n    padding-right: 39px;\n    opacity: 0.9;\n    box-shadow: 1px 1px 10px rgba(0, 0, 0, 0.2);\n    .dokit-text {\n      padding: 0;\n      margin: 0;\n      color: #ffffff;\n      font-size: 12px;\n      text-align: justify;\n      text-align-last: left;\n    }\n    .dokit-close_icon {\n      display: inline-block;\n      height: 24px;\n      width: 24px;\n      background: url(\"https://pt-starimg.didistatic.com/static/starimg/img/9by73wJnr31645431185877.png\")\n        no-repeat center;\n      background-size: auto 100%;\n      position: absolute;\n      right: 8px;\n      top: 50%;\n      transform: translateY(-50%);\n    }\n  }\n  .dokit-cover {\n    height: 220px;\n    width: 220px;\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    -webkit-transform: translate(-50%, -50%);\n    -moz-transform: translate(-50%, -50%);\n    -ms-transform: translate(-50%, -50%);\n    -o-transform: translate(-50%, -50%);\n    transform: translate(-50%, -50%);\n    border: 0.5px solid #999999;\n    z-index: 1111;\n    .dokit-line {\n      width: 200px;\n      height: 1px;\n      margin-left: 10px;\n      background: #5f68e8;\n      background: linear-gradient(\n        to right,\n        transparent,\n        #5f68e8,\n        #0165ff,\n        #5f68e8,\n        transparent\n      );\n      position: absolute;\n      -webkit-animation: scan 1.75s infinite linear;\n      -moz-animation: scan 1.75s infinite linear;\n      -ms-animation: scan 1.75s infinite linear;\n      -o-animation: scan 1.75s infinite linear;\n      animation: scan 1.75s infinite linear;\n      -webkit-animation-fill-mode: both;\n      -moz-animation-fill-mode: both;\n      -ms-animation-fill-mode: both;\n      -o-animation-fill-mode: both;\n      animation-fill-mode: both;\n      border-radius: 1px;\n    }\n    .dokit-square {\n      display: inline-block;\n      height: 20px;\n      width: 20px;\n      position: absolute;\n    }\n    .top {\n      top: 0;\n      border-top: 1px solid #5f68e8;\n    }\n    .left {\n      left: 0;\n      border-left: 1px solid #5f68e8;\n    }\n    .bottom {\n      bottom: 0;\n      border-bottom: 1px solid #5f68e8;\n    }\n    .right {\n      right: 0;\n      border-right: 1px solid #5f68e8;\n    }\n    .dokit-tips {\n      position: absolute;\n      bottom: -48px;\n      width: 100%;\n      font-size: 14px;\n      color: #ffffff;\n      opacity: 0.8;\n    }\n  }\n}\n@-webkit-keyframes scan {\n  0% {\n    top: 0;\n  }\n  25% {\n    top: 50px;\n  }\n  50% {\n    top: 100px;\n  }\n  75% {\n    top: 150px;\n  }\n  100% {\n    top: 200px;\n  }\n}\n@-moz-keyframes scan {\n  0% {\n    top: 0;\n  }\n  25% {\n    top: 50px;\n  }\n  50% {\n    top: 100px;\n  }\n  75% {\n    top: 150px;\n  }\n  100% {\n    top: 200px;\n  }\n}\n@-o-keyframes scan {\n  0% {\n    top: 0;\n  }\n  25% {\n    top: 50px;\n  }\n  50% {\n    top: 100px;\n  }\n  75% {\n    top: 150px;\n  }\n  100% {\n    top: 200px;\n  }\n}\n@keyframes scan {\n  0% {\n    top: 0;\n  }\n  25% {\n    top: 50px;\n  }\n  50% {\n    top: 100px;\n  }\n  75% {\n    top: 150px;\n  }\n  100% {\n    top: 200px;\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/components/ToolHelloWorld.vue",
    "content": "<template>\n  <div class=\"dokit-hello-world\">\n    <div style=\"font-weight:bold;font-size:30px;font-style:italic;\">Hello Dokit</div>\n    <div>Demo Plugin</div>\n  </div>\n</template>\n<script>\nexport default {\n  \n}\n</script>\n<style>\n.dokit-hello-world{\n  padding:10px;\n  text-align: center;\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/components/ToolsContainer.vue",
    "content": "<template>\n  <div class=\"dokit-tools-container\">\n    <div class=\"dokit-tools-tabs\">\n      <div\n        v-for=\"tab in tabs\"\n        v-bind:key=\"tab.displayName\"\n        v-bind:class=\"['dokit-tab-button', { active: currentTab === tab.component }]\"\n        v-on:click=\"currentTab = tab.component\"\n      >\n        {{ tab.displayName }}\n      </div>\n    </div>\n    <keep-alive>\n      <component v-bind:is=\"currentTool\" class=\"tab\"></component>\n    </keep-alive>\n  </div>\n</template>\n<script>\nimport ToolConsole from './ToolConsole'\nimport ToolAppInfo from './ToolAppInfo'\nimport ToolHelloWorld from './ToolHelloWorld'\n\nexport default {\n  components: {\n    ToolConsole,\n    ToolHelloWorld,\n    ToolAppInfo\n  },\n  data() {\n    return {\n      tabs: [{\n        component: 'console',\n        displayName: 'Console'\n      },{\n        component: 'app-info',\n        displayName: 'AppInfo'\n      },{\n        component: 'hello-world',\n        displayName: 'HelloWorld'\n      }],\n      currentTab: 'console'\n    }\n  },\n  computed: {\n    currentTool () {\n      return `tool-${this.currentTab}`\n    }\n  },\n  created() {\n    // TODO:读取全局变量不够优雅还需要优化一下\n    this.tabs = this.tabs.concat(window.dokit.outPlugins)\n  },\n}\n</script>\n<style lang=\"less\">\n.dokit-tools-container {\n  height: 61.8%;\n  width: 100%;\n  overflow: hidden;\n  position: fixed;\n  bottom: 0;\n  box-shadow:0px 0px 10px 5px #ddd;\n  border-top-left-radius: 10px;\n  border-top-right-radius: 10px;\n  .dokit-tools-tabs{\n    // position: absolute;\n    // top: 0;\n    width: 100%;\n    white-space: nowrap;\n    overflow-x: auto;\n    text-align: left;\n    border-bottom: 1px solid #ddd;\n    padding: 0;\n    .dokit-tab-button{\n      font-size: 14px;\n      display: inline-block;\n      height: 30px;\n      min-width: 50px;\n      line-height: 30px;\n      padding: 0 10px;\n      background-color: white;\n      text-align: center;\n      border-left: 1px solid #eee;\n    }\n    .active{\n      background-color: #337cc4;\n      color: #fff;\n      font-weight: bold;\n    }\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/feature.js",
    "content": "import Console from './plugins/console/index'\nimport AppInfo from './plugins/app-info/index'\nimport Element from './plugins/element/index'\nimport scanCode from './plugins/scan-code/index.js'\nimport OneMachineWithMultipleControls from './plugins/one-machine-with-multiple-controls/index.js'\nimport Network from './plugins/network/index'\nimport Storage from './plugins/storage/index'\nimport DemoPlugin from './plugins/demo-plugin/index'\nimport DemoIndependPlugin from './plugins/demo-single-plugin/index'\nimport H5DoorPlugin from './plugins/h5-door/index'\nimport AlignRuler from './plugins/align-ruler/index'\nimport HelloWorld from './components/ToolHelloWorld'\nimport Resource from './plugins/resources/index'\nimport ApiMock from './plugins/api-mock/index'\nimport WebVitals from './plugins/web-vitals-time/index'\n\nimport {IndependPlugin, RouterPlugin} from '@dokit/web-core'\n\nexport const BasicFeatures = {\n  title: '常用工具',\n  list: [Console, AppInfo, Resource, Network, Storage, DemoPlugin, DemoIndependPlugin, H5DoorPlugin, WebVitals, Element, OneMachineWithMultipleControls, scanCode]\n  // list: [Console, AppInfo, Resource, Network, Storage, H5DoorPlugin]\n}\n\nexport const DokitFeatures = {\n  title: '平台功能',\n  list: [ApiMock]\n}\n\nexport const UIFeatures = {\n  title: '视觉功能',\n  list: [AlignRuler]\n  // list: [AlignRuler,\n  // new RouterPlugin({\n  //   nameZh: 'UI结构',\n  //   name: 'view-selector',\n  //   icon: 'https://pt-starimg.didistatic.com/static/starimg/img/XNViIWzG7N1618997548483.png',\n  //   component: HelloWorld\n  // })]\n}\nexport const Features = [BasicFeatures, DokitFeatures, UIFeatures]\n"
  },
  {
    "path": "Web/packages/web/src/index.js",
    "content": "import {Dokit} from '@dokit/web-core'\nimport {Features} from './feature'\n/*\n* TODO 全局注册 Dokit\n*/\nwindow.Dokit = new Dokit({\n  features: Features,\n});\n\n"
  },
  {
    "path": "Web/packages/web/src/plugins/align-ruler/align-ruler.vue",
    "content": "<template>\n  <div>\n    <div class=\"dokit-ruler-center-bg\" :style=\"centerStyle\"></div>\n    <div class=\"dokit-ruler-center-round\" :style=\"centerStyle\"></div>\n    <div class=\"dokit-ruler-center-dot\" :style=\"centerStyle\"></div>\n    <div class=\"dokit-ruler-line dokit-ruler-horizon-line\" :style=\"horizonLineStyle\"></div>\n    <div\n      class=\"dokit-ruler-line dokit-ruler-vertical-line\"\n      :style=\"verticalLineStyle\"\n    ></div>\n    <div class=\"dokit-ruler-info\" :style=\"topInfoStyle\">{{ position.top }}</div>\n    <div class=\"dokit-ruler-info\" :style=\"rightInfoStyle\">\n      {{ position.right }}\n    </div>\n    <div class=\"dokit-ruler-info\" :style=\"bottomInfoStyle\">\n      {{ position.bottom }}\n    </div>\n    <div class=\"dokit-ruler-info\" :style=\"leftInfoStyle\">{{ position.left }}</div>\n\n    <InfoBox :position=\"position\" @remove=\"remove\" />\n\n    <div\n      class=\"dokit-ruler-drag-mask\"\n      :style=\"centerStyle\"\n      @mousedown=\"drag\"\n      @touchstart=\"drag\"\n    ></div>\n  </div>\n</template>\n\n<script>\nimport InfoBox from \"./info-box\";\nimport { removeIndependPlugin } from \"@dokit/web-core\";\n\nexport default {\n  name: \"align-ruler\",\n  components: {\n    InfoBox,\n  },\n  data() {\n    return {\n      posX: 200,\n      posY: 200,\n      screenWidth: document.documentElement.clientWidth,\n      screenHeight: document.documentElement.clientHeight,\n    };\n  },\n  computed: {\n    position() {\n      return {\n        top: this.posY,\n        left: this.posX,\n        right: this.screenWidth - this.posX,\n        bottom: this.screenHeight - this.posY,\n      };\n    },\n\n    centerStyle() {\n      return {\n        top: this.posY + \"px\",\n        left: this.posX + \"px\",\n      };\n    },\n    horizonLineStyle() {\n      return {\n        width: this.screenWidth + \"px\",\n        top: this.posY + \"px\",\n      };\n    },\n    verticalLineStyle() {\n      return {\n        height: this.screenHeight + \"px\",\n        left: this.posX + \"px\",\n      };\n    },\n    topInfoStyle() {\n      return {\n        top: this.posY / 2 + \"px\",\n        left: this.posX + \"px\",\n      };\n    },\n    rightInfoStyle() {\n      return {\n        top: this.posY + \"px\",\n        left: (this.posX + this.screenWidth) / 2 + \"px\",\n      };\n    },\n    bottomInfoStyle() {\n      return {\n        top: (this.screenHeight + this.posY) / 2 + \"px\",\n        left: this.posX + \"px\",\n      };\n    },\n    leftInfoStyle() {\n      return {\n        top: this.posY + \"px\",\n        left: this.posX / 2 + \"px\",\n      };\n    },\n  },\n\n  // 监听浏览器缩放\n  mounted() {\n    window.addEventListener(\"resize\", this.handleResize);\n  },\n  beforeUnmount() {\n    window.removeEventListener(\"resize\", this.handleResize);\n  },\n\n  methods: {\n    remove() {\n      removeIndependPlugin(\"align-ruler\");\n    },\n    handleResize(event) {\n      this.screenWidth = document.documentElement.clientWidth;\n      this.screenHeight = document.documentElement.clientHeight;\n      this.posX = this.screenWidth * 0.3;\n      this.posY = this.screenHeight * 0.3;\n    },\n    drag(e) {\n      e.preventDefault();\n      e.stopPropagation();\n      let el = e.target;\n\n      // 算出鼠标相对元素的位置\n      // 兼容触摸和鼠标\n      let startX = e.clientX ? e.clientX : e.touches[0].clientX;\n      let startY = e.clientY ? e.clientY : e.touches[0].clientY;\n      let offsetX = startX - el.offsetLeft;\n      let offsetY = startY - el.offsetTop;\n\n      let update = (X, Y) => {\n        let left = X - offsetX;\n        let top = Y - offsetY;\n\n        this.posX = Math.round(left);\n        this.posY = Math.round(top);\n      };\n\n      let handleMousemove = (e) => {\n        update(e.clientX, e.clientY);\n      };\n\n      let handleTouchmove = (e) => {\n        update(e.touches[0].clientX, e.touches[0].clientY);\n      };\n\n      let removeDragHandle = (e) => {\n        document.removeEventListener(\"mousemove\", handleMousemove);\n        document.removeEventListener(\"touchmove\", handleTouchmove);\n        document.removeEventListener(\"mouseup\", removeDragHandle);\n        document.removeEventListener(\"touchend\", removeDragHandle);\n      };\n\n      document.addEventListener(\"mousemove\", handleMousemove);\n      document.addEventListener(\"touchmove\", handleTouchmove);\n      document.addEventListener(\"mouseup\", removeDragHandle);\n      document.addEventListener(\"touchend\", removeDragHandle);\n    },\n  },\n};\n</script>\n\n\n<style scoped>\n.color {\n  color: #ffffff;\n  color: #cc3a4b;\n  color: #337cc4;\n  color: #cc3a4b30;\n}\n\n.dokit-ruler-center-bg {\n  box-sizing: border-box;\n  background-color: rgba(255, 255, 255, 0.392156863);\n  position: fixed;\n  width: 60px;\n  height: 60px;\n  transform: translate(-30px, -30px);\n  border-radius: 30px;\n  border: solid 1px rgba(51, 124, 196, 0.392156863);\n}\n\n.dokit-ruler-center-round {\n  background-color: rgba(204, 58, 75, 0.196078431);\n  position: fixed;\n  width: 40px;\n  height: 40px;\n  transform: translate(-20px, -20px);\n  border-radius: 20px;\n}\n\n.dokit-ruler-center-dot {\n  background-color: #cc3a4b;\n  position: fixed;\n  width: 6px;\n  height: 6px;\n  transform: translate(-3px, -3px);\n  border-radius: 3px;\n}\n\n.dokit-ruler-line {\n  position: fixed;\n  background-color: #cc3a4b;\n}\n\n.dokit-ruler-horizon-line {\n  left: 0;\n  height: 1px;\n  transform: translate(0px, -0.5px);\n}\n\n.dokit-ruler-vertical-line {\n  top: 0;\n  width: 1px;\n  transform: translate(-0.5px, 0px);\n}\n\n.dokit-ruler-info {\n  position: fixed;\n}\n\n.dokit-ruler-drag-mask {\n  position: fixed;\n  width: 60px;\n  height: 60px;\n  transform: translate(-30px, -30px);\n  border-radius: 30px;\n}\n</style>\n"
  },
  {
    "path": "Web/packages/web/src/plugins/align-ruler/index.js",
    "content": "// \n// 功能: 对齐标尺\n// 作者: foolbit (唐斌)\n// 时间v2021-5-30\n// \n\nimport AlignRuler from './align-ruler.vue'\nimport {IndependPlugin} from '@dokit/web-core'\n\nexport default new IndependPlugin({\n  nameZh: '对齐标尺',\n  name: 'align-ruler',\n  icon: 'https://pt-starimg.didistatic.com/static/starimg/img/a5UTjMn6lO1618997535798.png',\n  component: AlignRuler\n})\n\n"
  },
  {
    "path": "Web/packages/web/src/plugins/align-ruler/info-box.vue",
    "content": "<template>\n  <div class=\"dokit-position-infobox-container\">\n    位置: 左 {{ position.left }}, 右 {{ position.right }}, 上 {{ position.top }}, 下 {{ \n      position.bottom\n    }}\n    <span class=\"dokit-close-button\" @click=\"$emit('remove')\"></span>\n  </div>\n</template>\n\n<script>\nexport default {\n  props: {\n    position: {\n      top: 0,\n      right: 0,\n      bottom: 0,\n      left: 0,\n    },\n  },\n};\n</script>\n\n<style scoped>\n.dokit-position-infobox-container {\n  position: fixed;\n  left: 0;\n  right: 0;\n  margin: auto;\n  bottom: 20px;\n\n  width: 250px;\n  height: 50px;\n  padding: 0px 20px;\n  line-height: 50px;\n  font-size: 12px;\n\n  border-radius: 5px;\n  box-shadow: 0px 0px 2px #cccccc;\n}\n\n.position-info {\n  font-size: 10px;\n}\n\n.dokit-close-button {\n  background: #324456;\n  color: #fff;\n\n  border-radius: 10px;\n  width: 20px;\n  height: 20px;\n\n  line-height: 18px;\n  font-size: 15px;\n  text-align: center;\n\n  top: 15px;\n  right: 10px;\n  position: absolute;\n}\n\n.close-button::before {\n  content: \"×\";\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/api-mock/index.js",
    "content": "import ApiMock from './main.vue'\nimport {getGlobalData, RouterPlugin} from '@dokit/web-core'\nimport { getPartUrlByParam } from \"@dokit/web-utils\";\nimport { request } from './../../assets/util'\n\nconst mockBaseUrl = \"https://www.dokit.cn\";\n\nconst getCheckedInterfaceList = function (interfaceList) {\n  return interfaceList.filter(i => i.checked)\n}\n\n// 将本地接口数据 和 线上数据合并  主要是确认哪些接口/场景已经在本地开启\n// TODO: 性能有待优化\nconst getMergeData = function (list) {\n  let localList = JSON.parse(localStorage.getItem('dokit-interface-list') || '[]')\n  list.forEach(i => {\n    localList.forEach(l => {\n      if (i._id === l._id) {\n        i.checked = l.checked\n        i.sceneList.forEach(scene => {\n          l.sceneList.forEach(s => {\n            if (scene._id === s._id) {\n              scene.checked = s.checked\n            }\n          })\n        });\n      }\n    })\n  })\n\n  return list\n}\n\nexport default new RouterPlugin({\n  name: 'ApiMock',\n  nameZh: '数据mock',\n  component: ApiMock,\n  icon: 'https://pt-starimg.didistatic.com/static/starimg/img/GEAC1clsH81623297652210.png',\n  onProductReady(){\n    let state = getGlobalData();\n    // 获取接口数据\n    fetch(\n      `${mockBaseUrl}/api/app/interface?projectId=${state.productId}&isfull=1`,\n      {\n        mode: \"cors\",\n      }\n    )\n      .then((response) => {\n        return response.json();\n      })\n      .then((info) => {\n        let list = (info.data && info.data.datalist) || [];\n        list.forEach((element) => {\n          element.checked = false;\n          element.sceneList.forEach((scene, index) => {\n            scene.checked = false;\n            if (index === 0) {\n              scene.checked = true;\n            }\n          });\n        });\n        \n        state.interfaceList = getMergeData(list)\n      });\n\n\n\n      request.hookFetch({\n        onBeforeFetch: (fetchArgs) => {\n          let checkedInterfaceList = getCheckedInterfaceList(state.interfaceList)\n          let url = fetchArgs[0];\n          let path = `/`+getPartUrlByParam(url, 'path')\n          let sceneId = ''\n          checkedInterfaceList.forEach(i => {\n            if(i.path === path) {\n              i.sceneList.forEach(scene => {\n                if(scene.checked) {\n                  sceneId = scene._id\n                }\n              })\n            }\n          })\n          sceneId && fetchArgs[1] && (fetchArgs[1].method = 'get') && (fetchArgs[1].headers && delete fetchArgs[1].headers)\n          sceneId && (fetchArgs[0] = `${mockBaseUrl}/api/app/scene/${sceneId}`)\n          return fetchArgs;\n        },\n        \n      });\n      request.hookXhr({\n        onBeforeOpen: (args) => {\n          let checkedInterfaceList = getCheckedInterfaceList(state.interfaceList)\n          let url = args[1];\n          let path = `/`+getPartUrlByParam(url, 'path')\n          let sceneId = ''\n          checkedInterfaceList.forEach(i => {\n            if(i.path === path) {\n              i.sceneList.forEach(scene => {\n                if(scene.checked) {\n                  sceneId = scene._id\n                }\n              })\n            }\n          })\n          sceneId && (args[0] = 'get')\n          sceneId && (args[1] = `${mockBaseUrl}/api/app/scene/${sceneId}`)\n          return args;\n        },\n        onBeforeSetRequestHeader: (args, config) => {\n          let checkedInterfaceList = getCheckedInterfaceList(state.interfaceList)\n          let url = config.originRequestInfo.url;\n          let path = `/`+getPartUrlByParam(url, 'path')\n          let sceneId = ''\n          checkedInterfaceList.forEach(i => {\n            if(i.path === path) {\n              i.sceneList.forEach(scene => {\n                if(scene.checked) {\n                  sceneId = scene._id\n                }\n              })\n            }\n          })\n          if (sceneId) {\n            return false\n          }\n          return args\n        }\n      });\n  },\n  onUnload(){}\n})"
  },
  {
    "path": "Web/packages/web/src/plugins/api-mock/interface-item.vue",
    "content": "<template>\n  <div class=\"dokit-interface-item\">\n    <div class=\"dokit-interface-title\" @click=\"showContent=!showContent\">\n      <span class=\"dokit-interface-title-text\">{{interfaceItem.name}}</span>\n      <div class=\"dokit-interface-title-opt\" @click.stop=\"toggleInterfaceSwitch\">\n        <img v-if=\"!interfaceItem.checked\" src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAAEN1JREFUeNrt3X9sHGV6B/DvM2uvbZJsAs5vDBFWUwpNVAF3B4JQcDyz6xhIQo7mTgX1EEdboeTKXaXmD3o9pUJ3FeGP5g/fqXdcgbtCdQ1QaB2MPTNJCuVocjSgXiIQl9Yo55DESRw7JrHxj52nf3gd7S/bu57dHe/m+5Esed+ZeeeZV/vsvPPrHYCIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIio7EnQAaSzbfsewzAaRKQhHo9fJyINIrJEVSOqulBEIgAiQcdJs3IJwCCAQVUdFJHzInJCVU8A6FHVE9XV1e80NTV9EXSgkwJPEMdxbhGRJlVdD6AJwFVBx0SB22sYRpeqvmOa5q+DDCSQBHFdtxHAZgCbVfXuIBuA5jYReQ/A3lAo1N7U1HS05Osv5cocx9kCYCsmkqOm1BtL5U1VbQCvDw8Pv7xp06bPS7HOoifIgQMHFo2Pjz8C4GEAd+S42CiA3sk/ETmd/FlVe0VkGYDLf6q6PPkzgHApGpAC8amIvDQ2Nvbyhg0bPinmioqaILZt7xCRbQCuz2H24wDeBPCKZVn/4XfdXV1dfxgKhbao6hYA1xVzOykwIwCe8zxvVywW6ynGCoqSILZtbxSRHQDumm4+VT1qGIatqu2FSIqpOI5zL4D7VHWziPxOsdZDgfktgGcty2ordMUFTRDXdetVdReAx6aZrQ9Am6o60Wj0l4XeoJk4jhMTkYdU9fFSr5uKS1UdVf2bWCx2qFB1FixBbNu+S0R2A/jSFLOMAWgTkTbTNLuL21Qzc133blXdjomTBlQ5+gF827KsnxeisoIkiOM4jwHYDWDBFLM8D6DNsqwPS9FCecZ+H4DtAFqCjoUKR0R+YJrmX/uux28FjuM8DeC72aap6qsA2qLR6Nulb6K8t+OPRWSbqt4ZdCxUMK9ZlvWQnwp8JYjrurtV9cksk04C+JZlWf8aZOvMhm3bT4nI94OOgwrmfcuyvjLbhWedIK7r/req3pZl0rue5z0Ri8VKftWzUGzbflxEngs6DioMETltmuaKWS07m4Ucx7kIYF6WST/v7+//061bt44G3Sh+ua7bpKq/ALA06FjIPxH53DTNvG9yzTtBXNc9pKoZuyzP8/42FovtDLohCmnPnj3hRYsWHRaRNUHHQgVxyLKsXO/mAJBngriu+4KqPppebhjGnzQ3N/9T0FtfLI7jvArgq0HHQQWxx7Ksr+U6s5HrjLZt78iWHABaKjk5AMCyrIdE5Jmg46CC2Oq67ou5zpzTHiRx68i/ZSws8rRpmt8LeotLxXXdPar6R0HHQf6p6vZoNPrDmeabMUHeeuutG6uqqt4CcEPapH+3LGtT0BtaatOcvaPyMigipmma708304xdrKqqqt3ITI6DV2JyJNwHYCDoIMi3iKr+YKaZpk0Qx3H+Cmm3YKjqp6Ojow8GvXVBMU2zV0Q2Bh0HFYSZ+I5Pacou1v79+2/0PO9dVV2cVHzJMIyNzc3N+4PesqDxYmLFOCcid5mm+ZtsE6fcg8Tj8R1pyQEA32FyTIhGoz9V1R8HHQf5thjAjqkmZk2QxB2u6c90/KdlWfzFTKKqbQCGgo6D/FHVb7que3+2acYUCzySpbjgT2uVu1gsdlRE2C4VwPO8P8tWnpEgiXGqvp5W/KZlWXuC3oi5KB6PtwE4HXQc5I+IPJBtL5KRINn2HvyVnFpisAC2TwXIthdJSZADBw4sFpGH0+b5Z9M0O4MOfo5rE5GiDj9DxSciD+zbt29DcllKgoyPjz+IiTGlkhfir+MMLMu6kDhgpzLned7m5M/pXayUiSLykmma/xV00OUgMeTM/wUdB/mjqg+6rls/+flygnR2dv4BgNa0+d8MOuAy0xV0AOSPiCzxPO/ynSKXEyQUCm1OnlFVB2tra5kgeVBVJkgFEJHNl/+f/MdxHAeAmTTfC5ZlPZZHvQTAdd0eVW0IYt1XXXUVlixZgsWLFyMcDiMcDiMUCmF0dBSjo6MYHBzEuXPnMDAwgPHx8bzrb2xs9B1jd3fqkGiFqDNbvX6o6ohhGNeaptlXBQAdHR1LkJocUNX2gq3xytIF4JulXGF9fT2WL1+OFSuyj0tQW1uL2tpaRCIRNDQ0YHR0FKdOncKpU6dw8eLFnNfj98vc399flHoLmRwAICI1mMiHfzEAIBwOp4yhq6qjAwMD7F7Ngqq+Usr1NTY24pZbbpkyObIJh8NYtWoV7rjjDqxatarkbVQOVLUZSByDqGrKwbmIvFYJI5MEwbKsLgBnS7GuNWvWTPvrO9mtms7q1asL1s2pMM0AUAUAInKbql6eIiJlN+DbHPMqgCeKuYJbb70V11xzTUb5yZMn0d/fjwsXLmBoaOI+SsMwEIlEEIlE0NjYiKqqqpRlGhsbMTAwgPPnz+cVQ6G7NsWuN0+NHR0dkSoA8DzvOpGUR0N+M7s6CQBU9ZO09iyo1atXZ02OI0eOoLe3N6Pc8zwMDAxgYGAAfX19WLNmDRYsSB1Gee3atTh8+HBexyQVniAIh8MNRkdHR42ILEmb1jurGgkAoKpFa79FixZlPW54++23syZHukuXLuHQoUMZX8Lq6moej6RR1euMmpqajFOSpmkyQXwwDKNod/def33my7o+/vhjjI2N5VVPd3d3xvHJihUrUF9fn1c9lUxEGgzP89JfT8bk8K8obRiJRLB0aepIqGfOnMFnn302q/o++uijjLJ8zoZVung8fp2hqunjlTJBfIrH40Vpw/TjDs/zcOzYsVnXd/HixYyu1tKlS1HM46dyEgqFFhgA0hOED//41NLSch4Tb+otqCVLUg8Vh4eHMTw87KvO9INywzBw9dVXF7+RykOkyjCMSPIpXnAPUii9KPDbdcPh1Ddb+00OALhw4cKM65lKPtdP+vv7p7yS7qfeYp7xUtVIFbtYRVMWCTIyMoLh4WHU1dVdLqupqclp2Xy+yIcPH8553rmSIAAiOQ9eTXQlMkQk/V6EZbOqidIVvB1HR1MPa5J/9WerpqYmo56RkZEiN03ZGKzyPG8w7awFE6QwipIgyV/mQiTIwoULs64nF8Xo3uRzrFJsIjJYBSB9D7I86MDKXWdn5zUAcjvSzcPZs2dTvtB1dXWoq6vzdSwyf/78lM+Tt6XkotJvNQEwyC5WEYRCoaK0YfrNhIZhYPXq1bOub/78+RkHxGfPnoXneSVopbkvHo9/bhiG0ZNWzgTxryhtODg4iDNnzqSULV26FNdee+2s6rv55pszyk6ePFmC5ikPoVCoxxgZGTmRPsF1XSaJD57nFa2b2tPTk1F20003obq6Oq96GhsbEYmknuE/deoU+vr6StRKc5+qnjBaW1tHVDX9AR8miA8iUrT26+/vx/HjxzPK77nnHixbNvNq582bh9tvvz2jazU2Npa13itZPB4/UQUAhmH0qOqSpAm/C+DXQQdYrkTkxmLWf+zYMSxYsCDj3qy1a9eivr5+2gembrjhhqx7myNHjuT1LMiVwPO8nioAUNXDAG6dnBAKhTZh4qk4mp0txV7BBx98gDVr1mD58tTe3MqVK7Fy5UoAE6drv/jii4yuVLru7u68nyasdKr6aWtr66ABACLSkTZxc9ABlivHce5FibqoR48enfaUaDgcnjE5jh07NpdOq84ZIuICiUEbRkdHf5k2fX5XV9eGvGslACjpa6K7u7vx4Ycf4vTp3G/CHh0dxfHjx3Hw4EEed0xBRPYBiUEbWltbzzqO4yJpbCzDMFoBvBV0oGXIKvUK+/r60NfXh+7u7qIOHFesK9xz5cp5khEALpBIkIR3kZQgiW7Wt4KOtJwkulezv3Ln09DQEI4fP160vUI+d+Tmao5271zTNPuApLF54/H4G8lziEgDu1l5iwUdAPmnqm9M/n85QVpaWv4HQMrBeigUasm9WlLVknevqODOGobx+uSH9OdB3kj+oKpbHce5Ppdar3S2bT8oIrcFHQf5IyKvT3avgLQEqaqqeh2pTxQuB7A96KDLBNupAojIG8mfUxKkqanpnKq+nLbMdsdx1gYd+Fzmuu43RGR90HGQP6ra3tzcnHLmNuORWxF5Ka2oTkS2BR38XLVz505DVbn3qACGYfwkoyy9wLKsD1X1F8llqvrntm3fBcqwbt267QC+FHQc5I+qtpumuTe9POugDVn2IgD72Bn27t17NfcelSHb3gOYIkEsy3oTwPPJZSLyddu2W0GXhcPh7QjwwiAVzPPZ9h7AFAkCAKFQaJeInEsuE5Gn29vbZ/f4WoWxbfseEXkq6DjIt3Pj4+O7ppo4ZYKsX7/+E8/znk0rvrW2tvanQW9R0Do7O1eIyI8A1AYdC/m2a8OGDZ9MNXHageOi0eguAJ1pxS2u674Y9FYFKRQK/QTAzb4roqC5lmU9O90MM46sGAqFvg3g0+QyVf2GbdvfD3rrguC67m4A9wcdB/k2mEsXecYEWb9+/SeJJEkhIk/t27fvirqZ0XXdv1DVJ4OOg/xT1adM03x/pvlyfhGEbds7ROSZ9PKxsbGFra2tg7nWU65c172f746vDCLyM9M0H81l3pwHr45Go7tE5MX08urq6gsdHR0NudZTjmzbfpzJUTH25JocQB57kEmO4/wKwJfTyz3Pa47FYvuD3vpCc133BVV9NOg4yD9V/VU0Gr09n2Vm9a4tx3EuApiXJYAnotHoPwTdEIWSeAy5Oeg4qCAuWpa1IN+FZv0yOsdxUoYKSvL3lmX9ZdCt4Yfrul9W1X8EwLuYK0OvZVmzGu3S19saXdfdne2sjqp2eJ73ZEtLy/8G3TL5sm17o4g8B2Cp78poLnjfsqyvzHZh368zdRznaQDfzTLpMwBtQ0NDP9y0adPnATZQTrq6un7PMIxt4E2ZleQ1y7Ie8lNBQd736zjOYwB2A8jWx/tYRNpM0/xR6dtnZp2dnSsMw9gOYLuIRHxXSHOCqv5dNBr1fa9cwV6Ibdv2XSKyG1M/G3FQVX8YjUZfyqPaounq6poXCoW2JW5XL+jLNilQ/Z7nfScWi/2sEJUV9I3x7e3ti2tra58B8Ng0s9mGYbQ1NzcHcl1h7969V4fD4a9hYo/x+0HEQMUhIo6qfs+yrIMFq7MYge7fv39jPB7fAWC6pxCPi4jjeZ4djUZfKUYck9577726oaGh+wDcp6oPAKgv5vqo5H4L4FnLstoKXXFREmRS4vaUbQCmHTpIVfsT2f9qoZJl586dxrp16zaq6hZV/aqIXFXMbaVAjAB4zvO8XbFYrMd3bVkUNUEA4MCBA4vGx8cfAfAwgDtmml9V44mBg3sn/1S1F0CvYRinAfSaptmbeAvWssTbnJYlXlpz+U9VTRHhe+Ar06ci8tLY2NjL0z3LUQhFT5BkjuNsAbAVwGYANaVcN5U/VbUBvD48PPxyqS4dlDRBJrmu24iJJNmsqncHEQOVBxF5D8DeUCjU3tTUdLTk6w+6ARzHuUVEmlR1PYAmADxWoL0A7Hg8/k5izOjABJ4g6RzHuVdVG0SkARPXJxoALBGRiOd5CxMX83hBrzxdAjCY9HdeVU+ISI+InFDVE/PmzXvnzjvvHA46UCIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIqIK8v+29fNNeUoBkwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wNi0yM1QxOTo1MDowNyswODowMBBCfKkAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDYtMjNUMTk6NTA6MDcrMDg6MDBhH8QVAAAAAElFTkSuQmCC\" alt=\"\">\n        <img v-else src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAAEzlJREFUeNrt3Xt0VNW9B/Dv78wEkEd4RAFLQAtorVKETHhoJmCQ1scVTb2t1Wt7W7Gl7YIuq6simIkrbSY+8N6FvYL3FnuxtnK1rtuiBfGiVDSZ8MwkiI/KAopCpETeISBkMud3/5Bg5hGYzJnMzsTv5y/OnnP2/p1jfp7X3vsARERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERBlPTAcQbUJp5dSwbeWKSK5Ah6sgV1UuEGg2oP0BZAOSbTpOSspxAI0QNEK1USCHVFGvovWWuPaoan0/d3Plm2VFJ00H2sp4guSXrhuv0CLYOg2CIgC9TcdEpulKEVltu9yVtWWTt5qMxEiCTJhXNdJ2WcWwUAzVQpMHgLo2gaxT6Epxy4qasoJ3099+GuX5ArdawG0KFAPome6dpYz3GgTLe53CsuoF3mPpaLDTE2RcWd0Ad+j4d1VwJ4DJ6dgpMqYBqg0Q2aeQBhFtgI0GgTao2A2qVh+xZIiqDhWVIbAwRFWHCGQIgCEA+ibYzi4AzwmwrMbv3daZO9SpCZJfUjlXLWs2FCM6sx0y5hQgr4qE/1hTPuUFp5V5ytZdhlC4GCLFACYl0r4qnnYhvGBzxdQ9nbGDnZIg+b7AzQrMBVDQGfWTUY0QWa3AqmxX8wud9cRpnO+tS1xq3QDLuhGq1511ZcFusfF4TYV3UarjSGmCTJy/MafFal4gIjNTHSgZt0pFng+F7FfeebTwcDobPpMsIncDGNvuioLXAS0NlhduTFXbKUuQ/PmVBXBZTyiQ37mHi9JLq0V0USouoZy6pmxtr2MtPWZDMAeqF7ez2mER/XlNeeHvU9FmShIkv7Rqpqo8AaBfeg4VpcE7Ktai2vKrl5gOJNrYsurBWS06B8AcAAPjrSMiD9eUF5Q4bctxgnhKq8qh4kv/YaJO8hEEi3o0WYvXL7z6U9PBnE1e2brR0qJzoPYciLiif1fBn2rLvd9y0oajBPH4qp8A9B7Dx4lSRfFfbnWXbXx4coPpUDoir7TKIyqLEefJlwKba/3eicnWnXSCeHyBGgAe0weHUkOhc2r9hYtNx5GsgrmBfqd6YpkqZsT5eV/Q770wmXqTShCPr6oJkD6mDwqlRLOt8vW6ioJK04GkgscX+C2Au+P8dCzo93a4k6vV8QCqNzI5uo1gD7d1YXdJDgAI+r0/BGRenJ/6eXyBDR2tr0NnEE9J4BkIfmD6IFBKPBv0e39gOojO4imp8kKkKs5PLwb93u8kWk/CCZJfUjlXxXrM9I5TCqj+R7CisPs/XFEVT2m1HeeXhP/nkNAlVr4vcDOTo9vY+oVIDgAQUQEui/PL9/N8VbMTquJcK+T7Al9R4FUAXza9v+TYkaDfO9B5NZklr6Rqhoj8Jaq40ZKW6ZvLr9l8tm0TOYM8ASZHt6DQO0zHYEJtReEKQH8RVZwdVvfD59r2rAmS7wvcr8D1pneQUkHm1foL/890FKYE/YX/LioR3WYEmJ7nC9x/tu3aTZDTl1ZzTe8YpYDIU0F/wRf+HrJvVvM9AN5sWybA3IllVZe2t027CWKrzAVwvumdImcEsi5YXpDQDWl392ZZ0UkV/QWAcJvi88Mt0u6JIG6CeB6q/icR5ZiO7mGh6QC6ktrywiAg0QOr7s7zVd0Ub/24CaK2/V3TO0Ip8WKNv+B/TQfR1YTRshhAxKAvS2RWvHVjEiS/dN14gdxueifIMVXl2SOeLf6p2wFEnEVUMSPeWSQmQdQO8+zRHagurK3wdrjv0RdFyC2LIPJh2zJLEXMWiUgQT9na8yFyp+ngybE97lALzx5nsbWs4BPYGtG9X0Vm5D8YuKFtWUSCaIvrm/hsfiLKYAJ74cYFRfWm4+jq+mWFFgGInNrUJcVtFyMSRGAVn6tS6vJ21vin8OyRgM8e++KZtmWq+s2J8zfmtC6fSZDxpVVXArjRdNDkjELXmI4hk7jC8mZU0QVhCX2zdeFMgojy7NEdWGq9bjqGTLL54YItAOoiCl0obv2n+/NS29sFvoZwxuTRAzBp1ACMHZGNnL5ZyOmbBQA42BTCwaYQNu44jM1/P4q6jxo7XPesouExZUvWJjdzZSrrSoGQtpzgGaSjVNdCZPyZRVunT5y/MWfTI5MOugFg/PzKCwQy3XScOX2zcOO4wbjxygtwydD4o3pzB7mQO6gXrhzRD7OmAdv3Hceqt/dj1ZZPcLAplFhDAswqipwu2O2y8NSajzoetEhEktTsOgqYShDBquBjXz9qpvHMZbt0lWXLfa3LItKzxQpNB/BHCwAsl/k5dEcO7o3/vGsM7rnu4naTI55LhvbBPdddjP/+0Vh4vtw/6fZnTs3FN76W2V3PFNYq0zFkorpfTfkrgP1ty0T0WuDMPYgYvTkfk9sPL/5sPEYOTv7jUrmDeuE3M8c4ShJf8WhHMZgmVvgL2509BV6LWBK5FjhzD2J5ADUS1bCBvfC7H8efj3jJ2t3Ye/gU9hw8iY8OnIAqMDznPAzP6YXhg3ph1rTYryr8ZuYYfOPRTTh0PMHLrTZ693DBd8tozHza6Fe/kqRbg78s3G06ikwlkJcU+vlLcsXISWUbsk8niA5Psl7Hyv75kpiyoydacO9z72PrntiPCB2tP4Z36z8rrz98EvNmjELvHpGzTvqKR+O+ZX9LKp6xI/rhwZtH4eG/7DR1SJIikB2mY8hk4XD4fcsV2fOqGci1Rv9sVU8AF5gI6o6rvoTxF0XO5bV933Fc+8jGuMkRbdWW/ZhSvgHv1UeuO+WyQbjjqi8lHMf+Y80Ry7dOGIrbJiU1EZ8xCmTUdKFdTTj0acwTGmluHm71798/10RAg/pk4farYv8If/rMex2uK942t191IQb1yUpo+90HYudonnvTSEf3M+mmUCaIA1v/7brjgB5oW2ZZVq4lsI1cXk27IgfDBvaKKFuydg+OnOj4vcOJ5jDK/rw9omzYwF64YVziJ8Z4j3hLbhmFgQkmmWkWXPtMx5D5JOKPwIYOtwDp8HylqTD1skERyxt2HMGSN5K/x1xZ9wk27DgSUVb01ZyEt1/6Vj1efTviSR9G5JyH+TNGmTg8HcZLLOcEiEgQEaufZUv6EyTLJbjqksjpmd772PlXfbfvOx6xPO6ibGS5Eu8d8NjKndj2j8g6pl2RE/dteVdjCy+xnLKjEgTQbEuAtCfIoL49Ysre/7jJcb0f7I2tI15b7Wk6GcZjK3ciFI585D1r2ghMv6Jrv0R0h2wmiEMCjUwQRbaRS6zWflVtpSJB3t4dexaK19bZbN19DI+tiH3E++Ato7r0S8RTLSeYII65Ii+xgOwOf/6gK7M1NS87Xwo2YNm6vRFl2ee5MW/GSNO7SGlmAdrx7rAOxetUePmwvo7rHZMb+w3RhDswRln46i6s334koizv4v544KaumSQ93b05EtSx8EVtlxRotBRIe4IcamqOKUtFgnzlwthOjvHaStQjK3ai4eipiLJvT7oQ3544NA1HqWNasiwmiEMKiUgQCBotS9N/BgmFFeu3R36L/ophzr8gPWpI5D3Clo8aY264O2Lv4ZN4dMXfY8ofmDEKnou71ktES4UJ4pAFRCYIpNHIJRYAvPXBoYjlyaMHOHqc6r10IK6Jeu+x9m8HHcdZte0Qnnztw5jyB2aMxIAu9BJRONmGYxqVIKr2MUthGRnd88b7B7H38MmIslnTRmBA747/0bktweP/8tWIsr2HT8a8+EvWs1Uf45Utn0SUjRzcG/O60P2IjXDXu+7LOBqRIBZkj3X06FEj08Mcagrh+fX/iCl/6q4rOlzXkh9+LeaF4PPr/4FDSd6gx1Px8k78Leo9y/QxXefdiICXWE6M/cXqPoBE/Ae1bbve2vHkjacQNZoqXZ5fvzdmTPmlQ/tgzfyJGDv83Pcklw7tg1X3T4hZt/KDQ3h+/d5zbt8RzS02Kl7eiVOhyE/edZW37LzEcsaVdd5FMWUuq751RKGxWQZ+GdXJEAAG9M7C0lljMatoOIouz8H5/SLfhn9pQE9ce0UOfvujr2Fwduybcv/LnTM04oO9TV12nIgCmdFprItyuXB5dJnlcu85PWDKDgKSZyKw+kMncdeSrXhmVuyowrYjBusPncSnzWEMG9gLvXu62q3vJ0vfTemlVbRXtnyCkYN74/uFw0wcrrO5ctLctbmcUTE5Citq4mrdtbFscuPpM4jL6GD/d/Ycw3eerMOu/SfaXSd3UC9cMrRPu8nx8aGT+MnSdz+bVaSTPfnahwhsO+y8ohQL9ejxddMxZC79RlTBGuD0pA12uKXadHg7PzmBnyx9D79e/SF2NBxPeLvtDcfx69UfYubT76QlOVr5X96BvYdPOa8ohSzodaZjyEQTStZdDUjE6D1V+StwetKGukem7M/zBdYIYHRurINNzfhD4GP8IfDxmYnjrhyRjUFRE8cdagphw84j2Pz3I6j7MLnXOEGHyXTgWDP8L+/A3VONDMiMS6HXe2bVZAWX5HfeNWY3FJbwjdJ20kTVU267xxogYmZFCQBqfPK4Vht2HIkZAJUqS97YA8D5c4lNO49g087OiTE50l+HNk8DsNp0JJlExJqCth1dLWvNpopJB4E2c/Oq2C+ZDpScs2z7WtMxZJKJvre+DNXCyFJ9qfVfZxKkrrzwbQCcmS/DKTDNdAyZpAXWNVFF+10tWctbFyI/oAOeRboBT76v8lumg8gYKt9uuygiyzc9MulMJ77ID+i4w8vBwf8Zz4brXtMxZIL80urbRRDxyTWEP7+8AqISJFhWdACqy0wHTs4I9Oq80sDPTcfR1anqnLbLorqi5mHvq23LYobciuV6znTg5Jyo3ndlybou97q/q8j3BX4MRH7VwBYsiV4vJkFqyq+uU+gLpneAnJLhboTvc15P9+MpW9FbFZFnD8GKWn/hyuh1407aIJbFs0h3IHJvXklgsukwuhptGTgbgjFty2zVJfHWjZsgwV8VvKIqS03vCDkmIuANexsTyjYNFYk8e6jq0nhnD6CdBAEAS3QBgAOgTHdbXglv2FvZoeZyKNp+WOaAJbKgvfXbTZAav3ebqP246R0i50SwMM8XuNV0HKbl+QL3Q/DDtmUKLKjxe7e1t805J67N9wVeVeB60ztHDgn2NYf08nceLex6/fTTIN8XuFmBl9uWKbCm1u896xCBRGZW/DmAXaZ3kBxSDO3htr6Q3zD0lK27LDo5ADS6RB4817bnTJAav3ebfJYklPF0Yp6v+vemo0irMrXQYtfFHAnog5vLCzafa/OE5uat8Xv/Imo/YHpfyTmBfi+/pOoO03GkiycUeAdAr6jiZ2v9hYsT2T7xj2cA8JQEnoHgB6Z3mlJAcGew3Ps/psPoTJ7SwHvQmMkYXgz6vd9JtI4OJQgA5PkCmwSYYHrnKSUeCfq957wOzzQTHqweZ1saABA1WbNuCvoLJ3Wkrg4nCAB4fIGm2MYpM+nKsLvle1vKio6YjiQV8nxVswWyKM5PTUG/t8MTQCf1fZCg39sXkFrTB4NSQW5yhbPqPCVVXtOROJVfWl3RTnI0JJMcQJIJAgBBf4EHkF+bPiiUAoqLIVLl8VX+1HQoSRKPL/BbVY25XFRgc9DvTXre4qQusdrylFaXQ9Vn+ghRiqguV7EW1foL3jAdSiLyS6v+VdWaDejEmF0R/Km23OtodKXjBDkd5ExVeQKA8498UNcg8jvb1sV1Fd4a06HEk/9Q9S22rXPOMlVVSh5ApCRBACC/tLIAaj2hQH7ajhJ1Nhuqi8KStWiLf/J259U553koUCQ25ijQXt+yw1DcG6zwPpuK9lKWIADgKVt7voZ6PCaiMzv9SFE6HYbo4rDav9/in2okUTylVZNErR8r9K52VxK8rjYeqq3wbkhVuylNkFanO4bNRdSQRuoGFK9DdLUL9p83+ad2ah+9z5ICNyhwAyAT211RsFtsPF5T4V3UgeoT0ikJ0iq/pHKuWtbsqP731E0IUGkrlruyQi9sLival4o680oCk0W0GGLdAtXLzrH6KVU87UJ4weaKqZ3yCY9OTRAAGFdWN8AdOv5dFdwJgMM/u683BWhQ1QaFNliWe5+KNthhbXAj3HA8q6HBdfLCPllue6gFa4itMkQsGSLQIbatQwUyBAIPgEQeye4C8JwAy842liMVOj1B2srzBW61gNsUKAbQM51tU7fwGgTLe53CsuoF3mPpaDCtCdJqwryqkbbLKoaF4th5UYk+J5B1Cl0pbllRU1bwbvrbNyy/dN14hRbB1mkQFAHo7bhSynCyEsBrttiVp+eMNheJ6UMRLd9XdY0tVq5AcmGHh6tIrgVcoEA2oP0ByQaQbTpOSspxAI0AGhVoFNVDEKlXyB4LWm/bdn3PT92V6xde/anpQImIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi6kf8H0v8psCtpcmoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDYtMjNUMTk6NTA6MDcrMDg6MDAQQnypAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA2LTIzVDE5OjUwOjA3KzA4OjAwYR/EFQAAAABJRU5ErkJggg==\" alt=\"\" srcset=\"\">\n      </div>\n      <span class=\"dokit-interface-title-arrow\">\n        <span v-if=\"!showContent\">▸</span>\n        <span v-else>▾</span>\n      </span>\n    </div>\n    <div class=\"dokit-interface-content\" v-show=\"showContent\">\n      <div class=\"dokit-interface-sub-title\">接口信息</div>\n      <div class=\"dokit-interface-info\">\n        <DoRow class=\"dokit-interface-info-item\">\n          <DoCol :span=8>请求路径</DoCol>\n          <DoCol :span=16>{{interfaceItem.path}}</DoCol>\n        </DoRow>\n        <DoRow class=\"dokit-interface-info-item\">\n          <DoCol :span=8>query</DoCol>\n          <DoCol :span=16>{{interfaceItem.query}}</DoCol>\n        </DoRow>\n        <DoRow class=\"dokit-interface-info-item\">\n          <DoCol :span=8>body</DoCol>\n          <DoCol :span=16>{{interfaceItem.body}}</DoCol>\n        </DoRow>\n        <DoRow class=\"dokit-interface-info-item\">\n          <DoCol :span=8>分类</DoCol>\n          <DoCol :span=16>{{interfaceItem.categoryName}}</DoCol>\n        </DoRow>\n        <DoRow class=\"dokit-interface-info-item\">\n          <DoCol :span=8>创建人</DoCol>\n          <DoCol :span=16>{{interfaceItem.owner.name}}</DoCol>\n        </DoRow>\n      </div>\n      <div class=\"dokit-scene-list\">\n        <div class=\"dokit-interface-sub-title\">场景选择</div>\n        <div class=\"dokit-scene-item\" :class=\"item.checked ? 'dokit-actived-scene': ''\" v-for=\"(item, index) in interfaceItem.sceneList\" :key=\"item._id\" @click=\"setScene(index)\">\n          <span class=\"dokit-scene-checkbox\"></span>\n          {{item.name}}\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n<script>\nexport default {\n  props: {\n    interfaceIndex: [Number],\n    interfaceItem: [Object]\n  },\n  data() {\n    return {\n      showContent: false\n    }\n  },\n  methods: {\n    toggleInterfaceSwitch() {\n      this.$emit('toggleInterfaceSwitch', {\n        interfaceIndex: this.interfaceIndex\n      })\n    },\n    setScene(index) {\n      this.$emit('setScene', {\n        interfaceIndex: this.interfaceIndex,\n        sceneIndex: index\n      })\n    }\n  },\n}\n</script>\n<style lang=\"less\">\n.dokit-interface-item{\n  margin-bottom: 20px;\n  border: 1px solid #d6e4ef;\n  border-radius: 5px;\n  overflow: hidden;\n  .dokit-interface-title{\n    padding: 0 8px;\n    font-size: 18px;\n    color: #2c405a;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    border-bottom: 1px solid #d6e4ef;\n    img{\n      width: 40px;\n    }\n    .dokit-interface-title-opt{\n      flex: 1;\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      font-style: normal;\n    }\n    .dokit-interface-title-text{\n      flex: 10;\n    }\n    .dokit-interface-title-arrow{\n      flex: 1;\n      text-align: right;\n    }\n  }\n  .dokit-interface-sub-title{\n    text-align: center;\n    font-weight: bold;\n  }\n  .dokit-interface-content{\n    .dokit-interface-info{\n      font-size: 14px;\n      .dokit-interface-info-item{\n        padding: 5px 0;\n      }\n    }\n    padding: 0 10px;\n    .dokit-scene-list{\n      border-top: 1px solid #eee;\n      .dokit-scene-item{\n        margin: 5px 0;\n        padding: 0 10px;\n        border-radius: 5px;\n        border: 1px solid #fff;\n        background: #cccccc;\n        color: white;\n        height: 30px;\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n        .dokit-scene-checkbox{\n          display: inline-block;\n          box-sizing: border-box;\n          line-height: 30px;\n          width: 20px;\n          height: 20px;\n          border-radius: 10px;\n          background: #eee;\n          border: 5px solid #fff;\n          font-size: 18px;\n        }\n      }\n      .dokit-actived-scene {\n        background: #337CC4;\n        \n          .dokit-scene-checkbox{\n            background: #337CC4;\n          }\n      }\n    }\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/api-mock/main.vue",
    "content": "<template>\n  <div class=\"dokit-api-mock-plugin\">\n    <interfaceItem\n      v-for=\"(interfaceItem, index) in interfaceList\"\n      :key=\"interfaceItem._id\"\n      :interfaceItem=\"interfaceItem\"\n      :interfaceIndex=\"index\"\n      @toggleInterfaceSwitch=\"toggleInterfaceSwitch\"\n      @setScene=\"setScene\"\n    ></interfaceItem>\n  </div>\n</template>\n<script>\nimport interfaceItem from \"./interface-item\";\n\nexport default {\n  components: {\n    interfaceItem,\n  },\n  data() {\n    return {\n      interfaceList: this.$store.state.interfaceList\n    }\n  },\n  created() {\n    localStorage.setItem('dokit-interface-list', JSON.stringify(this.$store.state.interfaceList || []))\n  },\n  methods: {\n    toggleInterfaceSwitch(info) {\n      this.$store.state.interfaceList[info.interfaceIndex].checked = !this.interfaceList[\n        info.interfaceIndex\n      ].checked;\n\n      localStorage.setItem('dokit-interface-list', JSON.stringify(this.$store.state.interfaceList))\n    },\n    setScene(info) {\n      this.$store.state.interfaceList[info.interfaceIndex].sceneList.forEach((e, index) => {\n        e.checked = false;\n        if (index === info.sceneIndex) {\n          e.checked = true;\n        }\n      });\n      localStorage.setItem('dokit-interface-list', JSON.stringify(this.$store.state.interfaceList))\n    },\n  },\n};\n</script>\n<style>\n.dokit-api-mock-plugin {\n  padding: 5px;\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/app-info/ToolAppInfo.vue",
    "content": "<template>\n  <div class=\"dokit-app-info-container\">\n    <div class=\"dokit-info-wrapper\">\n      <Card title=\"Page Info\">\n        <template v-slot:body>\n          <DoRow class=\"dokit-app-info-item\">\n            <DoCol :span=8>UA</DoCol>\n            <DoCol :span=16>{{ua}}</DoCol>\n          </DoRow>\n          <DoRow class=\"dokit-app-info-item\">\n            <DoCol :span=8>URL</DoCol>\n            <DoCol :span=16>{{url}}</DoCol>\n          </DoRow>\n        </template>\n        \n      </Card>\n    </div>\n    <div class=\"dokit-info-wrapper\" style=\"margin-top:20px\">\n      <Card title=\"Device Info\">\n        <template v-slot:body>\n          <DoRow class=\"dokit-app-info-item\">\n            <DoCol :span=8>设备缩放比</DoCol>\n            <DoCol :span=16>{{ratio}}</DoCol>\n          </DoRow>\n          <DoRow class=\"dokit-app-info-item\">\n            <DoCol :span=8>screen</DoCol>\n            <DoCol :span=16>{{screen.width}} X {{screen.height}}</DoCol>\n          </DoRow>\n          <DoRow class=\"dokit-app-info-item\">\n            <DoCol :span=8>viewport</DoCol>\n            <DoCol :span=16>{{viewport.width}} X {{viewport.height}}</DoCol>\n          </DoRow>\n        </template>\n      </Card>\n    </div>\n  </div>\n</template>\n<script>\nimport Card from '@common/Card'\n\nexport default {\n  components: {\n    Card\n  },\n  data() {\n    return {\n      ua: window.navigator.userAgent,\n      url: window.location.href,\n      ratio: window.devicePixelRatio,\n      screen: window.screen,\n      viewport: {\n        width: document.documentElement.clientWidth,\n        height: document.documentElement.clientHeight\n      }\n    }\n  },\n}\n</script>\n<style lang=\"less\" scoped>\n.dokit-app-info-container{\n  font-size: 14px;\n  height: 100%;\n  overflow: hidden;\n}\n.dokit-info-wrapper{\n    margin: 5px 5px 0 5px;\n    .key{\n      font-weight: bold;\n    }\n    .dokit-app-info-item{\n      padding: 5px 0;\n    }\n  }\n  table{\n    border-color: #eee;\n    width: 100%;\n    border-collapse: collapse;\n    border-spacing: 0;\n  }\n  tr{\n    width: 100%;\n  }\n  td,th{\n    padding: 5px;\n  }\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/app-info/index.js",
    "content": "import AppInfo from './ToolAppInfo.vue'\nimport {RouterPlugin} from '@dokit/web-core'\n\nexport default new RouterPlugin({\n  nameZh: '应用信息',\n  name: 'app-info',\n  icon: 'https://pt-starimg.didistatic.com/static/starimg/img/z1346TQD531618997547642.png',\n  component: AppInfo\n})\n\n"
  },
  {
    "path": "Web/packages/web/src/plugins/console/console-tap.vue",
    "content": "<template>\n  <div class=\"dokit-tab-container\">\n    <div class=\"dokit-tab-list\">\n      <div\n        class=\"dokit-tab-item\"\n        :class=\"curIndex === index? 'dokit-tab-active': 'dokit-tab-default'\"\n        v-for=\"(item, index) in tabs\"\n        :key=\"index\"\n        @click=\"handleClickTab(item, index)\"\n      >\n        <span class=\"dokit-tab-item-text\">{{ item.name }}</span>\n      </div>\n    </div>\n  </div>\n</template>\n<style lang=\"less\" scoped>\n@import \"./css/var.less\";\n.dokit-tab-container{\n  .dokit-tab-list{\n    display: flex;\n    height: @tap-height;\n    justify-content: space-between;\n    align-items: center;\n    border: 1px solid @border-color;\n  }\n  .dokit-tab-item{\n    flex: 1;\n    height: @tap-height;\n    line-height: @tap-height;\n    text-align: center;\n  }\n  .dokit-tab-item-text{\n    font-size: @tap-font-size;\n    color: @tap-font-color;\n  }\n  .dokit-tab-active{\n    border-bottom: 1px solid @border-active-color;\n  }\n  .dokit-tab-default{\n    border: none;\n  }\n}\n</style>\n<script>\nexport default {\n  props: {\n    tabs: {\n      type: Array\n    },\n  },\n  data() {\n    return {\n      curIndex: 0\n    };\n  },\n  methods: {\n    handleClickTab(item, index) {\n      let { type } = item;\n      this.curIndex = index;\n      this.$emit(\"changeTap\", type);\n    },\n  },\n};\n</script>"
  },
  {
    "path": "Web/packages/web/src/plugins/console/css/var.less",
    "content": "\n// common\n@border-color:#f5f6f7;\n@border-active-color:#1485ee;\n@background-color: #ffffff;\n\n// console-tap\n@tap-height: 38px;\n@tap-font-size: 16px;\n@tap-font-color: #333333;\n\n// error\n\n"
  },
  {
    "path": "Web/packages/web/src/plugins/console/index.js",
    "content": "import Console from './main.vue'\nimport {overrideConsole,restoreConsole} from './js/console'\nimport {getGlobalData, RouterPlugin} from '@dokit/web-core'\n\nexport default new RouterPlugin({\n  name: 'console',\n  nameZh: '日志',\n  component: Console,\n  icon: 'https://pt-starimg.didistatic.com/static/starimg/img/PbNXVyzTbq1618997544543.png',\n  onLoad(){\n    overrideConsole(({name, type, value}) => {\n      let state = getGlobalData();\n      state.logList = state.logList || [];\n      state.logList.push({\n        type: type,\n        name: name,\n        value: value\n      });\n    });\n  },\n  onUnload(){\n    restoreConsole()\n  }\n})"
  },
  {
    "path": "Web/packages/web/src/plugins/console/js/console.js",
    "content": "export const LogMap = {\n  0: 'All',\n  1: 'Log',\n  2: 'Info',\n  3: 'Warn',\n  4: 'Error'\n}\nexport const LogEnum = {\n  ALL: 0,\n  LOG: 1,\n  INFO: 2,\n  WARN: 3,\n  ERROR: 4\n}\n\nexport const ConsoleLogMap = {\n  'log': LogEnum.LOG,\n  'info': LogEnum.INFO,\n  'warn': LogEnum.WARN,\n  'error': LogEnum.ERROR\n}\n\nexport const CONSOLE_METHODS = [\"log\", \"info\", 'warn', 'error']\n\nexport const LogTabs = Object.keys(LogMap).map(key => {\n  return {\n    type: parseInt(key),\n    name: LogMap[key]\n  }\n})\n\nexport const excuteScript = function(command){\n  let ret \n  try{\n    ret = eval.call(window, `(${command})`)\n  }catch(e){\n    ret = eval.call(window, command)\n  }\n  return ret\n}\n\nexport const origConsole = {}\nexport const noop = () => {}\nexport const overrideConsole = function(callback) {\n  const winConsole = window.console\n  CONSOLE_METHODS.forEach((name) => {\n    let origin = (origConsole[name] = noop)\n    if (winConsole[name]) {\n      origin = origConsole[name] = winConsole[name].bind(winConsole)\n    }\n\n    winConsole[name] = (...args) => {\n      callback({\n        name: name,\n        type: ConsoleLogMap[name],\n        value: args\n      })\n      origin(...args)\n    }\n  })\n}\n\nexport const restoreConsole = function(){\n  const winConsole = window.console\n  CONSOLE_METHODS.forEach((name) => {\n    winConsole[name] = origConsole[name]\n  })\n}\n"
  },
  {
    "path": "Web/packages/web/src/plugins/console/log-container.vue",
    "content": "<template>\n  <div class=\"dokit-log-container\">\n    <log-item\n      v-for=\"(log, index) in logList\"\n      :key=\"index\"\n      :value=\"log.value\"\n      :type=\"log.type\"\n      :logType=\"log.name\"\n    ></log-item>\n  </div>\n</template>\n<style lang=\"less\" scoped>\n@import \"./css/var.less\";\n.dokit-tab-container {\n  .dokit-tab-list {\n    display: flex;\n    height: @tap-height;\n    justify-content: space-between;\n    align-items: center;\n    border: 1px solid @border-color;\n  }\n  .dokit-tab-item {\n    flex: 1;\n    height: @tap-height;\n    line-height: @tap-height;\n    text-align: center;\n  }\n  .dokit-tab-item-text {\n    font-size: @tap-font-size;\n    color: @tap-font-color;\n  }\n  .dokit-tab-active {\n    border-bottom: 1px solid #1485ee;\n  }\n  .dokit-tab-default {\n    border: none;\n  }\n}\n</style>\n<script>\nimport LogItem from './log-item'\nexport default {\n  components: {\n    LogItem\n  },\n  props: {\n    logList: {\n      type: Array,\n      default: [],\n    },\n  },\n  data() {\n    return {};\n  }\n};\n</script>"
  },
  {
    "path": "Web/packages/web/src/plugins/console/log-detail.vue",
    "content": "<template>\n  <div\n    class=\"dokit-detail-container\"\n    :class=\"[canFold ? 'dokit-can-unfold' : '', unfold ? 'dokit-unfolded' : '']\"\n  >\n    <div @click=\"unfoldDetail\" v-html=\"displayDetailValue\"></div>\n    <template v-if=\"canFold\">\n      <div v-show=\"unfold\" v-for=\"(key, index) in newDetailValue\" :key=\"index\">\n        <Detail :detailValue=\"key\" :detailIndex=\"index\"></Detail>\n      </div>\n    </template>\n  </div>\n</template>\n<script>\nimport Detail from \"./log-detail\";\nimport { clone } from '@dokit/web-utils'\nimport { getDataType } from '../../assets/util'\nexport default {\n  name: \"Detail\",\n  components: {\n    Detail,\n  },\n  props: {\n    detailValue: [String, Number, Object],\n    detailIndex: [String, Number],\n  },\n  data() {\n    return {\n      unfold: false,\n      newDetailValue:null,\n    };\n  },\n  computed: {\n    dataType () {\n     return getDataType(this.newDetailValue)\n    },\n    canFold() {\n      if ((this.isObject(this.newDetailValue)) || this.isArray(this.newDetailValue)) {\n        return true;\n      }\n      return false;\n    },\n    displayDetailValue() {\n      let value = \"\";\n      if (this.canFold) {\n        if (this.isObject(this.newDetailValue)) {\n          if(this.dataType !== 'Function'){\n            value = \"Object\";\n          } else{\n            value = \"Function\";\n          }\n        }\n        if (this.isArray(this.newDetailValue)) {\n          value = `Array(${this.newDetailValue.length})`;\n        }\n      } else {\n        value = `<span style=\"color:#1802C7;\">${this.newDetailValue}</span>`;\n      }\n      return `<span style=\"color:#7D208C;\">${this.detailIndex}</span>: ${value}`;\n    },\n  },\n  watch: {\n    detailValue: {\n      immediate: true,\n      handler(newVal, oldVal) {\n        this.newDetailValue = clone(newVal)\n      },\n    },\n  },\n  methods: {\n    unfoldDetail() {\n      this.unfold = !this.unfold;\n    },\n    isObject(val) {\n      return Object.prototype.isPrototypeOf(val);\n    },\n    isArray(val) {\n      return Array.prototype.isPrototypeOf(val);\n    },\n  },\n};\n</script>\n<style lang=\"less\" scoped>\n.dokit-detail-container {\n  font-size: 12px;\n  margin-left: 24px;\n  position: relative;\n  white-space: pre-wrap;\n  word-wrap: break-word;\n  max-width: 100%;\n}\n.dokit-can-unfold {\n  &::before {\n    content: \"\";\n    width: 0;\n    height: 0;\n    border: 4px solid transparent;\n    position: absolute;\n    border-left-color: #333;\n    left: -12px;\n    top: 3px;\n  }\n}\n\n.dokit-unfolded {\n  &::before {\n    border: 4px solid transparent;\n    border-top-color: #333;\n    top: 6px;\n  }\n}\n</style>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Web/packages/web/src/plugins/console/log-item.vue",
    "content": "<template>\n  <div class=\"dokit-log-ltem\" :class=\"logType\">\n    <div class=\"dokit-log-preview\" v-html=\"logPreview\" @click=\"toggleDetail\"></div>\n    <div v-if=\"canShowDetail\">\n      <div class=\"dokit-list-item\" v-for=\"(key, index) in value\" :key=\"index\">\n        <Detail :detailValue=\"key\" :detailIndex=\"index\"></Detail>\n      </div>\n    </div>\n  </div>\n</template>\n<script>\nimport { getDataType, getDataStructureStr } from '../../assets/util'\nimport Detail from './log-detail'\n\nconst DATATYPE_NOT_DISPLAY = ['Number', 'String', 'Boolean', 'Undefined', 'Null']\nexport default {\n  components: {\n    Detail\n  },\n  props: {\n    type: [Number],\n    value: [String, Number, Object],\n    logType: [String]\n  },\n  data () {\n    return {\n      showDetail: false\n    }\n  },\n  computed: {\n    logPreview () {\n      let dataType = ''\n      let func = null\n      let html = `<div>`\n      if (this.logType === 'log' || this.logType === 'info') {\n        func = arg => {\n          dataType = getDataType(arg)\n          if (DATATYPE_NOT_DISPLAY.indexOf(dataType) === -1) {\n            html += `<span class=\"data-type\">${dataType}</span>`\n          }\n          html += `<span class=\"data-structure\">${getDataStructureStr(arg, true)}</span>`\n        }\n        // this.value.forEach(arg => {\n        //   dataType = getDataType(arg)\n        //   if (DATATYPE_NOT_DISPLAY.indexOf(dataType) === -1) {\n        //     html += `<span class=\"data-type\">${dataType}</span>`\n        //   }\n        //   html += `<span class=\"data-structure\">${getDataStructureStr(arg, true)}</span>`\n        // });\n      } else if (this.logType === 'error' || this.logType === 'warn') {\n        func = arg => {\n          if (arg.stack) {\n            html += `<span style=\"white-space: pre-wrap;\">${arg.stack}</span>`\n          } else {\n            dataType = getDataType(arg)\n            if (DATATYPE_NOT_DISPLAY.indexOf(dataType) === -1) {\n              html += `<span class=\"data-type\">${dataType}</span>`\n            }\n            html += `<span class=\"data-structure\">${getDataStructureStr(arg, true)}</span>`\n          }\n        }\n      } else {\n        \n      }\n      \n      this.value.forEach(func);\n\n      html += `</div>`\n      return html\n    },\n    canShowDetail () {\n      return this.showDetail \n        && typeof this.value === 'object'\n        && !this.value.stack\n    }\n  },\n  methods: {\n    toggleDetail () {\n      this.showDetail = !this.showDetail\n    }\n  }\n}\n</script>\n<style lang=\"less\" scoped>\n  .dokit-log-ltem{\n    padding: 5px;\n    padding-left: 20px;\n    border-top: 1px solid #eee;\n    text-align: left;\n    font-size: 12px;\n  }\n  .log{\n    \n  }\n  .info{\n    background-color: #ECF1F7;\n    position: relative;\n    &::before{\n      content:\"\";\n      background:url(\"https://pt-starimg.didistatic.com/static/starimg/img/M3nz7HYPH21621412737959.png\") no-repeat;\n      background-size: 10px 10px;\n      width: 10px;\n      height: 10px;\n      position: absolute;\n      top: 7px;\n      left: 8px;\n    }\n  }\n  .warn{\n    background-color: #FFFBE4;\n    color: #5C3C01;\n    position: relative;\n    &::before{\n      content:\"\";\n      background:url(\"https://pt-starimg.didistatic.com/static/starimg/img/39hzJzObhZ1621411397522.png\") no-repeat;\n      background-size: 10px 10px;\n      width: 10px;\n      height: 10px;\n      position: absolute;\n      top: 7px;\n      left: 8px;\n    }\n  }\n  .error{\n    background-color: #FEF0F0;\n    color: #FF161A;\n    position: relative;\n    &::before{\n      content:\"\";\n      background:url(\"https://pt-starimg.didistatic.com/static/starimg/img/z6EndYs29d1621411397532.png\") no-repeat;\n      background-size: 10px 10px;\n      width: 10px;\n      height: 10px;\n      position: absolute;\n      top: 7px;\n      left: 8px;\n    }\n  }\n  .log-ltem:first-child {\n    border: none;\n  }\n  .dokit-log-preview{\n    & >>> .data-type{\n      margin-left: 5px;\n      margin-right: 5px;\n      font-style: italic;\n      font-weight: bold;\n      color: #aaa;\n    }\n    & >>> .data-structure{\n      display: inline-block;\n      max-width: 100%;\n      font-style: italic;\n      white-space: pre-wrap;\n      word-wrap: break-word;\n      overflow: hidden;\n    }\n  }\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/console/main.vue",
    "content": "<template>\n  <div class=\"dokit-console-container\">\n    <console-tap :tabs=\"logTabs\" @changeTap=\"handleChangeTab\"></console-tap>\n    <div class=\"dokit-log-container\">\n      <div class=\"dokit-info-container\">\n        <log-container :logList=\"curLogList\"></log-container>\n      </div>\n      <div class=\"dokit-operation-container\">\n        <operation-command></operation-command>\n      </div>\n    </div>\n  </div>\n</template>\n<script>\nimport ConsoleTap from './console-tap';\nimport LogContainer from './log-container';\nimport OperationCommand from './op-command';\nimport {LogTabs, LogEnum} from './js/console'\nexport default {\n  components: {\n    ConsoleTap,\n    LogContainer,\n    OperationCommand\n  },\n  data() {\n    return {\n      logTabs: LogTabs,\n      curTab: LogEnum.ALL\n    }\n  },\n  computed:{\n    logList(){\n      return this.$store.state.logList || []\n    },\n    curLogList(){\n      if(this.curTab == LogEnum.ALL){\n        return this.logList\n      }\n      return this.logList.filter(log => {\n        return log.type == this.curTab\n      })\n    }\n  },\n  created () {},\n  methods: {\n    handleChangeTab(type){\n      this.curTab = type\n    }\n  }\n}\n</script>\n<style lang=\"less\" scoped>\n@import \"./css/var.less\";\n\n.dokit-console-container{\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n}\n.dokit-log-container{\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n  .dokit-info-container{\n    flex: 1;\n    background-color: @background-color;\n    border-bottom: 1px solid @border-color;\n    overflow-y: scroll;\n  } \n}\n\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/console/op-command.vue",
    "content": "<template>\n  <div class=\"dokit-operation\">\n    <div class=\"dokit-input-wrapper\">\n      <input class=\"dokit-input\" placeholder=\"Command……\" v-model=\"command\" />\n    </div>\n    <div class=\"dokit-button-wrapper\" @click=\"excuteCommand\">\n      <span>Excute</span>\n    </div>\n  </div>\n</template>\n<style lang=\"less\" scoped>\n@import \"./css/var.less\";\n.dokit-operation {\n  height: 50px;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n.dokit-input-wrapper {\n  flex: 1;\n  height: 100%;\n  .dokit-input {\n    height: 100%;\n    width: 100%;\n    outline: none;\n    border: none;\n    line-height: 100%;\n    padding: 0 10px;\n    font-size: 18px;\n  }\n}\n.dokit-button-wrapper {\n  height: 100%;\n  line-height: 100%;\n  margin-left: 10px;\n  padding: 0 10px;\n  border-left: 1px solid @border-color;\n  display: flex;\n  align-items: center;\n  font-size: 18px;\n}\n</style>\n<script>\nimport {excuteScript} from './js/console'\nexport default {\n  data(){\n    return {\n      command: \"\"\n    }\n  },\n  methods: {\n    excuteCommand(){\n      if(!this.command){\n        return\n      }\n      try {\n        let ret = excuteScript(this.command)\n        console.log(ret)\n      } catch (error) {\n        console.error(error)\n      }\n      this.command = ''\n    }\n  }\n};\n</script>"
  },
  {
    "path": "Web/packages/web/src/plugins/demo-plugin/ToolHelloWorld.vue",
    "content": "<template>\n  <div class=\"dokit-hello-world\">\n    <div style=\"font-weight:bold;font-size:30px;font-style:italic;\">Hello Dokit</div>\n    <div>Demo Plugin</div>\n  </div>\n</template>\n<script>\nexport default {\n  \n}\n</script>\n<style>\n.dokit-hello-world{\n  padding:10px;\n  text-align: center;\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/demo-plugin/index.js",
    "content": "import HelloWorld from './ToolHelloWorld.vue'\nimport {RouterPlugin} from '@dokit/web-core'\n\nexport default new RouterPlugin({\n  nameZh: '测试',\n  name: 'test',\n  icon: 'https://pt-starimg.didistatic.com/static/starimg/img/6WONqJCVks1621926657356.png',\n  component: HelloWorld\n})\n\n"
  },
  {
    "path": "Web/packages/web/src/plugins/demo-single-plugin/FPS.vue",
    "content": "<template>\n  <div class=\"hello-independ\" v-dragable>\n    <div style=\"font-weight: bold; font-size: 30px; font-style: italic\">\n      实时帧率展示\n    </div>\n    <div>平均FPS: {{fps}}</div>\n    <div>最小FPS: {{minFps}}</div>\n    <div>最大FPS: {{maxFps}}</div>\n    <div\n      @click=\"remove\"\n      style=\"background-color: red; color: white; margin-top: 10px\"\n    >\n      点击移除当前独立插件\n    </div>\n  </div>\n</template>\n<script>\nimport { dragable } from \"@dokit/web-utils\";\nimport { removeIndependPlugin } from \"@dokit/web-core\";\n\nexport default {\n  directives: {\n    dragable,\n  },\n  data() {\n    return {\n      fps: 0,\n      minFps: 60,\n      maxFps: 60\n    }\n  },\n  mounted() {\n    console.log('moounted')\n    this.showFPS()\n  },\n  methods: {\n    remove() {\n      removeIndependPlugin(\"test\");\n    },\n    showFPS() {\n      this.fps = 60\n      var rAF = (function () {\n        return (\n          window.requestAnimationFrame ||\n          window.webkitRequestAnimationFrame ||\n          function (callback) {\n            window.setTimeout(callback, 1000 / 60);\n          }\n        );\n      })();\n      var frame = 0;\n      var allFrameCount = 0;\n      var lastTime = Date.now();\n      var lastFameTime = Date.now();\n      var loop = () => {\n        var now = Date.now();\n        var fs = now - lastFameTime;\n        var fps = Math.round(1000 / fs);\n\n        lastFameTime = now;\n        // 不置 0，在动画的开头及结尾记录此值的差值算出 FPS\n        allFrameCount++;\n        frame++;\n\n        if (now > 1000 + lastTime) {\n          var fps = Math.round((frame * 1000) / (now - lastTime));\n          console.log(`${new Date()} 1S内 FPS：`, fps);\n          this.fps = fps\n          this.minFps = this.minFps > fps ? fps :  this.minFps\n          this.maxFps = this.maxFps < fps ? fps : this.maxFps\n          frame = 0;\n          lastTime = now;\n        }\n        rAF(loop);\n      };\n      loop();\n    },\n  },\n};\n</script>\n<style scoped>\n.hello-independ {\n  display: inline-block;\n  width: 200px;\n  /* padding: 10px; */\n  text-align: center;\n  background-color: white;\n  border-radius: 20px;\n  box-shadow: 0 8px 12px #ebedf0;\n  overflow: hidden;\n  border: 1px solid red;\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/demo-single-plugin/IndependPluginDemo.vue",
    "content": "<template>\n  <div class=\"hello-independ\" v-dragable>\n    <div style=\"font-weight: bold; font-size: 30px; font-style: italic\">\n      Hello Dokit\n    </div>\n    <div>Demo Independ Plugin</div>\n    <div @click=\"remove\" style=\"background-color: red; color:white;margin-top:10px\">\n      点击移除当前独立插件\n    </div>\n  </div>\n</template>\n<script>\nimport { dragable } from \"@dokit/web-utils\";\nimport { removeIndependPlugin } from \"@dokit/web-core\";\n\nexport default {\n  directives: {\n    dragable,\n  },\n  methods: {\n    remove() {\n      removeIndependPlugin(\"test\");\n    },\n  },\n};\n</script>\n<style scoped>\n.hello-independ {\n  display: inline-block;\n  width: 200px;\n  /* padding: 10px; */\n  text-align: center;\n  background-color: white;\n  border-radius: 20px;\n  box-shadow: 0 8px 12px #ebedf0;\n  overflow: hidden;\n  border: 1px solid red;\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/demo-single-plugin/index.js",
    "content": "import IndependPluginDemo from './IndependPluginDemo.vue'\nimport {IndependPlugin} from '@dokit/web-core'\n\nexport default new IndependPlugin({\n  nameZh: '独立插件',\n  name: 'test',\n  icon: 'https://pt-starimg.didistatic.com/static/starimg/img/z1346TQD531618997547642.png',\n  component: IndependPluginDemo\n})\n\n"
  },
  {
    "path": "Web/packages/web/src/plugins/element/components/ElementAttributes.vue",
    "content": "<template>\n  <div class=\"dokit-attributes dokit-section\">\n    <h2>Attributes</h2>\n    <div class=\"dokit-table-wrapper\">\n      <table>\n        <tbody v-if=\"highlightnNode\">\n          <tr v-if=\"highlightnNode.attributes.length === 0\">\n            <td>Empty</td>\n          </tr>\n          <tr\n            v-else\n            v-for=\"(item, index) in highlightnNode.attributes\"\n            :key=\"index\"\n          >\n            <td class=\"dokit-attribute-name-color\">{{ item.name }}</td>\n            <td\n              class=\"dokit-string-color\"\n              v-if=\"item.name === 'href' || item.name === 'src'\"\n            >\n              <a :href=\"item.value\" target=\"_blank\">{{ item.value }}</a>\n            </td>\n            <td class=\"dokit-string-color\" v-else>\n              {{ item.value }}\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  props: {\n    highlightnNode: Object,\n  },\n};\n</script>\n\n<style lang=\"less\" scoped>\n.dokit-attributes {\n  font-size: 15px;\n  margin-top: 10px;\n  .dokit-table-wrapper {\n    overflow-x: auto;\n  }\n  .dokit-attribute-name-color {\n    color: #994500;\n  }\n  .dokit-string-color {\n    color: #1a1aa6;\n  }\n  table {\n    td {\n      padding: 5px 10px;\n      vertical-align: baseline;\n    }\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/element/components/ElementBoxModel.vue",
    "content": "<template>\n  <div class=\"dokit-margin\">\n    <div class=\"dokit-label\">margin</div>\n    <div class=\"dokit-top\">{{ highlightnNode.boxStyle.marginTop }}</div>\n    <br />\n    <div class=\"dokit-left\">{{ highlightnNode.boxStyle.marginLeft }}</div>\n    <div class=\"dokit-border\">\n      <div class=\"dokit-label\">border</div>\n      <div class=\"dokit-top\">\n        {{ highlightnNode.boxStyle.borderTopWidth }}\n      </div>\n      <br />\n      <div class=\"dokit-left\">\n        {{ highlightnNode.boxStyle.borderLeftWidth }}\n      </div>\n      <div class=\"dokit-padding\">\n        <div class=\"dokit-label\">padding</div>\n        <div class=\"dokit-top\">\n          {{ highlightnNode.boxStyle.paddingTop }}\n        </div>\n        <br />\n        <div class=\"dokit-left\">\n          {{ highlightnNode.boxStyle.paddingLeft }}\n        </div>\n        <div class=\"dokit-content\">\n          <span>{{ contentWidth }}</span\n          >&nbsp;×&nbsp;<span>{{ contentHeight }}</span>\n        </div>\n        <div class=\"dokit-right\">\n          {{ highlightnNode.boxStyle.paddingRight }}\n        </div>\n        <br />\n        <div class=\"dokit-bottom\">\n          {{ highlightnNode.boxStyle.paddingBottom }}\n        </div>\n      </div>\n      <div class=\"dokit-right\">\n        {{ highlightnNode.boxStyle.borderRightWidth }}\n      </div>\n      <br />\n      <div class=\"dokit-bottom\">\n        {{ highlightnNode.boxStyle.borderBottomWidth }}\n      </div>\n    </div>\n    <div class=\"dokit-right\">\n      {{ highlightnNode.boxStyle.marginRight }}\n    </div>\n    <br />\n    <div class=\"dokit-bottom\">\n      {{ highlightnNode.boxStyle.marginBottom }}\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  props: {\n    highlightnNode: Object,\n    contentWidth: String | Number,\n    contentHeight: String | Number,\n  },\n};\n</script>\n\n<style>\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/element/components/ElementBreadcrumb.vue",
    "content": "<template>\n  <div class=\"dokit-breadcrumb\">\n    <ul class=\"dokit-parents\" v-if=\"parentTag.length > 0\">\n      <li v-for=\"(item, index) in parentTag\" :key=\"index\">\n        <div class=\"dokit-parent\" @click=\"selectElement(item)\">\n          <span class=\"nodeName\">{{ item.nodeName.toLowerCase() }}</span>\n          <span v-if=\"item.id !== ''\" class=\"nodeaId\">{{ `#${item.id}` }}</span>\n          <span v-if=\"item.className !== ''\" class=\"nodeaClass\">{{\n            `.${item.className}`\n          }}</span>\n        </div>\n        <span class=\"dokit-icon-arrow-right\">></span>\n      </li>\n    </ul>\n    <div class=\"dokit-element\" v-if=\"highlightElement\">\n      <span class=\"nodeName\">{{\n        highlightElement.nodeName.toLowerCase()\n      }}</span>\n      <span class=\"nodeaId\" v-if=\"highlightElement.id !== ''\">{{\n        `#${highlightElement.id}`\n      }}</span>\n      <span class=\"nodeaClass\" v-if=\"highlightElement.className !== ''\">{{\n        `.${highlightElement.className}`\n      }}</span>\n    </div>\n  </div>\n</template>\n\n<script>\nimport { toggleElement } from \"@dokit/web-core\";\nexport default {\n  props: {\n    parentTag: Array,\n    highlightElement: Object,\n  },\n  methods: {\n    selectElement(item) {\n      toggleElement(item);\n    },\n  },\n};\n</script>\n\n<style lang=\"less\" scoped>\n.dokit-breadcrumb {\n  .dokit-parents {\n    overflow-x: auto;\n    background: #f3f3f3;\n    color: #333;\n    padding: 10px;\n    white-space: nowrap;\n    border-bottom: 1px solid #ccc;\n    font-size: 15px;\n    margin: 0;\n    li {\n      display: inline-block;\n    }\n    .dokit-parent {\n      display: inline-block;\n    }\n    .dokit-icon-arrow-right {\n      font-size: 15px;\n      margin: 0 5px;\n    }\n  }\n  .dokit-element {\n    background: #f3f3f3;\n    color: #333;\n    user-select: text;\n    word-break: break-all;\n    padding: 10px;\n    font-size: 16px;\n    border-bottom: 1px solid #ccc;\n  }\n  .nodeName {\n    color: #881280;\n  }\n  .nodeaId {\n    color: #1a1aa8;\n  }\n  .nodeaClass {\n    color: rgb(143, 73, 25);\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/element/components/ElementComputedStyle.vue",
    "content": "<template>\n  <div class=\"dokit-computed-style dokit-section\">\n    <div class=\"dokit-box-model\">\n      <div\n        class=\"dokit-position\"\n        v-if=\"highlightnNode.boxStyle.position !== 'static'\"\n      >\n        <div class=\"dokit-label\">position</div>\n        <div class=\"dokit-top\">{{ highlightnNode.boxStyle.top }}</div>\n        <br />\n        <div class=\"dokit-left\">{{ highlightnNode.boxStyle.left }}</div>\n        <ElementBoxModel\n          :highlightnNode=\"highlightnNode\"\n          :contentWidth=\"contentWidth\"\n          :contentHeight=\"contentHeight\"\n        />\n        <div class=\"dokit-right\">{{ highlightnNode.boxStyle.right }}</div>\n        <br />\n        <div class=\"dokit-bottom\">\n          {{ highlightnNode.boxStyle.bottom }}\n        </div>\n      </div>\n      <ElementBoxModel\n        v-else\n        :highlightnNode=\"highlightnNode\"\n        :contentWidth=\"contentWidth\"\n        :contentHeight=\"contentHeight\"\n      />\n    </div>\n  </div>\n</template>\n\n<script>\nimport ElementBoxModel from \"./ElementBoxModel.vue\";\nexport default {\n  components: {\n    ElementBoxModel,\n  },\n  props: {\n    highlightElement: Object,\n    highlightnNode: Object,\n    contentWidth: String | Number,\n    contentHeight: String | Number,\n  },\n};\n</script>\n\n<style lang=\"less\" scoped>\n.dokit-computed-style {\n  font-size: 15px;\n  .dokit-box-model {\n    overflow-x: auto;\n    color: #222;\n    font-size: 12px;\n    padding: 10px;\n    text-align: center;\n    white-space: nowrap;\n    &:deep(.dokit-label) {\n      position: absolute;\n      margin-left: 3px;\n      padding: 0 2px;\n    }\n    &:deep(.dokit-top),\n    &:deep(.dokit-left),\n    &:deep(.dokit-right),\n    &:deep(.dokit-bottom) {\n      display: inline-block;\n    }\n    &:deep(.dokit-left),\n    &:deep(.dokit-right) {\n      vertical-align: middle;\n    }\n    &:deep(.dokit-border),\n    &:deep(.dokit-padding),\n    &:deep(.dokit-content),\n    &:deep(.dokit-position),\n    &:deep(.dokit-margin) {\n      position: relative;\n      background: #fff;\n      display: inline-block;\n      text-align: center;\n      vertical-align: middle;\n      padding: 3px;\n      margin: 3px;\n    }\n    .dokit-position {\n      border: 1px grey dotted;\n    }\n    &:deep(.dokit-margin) {\n      border: 1px dashed;\n      background: rgba(246, 178, 107, 0.66);\n    }\n    &:deep(.dokit-border) {\n      border: 1px #000 solid;\n      background: rgba(255, 229, 153, 0.66);\n    }\n    &:deep(.dokit-padding) {\n      border: 1px grey dashed;\n      background: rgba(147, 196, 125, 0.55);\n    }\n    &:deep(.dokit-content) {\n      border: 1px grey solid;\n      min-width: 100px;\n      background: rgba(111, 168, 220, 0.66);\n      &:deep(span) {\n        margin: 0;\n        padding: 0;\n        border: 0;\n        font: inherit;\n        vertical-align: baseline;\n      }\n    }\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/element/components/ElementStyles.vue",
    "content": "<template>\n  <div class=\"dokit-styles dokit-section\">\n    <h2>Styles</h2>\n    <div class=\"dokit-style-wrapper\">\n      <div class=\"dokit-style-rules\" v-for=\"(item, idx) in styles\" :key=\"idx\">\n        <div>{{ item.selectorText }} {</div>\n        <div\n          class=\"dokit-rule\"\n          v-for=\"(styleValue, styleKey, index) in item.style\"\n          :key=\"index\"\n        >\n          <span>{{ styleKey }}</span\n          >:\n          <span v-html=\"`${styleValue};`\"></span>\n        </div>\n        <div>}</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  props: {\n    styles: Array,\n  },\n};\n</script>\n\n<style lang=\"less\" scoped>\n.dokit-styles {\n  font-size: 15px;\n  .dokit-style-wrapper {\n    padding: 10px;\n    .dokit-style-rules {\n      border: 1px solid #ccc;\n      padding: 10px;\n      margin-bottom: 10px;\n      &:last-child {\n        margin-bottom: 0;\n      }\n      .dokit-rule {\n        padding-left: 2em;\n        word-break: break-all;\n       &:deep(.dokit-style-color) {\n          position: relative;\n          top: 1px;\n          width: 10px;\n          height: 10px;\n          border-radius: 50%;\n          margin-right: 2px;\n          border: 1px solid #ccc;\n          display: inline-block;\n        }\n        span {\n          color: #c80000;\n        }\n      }\n    }\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/element/elementContainer.vue",
    "content": "<template>\n  <div class=\"element-container\">\n    <div class=\"element-tab\">\n      <div\n        @click=\"changeMode(0)\"\n        :class=\"`elemen-tab-item ${\n          active === 0 ? 'elemen-tab-item-active' : ''\n        }`\"\n      >\n        视图树\n      </div>\n      <div\n        @click=\"changeMode(1)\"\n        :class=\"`elemen-tab-item ${\n          active === 1 ? 'elemen-tab-item-active' : ''\n        }`\"\n      >\n        元素属性\n      </div>\n    </div>\n    <div class=\"tree-container\" v-show=\"active === 0\">\n      <div class=\"real-time-switch\">\n        <div>实时更新</div>\n        <input class=\"switch\" type=\"checkbox\" v-model=\"isRealTime\" />\n      </div>\n      <ElementTree :node=\"node\" />\n    </div>\n    <ElementDetails v-show=\"active === 1\"></ElementDetails>\n    <ElementSnippet />\n  </div>\n</template>\n\n<script>\nimport ElementTree from './elementTree.vue';\nimport ElementDetails from './elementDetails.vue';\nimport ElementSnippet from './elementSnippet.vue';\n// import MutationObserver from 'mutation-observer';\nimport { guid, $bus } from '../../assets/util';\nimport { toggleElement } from '@dokit/web-core';\nexport default {\n  components: {\n    ElementTree,\n    ElementSnippet,\n    ElementDetails,\n  },\n  data() {\n    return {\n      node: null, // 页面元素节点\n      observer: null, // 页面元素监听实例\n      isRealTime: null, // 是否实时更新节点显示\n      config: {\n        // 页面元素监听配置\n        attributes: true,\n        childList: true,\n        characterData: true,\n        subtree: true,\n      },\n      active: 0,\n    };\n  },\n  mounted() {\n    this.node = this.getNode(document.documentElement);\n    this.observer = new MutationObserver((mutations) => {\n      for (let i = 0; i < mutations.length; i++) {\n        let mutation = mutations[i];\n        if (this._isInDokit(mutation.target)) {\n          continue;\n        }\n        this.onMutation(mutation);\n      }\n    });\n    this.isRealTime =\n      JSON.parse(localStorage.getItem(\"dokitElementRealTime\")) ?? true;\n    toggleElement(this.node.$view);\n  },\n  computed: {\n    state() {\n      return this.$store.state;\n    },\n    highlightElement() {\n      return this.state.highlightElement;\n    },\n  },\n  destroyed() {\n    this.observer.disconnect();\n  },\n  watch: {\n    isRealTime: {\n      handler: function (newval) {\n        if (newval) {\n          this.node = this.getNode(document.documentElement);\n          $bus.emit(this.node.key);\n          localStorage.setItem(\"dokitElementRealTime\", newval);\n          this.observer.observe(document.documentElement, this.config);\n        } else if (newval === false) {\n          this.observer.disconnect();\n          localStorage.setItem(\"dokitElementRealTime\", newval);\n        }\n        console.log(this.node)\n      },\n      immediate: true,\n    },\n  },\n  methods: {\n    changeMode(e) {\n      this.active = e;\n    },\n    getNode(elem) {\n      if (this._isIgnoredElement(elem)) {\n        return undefined;\n      }\n\n      let node = elem.__dokitForWeb_node || {};\n\n      // basic node info\n      node.$view = elem;\n      node.nodeType = elem.nodeType;\n      node.key = node.key || guid();\n      node.nodeName = elem.nodeName;\n      node.tagName = elem.tagName || \"\";\n      node.textContent = \"\";\n      if (\n        node.nodeType == elem.TEXT_NODE ||\n        node.nodeType == elem.DOCUMENT_TYPE_NODE\n      ) {\n        node.textContent = elem.textContent;\n      }\n\n      // boxStyle\n      if (node.nodeType === elem.ELEMENT_NODE) {\n        node.boxStyle = this.getBoxModelValue(elem);\n      }\n\n      // attrs\n      node.id = elem.id || \"\";\n      node.className = elem.className || \"\";\n      node.attributes = [];\n      if (elem.hasAttributes && elem.hasAttributes()) {\n        for (let i = 0; i < elem.attributes.length; i++) {\n          node.attributes.push({\n            name: elem.attributes[i].name,\n            value: elem.attributes[i].value || \"\",\n          });\n        }\n      }\n\n      // child nodes\n      node.childNodes = [];\n      if (elem.childNodes.length > 0) {\n        for (let i = 0; i < elem.childNodes.length; i++) {\n          let child = this.getNode(elem.childNodes[i]);\n          if (!child) {\n            continue;\n          }\n          node.childNodes.push(child);\n        }\n      }\n\n      // save node to element for further actions\n      elem.__dokitForWeb_node = node;\n      return node;\n    },\n    _isIgnoredElement(elem) {\n      // empty or line-break text\n      if (elem.nodeType == elem.TEXT_NODE) {\n        if (\n          elem.textContent.replace(\n            /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$|\\n+/g,\n            \"\"\n          ) == \"\"\n        ) {\n          // trim\n          return true;\n        }\n      }\n      return false;\n    },\n    _isInDokit(elem) {\n      let target = elem;\n      while (target != undefined) {\n        if (target.id == \"dokit-root\") {\n          return true;\n        }\n        target = target.parentNode || undefined;\n      }\n      return false;\n    },\n    onMutation(mutation) {\n      switch (mutation.type) {\n        case \"childList\":\n          if (mutation.removedNodes.length > 0) {\n            this.onChildRemove(mutation);\n          }\n          if (mutation.addedNodes.length > 0) {\n            this.onChildAdd(mutation);\n          }\n          break;\n        case \"attributes\":\n          this.onAttributesChange(mutation);\n          break;\n        case \"characterData\":\n          this.onCharacterDataChange(mutation);\n          break;\n        default:\n          break;\n      }\n    },\n    onAttributesChange(mutation) {\n      let node = mutation.target.__dokitForWeb_node;\n      if (!node) {\n        return;\n      }\n      if (mutation.target === this.highlightElement) {\n        toggleElement(null);\n        this.$nextTick(() => {\n          toggleElement(mutation.target);\n        });\n      }\n      node = this.getNode(mutation.target);\n      $bus.emit(node.key + \"refreshMy\");\n    },\n    onCharacterDataChange(mutation) {\n      let node = mutation.target.__dokitForWeb_node;\n      if (!node) {\n        return;\n      }\n      if (\n        mutation.target === this.highlightElement ||\n        mutation.target.parentNode === this.highlightElement\n      ) {\n        toggleElement(null);\n        this.$nextTick(() => {\n          toggleElement(mutation.target.parentNode);\n        });\n      }\n      node = this.getNode(mutation.target);\n      $bus.emit(node.key + \"refreshMy\");\n    },\n    onChildAdd(mutation) {\n      let $parent = mutation.target,\n        parentNode = $parent.__dokitForWeb_node;\n      if (!parentNode) {\n        return;\n      }\n      if ($parent === this.highlightElement) {\n        toggleElement(null);\n        this.$nextTick(() => {\n          toggleElement($parent);\n        });\n      }\n      this.getNode($parent);\n      $bus.emit(parentNode.key + \"refreshChild\");\n    },\n    onChildRemove(mutation) {\n      let $parent = mutation.target,\n        parentNode = $parent.__dokitForWeb_node;\n      if (!parentNode) {\n        return;\n      }\n      for (let i = 0; i < mutation.removedNodes.length; i++) {\n        let $target = mutation.removedNodes[i],\n          key = $target?.__dokitForWeb_node?.key;\n        if (!key) {\n          continue;\n        }\n        if ($target === this.highlightElement) {\n          toggleElement(null);\n        }\n        // remove view\n        this.deleteNode(parentNode, key);\n      }\n      $bus.emit(parentNode.key + \"refreshChild\");\n    },\n    deleteNode(parentNode, nodeKey) {\n      let deleteNodeIndex = parentNode.childNodes.findIndex(\n        (item) => item.key === nodeKey\n      );\n      parentNode.childNodes.splice(deleteNodeIndex, 1);\n    },\n    getBoxModelValue(elem) {\n      return {\n        display: this.getStyle(elem, 'display'),  \n        position: this.getStyle(elem, 'position'),\n        top:this.getStyle(elem, 'top'),\n        right:this.getStyle(elem, 'right'),\n        bottom:this.getStyle(elem, 'bottom'),\n        left:this.getStyle(elem, 'left'),\n        marginTop: this.getStyle(elem, 'marginTop'),\n        marginRight: this.getStyle(elem, 'marginRight'),\n        marginBottom: this.getStyle(elem, 'marginBottom'),\n        marginLeft: this.getStyle(elem, 'marginLeft'),\n        borderTopWidth: this.getStyle(elem, 'borderTopWidth'),\n        borderRightWidth: this.getStyle(elem, 'borderRightWidth'),\n        borderBottomWidth: this.getStyle(elem, 'borderBottomWidth'),\n        borderLeftWidth: this.getStyle(elem, 'borderLeftWidth'),\n        paddingTop: this.getStyle(elem, 'paddingTop'),\n        paddingRight: this.getStyle(elem, 'paddingRight'),\n        paddingBottom: this.getStyle(elem, 'paddingBottom'),\n        paddingLeft: this.getStyle(elem, 'paddingLeft'),\n        contentWidth:\n          (elem.offsetWidth -\n          parseInt(this.getStyle(elem, 'paddingLeft')) -\n          parseInt(this.getStyle(elem, 'paddingRight')) -\n          parseInt(this.getStyle(elem, 'borderLeftWidth')) -\n          parseInt(this.getStyle(elem, 'borderRightWidth'))),\n        contentHeight:\n          (elem.offsetHeight -\n          parseInt(this.getStyle(elem, 'paddingTop')) -\n          parseInt(this.getStyle(elem, 'paddingBottom')) -\n          parseInt(this.getStyle(elem, 'borderTopWidth')) -\n          parseInt(this.getStyle(elem, 'borderBottomWidth'))),\n      };\n    },\n    getStyle(elem, attr) {\n      let attrStyle = null;\n      if (elem?.currentStyle) {\n        attrStyle = elem.currentStyle[attr];\n      } else {\n        attrStyle = document.defaultView.getComputedStyle(elem, null)[attr];\n      }\n      if (attrStyle.indexOf(\"px\") !== -1) {\n        return /\\d+/.exec(attrStyle)[0];\n      } else {\n        return attrStyle;\n      }\n    },\n  },\n};\n</script>\n\n<style lang=\"less\" scope>\n.element-container {\n  position: relative;\n  height: 100%;\n  overflow: hidden;\n  .tree-container {\n    overflow-y: auto;\n    height: calc(100% - 76px);\n    .real-time-switch {\n      display: flex;\n      flex-direction: row;\n      align-items: center;\n      font-size: 18px;\n      padding: 5px;\n      .switch {\n        appearance: none;\n        -moz-appearance: button;\n        -webkit-appearance: none;\n      }\n      .switch {\n        position: relative;\n        margin: 0;\n        width: 40px;\n        height: 24px;\n        border: 1px solid #ebebf9;\n        outline: 0;\n        border-radius: 16px;\n        box-sizing: border-box;\n        background-color: #ebebf9;\n        -webkit-transition: background-color 0.1s, border 0.1s;\n        transition: background-color 0.1s, border 0.1s;\n        margin-left: auto;\n        &:before {\n          content: \" \";\n          position: absolute;\n          top: 0;\n          left: 0;\n          width: 38px;\n          height: 22px;\n          border-radius: 19px;\n          background-color: #ebebf9;\n          -webkit-transition: -webkit-transform 0.35s\n            cubic-bezier(0.45, 1, 0.4, 1);\n          transition: -webkit-transform 0.35s cubic-bezier(0.45, 1, 0.4, 1);\n          transition: transform 0.35s cubic-bezier(0.45, 1, 0.4, 1);\n        }\n\n        &:after {\n          content: \" \";\n          position: absolute;\n          top: 0;\n          left: 1px;\n          width: 22px;\n          height: 22px;\n          border-radius: 15px;\n          background-color: #ffffff;\n          /*box-shadow: 0 1PX 3PX rgba(0, 0, 0, 0.4);*/\n          -webkit-transition: -webkit-transform 0.35s\n            cubic-bezier(0.4, 0.4, 0.25, 1.35);\n          transition: -webkit-transform 0.35s cubic-bezier(0.4, 0.4, 0.25, 1.35);\n          transition: transform 0.35s cubic-bezier(0.4, 0.4, 0.25, 1.35);\n        }\n\n        &:checked {\n          background: rgb(69, 124, 190);\n          border: solid 1px rgb(69, 124, 190);\n        }\n\n        &:checked:before {\n          transform: scale(0);\n        }\n\n        &:checked:after {\n          transform: translateX(15px);\n        }\n      }\n    }\n  }\n  .element-tab {\n    width: 100%;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    .elemen-tab-item {\n      font-size: 18px;\n      width: 50%;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      padding: 5px 10px;\n      border-bottom: 1px #eee solid;\n      &:first-child {\n        border-right: 1px #eee solid;\n      }\n      &-active {\n        border-bottom: 1px transparent solid;\n        color: #457cbe;\n      }\n    }\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/element/elementDetails.vue",
    "content": "<template>\n  <div class=\"dokit-element-details\">\n    <ElementBreadcrumb\n      :parentTag=\"parentTag\"\n      :highlightElement=\"highlightElement\"\n    />\n    <ElementAttributes :highlightnNode=\"highlightnNode\" />\n    <ElementStyles :styles=\"styles\" />\n    <ElementComputedStyle\n      v-if=\"highlightElement\"\n      :highlightElement=\"highlightElement\"\n      :highlightnNode=\"highlightnNode\"\n      :contentWidth=\"contentWidth\"\n      :contentHeight=\"contentHeight\"\n    />\n  </div>\n</template>\n\n<script>\nimport CssStore from '../../assets/CssStore';\nimport ElementComputedStyle from './components/ElementComputedStyle';\nimport ElementStyles from './components/ElementStyles';\nimport ElementAttributes from './components/ElementAttributes';\nimport ElementBreadcrumb from './components/ElementBreadcrumb';\nexport default {\n  components: {\n    ElementComputedStyle,\n    ElementStyles,\n    ElementAttributes,\n    ElementBreadcrumb,\n  },\n  data() {\n    return {\n      parentTag: [],\n      curCssStore: null,\n      styles: [],\n      computedStyle: null,\n    };\n  },\n  computed: {\n    state() {\n      return this.$store.state;\n    },\n    highlightElement() {\n      return this.state.highlightElement;\n    },\n    highlightnNode() {\n      return this.highlightElement?.__dokitForWeb_node;\n    },\n    contentWidth() {\n      return this.computedStyle.width === \"auto\"\n        ? \"auto\"\n        : this.highlightnNode.boxStyle.contentWidth;\n    },\n    contentHeight() {\n      return this.computedStyle.height === \"auto\"\n        ? \"auto\"\n        : this.highlightnNode.boxStyle.contentHeight;\n    },\n  },\n  watch: {\n    highlightElement(val) {\n      // 元素改变时获取新的样式对象和父元素\n      if (val) {\n        this.curCssStore = new CssStore(val);\n        this.getStyle();\n        this.parentTag = this.getParentTag(val).reverse();\n        this.computedStyle = this.curCssStore.getComputedStyle();\n      }\n    },\n  },\n  methods: {\n    getParentTag(startTag, parentTagList = []) {\n      // 递归查找父元素\n      let self = this;\n      if (startTag?.parentElement) {\n        // 放入集合\n        parentTagList.push(startTag?.parentElement);\n        // 再上一层寻找\n        return self.getParentTag(startTag?.parentElement, parentTagList);\n      } else {\n        return parentTagList;\n      }\n    },\n    getStyle() {\n      // 获取元素样式对象\n      const styles = this.curCssStore.getMatchedCSSRules();\n      styles.unshift(this.getInlineStyle(this.highlightElement?.style));\n      styles.forEach((style) => this.processStyleRules(style?.style));\n      this.styles = styles;\n    },\n    getInlineStyle(style) {\n      // 获取行内样式\n      const ret = {\n        selectorText: \"element.style\",\n        style: {},\n      };\n      for (let i = 0, len = style?.length; i < len; i++) {\n        const s = style[i];\n        if (style[s] === \"initial\") continue;\n        ret.style[s] = style[s];\n      }\n      return ret;\n    },\n    processStyleRules(style) {\n      // 把颜色和url替换成标签 有助于查看\n      Object.keys(style).forEach((key) => {\n        style[key] = this.processStyleRule(style[key]);\n      });\n    },\n    processStyleRule(val) {\n      const regColor = /rgba?\\((.*?)\\)/g;\n      const regCssUrl = /url\\(\"?(.*?)\"?\\)/g;\n      val = val + \"\";\n      return val\n        .replace(\n          regColor,\n          '<span class=\"dokit-style-color\" style=\"background-color: $&\"></span>$&'\n        )\n        .replace(regCssUrl, (match, url) => `url(\"${this.wrapLink(url)}\")`);\n    },\n    wrapLink(link) {\n      return `<a href=\"${link}\" target=\"_blank\">${link}</a>`;\n    },\n  },\n};\n</script>\n\n<style lang=\"less\" scoped>\n.dokit-element-details {\n  overflow-y: auto;\n  height: calc(100% - 76px);\n  &:deep(.dokit-section) {\n    border-bottom: 1px solid #ccc;\n    color: #333;\n    margin-bottom: 10px;\n    &:deep(h2) {\n      background: #f3f3f3;\n      border-top: 1px solid #ccc;\n      padding: 10px;\n      font-size: 18px;\n      margin: 0;\n    }\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/element/elementSnippet.vue",
    "content": "<template>\n  <div class=\"element-snippet-component\">\n    <div class=\"element-btn\" @click=\"openCheck\">\n      <img\n        :src=\"\n          selectElement\n            ? 'https://pt-starimg.didistatic.com/static/starimg/img/mP3782Ooy71635737733419.png'\n            : 'https://pt-starimg.didistatic.com/static/starimg/img/6Yjqj9hBVz1635736368868.png'\n        \"\n      />\n    </div>\n  </div>\n</template>\n\n<script>\nimport {\n  toggleContainer,\n  toggleHighlight,\n  toggleElement,\n} from \"@dokit/web-core\";\nimport { debounce } from \"../../assets/util\";\nexport default {\n  data() {\n    return {\n      selectElement: false,\n      checkCurrentElement: false,\n      oldElement:null,\n    };\n  },\n  computed: {\n    state() {\n      return this.$store.state;\n    },\n    showHighlightElement() {\n      return this.state.showHighlightElement;\n    },\n    highlightElement() {\n      return this.state.highlightElement;\n    },\n  },\n  created() {\n    this.onScroll = debounce(this.onScroll, 300);\n  },\n  watch: {\n    showHighlightElement(val) {\n      if (val) {\n        document.body.addEventListener(\"click\", this.elementClick, true);\n        window.addEventListener(\"scroll\", this.onScroll);\n      } else {\n        document.body.removeEventListener(\"click\", this.elementClick, true);\n        window.removeEventListener(\"scroll\", this.onScroll);\n      }\n    },\n  },\n  methods: {\n    openCheck() {\n      this.selectElement = !this.selectElement;\n      if (!this.showHighlightElement) {\n        toggleContainer();\n      }\n      toggleHighlight();\n    },\n    elementClick(e) {\n      e.preventDefault();\n      e.stopImmediatePropagation();\n      if (e.target !== this.highlightElement) {\n        toggleElement(e.target);\n      }\n    },\n    onScroll() {\n      this.oldElement = this.highlightElement\n      toggleElement(null);\n      this.$nextTick(() => {\n        toggleElement(this.oldElement);\n      });\n    },\n  },\n};\n</script>\n\n<style lang=\"less\" scope>\n.element-snippet-component {\n  width: 100%;\n  height: 40px;\n  background: #f3f3f3;\n  position: absolute;\n  z-index: 100;\n  left: 0;\n  bottom: 0;\n  width: 100%;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  .element-btn {\n    height: 100%;\n    width: 100%;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    img {\n      height: 20px;\n    }\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/element/elementTree.vue",
    "content": "<template>\n  <div\n    class=\"element-tree-container\"\n    :class=\"[canFold ? 'can-unfold' : '', unfold ? 'unfolded' : '']\"\n    v-if=\"isShow\"\n  >\n    <!-- <div @click=\"unfoldDetail\" v-html=\"displayDetailValue\"></div> -->\n    <div\n      class=\"dkelm-l\"\n      v-if=\"node && node.nodeType === 1\"\n      :class=\"[\n        isNullEndTag(node.tagName) || node.childNodes.length == 0\n          ? 'dkelm-noc'\n          : '',\n        unfold ? 'dk-toggle' : '',\n      ]\"\n    >\n      <span class=\"dkelm-node\" @click=\"unfoldDetail\">\n        &lt;{{ node.tagName.toLowerCase() }}\n        <i class=\"dkelm-k\" v-if=\"node.className || node.attributes.length\">\n          <span v-for=\"(item, index) in node.attributes\" :key=\"index\">\n            <span v-if=\"item.value !== ''\">\n              {{ \" \" + item.name }}=<i class=\"dkelm-v\"> {{ item.value }} </i>\n            </span>\n            <span v-else>\n              {{ \" \" + item.name }}\n            </span>\n          </span> </i\n        >&gt;\n      </span>\n      <template v-if=\"canFold&&unfold\">\n        <div v-for=\"child in node.childNodes\" :key=\"child.key\">\n          <ElementTree :node=\"child\" :parentIsUnfold=\"unfold\"></ElementTree>\n        </div>\n      </template>\n      <span class=\"dkelm-node\" v-if=\"!isNullEndTag(node.tagName)\"\n        >&lt;/{{ node.tagName.toLowerCase() }}&gt;</span\n      >\n    </div>\n    <template v-else-if=\"node && node.nodeType === 3 && node.textContent\">\n      {{ _trim(node.textContent) }}\n    </template>\n  </div>\n</template>\n\n<script>\nimport ElementTree from \"./elementTree.vue\";\nimport { $bus } from \"../../assets/util\";\nexport default {\n  name: \"ElementTree\",\n  components: {\n    ElementTree,\n  },\n  data() {\n    return {\n      unfold: false,\n      isShow: true,\n    };\n  },\n  props: {\n    node: Object,\n    parentIsUnfold: Boolean,\n  },\n  watch: {\n    unfold(val) {\n      if (val) {\n        $bus.on(this.node.key + \"refreshChild\", this.refreshSon);\n      } else {\n        $bus.off(this.node.key + \"refreshChild\", this.refreshSon);\n      }\n    },\n    parentIsUnfold:{\n      handler: function (val) {\n        if(this.node?.key){\n          if (val) {\n            $bus.on(this.node.key + \"refreshMy\", this.refresh);\n          } else {\n            $bus.off(this.node.key + \"refreshMy\", this.refresh);\n          }\n        }\n      },\n      immediate: true,\n    }\n  },\n  created() {\n    if (this?.node?.tagName === \"HTML\") {\n      $bus.on(this.node.key, this.refresh);\n    }\n  },\n  destroyed() {\n    $bus.off(this.node.key, this.refresh);\n    $bus.off(this.node.key, this.refreshSon);\n  },\n  methods: {\n    refreshSon() {\n      this.unfold = false;\n      this.$nextTick(() => {\n        this.unfold = true;\n      });\n    },\n    refresh() {\n      this.isShow = false;\n      this.$nextTick(() => {\n        this.isShow = true;\n      });\n    },\n    isNullEndTag(tagName) {\n      let names = [\"br\", \"hr\", \"img\", \"input\", \"link\", \"meta\"];\n      tagName = tagName ? tagName.toLowerCase() : \"\";\n      return names.indexOf(tagName) > -1 ? true : false;\n    },\n    unfoldDetail() {\n      this.canFold()&&(this.unfold = !this.unfold);\n    },\n    canFold() {\n      if (this.node.childNodes.length > 0) {\n        return true;\n      }\n      return false;\n    },\n    _trim(str) {\n      return str.replace(/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g, \"\");\n    },\n  },\n};\n</script>\n\n<style lang=\"less\" scoped>\n.element-tree-container {\n  font-size: 16px;\n  position: relative;\n  .dkelm-l {\n    padding-left: 8px;\n    position: relative;\n    word-wrap: break-word;\n    line-height: 1;\n    &::before {\n      content: \"\";\n      display: block;\n      position: absolute;\n      top: 6px;\n      left: 3px;\n      width: 0;\n      height: 0;\n      border: 3px solid transparent;\n      border-left-color: #000;\n    }\n    &.dkelm-noc:before {\n      display: none;\n    }\n    &.dk-toggle:before {\n      display: block;\n      top: 8px;\n      left: 0;\n      border-top-color: #000;\n      border-left-color: transparent;\n    }\n  }\n  .dkelm-node {\n    color: #183691;\n  }\n  .dkelm-k {\n    color: #0086b3;\n  }\n  .dkelm-v {\n    color: #905;\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/element/index.js",
    "content": "import Element from './elementContainer.vue'\nimport {RouterPlugin} from '@dokit/web-core'\n\nexport default new RouterPlugin({\n  nameZh: '元素查看',\n  name: 'element',\n  icon: 'https://pt-starimg.didistatic.com/static/starimg/img/nktYHc1alL1635323338193.png',\n  component: Element\n})\n\n"
  },
  {
    "path": "Web/packages/web/src/plugins/h5-door/ToolH5Door.vue",
    "content": "\n<template>\n  <div class=\"h5-portal\">\n    <div class=\"portal-textarea-container\">\n      <textarea\n        class=\"portal-textarea\"\n        rows=\"5\"\n        v-model=\"url\"\n        placeholder=\"请输入目标传送地址\"\n      ></textarea>\n      <div class=\"portal-opt-area\">\n        <div class=\"opt-btn\" @click=\"jumpToTarget\">跳转</div>\n      </div>\n    </div>\n    <div class=\"url-edit-container\">\n      <div class=\"url-edit-container-title\">快捷编辑</div>\n      <DoRow>\n        <DoCol :span=6 class=\"url-edit-key\">baseUrl</DoCol>\n        <DoCol :span=18>\n          <textarea\n            rows=\"3\"\n            v-model=\"editUrlInfo.baseUrl\"\n            placeholder=\"eg. https://dokit.didi.cn\"\n          ></textarea>\n        </DoCol>\n      </DoRow>\n      <DoRow class=\"url-edit-container-query-item\" v-for=\"(query, index) in editUrlInfo.queryList\" :key=\"index\">\n        <DoCol :span=6 class=\"url-edit-key\">query参数{{index+1}}</DoCol>\n        <DoCol :span=7>\n          <input type=\"text\" v-model=\"editUrlInfo.queryList[index].key\"/>\n        </DoCol>\n        <DoCol :span=2 style=\"text-align:center;\">=</DoCol>\n        <DoCol :span=7>\n          <input type=\"text\" v-model=\"editUrlInfo.queryList[index].value\"/>\n        </DoCol>\n        <DoCol class=\"url-edit-container-query-item__icon\" :span=\"2\" @click=\"delQuery(index)\">\n          <img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAAHzVJREFUeNrtnXl0U1ee57+/J9nYxmYzmH0ziSUIYZMhAdskZKGnTlNzuk91kDM9p+gqmExPzeRMTWfGSMGu41OIsuyuVHdPTXenaiCdpKe6LKiePmfCzKmTsAQsGwKWTRIwemaJ2ZfYZrGxjS29O39ISgx4kaz7dLXcz1/B1rvvd5X38e/q6d3fjyDRhdf2MsP55jqTYkABMUOBpmi5BCWbMS2HiLIZYzkEygaQDSAHoGyA5QSOpi6AdQPoAtDNwLqJqIsx1k2kdDFN61agdDDytzKkqYvUK6379m32i55zMkKiA0h0Vu84MtdnSCtQNFYAaCbGqIAIBQAWxTiUC4yxVkUhlTFq1TR/q5FY68ldL1wR/R4lMlKQCFljryvwG2k9GF4F8CqAyaJjGoVOAj4B8IkPxqOnHM+fEx1QIiEFGYWVO+rnG0DrQP4ixuhVBLJDIqMywieKxhr8UBqadxVdEh1QPCMFGQJL+ZGNIOUPwGgdgOdFx6Mzx8FYAxno940/Lf5EdDDxhhQkyOodDes00jYBtAlgz4qORxBfAtivMP/+k7teaBAdTDyQ0oKsrKhbbmDYxECbkPyZIlKOM8JHMBj3N1U+/4XoYESRcoIs39Ew20j4EwZtEwGviI4nEWDAAQLbrxlpX3Nl8XXR8cSSlBFkReWxBYrPt42ItoJhhuh4EpSbINrtH+jfc8q5oU10MLEg6QVZY68r0Ay0jQFbAUwRHU+S0ElEuxWftudEVUmr6GD0JGkFKXy7filTtG0AbUXg22oJf7qJsJuA3Sd3Fp8RHYweJJ0ghRUNKxnYNjC2DUC66HhShH4Q7SbQ7sad65pFB8OTpBHkWVvd5HQjtgO0XXQsqQ2r7veh+ktnyR3RkfAgKQQprKj7PgNtB8MS0bFIABBaCKy6cWfJh6JDiX4qCczqivrVGkMZwP5EdCySoaDfKYSakzuLToqOZMwzEB3AWFhSeTg705dWBmA75OeMeKcfQHWvcaCmpXJDt+hgIiXhBCncUfc6IyoDsEJ0LJKIOEWM1TTuKvmt6EAiIWEEKap25/TdRw0Ify46FkkUMLybMQFl9duLu0SHEg4JIciqCncJMdRAPi+VLBxnhLKmncV1ogMZjbgXxFJe/ybAagBkiI5FwpU+gMo8jqJfig5kJOJWkDX2z3J9iq+GiP1QdCwS/WCMvWfU0stOVD3XITqWoYhLQVb+pOFlRfPXALRKdCySmODRNG1788/WHxQdyOPEnSCW8rq3AKoBoIiORRJT/ADb7nGUvCM6kMHElSCWivp3wNhfiI5DIhDGfuHZVfKW6DBCxI0glnL3+wC2iI5DEhd84HEU/5noIIA4EcRSXv8RwDaJjkMST7D9HkfJd0VHIVyQwnJ3AwPWio5DEn8QqKHRUVQkNgaBWCrcKljC15mS6IvqcRSbRZ1cmCCWcnc7gFxR55ckFO0eR/E0EScWIoil3M1EnFeSwDAwz67imN/6j/kJLeXuy7E+pyQJIJCl3B3zMqkxFWRVeV09gLmxnqQkaZhnKXe7Y3nCmAlSWO6uJdC6WE5OkpQUrSqvi9mekpgIYimv/2sGWGM1KUlyQ6DSwgr3X8XiXLoLUljurgHYf47FZCSpA2P48aqKumq9z6OrIJaK+p0M+G96T0KSmhCjMktF/U5dz6HXwMGncn+uZ/ASCQCA6C3PzqJf6DK0HoMG9nNoH0M+si6JDX4G2tjkKDrEe2DuF/Aa+2e5isbkfg5JLDEQWM3aygbuxcm5X8Q+xVcDMLkTUBJrLP0+rYb3oFwFsZTXvyn3kEsEstVSfvRNngNy+wwSLM3zMWT1EYlY+pgfG5uq+JQU4pJBiqrdOcG6VVIOiWgyyICaojJ3Do/BuAjS1y2Lukniiuf70sDl80jUS6xgrdx/Ev2OSCSPQ0SvN+4sqo1mjKgyyJLKw9nBQtISSdzBGNu+7L9+Pj6aMaISJNiCYIXoN0IiGYYVaRldUXUcG/MSK9C8hrkh+3NI4pt+hah4rE18xpxB/GCyeY0kEUj3a2zMWWRMglh2uLcQw/dEz1wiCQcifK+wouH7Yzo20gPWVjZM6fdrdfHeMPM7y6dhdf5EzJ+aiflTM+HXgHM3H6CtvRcHz7Sjue2+6BATilULJuDVpVORn5eF/LwsAMDF2z241N6L01e78X+abokOcWQILf0DrDjS7rsRC2Ipr3cCY09ZseCNl+bijQ3zhv29z8/wdwcv4cO6a6JDTQi2lMzGj16ZD4My/OVyxNuJt35zVnSoo0DVHkeRLaIjInmx5e2jq6AoHtHTHInGneEX4jtwuh12lwpZg2hoiACn1YSXn5ka9jGFFfWiwx5lTsqqxp3rmsN9fWSfQQyGraInOBLv/OniiF7/ytKpcJaaoQgvwBp/KERwWs0RyQEAb2yI76I1TPNvi+T1hnBfWPh2/VIQ+1Ukx8SSf71qOraUzI74uPy8LCyaPh6HWjrAZCoBABgUgrPUhJefibzwpWXhRFzu7MP5Wz2ipzE0RMvnvPDDf7l+9L2vw3l5+BnEwLYijm/rLp2TPeZjX1qSC6fVBKNMJTAaCE6rCS8tGXtV2NX5E0VPYyTSmYaws0hYgqyx1xUwFv6gIpg/NTOq4zcsyYWz1ASjIXUlSTMEllUbopADABZE+f9Cbxhh2xp7XVhF08MSRDMqWwGM/U90DAjdeoyGFxfnotpqRpoh9XYLpxsVOEvNeHFx9LtWo/1jFQOyfQrC+jw96pWwovLYAsZYXGcPANA4fX54YfEUVJeakG5MHUnGGRU4rSa8YOazpTsRPssRKVtXVB5eMNrrRr0KFJ9vGwDum+F5c+7mA25jrTcHJBmXlvySZKQpcJaasJ6THEDgC8T4h+Ua/OmjZpERr4CVle5ZBIrrW7shLrX3ch2vxDQFTqsJGUksSWa6AqfVjBIT379/iSEIAMa2rqx0zxrpJSP+31d87DUAM0TPIxwOnGmHj9c6K0iJaQqcpWZkpsflne2oyEo3wGk1o9g0meu4Pj/Dx6fbRU8vXGYGr/FhGVEQBkqYxprNbffx9wf4t48oLpgMp9WErCSSJGucAc5SE4oK+MoBAH934FJCPec22jU+rCArK+qWE/CK6AlEwgd113BAh79eRQWT4Sw1Yfy4xJdk/DgDqq0mrHuavxyfnG7Hh+7Eer6NgFdWVtQtH+73wwpiYErCZI/B2F0qDp7p4D7uuqcnw2k1IzsjcSXJzjCiutSMtTrIceBM4Lm2RMTAMOy1PqwgLEH7ljMAdpcXh1r4S7L26UlwWs3IyTCKnmbE5GQaUW014fmnJnEf++CZjoSVI4Dyh8P9Zsg/h4X2o0VQ6Ceiwx4rDMChlg48NT0LC6dF/wXiYOZMycDi2dlwq3fw0KeJnmpYTMg0wmk14Tkd5DjUEpAjEb77GIG5c0q2fXK9bs+Vx38xZAZhxsRcXg3GrzHYXSo+Pcs/kzy3aBKcVhMmZsZ/JpmYZYSz1ITnFk3iPvbhoBxagtsBABppQ17zQy+xRliTJRI+jcHmUnHkbCf3sdcsmgRnqRmTstJET3NYJmWlwWk1Y03+JO5jf3q2E/a9Kvycb62LY+i7WU8ssSzl9RsBvCU6XF5oDDjc0omCGeO5PyM0e3IGlszORv25O+gbiK/l1uTxaXCWmnR5svaItxN2lxc+f7LIAQCYPmv9lmM3jr5/YfAPn8ggxNh3REfKmwG/BptLRZ3KP5MU5k+E02rClPHxk0mmjE+D02pC4UL+chz1dsLuUjGQXHIAABgM/+rxnz25xCIUiw5UD/p9AUncakR79sPCsnAinKUm5GaLlyQ3Ow3OUjMsOshRpwbk6E+QmxORQ09c+49sflhla3iKjNo50WHqSUaagupSsy7fIje33Ydtr4qOrn4hc5uak46qzSasXDCB+9hu9Q5sLm/cLSX5Q095HEXfLLMeySCUpr0oOjy96RsIZJL6Vv6ZZOWCCXBaTZiaE/uNl9Ny0uG06iNHfesd2PemghwAgb04+N+PLrEYSkQHGAt6+/2w71XRcE4HSeYHJJk2IXaS5E1Ih7PUhBXz+cvRcO4O7C4Vvf3JLwcAMDzqwGOfQVhKCAIAPQ/9sLtUHDt/l/vYK+ZPgNNqRl4MJJk+IR1OqxnL5/GX41hQjp5+v+7ziBvYMIKsrDy+BKCFouOLJQ8e+mF3eXFcB0mWz8uBs9SM6RPH6Rb/9Inj4Cw1Y9k8Ls2UHuH4+buwuVQ8eJhCcgAAIX+l/eg3VUO/EcQw4HtJdGwi6O4LZJLPLtzlPvayuTlwWk2YMYm/JDMmjYPTasKzc/nL8VmqyhGEDLQh9N/fLrEIG8Y0WhLQ1eeD3aXihA6SPBuUZCZHSWbqKMeJC3dh26uiu8/HfeyEgYYQhIGlZAYJcb/XB/teFScv3uM+9tI5OXBazZg1Ofoep7MmZ8BZasbSOTrIcTGQObp6U1gOAMTwjQsKAKzecWQuQJNEByaaez2BTNL4FX9JnpmTDafVhNlRSDJ7cgacVhOemc2/AtPJi/dgd6m4n+JyBJkccCIoiM9AYRXRSgXu9gzA7lLh0UGSJbOz4Sw1Yc6UyCWZMyUDzlITluggR+NXATnu9Ug5QvhYwAkFABTNIAUZxJ0HAUmadNhbvXhWIJPMzQ1fkrm5gcyxeBZ/OTxBOe72DHAfO5FRFMO3ghAxKchjdD4YgN3l1aUAgXlWNpxWM+bljv508bzcTDitZph1kKOp7T7se1XceSDleJyQEwoAaBpMogOKRzq6B2Dfq+LUJf6SmGaOh9NqGvER/PlTM+EsNcE0M6pOxkPSfOk+7C4vOrulHEPBGAZnEMgMMgztXf2wuVR8fpm/JAVBSYYq9rxgaiacVhMKZvCX49Sl+7C7VHRIOUbCBAD02t69hotfzJKfzkZh2oR0VFv1+db6/K0HsLtUfPV1oDrkwmmZqLKa8NR0/nJ8fvk+bLUqvhb0xHEikb/sutGQYfoLM4H9R9HBxDs9D/04dv4Ols2bwP3xkSnZ6bAsmIimtvuBnYBWsy5yfHG5C3aXlCNcOm9NqiVL+dE/ApR/ER1MojB94jhUW01YqsO32Bdu94DAp5XD43x5pQs2l4pb9x7G4F1KFrQ/VojJW7yRcOveQ9j2qjh9tYv72IsGtVjmyemrgcwh5YgMYoYCRYMWXTuhFOTm3Yewu1ScudYtOpRROROU46aUI2I0aLkKKUpcd46KV24EJWmJY0larnXD7lJx466UYyyQomQrjGn8F9MpwvU7fbC7VJy9Hn+StFzrhs2l4rqUY8wwpuUoRCQzSBRcC0rivc6vw1W0nL3eDfteFdfv9IkOJaEhomyFMSYzSJRc7eyDfa8X6g3xknhvBL5TudYp5YgWxliOQpAZhAdXOgKZpJVjr8RIUW88gN3lxVUpBxcIlK0gzts7JxKXO3phd6lcG4qGS+vNQOa40iHl4Ei2AkAusThyqT0gyflbsWtkeS4ox+UOvo1MJchRIJdY3Glr74Xd5cWFGEhy/lYP7HtV7l1+JQACSyz5IV0Pvvq6F/a9qq4tkS/c6oHdpaLtaymHPrCc5G0CngoQHquuLOGNAhD/h4okgUfWN5t0ebYqxKK8LDitJizU8RypDXUpAIu/r4ETnAVTM1FlNWPRdP0vXCmJnrBuBYDMIByZPzW02Sl2F2xIEj2zVYrSpQCQGYQT83IDcjytwzbZ0ViUl4UqKQlvuhUml1hcmJubgSqd9pCHi8wknCF0KUTyQ3q0zJmSgarNZl2qj0RKflCSBdP4NixNSTTWrTAmM0g0zJ4cyBzmWeLlCJGfl4WaUjPmhlF3SzICRF0KkSIzyBiZFZRDj4qH0ZKfl4V3/o05qlrAKQ9j3QrTNJlBxsDMSeNQZdWnVi4v8vOy8Ff/djFm6NjEJ6khpUtRoHSIjiPRmBGUQ48q67zJz8vCf//+kpj2TEwWiKFTYeRvFR1IIjF94jg4N5t06c9x4XaPLs9u5edl4W+3PIMpcdDHPZFg5G9VGNJU0YEkCnkTAq2WdamJFXzw0OZSdXkKOD8vC+/+YCkmZhlj8VYlBQxpqrJo2RWZQcJgWrCbrB5tz87fegCby4uLwQxic3lx/hb/TVf5eVn4n1ufRU6GlCQcFi270qrs27zZD+CC6GDimak5gcyhR13eczcfwFb7bV1eIPCovK1Wn52J+XlZ2PPvnkVWukHX9ywJuLBv82a/AgCMQWaRYQjJoUcf8tYbD2BzqWgbYrNTW3svbDrtcc/Py8L7/34ZxhnlbocRUIFQhykF8nPIEORmp6Fqswkr5vOXQw3KMdJOwEvtgUyiR7WU/Lws/ON/WA6jQW4oGQoKJo1gBiGZQR5jyvg0VFnNWLmAvxze692wubxh7SG/3NELm8sLrw7F6fLzsvBPP1oBko48ASPtW0E0Rd7qHczk8WmospqwSgc5zl4PVDyMpPrIlY4+2HSq4JiflwXXf1rJfdxER9MGZRCjn0lBgkzKCshhWTiR+9gt17phq1XHVLfqamcfbLX61ALOz8vC3jelJIMxUsCJb5KrpdzdCWCy6MBEMjHLCKfVjNX5/OU4c5VPOdDQ8196fIt/8XYPNv+ymfu4CUinx1GcCwQzSJBPREclkgmZRlRtNukix+mrXbC5vFxq5V6/0wdbrVeX/iQykwSgQS4og36asoLkZBhRZTVhzaJJ3McOdXbi2YLgxt2HsLlUfHlFSqITTwpi8LGjoqMSQXaGAVVWE57TQY4vgnLc1KEFwU0piW74YPzGhW8EOVFV0gpKrS8Mx48zoMpqxvNPTeI+9ueXu2Cr9era9uzWvYew1XrxxWUpCUfUU47nz4X+8chXqcRSZ5mVNS6QOdbqIMepS/dhc3lx+77+3WRv3e+HzeXVpY97KkpCYAcG//sRQRihQXSAsSAz3YCqzSase5r/TbvmS/dhc6n4OgZyhLh9vx+2WhWnLklJooYZ6gf/8xFB/AMDSS9IRpoCp9WEogId5GgLyNEuoA/51139sLlUNLdJSaLBD/aIA48Icsq5oQ3AZ6KD1ItxaQqcVrMucjS13YPN5UWHADlCtHcFlltNUpKxcrx5V9GlwT944nFOhuRcZqUbA5mj2MRfDs9X92CrVdHRPSB6mujoHoCt1gvPV/e4j530khB74tp/QhAC/V50nLxJMwTkKDFN4T5248V7sLlUdD4QL0eIzgcDsLlUNEpJIoLoyWt/yOc4LeXuLwEsFR0wD4wGQrXVjBcW85fj5MV7sLtU3O2JHzkGE3quTI+nA87dfIDX//aU6Cny5EuPo3jZ4z8cescMYb/oaHlgVAhOq0kXOU5cuAtbrTdu5QCAuz0DsLm8OHHxLvexn54xHr/50QrRU+TJkNf8kIKQT0t4QQwKocpqwouLc7mP/dmFu7C5VNzr9Yme5qjc6/HBVqviswt3uY9tmjkeH/75ctFT5ILC/ENe88NulbGUu48BeF504GOaLAHOUjNeWsJfjuPn78LuUtHVF/9yDCYn0wjnZhOe0+GL0S+vdOEHv/5C9BTHDIGONTqK1g31O2WEgxIyixCAKqs+chw7dxc2lzfh5ACArl4ftrtUHD9/l/vYz87Nwe5tz4qe4pjRGBv2Wh9WED8l5jKrymrCy8/wl6Ph3B3YXF509/lFT3HMdPf5sL3Wi2Pn7nAfe8X8CfjVDxP0vk6acdhrfdjaLzeP/sOt2SU/KAFRvuj4w+X7JbPx+tpZ3Metb70Du0tFT3/iyhFiwM9wxNsJ08zx3Ku/z5qcgfEZRl2ylF4w4EDTT9f9fLjfj1j3hZHhI9ETCJeVCybgR6/M5z6uu/UObEkiR4ieh37YalXUt/LPJH+6bhbe2DBX9BTDhsBGXCmNKIhm1PaBcFP0JMLhlWemwqjwLc9Rp3bCVutFbxLJEaKn3w+bywu3yl+SN16alyBdrthNzUj7RnrFiII0VxZfZ4ztET2NcJg/le9yoU7thM2lom9AEz013ejt12BzeVGndnIfe4UOhfZ4w4A9zZXF10d6zail9TSjcTcA/u8gZ3g2zjzq7cT2WhUPk1iOEH0DGmy1Ko56+f4vXj6ff5lWznQGr+0RGVWQU5Vr2wiI+yzCa3V15GxAjn5f8ssR4qFPg82l4ghHSSjOq9ERsOdU5dq20V4XVnFWxch2I87bRfPoq/Hp2Q5sd3kx4E8dOUL0+zTYar349CwfSc7oUHWFI92Kn42aPYAwBTlRWdIKxPdnkUthlPEcicMtHbDVqvD5meipCGPAz2BzeXG4JfqmY3qUJeIH23OiqiSs+gthl/cmTdkNQNxuoFE4fWXsCe5QSwdsLhU+LXXlCOHzM9hcKg5FIcmnZztw5mrcLjj6SfOFlT2AEb4ofJzrde/dnvXC1pkAVoue4VCoNx7ANCsbCyK8m3XwTAfsLi+kG9/CWOB9yc/Livh2bfOl+7C7WuP3MxzRrzy71r8f7ssjaxDh98f1Muut35yN6PUHTrfDVivlGAqNBZZbB8+0h31M680H+MnvWtEdx8+qESjs7AFEkEEA4EbdP9yYtX5rJoBi0RMdjl8fvgIQRiw+7fMz/I8Dl/DO//tKdLhxz4HTHXjo01C4cCKUEe5M/fOJm6j83+fQ3hW/+2MAVu1xFP+vSI6IuFldupFq+v3su2BYInq6w/HrQ1dwpaMPq/MnYv7UTMyfmgm/FtgF19bei4Nn2nWp/pGsfFB3DV9e6cKrS6c+suy6eLsHl9p78fnlLvzfU7dFhzkyhJb+AVRHftgYsOxwbwHhfdFzlkjChYhtadxZ8mHEx431hKsq3L8jhu+JnrhEMjr0O4+j6LWxHDnmLo4GUDXi+LavRBKkX6GBmrEePGZBTu4sOglEvqaTSGJM9cmdL54c68FR9QHuNQ7UADgl+h2QSIbh1EBfTlR/xKMSpKVyQzcxNub0JZHoCRFVf/Hz5VH10I66k3zjrpLfgvCu6DdDInkEhncbdxbVRjtM1IIAQEY2ygAcF/2eSCRBjmcMoIzHQNwe2l9V4S4hho8BZAh7WyQSoI8RNjbtLK7jMRiXDAIAgYCIi7USydihMl5yABwzSIhVFfV7iLEfxvZNkUgAxth7TbtKtvIck1sGCWH0GcsAaord2yKRAABrMmoPua9gdNk4vPInDS8rGvsYYNwFlEiGQNM0bWPzz9Yf5D2wLhdw80/XHQT43EWQSEaHlekhB6BTBgmxqtz9CwL+i57nkKQ4xH7h2Vnylm7D6x2/pdz9PoAtep9HkpJ84HEU/5meJ4hJ8SJLef1HANsUi3NJUgW23+Mo+a7eZ4lZda/CcncDA9bG6nyS5IWAY42O4nXRjxTWuWKHpcKtgqEglueUJBmMtXp2lZhidbqY14e0lLvbAfDvcCNJBdo9juJpsTyhkAKqlnK3LLQjiQwG5tlVHPPv1YR8kedxFBOAKyLOLUlILouQAxAkCAB4HMXzGKhB1PklCUO9x1HMv3VYmAh9FKTJUVREgEtkDJL4hYFqPY5ioUUKhT8r1egoLgXob0THIYk32N80OYpeFx2FcEEAwOMo+jEBfyk6Dkl8QMBfehwlPxYdBxAnggBAo6O4jIF2iY5DIhgiR6OjOG4edI27PlmW8vq3ANTIR+VTDg1gZR5HyTuiAxlM3AkCfLOfpAZgq0THIokF1KQpVBbcJhFXxKUgALDG/lmuz+irkdt3kxvG6D2jZiw7UfVc9H3fdCBuBQlhKa9/E2A1kNVSko0+gMo8jqJfig5kJOJ+ne9xFP2SETZC1t1KJo4zwsZ4lwNIAEGAQEmhjBxslBUckwDCuxk5/OpW6R9uglG4o+51RlQGYIXoWCQRcYoYq2ncVfJb0YFEQsIJAgBLKg9nZ/rSygBsB5AuOh7JiPQDqO41DtS0VG6I297Qw5GQgoRYXVG/2g+2XXa6ik8Y4Z8NoOpgL5mEJKEFCWHZ4d4CBWXx3Fg0pSC0QEONZ1fxB6JDiX4qScLayoYp/T5WBrDtomNJbag63Ug1xyrXdYqOhMtsRAfAG8vbR1fBYNgKxrZBfj6JFf0g2g2/f4/nZ+uTquxs0gkSovDt+qVM0bYBtBVAtuh4kpRuIuyGn/Y0/qzotOhg9CBpBQmxprKuQPPRNgZsBTBFdDxJQicR7VZ82p4TVSWtooPRk6QXJMSKymMLFJ9vGxFtBcMM0fEkKDdBtNtvUPacqlzbJjqYWJAygoRYvqNhtgHaawrYJkb0suh4EgEGHCCw/ZqR9jVXFl8XHU8sSTlBBrOyom65gSmbWKAs6vOi44kzjhPYfj9hf/POks9FByOKlBZkMIUVR4sYlE1g2ARgqeh4hMBwGoSPFKbsP7lrnaw4AynIkFjK6zcSY98BoZgBhaLj0RMCGjWgjkC/9ziKPhYdT7whBRmFVZUNT5FfexEMJQArAWih6JiignARjLlBVMcGBj5tcm44LzqkeEYKEiGrK9zPMGADGDYw4CUAk0THNAp3CDgMhsP+NO1Qc+X6FtEBJRJSkChZvePIXJ8hrUDRWAGgmRijAiIUAFgU41AuMMZaFYVUxqhV0/ytRmKtJ3e9IEu8RoEURCde28sM55vrTIoBBcQMBZqi5RKUbMa0HCLKZozlECgbgW/5cwDKBlhO4GjqAlg3gC4A3Qysm4i6GGPdREoX07RuBUoHI38rQ5q6SL3Sum/fZr/oOScj/x/TB/10FjyBwQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wNi0yNVQxNzoxODowOCswODowMOxzWmIAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDYtMjVUMTc6MTg6MDgrMDg6MDCdLuLeAAAAAElFTkSuQmCC\" alt=\"\" srcset=\"\">\n        </DoCol>\n      </DoRow>\n      <DoRow>\n        <DoCol :span=16 :offset=6>\n          <div class=\"add-btn\" @click=\"addQuery\">新增query参数</div>\n        </DoCol>\n      </DoRow>\n      <DoRow>\n        <DoCol :span=16 :offset=6>\n          <div class=\"add-btn\" @click=\"handleQuickEdit\">跳转</div>\n        </DoCol>\n      </DoRow>\n    </div>\n    <div class=\"history-record-container\">\n      <div class=\"history-record-title\">历史记录</div>\n      <div class=\"history-record-list\">\n        <div class=\"history-record-list-item\" v-for=\"(url, index) in historyList\" :key=\"index\">\n          <DoRow>\n            <DoCol class=\"history-record-list-item__url\" :span=\"18\">{{url}}</DoCol>\n            <DoCol class=\"history-record-list-item__icon\" :span=\"3\" @click=\"jumpToUrl(url)\">\n              <img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAutQTFRFAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////4uz3xdrup8fmrMrnzN7w6fH55e73bqLVM3zEhbHc8vf7x9vvXJbQydzvutPrtdDq/v//OH/FfKvZpMTlm7/iqMfmkrnfiLPdibPdYprSgK3aPILGd6jY+vz+WJTP8fb7i7XeZZzT6PD4t9Hq3+r24Ov2U5HN1uXzfavZSovLzd/xRojJQYXIxNnuqcjmOH/Gu9Prss7piLLdoMLkkLjfl7zhmL3hjrbeocLkc6XX+Pv9vNTsNH3Eap/U7/X6xtruPYLHYZrR5u/40OHxRYjJ3en12ef0TY3MT47M1OPz4+33VZLORonKy97w7fT6XpjQPYPHwtjt9/r9aJ7TudLr7vT60+PycqTWsMzo0uLyk7ngwdftdafXnsHjZZzSy93wa6DUUpDN7PP67fP6jbbeUI/MyNzvc6XWpcXl9Pj8lbvgNn7Fg6/bYJnR2uf0eqrZ/P3+ncDjTo3Mv9btcaTW4ez2gq/blLrgpMXlZ57TWpXP8/f7irTdwNbtTYzLzuDxb6LVkbnfXZfQ4u33tM/pVJHO6vL5d6fYTY3Lmb7hsc3o2+j1TIzLbaHVmb3hOoDGWZXPpsbl0eHyQ4bJdabXQIXIoMLjO4HG3ur1fqzafazZ3On1vdXshbDcutLrs87pw9juZJvS9vn85e74hrHcdKbX1OPyn8HjSYrKlrvgjrfebKDUUY/NOIDG9fj8Y5vSSIrKuNHqQITI1OTzk7rgW5XPh7Lc5O73eKjYi7XdcKPWkLffV5PPOoHGwtjun8LjS4vLr8zoNX3Er8voPILHRIfJUZDNz+Dx1+XzW5bQe6rZosPkqcfm3Oj11eTz3ur2+/3+4Vk3TwAAACd0Uk5TABiAmLDI4fngfxdHnyrKySHUxBO0BZdV+Isc7B7fxayVtwbGFMsi6XbypQAAAAFiS0dEAf8CLd4AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAYDSURBVHja7dt5XJRFGMDxkd1lJVjKDlG0+5gSkFHI0iLLEBMiscw88gi1tLSMLs1M7DAPLLUozMwyO8UOU7q02w7TopPuzO47u/8MXFb3mnnnet+Z4TO//332+bq77PDuCwCttUvx+QOpQWhMwfYBvy9tDxBbeobqvXjLSI9ihDJVryPSnnvtcnRQvYtYe+/TNhwQ7rvfTojRr6twHVscWaq3kFCnUDPEr3oLGXUGIFv1DlLq0hWkqd5BTinAp3oFOfnaxlsEQj8IqF5BTgGQqnoFOaWCoOoV5BQEqjeQlYXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXoloXI6cijuuXk5hkP6Z6Pdtajp9mQAhSp8GiTIb1QVMeYCzm2dzQE5RgL6YNiO85QSAGK73gzIUUJEHSCkZC+iRB0oomQk5JAUD8DIScng6Di/sZBSpJCUP4A0yCnJIeggaWGQcowEFR4qlmQchwEnTbIKAiswEoGn24UpPwMrGTImSZB4FAsBJ01zCQILMZL0HCTILAfQTLUJAgcQZCMNAkCzyZIRpkEgaMJkjEmQeBYgqSHSRDST2HUxyQIHE6QnFNpEASOG4+XTDjXIAg8byJeMul8gyDwgsl4SckUgyDwwovwkqkXGwSBVZfgJZdeZhAEXn4FXjJtukEQeOUMvGT8VWohM6/OZWkW4QOlWhVk9jXFhUOQxLopgVx7nUxDuOu9h1TPkc9ATt8FyYfcMNcVBkLz5nsJWVDjEgM5XKyXDKle6J4DoRu9gsy+yU0GQou8gSxecrO7DnSLJ5CyEvFNHar1AHLrba4zEJroOqRu6TQPHOh2tyHLar1gIHSHu5Dld3rDQGiFq5C7JotvSFmZi5CCuz1joJX3uAbJW+UdA6F7SasIQXLu89JRQ9xFAHL/A14yyC8sAciABz1loIdWk/fhhdSv8dYx52GHhfgggx6hefBHH1vLcgfp4+vwo9Y73r7JA6lseIKC8eRTbPeOPv0MftYG53ttOCDDCikYG59lnFrwHH5YBcW/Z4Y8/wLNq+rFlxjHvkwYtolmACtkLM3FqpWvsP73vEqYVkM1gQ3y2usUjN6jNrM6SF+/LaEbwQJ5YwvNq2rrm6wM+BZh3FLKGQyQokYKxry1zAz4NmHeItoh1BC6627vzGR3kK6E0d+fTQlZMIaG8S7NNzLxbSIMrKcfQwepfo+C0VjEwUi4szyq98cxzKGB0F1329Kdg1G3FT9wI9PtZ86QD6iuuzVRfDuWWE/CG+/DXKZRjpCPaK67fcx6IAlX2oQfufATtllOkE9pXlXMB5Jw5Z/hR5awjnSAfE7BYD+QhKskPB+1X7BOI0O2OTM4DiStfYkfOmM+8zQiZLqzg+NA0lo9fuj2xezjiJDtTgyeA0mk9dip2+o4xpEgjk8Iz4Ek0nLsVL4/IiFBHL4O5DqQ7GokbuwIvnkkyAYSg+9AsruBmLlfcc4jQb4mOLgOJNFhvosfzTuPBPkGy2j6VpCBg3zHPY8E+R7D4DyQxJb0peV0wwknZEJyRwXfgSSuFUkm/yAwjwRJ+is674EkvsTrPz+WicwjQX5KZPAfSOJbHT/652VC84if7AlvSP4DSWJxz/cvBWLjiJCq2Mf6VeBAktjmSdGz1/0mOI58+m2IfiyRA0mypkTdWLrmd9FpDr+PVOVHHmqu0IEkuWRHZPgfoh+vFL/qNswajNDUVb2kM5rr/2djyw+QHX9JmEVzFSXvbzcUYUrpP//+J2WSPvf9WoiF6JmF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6JaF6BYIqt5ATkGQqnoFOe0PAqpXkNMBwK96BTkdCHyqV5DTQSBF9QpySgPtVK8gpS5dAchQvYSMOgMA0lUvIaFOoWYIyFS9hngdWxwg1EH1HqIdfAhoE5JDDwOthQ5XvYtImUeA3WUZ+wHvzwKxZaf5/IHUoOq96Au2D/h9admR/f8HhQjj09PWljsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDYtMjVUMTQ6MTk6NTcrMDg6MDAA0yMfAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA2LTI1VDE0OjE5OjU3KzA4OjAwcY6bowAAAABJRU5ErkJggg==\" alt=\"\" srcset=\"\">\n            </DoCol>\n            <DoCol class=\"history-record-list-item__icon\" :span=\"3\" @click=\"delHistory(index)\">\n              <img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAAGl1JREFUeNrtnXtwVNeZ4H9HEg8JCYQkY8xLPHWbh2PAjl+AXzDenZrM1OxkQiubWjsbvLXZTGVeroHmj63a2tktNcw4M1OZ8nq9djZOzUzUTqamau2qZGyMk2BjHMc22Dz6ykZGYGwshAAhhBCSvv3j3gZZ1qMf5/a53X1+VV2y2tK532nuT9/tvud8n8ISCCJSDjhAk/+oB6qBmlFfRz8HcAnoHfG1d4znzgFtgAu0KaWGTM+5GFGmAyh0RGQhNyQYKcSyPIdynBHCpB5KqVOmX6NCxgqSISLSBNwH/Jb/mG06pknoBl72H79SSn1gOqBCwgoyCSLSCNwLbMATosl0TDni4smyH9ivlOowHVCYsYKMgYg8DPwbPDHuNh1PwBzAk+XnSqmXTQcTNqwgPiJyL/AV/3Gr6XgM8T7wIvCiUmq/6WDCQEkLIiK3cUOKYs8UmXIAeAFPlvdMB2OKkhNEROYDf4gnxRbT8RQIe/Ayy0+UUp+YDiaflIwgIrIYeAzYBsw1HU+BcgZ4BnhWKXXCdDD5oOgF8T+WTYlRZzqeIqGbG6K0mQ4mSIpWEBFZww0xqk3HU6T04onyjFLqiOlggqDoBBGRdXhiPAZMNR1PiTDADVHeNR2MTopGEBGZDezwHxZz7AJ2KaXOmw5EB0UhiIg8gifGKtOxWAA4iifJj0wHkisFLYiIfBnYjvexrSV8/BTYrZR6y3Qg2VKQgohINZ4YO7DvM8LOAN5l126lVK/pYDKl4AQRka/jybHWdCyWjDiIJ8mPTQeSCQUjiIjUALuBb5uOxZITTwHblVKXTAeSDgUhiIhswpPDrpcqDg7gSbLPdCCTEXpBROS7eHJMNx2LRSv9eJJ833QgExFaQUSkHk+Mb5mOxRIoP8AT5ZzpQMaizHQAYyEim4GXsHKUAt8C/tX/Nw8docsgIvI4XuYIpbyWwBgCdiilnjAdyEhCJYiIPAH8uek4LEb5nlLqcdNBpAiNICLyQ+BR03FYQsFzSqlvmg4CQiKIiLyAt8PPYknxolLqd00HYVwQEdkP3GM6Dkso2a+U2mAyAKOCiIhL4deZsgSLq5SKmDq4MUFEpAuvXq3FMhldSqmbTBzYiCAiIiaOayloRCmV94/+835AETmZ72NaigIlInkvk5pXQUTkdWBhvidpKRoWichr+Txg3gQRkVa8WrcWSy5sEJG87SnJiyAi8rdANF+TshQ9zSLyN/k4UOCCiMhu4E/yMRlLSfGnIrIr6IMEKoiI/CXwF0FPwlKybPfPscAI7GNef1XuXwcZvMXi87hS6ntBDByIICP2c9gl65Z8MAQ8rJTaq3tg7YL4OwFfAtbn4YWxWFK8jSdJt85Bg/gLvxsrhyX/3I537mlFqyB+gQW7TdZiim3+OagNbZdYfmmel7DVRyxm6ce71NJSUkiLIH5Rt5ewdass4eAAniQ5F6fTdYlli7pZwsTdaHo/knMG8Wvl/pPpV8RiGYOvK6VacxkgJ0H8Kuv7sIWkLeHkILBRKXU52wFyvcSyVdYtYWYtOXYcyzqD+M1rXsP257CEmwG8LJJVE59cMohtXmMpBKaSQxbJShAReRT4qumZWyxp8lW/j2XGZHyJJSJ1eG/MQ90w82eHzvJW+0U6uq7Q0XWF8jJYMXcGixsq2by6gXWLZ5oOsaB450QPLx/uor2zj/bOPgCWzqmisaGSNQuq+b31N5sOcTKO4l1qZdR9NxtB4oS81fLTe0/y9Kunxv3/FeWK72xu5JFN802HWhA8t+80T+7pYGh4/GI090fqeOIbK02HOhm7lFKxTH4hI0FEZD3eqsnQcsd/fT3tn92ypoGWqGO+vGRIEYFYwuWVI11p/85v/tJoIcR0WK+UejfdH870Pcg207ObiMf/8VhGP7/ncBex1iTDtkrXFxgWIZZIZiQHMGHmDgmPZfLDaQsiImsyHTyf/L93PuOXycy3Arxy5Byx1uSElw+lxtCwEGt1eeVI5k2fnt57kp8fOmt6ChPxmIisTveHM8kg2wjxx7qHP86+Bffeo+eIJVwGrSQMDnly7D2afUe0t9ovmp7GREwlgz/0aQkiIk2ZDGqCjq4rOf3+q0fPEWt1GRwqXUmuDXmXVa8ey61d4Ikc/y3ywGP+OT0p6WaQbUC16VlNROqjx1z4xbFz7EgkuTY0bHo6eWdgcJhYa5JfHMt9x2pHV+7/FgFTTZrvpycVREQWE/LsAVCm6aOoXx7rZkery8Bg6UhydXCYWMLN6j3cWChVEJ8LbvPP7QlJJ4M8BtSZns1krJg7Q9tYv0p6kly9VvyS9F8bJtbq8itNcoB3A7EAqCeNLDKhICIyL51BwkBjQ6XW8fa53cQSLv1FLMmVgWFiiST7XK2FQApFEPCyyLyJfmCyDPI1YK7pWaTDltUNVOi6zvLZ53YTa01yZWDI9PS00zcwRCyR5DU3o5UXk1JRrnh4TYPp6aXLLXjn+LhMJkjBNNZct3gm/2VLo/ZxX2s7Tyzh0ldEkvRdHSLW6vJ6m145AL6zpbHQ1rlNeI6PK4iI3AZsMR19Jjy6aT5bAvjr9XrbeWKtLpevFr4kl68OsSPhsv8D/XL81poGHtlYcOvbtvjn+phMlEEKJnuMpCXqsHm1/taH+z84TyyRpLe/cCXp7R9kR2uSNwKQY8tqb11bgTLuuV50giigJRrhoVX6JXnjgwvEEkku9Q+anmbGXLoyyI6Ey4EPL2gfe/Pq+kKWA+B3xvsfYwoiIhso4DI+ZcrLJA8GIMmBDy8Qa3XpuVI4kvRcGSSWcHkzADkeWuXJURi3PsblHhEZs/vZeBmkILPHSMrLFC1RhwdW6pfkzeMXiCVcLhaAJBf7Bom1Jnnz+AXtYz/oy1FW4Hb4jHnOF60gABVlinjU4f6V+u9z/vr4BWKtSS70XTM9zXG50HeNWCLJrwNYPPjAynpatjqUa/5o3SDpCSIiDwNrTEeri4pyT5L7Ivoleav9IrGEy/nL4ZPk/OVrxFrdQFbW3h+poyXqUFFeNHIA3Oqf+59jrAzy26Yj1c2U8jLiUYdNjn5JfuNL0h0iSbovXyOWcPnNR/rluM+XY0pxyZHi345+YixBNpqOMgimVniSbAxAkrc/ukis1eVcr3lJzvVeI9aa5O0A5NjkeHJMrSjaxmFfOPc/92dARJYDH5iOMkj6rw2zozUZyF3kdYtnEo861Feb2VfWdWmAnc+7vHuiR/vYG53ZxKMRpk8pWjlSLFdKHU99M3q2D5iOLmimT/EyyYam2drHfvdED7FWl65LA3mf19lLA8QSwcixoWk2LVtLQg4Y5cDoGW8yHV0+qJxaTkvU4d4VAUjS0UMs4XK2J3+SdPYMEGt1OdihX457V8ymJepQObUk5IBRDpSkIABVviT3LK/VPvbBjh5iiSSdeZCks+cqsUSSQyf1y3GPL0fV1PLA5xEixhZERFYBS0xHl09mTCunJRrh7gAkOXTyErHWJJ9dvBpY/J9dvMqOVpf3TubcSOkL3L28lnjUYca0kpIDYKnvAvD5DPKQ6chMUD3dyyR3LavVPvZ7py4RS7icuaBfkjMXrhJLuLx/Sr8cd5WuHCkeTP1H2VhPlho10ytoiTrcGYAk7/uSfKpRkk8DlOPOZbXEtzpUT6/QPnYBcd2F6x/zish5oNZ0ZCa52DdILJEM5O7z6gXVtGx1mDc7tybAn5zvZ2fC5cjp7OuAjcedS2uJNzvMrCxpOQDOK6XqwM8gIrKQEpcDYFaVl0nuWDJL+9hHPu4llnA5fb4/6zFOn+8nFpAcX146i5aolcNntu/E9UustIpolQK1VVNoiTrcHoAkR0/3Emt1+bg7c0k+7u4n1upyNAA57ljiyTGrysoxgiawgozJ7BmeJOsX65fk2CdeJjl1Ln1JTp3zMsexT/TLcbsvR23VFO1jFzhWkImo8yUJogBB8pNeYokkJ89NXqLz5LkrxBJJkgHIsX7xTFq2OsyeYeUYg88JUtD7JYOivnoKLVsd1jbql8T99DKxhDthTeGOrivEWl3cT7PuYjwu6xpn0hKNUFdt5RgHm0HSoaFmKvGow22L9EvS5ksyVrHnE11XiCVc2s7ol2Nt40xaog71Vo6JcACUiJQD4d87apizPQPsSCQDuWu9/OYqWqIRltzkVYf86OwVdiZcPvxMvxy3LZpJvNnhpprQdrIIExVKRFbiNTi0TEJnzwA7WpOB3KBbdnMVLVu9K92dz7sc/0x/hfQvLaohHo0wZ6aVI01WKRH5feBfTEdSKHx28So7Ei6Hg5BkThWCnlYOo7l1YQ3xqMPNs6bl4VUqGv5dGfb9R0bcPGsa8a0OaxbUaB/7+IgWyzpZs6CGFitHNjSV4ZWBt2TA3NpptEQdVs8PdU8hwF/iEnWYa+XIhvoyQt45Kqzc4kuyKsSSrJpfTUs0wi21Vo4sqS4D9F8rlAjzZk+nJeqwcl74JFk1v5p41GGelSMXamwGyZH5viSREEmycp6elcMWm0G0sKBuOi1bHZxb9LWBy5bILTNoiTrMr7NyaMBmEF0srPcySZPGXomZ4twyg5ZohAVWDl1UW0E0sqi+kpaoo7WhaLo0zfUyx8J6K4dG7CWWbhobPEmW35y/RpYrfDkW1ettZGqxl1iBsLihkpZohGV5kGS5v0RFd5dfCwDVSkSuAnZxTgC0d/YRS7iB3B0Hb/1WPOqw5KaCabtcaAyUTLm8okRMB1D8lAH6V91ZvCXrzweXPcBbuxVrdfnobHDHKHEulQH693KWOCe6rrAzkQxkyfporksSoIglTK/NIJrp6EptdsrfCXs84Pc6JYzNIDo5ec6T44MAtslOxvHOPnZaSXTTawXRxKlzXsXDIPaQp4vNJNq5ZC+xNPBxdz87nw+m+kimpD5aPnF28pJClkmxGSRXTvu1coOoW5Ut7Z19bG9NciqNuluWCbEZJBdShaSDqHiYK+2dfTz+T8mcagFbbAbJmk8vXGVnIphaubpo7+zjz/7hGGcCbOJT5FwqA86ZjqLQOOPLEUSVdd20d/bxxz86mteeiUVEdxnQZjqKQuKzi1eJPe9y+ONgyv4snaN/XVV7Zx9/9NwRukPQx73AaCsDXNNRFAqdPV6r5UBqYt1cRUvUIR51AlkF3N7Zx7f/72Eu9tkimhng2tKjaVIspUeXzqni2f90KzWl3WItXSrKlFJDwHHTkYSZrkte5ghCjhVzZxBvviEHwJKbKok3B7Mzsb2zj23/5336BoYCfc2KgONKqaHUcnf7PmQcUnIE0Ye86ZYZxKMOi8fY7LS4oZJ4QHvc2zv7+Ob/fo+rg8OBvGZFggs32h/Y9yFjcK73GjufdznYoV8Ox5djop2AjQ1eJgmiWkp7Zx//4X8dYnDIbioZhza4IYjNIKPovnyNnQmXd0/olyMyr5p4NJLWHvJF9ZXEo5FA6m61d/bx7588iFhHxsIKMh7nfTneOaG/HfTKeV7Fw0yqjyysn048oAqO7Z19RP/+Xe3jFgFWkLG40OfJ8fZH+uVYNb+aeLOTVd2qBXXTiTcHUwu4vbOPrd+3koyiDUClvhORbmC26ahMcrFvkFgiyVvt+uVYvUBPOdDU+q8g7uIvnVPF899dp33cAqRbKVUPNzIIwMumozJJz5VBdj7vBiLHmgVeZycdtXLnzZ5OvDkSSH8Sm0muc90FKwhwqX+QnQmXXx+/oH3sVGcnnS0IbqmdRjzqcOtCK0lAjCnIr0xHZYLe/iF2JlzeDECOL/lyzA2gBcFcK0mQXHdBjXxWRFxKqCXb5atDxFqTvPHhBe1j37aohpY8NMzs7Lka2F3+En1P4iqlIqlvRheOK5nLrL4BL3MEIcfaxpl56yY7Z+Y04tFIIH3cSzST7Bn5zWhB9puOLh9c8eXY/8F57WOva5xJPOpwUx5bLc+ZOZV4s8PaRiuJBl4f+U3JCdJ/bZhYwuX1tgDkWDyTeLNDQ03+Sx3fVDOVeNRh3WIrSY58zoHPCaKUOgG8aTrCoLgaoBzrF88iHo1QX22uDnhDzVTi0QjrrSTZckAp1THyibGKVxdlFhkY9OR4ze3WPvbtS2YRb3aor55ieprUV08h3hzh9iWztI9dApJ84dwfS5Cfm45SN9eGPDn2BSDHHUtnEY861M0wL0eKuhlTiEcd7rCSZMoXzn011k+JyPvAGtPR6mBwSNiRSPLLY/rl+PLSWbREHWqrwiPHSFLryoJYHbBi7gx+/EdrTU9RJ+8rpb40+snx+oO8aDpaHQwOC7GEG4gcdy6rJd4cCa0cALVVU4hHI9y5VH8m+eDMZb7x5EHTU9TJmOd80QoyNCzsTLj84pj+qkZ3LaslHnWYVRn+fd2zqiqIN0e4a1mt9rHdTy/zyFOHTE9RF+kLopR6HThgOuJsGRbYmXB59ah+Oe5eXku82WFmAciRYmZlBfGow13La7WPffR0L//x6fdMTzFX3lBKjfnh1EQt2AoyiwiwM5FkbwBy3LOilng0UpAVQWoqK9gVdbg7AEneP3WJx5553/QUc2Hcc73oBNmZcHnliH457l0xm3g0QvX0ctNTzJrq6RXsao5wzwr9234OdvTwn39w2PQUsyVzQZRSh4BXTEeeCT/ad5o9h7u0j7uhaTbxZocZ0wpXjhQzppWzK+pwbwCSvP3RRf7mZx+ZnmKm7FFKjXuNOFmX2xdMR58u757o4ck9HbkPNIqNTbOJRx2qpha+HCmqppUTb3bY0KRfkn/c/wlPv3rK9BQzYcIrpckE+QlwxvQM0mHPkS4Gh/WW59jk1BFvjlBZRHKkqJpaTjwaYaOjX5Kn954slC5XZ/DO8XGZUBCl1CfAs6ZnkQ4dXXqbxWxy6ohHHaZPKd5W8pVTy4hHI2xy6rSPfTCAQnsB8Kx/jo9LOv/6zwD677RpRmfjzPsidexqdphWxHKkmD6ljHizw30RvZIc6gh9X6ZuvHN7QiY9A/wVvqHPIrquru5f6ckxtaL45UgxraKMeNThfo2SSPir0T3rn9sTku5Z8Awh70Slo6/GAyvr2RWNMKW8dORIMbWijHhzhAdW6pFkdQBVVzTSSxrZA9IURCnVRsizSGMaZTwn4sFV9cSbHSrKVU7jFDJTyhXxaIQHV9bnPFYQZYk08qx/Tk9KJn8qnwFC28drzcLsKw4+tKqeeNShoqx05UhRUa6INzs8tCp7SR5YWc/qBforQGpigDSzB2QgiFLqcCYD55vfW39zVtfQm1fXE2+OUG7luE55mSfJ5tWZS7KucSb/7Q+Wm57CRDzjn8tpkenFdqgvs574xsqMfn7LmgbizRGsG1+kTHmXW5tXN6T9O01zZ/Df/7CJ6nCvVcvoj3zGp4aIxIEdpmc5EU/vPTnh3dyKcsV3NjfyyKb5pkMtCJ7bd5on93QwNMFHhV+9cy7f2dzIrKpQy7FLKRXL5BeyEaQO2AesMj3bifjZobO81X6Rjq4rdHRdobzM2wW3uKGSzasbAqn+Ucy8c6KHlw930d7Zd/0u+dI5VTQ2VHLbohp+Z+0c0yFOxlFgo1Iqo4odWV1ciMijwA9Nz9hiyYBHlVI/yvSXsr76FpGfAl81PWuLJQ1+qpT6Wja/mIsgXwZeA8wVgrJYJmcA79LqrWx+Oetbxv4Bd5mevcUyCbuylQNyyCAAIlKN94Z9relXwWIZg4N42SPrlaw5LTpSSvUCu02/ChbLOOzKRQ7IURAApdSPgadMvxIWyyieUkq15jqIlnvIIlIDvATcbfpVsVjwSlY9rJTKeVOKtkUWIrIJT5LcO1VaLNnTjyfHPh2Dadv44Ae03dSrYrH4bNclB2jMIClE5FngW3l9SSwWjx8opbbpHDAIQerxLrXW5+tVsViAd/AurbRWDQxkobeIbMaTpPT2rlpMMIwnh/ZCh4GcwH6g9v2IJV9sD0IOCCiDpBCR7wF/FuQxLCXP95RSjwc1eOB76UTkh8CjQR/HUpI8p5T6ZpAHyMtmUxF5AfhKPo5lKRleVEr9btAHydtubBHZD9yTr+NZipo3lFL35uNAeS1XICIu0JTPY1qKjjallJOvg+W9noeIdAG5VyazlCJdSqmb8nlAIwVvpAAKt1pChyil8n5fzciNPKWUAgqqy4rFKCdNyAEG73QrpRYB+3MeyFLsvK6UajR1cKNLQZRSG4CEyRgsoaZVKbXRZADG10oppZqBvzMdhyV0/J1S6uumgzAuCIBS6k+BvzIdhyU0/JV/ThgnFIIAKKW2A//TdBwW4/wP/1wIBaGray4ij+NVSgmNvJa8MIy3KvcJ04GMJHSCwPX9JLuxm65KhXcIcMl6LoRSELi+M3E3dvtusfMDPDm07gTURWgFSSEi38UTxVZLKS768cT4vulAJiL0gsD1kkK7sXW3ioUDaK4+EhQFIQhcL063G/i26VgsOfEUnhw5F3XLBwUjSAoR+Trefve1pmOxZMRBYLdfqrZgKDhB4HpV+e14vRJtf5JwM4DXJmO3X+y8oChIQVL4TXx2YDtdhZV/Jsf+HKYpaEFS+D0TtxPyxqIlxFG8jPGc6UBypSgEgevdd1OXXRZzpC6nuk0HooOiESSFiKwHtgGPYd+f5IsB4BngWaXUO6aD0UnRCZJCRNbgSbINqDYdT5HSyw0xDpsOJgiKVpAUItLEDVHqTMdTJHRzQ4w208EESdELkkJEFnNDlLmm4ylQznBDjBOmg8kHJSNIChGZD3wNr9LjZtPxFAh7gBeBnyilPjEdTD4pOUFGIiK34YnyFew6r9EcwJPiRaXUIdPBmKKkBRmJiGzghixrTMdjiMPAC3hS2IozWEHGREQeBn4b2AjcYTqegPkNsA/4uVLqJdPBhA0ryCSIyHLgAWCT/1hiOqYcaQdew5PiF0qpD00HFGasIBkiIquBB/3HQ0Ct6Zgm4Tzwqv/Yq5Q6ajqgQsIKkiMishCvYn0T4Iz472V5DuU40Aa4/tc2vErotsRrDlhBAkJEyvm8MPV4d/RrRn0d/RzAJby71KmvvWM8d44RQiilhkzPuRj5/2omzr+oqKWjAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTA2LTI1VDE0OjE5OjU3KzA4OjAwANMjHwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wNi0yNVQxNDoxOTo1NyswODowMHGOm6MAAAAASUVORK5CYII=\" alt=\"\" srcset=\"\">\n            </DoCol>\n          </DoRow>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n\n<script>\nlet URL_REG = new RegExp(`(https?(://))?[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]`)\n\nexport default {\n  data() {\n    return {\n      url: '',\n      historyList: [],\n      editUrlInfo: {\n        baseUrl: '',\n        queryList: [{\n          key: '',\n          value: ''\n        }]\n      }\n    };\n  },\n  created() {\n    this.historyList = JSON.parse(localStorage.getItem(\"dokit-history-list\") || \"[]\")\n  },\n  methods: {\n    jumpToTarget() {\n      if (!URL_REG.test(this.url)) {\n        window.alert('输入的地址不符合URL规则')\n        return\n      }\n\n      let targetUrl = this.url.startsWith('http') ? this.url : `${location.protocol}//${this.url}`\n      if (this.historyList.indexOf(targetUrl) === -1) {\n        this.addHistory(targetUrl)\n      }\n\n      window.location.href = targetUrl\n      this.url = ''\n    },\n    addQuery() {\n      this.editUrlInfo.queryList.push({\n        key: '',\n        value: ''\n      })\n    },\n    delQuery(index) {\n      this.editUrlInfo.queryList.splice(index, 1)\n    },\n    handleQuickEdit() {\n      let targetUrl = this.editUrlInfo.baseUrl.startsWith('http') ?this.editUrlInfo.baseUrl : `${location.protocol}//${this.editUrlInfo.baseUrl}`\n      targetUrl = targetUrl.indexOf('?')>-1 ? targetUrl : targetUrl+ '?'\n      this.editUrlInfo.queryList.forEach((query, index) => {\n        if(query) {\n          if (index === 0 && targetUrl.endsWith('?')) {\n            targetUrl += `${query.key}=${query.value}`\n          } else {\n            targetUrl += `&${query.key}=${query.value}`\n          }\n        }\n      });\n      if (this.historyList.indexOf(targetUrl) === -1) {\n        this.addHistory(targetUrl)\n      }\n\n      window.location.href = targetUrl\n    },\n    jumpToUrl(url) {\n      window.location.href = url\n    },\n    addHistory(url) {\n      this.historyList.push(url)\n      this.updateStorage()\n    },\n    delHistory(index) {\n      this.historyList.splice(index, 1)\n      this.updateStorage()\n    },\n    clearHistory() {\n      this.historyList = []\n      this.updateStorage()\n    },\n    updateStorage() {\n      localStorage.setItem(\"dokit-history-list\", window.JSON.stringify(this.historyList))\n    }\n  },\n};\n</script>\n\n<style lang=\"less\" scoped>\n.h5-portal {\n  padding: 5px;\n  textarea,input {\n    font-size: 13px;\n  }\n  .portal-textarea-container{\n    .portal-textarea{\n      font-size: 13px;\n      border-radius: 5px;\n      box-sizing: border-box;\n      width: 100%;\n      border: 1px solid #d6e4ef;\n      resize: vertical;\n    }\n    .portal-opt-area{\n      margin-top: 5px;\n      height: 32px;\n      line-height: 32px;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      .opt-btn{\n        background-color: #337CC4;\n        border-radius: 5px;\n        font-size: 16px;\n        width: 100%;\n        text-align: center;\n        color: #fff;\n      }\n    }\n  }\n  .url-edit-container{\n    margin-top: 20px;\n    padding-top: 20px;\n    border-top: 1px solid #d6e4ef;\n    .url-edit-key{\n      font-size: 15px;\n    }\n    .url-edit-container-title{\n      text-align: center;\n      font-size: 18px;\n      color:#2c405a;\n      margin-bottom: 20px;\n    }\n    .url-edit-container-query-item{\n      margin-top: 5px;\n      height: 32px;\n      line-height: 32px;\n    }\n    .url-edit-container-query-item__icon{\n      height: 32px;\n      line-height: 32px;\n      text-align: center;\n      img{\n        width: 15px;\n        height: 15px;\n      }\n      \n    }\n    .add-btn{\n      margin-top: 5px;\n      height: 32px;\n      line-height: 32px;\n      background-color: #337CC4;\n      border-radius: 5px;\n      font-size: 16px;\n      text-align: center;\n      color: #fff;\n    }\n    textarea{\n      border-radius: 5px;\n      box-sizing: border-box;\n      width: 100%;\n      border: 1px solid #d6e4ef;\n      resize: vertical;\n    }\n    input{\n      height: 32px;\n      line-height: 32px;\n      border-radius: 5px;\n      box-sizing: border-box;\n      width: 100%;\n      border: 1px solid #d6e4ef;\n    }\n  }\n  .history-record-container{\n    margin-top: 20px;\n    padding-top: 20px;\n    border-top: 1px solid #d6e4ef;\n    .history-record-title{\n      text-align: center;\n      font-size: 18px;\n      color:#2c405a;\n      margin-bottom: 20px;\n    }\n    .history-record-list-item{\n      margin-top: 5px;\n      background-color: #337CC4;\n      border-radius: 5px;\n      height: 32px;\n      line-height: 32px;\n      color: #fff;\n      padding: 0 5px;\n      .history-record-list-item__url{\n        overflow:hidden;\n        text-overflow: ellipsis;\n        white-space: nowrap;\n      }\n      .history-record-list-item__icon{\n        img{\n          width: 15px;\n          height: 15px;\n        }\n        text-align: center;\n      }\n      \n    }\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/h5-door/index.js",
    "content": "import {RouterPlugin} from '@dokit/web-core'\nimport H5Door from './ToolH5Door.vue'\n\nexport default new RouterPlugin({\n  nameZh: '任意门',\n  name: 'h5-door',\n  icon: 'https://pt-starimg.didistatic.com/static/starimg/img/FHqpI3InaS1618997548865.png',\n  component: H5Door\n})\n\n"
  },
  {
    "path": "Web/packages/web/src/plugins/network/index.js",
    "content": "import Network from './main.vue'\nimport {getGlobalData, RouterPlugin} from '@dokit/web-core'\nimport { request } from \"./../../assets/util\";\n\n\nexport default new RouterPlugin({\n  name: 'Network',\n  nameZh: '网络请求',\n  component: Network,\n  icon: 'https://pt-starimg.didistatic.com/static/starimg/img/bZhn7wsssA1621588946807.png',\n  onLoad(){\n    let state = getGlobalData()\n    state.requestList = []\n    state.recorderRequestList = []\n    request.on('REQUEST.SEND', info=> {\n      console.log('REQUEST.SEND',info)\n      state.requestList.push(info)\n      state.startRecorder&&state.recorderRequestList.push(info)\n    })\n\n    request.on('REQUEST.DONE', info => {\n      console.log('REQUEST.DONE',info)\n      let index = state.requestList.findIndex(e => e.id === info.id)\n      let recorderIndex = state.recorderRequestList.findIndex(e => e.id === info.id)\n      state.requestList[index].responseInfo = info.responseInfo\n      state.startRecorder&&recorderIndex!==-1&&(state.recorderRequestList[recorderIndex].responseInfo = info.responseInfo,console.log(state.recorderRequestList))\n    })\n\n    request.on('REQUEST.ERROR', info => {\n      console.log('REQUEST.ERROR',info)\n      let index = state.requestList.findIndex(e => e.id === info.id)\n      let recorderIndex = state.recorderRequestList.findIndex(e => e.id === info.id)\n      state.requestList[index].responseInfo = {...state.requestList[index].responseInfo, ...info.responseInfo}\n      state.startRecorder&&(state.recorderRequestList[recorderIndex].responseInfo = {...state.recorderRequestList[recorderIndex].responseInfo, ...info.responseInfo},console.log(state.recorderRequestList))\n    })\n  },\n  onUnload(){\n\n  }\n})"
  },
  {
    "path": "Web/packages/web/src/plugins/network/js/request.js",
    "content": "// import {EventEmitter} from '@dokit/web-core'\n\n// class Request extends EventEmitter{\n//   constructor(){\n//     super()\n//     this.initialize()\n//   }\n//   initialize(){\n//     let _this = this\n//     let {send:originSend, open:originOpen} = window.XMLHttpRequest.prototype;\n//     // TODO 增加请求拦截器，增加单测\n//     window.XMLHttpRequest.prototype.open = function(method, url){\n//       originOpen.apply(this, arguments);\n//     }\n//     window.XMLHttpRequest.prototype.send = function(){\n//       originSend.apply(this, arguments);\n//     }\n//   }\n// }\n\n// // 单例，保证有且只有一个\n// export default new Request()"
  },
  {
    "path": "Web/packages/web/src/plugins/network/main.vue",
    "content": "<template>\n  <div class=\"network-plugin\">\n    <div class=\"network-header\">\n      <do-row>\n        <do-col :span=\"12\">url</do-col>\n        <do-col :span=\"3\">type</do-col>\n        <do-col :span=\"4\">method</do-col>\n        <do-col :span=\"3\">status</do-col>\n      </do-row>\n    </div>\n    <requestItem\n      v-for=\"(requestItem, index) in requestList\"\n      :key=\"requestItem.id\"\n      :index=\"index\"\n      :requestItem=\"requestItem\"\n    ></requestItem>\n  </div>\n</template>\n<script>\nimport requestItem from \"./request-item\";\n\nexport default {\n  components: {\n    requestItem\n  },\n  data() {\n    return {\n      requestList: this.$store.state.requestList || []\n    }\n  }\n};\n</script>\n<style lang=\"less\">\n.network-plugin {\n  padding: 5px;\n  .network-header{\n    text-align: center;\n    font-weight: bold;\n    font-size: 14px;\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/network/request-item.vue",
    "content": "<template>\n  <div class=\"request-item\" :class=\"requestInfo.resType === 'error' && 'request-item-error'\">\n    <do-row class=\"request-info\" @click=\"showContent = !showContent\">\n        <do-col :span=\"12\">\n          <div class=\"request-url\">{{ requestInfo.path }}</div>\n        </do-col>\n        <do-col :span=\"3\">\n          <div class=\"request-type\">{{requestInfo.type}}</div>\n        </do-col>\n        <do-col :span=\"4\">\n          <div class=\"request-method\">{{ requestInfo.method }}</div>\n        </do-col>\n        <do-col :span=\"3\">\n          <div class=\"request-status\">{{ requestInfo.status }}</div>\n        </do-col>\n        <do-col :span=\"2\">\n          <div class=\"request-toggle-icon\">\n            <span v-if=\"!showContent\">▸</span>\n            <span v-else>▾</span>\n          </div>\n        </do-col>\n    </do-row>\n    <div class=\"response-info\" v-show=\"showContent\">\n      <div class=\"response-info-item\">\n        <div class=\"response-info-item_title\">origin url</div>\n        <div class=\"response-info-item_content\">\n          <span class=\"response-info-item_content-key\">{{requestInfo.url}}</span>\n        </div>\n      </div>\n      <div class=\"response-info-item\" v-if=\"requestInfo.queryMap\">\n        <div class=\"response-info-item_title\">query params</div>\n        <div class=\"response-info-item_content\" v-for=\"(value, key) in requestInfo.queryMap\" :key=\"key\">\n          <span class=\"response-info-item_content-key\">{{key}}</span>\n          <span class=\"response-info-item_content-value\">{{value}}</span>\n        </div>\n      </div>\n      <div class=\"response-info-item\" v-if=\"requestInfo.body\">\n        <div class=\"response-info-item_title\">body params</div>\n        <div class=\"response-info-item_content\">\n          <span class=\"response-info-item_content-value\">{{requestInfo.body}}</span>\n        </div>\n      </div>\n      <div class=\"response-info-item\" v-if=\"requestInfo.headers && requestInfo.headers.contentType\">\n        <div class=\"response-info-item_title\">headers</div>\n        <div class=\"response-info-item_content\">\n          <span class=\"response-info-item_content-key\">content-type</span>\n          <span class=\"response-info-item_content-value\">{{requestInfo.headers&&requestInfo.headers.contentType}}</span>\n        </div>\n      </div>\n      <div class=\"response-info-item\" v-if=\"requestInfo.resRaw\">\n        <div class=\"response-info-item_title\">response</div>\n        <pre>{{requestInfo.resRaw}}</pre>\n      </div>\n    </div>\n  </div>\n</template>\n<script>\nimport { getPartUrlByParam, getQueryMap } from \"@dokit/web-utils\";\nexport default {\n  props: {\n    requestItem: [Object],\n    index: [Number]\n  },\n  computed: {\n    requestInfo() {\n      let { requestInfo: {url, method, contentType, body}, responseInfo: { status, resRaw, type: resType } = {}, type } = this.requestItem\n      \n      let queryString = getPartUrlByParam(url, 'query')\n      let queryMap = queryString ? getQueryMap(queryString) : null\n      try {\n        resRaw = JSON.stringify(JSON.parse(resRaw), null, 2)\n      } catch (error) {}\n      return {\n        path: getPartUrlByParam(url, 'path'),\n        url,\n        method,\n        status,\n        type,\n        resRaw,\n        headers: {\n          contentType: contentType\n        },\n        queryMap,\n        body,\n        resType\n      }\n\n    }\n  },\n  data() {\n    return {\n      showContent: false\n    }\n  }\n}\n</script>\n<style lang=\"less\" scoped>\n.request-item-error{\n  background-color:#FEF0F0;\n  border: 1px solid #F8D8D7 !important; \n}\n.request-item{\n  margin-top: 10px;\n  border-radius: 5px;\n  overflow: hidden;\n  border: 1px solid #d6e4ef;\n  font-size: 12px;\n  padding: 5px;\n  .request-info{\n    display: flex;\n    line-height: 24px;\n    text-align: center;\n    .request-url{\n      text-align: left;\n      word-wrap: break-word;\n      word-break:normal;\n      color: #1485ee;\n    }\n  }\n  .response-info{\n    border-top: 1px solid #d6e4ef;\n    .response-info-item{\n      margin-top: 5px;\n      .response-info-item_title{\n        font-size: 16px;\n      }\n      .response-info-item_content{\n        .response-info-item_content-key{\n          color: #1485ee;\n          margin-right: 10px;\n          word-break: break-all;\n          white-space: normal;\n        }\n      }\n    }\n    pre{\n      margin: 0;\n      margin-top: 5px;\n      min-height: 100px;\n      max-height: 300px;\n      overflow-y: scroll;\n      border: 1px solid #aaa;\n      border-radius: 5px;\n      white-space: pre-wrap;\n      word-break: break-word;\n    }\n  }\n}\n\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/one-machine-with-multiple-controls/app.vue",
    "content": "<template>\n  <div class=\"one-machine-with-multiple-controls-content\">\n    <div class=\"portal-textarea-container\">\n      <textarea\n        class=\"portal-textarea\"\n        rows=\"5\"\n        v-model=\"socketUrl\"\n        placeholder=\"请输入联网地址\"\n      ></textarea>\n      <div class=\"portal-opt-area\">\n        <div class=\"opt-btn\" @click=\"scanCode\">扫码</div>\n      </div>\n      <div class=\"portal-opt-area\">\n        <div class=\"opt-btn\" @click=\"connectHandle()\">\n          {{ connect ? \"断开联网\" : \"联网\" }}\n        </div>\n      </div>\n      <div class=\"portal-opt-area\">\n        <div class=\"opt-btn\" @click=\"recordHandle\">\n          {{ recording ? \"暂停录制\" : \"开始录制\" }}\n        </div>\n      </div>\n    </div>\n    <div class=\"history-record-container\">\n      <div class=\"history-record-title\">历史记录</div>\n      <div class=\"history-record-list\" v-if=\"historyList.length > 0\">\n        <div\n          class=\"history-record-list-item\"\n          v-for=\"(item, index) in historyList\"\n          :key=\"index\"\n        >\n          <svg\n            t=\"1646903397439\"\n            class=\"connectState\"\n            viewBox=\"0 0 1024 1024\"\n            version=\"1.1\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            p-id=\"7559\"\n            width=\"200\"\n            height=\"200\"\n          >\n            <path\n              d=\"M969.586403 624.769747l-129.946199-130.564991c-34.911813-35.071501-77.458713-52.607251-127.401169-52.607251-50.830721 0-94.146121 18.30425-129.946199 55.172242l-54.912749-55.172242c36.568577-36.089513 54.912749-79.74425 54.912749-131.203743 0-50.172008-17.32616-92.678986-51.848733-127.361247L401.885195 53.246004C367.362622 17.795244 324.815721 0 274.484025 0c-49.942456 0-92.229864 17.406004-126.762417 52.098246l-91.850604 91.521247c-35.291072 34.682261-52.996491 77.179259-52.996492 127.351267 0 50.172008 17.455906 92.928499 52.357739 128l129.946199 130.684757c34.901832 35.071501 77.458713 52.617232 127.40117 52.617232 50.830721 0 94.156101-18.30425 129.946198-55.172242l54.912749 55.172242c-36.688343 36.089513-54.912749 79.74425-54.912749 131.203742 0 50.172008 17.32616 92.669006 51.858714 127.361248l128.538947 129.916257c34.522573 35.460741 76.949708 53.246004 127.40117 53.246004 49.942456 0 92.239844-17.406004 126.762417-52.227992l91.850604-91.521248c35.291072-34.692242 52.996491-77.179259 52.996492-127.361247 0.019961-50.042261-17.435945-92.798752-52.347759-128.119766zM460.790146 365.556023c-1.626823-1.337388-5.90846-5.938402-13.453723-13.813021-7.395556-7.575205-12.555478-12.924756-15.519688-15.599532-2.95423-2.525068-7.545263-6.237817-13.753139-10.838831-6.357583-4.601014-12.41575-7.724912-18.473918-9.511423-6.058168-1.77653-12.715166-2.664795-19.961014-2.664795-19.212476 0-35.620429 6.676959-49.223859 20.350253-13.603431 13.513606-20.250448 30.001404-20.250449 49.453412 0 7.28577 1.027992 13.97271 2.664796 20.050838 1.626823 5.938402 4.870487 12.325926 9.45154 18.563743 4.581053 6.237817 8.283821 10.998519 10.788928 13.813021 2.515088 2.824483 7.68499 8.024327 15.529668 15.589552 7.68499 7.575205 12.276023 12.026511 13.743158 13.513606-14.481715 15.010682-31.917661 22.585887-52.17809 22.585887-19.801326 0-36.069552-6.387524-49.223859-19.601715L110.145 315.952904C96.541569 302.429318 89.894551 285.951501 89.894551 266.489513c0-19.012865 6.647018-35.201248 20.250449-48.864562l106.591813-106.3423c14.042573-13.074464 30.300819-19.611696 49.223859-19.611696 19.222456 0 35.630409 6.68694 49.22386 20.350254l149.168655 150.755555c13.59345 13.513606 20.250448 30.001404 20.250448 49.463392-0.009981 20.350253-7.844678 38.165458-23.813489 53.315867z m455.849669 443.334113L809.62882 915.511891c-13.503626 12.824951-29.981442 19.082729-49.423469 19.082729-19.89115 0-36.209279-6.417466-49.42347-19.68156L561.034356 763.708382c-13.663314-13.573489-20.330292-30.12117-20.330292-49.653021 0-20.430097 8.014347-38.325146 24.043041-53.385731 1.626823 1.347368 6.078129 6.118051 13.503626 13.872904 7.425497 7.754854 12.615361 13.124366 15.589551 15.649435 2.964211 2.535049 7.565224 6.257778 13.793061 10.888733 6.387524 4.620975 12.475634 7.904561 18.553762 9.541364 6.088109 1.936218 12.765068 2.684756 20.040858 2.684757 19.29232 0 35.770136-6.716881 49.423469-20.430098 13.653333-13.573489 20.330292-30.12117 20.330293-49.653021 0-7.305731-1.037973-14.012632-2.674776-20.130683-1.626823-5.958363-4.900429-12.375828-9.501442-18.643586-4.601014-6.257778-8.164055-10.888733-10.838831-13.862924-2.525068-2.844444-7.714932-8.054269-15.579571-15.659415-7.714932-7.605146-12.315945-12.076413-13.803041-13.563509 14.541598-15.509708 32.057388-23.414269 52.397661-23.414269 19.29232 0 35.760156 6.716881 49.413489 20.430097l151.384328 152.102924c13.653333 13.563509 20.330292 30.111189 20.330292 49.653022 0.009981 19.082729-6.816686 35.490682-20.470019 48.754775z\"\n              p-id=\"7560\"\n              :fill=\"connectStateColor(item[1])\"\n            ></path>\n          </svg>\n          <div class=\"name\">{{ item[0] }}</div>\n          <div\n            class=\"opt-btn\"\n            :style=\"`background-color:${item[1] === 'connect' ? '#d81e06' : '#28af31'}`\"\n            @click=\"connectHandle(item[0])\"\n          >\n            {{ item[1] === \"connect\" ? \"断开连接\" : \"连接\" }}\n          </div>\n          <img\n            class=\"delBtn\"\n            @click=\"delHistory(item[0])\"\n            src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAAGl1JREFUeNrtnXtwVNeZ4H9HEg8JCYQkY8xLPHWbh2PAjl+AXzDenZrM1OxkQiubWjsbvLXZTGVeroHmj63a2tktNcw4M1OZ8nq9djZOzUzUTqamau2qZGyMk2BjHMc22Dz6ykZGYGwshAAhhBCSvv3j3gZZ1qMf5/a53X1+VV2y2tK532nuT9/tvud8n8ISCCJSDjhAk/+oB6qBmlFfRz8HcAnoHfG1d4znzgFtgAu0KaWGTM+5GFGmAyh0RGQhNyQYKcSyPIdynBHCpB5KqVOmX6NCxgqSISLSBNwH/Jb/mG06pknoBl72H79SSn1gOqBCwgoyCSLSCNwLbMATosl0TDni4smyH9ivlOowHVCYsYKMgYg8DPwbPDHuNh1PwBzAk+XnSqmXTQcTNqwgPiJyL/AV/3Gr6XgM8T7wIvCiUmq/6WDCQEkLIiK3cUOKYs8UmXIAeAFPlvdMB2OKkhNEROYDf4gnxRbT8RQIe/Ayy0+UUp+YDiaflIwgIrIYeAzYBsw1HU+BcgZ4BnhWKXXCdDD5oOgF8T+WTYlRZzqeIqGbG6K0mQ4mSIpWEBFZww0xqk3HU6T04onyjFLqiOlggqDoBBGRdXhiPAZMNR1PiTDADVHeNR2MTopGEBGZDezwHxZz7AJ2KaXOmw5EB0UhiIg8gifGKtOxWAA4iifJj0wHkisFLYiIfBnYjvexrSV8/BTYrZR6y3Qg2VKQgohINZ4YO7DvM8LOAN5l126lVK/pYDKl4AQRka/jybHWdCyWjDiIJ8mPTQeSCQUjiIjUALuBb5uOxZITTwHblVKXTAeSDgUhiIhswpPDrpcqDg7gSbLPdCCTEXpBROS7eHJMNx2LRSv9eJJ833QgExFaQUSkHk+Mb5mOxRIoP8AT5ZzpQMaizHQAYyEim4GXsHKUAt8C/tX/Nw8docsgIvI4XuYIpbyWwBgCdiilnjAdyEhCJYiIPAH8uek4LEb5nlLqcdNBpAiNICLyQ+BR03FYQsFzSqlvmg4CQiKIiLyAt8PPYknxolLqd00HYVwQEdkP3GM6Dkso2a+U2mAyAKOCiIhL4deZsgSLq5SKmDq4MUFEpAuvXq3FMhldSqmbTBzYiCAiIiaOayloRCmV94/+835AETmZ72NaigIlInkvk5pXQUTkdWBhvidpKRoWichr+Txg3gQRkVa8WrcWSy5sEJG87SnJiyAi8rdANF+TshQ9zSLyN/k4UOCCiMhu4E/yMRlLSfGnIrIr6IMEKoiI/CXwF0FPwlKybPfPscAI7GNef1XuXwcZvMXi87hS6ntBDByIICP2c9gl65Z8MAQ8rJTaq3tg7YL4OwFfAtbn4YWxWFK8jSdJt85Bg/gLvxsrhyX/3I537mlFqyB+gQW7TdZiim3+OagNbZdYfmmel7DVRyxm6ce71NJSUkiLIH5Rt5ewdass4eAAniQ5F6fTdYlli7pZwsTdaHo/knMG8Wvl/pPpV8RiGYOvK6VacxkgJ0H8Kuv7sIWkLeHkILBRKXU52wFyvcSyVdYtYWYtOXYcyzqD+M1rXsP257CEmwG8LJJVE59cMohtXmMpBKaSQxbJShAReRT4qumZWyxp8lW/j2XGZHyJJSJ1eG/MQ90w82eHzvJW+0U6uq7Q0XWF8jJYMXcGixsq2by6gXWLZ5oOsaB450QPLx/uor2zj/bOPgCWzqmisaGSNQuq+b31N5sOcTKO4l1qZdR9NxtB4oS81fLTe0/y9Kunxv3/FeWK72xu5JFN802HWhA8t+80T+7pYGh4/GI090fqeOIbK02HOhm7lFKxTH4hI0FEZD3eqsnQcsd/fT3tn92ypoGWqGO+vGRIEYFYwuWVI11p/85v/tJoIcR0WK+UejfdH870Pcg207ObiMf/8VhGP7/ncBex1iTDtkrXFxgWIZZIZiQHMGHmDgmPZfLDaQsiImsyHTyf/L93PuOXycy3Arxy5Byx1uSElw+lxtCwEGt1eeVI5k2fnt57kp8fOmt6ChPxmIisTveHM8kg2wjxx7qHP86+Bffeo+eIJVwGrSQMDnly7D2afUe0t9ovmp7GREwlgz/0aQkiIk2ZDGqCjq4rOf3+q0fPEWt1GRwqXUmuDXmXVa8ey61d4Ikc/y3ywGP+OT0p6WaQbUC16VlNROqjx1z4xbFz7EgkuTY0bHo6eWdgcJhYa5JfHMt9x2pHV+7/FgFTTZrvpycVREQWE/LsAVCm6aOoXx7rZkery8Bg6UhydXCYWMLN6j3cWChVEJ8LbvPP7QlJJ4M8BtSZns1krJg7Q9tYv0p6kly9VvyS9F8bJtbq8itNcoB3A7EAqCeNLDKhICIyL51BwkBjQ6XW8fa53cQSLv1FLMmVgWFiiST7XK2FQApFEPCyyLyJfmCyDPI1YK7pWaTDltUNVOi6zvLZ53YTa01yZWDI9PS00zcwRCyR5DU3o5UXk1JRrnh4TYPp6aXLLXjn+LhMJkjBNNZct3gm/2VLo/ZxX2s7Tyzh0ldEkvRdHSLW6vJ6m145AL6zpbHQ1rlNeI6PK4iI3AZsMR19Jjy6aT5bAvjr9XrbeWKtLpevFr4kl68OsSPhsv8D/XL81poGHtlYcOvbtvjn+phMlEEKJnuMpCXqsHm1/taH+z84TyyRpLe/cCXp7R9kR2uSNwKQY8tqb11bgTLuuV50giigJRrhoVX6JXnjgwvEEkku9Q+anmbGXLoyyI6Ey4EPL2gfe/Pq+kKWA+B3xvsfYwoiIhso4DI+ZcrLJA8GIMmBDy8Qa3XpuVI4kvRcGSSWcHkzADkeWuXJURi3PsblHhEZs/vZeBmkILPHSMrLFC1RhwdW6pfkzeMXiCVcLhaAJBf7Bom1Jnnz+AXtYz/oy1FW4Hb4jHnOF60gABVlinjU4f6V+u9z/vr4BWKtSS70XTM9zXG50HeNWCLJrwNYPPjAynpatjqUa/5o3SDpCSIiDwNrTEeri4pyT5L7Ivoleav9IrGEy/nL4ZPk/OVrxFrdQFbW3h+poyXqUFFeNHIA3Oqf+59jrAzy26Yj1c2U8jLiUYdNjn5JfuNL0h0iSbovXyOWcPnNR/rluM+XY0pxyZHi345+YixBNpqOMgimVniSbAxAkrc/ukis1eVcr3lJzvVeI9aa5O0A5NjkeHJMrSjaxmFfOPc/92dARJYDH5iOMkj6rw2zozUZyF3kdYtnEo861Feb2VfWdWmAnc+7vHuiR/vYG53ZxKMRpk8pWjlSLFdKHU99M3q2D5iOLmimT/EyyYam2drHfvdED7FWl65LA3mf19lLA8QSwcixoWk2LVtLQg4Y5cDoGW8yHV0+qJxaTkvU4d4VAUjS0UMs4XK2J3+SdPYMEGt1OdihX457V8ymJepQObUk5IBRDpSkIABVviT3LK/VPvbBjh5iiSSdeZCks+cqsUSSQyf1y3GPL0fV1PLA5xEixhZERFYBS0xHl09mTCunJRrh7gAkOXTyErHWJJ9dvBpY/J9dvMqOVpf3TubcSOkL3L28lnjUYca0kpIDYKnvAvD5DPKQ6chMUD3dyyR3LavVPvZ7py4RS7icuaBfkjMXrhJLuLx/Sr8cd5WuHCkeTP1H2VhPlho10ytoiTrcGYAk7/uSfKpRkk8DlOPOZbXEtzpUT6/QPnYBcd2F6x/zish5oNZ0ZCa52DdILJEM5O7z6gXVtGx1mDc7tybAn5zvZ2fC5cjp7OuAjcedS2uJNzvMrCxpOQDOK6XqwM8gIrKQEpcDYFaVl0nuWDJL+9hHPu4llnA5fb4/6zFOn+8nFpAcX146i5aolcNntu/E9UustIpolQK1VVNoiTrcHoAkR0/3Emt1+bg7c0k+7u4n1upyNAA57ljiyTGrysoxgiawgozJ7BmeJOsX65fk2CdeJjl1Ln1JTp3zMsexT/TLcbsvR23VFO1jFzhWkImo8yUJogBB8pNeYokkJ89NXqLz5LkrxBJJkgHIsX7xTFq2OsyeYeUYg88JUtD7JYOivnoKLVsd1jbql8T99DKxhDthTeGOrivEWl3cT7PuYjwu6xpn0hKNUFdt5RgHm0HSoaFmKvGow22L9EvS5ksyVrHnE11XiCVc2s7ol2Nt40xaog71Vo6JcACUiJQD4d87apizPQPsSCQDuWu9/OYqWqIRltzkVYf86OwVdiZcPvxMvxy3LZpJvNnhpprQdrIIExVKRFbiNTi0TEJnzwA7WpOB3KBbdnMVLVu9K92dz7sc/0x/hfQvLaohHo0wZ6aVI01WKRH5feBfTEdSKHx28So7Ei6Hg5BkThWCnlYOo7l1YQ3xqMPNs6bl4VUqGv5dGfb9R0bcPGsa8a0OaxbUaB/7+IgWyzpZs6CGFitHNjSV4ZWBt2TA3NpptEQdVs8PdU8hwF/iEnWYa+XIhvoyQt45Kqzc4kuyKsSSrJpfTUs0wi21Vo4sqS4D9F8rlAjzZk+nJeqwcl74JFk1v5p41GGelSMXamwGyZH5viSREEmycp6elcMWm0G0sKBuOi1bHZxb9LWBy5bILTNoiTrMr7NyaMBmEF0srPcySZPGXomZ4twyg5ZohAVWDl1UW0E0sqi+kpaoo7WhaLo0zfUyx8J6K4dG7CWWbhobPEmW35y/RpYrfDkW1ettZGqxl1iBsLihkpZohGV5kGS5v0RFd5dfCwDVSkSuAnZxTgC0d/YRS7iB3B0Hb/1WPOqw5KaCabtcaAyUTLm8okRMB1D8lAH6V91ZvCXrzweXPcBbuxVrdfnobHDHKHEulQH693KWOCe6rrAzkQxkyfporksSoIglTK/NIJrp6EptdsrfCXs84Pc6JYzNIDo5ec6T44MAtslOxvHOPnZaSXTTawXRxKlzXsXDIPaQp4vNJNq5ZC+xNPBxdz87nw+m+kimpD5aPnF28pJClkmxGSRXTvu1coOoW5Ut7Z19bG9NciqNuluWCbEZJBdShaSDqHiYK+2dfTz+T8mcagFbbAbJmk8vXGVnIphaubpo7+zjz/7hGGcCbOJT5FwqA86ZjqLQOOPLEUSVdd20d/bxxz86mteeiUVEdxnQZjqKQuKzi1eJPe9y+ONgyv4snaN/XVV7Zx9/9NwRukPQx73AaCsDXNNRFAqdPV6r5UBqYt1cRUvUIR51AlkF3N7Zx7f/72Eu9tkimhng2tKjaVIspUeXzqni2f90KzWl3WItXSrKlFJDwHHTkYSZrkte5ghCjhVzZxBvviEHwJKbKok3B7Mzsb2zj23/5336BoYCfc2KgONKqaHUcnf7PmQcUnIE0Ye86ZYZxKMOi8fY7LS4oZJ4QHvc2zv7+Ob/fo+rg8OBvGZFggs32h/Y9yFjcK73GjufdznYoV8Ox5djop2AjQ1eJgmiWkp7Zx//4X8dYnDIbioZhza4IYjNIKPovnyNnQmXd0/olyMyr5p4NJLWHvJF9ZXEo5FA6m61d/bx7588iFhHxsIKMh7nfTneOaG/HfTKeV7Fw0yqjyysn048oAqO7Z19RP/+Xe3jFgFWkLG40OfJ8fZH+uVYNb+aeLOTVd2qBXXTiTcHUwu4vbOPrd+3koyiDUClvhORbmC26ahMcrFvkFgiyVvt+uVYvUBPOdDU+q8g7uIvnVPF899dp33cAqRbKVUPNzIIwMumozJJz5VBdj7vBiLHmgVeZycdtXLnzZ5OvDkSSH8Sm0muc90FKwhwqX+QnQmXXx+/oH3sVGcnnS0IbqmdRjzqcOtCK0lAjCnIr0xHZYLe/iF2JlzeDECOL/lyzA2gBcFcK0mQXHdBjXxWRFxKqCXb5atDxFqTvPHhBe1j37aohpY8NMzs7Lka2F3+En1P4iqlIqlvRheOK5nLrL4BL3MEIcfaxpl56yY7Z+Y04tFIIH3cSzST7Bn5zWhB9puOLh9c8eXY/8F57WOva5xJPOpwUx5bLc+ZOZV4s8PaRiuJBl4f+U3JCdJ/bZhYwuX1tgDkWDyTeLNDQ03+Sx3fVDOVeNRh3WIrSY58zoHPCaKUOgG8aTrCoLgaoBzrF88iHo1QX22uDnhDzVTi0QjrrSTZckAp1THyibGKVxdlFhkY9OR4ze3WPvbtS2YRb3aor55ieprUV08h3hzh9iWztI9dApJ84dwfS5Cfm45SN9eGPDn2BSDHHUtnEY861M0wL0eKuhlTiEcd7rCSZMoXzn011k+JyPvAGtPR6mBwSNiRSPLLY/rl+PLSWbREHWqrwiPHSFLryoJYHbBi7gx+/EdrTU9RJ+8rpb40+snx+oO8aDpaHQwOC7GEG4gcdy6rJd4cCa0cALVVU4hHI9y5VH8m+eDMZb7x5EHTU9TJmOd80QoyNCzsTLj84pj+qkZ3LaslHnWYVRn+fd2zqiqIN0e4a1mt9rHdTy/zyFOHTE9RF+kLopR6HThgOuJsGRbYmXB59ah+Oe5eXku82WFmAciRYmZlBfGow13La7WPffR0L//x6fdMTzFX3lBKjfnh1EQt2AoyiwiwM5FkbwBy3LOilng0UpAVQWoqK9gVdbg7AEneP3WJx5553/QUc2Hcc73oBNmZcHnliH457l0xm3g0QvX0ctNTzJrq6RXsao5wzwr9234OdvTwn39w2PQUsyVzQZRSh4BXTEeeCT/ad5o9h7u0j7uhaTbxZocZ0wpXjhQzppWzK+pwbwCSvP3RRf7mZx+ZnmKm7FFKjXuNOFmX2xdMR58u757o4ck9HbkPNIqNTbOJRx2qpha+HCmqppUTb3bY0KRfkn/c/wlPv3rK9BQzYcIrpckE+QlwxvQM0mHPkS4Gh/WW59jk1BFvjlBZRHKkqJpaTjwaYaOjX5Kn954slC5XZ/DO8XGZUBCl1CfAs6ZnkQ4dXXqbxWxy6ohHHaZPKd5W8pVTy4hHI2xy6rSPfTCAQnsB8Kx/jo9LOv/6zwD677RpRmfjzPsidexqdphWxHKkmD6ljHizw30RvZIc6gh9X6ZuvHN7QiY9A/wVvqHPIrquru5f6ckxtaL45UgxraKMeNThfo2SSPir0T3rn9sTku5Z8Awh70Slo6/GAyvr2RWNMKW8dORIMbWijHhzhAdW6pFkdQBVVzTSSxrZA9IURCnVRsizSGMaZTwn4sFV9cSbHSrKVU7jFDJTyhXxaIQHV9bnPFYQZYk08qx/Tk9KJn8qnwFC28drzcLsKw4+tKqeeNShoqx05UhRUa6INzs8tCp7SR5YWc/qBforQGpigDSzB2QgiFLqcCYD55vfW39zVtfQm1fXE2+OUG7luE55mSfJ5tWZS7KucSb/7Q+Wm57CRDzjn8tpkenFdqgvs574xsqMfn7LmgbizRGsG1+kTHmXW5tXN6T9O01zZ/Df/7CJ6nCvVcvoj3zGp4aIxIEdpmc5EU/vPTnh3dyKcsV3NjfyyKb5pkMtCJ7bd5on93QwNMFHhV+9cy7f2dzIrKpQy7FLKRXL5BeyEaQO2AesMj3bifjZobO81X6Rjq4rdHRdobzM2wW3uKGSzasbAqn+Ucy8c6KHlw930d7Zd/0u+dI5VTQ2VHLbohp+Z+0c0yFOxlFgo1Iqo4odWV1ciMijwA9Nz9hiyYBHlVI/yvSXsr76FpGfAl81PWuLJQ1+qpT6Wja/mIsgXwZeA8wVgrJYJmcA79LqrWx+Oetbxv4Bd5mevcUyCbuylQNyyCAAIlKN94Z9relXwWIZg4N42SPrlaw5LTpSSvUCu02/ChbLOOzKRQ7IURAApdSPgadMvxIWyyieUkq15jqIlnvIIlIDvATcbfpVsVjwSlY9rJTKeVOKtkUWIrIJT5LcO1VaLNnTjyfHPh2Dadv44Ae03dSrYrH4bNclB2jMIClE5FngW3l9SSwWjx8opbbpHDAIQerxLrXW5+tVsViAd/AurbRWDQxkobeIbMaTpPT2rlpMMIwnh/ZCh4GcwH6g9v2IJV9sD0IOCCiDpBCR7wF/FuQxLCXP95RSjwc1eOB76UTkh8CjQR/HUpI8p5T6ZpAHyMtmUxF5AfhKPo5lKRleVEr9btAHydtubBHZD9yTr+NZipo3lFL35uNAeS1XICIu0JTPY1qKjjallJOvg+W9noeIdAG5VyazlCJdSqmb8nlAIwVvpAAKt1pChyil8n5fzciNPKWUAgqqy4rFKCdNyAEG73QrpRYB+3MeyFLsvK6UajR1cKNLQZRSG4CEyRgsoaZVKbXRZADG10oppZqBvzMdhyV0/J1S6uumgzAuCIBS6k+BvzIdhyU0/JV/ThgnFIIAKKW2A//TdBwW4/wP/1wIBaGray4ij+NVSgmNvJa8MIy3KvcJ04GMJHSCwPX9JLuxm65KhXcIcMl6LoRSELi+M3E3dvtusfMDPDm07gTURWgFSSEi38UTxVZLKS768cT4vulAJiL0gsD1kkK7sXW3ioUDaK4+EhQFIQhcL063G/i26VgsOfEUnhw5F3XLBwUjSAoR+Trefve1pmOxZMRBYLdfqrZgKDhB4HpV+e14vRJtf5JwM4DXJmO3X+y8oChIQVL4TXx2YDtdhZV/Jsf+HKYpaEFS+D0TtxPyxqIlxFG8jPGc6UBypSgEgevdd1OXXRZzpC6nuk0HooOiESSFiKwHtgGPYd+f5IsB4BngWaXUO6aD0UnRCZJCRNbgSbINqDYdT5HSyw0xDpsOJgiKVpAUItLEDVHqTMdTJHRzQ4w208EESdELkkJEFnNDlLmm4ylQznBDjBOmg8kHJSNIChGZD3wNr9LjZtPxFAh7gBeBnyilPjEdTD4pOUFGIiK34YnyFew6r9EcwJPiRaXUIdPBmKKkBRmJiGzghixrTMdjiMPAC3hS2IozWEHGREQeBn4b2AjcYTqegPkNsA/4uVLqJdPBhA0ryCSIyHLgAWCT/1hiOqYcaQdew5PiF0qpD00HFGasIBkiIquBB/3HQ0Ct6Zgm4Tzwqv/Yq5Q6ajqgQsIKkiMishCvYn0T4Iz472V5DuU40Aa4/tc2vErotsRrDlhBAkJEyvm8MPV4d/RrRn0d/RzAJby71KmvvWM8d44RQiilhkzPuRj5/2omzr+oqKWjAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTA2LTI1VDE0OjE5OjU3KzA4OjAwANMjHwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wNi0yNVQxNDoxOTo1NyswODowMHGOm6MAAAAASUVORK5CYII=\"\n            alt=\"\"\n            srcset=\"\"\n          />\n        </div>\n      </div>\n      <div class=\"history-record-list-empty\" v-else>暂无</div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport { $bus } from \"../../assets/util\";\nexport default {\n  data() {\n    return {\n      recording: false,\n      socketUrl: \"\",\n      historyList: [],\n      testSocket: null,\n    };\n  },\n  watch: {\n    socketHistoryList: {\n      handler: function (newVal, oldVal) {\n        this.historyList = newVal && [...newVal] || [];\n      },\n      deep: true,\n      immediate: true,\n    },\n  },\n  computed: {\n    connect() {\n      return this.$store.state.socketConnect;\n    },\n    isHost() {\n      return this.$store.state.isHost;\n    },\n    socketHistoryList() {\n      return this.$store.state.socketHistoryList;\n    },\n  },\n  created() {\n    let list = JSON.parse(localStorage.getItem(\"dokit-socket-history-list\") || \"[]\");\n    list.forEach((item) => {\n      if (this.connect && item[0] === this.$store.state.socketUrl) {\n        this.$store.state.socketHistoryList.set(item[0], \"connect\");\n        this.socketUrl = this.$store.state.socketUrl;\n      } else {\n        this.$store.state.socketHistoryList.set(item[0], \"close\");\n      }\n    });\n    $bus.on(\"scanCode\", this.scanCodeCallback);\n  },\n  destroyed() {\n    $bus.off(\"scanCode\", this.scanCodeCallback);\n  },\n  methods: {\n    recordHandle() {},\n    connectHandle(url) {\n      if (this.$store.state.socketConnect) {\n        this.$store.state.socketConnect = false;\n        return;\n      }\n      if (\n        (url && !/^(ws?s:\\/\\/)/.test(url)) ||\n        (this.socketUrl && !/^(ws?s:\\/\\/)/.test(this.socketUrl))\n      ) {\n        this.$toast(\"url地址格式不对\", 1000);\n        return;\n      }\n      try {\n        this.testSocket = new WebSocket(url || this.socketUrl);\n        this.testSocket.addEventListener(\"error\", (e) => {\n          this.$toast(\"url地址无法连接\", 2000);\n          this.testSocket.close();\n          this.testSocket = null;\n        });\n        this.testSocket.addEventListener(\"open\", (e) => {\n          url && (this.socketUrl = url);\n          this.$store.state.socketUrl = this.socketUrl;\n          this.$nextTick(() => {\n            this.$store.state.socketConnect = !this.$store.state.socketConnect;\n            this.$store.state.socketConnect && (this.$store.state.showContainer = false);\n          });\n          this.testSocket.close();\n          this.testSocket = null;\n        });\n      } catch (error) {\n        this.$toast(\"url地址无法连接\", 1000);\n        return;\n      }\n    },\n    scanCode() {\n      this.$router.push({\n        name: \"scanCode\",\n        params: { multiControl: true },\n      });\n    },\n    scanCodeCallback(e) {\n      this.$router.back();\n      this.socketUrl = e;\n      this.connectHandle();\n    },\n    delHistory(key) {\n      this.$store.state.socketHistoryList.delete(key);\n    },\n    connectStateColor(value) {\n      if (value === \"connect\") {\n        return \"#1afa29\";\n      } else {\n        return \"#d81e06\";\n      }\n    },\n  },\n};\n</script>\n\n<style lang=\"less\" scoped>\n.one-machine-with-multiple-controls-content {\n  padding: 5px;\n  textarea {\n    font-size: 13px;\n  }\n  .portal-textarea-container {\n    .portal-textarea {\n      font-size: 13px;\n      border-radius: 5px;\n      box-sizing: border-box;\n      width: 100%;\n      border: 1px solid #d6e4ef;\n      resize: vertical;\n    }\n    .portal-opt-area {\n      margin-top: 5px;\n      height: 32px;\n      line-height: 32px;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      .opt-btn {\n        background-color: #337cc4;\n        border-radius: 5px;\n        font-size: 16px;\n        width: 100%;\n        text-align: center;\n        color: #fff;\n      }\n    }\n  }\n  .history-record-container {\n    margin-top: 20px;\n    padding-top: 20px;\n    border-top: 1px solid #d6e4ef;\n    .history-record-title {\n      text-align: center;\n      font-size: 18px;\n      color: #2c405a;\n      margin-bottom: 20px;\n    }\n    .history-record-list-item {\n      margin-top: 5px;\n      background-color: #337cc4;\n      border-radius: 5px;\n      color: #fff;\n      padding: 5px;\n      display: flex;\n      align-items: center;\n      .delBtn {\n        width: 15px;\n        height: 15px;\n      }\n      .name {\n        word-break: break-all;\n        margin-right: 5px;\n        font-size: 15px;\n      }\n      .connectState {\n        width: 20px;\n        height: 20px;\n        margin-right: 7px;\n      }\n      .opt-btn {\n        border-radius: 5px;\n        font-size: 16px;\n        text-align: center;\n        color: #fff;\n        white-space: nowrap;\n        padding: 2px 15px;\n        margin-right: 5px;\n      }\n    }\n    .history-record-list-empty {\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      margin-top: 30px;\n      font-size: 17px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "Web/packages/web/src/plugins/one-machine-with-multiple-controls/index.js",
    "content": "import index from './app.vue'\nimport {\n  RouterPlugin,\n  getGlobalData\n} from '@dokit/web-core'\nimport {\n  hex_md5,\n  guid,\n  getQueryVariable,\n  completionUrlProtocol,\n  strMapToObj,\n  HTTP_STATUS_CODES,\n  $bus,\n} from '@dokit/web-utils'\nimport { request } from './../../assets/util'\nimport moment from 'moment'\nimport { ConsoleLogMap } from '../console/js/console'\n\nexport default new RouterPlugin({\n  nameZh: '一机多控',\n  name: 'OneMachineWithMultipleControls',\n  icon: 'https://pt-starimg.didistatic.com/static/starimg/img/nktYHc1alL1635323338193.png',\n  component: index,\n  onLoad() {\n    let state = getGlobalData()\n    state.channelSerial = `web-${hex_md5(location.pathname)}`\n    console.log('channelSerial:',state.channelSerial)\n    request.multiControlhook({\n      xhrHostRequest:function(xhr){\n        let state = getGlobalData()\n        const urlObject = new URL(xhr.reqConf.requestInfo.url)\n        console.log('url:',xhr.reqConf.requestInfo.url)\n        if(urlObject?.href.indexOf('http://sso.weidian.com/user/cookie/setvisitor')>=0){\n          console.log('socketConnect',state.socketConnect)\n          console.log('isHost',state.isHost)\n          console.log('webSocketState',state?.mySocket?.webSocketState)\n          console.log('urlObject',urlObject)\n          console.log('pid',xhr?.reqConf?.pid)\n          console.log('did',xhr?.reqConf?.did)\n          console.log('requestContentType',xhr?.reqConf?.requestHeaders['Content-Type']||xhr?.reqConf?.requestHeaders['content-type'] || '')\n          console.log('requestHeaders',xhr?.reqConf?.requestHeaders)\n          console.log('requestBody',xhr?.reqConf?.requestInfo?.body || '')\n          console.log('method',xhr?.reqConf?.requestInfo?.method || 'GET')\n        }\n        if (state.socketConnect) {\n          if (state.isHost) {\n            let data = {\n              type: 'DATA',\n              contentType: 'request',\n              channelSerial: state.channelSerial,\n              pid:xhr.reqConf.pid,\n              data: JSON.stringify({\n                did: xhr.reqConf.did, //数据唯一识别ID 32位随机数\n                aid: state.aid, //行为唯一识别ID 32位随机数，非空\n                url: urlObject?.href,\n                scheme: urlObject?.protocol,\n                host: urlObject?.host,\n                port: urlObject?.port,\n                path: `${urlObject?.pathname}${getQueryVariable('api',urlObject?.href)?`?api=${getQueryVariable('api',urlObject?.href)}`:''}`,\n                searchKey:hex_md5(`${urlObject?.pathname}${getQueryVariable('api',urlObject?.href)?`?api=${getQueryVariable('api',urlObject?.href)}`:''}`),\n                query: urlObject?.search?.split('?')[1] || \"\",\n                fragment: urlObject?.hash,\n                requestTime: moment(new Date()).format(\"YYYY-MM-DD HH:mm:ss.SSS\"), //请求发生时间\n                requestHeaders: JSON.stringify(xhr?.reqConf?.requestHeaders),\n                requestContentType: xhr?.reqConf?.requestHeaders['Content-Type']||xhr?.reqConf?.requestHeaders['content-type'] || '',\n                // requestBodyLength: 0,\n                requestBody: xhr?.reqConf?.requestInfo?.body || '', //接口参数\n                method: xhr?.reqConf?.requestInfo?.method || 'GET',\n                clientProtocol: 'http',\n                connectSerial: state.connectSerial\n              })\n            };\n            if(state?.mySocket?.webSocketState) {\n              state.mySocket.send(data)\n              console.log('主机发送Request请求')\n            }else{\n              let callback = function() {\n                console.log('主机队列发送Request请求',data)\n                state.mySocket.send(data)\n              }\n              $bus.once(\"webSocketState\", callback);\n            }\n          }\n        }\n      },\n      xhrHostResponse:function(xhr){\n        try {\n          let state = getGlobalData()\n          var headers = xhr.getAllResponseHeaders();\n          var arr = headers.trim().split(/[\\r\\n]+/);\n          var headerMap = {};\n          arr.forEach(function (line) {\n            var parts = line.split(': ');\n            var header = parts.shift();\n            var value = parts.join(': ');\n            headerMap[header] = value;\n          })\n          xhr.reqConf.headerMap = headerMap\n          if (state.socketConnect) {\n            if (state.isHost) {\n              let data = {\n                type: 'DATA',\n                contentType: 'response',\n                channelSerial: state?.channelSerial,\n                pid:xhr?.reqConf?.pid,\n                data: JSON.stringify({\n                  did:xhr?.reqConf?.did, //数据唯一识别ID 32位随机数\n                  responseTime: moment(new Date()).format(\"YYYY-MM-DD HH:mm:ss.SSS\"),\n                  responseHeaders: JSON.stringify(xhr?.reqConf?.headerMap),\n                  protocol: \"http\",\n                  responseContentType: xhr?.reqConf?.headerMap['Content-Type']||xhr?.reqConf?.headerMap['content-type']||'',\n                  // responseBodyLength: '',\n                  responseBody: xhr?.response, //响应消息体\n                  responseCode: xhr?.status, //响应状态码\n                  image: (xhr?.reqConf?.headerMap['Content-Type']||xhr?.reqConf?.headerMap['content-type'])?.indexOf('image/') >= 0 ? true:false,\n                  source: '',\n                  connectSerial: state.connectSerial,\n                  headersString: headers,\n                  responseXML:xhr?.responseType==='document'?xhr?.responseXML:null,\n                  resRaw: xhr?.reqConf?.responseInfo?.resRaw,\n                  // url: xhr?.reqConf?.requestInfo?.url,\n                })\n              };\n              if(state?.mySocket?.webSocketState){\n                console.log('主机发送Response请求')\n                state.mySocket.send(data)\n              }else{\n                let callback = function() {\n                  console.log('主机队列发送Response请求',data)\n                  state.mySocket.send(data)\n                }\n                $bus.once(\"webSocketState\", callback);\n              }\n            }\n          }     \n        } catch (error) {\n            console.error(error)\n        }\n      },\n      xhrClientQuery:function(originSend,...arg){\n        let state = getGlobalData();\n        let did = guid();\n        let pid = guid();\n        this.reqConf.did = did\n        this.reqConf.pid = pid\n        const urlObject = new URL(this.reqConf.requestInfo.url)\n        if (state.socketConnect) {\n          if (!state.isHost) {\n            let data = {\n              type: 'DATA',\n              contentType: 'query',\n              channelSerial: state.channelSerial,\n              pid,\n              data: JSON.stringify({\n                aid: state.aid, //行为唯一识别ID 32位随机数，非空\n                url: urlObject?.href,\n                scheme: urlObject?.protocol,\n                host: urlObject?.host,\n                port: urlObject?.port,\n                path: `${urlObject?.pathname}${getQueryVariable('api',urlObject?.href)?`?api=${getQueryVariable('api',urlObject?.href)}`:''}`,\n                searchKey:hex_md5(`${urlObject?.pathname}${getQueryVariable('api',urlObject?.href)?`?api=${getQueryVariable('api',urlObject?.href)}`:''}`),\n                query: urlObject?.search?.split('?')[1] || \"\",\n                fragment: urlObject?.hash,\n                requestTime: moment(new Date()).format(\"YYYY-MM-DD HH:mm:ss.SSS\"), //请求发生时间\n                requestHeaders: JSON.stringify(this.reqConf?.requestHeaders), //请求头\n                requestContentType:  this.reqConf.requestHeaders['Content-Type']||this.reqConf.requestHeaders['content-type'] || '',\n                // requestBodyLength: 0,\n                requestBody: this.reqConf?.requestInfo?.body || '', //接口参数\n                method: this.reqConf?.requestInfo?.method || 'GET',\n                clientProtocol: \"http\",\n                connectSerial: state.connectSerial,\n              })\n            }\n            if(state?.mySocket?.webSocketState){\n              // let mcClientWaitRequestQueueLength = state.mcClientWaitRequestQueue.length\n              // if(mcClientWaitRequestQueueLength>0){\n              //   while (mcClientWaitRequestQueueLength--) {\n              //     let item = state.mcClientWaitRequestQueue[mcClientWaitRequestQueueLength]\n              //     console.log('从机队列发送请求',item)\n              //     let socketMessage = (e) => {\n              //       let msg = JSON.parse(e.data);\n              //       if (msg.type === \"DATA\") {\n              //         if (msg.pid === item.data.pid) {\n              //           if(msg.code!==404){\n              //             let definePropertyKey = ['readyState','response','responseText','status','statusText','responseURL','responseXML']\n              //             definePropertyKey.forEach((definePropertyItem)=>{\n              //               Object.defineProperty(item.that, definePropertyItem, {\n              //                 writable:true,\n              //               });\n              //             })\n              //             let data = JSON.parse(msg.data)\n              //             let newData = data.responseBody\n              //             item.that.response = item.that.responseText = newData\n              //             item.that.status = data.responseCode || 200\n              //             item.that.statusText = HTTP_STATUS_CODES[item.that.status]\n              //             item.that.responseHeader = JSON.parse(data.responseHeaders)\n              //             item.that.headersString = data.headersString\n              //             item.that.responseURL = data.url\n              //             item.that.responseXML = data.responseXML\n              //             item.that.readyState = 2\n              //             item.that.dispatchEvent(new Event('readystatechange'))\n              //             item.that.readyState = 3\n              //             item.that.dispatchEvent(new Event('readystatechange'))\n              //             item.that.readyState = 4\n              //             item.that.dispatchEvent(new Event('readystatechange'))\n              //             item.that.dispatchEvent(new Event('load'));\n              //             item.that.dispatchEvent(new Event('loadend'));\n              //             console.log('从机队列接受中转消息',item.that)\n              //           }else {\n              //             item.that.socketCode = 404\n              //             originSend.apply(item.that, item.arg);\n              //           }\n              //           state.mySocket.socket.removeEventListener('message', socketMessage)\n              //         }\n              //       }\n              //     }\n              //     state.mySocket.socket.addEventListener('message', socketMessage)\n              //     state.mySocket.send(item.data)\n              //     console.log('从机队列发送请求成功',item)\n              //     state.mcClientWaitRequestQueue.splice(mcClientWaitRequestQueueLength, 1)\n              //   }\n              // }\n              console.log('从机发送请求',urlObject?.href)\n              let socketMessage = (e) => {\n                let msg = JSON.parse(e.data);\n                if (msg.type === \"DATA\") {\n                  if (msg.pid === pid) {\n                    if(msg.code!==404){\n                      let definePropertyKey = ['readyState','response','responseText','status','statusText','responseURL','responseXML']\n                      definePropertyKey.forEach((item)=>{\n                        Object.defineProperty(this, item, {\n                          writable:true,\n                        });\n                      })\n                      let data = JSON.parse(msg.data)\n                      let newData = data.responseBody\n                      this.response = this.responseText = newData\n                      this.status = data.responseCode || 200\n                      this.statusText = HTTP_STATUS_CODES[this.status]\n                      this.responseHeader = JSON.parse(data.responseHeaders)\n                      this.headersString = data.headersString\n                      this.responseURL = data.url\n                      this.responseXML = data.responseXML\n                      this.readyState = 2\n                      this.dispatchEvent(new Event('readystatechange'))\n                      this.readyState = 3\n                      this.dispatchEvent(new Event('readystatechange'))\n                      this.readyState = 4\n                      this.dispatchEvent(new Event('readystatechange'))\n                      this.dispatchEvent(new Event('load'));\n                      this.dispatchEvent(new Event('loadend'));\n                    }else {\n                      this.socketCode = 404\n                      originSend.apply(this, arg);\n                    }\n                    state.mySocket.socket.removeEventListener('message', socketMessage)\n                  }\n                }\n              }\n              state.mySocket.socket.addEventListener('message', socketMessage)\n              state.mySocket.send(data)\n              console.log('从机发送请求成功',urlObject?.href)\n            }else{\n              // state.mcClientWaitRequestQueue.push({that:this,data,arg:arg})\n              let that = this;\n              let callback = function(){\n                let socketMessage = (e) => {\n                  let msg = JSON.parse(e.data);\n                  if (msg.type === \"DATA\") {\n                    if (msg.pid === pid) {\n                      if(msg.code!==404){\n                        let definePropertyKey = ['readyState','response','responseText','status','statusText','responseURL','responseXML']\n                        definePropertyKey.forEach((item)=>{\n                          Object.defineProperty(that, item, {\n                            writable:true,\n                          });\n                        })\n                        let data = JSON.parse(msg.data)\n                        let newData = data.responseBody\n                        that.response = that.responseText = newData\n                        that.status = data.responseCode || 200\n                        that.statusText = HTTP_STATUS_CODES[that.status]\n                        that.responseHeader = JSON.parse(data.responseHeaders)\n                        that.headersString = data.headersString\n                        that.responseURL = data.url\n                        that.responseXML = data.responseXML\n                        that.readyState = 2\n                        that.dispatchEvent(new Event('readystatechange'))\n                        that.readyState = 3\n                        that.dispatchEvent(new Event('readystatechange'))\n                        that.readyState = 4\n                        that.dispatchEvent(new Event('readystatechange'))\n                        that.dispatchEvent(new Event('load'));\n                        that.dispatchEvent(new Event('loadend'));\n                      }else {\n                        that.socketCode = 404\n                        originSend.apply(that, arg);\n                      }\n                      state.mySocket.socket.removeEventListener('message', socketMessage)\n                    }\n                  }\n                }\n                state.mySocket.socket.addEventListener('message', socketMessage)\n                state.mySocket.send(data)\n                console.log('从机队列发送请求成功',data)\n              }\n              $bus.once(\"webSocketState\", callback);\n            }\n          }\n        }\n      },\n      getResponseHeader:function(getResponseHeader,name){\n        let state = getGlobalData()\n        if (!(state.socketConnect&&!state.isHost)||this.socketCode===404) {\n          return getResponseHeader.call(this, name);\n        }\n        return this.responseHeader[name]\n      },\n      getAllResponseHeaders:function(getAllResponseHeaders,...arg){\n        let state = getGlobalData()\n        if (!(state.socketConnect&&!state.isHost)||this.socketCode===404) {\n          return getAllResponseHeaders.apply(this, arg);\n        }\n        return this.headersString\n      },\n      isOriginSend:function(originSend,...arg){\n        let state = getGlobalData()\n        if (!(state.socketConnect&&!state.isHost)) {\n          originSend.apply(this, arg);\n        }   \n      },\n      fetchHostRequest:function(did,pid,...args){\n        let state = getGlobalData()\n        const urlObject = new URL(completionUrlProtocol(args[0]))\n        console.log('url:',urlObject?.href)\n        if (state.socketConnect) {\n          if (state.isHost) {\n            let data = {\n              type: 'DATA',\n              contentType: 'request',\n              channelSerial: state.channelSerial,\n              pid,\n              data: JSON.stringify({\n                did, //数据唯一识别ID 32位随机数\n                aid: state.aid, //行为唯一识别ID 32位随机数，非空\n                url: urlObject?.href,\n                scheme: urlObject?.protocol,\n                host: urlObject?.host,\n                port: urlObject?.port,\n                query: urlObject?.search?.split('?')[1] || \"\",\n                fragment: urlObject?.hash,\n                requestTime: moment(new Date()).format(\"YYYY-MM-DD HH:mm:ss.SSS\"), //请求发生时间\n                requestHeaders: args.length > 1 ? JSON.stringify((args[1].headers || {})) : JSON.stringify({}), //请求头\n                requestContentType: args.length > 1 && args[1].headers && args[1].headers['Content-Type'] || '',\n                // requestBodyLength: 0,\n                requestBody: args.length > 1 ? (args[1].body || '') : '', //接口参数\n                method: args.length > 1 && args[1].method || 'GET',\n                clientProtocol: 'http',\n                path: `${urlObject?.pathname}${getQueryVariable('api',urlObject?.href)?`?api=${getQueryVariable('api',urlObject?.href)}`:''}`,\n                searchKey:hex_md5(`${urlObject?.pathname}${getQueryVariable('api',urlObject?.href)?`?api=${getQueryVariable('api',urlObject?.href)}`:''}`),\n                connectSerial: state.connectSerial\n              })\n            }\n            if(state?.mySocket?.webSocketState) {\n              // let mcHostWaitRequestQueueLength = state.mcHostWaitFetchRequestQueue.length\n              // console.log('mcHostWaitRequestQueueLength:',mcHostWaitRequestQueueLength)\n              // if(mcHostWaitRequestQueueLength>0){\n              //   while (mcHostWaitRequestQueueLength--) {\n              //     let item = state.mcHostWaitFetchRequestQueue[mcHostWaitRequestQueueLength]\n              //     state.mySocket.send(item.data)\n              //     console.log('主机队列发送Fetch,Request请求',item)\n              //     state.mcHostWaitFetchRequestQueue.splice(mcHostWaitRequestQueueLength, 1)\n              //   }\n              // }\n              console.log('主机发送请求')\n              state.mySocket.send(data)\n            }else{\n              // state.mcHostWaitFetchRequestQueue.push({data})\n              let callback = function() {\n                console.log('主机队列发送Fetch,Request请求',data)\n                state.mySocket.send(data)\n              }\n              $bus.once(\"webSocketState\", callback);\n            }\n          }\n        }\n      },\n      fetchHostResponse:function(did,res,r){\n        let state = getGlobalData()\n        if (state.socketConnect) {\n          if (state.isHost) {\n            const responseHeaders = strMapToObj(res.headers)\n            let data = {\n              type: 'DATA',\n              contentType: 'response',\n              channelSerial: state.channelSerial,\n              data: JSON.stringify({\n                did, //数据唯一识别ID 32位随机数\n                responseTime: moment(new Date()).format(\"YYYY-MM-DD HH:mm:ss.SSS\"),\n                responseHeaders: JSON.stringify(responseHeaders),\n                protocol: \"http\",\n                responseContentType: res.headers.get('Content-Type'),\n                // responseBodyLength: '',\n                responseBody: JSON.stringify(JSON.parse(r)), //响应消息体\n                responseCode: res.status, //响应状态码\n                image: res.headers.get('Content-Type').indexOf('image/') >= 0 ? true:false,\n                source: '',\n                connectSerial: state.connectSerial,\n                resRaw: r\n              })\n            }\n            if(state?.mySocket?.webSocketState){\n              // let mcHostWaitRequestQueueLength = state.mcHostWaitFetchRequestQueue.length\n              // if(mcHostWaitRequestQueueLength>0){\n              //   while (mcHostWaitRequestQueueLength--) {\n              //     let requestItem = state.mcHostWaitFetchRequestQueue[mcHostWaitRequestQueueLength]\n              //     if(JSON.parse(requestItem.data.data).did===did){\n              //       state.mySocket.send(requestItem.data)\n              //       break;\n              //     }\n              //     state.mcHostWaitFetchRequestQueue.splice(mcHostWaitRequestQueueLength, 1)\n              //   }\n              // }\n              // let mcHostWaitResponseQueueLength =state.mcHostWaitFetchResponseQueue.length\n              // if(mcHostWaitResponseQueueLength>0){\n              //   while (mcHostWaitResponseQueueLength--) {\n              //     let item = state.mcHostWaitFetchResponseQueue[mcHostWaitResponseQueueLength]\n              //     let mcHostWaitRequestQueueLength = state.mcHostWaitFetchRequestQueue.length\n              //     if(mcHostWaitRequestQueueLength>0){\n              //       while (mcHostWaitRequestQueueLength--) {\n              //         let requestItem = state.mcHostWaitFetchRequestQueue[mcHostWaitRequestQueueLength]\n              //         if(JSON.parse(requestItem.data.data).did===JSON.parse(item.data.data).did){\n              //           state.mySocket.send(requestItem.data)\n              //           break;\n              //         }\n              //         state.mcHostWaitFetchRequestQueue.splice(mcHostWaitRequestQueueLength, 1)\n              //       }\n              //     }\n              //     state.mySocket.send(item.data)\n              //     console.log('主机队列发送Fetch,Response请求',item)\n              //     state.mcHostWaitFetchResponseQueue.splice(mcHostWaitResponseQueueLength, 1)\n              //   }\n              // }\n              state.mySocket.send(data)\n            }else{\n              // state.mcHostWaitFetchResponseQueue.push({data})\n              let callback = function() {\n                console.log('主机队列发送Fetch,Response请求',item)\n                state.mySocket.send(data)\n              }\n              $bus.once(\"webSocketState\", callback);\n            }\n          }\n        }\n      },\n      fetchClientQuery:function(pid,...args){\n        const urlObject = new URL(completionUrlProtocol(args[0]))\n        let state = getGlobalData()\n        if (state.socketConnect) {\n          if (!state.isHost) {\n            let data = {\n              type: 'DATA',\n              contentType: 'query',\n              channelSerial: state.channelSerial,\n              pid,\n              data: JSON.stringify({\n                aid: state.aid, //行为唯一识别ID 32位随机数，非空\n                url: urlObject?.href,\n                scheme: urlObject?.protocol,\n                host: urlObject?.host,\n                port: urlObject?.port,\n                path: `${urlObject?.pathname}${getQueryVariable('api',urlObject?.href)?`?api=${getQueryVariable('api',urlObject?.href)}`:''}`,\n                searchKey:hex_md5(`${urlObject?.pathname}${getQueryVariable('api',urlObject?.href)?`?api=${getQueryVariable('api',urlObject?.href)}`:''}`),\n                query: args.length > 1 ? (args[1].query || '') : '',\n                fragment: urlObject?.hash,\n                requestTime: moment(new Date()).format(\"YYYY-MM-DD HH:mm:ss.SSS\"), //请求发生时间\n                requestHeaders: args.length > 1 ? JSON.stringify((args[1].headers || {})) : JSON.stringify({}), //请求头\n                requestContentType: args.length > 1 && args[1].headers && args[1].headers['Content-Type'] || '',\n                // requestBodyLength: 0,\n                requestBody: args.length > 1 ? (args[1].body || '') : '',\n                method: args.length > 1 && args[1].method || 'GET',\n                clientProtocol: \"http\",\n                connectSerial: state.connectSerial,\n              })\n            }\n            if(state?.mySocket?.webSocketState) {\n              state.mySocket.send(data)\n            } else {\n              let callback = function() {\n                console.log('从机队列发送Fetch请求成功',data)\n                state.mySocket.send(data)\n              }\n              $bus.once(\"webSocketState\", callback);\n            }\n          }\n        }\n      },\n      fetchResult:function(pid,reqId,origFetch,...args){\n        let fetchResult = null;\n        let state = getGlobalData()\n        if (state.socketConnect) {\n          if (!state.isHost) {\n            fetchResult = new Promise((resolve, reject) => {\n              // 做一些异步操作\n              let socketMessage = (e) => {\n                let msg = JSON.parse(e.data);\n                if (msg.type === \"DATA\") {\n                  if (msg.pid === pid) {\n                    if(msg.code!==404) {\n                      let data = JSON.parse(msg.data)\n                      this.emit('REQUEST.DONE', {\n                        id: reqId,\n                        responseInfo: {\n                          contentType: data.contentType,\n                          status: data.responseCode,\n                          resRaw: data.resRaw\n                        }\n                      })\n                      let newData = data.responseBody\n                      let init = {\n                        \"status\": data.responseCode,\n                        \"statusText\": \"OK\"\n                      };\n                      let newResponse = new Response(newData, init);\n                      resolve(newResponse);\n                    }else{\n                      fetchResult = origFetch(...args);\n                      fetchResult.then(res => {\n                        res.clone().text().then(r => {\n                          resolve(res);\n                          this.emit('REQUEST.DONE', {\n                            id: reqId,\n                            responseInfo: {\n                              contentType: res.headers.get('Content-Type'),\n                              status: res.status,\n                              resRaw: r\n                            }\n                          })\n                        })\n                      })\n                    }\n                    state.mySocket.socket.removeEventListener('message', socketMessage)\n                  }\n                }\n              }\n              if(state?.mySocket?.webSocketState) {\n                state.mySocket.socket.addEventListener('message', socketMessage)\n              }else{\n                let webSocketStateCallback = function() {\n                  state.mySocket.socket.addEventListener('message', socketMessage)\n                }\n                $bus.once(\"webSocketState\", webSocketStateCallback);\n              }\n            });\n          }\n        }\n        return fetchResult\n      }\n    })\n  },\n})"
  },
  {
    "path": "Web/packages/web/src/plugins/resources/css/var.less",
    "content": "\n// common\n@border-color:#f5f6f7;\n@border-active-color:#1485ee;\n@background-color: #ffffff;\n\n// console-tap\n@tap-height: 38px;\n@tap-font-size: 16px;\n@tap-font-color: #333333;\n\n// error\n\n"
  },
  {
    "path": "Web/packages/web/src/plugins/resources/index.js",
    "content": "import Resources from './main.vue'\nimport { RouterPlugin } from '@dokit/web-core'\n\n\nexport default new RouterPlugin({\n    name: 'resources',\n    nameZh: '静态资源',\n    component: Resources,\n    icon: 'https://pt-starimg.didistatic.com/static/starimg/img/Sc7OC34ccw1622432302129.png',\n\n    onLoad() {\n        //console.log(window.performance.now())\n        // console.log('resources')\n    },\n    onUnload() {\n\n    },\n\n})"
  },
  {
    "path": "Web/packages/web/src/plugins/resources/js/resources.js",
    "content": "export const ResourceMap = {\n    0: 'css',\n    1: 'script',\n    2: 'img',\n    // 3: 'other',\n}\nexport const ResourceEnum = {\n    CSS: 0,\n    SCRIPT: 1,\n    IMG: 2,\n    OTHER: 3,\n}\n\nexport const ResourceEntriesMap = {\n    'css': ResourceEnum.CSS,\n    'script': ResourceEnum.SCRIPT,\n    'img': ResourceEnum.IMG,\n    // 'other': ResourceEnum.OTHER,\n}\n\nexport const Resource_METHODS = [\"css\", \"script\", \"img\"]\n\nexport const ResourceTabs = Object.keys(ResourceMap).map(key => {\n    return {\n        type: parseInt(key),\n        name: ResourceMap[key]\n    }\n})\n\nexport const regImg = /\\.(jpeg|jpg|gif|png|bmp)$/\nexport const regCss = /\\.(css|less)$/\n\nexport const isImg = (url) => regImg.test(url)\nexport const isCss = (url) => regCss.test(url)\n\nexport const getResourceEntries = function(callback) {\n    const performance = window.performance || window.webkitPerformance || window.msPerformance\n    if (performance && performance.getEntriesByType) {\n        const entries = performance.getEntriesByType(\"resource\")\n            //const curtime = performance.now()\n        let arr = []\n        entries.forEach((entry) => {\n                let tmp = {\n                    initiatorType: entry.initiatorType,\n                    name: entry.name,\n                }\n                let flag = 0\n                for (let i = 0; i < arr.length; i += 1) {\n                    if (arr[i].initiatorType == tmp.initiatorType && arr[i].name == tmp.name) {\n                        flag = 1;\n                        break;\n                    }\n                }\n\n                if (!flag) arr.push(tmp)\n            })\n            //arr = Array.from(new Set(arr))\n        arr.forEach((entry) => {\n            let entryType = ResourceEntriesMap['other']\n            Resource_METHODS.forEach((type) => {\n                if (entry.initiatorType === type || (type === \"img\" && entry.initiatorType === \"css\" && isImg(entry.name)) || (type === \"css\" && entry.initiatorType === \"link\" && isCss(entry.name))) {\n                    entryType = ResourceEntriesMap[type];\n                }\n            })\n            callback({\n                type: entryType,\n                initiatorType: entry.initiatorType,\n                entryName: entry.name,\n                base64: ''\n            })\n            // if (entryType === ResourceEntriesMap['img']) {\n            //     getBase64Image(entry.name, (res) => {\n            //         callback({\n            //             type: entryType,\n            //             initiatorType: entry.initiatorType,\n            //             entryName: entry.name,\n            //             base64: res\n            //         })\n            //     })\n            // } else {\n            //     callback({\n            //         type: entryType,\n            //         initiatorType: entry.initiatorType,\n            //         entryName: entry.name,\n            //         base64: ''\n            //     })\n            // }\n        })\n    }\n}\n\nexport const imgLoad = function(url, callback) {\n    var img = new Image();\n    img.src = url;\n    if (img.complete) {\n        callback(img.width, img.height);\n    } else {\n        img.onload = function() {\n            callback(img.width, img.height);\n            img.onload = null;\n        };\n    };\n}\n\nexport const getBase64Image = function(imgUrl, callback) {\n    var xhr = new XMLHttpRequest();\n    xhr.open(\"get\", imgUrl, true);\n    xhr.responseType = \"blob\";\n    xhr.onload = function() {\n        if (this.status == 200) {\n            //得到一个blob对象\n            var blob = this.response;\n            //console.log(\"blob\", blob)\n            let oFileReader = new FileReader();\n            oFileReader.onloadend = function(e) {\n                let base64 = e.target.result;\n                callback(base64)\n            };\n            oFileReader.readAsDataURL(blob);\n        } else {\n            callback('')\n        }\n    }\n    xhr.onerror = function(e) {\n        callback('')\n    }\n    xhr.send();\n}\n\n\nexport const url2blob = function(file_url, callback) {\n    let xhr = new XMLHttpRequest();\n    xhr.open(\"get\", file_url, true);\n    xhr.responseType = \"blob\";\n    xhr.onload = function() {\n        if (this.status == 200) {\n            const reader = new FileReader()\n            reader.onload = function() {\n                callback(reader.result)\n            }\n            reader.readAsText(this.response);\n        }\n    };\n    xhr.send();\n}\n\nexport const url2blobPromise = function(file_url) {\n    return new Promise(function(resolve, reject) {\n        let xhr = new XMLHttpRequest();\n        xhr.open(\"get\", file_url);\n        xhr.responseType = \"blob\";\n        xhr.addEventListener('load', function() {\n            if (this.status == 200) {\n                const reader = new FileReader()\n                reader.onload = function() {\n                    resolve(reader.result)\n                }\n                reader.readAsText(this.response);\n            } else {\n                reject()\n            }\n        })\n\n        xhr.addEventListener('error', function() {\n            reject()\n        })\n\n        xhr.send();\n    });\n}\n\nexport const trimLeft = function(s) {\n    if (s == null) {\n        return \"\";\n    }\n    var whitespace = new String(\" \\t\\n\\r\");\n    var str = new String(s);\n    if (whitespace.indexOf(str.charAt(0)) != -1) {\n        var j = 0,\n            i = str.length;\n        while (j < i && whitespace.indexOf(str.charAt(j)) != -1) {\n            j++;\n        }\n        str = str.substring(j, i);\n        str = (new Array(j).join(\"&nbsp;\")) + str\n\n    }\n    return str;\n}\n"
  },
  {
    "path": "Web/packages/web/src/plugins/resources/main.vue",
    "content": "<template>\n  <div class=\"all-resources-container\">\n    <resource-tap :tabs=\"resourceTabs\" @changeTap=\"handleChangeTab\" @refreshResource=\"refreshResource\"></resource-tap>\n    <div class=\"resource-container\">\n      <resource-container :resourceList = \"curResourceList\"></resource-container>\n    </div>\n  </div>\n</template>\n\n<script>\nimport ResourceTap from './resource-tap';\nimport ResourceContainer from './resource-container';\nimport {ResourceTabs, ResourceEnum, getResourceEntries} from './js/resources';\n\nexport default {\n  components: {\n    ResourceTap,\n    ResourceContainer,\n  },\n  data() {\n    return {\n      resourceTabs: ResourceTabs,\n      curTab: ResourceEnum.CSS,\n    }\n  },\n  computed:{\n    resourceList(){\n      let resourceList = this.$store.state.resourceList || [];\n      \n      return resourceList\n    },\n    curResourceList(){\n      return this.resourceList.filter(resource => {\n        return resource.type == this.curTab\n      })\n    },\n  },\n  created () {\n    //console.log(window.performance.now())\n    this.$nextTick(()=>{\n      this.refreshResource()\n    })\n  },\n  mounted (){\n    \n  },\n  methods: {\n    handleChangeTab(type){\n      this.curTab = type\n      //console.log(this.$store.state.resourceList)\n    },\n    refreshResource() {\n      let resourceList = [];\n      getResourceEntries(({ type, initiatorType, entryName, base64}) => {\n        resourceList.push({\n          type: type,\n          initiatorType: initiatorType,\n          entryName: entryName,\n          base64: base64\n        });\n      })\n      \n      this.$store.state.resourceList = resourceList\n    }\n\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n@import \"./css/var.less\";\n.all-resources-container{\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/resources/resource-container.vue",
    "content": "<template>\n  <div class=\"resource-container\">\n    <resource-item\n      v-for=\"(item, index) in resourceList\"\n      :key=\"item.entryName\"\n      :index=\"index\"\n      :type=\"item.type\"\n      :initiatorType=\"item.initiatorType\"\n      :entryName=\"item.entryName\"\n      :base64=\"item.base64\"\n    ></resource-item>\n  </div>\n</template>\n<style lang=\"less\" scoped>\n@import \"./css/var.less\";\n.resource-container{\n  padding: 0 5px;\n}\n</style>\n<script>\nimport ResourceItem from \"./resource-item\"\nexport default{\n  components:{\n    ResourceItem\n  },\n  props: {\n    resourceList: {\n      type: Array,\n      default: [],\n    },\n  },\n  data() {\n    return {};\n  },\n  methods:{\n    \n\n  }\n}\n</script>"
  },
  {
    "path": "Web/packages/web/src/plugins/resources/resource-item.vue",
    "content": "<template>\n  <div class=\"resource-item\">\n    <do-row>\n      <do-col :span=\"22\">\n        <div\n          class=\"resource-preview\"\n          v-html=\"resourcePreview\"\n          @click=\"toggleDetail\"\n        ></div>\n      </do-col>\n      <do-col :span=\"2\">\n        <div class=\"resource-toggle-icon\">\n          <span v-if=\"!showContent\">▸</span>\n          <span v-else>▾</span>\n        </div>\n      </do-col>\n    </do-row>\n    <div v-if=\"showContent\">\n      <div class=\"resource-detail\">\n        <div v-html=\"detailHtml\"></div>\n        <div class=\"resource-empty\" v-if=\"!detailHtml\">Loading ~</div>\n      </div>\n    </div>\n  </div>\n</template>\n<script>\nimport {\n  ResourceMap,\n  imgLoad,\n  url2blobPromise,\n  trimLeft,\n} from \"./js/resources\";\n\nexport default {\n  components: {},\n  props: {\n    index: [Number],\n    type: [Number],\n    initiatorType: [String],\n    entryName: [String],\n    base64: [String],\n  },\n  data() {\n    return {\n      showContent: false,\n      detailImgThumb: \"\",\n      detailHtml: \"\"\n    };\n  },\n\n  computed: {\n    resourcePreview() {\n      let index = this.index;\n      let url = this.entryName;\n      let html = `<div>${index + 1}.${url}</div>`;\n      return html;\n    },\n  },\n  methods: {\n    toggleDetail() {\n      this.showContent = !this.showContent;\n      if (this.detailHtml) return;\n\n      if (ResourceMap[this.type] === \"img\") {\n        this.getDetailImgThumb((htmlSrc) => {\n          this.detailHtml = htmlSrc;\n        });\n      } else {\n        this.getDetailCode((htmlSrc) => {\n          this.detailHtml =\n            `<div style=\"max-height: 300px;overflow-y: scroll;overflow-x:hidden;word-break:break-all;text-align: left;\">` +\n            htmlSrc +\n            `</div>`;\n        });\n      }\n    },\n    getDetailImgThumb(callback) {\n      let url;\n      if (this.base64 !== \"\") {\n        url = this.base64;\n      } else {\n        url = this.entryName;\n      }\n      let htmlSrc = \"\";\n      htmlSrc = `<img src = \"${url}\" style=\"object-fit: cover;height:100px\" />`;\n      this.getDetailImgSize((r) => {\n        htmlSrc += r;\n        callback(htmlSrc);\n      });\n    },\n    getDetailImgSize(callback) {\n      let url;\n      if (this.base64 !== \"\") {\n        url = this.base64;\n      } else {\n        url = this.entryName;\n      }\n      imgLoad(url, (w, h) => {\n        callback(`<div>${w}*${h}</div>`);\n      });\n    },\n    getDetailCode(callback) {\n      url2blobPromise(this.entryName)\n        .then((res) => {\n          res = res.replace(/</g, \"&lt;\");\n          res = res.replace(/>/g, \"&gt;\");\n          let tmp = res;\n          tmp = tmp.split(\"\\n\");\n          let len = tmp.length;\n          let r = ``;\n          for (let i = 0; i < len; i = i + 1) {\n            tmp[i] = trimLeft(tmp[i]);\n            r += `\n              <div class=\"codeline\" style=\"display:flex;line-height:14px;\">\n                <div class=\"codeindex\" style=\"min-width: 40px;background-color:#F0F0F0;color: #808080;text-align:right;padding-right:5px;\">${i + 1}</div>\n                <div class=\"codevalue\">${tmp[i]}</div>\n              </div>\n            `;\n          }\n          callback(r);\n        })\n        .catch(() => {\n          callback(`fail to load resource`);\n        });\n    },\n  },\n};\n</script>\n<style lang=\"less\" scoped>\n.resource-item {\n  margin-top: 10px;\n  border-radius: 5px;\n  overflow: hidden;\n  border: 1px solid #d6e4ef;\n  font-size: 12px;\n}\n.resource-preview {\n  word-break: break-all;\n  color: #1485ee;\n  padding: 5px;\n}\n.resource-toggle-icon {\n  line-height: 24px;\n}\n\n.resource-detail {\n  border-top: 1px solid #d6e4ef;\n  text-align: center;\n  .resource-empty {\n    padding: 10px;\n    font-size: 16px;\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/resources/resource-tap.vue",
    "content": "<template>\n  <div class=\"tab-container\">\n    <div class=\"tab-list\">\n      <div\n        class=\"tab-item\"\n        :class=\"curIndex === index? 'tab-active': 'tab-default'\"\n        v-for=\"(item, index) in tabs\"\n        :key=\"index\"\n        @click=\"handleClickTab(item, index)\"\n      >\n        <span class=\"tab-item-text\">{{ item.name }}</span>\n      </div>\n      <div class=\"tab-item\" @click=\"handleRefresh\">\n        <img style=\"width:16px;margin-top:12px;\" src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAvFQTFRFAAAAJCQkLCwsLS0tLCwsLCwsLCwsLS0tLCwsLCwsLS0tLCwsLCwsLCwsLCwsLCwsKysrLCwsLCwsLCwsLS0tKysrLCwsLCwsLCwsLCwsKCgoKioqKysrLCwsLCwsLCwsLCwsLS0tLS0tMDAwLCwsKysrLCwsLCwsICAgLS0tKysrLCwsLCwsKysrJiYmLS0tLCwsKysrKioqLS0tKysrLCwsLi4uKioqLCwsLCwsLS0tLi4uLS0tLCwsLS0tKSkpLCwsLCwsLS0tKioqLCwsLCwsLCwsLCwsLi4uKysrLCwsLCwsMzMzLCwsLCwsLCwsLCwsLi4uLS0tLS0tLS0tLCwsLy8vLCwsLCwsLCwsKysrLCwsLS0tLCwsKysrLCwsLCwsLCwsKysrLCwsKysrLS0tLS0tKysrLCwsLS0tKysrLCwsLCwsLCwsLCwsKioqLy8vLCwsLCwsKysrLS0tLCwsLCwsLCwsKysrMDAwLCwsLCwsKCgoLCwsKysrKysrQEBALCwsLS0tLCwsLCwsKysrJycnLCwsLCwsLCwsLS0tLCwsLCwsKysrLS0tLi4uKysrKysrLS0tLCwsLi4uMzMzLS0tLS0tLS0tLCwsLCwsLCwsAAAALCwsLCwsLCwsLCwsLCwsLCwsLCwsKysrLS0tKysrLCwsLS0tKysrLS0tKysrKysrLS0tLCwsKioqLS0tKSkpLCwsJycnLCwsLCwsLCwsKysrLCwsLCwsLi4uKioqLCwsLi4uKysrLCwsLi4uLS0tLCwsLCwsLCwsJCQkLCwsKysrLS0tOTk5LCwsLCwsLi4uKysrLCwsLCwsAAAALCwsKysrLCwsKioqKysrLS0tVVVVLS0tLS0tKysrLS0tKioqLCwsLS0tKSkpLCwsLS0tKysrLCwsLCwsLS0tKysrLS0tLS0tMTExMTExLCwsLCwsLCwsLCwsLS0tKysrLS0tLCwsKysrKysrLS0tKysrKioqLCwsLCws////4ewITwAAAPl0Uk5TAAcuVXuiwcjQ2N/n7/b+8eXLv7KlmYx/USMgYJO54PXHaz4QndnhqghEjsqpWRR9zGUYZ7azVDbwxGY9oNZ4GajoiStMr/qSHCqeaQV67kBWFj+m2WMbreTisClbYof71XlwXFNJT19ufo2su+NLPSbqbUcidKPTdiD06xO+gjsENHehrl4aF8bJKJf3Eo9DJEHlhicPbIjUdVe0AeaVnN2KxYCBSmpSmusRTS9xRTBhJe0Ni/y4ZKTNCzf9OM5oIbfa29EOm5QtCZBdTnzDbwK1vPkxVGUDq4NIMwy6gh+Y9vGnz72rjloaFZHs89yxwpSW3585sQbSvsraFAAAAAFiS0dE+tVtBkoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAk2SURBVHja7Z15XBVVFMfHhyiguBCIKW6kspmKSypqKAKKgQhugLgguOAWLqkpbqGFmrsZ5paSQYiSaQtZAS1WmqatWtmila22aTX/9Z4iMufeee/emXvnzufT/P59nN85X+bNm5m7nJEkS5YsWbJkyZIlS5YsWbJkiaPq2Nzquter7+Hp1aChd6PGTTyb+tzh69fMv/mdoisjV4uWAa1ay6pq09avXeBdoot0DdG+g0yijkFuwaKLVVVIaBgRRLUadbq7s+iSMbJ16UpDUc0SFN5CdOEKdeveg56imqXnPaKrr1Evv95aMW6oT0Qd0QgO9e2ni+KG+t8bKRojZIB+DIcGRg0SiREdwwbDodjBQ0RhxA1lh+GQ131CMOIThrHlsKtHovEcw6kufsRqluQsabfkJNL6CGUbwQXDrpGj1HKOHpNi/zw1bSxDjvQGvDjsGoO/2CfW/ME4ZhzjiQqaMDEmKmNSRGbW5MQpU6dlT28/Y+YsokDP2ZicLWv9wf1sMHJ6uixlju/cefNxsZ1DHliw0DXKIiTwwcW1Pl6Sy4Ijy8vFgVi6bLlzhxUrH6rngiQPljpd8XEoAw5/5xWsWv0wkc0jmXlLnPl0DVT+fb7i06b6OZyeHmsy1lJYBUeva6Pu9aji6rgefKr7u7VBPbP3uo3UdpuWOfmeLqv1h5vBZ8k6OdSvHlu2btPk+Nh29Qvr49xAVHMW7NBxxX1ip5rtBj4gu9TurXZPi9f3D5qmdt7v4QKyWCXbXv1DVYM6qXgHcAB5Ep8qrLluDIciUvD2+5iD7McnKmT1uL0pD5/gKcYgq7BZPA4wwnDoaW9sjiKmIOuwOYrZDrAF4n8VtzEEwd6XPBPOFMOuEuyzc2wuM5CD2LO8lDWHXYcaYTL5sAIZ3R/nzmck6jAuVxQjkAU4Dl6Dgzbcz/wOJiCH8Eebl8q2YPI9ywAkOdZQDkk68hyasLF+kKNtUdsCnhySFKl2M6QL5JjhHPZ/3vPsQRIxHPwnAXaxB3kB5TjCnUOS4liDzEUs5tgM4JCkIrYg85sgFi8awiFJdZmCFCMOxQZxSNJLDEGiEQMPAyeUy9mBoPNRLJ8/oKZk5Pko5M0K5DASX8iRI1SmEB0IMs/p8TI/jt00HHQggUh4Jj+O/VQcdCDIaMAYfhyL6DioQI7D4Fde5QfyGkcQ5BoSxY8jPpYfSCRcXzKL4yqLOEoOGpAKGJvBj4MepJLcuwqEvs5z9R7tV+sNcutSGDuJIwf1yd6e3DkbhL7J92lqEh0Ixe9nOQit4MqhNrasIopHiRAQmvIWZxDpBDlHEIUtvIN7mzeHJI0jxOi4ncY1H0TTT9jSq/PycYUuVfTOuzSeQwDHGgM4uMgNgPC8GHLVSQBCs6DBVCpQctQXXY9WwVNksOiCtOoUAEkXXZBWBQCQTaIL0qpWSo5ZouvRLLAfh+aWwFQKBt+sRfotxcgGQN4TXZBWweu6tiVlJtBpACK6Hs1yV3J4iq5Hs84oQdJE16NZYKk0x4E5OvkHFcgeC/zJA1KVIPeKBrip9bdma8qJd/6AZQbvi0a4obW1KiIc8zwLfrQiRDPcUO2ds0vJQkoACMdZEXIVKkoiO08qAUiWaAiHzilK+oAopgyATBYNISGDwx+uIAmCy08E7ERzWRPRtMJHJjwiB0BNZSRB8014jnwMaqokCco14a8WHEVYTxK0AgR9IprCrghQ06dEUWAlnhmu7GD+ZDFZFFjbZIZ7rQxlSU3IosAaYjPc/e5TltSfLAqsK40RTWFXmrKktmRR55VR3BeVEmiisqTzZFEXlFGxoinsmqAs6QJZVKaWqw9XwWs04WD0ShA2TzSH9Bmo6HOysC9A2FzRHNI0UBHpAjiw7ZxinQEngR2pqaRxM5Vx4ge25igLukgaB+dHhDUsqdZyUA/xDBq813QTDPIlqGcqaSCcQ2SxoV+PfEA9XxFHglndfLEco8F2kg7koeaaZ58KqqH4FTXXyge4sp34FDHZWpSxoBiKU0TM6iA1JYBawmiCBazXUhXcikz1GxoCglOOCuOApVB+O8pBtLgh+a9BJV3pwrP1hbPT2m9AJV3o4pF1v6IOyVZYCO2uuypzHJIkuAW5B61DhTkOyQ5YRndah8hLZjgk8eCuT+7djdoD2T/STgDIdliEH70HsqOn9WjDOYYgDZB7aXBB9lht0GCiT/AuXO6nxQXd9aa3qxWtkpEK+mryQfYh9jQYBOlXdVmbD7ozlF2HRBKNQvKHaHRC9uoONGYz+02VDoTpV2m1QndPtzKOY9dFJPtszWZoL59jhoE0Q3IP1W4W1xBxM2qO9xSSeZiebqwJiF0DY04T9ASRE/T4xaMtr741og93CbqbOkxfx77hiKEhw3VBaFq947aYHpPfcefYiyYdodfThulR/D1njitoSganZjrqyrX3A1wtd1M/MPDFNZSdzpFjBibfeCbOuI7LJ7hxLMVkY3S3ug3Xy7ZTCReMHzHtvGSvHEbuWRhzuQ+ProClHrhU7Ja+GdSnUQrHtpmlWETuUoZ0zvwJv4OazYl+Swb0Mj2A/VrJV5hyqHeXZeUfX4hP4MuYQ7Xfrw+bZcGz8/H2PObK1JoOMjgoST+reP/CgUO9J7bug5LpqeL8Kw8OyUmX8n6bdbhmnVOzvcqJQ5Iuq6WUfyvSaJm5VNWzDzcOp538fx9FP894NLytuiHHLliS83crpATQzVNu/CPFidufXDnwDwu3Rf6aiLEZzl/yw/K+BK+s1k4LkMP2THX1Iroj6YPrOzf5y4gtEtt2yq509dr2eZX44CmLgly/4KZnjgEcEuk7emILYqIOrZ59PSdXys25Hr36UNTfnkSB8lZjMCTOb03azeL5nFT83mMl+xo54i9xe7NYmPHrJ/9JaKi/bqBhCTrfZKJNcdcYcwxl+fYzKh1M0199jWKiRWE4dJjRGyrlAYdFYjgUmKefQs4L1F+Ifh0vvqSLonfxcdEItxRZUaUZo6pC+BtcFSrNLtdAUZ7NY7xSr0Ku0F0k80O1LgDgrxYt248kgig46SZ6MwcBjNP3s8utWwWcMj1EjYJtbqfdzyxMvd3Ro3Hqv2fc67rZzPtaduc6W1JZllhWWXJWdCGWLFmyZMmSJUuWLFmyZOn/ov8AOnjh87MOpuoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDYtMjNUMjE6MjY6MDkrMDg6MDBCOpSGAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA2LTIzVDIxOjI2OjA5KzA4OjAwM2csOgAAAABJRU5ErkJggg==\" alt=\"\" srcset=\"\">\n      </div>\n    </div>\n  </div>\n</template>\n<style lang=\"less\" scoped>\n@import \"./css/var.less\";\n.tab-container{\n  .tab-list{\n    display: flex;\n    height: @tap-height;\n    justify-content: space-between;\n    align-items: center;\n    border: 1px solid @border-color;\n  }\n  .tab-item{\n    flex: 1;\n    height: @tap-height;\n    line-height: @tap-height;\n    text-align: center;\n  }\n  .tab-item-text{\n    font-size: @tap-font-size;\n    color: @tap-font-color;\n  }\n  .tab-active{\n    border-bottom: 1px solid @border-active-color;\n  }\n  .tab-default{\n    border: none;\n  }\n}\n</style>\n<script>\nexport default {\n  props: {\n    tabs: {\n      type: Array\n    },\n  },\n  data() {\n    return {\n      curIndex: 0\n    };\n  },\n  methods: {\n    handleClickTab(item, index) {\n      let { type } = item;\n      this.curIndex = index;\n      this.$emit(\"changeTap\", type);\n    },\n    handleRefresh() {\n      this.$emit(\"refreshResource\");\n    }\n  },\n};\n</script>"
  },
  {
    "path": "Web/packages/web/src/plugins/scan-code/app.vue",
    "content": "<template>\n  <div class=\"scan\">\n    <div class=\"nav\">\n      <a class=\"close\" @click=\"() => $router.back()\"></a>\n      <p class=\"title\">Scan QRcode</p>\n    </div>\n    <div class=\"scroll-container\">\n      <Scaner\n        @code-scanned=\"codeScanned\"\n        @error-captured=\"errorCaptured\"\n        :stop-on-scanned=\"true\"\n        :draw-on-found=\"true\"\n        :responsive=\"false\"\n      />\n    </div>\n  </div>\n</template>\n\n<script>\nimport Scaner from \"../../components/ScanerComponent.vue\";\nimport { $bus } from \"../../assets/util\";\nexport default {\n  name: \"Scan\",\n  components: {\n    Scaner,\n  },\n  data() {\n    return {\n      errorMessage: \"\",\n      scanned: \"\",\n    };\n  },\n  methods: {\n    codeScanned(code) {\n      this.scanned = code;\n      $bus.emit(\"scanCode\", code);\n      setTimeout(() => {\n        alert(`扫码解析成功: ${code}`);\n      }, 200);\n    },\n    errorCaptured(error) {\n      switch (error.name) {\n        case \"NotAllowedError\":\n          this.errorMessage = \"Camera permission denied.\";\n          break;\n        case \"NotFoundError\":\n          this.errorMessage = \"There is no connected camera.\";\n          break;\n        case \"NotSupportedError\":\n          this.errorMessage =\n            \"Seems like this page is served in non-secure context.\";\n          break;\n        case \"NotReadableError\":\n          this.errorMessage =\n            \"Couldn't access your camera. Is it already in use?\";\n          break;\n        case \"OverconstrainedError\":\n          this.errorMessage = \"Constraints don't match any installed camera.\";\n          break;\n        default:\n          this.errorMessage = \"UNKNOWN ERROR: \" + error.message;\n      }\n      console.error(this.errorMessage);\n      alert(\"相机调用失败\");\n    },\n  },\n  mounted() {\n    var str = navigator.userAgent.toLowerCase();\n    var ver = str.match(/cpu iphone os (.*?) like mac os/);\n    if (ver && ver[1].replace(/_/g, \".\") < \"10.3.3\") {\n      alert(\"相机调用失败\");\n    }\n  },\n};\n</script>\n\n<style lang=\"less\" scoped>\n.scan {\n  height: 100%;\n  width: 100%;\n  .nav {\n    width: 100%;\n    height: 48px;\n    line-height: 48px;\n    position: fixed;\n    top: 0;\n    left: 0;\n    background-color: #ffffff;\n    .title {\n      padding: 0;\n      margin: 0;\n      font-size: 16px;\n      color: #000000;\n      text-align: center;\n    }\n    .close {\n      display: inline-block;\n      height: 22px;\n      width: 22px;\n      background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAzCAMAAADIDVqJAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAXFQTFRFAAAANX7GNHzFM3zFM3zFNn3ENYDHNHzFM3zENIDHNH7INH3ENX3GNn7GM33ENYDFNYDGM4DINH7HM3zENH3HNHzENYDGM37FM33FNH3FNn3FNX7HN4DINHzEM33HNH7FNIDGNn7GM33ENX3FNXzHNoDENH7FNH3EM3zEN33INHzFNH3FM4DHNX3HNH3GNX7ENHzENHzFNYDFNH3FM33ENn3JM33GN4DINX7EM3zEM33GNH7FM33FM33FNIDFNH3GNX3KM37HNHzFN4DINX7FNH3EOHzHNH3GM3zFM4DGM33EM3zENHzENH3EM33FOoTFgID/NHzFOIDHM3zEM3zFNoPJM33FM33ENH3FNHzENYDL////NHzENH3FM4PFM3zFOYDGNH3FM3zFN3zINHzEM33FNoDJM3zFM33FNH3ENHzFM37FNH3ENH3FM33ENH7GNX7FM33ENH3FNH7FM37ENH7FM3zFNH3EM4jMM3zE////TdHL6gAAAHl0Uk5TAEPy+vA9RPb0QEX3P0fzPkg8Sfg7SjpL+fE5TThON082Ue81UjRT++4zVO0yVjFX/OwwWOsvWi5b6i1d/eksXitf6Cph5yli/ihk5ieh3x8CoiCg4CGf4Z3iIgGc4yOaJJnkJZjlJpaVk5KQj42LioiHhYSCgHOyD3AmCqsAAAABYktHRFt0vJU0AAAACXBIWXMAAABIAAAASABGyWs+AAABPUlEQVQ4y43UxVoCUBQE4Ctgd3cXKAYoKqioINiBrdjdXby9C+6c3ZzPWf+rE2MMS4bDmY4rk5qs7FQ6ObnU5OXDFFBTCFNUTE2JmFJqysqtcVRQUymmiprqGmtq66ipF9NATSOMq4maZidMCzWtMG3t1HSI6aSmq9sat4eaHpheLzV9YvqpGRi0xuenZghmOEDNiJhRasZggiFqxiesmQxTMyVmmpoZmEiUmlmYWJyauXmYBWoWYZaWqTEr1qRW1zhaT4jaUNQm1Na2onagdjW1B7V/wFXyEOroWFEnok4VdQZ1fqGoS6ira0XdpP4x+uStKL5EY+6gYpq6h4rEFfUgKqqoRyjlkI15ggqGFfUsKqSoFyjlTY15hfIFFPUmyq+odyilhIz5gHJ7FfUpyqOoLyilYo35/rFJ/Jo/DZ3bT7fEcIgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDQtMjFUMTc6MzI6MjgrMDg6MDBBnT5hAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA0LTIxVDE3OjMyOjI4KzA4OjAwMMCG3QAAAABJRU5ErkJggg==\")\n        no-repeat center;\n      background-size: auto 100%;\n      position: absolute;\n      left: 16px;\n      top: 14px;\n    }\n  }\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/scan-code/index.js",
    "content": "import index from './app.vue'\nimport {RouterPlugin} from '@dokit/web-core'\n\nexport default new RouterPlugin({\n  nameZh: '扫码',\n  name: 'scanCode',\n  icon: 'https://pt-starimg.didistatic.com/static/starimg/img/jvD7qcMXX51645432343946.png',\n  component: index\n})\n\n"
  },
  {
    "path": "Web/packages/web/src/plugins/storage/cookie.vue",
    "content": "<template>\n  <div style=\"margin-top:20px;\">\n    <InfoCard :infoMap=\"storageMap\" title=\"Cookie\" @refresh=\"updateList\" @clear=\"clear\" @removeItem=\"removeItem\"></InfoCard>\n  </div>\n</template>\n<script>\nimport InfoCard from './info-card'\nimport { clearCookie, removeCookieItem, getCookieMap } from './js/storage'\n\nexport default {\n  components: {\n    InfoCard\n  },\n  data () {\n    return {\n      storageMap: {}\n    }\n  },\n  created() {\n    this.updateList()\n  },\n  methods: {\n    updateList() {\n      this.storageMap = getCookieMap()\n    },\n    removeItem(key) {\n      removeCookieItem(key)\n      this.updateList()\n    },\n    clear() {\n      clearCookie()\n      this.updateList()\n    }\n  },\n}\n</script>\n<style lang=\"\">\n  \n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/storage/index.js",
    "content": "import Storage from './main.vue'\n// import {overrideConsole,restoreConsole} from './js/console'\nimport {getGlobalData, RouterPlugin} from '@dokit/web-core'\n\nexport default new RouterPlugin({\n  name: 'Storage',\n  nameZh: '存储',\n  component: Storage,\n  icon: 'https://pt-starimg.didistatic.com/static/starimg/img/LM74BpA9bS1621926286444.png',\n  onLoad(){},\n  onUnload(){}\n})"
  },
  {
    "path": "Web/packages/web/src/plugins/storage/info-card.vue",
    "content": "<template>\n  <Card :title=\"title\">\n    <template v-slot:extra>\n      <div class=\"info-card-header__opt\">\n        <div class=\"filter-box\" :class=\"keyword ? 'filter-box-actived' : ''\">\n          <span class=\"filter-text\">{{ keyword }}</span>\n          <span class=\"filter opt-icon\" @click=\"openPrompt\"></span>\n        </div>\n        <span class=\"clear-all opt-icon\" @click=\"clearAll\"></span>\n        <span class=\"refresh opt-icon\" @click=\"refresh\"></span>\n      </div>\n    </template>\n    <template v-slot:body>\n      <div class=\"info-card-body\" v-show=\"Object.keys(filteredMap).length\">\n        <DoRow class=\"\" v-for=\"(value, key) in filteredMap\" :key=\"key\">\n          <DoCol :span=\"6\" class=\"info-key\">{{ key }}</DoCol>\n          <DoCol :span=\"16\" class=\"info-value\">: {{ value }}</DoCol>\n          <DoCol :span=\"2\" class=\"info-opt\">\n            <span class=\"info-delete\" @click=\"removeItem(key)\"></span>\n          </DoCol>\n        </DoRow>\n      </div>\n      <div\n        class=\"info-card-empty\"\n        v-show=\"Object.keys(filteredMap).length === 0\"\n      >\n        <span>empty</span>\n      </div>\n    </template>\n  </Card>\n</template>\n<script>\nimport Card from \"@common/Card\";\n\nexport default {\n  components: {\n    Card,\n  },\n  props: {\n    title: {\n      default: \"\",\n    },\n    infoMap: {\n      default: {},\n    },\n  },\n  data() {\n    return {\n      keyword: \"\",\n    };\n  },\n  computed: {\n    filteredMap() {\n      if (this.keyword) {\n        let map = Object.create({});\n        for (const key in this.infoMap) {\n          if (Object.hasOwnProperty.call(this.infoMap, key)) {\n            if (\n              this.infoMap[key].indexOf(this.keyword) > -1 ||\n              key.indexOf(this.keyword) > -1\n            ) {\n              map[key] = this.infoMap[key];\n            }\n          }\n        }\n        return map;\n      } else {\n        return this.infoMap;\n      }\n    },\n  },\n  methods: {\n    removeItem(key) {\n      if (window.confirm(`是否确认清除${key}`)) {\n        this.$emit(\"removeItem\", key);\n      }\n    },\n    openPrompt() {\n      this.keyword = window.prompt(\n        \"请输入过滤关键词\",\n        this.keyword ? this.keyword : \"\"\n      );\n    },\n    clearAll() {\n      if (window.confirm(`将清空所有${this.title}数据，是否确认清空？`)) {\n        this.$emit(\"clear\");\n      }\n    },\n    refresh() {\n      this.$emit(\"refresh\");\n    },\n  },\n};\n</script>\n<style lang=\"less\">\n.info-card-header__opt {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  .opt-icon {\n    display: inline-block;\n    width: 15px;\n    height: 15px;\n    background-size: 15px;\n    background-repeat: no-repeat;\n    margin: 0 10px;\n  }\n  .refresh {\n    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAvFQTFRFAAAAJCQkLCwsLS0tLCwsLCwsLCwsLS0tLCwsLCwsLS0tLCwsLCwsLCwsLCwsLCwsKysrLCwsLCwsLCwsLS0tKysrLCwsLCwsLCwsLCwsKCgoKioqKysrLCwsLCwsLCwsLCwsLS0tLS0tMDAwLCwsKysrLCwsLCwsICAgLS0tKysrLCwsLCwsKysrJiYmLS0tLCwsKysrKioqLS0tKysrLCwsLi4uKioqLCwsLCwsLS0tLi4uLS0tLCwsLS0tKSkpLCwsLCwsLS0tKioqLCwsLCwsLCwsLCwsLi4uKysrLCwsLCwsMzMzLCwsLCwsLCwsLCwsLi4uLS0tLS0tLS0tLCwsLy8vLCwsLCwsLCwsKysrLCwsLS0tLCwsKysrLCwsLCwsLCwsKysrLCwsKysrLS0tLS0tKysrLCwsLS0tKysrLCwsLCwsLCwsLCwsKioqLy8vLCwsLCwsKysrLS0tLCwsLCwsLCwsKysrMDAwLCwsLCwsKCgoLCwsKysrKysrQEBALCwsLS0tLCwsLCwsKysrJycnLCwsLCwsLCwsLS0tLCwsLCwsKysrLS0tLi4uKysrKysrLS0tLCwsLi4uMzMzLS0tLS0tLS0tLCwsLCwsLCwsAAAALCwsLCwsLCwsLCwsLCwsLCwsLCwsKysrLS0tKysrLCwsLS0tKysrLS0tKysrKysrLS0tLCwsKioqLS0tKSkpLCwsJycnLCwsLCwsLCwsKysrLCwsLCwsLi4uKioqLCwsLi4uKysrLCwsLi4uLS0tLCwsLCwsLCwsJCQkLCwsKysrLS0tOTk5LCwsLCwsLi4uKysrLCwsLCwsAAAALCwsKysrLCwsKioqKysrLS0tVVVVLS0tLS0tKysrLS0tKioqLCwsLS0tKSkpLCwsLS0tKysrLCwsLCwsLS0tKysrLS0tLS0tMTExMTExLCwsLCwsLCwsLCwsLS0tKysrLS0tLCwsKysrKysrLS0tKysrKioqLCwsLCws////4ewITwAAAPl0Uk5TAAcuVXuiwcjQ2N/n7/b+8eXLv7KlmYx/USMgYJO54PXHaz4QndnhqghEjsqpWRR9zGUYZ7azVDbwxGY9oNZ4GajoiStMr/qSHCqeaQV67kBWFj+m2WMbreTisClbYof71XlwXFNJT19ufo2su+NLPSbqbUcidKPTdiD06xO+gjsENHehrl4aF8bJKJf3Eo9DJEHlhicPbIjUdVe0AeaVnN2KxYCBSmpSmusRTS9xRTBhJe0Ni/y4ZKTNCzf9OM5oIbfa29EOm5QtCZBdTnzDbwK1vPkxVGUDq4NIMwy6gh+Y9vGnz72rjloaFZHs89yxwpSW3585sQbSvsraFAAAAAFiS0dE+tVtBkoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAk2SURBVHja7Z15XBVVFMfHhyiguBCIKW6kspmKSypqKAKKgQhugLgguOAWLqkpbqGFmrsZ5paSQYiSaQtZAS1WmqatWtmila22aTX/9Z4iMufeee/emXvnzufT/P59nN85X+bNm5m7nJEkS5YsWbJkyZIlS5YsWbJkiaPq2Nzquter7+Hp1aChd6PGTTyb+tzh69fMv/mdoisjV4uWAa1ay6pq09avXeBdoot0DdG+g0yijkFuwaKLVVVIaBgRRLUadbq7s+iSMbJ16UpDUc0SFN5CdOEKdeveg56imqXnPaKrr1Evv95aMW6oT0Qd0QgO9e2ni+KG+t8bKRojZIB+DIcGRg0SiREdwwbDodjBQ0RhxA1lh+GQ131CMOIThrHlsKtHovEcw6kufsRqluQsabfkJNL6CGUbwQXDrpGj1HKOHpNi/zw1bSxDjvQGvDjsGoO/2CfW/ME4ZhzjiQqaMDEmKmNSRGbW5MQpU6dlT28/Y+YsokDP2ZicLWv9wf1sMHJ6uixlju/cefNxsZ1DHliw0DXKIiTwwcW1Pl6Sy4Ijy8vFgVi6bLlzhxUrH6rngiQPljpd8XEoAw5/5xWsWv0wkc0jmXlLnPl0DVT+fb7i06b6OZyeHmsy1lJYBUeva6Pu9aji6rgefKr7u7VBPbP3uo3UdpuWOfmeLqv1h5vBZ8k6OdSvHlu2btPk+Nh29Qvr49xAVHMW7NBxxX1ip5rtBj4gu9TurXZPi9f3D5qmdt7v4QKyWCXbXv1DVYM6qXgHcAB5Ep8qrLluDIciUvD2+5iD7McnKmT1uL0pD5/gKcYgq7BZPA4wwnDoaW9sjiKmIOuwOYrZDrAF4n8VtzEEwd6XPBPOFMOuEuyzc2wuM5CD2LO8lDWHXYcaYTL5sAIZ3R/nzmck6jAuVxQjkAU4Dl6Dgzbcz/wOJiCH8Eebl8q2YPI9ywAkOdZQDkk68hyasLF+kKNtUdsCnhySFKl2M6QL5JjhHPZ/3vPsQRIxHPwnAXaxB3kB5TjCnUOS4liDzEUs5tgM4JCkIrYg85sgFi8awiFJdZmCFCMOxQZxSNJLDEGiEQMPAyeUy9mBoPNRLJ8/oKZk5Pko5M0K5DASX8iRI1SmEB0IMs/p8TI/jt00HHQggUh4Jj+O/VQcdCDIaMAYfhyL6DioQI7D4Fde5QfyGkcQ5BoSxY8jPpYfSCRcXzKL4yqLOEoOGpAKGJvBj4MepJLcuwqEvs5z9R7tV+sNcutSGDuJIwf1yd6e3DkbhL7J92lqEh0Ixe9nOQit4MqhNrasIopHiRAQmvIWZxDpBDlHEIUtvIN7mzeHJI0jxOi4ncY1H0TTT9jSq/PycYUuVfTOuzSeQwDHGgM4uMgNgPC8GHLVSQBCs6DBVCpQctQXXY9WwVNksOiCtOoUAEkXXZBWBQCQTaIL0qpWSo5ZouvRLLAfh+aWwFQKBt+sRfotxcgGQN4TXZBWweu6tiVlJtBpACK6Hs1yV3J4iq5Hs84oQdJE16NZYKk0x4E5OvkHFcgeC/zJA1KVIPeKBrip9bdma8qJd/6AZQbvi0a4obW1KiIc8zwLfrQiRDPcUO2ds0vJQkoACMdZEXIVKkoiO08qAUiWaAiHzilK+oAopgyATBYNISGDwx+uIAmCy08E7ERzWRPRtMJHJjwiB0BNZSRB8014jnwMaqokCco14a8WHEVYTxK0AgR9IprCrghQ06dEUWAlnhmu7GD+ZDFZFFjbZIZ7rQxlSU3IosAaYjPc/e5TltSfLAqsK40RTWFXmrKktmRR55VR3BeVEmiisqTzZFEXlFGxoinsmqAs6QJZVKaWqw9XwWs04WD0ShA2TzSH9Bmo6HOysC9A2FzRHNI0UBHpAjiw7ZxinQEngR2pqaRxM5Vx4ge25igLukgaB+dHhDUsqdZyUA/xDBq813QTDPIlqGcqaSCcQ2SxoV+PfEA9XxFHglndfLEco8F2kg7koeaaZ58KqqH4FTXXyge4sp34FDHZWpSxoBiKU0TM6iA1JYBawmiCBazXUhXcikz1GxoCglOOCuOApVB+O8pBtLgh+a9BJV3pwrP1hbPT2m9AJV3o4pF1v6IOyVZYCO2uuypzHJIkuAW5B61DhTkOyQ5YRndah8hLZjgk8eCuT+7djdoD2T/STgDIdliEH70HsqOn9WjDOYYgDZB7aXBB9lht0GCiT/AuXO6nxQXd9aa3qxWtkpEK+mryQfYh9jQYBOlXdVmbD7ozlF2HRBKNQvKHaHRC9uoONGYz+02VDoTpV2m1QndPtzKOY9dFJPtszWZoL59jhoE0Q3IP1W4W1xBxM2qO9xSSeZiebqwJiF0DY04T9ASRE/T4xaMtr741og93CbqbOkxfx77hiKEhw3VBaFq947aYHpPfcefYiyYdodfThulR/D1njitoSganZjrqyrX3A1wtd1M/MPDFNZSdzpFjBibfeCbOuI7LJ7hxLMVkY3S3ug3Xy7ZTCReMHzHtvGSvHEbuWRhzuQ+ProClHrhU7Ja+GdSnUQrHtpmlWETuUoZ0zvwJv4OazYl+Swb0Mj2A/VrJV5hyqHeXZeUfX4hP4MuYQ7Xfrw+bZcGz8/H2PObK1JoOMjgoST+reP/CgUO9J7bug5LpqeL8Kw8OyUmX8n6bdbhmnVOzvcqJQ5Iuq6WUfyvSaJm5VNWzDzcOp538fx9FP894NLytuiHHLliS83crpATQzVNu/CPFidufXDnwDwu3Rf6aiLEZzl/yw/K+BK+s1k4LkMP2THX1Iroj6YPrOzf5y4gtEtt2yq509dr2eZX44CmLgly/4KZnjgEcEuk7emILYqIOrZ59PSdXys25Hr36UNTfnkSB8lZjMCTOb03azeL5nFT83mMl+xo54i9xe7NYmPHrJ/9JaKi/bqBhCTrfZKJNcdcYcwxl+fYzKh1M0199jWKiRWE4dJjRGyrlAYdFYjgUmKefQs4L1F+Ifh0vvqSLonfxcdEItxRZUaUZo6pC+BtcFSrNLtdAUZ7NY7xSr0Ku0F0k80O1LgDgrxYt248kgig46SZ6MwcBjNP3s8utWwWcMj1EjYJtbqfdzyxMvd3Ro3Hqv2fc67rZzPtaduc6W1JZllhWWXJWdCGWLFmyZMmSJUuWLFmyZOn/ov8AOnjh87MOpuoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDYtMjNUMjE6MjY6MDkrMDg6MDBCOpSGAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA2LTIzVDIxOjI2OjA5KzA4OjAwM2csOgAAAABJRU5ErkJggg==);\n  }\n  .clear-all {\n    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAtBQTFRFAAAAKysrLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsKysrLi4uLi4uKysrLCwsLCwsLS0tMTExKysrLCwsLS0tLCwsKysrLCwsLCwsKioqLCwsKysrLCwsLCwsLCwsLS0tLCwsLCwsLCwsLCwsLS0tLS0tLCwsKysrLCwsLCwsLCwsLCwsLCwsLS0tKysrLCwsLCwsLCwsLCwsLCwsLCwsKysrLCwsLCwsLCwsLCwsLS0tLCwsLCwsLCwsLCwsLS0tKysrLCwsLCwsLCwsLCwsLCwsLCwsLS0tLCwsKysrLCwsLCwsLCwsLCwsLCwsLS0tKysrLCwsLCwsLCwsLS0tLS0tKysrKysrLCwsLCwsLCwsLCwsLCwsLCwsLS0tLCwsLCwsLCwsLCwsKysrLCwsLCwsLS0tKysrLCwsLCwsLCwsKioqLCwsLCwsKysrKysrLCwsLCwsLCwsLS0tJCQkLCwsLCwsLS0tLCwsLS0tLCwsKysrLCwsLS0tLCwsLCwsLCwsLCwsLS0tLS0tLS0tKysrLCwsLCwsLCwsLCwsLCwsKysrJycnLCwsLCwsLS0tLCwsLCwsKysrLCwsLS0tLCwsMzMzKysrLS0tLS0tLCwsLS0tLCwsLCwsLCwsLCwsLS0tKysrLCwsLS0tLi4uKysrLCwsKysrKysrLS0tLCwsLCwsLCwsLCwsLCwsLS0tKioqKysrLCwsKysrKysrLCwsLCwsLi4uLS0tLCwsLS0tLS0tLS0tLCwsKysrKysrLCwsKysrLCwsKCgoKysrLS0tLi4uLCwsLCwsLCwsKysrKysrLi4uLCwsKysrLS0tLi4uAAAAKysrLS0tLS0tKysrLS0tLS0tKysrLCwsKysrLi4uLS0tLCwsKysrLi4uJCQkQEBALy8vKioqMDAwKSkpOTk5KioqMzMzKSkpLi4uLCwsLCws////LfJrEgAAAO50Uk5TACqAosTm++nYx6NtQRYyfMmqVRWH+NR/atu5NvNw0ae+vbv+uv38t/y2tfq0svmxsPevrfas9aup9KjypqTx8O+gn+6e7Z2c7JrrmeqYlpXolJPnkZDlj+WO5Izji4riieHghoXfhN6Cgd3cTDzaLtlY11zW9gfVV37TfdKCe4N60HnPeHfOds11Rsx0iA1zy3I0ynFvyG4KjU9sxo5AxWloZ0LDZkNlksJkRMFjRcBiYWBHoV9IXZc3SbNbSlpLWZqbTVYgTlA4Ub9SU1RUKaU5HAFepWBra3F3HSQ9PrgvCw4EJjAQHwkYDxknrvwwQkkAAAABYktHRO+4sOKhAAAACXBIWXMAAABIAAAASABGyWs+AAAHlklEQVR42t3d+Z/NZRQH8GOGGGRMlkGWoj1SFJMoZE3GkiWJpkikREoxmjRMImWniFC0i0xFVEoS7atKizbt52/IGDL33nPnPss5z7lzP7/e7/k+5/0a7r3f53m+3wsQLJXS0itXOaFqtYzqNWqeGG5Y5tTKrI1lk3WSdkdOqVO3HkanfrZ2V/Zp0BCpNDpZuzHLpGOcNG6i3ZpVmmL8NNNuziJYbk7Rbo/JgXiqdoNMDsTm2i0apUViCJ6m3aRBTjdwHH4b1m4zYc4wciCeqd1ogpxl6EA8W7vVcnOCsQPxHO1my8m5Fg7EltrtMjkQW2k3HCfnWToQW2u3TKaetQPxfO2miVzg4EBso902kwOxrXbjUbnQ0YF4kXbrEWnn7EBsr908kwMxR7v9/3OxlwOxgzbgaBp7OpLl6vcSbwdiR20EkwOxkzYDLmVxIFZXdlzG5EBMV3V0ZnMgdkkRB2JXNcflrA7EbkqO7swOxB4p4kDsqeDoJeBA7B3ccYWIA7FPYMeVQg7EviniQMwN6Kgq6EDsF8zRX9SBOCBFHIgDgziuEncgDgrgqBLAgThY3DEkiANxqLDj6kAOxGGijmuCORCHCzquDehAHJEiDsSRQo7rAjsQ80Qc1wd3INYVcNyg4EAclSIOxNHMjhuVHIhjWB03qTkQx6aIA3Ecm+NmVQdiGpNjvLIDkWdzqr4D8RYGR0P/Nhhyq7djgjahNP1v83RM1BYci+emqKRxIE7ycVTT7r5MKns4btduPiKTnR13aLceGecNEknmwDunuDnu0m48Jnc7OeoTZ8pOU4U4TXVRjqkA4zQhLjsgpxHnyS95QVMyzd4xnThNZulL9+hB2lk7mhJnKTj24r1qkBYcjhnHXx5TUSD3EecoLHuAlsQSMpM4xazIQ0ZXBAjlKIo+aJQKJMPGcT9xgtmxh6lIbK7cHyDq51AH1lWAzDV3PEiUz6MPfSi4o//DXo758Q7OCw0xX4tbQFQvjH94aMkiU8dionhJeQUjgzqMbwagHAnudB4RElLT0FGbqF2aqCigxHTFZxlRuzxx2fBQjsWPmDkyiNoaJoWPBoKscHesNCsdFsRhODv3GFHawPDfJKwK4Fht1srjROkaUwfAUHGH4RsW5Vhr7gAYnByOykTpOhuHtOQJsyaeJErX2zkABgk6Nrg7nrJ1ADyt7XiGKH3W3iEnec5s+OeJ0hdcHAADRRwbzQZ/kSjd5OYAGCDgMLwmpBybXR0A/bQcjYjSl9wd/JItZsMWE6Uv+zgAclkdr5gNmkmUvurnANga3rGNeLCB+2qjgOQ1wxG3x5bu8HcA9GVyvG443ubY0jc4HAB9WBxvmg7XPKZ0J48D4K2Qjg0xpV4bJCLT29thvqmpR3Sp28pvnLzt6dhlPlT0HsV3OB0APUM5dkeVenwv4Ze8azHOnsjSLG4H8W/XOIbzPqXJjqx9jx8C3UI4ouY9Ggo4ALo6Ofb4DCJ0/6KLZK/lGG0iqqVulesi7oB9EeViz4kptnS8bz1C5HvKAimIpeQD+wEKIs/A+rEekXQLx4cO598ZeYrtYhCobuz4yOX0H0edhOtuAA+J4+Oyo/cFCD6qupORw/XLXiGGk3wi6IBFGFDSMaHD490mK5kkPhd1n2JISbNyHTu9zj0sqKSDmAM+G58kEu/Jm70YVJITx8EwmbYmrCRLyhFznSgs+bw9MRzDJG1JpoaUUH8R70nzY8kPJ8mRdNBrCyIS6pPkC84BCsJIqM92z0Wl6BSGkHxJDOK1yEdllryE+ib/FbcDoEhaQl1bsU/SlmS2rIS6bt8k4QCYJykpDucAmC8noWbpHDeJmGShlISaN3XatGOaJTKSbqEdAE0kJNQqicNmMLss55dQ61bWm/PsU4NbQq2JWm6WdMtKXklvLQfAfk4JtQPCahOuTxivfqk9KWtCOQDWckm26joA1vFIconTGG+258l6Dgm1J3B/WAfL34Tab/p1aAfD/xNqB7DhzSi88XzvovZkG90clGQSar+/wc1aMmngLhmcTA6Pz3jqPqWENwFKxvF7F3UPnPLPzzp9Fx6efA6n6xPqPtFvtB0OkpFEwULT0SSz1E6Sl6wOy+t46okD821Gk4zF3Ar1DIh5dqNJxni+i3oqxxzb0ZJAMjrZHYazqdQzeGa7jCYZgxnuscQh32r3HZs5iSTUs8OKXEeTTIL1E+q5dLPcRwst6VSp9LUD1HxJod94cqFW57rnbpny3feDqB8Xm6Hdb/xQK6Zxk8QOehU7Tgr8R5PMDFNHpnaniVKQIg5DSb52lybJTOyYqt2jWfJTxEHvVCsTwY2E3Mkuz8GyP1Ff0usH7d7ssnEZ7fjxoHZntvlpFfHb3/UFt5fIZfecqMfVTU/7Wbsn18wtyplQiug8c4Dhw6+SNr/U+XXbb4e0uyjJ7tUVNgeOK35vK/87k4IZ0vpgqWOHdif+ObJ+tMf/PPqpBXBI9+ecmJLxB0zS7oEnk8v/7ldxkgettFvgSUvPx30kTYrJxYIKmELYpd0CT1bQTzWscOlx+APxT+0mOPJXyUf73/v8T6Sbav8c/daY9m8y/KqeYyY2OzJj8x9fB7UeKfkpPwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wNi0yM1QyMToyNjowOSswODowMEI6lIYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDYtMjNUMjE6MjY6MDkrMDg6MDAzZyw6AAAAAElFTkSuQmCC);\n  }\n  .filter-box {\n    display: inline-flex;\n    justify-content: space-between;\n    align-items: center;\n    .filter-text {\n      font-size: 12px;\n    }\n    .opt-icon {\n      margin: 0 10px 0px 5px;\n    }\n  }\n  .filter-box-actived {\n    border-radius: 10px;\n    padding: 3px 0px 3px 10px;\n    border: 1px solid #d9e1e8;\n  }\n  .filter {\n    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAvdQTFRFAAAALy8vLCwsLS0tLCwsLCwsKysrKysrLS0tJiYmMDAwLCwsLCwsLCwsLS0tLCwsLCwsLCwsKysrKysrLCwsLS0tKioqLCwsLCwsLCwsLCwsKysrLS0tLCwsKysrLCwsLCwsLi4uLCwsKysrKysrLCwsLS0tLCwsLCwsLCwsKysrLS0tKysrLCwsLCwsLCwsLCwsLCwsKysrLS0tLCwsLi4uLCwsLCwsLS0tLCwsLy8vKysrLCwsKysrLS0tLCwsLCwsLCwsKysrKioqLCwsKysrLCwsLCwsLCwsLCwsLCwsLCwsLCwsMzMzAAAALS0tLS0tLS0tMzMzKysrLi4uICAgLCwsLS0tKSkpKioqLS0tLCwsKioqLCwsKysrOTk5LCwsLS0tKCgoLCwsLCwsLCwsLCwsKysrLCwsKioqLS0tLCwsMTExLS0tLCwsKysrLCwsLCwsLCwsLCwsLi4uLCwsLS0tLCwsLS0tKysrLi4uKysrLCwsLCwsLS0tLCwsKysrLi4uKysrLS0tLCwsLCwsLCwsLCwsLS0tKysrLCwsKysrLCwsLCwsLS0tLCwsKysrLCwsAAAAKysrLCwsLi4uJycnLCwsLS0tLS0tKysrKioqMTExLi4uLS0tLCwsHBwcKysrKysrLCwsLCwsLCwsLCwsLCwsLi4uLCwsKysrLS0tLS0tLCwsMDAwLCwsLCwsLCwsLS0tKSkpLCwsLCwsLi4uLS0tJCQkLS0tLCwsLS0tLCwsLS0tLi4uLS0tLCwsLy8vLCwsLCwsLCwsKioqLCwsKysrKSkpLCwsLCwsKioqLCwsLCwsLS0tLS0tVVVVLCwsKysrMzMzJCQkLCwsLS0tLS0tLS0tLCwsLCwsLCwsLCwsKioqJycnKCgoLCwsKioqLCwsLCwsLCwsLCwsKysrLS0tLCwsLCwsLCwsLCwsLS0tKysrLCwsKysrLCwsLCwsMzMzLCwsLS0tLS0tLCwsKioqLCwsLCwsLS0tLCws////cOkf3AAAAPt0Uk5TADFiiK2/n3ZVFBCE99pmUta5KkH11DDhKbWsWTPFjlHoC387avCa8sDVtqYv4JfQozrroEVUXZ1P6hsS+plai6SWZSvzk2+GaJB6sowPAYmPPgWwMgizgiUMt68Yu3wJuvwTvnnEF8J1Hs7pGnLY38k04tIhzWvrSVMsXttpW3OHN1h+5vxt+WFC7U1G8UqnSO8CsfhDDS4/IuU8FTgt3gk1X+zKY1fRJ24kbHHLIMcdgHgZhcEWgwexihH7jhxEkibnlakx3dQfovYGm5irlAORjQoOe71nyMzPQNM2GiacQ9fjruRk5fRcTMbfTv2l3MMUuGB3/jeqSzmNlSUkAAAAAWJLR0T8PA6jfwAAAAlwSFlzAAAASAAAAEgARslrPgAAB1dJREFUeNrtnXmcTlUchw9mLClS1skyjLFnyZ5hypZMM2OIbEnGaJK9ZE3SqJjRYinKUlLZEiFSKYqQVm3I0qpI+6Lef2JmGu/93vXcc8/yzuc8f5/zO99n5t733nPuRkgeRYoWi4pWmOIlSpYi7lxQ+sKQ8lxUxtWj7MWyQ3qj3CXOHpeWl53QKxUcTSpWkp2PwqSyg0gV2eloiLH3uEx2Njqq2opUkx2Njup2HjVkJ6MktqaNSC3ZyWiJKwy7+jlq24jEyw5GSx0bkboFLeqpTP2CmA1sRMKOhg2JqjS6/HzKxjZtmoT915rKDmxDsyvCQpawaRQdvv01lx3ZkhaGc8GWNq1aGfak1rJDW9DGkLDtlTbN2iUY2rWXHdtEB+OPVqJtw6uMDa+WHRzoaIyX0Mm2ZbPOxqZdZEc30BWOItc4tO0Gba+VHT6M7pAtybF1Y2h9nez4BZTGw3qyY/OUGGieKlsgnx7okebSoWcvii1RHHXQ43rXLr37QJcbZEsQ089pKNTXQ6d+/aFTLdkaZAB6DPTULflG6DZIrsZNgyHPzf089hySDj2HyvQolQFp6nrvO+wW6Jspz+PW4ZDlNpreI3A9ZaQsj8qjIMloyr/DGOjfVY7H2HGQ43baCneMhwp3yvBImwApqtHX6NsEanQT7zERl9TL+alSuQJU6SjaYxIePmL81ZmM+1lRsR5T0MP37LviVKg0RaTHXejBMDsaOxpqTRPncTd6TGepdg/++s0Q5XEvenRnqxeXZSw3syVbPa8UQ4/7WCumtTUWvL+GCI8u6PEAe81Zs40ly2fz92iNHoFsBzlzjEXrPsjb4yH0eDiYunh8fSSHq8bceegRx140j+z5xsITFnD0eBSnUTMfC654jZnG2rELuXkMNE2jFgVZviVUf/wJTh6LcBoVuzjYAWZA/XFLuHgsxdO7ZU8GPcQ0HOEpDh5L8ESiAodBlsMYU92XyGiZ9TSMsYKDByHPwCijAvw1ySUbp1H1uHgQUgb/753Ya4bxLB4++nPyIKQ2jPTcygCLF0WPKG4ehKyCsYavDqz0GvRoytGDkLUw2vPrAipcAj14X8DMhPFeWB9I2Q3o0YOzByEvwogZGwMomooem7h7EDIIxtz8EnNJ0zRKzPI/bgbpQxgLmqZRohaat8C4g5OZypmmUeKWNKfjkcvrhRcLtr6MHiIXz7bB2H16+630Cl4de1XI4kYBr8HwvXr6q7Mdp1FzXhfqQcgbECAmxU+VHTiN2slnmuPEmxDhLR81duE0anewp6HeaA8h6G8ejMNp1Nt7JHiYf/73UvZvYZpGtZPiQUhzCFKcqnebfdC93juSPMw3Vuyn6IurGba3vgoBD8rveu5pmkb5vKoWFO9BnPc99luDHrQ7WNB8MAACfeipm2ka5f1fyYutSRDpIw+dJEyj3DnQAEJtc+2CZ2pCplHufIyr52633JmmUZ/IVsin3acQbItjc9M0aq1sgQIWb4ZoGxwam6ZRn8mOH8ZGOBlPd2iLHq1khzew/XOfIgmTZEcH1h30JRLP/boqNasP+RDJOiw7tgUrv6AW2X1EdmhLDlOLTJYdOSgR2Ym1iBbRIiqJVC1Ti4aFDMvQPEWO4sMsroxjvtWOh8gxWo1zsF595CEynlYil6ruhQWLTPTlwfqkHgeR5bQKeUQrJzLUnwjFEz+CRMr6E2G8N4qDyGp/IseVEyHUR5FcvlRPJNuPR6J7XeEipNVsWg3WDYvXuVaj6V9RaTRgf4yVj4gEtIhqaBHV0CKqoUVUQ4uohhZRDS2iGlpENbSIamgR1dAiqqFFXNhxmIKFAdzNzUdk09chKvZ9o6RISnM6jXN8q6LISHoPn6+u4itSk3K7yodxHZuDyBFfHi5va5Uh8p0/kRNahJdIodm0+mXROuSywbWwaBHz8xmetizGB9N5iIwYQ2sRYn8ZLg8RQhJpT1HmMd9MzEek0Jw0SkCLqIYWUQ0tohpaRDW0iGpoEdXQIqqhRVRDi6iGFlENLaIaWsSRpWsbRtMwlP2d9lxEcr4P0bKX9b1pPER+oNYIMV8e4SEyxN/1kZPKibShVciD8aljDiIdaRXyOKiciK8LVl4KixbJ8edxSjmRgf5EqisnYv5SqBeyGN8Bvyus1vgWwYiM9SPC+tKH9YZqUceCECELfhTuQQ5AQZv3HNKJkMniz7XICjApvyoAERmYb4GpfzoiRYZZnKlWMX0PIwJESHWrfQ+/8R4JIuSUlclPxk/ZRoQICVmy7LR1E9lpnfjZWiX9/K4SISLmz8HnczzSREhalI1KZoSJEFIyw9ok63SEiRCy5hdrlV9XRpgI+a10Z7tdJbJEzp7s7Q+5IjujRxYkFRIRQjqsKCQiZO7ISg4e8bLj0XA0db6tCOvSpmB22T6Uk8leXCwTf7f0SCgiOxg9fxyyEGF8CaEcev+5Ez12yvmMDTN7evxlFBH0VXEO/N2wcHic5eSZ/zXO/CM7CyP/Jg6OPZGUyvurW/8B9GbxTqZIPjkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDYtMjNUMjE6MjY6MDkrMDg6MDBCOpSGAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA2LTIzVDIxOjI2OjA5KzA4OjAwM2csOgAAAABJRU5ErkJggg==);\n  }\n}\n.info-card-body {\n  font-size: 18px;\n  .info-key {\n    font-weight: bolder;\n    overflow-x: scroll;\n    margin-right: 5px;\n  }\n  .info-value {\n    overflow-x: scroll;\n  }\n  .info-opt {\n    text-align: center;\n    span {\n      display: inline-block;\n      width: 15px;\n      height: 15px;\n      background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAvFQTFRFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////A6m1bgAAAPl0Uk5TAB1FbIaTn6y5xtLf7Pn4697RxLeqnZCDYjsTCjFYgKfO9XZOJwhCy7p1NAI8tvvqpGctG2vm1ZtXCQNSovLhlEY6itrQghxywr5wLo3piD+e/jNRsKDBS7WXDIX99u/n4NnKw+jwZlTYsoxBJnGWvK81JN1jqe5+BW30LH3Ax1APIGCB1qEHaLMrf/wVKeVcKLF8IzaVjgZa489vDoc3hGEZebRTC9tkvRe4X6gSiUe/MlZ4AWqS7V7i3C+LXUNZaQTxq6bNETDFkXpMGK1ApUjkyZkeFER0zI+jWyUa07tNrnNle/MfIhBPPpwWbppV+iGYSUo9yNd3C18G8gAAAAFiS0dE+tVtBkoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAw7SURBVHja7V17XBXHFb4KiIjiEzWC8tAkGlFE8REVQVHjOxrxASQRQQWNJho1CWjwEW3UKD4qiqaGh2ISfBFTMGLU+GgTq+KLVmtsaJJGLKk1Tart/a9cNpe7Z2Z2d2Z3dufya74/Z2fO+b4Luzs7c84Zm80MNGjo4enVyLuxTxPfps38mrdo2ap1G/+27do/1sEUdyYgILBjp6DgELsimrfyD+3cRTRNdTz+hOeTdkp07faUaLpkdA8L7dGTVoWE8F4RvUXTRtAnsk1fNhFO9Os/4GnR7J0YOGhwlD4VEoZEx4iW4MDQYbFGVEgYPkLwDTPymVHGVUgYPaaPMBljxzXlJcOB8RPEPJSfnTiJpwwHQqKfs1zG5DjeKiRMedZSGe2nmiPDAe9plskIm26eDAfiG1giIyGR9t54/oUXZyTNbJmckpLcJHbIrNl+tEp851igIzWYQkHa3HkRL43EB8+ftuDlV4ZQSFm4yGQZr/prUVgcP+hVDSOPj12y9DUNM36vv2GmjnT1GVXGQo9ltKbClr+pLiWzs2kyRvZSc5zktWIlm71Vq4My1Cy+ZZKOzmuUfb62NvVXemz2eVvtCbhuvRk6Nij6e2dh+4367W4KVZ6vZW3mLmPLVsW7e9uvjRrfnq34abxjJ18dObsUHO1+9zc87M/fM0vBwZvv8dSRq+Blah43F/kFCt81URz/veLJLvbu4/lj2Qo9dpP9vMvLQxui+f3vc5XhQMKIZkRXI/iY/4B8F37IXUcNFnkTnWXzsD2cZLnoJTNkOHCA+LY6aNww6XE1JNIsGTU4dJikZK5Rsy8QjG41/OJQx5F+BKevGLM5m2DS01wZNWhAerx4G7HYBLcXzn/WQEAn0uNFv7m9uLXij6zQYbMdJdybY/Qae5nwHNQ1x9WD9Y1x7xH6TM3DLX1slYwa/HYt7j9PjyFP3E6JhTpqUIozyGG3gn9+9JxsrQ7iXJWZQypm4pjJbw8SPsFYtJjPZmHTcdRCmfUyanACU7KOzUBrdHymEB01tzymZALL8E+xv6ggHTbbh5iSk/SDV6NjM1YKE2LrjZIJp34nL8KWAYVuvp5C2RRTLkgEYMsz1i30E3Ea5RNNNw77HvhMrA6bbQzKqCPNqDB01DDROmy2cQil4EKKQeuQQdNFq6jBmWKEFMWy8BxkiK9bxL+8dxay8tPcl89fjAixfIJFxmcIrXNaA9oiA7aJVuBEf4TYJ+rd0Wf2edH869Dhd5BZv9+rdkc+y1rmi+bvQkPkN96j1vl9pPMA0ezlQN5vkz5X6fsF7NtLNHeAQ8ga5AXlrh1hz7NuFt92ANKLUt49Ru6QP4hmjgJZ4U5U6ncR9sscKJo4ikVw16HvJYV+l6GQFaJ54xgBGRaQe5XDXv6iWRNQCPe0YslzR2RBjOsmJC94QI7ppD5PwT5XRHMmohDumAaT+iCrYQ1FcyajALIcROgCtxAYl48sQ/4srf+bq1Aq351njtgDaDbDlz/hHsJU0XwV0QVGe4Si1xOugeurRfNVRjYgeh29nAMuzxTNVgXb4T0Qg1w+CK7OE81WDXBGeANerIDb0NZGETMCLo80hREY8I35pGiuqvjjeEAWbqPB+eLrormqAz5gQXTHwHBwLVA0VXX8CZD1Vb70gWimWvABdOXf7nCer3tj3ircBHTlET5waXWTaKJaWAboDnZdCHhHfuGWaJ7aSJPzjXW1TwMK/yyapjbgN6ArmwAGa5gXk84NJYCwa9sHZFKEnBFNUxtbgBAvZ3MAaC4SzZIGvnLGdTPgj4AQblG2ZuK2nPFZZ+uXQMgd0SRpkAco/+Xn1nbyxqaiOVIBRhE88XPrOXnjF6I5UmEnEOKcAH8lb2wkmiMdwN3ufGyB97qV0X4GUCTnXCm13QF/JjfZxdXCBTnnXVLbX4EQU9Ka+CMSkE6obesG2urBe90B+AUlpV+CTUYxEX/sqABCrta2gRUvg7H01gE8oVJrmwbLmy6LJkgLEByXV9v0tbxpomiCtAAvEmnDp1Le1FY0QVq0lLOW9p8z5U3s+bEVm9sU80Ak264+iFeUUlrADOUbVh2Jdm64zeIXxMdJc5QkeRNrTl4lG1d1sMy8r8gHShNEUNzkKJuOEjamWmDYgQXrplLaEggnOMKkI4avDrt9A7XrCfJhRUaF3GSjqQ361xhBiIF/rUa8heyndh0vHyZFOIJXy7dMQrYysaTAbGrX5+XDJtY2gQ32m0xC/sZbSGtq16DEQmltk4+8qR2TkBW8hRymdt1KPkyK3AKbi6VMQmzcymtJ+O4utWfwGpe+z0Fc2lo2IZf4CjlA7/m4fJy0RQIePXFsQmxVaUxM1XGVwTGIMZd+AC95UzGjEFvhvGJDpfTqcOsGy9YlzL2SgoRA5mRfViGCANfjpZgsuMfOpTqI+egMSEtLPzBW200Lc6LoBkhLSz8NQNuXoinSASQvOJd+QASUWSWUOAMUwXMmHYGdNw71LqwAiAh01h4IkjfuFk2RCvfA7eAsWQcyvkNEc6QCjD5zRmXBJAXri1fqQDqg7IyRDwStoYY8WISlcsZ1HzFwe7qraJI0yJIzdgVoJsubn08QzVIb68FP74o5Adu6olOMabAEEP57XXs1aHeD3FwtfA8I/6OufSAol3jdgAeLANLG5G8+GJu5RTRPLcBbRF5T6D64Us9CAeXJ7TCspp4FZ1bJL8Ev73oVLjscXIPVb908gDkekP0nuAanW/UqpPwBuBhwHFysR0H+ad3h1WhwtR6lXaB5rnDLph4lwmBla2BdySWi6SoD3up7seswPN5HNF1F9P4BEM3DOiCJoZYUMdQDWF3jGmE9cTToUSyasALmfwdokqoMIAV6HjD7sARI4YdqQpc+MKvHPQOy78ITT3rQiHXLWH8kMz+V2KkLTOGrFE2agE3wDzJKoRtMHrXfF00bxxTIUCkRF3kCZ/1LNG8U+yDBYMWOSLGEH0UTR7AzGfLzUOyJlK9wt4Wh5ZBdrErZM6SgyCh6Jxbgc/guVComUgukxIu9m2jycgRBbkmq9d7hypc9I0w0excikB9ZvWbvHfgZaU8+JJq/E4uQOuNa4TfPILrdJZ1kIHqeQLnWCLSQoJvcJmiZ2HGaI35CRtj/LVqDA2gV0P0UJzugtcnTVolWUfMVgp59RRPVe3cXMsgN4v4XIpS2Uo1CKia5QYEq9NipSY/RjZuCKjFwGAAPYKeZLaAceAg7HJc+rtgEYAcKfE899CF2WIfAXDjsvIjMlfSD30YH2wUUjZdwAGUSNZRluBemxLLTISCwgtiMIdY7R2MGhGRV52A0WFOOAsdjJk5ZrwMrhm2v7M5qAysNrvPQDCPYg1FoQX3eogvf4Eo6WSrj3hWMgF+MHkOEg1T8aaqDc8KJFNw/Q6S2HOdxSylmH0pah9MzcO8Feo0V47aOWVSdrh3umuIbRBEzCeasiLlZ9TXB8RQjFklh79NNryQ/jfQDGiyB2Zxg0tfkmVc3gk/mNAoMrUhW+5v4mj/VleSRPtVHEcSzjWfRfhKwIr8tyZ29PQ/b24imR181Q8ecxURnqcYtOzCGaNweXcFbRtg6oqNJbFmeKlhCVjLDk/FQKXVUK6RnllF+odNggMLh2SHR3NJNLl4mu7CnjOX5ay3bq+DGPpjL6aE5XZXslz7NU4cN20+VIe60wZDnVemPlGyPN2FGNDlWUUrWj/r3tg4dOOenaLg1138rJ+4etCvjP8Me6jB5ZrJ/uIrRRB0mqRBqV8OjwxeZju7O33yjTM1emYl1uR/2UJVif3HuAsoZ5dBcrTTfeO6vKYD7MzT82/eXLqm+pGai6kj6leZaVh6ZHkFyZ6IWBweibgW9tTms9917dYseZzpUBZZ/+2ncmp4Uw/02cD5fnogHwRRUXJKu9fPJXJOVwTImbrsFMmqQ4MnCihlJOk+d1YMTO46ZJSO24JJxfgyoWF5mnDSO4HTrE6MSIpONE4cYlWe5Cgmni4yTd6GHyDDK8uxw4wocuHah2jgbQ9hYUmxcxvQ8t6gHYPDPkpZo0VHpFNhYsjRJn4om/Y+623FZgR63Y9lE3Mo+WWXcrymo6Oh1fTaNhr7FuT+5xW2hhvzyiB3TFyso+OGrc7knt68UzZEBbyyL2bfao8DTq5F348bejS7k/rfk5IpT7vqv9At+wf8D/geF3QB8rZaJCgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wNS0yNVQxNzowNTozMiswODowMFGq0BYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDUtMjVUMTc6MDU6MzIrMDg6MDAg92iqAAAAAElFTkSuQmCC\");\n      background-size: 15px;\n      background-repeat: no-repeat;\n    }\n  }\n}\n.info-card-empty {\n  text-align: center;\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/storage/js/storage.js",
    "content": "export const overrideLocalStorage = function(callback) {\n  const originSetItem = localStorage.setItem.bind(localStorage);\n  localStorage.setItem = function (key, value) {\n    // if (!isStr(key) || !isStr(value)) return;\n    originSetItem(key, value);\n    callback({type: 'setItem'})\n  };\n\n  const originRemoveItem = localStorage.removeItem.bind(localStorage);\n  localStorage.removeItem = function (key) {\n    originRemoveItem(key);\n    callback({type: 'removeItem'})\n  };\n\n  const originClear = localStorage.clear.bind(localStorage);\n  localStorage.clear = function (key) {\n    originClear();\n    callback({type: 'clear'})\n  };\n}\n\nexport const overrideSessionStorage = function(callback) {\n  const originSetItem = sessionStorage.setItem.bind(sessionStorage);\n  sessionStorage.setItem = function (key, value) {\n    // if (!isStr(key) || !isStr(value)) return;\n    originSetItem(key, value);\n    callback({type: 'setItem'})\n  };\n\n  const originRemoveItem = sessionStorage.removeItem.bind(sessionStorage);\n  sessionStorage.removeItem = function (key) {\n    originRemoveItem(key);\n    callback({type: 'removeItem'})\n  };\n\n  const originClear = sessionStorage.clear.bind(sessionStorage);\n  sessionStorage.clear = function (key) {\n    originClear();\n    callback({type: 'clear'})\n  };\n}\n\nexport const clearCookie = function () {\n  let cookieMap = getCookieMap()\n  for (const key in cookieMap) {\n    if (cookieMap.hasOwnProperty.call(cookieMap, key)) {\n      removeCookieItem(key)\n    }\n  }\n}\n\nexport const removeCookieItem = function (key) {\n  document.cookie = encodeURIComponent(key) + \"=; expires=Thu, 01 Jan 1970 00:00:00 GMT\";\n}\n\nexport const getCookieMap = function (params) {\n  const cookieMap = Object.create({})\n  const cookie = document.cookie;\n  if (cookie.trim() !== '') {\n    cookie.split(';').forEach(ele => {\n      ele = ele.split('=');\n      const key = ele.shift().trim();\n      ele = decodeURIComponent(ele.join('='));\n      cookieMap[key] = ele\n    })\n  }\n\n  return cookieMap;\n}\n\n"
  },
  {
    "path": "Web/packages/web/src/plugins/storage/local-storage.vue",
    "content": "<template>\n  <div>\n    <InfoCard :infoMap=\"storageMap\" title=\"localStorage\" @refresh=\"updateList\" @clear=\"clear\" @removeItem=\"removeItem\"></InfoCard>\n  </div>\n</template>\n<script>\nimport InfoCard from './info-card'\nimport {overrideLocalStorage} from './js/storage'\n\nexport default {\n  components: {\n    InfoCard\n  },\n  data () {\n    return {\n      storageMap: {}\n    }\n  },\n  created() {\n    overrideLocalStorage(() => {\n      this.updateList()\n    });\n    this.updateList()\n  },\n  methods: {\n    updateList() {\n      let storageMap = {...window.localStorage}\n      // 有一些属性不需要展示\n      for (const key in storageMap) {\n        if (Object.hasOwnProperty.call(storageMap, key)) {\n          if (~key.indexOf('dokit') || typeof storageMap[key] !== 'string') delete storageMap[key]\n        }\n      }\n      this.storageMap = storageMap\n    },\n    removeItem(key) {\n      window.localStorage.removeItem(key)\n    },\n    clear() {\n      window.localStorage.clear()\n    }\n  },\n}\n</script>\n<style lang=\"\">\n  \n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/storage/main.vue",
    "content": "<template>\n  <div class=\"storage-plugin\">\n    <localStorage></localStorage>\n    <sessionStorage></sessionStorage>\n    <cookie></cookie>\n  </div>\n</template>\n<script>\nimport localStorage from './local-storage';\nimport sessionStorage from './session-storage';\nimport cookie from './cookie';\n\nexport default {\n  components: {\n    localStorage,\n    sessionStorage,\n    cookie\n  }\n}\n</script>\n<style>\n.storage-plugin{\n  padding: 5px;\n}\n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/storage/session-storage.vue",
    "content": "<template>\n  <div style=\"margin-top:20px;\">\n    <InfoCard :infoMap=\"storageMap\" title=\"sessionStorage\" @refresh=\"updateList\" @clear=\"clear\" @removeItem=\"removeItem\"></InfoCard>\n  </div>\n</template>\n<script>\nimport InfoCard from './info-card'\nimport {overrideSessionStorage} from './js/storage'\n\nexport default {\n  components: {\n    InfoCard\n  },\n  data () {\n    return {\n      storageMap: {}\n    }\n  },\n  created() {\n    overrideSessionStorage(() => {\n      this.updateList()\n    });\n    this.updateList()\n  },\n  methods: {\n    updateList() {\n      let storageMap = {...window.sessionStorage}\n      // 有一些属性不需要展示\n      for (const key in storageMap) {\n        if (Object.hasOwnProperty.call(storageMap, key)) {\n          if (~key.indexOf('dokit') || typeof storageMap[key] !== 'string') delete storageMap[key]\n        }\n      }\n      this.storageMap = storageMap\n    },\n    removeItem(key) {\n      window.sessionStorage.removeItem(key)\n    },\n    clear() {\n      window.sessionStorage.clear()\n    }\n  },\n}\n</script>\n<style lang=\"\">\n  \n</style>"
  },
  {
    "path": "Web/packages/web/src/plugins/web-vitals-time/index.js",
    "content": "import WebVitalsTime from './info-box.vue'\nimport {RouterPlugin} from '@dokit/web-core'\n\nexport default new RouterPlugin({\n  nameZh: '性能指标',\n  name: 'webVitalsTime',\n  icon: 'https://pt-starimg.didistatic.com/static/starimg/img/AilMLHvhF91634895636692.png',\n  component: WebVitalsTime\n})\n\n"
  },
  {
    "path": "Web/packages/web/src/plugins/web-vitals-time/info-box.vue",
    "content": "<template>\n  <div class=\"web-vitals-time\">\n    <div class=\"title\">性能指标(web vitals)</div>\n    <div class=\"sub-title\">请在页面加载完成后再获取指标</div>\n    <div class=\"content\">\n      <div><span class=\"item important\">LCP：</span>{{LCP}}</div>\n      <div><span class=\"item\">LOADED：</span>{{loadedTime}}</div>\n<!--      <div><span class=\"item\">FCP：</span>{{FCP}}</div>-->\n      <div><span class=\"item\">CLS：</span>{{CLS}}</div>\n      <div><span class=\"item\">FID：</span>{{FID}}</div>\n      <div><span class=\"item\">TTFB：</span>{{TTFB}}</div>\n    </div>\n\n    <div class=\"desc\">\n      <div class=\"title\">备注</div>\n      <div class=\"container\">\n        <div>1、LOADED指页面完全加载时间（页面load的总耗时）；</div>\n        <div>2、由于IOS系统暂不支持获取LCP指标相关接口，可用LOADED时间作为参考；</div>\n      </div>\n    </div>\n  </div>\n</template>\n<script>\nimport {getFCP, getLCP, getFID, getCLS, getTTFB} from 'web-vitals';\nexport default {\n  data() {\n    return {\n      loadedTime: '--',\n      FCP: '--',\n      CLS: '--',\n      FID: '--',\n      LCP: '--',\n      TTFB: '--'\n    }\n  },\n  mounted() {\n    getFCP(this.handleData);\n    getLCP(this.handleData, true);\n    getCLS(this.handleData, true);\n    getFID(this.handleData);\n    getTTFB(this.handleData);\n\n    // window.addEventListener('load', this.getTiming, false)\n    this.getTiming()\n  },\n  methods: {\n    handleData(detail) {\n      console.log(JSON.parse(JSON.stringify(detail)))\n      const { name, value } = detail\n      this[name] = Math.round(value) + ' ms'\n    },\n    getTiming() {\n      setTimeout(() => {\n        let t = window.performance.timing;\n        const loadedTime = t.loadEventEnd - t.navigationStart\n        this.loadedTime = loadedTime + ' ms'\n        let performanceInfo = [{\n          key: \"Redirect\",\n          desc: \"网页重定向的耗时\",\n          \"value(ms)\": t.redirectEnd - t.redirectStart\n          },\n          {\n            key: \"AppCache\",\n            desc: \"检查本地缓存的耗时\",\n            \"value(ms)\": t.domainLookupStart - t.fetchStart\n          },\n          {\n            key: \"DNS\",\n            desc: \"DNS查询的耗时\",\n            \"value(ms)\": t.domainLookupEnd - t.domainLookupStart\n          },\n          {\n            key: \"TCP\",\n            desc: \"TCP链接的耗时\",\n            \"value(ms)\": t.connectEnd - t.connectStart\n          },\n          {\n            key: \"Waiting(TTFB)\",\n            desc: \"从客户端发起请求到接收响应的时间\",\n            \"value(ms)\": t.responseStart - t.requestStart\n          }, {\n            key: \"Content Download\",\n            desc: \"下载服务端返回数据的时间\",\n            \"value(ms)\": t.responseEnd - t.responseStart\n          },\n          {\n            key: \"HTTP Total Time\",\n            desc: \"http请求总耗时\",\n            \"value(ms)\": t.responseEnd - t.requestStart\n          },\n          {\n            key: \"First Time\",\n            desc: \"首包时间\",\n            \"value(ms)\": t.responseStart - t.domainLookupStart\n          },\n          {\n            key: \"White screen time\",\n            desc: \"白屏时间\",\n            \"value(ms)\": t.responseEnd - t.fetchStart\n          },\n          {\n            key: \"Time to Interactive(TTI)\",\n            desc: \"首次可交互时间\",\n            \"value(ms)\": t.domInteractive - t.fetchStart\n          },\n          {\n            key: \"DOM Parsing\",\n            desc: \"DOM 解析耗时\",\n            \"value(ms)\": t.domInteractive - t.responseEnd\n          },\n          {\n            key: \"DOMContentLoaded\",\n            desc: \"DOM 加载完成的时间\",\n            \"value(ms)\": t.domInteractive - t.navigationStart\n          },\n          {\n            key: \"Loaded\",\n            desc: \"页面load的总耗时\",\n            \"value(ms)\": loadedTime\n          }]\n\n        console.table(performanceInfo);\n      }, 0)\n    }\n  }\n}\n</script>\n<style lang=\"less\" scoped>\n.web-vitals-time {\n  padding: 10px;\n  text-align: center;\n  font-size: 16px;\n  .title {\n    font-weight: bold;\n    font-size: 22px;\n  }\n  .sub-title {\n    font-size: 12px;\n    color: #999999;\n  }\n  .content {\n    text-align: left;\n    margin-top: 20px;\n    padding-left: 5%;\n    >div {\n      font-size: 22px;\n    }\n  }\n  .item {\n    display: inline-block;\n    text-align: right;\n    width: 80px;\n    margin-top: 4px;\n    font-size: 16px;\n  }\n  .important {\n    color: red;\n  }\n\n  .desc {\n    text-align: left;\n    margin-top: 40px;\n    .title {\n      text-align: center;\n      font-size: 18px;\n      font-weight: bold;\n    }\n    .container {\n      margin-top: 4px;\n      font-size: 14px;\n      padding: 10px;\n      background-color: rgba(133,122,122,0.12);\n      border-radius: 6px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "Web/packages/web-independent/README.md",
    "content": "# `Dokit For Web`\n\n\n\n### 基础插件\n#### Console\n基于 Web 的日志组件\n##### TODO\n- [ ] 支持多种类型的日志\n\n- [ ] 支持对象类型展开\n\n"
  },
  {
    "path": "Web/packages/web-independent/package.json",
    "content": "{\n  \"name\": \"@dokit/web-independent\",\n  \"version\": \"0.0.3\",\n  \"description\": \"Dokit Web Main Entry\",\n  \"keywords\": [\n    \"Dokit\"\n  ],\n  \"author\": \"duanlikang <duanlikang@didichuxing.com>\",\n  \"homepage\": \"http://dokit.cn\",\n  \"license\": \"ISC\",\n  \"main\": \"dist/dokit.js\",\n  \"unpkg\": \"dist/dokit.js\",\n  \"jsdelivr\": \"dist/dokit.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"publishConfig\": {\n    \"registry\": \"https://registry.npmjs.org/\",\n    \"access\": \"public\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/didi/DoraemonKit.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/didi/DoraemonKit/issues\"\n  },\n  \"scripts\": {\n    \"build\": \"npx cross-env NODE_ENV=production npx rollup -c\",\n    \"dev\": \"npx rollup -wc\"\n  },\n  \"dependencies\": {\n    \"@dokit/web-core\": \"file:../core\",\n    \"@dokit/web-utils\": \"file:../utils\"\n  },\n  \"gitHead\": \"886ea7c19806526668e5da0179da335e7df9d012\"\n}\n"
  },
  {
    "path": "Web/packages/web-independent/rollup.config.js",
    "content": "import vuePlugin from 'rollup-plugin-vue'\nimport postcssPlugin from 'rollup-plugin-postcss'\nimport resolve from 'rollup-plugin-node-resolve'\nimport replace from 'rollup-plugin-replace'\nimport {terser} from 'rollup-plugin-terser'\nimport rAlias from '@rollup/plugin-alias'\nimport commonjs from 'rollup-plugin-commonjs'\nconst path = require('path')\n\nconst extendPlugins = []\nif(process.env.NODE_ENV === 'production'){\n  extendPlugins.push(terser())\n}\n\nexport default {\n  input: path.join(__dirname, '../web/src/index.js'),\n  output: {\n    name: 'dokit',\n    file: 'dist/dokit.js',\n    format: 'iife',\n  },\n  plugins: [\n    rAlias({\n      entries:{\n        \"@common\": path.join(__dirname, '../web/src/common')\n      }\n    }),\n    vuePlugin(),\n    replace({\n      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),\n      'process.env.VUE_ENV': JSON.stringify('browser'),\n      '__VUE_OPTIONS_API__': JSON.stringify(true),\n      '__VUE_PROD_DEVTOOLS__': JSON.stringify(true)\n    }),\n    postcssPlugin(),\n    resolve({ extensions: ['.vue'] }),\n    commonjs(),\n    ...extendPlugins\n  ]\n}"
  },
  {
    "path": "Web/packages/web-independent/src/index.js",
    "content": "import {Dokit} from '@dokit/web-core-independent'\nimport {Features} from '../../web/src/feature'\n/**\n * 0.0.3 alpha 3\n* TODO 全局注册 Dokit\n*/\nwindow.Dokit = new Dokit({\n  features: Features,\n});\n\n"
  },
  {
    "path": "Web/playground/independent.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Dokit For Web</title>\n  <style>\n    .asd4 .asd {\n      color: rgba(241, 9, 9, 0.3);\n      background: url(https://pt-starimg.didistatic.com/static/starimg/img/UioDkptMhY1635838350336.png);\n      box-shadow: 10px 10px 5px rgb(122, 222, 222, 0.7);\n    }\n\n    body {\n      top: 10px;\n    }\n\n    .asd1 {\n      position: relative;\n    }\n  </style>\n  <script src=\"../packages/web-independent/dist/dokit.js\"></script>\n  <script>\n    Dokit.setProductId('749a0600b5e48dd77cf8ee680be7b1b7')\n    // Dokit.startMultiControl('ws://192.168.0.102:8000/proxy/multicontrol/qwe','host')\n  </script>\n</head>\n\n<body>\n  <input calss=\"inputDemo\" />\n  <div>DoKit For Web</div>\n  <div class=\"asd1\">\n    <div class=\"asd2\">\n      <div class=\"asd3\">\n        <div class=\"asd4\">\n          <span class=\"asd\" id=\"qwe\" style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</span>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div style=\"height:300px;overflow: scroll;\">\n    <a href=\"https://github.com/liriliri/eruda\">GitHub</a>\n    <img src=\"https://pt-starimg.didistatic.com/static/starimg/img/UioDkptMhY1635838350336.png\" />\n    <h2 class=\"asd\" id=\"qwe\" style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <input>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  </div>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n</body>\n<script src=\"https://unpkg.com/axios/dist/axios.min.js\"></script>\n<script>\n  let eventNode = document.getElementsByClassName('asd1')[0]\n  eventNode.onclick = (e) => {\n    fetch('https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7', {\n        // body: JSON.stringify({\n        //   appid: 68852321,\n        // appsecret:'BgGLDVc7',\n        // }),\n        // headers: {\n        //   'Content-Type': 'application/json',\n        // },\n        // method: 'POST',\n      })\n      .then((response) => {\n        let weatherInfo = response.json();\n        return weatherInfo;\n      })\n      .then((info) => {\n        console.log('weatherInfo', info)\n        // alert(JSON.stringify(info))\n      });\n    // function reqListener() {\n    //   console.log('asasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsd', this\n    //     .responseText);\n    // }\n    // var oReq = new XMLHttpRequest();\n    // oReq.addEventListener(\"load\", reqListener);\n    // oReq.open(\"post\", \"https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7\");\n    // oReq.setRequestHeader('Content-Type', 'text/html; charset=utf-8')\n    // oReq.setRequestHeader('Access-Control-Allow-Origin', 'http://localhost:3000/playground')\n    // oReq.setRequestHeader('Access-Control-Allow-Credentials', 'true')\n    // oReq.send(JSON.stringify({\n    //   a: 1\n    // }));\n    // axios({\n    //   url: 'https://daijia.kuaidadi.com/gateway?api=lj.assign.d.regions&apiVersion=1.0.0&appKey=b4f945fe780140d8a0d19d1f2d021db7&appVersion=6.8.9&mobileType=vivo+NEX+S&osType=2&osVersion=10&sign=f2d04df3510670528a067ae52ded2c49&timestamp=1648180651319&token=Mk_G1OHDSNax7QIHN8j_BmFarMeZA_ORR7yKj831WPYkyDvKw0AMAOG7TC2MtCtrbbV__98hD-fRbCAhlfHdg_E0A99KV5I66KAI3UgTeiGLqo5Cr6QK3Y-NJH__CCcShDNZJvPmc4tWprm6C9cdQ1jIlc_r-74sZGhR24QbaeGtVm9RhTuJmelRIDx2QXiSuv0CAAD__w%3D%3D&ttid=-1&userId=182784&userRole=2',\n    // }).then((res) => {\n    //   console.log(res)\n    //   // alert(res)\n    // }).catch((err) => {\n    //   console.log(err)\n    // });\n    // axios({\n    //   url: 'https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7',\n    //   method: \"POST\",\n    //   data: {\n    //     firstName: \"Fred\",\n    //     lastName: \"Flintstone\"\n    //   }\n    // }).then((res) => {\n    //   console.log(res)\n    // }).catch((err) => {\n    //   console.log(err)\n    // });\n  }\n  eventNode.addEventListener('touchstart', (e) => {\n    console.log(e)\n    console.log('----------touchstart--------')\n  }, false)\n  eventNode.addEventListener('touchmove', () => {\n    console.log('----------touchmove--------')\n  }, false)\n  eventNode.addEventListener('touchend', () => {\n    console.log('----------touchend--------')\n  }, false)\n  // setTimeout(() => {\n  //   var a = {\n  //     b: 1,\n  //     c: 3,\n  //   }\n  //   console.log(a);\n\n  // function reqListener() {\n  //   console.log('asasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsd', this\n  //     .responseText);\n  // }\n  // var oReq = new XMLHttpRequest();\n  // oReq.addEventListener(\"load\", reqListener);\n  // oReq.open(\"post\", \"https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7\");\n  // oReq.setRequestHeader('Content-Type', 'text/html; charset=utf-8')\n  // oReq.send(JSON.stringify({\n  //   a: 1\n  // }));\n  // }, 5000);\n</script>\n<script src=\"./playground/liveReload.js\"></script>\n</html>"
  },
  {
    "path": "Web/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Dokit For Web11111111</title>\n  <style>\n    .asd4 .asd {\n      color: rgba(241, 9, 9, 0.3);\n      background: url(https://pt-starimg.didistatic.com/static/starimg/img/UioDkptMhY1635838350336.png);\n      box-shadow: 10px 10px 5px rgb(122, 222, 222, 0.7);\n    }\n\n    body {\n      top: 10px;\n    }\n\n    .asd1 {\n      position: relative;\n    }\n\n    .weatherInfo {\n      font-size: 13px;\n      overflow: hidden;\n    }\n  </style>\n  <script src=\"https://unpkg.com/axios/dist/axios.min.js\"></script>\n  <script src=\"https://unpkg.com/vue@next\"></script>\n  <script src=\"../packages/web/dist/dokit.js\"></script>\n  <script>\n    Dokit.startMultiControl('ws://172.23.165.79:8000/proxy/multicontrol/OYZGTSEL', window.location.href.split(\"?asd=\")[1])\n    // axios({\n    //   url: 'https://thor.weidian.com/skittles/share.getConfig/1.0?wdtoken=4000fcea&_=1649759524263',\n    // }).then((res) => {\n    //   console.log('axios:',res)\n    //   // changeNode.style.color = \"blue\"\n    //   weatherInfoNode.innerText = `接口数据:${JSON.stringify(res)}`\n    // }).catch((err) => {\n    //   console.log(err)\n    // });\n    Dokit.setProductId('749a0600b5e48dd77cf8ee680be7b1b7')\n  </script>\n  <script src=\"./playground/liveReload.js\"></script>\n</head>\n\n<body>\n  <input calss=\"inputDemo\" />\n  <div>DoKit For Web</div>\n  <div class=\"asd1\">\n    <div class=\"asd2\">\n      <div class=\"asd3\">\n        <div class=\"asd4\">\n          <span class=\"asd\" id=\"qwe\" style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</span>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div style=\"height:300px;overflow: scroll;\">\n    <a href=\"https://hummer.didi.cn/home#/\">Hummer</a>\n    <img src=\"https://pt-starimg.didistatic.com/static/starimg/img/UioDkptMhY1635838350336.png\" />\n    <h2 class=\"asd\" id=\"qwe\" style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <input>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  </div>\n  <h2 class=\"weatherInfo\">接口数据：</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <div class=\"asdasd\">zepto测试</div>\n</body>\n<script src=\"https://cdnjs.gtimg.com/cdnjs/libs/zepto/1.1.4/zepto.js\"></script>\n<script>\n  let eventNode = document.getElementsByClassName('asd1')[0]\n  let changeNode = document.getElementById('qwe')\n  let weatherInfoNode = document.getElementsByClassName('weatherInfo')[0]\n  // axios({\n  //     url: 'https://thor.weidian.com/skittles/share.getConfig/1.0?wdtoken=4000fcea&_=1649759524263',\n  //   }).then((res) => {\n  //     console.log('axios:',res)\n  //     // changeNode.style.color = \"blue\"\n  //     weatherInfoNode.innerText = `接口数据:${JSON.stringify(res)}`\n  //   }).catch((err) => {\n  //     console.log(err)\n  //   });\n  eventNode.onclick = (e) => {\n    fetch('https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7', {})\n      .then((response) => {\n        let weatherInfo = response.json();\n        return weatherInfo;\n      })\n      .then((info) => {\n        console.log('weatherInfo', info)\n        changeNode.style.color = \"blue\"\n        weatherInfoNode.innerText = `接口数据:${JSON.stringify(info)}`\n        // axios({\n        //   url: 'https://daijia.kuaidadi.com/gateway?api=lj.assign.d.regions&apiVersion=1.0.0&appKey=b4f945fe780140d8a0d19d1f2d021db7&appVersion=6.8.9&mobileType=vivo+NEX+S&osType=2&osVersion=10&sign=f2d04df3510670528a067ae52ded2c49&timestamp=1648180651319&token=Mk_G1OHDSNax7QIHN8j_BmFarMeZA_ORR7yKj831WPYkyDvKw0AMAOG7TC2MtCtrbbV__98hD-fRbCAhlfHdg_E0A99KV5I66KAI3UgTeiGLqo5Cr6QK3Y-NJH__CCcShDNZJvPmc4tWprm6C9cdQ1jIlc_r-74sZGhR24QbaeGtVm9RhTuJmelRIDx2QXiSuv0CAAD__w%3D%3D&ttid=-1&userId=182784&userRole=2',\n        // }).then((res) => {\n        //   console.log(res)\n        // }).catch((err) => {\n        //   console.log(err)\n        // });\n      });\n    // for (let index = 0; index < 10; index++) {\n    // axios({\n    //   url: 'https://daijia.kuaidadi.com/gateway?api=lj.assign.d.regions&apiVersion=1.0.0&appKey=b4f945fe780140d8a0d19d1f2d021db7&appVersion=6.8.9&mobileType=vivo+NEX+S&osType=2&osVersion=10&sign=f2d04df3510670528a067ae52ded2c49&timestamp=1648180651319&token=Mk_G1OHDSNax7QIHN8j_BmFarMeZA_ORR7yKj831WPYkyDvKw0AMAOG7TC2MtCtrbbV__98hD-fRbCAhlfHdg_E0A99KV5I66KAI3UgTeiGLqo5Cr6QK3Y-NJH__CCcShDNZJvPmc4tWprm6C9cdQ1jIlc_r-74sZGhR24QbaeGtVm9RhTuJmelRIDx2QXiSuv0CAAD__w%3D%3D&ttid=-1&userId=182784&userRole=2',\n    // }).then((res) => {\n    //   console.log(res)\n    //   // alert(res)\n    // }).catch((err) => {\n    //   console.log(err)\n    // });\n    // }\n\n    // function reqListener() {\n    //   console.log('asasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsd', this\n    //     .responseText);\n    // }\n    // var oReq = new XMLHttpRequest();\n    // oReq.addEventListener(\"load\", reqListener);\n    // oReq.open(\"post\", \"https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7\");\n    // oReq.setRequestHeader('Content-Type', 'text/html; charset=utf-8')\n    // oReq.setRequestHeader('Access-Control-Allow-Origin', 'http://localhost:3000/playground')\n    // oReq.setRequestHeader('Access-Control-Allow-Credentials', 'true')\n    // oReq.send(JSON.stringify({\n    //   a: 1\n    // }));\n\n    // axios({\n    //   url: 'https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7',\n    //   method: \"POST\",\n    //   data: {\n    //     firstName: \"Fred\",\n    //     lastName: \"Flintstone\"\n    //   }\n    // }).then((res) => {\n    //   console.log(res)\n    // }).catch((err) => {\n    //   console.log(err)\n    // });\n  }\n  eventNode.addEventListener('touchstart', (e) => {\n    console.log(e)\n    console.log('----------touchstart--------')\n  }, false)\n  eventNode.addEventListener('touchmove', () => {\n    console.log('----------touchmove--------')\n  }, false)\n  eventNode.addEventListener('touchend', () => {\n    console.log('----------touchend--------')\n  }, false)\n  $('.asdasd').on('click', function (event) {\n    console.log('zepto', event);\n  });\n  setTimeout(() => {\n    document.body.appendChild(document.createElement('textarea'))\n  }, 5000)\n  // setTimeout(() => {\n  //   var a = {\n  //     b: 1,\n  //     c: 3,\n  //   }\n  //   console.log(a);\n\n  // function reqListener() {\n  //   console.log('asasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsd', this\n  //     .responseText);\n  // }\n  // var oReq = new XMLHttpRequest();\n  // oReq.addEventListener(\"load\", reqListener);\n  // oReq.open(\"post\", \"https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7\");\n  // oReq.setRequestHeader('Content-Type', 'text/html; charset=utf-8')\n  // oReq.send(JSON.stringify({\n  //   a: 1\n  // }));\n  // }, 5000);\n</script>\n\n</html>"
  },
  {
    "path": "Web/playground/index2.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Dokit For Web</title>\n  <style>\n    .asd4 .asd {\n      color: rgba(241, 9, 9, 0.3);\n      background: url(https://pt-starimg.didistatic.com/static/starimg/img/UioDkptMhY1635838350336.png);\n      box-shadow: 10px 10px 5px rgb(122, 222, 222, 0.7);\n    }\n\n    body {\n      top: 10px;\n    }\n\n    .asd1 {\n      position: relative;\n    }\n  </style>\n</head>\n\n<body>\n  <input calss=\"inputDemo\" />\n  <div>DoKit For Web</div>\n  <div class=\"asd1\">\n    <div class=\"asd2\">\n      <div class=\"asd3\">\n        <div class=\"asd4\">\n          <span class=\"asd\" id=\"qwe\" style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</span>\n        </div>\n      </div>\n    </div>\n  </div>\n  <a href=\"https://github.com/liriliri/eruda\">GitHub</a>\n  <img src=\"https://pt-starimg.didistatic.com/static/starimg/img/UioDkptMhY1635838350336.png\" />\n  <h2 class=\"asd\" id=\"qwe\" style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n</body>\n<script src=\"../packages/web-independent/dist/dokit.js\"></script>\n<script src=\"https://unpkg.com/axios/dist/axios.min.js\"></script>\n<!-- <script>\n  Dokit.setProductId('749a0600b5e48dd77cf8ee680be7b1b7')\n</script> -->\n<script>\n  let eventNode = document.getElementsByClassName('asd1')[0]\n  eventNode.onclick=()=>{\n    fetch('https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7', {\n      // body: JSON.stringify({\n      //   a:1\n      // }),\n      // headers: new Headers({\n      //   'Content-Type': 'text/html; charset=utf-8'\n      // }),\n      // method: 'post',\n    })\n      .then((response) => {\n        let weatherInfo = response.json();\n        return weatherInfo;\n      })\n      .then((info) => {\n        console.log('weatherInfo', info)\n        // alert(JSON.stringify(info))\n      });\n    // axios({\n    //   url: 'https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7',\n    // }).then((res)=>{\n    //   console.log(res)\n    // }).catch((err)=>{\n    //   console.log(err)\n    // });\n  }\n  eventNode.addEventListener('touchstart',(e)=>{\n    console.log(e)\n    console.log('----------touchstart--------')\n  }, false)\n  eventNode.addEventListener('touchmove',()=>{\n    console.log('----------touchmove--------')\n  }, false)\n  eventNode.addEventListener('touchend',()=>{\n    console.log('----------touchend--------')\n  }, false)\n  // setTimeout(() => {\n  //   // fetch('https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7', {\n  //   //   // body: JSON.stringify({\n  //   //   //   a:1\n  //   //   // }),\n  //   //   headers: new Headers({\n  //   //     'Content-Type': 'text/html; charset=utf-8'\n  //   //   }),\n  //   //   method: 'post',\n  //   // })\n  //   //   .then((response) => {\n  //   //     let weatherInfo = response.json();\n  //   //     return weatherInfo;\n  //   //   })\n  //   //   .then((info) => {\n  //   //     console.log('weatherInfo', info)\n  //   //   });\n\n  //   function reqListener () {\n  //     console.log('asasdasd', this.responseText);\n  //   }\n  //   var oReq = new XMLHttpRequest();\n  //   oReq.addEventListener(\"load\", reqListener);\n  //   oReq.open(\"post\", \"https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7\");\n  //   oReq.setRequestHeader('Content-Type', 'text/html; charset=utf-8')\n  //   oReq.send(JSON.stringify({\n  //     a:1\n  //   }));\n  // }, 5000);\n</script>\n<script src=\"./playground/liveReload.js\"></script>\n</html>\n"
  },
  {
    "path": "Web/playground/index3.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Dokit For Web</title>\n  <style>\n    .asd4 .asd {\n      color: rgba(241, 9, 9, 0.3);\n      background: url(https://pt-starimg.didistatic.com/static/starimg/img/UioDkptMhY1635838350336.png);\n      box-shadow: 10px 10px 5px rgb(122, 222, 222, 0.7);\n    }\n\n    body {\n      top: 10px;\n    }\n\n    .asd1 {\n      position: relative;\n    }\n  </style>\n  <script src=\"https://unpkg.com/vue@next\"></script>\n  <script src=\"../packages/web/dist/dokit.js\"></script>\n  <script>\n    Dokit.setProductId('749a0600b5e48dd77cf8ee680be7b1b7')\n    // Dokit.startMultiControl('ws://172.23.165.235:8000/proxy/multicontrol/wwww','host')\n  </script>\n</head>\n\n<body>\n  <input calss=\"inputDemo\" />\n  <div>DoKit For Web</div>\n  <div class=\"asd1\">\n    <div class=\"asd2\">\n      <div class=\"asd3\">\n        <div class=\"asd4\">\n          <span class=\"asd\" id=\"qwe\" style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</span>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div style=\"height:300px;overflow: scroll;\">\n    <a href=\"https://github.com/liriliri/eruda\">GitHub</a>\n    <img src=\"https://pt-starimg.didistatic.com/static/starimg/img/UioDkptMhY1635838350336.png\" />\n    <h2 class=\"asd\" id=\"qwe\" style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <input>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n    <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  </div>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n  <h2 style=\"padding: 10px 20px 30px 40px;\n    margin: 10px 20px 30px 40px;\n    border: 10px solid;\n    border-width:10px 20px 30px 50px;\">Playground</h2>\n</body>\n<script src=\"https://unpkg.com/axios/dist/axios.min.js\"></script>\n<script>\n  let eventNode = document.getElementsByClassName('asd1')[0]\n  eventNode.onclick = (e) => {\n    // fetch('https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7', {\n    //     // body: JSON.stringify({\n    //     //   appid: 68852321,\n    //     // appsecret:'BgGLDVc7',\n    //     // }),\n    //     // headers: {\n    //     //   'Content-Type': 'application/json',\n    //     // },\n    //     // method: 'POST',\n    //   })\n    //   .then((response) => {\n    //     let weatherInfo = response.json();\n    //     return weatherInfo;\n    //   })\n    //   .then((info) => {\n    //     console.log('weatherInfo', info)\n    //     alert(JSON.stringify(info))\n    //   });\n    // function reqListener() {\n    //   console.log('asasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsd', this\n    //     .responseText);\n    // }\n    // var oReq = new XMLHttpRequest();\n    // oReq.addEventListener(\"load\", reqListener);\n    // oReq.open(\"post\", \"https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7\");\n    // oReq.setRequestHeader('Content-Type', 'text/html; charset=utf-8')\n    // oReq.setRequestHeader('Access-Control-Allow-Origin', 'http://localhost:3000/playground')\n    // oReq.setRequestHeader('Access-Control-Allow-Credentials', 'true')\n    // oReq.send(JSON.stringify({\n    //   a: 1\n    // }));\n    axios({\n      url: 'https://daijia.kuaidadi.com/gateway?api=lj.assign.d.regions&apiVersion=1.0.0&appKey=b4f945fe780140d8a0d19d1f2d021db7&appVersion=6.8.9&mobileType=vivo+NEX+S&osType=2&osVersion=10&sign=f2d04df3510670528a067ae52ded2c49&timestamp=1648180651319&token=Mk_G1OHDSNax7QIHN8j_BmFarMeZA_ORR7yKj831WPYkyDvKw0AMAOG7TC2MtCtrbbV__98hD-fRbCAhlfHdg_E0A99KV5I66KAI3UgTeiGLqo5Cr6QK3Y-NJH__CCcShDNZJvPmc4tWprm6C9cdQ1jIlc_r-74sZGhR24QbaeGtVm9RhTuJmelRIDx2QXiSuv0CAAD__w%3D%3D&ttid=-1&userId=182784&userRole=2',\n    }).then((res) => {\n      console.log(res)\n      // alert(res)\n    }).catch((err) => {\n      console.log(err)\n    });\n    // axios({\n    //   url: 'https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7',\n    //   method: \"POST\",\n    //   data: {\n    //     firstName: \"Fred\",\n    //     lastName: \"Flintstone\"\n    //   }\n    // }).then((res) => {\n    //   console.log(res)\n    // }).catch((err) => {\n    //   console.log(err)\n    // });\n  }\n  eventNode.addEventListener('touchstart', (e) => {\n    console.log(e)\n    console.log('----------touchstart--------')\n  }, false)\n  eventNode.addEventListener('touchmove', () => {\n    console.log('----------touchmove--------')\n  }, false)\n  eventNode.addEventListener('touchend', () => {\n    console.log('----------touchend--------')\n  }, false)\n  // setTimeout(() => {\n  //   var a = {\n  //     b: 1,\n  //     c: 3,\n  //   }\n  //   console.log(a);\n\n  // function reqListener() {\n  //   console.log('asasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsdasasdasdasdasdasdasdasdqsd', this\n  //     .responseText);\n  // }\n  // var oReq = new XMLHttpRequest();\n  // oReq.addEventListener(\"load\", reqListener);\n  // oReq.open(\"post\", \"https://www.tianqiapi.com/free/week?appid=68852321&appsecret=BgGLDVc7\");\n  // oReq.setRequestHeader('Content-Type', 'text/html; charset=utf-8')\n  // oReq.send(JSON.stringify({\n  //   a: 1\n  // }));\n  // }, 5000);\n</script>\n<script src=\"./playground/liveReload.js\"></script>\n</html>"
  },
  {
    "path": "Web/playground/liveReload.js",
    "content": "// LiveReload client script\n(function() {\n  var uri, host, protocol, base;\n  uri      = document.location.href;\n  protocol = document.location.protocol;\n  host     = document.location.host;\n\n  var WebSocket = window.WebSocket || window.MozWebSocket;\n\n  // Create WebSocket\n  var socket = new WebSocket('ws' + (protocol == 'https:' ? 's' : '') + '://' + document.location.hostname + ':35729' + '/livereload');\n\n  socket.onopen = function(evt) {\n    // socket.send('hello from client');\n  };\n\n  socket.onerror = function(error) {\n    console.log('WebSocket error', error);\n  };\n\n  socket.onmessage = function(evt) {\n      document.location.reload();\n  };\n})();\n"
  },
  {
    "path": "Web/scripts/dev-playground.js",
    "content": "const serveHandler = require('serve-handler');\nconst open = require('open');\nconst http = require('http');\nconst path = require('path');\nconst livereload = require('livereload');\n// Create LiveReload server\nconst liveReloadServer = livereload.createServer({ port: 35729});\n// Watch the 'public' directory for changes\nliveReloadServer.watch([path.join(process.cwd(), 'packages/web/dist'), path.join(process.cwd(), 'packages/web-independent/dist')]);\n\n// Create HTTP server\nconst server = http.createServer((request, response) => {\n  return serveHandler(request, response);\n});\n\n// Start HTTP server\nserver.listen(3000, () => {\n  console.log('Server listening on port 3000');\n  open('http://localhost:3000/playground');\n});\n"
  },
  {
    "path": "iOS/.gitignore",
    "content": "# Swift.gitignore\n# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n## User settings\nxcuserdata/\n\n## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)\n*.xcscmblueprint\n*.xccheckout\n\n## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)\nbuild/\nDerivedData/\n*.moved-aside\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\n\n## Obj-C/Swift specific\n*.hmap\n\n## App packaging\n*.ipa\n*.dSYM.zip\n*.dSYM\n\n## Playgrounds\ntimeline.xctimeline\nplayground.xcworkspace\n\n# Swift Package Manager\n#\n# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.\n# Packages/\n# Package.pins\n# Package.resolved\n# *.xcodeproj\n#\n# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata\n# hence it is not needed unless you have added a package configuration file to your project\n# .swiftpm\n\n.build/\n\n# CocoaPods\n#\n# We recommend against adding the Pods directory to your .gitignore. However\n# you should judge for yourself, the pros and cons are mentioned at:\n# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control\n#\nPods/\n#\n# Add this line if you want to avoid checking in source code from the Xcode workspace\n*.xcworkspace\n\n# Carthage\n#\n# Add this line if you want to avoid checking in source code from Carthage dependencies.\n# Carthage/Checkouts\n\nCarthage/Build/\n\n# Accio dependency management\nDependencies/\n.accio/\n\n# fastlane\n#\n# It is recommended to not store the screenshots in the git repo.\n# Instead, use fastlane to re-generate the screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/#source-control\n\nfastlane/report.xml\nfastlane/Preview.html\nfastlane/screenshots/**/*.png\nfastlane/test_output\n\n# Code Injection\n#\n# After new code Injection tools there's a generated folder /iOSInjectionProject\n# https://github.com/johnno1962/injectionforxcode\n\niOSInjectionProject/\n\n.idea\n"
  },
  {
    "path": "iOS/Demo/AppDelegate.swift",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport UIKit\nimport DoraemonKit\n\n@main\nprivate class AppDelegate: UIResponder, UIApplicationDelegate {\n\n    var window: UIWindow?\n\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n        // Override point for customization after application launch.\n        DoraemonManager.shareInstance().install()\n\n        return true\n    }\n\n}\n\n"
  },
  {
    "path": "iOS/Demo/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"76x76\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"76x76\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"83.5x83.5\"\n    },\n    {\n      \"idiom\" : \"ios-marketing\",\n      \"scale\" : \"1x\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/Demo/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/Demo/Base.lproj/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"13122.16\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" launchScreen=\"YES\" useTraitCollections=\"YES\" useSafeAreas=\"YES\" colorMatched=\"YES\" initialViewController=\"01J-lp-oVM\">\n    <dependencies>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"13104.12\"/>\n        <capability name=\"Safe area layout guides\" minToolsVersion=\"9.0\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"EHf-IW-A2E\">\n            <objects>\n                <viewController id=\"01J-lp-oVM\" sceneMemberID=\"viewController\">\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"Ze5-6b-2t3\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"375\" height=\"667\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <color key=\"backgroundColor\" xcode11CocoaTouchSystemColor=\"systemBackgroundColor\" cocoaTouchSystemColor=\"whiteColor\"/>\n                        <viewLayoutGuide key=\"safeArea\" id=\"6Tk-OE-BBY\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"iYj-Kq-Ea1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"53\" y=\"375\"/>\n        </scene>\n    </scenes>\n</document>\n"
  },
  {
    "path": "iOS/Demo/Base.lproj/Main.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"20037\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" useTraitCollections=\"YES\" useSafeAreas=\"YES\" colorMatched=\"YES\" initialViewController=\"BYZ-38-t0r\">\n    <device id=\"retina6_1\" orientation=\"portrait\" appearance=\"light\"/>\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"20020\"/>\n        <capability name=\"Safe area layout guides\" minToolsVersion=\"9.0\"/>\n        <capability name=\"System colors in document resources\" minToolsVersion=\"11.0\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"tne-QT-ifu\">\n            <objects>\n                <viewController id=\"BYZ-38-t0r\" customClass=\"ViewController\" customModule=\"Demo\" customModuleProvider=\"target\" sceneMemberID=\"viewController\">\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"8bC-Xf-vdC\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"414\" height=\"896\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <viewLayoutGuide key=\"safeArea\" id=\"6Tk-OE-BBY\"/>\n                        <color key=\"backgroundColor\" systemColor=\"systemBackgroundColor\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"dkx-z0-nzr\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"137.68115942028987\" y=\"79.6875\"/>\n        </scene>\n    </scenes>\n    <resources>\n        <systemColor name=\"systemBackgroundColor\">\n            <color white=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"genericGamma22GrayColorSpace\"/>\n        </systemColor>\n    </resources>\n</document>\n"
  },
  {
    "path": "iOS/Demo/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict/>\n</plist>\n"
  },
  {
    "path": "iOS/Demo/ViewController.swift",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport UIKit\n\nclass ViewController: UIViewController {\n    \n    override func viewDidLoad() {\n        super.viewDidLoad()\n        // Do any additional setup after loading the view.\n    }\n\n}\n"
  },
  {
    "path": "iOS/Demo.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 55;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t18C7CAA526FB5F8D00A47F7C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C7CAA426FB5F8D00A47F7C /* AppDelegate.swift */; };\n\t\t18C7CAA926FB5F8D00A47F7C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C7CAA826FB5F8D00A47F7C /* ViewController.swift */; };\n\t\t18C7CAAC26FB5F8D00A47F7C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 18C7CAAA26FB5F8D00A47F7C /* Main.storyboard */; };\n\t\t18C7CAAE26FB5F8F00A47F7C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 18C7CAAD26FB5F8F00A47F7C /* Assets.xcassets */; };\n\t\t18C7CAB126FB5F8F00A47F7C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 18C7CAAF26FB5F8F00A47F7C /* LaunchScreen.storyboard */; };\n\t\tFF6F1A3FA338925D5551ADB6 /* libPods-Demo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D44C18175B75199F625E8EC7 /* libPods-Demo.a */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t175B450197F10CD038689527 /* Pods-Demo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-Demo.release.xcconfig\"; path = \"Target Support Files/Pods-Demo/Pods-Demo.release.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t18C7CAA126FB5F8D00A47F7C /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t18C7CAA426FB5F8D00A47F7C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = \"<group>\"; };\n\t\t18C7CAA826FB5F8D00A47F7C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = \"<group>\"; };\n\t\t18C7CAAB26FB5F8D00A47F7C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = \"<group>\"; };\n\t\t18C7CAAD26FB5F8F00A47F7C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t18C7CAB026FB5F8F00A47F7C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\t18C7CAB226FB5F8F00A47F7C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\tD44C18175B75199F625E8EC7 /* libPods-Demo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = \"libPods-Demo.a\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tEC6833A07F0818E63D44A2B9 /* Pods-Demo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-Demo.debug.xcconfig\"; path = \"Target Support Files/Pods-Demo/Pods-Demo.debug.xcconfig\"; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t18C7CA9E26FB5F8D00A47F7C /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tFF6F1A3FA338925D5551ADB6 /* libPods-Demo.a in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t18C7CA9826FB5F8D00A47F7C = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t18C7CAA326FB5F8D00A47F7C /* Demo */,\n\t\t\t\t18C7CAA226FB5F8D00A47F7C /* Products */,\n\t\t\t\t7CE2F3CE1303DA9217547990 /* Pods */,\n\t\t\t\t617A36452BCE105D0FCFECEA /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t18C7CAA226FB5F8D00A47F7C /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t18C7CAA126FB5F8D00A47F7C /* Demo.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t18C7CAA326FB5F8D00A47F7C /* Demo */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t18C7CAA426FB5F8D00A47F7C /* AppDelegate.swift */,\n\t\t\t\t18C7CAA826FB5F8D00A47F7C /* ViewController.swift */,\n\t\t\t\t18C7CAAA26FB5F8D00A47F7C /* Main.storyboard */,\n\t\t\t\t18C7CAAD26FB5F8F00A47F7C /* Assets.xcassets */,\n\t\t\t\t18C7CAAF26FB5F8F00A47F7C /* LaunchScreen.storyboard */,\n\t\t\t\t18C7CAB226FB5F8F00A47F7C /* Info.plist */,\n\t\t\t);\n\t\t\tpath = Demo;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t617A36452BCE105D0FCFECEA /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tD44C18175B75199F625E8EC7 /* libPods-Demo.a */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t7CE2F3CE1303DA9217547990 /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tEC6833A07F0818E63D44A2B9 /* Pods-Demo.debug.xcconfig */,\n\t\t\t\t175B450197F10CD038689527 /* Pods-Demo.release.xcconfig */,\n\t\t\t);\n\t\t\tpath = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t18C7CAA026FB5F8D00A47F7C /* Demo */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 18C7CAB526FB5F8F00A47F7C /* Build configuration list for PBXNativeTarget \"Demo\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t4D41A223381189A234AE62A7 /* [CP] Check Pods Manifest.lock */,\n\t\t\t\t18C7CA9D26FB5F8D00A47F7C /* Sources */,\n\t\t\t\t18C7CA9E26FB5F8D00A47F7C /* Frameworks */,\n\t\t\t\t18C7CA9F26FB5F8D00A47F7C /* Resources */,\n\t\t\t\t3F213C27944A91E0968B026B /* [CP] Copy Pods Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = Demo;\n\t\t\tproductName = Demo;\n\t\t\tproductReference = 18C7CAA126FB5F8D00A47F7C /* Demo.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t18C7CA9926FB5F8D00A47F7C /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = 1;\n\t\t\t\tLastSwiftUpdateCheck = 1300;\n\t\t\t\tLastUpgradeCheck = 1300;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t18C7CAA026FB5F8D00A47F7C = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 13.0;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 18C7CA9C26FB5F8D00A47F7C /* Build configuration list for PBXProject \"Demo\" */;\n\t\t\tcompatibilityVersion = \"Xcode 13.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 18C7CA9826FB5F8D00A47F7C;\n\t\t\tproductRefGroup = 18C7CAA226FB5F8D00A47F7C /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t18C7CAA026FB5F8D00A47F7C /* Demo */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t18C7CA9F26FB5F8D00A47F7C /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t18C7CAB126FB5F8F00A47F7C /* LaunchScreen.storyboard in Resources */,\n\t\t\t\t18C7CAAE26FB5F8F00A47F7C /* Assets.xcassets in Resources */,\n\t\t\t\t18C7CAAC26FB5F8D00A47F7C /* Main.storyboard in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t3F213C27944A91E0968B026B /* [CP] Copy Pods Resources */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-Demo/Pods-Demo-resources-${CONFIGURATION}-input-files.xcfilelist\",\n\t\t\t);\n\t\t\tname = \"[CP] Copy Pods Resources\";\n\t\t\toutputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-Demo/Pods-Demo-resources-${CONFIGURATION}-output-files.xcfilelist\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-Demo/Pods-Demo-resources.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t4D41A223381189A234AE62A7 /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-Demo-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"diff \\\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\\\" \\\"${PODS_ROOT}/Manifest.lock\\\" > /dev/null\\nif [ $? != 0 ] ; then\\n    # print error to STDERR\\n    echo \\\"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\\\" >&2\\n    exit 1\\nfi\\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\\necho \\\"SUCCESS\\\" > \\\"${SCRIPT_OUTPUT_FILE_0}\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t18C7CA9D26FB5F8D00A47F7C /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t18C7CAA926FB5F8D00A47F7C /* ViewController.swift in Sources */,\n\t\t\t\t18C7CAA526FB5F8D00A47F7C /* AppDelegate.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXVariantGroup section */\n\t\t18C7CAAA26FB5F8D00A47F7C /* Main.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t18C7CAAB26FB5F8D00A47F7C /* Base */,\n\t\t\t);\n\t\t\tname = Main.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t18C7CAAF26FB5F8F00A47F7C /* LaunchScreen.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t18C7CAB026FB5F8F00A47F7C /* Base */,\n\t\t\t);\n\t\t\tname = LaunchScreen.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXVariantGroup section */\n\n/* Begin XCBuildConfiguration section */\n\t\t18C7CAB326FB5F8F00A47F7C /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 9.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t18C7CAB426FB5F8F00A47F7C /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 9.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t18C7CAB626FB5F8F00A47F7C /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = EC6833A07F0818E63D44A2B9 /* Pods-Demo.debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = Demo/Info.plist;\n\t\t\t\tINFOPLIST_KEY_NSCameraUsageDescription = \"扫码需要开启您的摄像头权限，用于识别\";\n\t\t\t\tINFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;\n\t\t\t\tINFOPLIST_KEY_UIMainStoryboardFile = Main;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 9.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.didi.dokit;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t18C7CAB726FB5F8F00A47F7C /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 175B450197F10CD038689527 /* Pods-Demo.release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = Demo/Info.plist;\n\t\t\t\tINFOPLIST_KEY_NSCameraUsageDescription = \"扫码需要开启您的摄像头权限，用于识别\";\n\t\t\t\tINFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;\n\t\t\t\tINFOPLIST_KEY_UIMainStoryboardFile = Main;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 9.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.didi.dokit;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t18C7CA9C26FB5F8D00A47F7C /* Build configuration list for PBXProject \"Demo\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t18C7CAB326FB5F8F00A47F7C /* Debug */,\n\t\t\t\t18C7CAB426FB5F8F00A47F7C /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t18C7CAB526FB5F8F00A47F7C /* Build configuration list for PBXNativeTarget \"Demo\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t18C7CAB626FB5F8F00A47F7C /* Debug */,\n\t\t\t\t18C7CAB726FB5F8F00A47F7C /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 18C7CA9926FB5F8D00A47F7C /* Project object */;\n}\n"
  },
  {
    "path": "iOS/Demo.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1310\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"18C7CAA026FB5F8D00A47F7C\"\n               BuildableName = \"Demo.app\"\n               BlueprintName = \"Demo\"\n               ReferencedContainer = \"container:Demo.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"18C7CAA026FB5F8D00A47F7C\"\n            BuildableName = \"Demo.app\"\n            BlueprintName = \"Demo\"\n            ReferencedContainer = \"container:Demo.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"18C7CAA026FB5F8D00A47F7C\"\n            BuildableName = \"Demo.app\"\n            BlueprintName = \"Demo\"\n            ReferencedContainer = \"container:Demo.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "iOS/DoKit/Assets/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoKit/Assets/Assets.xcassets/dokit_logo.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"dokit_logo@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoKit/Assets/DKTrayViewController.xib",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" version=\"3.0\" toolsVersion=\"20037\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" useTraitCollections=\"YES\" useSafeAreas=\"YES\" colorMatched=\"YES\">\n    <device id=\"retina6_1\" orientation=\"portrait\" appearance=\"light\"/>\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"20020\"/>\n        <capability name=\"Safe area layout guides\" minToolsVersion=\"9.0\"/>\n        <capability name=\"System colors in document resources\" minToolsVersion=\"11.0\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <objects>\n        <placeholder placeholderIdentifier=\"IBFilesOwner\" id=\"-1\" userLabel=\"File's Owner\" customClass=\"DKTrayViewController\">\n            <connections>\n                <outlet property=\"view\" destination=\"iN0-l3-epB\" id=\"V21-eE-Ekr\"/>\n            </connections>\n        </placeholder>\n        <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"-2\" customClass=\"UIResponder\"/>\n        <view contentMode=\"scaleToFill\" id=\"iN0-l3-epB\">\n            <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"414\" height=\"896\"/>\n            <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n            <subviews>\n                <button opaque=\"NO\" contentMode=\"scaleToFill\" fixedFrame=\"YES\" contentHorizontalAlignment=\"center\" contentVerticalAlignment=\"center\" buttonType=\"system\" lineBreakMode=\"middleTruncation\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"EDq-pO-2FR\">\n                    <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"58\" height=\"58\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMaxY=\"YES\"/>\n                    <state key=\"normal\" title=\"Button\"/>\n                    <buttonConfiguration key=\"configuration\" style=\"plain\" image=\"dokit_logo\" title=\"\"/>\n                    <connections>\n                        <action selector=\"buttonHandler\" destination=\"-1\" eventType=\"touchUpInside\" id=\"cFG-UF-Q7R\"/>\n                    </connections>\n                </button>\n            </subviews>\n            <viewLayoutGuide key=\"safeArea\" id=\"vUN-kp-3ea\"/>\n            <color key=\"backgroundColor\" systemColor=\"systemBackgroundColor\"/>\n            <point key=\"canvasLocation\" x=\"139\" y=\"63\"/>\n        </view>\n    </objects>\n    <resources>\n        <image name=\"dokit_logo\" width=\"58\" height=\"58\"/>\n        <systemColor name=\"systemBackgroundColor\">\n            <color white=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"genericGamma22GrayColorSpace\"/>\n        </systemColor>\n    </resources>\n</document>\n"
  },
  {
    "path": "iOS/DoKit/Classes/CFoundation/common.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef dokit_common\n#define dokit_common\n\n#define DOKIT_EXPORT __attribute__((visibility(\"default\")))\n\n#endif\n"
  },
  {
    "path": "iOS/DoKit/Classes/CFoundation/hook.c",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if !defined(DEBUG) && !defined(NDEBUG)\n#define NDEBUG 1\n#endif\n\n#include \"hook.h\"\n#include <sys/queue.h>\n#include <stdlib.h>\n#include <string.h>\n#include <mach-o/dyld.h>\n#include <mach-o/nlist.h>\n#include <assert.h>\n#include <mach/vm_types.h>\n#include <mach/vm_map.h>\n#include <mach/mach_init.h>\n\nstatic const char *const SEG_DATA_CONST = \"__DATA_CONST\";\n\n#ifndef NDEBUG\nstatic char *const VM_PROTECT_ERROR = \"vm_protect() error.\";\n#endif\n\n#ifdef __LP64__\ntypedef struct mach_header_64 mach_header_t;\ntypedef struct segment_command_64 segment_command_t;\ntypedef struct section_64 section_t;\ntypedef struct nlist_64 nlist_t;\n#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64\n#else\ntypedef struct mach_header mach_header_t;\ntypedef struct segment_command segment_command_t;\ntypedef struct section section_t;\ntypedef struct nlist nlist_t;\n#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT\n#endif\n\nstruct RebindingEntry {\n    SLIST_ENTRY(RebindingEntry) node;\n    size_t length;\n    // [] is correct?\n    // or should we use [0]?\n    struct DKRebinding rebinding[];\n};\n\nstatic SLIST_HEAD(, RebindingEntry) rebindingEntryHead = SLIST_HEAD_INITIALIZER();\n\nstatic bool appendRebinding(const struct DKRebinding rebinding[], size_t length) {\n    if (!rebinding || !length) {\n        assert(false && \"appendRebinding() parameter must be non-null.\");\n\n        return true;\n    }\n    struct RebindingEntry *rebindingEntry = malloc(sizeof(struct RebindingEntry) + length * sizeof(struct DKRebinding));\n    if (!rebindingEntry) {\n        return false;\n    }\n    rebindingEntry->length = length;\n    memmove(rebindingEntry->rebinding, rebinding, length * sizeof(struct DKRebinding));\n    SLIST_INSERT_HEAD(&rebindingEntryHead, rebindingEntry, node);\n\n    return true;\n}\n\nstatic void performRebindingWithSection(const section_t *section, uintptr_t slide, const nlist_t *symbolTable, const char *stringTable, const uint32_t *indirectSymbolTable) {\n    // section->size could be zero?\n    if (!section || !symbolTable || !stringTable || !indirectSymbolTable || !section->size) {\n        assert(false && \"performRebindingWithSection() parameter error.\");\n\n        return;\n    }\n    uint32_t *sectionIndirectSymbolTable = (uint32_t *) (indirectSymbolTable + section->reserved1);\n    // section->addr is vm address\n    uintptr_t *got = (uintptr_t *) (slide + section->addr);\n\n    // Get got located vm region basic info\n    vm_address_t vmAddress = (vm_address_t) got;\n    vm_size_t vmSize = 0;\n    memory_object_t memoryObject;\n#ifdef __LP64__\n    mach_msg_type_number_t machMsgTypeNumber = VM_REGION_BASIC_INFO_COUNT_64;\n    vm_region_basic_info_data_64_t vmRegionBasicInfoData;\n    kern_return_t kernReturn = vm_region_64(mach_task_self(), &vmAddress, &vmSize, VM_REGION_BASIC_INFO_64, (vm_region_info_t) &vmRegionBasicInfoData, &machMsgTypeNumber, &memoryObject);\n#else\n    mach_msg_type_number_t machMsgTypeNumber = VM_REGION_BASIC_INFO_COUNT;\n    vm_region_basic_info_data_t vmRegionBasicInfoData;\n    kern_return_t kernReturn = vm_region(mach_task_self(), &vmAddress, &vmSize, VM_REGION_BASIC_INFO, (vm_region_info_t) &vmRegionBasicInfoData, &machMsgTypeNumber, &memoryObject);\n#endif\n    assert(vmAddress <= (vm_address_t) got && vmSize >= section->size && \"vmAddress <= got < got + size <= vmAddress + vmSize.\");\n    if (kernReturn != KERN_SUCCESS) {\n        assert(false && \"vm_region_64() error.\");\n\n        return;\n    }\n    vm_prot_t newProtection = VM_PROT_NONE;\n    if (vmRegionBasicInfoData.max_protection == VM_PROT_READ) {\n        assert(vmRegionBasicInfoData.protection == VM_PROT_READ);\n        newProtection = VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY;\n    } else if (vmRegionBasicInfoData.protection == VM_PROT_READ) {\n        newProtection = VM_PROT_READ | VM_PROT_WRITE;\n    }\n    if (newProtection != VM_PROT_NONE) {\n        // got and section->size will be Page-Aligned\n        kernReturn = vm_protect(mach_task_self(), (vm_address_t) got, section->size, false, newProtection);\n        if (kernReturn != KERN_SUCCESS) {\n            assert(false && VM_PROTECT_ERROR);\n\n            return;\n        }\n    }\n\n    for (uint64_t i = 0; i < section->size / sizeof(uintptr_t); ++i) {\n        uint32_t symbolTableIndex = sectionIndirectSymbolTable[i];\n        if (symbolTableIndex == INDIRECT_SYMBOL_ABS || symbolTableIndex == INDIRECT_SYMBOL_LOCAL || symbolTableIndex == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) {\n            continue;\n        }\n        uint32_t stringTableOffset = symbolTable[symbolTableIndex].n_un.n_strx;\n        const char *symbolName = stringTable + stringTableOffset;\n        // \"\\0\" || \"_\\0\" is invalid symbol name\n        bool symbolNameLongerThanOneCharacter = symbolName[0] && symbolName[1];\n        struct RebindingEntry *rebindingEntry;\n        SLIST_FOREACH(rebindingEntry, &rebindingEntryHead, node) {\n            for (size_t j = 0; j < rebindingEntry->length; ++j) {\n                // strcmp -> strncmp\n                if (symbolNameLongerThanOneCharacter && strcmp(&symbolName[1], rebindingEntry->rebinding[j].name) == 0) {\n                    if (rebindingEntry->rebinding[j].replaced && got[i] != (uintptr_t) rebindingEntry->rebinding[j].replacement) {\n                        *(rebindingEntry->rebinding[j].replaced) = got[i];\n                    }\n\n                    got[i] = (uintptr_t) rebindingEntry->rebinding[j].replacement;\n\n                    break;\n                }\n            }\n        }\n    }\n\n    if (vmRegionBasicInfoData.protection == VM_PROT_READ) {\n        kernReturn = vm_protect(mach_task_self(), (vm_address_t) got, section->size, false, vmRegionBasicInfoData.protection);\n        if (kernReturn != KERN_SUCCESS) {\n            assert(false && VM_PROTECT_ERROR);\n\n            return;\n        }\n    }\n}\n\nstatic void dyldAddImageCallback(const struct mach_header *machHeader, intptr_t vmAddrSlide) {\n    // Skip Mach-O file header\n    uintptr_t currentPointer = (uintptr_t) machHeader + sizeof(mach_header_t);\n\n    segment_command_t *linkEditCommand = NULL;\n    struct symtab_command *symbolTableCommand = NULL;\n    struct dysymtab_command *dynamicSymbolTableCommand = NULL;\n\n    // Iterator\n    segment_command_t *currentSegmentCommand;\n\n    for (uint32_t i = 0; i < machHeader->ncmds; ++i, currentPointer += currentSegmentCommand->cmdsize) {\n        currentSegmentCommand = (segment_command_t *) currentPointer;\n        if (currentSegmentCommand->cmd == LC_SEGMENT_ARCH_DEPENDENT) {\n            if (strcmp(currentSegmentCommand->segname, SEG_LINKEDIT) == 0) {\n                linkEditCommand = currentSegmentCommand;\n            }\n        } else if (currentSegmentCommand->cmd == LC_SYMTAB) {\n            symbolTableCommand = (struct symtab_command *) currentSegmentCommand;\n        } else if (currentSegmentCommand->cmd == LC_DYSYMTAB) {\n            dynamicSymbolTableCommand = (struct dysymtab_command *) currentSegmentCommand;\n        }\n    }\n    if (!symbolTableCommand || !dynamicSymbolTableCommand || !linkEditCommand) {\n        assert(false && \"SYMBOL or DYNAMIC_SYMBOL or LINK_EDIT error.\");\n\n        return;\n    } else if (!dynamicSymbolTableCommand->nindirectsyms) {\n        // UIKit will no indirect symbol call\n        return;\n    }\n    // Find read-only base address\n    uintptr_t linkEditBasePointer = vmAddrSlide + linkEditCommand->vmaddr - linkEditCommand->fileoff;\n    nlist_t *symbolTable = (nlist_t *) (linkEditBasePointer + symbolTableCommand->symoff);\n    char *stringTable = (char *) (linkEditBasePointer + symbolTableCommand->stroff);\n    uint32_t *indirectSymbolTable = (uint32_t *) (linkEditBasePointer + dynamicSymbolTableCommand->indirectsymoff);\n\n    currentPointer = (uintptr_t) machHeader + sizeof(mach_header_t);\n    for (uint32_t i = 0; i < machHeader->ncmds; ++i, currentPointer += currentSegmentCommand->cmdsize) {\n        currentSegmentCommand = (segment_command_t *) currentPointer;\n        if (currentSegmentCommand->cmd == LC_SEGMENT_ARCH_DEPENDENT) {\n            if (strncmp(currentSegmentCommand->segname, SEG_DATA, 6) != 0 && strncmp(currentSegmentCommand->segname, SEG_DATA_CONST, 12)) {\n                continue;\n            }\n            for (uint32_t j = 0; j < currentSegmentCommand->nsects; ++j) {\n                // section = sectionBase[j]\n                section_t *section = ((section_t *) (currentPointer + sizeof(segment_command_t))) + j;\n                uint32_t sectionType = section->flags & SECTION_TYPE;\n                if (sectionType == S_LAZY_SYMBOL_POINTERS || sectionType == S_NON_LAZY_SYMBOL_POINTERS) {\n                    performRebindingWithSection(section, (uintptr_t) vmAddrSlide, symbolTable, stringTable, indirectSymbolTable);\n                }\n            }\n        }\n    }\n}\n\nbool DKRebindSymbols(struct DKRebinding rebinding[], size_t length) {\n    bool retVal = appendRebinding(rebinding, length);\n    if (!retVal) {\n        return false;\n    }\n    if (SLIST_NEXT(SLIST_FIRST(&rebindingEntryHead), node)) {\n        uint32_t count = _dyld_image_count();\n        for (uint32_t i = 0; i < count; ++i) {\n            dyldAddImageCallback(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));\n        }\n    } else {\n        // if this is the first call, register the callback for image addition (which is also invoked for existing images)\n        _dyld_register_func_for_add_image(dyldAddImageCallback);\n    }\n\n    return retVal;\n}\n\n\n"
  },
  {
    "path": "iOS/DoKit/Classes/CFoundation/hook.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef dokit_hook\n#define dokit_hook\n\n#include <DoraemonKit/common.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n\n#ifdef __cplusplus\n#define EXTERN_C_START\nextern \"C\"\n{\n#define EXTERN_C_END }\n#else\n#define EXTERN_C_START\n#define EXTERN_C_END\n#endif\n\nEXTERN_C_START\n\nstruct DKRebinding {\n    const char *name;\n    void *replacement;\n    uintptr_t *replaced;\n};\n\nDOKIT_EXPORT bool DKRebindSymbols(struct DKRebinding rebinding[], size_t length);\n\nEXTERN_C_END\n\n#endif\n"
  },
  {
    "path": "iOS/DoKit/Classes/Core/DKTrayViewController.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKTrayViewController : UIViewController\n\n@property(nonatomic, nullable, copy) void (^tapHandler)(void);\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/Core/DKTrayViewController.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import \"DKTrayViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKTrayViewController ()\n\n- (IBAction)buttonHandler;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n@implementation DKTrayViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    // Do any additional setup after loading the view.\n}\n\n/*\n#pragma mark - Navigation\n\n// In a storyboard-based application, you will often want to do a little preparation before navigation\n- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {\n    // Get the new view controller using [segue destinationViewController].\n    // Pass the selected object to the new view controller.\n}\n*/\n\n- (IBAction)buttonHandler {\n    self.tapHandler ? self.tapHandler() : (void) nil;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/Core/DoKit.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoKit : NSObject\n\n+ (void)installWithWindowScene:(UIWindowScene *)windowScene productId:(nullable NSString *)productId API_AVAILABLE(ios(13.0));\n\n+ (void)installWithProductId:(nullable NSString *)productId;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/Core/DoKit.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import \"DoKit.h\"\n#import <DoraemonKit/DKTrayViewController.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\nstatic inline CGRect getInitialTrayWindowFrame(CGFloat screenHeight) {\n    return CGRectMake(0, screenHeight / 3, 58, 58);\n}\n\nAPI_AVAILABLE(ios(13.0)) static inline UIWindow *createTrayWindow(UIWindowScene *_Nullable windowScene) {\n    UIWindow *trayWindow = [[UIWindow alloc] initWithWindowScene:windowScene];\n    trayWindow.frame = getInitialTrayWindowFrame(windowScene.screen.bounds.size.height);\n\n    return trayWindow;\n}\n\nstatic inline UIWindow *createTrayWindowLegacy() {\n    return [[UIWindow alloc] initWithFrame:getInitialTrayWindowFrame(UIScreen.mainScreen.bounds.size.height)];\n}\n\nstatic NSSet<UIWindow *> *_Nullable windowSet = nil;\n\n@interface DoKit ()\n\n+ (void)initWithTrayWindow:(UIWindow *)trayWindow;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n@implementation DoKit\n\n+ (void)initWithTrayWindow:(UIWindow *)trayWindow {\n    NSURL *resourceBundleUrl = [[NSBundle bundleForClass:self.class] URLForResource:@\"DoKitResource\" withExtension:@\"bundle\"];\n    if (!resourceBundleUrl) {\n#ifndef NS_BLOCK_ASSERTIONS\n        NSCAssert(NO, @\"DoKitResource.bundle not found.\");\n#endif\n\n        return;\n    }\n    DKTrayViewController *trayViewController = [[DKTrayViewController alloc] initWithNibName:@\"DKTrayViewController\" bundle:[NSBundle bundleWithURL:resourceBundleUrl]];\n//    trayViewController.tapHandler = ^() {\n//    };\n    trayWindow.rootViewController = trayViewController;\n    trayWindow.windowLevel = UIWindowLevelStatusBar + 1;\n    trayWindow.hidden = NO;\n    NSMutableSet *mutableWindowSet = windowSet.mutableCopy;\n    windowSet = nil;\n    if (!mutableWindowSet) {\n        mutableWindowSet = NSMutableSet.set;\n    }\n    [mutableWindowSet addObject:trayWindow];\n    windowSet = mutableWindowSet.copy;\n}\n\n\n+ (void)installWithWindowScene:(UIWindowScene *)windowScene productId:(nullable NSString *)productId {\n    [self initWithTrayWindow:createTrayWindow(windowScene)];\n}\n\n+ (void)installWithProductId:(NSString *)productId {\n    if (@available(iOS 13.0, *)) {\n//        NSAssert(NO, @\"Please use +[DoKit installWithWindowScene:productId: instead].\");\n        __block UIWindowScene *windowScene = nil;\n        [UIApplication.sharedApplication.connectedScenes enumerateObjectsUsingBlock:^(UIScene *obj, BOOL *stop) {\n            if ([obj.session.role isEqualToString:UIWindowSceneSessionRoleApplication] && [obj isKindOfClass:UIWindowScene.class]) {\n                *stop = YES;\n                windowScene = (UIWindowScene *) obj;\n            }\n        }];\n        if (windowScene) {\n            [self installWithWindowScene:windowScene productId:productId];\n        }\n    } else {\n        [self initWithTrayWindow:createTrayWindowLegacy()];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/EventSynthesize/UITouch+DKEventSynthesize.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <DoraemonKit/common.h>\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\nDOKIT_EXPORT UIEvent *_Nullable DKEventWithTouches(NSArray<UITouch *> *touches);\n\n@interface UITouch (DKEventSynthesize)\n\n- (instancetype)initWithStartPoint:(CGPoint)startPoint view:(UIView *)view;\n\n- (void)dk_updateWithPhase:(UITouchPhase)phase;\n\n- (void)dk_updateWithPointInWindow:(CGPoint)pointInWindow;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/EventSynthesize/UITouch+DKEventSynthesize.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import \"UITouch+DKEventSynthesize.h\"\n#import <objc/runtime.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UITouch (APLPrivate)\n\n- (void)setTimestamp:(NSTimeInterval)timestamp;\n\n- (void)setPhase:(UITouchPhase)phase;\n\n- (void)setTapCount:(NSUInteger)tapCount;\n\n- (void)setWindow:(nullable UIWindow *)window;\n\n- (void)setView:(nullable UIView *)view;\n\n- (void)_setLocationInWindow:(CGPoint)locationInWindow resetPrevious:(BOOL)resetPrevious;\n\n- (void)_setIsFirstTouchForView:(BOOL)firstTouchForView;\n\n@end\n\n@interface UITouchesEvent : UIEvent\n\n- (void)_clearTouches;\n\n- (void)_addTouch:(UITouch *)touch forDelayedDelivery:(BOOL)forDelayedDelivery;\n\n@end\n\n@interface UIApplication (APLPrivate)\n\n- (nullable UITouchesEvent *)_touchesEvent;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\nUIEvent *_Nullable DKEventWithTouches(NSArray<UITouch *> *touches) {\n    NSCAssert(touches.count > 0, @\"touches have no element\");\n    UITouchesEvent *touchesEvent = [UIApplication.sharedApplication _touchesEvent];\n    NSCAssert(touchesEvent, @\"-[UIApplication _touchesEvent] return nil\");\n    [touchesEvent _clearTouches];\n    [touches enumerateObjectsUsingBlock:^(UITouch *_Nonnull obj, __attribute__((unused)) NSUInteger idx, __attribute__((unused)) BOOL *_Nonnull stop) {\n        [touchesEvent _addTouch:obj forDelayedDelivery:NO];\n    }];\n\n    return touchesEvent;\n}\n\n@implementation UITouch (DKEventSynthesize)\n\n- (instancetype)initWithStartPoint:(CGPoint)startPoint view:(UIView *)view {\n    if (!view.window) {\n        NSAssert(NO, @\"view.window is nil\");\n\n        return nil;\n    }\n    self = [self init];\n    self.timestamp = 0;\n    self.phase = UITouchPhaseBegan;\n    self.tapCount = 1;\n    self.window = view.window;\n    // - hitTest:withEvent: can pass event with nil\n    // and could return nil as hitTestView.\n    // Then we use window as hitTestView\n    self.view = view;\n    [self _setLocationInWindow:[view.window convertPoint:startPoint fromView:view] resetPrevious:YES];\n    if ([self respondsToSelector:@selector(_setIsFirstTouchForView:)]) {\n        [self _setIsFirstTouchForView:YES];\n    } else {\n        // We modify the touchFlags ivar struct directly.\n        // First entry is _firstTouchForView\n        Ivar flagsIvar = class_getInstanceVariable(UITouch.class, \"_touchFlags\");\n        ptrdiff_t touchFlagsOffset = ivar_getOffset(flagsIvar);\n        char *flags = (__bridge void *) self + touchFlagsOffset;\n        *flags = *flags | (char) 0x01;\n    }\n\n    return self;\n}\n\n- (void)dk_updateWithPhase:(UITouchPhase)phase {\n    self.phase = phase;\n}\n\n- (void)dk_updateWithPointInWindow:(CGPoint)pointInWindow {\n    [self _setLocationInWindow:pointInWindow resetPrevious:NO];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/EventSynthesize/UIView+EventSynthesize.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UIView (EventSynthesize)\n\n- (void)dk_tap;\n\n- (void)dk_longPress;\n\n- (void)dk_dragWithStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;\n\n- (void)dk_dragWithStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint stepCount:(NSUInteger)stepCount;\n\n- (void)dk_dragWithStartPoint:(CGPoint)startPoint displacement:(CGPoint)displacement stepCount:(NSUInteger)stepCount;\n\n- (void)dk_pinchInWithDistance:(CGFloat)distance;\n\n- (void)dk_pinchOutWithDistance:(CGFloat)distance;\n\n- (void)dk_dragWithPointEventArray:(nullable NSArray<NSArray<NSValue *> *> *)pointEventArray;\n\n- (void)dk_swipeWithDirection:(UISwipeGestureRecognizerDirection)swipeGestureRecognizerDirection;\n\n- (void)dk_rotateWithRadian:(CGFloat)radian;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/EventSynthesize/UIView+EventSynthesize.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import \"UIView+EventSynthesize.h\"\n#import <DoraemonKit/UITouch+DKEventSynthesize.h>\n\n@implementation UIView (EventSynthesize)\n\n- (void)dk_tap {\n    if (__builtin_expect(!self.window, NO)) {\n        NSAssert1(NO, @\"%@ without window property\", self);\n\n        return;\n    }\n    CGPoint point = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);\n    UITouch *touch = [[UITouch alloc] initWithStartPoint:point view:self];\n    UIEvent *event = DKEventWithTouches(@[touch]);\n    if (__builtin_expect(!event, NO)) {\n        NSAssert(NO, @\"DKEventWithTouches() return nil\");\n\n        return;\n    }\n    [UIApplication.sharedApplication sendEvent:event];\n    [touch dk_updateWithPhase:UITouchPhaseEnded];\n    [UIApplication.sharedApplication sendEvent:event];\n}\n\nstatic const double DELAY_TIME = 0.01;\n\n- (void)dk_longPress {\n    if (__builtin_expect(!self.window, NO)) {\n        NSAssert1(NO, @\"%@ without window property\", self);\n\n        return;\n    }\n    CGPoint point = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);\n    UITouch *touch = [[UITouch alloc] initWithStartPoint:point view:self];\n    UIEvent *event = DKEventWithTouches(@[touch]);\n    if (__builtin_expect(!event, NO)) {\n        NSAssert(NO, @\"DKEventWithTouches() return nil\");\n\n        return;\n    }\n    [UIApplication.sharedApplication sendEvent:event];\n    CFRunLoopRunResult runLoopRunResult = CFRunLoopRunInMode(kCFRunLoopDefaultMode, DELAY_TIME, false);\n    NSAssert(runLoopRunResult == kCFRunLoopRunTimedOut, @\"CFRunLoopRunInMode() must be timeout\");\n    // UILongPressGestureRecognizer ignore the timestamp member of UITouch and UIEvent\n    [touch dk_updateWithPhase:UITouchPhaseEnded];\n    [UIApplication.sharedApplication sendEvent:event];\n}\n\n- (void)dk_dragWithStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {\n    [self dk_dragWithStartPoint:startPoint endPoint:endPoint stepCount:2];\n}\n\n- (void)dk_dragWithStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint stepCount:(NSUInteger)stepCount {\n    CGPoint displacement = CGPointMake(endPoint.x - startPoint.x, endPoint.y - startPoint.y);\n    [self dk_dragWithStartPoint:startPoint displacement:displacement stepCount:stepCount];\n}\n\nstatic const CGFloat kTwoFingerConstantWidth = 40;\n\n- (void)dk_pinchOutWithDistance:(CGFloat)distance {\n    CGPoint centerPoint = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);\n    // estimate the first finger to be on the left\n    CGPoint finger1Start = CGPointMake(centerPoint.x - kTwoFingerConstantWidth, centerPoint.y);\n    CGPoint finger1End = CGPointMake(centerPoint.x - kTwoFingerConstantWidth - distance, centerPoint.y);\n    // estimate the second finger to be on the right\n    CGPoint finger2Start = CGPointMake(centerPoint.x + kTwoFingerConstantWidth, centerPoint.y);\n    CGPoint finger2End = CGPointMake(centerPoint.x + kTwoFingerConstantWidth + distance, centerPoint.y);\n\n    // Began will call - viewForZoomingInScrollView:\n    // Consume *two* move event then clear view for gesture recognizer, then call - viewForZoomingInScrollView: again\n    // We should pass *another* move event to trigger UIGestureRecognizerStateChange\n    // The last is end event\n    // So we should pass 1 began, 3 move, 1 end event\n    [self dk_dragWithPointEventArray:@[@[[NSValue valueWithCGPoint:finger1Start], [NSValue valueWithCGPoint:finger2Start]],\n            @[[NSValue valueWithCGPoint:CGPointMake(centerPoint.x - kTwoFingerConstantWidth - 1, centerPoint.y)], [NSValue valueWithCGPoint:CGPointMake(centerPoint.x + kTwoFingerConstantWidth + distance + 1, centerPoint.y)]],\n            @[[NSValue valueWithCGPoint:CGPointMake(centerPoint.x - kTwoFingerConstantWidth - distance / 2, centerPoint.y)], [NSValue valueWithCGPoint:CGPointMake(centerPoint.x + kTwoFingerConstantWidth + distance / 2, centerPoint.y)]],\n            @[[NSValue valueWithCGPoint:finger1End], [NSValue valueWithCGPoint:finger2End]]]];\n}\n\n- (void)dk_pinchInWithDistance:(CGFloat)distance {\n    CGPoint centerPoint = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);\n    // estimate the first finger to be on the left\n    CGPoint finger1Start = CGPointMake(centerPoint.x - kTwoFingerConstantWidth - distance, centerPoint.y);\n    CGPoint finger1End = CGPointMake(centerPoint.x - kTwoFingerConstantWidth, centerPoint.y);\n    // estimate the second finger to be on the right\n    CGPoint finger2Start = CGPointMake(centerPoint.x + kTwoFingerConstantWidth + distance, centerPoint.y);\n    CGPoint finger2End = CGPointMake(centerPoint.x + kTwoFingerConstantWidth, centerPoint.y);\n\n    // Began will call - viewForZoomingInScrollView:\n    // Consume *two* move event then clear view for gesture recognizer, then call - viewForZoomingInScrollView: again\n    // We should pass *another* move event to trigger UIGestureRecognizerStateChange\n    // The last is end event\n    // So we should pass 1 began, 3 move, 1 end event\n    [self dk_dragWithPointEventArray:@[@[[NSValue valueWithCGPoint:finger1Start], [NSValue valueWithCGPoint:finger2Start]],\n            @[[NSValue valueWithCGPoint:CGPointMake(centerPoint.x - kTwoFingerConstantWidth - distance + 1, centerPoint.y)], [NSValue valueWithCGPoint:CGPointMake(centerPoint.x + kTwoFingerConstantWidth + distance - 1, centerPoint.y)]],\n            @[[NSValue valueWithCGPoint:CGPointMake(centerPoint.x - kTwoFingerConstantWidth - distance / 2, centerPoint.y)], [NSValue valueWithCGPoint:CGPointMake(centerPoint.x + kTwoFingerConstantWidth + distance / 2, centerPoint.y)]],\n            @[[NSValue valueWithCGPoint:finger1End], [NSValue valueWithCGPoint:finger2End]]]];\n}\n\n- (void)dk_dragWithStartPoint:(CGPoint)startPoint displacement:(CGPoint)displacement stepCount:(NSUInteger)stepCount {\n    if (stepCount < 2 || CGPointEqualToPoint(displacement, CGPointZero)) {\n        NSAssert(NO, @\"drag need start, move at least(end will commit automatically). Or displacement equal to (0, 0)\");\n\n        return;\n    }\n    if (__builtin_expect(!self.window, NO)) {\n        NSAssert1(NO, @\"%@ without window property\", self);\n\n        return;\n    }\n    UITouch *touch = nil;\n    UIEvent *event = nil;\n    for (NSUInteger i = 0; i < stepCount; ++i) {\n        CGFloat progress = ((CGFloat) i) / (stepCount - 1);\n        CGPoint point = CGPointMake(startPoint.x + progress * displacement.x, startPoint.y + progress * displacement.y);\n        if (!i) {\n            // Start event\n            touch = [[UITouch alloc] initWithStartPoint:point view:self];\n            event = DKEventWithTouches(@[touch]);\n            if (__builtin_expect(!event, NO)) {\n                NSAssert(NO, @\"DKEventWithTouches() return nil\");\n\n                return;\n            }\n            [UIApplication.sharedApplication sendEvent:event];\n        } else {\n            // UIScrollView track mode!\n            // 如果不考虑延时（stepCount），可以注释下面的代码\n//            CFRunLoopMode runLoopMode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());\n//            CFRunLoopRunResult runLoopRunResult = CFRunLoopRunInMode(runLoopMode, DELAY_TIME, false);\n//            CFRelease(runLoopMode);\n//            NSAssert(runLoopRunResult == kCFRunLoopRunTimedOut, @\"CFRunLoopRunInMode() must be timeout\");\n\n            // Move event\n            [touch dk_updateWithPointInWindow:[self.window convertPoint:point fromView:self]];\n            [touch dk_updateWithPhase:UITouchPhaseMoved];\n            [UIApplication.sharedApplication sendEvent:event];\n\n            if (i == stepCount - 1) {\n                [touch dk_updateWithPhase:UITouchPhaseEnded];\n                [UIApplication.sharedApplication sendEvent:event];\n            }\n        }\n    }\n}\n\n- (void)dk_dragWithPointEventArray:(nullable NSArray<NSArray<NSValue *> *> *)pointEventArray {\n    if (pointEventArray.count < 2) {\n        NSAssert(NO, @\"drag must have one event at least\");\n\n        return;\n    }\n    NSUInteger pointCountInFirstEvent = pointEventArray.firstObject.count;\n    // Note: point should be in view's coordinate!\n\n    __block BOOL pointCountIsError = NO;\n    // -[NSArray enumerateObjectsAtIndexes:options:usingBlock:] can accept NSIndexSet init with (1, 0), then the block will not be invoked\n    [pointEventArray enumerateObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, pointEventArray.count - 1)] options:0 usingBlock:^(NSArray<NSValue *> *obj, __attribute__((unused)) NSUInteger idx, BOOL *stop) {\n        if (obj.count != pointCountInFirstEvent) {\n            pointCountIsError = YES;\n            *stop = YES;\n        }\n    }];\n    if (pointCountIsError) {\n        NSAssert(NO, @\"All paths must have the same number of points\");\n\n        return;\n    }\n    NSMutableArray<UITouch *> *touchArray = [NSMutableArray arrayWithCapacity:pointCountInFirstEvent];\n    __block UIEvent *event = nil;\n    [pointEventArray enumerateObjectsUsingBlock:^(NSArray<NSValue *> *obj, NSUInteger idx, BOOL *__attribute__((unused)) stop) {\n        if (!idx) {\n            // Start event\n            [obj enumerateObjectsUsingBlock:^(NSValue *obj, __attribute__((unused)) NSUInteger idx, BOOL *__attribute__((unused)) stop) {\n                UITouch *touch = [[UITouch alloc] initWithStartPoint:obj.CGPointValue view:self];\n                [touchArray addObject:touch];\n            }];\n            event = DKEventWithTouches(touchArray.copy);\n            if (__builtin_expect(!event, NO)) {\n                NSAssert(NO, @\"DKEventWithTouches() return nil\");\n\n                return;\n            }\n            [UIApplication.sharedApplication sendEvent:event];\n        } else {\n//            CFRunLoopMode runLoopMode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());\n//            CFRunLoopRunResult runLoopRunResult = CFRunLoopRunInMode(runLoopMode, DELAY_TIME, false);\n//            CFRelease(runLoopMode);\n//            NSAssert(runLoopRunResult == kCFRunLoopRunTimedOut, @\"CFRunLoopRunInMode() must be timeout\");\n            // Move event\n            [obj enumerateObjectsUsingBlock:^(NSValue *obj, NSUInteger idx, BOOL *__attribute__((unused)) stop) {\n                // TODO(ChasonTang): If CGPoint not change, UITouchPhase should be UITouchPhaseStationary instead of UITouchPhaseMoved\n                [touchArray[idx] dk_updateWithPointInWindow:[self.window convertPoint:obj.CGPointValue fromView:self]];\n                [touchArray[idx] dk_updateWithPhase:UITouchPhaseMoved];\n            }];\n            [UIApplication.sharedApplication sendEvent:event];\n\n            if (idx == pointEventArray.count - 1) {\n                [touchArray enumerateObjectsUsingBlock:^(UITouch *__attribute((unused)) obj, __attribute((unused)) NSUInteger idx, BOOL *__attribute((unused)) stop) {\n                    [touchArray[idx] dk_updateWithPhase:UITouchPhaseEnded];\n                }];\n                [UIApplication.sharedApplication sendEvent:event];\n            }\n        }\n    }];\n}\n\n- (void)dk_swipeWithDirection:(UISwipeGestureRecognizerDirection)swipeGestureRecognizerDirection {\n    if (__builtin_expect(!self.window, NO)) {\n        NSAssert1(NO, @\"%@ without window property\", self);\n\n        return;\n    }\n    if ((swipeGestureRecognizerDirection & UISwipeGestureRecognizerDirectionDown && swipeGestureRecognizerDirection & UISwipeGestureRecognizerDirectionUp) || (swipeGestureRecognizerDirection & UISwipeGestureRecognizerDirectionLeft && swipeGestureRecognizerDirection & UISwipeGestureRecognizerDirectionRight)) {\n        NSAssert(NO, @\"swipeGestureRecognizerDirection cannot be left and right or up and down in the meantime\");\n\n        return;\n    }\n    CGPoint point = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);\n    CGPoint endPoint = point;\n    if (swipeGestureRecognizerDirection & UISwipeGestureRecognizerDirectionRight) {\n        endPoint.x += point.x;\n    }\n    if (swipeGestureRecognizerDirection & UISwipeGestureRecognizerDirectionLeft) {\n        endPoint.x -= point.x;\n    }\n    if (swipeGestureRecognizerDirection & UISwipeGestureRecognizerDirectionUp) {\n        endPoint.y -= point.y;\n    }\n    if (swipeGestureRecognizerDirection & UISwipeGestureRecognizerDirectionDown) {\n        endPoint.y += point.y;\n    }\n    UITouch *touch = [[UITouch alloc] initWithStartPoint:point view:self];\n    UIEvent *event = DKEventWithTouches(@[touch]);\n    if (__builtin_expect(!event, NO)) {\n        NSAssert(NO, @\"DKEventWithTouches() return nil\");\n\n        return;\n    }\n    [UIApplication.sharedApplication sendEvent:event];\n    [touch dk_updateWithPhase:UITouchPhaseMoved];\n    [touch dk_updateWithPointInWindow:[self.window convertPoint:endPoint fromView:self]];\n    [UIApplication.sharedApplication sendEvent:event];\n    [touch dk_updateWithPhase:UITouchPhaseEnded];\n    [UIApplication.sharedApplication sendEvent:event];\n}\n\n- (void)dk_rotateWithRadian:(CGFloat)radian {\n    CGPoint centerPoint = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);\n    // estimate the first finger to be on the left\n    CGPoint finger1Start = CGPointMake(centerPoint.x - kTwoFingerConstantWidth, centerPoint.y);\n    CGPoint finger1End = CGPointMake(centerPoint.x - kTwoFingerConstantWidth * cos(radian), centerPoint.y - kTwoFingerConstantWidth * sin(radian));\n    \n    // estimate the second finger to be on the right\n    CGPoint finger2Start = CGPointMake(centerPoint.x + kTwoFingerConstantWidth, centerPoint.y);\n    CGPoint finger2End = CGPointMake(centerPoint.x + kTwoFingerConstantWidth * cos(radian), centerPoint.y + kTwoFingerConstantWidth * sin(radian));\n\n    [self dk_dragWithPointEventArray:@[@[[NSValue valueWithCGPoint:finger1Start], [NSValue valueWithCGPoint:finger2Start]],\n            @[[NSValue valueWithCGPoint:finger1End], [NSValue valueWithCGPoint:finger2End]]]];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DKMultiControlProtocol.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKMultiControlProtocol : NSURLProtocol\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DKMultiControlProtocol.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import \"DKMultiControlProtocol.h\"\n#import <DoraemonKit/DKMultiControlStreamManager.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\nstatic NSString *const ERROR_DOMAIN = @\"com.didi.dokit\";\n\nstatic NSString *const MULTI_CONTROL_PROTOCOL_KEY = @\"MULTI_CONTROL_PROTOCOL_KEY\";\n\n@interface DKMultiControlProtocol () <NSURLSessionDataDelegate>\n\n@property(nonatomic, nullable, weak) NSURLSession *urlSession;\n\n@property(nonatomic, nullable, copy) NSString *dataId;\n\n@property(nonatomic, nullable, copy) NSHTTPURLResponse *httpUrlResponse;\n\n@property(nonatomic, nullable, copy) NSString *responseBody;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n@implementation DKMultiControlProtocol\n\n+ (BOOL)canInitWithRequest:(NSURLRequest *)request {\n    // +[NSURLProtocol canInitWithRequest:] may be called from any thread.\n    BOOL returnValue = NO;\n    switch (DKMultiControlStreamManager.sharedInstance.state) {\n        case DKMultiControlStreamManagerStateMaster:\n            if (![NSURLProtocol propertyForKey:MULTI_CONTROL_PROTOCOL_KEY inRequest:request]) {\n                NSString *contentType = [request valueForHTTPHeaderField:@\"Content-Type\"];\n                NSString *accept = [request valueForHTTPHeaderField:@\"Accept\"];\n                if (![contentType hasPrefix:@\"multipart/form-data\"] && ![accept hasPrefix:@\"image/\"]) {\n                    returnValue = YES;\n                }\n            }\n            break;\n        case DKMultiControlStreamManagerStateSlave:\n            returnValue = YES;\n            break;\n\n        default:\n            break;\n    }\n\n    return returnValue;\n}\n\n+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {\n    // +[NSURLProtocol canonicalRequestForRequest:] may be called from any thread.\n    if (DKMultiControlStreamManager.sharedInstance.state != DKMultiControlStreamManagerStateMaster) {\n        return request;\n    }\n    NSMutableURLRequest *mutableUrlRequest = request.mutableCopy;\n    [NSURLProtocol setProperty:@(YES) forKey:MULTI_CONTROL_PROTOCOL_KEY inRequest:mutableUrlRequest];\n    \n    return mutableUrlRequest.copy;\n}\n\n- (void)startLoading {\n    NSOperationQueue *clientOperationQueue = [[NSOperationQueue alloc] init];\n    clientOperationQueue.maxConcurrentOperationCount = 1;\n    if ([NSURLProtocol propertyForKey:MULTI_CONTROL_PROTOCOL_KEY inRequest:self.request]) {\n        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);\n        dispatch_async(dispatch_get_main_queue(), ^{\n            self.dataId = [DKMultiControlStreamManager.sharedInstance recordWithUrlRequest:self.request];\n            dispatch_semaphore_signal(semaphore);\n        });\n        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);\n        self.urlSession = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration delegate:self delegateQueue:clientOperationQueue];\n        [[self.urlSession dataTaskWithRequest:self.request] resume];\n    } else {\n        // Slave device send request through websocket.\n        NSURLRequest *urlRequest = self.request.copy;\n        __weak typeof(self) weakSelf = self;\n        dispatch_async(dispatch_get_main_queue(), ^{\n            [DKMultiControlStreamManager.sharedInstance queryWithUrlRequest:urlRequest completionBlock:^(NSError *error, NSHTTPURLResponse *response, NSData *data) {\n                // Main thread.\n                [clientOperationQueue addOperationWithBlock:^{\n                    typeof(weakSelf) self = weakSelf;\n                    if (error || !response) {\n                        [self.client URLProtocol:self didFailWithError:error ?: [NSError errorWithDomain:ERROR_DOMAIN code:0 userInfo:nil]];\n\n                        return;\n                    }\n                    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];\n                    if (data) {\n                        [self.client URLProtocol:self didLoadData:data];\n                    }\n                    [self.client URLProtocolDidFinishLoading:self];\n                }];\n            }];\n        });\n    }\n}\n\n- (void)stopLoading {\n    [self.urlSession invalidateAndCancel];\n}\n\n- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {\n    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];\n    completionHandler(NSURLSessionResponseAllow);\n    if ([response isKindOfClass:NSHTTPURLResponse.class]) {\n        self.httpUrlResponse = (NSHTTPURLResponse *) response;\n    }\n}\n\n- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {\n    [self.client URLProtocol:self didLoadData:data];\n    // TODO(ChasonTang): Append data to `self.responseData`.\n    self.responseBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];\n}\n\n- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {\n    if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {\n        NSURLCredential *urlCredential = challenge.protectionSpace.serverTrust ? [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] : nil;\n        completionHandler(NSURLSessionAuthChallengeUseCredential, urlCredential);\n    } else {\n        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);\n    }\n}\n\n- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {\n    if (!error) {\n        [self.client URLProtocolDidFinishLoading:self];\n        if (self.httpUrlResponse && self.dataId) {\n            NSHTTPURLResponse *httpUrlResponse = self.httpUrlResponse.copy;\n            NSString *dataId = self.dataId.copy;\n            NSString *responseBody = self.responseBody.copy;\n            dispatch_async(dispatch_get_main_queue(), ^{\n                [DKMultiControlStreamManager.sharedInstance recordWithHTTPUrlResponse:httpUrlResponse dataId:dataId responseBody:responseBody];\n            });\n        }\n    } else {\n        [self.client URLProtocol:self didFailWithError:error];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DKMultiControlStreamManager.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\ntypedef NS_ENUM(NSUInteger, DKMultiControlStreamManagerState) {\n    DKMultiControlStreamManagerStateClosed = 0,\n    DKMultiControlStreamManagerStateSlave,\n    DKMultiControlStreamManagerStateMaster\n};\n\n@protocol DKMultiControlStreamManagerStateListener\n\n@required\n- (void)changeToState:(DKMultiControlStreamManagerState)state;\n\n@end\n\n/// @brief Main thread.\n@interface DKMultiControlStreamManager : NSObject\n\n@property(nonatomic, nullable, copy) NSString *(^searchIdConstructor)(NSURL *url);\n\n@property(nonatomic, nullable, copy) void (^tcpHandler)(NSString *_Nullable message);\n\n@property(readonly) DKMultiControlStreamManagerState state;\n\n+ (instancetype)sharedInstance;\n\n- (void)registerMultiControlStreamManagerStateListener:(id <DKMultiControlStreamManagerStateListener>)listener;\n\n- (void)unregisterWithListener:(id <DKMultiControlStreamManagerStateListener>)listener;\n\n- (void)enableMultiControlWithUrl:(NSURL *)url;\n\n- (void)changeToMaster;\n\n- (void)changeToSlave;\n\n- (void)disableMultiControl;\n\n- (nullable NSString *)recordWithUrlRequest:(NSURLRequest *)urlRequest;\n\n- (void)recordWithHTTPUrlResponse:(NSHTTPURLResponse *)httpUrlResponse dataId:(NSString *)dataId responseBody:(nullable NSString *)responseBody;\n\n- (void)queryWithUrlRequest:(NSURLRequest *)urlRequest completionBlock:(void (^)(NSError *_Nullable error, NSHTTPURLResponse *_Nullable response, NSData *_Nullable data))completionBlock;\n\n- (void)broadcastWithActionMessage:(NSString *)message;\n\n- (void)broadcastWithTCPMessage:(nullable NSString *)message;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DKMultiControlStreamManager.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import \"DKMultiControlStreamManager.h\"\n#import <DoraemonKit/DKActionDTOModel.h>\n#import <DoraemonKit/DKCommonDTOModel.h>\n#import <DoraemonKit/DKWebSocketSession.h>\n#import <DoraemonKit/DKDataRequestDTOModel.h>\n#import <DoraemonKit/DKDataResponseDTOModel.h>\n#import <DoraemonKit/DKMultiControlProtocol.h>\n#if __has_include(<DoraemonKit/DoraemonMCCommandExcutor.h>)\n#define HAS_MULTI_CONTROL 1\n#import <DoraemonKit/DoraemonMCCommandExcutor.h>\n#endif\nNS_ASSUME_NONNULL_BEGIN\n\nstatic NSString *generateId(void);\n\nstatic NSString *const MULTI_CONTROL_HOST = @\"mc_host\";\n\n//static NSString *const BEHAVIOR_ID = @\"68753A444D6F12269C600050E4C00067\";\n\n@interface DKMultiControlStreamManager ()\n\n@property(nonatomic, nullable, copy) NSString *behaviorId;\n\n@property(nonatomic, nullable, strong) DKWebSocketSession *webSocketSession;\n\n/// Default is NO.\n@property(nonatomic, assign) BOOL isMaster;\n\n@property(nonatomic, nullable, strong) NSHashTable<id <DKMultiControlStreamManagerStateListener>> *listenerArray;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\nNSString *generateId(void) {\n    return [NSUUID.UUID.UUIDString stringByReplacingOccurrencesOfString:@\"-\" withString:@\"\"];\n}\n\n@implementation DKMultiControlStreamManager\n\n+ (instancetype)sharedInstance {\n    static id _sharedInstance = nil;\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        _sharedInstance = [[self alloc] init];\n    });\n\n    return _sharedInstance;\n}\n\n- (void)registerMultiControlStreamManagerStateListener:(id <DKMultiControlStreamManagerStateListener>)listener {\n    if (!self.listenerArray) {\n        self.listenerArray = NSHashTable.weakObjectsHashTable;\n    }\n    [self.listenerArray addObject:listener];\n    [listener changeToState:self.webSocketSession ? (self.isMaster ? DKMultiControlStreamManagerStateMaster : DKMultiControlStreamManagerStateSlave) : DKMultiControlStreamManagerStateClosed];\n}\n\n- (void)unregisterWithListener:(id)listener {\n    [self.listenerArray removeObject:listener];\n    if (!self.listenerArray.count) {\n        self.listenerArray = nil;\n    }\n}\n\n- (void)enableMultiControlWithUrl:(NSURL *)url {\n    if (self.webSocketSession) {\n        return;\n    }\n    self.webSocketSession = [[DKWebSocketSession alloc] initWithUrl:url];\n    __weak typeof(self) weakSelf = self;\n    self.webSocketSession.notifyHandler = ^(DKCommonDTOModel *commonDTOModel) {\n        typeof(weakSelf) self = weakSelf;\n        if ([commonDTOModel.dataType isEqualToString:MULTI_CONTROL_HOST]) {\n            [self changeToSlave];\n        } else if ([commonDTOModel.dataType isEqualToString:DK_ACTION]) {\n            // Handle behaviorId and process data.\n            NSData *jsonData = [commonDTOModel.data dataUsingEncoding:NSUTF8StringEncoding];\n            if (jsonData) {\n                NSError *error = nil;\n                NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];\n                if (jsonDictionary) {\n                    DKActionDTOModel *actionDTOModel = [MTLJSONAdapter modelOfClass:DKActionDTOModel.class fromJSONDictionary:jsonDictionary error:&error];\n                    self.behaviorId = actionDTOModel.behaviorId;\n                    if (actionDTOModel.payload) {\n#if HAS_ENCRYPT_APOLLO\n                        [DoraemonMCCommandExcutor excuteMessageStrFromNet:actionDTOModel.payload];\n#endif\n                    }\n                }\n            }\n        } else if ([commonDTOModel.dataType isEqualToString:DK_TCP]) {\n            self.tcpHandler ? self.tcpHandler(commonDTOModel.data) : nil;\n        }\n    };\n    for (id <DKMultiControlStreamManagerStateListener> listener in self.listenerArray) {\n        [listener changeToState:DKMultiControlStreamManagerStateSlave];\n    }\n    [NSURLProtocol registerClass:DKMultiControlProtocol.class];\n}\n\n- (void)changeToMaster {\n    if (!self.webSocketSession) {\n        for (id <DKMultiControlStreamManagerStateListener> listener in self.listenerArray) {\n            [listener changeToState:DKMultiControlStreamManagerStateClosed];\n        }\n\n        return;\n    }\n    if (self.isMaster) {\n//        for (id <DKMultiControlStreamManagerStateListener> listener in self.listenerArray) {\n//            [listener changeToState:DKMultiControlStreamManagerStateMaster];\n//        }\n\n        return;\n    }\n    DKCommonDTOModel *commonDTOModel = [[DKCommonDTOModel alloc] init];\n    commonDTOModel.deviceType = DK_DEVICE_TYPE;\n    commonDTOModel.dataType = MULTI_CONTROL_HOST;\n    commonDTOModel.connectSerial = self.webSocketSession.sessionUUID;\n    commonDTOModel.method = DK_WEBSOCKET_BROADCAST;\n    commonDTOModel.requestId = nil;\n    commonDTOModel.data = nil;\n    NSError *error = nil;\n    NSDictionary *jsonDictionary = [MTLJSONAdapter JSONDictionaryFromModel:commonDTOModel error:&error];\n    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary ?: @{} options:0 error:&error];\n    NSString *jsonString = nil;\n    if (jsonData) {\n        jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n    }\n    if (!jsonString) {\n        return;\n    }\n    [self.webSocketSession sendString:jsonString requestId:nil completionHandler:nil];\n    self.isMaster = YES;\n    self.behaviorId = generateId();\n    for (id <DKMultiControlStreamManagerStateListener> listener in self.listenerArray) {\n        [listener changeToState:DKMultiControlStreamManagerStateMaster];\n    }\n}\n\n- (void)changeToSlave {\n    if (!self.webSocketSession || !self.isMaster) {\n        return;\n    }\n    self.isMaster = NO;\n    self.behaviorId = nil;\n    for (id <DKMultiControlStreamManagerStateListener> listener in self.listenerArray) {\n        [listener changeToState:DKMultiControlStreamManagerStateSlave];\n    }\n}\n\n- (DKMultiControlStreamManagerState)state {\n    if (!self.webSocketSession) {\n        return DKMultiControlStreamManagerStateClosed;\n    }\n\n    return self.isMaster ? DKMultiControlStreamManagerStateMaster : DKMultiControlStreamManagerStateSlave;\n}\n\n- (void)disableMultiControl {\n    if (!self.webSocketSession) {\n        return;\n    }\n    self.isMaster = NO;\n    self.webSocketSession = nil;\n    for (id <DKMultiControlStreamManagerStateListener> listener in self.listenerArray) {\n        [listener changeToState:DKMultiControlStreamManagerStateClosed];\n    }\n    [NSURLProtocol unregisterClass:DKMultiControlProtocol.class];\n}\n\n- (NSString *)recordWithUrlRequest:(NSURLRequest *)urlRequest {\n    if (!self.webSocketSession || !urlRequest.URL || urlRequest.HTTPBodyStream.delegate) {\n        return nil;\n    }\n    DKDataRequestDTOModel *dataRequestDTOModel = [[DKDataRequestDTOModel alloc] init];\n    dataRequestDTOModel.behaviorId = self.behaviorId;\n    dataRequestDTOModel.dataId = generateId();\n    dataRequestDTOModel.method = urlRequest.HTTPMethod;\n    dataRequestDTOModel.url = urlRequest.URL;\n    NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:urlRequest.URL resolvingAgainstBaseURL:YES];\n    urlComponents.fragment = nil;\n    urlComponents.password = nil;\n    urlComponents.query = nil;\n    urlComponents.user = nil;\n    dataRequestDTOModel.searchId = urlComponents.string;\n    if (self.searchIdConstructor) {\n        NSString *searchId = self.searchIdConstructor(urlRequest.URL);\n        if (searchId.length > 0) {\n            dataRequestDTOModel.searchId = searchId;\n        }\n    }\n    dataRequestDTOModel.requestHeader = urlRequest.allHTTPHeaderFields;\n    if (urlRequest.HTTPBodyStream) {\n        NSInputStream *inputStream = urlRequest.HTTPBodyStream;\n        [inputStream open];\n        uint8_t buffer[10] = {0};\n        NSMutableData *data = nil;\n        while (inputStream.hasBytesAvailable) {\n            NSInteger length = [inputStream read:buffer maxLength:10];\n            if (length > 0) {\n                if (!data) {\n                    data = [NSMutableData dataWithBytes:buffer length:length];\n                } else {\n                    [data appendBytes:buffer length:length];\n                }\n            }\n        }\n        [inputStream close];\n        if (data) {\n            dataRequestDTOModel.requestBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];\n        }\n    }\n    NSError *error = nil;\n    NSDictionary *jsonDictionary = [MTLJSONAdapter JSONDictionaryFromModel:dataRequestDTOModel error:&error];\n    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary ?: @{} options:0 error:&error];\n    NSString *dataString = nil;\n    if (jsonData) {\n        dataString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n    }\n    DKCommonDTOModel *commonDTOModel = [[DKCommonDTOModel alloc] init];\n    commonDTOModel.requestId = nil;\n    commonDTOModel.deviceType = DK_DEVICE_TYPE;\n    commonDTOModel.data = dataString;\n    commonDTOModel.method = DK_METHOD_DATA;\n    commonDTOModel.connectSerial = self.webSocketSession.sessionUUID;\n    commonDTOModel.dataType = DK_DATA_REQUEST;\n    jsonDictionary = [MTLJSONAdapter JSONDictionaryFromModel:commonDTOModel error:&error];\n    jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary ?: @{} options:0 error:&error];\n    if (jsonData) {\n        dataString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n    }\n    if (!dataString) {\n        return nil;\n    }\n    [self.webSocketSession sendString:dataString requestId:nil completionHandler:nil];\n\n    return dataRequestDTOModel.dataId;\n}\n\n- (void)recordWithHTTPUrlResponse:(NSHTTPURLResponse *)httpUrlResponse dataId:(NSString *)dataId responseBody:(nullable NSString *)responseBody {\n    if (!self.webSocketSession) {\n        return;\n    }\n    DKDataResponseDTOModel *dataResponseDTOModel = [[DKDataResponseDTOModel alloc] init];\n    dataResponseDTOModel.dataId = dataId;\n    dataResponseDTOModel.responseCode = httpUrlResponse.statusCode;\n    dataResponseDTOModel.responseHeader = httpUrlResponse.allHeaderFields;\n    dataResponseDTOModel.responseBody = responseBody;\n    NSError *error = nil;\n    NSDictionary *jsonDictionary = [MTLJSONAdapter JSONDictionaryFromModel:dataResponseDTOModel error:&error];\n    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary ?: @{} options:0 error:&error];\n    NSString *dataString = nil;\n    if (jsonData) {\n        dataString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n    }\n    DKCommonDTOModel *commonDTOModel = [[DKCommonDTOModel alloc] init];\n    commonDTOModel.requestId = nil;\n    commonDTOModel.deviceType = DK_DEVICE_TYPE;\n    commonDTOModel.data = dataString;\n    commonDTOModel.method = DK_METHOD_DATA;\n    commonDTOModel.connectSerial = self.webSocketSession.sessionUUID;\n    commonDTOModel.dataType = DK_DATA_RESPONSE;\n    jsonDictionary = [MTLJSONAdapter JSONDictionaryFromModel:commonDTOModel error:&error];\n    jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary ?: @{} options:0 error:&error];\n    if (jsonData) {\n        dataString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n    }\n    if (dataString) {\n        [self.webSocketSession sendString:dataString requestId:nil completionHandler:nil];\n    }\n}\n\n- (void)queryWithUrlRequest:(NSURLRequest *)urlRequest completionBlock:(void (^)(NSError *, NSHTTPURLResponse *, NSData *))completionBlock {\n    if (!self.webSocketSession || !urlRequest.URL || !self.behaviorId) {\n        completionBlock(nil, nil, nil);\n\n        return;\n    }\n    DKDataRequestDTOModel *dataRequestDTOModel = [[DKDataRequestDTOModel alloc] init];\n    dataRequestDTOModel.behaviorId = self.behaviorId;\n    dataRequestDTOModel.dataId = nil;\n    dataRequestDTOModel.method = urlRequest.HTTPMethod;\n    dataRequestDTOModel.url = urlRequest.URL;\n    NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:urlRequest.URL resolvingAgainstBaseURL:YES];\n    urlComponents.fragment = nil;\n    urlComponents.password = nil;\n    urlComponents.query = nil;\n    urlComponents.user = nil;\n    dataRequestDTOModel.searchId = urlComponents.string;\n    if (self.searchIdConstructor) {\n        NSString *searchId = self.searchIdConstructor(urlRequest.URL);\n        if (searchId.length > 0) {\n            dataRequestDTOModel.searchId = searchId;\n        }\n    }\n    dataRequestDTOModel.requestHeader = urlRequest.allHTTPHeaderFields;\n    dataRequestDTOModel.requestBody = nil;\n    NSError *error = nil;\n    NSDictionary *jsonDictionary = [MTLJSONAdapter JSONDictionaryFromModel:dataRequestDTOModel error:&error];\n    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary ?: @{} options:0 error:&error];\n    NSString *dataString = nil;\n    if (jsonData) {\n        dataString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n    }\n    DKCommonDTOModel *commonDTOModel = [[DKCommonDTOModel alloc] init];\n    commonDTOModel.requestId = @(self.webSocketSession.requestId++);\n    commonDTOModel.deviceType = DK_DEVICE_TYPE;\n    commonDTOModel.data = dataString;\n    commonDTOModel.method = DK_METHOD_DATA;\n    commonDTOModel.connectSerial = self.webSocketSession.sessionUUID;\n    commonDTOModel.dataType = DK_DATA_QUERY;\n    jsonDictionary = [MTLJSONAdapter JSONDictionaryFromModel:commonDTOModel error:&error];\n    jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary ?: @{} options:0 error:&error];\n    if (jsonData) {\n        dataString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n    }\n    if (!dataString) {\n        completionBlock(nil, nil, nil);\n\n        return;\n    }\n    [self.webSocketSession sendString:dataString requestId:commonDTOModel.requestId completionHandler:^(NSError *_Nullable error, NSString *_Nullable responseString) {\n        if (error) {\n            completionBlock(error, nil, nil);\n\n            return;\n        }\n        NSData *data = [responseString dataUsingEncoding:NSUTF8StringEncoding];\n        if (!responseString) {\n            completionBlock(nil, nil, nil);\n\n            return;\n        }\n        NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];\n        if (error) {\n            completionBlock(error, nil, nil);\n\n            return;\n        }\n        DKDataResponseDTOModel *dataResponseDTOModel = [MTLJSONAdapter modelOfClass:DKDataResponseDTOModel.class fromJSONDictionary:jsonDictionary ?: @{} error:&error];\n        if (error) {\n            completionBlock(error, nil, nil);\n\n            return;\n        }\n        NSHTTPURLResponse *httpUrlResponse = [[NSHTTPURLResponse alloc] initWithURL:urlRequest.URL statusCode:dataResponseDTOModel.responseCode ?: 404 HTTPVersion:@\"HTTP/1.1\" headerFields:dataResponseDTOModel.responseHeader];\n        completionBlock(nil, httpUrlResponse, [dataResponseDTOModel.responseBody dataUsingEncoding:NSUTF8StringEncoding]);\n    }];\n}\n\n- (void)broadcastWithActionMessage:(NSString *)message {\n    if (!self.webSocketSession || !self.behaviorId) {\n        return;\n    }\n    DKActionDTOModel *actionDTOModel = [[DKActionDTOModel alloc] init];\n    actionDTOModel.payload = message;\n    self.behaviorId = generateId();\n    actionDTOModel.behaviorId = self.behaviorId;\n    NSError *error = nil;\n    NSDictionary *jsonDictionary = [MTLJSONAdapter JSONDictionaryFromModel:actionDTOModel error:&error];\n    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary ?: @{} options:0 error:&error];\n    NSString *dataString = nil;\n    if (jsonData) {\n        dataString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n    }\n    DKCommonDTOModel *commonDTOModel = [[DKCommonDTOModel alloc] init];\n    commonDTOModel.requestId = nil;\n    commonDTOModel.deviceType = DK_DEVICE_TYPE;\n    commonDTOModel.data = dataString;\n    commonDTOModel.method = DK_WEBSOCKET_BROADCAST;\n    commonDTOModel.connectSerial = self.webSocketSession.sessionUUID;\n    commonDTOModel.dataType = DK_ACTION;\n    jsonDictionary = [MTLJSONAdapter JSONDictionaryFromModel:commonDTOModel error:&error];\n    jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary ?: @{} options:0 error:&error];\n    if (jsonData) {\n        dataString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n    }\n    if (!dataString) {\n        return;\n    }\n    [self.webSocketSession sendString:dataString requestId:nil completionHandler:nil];\n}\n\n- (void)broadcastWithTCPMessage:(NSString *)message {\n    if (!self.webSocketSession) {\n        return;\n    }\n    DKCommonDTOModel *commonDTOModel = [[DKCommonDTOModel alloc] init];\n    commonDTOModel.requestId = nil;\n    commonDTOModel.deviceType = DK_DEVICE_TYPE;\n    commonDTOModel.data = message;\n    commonDTOModel.method = DK_WEBSOCKET_BROADCAST;\n    commonDTOModel.connectSerial = self.webSocketSession.sessionUUID;\n    commonDTOModel.dataType = DK_ACTION;\n    NSError *error = nil;\n    NSDictionary *jsonDictionary = [MTLJSONAdapter JSONDictionaryFromModel:commonDTOModel error:&error];\n    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary ?: @{} options:0 error:&error];\n    NSString *dataString = nil;\n    if (jsonData) {\n        dataString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n    }\n    if (!dataString) {\n        return;\n    }\n    [self.webSocketSession sendString:dataString requestId:nil completionHandler:nil];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DKQRCodeScanLogic.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <Foundation/Foundation.h>\n\n@class AVCaptureVideoPreviewLayer;\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKQRCodeScanLogic : NSObject\n\n@property(nonatomic, nullable, copy) void (^completionBlock)(NSString *_Nullable decodedString);\n\n- (void)startScanQRCodeWithCompletionBlock:(void (^)(AVCaptureVideoPreviewLayer *_Nullable captureVideoPreviewLayer))completionBlock;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DKQRCodeScanLogic.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <AVFoundation/AVFoundation.h>\n#import \"DKQRCodeScanLogic.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\nstatic void requestVideoGrant(void (^completionBlock)(BOOL isGranted));\n\nstatic inline void safeMainThread(dispatch_block_t block);\n\n@interface DKQRCodeScanLogic () <AVCaptureMetadataOutputObjectsDelegate>\n\n@property(nonatomic, nullable, strong) AVCaptureSession *captureSession;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\nvoid requestVideoGrant(void (^completionBlock)(BOOL isGranted)) {\n    AVAuthorizationStatus authorizationStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];\n    if (authorizationStatus == AVAuthorizationStatusNotDetermined) {\n        // Request system authority.\n        [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {\n            safeMainThread(^{\n                completionBlock(granted);\n            });\n        }];\n    } else {\n        completionBlock(authorizationStatus == AVAuthorizationStatusAuthorized);\n    }\n}\n\nvoid safeMainThread(dispatch_block_t block) {\n    if (!NSThread.isMainThread) {\n        dispatch_async(dispatch_get_main_queue(), block);\n    } else {\n        block();\n    }\n}\n\n@implementation DKQRCodeScanLogic\n\n- (void)startScanQRCodeWithCompletionBlock:(void (^)(AVCaptureVideoPreviewLayer *_Nullable captureVideoPreviewLayer))completionBlock {\n    __weak typeof(self) weakSelf = self;\n    requestVideoGrant(^(BOOL isGranted) {\n        if (!weakSelf) {\n            completionBlock(nil);\n\n            return;\n        }\n        typeof(weakSelf) self = weakSelf;\n        if (!isGranted) {\n            completionBlock(nil);\n        } else {\n            AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];\n\n            NSCAssert(captureDevice, @\"+[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo] return nil.\");\n\n            NSError *error = nil;\n            AVCaptureDeviceInput *captureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:captureDevice error:&error];\n\n            NSCAssert(!error && captureDeviceInput, @\"AVCaptureDeviceInput creation is failed.\");\n\n            AVCaptureMetadataOutput *captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init];\n            [captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];\n\n            self.captureSession = [[AVCaptureSession alloc] init];\n            [self.captureSession beginConfiguration];\n\n            NSCAssert([self.captureSession canAddInput:captureDeviceInput], @\"-[captureSession canAddInput:captureDeviceInput] is failed.\");\n            [self.captureSession addInput:captureDeviceInput];\n\n            NSCAssert([self.captureSession canAddOutput:captureMetadataOutput], @\"-[AVCaptureSession canAddOutput:captureMetadataOutput] return NO.\");\n            [self.captureSession addOutput:captureMetadataOutput];\n            if ([captureMetadataOutput.availableMetadataObjectTypes containsObject:AVMetadataObjectTypeQRCode]) {\n                captureMetadataOutput.metadataObjectTypes = @[AVMetadataObjectTypeQRCode];\n            }\n            [self.captureSession commitConfiguration];\n\n            AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];\n            completionBlock(captureVideoPreviewLayer);\n\n            [self.captureSession startRunning];\n        }\n    });\n}\n\n- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection {\n    if (metadataObjects.count > 0) {\n        AVMetadataObject *metadataObject = metadataObjects.firstObject;\n        if ([metadataObject.type isEqualToString:AVMetadataObjectTypeQRCode] && [metadataObject isKindOfClass:AVMetadataMachineReadableCodeObject.class]) {\n            [self.captureSession stopRunning];\n            AVMetadataMachineReadableCodeObject *metadataMachineReadableCodeObject = (AVMetadataMachineReadableCodeObject *) metadataObject;\n            self.completionBlock ? self.completionBlock(metadataMachineReadableCodeObject.stringValue) : (void) nil;\n        } else {\n            goto exit;\n        }\n    } else {\n        goto exit;\n    }\n\n    return;\n\n    exit:\n    self.completionBlock ? self.completionBlock(nil) : (void) nil;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DKQRCodeScanView.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\ntypedef NS_ENUM(NSUInteger, DKQRCodeScanResult) {\n    DKQRCodeScanResultSuccess = 0,\n    DKQRCodeScanResultAuthorityError\n};\n\n@interface DKQRCodeScanView : UIView\n\n- (void)startScanQRCodeWithCompletionBlock:(void (^)(DKQRCodeScanResult qrCodeScanResult, NSString *_Nullable decodedString))completionBlock;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DKQRCodeScanView.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import \"DKQRCodeScanView.h\"\n#import <DoraemonKit/DKQRCodeScanLogic.h>\n#import <AVFoundation/AVFoundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKQRCodeScanView ()\n\n@property(nonatomic, strong, nullable) DKQRCodeScanLogic *qrCodeScanLogic;\n\n@property(nonatomic, weak, nullable) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;\n\n- (void)commonInit;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n@implementation DKQRCodeScanView\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    [self commonInit];\n\n    return self;\n}\n\n- (instancetype)initWithCoder:(NSCoder *)coder {\n    self = [super initWithCoder:coder];\n    if (self) {\n        [self commonInit];\n    }\n\n    return self;\n}\n\n- (void)commonInit {\n    self.backgroundColor = UIColor.blackColor;\n}\n\n- (void)layoutSubviews {\n    [super layoutSubviews];\n\n    self.captureVideoPreviewLayer.frame = self.bounds;\n}\n\n- (void)startScanQRCodeWithCompletionBlock:(void (^)(DKQRCodeScanResult, NSString *))completionBlock {\n    self.qrCodeScanLogic = [[DKQRCodeScanLogic alloc] init];\n    __weak typeof(self) weakSelf = self;\n    __weak typeof(self.qrCodeScanLogic) weakQRCodeScanLogic = self.qrCodeScanLogic;\n    [self.qrCodeScanLogic startScanQRCodeWithCompletionBlock:^(AVCaptureVideoPreviewLayer *_Nullable captureVideoPreviewLayer) {\n        if (!weakSelf || !captureVideoPreviewLayer) {\n            completionBlock(DKQRCodeScanResultAuthorityError, nil);\n        } else {\n            typeof(weakSelf) self = weakSelf;\n            typeof(weakQRCodeScanLogic) qrCodeScanLogic = weakQRCodeScanLogic;\n            qrCodeScanLogic.completionBlock = ^(NSString *_Nullable decodedString) {\n                completionBlock(DKQRCodeScanResultSuccess, decodedString);\n            };\n            captureVideoPreviewLayer.frame = self.bounds;\n            [self.layer addSublayer:captureVideoPreviewLayer];\n        }\n    }];\n}\n\n/*\n// Only override drawRect: if you perform custom drawing.\n// An empty implementation adversely affects performance during animation.\n- (void)drawRect:(CGRect)rect {\n    // Drawing code\n}\n*/\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DKQRCodeScanViewController.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKQRCodeScanViewController : UIViewController\n\n@property(nonatomic, nullable, copy) void (^completionBlock)(NSString *_Nullable decodedString);\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DKQRCodeScanViewController.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import \"DKQRCodeScanViewController.h\"\n#import <DoraemonKit/DKQRCodeScanView.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\nstatic inline void removeViewController(UIViewController *viewController, BOOL isAnimated);\n\n@interface DKQRCodeScanViewController ()\n\n@property(nonatomic, assign) BOOL originTranslucent;\n\n@property(nonatomic, assign) BOOL originNavigationBarIsHidden;\n\n@property(nonatomic, nullable, weak) DKQRCodeScanView *qrCodeScanView;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\nvoid removeViewController(UIViewController *viewController, BOOL isAnimated) {\n\n    NSCAssert(viewController.navigationController.viewControllers.count > 1, @\"viewController.navigationController.viewControllers.count <= 1.\");\n    // No COW.\n    NSMutableArray<__kindof UIViewController *> *viewControllerArray = viewController.navigationController.viewControllers.mutableCopy;\n    [viewControllerArray removeObject:viewController];\n    [viewController.navigationController setViewControllers:viewControllerArray.copy animated:isAnimated];\n}\n\n@implementation DKQRCodeScanViewController\n\n- (void)viewWillAppear:(BOOL)animated {\n    [super viewWillAppear:animated];\n    self.originNavigationBarIsHidden = self.navigationController.navigationBarHidden;\n    self.originTranslucent = self.navigationController.navigationBar.translucent;\n    if (self.navigationController.navigationBar.translucent) {\n        self.navigationController.navigationBar.translucent = NO;\n    }\n    if (self.navigationController.navigationBarHidden) {\n        [self.navigationController setNavigationBarHidden:NO animated:animated];\n    }\n}\n\n- (void)viewWillDisappear:(BOOL)animated {\n    [super viewWillDisappear:animated];\n    self.navigationController.navigationBar.translucent = self.originTranslucent;\n    [self.navigationController setNavigationBarHidden:self.originNavigationBarIsHidden animated:animated];\n}\n\n- (void)viewDidAppear:(BOOL)animated {\n    [super viewDidAppear:animated];\n    \n    __weak typeof(self) weakSelf = self;\n    [self.qrCodeScanView startScanQRCodeWithCompletionBlock:^(DKQRCodeScanResult qrCodeScanResult, NSString *decodedString) {\n        if (!weakSelf) {\n            return;\n        }\n        typeof(weakSelf) self = weakSelf;\n        void (^completionBlock)(NSString *_Nullable decodedString) = ^(NSString *_Nullable decodedString) {\n            UIViewController *efficientViewContainer = nil;\n            UIViewController *currentViewController = self;\n            while (!efficientViewContainer) {\n                if ([currentViewController.parentViewController isKindOfClass:UINavigationController.class] && currentViewController.navigationController.viewControllers.count > 1) {\n                    efficientViewContainer = currentViewController.navigationController;\n                } else if (currentViewController.parentViewController) {\n                    currentViewController = currentViewController.parentViewController;\n                } else if (currentViewController && currentViewController.presentingViewController.presentedViewController == currentViewController) {\n                    efficientViewContainer = currentViewController.presentingViewController;\n                } else {\n\n                    NSCAssert(NO, @\"currentViewController hasn't container.\");\n                    break;\n                }\n            }\n            if ([efficientViewContainer isKindOfClass:UINavigationController.class]) {\n                removeViewController(currentViewController, YES);\n            } else if (efficientViewContainer) {\n                [currentViewController dismissViewControllerAnimated:YES completion:nil];\n            }\n            typeof(weakSelf) self = weakSelf;\n            self.completionBlock ? self.completionBlock(decodedString) : (void) nil;\n        };\n        if (qrCodeScanResult == DKQRCodeScanResultAuthorityError) {\n            UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@\"相机权限未开启，请到「设置-隐私-相机」中允许访问您的相机\" message:nil preferredStyle:UIAlertControllerStyleAlert];\n            UIAlertAction *openAlertAction = [UIAlertAction actionWithTitle:@\"去开启\" style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull __attribute__((unused)) action) {\n                typeof(weakSelf) self = weakSelf;\n                completionBlock(nil);\n                NSURL *settingUrl = [NSURL URLWithString:UIApplicationOpenSettingsURLString];\n                if (@available(iOS 13.0, *)) {\n                    [self.view.window.windowScene openURL:settingUrl options:nil completionHandler:nil];\n                } else if (@available(iOS 10.0, *)) {\n                    [UIApplication.sharedApplication openURL:settingUrl options:@{} completionHandler:nil];\n                } else {\n                    if ([UIApplication.sharedApplication canOpenURL:settingUrl]) {\n                        [UIApplication.sharedApplication openURL:settingUrl];\n                    }\n                }\n            }];\n            UIAlertAction *cancelAlertAction = [UIAlertAction actionWithTitle:@\"暂不开启\" style:UIAlertActionStyleCancel handler:^(UIAlertAction *_Nonnull __attribute__((unused)) action) {\n                completionBlock(nil);\n            }];\n            [alertController addAction:openAlertAction];\n            [alertController addAction:cancelAlertAction];\n            [self presentViewController:alertController animated:YES completion:nil];\n        } else {\n            completionBlock(decodedString);\n        }\n    }];\n}\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    // Do any additional setup after loading the view.\n    self.title = @\"二维码扫描\";\n    DKQRCodeScanView *qrCodeScanView = [[DKQRCodeScanView alloc] initWithFrame:self.view.bounds];\n    [self.view addSubview:qrCodeScanView];\n    self.qrCodeScanView = qrCodeScanView;\n}\n\n- (void)viewDidLayoutSubviews {\n    [super viewDidLayoutSubviews];\n\n    self.qrCodeScanView.frame = self.view.bounds;\n}\n\n/*\n#pragma mark - Navigation\n\n// In a storyboard-based application, you will often want to do a little preparation before navigation\n- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {\n    // Get the new view controller using [segue destinationViewController].\n    // Pass the selected object to the new view controller.\n}\n*/\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DKWebSocketSession.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <Foundation/Foundation.h>\n\n@class DKCommonDTOModel;\n\nNS_ASSUME_NONNULL_BEGIN\n\nextern NSString *DK_WEBSOCKET_BROADCAST;\n\ntypedef void (^DKWebSocketCompletionHandler)(NSError *_Nullable error, NSString *_Nullable responseString);\n\n@interface DKWebSocketSession : NSObject\n\n@property(nullable, readonly, nonatomic, copy) NSUUID *sessionUUID;\n\n@property(nonatomic, assign) unsigned int requestId;\n\n@property(nonatomic, nullable, copy) void (^notifyHandler)(DKCommonDTOModel *commonDTOModel);\n\n- (instancetype)init NS_UNAVAILABLE;\n\n- (instancetype)initWithUrl:(NSURL *)url NS_DESIGNATED_INITIALIZER;\n\n- (void)sendString:(NSString *)string requestId:(nullable NSNumber *)requestId completionHandler:(nullable DKWebSocketCompletionHandler)completionHandler;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DKWebSocketSession.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import \"DKWebSocketSession.h\"\n#import <DoraemonKit/DKCommonDTOModel.h>\n#import <DoraemonKit/DKLoginDataDTOModel.h>\n#import <SocketRocket/SRWebSocket.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\nNSString *DK_WEBSOCKET_BROADCAST = @\"BROADCAST\";\n\ntypedef void (^DKWebSocketRequestBlock)(NSError *_Nullable error, DKWebSocketSession *_Nullable webSocketSession);\n\nstatic NSString *const DOKIT_WEBSOCKET_SESSION = @\"DOKIT_WEBSOCKET_SESSION\";\n\nstatic NSString *const DEVICE_AUTHENTICATION_ERROR = @\"Device authentication json error.\";\n\nstatic NSString *const JSON_SERIALIZATION_ERROR = @\"Dictionary to json error.\";\n\n@interface DKWebSocketSession () <SRWebSocketDelegate>\n\n/// Is Open/Connecting state.\n@property(readonly) BOOL isWebSocketRunning;\n\n/// Current is DeviceAuthentication state.\n@property(nonatomic, assign) BOOL isDeviceAuthenticating;\n\n@property(nonatomic, copy) NSURL *url;\n\n@property(nonatomic, nullable, weak) SRWebSocket *webSocket;\n\n@property(nonatomic, nullable, copy) NSUUID *sessionUUID;\n\n@property(nonatomic, nullable, copy) NSArray<DKWebSocketRequestBlock> *deferRequestQueue;\n\n@property(nonatomic, nullable, copy) NSDictionary<NSNumber *, DKWebSocketCompletionHandler> *completionHandlerDictionary;\n\n/// Make WebSocket connection available.\n- (void)connect;\n\n- (void)deviceAuthentication;\n\n- (void)handleWebSocketWithError:(nullable NSError *)error;\n\n- (void)addWithRequestId:(unsigned int)requestId webSocketCompletionHandler:(DKWebSocketCompletionHandler)webSocketCompletionHandler;\n\n- (void)handleWithDeviceAuthenticationError:(nullable NSError *)deviceAuthenticationError;\n\n- (void)sendDeviceAuthenticationRequest;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n@implementation DKWebSocketSession\n\n- (void)sendDeviceAuthenticationRequest {\n    NSAssert(self.isDeviceAuthenticating, @\"State error.\");\n    DKLoginDataDTOModel *loginDataDTOModel = [[DKLoginDataDTOModel alloc] init];\n    loginDataDTOModel.manufacturer = @\"Apple\";\n    // Load previous UUID.\n    loginDataDTOModel.connectSerial = self.sessionUUID;\n    NSError *error = nil;\n    NSDictionary *jsonDictionary = [MTLJSONAdapter JSONDictionaryFromModel:loginDataDTOModel error:&error];\n\n    NSAssert(!error, DEVICE_AUTHENTICATION_ERROR);\n    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary ?: @{} options:0 error:&error];\n\n    NSAssert(!error, JSON_SERIALIZATION_ERROR);\n    NSString *dataString = nil;\n    if (jsonData) {\n        dataString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n    }\n    DKCommonDTOModel *commonDTOModel = [[DKCommonDTOModel alloc] init];\n    commonDTOModel.requestId = @(self.requestId++);\n    commonDTOModel.deviceType = DK_DEVICE_TYPE;\n    commonDTOModel.data = dataString;\n    commonDTOModel.method = DK_METHOD_LOGIN;\n    commonDTOModel.connectSerial = self.sessionUUID;\n    commonDTOModel.dataType = nil;\n    jsonDictionary = [MTLJSONAdapter JSONDictionaryFromModel:commonDTOModel error:&error];\n\n    NSAssert(!error, DEVICE_AUTHENTICATION_ERROR);\n    jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary ?: @{} options:0 error:&error];\n\n    NSAssert(!error, JSON_SERIALIZATION_ERROR);\n    dataString = nil;\n    if (jsonData) {\n        dataString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n    }\n    [self.webSocket sendString:dataString error:nil];\n    // Add completionHandler to responseQueue.\n    __weak typeof(self) weakSelf = self;\n    DKWebSocketCompletionHandler webSocketCompletionHandler = ^(NSError *_Nullable error, NSString *_Nullable dataString) {\n        id jsonObject = [NSJSONSerialization JSONObjectWithData:[(dataString ?: @\"\") dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&error];\n        typeof(weakSelf) self = weakSelf;\n        if (![jsonObject isKindOfClass:NSDictionary.class]) {\n            [self handleWithDeviceAuthenticationError:error];\n\n            return;\n        }\n        DKLoginDataDTOModel *loginDataDTOModel = [MTLJSONAdapter modelOfClass:DKLoginDataDTOModel.class fromJSONDictionary:jsonObject error:&error];\n        self.sessionUUID = loginDataDTOModel.connectSerial;\n\n        self.isDeviceAuthenticating = NO;\n        // Trigger deferred request.\n        [self.deferRequestQueue enumerateObjectsUsingBlock:^(DKWebSocketRequestBlock obj, NSUInteger __attribute__((unused)) idx, BOOL *__attribute__((unused)) stop) {\n            obj(nil, self);\n        }];\n        self.deferRequestQueue = nil;\n    };\n    [self addWithRequestId:commonDTOModel.requestId.unsignedIntValue webSocketCompletionHandler:webSocketCompletionHandler];\n}\n\n- (void)addWithRequestId:(unsigned int)requestId webSocketCompletionHandler:(DKWebSocketCompletionHandler)webSocketCompletionHandler {\n    NSMutableDictionary<NSNumber *, DKWebSocketCompletionHandler> *completionHandlerDictionary = self.completionHandlerDictionary.mutableCopy;\n    self.completionHandlerDictionary = nil;\n    if (!completionHandlerDictionary) {\n        completionHandlerDictionary = NSMutableDictionary.dictionary;\n    }\n    completionHandlerDictionary[@(requestId)] = webSocketCompletionHandler;\n    self.completionHandlerDictionary = completionHandlerDictionary;\n}\n\n- (BOOL)isWebSocketRunning {\n    return _webSocket && (_webSocket.readyState == SR_CONNECTING || _webSocket.readyState == SR_OPEN);\n}\n\n- (void)handleWithDeviceAuthenticationError:(nullable NSError *)deviceAuthenticationError {\n    // Enumerate all request to tell that an error is happened.\n    [self.deferRequestQueue enumerateObjectsUsingBlock:^(DKWebSocketRequestBlock obj, NSUInteger __attribute__((unused)) idx, BOOL *__attribute__((unused)) stop) {\n        obj(deviceAuthenticationError, self);\n    }];\n    self.deferRequestQueue = nil;\n    self.isDeviceAuthenticating = NO;\n}\n\n- (instancetype)initWithUrl:(NSURL *)url {\n    self = [super init];\n    _url = url.copy;\n    _isDeviceAuthenticating = NO;\n    _requestId = 0;\n    id webSocketSession = [NSUserDefaults.standardUserDefaults objectForKey:DOKIT_WEBSOCKET_SESSION];\n    if ([webSocketSession isKindOfClass:NSString.class]) {\n        _sessionUUID = [[NSUUID alloc] initWithUUIDString:webSocketSession];\n    }\n    [self deviceAuthentication];\n\n    return self;\n}\n\n- (void)connect {\n    if (self.isWebSocketRunning) {\n        return;\n    }\n    SRWebSocket *webSocket = [[SRWebSocket alloc] initWithURL:_url];\n    _webSocket = webSocket;\n    webSocket.delegate = self;\n    [webSocket open];\n}\n\n- (void)deviceAuthentication {\n    [self connect];\n    if (_isDeviceAuthenticating) {\n        return;\n    }\n    _isDeviceAuthenticating = YES;\n    if (_webSocket.readyState == SR_OPEN) {\n        // SR_CONNECTING, defer request.\n        [self sendDeviceAuthenticationRequest];\n    }\n}\n\n- (void)sendString:(NSString *)string requestId:(NSNumber *)requestId completionHandler:(nullable DKWebSocketCompletionHandler)completionHandler {\n    if (!self.isWebSocketRunning || self.isDeviceAuthenticating) {\n        if (!self.isWebSocketRunning) {\n            [self deviceAuthentication];\n        }\n        // Add to deferRequestQueue.\n        DKWebSocketRequestBlock webSocketRequestBlock = ^(NSError *_Nullable error, DKWebSocketSession *_Nullable webSocketSession) {\n            if (error || !webSocketSession) {\n                completionHandler ? completionHandler(error, nil) : (void) nil;\n\n                return;\n            }\n            [webSocketSession.webSocket sendString:string error:nil];\n            if (requestId && completionHandler) {\n                [webSocketSession addWithRequestId:requestId.unsignedIntValue webSocketCompletionHandler:completionHandler];\n            }\n        };\n        NSMutableArray<DKWebSocketRequestBlock> *deferRequestQueue = self.deferRequestQueue.mutableCopy;\n        self.deferRequestQueue = nil;\n        if (!deferRequestQueue) {\n            deferRequestQueue = NSMutableArray.array;\n        }\n        [deferRequestQueue addObject:webSocketRequestBlock];\n        self.deferRequestQueue = deferRequestQueue;\n    } else {\n        // Send request.\n        [self.webSocket sendString:string error:nil];\n        if (requestId && completionHandler) {\n            [self addWithRequestId:requestId.unsignedIntValue webSocketCompletionHandler:completionHandler];\n        }\n    }\n}\n\n- (void)setSessionUUID:(NSUUID *)sessionUUID {\n    _sessionUUID = sessionUUID.copy;\n    [NSUserDefaults.standardUserDefaults setObject:sessionUUID.UUIDString forKey:DOKIT_WEBSOCKET_SESSION];\n}\n\n- (void)dealloc {\n    [_webSocket close];\n}\n\n- (void)handleWebSocketWithError:(nullable NSError *)error {\n    if (self.isDeviceAuthenticating) {\n        [self handleWithDeviceAuthenticationError:error];\n        self.completionHandlerDictionary = nil;\n    } else {\n        [self.completionHandlerDictionary enumerateKeysAndObjectsUsingBlock:^(NSNumber *__attribute__((unused)) key, DKWebSocketCompletionHandler obj, BOOL *__attribute__((unused)) stop) {\n            obj(error, nil);\n        }];\n        self.completionHandlerDictionary = nil;\n    }\n}\n\n#pragma mark - SRWebSocketDelegate\n\n- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithString:(NSString *)string {\n    NSError *error = nil;\n    id jsonObject = [NSJSONSerialization JSONObjectWithData:[(string ?: @\"\") dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&error];\n    if (![jsonObject isKindOfClass:NSDictionary.class]) {\n        return;\n    }\n    DKCommonDTOModel *commonDTOModel = [MTLJSONAdapter modelOfClass:DKCommonDTOModel.class fromJSONDictionary:jsonObject error:&error];\n    if ([commonDTOModel.method isEqualToString:DK_WEBSOCKET_BROADCAST]) {\n        self.notifyHandler ? self.notifyHandler(commonDTOModel) : (void) nil;\n    } else if (commonDTOModel.requestId) {\n        DKWebSocketCompletionHandler webSocketCompletionHandler = self.completionHandlerDictionary[commonDTOModel.requestId];\n        if (webSocketCompletionHandler) {\n            NSMutableDictionary<NSNumber *, DKWebSocketCompletionHandler> *completionHandlerDictionary = self.completionHandlerDictionary.mutableCopy;\n            self.completionHandlerDictionary = nil;\n            completionHandlerDictionary[commonDTOModel.requestId] = nil;\n            if (completionHandlerDictionary.count == 0) {\n                completionHandlerDictionary = nil;\n            }\n            self.completionHandlerDictionary = completionHandlerDictionary;\n            webSocketCompletionHandler(nil, commonDTOModel.data);\n        }\n    }\n}\n\n- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {\n    [self handleWebSocketWithError:error];\n}\n\n- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {\n    [self handleWebSocketWithError:nil];\n}\n\n- (void)webSocketDidOpen:(SRWebSocket *)webSocket {\n    if (self.isDeviceAuthenticating) {\n        [self sendDeviceAuthenticationRequest];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DTO/DKActionDTOModel.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <Mantle/Mantle.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKActionDTOModel : MTLModel <MTLJSONSerializing>\n\n@property(nonatomic, nullable, copy) NSString *behaviorId;\n\n@property(nonatomic, nullable, copy) NSString *payload;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DTO/DKActionDTOModel.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import \"DKActionDTOModel.h\"\n\n@implementation DKActionDTOModel\n\n+ (NSDictionary *)JSONKeyPathsByPropertyKey {\n    return @{\n            @\"behaviorId\": @\"behaviorId\",\n            @\"payload\": @\"payload\"\n    };\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DTO/DKCommonDTOModel.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <Mantle/Mantle.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\nextern NSString *DK_DEVICE_TYPE;\n\nextern NSString *DK_METHOD_LOGIN;\n\nextern NSString *DK_METHOD_DATA;\n\nextern NSString *DK_DATA_REQUEST;\n\nextern NSString *DK_DATA_RESPONSE;\n\nextern NSString *DK_DATA_QUERY;\n\nextern NSString *DK_ACTION;\n\nextern NSString *DK_TCP;\n\n@interface DKCommonDTOModel : MTLModel <MTLJSONSerializing>\n\n@property(nonatomic, nullable, copy) NSNumber *requestId;\n\n/// Main type.\n@property(nonatomic, nullable, copy) NSString *method;\n\n@property(nonatomic, nullable, copy) NSString *data;\n\n@property(nonatomic, nullable, copy) NSUUID *connectSerial;\n\n@property(nonatomic, nullable, copy) NSString *deviceType;\n\n// Subtype.\n@property(nonatomic, nullable, copy) NSString *dataType;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DTO/DKCommonDTOModel.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import \"DKCommonDTOModel.h\"\n\nNSString *DK_DEVICE_TYPE = @\"ios\";\n\nNSString *DK_METHOD_LOGIN = @\"LOGIN\";\n\nNSString *DK_METHOD_DATA = @\"DATA\";\n\nNSString *DK_DATA_REQUEST = @\"request\";\n\nNSString *DK_DATA_RESPONSE = @\"response\";\n\nNSString *DK_DATA_QUERY = @\"query\";\n\nNSString *DK_ACTION = @\"action\";\n\nNSString *DK_TCP = @\"tcp\";\n\n@implementation DKCommonDTOModel\n\n+ (NSDictionary *)JSONKeyPathsByPropertyKey {\n    return @{\n            @\"requestId\": @\"pid\",\n            @\"method\": @\"type\",\n            @\"data\": @\"data\",\n            @\"connectSerial\": @\"connectSerial\",\n            @\"dataType\": @\"contentType\",\n            @\"deviceType\": @\"channelSerial\"\n    };\n}\n\n+ (NSValueTransformer *)connectSerialJSONTransformer {\n    return [MTLValueTransformer\n            transformerUsingForwardBlock:^id(NSString *string, BOOL *success, NSError **__attribute__((unused)) error) {\n                if (string == nil) return nil;\n\n                if (![string isKindOfClass:NSString.class]) {\n                    *success = NO;\n                    return nil;\n                }\n\n                NSUUID *result = [[NSUUID alloc] initWithUUIDString:string];\n\n                if (result == nil) {\n                    *success = NO;\n                    return nil;\n                }\n\n                return result;\n            }\n                            reverseBlock:^id(NSUUID *uuid, BOOL *success, NSError **__attribute__((unused)) error) {\n                                if (uuid == nil) return nil;\n\n                                if (![uuid isKindOfClass:NSUUID.class]) {\n                                    *success = NO;\n                                    return nil;\n                                }\n\n                                return uuid.UUIDString.localizedLowercaseString;\n                            }];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DTO/DKDataRequestDTOModel.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <Mantle/Mantle.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKDataRequestDTOModel : MTLModel <MTLJSONSerializing>\n\n@property(nonatomic, nullable, copy) NSString *behaviorId;\n\n/// query will set dataId to nil.\n@property(nonatomic, nullable, copy) NSString *dataId;\n\n@property(nonatomic, nullable, copy) NSString *searchId;\n\n@property(nonatomic, nullable, copy) NSString *method;\n\n@property(nonatomic, nullable, copy) NSURL *url;\n\n@property(nonatomic, nullable, copy) NSDictionary<NSString *, NSString *> *requestHeader;\n\n@property(nonatomic, nullable, copy) NSString *requestBody;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DTO/DKDataRequestDTOModel.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import \"DKDataRequestDTOModel.h\"\n\n@implementation DKDataRequestDTOModel\n\n+ (NSDictionary *)JSONKeyPathsByPropertyKey {\n    return @{\n            @\"behaviorId\": @\"aid\",\n            @\"dataId\": @\"did\",\n            @\"searchId\": @\"searchKey\",\n            @\"method\": @\"method\",\n            @\"url\": @\"url\",\n            @\"requestHeader\": @\"requestHeader\",\n            @\"requestBody\": @\"requestBody\"\n    };\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DTO/DKDataResponseDTOModel.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <Mantle/Mantle.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKDataResponseDTOModel : MTLModel <MTLJSONSerializing>\n\n@property(nonatomic, nullable, copy) NSString *dataId;\n\n@property(nonatomic, assign) NSInteger responseCode;\n\n@property(nonatomic, nullable, copy) NSDictionary<NSString *, NSString *> *responseHeader;\n\n@property(nonatomic, nullable, copy) NSString *responseBody;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DTO/DKDataResponseDTOModel.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import \"DKDataResponseDTOModel.h\"\n\n@implementation DKDataResponseDTOModel\n\n+ (NSDictionary *)JSONKeyPathsByPropertyKey {\n    return @{\n            @\"dataId\": @\"did\",\n            @\"responseCode\": @\"responseCode\",\n            @\"responseHeader\": @\"responseHeader\",\n            @\"responseBody\": @\"responseBody\"\n    };\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DTO/DKLoginDataDTOModel.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <Mantle/Mantle.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKLoginDataDTOModel : MTLModel <MTLJSONSerializing>\n\n@property(nonatomic, nullable, copy) NSString *manufacturer;\n\n@property(nonatomic, nullable, copy) NSUUID *connectSerial;\n\n+ (nullable NSValueTransformer *)connectSerialJSONTransformer;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/DTO/DKLoginDataDTOModel.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import \"DKLoginDataDTOModel.h\"\n\n@implementation DKLoginDataDTOModel\n\n+ (NSDictionary *)JSONKeyPathsByPropertyKey {\n    return @{\n            @\"manufacturer\": @\"manufacturer\",\n            @\"connectSerial\": @\"connectSerial\"\n    };\n}\n\n+ (NSValueTransformer *)connectSerialJSONTransformer {\n    return [MTLValueTransformer\n            transformerUsingForwardBlock:^id(NSString *string, BOOL *success, NSError **__attribute__((unused)) error) {\n                if (string == nil) return nil;\n\n                if (![string isKindOfClass:NSString.class]) {\n                    *success = NO;\n                    return nil;\n                }\n\n                NSUUID *result = [[NSUUID alloc] initWithUUIDString:string];\n\n                if (result == nil) {\n                    *success = NO;\n                    return nil;\n                }\n\n                return result;\n            }\n                            reverseBlock:^id(NSUUID *uuid, BOOL *success, NSError **__attribute__((unused)) error) {\n                                if (uuid == nil) return nil;\n\n                                if (![uuid isKindOfClass:NSUUID.class]) {\n                                    *success = NO;\n                                    return nil;\n                                }\n\n                                return uuid.UUIDString.localizedLowercaseString;\n                            }];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/NSURLSessionConfiguration+DoKit.h",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface NSURLSessionConfiguration (DoKit)\n\n+ (NSURLSessionConfiguration *)dk_defaultSessionConfiguration;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoKit/Classes/Foundation/NSURLSessionConfiguration+DoKit.m",
    "content": "/**\n * Copyright 2017 Beijing DiDi Infinity Technology and Development Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#import <objc/runtime.h>\n#import \"NSURLSessionConfiguration+DoKit.h\"\n#import <DoraemonKit/DKMultiControlProtocol.h>\n\n@implementation NSURLSessionConfiguration (DoKit)\n\n+ (void)load {\n    static dispatch_once_t dispatchOnce;\n    dispatch_once(&dispatchOnce, ^{\n        SEL originalSelector = @selector(defaultSessionConfiguration);\n        SEL swizzledSelector = @selector(dk_defaultSessionConfiguration);\n        \n        Method originalMethod = class_getClassMethod(self.class, originalSelector);\n        Method swizzledMethod = class_getClassMethod(self.class, swizzledSelector);\n        \n        if (originalMethod && swizzledMethod) {\n            method_exchangeImplementations(originalMethod, swizzledMethod);\n        }\n    });\n}\n\n+ (NSURLSessionConfiguration *)dk_defaultSessionConfiguration {\n    NSURLSessionConfiguration *defaultSessionConfiguration = self.dk_defaultSessionConfiguration;\n    NSMutableArray<Class> *protocolClass = defaultSessionConfiguration.protocolClasses.mutableCopy;\n    defaultSessionConfiguration.protocolClasses = nil;\n    if (!protocolClass) {\n        protocolClass = NSMutableArray.array;\n    }\n    [protocolClass insertObject:DKMultiControlProtocol.class atIndex:0];\n    defaultSessionConfiguration.protocolClasses = protocolClass;\n    \n    return defaultSessionConfiguration;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Framework/DoraemonLoadAnalyze.framework/Headers/DoraemonLoadAnalyze.h",
    "content": "//\n//  DoraemonLoadAnalyze.h\n//  DoraemonLoadAnalyze\n//\n//  Created by yixiang on 2019/1/2.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\n//在控制台打印load耗时\nFOUNDATION_EXPORT void printLoadAnalyzeInfo(void);\n\nextern NSMutableArray<NSDictionary *> *dlaLoadModels;\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Framework/DoraemonLoadAnalyze.framework/Modules/module.modulemap",
    "content": "framework module DoraemonLoadAnalyze {\n  umbrella header \"DoraemonLoadAnalyze.h\"\n\n  export *\n  module * { export * }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/dk_icon_mc.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"dk_icon_mc.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/dk_mc_banner.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"dk_mc_banner.jpg\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_align.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_align@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_align@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_app_info.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_app_info@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_app_info@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_app_start_time.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_app_start_time@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_app_start_time@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_arrow_down.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_arrow_down@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_arrow_down@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_back.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_back@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_back@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_back_dark.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_back_dark@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_back_dark@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_check_circle.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_check_circle@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_check_circle@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_check_circle_fill.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_check_circle_fill@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_check_circle_fill@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_close.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_close@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_close@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_close_dark.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_close_dark@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_close_dark@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_close_white.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_close_white@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_close_white@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_cpu.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_cpu@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_cpu@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_crash.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_crash@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_crash@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_database.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_database@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_database@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_default.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_default@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_default@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_dir.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_dir@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_dir@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_expand.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_expand@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_expand@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_expand_no.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_expand_no@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_expand_no@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_file.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_file@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_file@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_file_2.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_file_2@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_file_2@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_file_sync.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_file_sync@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_file_sync@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_file_sync_banner.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_file_sync_banner@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_file_sync_banner@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_fps.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_fps@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_fps@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_h5.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_h5@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_h5@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_health.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_health@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_health@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_health_bg.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_health_bg@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_health_bg@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_health_cell_bg.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_health_cell_bg@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_health_cell_bg@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_health_end.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_health_end@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_health_end@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_health_slide.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_health_slide@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_health_slide@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_health_slideTop.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_health_slideTop@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_health_slideTop@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_health_start.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_health_start@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_health_start@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_hierarchy_info.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_hierarchy_info@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_hierarchy_info@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_hierarchy_parent.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_hierarchy_parent@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_hierarchy_parent@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_hierarchy_select.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_hierarchy_select@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_hierarchy_select@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_hierarchy_subview.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_hierarchy_subview@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_hierarchy_subview@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_js.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_js@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_js@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_kadun.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_kadun@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_kadun@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_location.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_location@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_location@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_log.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_log@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_log@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_logo.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"dk_doraemon.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_logo_dark.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_logo_dark@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_logo_dark@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_max.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_max@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_max@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_memory.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_memory@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_memory@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_memory_leak.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_memory_leak@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_memory_leak@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_method_use_time.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_method_use_time@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_method_use_time@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_min.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_min@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_min@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_mock.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_mock_cancle.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_cancle@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_cancle@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_mock_data.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_data@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_data@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_mock_data_selected.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_data_selected@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_data_selected@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_mock_detail_down.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_detail_down@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_detail_down@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_mock_detail_up.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_detail_up@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_detail_up@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_mock_filter_down.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_filter_down@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_filter_down@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_mock_filter_up.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_filter_up@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_filter_up@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_mock_gps.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_gps@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_gps@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_mock_selected.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_selected@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_selected@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_mock_up.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_up@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_up@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_mock_up_selected.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_up_selected@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_mock_up_selected@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_more.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_more@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_more@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_net.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_net@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_net@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_netflow_list_select.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_netflow_list_select@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_netflow_list_select@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_netflow_list_unselect.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_netflow_list_unselect@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_netflow_list_unselect@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_netflow_summary_select.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_netflow_summary_select@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_netflow_summary_select@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_netflow_summary_unselect.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_netflow_summary_unselect@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_netflow_summary_unselect@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_nslog.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_nslog@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_nslog@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_qingchu.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_qingchu@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_qingchu@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_scan.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_scan@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_scan@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_scan_line.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_scan_line@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_scan_line@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_search.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_search@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_search@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_search_highlight.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_search_highlight@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_search_highlight@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_self.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_self@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_self@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_setting.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_setting@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_setting@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_straw.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_straw@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_straw@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_time_profiler.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_time_profiler@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_time_profiler@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_ui.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_ui@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_ui@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_view_check.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_view_check@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_view_check@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_view_level.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_view_level@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_view_level@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_viewmetrics.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_viewmetrics@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_viewmetrics@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_visual.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_visual@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_visual@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/Assets.xcassets/doraemon_weaknet.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"doraemon_weaknet@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"doraemon_weaknet@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/en.lproj/Doraemon.strings",
    "content": "/*\n Doraemon.strings\n Pods\n \n Created by xgb on 2018/11/13.\n \n */\n\n{\n    //分组名称\n    \"业务工具\"                                      = \"Customize\";\n    \"平台工具\"                                      = \"Platform\";\n    \"常用工具\"                                      = \"Common\";\n    \"性能检测\"                                      = \"Performance\";\n    \"视觉工具\"                                      = \"UI\";\n    \"Weex\"                                         = \"Weex\";\n    \n    //每一个模块\n    \n    //数据Mock\n    \"Mock数据\"                                = \"Mock Data\";\n    \"上传模版\"                                 = \"Upload Data\";\n    \"接口分组\"                                 = \"Group\";\n    \"开关状态\"                                 = \"Status\";\n    \"分组: %@\\n\"                             = \"group : %@\\n\";\n    \"修改人: %@\\n\"                           = \"modified by : %@\\n\";\n    \"创建人: %@\\n\"                           = \"create by : %@\\n\";\n    \"数据预览\"                               = \"preview\";\n    \"上传\"                                  = \"upload\";\n    \"数据预览为空\"                            = \"data is empty !\";\n    \"上传成功\"                               = \"Upload Successfully\";\n    \"上传失败\"                               = \"Upload Failed\";\n    \"本地是否存在mock数据: %@\"                  = \"mock data exists locally: %@\";\n    \"存在\"                                  = \"exist\";\n    \"不存在\"                                 = \"not exist\";\n    \"App当前处于健康体检状态，无法进行此操作\"        = \"App is currently in the state of health check, so this operation cannot be carried out\";\n    \n    //健康体检\n    \"健康体检\"                                 = \"Health Check\";\n    \"点击开始检测\"                              = \"Click to start check\";\n    \"正在检测中...\"                            = \"Detecting ...\";\n    \"向下滑动查看功能使用说明\"                    = \"swipe down to see how to use the feature\";\n    \"回到顶部\"                                 = \"back to the top\";\n    \"提交成功\\n恭喜已完成检测！\"                  = \"Submitted successfully \\n Congratulations!\";\n    \"第%d步\"                                  = \"Step %d\";\n    \"是否确认\"                                 = \"Are you sure\";\n    \"结束前请完善下列信息\"                        = \"Please complete the following\";\n    \"测试用例名称\"                              = \"Test case name\";\n    \"测试人名称\"                               = \"Test person name\";\n    \"提交\"                                    = \"Submit\";\n    \"取消\"                                     = \"Cancel\";\n    \"丢弃\"                                     = \"Discard\";\n    \"数据上传成功\"                               = \"data upload successfully\";\n    \"数据上传失败\"                               = \"data upload failed\";\n    \n    //文件同步助手\n    \"文件同步\"                                 = \"File Sync\";\n    \"需要到www.dokit.cn上注册pId才能使用该功能\"    =  \"You need to register the pId on \\\"www.dokit.cn\\\" to use this function\";\n    \n    //应用设置\n    \"应用设置\"                               = \"App Settings\";\n    \n    //App信息\n    \"App信息\"                                =   \"App Info\";\n    \"手机信息\"                                =   \"Phone Info\";\n    \"设备名称\"                                = \"Device Name\";\n    \"手机型号\"                                =   \"Phone Model\";\n    \"系统版本\"                                =   \"System Version\";\n    \"手机屏幕\"                               = \"Phone Screen\";\n    \"权限信息\"                                =   \"Privacy Info\";\n    \"地理位置权限\"                          =   \"Location\";\n    \"网络权限\"                                =   \"Network\";\n    \"推送权限\"                                =   \"Push\";\n    \"相机权限\"                                =   \"Camera\";\n    \"麦克风权限\"                             =  \"Microphone\";\n    \"相册权限\"                                =   \"Photos\";\n    \"通讯录权限\"                             =   \"Contacts\";\n    \"日历权限\"                                =   \"Calendar\";\n    \"提醒事项权限\"                            =   \"Notes\";\n    \"用户没有选择\"                          =  \"Not Determined\";\n    \"用户已经授权\"                               = \"Authorized\";\n    \"用户没有授权\"                               = \"Denied\";\n    \"家长控制\"                                     = \"Restricted\";\n    \n    //沙盒浏览器\n    \"沙盒浏览器\"                             =  \"Sandbox\";\n    \"本地预览\"                                =   \"Preview\";\n    \"分享\"                                    = \"Share\";\n    \"请选择操作方式\"                       =   \"Choose Operation\";\n    \"返回上一级\"                             =   \"Back\";\n    \"根目录\"                                   =   \"Root Dir\";\n    \"文件不存在\"                             =   \"File not exist\";\n    \"数据库预览\"                                  = \"DB preview\";\n    \"文件预览\"                                =   \"File Preview\";\n    \n    //Mock GPS\n    \"Mock GPS\"                                     =  \"Mock GPS\";\n    \"打开Mock GPS\"                                   = \"Open Mock GPS\";\n    \"请输入经纬度\"                               = \"Please enter latitude and longitude\";\n    \"经纬度不能为空\"                       =  \"Please enter longitude and latitude\";\n    \"格式不正确\"                             =  \"Invalid format\";\n    \"(示例: 120.15 30.28)\"                           = \"(Like: 120.15 30.28)\";\n    \"经度不合法\"                             =   \"Invalid longitude\";\n    \"mock开关没有打开\"                      =   \"switch is not open\";\n    \"纬度不合法\"                             =   \"Invalid latitude\";\n    \n    //H5任意门\n    \"H5任意门\"                                 =  \"Browser\";\n    \"清除搜索历史\"                            = \"Clear search history\";\n    \"模拟器不支持扫码功能\"                      = \"The simulator does not support scanning\";\n    \"扫描二维码\"                             = \"QR Scanning\";\n    \"点击跳转\"                                =   \"Click to jump\";\n    \"Doraemon内置浏览器\"                          = \"Doraemon Built-in browser\";\n    \"链接不能为空\"                        =   \"url can not be nil\";\n    \"设备无相机——设备无相机功能，无法进行扫描\" = \"Device without camera\";\n    \"设备相机错误——无法启用相机，请检查\"    = \"Device camera error\";\n    \"相机权限未开启，请到「设置-隐私-相机」中允许DoKit访问您的相机\"    = \"Camera permission is not open\";\n    \n    //清除本地数据\n    \"清理缓存\"                          = \"Clear Sanbox\";\n    \"确定要删除本地数据\"                      = \"Confirm to clear sanbox data\";\n    \"正在清理中\"                                  = \"Deleting\";\n    \n    //NSLog\n    \"NSLog\"                               = \"NSLog\";\n    \"开关\"                                      = \"On/Off\";\n    \"查看记录\"                                = \"View log\";\n    \"NSLog日志记录\"                           = \"NSLog Results\";\n    \"导出\"                                  = \"export\";\n    \"清除\"                                  = \"delete\";\n    \"%@\\n触发时间: %@\"                             = \"%@\\nTrigger time: %@\";\n    \n    //UserDefaults\n    \n    //JS脚本\n    \"JS脚本\" = \"JavaScript\";\n    \"请选择WebView\" = \"Select WebView\";\n    \"无可用的WebView\" = \"No Available WebView\";\n    \"脚本列表\" = \"Script List\";\n    \"脚本执行\" = \"Execute Script\";\n    \"JS代码\" = \"JS Code\";\n    \"脚本不能为空\" = \"Code can not be empty\";\n    \n    //CocoaLumberjack\n    \"Lumberjack\"                                       = \"Lumberjack\";\n    \"CocoaLumberjack日志记录\"                 =  \"CocoaLumberjack\";\n    \"日志记录\"                           = \"Log Results\";\n    \"%@\\n触发时间: %@\\n文件名称: %@\\n所在行: %zi\\n线程id: %@ \\n线程名称: %@\" = \"%@\\nTrigger time: %@\\nFile name: %@\\nline: %zi\\nThread id: %@ \\nThread name: %@\";\n    \n    //DBView\n    \"开启服务\"                                   = \"Start server\";\n    \"关闭服务\"                                   = \"Stop server\";\n    \"温馨提示\"                                   = \"tips\";\n    \"你可以通过下面地址访问\"                         = \"access server address\";\n    \"请保证当前手机和PC处在同一个局域网内\"             = \"ensure phone and pc in the same wifi\";\n    \"服务已关闭\"                                   = \"server is off\";\n    \n    //FPS\n    \"帧率\"                                      =   \"FPS\";\n    \"帧率检测\"                                     = \"FPS monitor\";\n    \"帧率检测开关\"                                 =  \"FPS monitor switch\";\n    \n    \n    //CPU\n    \"CPU\"                                         =  \"CPU\";\n    \"CPU检测\"                                   =   \"CPU monitor\";\n    \"CPU检测开关\"                                  = \"CPU monitor switch\";\n    \n    //内存\n    \"内存\"                                      =   \"Memory\";\n    \"内存检测\"                                     = \"Memory monitor\";\n    \"内存检测开关\"                               = \"Memory monitor switch\";\n    \n    //网络\n    \"网络\"                                      =   \"Network\";\n    \"网络监控\"                                      =   \"Network monitor\";\n    \"网络检测开关\"                               = \"Network monitor switch\";\n    \"显示网络检测详情\"                         = \"Show network monitor detail\";\n    \"网络监控摘要\"                               = \"Network summary\";\n    \"暂未开启网络监控\"                      =   \"Network monitor is off\";\n    \"总计已为您抓包\"                       =   \"Total capture\";\n    \"秒\"                                = \"s\";\n    \"抓包数量\"                                =   \"Capture amount \";\n    \"数据上传\"                                =   \"Upload\";\n    \"数据下载\"                                =   \"Download\";\n    \"HTTP方法\"                                  =   \"HTTP Method\";\n    \"数据类型\"                                =   \"Data Type\";\n    \"网络监控列表\"                          =   \"Network list\";\n    \"耗时\"                                      =   \"cost\";\n    \"网络监控详情\"                          =   \"Network monitor info\";\n    \"请求\"                                      =   \"Request\";\n    \"请求概要\"                                  = \"Summary\";\n    \"链接\"                                     = \"Url\";\n    \"请求头\"                                   = \"Header\";\n    \"请求体\"                                   = \"Body\";\n    \"响应\"                                      =   \"Response\";\n    \"响应概要\"                                  =   \"Summary\";\n    \"响应头\"                                  =   \"Header\";\n    \"响应体\"                                  =   \"Body\";\n    \"数据大小 : %@\"                           =   \"Size : %@\";\n    \"你好\"                                      =   \"Hello\";\n    \n    //Crash\n    \"Crash\"                                 =   \"Crash\";\n    \"Crash日志收集开关\"                          = \"Crash log switch\";\n    \"查看Crash日志\"                                = \"View Crash log\";\n    \"一键清理Crash日志\"                          = \"Delete crash log\";\n    \"确认删除所有崩溃日志吗？\"             = \"Confirm to delete all crash logs?\";\n    \"Crash日志列表\"                                = \"Crash log list\";\n    \n    //子线程UI\n    \"子线程UI\"                                 =   \"Sub Thread UI\";\n    \"子线程UI渲染检测开关\"               =   \"Sub Thread UI switch\";\n    \"查看检测记录\"                          =   \"View monitor records\";\n    \"检测列表\"                                =   \"Monitor list\";\n    \"检测详情\"                                =   \"Monitor Info\";\n    \n     //卡顿\n     \"卡顿\"                                      =   \"ANR\";\n     \"卡顿检测\"                                =   \"ANR Check\";\n     \"卡顿检测开关\"                          =   \"ANR Switch\";\n     \"查看卡顿记录\"                          =   \"View ANR\";\n     \"一键清理卡顿记录\"                          = \"Delete ANR track\";\n     \"卡顿列表\"                                =   \"ANR List\";\n     \"卡顿详情\"                                =   \"ANR Detail\";\n     \"确认删除所有卡顿记录吗？\"             = \"Confirm to delete all ANR tracks?\";\n     \n     //大图检测\n     \"大图检测\"                                     = \"BigImg\";\n     \"大图检测开关\"                                  = \"Big image detection switch\";\n     \n     //弱网检测\n     \"模拟弱网\"                               = \"Weak Network\";\n     \"模拟弱网测试\"                            = \"Weak Network Test\";\n     \"弱网模式\"                               = \"Weak network mode\";\n     \"断网\"                                  = \"Broken\";\n     \"超时\"                                  = \"Timeout\";\n     \"限速\"                                  = \"Limit\";\n     \"延时\"                                  = \"Delay\";\n     \"延时时间\"                               = \"Delay time\";\n     \"请求限速\"                               = \"Request limit\";\n     \"响应限速\"                               = \"Response limit\";\n     \"上行流量\"                                 = \"Up Stream\";\n     \"下行流量\"                                 = \"Down Stream\";\n     \n     //开机启动\n     \"启动耗时\"                               = \"Launch Time\";\n     \"本次启动时间为\"                          = \"App launch time = \";\n     \n    //UI层级\n    \"UI层级\"                                  = \"UI Hierarchy\";\n    \"UI层级检查开关\"                              = \"UI Hierarchy switch\";\n\n    //函数耗时\n    \"函数耗时\"                                   = \"Time Profiler\";\n    \"函数耗时描述\" = \"\\n\\n\\n This function does not provide a UI operation interface，\\n before the code you need to analyze, insert \\n\\n [DoraemonTimeProfiler startRecord]; \\n\\nat the end,insert \\n\\n[DoraemonTimeProfiler stopRecord];\\n\\n\n    Then manually operate the App to execute the code，You can see the complete function time-consuming analysis in the console。\\n\\n\n    The sdk filters the code call level> 10 layers, and the function call takes less than 1ms. Of course, you can also set it yourself through the api \\n\\nAfter analyzing, remember to delete the function call of startRecord and stopRecord.\";\n    \n    //Load耗时\n    \"Load耗时\"                                       = \"Load\";\n    \"Load耗时检测开关\"                           = \"Load switch\";\n    \"Load耗时检测记录\"                           = \"Load Results\";\n    \"总共耗时\"                                     = \"Total cost\";\n    \n    //内存泄漏\n    \"内存泄漏\"                                   = \"Memory Leak\";\n    \"内存泄漏检测开关\"                            = \"Memory Leak Switch\";\n    \"内存泄漏检测弹框提醒\"                            = \"Memory Leak Alert Warning\";\n    \"内存泄漏检测结果\"                             = \"Memory Leak Result\";\n    \"内存泄漏详情\"                                =  \"Memory Leak Detail\";\n    \n    //取色器\n    \"取色器\"                             =   \"Color Picker\";\n    \n    //组件检查\n    \"组件检查\"                                =   \"View Check\";\n    \"控件名称\"                                 =  \"Widget\";\n    \"\\n控件位置：左%0.1lf  上%0.1lf  宽%0.1lf  高%0.1lf\" = \"\\nPosition: left:%0.1lf  top:%0.1lf  width:%0.1lf  height:%0.1lf\";\n    \"\\n背景颜色：%@\"                              = \"\\nBackground Color：%@\";\n    \"\\n背景颜色：%@  字体颜色：%@  字体大小：%.f\" = \"\\nBackground Color：%@  Font Color：%@  Font Size：%.f\";\n    \n    //对齐标尺\n    \"对齐标尺\"                                =   \"Align Ruler\";\n    \"位置：左%@  右%@  上%@  下%@\"                 =  \"Position: left:%@  right:%@  top:%@  bottom:%@\";\n    \n    //元素边框线\n    \"布局边框\"                                  = \"View Border\";\n    \"布局边框开关\"                            = \"View border switch\";\n    \n    //UI结构\n    \"UI结构\"                                 = \"UI Structure\";\n    \n    \n    //Weex Log\n    \"日志\"                                       = \"Log\";\n    \"Weex日志记录\"                               = \"Weex Log\";\n    \n    //Weex Cache\n    \"缓存\"                                       = \"Cache\";\n    \"Weex缓存\"                               = \"Weex Cache\";\n    \n    //Weex Info\n    \"信息\"                                       = \"Info\";\n    \"weex信息查看\"                                = \"Weex Info\";\n    \"weexsdk版本号\"                              = \"weex sdk version\";\n    \"wxJSLib版本号\"                              = \"weex jslib version\";\n    \"wxBundleType\"                              = \"weex bundle type\";\n    \"wxBundleSize\"                              = \"weex bundle size\";\n    \"请求bundle时间\"                              = \"query bundle time\";\n    \"处理bundle时间\"                              = \"handle bundle time\";\n    \"第一个view出现时间\"                              = \"first view appear time\";\n    \"可交互时间\"                              = \"interactive time\";\n    \n    //设置\n    \"更多\"                                           = \"More\";\n    \"设置\"                                           = \"Settings\";\n    \"工具管理\"                                        = \"Kit Manager\";\n    \"编辑\"                                           = \"Edit\";\n    \"完成\"                                           = \"Done\";\n    \"是否保存已编辑的内容\"                              = \"Whether to save the edited content\";\n    \"保存成功\"                                        = \"Saved successfully\";\n    \"还原\"                                           = \"Reset\";\n    \"是否还原到初始状态\"                                = \"Whether to reset to the initial state\";\n    \"每一个分组至少保留一项\"                             = \"Keep at least one item in each section\";\n    \n    //其他\n    \"提示\"                                      =   \"Tip\";\n    \"删除成功\"                                     = \"Delete Success\";\n    \"确定\"                                      =   \"OK\";\n    \"关闭DoraemonKit\"                                = \"Close DoraemonKit\";\n    \"当前版本\"                                 = \"Current Version\";\n    \"该功能需要重启App才能生效\"        =   \"Reboot to work\";\n    \"请输入您要搜索的关键字\"           =   \"Please enter search keywords\";\n    \"删除失败\"                                     = \"Delete fail\";\n    \"Doraemon关闭之后需要重启App才能重新打开\"   =   \"Reboot to open Doraemon\";\n    \"复制\"                                           = \"Copy\";\n    \"删除\"                                           = \"Delete\";\n\n    // 一机多控\n    \"一机多控\"                                  = \"MultiControl\";\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Resource/zh-Hans.lproj/Doraemon.strings",
    "content": "/*\n Doraemon.strings\n Pods\n \n Created by xgb on 2018/11/13.\n \n */\n\n{\n    //分组名称\n    \"业务工具\"                                      = \"业务工具\";\n    \"平台工具\"                                      = \"平台工具\";\n    \"常用工具\"                                      = \"常用工具\";\n    \"性能检测\"                                      = \"性能检测\";\n    \"视觉工具\"                                      = \"视觉工具\";\n    \"Weex\"                                         = \"Weex\";\n    \n    //每一个模块\n    \n    //数据Mock\n    \"Mock数据\"                                = \"Mock数据\";\n    \"上传模版\"                                 = \"上传模版\";\n    \"接口分组\"                                 = \"接口分组\";\n    \"开关状态\"                                 = \"开关状态\";\n    \"分组: %@\\n\"                             = \"分组: %@\\n\";\n    \"修改人: %@\\n\"                           = \"修改人: %@\\n\";\n    \"创建人: %@\\n\"                           = \"创建人: %@\\n\";\n    \"数据预览\"                               = \"数据预览\";\n    \"上传\"                                  = \"上传\";\n    \"数据预览为空\"                            = \"数据预览为空\";\n    \"上传成功\"                               = \"上传成功\";\n    \"上传失败\"                               = \"上传失败\";\n    \"本地是否存在mock数据: %@\"                  = \"本地是否存在mock数据: %@\";\n    \"存在\"                                  = \"存在\";\n    \"不存在\"                                 = \"不存在\";\n    \"App当前处于健康体检状态，无法进行此操作\"        = \"App当前处于健康体检状态，无法进行此操作\";\n    \n    //健康体检\n    \"健康体检\"                                 = \"健康体检\";\n    \"点击开始检测\"                              = \"点击开始检测\";\n    \"正在检测中...\"                            = \"正在检测中...\";\n    \"向下滑动查看功能使用说明\"                    = \"向下滑动查看功能使用说明\";\n    \"回到顶部\"                                 = \"回到顶部\";\n    \"提交成功\\n恭喜已完成检测！\"                  = \"提交成功\\n恭喜已完成检测！\";\n    \"第%d步\"                                  = \"第%d步\";\n    \"是否确认\"                                 = \"是否确认\";\n    \"结束前请完善下列信息\"                        = \"结束前请完善下列信息\";\n    \"测试用例名称\"                              = \"测试用例名称\";\n    \"测试人名称\"                               = \"测试人名称\";\n    \"提交\"                                    = \"提交\";\n    \"取消\"                                     = \"取消\";\n    \"丢弃\"                                     = \"丢弃\";\n    \"数据上传成功\"                               = \"数据上传成功\";\n    \"数据上传失败\"                               = \"数据上传失败\";\n    \n    //文件同步助手\n    \"文件同步\"                                 = \"文件同步\";\n    \"需要到www.dokit.cn上注册pId才能使用该功能\"    =  \"需要到www.dokit.cn上注册pId才能使用该功能\";\n    \n    //应用设置\n    \"应用设置\"                               = \"应用设置\";\n    \n    //App信息\n    \"App信息\"                                =   \"App信息\";\n    \"手机信息\"                                =   \"手机信息\";\n    \"设备名称\"                                = \"设备名称\";\n    \"手机型号\"                                =   \"手机型号\";\n    \"系统版本\"                                =   \"系统版本\";\n    \"手机屏幕\"                               = \"手机屏幕\";\n    \"权限信息\"                                =   \"权限信息\";\n    \"地理位置权限\"                          =   \"地理位置权限\";\n    \"网络权限\"                                =   \"网络权限\";\n    \"推送权限\"                                =   \"推送权限\";\n    \"相机权限\"                                =   \"相机权限\";\n    \"麦克风权限\"                             =  \"麦克风权限\";\n    \"相册权限\"                                =   \"相册权限\";\n    \"通讯录权限\"                             =   \"通讯录权限\";\n    \"日历权限\"                                =   \"日历权限\";\n    \"提醒事项权限\"                            =   \"提醒事项权限\";\n    \"用户没有选择\"                          =  \"用户没有选择\";\n    \"用户已经授权\"                               = \"用户已经授权\";\n    \"用户没有授权\"                               = \"用户没有授权\";\n    \"家长控制\"                                     = \"家长控制\";\n    \n    //沙盒浏览器\n    \"沙盒浏览器\"                             =  \"沙盒浏览器\";\n    \"本地预览\"                                =   \"本地预览\";\n    \"分享\"                                    = \"分享\";\n    \"请选择操作方式\"                       =   \"请选择操作方式\";\n    \"返回上一级\"                             =   \"返回上一级\";\n    \"根目录\"                                   =   \"根目录\";\n    \"文件不存在\"                             =   \"文件不存在\";\n    \"数据库预览\"                                  = \"数据库预览\";\n    \"文件预览\"                                =   \"文件预览\";\n    \n    //Mock GPS\n    \"Mock GPS\"                                     =  \"位置模拟\";\n    \"打开Mock GPS\"                                   = \"打开位置模拟\";\n    \"请输入经纬度\"                               = \"请输入经纬度\";\n    \"经纬度不能为空\"                       =  \"经纬度不能为空\";\n    \"格式不正确\"                             =  \"格式不正确\";\n    \"(示例: 120.15 30.28)\"                           = \"(示例: 120.15 30.28)\";\n    \"经度不合法\"                             =   \"经度不合法\";\n    \"mock开关没有打开\"                      =   \"mock开关没有打开\";\n    \"纬度不合法\"                             =   \"纬度不合法\";\n    \n    //H5任意门\n    \"H5任意门\"                                 =  \"H5任意门\";\n    \"清除搜索历史\"                            = \"清除搜索历史\";\n    \"模拟器不支持扫码功能\"                      = \"模拟器不支持扫码功能\";\n    \"扫描二维码\"                             = \"扫描二维码\";\n    \"点击跳转\"                                =   \"点击跳转\";\n    \"Doraemon内置浏览器\"                          = \"Doraemon内置浏览器\";\n    \"链接不能为空\"                        =   \"链接不能为空\";\n    \"设备无相机——设备无相机功能，无法进行扫描\" = \"设备无相机——设备无相机功能，无法进行扫描\";\n    \"设备相机错误——无法启用相机，请检查\"    = \"设备相机错误——无法启用相机，请检查\";\n    \"相机权限未开启，请到「设置-隐私-相机」中允许DoKit访问您的相机\"    = \"相机权限未开启，请到「设置-隐私-相机」中允许DoKit访问您的相机\";\n    \n    //清除本地数据\n    \"清理缓存\"                          = \"清理缓存\";\n    \"确定要删除本地数据\"                      = \"确定要删除本地数据\";\n    \"正在清理中\"                                  = \"正在清理中\";\n    \n    //NSLog\n    \"NSLog\"                               = \"NSLog\";\n    \"开关\"                                      = \"开关\";\n    \"查看记录\"                                = \"查看记录\";\n    \"NSLog日志记录\"                           = \"NSLog日志记录\";\n    \"导出\"                                  = \"导出\";\n    \"清除\"                                  = \"清除\";\n    \"%@\\n触发时间: %@\"                             = \"%@\\n触发时间: %@\";\n    \n    //UserDefaults\n    \n    //JS脚本\n    \"JS脚本\" = \"JavaScript\";\n    \"请选择WebView\" = \"请选择WebView\";\n    \"无可用的WebView\" = \"无可用的WebView\";\n    \"脚本列表\" = \"脚本列表\";\n    \"脚本执行\" = \"脚本执行\";\n    \"JS代码\" = \"JS代码\";\n    \"脚本不能为空\" = \"脚本不能为空\";\n    \n    //CocoaLumberjack\n    \"Lumberjack\"                                       = \"Lumberjack\";\n    \"CocoaLumberjack日志记录\"                 =  \"CocoaLumberjack日志记录\";\n    \"日志记录\"                           = \"日志记录\";\n    \"%@\\n触发时间: %@\\n文件名称: %@\\n所在行: %zi\\n线程id: %@ \\n线程名称: %@\" = \"%@\\n触发时间: %@\\n文件名称: %@\\n所在行: %zi\\n线程id: %@ \\n线程名称: %@\";\n    \n    //DBView\n    \"开启服务\"                                   = \"开启服务\";\n    \"关闭服务\"                                   = \"关闭服务\";\n    \"温馨提示\"                                   = \"温馨提示\";\n    \"你可以通过下面地址访问\"                         = \"你可以通过下面地址访问\";\n    \"请保证当前手机和PC处在同一个局域网内\"             = \"请保证当前手机和PC处在同一个局域网内\";\n    \"服务已关闭\"                                   = \"服务已关闭\";\n    \n    //FPS\n    \"帧率\"                                      =   \"帧率\";\n    \"帧率检测\"                                     = \"帧率检测\";\n    \"帧率检测开关\"                                 =  \"帧率检测开关\";\n    \n    \n    //CPU\n    \"CPU\"                                         =  \"CPU\";\n    \"CPU检测\"                                   =   \"CPU检测\";\n    \"CPU检测开关\"                                  = \"CPU检测开关\";\n    \n    //内存\n    \"内存\"                                      =   \"内存\";\n    \"内存检测\"                                     = \"内存检测\";\n    \"内存检测开关\"                               = \"内存检测开关\";\n    \n    //网络\n    \"网络\"                                      =   \"网络\";\n    \"网络监控\"                                      =   \"网络监控\";\n    \"网络检测开关\"                               = \"网络检测开关\";\n    \"显示网络检测详情\"                         = \"显示网络检测详情\";\n    \"网络监控摘要\"                               = \"网络监控摘要\";\n    \"暂未开启网络监控\"                      =   \"暂未开启网络监控\";\n    \"总计已为您抓包\"                       =   \"总计已为您抓包\";\n    \"秒\"                                = \"秒\";\n    \"抓包数量\"                                =   \"抓包数量\";\n    \"数据上传\"                                =   \"数据上传\";\n    \"数据下载\"                                =   \"数据下载\";\n    \"HTTP方法\"                                  =   \"HTTP方法\";\n    \"数据类型\"                                =   \"数据类型\";\n    \"网络监控列表\"                          =   \"网络监控列表\";\n    \"耗时\"                                      =   \"耗时\";\n    \"网络监控详情\"                          =   \"网络监控详情\";\n    \"请求\"                                      =   \"请求\";\n    \"请求概要\"                                  = \"请求概要\";\n    \"链接\"                                     = \"链接\";\n    \"请求头\"                                   = \"请求头\";\n    \"请求体\"                                   = \"请求体\";\n    \"响应\"                                      =   \"响应\";\n    \"响应概要\"                                  =   \"响应概要\";\n    \"响应头\"                                  =   \"响应头\";\n    \"响应体\"                                  =   \"响应体\";\n    \"数据大小 : %@\"                           =   \"数据大小 : %@\";\n    \"你好\"                                      =   \"你好\";\n    \n    //Crash\n    \"Crash\"                                 =   \"Crash\";\n    \"Crash日志收集开关\"                          = \"Crash日志收集开关\";\n    \"查看Crash日志\"                                = \"查看Crash日志\";\n    \"一键清理Crash日志\"                          = \"一键清理Crash日志\";\n    \"确认删除所有崩溃日志吗？\"             = \"确认删除所有崩溃日志吗？\";\n    \"Crash日志列表\"                                = \"Crash日志列表\";\n    \n    //子线程UI\n    \"子线程UI\"                                 =   \"子线程UI\";\n    \"子线程UI渲染检测开关\"               =   \"子线程UI渲染检测开关\";\n    \"查看检测记录\"                          =   \"查看检测记录\";\n    \"检测列表\"                                =   \"检测列表\";\n    \"检测详情\"                                =   \"检测详情\";\n    \n     //卡顿\n     \"卡顿\"                                      =   \"卡顿\";\n     \"卡顿检测\"                                =   \"卡顿检测\";\n     \"卡顿检测开关\"                          =   \"卡顿检测开关\";\n     \"查看卡顿记录\"                          =   \"查看卡顿记录\";\n     \"一键清理卡顿记录\"                          = \"一键清理卡顿记录\";\n     \"卡顿列表\"                                =   \"卡顿列表\";\n     \"卡顿详情\"                                =   \"卡顿详情\";\n     \"确认删除所有卡顿记录吗？\"             = \"确认删除所有卡顿记录吗？\";\n     \n     //大图检测\n     \"大图检测\"                                     = \"大图检测\";\n     \"大图检测开关\"                                  = \"大图检测开关\";\n     \n     //弱网检测\n     \"模拟弱网\"                               = \"模拟弱网\";\n     \"模拟弱网测试\"                            = \"模拟弱网测试\";\n     \"弱网模式\"                               = \"弱网模式\";\n     \"断网\"                                  = \"断网\";\n     \"超时\"                                  = \"超时\";\n     \"限速\"                                  = \"限速\";\n     \"延时\"                                  = \"延时\";\n     \"延时时间\"                               = \"延时时间\";\n     \"请求限速\"                               = \"请求限速\";\n     \"响应限速\"                               = \"响应限速\";\n     \"上行流量\"                                 = \"上行流量\";\n     \"下行流量\"                                 = \"下行流量\";\n     \n     //开机启动\n     \"启动耗时\"                               = \"启动耗时\";\n     \"本次启动时间为\"                          = \"本次启动时间为\";\n     \n    //UI层级\n    \"UI层级\"                                  = \"UI层级\";\n    \"UI层级检查开关\"                              = \"UI层级检查开关\";\n\n    //函数耗时\n    \"函数耗时\"                                   = \"函数耗时\";\n    \"函数耗时描述\" = \"\\n\\n\\n 该功能不提供UI操作界面，在你需要分析的代码之前插入 \\n\\n [DoraemonTimeProfiler startRecord]; \\n\\n结束的地方加上 \\n\\n[DoraemonTimeProfiler stopRecord];\\n\\n 然后手动操作App执行代码流程，即可在控制台看到完整的函数耗时分析。\\n\\nsdk过滤了代码调用层次>10层，耗时小于1ms的函数调用，当然你也可以通过api自行设置。 \\n\\n 分析完毕之后，记得删掉startRecord和stopRecord的函数调用。\";\n    \n    //Load耗时\n    \"Load耗时\"                                       = \"Load耗时\";\n    \"Load耗时检测开关\"                           = \"Load耗时检测开关\";\n    \"Load耗时检测记录\"                           = \"Load耗时检测记录\";\n    \"总共耗时\"                                     = \"总共耗时\";\n    \n    //内存泄漏\n    \"内存泄漏\"                                   = \"内存泄漏\";\n    \"内存泄漏检测开关\"                            = \"内存泄漏检测开关\";\n    \"内存泄漏检测弹框提醒\"                            = \"内存泄漏检测弹框提醒\";\n    \"内存泄漏检测结果\"                             = \"内存泄漏检测结果\";\n    \"内存泄漏详情\"                                =  \"内存泄漏详情\";\n    \n    //取色器\n    \"取色器\"                             =   \"取色器\";\n    \n    //组件检查\n    \"组件检查\"                                =   \"组件检查\";\n    \"控件名称\"                                 =  \"控件名称\";\n    \"\\n控件位置：左%0.1lf  上%0.1lf  宽%0.1lf  高%0.1lf\" = \"\\n控件位置：左%0.1lf  上%0.1lf  宽%0.1lf  高%0.1lf\";\n    \"\\n背景颜色：%@\"                              = \"\\n背景颜色：%@\";\n    \"\\n背景颜色：%@  字体颜色：%@  字体大小：%.f\" = \"\\n背景颜色：%@  字体颜色：%@  字体大小：%.f\";\n    \n    //对齐标尺\n    \"对齐标尺\"                                =   \"对齐标尺\";\n    \"位置：左%@  右%@  上%@  下%@\"                 =  \"位置：左%@  右%@  上%@  下%@\";\n    \n    //元素边框线\n    \"布局边框\"                                  = \"布局边框\";\n    \"布局边框开关\"                            = \"布局边框开关\";\n    \n    //UI结构\n    \"UI结构\"                                 = \"UI结构\";\n    \n    \n    //Weex Log\n    \"日志\"                                       = \"日志\";\n    \"Weex日志记录\"                               = \"Weex日志记录\";\n    \n    //Weex Cache\n    \"缓存\"                                       = \"缓存\";\n    \"Weex缓存\"                               = \"Weex缓存\";\n    \n    //Weex Info\n    \"信息\"                                       = \"信息\";\n    \"weex信息查看\"                                = \"weex信息查看\";\n    \"weexsdk版本号\"                              = \"weexsdk版本号\";\n    \"wxJSLib版本号\"                              = \"wxJSLib版本号\";\n    \"wxBundleType\"                              = \"wxBundleType\";\n    \"wxBundleSize\"                              = \"wxBundleSize\";\n    \"请求bundle时间\"                              = \"请求bundle时间\";\n    \"处理bundle时间\"                              = \"处理bundle时间\";\n    \"第一个view出现时间\"                              = \"第一个view出现时间\";\n    \"可交互时间\"                              = \"可交互时间\";\n    \n    //设置\n    \"更多\"                                           = \"更多\";\n    \"设置\"                                           = \"设置\";\n    \"工具管理\"                                        = \"工具管理\";\n    \"编辑\"                                           = \"编辑\";\n    \"完成\"                                           = \"完成\";\n    \"是否保存已编辑的内容\"                              = \"是否保存已编辑的内容\";\n    \"保存成功\"                                        = \"保存成功\";\n    \"还原\"                                           = \"还原\";\n    \"是否还原到初始状态\"                                = \"是否还原到初始状态\";\n    \"每一个分组至少保留一项\"                             = \"每一个分组至少保留一项\";\n    \n    //其他\n    \"提示\"                                      =   \"提示\";\n    \"删除成功\"                                     = \"删除成功\";\n    \"确定\"                                      =   \"确定\";\n    \"关闭DoraemonKit\"                                = \"关闭DoraemonKit\";\n    \"当前版本\"                                 = \"当前版本\";\n    \"该功能需要重启App才能生效\"        =   \"该功能需要重启App才能生效\";\n    \"请输入您要搜索的关键字\"           =   \"请输入您要搜索的关键字\";\n    \"删除失败\"                                     = \"删除失败\";\n    \"Doraemon关闭之后需要重启App才能重新打开\"   =   \"Doraemon关闭之后需要重启App才能重新打开\";\n    \"复制\"                                           = \"复制\";\n    \"删除\"                                           = \"删除\";\n    \n    // 一机多控\n    \"一机多控\"                                  = \"一机多控\" ;\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Base/DoraemonBaseViewController.h",
    "content": "//\n//  DoraemonBaseViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonBaseBigTitleView.h\"\n\n@interface DoraemonBaseViewController : UIViewController\n\n//是否需要大标题，默认不需要\n- (BOOL)needBigTitleView;\n@property (nonatomic, strong) DoraemonBaseBigTitleView *bigTitleView;\n\n- (void)setLeftNavBarItems:(NSArray *)items;\n- (void)leftNavBackClick:(id)clickView;\n- (void)setRightNavTitle:(NSString *)title;\n- (void)rightNavTitleClick:(id)clickView;\n- (void)setRightNavBarItems:(NSArray *)items;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Base/DoraemonBaseViewController.m",
    "content": "//\n//  DoraemonBaseViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonBaseViewController.h\"\n#import \"DoraemonNavBarItemModel.h\"\n#import \"UIImage+Doraemon.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonBaseViewController ()<DoraemonBaseBigTitleViewDelegate>\n \n@property (nonatomic, strong) DoraemonNavBarItemModel *leftModel;\n\n@property (nonatomic, strong) NSArray *leftNavBarItemArray;\n@end\n\n@implementation DoraemonBaseViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        self.view.backgroundColor = [UIColor systemBackgroundColor];\n        [self.navigationController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName : [UIColor labelColor]}];\n        if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n            [self.navigationController.navigationBar setShadowImage:[UIImage doraemon_imageWithColor:[UIColor doraemon_black_3] size:CGSizeMake(self.view.frame.size.width, 0.5)]];\n        }\n    } else {\n#endif\n        self.view.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n     \n    if ([self needBigTitleView]) {\n        _bigTitleView = [[DoraemonBaseBigTitleView alloc] initWithFrame:CGRectMake(0, 0, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(178))];\n        _bigTitleView.delegate = self;\n        [self.view addSubview:_bigTitleView];\n    } else {\n        UIImage *image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_back\"];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_back_dark\"];\n            }\n        }\n#endif\n        self.leftModel = [[DoraemonNavBarItemModel alloc] initWithImage:image selector:@selector(leftNavBackClick:)];\n        [self setLeftNavBarItems:@[self.leftModel]];\n    }\n}\n\n- (void)viewWillAppear:(BOOL)animated{\n    [super viewWillAppear:animated];\n    self.navigationController.navigationBarHidden = [self needBigTitleView];\n}\n\n\n- (void)viewWillDisappear:(BOOL)animated{\n    [super viewWillDisappear:animated];\n    //输入框聚焦的时候，会把当前window设置为keyWindow，我们在当页面消失的时候，判断一下，把keyWindow交还给[[UIApplication sharedApplication].delegate window]\n    if ([[UIApplication sharedApplication].delegate respondsToSelector:@selector(window)]) {\n        UIWindow *appWindow = [[UIApplication sharedApplication].delegate window];\n        UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;\n        if (appWindow != keyWindow) {\n            [appWindow makeKeyWindow];\n        }\n    }\n}\n\n- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {\n    [super traitCollectionDidChange:previousTraitCollection];\n    // trait发生了改变\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {\n            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                self.leftModel.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_back_dark\"];\n                [self.navigationController.navigationBar setShadowImage:[UIImage doraemon_imageWithColor:[UIColor doraemon_black_3] size:CGSizeMake(self.view.frame.size.width, 0.5)]];\n            } else {\n                self.leftModel.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_back\"];\n            }\n            if (self.leftNavBarItemArray) {\n                [self setLeftNavBarItems:self.leftNavBarItemArray];\n            }\n        }\n    }\n#endif\n}\n\n//是否需要大标题，默认不需要\n- (BOOL)needBigTitleView{\n    return NO;\n}\n\n- (void)setTitle:(NSString *)title{\n    if (_bigTitleView && !_bigTitleView.hidden) {\n        [_bigTitleView setTitle:title];\n    }else{\n        [super setTitle:title];\n    }\n}\n\n- (void)leftNavBackClick:(id)clickView{\n    if (self.navigationController.viewControllers.count==1) {\n        [[DoraemonHomeWindow shareInstance] hide];\n    }else{\n        [self.navigationController popViewControllerAnimated:YES];\n    }\n    \n}\n\n- (void)setLeftNavBarItems:(NSArray *)items{\n    _leftNavBarItemArray = items;\n    NSArray *barItems = [self navigationItems:items];\n    if (barItems) {\n        self.navigationItem.leftBarButtonItems = barItems;\n    }\n}\n\n- (void)setRightNavBarItems:(NSArray *)items{\n    NSArray *barItems = [self navigationItems:items];\n    if (barItems) {\n        self.navigationItem.rightBarButtonItems = barItems;\n    }\n}\n\n- (void)setRightNavTitle:(NSString *)title{\n    DoraemonNavBarItemModel *item = [[DoraemonNavBarItemModel alloc] initWithText:title color:[UIColor doraemon_blue] selector:@selector(rightNavTitleClick:)];\n    NSArray *barItems = [self navigationItems:@[item]];\n    if (barItems) {\n        self.navigationItem.rightBarButtonItems = barItems;\n    }\n}\n\n\n- (NSArray *)navigationItems:(NSArray *)items{\n    NSMutableArray *barItems = [NSMutableArray array];\n    //距离左右的间距\n    UIBarButtonItem *spacer = [self getSpacerByWidth:-10];\n    [barItems addObject:spacer];\n    \n    for (NSInteger i=0; i<items.count; i++) {\n        \n        DoraemonNavBarItemModel *model = items[i];\n        UIBarButtonItem *barItem;\n        if (model.type == DoraemonNavBarItemTypeText) {//文字按钮\n            barItem = [[UIBarButtonItem alloc] initWithTitle:model.text style:UIBarButtonItemStylePlain target:self action:model.selector];\n            barItem.tintColor = model.textColor;\n        }else if(model.type == DoraemonNavBarItemTypeImage){//图片按钮\n            UIImage *image = [model.image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];//设置图片没有默认蓝色效果\n            //默认的间距太大\n//            barItem = [[UIBarButtonItem alloc] initWithImage:image style:UIBarButtonItemStylePlain target:self action:model.selector];\n            \n            UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];\n            [btn setImage:image forState:UIControlStateNormal];\n            [btn addTarget:self action:model.selector forControlEvents:UIControlEventTouchUpInside];\n            btn.frame = CGRectMake(0, 0, 30, 30);\n            btn.clipsToBounds = YES;\n            barItem = [[UIBarButtonItem alloc] initWithCustomView:btn];\n        }else{\n            barItem = [[UIBarButtonItem alloc] init];\n        }\n        [barItems addObject:barItem];\n    }\n    return barItems;\n}\n\n/**\n * 获取间距\n */\n- (UIBarButtonItem *)getSpacerByWidth : (CGFloat)width{\n    UIBarButtonItem *spacer = [[UIBarButtonItem alloc]\n                               initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace\n                               target:nil action:nil];\n    /**\n     *  width为负数时，相当于btn向右移动width数值个像素，由于按钮本身和边界间距为5pix，所以width设为-5时，间距正好调整\n     *  为0；width为正数时，正好相反，相当于往左移动width数值个像素\n     */\n    spacer.width = width;\n    return spacer;\n}\n\n#pragma mark - DoraemonBaseBigTitleViewDelegate\n- (void)bigTitleCloseClick{\n    [self leftNavBackClick:nil];\n}\n\n- (void)rightNavTitleClick:(id)clickView{\n    \n}\n\n//点击屏幕空白处收起键盘\n-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{\n    [self.view endEditing:YES];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Base/DoraemonNavBarItemModel.h",
    "content": "//\n//  DoraemonNavBarItemModel.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\ntypedef NS_ENUM(NSUInteger, DoraemonNavBarItemType) {\n    DoraemonNavBarItemTypeText = 0,\n    DoraemonNavBarItemTypeImage,\n};\n\n@interface DoraemonNavBarItemModel : NSObject\n\n@property (nonatomic, assign) DoraemonNavBarItemType type;\n@property (nonatomic, copy) NSString *text;\n@property (nonatomic, strong) UIImage *image;\n@property (nonatomic, strong) UIColor *textColor;\n@property (nonatomic, assign) SEL selector;\n\n- (instancetype)initWithText:(NSString *)text color:(UIColor *)color selector:(SEL)selector;\n- (instancetype)initWithImage:(UIImage *)image selector:(SEL)selector;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Base/DoraemonNavBarItemModel.m",
    "content": "//\n//  DoraemonNavBarItemModel.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import \"DoraemonNavBarItemModel.h\"\n\n@implementation DoraemonNavBarItemModel\n\n- (instancetype)initWithText:(NSString *)text color:(UIColor *)color selector:(SEL)selector{\n    self = [[DoraemonNavBarItemModel alloc] init];\n    self.type = DoraemonNavBarItemTypeText;\n    self.text = text;\n    self.textColor = color;\n    self.selector = selector;\n    return self;\n}\n- (instancetype)initWithImage:(UIImage *)image selector:(SEL)selector{\n    self = [[DoraemonNavBarItemModel alloc] init];\n    self.type = DoraemonNavBarItemTypeImage;\n    self.image = image;\n    self.selector = selector;\n    return self;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Base/DoraemonStatusBarViewController.h",
    "content": "//\n//  DoraemonStatusBarViewController.h\n//  DoraemonKit\n//\n//  Created by 张伟 on 2019/2/22.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonStatusBarViewController : UIViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Base/DoraemonStatusBarViewController.m",
    "content": "//\n//  DoraemonStatusBarViewController.m\n//  DoraemonKit\n//\n//  Created by 张伟 on 2019/2/22.\n//\n\n#import \"DoraemonStatusBarViewController.h\"\n#import \"DoraemonManager.h\"\n\n@interface DoraemonStatusBarViewController ()\n\n@end\n\n@implementation DoraemonStatusBarViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    // Do any additional setup after loading the view.\n}\n\n// iOS9.0的系统中，新建的window设置的rootViewController默认没有显示状态栏\n\n#if __IPHONE_OS_VERSION_MAX_ALLOWED <= __IPHONE_9_3\n\n- (BOOL)prefersStatusBarHidden {\n    return NO;\n}\n\n- (UIStatusBarStyle)preferredStatusBarStyle {\n    return UIStatusBarStyleDefault;\n}\n\n#endif\n\n- (UIInterfaceOrientationMask)supportedInterfaceOrientations {\n    return DoraemonManager.shareInstance.supportedInterfaceOrientations;\n}\n\n\n/*\n#pragma mark - Navigation\n\n// In a storyboard-based application, you will often want to do a little preparation before navigation\n- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {\n    // Get the new view controller using [segue destinationViewController].\n    // Pass the selected object to the new view controller.\n}\n*/\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Base/View/DoraemonBaseBigTitleView.h",
    "content": "//\n//  DoraemonBaseBigTitleView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/2.\n//\n\n#import <UIKit/UIKit.h>\n\n@protocol DoraemonBaseBigTitleViewDelegate <NSObject>\n\n- (void)bigTitleCloseClick;\n\n@end\n\n@interface DoraemonBaseBigTitleView : UIView\n\n@property (nonatomic, strong) NSString *title;\n@property (nonatomic, weak) id<DoraemonBaseBigTitleViewDelegate> delegate;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Base/View/DoraemonBaseBigTitleView.m",
    "content": "//\n//  DoraemonBaseBigTitleView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/2.\n//\n\n#import \"DoraemonBaseBigTitleView.h\"\n#import \"DoraemonDefine.h\"\n#import \"UIView+Doraemon.h\"\n#import \"UIColor+Doraemon.h\"\n#import \"UIImage+Doraemon.h\"\n\n@interface DoraemonBaseBigTitleView()\n\n@property (nonatomic, strong) UILabel *titleLabel;\n@property (nonatomic, strong) UIButton *closeBtn;\n@property (nonatomic, strong) UIView *downLine;\n\n@end\n\n@implementation DoraemonBaseBigTitleView\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    if (self) {\n        CGFloat offsetY = IPHONE_STATUSBAR_HEIGHT;\n        CGFloat titleLabelOffsetY = offsetY + ((self.doraemon_height-offsetY)/2-kDoraemonSizeFrom750_Landscape(67)/2);\n        CGFloat closeBtnH = self.doraemon_height-offsetY;\n        \n        _titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(32), titleLabelOffsetY, self.doraemon_width-kDoraemonSizeFrom750_Landscape(32)-closeBtnH, kDoraemonSizeFrom750_Landscape(67))];\n        \n        UIImage *closeImage = [UIImage doraemon_xcassetImageNamed:@\"doraemon_close\"];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            self.backgroundColor = [UIColor systemBackgroundColor];\n            \n            _titleLabel.textColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n                if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                    return [UIColor whiteColor];\n                } else {\n                    return [UIColor doraemon_colorWithString:@\"#324456\"];\n                }\n            }];\n            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                closeImage = [UIImage doraemon_xcassetImageNamed:@\"doraemon_close_dark\"];\n            }\n        } else {\n#endif\n            _titleLabel.textColor = [UIColor doraemon_colorWithString:@\"#324456\"];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        \n        _titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(48)];\n        [self addSubview:_titleLabel];\n        \n        \n        _closeBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.doraemon_width-closeBtnH, offsetY, closeBtnH, closeBtnH)];\n        _closeBtn.imageView.contentMode = UIViewContentModeCenter;\n        [_closeBtn setImage:closeImage forState:UIControlStateNormal];\n        [_closeBtn addTarget:self action:@selector(closeClick) forControlEvents:UIControlEventTouchUpInside];\n        [self addSubview:_closeBtn];\n        \n        _downLine = [[UIView alloc] initWithFrame:CGRectMake(0, self.doraemon_height-kDoraemonSizeFrom750_Landscape(1), self.doraemon_width, kDoraemonSizeFrom750_Landscape(1))];\n        _downLine.backgroundColor = [UIColor doraemon_line];\n        [self addSubview:_downLine];\n    }\n    return self;\n}\n\n- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {\n    [super traitCollectionDidChange:previousTraitCollection];\n    // trait发生了改变\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {\n            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                [self.closeBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_close_dark\"] forState:UIControlStateNormal];\n            } else {\n                [self.closeBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_close\"] forState:UIControlStateNormal];\n            }\n        }\n    }\n#endif\n}\n\n- (void)setTitle:(NSString *)title {\n    _title = title;\n    _titleLabel.text = _title;\n}\n\n- (void)closeClick {\n    if (self.delegate && [self.delegate respondsToSelector:@selector(bigTitleCloseClick)]) {\n        [self.delegate bigTitleCloseClick];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Cache/DoraemonCacheManager.h",
    "content": "//\n//  DoraemonCacheManager.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/12.\n//\n\n#import <Foundation/Foundation.h>\n#import <MapKit/MapKit.h>\n\n@interface DoraemonCacheManager : NSObject\n\n+ (DoraemonCacheManager *)sharedInstance;\n\n- (void)saveLoggerSwitch:(BOOL)on;\n\n- (BOOL)loggerSwitch;\n\n- (void)saveMockGPSSwitch:(BOOL)on;\n\n- (BOOL)mockGPSSwitch;\n\n- (void)saveMockCoordinate:(CLLocationCoordinate2D)coordinate;\n\n- (CLLocationCoordinate2D)mockCoordinate;\n\n- (void)saveFpsSwitch:(BOOL)on;\n\n- (BOOL)fpsSwitch;\n\n- (void)saveCpuSwitch:(BOOL)on;\n\n- (BOOL)cpuSwitch;\n\n- (void)saveMemorySwitch:(BOOL)on;\n\n- (BOOL)memorySwitch;\n\n- (void)saveNetFlowSwitch:(BOOL)on;\n\n- (BOOL)netFlowSwitch;\n\n- (void)saveAllTestSwitch:(BOOL)on;\n\n- (BOOL)allTestSwitch;\n\n- (void)saveLargeImageDetectionSwitch:(BOOL)on;\n\n- (BOOL)largeImageDetectionSwitch;\n\n- (void)saveSubThreadUICheckSwitch:(BOOL)on;\n\n- (BOOL)subThreadUICheckSwitch;\n\n- (void)saveCrashSwitch:(BOOL)on;\n\n- (BOOL)crashSwitch;\n\n- (void)saveNSLogSwitch:(BOOL)on;\n\n- (BOOL)nsLogSwitch;\n\n- (void)saveMethodUseTimeSwitch:(BOOL)on;\n\n- (BOOL)methodUseTimeSwitch;\n\n- (void)saveStartTimeSwitch:(BOOL)on;\n\n- (BOOL)startTimeSwitch;\n\n- (void)saveANRTrackSwitch:(BOOL)on;\n\n- (BOOL)anrTrackSwitch;\n\n/// 历史记录\n- (NSArray<NSString *> *)h5historicalRecord;\n- (void)saveH5historicalRecordWithText:(NSString *)text;\n- (void)clearAllH5historicalRecord;\n- (void)clearH5historicalRecordWithText:(NSString *)text;\n\n/// JS历史脚本\n- (NSArray<NSDictionary *> *)jsHistoricalRecord;\n- (NSString *)jsHistoricalRecordForKey:(NSString *)key;\n- (void)saveJsHistoricalRecordWithText:(NSString *)text forKey:(NSString *)key;\n- (void)clearJsHistoricalRecordWithKey:(NSString *)key;\n\n/// 保存启动类\n- (void)saveStartClass : (NSString *)startClass;\n- (NSString *)startClass;\n\n// 内存泄漏开关\n- (void)saveMemoryLeak:(BOOL)on;\n- (BOOL)memoryLeak;\n\n// 内存泄漏弹框开关\n- (void)saveMemoryLeakAlert:(BOOL)on;\n- (BOOL)memoryLeakAlert;\n\n// mockapi本地缓存情况\n- (void)saveMockCache:(NSArray *)mocks;\n- (NSArray *)mockCahce;\n\n// 健康体检开关\n- (void)saveHealthStart:(BOOL)on;\n- (BOOL)healthStart;\n\n// Kit Manager数据保存\n- (void)saveKitManagerData:(NSMutableArray *)dataArray;\n- (NSMutableArray *)kitManagerData;\n- (NSMutableArray *)kitShowManagerData;\n- (NSMutableArray *)allKitShowManagerData;\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Cache/DoraemonCacheManager.m",
    "content": "//\n//  DoraemonCacheManager.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/12.\n//\n\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonManager.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonManager.h\"\n\nstatic NSString * const kDoraemonLoggerSwitchKey = @\"doraemon_env_key\";\nstatic NSString * const kDoraemonMockGPSSwitchKey = @\"doraemon_mock_gps_key\";\nstatic NSString * const kDoraemonMockCoordinateKey = @\"doraemon_mock_coordinate_key\";\nstatic NSString * const kDoraemonFpsKey = @\"doraemon_fps_key\";\nstatic NSString * const kDoraemonCpuKey = @\"doraemon_cpu_key\";\nstatic NSString * const kDoraemonMemoryKey = @\"doraemon_memory_key\";\nstatic NSString * const kDoraemonNetFlowKey = @\"doraemon_netflow_key\";\nstatic NSString * const kDoraemonSubThreadUICheckKey = @\"doraemon_sub_thread_ui_check_key\";\nstatic NSString * const kDoraemonCrashKey = @\"doraemon_crash_key\";\nstatic NSString * const kDoraemonNSLogKey = @\"doraemon_nslog_key\";\nstatic NSString * const kDoraemonMethodUseTimeKey = @\"doraemon_method_use_time_key\";\nstatic NSString * const kDoraemonLargeImageDetectionKey = @\"doraemon_large_image_detection_key\";\nstatic NSString * const kDoraemonH5historicalRecord = @\"doraemon_historical_record\";\nstatic NSString * const kDoraemonJsHistoricalRecord = @\"doraemon_js_historical_record\";\nstatic NSString * const kDoraemonStartTimeKey = @\"doraemon_start_time_key\";\nstatic NSString * const kDoraemonStartClassKey = @\"doraemon_start_class_key\";\nstatic NSString * const kDoraemonANRTrackKey = @\"doraemon_anr_track_key\";\nstatic NSString * const kDoraemonMemoryLeakKey = @\"doraemon_memory_leak_key\";\nstatic NSString * const kDoraemonMemoryLeakAlertKey = @\"doraemon_memory_leak_alert_key\";\nstatic NSString * const kDoraemonAllTestKey = @\"doraemon_allTest_window_key\";\nstatic NSString * const kDoraemonMockCacheKey = @\"doraemon_mock_cache_key\";\nstatic NSString * const kDoraemonHealthStartKey = @\"doraemon_health_start_key\";\n#define kDoraemonKitManagerKey [NSString stringWithFormat:@\"%@_doraemon_kit_manager_key\",DoKitVersion]\n\n@interface DoraemonCacheManager()\n\n@property (nonatomic, strong) NSUserDefaults *defaults;\n@property (nonatomic, assign) BOOL memoryLeakOn;\n@property (nonatomic, assign) BOOL firstReadMemoryLeakOn;\n\n@end\n\n@implementation DoraemonCacheManager\n\n+ (DoraemonCacheManager *)sharedInstance{\n    static dispatch_once_t once;\n    static DoraemonCacheManager *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonCacheManager alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init {\n    self  = [super init];\n    if (self) {\n        _defaults = [NSUserDefaults standardUserDefaults];\n    }\n    return self;\n}\n\n- (void)saveLoggerSwitch:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonLoggerSwitchKey];\n    [_defaults synchronize];\n}\n\n- (BOOL)loggerSwitch{\n    return [_defaults boolForKey:kDoraemonLoggerSwitchKey];\n\n}\n\n- (void)saveMockGPSSwitch:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonMockGPSSwitchKey];\n    [_defaults synchronize];\n}\n\n- (BOOL)mockGPSSwitch{\n    return [_defaults boolForKey:kDoraemonMockGPSSwitchKey];\n}\n\n- (void)saveMockCoordinate:(CLLocationCoordinate2D)coordinate{\n    NSDictionary *dic = @{\n                          @\"longitude\":@(coordinate.longitude),\n                          @\"latitude\":@(coordinate.latitude)\n                          };\n    [_defaults setObject:dic forKey:kDoraemonMockCoordinateKey];\n    [_defaults synchronize];\n}\n\n- (CLLocationCoordinate2D)mockCoordinate{\n    NSDictionary *dic = [_defaults valueForKey:kDoraemonMockCoordinateKey];\n    CLLocationCoordinate2D coordinate ;\n    if (dic[@\"longitude\"]) {\n        coordinate.longitude = [dic[@\"longitude\"] doubleValue];\n    }else{\n        coordinate.longitude = 0.;\n    }\n    if (dic[@\"latitude\"]) {\n        coordinate.latitude = [dic[@\"latitude\"] doubleValue];\n    }else{\n        coordinate.latitude = 0.;\n    }\n    \n    return coordinate;\n}\n\n- (void)saveFpsSwitch:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonFpsKey];\n    [_defaults synchronize];\n}\n\n- (BOOL)fpsSwitch{\n    return [_defaults boolForKey:kDoraemonFpsKey];\n}\n\n- (void)saveCpuSwitch:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonCpuKey];\n    [_defaults synchronize];\n}\n\n- (BOOL)cpuSwitch{\n    return [_defaults boolForKey:kDoraemonCpuKey];\n}\n\n- (void)saveMemorySwitch:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonMemoryKey];\n    [_defaults synchronize];\n}\n\n- (BOOL)memorySwitch{\n    return [_defaults boolForKey:kDoraemonMemoryKey];\n}\n\n- (void)saveNetFlowSwitch:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonNetFlowKey];\n    [_defaults synchronize];\n}\n\n- (BOOL)netFlowSwitch{\n    return [_defaults boolForKey:kDoraemonNetFlowKey];\n}\n\n- (void)saveAllTestSwitch:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonAllTestKey];\n    [_defaults synchronize];\n}\n\n- (BOOL)allTestSwitch{\n    return [_defaults boolForKey:kDoraemonAllTestKey];\n}\n\n- (void)saveLargeImageDetectionSwitch:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonLargeImageDetectionKey];\n    [_defaults synchronize];\n}\n\n- (BOOL)largeImageDetectionSwitch{\n    return [_defaults boolForKey: kDoraemonLargeImageDetectionKey];\n}\n\n- (void)saveSubThreadUICheckSwitch:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonSubThreadUICheckKey];\n    [_defaults synchronize];\n}\n\n- (BOOL)subThreadUICheckSwitch{\n    return [_defaults boolForKey:kDoraemonSubThreadUICheckKey];\n}\n\n- (void)saveCrashSwitch:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonCrashKey];\n    [_defaults synchronize];\n}\n\n- (BOOL)crashSwitch{\n    return [_defaults boolForKey:kDoraemonCrashKey];\n}\n\n- (void)saveNSLogSwitch:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonNSLogKey];\n    [_defaults synchronize];\n}\n\n- (BOOL)nsLogSwitch{\n    return [_defaults boolForKey:kDoraemonNSLogKey];\n}\n\n- (void)saveMethodUseTimeSwitch:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonMethodUseTimeKey];\n    [_defaults synchronize];\n}\n\n- (BOOL)methodUseTimeSwitch{\n    return [_defaults boolForKey:kDoraemonMethodUseTimeKey];\n}\n\n- (void)saveStartTimeSwitch:(BOOL)on {\n    [_defaults setBool:on forKey:kDoraemonStartTimeKey];\n    [_defaults synchronize];\n}\n\n- (BOOL)startTimeSwitch{\n    return [_defaults boolForKey:kDoraemonStartTimeKey];\n}\n\n- (void)saveANRTrackSwitch:(BOOL)on {\n    [_defaults setBool:on forKey:kDoraemonANRTrackKey];\n    [_defaults synchronize];\n}\n\n- (BOOL)anrTrackSwitch {\n    return [_defaults boolForKey:kDoraemonANRTrackKey];\n}\n\n- (NSArray<NSString *> *)h5historicalRecord {\n    return [_defaults objectForKey:kDoraemonH5historicalRecord];\n}\n\n- (void)saveH5historicalRecordWithText:(NSString *)text {\n    /// 过滤异常数据\n    if (!text || text.length <= 0) { return; }\n    \n    NSArray *records = [self h5historicalRecord];\n    \n    NSMutableArray *muarr = [NSMutableArray arrayWithArray:records];\n    \n    /// 去重\n    if ([muarr containsObject:text]) {\n        if ([muarr.firstObject isEqualToString:text]) {\n            return;\n        }\n        [muarr removeObject:text];\n    }\n    [muarr insertObject:text atIndex:0];\n    \n    /// 限制数量\n    if (muarr.count > 10) { [muarr removeLastObject]; }\n    \n    [_defaults setObject:muarr.copy forKey:kDoraemonH5historicalRecord];\n    [_defaults synchronize];\n}\n\n- (void)clearAllH5historicalRecord {\n    [_defaults removeObjectForKey:kDoraemonH5historicalRecord];\n    [_defaults synchronize];\n}\n\n- (void)clearH5historicalRecordWithText:(NSString *)text {\n    /// 过滤异常数据\n    if (!text || text.length <= 0) { return; }\n    NSArray *records = [self h5historicalRecord];\n    /// 不包含\n    if (![records containsObject:text]) { return; }\n    NSMutableArray *muarr = [NSMutableArray array];\n    if (records && records.count > 0) { [muarr addObjectsFromArray:records]; }\n    [muarr removeObject:text];\n    \n    \n    if (muarr.count > 0) {\n        [_defaults setObject:muarr.copy forKey:kDoraemonH5historicalRecord];\n    } else {\n        [_defaults removeObjectForKey:kDoraemonH5historicalRecord];\n    }\n    [_defaults synchronize];\n}\n\n- (NSArray<NSDictionary *> *)jsHistoricalRecord {\n    return [_defaults arrayForKey:kDoraemonJsHistoricalRecord];\n}\n\n- (NSString *)jsHistoricalRecordForKey:(NSString *)key {\n    NSArray *history = [self jsHistoricalRecord] ?: @[];\n    for (NSDictionary *dict in history) {\n        //是否同名配置\n        if ([[dict objectForKey:@\"key\"] isEqualToString:key]) {\n            return [dict objectForKey:@\"value\"];\n        }\n    }\n    return nil;\n}\n\n- (void)saveJsHistoricalRecordWithText:(NSString *)text forKey:(NSString *)key {\n    NSString *saveKey = [NSString stringWithFormat:@\"%.0f\", NSDate.date.timeIntervalSince1970];\n    if (key.length > 0) {\n        saveKey = key;\n    }\n    NSMutableArray *list = [NSMutableArray array];\n    BOOL matched = NO;\n    NSArray *history = [self jsHistoricalRecord] ?: @[];\n    for (NSDictionary *dict in history) {\n        //是否同名配置\n        if ([[dict objectForKey:@\"key\"] isEqualToString:saveKey]) {\n            [list addObject:@{\n                @\"key\": saveKey,\n                @\"value\": text\n            }];\n            matched = YES;\n            continue;\n        }\n        [list addObject:dict];\n    }\n    if (!matched) {\n        [list insertObject:@{\n            @\"key\": saveKey,\n            @\"value\": text\n        } atIndex:0];\n    }\n    [_defaults setObject:list forKey:kDoraemonJsHistoricalRecord];\n    [_defaults synchronize];\n}\n\n- (void)clearJsHistoricalRecordWithKey:(NSString *)key {\n    if (!key) {\n        return;\n    }\n    NSMutableArray *list = [NSMutableArray array];\n    NSArray *history = [self jsHistoricalRecord] ?: @[];\n    for (NSDictionary *dict in history) {\n        //是否同名配置\n        if ([[dict objectForKey:@\"key\"] isEqualToString:key]) {\n            continue;\n        }\n        [list addObject:dict];\n    }\n    [_defaults setObject:list forKey:kDoraemonJsHistoricalRecord];\n    [_defaults synchronize];\n}\n\n- (void)saveStartClass : (NSString *)startClass {\n    [_defaults setObject:startClass forKey:kDoraemonStartClassKey];\n    [_defaults synchronize];\n}\n\n- (NSString *)startClass {\n    NSString *startClass = [_defaults objectForKey:kDoraemonStartClassKey];\n    return startClass;\n}\n\n// 内存泄漏开关\n- (void)saveMemoryLeak:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonMemoryLeakKey];\n    [_defaults synchronize];\n}\n- (BOOL)memoryLeak{\n    if (_firstReadMemoryLeakOn) {\n        return _memoryLeakOn;\n    }\n    _firstReadMemoryLeakOn = YES;\n    _memoryLeakOn = [_defaults boolForKey:kDoraemonMemoryLeakKey];\n     \n    return _memoryLeakOn;\n}\n\n// 内存泄漏弹框开关\n- (void)saveMemoryLeakAlert:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonMemoryLeakAlertKey];\n    [_defaults synchronize];\n}\n- (BOOL)memoryLeakAlert{\n    return [_defaults boolForKey:kDoraemonMemoryLeakAlertKey];\n}\n\n// mockapi本地缓存情况\n- (void)saveMockCache:(NSArray *)mocks{\n    [_defaults setObject:mocks forKey:kDoraemonMockCacheKey];\n    [_defaults synchronize];\n}\n- (NSArray *)mockCahce{\n    return [_defaults objectForKey:kDoraemonMockCacheKey];\n}\n\n// 健康体检开关\n- (void)saveHealthStart:(BOOL)on{\n    [_defaults setBool:on forKey:kDoraemonHealthStartKey];\n    [_defaults synchronize];\n}\n- (BOOL)healthStart{\n    return [_defaults boolForKey:kDoraemonHealthStartKey];\n}\n\n// Kit Manager数据保存 只保存内部数据\n- (void)saveKitManagerData:(NSArray *)dataArray{\n    NSMutableArray *mutableDataArray = [[NSMutableArray alloc] init];\n    for (NSDictionary *dic in dataArray) {\n        NSString *moduleName = dic[@\"moduleName\"];\n        if (moduleName && ([moduleName isEqualToString:DoraemonLocalizedString(@\"常用工具\")] ||\n                           [moduleName isEqualToString:DoraemonLocalizedString(@\"性能检测\")] ||\n                           [moduleName isEqualToString:DoraemonLocalizedString(@\"视觉工具\")] ||\n                           [moduleName isEqualToString:DoraemonLocalizedString(@\"平台工具\")] ||\n                           [moduleName isEqualToString:@\"Weex\"])) {\n            NSArray *pluginArray = dic[@\"pluginArray\"];\n            NSMutableArray *mutablepluginArray = [[NSMutableArray alloc] init];\n            for (NSDictionary *subDic in pluginArray){\n                [mutablepluginArray addObject:subDic.mutableCopy];\n            }\n            NSMutableDictionary *mutableDic = [[NSMutableDictionary alloc] init];\n            [mutableDic setValue:dic[@\"moduleName\"] forKey:@\"moduleName\"];\n            [mutableDic setValue:mutablepluginArray forKey:@\"pluginArray\"];\n            \n            [mutableDataArray addObject:mutableDic];\n        }\n\n    }\n    [_defaults setObject:mutableDataArray forKey:kDoraemonKitManagerKey];\n    [_defaults synchronize];\n    [[NSNotificationCenter defaultCenter] postNotificationName:DoraemonKitManagerUpdateNotification object:nil userInfo:nil];\n}\n\n- (NSMutableArray *)kitManagerData{\n    //NSUserDefaults返回的对象都是不可变的,第一步要不他们都要变成可变的\n    NSArray *dataArray = [_defaults objectForKey:kDoraemonKitManagerKey];\n    NSMutableArray *mutableDataArray = [[NSMutableArray alloc] init];\n    for (NSDictionary *dic in dataArray) {\n        NSArray *pluginArray = dic[@\"pluginArray\"];\n        NSMutableArray *mutablepluginArray = [[NSMutableArray alloc] init];\n        for (NSDictionary *subDic in pluginArray){\n            [mutablepluginArray addObject:subDic.mutableCopy];\n        }\n        NSMutableDictionary *mutableDic = [[NSMutableDictionary alloc] init];\n        [mutableDic setValue:dic[@\"moduleName\"] forKey:@\"moduleName\"];\n        [mutableDic setValue:mutablepluginArray forKey:@\"pluginArray\"];\n        \n        [mutableDataArray addObject:mutableDic];\n    }\n    return mutableDataArray;\n}\n\n- (NSMutableArray *)kitShowManagerData{\n    //NSUserDefaults返回的对象都是不可变的,第一步要不他们都要变成可变的\n    NSArray *dataArray = [_defaults objectForKey:kDoraemonKitManagerKey];\n    NSMutableArray *mutableDataArray = [[NSMutableArray alloc] init];\n    for (NSDictionary *dic in dataArray) {\n        NSArray *pluginArray = dic[@\"pluginArray\"];\n        NSMutableArray *mutablepluginArray = [[NSMutableArray alloc] init];\n        for (NSDictionary *subDic in pluginArray){\n            BOOL show = [subDic[@\"show\"] boolValue];\n            if (show) {\n                [mutablepluginArray addObject:subDic.mutableCopy];\n            }\n        }\n        NSMutableDictionary *mutableDic = [[NSMutableDictionary alloc] init];\n        [mutableDic setValue:dic[@\"moduleName\"] forKey:@\"moduleName\"];\n        [mutableDic setValue:mutablepluginArray forKey:@\"pluginArray\"];\n        \n        [mutableDataArray addObject:mutableDic];\n    }\n    return mutableDataArray;\n}\n\n//外部数据+保存数据\n- (NSMutableArray *)allKitShowManagerData{\n     NSMutableArray *dataArray = [DoraemonManager shareInstance].dataArray;\n    NSMutableArray *mutableDataArray = [[NSMutableArray alloc] init];\n    if ([self kitShowManagerData].count>0) {\n        for (NSDictionary *dic in dataArray) {\n            NSString *moduleName = dic[@\"moduleName\"];\n            if (moduleName && ([moduleName isEqualToString:DoraemonLocalizedString(@\"常用工具\")] ||\n                               [moduleName isEqualToString:DoraemonLocalizedString(@\"性能检测\")] ||\n                               [moduleName isEqualToString:DoraemonLocalizedString(@\"视觉工具\")] ||\n                               [moduleName isEqualToString:DoraemonLocalizedString(@\"平台工具\")] ||\n                               [moduleName isEqualToString:@\"Weex\"])) {\n                continue;\n            }\n            \n            NSArray *pluginArray = dic[@\"pluginArray\"];\n            NSMutableArray *mutablepluginArray = [[NSMutableArray alloc] init];\n            for (NSDictionary *subDic in pluginArray){\n                [mutablepluginArray addObject:subDic.mutableCopy];\n            }\n            NSMutableDictionary *mutableDic = [[NSMutableDictionary alloc] init];\n            [mutableDic setValue:dic[@\"moduleName\"] forKey:@\"moduleName\"];\n            [mutableDic setValue:mutablepluginArray forKey:@\"pluginArray\"];\n            \n            [mutableDataArray addObject:mutableDic];\n\n        }\n        [mutableDataArray addObjectsFromArray:[self kitShowManagerData]];\n    }else{\n        for (NSDictionary *dic in dataArray) {\n            NSString *moduleName = dic[@\"moduleName\"];\n            if (moduleName && ([moduleName isEqualToString:DoraemonLocalizedString(@\"常用工具\")] ||\n                               [moduleName isEqualToString:DoraemonLocalizedString(@\"性能检测\")] ||\n                               [moduleName isEqualToString:DoraemonLocalizedString(@\"视觉工具\")] ||\n                               [moduleName isEqualToString:DoraemonLocalizedString(@\"平台工具\")] ||\n                               [moduleName isEqualToString:@\"Weex\"])) {\n                [mutableDataArray addObject:dic];\n                continue;\n            }\n            \n            NSArray *pluginArray = dic[@\"pluginArray\"];\n            NSMutableArray *mutablepluginArray = [[NSMutableArray alloc] init];\n            for (NSDictionary *subDic in pluginArray){\n                [mutablepluginArray addObject:subDic.mutableCopy];\n            }\n            NSMutableDictionary *mutableDic = [[NSMutableDictionary alloc] init];\n            [mutableDic setValue:dic[@\"moduleName\"] forKey:@\"moduleName\"];\n            [mutableDic setValue:mutablepluginArray forKey:@\"pluginArray\"];\n            if (mutableDic.allKeys.count) {\n                [mutableDataArray insertObject:mutableDic atIndex:0];\n            }\n        }\n    }\n    \n    return mutableDataArray;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Category/Foundation+Doraemon.m",
    "content": "//\n//  Foundation+Doraemon.m\n//  DoraemonKit\n//\n//  Created by DeveloperLY on 2019/8/18.\n//\n\n#import <Foundation/Foundation.h>\n\n#if DEBUG\n\n#define kUnicodeReadable 1\n#if kUnicodeReadable\n\n@implementation NSDictionary (DoraemonUnicodeReadable)\n\n- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level {\n    NSMutableString *desc = [NSMutableString string];\n    \n    NSMutableString *tabString = [[NSMutableString alloc] initWithCapacity:level];\n    for (NSUInteger i = 0; i < level; i++) {\n        [tabString appendString:@\"\\t\"];\n    }\n    \n    NSString *tab = @\"\";\n    if (level > 0) {\n        tab = tabString;\n    }\n    \n    [desc appendString:@\"{\\n\"];\n    \n    // 遍历数组,self就是当前的数组\n    for (id key in self.allKeys) {\n        id obj = [self objectForKey:key];\n        \n        if ([obj isKindOfClass:[NSString class]]) {\n            [desc appendFormat:@\"%@\\t%@ = \\\"%@\\\";\\n\", tab, key, obj];\n        } else if ([obj isKindOfClass:[NSArray class]]\n                   || [obj isKindOfClass:[NSDictionary class]]\n                   || [obj isKindOfClass:[NSSet class]]) {\n            [desc appendFormat:@\"%@\\t%@ = %@;\\n\", tab, key, [obj descriptionWithLocale:locale indent:level + 1]];\n        } else if ([obj isKindOfClass:[NSData class]]) {\n            // 如果是NSData类型，尝试去解析结果，以打印出可阅读的数据\n            NSError *error = nil;\n            NSObject *result =  [NSJSONSerialization JSONObjectWithData:obj\n                                                                options:NSJSONReadingMutableContainers\n                                                                  error:&error];\n            // 解析成功\n            if (error == nil && result != nil) {\n                if ([result isKindOfClass:[NSDictionary class]]\n                    || [result isKindOfClass:[NSArray class]]\n                    || [result isKindOfClass:[NSSet class]]) {\n                    NSString *str = [((NSDictionary *)result) descriptionWithLocale:locale indent:level + 1];\n                    [desc appendFormat:@\"%@\\t%@ = %@;\\n\", tab, key, str];\n                } else if ([obj isKindOfClass:[NSString class]]) {\n                    [desc appendFormat:@\"%@\\t%@ = \\\"%@\\\";\\n\", tab, key, result];\n                }\n            } else {\n                @try {\n                    NSString *str = [[NSString alloc] initWithData:obj encoding:NSUTF8StringEncoding];\n                    if (str != nil) {\n                        [desc appendFormat:@\"%@\\t%@ = \\\"%@\\\";\\n\", tab, key, str];\n                    } else {\n                        [desc appendFormat:@\"%@\\t%@ = %@;\\n\", tab, key, obj];\n                    }\n                }\n                @catch (NSException *exception) {\n                    [desc appendFormat:@\"%@\\t%@ = %@;\\n\", tab, key, obj];\n                }\n            }\n        } else {\n            [desc appendFormat:@\"%@\\t%@ = %@;\\n\", tab, key, obj];\n        }\n    }\n    \n    [desc appendFormat:@\"%@}\", tab];\n    \n    return desc;\n}\n\n@end\n\n\n@implementation NSArray (DoraemonUnicodeReadable)\n\n- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level {\n    NSMutableString *desc = [NSMutableString string];\n    \n    NSMutableString *tabString = [[NSMutableString alloc] initWithCapacity:level];\n    for (NSUInteger i = 0; i < level; i++) {\n        [tabString appendString:@\"\\t\"];\n    }\n    \n    NSString *tab = @\"\";\n    if (level > 0) {\n        tab = tabString;\n    }\n    [desc appendString:@\"(\\n\"];\n    \n    for (id obj in self) {\n        if ([obj isKindOfClass:[NSDictionary class]]\n            || [obj isKindOfClass:[NSArray class]]\n            || [obj isKindOfClass:[NSSet class]]) {\n            NSString *str = [((NSDictionary *)obj) descriptionWithLocale:locale indent:level + 1];\n            [desc appendFormat:@\"%@\\t%@,\\n\", tab, str];\n        } else if ([obj isKindOfClass:[NSString class]]) {\n            [desc appendFormat:@\"%@\\t\\\"%@\\\",\\n\", tab, obj];\n        } else if ([obj isKindOfClass:[NSData class]]) {\n            // 如果是NSData类型，尝试去解析结果，以打印出可阅读的数据\n            NSError *error = nil;\n            NSObject *result =  [NSJSONSerialization JSONObjectWithData:obj\n                                                                options:NSJSONReadingMutableContainers\n                                                                  error:&error];\n            // 解析成功\n            if (error == nil && result != nil) {\n                if ([result isKindOfClass:[NSDictionary class]]\n                    || [result isKindOfClass:[NSArray class]]\n                    || [result isKindOfClass:[NSSet class]]) {\n                    NSString *str = [((NSDictionary *)result) descriptionWithLocale:locale indent:level + 1];\n                    [desc appendFormat:@\"%@\\t%@,\\n\", tab, str];\n                } else if ([obj isKindOfClass:[NSString class]]) {\n                    [desc appendFormat:@\"%@\\t\\\"%@\\\",\\n\", tab, result];\n                }\n            } else {\n                @try {\n                    NSString *str = [[NSString alloc] initWithData:obj encoding:NSUTF8StringEncoding];\n                    if (str != nil) {\n                        [desc appendFormat:@\"%@\\t\\\"%@\\\",\\n\", tab, str];\n                    } else {\n                        [desc appendFormat:@\"%@\\t%@,\\n\", tab, obj];\n                    }\n                }\n                @catch (NSException *exception) {\n                    [desc appendFormat:@\"%@\\t%@,\\n\", tab, obj];\n                }\n            }\n        } else {\n            [desc appendFormat:@\"%@\\t%@,\\n\", tab, obj];\n        }\n    }\n    \n    [desc appendFormat:@\"%@)\", tab];\n    \n    NSRange range = [desc rangeOfString:@\",\\n\" options:NSBackwardsSearch];\n    \n    if (range.location == NSNotFound) {\n        return desc;\n    } else {\n        return [desc stringByReplacingOccurrencesOfString:@\",\\n\" withString:@\"\\n\" options:NSBackwardsSearch range:range];\n    }\n}\n\n@end\n\n\n@implementation NSSet (DoraemonUnicodeReadable)\n\n- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level {\n    NSMutableString *desc = [NSMutableString string];\n    \n    NSMutableString *tabString = [[NSMutableString alloc] initWithCapacity:level];\n    for (NSUInteger i = 0; i < level; i++) {\n        [tabString appendString:@\"\\t\"];\n    }\n    \n    NSString *tab = @\"\";\n    if (level > 0) {\n        tab = tabString;\n    }\n    [desc appendString:@\"{(\\n\"];\n    \n    for (id obj in self) {\n        if ([obj isKindOfClass:[NSDictionary class]]\n            || [obj isKindOfClass:[NSArray class]]\n            || [obj isKindOfClass:[NSSet class]]) {\n            NSString *str = [((NSDictionary *)obj) descriptionWithLocale:locale indent:level + 1];\n            [desc appendFormat:@\"%@\\t%@,\\n\", tab, str];\n        } else if ([obj isKindOfClass:[NSString class]]) {\n            [desc appendFormat:@\"%@\\t\\\"%@\\\",\\n\", tab, obj];\n        } else if ([obj isKindOfClass:[NSData class]]) {\n            // 如果是NSData类型，尝试去解析结果，以打印出可阅读的数据\n            NSError *error = nil;\n            NSObject *result =  [NSJSONSerialization JSONObjectWithData:obj\n                                                                options:NSJSONReadingMutableContainers\n                                                                  error:&error];\n            // 解析成功\n            if (error == nil && result != nil) {\n                if ([result isKindOfClass:[NSDictionary class]]\n                    || [result isKindOfClass:[NSArray class]]\n                    || [result isKindOfClass:[NSSet class]]) {\n                    NSString *str = [((NSDictionary *)result) descriptionWithLocale:locale indent:level + 1];\n                    [desc appendFormat:@\"%@\\t%@,\\n\", tab, str];\n                } else if ([obj isKindOfClass:[NSString class]]) {\n                    [desc appendFormat:@\"%@\\t\\\"%@\\\",\\n\", tab, result];\n                }\n            } else {\n                @try {\n                    NSString *str = [[NSString alloc] initWithData:obj encoding:NSUTF8StringEncoding];\n                    if (str != nil) {\n                        [desc appendFormat:@\"%@\\t\\\"%@\\\",\\n\", tab, str];\n                    } else {\n                        [desc appendFormat:@\"%@\\t%@,\\n\", tab, obj];\n                    }\n                }\n                @catch (NSException *exception) {\n                    [desc appendFormat:@\"%@\\t%@,\\n\", tab, obj];\n                }\n            }\n        } else {\n            [desc appendFormat:@\"%@\\t%@,\\n\", tab, obj];\n        }\n    }\n    \n    [desc appendFormat:@\"%@)}\", tab];\n    \n    NSRange range = [desc rangeOfString:@\",\\n\" options:NSBackwardsSearch];\n    \n    if (range.location == NSNotFound) {\n        return desc;\n    } else {\n        return [desc stringByReplacingOccurrencesOfString:@\",\\n\" withString:@\"\\n\" options:NSBackwardsSearch range:range];\n    }\n}\n\n@end\n\n#endif\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Category/NSObject+Doraemon.h",
    "content": "//\n//  NSObject+Doraemon.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/7/2.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface NSObject (Doraemon)\n\n/**\n swizzle 类方法\n \n @param oriSel 原有的方法\n @param swiSel swizzle的方法\n */\n+ (void)doraemon_swizzleClassMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel;\n\n/**\n swizzle 实例方法\n \n @param oriSel 原有的方法\n @param swiSel swizzle的方法\n */\n+ (void)doraemon_swizzleInstanceMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Category/NSObject+Doraemon.m",
    "content": "//\n//  NSObject+Doraemon.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/7/2.\n//\n\n#import \"NSObject+Doraemon.h\"\n#import <objc/runtime.h>\n\n@implementation NSObject (Doraemon)\n\n+ (void)doraemon_swizzleClassMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel {\n    Class cls = object_getClass(self);\n    \n    Method originAddObserverMethod = class_getClassMethod(cls, oriSel);\n    Method swizzledAddObserverMethod = class_getClassMethod(cls, swiSel);\n    \n    [self swizzleMethodWithOriginSel:oriSel oriMethod:originAddObserverMethod swizzledSel:swiSel swizzledMethod:swizzledAddObserverMethod class:cls];\n}\n\n+ (void)doraemon_swizzleInstanceMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel {\n    Method originAddObserverMethod = class_getInstanceMethod(self, oriSel);\n    Method swizzledAddObserverMethod = class_getInstanceMethod(self, swiSel);\n    \n    [self swizzleMethodWithOriginSel:oriSel oriMethod:originAddObserverMethod swizzledSel:swiSel swizzledMethod:swizzledAddObserverMethod class:self];\n}\n\n+ (void)swizzleMethodWithOriginSel:(SEL)oriSel\n                         oriMethod:(Method)oriMethod\n                       swizzledSel:(SEL)swizzledSel\n                    swizzledMethod:(Method)swizzledMethod\n                             class:(Class)cls {\n    BOOL didAddMethod = class_addMethod(cls, oriSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));\n    \n    if (didAddMethod) {\n        class_replaceMethod(cls, swizzledSel, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));\n    } else {\n        method_exchangeImplementations(oriMethod, swizzledMethod);\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Category/UIColor+Doraemon.h",
    "content": "//\n//  UIColor+Doraemon.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface UIColor (Doraemon)\n\n@property (nonatomic, readonly) CGFloat red; // Only valid if canProvideRGBComponents is YES\n@property (nonatomic, readonly) CGFloat green; // Only valid if canProvideRGBComponents is YES\n@property (nonatomic, readonly) CGFloat blue; // Only valid if canProvideRGBComponents is YES\n@property (nonatomic, readonly) CGFloat white; // Only valid if colorSpaceModel == kCGColorSpaceModelMonochrome\n@property (nonatomic, readonly) CGFloat alpha;\n\n\n+ (UIColor *)doraemon_colorWithHex:(UInt32)hex;\n+ (UIColor *)doraemon_colorWithHex:(UInt32)hex andAlpha:(CGFloat)alpha;\n+ (UIColor *)doraemon_colorWithHexString:(NSString *)hexString;\n\n+ (UIColor *)doraemon_colorWithString:(NSString *)hexString;\n\n+ (UIColor *)doraemon_black_1;//#333333\n+ (UIColor *)doraemon_black_2;//#666666\n+ (UIColor *)doraemon_black_3;//#999999\n\n+ (UIColor *)doraemon_blue;//#337CC4\n\n+ (UIColor *)doraemon_line;//[UIColor doraemon_colorWithHex:0x000000 andAlpha:0.1];\n\n+ (UIColor *)doraemon_randomColor;\n\n+ (UIColor *)doraemon_bg; //#F4F5F6\n\n+ (UIColor *)doraemon_orange; //#FF8903\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Category/UIColor+Doraemon.m",
    "content": "//\n//  UIColor+Doraemon.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import \"UIColor+Doraemon.h\"\n\nCGFloat doraemonColorComponentFrom(NSString *string, NSUInteger start, NSUInteger length) {\n    NSString *substring = [string substringWithRange:NSMakeRange(start, length)];\n    NSString *fullHex = length == 2 ? substring : [NSString stringWithFormat: @\"%@%@\", substring, substring];\n    \n    unsigned hexComponent;\n    [[NSScanner scannerWithString: fullHex] scanHexInt: &hexComponent];\n    return hexComponent / 255.0;\n}\n\n@implementation UIColor (DoraemonKit)\n\n- (CGColorSpaceModel)colorSpaceModel {\n    return CGColorSpaceGetModel(CGColorGetColorSpace(self.CGColor));\n}\n\n- (BOOL)canProvideRGBComponents {\n    switch (self.colorSpaceModel) {\n        case kCGColorSpaceModelRGB:\n        case kCGColorSpaceModelMonochrome:\n            return YES;\n        default:\n            return NO;\n    }\n}\n\n- (CGFloat)red {\n    NSAssert(self.canProvideRGBComponents, @\"Must be an RGB color to use -red\");\n    const CGFloat *c = CGColorGetComponents(self.CGColor);\n    return c[0];\n}\n\n- (CGFloat)green {\n    NSAssert(self.canProvideRGBComponents, @\"Must be an RGB color to use -green\");\n    const CGFloat *c = CGColorGetComponents(self.CGColor);\n    if (self.colorSpaceModel == kCGColorSpaceModelMonochrome) return c[0];\n    return c[1];\n}\n\n- (CGFloat)blue {\n    NSAssert(self.canProvideRGBComponents, @\"Must be an RGB color to use -blue\");\n    const CGFloat *c = CGColorGetComponents(self.CGColor);\n    if (self.colorSpaceModel == kCGColorSpaceModelMonochrome) return c[0];\n    return c[2];\n}\n\n- (CGFloat)white {\n    NSAssert(self.colorSpaceModel == kCGColorSpaceModelMonochrome, @\"Must be a Monochrome color to use -white\");\n    const CGFloat *c = CGColorGetComponents(self.CGColor);\n    return c[0];\n}\n\n- (CGFloat)alpha {\n    return CGColorGetAlpha(self.CGColor);\n}\n\n+ (UIColor *)doraemon_colorWithHex:(UInt32)hex{\n    return [UIColor doraemon_colorWithHex:hex andAlpha:1];\n}\n+ (UIColor *)doraemon_colorWithHex:(UInt32)hex andAlpha:(CGFloat)alpha{\n    return [UIColor colorWithRed:((hex >> 16) & 0xFF)/255.0\n                           green:((hex >> 8) & 0xFF)/255.0\n                            blue:(hex & 0xFF)/255.0\n                           alpha:alpha];\n}\n\n+ (UIColor *)doraemon_colorWithString:(NSString *)hexString{\n    return [self doraemon_colorWithHexString:hexString];\n}\n\n+ (UIColor *)doraemon_colorWithHexString:(NSString *)hexString {\n    CGFloat alpha, red, blue, green;\n    \n    NSString *colorString = [[hexString stringByReplacingOccurrencesOfString:@\"#\" withString:@\"\"] uppercaseString];\n    switch ([colorString length]) {\n        case 3: // #RGB\n            alpha = 1.0f;\n            red   = doraemonColorComponentFrom(colorString, 0, 1);\n            green = doraemonColorComponentFrom(colorString, 1, 1);\n            blue  = doraemonColorComponentFrom(colorString, 2, 1);\n            break;\n            \n        case 4: // #ARGB\n            alpha = doraemonColorComponentFrom(colorString, 0, 1);\n            red   = doraemonColorComponentFrom(colorString, 1, 1);\n            green = doraemonColorComponentFrom(colorString, 2, 1);\n            blue  = doraemonColorComponentFrom(colorString, 3, 1);\n            break;\n            \n        case 6: // #RRGGBB\n            alpha = 1.0f;\n            red   = doraemonColorComponentFrom(colorString, 0, 2);\n            green = doraemonColorComponentFrom(colorString, 2, 2);\n            blue  = doraemonColorComponentFrom(colorString, 4, 2);\n            break;\n            \n        case 8: // #AARRGGBB\n            alpha = doraemonColorComponentFrom(colorString, 0, 2);\n            red   = doraemonColorComponentFrom(colorString, 2, 2);\n            green = doraemonColorComponentFrom(colorString, 4, 2);\n            blue  = doraemonColorComponentFrom(colorString, 6, 2);\n            break;\n            \n        default:\n            return nil;\n    }\n    return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];\n}\n\n+ (UIColor *)doraemon_black_1 { // #333333\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        return [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n            if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {\n                return [UIColor doraemon_colorWithString:@\"#333333\"];\n            } else {\n                return [UIColor doraemon_colorWithString:@\"#DDDDDD\"];\n            }\n        }];\n    }\n#endif\n    return [UIColor doraemon_colorWithString:@\"#333333\"];\n}\n\n+ (UIColor *)doraemon_black_2 {  // #666666\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        return [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n            if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {\n                return [UIColor doraemon_colorWithString:@\"#666666\"];\n            } else {\n                return [UIColor doraemon_colorWithString:@\"#AAAAAA\"];\n            }\n        }];\n    }\n#endif\n    return [UIColor doraemon_colorWithString:@\"#666666\"];\n}\n\n+ (UIColor *)doraemon_black_3 {  // #999999\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        return [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n            if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {\n                return [UIColor doraemon_colorWithString:@\"#999999\"];\n            } else {\n                return [UIColor doraemon_colorWithString:@\"#666666\"];\n            }\n        }];\n    }\n#endif\n    return [UIColor doraemon_colorWithString:@\"#999999\"];\n}\n\n+ (UIColor *)doraemon_blue { // #337CC4\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        return [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n            if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {\n                return [UIColor doraemon_colorWithString:@\"#337CC4\"];\n            } else {\n                return [UIColor systemBlueColor];\n            }\n        }];\n    }\n#endif\n    return [UIColor doraemon_colorWithString:@\"#337CC4\"];\n}\n\n+ (UIColor *)doraemon_bg{ // #F4F5F6\n    return [UIColor doraemon_colorWithString:@\"#F4F5F6\"];\n}\n\n+ (UIColor *)doraemon_orange{ //#FF8903\n    return [UIColor doraemon_colorWithString:@\"#FF8903\"];\n}\n\n+ (UIColor *)doraemon_line {\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        return [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n            if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {\n                return [UIColor doraemon_colorWithHex:0x000000 andAlpha:0.1];\n            } else {\n                return [UIColor doraemon_colorWithHex:0x68686B andAlpha:0.6];\n            }\n        }];\n    }\n#endif\n    return [UIColor doraemon_colorWithHex:0x000000 andAlpha:0.1];\n}\n\n+ (UIColor *)doraemon_randomColor {\n    CGFloat red = ( arc4random() % 255 / 255.0 );\n    CGFloat green = ( arc4random() % 255 / 255.0 );\n    CGFloat blue = ( arc4random() % 255 / 255.0 );\n    return [UIColor colorWithRed:red green:green blue:blue alpha:1.0];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Category/UIImage+Doraemon.h",
    "content": "//\n//  UIImage+Doraemon.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UIImage (Doraemon)\n\n+ (nullable UIImage *)doraemon_imageNamed:(NSString *)name __attribute((deprecated(\"已废弃，请使用doraemon_xcassetImageNamed\")));\n\n+ (nullable UIImage *)doraemon_xcassetImageNamed:(NSString *)name;\n\n//压缩图片尺寸 等比缩放 通过计算得到缩放系数\n- (nullable UIImage*)doraemon_scaledToSize:(CGSize)newSize;\n\n/**\nCreate and return a 1x1 point size image with the given color.\n\n@param color  The color.\n*/\n+ (UIImage *)doraemon_imageWithColor:(UIColor *)color;\n\n/**\n Create and return a pure color image with the given color and size.\n \n @param color  The color.\n @param size   New image's type.\n */\n+ (UIImage *)doraemon_imageWithColor:(UIColor *)color size:(CGSize)size;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Category/UIImage+Doraemon.m",
    "content": "//\n//  UIImage+Doraemon.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import \"UIImage+Doraemon.h\"\n\n@class DoraemonManager;\n@implementation UIImage (Doraemon)\n\n+ (nullable UIImage *)doraemon_imageNamed:(NSString *)name{\n    if(name &&\n       ![name isEqualToString:@\"\"]){\n        NSBundle *bundle = [NSBundle bundleForClass:NSClassFromString(@\"DoraemonManager\")];\n        NSURL *url = [bundle URLForResource:@\"DoraemonKit\" withExtension:@\"bundle\"];\n        if(!url) return [UIImage new];\n        NSBundle *imageBundle = [NSBundle bundleWithURL:url];\n        \n        NSString *imageName = nil;\n        CGFloat scale = [UIScreen mainScreen].scale;\n        if (ABS(scale-3) <= 0.001) {\n            imageName = [NSString stringWithFormat:@\"%@@3x\",name];\n        } else {\n            imageName = [NSString stringWithFormat:@\"%@@2x\",name];\n        }\n        UIImage *image = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:imageName ofType:@\"png\"]];\n        if (!image) {\n            image = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:name ofType:@\"png\"]];\n            if (!image) {\n                image = [UIImage imageNamed:name];\n            }\n        }\n        return image;\n    }\n    \n    return nil;\n}\n\n+ (nullable UIImage *)doraemon_xcassetImageNamed:(NSString *)name {\n    if(name &&\n       ![name isEqualToString:@\"\"]){\n        NSBundle *bundle = [NSBundle bundleForClass:NSClassFromString(@\"DoraemonManager\")];\n        NSURL *url = [bundle URLForResource:@\"DoraemonKit\" withExtension:@\"bundle\"];\n        if(!url) return [UIImage new];\n        NSBundle *imageBundle = [NSBundle bundleWithURL:url];\n        UIImage *image = [UIImage imageNamed:name inBundle:imageBundle compatibleWithTraitCollection:nil];\n        return image;\n    }\n    \n    return nil;\n}\n\n//压缩图片尺寸 等比缩放 通过计算得到缩放系数\n- (nullable UIImage*)doraemon_scaledToSize:(CGSize)newSize{\n    UIImage *sourceImage = self;\n    UIImage *newImage = nil;\n    CGSize imageSize = sourceImage.size;\n    CGFloat width = imageSize.width;\n    CGFloat height = imageSize.height;\n    CGFloat targetWidth = newSize.width;\n    CGFloat targetHeight = newSize.height;\n    CGFloat scaleFactor = 0.0;\n    CGFloat scaledWidth = targetWidth;\n    CGFloat scaledHeight = targetHeight;\n    CGPoint thumbnailPoint = CGPointMake(0.0,0.0);\n    \n    if (CGSizeEqualToSize(imageSize, newSize) == NO)\n    {\n        CGFloat widthFactor = targetWidth / width;\n        CGFloat heightFactor = targetHeight / height;\n        if (widthFactor > heightFactor)\n            scaleFactor = widthFactor; // scale to fit height\n        else\n            scaleFactor = heightFactor; // scale to fit width\n        \n        scaledWidth= width * scaleFactor;\n        scaledHeight = height * scaleFactor;\n        // center the image\n        if (widthFactor > heightFactor)\n        {\n            thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;\n        }\n        else if (widthFactor < heightFactor)\n        {\n            thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;\n        }\n    }\n    \n    UIGraphicsBeginImageContextWithOptions(newSize, NO, [UIScreen mainScreen].scale);\n    CGRect thumbnailRect = CGRectZero;\n    thumbnailRect.origin = thumbnailPoint;\n    thumbnailRect.size.width= scaledWidth;\n    thumbnailRect.size.height = scaledHeight;\n    [sourceImage drawInRect:thumbnailRect];\n    newImage = UIGraphicsGetImageFromCurrentImageContext();\n    UIGraphicsEndImageContext();\n    \n    return newImage;\n}\n\n+ (UIImage *)doraemon_imageWithColor:(UIColor *)color {\n    return [self doraemon_imageWithColor:color size:CGSizeMake(1, 1)];\n}\n\n+ (UIImage *)doraemon_imageWithColor:(UIColor *)color size:(CGSize)size {\n    if (!color || size.width <= 0 || size.height <= 0) return [[UIImage alloc] init];\n    CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);\n    UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);\n    CGContextRef context = UIGraphicsGetCurrentContext();\n    CGContextSetFillColorWithColor(context, color.CGColor);\n    CGContextFillRect(context, rect);\n    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();\n    UIGraphicsEndImageContext();\n    return image;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Category/UIView+Doraemon.h",
    "content": "//\n//  UIView+Doraemon.h\n//  DoraemonKit\n//\n//  Created by xgb on 2018/11/15.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface UIView (Doraemon)\n\n/** View's X Position */\n@property (nonatomic, assign) CGFloat   doraemon_x;\n\n/** View's Y Position */\n@property (nonatomic, assign) CGFloat   doraemon_y;\n\n/** View's width */\n@property (nonatomic, assign) CGFloat   doraemon_width;\n\n/** View's height */\n@property (nonatomic, assign) CGFloat   doraemon_height;\n\n/** View's origin - Sets X and Y Positions */\n@property (nonatomic, assign) CGPoint   doraemon_origin;\n\n/** View's size - Sets Width and Height */\n@property (nonatomic, assign) CGSize    doraemon_size;\n\n/** Y value representing the bottom of the view **/\n@property (nonatomic, assign) CGFloat   doraemon_bottom;\n\n/** X Value representing the right side of the view **/\n@property (nonatomic, assign) CGFloat   doraemon_right;\n\n/** X Value representing the top of the view (alias of x) **/\n@property (nonatomic, assign) CGFloat   doraemon_left;\n\n/** Y Value representing the top of the view (alias of y) **/\n@property (nonatomic, assign) CGFloat   doraemon_top;\n\n/** X value of the object's center **/\n@property (nonatomic, assign) CGFloat   doraemon_centerX;\n\n/** Y value of the object's center **/\n@property (nonatomic, assign) CGFloat   doraemon_centerY;\n\n- (UIViewController *)doraemon_viewController;\n\n- (NSArray *)doraemon_findViewsForClass:(Class)clazz;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Category/UIView+Doraemon.m",
    "content": "//\n//  UIView+Doraemon.m\n//  DoraemonKit\n//\n//  Created by xgb on 2018/11/15.\n//\n\n#import \"UIView+Doraemon.h\"\n\n#define Doraemon_SCREEN_SCALE                    ([[UIScreen mainScreen] scale])\n#define Doraemon_PIXEL_INTEGRAL(pointValue)      (round(pointValue * Doraemon_SCREEN_SCALE) / Doraemon_SCREEN_SCALE)\n\n@implementation UIView (Doraemon)\n\n@dynamic doraemon_x, doraemon_y, doraemon_width, doraemon_height, doraemon_origin, doraemon_size;\n\n// Setters\n-(void)setDoraemon_x:(CGFloat)x{\n    self.frame      = CGRectMake(Doraemon_PIXEL_INTEGRAL(x), self.doraemon_y, self.doraemon_width, self.doraemon_height);\n}\n\n-(void)setDoraemon_y:(CGFloat)y{\n    self.frame      = CGRectMake(self.doraemon_x, Doraemon_PIXEL_INTEGRAL(y), self.doraemon_width, self.doraemon_height);\n}\n\n-(void)setDoraemon_width:(CGFloat)width{\n    self.frame      = CGRectMake(self.doraemon_x, self.doraemon_y, Doraemon_PIXEL_INTEGRAL(width), self.doraemon_height);\n}\n\n-(void)setDoraemon_height:(CGFloat)height{\n    self.frame      = CGRectMake(self.doraemon_x, self.doraemon_y, self.doraemon_width, Doraemon_PIXEL_INTEGRAL(height));\n}\n\n-(void)setDoraemon_origin:(CGPoint)origin{\n    self.doraemon_x          = origin.x;\n    self.doraemon_y          = origin.y;\n}\n\n-(void)setDoraemon_size:(CGSize)size{\n    self.doraemon_width      = size.width;\n    self.doraemon_height     = size.height;\n}\n\n-(void)setDoraemon_right:(CGFloat)right {\n    self.doraemon_x          = right - self.doraemon_width;\n}\n\n-(void)setDoraemon_bottom:(CGFloat)bottom {\n    self.doraemon_y          = bottom - self.doraemon_height;\n}\n\n-(void)setDoraemon_left:(CGFloat)left{\n    self.doraemon_x          = left;\n}\n\n-(void)setDoraemon_top:(CGFloat)top{\n    self.doraemon_y          = top;\n}\n\n-(void)setDoraemon_centerX:(CGFloat)centerX {\n    self.center     = CGPointMake(Doraemon_PIXEL_INTEGRAL(centerX), self.center.y);\n}\n\n-(void)setDoraemon_centerY:(CGFloat)centerY {\n    self.center     = CGPointMake(self.center.x, Doraemon_PIXEL_INTEGRAL(centerY));\n}\n\n// Getters\n-(CGFloat)doraemon_x{\n    return self.frame.origin.x;\n}\n\n-(CGFloat)doraemon_y{\n    return self.frame.origin.y;\n}\n\n-(CGFloat)doraemon_width{\n    return self.frame.size.width;\n}\n\n-(CGFloat)doraemon_height{\n    return self.frame.size.height;\n}\n\n-(CGPoint)doraemon_origin{\n    return CGPointMake(self.doraemon_x, self.doraemon_y);\n}\n\n-(CGSize)doraemon_size{\n    return CGSizeMake(self.doraemon_width, self.doraemon_height);\n}\n\n-(CGFloat)doraemon_right {\n    return self.frame.origin.x + self.frame.size.width;\n}\n\n-(CGFloat)doraemon_bottom {\n    return self.frame.origin.y + self.frame.size.height;\n}\n\n-(CGFloat)doraemon_left{\n    return self.doraemon_x;\n}\n\n-(CGFloat)doraemon_top{\n    return self.doraemon_y;\n}\n\n-(CGFloat)doraemon_centerX {\n    return self.center.x;\n}\n\n-(CGFloat)doraemon_centerY {\n    return self.center.y;\n}\n\n-(UIViewController *)doraemon_viewController{\n    for(UIView *next =self.superview ; next ; next = next.superview){\n        UIResponder*nextResponder = [next nextResponder];\n        if([nextResponder isKindOfClass:[UIViewController class]]){\n            return(UIViewController*)nextResponder;\n        }\n    }\n    return nil;\n}\n\n- (NSArray *)doraemon_findViewsForClass:(Class)clazz {\n    NSMutableArray *result = [NSMutableArray array];\n    for (UIView *subview in self.subviews) {\n        if ([subview isKindOfClass:clazz]) {\n            [result addObject:subview];\n        }\n        [result addObjectsFromArray:[subview doraemon_findViewsForClass:clazz]];\n    }\n    return result;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Category/UIViewController+Doraemon.h",
    "content": "//\n//  UIViewController+Doraemon.h\n//  DoraemonKit\n//\n//  Created by dengyouhua on 2019/9/5.\n//\n#import <UIKit/UIKit.h>\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UIViewController (Doraemon)\n\n// safe area inset\n- (UIEdgeInsets)safeAreaInset;\n\n// 默认采用view frame | 调整刘海屏 | 支持转向调整\n- (CGRect) fullscreen;\n\n// key window root vc\n+ (UIViewController *)rootViewControllerForKeyWindow;\n\n// key window top vc\n+ (UIViewController *)topViewControllerForKeyWindow;\n\n+ (UIViewController *)rootViewControllerForDoraemonHomeWindow;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Category/UIViewController+Doraemon.m",
    "content": "//\n//  UIViewController+Doraemon.m\n//  DoraemonKit\n//\n//  Created by dengyouhua on 2019/9/5.\n//\n\n#import \"UIViewController+Doraemon.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonUtil.h\"\n\n@implementation UIViewController (Doraemon)\n\n- (UIEdgeInsets)safeAreaInset:(UIView *)view {\n    if (@available(iOS 11.0, *)) {\n        return view.safeAreaInsets;\n    }\n    return UIEdgeInsetsZero;\n}\n\n// safe area inset\n- (UIEdgeInsets)safeAreaInset {\n    return [self safeAreaInset:self.view];\n}\n\n// 默认采用view frame | 调整刘海屏 | 支持转向调整\n- (CGRect) fullscreen {\n    CGRect screen = self.view.frame;\n    UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;\n    switch (orientation) {\n        case UIInterfaceOrientationLandscapeLeft:\n        case UIInterfaceOrientationLandscapeRight:\n        {\n            CGSize size = self.view.doraemon_size;\n            if (size.width > size.height) {\n                UIEdgeInsets safeAreaInsets = [self safeAreaInset];\n                CGRect frame = screen;\n                CGFloat width = self.view.doraemon_width - safeAreaInsets.left - safeAreaInsets.right;\n                frame.origin.x = safeAreaInsets.left;\n                frame.size.width = width;\n                screen = frame;\n            }\n        }\n            break;\n        default:\n        {\n            UIEdgeInsets safeAreaInsets = [self safeAreaInset];\n            CGRect frame = screen;\n            frame.origin.y = safeAreaInsets.top;\n            frame.size.height = self.view.doraemon_height - safeAreaInsets.top;\n            screen = frame;\n        }\n            break;\n    }\n    \n    return screen;\n}\n\n+ (UIViewController *)rootViewControllerForKeyWindow{\n    UIWindow *keyWindow = [DoraemonUtil getKeyWindow];\n    return [keyWindow rootViewController];\n}\n\n+ (UIViewController *)topViewControllerForKeyWindow {\n    UIViewController *resultVC;\n    UIWindow *keyWindow = [DoraemonUtil getKeyWindow];\n    resultVC = [self _topViewController:[keyWindow rootViewController]];\n    while (resultVC.presentedViewController) {\n        resultVC = [self _topViewController:resultVC.presentedViewController];\n    }\n    return resultVC;\n}\n\n+ (UIViewController *)_topViewController:(UIViewController *)vc {\n    if ([vc isKindOfClass:[UINavigationController class]]) {\n        return [self _topViewController:[(UINavigationController *)vc topViewController]];\n    } else if ([vc isKindOfClass:[UITabBarController class]]) {\n        return [self _topViewController:[(UITabBarController *)vc selectedViewController]];\n    } else {\n        return vc;\n    }\n    return nil;\n}\n\n+ (UIViewController *)rootViewControllerForDoraemonHomeWindow{\n    return [DoraemonHomeWindow shareInstance].rootViewController;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Alert/DoraemonAlertUtil.h",
    "content": "//\n//  DoraemonAlertUtil.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/8/27.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\ntypedef void (^DoraemonAlertOKActionBlock)(void);\ntypedef void (^DoraemonAlertCancleActionBlock)(void);\n\n@interface DoraemonAlertUtil : NSObject\n\n+ (void)handleAlertActionWithVC:(UIViewController *)vc\n                     okBlock:(DoraemonAlertOKActionBlock)okBlock\n                      cancleBlock:(DoraemonAlertCancleActionBlock)cancleBlock;\n\n\n+ (void)handleAlertActionWithVC:(UIViewController *)vc\n                             text:(NSString *)text\n                     okBlock:(DoraemonAlertOKActionBlock)okBlock\n                      cancleBlock:(DoraemonAlertCancleActionBlock)cancleBlock;\n\n+ (void)handleAlertActionWithVC:(UIViewController *)vc\n                           text:(NSString *)text\n                        okBlock:(DoraemonAlertOKActionBlock)okBlock;\n\n+ (void)handleAlertActionWithVC:(UIViewController *)vc\n                          title: (NSString *)title\n                           text:(NSString *)text\n                             ok:(NSString *)ok\n                        okBlock:(DoraemonAlertOKActionBlock)okBlock;\n\n+ (void)handleAlertActionWithVC:(UIViewController *)vc\n                          title: (NSString *)title\n                           text:(NSString *)text\n                             ok:(NSString *)ok\n                         cancel:(NSString *)cancel\n                        okBlock:(DoraemonAlertOKActionBlock)okBlock\n                    cancleBlock:(DoraemonAlertCancleActionBlock)cancleBlock;\n\n\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Alert/DoraemonAlertUtil.m",
    "content": "//\n//  DoraemonAlertUtil.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/8/27.\n//\n\n#import \"DoraemonAlertUtil.h\"\n#import \"Doraemoni18NUtil.h\"\n\n@implementation DoraemonAlertUtil\n\n+ (void)handleAlertActionWithVC:(UIViewController *)vc\n                        okBlock:(DoraemonAlertOKActionBlock)okBlock\n                    cancleBlock:(DoraemonAlertCancleActionBlock)cancleBlock {\n    [self handleAlertActionWithVC:vc text:DoraemonLocalizedString(@\"该功能需要重启App才能生效\") okBlock:okBlock cancleBlock:cancleBlock];\n}\n\n\n+ (void)handleAlertActionWithVC:(UIViewController *)vc\n                           text:(NSString *)text\n                        okBlock:(DoraemonAlertOKActionBlock)okBlock\n                    cancleBlock:(DoraemonAlertCancleActionBlock)cancleBlock {\n    UIAlertController * alertController = [UIAlertController alertControllerWithTitle:DoraemonLocalizedString(@\"提示\") message:text preferredStyle:UIAlertControllerStyleAlert];\n    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"取消\") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {\n        cancleBlock ? cancleBlock():nil;\n    }];\n    UIAlertAction *okAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"确定\") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n        okBlock ? okBlock():nil;\n    }];\n    [alertController addAction:cancelAction];\n    [alertController addAction:okAction];\n    [vc presentViewController:alertController animated:YES completion:nil];\n}\n\n+ (void)handleAlertActionWithVC:(UIViewController *)vc\n                           text:(NSString *)text\n                        okBlock:(DoraemonAlertOKActionBlock)okBlock{\n    UIAlertController * alertController = [UIAlertController alertControllerWithTitle:DoraemonLocalizedString(@\"提示\") message:text preferredStyle:UIAlertControllerStyleAlert];\n    UIAlertAction *okAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"确定\") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n        okBlock ? okBlock():nil;\n    }];\n    [alertController addAction:okAction];\n    [vc presentViewController:alertController animated:YES completion:nil];\n}\n\n+ (void)handleAlertActionWithVC:(UIViewController *)vc\n                          title: (NSString *)title\n                           text:(NSString *)text\n                             ok:(NSString *)ok\n                        okBlock:(DoraemonAlertOKActionBlock)okBlock{\n    UIAlertController * alertController = [UIAlertController alertControllerWithTitle:title message:text preferredStyle:UIAlertControllerStyleAlert];\n    UIAlertAction *okAction = [UIAlertAction actionWithTitle:ok style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n        okBlock ? okBlock():nil;\n    }];\n    [alertController addAction:okAction];\n    [vc presentViewController:alertController animated:YES completion:nil];\n}\n\n+ (void)handleAlertActionWithVC:(UIViewController *)vc\n                          title: (NSString *)title\n                           text:(NSString *)text\n                             ok:(NSString *)ok\n                         cancel:(NSString *)cancel\n                        okBlock:(DoraemonAlertOKActionBlock)okBlock\n                    cancleBlock:(DoraemonAlertCancleActionBlock)cancleBlock {\n    UIAlertController * alertController = [UIAlertController alertControllerWithTitle:title message:text preferredStyle:UIAlertControllerStyleAlert];\n    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancel style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {\n        cancleBlock ? cancleBlock():nil;\n    }];\n    UIAlertAction *okAction = [UIAlertAction actionWithTitle:ok style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n        okBlock ? okBlock():nil;\n    }];\n    \n    [alertController addAction:cancelAction];\n    [alertController addAction:okAction];\n    [vc presentViewController:alertController animated:YES completion:nil];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/CellBtn/DoraemonCellButton.h",
    "content": "//\n//  DoraemonCellButton.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/15.\n//\n\n#import <UIKit/UIKit.h>\n\n@protocol DoraemonCellButtonDelegate<NSObject>\n\n- (void)cellBtnClick:(id)sender;\n\n@end\n\n@interface DoraemonCellButton : UIView\n\n@property (nonatomic, weak) id<DoraemonCellButtonDelegate> delegate;\n\n- (void)renderUIWithTitle:(NSString *)title;\n\n- (void)renderUIWithRightContent:(NSString *)rightContent;\n\n- (void)needTopLine;\n\n- (void)needDownLine;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/CellBtn/DoraemonCellButton.m",
    "content": "//\n//  DoraemonCellButton.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/15.\n//\n\n#import \"DoraemonCellButton.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonCellButton()\n\n@property (nonatomic, strong) UILabel *titleLabel;\n@property (nonatomic, strong) UILabel *rightLabel;\n@property (nonatomic, strong) UIView *topLine;\n@property (nonatomic, strong) UIView *downLine;\n@property (nonatomic, strong) UIImageView *arrowImageView;\n\n@end\n\n@implementation DoraemonCellButton\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        _titleLabel = [[UILabel alloc] init];\n        _titleLabel.textColor = [UIColor doraemon_black_1];\n        _titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        [self addSubview:_titleLabel];\n        \n        _topLine = [[UIView alloc] init];\n        _topLine.hidden = YES;\n        _topLine.backgroundColor = [UIColor doraemon_line];\n        [self addSubview:_topLine];\n        \n        _downLine = [[UIView alloc] init];\n        _downLine.hidden = YES;\n        _downLine.backgroundColor = [UIColor doraemon_line];\n        [self addSubview:_downLine];\n        \n        _arrowImageView = [[UIImageView alloc] initWithImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_more\"]];\n        _arrowImageView.frame = CGRectMake(self.doraemon_width-kDoraemonSizeFrom750_Landscape(32)-_arrowImageView.doraemon_width, self.doraemon_height/2-_arrowImageView.doraemon_height/2, _arrowImageView.doraemon_width, _arrowImageView.doraemon_height);\n        [self addSubview:_arrowImageView];\n        \n        _rightLabel = [[UILabel alloc] init];\n        _rightLabel.hidden = YES;\n        _rightLabel.textColor = [UIColor doraemon_black_2];\n        _rightLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        [self addSubview:_rightLabel];\n        \n        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];\n        self.userInteractionEnabled = YES;\n        [self addGestureRecognizer:tap];\n    }\n    return self;\n}\n\n- (void)renderUIWithTitle:(NSString *)title{\n    _titleLabel.text = title;\n    [_titleLabel sizeToFit];\n    _titleLabel.frame = CGRectMake(20, self.doraemon_height/2-_titleLabel.doraemon_height/2, _titleLabel.doraemon_width, _titleLabel.doraemon_height);\n}\n\n- (void)renderUIWithRightContent:(NSString *)rightContent{\n    _rightLabel.hidden = NO;\n    _rightLabel.text = rightContent;\n    [_rightLabel sizeToFit];\n    _rightLabel.frame = CGRectMake(_arrowImageView.doraemon_left-kDoraemonSizeFrom750_Landscape(24)-_rightLabel.doraemon_width, self.doraemon_height/2-_rightLabel.doraemon_height/2, _rightLabel.doraemon_width, _rightLabel.doraemon_height);\n}\n\n- (void)needTopLine{\n    _topLine.hidden = NO;\n    _topLine.frame = CGRectMake(0, 0, self.doraemon_width, 0.5);\n}\n\n- (void)needDownLine{\n    _downLine.hidden = NO;\n    _downLine.frame = CGRectMake(0, self.doraemon_height-0.5, self.doraemon_width, 0.5);\n}\n\n- (void)tap{\n    if (_delegate && [_delegate respondsToSelector:@selector(cellBtnClick:)]) {\n        [_delegate cellBtnClick:self];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/CellInput/DoraemonCellInput.h",
    "content": "//\n//  DoraemonCellInput.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/7.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonCellInput : UIView\n\n@property (nonatomic, strong) UITextField *textField;\n\n- (void)renderUIWithTitle:(NSString *)title;\n\n- (void)renderUIWithPlaceholder:(NSString *)placeholder;\n\n- (void)needTopLine;\n\n- (void)needDownLine;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/CellInput/DoraemonCellInput.m",
    "content": "//\n//  DoraemonCellInput.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/7.\n//\n\n#import \"DoraemonCellInput.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonCellInput()\n\n@property (nonatomic, strong) UILabel *titleLabel;\n@property (nonatomic, strong) UIView *topLine;\n@property (nonatomic, strong) UIView *downLine;\n\n@end\n\n@implementation DoraemonCellInput\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        _titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(32), 0, kDoraemonSizeFrom750_Landscape(150), self.doraemon_height)];\n        _titleLabel.textColor = [UIColor doraemon_black_1];\n        _titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        [self addSubview:_titleLabel];\n        \n        _textField = [[UITextField alloc] initWithFrame:CGRectMake(_titleLabel.doraemon_right+kDoraemonSizeFrom750_Landscape(30), self.doraemon_height/2-kDoraemonSizeFrom750_Landscape(48)/2, self.doraemon_width-_titleLabel.doraemon_right-kDoraemonSizeFrom750_Landscape(30), kDoraemonSizeFrom750_Landscape(48))];\n        [self addSubview:_textField];\n        \n        _topLine = [[UIView alloc] init];\n        _topLine.hidden = YES;\n        _topLine.backgroundColor = [UIColor doraemon_line];\n        [self addSubview:_topLine];\n        \n        _downLine = [[UIView alloc] init];\n        _downLine.hidden = YES;\n        _downLine.backgroundColor = [UIColor doraemon_line];\n        [self addSubview:_downLine];\n    }\n    return self;\n}\n\n- (void)renderUIWithTitle:(NSString *)title{\n    _titleLabel.text = title;\n}\n\n- (void)renderUIWithPlaceholder:(NSString *)placeholder{\n    _textField.placeholder = placeholder;\n}\n\n- (void)needTopLine{\n    _topLine.hidden = NO;\n    _topLine.frame = CGRectMake(0, 0, self.doraemon_width, 0.5);\n}\n\n- (void)needDownLine{\n    _downLine.hidden = NO;\n    _downLine.frame = CGRectMake(0, self.doraemon_height-0.5, self.doraemon_width, 0.5);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/CellSwitch/DoraemonCellSwitch.h",
    "content": "//\n//  DoraemonSwitchView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/13.\n//\n\n#import <UIKit/UIKit.h>\n\n@protocol DoraemonSwitchViewDelegate<NSObject>\n\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender;\n\n@end\n\n@interface DoraemonCellSwitch : UIView\n\n@property (nonatomic, weak) id<DoraemonSwitchViewDelegate> delegate;\n\n@property (nonatomic, strong) UISwitch *switchView;\n\n- (void)renderUIWithTitle:(NSString *)title switchOn:(BOOL)on;\n\n- (void)needTopLine;\n\n- (void)needDownLine;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/CellSwitch/DoraemonCellSwitch.m",
    "content": "//\n//  DoraemonSwitchView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/13.\n//\n\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonCellSwitch()\n\n@property (nonatomic, strong) UILabel *titleLabel;\n@property (nonatomic, strong) UIView *topLine;\n@property (nonatomic, strong) UIView *downLine;\n\n@end\n\n@implementation DoraemonCellSwitch\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        _titleLabel = [[UILabel alloc] init];\n        _titleLabel.textColor = [UIColor doraemon_black_1];\n        _titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        [self addSubview:_titleLabel];\n        \n        _switchView = [[UISwitch alloc] init];\n        _switchView.onTintColor = [UIColor doraemon_blue];\n        [_switchView addTarget:self action:@selector(switchChange:)forControlEvents:UIControlEventValueChanged];\n        [self addSubview:_switchView];\n        \n        _topLine = [[UIView alloc] init];\n        _topLine.hidden = YES;\n        _topLine.backgroundColor = [UIColor doraemon_line];\n        [self addSubview:_topLine];\n        \n        _downLine = [[UIView alloc] init];\n        _downLine.hidden = YES;\n        _downLine.backgroundColor = [UIColor doraemon_line];\n        [self addSubview:_downLine];\n    }\n    return self;\n}\n\n- (void)renderUIWithTitle:(NSString *)title switchOn:(BOOL)on{\n    _titleLabel.text = title;\n    [_titleLabel sizeToFit];\n    _titleLabel.frame = CGRectMake(20, self.doraemon_height/2-_titleLabel.doraemon_height/2, _titleLabel.doraemon_width, _titleLabel.doraemon_height);\n    \n    _switchView.on = on;\n    _switchView.frame = CGRectMake(self.doraemon_width-20-_switchView.doraemon_width, self.doraemon_height/2-_switchView.doraemon_height/2, _switchView.doraemon_width, _switchView.doraemon_height);\n}\n\n- (void)needTopLine{\n    _topLine.hidden = NO;\n    _topLine.frame = CGRectMake(0, 0, self.doraemon_width, 0.5);\n}\n\n- (void)needDownLine{\n    _downLine.hidden = NO;\n    _downLine.frame = CGRectMake(0, self.doraemon_height-0.5, self.doraemon_width, 0.5);\n}\n\n- (void)switchChange:(UISwitch*)sender{\n    BOOL on = sender.on;\n    if (_delegate && [_delegate respondsToSelector:@selector(changeSwitchOn:sender:)]) {\n        [_delegate changeSwitchOn:on sender:sender];\n    }\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Charts/DoraemonBarChart.h",
    "content": "//\n//  BarChart.h\n//  DoraemonKit\n//\n//  Created by 0xd on 2019/9/11.\n//  Copyright © 2019 000. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonChart.h\"\n#import \"DoraemonXAxis.h\"\n#import \"DoraemonYAxis.h\"\n#import \"DoraemonChartDataItem.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonBarChart : DoraemonChart\n@property (nonatomic, strong) DoraemonXAxis *xAxis;\n@property (nonatomic, strong) DoraemonYAxis *yAxis;\n@property (nonatomic, assign) CGFloat barsSpacingRatio;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Charts/DoraemonBarChart.m",
    "content": "//\n//  BarChart.m\n//  DoraemonKit\n//\n//  Created by 0xd on 2019/9/11.\n//  Copyright © 2019 000. All rights reserved.\n//\n\n#import \"DoraemonBarChart.h\"\n\n@implementation DoraemonBarChart\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    if (self) {\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            self.backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n                if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                    return [UIColor secondarySystemBackgroundColor];\n                } else {\n                    return [UIColor whiteColor];\n                }\n            }];\n        } else {\n#endif\n            self.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        self.xAxis = [[DoraemonXAxis alloc] init];\n        self.yAxis = [[DoraemonYAxis alloc] init];\n        self.yAxis.labelCount = 5;\n        self.vauleFormatter = self.vauleFormatter;\n        self.barsSpacingRatio = 1;\n    }\n    return self;\n}\n\n- (void)display {\n    [super display];\n    NSMutableArray<NSNumber *> *yAxisValues = [NSMutableArray arrayWithCapacity:self.items.count];\n    for (DoraemonChartDataItem *entry in self.items) {\n        [yAxisValues addObject: [NSNumber numberWithDouble:entry.value]];\n    }\n    self.yAxis.values = [yAxisValues copy];\n    [self.yAxis update];\n    [self setNeedsDisplay];\n}\n\n- (void)drawRect:(CGRect)rect {\n    CGContextRef context = UIGraphicsGetCurrentContext();\n    [self drawXAxisWithContext:context];\n    [self drawYAxisWithContext:context];\n    [self drawBarsWithContext:context];\n}\n\n- (void) drawXAxisWithContext:(CGContextRef)context {\n    if (!self.xAxis) {\n        return;\n    }\n    CGContextSaveGState(context);\n    CGContextSetStrokeColorWithColor(context, self.xAxis.axisLineColor.CGColor);\n    CGContextSetLineWidth(context, self.xAxis.axisLineWidth);\n    CGFloat y = CGRectGetMaxY(self.bounds) - self.contentInset.bottom;\n    CGContextMoveToPoint(context, self.contentInset.left, y);\n    CGContextAddLineToPoint(context, CGRectGetMaxX(self.bounds) - self.contentInset.right, y);\n    CGContextStrokePath(context);\n    CGContextRestoreGState(context);\n}\n\n- (void)drawYAxisWithContext:(CGContextRef)context {\n    if (!self.yAxis) {\n        return;\n    }\n    CGContextSaveGState(context);\n    CGFloat labelHeight = self.yAxis.labelFont.lineHeight;\n    CGFloat yAxisHeight = self.bounds.size.height - self.contentInset.top - self.contentInset.bottom;\n    \n    CGContextSetStrokeColorWithColor(context, self.yAxis.axisLineColor.CGColor);\n    CGContextSetLineWidth(context, self.yAxis.axisLineWidth);\n    CGContextMoveToPoint(context, self.contentInset.left, self.contentInset.top);\n    CGContextAddLineToPoint(context, self.contentInset.left, self.contentInset.top + yAxisHeight);\n    CGContextStrokePath(context);\n    \n   \n    CGFloat spacing = (yAxisHeight - self.yAxis.marginTop) / (self.yAxis.labels.count - 1);\n\n    NSArray<NSString *> *labels = [self.yAxis.labels.reverseObjectEnumerator allObjects];\n    // Render Grid Lines\n    for (NSUInteger i = 1; i < labels.count; i++) {\n        CGFloat dashes[] = {2,2};\n        CGFloat y = self.contentInset.top + self.yAxis.marginTop + spacing * (i - 1);\n        CGContextMoveToPoint(context, self.contentInset.left, y);\n        CGContextAddLineToPoint(context, self.bounds.size.width - self.contentInset.right, y);\n        CGContextSetLineDash(context, 0.0, dashes, 2);\n        CGContextStrokePath(context);\n    }\n    \n    // draw x axis value labels\n    for (NSUInteger i = 0; i < labels.count; i++) {\n        CGFloat y = self.contentInset.top + self.yAxis.marginTop + spacing * i;\n        NSString *text = labels[i];\n        UIColor *attColor = [UIColor blackColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            attColor = [UIColor labelColor];\n        }\n#endif\n        NSDictionary *attributes = @{NSFontAttributeName: self.yAxis.axisLabelFont, NSForegroundColorAttributeName: attColor};\n        CGSize size = [text sizeWithAttributes:attributes];\n        \n        CGFloat labelX = MAX(self.contentInset.left - size.width, 0);\n        CGRect rect = CGRectMake(labelX - 10, y - labelHeight / 2, self.contentInset.left - 1, labelHeight);\n         \n        [labels[i] drawInRect:rect withAttributes:attributes];\n    }\n    \n    CGContextRestoreGState(context);\n}\n\n- (void)drawBarsWithContext:(CGContextRef)context {\n    if (!self.items || self.items.count == 0) {\n        return;\n    }\n    CGFloat barWidth = (self.bounds.size.width - self.contentInset.left - self.contentInset.right) / (self.items.count * (1 + self.barsSpacingRatio) + self.barsSpacingRatio);\n    \n    for (NSUInteger i = 0; i < self.items.count; i++) {\n        DoraemonChartDataItem *item = self.items[i];\n        CGFloat yAxisHeight = self.bounds.size.height - self.contentInset.top - self.contentInset.bottom - self.yAxis.marginTop;\n        CGFloat height = item.value / self.yAxis.maxY * yAxisHeight;\n        CGFloat x = barWidth * i + self.contentInset.left + (barWidth * self.barsSpacingRatio) * (i + 1);\n        CGFloat y = self.bounds.size.height - height - self.contentInset.bottom;\n        CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];\n        shapeLayer.frame = CGRectMake(x, y, barWidth, height);\n        shapeLayer.backgroundColor = item.color.CGColor;\n        [self.layer addSublayer:shapeLayer];\n        \n        NSString *value = [self.vauleFormatter stringFromNumber: [NSNumber numberWithDouble:item.value]];\n        UIColor *attColor = [UIColor blackColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            attColor = [UIColor labelColor];\n        }\n#endif\n        NSDictionary *attributes = @{NSFontAttributeName: self.yAxis.axisLabelFont, NSForegroundColorAttributeName: attColor};\n        CGSize valueLabelSize = [value sizeWithAttributes:attributes];\n        CGFloat valueLabelOffSetX = (barWidth - valueLabelSize.width) / 2;\n        CGRect valueRect = CGRectMake(x + valueLabelOffSetX, y - valueLabelSize.height - 5, valueLabelSize.width, valueLabelSize.height);\n        [value drawInRect:valueRect withAttributes:attributes];\n        \n        CGSize descLabelSize = [item.name sizeWithAttributes:attributes];\n        CGRect descRect = CGRectMake(x + valueLabelOffSetX, CGRectGetMaxY(shapeLayer.frame) + 10, descLabelSize.width, descLabelSize.height);\n        [item.name drawInRect:descRect withAttributes:attributes];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Charts/DoraemonChart.h",
    "content": "//\n//  Chart.h\n//  DoraemonKit\n//\n//  Created by 0xd on 2019/9/11.\n//  Copyright © 2019 000. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonChartDataItem.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonChart : UIView\n@property (nonatomic, strong) UILabel *titleLabel;\n@property (nonatomic, strong) NSNumberFormatter *vauleFormatter;\n@property (nonatomic, copy) NSArray<DoraemonChartDataItem *> *items;\n@property (nonatomic, assign) UIEdgeInsets contentInset;\n\n- (void)display;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Charts/DoraemonChart.m",
    "content": "//\n//  Chart.m\n//  DoraemonKit\n//\n//  Created by 0xd on 2019/9/11.\n//  Copyright © 2019 000. All rights reserved.\n//\n\n#import \"DoraemonChart.h\"\n\n@implementation DoraemonChart\n\n- (instancetype)initWithFrame:(CGRect)frame\n{\n    self = [super initWithFrame:frame];\n    if (self) {\n        self.vauleFormatter = [[NSNumberFormatter alloc] init];\n        self.vauleFormatter.maximumFractionDigits = 2;\n        self.contentInset = UIEdgeInsetsMake(20, 30, 20, 20);\n        self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, frame.size.width - 20, 30)];\n        self.backgroundColor = [UIColor whiteColor];\n        [self addSubview:self.titleLabel];\n    }\n    return self;\n}\n\n- (void)display {}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Charts/DoraemonChartAxis.h",
    "content": "//\n//  DoraemonChartAxis.h\n//  DoraemonKit\n//\n//  Created by 0xd on 2019/9/4.\n//  Copyright © 2019 000. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonChartAxis : NSObject\n@property (nonatomic, strong) UIFont *labelFont;\n@property (nonatomic, strong) UIColor *labelTextColor;\n@property (nonatomic, strong) UIColor *axisLineColor;\n\n@property (nonatomic, assign) CGFloat axisLineWidth;\n\n@property (nonatomic, strong) UIFont *axisLabelFont;\n@property (nonatomic, strong) UIColor *axisLabelTextColor;\n\n@property (nonatomic, strong) NSNumberFormatter *vauleFormatter;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Charts/DoraemonChartAxis.m",
    "content": "//\n//  DoraemonChartAxis.m\n//  DoraemonKit\n//\n//  Created by 0xd on 2019/9/4.\n//  Copyright © 2019 000. All rights reserved.\n//\n\n#import \"DoraemonChartAxis.h\"\n#import <CoreGraphics/CoreGraphics.h>\n\n@implementation DoraemonChartAxis\n\n- (instancetype)init\n{\n    self = [super init];\n    if (self) {\n        self.labelFont = [UIFont systemFontOfSize:13];\n        self.axisLineColor = [UIColor grayColor];\n        self.axisLineWidth = 1;\n        self.axisLabelFont = [UIFont systemFontOfSize:13];\n        self.axisLabelTextColor = [UIColor grayColor];\n        self.vauleFormatter = [[NSNumberFormatter alloc] init];\n    }\n    return self;\n}\n\n- (NSNumberFormatter *)vauleFormatter {\n    if (!_vauleFormatter) {\n        _vauleFormatter = [[NSNumberFormatter alloc] init];\n        _vauleFormatter.maximumFractionDigits = 2;\n    }\n    return _vauleFormatter;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Charts/DoraemonChartDataItem.h",
    "content": "//\n//  DoraemonChartDataItem.h\n//  DoraemonKit\n//\n//  Created by 0xd on 2019/9/9.\n//  Copyright © 2019 000. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonChartDataItem : NSObject\n@property (nonatomic, assign) double value;\n@property (nonatomic, strong) NSString *name;\n@property (nonatomic, strong) UIColor *color;\n\n\n- (instancetype)initWithValue:(double)value\n                         name:(NSString *)name\n                        color:(UIColor *)color;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Charts/DoraemonChartDataItem.m",
    "content": "//\n//  DoraemonChartDataItem.m\n//  DoraemonKit\n//\n//  Created by 0xd on 2019/9/9.\n//  Copyright © 2019 000. All rights reserved.\n//\n\n#import \"DoraemonChartDataItem.h\"\n\n@implementation DoraemonChartDataItem\n\n- (instancetype)initWithValue:(double)value\n                         name:(NSString *)name\n                        color:(UIColor *)color {\n    if (self = [super init]) {\n        self.value = value;\n        self.name = name;\n        self.color = color;\n    }\n    return self;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Charts/DoraemonPieChart.h",
    "content": "//\n//  DoraemonPieChart.h\n//  DoraemonKit\n//\n//  Created by 0xd on 2019/9/25.\n//  Copyright © 2019 000. All rights reserved.\n//\n\n#import \"DoraemonChart.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonPieChart : DoraemonChart\n@property (nonatomic, strong) UIFont *itemDescriptionFont;\n@property (nonatomic, strong) UIColor *itemDescriptionTextColor;\n\n@property (nonatomic, assign) CGFloat innerCircleRadiusRatio;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Charts/DoraemonPieChart.m",
    "content": "//\n//  DoraemonPieChart.m\n//  DoraemonKit\n//\n//  Created by 0xd on 2019/9/25.\n//  Copyright © 2019 000. All rights reserved.\n//\n\n#import \"DoraemonPieChart.h\"\n\n@implementation DoraemonPieChart\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    if (self) {\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            self.backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n                if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                    return [UIColor secondarySystemBackgroundColor];\n                } else {\n                    return [UIColor whiteColor];\n                }\n            }];\n        } else {\n#endif\n            self.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        self.itemDescriptionFont =  [UIFont systemFontOfSize:11];\n        self.itemDescriptionTextColor = [UIColor whiteColor];\n        self.innerCircleRadiusRatio = 0.3;\n        self.contentInset = UIEdgeInsetsMake(10, 10, 10, 10);\n    }\n    return self;\n}\n\n- (void)display {\n    [super display];\n    [self setNeedsDisplay];\n}\n\n- (void)drawRect:(CGRect)rect {\n    CGFloat chartFrameY = self.contentInset.top + self.titleLabel.frame.size.height;\n    CGFloat chartFrameWidht = self.frame.size.width - self.contentInset.left - self.contentInset.right;\n    CGFloat chartFrameHeight = self.frame.size.height - self.titleLabel.frame.size.height - self.contentInset.top - self.contentInset.bottom;\n    CGRect chartFrame = CGRectMake(self.contentInset.left, chartFrameY, chartFrameWidht, chartFrameHeight);\n    CGFloat outerCircleRadius = MIN(chartFrame.size.width, chartFrame.size.height) / 2;\n    CGFloat innerCircleRadius = outerCircleRadius * self.innerCircleRadiusRatio;\n    \n    double sum = 0;\n    for (DoraemonChartDataItem *item in self.items) {\n        sum += item.value;\n    }\n    CGPoint chartCenter = CGPointMake(self.center.x, CGRectGetHeight(self.bounds) - outerCircleRadius - self.contentInset.bottom);\n    __block CGFloat lastestEndAngle = -M_PI_2;\n    [self.items enumerateObjectsUsingBlock:^(DoraemonChartDataItem * _Nonnull item, NSUInteger idx, BOOL * _Nonnull stop) {\n        CGFloat currentAngle = item.value / sum * M_PI * 2;\n        CGFloat startAngle = lastestEndAngle;\n\n        CGFloat endAngle = startAngle + currentAngle;\n        \n        CAShapeLayer *shapeLayer = [self shapeLayerRadius:outerCircleRadius\n                                        innerCircleRadius:innerCircleRadius\n                                                   center:chartCenter\n                                                fillColor:item.color\n                                               startAngle:startAngle\n                                                 endAngle:endAngle\n                                                    name:item.name\n                                                  precent:item.value / sum * 100];\n        [self.layer addSublayer:shapeLayer];\n        lastestEndAngle = endAngle;\n    }];\n    [self addInnerCircleWithRadius:innerCircleRadius center:chartCenter];\n}\n\n- (CAShapeLayer *)shapeLayerRadius:(CGFloat)radius\n                 innerCircleRadius:(CGFloat)innerCircleRadius\n                            center:(CGPoint)center\n                         fillColor:(UIColor *)fillColor\n                        startAngle:(CGFloat)startAngle\n                          endAngle:(CGFloat)endAngle\n                             name:(NSString *)name\n                           precent:(CGFloat)precent {\n\n    UIBezierPath *path = [UIBezierPath bezierPath];\n    [path moveToPoint:center];\n    [path addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];\n    CAShapeLayer *shapeLayer = [CAShapeLayer layer];\n    shapeLayer.path = path.CGPath;\n    shapeLayer.fillColor = fillColor.CGColor;\n    \n    CATextLayer *textLayer = [CATextLayer layer];\n    textLayer.frame = CGRectMake(0, 0, FLT_MAX, FLT_MAX);\n    textLayer.font = (__bridge CFTypeRef _Nullable)self.itemDescriptionFont;\n    textLayer.fontSize = self.itemDescriptionFont.pointSize;\n    textLayer.foregroundColor = (__bridge CGColorRef _Nullable)self.itemDescriptionTextColor;\n    textLayer.alignmentMode = kCAAlignmentCenter;\n    NSString *precentText = [[self.vauleFormatter stringFromNumber:[NSNumber numberWithDouble:precent]] stringByAppendingString:@\"%\"];\n    textLayer.string = [precentText stringByAppendingFormat:@\"\\n%@\",name];\n    textLayer.contentsScale = [UIScreen mainScreen].scale;\n    // 计算文字中心点\n    CGFloat textCenterAngle = startAngle + (endAngle - startAngle) / 2;\n    CGSize textLayerSize = textLayer.preferredFrameSize;\n    CGFloat textLayerOriginX = cos(textCenterAngle) * (radius + innerCircleRadius) / 2 + center.x - textLayerSize.width / 2;\n    CGFloat textLayerOriginy = sin(textCenterAngle) * (radius + innerCircleRadius) / 2 + center.y - textLayerSize.height / 2;\n    [shapeLayer addSublayer:textLayer];\n    \n    textLayer.frame = CGRectMake(textLayerOriginX, textLayerOriginy, textLayerSize.width, textLayerSize.height);\n    return shapeLayer;\n}\n\n- (void)addInnerCircleWithRadius:(CGFloat)radius\n                          center:(CGPoint)center {\n    CAShapeLayer *shape = [CAShapeLayer layer];\n    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:(2 * M_PI) clockwise:YES];\n    [path moveToPoint:center];\n    shape.path = path.CGPath;\n    shape.fillColor = [UIColor whiteColor].CGColor;\n    [self.layer addSublayer:shape];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Charts/DoraemonXAxis.h",
    "content": "//\n//  DoraemonXAxis.h\n//  DoraemonKit\n//\n//  Created by 0xd on 2019/9/9.\n//  Copyright © 2019 000. All rights reserved.\n//\n\n#import \"DoraemonChartAxis.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonXAxis : DoraemonChartAxis\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Charts/DoraemonXAxis.m",
    "content": "//\n//  DoraemonXAxis.m\n//  DoraemonKit\n//\n//  Created by 0xd on 2019/9/9.\n//  Copyright © 2019 000. All rights reserved.\n//\n\n#import \"DoraemonXAxis.h\"\n\n@implementation DoraemonXAxis\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Charts/DoraemonYAxis.h",
    "content": "//\n//  DoraemonYAxis.h\n//  DoraemonKit\n//\n//  Created by 0xd on 2019/9/6.\n//  Copyright © 2019 000. All rights reserved.\n//\n\n#import \"DoraemonChartAxis.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonYAxis : DoraemonChartAxis\n@property (nonatomic, assign) CGFloat marginTop;\n@property (nonatomic, assign) CGFloat labelWidth;\n@property (nonatomic, assign) NSInteger labelCount;\n@property (nonatomic, assign) double maxY;\n@property (nonatomic, copy) NSArray<NSString *> *labels;\n@property (nonatomic, copy) NSArray<NSNumber *> *values;\n- (void)update;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Charts/DoraemonYAxis.m",
    "content": "//\n//  DoraemonYAxis.m\n//  DoraemonKit\n//\n//  Created by 0xd on 2019/9/6.\n//  Copyright © 2019 000. All rights reserved.\n//\n\n#import \"DoraemonYAxis.h\"\n\n@interface DoraemonYAxis()\n\n@end\n\n@implementation DoraemonYAxis\n\n- (instancetype)init\n{\n    self = [super init];\n    if (self) {\n        _labelCount = 5;\n        _maxY = 1;\n        _marginTop = 10;\n        _labels = [NSArray array];\n    }\n    return self;\n}\n\n- (void)update {\n    if (self.values.count == 0) {\n        return;\n    }\n    double maxValueY = DBL_MIN;\n    for (NSNumber *value in self.values) {\n        maxValueY = MAX([value doubleValue], maxValueY);\n    }\n    // 防止出现y轴最大值小于label个数\n    double range = MAX(maxValueY, self.labelCount - 1);\n    double rawInterval = range / (self.labelCount - 1);\n    double interval = roundedToNextSignficant(rawInterval);\n    while (interval * (self.labelCount  - 1) < range) {\n        rawInterval = rawInterval * 1.1;\n        interval = roundedToNextSignficant(rawInterval);\n    }\n    self.maxY = interval * (self.labelCount  - 1);\n\n    NSMutableArray *labels = [NSMutableArray arrayWithCapacity:self.labelCount];\n    for (int i = 0; i < self.labelCount; i++) {\n        NSString *label = [self.vauleFormatter stringFromNumber: [[NSNumber alloc] initWithDouble:interval * i]];\n        [labels addObject:label];\n    }\n    self.labels = [labels copy];\n}\n\ndouble roundedToNextSignficant(double number) {\n    if (number == 0) { return number; }\n    NSInteger d = (NSInteger)ceil(log10(number < 0 ? -number : number));\n    NSInteger pw = 1 - d;\n    double magnitude = pow(10.0, (double)pw);\n    double shifted = roundf(number * magnitude);\n    return shifted / magnitude;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Label/DoraemonCopyLabel.h",
    "content": "//\n//  DoraemonCopyLabel.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/2/26.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonCopyLabel : UILabel\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Label/DoraemonCopyLabel.m",
    "content": "//\n//  DoraemonCopyLabel.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/2/26.\n//\n\n#import \"DoraemonCopyLabel.h\"\n\n@implementation DoraemonCopyLabel\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    if (self = [super initWithFrame:frame]) {\n        [self pressAction];\n    }\n    return self;\n}\n\n\n- (void)pressAction {\n    self.userInteractionEnabled = YES;\n    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressAction:)];\n    longPress.minimumPressDuration = 1;\n    [self addGestureRecognizer:longPress];\n}\n\n// 使label能够成为响应事件\n- (BOOL)canBecomeFirstResponder {\n     return YES;\n }\n\n// 控制响应的方法\n- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {\n     return action == @selector(customCopy:);\n}\n\n- (void)customCopy:(id)sender {\n  UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];\n  pasteboard.string = self.text;\n}\n\n- (void)longPressAction:(UIGestureRecognizer *)recognizer {\n    [self becomeFirstResponder];\n    UIMenuItem *copyItem = [[UIMenuItem alloc] initWithTitle:@\"copy\" action:@selector(customCopy:)];\n    \n    [[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObjects:copyItem, nil]];\n    [[UIMenuController sharedMenuController] setTargetRect:CGRectMake(0, 0, 100, 20) inView:self];\n    [[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Oscillogram/DoraemonOscillogramView.h",
    "content": "//\n//  DoraemonOscillogramView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/3.\n//\n\n#import <UIKit/UIKit.h>\n\n@class DoraemonPerformanceInfoModel;\n\n@interface DoraemonPoint : NSObject\n\n@property (nonatomic, assign) CGFloat x;\n@property (nonatomic, assign) CGFloat y;\n\n@end\n\n@interface DoraemonOscillogramView : UIScrollView\n\n@property (nonatomic, strong) UIColor *strokeColor;\n@property (nonatomic, assign) NSInteger numberOfPoints;\n\n- (void)addHeightValue:(CGFloat)showHeight andTipValue:(NSString *)tipValue;\n\n- (void)setLowValue:(NSString *)value;\n\n- (void)setHightValue:(NSString *)value;\n\n- (void)clear;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Oscillogram/DoraemonOscillogramView.m",
    "content": "//\n//  DoraemonOscillogramView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/3.\n//\n\n#import \"DoraemonOscillogramView.h\"\n#import \"DoraemonDefine.h\"\n\n@implementation DoraemonPoint\n\n@end\n\n@interface DoraemonOscillogramView()<UIScrollViewDelegate>\n\n@property (nonatomic, assign) CGFloat kStartX;\n\n@property (nonatomic, strong) NSMutableArray *pointList;\n@property (nonatomic, strong) NSMutableArray *pointLayerList;\n@property (nonatomic, assign) CGFloat x;\n@property (nonatomic, assign) CGFloat y;\n\n@property (nonatomic, strong) UIView *bottomLine;\n@property (nonatomic, strong) UILabel *lowValueLabel;\n@property (nonatomic, strong) UILabel *highValueLabel;\n\n@property (nonatomic, strong) CAShapeLayer *lineLayer;\n@property (nonatomic, strong) UILabel       *tipLabel;\n\n@end\n\n@implementation DoraemonOscillogramView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        _kStartX = kDoraemonSizeFrom750_Landscape(52);\n        \n        self.backgroundColor = [UIColor clearColor];\n        self.showsVerticalScrollIndicator = NO;\n        self.showsHorizontalScrollIndicator = NO;\n        self.delegate = self;\n        self.clipsToBounds = NO;\n        \n        _strokeColor = [UIColor orangeColor];\n        _numberOfPoints = 12;\n        _pointList = [NSMutableArray array];\n        _pointLayerList = [NSMutableArray array];\n        \n        _bottomLine = [[UIView alloc] initWithFrame:CGRectMake(_kStartX, self.doraemon_height-kDoraemonSizeFrom750_Landscape(1), self.doraemon_width, kDoraemonSizeFrom750_Landscape(1))];\n        _bottomLine.backgroundColor = [UIColor doraemon_colorWithString:@\"#999999\"];\n        [self addSubview:_bottomLine];\n        \n        _lowValueLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, self.doraemon_height-kDoraemonSizeFrom750_Landscape(28)/2, _kStartX, kDoraemonSizeFrom750_Landscape(28))];\n        _lowValueLabel.text = @\"0\";\n        _lowValueLabel.textColor = [UIColor whiteColor];\n        _lowValueLabel.textAlignment = NSTextAlignmentCenter;\n        _lowValueLabel.font = [UIFont systemFontOfSize:10];\n        [self addSubview:_lowValueLabel];\n        \n        _highValueLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, -kDoraemonSizeFrom750_Landscape(28)/2, _kStartX, kDoraemonSizeFrom750_Landscape(28))];\n        _highValueLabel.text = @\"100\";\n        _highValueLabel.textColor = [UIColor whiteColor];\n        _highValueLabel.textAlignment = NSTextAlignmentCenter;\n        _highValueLabel.font = [UIFont systemFontOfSize:10];\n        [self addSubview:_highValueLabel];\n        \n        _tipLabel = [[UILabel alloc] init];\n        _tipLabel.textColor = [UIColor doraemon_colorWithString:@\"#00DFDD\"];\n        _tipLabel.textAlignment = NSTextAlignmentCenter;\n        _tipLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(20)];\n        _tipLabel.lineBreakMode = NSLineBreakByClipping;\n        [self addSubview:_tipLabel];\n    }\n    \n    return self;\n}\n\n- (void)setLowValue:(NSString *)value{\n    _lowValueLabel.text = value;\n}\n\n- (void)setHightValue:(NSString *)value{\n    _highValueLabel.text = value;\n}\n\n- (void)addHeightValue:(CGFloat)showHeight andTipValue:(NSString *)tipValue{\n    CGFloat width = self.doraemon_width;\n    CGFloat height = self.doraemon_height;\n    CGFloat step = width / _numberOfPoints;\n    if (_pointList.count == 0) {\n        _x = _kStartX;\n    }else{\n        if (_x <= width-step) {\n            _x += step;\n        }\n    }\n    \n    _y = fabs(MIN(height, showHeight));\n    DoraemonPoint *point = [[DoraemonPoint alloc] init];\n    point.x = _x;\n    point.y = _y;\n    [_pointList addObject:point];\n    \n    if (_pointList.count > _numberOfPoints) {\n        NSMutableArray *oldList = [NSMutableArray array];\n        \n        for (DoraemonPoint *point in _pointList) {\n            point.x -= step;\n            if (point.x < _kStartX) {\n                [oldList addObject:point];\n            }\n        }\n        \n        [_pointList removeObjectsInArray:oldList];\n    }\n    \n    [self drawLine];\n    [self drawTipViewWithValue:tipValue point:point time:nil];\n}\n\n- (void)drawLine{\n    if (_lineLayer) {\n        [_lineLayer removeFromSuperlayer];\n    }\n    if (_pointLayerList.count>0) {\n        for (CALayer *layer in _pointLayerList) {\n            [layer removeFromSuperlayer];\n        }\n        _pointLayerList = [NSMutableArray array];\n    }\n    if (self.pointList.count==0) {\n        return ;\n    }\n    UIBezierPath *path = [UIBezierPath bezierPath];\n    \n    DoraemonPoint *point = self.pointList[0];\n    CGPoint p1 = CGPointMake(point.x, self.doraemon_height - point.y);\n    [path moveToPoint:p1];\n    [self addPointLayer:p1];\n    \n    for (int i=1; i<self.pointList.count; i++) {\n        point = self.pointList[i];\n        CGPoint p2 = CGPointMake(point.x, self.doraemon_height - point.y);\n        [path addLineToPoint:p2];\n        \n        [self addPointLayer:p2];\n    }\n    \n    path.lineWidth = 2.;\n    \n    CAShapeLayer *layer = [CAShapeLayer layer];\n    layer.path = path.CGPath;\n    layer.strokeColor = [UIColor doraemon_colorWithString:@\"#00DFDD\"].CGColor;\n    layer.fillColor = [UIColor clearColor].CGColor;\n    \n    _lineLayer = layer;\n    \n    [self.layer addSublayer:layer];\n    \n    for (CALayer *layer in _pointLayerList) {\n        [self.layer addSublayer:layer];\n    }\n}\n\n- (void)addPointLayer:(CGPoint)point{\n    CALayer *pointLayer = [CALayer layer];\n    pointLayer.backgroundColor = [UIColor doraemon_colorWithString:@\"#00DFDD\"].CGColor;\n    pointLayer.cornerRadius = 2;\n    pointLayer.frame = CGRectMake(point.x-kDoraemonSizeFrom750_Landscape(8)/2, point.y-kDoraemonSizeFrom750_Landscape(8)/2, kDoraemonSizeFrom750_Landscape(8), kDoraemonSizeFrom750_Landscape(8));\n    [_pointLayerList addObject:pointLayer];\n}\n\n- (void)drawTipViewWithValue:(NSString *)tip point:(DoraemonPoint *)point time:(NSString *)time {\n    if (_tipLabel.hidden) {\n        _tipLabel.hidden = NO;\n    }\n    \n    if (time) {\n        _tipLabel.text = [NSString stringWithFormat:@\"%@\\n%@\", tip, time];\n        _tipLabel.numberOfLines = 2;\n    } else {\n        _tipLabel.text = tip;\n        _tipLabel.numberOfLines = 1;\n    }\n    \n    [_tipLabel sizeToFit];\n    _tipLabel.frame = CGRectMake(point.x, self.doraemon_height-point.y-_tipLabel.doraemon_height, _tipLabel.doraemon_width, _tipLabel.doraemon_height);\n    //self.indicatorLayer.frame = CGRectMake(point.x, 0, 1, self.bottomLine.doraemon_bottom);\n}\n\n- (void)clear {\n    if (_pointLayerList.count>0) {\n        for (CALayer *layer in _pointLayerList) {\n            [layer removeFromSuperlayer];\n        }\n        _pointLayerList = [NSMutableArray array];\n    }\n    if (_lineLayer) {\n        [_lineLayer removeFromSuperlayer];\n    }\n    _pointList = [NSMutableArray array];\n    _tipLabel.hidden = YES;\n}\n\n\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Oscillogram/DoraemonOscillogramViewController.h",
    "content": "//\n//  DoraemonOscillogramViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/4.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonOscillogramView.h\"\n\n@interface DoraemonOscillogramViewController : UIViewController\n\n@property (nonatomic, strong) DoraemonOscillogramView *oscillogramView;\n@property (nonatomic, strong) UIButton *closeBtn;\n\n- (NSString *)title;\n- (NSString *)lowValue;\n- (NSString *)highValue;\n- (void)closeBtnClick;\n- (void)startRecord;\n- (void)endRecord;\n- (void)doSecondFunction;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Oscillogram/DoraemonOscillogramViewController.m",
    "content": "//\n//  DoraemonOscillogramViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/4.\n//\n\n#import \"DoraemonOscillogramViewController.h\"\n#import \"DoraemonOscillogramWindowManager.h\"\n#import \"DoraemonDefine.h\"\n\n\n@interface DoraemonOscillogramViewController ()\n\n//每秒运行一次\n@property (nonatomic, strong) NSTimer *secondTimer;\n\n@end\n\n@implementation DoraemonOscillogramViewController\n\n- (void)viewDidLoad{\n    [super viewDidLoad];\n    self.view.backgroundColor = [UIColor clearColor];\n    [self setEdgesForExtendedLayout:UIRectEdgeNone];\n    \n    UILabel *titleLabel = [[UILabel alloc] init];\n    titleLabel.backgroundColor = [UIColor clearColor];\n    titleLabel.text = [self title];\n    titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(20)];\n    titleLabel.textColor = [UIColor whiteColor];\n    [self.view addSubview:titleLabel];\n    [titleLabel sizeToFit];\n    titleLabel.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(20), kDoraemonSizeFrom750_Landscape(10), titleLabel.doraemon_width, titleLabel.doraemon_height);\n    \n    UIButton *closeBtn = [[UIButton alloc] init];\n    [closeBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_close_white\"] forState:UIControlStateNormal];\n    closeBtn.frame = CGRectMake((kInterfaceOrientationPortrait ? DoraemonScreenWidth : DoraemonScreenHeight)-kDoraemonSizeFrom750_Landscape(80), 0, kDoraemonSizeFrom750_Landscape(80), kDoraemonSizeFrom750_Landscape(80));\n    [closeBtn addTarget:self action:@selector(closeBtnClick) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:closeBtn];\n    _closeBtn = closeBtn;\n    \n    _oscillogramView = [[DoraemonOscillogramView alloc] initWithFrame:CGRectMake(0, titleLabel.doraemon_bottom+kDoraemonSizeFrom750_Landscape(12), (kInterfaceOrientationPortrait ? DoraemonScreenWidth : DoraemonScreenHeight), kDoraemonSizeFrom750_Landscape(184))];\n    _oscillogramView.backgroundColor = [UIColor clearColor];\n    [_oscillogramView setLowValue:[self lowValue]];\n    [_oscillogramView setHightValue:[self highValue]];\n    [self.view addSubview:_oscillogramView];\n}\n\n- (NSString *)title{\n    return @\"\";\n}\n\n- (NSString *)lowValue{\n    return @\"0\";\n}\n\n- (NSString *)highValue{\n    return @\"100\";\n}\n\n- (void)closeBtnClick{\n}\n\n- (void)startRecord{\n    if(!_secondTimer){\n        _secondTimer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(doSecondFunction) userInfo:nil repeats:YES];\n        [[NSRunLoop currentRunLoop] addTimer:_secondTimer forMode:NSRunLoopCommonModes];\n    }\n}\n\n- (void)doSecondFunction{\n    \n}\n\n- (void)endRecord{\n    if(_secondTimer){\n        [_secondTimer invalidate];\n        _secondTimer = nil;\n        [self.oscillogramView clear];\n    }\n}\n\n- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {\n    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];\n    dispatch_async(dispatch_get_main_queue(), ^{\n        [[DoraemonOscillogramWindowManager shareInstance] resetLayout];\n    });\n}\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Oscillogram/DoraemonOscillogramWindow.h",
    "content": "//\n//  DoraemonOscillogramWindow.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/3.\n//\n\n#import <UIKit/UIKit.h>\n#import <Foundation/Foundation.h>\n#import \"DoraemonOscillogramViewController.h\"\n\n@protocol DoraemonOscillogramWindowDelegate <NSObject>\n\n- (void)doraemonOscillogramWindowClosed;\n\n@end\n\n@interface DoraemonOscillogramWindow : UIWindow\n\n+ (DoraemonOscillogramWindow *)shareInstance;\n\n@property (nonatomic, strong) DoraemonOscillogramViewController *vc;\n\n//需要子类重写\n- (void)addRootVc;\n\n- (void)show;\n\n- (void)hide;\n\n- (void)addDelegate:(id<DoraemonOscillogramWindowDelegate>) delegate;\n\n- (void)removeDelegate:(id<DoraemonOscillogramWindowDelegate>)delegate;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Oscillogram/DoraemonOscillogramWindow.m",
    "content": "//\n//  DoraemonOscillogramWindow.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/3.\n//\n\n#import \"DoraemonOscillogramWindow.h\"\n#import \"DoraemonOscillogramViewController.h\"\n#import \"UIColor+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonOscillogramWindowManager.h\"\n\n@interface DoraemonOscillogramWindow()\n\n@property (nonatomic, strong) NSHashTable *delegates;\n\n@end\n\n@implementation DoraemonOscillogramWindow\n\n- (NSHashTable *)delegates {\n    if (_delegates == nil) {\n        self.delegates = [NSHashTable weakObjectsHashTable];\n    }\n    return _delegates;\n}\n\n- (void)addDelegate:(id<DoraemonOscillogramWindowDelegate>) delegate {\n    [self.delegates addObject:delegate];\n}\n\n- (void)removeDelegate:(id<DoraemonOscillogramWindowDelegate>)delegate {\n    [self.delegates removeObject:delegate];\n}\n\n+ (DoraemonOscillogramWindow *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonOscillogramWindow *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonOscillogramWindow alloc] initWithFrame:CGRectZero];\n    });\n    return instance;\n}\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        self.windowLevel = UIWindowLevelStatusBar + 2.f;\n        self.backgroundColor = [UIColor doraemon_colorWithHex:0x000000 andAlpha:0.33];\n        self.layer.masksToBounds = YES;\n        #if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n            if (@available(iOS 13.0, *)) {\n                for (UIWindowScene* windowScene in [UIApplication sharedApplication].connectedScenes){\n                    if (windowScene.activationState == UISceneActivationStateForegroundActive){\n                        self.windowScene = windowScene;\n                        break;\n                    }\n                }\n            }\n        #endif\n        [self addRootVc];\n    }\n    return self;\n}\n\n- (void)addRootVc{\n   //需要子类重写\n}\n\n- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{\n    // 默认曲线图不拦截触摸事件，只有在关闭按钮z之类才响应\n    if (CGRectContainsPoint(self.vc.closeBtn.frame, point)) {\n        return [super pointInside:point withEvent:event];\n    }\n    return NO;\n}\n\n- (void)show{\n    self.hidden = NO;\n    [_vc startRecord];\n    [self resetLayout];\n}\n\n- (void)hide{\n    [_vc endRecord];\n    self.hidden = YES;\n    [self resetLayout];\n    \n    for (id<DoraemonOscillogramWindowDelegate> delegate in self.delegates) {\n        [delegate doraemonOscillogramWindowClosed];\n    }\n}\n\n- (void)resetLayout{\n    [[DoraemonOscillogramWindowManager shareInstance] resetLayout];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Oscillogram/DoraemonOscillogramWindowManager.h",
    "content": "//\n//  DoraemonOscillogramWindowManager.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/16.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n\n@interface DoraemonOscillogramWindowManager : NSObject\n\n+ (DoraemonOscillogramWindowManager *)shareInstance;\n\n- (void)resetLayout;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Oscillogram/DoraemonOscillogramWindowManager.m",
    "content": "//\n//  DoraemonOscillogramWindowManager.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/16.\n//\n\n#import \"DoraemonOscillogramWindowManager.h\"\n#import \"DoraemonFPSOscillogramWindow.h\"\n#import \"DoraemonCPUOscillogramWindow.h\"\n#import \"DoraemonMemoryOscillogramWindow.h\"\n#import \"DoraemonNetFlowOscillogramWindow.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonOscillogramWindowManager()\n\n@property (nonatomic, strong) DoraemonFPSOscillogramWindow *fpsWindow;\n@property (nonatomic, strong) DoraemonCPUOscillogramWindow *cpuWindow;\n@property (nonatomic, strong) DoraemonMemoryOscillogramWindow *memoryWindow;\n@property (nonatomic, strong) DoraemonNetFlowOscillogramWindow *netflowWindow;\n\n@end\n\n@implementation DoraemonOscillogramWindowManager\n\n+ (DoraemonOscillogramWindowManager *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonOscillogramWindowManager *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonOscillogramWindowManager alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    if (self = [super init]) {\n        _fpsWindow = [DoraemonFPSOscillogramWindow shareInstance];\n        _cpuWindow = [DoraemonCPUOscillogramWindow shareInstance];\n        _memoryWindow = [DoraemonMemoryOscillogramWindow shareInstance];\n        _netflowWindow = [DoraemonNetFlowOscillogramWindow shareInstance];\n    }\n    return self;\n}\n\n- (void)resetLayout{\n    CGFloat offsetY = 0;\n    CGFloat width = 0;\n    CGFloat height = kDoraemonSizeFrom750_Landscape(240);\n    if (kInterfaceOrientationPortrait){\n        width = DoraemonScreenWidth;\n        offsetY = IPHONE_TOPSENSOR_HEIGHT;\n    }else{\n        width = DoraemonScreenHeight;\n    }\n    if (!_fpsWindow.hidden) {\n        _fpsWindow.frame = CGRectMake(0, offsetY, width, height);\n        offsetY += _fpsWindow.doraemon_height+kDoraemonSizeFrom750_Landscape(4);\n    }\n    \n    if (!_cpuWindow.hidden) {\n        _cpuWindow.frame = CGRectMake(0, offsetY, width, height);\n        offsetY += _cpuWindow.doraemon_height+kDoraemonSizeFrom750_Landscape(4);\n    }\n    \n    if (!_memoryWindow.hidden) {\n        _memoryWindow.frame = CGRectMake(0, offsetY, width, height);\n        offsetY += _memoryWindow.doraemon_height+kDoraemonSizeFrom750_Landscape(4);\n    }\n    \n    if (!_netflowWindow.hidden) {\n        _netflowWindow.frame = CGRectMake(0, offsetY, width, height);\n    }\n    \n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Toast/DoraemonToastUtil.h",
    "content": "//\n//  DoraemonToastUtil.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/5/4.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\n@interface DoraemonToastUtil : NSObject\n\n+ (void)showToast:(NSString *)text inView:(UIView *)superView;\n+ (void)showToastBlack:(NSString *)text inView:(UIView *)superView;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Toast/DoraemonToastUtil.m",
    "content": "//\n//  DoraemonToastUtil.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/5/4.\n//\n\n#import \"DoraemonToastUtil.h\"\n#import \"UIColor+Doraemon.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n\n\n@implementation DoraemonToastUtil\n\n+ (void)showToast:(NSString *)text inView:(UIView *)superView {\n    if (!superView) {\n        return;\n    }\n    \n    UILabel *label = [[UILabel alloc] init];\n    label.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(14)];\n    label.text = text;\n    [label sizeToFit];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        label.textColor = [UIColor labelColor];\n    } else {\n#endif\n        label.textColor = [UIColor blackColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n    label.frame = CGRectMake(superView.doraemon_width/2-label.doraemon_width/2, superView.doraemon_height/2-label.doraemon_height/2, label.doraemon_width, label.doraemon_height);\n    [superView addSubview:label];\n    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{\n        [label removeFromSuperview];\n    });\n    \n}\n\n+ (void)showToastBlack:(NSString *)text inView:(UIView *)superView {\n    if (!superView) {\n        return;\n    }\n    \n    UILabel *label = [[UILabel alloc] init];\n    label.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)];\n    NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];\n    paragraphStyle.lineSpacing = kDoraemonSizeFrom750_Landscape(8);\n    paragraphStyle.alignment = NSTextAlignmentCenter;\n    NSMutableDictionary *attributes = [NSMutableDictionary dictionary];\n    [attributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];\n    label.attributedText = [[NSAttributedString alloc] initWithString:DoraemonLocalizedString(text) attributes:attributes];\n    label.numberOfLines = 0;\n    CGSize size = [label sizeThatFits:CGSizeMake(superView.doraemon_width-50, CGFLOAT_MAX)];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        label.backgroundColor = [UIColor labelColor];\n    } else {\n#endif\n        label.backgroundColor = [UIColor blackColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n    CGFloat padding = kDoraemonSizeFrom750_Landscape(37);\n    label.frame = CGRectMake(superView.doraemon_width/2-size.width/2 - padding, superView.doraemon_height/2-size.height/2 - padding, size.width + padding*2, size.height + padding*2);\n    label.layer.cornerRadius = kDoraemonSizeFrom750_Landscape(8);\n    [label.layer setMasksToBounds:YES];\n    label.textColor = [UIColor whiteColor];\n    [superView addSubview:label];\n    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{\n        [label removeFromSuperview];\n    });\n    \n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Visual/DoraemonVisualInfoWindow.h",
    "content": "//\n//  DoraemonVisualInfoWindow.h\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/12/5.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonVisualInfoWindow : UIWindow\n/** 显示的文本 */\n@property (nonatomic, copy) NSString *infoText;\n/** 显示的属性文本 */\n@property (nonatomic, copy) NSAttributedString *infoAttributedText;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Visual/DoraemonVisualInfoWindow.m",
    "content": "//\n//  DoraemonVisualInfoWindow.m\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/12/5.\n//\n\n#import \"DoraemonVisualInfoWindow.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonVisualInfoViewController : UIViewController\n@property (nonatomic, strong) UIButton *closeBtn;\n@property (nonatomic, strong) UILabel *infoLabel;\n@end\n\n@implementation DoraemonVisualInfoViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    [self initUI];\n}\n\n- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {\n    [super traitCollectionDidChange:previousTraitCollection];\n    // trait发生了改变\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {\n            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                [self.closeBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_close_dark\"] forState:UIControlStateNormal];\n            } else {\n                [self.closeBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_close\"] forState:UIControlStateNormal];\n            }\n        }\n    }\n#endif\n}\n\n- (void)initUI {\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        self.view.backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n            if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {\n                return [UIColor whiteColor];\n            } else {\n                return [UIColor secondarySystemBackgroundColor];\n            }\n        }];\n    }\n#endif\n    \n    // 视图加载完成之前拿不到当前window，所以这里等待 UI 线程执行完成 \n    dispatch_async(dispatch_get_main_queue(), ^{\n        CGSize viewSize = self.view.window.bounds.size;\n        \n        CGFloat closeWidth = kDoraemonSizeFrom750_Landscape(44);\n        CGFloat closeHeight = kDoraemonSizeFrom750_Landscape(44);\n        self.closeBtn = [[UIButton alloc] initWithFrame:CGRectMake(viewSize.width - closeWidth - kDoraemonSizeFrom750_Landscape(16), kDoraemonSizeFrom750_Landscape(16), closeWidth, closeHeight)];\n        \n        UIImage *closeImage = [UIImage doraemon_xcassetImageNamed:@\"doraemon_close\"];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                closeImage = [UIImage doraemon_xcassetImageNamed:@\"doraemon_close_dark\"];\n            }\n        }\n#endif\n        \n        [self.closeBtn setBackgroundImage:closeImage forState:UIControlStateNormal];\n        [self.closeBtn addTarget:self action:@selector(closeBtnClicked:) forControlEvents:UIControlEventTouchUpInside];\n        [self.view addSubview:self.closeBtn];\n        \n        self.infoLabel = [[UILabel alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(32), 0, viewSize.width - kDoraemonSizeFrom750_Landscape(32 + 16) - closeWidth , viewSize.height)];\n        self.infoLabel.backgroundColor = [UIColor clearColor];\n        self.infoLabel.textColor = [UIColor doraemon_black_1];\n        self.infoLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n        self.infoLabel.numberOfLines = 0;\n        [self.view addSubview:self.infoLabel];\n        \n        [(id)self.view.window setInfoLabel:self.infoLabel];\n    });\n}\n\n#pragma mark - Actions\n- (void)closeBtnClicked:(id)sender {\n    [[NSNotificationCenter defaultCenter] postNotificationName:DoraemonClosePluginNotification object:nil userInfo:nil];\n}\n\n- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {\n    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];\n    dispatch_async(dispatch_get_main_queue(), ^{\n        self.view.window.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(30), DoraemonScreenHeight - self.infoLabel.frame.size.height - kDoraemonSizeFrom750_Landscape(30), size.height, size.width);\n    });\n}\n\n@end\n\n@interface DoraemonVisualInfoWindow ()\n@property (nonatomic, weak) UILabel *infoLabel;\n@end\n\n@implementation DoraemonVisualInfoWindow\n\n#pragma mark - set\n\n- (void)setInfoText:(NSString *)infoText {\n    _infoText = infoText;\n    _infoLabel.text = infoText;\n}\n- (void)setInfoAttributedText:(NSAttributedString *)infoAttributedText {\n    _infoAttributedText = infoAttributedText;\n    _infoLabel.attributedText = infoAttributedText;\n}\n\n#pragma mark - Lifecycle\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    if (self) {\n        [self commonInit];\n    }\n    return self;\n}\n\n- (void)commonInit {\n    #if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            for (UIWindowScene* windowScene in [UIApplication sharedApplication].connectedScenes){\n                if (windowScene.activationState == UISceneActivationStateForegroundActive){\n                    self.windowScene = windowScene;\n                    break;\n                }\n            }\n        }\n    #endif\n    self.backgroundColor = [UIColor whiteColor];\n    self.layer.cornerRadius = kDoraemonSizeFrom750_Landscape(8);\n    self.layer.borderWidth = 1.;\n    self.layer.borderColor = [UIColor doraemon_colorWithHex:0x999999 andAlpha:0.2].CGColor;\n    self.windowLevel = UIWindowLevelAlert;\n    self.rootViewController = [[DoraemonVisualInfoViewController alloc] init];\n    \n    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];\n    [self addGestureRecognizer:pan];\n}\n\n#pragma mark - Actions\n\n- (void)pan:(UIPanGestureRecognizer *)sender{\n    UIView *panView = sender.view;\n    \n    if (!panView.hidden) {\n        //1、获得拖动位移\n        CGPoint offsetPoint = [sender translationInView:sender.view];\n        //2、清空拖动位移\n        [sender setTranslation:CGPointZero inView:sender.view];\n        //3、重新设置控件位置\n        CGFloat newX = panView.doraemon_centerX+offsetPoint.x;\n        CGFloat newY = panView.doraemon_centerY+offsetPoint.y;\n        \n        CGPoint centerPoint = CGPointMake(newX, newY);\n        panView.center = centerPoint;\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Visual/DoraemonVisualMagnifierWindow.h",
    "content": "//\n//  DoraemonVisualMagnifierWindow.h\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/12/6.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonVisualMagnifierWindow : UIWindow\n\n/**\n 放大镜的宽高（默认为90）\n */\n@property (nonatomic, assign) CGFloat magnifierSize;\n\n/**\n 放大倍数（默认2.0）\n */\n@property (nonatomic, assign) CGFloat magnification;\n\n/**\n 目标视图的Window\n */\n@property (nonatomic, strong) UIView *targetWindow;\n\n/**\n 目标视图展示位置\n */\n@property (nonatomic, assign) CGPoint targetPoint;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/CommonUI/Visual/DoraemonVisualMagnifierWindow.m",
    "content": "//\n//  DoraemonVisualMagnifierWindow.m\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/12/6.\n//\n\n#import \"DoraemonVisualMagnifierWindow.h\"\n\n@implementation DoraemonVisualMagnifierWindow\n\n-(instancetype)init{\n    self = [super init];\n    if (self) {\n        self.magnifierSize = 90;\n        self.magnification = 2.0;\n        #if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n            if (@available(iOS 13.0, *)) {\n                for (UIWindowScene* windowScene in [UIApplication sharedApplication].connectedScenes){\n                    if (windowScene.activationState == UISceneActivationStateForegroundActive){\n                        self.windowScene = windowScene;\n                        break;\n                    }\n                }\n            }\n        #endif\n        self.frame = CGRectMake(0, 0, self.magnifierSize, self.magnifierSize);\n        self.layer.delegate = self;\n        self.layer.borderWidth = 1;\n        self.layer.borderColor = [[UIColor clearColor] CGColor];\n        self.windowLevel = UIWindowLevelStatusBar + 1.f;\n        self.layer.contentsScale = [[UIScreen mainScreen] scale];\n    }\n    return self;\n}\n\n#pragma mark - Override\n\n- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {\n    CGContextTranslateCTM(ctx, self.frame.size.width * 0.5, self.frame.size.height * 0.5);\n    CGContextScaleCTM(ctx, _magnification, _magnification);\n    CGContextTranslateCTM(ctx, -1 * self.targetPoint.x, -1 * self.targetPoint.y);\n    \n    [self.targetWindow.layer renderInContext:ctx];\n}\n\n#pragma mark - Setter\n\n- (void)setMagnification:(CGFloat)magnification {\n    if (magnification > 0) {\n        _magnification = magnification;\n    }\n}\n\n- (void)setTargetWindow:(UIView *)targetWindow {\n    _targetWindow = targetWindow;\n    [self setTargetPoint:self.targetPoint];\n    \n}\n\n- (void)setMagnifierSize:(CGFloat)magnifierSize {\n    _magnifierSize = magnifierSize;\n    self.frame = CGRectMake(0, 0, _magnifierSize, _magnifierSize);\n    self.layer.cornerRadius = self.magnifierSize/2;\n    self.layer.masksToBounds = YES;\n}\n\n- (void)setTargetPoint:(CGPoint)targetPoint {\n    _targetPoint = targetPoint;\n    if (self.targetWindow) {\n        self.center = CGPointMake(targetPoint.x, targetPoint.y);\n        [self.layer setNeedsDisplay];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Define/DoraemonDefine.h",
    "content": "//\n//  DoraemonDefine.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#ifndef DoraemonDefine_h\n#define DoraemonDefine_h\n\n#import \"DoraemonAppInfoUtil.h\"\n#import \"UIColor+Doraemon.h\"\n#import \"UIView+Doraemon.h\"\n#import \"UIImage+Doraemon.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonToastUtil.h\"\n#import \"DoraemonAlertUtil.h\"\n#import \"DoraemonUtil.h\"\n\n#define DoKitVersion @\"3.0.8\"\n#define DoKitKbChange(x) x * 1000\n\n//#define DoKit_OpenLog\n\n#ifdef DoKit_OpenLog\n#define DoKitLog(...) NSLog(@\"DoKitLog -> %s\\n %@ \\n\\n\",__func__,[NSString stringWithFormat:__VA_ARGS__]);\n#else\n#define DoKitLog(...)\n#endif\n\n#define WEAKSELF(weakSelf)  __weak __typeof(&*self)weakSelf = self;\n\n#define DoraemonScreenWidth [UIScreen mainScreen].bounds.size.width\n#define DoraemonScreenHeight [UIScreen mainScreen].bounds.size.height\n\n//Doraemon默认位置\n#define DoraemonStartingPosition            CGPointMake(0, DoraemonScreenHeight/3.0)\n\n//Doraemon全屏默认位置\n#define DoraemonFullScreenStartingPosition  CGPointZero\n\n//根据750*1334分辨率计算size\n#define kDoraemonSizeFrom750(x) ((x)*DoraemonScreenWidth/750)\n// 如果横屏显示\n#define kDoraemonSizeFrom750_Landscape(x) (kInterfaceOrientationLandscape ? ((x)*DoraemonScreenHeight/750) : kDoraemonSizeFrom750(x))\n\n#define kInterfaceOrientationPortrait UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation)\n\n#define kInterfaceOrientationLandscape UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)\n\n\n#define IS_IPHONE_X_Series [DoraemonAppInfoUtil isIPhoneXSeries]\n#define IPHONE_NAVIGATIONBAR_HEIGHT  (IS_IPHONE_X_Series ? 88 : 64)\n#define IPHONE_STATUSBAR_HEIGHT      (IS_IPHONE_X_Series ? 44 : 20)\n#define IPHONE_SAFEBOTTOMAREA_HEIGHT (IS_IPHONE_X_Series ? 34 : 0)\n#define IPHONE_TOPSENSOR_HEIGHT      (IS_IPHONE_X_Series ? 32 : 0)\n\n//判空\n#define STRING_IS_BLANK(str) (str==nil ||![str isKindOfClass:[NSString class]]|| [str length]<1)\n#define STRING_NOT_NULL(str) ((str==nil)?@\"\":str)\n#define DICTIONARY_IS_EMPTY(dic) ((dic)==nil || ![(dic) isKindOfClass:[NSDictionary class]] || (dic).count < 1)\n#define ARRAY_IS_NULL(array) ((array)==nil || ![(array) isKindOfClass:[NSArray class]] || ([array count]) < 1)\n#define ARRAY_IS_EMPTY(array) ((array)==nil || ![(array) isKindOfClass:[NSArray class]] || (array).count < 1)\n#define STRING_DEFAULT_BLANK(str) ((str==nil)?@\"\":str)\n\n\n#define DoraemonClosePluginNotification @\"DoraemonClosePluginNotification\"\n#define DoraemonQuickOpenLogVCNotification @\"DoraemonQuickOpenLogVCNotification\"\n#define DoraemonKitManagerUpdateNotification @\"DoraemonKitManagerUpdateNotification\"\n\n\n#endif /* DoraemonDefine_h */\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/DoraemonKit.h",
    "content": "//\n//  DoraemonKit.h\n//  Pods\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#ifndef DoraemonKit_h\n#define DoraemonKit_h\n\n#import \"DoraemonManager.h\"\n#import \"DoraemonBaseViewController.h\"\n\n#endif /* DoraemonKit_h */\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Entry/DoraemonEntryWindow.h",
    "content": "//\n//  DoraemonEntryWindow.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\n/// 入口滑动浮窗（默认记录上次坐标）\n@interface DoraemonEntryWindow : UIWindow\n\n/// 是否自动停靠，默认为YES\n@property (nonatomic, assign) BOOL autoDock;\n\n// 定制位置\n- (instancetype)initWithStartPoint:(CGPoint)startingPosition;\n- (void)show;\n\n- (void)configEntryBtnBlingWithText:(NSString *)text backColor:(UIColor *)backColor;\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Entry/DoraemonEntryWindow.m",
    "content": "//\n//  DoraemonEntryWindow.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonEntryWindow.h\"\n#import \"DoraemonDefine.h\"\n#import \"UIView+Doraemon.h\"\n#import \"UIImage+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonStatusBarViewController.h\"\n#import \"DoraemonBuriedPointManager.h\"\n\n@interface DoraemonEntryWindow()\n\n@property (nonatomic, strong) UIButton *entryBtn;\n@property (nonatomic, assign) CGFloat kEntryViewSize;\n@property (nonatomic) CGPoint startingPosition;\n@property (nonatomic, strong) UILabel *entryBtnBlingTextLabel;\n\n@end\n\n@implementation DoraemonEntryWindow\n\n- (UIButton *)entryBtn {\n    if (!_entryBtn) {\n        _entryBtn = [[UIButton alloc] initWithFrame:self.bounds];\n        _entryBtn.backgroundColor = [UIColor clearColor];\n        UIImage *image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_logo\"];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_logo_dark\"];\n            }\n        }\n#endif\n        [_entryBtn setImage:image forState:UIControlStateNormal];\n        _entryBtn.layer.cornerRadius = 20.;\n        [_entryBtn addTarget:self action:@selector(entryClick:) forControlEvents:UIControlEventTouchUpInside];\n    }\n\n    return _entryBtn;\n}\n\n- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {\n    [super traitCollectionDidChange:previousTraitCollection];\n    // trait发生了改变\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {\n            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                [self.entryBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_logo_dark\"] forState:UIControlStateNormal];\n            } else {\n                [self.entryBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_logo\"] forState:UIControlStateNormal];\n            }\n        }\n    }\n#endif\n}\n\n- (instancetype)initWithStartPoint:(CGPoint)startingPosition{\n    self.startingPosition = startingPosition;\n    _kEntryViewSize = 58;\n    CGFloat x = self.startingPosition.x;\n    CGFloat y = self.startingPosition.y;\n    CGPoint defaultPosition = DoraemonStartingPosition;\n    if (x < 0 || x > (DoraemonScreenWidth - _kEntryViewSize)) {\n        x = defaultPosition.x;\n    }\n    \n    if (y < 0 || y > (DoraemonScreenHeight - _kEntryViewSize)) {\n        y = defaultPosition.y;\n    }\n    \n    self = [super initWithFrame:CGRectMake(x, y, _kEntryViewSize, _kEntryViewSize)];\n    if (self) {\n        #if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n            if (@available(iOS 13.0, *)) {\n                UIScene *scene = [[UIApplication sharedApplication].connectedScenes anyObject];\n                if (scene) {\n                    self.windowScene = (UIWindowScene *)scene;\n                }\n            }\n        #endif\n        self.backgroundColor = [UIColor clearColor];\n        self.windowLevel = UIWindowLevelStatusBar + 100.f;\n        self.layer.masksToBounds = YES;\n        \n        // 统一使用 DoraemonStatusBarViewController\n        // 对系统的版本处理放入 DoraemonStatusBarViewController 类中\n        if (!self.rootViewController) {\n            self.rootViewController = [[DoraemonStatusBarViewController alloc] init];\n        }\n\n        \n        [self.rootViewController.view addSubview:self.entryBtn];\n        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];\n        [self addGestureRecognizer:pan];\n    }\n    return self;\n}\n\n- (void)show{\n    self.hidden = NO;\n}\n\n- (void)showClose:(NSNotification *)notification{\n    [_entryBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_close\"] forState:UIControlStateNormal];\n    [_entryBtn removeTarget:self action:@selector(showClose:) forControlEvents:UIControlEventTouchUpInside];\n    [_entryBtn addTarget:self action:@selector(closePluginClick:) forControlEvents:UIControlEventTouchUpInside];\n}\n\n- (void)closePluginClick:(UIButton *)btn{\n    [_entryBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_logo\"] forState:UIControlStateNormal];\n    [_entryBtn removeTarget:self action:@selector(closePluginClick:) forControlEvents:UIControlEventTouchUpInside];\n    [_entryBtn addTarget:self action:@selector(entryClick:) forControlEvents:UIControlEventTouchUpInside];\n}\n\n/**\n 进入工具主面板\n */\n- (void)entryClick:(UIButton *)btn{\n    if ([DoraemonHomeWindow shareInstance].hidden) {\n        [[DoraemonHomeWindow shareInstance] show];\n        DoKitBP(@\"dokit_sdk_home_ck_entry\")\n    }else{\n        [[DoraemonHomeWindow shareInstance] hide];\n    }\n}\n\n- (void)pan:(UIPanGestureRecognizer *)pan{\n    if (self.autoDock) {\n        [self autoDocking:pan];\n    }else{\n        [self normalMode:pan];\n    }\n}\n\n- (void)normalMode: (UIPanGestureRecognizer *)panGestureRecognizer{\n    //1、获得拖动位移\n    CGPoint offsetPoint = [panGestureRecognizer translationInView:panGestureRecognizer.view];\n    //2、清空拖动位移\n    [panGestureRecognizer setTranslation:CGPointZero inView:panGestureRecognizer.view];\n    //3、重新设置控件位置\n    UIView *panView = panGestureRecognizer.view;\n    CGFloat newX = panView.doraemon_centerX+offsetPoint.x;\n    CGFloat newY = panView.doraemon_centerY+offsetPoint.y;\n    if (newX < _kEntryViewSize/2) {\n        newX = _kEntryViewSize/2;\n    }\n    if (newX > DoraemonScreenWidth - _kEntryViewSize/2) {\n        newX = DoraemonScreenWidth - _kEntryViewSize/2;\n    }\n    if (newY < _kEntryViewSize/2) {\n        newY = _kEntryViewSize/2;\n    }\n    if (newY > DoraemonScreenHeight - _kEntryViewSize/2) {\n        newY = DoraemonScreenHeight - _kEntryViewSize/2;\n    }\n    panView.center = CGPointMake(newX, newY);\n}\n\n- (void)autoDocking:(UIPanGestureRecognizer *)panGestureRecognizer {\n    UIView *panView = panGestureRecognizer.view;\n    switch (panGestureRecognizer.state) {\n        case UIGestureRecognizerStateBegan:\n        case UIGestureRecognizerStateChanged:\n        {\n            CGPoint translation = [panGestureRecognizer translationInView:panView];\n            [panGestureRecognizer setTranslation:CGPointZero inView:panView];\n            panView.center = CGPointMake(panView.center.x + translation.x, panView.center.y + translation.y);\n        }\n            break;\n        case UIGestureRecognizerStateEnded:\n        case UIGestureRecognizerStateCancelled:\n        {\n            CGPoint location = panView.center;\n            CGFloat centerX;\n            CGFloat safeBottom = 0;\n            if (@available(iOS 11.0, *)) {\n               safeBottom = self.safeAreaInsets.bottom;\n            }\n            CGFloat centerY = MAX(MIN(location.y, CGRectGetMaxY([UIScreen mainScreen].bounds)-safeBottom), [UIApplication sharedApplication].statusBarFrame.size.height);\n            if(location.x > CGRectGetWidth([UIScreen mainScreen].bounds)/2.f)\n            {\n                centerX = CGRectGetWidth([UIScreen mainScreen].bounds)-_kEntryViewSize/2;\n            }\n            else\n            {\n                centerX = _kEntryViewSize/2;\n            }\n            [[NSUserDefaults standardUserDefaults] setObject:@{\n                                                               @\"x\":[NSNumber numberWithFloat:centerX],\n                                                               @\"y\":[NSNumber numberWithFloat:centerY]\n                                                               } forKey:@\"FloatViewCenterLocation\"];\n            [UIView animateWithDuration:0.3 animations:^{\n                panView.center = CGPointMake(centerX, centerY);\n            }];\n        }\n\n        default:\n            break;\n    }\n}\n\n- (void)setAutoDock:(BOOL)autoDock {\n    _autoDock = autoDock;\n    NSDictionary *dict = [[NSUserDefaults standardUserDefaults] objectForKey:@\"FloatViewCenterLocation\"];\n    if (dict && dict[@\"x\"] && dict[@\"y\"]) {\n        self.center = CGPointMake([dict[@\"x\"] integerValue], [dict[@\"y\"] integerValue]);\n    }\n}\n\n- (void)configEntryBtnBlingWithText:(NSString *)text backColor:(UIColor *)backColor {\n\n    if (text == nil || text.length == 0) {\n        [self destoryBlingText];\n        return;\n    }\n    backColor = backColor ?: [UIColor whiteColor];\n\n    if (!self.entryBtnBlingTextLabel) {\n        self.entryBtnBlingTextLabel = [[UILabel alloc] initWithFrame:self.entryBtn.bounds];\n        self.entryBtnBlingTextLabel.transform = CGAffineTransformMakeScale(0.6, 0.6);\n        [self.entryBtn addSubview:self.entryBtnBlingTextLabel];\n        self.entryBtnBlingTextLabel.layer.cornerRadius = self.entryBtnBlingTextLabel.bounds.size.width/2.0;\n        self.entryBtnBlingTextLabel.clipsToBounds = YES;\n        self.entryBtnBlingTextLabel.font = [UIFont systemFontOfSize:self.entryBtn.bounds.size.width - 10];\n        self.entryBtnBlingTextLabel.textAlignment = NSTextAlignmentCenter;\n        self.entryBtnBlingTextLabel.textColor = [UIColor whiteColor];\n        CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@\"opacity\"];\n        animation.fromValue = @1;\n        animation.toValue = @0;\n        animation.duration = 3.0;\n        animation.beginTime = CACurrentMediaTime() + 0;\n        animation.repeatCount = HUGE_VALF ;\n        animation.autoreverses = YES;\n        animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];\n        animation.fillMode = kCAFillModeForwards;\n        [self.entryBtnBlingTextLabel.layer addAnimation:animation forKey:@\"bling\"];\n    }\n    self.entryBtnBlingTextLabel.backgroundColor = backColor;\n    self.entryBtnBlingTextLabel.text = [text substringToIndex:1];\n    \n}\n\n- (void)destoryBlingText {\n    [self.entryBtnBlingTextLabel.layer removeAllAnimations];\n    [self.entryBtnBlingTextLabel removeFromSuperview];\n    self.entryBtnBlingTextLabel = nil;\n}\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Cell/DoraemonHomeCell.h",
    "content": "//\n//  DoraemonHomeCell.h\n//  DoraemonKit\n//\n//  Created by dengyouhua on 2019/9/4.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHomeCell : UICollectionViewCell\n\n- (void)update:(NSString *)image name:(NSString *)name;\n- (void)updateImage:(UIImage *)image;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Cell/DoraemonHomeCell.m",
    "content": "//\n//  DoraemonHomeCell.m\n//  DoraemonKit\n//\n//  Created by dengyouhua on 2019/9/4.\n//\n\n#import \"DoraemonHomeCell.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHomeCell()\n\n@property (nonatomic, strong) UIImageView *icon;\n@property (nonatomic, strong) UILabel *name;\n\n@end\n\n@implementation DoraemonHomeCell\n\n- (UIImageView *)icon {\n    if (!_icon) {\n        CGFloat size = kDoraemonSizeFrom750_Landscape(68);\n        _icon = [[UIImageView alloc] initWithFrame:CGRectMake((self.doraemon_width - size) / 2.0, 4, size, size)];\n    }\n    \n    return _icon;\n}\n\n- (UILabel *)name {\n    if (!_name) {\n        CGFloat height = kDoraemonSizeFrom750_Landscape(32);\n        _name = [[UILabel alloc] initWithFrame:CGRectMake(0, self.doraemon_height - height - 4, self.doraemon_width, height)];\n        _name.textAlignment = NSTextAlignmentCenter;\n        _name.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n        _name.adjustsFontSizeToFitWidth = YES;\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            _name.textColor = [UIColor labelColor];\n        }\n#endif\n    }\n    \n    return _name;\n}\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    if (self) {\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            self.backgroundColor = [UIColor systemBackgroundColor];\n        } else {\n#endif\n            self.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        [self addSubview:self.icon];\n        [self addSubview:self.name];\n    }\n    return self;\n}\n\n- (void)update:(NSString *)image name:(NSString *)name {\n    self.icon.image = [UIImage doraemon_xcassetImageNamed:image];\n    self.name.text = name;\n}\n\n- (void)updateImage:(UIImage *)image {\n    if (image) {\n        self.icon.image = image;\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Cell/DoraemonHomeCloseCell.h",
    "content": "//\n//  DoraemonHomeCloseCell.h\n//  DoraemonKit\n//\n//  Created by dengyouhua on 2019/9/4.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHomeCloseCell : UICollectionViewCell\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Cell/DoraemonHomeCloseCell.m",
    "content": "//\n//  DoraemonHomeCloseCell.m\n//  DoraemonKit\n//\n//  Created by dengyouhua on 2019/9/4.\n//\n\n#import \"DoraemonHomeCloseCell.h\"\n#import \"DoraemonManager.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonDefine.h\"\n#import \"UIViewController+Doraemon.h\"\n\n@interface DoraemonHomeCloseCell ()\n\n@property (nonatomic, strong) UIButton *closeButton;\n\n@end\n\n@implementation DoraemonHomeCloseCell\n\n- (void)closeButtonHandle{\n    UIAlertController * alertController = [UIAlertController alertControllerWithTitle:DoraemonLocalizedString(@\"提示\") message:DoraemonLocalizedString(@\"Doraemon关闭之后需要重启App才能重新打开\") preferredStyle:UIAlertControllerStyleAlert];\n    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"取消\") style:UIAlertActionStyleCancel handler:nil];\n    UIAlertAction *okAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"确定\") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n        [[DoraemonManager shareInstance] hiddenDoraemon];\n    }];\n    [alertController addAction:cancelAction];\n    [alertController addAction:okAction];\n    [[UIViewController rootViewControllerForKeyWindow] presentViewController:alertController animated:YES completion:nil];\n\n    [[DoraemonHomeWindow shareInstance] hide];\n}\n\n- (UIButton *)closeButton {\n    if (!_closeButton) {\n        _closeButton = [UIButton buttonWithType:UIButtonTypeCustom];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n                if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {\n                    return [UIColor whiteColor];\n                } else {\n                    return [UIColor doraemon_colorWithString:@\"#C1C3BF\"];\n                }\n            }];\n            _closeButton.backgroundColor = dyColor;\n        } else {\n#endif\n            _closeButton.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        _closeButton.layer.cornerRadius = kDoraemonSizeFrom750_Landscape(5.0);\n        _closeButton.layer.masksToBounds = YES;\n        [_closeButton setTitle:DoraemonLocalizedString(@\"关闭DoraemonKit\") forState:UIControlStateNormal];\n        [_closeButton setTitleColor:[UIColor doraemon_colorWithString:@\"#CC3A4B\"] forState:UIControlStateNormal];\n        _closeButton.titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)];\n        [_closeButton addTarget:self action:@selector(closeButtonHandle) forControlEvents:UIControlEventTouchUpInside];\n    }\n    \n    return _closeButton;\n}\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    if (self) {\n        [self addSubview:self.closeButton];\n    }\n    return self;\n}\n\n- (void)layoutSubviews {\n    [super layoutSubviews];\n    \n    CGFloat x = kDoraemonSizeFrom750_Landscape(10);\n\n    self.closeButton.frame = CGRectMake(x, 0, self.doraemon_width - x * 2, kDoraemonSizeFrom750_Landscape(100));\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Cell/DoraemonHomeFootCell.h",
    "content": "//\n//  DoraemonHomeFootCell.h\n//  DoraemonKit\n//\n//  Created by dengyouhua on 2019/9/4.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHomeFootCell : UICollectionReusableView\n\n@property (nonatomic, strong) UILabel *title;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Cell/DoraemonHomeFootCell.m",
    "content": "//\n//  DoraemonHomeFootCell.m\n//  DoraemonKit\n//\n//  Created by dengyouhua on 2019/9/4.\n//\n\n#import \"DoraemonHomeFootCell.h\"\n\n@implementation DoraemonHomeFootCell\n\n- (UILabel *)title {\n    if (!_title) {\n        _title = [UILabel new];\n    }\n    \n    return _title;\n}\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    if (self) {\n        [self addSubview:self.title];\n    }\n    return self;\n}\n\n- (void)layoutSubviews {\n    [super layoutSubviews];\n    self.title.frame = UIEdgeInsetsInsetRect(self.bounds, UIEdgeInsetsMake(0, 15, 0, 15));\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Cell/DoraemonHomeHeadCell.h",
    "content": "//\n//  DoraemonHomeHeadCell.h\n//  DoraemonKit\n//\n//  Created by dengyouhua on 2019/9/4.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHomeHeadCell : UICollectionReusableView\n\n- (void)renderUIWithTitle:(nullable NSString *)title;\n- (void)renderUIWithSubTitle:(NSString *)subTitle;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Cell/DoraemonHomeHeadCell.m",
    "content": "//\n//  DoraemonHomeHeadCell.m\n//  DoraemonKit\n//\n//  Created by dengyouhua on 2019/9/4.\n//\n\n#import \"DoraemonHomeHeadCell.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHomeHeadCell()\n\n@property (nonatomic, strong) UILabel *title;\n@property (nonatomic, strong) UILabel *subTitleLabel;\n\n@end\n\n@implementation DoraemonHomeHeadCell\n\n- (UILabel *)title {\n    if (!_title) {\n        _title = [UILabel new];\n    }\n    \n    return _title;\n}\n\n- (UILabel *)subTitleLabel{\n    if (!_subTitleLabel) {\n        _subTitleLabel = [UILabel new];\n    }\n    \n    return _subTitleLabel;\n}\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    if (self) {\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            self.backgroundColor = [UIColor systemBackgroundColor];\n        } else {\n#endif\n            self.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        [self addSubview:self.title];\n    }\n    return self;\n}\n\n- (void)renderUIWithTitle:(NSString *)title{\n    _title.text = title;\n    _title.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n    if (_subTitleLabel) {\n        [_subTitleLabel removeFromSuperview];\n        _subTitleLabel = nil;\n    }\n    if (title && [title isEqualToString:DoraemonLocalizedString(@\"平台工具\")]) {\n        [self renderUIWithSubTitle:@\"(www.dokit.cn)\"];\n    }\n    [self setNeedsLayout];\n}\n\n- (void)renderUIWithSubTitle:(NSString *)subTitle{\n    if (subTitle && subTitle.length>0) {\n        [self addSubview:self.subTitleLabel];\n        self.subTitleLabel.textColor = [UIColor redColor];\n        self.subTitleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n        self.subTitleLabel.text = subTitle;\n    }\n}\n\n- (void)layoutSubviews {\n    [super layoutSubviews];\n    \n    [self.title sizeToFit];\n    self.title.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), self.doraemon_height/2-self.title.doraemon_height/2, self.title.doraemon_width, self.title.doraemon_height);\n    if (self.subTitleLabel) {\n        [self.subTitleLabel sizeToFit];\n        self.subTitleLabel.frame = CGRectMake(self.title.doraemon_right+kDoraemonSizeFrom750_Landscape(2), self.doraemon_height/2-self.subTitleLabel.doraemon_height/2, self.subTitleLabel.doraemon_width, self.subTitleLabel.doraemon_height);\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/DoraemonHomeViewController.h",
    "content": "//\n//  DoraemonHomeViewController.h\n//  DoraemonKit\n//\n//  Created by dengyouhua on 2019/9/4.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n/**\n * DoraemonHomeEntry | 新主界面入口\n */\n@interface DoraemonHomeViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/DoraemonHomeViewController.m",
    "content": "//\n//  DoraemonHomeViewController.m\n//  DoraemonKit\n//\n//  Created by dengyouhua on 2019/9/4.\n//\n\n#import \"DoraemonHomeViewController.h\"\n#import \"UIView+Doraemon.h\"\n#import \"UIColor+Doraemon.h\"\n#import \"DoraemonManager.h\"\n#import \"DoraemonPluginProtocol.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonHomeCell.h\"\n#import \"DoraemonHomeHeadCell.h\"\n#import \"DoraemonHomeFootCell.h\"\n#import \"DoraemonHomeCloseCell.h\"\n#import \"UIViewController+Doraemon.h\"\n#import \"DoraemonBuriedPointManager.h\"\n#import \"DoraemonSettingViewController.h\"\n#import \"DoraemonCacheManager.h\"\n\nstatic NSString *DoraemonHomeCellID = @\"DoraemonHomeCellID\";\nstatic NSString *DoraemonHomeHeadCellID = @\"DoraemonHomeHeadCellID\";\nstatic NSString *DoraemonHomeFootCellID = @\"DoraemonHomeFootCellID\";\nstatic NSString *DoraemonHomeCloseCellID = @\"DoraemonHomeCloseCellID\";\n\n@interface DoraemonHomeViewController () <UICollectionViewDelegate, UICollectionViewDataSource>\n\n@property (nonatomic, strong) UICollectionView *collectionView;\n@property (nonatomic,strong) NSMutableArray *dataArray;\n\n@end\n\n@implementation DoraemonHomeViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = @\"DoKit\";\n    [self setLeftNavBarItems:nil];\n    [self setRightNavTitle:DoraemonLocalizedString(@\"设置\")];\n    \n    \n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        self.view.backgroundColor = [UIColor tertiarySystemBackgroundColor];\n    } else {\n#endif\n        self.view.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n    NSMutableArray *dataArray = [[DoraemonCacheManager sharedInstance] allKitShowManagerData];\n    _dataArray = dataArray;\n    [self.view addSubview:self.collectionView];\n    \n    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kitManagerUpdate:) name:DoraemonKitManagerUpdateNotification object:nil];\n}\n\n- (void)viewDidLayoutSubviews {\n    [super viewDidLayoutSubviews];\n\n    self.collectionView.frame = [self fullscreen];\n}\n\n- (void)rightNavTitleClick:(id)clickView{\n    DoraemonSettingViewController *vc = [[DoraemonSettingViewController alloc] init];\n    [self.navigationController pushViewController:vc animated:YES];\n}\n\n- (void)kitManagerUpdate:(NSNotification *)aNotification {\n    _dataArray = [[DoraemonCacheManager sharedInstance] allKitShowManagerData];\n    [self.collectionView reloadData];\n}\n\n- (void)dealloc{\n    [[NSNotificationCenter defaultCenter] removeObserver:self];\n}\n\n#pragma mark -- UICollectionView\n- (UICollectionView *)collectionView {\n    if (!_collectionView) {\n        UICollectionViewFlowLayout *fl = [[UICollectionViewFlowLayout alloc] init];\n        _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:fl];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            _collectionView.backgroundColor = [UIColor systemBackgroundColor];\n        } else {\n#endif\n            _collectionView.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        _collectionView.showsVerticalScrollIndicator = NO;\n        _collectionView.delegate = self;\n        _collectionView.dataSource = self;\n        [_collectionView registerClass:[DoraemonHomeCell class] forCellWithReuseIdentifier:DoraemonHomeCellID];\n        [_collectionView registerClass:[DoraemonHomeCloseCell class] forCellWithReuseIdentifier:DoraemonHomeCloseCellID];\n        [_collectionView registerClass:[DoraemonHomeHeadCell class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:DoraemonHomeHeadCellID];\n        [_collectionView registerClass:[DoraemonHomeFootCell class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:DoraemonHomeFootCellID];\n    }\n    \n    return _collectionView;\n}\n\n- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {\n    if (indexPath.section < _dataArray.count) {\n        return CGSizeMake(kDoraemonSizeFrom750_Landscape(160), kDoraemonSizeFrom750_Landscape(128));\n    } else {\n        return CGSizeMake(DoraemonScreenWidth, kDoraemonSizeFrom750_Landscape(100));\n    }\n}\n\n- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {\n    if (section < _dataArray.count) {\n        return CGSizeMake(DoraemonScreenWidth, kDoraemonSizeFrom750_Landscape(88));\n    } else {\n        return CGSizeMake(DoraemonScreenWidth, 0);\n    }\n}\n\n- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section {\n    if (section < _dataArray.count) {\n        return CGSizeMake(DoraemonScreenWidth, kDoraemonSizeFrom750_Landscape(24));\n    } else {\n        return CGSizeMake(DoraemonScreenWidth, kDoraemonSizeFrom750_Landscape(80));\n    }\n}\n\n- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath {\n    view.layer.zPosition = 0.0;\n}\n\n- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {\n    return _dataArray.count + 1;\n}\n\n- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {\n    if (section < self.dataArray.count) {\n        NSDictionary *dict = _dataArray[section];\n        NSArray *pluginArray = dict[@\"pluginArray\"];\n        return pluginArray.count;\n    } else {\n        return 1;\n    }\n}\n\n- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {\n    NSInteger row = indexPath.row;\n    NSInteger section = indexPath.section;\n    \n    if (section < _dataArray.count) {\n        DoraemonHomeCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:DoraemonHomeCellID forIndexPath:indexPath];\n        NSDictionary *dict = _dataArray[section];\n        NSArray *pluginArray = dict[@\"pluginArray\"];\n        NSDictionary *item = pluginArray[row];\n        [cell update:item[@\"icon\"] name:item[@\"name\"]];\n        [cell updateImage:item[@\"image\"]];\n        return cell;\n    } else {\n        DoraemonHomeCloseCell *closeCell = [collectionView dequeueReusableCellWithReuseIdentifier:DoraemonHomeCloseCellID forIndexPath: indexPath];\n        return closeCell;\n    }\n}\n\n- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {\n    UICollectionReusableView *view;\n    if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {\n        DoraemonHomeHeadCell *head = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:DoraemonHomeHeadCellID  forIndexPath:indexPath];\n        [head renderUIWithTitle:nil];\n        NSInteger section = indexPath.section;\n        if (section < _dataArray.count) {\n            NSDictionary *dict = _dataArray[section];\n            [head renderUIWithTitle:dict[@\"moduleName\"]];\n        }\n        \n        view = head;\n    } else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) {\n        DoraemonHomeFootCell *foot = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:DoraemonHomeFootCellID forIndexPath:indexPath];\n        UIColor *dyColor;\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            __weak typeof(self) weakSelf = self;\n            dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n                if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {\n                    return [UIColor doraemon_colorWithString:@\"#F4F5F6\"];\n                } else {\n                    if (indexPath.section >= weakSelf.dataArray.count) {\n                        return [UIColor systemBackgroundColor];\n                    } else {\n                        return [UIColor doraemon_colorWithString:@\"#353537\"];\n                    }\n                }\n            }];\n        } else {\n#endif\n            dyColor = [UIColor doraemon_colorWithString:@\"#F4F5F6\"];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        if (indexPath.section >= self.dataArray.count) {\n            NSString *str = DoraemonLocalizedString(@\"当前版本\");\n            NSString *last = [NSString stringWithFormat:@\"%@：V%@\", str, DoKitVersion];\n            foot.title.text = last;\n            foot.title.textColor = [UIColor doraemon_colorWithString:@\"#999999\"];\n            foot.title.textAlignment = NSTextAlignmentCenter;\n            foot.title.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)]; // kDoraemonSizeFrom750\n        } else {\n            foot.title.text = nil;\n        }\n        foot.backgroundColor = dyColor;\n        view = foot;\n    }else{\n        view = [[UICollectionReusableView alloc] init];\n    }\n    \n    return view;\n}\n\n- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {\n    if (section < _dataArray.count)\n        return UIEdgeInsetsMake(0, kDoraemonSizeFrom750_Landscape(24), kDoraemonSizeFrom750_Landscape(24), kDoraemonSizeFrom750_Landscape(24));//分别为上、左、下、右\n    return UIEdgeInsetsMake(0, 0, 0, 0);\n}\n\n- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {\n    NSInteger section = indexPath.section;\n    if (section < self.dataArray.count) {\n        NSDictionary *dict = _dataArray[section];\n        NSArray *pluginArray = dict[@\"pluginArray\"];\n        NSDictionary *itemData = pluginArray[indexPath.row];\n        NSString *pluginName = itemData[@\"pluginName\"];\n        if(pluginName){\n            DoKitBP(itemData[@\"buriedPoint\"])\n            Class pluginClass = NSClassFromString(pluginName);\n            id<DoraemonPluginProtocol> plugin = [[pluginClass alloc] init];\n            if ([plugin respondsToSelector:@selector(pluginDidLoad)]) {\n                [plugin pluginDidLoad];\n            }\n            if ([plugin respondsToSelector:@selector(pluginDidLoad:)]) {\n                [plugin pluginDidLoad:(NSDictionary *)itemData];\n            }\n            \n            void (^handleBlock)(NSDictionary *itemData) = [DoraemonManager shareInstance].keyBlockDic[itemData[@\"key\"]];\n            if (handleBlock) {\n                handleBlock(itemData);\n            }\n        }\n    }\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/DoraemonHomeWindow.h",
    "content": "//\n//  DoraemonHomeWindow.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/5/16.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface DoraemonHomeWindow : UIWindow\n\n@property (nonatomic, strong) UINavigationController *nav;\n\n+ (DoraemonHomeWindow *)shareInstance;\n\n// open plugin vc\n+ (void)openPlugin:(UIViewController *)vc;\n\n- (void)show;\n- (void)hide;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/DoraemonHomeWindow.m",
    "content": "//\n//  DoraemonHomeWindow.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/5/16.\n//\n\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonDefine.h\"\n#import \"UIColor+Doraemon.h\"\n#import \"DoraemonHomeViewController.h\"\n#import \"DoraemonNavigationController.h\"\n\n@interface DoraemonHomeWindow()\n\n- (void)openPlugin:(UIViewController *)vc;\n\n@end\n\n@implementation DoraemonHomeWindow\n\n+ (DoraemonHomeWindow *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonHomeWindow *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonHomeWindow alloc] initWithFrame:CGRectMake(0, 0, DoraemonScreenWidth, DoraemonScreenHeight)];\n    });\n    return instance;\n}\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        self.windowLevel = UIWindowLevelStatusBar - 1.f;\n        self.backgroundColor = [UIColor clearColor];\n        self.hidden = YES;\n        #if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n            if (@available(iOS 13.0, *)) {\n                for (UIWindowScene* windowScene in [UIApplication sharedApplication].connectedScenes){\n                    if (windowScene.activationState == UISceneActivationStateForegroundActive){\n                        self.windowScene = windowScene;\n                        break;\n                    }\n                }\n            }\n        #endif\n    }\n    return self;\n}\n\n- (void)openPlugin:(UIViewController *)vc{\n    [self setRootVc:vc];\n     self.hidden = NO;\n}\n\n- (void)show{\n    DoraemonHomeViewController *vc = [[DoraemonHomeViewController alloc] init];\n    [self setRootVc:vc];\n    \n    self.hidden = NO;\n}\n\n- (void)hide{\n    if (self.rootViewController.presentedViewController) {\n        [self.rootViewController.presentedViewController dismissViewControllerAnimated:NO completion:nil];\n    }\n    [self setRootVc:nil];\n    \n    self.hidden = YES;\n}\n\n- (void)setRootVc:(UIViewController *)rootVc{\n    if (rootVc) {\n        DoraemonNavigationController *nav = [[DoraemonNavigationController alloc] initWithRootViewController:rootVc];\n        NSDictionary *attributesDic = @{\n                                        NSForegroundColorAttributeName:[UIColor blackColor],\n                                        NSFontAttributeName:[UIFont systemFontOfSize:18]\n                                        };\n        [nav.navigationBar setTitleTextAttributes:attributesDic];\n        _nav = nav;\n        \n        self.rootViewController = nav;\n    }else{\n        self.rootViewController = nil;\n        _nav = nil;\n    }\n\n}\n\n+ (void)openPlugin:(UIViewController *)vc{\n    [[self shareInstance] openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/DoraemonNavigationController.h",
    "content": "//\n//  DoraemonNavigationController.h\n//  DoraemonKit\n//\n//  Created by Chunhui Sun on 2020/7/14.\n//  Copyright © 2020 YunXIao. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonNavigationController : UINavigationController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/DoraemonNavigationController.m",
    "content": "//\n//  DoraemonNavigationController.m\n//  DoraemonKit\n//\n//  Created by Chunhui Sun on 2020/7/14.\n//  Copyright © 2020 YunXIao. All rights reserved.\n//\n\n#import \"DoraemonNavigationController.h\"\n#import \"DoraemonManager.h\"\n\n@interface DoraemonNavigationController ()\n\n@end\n\n@implementation DoraemonNavigationController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    // Do any additional setup after loading the view.\n}\n\n- (UIInterfaceOrientationMask)supportedInterfaceOrientations {\n    return DoraemonManager.shareInstance.supportedInterfaceOrientations;\n}\n\n/*\n#pragma mark - Navigation\n\n// In a storyboard-based application, you will often want to do a little preparation before navigation\n- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {\n    // Get the new view controller using [segue destinationViewController].\n    // Pass the selected object to the new view controller.\n}\n*/\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Settings/DoraemonSettingCell.h",
    "content": "//\n//  DoraemonSettingCell.h\n//  DoraemonKit\n//\n//  Created by yinhaichao on 2021/2/26.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonSettingCell : UITableViewCell\n\n@property (nonatomic, copy) NSDictionary *cellData;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Settings/DoraemonSettingCell.m",
    "content": "//\n//  DoraemonSettingCell.m\n//  DoraemonKit\n//\n//  Created by yinhaichao on 2021/2/26.\n//\n\n#import \"DoraemonSettingCell.h\"\n\n@interface DoraemonSettingCell ()\n\n@property (nonatomic, strong) UILabel *titleLab;\n@property (nonatomic, strong) UILabel *subTitleLab;\n\n@end\n\n@implementation DoraemonSettingCell\n\n- (void)awakeFromNib {\n    [super awakeFromNib];\n    // Initialization code\n}\n\n- (void)setSelected:(BOOL)selected animated:(BOOL)animated {\n    [super setSelected:selected animated:animated];\n\n    // Configure the view for the selected state\n}\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        [self.contentView addSubview:self.titleLab];\n        [self.contentView addSubview:self.subTitleLab];\n    }\n    return self;\n}\n\n- (UILabel *)titleLab {\n    if (!_titleLab) {\n        _titleLab = [[UILabel alloc] initWithFrame:CGRectMake(20, 10, [UIScreen mainScreen].bounds.size.width - 60, 20)];\n        _titleLab.font = [UIFont boldSystemFontOfSize:20];\n    }\n    return _titleLab;\n}\n\n- (UILabel *)subTitleLab {\n    if (!_subTitleLab) {\n        _subTitleLab = [[UILabel alloc] initWithFrame:CGRectMake(20, 30, [UIScreen mainScreen].bounds.size.width - 60, 70)];\n        _subTitleLab.font = [UIFont systemFontOfSize:14];\n        _subTitleLab.textColor = UIColor.lightGrayColor;\n        _subTitleLab.numberOfLines = 3;\n    }\n    return _subTitleLab;\n}\n\n- (void)setCellData:(NSDictionary *)cellData {\n    _cellData = cellData;\n    _titleLab.text = [_cellData objectForKey:@\"name\"];\n    _subTitleLab.text = [_cellData objectForKey:@\"desc\"];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Settings/DoraemonSettingViewController.h",
    "content": "//\n//  DoraemonSettingViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/4/24.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonSettingViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Settings/DoraemonSettingViewController.m",
    "content": "//\n//  DoraemonSettingViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/4/24.\n//\n\n#import \"DoraemonSettingViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonCellButton.h\"\n#import \"DoraemonKitManagerViewController.h\"\n#import \"DoraemonSettingCell.h\"\n#import \"DoraemonDefaultWebViewController.h\"\n#import \"UIViewController+Doraemon.h\"\n\n@interface DoraemonSettingViewController ()<DoraemonCellButtonDelegate, UITableViewDelegate, UITableViewDataSource>\n\n@property (nonatomic, strong) DoraemonCellButton *kitManagerBtn;\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, strong) NSArray *dataArr;\n\n@end\n\n@implementation DoraemonSettingViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"更多\");\n    \n    self.tableView.delegate = self;\n    self.tableView.dataSource = self;\n    [self loadData];\n    [self.tableView registerClass:[DoraemonSettingCell class] forCellReuseIdentifier:@\"dokit.setting.cell\"];\n    [self.view addSubview:self.tableView];\n}\n\n- (void)viewDidLayoutSubviews {\n    [super viewDidLayoutSubviews];\n    self.tableView.frame = [self fullscreen];\n}\n\n- (void)loadData {\n    \n    WEAKSELF(weakSelf)\n    NSURLSession *session = [NSURLSession sharedSession];\n    NSURL *url = [NSURL URLWithString:@\"https://star.xiaojukeji.com/config/get.node?city=-1&areaid=&name=group\"];\n    NSURLSessionTask *task = [session dataTaskWithURL:url\n                                    completionHandler:^(NSData *data, NSURLResponse *response, NSError* error) {\n        if (error == nil) {\n//            NSLog(@\"%@\", [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);\n            NSDictionary *dataDic = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];\n            weakSelf.dataArr = [[dataDic objectForKey:@\"data\"] objectForKey:@\"group\"];\n            \n            dispatch_async(dispatch_get_main_queue(), ^{\n                [weakSelf.tableView reloadData];\n            });\n        }\n    }];\n\n    [task resume];\n}\n\n#pragma mark -- DoraemonCellButtonDelegate\n- (void)cellBtnClick:(id)sender{\n    DoraemonKitManagerViewController *vc = [[DoraemonKitManagerViewController alloc] init];\n    [self.navigationController pushViewController:vc animated:YES];\n}\n\n#pragma mark -- UITableViewDelegate\n\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {\n    return self.dataArr.count;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    NSArray *listArr = [self.dataArr[section] objectForKey:@\"list\"];\n    return listArr.count;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return 100;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonSettingCell *cell = [tableView dequeueReusableCellWithIdentifier:@\"dokit.setting.cell\" forIndexPath:indexPath];\n    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;\n    cell.cellData = self.dataArr[indexPath.section][@\"list\"][indexPath.row];\n    return cell;\n}\n\n- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {\n    return [self.dataArr[section] objectForKey:@\"group\"];\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    \n    [tableView deselectRowAtIndexPath:indexPath animated:YES];\n    \n    NSDictionary *cellData = self.dataArr[indexPath.section][@\"list\"][indexPath.row];\n    \n    if ([[cellData objectForKey:@\"type\"] isEqualToString:@\"native\"]) {\n        DoraemonKitManagerViewController *vc = [[DoraemonKitManagerViewController alloc] init];\n        [self.navigationController pushViewController:vc animated:YES];\n    } else if ([[cellData objectForKey:@\"type\"] isEqualToString:@\"web\"]) {\n        DoraemonDefaultWebViewController *webVc = [[DoraemonDefaultWebViewController alloc] init];\n        webVc.h5Url = cellData[@\"link\"];\n        [self.navigationController pushViewController:webVc animated:YES];\n    }\n}\n#pragma mark -- Getter\n\n- (UITableView *)tableView {\n    if (!_tableView) {\n        _tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStyleGrouped];\n    }\n    return _tableView;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Settings/KitManager/DoraemonKitManagerViewController.h",
    "content": "//\n//  DoraemonKitManagerViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/4/24.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonKitManagerViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Settings/KitManager/DoraemonKitManagerViewController.m",
    "content": "//\n//  DoraemonKitManagerViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/4/24.\n//\n\n#import \"DoraemonKitManagerViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonKitManagerCell.h\"\n#import \"DoraemonManager.h\"\n#import \"UIViewController+Doraemon.h\"\n#import \"DoraemonKitManagerHeadCell.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonNavBarItemModel.h\"\n\nstatic NSString *DoraemonKitManagerCellID = @\"DoraemonKitManagerCellID\";\nstatic NSString *DoraemonKitManagerHeadCellID = @\"DoraemonKitManagerHeadCellID\";\n\n@interface DoraemonKitManagerViewController ()<UICollectionViewDelegate, UICollectionViewDataSource>\n\n@property (nonatomic, strong) UICollectionView *collectionView;\n@property (nonatomic, strong) NSMutableArray *currentArray;\n@property (nonatomic, strong) UILongPressGestureRecognizer *longPress;\n\n@property (nonatomic, assign) BOOL editStatus;\n\n@end\n\n@implementation DoraemonKitManagerViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"工具管理\");\n    [self setRightNavTitle:DoraemonLocalizedString(@\"编辑\")];\n    _editStatus = NO;\n    [self setRightNavBar];\n    _longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];\n\n    //非编辑状态只显示show=YES的Kit\n    _currentArray = [self getShowArray];\n    \n    [self.view addSubview:self.collectionView];\n}\n\n- (void)setRightNavBar{\n    NSString *title = nil;\n    if (_editStatus) {\n        title = DoraemonLocalizedString(@\"完成\");\n    }else{\n        title = DoraemonLocalizedString(@\"编辑\");\n    }\n    DoraemonNavBarItemModel *model1 = [[DoraemonNavBarItemModel alloc] initWithText:title color:[UIColor doraemon_blue] selector:@selector(rightNavTitleClick)];\n    DoraemonNavBarItemModel *model2 = [[DoraemonNavBarItemModel alloc] initWithText:DoraemonLocalizedString(@\"还原\") color:[UIColor doraemon_blue] selector:@selector(reset)];\n    [self setRightNavBarItems:@[model1,model2]];\n}\n\n- (void)viewDidLayoutSubviews {\n    [super viewDidLayoutSubviews];\n    self.collectionView.frame = [self fullscreen];\n}\n\n- (void)leftNavBackClick:(id)clickView{\n    if (_editStatus) {\n        [DoraemonAlertUtil handleAlertActionWithVC:self text:DoraemonLocalizedString(@\"是否保存已编辑的内容\") okBlock:^{\n            [self rightNavTitleClick];\n        } cancleBlock:^{\n            \n        }];\n    }else{\n        [super leftNavBackClick:clickView];\n    }\n}\n\n- (void)reset{\n    __weak typeof(self) weakSelf = self;\n    [DoraemonAlertUtil handleAlertActionWithVC:self text:DoraemonLocalizedString(@\"是否还原到初始状态\") okBlock:^{\n        __strong typeof(self) strongSelf = weakSelf;\n        [[DoraemonCacheManager sharedInstance] saveKitManagerData:[DoraemonManager shareInstance].dataArray];\n        strongSelf.currentArray = [self getAllArray];\n        [strongSelf.collectionView reloadData];\n    } cancleBlock:^{\n        \n    }];\n}\n\n- (void)rightNavTitleClick{\n    _editStatus = !_editStatus;\n    if (_editStatus) {\n        //点击进入编辑状态\n        [self setRightNavBar];\n        [self.collectionView addGestureRecognizer:_longPress];\n        _currentArray = [self getAllArray];\n        [self.collectionView reloadData];\n    }else{\n        //点击进入完成状态\n        [self setRightNavBar];\n        [self.collectionView removeGestureRecognizer:_longPress];\n        [[DoraemonCacheManager sharedInstance] saveKitManagerData:_currentArray];\n        _currentArray = [self getShowArray];\n        [self.collectionView reloadData];\n        \n        [DoraemonToastUtil showToastBlack:DoraemonLocalizedString(@\"保存成功\") inView:self.view];\n    }\n}\n\n- (NSMutableArray *)getAllArray{\n    NSMutableArray *dataArray = [[DoraemonCacheManager sharedInstance] kitManagerData];\n    if (ARRAY_IS_NULL(dataArray)) {\n        [[DoraemonCacheManager sharedInstance] saveKitManagerData:[DoraemonManager shareInstance].dataArray];\n        dataArray = [[DoraemonCacheManager sharedInstance] kitManagerData];\n    }\n    return dataArray;\n}\n\n- (NSMutableArray *)getShowArray{\n    NSMutableArray *dataArray = [[DoraemonCacheManager sharedInstance] kitShowManagerData];\n    if (ARRAY_IS_NULL(dataArray)) {\n        [[DoraemonCacheManager sharedInstance] saveKitManagerData:[DoraemonManager shareInstance].dataArray];\n        dataArray = [[DoraemonCacheManager sharedInstance] kitShowManagerData];\n    }\n    return dataArray;\n}\n\n\n#pragma mark -- UICollectionView\n- (UICollectionView *)collectionView {\n    if (!_collectionView) {\n        UICollectionViewFlowLayout *fl = [[UICollectionViewFlowLayout alloc] init];\n        fl.minimumLineSpacing = CGFLOAT_MIN;\n        fl.minimumInteritemSpacing = CGFLOAT_MIN;\n        _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:fl];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            _collectionView.backgroundColor = [UIColor systemBackgroundColor];\n        } else {\n#endif\n            _collectionView.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        _collectionView.showsVerticalScrollIndicator = NO;\n        _collectionView.delegate = self;\n        _collectionView.dataSource = self;\n        [_collectionView registerClass:[DoraemonKitManagerCell class] forCellWithReuseIdentifier:DoraemonKitManagerCellID];\n        [_collectionView registerClass:[DoraemonKitManagerHeadCell class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:DoraemonKitManagerHeadCellID];\n    }\n    \n    return _collectionView;\n}\n\n- (void)handleLongPress:(UILongPressGestureRecognizer *)longPress {\n    switch (longPress.state) {\n        case UIGestureRecognizerStateBegan:\n        {\n            NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:[longPress locationInView:self.collectionView]];\n            if (![self canMove:indexPath]) {\n                break;\n            }\n            if (@available(iOS 9.0, *)) {\n                [self.collectionView beginInteractiveMovementForItemAtIndexPath:indexPath];\n            } else {\n                // Fallback on earlier versions\n            }\n        }\n            break;\n        case UIGestureRecognizerStateChanged:\n        {\n            if (@available(iOS 9.0, *)) {\n                [self.collectionView updateInteractiveMovementTargetPosition:[longPress locationInView:self.collectionView]];\n            } else {\n                // Fallback on earlier versions\n            }\n        }\n            break;\n        case UIGestureRecognizerStateEnded:\n        {\n            if (@available(iOS 9.0, *)) {\n                [self.collectionView endInteractiveMovement];\n            } else {\n                // Fallback on earlier versions\n            }\n        }\n            break;\n        default:\n        {\n            if (@available(iOS 9.0, *)) {\n                [self.collectionView cancelInteractiveMovement];\n            } else {\n                // Fallback on earlier versions\n            }\n        }\n            break;\n    }\n}\n\n- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {\n    CGFloat w = kDoraemonSizeFrom750_Landscape(750)/4;\n    CGFloat h = w/187*209;\n    \n    CGSize size = CGSizeMake(w, h);\n    return size;\n}\n\n- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {\n    CGFloat w = DoraemonScreenWidth;\n    CGFloat h = kDoraemonSizeFrom750_Landscape(102);\n    CGSize size = CGSizeMake(w, h);\n    return size;\n}\n\n- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {\n    return _currentArray.count;\n}\n\n- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {\n    NSDictionary *dict = _currentArray[section];\n    NSArray *pluginArray = dict[@\"pluginArray\"];\n    return pluginArray.count;\n}\n\n- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonKitManagerCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:DoraemonKitManagerCellID forIndexPath:indexPath];\n    NSInteger row = indexPath.row;\n    NSInteger section = indexPath.section;\n    \n    NSDictionary *dict = _currentArray[section];\n    NSArray *pluginArray = dict[@\"pluginArray\"];\n    NSDictionary *item = pluginArray[row];\n    [cell update:item[@\"icon\"] name:item[@\"name\"] select:[item[@\"show\"] boolValue] editStatus:_editStatus];\n    [cell updateImage:item[@\"image\"]];\n    return cell;\n}\n\n- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {\n    UICollectionReusableView *view;\n    if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {\n        DoraemonKitManagerHeadCell *head = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:DoraemonKitManagerHeadCellID  forIndexPath:indexPath];\n        [head renderUIWithTitle:nil];\n        NSInteger section = indexPath.section;\n        if (section < _currentArray.count) {\n            NSDictionary *dict = _currentArray[section];\n            [head renderUIWithTitle:dict[@\"moduleName\"]];\n        }\n        \n        view = head;\n    }else{\n        view = [[UICollectionReusableView alloc] init];\n    }\n    \n    return view;\n}\n\n- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath {\n    return _editStatus;\n}\n\n- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {\n    if (!_editStatus) {\n        return;\n    }\n    NSInteger sourceSection = sourceIndexPath.section;\n    NSInteger sourceRow = sourceIndexPath.row;\n    \n    \n    NSDictionary *sourceDict = _currentArray[sourceSection];\n    NSMutableArray *sourcePluginArray = sourceDict[@\"pluginArray\"];\n    NSDictionary *sourceItem = sourcePluginArray[sourceRow];\n    \n    [sourcePluginArray removeObjectAtIndex:sourceRow];\n    \n    NSInteger destinationSection = destinationIndexPath.section;\n    NSInteger destinationRow = destinationIndexPath.row;\n    NSDictionary *destinationDict = _currentArray[destinationSection];\n    NSMutableArray *destinationPluginArray = destinationDict[@\"pluginArray\"];\n    [destinationPluginArray insertObject:sourceItem atIndex:destinationRow];\n}\n\n- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {\n    if (!_editStatus) {\n        return;\n    }\n    NSInteger section = indexPath.section;\n    NSMutableDictionary *dict = _currentArray[section];\n    NSMutableArray *pluginArray = dict[@\"pluginArray\"];\n    NSMutableDictionary *itemData = pluginArray[indexPath.row];\n    BOOL show = [itemData[@\"show\"] boolValue];\n    if (show){\n        //取消的时候要观察是不是这个section中的最后一个\n        NSInteger showCount = 0;\n        for (NSDictionary *subDic in pluginArray){\n            BOOL show = [subDic[@\"show\"] boolValue];\n            if (show) {\n                showCount++;\n            }\n        }\n        if (showCount == 1) {\n            [DoraemonToastUtil showToastBlack:DoraemonLocalizedString(@\"每一个分组至少保留一项\") inView:self.view];\n            return;\n        }\n    }\n    itemData[@\"show\"] = @(!show);\n    \n    [self.collectionView reloadData];\n}\n\n#pragma mark -- private\n- (BOOL)canMove:(NSIndexPath *)indexPath{\n    if (!indexPath) {\n        return NO;\n    }\n    NSInteger section = indexPath.section;\n    NSMutableDictionary *dict = _currentArray[section];\n    NSMutableArray *pluginArray = dict[@\"pluginArray\"];\n    NSInteger showCount = 0;\n    for (NSDictionary *subDic in pluginArray){\n        BOOL show = [subDic[@\"show\"] boolValue];\n        if (show) {\n            showCount++;\n        }\n    }\n    if (showCount <= 1) {\n        [DoraemonToastUtil showToastBlack:DoraemonLocalizedString(@\"每一个分组至少保留一项\") inView:self.view];\n        return NO;\n    }\n    return YES;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Settings/KitManager/View/DoraemonKitManagerCell.h",
    "content": "//\n//  DoraemonKitManagerCell.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/4/28.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonKitManagerCell : UICollectionViewCell\n\n- (void)update:(NSString *)image name:(NSString *)name select:(BOOL)select editStatus:(BOOL)editStatus;\n\n- (void)updateImage:(UIImage *)image;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Settings/KitManager/View/DoraemonKitManagerCell.m",
    "content": "//\n//  DoraemonKitManagerCell.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/4/28.\n//\n\n#import \"DoraemonKitManagerCell.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonKitManagerCell()\n\n@property (nonatomic, strong) UIView *centerView;\n@property (nonatomic, strong) UIImageView *icon;\n@property (nonatomic, strong) UILabel *name;\n@property (nonatomic, strong) UIImageView *select;\n@property (nonatomic, strong) UIView *coverView;\n\n@end\n\n@implementation DoraemonKitManagerCell\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    if (self) {\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            self.backgroundColor = [UIColor systemBackgroundColor];\n        } else {\n#endif\n            self.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        self.layer.borderWidth = kDoraemonSizeFrom750_Landscape(1);\n        self.layer.borderColor = [UIColor doraemon_colorWithHexString:@\"#EEEEEE\"].CGColor;\n        \n        [self addSubview:self.centerView];\n        [self addSubview:self.coverView];\n        [self addSubview:self.select];\n        \n        [self.centerView addSubview:self.icon];\n        [self.centerView addSubview:self.name];\n        \n        CGFloat centerViewH = self.name.doraemon_bottom;\n        self.centerView.frame = CGRectMake(self.centerView.doraemon_left, (self.doraemon_height-centerViewH)/2, self.centerView.doraemon_width, centerViewH);\n    }\n    return self;\n}\n\n- (UIView *)centerView{\n    if (!_centerView) {\n        _centerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.doraemon_width, 0)];\n    }\n    return _centerView;\n}\n\n- (UIImageView *)icon {\n    if (!_icon) {\n        CGFloat size = kDoraemonSizeFrom750_Landscape(60);\n        _icon = [[UIImageView alloc] initWithFrame:CGRectMake((self.centerView.doraemon_width - size) / 2.0, 0, size, size)];\n    }\n    \n    return _icon;\n}\n\n- (UILabel *)name {\n    if (!_name) {\n        CGFloat height = kDoraemonSizeFrom750_Landscape(33);\n        _name = [[UILabel alloc] initWithFrame:CGRectMake(0, self.icon.doraemon_bottom+kDoraemonSizeFrom750_Landscape(16), self.centerView.doraemon_width, height)];\n        _name.textAlignment = NSTextAlignmentCenter;\n        _name.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n        _name.adjustsFontSizeToFitWidth = YES;\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            _name.textColor = [UIColor labelColor];\n        }\n#endif\n    }\n    \n    return _name;\n}\n\n- (UIImageView *)select{\n    if (!_select) {\n        CGFloat size = kDoraemonSizeFrom750_Landscape(28);\n        _select = [[UIImageView alloc] initWithFrame:CGRectMake(self.doraemon_width-kDoraemonSizeFrom750_Landscape(12)-size, kDoraemonSizeFrom750_Landscape(12), size, size)];\n    }\n    return _select;\n}\n\n- (UIView *)coverView{\n    if (!_coverView) {\n        _coverView = [[UIView alloc] initWithFrame:self.bounds];\n        _coverView.backgroundColor = [UIColor whiteColor];\n        _coverView.alpha = 0.5;\n    }\n    return _coverView;\n}\n\n\n- (void)update:(NSString *)image name:(NSString *)name select:(BOOL)select editStatus:(BOOL)editStatus{\n    self.icon.image = [UIImage doraemon_xcassetImageNamed:image];\n    self.name.text = name;\n    if (editStatus) {\n        self.select.hidden = NO;\n        if (select) {\n            self.select.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_check_circle_fill\"];\n            self.coverView.hidden = YES;\n        }else{\n            self.select.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_check_circle\"];\n            self.coverView.hidden = NO;\n        }\n    }else{\n        self.select.hidden = YES;\n        self.coverView.hidden = YES;\n    }\n}\n\n- (void)updateImage:(UIImage *)image {\n    if (image) {        \n        self.icon.image = image;\n    }\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Settings/KitManager/View/DoraemonKitManagerHeadCell.h",
    "content": "//\n//  DoraemonKitManagerHeadCell.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/4/28.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonKitManagerHeadCell : UICollectionReusableView\n\n- (void)renderUIWithTitle:(nullable NSString *)title;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Settings/KitManager/View/DoraemonKitManagerHeadCell.m",
    "content": "//\n//  DoraemonKitManagerHeadCell.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/4/28.\n//\n\n#import \"DoraemonKitManagerHeadCell.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonKitManagerHeadCell()\n\n@property (nonatomic, strong) UILabel *title;\n\n@end\n\n@implementation DoraemonKitManagerHeadCell\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    if (self) {\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            self.backgroundColor = [UIColor systemBackgroundColor];\n        } else {\n#endif\n            self.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        [self addSubview:self.title];\n    }\n    return self;\n}\n\n- (UILabel *)title {\n    if (!_title) {\n        _title = [UILabel new];\n        _title.textColor = [UIColor doraemon_colorWithString:@\"#333333\"];\n        _title.font = [UIFont boldSystemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n    }\n    \n    return _title;\n}\n\n- (void)renderUIWithTitle:(NSString *)title{\n    _title.text = title;\n    [self setNeedsLayout];\n}\n\n- (void)layoutSubviews {\n    [super layoutSubviews];\n    \n    [self.title sizeToFit];\n    self.title.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), self.doraemon_height/2-self.title.doraemon_height/2, self.title.doraemon_width, self.title.doraemon_height);\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Settings/KitManager/View/DoraemonKitManagerResetCell.h",
    "content": "//\n//  DoraemonKitManagerResetCell.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/4/28.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonKitManagerResetCell : UICollectionViewCell\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Entry/Home/Settings/KitManager/View/DoraemonKitManagerResetCell.m",
    "content": "//\n//  DoraemonKitManagerResetCell.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/4/28.\n//\n\n#import \"DoraemonKitManagerResetCell.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonKitManagerResetCell()\n\n@property (nonatomic, strong) UIButton *resetButton;\n\n@end\n\n@implementation DoraemonKitManagerResetCell\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    if (self) {\n        [self addSubview:self.resetButton];\n    }\n    return self;\n}\n\n- (void)layoutSubviews {\n    [super layoutSubviews];\n    \n    CGFloat x = 5;\n\n    self.resetButton.frame = CGRectMake(x, x, self.doraemon_width - x * 2, 50);\n}\n\n- (UIButton *)resetButton {\n    if (!_resetButton) {\n        _resetButton = [UIButton buttonWithType:UIButtonTypeCustom];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n                if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {\n                    return [UIColor whiteColor];\n                } else {\n                    return [UIColor doraemon_colorWithString:@\"#C1C3BF\"];\n                }\n            }];\n            _resetButton.backgroundColor = dyColor;\n        } else {\n#endif\n            _resetButton.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        _resetButton.layer.cornerRadius = 5.0;\n        _resetButton.layer.masksToBounds = YES;\n        [_resetButton setTitle:DoraemonLocalizedString(@\"还原到初始状态\") forState:UIControlStateNormal];\n        [_resetButton setTitleColor:[UIColor doraemon_colorWithString:@\"#CC3A4B\"] forState:UIControlStateNormal];\n        [_resetButton addTarget:self action:@selector(resetButtonHandle) forControlEvents:UIControlEventTouchUpInside];\n    }\n    \n    return _resetButton;\n}\n\n- (void)resetButtonHandle{\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Manager/DoraemonManager.h",
    "content": "//\n//  DoraemonManager.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\n\nNS_ASSUME_NONNULL_BEGIN\ntypedef void (^DoraemonH5DoorBlock)(NSString *);\ntypedef UIImage * _Nullable (^DoraemonWebpHandleBlock)(NSString *filePath);\n\ntypedef NS_ENUM(NSUInteger, DoraemonManagerPluginType) {\n    #pragma mark - weex专项工具\n    // 日志\n    DoraemonManagerPluginType_DoraemonWeexLogPlugin,\n    // 缓存\n    DoraemonManagerPluginType_DoraemonWeexStoragePlugin,\n    // 信息\n    DoraemonManagerPluginType_DoraemonWeexInfoPlugin,\n    // DevTool\n    DoraemonManagerPluginType_DoraemonWeexDevToolPlugin,\n    #pragma mark - 常用工具\n    // App设置\n    DoraemonManagerPluginType_DoraemonAppSettingPlugin,\n    // App信息\n    DoraemonManagerPluginType_DoraemonAppInfoPlugin,\n    // 沙盒浏览\n    DoraemonManagerPluginType_DoraemonSandboxPlugin,\n    // MockGPS\n    DoraemonManagerPluginType_DoraemonGPSPlugin,\n    // H5任意门\n    DoraemonManagerPluginType_DoraemonH5Plugin,\n    // Crash查看\n    DoraemonManagerPluginType_DoraemonCrashPlugin,\n    // 子线程UI\n    DoraemonManagerPluginType_DoraemonSubThreadUICheckPlugin,\n    // 清理缓存\n    DoraemonManagerPluginType_DoraemonDeleteLocalDataPlugin,\n    // NSLog\n    DoraemonManagerPluginType_DoraemonNSLogPlugin,\n    // 日志显示\n    DoraemonManagerPluginType_DoraemonCocoaLumberjackPlugin,\n    // 数据库工具\n    DoraemonManagerPluginType_DoraemonDatabasePlugin,\n    // NSUserDefaults工具\n    DoraemonManagerPluginType_DoraemonNSUserDefaultsPlugin,\n    // JS脚本\n    DoraemonManagerPluginType_DoraemonJavaScriptPlugin,\n    \n    #pragma mark - 性能检测\n    // 帧率监控\n    DoraemonManagerPluginType_DoraemonFPSPlugin,\n    // CPU监控\n    DoraemonManagerPluginType_DoraemonCPUPlugin,\n    // 内存监控\n    DoraemonManagerPluginType_DoraemonMemoryPlugin,\n    // 流量监控\n    DoraemonManagerPluginType_DoraemonNetFlowPlugin,\n    // 卡顿检测\n    DoraemonManagerPluginType_DoraemonANRPlugin,\n    // Load耗时\n    DoraemonManagerPluginType_DoraemonMethodUseTimePlugin,\n    // 大图检测\n    DoraemonManagerPluginType_DoraemonLargeImageFilter,\n    // 启动耗时\n    DoraemonManagerPluginType_DoraemonStartTimePlugin,\n    // 内存泄漏\n    DoraemonManagerPluginType_DoraemonMemoryLeakPlugin,\n    // UI层级检查\n    DoraemonManagerPluginType_DoraemonUIProfilePlugin,\n    // UI结构调整\n    DoraemonManagerPluginType_DoraemonHierarchyPlugin,\n    // 函数耗时\n    DoraemonManagerPluginType_DoraemonTimeProfilePlugin,\n    // 模拟弱网\n    DoraemonManagerPluginType_DoraemonWeakNetworkPlugin,\n    \n    #pragma mark - 视觉工具\n    // 颜色吸管\n    DoraemonManagerPluginType_DoraemonColorPickPlugin,\n    // 组件检查\n    DoraemonManagerPluginType_DoraemonViewCheckPlugin,\n    // 对齐标尺\n    DoraemonManagerPluginType_DoraemonViewAlignPlugin,\n    // 元素边框线\n    DoraemonManagerPluginType_DoraemonViewMetricsPlugin,\n    \n    #pragma mark - 平台工具\n    // Mock 数据\n    DoraemonManagerPluginType_DoraemonMockPlugin,\n    DoraemonManagerPluginType_DoraemonHealthPlugin,\n    DoraemonManagerPluginType_DoraemonFileSyncPlugin\n};\n\n@interface DoraemonManagerPluginTypeModel : NSObject\n\n@property(nonatomic, copy) NSString *title;\n@property(nonatomic, copy) NSString *desc;\n@property(nonatomic, copy) NSString *icon;\n@property(nonatomic, copy) NSString *pluginName;\n@property(nonatomic, copy) NSString *atModule;\n@property(nonatomic, copy) NSString *buriedPoint;\n\n@end\n\n@interface DoraemonManager : NSObject\n\n+ (nonnull DoraemonManager *)shareInstance;\n\n@property (nonatomic, copy) NSString *appKey __attribute__((deprecated(\"此属性已被弃用，替换方式请参考最新 https://www.dokit.cn/ 的使用手册\")));\n\n@property (nonatomic, copy) NSString *pId; //产品id 平台端的工具必须填写\n\n@property (nonatomic, copy) NSString *mockDomain; //产品mockDomain 非必填 默认mock.dokit.cn\n\n@property (nonatomic, assign) BOOL autoDock; //dokit entry icon support autoDock，deffault yes\n\n- (void)install;\n// 带有平台端功能的s初始化方式\n- (void)installWithPid:(NSString *)pId;\n\n// 自定义平台mockDomain初始化方式\n- (void)installWithMockDomain:(NSString *)mockDomain;\n\n// 定制起始位置 | 适用正好挡住关键位置\n- (void)installWithStartingPosition:(CGPoint) position;\n\n- (void)installWithCustomBlock:(void(^)(void))customBlock;\n\n@property (nonatomic,strong) NSMutableArray *dataArray;\n\n@property (nonatomic, copy) DoraemonH5DoorBlock h5DoorBlock;\n@property (nonatomic, copy) DoraemonWebpHandleBlock webpHandleBlock;\n\n- (void)addPluginWithTitle:(NSString *)title icon:(NSString *)iconName desc:(NSString *)desc pluginName:(NSString *)entryName atModule:(NSString *)moduleName;\n- (void)addPluginWithTitle:(NSString *)title icon:(NSString *)iconName desc:(NSString *)desc pluginName:(NSString *)entryName atModule:(NSString *)moduleName handle:(void(^)(NSDictionary *itemData))handleBlock;\n\n- (void)addPluginWithTitle:(NSString *)title image:(UIImage *)image desc:(NSString *)desc pluginName:(NSString *)entryName atModule:(NSString *)moduleName handle:(void(^ _Nullable)(NSDictionary *itemData))handleBlock;\n\n\n- (void)removePluginWithPluginType:(DoraemonManagerPluginType)pluginType;\n\n- (void)removePluginWithPluginName:(NSString *)pluginName atModule:(NSString *)moduleName;\n\n- (void)addStartPlugin:(NSString *)pluginName;\n\n- (void)addH5DoorBlock:(DoraemonH5DoorBlock)block;\n\n- (void)addANRBlock:(void(^)(NSDictionary *anrDic))block;\n\n- (void)addPerformanceBlock:(void(^)(NSDictionary *performanceDic))block;\n\n- (void)addWebpHandleBlock:(DoraemonWebpHandleBlock)block;\n\n- (BOOL)isShowDoraemon;\n\n- (void)showDoraemon;\n\n- (void)hiddenDoraemon;\n\n- (void)hiddenHomeWindow;\n\n@property (nonatomic, assign) int64_t bigImageDetectionSize; // 外部设置大图检测的监控数值  比如监控所有图片大于50K的图片 那么这个值就设置为 50 * 1024；\n\n@property (nonatomic, copy) NSString *startClass; //如果你的启动代理不是默认的AppDelegate,需要传入才能获取正确的启动时间\n\n@property (nonatomic, copy) NSArray *vcProfilerBlackList;//使用vcProfiler的使用，兼容一些异常情况，比如issue416\n\n@property (nonatomic, strong) NSMutableDictionary *keyBlockDic;//保存key和block的关系\n\n/// DoKit 支持的旋转方向\n@property (assign, nonatomic) UIInterfaceOrientationMask supportedInterfaceOrientations;\n\n\n- (void)configEntryBtnBlingWithText:(nullable NSString *)text backColor:(nullable UIColor *)backColor;\n@end\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Manager/DoraemonManager.m",
    "content": "//\n//  DoraemonManager.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n#import <UIKit/UIKit.h>\n#import \"DoraemonManager.h\"\n#import \"DoraemonEntryWindow.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonStartPluginProtocol.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonUtil.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonCrashUncaughtExceptionHandler.h\"\n#import \"DoraemonCrashSignalExceptionHandler.h\"\n#import \"DoraemonNSLogManager.h\"\n#import \"DoraemonNSLogViewController.h\"\n#import \"DoraemonNSLogListViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonStatisticsUtil.h\"\n#import \"DoraemonANRManager.h\"\n#import \"DoraemonLargeImageDetectionManager.h\"\n#import \"DoraemonMockManager.h\"\n#import \"DoraemonNetFlowOscillogramWindow.h\"\n#import \"DoraemonNetFlowManager.h\"\n#import \"DoraemonHealthManager.h\"\n\n#if DoraemonWithGPS\n#import \"DoraemonGPSMocker.h\"\n#endif\n\n\n#if DoraemonWithLogger\n#import \"DoraemonCocoaLumberjackLogger.h\"\n#import \"DoraemonCocoaLumberjackViewController.h\"\n#import \"DoraemonCocoaLumberjackListViewController.h\"\n#endif\n\n#if DoraemonWithWeex\n#import \"DoraemonWeexLogDataSource.h\"\n#import \"DoraemonWeexInfoDataManager.h\"\n#endif\n\n\n#define kTitle        @\"title\"\n#define kDesc         @\"desc\"\n#define kIcon         @\"icon\"\n#define kPluginName   @\"pluginName\"\n#define kAtModule     @\"atModule\"\n#define kBuriedPoint  @\"buriedPoint\"\n\n@implementation DoraemonManagerPluginTypeModel\n\n@end\n\ntypedef void (^DoraemonANRBlock)(NSDictionary *);\ntypedef void (^DoraemonPerformanceBlock)(NSDictionary *);\n\n@interface DoraemonManager()\n\n@property (nonatomic, strong) DoraemonEntryWindow *entryWindow;\n\n@property (nonatomic, strong) NSMutableArray *startPlugins;\n\n@property (nonatomic, copy) DoraemonANRBlock anrBlock;\n\n@property (nonatomic, copy) DoraemonPerformanceBlock performanceBlock;\n\n@property (nonatomic, assign) BOOL hasInstall;\n\n// 定制位置\n@property (nonatomic) CGPoint startingPosition;\n\n@end\n\n@implementation DoraemonManager\n\n+ (nonnull DoraemonManager *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonManager *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonManager alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    self = [super init];\n    if (self) {\n        _autoDock = YES;\n        _keyBlockDic = [[NSMutableDictionary alloc] init];\n    }\n    return self;\n}\n\n- (void)install{\n    //启用默认位置\n    CGPoint defaultPosition = DoraemonStartingPosition;\n    CGSize size = [UIScreen mainScreen].bounds.size;\n    if (size.width > size.height) {\n        defaultPosition = DoraemonFullScreenStartingPosition;\n    }\n    [self installWithStartingPosition:defaultPosition];\n}\n\n- (void)installWithPid:(NSString *)pId{\n    self.pId = pId;\n    [self install];\n}\n\n- (void)installWithMockDomain:(NSString *)mockDomain{\n    self.mockDomain = mockDomain;\n    [self install];\n}\n\n- (void)installWithStartingPosition:(CGPoint) position{\n    _startingPosition = position;\n    [self installWithCustomBlock:^{\n        //什么也没发生\n    }];\n}\n\n- (void)installWithCustomBlock:(void(^)(void))customBlock{\n    //保证install只执行一次\n    if (_hasInstall) {\n        return;\n    }\n    _hasInstall = YES;\n    for (int i=0; i<_startPlugins.count; i++) {\n        NSString *pluginName = _startPlugins[i];\n        Class pluginClass = NSClassFromString(pluginName);\n        id<DoraemonStartPluginProtocol> plugin = [[pluginClass alloc] init];\n        if (plugin) {\n            [plugin startPluginDidLoad];\n        }\n    }\n\n    [self initData];\n    customBlock();\n\n    [self initEntry:self.startingPosition];\n    \n    //根据开关判断是否收集Crash日志\n    if ([[DoraemonCacheManager sharedInstance] crashSwitch]) {\n        [DoraemonCrashUncaughtExceptionHandler registerHandler];\n        [DoraemonCrashSignalExceptionHandler registerHandler];\n    }\n    //根据开关判断是否开启流量监控\n    if ([[DoraemonCacheManager sharedInstance] netFlowSwitch]) {\n        [[DoraemonNetFlowManager shareInstance] canInterceptNetFlow:YES];\n        //[[DoraemonNetFlowOscillogramWindow shareInstance] show];\n    }\n\n    //重新启动的时候，把帧率、CPU、内存和流量监控关闭\n    [[DoraemonCacheManager sharedInstance] saveFpsSwitch:NO];\n    [[DoraemonCacheManager sharedInstance] saveCpuSwitch:NO];\n    [[DoraemonCacheManager sharedInstance] saveMemorySwitch:NO];\n\n#if DoraemonWithGPS\n    //开启mockGPS功能\n    if ([[DoraemonCacheManager sharedInstance] mockGPSSwitch]) {\n        CLLocationCoordinate2D coordinate = [[DoraemonCacheManager sharedInstance] mockCoordinate];\n        CLLocation *loc = [[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude];\n        [[DoraemonGPSMocker shareInstance] mockPoint:loc];\n    }\n#endif\n\n    \n    //开启NSLog监控功能\n    if ([[DoraemonCacheManager sharedInstance] nsLogSwitch]) {\n        [[DoraemonNSLogManager sharedInstance] startNSLogMonitor];\n    }\n    \n#if DoraemonWithLogger\n    //开启CocoaLumberjack监控\n    if ([[DoraemonCacheManager sharedInstance] loggerSwitch]) {\n        [[DoraemonCocoaLumberjackLogger sharedInstance] startMonitor];\n    }\n#endif\n    \n    [[DoraemonANRManager sharedInstance] addANRBlock:^(NSDictionary *anrInfo) {\n        if (self.anrBlock) {\n            self.anrBlock(anrInfo);\n        }\n    }];\n    \n    //外部设置大图检测的数值\n    if (_bigImageDetectionSize > 0){\n        [DoraemonLargeImageDetectionManager shareInstance].minimumDetectionSize = _bigImageDetectionSize;\n    }\n    \n    //统计开源项目使用量 不用于任何恶意行为\n    [[DoraemonStatisticsUtil shareInstance] upLoadUserInfo];\n    \n    //拉取最新的mock数据\n    [[DoraemonMockManager sharedInstance] queryMockData:^(int flag) {\n        DoKitLog(@\"mock get data, flag == %i\",flag);\n    }];\n    \n    //Weex工具的初始化\n#if DoraemonWithWeex\n    [DoraemonWeexLogDataSource shareInstance];\n    [DoraemonWeexInfoDataManager shareInstance];\n#endif\n    \n    //开启健康体检\n    if ([[DoraemonCacheManager sharedInstance] healthStart]) {\n        [[DoraemonHealthManager sharedInstance] startHealthCheck];\n    }\n    \n}\n\n\n/**\n 初始化内置工具数据\n */\n- (void)initData{\n    #pragma mark - 平台工具\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonMockPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonHealthPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonFileSyncPlugin];\n    \n    #pragma mark - 常用工具\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonAppSettingPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonAppInfoPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonSandboxPlugin];\n#if DoraemonWithGPS\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonGPSPlugin];\n#endif\n\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonH5Plugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonDeleteLocalDataPlugin];\n    \n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonNSLogPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonNSUserDefaultsPlugin];\n#if DoraemonWithLogger\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonCocoaLumberjackPlugin];\n#endif\n    \n#if DoraemonWithDatabase\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonDatabasePlugin];\n#endif\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonJavaScriptPlugin];\n    \n    #pragma mark - 性能检测\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonFPSPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonCPUPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonMemoryPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonNetFlowPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonCrashPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonSubThreadUICheckPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonANRPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonLargeImageFilter];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonWeakNetworkPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonStartTimePlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonUIProfilePlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonTimeProfilePlugin];\n#if DoraemonWithLoad\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonMethodUseTimePlugin];\n#endif\n#if DoraemonWithMLeaksFinder\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonMemoryLeakPlugin];\n#endif\n    \n    #pragma mark - 视觉工具\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonColorPickPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonViewCheckPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonViewAlignPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonViewMetricsPlugin];\n    [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonHierarchyPlugin];\n    \n    #pragma mark - Weex专项工具\n    #if DoraemonWithWeex\n        [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonWeexLogPlugin];\n        [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonWeexStoragePlugin];\n        [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonWeexInfoPlugin];\n        [self addPluginWithPluginType:DoraemonManagerPluginType_DoraemonWeexDevToolPlugin];\n    #endif\n}\n\n/**\n 初始化工具入口\n */\n- (void)initEntry:(CGPoint) startingPosition{\n    _entryWindow = [[DoraemonEntryWindow alloc] initWithStartPoint:startingPosition];\n    [_entryWindow show];\n    if(_autoDock){\n        [_entryWindow setAutoDock:YES];\n    }\n}\n\n- (void)addStartPlugin:(NSString *)pluginName{\n    if (!_startPlugins) {\n        _startPlugins = [[NSMutableArray alloc] init];\n    }\n    [_startPlugins addObject:pluginName];\n}\n\n- (void)addPluginWithPluginType:(DoraemonManagerPluginType)pluginType\n{\n    DoraemonManagerPluginTypeModel *model = [self getDefaultPluginDataWithPluginType:pluginType];\n    [self addPluginWithTitle:model.title icon:model.icon desc:model.desc pluginName:model.pluginName atModule:model.atModule buriedPoint:model.buriedPoint];\n}\n\n// out 1\n- (void)addPluginWithTitle:(NSString *)title icon:(NSString *)iconName desc:(NSString *)desc pluginName:(NSString *)entryName atModule:(NSString *)moduleName{\n    [self addPluginWithTitle:title icon:iconName desc:desc pluginName:entryName atModule:moduleName buriedPoint:@\"dokit_sdk_business_ck\"];\n}\n\n- (void)addPluginWithTitle:(NSString *)title icon:(NSString *)iconName desc:(NSString *)desc pluginName:(NSString *)entryName atModule:(NSString *)moduleName buriedPoint:(NSString *)buriedPoint{\n    \n    NSMutableDictionary *pluginDic = [self foundGroupWithModule:moduleName];\n    pluginDic[@\"key\"] = [NSString stringWithFormat:@\"%@-%@-%@-%@\",moduleName,title,iconName,desc];\n    pluginDic[@\"name\"] = title;\n    pluginDic[@\"icon\"] = iconName;\n    pluginDic[@\"desc\"] = desc;\n    pluginDic[@\"pluginName\"] = entryName;\n    pluginDic[@\"buriedPoint\"] = buriedPoint;\n    pluginDic[@\"show\"] = @1;\n}\n\n// out 2\n- (void)addPluginWithTitle:(NSString *)title icon:(NSString *)iconName desc:(NSString *)desc pluginName:(NSString *)entryName atModule:(NSString *)moduleName handle:(void (^)(NSDictionary *))handleBlock\n{\n    NSMutableDictionary *pluginDic = [self foundGroupWithModule:moduleName];\n    pluginDic[@\"key\"] = [NSString stringWithFormat:@\"%@-%@-%@-%@\",moduleName,title,iconName,desc];\n    pluginDic[@\"name\"] = title;\n    pluginDic[@\"icon\"] = iconName;\n    pluginDic[@\"desc\"] = desc;\n    pluginDic[@\"pluginName\"] = entryName;\n    [_keyBlockDic setValue:[handleBlock copy] forKey:pluginDic[@\"key\"]];\n    pluginDic[@\"buriedPoint\"] = @\"dokit_sdk_business_ck\";\n    pluginDic[@\"show\"] = @1;\n\n}\n\n- (void)addPluginWithTitle:(NSString *)title image:(UIImage *)image desc:(NSString *)desc pluginName:(NSString *)entryName atModule:(NSString *)moduleName handle:(void (^)(NSDictionary * _Nonnull))handleBlock {\n    NSMutableDictionary *pluginDic = [self foundGroupWithModule:moduleName];\n    pluginDic[@\"key\"] = [NSString stringWithFormat:@\"%@-%@-%@\",moduleName,title,desc];\n    pluginDic[@\"name\"] = title;\n    pluginDic[@\"image\"] = image;\n    pluginDic[@\"desc\"] = desc;\n    pluginDic[@\"pluginName\"] = entryName;\n    if (handleBlock) {\n        [_keyBlockDic setValue:[handleBlock copy] forKey:pluginDic[@\"key\"]];\n    }\n    pluginDic[@\"buriedPoint\"] = @\"dokit_sdk_business_ck\";\n    pluginDic[@\"show\"] = @1;\n}\n\n- (NSMutableDictionary *)foundGroupWithModule:(NSString *)module\n{\n    NSMutableDictionary *pluginDic = [NSMutableDictionary dictionary];\n    pluginDic[@\"moduleName\"] = module;\n    __block BOOL hasModule = NO;\n    [self.dataArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {\n        NSDictionary *moduleDic = obj;\n        NSString *moduleName = moduleDic[@\"moduleName\"];\n        if ([moduleName isEqualToString:module]) {\n            hasModule = YES;\n            NSMutableArray *pluginArray = moduleDic[@\"pluginArray\"];\n            if (pluginArray) {\n                [pluginArray addObject:pluginDic];\n            }\n            [moduleDic setValue:pluginArray forKey:@\"pluginArray\"];\n            *stop = YES;\n        }\n    }];\n    if (!hasModule) {\n        NSMutableArray *pluginArray = [[NSMutableArray alloc] initWithObjects:pluginDic, nil];\n        [self registerPluginArray:pluginArray withModule:module];\n    }\n    return pluginDic;\n}\n- (void)removePluginWithPluginType:(DoraemonManagerPluginType)pluginType\n{\n    DoraemonManagerPluginTypeModel *model = [self getDefaultPluginDataWithPluginType:pluginType];\n    [self removePluginWithPluginName:model.pluginName atModule:model.atModule];\n}\n\n- (void)removePluginWithPluginName:(NSString *)pluginName atModule:(NSString *)moduleName{\n    [self unregisterPlugin:pluginName withModule:moduleName];\n}\n\n- (void)registerPluginArray:(NSMutableArray*)array withModule:(NSString*)moduleName{\n    if (!_dataArray){\n        _dataArray = [[NSMutableArray alloc]init];\n    }\n    NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];\n    [dic setValue:moduleName forKey:@\"moduleName\"];\n    [dic setValue:array forKey:@\"pluginArray\"];\n    [_dataArray addObject:dic];\n}\n\n- (void)unregisterPlugin:(NSString*)pluginName withModule:(NSString*)moduleName{\n    if (!_dataArray){\n        return;\n    }\n    id object;\n    for (object in _dataArray) {\n        NSString *tempModuleName = [((NSMutableDictionary *)object) valueForKey:@\"moduleName\"];\n        if ([tempModuleName isEqualToString:moduleName]) {\n            NSMutableArray *tempPluginArray = [((NSMutableDictionary *)object) valueForKey:@\"pluginArray\"];\n            id pluginObject;\n            for (pluginObject in tempPluginArray) {\n                NSString *tempPluginName = [((NSMutableDictionary *)pluginObject) valueForKey:@\"pluginName\"];\n                if ([tempPluginName isEqualToString:pluginName]) {\n                    [tempPluginArray removeObject:pluginObject];\n                    return;\n                }\n            }\n        }\n    }\n}\n\n- (BOOL)isShowDoraemon{\n    if (!_entryWindow) {\n        return NO;\n    }\n    return !_entryWindow.hidden;\n}\n\n- (void)showDoraemon{\n    if (_entryWindow.hidden) {\n        _entryWindow.hidden = NO;\n    }\n}\n\n- (void)hiddenDoraemon{\n    if (!_entryWindow.hidden) {\n        _entryWindow.hidden = YES;\n     }\n}\n\n\n- (void)addH5DoorBlock:(void(^)(NSString *h5Url))block{\n    self.h5DoorBlock = block;\n}\n\n- (void)addANRBlock:(void(^)(NSDictionary *anrDic))block{\n    self.anrBlock = block;\n}\n\n- (void)addPerformanceBlock:(void(^)(NSDictionary *performanceDic))block{\n    self.performanceBlock = block;\n}\n\n- (void)addWebpHandleBlock:(UIImage *(^)(NSString *filePath))block{\n    self.webpHandleBlock = block;\n}\n\n- (void)hiddenHomeWindow{\n    [[DoraemonHomeWindow shareInstance] hide];\n}\n\n#pragma mark - default data\n- (DoraemonManagerPluginTypeModel *)getDefaultPluginDataWithPluginType:(DoraemonManagerPluginType)pluginType\n{\n    NSArray *dataArray = @{\n                           @(DoraemonManagerPluginType_DoraemonWeexLogPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"日志\")},\n                                   @{kDesc:@\"Weex log\"},\n                                   @{kIcon:@\"doraemon_log\"},\n                                   @{kPluginName:@\"DoraemonWeexLogPlugin\"},\n                                   @{kAtModule:@\"Weex\"},\n                                   @{kBuriedPoint:@\"dokit_sdk_weex_ck_log\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonWeexStoragePlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"缓存\")},\n                                   @{kDesc:@\"weex storage\"},\n                                   @{kIcon:@\"doraemon_file\"},\n                                   @{kPluginName:@\"DoraemonWeexStoragePlugin\"},\n                                   @{kAtModule:@\"Weex\"},\n                                   @{kBuriedPoint:@\"dokit_sdk_weex_ck_storage\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonWeexInfoPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"信息\")},\n                                   @{kDesc:@\"weex info\"},\n                                   @{kIcon:@\"doraemon_app_info\"},\n                                   @{kPluginName:@\"DoraemonWeexInfoPlugin\"},\n                                   @{kAtModule:@\"Weex\"},\n                                   @{kBuriedPoint:@\"dokit_sdk_weex_ck_vessel\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonWeexDevToolPlugin) : @[\n                                   @{kTitle:@\"DevTool\"},\n                                   @{kDesc:@\"weex devtool\"},\n                                   @{kIcon:@\"doraemon_default\"},\n                                   @{kPluginName:@\"DoraemonWeexDevTooloPlugin\"},\n                                   @{kAtModule:@\"Weex\"},\n                                   @{kBuriedPoint:@\"dokit_sdk_weex_ck_devtool\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonAppSettingPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"应用设置\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"应用设置\")},\n                                   @{kIcon:@\"doraemon_setting\"},\n                                   @{kPluginName:@\"DoraemonAppSettingPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"常用工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_comm_ck_setting\"}\n                                    ],\n                           @(DoraemonManagerPluginType_DoraemonAppInfoPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"App信息\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"App信息\")},\n                                   @{kIcon:@\"doraemon_app_info\"},\n                                   @{kPluginName:@\"DoraemonAppInfoPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"常用工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_comm_ck_appinfo\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonSandboxPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"沙盒浏览器\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"沙盒浏览器\")},\n                                   @{kIcon:@\"doraemon_file\"},\n                                   @{kPluginName:@\"DoraemonSandboxPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"常用工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_comm_ck_sandbox\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonGPSPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"Mock GPS\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"Mock GPS\")},\n                                   @{kIcon:@\"doraemon_mock_gps\"},\n                                   @{kPluginName:@\"DoraemonGPSPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"常用工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_comm_ck_gps\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonH5Plugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"H5任意门\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"H5任意门\")},\n                                   @{kIcon:@\"doraemon_h5\"},\n                                   @{kPluginName:@\"DoraemonH5Plugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"常用工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_comm_ck_h5\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonDeleteLocalDataPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"清理缓存\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"清理缓存\")},\n                                   @{kIcon:@\"doraemon_qingchu\"},\n                                   @{kPluginName:@\"DoraemonDeleteLocalDataPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"常用工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_comm_ck_cache\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonNSLogPlugin) : @[\n                                   @{kTitle:@\"NSLog\"},\n                                   @{kDesc:@\"NSLog\"},\n                                   @{kIcon:@\"doraemon_nslog\"},\n                                   @{kPluginName:@\"DoraemonNSLogPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"常用工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_comm_ck_log\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonCocoaLumberjackPlugin) : @[\n                                   @{kTitle:@\"Lumberjack\"},\n                                   @{kDesc:DoraemonLocalizedString(@\"Lumberjack\")},\n                                   @{kIcon:@\"doraemon_log\"},\n                                   @{kPluginName:@\"DoraemonCocoaLumberjackPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"常用工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_comm_ck_lumberjack\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonDatabasePlugin) : @[\n                                   @{kTitle:@\"DBView\"},\n                                   @{kDesc:DoraemonLocalizedString(@\"数据库预览\")},\n                                   @{kIcon:@\"doraemon_database\"},\n                                   @{kPluginName:@\"DoraemonDatabasePlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"常用工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_comm_ck_dbview\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonNSUserDefaultsPlugin) : @[\n                                   @{kTitle:@\"UserDefaults\"},\n                                   @{kDesc:@\"UserDefaults\"},\n                                   @{kIcon:@\"doraemon_database\"},\n                                   @{kPluginName:@\"DoraemonNSUserDefaultsPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"常用工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_comm_ck_userdefault\"}\n                           ],\n                           @(DoraemonManagerPluginType_DoraemonJavaScriptPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"JS脚本\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"JS脚本\")},\n                                   @{kIcon:@\"doraemon_js\"},\n                                   @{kPluginName:@\"DoraemonJavaScriptPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"常用工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_comm_ck_js\"}\n                           ],\n                           \n                           // 性能检测\n                           @(DoraemonManagerPluginType_DoraemonFPSPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"帧率\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"帧率\")},\n                                   @{kIcon:@\"doraemon_fps\"},\n                                   @{kPluginName:@\"DoraemonFPSPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"性能检测\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_performance_ck_fps\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonCPUPlugin) : @[\n                                   @{kTitle:@\"CPU\"},\n                                   @{kDesc:DoraemonLocalizedString(@\"CPU\")},\n                                   @{kIcon:@\"doraemon_cpu\"},\n                                   @{kPluginName:@\"DoraemonCPUPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"性能检测\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_performance_ck_cpu\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonMemoryPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"内存\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"内存\")},\n                                   @{kIcon:@\"doraemon_memory\"},\n                                   @{kPluginName:@\"DoraemonMemoryPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"性能检测\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_performance_ck_arm\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonNetFlowPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"网络\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"网络监控\")},\n                                   @{kIcon:@\"doraemon_net\"},\n                                   @{kPluginName:@\"DoraemonNetFlowPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"性能检测\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_performance_ck_network\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonCrashPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"Crash\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"Crash\")},\n                                   @{kIcon:@\"doraemon_crash\"},\n                                   @{kPluginName:@\"DoraemonCrashPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"性能检测\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_comm_ck_crash\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonSubThreadUICheckPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"子线程UI\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"子线程UI\")},\n                                   @{kIcon:@\"doraemon_ui\"},\n                                   @{kPluginName:@\"DoraemonSubThreadUICheckPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"性能检测\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_comm_ck_child_thread\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonANRPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"卡顿\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"卡顿\")},\n                                   @{kIcon:@\"doraemon_kadun\"},\n                                   @{kPluginName:@\"DoraemonANRPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"性能检测\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_performance_ck_block\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonMethodUseTimePlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"Load耗时\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"Load耗时\")},\n                                   @{kIcon:@\"doraemon_method_use_time\"},\n                                   @{kPluginName:@\"DoraemonMethodUseTimePlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"性能检测\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_performance_ck_load\"}\n                                   ],\n                           \n                           @(DoraemonManagerPluginType_DoraemonLargeImageFilter) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"大图检测\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"大图检测\")},\n                                   @{kIcon:@\"doraemon_net\"},\n                                   @{kPluginName:@\"DoraemonLargeImagePlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"性能检测\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_performance_ck_img\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonStartTimePlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"启动耗时\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"启动耗时\")},\n                                   @{kIcon:@\"doraemon_app_start_time\"},\n                                   @{kPluginName:@\"DoraemonStartTimePlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"性能检测\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_performance_ck_appstart_coast\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonMemoryLeakPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"内存泄漏\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"内存泄漏统计\")},\n                                   @{kIcon:@\"doraemon_memory_leak\"},\n                                   @{kPluginName:@\"DoraemonMLeaksFinderPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"性能检测\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_performance_ck_leak\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonUIProfilePlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"UI层级\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"UI层级s\")},\n                                   @{kIcon:@\"doraemon_view_level\"},\n                                   @{kPluginName:@\"DoraemonUIProfilePlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"性能检测\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_ui_ck_hierarchy\"}\n                           ],\n                           @(DoraemonManagerPluginType_DoraemonTimeProfilePlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"函数耗时\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"函数耗时统计\")},\n                                   @{kIcon:@\"doraemon_time_profiler\"},\n                                   @{kPluginName:@\"DoraemonTimeProfilerPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"性能检测\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_performance_ck_method_coast\"}\n                           ],\n                           @(DoraemonManagerPluginType_DoraemonWeakNetworkPlugin) : @[\n                                     @{kTitle:DoraemonLocalizedString(@\"模拟弱网\")},\n                                     @{kDesc:DoraemonLocalizedString(@\"模拟弱网测试\")},\n                                     @{kIcon:@\"doraemon_weaknet\"},\n                                     @{kPluginName:@\"DoraemonWeakNetworkPlugin\"},\n                                     @{kAtModule:DoraemonLocalizedString(@\"性能检测\")},\n                                     @{kBuriedPoint:@\"dokit_sdk_comm_ck_weaknetwork\"}\n                             ],\n                           // 视觉工具\n                           @(DoraemonManagerPluginType_DoraemonColorPickPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"取色器\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"取色器\")},\n                                   @{kIcon:@\"doraemon_straw\"},\n                                   @{kPluginName:@\"DoraemonColorPickPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"视觉工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_ui_ck_color_pick\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonViewCheckPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"组件检查\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"组件检查\")},\n                                   @{kIcon:@\"doraemon_view_check\"},\n                                   @{kPluginName:@\"DoraemonViewCheckPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"视觉工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_ui_ck_widget\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonViewAlignPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"对齐标尺\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"对齐标尺\")},\n                                   @{kIcon:@\"doraemon_align\"},\n                                   @{kPluginName:@\"DoraemonViewAlignPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"视觉工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_ui_ck_aligin_scaleplate\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonViewMetricsPlugin) : @[\n                                   @{kTitle:DoraemonLocalizedString(@\"布局边框\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"布局边框\")},\n                                   @{kIcon:@\"doraemon_viewmetrics\"},\n                                   @{kPluginName:@\"DoraemonViewMetricsPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"视觉工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_ui_ck_border\"}\n                                   ],\n                          @(DoraemonManagerPluginType_DoraemonHierarchyPlugin) : @[\n                                           @{kTitle:DoraemonLocalizedString(@\"UI结构\")},\n                                           @{kDesc:DoraemonLocalizedString(@\"显示UI结构\")},\n                                           @{kIcon:@\"doraemon_view_level\"},\n                                           @{kPluginName:@\"DoraemonHierarchyPlugin\"},\n                                           @{kAtModule:DoraemonLocalizedString(@\"视觉工具\")},\n                                           @{kBuriedPoint:@\"dokit_sdk_ui_ck_widget_3d\"}\n                                   ],\n                           // 平台工具\n                           @(DoraemonManagerPluginType_DoraemonMockPlugin) : @[\n                                @{kTitle:DoraemonLocalizedString(@\"Mock数据\")},\n                                   @{kDesc:DoraemonLocalizedString(@\"Mock数据\")},\n                                   @{kIcon:@\"doraemon_mock\"},\n                                   @{kPluginName:@\"DoraemonMockPlugin\"},\n                                   @{kAtModule:DoraemonLocalizedString(@\"平台工具\")},\n                                   @{kBuriedPoint:@\"dokit_sdk_platform_ck_mock\"}\n                                   ],\n                           @(DoraemonManagerPluginType_DoraemonHealthPlugin) : @[\n                               @{kTitle:DoraemonLocalizedString(@\"健康体检\")},\n                                  @{kDesc:DoraemonLocalizedString(@\"健康体检中心\")},\n                                  @{kIcon:@\"doraemon_health\"},\n                                  @{kPluginName:@\"DoraemonHealthPlugin\"},\n                                  @{kAtModule:DoraemonLocalizedString(@\"平台工具\")},\n                                  @{kBuriedPoint:@\"dokit_sdk_platform_ck_health\"}\n                                  ],\n                           @(DoraemonManagerPluginType_DoraemonFileSyncPlugin) : @[\n                                @{kTitle:DoraemonLocalizedString(@\"文件同步\")},\n                                    @{kDesc:DoraemonLocalizedString(@\"文件同步\")},\n                                    @{kIcon:@\"doraemon_file_sync\"},\n                                    @{kPluginName:@\"DoraemonFileSyncPlugin\"},\n                                    @{kAtModule:DoraemonLocalizedString(@\"平台工具\")},\n                                    @{kBuriedPoint:@\"dokit_sdk_platform_ck_filesync\"}\n                                    ]\n                           }[@(pluginType)];\n    \n    DoraemonManagerPluginTypeModel *model = [DoraemonManagerPluginTypeModel new];\n    model.title = dataArray[0][kTitle];\n    model.desc = dataArray[1][kDesc];\n    model.icon = dataArray[2][kIcon];\n    model.pluginName = dataArray[3][kPluginName];\n    model.atModule = dataArray[4][kAtModule];\n    model.buriedPoint = dataArray[5][kBuriedPoint];\n    \n    return model;\n}\n\n- (void)setStartClass:(NSString *)startClass {\n    [[DoraemonCacheManager sharedInstance] saveStartClass:startClass];\n}\n\n- (NSString *)startClass{\n    return [[DoraemonCacheManager sharedInstance] startClass];\n}\n\n- (void)configEntryBtnBlingWithText:(NSString *)text backColor:(UIColor *)backColor {\n    [self.entryWindow configEntryBtnBlingWithText:text backColor:backColor];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Network/Interceptor/DoraemonNSURLProtocol.h",
    "content": "//\n//  DoraemonNSURLProtocol.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/11.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface DoraemonNSURLProtocol : NSURLProtocol\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Network/Interceptor/DoraemonNSURLProtocol.m",
    "content": "//\n//  DoraemonNSURLProtocol.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/11.\n//\n\n#import \"DoraemonNSURLProtocol.h\"\n#import \"DoraemonNetFlowHttpModel.h\"\n#import \"DoraemonNetFlowDataSource.h\"\n#import \"DoraemonNetFlowManager.h\"\n#import \"DoraemonURLSessionDemux.h\"\n#import \"DoraemonNetworkInterceptor.h\"\n#import \"DoraemonManager.h\"\n#import \"DoraemonMockManager.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonUrlUtil.h\"\n#import \"UIViewController+Doraemon.h\"\n\nstatic NSString * const kDoraemonProtocolKey = @\"doraemon_protocol_key\";\n\n@interface DoraemonNSURLProtocol()<NSURLSessionDataDelegate>\n\n@property (nonatomic, strong) NSURLSession *urlSession;\n@property (nonatomic, assign) NSTimeInterval startTime;\n@property (nonatomic, strong) NSURLResponse *response;\n@property (nonatomic, strong) NSMutableData *data;\n@property (nonatomic, strong) NSError *error;\n\n@property (atomic, strong, readwrite) NSThread *clientThread;\n@property (atomic, copy,   readwrite) NSArray *modes;\n@property (atomic, strong, readwrite) NSURLSessionDataTask *task;\n\n@end\n\n@implementation DoraemonNSURLProtocol\n\n+ (DoraemonURLSessionDemux *)sharedDemux{\n    static dispatch_once_t      sOnceToken;\n    static DoraemonURLSessionDemux *sDemux;\n    dispatch_once(&sOnceToken, ^{\n        NSURLSessionConfiguration *config;\n        config = [NSURLSessionConfiguration defaultSessionConfiguration];\n        sDemux = [[DoraemonURLSessionDemux alloc] initWithConfiguration:config];\n    });\n    return sDemux;\n}\n\n+ (BOOL)canInitWithTask:(NSURLSessionTask *)task {\n    NSURLRequest *request = task.currentRequest;\n    return request == nil ? NO : [self canInitWithRequest:request];\n}\n\n+ (BOOL)canInitWithRequest:(NSURLRequest *)request{\n    if ([NSURLProtocol propertyForKey:kDoraemonProtocolKey inRequest:request]) {\n        return NO;\n    }\n    if (![DoraemonNetworkInterceptor shareInstance].shouldIntercept) {\n        return NO;\n    }\n    if (![request.URL.scheme isEqualToString:@\"http\"] &&\n        ![request.URL.scheme isEqualToString:@\"https\"]) {\n        return NO;\n    }\n    //文件类型不作处理\n    NSString *contentType = [request valueForHTTPHeaderField:@\"Content-Type\"];\n    if (contentType && [contentType containsString:@\"multipart/form-data\"]) {\n        return NO;\n    }\n    \n//    if ([self ignoreRequest:request]) {\n//        return NO;\n//    }\n    \n    return YES;\n}\n\n+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request{\n    NSMutableURLRequest *mutableReqeust = [request mutableCopy];\n    [NSURLProtocol setProperty:@YES forKey:kDoraemonProtocolKey inRequest:mutableReqeust];\n    if ([[DoraemonMockManager sharedInstance] needMock:request]) {\n        NSString *mockDomain = [DoraemonManager shareInstance].mockDomain ? [DoraemonManager shareInstance].mockDomain : @\"https://mock.dokit.cn/\";\n        NSString *mockSceneUrl = [mockDomain stringByAppendingString:@\"api/app/scene/%@\"];\n        NSString *sceneId = [[DoraemonMockManager sharedInstance] getSceneId:request];\n        NSString *urlString = [NSString stringWithFormat:mockSceneUrl, sceneId];\n        DoKitLog(@\"MOCK URL == %@\",urlString);\n        mutableReqeust = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];\n        dispatch_async(dispatch_get_main_queue(), ^{\n            [DoraemonToastUtil showToastBlack:[NSString stringWithFormat:@\"mock url = %@\",request.URL.absoluteURL] inView:[UIViewController rootViewControllerForKeyWindow].view];\n        });\n\n    }\n    return [mutableReqeust copy];\n}\n\n- (void)handleFromSelect{\n    if(DoraemonWeakNetwork_Delay == [[DoraemonNetworkInterceptor shareInstance].weakDelegate weakNetSelecte]){\n        DoKitLog(@\"yd Delay Net\");//此处有dispatch_get_main_queue，无法使用switch\n        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)([[DoraemonNetworkInterceptor shareInstance].weakDelegate delayTime] * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{\n            [self.task resume];\n        });\n    }else if(DoraemonWeakNetwork_WeakSpeed == [[DoraemonNetworkInterceptor shareInstance].weakDelegate weakNetSelecte]){\n        DoKitLog(@\"yd WeakUpFlow Net\");\n        [[DoraemonNetFlowManager shareInstance] httpBodyFromRequest:self.request bodyCallBack:^(NSData *body) {\n            [[DoraemonNetworkInterceptor shareInstance].weakDelegate handleWeak:body isDown:NO];\n            [self.task resume];\n        }];\n    }else{\n        [self.task resume];\n    }\n}\n\n- (BOOL)needLoading{\n    BOOL result = YES;\n    if ([DoraemonNetworkInterceptor shareInstance].weakDelegate){\n        if(DoraemonWeakNetwork_OutTime == [[DoraemonNetworkInterceptor shareInstance].weakDelegate weakNetSelecte]){\n            DoKitLog(@\"yd Outtime Net\");\n            [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:NSCocoaErrorDomain code:NSURLErrorTimedOut userInfo:nil]];\n            result = NO;\n        }else if(DoraemonWeakNetwork_Break == [[DoraemonNetworkInterceptor shareInstance].weakDelegate weakNetSelecte]){\n            DoKitLog(@\"yd Break Net\");\n            [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:NSCocoaErrorDomain code:NSURLErrorNotConnectedToInternet userInfo:nil]];\n            result = NO;\n        }\n    }\n    return result;\n}\n\n- (void)startLoading{\n    NSMutableURLRequest *   recursiveRequest;\n    NSMutableArray *        calculatedModes;\n    NSString *              currentMode;\n    \n    assert(self.clientThread == nil);\n    assert(self.task == nil);\n    assert(self.modes == nil);\n    \n    calculatedModes = [NSMutableArray array];\n    [calculatedModes addObject:NSDefaultRunLoopMode];\n    currentMode = [[NSRunLoop currentRunLoop] currentMode];\n    if ( (currentMode != nil) && ! [currentMode isEqual:NSDefaultRunLoopMode] ) {\n        [calculatedModes addObject:currentMode];\n    }\n    self.modes = calculatedModes;\n    assert([self.modes count] > 0);\n    \n    recursiveRequest = [[self request] mutableCopy];\n    assert(recursiveRequest != nil);\n    \n    self.clientThread = [NSThread currentThread];\n    self.data = [NSMutableData data];\n    self.startTime = [[NSDate date] timeIntervalSince1970];\n    self.task = [[[self class] sharedDemux] dataTaskWithRequest:recursiveRequest delegate:self modes:self.modes];\n    assert(self.task != nil);\n    if([DoraemonNetworkInterceptor shareInstance].weakDelegate){\n        [self handleFromSelect];\n    }else{\n        [self.task resume];\n    }\n}\n\n- (void)stopLoading{\n    assert(self.clientThread != nil);\n    assert([NSThread currentThread] == self.clientThread);\n    [[DoraemonNetworkInterceptor shareInstance] handleResultWithData: self.data\n                                                            response: self.response\n                                                             request:self.request\n                                                               error:self.error\n                                                           startTime:self.startTime];\n    \n    if (self.task != nil) {\n        [self.task cancel];\n        self.task = nil;\n    }\n}\n\n#pragma mark - NSURLSessionDelegate\n- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {\n    assert([NSThread currentThread] == self.clientThread);\n    self.response = response;\n    if([self needLoading]){\n        [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];\n        completionHandler(NSURLSessionResponseAllow);\n    }\n}\n\n- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {\n    assert([NSThread currentThread] == self.clientThread);\n    if ([DoraemonNetworkInterceptor shareInstance].weakDelegate) {\n        if(DoraemonWeakNetwork_WeakSpeed == [[DoraemonNetworkInterceptor shareInstance].weakDelegate weakNetSelecte]){\n            DoKitLog(@\"yd WeakDownFlow Net\");\n            [[DoraemonNetworkInterceptor shareInstance].weakDelegate handleWeak:data isDown:YES];\n        }\n    }\n    [self.data appendData:data];\n    [self.client URLProtocol:self didLoadData:data];\n}\n\n- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {\n    assert([NSThread currentThread] == self.clientThread);\n    if (error) {\n        self.error = error;\n        [self.client URLProtocol:self didFailWithError:error];\n    }else if([self needLoading]){\n        [self.client URLProtocolDidFinishLoading:self];\n    }\n}\n\n//- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {\n//    assert([NSThread currentThread] == self.clientThread);\n//    //判断服务器返回的证书类型, 是否是服务器信任\n//    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {\n//        //强制信任\n//        NSURLCredential *card = [[NSURLCredential alloc]initWithTrust:challenge.protectionSpace.serverTrust];\n//        completionHandler(NSURLSessionAuthChallengeUseCredential, card);\n//    }\n//}\n\n// 去掉一些我们不关心的链接, 与UIWebView的兼容还是要好好考略一下\n+ (BOOL)ignoreRequest:(NSURLRequest *)request{\n    NSString *pathExtension = [request.URL.absoluteString pathExtension];\n    //NSArray *blackList = @[@\"\",@\"\",@\"\"];\n    if (pathExtension.length > 0) {\n        return YES;\n    }\n    return NO;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Network/Interceptor/DoraemonNetworkInterceptor.h",
    "content": "//\n//  DoraemonNetworkInterceptor.h\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/5/15.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@protocol DoraemonNetworkInterceptorDelegate <NSObject>\n- (BOOL)shouldIntercept;\n\n- (void)doraemonNetworkInterceptorDidReceiveData: (NSData *)data\n                                              response: (NSURLResponse *)response\n                                              request: (NSURLRequest *)request\n                                              error: (NSError *)error\n                                              startTime: (NSTimeInterval)startTime;\n@end\n\n@protocol DoraemonNetworkWeakDelegate <NSObject>\n\ntypedef NS_ENUM(NSUInteger, DoraemonWeakNetType) {\n    #pragma mark - 弱网选项对应\n    // 断网\n    DoraemonWeakNetwork_Break,\n    // 超时\n    DoraemonWeakNetwork_OutTime,\n    // 限网\n    DoraemonWeakNetwork_WeakSpeed,\n    //延时\n    DoraemonWeakNetwork_Delay\n};\n\n- (NSInteger)weakNetSelecte;\n\n- (NSUInteger)delayTime;\n\n- (void)handleWeak:(NSData *)data isDown:(BOOL)is;\n//- (NSData *)doraemonNSURLProtocolWeak:(NSData *)data count:(NSInteger)times;\n\n@end\n\n@interface DoraemonNetworkInterceptor : NSObject\n@property (nonatomic, assign) BOOL shouldIntercept;\n\n@property (nonatomic, weak) id<DoraemonNetworkWeakDelegate> weakDelegate;\n\n+ (instancetype)shareInstance;\n\n- (void)addDelegate:(id<DoraemonNetworkInterceptorDelegate>) delegate;\n- (void)removeDelegate:(id<DoraemonNetworkInterceptorDelegate>)delegate;\n- (void)updateInterceptStatusForSessionConfiguration: (NSURLSessionConfiguration *)sessionConfiguration;\n- (void)handleResultWithData: (NSData *)data\n                    response: (NSURLResponse *)response\n                     request: (NSURLRequest *)request\n                       error: (NSError *)error\n                   startTime: (NSTimeInterval)startTime;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Network/Interceptor/DoraemonNetworkInterceptor.m",
    "content": "//\n//  DoraemonNetworkInterceptor.m\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/5/15.\n//\n\n#import \"DoraemonNetworkInterceptor.h\"\n#import \"DoraemonNSURLProtocol.h\"\n#import \"DoraemonNetFlowManager.h\"\n#import \"DoraemonLargeImageDetectionManager.h\"\n#import \"DoraemonNetFlowDataSource.h\"\n#import \"DoraemonNetFlowHttpModel.h\"\n#import \"DoraemonResponseImageModel.h\"\n#import \"DoraemonDefine.h\"\n#import<CommonCrypto/CommonDigest.h>\nstatic DoraemonNetworkInterceptor *instance = nil;\n\n@interface DoraemonNetworkInterceptor()\n\n@property (nonatomic, strong) NSHashTable *delegates;\n@end\n\n@implementation DoraemonNetworkInterceptor\n\n- (NSHashTable *)delegates {\n    if (_delegates == nil) {\n        self.delegates = [NSHashTable weakObjectsHashTable];\n    }\n    return _delegates;\n}\n\n- (void)addDelegate:(id<DoraemonNetworkInterceptorDelegate>) delegate {\n    [self.delegates addObject:delegate];\n    [self updateURLProtocolInterceptStatus];\n}\n\n- (void)removeDelegate:(id<DoraemonNetworkInterceptorDelegate>)delegate {\n    [self.delegates removeObject:delegate];\n    [self updateURLProtocolInterceptStatus];\n}\n    \n\n+ (instancetype)shareInstance {\n    static dispatch_once_t once;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonNetworkInterceptor alloc] init];\n    });\n    return instance;\n}\n\n- (BOOL)shouldIntercept {\n    // 当有对象监听 拦截后的网络请求时，才需要拦截\n    BOOL shouldIntercept = NO;\n    \n    for (id<DoraemonNetworkInterceptorDelegate> delegate in self.delegates) {\n        if (delegate.shouldIntercept) {\n            shouldIntercept = YES;\n//            if (shouldIntercept) {\n//                DoKitLog(@\"yixiang shouldIntercept from %@\",[delegate class]);\n//            }\n        }\n    }\n    return shouldIntercept;\n}\n\n\n- (void)updateURLProtocolInterceptStatus {\n    if (self.shouldIntercept) {\n        [NSURLProtocol registerClass:[DoraemonNSURLProtocol class]];\n    }else{\n        [NSURLProtocol unregisterClass:[DoraemonNSURLProtocol class]];\n    }\n}\n\n- (void)updateInterceptStatusForSessionConfiguration: (NSURLSessionConfiguration *)sessionConfiguration {\n    //BOOL shouldIntercept = [self shouldIntercept];\n    if ([sessionConfiguration respondsToSelector:@selector(protocolClasses)]\n        && [sessionConfiguration respondsToSelector:@selector(setProtocolClasses:)]) {\n        NSMutableArray * urlProtocolClasses = [NSMutableArray arrayWithArray: sessionConfiguration.protocolClasses];\n        Class protoCls = DoraemonNSURLProtocol.class;\n        if ( ![urlProtocolClasses containsObject: protoCls]) {\n            [urlProtocolClasses insertObject: protoCls atIndex: 0];\n        } else if ([urlProtocolClasses containsObject: protoCls]) {\n            [urlProtocolClasses removeObject: protoCls];\n        }\n        sessionConfiguration.protocolClasses = urlProtocolClasses;\n    }\n}\n\n- (void)handleResultWithData: (NSData *)data\n                    response: (NSURLResponse *)response\n                     request: (NSURLRequest *)request\n                       error: (NSError *)error\n                   startTime: (NSTimeInterval)startTime {\n    dispatch_async(dispatch_get_main_queue(), ^{\n        for (id<DoraemonNetworkInterceptorDelegate> delegate in self.delegates) {\n            [delegate doraemonNetworkInterceptorDidReceiveData:data response:response request:request error:error startTime:startTime];\n        }\n    });\n}\n\n\n\n\n-(NSData *)encodeMD5:(NSData *)input{\n    \n    unsigned char result[CC_MD5_DIGEST_LENGTH];\n    CC_MD5(input.bytes, (CC_LONG)input.length, result);\n    \n    NSData *data =[[NSData alloc] initWithBytes:result length:CC_MD5_DIGEST_LENGTH];\n    return data;\n}\n\n- (NSString *)md5:(NSString *)input {\n    const char *cStr = [input UTF8String];\n    unsigned char digest[CC_MD5_DIGEST_LENGTH];\n    CC_MD5(cStr, (CC_LONG)strlen(cStr), digest);\n    NSMutableString *output =  [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH *2];\n//    NSMutableString* ret = [NSMutableString stringWithCapacity: CC_MD5_DIGEST_LENGTH];\n    for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {\n        [output appendFormat:@\"%02x\",digest[i]];\n    }\n\n    return output;\n}\n\n- (NSString *)hexStringFromString:(NSString *)string{\n   NSData *myD = [string dataUsingEncoding:NSUTF8StringEncoding];\n   Byte *bytes = (Byte *)[myD bytes];\n   //下面是Byte 转换为16进制。\n   NSString *hexStr=@\"\";\n   for(int i=0;i<[myD length];i++)\n   {\n       NSString *newHexStr = [NSString stringWithFormat:@\"%x\",bytes[i]&0xff];///16进制数\n       if([newHexStr length]==1)\n           hexStr = [NSString stringWithFormat:@\"%@0%@\",hexStr,newHexStr];\n       else\n           hexStr = [NSString stringWithFormat:@\"%@%@\",hexStr,newHexStr];\n   }\n   return hexStr;\n}\n\n//data转换为十六进制的string\n- (NSString *)hexStringFromData:(NSData *)myD{\n    \n    Byte *bytes = (Byte *)[myD bytes];\n    //下面是Byte 转换为16进制。\n    NSString *hexStr=@\"\";\n    for(int i=0;i<[myD length];i++)\n        \n    {\n        NSString *newHexStr = [NSString stringWithFormat:@\"%x\",bytes[i]&0xff];///16进制数\n        \n        if([newHexStr length]==1)\n            \n            hexStr = [NSString stringWithFormat:@\"%@0%@\",hexStr,newHexStr];\n        \n        else\n            \n            hexStr = [NSString stringWithFormat:@\"%@%@\",hexStr,newHexStr];\n    }\n    NSLog(@\"hex = %@\",hexStr);\n    \n    return hexStr;\n}\n\n- (void)structureKeyByString:(NSString *)original {\n  original  = @\"method=POST&path=/gateway&fragment=null&query={\\\"api\\\":\\\"lj.u.d.changeOnline\\\",\\\"appKey\\\":\\\"b4f945fe780140d8a0d19d1f2d021db7\\\"}&contentType=application/json; charset=utf-8&requestBody={\\\"type\\\":1.0}\";\n    \n   original = @\"method=POST&path=/golden/stat&fragment=null&query={}&contentType=application/json;charset=utf-8&requestBody={\\\"attrs\\\":\\\"{\\\\\\\"module_id\\\\\\\":1602,\\\\\\\"static_version\\\\\\\":\\\\\\\"0.0.81\\\\\\\",\\\\\\\"module_version\\\\\\\":\\\\\\\"1.0.39\\\\\\\",\\\\\\\"app_id\\\\\\\":\\\\\\\"788119\\\\\\\",\\\\\\\"native_version\\\\\\\":\\\\\\\"6.8.2\\\\\\\",\\\\\\\"status\\\\\\\":0}\\\",\\\"e\\\":\\\"tech_mait_sdk_load\\\",\\\"ot\\\":\\\"android\\\",\\\"pn\\\":\\\"mait_tracker\\\",\\\"ua\\\":\\\"00000000-04a2-029e-ffff-ffffef05ac4a\\\",\\\"url\\\":\\\"hummer://user/dj_full_screen_page\\\"}\";\n\n  NSString *replaceString = [original stringByReplacingOccurrencesOfString:@\"\\\\\"withString:@\"\"];\n    \n  NSData *aData = [replaceString dataUsingEncoding: NSUTF8StringEncoding];\n  Byte *testByte = (Byte *)[aData bytes];\n  NSData *data = [NSData dataWithBytes:testByte length:sizeof(testByte)];\n  NSData *md5data = [self encodeMD5:data];\n  NSString *keyString = [self hexStringFromData:md5data];\n\n//  NSString *encodedUrl = [replaceString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];\n  NSString *md5String = [self md5:original];\n  keyString = [self hexStringFromString:md5String];\n    \n  NSLog(@\"keyString === %@\",keyString);\n    \n \n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Network/Interceptor/DoraemonURLSessionDemux.h",
    "content": "//\n//  DoraemonURLSessionDemux.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/1/16.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface DoraemonURLSessionDemux : NSObject\n\n- (instancetype)initWithConfiguration:(NSURLSessionConfiguration *)configuration;\n\n@property (atomic, copy,   readonly ) NSURLSessionConfiguration *   configuration;\n\n@property (atomic, strong, readonly ) NSURLSession *                session;\n\n- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request delegate:(id<NSURLSessionDataDelegate>)delegate modes:(NSArray *)modes;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Network/Interceptor/DoraemonURLSessionDemux.m",
    "content": "//\n//  DoraemonURLSessionDemux.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/1/16.\n//\n\n#import \"DoraemonURLSessionDemux.h\"\n\n@interface DoraemonURLSessionDemuxTaskInfo : NSObject\n\n- (instancetype)initWithTask:(NSURLSessionDataTask *)task delegate:(id<NSURLSessionDataDelegate>)delegate modes:(NSArray *)modes;\n\n@property (atomic, strong, readonly ) NSURLSessionDataTask *        task;\n@property (atomic, strong, readonly ) id<NSURLSessionDataDelegate>  delegate;\n@property (atomic, strong, readonly ) NSThread *                    thread;\n@property (atomic, copy,   readonly ) NSArray *                     modes;\n\n- (void)performBlock:(dispatch_block_t)block;\n\n- (void)invalidate;\n\n@end\n\n@interface DoraemonURLSessionDemuxTaskInfo ()\n\n@property (atomic, strong, readwrite) id<NSURLSessionDataDelegate>  delegate;\n@property (atomic, strong, readwrite) NSThread *                    thread;\n\n@end\n\n@implementation DoraemonURLSessionDemuxTaskInfo\n\n- (instancetype)initWithTask:(NSURLSessionDataTask *)task delegate:(id<NSURLSessionDataDelegate>)delegate modes:(NSArray *)modes\n{\n    assert(task != nil);\n    assert(delegate != nil);\n    assert(modes != nil);\n    \n    self = [super init];\n    if (self != nil) {\n        self->_task = task;\n        self->_delegate = delegate;\n        self->_thread = [NSThread currentThread];\n        self->_modes = [modes copy];\n    }\n    return self;\n}\n\n- (void)performBlock:(dispatch_block_t)block\n{\n    assert(self.delegate != nil);\n    assert(self.thread != nil);\n    [self performSelector:@selector(performBlockOnClientThread:) onThread:self.thread withObject:[block copy] waitUntilDone:NO modes:self.modes];\n}\n\n- (void)performBlockOnClientThread:(dispatch_block_t)block\n{\n    assert([NSThread currentThread] == self.thread);\n    block();\n}\n\n- (void)invalidate\n{\n    self.delegate = nil;\n    self.thread = nil;\n}\n\n@end\n\n\n@interface DoraemonURLSessionDemux()<NSURLSessionDataDelegate>\n\n@property (atomic, strong, readonly ) NSMutableDictionary *taskInfoByTaskID;\n@property (atomic, strong, readonly ) NSOperationQueue *sessionDelegateQueue;\n\n@end\n\n@implementation DoraemonURLSessionDemux\n\n- (instancetype)init{\n    return [self initWithConfiguration:nil];\n}\n\n- (instancetype)initWithConfiguration:(NSURLSessionConfiguration *)configuration{\n    self = [super init];\n    if (self != nil) {\n        if (configuration == nil) {\n            configuration = [NSURLSessionConfiguration defaultSessionConfiguration];\n        }\n        self->_configuration = [configuration copy];\n        \n        self->_taskInfoByTaskID = [[NSMutableDictionary alloc] init];\n        \n        self->_sessionDelegateQueue = [[NSOperationQueue alloc] init];\n        [self->_sessionDelegateQueue setMaxConcurrentOperationCount:1];\n        [self->_sessionDelegateQueue setName:@\"DoraemonURLSessionDemux\"];\n        \n        self->_session = [NSURLSession sessionWithConfiguration:self->_configuration delegate:self delegateQueue:self->_sessionDelegateQueue];\n        self->_session.sessionDescription = @\"DoraemonURLSessionDemux\";\n    }\n    return self;\n}\n\n- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request delegate:(id<NSURLSessionDataDelegate>)delegate modes:(NSArray *)modes\n{\n    NSURLSessionDataTask *          task;\n    DoraemonURLSessionDemuxTaskInfo *    taskInfo;\n    \n    assert(request != nil);\n    assert(delegate != nil);\n    \n    if ([modes count] == 0) {\n        modes = @[ NSDefaultRunLoopMode ];\n    }\n    \n    task = [self.session dataTaskWithRequest:request];\n    assert(task != nil);\n    \n    taskInfo = [[DoraemonURLSessionDemuxTaskInfo alloc] initWithTask:task delegate:delegate modes:modes];\n    \n    @synchronized (self) {\n        self.taskInfoByTaskID[@(task.taskIdentifier)] = taskInfo;\n    }\n    \n    return task;\n}\n\n- (DoraemonURLSessionDemuxTaskInfo *)taskInfoForTask:(NSURLSessionTask *)task{\n    DoraemonURLSessionDemuxTaskInfo *result;\n    \n    assert(task != nil);\n    \n    @synchronized (self) {\n        result = self.taskInfoByTaskID[@(task.taskIdentifier)];\n        assert(result != nil);\n    }\n    return result;\n}\n\n#pragma mark -- NSURLSessionDataDelegate\n- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)newRequest completionHandler:(void (^)(NSURLRequest *))completionHandler{\n    DoraemonURLSessionDemuxTaskInfo *taskInfo;\n    \n    taskInfo = [self taskInfoForTask:task];\n    if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) {\n        [taskInfo performBlock:^{\n            [taskInfo.delegate URLSession:session task:task willPerformHTTPRedirection:response newRequest:newRequest completionHandler:completionHandler];\n        }];\n    } else {\n        completionHandler(newRequest);\n    }\n}\n\n- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler{\n    DoraemonURLSessionDemuxTaskInfo *taskInfo;\n    \n    taskInfo = [self taskInfoForTask:task];\n    if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) {\n        [taskInfo performBlock:^{\n            [taskInfo.delegate URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];\n        }];\n    } else {\n        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);\n    }\n}\n\n- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler{\n    DoraemonURLSessionDemuxTaskInfo *taskInfo;\n    \n    taskInfo = [self taskInfoForTask:task];\n    if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:needNewBodyStream:)]) {\n        [taskInfo performBlock:^{\n            [taskInfo.delegate URLSession:session task:task needNewBodyStream:completionHandler];\n        }];\n    } else {\n        completionHandler(nil);\n    }\n}\n\n- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{\n    DoraemonURLSessionDemuxTaskInfo *taskInfo;\n    \n    taskInfo = [self taskInfoForTask:task];\n    if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)]) {\n        [taskInfo performBlock:^{\n            [taskInfo.delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend];\n        }];\n    }\n}\n\n- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{\n    DoraemonURLSessionDemuxTaskInfo *taskInfo;\n    \n    taskInfo = [self taskInfoForTask:task];\n    @synchronized (self) {\n        [self.taskInfoByTaskID removeObjectForKey:@(taskInfo.task.taskIdentifier)];\n    }\n    if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {\n        [taskInfo performBlock:^{\n            [taskInfo.delegate URLSession:session task:task didCompleteWithError:error];\n            [taskInfo invalidate];\n        }];\n    } else {\n        [taskInfo invalidate];\n    }\n}\n\n- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{\n    DoraemonURLSessionDemuxTaskInfo *taskInfo;\n    \n    taskInfo = [self taskInfoForTask:dataTask];\n    if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {\n        [taskInfo performBlock:^{\n            [taskInfo.delegate URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];\n        }];\n    } else {\n        completionHandler(NSURLSessionResponseAllow);\n    }\n}\n\n- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask{\n    DoraemonURLSessionDemuxTaskInfo *taskInfo;\n    \n    taskInfo = [self taskInfoForTask:dataTask];\n    if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:didBecomeDownloadTask:)]) {\n        [taskInfo performBlock:^{\n            [taskInfo.delegate URLSession:session dataTask:dataTask didBecomeDownloadTask:downloadTask];\n        }];\n    }\n}\n\n- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{\n    DoraemonURLSessionDemuxTaskInfo *taskInfo;\n    \n    taskInfo = [self taskInfoForTask:dataTask];\n    if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) {\n        [taskInfo performBlock:^{\n            [taskInfo.delegate URLSession:session dataTask:dataTask didReceiveData:data];\n        }];\n    }\n}\n\n- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler{\n    DoraemonURLSessionDemuxTaskInfo *taskInfo;\n    \n    taskInfo = [self taskInfoForTask:dataTask];\n    if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) {\n        [taskInfo performBlock:^{\n            [taskInfo.delegate URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];\n        }];\n    } else {\n        completionHandler(proposedResponse);\n    }\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Network/Interceptor/NSURLSessionConfiguration+Doraemon.h",
    "content": "//\n//  NSURLSessionConfiguration+Doraemon.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/7/2.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface NSURLSessionConfiguration (Doraemon)\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Network/Interceptor/NSURLSessionConfiguration+Doraemon.m",
    "content": "//\n//  NSURLSessionConfiguration+Doraemon.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/7/2.\n//\n\n#import \"NSURLSessionConfiguration+Doraemon.h\"\n#import \"DoraemonNSURLProtocol.h\"\n#import \"NSObject+Doraemon.h\"\n#import \"DoraemonNetFlowManager.h\"\n#import \"DoraemonCacheManager.h\"\n\n\n@implementation NSURLSessionConfiguration (Doraemon)\n\n+ (void)load{\n    [[self class] doraemon_swizzleClassMethodWithOriginSel:@selector(defaultSessionConfiguration) swizzledSel:@selector(doraemon_defaultSessionConfiguration)];\n    [[self class] doraemon_swizzleClassMethodWithOriginSel:@selector(ephemeralSessionConfiguration) swizzledSel:@selector(doraemon_ephemeralSessionConfiguration)];\n}\n\n+ (NSURLSessionConfiguration *)doraemon_defaultSessionConfiguration{\n    NSURLSessionConfiguration *configuration = [self doraemon_defaultSessionConfiguration];\n    [configuration addDoraemonNSURLProtocol];\n    return configuration;\n}\n\n+ (NSURLSessionConfiguration *)doraemon_ephemeralSessionConfiguration{\n    NSURLSessionConfiguration *configuration = [self doraemon_ephemeralSessionConfiguration];\n    [configuration addDoraemonNSURLProtocol];\n    return configuration;\n}\n\n- (void)addDoraemonNSURLProtocol {\n    if ([self respondsToSelector:@selector(protocolClasses)]\n        && [self respondsToSelector:@selector(setProtocolClasses:)]) {\n        NSMutableArray * urlProtocolClasses = [NSMutableArray arrayWithArray: self.protocolClasses];\n        Class protoCls = DoraemonNSURLProtocol.class;\n        if (![urlProtocolClasses containsObject:protoCls]) {\n            [urlProtocolClasses insertObject:protoCls atIndex:0];\n        }\n        self.protocolClasses = urlProtocolClasses;\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/AppInfo/DoraemonAppInfoCell.h",
    "content": "//\n//  DoraemonAppInfoCell.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/14.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface DoraemonAppInfoCell : UITableViewCell\n\n- (void)renderUIWithData:(NSDictionary *)data;\n\n+ (CGFloat)cellHeight;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/AppInfo/DoraemonAppInfoCell.m",
    "content": "//\n//  DoraemonAppInfoCell.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/14.\n//\n\n#import \"DoraemonAppInfoCell.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n#import \"UIColor+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonAppInfoCell()\n\n@property (nonatomic, strong) UILabel *titleLabel;\n@property (nonatomic, strong) UILabel *valueLabel;\n\n@end\n\n@implementation DoraemonAppInfoCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            self.backgroundColor = [UIColor systemBackgroundColor];\n        } else {\n#endif\n            self.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        self.titleLabel = [[UILabel alloc] init];\n        self.titleLabel.textColor = [UIColor doraemon_black_1];\n        self.titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        [self.contentView addSubview:self.titleLabel];\n        \n        self.valueLabel = [[UILabel alloc] init];\n        self.valueLabel.textColor = [UIColor doraemon_black_2];\n        self.valueLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        [self.contentView addSubview:self.valueLabel];\n    }\n    return self;\n}\n\n- (void)renderUIWithData:(NSDictionary *)data{\n    NSString *title = data[@\"title\"];\n    NSString *value = data[@\"value\"];\n    \n    self.titleLabel.text = title;\n    \n    NSString *cnValue = nil;\n    if([value isEqualToString:@\"NotDetermined\"]){\n        cnValue = DoraemonLocalizedString(@\"用户没有选择\");\n    }else if([value isEqualToString:@\"Restricted\"]){\n        cnValue = DoraemonLocalizedString(@\"家长控制\");\n    }else if([value isEqualToString:@\"Denied\"]){\n        cnValue = DoraemonLocalizedString(@\"用户没有授权\");\n    }else if([value isEqualToString:@\"Authorized\"]){\n        cnValue = DoraemonLocalizedString(@\"用户已经授权\");\n    }else{\n        cnValue = value;\n    }\n    \n    self.valueLabel.text = cnValue;\n    \n    [self.titleLabel sizeToFit];\n    [self.valueLabel sizeToFit];\n    \n    self.titleLabel.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), 0, self.titleLabel.doraemon_width, [[self class] cellHeight]);\n    self.valueLabel.frame = CGRectMake(DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)-self.valueLabel.doraemon_width, 0, self.valueLabel.doraemon_width, [[self class] cellHeight]);\n}\n\n+ (CGFloat)cellHeight{\n    return kDoraemonSizeFrom750_Landscape(104);\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/AppInfo/DoraemonAppInfoPlugin.h",
    "content": "//\n//  DoraemonAppInfoPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/13.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonAppInfoPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/AppInfo/DoraemonAppInfoPlugin.m",
    "content": "//\n//  DoraemonAppInfoPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/13.\n//\n\n#import \"DoraemonAppInfoPlugin.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonAppInfoViewController.h\"\n\n@implementation DoraemonAppInfoPlugin\n\n- (void)pluginDidLoad{\n    DoraemonAppInfoViewController *vc = [[DoraemonAppInfoViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/AppInfo/DoraemonAppInfoUtil.h",
    "content": "//\n//  DoraemonAppInfoUtil.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/15.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface DoraemonAppInfoUtil : NSObject\n\n+ (NSString *)appName;\n\n/**\n DeviceInfo：获取当前设备的 用户自定义的别名，例如：库克的 iPhone 9\n \n @return 当前设备的 用户自定义的别名，例如：库克的 iPhone 9\n */\n+ (NSString *)iphoneName;\n\n/**\n DeviceInfo：获取当前设备的 系统名称，例如：iOS 13.1\n \n @return 当前设备的 系统名称，例如：iOS 13.1\n */\n+ (NSString *)iphoneSystemVersion;\n\n+ (NSString *)bundleIdentifier;\n\n+ (NSString *)bundleVersion;\n\n+ (NSString *)bundleShortVersionString;\n\n+ (NSString *)iphoneType;\n\n+ (BOOL)isIPhoneXSeries;\n\n+ (BOOL)isIpad;\n\n+ (NSString *)locationAuthority;\n\n+ (NSString *)pushAuthority;\n\n+ (NSString *)cameraAuthority;\n\n+ (NSString *)audioAuthority;\n\n+ (NSString *)photoAuthority;\n\n+ (NSString *)addressAuthority;\n\n+ (NSString *)calendarAuthority;\n\n+ (NSString *)remindAuthority;\n\n+ (NSString *)bluetoothAuthority;\n\n/// 设备是否模拟器\n+ (BOOL)isSimulator;\n\n//获取设备当前网络IP地址\n+ (NSString *)getIPAddress:(BOOL)preferIPv4;\n\n//获取当前UUID\n+ (NSString *)uuid;\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/AppInfo/DoraemonAppInfoUtil.m",
    "content": "//\n//  DoraemonAppInfoUtil.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/15.\n//\n\n#import \"DoraemonAppInfoUtil.h\"\n#import <sys/utsname.h>\n#import <CoreLocation/CLLocationManager.h>\n#import <AVFoundation/AVFoundation.h>\n#import <Photos/Photos.h>\n#import <AddressBook/AddressBook.h>\n#import <Contacts/Contacts.h>\n#import <EventKit/EventKit.h>\n#import <UIKit/UIKit.h>\n#include <ifaddrs.h>\n#include <arpa/inet.h>\n#include <net/if.h>\n#import \"DoraemonUtil.h\"\n\n#define IOS_CELLULAR    @\"pdp_ip0\"\n#define IOS_WIFI        @\"en0\"\n//#define IOS_VPN       @\"utun0\"\n#define IP_ADDR_IPv4    @\"ipv4\"\n#define IP_ADDR_IPv6    @\"ipv6\"\n\n#define IOS8 ([[[UIDevice currentDevice] systemVersion] doubleValue] >=8.0 ? YES : NO)\n\n@implementation DoraemonAppInfoUtil\n\n+ (NSString *)appName\n{\n    NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@\"CFBundleDisplayName\"];\n    if (!appName) {\n        appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@\"CFBundleName\"];\n    }\n    return appName;\n}\n\n+ (NSString *)iphoneName\n{\n    return [UIDevice currentDevice].name;\n}\n\n+ (NSString *)iphoneSystemVersion\n{\n    return [UIDevice currentDevice].systemVersion;\n}\n\n+ (NSString *)bundleIdentifier\n{\n    return [[NSBundle mainBundle] bundleIdentifier];\n}\n\n+ (NSString *)bundleVersion\n{\n    return [[[NSBundle mainBundle] infoDictionary] objectForKey:@\"CFBundleVersion\"];\n}\n\n+ (NSString *)bundleShortVersionString\n{\n    return [[[NSBundle mainBundle] infoDictionary] objectForKey:@\"CFBundleShortVersionString\"];\n}\n\n+ (NSString *)iphoneType{\n    struct utsname systemInfo;\n    uname(&systemInfo);\n    NSString *platform = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];\n    \n    //iPhone\n    if ([platform isEqualToString:@\"iPhone1,1\"]) return @\"iPhone 1G\";\n    if ([platform isEqualToString:@\"iPhone1,2\"]) return @\"iPhone 3G\";\n    if ([platform isEqualToString:@\"iPhone2,1\"]) return @\"iPhone 3GS\";\n    if ([platform isEqualToString:@\"iPhone3,1\"]) return @\"iPhone 4\";\n    if ([platform isEqualToString:@\"iPhone3,2\"]) return @\"iPhone 4\";\n    if ([platform isEqualToString:@\"iPhone4,1\"]) return @\"iPhone 4S\";\n    if ([platform isEqualToString:@\"iPhone5,1\"]) return @\"iPhone 5\";\n    if ([platform isEqualToString:@\"iPhone5,2\"]) return @\"iPhone 5\";\n    if ([platform isEqualToString:@\"iPhone5,3\"]) return @\"iPhone 5C\";\n    if ([platform isEqualToString:@\"iPhone5,4\"]) return @\"iPhone 5C\";\n    if ([platform isEqualToString:@\"iPhone6,1\"]) return @\"iPhone 5S\";\n    if ([platform isEqualToString:@\"iPhone6,2\"]) return @\"iPhone 5S\";\n    if ([platform isEqualToString:@\"iPhone7,1\"]) return @\"iPhone 6 Plus\";\n    if ([platform isEqualToString:@\"iPhone7,2\"]) return @\"iPhone 6\";\n    if ([platform isEqualToString:@\"iPhone8,1\"]) return @\"iPhone 6S\";\n    if ([platform isEqualToString:@\"iPhone8,2\"]) return @\"iPhone 6S Plus\";\n    if ([platform isEqualToString:@\"iPhone8,4\"]) return @\"iPhone SE\";\n    if ([platform isEqualToString:@\"iPhone9,1\"]) return @\"iPhone 7\";\n    if ([platform isEqualToString:@\"iPhone9,3\"]) return @\"iPhone 7\";\n    if ([platform isEqualToString:@\"iPhone9,2\"]) return @\"iPhone 7 Plus\";\n    if ([platform isEqualToString:@\"iPhone9,4\"]) return @\"iPhone 7 Plus\";\n    if ([platform isEqualToString:@\"iPhone10,1\"]) return @\"iPhone 8\";\n    if ([platform isEqualToString:@\"iPhone10,4\"]) return @\"iPhone 8\";\n    if ([platform isEqualToString:@\"iPhone10,2\"]) return @\"iPhone 8 Plus\";\n    if ([platform isEqualToString:@\"iPhone10,5\"]) return @\"iPhone 8 Plus\";\n    if ([platform isEqualToString:@\"iPhone10,3\"]) return @\"iPhone X\";\n    if ([platform isEqualToString:@\"iPhone10,6\"]) return @\"iPhone X\";\n    if ([platform isEqualToString:@\"iPhone11,8\"]) return @\"iPhone XR\";\n    if ([platform isEqualToString:@\"iPhone11,2\"]) return @\"iPhone XS\";\n    if ([platform isEqualToString:@\"iPhone11,4\"]) return @\"iPhone XS Max\";\n    if ([platform isEqualToString:@\"iPhone11,6\"]) return @\"iPhone XS Max\";\n    if ([platform isEqualToString:@\"iPhone12,1\"]) return @\"iPhone 11\";\n    if ([platform isEqualToString:@\"iPhone12,3\"]) return @\"iPhone 11 Pro\";\n    if ([platform isEqualToString:@\"iPhone12,5\"]) return @\"iPhone 11 Pro Max\";\n    if ([platform isEqualToString:@\"iPhone12,8\"]) return @\"iPhone SE 2\";\n    if ([platform isEqualToString:@\"iPhone13,1\"]) return @\"iPhone 12 mini\";\n    if ([platform isEqualToString:@\"iPhone13,2\"]) return @\"iPhone 12\";\n    if ([platform isEqualToString:@\"iPhone13,3\"]) return @\"iPhone 12 Pro\";\n    if ([platform isEqualToString:@\"iPhone13,4\"]) return @\"iPhone 12 Pro Max\";\n    \n    return platform;\n}\n\n+ (BOOL)isIPhoneXSeries{\n    BOOL iPhoneXSeries = NO;\n    if (UIDevice.currentDevice.userInterfaceIdiom != UIUserInterfaceIdiomPhone) {\n        return iPhoneXSeries;\n    }\n    \n    if (@available(iOS 11.0, *)) {\n        UIWindow *mainWindow = [DoraemonUtil getKeyWindow];\n        if (mainWindow.safeAreaInsets.bottom > 0.0) {\n            iPhoneXSeries = YES;\n        }\n    }\n    \n    return iPhoneXSeries;\n}\n\n+ (BOOL)isIpad{\n    NSString *deviceType = [UIDevice currentDevice].model;\n    if ([deviceType isEqualToString:@\"iPad\"]) {\n        return YES;\n    }\n    return NO;\n}\n\n+ (NSString *)locationAuthority{\n    NSString *authority = @\"\";    \n    if ([CLLocationManager locationServicesEnabled]) {\n        CLAuthorizationStatus state = [CLLocationManager authorizationStatus];\n        if (state == kCLAuthorizationStatusNotDetermined) {\n            authority = @\"NotDetermined\";\n        }else if(state == kCLAuthorizationStatusRestricted){\n            authority = @\"Restricted\";\n        }else if(state == kCLAuthorizationStatusDenied){\n            authority = @\"Denied\";\n        }else if(state == kCLAuthorizationStatusAuthorizedAlways){\n            authority = @\"Always\";\n        }else if(state == kCLAuthorizationStatusAuthorizedWhenInUse){\n            authority = @\"WhenInUse\";\n        }\n    }else{\n        authority = @\"NoEnabled\";\n    }\n    return authority;\n}\n\n+ (NSString *)pushAuthority{\n    if ([[UIApplication sharedApplication] currentUserNotificationSettings].types  == UIUserNotificationTypeNone) {\n        return @\"NO\";\n    }\n    return @\"YES\";\n}\n\n+ (NSString *)cameraAuthority{\n    NSString *authority = @\"\";\n    NSString *mediaType = AVMediaTypeVideo;//读取媒体类型\n    AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];//读取设备授权状态\n    switch (authStatus) {\n        case AVAuthorizationStatusNotDetermined:\n            authority = @\"NotDetermined\";\n            break;\n        case AVAuthorizationStatusRestricted:\n            authority = @\"Restricted\";\n            break;\n        case AVAuthorizationStatusDenied:\n            authority = @\"Denied\";\n            break;\n        case AVAuthorizationStatusAuthorized:\n            authority = @\"Authorized\";\n            break;\n        default:\n            break;\n    }\n    return authority;\n}\n\n+ (NSString *)audioAuthority{\n    NSString *authority = @\"\";\n    NSString *mediaType = AVMediaTypeAudio;//读取媒体类型\n    AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];//读取设备授权状态\n    switch (authStatus) {\n        case AVAuthorizationStatusNotDetermined:\n            authority = @\"NotDetermined\";\n            break;\n        case AVAuthorizationStatusRestricted:\n            authority = @\"Restricted\";\n            break;\n        case AVAuthorizationStatusDenied:\n            authority = @\"Denied\";\n            break;\n        case AVAuthorizationStatusAuthorized:\n            authority = @\"Authorized\";\n            break;\n        default:\n            break;\n    }\n    return authority;\n}\n\n+ (NSString *)photoAuthority{\n    NSString *authority = @\"\";\n    #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 //iOS 8.0以下使用AssetsLibrary.framework\n    ALAuthorizationStatus status = [ALAssetsLibrary authorizationStatus];\n    switch (status) {\n        case ALAuthorizationStatusNotDetermined:    //用户还没有选择\n        {\n            authority = @\"NotDetermined\";\n        }\n            break;\n        case ALAuthorizationStatusRestricted:       //家长控制\n        {\n            authority = @\"Restricted\";\n        }\n            break;\n        case ALAuthorizationStatusDenied:           //用户拒绝\n        {\n            authority = @\"Denied\";\n        }\n            break;\n        case ALAuthorizationStatusAuthorized:       //已授权\n        {\n            authority = @\"Authorized\";\n        }\n            break;\n        default:\n            break;\n    }\n    #else   //iOS 8.0以上使用Photos.framework\n    PHAuthorizationStatus current = [PHPhotoLibrary authorizationStatus];\n    switch (current) {\n        case PHAuthorizationStatusNotDetermined:    //用户还没有选择(第一次)\n        {\n            authority = @\"NotDetermined\";\n        }\n            break;\n        case PHAuthorizationStatusRestricted:       //家长控制\n        {\n            authority = @\"Restricted\";\n        }\n            break;\n        case PHAuthorizationStatusDenied:           //用户拒绝\n        {\n            authority = @\"Denied\";\n        }\n            break;\n        case PHAuthorizationStatusAuthorized:       //已授权\n        {\n            authority = @\"Authorized\";\n        }\n            break;\n        default:\n            break;\n    }\n    #endif\n    return authority;\n}\n\n+ (NSString *)addressAuthority{\n    NSString *authority = @\"\";\n    if (@available(iOS 9.0, *)) {//iOS9.0之后\n        CNAuthorizationStatus authStatus = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];\n        switch (authStatus) {\n            case CNAuthorizationStatusAuthorized:\n                authority = @\"Authorized\";\n                break;\n            case CNAuthorizationStatusDenied:\n            {\n                authority = @\"Denied\";\n            }\n                break;\n            case CNAuthorizationStatusNotDetermined:\n            {\n                authority = @\"NotDetermined\";\n            }\n                break;\n            case CNAuthorizationStatusRestricted:\n                authority = @\"Restricted\";\n                break;\n        }\n    }else{//iOS9.0之前\n        ABAuthorizationStatus authorStatus = ABAddressBookGetAuthorizationStatus();\n        switch (authorStatus) {\n            case kABAuthorizationStatusAuthorized:\n                authority = @\"Authorized\";\n                break;\n            case kABAuthorizationStatusDenied:\n            {\n                authority = @\"Denied\";\n            }\n                break;\n            case kABAuthorizationStatusNotDetermined:\n            {\n                authority = @\"NotDetermined\";\n            }\n                break;\n            case kABAuthorizationStatusRestricted:\n                authority = @\"Restricted\";\n                break;\n            default:\n                break;\n        }\n    }\n    return authority;\n}\n\n+ (NSString *)calendarAuthority{\n    NSString *authority = @\"\";\n    EKAuthorizationStatus status = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent];\n    switch (status) {\n        case EKAuthorizationStatusNotDetermined:\n            authority = @\"NotDetermined\";\n            break;\n        case EKAuthorizationStatusRestricted:\n            authority = @\"Restricted\";\n            break;\n        case EKAuthorizationStatusDenied:\n            authority = @\"Denied\";\n            break;\n        case EKAuthorizationStatusAuthorized:\n            authority = @\"Authorized\";\n            break;\n        default:\n            break;\n    }\n    return authority;\n}\n\n+ (NSString *)remindAuthority{\n    NSString *authority = @\"\";\n    EKAuthorizationStatus status = [EKEventStore authorizationStatusForEntityType:EKEntityTypeReminder];\n    switch (status) {\n        case EKAuthorizationStatusNotDetermined:\n            authority = @\"NotDetermined\";\n            break;\n        case EKAuthorizationStatusRestricted:\n            authority = @\"Restricted\";\n            break;\n        case EKAuthorizationStatusDenied:\n            authority = @\"Denied\";\n            break;\n        case EKAuthorizationStatusAuthorized:\n            authority = @\"Authorized\";\n            break;\n        default:\n            break;\n    }\n    return authority;\n}\n\n+ (NSString *)bluetoothAuthority{\n    return @\"\";\n}\n\n\n\n#pragma mark 设备是否模拟器\n+ (NSString *)deviceIdentifier {\n    struct utsname systemInfo;\n    uname(&systemInfo);\n    return [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];\n}\n\n+ (BOOL)isSimulator {\n    NSString *identifier = [self deviceIdentifier];\n    return [identifier isEqualToString:@\"i386\"] || [identifier isEqualToString:@\"x86_64\"];\n}\n\n//获取设备当前网络IP地址\n+ (NSString *)getIPAddress:(BOOL)preferIPv4\n{\n    NSArray *searchArray = preferIPv4 ?\n                            @[ /*IOS_VPN @\"/\" IP_ADDR_IPv4, IOS_VPN @\"/\" IP_ADDR_IPv6,*/ IOS_WIFI @\"/\" IP_ADDR_IPv4, IOS_WIFI @\"/\" IP_ADDR_IPv6, IOS_CELLULAR @\"/\" IP_ADDR_IPv4, IOS_CELLULAR @\"/\" IP_ADDR_IPv6 ] :\n                            @[ /*IOS_VPN @\"/\" IP_ADDR_IPv6, IOS_VPN @\"/\" IP_ADDR_IPv4,*/ IOS_WIFI @\"/\" IP_ADDR_IPv6, IOS_WIFI @\"/\" IP_ADDR_IPv4, IOS_CELLULAR @\"/\" IP_ADDR_IPv6, IOS_CELLULAR @\"/\" IP_ADDR_IPv4 ] ;\n\n    NSDictionary *addresses = [[self class] getIPAddresses];\n    __block NSString *address;\n    [searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop)\n        {\n            address = addresses[key];\n            if(address) *stop = YES;\n        } ];\n    return address ? address : @\"0.0.0.0\";\n}\n\n//获取所有相关IP信息\n+ (NSDictionary *)getIPAddresses\n{\n    NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8];\n\n    // retrieve the current interfaces - returns 0 on success\n    struct ifaddrs *interfaces;\n    if(!getifaddrs(&interfaces)) {\n        // Loop through linked list of interfaces\n        struct ifaddrs *interface;\n        for(interface=interfaces; interface; interface=interface->ifa_next) {\n            if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) {\n                continue; // deeply nested code harder to read\n            }\n            const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;\n            char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];\n            if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) {\n                NSString *name = [NSString stringWithUTF8String:interface->ifa_name];\n                NSString *type;\n                if(addr->sin_family == AF_INET) {\n                    if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {\n                        type = IP_ADDR_IPv4;\n                    }\n                } else {\n                    const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;\n                    if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {\n                        type = IP_ADDR_IPv6;\n                    }\n                }\n                if(type) {\n                    NSString *key = [NSString stringWithFormat:@\"%@/%@\", name, type];\n                    addresses[key] = [NSString stringWithUTF8String:addrBuf];\n                }\n            }\n        }\n        // Free memory\n        freeifaddrs(interfaces);\n    }\n    return [addresses count] ? addresses : nil;\n}\n\n+ (NSString *)uuid{\n    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];\n    NSString *uuid = [ud objectForKey:@\"UUID\"];\n    if (!uuid) {\n        uuid = [[NSUUID UUID] UUIDString];\n        [ud setObject:uuid forKey:@\"UUID\"];\n        [ud synchronize];\n    }\n    return uuid;\n}\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/AppInfo/DoraemonAppInfoViewController.h",
    "content": "//\n//  DoraemonAppInfoViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/13.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonAppInfoViewController : DoraemonBaseViewController\n\n/// 自定义App信息处理\n@property (class, nonatomic, copy) void (^customAppInfoBlock)(NSMutableArray<NSDictionary *> *appInfos);\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/AppInfo/DoraemonAppInfoViewController.m",
    "content": "//\n//  DoraemonAppInfoViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/13.\n//\n\n#import \"DoraemonAppInfoViewController.h\"\n#import \"DoraemonAppInfoCell.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonAppInfoUtil.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"UIView+Doraemon.h\"\n#import \"UIColor+Doraemon.h\"\n#import <CoreTelephony/CTCellularData.h>\n#import <objc/runtime.h>\n\n@interface DoraemonAppInfoViewController ()<UITableViewDelegate,UITableViewDataSource>\n\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, strong) NSArray *dataArray;\n@property (nonatomic, strong) CTCellularData *cellularData API_AVAILABLE(ios(9.0));\n@property (nonatomic, copy) NSString *authority;\n\n@end\n\n@implementation DoraemonAppInfoViewController{\n    \n}\n\n+ (void)setCustomAppInfoBlock:(void (^)(NSMutableArray<NSDictionary *> *))customAppInfoBlock {\n    objc_setAssociatedObject(self, @selector(customAppInfoBlock), customAppInfoBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);\n}\n\n+ (void (^)(NSMutableArray<NSDictionary *> *))customAppInfoBlock {\n    return objc_getAssociatedObject(self, _cmd);\n}\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    [self initUI];\n    [self initData];\n}\n\n- (void)viewDidDisappear:(BOOL)animated{\n    [super viewDidDisappear:animated];\n    if (@available(iOS 9.0, *)){\n        _cellularData.cellularDataRestrictionDidUpdateNotifier = nil;\n        _cellularData = nil;\n    }\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n- (void)initUI\n{\n    self.title = DoraemonLocalizedString(@\"App信息\");\n\n    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, self.view.doraemon_height-self.bigTitleView.doraemon_bottom) style:UITableViewStyleGrouped];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        self.tableView.backgroundColor = [UIColor systemBackgroundColor];\n    } else {\n#endif\n        self.tableView.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n    self.tableView.delegate = self;\n    self.tableView.dataSource = self;\n    self.tableView.estimatedRowHeight = 0.;\n    self.tableView.estimatedSectionFooterHeight = 0.;\n    self.tableView.estimatedSectionHeaderHeight = 0.;\n    [self.view addSubview:self.tableView];\n}\n\n#pragma mark - default data\n\n- (void)initData\n{\n    // 获取设备名称\n    NSString *iphoneName = [DoraemonAppInfoUtil iphoneName];\n    \n    // 获取当前系统版本号\n    NSString *iphoneSystemVersion = [DoraemonAppInfoUtil iphoneSystemVersion];\n    \n    //获取手机型号\n    NSString *iphoneType = [DoraemonAppInfoUtil iphoneType];\n    \n    //获取手机屏幕大小\n    NSString *iphoneSize = [NSString stringWithFormat:@\"%.0f * %.0f\",DoraemonScreenWidth,DoraemonScreenHeight];\n    \n    //获取手机ipv4地址\n    NSString *ipv4String = [DoraemonAppInfoUtil getIPAddress:YES];\n    \n    //获取手机ipv6地址\n    NSString *ipv6String = [DoraemonAppInfoUtil getIPAddress:NO];\n    \n    //获取手机mac地址\n    \n    \n    //获取bundle id\n    NSString *bundleIdentifier = [DoraemonAppInfoUtil bundleIdentifier];\n    \n    //获取App版本号\n    NSString *bundleVersion = [DoraemonAppInfoUtil bundleVersion];\n    \n    //获取App版本Code\n    NSString *bundleShortVersionString = [DoraemonAppInfoUtil bundleShortVersionString];\n    \n    //获取手机是否有地理位置权限\n    NSString *locationAuthority = [DoraemonAppInfoUtil locationAuthority];\n    \n    //获取网络权限\n    if (@available(iOS 9.0, *)) {\n        _cellularData = [[CTCellularData alloc]init];\n        __weak typeof(self) weakSelf = self;\n        _cellularData.cellularDataRestrictionDidUpdateNotifier = ^(CTCellularDataRestrictedState state) {\n            if (state == kCTCellularDataRestricted) {\n                weakSelf.authority = @\"Restricted\";\n            }else if(state == kCTCellularDataNotRestricted){\n                weakSelf.authority = @\"NotRestricted\";\n            }else{\n                weakSelf.authority = @\"Unknown\";\n            }\n            dispatch_async(dispatch_get_main_queue(), ^{\n                [weakSelf.tableView reloadData];\n            });\n            \n        };\n    }\n    \n    //获取push权限\n    NSString *pushAuthority = [DoraemonAppInfoUtil pushAuthority];\n    \n    //获取拍照权限\n    NSString *cameraAuthority = [DoraemonAppInfoUtil cameraAuthority];\n    \n    //获取麦克风权限\n    NSString *audioAuthority = [DoraemonAppInfoUtil audioAuthority];\n    \n    //获取相册权限\n    NSString *photoAuthority = [DoraemonAppInfoUtil photoAuthority];\n    \n    //获取通讯录权限\n    NSString *addressAuthority = [DoraemonAppInfoUtil addressAuthority];\n    \n    //获取日历权限\n    NSString *calendarAuthority = [DoraemonAppInfoUtil calendarAuthority];\n    \n    //获取提醒事项权限\n    NSString *remindAuthority = [DoraemonAppInfoUtil remindAuthority];\n    \n    //可自定义的App信息\n    NSMutableArray *appInfos = @[@{@\"title\":@\"Bundle ID\",\n                            @\"value\":bundleIdentifier},\n                          @{@\"title\":@\"Version\",\n                            @\"value\":bundleVersion},\n                          @{@\"title\":@\"VersionCode\",\n                            @\"value\":bundleShortVersionString}].mutableCopy;\n    if (DoraemonAppInfoViewController.customAppInfoBlock) {\n        DoraemonAppInfoViewController.customAppInfoBlock(appInfos);\n    }\n    \n    \n    NSArray *dataArray = @[\n                           @{\n                               @\"title\":DoraemonLocalizedString(@\"手机信息\"),\n                               @\"array\":@[\n                                       @{\n                                           @\"title\":DoraemonLocalizedString(@\"设备名称\"),\n                                           @\"value\":iphoneName\n                                           },\n                                       @{\n                                           @\"title\":DoraemonLocalizedString(@\"手机型号\"),\n                                           @\"value\":iphoneType\n                                           },\n                                       @{\n                                           @\"title\":DoraemonLocalizedString(@\"系统版本\"),\n                                           @\"value\":iphoneSystemVersion\n                                           },\n                                       @{\n                                           @\"title\":DoraemonLocalizedString(@\"手机屏幕\"),\n                                           @\"value\":iphoneSize\n                                            },\n                                       @{\n                                           @\"title\":@\"ipV4\",\n                                           @\"value\":STRING_NOT_NULL(ipv4String)\n                                            },\n                                       @{\n                                           @\"title\":@\"ipV6\",\n                                           @\"value\":STRING_NOT_NULL(ipv6String)\n                                            }\n                                       ]\n                               },\n                           @{\n                               @\"title\":DoraemonLocalizedString(@\"App信息\"),\n                               @\"array\":appInfos\n                               },\n                           @{\n                               @\"title\":DoraemonLocalizedString(@\"权限信息\"),\n                               @\"array\":@[@{\n                                              @\"title\":DoraemonLocalizedString(@\"地理位置权限\"),\n                                              @\"value\":locationAuthority\n                                              },\n                                          @{\n                                              @\"title\":DoraemonLocalizedString(@\"网络权限\"),\n                                              @\"value\":@\"Unknown\"\n                                              },\n                                          @{\n                                              @\"title\":DoraemonLocalizedString(@\"推送权限\"),\n                                              @\"value\":pushAuthority\n                                              },\n                                          @{\n                                              @\"title\":DoraemonLocalizedString(@\"相机权限\"),\n                                              @\"value\":cameraAuthority\n                                              },\n                                          @{\n                                              @\"title\":DoraemonLocalizedString(@\"麦克风权限\"),\n                                              @\"value\":audioAuthority\n                                              },\n                                          @{\n                                              @\"title\":DoraemonLocalizedString(@\"相册权限\"),\n                                              @\"value\":photoAuthority\n                                              },\n                                          @{\n                                              @\"title\":DoraemonLocalizedString(@\"通讯录权限\"),\n                                              @\"value\":addressAuthority\n                                              },\n                                          @{\n                                              @\"title\":DoraemonLocalizedString(@\"日历权限\"),\n                                              @\"value\":calendarAuthority\n                                              },\n                                          @{\n                                              @\"title\":DoraemonLocalizedString(@\"提醒事项权限\"),\n                                              @\"value\":remindAuthority\n                                              }\n                                          ]\n                               }\n                           ];\n    _dataArray = dataArray;\n}\n\n#pragma mark - UITableView Delegate\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {\n    return _dataArray.count;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    NSArray *array = _dataArray[section][@\"array\"];\n    return array.count;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return [DoraemonAppInfoCell cellHeight];\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {\n    return kDoraemonSizeFrom750_Landscape(120);\n}\n\n- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{\n    UIView *sectionView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(120))];\n    UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(32), 0, DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32), kDoraemonSizeFrom750_Landscape(120))];\n    NSDictionary *dic = _dataArray[section];\n    titleLabel.text = dic[@\"title\"];\n    titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)];\n    titleLabel.textColor = [UIColor doraemon_black_3];\n    [sectionView addSubview:titleLabel];\n    return sectionView;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {\n    return 0.1;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"httpcell\";\n    DoraemonAppInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[DoraemonAppInfoCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];\n    }\n    NSArray *array = _dataArray[indexPath.section][@\"array\"];\n    NSDictionary *item = array[indexPath.row];\n    if (indexPath.section == 2 && indexPath.row == 1 && self.authority) {\n        NSMutableDictionary *tempItem = [item mutableCopy];\n        [tempItem setValue:self.authority forKey:@\"value\"];\n        [cell renderUIWithData:tempItem];\n    }else{\n       [cell renderUIWithData:item];\n    }\n    return cell;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{\n    if(indexPath.section == 2){\n        [DoraemonUtil openAppSetting];\n    }\n}\n\n- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{\n    return YES;\n}\n\n- (NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath{\n    __weak typeof(self) weakSelf = self;\n    UITableViewRowAction *action0 = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:DoraemonLocalizedString(@\"复制\")  handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) {\n        NSString *value = weakSelf.dataArray[indexPath.section][@\"array\"][indexPath.row][@\"value\"];\n        UIPasteboard *pboard = [UIPasteboard generalPasteboard];\n        pboard.string = value;\n    }];\n    \n    return @[action0];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/AppSetting/DoraemonAppSettingPlugin.h",
    "content": "//\n//  DoraemonAppSettingPlugin.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/2/28.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonAppSettingPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/AppSetting/DoraemonAppSettingPlugin.m",
    "content": "//\n//  DoraemonAppSettingPlugin.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/2/28.\n//\n\n#import \"DoraemonAppSettingPlugin.h\"\n#import \"DoraemonUtil.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonAppSettingPlugin\n\n- (void)pluginDidLoad {\n    [DoraemonUtil openAppSetting];\n    [[DoraemonHomeWindow shareInstance] hide];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/DeleteLocalData/DoraemonDeleteLocalDataPlugin.h",
    "content": "//\n//  DoraemonDeleteLocalDataPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/22.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n\n@interface DoraemonDeleteLocalDataPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/DeleteLocalData/DoraemonDeleteLocalDataPlugin.m",
    "content": "//\n//  DoraemonDeleteLocalDataPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/22.\n//\n\n#import \"DoraemonDeleteLocalDataPlugin.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonDeleteLocalDataViewController.h\"\n\n@implementation DoraemonDeleteLocalDataPlugin\n\n- (void)pluginDidLoad{\n    DoraemonDeleteLocalDataViewController *vc = [[DoraemonDeleteLocalDataViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/DeleteLocalData/DoraemonDeleteLocalDataViewController.h",
    "content": "//\n//  DoraemonDeleteLocalDataViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/22.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonDeleteLocalDataViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/DeleteLocalData/DoraemonDeleteLocalDataViewController.m",
    "content": "//\n//  DoraemonDeleteLocalDataViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/22.\n//\n\n#import \"DoraemonDeleteLocalDataViewController.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonUtil.h\"\n#import \"DoraemonCellButton.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonDeleteLocalDataViewController ()<DoraemonCellButtonDelegate>\n\n@property (nonatomic, strong) DoraemonCellButton *cellBtn;\n\n@end\n\n@implementation DoraemonDeleteLocalDataViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"清理缓存\");\n    \n    _cellBtn = [[DoraemonCellButton alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [_cellBtn renderUIWithTitle:DoraemonLocalizedString(@\"清理缓存\")];\n    [_cellBtn renderUIWithRightContent:[self getHomeDirFileSize]];\n    _cellBtn.delegate = self;\n    [_cellBtn needDownLine];\n    [self.view addSubview:_cellBtn];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n- (void)cellBtnClick:(id)sender{\n    [self deleteFile];\n}\n\n- (void)deleteFile{\n    \n    __weak typeof(self) weakSelf = self;\n    UIAlertController * alertController = [UIAlertController alertControllerWithTitle:DoraemonLocalizedString(@\"提示\") message:DoraemonLocalizedString(@\"确定要删除本地数据\") preferredStyle:UIAlertControllerStyleAlert];\n    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"取消\") style:UIAlertActionStyleCancel handler:nil];\n    UIAlertAction *okAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"确定\") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n        [weakSelf.cellBtn renderUIWithRightContent:DoraemonLocalizedString(@\"正在清理中\")];\n        [DoraemonUtil clearLocalDatas];\n        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{\n            [weakSelf.cellBtn renderUIWithRightContent:[self getHomeDirFileSize]];\n        });\n    }];\n    [alertController addAction:cancelAction];\n    [alertController addAction:okAction];\n    [self presentViewController:alertController animated:YES completion:nil];\n}\n\n- (NSString *)getHomeDirFileSize{\n    // 获取沙盒主目录路径\n    NSString *homeDir = NSHomeDirectory();\n    \n    DoraemonUtil *util = [[DoraemonUtil alloc] init];\n    [util getFileSizeWithPath:homeDir];\n    NSInteger fileSize = util.fileSize;\n    NSString *fileSizeString = [NSByteCountFormatter stringFromByteCount:fileSize countStyle: NSByteCountFormatterCountStyleFile];\n    return fileSizeString;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/H5/DoraemonDefaultWebViewController.h",
    "content": "//\n//  DoraemonDefaultWebViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/27.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDefaultWebViewController : DoraemonBaseViewController\n\n@property (nonatomic, copy) NSString *h5Url;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/H5/DoraemonDefaultWebViewController.m",
    "content": "//\n//  DoraemonDefaultWebViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/27.\n//\n\n#import \"DoraemonDefaultWebViewController.h\"\n#import <WebKit/WebKit.h>\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonDefaultWebViewController ()\n\n/** WebView */\n@property (nonatomic, strong) WKWebView *webView;\n/** 进度条 */\n@property (nonatomic, strong) UIProgressView *progressView;\n\n@end\n\n@implementation DoraemonDefaultWebViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"Doraemon内置浏览器\");\n    \n    \n    [self.view addSubview:self.webView];\n    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.h5Url]]];\n    \n    [self.view addSubview:self.progressView];\n    [self.view bringSubviewToFront:self.progressView];\n    \n    // 监听加载进度\n    [self.webView addObserver:self forKeyPath:@\"estimatedProgress\" options:NSKeyValueObservingOptionNew context:nil];\n}\n\n- (void)dealloc {\n    [self.webView removeObserver:self forKeyPath:@\"estimatedProgress\"];\n}\n\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {\n    // 加载进度条\n    if ([@\"estimatedProgress\" isEqualToString:keyPath]) {\n        self.progressView.alpha = 1.0;\n        NSLog(@\"loding Progress: %lf\", self.webView.estimatedProgress);\n        \n        [self.progressView setProgress:self.webView.estimatedProgress animated:YES];\n        \n        if (self.webView.estimatedProgress >= 1.0) {\n            [UIView animateWithDuration:0.3f delay:0.1f options:UIViewAnimationOptionCurveEaseOut animations:^{\n                self.progressView.alpha = 0;\n            } completion:^(BOOL finished) {\n                [self.progressView setProgress:0.0 animated:NO];\n            }];\n        }\n    }\n}\n\n#pragma mark - Lazy Loading\n- (WKWebView *)webView {\n    if (!_webView) {\n        _webView = [[WKWebView alloc] initWithFrame:self.view.bounds];\n    }\n    return _webView;\n}\n\n- (UIProgressView *)progressView {\n    if (!_progressView) {\n        _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0.0, IPHONE_NAVIGATIONBAR_HEIGHT, DoraemonScreenWidth, 1.0)];\n        _progressView.tintColor = [UIColor doraemon_blue];      // 进度条颜色\n        _progressView.trackTintColor = [UIColor whiteColor];    // 进度条背景色\n    }\n    return _progressView;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/H5/DoraemonH5Plugin.h",
    "content": "//\n//  DoraemonH5Plugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/5/4.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonH5Plugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/H5/DoraemonH5Plugin.m",
    "content": "//\n//  DoraemonH5Plugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/5/4.\n//\n\n#import \"DoraemonH5Plugin.h\"\n#import \"DoraemonH5ViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonH5Plugin\n\n- (void)pluginDidLoad{\n    DoraemonH5ViewController *vc = [[DoraemonH5ViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/H5/DoraemonH5ViewController.h",
    "content": "//\n//  DoraemonH5ViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/5/4.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonH5ViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/H5/DoraemonH5ViewController.m",
    "content": "//\n//  DoraemonH5ViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/5/4.\n//\n\n#import \"DoraemonH5ViewController.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonToastUtil.h\"\n#import \"DoraemonDefine.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonDefaultWebViewController.h\"\n#import \"DoraemonManager.h\"\n#import \"DoraemonQRCodeViewController.h\"\n#import \"DoraemonCacheManager.h\"\n\n@interface DoraemonH5ViewController () <UITableViewDelegate, UITableViewDataSource>\n\n@property (nonatomic, strong) UITextView *h5UrlTextView;\n@property (nonatomic, strong) UIView *lineView;\n@property (nonatomic, strong) UIButton *jumpBtn;\n/// 扫码跳转\n@property (nonatomic, strong) UIButton *scanJumpBtn;\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, strong) NSArray *dataSource;\n@end\n\n@implementation DoraemonH5ViewController\n\n#pragma mark - Life Cycle Methods\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"H5任意门\");\n    \n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        self.view.backgroundColor = [UIColor systemBackgroundColor];\n    } else {\n#endif\n        self.view.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n    \n    _h5UrlTextView = [[UITextView alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom + kDoraemonSizeFrom750_Landscape(32), self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(358))];\n    _h5UrlTextView.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n    [self.view addSubview:_h5UrlTextView];\n    _h5UrlTextView.keyboardType = UIKeyboardTypeURL;\n    _h5UrlTextView.autocorrectionType = UITextAutocorrectionTypeNo;\n    _h5UrlTextView.keyboardAppearance = UIKeyboardAppearanceDark;\n    _h5UrlTextView.autocapitalizationType = UITextAutocapitalizationTypeNone;\n//    _h5UrlTextView.backgroundColor = [UIColor purpleColor];\n    _lineView = [[UIView alloc] initWithFrame:CGRectMake(0, _h5UrlTextView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(1))];\n    _lineView.backgroundColor = [UIColor doraemon_line];\n    [self.view addSubview:_lineView];\n    \n    _jumpBtn = [[UIButton alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(30), self.view.doraemon_height-kDoraemonSizeFrom750_Landscape(30 + 100), self.view.doraemon_width-2*kDoraemonSizeFrom750_Landscape(30), kDoraemonSizeFrom750_Landscape(100))];\n    _jumpBtn.backgroundColor = [UIColor doraemon_colorWithHexString:@\"#337CC4\"];\n    [_jumpBtn setTitle:DoraemonLocalizedString(@\"点击跳转\") forState:UIControlStateNormal];\n    [_jumpBtn addTarget:self action:@selector(jump) forControlEvents:UIControlEventTouchUpInside];\n    _jumpBtn.layer.cornerRadius = kDoraemonSizeFrom750_Landscape(8);\n    [self.view addSubview:_jumpBtn];\n    \n    self.scanJumpBtn.frame = CGRectMake(self.view.doraemon_width - kDoraemonSizeFrom750_Landscape(38.6 + 33.2), _lineView.doraemon_top - kDoraemonSizeFrom750_Landscape(38.6 + 33.2), kDoraemonSizeFrom750_Landscape(38.6), kDoraemonSizeFrom750_Landscape(38.6));\n    \n    self.tableView.frame = CGRectMake(0, _lineView.doraemon_bottom + kDoraemonSizeFrom750_Landscape(32), self.view.doraemon_width, _jumpBtn.doraemon_top - _lineView.doraemon_bottom - kDoraemonSizeFrom750_Landscape(32));\n    \n    [self.view bringSubviewToFront:_jumpBtn];\n    \n    // 监听键盘的显示和隐藏\n    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];\n    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];\n}\n\n- (void)viewWillAppear:(BOOL)animated {\n    [super viewWillAppear:animated];\n    self.dataSource = [[DoraemonCacheManager sharedInstance] h5historicalRecord];\n    [self.tableView reloadData];\n}\n\n- (void)dealloc {\n    [[NSNotificationCenter defaultCenter] removeObserver:self];\n}\n\n#pragma mark - Override Methods\n- (BOOL)needBigTitleView {\n    return YES;\n}\n\n#pragma mark - Target Methods\n- (void)clickScan {\n    if ([DoraemonAppInfoUtil isSimulator]) {\n        [DoraemonToastUtil showToastBlack:DoraemonLocalizedString(@\"模拟器不支持扫码功能\") inView:self.view];\n        return;\n    }\n    \n    DoraemonQRCodeViewController *vc = [[DoraemonQRCodeViewController alloc] init];\n    __weak typeof(self) weakSelf = self;\n    vc.QRCodeBlock = ^(NSString * _Nonnull QRCodeResult) {\n        weakSelf.h5UrlTextView.text = QRCodeResult;\n        [weakSelf jump];\n    };\n    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];\n    nav.modalPresentationStyle = UIModalPresentationFullScreen;\n    [self presentViewController:nav animated:YES completion:nil];\n}\n\n- (void)jump {\n    if (_h5UrlTextView.text.length == 0) {\n        [DoraemonToastUtil showToastBlack:DoraemonLocalizedString(@\"链接不能为空\") inView:self.view];\n        return;\n    }\n    \n    if (![NSURL URLWithString:_h5UrlTextView.text]) {\n        [DoraemonToastUtil showToastBlack:DoraemonLocalizedString(@\"h5链接有误\") inView:self.view];\n        return;\n    }\n    \n    NSString *h5Url = _h5UrlTextView.text;\n    [[DoraemonCacheManager sharedInstance] saveH5historicalRecordWithText:h5Url];\n    if ([DoraemonManager shareInstance].h5DoorBlock) {\n        [self leftNavBackClick:nil];\n        [DoraemonManager shareInstance].h5DoorBlock(h5Url);\n    } else {\n        DoraemonDefaultWebViewController *vc = [[DoraemonDefaultWebViewController alloc] init];\n        vc.h5Url = [self urlCorrectionWithURL:h5Url];\n        [self.navigationController pushViewController:vc animated:YES];\n    }\n}\n\n#pragma mark - NSNotification\n// 当键盘出现或改变时调用（调整view位置，适应键盘高度，即：让view在键盘上）\n- (void)keyboardWillShow:(NSNotification *)aNotification {\n    // 获取键盘的高度\n    NSDictionary *userInfo = [aNotification userInfo];\n    NSValue *aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];\n    CGRect keyboardRect = [aValue CGRectValue];\n    CGFloat height = keyboardRect.size.height;\n    \n    CGRect frame = self.jumpBtn.frame;\n\n    CGFloat offset = height - (DoraemonScreenHeight - CGRectGetMaxY(frame));\n\n    CGFloat duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];\n    [UIView animateWithDuration:duration animations:^{\n        if (offset > 0) {\n            self.jumpBtn.doraemon_y = self.jumpBtn.doraemon_y - offset;\n            [self.view layoutIfNeeded];\n        }\n    }];\n}\n\n// 当键退出时调用\n- (void)keyboardWillHide:(NSNotification *)aNotification {\n    NSDictionary *userInfo = [aNotification userInfo];\n    CGFloat duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];\n    [UIView animateWithDuration:duration animations:^{\n        self.jumpBtn.doraemon_y = self.view.doraemon_height - kDoraemonSizeFrom750_Landscape(30 + 100);\n        [self.view layoutIfNeeded];\n    }];\n}\n\n#pragma mark - tableView\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return self.dataSource.count;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@\"cell\"];\n\n    if (self.dataSource.count > indexPath.row) {\n        cell.textLabel.text = self.dataSource[indexPath.row];\n    } else {\n        cell.textLabel.text = @\"default value\";\n    }\n    cell.textLabel.textColor = [UIColor doraemon_colorWithHex:0x333333 andAlpha:1];\n    cell.textLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(30)];\n    cell.imageView.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_search\"];\n    return cell;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    if (self.dataSource.count > indexPath.row) {\n        _h5UrlTextView.text = self.dataSource[indexPath.row];\n        [self jump];\n    }\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {\n    return kDoraemonSizeFrom750_Landscape(40 + 33);\n}\n\n- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {\n    UIView *footerView = [[UIView alloc] init];\n    footerView.frame = CGRectMake(0, 0, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(40 + 33));\n//    footerView.backgroundColor = [UIColor redColor];\n    \n    UIButton *clearButton = [UIButton buttonWithType:(UIButtonTypeCustom)];\n    clearButton.frame = CGRectMake((self.view.doraemon_width - kDoraemonSizeFrom750_Landscape(300))/2, kDoraemonSizeFrom750_Landscape(40), kDoraemonSizeFrom750_Landscape(300), kDoraemonSizeFrom750_Landscape(33));\n//    clearButton.backgroundColor = [UIColor orangeColor];\n    [clearButton setTitle:DoraemonLocalizedString(@\"清除搜索历史\") forState:(UIControlStateNormal)];\n    [clearButton setTitleColor:[UIColor doraemon_colorWithHex:0x999999 andAlpha:1] forState:UIControlStateNormal];\n    clearButton.titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n    [clearButton addTarget:self action:@selector(clearRecord) forControlEvents:(UIControlEventTouchUpInside)];\n    [footerView addSubview:clearButton];\n    \n    return footerView;\n}\n\n- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{\n    return YES;\n}\n\n- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath{\n    return DoraemonLocalizedString(@\"删除\");\n}\n\n- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {\n    [[DoraemonCacheManager sharedInstance] clearH5historicalRecordWithText:self.dataSource[indexPath.row]];\n    self.dataSource = [[DoraemonCacheManager sharedInstance] h5historicalRecord];\n    [self.tableView reloadData];\n}\n\n\n/// 清除记录\n- (void)clearRecord {\n    [[DoraemonCacheManager sharedInstance] clearAllH5historicalRecord];\n    self.dataSource = [[DoraemonCacheManager sharedInstance] h5historicalRecord];\n    [self.tableView reloadData];\n}\n\n/// 地址修正\n- (NSString *)urlCorrectionWithURL:(NSString *)URL {\n    if (!URL || URL.length <= 0) { return URL; }\n    \n    if (![URL hasPrefix:@\"http://\"] && ![URL hasPrefix:@\"https://\"]) {\n        return [NSString stringWithFormat:@\"https://%@\",URL];\n    }\n    \n    if ([URL hasPrefix:@\":\"]) {\n        return [NSString stringWithFormat:@\"https%@\",URL];\n    }\n    \n    if ([URL hasPrefix:@\"//\"]) {\n        return [NSString stringWithFormat:@\"https:%@\",URL];\n    }\n    \n    if ([URL hasPrefix:@\"/\"]) {\n        return [NSString stringWithFormat:@\"https:/%@\",URL];\n    }\n    \n    return URL;\n}\n\n#pragma mark - Lazy Loads\n- (UITableView *)tableView {\n    if (!_tableView) {\n        _tableView = [[UITableView alloc] init];\n//        _tableView.backgroundColor = [UIColor orangeColor];\n        _tableView.delegate = self;\n        _tableView.dataSource = self;\n        [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@\"cell\"];\n//        _tableView.tableFooterView = [UIView new];\n        [self.view addSubview:_tableView];\n    }\n    return _tableView;\n}\n\n- (UIButton *)scanJumpBtn {\n    if (!_scanJumpBtn) {\n        _scanJumpBtn = [UIButton buttonWithType:(UIButtonTypeCustom)];\n        [_scanJumpBtn setBackgroundImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_scan\"] forState:(UIControlStateNormal)];\n        [_scanJumpBtn addTarget:self action:@selector(clickScan) forControlEvents:UIControlEventTouchUpInside];\n        [self.view addSubview:_scanJumpBtn];\n    }\n    return _scanJumpBtn;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/H5/DoraemonQRCodeViewController.h",
    "content": "//\n//  DoraemonQRCodeViewController.h\n//  DoraemonKit\n//\n//  Created by love on 2019/5/22.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonQRCodeViewController : DoraemonBaseViewController\n@property (nonatomic, copy) void(^QRCodeBlock)(NSString *QRCodeResult);\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/H5/DoraemonQRCodeViewController.m",
    "content": "//\n//  DoraemonQRCodeViewController.m\n//  DoraemonKit\n//\n//  Created by love on 2019/5/22.\n//\n\n#import \"DoraemonQRCodeViewController.h\"\n#import \"DoraemonDefaultWebViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonQRScanView.h\"\n\n\n@interface DoraemonQRCodeViewController ()<DoraemonQRScanDelegate>\n\n@property (nonatomic, strong) DoraemonQRScanView *scanView;\n\n@end\n@implementation DoraemonQRCodeViewController\n\n- (void)leftNavBackClick:(id)clickView {\n    [self dismissViewControllerAnimated:YES completion:nil];\n}\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        self.view.backgroundColor = [UIColor systemBackgroundColor];\n    } else {\n#endif\n       self.view.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n    self.title = DoraemonLocalizedString(@\"扫描二维码\");\n    \n}\n\n- (void)viewWillAppear:(BOOL)animated{\n    [super viewWillAppear:animated];\n    \n    DoraemonQRScanView *scaner = [[DoraemonQRScanView alloc] initWithFrame:self.view.bounds];\n    scaner.delegate = self;\n    scaner.showScanLine = YES;\n    scaner.showBorderLine = YES;\n    scaner.showCornerLine = YES;\n    scaner.scanRect = CGRectMake(scaner.doraemon_width/2-kDoraemonSizeFrom750(480)/2, kDoraemonSizeFrom750(195), kDoraemonSizeFrom750(480), kDoraemonSizeFrom750(480));\n    [self.view addSubview:scaner];\n    self.scanView = scaner;\n    [scaner startScanning];\n}\n\n- (void)viewWillDisappear:(BOOL)animated{\n    [super viewWillDisappear:animated];\n    [self removeScanView];\n}\n\n- (void)removeScanView{\n    if (self.scanView) {\n        [self.scanView stopScanning];\n        [self.scanView removeFromSuperview];\n        self.scanView = nil;\n    }\n}\n\n\n#pragma mark -- DoraemonQRScanDelegate\n- (void)scanView:(DoraemonQRScanView *)scanView pickUpMessage:(NSString *)message{\n    if(message.length>0){\n        [self dismissViewControllerAnimated:YES completion:^{\n            if (self.QRCodeBlock) {\n                self.QRCodeBlock(message);\n            }\n        }];\n    }\n}\n\n- (void)scanView:(DoraemonQRScanView *)scanView aroundBrightness:(NSString *)brightnessValue{\n    \n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/H5/QRCode/DoraemonQRScanView.h",
    "content": "//\n//  DoraemonQRScanView.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/3/5.\n//\n\n#import <UIKit/UIKit.h>\n@class DoraemonQRScanView;\n\nNS_ASSUME_NONNULL_BEGIN\n\n@protocol DoraemonQRScanDelegate<NSObject>\n@required\n\n/**\n 扫描有结果回调方法，如需结束在回调里调用stopScanning方法\n\n @param scanView 回调的View\n @param message 结果字符串\n */\n- (void)scanView:(DoraemonQRScanView *)scanView pickUpMessage:(NSString *)message;\n\n/**\n 获取周围光线强弱\n \n @param scanView 回调的view\n @param brightnessValue 光线数值，越小越暗\n */\n- (void)scanView:(DoraemonQRScanView *)scanView aroundBrightness:(NSString *)brightnessValue;\n\n@end\n\n@interface DoraemonQRScanView : UIView\n\n@property (nonatomic, weak) id<DoraemonQRScanDelegate> delegate;\n\n/**\n 扫描区域的Frame，默认长宽为Frame的宽度3/4，位置为Frame中心\n */\n@property (nonatomic, assign) CGRect scanRect;\n\n//首次申请相机权限-系统弹框点击不允许\n@property(nonatomic,copy) void(^forbidCameraAuth)(void);\n//相机权限弹框-点击暂不开启\n@property(nonatomic,copy) void(^unopenCameraAuth)(void);\n\n/**\n 第一次调用会初始化相机相关并开始扫描\n 之后调用，可在暂停后恢复\n */\n- (void)startScanning;\n\n/**\n 暂停扫描\n */\n- (void)stopScanning;\n\n/**\n 可自定义的蒙版View，可在上面添加自定义控件,也可以改变背景颜色，透明度\n 默认为50%透明度黑色，遮盖区域依赖scanRect,需先指定scanRect，否则为默认\n */\n@property (nonatomic, strong) UIView *coverView;\n\n/**\n 上下移动的扫描线的颜色，默认为橙色\n */\n@property (nonatomic, strong) UIColor *scanLineColor;\n\n/**\n 四角的线的颜色，默认为橙色\n */\n@property (nonatomic, strong) UIColor *cornerLineColor;\n\n/**\n 扫描边框的颜色，默认为白色\n */\n@property (nonatomic, strong) UIColor *borderLineColor;\n\n/**\n 是否显示上下移动的扫描线，默认为YES\n */\n@property (nonatomic, assign, getter=isShowScanLine) BOOL showScanLine;\n\n/**\n 是否显示边框，默认为NO\n */\n@property (nonatomic, assign, getter=isShowBorderLine) BOOL showBorderLine;\n\n/**\n 是否显示四角，默认为YES\n */\n@property (nonatomic, assign, getter=isShowCornerLine) BOOL showCornerLine;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/H5/QRCode/DoraemonQRScanView.m",
    "content": "//\n//  DoraemonQRScanView.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/3/5.\n//\n\n#import \"DoraemonQRScanView.h\"\n#import <AVFoundation/AVFoundation.h>\n#import <ImageIO/ImageIO.h>\n#import \"DoraemonDefine.h\"\n\nstatic CGFloat scanTime = 3.0;\nstatic CGFloat borderLineWidth = 0.5;\nstatic CGFloat cornerLineWidth = 1.5;\nstatic CGFloat scanLineWidth = 42;\nstatic NSString *const scanLineAnimationName = @\"scanLineAnimation\";\n\n@interface DoraemonQRScanView () <AVCaptureMetadataOutputObjectsDelegate, AVCaptureVideoDataOutputSampleBufferDelegate>\n@property (nonatomic, strong) AVCaptureDevice *device;\n@property (nonatomic, strong) AVCaptureDeviceInput *deviceInput;\n@property (nonatomic, strong) AVCaptureMetadataOutput *dataOutput;\n@property (nonatomic, strong) AVCaptureSession *session;\n@property (nonatomic, strong) UIView *middleView;\n@property (nonatomic, strong) UIView *scanLine;\n@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer;\n@property (nonatomic, strong) AVCaptureVideoDataOutput *videoDataOutput;\n@end\n\n@implementation DoraemonQRScanView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    if (self = [super initWithFrame:frame]) {\n        self.backgroundColor = [UIColor blackColor];\n        _showScanLine = YES;\n        _showCornerLine = YES;\n        _showBorderLine = NO;\n    }\n    return self;\n}\n\n- (instancetype)init{\n    NSAssert(NO, @\"Please use -[ONEQRScanView initWithFrame:] to init\");\n    return [self initWithFrame:CGRectZero];\n}\n\n- (void)configCameraAndStart{\n    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{\n        self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];\n        NSError *error;\n        self.deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:&error];\n        if (error) {\n            NSLog(@\"%@\",error);\n        }\n        self.dataOutput = [[AVCaptureMetadataOutput alloc] init];\n        [self.dataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];\n\n        self.session = [[AVCaptureSession alloc] init];\n        if ([self.device supportsAVCaptureSessionPreset:AVCaptureSessionPreset1920x1080]) {\n            [self.session setSessionPreset:AVCaptureSessionPreset1920x1080];\n        }\n        else{\n            [self.session setSessionPreset:AVCaptureSessionPresetHigh];\n        }\n        if ([self.session canAddInput:self.deviceInput]){\n            [self.session addInput:self.deviceInput];\n        }\n        if ([self.session canAddOutput:self.dataOutput]){\n            [self.session addOutput:self.dataOutput];\n        }\n        \n        if ([self.dataOutput.availableMetadataObjectTypes containsObject:AVMetadataObjectTypeQRCode]) {\n            self.dataOutput.metadataObjectTypes = @[AVMetadataObjectTypeQRCode];\n        }\n     \n        // 获取光线强弱\n        self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];\n        [self.videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];\n        \n        if ([self.session canAddOutput:self.videoDataOutput]) {\n            [self.session addOutput:self.videoDataOutput];\n        }\n        \n        dispatch_async(dispatch_get_main_queue(), ^{\n            self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.session];\n            self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;\n            self.previewLayer.frame = self.frame;\n            [self.layer insertSublayer:self.previewLayer atIndex:0];\n            [self.session startRunning];\n             self.dataOutput.rectOfInterest = [self.previewLayer metadataOutputRectOfInterestForRect:self.scanRect];\n            [self showScanLine:self.isShowScanLine];\n        });\n    });\n}\n\n- (void)showScanLine:(BOOL)showScanLine{\n    if (showScanLine) {\n        [self addScanLineAnimation];\n    }\n    else{\n        [self removeScanLineAnimation];\n    }\n}\n\n- (void)startScanning{\n    if (![self statusCheck]) {\n        return;\n    }\n    if (!self.session) {\n        [self setupViews];\n        [self configCameraAndStart];\n        return;\n    }\n    if (self.session.isRunning){\n        return;\n    }\n    [self.session startRunning];\n    [self showScanLine:self.isShowScanLine];\n}\n\n- (void)stopScanning{\n    if (!self.session.isRunning){\n        return;\n    }\n    [self.session stopRunning];\n    // 自动开启手电筒后，在执行了stopRunning时系统会关闭手电筒，这时重新打开手电筒，效果会闪一下，PM宫赫可接受，观察几个版本\n    if (self.device.torchMode == AVCaptureTorchModeOn) {\n        [self.device lockForConfiguration:nil];\n        [self.device setTorchMode:AVCaptureTorchModeOn];\n        [self.device unlockForConfiguration];\n    }\n    [self showScanLine:NO];\n}\n\n\n- (BOOL)statusCheck{\n    if (![DoraemonQRScanView isCameraAvailable]){\n        [DoraemonAlertUtil handleAlertActionWithVC:[self doraemon_viewController] text:DoraemonLocalizedString(@\"设备无相机——设备无相机功能，无法进行扫描\")  okBlock:^{\n            \n        }];\n        return NO;\n    }\n    \n    if (![DoraemonQRScanView isRearCameraAvailable] && ![DoraemonQRScanView isFrontCameraAvailable]) {\n        [DoraemonAlertUtil handleAlertActionWithVC:[self doraemon_viewController] text:DoraemonLocalizedString(@\"设备相机错误——无法启用相机，请检查\")  okBlock:^{\n            \n        }];\n        return NO;\n    }\n    \n    if (![self isCameraAuthStatusCorrect]) {\n        [DoraemonAlertUtil handleAlertActionWithVC:[self doraemon_viewController] text:DoraemonLocalizedString(@\"相机权限未开启，请到「设置-隐私-相机」中允许DoKit访问您的相机\")  okBlock:^{\n            [DoraemonUtil openAppSetting];\n        } cancleBlock:^{\n            if(self.unopenCameraAuth){\n                self.unopenCameraAuth();\n            }\n        }];\n        return NO;\n    }\n    return YES;\n}\n\n- (void)setupViews{\n    [self addSubview:self.middleView];\n    [self addSubview:self.coverView];\n    [self.middleView addSubview:self.scanLine];\n    if (self.isShowCornerLine) {\n        [self addCornerLines];\n    }\n    if (self.isShowBorderLine){\n        [self addScanBorderLine];\n    }\n}\n\n+ (BOOL)isCameraAvailable{\n    return [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];\n}\n\n+ (BOOL)isFrontCameraAvailable{\n    return [UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront];\n}\n\n+ (BOOL)isRearCameraAvailable{\n    return [UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear];\n}\n\n- (BOOL)isCameraAuthStatusCorrect{\n    AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];\n    if (authStatus == AVAuthorizationStatusAuthorized) {\n        return YES;\n    }\n    else if (authStatus == AVAuthorizationStatusNotDetermined) {\n        [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {\n            if (NO == granted) {\n                dispatch_async(dispatch_get_main_queue(), ^{\n                    if(self.forbidCameraAuth){\n                        self.forbidCameraAuth();\n                    }\n                });\n            }\n        }];\n        return YES;\n    }\n    return NO;\n}\n\n- (void)addScanLineAnimation{\n    self.scanLine.hidden = NO;\n    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@\"transform.translation.y\"];\n    animation.fromValue = @(- scanLineWidth);\n    animation.toValue = @(self.scanRect.size.height - scanLineWidth);\n    animation.duration = scanTime;\n    animation.repeatCount = OPEN_MAX;\n    animation.fillMode = kCAFillModeForwards;\n    animation.removedOnCompletion = NO;\n    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];\n    [self.scanLine.layer addAnimation:animation forKey:scanLineAnimationName];\n}\n\n\n- (void)removeScanLineAnimation{\n    [self.scanLine.layer removeAnimationForKey:scanLineAnimationName];\n    self.scanLine.hidden = YES;\n}\n\n\n#pragma mark - bezierPath\n\n- (void)addScanBorderLine{\n    CGRect borderRect = CGRectMake(self.scanRect.origin.x + borderLineWidth, self.scanRect.origin.y + borderLineWidth, self.scanRect.size.width - 2*borderLineWidth, self.scanRect.size.height - 2*borderLineWidth);\n    UIBezierPath *scanBezierPath = [UIBezierPath bezierPathWithRect:borderRect];\n    CAShapeLayer *lineLayer = [CAShapeLayer layer];\n    lineLayer.path = scanBezierPath.CGPath;\n    lineLayer.lineWidth = borderLineWidth;\n    lineLayer.strokeColor = self.borderLineColor.CGColor;\n    lineLayer.fillColor = [UIColor clearColor].CGColor;\n    [self.layer addSublayer:lineLayer];\n}\n\n- (void)addCornerLines{\n    CAShapeLayer *lineLayer = [CAShapeLayer layer];\n    lineLayer.lineWidth = cornerLineWidth;\n    lineLayer.strokeColor = self.cornerLineColor.CGColor;\n    lineLayer.fillColor = [UIColor clearColor].CGColor;\n    CGFloat halfLineLong = self.scanRect.size.width / 12;\n    UIBezierPath *lineBezierPath = [UIBezierPath bezierPath];\n    \n    CGFloat spacing = cornerLineWidth/2;\n    \n    CGPoint leftUpPoint = (CGPoint){self.scanRect.origin.x + spacing ,self.scanRect.origin.y + spacing};\n    [lineBezierPath moveToPoint:(CGPoint){leftUpPoint.x,leftUpPoint.y + halfLineLong}];\n    [lineBezierPath addLineToPoint:leftUpPoint];\n    [lineBezierPath addLineToPoint:(CGPoint){leftUpPoint.x + halfLineLong,leftUpPoint.y}];\n    lineLayer.path = lineBezierPath.CGPath;\n    [self.layer addSublayer:lineLayer];\n    \n\n    CGPoint leftDownPoint = (CGPoint){self.scanRect.origin.x + spacing,self.scanRect.origin.y + self.scanRect.size.height - spacing};\n    [lineBezierPath moveToPoint:(CGPoint){leftDownPoint.x,leftDownPoint.y - halfLineLong}];\n    [lineBezierPath addLineToPoint:leftDownPoint];\n    [lineBezierPath addLineToPoint:(CGPoint){leftDownPoint.x + halfLineLong,leftDownPoint.y}];\n    lineLayer.path = lineBezierPath.CGPath;\n    [self.layer addSublayer:lineLayer];\n    \n    CGPoint rightUpPoint = (CGPoint){self.scanRect.origin.x + self.scanRect.size.width - spacing,self.scanRect.origin.y + spacing};\n    [lineBezierPath moveToPoint:(CGPoint){rightUpPoint.x - halfLineLong,rightUpPoint.y}];\n    [lineBezierPath addLineToPoint:rightUpPoint];\n    [lineBezierPath addLineToPoint:(CGPoint){rightUpPoint.x,rightUpPoint.y + halfLineLong}];\n    lineLayer.path = lineBezierPath.CGPath;\n    [self.layer addSublayer:lineLayer];\n    \n    CGPoint rightDownPoint = (CGPoint){self.scanRect.origin.x + self.scanRect.size.width - spacing,self.scanRect.origin.y + self.scanRect.size.height - spacing};\n    [lineBezierPath moveToPoint:(CGPoint){rightDownPoint.x - halfLineLong,rightDownPoint.y}];\n    [lineBezierPath addLineToPoint:rightDownPoint];\n    [lineBezierPath addLineToPoint:(CGPoint){rightDownPoint.x,rightDownPoint.y - halfLineLong}];\n    lineLayer.path = lineBezierPath.CGPath;\n    [self.layer addSublayer:lineLayer];\n}\n\n#pragma mark - AVCaptureMetadataOutputObjectsDelegate\n\n- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray<AVMetadataMachineReadableCodeObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection{\n    if (metadataObjects.count == 0) {\n        return;\n    }\n    \n    // 停止扫描\n    [self stopScanning];\n    \n    NSString *result = [metadataObjects.firstObject stringValue];\n    if ([self.delegate respondsToSelector:@selector(scanView:pickUpMessage:)]) {\n        [self.delegate scanView:self pickUpMessage:result];\n    }\n}\n\n#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate\n\n- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {\n    CFDictionaryRef metadataDict = CMCopyDictionaryOfAttachments(NULL,sampleBuffer, kCMAttachmentMode_ShouldPropagate);\n    NSDictionary *metadata = [[NSMutableDictionary alloc] initWithDictionary:(__bridge NSDictionary*)metadataDict];\n    CFRelease(metadataDict);\n    NSDictionary *exifMetadata = [[metadata objectForKey:(NSString *)kCGImagePropertyExifDictionary] mutableCopy];\n    NSString *brightnessValue = [[exifMetadata objectForKey:(NSString *)kCGImagePropertyExifBrightnessValue] stringValue];\n    if ([self.delegate respondsToSelector:@selector(scanView:aroundBrightness:)]) {\n        [self.delegate scanView:self aroundBrightness:brightnessValue];\n    }\n}\n\n#pragma mark - getter&setter\n\n- (CGRect)scanRect{\n    if (CGRectIsEmpty(_scanRect)) {\n        CGSize scanSize = CGSizeMake(self.frame.size.width * 3/4, self.frame.size.width * 3/4);\n        _scanRect = CGRectMake((self.frame.size.width - scanSize.width)/2, (self.frame.size.height - scanSize.height)/2, scanSize.width, scanSize.height);\n    }\n    return _scanRect;\n}\n\n\n- (UIView *)middleView{\n    if (!_middleView) {\n        _middleView = [[UIView alloc]initWithFrame:self.scanRect];\n        _middleView.clipsToBounds = YES;\n    }\n    return _middleView;\n}\n\n- (UIView *)coverView{\n    if (!_coverView) {\n        _coverView = [[UIView alloc]initWithFrame:self.bounds];\n        _coverView.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5];\n        UIBezierPath *fullBezierPath = [UIBezierPath bezierPathWithRect:self.bounds];\n        UIBezierPath *scanBezierPath = [UIBezierPath bezierPathWithRect:self.scanRect];\n        [fullBezierPath appendPath:[scanBezierPath  bezierPathByReversingPath]];\n        CAShapeLayer *shapeLayer = [CAShapeLayer layer];\n        shapeLayer.path = fullBezierPath.CGPath;\n        _coverView.layer.mask = shapeLayer;\n    }\n    return _coverView;\n}\n\n- (UIView *)scanLine{\n    if (!_scanLine) {\n        _scanLine = [[UIView alloc]initWithFrame:CGRectMake(0,0,self.scanRect.size.width, scanLineWidth)];\n        _scanLine.hidden = YES;\n        CAGradientLayer *gradient = [CAGradientLayer layer];\n        gradient.startPoint = CGPointMake(0.5, 0);\n        gradient.endPoint = CGPointMake(0.5, 1);\n        gradient.frame = _scanLine.layer.bounds;\n        gradient.colors = @[(__bridge id)[self.scanLineColor colorWithAlphaComponent:0].CGColor,(__bridge id)[self.scanLineColor colorWithAlphaComponent:0.4f].CGColor,(__bridge id)self.scanLineColor.CGColor];\n        gradient.locations = @[@0,@0.96,@0.97];\n        [_scanLine.layer addSublayer:gradient];\n    }\n    return _scanLine;\n}\n\n\n- (UIColor *)cornerLineColor{\n    if (!_cornerLineColor) {\n        _cornerLineColor = [UIColor doraemon_orange];\n    }\n    return _cornerLineColor;\n}\n\n- (UIColor *)borderLineColor{\n    if (!_borderLineColor) {\n        _borderLineColor = [UIColor whiteColor];\n    }\n    return _borderLineColor;\n}\n\n- (UIColor *)scanLineColor{\n    if (!_scanLineColor) {\n        _scanLineColor = [UIColor doraemon_orange];\n    }\n    return _scanLineColor;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/JavaScript/DoraemonJavaScriptDetailViewController.h",
    "content": "//\n//  DoraemonJavaScriptDetailViewController.h\n//  DoraemonKit\n//\n//  Created by carefree on 2022/5/11.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonJavaScriptDetailViewController : DoraemonBaseViewController\n\n@property (nonatomic, copy) NSString    *key;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/JavaScript/DoraemonJavaScriptDetailViewController.m",
    "content": "//\n//  DoraemonJavaScriptDetailViewController.m\n//  DoraemonKit\n//\n//  Created by carefree on 2022/5/11.\n//\n\n#import \"DoraemonJavaScriptDetailViewController.h\"\n#import \"DoraemonKit.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonToastUtil.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonJavaScriptManager.h\"\n\n@interface DoraemonJavaScriptDetailViewController ()\n\n@property (nonatomic, weak) UITextView  *textView;\n\n@end\n\n@implementation DoraemonJavaScriptDetailViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    self.title = DoraemonLocalizedString(@\"脚本执行\");\n    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPlay target:self action:@selector(runScript)];\n    UIEdgeInsets edge = UIEdgeInsetsMake(10, 10, 0, 10);\n    CGFloat width = self.view.bounds.size.width - edge.left - edge.right;\n    CGFloat height = self.view.bounds.size.height - edge.top - edge.bottom;\n    UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(edge.left, edge.top + IPHONE_NAVIGATIONBAR_HEIGHT, width, 30)];\n    titleLabel.text = DoraemonLocalizedString(@\"JS代码\");\n    \n    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(edge.left, CGRectGetMaxY(titleLabel.frame) + edge.top, width, height - 200)];\n    textView.layer.borderWidth = 1 / UIScreen.mainScreen.scale;\n    textView.layer.borderColor = [[UIColor lightGrayColor] CGColor];\n    textView.layer.cornerRadius = 6;\n    textView.font = [UIFont systemFontOfSize:16];\n    textView.textContainerInset = UIEdgeInsetsMake(8, 3, 8, 3);\n    \n    [self.view addSubview:titleLabel];\n    [self.view addSubview:textView];\n    self.textView = textView;\n    \n    if (self.key.length > 0) {\n        self.textView.text = [DoraemonCacheManager.sharedInstance jsHistoricalRecordForKey:self.key];\n    }\n}\n\n#pragma mark - Private\n- (void)runScript {\n    NSString *value = self.textView.text;\n    if (value.length == 0) {\n        [DoraemonToastUtil showToastBlack:@\"脚本不能为空\" inView:self.view];\n        return;\n    }\n    [DoraemonCacheManager.sharedInstance saveJsHistoricalRecordWithText:value forKey:self.key];\n    [DoraemonManager.shareInstance hiddenHomeWindow];\n    [DoraemonJavaScriptManager.shareInstance evalJavaScript:value];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/JavaScript/DoraemonJavaScriptPlugin.h",
    "content": "//\n//  DoraemonJavaScriptPlugin.h\n//  AFNetworking\n//\n//  Created by carefree on 2022/5/11.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonJavaScriptPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/JavaScript/DoraemonJavaScriptPlugin.m",
    "content": "//\n//  DoraemonJavaScriptPlugin.m\n//  AFNetworking\n//\n//  Created by carefree on 2022/5/11.\n//\n\n#import \"DoraemonJavaScriptPlugin.h\"\n#import \"DoraemonJavaScriptManager.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonJavaScriptPlugin\n\n- (void)pluginDidLoad {\n    [[DoraemonHomeWindow shareInstance] hide];\n    [[DoraemonJavaScriptManager shareInstance] show];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/JavaScript/DoraemonJavaScriptViewController.h",
    "content": "//\n//  DoraemonJavaScriptViewController.h\n//  DoraemonKit\n//\n//  Created by carefree on 2022/5/11.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonJavaScriptViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/JavaScript/DoraemonJavaScriptViewController.m",
    "content": "//\n//  DoraemonJavaScriptViewController.m\n//  DoraemonKit\n//\n//  Created by carefree on 2022/5/11.\n//\n\n#import \"DoraemonJavaScriptViewController.h\"\n#import \"DoraemonJavaScriptDetailViewController.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonJavaScriptViewController ()<UITableViewDelegate, UITableViewDataSource>\n\n@property (nonatomic, weak) UITableView *tableView;\n@property (nonatomic, strong) NSMutableArray *items;\n\n@end\n\n@implementation DoraemonJavaScriptViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"脚本列表\");\n    \n    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addNewScript)];\n    self.items = [NSMutableArray array];\n    \n    UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];\n    tableView.delegate = self;\n    tableView.dataSource = self;\n    tableView.rowHeight = 100;\n    [self.view addSubview:tableView];\n    self.tableView = tableView;\n}\n\n- (void)viewWillAppear:(BOOL)animated {\n    [super viewWillAppear:animated];\n    [self loadScriptData];\n    [self.tableView reloadData];\n}\n\n#pragma mark - UITableViewDataSource\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return self.items.count;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@\"cell\"];\n    if (!cell) {\n        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@\"cell\"];\n    }\n    cell.textLabel.text = [self.items[indexPath.row] objectForKey:@\"value\"];\n    cell.textLabel.numberOfLines = 4;\n    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;\n    return cell;\n}\n\n#pragma mark - UITableViewDelegate\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    [tableView deselectRowAtIndexPath:indexPath animated:YES];\n    DoraemonJavaScriptDetailViewController *detailVC = [[DoraemonJavaScriptDetailViewController alloc] init];\n    detailVC.key = [self.items[indexPath.row] objectForKey:@\"key\"];\n    [self.navigationController pushViewController:detailVC animated:YES];\n}\n\n- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return UITableViewCellEditingStyleDelete;\n}\n\n- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {\n    if (editingStyle == UITableViewCellEditingStyleDelete) {\n        NSString *key = [self.items[indexPath.row] objectForKey:@\"key\"];\n        [DoraemonCacheManager.sharedInstance clearJsHistoricalRecordWithKey:key];\n        [self loadScriptData];\n        [self.tableView reloadData];\n    }\n}\n\n#pragma mark - Private\n- (void)addNewScript {\n    DoraemonJavaScriptDetailViewController *detailVC = [[DoraemonJavaScriptDetailViewController alloc] init];\n    [self.navigationController pushViewController:detailVC animated:YES];\n}\n\n- (void)loadScriptData {\n    [self.items removeAllObjects];\n    //读取历史数据\n    NSArray *scriptItems = [DoraemonCacheManager.sharedInstance jsHistoricalRecord];\n    if (!scriptItems) {\n        //添加内置脚本\n        [DoraemonCacheManager.sharedInstance saveJsHistoricalRecordWithText:@\"//安装vConsole\\nimport('https://unpkg.com/vconsole').then(() => {\\n    new window.VConsole()\\n})\" forKey:@\"vConsole\"];\n        [DoraemonCacheManager.sharedInstance saveJsHistoricalRecordWithText:@\"//重新加载\\nlocation.reload()\" forKey:@\"Reload\"];\n        [DoraemonCacheManager.sharedInstance saveJsHistoricalRecordWithText:@\"//后退\\nhistory.go(-1)\" forKey:@\"Back\"];\n        [DoraemonCacheManager.sharedInstance saveJsHistoricalRecordWithText:@\"//前进\\nhistory.go(1)\" forKey:@\"Forward\"];\n        \n        scriptItems = [DoraemonCacheManager.sharedInstance jsHistoricalRecord];\n    }\n    [self.items addObjectsFromArray:scriptItems];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/JavaScript/Function/DoraemonJavaScriptManager.h",
    "content": "//\n//  DoraemonJavaScriptManager.h\n//  DoraemonKit\n//\n//  Created by carefree on 2022/5/11.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonJavaScriptManager : NSObject\n\n+ (DoraemonJavaScriptManager *)shareInstance;\n\n- (void)show;\n\n- (void)evalJavaScript:(NSString *)script;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/JavaScript/Function/DoraemonJavaScriptManager.m",
    "content": "//\n//  DoraemonJavaScriptManager.m\n//  DoraemonKit\n//\n//  Created by carefree on 2022/5/11.\n//\n\n#import \"DoraemonJavaScriptManager.h\"\n#import <WebKit/WebKit.h>\n#import \"DoraemonDefine.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"UIViewController+Doraemon.h\"\n#import \"DoraemonJavaScriptViewController.h\"\n\n@interface DoraemonJavaScriptManager ()\n\n@property (nonatomic, weak) id  webView;\n\n@end\n\n@implementation DoraemonJavaScriptManager\n\n+ (DoraemonJavaScriptManager *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonJavaScriptManager *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonJavaScriptManager alloc] init];\n    });\n    return instance;\n}\n\n- (void)show {\n    NSArray *webViews = [DoraemonUtil getWebViews];\n    \n    NSString *title = DoraemonLocalizedString(@\"请选择WebView\");\n    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:title preferredStyle:UIAlertControllerStyleActionSheet];\n    for (NSInteger i = 0; i < webViews.count; i++) {\n        WKWebView *webView = webViews[i];\n        NSString *actionTitle = webView.description;\n        UIAlertAction *action = [UIAlertAction actionWithTitle:actionTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n            [self selectWebView:webView];\n        }];\n        [alert addAction:action];\n    }\n    if (webViews.count == 0) {\n        UIAlertAction *action = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"无可用的WebView\") style:UIAlertActionStyleDestructive handler:nil];\n        action.enabled = NO;\n        [alert addAction:action];\n    }\n    if (webViews.count == 1) {\n        //只有一个，则跳过选择\n        [self selectWebView:webViews.firstObject];\n        return;\n    }\n    [alert addAction:[UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"取消\") style:UIAlertActionStyleCancel handler:nil]];\n    dispatch_async(dispatch_get_main_queue(), ^{\n        [UIViewController.topViewControllerForKeyWindow presentViewController:alert animated:YES completion:nil];\n    });\n}\n\n- (void)selectWebView:(id)webView {\n    self.webView = webView;\n    DoraemonJavaScriptViewController *vc = [[DoraemonJavaScriptViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n- (void)evalJavaScript:(NSString *)script {\n    id currentWebView = self.webView;\n    if (!currentWebView) {\n        return;\n    }\n    if ([currentWebView isKindOfClass:WKWebView.class]) {\n        WKWebView *webView = currentWebView;\n        [webView evaluateJavaScript:script completionHandler:^(id _Nullable result, NSError * _Nullable error) {\n            if (error) {\n                NSLog(@\"js error: %@\", error);\n            }\n        }];\n    }\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n    if ([currentWebView isKindOfClass:UIWebView.class]) {\n        UIWebView *webView = currentWebView;\n        [webView stringByEvaluatingJavaScriptFromString:script];\n    }\n#pragma clang diagnostic pop\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSLog/DoraemonNSLogPlugin.h",
    "content": "//\n//  DoraemonNSLogPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/25.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonNSLogPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSLog/DoraemonNSLogPlugin.m",
    "content": "//\n//  DoraemonNSLogPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/25.\n//\n\n#import \"DoraemonNSLogPlugin.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonNSLogViewController.h\"\n\n@implementation DoraemonNSLogPlugin\n\n- (void)pluginDidLoad{\n    DoraemonNSLogViewController *vc = [[DoraemonNSLogViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSLog/DoraemonNSLogViewController.h",
    "content": "//\n//  DoraemonNSLogViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/25.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonNSLogViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSLog/DoraemonNSLogViewController.m",
    "content": "//\n//  DoraemonNSLogViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/25.\n//\n\n#import \"DoraemonNSLogViewController.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonCellButton.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonNSLogListViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonNSLogManager.h\"\n\n@interface DoraemonNSLogViewController ()<DoraemonSwitchViewDelegate,DoraemonCellButtonDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n@property (nonatomic, strong) DoraemonCellButton *cellBtn;\n\n@end\n\n@implementation DoraemonNSLogViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = @\"NSLog\";\n    \n    _switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"开关\") switchOn:[[DoraemonCacheManager sharedInstance] nsLogSwitch]];\n    [_switchView needTopLine];\n    [_switchView needDownLine];\n    _switchView.delegate = self;\n    [self.view addSubview:_switchView];\n    \n    _cellBtn = [[DoraemonCellButton alloc] initWithFrame:CGRectMake(0, _switchView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [_cellBtn renderUIWithTitle:DoraemonLocalizedString(@\"查看记录\")];\n    _cellBtn.delegate = self;\n    [_cellBtn needDownLine];\n    [self.view addSubview:_cellBtn];\n    \n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    [[DoraemonCacheManager sharedInstance] saveNSLogSwitch:on];\n    if (on) {\n        [[DoraemonNSLogManager sharedInstance] startNSLogMonitor];\n    }else{\n        [[DoraemonNSLogManager sharedInstance] stopNSLogMonitor];\n    }\n}\n\n#pragma mark -- DoraemonCellButtonDelegate\n- (void)cellBtnClick:(id)sender{\n    if (sender == _cellBtn) {\n        DoraemonNSLogListViewController *vc = [[DoraemonNSLogListViewController alloc] init];\n        [self.navigationController pushViewController:vc animated:YES];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSLog/Function/DoraemonNSLogManager.h",
    "content": "//\n//  DoraemonNSLogManager.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/26.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonNSLogModel.h\"\n\n\n@interface DoraemonNSLogManager : NSObject\n\n+ (instancetype)sharedInstance;\n\n@property (nonatomic, strong) NSMutableArray<DoraemonNSLogModel *> *dataArray;\n\n- (void)startNSLogMonitor;\n\n- (void)stopNSLogMonitor;\n\n- (void)addNSLog:(NSString *)log;\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSLog/Function/DoraemonNSLogManager.m",
    "content": "//\n//  DoraemonNSLogManager.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/26.\n//\n\n#import \"DoraemonNSLogManager.h\"\n#import \"doraemon_fishhook.h\"\n\n//函数指针，用来保存原始的函数的地址\nstatic void(*old_nslog)(NSString *format, ...);\n\n//新的NSLog\nvoid myNSLog(NSString *format, ...){\n    \n    va_list vl;\n    va_start(vl, format);\n    NSString* str = [[NSString alloc] initWithFormat:format arguments:vl];\n    va_end(vl);\n    \n    [[DoraemonNSLogManager sharedInstance] addNSLog:str];\n    //再调用原来的nslog\n    //old_nslog(str);\n    old_nslog(@\"%@\",str);\n}\n\n\n@implementation DoraemonNSLogManager\n\n+ (instancetype)sharedInstance {\n    static id instance = nil;\n    static dispatch_once_t onceToken;\n    \n    dispatch_once(&onceToken, ^{\n        instance = [[self alloc] init];\n    });\n    return instance;\n}\n\n- (void)startNSLogMonitor{\n    doraemon_rebind_symbols((struct doraemon_rebinding[1]){\"NSLog\", (void *)myNSLog, (void **)&old_nslog},1);\n}\n\n- (void)stopNSLogMonitor{\n    doraemon_rebind_symbols((struct doraemon_rebinding[1]){\"NSLog\", (void *)old_nslog, NULL},1);\n}\n\n- (void)addNSLog:(NSString *)log{\n    DoraemonNSLogModel *model = [[DoraemonNSLogModel alloc] init];\n    model.content = log;\n    model.timeInterval = [[NSDate date] timeIntervalSince1970];\n    \n    if (!_dataArray) {\n        _dataArray = [[NSMutableArray alloc] init];\n    }\n    [_dataArray addObject:model];\n    \n//    return;\n//    if (@available(iOS 13.0, *)) {\n//    }else{\n//        dispatch_async(dispatch_get_main_queue(), ^{\n//            [[DoraemonStateBar shareInstance] renderUIWithContent:[NSString stringWithFormat:@\"[NSLog] : %@\",log] from:DoraemonStateBarFromNSLog];\n//        });\n//    }\n\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSLog/Function/DoraemonNSLogModel.h",
    "content": "//\n//  DoraemonNSLogModel.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/26.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface DoraemonNSLogModel : NSObject\n\n@property (nonatomic, copy) NSString *content;\n@property (nonatomic, assign) NSTimeInterval timeInterval;\n@property (nonatomic, assign) BOOL expand;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSLog/Function/DoraemonNSLogModel.m",
    "content": "//\n//  DoraemonNSLogModel.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/26.\n//\n\n#import \"DoraemonNSLogModel.h\"\n\n@implementation DoraemonNSLogModel\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSLog/List/DoraemonNSLogListCell.h",
    "content": "//\n//  DoraemonNSLogListCell.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/26.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonNSLogModel.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonNSLogListCell : UITableViewCell\n\n- (void)renderCellWithData:(DoraemonNSLogModel *)model;\n\n+ (CGFloat)cellHeightWith:(nullable DoraemonNSLogModel *)model;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSLog/List/DoraemonNSLogListCell.m",
    "content": "//\n//  DoraemonNSLogListCell.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/26.\n//\n\n#import \"DoraemonNSLogListCell.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonUtil.h\"\n\n@interface DoraemonNSLogListCell()\n\n@property (nonatomic, strong) UIImageView *arrowImageView;\n@property (nonatomic, strong) UILabel *logLabel;\n\n@end\n\n@implementation DoraemonNSLogListCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        self.selectionStyle = UITableViewCellSelectionStyleNone;\n        \n        _arrowImageView = [[UIImageView alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(27), [[self class] cellHeightWith:nil]/2-kDoraemonSizeFrom750_Landscape(25)/2, kDoraemonSizeFrom750_Landscape(25), kDoraemonSizeFrom750_Landscape(25))];\n        _arrowImageView.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_expand_no\"];\n        _arrowImageView.contentMode = UIViewContentModeCenter;\n        _arrowImageView.backgroundColor = [UIColor whiteColor];\n        [self.contentView addSubview:_arrowImageView];\n        \n        _logLabel = [[UILabel alloc] init];\n        _logLabel.textColor = [UIColor doraemon_black_1];\n        _logLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n        [self.contentView addSubview:_logLabel];\n    }\n    return self;\n}\n\n- (void)renderCellWithData:(DoraemonNSLogModel *)model{\n    NSString *content;\n    if (model && model.expand){\n        NSString *log = model.content;\n        NSTimeInterval timeInterval = model.timeInterval;\n        NSString *time = [DoraemonUtil dateFormatTimeInterval:timeInterval];\n        content = [NSString stringWithFormat:DoraemonLocalizedString(@\"%@\\n触发时间: %@\"),log,time];\n        _logLabel.numberOfLines = 0;\n        _logLabel.text = content;\n        CGSize size = [_logLabel sizeThatFits:CGSizeMake(DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)*2-kDoraemonSizeFrom750_Landscape(25)-kDoraemonSizeFrom750_Landscape(12)*2, MAXFLOAT)];\n        _logLabel.frame = CGRectMake(_arrowImageView.doraemon_right+kDoraemonSizeFrom750_Landscape(12), [[self class] cellHeightWith:model]/2-size.height/2, size.width, size.height);\n        \n        _arrowImageView.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_expand\"];\n    }else{\n        _logLabel.numberOfLines = 1;\n        _logLabel.text = model.content;\n        _logLabel.frame = CGRectMake(_arrowImageView.doraemon_right+kDoraemonSizeFrom750_Landscape(12), [[self class] cellHeightWith:model]/2-kDoraemonSizeFrom750_Landscape(34)/2,DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)*2-kDoraemonSizeFrom750_Landscape(25)-kDoraemonSizeFrom750_Landscape(12)*2 , kDoraemonSizeFrom750_Landscape(34));\n        _arrowImageView.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_expand_no\"];\n    }\n    \n\n\n}\n\n+ (CGFloat)cellHeightWith:(DoraemonNSLogModel *)model{\n    CGFloat cellHeight = kDoraemonSizeFrom750_Landscape(60);\n    if (model && model.expand) {\n        NSString *log = model.content;\n        NSTimeInterval timeInterval = model.timeInterval;\n        NSString *time = [DoraemonUtil dateFormatTimeInterval:timeInterval];\n        NSString *content = [NSString stringWithFormat:DoraemonLocalizedString(@\"%@\\n触发时间: %@\"),log,time];\n        \n        UILabel *logLabel = [[UILabel alloc] init];\n        logLabel.textColor = [UIColor doraemon_black_1];\n        logLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n        logLabel.text = content;\n        logLabel.numberOfLines = 0;\n        CGSize size = [logLabel sizeThatFits:CGSizeMake(DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)*2-kDoraemonSizeFrom750_Landscape(25)-kDoraemonSizeFrom750_Landscape(12)*2, MAXFLOAT)];\n        cellHeight = kDoraemonSizeFrom750_Landscape(10) + size.height + kDoraemonSizeFrom750_Landscape(10);\n    }\n    return cellHeight;\n}\n\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSLog/List/DoraemonNSLogListViewController.h",
    "content": "//\n//  DoraemonNSLogListViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/26.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonNSLogListViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSLog/List/DoraemonNSLogListViewController.m",
    "content": "//\n//  DoraemonNSLogListViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/11/26.\n//\n\n#import \"DoraemonNSLogListViewController.h\"\n#import \"DoraemonNSLogManager.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonNSLogListCell.h\"\n#import \"DoraemonNSLogModel.h\"\n#import \"DoraemonNSLogSearchView.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonNavBarItemModel.h\"\n\n@interface DoraemonNSLogListViewController ()<UITableViewDelegate,UITableViewDataSource,DoraemonNSLogSearchViewDelegate>\n\n@property (nonatomic, strong) DoraemonNSLogSearchView *searchView;\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, copy) NSArray *dataArray;\n\n@end\n\n@implementation DoraemonNSLogListViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"NSLog日志记录\");\n    \n    DoraemonNavBarItemModel *model1 = [[DoraemonNavBarItemModel alloc] initWithText:DoraemonLocalizedString(@\"清除\") color:[UIColor doraemon_blue] selector:@selector(clear)];\n    DoraemonNavBarItemModel *model2 = [[DoraemonNavBarItemModel alloc] initWithText:DoraemonLocalizedString(@\"导出\") color:[UIColor doraemon_blue] selector:@selector(export)];\n    [self setRightNavBarItems:@[model1,model2]];\n    \n    //按照时间倒序排列\n    self.dataArray = [[[DoraemonNSLogManager sharedInstance].dataArray reverseObjectEnumerator] allObjects];\n    \n  \n    _searchView = [[DoraemonNSLogSearchView alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(32), IPHONE_NAVIGATIONBAR_HEIGHT+kDoraemonSizeFrom750_Landscape(32), self.view.doraemon_width-2*kDoraemonSizeFrom750_Landscape(32), kDoraemonSizeFrom750_Landscape(100))];\n    _searchView.delegate = self;\n    [self.view addSubview:_searchView];\n    \n    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, _searchView.doraemon_bottom+kDoraemonSizeFrom750_Landscape(32), self.view.doraemon_width, self.view.doraemon_height-_searchView.doraemon_bottom-kDoraemonSizeFrom750_Landscape(32)) style:UITableViewStylePlain];\n//    self.tableView.backgroundColor = [UIColor whiteColor];\n    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;\n    self.tableView.delegate = self;\n    self.tableView.dataSource = self;\n    [self.view addSubview:self.tableView];\n}\n\n- (void)clear {\n    [[DoraemonNSLogManager sharedInstance].dataArray removeAllObjects];\n    self.dataArray = [[NSArray alloc] init];\n    [self.tableView reloadData];\n}\n\n- (void)export {\n    NSArray<DoraemonNSLogModel *> *dataArray = [[DoraemonNSLogManager sharedInstance].dataArray  copy];\n    NSMutableString *log = [[NSMutableString alloc] init];\n    for (DoraemonNSLogModel *model in dataArray) {\n        NSString *time = [NSString stringWithFormat:@\"[%@]\",[DoraemonUtil dateFormatTimeInterval:model.timeInterval]];\n        [log appendString:time];\n        [log appendString:@\" \"];\n        [log appendString:model.content];\n        [log appendString:@\"\\n\"];\n    }\n    \n    [DoraemonUtil shareText:log formVC:self];\n}\n\n#pragma mark - UITableView Delegate\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {\n    return 1;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return self.dataArray.count;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonNSLogModel* model = [self.dataArray objectAtIndex:indexPath.row];\n    return [DoraemonNSLogListCell cellHeightWith:model];\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {\n    return 0.1;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {\n    return 0.1;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"httpcell\";\n    DoraemonNSLogListCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[DoraemonNSLogListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];\n    }\n    DoraemonNSLogModel* model = [self.dataArray objectAtIndex:indexPath.row];\n    [cell renderCellWithData:model];\n    return cell;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    [tableView deselectRowAtIndexPath:indexPath animated:YES];\n    DoraemonNSLogModel* model = [self.dataArray objectAtIndex:indexPath.row];\n    model.expand = !model.expand;\n    [self.tableView reloadData];\n}\n\n- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{\n    return YES;\n}\n\n- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath{\n    return DoraemonLocalizedString(@\"复制\");\n}\n\n- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonNSLogModel* model = [self.dataArray objectAtIndex:indexPath.row];\n    NSString *content = model.content;\n    if (content.length>0) {\n        UIPasteboard *pboard = [UIPasteboard generalPasteboard];\n        pboard.string = content;\n    }\n}\n\n#pragma mark - DoraemonNSLogSearchViewDelegate\n- (void)searchViewInputChange:(NSString *)text{\n    if (text.length > 0) {\n        NSArray *dataArray = [[[DoraemonNSLogManager sharedInstance].dataArray reverseObjectEnumerator] allObjects];\n        NSMutableArray *resultArray = [[NSMutableArray alloc] init];\n        for(DoraemonNSLogModel *model in dataArray){\n            NSString *content = model.content;\n            if ([content containsString:text]) {\n                [resultArray addObject:model];\n            }\n        }\n        self.dataArray = [[NSArray alloc] initWithArray:resultArray];\n    }else{\n        self.dataArray = [[[DoraemonNSLogManager sharedInstance].dataArray reverseObjectEnumerator] allObjects];\n    }\n\n    [self.tableView reloadData];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSLog/List/DoraemonNSLogSearchView.h",
    "content": "//\n//  DoraemonNSLogSearchView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/3.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@protocol DoraemonNSLogSearchViewDelegate  <NSObject>\n\n- (void)searchViewInputChange:(NSString *)text;\n\n@end\n\n@interface DoraemonNSLogSearchView : UIView\n\n@property (nonatomic, weak) id<DoraemonNSLogSearchViewDelegate> delegate;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSLog/List/DoraemonNSLogSearchView.m",
    "content": "//\n//  DoraemonNSLogSearchView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/3.\n//\n\n#import \"DoraemonNSLogSearchView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonNSLogSearchView()\n\n@property (nonatomic, strong) UIImageView *searchIcon;\n@property (nonatomic, strong) UITextField *textField;\n\n@end\n\n@implementation DoraemonNSLogSearchView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        self.layer.cornerRadius = kDoraemonSizeFrom750_Landscape(8);\n        self.layer.borderWidth = kDoraemonSizeFrom750_Landscape(2);\n        self.layer.borderColor = [UIColor doraemon_colorWithHex:0x999999 andAlpha:0.2].CGColor;\n        \n        _searchIcon = [[UIImageView alloc] initWithImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_search\"]];\n        _searchIcon.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(20), self.doraemon_height/2-_searchIcon.doraemon_height/2, _searchIcon.doraemon_width, _searchIcon.doraemon_height);\n        [self addSubview:_searchIcon];\n        \n        _textField = [[UITextField alloc] initWithFrame:CGRectMake(_searchIcon.doraemon_right+kDoraemonSizeFrom750_Landscape(20), self.doraemon_height/2-kDoraemonSizeFrom750_Landscape(50)/2, self.doraemon_width-_searchIcon.doraemon_right-kDoraemonSizeFrom750_Landscape(20), kDoraemonSizeFrom750_Landscape(50))];\n        _textField.placeholder = DoraemonLocalizedString(@\"请输入您要搜索的关键字\");\n        [_textField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];\n        [self addSubview:_textField];\n    }\n    return self;\n}\n\n-(void)textFieldDidChange:(id)sender{\n    UITextField *senderTextField = (UITextField *)sender;\n    //去除首尾空格\n    NSString *textSearchStr = [senderTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];\n    if (self.delegate && [self.delegate respondsToSelector:@selector(searchViewInputChange:)]) {\n        [self.delegate searchViewInputChange:textSearchStr];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSUserDefaults/DoraemonNSUserDefaultsPlugin.h",
    "content": "//\n//  DoraemonNSUserDefaultsPlugin.h\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/11/26.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonNSUserDefaultsPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSUserDefaults/DoraemonNSUserDefaultsPlugin.m",
    "content": "//\n//  DoraemonNSUserDefaultsPlugin.m\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/11/26.\n//\n\n#import \"DoraemonNSUserDefaultsPlugin.h\"\n#import \"DoraemonNSUserDefaultsViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonNSUserDefaultsPlugin\n- (void)pluginDidLoad{\n    DoraemonNSUserDefaultsViewController *vc = [[DoraemonNSUserDefaultsViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSUserDefaults/Model/DoraemonNSUserDefaultsModel.h",
    "content": "//\n//  DoraemonNSUserDefaultsModel.h\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/11/26.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonNSUserDefaultsModel : NSObject\n@property (nonatomic, copy) NSString *key;\n@property (nonatomic, strong) id value;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSUserDefaults/Model/DoraemonNSUserDefaultsModel.m",
    "content": "//\n//  DoraemonNSUserDefaultsModel.m\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/11/26.\n//\n\n#import \"DoraemonNSUserDefaultsModel.h\"\n\n@implementation DoraemonNSUserDefaultsModel\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSUserDefaults/ViewControllers/DoraemonNSUserDefaultsEditViewController.h",
    "content": "//\n//  DoraemonNSUserDefaultsEditViewController.h\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/11/26.\n//\n\n#import <DoraemonKit/DoraemonKit.h>\n#import \"DoraemonBaseViewController.h\"\n@class DoraemonNSUserDefaultsModel;\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonNSUserDefaultsEditViewController : DoraemonBaseViewController\n@property (nonatomic, strong) DoraemonNSUserDefaultsModel *model;\n\n- (instancetype)initWithModel: (DoraemonNSUserDefaultsModel *)model;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSUserDefaults/ViewControllers/DoraemonNSUserDefaultsEditViewController.m",
    "content": "//\n//  DoraemonNSUserDefaultsEditViewController.m\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/11/26.\n//\n\n#import \"DoraemonNSUserDefaultsEditViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonNSUserDefaultsModel.h\"\n\n@interface DoraemonNSUserDefaultsEditViewController ()\n@property (nonatomic, strong) UITextView *keyTextView;\n@property (nonatomic, strong) UITextView *valueTextView;\n@end\n\n@implementation DoraemonNSUserDefaultsEditViewController\n\n- (instancetype)initWithModel: (DoraemonNSUserDefaultsModel *)model\n{\n    self = [super init];\n    if (self) {\n        self.model = model;\n    }\n    return self;\n}\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    self.keyTextView = [[UITextView alloc] init];\n    self.keyTextView.editable = NO;\n    self.keyTextView.font = [UIFont systemFontOfSize:16];\n    [self.view addSubview:self.keyTextView];\n    self.keyTextView.text = self.model.key;\n    CGFloat keyMaxWidth = self.view.doraemon_width;\n    CGFloat keyMaxHeight = 60;\n    CGSize keySize = [self.keyTextView sizeThatFits:CGSizeMake(keyMaxWidth, keyMaxHeight)];\n    self.keyTextView.frame = CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT + 10, keyMaxWidth, keySize.height + 1);\n    \n    self.valueTextView = [[UITextView alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(self.keyTextView.frame) + 10, self.view.doraemon_width, 300)];\n    [self.view addSubview:self.valueTextView];\n    self.valueTextView.layer.borderWidth = 0.5;\n    self.valueTextView.layer.borderColor = [[UIColor lightGrayColor] CGColor];\n    self.title = self.model.key;\n    self.valueTextView.text = [self.model.value description];\n    \n    UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(submit)];\n    self.navigationItem.rightBarButtonItems = @[item];\n    self.title = @\"Edit\";\n}\n\n- (void)submit {\n    [[NSUserDefaults standardUserDefaults] setObject:self.valueTextView.text forKey:_model.key];\n    [self.navigationController popViewControllerAnimated:true];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSUserDefaults/ViewControllers/DoraemonNSUserDefaultsViewController.h",
    "content": "//\n//  DoraemonNSUserDefaultsViewController.h\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/11/26.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonNSUserDefaultsViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/NSUserDefaults/ViewControllers/DoraemonNSUserDefaultsViewController.m",
    "content": "//\n//  DoraemonNSUserDefaultsViewController.m\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/11/26.\n//\n\n#import \"DoraemonNSUserDefaultsViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonNSUserDefaultsModel.h\"\n#import \"DoraemonNSUserDefaultsEditViewController.h\"\n\n@interface DoraemonNSUserDefaultsViewController ()<UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate>\n\n@property (nonatomic, strong) NSMutableArray<DoraemonNSUserDefaultsModel *> *modelList;\n@property (nonatomic, strong) NSMutableArray<DoraemonNSUserDefaultsModel *> *searchList;\n@property (nonatomic, strong, readonly) NSMutableArray<DoraemonNSUserDefaultsModel *> *dataArray;\n\n@property (nonatomic, assign) BOOL isSearch;\n\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, weak) UITextField *searchTextField;\n\n@property (nonatomic, strong) UIBarButtonItem *clearAllItem;\n@property (nonatomic, strong) UIBarButtonItem *cancelItem;\n@end\n\n@implementation DoraemonNSUserDefaultsViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    self.isSearch = NO;\n    \n    [self buildSearchUI];\n    \n    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT, self.view.doraemon_width, self.view.doraemon_height - IPHONE_NAVIGATIONBAR_HEIGHT) style:UITableViewStylePlain];\n    if (@available(iOS 13.0, *)) {\n        self.tableView.backgroundColor = [UIColor systemBackgroundColor];\n    } else {\n        self.tableView.backgroundColor = [UIColor whiteColor];\n    }\n    // 该方式退出键盘（系统键盘）时会在底部卡顿一下\n    //self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;\n    self.tableView.delegate = self;\n    self.tableView.dataSource = self;\n    [self.view addSubview:self.tableView];\n}\n\n- (void)buildSearchUI {\n    \n    CGFloat searchTextFieldWidth = MAX([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);\n    \n    UITextField *searchTextField = [[UITextField alloc] initWithFrame:CGRectMake(0.0, 0.0, searchTextFieldWidth, 30.0)];\n    searchTextField.placeholder = @\"Search Key\";\n    searchTextField.layer.cornerRadius = 15.0;\n    if (@available(iOS 13.0, *)) {\n        searchTextField.backgroundColor = [UIColor systemBackgroundColor];\n    } else {\n        searchTextField.backgroundColor = [UIColor whiteColor];\n    }\n    searchTextField.delegate = self;\n    searchTextField.clearButtonMode = UITextFieldViewModeWhileEditing;\n    searchTextField.returnKeyType = UIReturnKeySearch;\n    self.navigationItem.titleView = searchTextField;\n    _searchTextField = searchTextField;\n    \n    searchTextField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 36.0)];\n    searchTextField.rightView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 36.0)];\n    \n    searchTextField.leftViewMode = UITextFieldViewModeAlways;\n    searchTextField.rightViewMode = UITextFieldViewModeUnlessEditing;\n}\n\n- (void)viewWillAppear:(BOOL)animated {\n    [super viewWillAppear:animated];\n    [self reload];\n}\n\n- (void)reload {\n    NSDictionary<NSString *, id> *dic = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];\n    self.modelList = [NSMutableArray array];\n    [dic enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {\n        DoraemonNSUserDefaultsModel *model = [[DoraemonNSUserDefaultsModel alloc] init];\n        model.key = key;\n        model.value = obj;\n        [self.modelList addObject:model];\n    }];\n    [self.modelList sortUsingComparator:^NSComparisonResult(DoraemonNSUserDefaultsModel * _Nonnull obj1, DoraemonNSUserDefaultsModel *  _Nonnull obj2) {\n        return [obj1.key.lowercaseString compare:obj2.key.lowercaseString];\n    }];\n    \n    if (self.isSearch) {\n        \n        [self searchWithKeyword:self.searchTextField.text];\n        \n        return;\n    }\n    \n    [self.tableView reloadData];\n}\n\n- (void)clearUserDefaults {\n    \n    if ([self.searchTextField isFirstResponder]) {\n        \n        [self.searchTextField resignFirstResponder];\n    }\n    \n    [DoraemonAlertUtil handleAlertActionWithVC:self text:@\"clear all data?\" okBlock:^{\n        \n        self.isSearch = NO;\n        \n        NSString *bundleIdentifier = NSBundle.mainBundle.bundleIdentifier;\n        \n        [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:bundleIdentifier];\n        \n        [self reload];\n        \n    } cancleBlock:^{\n    \n    }];\n}\n\n#pragma mark\n#pragma mark - Private Method\n\n- (void)setIsSearch:(BOOL)isSearch {\n    _isSearch = isSearch;\n    \n    self.navigationItem.rightBarButtonItems = isSearch ? @[self.cancelItem] : @[self.clearAllItem];\n}\n\n- (void)cancelSearch {\n    \n    self.isSearch = NO;\n    \n    self.searchTextField.text = nil;\n    \n    if ([self.searchTextField isFirstResponder]) {\n        \n        [self.searchTextField resignFirstResponder];\n    }\n    \n    [_searchList removeAllObjects];\n    \n    [self.tableView reloadData];\n}\n\n- (void)searchWithKeyword:(NSString *)keyword {\n    \n    NSString *checkKeyWord = [keyword stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];\n    \n    if ([checkKeyWord length] <= 0) { return; }\n    \n    checkKeyWord = [checkKeyWord lowercaseString];\n    \n    self.searchTextField.text = checkKeyWord;\n    \n    [_searchList removeAllObjects];\n    \n    for (DoraemonNSUserDefaultsModel *model in self.modelList) {\n        \n        if ([[model.key lowercaseString] containsString:checkKeyWord]) {\n            \n            [self.searchList addObject:model];\n        }\n    }\n    \n    self.isSearch = YES;\n    \n    [self.tableView reloadData];\n}\n\n#pragma mark\n#pragma mark - UITableViewDataSource & UITableViewDelegate\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return self.dataArray.count;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"DoraemonNSUserDefaultsViewControllerCell\";\n    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifer];\n    }\n    DoraemonNSUserDefaultsModel *model = self.dataArray[indexPath.row];\n    cell.textLabel.text = model.key;\n    cell.detailTextLabel.text = [model.value description];\n    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;\n    return cell;\n}\n\n- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return UITableViewCellEditingStyleDelete;\n}\n\n- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{\n    if (editingStyle == UITableViewCellEditingStyleDelete) {\n        DoraemonNSUserDefaultsModel *model = self.dataArray[indexPath.row];\n        [[NSUserDefaults standardUserDefaults] setValue:nil forKey:model.key];\n        [self reload];\n    }\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonNSUserDefaultsModel *model = self.dataArray[indexPath.row];\n    DoraemonNSUserDefaultsEditViewController *vc = [[DoraemonNSUserDefaultsEditViewController alloc] initWithModel:model];\n    [self.navigationController pushViewController:vc animated:true];\n}\n\n#pragma mark\n#pragma mark - UIScrollViewDelegate\n\n- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {\n    \n    [self.searchTextField resignFirstResponder];\n}\n\n#pragma mark\n#pragma mark - UITextFieldDelegate\n\n- (BOOL)textFieldShouldReturn:(UITextField *)textField {\n    \n    if (![textField hasText]) {\n        \n        [self cancelSearch];\n        \n        return YES;\n    }\n    \n    [textField resignFirstResponder];\n    \n    [self searchWithKeyword:textField.text];\n    \n    return YES;\n}\n\n#pragma mark\n#pragma mark - Property\n\n- (NSMutableArray<DoraemonNSUserDefaultsModel *> *)dataArray {\n    \n    return self.isSearch ? self.searchList : self.modelList;\n}\n\n- (NSMutableArray<DoraemonNSUserDefaultsModel *> *)searchList {\n    if (!_searchList) {\n        _searchList = [NSMutableArray array];\n    }\n    return _searchList;\n}\n\n- (UIBarButtonItem *)clearAllItem {\n    if (!_clearAllItem) {\n        _clearAllItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(clearUserDefaults)];\n    }\n    return _clearAllItem;\n}\n\n- (UIBarButtonItem *)cancelItem {\n    if (!_cancelItem) {\n        _cancelItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelSearch)];\n    }\n    return _cancelItem;\n}\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/DoraemonSandboxPlugin.h",
    "content": "//\n//  DoraemonSandboxPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonSandboxPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/DoraemonSandboxPlugin.m",
    "content": "//\n//  DoraemonSandboxPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import \"DoraemonSandboxPlugin.h\"\n#import \"DoraemonSandboxViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonSandboxPlugin\n\n- (void)pluginDidLoad{\n    DoraemonSandboxViewController *vc = [[DoraemonSandboxViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/Util/DoraemonDBManager.h",
    "content": "//\n//  DoraemonDBManager.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/3/30.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDBManager : NSObject\n\n+ (DoraemonDBManager *)shareManager;\n\n@property (nonatomic, copy) NSString *dbPath;\n@property (nonatomic, copy) NSString *tableName;\n\n- (NSArray *)tablesAtDB;\n- (NSArray *)dataAtTable;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/Util/DoraemonDBManager.m",
    "content": "//\n//  DoraemonDBManager.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/3/30.\n//\n\n#import \"DoraemonDBManager.h\"\n#import <sqlite3.h>\n\n@interface DoraemonDBManager()\n\n@property (nonatomic, strong) NSMutableArray *dataArray;\n\n@end\n\n@implementation DoraemonDBManager\n\n+ (DoraemonDBManager *)shareManager{\n    static dispatch_once_t once;\n    static DoraemonDBManager *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonDBManager alloc] init];\n    });\n    return instance;\n}\n\n//打开数据库\n- (sqlite3 *)openDB{\n    sqlite3 *db = nil;\n    sqlite3_open([self.dbPath UTF8String], &db);\n    return db;\n}\n\n//获取指定路径数据库中的数据表\n- (NSArray *)tablesAtDB{\n    sqlite3 *db = [self openDB];\n    if (db == nil) {\n        return @[];\n    }\n    NSMutableArray *tableNameArray = [NSMutableArray array];\n    //查询sqlite_master表\n    NSString *sql = @\"select type, tbl_name from sqlite_master\";\n    sqlite3_stmt *stmt = NULL;\n    if (sqlite3_prepare_v2(db, sql.UTF8String, -1, &stmt, NULL) == SQLITE_OK) {\n        while (sqlite3_step(stmt) == SQLITE_ROW) {\n            const unsigned char *type_c = sqlite3_column_text(stmt, 0);\n            const unsigned char *tbl_name_c = sqlite3_column_text(stmt, 1);\n            NSString *type = [NSString stringWithUTF8String:(const char *)type_c];\n            NSString *tbl_name = [NSString stringWithUTF8String:(const char *)tbl_name_c];\n            if (type && [type isEqualToString:@\"table\"]) {\n                [tableNameArray addObject:tbl_name];\n            }\n        }\n    }\n    return tableNameArray;\n}\n\n//获取每一张表中的所有数据\n- (NSArray *)dataAtTable{\n    sqlite3 *db = [self openDB];\n    if (db == nil) {\n        return @[];\n    }\n    //查询sqlite_master表\n    NSString *sql = [NSString stringWithFormat:@\"select * from %@\",self.tableName];\n    //执行sql\n    char *errmsg = nil;\n    sqlite3_exec(db, [sql UTF8String], selectCallback, nil, &errmsg);\n    \n    //处理数据\n    NSMutableArray *arrayM = [NSMutableArray arrayWithArray:self.dataArray];\n    [self.dataArray removeAllObjects];\n    \n    return arrayM;\n}\n\n//查询回调\nint selectCallback(void *firstValue,int columnCount, char **columnValues, char **columnNames)\n{\n    NSMutableDictionary *dict = [NSMutableDictionary dictionary];\n    for (int i = 0; i < columnCount; i++) {\n        //获取当前的列表（字段名）\n        char *columnName = columnNames[i];\n        NSString *nameStr = nil;\n        if (columnName == NULL) {\n            nameStr = nil;\n        }else{\n            nameStr = [NSString stringWithUTF8String:columnName];\n        }\n        \n        //获取当前字段的值\n        char *columnValue = columnValues[i];\n        NSString *valueStr = nil;\n        if (columnValue == NULL) {\n            valueStr = nil;\n        }else{\n            valueStr = [NSString stringWithUTF8String:columnValue];\n        }\n        \n        if (nameStr.length>0) {\n            [dict setValue:valueStr forKey:nameStr];\n        }\n    }\n    [[[DoraemonDBManager shareManager] dataArray] addObject:dict];\n    return 0;\n}\n\n#pragma mark - 懒加载\n- (NSMutableArray *)dataArray{\n    if (_dataArray == nil) {\n        _dataArray = [NSMutableArray array];\n    }\n    return _dataArray;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DB/DoraemonDBCell.h",
    "content": "//\n//  DoraemonDBCell.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/4/1.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonDBRowView.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDBCell : UITableViewCell\n\n@property (nonatomic, strong) DoraemonDBRowView *rowView;\n\n- (void)renderCellWithArray:(NSArray *)array;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DB/DoraemonDBCell.m",
    "content": "//\n//  DoraemonDBCell.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/4/1.\n//\n\n#import \"DoraemonDBCell.h\"\n#import \"DoraemonDBRowView.h\"\n\n@interface DoraemonDBCell()\n\n@end\n\n@implementation DoraemonDBCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        _rowView = [[DoraemonDBRowView alloc] init];\n        [self.contentView addSubview:_rowView];\n    }\n    return self;\n}\n\n- (void)layoutSubviews{\n    [super layoutSubviews];\n    _rowView.frame = self.contentView.bounds;\n}\n\n- (void)renderCellWithArray:(NSArray *)array{\n    _rowView.dataArray = array;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DB/DoraemonDBRowView.h",
    "content": "//\n//  DoraemonDBRowView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/4/1.\n//\n\n#import <UIKit/UIKit.h>\n@class DoraemonDBRowView;\n\ntypedef NS_ENUM(NSInteger, DoraemonDBRowViewType) {\n    DoraemonDBRowViewTypeForTitle  = 0,\n    DoraemonDBRowViewTypeForOne   = 1,\n    DoraemonDBRowViewTypeForTwo   = 2\n    \n};\n\n@protocol DoraemonDBRowViewTypeDelegate <NSObject>\n\n- (void)rowView:(DoraemonDBRowView *)rowView didLabelTaped:(UILabel *)label;\n\n@end\n\n\n@interface DoraemonDBRowView : UIView\n\n@property(nonatomic, copy) NSArray *dataArray;\n\n@property(nonatomic, assign) DoraemonDBRowViewType type;\n\n@property(nonatomic, weak) id<DoraemonDBRowViewTypeDelegate> delegate;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DB/DoraemonDBRowView.m",
    "content": "//\n//  DoraemonDBRowView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/4/1.\n//\n\n#import \"DoraemonDBRowView.h\"\n#import \"DoraemonDefine.h\"\n\n@implementation DoraemonDBRowView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    if (self = [super initWithFrame:frame]) {\n        \n    }\n    return self;\n}\n\n- (void)setDataArray:(NSArray *)dataArray{\n    _dataArray = dataArray;\n    for (UIView *sub in self.subviews) {\n        [sub removeFromSuperview];\n    }\n    for (NSInteger i = 0; i < self.dataArray.count; i++) {\n        NSString *content = self.dataArray[i];\n        UILabel *label = [[UILabel alloc] init];\n        UIColor *color = [UIColor doraemon_colorWithString:@\"#dcdcdc\"];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            color = [UIColor doraemon_black_2];\n            if (self.type == DoraemonDBRowViewTypeForOne) {\n                color = [UIColor secondarySystemBackgroundColor];\n            }\n            if (self.type == DoraemonDBRowViewTypeForTwo) {\n                color = [UIColor tertiarySystemBackgroundColor];\n            }\n        } else {\n#endif\n            if (self.type == DoraemonDBRowViewTypeForOne) {\n                color = [UIColor doraemon_colorWithString:@\"#e6e6e6\"];\n            }\n            if (self.type == DoraemonDBRowViewTypeForTwo) {\n                color = [UIColor doraemon_colorWithString:@\"#ebebeb\"];\n            }\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        label.backgroundColor = color;\n        label.text = content;\n        label.textAlignment = NSTextAlignmentCenter;\n        label.tag = i;\n        label.userInteractionEnabled = YES;\n        [label addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapLabel:)]];\n        [self addSubview:label];\n    }\n}\n\n- (void)layoutSubviews{\n    [super layoutSubviews];\n    \n    for (UIView *subView in self.subviews) {\n        if ([subView isKindOfClass:UILabel.class]) {\n            CGFloat width = (self.bounds.size.width - (self.dataArray.count - 1)) / self.dataArray.count;\n            subView.frame = CGRectMake(subView.tag * (width + 1), 0, width, self.bounds.size.height);\n        }\n    }\n}\n\n- (void)tapLabel:(UITapGestureRecognizer *)tap{\n    UILabel *label = (UILabel *)tap.view;\n    if ([self.delegate respondsToSelector:@selector(rowView:didLabelTaped:)]) {\n        [self.delegate rowView:self didLabelTaped:label];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DB/DoraemonDBShowView.h",
    "content": "//\n//  DoraemonDBShowView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/4/1.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDBShowView : UIView\n\n- (void)showText:(NSString *)text;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DB/DoraemonDBShowView.m",
    "content": "//\n//  DoraemonDBShowView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/4/1.\n//\n\n#import \"DoraemonDBShowView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonDBShowView()\n\n@property (nonatomic, strong) UITextView *displayTextView;\n\n@end\n\n@implementation DoraemonDBShowView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        _displayTextView = [[UITextView alloc] init];\n        _displayTextView.font = [UIFont systemFontOfSize:16.0];\n        _displayTextView.editable = NO;\n        _displayTextView.textAlignment = NSTextAlignmentCenter;\n        _displayTextView.backgroundColor = [UIColor doraemon_black_2];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            _displayTextView.textColor = [UIColor labelColor];\n        }\n#endif\n        [self addSubview:_displayTextView];\n    }\n    return self;\n}\n\n- (void)showText:(NSString *)text{\n    _displayTextView.frame = CGRectMake(self.doraemon_width/2-150/2, self.doraemon_height/2-100/2, 150, 100);\n    __weak typeof(self) weakSelf = self;\n    [UIView animateWithDuration:0.25 animations:^{\n        __strong __typeof(weakSelf) strongSelf = weakSelf;\n        strongSelf.displayTextView.frame = CGRectMake(self.doraemon_width/2-300/2, self.doraemon_height/2-200/2, 300, 200);\n    } completion:^(BOOL finished) {\n        __strong __typeof(weakSelf) strongSelf = weakSelf;\n        strongSelf.displayTextView.text = text;\n    }];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DB/DoraemonDBTableViewController.h",
    "content": "//\n//  DoraemonDBTableViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/3/31.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDBTableViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DB/DoraemonDBTableViewController.m",
    "content": "//\n//  DoraemonDBTableViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/3/31.\n//\n\n#import \"DoraemonDBTableViewController.h\"\n#import \"DoraemonDBManager.h\"\n#import \"DoraemonDBRowView.h\"\n#import \"DoraemonDBCell.h\"\n#import \"DoraemonDBShowView.h\"\n\n@interface DoraemonDBTableViewController ()<UITableViewDelegate , UITableViewDataSource , DoraemonDBRowViewTypeDelegate>\n\n@property (nonatomic, strong) UIScrollView *scrollView;\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, copy) NSArray *dataAtTable;\n@property (nonatomic, strong) DoraemonDBShowView *showView;\n\n\n@end\n\n@implementation DoraemonDBTableViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = [DoraemonDBManager shareManager].tableName;\n    \n    NSArray *dataAtTable = [[DoraemonDBManager shareManager] dataAtTable];\n    self.dataAtTable = dataAtTable;\n    \n    UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];\n//    scrollView.backgroundColor = [UIColor whiteColor];\n    scrollView.bounces  = NO;\n    [self.view addSubview:scrollView];\n    self.scrollView = scrollView;\n    \n    UITableView *tableView = [[UITableView alloc] init];\n    tableView.separatorStyle = UITableViewCellSeparatorStyleNone;\n//    tableView.backgroundColor = [UIColor whiteColor];\n    tableView.delegate = self;\n    tableView.dataSource = self;\n    [self.scrollView addSubview:tableView];\n    self.tableView = tableView;\n    \n}\n\n- (void)viewWillLayoutSubviews{\n    [super viewWillLayoutSubviews];\n    \n    if (self.dataAtTable.count) {\n        NSDictionary *dict = self.dataAtTable.firstObject;\n        NSUInteger num = [dict allKeys].count;\n        self.tableView.frame = CGRectMake(0, 0, num * 200, self.scrollView.frame.size.height);\n        self.scrollView.contentSize = CGSizeMake(self.tableView.frame.size.width, self.tableView.bounds.size.height);\n    }\n}\n\n#pragma mark - UITableViewDelegate,UITableViewDataSource\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{\n    return self.dataAtTable.count == 0 ? 0 : 1;\n}\n\n- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{\n    DoraemonDBRowView *headerView = nil;\n    if (headerView == nil) {\n        headerView = [[DoraemonDBRowView alloc] init];\n    }\n    \n    NSDictionary *dict = self.dataAtTable.firstObject;\n    headerView.dataArray = [dict allKeys];\n    \n    return headerView;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{\n    return 44;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{\n    return self.dataAtTable.count;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{\n    static NSString *identifer = @\"db_data\";\n    DoraemonDBCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (cell == nil) {\n        cell = [[DoraemonDBCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identifer];\n        cell.selectionStyle = UITableViewCellSelectionStyleNone;\n        cell.rowView.delegate = self;\n    }\n    \n    cell.rowView.type = (indexPath.row % 2 == 0) ? DoraemonDBRowViewTypeForOne : DoraemonDBRowViewTypeForTwo;\n    NSDictionary *dict = self.dataAtTable[indexPath.row];\n    [cell renderCellWithArray:[dict allValues]];\n    \n    return cell;\n}\n\n#pragma mark -- DoraemonDBRowViewTypeDelegate\n- (void)rowView:(DoraemonDBRowView *)rowView didLabelTaped:(UILabel *)label{\n    NSString *content = label.text;\n    [self showText:content];\n}\n\n#pragma mark -- 显示弹出文案\n- (void)showText:(NSString *)content{\n    if (self.showView) {\n        [self.showView removeFromSuperview];\n    }\n    self.showView = [[DoraemonDBShowView alloc] initWithFrame:self.view.bounds];\n    [self.view addSubview:self.showView];\n    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissView)];\n    self.showView.userInteractionEnabled = YES;\n    [self.showView addGestureRecognizer:tap];\n    \n    [self.showView showText:content];\n}\n\n- (void)dismissView{\n    if (self.showView) {\n        [self.showView removeFromSuperview];\n    }\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DoraemonSanboxDetailViewController.h",
    "content": "//\n//  DoraemonSanboxDetailViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/20.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonSanboxDetailViewController : DoraemonBaseViewController\n\n@property (nonatomic, copy) NSString *filePath;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DoraemonSanboxDetailViewController.m",
    "content": "//\n//  DoraemonSanboxDetailViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/20.\n//\n\n#import <AVKit/AVKit.h>\n#import <AVFoundation/AVFoundation.h>\n#import \"DoraemonSanboxDetailViewController.h\"\n#import \"DoraemonToastUtil.h\"\n#import \"UIView+Doraemon.h\"\n#import \"Doraemoni18NUtil.h\"\n#import <QuickLook/QuickLook.h>\n#import \"DoraemonDBManager.h\"\n#import \"DoraemonDBTableViewController.h\"\n#import \"DoraemonManager.h\"\n\n@interface DoraemonSanboxDetailViewController ()<QLPreviewControllerDelegate,QLPreviewControllerDataSource,UITableViewDelegate,UITableViewDataSource>\n\n@property (nonatomic, strong) UIImageView *imageView;\n@property (nonatomic, strong) UITextView *textView;\n@property (nonatomic, copy) NSArray *tableNameArray;\n@property (nonatomic, strong) UITableView *dbTableNameTableView;\n\n@end\n\n@implementation DoraemonSanboxDetailViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"文件预览\");\n    \n    if (self.filePath.length > 0) {\n        NSString *path = self.filePath;\n        if ([path hasSuffix:@\".strings\"] || [path hasSuffix:@\".plist\"]) {\n            // 文本文件\n            [self setContent:[[NSDictionary dictionaryWithContentsOfFile:path] description]];\n        } else if ([path hasSuffix:@\".DB\"] || [path hasSuffix:@\".db\"] || [path hasSuffix:@\".sqlite\"] || [path hasSuffix:@\".SQLITE\"] || [self isSQLiteFile:self.filePath]) {\n            // 数据库文件\n            self.title = DoraemonLocalizedString(@\"数据库预览\");\n            [self browseDBTable];\n        } else if ([[path lowercaseString] hasSuffix:@\".webp\"]) {\n            // webp文件\n            DoraemonWebpHandleBlock block = [DoraemonManager shareInstance].webpHandleBlock;\n            if (block) {\n                UIImage *img = [DoraemonManager shareInstance].webpHandleBlock(path);\n                [self setOriginalImage:img];\n            } else {\n                [self setContent:@\"webp need implement webpHandleBlock in DoraemonManager\"];\n            }\n        } else {\n            // 其他文件 尝试使用 QLPreviewController 进行打开\n            QLPreviewController *previewController = [[QLPreviewController alloc] init];\n            previewController.delegate = self;\n            previewController.dataSource = self;\n            [self presentViewController:previewController animated:YES completion:nil];\n        }\n    } else {\n        [DoraemonToastUtil showToast:DoraemonLocalizedString(@\"文件不存在\") inView:self.view];\n    }\n}\n\n- (void)setContent:(NSString *)text {\n    _textView = [[UITextView alloc] initWithFrame:self.view.bounds];\n    _textView.font = [UIFont systemFontOfSize:12.0f];\n    _textView.textColor = [UIColor blackColor];\n    _textView.textAlignment = NSTextAlignmentLeft;\n    _textView.editable = NO;\n    _textView.dataDetectorTypes = UIDataDetectorTypeLink;\n    _textView.scrollEnabled = YES;\n    _textView.backgroundColor = [UIColor whiteColor];\n    _textView.layer.borderColor = [UIColor grayColor].CGColor;\n    _textView.layer.borderWidth = 2.0f;\n    _textView.text = text;\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        _textView.textColor = [UIColor labelColor];\n        _textView.backgroundColor = [UIColor systemBackgroundColor];\n    } else {\n#endif\n        _textView.textColor = [UIColor blackColor];\n        _textView.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n    [self.view addSubview:_textView];\n}\n\n- (void)setOriginalImage:(UIImage *)originalImage{\n    if (!originalImage) {\n        return;\n    }\n    CGFloat viewWidth = self.view.doraemon_width;\n    CGFloat viewHeight = self.view.doraemon_height;\n    CGFloat imageWidth = originalImage.size.width;\n    CGFloat imageHeight = originalImage.size.height;\n    BOOL isPortrait = imageHeight / viewHeight > imageWidth / viewWidth;\n    CGFloat scaledImageWidth, scaledImageHeight;\n    CGFloat x,y;\n    CGFloat imageScale;\n    if (isPortrait) {//图片竖屏分量比较大\n        imageScale = imageHeight / viewHeight;\n        scaledImageHeight = viewHeight;\n        scaledImageWidth = imageWidth / imageScale;\n        x = (viewWidth - scaledImageWidth) / 2;\n        y = 0;\n    }else{//图片横屏分量比较大\n        imageScale = imageWidth / viewWidth;\n        scaledImageWidth = viewWidth;\n        scaledImageHeight = imageHeight / imageScale;\n        x = 0;\n        y = (viewHeight - scaledImageHeight) / 2;\n    }\n    _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(x, y, scaledImageWidth, scaledImageHeight)];\n    _imageView.image = originalImage;\n    _imageView.userInteractionEnabled = YES;\n    [self.view addSubview:_imageView];\n}\n\n//浏览数据库中所有数据表\n- (void)browseDBTable{\n    [DoraemonDBManager shareManager].dbPath = self.filePath;\n    self.tableNameArray = [[DoraemonDBManager shareManager] tablesAtDB];\n    self.dbTableNameTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.doraemon_width, self.view.doraemon_height) style:UITableViewStylePlain];\n//    self.dbTableNameTableView.backgroundColor = [UIColor whiteColor];\n    self.dbTableNameTableView.delegate = self;\n    self.dbTableNameTableView.dataSource = self;\n    [self.view addSubview:self.dbTableNameTableView];\n}\n\n#pragma mark - UITableViewDelegate,UITableViewDataSource\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return self.tableNameArray.count;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"db_table_name\";\n    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];\n    }\n    cell.textLabel.text = self.tableNameArray[indexPath.row];\n    return cell;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    [tableView deselectRowAtIndexPath:indexPath animated:YES];\n    NSString *tableName = [self.tableNameArray objectAtIndex:indexPath.row];\n    [DoraemonDBManager shareManager].tableName = tableName;\n    \n    DoraemonDBTableViewController *vc = [[DoraemonDBTableViewController alloc] init];\n    [self.navigationController pushViewController:vc animated:YES];\n}\n\n\n#pragma mark - QLPreviewControllerDataSource, QLPreviewControllerDelegate\n- (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller{\n    return 1;\n}\n- (id)previewController:(QLPreviewController *)controller previewItemAtIndex:(NSInteger)index{\n    return [NSURL fileURLWithPath:self.filePath];\n}\n- (void)previewControllerDidDismiss:(QLPreviewController *)controller {\n    [self leftNavBackClick:nil];\n}\n\n#pragma mark - Private Methods\n- (BOOL)isSQLiteFile:(NSString *)file {\n    NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:file];\n    if (!fileHandle) {\n        return NO;\n    }\n    NSData *data = [fileHandle readDataOfLength:16];\n    NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];\n    [fileHandle closeFile];\n    if ([str isEqual:@\"SQLite format 3\\0\"]) {\n        return YES;\n    } else {\n        return NO;\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DoraemonSandboxCell.h",
    "content": "//\n//  DoraemonSandboxCell.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import <UIKit/UIKit.h>\n@class DoraemonSandboxModel;\n\n@interface DoraemonSandBoxCell : UITableViewCell\n\n- (void)renderUIWithData : (DoraemonSandboxModel *)model;\n\n+ (CGFloat)cellHeight;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DoraemonSandboxCell.m",
    "content": "//\n//  DoraemonSandboxCell.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import \"DoraemonSandboxCell.h\"\n#import \"DoraemonSandboxModel.h\"\n#import \"DoraemonUtil.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n#import \"UIImage+Doraemon.h\"\n#import \"UIColor+Doraemon.h\"\n\n@interface DoraemonSandBoxCell()\n\n@property (nonatomic, strong) UIImageView *fileTypeIcon;\n@property (nonatomic, strong) UILabel *fileTitleLabel;\n@property (nonatomic, strong) UILabel *fileSizeLabel;\n\n@end\n\n@implementation DoraemonSandBoxCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        self.selectionStyle = UITableViewCellSelectionStyleNone;\n        \n        self.fileTypeIcon = [[UIImageView alloc] init];\n        [self.contentView addSubview:self.fileTypeIcon];\n        \n        self.fileTitleLabel = [[UILabel alloc] init];\n        self.fileTitleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        self.fileSizeLabel.textColor = [UIColor doraemon_black_1];\n        [self.contentView addSubview:self.fileTitleLabel];\n        \n        self.fileSizeLabel = [[UILabel alloc] init];\n        self.fileSizeLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        self.fileSizeLabel.textColor = [UIColor doraemon_black_2];\n        [self.contentView addSubview:self.fileSizeLabel];\n    }\n    return self;\n}\n\n- (void)renderUIWithData : (DoraemonSandboxModel *)model{\n    NSString *iconName = nil;\n    if (model.type == DoraemonSandboxFileTypeDirectory) {\n        iconName = @\"doraemon_dir\";\n    }else{\n        iconName = @\"doraemon_file_2\";\n    }\n    self.fileTypeIcon.image = [UIImage doraemon_xcassetImageNamed:iconName];\n    [self.fileTypeIcon sizeToFit];\n    self.fileTypeIcon.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), [[self class] cellHeight]/2-self.fileTypeIcon.doraemon_height/2, self.fileTypeIcon.doraemon_width, self.fileTypeIcon.doraemon_height);\n    \n    self.fileTitleLabel.text = model.name;\n    self.fileTitleLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;\n    [self.fileTitleLabel sizeToFit];\n    self.fileTitleLabel.frame = CGRectMake(self.fileTypeIcon.doraemon_right+kDoraemonSizeFrom750_Landscape(32), [[self class] cellHeight]/2-self.fileTitleLabel.doraemon_height/2, DoraemonScreenWidth-150, self.fileTitleLabel.doraemon_height);\n\n    DoraemonUtil *util = [[DoraemonUtil alloc] init];\n    [util getFileSizeWithPath:model.path];\n    NSInteger fileSize = util.fileSize;\n    //将文件夹大小转换为 M/KB/B\n    NSString *fileSizeStr = nil;\n    if (fileSize > 1024 * 1024){\n        fileSizeStr = [NSString stringWithFormat:@\"%.2fM\",fileSize / 1024.00f /1024.00f];\n        \n    }else if (fileSize > 1024){\n        fileSizeStr = [NSString stringWithFormat:@\"%.2fKB\",fileSize / 1024.00f ];\n        \n    }else{\n        fileSizeStr = [NSString stringWithFormat:@\"%.2fB\",fileSize / 1.00f];\n    }\n    \n    self.fileSizeLabel.text = fileSizeStr;\n    [self.fileSizeLabel sizeToFit];\n    self.fileSizeLabel.frame = CGRectMake(DoraemonScreenWidth-15-self.fileSizeLabel.doraemon_width, [[self class] cellHeight]/2-self.fileSizeLabel.doraemon_height/2, self.fileSizeLabel.doraemon_width, self.fileSizeLabel.doraemon_height);\n}\n\n+ (CGFloat)cellHeight{\n    return 48.;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DoraemonSandboxModel.h",
    "content": "//\n//  DoraemonSandboxModel.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import <Foundation/Foundation.h>\n\ntypedef NS_ENUM(NSInteger, DoraemonSandboxFileType) {\n    DoraemonSandboxFileTypeDirectory = 0,//目录\n    DoraemonSandboxFileTypeFile,//文件\n    DoraemonSandboxFileTypeBack,//返回\n    DoraemonSandboxFileTypeRoot,//根目录\n};\n\n@interface DoraemonSandboxModel : NSObject\n\n@property (nonatomic, copy) NSString *name;\n@property (nonatomic, copy) NSString *path;\n@property (nonatomic, assign) DoraemonSandboxFileType type;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DoraemonSandboxModel.m",
    "content": "//\n//  DoraemonSandboxModel.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import \"DoraemonSandboxModel.h\"\n\n@implementation DoraemonSandboxModel\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DoraemonSandboxViewController.h",
    "content": "//\n//  DoraemonSandboxViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonSandboxViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Common/Sanbox/VC/DoraemonSandboxViewController.m",
    "content": "//\n//  DoraemonSandboxViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import \"DoraemonSandboxViewController.h\"\n#import \"DoraemonSandboxModel.h\"\n#import \"DoraemonSanboxDetailViewController.h\"\n#import \"DoraemonNavBarItemModel.h\"\n#import \"DoraemonAppInfoUtil.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonSandboxCell.h\"\n#import \"DoraemonUtil.h\"\n\n@interface DoraemonSandboxViewController () <UITableViewDelegate, UITableViewDataSource>\n\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, strong) DoraemonSandboxModel *currentDirModel;\n@property (nonatomic, copy) NSArray *dataArray;\n@property (nonatomic, copy) NSString *rootPath;\n\n@property (nonatomic, strong) DoraemonNavBarItemModel *leftModel;\n\n@end\n\n@implementation DoraemonSandboxViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    [self initData];\n    [self initUI];\n}\n\n- (void)viewWillAppear:(BOOL)animated {\n    [super viewWillAppear:animated];\n    [self loadPath:_currentDirModel.path];\n}\n\n- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {\n    [super traitCollectionDidChange:previousTraitCollection];\n    // trait发生了改变\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {\n            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                self.leftModel.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_back_dark\"];\n            } else {\n                self.leftModel.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_back\"];\n            }\n        }\n    }\n#endif\n}\n\n- (BOOL)needBigTitleView {\n    return YES;\n}\n\n- (void)initData {\n    _dataArray = @[];\n    _rootPath = NSHomeDirectory();\n}\n\n- (void)initUI {\n    self.title = DoraemonLocalizedString(@\"沙盒浏览器\");\n    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, self.view.doraemon_height-self.bigTitleView.doraemon_bottom) style:UITableViewStylePlain];\n    self.tableView.delegate = self;\n    self.tableView.dataSource = self;\n    [self.view addSubview:self.tableView];\n}\n\n\n- (void)loadPath:(NSString *)filePath {\n    NSFileManager *fm = [NSFileManager defaultManager];\n    NSString *targetPath = filePath;\n    //该目录信息\n    DoraemonSandboxModel *model = [[DoraemonSandboxModel alloc] init];\n    if (!targetPath || [targetPath isEqualToString:_rootPath]) {\n        targetPath = _rootPath;\n        model.name = DoraemonLocalizedString(@\"根目录\");\n        model.type = DoraemonSandboxFileTypeRoot;\n        self.tableView.frame = CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, self.view.doraemon_height-self.bigTitleView.doraemon_bottom);\n        self.bigTitleView.hidden = NO;\n        self.navigationController.navigationBarHidden = YES;\n        [self setLeftNavBarItems:nil];\n    }else{\n        model.name = DoraemonLocalizedString(@\"返回上一级\");\n        model.type = DoraemonSandboxFileTypeBack;\n        self.bigTitleView.hidden = YES;\n        self.navigationController.navigationBarHidden = NO;\n        self.tableView.frame = CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT, self.view.doraemon_width, self.view.doraemon_height-IPHONE_NAVIGATIONBAR_HEIGHT);\n        NSString *dirTitle =  [fm displayNameAtPath:targetPath];\n        self.title = dirTitle;\n        UIImage *image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_back\"];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_back_dark\"];\n            }\n        }\n#endif\n        self.leftModel = [[DoraemonNavBarItemModel alloc] initWithImage:image selector:@selector(leftNavBackClick:)];\n        \n        [self setLeftNavBarItems:@[self.leftModel]];\n    }\n    model.path = filePath;\n    _currentDirModel = model;\n    \n    \n    //该目录下面的内容信息\n    NSMutableArray *files = @[].mutableCopy;\n    NSError *error = nil;\n    NSArray *paths = [fm contentsOfDirectoryAtPath:targetPath error:&error];\n    for (NSString *path in paths) {\n        BOOL isDir = false;\n        NSString *fullPath = [targetPath stringByAppendingPathComponent:path];\n        [fm fileExistsAtPath:fullPath isDirectory:&isDir];\n        \n        DoraemonSandboxModel *model = [[DoraemonSandboxModel alloc] init];\n        model.path = fullPath;\n        if (isDir) {\n            model.type = DoraemonSandboxFileTypeDirectory;\n        }else{\n            model.type = DoraemonSandboxFileTypeFile;\n        }\n        model.name = path;\n        \n        [files addObject:model];\n    }\n    \n    //_dataArray = files.copy;\n    \n    // 按名称排序，并保持文件夹在上\n    _dataArray = [files sortedArrayUsingComparator:^NSComparisonResult(DoraemonSandboxModel * _Nonnull obj1, DoraemonSandboxModel * _Nonnull obj2) {\n        \n        BOOL isObj1Directory = (obj1.type == DoraemonSandboxFileTypeDirectory);\n        BOOL isObj2Directory = (obj2.type == DoraemonSandboxFileTypeDirectory);\n        \n        // 都是目录 或 都不是目录\n        BOOL isSameType = ((isObj1Directory && isObj2Directory) || (!isObj1Directory && !isObj2Directory));\n        \n        if (isSameType) { // 都是目录 或 都不是目录\n            \n            // 按名称排序\n            return [obj1.name.lowercaseString compare:obj2.name.lowercaseString];\n        }\n        \n        // 以下是一个为目录，一个不为目录的情况\n        \n        if (isObj1Directory) { // obj1是目录\n            \n            // 升序，保持文件夹在上\n            return NSOrderedAscending;\n        }\n        \n        // obj2是目录，降序\n        return NSOrderedDescending;\n    }];\n    \n    [self.tableView reloadData];\n}\n\n\n#pragma mark- UITableViewDelegate\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return _dataArray.count;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *cellId = @\"cellId\";\n    DoraemonSandBoxCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];\n    if (!cell) {\n        cell = [[DoraemonSandBoxCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];\n    }\n    DoraemonSandboxModel *model = _dataArray[indexPath.row];\n    [cell renderUIWithData:model];\n    return cell;\n}\n\n- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{\n    return YES;\n}\n\n- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath{\n    return DoraemonLocalizedString(@\"删除\");\n}\n\n- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonSandboxModel *model = _dataArray[indexPath.row];\n    [self deleteByDoraemonSandboxModel:model];\n}\n\n\n#pragma mark- UITableViewDataSource\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return [DoraemonSandBoxCell cellHeight];\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonSandboxModel *model = _dataArray[indexPath.row];\n    if (model.type == DoraemonSandboxFileTypeFile) {\n        [self handleFileWithPath:model.path];\n    } else if (model.type == DoraemonSandboxFileTypeDirectory) {\n        [self loadPath:model.path];\n    }\n}\n\n\n- (void)leftNavBackClick:(id)clickView {\n    if (_currentDirModel.type == DoraemonSandboxFileTypeRoot) {\n        [super leftNavBackClick:clickView];\n    } else {\n        [self loadPath:[_currentDirModel.path stringByDeletingLastPathComponent]];\n    }\n}\n\n- (void)handleFileWithPath:(NSString *)filePath {\n    UIAlertControllerStyle style;\n    if ([DoraemonAppInfoUtil isIpad]) {\n        style = UIAlertControllerStyleAlert;\n    } else {\n        style = UIAlertControllerStyleActionSheet;\n    }\n    \n    UIAlertController *alertVc = [UIAlertController alertControllerWithTitle:DoraemonLocalizedString(@\"请选择操作方式\") message:nil preferredStyle:style];\n    __weak typeof(self) weakSelf = self;\n    UIAlertAction *previewAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"本地预览\") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n        __strong typeof(self) strongSelf = weakSelf;\n        [strongSelf previewFile:filePath];\n    }];\n    UIAlertAction *shareAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"分享\") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n        __strong typeof(self) strongSelf = weakSelf;\n        [strongSelf shareFileWithPath:filePath];\n    }];\n    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"取消\") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {\n    }];\n    [alertVc addAction:previewAction];\n    [alertVc addAction:shareAction];\n    [alertVc addAction:cancelAction];\n    \n    [self presentViewController:alertVc animated:YES completion:nil];\n}\n\n- (void)previewFile:(NSString *)filePath {\n    DoraemonSanboxDetailViewController *detalVc = [[DoraemonSanboxDetailViewController alloc] init];\n    detalVc.filePath = filePath;\n    [self.navigationController pushViewController:detalVc animated:YES];\n}\n\n\n- (void)shareFileWithPath:(NSString *)filePath {\n    [DoraemonUtil shareURL:[NSURL fileURLWithPath:filePath] formVC:self];\n}\n\n- (void)deleteByDoraemonSandboxModel:(DoraemonSandboxModel *)model {\n    NSFileManager *fm = [NSFileManager defaultManager];\n    [fm removeItemAtPath:model.path error:nil];\n    [self loadPath:_currentDirModel.path];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/Detail/DoraemonANRDetailViewController.h",
    "content": "//\n//  DoraemonANRDetailViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/16.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonANRDetailViewController : DoraemonBaseViewController\n\n@property (nonatomic, strong) NSString *filePath;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/Detail/DoraemonANRDetailViewController.m",
    "content": "//\n//  DoraemonANRDetailViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/16.\n//\n\n#import \"DoraemonANRDetailViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonUtil.h\"\n\n@interface DoraemonANRDetailViewController ()\n\n@property (nonatomic, strong) UILabel *anrTimeLabel;\n@property (nonatomic, strong) UILabel *contentLabel;\n@property (nonatomic, strong) NSDictionary *anrInfo;\n\n@end\n\n@implementation DoraemonANRDetailViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"卡顿详情\");\n    [self setRightNavTitle:DoraemonLocalizedString(@\"导出\")];\n    \n    self.anrInfo = [NSDictionary dictionaryWithContentsOfFile:self.filePath];\n    \n    _contentLabel = [[UILabel alloc] init];\n    _contentLabel.textColor = [UIColor doraemon_black_2];\n    _contentLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(16)];\n    _contentLabel.numberOfLines = 0;\n    _contentLabel.text = _anrInfo[@\"content\"];\n    \n    CGSize fontSize = [_contentLabel sizeThatFits:CGSizeMake(self.view.doraemon_width-40, MAXFLOAT)];\n    _contentLabel.frame = CGRectMake(20, IPHONE_NAVIGATIONBAR_HEIGHT, fontSize.width, fontSize.height);\n    [self.view addSubview:_contentLabel];\n    \n    _anrTimeLabel = [[UILabel alloc] init];\n    _anrTimeLabel.textColor = [UIColor doraemon_black_1];\n    _anrTimeLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(16)];\n    _anrTimeLabel.text = [NSString stringWithFormat:@\"anr time : %@ms\",_anrInfo[@\"duration\"]];\n    [_anrTimeLabel sizeToFit];\n    _anrTimeLabel.frame = CGRectMake(20, _contentLabel.doraemon_bottom+20, _anrTimeLabel.doraemon_width, _anrTimeLabel.doraemon_height);\n    [self.view addSubview:_anrTimeLabel];\n    \n    \n}\n\n- (void)rightNavTitleClick:(id)clickView{\n    [DoraemonUtil shareURL:[NSURL fileURLWithPath:self.filePath] formVC:self];\n}\n\n\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/DoraemonANRPlugin.h",
    "content": "//\n//  DoraemonANRPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/13.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonANRPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/DoraemonANRPlugin.m",
    "content": "//\n//  DoraemonANRPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/13.\n//\n\n#import \"DoraemonANRPlugin.h\"\n#import \"DoraemonANRViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonANRPlugin\n\n- (void)pluginDidLoad{\n    DoraemonANRViewController *vc = [[DoraemonANRViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/DoraemonANRViewController.h",
    "content": "//\n//  DoraemonANRViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/13.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonANRViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/DoraemonANRViewController.m",
    "content": "//\n//  DoraemonANRViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/13.\n//\n\n#import \"DoraemonANRViewController.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonANRManager.h\"\n#import \"DoraemonCellButton.h\"\n#import \"DoraemonANRListViewController.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonANRTool.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonANRViewController () <DoraemonSwitchViewDelegate, DoraemonCellButtonDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n@property (nonatomic, strong) DoraemonCellButton *checkBtn;\n@property (nonatomic, strong) DoraemonCellButton *clearBtn;\n\n@end\n\n@implementation DoraemonANRViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"卡顿检测\");\n    \n    self.switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, 53)];\n    [self.switchView renderUIWithTitle:DoraemonLocalizedString(@\"卡顿检测开关\") switchOn:[DoraemonANRManager sharedInstance].anrTrackOn];\n    [self.switchView needTopLine];\n    [self.switchView needDownLine];\n    self.switchView.delegate = self;\n    [self.view addSubview:self.switchView];\n    \n    self.checkBtn = [[DoraemonCellButton alloc] initWithFrame:CGRectMake(0, _switchView.doraemon_bottom, self.view.doraemon_width, 53)];\n    [self.checkBtn renderUIWithTitle:DoraemonLocalizedString(@\"查看卡顿记录\")];\n    self.checkBtn.delegate = self;\n    [self.checkBtn needDownLine];\n    [self.view addSubview:self.checkBtn];\n    \n    self.clearBtn = [[DoraemonCellButton alloc] initWithFrame:CGRectMake(0, self.checkBtn.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [self.clearBtn renderUIWithTitle:DoraemonLocalizedString(@\"一键清理卡顿记录\")];\n    self.clearBtn.delegate = self;\n    [self.clearBtn needDownLine];\n    [self.view addSubview:self.clearBtn];\n}\n\n- (BOOL)needBigTitleView {\n    return YES;\n}\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender {\n    [DoraemonANRManager sharedInstance].anrTrackOn = on;\n    if (on) {\n        [[DoraemonANRManager sharedInstance] start];\n    } else {\n        [[DoraemonANRManager sharedInstance] stop];\n    }\n}\n\n#pragma mark -- DoraemonCellButtonDelegate\n- (void)cellBtnClick:(id)sender {\n    if (sender == self.checkBtn) {\n        DoraemonANRListViewController *vc = [[DoraemonANRListViewController alloc] init];\n        [self.navigationController pushViewController:vc animated:YES];\n    } else if (sender == self.clearBtn) {\n           UIAlertController * alertController = [UIAlertController alertControllerWithTitle:DoraemonLocalizedString(@\"提示\") message:DoraemonLocalizedString(@\"确认删除所有卡顿记录吗？\") preferredStyle:UIAlertControllerStyleAlert];\n           UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"取消\") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {\n           }];\n           UIAlertAction *okAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"确定\") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n               NSFileManager *fm = [NSFileManager defaultManager];\n               if ([fm removeItemAtPath:[DoraemonANRTool anrDirectory] error:nil]) {\n                   [DoraemonToastUtil showToast:DoraemonLocalizedString(@\"删除成功\") inView:self.view];\n               } else {\n                   [DoraemonToastUtil showToast:DoraemonLocalizedString(@\"删除失败\") inView:self.view];\n               }\n           }];\n           [alertController addAction:cancelAction];\n           [alertController addAction:okAction];\n           [self presentViewController:alertController animated:YES completion:nil];\n       }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/Function/DoraemonANRManager.h",
    "content": "//\n//  DoraemonANRManager.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/14.\n//\n#import <UIKit/UIKit.h>\n#import <Foundation/Foundation.h>\n\ntypedef void (^DoraemonANRManagerBlock)(NSDictionary *anrInfo);\n\n@interface DoraemonANRManager : NSObject\n\n+ (instancetype)sharedInstance;\n\n@property (nonatomic, assign) BOOL anrTrackOn; \n\n/*\n 卡顿时长阈值，单位为秒，\n */\n@property (nonatomic, assign) CGFloat timeOut;\n\n- (void)addANRBlock:(DoraemonANRManagerBlock)block;\n\n- (void)start;\n- (void)stop;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/Function/DoraemonANRManager.m",
    "content": "//\n//  DoraemonANRManager.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/14.\n//\n\n#import \"DoraemonANRManager.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonANRTracker.h\"\n#import \"DoraemonMemoryUtil.h\"\n#import \"DoraemonAppInfoUtil.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonANRTool.h\"\n#import \"DoraemonHealthManager.h\"\n\n//默认超时间隔\nstatic CGFloat const kDoraemonBlockMonitorTimeInterval = 0.2f;\n\n@interface DoraemonANRManager()\n\n@property (nonatomic, strong) DoraemonANRTracker *doraemonANRTracker;\n@property (nonatomic, copy) DoraemonANRManagerBlock block;\n\n@end\n\n@implementation DoraemonANRManager\n\n+ (instancetype)sharedInstance {\n    static id instance = nil;\n    static dispatch_once_t onceToken;\n    \n    dispatch_once(&onceToken, ^{\n        instance = [[self alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init {\n    self = [super init];\n    \n    if (self) {\n        _doraemonANRTracker = [[DoraemonANRTracker alloc] init];\n        _timeOut = kDoraemonBlockMonitorTimeInterval;\n        _anrTrackOn = [DoraemonCacheManager sharedInstance].anrTrackSwitch;\n        if (_anrTrackOn) {\n            [self start];\n        } else {\n            [self stop];\n            // 如果是关闭的话，删除上一次的卡顿记录\n            NSFileManager *fm = [NSFileManager defaultManager];\n            [fm removeItemAtPath:[DoraemonANRTool anrDirectory] error:nil];\n        }\n    }\n    \n    return self;\n}\n\n- (void)start {\n    __weak typeof(self) weakSelf = self;\n    [_doraemonANRTracker startWithThreshold:self.timeOut handler:^(NSDictionary *info) {\n        __strong typeof(weakSelf) strongSelf = weakSelf;\n        [strongSelf dumpWithInfo:info];\n    }];\n}\n\n- (void)dumpWithInfo:(NSDictionary *)info {\n    if (![info isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    dispatch_async(dispatch_get_main_queue(), ^{\n        [[DoraemonHealthManager sharedInstance] addANRInfo:info];\n        if (self.block) {\n            self.block(info);\n        }\n        [DoraemonANRTool saveANRInfo:info];\n    });\n\n}\n\n- (void)addANRBlock:(DoraemonANRManagerBlock)block{\n    self.block = block;\n}\n\n\n- (void)dealloc {\n    [self stop];\n}\n\n- (void)stop {\n    [self.doraemonANRTracker stop];\n}\n\n- (void)setAnrTrackOn:(BOOL)anrTrackOn {\n    _anrTrackOn = anrTrackOn;\n    [[DoraemonCacheManager sharedInstance] saveANRTrackSwitch:anrTrackOn];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/Function/DoraemonANRTool.h",
    "content": "//\n//  DoraemonANRTool.h\n//  DoraemonKit\n//\n//  Created by DeveloperLY on 2019/9/18.\n//\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonANRTool : NSObject\n\n/**\n 保存卡顿记录到沙盒中的Library/Caches/ANR目录下\n \n @param info 卡顿信息的内容\n */\n+ (void)saveANRInfo:(NSDictionary *)info;\n\n/**\n 获取卡顿记录的目录\n\n @return 卡顿记录的目录\n */\n+ (NSString *)anrDirectory;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/Function/DoraemonANRTool.m",
    "content": "//\n//  DoraemonANRTool.m\n//  DoraemonKit\n//   \n//  Created by DeveloperLY on 2019/9/18.\n//  \n//\n\n#import \"DoraemonANRTool.h\"\n\n@implementation DoraemonANRTool\n\n+ (void)saveANRInfo:(NSDictionary *)info {\n    if ([info isKindOfClass:[NSDictionary class]] && (info.count > 0)) {\n        NSFileManager *manager = [NSFileManager defaultManager];\n        NSString *anrDirectory = [self anrDirectory];\n        if (anrDirectory && [manager fileExistsAtPath:anrDirectory]) {\n            // 获取 ANR 保存的路径\n            NSString *anrPath = [anrDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@\"ANR %@.plist\", info[@\"title\"]]];\n            [info writeToFile:anrPath atomically:YES];\n        }\n    }\n}\n\n+ (NSString *)anrDirectory {\n    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];\n    NSString *directory = [cachePath stringByAppendingPathComponent:@\"ANR\"];\n    \n    NSFileManager *manager = [NSFileManager defaultManager];\n    if (![manager fileExistsAtPath:directory]) {\n        [manager createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:nil];\n    }\n    \n    return directory;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/Function/DoraemonANRTracker.h",
    "content": "//\n//  DoraemonANRTracker.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/14.\n//\n\n// 参考ONEANRTracker\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPingThread.h\"\n\n//ANR监控状态枚举\ntypedef NS_ENUM(NSUInteger, DoraemonANRTrackerStatus) {\n    DoraemonANRTrackerStatusStart, //监控开启\n    DoraemonANRTrackerStatusStop,  //监控停止\n};\n\n/**\n *  主线程卡顿监控类\n */\n@interface DoraemonANRTracker : NSObject\n\n/**\n *  开始监控\n *\n *  @param threshold 卡顿阈值\n *  @param handler   监控到卡顿回调(回调时会自动暂停卡顿监控)\n */\n- (void)startWithThreshold:(double)threshold\n                   handler:(DoraemonANRTrackerBlock)handler;\n\n/**\n *  停止监控\n */\n- (void)stop;\n\n/**\n *  ANR监控状态\n */\n- (DoraemonANRTrackerStatus)status;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/Function/DoraemonANRTracker.m",
    "content": "//\n//  DoraemonANRTracker.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/14.\n//\n\n#import \"DoraemonANRTracker.h\"\n#import \"sys/utsname.h\"\n\n/**\n *  主线程卡顿监控看门狗类\n */\n@interface DoraemonANRTracker()\n\n/**\n *  用于Ping主线程的线程实例\n */\n@property (nonatomic, strong) DoraemonPingThread *pingThread;\n\n@end\n\n@implementation DoraemonANRTracker\n\n- (instancetype)init\n{\n    self = [super init];\n    if (self) {\n    }\n    return self;\n}\n\n- (void)startWithThreshold:(double)threshold\n                   handler:(DoraemonANRTrackerBlock)handler {\n    \n    self.pingThread = [[DoraemonPingThread alloc] initWithThreshold:threshold\n                                                            handler:^(NSDictionary *info) {\n                                                                handler(info);\n                                                            }];\n    \n    [self.pingThread start];\n}\n\n- (void)stop {\n    if (self.pingThread != nil) {\n        [self.pingThread cancel];\n        self.pingThread = nil;\n    }\n}\n\n- (DoraemonANRTrackerStatus)status {\n    if (self.pingThread != nil && self.pingThread.isCancelled != YES) {\n        return DoraemonANRTrackerStatusStart;\n    }else {\n        return DoraemonANRTrackerStatusStop;\n    }\n}\n\n- (void)dealloc\n{\n    [self.pingThread cancel];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/Function/DoraemonPingThread.h",
    "content": "//\n//  DoraemonPingThread.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/14.\n//\n\n// 参考ONEANRTracker\n\n#import <Foundation/Foundation.h>\n\ntypedef void (^DoraemonANRTrackerBlock)(NSDictionary *info);\n\n/**\n *  用于Ping主线程的线程类\n *  通过信号量控制来Ping主线程，判断主线程是否卡顿\n */\n@interface DoraemonPingThread : NSThread\n\n/**\n *  初始化Ping主线程的线程类\n *\n *  @param threshold 主线程卡顿阈值\n *  @param handler   监控到卡顿回调\n */\n- (instancetype)initWithThreshold:(double)threshold\n                          handler:(DoraemonANRTrackerBlock)handler;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/Function/DoraemonPingThread.m",
    "content": "//\n//  DoraemonPingThread.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/14.\n//\n\n#import \"DoraemonPingThread.h\"\n#import <UIKit/UIKit.h>\n#import \"DoraemonUtil.h\"\n#import \"DoraemonBacktraceLogger.h\"\n\n@interface DoraemonPingThread()\n\n/**\n *  应用是否在活跃状态\n */\n@property (nonatomic, assign) BOOL isApplicationInActive;\n\n/**\n *  控制ping主线程的信号量\n */\n@property (nonatomic, strong) dispatch_semaphore_t semaphore;\n\n/**\n *  卡顿阈值\n */\n@property (nonatomic, assign) double threshold;\n\n/**\n *  卡顿回调\n */\n@property (nonatomic, copy) DoraemonANRTrackerBlock handler;\n\n/**\n *  主线程是否阻塞\n */\n@property (nonatomic, assign,getter=isMainThreadBlock) BOOL mainThreadBlock;\n/**\n *  判断是否需要上报\n */\n@property (nonatomic, copy) NSString *reportInfo;\n/**\n *  每一次ping开始的时间,上报延迟时间统计\n */\n@property (nonatomic, assign) double startTimeValue;\n@end\n\n@implementation DoraemonPingThread\n\n- (instancetype)initWithThreshold:(double)threshold\n                          handler:(DoraemonANRTrackerBlock)handler {\n    self = [super init];\n    if (self) {\n        self.semaphore = dispatch_semaphore_create(0);\n        \n        self.threshold = threshold;\n        self.handler = handler;\n        _isApplicationInActive = YES;\n        \n        [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationDidBecomeActive) name: UIApplicationDidBecomeActiveNotification object: nil];\n        [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationDidEnterBackground) name: UIApplicationDidEnterBackgroundNotification object: nil];\n    }\n    return self;\n}\n\n- (void)dealloc {\n    [[NSNotificationCenter defaultCenter] removeObserver: self];\n}\n\n- (void)main {\n    //判断是否需要上报\n    __weak typeof(self) weakSelf = self;\n    void (^ verifyReport)(void) = ^() {\n        __strong typeof(weakSelf) strongSelf = weakSelf;\n        if (strongSelf.reportInfo.length > 0) {\n            if (strongSelf.handler) {\n                double responseTimeValue = [[NSDate date] timeIntervalSince1970];\n                double duration = (responseTimeValue - strongSelf.startTimeValue)*1000;\n                strongSelf.handler(@{\n                                     @\"title\": [DoraemonUtil dateFormatNow].length > 0 ? [DoraemonUtil dateFormatNow] : @\"\",\n                                     @\"duration\": [NSString stringWithFormat:@\"%.0f\",duration],//单位ms\n                                     @\"content\": strongSelf.reportInfo\n                                     });\n            }\n            strongSelf.reportInfo = @\"\";\n        }\n    };\n    \n    while (!self.cancelled) {\n        if (_isApplicationInActive) {\n            self.mainThreadBlock = YES;\n            self.reportInfo = @\"\";\n            self.startTimeValue = [[NSDate date] timeIntervalSince1970];\n            dispatch_async(dispatch_get_main_queue(), ^{\n                self.mainThreadBlock = NO;\n                verifyReport();\n                dispatch_semaphore_signal(self.semaphore);\n            });\n            [NSThread sleepForTimeInterval:self.threshold];\n            if (self.isMainThreadBlock) {\n                self.reportInfo = [DoraemonBacktraceLogger doraemon_backtraceOfMainThread];\n            }\n            dispatch_semaphore_wait(self.semaphore, dispatch_time(DISPATCH_TIME_NOW, 5.0 * NSEC_PER_SEC));\n            {\n                //卡顿超时情况;\n                verifyReport();\n            }\n        } else {\n            [NSThread sleepForTimeInterval:self.threshold];\n        }\n    }\n}\n\n#pragma mark - Notific ation\n- (void)applicationDidBecomeActive {\n    _isApplicationInActive = YES;\n}\n\n- (void)applicationDidEnterBackground {\n    _isApplicationInActive = NO;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/List/DoraemonANRListCell.h",
    "content": "//\n//  DoraemonANRListCell.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/15.\n//\n\n#import <UIKit/UIKit.h>\n\n@class DoraemonSandboxModel;\n\n@interface DoraemonANRListCell : UITableViewCell\n\n- (void)renderCellWithData:(DoraemonSandboxModel *)model;\n\n+ (CGFloat)cellHeight;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/List/DoraemonANRListCell.m",
    "content": "//\n//  DoraemonANRListCell.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/15.\n//\n\n#import \"DoraemonANRListCell.h\"\n#import \"DoraemonSandboxModel.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonANRListCell()\n\n\n@property (nonatomic, strong) UILabel *titleLabel;\n@property (nonatomic, strong) UIImageView *arrowImageView;\n\n@end\n\n@implementation DoraemonANRListCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        self.selectionStyle = UITableViewCellSelectionStyleNone;\n        \n        _titleLabel = [[UILabel alloc] init];\n        _titleLabel.textColor = [UIColor doraemon_black_1];\n        _titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        [self.contentView addSubview:_titleLabel];\n        \n        _arrowImageView = [[UIImageView alloc] initWithImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_more\"]];\n        _arrowImageView.frame = CGRectMake(DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)-_arrowImageView.doraemon_width, [[self class] cellHeight]/2-_arrowImageView.doraemon_height/2, _arrowImageView.doraemon_width, _arrowImageView.doraemon_height);\n        [self.contentView addSubview:_arrowImageView];\n    }\n    return self;\n}\n\n- (void)renderCellWithData:(DoraemonSandboxModel *)model {\n    self.titleLabel.text = @\"\";\n    if ([model.name isKindOfClass:[NSString class]] && (model.name.length > 0)) {\n        self.titleLabel.text = model.name;\n        [self.titleLabel sizeToFit];\n        CGFloat w = self.titleLabel.doraemon_width;\n        if (w > DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(120)) {\n            w = DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(120);\n        }\n        self.titleLabel.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), [[self class] cellHeight]/2-self.titleLabel.doraemon_height/2, w, self.titleLabel.doraemon_height);\n    } \n}\n\n+ (CGFloat)cellHeight{\n    return kDoraemonSizeFrom750_Landscape(104);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/List/DoraemonANRListViewController.h",
    "content": "//\n//  DoraemonANRListViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/15.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonANRListViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/ANR/List/DoraemonANRListViewController.m",
    "content": "//\n//  DoraemonANRListViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/15.\n//\n\n#import \"DoraemonANRListViewController.h\"\n#import \"DoraemonANRManager.h\"\n#import \"DoraemonANRListCell.h\"\n#import \"DoraemonANRDetailViewController.h\"\n#import \"DoraemonSandboxModel.h\"\n#import \"DoraemonANRTool.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonANRListViewController ()<UITableViewDelegate,UITableViewDataSource>\n\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, copy) NSArray *anrArray;\n\n@end\n\n@implementation DoraemonANRListViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"卡顿列表\");\n    \n    [self loadANRData];\n    \n    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT, self.view.doraemon_width, self.view.doraemon_height-IPHONE_NAVIGATIONBAR_HEIGHT) style:UITableViewStylePlain];\n//    self.tableView.backgroundColor = [UIColor whiteColor];\n    self.tableView.delegate = self;\n    self.tableView.dataSource = self;\n    [self.view addSubview:self.tableView];\n}\n\n#pragma mark ANRData\n- (void)loadANRData {\n    // 获取 ANR 目录\n    NSFileManager *manager = [NSFileManager defaultManager];\n    NSString *anrDirectory = [DoraemonANRTool anrDirectory];\n    \n    if (anrDirectory && [manager fileExistsAtPath:anrDirectory]) {\n        [self loadPath:anrDirectory];\n    }\n}\n\n- (void)loadPath:(NSString *)filePath {\n    NSFileManager *fm = [NSFileManager defaultManager];\n    NSString *targetPath = NSHomeDirectory();\n    if ([filePath isKindOfClass:[NSString class]] && (filePath.length > 0)) {\n        targetPath = filePath;\n    }\n    \n    // 该目录下面的内容信息\n    NSError *error = nil;\n    NSArray *paths = [fm contentsOfDirectoryAtPath:targetPath error:&error];\n    \n    // 对paths按照创建时间的降序进行排列\n    NSArray *sortedPaths = [paths sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {\n        if ([obj1 isKindOfClass:[NSString class]] && [obj2 isKindOfClass:[NSString class]]) {\n            // 获取文件完整路径\n            NSString *firstPath = [targetPath stringByAppendingPathComponent:obj1];\n            NSString *secondPath = [targetPath stringByAppendingPathComponent:obj2];\n            \n            // 获取文件信息\n            NSDictionary *firstFileInfo = [[NSFileManager defaultManager] attributesOfItemAtPath:firstPath error:nil];\n            NSDictionary *secondFileInfo = [[NSFileManager defaultManager] attributesOfItemAtPath:secondPath error:nil];\n            \n            // 获取文件创建时间\n            id firstData = [firstFileInfo objectForKey:NSFileCreationDate];\n            id secondData = [secondFileInfo objectForKey:NSFileCreationDate];\n            \n            // 按照创建时间降序排列\n            return [secondData compare:firstData];\n        }\n        return NSOrderedSame;\n    }];\n    \n    // 构造数据源\n    NSMutableArray *files = [NSMutableArray array];\n    [sortedPaths enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {\n        if ([obj isKindOfClass:[NSString class]]) {\n            NSString *sortedPath = obj;\n            \n            BOOL isDir = NO;\n            NSString *fullPath = [targetPath stringByAppendingPathComponent:sortedPath];\n            [fm fileExistsAtPath:fullPath isDirectory:&isDir];\n            \n            DoraemonSandboxModel *model = [[DoraemonSandboxModel alloc] init];\n            model.path = fullPath;\n            if (isDir) {\n                model.type = DoraemonSandboxFileTypeDirectory;\n            } else {\n                model.type = DoraemonSandboxFileTypeFile;\n            }\n            model.name = sortedPath;\n            \n            [files addObject:model];\n        }\n    }];\n    self.anrArray = files.copy;\n    \n    [self.tableView reloadData];\n}\n\n- (void)deleteByDoraemonSandboxModel:(DoraemonSandboxModel *)model {\n    NSFileManager *fm = [NSFileManager defaultManager];\n    [fm removeItemAtPath:model.path error:nil];\n    \n    [self loadANRData];\n}\n\n#pragma mark - UITableView Delegate\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {\n    return 1;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return self.anrArray.count;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return [DoraemonANRListCell cellHeight];\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {\n    return 0.1;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {\n    return 0.1;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"anrcell\";\n    DoraemonANRListCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[DoraemonANRListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];\n    } \n    \n    if (indexPath.row < self.anrArray.count) {\n        DoraemonSandboxModel *model = [self.anrArray objectAtIndex:indexPath.row];\n        [cell renderCellWithData:model];\n    }\n    \n    return cell;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    [tableView deselectRowAtIndexPath:indexPath animated:YES];\n    if (indexPath.row < self.anrArray.count) {\n        DoraemonSandboxModel *model = [self.anrArray objectAtIndex:indexPath.row];\n        if (model.type == DoraemonSandboxFileTypeFile) {\n            DoraemonANRDetailViewController *vc = [[DoraemonANRDetailViewController alloc] init];\n            vc.filePath = model.path;\n            [self.navigationController pushViewController:vc animated:YES];\n        } else if (model.type == DoraemonSandboxFileTypeDirectory) {\n            [self loadPath:model.path];\n        }\n    }\n}\n\n- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{\n    return YES;\n}\n\n- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath{\n    return DoraemonLocalizedString(@\"删除\");\n}\n\n- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{\n    if (indexPath.row < self.anrArray.count) {\n        DoraemonSandboxModel *model = self.anrArray[indexPath.row];\n        [self deleteByDoraemonSandboxModel:model];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/CPU/DoraemonCPUPlugin.h",
    "content": "//\n//  DoraemonCPUPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/12.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonCPUPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/CPU/DoraemonCPUPlugin.m",
    "content": "//\n//  DoraemonCPUPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/12.\n//\n\n#import \"DoraemonCPUPlugin.h\"\n#import \"DoraemonCPUViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonCPUPlugin\n\n- (void)pluginDidLoad{\n    DoraemonCPUViewController *vc = [[DoraemonCPUViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/CPU/DoraemonCPUViewController.h",
    "content": "//\n//  DoraemonCPUViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/12.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonCPUViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/CPU/DoraemonCPUViewController.m",
    "content": "//\n//  DoraemonCPUViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/12.\n//\n\n#import \"DoraemonCPUViewController.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonCPUOscillogramWindow.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonCPUOscillogramViewController.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonCPUViewController ()<DoraemonSwitchViewDelegate, DoraemonOscillogramWindowDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n\n@end\n\n@implementation DoraemonCPUViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"CPU检测\");\n    \n    _switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"CPU检测开关\") switchOn:[[DoraemonCacheManager sharedInstance] cpuSwitch]];\n    [_switchView needTopLine];\n    [_switchView needDownLine];\n    _switchView.delegate = self;\n    [self.view addSubview:_switchView];\n    [[DoraemonCPUOscillogramWindow shareInstance] addDelegate:self];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    [[DoraemonCacheManager sharedInstance] saveCpuSwitch:on];\n    if(on){\n        [[DoraemonCPUOscillogramWindow shareInstance] show];\n    }else{\n        [[DoraemonCPUOscillogramWindow shareInstance] hide];\n    }\n}\n\n#pragma mark -- DoraemonOscillogramWindowDelegate\n- (void)doraemonOscillogramWindowClosed {\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"CPU检测开关\") switchOn:[[DoraemonCacheManager sharedInstance] cpuSwitch]];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/CPU/Function/DoraemonCPUOscillogramViewController.h",
    "content": "//\n//  DoraemonCPUOscillogramViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/12.\n//\n\n#import \"DoraemonOscillogramViewController.h\"\n\n@interface DoraemonCPUOscillogramViewController : DoraemonOscillogramViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/CPU/Function/DoraemonCPUOscillogramViewController.m",
    "content": "//\n//  DoraemonCPUOscillogramViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/12.\n//\n\n#import \"DoraemonCPUOscillogramViewController.h\"\n#import \"DoraemonOscillogramView.h\"\n#import \"DoraemonCPUUtil.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonCPUOscillogramWindow.h\"\n\n@interface DoraemonCPUOscillogramViewController ()\n\n\n@end\n\n@implementation DoraemonCPUOscillogramViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n}\n\n- (NSString *)title{\n    return DoraemonLocalizedString(@\"CPU检测\");\n}\n\n- (NSString *)lowValue{\n    return @\"0\";\n}\n\n- (NSString *)highValue{\n    return @\"100\";\n}\n\n- (void)closeBtnClick{\n    [[DoraemonCacheManager sharedInstance] saveCpuSwitch:NO];\n    [[DoraemonCPUOscillogramWindow shareInstance] hide];\n}\n\n//每一秒钟采样一次cpu使用率\n- (void)doSecondFunction{\n    CGFloat cpuUsage = [DoraemonCPUUtil cpuUsageForApp];\n    if (cpuUsage * 100 > 100) {\n        cpuUsage = 100;\n    }else{\n        cpuUsage = cpuUsage * 100;\n    }\n    // 0~100   对应 高度0~_oscillogramView.doraemon_height\n    [self.oscillogramView addHeightValue:cpuUsage*self.oscillogramView.doraemon_height/100. andTipValue:[NSString stringWithFormat:@\"%.f\",cpuUsage]];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/CPU/Function/DoraemonCPUOscillogramWindow.h",
    "content": "//\n//  DoraemonCPUOscillogramWindow.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/12.\n//\n\n\n#import \"DoraemonOscillogramWindow.h\"\n\n@interface DoraemonCPUOscillogramWindow : DoraemonOscillogramWindow\n\n+ (DoraemonCPUOscillogramWindow *)shareInstance;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/CPU/Function/DoraemonCPUOscillogramWindow.m",
    "content": "//\n//  DoraemonCPUOscillogramWindow.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/12.\n//\n\n#import \"DoraemonCPUOscillogramWindow.h\"\n#import \"DoraemonCPUOscillogramViewController.h\"\n\n@implementation DoraemonCPUOscillogramWindow\n\n+ (DoraemonCPUOscillogramWindow *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonCPUOscillogramWindow *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonCPUOscillogramWindow alloc] initWithFrame:CGRectZero];\n    });\n    return instance;\n}\n\n- (void)addRootVc{\n    DoraemonCPUOscillogramViewController *vc = [[DoraemonCPUOscillogramViewController alloc] init];\n    self.rootViewController = vc;\n    self.vc = vc;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/CPU/Function/DoraemonCPUUtil.h",
    "content": "//\n//  DoraemonCPUUtil.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/15.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\n@interface DoraemonCPUUtil : NSObject\n\n//获取CPU使用率\n+ (CGFloat)cpuUsageForApp;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/CPU/Function/DoraemonCPUUtil.m",
    "content": "//\n//  DoraemonCPUUtil.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/15.\n//\n\n#import \"DoraemonCPUUtil.h\"\n#import <mach/mach.h>\n#import <UIKit/UIKit.h>\n//#import <sys/sysctl.h>\n\n@implementation DoraemonCPUUtil\n\n+ (CGFloat)cpuUsageForApp {\n    kern_return_t kr;\n    thread_array_t         thread_list;\n    mach_msg_type_number_t thread_count;\n    thread_info_data_t     thinfo;\n    mach_msg_type_number_t thread_info_count;\n    thread_basic_info_t basic_info_th;\n    \n    // get threads in the task\n    //  获取当前进程中 线程列表\n    kr = task_threads(mach_task_self(), &thread_list, &thread_count);\n    if (kr != KERN_SUCCESS)\n        return -1;\n\n    float tot_cpu = 0;\n    \n    for (int j = 0; j < thread_count; j++) {\n        thread_info_count = THREAD_INFO_MAX;\n        //获取每一个线程信息\n        kr = thread_info(thread_list[j], THREAD_BASIC_INFO,\n                         (thread_info_t)thinfo, &thread_info_count);\n        if (kr != KERN_SUCCESS)\n            return -1;\n        \n        basic_info_th = (thread_basic_info_t)thinfo;\n        if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {\n            // cpu_usage : Scaled cpu usage percentage. The scale factor is TH_USAGE_SCALE.\n            //宏定义TH_USAGE_SCALE返回CPU处理总频率：\n            tot_cpu += basic_info_th->cpu_usage / (float)TH_USAGE_SCALE;\n        }\n        \n    } // for each thread\n    \n    // 注意方法最后要调用 vm_deallocate，防止出现内存泄漏\n    kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));\n    assert(kr == KERN_SUCCESS);\n    \n    if (tot_cpu < 0) {\n        tot_cpu = 0.;\n    }\n    \n    return tot_cpu;\n}\n\n/**\n// Credit to: http://stackoverflow.com/questions/8223348/ios-get-cpu-usage-from-application\n+ (CGFloat)cpuUsageForApp {\n    kern_return_t kr;\n    task_info_data_t tinfo;\n    mach_msg_type_number_t task_info_count;\n    \n    task_info_count = TASK_INFO_MAX;\n    //获取当前任务，即当前的进程信息\n    kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count);\n    if (kr != KERN_SUCCESS)\n        return -1;\n    \n    task_basic_info_t      basic_info;\n    thread_array_t         thread_list;\n    mach_msg_type_number_t thread_count;\n    thread_info_data_t     thinfo;\n    mach_msg_type_number_t thread_info_count;\n    thread_basic_info_t basic_info_th;\n    uint32_t stat_thread = 0; // Mach threads\n    \n    basic_info = (task_basic_info_t)tinfo;\n    \n    // get threads in the task\n    //  获取当前进程中 线程列表\n    kr = task_threads(mach_task_self(), &thread_list, &thread_count);\n    if (kr != KERN_SUCCESS)\n        return -1;\n    if (thread_count > 0)\n        stat_thread += thread_count;\n    \n    long tot_sec = 0;\n    long tot_usec = 0;\n    float tot_cpu = 0;\n    int j;\n    \n    for (j = 0; j < thread_count; j++) {\n        thread_info_count = THREAD_INFO_MAX;\n        //获取每一个线程信息\n        kr = thread_info(thread_list[j], THREAD_BASIC_INFO,\n                         (thread_info_t)thinfo, &thread_info_count);\n        if (kr != KERN_SUCCESS)\n            return -1;\n        \n        basic_info_th = (thread_basic_info_t)thinfo;\n        if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {\n            tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds;\n            tot_usec = tot_usec + basic_info_th->system_time.microseconds + basic_info_th->system_time.microseconds;\n            // cpu_usage : Scaled cpu usage percentage. The scale factor is TH_USAGE_SCALE.\n            //宏定义TH_USAGE_SCALE返回CPU处理总频率：\n            tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE;\n        }\n        \n    } // for each thread\n    \n    // 注意方法最后要调用 vm_deallocate，防止出现内存泄漏\n    kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));\n    assert(kr == KERN_SUCCESS);\n    \n    return tot_cpu;\n}\n \n */\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Crash/DoraemonCrashPlugin.h",
    "content": "//\n//  DoraemonCrashPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/19.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonCrashPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Crash/DoraemonCrashPlugin.m",
    "content": "//\n//  DoraemonCrashPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/19.\n//\n\n#import \"DoraemonCrashPlugin.h\"\n#import \"DoraemonCrashViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonCrashPlugin\n\n- (void)pluginDidLoad{\n    DoraemonCrashViewController *vc = [[DoraemonCrashViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Crash/DoraemonCrashViewController.h",
    "content": "//\n//  DoraemonCrashViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/19.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonCrashViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Crash/DoraemonCrashViewController.m",
    "content": "//\n//  DoraemonCrashViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/19.\n//\n\n#import \"DoraemonCrashViewController.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonCellButton.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonCrashListViewController.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonCrashTool.h\"\n#import \"DoraemonToastUtil.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonCrashViewController () <DoraemonSwitchViewDelegate, DoraemonCellButtonDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n@property (nonatomic, strong) DoraemonCellButton *checkBtn;\n@property (nonatomic, strong) DoraemonCellButton *clearBtn;\n\n@end\n\n@implementation DoraemonCrashViewController\n\n#pragma mark - Lifecycle\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    [self commonInit];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n- (void)commonInit {\n    self.title = DoraemonLocalizedString(@\"Crash\");\n    \n    self.switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [self.switchView renderUIWithTitle:DoraemonLocalizedString(@\"Crash日志收集开关\") switchOn:[[DoraemonCacheManager sharedInstance] crashSwitch]];\n    [self.switchView needDownLine];\n    self.switchView.delegate = self;\n    [self.view addSubview:self.switchView];\n    \n    self.checkBtn = [[DoraemonCellButton alloc] initWithFrame:CGRectMake(0, self.switchView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [self.checkBtn renderUIWithTitle:DoraemonLocalizedString(@\"查看Crash日志\")];\n    self.checkBtn.delegate = self;\n    [self.checkBtn needDownLine];\n    [self.view addSubview:self.checkBtn];\n    \n    self.clearBtn = [[DoraemonCellButton alloc] initWithFrame:CGRectMake(0, self.checkBtn.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [self.clearBtn renderUIWithTitle:DoraemonLocalizedString(@\"一键清理Crash日志\")];\n    self.clearBtn.delegate = self;\n    [self.clearBtn needDownLine];\n    [self.view addSubview:self.clearBtn];\n}\n\n#pragma mark - Delegate\n\n#pragma mark DoraemonSwitchViewDelegate\n\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    __weak typeof(self) weakSelf = self;\n    [DoraemonAlertUtil handleAlertActionWithVC:self okBlock:^{\n        [[DoraemonCacheManager sharedInstance] saveCrashSwitch:on];\n        exit(0);\n    } cancleBlock:^{\n        weakSelf.switchView.switchView.on = !on;\n    }];\n}\n\n#pragma mark DoraemonCellButtonDelegate\n\n- (void)cellBtnClick:(id)sender {\n    if (sender == self.checkBtn) {\n        DoraemonCrashListViewController *vc = [[DoraemonCrashListViewController alloc] init];\n        [self.navigationController pushViewController:vc animated:YES];\n    } else if (sender == self.clearBtn) {\n        UIAlertController * alertController = [UIAlertController alertControllerWithTitle:DoraemonLocalizedString(@\"提示\") message:DoraemonLocalizedString(@\"确认删除所有崩溃日志吗？\") preferredStyle:UIAlertControllerStyleAlert];\n        UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"取消\") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {\n        }];\n        UIAlertAction *okAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"确定\") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n            NSFileManager *fm = [NSFileManager defaultManager];\n            if ([fm removeItemAtPath:[DoraemonCrashTool crashDirectory] error:nil]) {\n                [DoraemonToastUtil showToast:DoraemonLocalizedString(@\"删除成功\") inView:self.view];\n            } else {\n                [DoraemonToastUtil showToast:DoraemonLocalizedString(@\"删除失败\") inView:self.view];\n            }\n        }];\n        [alertController addAction:cancelAction];\n        [alertController addAction:okAction];\n        [self presentViewController:alertController animated:YES completion:nil];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Crash/Function/DoraemonCrashSignalExceptionHandler.h",
    "content": "//\n//  DoraemonCrashSignalExceptionHandler.h\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/11/22.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonCrashSignalExceptionHandler : NSObject\n\n+ (void)registerHandler;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Crash/Function/DoraemonCrashSignalExceptionHandler.m",
    "content": "//\n//  DoraemonCrashSignalExceptionHandler.m\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/11/22.\n//\n\n#import \"DoraemonCrashSignalExceptionHandler.h\"\n\n#import <execinfo.h>\n\n#import \"DoraemonCrashTool.h\"\n\ntypedef void (*SignalHandler)(int signal, siginfo_t *info, void *context);\n\nstatic SignalHandler previousABRTSignalHandler = NULL;\nstatic SignalHandler previousBUSSignalHandler  = NULL;\nstatic SignalHandler previousFPESignalHandler  = NULL;\nstatic SignalHandler previousILLSignalHandler  = NULL;\nstatic SignalHandler previousPIPESignalHandler = NULL;\nstatic SignalHandler previousSEGVSignalHandler = NULL;\nstatic SignalHandler previousSYSSignalHandler  = NULL;\nstatic SignalHandler previousTRAPSignalHandler = NULL;\n\n@implementation DoraemonCrashSignalExceptionHandler\n\n#pragma mark - Register\n\n+ (void)registerHandler {\n    // Backup original handler\n    [self backupOriginalHandler];\n    \n    [self signalRegister];\n}\n\n+ (void)backupOriginalHandler {\n    struct sigaction old_action_abrt;\n    sigaction(SIGABRT, NULL, &old_action_abrt);\n    if (old_action_abrt.sa_sigaction) {\n        previousABRTSignalHandler = old_action_abrt.sa_sigaction;\n    }\n    \n    struct sigaction old_action_bus;\n    sigaction(SIGBUS, NULL, &old_action_bus);\n    if (old_action_bus.sa_sigaction) {\n        previousBUSSignalHandler = old_action_bus.sa_sigaction;\n    }\n    \n    struct sigaction old_action_fpe;\n    sigaction(SIGFPE, NULL, &old_action_fpe);\n    if (old_action_fpe.sa_sigaction) {\n        previousFPESignalHandler = old_action_fpe.sa_sigaction;\n    }\n    \n    struct sigaction old_action_ill;\n    sigaction(SIGILL, NULL, &old_action_ill);\n    if (old_action_ill.sa_sigaction) {\n        previousILLSignalHandler = old_action_ill.sa_sigaction;\n    }\n    \n    struct sigaction old_action_pipe;\n    sigaction(SIGPIPE, NULL, &old_action_pipe);\n    if (old_action_pipe.sa_sigaction) {\n        previousPIPESignalHandler = old_action_pipe.sa_sigaction;\n    }\n    \n    struct sigaction old_action_segv;\n    sigaction(SIGSEGV, NULL, &old_action_segv);\n    if (old_action_segv.sa_sigaction) {\n        previousSEGVSignalHandler = old_action_segv.sa_sigaction;\n    }\n    \n    struct sigaction old_action_sys;\n    sigaction(SIGSYS, NULL, &old_action_sys);\n    if (old_action_sys.sa_sigaction) {\n        previousSYSSignalHandler = old_action_sys.sa_sigaction;\n    }\n    \n    struct sigaction old_action_trap;\n    sigaction(SIGTRAP, NULL, &old_action_trap);\n    if (old_action_trap.sa_sigaction) {\n        previousTRAPSignalHandler = old_action_trap.sa_sigaction;\n    }\n}\n\n+ (void)signalRegister {\n    DoraemonSignalRegister(SIGABRT);\n    DoraemonSignalRegister(SIGBUS);\n    DoraemonSignalRegister(SIGFPE);\n    DoraemonSignalRegister(SIGILL);\n    DoraemonSignalRegister(SIGPIPE);\n    DoraemonSignalRegister(SIGSEGV);\n    DoraemonSignalRegister(SIGSYS);\n    DoraemonSignalRegister(SIGTRAP);\n}\n\n#pragma mark - Private\n\n#pragma mark Register Signal\n\nstatic void DoraemonSignalRegister(int signal) {\n    struct sigaction action;\n    action.sa_sigaction = DoraemonSignalHandler;\n    action.sa_flags = SA_NODEFER | SA_SIGINFO;\n    sigemptyset(&action.sa_mask);\n    sigaction(signal, &action, 0);\n}\n\n#pragma mark SignalCrash Handler\n\nstatic void DoraemonSignalHandler(int signal, siginfo_t* info, void* context) {\n    NSMutableString *mstr = [[NSMutableString alloc] init];\n    [mstr appendString:@\"Signal Exception:\\n\"];\n    [mstr appendString:[NSString stringWithFormat:@\"Signal %@ was raised.\\n\", signalName(signal)]];\n    [mstr appendString:@\"Call Stack:\\n\"];\n    \n    //    void* callstack[128];\n    //    int i, frames = backtrace(callstack, 128);\n    //    char** strs = backtrace_symbols(callstack, frames);\n    //    for (i = 0; i <frames; ++i) {\n    //        [mstr appendFormat:@\"%s\\n\", strs[i]];\n    //    }\n    \n    // 这里过滤掉第一行日志\n    // 因为注册了信号崩溃回调方法，系统会来调用，将记录在调用堆栈上，因此此行日志需要过滤掉\n    for (NSUInteger index = 1; index < NSThread.callStackSymbols.count; index++) {\n        NSString *str = [NSThread.callStackSymbols objectAtIndex:index];\n        [mstr appendString:[str stringByAppendingString:@\"\\n\"]];\n    }\n    \n    [mstr appendString:@\"threadInfo:\\n\"];\n    [mstr appendString:[[NSThread currentThread] description]];\n    \n    // 保存崩溃日志到沙盒cache目录\n    [DoraemonCrashTool saveCrashLog:[NSString stringWithString:mstr] fileName:@\"Crash(Signal)\"];\n    \n    DoraemonClearSignalRigister();\n    \n    // 调用之前崩溃的回调函数\n    previousSignalHandler(signal, info, context);\n    \n    kill(getpid(), SIGKILL);\n}\n\n#pragma mark Signal To Name\n\nstatic NSString *signalName(int signal) {\n    NSString *signalName;\n    switch (signal) {\n        case SIGABRT:\n            signalName = @\"SIGABRT\";\n            break;\n        case SIGBUS:\n            signalName = @\"SIGBUS\";\n            break;\n        case SIGFPE:\n            signalName = @\"SIGFPE\";\n            break;\n        case SIGILL:\n            signalName = @\"SIGILL\";\n            break;\n        case SIGPIPE:\n            signalName = @\"SIGPIPE\";\n            break;\n        case SIGSEGV:\n            signalName = @\"SIGSEGV\";\n            break;\n        case SIGSYS:\n            signalName = @\"SIGSYS\";\n            break;\n        case SIGTRAP:\n            signalName = @\"SIGTRAP\";\n            break;\n        default:\n            break;\n    }\n    return signalName;\n}\n\n#pragma mark Previous Signal\n\nstatic void previousSignalHandler(int signal, siginfo_t *info, void *context) {\n    SignalHandler previousSignalHandler = NULL;\n    switch (signal) {\n        case SIGABRT:\n            previousSignalHandler = previousABRTSignalHandler;\n            break;\n        case SIGBUS:\n            previousSignalHandler = previousBUSSignalHandler;\n            break;\n        case SIGFPE:\n            previousSignalHandler = previousFPESignalHandler;\n            break;\n        case SIGILL:\n            previousSignalHandler = previousILLSignalHandler;\n            break;\n        case SIGPIPE:\n            previousSignalHandler = previousPIPESignalHandler;\n            break;\n        case SIGSEGV:\n            previousSignalHandler = previousSEGVSignalHandler;\n            break;\n        case SIGSYS:\n            previousSignalHandler = previousSYSSignalHandler;\n            break;\n        case SIGTRAP:\n            previousSignalHandler = previousTRAPSignalHandler;\n            break;\n        default:\n            break;\n    }\n    \n    if (previousSignalHandler) {\n        previousSignalHandler(signal, info, context);\n    }\n}\n\n#pragma mark Clear\n\nstatic void DoraemonClearSignalRigister() {\n    signal(SIGSEGV,SIG_DFL);\n    signal(SIGFPE,SIG_DFL);\n    signal(SIGBUS,SIG_DFL);\n    signal(SIGTRAP,SIG_DFL);\n    signal(SIGABRT,SIG_DFL);\n    signal(SIGILL,SIG_DFL);\n    signal(SIGPIPE,SIG_DFL);\n    signal(SIGSYS,SIG_DFL);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Crash/Function/DoraemonCrashTool.h",
    "content": "//\n//  DoraemonCrashTool.h\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/11/22.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonCrashTool : NSObject\n\n/**\n 保存崩溃日志到沙盒中的Library/Caches/Crash目录下\n \n @param log 崩溃日志的内容\n @param fileName 保存的文件名\n */\n+ (void)saveCrashLog:(NSString *)log fileName:(NSString *)fileName;\n\n/**\n 获取崩溃日志的目录\n\n @return 崩溃日志的目录\n */\n+ (NSString *)crashDirectory;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Crash/Function/DoraemonCrashTool.m",
    "content": "//\n//  DoraemonCrashTool.m\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/11/22.\n//\n\n#import \"DoraemonCrashTool.h\"\n\n@implementation DoraemonCrashTool\n\n+ (void)saveCrashLog:(NSString *)log fileName:(NSString *)fileName {\n    if ([log isKindOfClass:[NSString class]] && (log.length > 0)) {\n        // 获取当前年月日字符串\n        NSDateFormatter *dateFormart = [[NSDateFormatter alloc]init];\n        [dateFormart setDateFormat:@\"yyyy-MM-dd HH:mm:ss\"];\n        dateFormart.timeZone = [NSTimeZone systemTimeZone];\n        NSString *dateString = [dateFormart stringFromDate:[NSDate date]];\n        \n        NSFileManager *manager = [NSFileManager defaultManager];\n        NSString *crashDirectory = [self crashDirectory];\n        if (crashDirectory && [manager fileExistsAtPath:crashDirectory]) {\n            // 获取crash保存的路径\n            NSString *crashPath = [crashDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@\"Crash %@.txt\", dateString]];\n            if ([fileName isKindOfClass:[NSString class]] && (fileName.length > 0)) {\n                crashPath = [crashDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@\"%@ %@.txt\", fileName, dateString]];\n            }\n            \n            [log writeToFile:crashPath atomically:YES encoding:NSUTF8StringEncoding error:nil];\n        }\n    }\n}\n\n+ (NSString *)crashDirectory {\n    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];\n    NSString *directory = [cachePath stringByAppendingPathComponent:@\"Crash\"];\n    \n    NSFileManager *manager = [NSFileManager defaultManager];\n    if (![manager fileExistsAtPath:directory]) {\n        [manager createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:nil];\n    }\n    \n    return directory;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Crash/Function/DoraemonCrashUncaughtExceptionHandler.h",
    "content": "//\n//  DoraemonCrashUncaughtExceptionHandler.h\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/11/22.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonCrashUncaughtExceptionHandler : NSObject\n\n+ (void)registerHandler;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Crash/Function/DoraemonCrashUncaughtExceptionHandler.m",
    "content": "//\n//  DoraemonCrashUncaughtExceptionHandler.m\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/11/22.\n//\n\n#import \"DoraemonCrashUncaughtExceptionHandler.h\"\n\n#import \"DoraemonCrashTool.h\"\n\n// 记录之前的崩溃回调函数\nstatic NSUncaughtExceptionHandler *previousUncaughtExceptionHandler = NULL;\n\n@implementation DoraemonCrashUncaughtExceptionHandler\n\n#pragma mark - Register\n\n+ (void)registerHandler {\n    // Backup original handler\n    previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();\n    \n    NSSetUncaughtExceptionHandler(&DoraemonUncaughtExceptionHandler);\n}\n\n#pragma mark - Private\n\n// 崩溃时的回调函数\nstatic void DoraemonUncaughtExceptionHandler(NSException * exception) {\n    // 异常的堆栈信息\n    NSArray * stackArray = [exception callStackSymbols];\n    // 出现异常的原因\n    NSString * reason = [exception reason];\n    // 异常名称\n    NSString * name = [exception name];\n    \n    NSString * exceptionInfo = [NSString stringWithFormat:@\"========uncaughtException异常错误报告========\\nname:%@\\nreason:\\n%@\\ncallStackSymbols:\\n%@\", name, reason, [stackArray componentsJoinedByString:@\"\\n\"]];\n    \n    // 保存崩溃日志到沙盒cache目录\n    [DoraemonCrashTool saveCrashLog:exceptionInfo fileName:@\"Crash(Uncaught)\"];\n    \n    // 调用之前崩溃的回调函数\n    if (previousUncaughtExceptionHandler) {\n        previousUncaughtExceptionHandler(exception);\n    }\n    \n    // 杀掉程序，这样可以防止同时抛出的SIGABRT被SignalException捕获\n    kill(getpid(), SIGKILL);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Crash/List/DoraemonCrashListCell.h",
    "content": "//\n//  DoraemonCrashListCell.h\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/11/22.\n//\n\n#import <UIKit/UIKit.h>\n\n@class DoraemonSandboxModel;\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonCrashListCell : UITableViewCell\n\n- (void)renderUIWithData:(DoraemonSandboxModel *)model;\n\n+ (CGFloat)cellHeight;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Crash/List/DoraemonCrashListCell.m",
    "content": "//\n//  DoraemonCrashListCell.m\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/11/22.\n//\n\n#import \"DoraemonCrashListCell.h\"\n#import \"DoraemonSandboxModel.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonCrashListCell ()\n\n@property (nonatomic, strong) UILabel *titleLabel;\n@property (nonatomic, strong) UIImageView *arrowImageView;\n\n@end\n\n@implementation DoraemonCrashListCell\n\n#pragma mark - Lifecycle\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        self.selectionStyle = UITableViewCellSelectionStyleNone;\n        \n        _titleLabel = [[UILabel alloc] init];\n        _titleLabel.textColor = [UIColor doraemon_black_1];\n        _titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        [self.contentView addSubview:_titleLabel];\n        \n        _arrowImageView = [[UIImageView alloc] initWithImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_more\"]];\n        _arrowImageView.frame = CGRectMake(DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)-_arrowImageView.doraemon_width, [[self class] cellHeight]/2-_arrowImageView.doraemon_height/2, _arrowImageView.doraemon_width, _arrowImageView.doraemon_height);\n        [self.contentView addSubview:_arrowImageView];\n        \n    }\n    return self;\n}\n\n#pragma mark - Public\n\n- (void)renderUIWithData:(DoraemonSandboxModel *)model {\n    self.titleLabel.text = @\"\";\n    if ([model.name isKindOfClass:[NSString class]] && (model.name.length > 0)) {\n        self.titleLabel.text = model.name;\n        [self.titleLabel sizeToFit];\n        CGFloat w = self.titleLabel.doraemon_width;\n        if (w > DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(120)) {\n            w = DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(120);\n        }\n        self.titleLabel.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), [[self class] cellHeight]/2-self.titleLabel.doraemon_height/2, w, self.titleLabel.doraemon_height);\n    }\n}\n\n+ (CGFloat)cellHeight{\n    return kDoraemonSizeFrom750_Landscape(104);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Crash/List/DoraemonCrashListViewController.h",
    "content": "//\n//  DoraemonCrashListViewController.h\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/11/22.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonCrashListViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Crash/List/DoraemonCrashListViewController.m",
    "content": "//\n//  DoraemonCrashListViewController.m\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/11/22.\n//\n\n#import \"DoraemonCrashListViewController.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonCrashListCell.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonSanboxDetailViewController.h\"\n#import \"DoraemonSandboxModel.h\"\n#import \"DoraemonCrashTool.h\"\n#import \"DoraemonDefine.h\"\n\nstatic NSString *const kDoraemonCrashListCellIdentifier = @\"kDoraemonCrashListCellIdentifier\";\n\n@interface DoraemonCrashListViewController () <UITableViewDelegate,UITableViewDataSource>\n\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, copy) NSArray<DoraemonSandboxModel *> *dataArray;\n\n@end\n\n@implementation DoraemonCrashListViewController\n\n#pragma mark - View Lifecycle\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    // Do any additional setup after loading the view.\n    \n    [self commonInit];\n    \n    [self loadCrashData];\n}\n\n- (void)commonInit {\n    self.dataArray = [NSArray array];\n    \n    self.title = DoraemonLocalizedString(@\"Crash日志列表\");\n    [self.view addSubview:self.tableView];\n}\n\n#pragma mark - Layout\n\n- (void)viewDidLayoutSubviews {\n    [super viewDidLayoutSubviews];\n    self.tableView.frame = CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT, self.view.doraemon_width, self.view.doraemon_height-IPHONE_NAVIGATIONBAR_HEIGHT);\n}\n\n#pragma mark - Private\n\n#pragma mark CrashData\n\n- (void)loadCrashData {\n    // 获取crash目录\n    NSFileManager *manager = [NSFileManager defaultManager];\n    NSString *crashDirectory = [DoraemonCrashTool crashDirectory];\n    \n    if (crashDirectory && [manager fileExistsAtPath:crashDirectory]) {\n        [self loadPath:crashDirectory];\n    }\n}\n\n- (void)loadPath:(NSString *)filePath{\n    NSFileManager *fm = [NSFileManager defaultManager];\n    NSString *targetPath = NSHomeDirectory();\n    if ([filePath isKindOfClass:[NSString class]] && (filePath.length > 0)) {\n        targetPath = filePath;\n    }\n    \n    //该目录下面的内容信息\n    NSError *error = nil;\n    NSArray *paths = [fm contentsOfDirectoryAtPath:targetPath error:&error];\n    \n    // 对paths按照创建时间的降序进行排列\n    NSArray *sortedPaths = [paths sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {\n        if ([obj1 isKindOfClass:[NSString class]] && [obj2 isKindOfClass:[NSString class]]) {\n            // 获取文件完整路径\n            NSString *firstPath = [targetPath stringByAppendingPathComponent:obj1];\n            NSString *secondPath = [targetPath stringByAppendingPathComponent:obj2];\n            \n            // 获取文件信息\n            NSDictionary *firstFileInfo = [[NSFileManager defaultManager] attributesOfItemAtPath:firstPath error:nil];\n            NSDictionary *secondFileInfo = [[NSFileManager defaultManager] attributesOfItemAtPath:secondPath error:nil];\n            \n            // 获取文件创建时间\n            id firstData = [firstFileInfo objectForKey:NSFileCreationDate];\n            id secondData = [secondFileInfo objectForKey:NSFileCreationDate];\n            \n            // 按照创建时间降序排列\n            return [secondData compare:firstData];\n        }\n        return NSOrderedSame;\n    }];\n    \n    // 构造数据源\n    NSMutableArray *files = [NSMutableArray array];\n    [sortedPaths enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {\n        if ([obj isKindOfClass:[NSString class]]) {\n            NSString *sortedPath = obj;\n            \n            BOOL isDir = false;\n            NSString *fullPath = [targetPath stringByAppendingPathComponent:sortedPath];\n            [fm fileExistsAtPath:fullPath isDirectory:&isDir];\n            \n            DoraemonSandboxModel *model = [[DoraemonSandboxModel alloc] init];\n            model.path = fullPath;\n            if (isDir) {\n                model.type = DoraemonSandboxFileTypeDirectory;\n            }else{\n                model.type = DoraemonSandboxFileTypeFile;\n            }\n            model.name = sortedPath;\n            \n            [files addObject:model];\n        }\n    }];\n    self.dataArray = files.copy;\n    \n    [self.tableView reloadData];\n}\n\n- (void)deleteByDoraemonSandboxModel:(DoraemonSandboxModel *)model{\n    NSFileManager *fm = [NSFileManager defaultManager];\n    [fm removeItemAtPath:model.path error:nil];\n    \n    [self loadCrashData];\n}\n\n#pragma mark HandleFile\n\n- (void)handleFileWithPath:(NSString *)filePath{\n    UIAlertControllerStyle style;\n    if ([DoraemonAppInfoUtil isIpad]) {\n        style = UIAlertControllerStyleAlert;\n    }else{\n        style = UIAlertControllerStyleActionSheet;\n    }\n    \n    UIAlertController *alertVc = [UIAlertController alertControllerWithTitle:DoraemonLocalizedString(@\"请选择操作方式\") message:nil preferredStyle:style];\n    __weak typeof(self) weakSelf = self;\n    UIAlertAction *previewAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"本地预览\") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n        __strong typeof(self) strongSelf = weakSelf;\n        [strongSelf previewFile:filePath];\n    }];\n    UIAlertAction *shareAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"分享\") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n        __strong typeof(self) strongSelf = weakSelf;\n        [strongSelf shareFileWithPath:filePath];\n    }];\n    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:DoraemonLocalizedString(@\"取消\") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {\n    }];\n    [alertVc addAction:previewAction];\n    [alertVc addAction:shareAction];\n    [alertVc addAction:cancelAction];\n    \n    [self presentViewController:alertVc animated:YES completion:nil];\n}\n\n- (void)previewFile:(NSString *)filePath{\n    DoraemonSanboxDetailViewController *detalVc = [[DoraemonSanboxDetailViewController alloc] init];\n    detalVc.filePath = filePath;\n    [self.navigationController pushViewController:detalVc animated:YES];\n}\n\n- (void)shareFileWithPath:(NSString *)filePath{\n    [DoraemonUtil shareURL:[NSURL fileURLWithPath:filePath] formVC:self];\n}\n\n#pragma mark - Delegate\n\n#pragma mark <UITableViewDataSource>\n\n// Default is 1 if not implemented\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {\n    return 1;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return self.dataArray.count;\n}\n\n// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonCrashListCell *cell = [tableView dequeueReusableCellWithIdentifier:kDoraemonCrashListCellIdentifier forIndexPath:indexPath];\n    if (!cell) {\n        cell = [[DoraemonCrashListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kDoraemonCrashListCellIdentifier];\n    }\n    \n    if (indexPath.row < self.dataArray.count) {\n        DoraemonSandboxModel *model = [self.dataArray objectAtIndex:indexPath.row];\n        [cell renderUIWithData:model];\n    }\n    \n    return cell;\n}\n\n#pragma mark - <UITableViewDelegate>\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return [DoraemonCrashListCell cellHeight];\n}\n\n// Called after the user changes the selection.\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    if (indexPath.row < self.dataArray.count) {\n        DoraemonSandboxModel *model = [self.dataArray objectAtIndex:indexPath.row];\n        if (model.type == DoraemonSandboxFileTypeFile) {\n            [self handleFileWithPath:model.path];\n        }else if(model.type == DoraemonSandboxFileTypeDirectory){\n            [self loadPath:model.path];\n        }\n    }\n}\n\n- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{\n    return YES;\n}\n\n- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath{\n    return DoraemonLocalizedString(@\"删除\");\n}\n\n- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{\n    if (indexPath.row < self.dataArray.count) {\n        DoraemonSandboxModel *model = self.dataArray[indexPath.row];\n        [self deleteByDoraemonSandboxModel:model];\n    }\n}\n\n#pragma mark - Getter\n\n- (UITableView *)tableView {\n    if (!_tableView) {\n        _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];\n        [_tableView registerClass:[DoraemonCrashListCell class] forCellReuseIdentifier:kDoraemonCrashListCellIdentifier];\n//        _tableView.backgroundColor = [UIColor whiteColor];\n        _tableView.delegate = self;\n        _tableView.dataSource = self;\n    }\n    return _tableView;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/FPS/DoraemonFPSPlugin.h",
    "content": "//\n//  DoraemonFPSPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/3.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonFPSPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/FPS/DoraemonFPSPlugin.m",
    "content": "//\n//  DoraemonFPSPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/3.\n//\n\n#import \"DoraemonFPSPlugin.h\"\n#import \"DoraemonFPSViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonFPSPlugin\n\n- (void)pluginDidLoad{\n    DoraemonFPSViewController *vc = [[DoraemonFPSViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/FPS/DoraemonFPSViewController.h",
    "content": "//\n//  DoraemonFPSViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/3.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonFPSViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/FPS/DoraemonFPSViewController.m",
    "content": "//\n//  DoraemonFPSViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/3.\n//\n\n#import \"DoraemonFPSViewController.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonFPSOscillogramWindow.h\"\n#import \"DoraemonFPSOscillogramViewController.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonFPSViewController ()<DoraemonSwitchViewDelegate, DoraemonOscillogramWindowDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n\n@end\n\n@implementation DoraemonFPSViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"帧率检测\");\n    \n    _switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"帧率检测开关\") switchOn:[[DoraemonCacheManager sharedInstance] fpsSwitch]];\n    [_switchView needTopLine];\n    [_switchView needDownLine];\n    _switchView.delegate = self;\n    [self.view addSubview:_switchView];\n    [[DoraemonFPSOscillogramWindow shareInstance] addDelegate:self];\n}\n\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    [[DoraemonCacheManager sharedInstance] saveFpsSwitch:on];\n    if(on){\n        [[DoraemonFPSOscillogramWindow shareInstance] show];\n    }else{\n        [[DoraemonFPSOscillogramWindow shareInstance] hide];\n    }\n}\n\n#pragma mark -- DoraemonOscillogramWindowDelegate\n- (void)doraemonOscillogramWindowClosed {\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"帧率检测开关\") switchOn:[[DoraemonCacheManager sharedInstance] fpsSwitch]];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/FPS/Function/DoraemonFPSOscillogramViewController.h",
    "content": "//\n//  DoraemonFPSOscillogramViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/12.\n//\n\n#import \"DoraemonOscillogramViewController.h\"\n\n@interface DoraemonFPSOscillogramViewController : DoraemonOscillogramViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/FPS/Function/DoraemonFPSOscillogramViewController.m",
    "content": "//\n//  DoraemonFPSOscillogramViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/12.\n//\n\n#import \"DoraemonFPSOscillogramViewController.h\"\n#import \"DoraemonOscillogramView.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonFPSOscillogramWindow.h\"\n#import \"DoraemonFPSUtil.h\"\n\n\n@interface DoraemonFPSOscillogramViewController ()\n\n@property (nonatomic, strong) DoraemonFPSUtil *fpsUtil;\n\n@end\n\n@implementation DoraemonFPSOscillogramViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n}\n\n- (NSString *)title{\n    return DoraemonLocalizedString(@\"帧率检测\");\n}\n\n- (NSString *)lowValue{\n    return @\"0\";\n}\n\n- (NSString *)highValue{\n    return @\"60\";\n}\n\n\n- (void)closeBtnClick{\n    [[DoraemonCacheManager sharedInstance] saveFpsSwitch:NO];\n    [[DoraemonFPSOscillogramWindow shareInstance] hide];\n}\n\n- (void)startRecord{\n    if (!_fpsUtil) {\n        _fpsUtil = [[DoraemonFPSUtil alloc] init];\n        __weak typeof(self) weakSelf = self;\n        [_fpsUtil addFPSBlock:^(NSInteger fps) {\n            // 0~60   对应 高度0~_oscillogramView.doraemon_height\n            [weakSelf.oscillogramView addHeightValue:fps*weakSelf.oscillogramView.doraemon_height/60. andTipValue:[NSString stringWithFormat:@\"%zi\",fps]];\n        }];\n    }\n    [_fpsUtil start];\n}\n\n- (void)endRecord{\n    if (_fpsUtil) {\n        [_fpsUtil end];\n    }\n    [self.oscillogramView clear];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/FPS/Function/DoraemonFPSOscillogramWindow.h",
    "content": "//\n//  DoraemonFPSOscillogramWindow.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/12.\n//\n\n#import \"DoraemonOscillogramWindow.h\"\n\n@interface DoraemonFPSOscillogramWindow : DoraemonOscillogramWindow\n\n+ (DoraemonFPSOscillogramWindow *)shareInstance;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/FPS/Function/DoraemonFPSOscillogramWindow.m",
    "content": "//\n//  DoraemonFPSOscillogramWindow.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/12.\n//\n\n#import \"DoraemonFPSOscillogramWindow.h\"\n#import \"DoraemonFPSOscillogramViewController.h\"\n\n@implementation DoraemonFPSOscillogramWindow\n\n+ (DoraemonFPSOscillogramWindow *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonFPSOscillogramWindow *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonFPSOscillogramWindow alloc] initWithFrame:CGRectZero];\n    });\n    return instance;\n}\n\n- (void)addRootVc{\n    DoraemonFPSOscillogramViewController *vc = [[DoraemonFPSOscillogramViewController alloc] init];\n    self.rootViewController = vc;\n    self.vc = vc;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/FPS/Function/DoraemonFPSUtil.h",
    "content": "//\n//  DoraemonFPSUtil.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/9.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\ntypedef void (^DoraemonFPSBlock)(NSInteger fps);\n\n@interface DoraemonFPSUtil : NSObject\n\n- (void)start;\n- (void)end;\n- (void)addFPSBlock:(DoraemonFPSBlock)block;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/FPS/Function/DoraemonFPSUtil.m",
    "content": "//\n//  DoraemonFPSUtil.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/9.\n//\n\n#import \"DoraemonFPSUtil.h\"\n#import <UIKit/UIKit.h>\n\n@interface DoraemonFPSUtil()\n\n@property (nonatomic, strong) CADisplayLink *link;\n@property (nonatomic, assign) NSUInteger count;\n@property (nonatomic, assign) NSTimeInterval lastTime;\n@property (nonatomic, assign) BOOL isStart;\n@property (nonatomic, assign) NSInteger fps;\n@property (nonatomic, copy) DoraemonFPSBlock block;\n@end\n\n@implementation DoraemonFPSUtil\n\n- (instancetype)init{\n    self = [super init];\n    if (self) {\n        _isStart = NO;\n        _count = 0;\n        _lastTime = 0;\n    }\n    return self;\n}\n\n- (void)start{\n    if (_link) {\n        _link.paused = NO;\n    }else{\n        _link = [CADisplayLink displayLinkWithTarget:self selector:@selector(trigger:)];\n        [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];\n    }\n}\n\n- (void)end{\n    if (_link) {\n        _link.paused = YES;\n        [_link invalidate];\n        _link = nil;\n        _lastTime = 0;\n        _count = 0;\n    }\n}\n\n- (void)trigger:(CADisplayLink *)link{\n    if (_lastTime == 0) {\n        _lastTime = link.timestamp;\n        return;\n    }\n    \n    _count++;\n    NSTimeInterval delta = link.timestamp - _lastTime;\n    if (delta < 1) return;\n    _lastTime = link.timestamp;\n    CGFloat fps = _count / delta;\n    _count = 0;\n    \n    NSInteger intFps = (NSInteger)(fps+0.5);\n    self.fps = intFps;\n    if (self.block) {\n        self.block(self.fps);\n    }\n}\n\n- (void)addFPSBlock:(void(^)(NSInteger fps))block{\n    self.block = block;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/LargeImageDetection/Detail/DoraemonImageDetectionCell.h",
    "content": "//\n//  DoraemonImageDetectionCell.h\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/5/17.\n//\n\n#import <UIKit/UIKit.h>\n@class DoraemonResponseImageModel;\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonImageDetectionCell : UITableViewCell\n+ (CGFloat)cellHeight;\n\n- (void)setupWithModel:(DoraemonResponseImageModel *)model;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/LargeImageDetection/Detail/DoraemonImageDetectionCell.m",
    "content": "//\n//  DoraemonImageDetectionCell.m\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/5/17.\n//\n\n#import \"DoraemonImageDetectionCell.h\"\n#import \"DoraemonResponseImageModel.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonImageDetectionCell()\n@property (nonatomic, strong) UIImageView *previewImageView;\n@property (nonatomic, strong) UILabel *urlLabel;\n@property (nonatomic, strong) UILabel *sizeLabel;\n@property (nonatomic, strong) UIButton *button;\n\n@end\n\n@implementation DoraemonImageDetectionCell\n\n+ (CGFloat)cellHeight {\n    return 116;\n}\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    [self initUI];\n    return self;\n}\n\n- (void)setupWithModel:(DoraemonResponseImageModel *)model {\n    self.urlLabel.text = [model.url absoluteString];\n    self.previewImageView.image = [UIImage imageWithData: model.data];\n    self.sizeLabel.text = [NSString stringWithFormat: @\"size: %@\", model.size];\n}\n\n- (void) initUI {\n    CGFloat space = 8;\n    \n    self.previewImageView = [[UIImageView alloc] initWithFrame: CGRectMake(space, space, 100, 100)];\n    self.previewImageView.backgroundColor = [UIColor colorWithRed: 0 green: 0 blue:0 alpha: 0.3];\n    self.previewImageView.contentMode = UIViewContentModeScaleAspectFit;\n    [self addSubview: self.previewImageView];\n    \n    UIView *imageInfoView = [[UIView alloc] initWithFrame: CGRectMake(self.previewImageView.doraemon_width + (space * 2), self.previewImageView.doraemon_y, DoraemonScreenWidth - self.previewImageView.doraemon_right - (space * 2), self.previewImageView.doraemon_height)];\n    [self addSubview: imageInfoView];\n    \n    self.sizeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, imageInfoView.doraemon_width, 15)];\n    self.sizeLabel.textColor = [UIColor doraemon_black_1];\n    self.sizeLabel.font = [UIFont systemFontOfSize: 11];\n    [imageInfoView addSubview: self.sizeLabel];\n    \n    self.urlLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, self.sizeLabel.doraemon_bottom, imageInfoView.doraemon_width, 80)];\n    [imageInfoView addSubview: self.urlLabel];\n    self.urlLabel.lineBreakMode = NSLineBreakByCharWrapping;\n    self.sizeLabel.textColor = [UIColor doraemon_black_1];\n    self.urlLabel.numberOfLines = 5;\n    self.urlLabel.font = [UIFont systemFontOfSize: 11];\n\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/LargeImageDetection/Detail/DoraemonLargeImageDetectionListViewController.h",
    "content": "//\n//  DoraemonLargeImageDetectionListViewController.h\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/5/15.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonBaseViewController.h\"\n@class DoraemonResponseImageModel;\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonLargeImageDetectionListViewController : DoraemonBaseViewController\n- (instancetype)initWithImages:(NSArray <DoraemonResponseImageModel *> *) images;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/LargeImageDetection/Detail/DoraemonLargeImageDetectionListViewController.m",
    "content": "//\n//  DoraemonLargeImageDetectionListViewController.m\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/5/15.\n//\n\n#import \"DoraemonLargeImageDetectionListViewController.h\"\n#import \"DoraemonLargeImageDetectionManager.h\"\n#import \"DoraemonImageDetectionCell.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonLargeImageDetectionListViewController ()<UITableViewDelegate, UITableViewDataSource>\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, strong) UITextField *textField;\n@property (nonatomic, strong) UIButton *filterButton;\n@property (nonatomic, strong) NSArray <DoraemonResponseImageModel *> *images;\n@end\n\n@implementation DoraemonLargeImageDetectionListViewController\n\n- (instancetype)initWithImages:(NSArray <DoraemonResponseImageModel *> *) images {\n    if (self = [super init]) {\n        self.images = images;\n    }\n    return self;\n}\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    [self initUI];\n}\n\n- (void)initUI {\n    \n    CGRect tableViewFrame = CGRectMake(0, CGRectGetMaxY(self.textField.frame), DoraemonScreenWidth, DoraemonScreenHeight);\n    self.tableView = [[UITableView alloc] initWithFrame: tableViewFrame];\n    self.tableView.translatesAutoresizingMaskIntoConstraints = false;\n    self.tableView.delegate = self;\n    self.tableView.dataSource = self;\n    self.tableView.allowsSelection = false;\n    [self.tableView registerClass: [DoraemonImageDetectionCell class] forCellReuseIdentifier: NSStringFromClass([DoraemonImageDetectionCell class])];\n    self.tableView.tableFooterView = [[UIView alloc] init];\n    [self.view addSubview:self.tableView];\n    [self.tableView reloadData];\n    \n}\n\n- (void)setting {\n    \n}\n    \n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return [DoraemonLargeImageDetectionManager shareInstance].images.count;\n}\n    \n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonImageDetectionCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([DoraemonImageDetectionCell class]) forIndexPath:indexPath];\n    [cell setupWithModel: self.images[indexPath.item]];\n    return cell;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return [DoraemonImageDetectionCell cellHeight];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/LargeImageDetection/Detail/DoraemonResponseImageModel.h",
    "content": "//\n//  DoraemonResponseImageModel.h\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/5/16.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonResponseImageModel : NSObject\n@property (nonatomic, strong) NSURL *url;\n@property (nonatomic, strong) NSData *data;\n@property (nonatomic, copy) NSString *size;\n\n- (instancetype)initWithResponse: (NSURLResponse *)response data:(NSData *) data;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/LargeImageDetection/Detail/DoraemonResponseImageModel.m",
    "content": "//\n//  DoraemonResponseImageModel.m\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/5/16.\n//\n\n#import \"DoraemonResponseImageModel.h\"\n#import \"DoraemonUrlUtil.h\"\n\n@implementation DoraemonResponseImageModel\n- (instancetype)initWithResponse: (NSURLResponse *)response data:(NSData *) data {\n    \n    self = [[DoraemonResponseImageModel alloc] init];\n    self.url = response.URL;\n    self.data = data;\n    int64_t byte = [DoraemonUrlUtil getResponseLength:(NSHTTPURLResponse *)response data:data];\n    self.size = [NSByteCountFormatter stringFromByteCount: byte countStyle: NSByteCountFormatterCountStyleBinary];\n    return self;\n}\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/LargeImageDetection/DoraemonLargeImagePlugin.h",
    "content": "//\n//  DoraemonLargeImagePlugin.h\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/5/15.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonLargeImagePlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/LargeImageDetection/DoraemonLargeImagePlugin.m",
    "content": "//\n//  DoraemonLargeImagePlugin.m\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/5/15.\n//\n\n#import \"DoraemonLargeImagePlugin.h\"\n#import \"DoraemonLargeImageViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonLargeImagePlugin\n- (void)pluginDidLoad {\n    DoraemonLargeImageViewController *vc = [[DoraemonLargeImageViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/LargeImageDetection/DoraemonLargeImageViewController.h",
    "content": "//\n//  DoraemonLargeImageViewController.h\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/5/15.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonLargeImageViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/LargeImageDetection/DoraemonLargeImageViewController.m",
    "content": "//\n//  DoraemonLargeImageViewController.m\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/5/15.\n//\n\n#import \"DoraemonLargeImageViewController.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonCellButton.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonNetworkInterceptor.h\"\n#import \"DoraemonLargeImageDetectionManager.h\"\n#import \"DoraemonLargeImageDetectionListViewController.h\"\n#import \"DoraemonCellInput.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonLargeImageViewController() <DoraemonSwitchViewDelegate, DoraemonCellButtonDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n@property (nonatomic, strong) DoraemonCellButton *cellBtn;\n@end\n\n@implementation DoraemonLargeImageViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    [self initUI];\n}\n\n- (void)initUI {\n    self.title = DoraemonLocalizedString(@\"大图检测\");\n    \n    _switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750(104))];\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"大图检测开关\") switchOn:[[DoraemonLargeImageDetectionManager shareInstance] detecting]];\n    [_switchView needTopLine];\n    [_switchView needDownLine];\n    _switchView.delegate = self;\n    [self.view addSubview:_switchView];\n    \n    _cellBtn = [[DoraemonCellButton alloc] initWithFrame:CGRectMake(0, _switchView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750(104))];\n    [_cellBtn renderUIWithTitle:DoraemonLocalizedString(@\"查看检测记录\")];\n    _cellBtn.delegate = self;\n    [_cellBtn needDownLine];\n    [self.view addSubview:_cellBtn];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender {\n    [DoraemonLargeImageDetectionManager shareInstance].detecting = on;\n}\n\n- (void)cellBtnClick:(id)sender {\n    DoraemonLargeImageDetectionListViewController *vc = [[DoraemonLargeImageDetectionListViewController alloc] initWithImages: [DoraemonLargeImageDetectionManager shareInstance].images];\n    [self.navigationController pushViewController:vc animated:YES];\n}\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/LargeImageDetection/Function/DoraemonLargeImageDetectionManager.h",
    "content": "//\n//  DoraemonLargeImageDetectionManager.h\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/5/15.\n//\n\n#import <Foundation/Foundation.h>\n@class DoraemonResponseImageModel;\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonLargeImageDetectionManager : NSObject\n@property (nonatomic, strong) NSMutableArray *images;\n@property (nonatomic, assign) BOOL detecting;\n@property (nonatomic, assign) int64_t minimumDetectionSize;\n\n+ (instancetype) shareInstance;\n- (void)updateInterceptStatus;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/LargeImageDetection/Function/DoraemonLargeImageDetectionManager.m",
    "content": "//\n//  DoraemonLargeImageDetectionManager.m\n//  DoraemonKit\n//\n//  Created by 0xd-cc on 2019/5/15.\n//\n\n#import \"DoraemonLargeImageDetectionManager.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonResponseImageModel.h\"\n#import \"DoraemonNetworkInterceptor.h\"\n#import \"DoraemonUrlUtil.h\"\n\nstatic DoraemonLargeImageDetectionManager *instance = nil;\n\n@interface DoraemonLargeImageDetectionManager() <DoraemonNetworkInterceptorDelegate>\n@end\n\n@implementation DoraemonLargeImageDetectionManager {\n    dispatch_semaphore_t semaphore;\n    BOOL _isDetecting;\n}\n\n+ (instancetype)shareInstance {\n    static dispatch_once_t once;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonLargeImageDetectionManager alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init {\n    self = [super init];\n    if (self) {\n        _images = [NSMutableArray array];\n        semaphore = dispatch_semaphore_create(1);\n        _isDetecting = NO;\n        _minimumDetectionSize = 500 * 1024;\n    }\n    return self;\n}\n\n- (void)setDetecting:(BOOL)detecting {\n    _isDetecting = detecting;\n    [self updateInterceptStatus];\n}\n\n- (BOOL)detecting {\n    return _isDetecting;\n}\n\n- (void)updateInterceptStatus {\n    if (_isDetecting) {\n        [[DoraemonNetworkInterceptor shareInstance] addDelegate: self];\n    } else {\n        [[DoraemonNetworkInterceptor shareInstance] removeDelegate: self];\n    }\n}\n\n#pragma mark -- DoraemonNetworkInterceptorDelegate\n- (void)doraemonNetworkInterceptorDidReceiveData:(NSData *)data response:(NSURLResponse *)response request:(NSURLRequest *)request error:(NSError *)error startTime:(NSTimeInterval)startTime {\n    if (![response.MIMEType hasPrefix:@\"image/\"]) {\n        return;\n    }\n    if ([DoraemonUrlUtil getResponseLength:(NSHTTPURLResponse *)response data:data] < self.minimumDetectionSize) {\n        return;\n    }\n    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);\n    DoraemonResponseImageModel *model = [[DoraemonResponseImageModel alloc] initWithResponse: response data: data];\n    [self.images addObject: model];\n    dispatch_semaphore_signal(semaphore);\n}\n\n\n- (BOOL)shouldIntercept {\n    return _isDetecting;\n}\n    \n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/LargeImageDetection/Function/UIImageView+DoraemonSDImage.h",
    "content": "//\n//  UIImageView+DoraemonSDImage.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/19.\n//\n\n#import <UIKit/UIKit.h>\n\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UIImageView (DoraemonSDImage)\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/LargeImageDetection/Function/UIImageView+DoraemonSDImage.m",
    "content": "//\n//  UIImageView+DoraemonSDImage.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/19.\n//\n\n#if __has_include(<SDWebImage/UIImageView+WebCache.h>)\n#import \"UIImageView+DoraemonSDImage.h\"\n//#import <SDWebImage/UIImageView+WebCache.h>\n#import \"NSObject+Doraemon.h\"\n#import \"DoraemonLargeImageDetectionManager.h\"\n#import <objc/runtime.h>\n\n@implementation UIImageView (DoraemonSDImage)\n\n+ (void)load {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wundeclared-selector\"\n    \n    BOOL respondToSelector = [UIImageView instancesRespondToSelector:@selector(sd_internalSetImageWithURL:placeholderImage:options:context:setImageBlock:progress:completed:)];\n    if (respondToSelector) {\n        // 兼容SDWebImage 最新版本 5.0.6\n        [self doraemon_swizzleInstanceMethodWithOriginSel:@selector(sd_internalSetImageWithURL:placeholderImage:options:context:setImageBlock:progress:completed:)\n                                              swizzledSel:@selector(doraemon_sd_internalSetImageWithURL:placeholderImage:options:context:setImageBlock:progress:completed:)];\n    } else {\n        // 兼容SDWebImage 我们使用的版本 3.7.6\n        [self doraemon_swizzleInstanceMethodWithOriginSel:@selector(sd_setImageWithURL:placeholderImage:options:progress:completed:) swizzledSel:@selector(doraemon_sd_setImageWithURL:placeholderImage:options:progress:completed:)];\n    }\n#pragma clang diagnostic pop\n}\n\n- (void)doraemon_sd_internalSetImageWithURL:(nullable NSURL *)url\n                           placeholderImage:(nullable UIImage *)placeholder\n                                    options:(NSUInteger)options\n                                    context:(id)context\n                              setImageBlock:(id)setImageBlock\n                                   progress:(id)progressBlock\n                                  completed:(void(^)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, NSInteger cacheType, BOOL finished, NSURL * _Nullable imageURL))completedBlock {\n    \n    __weak typeof(self) weafSelf = self;\n    if ([DoraemonLargeImageDetectionManager shareInstance].detecting) {\n        id replaceCompletedBlock = ^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, NSInteger cacheType,BOOL finished, NSURL * _Nullable imageURL) {\n            if (data.length > [DoraemonLargeImageDetectionManager shareInstance].minimumDetectionSize) {\n                NSString *drawText = [NSString stringWithFormat:@\"url : %@ \\n size : %fKB\",[url absoluteString],data.length/1024.];\n                weafSelf.image = [self drawText:drawText inImage:weafSelf.image];\n            }\n            if (completedBlock) {\n                completedBlock(weafSelf.image, data, error, cacheType, finished, imageURL);\n            }\n        };\n        [self doraemon_sd_internalSetImageWithURL:url placeholderImage:placeholder options:options context:context setImageBlock:setImageBlock progress:progressBlock completed:replaceCompletedBlock];\n    } else {\n        [self doraemon_sd_internalSetImageWithURL:url placeholderImage:placeholder options:options context:context setImageBlock:setImageBlock progress:progressBlock completed:completedBlock];\n    }\n}\n\n- (void)doraemon_sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(NSUInteger)options progress:(id)progressBlock completed:(void(^)(UIImage *image, NSError *error, NSInteger cacheType, NSURL *imageURL))completedBlock{\n    __weak typeof(self) weafSelf = self;\n    if ([DoraemonLargeImageDetectionManager shareInstance].detecting) {\n        id replaceCompletedBlock = ^(UIImage *image, NSError *error, NSInteger cacheType, NSURL *imageURL) {\n            NSData *data = UIImageJPEGRepresentation(image, 1.0);\n            if (!data && data.length <= 0) {\n                data = UIImagePNGRepresentation(image);\n            }\n            if (data.length > [DoraemonLargeImageDetectionManager shareInstance].minimumDetectionSize) {\n                NSString *drawText = [NSString stringWithFormat:@\"url : %@ \\n size : %fKB\",[url absoluteString],data.length/1024.];\n                weafSelf.image = [self drawText:drawText inImage:weafSelf.image];\n            }\n            if (completedBlock) {\n                completedBlock(weafSelf.image, error, cacheType,imageURL);\n            }\n        };\n        [self doraemon_sd_setImageWithURL:url placeholderImage:placeholder options:options progress:progressBlock completed:replaceCompletedBlock];\n    } else {\n        [self doraemon_sd_setImageWithURL:url placeholderImage:placeholder options:options progress:progressBlock completed:completedBlock];\n    }\n}\n\n- (UIImage *)drawText:(NSString *)text inImage:(UIImage *)image {\n    UIFont *font = [UIFont boldSystemFontOfSize:12 * (image.size.height / 230)];\n    UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);\n    CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);\n    [image drawInRect:rect];\n    [text drawInRect:CGRectIntegral(rect) withAttributes:@{\n                                                           NSFontAttributeName : font,\n                                                           NSForegroundColorAttributeName : [UIColor redColor]\n                                                           }];\n    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();\n    UIGraphicsEndImageContext();\n    \n    return newImage;\n}\n\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Memory/DoraemonMemoryPlugin.h",
    "content": "//\n//  DoraemonMemoryPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/25.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonMemoryPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Memory/DoraemonMemoryPlugin.m",
    "content": "//\n//  DoraemonMemoryPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/25.\n//\n\n#import \"DoraemonMemoryPlugin.h\"\n#import \"DoraemonMemoryViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonMemoryPlugin\n\n- (void)pluginDidLoad{\n    DoraemonMemoryViewController *vc = [[DoraemonMemoryViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Memory/DoraemonMemoryViewController.h",
    "content": "//\n//  DoraemonMemoryViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/25.\n//\n\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonMemoryViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Memory/DoraemonMemoryViewController.m",
    "content": "//\n//  DoraemonMemoryViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/25.\n//\n\n#import \"DoraemonMemoryViewController.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonMemoryOscillogramWindow.h\"\n#import \"DoraemonMemoryOscillogramViewController.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMemoryViewController ()<DoraemonSwitchViewDelegate, DoraemonOscillogramWindowDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n\n@end\n\n@implementation DoraemonMemoryViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"内存检测\");\n    \n    _switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"内存检测开关\") switchOn:[[DoraemonCacheManager sharedInstance] memorySwitch]];\n    [_switchView needTopLine];\n    [_switchView needDownLine];\n    _switchView.delegate = self;\n    [self.view addSubview:_switchView];\n    [[DoraemonMemoryOscillogramWindow shareInstance] addDelegate:self];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    [[DoraemonCacheManager sharedInstance] saveMemorySwitch:on];\n    if(on){\n        [[DoraemonMemoryOscillogramWindow shareInstance] show];\n    }else{\n        [[DoraemonMemoryOscillogramWindow shareInstance] hide];\n    }\n}\n\n#pragma mark -- DoraemonOscillogramWindowDelegate\n- (void)doraemonOscillogramWindowClosed {\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"内存检测开关\") switchOn:[[DoraemonCacheManager sharedInstance] memorySwitch]];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Memory/Function/DoraemonMemoryOscillogramViewController.h",
    "content": "//\n//  DoraemonMemoryOscillogramViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/25.\n//\n\n#import \"DoraemonOscillogramViewController.h\"\n\n@interface DoraemonMemoryOscillogramViewController : DoraemonOscillogramViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Memory/Function/DoraemonMemoryOscillogramViewController.m",
    "content": "//\n//  DoraemonMemoryOscillogramViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/25.\n//\n\n#import \"DoraemonMemoryOscillogramViewController.h\"\n#import \"DoraemonOscillogramView.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonMemoryUtil.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonMemoryOscillogramWindow.h\"\n\n@interface DoraemonMemoryOscillogramViewController ()\n\n@end\n\n@implementation DoraemonMemoryOscillogramViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n}\n\n- (NSString *)title{\n    return DoraemonLocalizedString(@\"内存检测\");\n}\n\n- (NSString *)lowValue{\n    return @\"0\";\n}\n\n- (NSString *)highValue{\n    return [NSString stringWithFormat:@\"%zi\",[self deviceMemory]];\n}\n\n- (void)closeBtnClick{\n    [[DoraemonCacheManager sharedInstance] saveMemorySwitch:NO];\n    [[DoraemonMemoryOscillogramWindow shareInstance] hide];\n}\n\n//每一秒钟采样一次内存使用率\n- (void)doSecondFunction{\n    NSUInteger useMemoryForApp = [DoraemonMemoryUtil useMemoryForApp];\n    NSUInteger totalMemoryForDevice = [self deviceMemory];\n    \n    // 0~totalMemoryForDevice   对应 高度0~_oscillogramView.doraemon_height\n    [self.oscillogramView addHeightValue:useMemoryForApp*self.oscillogramView.doraemon_height/totalMemoryForDevice andTipValue:[NSString stringWithFormat:@\"%zi\",useMemoryForApp]];\n}\n\n- (NSUInteger)deviceMemory {\n    return 1000;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Memory/Function/DoraemonMemoryOscillogramWindow.h",
    "content": "//\n//  DoraemonMemoryOscillogramWindow.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/25.\n//\n\n#import \"DoraemonOscillogramWindow.h\"\n\n@interface DoraemonMemoryOscillogramWindow : DoraemonOscillogramWindow\n\n+ (DoraemonMemoryOscillogramWindow *)shareInstance;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Memory/Function/DoraemonMemoryOscillogramWindow.m",
    "content": "//\n//  DoraemonMemoryOscillogramWindow.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/25.\n//\n\n#import \"DoraemonMemoryOscillogramWindow.h\"\n#import \"DoraemonMemoryOscillogramViewController.h\"\n\n@implementation DoraemonMemoryOscillogramWindow\n\n+ (DoraemonMemoryOscillogramWindow *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonMemoryOscillogramWindow *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonMemoryOscillogramWindow alloc] initWithFrame:CGRectZero];\n    });\n    return instance;\n}\n\n- (void)addRootVc{\n    DoraemonMemoryOscillogramViewController *vc = [[DoraemonMemoryOscillogramViewController alloc] init];\n    self.rootViewController = vc;\n    self.vc = vc;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Memory/Function/DoraemonMemoryUtil.h",
    "content": "//\n//  DoraemonMemoryUtil.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/25.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface DoraemonMemoryUtil : NSObject\n\n//当前app内存使用量\n+ (NSInteger)useMemoryForApp;\n\n//设备总的内存\n+ (NSInteger)totalMemoryForDevice;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/Memory/Function/DoraemonMemoryUtil.m",
    "content": "//\n//  DoraemonMemoryUtil.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/1/25.\n//\n\n#import \"DoraemonMemoryUtil.h\"\n#include <mach/mach.h>\n\n@implementation DoraemonMemoryUtil\n\n+ (NSInteger)useMemoryForApp{\n    task_vm_info_data_t vmInfo;\n    mach_msg_type_number_t count = TASK_VM_INFO_COUNT;\n    kern_return_t kernelReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);\n    if(kernelReturn == KERN_SUCCESS)\n    {\n        int64_t memoryUsageInByte = (int64_t) vmInfo.phys_footprint;\n        return (NSInteger)(memoryUsageInByte/1024/1024);\n    }\n    else\n    {\n        return -1;\n    }\n}\n\n//设备总的内存\n+ (NSInteger)totalMemoryForDevice{\n    return (NSInteger)([NSProcessInfo processInfo].physicalMemory/1024/1024);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Detail/DoraemonNetFlowDetailCell.h",
    "content": "//\n//  DoraemonNetFlowDetailCell.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/19.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface DoraemonNetFlowDetailCell : UITableViewCell\n\n- (void)renderUIWithContent:(NSString *)content isFirst:(BOOL)isFirst isLast:(BOOL)isLast;\n+ (CGFloat)cellHeightWithContent:(NSString *)content;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Detail/DoraemonNetFlowDetailCell.m",
    "content": "//\n//  DoraemonNetFlowDetailCell.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/19.\n//\n\n#import \"DoraemonNetFlowDetailCell.h\"\n#import \"DoraemonDefine.h\"\n#import \"UIColor+Doraemon.h\"\n\n@interface DoraemonNetFlowDetailCell()\n\n@property (nonatomic, strong) UITextView *contentLabel;\n@property (nonatomic, strong) UIView *upLine;\n@property (nonatomic, strong) UIView *downLine;\n\n@end\n\n@implementation DoraemonNetFlowDetailCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        self.selectionStyle =  UITableViewCellSelectionStyleNone;\n        \n        //大文本显示的时候，UIlabel在模拟器上会显示空白，使用TextView代替。\n        //网上相似问题： https://blog.csdn.net/minghuyong2016/article/details/82882314\n        _contentLabel = [DoraemonNetFlowDetailCell genTextView:16.0];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            self.backgroundColor = [UIColor systemBackgroundColor];\n            \n            _contentLabel.textColor = [UIColor labelColor];\n        } else {\n#endif\n            _contentLabel.textColor = [UIColor blackColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        _contentLabel.editable = NO;\n        [self.contentView addSubview:_contentLabel];\n        \n        _upLine = [[UIView alloc] init];\n        _upLine.backgroundColor = [UIColor doraemon_colorWithHex:0xF2F2F2];\n        [self.contentView addSubview:_upLine];\n        _upLine.hidden = YES;\n        \n        _downLine = [[UIView alloc] init];\n        _downLine.backgroundColor = [UIColor doraemon_colorWithHex:0xF2F2F2];\n        [self.contentView addSubview:_downLine];\n        _downLine.hidden = YES;\n    }\n    return self;\n}\n\n- (void)layoutSubviews {\n    [super layoutSubviews];\n    // 禁用 UITextView 滑动，解决其与 UITableView 的滑动冲突；\n    // 放这里调用是因为在其他地方调用会出现文本未显示的问题(模拟器环境下)\n    _contentLabel.scrollEnabled = false;\n}\n\n- (void)renderUIWithContent:(NSString *)content isFirst:(BOOL)isFirst isLast:(BOOL)isLast{\n    _contentLabel.text = content;\n    CGSize fontSize = [_contentLabel sizeThatFits:CGSizeMake(DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)*2, MAXFLOAT)];\n    _contentLabel.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), kDoraemonSizeFrom750_Landscape(28), fontSize.width, fontSize.height);\n    \n    CGFloat cellHeight = [[self class] cellHeightWithContent:content];\n    if(isFirst && isLast){\n        _upLine.hidden = NO;\n        _upLine.frame = CGRectMake(0, 0, DoraemonScreenWidth, 0.5);\n        _downLine.hidden = NO;\n        _downLine.frame = CGRectMake(0, cellHeight-0.5, DoraemonScreenWidth, 0.5);\n    }else if(isFirst && !isLast){\n        _upLine.hidden = NO;\n        _upLine.frame = CGRectMake(0, 0, DoraemonScreenWidth, 0.5);\n        _downLine.hidden = NO;\n        _downLine.frame = CGRectMake(20, cellHeight-0.5, DoraemonScreenWidth-20, 0.5);\n    }else if(!isFirst && isLast){\n        _upLine.hidden = YES;\n        _downLine.hidden = NO;\n        _downLine.frame = CGRectMake(0, cellHeight-0.5, DoraemonScreenWidth, 0.5);\n    }else{\n        _upLine.hidden = YES;\n        _downLine.hidden = NO;\n        _downLine.frame = CGRectMake(20, cellHeight-0.5, DoraemonScreenWidth-20, 0.5);\n    }\n}\n\n+ (CGFloat)cellHeightWithContent:(NSString *)content{\n    UITextView *tempLabel = [DoraemonNetFlowDetailCell genTextView:16.0];\n    tempLabel.text = content;\n    CGSize fontSize = [tempLabel sizeThatFits:CGSizeMake(DoraemonScreenWidth-2*kDoraemonSizeFrom750_Landscape(32), MAXFLOAT)];\n    return fontSize.height+kDoraemonSizeFrom750_Landscape(28)*2;\n}\n\n/// 生成 UITextView\n+ (UITextView *)genTextView:(CGFloat)fontSize {\n    UITextView *tempTextView = [[UITextView alloc] init];\n    tempTextView.font = [UIFont systemFontOfSize:fontSize];\n    return tempTextView;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Detail/DoraemonNetFlowDetailSegment.h",
    "content": "//\n//  DoraemonNetFlowDetailSegment.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/9.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@protocol DoraemonNetFlowDetailSegmentDelegate <NSObject>\n\n- (void)segmentClick:(NSInteger)index;\n\n@end\n\n@interface DoraemonNetFlowDetailSegment : UIView\n\n@property (nonatomic, weak) id<DoraemonNetFlowDetailSegmentDelegate> delegate;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Detail/DoraemonNetFlowDetailSegment.m",
    "content": "//\n//  DoraemonNetFlowDetailSegment.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/9.\n//\n\n#import \"DoraemonNetFlowDetailSegment.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonNetFlowDetailSegment()\n\n@property (nonatomic, strong) UILabel *leftLabel;\n@property (nonatomic, strong) UILabel *rightLabel;\n@property (nonatomic, strong) UIView *selectLine;\n\n@end\n\n@implementation DoraemonNetFlowDetailSegment\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        self.backgroundColor = [UIColor whiteColor];\n        _leftLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.doraemon_width/2, self.doraemon_height)];\n        _leftLabel.textColor = [UIColor doraemon_colorWithHexString:@\"337CC4\"];\n        _leftLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        _leftLabel.text = DoraemonLocalizedString(@\"请求\");\n        _leftLabel.textAlignment = NSTextAlignmentCenter;\n        [self addSubview:_leftLabel];\n        \n        UITapGestureRecognizer *leftTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(leftTap)];\n        _leftLabel.userInteractionEnabled = YES;\n        [_leftLabel addGestureRecognizer:leftTap];\n        \n        _rightLabel = [[UILabel alloc] initWithFrame:CGRectMake(self.doraemon_width/2, 0, self.doraemon_width/2, self.doraemon_height)];\n        _rightLabel.textColor = [UIColor doraemon_colorWithHexString:@\"333333\"];\n        _rightLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        _rightLabel.text = DoraemonLocalizedString(@\"响应\");\n        _rightLabel.textAlignment = NSTextAlignmentCenter;\n        [self addSubview:_rightLabel];\n        \n        UITapGestureRecognizer *rightTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(rightTap)];\n        _rightLabel.userInteractionEnabled = YES;\n        [_rightLabel addGestureRecognizer:rightTap];\n        \n        _selectLine = [[UIView alloc] initWithFrame:CGRectMake(self.doraemon_width/4-kDoraemonSizeFrom750_Landscape(128)/2, self.doraemon_height-kDoraemonSizeFrom750_Landscape(4), kDoraemonSizeFrom750_Landscape(128), kDoraemonSizeFrom750_Landscape(4))];\n        _selectLine.backgroundColor = [UIColor doraemon_colorWithHexString:@\"337CC4\"];\n        [self addSubview:_selectLine];\n        \n    }\n    return self;\n}\n\n- (void)leftTap{\n    if (_delegate && [_delegate respondsToSelector:@selector(segmentClick:)]) {\n        [_delegate segmentClick:0];\n    }\n    _leftLabel.textColor = [UIColor doraemon_colorWithHexString:@\"337CC4\"];\n    _rightLabel.textColor = [UIColor doraemon_colorWithHexString:@\"333333\"];\n    _selectLine.frame = CGRectMake(self.doraemon_width/4-kDoraemonSizeFrom750_Landscape(128)/2, self.doraemon_height-kDoraemonSizeFrom750_Landscape(4), kDoraemonSizeFrom750_Landscape(128), kDoraemonSizeFrom750_Landscape(4));\n}\n\n- (void)rightTap{\n    if (_delegate && [_delegate respondsToSelector:@selector(segmentClick:)]) {\n        [_delegate segmentClick:1];\n    }\n    _leftLabel.textColor = [UIColor doraemon_colorWithHexString:@\"333333\"];\n    _rightLabel.textColor = [UIColor doraemon_colorWithHexString:@\"337CC4\"];\n    _selectLine.frame = CGRectMake(self.doraemon_width*3/4-kDoraemonSizeFrom750_Landscape(128)/2, self.doraemon_height-kDoraemonSizeFrom750_Landscape(4), kDoraemonSizeFrom750_Landscape(128), kDoraemonSizeFrom750_Landscape(4));\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Detail/DoraemonNetFlowDetailViewController.h",
    "content": "//\n//  DoraemonNetFlowDetailViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/13.\n//\n\n#import \"DoraemonBaseViewController.h\"\n#import \"DoraemonNetFlowHttpModel.h\"\n\n@interface DoraemonNetFlowDetailViewController : DoraemonBaseViewController\n\n@property (nonatomic, strong) DoraemonNetFlowHttpModel *httpModel;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Detail/DoraemonNetFlowDetailViewController.m",
    "content": "//\n//  DoraemonNetFlowDetailViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/13.\n//\n\n#import \"DoraemonNetFlowDetailViewController.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonNetFlowDetailCell.h\"\n#import \"UIColor+Doraemon.h\"\n#import \"DoraemonUrlUtil.h\"\n#import \"DoraemonUtil.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonNetFlowDetailSegment.h\"\n\ntypedef NS_ENUM(NSUInteger, NetFlowSelectState) {\n    NetFlowSelectStateForRequest = 0,\n    NetFlowSelectStateForResponse\n};\n\n@interface DoraemonNetFlowDetailViewController ()<UITableViewDelegate,UITableViewDataSource,DoraemonNetFlowDetailSegmentDelegate>\n\n@property (nonatomic, strong) DoraemonNetFlowDetailSegment *segmentView;\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, assign) NSInteger selectedSegmentIndex;//当前选中的tab\n\n@property (nonatomic, copy) NSArray* requestArray;\n@property (nonatomic, copy) NSArray* responseArray;\n\n@end\n\n@implementation DoraemonNetFlowDetailViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        self.view.backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n            if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                return [UIColor systemBackgroundColor];\n            } else {\n                return [UIColor doraemon_colorWithHex:0xeff0f4];\n            }\n        }];\n    } else {\n#endif\n        self.view.backgroundColor = [UIColor doraemon_colorWithHex:0xeff0f4];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n    \n    [self initData];\n    \n    self.title = DoraemonLocalizedString(@\"网络监控详情\");\n    \n    _segmentView = [[DoraemonNetFlowDetailSegment alloc] initWithFrame:CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(88))];\n    _segmentView.delegate = self;\n    [self.view addSubview:_segmentView];\n    \n    CGFloat tabBarHeight = self.tabBarController.tabBar.doraemon_height;\n    _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, _segmentView.doraemon_bottom, self.view.doraemon_width, self.view.doraemon_height-tabBarHeight-_segmentView.doraemon_bottom) style:UITableViewStyleGrouped];\n    _tableView.delegate = self;\n    _tableView.dataSource = self;\n    _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;\n    _tableView.estimatedRowHeight = 0.;\n    _tableView.estimatedSectionFooterHeight = 0.;\n    _tableView.estimatedSectionHeaderHeight = 0.;\n    [self.view addSubview:_tableView];\n}\n\n- (void)initData{\n    \n    NSString *requestDataSize = [NSString stringWithFormat:DoraemonLocalizedString(@\"数据大小 : %@\"),[DoraemonUtil formatByte:[self.httpModel.uploadFlow floatValue]]];\n    NSString *method = [NSString stringWithFormat:@\"Method : %@\",self.httpModel.method];\n    NSString *linkUrl = self.httpModel.url;\n    NSDictionary<NSString *, NSString *> *allHTTPHeaderFields = self.httpModel.request.allHTTPHeaderFields;\n    NSMutableString *allHTTPHeaderString = [NSMutableString string];\n    for (NSString *key in allHTTPHeaderFields) {\n        NSString *value = allHTTPHeaderFields[key];\n        [allHTTPHeaderString appendFormat:@\"%@ : %@\\r\\n\",key,value];\n    }\n    if (allHTTPHeaderString.length == 0) {\n        allHTTPHeaderString = [NSMutableString stringWithFormat:@\"NULL\"];\n    }\n    \n    NSString *requestBody = self.httpModel.requestBody;\n    if (!requestBody || requestBody.length == 0) {\n        requestBody = @\"NULL\";\n    }\n    \n    _requestArray = @[@{\n                          @\"sectionTitle\":DoraemonLocalizedString(@\"请求概要\"),\n                          @\"dataArray\":@[requestDataSize,method]\n                          },\n                      @{\n                          @\"sectionTitle\":DoraemonLocalizedString(@\"链接\"),\n                          @\"dataArray\":@[linkUrl]\n                          },\n                      @{\n                          @\"sectionTitle\":DoraemonLocalizedString(@\"请求头\"),\n                          @\"dataArray\":@[allHTTPHeaderString]\n                          },\n                      @{\n                          @\"sectionTitle\":DoraemonLocalizedString(@\"请求体\"),\n                          @\"dataArray\":@[requestBody]\n                          }\n                      ];\n    \n    NSString *respanseDataSize = [NSString stringWithFormat:DoraemonLocalizedString(@\"数据大小 : %@\"),[DoraemonUtil formatByte:[self.httpModel.downFlow floatValue]]];\n    NSString *mineType = [NSString stringWithFormat:@\"mineType : %@\",self.httpModel.mineType];\n    NSMutableString *responseHeaderString = [NSMutableString string];\n    for (NSString *key in allHTTPHeaderFields) {\n        NSString *value = allHTTPHeaderFields[key];\n        [responseHeaderString appendFormat:@\"%@ : %@\\r\\n\",key,value];\n    }\n    if (responseHeaderString.length == 0) {\n        responseHeaderString = [NSMutableString stringWithFormat:@\"NULL\"];\n    }\n    NSString *responseBody = self.httpModel.responseBody;\n    if (!responseBody || requestBody.length == 0) {\n        responseBody = @\"NULL\";\n    }\n    \n    _responseArray = @[@{\n                          @\"sectionTitle\":DoraemonLocalizedString(@\"响应概要\"),\n                          @\"dataArray\":@[respanseDataSize,mineType]\n                          },\n                      @{\n                          @\"sectionTitle\":DoraemonLocalizedString(@\"响应头\"),\n                          @\"dataArray\":@[responseHeaderString]\n                          },\n                      @{\n                          @\"sectionTitle\":DoraemonLocalizedString(@\"响应体\"),\n                          @\"dataArray\":@[responseBody]\n                          }\n                      ];\n    \n    _selectedSegmentIndex = NetFlowSelectStateForRequest;\n}\n\n#pragma mark - DoraemonNetFlowDetailSegmentDelegate\n- (void)segmentClick:(NSInteger)index{\n    _selectedSegmentIndex = index;\n    [_tableView reloadData];\n}\n\n#pragma mark - UITableView Delegate\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {\n    NSInteger section=0;\n    if (_selectedSegmentIndex == NetFlowSelectStateForRequest) {\n        section = 4;\n    }else{\n        section = 3;\n    }\n    return section;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    NSInteger row = 0;\n    if (_selectedSegmentIndex == NetFlowSelectStateForRequest) {\n        if(section == 0){\n            row = 2;\n        }else if(section == 1){\n            row = 1;\n        }else if(section == 2){\n            row = 1;\n        }else{\n            row = 1;\n        }\n    }else{\n        if(section == 0){\n            row = 2;\n        }else if(section == 1){\n            row = 1;\n        }else{\n            row = 1;\n        }\n    }\n    \n    return row;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    NSString *content;\n    if (_selectedSegmentIndex == NetFlowSelectStateForRequest) {\n        NSDictionary *itemInfo = _requestArray[indexPath.section];\n        content = itemInfo[@\"dataArray\"][indexPath.row];\n    }else{\n        NSDictionary *itemInfo = _responseArray[indexPath.section];\n        content = itemInfo[@\"dataArray\"][indexPath.row];\n    }\n    return [DoraemonNetFlowDetailCell cellHeightWithContent:content];\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {\n    return kDoraemonSizeFrom750_Landscape(100);\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {\n    return 0.1;\n}\n\n- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{\n    NSString *title;\n    if (_selectedSegmentIndex == NetFlowSelectStateForRequest) {\n        NSDictionary *itemInfo = _requestArray[section];\n        title = itemInfo[@\"sectionTitle\"];\n    }else{\n        NSDictionary *itemInfo = _responseArray[section];\n        title = itemInfo[@\"sectionTitle\"];\n    }\n    \n    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(100))];\n    \n    UILabel *tipLabel = [[UILabel alloc] init];\n    tipLabel.textColor = [UIColor doraemon_colorWithHex:0x337CC4];\n    tipLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n    tipLabel.text = title;\n    tipLabel.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), 0, self.view.doraemon_width-kDoraemonSizeFrom750_Landscape(32), kDoraemonSizeFrom750_Landscape(100));\n    [view addSubview:tipLabel];\n    //tipLabel.backgroundColor = [UIColor doraemon_colorWithHex:0xeff0f4];\n    \n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        view.backgroundColor =  [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n            if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                return [UIColor systemBackgroundColor];\n            } else {\n                return [UIColor whiteColor];\n            }\n        }];\n    }\n#endif\n    return view;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"httpcell\";\n    DoraemonNetFlowDetailCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[DoraemonNetFlowDetailCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];\n    }\n    NSInteger section = indexPath.section;\n    NSInteger row = indexPath.row;\n    \n    NSString *content;\n    if (_selectedSegmentIndex == NetFlowSelectStateForRequest) {\n        NSDictionary *itemInfo = _requestArray[section];\n        content = itemInfo[@\"dataArray\"][row];\n    }else{\n        NSDictionary *itemInfo = _responseArray[section];\n        content = itemInfo[@\"dataArray\"][row];\n    }\n    \n    if (section == 0) {\n        if (row==0) {\n            [cell renderUIWithContent:content isFirst:YES isLast:NO];\n        }else if(row==1){\n            [cell renderUIWithContent:content isFirst:NO isLast:YES];\n        }\n    }else if(section == 1){\n        [cell renderUIWithContent:content isFirst:YES isLast:YES];\n    }else if(section == 2){\n        [cell renderUIWithContent:content isFirst:YES isLast:YES];\n    }else if(section == 3){\n        [cell renderUIWithContent:content isFirst:YES isLast:YES];\n    }\n    return cell;\n}\n\n- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{\n    return YES;\n}\n\n- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath{\n    return DoraemonLocalizedString(@\"复制\");\n}\n\n- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{\n    NSInteger section = indexPath.section;\n    NSInteger row = indexPath.row;\n    \n    NSString *content;\n    if (_selectedSegmentIndex == NetFlowSelectStateForRequest) {\n        NSDictionary *itemInfo = _requestArray[section];\n        content = itemInfo[@\"dataArray\"][row];\n    }else{\n        NSDictionary *itemInfo = _responseArray[section];\n        content = itemInfo[@\"dataArray\"][row];\n    }\n    UIPasteboard *pboard = [UIPasteboard generalPasteboard];\n    pboard.string = content;\n}\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/DoraemonNetFlowPlugin.h",
    "content": "//\n//  DoraemonNetFlowPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/9.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonNetFlowPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/DoraemonNetFlowPlugin.m",
    "content": "//\n//  DoraemonNetFlowPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/9.\n//\n\n#import \"DoraemonNetFlowPlugin.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonNetFlowViewController.h\"\n\n@implementation DoraemonNetFlowPlugin\n\n- (void)pluginDidLoad{\n    DoraemonNetFlowViewController *vc = [[DoraemonNetFlowViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/DoraemonNetFlowViewController.h",
    "content": "//\n//  DoraemonNetFlowViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/9.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonNetFlowViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/DoraemonNetFlowViewController.m",
    "content": "//\n//  DoraemonNetFlowViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/9.\n//\n\n#import \"DoraemonNetFlowViewController.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonNetFlowManager.h\"\n#import \"DoraemonDefine.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonNetFlowListViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonNetFlowSummaryViewController.h\"\n#import \"UIImage+Doraemon.h\"\n#import \"UIColor+Doraemon.h\"\n#import \"DoraemonNetFlowOscillogramWindow.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonDefine.h\"\n\n\n@interface DoraemonNetFlowViewController ()<DoraemonSwitchViewDelegate, DoraemonOscillogramWindowDelegate>\n@property (nonatomic, strong) UITabBarController *tabBar;\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n\n@end\n\n@implementation DoraemonNetFlowViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    [self initUI];\n    [[DoraemonNetFlowOscillogramWindow shareInstance] addDelegate:self];\n}\n\n- (void)initUI{\n    self.title = DoraemonLocalizedString(@\"网络检测\");\n    \n    _switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"网络检测开关\") switchOn:[[DoraemonCacheManager sharedInstance] netFlowSwitch]];\n    [_switchView needTopLine];\n    [_switchView needDownLine];\n    _switchView.delegate = self;\n    [self.view addSubview:_switchView];\n\n    UIButton *showNetFlowDetailBtn = [UIButton buttonWithType:UIButtonTypeCustom];\n    showNetFlowDetailBtn.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), _switchView.doraemon_bottom+kDoraemonSizeFrom750_Landscape(60), self.view.doraemon_width-2*kDoraemonSizeFrom750_Landscape(32), kDoraemonSizeFrom750_Landscape(100));\n    [showNetFlowDetailBtn setTitle:DoraemonLocalizedString(@\"显示网络检测详情\") forState:UIControlStateNormal];\n    showNetFlowDetailBtn.backgroundColor = [UIColor doraemon_colorWithHexString:@\"#337CC4\"];\n    [showNetFlowDetailBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];\n    [showNetFlowDetailBtn addTarget:self action:@selector(showNetFlowDetail) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:showNetFlowDetailBtn];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {\n    [super traitCollectionDidChange:previousTraitCollection];\n    // trait发生了改变\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {\n            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, -0.5, CGRectGetWidth(self.tabBar.tabBar.frame), 0.5)];\n                view.backgroundColor = [UIColor doraemon_black_3];\n                [self.tabBar.tabBar insertSubview:view atIndex:0];\n            }\n        }\n    }\n#endif\n}\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    if (sender == _switchView.switchView) {\n        [[DoraemonCacheManager sharedInstance] saveNetFlowSwitch:on];\n        if(on){\n            [[DoraemonNetFlowManager shareInstance] canInterceptNetFlow:YES];\n            [self showOscillogramView];\n        }else{\n            [[DoraemonNetFlowManager shareInstance] canInterceptNetFlow:NO];\n            [self hiddenOscillogramView];\n        }\n    }\n}\n\n- (void)showOscillogramView{\n    [[DoraemonNetFlowOscillogramWindow shareInstance] show];\n}\n\n- (void)hiddenOscillogramView{\n    [[DoraemonNetFlowOscillogramWindow shareInstance] hide];\n}\n\n\n- (void)showNetFlowDetail {\n    UITabBarController *tabBar = [[UITabBarController alloc] init];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        tabBar.tabBar.backgroundColor = [UIColor systemBackgroundColor];\n        if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n            UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, -0.5, CGRectGetWidth(tabBar.tabBar.frame), 0.5)];\n            view.backgroundColor = [UIColor doraemon_black_3];\n            [tabBar.tabBar insertSubview:view atIndex:0];\n        }\n    } else {\n#endif\n        tabBar.tabBar.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n    _tabBar = tabBar;\n    \n    UIViewController *vc1 = [[DoraemonNetFlowSummaryViewController alloc] init];\n    UINavigationController *nav1 = [[UINavigationController alloc] initWithRootViewController:vc1];\n    nav1.tabBarItem = [[UITabBarItem alloc] initWithTitle:DoraemonLocalizedString(@\"网络监控摘要\") image:[[[UIImage doraemon_xcassetImageNamed:@\"doraemon_netflow_summary_unselect\"] doraemon_scaledToSize:CGSizeMake(30,30)] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] selectedImage:[[[UIImage doraemon_xcassetImageNamed:@\"doraemon_netflow_summary_select\"] doraemon_scaledToSize:CGSizeMake(30,30)] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];\n    [nav1.tabBarItem setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor doraemon_colorWithHex:0x333333],NSFontAttributeName:[UIFont systemFontOfSize:10]} forState:UIControlStateNormal];\n    [nav1.tabBarItem setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor doraemon_colorWithHex:0x337CC4],NSFontAttributeName:[UIFont systemFontOfSize:10]} forState:UIControlStateSelected];\n    \n    \n    UIViewController *vc2 = [[DoraemonNetFlowListViewController alloc] init];\n    UINavigationController *nav2 = [[UINavigationController alloc] initWithRootViewController:vc2];\n    nav2.tabBarItem = [[UITabBarItem alloc] initWithTitle:DoraemonLocalizedString(@\"网络监控列表\") image:[[[UIImage doraemon_xcassetImageNamed:@\"doraemon_netflow_list_unselect\"] doraemon_scaledToSize:CGSizeMake(30,30)] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] selectedImage:[[[UIImage doraemon_xcassetImageNamed:@\"doraemon_netflow_list_select\"] doraemon_scaledToSize:CGSizeMake(30,30)] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];\n    [nav2.tabBarItem setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor doraemon_colorWithHex:0x333333],NSFontAttributeName:[UIFont systemFontOfSize:10]} forState:UIControlStateNormal];\n    [nav2.tabBarItem setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor doraemon_colorWithHex:0x337CC4],NSFontAttributeName:[UIFont systemFontOfSize:10]} forState:UIControlStateSelected];\n    \n    tabBar.viewControllers = @[nav1,nav2];\n    \n    tabBar.modalPresentationStyle = UIModalPresentationFullScreen;\n    [self presentViewController:tabBar animated:YES completion:nil];\n}\n\n#pragma mark -- DoraemonOscillogramWindowDelegate\n- (void)doraemonOscillogramWindowClosed {\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"网络检测开关\") switchOn:[[DoraemonCacheManager sharedInstance] netFlowSwitch]];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Function/DoraemonNetFlowDataSource.h",
    "content": "//\n//  DoraemonNetFlowDataSource.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/11.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonNetFlowHttpModel.h\"\n\n@interface DoraemonNetFlowDataSource : NSObject\n\n@property (nonatomic, strong) NSMutableArray<DoraemonNetFlowHttpModel *> *httpModelArray;\n\n+ (DoraemonNetFlowDataSource *)shareInstance;\n\n- (void)addHttpModel:(DoraemonNetFlowHttpModel *)httpModel;\n\n- (void)clear;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Function/DoraemonNetFlowDataSource.m",
    "content": "//\n//  DoraemonNetFlowDataSource.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/11.\n//\n\n#import \"DoraemonNetFlowDataSource.h\"\n\n@implementation DoraemonNetFlowDataSource{\n    dispatch_semaphore_t semaphore;\n}\n\n+ (DoraemonNetFlowDataSource *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonNetFlowDataSource *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonNetFlowDataSource alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    self = [super init];\n    if (self) {\n        _httpModelArray = [NSMutableArray array];\n        semaphore = dispatch_semaphore_create(1);\n    }\n    return self;\n}\n\n- (void)addHttpModel:(DoraemonNetFlowHttpModel *)httpModel{\n    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);\n    [_httpModelArray insertObject:httpModel atIndex:0];\n    dispatch_semaphore_signal(semaphore);\n}\n\n- (void)clear{\n    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);\n    [_httpModelArray removeAllObjects];\n    dispatch_semaphore_signal(semaphore);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Function/DoraemonNetFlowHttpModel.h",
    "content": "//\n//  DoraemonNetFlowHttpModel.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/11.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface DoraemonNetFlowHttpModel : NSObject\n\n@property (nonatomic, copy) NSString *requestId;\n@property (nonatomic, copy) NSString *url;\n@property (nonatomic, copy) NSString *method;\n@property (nonatomic, copy) NSString *requestBody;\n@property (nonatomic, copy) NSString *statusCode;\n@property (nonatomic, copy) NSData *responseData;\n@property (nonatomic, copy) NSString *responseBody;\n@property (nonatomic, copy) NSString *mineType;\n@property (nonatomic, assign) NSTimeInterval startTime;\n@property (nonatomic, assign) NSTimeInterval endTime;\n@property (nonatomic, copy) NSString *totalDuration;\n@property (nonatomic, copy) NSString *uploadFlow;//上行流量 单位字节B\n@property (nonatomic, copy) NSString *downFlow;//下行流量 单位字节B\n\n@property (nonatomic, strong) NSURLRequest *request;\n@property (nonatomic, strong) NSURLResponse *response;\n\n@property (nonatomic, copy) NSString *topVc;//流量触发时候的顶层vc\n\n+ (void)dealWithResponseData:(NSData *)responseData response:(NSURLResponse*)response request:(NSURLRequest *)request complete:(void (^)(DoraemonNetFlowHttpModel *model))complete;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Function/DoraemonNetFlowHttpModel.m",
    "content": "//\n//  DoraemonNetFlowHttpModel.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/11.\n//\n\n#import \"DoraemonNetFlowHttpModel.h\"\n#import \"DoraemonNetFlowManager.h\"\n#import \"NSURLRequest+Doraemon.h\"\n#import \"DoraemonUrlUtil.h\"\n\n@implementation DoraemonNetFlowHttpModel\n\n+ (void)dealWithResponseData:(NSData *)responseData response:(NSURLResponse*)response request:(NSURLRequest *)request complete:(void (^)(DoraemonNetFlowHttpModel *model))complete {\n    DoraemonNetFlowHttpModel *httpModel = [[DoraemonNetFlowHttpModel alloc] init];\n    //request\n    httpModel.request = request;\n    httpModel.requestId = request.requestId;\n    httpModel.url = [request.URL absoluteString];\n    httpModel.method = request.HTTPMethod;\n    //response\n    httpModel.mineType = response.MIMEType;\n    httpModel.response = response;\n    NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;\n    httpModel.statusCode = [NSString stringWithFormat:@\"%d\",(int)httpResponse.statusCode];\n    httpModel.responseData = responseData;\n    httpModel.responseBody = [DoraemonUrlUtil convertJsonFromData:responseData];\n    httpModel.totalDuration = [NSString stringWithFormat:@\"%fs\",[[NSDate date] timeIntervalSince1970] - request.startTime.doubleValue];\n    httpModel.downFlow = [NSString stringWithFormat:@\"%lli\",[DoraemonUrlUtil getResponseLength:(NSHTTPURLResponse *)response data:responseData]];\n    [[DoraemonNetFlowManager shareInstance] httpBodyFromRequest:request bodyCallBack:^(NSData *body) {\n        httpModel.requestBody = [DoraemonUrlUtil convertJsonFromData:body];\n        NSUInteger length = [DoraemonUrlUtil getHeadersLengthWithRequest:request] + [body length];\n        httpModel.uploadFlow = [NSString stringWithFormat:@\"%zi\", length];\n        complete(httpModel);\n    }];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Function/DoraemonNetFlowManager.h",
    "content": "//\n//  DoraemonNetFlowManager.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/11.\n//\n\n#import <Foundation/Foundation.h>\n\ntypedef void(^HttpBodyCallBack)(NSData *body);\n\n@interface DoraemonNetFlowManager : NSObject\n\n+ (DoraemonNetFlowManager *)shareInstance;\n\n@property (nonatomic, strong) NSDate *startInterceptDate;\n@property (nonatomic, assign) BOOL canIntercept;\n\n- (void)canInterceptNetFlow:(BOOL)enable;\n\n- (void)httpBodyFromRequest:(NSURLRequest *)request bodyCallBack:(HttpBodyCallBack)complete;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Function/DoraemonNetFlowManager.m",
    "content": "//\n//  DoraemonNetFlowManager.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/11.\n//\n\n#import \"DoraemonNetFlowManager.h\"\n#import \"DoraemonNSURLProtocol.h\"\n#import \"DoraemonNetFlowDataSource.h\"\n#import \"NSObject+Doraemon.h\"\n#import \"DoraemonNetworkInterceptor.h\"\n#import \"UIViewController+Doraemon.h\"\n#import \"DoraemonHealthManager.h\"\n#import <objc/runtime.h>\n\n@interface NSInputStream (DoraemonHttpBodyCallBack)\n\n@property (nonatomic, strong) id<NSStreamDelegate> dkStrongDelegate;\n@end\n\n@implementation NSInputStream (DoraemonHttpBodyCallBack)\n\n- (void)setDkStrongDelegate:(id<NSStreamDelegate>)dkStrongDelegate {\n    objc_setAssociatedObject(self, @selector(dkStrongDelegate), dkStrongDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);\n}\n\n- (id<NSStreamDelegate>)dkStrongDelegate {\n    return objc_getAssociatedObject(self, _cmd);\n}\n@end\n\n@interface DoraemonInputStreamDelegate : NSObject<NSStreamDelegate>\n\n@property (nonatomic, strong) NSMutableData *bodyData;\n@property (nonatomic, copy) HttpBodyCallBack bodyCallBack;\n@end\n\n@implementation DoraemonInputStreamDelegate\n- (instancetype)initWithCallback:(HttpBodyCallBack)callback inputStream:(NSInputStream *)inputStream{\n    self = [super init];\n    if(self){\n        _bodyCallBack = callback;\n        inputStream.dkStrongDelegate = self;//keep alive\n        inputStream.delegate = self;\n    }\n    return self;\n}\n\n#pragma mark -- NSStreamDelegate\n\n- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {\n    switch (eventCode) {\n        case NSStreamEventHasBytesAvailable:\n        {\n            if (!self.bodyData) {\n                self.bodyData = [NSMutableData data];\n            }\n            uint8_t buf[1024];\n            NSInteger len = 0;\n            len = [(NSInputStream *)aStream read:buf maxLength:1024];\n            if (len) {\n                [self.bodyData appendBytes:(const void *)buf length:len];\n            }\n        }\n            break;\n        case NSStreamEventErrorOccurred:\n        {\n            NSError * error = [aStream streamError];\n            NSString * errorInfo = [NSString stringWithFormat:@\"Failed while reading stream; error '%@' (code %ld)\", error.localizedDescription, error.code];\n            NSLog(@\"%@\",errorInfo);\n        }\n            break;\n        case NSStreamEventEndEncountered:\n        {\n            [aStream close];\n            [aStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];\n            if (self.bodyCallBack) {\n                self.bodyCallBack([self.bodyData copy]);\n            }\n            self.bodyData = nil;\n        }\n            break;\n        default:\n            break;\n    }\n}\n\n\n@end\n\n\n\n\n@interface DoraemonNetFlowManager() <DoraemonNetworkInterceptorDelegate>\n\n\n@property (nonatomic, strong) NSMapTable *bodyCallBackMap;\n\n@end\n\n@implementation DoraemonNetFlowManager\n\n+ (DoraemonNetFlowManager *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonNetFlowManager *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonNetFlowManager alloc] init];\n        instance.bodyCallBackMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsWeakMemory valueOptions:NSPointerFunctionsStrongMemory];\n    });\n    return instance;\n}\n\n- (void)canInterceptNetFlow:(BOOL)enable{\n    _canIntercept = enable;\n    if (enable) {\n        [[DoraemonNetworkInterceptor shareInstance] addDelegate:self];\n        _startInterceptDate = [NSDate date];\n    }else{\n        [DoraemonNetworkInterceptor.shareInstance removeDelegate:self];\n        _startInterceptDate = nil;\n        [[DoraemonNetFlowDataSource shareInstance] clear];\n    }\n}\n\n- (void)httpBodyFromRequest:(NSURLRequest *)request bodyCallBack:(HttpBodyCallBack)complete {\n    NSData *httpBody = nil;\n    if (request.HTTPBody) {\n        httpBody = request.HTTPBody;\n        complete(httpBody);\n        return;\n    }\n    if ([request.HTTPMethod isEqualToString:@\"POST\"]) {\n        NSInputStream *stream = request.HTTPBodyStream;\n        DoraemonInputStreamDelegate *delegate = [[DoraemonInputStreamDelegate alloc] initWithCallback:complete inputStream:stream];\n        [stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];\n        [stream open];\n    } else {\n        complete(httpBody);\n    }\n}\n\n#pragma mark -- DoraemonNetworkInterceptorDelegate\n- (void)doraemonNetworkInterceptorDidReceiveData:(NSData *)data response:(NSURLResponse *)response request:(NSURLRequest *)request error:(NSError *)error startTime:(NSTimeInterval)startTime {\n    [DoraemonNetFlowHttpModel dealWithResponseData:data response:response request:request complete:^(DoraemonNetFlowHttpModel *httpModel) {\n        if (!response) {\n            httpModel.statusCode = error.localizedDescription;\n        }\n        httpModel.startTime = startTime;\n        httpModel.endTime = [[NSDate date] timeIntervalSince1970];\n        \n        httpModel.totalDuration = [NSString stringWithFormat:@\"%f\",[[NSDate date] timeIntervalSince1970] - startTime];\n        httpModel.topVc = NSStringFromClass([[UIViewController topViewControllerForKeyWindow] class]);\n        \n        [[DoraemonNetFlowDataSource shareInstance] addHttpModel:httpModel];\n        [[DoraemonHealthManager sharedInstance] addHttpModel:httpModel];\n    }];\n}\n\n- (BOOL)shouldIntercept {\n    return _canIntercept;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Function/Util/DoraemonUrlUtil.h",
    "content": "//\n//  DoraemonUrlUtil.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/23.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface DoraemonUrlUtil : NSObject\n\n+ (NSString *)convertJsonFromData:(NSData *)data;\n\n+ (NSDictionary *)convertDicFromData:(NSData *)data;\n\n+ (NSUInteger)getHeadersLengthWithRequest:(NSURLRequest *)request;\n\n+ (void)requestLength:(NSURLRequest *)request callBack:(void (^)(NSUInteger))callBack;\n\n+ (NSUInteger)getHeadersLength:(NSDictionary *)headers ;\n\n+ (NSDictionary<NSString *, NSString *> *)getCookies:(NSURLRequest *)request ;\n\n+ (int64_t)getResponseLength:(NSHTTPURLResponse *)response data:(NSData *)responseData;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Function/Util/DoraemonUrlUtil.m",
    "content": "//\n//  DoraemonUrlUtil.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/23.\n//\n\n#import \"DoraemonUrlUtil.h\"\n#import \"DoraemonNetFlowManager.h\"\n\n@implementation DoraemonUrlUtil\n\n+ (NSString *)convertJsonFromData:(NSData *)data{\n    if (!data) {\n        return nil;\n    }\n    NSString *jsonString = nil;\n    \n    id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];\n    if ([NSJSONSerialization isValidJSONObject:jsonObject]) {\n        jsonString = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:jsonObject options:NSJSONWritingPrettyPrinted error:NULL] encoding:NSUTF8StringEncoding];\n        // NSJSONSerialization escapes forward slashes. We want pretty json, so run through and unescape the slashes.\n        jsonString = [jsonString stringByReplacingOccurrencesOfString:@\"\\\\/\" withString:@\"/\"];\n    } else {\n        jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];\n    }\n    return jsonString;\n}\n\n+ (NSDictionary *)convertDicFromData:(NSData *)data{\n    if (!data) {\n        return nil;\n    }\n    NSDictionary *jsonObj = nil;\n    \n    id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];\n    if ([NSJSONSerialization isValidJSONObject:jsonObject]){\n        jsonObj = jsonObject;\n    }else{\n        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];\n        if (!str) return jsonObj;\n        NSArray *componentsArray =  [str componentsSeparatedByString:@\"&\"];\n        NSMutableDictionary *dic = @{}.mutableCopy;\n        [componentsArray enumerateObjectsUsingBlock:^(NSString *_Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {\n            NSArray *keyValues =  [obj componentsSeparatedByString:@\"=\"];\n            if (keyValues.count == 2) {\n                [dic setValue:keyValues.lastObject forKey:keyValues.firstObject];\n            }\n        }];\n        if (dic.allKeys.count > 0) {\n            jsonObj = dic.copy;\n        }\n    }\n    return jsonObj;\n}\n+ (void)requestLength:(NSURLRequest *)request callBack:(void (^)(NSUInteger))callBack {\n    NSUInteger headersLength = [self getHeadersLengthWithRequest:request];\n    [[DoraemonNetFlowManager shareInstance] httpBodyFromRequest:request bodyCallBack:^(NSData *body) {\n        NSUInteger bodyLength = [body length];\n        callBack(headersLength + bodyLength);\n    }];\n}\n\n+ (NSUInteger)getHeadersLengthWithRequest:(NSURLRequest *)request {\n    NSDictionary<NSString *, NSString *> *headerFields = request.allHTTPHeaderFields;\n    NSDictionary<NSString *, NSString *> *cookiesHeader = [self getCookies:request];\n    if (cookiesHeader.count) {\n        NSMutableDictionary *headerFieldsWithCookies = [NSMutableDictionary dictionaryWithDictionary:headerFields];\n        [headerFieldsWithCookies addEntriesFromDictionary:cookiesHeader];\n        headerFields = [headerFieldsWithCookies copy];\n    }\n    return [self getHeadersLength:headerFields];\n}\n\n+ (NSUInteger)getHeadersLength:(NSDictionary *)headers {\n    NSUInteger headersLength = 0;\n    if (headers) {\n        NSData *data = [NSJSONSerialization dataWithJSONObject:headers\n                                                       options:NSJSONWritingPrettyPrinted\n                                                         error:nil];\n        headersLength = data.length;\n    }\n    \n    return headersLength;\n}\n\n+ (NSDictionary<NSString *, NSString *> *)getCookies:(NSURLRequest *)request {\n    NSDictionary<NSString *, NSString *> *cookiesHeader;\n    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];\n    NSArray<NSHTTPCookie *> *cookies = [cookieStorage cookiesForURL:request.URL];\n    if (cookies.count) {\n        cookiesHeader = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];\n    }\n    return cookiesHeader;\n}\n\n+ (int64_t)getResponseLength:(NSHTTPURLResponse *)response data:(NSData *)responseData{\n    int64_t responseLength = 0;\n    if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {\n        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;\n        NSDictionary<NSString *, NSString *> *headerFields = httpResponse.allHeaderFields;\n        NSUInteger headersLength = [self getHeadersLength:headerFields];\n        \n        int64_t contentLength = 0.;\n        if(httpResponse.expectedContentLength != NSURLResponseUnknownLength){\n            contentLength = httpResponse.expectedContentLength;\n        }else{\n            contentLength = responseData.length;\n        }\n        \n        responseLength = headersLength + contentLength;\n    }\n    return responseLength;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Function/Util/NSURLRequest+Doraemon.h",
    "content": "//\n//  NSURLRequest+Doraemon.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/11.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface NSURLRequest (Doraemon)\n\n- (NSString *)requestId;\n- (void)setRequestId:(NSString *)requestId;\n\n\n- (NSNumber*)startTime;\n- (void)setStartTime:(NSNumber*)startTime;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Function/Util/NSURLRequest+Doraemon.m",
    "content": "//\n//  NSURLRequest+Doraemon.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/11.\n//\n\n#import \"NSURLRequest+Doraemon.h\"\n#import <objc/runtime.h>\n\n@implementation NSURLRequest (Doraemon)\n\n- (NSString *)requestId {\n    return objc_getAssociatedObject(self, @\"requestId\");\n}\n\n- (void)setRequestId:(NSString *)requestId {\n    objc_setAssociatedObject(self, @\"requestId\", requestId, OBJC_ASSOCIATION_COPY_NONATOMIC);\n}\n\n- (NSNumber*)startTime {\n    return objc_getAssociatedObject(self, @\"startTime\");\n}\n\n- (void)setStartTime:(NSNumber*)startTime {\n    objc_setAssociatedObject(self, @\"startTime\", startTime, OBJC_ASSOCIATION_COPY_NONATOMIC);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Function/View/DoraemonNetFlowOscillogramViewController.h",
    "content": "//\n//  DoraemonNetFlowOscillogramViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/5/2.\n//\n\n#import \"DoraemonOscillogramViewController.h\"\n\n@interface DoraemonNetFlowOscillogramViewController : DoraemonOscillogramViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Function/View/DoraemonNetFlowOscillogramViewController.m",
    "content": "//\n//  DoraemonNetFlowOscillogramViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/5/2.\n//\n\n#import \"DoraemonNetFlowOscillogramViewController.h\"\n#import \"DoraemonOscillogramView.h\"\n#import \"DoraemonNetFlowDataSource.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonNetFlowOscillogramWindow.h\"\n\n@interface DoraemonNetFlowOscillogramViewController ()\n\n@end\n\n@implementation DoraemonNetFlowOscillogramViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n}\n\n- (NSString *)title{\n    return DoraemonLocalizedString(@\"网络监控\");\n}\n\n- (NSString *)lowValue{\n    return @\"0\";\n}\n\n- (NSString *)highValue{\n    return [NSString stringWithFormat:@\"%zi\",[self highestNetFlow]];\n}\n\n- (void)closeBtnClick{\n    [[DoraemonCacheManager sharedInstance] saveNetFlowSwitch:NO];\n    [[DoraemonNetFlowOscillogramWindow shareInstance] hide];\n}\n\n//每一秒钟采样一次流量情况\n- (void)doSecondFunction{\n    NSUInteger useNetFlowForApp = 0.;\n    NSUInteger totalNetFlowForDevice = [self highestNetFlow];\n    \n    NSTimeInterval now = [[NSDate date] timeIntervalSince1970];\n    NSTimeInterval start = now - 1;\n    \n    NSMutableArray<DoraemonNetFlowHttpModel *> *httpModelArray = [DoraemonNetFlowDataSource shareInstance].httpModelArray;\n    \n    NSInteger totalNetFlow = 0.;\n    for (DoraemonNetFlowHttpModel *httpModel in httpModelArray) {\n        NSTimeInterval netFlowEndTime = httpModel.endTime;\n        if (netFlowEndTime >= start && netFlowEndTime <= now) {\n            NSString *upFlow = httpModel.uploadFlow;\n            NSString *downFlow = httpModel.downFlow;\n            NSUInteger upFlowInt = [upFlow integerValue];\n            NSUInteger downFlowInt = [downFlow integerValue];\n            totalNetFlow += (upFlowInt + downFlowInt);\n        }\n    }\n    \n    useNetFlowForApp = totalNetFlow;\n    \n    // 0~highestNetFlow   对应 高度0~200\n    [self.oscillogramView addHeightValue:useNetFlowForApp*self.oscillogramView.doraemon_height/totalNetFlowForDevice andTipValue:[NSString stringWithFormat:@\"%ziB\",useNetFlowForApp]];\n}\n\n- (NSUInteger)highestNetFlow {\n    return 1000;//10000Byte\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Function/View/DoraemonNetFlowOscillogramWindow.h",
    "content": "//\n//  DoraemonNetFlowOscillogramWindow.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/5/2.\n//\n\n#import \"DoraemonOscillogramWindow.h\"\n\n@interface DoraemonNetFlowOscillogramWindow : DoraemonOscillogramWindow\n\n+ (DoraemonNetFlowOscillogramWindow *)shareInstance;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Function/View/DoraemonNetFlowOscillogramWindow.m",
    "content": "//\n//  DoraemonNetFlowOscillogramWindow.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/5/2.\n//\n\n#import \"DoraemonNetFlowOscillogramWindow.h\"\n#import \"DoraemonNetFlowOscillogramViewController.h\"\n\n@implementation DoraemonNetFlowOscillogramWindow\n\n+ (DoraemonNetFlowOscillogramWindow *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonNetFlowOscillogramWindow *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonNetFlowOscillogramWindow alloc] initWithFrame:CGRectZero];\n    });\n    return instance;\n}\n\n- (void)addRootVc{\n    DoraemonNetFlowOscillogramViewController *vc = [[DoraemonNetFlowOscillogramViewController alloc] init];\n    self.rootViewController = vc;\n    self.vc = vc;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/List/DoraemonNetFlowListCell.h",
    "content": "//\n//  DoraemonNetFlowListCell.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/13.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonNetFlowHttpModel.h\"\n\n@interface DoraemonNetFlowListCell : UITableViewCell\n\n- (void)renderCellWithModel:(DoraemonNetFlowHttpModel *)httpModel;\n\n+ (CGFloat)cellHeightWithModel:(DoraemonNetFlowHttpModel *)httpModel;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/List/DoraemonNetFlowListCell.m",
    "content": "//\n//  DoraemonNetFlowListCell.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/13.\n//\n\n#import \"DoraemonNetFlowListCell.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n#import \"UIColor+Doraemon.h\"\n#import \"DoraemonUtil.h\"\n#import \"Doraemoni18NUtil.h\"\n\nstatic CGFloat const kFontSize = 10;\n\n@interface DoraemonNetFlowListCell()\n\n@property (nonatomic, strong) UILabel *urlLabel;//url信息\n@property (nonatomic, strong) UILabel *methodLabel;//请求方式\n@property (nonatomic, strong) UILabel *statusLabel;//请求状态\n@property (nonatomic, strong) UILabel *startTimeLabel;//请求开始时间\n@property (nonatomic, strong) UILabel *timeLabel;//请求耗时\n@property (nonatomic, strong) UILabel *flowLabel;//流量信息\n\n@end\n\n@implementation DoraemonNetFlowListCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        \n        self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;\n        \n        self.urlLabel = [[UILabel alloc] init];\n        self.urlLabel.font = [UIFont systemFontOfSize:kFontSize];\n        \n        self.urlLabel.numberOfLines = 0;\n        [self.contentView addSubview:self.urlLabel];\n        \n        self.methodLabel = [[UILabel alloc] init];\n        self.methodLabel.font = [UIFont systemFontOfSize:kFontSize];\n        self.methodLabel.textColor = [UIColor whiteColor];\n        self.methodLabel.backgroundColor = [UIColor doraemon_colorWithHex:0XD26282];\n        self.methodLabel.layer.cornerRadius = 2;\n        self.methodLabel.layer.masksToBounds = YES;\n        [self.contentView addSubview:self.methodLabel];\n        \n        self.statusLabel = [[UILabel alloc] init];\n        self.statusLabel.font = [UIFont systemFontOfSize:kFontSize];\n        \n        [self.contentView addSubview:self.statusLabel];\n        \n        self.startTimeLabel = [[UILabel alloc] init];\n        self.startTimeLabel.font = [UIFont systemFontOfSize:kFontSize];\n        \n        [self.contentView addSubview:self.startTimeLabel];\n        \n        self.timeLabel = [[UILabel alloc] init];\n        self.timeLabel.font = [UIFont systemFontOfSize:kFontSize];\n        \n        [self.contentView addSubview:self.timeLabel];\n        \n        self.flowLabel = [[UILabel alloc] init];\n        self.flowLabel.font = [UIFont systemFontOfSize:kFontSize];\n        [self.contentView addSubview:self.flowLabel];\n        \n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            self.urlLabel.textColor = [UIColor labelColor];\n            self.statusLabel.textColor = [UIColor labelColor];\n            self.startTimeLabel.textColor = [UIColor labelColor];\n            self.timeLabel.textColor = [UIColor labelColor];\n            self.flowLabel.textColor = [UIColor labelColor];\n        } else {\n#endif\n            self.urlLabel.textColor = [UIColor blackColor];\n            self.statusLabel.textColor = [UIColor blackColor];\n            self.startTimeLabel.textColor = [UIColor blackColor];\n            self.timeLabel.textColor = [UIColor blackColor];\n            self.flowLabel.textColor = [UIColor blackColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n    }\n    return self;\n}\n\n- (void)renderCellWithModel:(DoraemonNetFlowHttpModel *)httpModel{\n    CGFloat startY = 5,startX=10;\n    NSString *urlString = httpModel.url;\n    if (urlString.length>0){\n        self.urlLabel.text = urlString;\n        CGSize size = [self.urlLabel sizeThatFits:CGSizeMake(DoraemonScreenWidth-50, CGFLOAT_MAX)];\n        self.urlLabel.frame = CGRectMake(startX, startY, DoraemonScreenWidth-40, size.height);\n        startY += self.urlLabel.doraemon_height+2;\n    }\n    \n    CGFloat height = 0;\n    NSString *method = httpModel.method;\n    NSString *status = httpModel.statusCode;\n    if (method.length>0) {\n        NSString *mineType = httpModel.mineType;\n        if (mineType.length>0) {\n            self.methodLabel.text = [NSString stringWithFormat:@\" %@ > %@ \",method,mineType];\n        }else{\n            self.methodLabel.text = [NSString stringWithFormat:@\" %@ \",method];\n        }\n        [self.methodLabel sizeToFit];\n        self.methodLabel.frame = CGRectMake(10, startY, self.methodLabel.doraemon_width, self.methodLabel.doraemon_height);\n        startX = self.methodLabel.doraemon_right+5;\n        height = self.methodLabel.doraemon_height;\n    }\n    if (status.length>0) {\n        self.statusLabel.text =[NSString stringWithFormat:@\"[%@]\",status];\n        [self.statusLabel sizeToFit];\n        self.statusLabel.frame = CGRectMake(startX, self.urlLabel.doraemon_bottom+2, self.statusLabel.doraemon_width, self.statusLabel.doraemon_height);\n        height = self.statusLabel.doraemon_height;\n    }\n    if (method.length>0 || status.length>0) {\n        startY += height + 2;\n    }\n    \n    startX = 10;\n    \n    NSString *startTime = [DoraemonUtil dateFormatTimeInterval:httpModel.startTime];\n    NSString *time = httpModel.totalDuration;\n    if (startTime.length>0) {\n        self.startTimeLabel.text = startTime;\n        [self.startTimeLabel sizeToFit];\n        self.startTimeLabel.frame = CGRectMake(startX, startY, self.startTimeLabel.doraemon_width, self.startTimeLabel.doraemon_height);\n        startX = self.startTimeLabel.doraemon_right + 5;\n        height = self.startTimeLabel.doraemon_height;\n    }\n    if (time.length>0) {\n        self.timeLabel.text = [NSString stringWithFormat:@\"%@:%@s\",DoraemonLocalizedString(@\"耗时\"),time];\n        [self.timeLabel sizeToFit];\n        self.timeLabel.frame = CGRectMake(startX, startY, self.timeLabel.doraemon_width, self.timeLabel.doraemon_height);\n        height = self.startTimeLabel.doraemon_height;\n    }\n    \n    if (startTime.length>0 || time.length>0) {\n        startY += height+2;\n    }\n    startX = 10;\n    \n    NSString *uploadFlow = [DoraemonUtil formatByte:[httpModel.uploadFlow floatValue]];\n    NSString *downFlow = [DoraemonUtil formatByte:[httpModel.downFlow floatValue]];\n    if(uploadFlow.length>0 || downFlow.length>0){\n        NSMutableString *netflow = [NSMutableString string];\n        if (uploadFlow.length>0) {\n            [netflow appendString:[NSString stringWithFormat:@\"↑ %@\",uploadFlow]];\n        }\n        if (downFlow.length>0) {\n            [netflow appendString:[NSString stringWithFormat:@\"↓ %@\",downFlow]];\n        }\n        \n        self.flowLabel.text = netflow;\n        [self.flowLabel sizeToFit];\n        self.flowLabel.frame = CGRectMake(startX, startY, self.flowLabel.doraemon_width, self.flowLabel.doraemon_height);\n    }\n}\n\n+ (CGFloat)cellHeightWithModel:(DoraemonNetFlowHttpModel *)httpModel{\n    CGFloat height = 5;\n\n    UILabel *tempLabel = [[UILabel alloc] init];\n    tempLabel.font = [UIFont systemFontOfSize:10];\n    NSString *urlString = httpModel.url;\n    if (urlString.length>0) {\n        tempLabel.numberOfLines = 0;\n        tempLabel.text = urlString;\n        CGSize size = [tempLabel sizeThatFits:CGSizeMake(DoraemonScreenWidth-50, CGFLOAT_MAX)];\n        height += size.height;\n        height += 2.;\n    }\n    \n    NSString *method = httpModel.method;\n    NSString *status = httpModel.statusCode;\n    if (method.length>0 || status.length>0) {\n        tempLabel.numberOfLines = 1;\n        tempLabel.text = DoraemonLocalizedString(@\"你好\");\n        [tempLabel sizeToFit];\n        height += tempLabel.doraemon_height;\n        height += 2;\n    }\n    \n    NSString *startTime = [DoraemonUtil dateFormatTimeInterval:httpModel.startTime];\n    NSString *time = httpModel.totalDuration;\n    if (startTime.length>0 || time.length>0) {\n        tempLabel.numberOfLines = 1;\n        tempLabel.text = DoraemonLocalizedString(@\"你好\");\n        [tempLabel sizeToFit];\n        height += tempLabel.doraemon_height;\n        height += 2;\n    }\n    \n    NSString *uploadFlow = httpModel.uploadFlow;\n    NSString *downFlow = httpModel.downFlow;\n    if (uploadFlow.length>0 || downFlow.length>0) {\n        tempLabel.numberOfLines = 1;\n        tempLabel.text = DoraemonLocalizedString(@\"你好\");\n        [tempLabel sizeToFit];\n        height += tempLabel.doraemon_height;\n        height += 2;\n    }\n    \n    height += 3;\n\n    return height;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/List/DoraemonNetFlowListViewController.h",
    "content": "//\n//  DoraemonNetFlowListViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/11.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonNetFlowListViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/List/DoraemonNetFlowListViewController.m",
    "content": "//\n//  DoraemonNetFlowListViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/11.\n//\n\n#import \"DoraemonNetFlowListViewController.h\"\n#import \"DoraemonNetFlowDataSource.h\"\n#import \"DoraemonNetFlowListCell.h\"\n#import \"DoraemonNetFlowHttpModel.h\"\n#import \"DoraemonNetFlowDetailViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonNSLogSearchView.h\"\n\n@interface DoraemonNetFlowListViewController ()<UITableViewDelegate,UITableViewDataSource,DoraemonNSLogSearchViewDelegate>\n\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, copy) NSArray *dataArray;\n@property (nonatomic, copy) NSArray *allHttpModelArray;\n@property (nonatomic, strong) DoraemonNSLogSearchView *searchView;\n\n@end\n\n@implementation DoraemonNetFlowListViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    self.title = DoraemonLocalizedString(@\"网络监控列表\");\n    \n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        self.view.backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n            if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                return [UIColor systemBackgroundColor];\n            } else {\n                return [UIColor whiteColor];\n            }\n        }];\n    } else {\n#endif\n        self.view.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n    \n    NSArray *dataArray = [DoraemonNetFlowDataSource shareInstance].httpModelArray;\n    _dataArray = [NSArray arrayWithArray:dataArray];\n    _allHttpModelArray = [NSArray arrayWithArray:dataArray];\n\n    _searchView = [[DoraemonNSLogSearchView alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(32), IPHONE_NAVIGATIONBAR_HEIGHT+kDoraemonSizeFrom750_Landscape(32), self.view.doraemon_width-2*kDoraemonSizeFrom750_Landscape(32), kDoraemonSizeFrom750_Landscape(100))];\n    _searchView.delegate = self;\n    [self.view addSubview:_searchView];\n    \n\n    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, _searchView.doraemon_bottom+kDoraemonSizeFrom750_Landscape(30), self.view.doraemon_width, self.view.doraemon_height-_searchView.doraemon_bottom-kDoraemonSizeFrom750_Landscape(30)) style:UITableViewStylePlain];\n//    self.tableView.backgroundColor = [UIColor whiteColor];\n    self.tableView.delegate = self;\n    self.tableView.dataSource = self;\n    [self.view addSubview:self.tableView];\n}\n\n\n\n#pragma mark - UITableView Delegate\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {\n    return 1;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return self.dataArray.count;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonNetFlowHttpModel* model = [self.dataArray objectAtIndex:indexPath.row];\n    return [DoraemonNetFlowListCell cellHeightWithModel:model];\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {\n    return 0.1;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {\n    return 0.1;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"httpcell\";\n    DoraemonNetFlowListCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[DoraemonNetFlowListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];\n    }\n    DoraemonNetFlowHttpModel* model = [self.dataArray objectAtIndex:indexPath.row];\n    [cell renderCellWithModel:model];\n    return cell;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    [tableView deselectRowAtIndexPath:indexPath animated:YES];\n    DoraemonNetFlowHttpModel* model = [self.dataArray objectAtIndex:indexPath.row];\n    DoraemonNetFlowDetailViewController *detailVc = [[DoraemonNetFlowDetailViewController alloc] init];\n    detailVc.httpModel = model;\n    \n    [self.navigationController pushViewController:detailVc animated:YES];\n}\n\n- (void)leftNavBackClick:(id)clickView{\n    [self.tabBarController dismissViewControllerAnimated:YES completion:nil];\n}\n\n#pragma mark - DoraemonNSLogSearchViewDelegate\n- (void)searchViewInputChange:(NSString *)text{\n    NSArray *allHttpModelArray = _allHttpModelArray;\n    if (text.length == 0) {\n        _dataArray = allHttpModelArray;\n        [self.tableView reloadData];\n        return;\n    }\n    NSMutableArray *tempArray = [NSMutableArray array];\n    for (DoraemonNetFlowHttpModel *httpModel in allHttpModelArray) {\n        NSString *url = httpModel.url;\n        if ([url containsString:text]) {\n            [tempArray addObject:httpModel];\n        }\n    }\n    \n    _dataArray = [NSArray arrayWithArray:tempArray];\n    [self.tableView reloadData];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Summary/DoraemonNetFlowSummaryViewController.h",
    "content": "//\n//  DoraemonNetFlowSummaryViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/12.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonNetFlowSummaryViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Summary/DoraemonNetFlowSummaryViewController.m",
    "content": "//\n//  DoraemonNetFlowSummaryViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/12.\n//\n\n#import \"DoraemonNetFlowSummaryViewController.h\"\n#import \"DoraemonNetFlowSummaryTotalDataView.h\"\n#import \"DoraemonNetFlowSummaryMethodDataView.h\"\n#import \"DoraemonNetFlowSummaryTypeDataView.h\"\n#import \"UIView+Doraemon.h\"\n#import \"UIColor+Doraemon.h\"\n#import \"Doraemoni18NUtil.h\"\n\n@interface DoraemonNetFlowSummaryViewController ()\n\n@property (nonatomic, strong) DoraemonNetFlowSummaryTotalDataView *totalView;//数据概要\n@property (nonatomic, strong) DoraemonNetFlowSummaryMethodDataView *methodView;//Http 方法概要\n@property (nonatomic, strong) DoraemonNetFlowSummaryTypeDataView *typeView;//数据类型 概要\n\n@end\n\n@implementation DoraemonNetFlowSummaryViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"网络监控摘要\");\n    \n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        self.view.backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n            if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                return [UIColor groupTableViewBackgroundColor];\n            } else {\n                return [UIColor doraemon_colorWithHex:0xeff0f4];\n            }\n        }];\n    } else {\n#endif\n        self.view.backgroundColor = [UIColor doraemon_colorWithHex:0xeff0f4];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n    \n    CGFloat tabBarHeight = self.tabBarController.tabBar.doraemon_height;\n    UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.doraemon_width, self.view.doraemon_height-tabBarHeight)];\n    scrollView.contentSize = CGSizeMake(self.view.doraemon_width, 20+160+20+260+20+260);\n    [self.view addSubview:scrollView];\n    \n    _totalView = [[DoraemonNetFlowSummaryTotalDataView alloc] initWithFrame:CGRectMake(10, 20, self.view.doraemon_width-20, 160)];\n    [scrollView addSubview:_totalView];\n    \n    _methodView = [[DoraemonNetFlowSummaryMethodDataView alloc] initWithFrame:CGRectMake(10, _totalView.doraemon_bottom+20, self.view.doraemon_width-20, 260)];\n    [scrollView addSubview:_methodView];\n    \n    _typeView = [[DoraemonNetFlowSummaryTypeDataView alloc] initWithFrame:CGRectMake(10, _methodView.doraemon_bottom+20, self.view.doraemon_width-20, 260)];\n    [scrollView addSubview:_typeView];\n}\n\n- (void)leftNavBackClick:(id)clickView{\n    [self.tabBarController dismissViewControllerAnimated:YES completion:nil];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Summary/View/DoraemonNetFlowSummaryMethodDataView.h",
    "content": "//\n//  DoraemonNetFlowSummaryMethodDataView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/23.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface DoraemonNetFlowSummaryMethodDataView : UIView\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Summary/View/DoraemonNetFlowSummaryMethodDataView.m",
    "content": "//\n//  DoraemonNetFlowSummaryMethodDataView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/23.\n//\n\n#import \"DoraemonNetFlowSummaryMethodDataView.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonNetFlowDataSource.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonBarChart.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonNetFlowSummaryMethodDataView()\n@property (nonatomic, strong) NSArray<DoraemonChartDataItem *> *chartItems;\n\n@end\n\n@implementation DoraemonNetFlowSummaryMethodDataView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        self.layer.cornerRadius = 5.f;\n\n        UILabel *tipLabel = [[UILabel alloc] init];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            self.backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n                if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                    return [UIColor secondarySystemBackgroundColor];\n                } else {\n                    return [UIColor whiteColor];\n                }\n            }];\n            \n            tipLabel.textColor = [UIColor labelColor];\n        } else {\n#endif\n            self.backgroundColor = [UIColor whiteColor];\n            \n            tipLabel.textColor = [UIColor blackColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        tipLabel.text = DoraemonLocalizedString(@\"HTTP方法\");\n        tipLabel.font = [UIFont systemFontOfSize:14];\n        [tipLabel sizeToFit];\n        tipLabel.frame = CGRectMake(10, 10, tipLabel.doraemon_width, tipLabel.doraemon_height);\n        [self addSubview:tipLabel];\n        \n        [self getData];\n        \n        if (self.chartItems.count > 0) {\n            DoraemonBarChart *chart = [[DoraemonBarChart alloc] initWithFrame:CGRectMake(0, tipLabel.doraemon_bottom+10, self.doraemon_width, self.doraemon_height-tipLabel.doraemon_bottom-10)];\n            chart.items = _chartItems;\n            chart.yAxis.labelCount = 5;\n            chart.contentInset = UIEdgeInsetsMake(0, 50, 40, 20);\n            NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];\n            formatter.maximumFractionDigits = 2;\n            chart.vauleFormatter = formatter;\n            \n            [self addSubview:chart];\n            [chart display];\n        }\n    }\n    return self;\n}\n\n- (void)getData{\n    NSArray *dataArray = [DoraemonNetFlowDataSource shareInstance].httpModelArray;\n    NSMutableArray<NSString *> *methodArray = [NSMutableArray array];\n    for (DoraemonNetFlowHttpModel* httpModel in dataArray) {\n        NSString *method = httpModel.method;\n        if (!method || [methodArray containsObject:method]) {\n            continue;\n        }\n        [methodArray addObject:method];\n    }\n    \n    NSMutableArray *methodDataArray = [NSMutableArray array];\n    for (NSString *methodA in methodArray) {\n        NSMutableDictionary *dic = [NSMutableDictionary dictionary];\n        [dic setValue:methodA forKey:@\"method\"];\n        NSInteger num = 0;\n        for (DoraemonNetFlowHttpModel* httpModel in dataArray) {\n            NSString *methodB = httpModel.method;\n            if ([methodA isEqualToString:methodB]) {\n                num++;\n            }\n        }\n        [dic setValue:@(num) forKey:@\"num\"];\n        [methodDataArray addObject:dic];\n    }\n\n    NSMutableArray<DoraemonChartDataItem *> *items = [NSMutableArray array];\n    for (NSDictionary *methodData in methodDataArray) {\n        DoraemonChartDataItem *item = [[DoraemonChartDataItem alloc] initWithValue:[methodData[@\"num\"] doubleValue] name:methodData[@\"method\"] color: [UIColor doraemon_randomColor]];\n        [items addObject:item];\n    }\n    \n    self.chartItems = [NSArray arrayWithArray:items];;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Summary/View/DoraemonNetFlowSummaryTotalDataView.h",
    "content": "//\n//  DoraemonNetFlowSummaryTotalDataView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/23.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface DoraemonNetFlowSummaryTotalDataView : UIView\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Summary/View/DoraemonNetFlowSummaryTotalDataView.m",
    "content": "//\n//  DoraemonNetFlowSummaryTotalDataView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/23.\n//\n\n#import \"DoraemonNetFlowSummaryTotalDataView.h\"\n#import \"DoraemonNetFlowDataSource.h\"\n#import \"DoraemonNetFlowManager.h\"\n#import \"DoraemonUtil.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonNetFlowSummaryTotalDataItemView : UIView\n\n@property (nonatomic, strong) UILabel *titleLabel;\n@property (nonatomic, strong) UILabel *valueLabel;\n\n@end\n\n@implementation DoraemonNetFlowSummaryTotalDataItemView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        _valueLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.doraemon_width, kDoraemonSizeFrom750_Landscape(44))];\n        _valueLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(44)];\n        _valueLabel.textColor = [UIColor doraemon_black_1];\n        _valueLabel.textAlignment = NSTextAlignmentCenter;\n        [self addSubview:_valueLabel];\n        \n        _titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, _valueLabel.doraemon_bottom+kDoraemonSizeFrom750_Landscape(16), self.doraemon_width, kDoraemonSizeFrom750_Landscape(24))];\n        _titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(20)];\n        _titleLabel.textColor = [UIColor doraemon_black_2];\n        _titleLabel.textAlignment = NSTextAlignmentCenter;\n        [self addSubview:_titleLabel];\n        \n\n    }\n    return self;\n}\n\n- (void)renderUIWithTitle:(NSString *)title value:(NSString *)value{\n    _titleLabel.text = title;\n    _valueLabel.text = value;\n}\n\n@end\n\n@interface DoraemonNetFlowSummaryTotalDataView()\n\n@property (nonatomic, strong) DoraemonNetFlowSummaryTotalDataItemView *timeView;//抓包时间\n@property (nonatomic, strong) DoraemonNetFlowSummaryTotalDataItemView *numView;//抓包数量\n@property (nonatomic, strong) DoraemonNetFlowSummaryTotalDataItemView *upLoadView;//数据上传\n@property (nonatomic, strong) DoraemonNetFlowSummaryTotalDataItemView *downLoadView;//数据下载\n\n@end\n\n@implementation DoraemonNetFlowSummaryTotalDataView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self =[super initWithFrame:frame];\n    if (self) {\n        [self initUI];\n    }\n    return self;\n}\n\n- (void)initUI{\n    self.layer.cornerRadius = 5.f;\n\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        self.backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n            if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                return [UIColor secondarySystemBackgroundColor];\n            } else {\n                return [UIColor whiteColor];\n            }\n        }];\n    } else {\n#endif\n       self.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n     \n    //抓包时间\n    NSString *time;\n    NSDate *startInterceptDate = [DoraemonNetFlowManager shareInstance].startInterceptDate;\n    if (startInterceptDate) {\n        NSDate *nowDate = [NSDate date];\n        NSTimeInterval cha = [nowDate timeIntervalSinceDate:startInterceptDate];\n        time = [NSString stringWithFormat:@\"%.2f%@\",cha,DoraemonLocalizedString(@\"秒\")];\n    }else{\n        time = DoraemonLocalizedString(@\"暂未开启网络监控\");\n    }\n    \n    //抓包数量\n    NSArray *httpModelArray = [DoraemonNetFlowDataSource shareInstance].httpModelArray;\n    NSString *num = [NSString stringWithFormat:@\"%zi\",httpModelArray.count];\n    \n    CGFloat totalUploadFlow = 0.;\n    CGFloat totalDownFlow = 0.;\n    for (int i=0; i<httpModelArray.count; i++) {\n        DoraemonNetFlowHttpModel *httpModel = httpModelArray[i];\n        CGFloat uploadFlow =  [httpModel.uploadFlow floatValue];\n        CGFloat downFlow = [httpModel.downFlow floatValue];\n        totalUploadFlow += uploadFlow;\n        totalDownFlow += downFlow;\n    }\n    //数据上传\n    NSString *upLoad = [DoraemonUtil formatByte:totalUploadFlow];\n    \n    //数据下载\n    NSString *downLoad = [DoraemonUtil formatByte:totalDownFlow];\n    \n    _timeView = [[DoraemonNetFlowSummaryTotalDataItemView alloc] initWithFrame:CGRectMake(0, 20, self.doraemon_width, 40)];\n    [_timeView renderUIWithTitle:DoraemonLocalizedString(@\"总计已为您抓包\") value:time];\n    [self addSubview:_timeView];\n    \n    CGFloat offsetY = 20+40+40;\n    CGFloat itemWidth = self.doraemon_width/3;\n    CGFloat offsetX = 0;\n    \n    _numView = [[DoraemonNetFlowSummaryTotalDataItemView alloc] initWithFrame:CGRectMake(0, offsetY, itemWidth, 40)];\n    [_numView renderUIWithTitle:DoraemonLocalizedString(@\"抓包数量\") value:num];\n    [self addSubview:_numView];\n    \n    offsetX += _numView.doraemon_width;\n    \n    _upLoadView = [[DoraemonNetFlowSummaryTotalDataItemView alloc] initWithFrame:CGRectMake(offsetX, offsetY, itemWidth, 40)];\n    [_upLoadView renderUIWithTitle:DoraemonLocalizedString(@\"数据上传\") value:upLoad];\n    [self addSubview:_upLoadView];\n    \n    offsetX += _upLoadView.doraemon_width;\n    \n    _downLoadView = [[DoraemonNetFlowSummaryTotalDataItemView alloc] initWithFrame:CGRectMake(offsetX, offsetY, itemWidth, 40)];\n    [_downLoadView renderUIWithTitle:DoraemonLocalizedString(@\"数据下载\") value:downLoad];\n    [self addSubview:_downLoadView];\n    \n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Summary/View/DoraemonNetFlowSummaryTypeDataView.h",
    "content": "//\n//  DoraemonNetFlowSummaryTypeDataView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/23.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface DoraemonNetFlowSummaryTypeDataView : UIView\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/NetFlow/Summary/View/DoraemonNetFlowSummaryTypeDataView.m",
    "content": "//\n//  DoraemonNetFlowSummaryTypeDataView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/4/23.\n//\n\n#import \"DoraemonNetFlowSummaryTypeDataView.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonPieChart.h\"\n#import \"DoraemonNetFlowDataSource.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonNetFlowSummaryTypeDataView()\n@property (nonatomic, strong) NSArray<DoraemonChartDataItem *> *chartItems;\n\n@end\n\n@implementation DoraemonNetFlowSummaryTypeDataView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        self.layer.cornerRadius = 5.f;\n        \n        UILabel *tipLabel = [[UILabel alloc] init];\n        \n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            self.backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n                if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                    return [UIColor secondarySystemBackgroundColor];\n                } else {\n                    return [UIColor whiteColor];\n                }\n            }];\n            tipLabel.textColor = [UIColor labelColor];\n        } else {\n#endif\n            self.backgroundColor = [UIColor whiteColor];\n            tipLabel.textColor = [UIColor blackColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        \n        tipLabel.text = DoraemonLocalizedString(@\"数据类型\");\n        tipLabel.font = [UIFont systemFontOfSize:14];\n        [tipLabel sizeToFit];\n        tipLabel.frame = CGRectMake(10, 10, tipLabel.doraemon_width, tipLabel.doraemon_height);\n        [self addSubview:tipLabel];\n        \n        [self getData];\n        \n        if (self.chartItems.count > 0) {\n            DoraemonPieChart *chart = [[DoraemonPieChart alloc] initWithFrame:CGRectMake(0, tipLabel.doraemon_bottom+10, self.doraemon_width, self.doraemon_height-tipLabel.doraemon_bottom-10)];\n            chart.items = self.chartItems;\n            [self addSubview:chart];\n            [chart display];\n        }\n    }\n    return self;\n}\n\n- (void)getData{\n    NSArray *dataArray = [DoraemonNetFlowDataSource shareInstance].httpModelArray;\n    NSMutableArray *mineTypeArray = [NSMutableArray array];\n    for (DoraemonNetFlowHttpModel* httpModel in dataArray) {\n        NSString *mineType = httpModel.mineType;\n        if (!mineType || [mineTypeArray containsObject:mineType]) {\n            continue;\n        }\n        [mineTypeArray addObject:mineType];\n    }\n    \n    NSMutableArray *mineTypeDataArray = [NSMutableArray array];\n    for (NSString *mineTypeA in mineTypeArray) {\n        NSMutableDictionary *dic = [NSMutableDictionary dictionary];\n        [dic setValue:mineTypeA forKey:@\"mineType\"];\n        NSInteger num = 0;\n        for (DoraemonNetFlowHttpModel* httpModel in dataArray) {\n            NSString *mineTypeB = httpModel.mineType;\n            if ([mineTypeA isEqualToString:mineTypeB]) {\n                num++;\n            }\n        }\n        [dic setValue:@(num) forKey:@\"num\"];\n        [mineTypeDataArray addObject:dic];\n    }\n\n    NSMutableArray<DoraemonChartDataItem *> *items = [NSMutableArray array];\n    for (NSDictionary *mineTypeData in mineTypeDataArray) {\n        DoraemonChartDataItem *item = [[DoraemonChartDataItem alloc] initWithValue:[mineTypeData[@\"num\"] doubleValue] name:mineTypeData[@\"mineType\"] color:[UIColor doraemon_randomColor]];\n        [items addObject:item];\n    }\n    self.chartItems = [NSArray arrayWithArray:items];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/StartTime/DoraemonStartTimePlugin.h",
    "content": "//\n//  DoraemonStartTimePlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/7/17.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonStartTimePlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/StartTime/DoraemonStartTimePlugin.m",
    "content": "//\n//  DoraemonStartTimePlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/7/17.\n//\n\n#import \"DoraemonStartTimePlugin.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonStartTimeViewController.h\"\n\n@implementation DoraemonStartTimePlugin\n\n- (void)pluginDidLoad{\n    DoraemonStartTimeViewController *vc = [[DoraemonStartTimeViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/StartTime/DoraemonStartTimeViewController.h",
    "content": "//\n//  DoraemonStartTimeViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/7/17.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonStartTimeViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/StartTime/DoraemonStartTimeViewController.m",
    "content": "//\n//  DoraemonStartTimeViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/7/17.\n//\n\n#import \"DoraemonStartTimeViewController.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonCellButton.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"NSObject+Doraemon.h\"\n#import \"DoraemonManager.h\"\n#import <objc/runtime.h>\n#import \"DoraemonHealthManager.h\"\n#import \"DoraemonTimeProfiler.h\"\n#import \"DoraemonStartTimeProfilerViewController.h\"\n\nstatic NSTimeInterval startTime;\nstatic NSTimeInterval endTime;\n\n@interface DoraemonStartTimeViewController ()<DoraemonSwitchViewDelegate,DoraemonCellButtonDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n@property (nonatomic, strong) DoraemonCellButton *cellBtn;\n\n@end\n\n@implementation DoraemonStartTimeViewController\n\n+ (void)load{\n    startTime = [[NSDate date] timeIntervalSince1970];\n    if ([[DoraemonCacheManager sharedInstance] startTimeSwitch]) {\n        NSString *startClass = [DoraemonManager shareInstance].startClass;\n        if (!startClass) {\n            startClass = @\"AppDelegate\";\n        }\n        Class class = NSClassFromString(startClass);\n        Method originMethod = class_getInstanceMethod(class, @selector(application:didFinishLaunchingWithOptions:));\n        Method swizzledMethod = class_getInstanceMethod([self class], @selector(doraemon_application:didFinishLaunchingWithOptions:));\n        class_addMethod(class, method_getName(swizzledMethod), method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));\n        Method swizzledMethod2 = class_getInstanceMethod(class, @selector(doraemon_application:didFinishLaunchingWithOptions:));\n        method_exchangeImplementations(originMethod, swizzledMethod2);\n    }\n}\n\n- (BOOL)doraemon_application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{\n    [DoraemonTimeProfiler startRecord];\n    BOOL ret = [self doraemon_application:application didFinishLaunchingWithOptions:launchOptions];\n    [DoraemonTimeProfiler stopRecord];\n    endTime = [[NSDate date] timeIntervalSince1970];\n    \n    [DoraemonHealthManager sharedInstance].startTime = (endTime-startTime)*1000;\n    return ret;\n}\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"启动耗时\");\n    \n    _switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750(104))];\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"开关\") switchOn:[[DoraemonCacheManager sharedInstance] startTimeSwitch]];\n    [_switchView needTopLine];\n    [_switchView needDownLine];\n    _switchView.delegate = self;\n    [self.view addSubview:_switchView];\n    \n    _cellBtn = [[DoraemonCellButton alloc] initWithFrame:CGRectMake(0, _switchView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750(104))];\n    if ([[DoraemonCacheManager sharedInstance] startTimeSwitch]){\n        [_cellBtn renderUIWithTitle:[NSString stringWithFormat:@\"%@%fs\",DoraemonLocalizedString(@\"本次启动时间为\"),endTime-startTime]];\n    }\n    _cellBtn.delegate = self;\n    [_cellBtn needDownLine];\n    [self.view addSubview:_cellBtn];\n    \n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    __weak typeof(self) weakSelf = self;\n    [DoraemonAlertUtil handleAlertActionWithVC:self okBlock:^{\n        [[DoraemonCacheManager sharedInstance] saveStartTimeSwitch:on];\n        exit(0);\n    } cancleBlock:^{\n        weakSelf.switchView.switchView.on = !on;\n    }];\n}\n\n#pragma mark -- DoraemonCellButtonDelegate\n- (void)cellBtnClick:(id)sender{\n    DoraemonStartTimeProfilerViewController *vc = [[DoraemonStartTimeProfilerViewController alloc] init];\n    [self.navigationController pushViewController:vc animated:YES];\n}\n\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/StartTime/TimeProfiler/DoraemonStartTimeProfilerViewController.h",
    "content": "//\n//  DoraemonStartTimeProfilerViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/4/13.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonStartTimeProfilerViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/StartTime/TimeProfiler/DoraemonStartTimeProfilerViewController.m",
    "content": "//\n//  DoraemonStartTimeProfilerViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/4/13.\n//\n\n#import \"DoraemonStartTimeProfilerViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonHealthManager.h\"\n\n@interface DoraemonStartTimeProfilerViewController ()\n\n@property (nonatomic, strong) UITextView *contentLabel;\n\n@end\n\n@implementation DoraemonStartTimeProfilerViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"启动耗时\");\n    [self setRightNavTitle:DoraemonLocalizedString(@\"导出\")];\n    \n    NSString *costDetail = [DoraemonHealthManager sharedInstance].costDetail;\n    \n    _contentLabel = [[UITextView alloc] initWithFrame:self.view.bounds];\n    _contentLabel.textColor = [UIColor doraemon_black_2];\n    _contentLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(16)];\n    _contentLabel.text = costDetail;\n    \n    [self.view addSubview:_contentLabel];\n}\n\n- (void)rightNavTitleClick:(id)clickView{\n    [self export:_contentLabel.text];\n}\n\n- (void)export:(NSString *)text {\n    [DoraemonUtil shareText:text formVC:self];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/SubThreadUICheck/Detail/DoraemonSubThreadUICheckDetailViewController.h",
    "content": "//\n//  DoraemonSubThreadUICheckDetailViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/9/13.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonSubThreadUICheckDetailViewController : DoraemonBaseViewController\n\n@property (nonatomic, strong) NSDictionary *checkInfo;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/SubThreadUICheck/Detail/DoraemonSubThreadUICheckDetailViewController.m",
    "content": "//\n//  DoraemonSubThreadUICheckDetailViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/9/13.\n//\n\n#import \"DoraemonSubThreadUICheckDetailViewController.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonSubThreadUICheckDetailViewController ()\n\n@property (nonatomic, strong) UILabel *contentLabel;\n\n@end\n\n@implementation DoraemonSubThreadUICheckDetailViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"检测详情\");\n    \n    _contentLabel = [[UILabel alloc] init];\n    _contentLabel.textColor = [UIColor doraemon_black_2];\n    _contentLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(16)];\n    _contentLabel.numberOfLines = 0;\n    _contentLabel.text = _checkInfo[@\"content\"];\n    \n    CGSize fontSize = [_contentLabel sizeThatFits:CGSizeMake(self.view.frame.size.width-40, MAXFLOAT)];\n    _contentLabel.frame = CGRectMake(20, IPHONE_NAVIGATIONBAR_HEIGHT, fontSize.width, fontSize.height);\n    [self.view addSubview:_contentLabel];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/SubThreadUICheck/DoraemonSubThreadUICheckPlugin.h",
    "content": "//\n//  DoraemonSubThreadUICheckPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/9/12.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonSubThreadUICheckPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/SubThreadUICheck/DoraemonSubThreadUICheckPlugin.m",
    "content": "//\n//  DoraemonSubThreadUICheckPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/9/12.\n//\n\n#import \"DoraemonSubThreadUICheckPlugin.h\"\n#import \"DoraemonSubThreadUICheckViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonSubThreadUICheckPlugin\n\n- (void)pluginDidLoad{\n    DoraemonSubThreadUICheckViewController *vc = [[DoraemonSubThreadUICheckViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/SubThreadUICheck/DoraemonSubThreadUICheckViewController.h",
    "content": "//\n//  DoraemonSubThreadUICheckViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/9/12.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonSubThreadUICheckViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/SubThreadUICheck/DoraemonSubThreadUICheckViewController.m",
    "content": "//\n//  DoraemonSubThreadUICheckViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/9/12.\n//\n\n#import \"DoraemonSubThreadUICheckViewController.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonCellButton.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonSubThreadUICheckListViewController.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonSubThreadUICheckViewController ()<DoraemonSwitchViewDelegate,DoraemonCellButtonDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n@property (nonatomic, strong) DoraemonCellButton *cellBtn;\n\n@end\n\n@implementation DoraemonSubThreadUICheckViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"子线程UI\");\n    \n    _switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"子线程UI渲染检测开关\") switchOn:[[DoraemonCacheManager sharedInstance] subThreadUICheckSwitch]];\n    [_switchView needTopLine];\n    [_switchView needDownLine];\n    _switchView.delegate = self;\n    [self.view addSubview:_switchView];\n    \n    _cellBtn = [[DoraemonCellButton alloc] initWithFrame:CGRectMake(0, _switchView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [_cellBtn renderUIWithTitle:DoraemonLocalizedString(@\"查看检测记录\")];\n    _cellBtn.delegate = self;\n    [_cellBtn needDownLine];\n    [self.view addSubview:_cellBtn];\n\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    [[DoraemonCacheManager sharedInstance] saveSubThreadUICheckSwitch:on];\n}\n\n#pragma mark -- DoraemonCellButtonDelegate\n- (void)cellBtnClick:(id)sender{\n    if (sender == _cellBtn) {\n        DoraemonSubThreadUICheckListViewController *vc = [[DoraemonSubThreadUICheckListViewController alloc] init];\n        [self.navigationController pushViewController:vc animated:YES];\n    }\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/SubThreadUICheck/Function/DoraemonSubThreadUICheckManager.h",
    "content": "//\n//  DoraemonSubThreadUICheckManager.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/9/13.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface DoraemonSubThreadUICheckManager : NSObject\n\n+ (instancetype)sharedInstance;\n\n@property (nonatomic, strong) NSMutableArray *checkArray;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/SubThreadUICheck/Function/DoraemonSubThreadUICheckManager.m",
    "content": "//\n//  DoraemonSubThreadUICheckManager.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/9/13.\n//\n\n#import \"DoraemonSubThreadUICheckManager.h\"\n\n@implementation DoraemonSubThreadUICheckManager\n\n+ (instancetype)sharedInstance {\n    static id instance = nil;\n    static dispatch_once_t onceToken;\n    \n    dispatch_once(&onceToken, ^{\n        instance = [[self alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    self = [super init];\n    if(self){\n        _checkArray = [[NSMutableArray alloc] init];\n    }\n    return self;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/SubThreadUICheck/Function/UIView+DoraemonSubThreadUICheck.h",
    "content": "//\n//  UIView+DoraemonSubThreadUICheck.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/9/13.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface UIView (DoraemonSubThreadUICheck)\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/SubThreadUICheck/Function/UIView+DoraemonSubThreadUICheck.m",
    "content": "//\n//  UIView+Doraemon.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/9/13.\n//\n\n#import \"UIView+Doraemon.h\"\n#import \"NSObject+Doraemon.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonSubThreadUICheckManager.h\"\n#import \"DoraemonUtil.h\"\n#import \"DoraemonHealthManager.h\"\n#import \"DoraemonBacktraceLogger.h\"\n\n@implementation UIView (DoraemonSubThreadUICheck)\n\n+ (void)load{\n    if ([NSStringFromClass([self class]) isEqualToString:@\"UIView\"]) {\n        [[self  class] doraemon_swizzleInstanceMethodWithOriginSel:@selector(setNeedsLayout) swizzledSel:@selector(doraemon_setNeedsLayout)];\n        [[self class] doraemon_swizzleInstanceMethodWithOriginSel:@selector(setNeedsDisplay) swizzledSel:@selector(doraemon_setNeedsDisplay)];\n        [[self class] doraemon_swizzleInstanceMethodWithOriginSel:@selector(setNeedsDisplayInRect:) swizzledSel:@selector(doraemon_setNeedsDisplayInRect:)];\n    }\n}\n\n- (void)doraemon_setNeedsLayout{\n    [self doraemon_setNeedsLayout];\n    [self uiCheck];\n}\n\n- (void)doraemon_setNeedsDisplay{\n    [self doraemon_setNeedsDisplay];\n    [self uiCheck];\n}\n\n- (void)doraemon_setNeedsDisplayInRect:(CGRect)rect{\n    [self doraemon_setNeedsDisplayInRect:rect];\n    [self uiCheck];\n}\n\n- (void)uiCheck{\n    if(![NSThread isMainThread]){\n        if ([[DoraemonCacheManager sharedInstance] subThreadUICheckSwitch]) {\n            NSString *report = [DoraemonBacktraceLogger doraemon_backtraceOfCurrentThread];\n            NSDictionary *dic = @{\n                                  @\"title\":[DoraemonUtil dateFormatNow],\n                                  @\"content\":report\n                                  };\n            [[DoraemonSubThreadUICheckManager sharedInstance].checkArray addObject:dic];\n            [[DoraemonHealthManager sharedInstance] addSubThreadUI:dic];\n        }\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/SubThreadUICheck/List/DoraemonSubThreadUICheckListCell.h",
    "content": "//\n//  DoraemonSubThreadUICheckListCell.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/9/13.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface DoraemonSubThreadUICheckListCell : UITableViewCell\n\n- (void)renderCellWithData:(NSDictionary *)dic;\n\n+ (CGFloat)cellHeight;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/SubThreadUICheck/List/DoraemonSubThreadUICheckListCell.m",
    "content": "//\n//  DoraemonSubThreadUICheckListCell.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/9/13.\n//\n\n#import \"DoraemonSubThreadUICheckListCell.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonSubThreadUICheckListCell()\n\n@property (nonatomic, strong) UILabel *titleLabel;\n@property (nonatomic, strong) UIImageView *arrowImageView;\n\n@end\n\n@implementation DoraemonSubThreadUICheckListCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        self.selectionStyle = UITableViewCellSelectionStyleNone;\n        \n        _titleLabel = [[UILabel alloc] init];\n        _titleLabel.textColor = [UIColor doraemon_black_1];\n        _titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        [self.contentView addSubview:_titleLabel];\n        \n        _arrowImageView = [[UIImageView alloc] initWithImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_more\"]];\n        _arrowImageView.frame = CGRectMake(DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)-_arrowImageView.doraemon_width, [[self class] cellHeight]/2-_arrowImageView.doraemon_height/2, _arrowImageView.doraemon_width, _arrowImageView.doraemon_height);\n        [self.contentView addSubview:_arrowImageView];\n    }\n    return self;\n}\n\n- (void)renderCellWithData:(NSDictionary *)dic{\n    self.titleLabel.text = dic[@\"title\"];\n    [self.titleLabel sizeToFit];\n    CGFloat w = self.titleLabel.doraemon_width;\n    if (w > DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(120)) {\n        w = DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(120);\n    }\n    self.titleLabel.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), [[self class] cellHeight]/2-self.titleLabel.doraemon_height/2, w, self.titleLabel.doraemon_height);\n}\n\n+ (CGFloat)cellHeight{\n    return kDoraemonSizeFrom750_Landscape(104);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/SubThreadUICheck/List/DoraemonSubThreadUICheckListViewController.h",
    "content": "//\n//  DoraemonSubThreadUICheckListViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/9/13.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonSubThreadUICheckListViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/SubThreadUICheck/List/DoraemonSubThreadUICheckListViewController.m",
    "content": "//\n//  DoraemonSubThreadUICheckListViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/9/13.\n//\n\n#import \"DoraemonSubThreadUICheckListViewController.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonSubThreadUICheckListCell.h\"\n#import \"DoraemonSubThreadUICheckManager.h\"\n#import \"DoraemonSubThreadUICheckDetailViewController.h\"\n#import \"Doraemoni18NUtil.h\"\n\n@interface DoraemonSubThreadUICheckListViewController ()<UITableViewDelegate,UITableViewDataSource>\n\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, copy) NSArray *checkArray;\n\n@end\n\n@implementation DoraemonSubThreadUICheckListViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"检测列表\");\n    \n    self.checkArray = [DoraemonSubThreadUICheckManager sharedInstance].checkArray;\n    \n    CGFloat navBarHeight = self.navigationController.navigationBar.doraemon_height+[[UIApplication sharedApplication] statusBarFrame].size.height;\n    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.doraemon_width, self.view.doraemon_height-navBarHeight) style:UITableViewStylePlain];\n//    self.tableView.backgroundColor = [UIColor whiteColor];\n    self.tableView.delegate = self;\n    self.tableView.dataSource = self;\n    [self.view addSubview:self.tableView];\n}\n\n#pragma mark - UITableView Delegate\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {\n    return 1;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return self.checkArray.count;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return [DoraemonSubThreadUICheckListCell cellHeight];\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {\n    return 0.1;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {\n    return 0.1;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"httpcell\";\n    DoraemonSubThreadUICheckListCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[DoraemonSubThreadUICheckListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];\n    }\n    NSDictionary* dic = [self.checkArray objectAtIndex:indexPath.row];\n    [cell renderCellWithData:dic];\n    return cell;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    [tableView deselectRowAtIndexPath:indexPath animated:YES];\n    NSDictionary* dic = [self.checkArray objectAtIndex:indexPath.row];\n    \n    DoraemonSubThreadUICheckDetailViewController *vc = [[DoraemonSubThreadUICheckDetailViewController alloc] init];\n    vc.checkInfo = dic;\n    [self.navigationController pushViewController:vc animated:YES];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/TimeProfiler/DoraemonTimeProfilerPlugin.h",
    "content": "//\n//  DoraemonTimeProfilerPlugin.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/15.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonTimeProfilerPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/TimeProfiler/DoraemonTimeProfilerPlugin.m",
    "content": "//\n//  DoraemonTimeProfilerPlugin.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/15.\n//\n\n#import \"DoraemonTimeProfilerPlugin.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonTimeProfilerViewController.h\"\n\n@implementation DoraemonTimeProfilerPlugin\n\n- (void)pluginDidLoad{\n    DoraemonTimeProfilerViewController *vc = [[DoraemonTimeProfilerViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/TimeProfiler/DoraemonTimeProfilerViewController.h",
    "content": "//\n//  DoraemonTimeProfilerViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/15.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonTimeProfilerViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/TimeProfiler/DoraemonTimeProfilerViewController.m",
    "content": "//\n//  DoraemonTimeProfilerViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/15.\n//\n\n#import \"DoraemonTimeProfilerViewController.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonTimeProfilerViewController()\n\n@property (nonatomic, strong) UILabel *contentLabel;\n\n@end\n\n@implementation DoraemonTimeProfilerViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    self.title = DoraemonLocalizedString(@\"函数耗时\");\n    \n    NSString *contet = DoraemonLocalizedString(@\"函数耗时描述\");\n    _contentLabel = [[UILabel alloc] init];\n    _contentLabel.textColor = [UIColor doraemon_black_2];\n    _contentLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n    _contentLabel.numberOfLines = 0;\n    [self.view addSubview:_contentLabel];\n    NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:contet];\n    NSRange range = [contet rangeOfString:@\"[DoraemonTimeProfiler startRecord];\"];\n    [attrStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:range];\n    range = [contet rangeOfString:@\"[DoraemonTimeProfiler stopRecord];\"];\n    [attrStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:range];\n    range = [contet rangeOfString:@\"分析完毕之后，记得删掉startRecord和stopRecord的函数调用。\"];\n    [attrStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:range];\n    _contentLabel.attributedText = attrStr;\n    \n    CGSize fontSize = [_contentLabel sizeThatFits:CGSizeMake(self.view.doraemon_width-40, MAXFLOAT)];\n    _contentLabel.frame = CGRectMake(20, self.bigTitleView.doraemon_bottom, fontSize.width, fontSize.height);\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/TimeProfiler/Function/DoraemonTimeProfiler.h",
    "content": "//\n//  DoraemonTimeProfiler.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/7/10.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonTimeProfilerRecord.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonTimeProfiler : NSObject\n\n+ (instancetype)sharedInstance;\n@property (nonatomic, assign) BOOL on;\n\n/// 开始记录\n+ (void)startRecord;\n\n/// 停止记录\n+ (void)stopRecord;\n\n/// 清空已有记录\n+ (void)clearRecords;\n\n/// 分享调用记录\n+ (void)shareRecords;\n\n/// 打印调用记录\n+ (void)printRecords;\n\n/// 获取调用记录\n+ (NSArray<DoraemonTimeProfilerRecord *> *)getRecords;\n\n#pragma mark - 配置项\n/// 设置过滤的最小函数调用时间（毫秒），小于该时间的函数调用将被忽略。 默认值:1.0\n+ (void)setMinCallCost:(double)ms;\n\n/// 设置过滤的最深函数调用，调用深度超过该值的函数将被忽略。 默认值:10\n+ (void)setMaxCallDepth:(int)depth;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/TimeProfiler/Function/DoraemonTimeProfiler.m",
    "content": "//\n//  DoraemonTimeProfiler.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/7/10.\n//\n\n#import \"DoraemonTimeProfiler.h\"\n#import \"DoraemonTimeProfilerCore.h\"\n#include <objc/message.h>\n#include <sys/sysctl.h>\n#import \"DoraemonHealthManager.h\"\n#import \"DoraemonDefine.h\"\n\nstatic NSTimeInterval startTime;\nstatic NSTimeInterval stopTime;\n\n@implementation DoraemonTimeProfiler\n\n+ (instancetype)sharedInstance {\n    static id instance = nil;\n    static dispatch_once_t onceToken;\n    \n    dispatch_once(&onceToken, ^{\n        instance = [[self alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    if (self = [super init]) {\n        _on = NO;\n    }\n    return self;\n}\n\n- (void)setOn:(BOOL)on{\n    if (on) {\n        [[self class] startRecord];\n    }else{\n        [[self class] stopRecord];\n    }\n}\n\n\n+ (void)startRecord {\n    startTime = [NSDate new].timeIntervalSince1970;\n    dtp_hook_begin();\n}\n\n+ (void)stopRecord {\n    dtp_hook_end();\n    stopTime = [NSDate new].timeIntervalSince1970;\n    [self printRecords];\n    [self clearRecords];\n}\n\n+ (void)clearRecords {\n    dtp_clear_call_records();\n}\n\n+ (void)shareRecords {\n    NSString *result = [self getRecordsResult];\n    [self share:result];\n}\n\n/// 打印调用记录\n+ (void)printRecords {\n    NSString *result = [self getRecordsResult];\n    [DoraemonHealthManager sharedInstance].costDetail = result;\n    NSLog(@\"%@\",result);\n}\n\n+ (NSString *)getRecordsResult {\n    NSMutableString *str = [NSMutableString new];\n    [str appendFormat:@\"\\n\\ntime profile result : \\n\"];\n    NSTimeInterval totalRecord = 0;\n    NSArray<DoraemonTimeProfilerRecord *>*arr = [self getRecords];\n    for (DoraemonTimeProfilerRecord *r in arr) {\n        [self appendRecord:r to:str];\n        totalRecord += r.timeCost;\n    }\n    \n    [str appendFormat:@\"\\n\"];\n    [str appendFormat:@\"totalTimeInRecord:%.2f\\n\",totalRecord * 1000];\n    \n    [str appendFormat:@\"\\ntime profile end\\n\"];\n    \n    return str;\n}\n\n\n+ (void)appendRecord:(DoraemonTimeProfilerRecord *)record to:(NSMutableString *)str {\n    [str appendFormat:@\"%@\\n\", [record descriptionWithDepth]];\n    for (DoraemonTimeProfilerRecord *r in record.subRecords) {\n        [self appendRecord:r to:str];\n    }\n}\n\n+ (NSArray<DoraemonTimeProfilerRecord *>*)getRecords {\n    NSMutableArray<DoraemonTimeProfilerRecord *> *arr = [NSMutableArray new];\n    int record_num = 0;\n    dtp_call_record *records = dtp_get_call_records(&record_num);\n    for (int i = 0; i < record_num; i++) {\n        dtp_call_record *rec = &records[i];\n        \n        DoraemonTimeProfilerRecord *r = [DoraemonTimeProfilerRecord new];\n        r.className = NSStringFromClass(rec->cls);\n        r.methodName = NSStringFromSelector(rec->sel);\n        r.isClassMethod = class_isMetaClass(rec->cls);\n        r.timeCost = (double)rec->time / 1000000.0;\n        r.callDepth = rec->depth;\n        [arr addObject:r];\n    }\n    \n    for (int i = 0, max = (int)arr.count; i < max; i++) {\n        DoraemonTimeProfilerRecord *r = arr[i];\n        if (r.callDepth > 0) {\n            [arr removeObjectAtIndex:i];\n            for (int j = i; j < max - 1; j++) {\n                if (arr[j].callDepth + 1 == r.callDepth) {\n                    NSMutableArray *sub = (NSMutableArray *)arr[j].subRecords;\n                    if (!sub) {\n                        sub = [NSMutableArray new];\n                        arr[j].subRecords = sub;\n                    }\n                    [sub insertObject:r atIndex:0];\n                    break;\n                }\n            }\n            i--; max--;\n        }\n    }\n    return arr;\n}\n\n+ (void)setMinCallCost:(double)ms {\n    dtp_set_min_time(ms * 1000);\n}\n\n+ (void)setMaxCallDepth:(int)depth {\n    if (depth < 0) depth = 0;\n    dtp_set_max_depth(depth);\n}\n\n+ (void)share:(NSString *)str {\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/TimeProfiler/Function/DoraemonTimeProfilerCore.c",
    "content": "//\n//  DoraemonTimeProfilerCore.c\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/7/9.\n//\n\n#include \"DoraemonTimeProfilerCore.h\"\n\n#ifdef __aarch64__\n#include <pthread.h>\n#include <stdlib.h>\n#include <sys/time.h>\n#include <objc/runtime.h>\n#include <dispatch/dispatch.h>\n#include \"doraemon_fishhook.h\"\n\nstatic bool _call_record_enabled = true;\nstatic uint64_t _min_time_cost = 1000; //us\nstatic int _max_call_depth = 10;\nstatic pthread_key_t _thread_key;\n__unused static id (*orig_objc_msgSend)(id, SEL, ...);\n\nstatic dtp_call_record *dtp_records;\nstatic int dtp_record_num;\nstatic int dtp_record_alloc;\n\ntypedef struct {\n    id self; //通过 object_getClass 能够得到 Class 再通过 NSStringFromClass 能够得到类名\n    Class cls;\n    SEL cmd; //通过 NSStringFromSelector 方法能够得到方法名\n    uint64_t time; //us\n    uintptr_t lr; //link register\n} thread_call_record;\n\ntypedef struct {\n    thread_call_record *stack;\n    int allocated_length;\n    int index;\n    bool is_main_thread;\n} thread_call_stack;\n\nstatic inline thread_call_stack *get_thread_call_stack() {\n    thread_call_stack *cs = (thread_call_stack *)pthread_getspecific(_thread_key);//pthread_getpecific和pthread_setspecific实现同一个线程中不同函数间共享数据的一种很好的方式\n    if (cs == NULL) {\n        cs = (thread_call_stack *)malloc(sizeof(thread_call_stack));\n        cs->stack = (thread_call_record *)calloc(128, sizeof(thread_call_record));\n        cs->allocated_length = 64;\n        cs->index = -1;\n        cs->is_main_thread = pthread_main_np();\n        pthread_setspecific(_thread_key, cs);\n    }\n    return cs;\n}\n\nstatic void release_thread_call_stack(void *ptr){\n    thread_call_stack *cs = (thread_call_stack *)ptr;\n    if (!cs) return;\n    if (cs->stack) free(cs->stack);\n    free(cs);\n}\n\nstatic inline void push_call_record(id _self, Class _cls, SEL _cmd, uintptr_t lr) {\n    thread_call_stack *cs = get_thread_call_stack();\n    if (cs) {\n        int nextIndex = (++cs->index);\n        if (nextIndex >= cs->allocated_length) {\n            cs->allocated_length += 64;\n            cs->stack = (thread_call_record *)realloc(cs->stack, cs->allocated_length * sizeof(thread_call_record));\n        }\n        thread_call_record *newRecord = &cs->stack[nextIndex];\n        newRecord->self = _self;\n        newRecord->cls = _cls;\n        newRecord->cmd = _cmd;\n        newRecord->lr = lr;\n        if (cs->is_main_thread && _call_record_enabled) {\n            struct timeval now;\n            gettimeofday(&now, NULL);\n            newRecord->time = (now.tv_sec % 100) * 1000000 + now.tv_usec;\n        }\n    }\n}\n\nstatic inline uintptr_t pop_call_record() {\n    thread_call_stack *cs = get_thread_call_stack();\n    int curIndex = cs->index;\n    int nextIndex = cs->index--;\n    thread_call_record *pRecord = &cs->stack[nextIndex];\n    \n    if (cs->is_main_thread && _call_record_enabled) {\n        struct timeval now;\n        gettimeofday(&now, NULL);\n        uint64_t time = (now.tv_sec % 100) * 1000000 + now.tv_usec;\n        if (time < pRecord->time) {\n            time += 100 * 1000000;\n        }\n        uint64_t cost = time - pRecord->time;\n        if (cost > _min_time_cost && cs->index < _max_call_depth) {\n            if (!dtp_records) {\n                dtp_record_alloc = 1024;\n                dtp_records = malloc(sizeof(dtp_call_record) * dtp_record_alloc);\n            }\n            dtp_record_num++;\n            if (dtp_record_num >= dtp_record_alloc) {\n                dtp_record_alloc += 1024;\n                dtp_records = realloc(dtp_records, sizeof(dtp_call_record) * dtp_record_alloc);\n            }\n            dtp_call_record *log = &dtp_records[dtp_record_num - 1];\n            log->cls = pRecord->cls;\n            log->depth = curIndex;\n            log->sel = pRecord->cmd;\n            log->time = cost;\n        }\n    }\n    return pRecord->lr;\n}\n\nvoid before_objc_msgSend(id self, SEL _cmd, uintptr_t lr) {\n    push_call_record(self, object_getClass(self), _cmd, lr);\n}\n\nuintptr_t after_objc_msgSend() {\n    return pop_call_record();\n}\n\n// arm64 hook objc_msgSend\n// volatile为可选关键字，表示不需要gcc对下面的汇编代码做任何优化\n\n// 函数调用，value传入函数地址\n// 将参数value(地址)传递给x12寄存器\n// blr开始执行\n#define call(b, value) \\\n    __asm volatile (\"stp x8, x9, [sp, #-16]!\\n\"); \\\n    __asm volatile (\"mov x12, %0\\n\" :: \"r\"(value)); \\\n    __asm volatile (\"ldp x8, x9, [sp], #16\\n\"); \\\n    __asm volatile (#b \" x12\\n\");\n\n//保存寄存器参数信息\n//依次将寄存器数据入栈\n#define save() \\\n    __asm volatile ( \\\n        \"stp x8, x9, [sp, #-16]!\\n\" \\\n        \"stp x6, x7, [sp, #-16]!\\n\" \\\n        \"stp x4, x5, [sp, #-16]!\\n\" \\\n        \"stp x2, x3, [sp, #-16]!\\n\" \\\n        \"stp x0, x1, [sp, #-16]!\\n\");\n\n//还原寄存器参数信息\n//依次将寄存器数据出栈\n#define load() \\\n    __asm volatile ( \\\n        \"ldp x0, x1, [sp], #16\\n\" \\\n        \"ldp x2, x3, [sp], #16\\n\" \\\n        \"ldp x4, x5, [sp], #16\\n\" \\\n        \"ldp x6, x7, [sp], #16\\n\" \\\n        \"ldp x8, x9, [sp], #16\\n\");\n\n#define link(b, value) \\\n    __asm volatile (\"stp x8, lr, [sp, #-16]!\\n\"); \\\n    __asm volatile (\"sub sp, sp, #16\\n\"); \\\n    call(b, value); \\\n    __asm volatile (\"add sp, sp, #16\\n\"); \\\n    __asm volatile (\"ldp x8, lr, [sp], #16\\n\");\n\n//程序执行完成,返回将继续执行lr中的函数\n#define ret() __asm volatile (\"ret\\n\");\n\n/**\n *__naked__修饰的函数告诉编译器在函数调用的时候不使用栈保存参数信息，\n *同时函数返回地址会被保存到LR寄存器上。\n *由于msgSend本身就是用这个修饰符的，\n *因此在记录函数调用的出入栈操作中，\n *必须保证能够保存以及还原寄存器数据。\n *msgSend利用x0 - x9的寄存器存储参数信息，\n *可以手动使用sp寄存器来存储和还原这些参数信息\n */\n\n//msgSend必须使用汇编实现\n__attribute__((__naked__))\nstatic void hook_objc_msgSend() {\n    //before之前保存objc_msgSend的参数\n    save()\n    \n    //将objc_msgSend执行的下一个函数地址传递给before_objc_msgSend的第二个参数x0 self, x1 _cmd, x2: lr address\n    __asm volatile (\"mov x2, lr\\n\");\n    __asm volatile (\"mov x3, x4\\n\");\n    \n    // 执行before_objc_msgSend   blr 除了从指定寄存器读取新的 PC 值外效果和 bl 一...\n    call(blr, &before_objc_msgSend)\n    \n    // 恢复objc_msgSend参数，并执行\n    load()\n    call(blr, orig_objc_msgSend)\n    \n    //after之前保存objc_msgSend执行完成的参数\n    save()\n    \n    //调用 after_objc_msgSend\n    call(blr, &after_objc_msgSend)\n    \n    //将after_objc_msgSend返回的参数放入lr,恢复调用before_objc_msgSend前的lr地址\n    // x0 是整数/指针args的第一个arg传递寄存器 x0 是整数/指针值的（第一个）返回值寄存器\n    __asm volatile (\"mov lr, x0\\n\");\n    \n    //恢复objc_msgSend执行完成的参数\n    load()\n    \n    //方法结束,继续执行lr\n    ret()\n}\n\n\nvoid dtp_hook_begin(void) {\n    _call_record_enabled = true;\n//    static dispatch_once_t onceToken;\n//    dispatch_once(&onceToken, ^{\n//        pthread_key_create(&_thread_key, &release_thread_call_stack);\n//        rebind_symbols((struct rebinding[1]){\"objc_msgSend\", (void *)hook_objc_msgSend, (void **)&orig_objc_msgSend},1);\n//    });\n    pthread_key_create(&_thread_key, &release_thread_call_stack);\n    doraemon_rebind_symbols((struct doraemon_rebinding[1]){\"objc_msgSend\", (void *)hook_objc_msgSend, (void **)&orig_objc_msgSend},1);\n}\n\nvoid dtp_hook_end(void) {\n    _call_record_enabled = false;\n    doraemon_rebind_symbols((struct doraemon_rebinding[1]){\"objc_msgSend\", (void *)orig_objc_msgSend, NULL},1);\n}\n\nvoid dtp_set_min_time(uint64_t us) {\n    _min_time_cost = us;\n}\n\nvoid dtp_set_max_depth(int depth) {\n    _max_call_depth = depth;\n}\n\ndtp_call_record *dtp_get_call_records(int *num) {\n    if (num) {\n        *num = dtp_record_num;\n    }\n    return dtp_records;\n}\n\nvoid dtp_clear_call_records(void) {\n    if (dtp_records) {\n        free(dtp_records);\n        dtp_records = NULL;\n    }\n    dtp_record_num = 0;\n}\n\n#else\n\nvoid dtp_hook_begin(void) {}\n\nvoid dtp_hook_end(void) {}\n\nvoid dtp_set_min_time(uint64_t us) {}\n\nvoid dtp_set_max_depth(int depth) {}\n\ndtp_call_record *dtp_get_call_records(int *num) {return NULL;}\n\nvoid dtp_clear_call_records(void) {}\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/TimeProfiler/Function/DoraemonTimeProfilerCore.h",
    "content": "//\n//  DoraemonTimeProfilerCore.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/7/9.\n//\n\n#ifndef DoraemonTimeProfilerCore_h\n#define DoraemonTimeProfilerCore_h\n\n#include <stdio.h>\n#include <objc/objc.h>\n\ntypedef struct {\n    __unsafe_unretained Class cls;\n    SEL sel;\n    uint64_t time; // us （1/1000 ms）\n    int depth;\n} dtp_call_record;\n\nextern void dtp_hook_begin(void);\nextern void dtp_hook_end(void);\n\nextern void dtp_set_min_time(uint64_t us); //default 1000\nextern void dtp_set_max_depth(int depth); //deafult 10\n\nextern dtp_call_record *dtp_get_call_records(int *num);\nextern void dtp_clear_call_records(void);\n\n#endif /* DoraemonTimeProfilerCore_h */\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/TimeProfiler/Function/DoraemonTimeProfilerRecord.h",
    "content": "//\n//  DoraemonTimeProfilerRecord.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/7/10.\n//\n\n#import <Foundation/Foundation.h>\n\n\nNS_ASSUME_NONNULL_BEGIN\n\n// 一条 OC 函数 调用记录\n@interface DoraemonTimeProfilerRecord : NSObject\n\n@property (nonatomic, strong) NSString *className;\n@property (nonatomic, strong) NSString *methodName;\n@property (nonatomic, assign) BOOL isClassMethod;\n@property (nonatomic, assign) NSTimeInterval timeCost;\n@property (nonatomic, assign) NSUInteger callDepth;\n@property (nonatomic, strong) NSArray <DoraemonTimeProfilerRecord *>*subRecords;\n\n- (NSString *)descriptionWithDepth ;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/TimeProfiler/Function/DoraemonTimeProfilerRecord.m",
    "content": "//\n//  DoraemonTimeProfilerRecord.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/7/10.\n//\n\n#import \"DoraemonTimeProfilerRecord.h\"\n\n@implementation DoraemonTimeProfilerRecord\n\n- (NSString *)descriptionWithDepth {\n    NSMutableString *str = [NSMutableString new];\n    [str appendFormat:@\"%2d| \",(int)_callDepth];\n    [str appendFormat:@\"%6.2f| \",_timeCost * 1000.0];\n    for (NSUInteger i = 0; i < _callDepth; i++) {\n        [str appendString:@\"  \"];\n    }\n    [str appendFormat:@\"%s[%@ %@]\",(_isClassMethod ? \"+\" : \"-\"),_className,_methodName];\n    return str;\n}\n\n- (NSString *)description {\n    NSMutableString *str = [NSMutableString new];\n    [str appendFormat:@\"%2d| \",(int)_callDepth];\n    [str appendFormat:@\"%6.2f|\",_timeCost * 1000.0];\n    [str appendFormat:@\"%s[%@ %@]\", (_isClassMethod ? \"+\" : \"-\"), _className, _methodName];\n    return str;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/UIProfile/DoraemonUIProfilePlugin.h",
    "content": "//\n//  DoraemonUIProfilePlugin.h\n//  DoraemonKit\n//\n//  Created by xgb on 2019/8/1.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonUIProfilePlugin : NSObject <DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/UIProfile/DoraemonUIProfilePlugin.m",
    "content": "//\n//  DoraemonUIProfilePlugin.m\n//  DoraemonKit\n//\n//  Created by xgb on 2019/8/1.\n//\n\n#import \"DoraemonUIProfilePlugin.h\"\n#import \"DoraemonUIProfileViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonUIProfilePlugin\n\n- (void)pluginDidLoad{\n    DoraemonUIProfileViewController *vc = [[DoraemonUIProfileViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/UIProfile/DoraemonUIProfileViewController.h",
    "content": "//\n//  DoraemonUIProfileViewController.h\n//  DoraemonKit\n//\n//  Created by xgb on 2019/8/1.\n//\n\n#import <DoraemonKit/DoraemonKit.h>\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonUIProfileViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/UIProfile/DoraemonUIProfileViewController.m",
    "content": "//\n//  DoraemonUIProfileViewController.m\n//  DoraemonKit\n//\n//  Created by xgb on 2019/8/1.\n//\n\n#import \"DoraemonUIProfileViewController.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonUIProfileManager.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@interface DoraemonUIProfileViewController () <DoraemonSwitchViewDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n\n@end\n\n@implementation DoraemonUIProfileViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    self.title = DoraemonLocalizedString(@\"UI层级\");\n    \n    _switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750(104))];\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"UI层级检查开关\") switchOn:[DoraemonUIProfileManager sharedInstance].enable];\n    [_switchView needTopLine];\n    [_switchView needDownLine];\n    _switchView.delegate = self;\n    [self.view addSubview:_switchView];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    [DoraemonUIProfileManager sharedInstance].enable = on;\n    [[DoraemonHomeWindow shareInstance] hide];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/UIProfile/Function/DoraemonUIProfileManager.h",
    "content": "//\n//  DoraemonUIProfileManager.h\n//  DoraemonKit\n//\n//  Created by xgb on 2019/8/1.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonUIProfileManager : NSObject\n\n@property (nonatomic, assign) BOOL enable;              //default NO\n\n+ (instancetype)sharedInstance;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/UIProfile/Function/DoraemonUIProfileManager.m",
    "content": "//\n//  DoraemonUIProfileManager.m\n//  DoraemonKit\n//\n//  Created by xgb on 2019/8/1.\n//\n\n#import \"DoraemonUIProfileManager.h\"\n#import \"UIViewController+DoraemonUIProfile.h\"\n#import \"DoraemonUIProfileWindow.h\"\n#import \"UIViewController+Doraemon.h\"\n\n@interface DoraemonUIProfileManager ()\n\n@end\n\n@implementation DoraemonUIProfileManager\n\n+ (instancetype)sharedInstance\n{\n    static DoraemonUIProfileManager *sharedInstance = nil;\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        sharedInstance = [DoraemonUIProfileManager new];\n    });\n    \n    return sharedInstance;\n}\n\n- (void)setEnable:(BOOL)enable\n{\n    _enable = enable;\n    if (enable) {\n        [[UIViewController topViewControllerForKeyWindow] profileViewDepth];\n    } else {\n        [[UIViewController topViewControllerForKeyWindow] resetProfileData];\n        [[DoraemonUIProfileWindow sharedInstance] hide];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/UIProfile/Function/DoraemonUIProfileWindow.h",
    "content": "//\n//  DoraemonUIProfileWindow.h\n//  DoraemonKit\n//\n//  Created by xgb on 2019/8/1.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonUIProfileWindow : UIWindow\n\n+ (instancetype)sharedInstance;\n- (void)showWithDepthText:(NSString *)text\n               detailInfo:(NSString *)detail;\n- (void)hide;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/UIProfile/Function/DoraemonUIProfileWindow.m",
    "content": "//\n//  DoraemonUIProfileWindow.m\n//  DoraemonKit\n//\n//  Created by xgb on 2019/8/1.\n//\n\n#import \"DoraemonUIProfileWindow.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n\n#define kWindowWidth        220\n#define kExpandHeight       250\n#define kTextHeight         30\n\n@interface DoraemonUIProfileWindow ()\n\n@property (nonatomic, strong) UILabel *lbText;\n@property (nonatomic, strong) UITextView *textView;\n@property (nonatomic, assign) CGRect storedFrame;\n\n@end\n\n@implementation DoraemonUIProfileWindow\n\n+ (instancetype)sharedInstance\n{\n    static DoraemonUIProfileWindow *sharedInstance = nil;\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        sharedInstance = [DoraemonUIProfileWindow new];\n    });\n    \n    return sharedInstance;\n}\n\n- (instancetype)init\n{\n    self = [super init];\n    if (self) {\n        #if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n            if (@available(iOS 13.0, *)) {\n                for (UIWindowScene* windowScene in [UIApplication sharedApplication].connectedScenes){\n                    if (windowScene.activationState == UISceneActivationStateForegroundActive){\n                        self.windowScene = windowScene;\n                        break;\n                    }\n                }\n            }\n        #endif\n        self.backgroundColor = [UIColor whiteColor];\n        self.layer.borderWidth = 2;\n        self.layer.borderColor = [UIColor lightGrayColor].CGColor;\n        self.windowLevel = UIWindowLevelStatusBar + 50.f;\n        self.frame = CGRectMake(10, 65, kWindowWidth, kTextHeight);\n        self.clipsToBounds = YES;\n        \n        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];\n        [self addGestureRecognizer:pan];\n        [self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self\n                                                                                 action:@selector(tap)]];\n    }\n    return self;\n}\n\n- (void)showWithDepthText:(NSString *)text\n               detailInfo:(NSString *)detail\n{\n    self.lbText.text = text;\n    self.textView.text = detail;\n    [self addSubview:self.lbText];\n    [self addSubview:self.textView];\n    self.hidden = NO;\n}\n\n- (void)hide\n{\n    [self.lbText removeFromSuperview];\n    [self.textView removeFromSuperview];\n    self.hidden = YES;\n}\n\n- (void)pan:(UIPanGestureRecognizer *)sender{\n    //1、获得拖动位移\n    CGPoint offsetPoint = [sender translationInView:sender.view];\n    //2、清空拖动位移\n    [sender setTranslation:CGPointZero inView:sender.view];\n    //3、重新设置控件位置\n    UIView *panView = sender.view;\n    CGFloat newX = panView.doraemon_centerX+offsetPoint.x;\n    CGFloat newY = panView.doraemon_centerY+offsetPoint.y;\n    if (newX < kWindowWidth/2) {\n        newX = kWindowWidth/2;\n    }\n    if (newX > DoraemonScreenWidth - kWindowWidth/2) {\n        newX = DoraemonScreenWidth - kWindowWidth/2;\n    }\n    if (newY < self.doraemon_height/2) {\n        newY = self.doraemon_height/2;\n    }\n    if (newY > DoraemonScreenHeight - self.doraemon_height/2) {\n        newY = DoraemonScreenHeight - self.doraemon_height/2;\n    }\n    panView.center = CGPointMake(newX, newY);\n}\n\n- (void)tap\n{\n    if (CGRectIsEmpty(self.storedFrame)) {\n        self.storedFrame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, 180);\n    }\n    \n    [UIView animateWithDuration:.25 animations:^{\n        CGRect tmp = self.frame;\n        self.frame = self.storedFrame;\n        self.storedFrame = tmp;\n    }];\n}\n\n#pragma mark - getters\n- (UILabel *)lbText\n{\n    if (!_lbText) {\n        _lbText = [UILabel new];\n        _lbText.frame = CGRectMake(0, 0, kWindowWidth, kTextHeight);\n        _lbText.backgroundColor = [UIColor lightGrayColor];\n        _lbText.textAlignment = NSTextAlignmentCenter;\n    }\n    return _lbText;\n}\n\n- (UITextView *)textView\n{\n    if (!_textView) {\n        _textView = [UITextView new];\n        _textView.frame = CGRectMake(0, kTextHeight, kWindowWidth, kExpandHeight-kTextHeight);\n        _textView.textAlignment = NSTextAlignmentCenter;\n        _textView.editable = NO;\n        _textView.userInteractionEnabled = NO;\n        _textView.backgroundColor = [UIColor clearColor];\n    }\n    return _textView;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/UIProfile/Function/UIViewController+DoraemonUIProfile.h",
    "content": "//\n//  UIViewController+Doraemon.h\n//  DoraemonKit\n//\n//  Created by xgb on 2019/8/1.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UIViewController (DoraemonUIProfile)\n\n- (void)profileViewDepth;\n- (void)resetProfileData;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/UIProfile/Function/UIViewController+DoraemonUIProfile.m",
    "content": "//\n//  UIViewController+Doraemon.m\n//  DoraemonKit\n//\n//  Created by xgb on 2019/8/1.\n//\n\n#import \"UIViewController+Doraemon.h\"\n#import \"DoraemonUIProfileManager.h\"\n#import \"NSObject+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n#import <objc/runtime.h>\n#import \"DoraemonUIProfileWindow.h\"\n#import \"DoraemonHealthManager.h\"\n\n@interface UIViewController ()\n\n@property (nonatomic, strong) NSNumber *doraemon_depth;\n@property (nonatomic, strong) UIView *doraemon_depthView;\n\n@end\n\n@implementation UIViewController (DoraemonUIProfile)\n\n+ (void)load {\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [[self class] doraemon_swizzleInstanceMethodWithOriginSel:@selector(viewDidAppear:) swizzledSel:@selector(doraemon_viewDidAppear:)];\n        [[self class] doraemon_swizzleInstanceMethodWithOriginSel:@selector(viewWillDisappear:) swizzledSel:@selector(doraemon_viewWillDisappear:)];\n    });\n}\n\n- (void)doraemon_viewDidAppear:(BOOL)animated{\n    [self doraemon_viewDidAppear:animated];\n    if (![DoraemonHealthManager sharedInstance].start) {\n        [self profileViewDepth];\n    }\n}\n\n- (void)doraemon_viewWillDisappear:(BOOL)animated\n{\n    if ([DoraemonHealthManager sharedInstance].start) {\n        [self profileViewDepth];\n    }\n    [self doraemon_viewWillDisappear:animated];\n    [self resetProfileData];\n}\n\n- (void)profileViewDepth\n{\n    if (![DoraemonUIProfileManager sharedInstance].enable) {\n        return;\n    }\n    if ([[DoraemonHealthManager sharedInstance] blackList:[self class]]){\n        return ;\n    }\n    [self travelView:self.view depth:0];\n    [self showUIProfile];\n}\n\n- (void)showUIProfile\n{\n    NSString *text = [NSString stringWithFormat:@\"[%d][%@]\",self.doraemon_depth.intValue,NSStringFromClass([self.doraemon_depthView class])];\n    \n    NSMutableArray *tmp = [NSMutableArray new];\n    if (self.doraemon_depthView) {\n        [tmp addObject:NSStringFromClass([self.doraemon_depthView class])];\n    }\n\n    UIView *tmpSuperView = self.doraemon_depthView.superview;\n    \n    while (tmpSuperView && tmpSuperView != self.view) {\n        [tmp addObject:NSStringFromClass([tmpSuperView class])];\n        tmpSuperView = tmpSuperView.superview;\n    }\n    \n    [tmp addObject:NSStringFromClass([self.view class])];\n\n\n    NSArray *result = [[tmp reverseObjectEnumerator] allObjects];\n    NSString *detail = [result componentsJoinedByString:@\"\\r\\n\"];\n    \n    if ([DoraemonHealthManager sharedInstance].start) {\n        [[DoraemonHealthManager sharedInstance] addUILevel:@{\n            @\"level\":self.doraemon_depth,\n            @\"detail\":detail\n        }];\n    }else{\n        [[DoraemonUIProfileWindow sharedInstance] showWithDepthText:text detailInfo:detail];\n        \n        self.doraemon_depthView.layer.borderWidth = 1;\n        self.doraemon_depthView.layer.borderColor = [UIColor redColor].CGColor;\n    }\n\n}\n\n- (void)travelView:(UIView *)view depth:(int)depth\n{\n    depth++;\n    if (depth > self.doraemon_depth.intValue) {\n        self.doraemon_depth = @(depth);\n        self.doraemon_depthView = view;\n    }\n    \n    if (view.subviews.count == 0) {\n        return;\n    }\n    \n    for (int i = 0; i < view.subviews.count; i++) {\n        UIView *subView = view.subviews[i];\n        [self travelView:subView depth:depth];\n    }\n}\n\n- (void)resetProfileData\n{\n    self.doraemon_depth = @(0);\n    self.doraemon_depthView.layer.borderWidth = 0;\n    self.doraemon_depthView.layer.borderColor = nil;\n}\n\n- (void)setDoraemon_depth:(NSNumber *)doraemon_depth\n{\n    objc_setAssociatedObject(self, @selector(doraemon_depth), doraemon_depth, OBJC_ASSOCIATION_RETAIN_NONATOMIC);\n}\n\n- (NSNumber *)doraemon_depth\n{\n    return objc_getAssociatedObject(self, @selector(doraemon_depth));\n}\n\n- (void)setDoraemon_depthView:(UIView *)doraemon_depthView\n{\n    objc_setAssociatedObject(self, @selector(doraemon_depthView), doraemon_depthView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);\n}\n\n- (UIView *)doraemon_depthView\n{\n    return objc_getAssociatedObject(self, @selector(doraemon_depthView));\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/VCProfiler/UIViewController+DoraemonVCProfiler.h",
    "content": "//\n//  UIViewController+DoraemonVCProfiler.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/5.\n//\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UIViewController (DoraemonVCProfiler)\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/VCProfiler/UIViewController+DoraemonVCProfiler.m",
    "content": "//\n//  UIViewController+DoraemonVCProfiler.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/5.\n//\n\n#import \"UIViewController+DoraemonVCProfiler.h\"\n#import \"NSObject+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n#import <objc/runtime.h>\n#import \"DoraemonHealthManager.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonManager.h\"\n\n//#define Doraemon_VC_Profiler_LOG_ENABLE \n\n#ifdef Doraemon_VC_Profiler_LOG_ENABLE\n#define VCLog(...) NSLog(__VA_ARGS__)\n#else\n#define VCLog(...)\n#endif\n\nstatic char const kAssociatedRemoverKey;\nstatic NSString *const kUniqueFakeKeyPath = @\"doraemon_vc_profiler_key_path\";\n\n#pragma mark - IMP of Key Method\n\nstatic void doraemon_vc_profiler_viewDidLoad(UIViewController *kvo_self, SEL _sel) {\n    Class kvo_cls = object_getClass(kvo_self);\n    Class origin_cls = class_getSuperclass(kvo_cls);\n    IMP origin_imp = method_getImplementation(class_getInstanceMethod(origin_cls, _sel));\n    assert(origin_imp != NULL);\n\n    [[DoraemonHealthManager sharedInstance] startEnterPage:origin_cls];\n    \n    void (*func)(UIViewController *, SEL) = (void (*)(UIViewController *, SEL))origin_imp;\n    func(kvo_self, _sel);\n}\n\nstatic void doraemon_vc_profiler_viewWillAppear(UIViewController *kvo_self, SEL _sel, BOOL animated) {\n    Class kvo_cls = object_getClass(kvo_self);\n    Class origin_cls = class_getSuperclass(kvo_cls);\n\n    IMP origin_imp = method_getImplementation(class_getInstanceMethod(origin_cls, _sel));\n    assert(origin_imp != NULL);\n\n    void (*func)(UIViewController *, SEL, BOOL) = (void (*)(UIViewController *, SEL, BOOL))origin_imp;\n    func(kvo_self, _sel, animated);\n}\n\nstatic void doraemon_vc_profiler_viewDidAppear(UIViewController *kvo_self, SEL _sel, BOOL animated) {\n    Class kvo_cls = object_getClass(kvo_self);\n    Class origin_cls = class_getSuperclass(kvo_cls);\n    IMP origin_imp = method_getImplementation(class_getInstanceMethod(origin_cls, _sel));\n    assert(origin_imp != NULL);\n\n    void (*func)(UIViewController *, SEL, BOOL) = (void (*)(UIViewController *, SEL, BOOL))origin_imp;\n    \n    func(kvo_self, _sel, animated);\n    [[DoraemonHealthManager sharedInstance] enterPage:origin_cls];\n}\n\nstatic void doraemon_vc_profiler_viewWillDisAppear(UIViewController *kvo_self, SEL _sel, BOOL animated) {\n    Class kvo_cls = object_getClass(kvo_self);\n    Class origin_cls = class_getSuperclass(kvo_cls);\n    IMP origin_imp = method_getImplementation(class_getInstanceMethod(origin_cls, _sel));\n    assert(origin_imp != NULL);\n\n    void (*func)(UIViewController *, SEL, BOOL) = (void (*)(UIViewController *, SEL, BOOL))origin_imp;\n\n    func(kvo_self, _sel, animated);\n}\n\nstatic void doraemon_vc_profiler_viewDidDisappear(UIViewController *kvo_self, SEL _sel, BOOL animated) {\n    Class kvo_cls = object_getClass(kvo_self);\n    Class origin_cls = class_getSuperclass(kvo_cls);\n    IMP origin_imp = method_getImplementation(class_getInstanceMethod(origin_cls, _sel));\n    assert(origin_imp != NULL);\n\n    void (*func)(UIViewController *, SEL, BOOL) = (void (*)(UIViewController *, SEL, BOOL))origin_imp;\n\n    func(kvo_self, _sel, animated);\n    [[DoraemonHealthManager sharedInstance] leavePage:origin_cls];\n}\n\n\n@interface DoraemonFakeKVOObserver : NSObject\n\n@end\n\n@implementation DoraemonFakeKVOObserver\n\n+ (instancetype)shared{\n    static id sharedInstance;\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        sharedInstance = [[self alloc] init];\n    });\n    return sharedInstance;\n}\n\n@end\n\n@interface DoraemonFakeKVORemover : NSObject\n\n@property (nonatomic, unsafe_unretained) id target;\n@property (nonatomic, copy) NSString *keyPath;\n\n@end\n\n@implementation DoraemonFakeKVORemover\n\n- (void)dealloc{\n    //DoKitLog(@\"target == %@ , dealloc\",_target);\n    [_target removeObserver:[DoraemonFakeKVOObserver shared] forKeyPath:_keyPath];\n    _target = nil;\n}\n\n@end\n\n@implementation UIViewController (DoraemonVCProfiler)\n\n+ (void)load {\n    if ([[DoraemonCacheManager sharedInstance] healthStart]){\n        [self doraemon_swizzleInstanceMethodWithOriginSel:@selector(initWithNibName:bundle:) swizzledSel:@selector(doraemon_initWithNibName:bundle:)];\n        [self doraemon_swizzleInstanceMethodWithOriginSel:@selector(initWithCoder:) swizzledSel:@selector(doraemon_initWithCoder:)];\n    }\n}\n\n- (instancetype)doraemon_initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{\n    [self createAndHookKVOClass];\n    //DoKitLog(@\"vc(initWithNibName) ==  %@\",[self class]);\n    [self doraemon_initWithNibName:nibNameOrNil bundle:nibBundleOrNil];\n    return self;\n}\n\n- (instancetype)doraemon_initWithCoder:(NSCoder *)coder{\n    [self createAndHookKVOClass]; \n    //DoKitLog(@\"vc(initWithCoder) == %@\",[self class]);\n    [self doraemon_initWithCoder:coder];\n    return self;\n}\n\n// 黑名单用户不会触发KVO监控\n- (BOOL)blackList:(NSString *)className{\n    NSArray *blackList=[DoraemonManager shareInstance].vcProfilerBlackList;\n    if (blackList && blackList.count>0 && [blackList containsObject:className]) {\n       return YES;\n    }\n    return NO;\n}\n\n- (void)createAndHookKVOClass {\n    if ([self blackList:NSStringFromClass(self.class)]) {\n        return;\n    }\n    [self addObserver:[DoraemonFakeKVOObserver shared] forKeyPath:kUniqueFakeKeyPath options:NSKeyValueObservingOptionNew context:nil];\n    \n    DoraemonFakeKVORemover *remover = [[DoraemonFakeKVORemover alloc] init];\n    remover.target = self;\n    remover.keyPath = kUniqueFakeKeyPath;\n    objc_setAssociatedObject(self, &kAssociatedRemoverKey, remover, OBJC_ASSOCIATION_RETAIN_NONATOMIC);\n    \n    //NSKVONotifying_ViewController\n    Class kvoCls = object_getClass(self);\n    \n    IMP currentViewDidLoadImp = class_getMethodImplementation(kvoCls, @selector(viewDidLoad));\n    if (currentViewDidLoadImp == (IMP)doraemon_vc_profiler_viewDidLoad) {\n        return;\n    }\n    \n    Class originCls = class_getSuperclass(kvoCls);\n    \n    //DoKitLog(@\"Hook %@\", kvoCls);\n    \n    // 获取原来实现的encoding\n    const char *originViewDidLoadEncoding = method_getTypeEncoding(class_getInstanceMethod(originCls, @selector(viewDidLoad)));\n    const char *originViewWillAppearEncoding = method_getTypeEncoding(class_getInstanceMethod(originCls, @selector(viewWillAppear:)));\n    const char *originViewDidAppearEncoding = method_getTypeEncoding(class_getInstanceMethod(originCls, @selector(viewDidAppear:)));\n    const char *originViewWillDisappearEncoding = method_getTypeEncoding(class_getInstanceMethod(originCls, @selector(viewWillDisappear:)));\n    const char *originViewDidDisappearEncoding = method_getTypeEncoding(class_getInstanceMethod(originCls, @selector(viewDidDisappear:)));\n    \n    // 重点，添加方法。\n    class_addMethod(kvoCls, @selector(viewDidLoad), (IMP)doraemon_vc_profiler_viewDidLoad, originViewDidLoadEncoding);\n    class_addMethod(kvoCls, @selector(viewDidAppear:), (IMP)doraemon_vc_profiler_viewDidAppear, originViewDidAppearEncoding);\n    class_addMethod(kvoCls, @selector(viewWillAppear:), (IMP)doraemon_vc_profiler_viewWillAppear, originViewWillAppearEncoding);\n    class_addMethod(kvoCls, @selector(viewWillDisappear:), (IMP)doraemon_vc_profiler_viewWillDisAppear, originViewWillDisappearEncoding);\n    class_addMethod(kvoCls, @selector(viewDidDisappear:),  (IMP)doraemon_vc_profiler_viewDidDisappear, originViewDidDisappearEncoding);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/VCProfiler/WKWebView+Doraemon.h",
    "content": "//\n//  WKWebView+Doraemon.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/2/7.\n//\n\n#import <WebKit/WebKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface WKWebView (Doraemon)\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/VCProfiler/WKWebView+Doraemon.m",
    "content": "//\n//  WKWebView+Doraemon.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/2/7.\n//\n\n#import \"WKWebView+Doraemon.h\"\n#import <objc/runtime.h>\n#import \"NSObject+Doraemon.h\"\n#import \"DoraemonHealthManager.h\"\n\n@implementation WKWebView (Doraemon)\n\n+ (void)load{\n    [self doraemon_swizzleInstanceMethodWithOriginSel:@selector(loadRequest:) swizzledSel:@selector(doraemon_loadRequest:)];\n}\n\n- (WKNavigation *)doraemon_loadRequest:(NSURLRequest *)request{\n    WKNavigation *navigation = [self doraemon_loadRequest:request];\n    NSString *urlString = request.URL.absoluteString;\n    [[DoraemonHealthManager sharedInstance] openH5Page:urlString];\n    return navigation;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/DoraemonWeakNetworkPlugin.h",
    "content": "//\n//  DoraemonWeakNetworkPlugin.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/21.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonWeakNetworkPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/DoraemonWeakNetworkPlugin.m",
    "content": "//\n//  DoraemonWeakNetworkPlugin.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/21.\n//\n\n#import \"DoraemonWeakNetworkPlugin.h\"\n#import \"DoraemonWeakNetworkViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonWeakNetworkPlugin\n\n- (void)pluginDidLoad{\n    DoraemonWeakNetworkViewController *vc = [[DoraemonWeakNetworkViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/DoraemonWeakNetworkViewController.h",
    "content": "//\n//  DoraemonWeakNetworkViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/21.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonWeakNetworkViewController : DoraemonBaseViewController\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/DoraemonWeakNetworkViewController.m",
    "content": "//\n//  DoraemonWeakNetworkViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/21.\n//\n\n#import \"DoraemonWeakNetworkViewController.h\"\n#import \"DoraemonWeakNetworkManager.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonWeakNetworkDetailView.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonToastUtil.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonWeakNetworkWindow.h\"\n\n@interface DoraemonWeakNetworkViewController()<DoraemonWeakNetworkWindowDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *weakSwitchView;\n@property (nonatomic, strong) DoraemonWeakNetworkDetailView *detail;\n\n@end\n\n@implementation DoraemonWeakNetworkViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"模拟弱网测试\");\n    \n    _weakSwitchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [_weakSwitchView renderUIWithTitle:DoraemonLocalizedString(@\"弱网模式\") switchOn:[DoraemonWeakNetworkManager shareInstance].shouldWeak];\n    [_weakSwitchView.switchView addTarget:self action:@selector(switchAction:) forControlEvents:UIControlEventValueChanged];\n    [_weakSwitchView needDownLine];\n    [self.view addSubview:_weakSwitchView];\n    [DoraemonWeakNetworkWindow shareInstance].delegate = self;\n    _detail = [[DoraemonWeakNetworkDetailView alloc] initWithFrame:CGRectMake(0, _weakSwitchView.doraemon_bottom , self.view.doraemon_width, self.view.doraemon_height - _weakSwitchView.doraemon_bottom)];\n    _detail.hidden = ![DoraemonWeakNetworkManager shareInstance].shouldWeak;\n    [self.view addSubview:_detail];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n- (void)switchAction:(id)sender{\n    UISwitch *switchButton = (UISwitch*)sender;\n    if([[DoraemonCacheManager sharedInstance] healthStart]){\n        switchButton.on = NO;\n        [DoraemonToastUtil showToastBlack:DoraemonLocalizedString(@\"App当前处于健康体检状态，无法进行此操作\") inView:self.view];\n        return ;\n    }\n    [DoraemonWeakNetworkManager shareInstance].shouldWeak = [switchButton isOn];\n    \n    [[DoraemonWeakNetworkManager shareInstance] canInterceptNetFlow:[switchButton isOn]];\n    _detail.hidden = ![switchButton isOn];\n    [DoraemonWeakNetworkWindow shareInstance].hidden = _detail.hidden;\n    if([switchButton isOn]){\n        [[DoraemonWeakNetworkManager shareInstance] startRecord];\n    }else{\n        [[DoraemonWeakNetworkManager shareInstance] endRecord];\n    }\n}\n\n#pragma mark - DoraemonWeakNetworkWindowDelegate\n- (void)doraemonWeakNetworkWindowClosed {\n    _weakSwitchView.switchView.on = NO;\n    _detail.hidden = YES;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/Function/DoraemonWeakNetworkHandle.h",
    "content": "//\n//  DoraemonWeakNetworkHandle.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/12.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonWeakNetworkHandle : NSObject\n\n- (NSData *)weakFlow:(NSData *)data count:(NSInteger)times size:(NSInteger)weakSize;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/Function/DoraemonWeakNetworkHandle.m",
    "content": "//\n//  DoraemonWeakNetworkHandle.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/12.\n//\n\n#import \"DoraemonWeakNetworkHandle.h\"\n#import \"DoraemonWeakNetworkManager.h\"\n#import \"DoraemonNetworkInterceptor.h\"\n\n@interface DoraemonWeakNetworkHandle()\n\n@end\n\n@implementation DoraemonWeakNetworkHandle\n\n- (NSData *)weakFlow:(NSData *)data count:(NSInteger)times size:(NSInteger)weakSize{\n    if(data.length < weakSize){\n        return data;\n    }\n    NSRange range = NSMakeRange(weakSize * times, weakSize);\n    NSInteger endPoint = weakSize * (times + 1);\n    if(endPoint > data.length || ![DoraemonWeakNetworkManager shareInstance].shouldWeak){\n        endPoint = data.length - weakSize * times;\n        range = NSMakeRange(weakSize * times, endPoint);\n    }\n    return [data subdataWithRange:range];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/Function/DoraemonWeakNetworkManager.h",
    "content": "//\n//  DoraemonWeakNetworkManager.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/21.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonWeakNetworkManager : NSObject\n\n@property (nonatomic, assign) BOOL shouldWeak;\n@property (nonatomic, assign) NSUInteger selecte;\n@property (nonatomic, assign) NSUInteger upFlowSpeed;\n@property (nonatomic, assign) NSUInteger downFlowSpeed;\n@property (nonatomic, assign) NSUInteger delayTime;\n\n\n+ (DoraemonWeakNetworkManager *)shareInstance;\n\n- (void)canInterceptNetFlow:(BOOL)enable;\n- (void)startRecord;\n- (void)endRecord;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/Function/DoraemonWeakNetworkManager.m",
    "content": "//\n//  DoraemonWeakNetworkManager.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/21.\n//\n\n#import \"DoraemonWeakNetworkManager.h\"\n#import \"DoraemonNetworkInterceptor.h\"\n#import \"DoraemonWeakNetworkHandle.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonWeakNetworkWindow.h\"\n#import \"DoraemonNetFlowManager.h\"\n#import \"DoraemonUrlUtil.h\"\n\n@interface DoraemonWeakNetworkManager()<DoraemonNetworkInterceptorDelegate,DoraemonNetworkWeakDelegate>\n\n@property (nonatomic, assign) CGFloat sleepTime;\n@property (nonatomic, strong) DoraemonWeakNetworkHandle *weakHandle;\n@property (nonatomic, strong) NSDate *startTime;\n@property (nonatomic, strong) NSTimer *secondTimer;\n\n\n@end\n\n@implementation DoraemonWeakNetworkManager\n\n+ (DoraemonWeakNetworkManager *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonWeakNetworkManager *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonWeakNetworkManager alloc] init];\n        instance.shouldWeak = NO;\n        instance.sleepTime = 500000;\n    });\n    return instance;\n}\n\n- (void)startRecord{\n    [DoraemonWeakNetworkManager shareInstance].startTime = [NSDate date];\n    if(!_secondTimer){\n        _secondTimer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(doSecondFunction) userInfo:nil repeats:YES];\n        [[NSRunLoop currentRunLoop] addTimer:_secondTimer forMode:NSRunLoopCommonModes];\n    }\n}\n\n- (void)doSecondFunction{\n    NSString *str = nil;\n    if(![DoraemonWeakNetworkWindow shareInstance].upFlowChanged){\n        [[DoraemonWeakNetworkWindow shareInstance] updateFlowValue:@\"0\" downFlow:str fromWeak:YES];\n    }\n    if(![DoraemonWeakNetworkWindow shareInstance].downFlowChanged){\n        [[DoraemonWeakNetworkWindow shareInstance] updateFlowValue:str downFlow:@\"0\" fromWeak:YES];\n    }\n}\n\n- (void)endRecord{\n    if(_secondTimer){\n        [_secondTimer invalidate];\n        _secondTimer = nil;\n    }\n    [self canInterceptNetFlow:NO];\n}\n\n- (void)canInterceptNetFlow:(BOOL)enable{\n    _shouldWeak = enable;\n    if (enable) {\n        [[DoraemonNetworkInterceptor shareInstance] addDelegate:self];\n        [DoraemonNetworkInterceptor shareInstance].weakDelegate = self;\n        _weakHandle = [[DoraemonWeakNetworkHandle alloc] init];\n    }else{\n        [DoraemonNetworkInterceptor.shareInstance removeDelegate:self];\n        [DoraemonNetworkInterceptor shareInstance].weakDelegate = nil;\n    }\n}\n\n- (BOOL)shouldWeak{\n    return _shouldWeak;\n}\n\n- (BOOL)limitSpeed:(NSData *)data isDown:(BOOL)is{\n    BOOL result = NO;\n    CGFloat speed = is ? _downFlowSpeed : _upFlowSpeed ;\n    if(0 == data.length || data.length < (DoKitKbChange(speed) ? : DoKitKbChange(2000))){\n        [self showWeakNetworkWindow:is speed:speed];\n        result = YES;\n    }\n    else{\n        [self showWeakNetworkWindow:is speed:speed];\n        usleep(_sleepTime);\n        [self showWeakNetworkWindow:is speed:speed];\n        usleep(_sleepTime);\n    }\n    [self flowChange:is change:NO];\n    return result;\n}\n\n- (void)flowChange:(BOOL)isDownFlow change:(BOOL)change{\n    if(isDownFlow){\n        [DoraemonWeakNetworkWindow shareInstance].downFlowChanged = change;\n    }else{\n        [DoraemonWeakNetworkWindow shareInstance].upFlowChanged = change;\n    }\n}\n\n- (void)showWeakNetworkWindow:(BOOL) is speed:(CGFloat)speed{\n    NSString *str = nil;\n    [self flowChange:is change:YES];\n    if(is){\n        [[DoraemonWeakNetworkWindow shareInstance] updateFlowValue:str downFlow:[NSString stringWithFormat: @\"%f\", (DoKitKbChange(speed) ? : DoKitKbChange(2000))] fromWeak:YES];\n    }else{\n        [[DoraemonWeakNetworkWindow shareInstance] updateFlowValue:[NSString stringWithFormat: @\"%f\", (DoKitKbChange(speed) ? : DoKitKbChange(2000))] downFlow:str fromWeak:YES];\n    }\n}\n\n#pragma mark -- DoraemonNetworkInterceptorDelegate\n- (void)doraemonNetworkInterceptorDidReceiveData:(NSData *)data response:(NSURLResponse *)response request:(NSURLRequest *)request error:(NSError *)error startTime:(NSTimeInterval)startTime {\n    [DoraemonUrlUtil requestLength:request callBack:^(NSUInteger length) {\n        [[DoraemonWeakNetworkWindow shareInstance] updateFlowValue:[NSString stringWithFormat:@\"%zi\",length] downFlow:[NSString stringWithFormat:@\"%lli\",[DoraemonUrlUtil getResponseLength:(NSHTTPURLResponse *)response data:data]] fromWeak:NO];\n    }];\n}\n\n- (BOOL)shouldIntercept {\n    return _shouldWeak;\n}\n\n\n#pragma mark - doraemonNSURLProtocolWeakNetDelegate\n- (void)handleWeak:(NSData *)data isDown:(BOOL)is{\n    NSUInteger count = 0;\n    NSData *limitedData = nil;\n    NSInteger speed = 0;\n    speed = is ? _downFlowSpeed : _upFlowSpeed;\n    while (true) {\n        limitedData = [_weakHandle weakFlow:data count:count size:DoKitKbChange(speed) ? : DoKitKbChange(2000)];\n        if([self limitSpeed:limitedData isDown:is]){\n            return ;\n        }\n        DoKitLog(@\"count == %ld\",count);\n        [self flowChange:is change:YES];\n        count++;\n    }\n}\n\n- (NSUInteger)delayTime{\n    return _delayTime;\n}\n\n- (NSInteger)weakNetSelecte{\n    return _selecte;\n}\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/View/DoraemonWeakNetworkDetailView.h",
    "content": "//\n//  DoraemonWeakNetworkDetailView.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/16.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonWeakNetworkDetailView : UIView\n\n- (void)renderInputView:(NSInteger)select;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/View/DoraemonWeakNetworkDetailView.m",
    "content": "//\n//  DoraemonWeakNetworkDetailView.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/16.\n//\n\n#import \"DoraemonWeakNetworkDetailView.h\"\n#import \"DoraemonWeakNetworkManager.h\"\n#import \"DoraemonWeakNetworkLevelView.h\"\n#import \"DoraemonWeakNetworkInputView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonWeakNetworkDetailView()<DoraemonWeakNetworkLevelViewDelegate>\n\n@property (nonatomic, strong) DoraemonWeakNetworkLevelView *levelView;\n@property (nonatomic, strong) DoraemonWeakNetworkInputView *delayInputView;\n@property (nonatomic, strong) DoraemonWeakNetworkInputView *upInputView;\n@property (nonatomic, strong) DoraemonWeakNetworkInputView *downInputView;\n@property (nonatomic, strong) NSArray *weakItemArray;\n@property (nonatomic, strong) NSDictionary *inputItemArray;\n@property (nonatomic, assign) NSInteger weakSize;\n@property (nonatomic, strong) NSString *delayTitle;\n@property (nonatomic, strong) NSString *upFlowTitle;\n@property (nonatomic, strong) NSString *downFlowTitle;\n@property (nonatomic, strong) NSString *flowEpilog;\n@property (nonatomic, strong) NSString *timeEpilog;\n\n\n@end\n\n\n@implementation DoraemonWeakNetworkDetailView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if(self){\n        CGFloat padding = kDoraemonSizeFrom750_Landscape(68);\n        _levelView = [[DoraemonWeakNetworkLevelView alloc] initWithFrame:CGRectMake(0, padding, self.doraemon_width, padding)];\n        _levelView.delegate = self;\n        [self initWeakItem];\n        [_levelView renderUIWithItemArray:_weakItemArray selecte:[DoraemonWeakNetworkManager shareInstance].selecte ? :0];\n        [self addSubview:_levelView];\n        \n        _delayInputView = [[DoraemonWeakNetworkInputView alloc] initWithFrame:CGRectMake(padding * 2, _levelView.doraemon_bottom + padding/2, self.doraemon_width - padding, padding)];\n        _upInputView = [[DoraemonWeakNetworkInputView alloc] initWithFrame:CGRectMake(padding * 2, _levelView.doraemon_bottom + padding/2, self.doraemon_width - padding, padding)];\n        _downInputView = [[DoraemonWeakNetworkInputView alloc] initWithFrame:CGRectMake(padding * 2, _upInputView.doraemon_bottom , self.doraemon_width - padding, padding)];\n        [self renderInputView:[DoraemonWeakNetworkManager shareInstance].selecte];\n        \n        __weak typeof(self) weakSelf = self;\n         [_delayInputView addBlock:^{\n             [DoraemonWeakNetworkManager shareInstance].delayTime = [weakSelf.delayInputView getInputValue];\n         }];\n         [_upInputView addBlock:^{\n             [DoraemonWeakNetworkManager shareInstance].upFlowSpeed = [weakSelf.upInputView getInputValue];\n         }];\n         [_downInputView addBlock:^{\n             [DoraemonWeakNetworkManager shareInstance].downFlowSpeed = [weakSelf.downInputView getInputValue];\n         }];\n        \n        [self addSubview:_delayInputView];\n        [self addSubview:_upInputView];\n        [self addSubview:_downInputView];\n        \n    }\n    return self;\n}\n\n- (void)initWeakItem{\n    _weakItemArray = @[\n        @\"断网\",\n        @\"超时\",\n        @\"限速\",\n        @\"延时\"\n    ];\n    \n    _delayTitle = [NSString stringWithFormat:@\"%@:\",DoraemonLocalizedString(@\"延时时间\")];\n    _upFlowTitle = [NSString stringWithFormat:@\"%@:\",DoraemonLocalizedString(@\"请求限速\")];\n    _downFlowTitle = [NSString stringWithFormat:@\"%@:\",DoraemonLocalizedString(@\"响应限速\")];\n    _flowEpilog = @\"Kb/s\";\n    _timeEpilog = @\"S\";\n}\n\n- (void)_renderInputHidden:(BOOL)hidden{\n    _delayInputView.hidden = !hidden;\n    _upInputView.hidden = hidden;\n    _downInputView.hidden = hidden;\n}\n\n- (void)_renderInputValue{\n    [DoraemonWeakNetworkManager shareInstance].delayTime = [_delayInputView getInputValue];\n    [DoraemonWeakNetworkManager shareInstance].upFlowSpeed = [_upInputView getInputValue];\n    [DoraemonWeakNetworkManager shareInstance].downFlowSpeed = [_downInputView getInputValue];\n}\n\n- (void)renderInputView:(NSInteger)select{\n    \n    switch (select) {\n        case 0:\n        case 1:\n            _delayInputView.hidden = YES;\n            _upInputView.hidden = YES;\n            _downInputView.hidden = YES;\n            break;\n        case 2:\n            [_upInputView renderUIWithTitle:_upFlowTitle end:_flowEpilog];\n            [_upInputView renderUIWithSpeed:[DoraemonWeakNetworkManager shareInstance].upFlowSpeed define:2000];\n            [_downInputView renderUIWithTitle:_downFlowTitle end:_flowEpilog];\n            [_downInputView renderUIWithSpeed:[DoraemonWeakNetworkManager shareInstance].downFlowSpeed define:2000];\n           [self _renderInputHidden:NO];\n            break;\n        case 3:\n            [_delayInputView renderUIWithTitle:_delayTitle end:_timeEpilog];\n            [_delayInputView renderUIWithSpeed:[DoraemonWeakNetworkManager shareInstance].delayTime define:10];\n            [self _renderInputHidden:YES];\n            break;\n                \n        default:\n            break;\n    }\n}\n\n#pragma mark - DoraemonWeakNetworkLevelViewDelegate\n- (void)segmentSelected:(NSInteger)index{\n\n    [DoraemonWeakNetworkManager shareInstance].selecte = index;\n    [self renderInputView:index];\n    [self _renderInputValue];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/View/DoraemonWeakNetworkInputView.h",
    "content": "//\n//  DoraemonWeakNetworkInputView.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/16.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\ntypedef void (^DoraemonNetWeakInputBlock)(void);\n\n@interface DoraemonWeakNetworkInputView : UIView\n\n- (void)renderUIWithTitle:(NSString *)title end:(NSString *)epilog;\n- (void)renderUIWithSpeed:(long)speed define:(NSInteger)value;\n- (long)getInputValue;\n\n- (void)addBlock:(DoraemonNetWeakInputBlock)block;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/View/DoraemonWeakNetworkInputView.m",
    "content": "//\n//  DoraemonWeakNetworkInputView.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/16.\n//\n\n#import \"DoraemonWeakNetworkInputView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonWeakNetworkInputView()<UITextFieldDelegate>\n\n@property (nonatomic, strong) UILabel *inputTitle;\n@property (nonatomic, strong) UILabel *inputEpilog;\n@property (nonatomic, strong) UITextField *speedInput;\n@property (nonatomic, copy) DoraemonNetWeakInputBlock block;\n\n@end\n\n@implementation DoraemonWeakNetworkInputView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        \n        _inputTitle = [[UILabel alloc] init];\n        _speedInput = [[UITextField alloc] init];\n        _speedInput.textAlignment = NSTextAlignmentCenter;\n        _speedInput.delegate = self;\n        _speedInput.keyboardType = UIKeyboardTypeNumberPad;\n        _inputEpilog = [[UILabel alloc] init];\n        _inputTitle.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)];\n        \n        [self addSubview:_inputTitle];\n        [self addSubview:_speedInput];\n        [self addSubview:_inputEpilog];\n    }\n    return self;\n}\n\n- (void)renderUIWithTitle:(NSString *)title end:(NSString *)epilog{\n    _inputTitle.text = title;\n    CGSize size = [title sizeWithAttributes:@{\n        @\"NSFontAttributeName\" : [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)]\n    }];\n    _inputTitle.frame = CGRectMake(0, 0,size.width + kDoraemonSizeFrom750_Landscape(58), self.doraemon_height);\n    _inputEpilog.text = epilog;\n    _speedInput.frame = CGRectMake(_inputTitle.doraemon_right, 0, kDoraemonSizeFrom750_Landscape(100), self.doraemon_height);\n    _inputEpilog.frame = CGRectMake(_speedInput.doraemon_right + kDoraemonSizeFrom750_Landscape(32), 0, kDoraemonSizeFrom750_Landscape(100), self.doraemon_height);\n}\n\n- (void)renderUIWithSpeed:(long)speed define:(NSInteger)value{\n    if(speed > 0){\n        _speedInput.text = [NSString stringWithFormat:@\"%ld\",speed];\n        _speedInput.textColor = [UIColor doraemon_black_1];\n    }else{\n        _speedInput.placeholder = [NSString stringWithFormat:@\"%ld\",(long)value];\n        _speedInput.textColor = [UIColor lightGrayColor];\n    }\n}\n\n- (long)getInputValue{\n    return [_speedInput.text intValue];\n}\n\n- (void)addBlock:(DoraemonNetWeakInputBlock)block{\n    self.block = block;\n}\n\n#pragma mark - UITextFieldDelegate\n- (void)textFieldDidEndEditing:(UITextField *)textField{\n    if(self.block){\n        self.block();\n    }\n}\n\n#pragma mark - UITextFieldDelegate\n- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {\n    if(_speedInput.text.length == 0){\n        if([@\"0\" isEqualToString:string] || [@\"0\" isEqualToString:[string substringToIndex:1]])\n             return NO;\n    }\n    return [self validateNumber:string];\n}\n\n//键盘输入\n- (BOOL)validateNumber:(NSString*)number {\n    BOOL res = YES;\n    NSCharacterSet* tmpSet = [NSCharacterSet characterSetWithCharactersInString:@\"0123456789\"];\n    int i = 0;\n    while (i < number.length) {\n        NSString * string = [number substringWithRange:NSMakeRange(i, 1)];\n        NSRange range = [string rangeOfCharacterFromSet:tmpSet];\n        if (range.length == 0) {\n            res = NO;\n            break;\n        }\n        i++;\n    }\n    if(res){\n        _speedInput.textColor = [UIColor doraemon_black_1];\n    }\n    return res;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/View/DoraemonWeakNetworkLevelView.h",
    "content": "//\n//  DoraemonWeakNetworkLevelView.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/16.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@protocol DoraemonWeakNetworkLevelViewDelegate<NSObject>\n\n- (void)segmentSelected:(NSInteger)index;\n\n@end\n\n@interface DoraemonWeakNetworkLevelView : UIView\n\n@property (nonatomic, weak) id<DoraemonWeakNetworkLevelViewDelegate> delegate;\n\n-(void)renderUIWithItemArray:(NSArray *)itemArray selecte:(NSUInteger)selected;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/View/DoraemonWeakNetworkLevelView.m",
    "content": "//\n//  DoraemonWeakNetworkLevelView.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/16.\n//\n\n#import \"DoraemonWeakNetworkLevelView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonWeakNetworkLevelView()\n\n@property (nonatomic, strong) UISegmentedControl *segment;\n\n@end\n\n@implementation DoraemonWeakNetworkLevelView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        //NSArray *dataArray = @[@\"Verbose\",@\"Debug\",@\"Info\"];\n        _segment = [[UISegmentedControl alloc] init];\n        _segment.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(68), self.doraemon_height/2-kDoraemonSizeFrom750_Landscape(68)/2, self.doraemon_width-kDoraemonSizeFrom750_Landscape(68)*2, kDoraemonSizeFrom750_Landscape(68));\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13, *)) {\n           _segment.selectedSegmentTintColor = [UIColor doraemon_colorWithString:@\"#337CC4\"];\n        } else {\n#endif\n            _segment.tintColor = [UIColor doraemon_colorWithString:@\"#337CC4\"];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        [_segment addTarget:self action:@selector(segmentChange:) forControlEvents:UIControlEventValueChanged];\n        UIFont *font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)];   // 设置字体大小\n        NSDictionary *attributes = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];\n        [_segment setTitleTextAttributes:attributes forState:UIControlStateNormal];\n        [self addSubview:_segment];\n    }\n    return self;\n}\n\n-(void)renderUIWithItemArray:(NSArray *)itemArray selecte:(NSUInteger)selected{\n    for (int i = 0; i<itemArray.count; i++) {\n        [_segment insertSegmentWithTitle:DoraemonLocalizedString(itemArray[i]) atIndex:i animated:NO];\n    }\n    [_segment setSelectedSegmentIndex:selected];\n}\n\n-(void)segmentChange:(UISegmentedControl *)sender{\n    NSInteger index = sender.selectedSegmentIndex;\n    if (self.delegate && [self.delegate respondsToSelector:@selector(segmentSelected:)]) {\n        [self.delegate segmentSelected:index];\n    }\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/View/Window/DoraemonWeakNetworkWindow.h",
    "content": "//\n//  DoraemonWeakNetworkWindow.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/3/21.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@protocol DoraemonWeakNetworkWindowDelegate <NSObject>\n\n- (void)doraemonWeakNetworkWindowClosed;\n\n@end\n\n\n@interface DoraemonWeakNetworkWindow : UIWindow\n\n+ (DoraemonWeakNetworkWindow *)shareInstance;\n\n- (void)hide;\n\n-(void)updateFlowValue:(NSString *)upFlow downFlow:(NSString *)downFlow fromWeak:(BOOL)is;\n\n// start位置\n@property (nonatomic) CGPoint startingPosition;\n@property (nonatomic, assign) BOOL upFlowChanged;\n@property (nonatomic, assign) BOOL downFlowChanged;\n@property (nonatomic, weak) id<DoraemonWeakNetworkWindowDelegate> delegate;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Performance/WeakNetwork/View/Window/DoraemonWeakNetworkWindow.m",
    "content": "//\n//  DoraemonWeakNetworkWindow.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/3/21.\n//\n\n#import \"DoraemonWeakNetworkWindow.h\"\n#import \"DoraemonDefine.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonStatusBarViewController.h\"\n#import \"DoraemonWeakNetworkManager.h\"\n#import \"DoraemonNetworkInterceptor.h\"\n\n@interface DoraemonWeakNetworkWindow()\n\n@property (nonatomic, assign) CGFloat showViewSize;\n@property (nonatomic, strong) UILabel *flowtitle;\n@property (nonatomic, strong) UILabel *upFlowValue;\n@property (nonatomic, strong) UILabel *downFlowValue;\n@property (nonatomic, strong) UIButton *closeBtn;\n\n@property(nonatomic, assign) int height;\n@property(nonatomic, assign) int width;\n@property(nonatomic, assign) int fontSize;\n@property(nonatomic, strong) UIColor *fontColor;\n\n@end\n\n@implementation DoraemonWeakNetworkWindow\n\n+ (DoraemonWeakNetworkWindow *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonWeakNetworkWindow *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonWeakNetworkWindow alloc] init];\n        [instance _initialization];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    _showViewSize = kDoraemonSizeFrom750_Landscape(148);\n    CGFloat x = self.startingPosition.x;\n    CGFloat y = self.startingPosition.y;\n    CGPoint defaultPosition = DoraemonStartingPosition;\n    if (x < 0 || x > (DoraemonScreenWidth - _showViewSize*2.5)) {\n        x = defaultPosition.x;\n    }\n    if (y <= 0 || y > (DoraemonScreenHeight - _showViewSize)) {\n        y = _showViewSize/2;\n    }\n    \n    self = [super initWithFrame:CGRectMake(x, y, _showViewSize*2.5, _showViewSize)];\n    if (self) {\n        self.backgroundColor = [UIColor doraemon_colorWithHex:0x000000 andAlpha:0.33];\n        self.windowLevel = UIWindowLevelStatusBar + 50;\n        self.layer.cornerRadius = kDoraemonSizeFrom750_Landscape(8);\n        self.layer.masksToBounds = YES;//保证显示\n        NSString *version= [UIDevice currentDevice].systemVersion;\n        if(version.doubleValue >=10.0) {\n            if (!self.rootViewController) {\n                self.rootViewController = [[UIViewController alloc] init];\n            }\n        }else{\n            //iOS9.0的系统中，新建的window设置的rootViewController默认没有显示状态栏\n            if (!self.rootViewController) {\n                self.rootViewController = [[DoraemonStatusBarViewController alloc] init];\n            }\n        }\n        UIPanGestureRecognizer *move = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_move:)];\n        [self addGestureRecognizer:move];\n    }\n    return self;\n}\n\n- (void)_initialization{\n    _height = kDoraemonSizeFrom750_Landscape(26);\n    _width = self.doraemon_width/5;\n    _fontSize = kDoraemonSizeFrom750_Landscape(26);\n    _fontColor = [UIColor whiteColor];\n    _width = self.doraemon_width - _width;\n    [self.rootViewController.view addSubview:self.flowtitle];\n    [self.rootViewController.view addSubview:self.upFlowValue];\n    [self.rootViewController.view addSubview:self.downFlowValue];\n    [self.rootViewController.view addSubview:self.closeBtn];\n}\n\n- (void)closeBtnClick{\n    [self hide];\n}\n\n- (void)hide{\n    self.hidden = YES;\n    [[DoraemonWeakNetworkManager shareInstance] endRecord];\n    if(self.delegate) {\n        [_delegate doraemonWeakNetworkWindowClosed];\n    }\n}\n\n\n- (UIButton *)closeBtn{\n    _closeBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.doraemon_width-_height, 0, _height, _height)];\n    [_closeBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_close_white\"] forState:UIControlStateNormal];\n    [_closeBtn addTarget:self action:@selector(closeBtnClick) forControlEvents:UIControlEventTouchUpInside];\n    _closeBtn.backgroundColor = [UIColor clearColor];\n    return _closeBtn;\n}\n\n- (UILabel *)flowtitle{\n    if (!_flowtitle) {\n        _flowtitle = [[UILabel alloc] initWithFrame:CGRectMake(0, self.doraemon_height/8, _width*2, _height)];\n        _flowtitle.textAlignment = NSTextAlignmentLeft;\n        _flowtitle.font = [UIFont systemFontOfSize:_fontSize];\n        _flowtitle.textColor = _fontColor;\n        _flowtitle.text = DoraemonLocalizedString(@\"弱网模式\");\n        _flowtitle.adjustsFontSizeToFitWidth = YES;\n    }\n    return _flowtitle;\n}\n\n- (UILabel *)upFlowValue {\n    if (!_upFlowValue) {\n        _upFlowValue = [[UILabel alloc] initWithFrame:CGRectMake(0, self.doraemon_height/3 + _height/2, _width*2, _height)];\n        _upFlowValue.textAlignment = NSTextAlignmentLeft;\n        _upFlowValue.font = [UIFont systemFontOfSize:_fontSize];\n        _upFlowValue.textColor = _fontColor;\n        _upFlowValue.adjustsFontSizeToFitWidth = YES;\n    }\n    return _upFlowValue;\n}\n\n- (UILabel *)downFlowValue {\n    if (!_downFlowValue) {\n        _downFlowValue = [[UILabel alloc] initWithFrame:CGRectMake(0, _upFlowValue.doraemon_bottom + _height/2, _width*2, _height)];\n        _downFlowValue.textAlignment = NSTextAlignmentLeft;\n        _downFlowValue.font = [UIFont systemFontOfSize:_fontSize];\n        _downFlowValue.textColor = _fontColor;\n        _downFlowValue.adjustsFontSizeToFitWidth = YES;\n    }\n    return _downFlowValue;\n}\n\n- (void)_move:(UIPanGestureRecognizer *)sender{\n    //1、获得拖动位移\n    CGPoint offsetPoint = [sender translationInView:sender.view];\n    //2、清空拖动位移\n    [sender setTranslation:CGPointZero inView:sender.view];\n    //3、重新设置控件位置\n    UIView *panView = sender.view;\n    CGFloat newX = panView.doraemon_centerX+offsetPoint.x;\n    CGFloat newY = panView.doraemon_centerY+offsetPoint.y;\n    if (newX < _showViewSize) {\n        newX = _showViewSize;\n    }\n    if (newX > DoraemonScreenWidth - _showViewSize) {\n        newX = DoraemonScreenWidth - _showViewSize;\n    }\n    if (newY < _showViewSize/2) {\n        newY = _showViewSize/2;\n    }\n    if (newY > DoraemonScreenHeight - _showViewSize/2) {\n        newY = DoraemonScreenHeight - _showViewSize/2;\n    }\n    panView.center = CGPointMake(newX, newY);\n}\n\n-(void)updateFlowValue:(NSString *)upFlow downFlow:(NSString *)downFlow fromWeak:(BOOL)is{\n    dispatch_async(dispatch_get_main_queue(), ^{\n        [self _updateFlowValue:upFlow downFlow:downFlow fromWeak:is];\n    });\n}\n\n-(void)_updateFlowValue:(NSString *)upFlow downFlow:(NSString *)downFlow fromWeak:(BOOL)is{\n    NSInteger selecte = [DoraemonWeakNetworkManager shareInstance].selecte;\n    if(self.hidden){\n        return ;\n    }else if(!is){\n        if(selecte == DoraemonWeakNetwork_Break ||selecte == DoraemonWeakNetwork_OutTime){\n            downFlow = nil;\n        }else if(selecte == DoraemonWeakNetwork_WeakSpeed){\n            return ;\n        }\n    }\n        \n    if([upFlow floatValue]>=1000){\n        upFlow = [NSString stringWithFormat:@\"%.1fK\",[upFlow floatValue]/1000];\n        NSLog(@\"the end === %@\",upFlow);\n    }\n    \n    if([downFlow floatValue]>=1000){\n        downFlow = [NSString stringWithFormat:@\"%.1fK\",[downFlow floatValue]/1000];\n    }\n    _upFlowValue.text = upFlow ? [NSString stringWithFormat:@\"%@ : %@ B/s\",DoraemonLocalizedString(@\"上行流量\"),upFlow] : _upFlowValue.text;\n    _downFlowValue.text = downFlow ? [NSString stringWithFormat:@\"%@ : %@ B/s\",DoraemonLocalizedString(@\"下行流量\"),downFlow] :_downFlowValue.text;\n}\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/FileSync/DoraemonFileSyncPlugin.h",
    "content": "//\n//  DoraemonFileSyncPlugin.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/6/10.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonFileSyncPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/FileSync/DoraemonFileSyncPlugin.m",
    "content": "//\n//  DoraemonFileSyncPlugin.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/6/10.\n//\n\n#import \"DoraemonFileSyncPlugin.h\"\n#import \"DoraemonManager.h\"\n#import \"DoraemonFileSyncViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonToastUtil.h\"\n#import \"UIViewController+Doraemon.h\"\n#import \"Doraemoni18NUtil.h\"\n\n@implementation DoraemonFileSyncPlugin\n\n- (void)pluginDidLoad{\n    if ([DoraemonManager shareInstance].pId) {\n        DoraemonFileSyncViewController *vc = [[DoraemonFileSyncViewController alloc] init];\n        [DoraemonHomeWindow openPlugin:vc];\n    }else{\n        [DoraemonToastUtil showToastBlack:DoraemonLocalizedString(@\"需要到www.dokit.cn上注册pId才能使用该功能\") inView:[UIViewController rootViewControllerForDoraemonHomeWindow].view];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/FileSync/DoraemonFileSyncViewController.h",
    "content": "//\n//  DoraemonFileSyncViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/6/10.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonFileSyncViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/FileSync/DoraemonFileSyncViewController.m",
    "content": "//\n//  DoraemonFileSyncViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/6/10.\n//\n\n#import \"DoraemonFileSyncViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonFileSyncManager.h\"\n#import <GCDWebServer/GCDWebUploader.h>\n\n@interface DoraemonFileSyncViewController ()<GCDWebUploaderDelegate>\n\n@property (nonatomic, strong) UIImageView *bannerImage;\n@property (nonatomic, strong) UIButton *ctrlBtn;\n@property (nonatomic, strong) UILabel *tipLabel;\n@property (nonatomic, strong) UILabel *ipLabel;\n\n@property (nonatomic, strong) GCDWebUploader *webServer;\n\n@end\n\n@implementation DoraemonFileSyncViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"文件同步\");\n    \n    _bannerImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(422))];\n    _bannerImage.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_file_sync_banner\"];\n    [self.view addSubview:_bannerImage];\n    \n    _ctrlBtn = [[UIButton alloc] init];\n    _ctrlBtn.layer.cornerRadius = kDoraemonSizeFrom750_Landscape(8);\n    [_ctrlBtn addTarget:self action:@selector(ctrlBtnClick) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:_ctrlBtn];\n    \n    _tipLabel = [[UILabel alloc] init];\n   \n    _tipLabel.textColor = [UIColor doraemon_black_3];\n    \n    _tipLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n    _tipLabel.textAlignment = NSTextAlignmentCenter;\n    [self.view addSubview:_tipLabel];\n    \n    _ipLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, _bannerImage.doraemon_bottom + kDoraemonSizeFrom750_Landscape(290), self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(64))];\n    _ipLabel.textColor = [UIColor doraemon_black_1];\n    _ipLabel.textAlignment = NSTextAlignmentCenter;\n    _ipLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(56)];\n    [self.view addSubview:_ipLabel];\n    \n    [self refreshUIWithStatus:[DoraemonFileSyncManager sharedInstance].start];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n- (void)ctrlBtnClick{\n    BOOL status = [DoraemonFileSyncManager sharedInstance].start;\n    status = !status;\n    [DoraemonFileSyncManager sharedInstance].start = status;\n    if (status) {\n        [[DoraemonFileSyncManager sharedInstance] startServer];\n//        NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];\n//        self.webServer = [[GCDWebUploader alloc] initWithUploadDirectory:docPath];\n//        self.webServer.delegate = self;\n//        self.webServer.allowHiddenItems = YES;\n//        [self.webServer start];\n        \n    }else{\n        [[DoraemonFileSyncManager sharedInstance] stop];\n//        [self.webServer stop];\n//        self.webServer = nil;\n    }\n    [self refreshUIWithStatus:status];\n}\n\n- (void)webUploader:(GCDWebUploader*)uploader didDownloadFileAtPath:(NSString*)path{\n    NSLog(@\"upload == %@  path == %@\",uploader,path);\n}\n\n- (void)webUploader:(GCDWebUploader*)uploader didUploadFileAtPath:(NSString*)path{\n    NSLog(@\"upload == %@  path == %@\",uploader,path);\n}\n\n- (void)webUploader:(GCDWebUploader*)uploader didMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath{\n    \n}\n\n- (void)webUploader:(GCDWebUploader*)uploader didDeleteItemAtPath:(NSString*)path{\n    \n}\n\n\n- (void)webUploader:(GCDWebUploader*)uploader didCreateDirectoryAtPath:(NSString*)path{\n    \n}\n\n- (void)refreshUIWithStatus:(BOOL)start{\n    if (start) {\n        _ctrlBtn.frame = CGRectMake(self.view.doraemon_width/2-kDoraemonSizeFrom750_Landscape(300)/2, self.view.doraemon_height-kDoraemonSizeFrom750_Landscape(80)-kDoraemonSizeFrom750_Landscape(100), kDoraemonSizeFrom750_Landscape(300), kDoraemonSizeFrom750_Landscape(100));\n        _ctrlBtn.backgroundColor = [UIColor doraemon_colorWithHexString:@\"#F6F7F9\"];\n        [_ctrlBtn setTitleColor:[UIColor doraemon_blue] forState:UIControlStateNormal];\n        [_ctrlBtn setTitle:@\"关闭本地服务\" forState:UIControlStateNormal];\n        \n        _tipLabel.frame = CGRectMake(0, _ipLabel.doraemon_bottom+kDoraemonSizeFrom750_Landscape(24), self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(32));\n        _tipLabel.text = @\"请在Web端通过当前ip进行连接\";\n        \n        _ipLabel.hidden = NO;\n        NSURL *url = [DoraemonFileSyncManager sharedInstance].serverURL;\n        \n        _ipLabel.text = [NSString stringWithFormat:@\"%@:%@\",url.host,url.port];\n    }else{\n        _ctrlBtn.frame = CGRectMake(self.view.doraemon_width/2-kDoraemonSizeFrom750_Landscape(300)/2, _bannerImage.doraemon_bottom+kDoraemonSizeFrom750_Landscape(290), kDoraemonSizeFrom750_Landscape(300), kDoraemonSizeFrom750_Landscape(100));\n        _ctrlBtn.backgroundColor = [UIColor doraemon_blue];\n        [_ctrlBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];\n        [_ctrlBtn setTitle:@\"开启本地服务\" forState:UIControlStateNormal];\n        \n        _tipLabel.frame = CGRectMake(0, self.view.doraemon_height-kDoraemonSizeFrom750_Landscape(40)-kDoraemonSizeFrom750_Landscape(32), self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(32));\n        _tipLabel.text = @\"提示：点击按钮开启本地服务\";\n        \n        _ipLabel.hidden = YES;\n        _ipLabel.text = @\"\";\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/FileSync/Function/DoraemonFileSyncManager.h",
    "content": "//\n//  DoraemonFileSyncManager.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/6/10.\n//\n\n#import <Foundation/Foundation.h>\n#import <GCDWebServer/GCDWebServer.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonFileSyncManager : GCDWebServer\n\n+ (instancetype)sharedInstance;\n\n@property (nonatomic, assign) BOOL start;//服务时候开启\n\n- (void)startServer;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/FileSync/Function/DoraemonFileSyncManager.m",
    "content": "//\n//  DoraemonFileSyncManager.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/6/10.\n//\n\n#import \"DoraemonFileSyncManager.h\"\n#import <GCDWebServer/GCDWebServerRequest.h>\n#import <GCDWebServer/GCDWebServerDataRequest.h>\n#import <GCDWebServer/GCDWebServerDataResponse.h>\n#import <GCDWebServer/GCDWebServerMultiPartFormRequest.h>\n#import <GCDWebServer/GCDWebServerFileResponse.h>\n#import <FMDB/FMDB.h>\n#import \"DoraemonAppInfoUtil.h\"\n\n\n#define DK_SERVER_PORT 9002\n\n@interface DoraemonFileSyncManager()\n\n@property (nonatomic, strong) NSFileManager *fm;\n\n@end\n\n\n@implementation DoraemonFileSyncManager\n\n+ (instancetype)sharedInstance{\n    static DoraemonFileSyncManager *instance;\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        instance = [[DoraemonFileSyncManager alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    self = [super init];\n    if (self) {\n        _start = NO;\n        [self setRouter];\n        _fm = [NSFileManager defaultManager];\n    }\n    return self;\n}\n\n- (void)setRouter{\n#pragma mark - file\n    [self addDefaultHandlerForMethod:@\"GET\"\n                        requestClass:[GCDWebServerRequest class]\n                        processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        NSString *html = @\"<html><body>请访问 <b><a href=\\\"https://www.dokit.cn\\\">www.dokit.cn</a></b> 使用该功能</body></html>\";\n        return [GCDWebServerDataResponse responseWithHTML:html];\n    }];\n    \n    __weak typeof(self) weakSelf = self;\n    [self addHandlerForMethod:@\"GET\"\n                         path:@\"/getDeviceInfo\"\n                 requestClass:[GCDWebServerRequest class]\n                 processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        return  [weakSelf getDeviceInfo];\n    }];\n    \n    [self addHandlerForMethod:@\"GET\"\n                         path:@\"/getFileList\"\n                 requestClass:[GCDWebServerRequest class]\n                 processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        return [weakSelf getFileList:request];\n    }];\n    \n    [self addHandlerForMethod:@\"POST\"\n                         path:@\"/uploadFile\"\n                 requestClass:[GCDWebServerMultiPartFormRequest class]\n                 processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        return [weakSelf uploadFile:(GCDWebServerMultiPartFormRequest *)request];\n    }];\n    \n    [self addHandlerForMethod:@\"GET\"\n                         path:@\"/downloadFile\"\n                 requestClass:[GCDWebServerRequest class]\n                 processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        return [weakSelf downloadFile:request];\n    }];\n    \n    [self addHandlerForMethod:@\"POST\"\n                         path:@\"/createFolder\"\n                 requestClass:[GCDWebServerDataRequest class]\n                 processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        return [weakSelf createFolder:(GCDWebServerDataRequest *)request];\n    }];\n    \n    [self addHandlerForMethod:@\"GET\"\n                         path:@\"/getFileDetail\"\n                 requestClass:[GCDWebServerRequest class]\n                 processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        return [weakSelf getFileDetail:request];\n    }];\n    \n    [self addHandlerForMethod:@\"POST\"\n                         path:@\"/deleteFile\"\n                 requestClass:[GCDWebServerDataRequest class]\n                 processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        return [weakSelf deleteFile:(GCDWebServerDataRequest *)request];\n    }];\n    \n    [self addHandlerForMethod:@\"POST\"\n                         path:@\"/rename\"\n                 requestClass:[GCDWebServerDataRequest class]\n                 processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        return [weakSelf rename:(GCDWebServerDataRequest *)request];\n    }];\n    \n    [self addHandlerForMethod:@\"POST\"\n                         path:@\"/saveFile\"\n                 requestClass:[GCDWebServerDataRequest class]\n                 processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        return [weakSelf saveFile:(GCDWebServerDataRequest *)request];\n    }];\n    \n#pragma mark - database\n    [self addHandlerForMethod:@\"GET\"\n                         path:@\"/getAllTable\"\n                 requestClass:[GCDWebServerRequest class]\n                 processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        return [weakSelf getAllTable:request];\n    }];\n    \n    [self addHandlerForMethod:@\"GET\"\n                         path:@\"/getTableData\"\n                 requestClass:[GCDWebServerRequest class]\n                 processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        return [weakSelf getTableData:request];\n    }];\n    \n    [self addHandlerForMethod:@\"POST\"\n                         path:@\"/insertRow\"\n                 requestClass:[GCDWebServerDataRequest class]\n                 processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        return [weakSelf insertRow:(GCDWebServerDataRequest *)request];\n    }];\n    \n    [self addHandlerForMethod:@\"POST\"\n                         path:@\"/updateRow\"\n                 requestClass:[GCDWebServerDataRequest class]\n                 processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        return [weakSelf updateRow:(GCDWebServerDataRequest *)request];\n    }];\n    \n    [self addHandlerForMethod:@\"POST\"\n                         path:@\"/deleteRow\"\n                 requestClass:[GCDWebServerDataRequest class]\n                 processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        return [weakSelf deleteRow:(GCDWebServerDataRequest *)request];\n    }];\n    \n    [self addDefaultHandlerForMethod:@\"OPTIONS\"\n                        requestClass:[GCDWebServerDataRequest class]\n                        processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {\n        GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:@{}];\n        [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Methods\"];\n        [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n        [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Headers\"];\n        return response;\n    }];\n}\n\n- (NSString *)getRelativeFilePath:(NSString *)fullPath{\n    NSString *rootPath = NSHomeDirectory();\n    return [fullPath stringByReplacingOccurrencesOfString:rootPath withString:@\"\"];\n}\n\n- (NSDictionary *)getCode:(NSInteger)code data:(NSDictionary *)data{\n    NSMutableDictionary *info = [[NSMutableDictionary alloc] init];\n    [info setValue:@(code) forKey:@\"code\"];\n    [info setValue:data forKey:@\"data\"];\n    return info;\n}\n\n- (void)startServer{\n    [self startWithPort:DK_SERVER_PORT bonjourName:@\"Hello DoKit\"];\n}\n\n\n#pragma mark -- 服务具体处理\n\n- (GCDWebServerResponse *)responseWhenFailed {\n    GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:[self getCode:0 data:nil]];\n    [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n    return response;\n}\n\n- (GCDWebServerResponse *)deleteRow:(GCDWebServerDataRequest *)request {\n    NSDictionary *data = [NSJSONSerialization JSONObjectWithData:request.data options:0 error:nil];\n    NSString *dirPath = data[@\"dirPath\"];\n    if ([dirPath hasPrefix:@\"/root\"]) {\n        dirPath = [dirPath substringFromIndex:5];\n    }\n    NSString *fileName = data[@\"fileName\"];\n    NSString *tableName = data[@\"tableName\"];\n    NSArray *rowDatas = data[@\"rowDatas\"];\n    NSString *rootPath = NSHomeDirectory();\n    NSString *targetPath = [NSString stringWithFormat:@\"%@%@%@\", rootPath, dirPath, fileName];\n    \n    if (![_fm fileExistsAtPath:targetPath]) {\n        return [self responseWhenFailed];\n    }\n    \n    FMDatabase *db = [FMDatabase databaseWithPath:targetPath];\n    if (![db open]) {\n        return [self responseWhenFailed];\n    }\n    \n    /**\n     构造sql\n     DELETE FROM tableName\n     WHERE pk=pkValue;\n     */\n    NSMutableString *sql = [NSString stringWithFormat:@\"DELETE FROM %@ WHERE \", tableName].mutableCopy;\n    __block NSString *pk = nil;\n    __block id pkValue = nil;\n    \n    [rowDatas enumerateObjectsUsingBlock:^(NSDictionary *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {\n        if ([obj[@\"isPrimary\"] boolValue]) {\n            pk = obj[@\"title\"];\n            pkValue = obj[@\"value\"];\n            *stop = YES;\n        }\n    }];\n    \n    [sql appendString:[NSString stringWithFormat:@\"%@=%@;\", pk, pkValue]];\n\n    BOOL sus = [db executeUpdate:sql];;\n    \n    [db close];\n    \n    if (sus) {\n        GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:[self getCode:200 data:nil]];\n        [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n        return response;\n    } else {\n        return [self responseWhenFailed];\n    }\n}\n\n- (GCDWebServerResponse *)updateRow:(GCDWebServerDataRequest *)request {\n    NSDictionary *data = [NSJSONSerialization JSONObjectWithData:request.data options:0 error:nil];\n    NSString *dirPath = data[@\"dirPath\"];\n    if ([dirPath hasPrefix:@\"/root\"]) {\n        dirPath = [dirPath substringFromIndex:5];\n    }\n    NSString *fileName = data[@\"fileName\"];\n    NSString *tableName = data[@\"tableName\"];\n    NSArray *rowDatas = data[@\"rowDatas\"];\n    NSString *rootPath = NSHomeDirectory();\n    NSString *targetPath = [NSString stringWithFormat:@\"%@%@%@\", rootPath, dirPath, fileName];\n    \n    if (![_fm fileExistsAtPath:targetPath]) {\n        return [self responseWhenFailed];\n    }\n    \n    FMDatabase *db = [FMDatabase databaseWithPath:targetPath];\n    if (![db open]) {\n        return [self responseWhenFailed];\n    }\n    \n    /**\n     构造sql\n     UPDATE tableName\n     SET title=value, title_2=value_2\n     WHERE pk=pkValue;\n     */\n    NSMutableString *sql = [NSString stringWithFormat:@\"UPDATE %@ SET \", tableName].mutableCopy;\n    NSMutableArray *newValues = @[].mutableCopy;\n    __block NSString *pk = nil;\n    __block id pkValue = nil;\n    \n    @autoreleasepool {\n        [rowDatas enumerateObjectsUsingBlock:^(NSDictionary *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {\n            if ([obj[@\"isPrimary\"] boolValue]) {\n                pk = obj[@\"title\"];\n                pkValue = obj[@\"value\"];\n            } else {\n                NSString *title = obj[@\"title\"];\n                id value = obj[@\"value\"] != nil ? obj[@\"value\"] : @\"NULL\";\n                if ([value isKindOfClass:[NSString class]] && ![value isEqualToString:@\"NULL\"]) {\n                    value = [NSString stringWithFormat:@\"'%@'\", value];\n                }\n                NSString *newValue = [NSString stringWithFormat:@\"%@=%@\", title, value];\n                [newValues addObject:newValue];\n            }\n        }];\n    }\n    \n    NSString *newValuesStr = [newValues componentsJoinedByString:@\",\"];\n    [sql appendString:newValuesStr];\n    NSString *condition = [NSString stringWithFormat:@\" WHERE %@=%@;\", pk, pkValue];\n    [sql appendString:condition];\n\n    BOOL sus = [db executeUpdate:sql];;\n    \n    [db close];\n    \n    if (sus) {\n        GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:[self getCode:200 data:nil]];\n        [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n        return response;\n    } else {\n        return [self responseWhenFailed];\n    }\n}\n\n- (GCDWebServerResponse *)getTableData:(GCDWebServerRequest *)request {\n    NSDictionary *query = request.query;\n    NSString *dirPath = query[@\"dirPath\"];\n    if ([dirPath hasPrefix:@\"/root\"]) {\n        dirPath = [dirPath substringFromIndex:5];\n    }\n    NSString *fileName = query[@\"fileName\"];\n    NSString *tableName = query[@\"tableName\"];\n    NSString *rootPath = NSHomeDirectory();\n    NSString *targetPath = [NSString stringWithFormat:@\"%@%@%@\", rootPath, dirPath, fileName];\n\n    if (![_fm fileExistsAtPath:targetPath]) {\n        return [self responseWhenFailed];\n    }\n    \n    FMDatabase *db = [FMDatabase databaseWithPath:targetPath];\n    if (![db open]) {\n        return [self responseWhenFailed];\n    }\n    \n    NSMutableArray *fieldInfo = @[].mutableCopy;\n    NSMutableArray *rows = @[].mutableCopy;\n    FMResultSet *tableInfo = [db executeQuery:[NSString stringWithFormat:@\"PRAGMA table_info(%@)\", tableName]];\n    while ([tableInfo next]) {\n        NSString *title = [tableInfo stringForColumn:@\"name\"];\n        BOOL isPrimary = [tableInfo boolForColumn:@\"pk\"];\n        if (title) {\n            [fieldInfo addObject:@{@\"title\" : title,\n                                   @\"isPrimary\" : @(isPrimary)}];\n        }\n    }\n    \n    FMResultSet *set = [db executeQuery:[NSString stringWithFormat:@\"SELECT * FROM %@;\", tableName]];\n    while ([set next]) {\n        NSDictionary *row = [NSDictionary dictionaryWithDictionary:set.resultDictionary];\n        [rows addObject:row];\n    }\n    \n    [db close];\n    \n    NSDictionary *res = [self getCode:200 data:@{@\"fieldInfo\" : fieldInfo,\n                                                 @\"rows\" : rows}];\n    \n    GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:res];\n    [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n    \n    return response;\n}\n\n- (GCDWebServerResponse *)insertRow:(GCDWebServerDataRequest *)request {\n    NSDictionary *data = [NSJSONSerialization JSONObjectWithData:request.data options:0 error:nil];\n    NSString *dirPath = data[@\"dirPath\"];\n    if ([dirPath hasPrefix:@\"/root\"]) {\n        dirPath = [dirPath substringFromIndex:5];\n    }\n    NSString *fileName = data[@\"fileName\"];\n    NSString *tableName = data[@\"tableName\"];\n    NSArray *rowDatas = data[@\"rowDatas\"];\n    NSString *rootPath = NSHomeDirectory();\n    NSString *targetPath = [NSString stringWithFormat:@\"%@%@%@\", rootPath, dirPath, fileName];\n    \n    if (![_fm fileExistsAtPath:targetPath]) {\n        return [self responseWhenFailed];\n    }\n    \n    FMDatabase *db = [FMDatabase databaseWithPath:targetPath];\n    if (![db open]) {\n        return [self responseWhenFailed];\n    }\n    \n    //获取列名\n    NSMutableArray *colArr = @[].mutableCopy;\n    FMResultSet *tableInfo = [db executeQuery:[NSString stringWithFormat:@\"PRAGMA table_info(%@)\", tableName]];\n    while ([tableInfo next]) {\n        NSString *colName = [tableInfo stringForColumn:@\"name\"];\n        if (colName) {\n            [colArr addObject:colName];\n        }\n    }\n    \n    NSString *columns = [colArr componentsJoinedByString:@\",\"];\n    \n    BOOL sus = NO;\n    \n    if (rowDatas.count) {\n        NSMutableString *sql = [NSString stringWithFormat:@\"INSERT INTO %@(%@) VALUES \", tableName, columns].mutableCopy;\n        NSMutableArray *allValues = @[].mutableCopy;\n        @autoreleasepool {\n            /**\n             构造sql\n             INSERT INTO\n             tableName(key_1,key_2,key_3)\n             VALUES\n             (value_1,value_2,value_3),\n             */\n            [rowDatas enumerateObjectsUsingBlock:^(NSDictionary *  _Nonnull data, NSUInteger idx, BOOL * _Nonnull stop) {\n                id value = data[@\"value\"] ? : @\"NULL\";\n                if ([value isKindOfClass:[NSString class]] && ![value isEqualToString:@\"NULL\"]) {\n                    value = [NSString stringWithFormat:@\"'%@'\", value];\n                }\n                [allValues addObject:value];\n            }];\n        }\n        \n        NSString *allValuesStr = [NSString stringWithFormat:@\"(%@)\", [allValues componentsJoinedByString:@\",\"]];\n        [sql appendString:allValuesStr];\n        [sql appendString:@\";\"];\n        \n        sus = [db executeUpdate:sql];\n    }\n    \n    [db close];\n    \n    if (sus) {\n        GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:[self getCode:200 data:nil]];\n        [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n        return response;\n    } else {\n        return [self responseWhenFailed];\n    }\n}\n\n- (GCDWebServerResponse *)getAllTable:(GCDWebServerRequest *)request {\n    NSDictionary *query = request.query;\n    NSString *dirPath = query[@\"dirPath\"];\n    if ([dirPath hasPrefix:@\"/root\"]) {\n        dirPath = [dirPath substringFromIndex:5];\n    }\n    NSString *fileName = query[@\"fileName\"];\n    NSString *rootPath = NSHomeDirectory();\n    NSString *targetPath = [NSString stringWithFormat:@\"%@%@%@\", rootPath, dirPath, fileName];\n\n    if (![_fm fileExistsAtPath:targetPath]) {\n        return [self responseWhenFailed];\n    }\n    \n    FMDatabase *db = [FMDatabase databaseWithPath:targetPath];\n    if (![db open]) {\n        return [self responseWhenFailed];\n    }\n    \n    NSString *sql = @\"SELECT * FROM sqlite_master WHERE type='table' ORDER BY name;\";\n    FMResultSet *set = [db executeQuery:sql];\n    NSMutableArray *data = @[].mutableCopy;\n    while ([set next]) {\n        NSString *name = [set stringForColumnIndex:1];\n        [data addObject:name];\n    }\n    \n    [db close];\n    \n    NSMutableDictionary *info = [[NSMutableDictionary alloc] init];\n    [info setValue:@(200) forKey:@\"code\"];\n    [info setValue:data forKey:@\"data\"];\n\n    GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:info];\n    [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n    \n    return response;\n}\n\n- (GCDWebServerResponse *)saveFile:(GCDWebServerDataRequest *)request {\n    NSDictionary *data = [NSJSONSerialization JSONObjectWithData:request.data options:0 error:nil];\n    NSString *dirPath = data[@\"dirPath\"];\n    if ([dirPath hasPrefix:@\"/root\"]) {\n        dirPath = [dirPath substringFromIndex:5];\n    }\n    NSString *fileName = data[@\"fileName\"];\n    NSString *content = data[@\"content\"];\n    NSString *rootPath = NSHomeDirectory();\n    NSString *targetPath = [NSString stringWithFormat:@\"%@%@%@\", rootPath, dirPath, fileName];\n\n    NSDictionary *res;\n    if (![_fm createFileAtPath:targetPath contents:[content dataUsingEncoding:NSUTF8StringEncoding] attributes:nil]) {\n        NSLog(@\"Failed save file at \\\"%@\\\"\", targetPath);\n        res = [self getCode:0 data:nil];\n    }else{\n        res = [self getCode:200 data:nil];\n    }\n    \n    GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:res];\n    [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n    \n    return response;\n}\n\n- (GCDWebServerResponse *)deleteFile:(GCDWebServerDataRequest *)request {\n    NSDictionary *data = [NSJSONSerialization JSONObjectWithData:request.data options:0 error:nil];\n    NSString *dirPath = data[@\"dirPath\"];\n    if ([dirPath hasPrefix:@\"/root\"]) {\n        dirPath = [dirPath substringFromIndex:5];\n    }\n    NSString *fileName = data[@\"fileName\"];\n    NSString *rootPath = NSHomeDirectory();\n    NSString *targetPath = [NSString stringWithFormat:@\"%@%@%@\", rootPath, dirPath, fileName];\n    NSError *error = nil;\n\n    NSDictionary *res;\n    if (![_fm removeItemAtPath:targetPath error:&error]) {\n        NSLog(@\"Failed deleting file at \\\"%@\\\"\", targetPath);\n        res = [self getCode:0 data:nil];\n    }else{\n        res = [self getCode:200 data:nil];\n    }\n    \n    GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:res];\n    [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n    \n    return response;\n}\n\n- (GCDWebServerResponse *)rename:(GCDWebServerDataRequest *)request {\n    NSDictionary *data = [NSJSONSerialization JSONObjectWithData:request.data options:0 error:nil];\n    NSString *dirPath = data[@\"dirPath\"];\n    if ([dirPath hasPrefix:@\"/root\"]) {\n        dirPath = [dirPath substringFromIndex:5];\n    }\n    NSString *oldName = data[@\"oldName\"];\n    NSString *newName = data[@\"newName\"];\n    NSString *rootPath = NSHomeDirectory();\n    NSString *targetPath = [NSString stringWithFormat:@\"%@%@%@\", rootPath, dirPath, oldName];\n    NSString *destinationPath = [NSString stringWithFormat:@\"%@%@%@\", rootPath, dirPath, newName];\n    NSError *error = nil;\n\n    NSDictionary *res;\n    if (![_fm moveItemAtPath:targetPath toPath:destinationPath error:&error]) {\n        NSLog(@\"Failed rename file at \\\"%@\\\"\", targetPath);\n        res = [self getCode:0 data:nil];\n    }else{\n        res = [self getCode:200 data:nil];\n    }\n    \n    GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:res];\n    [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n    \n    return response;\n}\n\n- (GCDWebServerResponse *)getDeviceInfo{\n    NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];\n    [dic setValue:[DoraemonAppInfoUtil iphoneName] forKey:@\"deviceName\"];\n    [dic setValue:[DoraemonAppInfoUtil uuid] forKey:@\"deviceId\"];\n    [dic setValue:[NSString stringWithFormat:@\"%@:%@\", [DoraemonAppInfoUtil getIPAddress:YES], @(DK_SERVER_PORT)] forKey:@\"deviceIp\"];\n    \n    NSDictionary *info = [self getCode:200 data:dic];\n    GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:info];\n    [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n    \n    return response;\n}\n\n- (GCDWebServerResponse *)getFileList: (GCDWebServerRequest *)request{\n    \n    NSDictionary *query = request.query;\n    NSString *dirPath = query[@\"dirPath\"];\n    NSString *realDirPath = dirPath;\n    if ([realDirPath hasPrefix:@\"/root\"]) {\n        realDirPath = [realDirPath substringFromIndex:5];\n    }\n    NSString *rootPath = NSHomeDirectory();\n    NSString *targetPath = [NSString stringWithFormat:@\"%@%@\",rootPath,realDirPath];\n    \n    NSMutableArray *files = @[].mutableCopy;\n       NSError *error = nil;\n    NSArray *paths = [_fm contentsOfDirectoryAtPath:targetPath error:&error];\n    for (NSString *path in paths) {\n        BOOL isDir = false;\n        NSString *fullPath = [targetPath stringByAppendingPathComponent:path];\n        [_fm fileExistsAtPath:fullPath isDirectory:&isDir];\n        \n        NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];\n        dic[@\"dirPath\"] = dirPath;\n        dic[@\"isRootPath\"] = [path isEqualToString:rootPath] ? @YES : @NO;\n        dic[@\"fileName\"] = path;\n//        dic[@\"fileUrl\"] = [self getRelativeFilePath:fullPath];\n        if (isDir) {\n            dic[@\"fileType\"] = @\"folder\";\n        }else{\n            dic[@\"fileType\"] = [path pathExtension];\n        }\n        \n        NSDictionary *fileAttributes = [_fm attributesOfItemAtPath:fullPath error:nil];\n        NSDate *modifyDate = [fileAttributes objectForKey:NSFileModificationDate];\n        dic[@\"modifyTime\"] = @([modifyDate timeIntervalSince1970]*1000);\n        [files addObject:dic];\n    }\n    \n    NSMutableDictionary *data = [[NSMutableDictionary alloc] init];\n    [data setValue:dirPath forKey:@\"dirPath\"];\n    [data setValue:[DoraemonAppInfoUtil uuid] forKey:@\"deviceId\"];\n    [data setValue:files forKey:@\"fileList\"];\n    \n    NSDictionary *res = [self getCode:200 data:data];\n    \n    GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:res];\n    [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n    \n    return response;\n}\n\n- (GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request{\n    GCDWebServerMultiPartFile *file = [request firstFileForControlName:@\"file\"];\n    NSString *dirPath = [[request firstArgumentForControlName:@\"dirPath\"] string];\n    if ([dirPath hasPrefix:@\"/root\"]) {\n        dirPath = [dirPath substringFromIndex:5];\n    }\n    NSString *rootPath = NSHomeDirectory();\n    NSString *targetPath = [NSString stringWithFormat:@\"%@%@%@\", rootPath, dirPath, file.fileName];\n    NSError *error = nil;\n    \n    if (![_fm fileExistsAtPath:[NSString stringWithFormat:@\"%@%@\", rootPath, dirPath]]) {\n        [_fm createDirectoryAtPath:[NSString stringWithFormat:@\"%@%@\", rootPath, dirPath] withIntermediateDirectories:YES attributes:nil error:nil];\n    }\n    \n    NSDictionary *res;\n    if (![_fm moveItemAtPath:file.temporaryPath toPath:targetPath error:&error]) {\n        NSLog(@\"Failed moving uploaded file to \\\"%@\\\"\", targetPath);\n        res = [self getCode:0 data:nil];\n    }else{\n        res = [self getCode:200 data:nil];\n    }\n    \n    GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:res];\n    [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n    \n    return response;\n}\n\n- (GCDWebServerResponse *)downloadFile:(GCDWebServerRequest *)request{\n    NSString *rootPath = NSHomeDirectory();\n    NSString *dirPath = [[request query] objectForKey:@\"dirPath\"];\n    if ([dirPath hasPrefix:@\"/root\"]) {\n        dirPath = [dirPath substringFromIndex:5];\n    }\n    NSString *fileName = [[request query] objectForKey:@\"fileName\"];\n    NSString *targetPath = [NSString stringWithFormat:@\"%@%@%@\",rootPath,dirPath,fileName];\n    \n    NSDictionary *res;\n    BOOL isDirectory = NO;\n    if (![_fm fileExistsAtPath:targetPath isDirectory:&isDirectory]) {\n        NSLog(@\"\\\"%@\\\" does not exist\", targetPath);\n        res = [self getCode:0 data:nil];\n        GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:res];\n        [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n        return response;\n    } else {\n        GCDWebServerFileResponse *response = [GCDWebServerFileResponse responseWithFile:targetPath isAttachment:YES];\n        [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n        return response;\n    }\n}\n\n- (GCDWebServerResponse *)createFolder:(GCDWebServerDataRequest *)request{\n    NSDictionary *data = [NSJSONSerialization JSONObjectWithData:request.data options:0 error:nil];\n    NSString *dirPath = data[@\"dirPath\"];\n    if ([dirPath hasPrefix:@\"/root\"]) {\n        dirPath = [dirPath substringFromIndex:5];\n    }\n    NSString *fileName = data[@\"fileName\"];\n    NSString *rootPath = NSHomeDirectory();\n    NSString *targetPath = [NSString stringWithFormat:@\"%@%@%@\",rootPath,dirPath,fileName];\n    NSError *error;\n    \n    NSDictionary *res;\n    if (![[NSFileManager defaultManager] createDirectoryAtPath:targetPath withIntermediateDirectories:YES attributes:nil error:&error]) {\n        NSLog(@\"Failed creating directory \\\"%@\\\"\", targetPath);\n        res = [self getCode:0 data:nil];\n    } else {\n        res = [self getCode:200 data:nil];\n    }\n    \n    GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:res];\n    [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n    return response;\n}\n\n- (GCDWebServerResponse *)getFileDetail:(GCDWebServerRequest *)request{\n    NSString *rootPath = NSHomeDirectory();\n    NSString *dirPath = [[request query] objectForKey:@\"dirPath\"];\n    if ([dirPath hasPrefix:@\"/root\"]) {\n        dirPath = [dirPath substringFromIndex:5];\n    }\n    NSString *fileName = [[request query] objectForKey:@\"fileName\"];\n    NSString *targetPath = [NSString stringWithFormat:@\"%@%@%@\",rootPath,dirPath,fileName];\n    \n    NSDictionary *res;\n    if ([_fm fileExistsAtPath:targetPath]) {\n        NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];\n        NSString *fileContent = [[NSString alloc] initWithData:[_fm contentsAtPath:targetPath] encoding:NSUTF8StringEncoding];\n        [dic setValue:targetPath.pathExtension forKey:@\"fileType\"];\n        [dic setValue:fileContent forKey:@\"fileContent\"];\n        res = [self getCode:200 data:dic];\n    } else {\n        NSLog(@\"File not founded at \\\"%@\\\"\", targetPath);\n        res = [self getCode:0 data:nil];\n    }\n\n    GCDWebServerResponse *response = [GCDWebServerDataResponse responseWithJSONObject:res];\n    [response setValue:@\"*\" forAdditionalHeader:@\"Access-Control-Allow-Origin\"];\n    return response;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/Alert/DoraemonHealthAlertView.h",
    "content": "//\n//  DoraemonHealthAlertView.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/8.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\ntypedef void (^DoraemonHealthAlertOKActionBlock)(void);\ntypedef void (^DoraemonHealthAlertCancleActionBlock)(void);\ntypedef void (^DoraemonHealthAlertQuitActionBlock)(void);\n\n@interface DoraemonHealthAlertView : UIView\n\n- (void)renderUI:(NSString *)title placeholder:(NSArray*)placeholders inputTip:(NSArray*)inputTips ok:(NSString *)okText quit:(NSString *)quitText cancle:(NSString *)cancleText  okBlock:(DoraemonHealthAlertOKActionBlock)okBlock quitBlock:(DoraemonHealthAlertQuitActionBlock) quitBlock\ncancleBlock:(DoraemonHealthAlertCancleActionBlock)cancleBlock ;\n\n- (NSArray *)getInputText;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/Alert/DoraemonHealthAlertView.m",
    "content": "//\n//  DoraemonHealthAlertView.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/8.\n//\n\n#import \"DoraemonHealthAlertView.h\"\n#import \"DoraemonToastUtil.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonHealthEndInputView.h\"\n\n@interface DoraemonHealthAlertView()<UITextFieldDelegate>\n\n@property (nonatomic, assign) CGFloat width;\n@property (nonatomic, assign) CGFloat height;\n@property (nonatomic, assign) CGFloat padding;\n@property (nonatomic, strong) UILabel *title;\n@property (nonatomic, strong) UIView *alertView;\n@property (nonatomic, strong) NSMutableArray *inputViewArray;\n@property (nonatomic, strong) UIButton *okBtn;\n@property (nonatomic, strong) UIButton *cancleBtn;\n@property (nonatomic, strong) UIButton *quitBtn;\n@property (nonatomic, copy) DoraemonHealthAlertOKActionBlock okBlock;\n@property (nonatomic, copy) DoraemonHealthAlertCancleActionBlock cancleBlock;\n@property (nonatomic, copy) DoraemonHealthAlertQuitActionBlock quitBlock;\n\n@end\n\n@implementation DoraemonHealthAlertView\n\n- (instancetype)init{\n    self = [super initWithFrame:[UIScreen mainScreen].bounds];\n    if (self) {\n        self.backgroundColor = [UIColor colorWithWhite:0.2 alpha:0.6];\n        self.userInteractionEnabled = YES;\n        _padding = kDoraemonSizeFrom750_Landscape(134);\n        _width = self.doraemon_width - _padding*2;\n        _height = _padding;\n        _alertView = [[UIView alloc] initWithFrame:CGRectMake(_padding, self.doraemon_height/5, _width, _height)];\n        _alertView.backgroundColor = [UIColor whiteColor];\n        _alertView.layer.cornerRadius = kDoraemonSizeFrom750_Landscape(10);\n        \n        _title = [[UILabel alloc] initWithFrame:CGRectMake(0, _padding/3, _width, _padding/2)];\n        _title.textAlignment = NSTextAlignmentCenter;\n        _title.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(30)];\n        _title.textColor = [UIColor doraemon_black_1];\n        \n        _inputViewArray = [[NSMutableArray alloc] init];\n\n        _okBtn = [[UIButton alloc] initWithFrame:CGRectMake(_width/3*2, 0, _width/3, kDoraemonSizeFrom750_Landscape(90))];\n        _okBtn.titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(30)];\n        [_okBtn.layer setBorderColor:[UIColor doraemon_black_3].CGColor];\n        [_okBtn.layer setBorderWidth:kDoraemonSizeFrom750_Landscape(0.5)];\n        [_okBtn.layer setMasksToBounds:YES];\n        [_okBtn setTitleColor:[UIColor doraemon_black_3] forState:UIControlStateNormal];\n        [_okBtn setTitle:DoraemonLocalizedString(@\"确定\") forState:UIControlStateNormal];\n        _okBtn.enabled = NO;\n        [_okBtn addTarget:self action:@selector(okBtnAction:) forControlEvents:UIControlEventTouchUpInside];\n        \n        _quitBtn = [[UIButton alloc] initWithFrame:CGRectMake(_width/3, 0, _width/3, kDoraemonSizeFrom750_Landscape(90))];\n        _quitBtn.titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(30)];\n        [_quitBtn.layer setBorderColor:[UIColor doraemon_black_3].CGColor];\n        [_quitBtn.layer setBorderWidth:kDoraemonSizeFrom750_Landscape(0.5)];\n        [_quitBtn.layer setMasksToBounds:YES];\n        [_quitBtn setTitleColor:[UIColor doraemon_black_3] forState:UIControlStateNormal];\n        [_quitBtn setTitle:DoraemonLocalizedString(@\"丢弃\") forState:UIControlStateNormal];\n        [_quitBtn addTarget:self action:@selector(quitBtnAction:) forControlEvents:UIControlEventTouchUpInside];\n        \n        _cancleBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, _width/3, _okBtn.doraemon_height)];\n        _cancleBtn.titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(30)];\n        [_cancleBtn.layer setBorderColor:[UIColor doraemon_black_3].CGColor];\n        [_cancleBtn.layer setBorderWidth:kDoraemonSizeFrom750_Landscape(0.5)];\n        [_cancleBtn.layer setMasksToBounds:YES];\n        [_cancleBtn setTitleColor:[UIColor doraemon_black_3] forState:UIControlStateNormal];\n        [_cancleBtn setTitle:DoraemonLocalizedString(@\"取消\") forState:UIControlStateNormal];\n        [_cancleBtn addTarget:self action:@selector(cancleBtnAction:) forControlEvents:UIControlEventTouchUpInside];\n        \n        [self addSubview:_alertView];\n        \n        [_alertView addSubview:_title];\n        [_alertView addSubview:_okBtn];\n        [_alertView addSubview:_quitBtn];\n        [_alertView addSubview:_cancleBtn];\n        \n    }\n    return self;\n}\n\n- (void)renderUI:(NSString *)title placeholder:(NSArray*)placeholders inputTip:(NSArray*)inputTips ok:(NSString *)okText quit:(NSString *)quitText cancle:(NSString *)cancleText  okBlock:(DoraemonHealthAlertOKActionBlock)okBlock quitBlock:(DoraemonHealthAlertQuitActionBlock) quitBlock\ncancleBlock:(DoraemonHealthAlertCancleActionBlock)cancleBlock{\n    int index = 0;\n    _title.text = title;\n    NSString *placeholder = nil;\n    for (NSString* inputTip in inputTips) {\n        DoraemonHealthEndInputView *inputView = [[DoraemonHealthEndInputView alloc] initWithFrame:CGRectMake(0, _height, _width, _padding)];\n        if(index < placeholders.count){\n            placeholder = placeholders[index];\n        }else{\n            placeholder = @\"\";\n        }\n        [inputView renderUIWithTitle:inputTip placeholder:placeholder];\n        inputView.textField.delegate = self;\n        [_alertView addSubview:inputView];\n        [_inputViewArray addObject:inputView];\n        _height += _padding + kDoraemonSizeFrom750_Landscape(10);\n        index++;\n    }\n    _height += kDoraemonSizeFrom750_Landscape(38);\n    _okBtn.frame = CGRectMake(_okBtn.doraemon_x, _height, _okBtn.doraemon_width, _okBtn.doraemon_height);\n    if(okText.length>0){\n        [_okBtn setTitle:okText forState:UIControlStateNormal];\n    }\n    _quitBtn.frame = CGRectMake(_quitBtn.doraemon_x, _height, _quitBtn.doraemon_width, _quitBtn.doraemon_height);\n    if (quitText.length>0) {\n        [_quitBtn setTitle:quitText forState:UIControlStateNormal];\n    }\n    _cancleBtn.frame = CGRectMake(_cancleBtn.doraemon_x, _height, _cancleBtn.doraemon_width, _cancleBtn.doraemon_height);\n    if(cancleText.length>0){\n        [_cancleBtn setTitle:cancleText forState:UIControlStateNormal];\n    }\n    _height += _okBtn.doraemon_height;\n    _alertView.frame = CGRectMake(_padding, _alertView.doraemon_y, _width, _height);\n    self.okBlock = okBlock;\n    self.cancleBlock = cancleBlock;\n    self.quitBlock = quitBlock;\n}\n\n- (NSArray *)getInputText{\n    NSMutableArray *array = [[NSMutableArray alloc] init];\n    for (DoraemonHealthEndInputView *inputView in _inputViewArray) {\n        [array addObject:inputView.textField.text];\n    }\n    return array;\n}\n\n- (void)cancleBtnAction:(id)sender{\n    self.cancleBlock ? self.cancleBlock() : nil;\n    self.hidden = YES;\n}\n\n- (void)okBtnAction:(id)sender{\n    self.okBlock ? self.okBlock() : nil;\n    self.hidden = YES;\n}\n\n- (void)quitBtnAction:(id)sender{\n    self.quitBlock ? self.quitBlock() : nil;\n    self.hidden = YES;\n}\n\n- (void)textFieldDidEndEditing:(UITextField *)textField{\n    BOOL enabled = YES;\n    for (DoraemonHealthEndInputView *inputView in _inputViewArray) {\n        if(inputView.textField.text.length <= 0){\n            enabled = NO;\n            break;\n        }\n    }\n    _okBtn.enabled = enabled;\n    if(enabled){\n        [_okBtn setTitleColor:[UIColor doraemon_black_1] forState:UIControlStateNormal];\n    }else{\n        [_okBtn setTitleColor:[UIColor doraemon_black_3] forState:UIControlStateNormal];\n    }\n}\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/Alert/DoraemonHealthEndInputView.h",
    "content": "//\n//  DoraemonHealthEndInputView.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/8.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHealthEndInputView : UIView\n\n@property (nonatomic, strong) UILabel *label;\n@property (nonatomic, strong) UITextField *textField;\n\n- (void)renderUIWithTitle:(NSString *)tip placeholder:(NSString *)placeholder;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/Alert/DoraemonHealthEndInputView.m",
    "content": "//\n//  DoraemonHealthEndInputView.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/8.\n//\n\n#import \"DoraemonHealthEndInputView.h\"\n#import \"DoraemonDefine.h\"\n\n@implementation DoraemonHealthEndInputView\n\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        CGFloat padding = kDoraemonSizeFrom750_Landscape(40);\n        _label = [[UILabel alloc] initWithFrame:CGRectMake(padding, 0, self.doraemon_width - padding*2, padding + kDoraemonSizeFrom750_Landscape(5))];\n        _label.textAlignment = NSTextAlignmentLeft;\n        _label.textColor = [UIColor doraemon_black_3];\n        _label.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n        \n        _textField = [[UITextField alloc] initWithFrame:CGRectMake(padding, _label.doraemon_bottom, self.doraemon_width- padding*2, padding*2)];\n        _textField.textAlignment = NSTextAlignmentLeft;\n        _textField.font = _label.font;\n        _textField.layer.borderColor = [UIColor doraemon_black_1].CGColor;\n        _textField.borderStyle = UITextBorderStyleRoundedRect;\n        _textField.layer.cornerRadius = kDoraemonSizeFrom750_Landscape(8);\n        \n        [self addSubview:_label];\n        [self addSubview:_textField];\n    }\n    return self;\n}\n\n- (void)renderUIWithTitle:(NSString *)tip placeholder:(NSString *)placeholder{\n    _label.text = tip;\n    _textField.placeholder = placeholder;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/DoraemonHealthPlugin.h",
    "content": "//\n//  DoraemonHealthPlugin.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/30.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHealthPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/DoraemonHealthPlugin.m",
    "content": "//\n//  DoraemonHealthPlugin.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/30.\n//\n\n#import \"DoraemonHealthPlugin.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonHealthViewController.h\"\n#import \"DoraemonManager.h\"\n#import \"DoraemonToastUtil.h\"\n#import \"UIViewController+Doraemon.h\"\n#import \"Doraemoni18NUtil.h\"\n\n@implementation DoraemonHealthPlugin\n\n- (void)pluginDidLoad{\n    if ([DoraemonManager shareInstance].pId) {\n        DoraemonHealthViewController *vc = [[DoraemonHealthViewController alloc] init];\n        [DoraemonHomeWindow openPlugin:vc];\n    }else{\n        [DoraemonToastUtil showToastBlack:DoraemonLocalizedString(@\"需要到www.dokit.cn上注册pId才能使用该功能\") inView:[UIViewController rootViewControllerForDoraemonHomeWindow].view];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/DoraemonHealthViewController.h",
    "content": "//\n//  DoraemonHealthViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/30.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHealthViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/DoraemonHealthViewController.m",
    "content": "//\n//  DoraemonHealthViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/30.\n//\n\n#import \"DoraemonHealthViewController.h\"\n#import \"DoraemonHealthHomeView.h\"\n#import \"DoraemonHealthFooterView.h\"\n#import \"DoraemonHealthManager.h\"\n#import \"DoraemonHealthAlertView.h\"\n#import \"DoraemonHealthInstructionsView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHealthViewController()<UIScrollViewDelegate,DoraemonHealthFooterButtonDelegate>\n\n@property (nonatomic, strong) UIScrollView *scrollView;\n@property (nonatomic, strong) DoraemonHealthHomeView *homeView;\n@property (nonatomic, strong) DoraemonHealthInstructionsView *instructionsView;\n@property (nonatomic, strong) DoraemonHealthFooterView *footerView;\n\n@end\n\n@implementation DoraemonHealthViewController \n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"健康体检\");\n    CGFloat offset_y = self.bigTitleView.doraemon_bottom;\n    _homeView = [[DoraemonHealthHomeView alloc] initWithFrame:CGRectMake(0, 0, self.view.doraemon_width, self.view.doraemon_height - offset_y)];\n    \n    _instructionsView = [[DoraemonHealthInstructionsView alloc] initWithFrame:CGRectMake(0, _homeView.doraemon_bottom, self.view.doraemon_width, self.view.doraemon_height - offset_y)];\n    \n    _scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, offset_y, self.view.doraemon_width, self.view.doraemon_height - offset_y)];\n    _scrollView.backgroundColor = [UIColor whiteColor];\n    _scrollView.delegate = self;\n    _scrollView.contentSize = CGSizeMake(_homeView.doraemon_width, _homeView.doraemon_height*2);//设置大小\n    _scrollView.pagingEnabled = YES;\n    \n    CGFloat footerHeight = kDoraemonSizeFrom750_Landscape(145);\n    CGFloat footerTop = self.view.doraemon_height - footerHeight;\n    if (@available(iOS 11.0, *)) {\n        footerTop -= self.view.safeAreaInsets.bottom;\n    }\n    _footerView = [[DoraemonHealthFooterView alloc] initWithFrame:CGRectMake(0, footerTop, self.view.doraemon_width, footerHeight)];\n    _footerView.delegate = self;\n    [_footerView renderUIWithTitleImg:YES];\n    \n    \n    __weak typeof(self) weakSelf = self;\n    [_homeView addBlock:^{\n        BOOL currentStatus = [DoraemonHealthManager sharedInstance].start;\n        if (currentStatus) {\n            [weakSelf showEndAlert:YES];\n        }else{\n            [DoraemonAlertUtil handleAlertActionWithVC:weakSelf text:@\"是否重启App开启健康体检\" okBlock:^{\n                [[DoraemonHealthManager sharedInstance] rebootAppForHealthCheck];\n            } cancleBlock:^{}];\n            \n        }\n    }];\n    \n    [_scrollView addSubview:_homeView];\n    [_scrollView addSubview:_instructionsView];\n    [self.view addSubview:_scrollView];\n    [self.view addSubview:_footerView];\n    \n    [self showFooter:![DoraemonHealthManager sharedInstance].start];\n    \n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n- (void)showFooter:(BOOL)show{\n    _footerView.hidden = !show;\n    _scrollView.scrollEnabled = show;\n}\n\n- (void)showEndAlert:(BOOL)show{\n    if(show){\n        __weak typeof(self) weakSelf = self;\n        DoraemonHealthAlertView *alertView = [[DoraemonHealthAlertView alloc] init];\n        [alertView renderUI:DoraemonLocalizedString(@\"结束前请完善下列信息\") placeholder:@[] inputTip:@[DoraemonLocalizedString(@\"测试用例名称\"),DoraemonLocalizedString(@\"测试人名称\")] ok:DoraemonLocalizedString(@\"提交\") quit:DoraemonLocalizedString(@\"丢弃\") cancle:DoraemonLocalizedString(@\"取消\") okBlock:^{\n            \n            NSArray *result = [alertView getInputText];\n            if (result.count == 2) {\n                [DoraemonHealthManager sharedInstance].caseName = result[0];\n                [DoraemonHealthManager sharedInstance].testPerson = result[1];\n                [weakSelf showFooter:YES];\n                [weakSelf.homeView.btnView statusForBtn:NO];\n                [weakSelf.homeView.startingTitle renderUIWithTitle:DoraemonLocalizedString(@\"点击开始检测\")];\n                [[DoraemonHealthManager sharedInstance] stopHealthCheck];\n            }\n        } quitBlock:^{\n            [DoraemonHealthManager sharedInstance].caseName = @\"\";\n            [DoraemonHealthManager sharedInstance].testPerson = @\"\";\n            [weakSelf showFooter:YES];\n            [weakSelf.homeView.btnView statusForBtn:NO];\n            [weakSelf.homeView.startingTitle renderUIWithTitle:DoraemonLocalizedString(@\"点击开始检测\")];\n            [[DoraemonHealthManager sharedInstance] stopHealthCheck];\n        } cancleBlock:^{\n        }];\n        [self.view addSubview:alertView];\n    }\n}\n\n\n#pragma mark - DoraemonHealthFooterButtonDelegate\n- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {\n    BOOL scrollToScrollStop = !scrollView.tracking && !scrollView.dragging && !scrollView.decelerating;\n    if (scrollToScrollStop) {\n        [_footerView renderUIWithTitleImg: scrollView.contentOffset.y <= 0];\n        _footerView.hidden = NO;\n    }\n}\n\n- (void)scrollViewDidScroll:(UIScrollView *)scrollView{\n    _footerView.hidden = YES;\n}\n\n#pragma mark - DoraemonHealthFooterButtonDelegate\n- (void)footerBtnClick:(id)sender{\n    if(_footerView.top){\n        _scrollView.contentOffset = CGPointMake(0, _scrollView.doraemon_height);\n    }else{\n        _scrollView.contentOffset = CGPointMake(0, 0);\n    }\n    [_footerView renderUIWithTitleImg:!_footerView.top];\n    _footerView.hidden = NO;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/Function/DoraemonHealthManager.h",
    "content": "//\n//  DoraemonHealthManager.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/2.\n//\n#import <UIKit/UIKit.h>\n#import <Foundation/Foundation.h>\n#import \"DoraemonNetFlowHttpModel.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHealthManager : NSObject\n\n+ (instancetype)sharedInstance;\n\n@property (nonatomic, assign) BOOL start;\n\n- (void)rebootAppForHealthCheck;\n\n- (void)startHealthCheck;\n\n- (void)stopHealthCheck;\n\n- (void)startEnterPage:(Class)vcClass;\n\n- (void)enterPage:(Class)vcClass;\n\n- (void)leavePage:(Class)vcClass;\n\n- (void)addHttpModel:(DoraemonNetFlowHttpModel *)httpModel;\n\n- (void)addANRInfo:(NSDictionary *)anrInfo;\n\n- (void)addSubThreadUI:(NSDictionary *)info;\n\n- (void)addUILevel:(NSDictionary *)info;\n\n- (void)addLeak:(NSDictionary *)info;\n\n- (void)openH5Page:(NSString *)h5Url;\n\n//检测结果\n@property (nonatomic, assign) CGFloat startTime;//本次启动时间 单位ms\n@property (nonatomic, copy) NSString *costDetail;//启动流程耗时详情\n@property (nonatomic, copy) NSString *caseName;//用例名称\n@property (nonatomic, copy) NSString *testPerson;//测试人名称\n\n\n- (BOOL)blackList:(Class)vcClass;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/Function/DoraemonHealthManager.m",
    "content": "//\n//  DoraemonHealthManager.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/2.\n//\n\n#import \"DoraemonHealthManager.h\"\n#import \"DoraemonFPSUtil.h\"\n#import \"DoraemonCPUUtil.h\"\n#import \"DoraemonMemoryUtil.h\"\n#import \"DoraemonNetworkUtil.h\"\n#import \"DoraemonUtil.h\"\n#import \"DoraemonAppInfoUtil.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonManager.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonANRManager.h\"\n#import \"UIViewController+Doraemon.h\"\n#import \"DoraemonUIProfileManager.h\"\n#import <UIKit/UIKit.h>\n#import \"DoraemonUtil.h\"\n#import \"DoraemonHealthCountdownWindow.h\"\n#import \"DoraemonBaseViewController.h\"\n#import \"DoraemonToastUtil.h\"\n\n#if __has_include(\"DoraemonMethodUseTimeManager.h\")\n#import \"DoraemonMethodUseTimeManager.h\"\n#endif\n\n\n\n@interface DoraemonHealthManager()\n//每秒运行一次\n@property (nonatomic, strong) NSTimer *secondTimer;\n@property (nonatomic, strong) DoraemonFPSUtil *fpsUtil;\n@property (nonatomic, assign) BOOL firstEnter;\n\n@property (nonatomic, strong) NSMutableArray *cpuPageArray;\n@property (nonatomic, strong) NSMutableArray *cpuArray;\n@property (nonatomic, strong) NSMutableArray *memoryPageArray;\n@property (nonatomic, strong) NSMutableArray *memoryArray;\n@property (nonatomic, strong) NSMutableArray *fpsPageArray;\n@property (nonatomic, strong) NSMutableArray *fpsArray;\n@property (nonatomic, strong) NSMutableArray *networkPageArray;\n@property (nonatomic, strong) NSMutableArray *networkArray;\n@property (nonatomic, strong) NSMutableArray *blockArray;\n@property (nonatomic, strong) NSMutableArray *subThreadUIArray;\n@property (nonatomic, strong) NSMutableArray *uiLevelArray;\n@property (nonatomic, strong) NSMutableArray *leakArray;\n@property (nonatomic, strong) NSMutableDictionary *pageEnterMap;\n@property (nonatomic, strong) NSMutableArray *pageLoadArray;\n@property (nonatomic, strong) NSMutableArray *bigFileArray;\n@property (nonatomic, copy) NSString *h5UrlString;\n\n@end\n\n@implementation DoraemonHealthManager{\n    dispatch_semaphore_t semaphore;\n}\n\n+ (instancetype)sharedInstance {\n    static id instance = nil;\n    static dispatch_once_t onceToken;\n    \n    dispatch_once(&onceToken, ^{\n        instance = [[self alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    self = [super init];\n    if (self) {\n        _start = [[DoraemonCacheManager sharedInstance] healthStart];\n        _cpuPageArray = [[NSMutableArray alloc] init];\n        _cpuArray = [[NSMutableArray alloc] init];\n        _memoryPageArray = [[NSMutableArray alloc] init];\n        _memoryArray = [[NSMutableArray alloc] init];\n        _fpsPageArray = [[NSMutableArray alloc] init];\n        _fpsArray = [[NSMutableArray alloc] init];\n        _networkPageArray = [[NSMutableArray alloc] init];\n        _networkArray = [[NSMutableArray alloc] init];\n        _blockArray = [[NSMutableArray alloc] init];\n        _subThreadUIArray = [[NSMutableArray alloc] init];\n        _uiLevelArray = [[NSMutableArray alloc] init];\n        _leakArray = [[NSMutableArray alloc] init];\n        _pageEnterMap = [[NSMutableDictionary alloc] init];\n        _pageLoadArray = [[NSMutableArray alloc] init];\n        _bigFileArray = [[NSMutableArray alloc] init];\n        semaphore = dispatch_semaphore_create(1);\n    }\n    return self;\n}\n\n- (void)rebootAppForHealthCheck{\n    [[DoraemonCacheManager sharedInstance] saveHealthStart:YES];\n    [[DoraemonCacheManager sharedInstance] saveStartTimeSwitch:YES];\n    #if __has_include(\"DoraemonMethodUseTimeManager.h\")\n    [DoraemonMethodUseTimeManager sharedInstance].on = YES;\n    #endif\n    [[DoraemonCacheManager sharedInstance] saveNetFlowSwitch:YES];\n    [[DoraemonCacheManager sharedInstance] saveSubThreadUICheckSwitch:YES];\n    [[DoraemonCacheManager sharedInstance] saveMemoryLeak:YES];\n    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{\n        exit(0);\n    });\n    \n}\n\n- (void)startHealthCheck{\n    _start = YES;\n    if (_start) {\n        if(!_secondTimer){\n            _secondTimer = [NSTimer timerWithTimeInterval:0.5f target:self selector:@selector(doSecondFunction) userInfo:nil repeats:YES];\n            [[NSRunLoop currentRunLoop] addTimer:_secondTimer forMode:NSRunLoopCommonModes];\n            if (!_fpsUtil) {\n                _fpsUtil = [[DoraemonFPSUtil alloc] init];\n                __weak typeof(self) weakSelf = self;\n                [_fpsUtil addFPSBlock:^(NSInteger fps) {\n                    [weakSelf handleFPS:fps];\n                    \n                }];\n            }\n            [_fpsUtil start];\n        }\n        [[DoraemonANRManager sharedInstance] start];\n        [DoraemonUIProfileManager sharedInstance].enable = YES;\n    }\n}\n\n- (void)stopHealthCheck{\n    _start = NO;\n    [[DoraemonHealthCountdownWindow shareInstance] hide];\n    [[DoraemonCacheManager sharedInstance] saveHealthStart:NO];\n    [[DoraemonCacheManager sharedInstance] saveStartTimeSwitch:NO];\n    #if __has_include(\"DoraemonMethodUseTimeManager.h\")\n    [DoraemonMethodUseTimeManager sharedInstance].on = NO;\n    #endif\n    [[DoraemonCacheManager sharedInstance] saveNetFlowSwitch:NO];\n    [[DoraemonCacheManager sharedInstance] saveSubThreadUICheckSwitch:NO];\n    [[DoraemonCacheManager sharedInstance] saveMemoryLeak:NO];\n    [DoraemonUIProfileManager sharedInstance].enable = NO;\n    [[DoraemonANRManager sharedInstance] stop];\n    if(_secondTimer){\n        [_secondTimer invalidate];\n        _secondTimer = nil;\n    }\n    if (_fpsUtil) {\n        [_fpsUtil end];\n    }\n    [self upLoadData];\n}\n\n- (void)doSecondFunction{\n    //最多采样40个点\n    if(_cpuPageArray.count > 40){\n        return;\n    }\n    \n    //1、获取当前时间\n    NSString *currentTimeInterval = [DoraemonUtil currentTimeInterval];\n    \n    //2、获取当前cpu占用率\n    CGFloat cpuValue = -1;\n    cpuValue = [DoraemonCPUUtil cpuUsageForApp];\n    if (cpuValue * 100 > 100) {\n        cpuValue = 100;\n    }else{\n        cpuValue = cpuValue * 100;\n    }\n    \n    [_cpuPageArray addObject:@{\n        @\"time\":currentTimeInterval,\n        @\"value\":[NSString stringWithFormat:@\"%f\",cpuValue]//单位百分比\n    }];\n    \n    //3、获取当前memoryValue使用量\n    NSInteger memoryValue = [DoraemonMemoryUtil useMemoryForApp];//单位MB\n    [_memoryPageArray addObject:@{\n        @\"time\":currentTimeInterval,\n        @\"value\":[NSString stringWithFormat:@\"%zi\",memoryValue]//单位MB\n    }];\n}\n\n- (void)handleFPS:(NSInteger)fps{\n    //最多采样40个点\n    if (_fpsPageArray.count > 40) {\n        return;\n    }\n    [_fpsPageArray addObject:@{\n        @\"time\":[DoraemonUtil currentTimeInterval],\n        @\"value\":[NSString stringWithFormat:@\"%zi\",fps]\n    }];\n}\n\n- (void)upLoadData{\n    if (self.caseName.length>0 && self.testPerson.length>0) {\n        NSString *testTime = [DoraemonUtil dateFormatNow];\n        NSString *phoneName = [DoraemonAppInfoUtil iphoneType];\n        NSString *phoneSystem = [[UIDevice currentDevice] systemVersion];\n        NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey: @\"CFBundleShortVersionString\"];\n        NSString *appName = [DoraemonAppInfoUtil appName];\n        \n        \n        //启动流程\n        NSArray *loadArray = nil;\n        #if __has_include(\"DoraemonMethodUseTimeManager.h\")\n        loadArray = [[DoraemonMethodUseTimeManager sharedInstance] fixLoadModelArrayForHealth];\n        #endif\n        \n        NSDictionary *appStart = @{\n            @\"costTime\" : @(self.startTime),\n            @\"costDetail\" : STRING_NOT_NULL(self.costDetail),\n            @\"loadFunc\" : loadArray ? loadArray : @[]\n        };\n        \n        //大文件扫描\n        NSString *homeDir = NSHomeDirectory();\n        DoraemonUtil *util = [[DoraemonUtil alloc] init];\n        [util getBigSizeFileFormPath:homeDir];\n        NSArray *bigFileInfoArray = [self formatInfoByPathArray:util.bigFileArray];\n        \n        NSDictionary *dic = @{\n            @\"baseInfo\":@{\n                    @\"caseName\":STRING_NOT_NULL(self.caseName),\n                    @\"testPerson\":STRING_NOT_NULL(self.testPerson),\n                    @\"platform\":@\"iOS\",\n                    @\"time\":testTime,\n                    @\"phoneMode\":phoneName,\n                    @\"systemVersion\":phoneSystem,\n                    @\"appName\":appName,\n                    @\"appVersion\":appVersion,\n                    @\"dokitVersion\":DoKitVersion,\n                    @\"pId\":STRING_NOT_NULL([DoraemonManager shareInstance].pId)\n            },\n            @\"data\":@{\n                    @\"cpu\":[_cpuArray copy],\n                    @\"memory\":[_memoryArray copy],\n                    @\"fps\":[_fpsArray copy],\n                    @\"appStart\":appStart,\n                    @\"network\": [_networkArray copy],\n                    @\"block\":[_blockArray copy],\n                    @\"subThreadUI\":[_subThreadUIArray copy],\n                    @\"uiLevel\":[_uiLevelArray copy],\n                    @\"leak\":[_leakArray copy],\n                    @\"pageLoad\":[_pageLoadArray copy],\n                    @\"bigFile\":[bigFileInfoArray copy]\n            }\n        };\n        \n        DoKitLog(@\"upload info == %@\",dic);\n        \n        if (![DoraemonManager shareInstance].pId) {\n            DoKitLog(@\"dokik pId empty\");\n        }\n\n        [DoraemonNetworkUtil postWithUrlString:@\"https://www.dokit.cn/healthCheck/addCheckData\" params:dic success:^(NSDictionary * _Nonnull result) {\n            NSInteger code = [result[@\"code\"] integerValue];\n            if (code == 200) {\n                [DoraemonToastUtil showToastBlack:DoraemonLocalizedString(@\"数据上传成功\")  inView:[UIViewController rootViewControllerForDoraemonHomeWindow].view];\n            }else{\n                NSString *msg = result[@\"msg\"];\n                if (msg) {\n                    [DoraemonToastUtil showToastBlack:msg inView:[UIViewController rootViewControllerForDoraemonHomeWindow].view];\n                }\n            }\n\n        } error:^(NSError * _Nonnull error) {\n            [DoraemonToastUtil showToastBlack:DoraemonLocalizedString(@\"数据上传失败\")  inView:[UIViewController rootViewControllerForDoraemonHomeWindow].view];\n        }];\n    }\n    \n    [_cpuPageArray removeAllObjects];\n    [_memoryPageArray removeAllObjects];\n    [_fpsPageArray removeAllObjects];\n    [_networkPageArray removeAllObjects];\n    [_cpuArray removeAllObjects];\n    [_memoryArray removeAllObjects];\n    [_fpsArray removeAllObjects];\n    [_networkArray removeAllObjects];\n    [_blockArray removeAllObjects];\n    [_subThreadUIArray removeAllObjects];\n    [_uiLevelArray removeAllObjects];\n    [_leakArray removeAllObjects];\n    [_pageLoadArray removeAllObjects];\n    [_bigFileArray removeAllObjects];\n    _h5UrlString = nil;\n}\n\n- (void)startEnterPage:(Class)vcClass{\n    if (!_start) {\n        return;\n    }\n    if ([self blackList:vcClass]) {\n        return;\n    }\n    NSString *pageName = NSStringFromClass(vcClass);\n    CGFloat beginTime = CACurrentMediaTime();\n    [_pageEnterMap setValue:@(beginTime) forKey:pageName];\n    DoKitLog(@\"yixiang 开始进入页面 == %@ 时间 == %f\",pageName,beginTime);\n    \n}\n\n- (void)enterPage:(Class)vcClass{\n    if (!_start) {\n        return;\n    }\n    if ([self blackList:vcClass]) {\n        return;\n    }\n    [[DoraemonHealthCountdownWindow shareInstance] start:10];\n    NSString *pageName = NSStringFromClass(vcClass);\n    DoKitLog(@\"yixiang 已经进入页面 == %@\",pageName);\n    if (_pageEnterMap[pageName]) {\n        CGFloat beginTime = [_pageEnterMap[pageName] floatValue];\n        CGFloat endTime = CACurrentMediaTime();\n        NSInteger costTime = (NSInteger)((endTime - beginTime)*1000+0.5);//四舍五入 ms\n        [_pageLoadArray addObject:@{\n            @\"page\":NSStringFromClass(vcClass),\n            @\"time\":@(costTime)//ms\n        }];\n    }\n    [_pageEnterMap removeObjectForKey:pageName];\n    [_cpuPageArray removeAllObjects];\n    [_memoryPageArray removeAllObjects];\n    [_fpsPageArray removeAllObjects];\n    [_networkPageArray removeAllObjects];\n}\n\n- (void)leavePage:(Class)vcClass{\n    if (!_start) {\n        return;\n    }\n    if ([self blackList:vcClass]) {\n        return;\n    }\n    NSString *pageName = NSStringFromClass(vcClass);\n    if (_h5UrlString.length>0) {\n        pageName = [NSString stringWithFormat:@\"%@(%@)\",pageName,_h5UrlString];\n        _h5UrlString = nil;\n    }\n    \n    DoKitLog(@\"离开页面 == %@\",pageName);\n    \n    if (_networkPageArray.count>0) {\n        [_networkArray addObject:@{\n            @\"page\":pageName,\n            @\"values\":[_networkPageArray copy]\n        }];\n    }\n    \n    //cpu 内存 fps必须保证每一个页面运行10秒\n    if ([[DoraemonHealthCountdownWindow shareInstance] getCountdown] > 0) {\n        return;\n    }\n\n    if (_cpuPageArray.count>0) {\n        [_cpuArray addObject:@{\n            @\"page\":pageName,\n            @\"values\":[_cpuPageArray copy]\n        }];\n    }\n    \n    if(_memoryPageArray.count>0) {\n        [_memoryArray addObject:@{\n            @\"page\":pageName,\n            @\"values\":[_memoryPageArray copy]\n        }];\n    }\n\n    if (_fpsPageArray.count>0) {\n        [_fpsArray addObject:@{\n            @\"page\":pageName,\n            @\"values\":[_fpsPageArray copy]\n        }];\n    }\n    \n    [[DoraemonHealthCountdownWindow shareInstance] hide];\n}\n\n- (BOOL)blackList:(Class)vcClass{\n    if ([vcClass isSubclassOfClass:[DoraemonBaseViewController class]]) {\n        return YES;\n    }\n    if ([vcClass isSubclassOfClass:[UINavigationController class]] || [vcClass isSubclassOfClass:[UITabBarController class]]) {\n        return YES;\n    }\n    NSString *vcName = NSStringFromClass(vcClass);\n    NSArray *blackList = @[\n        @\"UIViewController\",\n        @\"UIInputWindowController\",\n        @\"UICompatibilityInputViewController\",\n        @\"UIEditingOverlayViewController\",\n        @\"UISystemInputAssistantViewController\",\n        @\"UIPredictionViewController\",\n        @\"_UIRemoteInputViewController\",\n        @\"UIEditingOverlayViewController\",\n        @\"AssistiveTouchController\",\n        @\"UICandidateViewController\",\n        @\"UISystemKeyboardDockController\",\n        @\"UIApplicationRotationFollowingControllerNoTouches\"\n    ];\n    if ([blackList containsObject:vcName]) {\n        return YES;\n    }\n    return NO;\n}\n\n- (void)addHttpModel:(DoraemonNetFlowHttpModel *)httpModel{\n    if (_start) {\n        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);\n        [_networkPageArray addObject:@{\n            @\"time\": [DoraemonUtil currentTimeInterval],\n            @\"url\": STRING_NOT_NULL(httpModel.url) ,\n            @\"up\": STRING_NOT_NULL(httpModel.uploadFlow),\n            @\"down\": STRING_NOT_NULL(httpModel.downFlow),\n            @\"code\": STRING_NOT_NULL(httpModel.statusCode),\n            @\"method\": STRING_NOT_NULL(httpModel.method)\n        }];\n        dispatch_semaphore_signal(semaphore);\n    }\n}\n\n- (void)addANRInfo:(NSDictionary *)anrInfo{\n    if (_start) {\n        [_blockArray addObject:@{\n            @\"page\":[self currentTopVC],\n            @\"blockTime\":anrInfo[@\"duration\"],\n            @\"detail\":anrInfo[@\"content\"]\n        }];\n    }\n}\n\n- (void)addSubThreadUI:(NSDictionary *)info{\n    if (_start) {\n        [_subThreadUIArray addObject:@{\n            @\"page\":[self currentTopVC],\n            @\"detail\":info[@\"content\"]\n        }];\n    }\n}\n\n- (void)addUILevel:(NSDictionary *)info{\n    if (_start) {\n        [_uiLevelArray addObject:@{\n            @\"page\":STRING_NOT_NULL([self currentTopVC]),\n            @\"level\":info[@\"level\"],\n            @\"detail\":info[@\"detail\"]\n        }];\n    }\n}\n\n- (void)addLeak:(NSDictionary *)info{\n    if (_start) {\n        NSString *viewStack = info[@\"viewStack\"];\n        NSString *retainCycle = info[@\"retainCycle\"];\n        NSString *detail = [NSString stringWithFormat:@\"viewStack : \\n%@ \\n\\n retainCycle : \\n%@\\n\\n\",STRING_NOT_NULL(viewStack),STRING_NOT_NULL(retainCycle)];\n        [_leakArray addObject:@{\n            @\"page\":info[@\"className\"],\n            @\"detail\":detail\n        }];\n    }\n}\n\n- (void)openH5Page:(NSString *)h5Url{\n    if (_start) {\n        __weak typeof(self) weakSelf = self;\n        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1. * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{\n            weakSelf.h5UrlString = h5Url;\n        });\n    }\n}\n\n\n\n- (NSString *)currentTopVC{\n    UIViewController *vc = [UIViewController topViewControllerForKeyWindow];\n    NSString *vcName = NSStringFromClass([vc class]);\n    return vcName;\n}\n\n- (NSArray *)formatInfoByPathArray:(NSArray *)pathArray{\n    NSMutableArray *fileInfoArray = [[NSMutableArray alloc] init];\n    for (NSString *path in pathArray) {\n        NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];\n        NSInteger fileSize = [dict[@\"NSFileSize\"] integerValue];\n        //NSString *fileSizeString = [NSByteCountFormatter stringFromByteCount:fileSize countStyle: NSByteCountFormatterCountStyleFile];\n        NSString *fileSizeString = [NSString stringWithFormat:@\"%zi\",fileSize];\n        NSString *fileName = [path lastPathComponent];\n        NSString *filePtahFromHomeDir = [self getPathFromHomeDir:path];\n        [fileInfoArray addObject:@{\n            @\"fileName\":fileName,\n            @\"fileSize\":fileSizeString,\n            @\"filePath\":filePtahFromHomeDir\n        }];\n    }\n    return fileInfoArray;\n}\n\n- (NSString *)getPathFromHomeDir:(NSString *)path{\n    NSString *homeDir = NSHomeDirectory();\n    NSString *relativePath = @\"\";\n    if ([path hasPrefix:homeDir]) {\n        relativePath = [path substringFromIndex:homeDir.length];\n    }\n    return relativePath;\n}\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/DoraemonHealthFooterView.h",
    "content": "//\n//  DoraemonHealthFooterView.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/1.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@protocol DoraemonHealthFooterButtonDelegate<NSObject>\n\n- (void)footerBtnClick:(id)sender;\n\n@end\n\n@interface DoraemonHealthFooterView : UIView\n\n\n@property (nonatomic, weak) id<DoraemonHealthFooterButtonDelegate> delegate;\n@property (nonatomic, assign) BOOL top;\n\n- (void)renderUIWithTitleImg:(NSString *)title img:(NSString *)imgName;\n- (void)renderUIWithTitleImg:(BOOL)top;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/DoraemonHealthFooterView.m",
    "content": "//\n//  DoraemonHealthFooterView.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/1.\n//\n\n#import \"DoraemonHealthFooterView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHealthFooterView ()\n\n@property (nonatomic, strong) UILabel *footerTitle;\n@property (nonatomic, strong) UIImageView *footerImg;\n@property (nonatomic, strong) NSDictionary *titleDictionary;\n\n@end\n\n@implementation DoraemonHealthFooterView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        \n        _footerTitle = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.doraemon_width, kDoraemonSizeFrom750_Landscape(36))];\n        _footerTitle.textAlignment = NSTextAlignmentCenter;\n        _footerTitle.textColor = [UIColor doraemon_colorWithString:@\"#27BCB7\"];\n        [_footerTitle setFont:[UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)]];\n        CGFloat size = kDoraemonSizeFrom750_Landscape(56);\n        _footerImg = [[UIImageView alloc] initWithFrame:CGRectMake((self.doraemon_width - size)/2, size, size, size)];\n        _footerImg.userInteractionEnabled = YES;\n        \n        [self addSubview:_footerTitle];\n        [self addSubview:_footerImg];\n        \n        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];\n        [_footerImg addGestureRecognizer:tap];\n    }\n    return self;\n}\n\n- (void)renderUIWithTitleImg:(NSString *)title img:(NSString *)imgName{\n    [_footerTitle setText:title];\n    [_footerImg setImage:[UIImage doraemon_xcassetImageNamed:imgName]];\n}\n\n- (void)renderUIWithTitleImg:(BOOL)top{\n    NSString *title;\n    NSString *icon;\n    _top = top;\n    if(top){\n        title = self.titleDictionary[@\"top\"];\n        icon = self.titleDictionary[@\"top_icon\"];\n    }else{\n        title = self.titleDictionary[@\"bottom\"];\n        icon = self.titleDictionary[@\"bottom_icon\"];\n    }\n    [_footerTitle setText:DoraemonLocalizedString(title)];\n    [_footerImg setImage:[UIImage doraemon_xcassetImageNamed:icon]];\n}\n\n- (NSDictionary *)titleDictionary{\n    if(!_titleDictionary){\n        _titleDictionary =  @{\n            @\"top\":@\"向下滑动查看功能使用说明\",\n            @\"bottom\":@\"回到顶部\",\n            @\"top_icon\":@\"doraemon_health_slide\",\n            @\"bottom_icon\":@\"doraemon_health_slideTop\",\n        };\n    }\n    return _titleDictionary;\n}\n\n- (void)tap{\n    if (_delegate && [_delegate respondsToSelector:@selector(footerBtnClick:)]) {\n        [_delegate footerBtnClick:self];\n    }\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/Home/Detail/DoraemonHealthBgView.h",
    "content": "//\n//  DoraemonHealthBgView.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/31.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHealthBgView : UIView\n\n- (CGRect)getButtonCGRect;\n- (CGRect)getStartingTitleCGRect;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/Home/Detail/DoraemonHealthBgView.m",
    "content": "//\n//  DoraemonHealthBgView.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/31.\n//\n\n#import \"DoraemonHealthBgView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHealthBgView()\n\n@property (nonatomic, strong) UIImageView *bgImgView;\n\n@end\n\n@implementation DoraemonHealthBgView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        CGFloat bg_x = kDoraemonSizeFrom750_Landscape(98);\n        CGFloat bg_width = self.doraemon_width - bg_x * 2;\n        _bgImgView = [[UIImageView alloc] initWithFrame:CGRectMake(bg_x,kDoraemonSizeFrom750_Landscape(89), bg_width, bg_width*16/9)];\n        [_bgImgView setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_health_bg\"]];\n        [self addSubview:_bgImgView];\n        self.userInteractionEnabled = NO;\n        [self sendSubviewToBack:_bgImgView];\n    }\n    return self;\n}\n\n- (CGRect)getStartingTitleCGRect{\n    CGRect rect;\n    rect.size.width = self.doraemon_width; rect.size.height = kDoraemonSizeFrom750_Landscape(40);\n    rect.origin.x = 0; rect.origin.y = _bgImgView.doraemon_y + _bgImgView.doraemon_height* 11/60;//根据图片比例获取\n    return rect;\n}\n\n- (CGRect)getButtonCGRect{\n    CGPoint point = CGPointMake(_bgImgView.doraemon_x + _bgImgView.doraemon_width/2, _bgImgView.doraemon_y + _bgImgView.doraemon_height * 7/10);//根据图片比例获取\n    CGRect rect;\n    rect.size.width = _bgImgView.doraemon_width*2/5; rect.size.height = rect.size.width;\n    rect.origin.x = point.x - rect.size.width/2 ; rect.origin.y = point.y - rect.size.width/2 + kDoraemonSizeFrom750_Landscape(5);\n\n    return rect;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/Home/Detail/DoraemonHealthBtnView.h",
    "content": "//\n//  DoraemonHealthBtnView.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/30.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@protocol DoraemonHealthButtonDelegate<NSObject>\n\n- (void)healthBtnClick:(id)sender;\n\n@end\n\n@interface DoraemonHealthBtnView : UIView\n\n@property (nonatomic, weak) id<DoraemonHealthButtonDelegate> delegate;\n\n- (void)statusForBtn:(BOOL)start;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/Home/Detail/DoraemonHealthBtnView.m",
    "content": "//\n//  DoraemonHealthBtnView.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/12/30.\n//\n\n#import \"DoraemonHealthBtnView.h\"\n#import \"DoraemonHealthManager.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHealthBtnView()\n\n@property (nonatomic, strong) UIImageView *healthBtn;\n\n@end\n\n@implementation DoraemonHealthBtnView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        _healthBtn = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.doraemon_width, self.doraemon_height)];\n        [_healthBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_health_start\"]];\n        [self addSubview:_healthBtn];\n        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];\n        [self addGestureRecognizer:tap];\n    }\n    return self;\n}\n\n- (void)tap{\n    if (_delegate && [_delegate respondsToSelector:@selector(healthBtnClick:)]) {\n        [_delegate healthBtnClick:self];\n    }\n}\n\n- (void)statusForBtn:(BOOL)start{\n    NSString *imgName = @\"doraemon_health_start\";\n    if(start){\n        imgName = @\"doraemon_health_end\";\n    }\n    [_healthBtn setImage:[UIImage doraemon_xcassetImageNamed:imgName]];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/Home/Detail/DoraemonHealthStartingTitle.h",
    "content": "//\n//  DoraemonHealthStartingTitle.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/2.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHealthStartingTitle : UIView\n\n- (void)renderUIWithTitle:(NSString *)title;\n- (void)showUITitle:(BOOL)show;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/Home/Detail/DoraemonHealthStartingTitle.m",
    "content": "//\n//  DoraemonHealthStartingTitle.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/2.\n//\n\n#import \"DoraemonHealthStartingTitle.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHealthStartingTitle()\n\n@property (nonatomic, strong) UILabel *title;\n\n@end\n\n@implementation DoraemonHealthStartingTitle\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        _title = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.doraemon_width, self.doraemon_height)];\n        _title.textColor = [UIColor doraemon_colorWithString:@\"#27BCB7\"];\n        _title.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)];\n        _title.textAlignment = NSTextAlignmentCenter;\n        [self addSubview:_title];\n    }\n    return self;\n}\n\n- (void)renderUIWithTitle:(NSString *)title{\n    if(title){\n        _title.text = title;\n    }\n}\n\n- (void)showUITitle:(BOOL)show{\n    if(show){\n        _title.hidden = NO;\n    }else{\n        _title.hidden = YES;\n    }\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/Home/DoraemonHealthHomeView.h",
    "content": "//\n//  DoraemonHealthHomeView.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/2.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonHealthBtnView.h\"\n#import \"DoraemonHealthStartingTitle.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\ntypedef void (^DoraemonHealthHomeBlock)(void);\n\n@interface DoraemonHealthHomeView : UIView\n\n@property (nonatomic, strong) DoraemonHealthBtnView *btnView;\n@property (nonatomic, strong) DoraemonHealthStartingTitle *startingTitle;\n\n- (void)addBlock:(DoraemonHealthHomeBlock)block;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/Home/DoraemonHealthHomeView.m",
    "content": "//\n//  DoraemonHealthHomeView.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/2.\n//\n\n#import \"DoraemonHealthHomeView.h\"\n#import \"DoraemonHealthBgView.h\"\n#import \"DoraemonHealthManager.h\"\n#import \"DoraemonToastUtil.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonHealthManager.h\"\n\n@interface DoraemonHealthHomeView ()<DoraemonHealthButtonDelegate>\n\n@property (nonatomic, strong) DoraemonHealthBgView *bgView;\n@property (nonatomic, copy) DoraemonHealthHomeBlock block;\n\n@end\n\n@implementation DoraemonHealthHomeView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        _bgView = [[DoraemonHealthBgView alloc] initWithFrame:CGRectMake(0, 0, self.doraemon_width, self.doraemon_height)];\n        _startingTitle = [[DoraemonHealthStartingTitle alloc] initWithFrame:[_bgView getStartingTitleCGRect]];\n        if ([DoraemonHealthManager sharedInstance].start) {\n            [_startingTitle renderUIWithTitle:DoraemonLocalizedString(@\"正在检测中...\")];\n        }else{\n            [_startingTitle renderUIWithTitle:DoraemonLocalizedString(@\"点击开始检测\")];\n        }\n        \n        _btnView = [[DoraemonHealthBtnView alloc] initWithFrame:[_bgView getButtonCGRect]];\n        [_btnView statusForBtn:[DoraemonHealthManager sharedInstance].start];\n        _btnView.delegate = self;\n        \n        [self addSubview:_bgView];\n        [self addSubview:_startingTitle];\n        [self addSubview:_btnView];\n    }\n    return self;\n}\n\n- (void)addBlock:(DoraemonHealthHomeBlock)block{\n    self.block = block;\n}\n\n- (void)_selfHandle{\n    if(self.block){\n        self.block();\n    }\n}\n\n#pragma mark - DoraemonHealthButtonDelegate\n- (void)healthBtnClick:(nonnull id)sender {\n    [self _selfHandle];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/Instructions/DoraemonHealthInstructionsCell.h",
    "content": "//\n//  DoraemonHealthInstructionsCell.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/2.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHealthInstructionsCell : UITableViewCell\n\n- (CGFloat)renderUIWithTitle:(NSString *)title item:(NSString *)itemLable;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/Instructions/DoraemonHealthInstructionsCell.m",
    "content": "//\n//  DoraemonHealthInstructionsCell.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/2.\n//\n\n#import \"DoraemonHealthInstructionsCell.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHealthInstructionsCell()\n\n@property (nonatomic, strong) UILabel *title;\n@property (nonatomic, strong) UIImageView *bgImg;\n@property (nonatomic, strong) UILabel *itemLable;\n@property (nonatomic, assign) CGFloat padding;\n@property (nonatomic, strong) NSMutableDictionary *attributes;\n\n@end\n\n@implementation DoraemonHealthInstructionsCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if(self){\n        self.selectionStyle = UITableViewCellSelectionStyleNone;\n        \n        _padding = kDoraemonSizeFrom750_Landscape(32);\n        _bgImg = [[UIImageView alloc] initWithFrame:CGRectMake(_padding, 0, kDoraemonSizeFrom750_Landscape(108), kDoraemonSizeFrom750_Landscape(48))];\n        [_bgImg setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_health_cell_bg\"]];\n        \n        _title = [[UILabel alloc] initWithFrame:CGRectMake(_padding, _bgImg.doraemon_top + _bgImg.doraemon_height/8, _bgImg.doraemon_width, _padding)];\n        _title.textAlignment = NSTextAlignmentCenter;\n        _title.textColor = [UIColor whiteColor];\n        _title.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)];\n        \n        _itemLable = [[UILabel alloc] initWithFrame:CGRectMake(_padding, _bgImg.doraemon_bottom + _padding/2, DoraemonScreenWidth - _padding*2, _padding)];\n        _itemLable.numberOfLines = 0;\n        _itemLable.textColor = [UIColor doraemon_black_1];\n        _itemLable.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)];\n        \n        NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];\n        paragraphStyle.lineSpacing = kDoraemonSizeFrom750_Landscape(8);\n        paragraphStyle.alignment = NSTextAlignmentLeft;\n        _attributes = [NSMutableDictionary dictionary];\n        [_attributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];\n        \n        [self addSubview:_bgImg];\n        [self addSubview:_title];\n        [self addSubview:_itemLable];\n    }\n    return self;\n}\n\n- (CGFloat)renderUIWithTitle:(NSString *)title item:(NSString *)itemLable{\n    \n    _title.text = title;\n    _itemLable.attributedText = [[NSAttributedString alloc] initWithString:itemLable attributes:_attributes];\n    [_itemLable sizeToFit];\n    _itemLable.frame = CGRectMake(_itemLable.doraemon_left, _itemLable.doraemon_top, _itemLable.doraemon_width, _itemLable.doraemon_height);\n    return _itemLable.doraemon_bottom + _padding*2;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/Instructions/DoraemonHealthInstructionsView.h",
    "content": "//\n//  DoraemonHealthInstructionsView.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/2.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHealthInstructionsView : UIView\n\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/Instructions/DoraemonHealthInstructionsView.m",
    "content": "//\n//  DoraemonHealthInstructionsView.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/2.\n//\n\n#import \"DoraemonHealthInstructionsView.h\"\n#import \"DoraemonHealthInstructionsCell.h\"\n#import \"DoraemonHealthManager.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHealthInstructionsView()<UITableViewDataSource,UITableViewDelegate>\n\n@property(nonatomic, assign) CGFloat cellHeight;\n@property(nonatomic, strong) UITableView *tableView;\n@property(nonatomic, copy) NSArray *itemTitleArray;\n@property(nonatomic, strong) NSString *DoraemonHealthInstructionsCellID;\n\n@end\n\n@implementation DoraemonHealthInstructionsView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if(self){\n        CGFloat bg_y = kDoraemonSizeFrom750_Landscape(89);\n        _itemTitleArray = @[\n            @\"点击开始体检按钮开始本次的性能数据手机。\",\n            @\"在每一个页面至少停留10秒钟，如果低于10秒钟的话，我们将会丢弃该页面的收集数据。\",\n            @\"测试完毕之后，重新进入该页面，点击结束测试按钮，填写本次的测试用例名称和测试人的名字，即可上传。\",\n            @\"打开dokit.cn平台，进入app健康体检列表，查看本次的数据报告。\"\n        ];\n        _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, bg_y, self.doraemon_width, self.doraemon_height)];\n        \n        _tableView.delegate = self;\n        _tableView.dataSource = self;\n        _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;\n        _DoraemonHealthInstructionsCellID = @\"DoraemonHealthInstructionsCell\";\n        [self addSubview:_tableView];\n        _cellHeight = kDoraemonSizeFrom750_Landscape(104);\n\n    }\n    return self;\n}\n\n- (UITableView *)tableView{\n    if(!_tableView){\n        _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.doraemon_width, self.doraemon_height)];\n        _tableView.backgroundColor = [UIColor yellowColor];\n        [_tableView registerClass:[DoraemonHealthInstructionsCell class] forCellReuseIdentifier:_DoraemonHealthInstructionsCellID];\n    }\n    return _tableView;\n}\n\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{\n    return 1;\n}\n\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{\n    return _itemTitleArray.count;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{\n    DoraemonHealthInstructionsCell *tableCell = [self.tableView dequeueReusableCellWithIdentifier:_DoraemonHealthInstructionsCellID];\n    if(!tableCell){\n       tableCell = [[DoraemonHealthInstructionsCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:_DoraemonHealthInstructionsCellID];\n    }\n    NSString *title = [NSString stringWithFormat:DoraemonLocalizedString(@\"第%d步\"),(int)indexPath.section + 1];\n    _cellHeight = [tableCell renderUIWithTitle:title item:_itemTitleArray[indexPath.section]];\n    \n    return  tableCell;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{\n    return _cellHeight;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{\n    \n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/Window/DoraemonHealthCountdownWindow.h",
    "content": "//\n//  DoraemonHealthCountdownWindow.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/9.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHealthCountdownWindow : UIWindow\n\n\n+ (DoraemonHealthCountdownWindow *)shareInstance;\n\n- (void)start:(NSInteger)number;\n- (void)hide;\n- (NSInteger)getCountdown;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Health/View/Window/DoraemonHealthCountdownWindow.m",
    "content": "//\n//  DoraemonHealthCountdownWindow.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/9.\n//\n\n#import \"DoraemonHealthCountdownWindow.h\"\n#import \"DoraemonStatusBarViewController.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHealthCountdownWindow()\n\n@property (nonatomic, assign) CGFloat showViewSize;\n@property (nonatomic, assign) int height;\n@property (nonatomic, assign) int width;\n@property (nonatomic, strong) UILabel *numberLabel;\n@property (nonatomic, strong) NSTimer *timer;\n@property (nonatomic, assign) NSInteger count;\n\n@end\n\n@implementation DoraemonHealthCountdownWindow\n\n+ (DoraemonHealthCountdownWindow *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonHealthCountdownWindow *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonHealthCountdownWindow alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    _showViewSize = kDoraemonSizeFrom750_Landscape(100);\n    CGFloat x = DoraemonScreenWidth - _showViewSize;\n    CGFloat y = DoraemonScreenHeight/5;\n    \n    self = [super initWithFrame:CGRectMake(x, y, _showViewSize, _showViewSize)];\n    if (self) {\n    #if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            for (UIWindowScene* windowScene in [UIApplication sharedApplication].connectedScenes){\n                if (windowScene.activationState == UISceneActivationStateForegroundActive){\n                    self.windowScene = windowScene;\n                    break;\n                }\n            }\n        }\n    #endif\n        _numberLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, _showViewSize, _showViewSize)];\n        _numberLabel.textColor = [UIColor doraemon_colorWithString:@\"#3CBCA3\"];\n        _numberLabel.font = [UIFont systemFontOfSize:_showViewSize*2/5];\n        _numberLabel.textAlignment = NSTextAlignmentCenter;\n        [self addSubview:_numberLabel];\n        self.backgroundColor = [UIColor clearColor];\n        self.windowLevel = UIWindowLevelStatusBar + 50;\n        self.layer.cornerRadius = _showViewSize/2;\n        self.layer.masksToBounds = YES;//保证显示\n        self.layer.borderWidth = _showViewSize/20;\n        self.layer.borderColor = _numberLabel.textColor.CGColor;\n        NSString *version= [UIDevice currentDevice].systemVersion;\n        if(version.doubleValue >=10.0) {\n            if (!self.rootViewController) {\n                self.rootViewController = [[UIViewController alloc] init];\n            }\n        }else{\n            //iOS9.0的系统中，新建的window设置的rootViewController默认没有显示状态栏\n            if (!self.rootViewController) {\n                self.rootViewController = [[DoraemonStatusBarViewController alloc] init];\n            }\n        }\n        UIPanGestureRecognizer *move = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_move:)];\n        [self addGestureRecognizer:move];\n    }\n    return self;\n}\n\n- (void)_move:(UIPanGestureRecognizer *)sender{\n    //1、获得拖动位移\n    CGPoint offsetPoint = [sender translationInView:sender.view];\n    //2、清空拖动位移\n    [sender setTranslation:CGPointZero inView:sender.view];\n    //3、重新设置控件位置\n    UIView *panView = sender.view;\n    CGFloat newX = panView.doraemon_centerX+offsetPoint.x;\n    CGFloat newY = panView.doraemon_centerY+offsetPoint.y;\n    if (newX < _showViewSize/2) {\n        newX = _showViewSize/2;\n    }\n    if (newX > DoraemonScreenWidth - _showViewSize/2) {\n        newX = DoraemonScreenWidth - _showViewSize/2;\n    }\n    if (newY < _showViewSize/2) {\n        newY = _showViewSize/2;\n    }\n    if (newY > DoraemonScreenHeight - _showViewSize/2) {\n        newY = DoraemonScreenHeight - _showViewSize/2;\n    }\n    panView.center = CGPointMake(newX, newY);\n}\n\n\n- (void)secondBtnAction{\n    if (!_timer) {\n        _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(handleTimer) userInfo:nil repeats:YES];\n        [_timer fire];\n    }\n}\n//定时操作，更新UI\n- (void)handleTimer {\n    if (self.count < 0) {\n        [self hide];\n    } else {\n        self.numberLabel.text = [NSString stringWithFormat:@\"%zi\",self.count];\n    }\n    self.count--;\n}\n\n- (void)start:(NSInteger)number{\n    self.hidden = NO;\n    _count = number > 0 ? number: 10;\n    [self secondBtnAction];\n}\n\n- (void)hide{\n    self.hidden = YES;\n    [_timer invalidate];\n    _timer = nil;\n}\n\n- (NSInteger)getCountdown{\n    return _count;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/DoraemonMockPlugin.h",
    "content": "//\n//  DoraemonMockPlugin.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/23.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonMockPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/DoraemonMockPlugin.m",
    "content": "//\n//  DoraemonMockPlugin.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/23.\n//\n\n#import \"DoraemonMockPlugin.h\"\n#import \"DoraemonMockViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonManager.h\"\n#import \"DoraemonToastUtil.h\"\n#import \"UIViewController+Doraemon.h\"\n#import \"Doraemoni18NUtil.h\"\n\n@implementation DoraemonMockPlugin\n\n- (void)pluginDidLoad{\n    if ([DoraemonManager shareInstance].pId) {\n        DoraemonMockViewController *vc = [[DoraemonMockViewController alloc] init];\n        [DoraemonHomeWindow openPlugin:vc];\n    }else{\n        [DoraemonToastUtil showToastBlack:DoraemonLocalizedString(@\"需要到www.dokit.cn上注册pId才能使用该功能\") inView:[UIViewController rootViewControllerForDoraemonHomeWindow].view];\n    }\n\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/DoraemonMockViewController.h",
    "content": "//\n//  DoraemonMockViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/23.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonMockViewController : DoraemonBaseViewController\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/DoraemonMockViewController.m",
    "content": "//\n//  DoraemonMockViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/23.\n//\n\n#import \"DoraemonMockViewController.h\"\n#import \"DoraemonDefine.h\"\n\n#import \"DoraemonMockUploadViewController.h\"\n#import \"DoraemonMockAPIViewController.h\"\n#import \"DoraemonMockManager.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@interface DoraemonMockViewController()\n\n@end\n\n@implementation DoraemonMockViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, DoraemonScreenHeight/2-100/2, DoraemonScreenWidth, 100)];\n    titleLabel.text = @\"正在请求数据，请稍等\";\n    titleLabel.font = [UIFont systemFontOfSize:16];\n    titleLabel.textColor = [UIColor doraemon_blue];\n    titleLabel.textAlignment = NSTextAlignmentCenter;\n    [self.view addSubview:titleLabel];\n    \n    //拉取最新的mock数据\n    [[DoraemonMockManager sharedInstance] queryMockData:^(int flag) {\n        NSString *toast = nil;\n        if (flag == 1) {\n            //toast = @\"数据更新成功\";\n            toast = nil;\n        }else if(flag == 2){\n            toast = @\"数据更新失败\";\n        }else if(flag == 3){\n            toast = @\"pId为空 更新失败\";\n        }\n        DoKitLog(@\"mock get data, flag == %i\",flag);\n        [self renderUI];\n        if (toast.length > 0) {\n            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{\n                [DoraemonToastUtil showToastBlack:toast inView:[DoraemonHomeWindow shareInstance]];\n            });\n        }\n    }];\n    \n}\n\n- (void)renderUI{\n        UITabBarController *tabBar = [[UITabBarController alloc] init];\n    #if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            tabBar.tabBar.backgroundColor = [UIColor systemBackgroundColor];\n            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, -0.5, CGRectGetWidth(tabBar.tabBar.frame), 0.5)];\n                view.backgroundColor = [UIColor doraemon_black_3];\n                [tabBar.tabBar insertSubview:view atIndex:0];\n            }\n        } else {\n    #endif\n            tabBar.tabBar.backgroundColor = [UIColor whiteColor];\n    #if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n    #endif\n    \n    UIViewController *vc1 = [[DoraemonMockAPIViewController alloc] init];\n    UINavigationController *nav1 = [[UINavigationController alloc] initWithRootViewController:vc1];\n    nav1.tabBarItem = [[UITabBarItem alloc] initWithTitle:DoraemonLocalizedString(@\"Mock数据\") image:[[[UIImage doraemon_xcassetImageNamed:@\"doraemon_mock_data\"] doraemon_scaledToSize:CGSizeMake(30,30)] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] selectedImage:[[[UIImage doraemon_xcassetImageNamed:@\"doraemon_mock_data_selected\"] doraemon_scaledToSize:CGSizeMake(30,30)] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];\n    [nav1.tabBarItem setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor doraemon_colorWithHex:0x333333],NSFontAttributeName:[UIFont systemFontOfSize:10]} forState:UIControlStateNormal];\n    [nav1.tabBarItem setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor doraemon_colorWithHex:0x337CC4],NSFontAttributeName:[UIFont systemFontOfSize:10]} forState:UIControlStateSelected];\n    \n    \n    UIViewController *vc2 = [[DoraemonMockUploadViewController alloc] init];\n    UINavigationController *nav2 = [[UINavigationController alloc] initWithRootViewController:vc2];\n    nav2.tabBarItem = [[UITabBarItem alloc] initWithTitle:DoraemonLocalizedString(@\"上传模版\") image:[[[UIImage doraemon_xcassetImageNamed:@\"doraemon_mock_up\"] doraemon_scaledToSize:CGSizeMake(30,30)] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] selectedImage:[[[UIImage doraemon_xcassetImageNamed:@\"doraemon_mock_up_selected\"] doraemon_scaledToSize:CGSizeMake(30,30)] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];\n    [nav2.tabBarItem setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor doraemon_colorWithHex:0x333333],NSFontAttributeName:[UIFont systemFontOfSize:10]} forState:UIControlStateNormal];\n    [nav2.tabBarItem setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor doraemon_colorWithHex:0x337CC4],NSFontAttributeName:[UIFont systemFontOfSize:10]} forState:UIControlStateSelected];\n    \n    tabBar.viewControllers = @[nav1,nav2];\n    \n    tabBar.modalPresentationStyle = UIModalPresentationFullScreen;\n    \n    [self.navigationController presentViewController:tabBar animated:NO completion:nil];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/Function/DoraemonMockManager.h",
    "content": "//\n//  DoraemonMockManager.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/31.\n//\n#import <UIKit/UIKit.h>\n#import <Foundation/Foundation.h>\n#import \"DoraemonMockAPIModel.h\"\n#import \"DoraemonMockUpLoadModel.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n@interface DoraemonMockManager : NSObject\n\n+ (instancetype)sharedInstance;\n\n@property (nonatomic, assign) BOOL mock;\n@property (nonatomic, strong) NSMutableArray<DoraemonMockAPIModel *> *mockArray;\n@property (nonatomic, strong) NSMutableArray<DoraemonMockUpLoadModel *> *upLoadArray;\n\n@property (nonatomic, copy) NSArray *groups;//分组数据\n@property (nonatomic, copy) NSArray *states;//状态数据\n@property (nonatomic, copy) NSString *mockGroup;//mock数据选中的分组\n@property (nonatomic, copy) NSString *mockState;//mock数据选中的状态\n@property (nonatomic, copy) NSString *mockSearchText;//mock数据的搜索关键字\n@property (nonatomic, copy) NSString *uploadGroup;//上传信息选中的分组\n@property (nonatomic, copy) NSString *uploadState;//上传信息选中的状态\n@property (nonatomic, copy) NSString *uploadSearchText;//上传信息的搜索关键字\n\n- (void)queryMockData:(void(^)(int flag))block;\n\n- (BOOL)needMock:(NSURLRequest *)request;\n\n- (NSString *)getSceneId:(NSURLRequest *)request;\n\n- (BOOL)needSave:(NSURLRequest *)request;\n\n- (NSMutableArray<DoraemonMockAPIModel *> *)filterMockArray;\n\n- (NSMutableArray<DoraemonMockUpLoadModel *> *)filterUpLoadArray;\n\n- (void)uploadSaveData:(DoraemonMockUpLoadModel *)upload atView:(UIView *)view;\n\n\n@end\nNS_ASSUME_NONNULL_END\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/Function/DoraemonMockManager.m",
    "content": "//\n//  DoraemonMockManager.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/31.\n//\n\n#import \"DoraemonMockManager.h\"\n#import \"DoraemonNetworkUtil.h\"\n#import \"DoraemonNetworkInterceptor.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonUrlUtil.h\"\n#import \"DoraemonManager.h\"\n#import \"DoraemonMockUtil.h\"\n#import \"DoraemonDefine.h\"\n#import \"UIViewController+Doraemon.h\"\n\n@interface DoraemonMockManager()<DoraemonNetworkInterceptorDelegate>\n\n@end\n\n@implementation DoraemonMockManager {\n    BOOL _isDetecting;\n}\n\n+ (instancetype)sharedInstance {\n    static id instance = nil;\n    static dispatch_once_t onceToken;\n    \n    dispatch_once(&onceToken, ^{\n        instance = [[self alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    self = [super init];\n    if (self) {\n        _states = @[@\"所有\",@\"打开\",@\"关闭\"];\n    }\n    return self;\n}\n\n- (void)setMock:(BOOL)mock{\n    if (_isDetecting == mock) {\n        return;\n    }\n    _isDetecting = mock;\n    [self updateInterceptStatus];\n}\n\n- (void)updateInterceptStatus {\n    if (_isDetecting) {\n        [[DoraemonNetworkInterceptor shareInstance] addDelegate: self];\n    } else {\n        [[DoraemonNetworkInterceptor shareInstance] removeDelegate: self];\n    }\n}\n\n- (void)queryMockData:(void(^)(int flag))block{\n    NSString *pId = [DoraemonManager shareInstance].pId;\n    NSString *mockDomain = [DoraemonManager shareInstance].mockDomain ? [DoraemonManager shareInstance].mockDomain : @\"https://mock.dokit.cn/\";\n    if (pId && pId.length>0) {\n        NSDictionary *params = @{\n            @\"projectId\":pId,\n            @\"isfull\":@\"1\",\n            @\"curPage\":@\"1\",\n            @\"pageSize\":@\"500\"\n        };\n        \n        __weak typeof(self) weakSelf = self;\n        NSString *mockInterfaceUrl = [mockDomain stringByAppendingString:@\"api/app/interface\"];\n        [DoraemonNetworkUtil getWithUrlString:mockInterfaceUrl params:params success:^(NSDictionary * _Nonnull result) {\n            NSArray *apis = result[@\"data\"][@\"datalist\"];\n            NSMutableArray<DoraemonMockAPIModel *> *mockArray = [[NSMutableArray alloc] init];\n            NSMutableArray<DoraemonMockUpLoadModel *> *uploadArray = [[NSMutableArray alloc] init];\n            NSMutableArray *groups = [[NSMutableArray alloc] init];\n            [groups addObject:@\"所有\"];\n            for (NSDictionary *item in apis) {\n                DoraemonMockAPIModel *mock = [[DoraemonMockAPIModel alloc] init];\n                mock.apiId = item[@\"_id\"];\n                mock.name = item[@\"name\"];\n                mock.path = item[@\"path\"];\n                mock.query = item[@\"query\"];\n                mock.body = item[@\"body\"];\n                //mock.body = @{@\"bodyTitle\":@\"bodyName\"};\n                mock.category = item[@\"categoryName\"];\n                mock.owner = item[@\"owner\"][@\"name\"];\n                mock.editor = item[@\"curStatus\"][@\"operator\"][@\"name\"];\n                NSArray *sceneList = item[@\"sceneList\"];\n                NSMutableArray *sList = [[NSMutableArray alloc] init];\n                for (NSDictionary *scene in sceneList) {\n                    DoraemonMockScene *s = [[DoraemonMockScene alloc] init];\n                    s.sceneId = scene[@\"_id\"];\n                    s.name = scene[@\"name\"];\n                    [sList addObject:s];\n                }\n                mock.sceneList = sList;\n                \n                [mockArray addObject:mock];\n                \n                \n                DoraemonMockUpLoadModel *upload = [[DoraemonMockUpLoadModel alloc] init];\n                upload.apiId = item[@\"_id\"];\n                upload.name = item[@\"name\"];\n                upload.path = item[@\"path\"];\n                upload.query = item[@\"query\"];\n                upload.body = item[@\"body\"];\n                //upload.body = @{@\"bodyTitle\":@\"bodyName\"};\n                upload.category = item[@\"categoryName\"];\n                upload.owner = item[@\"owner\"][@\"name\"];\n                upload.editor = item[@\"curStatus\"][@\"operator\"][@\"name\"];\n                [uploadArray addObject:upload];\n                \n                NSString *category = item[@\"categoryName\"];\n                if (groups && ![groups containsObject:category]) {\n                    [groups addObject:category];\n                }\n            }\n            weakSelf.mockArray = mockArray;\n            weakSelf.upLoadArray = uploadArray;\n            weakSelf.groups = groups;\n            [self handleData];\n            block(1);\n        } error:^(NSError * _Nonnull error) {\n            DoKitLog(@\"error == %@\",error);\n            block(2);\n        }];\n    }else{\n        DoKitLog(@\"Request interface list must ensure that pId is not empty\");\n        block(3);\n    }\n    \n}\n\n// 处理数据：合并网络数据和本地数据\n- (void)handleData {\n    _mockGroup = @\"所有\";\n    _mockState = @\"所有\";\n    _uploadGroup = @\"所有\";\n    _uploadState = @\"所有\";\n    \n    [[DoraemonMockUtil sharedInstance] readMockArrayCache];\n    [[DoraemonMockUtil sharedInstance] readUploadArrayCache];\n    for (DoraemonMockAPIModel *api in self.mockArray) {\n        if (api.selected) {\n            self.mock = YES;\n            break;\n        }\n    }\n    for (DoraemonMockUpLoadModel *upload in self.upLoadArray) {\n        if (upload.selected) {\n            self.mock = YES;\n            break;\n        }\n    }\n}\n\n- (BOOL)needMock:(NSURLRequest *)request{\n    DoraemonMockBaseModel *api = [self getSelectedData:request dataArray:_mockArray];\n    BOOL mock = NO;\n    if (api) {\n        mock = YES;\n    }\n    DoKitLog(@\"yixiang mock = %d\",mock);\n    return mock;\n}\n\n- (NSString *)getSceneId:(NSURLRequest *)request{\n    DoraemonMockAPIModel *api = (DoraemonMockAPIModel *)[self getSelectedData:request dataArray:_mockArray];\n    NSArray<DoraemonMockScene *> *sceneList = api.sceneList;\n    NSString *sceneId = @\"\";\n    for (DoraemonMockScene *scene in sceneList) {\n        if (scene.selected) {\n            sceneId = scene.sceneId;\n            break;\n        }\n    }\n    \n    return sceneId;\n}\n\n- (DoraemonMockBaseModel *)getSelectedData:(NSURLRequest *)request dataArray:(NSArray *)dataArray{\n    NSString *path = request.URL.path;\n    NSString *query = request.URL.query;\n    // 这里暂时使用不严谨body match\n    NSData *httpBody = request.HTTPBody;\n    NSDictionary *requestBody = [DoraemonUrlUtil convertDicFromData:httpBody];\n    DoraemonMockBaseModel *selectedApi;\n    for (DoraemonMockBaseModel *api in dataArray) {\n        //匹配path\n        if (([path hasSuffix:api.path]) && api.selected) {\n            //匹配query\n            if (api.query && api.query.allKeys.count>0 && query && query.length>0) {\n                NSDictionary *q = api.query;\n                BOOL match = YES;\n                for (NSString *key in q.allKeys) {\n                    NSString *value = q[key];\n                    NSString *item = [NSString stringWithFormat:@\"%@=%@\",key,value];\n                    if (![query containsString:item]) {\n                        match = NO;\n                        break;\n                    }\n                }\n                if (match) {\n                    selectedApi = api;\n                    DoKitLog(@\"yixiang mock query match\");\n                    break;\n                }\n            }\n            \n            //匹配body\n            if (api.body && api.body.allKeys.count>0 && requestBody && requestBody.allKeys.count>0) {\n                NSDictionary *q = api.body;\n                BOOL match = YES;\n                for (NSString *key in q.allKeys) {\n                    NSString *value1 = q[key];\n                    NSString *value2 = requestBody[key];\n                    if (!(value1 && value2 && [value1 isEqualToString:value2])) {\n                        match = NO;\n                        break;\n                    }\n                }\n                if (match) {\n                    selectedApi = api;\n                    DoKitLog(@\"yixiang mock body match\");\n                    break;\n                }\n            }\n            \n            if ((!api.query || api.query.allKeys.count==0) && (!api.body || api.body.allKeys.count==0)) {\n                //都没有匹配到的话，只匹配path\n                selectedApi = api;\n                DoKitLog(@\"yixiang mock path match\");\n                break;\n            }\n        }\n    }\n    return selectedApi;\n}\n\n- (BOOL)needSave:(NSURLRequest *)request{\n    DoraemonMockBaseModel *api = [self getSelectedData:request dataArray:_upLoadArray];\n    BOOL save = NO;\n    if (api) {\n        save = YES;\n    }\n    DoKitLog(@\"yixiang save = %d api = %@ query = %@\",save,api.path,api.query);\n    return save;\n}\n\n- (NSMutableArray<DoraemonMockAPIModel *> *)filterMockArray{\n    NSMutableArray<DoraemonMockAPIModel *> *filter_1_Array = [[NSMutableArray alloc] init];\n    if ([_mockGroup isEqualToString:@\"所有\"]) {\n        filter_1_Array = _mockArray;\n    }else{\n        for (DoraemonMockAPIModel *mockModel in _mockArray) {\n            if ([_mockGroup isEqualToString:mockModel.category]) {\n                [filter_1_Array addObject:mockModel];\n            }\n        }\n    }\n\n    NSMutableArray<DoraemonMockAPIModel *> *filter_2_Array = [[NSMutableArray alloc] init];\n    if ([_mockState isEqualToString:@\"所有\"]) {\n        filter_2_Array = filter_1_Array;\n    }else{\n        for (DoraemonMockAPIModel *mockModel in filter_1_Array) {\n            if([_mockState isEqualToString:@\"打开\"] && mockModel.selected){\n                [filter_2_Array addObject:mockModel];\n            }else if([_mockState isEqualToString:@\"关闭\"] && !mockModel.selected){\n                [filter_2_Array addObject:mockModel];\n            }\n        }\n    }\n    \n    NSMutableArray<DoraemonMockAPIModel *> *filter_3_Array = [[NSMutableArray alloc] init];\n    if(!_mockSearchText || _mockSearchText.length==0){\n        filter_3_Array = filter_2_Array;\n    }else {\n        for (DoraemonMockAPIModel *mockModel in filter_2_Array) {\n            if([mockModel.name containsString:_mockSearchText]){\n                [filter_3_Array addObject:mockModel];\n            }\n        }\n    }\n\n    return  filter_3_Array;\n}\n\n- (NSMutableArray<DoraemonMockUpLoadModel *> *)filterUpLoadArray{\n    NSMutableArray<DoraemonMockUpLoadModel *> *filter_1_Array = [[NSMutableArray alloc] init];\n    if ([_uploadGroup isEqualToString:@\"所有\"]) {\n        filter_1_Array = _upLoadArray;\n    }else{\n        for (DoraemonMockUpLoadModel *uploadModel in _upLoadArray) {\n            if ([_uploadGroup isEqualToString:uploadModel.category]) {\n                [filter_1_Array addObject:uploadModel];\n            }\n        }\n    }\n\n    NSMutableArray<DoraemonMockUpLoadModel *> *filter_2_Array = [[NSMutableArray alloc] init];\n    if ([_uploadState isEqualToString:@\"所有\"]) {\n        filter_2_Array = filter_1_Array;\n    }else{\n        for (DoraemonMockUpLoadModel *uploadModel in filter_1_Array) {\n            if([_uploadState isEqualToString:@\"打开\"] && uploadModel.selected){\n                [filter_2_Array addObject:uploadModel];\n            }else if([_uploadState isEqualToString:@\"关闭\"] && !uploadModel.selected){\n                [filter_2_Array addObject:uploadModel];\n            }\n        }\n    }\n    \n    NSMutableArray<DoraemonMockUpLoadModel *> *filter_3_Array = [[NSMutableArray alloc] init];\n    if(!_uploadSearchText || _uploadSearchText.length==0){\n        filter_3_Array = filter_2_Array;\n    }else {\n        for (DoraemonMockUpLoadModel *uploadModel in filter_2_Array) {\n            if([uploadModel.name containsString:_uploadSearchText]){\n                [filter_3_Array addObject:uploadModel];\n            }\n        }\n    }\n\n    return  filter_3_Array;\n\n}\n\n- (void)uploadSaveData:(DoraemonMockUpLoadModel *)upload atView:(UIView *)view{\n    NSString *apiId = upload.apiId;\n    NSString *result = upload.result;\n    NSString *projectId = [DoraemonManager shareInstance].pId;\n    NSString *mockDomain = [DoraemonManager shareInstance].mockDomain ? [DoraemonManager shareInstance].mockDomain : @\"https://mock.dokit.cn/\";\n    if (projectId && projectId.length > 0) {\n        if (!result) {\n            return;\n        }\n        \n        NSDictionary *params = @{\n            @\"projectId\":projectId,\n            @\"id\":apiId,\n            @\"tempData\":result\n        };\n        NSString *mockInterfaceUrl = [mockDomain stringByAppendingString:@\"api/app/interface\"];\n        [DoraemonNetworkUtil patchWithUrlString:mockInterfaceUrl params:params success:^(NSDictionary * _Nonnull result) {\n            [self showToast:DoraemonLocalizedString(@\"上传成功\") atView:view];\n        } error:^(NSError * _Nonnull error) {\n            DoKitLog(@\"error == %@\",error);\n            [self showToast:DoraemonLocalizedString(@\"上传失败\") atView:view];\n        }];\n    }else{\n        DoKitLog(@\"Upload template must has pid\");\n    }\n}\n\n- (void)showToast:(NSString *)toast atView:view{\n    if ([NSThread isMainThread]) {\n        [DoraemonToastUtil showToastBlack:toast inView:view];\n    }else{\n        dispatch_async(dispatch_get_main_queue(), ^{\n            [DoraemonToastUtil showToastBlack:toast inView:view];\n        });\n    }\n    \n}\n\n#pragma mark -- DoraemonNetworkInterceptorDelegate\n- (void)doraemonNetworkInterceptorDidReceiveData:(NSData *)data response:(NSURLResponse *)response request:(NSURLRequest *)request error:(NSError *)error startTime:(NSTimeInterval)startTime {\n    if ([self needSave:request]) {\n        NSString *result = [DoraemonUrlUtil convertJsonFromData:data];\n        DoraemonMockUpLoadModel *upload = (DoraemonMockUpLoadModel *)[self getSelectedData:request dataArray:_upLoadArray];\n        upload.result = result;\n        [[DoraemonMockUtil sharedInstance] saveUploadArrayCache];\n        dispatch_async(dispatch_get_main_queue(), ^{\n            [DoraemonToastUtil showToastBlack:[NSString stringWithFormat:@\"save url = %@\",request.URL.absoluteURL] inView:[UIViewController rootViewControllerForKeyWindow].view];\n        });\n    }\n}\n\n\n- (BOOL)shouldIntercept {\n    return _isDetecting;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/Function/DoraemonMockUtil.h",
    "content": "//\n//  DoraemonMockUtil.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/18.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMockUtil : NSObject\n\n+ (instancetype)sharedInstance;\n\n- (void)saveMockArrayCache;\n\n- (void)saveUploadArrayCache;\n\n- (void)readMockArrayCache;\n\n- (void)readUploadArrayCache;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/Function/DoraemonMockUtil.m",
    "content": "//\n//  DoraemonMockUtil.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/18.\n//\n\n#import \"DoraemonMockUtil.h\"\n#import \"DoraemonUtil.h\"\n#import \"DoraemonMockManager.h\"\n#import \"DoraemonDefine.h\"\n\n#define DoraemonMockFileName @\"mock\"\n#define DoraemonUploadFileName @\"upload\"\n\n@interface DoraemonMockUtil()\n\n@end\n\n@implementation DoraemonMockUtil\n\n+ (instancetype)sharedInstance {\n    static id instance = nil;\n    static dispatch_once_t onceToken;\n    \n    dispatch_once(&onceToken, ^{\n        instance = [[self alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init {\n    self = [super init];\n    if (self) {\n    }\n    return self;\n}\n\n- (void)saveMockArrayCache{\n    NSMutableArray<DoraemonMockAPIModel *> *mockArray = [DoraemonMockManager sharedInstance].mockArray;\n    NSMutableArray *dataArray = [[NSMutableArray alloc] init];\n    for (DoraemonMockAPIModel *api in mockArray) {\n        if (api.selected) {\n            NSMutableDictionary *apiDic = [[NSMutableDictionary alloc] init];\n            [apiDic setValue:api.apiId forKey:@\"apiId\"];\n            NSArray<DoraemonMockScene *> *sceneList = api.sceneList;\n            for (DoraemonMockScene *scene in sceneList) {\n                if (scene.selected) {\n                    [apiDic setValue:scene.sceneId forKey:@\"sceneId\"];\n                    break;\n                }\n            }\n            [dataArray addObject:apiDic];\n        }\n    }\n    \n    [self saveArray:dataArray toFile:DoraemonMockFileName];\n}\n\n- (void)saveUploadArrayCache{\n    NSMutableArray<DoraemonMockUpLoadModel *> *upLoadArray = [DoraemonMockManager sharedInstance].upLoadArray;\n    NSMutableArray *dataArray = [[NSMutableArray alloc] init];\n    for (DoraemonMockUpLoadModel *upload in upLoadArray) {\n        if (upload.selected) {\n            NSMutableDictionary *uploadDic = [[NSMutableDictionary alloc] init];\n            [uploadDic setValue:upload.apiId forKey:@\"apiId\"];\n            if(upload.result.length>0){\n                [uploadDic setValue:upload.result forKey:@\"result\"];\n            }\n            [dataArray addObject:uploadDic];\n        }\n    }\n    \n    [self saveArray:dataArray toFile:DoraemonUploadFileName];\n}\n\n- (void)readMockArrayCache{\n    NSMutableArray<DoraemonMockAPIModel *> *mockArray = [DoraemonMockManager sharedInstance].mockArray;\n    for (DoraemonMockAPIModel *api in mockArray) {\n        NSString *apiId = api.apiId;\n        NSArray *dataArray = [self getArrayFromFile:DoraemonMockFileName];\n        for (NSDictionary *apiDic in dataArray) {\n            if([apiId isEqualToString:apiDic[@\"apiId\"]]){\n                api.selected = YES;\n                for (DoraemonMockScene *scene in api.sceneList) {\n                    if ([scene.sceneId isEqualToString: apiDic[@\"sceneId\"]]) {\n                        scene.selected = YES;\n                        break;\n                    }\n                }\n                break;\n            }\n        }\n    }\n}\n\n- (void)readUploadArrayCache{\n    NSMutableArray<DoraemonMockUpLoadModel *> *upLoadArray = [DoraemonMockManager sharedInstance].upLoadArray;\n    for (DoraemonMockUpLoadModel *upload in upLoadArray) {\n        NSArray *dataArray = [self getArrayFromFile:DoraemonUploadFileName];\n        for (NSDictionary *uploadDic in dataArray) {\n            if ([upload.apiId isEqualToString:uploadDic[@\"apiId\"]]) {\n                upload.selected = YES;\n                if (uploadDic[@\"result\"]){\n                    upload.result = uploadDic[@\"result\"];\n                }\n                break;\n            }\n        }\n    }\n}\n\n- (void)saveArray:(NSArray *)dataArray toFile:(NSString *)fileName{\n    NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];\n    NSString *anrDir = [cachesDir stringByAppendingPathComponent:@\"DoraemonMockCache\"];\n    NSFileManager *fileManager = [NSFileManager defaultManager];\n    BOOL isDir = NO;\n    BOOL existed = [fileManager fileExistsAtPath:anrDir isDirectory:&isDir];\n    if(!(isDir && existed)){\n        [fileManager createDirectoryAtPath:anrDir withIntermediateDirectories:YES attributes:nil error:nil];\n    }\n    NSString *path = [anrDir stringByAppendingPathComponent:[NSString stringWithFormat:@\"%@.json\",fileName]];\n    NSString *text = [DoraemonUtil arrayToJsonStr:dataArray];\n    BOOL writeSuccess = [text writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];\n    if (writeSuccess) {\n        DoKitLog(@\"写入成功\");\n    }\n}\n\n- (NSArray *)getArrayFromFile:(NSString *)fileName{\n    NSArray *dataArray;\n    NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];\n    NSString *anrDir = [cachesDir stringByAppendingPathComponent:@\"DoraemonMockCache\"];\n    NSFileManager *fileManager = [NSFileManager defaultManager];\n    BOOL isDir = NO;\n    BOOL existed = [fileManager fileExistsAtPath:anrDir isDirectory:&isDir];\n    if (existed){\n        NSString *path = [anrDir stringByAppendingPathComponent:[NSString stringWithFormat:@\"%@.json\",fileName]];\n        NSError *error = nil;\n        NSString *json = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];\n        dataArray = [DoraemonUtil arrayWithJsonString:json];\n    }\n    return dataArray;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/Model/DoraemonMockAPIModel.h",
    "content": "//\n//  DoraemonMockAPI.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/12.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonMockBaseModel.h\"\n#import \"DoraemonMockScene.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMockAPIModel : DoraemonMockBaseModel\n\n//业务数据\n@property (nonatomic, copy) NSArray<DoraemonMockScene *> *sceneList;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/Model/DoraemonMockAPIModel.m",
    "content": "//\n//  DoraemonMockAPI.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/12.\n//\n\n#import \"DoraemonMockAPIModel.h\"\n\n@implementation DoraemonMockAPIModel\n\n- (NSString *)info{\n    NSMutableString *info = [[NSMutableString alloc] init];\n    if (self.path) {\n        [self appendFormat:info text:@\"path : %@\\n\" append:self.path];\n    }\n    if (self.query && self.query.allKeys.count>0) {\n        [info appendFormat :@\"query: %@\\n\",self.query];\n    }\n    if (self.body && self.body.allValues.count>0) {\n        [info appendFormat :@\"body: %@\\n\",self.body];\n    }\n    if (self.category) {\n        [self appendFormat:info text:@\"分组 : %@\\n\" append:self.category];\n    }\n    if (self.owner) {\n        [self appendFormat:info text:@\"创建人 : %@\\n\" append:self.owner];\n    }\n    if (self.editor) {\n        [self appendFormat:info text:@\"修改人 : %@\\n\" append:self.editor];\n    }\n    \n    [info replaceCharactersInRange:NSMakeRange([info length] - 1, 1) withString:@\"\"];\n    \n    return info;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/Model/DoraemonMockBaseModel.h",
    "content": "//\n//  DoraemonMockBaseModel.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMockBaseModel : NSObject\n\n//通用业务数据\n@property (nonatomic, copy) NSString *apiId; //接口id\n@property (nonatomic, copy) NSString *name; //接口名称\n@property (nonatomic, copy) NSString *path; //path\n@property (nonatomic, copy) NSDictionary *query; //query\n@property (nonatomic, copy) NSDictionary *body; //body\n@property (nonatomic, copy) NSString *category; //分组\n@property (nonatomic, copy) NSString *owner;//创建者\n@property (nonatomic, copy) NSString *editor;//最新的一次修改人\n\n//通用逻辑数据\n@property (nonatomic, assign) BOOL expand;//该数据是否展开\n@property (nonatomic, copy) NSString *info;//info面板数据，由path、query、category、owner、editor组成\n@property (nonatomic, assign) BOOL selected;//是否选中\n-  (void)appendFormat:(NSMutableString *)info text:(NSString *)text append:(NSString *)appendInfo;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/Model/DoraemonMockBaseModel.m",
    "content": "//\n//  DoraemonMockBaseModel.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import \"DoraemonMockBaseModel.h\"\n#import \"DoraemonDefine.h\"\n\n@implementation DoraemonMockBaseModel\n\n-  (void)appendFormat:(NSMutableString *)info text:(NSString *)text append:(NSString *)appendInfo{\n    [info appendFormat:DoraemonLocalizedString(text),appendInfo];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/Model/DoraemonMockScene.h",
    "content": "//\n//  DoraemonMockScene.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/12.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMockScene : NSObject\n\n@property (nonatomic, copy) NSString *sceneId;//场景id\n@property (nonatomic, copy) NSString *name;//场景名称\n@property (nonatomic, assign) BOOL selected;//是否选中\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/Model/DoraemonMockScene.m",
    "content": "//\n//  DoraemonMockScene.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/12.\n//\n\n#import \"DoraemonMockScene.h\"\n\n@implementation DoraemonMockScene\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/Model/DoraemonMockUpLoadModel.h",
    "content": "//\n//  DoraemonMockUpLoadModel.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import \"DoraemonMockBaseModel.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMockUpLoadModel : DoraemonMockBaseModel\n\n@property (nonatomic, copy) NSString *result;//本地a保存的mock数据\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/Model/DoraemonMockUpLoadModel.m",
    "content": "//\n//  DoraemonMockUpLoadModel.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import \"DoraemonMockUpLoadModel.h\"\n#import \"DoraemonDefine.h\"\n\n@implementation DoraemonMockUpLoadModel\n\n- (NSString *)info{\n    NSMutableString *info = [[NSMutableString alloc] init];\n    if (self.path) {\n        [info appendFormat:@\"path: %@\\n\",self.path];\n    }\n    if (self.query && self.query.allKeys.count>0) {\n        [info appendFormat:@\"query: %@\\n\",self.query];\n    }\n    if (self.body && self.body.allValues.count>0) {\n        [info appendFormat :@\"body: %@\\n\",self.body];\n    }\n    if (self.category) {\n        [self appendFormat:info text:@\"分组: %@\\n\" append:self.category];\n    }\n    if (self.owner) {\n        [self appendFormat:info text:@\"创建人: %@\\n\" append:self.owner];\n    }\n    if (self.editor) {\n        [self appendFormat:info text:@\"修改人: %@\\n\" append:self.editor];\n    }\n    if (self.result && self.result.length>0) {\n        [self appendFormat:info text:@\"本地是否存在mock数据: %@\" append:DoraemonLocalizedString(@\"存在\")];\n    }else{\n        [self appendFormat:info text:@\"本地是否存在mock数据: %@\" append:DoraemonLocalizedString(@\"不存在\")];\n    }\n    \n    return info;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/VC/DoraemonMockAPIViewController.h",
    "content": "//\n//  DoraemonMockDataViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/5.\n//\n#import \"DoraemonMockBaseViewController.h\"\n\n@interface DoraemonMockAPIViewController : DoraemonMockBaseViewController\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/VC/DoraemonMockAPIViewController.m",
    "content": "//\n//  DoraemonMockDataViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/5.\n//\n\n#import \"DoraemonMockAPIViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonMockApiListView.h\"\n\n\n@interface DoraemonMockAPIViewController()\n\n@property (nonatomic, strong) DoraemonMockApiListView *detailView;\n@property (nonatomic, assign) CGFloat padding_left;\n\n@end\n\n@implementation DoraemonMockAPIViewController\n\n- (void)viewDidLoad{\n    [super viewDidLoad];\n    \n    self.searchView.textField.text = [DoraemonMockManager sharedInstance].mockSearchText;\n    \n    _detailView = [[DoraemonMockApiListView alloc] initWithFrame:CGRectMake(0, self.sepeatorLine.doraemon_bottom, self.view.doraemon_width, self.view.doraemon_height - self.sepeatorLine.doraemon_bottom)];\n    [self.view addSubview:_detailView];\n    \n    NSString *leftTitle = [DoraemonMockManager sharedInstance].mockGroup;\n    if ([leftTitle isEqualToString:@\"所有\"]) {\n        leftTitle = @\"接口分组\";\n    }\n    [self.leftButton renderUIWithTitle:leftTitle];\n    \n    NSString *rightTitle = [DoraemonMockManager sharedInstance].mockState;\n    if ([rightTitle isEqualToString:@\"所有\"]) {\n        rightTitle = @\"开关状态\";\n    }\n    [self.rightButton renderUIWithTitle:rightTitle];\n}\n\n#pragma mark --DoraemonMockFilterBgroundDelegate\n- (void)filterSelectedClick{\n    if(self.rightButton.down){\n        NSString *rightTitle = [DoraemonMockManager sharedInstance].states[self.listView.selectedIndex];\n        [DoraemonMockManager sharedInstance].mockState = rightTitle;\n        if ([rightTitle isEqualToString:@\"所有\"]) {\n            rightTitle = @\"开关状态\";\n        }\n        [self.rightButton renderUIWithTitle:rightTitle];\n    }else{\n        NSString *leftTitle = [DoraemonMockManager sharedInstance].groups[self.listView.selectedIndex];\n        [DoraemonMockManager sharedInstance].mockGroup = leftTitle;\n        if ([leftTitle isEqualToString:@\"所有\"]) {\n            leftTitle = @\"接口分组\";\n        }\n        [self.leftButton renderUIWithTitle:leftTitle];\n    }\n    \n    [super filterSelectedClick];\n    \n    \n    [_detailView reloadUI];\n\n}\n\n#pragma mark - DoraemonMockSearchViewDelegate\n- (void)searchViewInputChange:(NSString *)text{\n    [DoraemonMockManager sharedInstance].mockSearchText = text;\n    [self.detailView reloadUI];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/VC/DoraemonMockBaseViewController.h",
    "content": "//\n//  DoraemonMockBaseViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/7.\n//\n\n#import \"DoraemonBaseViewController.h\"\n#import \"DoraemonMockFilterListView.h\"\n#import \"DoraemonMockFilterButton.h\"\n#import \"DoraemonMockSearchView.h\"\n\n@interface DoraemonMockBaseViewController : DoraemonBaseViewController\n\n@property (nonatomic, strong) DoraemonMockSearchView *searchView;\n@property (nonatomic, strong) UIView *sepeatorLine;\n@property (nonatomic, strong) DoraemonMockFilterListView *listView;\n@property (nonatomic, strong) DoraemonMockFilterButton *leftButton;\n@property (nonatomic, strong) DoraemonMockFilterButton *rightButton;\n\n- (void)filterSelectedClick;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/VC/DoraemonMockBaseViewController.m",
    "content": "//\n//  DoraemonMockBaseViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/7.\n//\n\n#import \"DoraemonMockBaseViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonMockManager.h\"\n\n@interface DoraemonMockBaseViewController ()<DoraemonMockSearchViewDelegate,DoraemonMockFilterButtonDelegate,DoraemonMockFilterBgroundDelegate>\n@property (nonatomic, assign) CGFloat padding_left;\n\n@end\n\n@implementation DoraemonMockBaseViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"Mock数据\");\n    _padding_left = kDoraemonSizeFrom750_Landscape(32);\n    _searchView = [[DoraemonMockSearchView alloc] initWithFrame:CGRectMake(_padding_left, self.bigTitleView.doraemon_bottom + _padding_left, self.view.doraemon_width-2* _padding_left, kDoraemonSizeFrom750_Landscape(100))];\n    _searchView.delegate = self;\n    [self.view addSubview:_searchView];\n    \n    _leftButton = [[DoraemonMockFilterButton alloc] initWithFrame:CGRectMake(0, _searchView.doraemon_bottom, self.view.doraemon_width/2, kDoraemonSizeFrom750_Landscape(126))];\n    [_leftButton renderUIWithTitle:DoraemonLocalizedString(@\"接口分组\")];\n    _leftButton.delegate = self;\n    _leftButton.tag = 0;\n    [self.view addSubview:_leftButton];\n\n    _rightButton = [[DoraemonMockFilterButton alloc] initWithFrame:CGRectMake(_leftButton.doraemon_right, _searchView.doraemon_bottom, self.view.doraemon_width/2, kDoraemonSizeFrom750_Landscape(126))];\n    [_rightButton renderUIWithTitle:DoraemonLocalizedString(@\"开关状态\")];\n    _rightButton.delegate = self;\n    _rightButton.tag = 1;\n    [self.view addSubview:_rightButton];\n\n    _listView = [[DoraemonMockFilterListView alloc] initWithFrame:CGRectMake(0, _leftButton.doraemon_bottom, self.view.doraemon_width, self.view.doraemon_height - _leftButton.doraemon_bottom)];\n    _listView.delegate = self;\n    [self.view addSubview:_listView];\n\n    _sepeatorLine = [[UIView alloc] initWithFrame:CGRectMake(0, _leftButton.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(12))];\n    _sepeatorLine.backgroundColor = [UIColor doraemon_bg];\n    [self.view addSubview:_sepeatorLine];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n- (void)showList:(DoraemonMockFilterButton *)filterBtn{\n    _listView.tag = filterBtn.tag;\n    _listView.selectedIndex = filterBtn.selectedItemIndex;\n    [self.view addSubview:_listView];\n    [_listView showList: [self getItemArray:filterBtn.tag]];\n}\n\n- (NSArray *)getItemArray:(NSInteger)tag{\n    NSArray *dataArray;\n    if (tag == 0) {\n        dataArray = [DoraemonMockManager sharedInstance].groups;\n    }else{\n        dataArray = [DoraemonMockManager sharedInstance].states;\n    }\n    return dataArray;\n}\n\n- (void)closeList{\n    if(_rightButton.down){\n        _rightButton.selectedItemIndex = _listView.selectedIndex;\n        [_rightButton setDropdown:NO];\n    }else{\n        _leftButton.selectedItemIndex = _listView.selectedIndex;\n        [_leftButton setDropdown:NO];\n    }\n    [_listView closeList];\n}\n\n#pragma mark - DoraemonMockSearchViewDelegate\n- (void)searchViewInputChange:(NSString *)text{\n    \n}\n\n#pragma mark --DoraemonMockFilterBgroundDelegate\n- (void)filterBgroundClick{\n    [self closeList];\n}\n\n#pragma mark --DoraemonMockFilterBgroundDelegate\n- (void)filterSelectedClick{\n    [self closeList];\n}\n\n#pragma mark - DoraemonMockHalfButton\n- (void)filterBtnClick:(id)sender{\n    if(_rightButton.down&&_leftButton.down){\n        if(sender==_rightButton){\n            _leftButton.selectedItemIndex = _listView.selectedIndex;\n            [self showList:_rightButton];\n            [_leftButton setDropdown:NO];\n            return;\n        }\n        _rightButton.selectedItemIndex = _listView.selectedIndex;\n        [self showList:_leftButton];\n        [_rightButton setDropdown:NO];\n    }\n    else if(_rightButton.down){\n        [self showList:_rightButton];\n    }\n    else if(_leftButton.down){\n        [self showList:_leftButton];\n    }else{\n        if(sender==_leftButton){\n            _leftButton.selectedItemIndex = _listView.selectedIndex;\n        }\n        else{\n            _rightButton.selectedItemIndex = _listView.selectedIndex;\n        }\n        [_listView closeList];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/VC/DoraemonMockUploadViewController.h",
    "content": "//\n//  DoraemonMockUploadDataViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/7.\n//\n\n#import \"DoraemonMockBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMockUploadViewController : DoraemonMockBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/VC/DoraemonMockUploadViewController.m",
    "content": "//\n//  DoraemonMockUploadDataViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/7.\n//\n\n#import \"DoraemonMockUploadViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonMockUploadListView.h\"\n#import \"DoraemonMockDataPreviewViewController.h\"\n\n@interface DoraemonMockUploadViewController ()<DoraemonMockUploadListViewDelegate>\n\n@property (nonatomic, strong) DoraemonMockUploadListView *detailView;\n\n\n@end\n\n@implementation DoraemonMockUploadViewController\n\n- (void)viewDidLoad{\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"上传模版\");\n    \n    self.searchView.textField.text = [DoraemonMockManager sharedInstance].uploadSearchText;\n    \n    _detailView = [[DoraemonMockUploadListView alloc] initWithFrame:CGRectMake(0, self.sepeatorLine.doraemon_bottom, self.view.doraemon_width, self.view.doraemon_height - self.sepeatorLine.doraemon_bottom)];\n    _detailView.delegate = self;\n    [self.view addSubview:_detailView];\n    \n    NSString *leftTitle = [DoraemonMockManager sharedInstance].uploadGroup;\n    if ([leftTitle isEqualToString:@\"所有\"]) {\n        leftTitle = @\"接口分组\";\n    }\n    [self.leftButton renderUIWithTitle:leftTitle];\n    \n    NSString *rightTitle = [DoraemonMockManager sharedInstance].uploadState;\n    if ([rightTitle isEqualToString:@\"所有\"]) {\n        rightTitle = @\"开关状态\";\n    }\n    [self.rightButton renderUIWithTitle:rightTitle];\n}\n\n#pragma mark - DoraemonMockUploadListViewDelegate\n- (void)previewClick:(DoraemonMockUpLoadModel *)uploadModel{\n    NSString *result = uploadModel.result;\n    if (result && result.length>0) {\n        DoraemonMockDataPreviewViewController *vc = [[DoraemonMockDataPreviewViewController alloc] init];\n        vc.upLoadModel = uploadModel;\n        [self.navigationController pushViewController:vc animated:YES];\n    }else{\n        [DoraemonToastUtil showToastBlack:@\"数据预览为空\" inView:self.view];\n    }\n}\n\n#pragma mark --DoraemonMockFilterBgroundDelegate\n- (void)filterSelectedClick{\n    if(self.rightButton.down){\n        NSString *rightTitle = [DoraemonMockManager sharedInstance].states[self.listView.selectedIndex];\n        [DoraemonMockManager sharedInstance].uploadState = rightTitle;\n        if ([rightTitle isEqualToString:@\"所有\"]) {\n            rightTitle = @\"开关状态\";\n        }\n        [self.rightButton renderUIWithTitle:rightTitle];\n    }else{\n        NSString *leftTitle = [DoraemonMockManager sharedInstance].groups[self.listView.selectedIndex];\n        [DoraemonMockManager sharedInstance].uploadGroup = leftTitle;\n        if ([leftTitle isEqualToString:@\"所有\"]) {\n            leftTitle = @\"接口分组\";\n        }\n        [self.leftButton renderUIWithTitle:leftTitle];\n    }\n    \n    [super filterSelectedClick];\n    \n    [_detailView reloadUI];\n}\n\n#pragma mark - DoraemonMockSearchViewDelegate\n- (void)searchViewInputChange:(NSString *)text{\n    [DoraemonMockManager sharedInstance].uploadSearchText = text;\n    [self.detailView reloadUI];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/VC/Preview/DoraemonMockDataPreviewViewController.h",
    "content": "//\n//  DoraemonMockDataPreviewViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/17.\n//\n\n#import \"DoraemonBaseViewController.h\"\n#import \"DoraemonMockUpLoadModel.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMockDataPreviewViewController : DoraemonBaseViewController\n\n@property (nonatomic, strong) DoraemonMockUpLoadModel *upLoadModel;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/VC/Preview/DoraemonMockDataPreviewViewController.m",
    "content": "//\n//  DoraemonMockDataPreviewViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/17.\n//\n\n#import \"DoraemonMockDataPreviewViewController.h\"\n#import \"DoraemonMockManager.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMockDataPreviewViewController ()\n\n@property (nonatomic, strong) UITextView *textView;\n\n@end\n\n@implementation DoraemonMockDataPreviewViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = @\"数据预览\";\n    CGFloat upLoadBtnHeight = kDoraemonSizeFrom750_Landscape(100);\n    CGFloat tabBarHeight = self.tabBarController.tabBar.doraemon_height;\n    _textView = [[UITextView alloc] initWithFrame:CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT, self.view.doraemon_width, self.view.doraemon_height-IPHONE_NAVIGATIONBAR_HEIGHT-tabBarHeight-upLoadBtnHeight)];\n    _textView.text = self.upLoadModel.result;\n    [self.view addSubview:_textView];\n    \n    UIButton *upLoadBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, _textView.doraemon_bottom, self.view.doraemon_width, upLoadBtnHeight)];\n    upLoadBtn.backgroundColor = [UIColor doraemon_blue];\n    [upLoadBtn setTitle:DoraemonLocalizedString(@\"上传模版\") forState:UIControlStateNormal];\n    [upLoadBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];\n    [upLoadBtn addTarget:self action:@selector(upload) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:upLoadBtn];\n}\n\n- (void)upload{\n    [[DoraemonMockManager sharedInstance] uploadSaveData:self.upLoadModel atView:self.view];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/Filter/DoraemonMockFilterButton.h",
    "content": "//\n//  DoraemonMockHalfButton.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/23.\n//\n\n#import <UIKit/UIKit.h>\n\n@protocol DoraemonMockFilterButtonDelegate<NSObject>\n\n- (void)filterBtnClick:(id)sender;\n\n@end\n\n@interface DoraemonMockFilterButton : UIView\n\n@property (nonatomic, weak) id<DoraemonMockFilterButtonDelegate> delegate;\n\n@property (nonatomic, assign) BOOL down;\n@property (nonatomic, assign) NSInteger selectedItemIndex;\n\n- (void)renderUIWithTitle:(NSString *)title;\n- (void)setDropdown:(BOOL )isDown;\n\n@end\n\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/Filter/DoraemonMockFilterButton.m",
    "content": "//\n//  DoraemonMockHalfButton.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/23.\n//\n\n#import \"DoraemonMockFilterButton.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMockFilterButton()\n\n@property(nonatomic, strong) UILabel *titleLabel;\n@property(nonatomic, strong) UIImageView *arrow;\n\n@end\n\n@implementation DoraemonMockFilterButton\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        _titleLabel = [[UILabel alloc] init];\n        _titleLabel.textColor = [UIColor doraemon_black_1];\n        _titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)];\n        [self addSubview:_titleLabel];\n        _down = NO;\n        _arrow = [[UIImageView alloc] init];\n        [self addSubview:_arrow];\n        \n        UITapGestureRecognizer *todo = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];\n        [self addGestureRecognizer:todo];\n        \n        _selectedItemIndex = 0;\n    }\n    return self;\n}\n\n\n- (void)renderUIWithTitle:(NSString *)title{\n\n    _titleLabel.text = DoraemonLocalizedString(title);\n    [_titleLabel sizeToFit];\n    _titleLabel.frame = CGRectMake(self.doraemon_width/2-_titleLabel.doraemon_width/3*2, self.doraemon_height/2-_titleLabel.doraemon_height/2, _titleLabel.doraemon_width, _titleLabel.doraemon_height);\n    _arrow.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_mock_filter_down\"];\n    _arrow.frame = CGRectMake(_titleLabel.doraemon_right + _arrow.image.size.width, self.doraemon_height/2-_arrow.image.size.height/2, _arrow.image.size.width, _arrow.image.size.height);\n}\n\n- (void)setDropdown:(BOOL )isDown{\n    NSString *imgName = @\"doraemon_mock_filter_down\";\n    _down = NO;\n    if(isDown){\n        imgName = @\"doraemon_mock_filter_up\";\n        _down = YES;\n    }\n    _arrow.image = [UIImage doraemon_xcassetImageNamed:imgName];\n    _arrow.doraemon_width = _arrow.image.size.width;\n    _arrow.doraemon_height = _arrow.image.size.height;\n}\n\n- (void)tap{\n    if (_delegate && [_delegate respondsToSelector:@selector(filterBtnClick:)]) {\n        _down = !_down;\n        [self setDropdown:self.down];\n        [_delegate filterBtnClick:self];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/Filter/DoraemonMockFilterListView.h",
    "content": "//\n//  DoraemonMockDropdownListView.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/24.\n//\n\n#import <UIKit/UIKit.h>\n\n@protocol DoraemonMockFilterBgroundDelegate<NSObject>\n\n- (void)filterBgroundClick;\n- (void)filterSelectedClick;\n\n@end\n\n@interface DoraemonMockFilterListView : UIView\n\n@property (nonatomic, weak) id<DoraemonMockFilterBgroundDelegate> delegate;\n\n@property (nonatomic, assign) NSInteger selectedIndex;\n\n- (void)showList:(NSArray *)itemArray;\n\n- (void)closeList;\n\n@end\n\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/Filter/DoraemonMockFilterListView.m",
    "content": "//\n//  DoraemonMockDropdownListView.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/24.\n//\n\n#import \"DoraemonMockFilterListView.h\"\n#import \"DoraemonMockFilterTableCell.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMockFilterListView()<UITableViewDataSource,UITableViewDelegate>\n\n@property(nonatomic, strong) UIView *bgroundView;\n@property(nonatomic, strong) UITableView *tableView;\n@property(nonatomic, assign) CGFloat cellHeight;\n@property(nonatomic, copy) NSArray *itemTitleArray;\n@property(nonatomic, strong) NSString *DoraemonMockFilterTableCellID;\n\n@end\n\n@implementation DoraemonMockFilterListView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if(self){\n        _bgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.doraemon_width, self.doraemon_height)];\n        _bgroundView.alpha = 0;\n        _bgroundView.backgroundColor = [UIColor lightGrayColor];\n        [self addSubview:_bgroundView];\n        UITapGestureRecognizer *todo = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];\n        [_bgroundView addGestureRecognizer:todo];\n        _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.doraemon_width, 0)];\n        [self addSubview:_tableView];\n        _tableView.delegate = self;\n        _tableView.dataSource = self;\n        _selectedIndex = 0;\n        _cellHeight = kDoraemonSizeFrom750_Landscape(104);\n        _DoraemonMockFilterTableCellID = @\"DoraemonMockFilterTableCell\";\n    }\n    return self;\n}\n\n- (UITableView *)tableView{\n    if(!_tableView){\n        _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.doraemon_width, 0)];\n        _tableView.backgroundColor = [UIColor whiteColor];\n        [_tableView registerClass:[DoraemonMockFilterTableCell class] forCellReuseIdentifier:_DoraemonMockFilterTableCellID];\n    }\n    return _tableView;\n}\n\n- (void)showList:(NSArray *)itemArray{\n    _itemTitleArray = itemArray;\n    [_tableView reloadData];\n    [UIView beginAnimations:nil context:nil];\n    [UIView setAnimationDuration:0.25f];\n    self.tableView.frame = CGRectMake(0, 0, self.doraemon_width, itemArray.count * _cellHeight);\n    self.bgroundView.alpha = 0.5f;\n    [UIView commitAnimations];\n    self.userInteractionEnabled = YES;\n}\n\n- (void)closeList{\n    [UIView beginAnimations:nil context:nil];\n    [UIView setAnimationDuration:0.25f];\n    self.bgroundView.alpha = 0;\n    self.tableView.frame = CGRectMake(0, 0, self.doraemon_width, 0);\n    [UIView commitAnimations];\n    self.userInteractionEnabled = NO;\n}\n\n- (void)tap{\n    if (_delegate && [_delegate respondsToSelector:@selector(filterBgroundClick)]) {\n        [_delegate filterBgroundClick];\n    }\n}\n\n#pragma mark - UITableView Delegate\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{\n    return 1;\n}\n\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{\n    return _itemTitleArray.count;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{\n    DoraemonMockFilterTableCell *tableCell = [self.tableView dequeueReusableCellWithIdentifier:_DoraemonMockFilterTableCellID];\n    if(!tableCell){\n       tableCell = [[DoraemonMockFilterTableCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:_DoraemonMockFilterTableCellID];\n    }\n    [tableCell renderUIWithTitle:_itemTitleArray[indexPath.section]];\n    [tableCell selectedColor:NO];\n    if(_selectedIndex == indexPath.section){\n        [tableCell selectedColor:YES];\n    }\n    return  tableCell;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{\n    return _cellHeight;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{\n    _selectedIndex = indexPath.section;\n    [_tableView reloadData];\n    if (_delegate && [_delegate respondsToSelector:@selector(filterSelectedClick)]) {\n        [_delegate filterSelectedClick];\n    }\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/Filter/DoraemonMockFilterTableCell.h",
    "content": "//\n//  DoraemonMockFilterTableCell.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/25.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface DoraemonMockFilterTableCell : UITableViewCell\n\n- (void)renderUIWithTitle:(NSString *)title;\n\n- (void)selectedColor:(BOOL)selected;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/Filter/DoraemonMockFilterTableCell.m",
    "content": "//\n//  DoraemonMockFilterTableCell.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/25.\n//\n\n#import \"DoraemonMockFilterTableCell.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMockFilterTableCell()\n\n@property (nonatomic, strong) UILabel *itemTitle;\n\n@end\n\n@implementation DoraemonMockFilterTableCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if(self){\n        self.selectionStyle = UITableViewCellSelectionStyleNone;\n        _itemTitle = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, DoraemonScreenWidth, self.doraemon_height)];\n        _itemTitle.textAlignment = NSTextAlignmentCenter;\n        _itemTitle.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        [self addSubview:_itemTitle];\n    }\n    return self;\n}\n\n- (void)renderUIWithTitle:(NSString *)title{\n    _itemTitle.text = title;\n    _itemTitle.textColor = [UIColor doraemon_black_1];\n}\n\n- (void)selectedColor:(BOOL)selected{\n    if(selected)\n        _itemTitle.textColor = [UIColor doraemon_blue];\n    else\n        _itemTitle.textColor = [UIColor doraemon_black_1];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/Cell/DoraemonMockApiCell.h",
    "content": "//\n//  DoraemonMockApiCell.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import \"DoraemonMockBaseCell.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMockApiCell : DoraemonMockBaseCell\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/Cell/DoraemonMockApiCell.m",
    "content": "//\n//  DoraemonMockApiCell.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import \"DoraemonMockApiCell.h\"\n#import \"DoraemonMockSceneButton.h\"\n#import \"DoraemonMockAPIModel.h\"\n#import \"DoraemonMockManager.h\"\n\n@interface DoraemonMockApiCell()<DoraemonMockSceneButtonDelegate>\n\n@property (nonatomic, strong) UIView *sceneView;\n\n@end\n\n@implementation DoraemonMockApiCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        _sceneView = [[UIView alloc] init];\n        [self.contentView addSubview:_sceneView];\n    }\n    return self;\n}\n\n- (void)renderCellWithData:(DoraemonMockBaseModel *)model{\n    [super renderCellWithData: model];\n    if(!model||!model.expand){\n        _sceneView.hidden = YES;\n        return ;\n    }else{\n        _sceneView.hidden = NO;\n    }\n    if (_sceneView.subviews.count>0) {\n        [_sceneView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];\n    }\n    \n    DoraemonMockAPIModel *apiModel = (DoraemonMockAPIModel *)self.model;\n    \n    NSArray *sceneList = apiModel.sceneList;\n    if (sceneList.count > 0) {\n        _sceneView.frame = CGRectMake(0, self.infoLabel.doraemon_bottom, DoraemonScreenWidth, [[self class] sceneViewHeight:apiModel]);\n        CGFloat h = kDoraemonSizeFrom750_Landscape(32);\n        CGFloat offsetY = kDoraemonSizeFrom750_Landscape(32);\n        CGFloat offsetX = kDoraemonSizeFrom750_Landscape(32);\n        NSInteger i = 0;\n        for (DoraemonMockScene *scene in sceneList) {\n            DoraemonMockSceneButton *btn = [[DoraemonMockSceneButton alloc] init];\n            btn.tag = i; i++;\n            btn.delegate = self;\n            [btn renderTitle:scene.name isSelected:scene.selected];\n            \n            CGFloat w = [DoraemonMockSceneButton viewWidth:scene.name];\n            if (offsetX>DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)) {\n                offsetX = kDoraemonSizeFrom750_Landscape(32);\n                offsetY += kDoraemonSizeFrom750_Landscape(32)*2;\n            }\n            btn.frame = CGRectMake(offsetX, offsetY, w, h);\n            offsetX += w+kDoraemonSizeFrom750_Landscape(32);\n            [_sceneView addSubview:btn];\n        }\n    }\n}\n\n+ (CGFloat)cellHeightWith:(DoraemonMockBaseModel *)model{\n    CGFloat cellHeight = [super cellHeightWith:model];\n    \n    if (model && model.expand){\n        DoraemonMockAPIModel *apiModel = (DoraemonMockAPIModel *)model;\n        cellHeight += [self sceneViewHeight:apiModel];\n    }\n\n    return cellHeight;\n}\n\n\n+ (CGFloat)sceneViewHeight:(DoraemonMockAPIModel *)model{\n    NSArray *sceneList = model.sceneList;\n    \n    CGFloat w = kDoraemonSizeFrom750_Landscape(32);\n    CGFloat h = 0;\n    if (sceneList.count>0) {\n        h = kDoraemonSizeFrom750_Landscape(32);\n        h += kDoraemonSizeFrom750_Landscape(32);\n    }\n    for (DoraemonMockScene *scene in sceneList) {\n        w += [DoraemonMockSceneButton viewWidth:scene.name];\n        if (w > DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)*2) {\n            w = kDoraemonSizeFrom750_Landscape(32);\n            h += kDoraemonSizeFrom750_Landscape(32)*2;\n        }\n        w += kDoraemonSizeFrom750_Landscape(32);\n    }\n    \n    return h;\n}\n\n#pragma mark - DoraemonMockSceneButtonDelegate\n- (void)sceneBtnClick:(NSInteger)tag{\n    DoraemonMockAPIModel *apiModel = (DoraemonMockAPIModel *)self.model;\n    NSArray<DoraemonMockScene *> *sceneList = apiModel.sceneList;\n    for (int i=0; i<sceneList.count; i++) {\n        DoraemonMockScene *scene = sceneList[i];\n        if (i == tag) {\n            scene.selected = YES;\n        }else {\n            scene.selected = NO;\n        }\n    }\n    if (self.delegate && [self.delegate respondsToSelector:@selector(sceneBtnClick)]) {\n        [self.delegate sceneBtnClick];\n    }\n}\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    self.model.selected = on;\n    \n    // 1、是否开启mock功能 : mock列表中只要有一个选中就需要打开mock功能\n    BOOL needMockOn = NO;\n    for (DoraemonMockAPIModel *api in [DoraemonMockManager sharedInstance].mockArray) {\n        if (api.selected) {\n            needMockOn = YES;\n        }\n    }\n    \n    [DoraemonMockManager sharedInstance].mock = needMockOn;\n    \n    DoraemonMockAPIModel *apiModel = (DoraemonMockAPIModel *)self.model;\n    \n    if (on) {\n        // 2、如果场景没有选中 默认选中第一个\n        NSArray<DoraemonMockScene *> *sceneList = apiModel.sceneList;\n        BOOL select = NO;\n        for (DoraemonMockScene *s in sceneList) {\n            if (s.selected) {\n                select = s.selected;\n            }\n        }\n        if (!select && sceneList.count>0) {\n            DoraemonMockScene *s = sceneList[0];\n            s.selected = YES;\n        }\n    }\n    \n    if (self.delegate && [self.delegate respondsToSelector:@selector(cellSwitchClick)]) {\n        [self.delegate cellSwitchClick];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/Cell/DoraemonMockBaseCell.h",
    "content": "//\n//  DoraemonMockBaseCell.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonMockBaseModel.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonMockDetailSwitch.h\"\n#import \"DoraemonMockUpLoadModel.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@protocol DoraemonMockBaseCellDelegate<NSObject>\n\n@optional\n- (void)cellExpandClick;\n- (void)sceneBtnClick;\n- (void)cellSwitchClick;\n- (void)previewClick:(DoraemonMockUpLoadModel *)uploadModel;\n\n@end\n\n@interface DoraemonMockBaseCell : UITableViewCell\n\n@property (nonatomic, weak) id<DoraemonMockBaseCellDelegate> delegate;\n@property (nonatomic, strong) DoraemonMockBaseModel *model;\n@property (nonatomic, strong) DoraemonMockDetailSwitch *detailSwitch;\n@property (nonatomic, strong) UILabel *infoLabel;\n\n- (void)renderCellWithData:(DoraemonMockBaseModel *)model;\n\n+ (CGFloat)cellHeightWith:(DoraemonMockBaseModel *)model;\n\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/Cell/DoraemonMockBaseCell.m",
    "content": "//\n//  DoraemonMockBaseCell.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import \"DoraemonMockBaseCell.h\"\n\n@interface DoraemonMockBaseCell()<DoraemonSwitchViewDelegate>\n\n@end\n\n@implementation DoraemonMockBaseCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        self.selectionStyle = UITableViewCellSelectionStyleNone;\n        \n        _detailSwitch = [[DoraemonMockDetailSwitch alloc] initWithFrame:CGRectMake(0, 0, DoraemonScreenWidth, kDoraemonSizeFrom750_Landscape(104))];\n        [_detailSwitch needDownLine];\n        [_detailSwitch needArrow];\n        _detailSwitch.delegate = self;\n        [self.contentView addSubview:_detailSwitch];\n        _detailSwitch.userInteractionEnabled = YES;\n        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(expandClick)];\n        [_detailSwitch addGestureRecognizer:tap];\n\n        _infoLabel = [[UILabel alloc] init];\n        _infoLabel.textColor = [UIColor doraemon_black_1];\n        _infoLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n        _infoLabel.numberOfLines = 0;\n        [self.contentView addSubview:_infoLabel];\n    }\n    return self;\n}\n\n- (void)renderCellWithData:(DoraemonMockBaseModel *)model{\n    _model = model;\n    self.backgroundColor = [UIColor doraemon_bg];\n    \n    [_detailSwitch renderUIWithTitle:model.name switchOn:model.selected];\n    [_detailSwitch setSwitchFrame];\n    [_detailSwitch setArrowDown:model.expand];\n    \n    if(!model||!model.expand){\n        _infoLabel.hidden = YES;\n        return ;\n    }else{\n        _infoLabel.hidden = NO;\n        _infoLabel.text = model.info;\n        CGSize size = [_infoLabel sizeThatFits:CGSizeMake(DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)*2, CGFLOAT_MAX)];\n        _infoLabel.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), _detailSwitch.doraemon_bottom+kDoraemonSizeFrom750_Landscape(24), size.width, size.height);\n    }\n}\n\n+ (CGFloat)cellHeightWith:(DoraemonMockBaseModel *)model{\n    CGFloat cellHeight = kDoraemonSizeFrom750_Landscape(104);\n    if (model && model.expand) {\n        cellHeight += kDoraemonSizeFrom750_Landscape(24);\n        NSString *info = model.info;\n        UILabel *tempLabel = [[UILabel alloc] init];\n        tempLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n        tempLabel.text = info;\n        tempLabel.numberOfLines = 0;\n        CGSize size = [tempLabel sizeThatFits:CGSizeMake(DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)*2, CGFLOAT_MAX)];\n        cellHeight += size.height;\n        cellHeight += kDoraemonSizeFrom750_Landscape(24);\n        \n    }\n    return cellHeight;\n}\n\n- (void)expandClick{\n    _model.expand = !_model.expand;\n    if (_delegate && [_delegate respondsToSelector:@selector(cellExpandClick)]) {\n        [_delegate cellExpandClick];\n    }\n}\n\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/Cell/DoraemonMockUploadCell.h",
    "content": "//\n//  DoraemonMockUploadCell.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import \"DoraemonMockBaseCell.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMockUploadCell : DoraemonMockBaseCell\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/Cell/DoraemonMockUploadCell.m",
    "content": "//\n//  DoraemonMockUploadCell.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import \"DoraemonMockUploadCell.h\"\n#import \"DoraemonMockManager.h\"\n#import \"DoraemonNetworkUtil.h\"\n#import \"DoraemonManager.h\"\n#import \"DoraemonToastUtil.h\"\n\n@interface DoraemonMockUploadCell()\n\n@property (nonatomic, strong) UIView *containerView;\n@property (nonatomic, strong) UIButton *previewBtn;\n@property (nonatomic, strong) UIButton *uploadBtn;\n\n@end\n\n@implementation DoraemonMockUploadCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        _containerView = [[UIView alloc] init];\n        [self.contentView addSubview:_containerView];\n        \n        _previewBtn = [[UIButton alloc] init];\n        [_previewBtn setTitle:DoraemonLocalizedString(@\"数据预览\") forState:UIControlStateNormal];\n        [_previewBtn setTitleColor:[UIColor doraemon_blue] forState:UIControlStateNormal];\n        [_previewBtn addTarget:self action:@selector(preview) forControlEvents:UIControlEventTouchUpInside];\n        [_containerView addSubview:_previewBtn];\n        \n        _uploadBtn = [[UIButton alloc] init];\n        [_uploadBtn setTitle:DoraemonLocalizedString(@\"上传\") forState:UIControlStateNormal];\n        [_uploadBtn setTitleColor:[UIColor doraemon_blue] forState:UIControlStateNormal];\n        [_uploadBtn addTarget:self action:@selector(upload) forControlEvents:UIControlEventTouchUpInside];\n        [_containerView addSubview:_uploadBtn];\n    }\n    return self;\n}\n\n- (void)renderCellWithData:(DoraemonMockBaseModel *)model{\n    [super renderCellWithData: model];\n    if(!model||!model.expand){\n        _containerView.hidden = YES;\n        return ;\n    }else{\n        _containerView.hidden = NO;\n    }\n    \n    [_previewBtn sizeToFit];\n    _previewBtn.frame = CGRectMake(0, 0, _previewBtn.doraemon_width, _previewBtn.doraemon_height);\n    [_uploadBtn sizeToFit];\n    _uploadBtn.frame = CGRectMake(_previewBtn.doraemon_right+kDoraemonSizeFrom750_Landscape(48), 0, _uploadBtn.doraemon_width, _uploadBtn.doraemon_height);\n    \n    _containerView.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), self.infoLabel.doraemon_bottom, DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)*2, _previewBtn.doraemon_height);\n}\n\n+ (CGFloat)cellHeightWith:(DoraemonMockBaseModel *)model{\n    CGFloat cellHeight = [super cellHeightWith:model];\n    \n    if (model && model.expand){\n        cellHeight += kDoraemonSizeFrom750_Landscape(32);\n        cellHeight += kDoraemonSizeFrom750_Landscape(32);\n    }\n\n    return cellHeight;\n}\n\n- (void)preview{\n    DoraemonMockUpLoadModel *upload = (DoraemonMockUpLoadModel *)self.model;\n    if (self.delegate && [self.delegate respondsToSelector:@selector(previewClick:)]) {\n        [self.delegate previewClick:upload];\n    }\n}\n\n- (void)upload{\n    DoraemonMockUpLoadModel *upload = (DoraemonMockUpLoadModel *)self.model;\n    [[DoraemonMockManager sharedInstance] uploadSaveData:upload atView:self];\n}\n\n- (NSString *)convertToJsonData:(NSDictionary *)dict\n{\n    NSError *error;\n    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error];\n    NSString *jsonString;\n\n    if (!jsonData) {\n        DoKitLog(@\"%@\",error);\n    } else {\n        jsonString = [[NSString alloc]initWithData:jsonData encoding:NSUTF8StringEncoding];\n    }\n\n    NSMutableString *mutStr = [NSMutableString stringWithString:jsonString];\n\n    NSRange range = {0,jsonString.length};\n\n    //去掉字符串中的空格\n    [mutStr replaceOccurrencesOfString:@\" \" withString:@\"\" options:NSLiteralSearch range:range];\n\n    NSRange range2 = {0,mutStr.length};\n\n    //去掉字符串中的换行符\n    [mutStr replaceOccurrencesOfString:@\"\\n\" withString:@\"\" options:NSLiteralSearch range:range2];\n\n    return mutStr;\n}\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    self.model.selected = on;\n    \n    // 1、是否开启网络拦截功能 : mock列表中只要有一个选中就需要打开mock功能\n    BOOL needMockOn = NO;\n    for (DoraemonMockAPIModel *api in [DoraemonMockManager sharedInstance].upLoadArray) {\n        if (api.selected) {\n            needMockOn = YES;\n        }\n    }\n    [DoraemonMockManager sharedInstance].mock = needMockOn;\n    \n    if (self.delegate && [self.delegate respondsToSelector:@selector(cellSwitchClick)]) {\n        [self.delegate cellSwitchClick];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/DoraemonMockDetailSwitch.h",
    "content": "//\n//  DoraemonMockCellSwitch.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/23.\n//\n\n#import \"DoraemonCellSwitch.h\"\n\n@interface DoraemonMockDetailSwitch : DoraemonCellSwitch\n\n- (void)needArrow;\n\n- (void)setArrowDown:(BOOL)isDown;\n\n- (void)setSwitchFrame;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/DoraemonMockDetailSwitch.m",
    "content": "//\n//  DoraemonMockCellSwitch.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/23.\n//\n\n#import \"DoraemonMockDetailSwitch.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMockDetailSwitch()\n\n@property (nonatomic, strong) UIImageView *arrow;\n@property (nonatomic, assign) CGFloat size;\n@end\n\n@implementation DoraemonMockDetailSwitch\n\n- (void)needArrow{\n    _size = kDoraemonSizeFrom750_Landscape(24);\n    _arrow = [[UIImageView alloc] init];\n    _arrow.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_mock_detail_up\"];\n    _arrow.frame = CGRectMake(self.doraemon_width - _size*3, self.doraemon_height/2-_arrow.image.size.height/2, _arrow.image.size.width, _arrow.image.size.height);\n    self.backgroundColor = [UIColor whiteColor];\n    [self addSubview:_arrow];\n}\n\n- (void)setSwitchFrame{\n    self.switchView.frame = CGRectMake(_arrow.doraemon_left - self.switchView.doraemon_width-_size*2, self.doraemon_height/2-self.switchView.doraemon_height/2, self.switchView.doraemon_width, self.switchView.doraemon_height);\n}\n\n- (void)setArrowDown:(BOOL)isDown{\n    NSString *imgName = @\"doraemon_mock_detail_up\";\n    if(isDown){\n        imgName = @\"doraemon_mock_detail_down\";\n    }\n    _arrow.image = [UIImage doraemon_xcassetImageNamed:imgName];\n    _arrow.doraemon_width = _arrow.image.size.width;\n    _arrow.doraemon_height =  _arrow.image.size.height;\n}\n\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/DoraemonMockSceneButton.h",
    "content": "//\n//  DoraemonMockDetailButton.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/4.\n//\n\n#import <UIKit/UIKit.h>\n\n@protocol DoraemonMockSceneButtonDelegate<NSObject>\n\n- (void)sceneBtnClick:(NSInteger)tag;\n\n@end\n\n@interface DoraemonMockSceneButton : UIView\n\n@property (nonatomic, weak) id<DoraemonMockSceneButtonDelegate> delegate;\n\n@property (nonatomic, assign) BOOL isSelected;\n\n- (void) renderTitle:(NSString *)title  isSelected:(BOOL)select;\n\n- (void) didSelected;\n\n- (void) cancelSelected;\n\n+ (CGFloat)viewWidth:(NSString *)sceneName;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/DoraemonMockSceneButton.m",
    "content": "//\n//  DoraemonMockDetailButton.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/4.\n//\n\n#import \"DoraemonMockSceneButton.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMockSceneButton()\n\n@property (nonatomic, assign) CGFloat imgSize;\n@property (nonatomic, strong) UIImageView *img;\n@property (nonatomic, strong) UILabel *title;\n\n\n@end\n\n@implementation DoraemonMockSceneButton\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if(self){\n        _imgSize = kDoraemonSizeFrom750_Landscape(32);\n        _img = [[UIImageView alloc] init];\n        [self addSubview:_img];\n        _title = [[UILabel alloc] init];\n        _title.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)];\n        [self addSubview:_title];\n        \n        self.userInteractionEnabled = YES;\n        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];\n        [self addGestureRecognizer:tap];\n    }\n    return self;\n}\n\n- (void) renderTitle:(NSString *)title isSelected:(BOOL)select{\n    _img.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_mock_cancle\"];\n    _img.frame = CGRectMake(0, 0, _imgSize, _imgSize);\n    \n    _title.text = title;\n    _isSelected = select;\n    [_title sizeToFit];\n    _title.frame = CGRectMake(_img.doraemon_right + kDoraemonSizeFrom750_Landscape(8), 0,  _title.doraemon_width, _title.doraemon_height);\n    if(select){\n        [self didSelected];\n    }else{\n        [self cancelSelected];\n    }\n}\n\n- (void) didSelected{\n    _isSelected = YES;\n    _img.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_mock_selected\"];\n    _title.textColor = [UIColor doraemon_blue];\n}\n\n- (void) cancelSelected{\n    _isSelected = NO;\n    _img.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_mock_cancle\"];\n    _title.textColor = [UIColor doraemon_black_1];\n}\n\n- (void)tap{\n    if(_isSelected){\n        [self cancelSelected];\n    }else{\n        [self didSelected];\n    }\n    \n    if (_delegate && [_delegate respondsToSelector:@selector(sceneBtnClick:)]) {\n        [_delegate sceneBtnClick:self.tag];\n    }\n}\n\n+ (CGFloat)viewWidth:(NSString *)sceneName{\n    CGSize fontSize = [sceneName sizeWithAttributes:@{\n        @\"NSFontAttributeName\" : [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)]\n    }];\n    return kDoraemonSizeFrom750_Landscape(32) + kDoraemonSizeFrom750_Landscape(8) + fontSize.width;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/ListView/DoraemonMockApiListView.h",
    "content": "//\n//  DoraemonMockApiListView.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import \"DoraemonMockBaseListView.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMockApiListView : DoraemonMockBaseListView\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/ListView/DoraemonMockApiListView.m",
    "content": "//\n//  DoraemonMockApiListView.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import \"DoraemonMockApiListView.h\"\n#import \"DoraemonMockApiCell.h\"\n#import \"DoraemonMockUtil.h\"\n\n@interface DoraemonMockApiListView()\n\n@end\n\n@implementation DoraemonMockApiListView\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    if (self = [super initWithFrame:frame]) {\n        self.dataArray = [[DoraemonMockManager sharedInstance] filterMockArray];\n    }\n    return self;\n}\n\n- (void)reloadUI{\n    self.dataArray = [[DoraemonMockManager sharedInstance] filterMockArray];\n    [super reloadUI];\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonMockBaseModel* model = [self.dataArray objectAtIndex:indexPath.row];\n    return [DoraemonMockApiCell cellHeightWith:model];\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"detailcell\";\n    DoraemonMockApiCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[DoraemonMockApiCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];\n        cell.delegate = self;\n    }\n    DoraemonMockBaseModel* model = [self.dataArray objectAtIndex:indexPath.row];\n    [cell renderCellWithData:model];\n    return cell;\n}\n\n- (void)cellExpandClick{\n    [self.tableView reloadData];\n}\n\n- (void)sceneBtnClick{\n    [[DoraemonMockUtil sharedInstance] saveMockArrayCache];\n    [self.tableView reloadData];\n}\n\n- (void)cellSwitchClick{\n    [[DoraemonMockUtil sharedInstance] saveMockArrayCache];\n    [self.tableView reloadData];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/ListView/DoraemonMockBaseListView.h",
    "content": "//\n//  DoraemonMockBaseListView.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonMockBaseModel.h\"\n#import \"DoraemonMockManager.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonMockBaseCell.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMockBaseListView : UIView<DoraemonMockBaseCellDelegate>\n\n@property (nonatomic, copy) NSArray<DoraemonMockBaseModel *> *dataArray;\n@property (nonatomic, strong) UITableView *tableView;\n\n- (void)reloadUI;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/ListView/DoraemonMockBaseListView.m",
    "content": "//\n//  DoraemonMockBaseListView.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import \"DoraemonMockBaseListView.h\"\n\n@interface DoraemonMockBaseListView()<UITableViewDelegate,UITableViewDataSource>\n\n@end\n\n@implementation DoraemonMockBaseListView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if(self){\n        _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.doraemon_width, self.doraemon_height)];\n        self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;\n        self.tableView.delegate = self;\n        self.tableView.dataSource = self;\n        [self addSubview:_tableView];        \n    }\n    return self;\n}\n\n- (void)reloadUI{\n    [self.tableView reloadData];\n}\n\n#pragma mark - UITableView Delegate\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {\n    return 1;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return self.dataArray.count;\n}\n\n\n\n- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {\n    return CGFLOAT_MIN;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {\n    return CGFLOAT_MIN;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return CGFLOAT_MIN;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return [[UITableViewCell alloc] init];\n}\n\n\n- (void)cellExpandClick{\n    [self.tableView reloadData];\n}\n\n- (void)sceneBtnClick{\n    [self.tableView reloadData];\n}\n\n- (void)cellSwitchClick{\n    [self.tableView reloadData];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/ListView/DoraemonMockUploadListView.h",
    "content": "//\n//  DoraemonMockUploadListView.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import \"DoraemonMockBaseListView.h\"\n#import \"DoraemonMockUpLoadModel.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n@protocol DoraemonMockUploadListViewDelegate <NSObject>\n\n- (void)previewClick:(DoraemonMockUpLoadModel *)uploadModel;\n\n@end\n\n@interface DoraemonMockUploadListView : DoraemonMockBaseListView\n\n@property (nonatomic, weak) id<DoraemonMockUploadListViewDelegate> delegate;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/List/ListView/DoraemonMockUploadListView.m",
    "content": "//\n//  DoraemonMockUploadListView.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/11/15.\n//\n\n#import \"DoraemonMockUploadListView.h\"\n#import \"DoraemonMockUploadCell.h\"\n#import \"DoraemonMockUtil.h\"\n\n@interface DoraemonMockUploadListView()\n\n@end\n\n@implementation DoraemonMockUploadListView\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    if (self = [super initWithFrame:frame]) {\n        self.dataArray = [[DoraemonMockManager sharedInstance] filterUpLoadArray];\n    }\n    return self;\n}\n\n- (void)reloadUI{\n    self.dataArray = [[DoraemonMockManager sharedInstance] filterUpLoadArray];\n    [super reloadUI];\n}\n\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonMockBaseModel* model = [self.dataArray objectAtIndex:indexPath.row];\n    return [DoraemonMockUploadCell cellHeightWith:model];\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"detailcell\";\n    DoraemonMockUploadCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[DoraemonMockUploadCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];\n        cell.delegate = self;\n    }\n    DoraemonMockBaseModel* model = [self.dataArray objectAtIndex:indexPath.row];\n    [cell renderCellWithData:model];\n    return cell;\n}\n\n#pragma mark - DoraemonMockBaseCellDelegate\n- (void)previewClick:(DoraemonMockUpLoadModel *)uploadModel{\n    if (_delegate && [_delegate respondsToSelector:@selector(previewClick:)]) {\n        [_delegate previewClick:uploadModel];\n    }\n}\n\n- (void)cellSwitchClick{\n    [[DoraemonMockUtil sharedInstance] saveUploadArrayCache];\n    [self.tableView reloadData];\n}\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/Search/DoraemonMockSearchView.h",
    "content": "//\n//  DoraemonMockSearchView.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/23.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@protocol DoraemonMockSearchViewDelegate  <NSObject>\n\n- (void)searchViewInputChange:(NSString *)text;\n\n@end\n\n@interface DoraemonMockSearchView : UIView\n\n@property (nonatomic, strong) UITextField *textField;\n@property (nonatomic, weak) id<DoraemonMockSearchViewDelegate> delegate;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Platform/Mock/View/Search/DoraemonMockSearchView.m",
    "content": "//\n//  DoraemonMockSearchView.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/23.\n//\n\n#import \"DoraemonMockSearchView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMockSearchView()\n\n@property (nonatomic, strong) UIImageView *searchIcon;\n\n@end\n\n@implementation DoraemonMockSearchView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        self.layer.cornerRadius = kDoraemonSizeFrom750_Landscape(8);\n        self.layer.borderWidth = kDoraemonSizeFrom750_Landscape(2);\n        self.layer.borderColor = [UIColor doraemon_colorWithHex:0x999999 andAlpha:0.2].CGColor;\n        \n        _searchIcon = [[UIImageView alloc] initWithImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_search\"]];\n        _searchIcon.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(20), self.doraemon_height/2-_searchIcon.doraemon_height/2, _searchIcon.doraemon_width, _searchIcon.doraemon_height);\n        [self addSubview:_searchIcon];\n        \n        _textField = [[UITextField alloc] initWithFrame:CGRectMake(_searchIcon.doraemon_right+kDoraemonSizeFrom750_Landscape(20), self.doraemon_height/2-kDoraemonSizeFrom750_Landscape(50)/2, self.doraemon_width-_searchIcon.doraemon_right-kDoraemonSizeFrom750_Landscape(20), kDoraemonSizeFrom750_Landscape(50))];\n        _textField.placeholder = DoraemonLocalizedString(@\"请输入您要搜索的关键字\");\n        [_textField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];\n        [self addSubview:_textField];\n    }\n    return self;\n}\n\n-(void)textFieldDidChange:(id)sender{\n    UITextField *senderTextField = (UITextField *)sender;\n    //去除首尾空格\n    NSString *textSearchStr = [senderTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];\n    if (self.delegate && [self.delegate respondsToSelector:@selector(searchViewInputChange:)]) {\n        [self.delegate searchViewInputChange:textSearchStr];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Protocol/DoraemonPluginProtocol.h",
    "content": "//\n//  DoraemonPluginProtocol.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//\n\n#import <Foundation/Foundation.h>\n\n@protocol DoraemonPluginProtocol <NSObject>\n\n@optional\n- (void)pluginDidLoad;\n- (void)pluginDidLoad:(NSDictionary *)itemData;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/Protocol/DoraemonStartPluginProtocol.h",
    "content": "//\n//  DoraemonStartPluginProtocol.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/17.\n//\n\n#import <Foundation/Foundation.h>\n\n@protocol DoraemonStartPluginProtocol <NSObject>\n\n@optional\n- (void)startPluginDidLoad;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ColorPick/DoraemonColorPickPlugin.h",
    "content": "//\n//  DoraemonColorPickPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/3/5.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonColorPickPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ColorPick/DoraemonColorPickPlugin.m",
    "content": "//\n//  DoraemonColorPickPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/3/5.\n//\n\n#import \"DoraemonColorPickPlugin.h\"\n#import \"DoraemonColorPickWindow.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonColorPickInfoWindow.h\"\n\n@implementation DoraemonColorPickPlugin\n\n- (void)pluginDidLoad {\n    [[DoraemonColorPickWindow shareInstance] show];\n    [[DoraemonColorPickInfoWindow shareInstance] show];\n    [[DoraemonHomeWindow shareInstance] hide];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ColorPick/Function/DoraemonColorPickInfoView.h",
    "content": "//\n//  DoraemonColorPickInfoView.h\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/12/3.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n@class DoraemonColorPickInfoView;\n\n@protocol DoraemonColorPickInfoViewDelegate <NSObject>\n\n@optional\n\n- (void)closeBtnClicked:(id)sender onColorPickInfoView:(DoraemonColorPickInfoView *)colorPickInfoView;\n\n@end\n\n@interface DoraemonColorPickInfoView : UIView\n\n@property (nonatomic, weak) id<DoraemonColorPickInfoViewDelegate> delegate;\n\n- (void)setCurrentColor:(NSString *)hexColor;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ColorPick/Function/DoraemonColorPickInfoView.m",
    "content": "//\n//  DoraemonColorPickInfoView.m\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/12/3.\n//\n\n#import \"DoraemonColorPickInfoView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonColorPickInfoView ()\n\n@property (nonatomic, strong) UIView *colorView;\n@property (nonatomic, strong) UILabel *colorValueLbl;\n@property (nonatomic, strong) UIButton *closeBtn;\n\n@end\n\n@implementation DoraemonColorPickInfoView\n\n#pragma mark - Lifecycle\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    if (self) {\n        [self commonInit];\n    }\n    return self;\n}\n\n- (void)commonInit {\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        self.backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {\n            if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {\n                return [UIColor whiteColor];\n            } else {\n                return [UIColor secondarySystemBackgroundColor];\n            }\n        }];\n    } else {\n#endif\n        self.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n    self.layer.cornerRadius = kDoraemonSizeFrom750_Landscape(8);\n    self.layer.borderWidth = 1.;\n    self.layer.borderColor = [UIColor doraemon_colorWithHex:0x999999 andAlpha:0.2].CGColor;\n    \n    [self addSubview:self.colorView];\n    [self addSubview:self.colorValueLbl];\n    [self addSubview:self.closeBtn];\n}\n\n- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {\n    [super traitCollectionDidChange:previousTraitCollection];\n    // trait发生了改变\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {\n            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                [self.closeBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_close_dark\"] forState:UIControlStateNormal];\n            } else {\n                [self.closeBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_close\"] forState:UIControlStateNormal];\n            }\n        }\n    }\n#endif\n}\n\n#pragma mark - Layout\n\n- (void)layoutSubviews {\n    [super layoutSubviews];\n    \n    CGFloat colorWidth = kDoraemonSizeFrom750_Landscape(28);\n    CGFloat colorHeight = kDoraemonSizeFrom750_Landscape(28);\n    self.colorView.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), (self.doraemon_height - colorHeight) / 2.0, colorWidth, colorHeight);\n    \n    CGFloat colorValueWidth = kDoraemonSizeFrom750_Landscape(150);\n    self.colorValueLbl.frame = CGRectMake(self.colorView.doraemon_right + kDoraemonSizeFrom750_Landscape(20), 0, colorValueWidth, self.doraemon_height);\n    \n    CGFloat closeWidth = kDoraemonSizeFrom750_Landscape(44);\n    CGFloat closeHeight = kDoraemonSizeFrom750_Landscape(44);\n    self.closeBtn.frame = CGRectMake(self.doraemon_width - closeWidth - kDoraemonSizeFrom750_Landscape(32), (self.doraemon_height - closeHeight) / 2.0, closeWidth, closeHeight);\n}\n\n#pragma mark - Public\n\n- (void)setCurrentColor:(NSString *)hexColor{\n    self.colorView.backgroundColor = [UIColor doraemon_colorWithHexString:hexColor];\n    self.colorValueLbl.text = hexColor;\n}\n\n#pragma mark - Actions\n\n- (void)closeBtnClicked:(id)sender {\n    if ([self.delegate respondsToSelector:@selector(closeBtnClicked:onColorPickInfoView:)]) {\n        [self.delegate closeBtnClicked:sender onColorPickInfoView:self];\n    }\n}\n\n#pragma mark - Private\n- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {\n    \n    UITouch *touch = [touches anyObject];\n    \n    CGPoint currentPoint = [touch locationInView:self];\n    // 获取上一个点\n    CGPoint prePoint = [touch previousLocationInView:self];\n    CGFloat offsetX = currentPoint.x - prePoint.x;\n    CGFloat offsetY = currentPoint.y - prePoint.y;\n\n    self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);\n}\n\n\n#pragma mark - Getter\n\n- (UIView *)colorView {\n    if (!_colorView) {\n        _colorView = [[UIView alloc] init];\n        _colorView.layer.borderWidth = 1.;\n        _colorView.layer.borderColor = [UIColor doraemon_colorWithHex:0x999999 andAlpha:0.2].CGColor;\n    }\n    return _colorView;\n}\n\n- (UILabel *)colorValueLbl {\n    if (!_colorValueLbl) {\n        _colorValueLbl = [[UILabel alloc] init];\n        _colorValueLbl.textColor = [UIColor doraemon_black_1];\n        _colorValueLbl.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)];\n    }\n    return _colorValueLbl;\n}\n\n- (UIButton *)closeBtn {\n    if (!_closeBtn) {\n        _closeBtn = [[UIButton alloc] init];\n        UIImage *closeImage = [UIImage doraemon_xcassetImageNamed:@\"doraemon_close\"];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {\n                closeImage = [UIImage doraemon_xcassetImageNamed:@\"doraemon_close_dark\"];\n            }\n        }\n#endif\n        [_closeBtn setBackgroundImage:closeImage forState:UIControlStateNormal];\n        [_closeBtn addTarget:self action:@selector(closeBtnClicked:) forControlEvents:UIControlEventTouchUpInside];\n    }\n    return _closeBtn;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ColorPick/Function/DoraemonColorPickInfoWindow.h",
    "content": "//\n//  DoraemonColorPickInfoWindow.h\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/12/4.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonColorPickInfoWindow : UIWindow\n\n+ (DoraemonColorPickInfoWindow *)shareInstance;\n\n- (void)show;\n\n- (void)hide;\n\n- (void)setCurrentColor:(NSString *)hexColor;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ColorPick/Function/DoraemonColorPickInfoWindow.m",
    "content": "//\n//  DoraemonColorPickInfoWindow.m\n//  DoraemonKit\n//\n//  Created by wenquan on 2018/12/4.\n//\n\n#import \"DoraemonColorPickInfoWindow.h\"\n#import \"DoraemonColorPickInfoView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonColorPickInfoController: UIViewController\n\n@end\n\n@implementation DoraemonColorPickInfoController\n- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {\n    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];\n    dispatch_async(dispatch_get_main_queue(), ^{\n        self.view.window.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(30), DoraemonScreenHeight - (size.height < size.width ? size.height : size.width) - kDoraemonSizeFrom750_Landscape(30), size.height, size.width);\n    });\n}\n@end\n\n@interface DoraemonColorPickInfoWindow () <DoraemonColorPickInfoViewDelegate>\n\n@property (nonatomic, strong) DoraemonColorPickInfoView *pickInfoView;\n\n@end\n\n@implementation DoraemonColorPickInfoWindow\n\n#pragma mark - Lifecycle\n\n+ (DoraemonColorPickInfoWindow *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonColorPickInfoWindow *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonColorPickInfoWindow alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init {\n    \n    if (kInterfaceOrientationPortrait) {\n        self = [super initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(30), DoraemonScreenHeight - kDoraemonSizeFrom750_Landscape(100) - kDoraemonSizeFrom750_Landscape(30) - IPHONE_SAFEBOTTOMAREA_HEIGHT, DoraemonScreenWidth - 2*kDoraemonSizeFrom750_Landscape(30), kDoraemonSizeFrom750_Landscape(100))];\n    } else {\n        self = [super initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(30), DoraemonScreenHeight - kDoraemonSizeFrom750_Landscape(100) - kDoraemonSizeFrom750_Landscape(30) - IPHONE_SAFEBOTTOMAREA_HEIGHT, DoraemonScreenHeight - 2*kDoraemonSizeFrom750_Landscape(30), kDoraemonSizeFrom750_Landscape(100))];\n    }\n    \n    if (self) {\n        #if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n            if (@available(iOS 13.0, *)) {\n                for (UIWindowScene* windowScene in [UIApplication sharedApplication].connectedScenes){\n                    if (windowScene.activationState == UISceneActivationStateForegroundActive){\n                        self.windowScene = windowScene;\n                        break;\n                    }\n                }\n            }\n        #endif\n        self.backgroundColor = [UIColor clearColor];\n        self.windowLevel = UIWindowLevelAlert;\n        if (!self.rootViewController) {\n            self.rootViewController = [[DoraemonColorPickInfoController alloc] init];\n        }\n        \n        DoraemonColorPickInfoView *pickInfoView = [[DoraemonColorPickInfoView alloc] initWithFrame:self.bounds];\n        pickInfoView.delegate = self;\n        [self.rootViewController.view addSubview:pickInfoView];\n        self.pickInfoView = pickInfoView;\n        \n        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];\n        [self addGestureRecognizer:pan];\n        \n        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(closePlugin:) name:DoraemonClosePluginNotification object:nil];\n    }\n    return self;\n}\n\n#pragma mark - Public\n\n- (void)show{\n    self.hidden = NO;\n}\n\n- (void)hide{\n    self.hidden = YES;\n}\n\n- (void)setCurrentColor:(NSString *)hexColor {\n    [self.pickInfoView setCurrentColor:hexColor];\n}\n\n#pragma mark - Actions\n\n- (void)pan:(UIPanGestureRecognizer *)sender{\n    //1、获得拖动位移\n    CGPoint offsetPoint = [sender translationInView:sender.view];\n    //2、清空拖动位移\n    [sender setTranslation:CGPointZero inView:sender.view];\n    //3、重新设置控件位置\n    UIView *panView = sender.view;\n    CGFloat newX = panView.doraemon_centerX+offsetPoint.x;\n    CGFloat newY = panView.doraemon_centerY+offsetPoint.y;\n   \n    CGPoint centerPoint = CGPointMake(newX, newY);\n    panView.center = centerPoint;\n}\n \n#pragma mark DoraemonColorPickInfoViewDelegate\n\n- (void)closeBtnClicked:(id)sender onColorPickInfoView:(DoraemonColorPickInfoView *)colorPickInfoView {\n    [[NSNotificationCenter defaultCenter] postNotificationName:DoraemonClosePluginNotification object:nil userInfo:nil];\n}\n\n#pragma mark - Notification\n\n- (void)closePlugin:(NSNotification *)notification{\n    [self hide];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ColorPick/Function/DoraemonColorPickMagnifyLayer.h",
    "content": "//\n//  DoraemonColorPickMagnifyLayer.h\n//  DoraemonKit\n//\n//  Created by wenquan on 2019/1/31.\n//\n\n#import <QuartzCore/QuartzCore.h>\n\nNS_ASSUME_NONNULL_BEGIN\ntypedef NSString* _Nullable (^DoraemonColorPickMagnifyLayerPointColorBlock) (CGPoint point);\n\n@interface DoraemonColorPickMagnifyLayer : CALayer\n\n/**\n 获取指定点的颜色值\n */\n@property (nonatomic, copy) DoraemonColorPickMagnifyLayerPointColorBlock pointColorBlock;\n\n/**\n 目标视图展示位置\n */\n@property (nonatomic, assign) CGPoint targetPoint;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ColorPick/Function/DoraemonColorPickMagnifyLayer.m",
    "content": "//\n//  DoraemonColorPickMagnifyLayer.m\n//  DoraemonKit\n//\n//  Created by wenquan on 2019/1/31.\n//\n\n#import \"DoraemonColorPickMagnifyLayer.h\"\n\n#import <DoraemonKit/UIColor+Doraemon.h>\n\nstatic CGFloat const kMagnifySize = 150; // 放大镜尺寸\nstatic CGFloat const kRimThickness = 3.0; // 放大镜边缘的厚度\nstatic NSInteger const kGridNum = 15; // 放大镜网格的数量\nstatic NSInteger const kPixelSkip = 1; // 采集像素颜色时像素的间隔\n\n@interface DoraemonColorPickMagnifyLayer ()\n\n@property (nonatomic) struct CGPath *gridCirclePath;\n\n@end\n\n@implementation DoraemonColorPickMagnifyLayer\n\n#pragma mark - Lifecycle\n\n- (void)dealloc {\n    if (_gridCirclePath) CGPathRelease(_gridCirclePath);\n}\n\n- (id)init {\n    self = [super init];\n    if (self) {\n        self.bounds = CGRectMake(-kMagnifySize/2, -kMagnifySize/2, kMagnifySize, kMagnifySize);\n        self.anchorPoint = CGPointMake(0.5, 1);\n        \n        UIImage *magnifyImage = [self magnifyImage];\n        CALayer *magnifyLayer = [CALayer layer];\n        magnifyLayer.bounds = self.bounds;\n        magnifyLayer.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));\n        magnifyLayer.contents = (id)magnifyImage.CGImage;\n        magnifyLayer.magnificationFilter = kCAFilterNearest;\n        [self addSublayer:magnifyLayer];\n    }\n    return self;\n}\n\n#pragma mark - Override\n\n- (void)drawInContext:(CGContextRef)ctx {\n    // 对于内部的放大镜进行网格裁剪\n    CGContextAddPath(ctx, self.gridCirclePath);\n    CGContextClip(ctx);\n    // 画网格\n    [self drawGridInContext:ctx];\n}\n\n- (void)drawGridInContext:(CGContextRef)ctx {\n    CGFloat gridSize = ceilf(kMagnifySize/kGridNum);\n    \n    // 由于锚点修改，这里需要偏移\n    CGPoint currentPoint = self.targetPoint;\n    currentPoint.x -= kGridNum*kPixelSkip/2;\n    currentPoint.y -= kGridNum*kPixelSkip/2;\n    NSInteger i,j;\n    \n    // 放大镜中画出网格，并使用当前点和周围点的颜色进行填充\n    for (j=0; j<kGridNum; j++) {\n        for (i=0; i<kGridNum; i++) {\n            CGRect gridRect = CGRectMake(gridSize*i-kMagnifySize/2, gridSize*j-kMagnifySize/2, gridSize, gridSize);\n            UIColor *gridColor = [UIColor clearColor];\n            if (self.pointColorBlock) {\n                NSString *pointColorHexString = self.pointColorBlock(currentPoint);\n                gridColor = [UIColor doraemon_colorWithHexString:pointColorHexString];\n            }\n            CGContextSetFillColorWithColor(ctx, gridColor.CGColor);\n            CGContextFillRect(ctx, gridRect);\n            // 横向寻找下一个相邻点\n            currentPoint.x += kPixelSkip;\n        }\n        // 一行绘制完毕，横向回归起始点，纵向寻找下一个点\n        currentPoint.x -= kGridNum*kPixelSkip;\n        currentPoint.y += kPixelSkip;\n    }\n}\n\n#pragma mark - Private\n\n- (UIImage *)magnifyImage {\n    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);\n    \n    CGContextRef ctx = UIGraphicsGetCurrentContext();\n    \n    CGFloat size = kMagnifySize;\n    CGContextTranslateCTM(ctx, size/2, size/2);\n    \n    // 绘制裁剪区域\n    CGContextSaveGState(ctx);\n    CGContextAddPath(ctx, self.gridCirclePath);\n    CGContextClip(ctx);\n    CGContextRestoreGState(ctx);\n    \n    // 绘制放大镜边缘\n    CGContextSetLineWidth(ctx, kRimThickness);\n    CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);\n    CGContextAddPath(ctx, self.gridCirclePath);\n    CGContextStrokePath(ctx);\n    \n    // 绘制两条边缘线中间的内容\n    CGContextSetLineWidth(ctx, kRimThickness-1);\n    CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);\n    CGContextAddPath(ctx, self.gridCirclePath);\n    CGContextStrokePath(ctx);\n    \n    // 绘制中心的选择区域\n    CGFloat gridWidth = ceilf(kMagnifySize/kGridNum);\n    CGFloat xyOffset = -(gridWidth+1)/2;\n    CGRect selectedRect = CGRectMake(xyOffset, xyOffset, gridWidth, gridWidth);\n    CGContextAddRect(ctx, selectedRect);\n    \n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    if (@available(iOS 13.0, *)) {\n        UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {\n            if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {\n                return [UIColor blackColor];\n            }\n            else {\n                return [UIColor whiteColor];\n            }\n        }];\n        CGContextSetStrokeColorWithColor(ctx, dyColor.CGColor);\n    } else {\n#endif\n        CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n    }\n#endif\n    CGContextSetLineWidth(ctx, 1.0);\n    CGContextStrokePath(ctx);\n    \n    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();\n    UIGraphicsEndImageContext();\n    return image;\n}\n\n#pragma mark - Getter\n\n- (struct CGPath *)gridCirclePath {\n    if (_gridCirclePath == NULL) {\n        CGMutablePathRef circlePath = CGPathCreateMutable();\n        const CGFloat radius = kMagnifySize/2;\n        CGPathAddArc(circlePath, nil, 0, 0, radius-kRimThickness/2, 0, 2*M_PI, YES);\n        _gridCirclePath = circlePath;\n    }\n    return _gridCirclePath;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ColorPick/Function/DoraemonColorPickView.h",
    "content": "//\n//  DoraemonColorPickView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/3/6.\n//\n\n#import <UIKit/UIKit.h>\n\n\n@interface DoraemonColorPickView : UIView\n\n- (void)setCurrentImage:(UIImage *)image;\n\n- (void)setCurrentColor:(NSString *)hexColor;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ColorPick/Function/DoraemonColorPickView.m",
    "content": "//\n//  DoraemonColorPickView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/3/6.\n//\n\n#import \"DoraemonColorPickView.h\"\n#import \"UIView+Doraemon.h\"\n#import \"UIImage+Doraemon.h\"\n#import \"UIColor+Doraemon.h\"\n\n@interface DoraemonColorPickView()\n\n@property (nonatomic, strong) UIImageView *circleView;\n@property (nonatomic, strong) UILabel *colorLabel;\n\n@end\n\n@implementation DoraemonColorPickView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        _circleView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.doraemon_width, self.doraemon_height)];\n        _circleView.layer.masksToBounds = YES;\n        _circleView.layer.cornerRadius = self.doraemon_width/2;\n        _circleView.layer.borderWidth = 5;\n        _circleView.layer.borderColor = [UIColor clearColor].CGColor;\n        [self addSubview:_circleView];\n        \n        UIBezierPath *path1 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(self.doraemon_left-0.5, self.doraemon_top-0.5, self.doraemon_width+1, self.doraemon_height+1)];\n        CAShapeLayer *layer1 = [CAShapeLayer layer];\n        layer1.lineWidth = 1;\n        layer1.strokeColor = [UIColor lightGrayColor].CGColor;\n        layer1.fillColor = [UIColor clearColor].CGColor;\n        layer1.path = path1.CGPath;\n        [self.layer addSublayer:layer1];\n        \n        UIBezierPath *path2 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(self.doraemon_left+5, self.doraemon_top+5, self.doraemon_width-10, self.doraemon_height-10)];\n        CAShapeLayer *layer2 = [CAShapeLayer layer];\n        layer2.lineWidth = 1;\n        layer2.strokeColor = [UIColor lightGrayColor].CGColor;\n        layer2.fillColor = [UIColor clearColor].CGColor;\n        layer2.path = path2.CGPath;\n        [self.layer addSublayer:layer2];\n        \n        UIView *pointView = [[UIView alloc] init];\n        pointView.doraemon_size = CGSizeMake(6, 6);\n        pointView.center = self.center;\n        pointView.backgroundColor = [UIColor blackColor];\n        pointView.layer.cornerRadius = 1.5;\n        [self addSubview:pointView];\n        \n        UILabel *colorLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 15, self.doraemon_width, 12)];\n        colorLabel.backgroundColor = [UIColor clearColor];\n        colorLabel.font = [UIFont systemFontOfSize:12];\n        colorLabel.textColor = [UIColor blackColor];\n        colorLabel.textAlignment = NSTextAlignmentCenter;\n        [self addSubview:colorLabel];\n        self.colorLabel = colorLabel;\n    }\n    return self;\n}\n\n- (void)setCurrentImage:(UIImage *)image{\n    _circleView.image = image;\n}\n\n- (void)setCurrentColor:(NSString *)hexColor{\n    _circleView.layer.borderColor = [UIColor doraemon_colorWithHexString:hexColor].CGColor;\n    _colorLabel.text = hexColor;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ColorPick/Function/DoraemonColorPickWindow.h",
    "content": "//\n//  DoraemonColorPickWindow.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/3/5.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface DoraemonColorPickWindow : UIWindow\n\n+ (DoraemonColorPickWindow *)shareInstance;\n\n- (void)show;\n\n- (void)hide;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ColorPick/Function/DoraemonColorPickWindow.m",
    "content": "//\n//  DoraemonColorPickWindow.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/3/5.\n//\n\n#import \"DoraemonColorPickWindow.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonColorPickView.h\"\n#import \"UIImage+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n#import \"UIColor+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonColorPickInfoWindow.h\"\n#import \"DoraemonColorPickMagnifyLayer.h\"\n\nstatic CGFloat const kColorPickWindowSize = 150;\n\n@interface DoraemonColorPickWindow()\n\n// 这里先屏蔽掉，先使用layer自带的圆圈\n//@property (nonatomic, strong) DoraemonColorPickView *colorPickView;\n\n@property (nonatomic, strong) DoraemonColorPickMagnifyLayer *magnifyLayer;\n\n@property (nonatomic, strong) UIImage *screenShotImage;\n\n@end\n\n@implementation DoraemonColorPickWindow\n\n#pragma mark - Lifecycle\n\n+ (DoraemonColorPickWindow *)shareInstance {\n    static dispatch_once_t once;\n    static DoraemonColorPickWindow *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonColorPickWindow alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init {\n    self = [super initWithFrame:CGRectMake(DoraemonScreenWidth/2-kColorPickWindowSize/2, DoraemonScreenHeight/2-kColorPickWindowSize/2, kColorPickWindowSize, kColorPickWindowSize)];\n    if (self) {\n        #if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n            if (@available(iOS 13.0, *)) {\n                for (UIWindowScene* windowScene in [UIApplication sharedApplication].connectedScenes){\n                    if (windowScene.activationState == UISceneActivationStateForegroundActive){\n                        self.windowScene = windowScene;\n                        break;\n                    }\n                }\n            }\n        #endif\n        self.backgroundColor = [UIColor clearColor];\n        self.windowLevel = UIWindowLevelStatusBar + 1.f;\n        if (!self.rootViewController) {\n            self.rootViewController = [[UIViewController alloc] init];\n        }\n        \n        //        DoraemonColorPickView *colorPickView = [[DoraemonColorPickView alloc] initWithFrame:self.bounds];\n        //        colorPickView.backgroundColor = [UIColor clearColor];\n        //        [self.rootViewController.view addSubview:colorPickView];\n        //        self.colorPickView = colorPickView;\n        \n        self.magnifyLayer.frame = self.bounds;\n        __weak __typeof(self)weakSelf = self;\n        self.magnifyLayer.pointColorBlock = ^(CGPoint currentPoint) {\n            __strong __typeof(weakSelf) strongSelf = weakSelf;\n            return [strongSelf colorAtPoint:currentPoint];\n        };\n        [self.layer addSublayer:self.magnifyLayer];\n        \n        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];\n        [self addGestureRecognizer:pan];\n        \n        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(closePlugin:) name:DoraemonClosePluginNotification object:nil];\n        \n        //        NSString *hexColor = [self getColorWithCenterPoint:self.center];\n        //    [self.colorPickView setCurrentColor:hexColor];\n        //        [[DoraemonColorPickInfoWindow shareInstance] setCurrentColor:hexColor];\n    }\n    return self;\n}\n\n#pragma mark - Public\n\n- (void)show {\n    self.hidden = NO;\n}\n\n- (void)hide {\n    self.hidden = YES;\n}\n\n- (NSString *)colorAtPoint:(CGPoint)point {\n    return [self colorAtPoint:point inImage:self.screenShotImage];\n}\n\n#pragma mark - Private\n\n- (void)updateScreeShotImage {\n    UIGraphicsBeginImageContext([UIScreen mainScreen].bounds.size);\n    [[DoraemonUtil getKeyWindow].layer renderInContext:UIGraphicsGetCurrentContext()];\n    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();\n    UIGraphicsEndImageContext();\n    \n    self.screenShotImage = image;\n}\n\n- (NSString *)colorAtPoint:(CGPoint)point inImage:(UIImage *)image {\n    // Cancel if point is outside image coordinates\n    if (!image || !CGRectContainsPoint(CGRectMake(0.0f, 0.0f, image.size.width, image.size.height), point)) {\n        return nil;\n    }\n    \n    // Create a 1x1 pixel byte array and bitmap context to draw the pixel into.\n    // Reference: http://stackoverflow.com/questions/1042830/retrieving-a-pixel-alpha-value-for-a-uiimage\n    NSInteger pointX = trunc(point.x);\n    NSInteger pointY = trunc(point.y);\n    CGImageRef cgImage = image.CGImage;\n    NSUInteger width = image.size.width;\n    NSUInteger height = image.size.height;\n    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();\n    int bytesPerPixel = 4;\n    int bytesPerRow = bytesPerPixel * 1;\n    NSUInteger bitsPerComponent = 8;\n    unsigned char pixelData[4] = { 0, 0, 0, 0 };\n    CGContextRef context = CGBitmapContextCreate(pixelData,\n                                                 1,\n                                                 1,\n                                                 bitsPerComponent,\n                                                 bytesPerRow,\n                                                 colorSpace,\n                                                 kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);\n    CGColorSpaceRelease(colorSpace);\n    CGContextSetBlendMode(context, kCGBlendModeCopy);\n    \n    // Draw the pixel we are interested in onto the bitmap context\n    CGContextTranslateCTM(context, -pointX, pointY-(CGFloat)height);\n    CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, (CGFloat)width, (CGFloat)height), cgImage);\n    CGContextRelease(context);\n    \n    NSString *hexColor = [NSString stringWithFormat:@\"#%02x%02x%02x\",pixelData[0],pixelData[1],pixelData[2]];\n    return hexColor;\n}\n\n#pragma mark - Actions\n\n- (void)pan:(UIPanGestureRecognizer *)sender {\n    if (sender.state == UIGestureRecognizerStateBegan) {\n        // 开始拖动的时候更新屏幕快照\n        [self updateScreeShotImage];\n    }\n    \n    //1、获得拖动位移\n    CGPoint offsetPoint = [sender translationInView:sender.view];\n    //2、清空拖动位移\n    [sender setTranslation:CGPointZero inView:sender.view];\n    //3、重新设置控件位置\n    UIView *panView = sender.view;\n    CGFloat newX = panView.doraemon_centerX+offsetPoint.x;\n    CGFloat newY = panView.doraemon_centerY+offsetPoint.y;\n    \n    [CATransaction begin];\n    [CATransaction setDisableActions:YES];\n    \n    CGPoint centerPoint = CGPointMake(newX, newY);\n    panView.center = centerPoint;\n    \n    self.magnifyLayer.targetPoint = centerPoint;\n    \n    // update positions\n    //    self.magnifyLayer.position = centerPoint;\n    \n    // Make magnifyLayer sharp on screen\n    CGRect magnifyFrame     = self.magnifyLayer.frame;\n    magnifyFrame.origin     = CGPointMake(round(magnifyFrame.origin.x), round(magnifyFrame.origin.y));\n    self.magnifyLayer.frame = magnifyFrame;\n    [self.magnifyLayer setNeedsDisplay];\n    \n    [CATransaction commit];\n    \n    NSString *hexColor = [self colorAtPoint:centerPoint];\n    [[DoraemonColorPickInfoWindow shareInstance] setCurrentColor:hexColor];\n}\n\n#pragma mark - Notification\n\n- (void)closePlugin:(NSNotification *)notification{\n    self.hidden = YES;\n}\n\n#pragma mark - Getter\n\n- (DoraemonColorPickMagnifyLayer *)magnifyLayer {\n    if (!_magnifyLayer) {\n        _magnifyLayer = [DoraemonColorPickMagnifyLayer layer];\n        _magnifyLayer.contentsScale = [[UIScreen mainScreen] scale];\n    }\n    return _magnifyLayer;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/DoraemonHierarchyPlugin.h",
    "content": "//\n//  DoraemonHierarchyPlugin.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHierarchyPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/DoraemonHierarchyPlugin.m",
    "content": "//\n//  DoraemonHierarchyPlugin.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyPlugin.h\"\n#import \"DoraemonHierarchyWindow.h\"\n#import \"DoraemonHierarchyHelper.h\"\n#import \"DoraemonHomeWindow.h\"\n\n\n@implementation DoraemonHierarchyPlugin\n\n- (void)pluginDidLoad {\n    DoraemonHierarchyWindow *window = [[DoraemonHierarchyWindow alloc] init];\n    [DoraemonHierarchyHelper shared].window = window;\n    [window show];\n    [[DoraemonHomeWindow shareInstance] hide];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/Function/Category/NSObject+DoraemonHierarchy.h",
    "content": "//\n//  NSObject+DoraemonHierarchy.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\nFOUNDATION_EXPORT NSNotificationName _Nonnull const DoraemonHierarchyChangeNotificationName;\n\n@class DoraemonHierarchyCategoryModel;\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface NSObject (DoraemonHierarchy)\n\n- (NSArray <DoraemonHierarchyCategoryModel *>*)doraemon_hierarchyCategoryModels;\n\n- (void)doraemon_showIntAlertAndAutomicSetWithKeyPath:(NSString *)keyPath;\n\n- (void)doraemon_showFrameAlertAndAutomicSetWithKeyPath:(NSString *)keyPath;\n\n- (void)doraemon_showColorAlertAndAutomicSetWithKeyPath:(NSString *)keyPath;\n\n- (void)doraemon_showFontAlertAndAutomicSetWithKeyPath:(NSString *)keyPath;\n\n- (UIColor *)doraemon_hashColor;\n\n@end\n\n@interface UIView (DoraemonHierarchy)\n\n- (NSArray <DoraemonHierarchyCategoryModel *>*)doraemon_sizeHierarchyCategoryModels;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/Function/Category/NSObject+DoraemonHierarchy.m",
    "content": "//\n//  NSObject+DoraemonHierarchy.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"NSObject+DoraemonHierarchy.h\"\n#import \"UIViewController+DoraemonHierarchy.h\"\n#import \"DoraemonHierarchyFormatterTool.h\"\n#import \"DoraemonHierarchyCategoryModel.h\"\n#import \"DoraemonHierarchyCellModel.h\"\n#import \"UIColor+DoraemonHierarchy.h\"\n#import \"DoraemonEnumDescription.h\"\n#import \"DoraemonHierarchyHelper.h\"\n#import \"UIColor+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n\nNSNotificationName const DoraemonHierarchyChangeNotificationName = @\"DoraemonHierarchyChangeNotificationName\";\n\n@implementation NSObject (DoraemonHierarchy)\n\n#pragma mark - Public\n- (NSArray <DoraemonHierarchyCategoryModel *>*)doraemon_hierarchyCategoryModels {\n    \n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Class Name\" detailTitle:NSStringFromClass(self.class)] noneInsets];\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Address\" detailTitle:[NSString stringWithFormat:@\"%p\",self]] noneInsets];\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Description\" detailTitle:self.description] noneInsets];\n    [settings addObject:model3];\n    \n    return @[[[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Object\" items:settings]];\n}\n\n- (void)doraemon_showIntAlertAndAutomicSetWithKeyPath:(NSString *)keyPath {\n    __weak typeof(self) weakSelf = self;\n    [self doraemon_showTextFieldAlertWithText:[NSString stringWithFormat:@\"%@\",[self valueForKeyPath:keyPath]] handler:^(NSString * _Nullable newText) {\n        [weakSelf setValue:@([newText integerValue]) forKeyPath:keyPath];\n    }];\n}\n\n- (void)doraemon_showFrameAlertAndAutomicSetWithKeyPath:(NSString *)keyPath {\n    __block NSValue *value = [self valueForKeyPath:keyPath];\n    __weak typeof(self) weakSelf = self;\n    [self doraemon_showTextFieldAlertWithText:NSStringFromCGRect([value CGRectValue]) handler:^(NSString * _Nullable newText) {\n        [weakSelf setValue:[NSValue valueWithCGRect:[weakSelf doraemon_rectFromString:newText originalRect:[value CGRectValue]]] forKeyPath:keyPath];\n    }];\n}\n\n- (void)doraemon_showColorAlertAndAutomicSetWithKeyPath:(NSString *)keyPath {\n    __block UIColor *color = [self valueForKeyPath:keyPath];\n    if (color && ![color isKindOfClass:[UIColor class]]) {\n        return;\n    }\n    __weak typeof(self) weakSelf = self;\n    [self doraemon_showTextFieldAlertWithText:[color doraemon_HexString] handler:^(NSString * _Nullable newText) {\n        [weakSelf setValue:[weakSelf doraemon_colorFromString:newText originalColor:color] forKeyPath:keyPath];\n    }];\n}\n\n- (void)doraemon_showFontAlertAndAutomicSetWithKeyPath:(NSString *)keyPath {\n    __block UIFont *font = [self valueForKeyPath:keyPath];\n    __weak typeof(self) weakSelf = self;\n    [self doraemon_showTextFieldAlertWithText:[DoraemonHierarchyFormatterTool formatNumber:@(font.pointSize)] handler:^(NSString * _Nullable newText) {\n        [weakSelf setValue:[font fontWithSize:[newText doubleValue]] forKeyPath:keyPath];\n    }];\n}\n\n- (UIColor *)doraemon_hashColor {\n    CGFloat hue = ((self.hash >> 4) % 256) / 255.0;\n    return [UIColor colorWithHue:hue saturation:1.0 brightness:1.0 alpha:1.0];\n}\n\n#pragma mark - Primary\n- (NSString *)doraemon_hierarchyColorDescription:(UIColor *_Nullable)color {\n    if (!color) {\n        return @\"<nil color>\";\n    }\n    \n    CGFloat r = [color red];\n    CGFloat g = [color green];\n    CGFloat b = [color blue];\n    CGFloat a = [color alpha];\n    NSString *rgb = [NSString stringWithFormat:@\"R:%@ G:%@ B:%@ A:%@\", [DoraemonHierarchyFormatterTool formatNumber:@(r)], [DoraemonHierarchyFormatterTool formatNumber:@(g)], [DoraemonHierarchyFormatterTool formatNumber:@(b)], [DoraemonHierarchyFormatterTool formatNumber:@(a)]];\n    \n    NSString *colorName = [color doraemon_systemColorName];\n    \n    return colorName ? [rgb stringByAppendingFormat:@\"\\n%@\",colorName] : [rgb stringByAppendingFormat:@\"\\n%@\",[color doraemon_HexString]];\n}\n\n- (UIColor *)doraemon_colorFromString:(NSString *)string originalColor:(UIColor *)color {\n    UIColor *newColor = [UIColor doraemon_colorWithHexString:string];\n    if (!newColor) {\n        return color;\n    }\n    return newColor;\n}\n\n- (NSString *)doraemon_hierarchyBoolDescription:(BOOL)flag {\n    return flag ? @\"On\" : @\"Off\";\n}\n\n- (NSString *)doraemon_hierarchyImageDescription:(UIImage *)image {\n    return image ? image.description : @\"No image\";\n}\n\n- (NSString *)doraemon_hierarchyObjectDescription:(NSObject *)obj {\n    NSString *text = @\"<null>\";\n    if (obj) {\n        text = [NSString stringWithFormat:@\"%@\",obj];\n    }\n    if ([text length] == 0) {\n        text = @\"<empty string>\";\n    }\n    return text;\n}\n\n- (NSString *)doraemon_hierarchyDateDescription:(NSDate *)date {\n    if (!date) {\n        return @\"<null>\";\n    }\n    return [DoraemonHierarchyFormatterTool stringFromDate:date] ?: @\"<null>\";\n}\n\n- (NSString *)doraemon_hierarchyTextDescription:(NSString *)text {\n    if (text == nil) {\n        return @\"<nil>\";\n    }\n    if ([text length] == 0) {\n        return @\"<empty string>\";\n    }\n    return text;\n}\n\n- (NSString *)doraemon_hierarchyPointDescription:(CGPoint)point {\n    return [NSString stringWithFormat:@\"X: %@   Y: %@\",[DoraemonHierarchyFormatterTool formatNumber:@(point.x)],[DoraemonHierarchyFormatterTool formatNumber:@(point.y)]];\n}\n\n- (CGPoint)doraemon_pointFromString:(NSString *)string orginalPoint:(CGPoint)point {\n    CGPoint newPoint = CGPointFromString(string);\n    return newPoint;\n}\n\n- (NSString *)doraemon_hierarchySizeDescription:(CGSize)size {\n    return [NSString stringWithFormat:@\"W: %@   H: %@\",[DoraemonHierarchyFormatterTool formatNumber:@(size.width)], [DoraemonHierarchyFormatterTool formatNumber:@(size.height)]];\n}\n\n- (CGRect)doraemon_rectFromString:(NSString *)string originalRect:(CGRect)rect {\n    CGRect newRect = CGRectFromString(string);\n    if (CGRectEqualToRect(newRect, CGRectZero) && ![string isEqualToString:NSStringFromCGRect(CGRectZero)]) {\n        // Wrong text.\n        return rect;\n    }\n    return newRect;\n}\n\n- (CGSize)doraemon_sizeFromString:(NSString *)string originalSize:(CGSize)size {\n    CGSize newSize = CGSizeFromString(string);\n    if (CGSizeEqualToSize(newSize, CGSizeZero) && ![string isEqualToString:NSStringFromCGSize(CGSizeZero)]) {\n        // Wrong text.\n        return size;\n    }\n    return newSize;\n}\n\n- (NSString *)doraemon_hierarchyInsetsTopBottomDescription:(UIEdgeInsets)insets {\n    return [NSString stringWithFormat:@\"top %@    bottom %@\",[DoraemonHierarchyFormatterTool formatNumber:@(insets.top)], [DoraemonHierarchyFormatterTool formatNumber:@(insets.bottom)]];\n}\n\n- (UIEdgeInsets)doraemon_insetsFromString:(NSString *)string originalInsets:(UIEdgeInsets)insets {\n    UIEdgeInsets newInsets = UIEdgeInsetsFromString(string);\n    if (UIEdgeInsetsEqualToEdgeInsets(newInsets, UIEdgeInsetsZero) && ![string isEqualToString:NSStringFromUIEdgeInsets(UIEdgeInsetsZero)]) {\n        // Wrong text.\n        return insets;\n    }\n    return newInsets;\n}\n\n- (NSString *)doraemon_hierarchyInsetsLeftRightDescription:(UIEdgeInsets)insets {\n    return [NSString stringWithFormat:@\"left %@    right %@\",[DoraemonHierarchyFormatterTool formatNumber:@(insets.left)], [DoraemonHierarchyFormatterTool formatNumber:@(insets.right)]];\n}\n\n- (NSString *)doraemon_hierarchyOffsetDescription:(UIOffset)offset {\n    return [NSString stringWithFormat:@\"h %@   v %@\",[DoraemonHierarchyFormatterTool formatNumber:@(offset.horizontal)], [DoraemonHierarchyFormatterTool formatNumber:@(offset.vertical)]];\n}\n\n- (void)doraemon_showDoubleAlertAndAutomicSetWithKeyPath:(NSString *)keyPath {\n    __weak typeof(self) weakSelf = self;\n    [self doraemon_showTextFieldAlertWithText:[DoraemonHierarchyFormatterTool formatNumber:[self valueForKeyPath:keyPath]] handler:^(NSString * _Nullable newText) {\n        [weakSelf setValue:@([newText doubleValue]) forKeyPath:keyPath];\n    }];\n}\n\n- (void)doraemon_showPointAlertAndAutomicSetWithKeyPath:(NSString *)keyPath {\n    __block NSValue *value = [self valueForKeyPath:keyPath];\n    __weak typeof(self) weakSelf = self;\n    [self doraemon_showTextFieldAlertWithText:NSStringFromCGPoint([value CGPointValue]) handler:^(NSString * _Nullable newText) {\n        [weakSelf setValue:[NSValue valueWithCGPoint:[weakSelf doraemon_pointFromString:newText orginalPoint:[value CGPointValue]]] forKeyPath:keyPath];\n    }];\n}\n\n- (void)doraemon_showEdgeInsetsAndAutomicSetWithKeyPath:(NSString *)keyPath {\n    __block NSValue *value = [self valueForKeyPath:keyPath];\n    __weak typeof(self) weakSelf = self;\n    [self doraemon_showTextFieldAlertWithText:NSStringFromUIEdgeInsets([value UIEdgeInsetsValue]) handler:^(NSString * _Nullable newText) {\n        [weakSelf setValue:[NSValue valueWithUIEdgeInsets:[weakSelf doraemon_insetsFromString:newText originalInsets:[value UIEdgeInsetsValue]]] forKeyPath:keyPath];\n    }];\n}\n\n- (void)doraemon_showTextAlertAndAutomicSetWithKeyPath:(NSString *)keyPath {\n    __weak typeof(self) weakSelf = self;\n    [self doraemon_showTextFieldAlertWithText:[self valueForKeyPath:keyPath] handler:^(NSString * _Nullable newText) {\n        [weakSelf setValue:newText forKeyPath:keyPath];\n    }];\n}\n\n- (void)doraemon_showAttributeTextAlertAndAutomicSetWithKeyPath:(NSString *)keyPath {\n    __block NSAttributedString *attribute = [self valueForKeyPath:keyPath];\n    __weak typeof(self) weakSelf = self;\n    [self doraemon_showTextFieldAlertWithText:attribute.string handler:^(NSString * _Nullable newText) {\n        NSMutableAttributedString *mutAttribute = [[NSMutableAttributedString alloc] initWithAttributedString:attribute];\n        [mutAttribute replaceCharactersInRange:NSMakeRange(0, attribute.string.length) withString:newText];\n        [weakSelf setValue:[mutAttribute copy] forKeyPath:keyPath];\n    }];\n}\n\n- (void)doraemon_showSizeAlertAndAutomicSetWithKeyPath:(NSString *)keyPath {\n    __block NSValue *value = [self valueForKeyPath:keyPath];\n    __weak typeof(self) weakSelf = self;\n    [self doraemon_showTextFieldAlertWithText:NSStringFromCGSize([value CGSizeValue]) handler:^(NSString * _Nullable newText) {\n        [weakSelf setValue:[NSValue valueWithCGSize:[weakSelf doraemon_sizeFromString:newText originalSize:[value CGSizeValue]]] forKeyPath:keyPath];\n    }];\n}\n\n- (void)doraemon_showDateAlertAndAutomicSetWithKeyPath:(NSString *)keyPath {\n    NSDate *date = [self valueForKeyPath:keyPath];\n    __weak typeof(self) weakSelf = self;\n    [self doraemon_showTextFieldAlertWithText:[DoraemonHierarchyFormatterTool stringFromDate:date] handler:^(NSString * _Nullable newText) {\n        NSDate *newDate = [DoraemonHierarchyFormatterTool dateFromString:newText];\n        if (newDate) {\n            [weakSelf setValue:newDate forKeyPath:keyPath];\n        }\n    }];\n}\n\n- (void)doraemon_showTextFieldAlertWithText:(NSString *)text handler:(nullable void (^)(NSString * _Nullable newText))handler {\n    __weak typeof(self) weakSelf = self;\n    UIWindow *window = (UIWindow *)[DoraemonHierarchyHelper shared].window;\n    [window.rootViewController.doraemon_currentShowingViewController doraemon_showTextFieldAlertControllerWithMessage:@\"Change Property\" text:text handler:^(NSString * _Nullable newText) {\n        if (handler) {\n            handler(newText);\n        }\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    }];\n}\n\n- (void)doraemon_showActionSheetWithActions:(NSArray *)actions currentAction:(NSString *)currentAction completion:(void (^)(NSInteger index))completion {\n    __weak typeof(self) weakSelf = self;\n    UIWindow *window = (UIWindow *)[DoraemonHierarchyHelper shared].window;\n    [window.rootViewController.doraemon_currentShowingViewController doraemon_showActionSheetWithTitle:@\"Change Property\" actions:actions currentAction:currentAction completion:^(NSInteger index) {\n        if (completion) {\n            completion(index);\n        }\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    }];\n}\n\n- (void)doraemon_postHierarchyChangeNotification {\n    [[NSNotificationCenter defaultCenter] postNotificationName:DoraemonHierarchyChangeNotificationName object:self];\n}\n\n- (void)doraemon_replaceAttributeString:(NSString *)newString key:(NSString *)key {\n    NSAttributedString *string = [self valueForKey:key];\n    if (string && ![string isKindOfClass:[NSAttributedString class]]) {\n        return;\n    }\n    NSMutableAttributedString *attribute = string ? [[NSMutableAttributedString alloc] initWithAttributedString:string] : [[NSMutableAttributedString alloc] init];\n    [attribute replaceCharactersInRange:NSMakeRange(0, string.length) withString:newString];\n    [self setValue:string forKey:key];\n}\n\n@end\n\n@implementation UIView (DoraemonHierarchy)\n\n#pragma mark - Public\n- (NSArray <DoraemonHierarchyCategoryModel *>*)doraemon_sizeHierarchyCategoryModels {\n    __weak typeof(self) weakSelf = self;\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Frame\" detailTitle:[self doraemon_hierarchyPointDescription:self.frame.origin]] noneInsets];\n    model1.block = ^{\n        [weakSelf doraemon_showFrameAlertAndAutomicSetWithKeyPath:@\"frame\"];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[self doraemon_hierarchySizeDescription:self.frame.size]] noneInsets];\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Bounds\" detailTitle:[self doraemon_hierarchyPointDescription:self.bounds.origin]] noneInsets];\n    model3.block = ^{\n        [weakSelf doraemon_showFrameAlertAndAutomicSetWithKeyPath:@\"bounds\"];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[self doraemon_hierarchySizeDescription:self.bounds.size]] noneInsets];\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Center\" detailTitle:[self doraemon_hierarchyPointDescription:self.center]] noneInsets];\n    model5.block = ^{\n        [weakSelf doraemon_showPointAlertAndAutomicSetWithKeyPath:@\"center\"];\n    };\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Position\" detailTitle:[self doraemon_hierarchyPointDescription:self.layer.position]] noneInsets];\n    model6.block = ^{\n        [weakSelf doraemon_showPointAlertAndAutomicSetWithKeyPath:@\"layer.position\"];\n    };\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Z Position\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.layer.zPosition)]];\n    model7.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"layer.zPosition\"];\n    };\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCellModel *model8 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Anchor Point\" detailTitle:[self doraemon_hierarchyPointDescription:self.layer.anchorPoint]] noneInsets];\n    model8.block = ^{\n        [weakSelf doraemon_showPointAlertAndAutomicSetWithKeyPath:@\"layer.anchorPoint\"];\n    };\n    [settings addObject:model8];\n    \n    DoraemonHierarchyCellModel *model9 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Anchor Point Z\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.layer.anchorPointZ)]];\n    model9.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"layer.anchorPointZ\"];\n    };\n    [settings addObject:model9];\n\n    DoraemonHierarchyCellModel *lastConstrainModel = nil;\n    \n    for (NSLayoutConstraint *constrain in self.constraints) {\n        if (!constrain.shouldBeArchived) {\n            continue;\n        }\n        NSString *constrainDesc = [self doraemon_hierarchyLayoutConstraintDescription:constrain];\n        if (constrainDesc) {\n            DoraemonHierarchyCellModel *mod = [[[DoraemonHierarchyCellModel alloc] initWithTitle:lastConstrainModel ? nil : @\"Constrains\" detailTitle:constrainDesc] noneInsets];\n            __weak NSLayoutConstraint *cons = constrain;\n            mod.block = ^{\n                [weakSelf doraemon_showTextFieldAlertWithText:[DoraemonHierarchyFormatterTool formatNumber:@(cons.constant)] handler:^(NSString * _Nullable newText) {\n                    cons.constant = [newText doubleValue];\n                    [weakSelf setNeedsLayout];\n                }];\n            };\n            [settings addObject:mod];\n            lastConstrainModel = mod;\n        }\n    }\n    \n    for (NSLayoutConstraint *constrain in self.superview.constraints) {\n        if (!constrain.shouldBeArchived) {\n            continue;\n        }\n        if (constrain.firstItem == self || constrain.secondItem == self) {\n            NSString *constrainDesc = [self doraemon_hierarchyLayoutConstraintDescription:constrain];\n            if (constrainDesc) {\n                DoraemonHierarchyCellModel *mod = [[[DoraemonHierarchyCellModel alloc] initWithTitle:lastConstrainModel ? nil : @\"Constrains\" detailTitle:constrainDesc] noneInsets];\n                __weak NSLayoutConstraint *cons = constrain;\n                mod.block = ^{\n                    [weakSelf doraemon_showTextFieldAlertWithText:[DoraemonHierarchyFormatterTool formatNumber:@(cons.constant)] handler:^(NSString * _Nullable newText) {\n                        cons.constant = [newText doubleValue];\n                        [weakSelf setNeedsLayout];\n                    }];\n                };\n                [settings addObject:mod];\n                lastConstrainModel = mod;\n            }\n        }\n    }\n    \n    [lastConstrainModel normalInsets];\n    \n    return @[[[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"View\" items:settings]];\n}\n\n#pragma mark - Primary\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    \n    __weak typeof(self) weakSelf = self;\n    \n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Layer\" detailTitle:self.layer.description] noneInsets];\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Layer Class\" detailTitle:NSStringFromClass(self.layer.class)];\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Content Model\" detailTitle:[DoraemonEnumDescription viewContentModeDescription:self.contentMode]] noneInsets];\n    model3.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription viewContentModeDescriptions] currentAction:[DoraemonEnumDescription viewContentModeDescription:weakSelf.contentMode] completion:^(NSInteger index) {\n            weakSelf.contentMode = index;\n        }];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Tag\" detailTitle:[NSString stringWithFormat:@\"%ld\",(long)self.tag]];\n    model4.block = ^{\n        [weakSelf doraemon_showIntAlertAndAutomicSetWithKeyPath:@\"tag\"];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"User Interaction\" flag: self.isUserInteractionEnabled] noneInsets];\n    model5.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.userInteractionEnabled = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Multiple Touch\" flag:self.isMultipleTouchEnabled];\n    model6.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.multipleTouchEnabled = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Alpha\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.alpha)]] noneInsets];\n    model7.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"alpha\"];\n    };\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCellModel *model8 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Background\" detailTitle:[self doraemon_hierarchyColorDescription:self.backgroundColor]] noneInsets];\n    model8.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"backgroundColor\"];\n    };\n    [settings addObject:model8];\n    \n    DoraemonHierarchyCellModel *model9 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Tint\" detailTitle:[self doraemon_hierarchyColorDescription:self.tintColor]];\n    model9.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"tintColor\"];\n    };\n    [settings addObject:model9];\n    \n    DoraemonHierarchyCellModel *model10 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Drawing\" detailTitle:@\"Opaque\" flag:self.isOpaque] noneInsets];\n    model10.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.opaque = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model10];\n    \n    DoraemonHierarchyCellModel *model11 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Hidden\" flag:self.isHidden] noneInsets];\n    model11.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.hidden = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model11];\n    \n    DoraemonHierarchyCellModel *model12 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Clears Graphics Context\" flag:self.clearsContextBeforeDrawing] noneInsets];\n    model12.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.clearsContextBeforeDrawing = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model12];\n    \n    DoraemonHierarchyCellModel *model13 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Clip To Bounds\" flag:self.clipsToBounds] noneInsets];\n    model13.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.clipsToBounds = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model13];\n    \n    DoraemonHierarchyCellModel *model14 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Autoresizes Subviews\" flag:self.autoresizesSubviews];\n    model14.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.autoresizesSubviews = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model14];\n    \n    DoraemonHierarchyCellModel *model15 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Trait Collection\" detailTitle:nil] noneInsets];\n    [settings addObject:model15];\n    \n    if (@available(iOS 12.0, *)) {\n        DoraemonHierarchyCellModel *model16 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[DoraemonEnumDescription userInterfaceStyleDescription:self.traitCollection.userInterfaceStyle]] noneInsets];\n        [settings addObject:model16];\n    }\n    \n    DoraemonHierarchyCellModel *model17 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[@\"Vertical\" stringByAppendingFormat:@\" %@\",[DoraemonEnumDescription userInterfaceSizeClassDescription:self.traitCollection.verticalSizeClass]]] noneInsets];\n    [settings addObject:model17];\n    \n    DoraemonHierarchyCellModel *model18 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[@\"Horizontal\" stringByAppendingFormat:@\" %@\",[DoraemonEnumDescription userInterfaceSizeClassDescription:self.traitCollection.horizontalSizeClass]]] noneInsets];\n    [settings addObject:model18];\n    \n    if (@available(iOS 10.0, *)) {\n        DoraemonHierarchyCellModel *model19 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[DoraemonEnumDescription traitEnvironmentLayoutDirectionDescription:self.traitCollection.layoutDirection]];\n        [settings addObject:model19];\n    }\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"View\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n- (NSString *)doraemon_hierarchyLayoutConstraintDescription:(NSLayoutConstraint *)constraint {\n    NSMutableString *string = [[NSMutableString alloc] init];\n    if (constraint.firstItem == self) {\n        [string appendString:@\"self.\"];\n    } else if (constraint.firstItem == self.superview) {\n        [string appendString:@\"superview.\"];\n    } else {\n        [string appendFormat:@\"%@.\",NSStringFromClass([constraint.firstItem class])];\n    }\n    [string appendString:[DoraemonEnumDescription layoutAttributeDescription:constraint.firstAttribute]];\n    [string appendString:[DoraemonEnumDescription layoutRelationDescription:constraint.relation]];\n    if (constraint.secondItem) {\n        if (constraint.secondItem == self) {\n            [string appendString:@\"self.\"];\n        } else if (constraint.secondItem == self.superview) {\n            [string appendString:@\"superview.\"];\n        } else {\n            [string appendFormat:@\"%@.\",NSStringFromClass([constraint.secondItem class])];\n        }\n        [string appendString:[DoraemonEnumDescription layoutAttributeDescription:constraint.secondAttribute]];\n        if (constraint.multiplier != 1) {\n            [string appendFormat:@\" * %@\",[DoraemonHierarchyFormatterTool formatNumber:@(constraint.multiplier)]];\n        }\n        if (constraint.constant > 0) {\n            [string appendFormat:@\" + %@\",[DoraemonHierarchyFormatterTool formatNumber:@(constraint.constant)]];\n        } else if (constraint.constant < 0) {\n            [string appendFormat:@\" - %@\",[DoraemonHierarchyFormatterTool formatNumber:@(fabs(constraint.constant))]];\n        }\n    } else if (constraint.constant) {\n        [string appendString:[DoraemonHierarchyFormatterTool formatNumber:@(constraint.constant)]];\n    } else {\n        return nil;\n    }\n    \n    [string appendFormat:@\" @ %@\",[DoraemonHierarchyFormatterTool formatNumber:@(constraint.priority)]];\n    return string;\n}\n\n@end\n\n@implementation UILabel (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self) weakSelf = self;\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Text\" detailTitle:[self doraemon_hierarchyTextDescription:self.text]] noneInsets];\n    model1.block = ^{\n        [weakSelf doraemon_showTextAlertAndAutomicSetWithKeyPath:@\"text\"];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:self.attributedText == nil ? @\"Plain Text\" : @\"Attributed Text\"] noneInsets];\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Text\" detailTitle:[self doraemon_hierarchyColorDescription:self.textColor]] noneInsets];\n    model3.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"textColor\"];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[self doraemon_hierarchyObjectDescription:self.font]] noneInsets];\n    model4.block = ^{\n        [weakSelf doraemon_showFontAlertAndAutomicSetWithKeyPath:@\"font\"];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[NSString stringWithFormat:@\"Aligned %@\", [DoraemonEnumDescription textAlignmentDescription:self.textAlignment]]] noneInsets];\n    model5.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription textAlignments] currentAction:[DoraemonEnumDescription textAlignmentDescription:weakSelf.textAlignment] completion:^(NSInteger index) {\n            weakSelf.textAlignment = index;\n        }];\n    };\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Lines\" detailTitle:[NSString stringWithFormat:@\"%ld\",(long)self.numberOfLines]] noneInsets];\n    model6.block = ^{\n        [weakSelf doraemon_showIntAlertAndAutomicSetWithKeyPath:@\"numberOfLines\"];\n    };\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Behavior\" detailTitle:@\"Enabled\" flag:self.isEnabled] noneInsets];\n    model7.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.enabled = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCellModel *model8 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Highlighted\" flag:self.isHighlighted];\n    model8.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.highlighted = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model8];\n    \n    DoraemonHierarchyCellModel *model9 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Baseline\" detailTitle:[NSString stringWithFormat:@\"Align %@\",[DoraemonEnumDescription baselineAdjustmentDescription:self.baselineAdjustment]]] noneInsets];\n    model9.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription baselineAdjustments] currentAction:[DoraemonEnumDescription baselineAdjustmentDescription:weakSelf.baselineAdjustment] completion:^(NSInteger index) {\n            weakSelf.baselineAdjustment = index;\n        }];\n    };\n    [settings addObject:model9];\n    \n    DoraemonHierarchyCellModel *model10 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Line Break\" detailTitle:[DoraemonEnumDescription lineBreakModeDescription:self.lineBreakMode]] noneInsets];\n    model10.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription lineBreaks] currentAction:[DoraemonEnumDescription lineBreakModeDescription:weakSelf.lineBreakMode] completion:^(NSInteger index) {\n            weakSelf.lineBreakMode = index;\n        }];\n    };\n    [settings addObject:model10];\n    \n    DoraemonHierarchyCellModel *model11 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Min Font Scale\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.minimumScaleFactor)]];\n    model11.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"minimumScaleFactor\"];\n    };\n    [settings addObject:model11];\n    \n    DoraemonHierarchyCellModel *model12 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Highlighted\" detailTitle:[self doraemon_hierarchyColorDescription:self.highlightedTextColor]] noneInsets];\n    model12.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"highlightedTextColor\"];\n    };\n    [settings addObject:model12];\n    \n    DoraemonHierarchyCellModel *model13 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Shadow\" detailTitle:[self doraemon_hierarchyColorDescription:self.shadowColor]] noneInsets];\n    model13.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"shadowColor\"];\n    };\n    [settings addObject:model13];\n    \n    DoraemonHierarchyCellModel *model14 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Shadow Offset\" detailTitle:[self doraemon_hierarchySizeDescription:self.shadowOffset]];\n    model14.block = ^{\n        [weakSelf doraemon_showSizeAlertAndAutomicSetWithKeyPath:@\"shadowOffset\"];\n    };\n    [settings addObject:model14];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Label\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UIControl (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self) weakSelf = self;\n    \n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Alignment\" detailTitle:[NSString stringWithFormat:@\"%@ Horizonally\", [DoraemonEnumDescription controlContentHorizontalAlignmentDescription:self.contentHorizontalAlignment]]] noneInsets];\n    model1.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription controlContentHorizontalAlignments] currentAction:[DoraemonEnumDescription controlContentHorizontalAlignmentDescription:weakSelf.contentHorizontalAlignment] completion:^(NSInteger index) {\n            weakSelf.contentHorizontalAlignment = index;\n        }];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[NSString stringWithFormat:@\"%@ Vertically\", [DoraemonEnumDescription controlContentVerticalAlignmentDescription:self.contentVerticalAlignment]]];\n    model2.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription controlContentVerticalAlignments] currentAction:[DoraemonEnumDescription controlContentVerticalAlignmentDescription:weakSelf.contentVerticalAlignment] completion:^(NSInteger index) {\n            weakSelf.contentVerticalAlignment = index;\n        }];\n    };\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Content\" detailTitle:self.isSelected ? @\"Selected\" : @\"Not Selected\" flag:self.isSelected] noneInsets];\n    model3.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.selected = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:self.isEnabled ? @\"Enabled\" : @\"Not Enabled\" flag:self.isEnabled] noneInsets];\n    model4.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.enabled = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:self.isHighlighted ? @\"Highlighted\" : @\"Not Highlighted\" flag:self.isHighlighted];\n    model5.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.highlighted = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Control\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UIButton (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    \n    __weak typeof(self) weakSelf = self;\n    \n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Type\" detailTitle:[DoraemonEnumDescription buttonTypeDescription:self.buttonType]];\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"State\" detailTitle:[DoraemonEnumDescription controlStateDescription:self.state]] noneInsets];\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Title\" detailTitle:[self doraemon_hierarchyTextDescription:self.currentTitle]] noneInsets];\n    model3.block = ^{\n        [weakSelf doraemon_showTextFieldAlertWithText:weakSelf.currentTitle handler:^(NSString * _Nullable newText) {\n            [weakSelf setTitle:newText forState:weakSelf.state];\n        }];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:self.currentAttributedTitle == nil ? @\"Plain Text\" : @\"Attributed Text\"] noneInsets];\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Text Color\" detailTitle:[self doraemon_hierarchyColorDescription:self.currentTitleColor]] noneInsets];\n    model5.block = ^{\n        [weakSelf doraemon_showTextFieldAlertWithText:[weakSelf.currentTitleColor doraemon_HexString] handler:^(NSString * _Nullable newText) {\n            [weakSelf setTitleColor:[weakSelf doraemon_colorFromString:newText originalColor:weakSelf.currentTitleColor] forState:weakSelf.state];\n        }];\n    };\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Shadow Color\" detailTitle:[self doraemon_hierarchyColorDescription:self.currentTitleShadowColor]];\n    model6.block = ^{\n        [weakSelf doraemon_showTextFieldAlertWithText:[weakSelf.currentTitleShadowColor doraemon_HexString] handler:^(NSString * _Nullable newText) {\n            [weakSelf setTitleShadowColor:[weakSelf doraemon_colorFromString:newText originalColor:weakSelf.currentTitleShadowColor] forState:weakSelf.state];\n        }];\n    };\n    [settings addObject:model6];\n    \n    id target = self.allTargets.allObjects.firstObject;\n    DoraemonHierarchyCellModel *model7 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Target\" detailTitle:target ? [NSString stringWithFormat:@\"%@\",target] : @\"<nil>\"] noneInsets];\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCellModel *model8 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Action\" detailTitle:[self doraemon_hierarchyTextDescription:[self actionsForTarget:target forControlEvent:UIControlEventTouchUpInside].firstObject]];;\n    [settings addObject:model8];\n    \n    DoraemonHierarchyCellModel *model9 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Image\" detailTitle:[self doraemon_hierarchyImageDescription:self.currentImage]];\n    [settings addObject:model9];\n    \n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n    DoraemonHierarchyCellModel *model10 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Shadow Offset\" detailTitle:[self doraemon_hierarchySizeDescription:self.titleShadowOffset]] noneInsets];\n    model10.block = ^{\n        [weakSelf doraemon_showSizeAlertAndAutomicSetWithKeyPath:@\"titleShadowOffset\"];\n    };\n    [settings addObject:model10];\n#pragma clang diagnostic pop\n    \n    DoraemonHierarchyCellModel *model11 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"On Highlight\" detailTitle:self.reversesTitleShadowWhenHighlighted ? @\"Shadow Reverses\" : @\"Normal Shadow\"] noneInsets];\n    [settings addObject:model11];\n    \n    DoraemonHierarchyCellModel *model12 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:self.showsTouchWhenHighlighted ? @\"Shows Touch\" : @\"Doesn't Show Touch\"] noneInsets];\n    [settings addObject:model12];\n    \n    DoraemonHierarchyCellModel *model13 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:self.adjustsImageWhenHighlighted ? @\"Adjusts Image\" : @\"No Image Adjustment\"] noneInsets];\n    [settings addObject:model13];\n    \n    DoraemonHierarchyCellModel *model14 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"When Disabled\" detailTitle:self.adjustsImageWhenDisabled ? @\"Adjusts Image\" : @\"No Image Adjustment\"] noneInsets];\n    [settings addObject:model14];\n    \n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n    DoraemonHierarchyCellModel *model15 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Line Break\" detailTitle:[DoraemonEnumDescription lineBreakModeDescription:self.lineBreakMode]];\n    [settings addObject:model15];\n#pragma clang diagnostic pop\n    \n    DoraemonHierarchyCellModel *model16 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Content Insets\" detailTitle:[self doraemon_hierarchyInsetsTopBottomDescription:self.contentEdgeInsets]] noneInsets];\n    model16.block = ^{\n        [weakSelf doraemon_showEdgeInsetsAndAutomicSetWithKeyPath:@\"contentEdgeInsets\"];\n    };\n    [settings addObject:model16];\n    \n    DoraemonHierarchyCellModel *model17 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[self doraemon_hierarchyInsetsLeftRightDescription:self.contentEdgeInsets]] noneInsets];\n    [settings addObject:model17];\n    \n    DoraemonHierarchyCellModel *model18 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Title Insets\" detailTitle:[self doraemon_hierarchyInsetsTopBottomDescription:self.titleEdgeInsets]] noneInsets];\n    model18.block = ^{\n        [weakSelf doraemon_showEdgeInsetsAndAutomicSetWithKeyPath:@\"titleEdgeInsets\"];\n    };\n    [settings addObject:model18];\n    \n    DoraemonHierarchyCellModel *model19 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[self doraemon_hierarchyInsetsLeftRightDescription:self.titleEdgeInsets]] noneInsets];\n    [settings addObject:model19];\n    \n    DoraemonHierarchyCellModel *model20 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Image Insets\" detailTitle:[self doraemon_hierarchyInsetsTopBottomDescription:self.imageEdgeInsets]] noneInsets];\n    model20.block = ^{\n        [weakSelf doraemon_showEdgeInsetsAndAutomicSetWithKeyPath:@\"imageEdgeInsets\"];\n    };\n    [settings addObject:model20];\n    \n    DoraemonHierarchyCellModel *model21 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[self doraemon_hierarchyInsetsLeftRightDescription:self.imageEdgeInsets]];\n    [settings addObject:model21];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Button\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UIImageView (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self)weakSelf = self;\n    \n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Image\" detailTitle: [self doraemon_hierarchyImageDescription:self.image]];\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Highlighted\" detailTitle: [self doraemon_hierarchyImageDescription:self.highlightedImage]];\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"State\" detailTitle:self.isHighlighted ? @\"Highlighted\" : @\"Not Highlighted\" flag:self.isHighlighted];\n    model3.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.highlighted = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Image View\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UITextField (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self) weakSelf = self;\n    \n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Plain Text\" detailTitle:[self doraemon_hierarchyTextDescription:self.text]] noneInsets];\n    model1.block = ^{\n        [weakSelf doraemon_showTextAlertAndAutomicSetWithKeyPath:@\"text\"];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Attributed Text\" detailTitle:[self doraemon_hierarchyObjectDescription:self.attributedText]] noneInsets];\n    model2.block = ^{\n        [weakSelf doraemon_showAttributeTextAlertAndAutomicSetWithKeyPath:@\"attributedText\"];\n    };\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Allows Editing Attributes\" flag:self.allowsEditingTextAttributes] noneInsets];\n    model3.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.allowsEditingTextAttributes = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Color\" detailTitle:[self doraemon_hierarchyColorDescription:self.textColor]] noneInsets];\n    model4.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"textColor\"];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Font\" detailTitle:[self doraemon_hierarchyObjectDescription:self.font]] noneInsets];\n    model5.block = ^{\n        [weakSelf doraemon_showFontAlertAndAutomicSetWithKeyPath:@\"font\"];\n    };\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Alignment\" detailTitle:[DoraemonEnumDescription textAlignmentDescription:self.textAlignment]] noneInsets];\n    model6.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription textAlignments] currentAction:[DoraemonEnumDescription textAlignmentDescription:weakSelf.textAlignment] completion:^(NSInteger index) {\n            weakSelf.textAlignment = index;\n        }];\n    };\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Placeholder\" detailTitle:[self doraemon_hierarchyTextDescription:self.placeholder ?: self.attributedPlaceholder.string]];\n    model7.block = ^{\n        if (weakSelf.placeholder) {\n            [weakSelf doraemon_showTextAlertAndAutomicSetWithKeyPath:@\"placeholder\"];\n        } else {\n            [weakSelf doraemon_showAttributeTextAlertAndAutomicSetWithKeyPath:@\"attributedPlaceholder\"];\n        }\n    };\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCellModel *model8 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Background\" detailTitle: [self doraemon_hierarchyImageDescription:self.background]] noneInsets];\n    [settings addObject:model8];\n    \n    DoraemonHierarchyCellModel *model9 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Disabled\" detailTitle: [self doraemon_hierarchyImageDescription:self.disabledBackground]];\n    [settings addObject:model9];\n    \n    DoraemonHierarchyCellModel *model10 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Border Style\" detailTitle:[DoraemonEnumDescription textBorderStyleDescription:self.borderStyle]];\n    model10.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription textBorderStyles] currentAction:[DoraemonEnumDescription textBorderStyleDescription:weakSelf.borderStyle] completion:^(NSInteger index) {\n            weakSelf.borderStyle = index;\n        }];\n    };\n    [settings addObject:model10];\n    \n    DoraemonHierarchyCellModel *model11 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Clear Button\" detailTitle:[DoraemonEnumDescription textFieldViewModeDescription:self.clearButtonMode]] noneInsets];\n    model11.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription textFieldViewModes] currentAction:[DoraemonEnumDescription textFieldViewModeDescription:weakSelf.clearButtonMode] completion:^(NSInteger index) {\n            weakSelf.clearButtonMode = index;\n        }];\n    };\n    [settings addObject:model11];\n    \n    DoraemonHierarchyCellModel *model12 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Clear when editing begins\" flag:self.clearsOnBeginEditing];\n    model12.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.clearsOnBeginEditing = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model12];\n    \n    DoraemonHierarchyCellModel *model13 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Min Font Size\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.minimumFontSize)]] noneInsets];\n    model13.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"minimumFontSize\"];\n    };\n    [settings addObject:model13];\n    \n    DoraemonHierarchyCellModel *model14 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Adjusts to Fit\" flag:self.adjustsFontSizeToFitWidth];\n    model14.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.adjustsFontSizeToFitWidth = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model14];\n    \n    DoraemonHierarchyCellModel *model15 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Capitalization\" detailTitle:[DoraemonEnumDescription textAutocapitalizationTypeDescription:self.autocapitalizationType]] noneInsets];\n    model15.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription textAutocapitalizationTypes] currentAction:[DoraemonEnumDescription textAutocapitalizationTypeDescription:weakSelf.autocapitalizationType] completion:^(NSInteger index) {\n            weakSelf.autocapitalizationType = index;\n        }];\n    };\n    [settings addObject:model15];\n    \n    DoraemonHierarchyCellModel *model16 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Correction\" detailTitle:[DoraemonEnumDescription textAutocorrectionTypeDescription:self.autocorrectionType]] noneInsets];\n    model16.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription textAutocorrectionTypes] currentAction:[DoraemonEnumDescription textAutocorrectionTypeDescription:weakSelf.autocorrectionType] completion:^(NSInteger index) {\n            weakSelf.autocorrectionType = index;\n        }];\n    };\n    [settings addObject:model16];\n    \n    DoraemonHierarchyCellModel *model17 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Keyboard\" detailTitle:[DoraemonEnumDescription keyboardTypeDescription:self.keyboardType]] noneInsets];\n    model17.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription keyboardTypes] currentAction:[DoraemonEnumDescription keyboardTypeDescription:weakSelf.keyboardType] completion:^(NSInteger index) {\n            weakSelf.keyboardType = index;\n        }];\n    };\n    [settings addObject:model17];\n    \n    DoraemonHierarchyCellModel *model18 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Appearance\" detailTitle:[DoraemonEnumDescription keyboardAppearanceDescription:self.keyboardAppearance]] noneInsets];\n    model18.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription keyboardAppearances] currentAction:[DoraemonEnumDescription keyboardAppearanceDescription:weakSelf.keyboardAppearance] completion:^(NSInteger index) {\n            weakSelf.keyboardAppearance = index;\n        }];\n    };\n    [settings addObject:model18];\n    \n    DoraemonHierarchyCellModel *model19 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Return Key\" detailTitle:[DoraemonEnumDescription returnKeyTypeDescription:self.returnKeyType]] noneInsets];\n    model19.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription returnKeyTypes] currentAction:[DoraemonEnumDescription returnKeyTypeDescription:weakSelf.returnKeyType] completion:^(NSInteger index) {\n            weakSelf.returnKeyType = index;\n        }];\n    };\n    [settings addObject:model19];\n    \n    DoraemonHierarchyCellModel *model20 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Auto-enable Return Key\" flag:self.enablesReturnKeyAutomatically] noneInsets];\n    model20.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.enablesReturnKeyAutomatically = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model20];\n    \n    DoraemonHierarchyCellModel *model21 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Secure Entry\" flag:self.isSecureTextEntry];\n    model21.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.secureTextEntry = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model21];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Text Field\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UISegmentedControl (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self) weakSelf = self;\n    \n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Behavior\" detailTitle:self.isMomentary ? @\"Momentary\" : @\"Persistent Selection\" flag:self.isMomentary] noneInsets];\n    model1.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.momentary = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Segments\" detailTitle:[NSString stringWithFormat:@\"%ld\",(unsigned long)self.numberOfSegments]];\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Selected Index\" detailTitle:[NSString stringWithFormat:@\"%ld\",(long)self.selectedSegmentIndex]] noneInsets];\n    model3.block = ^{\n        NSMutableArray *actions = [[NSMutableArray alloc] init];\n        for (NSInteger i = 0; i < weakSelf.numberOfSegments; i++) {\n            [actions addObject:[NSString stringWithFormat:@\"%ld\",(long)i]];\n        }\n        [weakSelf doraemon_showActionSheetWithActions:actions currentAction:[NSString stringWithFormat:@\"%ld\",(long)weakSelf.selectedSegmentIndex] completion:^(NSInteger index) {\n            weakSelf.selectedSegmentIndex = index;\n        }];\n    };\n    [settings addObject:model3];\n    \n#ifdef __IPHONE_13_0\n    if (@available(iOS 13.0, *)) {\n        DoraemonHierarchyCellModel *model4 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Large title\" detailTitle:[self doraemon_hierarchyTextDescription:self.largeContentTitle]] noneInsets];\n        model4.block = ^{\n            [weakSelf doraemon_showTextAlertAndAutomicSetWithKeyPath:@\"largeContentTitle\"];\n        };\n        [settings addObject:model4];\n        \n        DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Image\" detailTitle: [self doraemon_hierarchyImageDescription:self.largeContentImage]] noneInsets];\n        [settings addObject:model5];\n    }\n#endif\n    \n    DoraemonHierarchyCellModel *model6 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Selected\" detailTitle:[self isEnabledForSegmentAtIndex:self.selectedSegmentIndex] ? @\"Enabled\" : @\"Not Enabled\"];\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Offset\" detailTitle:[self doraemon_hierarchySizeDescription:[self contentOffsetForSegmentAtIndex:self.selectedSegmentIndex]]] noneInsets];\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCellModel *model8 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Size Mode\" detailTitle:self.apportionsSegmentWidthsByContent ? @\"Proportional to Content\" : @\"Equal Widths\" flag:self.apportionsSegmentWidthsByContent] noneInsets];\n    model8.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.apportionsSegmentWidthsByContent = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model8];\n    \n    DoraemonHierarchyCellModel *model9 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Width\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@([self widthForSegmentAtIndex:self.selectedSegmentIndex])]];\n    [settings addObject:model9];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Segmented Control\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UISlider (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self)weakSelf = self;\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Current\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.value)]] noneInsets];\n    model1.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"value\"];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Minimum\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.minimumValue)]] noneInsets];\n    model2.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"minimumValue\"];\n    };\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Maximum\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.maximumValue)]];\n    model3.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"maximumValue\"];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Min Image\" detailTitle: [self doraemon_hierarchyImageDescription:self.minimumValueImage]] noneInsets];\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Max Image\" detailTitle: [self doraemon_hierarchyImageDescription:self.maximumValueImage]];\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Min Track Tint\" detailTitle:[self doraemon_hierarchyColorDescription:self.minimumTrackTintColor]] noneInsets];\n    model6.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"minimumTrackTintColor\"];\n    };\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Max Track Tint\" detailTitle:[self doraemon_hierarchyColorDescription:self.maximumTrackTintColor]] noneInsets];\n    model7.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"maximumTrackTintColor\"];\n    };\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCellModel *model8 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Thumb Tint\" detailTitle:[self doraemon_hierarchyColorDescription:self.tintColor]];\n    model8.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"tintColor\"];\n    };\n    [settings addObject:model8];\n    \n    DoraemonHierarchyCellModel *model9 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Events\" detailTitle:@\"Continuous Update\" flag:self.isContinuous];\n    model9.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.continuous = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model9];\n    \n    DoraemonHierarchyCategoryModel *model =  [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Slider\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UISwitch (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self)weakSelf = self;\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"State\" flag:self.isOn] noneInsets];\n    model1.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.on = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"On Tint\" detailTitle:[self doraemon_hierarchyColorDescription:self.onTintColor]] noneInsets];\n    model2.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"onTintColor\"];\n    };\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Thumb Tint\" detailTitle:[self doraemon_hierarchyColorDescription:self.thumbTintColor]];\n    model3.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"thumbTintColor\"];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Switch\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UIActivityIndicatorView (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self)weakSelf = self;\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Style\" detailTitle:[DoraemonEnumDescription activityIndicatorViewStyleDescription:self.activityIndicatorViewStyle]] noneInsets];\n    model1.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription activityIndicatorViewStyles] currentAction:[DoraemonEnumDescription activityIndicatorViewStyleDescription:weakSelf.activityIndicatorViewStyle] completion:^(NSInteger index) {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n            if (index <= UIActivityIndicatorViewStyleGray) {\n                weakSelf.activityIndicatorViewStyle = index;\n            } else {\n                #if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n                if (@available(iOS 13.0, *)) {\n                    weakSelf.activityIndicatorViewStyle = index + (UIActivityIndicatorViewStyleMedium - UIActivityIndicatorViewStyleGray - 1);\n                }\n                #endif\n            }\n#pragma clang diagnostic pop\n        }];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Color\" detailTitle:[self doraemon_hierarchyColorDescription:self.color]] noneInsets];\n    model2.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"color\"];\n    };\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Behavior\" detailTitle:@\"Animating\" flag:self.isAnimating] noneInsets];\n    model3.changePropertyBlock = ^(id  _Nullable obj) {\n        if ([obj boolValue]) {\n            if (!weakSelf.isAnimating) {\n                [weakSelf startAnimating];\n            };\n        } else {\n            if (weakSelf.isAnimating) {\n                [weakSelf stopAnimating];\n            }\n        }\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Hides When Stopped\" flag:self.hidesWhenStopped];\n    model4.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.hidesWhenStopped = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Activity Indicator View\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UIProgressView (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self)weakSelf = self;\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Style\" detailTitle:[DoraemonEnumDescription progressViewStyleDescription:self.progressViewStyle]];\n    model1.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription progressViewStyles] currentAction:[DoraemonEnumDescription progressViewStyleDescription:weakSelf.progressViewStyle] completion:^(NSInteger index) {\n            weakSelf.progressViewStyle = index;\n        }];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Progress\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.progress)]];\n    model2.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"progress\"];\n    };\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Progress Tint\" detailTitle:[self doraemon_hierarchyColorDescription:self.progressTintColor]] noneInsets];\n    model3.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"progressTintColor\"];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Track Tint\" detailTitle:[self doraemon_hierarchyColorDescription:self.trackTintColor]];\n    model4.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"trackTintColor\"];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Progress Image\" detailTitle:[self doraemon_hierarchyImageDescription:self.progressImage]] noneInsets];\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Track Image\" detailTitle:[self doraemon_hierarchyImageDescription:self.trackImage]];\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Progress View\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UIPageControl (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self)weakSelf = self;\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Pages\" detailTitle:[NSString stringWithFormat:@\"%ld\",(long)self.numberOfPages]] noneInsets];\n    model1.block = ^{\n        [weakSelf doraemon_showIntAlertAndAutomicSetWithKeyPath:@\"numberOfPages\"];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Current Page\" detailTitle:[NSString stringWithFormat:@\"%ld\",(long)self.currentPage]] noneInsets];\n    model2.block = ^{\n        if (weakSelf.numberOfPages < 10) {\n            NSMutableArray *actions = [[NSMutableArray alloc] init];\n            for (NSInteger i = 0; i < weakSelf.numberOfPages; i++) {\n                [actions addObject:[NSString stringWithFormat:@\"%ld\",(long)i]];\n            }\n            [weakSelf doraemon_showActionSheetWithActions:actions currentAction:[NSString stringWithFormat:@\"%ld\",(long)weakSelf.currentPage] completion:^(NSInteger index) {\n                weakSelf.currentPage = index;\n            }];\n        } else {\n            [weakSelf doraemon_showIntAlertAndAutomicSetWithKeyPath:@\"currentPage\"];\n        }\n    };\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Behavior\" detailTitle:@\"Hides for Single Page\" flag:self.hidesForSinglePage] noneInsets];\n    model3.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.hidesForSinglePage = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Defers Page Display\" flag:self.defersCurrentPageDisplay];\n    model4.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.defersCurrentPageDisplay = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Tint Color\" detailTitle:[self doraemon_hierarchyColorDescription:self.pageIndicatorTintColor]] noneInsets];\n    model5.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"pageIndicatorTintColor\"];\n    };\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Current Page\" detailTitle:[self doraemon_hierarchyColorDescription:self.currentPageIndicatorTintColor]];\n    model6.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"currentPageIndicatorTintColor\"];\n    };\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Page Control\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UIStepper (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    \n    __weak typeof(self)weakSelf = self;\n    \n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Value\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.value)]] noneInsets];\n    model1.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"value\"];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Minimum\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.minimumValue)]] noneInsets];\n    model2.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"minimumValue\"];\n    };\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Maximum\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.maximumValue)]] noneInsets];\n    model3.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"maximumValue\"];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Step\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.stepValue)]];\n    model4.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"stepValue\"];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Behavior\" detailTitle:@\"Autorepeat\" flag:self.autorepeat] noneInsets];\n    model5.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.autorepeat = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Continuous\" flag:self.isContinuous] noneInsets];\n    model6.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.continuous = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Wrap\" flag:self.wraps];\n    model7.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.wraps = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Stepper\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UIScrollView (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self)weakSelf = self;\n    \n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Style\" detailTitle:[DoraemonEnumDescription scrollViewIndicatorStyleDescription:self.indicatorStyle]];\n    model1.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription scrollViewIndicatorStyles] currentAction:[DoraemonEnumDescription scrollViewIndicatorStyleDescription:weakSelf.indicatorStyle] completion:^(NSInteger index) {\n            weakSelf.indicatorStyle = index;\n        }];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Indicators\" detailTitle:@\"Shows Horizontal Indicator\" flag:self.showsHorizontalScrollIndicator] noneInsets];\n    model2.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.showsHorizontalScrollIndicator = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Shows Vertical Indicator\" flag:self.showsVerticalScrollIndicator];\n    model3.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.showsVerticalScrollIndicator = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Scrolling\" detailTitle:@\"Enable\" flag:self.isScrollEnabled] noneInsets];\n    model4.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.scrollEnabled = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Paging\" flag:self.isPagingEnabled] noneInsets];\n    model5.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.pagingEnabled = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Direction Lock\" flag:self.isDirectionalLockEnabled];\n    model6.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.directionalLockEnabled = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Bounce\" detailTitle:@\"Bounces\" flag:self.bounces] noneInsets];\n    model7.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.bounces = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCellModel *model8 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Bounce Horizontal\" flag:self.alwaysBounceHorizontal] noneInsets];\n    model8.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.alwaysBounceHorizontal = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model8];\n    \n    DoraemonHierarchyCellModel *model9 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Bounce Vertical\" flag:self.alwaysBounceVertical];\n    model9.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.alwaysBounceVertical = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model9];\n    \n    DoraemonHierarchyCellModel *model10 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Zoom Min\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.minimumZoomScale)]] noneInsets];\n    model10.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"minimumZoomScale\"];\n    };\n    [settings addObject:model10];\n    \n    DoraemonHierarchyCellModel *model11 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Max\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.maximumZoomScale)]];\n    model11.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"maximumZoomScale\"];\n    };\n    [settings addObject:model11];\n    \n    DoraemonHierarchyCellModel *model12 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Touch\" detailTitle:[NSString stringWithFormat:@\"Zoom Bounces %@\",[self doraemon_hierarchyBoolDescription:self.isZoomBouncing]]] noneInsets];\n    [settings addObject:model12];\n    \n    DoraemonHierarchyCellModel *model13 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Delays Content Touches\" flag:self.delaysContentTouches] noneInsets];\n    model13.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.delaysContentTouches = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model13];\n    \n    DoraemonHierarchyCellModel *model14 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Cancellable Content Touches\" flag:self.canCancelContentTouches];\n    model14.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.canCancelContentTouches = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model14];\n    \n    DoraemonHierarchyCellModel *model15 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Keyboard\" detailTitle:[DoraemonEnumDescription scrollViewKeyboardDismissModeDescription:self.keyboardDismissMode]];\n    model15.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription scrollViewKeyboardDismissModes] currentAction:[DoraemonEnumDescription scrollViewKeyboardDismissModeDescription:weakSelf.keyboardDismissMode] completion:^(NSInteger index) {\n            weakSelf.keyboardDismissMode = index;\n        }];\n    };\n    [settings addObject:model15];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Scroll View\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UITableView (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self)weakSelf = self;\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Sections\" detailTitle:[NSString stringWithFormat:@\"%ld\",(long)self.numberOfSections]] noneInsets];\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Style\" detailTitle:[DoraemonEnumDescription tableViewStyleDescription:self.style]] noneInsets];\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Separator\" detailTitle:[DoraemonEnumDescription tableViewCellSeparatorStyleDescription:self.separatorStyle]] noneInsets];\n    model3.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription tableViewCellSeparatorStyles] currentAction:[DoraemonEnumDescription tableViewCellSeparatorStyleDescription:weakSelf.separatorStyle] completion:^(NSInteger index) {\n            weakSelf.separatorStyle = index;\n        }];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[self doraemon_hierarchyColorDescription:self.separatorColor]];\n    model4.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"separatorColor\"];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Data Source\" detailTitle:[self doraemon_hierarchyObjectDescription:self.dataSource]] noneInsets];\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Delegate\" detailTitle:[self doraemon_hierarchyObjectDescription:self.delegate]];\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Separator Inset\" detailTitle:[self doraemon_hierarchyInsetsTopBottomDescription:self.separatorInset]] noneInsets];\n    model7.block = ^{\n        [weakSelf doraemon_showEdgeInsetsAndAutomicSetWithKeyPath:@\"separatorInset\"];\n    };\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCellModel *model8 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[self doraemon_hierarchyInsetsLeftRightDescription:self.separatorInset]] noneInsets];\n    [settings addObject:model8];\n    \n    if (@available(iOS 11.0, *)) {\n        DoraemonHierarchyCellModel *model9 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[DoraemonEnumDescription tableViewSeparatorInsetReferenceDescription:self.separatorInsetReference]];\n        model9.block = ^{\n            [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription tableViewSeparatorInsetReferences] currentAction:[DoraemonEnumDescription tableViewSeparatorInsetReferenceDescription:weakSelf.separatorInsetReference] completion:^(NSInteger index) {\n                weakSelf.separatorInsetReference = index;\n            }];\n        };\n        [settings addObject:model9];\n    }\n    \n    DoraemonHierarchyCellModel *model10 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Selection\" detailTitle:self.allowsSelection ? @\"Allowed\" : @\"Disabled\" flag:self.allowsSelection] noneInsets];\n    model10.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.allowsSelection = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model10];\n    \n    DoraemonHierarchyCellModel *model11 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[NSString stringWithFormat:@\"Multiple Selection %@\",self.allowsMultipleSelection ? @\"\" : @\"Disabled\"] flag:self.allowsMultipleSelection] noneInsets];\n    model11.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.allowsMultipleSelection = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model11];\n    \n    DoraemonHierarchyCellModel *model12 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Edit Selection\" detailTitle:self.allowsSelectionDuringEditing ? @\"Allowed\" : @\"Disabled\" flag:self.allowsSelectionDuringEditing] noneInsets];\n    model12.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.allowsSelectionDuringEditing = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model12];\n    \n    DoraemonHierarchyCellModel *model13 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[NSString stringWithFormat:@\"Multiple Selection %@\",self.allowsMultipleSelectionDuringEditing ? @\"\" : @\"Disabled\"] flag:self.allowsMultipleSelectionDuringEditing];\n    model13.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.allowsMultipleSelectionDuringEditing = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model13];\n    \n    DoraemonHierarchyCellModel *model14 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Min Display\" detailTitle:[NSString stringWithFormat:@\"%ld\",(long)self.sectionIndexMinimumDisplayRowCount]] noneInsets];\n    model14.block = ^{\n        [weakSelf doraemon_showIntAlertAndAutomicSetWithKeyPath:@\"sectionIndexMinimumDisplayRowCount\"];\n    };\n    [settings addObject:model14];\n    \n    DoraemonHierarchyCellModel *model15 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Text\" detailTitle:[self doraemon_hierarchyColorDescription:self.sectionIndexColor]] noneInsets];\n    model15.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"sectionIndexColor\"];\n    };\n    [settings addObject:model15];\n    \n    DoraemonHierarchyCellModel *model16 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Background\" detailTitle:[self doraemon_hierarchyColorDescription:self.sectionIndexBackgroundColor]] noneInsets];\n    model16.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"sectionIndexBackgroundColor\"];\n    };\n    [settings addObject:model16];\n    \n    DoraemonHierarchyCellModel *model17 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Tracking\" detailTitle:[self doraemon_hierarchyColorDescription:self.sectionIndexTrackingBackgroundColor]];\n    model17.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"sectionIndexTrackingBackgroundColor\"];\n    };\n    model17.block = ^{\n        \n    };\n    [settings addObject:model17];\n    \n    DoraemonHierarchyCellModel *model18 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Row Height\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.rowHeight)]] noneInsets];\n    model18.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"rowHeight\"];\n    };\n    [settings addObject:model18];\n    \n    DoraemonHierarchyCellModel *model19 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Section Header\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.sectionHeaderHeight)]] noneInsets];\n    model19.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"sectionHeaderHeight\"];\n    };\n    [settings addObject:model19];\n    \n    DoraemonHierarchyCellModel *model20 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Section Footer\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.sectionFooterHeight)]];\n    model20.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"sectionFooterHeight\"];\n    };\n    [settings addObject:model20];\n\n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Table View\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UITableViewCell (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self) weakSelf = self;\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Image\" detailTitle:[self doraemon_hierarchyImageDescription:self.imageView.image]];\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Identifier\" detailTitle:[self doraemon_hierarchyTextDescription:self.reuseIdentifier]];\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Selection\" detailTitle:[DoraemonEnumDescription tableViewCellSelectionStyleDescription:self.selectionStyle]] noneInsets];\n    model3.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription tableViewCellSelectionStyles] currentAction:[DoraemonEnumDescription tableViewCellSelectionStyleDescription:weakSelf.selectionStyle] completion:^(NSInteger index) {\n            weakSelf.selectionStyle = index;\n        }];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Accessory\" detailTitle:[DoraemonEnumDescription tableViewCellAccessoryTypeDescription:self.accessoryType]] noneInsets];\n    model4.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription tableViewCellAccessoryTypes] currentAction:[DoraemonEnumDescription tableViewCellAccessoryTypeDescription:weakSelf.accessoryType] completion:^(NSInteger index) {\n            weakSelf.accessoryType = index;\n        }];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Editing Acc.\" detailTitle:[DoraemonEnumDescription tableViewCellAccessoryTypeDescription:self.editingAccessoryType]];\n    model5.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription tableViewCellAccessoryTypes] currentAction:[DoraemonEnumDescription tableViewCellAccessoryTypeDescription:weakSelf.editingAccessoryType] completion:^(NSInteger index) {\n            weakSelf.editingAccessoryType = index;\n        }];\n    };\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Indentation\" detailTitle:[NSString stringWithFormat:@\"%ld\",(long)self.indentationLevel]] noneInsets];\n    model6.block = ^{\n        [weakSelf doraemon_showIntAlertAndAutomicSetWithKeyPath:@\"indentationLevel\"];\n    };\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.indentationWidth)]] noneInsets];\n    model7.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"indentationWidth\"];\n    };\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCellModel *model8 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Indent While Editing\" flag:self.shouldIndentWhileEditing] noneInsets];\n    model8.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.shouldIndentWhileEditing = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model8];\n    \n    DoraemonHierarchyCellModel *model9 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Shows Re-order Controls\" flag:self.showsReorderControl];\n    model9.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.showsReorderControl = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model9];\n    \n    DoraemonHierarchyCellModel *model10 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Separator Inset\" detailTitle:[self doraemon_hierarchyInsetsTopBottomDescription:self.separatorInset]] noneInsets];\n    model10.block = ^{\n        [weakSelf doraemon_showEdgeInsetsAndAutomicSetWithKeyPath:@\"separatorInset\"];\n    };\n    [settings addObject:model10];\n    \n    DoraemonHierarchyCellModel *model11 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[self doraemon_hierarchyInsetsLeftRightDescription:self.separatorInset]];\n    [settings addObject:model11];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Table View Cell\" items:settings];\n    \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UICollectionView (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Sections\" detailTitle:[NSString stringWithFormat:@\"%ld\",(long)self.numberOfSections]];\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Delegate\" detailTitle:[self doraemon_hierarchyObjectDescription:self.delegate]] noneInsets];\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Data Source\" detailTitle:[self doraemon_hierarchyObjectDescription:self.dataSource]] noneInsets];\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Layout\" detailTitle:[self doraemon_hierarchyObjectDescription:self.collectionViewLayout]];\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Collection View\" items:settings];\n                                \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UICollectionReusableView (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Identifier\" detailTitle:[self doraemon_hierarchyTextDescription:self.reuseIdentifier]] noneInsets];\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Collection Reusable View\" items:settings];\n                                \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UITextView (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self)weakSelf = self;\n    \n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Plain Text\" detailTitle:[self doraemon_hierarchyTextDescription:self.text]] noneInsets];\n    model1.block = ^{\n        [weakSelf doraemon_showTextAlertAndAutomicSetWithKeyPath:@\"text\"];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Attributed Text\" detailTitle:[self doraemon_hierarchyObjectDescription:self.attributedText]] noneInsets];\n    model2.block = ^{\n        [weakSelf doraemon_showAttributeTextAlertAndAutomicSetWithKeyPath:@\"attributedText\"];\n    };\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Allows Editing Attributes\" flag:self.allowsEditingTextAttributes] noneInsets];\n    model3.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.allowsEditingTextAttributes = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Color\" detailTitle:[self doraemon_hierarchyColorDescription:self.textColor]] noneInsets];\n    model4.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"textColor\"];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Font\" detailTitle:[self doraemon_hierarchyObjectDescription:self.font]] noneInsets];\n    model5.block = ^{\n        [weakSelf doraemon_showFontAlertAndAutomicSetWithKeyPath:@\"font\"];\n    };\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Alignment\" detailTitle:[DoraemonEnumDescription textAlignmentDescription:self.textAlignment]];\n    model6.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription textAlignments] currentAction:[DoraemonEnumDescription textAlignmentDescription:weakSelf.textAlignment] completion:^(NSInteger index) {\n            weakSelf.textAlignment = index;\n        }];\n    };\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Behavior\" detailTitle:@\"Editable\" flag:self.isEditable] noneInsets];\n    model7.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.editable = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCellModel *model8 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Selectable\" flag:self.isSelectable];\n    model8.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.selectable = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model8];\n    \n    DoraemonHierarchyCellModel *model9 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Data Detectors\" detailTitle:@\"Phone Number\" flag:self.dataDetectorTypes & UIDataDetectorTypePhoneNumber] noneInsets];\n    model9.changePropertyBlock = ^(id  _Nullable obj) {\n        if ([obj boolValue]) {\n            weakSelf.dataDetectorTypes = weakSelf.dataDetectorTypes | UIDataDetectorTypePhoneNumber;\n        } else {\n            weakSelf.dataDetectorTypes = weakSelf.dataDetectorTypes & ~UIDataDetectorTypePhoneNumber;\n        }\n    };\n    [settings addObject:model9];\n    \n    DoraemonHierarchyCellModel *model10 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Link\" flag:self.dataDetectorTypes & UIDataDetectorTypeLink] noneInsets];\n    model10.changePropertyBlock = ^(id  _Nullable obj) {\n        if ([obj boolValue]) {\n            weakSelf.dataDetectorTypes = weakSelf.dataDetectorTypes | UIDataDetectorTypeLink;\n        } else {\n            weakSelf.dataDetectorTypes = weakSelf.dataDetectorTypes & ~UIDataDetectorTypeLink;\n        }\n    };\n    [settings addObject:model10];\n    \n    DoraemonHierarchyCellModel *model11 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Address\" flag:self.dataDetectorTypes & UIDataDetectorTypeAddress] noneInsets];\n    model11.changePropertyBlock = ^(id  _Nullable obj) {\n        if ([obj boolValue]) {\n            weakSelf.dataDetectorTypes = weakSelf.dataDetectorTypes | UIDataDetectorTypeAddress;\n        } else {\n            weakSelf.dataDetectorTypes = weakSelf.dataDetectorTypes & ~UIDataDetectorTypeAddress;\n        }\n    };\n    [settings addObject:model11];\n    \n    DoraemonHierarchyCellModel *model12 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Calendar Event\" flag:self.dataDetectorTypes & UIDataDetectorTypeCalendarEvent] noneInsets];\n    model12.changePropertyBlock = ^(id  _Nullable obj) {\n        if ([obj boolValue]) {\n            weakSelf.dataDetectorTypes = weakSelf.dataDetectorTypes | UIDataDetectorTypeCalendarEvent;\n        } else {\n            weakSelf.dataDetectorTypes = weakSelf.dataDetectorTypes & ~UIDataDetectorTypeCalendarEvent;\n        }\n    };\n    [settings addObject:model12];\n    \n    if (@available(iOS 10.0, *)) {\n        DoraemonHierarchyCellModel *model13 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Shipment Tracking Number\" flag:self.dataDetectorTypes & UIDataDetectorTypeShipmentTrackingNumber] noneInsets];\n        model13.changePropertyBlock = ^(id  _Nullable obj) {\n            if ([obj boolValue]) {\n                weakSelf.dataDetectorTypes = weakSelf.dataDetectorTypes | UIDataDetectorTypeShipmentTrackingNumber;\n            } else {\n                weakSelf.dataDetectorTypes = weakSelf.dataDetectorTypes & ~UIDataDetectorTypeShipmentTrackingNumber;\n            }\n        };\n        [settings addObject:model13];\n        \n        DoraemonHierarchyCellModel *model14 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Flight Number\" flag:self.dataDetectorTypes & UIDataDetectorTypeFlightNumber] noneInsets];\n        model14.changePropertyBlock = ^(id  _Nullable obj) {\n            if ([obj boolValue]) {\n                weakSelf.dataDetectorTypes = weakSelf.dataDetectorTypes | UIDataDetectorTypeFlightNumber;\n            } else {\n                weakSelf.dataDetectorTypes = weakSelf.dataDetectorTypes & ~UIDataDetectorTypeFlightNumber;\n            }\n        };\n        [settings addObject:model14];\n        \n        DoraemonHierarchyCellModel *model15 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Lookup Suggestion\" flag:self.dataDetectorTypes & UIDataDetectorTypeLookupSuggestion];\n        model15.changePropertyBlock = ^(id  _Nullable obj) {\n            if ([obj boolValue]) {\n                weakSelf.dataDetectorTypes = weakSelf.dataDetectorTypes | UIDataDetectorTypeLookupSuggestion;\n            } else {\n                weakSelf.dataDetectorTypes = weakSelf.dataDetectorTypes & ~UIDataDetectorTypeLookupSuggestion;\n            }\n        };\n        [settings addObject:model15];\n    } else {\n        [model12 normalInsets];\n    }\n    \n    DoraemonHierarchyCellModel *model16 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Capitalization\" detailTitle:[DoraemonEnumDescription textAutocapitalizationTypeDescription:self.autocapitalizationType]] noneInsets];\n    model16.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription textAutocapitalizationTypes] currentAction:[DoraemonEnumDescription textAutocapitalizationTypeDescription:weakSelf.autocapitalizationType] completion:^(NSInteger index) {\n            weakSelf.autocapitalizationType = index;\n        }];\n    };\n    [settings addObject:model16];\n    \n    DoraemonHierarchyCellModel *model17 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Correction\" detailTitle:[DoraemonEnumDescription textAutocorrectionTypeDescription:self.autocorrectionType]] noneInsets];\n    model17.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription textAutocorrectionTypes] currentAction:[DoraemonEnumDescription textAutocorrectionTypeDescription:weakSelf.autocorrectionType] completion:^(NSInteger index) {\n            weakSelf.autocorrectionType = index;\n        }];\n    };\n    [settings addObject:model17];\n    \n    DoraemonHierarchyCellModel *model18 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Keyboard\" detailTitle:[DoraemonEnumDescription keyboardTypeDescription:self.keyboardType]] noneInsets];\n    model18.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription keyboardTypes] currentAction:[DoraemonEnumDescription keyboardTypeDescription:weakSelf.keyboardType] completion:^(NSInteger index) {\n            weakSelf.keyboardType = index;\n        }];\n    };\n    [settings addObject:model18];\n    \n    DoraemonHierarchyCellModel *model19 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Appearance\" detailTitle:[DoraemonEnumDescription keyboardAppearanceDescription:self.keyboardAppearance]] noneInsets];\n    model19.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription keyboardAppearances] currentAction:[DoraemonEnumDescription keyboardAppearanceDescription:weakSelf.keyboardAppearance] completion:^(NSInteger index) {\n            weakSelf.keyboardAppearance = index;\n        }];\n    };\n    [settings addObject:model19];\n    \n    DoraemonHierarchyCellModel *model20 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Return Key\" detailTitle:[DoraemonEnumDescription returnKeyTypeDescription:self.returnKeyType]] noneInsets];\n    model20.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription returnKeyTypes] currentAction:[DoraemonEnumDescription returnKeyTypeDescription:weakSelf.returnKeyType] completion:^(NSInteger index) {\n            weakSelf.returnKeyType = index;\n        }];\n    };\n    [settings addObject:model20];\n    \n    DoraemonHierarchyCellModel *model21 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Auto-enable Return Key\" flag:self.enablesReturnKeyAutomatically] noneInsets];\n    model21.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.enablesReturnKeyAutomatically = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model21];\n    \n    DoraemonHierarchyCellModel *model22 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Secure Entry\" flag:self.isSecureTextEntry];\n    model22.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.secureTextEntry = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model22];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Text View\" items:settings];\n                                \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UIDatePicker (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self) weakSelf = self;\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Mode\" detailTitle:[DoraemonEnumDescription datePickerModeDescription:self.datePickerMode]] noneInsets];\n    model1.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription datePickerModes] currentAction:[DoraemonEnumDescription datePickerModeDescription:weakSelf.datePickerMode] completion:^(NSInteger index) {\n            weakSelf.datePickerMode = index;\n        }];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Locale Identifier\" detailTitle:self.locale.localeIdentifier] noneInsets];\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Interval\" detailTitle:[NSString stringWithFormat:@\"%ld\",(long)self.minuteInterval]];\n    model3.block = ^{\n        [weakSelf doraemon_showIntAlertAndAutomicSetWithKeyPath:@\"minuteInterval\"];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Date\" detailTitle:[self doraemon_hierarchyDateDescription:self.date]] noneInsets];\n    model4.block = ^{\n        [weakSelf doraemon_showDateAlertAndAutomicSetWithKeyPath:@\"date\"];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Min Date\" detailTitle:[self doraemon_hierarchyDateDescription:self.minimumDate]] noneInsets];\n    model5.block = ^{\n        [weakSelf doraemon_showDateAlertAndAutomicSetWithKeyPath:@\"minimumDate\"];\n    };\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Max Date\" detailTitle:[self doraemon_hierarchyDateDescription:self.maximumDate]];\n    model6.block = ^{\n        [weakSelf doraemon_showDateAlertAndAutomicSetWithKeyPath:@\"maximumDate\"];\n    };\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Count Down\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.countDownDuration)]];\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Date Picker\" items:settings];\n                                \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UIPickerView (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Delegate\" detailTitle:[self doraemon_hierarchyObjectDescription:self.delegate]];\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Picker View\" items:settings];\n                                \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UINavigationBar (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self)weakSelf = self;\n    \n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Style\" detailTitle:[DoraemonEnumDescription barStyleDescription:self.barStyle]] noneInsets];\n    model1.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription barStyles] currentAction:[DoraemonEnumDescription barStyleDescription:weakSelf.barStyle] completion:^(NSInteger index) {\n            weakSelf.barStyle = index;\n        }];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Translucent\" flag:self.isTranslucent] noneInsets];\n    model2.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.translucent = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model2];\n    \n    if (@available(iOS 11.0, *)) {\n        DoraemonHierarchyCellModel *model3 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Prefers Large Titles\" flag:self.prefersLargeTitles] noneInsets];\n        model3.changePropertyBlock = ^(id  _Nullable obj) {\n            weakSelf.prefersLargeTitles = [obj boolValue];\n            [weakSelf doraemon_postHierarchyChangeNotification];\n        };\n        [settings addObject:model3];\n    }\n    \n    DoraemonHierarchyCellModel *model4 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Bar Tint\" detailTitle:[self doraemon_hierarchyColorDescription:self.barTintColor]] noneInsets];\n    model4.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"barTintColor\"];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Shadow Image\" detailTitle:[self doraemon_hierarchyImageDescription:self.shadowImage]] noneInsets];\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Back Image\" detailTitle:[self doraemon_hierarchyImageDescription:self.backIndicatorImage]] noneInsets];\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Back Mask\" detailTitle:[self doraemon_hierarchyImageDescription:self.backIndicatorTransitionMaskImage]];\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCellModel *model8 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Title Attr.\" detailTitle:nil] noneInsets];\n    [settings addObject:model8];\n    \n    DoraemonHierarchyCellModel *model9 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Title Font\" detailTitle:[self doraemon_hierarchyObjectDescription:self.titleTextAttributes[NSFontAttributeName]]] noneInsets];\n    if (self.titleTextAttributes[NSFontAttributeName]) {\n        model9.block = ^{\n            __block UIFont *font = weakSelf.titleTextAttributes[NSFontAttributeName];\n            if (!font) {\n                return;\n            }\n            [weakSelf doraemon_showTextFieldAlertWithText:[NSString stringWithFormat:@\"%@\",[DoraemonHierarchyFormatterTool formatNumber:@(font.pointSize)]] handler:^(NSString * _Nullable newText) {\n                NSMutableDictionary *attributes = [[NSMutableDictionary alloc] initWithDictionary:weakSelf.titleTextAttributes];\n                attributes[NSFontAttributeName] = [font fontWithSize:[newText doubleValue]];\n                weakSelf.titleTextAttributes = [attributes copy];\n            }];\n        };\n    }\n    [settings addObject:model9];\n    \n    DoraemonHierarchyCellModel *model10 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Title Color\" detailTitle:[self doraemon_hierarchyColorDescription:self.titleTextAttributes[NSForegroundColorAttributeName]]] noneInsets];\n    [settings addObject:model10];\n    \n    NSShadow *shadow = self.titleTextAttributes[NSShadowAttributeName];\n    if (![shadow isKindOfClass:[NSShadow class]]) {\n        shadow = nil;\n    }\n    \n    DoraemonHierarchyCellModel *model11 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Shadow\" detailTitle:[self doraemon_hierarchyColorDescription:shadow.shadowColor]] noneInsets];\n    [settings addObject:model11];\n    \n    DoraemonHierarchyCellModel *model12 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Shadow Offset\" detailTitle:[self doraemon_hierarchySizeDescription:shadow.shadowOffset]];\n    [settings addObject:model12];\n    \n    if (@available(iOS 11.0, *)) {\n        DoraemonHierarchyCellModel *model13 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Large Title Attr.\" detailTitle:nil] noneInsets];\n        [settings addObject:model13];\n        \n        DoraemonHierarchyCellModel *model14 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Title Font\" detailTitle:[self doraemon_hierarchyColorDescription:self.largeTitleTextAttributes[NSFontAttributeName]]] noneInsets];\n        if (self.largeTitleTextAttributes[NSFontAttributeName]) {\n            model14.block = ^{\n                __block UIFont *font = weakSelf.largeTitleTextAttributes[NSFontAttributeName];\n                if (!font) {\n                    return;\n                }\n                [weakSelf doraemon_showTextFieldAlertWithText:[NSString stringWithFormat:@\"%@\",[DoraemonHierarchyFormatterTool formatNumber:@(font.pointSize)]] handler:^(NSString * _Nullable newText) {\n                    NSMutableDictionary *attributes = [[NSMutableDictionary alloc] initWithDictionary:weakSelf.largeTitleTextAttributes];\n                    attributes[NSFontAttributeName] = [font fontWithSize:[newText doubleValue]];\n                    weakSelf.largeTitleTextAttributes = [attributes copy];\n                }];\n            };\n        }\n        [settings addObject:model14];\n        \n        DoraemonHierarchyCellModel *model15 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Title Color\" detailTitle:[self doraemon_hierarchyColorDescription:self.largeTitleTextAttributes[NSForegroundColorAttributeName]]] noneInsets];\n        [settings addObject:model15];\n        \n        shadow = self.largeTitleTextAttributes[NSShadowAttributeName];\n        if (![shadow isKindOfClass:[NSShadow class]]) {\n            shadow = nil;\n        }\n        \n        DoraemonHierarchyCellModel *model16 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Shadow\" detailTitle:[self doraemon_hierarchyColorDescription:shadow.shadowColor]] noneInsets];\n        [settings addObject:model16];\n        \n        DoraemonHierarchyCellModel *model17 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Shadow Offset\" detailTitle:[self doraemon_hierarchySizeDescription:shadow.shadowOffset]];\n        [settings addObject:model17];\n    }\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Navigation Bar\" items:settings];\n                                \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UIToolbar (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self) weakSelf = self;\n    \n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Style\" detailTitle:[DoraemonEnumDescription barStyleDescription:self.barStyle]] noneInsets];\n    model1.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription barStyles] currentAction:[DoraemonEnumDescription barStyleDescription:weakSelf.barStyle] completion:^(NSInteger index) {\n            weakSelf.barStyle = index;\n        }];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Translucent\" flag:self.isTranslucent] noneInsets];\n    model2.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.translucent = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Bar Tint\" detailTitle:[self doraemon_hierarchyColorDescription:self.barTintColor]];\n    model3.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"barTintColor\"];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Tool Bar\" items:settings];\n                                \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UITabBar (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self) weakSelf = self;\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Background\" detailTitle:[self doraemon_hierarchyImageDescription:self.backgroundImage]] noneInsets];\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Shadow\" detailTitle:[self doraemon_hierarchyImageDescription:self.shadowImage]] noneInsets];\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Selection\" detailTitle:[self doraemon_hierarchyImageDescription:self.selectionIndicatorImage]];\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Style\" detailTitle:[DoraemonEnumDescription barStyleDescription:self.barStyle]] noneInsets];\n    model4.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription barStyles] currentAction:[DoraemonEnumDescription barStyleDescription:weakSelf.barStyle] completion:^(NSInteger index) {\n            weakSelf.barStyle = index;\n        }];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Translucent\" flag:self.isTranslucent] noneInsets];\n    model5.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.translucent = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Bar Tint\" detailTitle:[self doraemon_hierarchyColorDescription:self.barTintColor]];\n    model6.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"barTintColor\"];\n    };\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Style\" detailTitle:[DoraemonEnumDescription tabBarItemPositioningDescription:self.itemPositioning]] noneInsets];\n    model7.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription tabBarItemPositionings] currentAction:[DoraemonEnumDescription tabBarItemPositioningDescription:weakSelf.itemPositioning] completion:^(NSInteger index) {\n            weakSelf.itemPositioning = index;\n        }];\n    };\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCellModel *model8 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Item Width\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.itemWidth)]] noneInsets];\n    model8.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"itemWidth\"];\n    };\n    [settings addObject:model8];\n    \n    DoraemonHierarchyCellModel *model9 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Item Spacing\" detailTitle:[DoraemonHierarchyFormatterTool formatNumber:@(self.itemSpacing)]];\n    model9.block = ^{\n        [weakSelf doraemon_showDoubleAlertAndAutomicSetWithKeyPath:@\"itemSpacing\"];\n    };\n    [settings addObject:model9];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Tab Bar\" items:settings];\n                                \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UISearchBar (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    __weak typeof(self) weakSelf = self;\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Text\" detailTitle:[self doraemon_hierarchyTextDescription:self.text]] noneInsets];\n    model1.block = ^{\n        [weakSelf doraemon_showTextAlertAndAutomicSetWithKeyPath:@\"text\"];\n    };\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Placeholder\" detailTitle:[self doraemon_hierarchyTextDescription:self.placeholder]] noneInsets];\n    model2.block = ^{\n        [weakSelf doraemon_showTextAlertAndAutomicSetWithKeyPath:@\"placeholder\"];\n    };\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCellModel *model3 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Prompt\" detailTitle:[self doraemon_hierarchyTextDescription:self.prompt]];\n    model3.block = ^{\n        [weakSelf doraemon_showTextAlertAndAutomicSetWithKeyPath:@\"prompt\"];\n    };\n    [settings addObject:model3];\n    \n    DoraemonHierarchyCellModel *model4 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Search Style\" detailTitle:[DoraemonEnumDescription searchBarStyleDescription:self.searchBarStyle]] noneInsets];\n    model4.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription searchBarStyles] currentAction:[DoraemonEnumDescription searchBarStyleDescription:weakSelf.searchBarStyle] completion:^(NSInteger index) {\n            weakSelf.searchBarStyle = index;\n        }];\n    };\n    [settings addObject:model4];\n    \n    DoraemonHierarchyCellModel *model5 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Bar Style\" detailTitle:[DoraemonEnumDescription barStyleDescription:self.barStyle]] noneInsets];\n    model5.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription barStyles] currentAction:[DoraemonEnumDescription barStyleDescription:weakSelf.barStyle] completion:^(NSInteger index) {\n            weakSelf.barStyle = index;\n        }];\n    };\n    [settings addObject:model5];\n    \n    DoraemonHierarchyCellModel *model6 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Translucent\" flag:self.isTranslucent] noneInsets];\n    model6.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.translucent = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model6];\n    \n    DoraemonHierarchyCellModel *model7 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[self doraemon_hierarchyColorDescription:self.barTintColor]];\n    model7.block = ^{\n        [weakSelf doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"barTintColor\"];\n    };\n    [settings addObject:model7];\n    \n    DoraemonHierarchyCellModel *model8 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Background\" detailTitle:[self doraemon_hierarchyImageDescription:self.backgroundImage]] noneInsets];\n    [settings addObject:model8];\n    \n    DoraemonHierarchyCellModel *model9 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Scope Bar\" detailTitle:[self doraemon_hierarchyImageDescription:self.scopeBarBackgroundImage]];\n    [settings addObject:model9];\n    \n    DoraemonHierarchyCellModel *model10 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Text Offset\" detailTitle:[self doraemon_hierarchyOffsetDescription:self.searchTextPositionAdjustment]] noneInsets];\n    [settings addObject:model10];\n    \n    DoraemonHierarchyCellModel *model11 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"BG Offset\" detailTitle:[self doraemon_hierarchyOffsetDescription:self.searchFieldBackgroundPositionAdjustment]];\n    [settings addObject:model11];\n    \n    DoraemonHierarchyCellModel *model12 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Options\" detailTitle:@\"Shows Search Results Button\" flag:self.showsSearchResultsButton] noneInsets];\n    model12.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.showsSearchResultsButton = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model12];\n    \n    DoraemonHierarchyCellModel *model13 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Shows Bookmarks Button\" flag:self.showsBookmarkButton] noneInsets];\n    model13.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.showsBookmarkButton = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model13];\n    \n    DoraemonHierarchyCellModel *model14 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Shows Cancel Button\" flag:self.showsCancelButton] noneInsets];\n    model14.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.showsCancelButton = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model14];\n    \n    DoraemonHierarchyCellModel *model15 = [[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:@\"Shows Scope Bar\" flag:self.showsScopeBar];\n    model15.changePropertyBlock = ^(id  _Nullable obj) {\n        weakSelf.showsScopeBar = [obj boolValue];\n        [weakSelf doraemon_postHierarchyChangeNotification];\n    };\n    [settings addObject:model15];\n    \n    DoraemonHierarchyCellModel *model16 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Scope Titles\" detailTitle:[self doraemon_hierarchyObjectDescription:self.scopeButtonTitles]];\n    [settings addObject:model16];\n    \n    DoraemonHierarchyCellModel *model17 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Capitalization\" detailTitle:[DoraemonEnumDescription textAutocapitalizationTypeDescription:self.autocapitalizationType]] noneInsets];\n    model17.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription textAutocapitalizationTypes] currentAction:[DoraemonEnumDescription textAutocapitalizationTypeDescription:weakSelf.autocapitalizationType] completion:^(NSInteger index) {\n            weakSelf.autocapitalizationType = index;\n        }];\n    };\n    [settings addObject:model17];\n    \n    DoraemonHierarchyCellModel *model18 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Correction\" detailTitle:[DoraemonEnumDescription textAutocorrectionTypeDescription:self.autocorrectionType]] noneInsets];\n    model18.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription textAutocorrectionTypes] currentAction:[DoraemonEnumDescription textAutocorrectionTypeDescription:weakSelf.autocorrectionType] completion:^(NSInteger index) {\n            weakSelf.autocorrectionType = index;\n        }];\n    };\n    [settings addObject:model18];\n    \n    DoraemonHierarchyCellModel *model19 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Keyboard\" detailTitle:[DoraemonEnumDescription keyboardTypeDescription:self.keyboardType]];\n    model19.block = ^{\n        [weakSelf doraemon_showActionSheetWithActions:[DoraemonEnumDescription keyboardTypes] currentAction:[DoraemonEnumDescription keyboardTypeDescription:weakSelf.keyboardType] completion:^(NSInteger index) {\n            weakSelf.keyboardType = index;\n        }];\n    };\n    [settings addObject:model19];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Search Bar\" items:settings];\n                                \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n\n@implementation UIWindow (DoraemonHierarchy)\n\n- (NSArray<DoraemonHierarchyCategoryModel *> *)doraemon_hierarchyCategoryModels {\n    NSMutableArray *settings = [[NSMutableArray alloc] init];\n    \n    DoraemonHierarchyCellModel *model1 = [[[DoraemonHierarchyCellModel alloc] initWithTitle:nil detailTitle:[NSString stringWithFormat:@\"Key Window %@\",[self doraemon_hierarchyBoolDescription:self.isKeyWindow]]] noneInsets];\n    [settings addObject:model1];\n    \n    DoraemonHierarchyCellModel *model2 = [[DoraemonHierarchyCellModel alloc] initWithTitle:@\"Root Controller\" detailTitle:[self doraemon_hierarchyObjectDescription:self.rootViewController]];\n    [settings addObject:model2];\n    \n    DoraemonHierarchyCategoryModel *model = [[DoraemonHierarchyCategoryModel alloc] initWithTitle:@\"Window\" items:settings];\n                                \n    NSMutableArray *models = [[NSMutableArray alloc] initWithArray:[super doraemon_hierarchyCategoryModels]];\n    if (models.count > 0) {\n        [models insertObject:model atIndex:1];\n    } else {\n        [models addObject:model];\n    }\n    return [models copy];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/Function/Category/UIColor+DoraemonHierarchy.h",
    "content": "//\n//  UIColor+DoraemonHierarchy.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UIColor (DoraemonHierarchy)\n\n- (NSString *)doraemon_HexString;\n\n- (NSString *)doraemon_description;\n\n- (NSString *_Nullable)doraemon_systemColorName;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/Function/Category/UIColor+DoraemonHierarchy.m",
    "content": "//\n//  UIColor+DoraemonHierarchy.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"UIColor+DoraemonHierarchy.h\"\n#import \"UIColor+Doraemon.h\"\n\n@implementation UIColor (DoraemonHierarchy)\n\n- (NSString *)doraemon_HexString {\n    int r = [self red] * 255.0;\n    int g = [self green] * 255.0;\n    int b = [self blue] * 255.0;\n    return [NSString stringWithFormat:@\"#%02X%02X%02X\",r, g, b];\n}\n\n- (NSString *)doraemon_description {\n    if ([self isEqual:[UIColor clearColor]]) {\n        return @\"Clear Color\";\n    }\n    \n    NSString *color = [self doraemon_systemColorName];\n    \n    if (color) {\n        color = [color stringByAppendingFormat:@\" (%@)\",[self doraemon_RGBADescrption]];\n    } else {\n        color = [self doraemon_RGBADescrption];\n    }\n    \n    return color;\n}\n\n- (NSString *)doraemon_systemColorName {\n    return [self valueForKeyPath:@\"systemColorName\"];\n}\n\n#pragma mark - Primary\n- (NSString *)doraemon_RGBADescrption {\n    int r = [self red] * 255.0;\n    int g = [self green] * 255.0;\n    int b = [self blue] * 255.0;\n    int a = [self alpha] * 255.0;\n    NSString *desc = [NSString stringWithFormat:@\"#%02X%02X%02X\",r,g,b];\n    if (a < 255) {\n        desc = [desc stringByAppendingFormat:@\", Alpha: %0.2f\", [self alpha]];\n    }\n    return desc;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/Function/Category/UIViewController+DoraemonHierarchy.h",
    "content": "//\n//  UIViewController+DoraemonHierarchy.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UIViewController (DoraemonHierarchy)\n\n- (UIViewController *_Nullable)doraemon_currentShowingViewController;\n\n- (void)doraemon_showAlertControllerWithMessage:(NSString *)message handler:(nullable void (^)(NSInteger action))handler;\n\n- (void)doraemon_showActionSheetWithTitle:(NSString *)title actions:(NSArray *)actions currentAction:(nullable NSString *)currentAction completion:(nullable void (^)(NSInteger index))completion;\n\n- (void)doraemon_showTextFieldAlertControllerWithMessage:(NSString *)message text:(nullable NSString *)text handler:(nullable void (^)(NSString * _Nullable))handler;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/Function/Category/UIViewController+DoraemonHierarchy.m",
    "content": "//\n//  UIViewController+DoraemonHierarchy.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"UIViewController+DoraemonHierarchy.h\"\n#import \"DoraemonDefine.h\"\n\n@implementation UIViewController (DoraemonHierarchy)\n\n- (UIViewController *)doraemon_currentShowingViewController {\n    \n    UIViewController *vc = self;\n    if ([self presentedViewController]) {\n        vc = [[self presentedViewController] doraemon_currentShowingViewController];\n    } else if ([vc isKindOfClass:[UITabBarController class]]) {\n        UITabBarController *tabBar = (UITabBarController *)vc;\n        vc = [tabBar.selectedViewController doraemon_currentShowingViewController];\n    } else if ([vc isKindOfClass:[UINavigationController class]]) {\n        UINavigationController *nav = (UINavigationController *)vc;\n        vc = [[nav visibleViewController] doraemon_currentShowingViewController];\n    }\n    return vc;\n}\n\n- (void)doraemon_showAlertControllerWithMessage:(NSString *)message handler:(void (^)(NSInteger action))handler {\n    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@\"Note\" message:message preferredStyle:UIAlertControllerStyleAlert];\n    UIAlertAction *cancel = [UIAlertAction actionWithTitle:@\"Cancel\" style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {\n        if (handler) {\n            handler(0);\n        }\n    }];\n    UIAlertAction *confirm = [UIAlertAction actionWithTitle:@\"Confirm\" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) {\n        if (handler) {\n            handler(1);\n        }\n    }];\n    [alert addAction:cancel];\n    [alert addAction:confirm];\n    dispatch_async(dispatch_get_main_queue(), ^{\n        [self presentViewController:alert animated:YES completion:nil];\n    });\n}\n\n- (void)doraemon_showActionSheetWithTitle:(NSString *)title actions:(NSArray *)actions currentAction:(NSString *)currentAction completion:(void (^)(NSInteger index))completion {\n    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:title preferredStyle:UIAlertControllerStyleActionSheet];\n    for (NSInteger i = 0; i < actions.count; i++) {\n        NSString *actionTitle = actions[i];\n        __block NSInteger index = i;\n        UIAlertAction *action = [UIAlertAction actionWithTitle:actionTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n            if (completion) {\n                completion(index);\n            }\n        }];\n        if (currentAction && [actionTitle isEqualToString:currentAction]) {\n            action.enabled = NO;\n            [action setValue:[UIImage doraemon_xcassetImageNamed:@\"doraemon_hierarchy_select\"] forKey:@\"image\"];\n        }\n        [alert addAction:action];\n    }\n    [alert addAction:[UIAlertAction actionWithTitle:@\"Cancel\" style:UIAlertActionStyleCancel handler:nil]];\n    dispatch_async(dispatch_get_main_queue(), ^{\n        [self presentViewController:alert animated:YES completion:nil];\n    });\n}\n\n- (void)doraemon_showTextFieldAlertControllerWithMessage:(NSString *)message text:(nullable NSString *)text handler:(nullable void (^)(NSString * _Nullable))handler {\n    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert];\n    [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {\n        textField.text = text;\n    }];\n    [alert addAction:[UIAlertAction actionWithTitle:@\"Cancel\" style:UIAlertActionStyleCancel handler:nil]];\n    [alert addAction:[UIAlertAction actionWithTitle:@\"Confirm\" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {\n        if (handler) {\n            handler(alert.textFields.firstObject.text);\n        }\n    }]];\n    dispatch_async(dispatch_get_main_queue(), ^{\n        [self presentViewController:alert animated:YES completion:nil];\n    });\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/Function/DoraemonEnumDescription.h",
    "content": "//\n//  DoraemonEnumDescription.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonEnumDescription : NSObject\n\n+ (NSString *_Nullable)lineBreakModeDescription:(NSLineBreakMode)mode;\n\n+ (NSArray <NSString *>*)lineBreaks;\n\n+ (NSString *_Nullable)userInterfaceStyleDescription:(UIUserInterfaceStyle)style API_AVAILABLE(ios(12.0));\n\n+ (NSString *_Nullable)userInterfaceSizeClassDescription:(UIUserInterfaceSizeClass)sizeClass;\n\n+ (NSString *_Nullable)traitEnvironmentLayoutDirectionDescription:(UITraitEnvironmentLayoutDirection)direction API_AVAILABLE(ios(10.0));\n\n+ (NSString *_Nullable)viewContentModeDescription:(UIViewContentMode)mode;\n\n+ (NSArray <NSString *>*)viewContentModeDescriptions;\n\n+ (NSString *_Nullable)textAlignmentDescription:(NSTextAlignment)textAlignment;\n\n+ (NSArray <NSString *>*)textAlignments;\n\n+ (NSString *_Nullable)baselineAdjustmentDescription:(UIBaselineAdjustment)baselineAdjustment;\n\n+ (NSArray <NSString *>*)baselineAdjustments;\n\n+ (NSString *_Nullable)controlContentVerticalAlignmentDescription:(UIControlContentVerticalAlignment)contentVerticalAlignment;\n\n+ (NSArray <NSString *>*)controlContentVerticalAlignments;\n\n+ (NSString *_Nullable)controlContentHorizontalAlignmentDescription:(UIControlContentHorizontalAlignment)contentHorizontalAlignment;\n\n+ (NSArray <NSString *>*)controlContentHorizontalAlignments;\n\n+ (NSString *_Nullable)buttonTypeDescription:(UIButtonType)buttonType;\n\n+ (NSString *_Nullable)controlStateDescription:(UIControlState)state;\n\n+ (NSString *_Nullable)textBorderStyleDescription:(UITextBorderStyle)style;\n\n+ (NSArray <NSString *>*)textBorderStyles;\n\n+ (NSString *_Nullable)textFieldViewModeDescription:(UITextFieldViewMode)mode;\n\n+ (NSArray <NSString *>*)textFieldViewModes;\n\n+ (NSString *_Nullable)textAutocapitalizationTypeDescription:(UITextAutocapitalizationType)type;\n\n+ (NSArray <NSString *>*)textAutocapitalizationTypes;\n\n+ (NSString *_Nullable)textAutocorrectionTypeDescription:(UITextAutocorrectionType)type;\n\n+ (NSArray <NSString *>*)textAutocorrectionTypes;\n\n+ (NSString *_Nullable)keyboardTypeDescription:(UIKeyboardType)type;\n\n+ (NSArray <NSString *>*)keyboardTypes;\n\n+ (NSString *_Nullable)keyboardAppearanceDescription:(UIKeyboardAppearance)appearance;\n\n+ (NSArray <NSString *>*)keyboardAppearances;\n\n+ (NSString *_Nullable)returnKeyTypeDescription:(UIReturnKeyType)type;\n\n+ (NSArray <NSString *>*)returnKeyTypes;\n\n+ (NSString *_Nullable)activityIndicatorViewStyleDescription:(UIActivityIndicatorViewStyle)style;\n\n+ (NSArray <NSString *>*)activityIndicatorViewStyles;\n\n+ (NSString *_Nullable)progressViewStyleDescription:(UIProgressViewStyle)style;\n\n+ (NSArray <NSString *>*)progressViewStyles;\n\n+ (NSString *_Nullable)scrollViewIndicatorStyleDescription:(UIScrollViewIndicatorStyle)style;\n\n+ (NSArray <NSString *>*)scrollViewIndicatorStyles;\n\n+ (NSString *_Nullable)scrollViewKeyboardDismissModeDescription:(UIScrollViewKeyboardDismissMode)mode;\n\n+ (NSArray <NSString *>*)scrollViewKeyboardDismissModes;\n\n+ (NSString *_Nullable)tableViewStyleDescription:(UITableViewStyle)style;\n\n+ (NSString *_Nullable)tableViewCellSeparatorStyleDescription:(UITableViewCellSeparatorStyle)style;\n\n+ (NSArray <NSString *>*)tableViewCellSeparatorStyles;\n\n+ (NSString *_Nullable)tableViewSeparatorInsetReferenceDescription:(UITableViewSeparatorInsetReference)reference API_AVAILABLE(ios(11.0));\n\n+ (NSArray <NSString *>*)tableViewSeparatorInsetReferences API_AVAILABLE(ios(11.0));\n\n+ (NSString *_Nullable)tableViewCellSelectionStyleDescription:(UITableViewCellSelectionStyle)style;\n\n+ (NSArray <NSString *>*)tableViewCellSelectionStyles;\n\n+ (NSString *_Nullable)tableViewCellAccessoryTypeDescription:(UITableViewCellAccessoryType)type;\n\n+ (NSArray <NSString *>*)tableViewCellAccessoryTypes;\n\n+ (NSString *_Nullable)datePickerModeDescription:(UIDatePickerMode)mode;\n\n+ (NSArray <NSString *>*)datePickerModes;\n\n+ (NSString *_Nullable)barStyleDescription:(UIBarStyle)style;\n\n+ (NSArray <NSString *>*)barStyles;\n\n+ (NSString *_Nullable)searchBarStyleDescription:(UISearchBarStyle)style;\n\n+ (NSArray <NSString *>*)searchBarStyles;\n\n+ (NSString *_Nullable)tabBarItemPositioningDescription:(UITabBarItemPositioning)positioning;\n\n+ (NSArray <NSString *>*)tabBarItemPositionings;\n\n+ (NSString *_Nullable)layoutAttributeDescription:(NSLayoutAttribute)attribute;\n\n+ (NSArray <NSString *>*)layoutAttributes;\n\n+ (NSString *_Nullable)layoutRelationDescription:(NSLayoutRelation)relation;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/Function/DoraemonEnumDescription.m",
    "content": "//\n//  DoraemonEnumDescription.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonEnumDescription.h\"\n\n@implementation DoraemonEnumDescription\n\n+ (NSString *)lineBreakModeDescription:(NSLineBreakMode)mode {\n    switch (mode) {\n        case NSLineBreakByWordWrapping:\n            return @\"Word Wrapping\";\n        case NSLineBreakByCharWrapping:\n            return @\"Char Wrapping\";\n        case NSLineBreakByClipping:\n            return @\"Clipping\";\n        case NSLineBreakByTruncatingHead:\n            return @\"Truncating Head\";\n        case NSLineBreakByTruncatingMiddle:\n            return @\"Truncating Middle\";\n        case NSLineBreakByTruncatingTail:\n            return @\"Truncation Tail\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)lineBreaks {\n    NSMutableArray *breaks = [[NSMutableArray alloc] init];\n    for (NSLineBreakMode i = NSLineBreakByWordWrapping; i <= NSLineBreakByTruncatingMiddle; i++) {\n        NSString *lineBreak = [self lineBreakModeDescription:i];\n        if (lineBreak) {\n            [breaks addObject:lineBreak];\n        }\n    }\n    return [breaks copy];\n}\n\n+ (NSString *)userInterfaceStyleDescription:(UIUserInterfaceStyle)style {\n    switch (style) {\n        case UIUserInterfaceStyleUnspecified:\n            return @\"Unspecified\";\n        case UIUserInterfaceStyleLight:\n            return @\"Light User Interface Style\";\n        case UIUserInterfaceStyleDark:\n            return @\"Dark User Interface Style\";\n    }\n    return nil;\n}\n\n+ (NSString *)userInterfaceSizeClassDescription:(UIUserInterfaceSizeClass)sizeClass {\n    switch (sizeClass) {\n        case UIUserInterfaceSizeClassUnspecified:\n            return @\"Unspecified\";\n        case UIUserInterfaceSizeClassCompact:\n            return @\"Compact Size Class\";\n        case UIUserInterfaceSizeClassRegular:\n            return @\"Regular Size Class\";\n    }\n    return nil;\n}\n\n+ (NSString *)traitEnvironmentLayoutDirectionDescription:(UITraitEnvironmentLayoutDirection)direction {\n    switch (direction) {\n        case UITraitEnvironmentLayoutDirectionUnspecified:\n            return @\"Unspecified\";\n        case UITraitEnvironmentLayoutDirectionLeftToRight:\n            return @\"Left To Right Layout\";\n        case UITraitEnvironmentLayoutDirectionRightToLeft:\n            return @\"Right To Left Layout\";\n    }\n    return nil;\n}\n\n+ (NSString *)viewContentModeDescription:(UIViewContentMode)mode {\n    switch (mode) {\n        case UIViewContentModeScaleToFill:\n            return @\"ScaleToFill\";\n        case UIViewContentModeScaleAspectFit:\n            return @\"ScaleAspectFit\";\n        case UIViewContentModeScaleAspectFill:\n            return @\"ScaleAspectFill\";\n        case UIViewContentModeRedraw:\n            return @\"Redraw\";\n        case UIViewContentModeCenter:\n            return @\"Center\";\n        case UIViewContentModeTop:\n            return @\"Top\";\n        case UIViewContentModeBottom:\n            return @\"Bottom\";\n        case UIViewContentModeLeft:\n            return @\"Left\";\n        case UIViewContentModeRight:\n            return @\"Right\";\n        case UIViewContentModeTopLeft:\n            return @\"TopLeft\";\n        case UIViewContentModeTopRight:\n            return @\"TopRight\";\n        case UIViewContentModeBottomLeft:\n            return @\"BottomLeft\";\n        case UIViewContentModeBottomRight:\n            return @\"BottomRight\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)viewContentModeDescriptions {\n    NSMutableArray *descriptions = [[NSMutableArray alloc] init];\n    for (UIViewContentMode i = UIViewContentModeScaleToFill; i <= UIViewContentModeBottomRight; i++) {\n        NSString *description = [self viewContentModeDescription:i];\n        if (description) {\n            [descriptions addObject:description];\n        }\n    }\n    return [descriptions copy];\n}\n\n+ (NSString *)textAlignmentDescription:(NSTextAlignment)textAlignment {\n    switch (textAlignment) {\n        case NSTextAlignmentLeft:\n            return @\"Left\";\n        case NSTextAlignmentRight:\n            return @\"Right\";\n        case NSTextAlignmentCenter:\n            return @\"Center\";\n        case NSTextAlignmentJustified:\n            return @\"Justified\";\n        case NSTextAlignmentNatural:\n            return @\"Natural\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)textAlignments {\n    NSMutableArray *alignments = [[NSMutableArray alloc] init];\n    for (NSTextAlignment i = NSTextAlignmentLeft; i <= NSTextAlignmentNatural; i++) {\n        NSString *alignment = [self textAlignmentDescription:i];\n        if (alignment) {\n            [alignments addObject:alignment];\n        }\n    }\n    return [alignments copy];\n}\n\n+ (NSString *)baselineAdjustmentDescription:(UIBaselineAdjustment)baselineAdjustment {\n    switch (baselineAdjustment) {\n        case UIBaselineAdjustmentAlignBaselines:\n            return @\"Baselines\";\n        case UIBaselineAdjustmentAlignCenters:\n            return @\"Centers\";\n        case UIBaselineAdjustmentNone:\n            return @\"None\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)baselineAdjustments {\n    NSMutableArray *adjustments = [[NSMutableArray alloc] init];\n    for (UIBaselineAdjustment i = UIBaselineAdjustmentAlignBaselines; i <= UIBaselineAdjustmentNone; i++) {\n        NSString *adjustment = [self baselineAdjustmentDescription:i];\n        if (adjustment) {\n            [adjustments addObject:adjustment];\n        }\n    }\n    return adjustments;\n}\n\n+ (NSString *)controlContentVerticalAlignmentDescription:(UIControlContentVerticalAlignment)contentVerticalAlignment {\n    switch (contentVerticalAlignment) {\n        case UIControlContentVerticalAlignmentCenter:\n            return @\"Centered\";\n        case UIControlContentVerticalAlignmentTop:\n            return @\"Top\";\n        case UIControlContentVerticalAlignmentBottom:\n            return @\"Bottom\";\n        case UIControlContentVerticalAlignmentFill:\n            return @\"Fill\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)controlContentVerticalAlignments {\n    NSMutableArray *alignments = [[NSMutableArray alloc] init];\n    for (UIControlContentVerticalAlignment i = UIControlContentVerticalAlignmentCenter; i <= UIControlContentVerticalAlignmentFill; i++) {\n        NSString *alignment = [self controlContentVerticalAlignmentDescription:i];\n        if (alignment) {\n            [alignments addObject:alignment];\n        }\n    }\n    return [alignments copy];\n}\n\n+ (NSString *)controlContentHorizontalAlignmentDescription:(UIControlContentHorizontalAlignment)contentHorizontalAlignment {\n    switch (contentHorizontalAlignment) {\n        case UIControlContentHorizontalAlignmentCenter:\n            return @\"Centered\";\n        case UIControlContentHorizontalAlignmentLeft:\n            return @\"Left\";\n        case UIControlContentHorizontalAlignmentRight:\n            return @\"Right\";\n        case UIControlContentHorizontalAlignmentFill:\n            return @\"Fill\";\n        case UIControlContentHorizontalAlignmentLeading:\n            return @\"Leading\";\n        case UIControlContentHorizontalAlignmentTrailing:\n            return @\"Trailing\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)controlContentHorizontalAlignments {\n    NSMutableArray *alignments = [[NSMutableArray alloc] init];\n    UIControlContentHorizontalAlignment max = UIControlContentHorizontalAlignmentFill;\n    if (@available(iOS 11.0, *)) {\n        max = UIControlContentHorizontalAlignmentTrailing;\n    }\n    for (UIControlContentHorizontalAlignment i = UIControlContentHorizontalAlignmentCenter; i <= max; i++) {\n        NSString *alignment = [self controlContentHorizontalAlignmentDescription:i];\n        if (alignment) {\n            [alignments addObject:alignment];\n        }\n    }\n    return [alignments copy];\n}\n\n+ (NSString *)buttonTypeDescription:(UIButtonType)buttonType {\n    switch (buttonType) {\n        case UIButtonTypeCustom:\n            return @\"Custom\";\n        case UIButtonTypeSystem:\n            return @\"System\";\n        case UIButtonTypeDetailDisclosure:\n            return @\"Detail Disclosure\";\n        case UIButtonTypeInfoLight:\n            return @\"Info Light\";\n        case UIButtonTypeInfoDark:\n            return @\"Info Dark\";\n        case UIButtonTypeContactAdd:\n            return @\"Contact Add\";\n#ifdef __IPHONE_13_0\n        case UIButtonTypeClose:\n            return @\"Close\";\n#endif\n        default:\n            break;\n    }\n    return nil;\n}\n\n+ (NSString *)controlStateDescription:(UIControlState)state {\n    switch (state) {\n        case UIControlStateNormal:\n            return @\"Normal\";\n        case UIControlStateFocused:\n            return @\"Focused\";\n        case UIControlStateDisabled:\n            return @\"Disabled\";\n        case UIControlStateReserved:\n            return @\"Reserved\";\n        case UIControlStateSelected:\n            return @\"Selected\";\n        case UIControlStateApplication:\n            return @\"Application\";\n        case UIControlStateHighlighted:\n            return @\"Highlighted\";\n    }\n    return nil;\n}\n\n+ (NSString *)textBorderStyleDescription:(UITextBorderStyle)style {\n    switch (style) {\n        case UITextBorderStyleNone:\n            return @\"None\";\n        case UITextBorderStyleLine:\n            return @\"Line\";\n        case UITextBorderStyleBezel:\n            return @\"Bezel\";\n        case UITextBorderStyleRoundedRect:\n            return @\"Rounded Rect\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)textBorderStyles {\n    NSMutableArray *styles = [[NSMutableArray alloc] init];\n    for (UITextBorderStyle i = UITextBorderStyleNone; i <= UITextBorderStyleRoundedRect; i++) {\n        NSString *style = [self textBorderStyleDescription:i];\n        if (style) {\n            [styles addObject:style];\n        }\n    }\n    return [styles copy];\n}\n\n+ (NSString *)textFieldViewModeDescription:(UITextFieldViewMode)mode {\n    switch (mode) {\n        case UITextFieldViewModeNever:\n            return @\"Never appears\";\n        case UITextFieldViewModeWhileEditing:\n            return @\"While editing\";\n        case UITextFieldViewModeUnlessEditing:\n            return @\"Unless editing\";\n        case UITextFieldViewModeAlways:\n            return @\"Always appears\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)textFieldViewModes {\n    NSMutableArray *modes = [[NSMutableArray alloc] init];\n    for (UITextFieldViewMode i = UITextFieldViewModeNever; i <= UITextFieldViewModeAlways; i++) {\n        NSString *mode = [self textFieldViewModeDescription:i];\n        if (mode) {\n            [modes addObject:mode];\n        }\n    }\n    return [modes copy];\n}\n\n+ (NSString *)textAutocapitalizationTypeDescription:(UITextAutocapitalizationType)type {\n    switch (type) {\n        case UITextAutocapitalizationTypeNone:\n            return @\"None\";\n        case UITextAutocapitalizationTypeWords:\n            return @\"Words\";\n        case UITextAutocapitalizationTypeSentences:\n            return @\"Sentences\";\n        case UITextAutocapitalizationTypeAllCharacters:\n            return @\"All Characters\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)textAutocapitalizationTypes {\n    NSMutableArray *types = [[NSMutableArray alloc] init];\n    for (UITextAutocapitalizationType i = UITextAutocapitalizationTypeNone; i <= UITextAutocapitalizationTypeAllCharacters; i++) {\n        NSString *type = [self textAutocapitalizationTypeDescription:i];\n        if (type) {\n            [types addObject:type];\n        }\n    }\n    return [types copy];\n}\n\n+ (NSString *)textAutocorrectionTypeDescription:(UITextAutocorrectionType)type {\n    switch (type) {\n        case UITextAutocorrectionTypeDefault:\n            return @\"Default\";\n        case UITextAutocorrectionTypeNo:\n            return @\"NO\";\n        case UITextAutocorrectionTypeYes:\n            return @\"YES\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)textAutocorrectionTypes {\n    NSMutableArray *types = [[NSMutableArray alloc] init];\n    for (UITextAutocorrectionType i = 0; i <= UITextAutocorrectionTypeYes; i++) {\n        NSString *type = [self textAutocorrectionTypeDescription:i];\n        if (type) {\n            [types addObject:type];\n        }\n    }\n    return [types copy];\n}\n\n+ (NSString *)keyboardTypeDescription:(UIKeyboardType)type {\n    switch (type) {\n        case UIKeyboardTypeDefault:\n            return @\"Default\";\n        case UIKeyboardTypeASCIICapable:\n            return @\"ASCII capable\";\n        case UIKeyboardTypeNumbersAndPunctuation:\n            return @\"Numbers and punctuation\";\n        case UIKeyboardTypeURL:\n            return @\"URL\";\n        case UIKeyboardTypeNumberPad:\n            return @\"Number pad\";\n        case UIKeyboardTypePhonePad:\n            return @\"Phone pad\";\n        case UIKeyboardTypeNamePhonePad:\n            return @\"Name phone pad\";\n        case UIKeyboardTypeEmailAddress:\n            return @\"Email address\";\n        case UIKeyboardTypeDecimalPad:\n            return @\"Decimal pad\";\n        case UIKeyboardTypeTwitter:\n            return @\"Twitter\";\n        case UIKeyboardTypeWebSearch:\n            return @\"Web search\";\n        case UIKeyboardTypeASCIICapableNumberPad:\n            return @\"ASCII capable number pad\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)keyboardTypes {\n    NSMutableArray *types = [[NSMutableArray alloc] init];\n    UIKeyboardType max = UIKeyboardTypeWebSearch;\n    if (@available(iOS 10.0, *)) {\n        max = UIKeyboardTypeASCIICapableNumberPad;\n    }\n    for (UIKeyboardType i = UIKeyboardTypeDefault; i <= max; i++) {\n        NSString *type = [self keyboardTypeDescription:i];\n        if (type) {\n            [types addObject:type];\n        }\n    }\n    return [types copy];\n}\n\n+ (NSString *)keyboardAppearanceDescription:(UIKeyboardAppearance)appearance {\n    switch (appearance) {\n        case UIKeyboardAppearanceDefault:\n            return @\"Default\";\n        case UIKeyboardAppearanceDark:\n            return @\"Dark\";\n        case UIKeyboardAppearanceLight:\n            return @\"Light\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)keyboardAppearances {\n    NSMutableArray *appearances = [[NSMutableArray alloc] init];\n    for (UIKeyboardAppearance i = UIKeyboardAppearanceDefault; i <= UIKeyboardAppearanceLight; i++) {\n        NSString *appearance = [self keyboardAppearanceDescription:i];\n        if (appearance) {\n            [appearances addObject:appearance];\n        }\n    }\n    return [appearances copy];\n}\n\n+ (NSString *)returnKeyTypeDescription:(UIReturnKeyType)type {\n    switch (type) {\n        case UIReturnKeyDefault:\n            return @\"Default\";\n        case UIReturnKeyGo:\n            return @\"Go\";\n        case UIReturnKeyGoogle:\n            return @\"Google\";\n        case UIReturnKeyJoin:\n            return @\"Join\";\n        case UIReturnKeyNext:\n            return @\"Next\";\n        case UIReturnKeyRoute:\n            return @\"Route\";\n        case UIReturnKeySearch:\n            return @\"Search\";\n        case UIReturnKeySend:\n            return @\"Send\";\n        case UIReturnKeyYahoo:\n            return @\"Yahoo\";\n        case UIReturnKeyDone:\n            return @\"Done\";\n        case UIReturnKeyEmergencyCall:\n            return @\"Emergency call\";\n        case UIReturnKeyContinue:\n            return @\"Continue\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)returnKeyTypes {\n    NSMutableArray *types = [[NSMutableArray alloc] init];\n    UIReturnKeyType max = UIReturnKeyEmergencyCall;\n    if (@available(iOS 9.0, *)) {\n        max = UIReturnKeyContinue;\n    }\n    for (UIReturnKeyType i = UIReturnKeyDefault; i <= max; i++) {\n        NSString *type = [self returnKeyTypeDescription:i];\n        if (type) {\n            [types addObject:type];\n        }\n    }\n    return [types copy];\n}\n\n+ (NSString *)activityIndicatorViewStyleDescription:(UIActivityIndicatorViewStyle)style {\n    switch (style) {\n#ifdef __IPHONE_13_0\n        case UIActivityIndicatorViewStyleMedium:\n            return @\"Medium\";\n        case UIActivityIndicatorViewStyleLarge:\n            return @\"Large\";\n#endif\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n        case UIActivityIndicatorViewStyleWhiteLarge:\n            return @\"White Large\";\n        case UIActivityIndicatorViewStyleWhite:\n            return @\"White\";\n        case UIActivityIndicatorViewStyleGray:\n            return @\"Gray\";\n#pragma clang diagnostic pop\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)activityIndicatorViewStyles {\n    NSMutableArray *styles = [[NSMutableArray alloc] init];\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n    NSArray *actions = @[@(UIActivityIndicatorViewStyleWhiteLarge),@(UIActivityIndicatorViewStyleWhite),@(UIActivityIndicatorViewStyleGray)];\n#ifdef __IPHONE_13_0\n    if (@available(iOS 13.0, *)) {\n        actions = @[@(UIActivityIndicatorViewStyleMedium),@(UIActivityIndicatorViewStyleLarge),@(UIActivityIndicatorViewStyleWhiteLarge),@(UIActivityIndicatorViewStyleWhite),@(UIActivityIndicatorViewStyleGray)];\n    }\n#endif\n#pragma clang diagnostic pop\n    for (NSNumber *num in actions) {\n        NSString *style = [self activityIndicatorViewStyleDescription:[num integerValue]];\n        if (style) {\n            [styles addObject:style];\n        }\n    }\n    return [styles copy];\n}\n\n+ (NSString *)progressViewStyleDescription:(UIProgressViewStyle)style {\n    switch (style) {\n        case UIProgressViewStyleDefault:\n            return @\"Default\";\n        case UIProgressViewStyleBar:\n            return @\"Bar\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)progressViewStyles {\n    NSMutableArray *styles = [[NSMutableArray alloc] init];\n    for (UIProgressViewStyle i = UIProgressViewStyleDefault; i <= UIProgressViewStyleBar; i++) {\n        NSString *style = [self progressViewStyleDescription:i];\n        if (style) {\n            [styles addObject:style];\n        }\n    }\n    return [styles copy];\n}\n\n+ (NSString *)scrollViewIndicatorStyleDescription:(UIScrollViewIndicatorStyle)style {\n    switch (style) {\n        case UIScrollViewIndicatorStyleDefault:\n            return @\"Default\";\n        case UIScrollViewIndicatorStyleBlack:\n            return @\"Black\";\n        case UIScrollViewIndicatorStyleWhite:\n            return @\"White\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)scrollViewIndicatorStyles {\n    NSMutableArray *styles = [[NSMutableArray alloc] init];\n    for (UIScrollViewIndicatorStyle i = UIScrollViewIndicatorStyleDefault; i <= UIScrollViewIndicatorStyleWhite; i++) {\n        NSString *style = [self scrollViewIndicatorStyleDescription:i];\n        if (style) {\n            [styles addObject:style];\n        }\n    }\n    return [styles copy];\n}\n\n+ (NSString *)scrollViewKeyboardDismissModeDescription:(UIScrollViewKeyboardDismissMode)mode {\n    switch (mode) {\n        case UIScrollViewKeyboardDismissModeNone:\n            return @\"Do not dismiss\";\n        case UIScrollViewKeyboardDismissModeOnDrag:\n            return @\"Dismiss on drag\";\n        case UIScrollViewKeyboardDismissModeInteractive:\n            return @\"Dismiss interactively\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)scrollViewKeyboardDismissModes {\n    NSMutableArray *modes = [[NSMutableArray alloc] init];\n    for (UIScrollViewKeyboardDismissMode i = UIScrollViewKeyboardDismissModeNone; i <= UIScrollViewKeyboardDismissModeInteractive; i++) {\n        NSString *mode = [self scrollViewKeyboardDismissModeDescription:i];\n        if (mode) {\n            [modes addObject:mode];\n        }\n    }\n    return [modes copy];\n}\n\n+ (NSString *)tableViewStyleDescription:(UITableViewStyle)style {\n    switch (style) {\n        case UITableViewStylePlain:\n            return @\"Plain\";\n        case UITableViewStyleGrouped:\n            return @\"Grouped\";\n#ifdef __IPHONE_13_0\n        case UITableViewStyleInsetGrouped:\n            return @\"Inset Grouped\";\n#endif\n    }\n    return nil;\n}\n\n+ (NSString *)tableViewCellSeparatorStyleDescription:(UITableViewCellSeparatorStyle)style {\n    switch (style) {\n        case UITableViewCellSeparatorStyleNone:\n            return @\"None\";\n        case UITableViewCellSeparatorStyleSingleLine:\n            return @\"Single Line\";\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n        case UITableViewCellSeparatorStyleSingleLineEtched:\n            return @\"Single Line Etched\";\n#pragma clang diagnostic pop\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)tableViewCellSeparatorStyles {\n    NSMutableArray *styles = [[NSMutableArray alloc] init];\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n    for (UITableViewCellSeparatorStyle i = UITableViewCellSeparatorStyleNone; i <= UITableViewCellSeparatorStyleSingleLineEtched; i++) {\n        NSString *style = [self tableViewCellSeparatorStyleDescription:i];\n        if (style) {\n            [styles addObject:style];\n        }\n    }\n#pragma clang diagnostic pop\n    return [styles copy];\n}\n\n+ (NSString *)tableViewSeparatorInsetReferenceDescription:(UITableViewSeparatorInsetReference)reference {\n    switch (reference) {\n        case UITableViewSeparatorInsetFromCellEdges:\n            return @\"From Cell Edges\";\n        case UITableViewSeparatorInsetFromAutomaticInsets:\n            return @\"Automatic Insets\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)tableViewSeparatorInsetReferences {\n    NSMutableArray *references = [[NSMutableArray alloc] init];\n    for (UITableViewSeparatorInsetReference i = UITableViewSeparatorInsetFromCellEdges; i <= UITableViewSeparatorInsetFromAutomaticInsets; i++) {\n        NSString *reference = [self tableViewSeparatorInsetReferenceDescription:i];\n        if (reference) {\n            [references addObject:reference];\n        }\n    }\n    return [references copy];\n}\n\n+ (NSString *)tableViewCellSelectionStyleDescription:(UITableViewCellSelectionStyle)style {\n    switch (style) {\n        case UITableViewCellSelectionStyleNone:\n            return @\"None\";\n        case UITableViewCellSelectionStyleBlue:\n            return @\"Blue\";\n        case UITableViewCellSelectionStyleGray:\n            return @\"Gray\";\n        case UITableViewCellSelectionStyleDefault:\n            return @\"Default\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)tableViewCellSelectionStyles {\n    NSMutableArray *styles = [[NSMutableArray alloc] init];\n    for (UITableViewCellSelectionStyle i = UITableViewCellSelectionStyleNone; i <= UITableViewCellSelectionStyleDefault; i++) {\n        NSString *style = [self tableViewCellSelectionStyleDescription:i];\n        if (style) {\n            [styles addObject:style];\n        }\n    }\n    return [styles copy];\n}\n\n+ (NSString *)tableViewCellAccessoryTypeDescription:(UITableViewCellAccessoryType)type {\n    switch (type) {\n        case UITableViewCellAccessoryNone:\n            return @\"None\";\n        case UITableViewCellAccessoryDisclosureIndicator:\n            return @\"Disclosure Indicator\";\n        case UITableViewCellAccessoryDetailDisclosureButton:\n            return @\"Disclosure Button\";\n        case UITableViewCellAccessoryCheckmark:\n            return @\"Checkmark\";\n        case UITableViewCellAccessoryDetailButton:\n            return @\"Detail Button\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)tableViewCellAccessoryTypes {\n    NSMutableArray *types = [[NSMutableArray alloc] init];\n    for (UITableViewCellAccessoryType i = UITableViewCellAccessoryNone; i <= UITableViewCellAccessoryDetailButton; i++) {\n        NSString *type = [self tableViewCellAccessoryTypeDescription:i];\n        if (type) {\n            [types addObject:type];\n        }\n    }\n    return [types copy];\n}\n\n+ (NSString *)datePickerModeDescription:(UIDatePickerMode)mode {\n    switch (mode) {\n        case UIDatePickerModeDate:\n            return @\"Date\";\n        case UIDatePickerModeTime:\n            return @\"Time\";\n        case UIDatePickerModeDateAndTime:\n            return @\"Date and Time\";\n        case UIDatePickerModeCountDownTimer:\n            return @\"Count Down Timer\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)datePickerModes {\n    NSMutableArray *modes = [[NSMutableArray alloc] init];\n    for (UIDatePickerMode i = UIDatePickerModeDate; i <= UIDatePickerModeCountDownTimer; i++) {\n        NSString *mode = [self datePickerModeDescription:i];\n        if (mode) {\n            [modes addObject:mode];\n        }\n    }\n    return [modes copy];\n}\n\n+ (NSString *)barStyleDescription:(UIBarStyle)style {\n    switch (style) {\n        case UIBarStyleDefault:\n            return @\"Default\";\n        case UIBarStyleBlack:\n            return @\"Black\";\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n        case UIBarStyleBlackTranslucent:\n            return @\"Black Translucent\";\n#pragma clang diagnostic pop\n        }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)barStyles {\n    NSMutableArray *styles = [[NSMutableArray alloc] init];\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n    for (UIBarStyle i = UIBarStyleDefault; i <= UIBarStyleBlackTranslucent; i++) {\n        NSString *style = [self barStyleDescription:i];\n        if (style) {\n            [styles addObject:style];\n        }\n    }\n#pragma clang diagnostic pop\n    return [styles copy];\n}\n\n+ (NSString *)searchBarStyleDescription:(UISearchBarStyle)style {\n    switch (style) {\n        case UISearchBarStyleDefault:\n            return @\"Default\";\n        case UISearchBarStyleProminent:\n            return @\"Prominent\";\n        case UISearchBarStyleMinimal:\n            return @\"Minimal\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)searchBarStyles {\n    NSMutableArray *styles = [[NSMutableArray alloc] init];\n    for (UISearchBarStyle i = UISearchBarStyleDefault; i <= UISearchBarStyleMinimal; i++) {\n        NSString *style = [self searchBarStyleDescription:i];\n        if (style) {\n            [styles addObject:style];\n        }\n    }\n    return [styles copy];\n}\n\n+ (NSString *)tabBarItemPositioningDescription:(UITabBarItemPositioning)positioning {\n    switch (positioning) {\n        case UITabBarItemPositioningAutomatic:\n            return @\"Automatic\";\n        case UITabBarItemPositioningFill:\n            return @\"Fill\";\n        case UITabBarItemPositioningCentered:\n            return @\"Centered\";\n    }\n    return nil;\n}\n\n+ (NSArray <NSString *>*)tabBarItemPositionings {\n    NSMutableArray *positionings = [[NSMutableArray alloc] init];\n    for (UITabBarItemPositioning i = UITabBarItemPositioningAutomatic; i <= UITabBarItemPositioningCentered; i++) {\n        NSString *positioning = [self tabBarItemPositioningDescription:i];\n        if (positioning) {\n            [positionings addObject:positioning];\n        }\n    }\n    return [positionings copy];\n}\n\n+ (NSString *_Nullable)layoutAttributeDescription:(NSLayoutAttribute)attribute {\n    switch (attribute) {\n        case NSLayoutAttributeLeft:\n            return @\"left\";\n        case NSLayoutAttributeRight:\n            return @\"right\";\n        case NSLayoutAttributeTop:\n            return @\"top\";\n        case NSLayoutAttributeBottom:\n            return @\"bottom\";\n        case NSLayoutAttributeLeading:\n            return @\"leading\";\n        case NSLayoutAttributeTrailing:\n            return @\"trailing\";\n        case NSLayoutAttributeWidth:\n            return @\"width\";\n        case NSLayoutAttributeHeight:\n            return @\"height\";\n        case NSLayoutAttributeCenterX:\n            return @\"centerX\";\n        case NSLayoutAttributeCenterY:\n            return @\"centerY\";\n        case NSLayoutAttributeLastBaseline:\n            return @\"lastBaseline\";\n        case NSLayoutAttributeFirstBaseline:\n            return @\"firstBaseline\";\n        case NSLayoutAttributeLeftMargin:\n            return @\"leftMargin\";\n        case NSLayoutAttributeRightMargin:\n            return @\"rightMargin\";\n        case NSLayoutAttributeTopMargin:\n            return @\"topMargin\";\n        case NSLayoutAttributeBottomMargin:\n            return @\"bottomMargin\";\n        case NSLayoutAttributeLeadingMargin:\n            return @\"leadingMargin\";\n        case NSLayoutAttributeTrailingMargin:\n            return @\"trailingMargin\";\n        case NSLayoutAttributeCenterXWithinMargins:\n            return @\"centerXMargin\";\n        case NSLayoutAttributeCenterYWithinMargins:\n            return @\"centerYMargin\";\n        case NSLayoutAttributeNotAnAttribute:\n            return nil;\n        default:\n            return nil;\n    }\n}\n\n+ (NSArray <NSString *>*)layoutAttributes {\n    NSMutableArray *attributes = [[NSMutableArray alloc] init];\n    for (NSLayoutAttribute i = NSLayoutAttributeNotAnAttribute; i <= NSLayoutAttributeCenterYWithinMargins; i++) {\n        NSString *attribute = [self layoutAttributeDescription:i];\n        if (attribute) {\n            [attributes addObject:attribute];\n        }\n    }\n    return [attributes copy];\n}\n\n+ (NSString *_Nullable)layoutRelationDescription:(NSLayoutRelation)relation {\n    switch (relation) {\n        case NSLayoutRelationLessThanOrEqual:\n            return @\"<=\";\n        case NSLayoutRelationEqual:\n            return @\"=\";\n        case NSLayoutRelationGreaterThanOrEqual:\n            return @\">=\";\n        default:\n            return nil;\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/Function/DoraemonHierarchyFormatterTool.h",
    "content": "//\n//  DoraemonHierarchyFormatterTool.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n#import <UIKit/UIKit.h>\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHierarchyFormatterTool : NSObject\n\n/**\n Format date use style.\n\n @param date Date.\n @return Format string.\n */\n+ (NSString *_Nullable)stringFromDate:(NSDate *)date;\n\n/**\n Get date use formatted string use style\n\n @param string Formatted string.\n @return Date.\n */\n+ (NSDate *_Nullable)dateFromString:(NSString *)string;\n\n/**\n Format a CGFloat value with maximumFractionDigits = 2.\n\n @param number NSNumber.\n @return Format string.\n */\n+ (NSString *)formatNumber:(NSNumber *)number;\n\n/**\n Format print frame with maximumFractionDigits = 2.\n \n @param frame CGRect.\n @return Format string.\n */\n+ (NSString *)stringFromFrame:(CGRect)frame;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/Function/DoraemonHierarchyFormatterTool.m",
    "content": "//\n//  DoraemonHierarchyFormatterTool.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n#import <UIKit/UIKit.h>\n#import \"DoraemonHierarchyFormatterTool.h\"\n\nstatic DoraemonHierarchyFormatterTool *_instance = nil;\n\n@interface DoraemonHierarchyFormatterTool ()\n\n@property (nonatomic, strong) NSDateFormatter *formatter;\n\n@property (nonatomic, strong) NSNumberFormatter *numberFormatter;\n\n@end\n\n@implementation DoraemonHierarchyFormatterTool\n\n#pragma mark - Public\n+ (NSString *)stringFromDate:(NSDate *)date {\n    return [[self shared] stringFromDate:date];\n}\n\n+ (NSDate *)dateFromString:(NSString *)string {\n    return [[self shared] dateFromString:string];\n}\n\n+ (NSString *)formatNumber:(NSNumber *)number {\n    return [[self shared] formatNumber:number];\n}\n\n+ (NSString *)stringFromFrame:(CGRect)frame {\n    return [NSString stringWithFormat:@\"{{%@, %@}, {%@, %@}}\",[self formatNumber:@(frame.origin.x)],[self formatNumber:@(frame.origin.y)],[self formatNumber:@(frame.size.width)],[self formatNumber:@(frame.size.height)]];\n}\n\n#pragma mark - Primary\n+ (instancetype)shared {\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        _instance = [[DoraemonHierarchyFormatterTool alloc] init];\n    });\n    return _instance;\n}\n\n- (NSString *)stringFromDate:(NSDate *)date {\n    if (!date) {\n        return nil;\n    }\n    return [self.formatter stringFromDate:date];\n}\n\n- (NSDate *)dateFromString:(NSString *)string {\n    if (!string) {\n        return nil;\n    }\n    return [self.formatter dateFromString:string];\n}\n\n- (NSString *)formatNumber:(NSNumber *)number {\n    return [self.numberFormatter stringFromNumber:number];\n}\n\n#pragma mark - Getters and setters\n- (NSDateFormatter *)formatter {\n    if (!_formatter) {\n        _formatter = [[NSDateFormatter alloc] init];\n        _formatter.dateFormat = @\"yyyy-MM-dd HH:mm:ss\";\n    }\n    return _formatter;\n}\n\n- (NSNumberFormatter *)numberFormatter {\n    if (!_numberFormatter) {\n        _numberFormatter = [[NSNumberFormatter alloc] init];\n        _numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;\n        _numberFormatter.maximumFractionDigits = 2;\n        _numberFormatter.usesGroupingSeparator = NO;\n    }\n    return _numberFormatter;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/Function/DoraemonHierarchyHelper.h",
    "content": "//\n//  DoraemonHierarchyHelper.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\n@class DoraemonHierarchyWindow;\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHierarchyHelper : NSObject\n\n+ (instancetype _Nonnull)shared;\n\n// DoraemonHierarchyWindow isn't a shared instance, so we need a object onwer it when show, and free it when hide.\n@property (nonatomic, strong, nullable) DoraemonHierarchyWindow *window;\n\n@property (nonatomic, assign) BOOL isHierarchyIgnorePrivateClass;\n\n- (NSArray <UIWindow *>*)allWindows;\n\n- (NSArray <UIWindow *>*)allWindowsIgnorePrefix:(NSString *_Nullable)prefix;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/Function/DoraemonHierarchyHelper.m",
    "content": "//\n//  DoraemonHierarchyHelper.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyHelper.h\"\n\nstatic DoraemonHierarchyHelper *_instance = nil;\n\n@implementation DoraemonHierarchyHelper\n\n+ (instancetype)shared {\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        _instance = [[DoraemonHierarchyHelper alloc] init];\n    });\n    return _instance;\n}\n\n- (NSArray <UIWindow *>*)allWindows {\n    return [self allWindowsIgnorePrefix:nil];\n}\n\n- (NSArray <UIWindow *>*)allWindowsIgnorePrefix:(NSString *_Nullable)prefix {\n    BOOL includeInternalWindows = YES;\n    BOOL onlyVisibleWindows = NO;\n    \n    SEL allWindowsSelector = NSSelectorFromString(@\"allWindowsIncludingInternalWindows:onlyVisibleWindows:\");\n    \n    NSMethodSignature *methodSignature = [[UIWindow class] methodSignatureForSelector:allWindowsSelector];\n    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];\n    \n    invocation.target = [UIWindow class];\n    invocation.selector = allWindowsSelector;\n    [invocation setArgument:&includeInternalWindows atIndex:2];\n    [invocation setArgument:&onlyVisibleWindows atIndex:3];\n    [invocation invoke];\n    \n    __unsafe_unretained NSArray<UIWindow *> *windows = nil;\n    [invocation getReturnValue:&windows];\n    \n    windows = [windows sortedArrayUsingComparator:^NSComparisonResult(UIWindow * obj1, UIWindow * obj2) {\n        return obj1.windowLevel > obj2.windowLevel;\n    }];\n    \n    NSMutableArray *results = [[NSMutableArray alloc] initWithArray:windows];\n    NSMutableArray *removeResults = [[NSMutableArray alloc] init];\n    if ([prefix length] > 0) {\n        for (UIWindow *window in results) {\n            if ([NSStringFromClass(window.class) hasPrefix:prefix]) {\n                [removeResults addObject:window];\n            }            \n        }\n    }\n    [results removeObjectsInArray:removeResults];\n    \n    return [NSArray arrayWithArray:results];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/Cell/DoraemonHierarchyDetailTitleCell.h",
    "content": "//\n//  DoraemonHierarchyDetailTitleCell.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyTitleCell.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHierarchyDetailTitleCell : DoraemonHierarchyTitleCell\n\n@property (nonatomic, strong, readonly) UILabel *detailLabel;\n\n@property (nonatomic, strong, readonly) NSLayoutConstraint *detailLabelRightCons;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/Cell/DoraemonHierarchyDetailTitleCell.m",
    "content": "//\n//  DoraemonHierarchyDetailTitleCell.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyDetailTitleCell.h\"\n#import \"DoraemonHierarchyCellModel.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHierarchyDetailTitleCell ()\n\n@property (nonatomic, strong) UILabel *detailLabel;\n\n@property (nonatomic, strong) NSLayoutConstraint *detailLabelRightCons;\n\n@end\n\n@implementation DoraemonHierarchyDetailTitleCell\n\n#pragma mark - Over write\n- (void)initUI {\n    [super initUI];\n    \n    [self.contentView addSubview:self.detailLabel];\n    \n    [self.contentView removeConstraint:self.titleLabelBottomCons];\n    \n    NSLayoutConstraint *right = [NSLayoutConstraint constraintWithItem:self.detailLabel attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.detailLabel.superview attribute:NSLayoutAttributeTrailing multiplier:1 constant:-10];\n    NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:self.detailLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.detailLabel.superview attribute:NSLayoutAttributeTop multiplier:1 constant:10];\n    NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:self.detailLabel attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.detailLabel.superview attribute:NSLayoutAttributeBottom multiplier:1 constant:-10];\n    NSLayoutConstraint *left = [NSLayoutConstraint constraintWithItem:self.detailLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.titleLabel attribute:NSLayoutAttributeTrailing multiplier:1 constant:10 / 2.0];\n    self.detailLabel.translatesAutoresizingMaskIntoConstraints = NO;\n    [self.contentView addConstraints:@[right, top, bottom, left]];\n    \n    self.detailLabelRightCons = right;\n}\n\n#pragma mark - Getters and setters\n- (void)setModel:(DoraemonHierarchyCellModel *)model {\n    [super setModel:model];\n    if (model.detailTitle == nil || model.detailTitle.length == 0) {\n        self.detailLabel.text = @\" \";\n    } else {\n        self.detailLabel.text = model.detailTitle;\n    }\n}\n\n- (UILabel *)detailLabel {\n    if (!_detailLabel) {\n        _detailLabel = [[UILabel alloc] init];\n        _detailLabel.font = [UIFont systemFontOfSize:14];\n        _detailLabel.textColor = [UIColor doraemon_black_1];\n        _detailLabel.textAlignment = NSTextAlignmentRight;\n        _detailLabel.numberOfLines = 0;\n    }\n    return _detailLabel;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/Cell/DoraemonHierarchySelectorCell.h",
    "content": "//\n//  DoraemonHierarchySelectorCell.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyDetailTitleCell.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHierarchySelectorCell : DoraemonHierarchyDetailTitleCell\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/Cell/DoraemonHierarchySelectorCell.m",
    "content": "//\n//  DoraemonHierarchySelectorCell.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchySelectorCell.h\"\n\n@implementation DoraemonHierarchySelectorCell\n\n#pragma mark - Over write\n- (void)initUI {\n    [super initUI];\n    self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/Cell/DoraemonHierarchySwitchCell.h",
    "content": "//\n//  DoraemonHierarchySwitchCell.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyDetailTitleCell.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHierarchySwitchCell : DoraemonHierarchyDetailTitleCell\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/Cell/DoraemonHierarchySwitchCell.m",
    "content": "//\n//  DoraemonHierarchySwitchCell.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchySwitchCell.h\"\n#import \"DoraemonHierarchyCellModel.h\"\n\n@interface DoraemonHierarchySwitchCell ()\n\n@property (nonatomic, strong) UISwitch *swit;\n\n@end\n\n@implementation DoraemonHierarchySwitchCell\n\n#pragma mark - Over write\n- (void)initUI {\n    [super initUI];\n    [self.contentView addSubview:self.swit];\n    \n    [self.contentView removeConstraint:self.detailLabelRightCons];\n    \n    NSLayoutConstraint *left = [NSLayoutConstraint constraintWithItem:self.swit attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.detailLabel attribute:NSLayoutAttributeTrailing multiplier:1 constant:10 / 2.0];\n    NSLayoutConstraint *right = [NSLayoutConstraint constraintWithItem:self.swit attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.swit.superview attribute:NSLayoutAttributeTrailing multiplier:1 constant:-10];\n    NSLayoutConstraint *centerY = [NSLayoutConstraint constraintWithItem:self.swit attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.swit.superview attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];\n    NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:self.swit attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:51];\n    NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:self.swit attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:31];\n    self.swit.translatesAutoresizingMaskIntoConstraints = NO;\n    [self.contentView addConstraints:@[left, right, centerY, width, height]];\n}\n\n#pragma mark - Event responses\n- (void)switchValueChanged:(UISwitch *)sender {\n    self.model.flag = sender.isOn;\n    if (self.model.changePropertyBlock) {\n        self.model.changePropertyBlock(@(sender.isOn));\n    }\n}\n\n#pragma mark - Getters and settings\n- (void)setModel:(DoraemonHierarchyCellModel *)model {\n    [super setModel:model];\n    _swit.on = model.flag;\n}\n\n- (UISwitch *)swit {\n    if (!_swit) {\n        _swit = [[UISwitch alloc] init];\n        [_swit addTarget:self action:@selector(switchValueChanged:) forControlEvents:UIControlEventValueChanged];\n    }\n    return _swit;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/Cell/DoraemonHierarchyTitleCell.h",
    "content": "//\n//  DoraemonHierarchyTitleCell.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import <UIKit/UIKit.h>\n\n@class DoraemonHierarchyCellModel;\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHierarchyTitleCell : UITableViewCell\n\n@property (nonatomic, strong, readonly) UILabel *titleLabel;\n\n@property (nonatomic, strong, readonly) NSLayoutConstraint *titleLabelBottomCons;\n\n@property (nonatomic, strong) DoraemonHierarchyCellModel *model;\n\n- (void)initUI;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/Cell/DoraemonHierarchyTitleCell.m",
    "content": "//\n//  DoraemonHierarchyTitleCell.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyTitleCell.h\"\n#import \"DoraemonHierarchyCellModel.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHierarchyTitleCell ()\n\n@property (nonatomic, strong) UILabel *titleLabel;\n\n@property (nonatomic, strong) NSLayoutConstraint *titleLabelBottomCons;\n\n@end\n\n@implementation DoraemonHierarchyTitleCell\n\n#pragma mark - Life cycle\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {\n    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {\n        [self initUI];\n    }\n    return self;\n}\n\n#pragma mark - Public\n- (void)initUI {\n    self.selectedBackgroundView = [[UIView alloc] init];\n    self.selectionStyle = UITableViewCellSelectionStyleNone;\n    \n    [self.contentView addSubview:self.titleLabel];\n    \n    NSLayoutConstraint *left = [NSLayoutConstraint constraintWithItem:self.titleLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.titleLabel.superview attribute:NSLayoutAttributeLeading multiplier:1 constant:10];\n    NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:self.titleLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.titleLabel.superview attribute:NSLayoutAttributeTop multiplier:1 constant:10];\n    NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:self.titleLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:120];\n    NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:self.titleLabel attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.titleLabel.superview attribute:NSLayoutAttributeBottom multiplier:1 constant:-10];\n    self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;\n    [self.contentView addConstraints:@[left, top, width, bottom]];\n    \n    self.titleLabelBottomCons = bottom;\n}\n\n#pragma mark - Getters and setters\n- (void)setModel:(DoraemonHierarchyCellModel *)model {\n    _model = model;\n    self.titleLabel.text = model.title;\n}\n\n- (UILabel *)titleLabel {\n    if (!_titleLabel) {\n        _titleLabel = [[UILabel alloc] init];\n        _titleLabel.font = [UIFont systemFontOfSize:16];\n        _titleLabel.textColor = [UIColor doraemon_black_1];\n    }\n    return _titleLabel;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/DoraemonHierarchyDetailViewController.h",
    "content": "//\n//  DoraemonHierarchyDetailViewController.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyTableViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHierarchyDetailViewController : DoraemonHierarchyTableViewController\n\n@property (nonatomic, strong, nullable) UIView *selectView;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/DoraemonHierarchyDetailViewController.m",
    "content": "//\n//  DoraemonHierarchyDetailViewController.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyDetailViewController.h\"\n#import \"UIViewController+DoraemonHierarchy.h\"\n#import \"DoraemonHierarchyDetailTitleCell.h\"\n#import \"DoraemonHierarchyFormatterTool.h\"\n#import \"DoraemonHierarchyCategoryModel.h\"\n#import \"NSObject+DoraemonHierarchy.h\"\n#import \"DoraemonHierarchyCellModel.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHierarchyDetailViewController ()\n\n@property (nonatomic, strong) UISegmentedControl *segmentedControl;\n\n@property (nonatomic, strong) NSMutableArray *objectDatas;\n\n@property (nonatomic, strong) NSMutableArray *sizeDatas;\n\n@end\n\n@implementation DoraemonHierarchyDetailViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    NSAssert(self.selectView, @\"SelectView can't be nil\");\n    \n    [self setTitle:DoraemonLocalizedString(@\"UI结构\")];\n    self.objectDatas = [[NSMutableArray alloc] init];\n    self.sizeDatas = [[NSMutableArray alloc] init];\n    \n    UIView *headerView = ({\n        UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, DoraemonScreenWidth, 30 + 10 * 2)];\n        view;\n    });\n    \n    [headerView addSubview:self.segmentedControl];\n    \n    self.tableView.tableHeaderView = headerView;\n    \n    [self loadData];\n    \n    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveDoraemonHierarchyChangeNotification:) name:DoraemonHierarchyChangeNotificationName object:nil];\n}\n\n#pragma mark - Over write\n- (BOOL)needBigTitleView {\n    return YES;\n}\n\n- (void)leftNavBackClick:(id)clickView {\n    [self dismissViewControllerAnimated:YES completion:nil];\n}\n\n#pragma mark - UITableViewDataSource\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];\n    if ([cell isKindOfClass:[DoraemonHierarchyDetailTitleCell class]]) {\n        DoraemonHierarchyDetailTitleCell *detailCell = (DoraemonHierarchyDetailTitleCell *)cell;\n        detailCell.detailLabel.textAlignment = NSTextAlignmentLeft;\n    }\n    DoraemonHierarchyCellModel *model = self.dataArray[indexPath.section].items[indexPath.row];\n    cell.separatorInset = model.separatorInsets;\n    return cell;\n}\n\n#pragma mark - NSNotifications\n- (void)didReceiveDoraemonHierarchyChangeNotification:(NSNotification *)notification {\n    [self loadData];\n}\n\n#pragma mark - Event responses\n- (void)segmentedControlValueChanged:(UISegmentedControl *)sender {\n    [self reloadTableView];\n}\n\n#pragma mark - Primary\n- (void)loadData {\n    [self.objectDatas removeAllObjects];\n    NSArray *models = [self.selectView doraemon_hierarchyCategoryModels];\n    [self.objectDatas addObjectsFromArray:models];\n    \n    [self.sizeDatas removeAllObjects];\n    NSArray *sizeModels = [self.selectView doraemon_sizeHierarchyCategoryModels];\n    [self.sizeDatas addObjectsFromArray:sizeModels];\n    \n    [self reloadTableView];\n}\n\n- (void)reloadTableView {\n    [self.dataArray removeAllObjects];\n    if (self.segmentedControl.selectedSegmentIndex == 0) {\n        [self.dataArray addObjectsFromArray:self.objectDatas];\n    } else if (self.segmentedControl.selectedSegmentIndex == 1) {\n        [self.dataArray addObjectsFromArray:self.sizeDatas];\n    }\n    [self.tableView reloadData];\n}\n\n#pragma mark - Getters and setters\n- (UISegmentedControl *)segmentedControl {\n    if (!_segmentedControl) {\n        _segmentedControl = [[UISegmentedControl alloc] initWithItems:@[@\"Object\", @\"Size\"]];\n        _segmentedControl.frame = CGRectMake(10, 10, self.view.doraemon_width - 10 * 2, 30);\n        [_segmentedControl addTarget:self action:@selector(segmentedControlValueChanged:) forControlEvents:UIControlEventValueChanged];\n        _segmentedControl.selectedSegmentIndex = 0;\n    }\n    return _segmentedControl;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/DoraemonHierarchyTableViewController.h",
    "content": "//\n//  DoraemonHierarchyTableViewController.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@class DoraemonHierarchyCategoryModel;\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHierarchyTableViewController : DoraemonBaseViewController <UITableViewDelegate, UITableViewDataSource>\n\n@property (nonatomic, strong, readonly) UITableView *tableView;\n\n@property (nonatomic, strong, readonly) NSMutableArray <DoraemonHierarchyCategoryModel *>*dataArray;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/DoraemonHierarchyTableViewController.m",
    "content": "//\n//  DoraemonHierarchyTableViewController.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyTableViewController.h\"\n#import \"DoraemonHierarchyCategoryModel.h\"\n#import \"DoraemonHierarchySelectorCell.h\"\n#import \"DoraemonHierarchySwitchCell.h\"\n#import \"DoraemonHierarchyHeaderView.h\"\n#import \"DoraemonHierarchyCellModel.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHierarchyTableViewController ()\n\n@property (nonatomic, strong) UITableView *tableView;\n\n@property (nonatomic, assign) UITableViewStyle style;\n\n@property (nonatomic, strong) NSMutableArray <DoraemonHierarchyCategoryModel *>*dataArray;\n\n@end\n\n@implementation DoraemonHierarchyTableViewController\n\n#pragma mark - Life cycle\n- (instancetype)init\n{\n    return [self initWithStyle:UITableViewStyleGrouped];\n}\n\n- (instancetype)initWithStyle:(UITableViewStyle)style {\n    if (self = [super init]) {\n        _style = style;\n    }\n    return self;\n}\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    [self.view addSubview:self.tableView];\n}\n\n#pragma mark - Over write\n- (void)viewDidLayoutSubviews {\n    [super viewDidLayoutSubviews];\n    self.tableView.frame = CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, self.view.doraemon_height - self.bigTitleView.doraemon_bottom);\n}\n\n#pragma mark - UITableViewDelegate, UITableViewDataSource\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {\n    return self.dataArray.count;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return self.dataArray[section].items.count;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonHierarchyCellModel *model = self.dataArray[indexPath.section].items[indexPath.row];\n    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:model.cellClass];\n    [cell setValue:model forKey:@\"model\"];\n    return cell;\n}\n\n- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {\n    DoraemonHierarchyCategoryModel *model = self.dataArray[section];\n    if (!model.title) {\n        return nil;\n    }\n    DoraemonHierarchyHeaderView *view = [[DoraemonHierarchyHeaderView alloc] initWithFrame:CGRectMake(0, 0, DoraemonScreenWidth, 40)];\n    view.titleLabel.text = model.title;\n    return view;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {\n    DoraemonHierarchyCategoryModel *model = self.dataArray[section];\n    if (!model.title) {\n        return CGFLOAT_MIN;\n    }\n    return 40;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {\n    return CGFLOAT_MIN;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonHierarchyCellModel *model = self.dataArray[indexPath.section].items[indexPath.row];\n    if (model.block) {\n        model.block();\n    }\n}\n\n#pragma mark - Getters and setters\n- (UITableView *)tableView {\n    if (!_tableView) {\n        _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];\n        _tableView.delegate = self;\n        _tableView.dataSource = self;\n        _tableView.bounces = NO;\n        _tableView.separatorInset = UIEdgeInsetsMake(0, 10, 0, 0);\n        _tableView.estimatedRowHeight = UITableViewAutomaticDimension;\n        _tableView.estimatedSectionFooterHeight = 0;\n        _tableView.estimatedSectionHeaderHeight = 0;\n        [_tableView registerClass:[DoraemonHierarchySwitchCell class] forCellReuseIdentifier:NSStringFromClass([DoraemonHierarchySwitchCell class])];\n        [_tableView registerClass:[DoraemonHierarchyDetailTitleCell class] forCellReuseIdentifier:NSStringFromClass([DoraemonHierarchyDetailTitleCell class])];\n        [_tableView registerClass:[DoraemonHierarchySelectorCell class] forCellReuseIdentifier:NSStringFromClass([DoraemonHierarchySelectorCell class])];\n        if (@available(iOS 11.0, *)) {\n            _tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAutomatic;\n        }\n    }\n    return _tableView;\n}\n\n- (NSMutableArray<DoraemonHierarchyCategoryModel *> *)dataArray {\n    if (!_dataArray) {\n        _dataArray = [[NSMutableArray alloc] init];\n    }\n    return _dataArray;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/DoraemonHierarchyViewController.h",
    "content": "//\n//  DoraemonHierarchyViewController.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHierarchyViewController : UIViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/DoraemonHierarchyViewController.m",
    "content": "//\n//  DoraemonHierarchyViewController.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyViewController.h\"\n#import \"DoraemonHierarchyDetailViewController.h\"\n#import \"UIViewController+DoraemonHierarchy.h\"\n#import \"DKHierarchyPickerView.h\"\n#import \"NSObject+DoraemonHierarchy.h\"\n#import \"DKHierarchyInfoView.h\"\n#import \"DoraemonHierarchyHelper.h\"\n#import \"DoraemonHierarchyWindow.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHierarchyViewController ()<DKHierarchyViewDelegate, DKHierarchyInfoViewDelegate>\n\n@property (nonatomic, strong) UIView *borderView;\n\n@property (nonatomic, strong) DKHierarchyPickerView *pickerView;\n\n@property (nonatomic, strong) DKHierarchyInfoView *infoView;\n\n@property (nonatomic, strong) NSMutableSet *observeViews;\n\n@property (nonatomic, strong) NSMutableDictionary *borderViews;\n\n@end\n\n@implementation DoraemonHierarchyViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.1];\n    self.observeViews = [NSMutableSet set];\n    self.borderViews = [[NSMutableDictionary alloc] init];\n    \n    CGFloat height = 100;\n    self.infoView = [[DKHierarchyInfoView alloc] initWithFrame:CGRectMake(10, DoraemonScreenHeight - 10 * 2 - height, DoraemonScreenWidth - 10 * 2, height)];\n    self.infoView.delegate = self;\n    [self.view addSubview:self.infoView];\n    \n    [self.view addSubview:self.borderView];\n    \n    self.pickerView = [[DKHierarchyPickerView alloc] initWithFrame:CGRectMake((self.view.doraemon_width - 60) / 2.0, (self.view.doraemon_height - 60) / 2.0, 60, 60)];\n    self.pickerView.delegate = self;\n    [self.view addSubview:self.pickerView];\n}\n\n- (void)dealloc {\n    for (UIView *view in self.observeViews) {\n        [self stopObserveView:view];\n    }\n    [self.observeViews removeAllObjects];\n}\n\n#pragma mark - Primary\n- (void)beginObserveView:(UIView *)view borderWidth:(CGFloat)borderWidth {\n    if ([self.observeViews containsObject:view]) {\n        return;\n    }\n    \n    UIView *borderView = [[UIView alloc] init];\n    borderView.backgroundColor = [UIColor clearColor];\n    [self.view addSubview:borderView];\n    [self.view sendSubviewToBack:borderView];\n    borderView.layer.borderColor = view.doraemon_hashColor.CGColor;\n    borderView.layer.borderWidth = borderWidth;\n    borderView.frame = [self frameInLocalForView:view];\n    [self.borderViews setObject:borderView forKey:@(view.hash)];\n\n    [view addObserver:self forKeyPath:@\"frame\" options:0 context:NULL];\n}\n\n- (void)stopObserveView:(UIView *)view {\n    if (![self.observeViews containsObject:view]) {\n        return;\n    }\n    \n    UIView *borderView = self.borderViews[@(view.hash)];\n    [borderView removeFromSuperview];\n    [view removeObserver:self forKeyPath:@\"frame\"];\n}\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *, id> *)change context:(void *)context {\n    if ([object isKindOfClass:[UIView class]]) {\n        UIView *view = (UIView *)object;\n        [self updateOverlayIfNeeded:view];\n    }\n}\n\n- (void)updateOverlayIfNeeded:(UIView *)view {\n    UIView *borderView = self.borderViews[@(view.hash)];\n    if (borderView) {\n        borderView.frame = [self frameInLocalForView:view];\n    }\n}\n\n- (CGRect)frameInLocalForView:(UIView *)view {\n    UIWindow *window = [DoraemonUtil getKeyWindow];\n    CGRect rect = [view convertRect:view.bounds toView:window];\n    rect = [self.view convertRect:rect fromView:window];\n    return rect;\n}\n\n- (UIView *)findSelectedViewInViews:(NSArray *)selectedViews {\n    if ([DoraemonHierarchyHelper shared].isHierarchyIgnorePrivateClass) {\n        NSMutableArray *views = [[NSMutableArray alloc] init];\n        for (UIView *view in selectedViews) {\n            if (![NSStringFromClass(view.class) hasPrefix:@\"_\"]) {\n                [views addObject:view];\n            }\n        }\n        return [views lastObject];\n    } else {\n        return [selectedViews lastObject];\n    }\n}\n\n- (NSArray <UIView *>*)findParentViewsBySelectedView:(UIView *)selectedView {\n    NSMutableArray *views = [[NSMutableArray alloc] init];\n    UIView *view = [selectedView superview];\n    while (view) {\n        if ([DoraemonHierarchyHelper shared].isHierarchyIgnorePrivateClass) {\n            if (![NSStringFromClass(view.class) hasPrefix:@\"_\"]) {\n                [views addObject:view];\n            }\n        } else {\n            [views addObject:view];\n        }\n        view = view.superview;\n    }\n    return [views copy];\n}\n\n- (NSArray <UIView *>*)findSubviewsBySelectedView:(UIView *)selectedView {\n    NSMutableArray *views = [[NSMutableArray alloc] init];\n    for (UIView *view in selectedView.subviews) {\n        if ([DoraemonHierarchyHelper shared].isHierarchyIgnorePrivateClass) {\n            if (![NSStringFromClass(view.class) hasPrefix:@\"_\"]) {\n                [views addObject:view];\n            }\n        } else {\n            [views addObject:view];\n        }\n    }\n    return [views copy];\n}\n\n#pragma mark - LLHierarchyPickerViewDelegate\n- (void)hierarchyView:(DKHierarchyPickerView *)view didMoveTo:(NSArray <UIView *>*)selectedViews {\n    \n    @synchronized (self) {\n        for (UIView *view in self.observeViews) {\n            [self stopObserveView:view];\n        }\n        [self.observeViews removeAllObjects];\n        \n        for (NSInteger i = selectedViews.count - 1; i >= 0; i--) {\n            UIView *view = selectedViews[i];\n            CGFloat borderWidth = 1;\n            if (i == selectedViews.count - 1) {\n                borderWidth = 2;\n            }\n            [self beginObserveView:view borderWidth:borderWidth];\n        }\n        [self.observeViews addObjectsFromArray:selectedViews];\n    }\n\n    [self.infoView updateSelectedView:[self findSelectedViewInViews:selectedViews]];\n}\n\n#pragma mark - DoraemonHierarchyInfoViewDelegate\n- (void)hierarchyInfoView:(DKHierarchyInfoView *)view didSelectAt:(DKHierarchyInfoViewAction)action {\n    UIView *selectView = self.infoView.selectedView;\n    if (selectView == nil) {\n        return;\n    }\n    switch (action) {\n        case DKHierarchyInfoViewActionShowMoreInfo:{\n            [self showHierarchyInfo:selectView];\n        }\n            break;\n        case DKHierarchyInfoViewActionShowParent: {\n            [self showParentSheet:selectView];\n        }\n            break;\n        case DKHierarchyInfoViewActionShowSubview: {\n            [self showSubviewSheet:selectView];\n        }\n            break;\n    }\n}\n\n- (void)hierarchyInfoViewDidSelectCloseButton:(DKHierarchyInfoView *)view {\n    [[DoraemonHierarchyHelper shared].window hide];\n    [DoraemonHierarchyHelper shared].window = nil;\n}\n\n- (void)showHierarchyInfo:(UIView *)selectView {\n    DoraemonHierarchyDetailViewController *vc = [[DoraemonHierarchyDetailViewController alloc] init];\n    vc.modalPresentationStyle = UIModalPresentationFullScreen;\n    vc.selectView = selectView;\n    [self presentViewController:vc animated:YES completion:nil];\n}\n\n- (void)showParentSheet:(UIView *)selectView {\n    NSMutableArray *actions = [[NSMutableArray alloc] init];\n    __block NSArray *parentViews = [self findParentViewsBySelectedView:selectView];\n    for (UIView *view in parentViews) {\n        [actions addObject:NSStringFromClass(view.class)];\n    }\n    __weak typeof(self) weakSelf = self;\n    [self doraemon_showActionSheetWithTitle:@\"Parent Views\" actions:actions currentAction:nil completion:^(NSInteger index) {\n        [weakSelf setNewSelectView:parentViews[index]];\n    }];\n}\n\n- (void)showSubviewSheet:(UIView *)selectView {\n    NSMutableArray *actions = [[NSMutableArray alloc] init];\n    __block NSArray *subviews = [self findSubviewsBySelectedView:selectView];\n    for (UIView *view in subviews) {\n        [actions addObject:NSStringFromClass(view.class)];\n    }\n    __weak typeof(self) weakSelf = self;\n    [self doraemon_showActionSheetWithTitle:@\"Subviews\" actions:actions currentAction:nil completion:^(NSInteger index) {\n        [weakSelf setNewSelectView:subviews[index]];\n    }];\n}\n\n- (void)setNewSelectView:(UIView *)view {\n    [self hierarchyView:self.pickerView didMoveTo:@[view]];\n}\n\n#pragma mark - Getters and setters\n- (UIView *)borderView {\n    if (!_borderView) {\n        _borderView = [[UIView alloc] init];\n        _borderView.backgroundColor = [UIColor clearColor];\n        _borderView.layer.borderWidth = 2;\n    }\n    return _borderView;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/DoraemonHierarchyWindow.h",
    "content": "//\n//  DoraemonHierarchyWindow.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHierarchyWindow : UIWindow\n\n- (void)show;\n- (void)hide;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/DoraemonHierarchyWindow.m",
    "content": "//\n//  DoraemonHierarchyWindow.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyWindow.h\"\n#import \"DoraemonHierarchyViewController.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonHierarchyWindow ()\n\n@end\n\n@implementation DoraemonHierarchyWindow\n\n- (instancetype)init {\n    return [self initWithFrame:[UIScreen mainScreen].bounds];\n}\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    if (self = [super initWithFrame:frame]) {\n        #if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n            if (@available(iOS 13.0, *)) {\n                for (UIWindowScene* windowScene in [UIApplication sharedApplication].connectedScenes){\n                    if (windowScene.activationState == UISceneActivationStateForegroundActive){\n                        self.windowScene = windowScene;\n                        break;\n                    }\n                }\n            }\n        #endif\n        self.windowLevel = UIWindowLevelAlert - 1;\n        if (!self.rootViewController) {\n            self.rootViewController = [[DoraemonHierarchyViewController alloc] init];\n        }\n    }\n    return self;\n}\n\n- (void)show {\n    self.hidden = NO;\n}\n\n- (void)hide {\n    self.hidden = YES;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/Model/DoraemonHierarchyCategoryModel.h",
    "content": "//\n//  DoraemonHierarchyCategoryModel.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import <Foundation/Foundation.h>\n\n@class DoraemonHierarchyCellModel;\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHierarchyCategoryModel : NSObject\n\n@property (nonatomic, strong, readonly, nullable) NSString *title;\n\n@property (nonatomic, strong, readonly) NSArray <DoraemonHierarchyCellModel *>*items;\n\n- (instancetype)initWithTitle:(NSString *_Nullable)title items:(NSArray <DoraemonHierarchyCellModel *>*)items;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/Model/DoraemonHierarchyCategoryModel.m",
    "content": "//\n//  DoraemonHierarchyCategoryModel.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyCategoryModel.h\"\n\n@implementation DoraemonHierarchyCategoryModel\n\n- (instancetype)initWithTitle:(NSString *)title items:(NSArray <DoraemonHierarchyCellModel *>*)items {\n    if (self = [super init]) {\n        _title = title;\n        _items = [items copy];\n    }\n    return self;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/Model/DoraemonHierarchyCellModel.h",
    "content": "//\n//  DoraemonHierarchyCellModel.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHierarchyCellModel : NSObject\n\n@property (nonatomic, copy, nullable, readonly) NSString *title;\n\n@property (nonatomic, copy, readonly) NSString *cellClass;\n\n// Style1\n@property (nonatomic, assign) BOOL flag;\n\n// Style2 / Style3\n@property (nonatomic, copy, nullable, readonly) NSString *detailTitle;\n\n// Style4\n@property (nonatomic, assign) CGFloat value;\n\n@property (nonatomic, assign, readonly) CGFloat minValue;\n\n@property (nonatomic, assign, readonly) CGFloat maxValue;\n\n// Block\n@property (nonatomic, copy, nullable) void (^block)(void);\n\n@property (nonatomic, copy, nullable) void (^changePropertyBlock)(__nullable id obj);\n\n// Separator\n@property (nonatomic, assign) UIEdgeInsets separatorInsets;\n\n// DoraemonHierarchySwitchCell\n- (instancetype)initWithTitle:(NSString *_Nullable)title flag:(BOOL)flag;\n- (instancetype)initWithTitle:(NSString *_Nullable)title detailTitle:(NSString *_Nullable)detailTitle flag:(BOOL)flag;\n\n// DoraemonHierarchyDetailTitleCell\n- (instancetype)initWithTitle:(NSString *_Nullable)title detailTitle:(NSString *_Nullable)detailTitle;\n\n- (DoraemonHierarchyCellModel *)normalInsets;\n\n- (DoraemonHierarchyCellModel *)noneInsets;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/Model/DoraemonHierarchyCellModel.m",
    "content": "//\n//  DoraemonHierarchyCellModel.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyCellModel.h\"\n#import \"DoraemonHierarchyDetailTitleCell.h\"\n#import \"DoraemonHierarchySelectorCell.h\"\n#import \"DoraemonHierarchySwitchCell.h\"\n#import \"DoraemonDefine.h\"\n\n@implementation DoraemonHierarchyCellModel\n\n- (instancetype)initWithTitle:(NSString *)title flag:(BOOL)flag {\n    return [self initWithTitle:title detailTitle:nil flag:flag];\n}\n\n- (instancetype)initWithTitle:(NSString *_Nullable)title detailTitle:(NSString *_Nullable)detailTitle flag:(BOOL)flag {\n    if (self = [super init]) {\n        _title = [title copy];\n        _detailTitle = [detailTitle copy];\n        _flag = flag;\n        _cellClass = NSStringFromClass(DoraemonHierarchySwitchCell.class);\n        _separatorInsets = UIEdgeInsetsMake(0, 10, 0, 0);\n    }\n    return self;\n}\n\n- (instancetype)initWithTitle:(NSString *)title detailTitle:(NSString *)detailTitle {\n    if (self = [super init]) {\n        _title = [title copy];\n        _detailTitle = [detailTitle copy];\n        _cellClass = NSStringFromClass(DoraemonHierarchyDetailTitleCell.class);\n        _separatorInsets = UIEdgeInsetsMake(0, 10, 0, 0);\n    }\n    return self;\n}\n\n- (DoraemonHierarchyCellModel *)normalInsets {\n    self.separatorInsets = UIEdgeInsetsMake(0, 10, 0, 0);\n    return self;\n}\n\n- (DoraemonHierarchyCellModel *)noneInsets {\n    self.separatorInsets = UIEdgeInsetsMake(0, DoraemonScreenWidth, 0, 0);\n    return self;\n}\n\n- (void)setBlock:(void (^)(void))block {\n    if (_block != block) {\n        _block = [block copy];\n        _cellClass = NSStringFromClass(block ? DoraemonHierarchySelectorCell.class : DoraemonHierarchyDetailTitleCell.class);\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/View/DKHierarchyInfoView.h",
    "content": "#import <DoraemonKit/DKMoveView.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\ntypedef NS_ENUM(NSUInteger, DKHierarchyInfoViewAction) {\n    DKHierarchyInfoViewActionShowParent,\n    DKHierarchyInfoViewActionShowSubview,\n    DKHierarchyInfoViewActionShowMoreInfo\n};\n\n@class DKHierarchyInfoView;\n\n@protocol DKHierarchyInfoViewDelegate <NSObject>\n\n- (void)hierarchyInfoView:(DKHierarchyInfoView *)view didSelectAt:(DKHierarchyInfoViewAction)action;\n\n- (void)hierarchyInfoViewDidSelectCloseButton:(DKHierarchyInfoView *)view;\n\n@end\n\n@interface DKHierarchyInfoView : DKMoveView\n\n@property (nonatomic, weak, nullable) id <DKHierarchyInfoViewDelegate> delegate;\n\n@property (nonatomic, strong, nullable, readonly) UIView *selectedView;\n\n@property (nonatomic, strong, readonly) UIButton *closeButton;\n\n- (void)updateSelectedView:(UIView *)view;\n\n- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;\n\n- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/View/DKHierarchyInfoView.m",
    "content": "#import \"DKHierarchyInfoView.h\"\n#import \"UIColor+DoraemonHierarchy.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n#import \"NSObject+DoraemonHierarchy.h\"\n#import \"DoraemonHierarchyFormatterTool.h\"\n\n@interface DKHierarchyInfoView ()\n\n@property (nonatomic, strong, nullable) UIView *selectedView;\n\n@property (nonatomic, strong) UIButton *closeButton;\n\n@property (nonatomic, strong) UILabel *contentLabel;\n\n@property (nonatomic, strong) UILabel *frameLabel;\n\n@property (nonatomic, strong) UILabel *backgroundColorLabel;\n\n@property (nonatomic, strong) UILabel *textColorLabel;\n\n@property (nonatomic, strong) UILabel *fontLabel;\n\n@property (nonatomic, strong) UILabel *tagLabel;\n\n@property (nonatomic, strong) UIView *actionContentView;\n\n@property (nonatomic, strong) UIButton *moreButton;\n\n@property (nonatomic, strong) UIButton *parentViewsButton;\n\n@property (nonatomic, strong) UIButton *subviewsButton;\n\n@property (nonatomic, assign) CGFloat actionContentViewHeight;\n\n- (void)hierarchyInfoViewInit;\n\n@end\n\n@implementation DKHierarchyInfoView\n\n- (void)updateSelectedView:(UIView *)selectedView {\n\n    UIView *view = selectedView;\n\n    if (!view) {\n        return;\n    }\n\n    if (self.selectedView == view) {\n        return;\n    }\n\n    self.moreButton.enabled = YES;\n    self.parentViewsButton.enabled = view.superview != nil;\n    self.subviewsButton.enabled = view.subviews.count;\n\n    self.selectedView = view;\n\n    NSDictionary *boldAttri = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:17]};\n    NSDictionary *attri = @{NSFontAttributeName: [UIFont systemFontOfSize:14]};\n\n    NSMutableAttributedString *name = [[NSMutableAttributedString alloc] initWithString:@\"Name: \" attributes:boldAttri];\n    [name appendAttributedString:[[NSAttributedString alloc] initWithString:NSStringFromClass(view.class) attributes:attri]];\n\n    self.contentLabel.attributedText = name;\n\n    NSMutableAttributedString *frame = [[NSMutableAttributedString alloc] initWithString:@\"Frame: \" attributes:boldAttri];\n    [frame appendAttributedString:[[NSAttributedString alloc] initWithString:[DoraemonHierarchyFormatterTool stringFromFrame:view.frame] attributes:attri]];\n\n    self.frameLabel.attributedText = frame;\n\n    if (view.backgroundColor) {\n        NSMutableAttributedString *color = [[NSMutableAttributedString alloc] initWithString:@\"Background: \" attributes:boldAttri];\n        [color appendAttributedString:[[NSAttributedString alloc] initWithString:[view.backgroundColor doraemon_description] attributes:attri]];\n        self.backgroundColorLabel.attributedText = color;\n    } else {\n        self.backgroundColorLabel.attributedText = nil;\n    }\n\n    if ([view isKindOfClass:[UILabel class]]) {\n        UILabel *label = (UILabel *) view;\n        NSMutableAttributedString *textColor = [[NSMutableAttributedString alloc] initWithString:@\"Text Color: \" attributes:boldAttri];\n        [textColor appendAttributedString:[[NSAttributedString alloc] initWithString:[label.textColor doraemon_description] attributes:attri]];\n        self.textColorLabel.attributedText = textColor;\n\n        NSMutableAttributedString *font = [[NSMutableAttributedString alloc] initWithString:@\"Font: \" attributes:boldAttri];\n        [font appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@\"%0.2f\", label.font.pointSize] attributes:attri]];\n        self.fontLabel.attributedText = font;\n    } else {\n        self.textColorLabel.attributedText = nil;\n        self.fontLabel.attributedText = nil;\n    }\n\n    if (view.tag != 0) {\n        NSMutableAttributedString *tag = [[NSMutableAttributedString alloc] initWithString:@\"Tag: \" attributes:boldAttri];\n        [tag appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@\"%ld\", (long) view.tag] attributes:attri]];\n        self.tagLabel.attributedText = tag;\n    } else {\n        self.tagLabel.attributedText = nil;\n    }\n\n    [self.contentLabel sizeToFit];\n    [self.frameLabel sizeToFit];\n    [self.backgroundColorLabel sizeToFit];\n    [self.textColorLabel sizeToFit];\n    [self.fontLabel sizeToFit];\n    [self.tagLabel sizeToFit];\n\n    [self updateHeightIfNeeded];\n}\n\n- (void)layoutSubviews {\n    [super layoutSubviews];\n\n    self.closeButton.frame = CGRectMake(self.doraemon_width - 10 - 30, 10, 30, 30);\n\n    self.actionContentView.frame = CGRectMake(0, self.doraemon_height - self.actionContentViewHeight - 10, self.doraemon_width, self.actionContentViewHeight);\n\n    self.parentViewsButton.frame = CGRectMake(10, 0, self.actionContentView.doraemon_width / 2.0 - 10 * 1.5, (self.actionContentView.doraemon_height - 10) / 2.0);\n\n    self.subviewsButton.frame = CGRectMake(self.actionContentView.doraemon_width / 2.0 + 10 * 0.5, self.parentViewsButton.doraemon_top, self.parentViewsButton.doraemon_width, self.parentViewsButton.doraemon_height);\n\n    self.moreButton.frame = CGRectMake(10, self.parentViewsButton.doraemon_bottom + 10, self.actionContentView.doraemon_width - 10 * 2, self.parentViewsButton.doraemon_height);\n\n    self.contentLabel.frame = CGRectMake(10, 10, self.closeButton.doraemon_x - 10 - 10, self.contentLabel.doraemon_height);\n\n    self.frameLabel.frame = CGRectMake(self.contentLabel.doraemon_x, self.contentLabel.doraemon_bottom, self.contentLabel.doraemon_width, self.frameLabel.doraemon_height);\n\n    self.backgroundColorLabel.frame = CGRectMake(self.contentLabel.doraemon_x, self.frameLabel.doraemon_bottom, self.contentLabel.doraemon_width, self.backgroundColorLabel.doraemon_height);\n\n    self.textColorLabel.frame = CGRectMake(self.contentLabel.doraemon_x, self.backgroundColorLabel.doraemon_bottom, self.contentLabel.doraemon_width, self.textColorLabel.doraemon_height);\n\n    self.fontLabel.frame = CGRectMake(self.contentLabel.doraemon_x, self.textColorLabel.doraemon_bottom, self.contentLabel.doraemon_width, self.fontLabel.doraemon_height);\n\n    self.tagLabel.frame = CGRectMake(self.contentLabel.doraemon_x, self.fontLabel.doraemon_bottom, self.contentLabel.doraemon_width, self.tagLabel.doraemon_height);\n}\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    [self hierarchyInfoViewInit];\n\n    return self;\n}\n\n- (instancetype)initWithCoder:(NSCoder *)coder {\n    if (self = [super initWithCoder:coder]) {\n        [self hierarchyInfoViewInit];\n    }\n\n    return self;\n}\n\n- (void)hierarchyInfoViewInit {\n    self.layer.borderColor = [UIColor doraemon_black_1].CGColor;\n    self.layer.borderWidth = 2;\n    self.layer.cornerRadius = 5;\n    self.layer.masksToBounds = YES;\n    self.backgroundColor = [UIColor whiteColor];\n\n    self.actionContentViewHeight = 80;\n\n    [self addSubview:self.closeButton];\n    [self addSubview:self.contentLabel];\n    [self addSubview:self.frameLabel];\n    [self addSubview:self.backgroundColorLabel];\n    [self addSubview:self.textColorLabel];\n    [self addSubview:self.fontLabel];\n    [self addSubview:self.tagLabel];\n    [self addSubview:self.actionContentView];\n    [self.actionContentView addSubview:self.parentViewsButton];\n    [self.actionContentView addSubview:self.subviewsButton];\n    [self.actionContentView addSubview:self.moreButton];\n\n    [self updateHeightIfNeeded];\n}\n\n#pragma mark - Event responses\n\n- (void)buttonClicked:(UIButton *)sender {\n    [self.delegate hierarchyInfoView:self didSelectAt:sender.tag];\n}\n\n- (void)closeButtonClicked:(UIButton *)sender {\n    [self.delegate hierarchyInfoViewDidSelectCloseButton:self];\n}\n\n- (void)frameLabelTapGestureRecognizer:(UITapGestureRecognizer *)sender {\n    [self.selectedView doraemon_showFrameAlertAndAutomicSetWithKeyPath:@\"frame\"];\n}\n\n- (void)backgroundColorLabelTapGestureRecognizer:(UITapGestureRecognizer *)sender {\n    [self.selectedView doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"backgroundColor\"];\n}\n\n- (void)textColorLabelTapGestureRecognizer:(UITapGestureRecognizer *)sender {\n    [self.selectedView doraemon_showColorAlertAndAutomicSetWithKeyPath:@\"textColor\"];\n}\n\n- (void)fontLabelTapGestureRecognizer:(UITapGestureRecognizer *)sender {\n    [self.selectedView doraemon_showFontAlertAndAutomicSetWithKeyPath:@\"font\"];\n}\n\n- (void)tagLabelTapGestureRecognizer:(UITapGestureRecognizer *)sender {\n    [self.selectedView doraemon_showIntAlertAndAutomicSetWithKeyPath:@\"tag\"];\n}\n\n#pragma mark - Primary\n\n- (void)updateHeightIfNeeded {\n    CGFloat contentHeight = self.contentLabel.doraemon_height + self.frameLabel.doraemon_height + self.backgroundColorLabel.doraemon_height + self.textColorLabel.doraemon_height + self.fontLabel.doraemon_height + self.tagLabel.doraemon_height;\n    CGFloat height = 10 + MAX(contentHeight, 10 + 30/*self.closeButton.doraemon_height*/) + 10 + self.actionContentViewHeight + 10;\n    if (height != self.doraemon_height) {\n        self.doraemon_height = height;\n        if (!self.isMoved) {\n            if (self.doraemon_bottom != DoraemonScreenHeight - 10 * 2) {\n                self.doraemon_bottom = DoraemonScreenHeight - 10 * 2;\n            }\n        }\n    }\n}\n\n#pragma mark - Getters and setters\n\n- (UIButton *)closeButton {\n    if (!_closeButton) {\n        _closeButton = [UIButton buttonWithType:UIButtonTypeCustom];\n        [_closeButton addTarget:self action:@selector(closeButtonClicked:) forControlEvents:UIControlEventTouchUpInside];\n        [_closeButton setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_close\"] forState:UIControlStateNormal];\n    }\n    return _closeButton;\n}\n\n- (UILabel *)contentLabel {\n    if (!_contentLabel) {\n        _contentLabel = [[UILabel alloc] init];\n        _contentLabel.font = [UIFont systemFontOfSize:14];\n        _contentLabel.textColor = [UIColor doraemon_black_1];\n        _contentLabel.numberOfLines = 0;\n        _contentLabel.lineBreakMode = NSLineBreakByCharWrapping;\n    }\n    return _contentLabel;\n}\n\n- (UILabel *)frameLabel {\n    if (!_frameLabel) {\n        _frameLabel = [[UILabel alloc] init];\n        _frameLabel.font = [UIFont systemFontOfSize:14];\n        _frameLabel.textColor = [UIColor doraemon_black_1];\n        _frameLabel.numberOfLines = 0;\n        _frameLabel.lineBreakMode = NSLineBreakByCharWrapping;\n        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(frameLabelTapGestureRecognizer:)];\n        _frameLabel.userInteractionEnabled = YES;\n        [_frameLabel addGestureRecognizer:tap];\n    }\n    return _frameLabel;\n}\n\n- (UILabel *)backgroundColorLabel {\n    if (!_backgroundColorLabel) {\n        _backgroundColorLabel = [[UILabel alloc] init];\n        _backgroundColorLabel.font = [UIFont systemFontOfSize:14];\n        _backgroundColorLabel.textColor = [UIColor doraemon_black_1];\n        _backgroundColorLabel.numberOfLines = 0;\n        _backgroundColorLabel.lineBreakMode = NSLineBreakByCharWrapping;\n        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(backgroundColorLabelTapGestureRecognizer:)];\n        _backgroundColorLabel.userInteractionEnabled = YES;\n        [_backgroundColorLabel addGestureRecognizer:tap];\n    }\n    return _backgroundColorLabel;\n}\n\n- (UILabel *)textColorLabel {\n    if (!_textColorLabel) {\n        _textColorLabel = [[UILabel alloc] init];\n        _textColorLabel.font = [UIFont systemFontOfSize:14];\n        _textColorLabel.textColor = [UIColor doraemon_black_1];\n        _textColorLabel.numberOfLines = 0;\n        _textColorLabel.lineBreakMode = NSLineBreakByCharWrapping;\n        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(textColorLabelTapGestureRecognizer:)];\n        _textColorLabel.userInteractionEnabled = YES;\n        [_textColorLabel addGestureRecognizer:tap];\n    }\n    return _textColorLabel;\n}\n\n- (UILabel *)fontLabel {\n    if (!_fontLabel) {\n        _fontLabel = [[UILabel alloc] init];\n        _fontLabel.font = [UIFont systemFontOfSize:14];\n        _fontLabel.textColor = [UIColor doraemon_black_1];\n        _fontLabel.numberOfLines = 0;\n        _fontLabel.lineBreakMode = NSLineBreakByCharWrapping;\n        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(fontLabelTapGestureRecognizer:)];\n        _fontLabel.userInteractionEnabled = YES;\n        [_fontLabel addGestureRecognizer:tap];\n    }\n    return _fontLabel;\n}\n\n- (UILabel *)tagLabel {\n    if (!_tagLabel) {\n        _tagLabel = [[UILabel alloc] init];\n        _tagLabel.font = [UIFont systemFontOfSize:14];\n        _tagLabel.textColor = [UIColor doraemon_black_1];\n        _tagLabel.numberOfLines = 0;\n        _tagLabel.lineBreakMode = NSLineBreakByCharWrapping;\n        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tagLabelTapGestureRecognizer:)];\n        _tagLabel.userInteractionEnabled = YES;\n        [_tagLabel addGestureRecognizer:tap];\n    }\n    return _tagLabel;\n}\n\n- (UIView *)actionContentView {\n    if (!_actionContentView) {\n        _actionContentView = [[UIView alloc] init];\n    }\n    return _actionContentView;\n}\n\n- (UIButton *)parentViewsButton {\n    if (!_parentViewsButton) {\n        _parentViewsButton = [UIButton buttonWithType:UIButtonTypeCustom];\n        [_parentViewsButton addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];\n        [_parentViewsButton setTitle:@\"Parent Views\" forState:UIControlStateNormal];\n        [_parentViewsButton setTitleColor:[UIColor doraemon_black_1] forState:UIControlStateNormal];\n        _parentViewsButton.titleLabel.font = [UIFont systemFontOfSize:14];\n        _parentViewsButton.backgroundColor = [UIColor whiteColor];\n        _parentViewsButton.layer.borderColor = [UIColor doraemon_black_1].CGColor;\n        _parentViewsButton.layer.borderWidth = 1;\n        _parentViewsButton.layer.cornerRadius = 5;\n        _parentViewsButton.layer.masksToBounds = YES;\n        _parentViewsButton.tintColor = [UIColor doraemon_black_1];\n        _parentViewsButton.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 10);\n        [_parentViewsButton setImage:[[UIImage doraemon_xcassetImageNamed:@\"doraemon_hierarchy_parent\"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];\n        _parentViewsButton.tag = DKHierarchyInfoViewActionShowParent;\n        _parentViewsButton.enabled = NO;\n    }\n    return _parentViewsButton;\n}\n\n- (UIButton *)subviewsButton {\n    if (!_subviewsButton) {\n        _subviewsButton = [UIButton buttonWithType:UIButtonTypeCustom];\n        [_subviewsButton addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];\n        [_subviewsButton setTitle:@\"Subviews\" forState:UIControlStateNormal];\n        [_subviewsButton setTitleColor:[UIColor doraemon_black_1] forState:UIControlStateNormal];\n        _subviewsButton.titleLabel.font = [UIFont systemFontOfSize:14];\n        _subviewsButton.backgroundColor = [UIColor whiteColor];\n        _subviewsButton.layer.borderColor = [UIColor doraemon_black_1].CGColor;\n        _subviewsButton.layer.borderWidth = 1;\n        _subviewsButton.layer.cornerRadius = 5;\n        _subviewsButton.layer.masksToBounds = YES;\n        _subviewsButton.tintColor = [UIColor doraemon_black_1];\n        _subviewsButton.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 10);\n        [_subviewsButton setImage:[[UIImage doraemon_xcassetImageNamed:@\"doraemon_hierarchy_subview\"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];\n        _subviewsButton.tag = DKHierarchyInfoViewActionShowSubview;\n        _subviewsButton.enabled = NO;\n    }\n    return _subviewsButton;\n}\n\n- (UIButton *)moreButton {\n    if (!_moreButton) {\n        _moreButton = [UIButton buttonWithType:UIButtonTypeCustom];\n        [_moreButton addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];\n        [_moreButton setTitle:@\"More Info\" forState:UIControlStateNormal];\n        [_moreButton setTitleColor:[UIColor doraemon_black_1] forState:UIControlStateNormal];\n        _moreButton.titleLabel.font = [UIFont systemFontOfSize:14];\n        _moreButton.backgroundColor = [UIColor whiteColor];\n        _moreButton.layer.borderColor = [UIColor doraemon_black_1].CGColor;\n        _moreButton.layer.borderWidth = 1;\n        _moreButton.layer.cornerRadius = 5;\n        _moreButton.layer.masksToBounds = YES;\n        _moreButton.tintColor = [UIColor doraemon_black_1];\n        _moreButton.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 10);\n        [_moreButton setImage:[[UIImage doraemon_xcassetImageNamed:@\"doraemon_hierarchy_info\"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];\n        _moreButton.tag = DKHierarchyInfoViewActionShowMoreInfo;\n        _moreButton.enabled = NO;\n    }\n    return _moreButton;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/View/DKHierarchyPickerView.h",
    "content": "#import <DoraemonKit/DKPickerView.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@class DKHierarchyPickerView;\n\n@protocol DKHierarchyViewDelegate <NSObject>\n\n- (void)hierarchyView:(DKHierarchyPickerView *)view didMoveTo:(nullable NSArray <UIView *> *)selectedViews;\n\n@end\n\n@interface DKHierarchyPickerView : DKPickerView\n\n@property (nonatomic, weak, nullable) id <DKHierarchyViewDelegate> delegate;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/View/DKHierarchyPickerView.m",
    "content": "#import <DoraemonKit/DKHierarchyPickerView.h>\n#import <DoraemonKit/DoraemonHierarchyHelper.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKHierarchyPickerView ()\n\n- (nullable NSArray<UIView *> *)viewForSelectionAtPoint:(CGPoint)tapPointInWindow;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n@implementation DKHierarchyPickerView\n\n#pragma mark - Over write\n\n- (void)viewDidUpdateOffset:(UIPanGestureRecognizer *)sender offset:(CGPoint)offsetPoint {\n    NSArray <UIView *> *views = [self viewForSelectionAtPoint:self.center];\n    [self.delegate hierarchyView:self didMoveTo:views];\n}\n\n#pragma mark - Primary\n\n- (nullable NSArray<UIView *> *)viewForSelectionAtPoint:(CGPoint)tapPointInWindow {\n    // Select in the window that would handle the touch, but don't just use the result of hitTest:withEvent: so we can still select views with interaction disabled.\n    // Default to the the application's key window if none of the windows want the touch.\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n    UIWindow *windowForSelection = [[UIApplication sharedApplication] keyWindow];\n#pragma clang diagnostic pop\n    for (UIWindow *window in [[[DoraemonHierarchyHelper shared] allWindowsIgnorePrefix:@\"Doraemon\"] reverseObjectEnumerator]) {\n        if ([window hitTest:tapPointInWindow withEvent:nil]) {\n            windowForSelection = window;\n            break;\n        }\n    }\n\n    // Select the deepest visible view at the tap point. This generally corresponds to what the user wants to select.\n    return [self recursiveSubviewsAtPoint:tapPointInWindow inView:windowForSelection skipHiddenViews:YES];\n}\n\n- (NSArray<UIView *> *)recursiveSubviewsAtPoint:(CGPoint)pointInView inView:(UIView *)view skipHiddenViews:(BOOL)skipHidden {\n    NSMutableArray<UIView *> *subviewsAtPoint = [NSMutableArray array];\n    NSEnumerator<__kindof UIView *> *enumerator = [view.subviews reverseObjectEnumerator];\n    UIView *subview = nil;\n    while ((subview = enumerator.nextObject)) {\n        BOOL isHidden = subview.hidden || subview.alpha < 0.01;\n        if (skipHidden && isHidden) {\n            continue;\n        }\n        \n        BOOL subviewContainsPoint = CGRectContainsPoint(subview.frame, pointInView);\n        if (subviewContainsPoint) {\n            [subviewsAtPoint addObject:subview];\n        }\n        \n        // If this view doesn't clip to its bounds, we need to check its subviews even if it doesn't contain the selection point.\n        // They may be visible and contain the selection point.\n        if (subviewContainsPoint || !subview.clipsToBounds) {\n            CGPoint pointInSubview = [view convertPoint:pointInView toView:subview];\n            [subviewsAtPoint addObjectsFromArray:[self recursiveSubviewsAtPoint:pointInSubview inView:subview skipHiddenViews:skipHidden]];\n            break;\n        }\n    }\n    \n    return subviewsAtPoint;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/View/DKMoveView.h",
    "content": "#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKMoveView : UIView\n\n@property (nonatomic, assign, getter=isOverflow) BOOL overflow;\n\n// assign\n@property (nonatomic, readonly, getter=isMoved) BOOL moved;\n\n@property (nonatomic, getter=isMovable) BOOL movable;\n\n@property (nonatomic, assign) CGRect movableRect;\n\n- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;\n\n- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;\n\n- (void)viewWillUpdateOffset:(UIPanGestureRecognizer *)sender offset:(CGPoint)offsetPoint;\n\n- (void)viewDidUpdateOffset:(UIPanGestureRecognizer *)sender offset:(CGPoint)offsetPoint;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/View/DKMoveView.m",
    "content": "#import <DoraemonKit/DKMoveView.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKMoveView ()\n\n@property (nonatomic, assign) BOOL moved;\n\n@property (nonatomic, strong) UIPanGestureRecognizer *panGestureRecognizer;\n\n- (void)moveViewInit;\n\n- (void)handleGesture:(nullable UIGestureRecognizer *)gestureRecognizer;\n\n- (void)changeFrameWithPoint:(CGPoint)point;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n@implementation DKMoveView\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    [self moveViewInit];\n\n    return self;\n}\n\n- (instancetype)initWithCoder:(NSCoder *)coder {\n    self = [super initWithCoder:coder];\n    if (self = [super initWithCoder:coder]) {\n        [self moveViewInit];\n    }\n\n    return self;\n}\n\n- (void)moveViewInit {\n    _movableRect = CGRectNull;\n    _panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];\n\n    [self addGestureRecognizer:self.panGestureRecognizer];\n}\n\n- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer {\n    if (gestureRecognizer != self.panGestureRecognizer) {\n        return;\n    }\n    if (!self.isMoved) {\n        self.moved = YES;\n    }\n\n    CGPoint offsetPoint = [self.panGestureRecognizer translationInView:gestureRecognizer.view];\n    [self viewWillUpdateOffset:self.panGestureRecognizer offset:offsetPoint];\n    [self.panGestureRecognizer setTranslation:CGPointZero inView:gestureRecognizer.view];\n    [self changeFrameWithPoint:offsetPoint];\n    [self viewDidUpdateOffset:self.panGestureRecognizer offset:offsetPoint];\n}\n\n- (void)changeFrameWithPoint:(CGPoint)point {\n    CGPoint center = self.center;\n    center.x += point.x;\n    center.y += point.y;\n    if (self.isOverflow) {\n        center.x = MAX(MIN(center.x, self.superview.bounds.size.width), 0);\n        center.y = MAX(MIN(center.y, self.superview.bounds.size.height), 0);\n    } else {\n        if (center.x < self.frame.size.width / 2) {\n            center.x = self.frame.size.width / 2;\n        } else if (center.x > self.superview.bounds.size.width - self.frame.size.width / 2) {\n            center.x = self.superview.bounds.size.width - self.frame.size.width / 2;\n        }\n        if (center.y < self.frame.size.height / 2) {\n            center.y = self.frame.size.height / 2;\n        } else if (center.y > self.superview.bounds.size.height - self.frame.size.height / 2) {\n            center.y = self.superview.bounds.size.height - self.frame.size.height / 2;\n        }\n    }\n\n    if (!CGRectIsNull(self.movableRect)) {\n        // movableRect 指的是以 DKMoveView 父视图作为坐标系\n        if (!CGRectContainsPoint(self.movableRect, center)) {\n            if (center.x < self.movableRect.origin.x) {\n                center.x = self.movableRect.origin.x;\n            } else if (center.x > self.movableRect.origin.x + self.movableRect.size.width) {\n                center.x = self.movableRect.origin.x + self.movableRect.size.width;\n            }\n            if (center.y < self.movableRect.origin.y) {\n                center.y = self.movableRect.origin.y;\n            } else if (center.y > self.movableRect.origin.y + self.movableRect.size.height) {\n                center.y = self.movableRect.origin.y + self.movableRect.size.height;\n            }\n        }\n    }\n    self.center = center;\n}\n\n- (void)viewWillUpdateOffset:(UIPanGestureRecognizer *)sender offset:(CGPoint)offsetPoint {\n\n}\n\n- (void)viewDidUpdateOffset:(UIPanGestureRecognizer *)sender offset:(CGPoint)offsetPoint {\n\n}\n\n- (void)setMovable:(BOOL)movable {\n    self.panGestureRecognizer.enabled = movable;\n}\n\n- (BOOL)isMovable {\n    return self.panGestureRecognizer.enabled;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/View/DKPickerView.h",
    "content": "#import <DoraemonKit/DKMoveView.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKPickerView : DKMoveView\n\n- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;\n\n- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/View/DKPickerView.m",
    "content": "#import <DoraemonKit/DKPickerView.h>\n#import <DoraemonKit/UIImage+Doraemon.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DKPickerView ()\n\n- (void)pickerViewInit;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n@implementation DKPickerView\n\n- (void)pickerViewInit {\n    self.overflow = YES;\n    self.backgroundColor = UIColor.clearColor;\n    self.layer.cornerRadius = MIN(self.bounds.size.width, self.bounds.size.height) / 2.0;\n    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_visual\"]];\n    imageView.frame = self.bounds;\n    [self addSubview:imageView];\n}\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    [self pickerViewInit];\n\n    return self;\n}\n\n- (instancetype)initWithCoder:(NSCoder *)coder {\n    if (self = [super initWithCoder:coder]) {\n        [self pickerViewInit];\n    }\n\n    return self;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/View/DoraemonHierarchyHeaderView.h",
    "content": "//\n//  DoraemonHierarchyHeaderView.h\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonHierarchyHeaderView : UIView\n\n@property (nonatomic, strong, readonly) UILabel *titleLabel;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/Hierarchy/UserInterface/View/DoraemonHierarchyHeaderView.m",
    "content": "//\n//  DoraemonHierarchyHeaderView.m\n//  DoraemonKit\n//\n//  Created by lijiahuan on 2019/11/2.\n//\n\n#import \"DoraemonHierarchyHeaderView.h\"\n#import \"UIView+Doraemon.h\"\n\n@interface DoraemonHierarchyHeaderView ()\n\n@property (nonatomic, strong) UILabel *titleLabel;\n\n@end\n\n@implementation DoraemonHierarchyHeaderView\n\n- (instancetype)init {\n    if (self = [super init]) {\n        [self initUI];\n    }\n    return self;\n}\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    if (self = [super initWithFrame:frame]) {\n        [self initUI];\n    }\n    return self;\n}\n\n#pragma mark - Primary\n- (void)initUI {    \n    [self addSubview:self.titleLabel];\n}\n\n- (void)layoutSubviews {\n    [super layoutSubviews];\n    \n    self.titleLabel.frame = CGRectMake(10, 0, self.doraemon_width - 10 * 2, self.doraemon_height);\n}\n\n#pragma mark - Getters and setters\n- (UILabel *)titleLabel {\n    if (!_titleLabel) {\n        _titleLabel = [[UILabel alloc] init];\n        _titleLabel.font = [UIFont boldSystemFontOfSize:18];\n        _titleLabel.textColor = [UIColor blackColor];\n    }\n    return _titleLabel;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewAlign/DoraemonViewAlignPlugin.h",
    "content": "//\n//  DoraemonViewAlignPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/16.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonViewAlignPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewAlign/DoraemonViewAlignPlugin.m",
    "content": "//\n//  DoraemonViewAlignPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/16.\n//\n\n#import \"DoraemonViewAlignPlugin.h\"\n#import \"DoraemonViewAlignManager.h\"\n#import \"DoraemonHomeWindow.h\"\n\n\n@implementation DoraemonViewAlignPlugin\n\n- (void)pluginDidLoad{\n    [[DoraemonViewAlignManager shareInstance] show];\n    [[DoraemonHomeWindow shareInstance] hide];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewAlign/Function/DoraemonViewAlignManager.h",
    "content": "//\n//  DoraemonViewAlignManager.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/16.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface DoraemonViewAlignManager : NSObject\n\n+ (DoraemonViewAlignManager *)shareInstance;\n\n- (void)show;\n\n- (void)hidden;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewAlign/Function/DoraemonViewAlignManager.m",
    "content": "//\n//  DoraemonViewAlignManager.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/16.\n//\n\n#import \"DoraemonViewAlignManager.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonViewAlignView.h\"\n\n\n@interface DoraemonViewAlignManager()\n\n@property (nonatomic, strong) DoraemonViewAlignView *alignView;\n\n@end\n\n@implementation DoraemonViewAlignManager\n\n+ (DoraemonViewAlignManager *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonViewAlignManager *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonViewAlignManager alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    self = [super init];\n    if (self) {\n        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(closePlugin:) name:DoraemonClosePluginNotification object:nil];\n        [[DoraemonUtil getKeyWindow] addObserver:self forKeyPath:@\"rootViewController\" options:NSKeyValueObservingOptionNew context:nil];\n    }\n    return self;\n}\n\n- (void)dealloc {\n     [[DoraemonUtil getKeyWindow] removeObserver:self forKeyPath:@\"rootViewController\"];\n}\n\n- (void)show{\n    if (!_alignView) {\n        _alignView = [[DoraemonViewAlignView alloc] init];\n//        _alignView.hidden = YES;\n        [_alignView hide];\n        [[DoraemonUtil getKeyWindow] addSubview:_alignView];\n    }\n//    _alignView.hidden = NO;\n    [_alignView show];\n}\n\n- (void)hidden{\n//    _alignView.hidden = YES;\n    [_alignView hide];\n}\n\n- (void)closePlugin:(NSNotification *)notification{\n    [self hidden];\n}\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {\n    [[DoraemonUtil getKeyWindow] bringSubviewToFront:self.alignView];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewAlign/Function/DoraemonViewAlignView.h",
    "content": "//\n//  DoraemonViewAlignView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/16.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface DoraemonViewAlignView : UIView\n\n- (void)show;\n\n- (void)hide;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewAlign/Function/DoraemonViewAlignView.m",
    "content": "//\n//  DoraemonViewAlignView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/6/16.\n//\n\n#import \"DoraemonViewAlignView.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonVisualInfoWindow.h\"\n\n#define ALIGN_COLOR @\"#FF0000\"\n\nstatic CGFloat const kViewCheckSize = 62;\n\n@interface DoraemonViewAlignView()\n\n@property (nonatomic, strong) UIImageView *imageView;\n@property (nonatomic, strong) UIView *horizontalLine;//水平线\n@property (nonatomic, strong) UIView *verticalLine;//垂直线\n@property (nonatomic, strong) UILabel *leftLabel;\n@property (nonatomic, strong) UILabel *topLabel;\n@property (nonatomic, strong) UILabel *rightLabel;\n@property (nonatomic, strong) UILabel *bottomLabel;\n@property (nonatomic, strong) DoraemonVisualInfoWindow *infoWindow; \n\n@end\n\n\n\n@implementation DoraemonViewAlignView\n\n-(instancetype)init{\n    self = [super init];\n    if (self) {\n        self.frame = CGRectMake(0, 0, DoraemonScreenWidth, DoraemonScreenHeight);\n        self.backgroundColor = [UIColor clearColor];\n        self.layer.zPosition = FLT_MAX;\n        //self.userInteractionEnabled = NO;\n        \n        \n        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(DoraemonScreenWidth/2-kViewCheckSize/2, DoraemonScreenHeight/2-kViewCheckSize/2, kViewCheckSize, kViewCheckSize)];\n        imageView.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_visual\"];\n        [self addSubview:imageView];\n        _imageView = imageView;\n        \n        imageView.userInteractionEnabled = YES;\n        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];\n        [imageView addGestureRecognizer:pan];\n        \n        _horizontalLine = [[UIView alloc] initWithFrame:CGRectMake(0, imageView.doraemon_centerY-0.25, self.doraemon_width, 0.5)];\n        _horizontalLine.backgroundColor = [UIColor doraemon_colorWithHexString:ALIGN_COLOR];\n        [self addSubview:_horizontalLine];\n        \n        _verticalLine = [[UIView alloc] initWithFrame:CGRectMake(imageView.doraemon_centerX-0.25, 0, 0.5, self.doraemon_height)];\n        _verticalLine.backgroundColor = [UIColor doraemon_colorWithHexString:ALIGN_COLOR];\n        [self addSubview:_verticalLine];\n        \n        [self bringSubviewToFront:_imageView];\n        \n        _leftLabel = [[UILabel alloc] init];\n        _leftLabel.font = [UIFont systemFontOfSize:12];\n        _leftLabel.textColor = [UIColor doraemon_colorWithHexString:ALIGN_COLOR];\n        _leftLabel.text = [NSString stringWithFormat:@\"%.1f\",imageView.doraemon_centerX];\n        [self addSubview:_leftLabel];\n        [_leftLabel sizeToFit];\n        _leftLabel.frame = CGRectMake(imageView.doraemon_centerX/2, imageView.doraemon_centerY-_leftLabel.doraemon_height, _leftLabel.doraemon_width, _leftLabel.doraemon_height);\n        \n        _topLabel = [[UILabel alloc] init];\n        _topLabel.font = [UIFont systemFontOfSize:12];\n        _topLabel.textColor = [UIColor doraemon_colorWithHexString:ALIGN_COLOR];\n        _topLabel.text = [NSString stringWithFormat:@\"%.1f\",imageView.doraemon_centerY];\n        [self addSubview:_topLabel];\n        [_topLabel sizeToFit];\n        _topLabel.frame = CGRectMake(imageView.doraemon_centerX-_topLabel.doraemon_width, imageView.doraemon_centerY/2, _topLabel.doraemon_width, _topLabel.doraemon_height);\n        \n        _rightLabel = [[UILabel alloc] init];\n        _rightLabel.font = [UIFont systemFontOfSize:12];\n        _rightLabel.textColor = [UIColor doraemon_colorWithHexString:ALIGN_COLOR];\n        _rightLabel.text = [NSString stringWithFormat:@\"%.1f\",self.doraemon_width-imageView.doraemon_centerX];\n        [self addSubview:_rightLabel];\n        [_rightLabel sizeToFit];\n        _rightLabel.frame = CGRectMake(imageView.doraemon_centerX+(self.doraemon_width-imageView.doraemon_centerX)/2, imageView.doraemon_centerY-_rightLabel.doraemon_height, _rightLabel.doraemon_width, _rightLabel.doraemon_height);\n        \n        _bottomLabel = [[UILabel alloc] init];\n        _bottomLabel.font = [UIFont systemFontOfSize:12];\n        _bottomLabel.textColor = [UIColor doraemon_colorWithHexString:ALIGN_COLOR];\n        _bottomLabel.text = [NSString stringWithFormat:@\"%.1f\",self.doraemon_height - imageView.doraemon_centerY];\n        [self addSubview:_bottomLabel];\n        [_bottomLabel sizeToFit];\n        _bottomLabel.frame = CGRectMake(imageView.doraemon_centerX-_bottomLabel.doraemon_width, imageView.doraemon_centerY+(self.doraemon_height - imageView.doraemon_centerY)/2, _bottomLabel.doraemon_width, _bottomLabel.doraemon_height);\n        \n        CGRect infoWindowFrame = CGRectZero;\n        if (kInterfaceOrientationPortrait) {\n            infoWindowFrame = CGRectMake(kDoraemonSizeFrom750_Landscape(30), DoraemonScreenHeight - kDoraemonSizeFrom750_Landscape(100) - kDoraemonSizeFrom750_Landscape(30), DoraemonScreenWidth - 2*kDoraemonSizeFrom750_Landscape(30), kDoraemonSizeFrom750_Landscape(100));\n        } else {\n            infoWindowFrame = CGRectMake(kDoraemonSizeFrom750_Landscape(30), DoraemonScreenHeight - kDoraemonSizeFrom750_Landscape(100) - kDoraemonSizeFrom750_Landscape(30), DoraemonScreenHeight - 2*kDoraemonSizeFrom750_Landscape(30), kDoraemonSizeFrom750_Landscape(100));\n        } \n        _infoWindow = [[DoraemonVisualInfoWindow alloc] initWithFrame:infoWindowFrame];\n        \n        \n         [self configInfoLblText];\n    }\n    return self;\n}\n\n- (void)pan:(UIPanGestureRecognizer *)sender{\n    //1、获得拖动位移\n    CGPoint offsetPoint = [sender translationInView:sender.view];\n    //2、清空拖动位移\n    [sender setTranslation:CGPointZero inView:sender.view];\n    //3、重新设置控件位置\n    UIView *panView = sender.view;\n    CGFloat newX = panView.doraemon_centerX+offsetPoint.x;\n    CGFloat newY = panView.doraemon_centerY+offsetPoint.y;\n\n    CGPoint centerPoint = CGPointMake(newX, newY);\n    panView.center = centerPoint;\n    \n    _horizontalLine.frame = CGRectMake(0, _imageView.doraemon_centerY-0.25, self.doraemon_width, 0.5);\n    _verticalLine.frame = CGRectMake(_imageView.doraemon_centerX-0.25, 0, 0.5, self.doraemon_height);\n    \n    _leftLabel.text = [NSString stringWithFormat:@\"%.1f\",_imageView.doraemon_centerX];\n    [_leftLabel sizeToFit];\n    _leftLabel.frame = CGRectMake(_imageView.doraemon_centerX/2, _imageView.doraemon_centerY-_leftLabel.doraemon_height, _leftLabel.doraemon_width, _leftLabel.doraemon_height);\n    \n    _topLabel.text = [NSString stringWithFormat:@\"%.1f\",_imageView.doraemon_centerY];\n    [_topLabel sizeToFit];\n    _topLabel.frame = CGRectMake(_imageView.doraemon_centerX-_topLabel.doraemon_width, _imageView.doraemon_centerY/2, _topLabel.doraemon_width, _topLabel.doraemon_height);\n    \n    _rightLabel.text = [NSString stringWithFormat:@\"%.1f\",self.doraemon_width-_imageView.doraemon_centerX];\n    [_rightLabel sizeToFit];\n    _rightLabel.frame = CGRectMake(_imageView.doraemon_centerX+(self.doraemon_width-_imageView.doraemon_centerX)/2, _imageView.doraemon_centerY-_rightLabel.doraemon_height, _rightLabel.doraemon_width, _rightLabel.doraemon_height);\n    \n    _bottomLabel.text = [NSString stringWithFormat:@\"%.1f\",self.doraemon_height - _imageView.doraemon_centerY];\n    [_bottomLabel sizeToFit];\n    _bottomLabel.frame = CGRectMake(_imageView.doraemon_centerX-_bottomLabel.doraemon_width, _imageView.doraemon_centerY+(self.doraemon_height - _imageView.doraemon_centerY)/2, _bottomLabel.doraemon_width, _bottomLabel.doraemon_height);\n    \n    [self configInfoLblText];\n}\n\n- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{\n    if(CGRectContainsPoint(_imageView.frame, point)){\n        return YES;\n    }\n    return NO;\n}\n\n- (void)configInfoLblText {\n    _infoWindow.infoText = [NSString stringWithFormat:DoraemonLocalizedString(@\"位置：左%@  右%@  上%@  下%@\"), _leftLabel.text, _rightLabel.text, _topLabel.text, _bottomLabel.text];\n}\n \n\n- (void)show {\n    _infoWindow.hidden = NO;\n    self.hidden = NO;\n}\n\n- (void)hide {\n    _infoWindow.hidden = YES;\n    self.hidden = YES;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewCheck/DoraemonViewCheckPlugin.h",
    "content": "//\n//  DoraemonViewCheckPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/3/28.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonViewCheckPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewCheck/DoraemonViewCheckPlugin.m",
    "content": "//\n//  DoraemonViewCheckPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/3/28.\n//\n\n#import \"DoraemonViewCheckPlugin.h\"\n#import \"DoraemonViewCheckManager.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonViewCheckPlugin\n\n- (void)pluginDidLoad{\n    [[DoraemonViewCheckManager shareInstance] show];\n    [[DoraemonHomeWindow shareInstance] hide];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewCheck/Function/DoraemonViewCheckManager.h",
    "content": "//\n//  DoraemonViewCheckManager.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/3/28.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface DoraemonViewCheckManager : NSObject\n\n+ (DoraemonViewCheckManager *)shareInstance;\n\n- (void)show;\n\n- (void)hidden;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewCheck/Function/DoraemonViewCheckManager.m",
    "content": "//\n//  DoraemonViewCheckManager.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/3/28.\n//\n\n#import \"DoraemonViewCheckManager.h\"\n#import \"DoraemonViewCheckView.h\"\n#import \"DoraemonDefine.h\"\n\n\n@interface DoraemonViewCheckManager()\n\n@property (nonatomic, strong) DoraemonViewCheckView *viewCheckView;\n\n@end\n\n@implementation DoraemonViewCheckManager\n\n+ (DoraemonViewCheckManager *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonViewCheckManager *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonViewCheckManager alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    self = [super init];\n    if (self) {\n        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(closePlugin:) name:DoraemonClosePluginNotification object:nil];\n        [[DoraemonUtil getKeyWindow] addObserver:self forKeyPath:@\"rootViewController\" options:NSKeyValueObservingOptionNew context:nil];\n    }\n    return self;\n}\n\n- (void)dealloc {\n    [[DoraemonUtil getKeyWindow] removeObserver:self forKeyPath:@\"rootViewController\"];\n}\n\n- (void)show{\n    if (!_viewCheckView) {\n        _viewCheckView = [[DoraemonViewCheckView alloc] init];\n        _viewCheckView.hidden = YES;\n        [[DoraemonUtil getKeyWindow] addSubview:_viewCheckView];\n    }\n    [_viewCheckView show];\n}\n\n- (void)hidden{\n    [_viewCheckView hide];\n}\n\n- (void)closePlugin:(NSNotification *)notification{\n    [self hidden];\n}\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {\n    [[DoraemonUtil getKeyWindow] bringSubviewToFront:self.viewCheckView];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewCheck/Function/DoraemonViewCheckView.h",
    "content": "//\n//  DoraemonViewCheckView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/3/28.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface DoraemonViewCheckView : UIView\n\n- (void)hide;\n\n- (void)show;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewCheck/Function/DoraemonViewCheckView.m",
    "content": "//\n//  DoraemonViewCheckView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/3/28.\n//\n\n#import \"DoraemonViewCheckView.h\"\n#import \"DoraemonDefine.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonVisualInfoWindow.h\"\n#import <objc/runtime.h>\n\n\nstatic CGFloat const kViewCheckSize = 62;\n\n@interface DoraemonViewCheckView()\n\n@property (nonatomic, strong) UIView *viewBound;//当前需要探测的view的边框\n@property (nonatomic, strong) DoraemonVisualInfoWindow *infoWindow;//顶部被探测到的view的信息显示的UIwindow\n\n@property (nonatomic, assign) CGFloat left;\n@property (nonatomic, assign) CGFloat top;\n@property (nonatomic, strong) NSMutableArray *arrViewHit;\n@property (nonatomic, strong) UIView *oldView;\n\n@end\n\n@implementation DoraemonViewCheckView\n\n-(instancetype)init{\n    self = [super init];\n    if (self) {\n        self.frame = CGRectMake(DoraemonScreenWidth/2-kViewCheckSize/2, DoraemonScreenHeight/2-kViewCheckSize/2, kViewCheckSize, kViewCheckSize);\n        self.backgroundColor = [UIColor clearColor];\n        self.layer.zPosition = FLT_MAX;\n        \n        UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds];\n        imageView.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_visual\"];\n        [self addSubview:imageView];\n        \n        _arrViewHit = [[NSMutableArray alloc] initWithCapacity:30];\n        \n        _viewBound = [[UIView alloc] init];\n        _viewBound.layer.masksToBounds = YES;\n        _viewBound.layer.borderWidth = 2.;\n        _viewBound.layer.borderColor = [UIColor doraemon_colorWithHex:0xCC3A4B].CGColor;\n        _viewBound.layer.zPosition = FLT_MAX;\n        \n        CGRect infoWindowFrame = CGRectZero;\n        if (kInterfaceOrientationPortrait) {\n            infoWindowFrame = CGRectMake(kDoraemonSizeFrom750_Landscape(30), DoraemonScreenHeight - kDoraemonSizeFrom750_Landscape(180) - kDoraemonSizeFrom750_Landscape(30), DoraemonScreenWidth - 2*kDoraemonSizeFrom750_Landscape(30), kDoraemonSizeFrom750_Landscape(180));\n        } else {\n            infoWindowFrame = CGRectMake(kDoraemonSizeFrom750_Landscape(30), DoraemonScreenHeight - kDoraemonSizeFrom750_Landscape(180) - kDoraemonSizeFrom750_Landscape(30), DoraemonScreenHeight - 2*kDoraemonSizeFrom750_Landscape(30), kDoraemonSizeFrom750_Landscape(180));\n        }\n        _infoWindow = [[DoraemonVisualInfoWindow alloc] initWithFrame:infoWindowFrame];\n        \n    }\n     \n    return self;\n}\n\n- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{\n    UITouch *touch = [touches anyObject];\n    CGPoint point = [touch locationInView:self];\n    _left = point.x;\n    _top = point.y;\n    CGPoint topPoint = [touch locationInView:self.window];\n    UIView *view = [self topView:self.window Point:topPoint];\n    CGRect frame = [self.window convertRect:view.bounds fromView:view];\n    _viewBound.frame = frame;\n    [self.window addSubview:_viewBound];\n\n    if ([self needRefresh:view]) {\n        _infoWindow.infoAttributedText = [self viewInfo:view];\n    }\n}\n\n-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{\n    UITouch *touch = [touches anyObject];\n    CGPoint point = [touch locationInView:self.window];\n    self.frame = CGRectMake(point.x-_left, point.y-_top, self.frame.size.width, self.frame.size.height);\n    \n    CGPoint topPoint = [touch locationInView:self.window];\n    UIView *view = [self topView:self.window Point:topPoint];\n    CGRect frame = [self.window convertRect:view.bounds fromView:view];\n    _viewBound.frame = frame;\n    if ([self needRefresh:view]) {\n        _infoWindow.infoAttributedText = [self viewInfo:view];\n    }\n}\n\n-(void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{\n    [_viewBound removeFromSuperview];\n}\n\n-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{\n    [_viewBound removeFromSuperview];\n}\n\n-(UIView*)topView:(UIView*)view Point:(CGPoint) point{\n    [_arrViewHit removeAllObjects];\n    [self hitTest:view Point:point];\n    UIView *viewTop=[_arrViewHit lastObject];\n    [_arrViewHit removeAllObjects];\n    return viewTop;\n}\n\n-(void)hitTest:(UIView*)view Point:(CGPoint) point{\n    if([view isKindOfClass:[UIScrollView class]])\n    {\n        point.x+=((UIScrollView*)view).contentOffset.x;\n        point.y+=((UIScrollView*)view).contentOffset.y;\n    }\n    if ([view pointInside:point withEvent:nil] &&\n        (!view.hidden) &&\n        (view.alpha >= 0.01f) && (view!=_viewBound) && ![view isDescendantOfView:self]) {\n        [_arrViewHit addObject:view];\n        for (UIView *subView in view.subviews) {\n            CGPoint subPoint = CGPointMake(point.x - subView.frame.origin.x,\n                                           point.y - subView.frame.origin.y);\n            [self hitTest:subView Point:subPoint];\n        }\n    }\n}\n\n- (void)show {\n    _infoWindow.hidden = NO;\n    self.hidden = NO;\n}\n\n- (void)hide {\n    [_viewBound removeFromSuperview];\n    _infoWindow.hidden = YES;\n    self.hidden = YES;\n}\n\n- (BOOL)needRefresh:(UIView *)view{\n    if (!_oldView) {\n        _oldView = view;\n    }\n    BOOL needRefresh = NO;\n    if (_oldView != view) {\n        needRefresh = YES;\n        _oldView = view;\n    }\n    return needRefresh;\n}\n\n-(NSMutableAttributedString *)viewInfo:(UIView *)view{\n    if (view) {\n        //获取属性名\n        UIView *tempView = view;\n        NSString *ivarName = nil;\n        while(tempView != nil && tempView != self.doraemon_viewController.view) {\n            ivarName =  [self nameWithInstance:view inTarger:tempView.superview];\n            if (ivarName) {\n                break;\n            }\n            tempView = tempView.superview;\n        }\n        if (!ivarName) {\n            ivarName = [self nameWithInstance:view inTarger:self.doraemon_viewController.view];\n        }\n        \n        if (!ivarName) {\n            ivarName = [self nameWithInstance:view inTarger:view.doraemon_viewController];\n        }\n        \n        NSMutableString *showString = [[NSMutableString alloc] init];\n        NSString *tempString = nil;\n        if (ivarName) {\n            tempString = [NSString stringWithFormat:@\"%@:%@(%@)\",DoraemonLocalizedString(@\"控件名称\"),NSStringFromClass([view class]),ivarName];\n        }else{\n            tempString = [NSString stringWithFormat:@\"%@:%@\",DoraemonLocalizedString(@\"控件名称\"),NSStringFromClass([view class])];\n        }\n        NSLog(@\"tempString == %@\",tempString);\n        [showString appendString:tempString];\n        \n        tempString = [NSString stringWithFormat:DoraemonLocalizedString(@\"\\n控件位置：左%0.1lf  上%0.1lf  宽%0.1lf  高%0.1lf\"),view.frame.origin.x,view.frame.origin.y,view.frame.size.width,view.frame.size.height];\n        [showString appendString:tempString];\n        \n        if([view isKindOfClass:[UILabel class]]){\n            UILabel *vLabel = (UILabel *)view;\n            tempString = [NSString stringWithFormat:DoraemonLocalizedString(@\"\\n背景颜色：%@  字体颜色：%@  字体大小：%.f\"),[self hexFromUIColor:vLabel.backgroundColor],[self hexFromUIColor:vLabel.textColor],vLabel.font.pointSize];\n            [showString appendString:tempString];\n        }else if ([view isMemberOfClass:[UIView class]]) {\n            tempString = [NSString stringWithFormat:DoraemonLocalizedString(@\"\\n背景颜色：%@\"),[self hexFromUIColor:view.backgroundColor]];\n            [showString appendString:tempString];\n        }\n        \n        NSString *string = [NSString stringWithFormat:@\"%@\",showString];\n        // 行间距\n        NSMutableParagraphStyle *style = [NSMutableParagraphStyle new];\n        style.lineSpacing = kDoraemonSizeFrom750_Landscape(12);\n        \n\n        style.lineBreakMode = NSLineBreakByTruncatingTail;\n        \n        NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:string];\n        [attrString addAttributes:@{\n                                    NSParagraphStyleAttributeName : style,\n                                    NSFontAttributeName : [UIFont systemFontOfSize: kDoraemonSizeFrom750_Landscape(24)],\n                                    NSForegroundColorAttributeName : [UIColor doraemon_black_1]\n                                    }\n                            range:NSMakeRange(0, string.length)];\n        return attrString;\n    }\n    return nil;\n}\n\n- (NSString *)nameWithInstance:(id)instance inTarger:(id)target{\n    unsigned int numIvars = 0;\n    NSString *key=nil;\n    Ivar * ivars = class_copyIvarList([target class], &numIvars);\n    for(int i = 0; i < numIvars; i++) {\n        Ivar thisIvar = ivars[i];\n        const char *type = ivar_getTypeEncoding(thisIvar);\n        NSString *stringType =  [NSString stringWithCString:type encoding:NSUTF8StringEncoding];\n        if (![stringType hasPrefix:@\"@\"]) {\n            continue;\n        }\n        if ((object_getIvar(target, thisIvar) == instance)) {\n            key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];\n            break;\n        }\n    }\n    free(ivars);\n    return key;\n}\n\n- (NSString *)hexFromUIColor: (UIColor*) color {\n    if (!color) {\n        return @\"nil\";\n    }\n    if(color == [UIColor clearColor]){\n        return @\"clear\";\n    }\n    if (CGColorGetNumberOfComponents(color.CGColor) < 4) {\n        const CGFloat *components = CGColorGetComponents(color.CGColor);\n        color = [UIColor colorWithRed:components[0]\n                                green:components[0]\n                                 blue:components[0]\n                                alpha:components[1]];\n    }\n    \n    if (CGColorSpaceGetModel(CGColorGetColorSpace(color.CGColor)) != kCGColorSpaceModelRGB) {\n        //return [NSString stringWithFormat:@\"#FFFFFF\"];\n        return @\"单色色彩空间模式\";\n    }\n    \n    int alpha = (int)((CGColorGetComponents(color.CGColor))[3]*255.0);\n    NSString *hex = [NSString stringWithFormat:@\"#%02X%02X%02X\", (int)((CGColorGetComponents(color.CGColor))[0]*255.0),\n                     (int)((CGColorGetComponents(color.CGColor))[1]*255.0),\n                     (int)((CGColorGetComponents(color.CGColor))[2]*255.0)];\n    if (alpha < 255) {//存在透明度\n        hex = [NSString stringWithFormat:@\"%@ alpha:%.2f\",hex,(CGColorGetComponents(color.CGColor))[3]];\n    }\n    \n    \n    return hex;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewMetrics/DoraemonMetricsViewController.h",
    "content": "//\n//  DoraemonMetricsViewController.h\n//  DoraemonKit\n//\n//  Created by xgb on 2019/1/10.\n//\n\n#import <DoraemonKit/DoraemonKit.h>\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMetricsViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewMetrics/DoraemonMetricsViewController.m",
    "content": "//\n//  DoraemonMetricsViewController.m\n//  DoraemonKit\n//\n//  Created by xgb on 2019/1/10.\n//\n\n#import \"DoraemonMetricsViewController.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonViewMetricsConfig.h\"\n\n@interface DoraemonMetricsViewController () <DoraemonSwitchViewDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n\n@end\n\n@implementation DoraemonMetricsViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    self.title = DoraemonLocalizedString(@\"布局边框\");\n    \n    _switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"布局边框开关\") switchOn:[DoraemonViewMetricsConfig defaultConfig].enable];\n    [_switchView needTopLine];\n    [_switchView needDownLine];\n    _switchView.delegate = self;\n    [self.view addSubview:_switchView];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    [DoraemonViewMetricsConfig defaultConfig].opened = YES;\n    [DoraemonViewMetricsConfig defaultConfig].enable = on;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewMetrics/DoraemonViewMetricsPlugin.h",
    "content": "//\n//  DoraemonViewMetricsPlugin.h\n//  DoraemonKit\n//\n//  Created by xgb on 2018/12/11.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonViewMetricsPlugin : NSObject <DoraemonPluginProtocol>\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewMetrics/DoraemonViewMetricsPlugin.m",
    "content": "//\n//  DoraemonViewMetricsPlugin.m\n//  DoraemonKit\n//\n//  Created by xgb on 2018/12/11.\n//\n\n#import \"DoraemonViewMetricsPlugin.h\"\n#import \"DoraemonMetricsViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonViewMetricsPlugin\n\n- (void)pluginDidLoad{\n    DoraemonMetricsViewController *vc = [[DoraemonMetricsViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewMetrics/Function/DoraemonViewMetricsConfig.h",
    "content": "//\n//  DoraemonViewMetricsManager.h\n//  DoraemonKit\n//\n//  Created by xgb on 2018/12/11.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\n@interface DoraemonViewMetricsConfig : NSObject\n\n@property (nonatomic, strong) UIColor *borderColor;     //default randomColor\n@property (nonatomic, assign) CGFloat borderWidth;      //default 1\n@property (nonatomic, assign) BOOL enable;              //default NO\n@property (nonatomic, assign) BOOL opened;              //default NO\n+ (instancetype)defaultConfig;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewMetrics/Function/DoraemonViewMetricsConfig.m",
    "content": "//\n//  DoraemonViewMetricsManager.m\n//  DoraemonKit\n//\n//  Created by xgb on 2018/12/11.\n//\n\n#import \"DoraemonViewMetricsConfig.h\"\n#import \"UIView+DoraemonViewMetrics.h\"\n\n@implementation DoraemonViewMetricsConfig\n\n+ (instancetype)defaultConfig\n{\n    static DoraemonViewMetricsConfig *sharedInstance = nil;\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        sharedInstance = [DoraemonViewMetricsConfig new];\n    });\n    \n    return sharedInstance;\n}\n\n- (instancetype)init\n{\n    self = [super init];\n    if (self) {\n        self.borderWidth = 1;\n        //self.enable = NO;\n    }\n    return self;\n}\n\n- (void)setEnable:(BOOL)enable\n{\n    _enable = enable;\n    \n    for (UIWindow *window in [UIApplication sharedApplication].windows) {\n        [window doraemonMetricsRecursiveEnable:enable];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewMetrics/Function/UIView+DoraemonViewMetrics.h",
    "content": "//\n//  UIView+DoraemonViewMetrics.h\n//  DoraemonKit\n//\n//  Created by xgb on 2018/12/11.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface UIView (DoraemonViewMetrics)\n\n- (void)doraemonMetricsRecursiveEnable:(BOOL)enable;\n\n@end\n\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Plugin/UI/ViewMetrics/Function/UIView+DoraemonViewMetrics.m",
    "content": "//\n//  UIView+DoraemonViewMetrics.m\n//  DoraemonKit\n//\n//  Created by xgb on 2018/12/11.\n//\n\n#import \"UIView+DoraemonViewMetrics.h\"\n#import \"DoraemonViewMetricsConfig.h\"\n#import \"NSObject+Doraemon.h\"\n#import <objc/runtime.h>\n#import \"DoraemonDefine.h\"\n\n\n@interface UIView ()\n\n@property (nonatomic ,strong) CALayer *metricsBorderLayer;\n\n@end\n\n\n@implementation UIView (DoraemonViewMetrics)\n\n+ (void)load {\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [[self class] doraemon_swizzleInstanceMethodWithOriginSel:@selector(layoutSubviews) swizzledSel:@selector(doraemon_layoutSubviews)];\n    });\n}\n\n- (void)doraemon_layoutSubviews\n{\n    [self doraemon_layoutSubviews];\n    if (DoraemonViewMetricsConfig.defaultConfig.opened) {\n        [self doraemonMetricsRecursiveEnable:DoraemonViewMetricsConfig.defaultConfig.enable];\n    }\n}\n\n- (void)doraemonMetricsRecursiveEnable:(BOOL)enable\n{\n    // 状态栏不显示元素边框\n    UIWindow *statusBarWindow = [[UIApplication sharedApplication] valueForKey:@\"_statusBarWindow\"];\n    if (statusBarWindow && [self isDescendantOfView:statusBarWindow]) {\n        return;\n    }\n\n    for (UIView *subView in self.subviews) {\n        [subView doraemonMetricsRecursiveEnable:enable];\n    }\n    \n    if (enable) {\n        if (!self.metricsBorderLayer) {\n            UIColor *borderColor = DoraemonViewMetricsConfig.defaultConfig.borderColor ? DoraemonViewMetricsConfig.defaultConfig.borderColor : UIColor.doraemon_randomColor;\n            self.metricsBorderLayer = ({\n                CALayer *layer = CALayer.new;\n                layer.borderWidth = DoraemonViewMetricsConfig.defaultConfig.borderWidth;\n                layer.borderColor = borderColor.CGColor;\n                layer;\n            });\n            [self.layer addSublayer:self.metricsBorderLayer];\n        }\n        \n        self.metricsBorderLayer.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);\n        self.metricsBorderLayer.hidden = NO;\n    } else if (self.metricsBorderLayer) {\n        self.metricsBorderLayer.hidden = YES;\n    }\n}\n\n- (void)setMetricsBorderLayer:(CALayer *)metricsBorderLayer\n{\n    objc_setAssociatedObject(self, @selector(metricsBorderLayer), metricsBorderLayer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);\n}\n\n- (CALayer *)metricsBorderLayer\n{\n    return objc_getAssociatedObject(self, _cmd);\n}\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/BSBacktraceLogger/DoraemonBacktraceLogger.h",
    "content": "//\n//  DoraemonBacktraceLogger.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/3/18.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n#define DoraemonBSLOG NSLog(@\"%@\",[BSBacktraceLogger bs_backtraceOfCurrentThread]);\n#define DoraemonBSLOG_MAIN NSLog(@\"%@\",[BSBacktraceLogger bs_backtraceOfMainThread]);\n#define DoraemonBSLOG_ALL NSLog(@\"%@\",[BSBacktraceLogger bs_backtraceOfAllThread]);\n\n@interface DoraemonBacktraceLogger : NSObject\n\n+ (NSString *)doraemon_backtraceOfAllThread;\n+ (NSString *)doraemon_backtraceOfCurrentThread;\n+ (NSString *)doraemon_backtraceOfMainThread;\n+ (NSString *)doraemon_backtraceOfNSThread:(NSThread *)thread;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/BSBacktraceLogger/DoraemonBacktraceLogger.m",
    "content": "//\n//  DoraemonBacktraceLogger.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/3/18.\n//\n\n#import \"DoraemonBacktraceLogger.h\"\n#import <mach/mach.h>\n#include <dlfcn.h>\n#include <pthread.h>\n#include <sys/types.h>\n#include <limits.h>\n#include <string.h>\n#include <mach-o/dyld.h>\n#include <mach-o/nlist.h>\n\n#pragma -mark DEFINE MACRO FOR DIFFERENT CPU ARCHITECTURE\n#if defined(__arm64__)\n#define Doraemon_DETAG_INSTRUCTION_ADDRESS(A) ((A) & ~(3UL))\n#define Doraemon_THREAD_STATE_COUNT ARM_THREAD_STATE64_COUNT\n#define Doraemon_THREAD_STATE ARM_THREAD_STATE64\n#define Doraemon_FRAME_POINTER __fp\n#define Doraemon_STACK_POINTER __sp\n#define Doraemon_INSTRUCTION_ADDRESS __pc\n\n#elif defined(__arm__)\n#define Doraemon_DETAG_INSTRUCTION_ADDRESS(A) ((A) & ~(1UL))\n#define Doraemon_THREAD_STATE_COUNT ARM_THREAD_STATE_COUNT\n#define Doraemon_THREAD_STATE ARM_THREAD_STATE\n#define Doraemon_FRAME_POINTER __r[7]\n#define Doraemon_STACK_POINTER __sp\n#define Doraemon_INSTRUCTION_ADDRESS __pc\n\n#elif defined(__x86_64__)\n#define Doraemon_DETAG_INSTRUCTION_ADDRESS(A) (A)\n#define Doraemon_THREAD_STATE_COUNT x86_THREAD_STATE64_COUNT\n#define Doraemon_THREAD_STATE x86_THREAD_STATE64\n#define Doraemon_FRAME_POINTER __rbp\n#define Doraemon_STACK_POINTER __rsp\n#define Doraemon_INSTRUCTION_ADDRESS __rip\n\n#elif defined(__i386__)\n#define Doraemon_DETAG_INSTRUCTION_ADDRESS(A) (A)\n#define Doraemon_THREAD_STATE_COUNT x86_THREAD_STATE32_COUNT\n#define Doraemon_THREAD_STATE x86_THREAD_STATE32\n#define Doraemon_FRAME_POINTER __ebp\n#define Doraemon_STACK_POINTER __esp\n#define Doraemon_INSTRUCTION_ADDRESS __eip\n\n#endif\n\n#define Doraemon_CALL_INSTRUCTION_FROM_RETURN_ADDRESS(A) (Doraemon_DETAG_INSTRUCTION_ADDRESS((A)) - 1)\n\n#if defined(__LP64__)\n#define Doraemon_TRACE_FMT         \"%-4d%-31s 0x%016lx %s + %lu\"\n#define Doraemon_POINTER_FMT       \"0x%016lx\"\n#define Doraemon_POINTER_SHORT_FMT \"0x%lx\"\n#define Doraemon_NLIST struct nlist_64\n#else\n#define Doraemon_TRACE_FMT         \"%-4d%-31s 0x%08lx %s + %lu\"\n#define Doraemon_POINTER_FMT       \"0x%08lx\"\n#define Doraemon_POINTER_SHORT_FMT \"0x%lx\"\n#define Doraemon_NLIST struct nlist\n#endif\n\ntypedef struct DoraemonStackFrameEntry{\n    const struct DoraemonStackFrameEntry *const previous;\n    const uintptr_t return_address;\n} DoraemonStackFrameEntry;\n\nstatic mach_port_t main_thread_id;\n\n@implementation DoraemonBacktraceLogger\n\n\n+ (void)load {\n    main_thread_id = mach_thread_self();\n}\n\n#pragma -mark Implementation of interface\n+ (NSString *)doraemon_backtraceOfNSThread:(NSThread *)thread {\n    return _doraemon_backtraceOfThread(doraemon_machThreadFromNSThread(thread));\n}\n\n+ (NSString *)doraemon_backtraceOfCurrentThread {\n    return [self doraemon_backtraceOfNSThread:[NSThread currentThread]];\n}\n\n+ (NSString *)doraemon_backtraceOfMainThread {\n    return [self doraemon_backtraceOfNSThread:[NSThread mainThread]];\n}\n\n+ (NSString *)doraemon_backtraceOfAllThread {\n    thread_act_array_t threads;\n    mach_msg_type_number_t thread_count = 0;\n    const task_t this_task = mach_task_self();\n    \n    kern_return_t kr = task_threads(this_task, &threads, &thread_count);\n    if(kr != KERN_SUCCESS) {\n        return @\"Fail to get information of all threads\";\n    }\n    \n    NSMutableString *resultString = [NSMutableString stringWithFormat:@\"Call Backtrace of %u threads:\\n\", thread_count];\n    for(int i = 0; i < thread_count; i++) {\n        [resultString appendString:_doraemon_backtraceOfThread(threads[i])];\n    }\n    return [resultString copy];\n}\n\n#pragma -mark Get call backtrace of a mach_thread\nNSString *_doraemon_backtraceOfThread(thread_t thread) {\n    uintptr_t backtraceBuffer[50];\n    int i = 0;\n    NSMutableString *resultString = [[NSMutableString alloc] initWithFormat:@\"Backtrace of Thread %u:\\n\", thread];\n    \n    _STRUCT_MCONTEXT machineContext;\n    if(!doraemon_fillThreadStateIntoMachineContext(thread, &machineContext)) {\n        return [NSString stringWithFormat:@\"Fail to get information about thread: %u\", thread];\n    }\n    \n    const uintptr_t instructionAddress = doraemon_mach_instructionAddress(&machineContext);\n    backtraceBuffer[i] = instructionAddress;\n    ++i;\n    \n    uintptr_t linkRegister = doraemon_mach_linkRegister(&machineContext);\n    if (linkRegister) {\n        backtraceBuffer[i] = linkRegister;\n        i++;\n    }\n    \n    if(instructionAddress == 0) {\n        return @\"Fail to get instruction address\";\n    }\n    \n    DoraemonStackFrameEntry frame = {0};\n    const uintptr_t framePtr = doraemon_mach_framePointer(&machineContext);\n    if(framePtr == 0 ||\n       doraemon_mach_copyMem((void *)framePtr, &frame, sizeof(frame)) != KERN_SUCCESS) {\n        return @\"Fail to get frame pointer\";\n    }\n    \n    for(; i < 50; i++) {\n        backtraceBuffer[i] = frame.return_address;\n        if(backtraceBuffer[i] == 0 ||\n           frame.previous == 0 ||\n           doraemon_mach_copyMem(frame.previous, &frame, sizeof(frame)) != KERN_SUCCESS) {\n            break;\n        }\n    }\n    \n    int backtraceLength = i;\n    Dl_info symbolicated[backtraceLength];\n    doraemon_symbolicate(backtraceBuffer, symbolicated, backtraceLength, 0);\n    for (int i = 0; i < backtraceLength; ++i) {\n        [resultString appendFormat:@\"%@\", doraemon_logBacktraceEntry(i, backtraceBuffer[i], &symbolicated[i])];\n    }\n    [resultString appendFormat:@\"\\n\"];\n    return [resultString copy];\n}\n\n#pragma -mark Convert NSThread to Mach thread\nthread_t doraemon_machThreadFromNSThread(NSThread *nsthread) {\n    char name[256];\n    mach_msg_type_number_t count;\n    thread_act_array_t list;\n    task_threads(mach_task_self(), &list, &count);\n    \n    NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970];\n    NSString *originName = [nsthread name];\n    [nsthread setName:[NSString stringWithFormat:@\"%f\", currentTimestamp]];\n    \n    if ([nsthread isMainThread]) {\n        return (thread_t)main_thread_id;\n    }\n    \n    for (int i = 0; i < count; ++i) {\n        pthread_t pt = pthread_from_mach_thread_np(list[i]);\n        if ([nsthread isMainThread]) {\n            if (list[i] == main_thread_id) {\n                return list[i];\n            }\n        }\n        if (pt) {\n            name[0] = '\\0';\n            pthread_getname_np(pt, name, sizeof name);\n            if (!strcmp(name, [nsthread name].UTF8String)) {\n                [nsthread setName:originName];\n                return list[i];\n            }\n        }\n    }\n    \n    [nsthread setName:originName];\n    return mach_thread_self();\n}\n\n#pragma -mark GenerateBacbsrackEnrty\nNSString* doraemon_logBacktraceEntry(const int entryNum,\n                               const uintptr_t address,\n                               const Dl_info* const dlInfo) {\n    char faddrBuff[20];\n    char saddrBuff[20];\n    \n    const char* fname = doraemon_lastPathEntry(dlInfo->dli_fname);\n    if(fname == NULL) {\n        sprintf(faddrBuff, Doraemon_POINTER_FMT, (uintptr_t)dlInfo->dli_fbase);\n        fname = faddrBuff;\n    }\n    \n    uintptr_t offset = address - (uintptr_t)dlInfo->dli_saddr;\n    const char* sname = dlInfo->dli_sname;\n    if(sname == NULL) {\n        sprintf(saddrBuff, Doraemon_POINTER_SHORT_FMT, (uintptr_t)dlInfo->dli_fbase);\n        sname = saddrBuff;\n        offset = address - (uintptr_t)dlInfo->dli_fbase;\n    }\n    return [NSString stringWithFormat:@\"%-30s  0x%08\" PRIxPTR \" %s + %lu\\n\" ,fname, (uintptr_t)address, sname, offset];\n}\n\nconst char* doraemon_lastPathEntry(const char* const path) {\n    if(path == NULL) {\n        return NULL;\n    }\n    \n    char* lastFile = strrchr(path, '/');\n    return lastFile == NULL ? path : lastFile + 1;\n}\n\n#pragma -mark HandleMachineContext\nbool doraemon_fillThreadStateIntoMachineContext(thread_t thread, _STRUCT_MCONTEXT *machineContext) {\n    mach_msg_type_number_t state_count = Doraemon_THREAD_STATE_COUNT;\n    kern_return_t kr = thread_get_state(thread, Doraemon_THREAD_STATE, (thread_state_t)&machineContext->__ss, &state_count);\n    return (kr == KERN_SUCCESS);\n}\n\nuintptr_t doraemon_mach_framePointer(mcontext_t const machineContext){\n    return machineContext->__ss.Doraemon_FRAME_POINTER;\n}\n\nuintptr_t doraemon_mach_stackPointer(mcontext_t const machineContext){\n    return machineContext->__ss.Doraemon_STACK_POINTER;\n}\n\nuintptr_t doraemon_mach_instructionAddress(mcontext_t const machineContext){\n    return machineContext->__ss.Doraemon_INSTRUCTION_ADDRESS;\n}\n\nuintptr_t doraemon_mach_linkRegister(mcontext_t const machineContext){\n#if defined(__i386__) || defined(__x86_64__)\n    return 0;\n#else\n    return machineContext->__ss.__lr;\n#endif\n}\n\nkern_return_t doraemon_mach_copyMem(const void *const src, void *const dst, const size_t numBytes){\n    vm_size_t bytesCopied = 0;\n    return vm_read_overwrite(mach_task_self(), (vm_address_t)src, (vm_size_t)numBytes, (vm_address_t)dst, &bytesCopied);\n}\n\n#pragma -mark Symbolicate\nvoid doraemon_symbolicate(const uintptr_t* const backtraceBuffer,\n                    Dl_info* const symbolsBuffer,\n                    const int numEntries,\n                    const int skippedEntries){\n    int i = 0;\n    \n    if(!skippedEntries && i < numEntries) {\n        doraemon_dladdr(backtraceBuffer[i], &symbolsBuffer[i]);\n        i++;\n    }\n    \n    for(; i < numEntries; i++) {\n        doraemon_dladdr(Doraemon_CALL_INSTRUCTION_FROM_RETURN_ADDRESS(backtraceBuffer[i]), &symbolsBuffer[i]);\n    }\n}\n\nbool doraemon_dladdr(const uintptr_t address, Dl_info* const info) {\n    info->dli_fname = NULL;\n    info->dli_fbase = NULL;\n    info->dli_sname = NULL;\n    info->dli_saddr = NULL;\n    \n    const uint32_t idx = doraemon_imageIndexContainingAddress(address);\n    if(idx == UINT_MAX) {\n        return false;\n    }\n    const struct mach_header* header = _dyld_get_image_header(idx);\n    const uintptr_t imageVMAddrSlide = (uintptr_t)_dyld_get_image_vmaddr_slide(idx);\n    const uintptr_t addressWithSlide = address - imageVMAddrSlide;\n    const uintptr_t segmentBase = doraemon_segmentBaseOfImageIndex(idx) + imageVMAddrSlide;\n    if(segmentBase == 0) {\n        return false;\n    }\n    \n    info->dli_fname = _dyld_get_image_name(idx);\n    info->dli_fbase = (void*)header;\n    \n    // Find symbol tables and get whichever symbol is closest to the address.\n    const Doraemon_NLIST* bestMatch = NULL;\n    uintptr_t bestDistance = ULONG_MAX;\n    uintptr_t cmdPtr = doraemon_firstCmdAfterHeader(header);\n    if(cmdPtr == 0) {\n        return false;\n    }\n    for(uint32_t iCmd = 0; iCmd < header->ncmds; iCmd++) {\n        const struct load_command* loadCmd = (struct load_command*)cmdPtr;\n        if(loadCmd->cmd == LC_SYMTAB) {\n            const struct symtab_command* symtabCmd = (struct symtab_command*)cmdPtr;\n            const Doraemon_NLIST* symbolTable = (Doraemon_NLIST*)(segmentBase + symtabCmd->symoff);\n            const uintptr_t stringTable = segmentBase + symtabCmd->stroff;\n            \n            for(uint32_t iSym = 0; iSym < symtabCmd->nsyms; iSym++) {\n                // If n_value is 0, the symbol refers to an external object.\n                if(symbolTable[iSym].n_value != 0) {\n                    uintptr_t symbolBase = symbolTable[iSym].n_value;\n                    uintptr_t currentDistance = addressWithSlide - symbolBase;\n                    if((addressWithSlide >= symbolBase) &&\n                       (currentDistance <= bestDistance)) {\n                        bestMatch = symbolTable + iSym;\n                        bestDistance = currentDistance;\n                    }\n                }\n            }\n            if(bestMatch != NULL) {\n                info->dli_saddr = (void*)(bestMatch->n_value + imageVMAddrSlide);\n                info->dli_sname = (char*)((intptr_t)stringTable + (intptr_t)bestMatch->n_un.n_strx);\n                if(*info->dli_sname == '_') {\n                    info->dli_sname++;\n                }\n                // This happens if all symbols have been stripped.\n                if(info->dli_saddr == info->dli_fbase && bestMatch->n_type == 3) {\n                    info->dli_sname = NULL;\n                }\n                break;\n            }\n        }\n        cmdPtr += loadCmd->cmdsize;\n    }\n    return true;\n}\n\nuintptr_t doraemon_firstCmdAfterHeader(const struct mach_header* const header) {\n    switch(header->magic) {\n        case MH_MAGIC:\n        case MH_CIGAM:\n            return (uintptr_t)(header + 1);\n        case MH_MAGIC_64:\n        case MH_CIGAM_64:\n            return (uintptr_t)(((struct mach_header_64*)header) + 1);\n        default:\n            return 0;  // Header is corrupt\n    }\n}\n\nuint32_t doraemon_imageIndexContainingAddress(const uintptr_t address) {\n    const uint32_t imageCount = _dyld_image_count();\n    const struct mach_header* header = 0;\n    \n    for(uint32_t iImg = 0; iImg < imageCount; iImg++) {\n        header = _dyld_get_image_header(iImg);\n        if(header != NULL) {\n            // Look for a segment command with this address within its range.\n            uintptr_t addressWSlide = address - (uintptr_t)_dyld_get_image_vmaddr_slide(iImg);\n            uintptr_t cmdPtr = doraemon_firstCmdAfterHeader(header);\n            if(cmdPtr == 0) {\n                continue;\n            }\n            for(uint32_t iCmd = 0; iCmd < header->ncmds; iCmd++) {\n                const struct load_command* loadCmd = (struct load_command*)cmdPtr;\n                if(loadCmd->cmd == LC_SEGMENT) {\n                    const struct segment_command* segCmd = (struct segment_command*)cmdPtr;\n                    if(addressWSlide >= segCmd->vmaddr &&\n                       addressWSlide < segCmd->vmaddr + segCmd->vmsize) {\n                        return iImg;\n                    }\n                }\n                else if(loadCmd->cmd == LC_SEGMENT_64) {\n                    const struct segment_command_64* segCmd = (struct segment_command_64*)cmdPtr;\n                    if(addressWSlide >= segCmd->vmaddr &&\n                       addressWSlide < segCmd->vmaddr + segCmd->vmsize) {\n                        return iImg;\n                    }\n                }\n                cmdPtr += loadCmd->cmdsize;\n            }\n        }\n    }\n    return UINT_MAX;\n}\n\nuintptr_t doraemon_segmentBaseOfImageIndex(const uint32_t idx) {\n    const struct mach_header* header = _dyld_get_image_header(idx);\n    \n    // Look for a segment command and return the file image address.\n    uintptr_t cmdPtr = doraemon_firstCmdAfterHeader(header);\n    if(cmdPtr == 0) {\n        return 0;\n    }\n    for(uint32_t i = 0;i < header->ncmds; i++) {\n        const struct load_command* loadCmd = (struct load_command*)cmdPtr;\n        if(loadCmd->cmd == LC_SEGMENT) {\n            const struct segment_command* segmentCmd = (struct segment_command*)cmdPtr;\n            if(strcmp(segmentCmd->segname, SEG_LINKEDIT) == 0) {\n                return segmentCmd->vmaddr - segmentCmd->fileoff;\n            }\n        }\n        else if(loadCmd->cmd == LC_SEGMENT_64) {\n            const struct segment_command_64* segmentCmd = (struct segment_command_64*)cmdPtr;\n            if(strcmp(segmentCmd->segname, SEG_LINKEDIT) == 0) {\n                return (uintptr_t)(segmentCmd->vmaddr - segmentCmd->fileoff);\n            }\n        }\n        cmdPtr += loadCmd->cmdsize;\n    }\n    return 0;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/BuriedPoint/DoraemonBuriedPointManager.h",
    "content": "//\n//  DoraemonBuriedPointManager.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/2/23.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n#define DoKitBP(name) [[DoraemonBuriedPointManager shareManager] addPointName:name];\n\n@interface DoraemonBuriedPointManager : NSObject\n\n+ (DoraemonBuriedPointManager *)shareManager;\n- (void)addPointName:(NSString *)name;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/BuriedPoint/DoraemonBuriedPointManager.m",
    "content": "//\n//  DoraemonBuriedPointManager.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/2/23.\n//\n\n#import \"DoraemonBuriedPointManager.h\"\n#import \"DoraemonUtil.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonAppInfoUtil.h\"\n#import \"DoraemonNetworkUtil.h\"\n#import \"DoraemonManager.h\"\n\n@interface DoraemonBuriedPointManager()\n\n@property (nonatomic, strong) NSMutableArray *pointArray;\n@property (nonatomic, strong) NSMutableDictionary *basicInfoDic;\n@property (nonatomic, assign) NSTimeInterval nowTime;\n@property (nonatomic, assign) NSInteger count;\n@property (nonatomic, assign) NSInteger timeOut;\n\n\n@end\n\n@implementation DoraemonBuriedPointManager\n\n+ (DoraemonBuriedPointManager *)shareManager{\n    static dispatch_once_t once;\n    static DoraemonBuriedPointManager *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonBuriedPointManager alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    if (self = [super init]) {\n        self.count = 10;//暂定的10\n        self.timeOut = 60;//时隔60s\n        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillEnterBackgroundNotification:) name:UIApplicationDidEnterBackgroundNotification object:nil];\n    }\n    return self;\n}\n\n- (NSMutableArray *)pointArray{\n    if(nil == _pointArray){\n        _pointArray = [[NSMutableArray alloc] init];\n    }\n    return _pointArray;\n}\n\n- (NSMutableDictionary *)basicInfoDic{\n    if(nil == _basicInfoDic){\n        _basicInfoDic = [NSMutableDictionary dictionary];\n        \n        [_basicInfoDic setValue:STRING_NOT_NULL([DoraemonManager shareInstance].pId) forKey:@\"pId\"];\n        [_basicInfoDic setValue:@\"iOS\" forKey:@\"platform\"];\n        [_basicInfoDic setValue:[DoraemonUtil currentTimeInterval] forKey:@\"time\"];\n        [_basicInfoDic setValue:[DoraemonAppInfoUtil iphoneType] forKey:@\"phoneModel\"];\n        [_basicInfoDic setValue:[DoraemonAppInfoUtil iphoneSystemVersion] forKey:@\"systemVersion\"];\n        [_basicInfoDic setValue:[DoraemonAppInfoUtil appName] forKey:@\"appName\"];\n        [_basicInfoDic setValue:[DoraemonAppInfoUtil bundleIdentifier] forKey:@\"appId\"];\n        [_basicInfoDic setValue:DoKitVersion forKey:@\"dokitVersion\"];\n        \n        NSString *currentLanguageRegion = [[[NSUserDefaults standardUserDefaults] objectForKey:@\"AppleLanguages\"] firstObject];\n        [_basicInfoDic setValue:STRING_NOT_NULL(currentLanguageRegion) forKey:@\"language\"];//语言这一块主要是为了统计国内外用户分布\n    }\n    return _basicInfoDic;\n}\n\n- (void)addPointName:(NSString *)name{\n    if (name.length<1) {\n        return;\n    }\n    NSDictionary *dic = @{\n        @\"eventName\":STRING_NOT_NULL(name),\n        @\"time\":[DoraemonUtil currentTimeInterval]\n    };\n    [self.pointArray addObject:dic];\n    \n    if([self needUpload]){\n        [self uploadData];\n    }\n}\n\n- (void)uploadData{\n    if(self.pointArray.count > 0){\n        [self.basicInfoDic setValue:self.pointArray forKey:@\"events\"];\n        NSMutableDictionary *params = [self.basicInfoDic copy];\n\n        [DoraemonNetworkUtil postWithUrlString:@\"https://www.dokit.cn/pointData/addPointData\" params:params success:^(NSDictionary * _Nonnull result) {\n            NSInteger code = [result[@\"code\"] integerValue];\n            if (code == 200) {\n                [self removePointArray];\n            }\n        } error:^(NSError * _Nonnull error){\n            \n        }];\n    }\n}\n\n\n- (void)removePointArray{\n    if(self.pointArray.count > 0){\n        [self.pointArray removeAllObjects];\n    }\n}\n\n- (BOOL)needUpload{\n    //1、数据列表大于10条(暂定)时上传\n    if (self.pointArray.count > self.count) {\n        return YES;\n    }else if(self.pointArray.count > 2){\n        //2、条数据之间的时间大于60s(暂定)时上传\n        NSInteger count = self.pointArray.count;\n        NSDictionary *lastItem = self.pointArray[count-1];\n        NSDictionary *lastSecondItem = self.pointArray[count-2];\n        NSInteger lastItemTime = [lastItem[@\"time\"] integerValue];\n        NSInteger lastSecondItemTime = [lastSecondItem[@\"time\"] integerValue];\n        if (lastItemTime - lastSecondItemTime > self.timeOut*1000) {\n            return YES;\n        }\n    }\n\n    return NO;\n}\n\n//3、app 切换到后台时 上传\n- (void)appWillEnterBackgroundNotification:(UIApplication *)application{\n    [self uploadData];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/DoraemonNetworkUtil.h",
    "content": "//\n//  DoraemonNetworkUtil.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/2/28.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\ntypedef void (^DoraemonNetworkSucceedCallback)(NSDictionary *result);\ntypedef void (^DoraemonNetworkFailureCallback)(NSError *error);\n\n#define DoraemonNetworkUtilUrl\n\n@interface DoraemonNetworkUtil : NSObject\n// get 请求\n+ (void)getWithUrlString:(NSString *)url params:(NSDictionary *)params success:(DoraemonNetworkSucceedCallback)successAction\nerror:(DoraemonNetworkFailureCallback)errorAction;\n\n// post 请求\n+ (void)postWithUrlString:(NSString *)url params:(NSDictionary *)params success:(DoraemonNetworkSucceedCallback)successAction\nerror:(DoraemonNetworkFailureCallback)errorAction;\n\n// patch请求\n+ (void)patchWithUrlString:(NSString *)url params:(NSDictionary *)params success:(DoraemonNetworkSucceedCallback)successAction error:(DoraemonNetworkFailureCallback)errorAction;\n\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/DoraemonNetworkUtil.m",
    "content": "//\n//  DoraemonNetworkUtil.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/2/28.\n//\n\n#import \"DoraemonNetworkUtil.h\"\n\n@interface DoraemonNetworkUtil()<NSURLSessionDelegate>\n\n@end\n\n@implementation DoraemonNetworkUtil\n\n+ (nonnull DoraemonNetworkUtil *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonNetworkUtil *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonNetworkUtil alloc] init];\n    });\n    return instance;\n}\n\n+ (void)requestURL:(NSString *)url method:(NSString *)method param:(NSDictionary *)param success:(DoraemonNetworkSucceedCallback)successAction\n             error:(DoraemonNetworkFailureCallback)errorAction{\n    NSError *error;\n    if (!param) {\n        param = @{};\n    }\n    NSData *postData = [NSJSONSerialization dataWithJSONObject:param options:0 error:&error];\n    if (!error) {\n        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];\n        request.HTTPMethod = method;\n        [request addValue:@\"application/json\" forHTTPHeaderField:@\"Content-Type\"];\n        [request addValue:@\"application/json\" forHTTPHeaderField:@\"Accept\"];\n        [request setHTTPBody:postData];\n        \n        NSURLSession *session = [NSURLSession sharedSession];\n        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {\n            dispatch_async(dispatch_get_main_queue(), ^{\n                if (error) {\n                    errorAction(error);\n                }else{\n                    NSError *error2;\n                    id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error2];\n                    if (!error2) {\n                        successAction(result);\n                    }\n                }\n            });\n        }];\n        [task resume];\n    }\n}\n\n// get 请求\n+ (void)getWithUrlString:(NSString *)url params:(NSDictionary *)params success:(DoraemonNetworkSucceedCallback)successAction\n                   error:(DoraemonNetworkFailureCallback)errorAction{\n    NSMutableString *mutableUrl = [[NSMutableString alloc] initWithString:url];\n    if ([params allKeys].count>0) {\n        [mutableUrl appendString:@\"?\"];\n        for (id key in params) {\n            NSString *value = [[params objectForKey:key] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];\n            [mutableUrl appendString:[NSString stringWithFormat:@\"%@=%@&\", key, value]];\n        }\n        mutableUrl = [[mutableUrl substringToIndex:mutableUrl.length - 1] mutableCopy];\n    }\n    NSString *urlEnCode = [mutableUrl stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];\n    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlEnCode]];\n    NSOperationQueue *queue = [[NSOperationQueue alloc] init];\n    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];\n    NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:[self shareInstance] delegateQueue:queue];\n    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {\n        dispatch_async(dispatch_get_main_queue(), ^{\n            if (error) {\n                errorAction(error);\n            } else {\n                NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];\n                successAction(dic);\n            }\n        });\n    }];\n    [dataTask resume];\n}\n\n// post 请求\n+ (void)postWithUrlString:(NSString *)url params:(NSDictionary *)params success:(DoraemonNetworkSucceedCallback)successAction\n                    error:(DoraemonNetworkFailureCallback)errorAction{\n    [DoraemonNetworkUtil requestURL:url method:@\"POST\" param:params success:successAction error:errorAction];\n}\n\n// patch请求\n+ (void)patchWithUrlString:(NSString *)url params:(NSDictionary *)params success:(DoraemonNetworkSucceedCallback)successAction error:(DoraemonNetworkFailureCallback)errorAction{\n    [DoraemonNetworkUtil requestURL:url method:@\"PATCH\" param:params success:successAction error:errorAction];\n}\n\n\n\n#pragma mark - NSURLSessionDelegate 代理方法\n\n//主要就是处理HTTPS请求的\n- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler\n{\n    NSURLProtectionSpace *protectionSpace = challenge.protectionSpace;\n    if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {\n        SecTrustRef serverTrust = protectionSpace.serverTrust;\n        completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:serverTrust]);\n    } else {\n        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/DoraemonStatisticsUtil.h",
    "content": "//\n//  DoraemonStatisticsUtil.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/10.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonStatisticsUtil : NSObject\n\n+ (nonnull DoraemonStatisticsUtil *)shareInstance;\n\n@property (nonatomic, assign) BOOL noUpLoad;\n\n//统计用户信息，便于了解该开源产品的使用量 (请大家放心，我们不用于任何恶意行为)\n- (void)upLoadUserInfo;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/DoraemonStatisticsUtil.m",
    "content": "//\n//  DoraemonStatisticsUtil.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/10.\n//\n\n#import \"DoraemonStatisticsUtil.h\"\n#import \"DoraemonDefine.h\"\n\n@implementation DoraemonStatisticsUtil\n\n+ (nonnull DoraemonStatisticsUtil *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonStatisticsUtil *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonStatisticsUtil alloc] init];\n    });\n    return instance;\n}\n\n- (void)upLoadUserInfo{\n    if (_noUpLoad) {\n        return;\n    }\n    \n    NSURL *url = [NSURL URLWithString:@\"https://doraemon.xiaojukeji.com/uploadAppData\"];\n    \n    NSString *appId = [DoraemonAppInfoUtil bundleIdentifier];\n    NSString *appName = [DoraemonAppInfoUtil appName];\n    NSString *doKitVersion = DoKitVersion;\n    NSString *type = @\"iOS\";\n    NSString *from = @\"1\";\n    NSString *currentLanguageRegion = [[[NSUserDefaults standardUserDefaults] objectForKey:@\"AppleLanguages\"] firstObject];\n    \n    NSMutableDictionary *param = [[NSMutableDictionary alloc] init];\n    [param setValue:appId forKey:@\"appId\"];\n    [param setValue:appName forKey:@\"appName\"];\n    [param setValue:doKitVersion forKey:@\"version\"];\n    [param setValue:type forKey:@\"type\"];\n    [param setValue:from forKey:@\"from\"];\n    [param setValue:STRING_NOT_NULL(currentLanguageRegion) forKey:@\"language\"];//用于区分用户国家\n    NSError *error;\n    NSData *postData = [NSJSONSerialization dataWithJSONObject:param options:0 error:&error];\n    \n    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];\n    request.HTTPMethod = @\"POST\";\n    [request addValue:@\"application/json\" forHTTPHeaderField:@\"Content-Type\"];\n    [request addValue:@\"application/json\" forHTTPHeaderField:@\"Accept\"];\n    [request setHTTPBody:postData];\n    \n    NSURLSession *session = [NSURLSession sharedSession];\n    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {\n//        if (error) {\n//            NSLog(@\"%@\",error);\n//        }else{\n//            NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];\n//            NSLog(@\"%@\",str);\n//        }\n    }];\n    [task resume];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/DoraemonThreadSafeMutableArray.h",
    "content": "//\n//  DoraemonThreadSafeMutableArray.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/6.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonThreadSafeMutableArray : NSMutableArray\n\n- (instancetype)init;\n- (instancetype)initWithCapacity:(NSUInteger)numItems;\n- (instancetype)initWithArray:(NSArray *)array;\n- (instancetype)initWithCoder:(NSCoder *)aDecoder;\n\n- (NSUInteger)count;\n- (id)objectAtIndex:(NSUInteger)index;\n- (id)objectAtIndexedSubscript:(NSUInteger)index;\n- (id)firstObject;\n- (id)lastObject;\n- (BOOL)containsObject:(id)anObject;\n- (NSEnumerator *)objectEnumerator;\n- (NSEnumerator *)reverseObjectEnumerator;\n- (void)insertObject:(id)anObject atIndex:(NSUInteger)index;\n- (void)setObject:(id)anObject atIndexedSubscript:(NSUInteger)index;\n- (void)addObject:(id)anObject;\n- (void)removeObject:(id)anObject;\n- (void)removeObjectAtIndex:(NSUInteger)index;\n- (void)removeLastObject;\n- (void)removeAllObjects;\n- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject;\n- (NSUInteger)indexOfObject:(id)anObject;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/DoraemonThreadSafeMutableArray.m",
    "content": "//\n//  DoraemonThreadSafeMutableArray.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/6.\n//\n\n#import \"DoraemonThreadSafeMutableArray.h\"\n#import <pthread/pthread.h>\n\n@interface DoraemonThreadSafeMutableArray(){\n    NSMutableArray* _array;\n    pthread_mutex_t _safeThreadArrayMutex;\n    pthread_mutexattr_t _safeThreadArrayMutexAttr;\n}\n\n@end\n\n@implementation DoraemonThreadSafeMutableArray\n\n- (instancetype)initCommon\n{\n    self = [super init];\n    if (self) {\n        pthread_mutexattr_init(&(_safeThreadArrayMutexAttr));\n        pthread_mutexattr_settype(&(_safeThreadArrayMutexAttr), PTHREAD_MUTEX_RECURSIVE); // must use recursive lock\n        pthread_mutex_init(&(_safeThreadArrayMutex), &(_safeThreadArrayMutexAttr));\n    }\n    return self;\n}\n\n- (instancetype)init\n{\n    self = [self initCommon];\n    if (self) {\n        _array = [NSMutableArray array];\n    }\n    return self;\n}\n\n- (instancetype)initWithCapacity:(NSUInteger)numItems\n{\n    self = [self initCommon];\n    if (self) {\n        _array = [NSMutableArray arrayWithCapacity:numItems];\n    }\n    return self;\n}\n\n- (instancetype)initWithArray:(NSArray *)array\n{\n    self = [self initCommon];\n    if (self) {\n        _array = [NSMutableArray arrayWithArray:array];\n    }\n    return self;\n}\n\n- (instancetype)initWithCoder:(NSCoder *)aDecoder\n{\n    self = [self initCommon];\n    if (self) {\n        _array = [[NSMutableArray alloc] initWithCoder:aDecoder];\n    }\n    return self;\n}\n\n- (instancetype)initWithObjects:(const id [])objects count:(NSUInteger)cnt\n{\n    self = [self initCommon];\n    if (self) {\n        _array = [NSMutableArray array];\n        for (NSUInteger i = 0; i < cnt; ++i) {\n            [_array addObject:objects[i]];\n        }\n    }\n    return self;\n}\n\n- (NSUInteger)count\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        return [_array count];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (id)objectAtIndex:(NSUInteger)index\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        return [_array objectAtIndex:index];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (id)objectAtIndexedSubscript:(NSUInteger)index\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        return [_array objectAtIndexedSubscript:index];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (id)firstObject\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        return [_array firstObject];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (id)lastObject\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        return [_array lastObject];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (BOOL)containsObject:(id)anObject\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        return [_array containsObject:anObject];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (NSEnumerator *)objectEnumerator\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        return [_array objectEnumerator];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (NSEnumerator *)reverseObjectEnumerator\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        return [_array reverseObjectEnumerator];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (void)insertObject:(id)anObject atIndex:(NSUInteger)index\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        [_array insertObject:anObject atIndex:index];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (void)setObject:(id)anObject atIndexedSubscript:(NSUInteger)index\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        [_array setObject:anObject atIndexedSubscript:index];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (void)addObject:(id)anObject\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        [_array addObject:anObject];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (void)removeObject:(id)anObject\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        [_array removeObject:anObject];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (void)removeObjectAtIndex:(NSUInteger)index\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        [_array removeObjectAtIndex:index];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (void)removeLastObject\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        [_array removeLastObject];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (void)removeAllObjects\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        [_array removeAllObjects];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        [_array replaceObjectAtIndex:index withObject:anObject];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (NSUInteger)indexOfObject:(id)anObject\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        return [_array indexOfObject:anObject];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (id)copy\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadArrayMutex);\n        return [_array copy];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadArrayMutex);\n    }\n}\n\n- (void)dealloc\n{\n    pthread_mutex_destroy(&_safeThreadArrayMutex);\n    pthread_mutexattr_destroy(&_safeThreadArrayMutexAttr);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/DoraemonThreadSafeMutableDictionary.h",
    "content": "//\n//  DoraemonThreadSafeMutableDictionary.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/6.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonThreadSafeMutableDictionary<KeyType, ObjectType> : NSMutableDictionary\n\n- (instancetype)init;\n- (instancetype)initWithCapacity:(NSUInteger)numItems;\n- (instancetype)initWithDictionary:(NSDictionary *)dictionary;\n- (instancetype)initWithCoder:(NSCoder *)aDecoder;\n\n- (NSUInteger)count;\n- (id)objectForKey:(id)key;\n- (id)objectForKeyedSubscript:(id)key;\n- (NSEnumerator *)keyEnumerator;\n- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey;\n- (NSArray *)allKeys;\n- (NSArray *)allValues;\n- (void)removeObjectForKey:(id)aKey;\n- (void)removeAllObjects;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/DoraemonThreadSafeMutableDictionary.m",
    "content": "//\n//  DoraemonThreadSafeMutableDictionary.m\n//  DoraemonKit\n//\n//  Created by didi on 2020/1/6.\n//\n\n#import \"DoraemonThreadSafeMutableDictionary.h\"\n#import <pthread/pthread.h>\n\n@interface DoraemonThreadSafeMutableDictionary(){\n    NSMutableDictionary* _dict;\n    pthread_mutex_t _safeThreadDictionaryMutex;\n    pthread_mutexattr_t _safeThreadDictionaryMutexAttr;\n}\n\n@end\n\n@implementation DoraemonThreadSafeMutableDictionary\n\n- (instancetype)initCommon\n{\n    self = [super init];\n    if (self) {\n        pthread_mutexattr_init(&(_safeThreadDictionaryMutexAttr));\n        pthread_mutexattr_settype(&(_safeThreadDictionaryMutexAttr), PTHREAD_MUTEX_RECURSIVE); // must use recursive lock\n        pthread_mutex_init(&(_safeThreadDictionaryMutex), &(_safeThreadDictionaryMutexAttr));\n    }\n    return self;\n}\n\n- (instancetype)init\n{\n    self = [self initCommon];\n    if (self) {\n        _dict = [NSMutableDictionary dictionary];\n    }\n    return self;\n}\n\n- (instancetype)initWithCapacity:(NSUInteger)numItems\n{\n    self = [self initCommon];\n    if (self) {\n        _dict = [NSMutableDictionary dictionaryWithCapacity:numItems];\n    }\n    return self;\n}\n\n- (instancetype)initWithDictionary:(NSDictionary *)dictionary\n{\n    self = [self initCommon];\n    if (self) {\n        _dict = [NSMutableDictionary dictionaryWithDictionary:dictionary];\n    }\n    return self;\n}\n\n- (instancetype)initWithCoder:(NSCoder *)aDecoder\n{\n    self = [self initCommon];\n    if (self) {\n        _dict = [[NSMutableDictionary alloc] initWithCoder:aDecoder];\n    }\n    return self;\n}\n\n- (instancetype)initWithObjects:(const id [])objects forKeys:(const id<NSCopying> [])keys count:(NSUInteger)cnt\n{\n    self = [self initCommon];\n    if (self) {\n        _dict = [NSMutableDictionary dictionary];\n        for (NSUInteger i = 0; i < cnt; ++i) {\n            _dict[keys[i]] = objects[i];\n        }\n        \n    }\n    return self;\n}\n\n- (NSUInteger)count\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadDictionaryMutex);\n        return [_dict count];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadDictionaryMutex);\n    }\n}\n\n- (id)objectForKey:(id)key\n{\n    if (nil == key) {\n        return nil;\n    }\n    \n    @try {\n        pthread_mutex_lock(&_safeThreadDictionaryMutex);\n        return [_dict objectForKey:key];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadDictionaryMutex);\n    }\n}\n\n- (id)objectForKeyedSubscript:(id)key\n{\n    if (nil == key) {\n        return nil;\n    }\n    \n    @try {\n        pthread_mutex_lock(&_safeThreadDictionaryMutex);\n        return [_dict objectForKeyedSubscript:key];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadDictionaryMutex);\n    }\n}\n\n- (NSEnumerator *)keyEnumerator\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadDictionaryMutex);\n        return [_dict keyEnumerator];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadDictionaryMutex);\n    }\n}\n\n- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey\n{\n    id originalObject = nil; // make sure that object is not released in lock\n    @try {\n        pthread_mutex_lock(&_safeThreadDictionaryMutex);\n        originalObject = [_dict objectForKey:aKey];\n        [_dict setObject:anObject forKey:aKey];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadDictionaryMutex);\n    }\n    originalObject = nil;\n}\n\n- (void)setObject:(id)anObject forKeyedSubscript:(id <NSCopying>)key\n{\n    id originalObject = nil; // make sure that object is not released in lock\n    @try {\n        pthread_mutex_lock(&_safeThreadDictionaryMutex);\n        originalObject = [_dict objectForKey:key];\n        [_dict setObject:anObject forKeyedSubscript:key];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadDictionaryMutex);\n    }\n    originalObject = nil;\n}\n\n- (NSArray *)allKeys\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadDictionaryMutex);\n        return [_dict allKeys];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadDictionaryMutex);\n    }\n}\n\n- (NSArray *)allValues\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadDictionaryMutex);\n        return [_dict allValues];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadDictionaryMutex);\n    }\n}\n\n- (void)removeObjectForKey:(id)aKey\n{\n    id originalObject = nil; // make sure that object is not released in lock\n    @try {\n        pthread_mutex_lock(&_safeThreadDictionaryMutex);\n        originalObject = [_dict objectForKey:aKey];\n        if (originalObject) {\n            [_dict removeObjectForKey:aKey];\n        }\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadDictionaryMutex);\n    }\n    originalObject = nil;\n}\n\n- (void)removeAllObjects\n{\n    NSArray* allValues = nil; // make sure that objects are not released in lock\n    @try {\n        pthread_mutex_lock(&_safeThreadDictionaryMutex);\n        allValues = [_dict allValues];\n        [_dict removeAllObjects];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadDictionaryMutex);\n    }\n    allValues = nil;\n}\n\n- (id)copy\n{\n    @try {\n        pthread_mutex_lock(&_safeThreadDictionaryMutex);\n        return [_dict copy];\n    }\n    @finally {\n        pthread_mutex_unlock(&_safeThreadDictionaryMutex);\n    }\n}\n\n- (void)dealloc\n{\n    pthread_mutex_destroy(&_safeThreadDictionaryMutex);\n    pthread_mutexattr_destroy(&_safeThreadDictionaryMutexAttr);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/DoraemonUtil.h",
    "content": "//\n//  DoraemonUtil.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\n@interface DoraemonUtil : NSObject\n\n@property (nonatomic, assign) NSInteger fileSize;\n@property (nonatomic, strong) NSMutableArray *bigFileArray;\n\n+ (NSString *)dateFormatTimeInterval:(NSTimeInterval)timeInterval;\n\n+ (NSString *)dateFormatNSDate:(NSDate *)date;\n\n+ (NSString *)dateFormatNow;\n\n// byte格式化为 B KB MB 方便流量查看\n+ (NSString *)formatByte:(CGFloat)byte;\n\n+ (void)savePerformanceDataInFile:(NSString *)fileName data:(NSString *)data;\n\n+ (NSString *)dictToJsonStr:(NSDictionary *)dict;\n\n+ (NSString *)arrayToJsonStr:(NSArray *)array;\n\n+ (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString;\n\n+ (NSArray *)arrayWithJsonString:(NSString *)jsonString;\n\n+ (NSString *)formatTimeIntervalToMS:(NSTimeInterval)timeInterval;\n\n+ (NSString *)currentTimeInterval;\n\n//获取某一条文件路径的文件大小\n- (void)getFileSizeWithPath:(NSString *)path;\n\n- (NSArray *)getBigSizeFileFormPath:(NSString *)path;\n\n//删除某一路径下的所有文件\n+ (void)clearFileWithPath:(NSString *)path;\n\n+ (void)clearLocalDatas;\n\n+ (void)shareText:(NSString *)text formVC:(UIViewController *)vc;//share text\n+ (void)shareImage:(UIImage *)image formVC:(UIViewController *)vc;//share image\n+ (void)shareURL:(NSURL *)url formVC:(UIViewController *)vc;//share url\n\n+ (void)openAppSetting;\n\n+ (UIWindow *)getKeyWindow;\n\n+ (NSArray *)getWebViews;\n\n+ (void)openPlugin:(UIViewController *)vc __attribute__((deprecated(\"此方法已弃用,请使用[DoraemonHomeWindow openPlugin:vc];\")));\n\n+ (UIViewController *)rootViewControllerForKeyWindow __attribute__((deprecated(\"此方法已弃用,请使用[UIViewController rootViewControllerForKeyWindow]\")));\n\n+ (UIViewController *)topViewControllerForKeyWindow __attribute__((deprecated(\"此方法已弃用,请使用[UIViewController topViewControllerForKeyWindow]\")));\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/DoraemonUtil.m",
    "content": "//\n//  DoraemonUtil.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/11.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonUtil.h\"\n#import <WebKit/WebKit.h>\n#import \"UIViewController+Doraemon.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonAppInfoUtil.h\"\n#import \"DoraemonDefine.h\"\n\n@implementation DoraemonUtil\n\n- (instancetype)init{\n    self = [super init];\n    if (self) {\n        _fileSize = 0;\n        _bigFileArray = [[NSMutableArray alloc] init];\n    }\n    return self;\n}\n\n+ (NSString *)dateFormatTimeInterval:(NSTimeInterval)timeInterval{\n    NSDate *date = [NSDate dateWithTimeIntervalSince1970:timeInterval];\n    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];\n    [formatter setDateFormat:@\"yyyy-MM-dd HH:mm:ss\"];\n    NSString *dateString = [formatter stringFromDate: date];\n    return dateString;\n}\n\n+ (NSString *)dateFormatNSDate:(NSDate *)date{\n    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];\n    [formatter setDateFormat:@\"yyyy-MM-dd HH:mm:ss\"];\n    NSString *dateString = [formatter stringFromDate: date];\n    return dateString;\n}\n\n+ (NSString *)dateFormatNow{\n    NSDate *date = [NSDate date];\n    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];\n    [formatter setDateFormat:@\"yyyy-MM-dd HH:mm:ss\"];\n    NSString *dateString = [formatter stringFromDate: date];\n    return dateString;\n}\n\n// byte格式化为 B KB MB 方便流量查看\n+ (NSString *)formatByte:(CGFloat)byte{\n    double convertedValue = byte;\n    int multiplyFactor = 0;\n    NSArray *tokens = [NSArray arrayWithObjects:@\"B\",@\"KB\",@\"MB\",@\"GB\",@\"TB\",nil];\n    \n    while (convertedValue > 1024) {\n        convertedValue /= 1024;\n        multiplyFactor++;\n    }\n    return [NSString stringWithFormat:@\"%4.2f%@\",convertedValue, [tokens objectAtIndex:multiplyFactor]]; ;\n}\n\n+ (NSString *)formatTimeIntervalToMS:(NSTimeInterval)timeInterval{\n    CGFloat ms = timeInterval * 1000;\n    return [NSString stringWithFormat:@\"%.0f\",ms];\n}\n\n+ (NSString *)currentTimeInterval{\n    NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970]*1000;\n    return [NSString stringWithFormat:@\"%0.f\",timeInterval];\n}\n\n+ (void)savePerformanceDataInFile:(NSString *)fileName data:(NSString *)data{\n    NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];\n    NSString *anrDir = [cachesDir stringByAppendingPathComponent:@\"DoraemonPerformance\"];\n    NSFileManager *fileManager = [NSFileManager defaultManager];\n    BOOL isDir = NO;\n    BOOL existed = [fileManager fileExistsAtPath:anrDir isDirectory:&isDir];\n    if(!(isDir && existed)){\n        [fileManager createDirectoryAtPath:anrDir withIntermediateDirectories:YES attributes:nil error:nil];\n    }\n    NSString *path = [anrDir stringByAppendingPathComponent:[NSString stringWithFormat:@\"%@.txt\",fileName]];\n    NSString *text = data;\n    BOOL writeSuccess = [text writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];\n    if (writeSuccess) {\n        DoKitLog(@\"write success\");\n    }\n}\n\n+ (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString {\n    if (jsonString == nil) {\n        return nil;\n    }\n    \n    NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];\n    NSError *err;\n    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData\n                                                        options:NSJSONReadingMutableContainers\n                                                          error:&err];\n    if(err) {\n        DoKitLog(@\"json read error：%@\",err);\n        return nil;\n    }\n    return dic;\n}\n\n+ (NSArray *)arrayWithJsonString:(NSString *)jsonString {\n    if (jsonString == nil) {\n        return nil;\n    }\n    \n    NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];\n    NSError *err;\n    NSArray *array = [NSJSONSerialization JSONObjectWithData:jsonData\n                                                        options:NSJSONReadingMutableContainers\n                                                          error:&err];\n    if(err) {\n        DoKitLog(@\"json read error：%@\",err);\n        return nil;\n    }\n    return array;\n}\n\n+(NSString *)dictToJsonStr:(NSDictionary *)dict{\n    \n    NSString *jsonString = nil;\n    if ([NSJSONSerialization isValidJSONObject:dict])\n    {\n        NSError *error;\n        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error];\n        jsonString =[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n        if (error) {\n            DoKitLog(@\"Error:%@\" , error);\n        }\n    }\n    return jsonString;\n}\n\n+(NSString *)arrayToJsonStr:(NSArray *)array{\n    \n    NSString *jsonString = nil;\n    if ([NSJSONSerialization isValidJSONObject:array])\n    {\n        NSError *error;\n        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:array options:NSJSONWritingPrettyPrinted error:&error];\n        jsonString =[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n        if (error) {\n            DoKitLog(@\"Error:%@\" , error);\n        }\n    }\n    return jsonString;\n}\n\n//获取某一条文件路径的文件大小\n- (void)getFileSizeWithPath:(NSString *)path{\n    NSFileManager * fileManger = [NSFileManager defaultManager];\n    BOOL isDir = NO;\n    BOOL isExist = [fileManger fileExistsAtPath:path isDirectory:&isDir];\n    if (isExist){\n        if(isDir){\n            //文件夹\n            NSArray * dirArray = [fileManger contentsOfDirectoryAtPath:path error:nil];\n            NSString * subPath = nil;\n            for(NSString *str in dirArray) {\n                subPath = [path stringByAppendingPathComponent:str];\n                [self getFileSizeWithPath:subPath];\n            }\n        }else{\n            //文件\n            NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];\n            NSInteger size = [dict[@\"NSFileSize\"] integerValue];\n            _fileSize += size;\n        }\n    }else{\n        //不存在该文件path\n        DoKitLog(@\"不存在该文件\");\n    }\n}\n\n//获取所有>1M的文件\n- (NSArray *)getBigSizeFileFormPath:(NSString *)path{\n     NSFileManager * fileManger = [NSFileManager defaultManager];\n     BOOL isDir = NO;\n     BOOL isExist = [fileManger fileExistsAtPath:path isDirectory:&isDir];\n     if (isExist){\n         if(isDir){\n             //文件夹\n             NSArray * dirArray = [fileManger contentsOfDirectoryAtPath:path error:nil];\n             NSString * subPath = nil;\n             for(NSString *str in dirArray) {\n                 subPath = [path stringByAppendingPathComponent:str];\n                 [self getBigSizeFileFormPath:subPath];\n             }\n         }else{\n             //文件\n             NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];\n             NSInteger size = [dict[@\"NSFileSize\"] integerValue];\n             if (size > 1024 * 1014) { //大于1M的内容被称为大文件\n                 [_bigFileArray addObject:path];\n             }\n         }\n     }else{\n         //不存在该文件path\n         DoKitLog(@\"file not exist\");\n     }\n     \n     return nil;\n}\n\n//删除某一路径下的所有文件\n+ (void)clearFileWithPath:(NSString *)path{\n    NSFileManager *fm = [NSFileManager defaultManager];\n    NSArray *files = [fm subpathsAtPath:path];\n    for (NSString *file in files) {\n        NSError *error;\n        NSString *filePath = [path stringByAppendingPathComponent:file];\n        if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {\n            [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];\n            if (!error) {\n                NSLog(@\"remove file: %@\", file);\n            }\n        }\n    }\n}\n\n+ (void)clearLocalDatas {\n    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{\n        NSString *homePath = NSHomeDirectory();\n        NSArray *folders = @[@\"Documents\", @\"Library\", @\"tmp\"];\n        for (NSString *folder in folders) {\n            [DoraemonUtil clearFileWithPath:[homePath stringByAppendingPathComponent:folder]];\n        }\n    });\n}\n\n+ (void)openPlugin:(UIViewController *)vc {\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n\n+ (UIViewController *)rootViewControllerForKeyWindow {\n    return [UIViewController rootViewControllerForKeyWindow];\n}\n\n+ (UIViewController *)topViewControllerForKeyWindow {\n    return [UIViewController topViewControllerForKeyWindow];\n}\n\n//share text\n+ (void)shareText:(NSString *)text formVC:(UIViewController *)vc{\n    [self share:text formVC:vc];\n}\n\n//share image\n+ (void)shareImage:(UIImage *)image formVC:(UIViewController *)vc{\n    [self share:image formVC:vc];\n}\n\n//share url\n+ (void)shareURL:(NSURL *)url formVC:(UIViewController *)vc{\n    [self share:url formVC:vc];\n}\n\n+ (void)share:(id)object formVC:(UIViewController *)vc{\n    if (!object) {\n        return;\n    }\n    NSArray *objectsToShare = @[object];//support NSString、NSURL、UIImage\n\n    UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:objectsToShare applicationActivities:nil];\n\n    if([DoraemonAppInfoUtil isIpad]){\n        if ( [controller respondsToSelector:@selector(popoverPresentationController)] ) {\n            controller.popoverPresentationController.sourceView = vc.view;\n            controller.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionUp;\n            controller.popoverPresentationController.sourceRect = CGRectMake(vc.view.frame.size.width/2.0, vc.view.frame.size.height/2.0, 1.0, 1.0);\n        }\n        [vc presentViewController:controller animated:YES completion:nil];\n    }else{\n        [vc presentViewController:controller animated:YES completion:nil];\n    }\n}\n\n+ (void)openAppSetting{\n    NSURL * url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];\n    if([[UIApplication sharedApplication] canOpenURL:url]) {\n        NSURL*url =[NSURL URLWithString:UIApplicationOpenSettingsURLString];\n        if (@available(iOS 10.0, *)) {\n            [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:^(BOOL success) {\n                \n            }];\n        } else {\n            [[UIApplication sharedApplication] openURL:url];\n        }\n    }\n}\n\n+ (UIWindow *)getKeyWindow{\n    UIWindow *keyWindow = nil;\n    if ([[UIApplication sharedApplication].delegate respondsToSelector:@selector(window)]) {\n        keyWindow = [[UIApplication sharedApplication].delegate window];\n    }else{\n        NSArray *windows = [UIApplication sharedApplication].windows;\n        for (UIWindow *window in windows) {\n            if (!window.hidden) {\n                keyWindow = window;\n                break;\n            }\n        }\n    }\n    return keyWindow;\n}\n\n+ (NSArray *)getWebViews {\n    NSMutableArray *webViews = [NSMutableArray array];\n    // 查找当前window中的所有webView\n    [webViews addObjectsFromArray:[[self getKeyWindow] doraemon_findViewsForClass:WKWebView.class]];\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n    [webViews addObjectsFromArray:[[self getKeyWindow] doraemon_findViewsForClass:UIWebView.class]];\n#pragma clang diagnostic pop\n    return webViews;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/Doraemoni18NUtil.h",
    "content": "//\n//  Doraemoni18NUtil.h\n//  DoraemonKit\n//\n//  Created by xgb on 2018/11/14.\n//\n\n#import <Foundation/Foundation.h>\n\n#define DoraemonLocalizedString(key)   [Doraemoni18NUtil localizedString:key]\n\n@interface Doraemoni18NUtil : NSObject\n\n+ (NSString *)localizedString:(NSString *)key;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/Doraemoni18NUtil.m",
    "content": "//\n//  Doraemoni18NUtil.m\n//  DoraemonKit\n//\n//  Created by xgb on 2018/11/14.\n//\n\n#import \"Doraemoni18NUtil.h\"\n\n@implementation Doraemoni18NUtil\n\n+ (NSString *)localizedString:(NSString *)key {\n    \n    NSString *language = [[NSLocale preferredLanguages] firstObject];\n    if (language.length == 0) {\n        return key;\n    }\n    NSString *fileNamePrefix = @\"zh-Hans\";\n    if([language hasPrefix:@\"en\"]) {\n        fileNamePrefix = @\"en\";\n    }\n//    NSBundle *tmp = [NSBundle bundleWithPath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:[NSString stringWithFormat:@\"%@.bundle/\", @\"DoraemonKit\"]]];\n    NSBundle *tmpBundle = [NSBundle bundleForClass:NSClassFromString(@\"DoraemonManager\")];\n    NSURL *url = [tmpBundle URLForResource:@\"DoraemonKit\" withExtension:@\"bundle\"];\n    if(!url) return key;\n    NSBundle *tmp = [NSBundle bundleWithURL:url];\n    \n    NSString *path = [tmp pathForResource:fileNamePrefix ofType:@\"lproj\"];\n    NSBundle *bundle = [NSBundle bundleWithPath:path];\n    NSString *localizedString = [bundle localizedStringForKey:key value:nil table:@\"Doraemon\"];\n    if (!localizedString) {\n        localizedString = key;\n    }\n    return localizedString;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/fishhook/doraemon_fishhook.c",
    "content": "//\n//  doraemon_fishhook.c\n//  DoraemonKit\n//\n//  Created by didi on 2020/3/18.\n//\n\n#include \"doraemon_fishhook.h\"\n\n#include <assert.h>\n#include <dlfcn.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/mman.h>\n#include <sys/types.h>\n#include <mach/mach.h>\n#include <mach/vm_map.h>\n#include <mach/vm_region.h>\n#include <mach-o/dyld.h>\n#include <mach-o/loader.h>\n#include <mach-o/nlist.h>\n\n#ifdef __LP64__\ntypedef struct mach_header_64 mach_header_t;\ntypedef struct segment_command_64 segment_command_t;\ntypedef struct section_64 section_t;\ntypedef struct nlist_64 nlist_t;\n#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64\n#else\ntypedef struct mach_header mach_header_t;\ntypedef struct segment_command segment_command_t;\ntypedef struct section section_t;\ntypedef struct nlist nlist_t;\n#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT\n#endif\n\n#ifndef SEG_DATA_CONST\n#define SEG_DATA_CONST  \"__DATA_CONST\"\n#endif\n\nstruct doraemon_rebindings_entry {\n  struct doraemon_rebinding *rebindings;\n  size_t rebindings_nel;\n  struct doraemon_rebindings_entry *next;\n};\n\nstatic struct doraemon_rebindings_entry *_rebindings_head;\n\nstatic int doraemon_prepend_rebindings(struct doraemon_rebindings_entry **rebindings_head,\n                              struct doraemon_rebinding rebindings[],\n                              size_t nel) {\n  struct doraemon_rebindings_entry *new_entry = (struct doraemon_rebindings_entry *) malloc(sizeof(struct doraemon_rebindings_entry));\n  if (!new_entry) {\n    return -1;\n  }\n  new_entry->rebindings = (struct doraemon_rebinding *) malloc(sizeof(struct doraemon_rebinding) * nel);\n  if (!new_entry->rebindings) {\n    free(new_entry);\n    return -1;\n  }\n  memcpy(new_entry->rebindings, rebindings, sizeof(struct doraemon_rebinding) * nel);\n  new_entry->rebindings_nel = nel;\n  new_entry->next = *rebindings_head;\n  *rebindings_head = new_entry;\n  return 0;\n}\n\nstatic vm_prot_t doraemon_get_protection(void *sectionStart) {\n  mach_port_t task = mach_task_self();\n  vm_size_t size = 0;\n  vm_address_t address = (vm_address_t)sectionStart;\n  memory_object_name_t object;\n#if __LP64__\n  mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;\n  vm_region_basic_info_data_64_t info;\n  kern_return_t info_ret = vm_region_64(\n      task, &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_64_t)&info, &count, &object);\n#else\n  mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT;\n  vm_region_basic_info_data_t info;\n  kern_return_t info_ret = vm_region(task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object);\n#endif\n  if (info_ret == KERN_SUCCESS) {\n    return info.protection;\n  } else {\n    return VM_PROT_READ;\n  }\n}\n\nstatic void doraemon_perform_rebinding_with_section(struct doraemon_rebindings_entry *rebindings,\n                                           section_t *section,\n                                           intptr_t slide,\n                                           nlist_t *symtab,\n                                           char *strtab,\n                                           uint32_t *indirect_symtab) {\n  const bool isDataConst = strcmp(section->segname, \"__DATA_CONST\") == 0;\n  uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;\n  void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);\n  if (isDataConst) {\n      kern_return_t kernelReturn = vm_protect(mach_task_self(), (vm_address_t)indirect_symbol_bindings, section->size, false, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);\n      if (__builtin_expect(kernelReturn != KERN_SUCCESS, false)) {\n          assert(false && \"vm_protect() failure.\");\n          \n          return;\n      }\n  }\n  for (uint i = 0; i < section->size / sizeof(void *); i++) {\n    uint32_t symtab_index = indirect_symbol_indices[i];\n    if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||\n        symtab_index == (INDIRECT_SYMBOL_LOCAL   | INDIRECT_SYMBOL_ABS)) {\n      continue;\n    }\n    uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;\n    char *symbol_name = strtab + strtab_offset;\n    bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1];\n    struct doraemon_rebindings_entry *cur = rebindings;\n    while (cur) {\n      for (uint j = 0; j < cur->rebindings_nel; j++) {\n        if (symbol_name_longer_than_1 &&\n            strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {\n          if (cur->rebindings[j].replaced != NULL &&\n              indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {\n            *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];\n          }\n          indirect_symbol_bindings[i] = cur->rebindings[j].replacement;\n          goto symbol_loop;\n       \n        }\n      }\n      cur = cur->next;\n    }\n  symbol_loop:;\n  }\n}\n\nstatic void doraemon_rebind_symbols_for_image(struct doraemon_rebindings_entry *rebindings,\n                                     const struct mach_header *header,\n                                     intptr_t slide) {\n  Dl_info info;\n  if (dladdr(header, &info) == 0) {\n    return;\n  }\n\n  segment_command_t *cur_seg_cmd;\n  segment_command_t *linkedit_segment = NULL;\n  struct symtab_command* symtab_cmd = NULL;\n  struct dysymtab_command* dysymtab_cmd = NULL;\n\n  uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);\n  for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {\n    cur_seg_cmd = (segment_command_t *)cur;\n    if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {\n      if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {\n        linkedit_segment = cur_seg_cmd;\n      }\n    } else if (cur_seg_cmd->cmd == LC_SYMTAB) {\n      symtab_cmd = (struct symtab_command*)cur_seg_cmd;\n    } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {\n      dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;\n    }\n  }\n\n  if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||\n      !dysymtab_cmd->nindirectsyms) {\n    return;\n  }\n\n  // Find base symbol/string table addresses\n  uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;\n  nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);\n  char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);\n\n  // Get indirect symbol table (array of uint32_t indices into symbol table)\n  uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);\n\n  cur = (uintptr_t)header + sizeof(mach_header_t);\n  for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {\n    cur_seg_cmd = (segment_command_t *)cur;\n    if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {\n      if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&\n          strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {\n        continue;\n      }\n      for (uint j = 0; j < cur_seg_cmd->nsects; j++) {\n        section_t *sect =\n          (section_t *)(cur + sizeof(segment_command_t)) + j;\n        if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {\n          doraemon_perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);\n        }\n        if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {\n          doraemon_perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);\n        }\n      }\n    }\n  }\n}\n\nstatic void _doraemon_rebind_symbols_for_image(const struct mach_header *header,\n                                      intptr_t slide) {\n    doraemon_rebind_symbols_for_image(_rebindings_head, header, slide);\n}\n\nint doraemon_rebind_symbols_image(void *header,\n                         intptr_t slide,\n                         struct doraemon_rebinding rebindings[],\n                         size_t rebindings_nel) {\n    struct doraemon_rebindings_entry *rebindings_head = NULL;\n    int retval = doraemon_prepend_rebindings(&rebindings_head, rebindings, rebindings_nel);\n    doraemon_rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide);\n    if (rebindings_head) {\n      free(rebindings_head->rebindings);\n    }\n    free(rebindings_head);\n    return retval;\n}\n\nint doraemon_rebind_symbols(struct doraemon_rebinding rebindings[], size_t rebindings_nel) {\n  int retval = doraemon_prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);\n  if (retval < 0) {\n    return retval;\n  }\n  // If this was the first call, register callback for image additions (which is also invoked for\n  // existing images, otherwise, just run on existing images\n  if (!_rebindings_head->next) {\n    _dyld_register_func_for_add_image(_doraemon_rebind_symbols_for_image);\n  } else {\n    uint32_t c = _dyld_image_count();\n    for (uint32_t i = 0; i < c; i++) {\n      _doraemon_rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));\n    }\n  }\n  return retval;\n}\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Core/Util/fishhook/doraemon_fishhook.h",
    "content": "//\n//  doraemon_fishhook.h\n//  DoraemonKit\n//\n//  Created by didi on 2020/3/18.\n//\n\n#ifndef doraemon_fishhook_h\n#define doraemon_fishhook_h\n\n#include <stddef.h>\n#include <stdint.h>\n\n#if !defined(DORAEMON_FISHHOOK_EXPORT)\n#define DORAEMON_FISHHOOK_VISIBILITY __attribute__((visibility(\"hidden\")))\n#else\n#define DORAEMON_FISHHOOK_VISIBILITY __attribute__((visibility(\"default\")))\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif //__cplusplus\n\n/*\n * A structure representing a particular intended rebinding from a symbol\n * name to its replacement\n */\nstruct doraemon_rebinding {\n  const char *name;\n  void *replacement;\n  void **replaced;\n};\n\n/*\n * For each rebinding in rebindings, rebinds references to external, indirect\n * symbols with the specified name to instead point at replacement for each\n * image in the calling process as well as for all future images that are loaded\n * by the process. If rebind_functions is called more than once, the symbols to\n * rebind are added to the existing list of rebindingdoraemon_rebind_symbolsl\n * is rebound more than once, the later rebinding will take precedence.\n */\nDORAEMON_FISHHOOK_VISIBILITY\nint doraemon_rebind_symbols(struct doraemon_rebinding rebindings[], size_t rebindings_nel);\n\n/*\n * Rebinds as above, but only in the specified image. The header should point\n * to the mach-o header, the slide should be the slide offset. Others as above.\n */\nDORAEMON_FISHHOOK_VISIBILITY\nint doraemon_rebind_symbols_image(void *header,\n                         intptr_t slide,\n                         struct doraemon_rebinding rebindings[],\n                         size_t rebindings_nel);\n#ifdef __cplusplus\n}\n#endif //__cplusplus\n\n\n#endif /* doraemon_fishhook_h */\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Database/DoraemonDatabasePlugin.h",
    "content": "//\n//  DoraemonDatabasePlugin.h\n//  DoraemonKit\n//\n//  Created by wentian on 2019/7/11.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDatabasePlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Database/DoraemonDatabasePlugin.m",
    "content": "//\n//  DoraemonDatabasePlugin.m\n//  DoraemonKit\n//\n//  Created by wentian on 2019/7/11.\n//\n\n#import \"DoraemonDatabasePlugin.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonDatabaseViewController.h\"\n\n@implementation DoraemonDatabasePlugin\n\n- (void)pluginDidLoad{\n    DoraemonDatabaseViewController *vc = [[DoraemonDatabaseViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Database/DoraemonDatabaseViewController.h",
    "content": "//\n//  DoraemonDatabaseViewController.h\n//  DoraemonKit\n//\n//  Created by wentian on 2019/7/11.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDatabaseViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Database/DoraemonDatabaseViewController.m",
    "content": "//\n//  DoraemonDatabaseViewController.m\n//  DoraemonKit\n//\n//  Created by wentian on 2019/7/11.\n//\n\n#import \"DoraemonDatabaseViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DebugDatabaseManager.h\"\n\n@interface DoraemonDatabaseViewController ()\n\n@property (nonatomic, strong) UIButton *startButton;\n@property (nonatomic, strong) UILabel *tipLabel;\n@property (nonatomic, strong) UILabel *stateLable;\n\n@end\n\n@implementation DoraemonDatabaseViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    // Do any additional setup after loading the view.\n    \n    self.title = @\"DBView\";\n    \n    _startButton = [UIButton buttonWithType:UIButtonTypeCustom];\n    _startButton.frame = CGRectMake(15, self.bigTitleView.doraemon_bottom + 50, self.view.doraemon_width - 40, 50);\n    _startButton.backgroundColor = [UIColor doraemon_colorWithHex:0x4889db];\n    [_startButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];\n    [_startButton addTarget:self action:@selector(startServer) forControlEvents:UIControlEventTouchUpInside];\n    _startButton.layer.cornerRadius = 4;\n    _startButton.layer.masksToBounds = YES;\n    [self.view addSubview:_startButton];\n    \n    _tipLabel = [[UILabel alloc] initWithFrame:CGRectMake(_startButton.doraemon_left, _startButton.doraemon_bottom + 40, _startButton.doraemon_width, 150)];\n    _tipLabel.textColor = [UIColor doraemon_colorWithHex:0x808080];\n    _tipLabel.numberOfLines = 0;\n    \n    [self.view addSubview:_tipLabel];\n    \n    [self updateStateDesc];\n}\n\n- (void)startServer {\n    \n    if ([[DebugDatabaseManager shared] isRunning]) {\n        [[DebugDatabaseManager shared] stop];\n    }else {\n        [[DebugDatabaseManager shared] startServerOnPort:9002];\n    }\n    \n    [self updateStateDesc];\n}\n\n- (void)updateStateDesc {\n    \n    BOOL isrunning = [[DebugDatabaseManager shared] isRunning];\n    \n    [_startButton setTitle:isrunning ? DoraemonLocalizedString(@\"关闭服务\") : DoraemonLocalizedString(@\"开启服务\") forState:UIControlStateNormal];\n    \n    NSString *tips = @\"\";\n    if (isrunning) {\n        tips = [NSString stringWithFormat:@\"%@：\\n\\n%@：\\n\\n%@\", DoraemonLocalizedString(@\"温馨提示\"), DoraemonLocalizedString(@\"你可以通过下面地址访问\"), [DebugDatabaseManager shared].serverURL];\n    }else {\n        tips = [NSString stringWithFormat:@\"%@：\\n\\n%@！\\n%@!\\n\", DoraemonLocalizedString(@\"温馨提示\"), DoraemonLocalizedString(@\"服务已关闭\"), DoraemonLocalizedString(@\"请保证当前手机和PC处在同一个局域网内\")];\n    }\n    _tipLabel.text = tips;\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/GPS/DoraemonGPSPlugin.h",
    "content": "//\n//  DoraemonGPSPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/20.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonGPSPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/GPS/DoraemonGPSPlugin.m",
    "content": "//\n//  DoraemonGPSPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/20.\n//\n\n#import \"DoraemonGPSPlugin.h\"\n#import \"DoraemonGPSViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonGPSPlugin\n\n- (void)pluginDidLoad{\n    DoraemonGPSViewController *vc = [[DoraemonGPSViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/GPS/DoraemonGPSViewController.h",
    "content": "//\n//  DoraemonGPSViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/20.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\n@interface DoraemonGPSViewController : DoraemonBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/GPS/DoraemonGPSViewController.m",
    "content": "//\n//  DoraemonGPSViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2017/12/20.\n//\n\n#import \"DoraemonGPSViewController.h\"\n#import <MapKit/MapKit.h>\n#import \"UIImage+Doraemon.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonToastUtil.h\"\n#import \"DoraemonGPSMocker.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonMockGPSOperateView.h\"\n#import \"DoraemonMockGPSInputView.h\"\n#import \"DoraemonMockGPSCenterView.h\"\n\n@interface DoraemonGPSViewController ()<MKMapViewDelegate,DoraemonMockGPSInputViewDelegate>\n\n@property (nonatomic, strong) MKMapView *mapView;\n@property (nonatomic, strong) CLLocationManager *locationManager;\n@property (nonatomic, strong) DoraemonMockGPSOperateView *operateView;\n@property (nonatomic, strong) DoraemonMockGPSInputView *inputView;\n@property (nonatomic, strong) DoraemonMockGPSCenterView *mapCenterView;\n\n@end\n\n@implementation DoraemonGPSViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"Mock GPS\");\n    \n    [self initUI];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n- (void)initUI{\n    _operateView = [[DoraemonMockGPSOperateView alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(6), self.bigTitleView.doraemon_bottom+kDoraemonSizeFrom750_Landscape(24), self.view.doraemon_width-2*kDoraemonSizeFrom750_Landscape(6), kDoraemonSizeFrom750_Landscape(124))];\n    _operateView.switchView.on = [[DoraemonCacheManager sharedInstance] mockGPSSwitch];\n    [self.view addSubview:_operateView];\n    [_operateView.switchView addTarget:self action:@selector(switchAction:) forControlEvents:UIControlEventValueChanged];\n    \n    _inputView = [[DoraemonMockGPSInputView alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(6), _operateView.doraemon_bottom+kDoraemonSizeFrom750_Landscape(17), self.view.doraemon_width-2*kDoraemonSizeFrom750_Landscape(6), kDoraemonSizeFrom750_Landscape(170))];\n    _inputView.delegate = self;\n    [self.view addSubview:_inputView];\n    \n    //获取定位服务授权\n    [self requestUserLocationAuthor];\n    //初始化地图\n    MKMapView *mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, self.view.doraemon_height-self.bigTitleView.doraemon_bottom)];\n    mapView.mapType = MKMapTypeStandard;\n    mapView.delegate = self;\n    [self.view addSubview:mapView];\n    self.mapView = mapView;\n    \n    [self.view sendSubviewToBack:self.mapView];\n    \n    _mapCenterView = [[DoraemonMockGPSCenterView alloc] initWithFrame:CGRectMake(_mapView.doraemon_width/2-kDoraemonSizeFrom750_Landscape(250)/2, _mapView.doraemon_height/2-kDoraemonSizeFrom750_Landscape(250)/2, kDoraemonSizeFrom750_Landscape(250), kDoraemonSizeFrom750_Landscape(250))];\n    [_mapView addSubview:_mapCenterView];\n\n    if (_operateView.switchView.on) {\n        CLLocationCoordinate2D coordinate = [[DoraemonCacheManager sharedInstance] mockCoordinate];\n        [_mapCenterView hiddenGPSInfo:NO];\n        [_mapCenterView renderUIWithGPS:[NSString stringWithFormat:@\"%f , %f\",coordinate.longitude,coordinate.latitude]];\n        [self.mapView setCenterCoordinate:coordinate animated:NO];\n        CLLocation *loc = [[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude];\n        [[DoraemonGPSMocker shareInstance] mockPoint:loc];\n    }else{\n        [_mapCenterView hiddenGPSInfo:YES];\n        [[DoraemonGPSMocker shareInstance] stopMockPoint];\n    }\n}\n    \n\n- (void)switchAction:(id)sender{\n    UISwitch *switchButton = (UISwitch*)sender;\n    BOOL isButtonOn = [switchButton isOn];\n    [[DoraemonCacheManager sharedInstance] saveMockGPSSwitch:isButtonOn];\n    if (isButtonOn) {\n        CLLocationCoordinate2D coordinate = [[DoraemonCacheManager sharedInstance] mockCoordinate];\n        [_mapCenterView hiddenGPSInfo:NO];\n        [_mapCenterView renderUIWithGPS:[NSString stringWithFormat:@\"%f , %f\",coordinate.longitude,coordinate.latitude]];\n        [self.mapView setCenterCoordinate:coordinate animated:NO];\n        CLLocation *loc = [[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude];\n        [[DoraemonGPSMocker shareInstance] mockPoint:loc];\n    }else{\n        [_mapCenterView hiddenGPSInfo:YES];\n        [[DoraemonGPSMocker shareInstance] stopMockPoint];\n    }\n}\n\n#pragma mark - DoraemonMockGPSInputViewDelegate\n- (void)inputViewOkClick:(NSString *)gps{\n    if (![[DoraemonCacheManager sharedInstance] mockGPSSwitch]) {\n        [DoraemonToastUtil showToast:DoraemonLocalizedString(@\"mock开关没有打开\") inView:self.view];\n        return;\n    }\n    NSArray *array = [gps componentsSeparatedByString:@\" \"];\n    if(array && array.count == 2){\n        NSString *longitudeValue = array[0];\n        NSString *latitudeValue = array[1];\n        if (longitudeValue.length==0 || latitudeValue.length==0) {\n            [DoraemonToastUtil showToast:DoraemonLocalizedString(@\"经纬度不能为空\") inView:self.view];\n            return;\n        }\n        \n        CGFloat longitude = [longitudeValue floatValue];\n        CGFloat latitude = [latitudeValue floatValue];\n        if (longitude < -180 || longitude > 180) {\n            [DoraemonToastUtil showToast:DoraemonLocalizedString(@\"经度不合法\") inView:self.view];\n            return;\n        }\n        if (latitude < -90 || latitude > 90){\n            [DoraemonToastUtil showToast:DoraemonLocalizedString(@\"纬度不合法\") inView:self.view];\n            return;\n        }\n        \n        CLLocationCoordinate2D coordinate;\n        coordinate.longitude = longitude;\n        coordinate.latitude = latitude;\n\n        [_mapCenterView hiddenGPSInfo:NO];\n        [_mapCenterView renderUIWithGPS: [NSString stringWithFormat:@\"%f , %f\",coordinate.longitude,coordinate.latitude]];\n        [self.mapView setCenterCoordinate:coordinate animated:NO];\n        \n        CLLocation *loc = [[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude];\n        [[DoraemonGPSMocker shareInstance] mockPoint:loc];\n    }else{\n        [DoraemonToastUtil showToast:DoraemonLocalizedString(@\"格式不正确\") inView:self.view];\n        return;\n    }\n    \n}\n\n//如果没有获得定位授权，获取定位授权请求\n- (void)requestUserLocationAuthor{\n    self.locationManager = [[CLLocationManager alloc] init];\n    if ([CLLocationManager locationServicesEnabled]) {\n        if ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorizedWhenInUse) {\n            [self.locationManager requestWhenInUseAuthorization];\n        }\n    }\n}\n\n- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {\n    CLLocationCoordinate2D centerCoordinate = mapView.region.center;\n    \n    if (![[DoraemonCacheManager sharedInstance] mockGPSSwitch]) {\n        return;\n    }\n    [[DoraemonCacheManager sharedInstance] saveMockCoordinate:centerCoordinate];\n    [_mapCenterView hiddenGPSInfo:NO];\n    [_mapCenterView renderUIWithGPS:[NSString stringWithFormat:@\"%f , %f\",centerCoordinate.longitude,centerCoordinate.latitude]];\n    CLLocation *loc = [[CLLocation alloc] initWithLatitude:centerCoordinate.latitude longitude:centerCoordinate.longitude];\n    [[DoraemonGPSMocker shareInstance] mockPoint:loc];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/GPS/Function/CLLocationManager+Doraemon.h",
    "content": "//\n//  CLLocationManager+Doraemon.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/7/4.\n//\n\n#import <CoreLocation/CoreLocation.h>\n\n//参考wander\n@interface CLLocationManager (Doraemon)\n\n- (void)doraemon_swizzleLocationDelegate:(id)delegate;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/GPS/Function/CLLocationManager+Doraemon.m",
    "content": "//\n//  CLLocationManager+Doraemon.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/7/4.\n//\n\n#import \"CLLocationManager+Doraemon.h\"\n#import \"DoraemonGPSMocker.h\"\n#import <objc/runtime.h>\n\n@implementation CLLocationManager (Doraemon)\n\n- (void)doraemon_swizzleLocationDelegate:(id)delegate {\n    if (delegate) {\n        [self doraemon_swizzleLocationDelegate:[DoraemonGPSMocker shareInstance]];\n        [[DoraemonGPSMocker shareInstance] addLocationBinder:self delegate:delegate];\n        \n        Protocol *proto = objc_getProtocol(\"CLLocationManagerDelegate\");\n        unsigned int count;\n        struct objc_method_description *methods = protocol_copyMethodDescriptionList(proto, NO, YES, &count);\n        for(unsigned i = 0; i < count; i++)\n        {\n            SEL sel = methods[i].name;\n            if ([delegate respondsToSelector:sel]) {\n                if (![[DoraemonGPSMocker shareInstance] respondsToSelector:sel]) {\n                    NSAssert(NO, @\"Delegate : %@ not implementation SEL : %@\",delegate,NSStringFromSelector(sel));\n\n                }\n            }\n        }\n        free(methods);\n        \n    }else{\n        [self doraemon_swizzleLocationDelegate:delegate];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/GPS/Function/DoraemonGPSMocker.h",
    "content": "//\n//  DoraemonGPSMocker.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/7/4.\n//\n\n#import <Foundation/Foundation.h>\n#import <CoreLocation/CoreLocation.h>\n\n//参考wander\n@interface DoraemonGPSMocker : NSObject\n\n+ (DoraemonGPSMocker *)shareInstance;\n\n- (void)addLocationBinder:(id)binder delegate:(id)delegate;\n\n- (BOOL)mockPoint:(CLLocation*)location;\n\n- (void)stopMockPoint;\n\n@property (nonatomic, assign) BOOL isMocking;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/GPS/Function/DoraemonGPSMocker.m",
    "content": "//\n//  DoraemonGPSMocker.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/7/4.\n//\n\n#import \"DoraemonGPSMocker.h\"\n#import \"NSObject+Doraemon.h\"\n#import \"CLLocationManager+Doraemon.h\"\n\n@interface DoraemonGPSMocker()<CLLocationManagerDelegate>\n\n@property (nonatomic, strong) NSMapTable *locationMonitor;\n@property (nonatomic,strong) CLLocation *oldLocation;\n@property (nonatomic, strong) CLLocation *pointLocation;\n@property (nonatomic,strong) NSTimer *simTimer;\n\n@end\n\n@implementation DoraemonGPSMocker\n\n+ (void)load {\n    [[DoraemonGPSMocker shareInstance] swizzleCLLocationMangagerDelegate];\n}\n\n+ (DoraemonGPSMocker *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonGPSMocker *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonGPSMocker alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    self = [super init];\n    if(self){\n        _locationMonitor = [NSMapTable strongToWeakObjectsMapTable];\n        _isMocking = NO;\n    }\n    return self;\n}\n\n\n- (void)addLocationBinder:(id)binder delegate:(id)delegate{\n    NSString *binderKey = [NSString stringWithFormat:@\"%p_binder\",binder];\n    NSString *delegateKey = [NSString stringWithFormat:@\"%p_delegate\",binder];\n    [_locationMonitor setObject:binder forKey:binderKey];\n    [_locationMonitor setObject:delegate forKey:delegateKey];\n}\n\n- (void)swizzleCLLocationMangagerDelegate {\n    [[CLLocationManager class] doraemon_swizzleInstanceMethodWithOriginSel:@selector(setDelegate:) swizzledSel:@selector(doraemon_swizzleLocationDelegate:)];\n}\n\n-(BOOL)mockPoint:(CLLocation*)location{\n    _isMocking = YES;\n    self.pointLocation = location;\n    if (self.simTimer) {\n        [self pointMock];\n    } else {\n        self.simTimer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(pointMock) userInfo:nil repeats:YES];\n        [self.simTimer fire];\n    }\n    return YES;\n}\n\n- (void)pointMock {\n    CLLocation *mockLocation = [[CLLocation alloc] initWithCoordinate:CLLocationCoordinate2DMake(self.pointLocation.coordinate.latitude, self.pointLocation.coordinate.longitude) altitude:0 horizontalAccuracy:5 verticalAccuracy:5 timestamp:[NSDate date]];\n    [self dispatchLocationsToAll:@[mockLocation]];\n}\n\n- (void)dispatchLocationsToAll:(NSArray*)locations{\n    for (NSString *key in _locationMonitor.keyEnumerator) {\n        if ([key hasSuffix:@\"_binder\"]) {\n            NSString *binderKey = key;\n            CLLocationManager *binderManager = [_locationMonitor objectForKey:binderKey];\n            [self dispatchLocationUpdate:binderManager locations:locations];\n        }\n    }\n}\n\n- (void)stopMockPoint{\n    _isMocking = NO;\n    if(self.simTimer){\n        [self.simTimer invalidate];\n        self.simTimer = nil;\n    }\n}\n\n//if manager is nil.enum all manager.\n-(void)enumDelegate:(CLLocationManager*)manager block:(void (^)(id<CLLocationManagerDelegate> delegate))block{\n    NSString *key = [NSString stringWithFormat:@\"%p_delegate\",manager];\n    id<CLLocationManagerDelegate> delegate = [_locationMonitor objectForKey:key];\n    if (delegate) {\n        block(delegate);\n    }\n}\n\n#pragma mark - CLLocationManagerDelegate\n// 这个过期接口不能删掉，防止应用方实现了这个方法\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-implementations\"\n-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{\n    if (!self.isMocking){\n        [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n            if ([delegate respondsToSelector:@selector(locationManager:didUpdateToLocation:fromLocation:)]) {\n                #pragma clang diagnostic push\n                #pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n                [delegate locationManager:manager didUpdateToLocation:newLocation fromLocation:oldLocation];\n                #pragma clang diagnostic pop\n            }\n        }];\n    }\n}\n#pragma clang diagnostic pop\n\n- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {\n    if (!self.isMocking) {\n        [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n            if ([delegate respondsToSelector:@selector(locationManager:didUpdateLocations:)]) {\n                [delegate locationManager:manager didUpdateLocations:locations];\n            }\n        }];\n    }\n}\n    \n- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading {\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManager:didUpdateHeading:)]) {\n            [delegate locationManager:manager didUpdateHeading:newHeading];\n        }\n    }];\n}\n\n-(BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager{\n    __block BOOL ret = NO;\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManagerShouldDisplayHeadingCalibration:)]) {\n            ret = [delegate locationManagerShouldDisplayHeadingCalibration:manager];\n        }\n    }];\n    \n    return ret;\n}\n\n- (void)locationManager:(CLLocationManager *)manager\n      didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManager:didDetermineState:forRegion:)]) {\n            [delegate locationManager:manager didDetermineState:state forRegion:region];\n        }\n    }];\n}\n\n- (void)locationManager:(CLLocationManager *)manager\n        didRangeBeacons:(NSArray<CLBeacon *> *)beacons inRegion:(CLBeaconRegion *)region{\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManager:didRangeBeacons:inRegion:)]) {\n            [delegate locationManager:manager didRangeBeacons:beacons inRegion:region];\n        }\n    }];\n}\n\n- (void)locationManager:(CLLocationManager *)manager rangingBeaconsDidFailForRegion:(CLBeaconRegion *)region\n              withError:(NSError *)error{\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManager:rangingBeaconsDidFailForRegion:withError:)]) {\n            [delegate locationManager:manager rangingBeaconsDidFailForRegion:region withError:error];\n        }\n    }];\n}\n\n- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray<CLBeacon *> *)beacons satisfyingConstraint:(CLBeaconIdentityConstraint *)beaconConstraint API_AVAILABLE(ios(13.0)) {\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManager:didRangeBeacons:satisfyingConstraint:)]) {\n            [delegate locationManager:manager didRangeBeacons:beacons satisfyingConstraint:beaconConstraint];\n        }\n    }];\n}\n\n- (void)locationManager:(CLLocationManager *)manager didFailRangingBeaconsForConstraint:(CLBeaconIdentityConstraint *)beaconConstraint error:(NSError *)error API_AVAILABLE(ios(13.0)) {\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManager:didFailRangingBeaconsForConstraint:error:)]) {\n            [delegate locationManager:manager didFailRangingBeaconsForConstraint:beaconConstraint error:error];\n        }\n    }];\n}\n\n- (void)locationManager:(CLLocationManager *)manager\n         didEnterRegion:(CLRegion *)region{\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManager:didEnterRegion:)]) {\n            [delegate locationManager:manager didEnterRegion:region];\n        }\n    }];\n}\n\n- (void)locationManager:(CLLocationManager *)manager\n          didExitRegion:(CLRegion *)region{\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManager:didExitRegion:)]) {\n            [delegate locationManager:manager didExitRegion:region];\n        }\n    }];\n}\n\n-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManager:didFailWithError:)]) {\n            [delegate locationManager:manager didFailWithError:error];\n        }\n    }];\n}\n\n- (void)locationManager:(CLLocationManager *)manager\nmonitoringDidFailForRegion:(nullable CLRegion *)region\n              withError:(NSError *)error{\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManager:monitoringDidFailForRegion:withError:)]) {\n            [delegate locationManager:manager monitoringDidFailForRegion:region withError:error];\n        }\n    }];\n}\n\n-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status{\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManager:didChangeAuthorizationStatus:)]) {\n            [delegate locationManager:manager didChangeAuthorizationStatus:status];\n        }\n    }];\n}\n\n-(void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager {\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManagerDidChangeAuthorization:)]) {\n            [delegate performSelector:@selector(locationManagerDidChangeAuthorization:) withObject:manager];\n        }\n    }];\n}\n\n- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region{\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManager:didStartMonitoringForRegion:)]) {\n            [delegate locationManager:manager didStartMonitoringForRegion:region];\n        }\n    }];\n}\n\n- (void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager {\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManagerDidPauseLocationUpdates:)]) {\n            [delegate locationManagerDidPauseLocationUpdates:manager];\n        }\n    }];\n}\n\n- (void)locationManagerDidResumeLocationUpdates:(CLLocationManager *)manager {\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManagerDidResumeLocationUpdates:)]) {\n            [delegate locationManagerDidResumeLocationUpdates:manager];\n        }\n    }];\n}\n\n- (void)locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(nullable NSError *)error{\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManager:didFinishDeferredUpdatesWithError:)]) {\n            [delegate locationManager:manager didFinishDeferredUpdatesWithError:error];\n        }\n    }];\n}\n\n- (void)locationManager:(CLLocationManager *)manager didVisit:(CLVisit *)visit {\n    [self enumDelegate:manager block:^(id<CLLocationManagerDelegate> delegate) {\n        if ([delegate respondsToSelector:@selector(locationManager:didVisit:)]) {\n            [delegate locationManager:manager didVisit:visit];\n        }\n    }];\n}\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n-(void)dispatchLocationUpdate:(CLLocationManager *)manager locations:(NSArray*)locations{\n    NSString *key = [NSString stringWithFormat:@\"%p_delegate\",manager];\n    id<CLLocationManagerDelegate> delegate = [_locationMonitor objectForKey:key];\n    if ([delegate respondsToSelector:@selector(locationManager:didUpdateLocations:)]) {\n        [delegate locationManager:manager didUpdateLocations:locations];\n    }else if ([delegate respondsToSelector:@selector(locationManager:didUpdateToLocation:fromLocation:)]){\n        [delegate locationManager:manager didUpdateToLocation:locations.firstObject fromLocation:self.oldLocation];\n        self.oldLocation = locations.firstObject;\n    }\n}\n#pragma clang diagnostic pop\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/GPS/View/DoraemonMockGPSCenterView.h",
    "content": "//\n//  DoraemonMockGPSCenterView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/2.\n//\n\n#import <UIKit/UIKit.h>\n\n\n@interface DoraemonMockGPSCenterView : UIView\n\n- (void)renderUIWithGPS:(NSString *)gps;\n\n- (void)hiddenGPSInfo:(BOOL)hidden;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/GPS/View/DoraemonMockGPSCenterView.m",
    "content": "//\n//  DoraemonMockGPSCenterView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/2.\n//\n\n#import \"DoraemonMockGPSCenterView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMockGPSCenterView()\n\n@property (nonatomic, strong) UIView *circleView;\n@property (nonatomic, strong) UIImageView *locationIconView;\n@property (nonatomic, strong) UILabel *gpsLabel;\n@property (nonatomic, strong) UIImageView *arrowImageView;\n\n@end\n\n@implementation DoraemonMockGPSCenterView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        _circleView = [[UIView alloc] initWithFrame:CGRectMake(self.doraemon_width/2-kDoraemonSizeFrom750_Landscape(100)/2, self.doraemon_height/2-kDoraemonSizeFrom750_Landscape(100)/2, kDoraemonSizeFrom750_Landscape(100), kDoraemonSizeFrom750_Landscape(100))];\n        _circleView.layer.cornerRadius = kDoraemonSizeFrom750_Landscape(50);\n        _circleView.backgroundColor = [UIColor doraemon_colorWithHex:0xFFA511 andAlpha:0.37];\n        [self addSubview:_circleView];\n        \n        _locationIconView = [[UIImageView alloc] initWithImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_location\"]];\n        _locationIconView.frame = CGRectMake(self.circleView.center.x-_locationIconView.doraemon_width/2, self.circleView.center.y-_locationIconView.doraemon_height, _locationIconView.doraemon_width, _locationIconView.doraemon_height);\n        [self addSubview:_locationIconView];\n        \n        _gpsLabel = [[UILabel alloc] init];\n        _gpsLabel.textColor = [UIColor doraemon_black_1];\n        _gpsLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n        _gpsLabel.backgroundColor = [UIColor whiteColor];\n        _gpsLabel.textAlignment = NSTextAlignmentCenter;\n        [self addSubview:_gpsLabel];\n        \n        _arrowImageView = [[UIImageView alloc] initWithImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_arrow_down\"]];\n        _arrowImageView.frame = CGRectMake(self.doraemon_width/2-_arrowImageView.doraemon_width/2, _locationIconView.doraemon_top-kDoraemonSizeFrom750_Landscape(20)-_arrowImageView.doraemon_height, _arrowImageView.doraemon_width, _arrowImageView.doraemon_height);\n        [self addSubview:_arrowImageView];\n        \n    }\n    return self;\n}\n\n- (void)renderUIWithGPS:(NSString *)gps{\n    _gpsLabel.text = gps;\n    [_gpsLabel sizeToFit];\n    CGFloat w = _gpsLabel.doraemon_width + kDoraemonSizeFrom750_Landscape(30)*2;\n    CGFloat h = _gpsLabel.doraemon_height + kDoraemonSizeFrom750_Landscape(12)*2;\n    _gpsLabel.frame = CGRectMake(self.doraemon_width/2-w/2, _arrowImageView.doraemon_top-h+kDoraemonSizeFrom750_Landscape(10), w, h);\n    _gpsLabel.layer.cornerRadius = h/2;\n    _gpsLabel.clipsToBounds = YES;\n}\n\n- (void)hiddenGPSInfo:(BOOL)hidden{\n    _gpsLabel.hidden = hidden;\n    _arrowImageView.hidden = hidden;\n}\n\n- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{\n    UIView *hitView = [super hitTest:point withEvent:event];\n    \n    if (hitView == self) {\n        return nil;\n    }\n    return hitView;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/GPS/View/DoraemonMockGPSInputView.h",
    "content": "//\n//  DoraemonMockGPSInputView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/2.\n//\n\n#import <UIKit/UIKit.h>\n\n@protocol DoraemonMockGPSInputViewDelegate <NSObject>\n\n- (void)inputViewOkClick:(NSString *)gps;\n\n@end\n\n@interface DoraemonMockGPSInputView : UIView\n\n@property (nonatomic, weak) id<DoraemonMockGPSInputViewDelegate> delegate;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/GPS/View/DoraemonMockGPSInputView.m",
    "content": "//\n//  DoraemonMockGPSInputView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/2.\n//\n\n#import \"DoraemonMockGPSInputView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMockGPSInputView()\n\n@property (nonatomic, strong) UITextField *textField;\n@property (nonatomic, strong) UIButton *searchBtn;\n@property (nonatomic, strong) UIView *lineView;\n@property (nonatomic, strong) UILabel *exampleLabel;\n\n@end\n\n@implementation DoraemonMockGPSInputView\n\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        self.layer.cornerRadius = kDoraemonSizeFrom750_Landscape(8);\n        \n        _textField = [[UITextField alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(32), kDoraemonSizeFrom750_Landscape(40), self.doraemon_width-2*kDoraemonSizeFrom750_Landscape(32), kDoraemonSizeFrom750_Landscape(45))];\n        _textField.placeholder = DoraemonLocalizedString(@\"请输入经纬度\");\n        [_textField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];\n        [self addSubview:_textField];\n        \n        _searchBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.doraemon_width-kDoraemonSizeFrom750_Landscape(120), 0, kDoraemonSizeFrom750_Landscape(120), kDoraemonSizeFrom750_Landscape(120))];\n        _searchBtn.imageView.contentMode = UIViewContentModeCenter;\n        [_searchBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_search\"] forState:UIControlStateNormal];\n        [_searchBtn addTarget:self action:@selector(searchBtnClick:) forControlEvents:UIControlEventTouchUpInside];\n        [self addSubview:_searchBtn];\n        \n        _lineView = [[UIView alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(32), _textField.doraemon_bottom+kDoraemonSizeFrom750_Landscape(19), self.doraemon_width-2*kDoraemonSizeFrom750_Landscape(32), kDoraemonSizeFrom750_Landscape(1))];\n        _lineView.backgroundColor = [UIColor doraemon_colorWithHexString:@\"#EEEEEE\"];\n        [self addSubview:_lineView];\n        \n        _exampleLabel = [[UILabel alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(32),_lineView.doraemon_bottom+kDoraemonSizeFrom750_Landscape(15), self.doraemon_width-kDoraemonSizeFrom750_Landscape(32), kDoraemonSizeFrom750_Landscape(33))];\n        _exampleLabel.textColor = [UIColor doraemon_black_3];\n        _exampleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n        _exampleLabel.text = DoraemonLocalizedString(@\"(示例: 120.15 30.28)\");\n        [self addSubview:_exampleLabel];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            self.backgroundColor = [UIColor systemBackgroundColor];\n        } else {\n#endif\n            self.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n    }\n    return self;\n}\n\n-(void)textFieldDidChange:(id)sender{\n    UITextField *senderTextField = (UITextField *)sender;\n    //去除首尾空格\n    NSString *textSearchStr = [senderTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];\n    if (textSearchStr.length > 0) {\n        [_searchBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_search_highlight\"] forState:UIControlStateNormal];\n    }else{\n        [_searchBtn setImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_search\"] forState:UIControlStateNormal];\n    }\n}\n\n- (void)searchBtnClick:(id)sender{\n    //去除首尾空格\n    NSString *textSearchStr = [_textField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];\n    if (textSearchStr.length>0) {\n        if (_delegate && [_delegate respondsToSelector:@selector(inputViewOkClick:)]) {\n            [_delegate inputViewOkClick:textSearchStr];\n        }\n    }\n}\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/GPS/View/DoraemonMockGPSOperateView.h",
    "content": "//\n//  DoraemonMockGPSOperateView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/2.\n//\n\n#import <UIKit/UIKit.h>\n\n\n@interface DoraemonMockGPSOperateView : UIView\n\n@property (nonatomic, strong) UISwitch *switchView;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/GPS/View/DoraemonMockGPSOperateView.m",
    "content": "//\n//  DoraemonMockGPSOperateView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/2.\n//\n\n#import \"DoraemonMockGPSOperateView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMockGPSOperateView()\n\n@property (nonatomic, strong) UILabel *titleLabel;\n\n@end\n\n@implementation DoraemonMockGPSOperateView\n\n- (instancetype)initWithFrame:(CGRect)frame {\n    self = [super initWithFrame:frame];\n    if (self) {\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            self.backgroundColor = [UIColor systemBackgroundColor];\n        } else {\n#endif\n            self.backgroundColor = [UIColor whiteColor];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        self.layer.cornerRadius = kDoraemonSizeFrom750_Landscape(8);\n        \n        _titleLabel = [[UILabel alloc] init];\n        _titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(32)];\n        _titleLabel.textColor = [UIColor doraemon_black_1];\n        _titleLabel.text = DoraemonLocalizedString(@\"打开Mock GPS\");\n        [self addSubview:_titleLabel];\n        [_titleLabel sizeToFit];\n        _titleLabel.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), self.doraemon_height/2-_titleLabel.doraemon_height/2, _titleLabel.doraemon_width, _titleLabel.doraemon_height);\n        \n        _switchView = [[UISwitch alloc] init];\n        _switchView.onTintColor = [UIColor doraemon_blue];\n        _switchView.doraemon_origin = CGPointMake(self.doraemon_width-kDoraemonSizeFrom750_Landscape(32)-_switchView.doraemon_width, self.doraemon_height/2-_switchView.doraemon_height/2);\n        [self addSubview:_switchView];\n    }\n    return self;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Logger/DoraemonCocoaLumberjackPlugin.h",
    "content": "//\n//  DoraemonCocoaLumberjackPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/4.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonCocoaLumberjackPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Logger/DoraemonCocoaLumberjackPlugin.m",
    "content": "//\n//  DoraemonCocoaLumberjackPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/4.\n//\n\n#import \"DoraemonCocoaLumberjackPlugin.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonCocoaLumberjackViewController.h\"\n\n@implementation DoraemonCocoaLumberjackPlugin\n\n- (void)pluginDidLoad{\n    DoraemonCocoaLumberjackViewController *vc = [[DoraemonCocoaLumberjackViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Logger/DoraemonCocoaLumberjackViewController.h",
    "content": "//\n//  DoraemonCocoaLumberjackViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/4.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonCocoaLumberjackViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Logger/DoraemonCocoaLumberjackViewController.m",
    "content": "//\n//  DoraemonCocoaLumberjackViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/4.\n//\n\n#import \"DoraemonCocoaLumberjackViewController.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonCellButton.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonCocoaLumberjackListViewController.h\"\n#import \"DoraemonCocoaLumberjackLogger.h\"\n\n@interface DoraemonCocoaLumberjackViewController ()<DoraemonSwitchViewDelegate,DoraemonCellButtonDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n@property (nonatomic, strong) DoraemonCellButton *cellBtn;\n\n@end\n\n@implementation DoraemonCocoaLumberjackViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    self.title = @\"CocoaLumberjack\";\n    \n    _switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"开关\") switchOn:[[DoraemonCacheManager sharedInstance] loggerSwitch]];\n    [_switchView needTopLine];\n    [_switchView needDownLine];\n    _switchView.delegate = self;\n    [self.view addSubview:_switchView];\n    \n    _cellBtn = [[DoraemonCellButton alloc] initWithFrame:CGRectMake(0, _switchView.doraemon_bottom, self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(104))];\n    [_cellBtn renderUIWithTitle:DoraemonLocalizedString(@\"查看记录\")];\n    _cellBtn.delegate = self;\n    [_cellBtn needDownLine];\n    [self.view addSubview:_cellBtn];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    [[DoraemonCacheManager sharedInstance] saveLoggerSwitch:on];\n    if (on) {\n        [[DoraemonCocoaLumberjackLogger sharedInstance] startMonitor];\n    }else{\n        [[DoraemonCocoaLumberjackLogger sharedInstance] stopMonitor];\n    }\n}\n\n#pragma mark -- DoraemonCellButtonDelegate\n- (void)cellBtnClick:(id)sender{\n    if (sender == _cellBtn) {\n        DoraemonCocoaLumberjackListViewController *vc = [[DoraemonCocoaLumberjackListViewController alloc] init];\n        [self.navigationController pushViewController:vc animated:YES];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Logger/Function/DoraemonCocoaLumberjackLogger.h",
    "content": "//\n//  DoraemonCocoaLumberjackLogger.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/5.\n//\n\n#import <CocoaLumberjack/DDLog.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonCocoaLumberjackLogger : DDAbstractLogger\n\n+ (instancetype)sharedInstance;\n\n- (void)startMonitor;\n\n- (void)stopMonitor;\n\n@property (nonatomic, strong) dispatch_queue_t consoleQueue;\n@property (nonatomic, strong) NSMutableArray *messages; \n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Logger/Function/DoraemonCocoaLumberjackLogger.m",
    "content": "//\n//  DoraemonCocoaLumberjackLogger.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/5.\n//\n\n#import \"DoraemonCocoaLumberjackLogger.h\"\n#import <CocoaLumberjack/CocoaLumberjack.h>\n#import \"DoraemonDDLogMessage.h\"\n\n@implementation DoraemonCocoaLumberjackLogger\n\n+ (instancetype)sharedInstance {\n    static id instance = nil;\n    static dispatch_once_t onceToken;\n    \n    dispatch_once(&onceToken, ^{\n        instance = [[self alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    self = [super init];\n    if (self) {\n        _consoleQueue = dispatch_queue_create(\"console_queue\", NULL);\n        _messages = NSMutableArray.array;\n    }\n    return self;\n}\n\n- (void)startMonitor{\n    [DDLog addLogger:self];\n}\n\n- (void)stopMonitor{\n    [DDLog removeLogger:self];\n}\n\n- (void)logMessage:(DDLogMessage *)logMessage{\n    DoraemonDDLogMessage *message = [[DoraemonDDLogMessage alloc] init];\n    message.timestamp = logMessage.timestamp;\n    message.flag = logMessage.flag;\n    message.message = logMessage.message;\n    message.fileName = logMessage.fileName;\n    message.line = logMessage.line;\n    message.threadId = logMessage.threadID;\n    message.threadName = logMessage.threadName;\n    message.expand = NO;\n    \n    __weak typeof(self) weakSelf = self;\n    dispatch_async(_consoleQueue, ^{\n        [weakSelf.messages insertObject:message atIndex:0];\n    });\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Logger/Function/DoraemonDDLogMessage.h",
    "content": "//\n//  DoraemonDDLogMessage.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/6.\n//\n\n#import <Foundation/Foundation.h>\n#import <CocoaLumberjack/CocoaLumberjack.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDDLogMessage : NSObject\n\n@property (nonatomic, copy) NSDate *timestamp;\n@property (nonatomic, assign) DDLogFlag flag;\n@property (nonatomic, copy) NSString *message;\n@property (nonatomic, copy) NSString *fileName;\n@property (nonatomic, assign) NSInteger line;\n@property (nonatomic, copy) NSString *threadId;\n@property (nonatomic, copy) NSString *threadName;\n@property (nonatomic, assign) BOOL expand;\n\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Logger/Function/DoraemonDDLogMessage.m",
    "content": "//\n//  DoraemonDDLogMessage.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/6.\n//\n\n#import \"DoraemonDDLogMessage.h\"\n\n@implementation DoraemonDDLogMessage\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Logger/List/DoraemonCocoaLumberjackLevelView.h",
    "content": "//\n//  DoraemonCocoaLumberjackLevelView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/6.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@protocol DoraemonCocoaLumberjackLevelViewDelegate<NSObject>\n\n- (void)segmentSelected:(NSInteger)index;\n\n@end\n\n@interface DoraemonCocoaLumberjackLevelView : UIView\n\n@property (nonatomic, weak) id<DoraemonCocoaLumberjackLevelViewDelegate> delegate;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Logger/List/DoraemonCocoaLumberjackLevelView.m",
    "content": "//\n//  DoraemonCocoaLumberjackLevelView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/6.\n//\n\n#import \"DoraemonCocoaLumberjackLevelView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonCocoaLumberjackLevelView()\n\n@property (nonatomic, strong) UISegmentedControl *segment;\n\n@end\n\n@implementation DoraemonCocoaLumberjackLevelView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        NSArray *dataArray = @[@\"Verbose\",@\"Debug\",@\"Info\",@\"Warn\",@\"Error\"];\n        _segment = [[UISegmentedControl alloc] initWithItems:dataArray];\n        _segment.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), self.doraemon_height/2-kDoraemonSizeFrom750_Landscape(68)/2, self.doraemon_width-kDoraemonSizeFrom750_Landscape(32)*2, kDoraemonSizeFrom750_Landscape(68));\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13, *)) {\n           _segment.selectedSegmentTintColor = [UIColor doraemon_colorWithString:@\"#337CC4\"];\n        } else {\n#endif\n            _segment.tintColor = [UIColor doraemon_colorWithString:@\"#337CC4\"];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        [_segment setSelectedSegmentIndex:0];\n        [_segment addTarget:self action:@selector(segmentChange:) forControlEvents:UIControlEventValueChanged];\n        [self addSubview:_segment];\n    }\n    return self;\n}\n\n-(void)segmentChange:(UISegmentedControl *)sender{\n    NSInteger index = sender.selectedSegmentIndex;\n    if (self.delegate && [self.delegate respondsToSelector:@selector(segmentSelected:)]) {\n        [self.delegate segmentSelected:index];\n    }\n}\n\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Logger/List/DoraemonCocoaLumberjackListCell.h",
    "content": "//\n//  DoraemonCocoaLumberjackListCell.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/6.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonDDLogMessage.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonCocoaLumberjackListCell : UITableViewCell\n\n- (void)renderCellWithData:(DoraemonDDLogMessage *)model;\n\n+ (CGFloat)cellHeightWith:(nullable DoraemonDDLogMessage *)model;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Logger/List/DoraemonCocoaLumberjackListCell.m",
    "content": "//\n//  DoraemonCocoaLumberjackListCell.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/6.\n//\n\n#import \"DoraemonCocoaLumberjackListCell.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonUtil.h\"\n\n@interface DoraemonCocoaLumberjackListCell()\n\n@property (nonatomic, strong) UIImageView *arrowImageView;\n@property (nonatomic, strong) UILabel *logLabel;\n\n@end\n\n@implementation DoraemonCocoaLumberjackListCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        self.selectionStyle = UITableViewCellSelectionStyleNone;\n        \n        _arrowImageView = [[UIImageView alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(27), [[self class] cellHeightWith:nil]/2-kDoraemonSizeFrom750_Landscape(25)/2, kDoraemonSizeFrom750_Landscape(25), kDoraemonSizeFrom750_Landscape(25))];\n        _arrowImageView.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_expand_no\"];\n        _arrowImageView.contentMode = UIViewContentModeCenter;\n        [self.contentView addSubview:_arrowImageView];\n        \n        _logLabel = [[UILabel alloc] init];\n        _logLabel.textColor = [UIColor doraemon_black_1];\n        _logLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n        [self.contentView addSubview:_logLabel];\n    }\n    return self;\n}\n\n- (void)renderCellWithData:(DoraemonDDLogMessage *)model{\n    NSString *content;\n    if (model && model.expand){\n        NSString *log = model.message;\n        NSString *time = [DoraemonUtil dateFormatNSDate:model.timestamp];\n        content = [NSString stringWithFormat:DoraemonLocalizedString(@\"%@\\n触发时间: %@\\n文件名称: %@\\n所在行: %zi\\n线程id: %@ \\n线程名称: %@\"),log,time,model.fileName,model.line,model.threadId,model.threadName];\n        _logLabel.numberOfLines = 0;\n        _logLabel.text = content;\n        CGSize size = [_logLabel sizeThatFits:CGSizeMake(DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)*2-kDoraemonSizeFrom750_Landscape(25)-kDoraemonSizeFrom750_Landscape(12)*2, MAXFLOAT)];\n        _logLabel.frame = CGRectMake(_arrowImageView.doraemon_right+kDoraemonSizeFrom750_Landscape(12), [[self class] cellHeightWith:model]/2-size.height/2, size.width, size.height);\n        \n        _arrowImageView.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_expand\"];\n    }else{\n        _logLabel.numberOfLines = 1;\n        _logLabel.text = model.message;\n        _logLabel.frame = CGRectMake(_arrowImageView.doraemon_right+kDoraemonSizeFrom750_Landscape(12), [[self class] cellHeightWith:model]/2-kDoraemonSizeFrom750_Landscape(34)/2,DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)*2-kDoraemonSizeFrom750_Landscape(25)-kDoraemonSizeFrom750_Landscape(12)*2 , kDoraemonSizeFrom750_Landscape(34));\n        _arrowImageView.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_expand_no\"];\n    }\n    \n    \n    \n}\n\n+ (CGFloat)cellHeightWith:(DoraemonDDLogMessage *)model{\n    CGFloat cellHeight = kDoraemonSizeFrom750_Landscape(60);\n    if (model && model.expand) {\n        NSString *log = model.message;\n        NSString *time = [DoraemonUtil dateFormatNSDate:model.timestamp];\n        NSString *content = [NSString stringWithFormat:DoraemonLocalizedString(@\"%@\\n触发时间: %@\\n文件名称: %@\\n所在行: %zi\\n线程id: %@\\n线程名称: %@\"),log,time,model.fileName,model.line,model.threadId,model.threadName];\n        \n        UILabel *logLabel = [[UILabel alloc] init];\n        logLabel.textColor = [UIColor doraemon_black_1];\n        logLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(24)];\n        logLabel.text = content;\n        logLabel.numberOfLines = 0;\n        CGSize size = [logLabel sizeThatFits:CGSizeMake(DoraemonScreenWidth-kDoraemonSizeFrom750_Landscape(32)*2-kDoraemonSizeFrom750_Landscape(25)-kDoraemonSizeFrom750_Landscape(12)*2, MAXFLOAT)];\n        cellHeight = kDoraemonSizeFrom750_Landscape(10) + size.height + kDoraemonSizeFrom750_Landscape(10);\n    }\n    return cellHeight;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Logger/List/DoraemonCocoaLumberjackListViewController.h",
    "content": "//\n//  DoraemonCocoaLumberjackListViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/4.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonCocoaLumberjackListViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Logger/List/DoraemonCocoaLumberjackListViewController.m",
    "content": "//\n//  DoraemonCocoaLumberjackListViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2018/12/4.\n//\n\n#import \"DoraemonCocoaLumberjackListViewController.h\"\n#import \"DoraemonNSLogSearchView.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonCocoaLumberjackLevelView.h\"\n#import \"DoraemonCocoaLumberjackListCell.h\"\n#import \"DoraemonDDLogMessage.h\"\n#import \"DoraemonCocoaLumberjackLogger.h\"\n#import \"DoraemonNavBarItemModel.h\"\n\n@interface DoraemonCocoaLumberjackListViewController ()<DoraemonNSLogSearchViewDelegate,DoraemonCocoaLumberjackLevelViewDelegate,UITableViewDelegate,UITableViewDataSource>\n\n@property (nonatomic, strong) DoraemonNSLogSearchView *searchView;\n@property (nonatomic, strong) DoraemonCocoaLumberjackLevelView *levelView;\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, copy) NSArray *dataArray;\n@property (nonatomic, copy) NSArray *origArray;\n\n@end\n\n@implementation DoraemonCocoaLumberjackListViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    self.title = DoraemonLocalizedString(@\"日志记录\");\n    DoraemonNavBarItemModel *model1 = [[DoraemonNavBarItemModel alloc] initWithText:DoraemonLocalizedString(@\"清除\") color:[UIColor doraemon_blue] selector:@selector(clear)];\n    DoraemonNavBarItemModel *model2 = [[DoraemonNavBarItemModel alloc] initWithText:DoraemonLocalizedString(@\"导出\") color:[UIColor doraemon_blue] selector:@selector(export)];\n    [self setRightNavBarItems:@[model1,model2]];\n    \n    self.origArray = [NSArray arrayWithArray:[DoraemonCocoaLumberjackLogger sharedInstance].messages];\n    self.dataArray = [NSArray arrayWithArray:self.origArray];\n    \n    _searchView = [[DoraemonNSLogSearchView alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750_Landscape(32), IPHONE_NAVIGATIONBAR_HEIGHT+kDoraemonSizeFrom750_Landscape(32), self.view.doraemon_width-2*kDoraemonSizeFrom750_Landscape(32), kDoraemonSizeFrom750_Landscape(100))];\n    _searchView.delegate = self;\n    [self.view addSubview:_searchView];\n    \n    _levelView = [[DoraemonCocoaLumberjackLevelView alloc] initWithFrame:CGRectMake(0, _searchView.doraemon_bottom+kDoraemonSizeFrom750_Landscape(32), self.view.doraemon_width, kDoraemonSizeFrom750_Landscape(68))];\n    _levelView.delegate = self;\n    [self.view addSubview:_levelView];\n    \n    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, _levelView.doraemon_bottom+kDoraemonSizeFrom750_Landscape(32), self.view.doraemon_width, self.view.doraemon_height-_searchView.doraemon_bottom-kDoraemonSizeFrom750_Landscape(32)) style:UITableViewStylePlain];\n//    self.tableView.backgroundColor = [UIColor whiteColor];\n    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;\n    self.tableView.delegate = self;\n    self.tableView.dataSource = self;\n    [self.view addSubview:self.tableView];\n    \n}\n\n- (void)clear {\n    [[DoraemonCocoaLumberjackLogger sharedInstance].messages removeAllObjects];\n    self.origArray = @[];\n    self.dataArray = @[];\n    [self.tableView reloadData];\n}\n\n- (void)export {\n    NSArray<DoraemonDDLogMessage *> *dataArray = [[DoraemonCocoaLumberjackLogger sharedInstance].messages copy];\n    NSMutableString *log = [[NSMutableString alloc] init];\n    for (DoraemonDDLogMessage *model in dataArray) {\n        NSString *time = [NSString stringWithFormat:@\"[%@]\",[DoraemonUtil dateFormatNSDate:model.timestamp]];\n        [log appendString:time];\n        [log appendString:@\" \"];\n        [log appendString:model.message];\n        [log appendString:@\"\\n\"];\n    }\n    \n    [DoraemonUtil shareText:log formVC:self];\n}\n\n#pragma mark - UITableView Delegate\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {\n    return 1;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return self.dataArray.count;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonDDLogMessage* model = [self.dataArray objectAtIndex:indexPath.row];\n    return [DoraemonCocoaLumberjackListCell cellHeightWith:model];\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {\n    return 0.1;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {\n    return 0.1;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"httpcell\";\n    DoraemonCocoaLumberjackListCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[DoraemonCocoaLumberjackListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];\n    }\n    DoraemonDDLogMessage* model = [self.dataArray objectAtIndex:indexPath.row];\n    [cell renderCellWithData:model];\n    return cell;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    [tableView deselectRowAtIndexPath:indexPath animated:YES];\n    DoraemonDDLogMessage* model = [self.dataArray objectAtIndex:indexPath.row];\n    model.expand = !model.expand;\n    [self.tableView reloadData];\n}\n\n- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{\n    return YES;\n}\n\n- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath{\n    return DoraemonLocalizedString(@\"复制\");\n}\n\n- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonDDLogMessage* model = [self.dataArray objectAtIndex:indexPath.row];\n    NSString *content = model.message;\n    if (content.length>0) {\n        UIPasteboard *pboard = [UIPasteboard generalPasteboard];\n        pboard.string = content;\n    }\n}\n\n#pragma mark - DoraemonNSLogSearchViewDelegate\n- (void)searchViewInputChange:(NSString *)text{\n    if (text.length > 0) {\n        NSArray *dataArray = self.origArray;\n        NSMutableArray *resultArray = [[NSMutableArray alloc] init];\n        for(DoraemonDDLogMessage *model in dataArray){\n            NSString *content = model.message;\n            if ([content containsString:text]) {\n                [resultArray addObject:model];\n            }\n        }\n        self.dataArray = [[NSArray alloc] initWithArray:resultArray];\n    }else{\n        self.dataArray = [[NSArray alloc] initWithArray:self.origArray];\n    }\n    \n    [self.tableView reloadData];\n}\n\n#pragma mark - DoraemonCocoaLumberjackLevelViewDelegate\n- (void)segmentSelected:(NSInteger)index{\n    NSLog(@\"%zi\",DDLogFlagError);\n    NSLog(@\"%zi\",DDLogFlagWarning);\n    NSLog(@\"%zi\",DDLogFlagInfo);\n    NSLog(@\"%zi\",DDLogFlagDebug);\n    NSLog(@\"%zi\",DDLogFlagVerbose);\n    DDLogFlag flag = DDLogFlagVerbose;\n    if (index==0) {\n        flag = DDLogFlagVerbose;//16\n    }else if(index==1){\n        flag = DDLogFlagDebug;//8\n    }else if(index==2){\n        flag = DDLogFlagInfo;//4\n    }else if(index==3){\n        flag = DDLogFlagWarning;//2\n    }else if(index==4){\n        flag = DDLogFlagError;//1\n    }\n    \n    NSArray *dataArray = self.origArray;\n    NSMutableArray *resultArray = [[NSMutableArray alloc] init];\n    for(DoraemonDDLogMessage *model in dataArray){\n        DDLogFlag modelFlag = model.flag;\n        if (modelFlag <= flag) {\n            [resultArray addObject:model];\n        }\n    }\n    self.dataArray = [[NSArray alloc] initWithArray:resultArray];\n    [self.tableView reloadData];\n    \n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Detail/DoraemonMLeaksFinderDetailViewController.h",
    "content": "//\n//  DoraemonMLeaksFinderDetailViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/7.\n//\n\n#import <DoraemonKit/DoraemonKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMLeaksFinderDetailViewController : DoraemonBaseViewController\n\n@property (nonatomic, copy) NSDictionary *info;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Detail/DoraemonMLeaksFinderDetailViewController.m",
    "content": "//\n//  DoraemonMLeaksFinderDetailViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/7.\n//\n\n#import \"DoraemonMLeaksFinderDetailViewController.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMLeaksFinderDetailViewController ()\n\n@property (nonatomic, strong) UILabel *contentLabel;\n\n@end\n\n@implementation DoraemonMLeaksFinderDetailViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"内存泄漏详情\");\n    \n    _contentLabel = [[UILabel alloc] init];\n    _contentLabel.textColor = [UIColor doraemon_black_2];\n    _contentLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(16)];\n    _contentLabel.numberOfLines = 0;\n    _contentLabel.text = [_info description];\n    \n    CGSize fontSize = [_contentLabel sizeThatFits:CGSizeMake(self.view.doraemon_width-40, MAXFLOAT)];\n    _contentLabel.frame = CGRectMake(20, IPHONE_NAVIGATIONBAR_HEIGHT, fontSize.width, fontSize.height);\n    [self.view addSubview:_contentLabel];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/DoraemonMLeaksFinderPlugin.h",
    "content": "//\n//  DoraemonMLeaksFinderPlugin.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/6.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMLeaksFinderPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/DoraemonMLeaksFinderPlugin.m",
    "content": "//\n//  DoraemonMLeaksFinderPlugin.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/6.\n//\n\n#import \"DoraemonMLeaksFinderPlugin.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonMLeaksFinderViewController.h\"\n\n@implementation DoraemonMLeaksFinderPlugin\n\n- (void)pluginDidLoad{\n    DoraemonMLeaksFinderViewController *vc = [[DoraemonMLeaksFinderViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/DoraemonMLeaksFinderViewController.h",
    "content": "//\n//  DoraemonMLeaksFinderViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/6.\n//\n\n#import <DoraemonKit/DoraemonKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMLeaksFinderViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/DoraemonMLeaksFinderViewController.m",
    "content": "//\n//  DoraemonMLeaksFinderViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/6.\n//\n\n#import \"DoraemonMLeaksFinderViewController.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"DoraemonCellButton.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonMLeaksFinderListViewController.h\"\n\n\n@interface DoraemonMLeaksFinderViewController ()<DoraemonSwitchViewDelegate,DoraemonCellButtonDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n@property (nonatomic, strong) DoraemonCellSwitch *alertSwitchView;\n@property (nonatomic, strong) DoraemonCellButton *cellBtn;\n\n@end\n\n@implementation DoraemonMLeaksFinderViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"内存泄漏\");\n    \n    _switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, 53)];\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"内存泄漏检测开关\") switchOn:[[DoraemonCacheManager sharedInstance] memoryLeak]];\n    [_switchView needTopLine];\n    [_switchView needDownLine];\n    _switchView.delegate = self;\n    [self.view addSubview:_switchView];\n    \n    _alertSwitchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, _switchView.doraemon_bottom, self.view.doraemon_width, 53)];\n    [_alertSwitchView renderUIWithTitle:DoraemonLocalizedString(@\"内存泄漏检测弹框提醒\") switchOn:[[DoraemonCacheManager sharedInstance] memoryLeakAlert]];\n    [_alertSwitchView needDownLine];\n    _alertSwitchView.delegate = self;\n    [self.view addSubview:_alertSwitchView];\n    \n    _cellBtn = [[DoraemonCellButton alloc] initWithFrame:CGRectMake(0, _alertSwitchView.doraemon_bottom, self.view.doraemon_width, 53)];\n    [_cellBtn renderUIWithTitle:DoraemonLocalizedString(@\"查看检测记录\")];\n    _cellBtn.delegate = self;\n    [_cellBtn needDownLine];\n    [self.view addSubview:_cellBtn];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    if (sender == _switchView.switchView) {\n        __weak typeof(self) weakSelf = self;\n        [DoraemonAlertUtil handleAlertActionWithVC:self okBlock:^{\n            [[DoraemonCacheManager sharedInstance] saveMemoryLeak:on];\n            exit(0);\n        } cancleBlock:^{\n            weakSelf.switchView.switchView.on = !on;\n        }];\n    }else{\n        [[DoraemonCacheManager sharedInstance] saveMemoryLeakAlert:on];\n    }\n}\n\n#pragma mark -- DoraemonCellButtonDelegate\n- (void)cellBtnClick:(id)sender{\n    if (sender == _cellBtn) {\n        DoraemonMLeaksFinderListViewController *vc = [[DoraemonMLeaksFinderListViewController alloc] init];\n        [self.navigationController pushViewController:vc animated:YES];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/Extra/DoraemonMemoryLeakData.h",
    "content": "//\n//  DoraemonMemoryLeakData.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/7.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\ntypedef void (^DoraemonLeakManagerBlock)(NSDictionary *leakInfo);\n\n@interface DoraemonMemoryLeakData : NSObject\n\n+ (DoraemonMemoryLeakData *)shareInstance;\n\n- (void)addLeakBlock:(DoraemonLeakManagerBlock)block;\n\n- (void)addObject:(id)object;\n\n- (void)removeObjectPtr:(NSNumber *)objectPtr;\n\n- (NSArray *)getResult;\n\n- (void)clearResult;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/Extra/DoraemonMemoryLeakData.m",
    "content": "//\n//  DoraemonMemoryLeakData.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/7.\n//\n\n#import \"MLeaksFinder.h\"\n#import \"DoraemonMemoryLeakData.h\"\n#if _INTERNAL_MLF_RC_ENABLED\n#import <FBRetainCycleDetector/FBRetainCycleDetector.h>\n#endif\n#import \"DoraemonHealthManager.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMemoryLeakData()\n\n@property (nonatomic, strong) NSMutableArray *dataArray;\n@property (nonatomic, copy) DoraemonLeakManagerBlock block;\n\n@end\n\n@implementation DoraemonMemoryLeakData\n\n+ (DoraemonMemoryLeakData *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonMemoryLeakData *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonMemoryLeakData alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    self = [super init];\n    if (self) {\n        _dataArray = [NSMutableArray array];\n    }\n    return self;\n}\n\n- (void)addLeakBlock:(DoraemonLeakManagerBlock)block{\n    self.block = block;\n}\n\n- (void)addObject:(id)object{\n    NSString *className = NSStringFromClass([object class]);\n    NSNumber *classPtr = @((uintptr_t)object);\n    NSArray *viewStack = [object viewStack];\n    NSString *retainCycle = [self getRetainCycleByObject:object];\n    \n    NSDictionary *info = @{\n        @\"className\":STRING_NOT_NULL(className),\n        @\"classPtr\":STRING_NOT_NULL(classPtr),\n        @\"viewStack\":STRING_NOT_NULL(viewStack),\n        @\"retainCycle\":STRING_NOT_NULL(retainCycle)\n    };\n    [_dataArray addObject:info];\n    [[DoraemonHealthManager sharedInstance] addLeak:info];\n    \n    if (self.block) {\n        self.block(info);\n    }\n}\n\n- (void)removeObjectPtr:(NSNumber *)objectPtr{\n    for (NSInteger i=_dataArray.count-1; i == 0; i--) {\n        NSDictionary *dic = _dataArray[i];\n        if ([dic[@\"classPtr\"] isEqualToNumber:objectPtr]) {\n            [_dataArray removeObjectAtIndex:i];\n        }\n    }\n}\n\n\n- (NSArray *)getResult{\n    return _dataArray;\n}\n\n- (void)clearResult{\n    [_dataArray removeAllObjects];\n}\n\n- (NSString *)getRetainCycleByObject:(id)object{\n    NSString *result;\n#if _INTERNAL_MLF_RC_ENABLED\n    FBRetainCycleDetector *detector = [FBRetainCycleDetector new];\n    [detector addCandidate:object];\n    NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:20];\n    \n    BOOL hasFound = NO;\n    for (NSArray *retainCycle in retainCycles) {\n        NSInteger index = 0;\n        for (FBObjectiveCGraphElement *element in retainCycle) {\n            if (element.object == object) {\n                NSArray *shiftedRetainCycle = [self shiftArray:retainCycle toIndex:index];\n                \n                result = [NSString stringWithFormat:@\"%@\", shiftedRetainCycle];\n                hasFound = YES;\n                break;\n            }\n            \n            ++index;\n        }\n        if (hasFound) {\n            break;\n        }\n    }\n    if (!hasFound) {\n        result = @\"Fail to find a retain cycle\";\n    }\n#endif\n    return result;\n}\n\n- (NSArray *)shiftArray:(NSArray *)array toIndex:(NSInteger)index {\n    if (index == 0) {\n        return array;\n    }\n    \n    NSRange range = NSMakeRange(index, array.count - index);\n    NSMutableArray *result = [[array subarrayWithRange:range] mutableCopy];\n    [result addObjectsFromArray:[array subarrayWithRange:NSMakeRange(0, index)]];\n    return result;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/MLeakedObjectProxy.h",
    "content": "//\n//  MLeakedObjectProxy.h\n//  MLeaksFinder\n//\n//  Created by 佘泽坡 on 7/15/16.\n//  Copyright © 2016 zeposhe. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface MLeakedObjectProxy : NSObject\n\n+ (BOOL)isAnyObjectLeakedAtPtrs:(NSSet *)ptrs;\n+ (void)addLeakedObject:(id)object;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/MLeakedObjectProxy.m",
    "content": "//\n//  MLeakedObjectProxy.m\n//  MLeaksFinder\n//\n//  Created by 佘泽坡 on 7/15/16.\n//  Copyright © 2016 zeposhe. All rights reserved.\n//\n\n#import \"MLeakedObjectProxy.h\"\n#import \"MLeaksFinder.h\"\n#import <objc/runtime.h>\n#import <UIKit/UIKit.h>\n#import \"DoraemonMemoryLeakData.h\"\n#import \"DoraemonCacheManager.h\"\n#import \"DoraemonAlertUtil.h\"\n#import \"UIViewController+Doraemon.h\"\n#import \"DoraemonAlertUtil.h\"\n#import \"UIViewController+Doraemon.h\"\n\n#if _INTERNAL_MLF_RC_ENABLED\n#import <FBRetainCycleDetector/FBRetainCycleDetector.h>\n#endif\n\nstatic NSMutableSet *leakedObjectPtrs;\n\n@interface MLeakedObjectProxy ()\n@property (nonatomic, weak) id object;\n@property (nonatomic, strong) NSNumber *objectPtr;\n@property (nonatomic, strong) NSArray *viewStack;\n@end\n\n@implementation MLeakedObjectProxy\n\n+ (BOOL)isAnyObjectLeakedAtPtrs:(NSSet *)ptrs {\n    NSAssert([NSThread isMainThread], @\"Must be in main thread.\");\n    \n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        leakedObjectPtrs = [[NSMutableSet alloc] init];\n    });\n    \n    if (!ptrs.count) {\n        return NO;\n    }\n    if ([leakedObjectPtrs intersectsSet:ptrs]) {\n        return YES;\n    } else {\n        return NO;\n    }\n}\n\n+ (void)addLeakedObject:(id)object {\n    NSAssert([NSThread isMainThread], @\"Must be in main thread.\");\n    \n    MLeakedObjectProxy *proxy = [[MLeakedObjectProxy alloc] init];\n    proxy.object = object;\n    proxy.objectPtr = @((uintptr_t)object);\n    proxy.viewStack = [object viewStack];\n    static const void *const kLeakedObjectProxyKey = &kLeakedObjectProxyKey;\n    objc_setAssociatedObject(object, kLeakedObjectProxyKey, proxy, OBJC_ASSOCIATION_RETAIN);\n    \n    [leakedObjectPtrs addObject:proxy.objectPtr];\n    [[DoraemonMemoryLeakData shareInstance] addObject:object];\n    \n    if ([[DoraemonCacheManager sharedInstance] memoryLeakAlert]) {\n        #if _INTERNAL_MLF_RC_ENABLED\n        [DoraemonAlertUtil handleAlertActionWithVC:[UIViewController rootViewControllerForKeyWindow] title:@\"Memory Leak\" text:[NSString stringWithFormat:@\"%@\", proxy.viewStack] ok:@\"OK\" cancel:@\"Retain Cycle\" okBlock:^{\n            \n        } cancleBlock:^{\n            [proxy searchRetainCycle];\n        }];\n        #else\n            [DoraemonAlertUtil handleAlertActionWithVC:[UIViewController rootViewControllerForKeyWindow] title:@\"Memory Leak\" text:[NSString stringWithFormat:@\"%@\", proxy.viewStack] ok:@\"OK\" okBlock:^{\n\n            }];\n        #endif\n    }\n}\n\n- (void)dealloc {\n    NSNumber *objectPtr = _objectPtr;\n    NSArray *viewStack = _viewStack;\n    dispatch_async(dispatch_get_main_queue(), ^{\n        [leakedObjectPtrs removeObject:objectPtr];\n        [[DoraemonMemoryLeakData shareInstance] removeObjectPtr:objectPtr];\n        [DoraemonAlertUtil handleAlertActionWithVC:[UIViewController rootViewControllerForKeyWindow] title:@\"Object Deallocated\" text:[NSString stringWithFormat:@\"%@\", viewStack] ok:@\"OK\" okBlock:^{\n            \n        }];\n    });\n}\n\n- (void)searchRetainCycle{\n    id object = self.object;\n    if (!object) {\n        return;\n    }\n        \n    #if _INTERNAL_MLF_RC_ENABLED\n        dispatch_async(dispatch_get_global_queue(0, 0), ^{\n            FBRetainCycleDetector *detector = [FBRetainCycleDetector new];\n            [detector addCandidate:self.object];\n            NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:20];\n            \n            BOOL hasFound = NO;\n            for (NSArray *retainCycle in retainCycles) {\n                NSInteger index = 0;\n                for (FBObjectiveCGraphElement *element in retainCycle) {\n                    if (element.object == object) {\n                        NSArray *shiftedRetainCycle = [self shiftArray:retainCycle toIndex:index];\n                        \n                        dispatch_async(dispatch_get_main_queue(), ^{\n                            [DoraemonAlertUtil handleAlertActionWithVC:[UIViewController rootViewControllerForKeyWindow] title:@\"Retain Cycle\" text:[NSString stringWithFormat:@\"%@\", shiftedRetainCycle] ok:@\"OK\" okBlock:^{\n                            \n                            }];\n                        });\n                        hasFound = YES;\n                        break;\n                    }\n                    \n                    ++index;\n                }\n                if (hasFound) {\n                    break;\n                }\n            }\n            if (!hasFound) {\n                dispatch_async(dispatch_get_main_queue(), ^{\n                    [DoraemonAlertUtil handleAlertActionWithVC:[UIViewController rootViewControllerForKeyWindow] title:@\"Retain Cycle\" text:@\"Fail to find a retain cycle\" ok:@\"OK\" okBlock:^{\n                        \n                    }];\n                });\n            }\n        });\n    #endif\n}\n\n- (NSArray *)shiftArray:(NSArray *)array toIndex:(NSInteger)index {\n    if (index == 0) {\n        return array;\n    }\n    \n    NSRange range = NSMakeRange(index, array.count - index);\n    NSMutableArray *result = [[array subarrayWithRange:range] mutableCopy];\n    [result addObjectsFromArray:[array subarrayWithRange:NSMakeRange(0, index)]];\n    return result;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/MLeaksFinder.h",
    "content": "//\n//  MLeaksFinder.h\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import \"NSObject+MemoryLeak.h\"\n\n//#define MEMORY_LEAKS_FINDER_ENABLED 0\n\n#ifdef MEMORY_LEAKS_FINDER_ENABLED\n#define _INTERNAL_MLF_ENABLED MEMORY_LEAKS_FINDER_ENABLED\n#else\n#define _INTERNAL_MLF_ENABLED DEBUG\n#endif\n\n#define MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED 0\n\n#if MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED\n#define _INTERNAL_MLF_RC_ENABLED MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED\n#elif __has_include(<FBRetainCycleDetector/FBRetainCycleDetector.h>)\n#define _INTERNAL_MLF_RC_ENABLED 1\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/NSObject+MemoryLeak.h",
    "content": "//\n//  NSObject+MemoryLeak.h\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n\n#define MLCheck(TARGET) [self willReleaseObject:(TARGET) relationship:@#TARGET];\n\n@interface NSObject (MemoryLeak)\n\n- (BOOL)willDealloc;\n- (void)willReleaseObject:(id)object relationship:(NSString *)relationship;\n\n- (void)willReleaseChild:(id)child;\n- (void)willReleaseChildren:(NSArray *)children;\n\n- (NSArray *)viewStack;\n\n+ (void)addClassNamesToWhitelist:(NSArray *)classNames;\n\n+ (void)swizzleSEL:(SEL)originalSEL withSEL:(SEL)swizzledSEL;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/NSObject+MemoryLeak.m",
    "content": "//\n//  NSObject+MemoryLeak.m\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import \"NSObject+MemoryLeak.h\"\n#import \"MLeakedObjectProxy.h\"\n#import \"MLeaksFinder.h\"\n#import <objc/runtime.h>\n#import <UIKit/UIKit.h>\n\n#if _INTERNAL_MLF_RC_ENABLED\n#import <FBRetainCycleDetector/FBRetainCycleDetector.h>\n#endif\n\nstatic const void *const kViewStackKey = &kViewStackKey;\nstatic const void *const kParentPtrsKey = &kParentPtrsKey;\nconst void *const kLatestSenderKey = &kLatestSenderKey;\n\n@implementation NSObject (MemoryLeak)\n\n- (BOOL)willDealloc {\n    NSString *className = NSStringFromClass([self class]);\n    if ([[NSObject classNamesWhitelist] containsObject:className])\n        return NO;\n    \n    NSNumber *senderPtr = objc_getAssociatedObject([UIApplication sharedApplication], kLatestSenderKey);\n    if ([senderPtr isEqualToNumber:@((uintptr_t)self)])\n        return NO;\n    \n    __weak id weakSelf = self;\n    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{\n        __strong id strongSelf = weakSelf;\n        [strongSelf assertNotDealloc];\n    });\n    \n    return YES;\n}\n\n- (void)assertNotDealloc {\n    if ([MLeakedObjectProxy isAnyObjectLeakedAtPtrs:[self parentPtrs]]) {\n        return;\n    }\n    [MLeakedObjectProxy addLeakedObject:self];\n    \n    NSString *className = NSStringFromClass([self class]);\n    NSLog(@\"Possibly Memory Leak.\\nIn case that %@ should not be dealloced, override -willDealloc in %@ by returning NO.\\nView-ViewController stack: %@\", className, className, [self viewStack]);\n}\n\n- (void)willReleaseObject:(id)object relationship:(NSString *)relationship {\n    if ([relationship hasPrefix:@\"self\"]) {\n        relationship = [relationship stringByReplacingCharactersInRange:NSMakeRange(0, 4) withString:@\"\"];\n    }\n    NSString *className = NSStringFromClass([object class]);\n    className = [NSString stringWithFormat:@\"%@(%@), \", relationship, className];\n    \n    [object setViewStack:[[self viewStack] arrayByAddingObject:className]];\n    [object setParentPtrs:[[self parentPtrs] setByAddingObject:@((uintptr_t)object)]];\n    [object willDealloc];\n}\n\n- (void)willReleaseChild:(id)child {\n    if (!child) {\n        return;\n    }\n    \n    [self willReleaseChildren:@[ child ]];\n}\n\n- (void)willReleaseChildren:(NSArray *)children {\n    NSArray *viewStack = [self viewStack];\n    NSSet *parentPtrs = [self parentPtrs];\n    for (id child in children) {\n        NSString *className = NSStringFromClass([child class]);\n        [child setViewStack:[viewStack arrayByAddingObject:className]];\n        [child setParentPtrs:[parentPtrs setByAddingObject:@((uintptr_t)child)]];\n        [child willDealloc];\n    }\n}\n\n- (NSArray *)viewStack {\n    NSArray *viewStack = objc_getAssociatedObject(self, kViewStackKey);\n    if (viewStack) {\n        return viewStack;\n    }\n    \n    NSString *className = NSStringFromClass([self class]);\n    return @[ className ];\n}\n\n- (void)setViewStack:(NSArray *)viewStack {\n    objc_setAssociatedObject(self, kViewStackKey, viewStack, OBJC_ASSOCIATION_RETAIN);\n}\n\n- (NSSet *)parentPtrs {\n    NSSet *parentPtrs = objc_getAssociatedObject(self, kParentPtrsKey);\n    if (!parentPtrs) {\n        parentPtrs = [[NSSet alloc] initWithObjects:@((uintptr_t)self), nil];\n    }\n    return parentPtrs;\n}\n\n- (void)setParentPtrs:(NSSet *)parentPtrs {\n    objc_setAssociatedObject(self, kParentPtrsKey, parentPtrs, OBJC_ASSOCIATION_RETAIN);\n}\n\n+ (NSMutableSet *)classNamesWhitelist {\n    static NSMutableSet *whitelist = nil;\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        whitelist = [NSMutableSet setWithObjects:\n                     @\"UIFieldEditor\", // UIAlertControllerTextField\n                     @\"UINavigationBar\",\n                     @\"_UIAlertControllerActionView\",\n                     @\"_UIVisualEffectBackdropView\",\n                     nil];\n        \n        // System's bug since iOS 10 and not fixed yet up to this ci.\n        NSString *systemVersion = [UIDevice currentDevice].systemVersion;\n        if ([systemVersion compare:@\"10.0\" options:NSNumericSearch] != NSOrderedAscending) {\n            [whitelist addObject:@\"UISwitch\"];\n        }\n    });\n    return whitelist;\n}\n\n+ (void)addClassNamesToWhitelist:(NSArray *)classNames {\n    [[self classNamesWhitelist] addObjectsFromArray:classNames];\n}\n\n+ (void)swizzleSEL:(SEL)originalSEL withSEL:(SEL)swizzledSEL {\n#if _INTERNAL_MLF_ENABLED\n    \n#if _INTERNAL_MLF_RC_ENABLED\n    // Just find a place to set up FBRetainCycleDetector.\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        dispatch_async(dispatch_get_main_queue(), ^{\n            [FBAssociationManager hook];\n        });\n    });\n#endif\n    \n    Class class = [self class];\n    \n    Method originalMethod = class_getInstanceMethod(class, originalSEL);\n    Method swizzledMethod = class_getInstanceMethod(class, swizzledSEL);\n    \n    BOOL didAddMethod =\n    class_addMethod(class,\n                    originalSEL,\n                    method_getImplementation(swizzledMethod),\n                    method_getTypeEncoding(swizzledMethod));\n    \n    if (didAddMethod) {\n        class_replaceMethod(class,\n                            swizzledSEL,\n                            method_getImplementation(originalMethod),\n                            method_getTypeEncoding(originalMethod));\n    } else {\n        method_exchangeImplementations(originalMethod, swizzledMethod);\n    }\n#endif\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UIApplication+MemoryLeak.h",
    "content": "//\n//  UIApplication+MemoryLeak.h\n//  MLeaksFinder\n//\n//  Created by 佘泽坡 on 5/11/16.\n//  Copyright © 2016 zeposhe. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"MLeaksFinder.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\n@interface UIApplication (MemoryLeak)\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UIApplication+MemoryLeak.m",
    "content": "//\n//  UIApplication+MemoryLeak.m\n//  MLeaksFinder\n//\n//  Created by 佘泽坡 on 5/11/16.\n//  Copyright © 2016 zeposhe. All rights reserved.\n//\n\n#import \"UIApplication+MemoryLeak.h\"\n#import \"NSObject+MemoryLeak.h\"\n#import <objc/runtime.h>\n#import \"DoraemonCacheManager.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\nextern const void *const kLatestSenderKey;\n\n@implementation UIApplication (MemoryLeak)\n\n+ (void)load {\n    if ([[DoraemonCacheManager sharedInstance] memoryLeak]){\n        static dispatch_once_t onceToken;\n        dispatch_once(&onceToken, ^{\n            [self swizzleSEL:@selector(sendAction:to:from:forEvent:) withSEL:@selector(swizzled_sendAction:to:from:forEvent:)];\n        });\n    }\n}\n\n- (BOOL)swizzled_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event {\n    objc_setAssociatedObject(self, kLatestSenderKey, @((uintptr_t)sender), OBJC_ASSOCIATION_RETAIN);\n    \n    return [self swizzled_sendAction:action to:target from:sender forEvent:event];\n}\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UINavigationController+MemoryLeak.h",
    "content": "//\n//  UINavigationController+MemoryLeak.h\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"MLeaksFinder.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\n@interface UINavigationController (MemoryLeak)\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UINavigationController+MemoryLeak.m",
    "content": "//\n//  UINavigationController+MemoryLeak.m\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import \"UINavigationController+MemoryLeak.h\"\n#import \"NSObject+MemoryLeak.h\"\n#import <objc/runtime.h>\n#import \"DoraemonCacheManager.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\nstatic const void *const kPoppedDetailVCKey = &kPoppedDetailVCKey;\n\n@implementation UINavigationController (MemoryLeak)\n\n+ (void)load {\n    if ([[DoraemonCacheManager sharedInstance]  memoryLeak]){\n        static dispatch_once_t onceToken;\n        dispatch_once(&onceToken, ^{\n            [self swizzleSEL:@selector(pushViewController:animated:) withSEL:@selector(swizzled_pushViewController:animated:)];\n            [self swizzleSEL:@selector(popViewControllerAnimated:) withSEL:@selector(swizzled_popViewControllerAnimated:)];\n            [self swizzleSEL:@selector(popToViewController:animated:) withSEL:@selector(swizzled_popToViewController:animated:)];\n            [self swizzleSEL:@selector(popToRootViewControllerAnimated:) withSEL:@selector(swizzled_popToRootViewControllerAnimated:)];\n        });\n    }\n}\n\n- (void)swizzled_pushViewController:(UIViewController *)viewController animated:(BOOL)animated {\n    if (self.splitViewController) {\n        id detailViewController = objc_getAssociatedObject(self, kPoppedDetailVCKey);\n        if ([detailViewController isKindOfClass:[UIViewController class]]) {\n            [detailViewController willDealloc];\n            objc_setAssociatedObject(self, kPoppedDetailVCKey, nil, OBJC_ASSOCIATION_RETAIN);\n        }\n    }\n    \n    [self swizzled_pushViewController:viewController animated:animated];\n}\n\n- (UIViewController *)swizzled_popViewControllerAnimated:(BOOL)animated {\n    UIViewController *poppedViewController = [self swizzled_popViewControllerAnimated:animated];\n    \n    if (!poppedViewController) {\n        return nil;\n    }\n    \n    // Detail VC in UISplitViewController is not dealloced until another detail VC is shown\n    if (self.splitViewController &&\n        self.splitViewController.viewControllers.firstObject == self &&\n        self.splitViewController == poppedViewController.splitViewController) {\n        objc_setAssociatedObject(self, kPoppedDetailVCKey, poppedViewController, OBJC_ASSOCIATION_RETAIN);\n        return poppedViewController;\n    }\n    \n    // VC is not dealloced until disappear when popped using a left-edge swipe gesture\n    extern const void *const kHasBeenPoppedKey;\n    objc_setAssociatedObject(poppedViewController, kHasBeenPoppedKey, @(YES), OBJC_ASSOCIATION_RETAIN);\n    \n    return poppedViewController;\n}\n\n- (NSArray<UIViewController *> *)swizzled_popToViewController:(UIViewController *)viewController animated:(BOOL)animated {\n    NSArray<UIViewController *> *poppedViewControllers = [self swizzled_popToViewController:viewController animated:animated];\n    \n    for (UIViewController *viewController in poppedViewControllers) {\n        [viewController willDealloc];\n    }\n    \n    return poppedViewControllers;\n}\n\n- (NSArray<UIViewController *> *)swizzled_popToRootViewControllerAnimated:(BOOL)animated {\n    NSArray<UIViewController *> *poppedViewControllers = [self swizzled_popToRootViewControllerAnimated:animated];\n    \n    for (UIViewController *viewController in poppedViewControllers) {\n        [viewController willDealloc];\n    }\n    \n    return poppedViewControllers;\n}\n\n- (BOOL)willDealloc {\n    if (![super willDealloc]) {\n        return NO;\n    }\n    \n    [self willReleaseChildren:self.viewControllers];\n    \n    return YES;\n}\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UIPageViewController+MemoryLeak.h",
    "content": "//\n//  UIPageViewController+MemoryLeak.h\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"MLeaksFinder.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\n@interface UIPageViewController (MemoryLeak)\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UIPageViewController+MemoryLeak.m",
    "content": "//\n//  UIPageViewController+MemoryLeak.m\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import \"UIPageViewController+MemoryLeak.h\"\n#import \"NSObject+MemoryLeak.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\n@implementation UIPageViewController (MemoryLeak)\n\n- (BOOL)willDealloc {\n    if (![super willDealloc]) {\n        return NO;\n    }\n    \n    [self willReleaseChildren:self.viewControllers];\n    \n    return YES;\n}\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UISplitViewController+MemoryLeak.h",
    "content": "//\n//  UISplitViewController+MemoryLeak.h\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"MLeaksFinder.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\n@interface UISplitViewController (MemoryLeak)\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UISplitViewController+MemoryLeak.m",
    "content": "//\n//  UISplitViewController+MemoryLeak.m\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import \"UISplitViewController+MemoryLeak.h\"\n#import \"NSObject+MemoryLeak.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\n@implementation UISplitViewController (MemoryLeak)\n\n- (BOOL)willDealloc {\n    if (![super willDealloc]) {\n        return NO;\n    }\n    \n    [self willReleaseChildren:self.viewControllers];\n    \n    return YES;\n}\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UITabBarController+MemoryLeak.h",
    "content": "//\n//  UITabBarController+MemoryLeak.h\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"MLeaksFinder.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\n@interface UITabBarController (MemoryLeak)\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UITabBarController+MemoryLeak.m",
    "content": "//\n//  UITabBarController+MemoryLeak.m\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import \"UITabBarController+MemoryLeak.h\"\n#import \"NSObject+MemoryLeak.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\n@implementation UITabBarController (MemoryLeak)\n\n- (BOOL)willDealloc {\n    if (![super willDealloc]) {\n        return NO;\n    }\n    \n    [self willReleaseChildren:self.viewControllers];\n    \n    return YES;\n}\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UITouch+MemoryLeak.h",
    "content": "//\n//  UITouch+MemoryLeak.h\n//  MLeaksFinder\n//\n//  Created by 佘泽坡 on 8/31/16.\n//  Copyright © 2016 zeposhe. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"MLeaksFinder.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\n@interface UITouch (MemoryLeak)\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UITouch+MemoryLeak.m",
    "content": "//\n//  UITouch+MemoryLeak.m\n//  MLeaksFinder\n//\n//  Created by 佘泽坡 on 8/31/16.\n//  Copyright © 2016 zeposhe. All rights reserved.\n//\n\n#import \"UITouch+MemoryLeak.h\"\n#import <objc/runtime.h>\n#import \"DoraemonCacheManager.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\nextern const void *const kLatestSenderKey;\n\n@implementation UITouch (MemoryLeak)\n\n+ (void)load {\n    if ([[DoraemonCacheManager sharedInstance]  memoryLeak]){\n        static dispatch_once_t onceToken;\n        dispatch_once(&onceToken, ^{\n            [self swizzleSEL:@selector(setView:) withSEL:@selector(swizzled_setView:)];\n        });\n    }\n}\n\n- (void)swizzled_setView:(UIView *)view {\n    [self swizzled_setView:view];\n    \n    if (view) {\n        objc_setAssociatedObject([UIApplication sharedApplication],\n                                 kLatestSenderKey,\n                                 @((uintptr_t)view),\n                                 OBJC_ASSOCIATION_RETAIN);\n    }\n}\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UIView+MemoryLeak.h",
    "content": "//\n//  UIView+MemoryLeak.h\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"MLeaksFinder.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\n@interface UIView (MemoryLeak)\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UIView+MemoryLeak.m",
    "content": "//\n//  UIView+MemoryLeak.m\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import \"UIView+MemoryLeak.h\"\n#import \"NSObject+MemoryLeak.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\n@implementation UIView (MemoryLeak)\n\n- (BOOL)willDealloc {\n    if (![super willDealloc]) {\n        return NO;\n    }\n    \n    [self willReleaseChildren:self.subviews];\n    \n    return YES;\n}\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UIViewController+MemoryLeak.h",
    "content": "//\n//  UIViewController+MemoryLeak.h\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"MLeaksFinder.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\n@interface UIViewController (MemoryLeak)\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/Function/UIViewController+MemoryLeak.m",
    "content": "//\n//  UIViewController+MemoryLeak.m\n//  MLeaksFinder\n//\n//  Created by zeposhe on 12/12/15.\n//  Copyright © 2015 zeposhe. All rights reserved.\n//\n\n#import \"UIViewController+MemoryLeak.h\"\n#import \"NSObject+MemoryLeak.h\"\n#import <objc/runtime.h>\n#import \"DoraemonCacheManager.h\"\n\n#if _INTERNAL_MLF_ENABLED\n\nconst void *const kHasBeenPoppedKey = &kHasBeenPoppedKey;\n\n@implementation UIViewController (MemoryLeak)\n\n+ (void)load {\n    if ([[DoraemonCacheManager sharedInstance]  memoryLeak]){\n        static dispatch_once_t onceToken;\n        dispatch_once(&onceToken, ^{\n            [self swizzleSEL:@selector(viewDidDisappear:) withSEL:@selector(swizzled_viewDidDisappear:)];\n            [self swizzleSEL:@selector(viewWillAppear:) withSEL:@selector(swizzled_viewWillAppear:)];\n            [self swizzleSEL:@selector(dismissViewControllerAnimated:completion:) withSEL:@selector(swizzled_dismissViewControllerAnimated:completion:)];\n        });\n    }\n}\n\n- (void)swizzled_viewDidDisappear:(BOOL)animated {\n    [self swizzled_viewDidDisappear:animated];\n    \n    if ([objc_getAssociatedObject(self, kHasBeenPoppedKey) boolValue]) {\n        [self willDealloc];\n    }\n}\n\n- (void)swizzled_viewWillAppear:(BOOL)animated {\n    [self swizzled_viewWillAppear:animated];\n    \n    objc_setAssociatedObject(self, kHasBeenPoppedKey, @(NO), OBJC_ASSOCIATION_RETAIN);\n}\n\n- (void)swizzled_dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {\n    [self swizzled_dismissViewControllerAnimated:flag completion:completion];\n    \n    UIViewController *dismissedViewController = self.presentedViewController;\n    if (!dismissedViewController && self.presentingViewController) {\n        dismissedViewController = self;\n    }\n    \n    if (!dismissedViewController) return;\n    \n    [dismissedViewController willDealloc];\n}\n\n- (BOOL)willDealloc {\n    if (![super willDealloc]) {\n        return NO;\n    }\n    \n    [self willReleaseChildren:self.childViewControllers];\n    [self willReleaseChild:self.presentedViewController];\n    \n    if (self.isViewLoaded) {\n        [self willReleaseChild:self.view];\n    }\n    \n    return YES;\n}\n\n@end\n\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/List/DoraemonMLeaksFinderListCell.h",
    "content": "//\n//  DoraemonMLeaksFinderListCell.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/7.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMLeaksFinderListCell : UITableViewCell\n\n- (void)renderCellWithData:(NSDictionary *)dic;\n\n+ (CGFloat)cellHeight;\n\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/List/DoraemonMLeaksFinderListCell.m",
    "content": "//\n//  DoraemonMLeaksFinderListCell.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/7.\n//\n\n#import \"DoraemonMLeaksFinderListCell.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMLeaksFinderListCell()\n\n@property (nonatomic, strong) UILabel *titleLabel;\n@property (nonatomic, strong) UILabel *rightLabel;\n\n@end\n\n@implementation DoraemonMLeaksFinderListCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        self.selectionStyle = UITableViewCellSelectionStyleNone;\n        \n        _titleLabel = [[UILabel alloc] init];\n        _titleLabel.textColor = [UIColor doraemon_black_1];\n        _titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)];\n        [self.contentView addSubview:_titleLabel];\n    }\n    return self;\n}\n\n- (void)renderCellWithData:(NSDictionary *)dic{\n    self.titleLabel.text = dic[@\"className\"];\n    [self.titleLabel sizeToFit];\n    self.titleLabel.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), [[self class] cellHeight]/2-self.titleLabel.doraemon_height/2, DoraemonScreenWidth - 120, self.titleLabel.doraemon_height);\n    self.titleLabel.adjustsFontSizeToFitWidth = YES;\n    self.titleLabel.minimumScaleFactor = 0.1;\n    \n}\n\n+ (CGFloat)cellHeight{\n    return kDoraemonSizeFrom750_Landscape(104);\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/List/DoraemonMLeaksFinderListViewController.h",
    "content": "//\n//  DoraemonMLeaksFinderListViewController.h\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/6.\n//\n\n#import <DoraemonKit/DoraemonKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMLeaksFinderListViewController : DoraemonBaseViewController\n\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MLeaksFinder/List/DoraemonMLeaksFinderListViewController.m",
    "content": "//\n//  DoraemonMLeaksFinderListViewController.m\n//  DoraemonKit\n//\n//  Created by didi on 2019/10/6.\n//\n\n#import \"DoraemonMLeaksFinderListViewController.h\"\n#import \"DoraemonMemoryLeakData.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonMLeaksFinderListCell.h\"\n#import \"DoraemonMLeaksFinderDetailViewController.h\"\n\n@interface DoraemonMLeaksFinderListViewController ()<UITableViewDelegate,UITableViewDataSource>\n\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, copy) NSArray *dataArray;\n\n@end\n\n@implementation DoraemonMLeaksFinderListViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"内存泄漏检测结果\");\n    \n    _dataArray = [ NSArray arrayWithArray:[[DoraemonMemoryLeakData shareInstance] getResult]];\n    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.doraemon_width, self.view.doraemon_height) style:UITableViewStylePlain];\n//    self.tableView.backgroundColor = [UIColor whiteColor];\n    self.tableView.delegate = self;\n    self.tableView.dataSource = self;\n    [self.view addSubview:self.tableView];\n}\n\n#pragma mark - UITableView Delegate\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {\n    return 1;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return _dataArray.count;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return [DoraemonMLeaksFinderListCell cellHeight];\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {\n    return 0.001;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {\n    return 0.001;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"DoraemonMLeaksFinderListCellID\";\n    DoraemonMLeaksFinderListCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[DoraemonMLeaksFinderListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];\n    }\n    NSDictionary* dic = [_dataArray objectAtIndex:indexPath.row];\n    [cell renderCellWithData:dic];\n    return cell;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{\n    NSDictionary* dic = [_dataArray objectAtIndex:indexPath.row];\n    DoraemonMLeaksFinderDetailViewController *vc = [[DoraemonMLeaksFinderDetailViewController alloc] init];\n    vc.info = dic;\n    [self.navigationController pushViewController:vc animated:YES];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MethodUseTime/DoraemonMethodUseTimePlugin.h",
    "content": "//\n//  DoraemonMethodUseTimePlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/1/18.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMethodUseTimePlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MethodUseTime/DoraemonMethodUseTimePlugin.m",
    "content": "//\n//  DoraemonMethodUseTimePlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/1/18.\n//\n\n#import \"DoraemonMethodUseTimePlugin.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonMethodUseTimeViewController.h\"\n\n@implementation DoraemonMethodUseTimePlugin\n\n- (void)pluginDidLoad{\n    DoraemonMethodUseTimeViewController *vc = [[DoraemonMethodUseTimeViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MethodUseTime/DoraemonMethodUseTimeViewController.h",
    "content": "//\n//  DoraemonMethodUseTimeViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/1/18.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMethodUseTimeViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MethodUseTime/DoraemonMethodUseTimeViewController.m",
    "content": "//\n//  DoraemonMethodUseTimeViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/1/18.\n//\n\n#import \"DoraemonMethodUseTimeViewController.h\"\n#import \"DoraemonCellSwitch.h\"\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonCellButton.h\"\n#import \"Doraemoni18NUtil.h\"\n#import \"DoraemonMethodUseTimeManager.h\"\n#import \"DoraemonMethodUseTimeListViewController.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMethodUseTimeViewController ()<DoraemonSwitchViewDelegate,DoraemonCellButtonDelegate>\n\n@property (nonatomic, strong) DoraemonCellSwitch *switchView;\n@property (nonatomic, strong) DoraemonCellButton *cellBtn;\n\n@end\n\n@implementation DoraemonMethodUseTimeViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"Load耗时\");\n    \n    _switchView = [[DoraemonCellSwitch alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, 53)];\n    [_switchView renderUIWithTitle:DoraemonLocalizedString(@\"Load耗时检测开关\") switchOn:[DoraemonMethodUseTimeManager sharedInstance].on];\n    [_switchView needTopLine];\n    [_switchView needDownLine];\n    _switchView.delegate = self;\n    [self.view addSubview:_switchView];\n    \n    _cellBtn = [[DoraemonCellButton alloc] initWithFrame:CGRectMake(0, _switchView.doraemon_bottom, self.view.doraemon_width, 53)];\n    [_cellBtn renderUIWithTitle:DoraemonLocalizedString(@\"查看检测记录\")];\n    _cellBtn.delegate = self;\n    [_cellBtn needDownLine];\n    [self.view addSubview:_cellBtn];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n#pragma mark -- DoraemonSwitchViewDelegate\n- (void)changeSwitchOn:(BOOL)on sender:(id)sender{\n    __weak typeof(self) weakSelf = self;\n    [DoraemonAlertUtil handleAlertActionWithVC:self okBlock:^{\n        [DoraemonMethodUseTimeManager sharedInstance].on = on;\n        exit(0);\n    } cancleBlock:^{\n        weakSelf.switchView.switchView.on = !on;\n    }];\n}\n\n#pragma mark -- DoraemonCellButtonDelegate\n- (void)cellBtnClick:(id)sender{\n    if (sender == _cellBtn) {\n        DoraemonMethodUseTimeListViewController *vc = [[DoraemonMethodUseTimeListViewController alloc] init];\n        [self.navigationController pushViewController:vc animated:YES];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MethodUseTime/Function/DoraemonMethodUseTimeManager.h",
    "content": "//\n//  DoraemonMethodUseTimeManager.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/1/18.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMethodUseTimeManager : NSObject\n\n+ (instancetype)sharedInstance;\n\n@property (nonatomic, assign) BOOL on;\n\n- (NSArray *)fixLoadModelArray;\n\n- (NSArray *)fixLoadModelArrayForHealth;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MethodUseTime/Function/DoraemonMethodUseTimeManager.m",
    "content": "//\n//  DoraemonMethodUseTimeManager.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/1/18.\n//\n\n#import \"DoraemonMethodUseTimeManager.h\"\n#import \"DoraemonCacheManager.h\"\n#import <DoraemonLoadAnalyze/DoraemonLoadAnalyze.h>\n#import \"DoraemonDefine.h\"\n\n@implementation DoraemonMethodUseTimeManager\n\n+ (instancetype)sharedInstance {\n    static id instance = nil;\n    static dispatch_once_t onceToken;\n    \n    dispatch_once(&onceToken, ^{\n        instance = [[self alloc] init];\n    });\n    return instance;\n}\n\n- (void)setOn:(BOOL)on{\n    [[DoraemonCacheManager sharedInstance] saveMethodUseTimeSwitch:on];\n}\n\n- (BOOL)on{\n    return [[DoraemonCacheManager sharedInstance] methodUseTimeSwitch];\n}\n\n- (NSArray *)fixLoadModelArray{\n    NSMutableArray *loadModelArray = [NSMutableArray arrayWithArray:dlaLoadModels];\n    [loadModelArray sortUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {\n        CGFloat costA = [obj1[@\"cost\"] floatValue];\n        CGFloat costB = [obj2[@\"cost\"] floatValue];\n        if (costA < costB) {\n            return NSOrderedDescending;\n        }else{\n            return NSOrderedAscending;\n        }\n    }];\n    CGFloat allCost = 0.f;\n    if(loadModelArray && loadModelArray.count>0){\n        for (NSDictionary *dic in loadModelArray) {\n            CGFloat cost = [dic[@\"cost\"] floatValue];\n            allCost += cost;\n        }\n        NSDictionary *allDic = @{\n                                 @\"name\":DoraemonLocalizedString(@\"总共耗时\"),\n                                 @\"cost\":@(allCost)\n                                 };\n        [loadModelArray insertObject:allDic atIndex:0];\n    }\n    return [NSArray arrayWithArray:loadModelArray];\n}\n\n- (NSArray *)fixLoadModelArrayForHealth{\n    NSMutableArray *loadModelArray = [NSMutableArray arrayWithArray:dlaLoadModels];\n    [loadModelArray sortUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {\n        CGFloat costA = [obj1[@\"cost\"] floatValue];\n        CGFloat costB = [obj2[@\"cost\"] floatValue];\n        if (costA < costB) {\n            return NSOrderedDescending;\n        }else{\n            return NSOrderedAscending;\n        }\n    }];\n    NSMutableArray *fixArrayForHealth = [[NSMutableArray alloc] init];\n    for (NSDictionary *dic in loadModelArray) {\n        NSString *className = dic[@\"name\"];\n        NSString *costTime = dic[@\"cost\"];\n        [fixArrayForHealth addObject:@{\n            @\"className\" : className,\n            @\"costTime\" : costTime//单位ms\n        }];\n    }\n    return [NSArray arrayWithArray:fixArrayForHealth];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MethodUseTime/List/DoraemonMethodUseTimeListCell.h",
    "content": "//\n//  DoraemonMethodUseTimeListCell.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/1/23.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMethodUseTimeListCell : UITableViewCell\n\n- (void)renderCellWithData:(NSDictionary *)dic;\n\n+ (CGFloat)cellHeight;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MethodUseTime/List/DoraemonMethodUseTimeListCell.m",
    "content": "//\n//  DoraemonMethodUseTimeListCell.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/1/23.\n//\n\n#import \"DoraemonMethodUseTimeListCell.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonMethodUseTimeListCell()\n\n@property (nonatomic, strong) UILabel *titleLabel;\n@property (nonatomic, strong) UILabel *rightLabel;\n\n@end\n\n@implementation DoraemonMethodUseTimeListCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        self.selectionStyle = UITableViewCellSelectionStyleNone;\n        \n        _titleLabel = [[UILabel alloc] init];\n        _titleLabel.textColor = [UIColor doraemon_black_1];\n        _titleLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)];\n        [self.contentView addSubview:_titleLabel];\n        \n        _rightLabel = [[UILabel alloc] init]; \n        _rightLabel.textColor = [UIColor doraemon_blue];\n        _rightLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750_Landscape(28)];\n        [self.contentView addSubview:_rightLabel];\n    }\n    return self;\n}\n\n- (void)renderCellWithData:(NSDictionary *)dic{\n    self.titleLabel.text = dic[@\"name\"];\n    [self.titleLabel sizeToFit];\n    self.titleLabel.frame = CGRectMake(kDoraemonSizeFrom750_Landscape(32), [[self class] cellHeight]/2-self.titleLabel.doraemon_height/2, DoraemonScreenWidth - 120, self.titleLabel.doraemon_height);\n    self.titleLabel.adjustsFontSizeToFitWidth = YES;\n    self.titleLabel.minimumScaleFactor = 0.1;\n\n    \n    CGFloat cost = [dic[@\"cost\"] floatValue];\n    NSString *costString = [NSString stringWithFormat:@\"%.3fms\",cost];\n    self.rightLabel.text = costString;\n    [self.rightLabel sizeToFit];\n    self.rightLabel.frame = CGRectMake(DoraemonScreenWidth - kDoraemonSizeFrom750_Landscape(32) - self.rightLabel.doraemon_width, [[self class] cellHeight]/2-self.rightLabel.doraemon_height/2, self.rightLabel.doraemon_width, self.rightLabel.doraemon_height);\n    \n}\n\n+ (CGFloat)cellHeight{\n    return kDoraemonSizeFrom750_Landscape(104);\n}\n\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MethodUseTime/List/DoraemonMethodUseTimeListViewController.h",
    "content": "//\n//  DoraemonMethodUseTimeListViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/1/18.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMethodUseTimeListViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MethodUseTime/List/DoraemonMethodUseTimeListViewController.m",
    "content": "//\n//  DoraemonMethodUseTimeListViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/1/18.\n//\n\n#import \"DoraemonMethodUseTimeListViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonMethodUseTimeListCell.h\"\n#import \"DoraemonMethodUseTimeManager.h\"\n\n@interface DoraemonMethodUseTimeListViewController ()<UITableViewDelegate,UITableViewDataSource>\n\n@property (nonatomic, copy) NSArray *loadModelArray;\n@property (nonatomic, strong) UITableView *tableView;\n\n@end\n\n@implementation DoraemonMethodUseTimeListViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"Load耗时检测记录\") ;\n    \n    _loadModelArray = [[DoraemonMethodUseTimeManager sharedInstance] fixLoadModelArray];\n    \n    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.doraemon_width, self.view.doraemon_height) style:UITableViewStylePlain];\n//    self.tableView.backgroundColor = [UIColor whiteColor];\n    self.tableView.delegate = self;\n    self.tableView.dataSource = self;\n    [self.view addSubview:self.tableView];\n}\n\n#pragma mark - UITableView Delegate\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {\n    return 1;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return _loadModelArray.count;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return [DoraemonMethodUseTimeListCell cellHeight];\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {\n    return 0.1;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {\n    return 0.1;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"httpcell\";\n    DoraemonMethodUseTimeListCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[DoraemonMethodUseTimeListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];\n    }\n    NSDictionary* dic = [_loadModelArray objectAtIndex:indexPath.row];\n    [cell renderCellWithData:dic];\n    return cell;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Communication/DoraemonMCClient.h",
    "content": "//\n//  DoraemonMCClient.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/6/30.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMCClient : NSObject\n\n+ (void)showToast:(NSString *)toastContent;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Communication/DoraemonMCClient.m",
    "content": "//\n//  DoraemonMCClient.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/6/30.\n//\n\n#import \"DoraemonMCClient.h\"\n#import <SocketRocket/SocketRocket.h>\n#import \"DoraemonToastUtil.h\"\n#import \"DoraemonMCCommandExcutor.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonManager.h\"\n#import \"UIColor+Doraemon.h\"\n\n@implementation DoraemonMCClient\n\n+ (void)showToast:(NSString *)toastContent {\n    if (![toastContent isKindOfClass:[NSString class]] ||\n        toastContent.length == 0) {\n        return;\n    }\n    UIWindow *currentWindow = nil;\n    if ([DoraemonHomeWindow shareInstance].hidden) {\n        currentWindow = [UIApplication sharedApplication].keyWindow;\n    }else {\n        currentWindow = [DoraemonHomeWindow shareInstance];\n    }\n    if ([NSThread currentThread].isMainThread) {\n        [DoraemonToastUtil showToastBlack:toastContent inView:currentWindow];\n    }else {\n        dispatch_async(dispatch_get_main_queue(), ^{\n            [DoraemonToastUtil showToastBlack:toastContent inView:currentWindow];\n        });\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Communication/DoraemonMCServer.h",
    "content": "//\n//  DoraemonMCServer.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/6/30.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMCServer : NSObject\n\n+ (void)sendMessage:(NSString *)message;\n\n+ (BOOL)isOpen;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Communication/DoraemonMCServer.m",
    "content": "//\n//  DoraemonMCServer.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/6/30.\n//\n\n#import \"DoraemonMCServer.h\"\n#import <DoraemonKit/DKMultiControlStreamManager.h>\n\n@implementation DoraemonMCServer\n\n+ (void)sendMessage:(NSString *)message {\n    [DKMultiControlStreamManager.sharedInstance broadcastWithActionMessage:message];\n}\n\n\n+ (BOOL)isOpen {\n    return DKMultiControlStreamManager.sharedInstance.state == DKMultiControlStreamManagerStateMaster;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Dispatcher/DoraemonMCCommandExcutor.h",
    "content": "//\n//  DoraemonMCCommandExcutor.h\n//  DoraemonKit-DoraemonKit\n//\n//  Created by litianhao on 2021/7/12.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonMCMessagePackager.h\"\n#import \"DoraemonMCEventHandler.h\"\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMCCommandExcutor : NSObject\n\n+ (void)excuteMessageStrFromNet:(NSString *)message;\n\n\n+ (void)excuteMessage:(DoraemonMCMessage *)message;\n\n\n//增加自定义事件\n+ (void)addCustomMessage:(NSString *)type eventHandlerName:(DoraemonMCEventHandler *)eventHandler;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Dispatcher/DoraemonMCCommandExcutor.m",
    "content": "//\n//  DoraemonMCCommandExcutor.m\n//  DoraemonKit-DoraemonKit\n//\n//  Created by litianhao on 2021/7/12.\n//\n\n#import \"DoraemonMCCommandExcutor.h\"\n#import \"DoraemonMCEventHandler.h\"\n\nstatic NSMutableDictionary *eventHandlerMap = nil;\nstatic NSMutableDictionary *externalEventHandlerMap = nil;\n\n@implementation DoraemonMCCommandExcutor\n\n+ (void)excuteMessageStrFromNet:(NSString *)message {\n    DoraemonMCMessage *messageInstance = [DoraemonMCMessagePackager parseMessageString:message];\n    [self excuteMessage:messageInstance];\n    \n}\n\n+ (void)excuteMessage:(DoraemonMCMessage *)message {\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        DoraemonMCReuseCellEventHandler *handlerReuseCell =  [DoraemonMCReuseCellEventHandler new];\n        eventHandlerMap = @{\n            @(DoraemonMCMessageTypeControl): [DoraemonMCControlEventHandler new],\n            @(DoraemonMCMessageTypeDidSelectCell) : handlerReuseCell,\n            @(DoraemonMCMessageTypeDidScrollToCell) : handlerReuseCell,\n            @(DoraemonMCMessageTypeGuesture) : [DoraemonMCGestureRecognizerEventHandler new],\n            @(DoraemonMCMessageTypeTextInput) : [DoraemonMCTextFiledEventHandler new],\n            @(DoraemonMCMessageTypeTarbarSelected) : [DoraemonMCTabbarEventHandler new]\n        };\n        externalEventHandlerMap = [NSMutableDictionary new];\n    });\n\n    [eventHandlerMap enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull typeNumber, DoraemonMCEventHandler * _Nonnull eventHandler, BOOL * _Nonnull stop) {\n        if (message.type  == typeNumber.intValue) {\n            [eventHandler handleEvent:message];\n            *stop = YES;\n        }\n    }];\n    \n    \n    [externalEventHandlerMap enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull typeString, DoraemonMCEventHandler * _Nonnull eventHandler, BOOL * _Nonnull stop) {\n        if ([message.customType isEqualToString:typeString]) {\n            [eventHandler handleEvent:message];\n            *stop = YES;\n        }\n    }];\n\n    \n}\n\n//增加自定义事件\n+ (void)addCustomMessage:(NSString *)type eventHandlerName:(DoraemonMCEventHandler *)eventHandler {\n    if (eventHandler && type) {\n        \n        [externalEventHandlerMap enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull typeString, DoraemonMCEventHandler * _Nonnull eventHandler, BOOL * _Nonnull stop) {\n            if ([type isEqualToString:typeString]) {\n                *stop = YES;\n                NSAssert(stop, @\"重复添加事件\");\n            }\n        }];\n        \n        [externalEventHandlerMap setValue:eventHandler forKey:type];\n    }\n}\n\n\n\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Dispatcher/DoraemonMCCommandGenerator.h",
    "content": "//\n//  DoraemonMCCommandGenerator.h\n//  DoraemonKit-DoraemonKit\n//\n//  Created by litianhao on 2021/7/12.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonMCMessagePackager.h\"\n\n\n\n@interface DoraemonMCCommandGenerator : NSObject\n\n+ (void )sendMessageWithView:(UIView *)view\n                                    gusture:(UIGestureRecognizer *)gusture\n                                     action:(SEL)action\n                                  indexPath:(NSIndexPath *)indexPath\n                               messageType:(DoraemonMCMessageType)type;\n\n+ (DoraemonMCMessage *)sendCustomMessageWithView:(UIView *)view\n                                       eventInfo:(NSDictionary *)eventInfo\n                                     messageType:(NSString *)type;\n\n\n\n@end\n\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Dispatcher/DoraemonMCCommandGenerator.m",
    "content": "//\n//  DoraemonMCCommandGenerator.m\n//  DoraemonKit-DoraemonKit\n//\n//  Created by litianhao on 2021/7/12.\n//\n\n#import \"DoraemonMCCommandGenerator.h\"\n#import \"DoraemonMCMessagePackager.h\"\n#import \"DoraemonMCServer.h\"\n\n@implementation DoraemonMCCommandGenerator\n\n+ (void)sendMessageWithView:(UIView *)view\n                                   gusture:(UIGestureRecognizer *)gusture\n                                    action:(SEL)action\n                                 indexPath:(NSIndexPath *)indexPath\n                               messageType:(DoraemonMCMessageType)type {\n    @autoreleasepool {\n        DoraemonMCMessage *message = [DoraemonMCMessagePackager packageMessageWithView:view\n                                                                               gusture:gusture\n                                                                                action:action\n                                                                             indexPath:indexPath\n                                                                           messageType:type];\n        if (message) {\n            [DoraemonMCServer sendMessage:message.toMessageString ?: @\"\"];\n        }\n        message = nil;\n    }\n\n}\n\n\n+ (DoraemonMCMessage *)sendCustomMessageWithView:(UIView *)view\n                                       eventInfo:(NSDictionary *)eventInfo\n                                     messageType:(NSString *)type {\n    \n    DoraemonMCMessage *message = [DoraemonMCMessagePackager packageCustomMessageWithView:view\n                                                                               eventInfo:eventInfo\n                                                                             messageType:type];\n    \n    [DoraemonMCServer sendMessage:message.toMessageString ?: @\"\"];\n    return message;\n}\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Event/Capture/DoraemonMCEventCapturer.h",
    "content": "//\n//  DoraemonMCEventCapturer.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/6/30.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonMCGestureTargetActionPair.h\"\n\n@interface UIGestureRecognizer (DoraemonMCSupport)\n\n- (void)do_mc_handleGestureSend:(id)sender;\n\n@end\n\n@interface UIApplication (DoraemonMCSupport)\n\n@end\n\n\n@interface UIControl (DoraemonMCSupport)\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Event/Capture/DoraemonMCEventCapturer.m",
    "content": "//\n//  DoraemonMCEventCapturer.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/6/30.\n//\n\n#import \"DoraemonMCEventCapturer.h\"\n#import \"DoraemonMCServer.h\"\n#import \"DoraemonMCXPathSerializer.h\"\n#import \"DoraemonMCCommandGenerator.h\"\n#import \"DoraemonMCReuseViewDelegateProxy.h\"\n#import \"NSObject+DoraemonMCSupport.h\"\n#import \"UIGestureRecognizer+DoraemonMCSerializer.h\"\n#import <objc/runtime.h>\n\n@implementation UIApplication (DoraemonMCSupport)\n\n+ (void)load {\n        static dispatch_once_t onceToken;\n        dispatch_once(&onceToken, ^{\n            [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(sendAction:to:from:forEvent:) swizzledSel:@selector(do_mc_sendAction:to:from:forEvent:)];\n        });\n}\n\n- (BOOL)do_mc_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event {\n    \n    if ([DoraemonMCServer isOpen]) {\n        \n        UIView *senderV = sender;\n        if ([sender isKindOfClass:[UIGestureRecognizer class]]) {\n            UIGestureRecognizer *ges = sender;\n            senderV = [ges view];\n            [ges do_mc_handleGestureSend:sender];\n        }else if ([senderV isKindOfClass:[UIView class]]) {\n            [DoraemonMCCommandGenerator sendMessageWithView:senderV\n                                                    gusture:nil\n                                                     action:action\n                                                  indexPath:nil\n                                                messageType:DoraemonMCMessageTypeControl];\n        }\n    }\n    \n    return [self do_mc_sendAction:action to:target from:sender forEvent:event];\n}\n\n@end\n\n@implementation UIGestureRecognizer (DoraemonMCSupport)\n\n+ (void)load {\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(initWithTarget:action:) swizzledSel:@selector(do_mc_initWithTarget:action:)];\n    });\n}\n\n- (instancetype)do_mc_initWithTarget:(id)target action:(SEL)action {\n    [self do_mc_initWithTarget:self action:@selector(do_mc_action:)];\n    [self addTarget:target action:action];\n    return self;\n}\n\n\n- (void)do_mc_handleGestureSend:(id)sender {\n    [DoraemonMCCommandGenerator sendMessageWithView:self.view\n                                            gusture:self\n                                             action:nil\n                                          indexPath:nil\n                                        messageType:DoraemonMCMessageTypeGuesture];\n}\n\n- (void)do_mc_action:(id)sender {\n    if ([DoraemonMCServer isOpen]) {\n        [self do_mc_handleGestureSend:sender];\n    }\n}\n\n@end\n\n@implementation UITextField (support)\n\n\n+ (void)load {\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(initWithFrame:) swizzledSel:@selector(do_mc_initWithFrame:)];\n    });\n}\n\n- (instancetype)do_mc_initWithFrame:(CGRect)frame {\n    id instance = [self do_mc_initWithFrame:frame];\n    [instance addTarget:self action:@selector(do_mc_UIControlEventEditingChangedHandle) forControlEvents:UIControlEventEditingChanged];\n    [instance addTarget:self action:@selector(do_mc_UIControlEventEditingDidEndHandle) forControlEvents:UIControlEventEditingDidEnd];\n    return instance;\n}\n\n- (void)do_mc_UIControlEventEditingChangedHandle {\n    [self do_mc_sendPayload:^NSDictionary *(NSDictionary *input) {\n        if ([self respondsToSelector:@selector(text)]) {\n            NSMutableDictionary *inputM = input.mutableCopy;\n            inputM[@\"text\"] = [self valueForKey:@\"text\"];\n            return inputM.copy;\n        }\n        return input;\n    }];\n}\n\n- (void)do_mc_sendPayload:(NSDictionary*(^)(NSDictionary*))payload {\n    if (![DoraemonMCServer isOpen]) {\n        return;\n    }\n    if (![self isKindOfClass:[UITextField class]]) {\n        return;\n        \n    }\n    [DoraemonMCCommandGenerator sendMessageWithView:self\n                                            gusture:nil\n                                             action:nil\n                                          indexPath:nil\n                                        messageType:DoraemonMCMessageTypeTextInput];\n}\n\n- (void)do_mc_UIControlEventEditingDidEndHandle {\n    [self do_mc_sendPayload:nil];\n}\n\n@end\n\n@implementation UITextView (support)\n\n+ (void)load {\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(initWithFrame:) swizzledSel:@selector(do_mc_initWithFrame:)];\n    });\n}\n\n- (instancetype)do_mc_initWithFrame:(CGRect)frame {\n    id instance = [self do_mc_initWithFrame:frame];\n    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(do_mc_sendTextViewPayload:) name:UITextViewTextDidChangeNotification object:nil];\n\n    return instance;\n}\n\n- (void)do_mc_sendTextViewPayload:(NSDictionary*(^)(NSDictionary*))payload {\n    if (![DoraemonMCServer isOpen]) {\n        return;\n    }\n    if (![self isKindOfClass:[UITextView class]]) {\n        return;\n        \n    }\n    [DoraemonMCCommandGenerator sendMessageWithView:self\n                                            gusture:nil\n                                             action:nil\n                                          indexPath:nil\n                                        messageType:DoraemonMCMessageTypeTextInput];\n}\n\n@end\n\n\n@interface UITableView (DoraemonMCSupport)\n\n@end\n\n@implementation UITableView (DoraemonMCSupport)\n\n+ (void)load {\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(setDelegate:) swizzledSel:@selector(do_mc_setDelegate:)];\n    });\n}\n\n- (void)do_mc_setDelegate:(id<UITableViewDelegate>)delegate {\n\n    DoraemonMCReuseViewDelegateProxy *delegateProxy = [DoraemonMCReuseViewDelegateProxy proxyWithTarget:delegate];\n    objc_setAssociatedObject(self, _cmd, delegateProxy, OBJC_ASSOCIATION_RETAIN);\n    [self do_mc_setDelegate:delegateProxy];\n}\n\n@end\n\n\n@interface UICollectionView (DoraemonMCSupport) <UICollectionViewDelegate>\n\n@end\n\n@implementation UICollectionView (DoraemonMCSupport)\n\n+ (void)load {\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(setDelegate:) swizzledSel:@selector(do_mc_setDelegate:)];\n    });\n}\n\n- (void)do_mc_setDelegate:(id<UICollectionViewDelegate>)delegate {\n    DoraemonMCReuseViewDelegateProxy *delegateProxy = [DoraemonMCReuseViewDelegateProxy proxyWithTarget:delegate];\n    objc_setAssociatedObject(self, _cmd, delegateProxy, OBJC_ASSOCIATION_RETAIN);\n    [self do_mc_setDelegate:delegateProxy];\n}\n\n@end\n\n@interface UITabBarController (DoraemonMCSupport)\n\n@end\n\n@implementation UITabBarController (DoraemonMCSupport)\n\n+ (void)load {\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(setSelectedViewController:) swizzledSel:@selector(do_mc_setSelectedViewController:)];\n    });\n}\n\n\n- (void)do_mc_setSelectedViewController:(__kindof UIViewController *)selectedViewController {\n    [self do_mc_setSelectedViewController:selectedViewController];\n    if (![DoraemonMCServer isOpen]) {\n        return;\n    }\n    \n    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[self.viewControllers indexOfObject:selectedViewController] inSection:0];\n    [DoraemonMCCommandGenerator sendMessageWithView:self.view\n                                            gusture:nil\n                                             action:nil\n                                          indexPath:indexPath\n                                        messageType:DoraemonMCMessageTypeTarbarSelected];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Event/Capture/DoraemonMCGestureTargetActionPair.h",
    "content": "//\n//  DoraemonMCGestureTargetActionPair.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMCGestureTargetActionPair : NSObject\n\n@property (nonatomic , weak ) id target ;\n@property (nonatomic , assign) SEL action ;\n@property (nonatomic , weak) id sender;\n- (instancetype)initWithTarget:(id)target action:(SEL)action sender:(id)sender;\n- (BOOL)isEqualToTarget:(id)target andAction:(SEL)action;\n\n- (BOOL)valid;\n\n- (void)doAction ;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Event/Capture/DoraemonMCGestureTargetActionPair.m",
    "content": "//\n//  DoraemonMCGestureTargetActionPair.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import \"DoraemonMCGestureTargetActionPair.h\"\n\n@implementation DoraemonMCGestureTargetActionPair\n\n- (instancetype)initWithTarget:(id)target action:(SEL)action sender:(id)sender {\n    if (self = [super init]) {\n        self.target = target;\n        self.action = action;\n        self.sender = sender;\n    }\n    return self;\n}\n\n- (BOOL)isEqualToTarget:(id)target andAction:(SEL)action {\n    return (self.target == target) && [NSStringFromSelector(self.action) isEqualToString:NSStringFromSelector(action)];\n}\n\n- (BOOL)valid {\n    return [self.target respondsToSelector:self.action];\n}\n\n- (void)doAction {\n    if (![self valid]) {\n        return;\n    }\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Warc-performSelector-leaks\"\n    if ([NSStringFromSelector(self.action) containsString:@\":\"]) {\n        [self.target performSelector:self.action withObject:self.sender];\n    }else {\n        [self.target performSelector:self.action];\n    }\n#pragma clang diagnostic pop\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Event/Capture/DoraemonMCReuseViewDelegateProxy.h",
    "content": "//\n//  DoraemonMCReuseViewDelegateProxy.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/7/16.\n//\n\n#import <UIKit/UIKit.h>\n\n\n@interface DoraemonMCReuseViewDelegateProxy : NSProxy <UICollectionViewDelegate , UITableViewDelegate>\n\n@property (nonatomic , weak) id target;\n\n+ (instancetype)proxyWithTarget:(id)target;\n\n@end\n\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Event/Capture/DoraemonMCReuseViewDelegateProxy.m",
    "content": "//\n//  DoraemonMCReuseViewDelegateProxy.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/7/16.\n//\n\n#import \"DoraemonMCReuseViewDelegateProxy.h\"\n#import \"DoraemonMCServer.h\"\n#import \"DoraemonMCCommandGenerator.h\"\n\n@implementation DoraemonMCReuseViewDelegateProxy\n\n+ (instancetype)proxyWithTarget:(id)target {\n    return [[self alloc] initWithTarget:target];\n}\n\n- (instancetype)initWithTarget:(id)target {\n    _target = target ;\n    return self;\n}\n\n\n- (id)forwardingTargetForSelector:(SEL)selector {\n    return _target;\n}\n\n- (void)forwardInvocation:(NSInvocation *)invocation {\n    void *null = NULL;\n    [invocation setReturnValue:&null];\n}\n\n- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {\n    return [NSObject instanceMethodSignatureForSelector:@selector(init)];\n}\n\n- (BOOL)respondsToSelector:(SEL)aSelector {\n    if ([NSStringFromSelector(aSelector) isEqualToString:NSStringFromSelector(@selector(scrollViewDidEndDecelerating:))]) {\n        return YES;\n    }\n    return [_target respondsToSelector:aSelector];\n}\n\n- (BOOL)isEqual:(id)object {\n    return [_target isEqual:object];\n}\n\n- (NSUInteger)hash {\n    return [_target hash];\n}\n\n- (Class)superclass {\n    return [_target superclass];\n}\n\n- (Class)class {\n    return [_target class];\n}\n\n- (BOOL)isKindOfClass:(Class)aClass {\n    return [_target isKindOfClass:aClass];\n}\n\n- (BOOL)isMemberOfClass:(Class)aClass {\n    return [_target isMemberOfClass:aClass];\n}\n\n- (BOOL)conformsToProtocol:(Protocol *)aProtocol {\n    return [_target conformsToProtocol:aProtocol];\n}\n\n- (BOOL)isProxy {\n    return YES;\n}\n\n- (NSString *)description {\n    return [_target description];\n}\n\n- (NSString *)debugDescription {\n    return [_target debugDescription];\n}\n\n\n- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {\n    if ([DoraemonMCServer isOpen]) {\n        [DoraemonMCCommandGenerator sendMessageWithView:collectionView\n                                                gusture:nil\n                                                 action:nil\n                                              indexPath:indexPath\n                                            messageType:DoraemonMCMessageTypeDidSelectCell];\n    }\n    if ([self.target respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:)]) {\n        [self.target collectionView:collectionView didSelectItemAtIndexPath:indexPath];\n    }\n}\n\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    if ([DoraemonMCServer isOpen]) {\n        [DoraemonMCCommandGenerator sendMessageWithView:tableView\n                                                gusture:nil\n                                                 action:nil\n                                              indexPath:indexPath\n                                            messageType:DoraemonMCMessageTypeDidSelectCell];\n    }\n    if ([self.target respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) {\n        [self.target tableView:tableView didSelectRowAtIndexPath:indexPath];\n    }\n}\n\n- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {\n    if ([DoraemonMCServer isOpen]) {\n        NSIndexPath *lastIndexPath = nil;\n        if ([scrollView isKindOfClass:[UITableView class]]) {\n            UITableView *tableView = (UITableView *)scrollView;\n            lastIndexPath = [tableView indexPathsForVisibleRows].lastObject;\n        }else  if ([scrollView isKindOfClass:[UICollectionView class]]) {\n            UICollectionView *collectionView = (UICollectionView *)scrollView;\n            lastIndexPath = [collectionView indexPathsForVisibleItems].lastObject;\n        }\n        if (lastIndexPath != nil) {\n            [DoraemonMCCommandGenerator sendMessageWithView:scrollView\n                                                    gusture:nil\n                                                     action:nil\n                                                  indexPath:lastIndexPath\n                                                messageType:DoraemonMCMessageTypeDidScrollToCell];\n        }\n    }\n    if ([self.target respondsToSelector:_cmd]) {\n        [self.target scrollViewDidEndDecelerating:scrollView];\n    }\n}\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Event/Excutor/DoraemonMCEventHandler.h",
    "content": "//\n//  DoraemonMCEventHandler.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/6/30.\n//\n\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonMCMessagePackager.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMCEventHandler : NSObject\n\n@property (nonatomic , strong) DoraemonMCMessage *messageInfo;\n\n@property (nonatomic , strong) UIView *targetView;\n\n- (BOOL)handleEvent:(DoraemonMCMessage*)eventInfo;\n\n- (UIView *)fetchTargetView;\n\n@end\n\n@interface DoraemonMCGestureRecognizerEventHandler : DoraemonMCEventHandler\n\n\n@end\n\n@interface DoraemonMCControlEventHandler : DoraemonMCEventHandler\n\n\n@end\n\n@interface DoraemonMCReuseCellEventHandler : DoraemonMCEventHandler\n\n\n\n@end\n\n@interface DoraemonMCTextFiledEventHandler : DoraemonMCEventHandler\n\n\n@end\n\n@interface DoraemonMCTabbarEventHandler : DoraemonMCEventHandler\n\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Event/Excutor/DoraemonMCEventHandler.m",
    "content": "//\n//  DoraemonMCEventHandler.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/6/30.\n//\n\n#import \"DoraemonMCEventHandler.h\"\n#import <objc/runtime.h>\n#import <DoraemonKit/DoraemonMCServer.h>\n#import \"DoraemonMCEventCapturer.h\"\n#import \"DoraemonMCXPathSerializer.h\"\n#import \"DoraemonMCGustureSerializer.h\"\n#import \"UIGestureRecognizer+DoraemonMCSerializer.h\"\n#import \"UIResponder+DoraemonMCSerializer.h\"\n#import \"DoraemonMCXPathSerializer.h\"\n#import \"DoraemonMCClient.h\"\n\n#define DoraemonMCEventHandler_call_super_handle_event if (![super handleEvent:eventInfo]) {\\\n        return NO;\\\n    }\n\n@implementation DoraemonMCEventHandler\n\n- (BOOL)handleEvent:(DoraemonMCMessage*)eventInfo {\n    self.messageInfo = eventInfo;\n    self.targetView = [self fetchTargetView];\n    NSString *vcClsnameSufix =  [eventInfo.currentVCClassName componentsSeparatedByString:@\".\"].lastObject;\n    NSString *currentViewVCClsnameSufix = [NSStringFromClass([DoraemonMCXPathSerializer ownerVCWithView:self.targetView].class) componentsSeparatedByString:@\".\"].lastObject;\n    \n    // 页面类名校验\n    if (eventInfo.currentVCClassName.length > 0 &&\n        ![vcClsnameSufix isEqualToString:currentViewVCClsnameSufix]) {\n        [DoraemonMCClient showToast:@\"一机多控：收到主机手势消息，但当前页面与主机不一致，因此未执行此手势\"];\n        return NO;\n    }\n\n    if (self.targetView == nil) {\n        [DoraemonMCClient showToast:@\"一机多控：收到主机手势消息，但并未成功在当前页面找到对应控件，因此未执行此手势\"];\n        return NO;\n    }\n    \n    if (self.messageInfo.isFirstResponder && self.targetView.isFirstResponder == NO) {\n        [self.targetView becomeFirstResponder];\n    }else if (self.messageInfo.isFirstResponder == NO && self.targetView.isFirstResponder) {\n        [self.targetView resignFirstResponder];\n    }\n    return YES;\n}\n\n- (UIView *)fetchTargetView {\n    return [DoraemonMCXPathSerializer viewFromXpath:self.messageInfo.xPath];\n}\n@end\n\n\n@implementation DoraemonMCGestureRecognizerEventHandler\n\n- (BOOL)handleEvent:(DoraemonMCMessage*)eventInfo {\n   \n    DoraemonMCEventHandler_call_super_handle_event\n    \n    UIView *targetView = self.targetView;\n    \n    NSDictionary *data = self.messageInfo.eventInfo;\n    NSInteger gesIndex = [data[kUIGestureRecognizerDoraemonMCSerializerWrapperKey][kUIGestureRecognizerDoraemonMCSerializerIndexKey] intValue];\n    \n    if (targetView.gestureRecognizers.count <= gesIndex) {\n        NSLog(@\"gestureRecognizer has not found %@\",eventInfo);\n        return NO;\n    }\n    \n    UIGestureRecognizer *ges = targetView.gestureRecognizers[gesIndex];\n    \n    [DoraemonMCGustureSerializer syncInfoToGusture:ges withDict:data];\n\n    return YES;\n}\n\n@end\n\n@implementation DoraemonMCControlEventHandler\n\n- (BOOL)handleEvent:(DoraemonMCMessage*)eventInfo {\n    DoraemonMCEventHandler_call_super_handle_event\n    \n    UIView *rootView = self.targetView;\n    NSDictionary *data = self.messageInfo.eventInfo;\n\n    SEL action = NSSelectorFromString(data[@\"action\"]);\n    if ([rootView isKindOfClass:[UIControl class]]) {\n        UIControl *ctl = (UIControl *)rootView;\n        [[ctl allTargets] enumerateObjectsUsingBlock:^(id  _Nonnull obj, BOOL * _Nonnull stop) {\n            if ([obj respondsToSelector:action]) {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Warc-performSelector-leaks\"\n                if ([data[@\"action\"] containsString:@\":\"]) {\n                    [obj performSelector:action withObject:rootView];\n                }else {\n                    [obj performSelector:action];\n                }\n#pragma clang diagnostic pop\n            }\n        }];\n    }\n    return YES;\n}\n\n@end\n\n@implementation DoraemonMCReuseCellEventHandler\n\n- (BOOL)handleEvent:(DoraemonMCMessage*)eventInfo {\n    DoraemonMCEventHandler_call_super_handle_event\n    \n    NSDictionary *data = self.messageInfo.eventInfo;\n\n    UIView *rootView = self.targetView;\n    if (DoraemonMCMessageTypeDidSelectCell == eventInfo.type ) {\n        if ([rootView isKindOfClass:[UITableView class]]) {\n            UITableView *tableView =  (UITableView *)rootView ;\n            NSIndexPath *targetIndexPath = [NSIndexPath indexPathForRow:[data[@\"row\"] intValue]\n                                                              inSection:[data[@\"section\"] intValue]];\n            NSInteger rowCount = [tableView numberOfRowsInSection:targetIndexPath.section];\n            if (rowCount > targetIndexPath.row) {\n                [tableView.delegate tableView:tableView didSelectRowAtIndexPath:targetIndexPath];\n            }else {\n                [DoraemonMCClient showToast:@\"一机多控：收到主机点选列表项手势消息，但并未在从机上找到对应列表项，因此未执行此手势\"];\n            }\n        }else if ([rootView isKindOfClass:[UICollectionView class]]) {\n            UICollectionView *collectionView =  (UICollectionView *)rootView ;\n\n            NSIndexPath *targetIndexPath = [NSIndexPath indexPathForRow:[data[@\"row\"] intValue]\n                                                              inSection:[data[@\"section\"] intValue]];\n            NSInteger rowCount = [collectionView numberOfItemsInSection:targetIndexPath.section];\n            if (rowCount > targetIndexPath.item) {\n                [collectionView.delegate collectionView:collectionView didSelectItemAtIndexPath:targetIndexPath];\n            }else {\n                [DoraemonMCClient showToast:@\"一机多控：收到主机点选列表项手势消息，但并未在从机上找到对应列表项，因此未执行此手势\"];\n            }\n        }\n    }else if (DoraemonMCMessageTypeDidScrollToCell == eventInfo.type ) {\n        if ([rootView isKindOfClass:[UITableView class]]) {\n            UITableView *tableView =  (UITableView *)rootView ;\n            NSIndexPath *targetIndexPath = [NSIndexPath indexPathForRow:[data[@\"row\"] intValue]\n                                                              inSection:[data[@\"section\"] intValue]];\n            NSInteger rowCount = [tableView numberOfRowsInSection:targetIndexPath.section];\n            if (rowCount > targetIndexPath.row) {\n                [tableView scrollToRowAtIndexPath:targetIndexPath\n                                 atScrollPosition:UITableViewScrollPositionBottom\n                                         animated:YES];\n            }\n        }else if ([rootView isKindOfClass:[UICollectionView class]]) {\n            UICollectionView *collectionView =  (UICollectionView *)rootView ;\n\n            NSIndexPath *targetIndexPath = [NSIndexPath indexPathForRow:[data[@\"row\"] intValue]\n                                                              inSection:[data[@\"section\"] intValue]];\n            NSInteger rowCount = [collectionView numberOfItemsInSection:targetIndexPath.section];\n            if (rowCount > targetIndexPath.item) {\n                [collectionView scrollToItemAtIndexPath:targetIndexPath\n                                       atScrollPosition:UICollectionViewScrollPositionBottom\n                                               animated:YES];\n            }\n        }\n    }\n\n    return YES;\n}\n\n@end\n\n\n@implementation DoraemonMCTextFiledEventHandler\n\n- (BOOL)handleEvent:(DoraemonMCMessage *)eventInfo {\n    DoraemonMCEventHandler_call_super_handle_event\n\n    UIView *rootView = self.targetView;\n    NSDictionary *data = self.messageInfo.eventInfo;    \n    [rootView do_mc_serialize_syncInfoWithDictionary:data];\n    \n    if ([rootView isKindOfClass:[UITextField class]]) {\n        UITextField *tfView =  (UITextField *)rootView ;\n        \n        if (data[@\"text\"]) {\n            tfView.text = data[@\"text\"];\n        }\n    }\n    \n    if ([rootView isKindOfClass:[UITextView class]]) {\n        UITextView *tVView =  (UITextView *)rootView ;\n        \n        if (data[@\"text\"]) {\n            tVView.text = data[@\"text\"];\n        }\n    }    \n    return YES;\n}\n\n@end\n\n@implementation DoraemonMCTabbarEventHandler\n\n- (BOOL)handleEvent:(DoraemonMCMessage *)eventInfo {\n    DoraemonMCEventHandler_call_super_handle_event\n\n    UIView *rootView = self.targetView;\n    \n    UITabBarController *tabbarC = (UITabBarController *)[DoraemonMCXPathSerializer ownerVCWithView:rootView];\n    if ([tabbarC isKindOfClass:[UITabBarController class]]) {\n        NSDictionary *data = self.messageInfo.eventInfo;\n        NSInteger selectIndex = [data[@\"selectIndex\"] integerValue];\n        BOOL notMatch = NO;\n        if (tabbarC.viewControllers.count <= selectIndex ||\n            ![data[@\"selectVC\"] isEqualToString:NSStringFromClass([tabbarC.viewControllers[selectIndex] class])]) {\n            notMatch = YES;\n        }\n        if (notMatch) {\n            __block UIViewController *matchVC = nil;\n            [tabbarC.viewControllers enumerateObjectsUsingBlock:^(__kindof UIViewController * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {\n                if ([NSStringFromClass(obj.class) isEqualToString:data[@\"selectVC\"]]) {\n                    matchVC = obj;\n                }\n            }];\n            if (matchVC) {\n                [tabbarC setSelectedViewController:matchVC];\n            }\n        }else {\n            [tabbarC setSelectedIndex:selectIndex];\n        }\n        return YES;\n    }\n    \n    return NO;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/MessagePackager/DoraemonMCMessagePackager.h",
    "content": "//\n//  DoraemonMessagePackager.h\n//  DoraemonKit-DoraemonKit\n//\n//  Created by litianhao on 2021/7/13.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n/// 事件消息类型\ntypedef NS_ENUM(NSInteger , DoraemonMCMessageType) {\n    /// 基于手势识别器的\n    DoraemonMCMessageTypeGuesture,\n    /// 基于UIControl的\n    DoraemonMCMessageTypeControl,\n    /// 基于复用列表项被点击的\n    DoraemonMCMessageTypeDidSelectCell,\n    DoraemonMCMessageTypeDidScrollToCell,\n    /// 基于文本输入\n    DoraemonMCMessageTypeTextInput,\n    /// 底部tabbar选中\n    DoraemonMCMessageTypeTarbarSelected\n};\n\n@interface DoraemonMCMessage : NSObject\n\n// 消息类型\n@property (nonatomic , assign) DoraemonMCMessageType type;\n// 自定义消息类型\n@property (nonatomic , assign) NSString * customType;\n// 控件的xPath\n@property (nonatomic , copy  ) NSString *xPath;\n\n// 当前消息对应的事件信息\n@property (nonatomic , copy  ) NSDictionary *eventInfo;\n\n// 当前所在的页面类名\n@property (nonatomic , copy  ) NSString *currentVCClassName;\n\n@property (nonatomic , assign) BOOL isFirstResponder;\n- (NSString *)toMessageString;\n\n@end\n\n@interface DoraemonMCMessagePackager : NSObject\n\n/**\n 根据类型,xPath和事件信息组装消息字符串,用于发送到网络\n */\n+ (DoraemonMCMessage *)packageMessageWithView:(UIView *)view\n                             gusture:(UIGestureRecognizer *)gusture\n                              action:(SEL)action\n                           indexPath:(NSIndexPath *)indexPath\n                        messageType:(DoraemonMCMessageType)type;\n\n/**\n 自定义事件\n */\n+ (DoraemonMCMessage *)packageCustomMessageWithView:(UIView *)view\n                                          eventInfo:(NSDictionary *)eventInfo\n                                        messageType:(NSString *)type;\n\n/***\n 根据从网络上获取的消息字符串, 解析出消息对象\n */\n+ (DoraemonMCMessage *)parseMessageString:(NSString *)messageString;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/MessagePackager/DoraemonMCMessagePackager.m",
    "content": "//\n//  DoraemonMessagePackager.m\n//  DoraemonKit-DoraemonKit\n//\n//  Created by litianhao on 2021/7/13.\n//\n\n#import \"DoraemonMCMessagePackager.h\"\n#import \"DoraemonMCXPathSerializer.h\"\n#import \"DoraemonMCGustureSerializer.h\"\n#import \"UIResponder+DoraemonMCSerializer.h\"\n\nstatic NSString const *kIsFirstResponderKey = @\"isFirstResponder\";\nstatic NSString const *kVcClsNameKey = @\"vcClsName\";\nstatic NSString const *kEventInfoKey = @\"eventInfo\";\nstatic NSString const *kXpathKey = @\"xPath\";\nstatic NSString const *kTypeKey = @\"type\";\nstatic NSString const *kcustomTypeKey =@\"customType\";\n@implementation DoraemonMCMessagePackager\n\n/**\n 根据类型,xPath和事件信息组装消息字符串,用于发送到网络\n */\n+ (DoraemonMCMessage *)packageMessageWithView:(UIView *)view\n                             gusture:(UIGestureRecognizer *)gusture\n                              action:(SEL)action\n                           indexPath:(NSIndexPath *)indexPath\n                         messageType:(DoraemonMCMessageType)type {\n    DoraemonMCMessage *messageInstance = [[DoraemonMCMessage alloc] init];\n    messageInstance.type = type;\n    DoraemonMCXPathSerializer *xPathInstance = [DoraemonMCXPathSerializer xPathInstanceWithView:view];\n    if (xPathInstance.windowIndex == NSNotFound) {\n        // 如果存在埋点 SDK，埋点会被后调用，先前调用的业务 action 关闭了当前页面，导致 sender 不存在于视图中，会出现这种情况。这种情况需要过滤\n        return nil;\n    }\n    if (xPathInstance.ignore) {\n        return nil;\n    }\n    // tabbar上的控件 使用hook系统tabbar点击的方式同步， 只处理DoraemonMCMessageTypeTarbarSelected的情况\n    if ([xPathInstance.vcCls isKindOfClass:[UITabBarController class]] && type != DoraemonMCMessageTypeTarbarSelected) {\n        return nil;\n    }\n    messageInstance.xPath = [xPathInstance generalPathToTransfer];\n    messageInstance.isFirstResponder = view.isFirstResponder;\n    UIViewController *vc = [DoraemonMCXPathSerializer ownerVCWithView:view];\n    if (vc) {\n        messageInstance.currentVCClassName = NSStringFromClass(vc.class) ;\n    }\n    \n    switch (type) {\n        case DoraemonMCMessageTypeControl:\n        {\n            messageInstance.eventInfo =  @{\n                @\"action\": NSStringFromSelector(action)?:@\"\",\n            };\n            break;\n        }\n        case DoraemonMCMessageTypeGuesture:\n        {\n            messageInstance.eventInfo =  [DoraemonMCGustureSerializer dictFromGusture:gusture];\n            break;\n        }\n        case DoraemonMCMessageTypeDidSelectCell:\n        case DoraemonMCMessageTypeDidScrollToCell:\n        {\n            messageInstance.eventInfo =  @{\n                @\"section\": @(indexPath.section),\n                @\"row\": @(indexPath.row)\n            };\n            break;\n        }\n        case DoraemonMCMessageTypeTextInput:\n        {\n            NSString *text = nil;\n            if ([view respondsToSelector:@selector(text)]) {\n                text = [view valueForKey:@\"text\"];\n            }\n            NSMutableDictionary *dictM = [NSMutableDictionary dictionary];\n            if (text) {\n                dictM[@\"text\"] = text;\n            }\n            [dictM addEntriesFromDictionary:[view do_mc_serialize_dictionary]];\n            messageInstance.eventInfo =  dictM.copy;\n            break;\n        }\n        case DoraemonMCMessageTypeTarbarSelected:\n        {\n            if ([vc isKindOfClass:[UITabBarController class]]) {\n                UITabBarController *tabbarC = (UITabBarController *)vc;\n                messageInstance.eventInfo = @{\n                    @\"selectIndex\" : @(indexPath.row),\n                    @\"selectVC\" : NSStringFromClass(tabbarC.selectedViewController.class)\n                };\n            }else {\n                messageInstance = nil;\n            }\n            break;\n        }\n        default:\n            break;\n    }\n\n    return messageInstance;\n}\n\n\n/*\n * 自定义事件\n */\n+ (DoraemonMCMessage *)packageCustomMessageWithView:(UIView *)view\n                                          eventInfo:(NSDictionary *)eventInfo\n                                        messageType:(NSString *)type {\n    DoraemonMCMessage *messageInstance = [[DoraemonMCMessage alloc] init];\n    messageInstance.customType = type;\n    messageInstance.xPath = [DoraemonMCXPathSerializer xPathStringWithView:view];\n    messageInstance.eventInfo = eventInfo;\n    messageInstance.isFirstResponder = view.isFirstResponder;\n    UIViewController *vc = [DoraemonMCXPathSerializer ownerVCWithView:view];\n    if (vc) {\n        messageInstance.currentVCClassName = NSStringFromClass(vc.class) ;\n    }\n    \n    return messageInstance;\n    \n}\n/***\n 根据从网络上获取的消息字符串, 解析出消息对象\n */\n+ (DoraemonMCMessage *)parseMessageString:(NSString *)messageString {\n    NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:[messageString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:NULL];\n    DoraemonMCMessage *messageInstance = [[DoraemonMCMessage alloc] init];\n    messageInstance.type = [dict[kTypeKey] integerValue];\n    messageInstance.customType = dict[kcustomTypeKey];\n    messageInstance.xPath = dict[kXpathKey];\n    messageInstance.eventInfo = dict[kEventInfoKey];\n    messageInstance.currentVCClassName = dict[kVcClsNameKey];\n    messageInstance.isFirstResponder = [dict[kIsFirstResponderKey] boolValue];\n    return messageInstance;\n}\n\n@end\n\n@implementation DoraemonMCMessage\n\n- (NSString *)toMessageString {\n    \n    NSDictionary *dict = @{\n        kTypeKey : @(self.type),\n        kXpathKey : self.xPath?:@\"\",\n        kEventInfoKey : self.eventInfo?:@{},\n        kVcClsNameKey: self.currentVCClassName?:@\"\",\n        kIsFirstResponderKey : @(self.isFirstResponder)\n    };\n    if (self.customType.length) {\n        dict = @{\n            kcustomTypeKey : self.customType,\n            kXpathKey : self.xPath?:@\"\",\n            kEventInfoKey : self.eventInfo?:@{},\n            kVcClsNameKey: self.currentVCClassName?:@\"\",\n            kIsFirstResponderKey : @(self.isFirstResponder)\n        };\n    }\n    if ([NSJSONSerialization isValidJSONObject:dict]) {\n        return [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:NULL] encoding:NSUTF8StringEncoding];\n    }\n    return nil;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/DoraemonMCGustureSerializer.h",
    "content": "//\n//  DoraemonMCGustureSerializer.h\n//  DoraemonKit-DoraemonKit\n//\n//  Created by litianhao on 2021/7/13.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMCGustureSerializer : NSObject\n\n+ (NSDictionary *)dictFromGusture:(UIGestureRecognizer *)gusture;\n\n+ (void)syncInfoToGusture:(UIGestureRecognizer *)gusture withDict:(NSDictionary *)dict;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/DoraemonMCGustureSerializer.m",
    "content": "//\n//  DoraemonMCGustureSerializer.m\n//  DoraemonKit-DoraemonKit\n//\n//  Created by litianhao on 2021/7/13.\n//\n\n#import \"DoraemonMCGustureSerializer.h\"\n#import \"UIGestureRecognizer+DoraemonMCSerializer.h\"\n#import \"UIResponder+DoraemonMCSerializer.h\"\n\n@implementation DoraemonMCGustureSerializer\n\n+ (NSDictionary *)dictFromGusture:(UIGestureRecognizer *)gusture {\n    if (![gusture isKindOfClass:[UIGestureRecognizer class]] ) {\n        return nil;\n    }\n    \n    NSMutableDictionary *dictM = [NSMutableDictionary dictionary];\n    [dictM addEntriesFromDictionary:[gusture do_mc_serialize_dictionary]];\n    [dictM addEntriesFromDictionary:[gusture.view do_mc_serialize_dictionary]];\n    return dictM.copy;\n}\n\n+ (void)syncInfoToGusture:(UIGestureRecognizer *)gusture withDict:(NSDictionary *)eventInfo {\n    [gusture do_mc_serialize_syncInfoWithDictionary:eventInfo];\n    [gusture.view do_mc_serialize_syncInfoWithDictionary:eventInfo];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UIControl+DoraemonMCSerializer.h",
    "content": "//\n//  UIControl+DoraemonMCSerializer.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\nUIKIT_EXTERN NSString const *kUIControlDoraemonMCSerializerWrapperKey ;\n\n@interface UIControl (DoraemonMCSerializer)\n\n/// 当前对象的信息转为字典\n- (NSDictionary *)do_mc_serialize_dictionary;\n/// 将字典中的值同步到当前对象的属性参数\n- (void)do_mc_serialize_syncInfoWithDictionary:(NSDictionary *)dictionary;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UIControl+DoraemonMCSerializer.m",
    "content": "//\n//  UIControl+DoraemonMCSerializer.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import \"UIControl+DoraemonMCSerializer.h\"\n\nNSString const *kUIControlDoraemonMCSerializerWrapperKey = @\"ctlrapper\";\n\nstatic NSString const *kUIControlDoraemonMCSerializerStateKey = @\"state\";\n\n@implementation UIControl (DoraemonMCSerializer)\n\n/// 当前对象的信息转为字典\n- (NSDictionary *)do_mc_serialize_dictionary {\n    return @{\n        kUIControlDoraemonMCSerializerWrapperKey : @{\n                kUIControlDoraemonMCSerializerStateKey : @(self.state)\n        }\n    };\n}\n\n/// 将字典中的值同步到当前对象的属性参数\n- (void)do_mc_serialize_syncInfoWithDictionary:(NSDictionary *)dictionary {    \n    if (![dictionary isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    NSDictionary *map = dictionary[kUIControlDoraemonMCSerializerWrapperKey];\n    \n    if (![map isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    \n    NSNumber *state = map[kUIControlDoraemonMCSerializerStateKey];\n    \n    if (![state isKindOfClass:[NSNumber class]]) {\n        return;\n    }\n    \n    UIControlState stateFromDict = state.intValue;\n    \n    [self setHighlighted:(stateFromDict & UIControlStateHighlighted)];\n\n    [self setSelected:(stateFromDict & UIControlStateSelected)];\n\n    [self setEnabled:(stateFromDict & UIControlStateDisabled)];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UIGestureRecognizer+DoraemonMCSerializer.h",
    "content": "//\n//  UIGestureRecognizer+DoraemonMCSerializer.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\nUIKIT_EXTERN NSString  const * _Nonnull kUIGestureRecognizerDoraemonMCSerializerWrapperKey ;\n\nUIKIT_EXTERN NSString  const * _Nonnull kUIGestureRecognizerDoraemonMCSerializerIndexKey;\n\n@interface UIGestureRecognizer (DoraemonMCSerializer)\n\n\n/// 当前手势对象的信息转为字典\n- (NSDictionary *)do_mc_serialize_dictionary;\n/// 将字典中的值同步到当前手势对象的属性参数\n- (void)do_mc_serialize_syncInfoWithDictionary:(NSDictionary *)dictionary;\n\n/// 继承链上的每个类都将自己需要序列化的参数加入到dict中\n- (void)do_mc_serialize_setupDictionary:(NSMutableDictionary *)dictM;\n\n/// 主机上 手势的触摸坐标\n@property (nonatomic , assign) CGPoint do_mc_location_at_host;\n\n/// 清除用于一机多控的所有关联属性\n- (void)do_mc_clear_all_value_at_host;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UIGestureRecognizer+DoraemonMCSerializer.m",
    "content": "//\n//  UIGestureRecognizer+DoraemonMCSerializer.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import \"UIGestureRecognizer+DoraemonMCSerializer.h\"\n#import <objc/runtime.h>\n#import \"DoraemonMCEventCapturer.h\"\n#import \"NSObject+DoraemonMCSupport.h\"\n\nNSString const *kUIGestureRecognizerDoraemonMCSerializerWrapperKey = @\"ges_wrapper\";\n\nNSString const *kUIGestureRecognizerDoraemonMCSerializerIndexKey = @\"gesIndex\";\n\nstatic NSString const *kUIGestureRecognizerStateKey = @\"gesState\";\n\nstatic NSString const *kUIGestureRecognizerLocationKey = @\"gesLocation\";\n\nstatic NSString const *kUIGestureRecognizerLocationXKey = @\"x\";\n\nstatic NSString const *kUIGestureRecognizerLocationYKey = @\"y\";\n\nstatic NSString const *kUIGestureRecognizerPropsKey = @\"gesProps\";\n\n@implementation UIGestureRecognizer (DoraemonMCSerializer)\n\n/// 当前手势对象的信息转为字典\n- (NSDictionary *)do_mc_serialize_dictionary {\n        \n    NSMutableDictionary *dictM = [NSMutableDictionary dictionary];\n    [self do_mc_serialize_setupDictionary:dictM];\n    return @{\n        kUIGestureRecognizerDoraemonMCSerializerWrapperKey : dictM.copy\n    };\n   \n}\n\n- (void)do_mc_serialize_setupDictionary:(NSMutableDictionary *)dictM {\n    \n    CGPoint locationP = [self locationInView:self.view];\n    \n    NSInteger gesIndex =  [self.view.gestureRecognizers indexOfObject:self];\n\n    NSMutableDictionary *propsM = [NSMutableDictionary dictionary];\n    unsigned int count;\n    objc_property_t *propertyList = class_copyPropertyList([self class], &count);\n    \n    for (int i = 0; i < count; i++) {\n        objc_property_t property = propertyList[i];\n        const char *cName = property_getName(property);\n        NSString *name = [NSString stringWithUTF8String:cName];\n        NSObject *value = [self valueForKey:name];\n        if ([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]]) {\n            propsM[name] = value?:@\"null\";\n        }\n    }\n    free(propertyList);\n    \n    [dictM addEntriesFromDictionary:@{\n            kUIGestureRecognizerPropsKey : propsM.copy,\n            kUIGestureRecognizerDoraemonMCSerializerIndexKey : @(gesIndex),\n            kUIGestureRecognizerStateKey : @(self.state),\n            kUIGestureRecognizerLocationKey : @{\n                    kUIGestureRecognizerLocationXKey : @(locationP.x),\n                    kUIGestureRecognizerLocationYKey : @(locationP.y)\n            }\n    }];\n}\n\n/// 将字典中的值同步到当前手势对象的属性参数\n- (void)do_mc_serialize_syncInfoWithDictionary:(NSDictionary *)dictionary {\n    if (![dictionary isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    NSDictionary *map = dictionary[kUIGestureRecognizerDoraemonMCSerializerWrapperKey];\n    if (![map isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    NSDictionary *locationMap = map[kUIGestureRecognizerLocationKey];\n    if ([locationMap isKindOfClass:[NSDictionary class]]) {\n        self.do_mc_location_at_host = CGPointMake([locationMap[kUIGestureRecognizerLocationXKey] doubleValue],\n                                                  [locationMap[kUIGestureRecognizerLocationYKey] doubleValue]);\n    }\n\n    NSNumber *stateNumber = map[kUIGestureRecognizerStateKey];\n    self.state = [stateNumber intValue];\n    \n    NSDictionary *pps = map[kUIGestureRecognizerPropsKey];\n    if ([pps isKindOfClass:[NSDictionary class]]) {\n        [pps enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {\n            if ([obj isKindOfClass:[NSString class]] && [obj isEqualToString:@\"null\"]) {\n                [self setValue:nil forKey:key];\n            }else {\n                [self setValue:obj forKey:key];\n            }\n        }];\n    }\n\n    if ([stateNumber intValue] == UIGestureRecognizerStateCancelled ||\n        [stateNumber intValue] == UIGestureRecognizerStateEnded) {\n        dispatch_async(dispatch_get_main_queue(), ^{\n            dispatch_async(dispatch_get_main_queue(), ^{\n                [self do_mc_clear_all_value_at_host];\n            });\n        });\n    }\n}\n\n#pragma mark - hook\n\n+ (void)load {\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(locationInView:) swizzledSel:@selector(do_mc_base_gesture_locationInView:)];\n    });\n}\n\n- (CGPoint)do_mc_base_gesture_locationInView:(UIView *)view{\n    if (CGPointEqualToPoint(CGPointZero, self.do_mc_location_at_host)) {\n        return [self do_mc_base_gesture_locationInView:view];\n    }\n    return self.do_mc_location_at_host;\n}\n\n- (void)setDo_mc_location_at_host:(CGPoint)do_mc_location_at_host {\n    objc_setAssociatedObject(self, @selector(do_mc_location_at_host), [NSValue valueWithCGPoint:do_mc_location_at_host], OBJC_ASSOCIATION_RETAIN);\n}\n\n- (CGPoint)do_mc_location_at_host {\n    return [self do_mc_point_value_forkey:_cmd];\n}\n\n\n- (void)do_mc_clear_all_value_at_host {\n    self.do_mc_location_at_host = CGPointZero;\n}\n\n- (void)setValue:(id)value forUndefinedKey:(NSString *)key {}\n- (id)valueForUndefinedKey:(NSString *)key {return nil;}\n\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UILongPressGestureRecognizer+DoraemonMCSerializer.h",
    "content": "//\n//  UILongPressGestureRecognizer+DoraemonMCSerializer.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UILongPressGestureRecognizer (DoraemonMCSerializer)\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UILongPressGestureRecognizer+DoraemonMCSerializer.m",
    "content": "//\n//  UILongPressGestureRecognizer+DoraemonMCSerializer.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import \"UILongPressGestureRecognizer+DoraemonMCSerializer.h\"\n#import \"UIGestureRecognizer+DoraemonMCSerializer.h\"\n#import <objc/runtime.h>\n#import \"NSObject+DoraemonMCSupport.h\"\n\n@implementation UILongPressGestureRecognizer (DoraemonMCSerializer)\n\n\n/// 当前手势对象的信息转为字典\n- (NSDictionary *)do_mc_serialize_dictionary {\n    return [super do_mc_serialize_dictionary];\n}\n\n/// 将字典中的值同步到当前手势对象的属性参数\n- (void)do_mc_serialize_syncInfoWithDictionary:(NSDictionary *)dictionary {\n    [super do_mc_serialize_syncInfoWithDictionary:dictionary];\n}\n\n#pragma mark - hook\n\n+ (void)load {\n\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(locationInView:) swizzledSel:@selector(do_mc_longPress_locationInView:)];\n    });\n}\n\n- (CGPoint)do_mc_longPress_locationInView:(UIView *)view{\n    if (CGPointEqualToPoint(CGPointZero, self.do_mc_location_at_host)) {\n        return [self do_mc_longPress_locationInView:view];\n    }\n    return self.do_mc_location_at_host;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UIPanGestureRecognizer+DoraemonMCSerializer.h",
    "content": "//\n//  UIPanGestureRecognizer+DoraemonMCSerializer.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UIPanGestureRecognizer (DoraemonMCSerializer)\n\n/// 主机上 平移手势的偏移距离\n@property (nonatomic , assign) CGPoint do_mc_translation_at_host;\n/// 主机上平移手势的加速度\n@property (nonatomic , assign) CGPoint do_mc_vol_at_host;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UIPanGestureRecognizer+DoraemonMCSerializer.m",
    "content": "//\n//  UIPanGestureRecognizer+DoraemonMCSerializer.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import \"UIPanGestureRecognizer+DoraemonMCSerializer.h\"\n#import \"UIGestureRecognizer+DoraemonMCSerializer.h\"\n#import \"DoraemonMCEventCapturer.h\"\n#import <objc/runtime.h>\n#import \"NSObject+DoraemonMCSupport.h\"\n#import <objc/message.h>\n\nstatic NSString const *kUIPanGestureRecognizerTranslateKey = @\"panTranslate\";\n\nstatic NSString const *kUIPanGestureRecognizerTranslateXKey = @\"x\";\n\nstatic NSString const *kUIPanGestureRecognizerTranslateYKey = @\"y\";\n\nstatic NSString const *kUIPanGestureRecognizerVelocityKey = @\"panVelocity\";\n\nstatic NSString const *kUIPanGestureRecognizerVelocityXKey = @\"x\";\n\nstatic NSString const *kUIPanGestureRecognizerVelocityYKey = @\"y\";\n\n@implementation UIPanGestureRecognizer (DoraemonMCSerializer)\n\n#pragma mark - serialize\n- (void)do_mc_serialize_setupDictionary:(NSMutableDictionary *)dictM {\n    [super do_mc_serialize_setupDictionary:dictM];\n    \n    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;\n    CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;\n    CGPoint translationP = [self translationInView:self.view];\n    CGPoint velocityP = [self velocityInView:self.view];\n\n    dictM[kUIPanGestureRecognizerTranslateKey] = @{\n        kUIPanGestureRecognizerTranslateXKey : @(translationP.x/screenWidth),\n        kUIPanGestureRecognizerTranslateYKey : @(translationP.y/screenHeight),\n    };\n    \n    dictM[kUIPanGestureRecognizerVelocityKey] = @{\n        kUIPanGestureRecognizerVelocityXKey : @(velocityP.x/screenWidth),\n        kUIPanGestureRecognizerVelocityYKey : @(velocityP.y/screenHeight),\n    };\n    \n}\n\n/// 将字典中的值同步到当前手势对象的属性参数\n- (void)do_mc_serialize_syncInfoWithDictionary:(NSDictionary *)dictionary {\n    [super do_mc_serialize_syncInfoWithDictionary:dictionary];\n    \n    if (![dictionary isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    \n    NSDictionary *map = dictionary[kUIGestureRecognizerDoraemonMCSerializerWrapperKey];\n    \n    if (![map isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    \n    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;\n    CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;\n    NSDictionary *translateMap = map[kUIPanGestureRecognizerTranslateKey];\n    CGPoint translation = CGPointMake([translateMap[kUIPanGestureRecognizerTranslateXKey] doubleValue] * screenWidth ,\n                                      [translateMap[kUIPanGestureRecognizerTranslateYKey] doubleValue] * screenHeight);\n\n    NSDictionary *velocityMap = map[kUIPanGestureRecognizerVelocityKey];\n    CGPoint velocity = CGPointMake([velocityMap[kUIPanGestureRecognizerVelocityXKey] doubleValue] * screenWidth,\n                                   [velocityMap[kUIPanGestureRecognizerVelocityYKey] doubleValue] * screenHeight);\n    \n    if (self.state == UIGestureRecognizerStateEnded ||\n        self.state == UIGestureRecognizerStateCancelled) {\n        self.do_mc_translation_at_host = CGPointZero;\n    }else {\n        self.do_mc_translation_at_host = translation;\n        self.do_mc_vol_at_host = velocity;\n    }\n        \n}\n\n#pragma mark - hook\n\n+ (void)load {\n\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(translationInView:) swizzledSel:@selector(do_mc_pan_translationInView:)];\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(velocityInView:) swizzledSel:@selector(do_mc_pan_velocityInView:)];\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(locationInView:) swizzledSel:@selector(do_mc_pan_locationInView:)];\n    });\n}\n\n\n- (void)setDo_mc_vol_at_host:(CGPoint)do_mc_vol_at_host\n{\n    objc_setAssociatedObject(self, @selector(do_mc_vol_at_host), [NSValue valueWithCGPoint:do_mc_vol_at_host], OBJC_ASSOCIATION_RETAIN);\n}\n\n- (CGPoint)do_mc_vol_at_host {\n    return [self do_mc_point_value_forkey:_cmd];\n}\n\n\n- (void)setDo_mc_translation_at_host:(CGPoint)do_mc_translation_at_host {\n    objc_setAssociatedObject(self, @selector(do_mc_translation_at_host), [NSValue valueWithCGPoint:do_mc_translation_at_host], OBJC_ASSOCIATION_RETAIN);\n}\n\n- (CGPoint)do_mc_translation_at_host {\n    return [self do_mc_point_value_forkey:_cmd];\n}\n\n- (CGPoint)do_mc_pan_translationInView:(UIView *)view {\n    if (CGPointEqualToPoint(CGPointZero, self.do_mc_translation_at_host)) {\n        return [self do_mc_pan_translationInView:view];\n    }\n    return self.do_mc_translation_at_host;\n}\n\n- (CGPoint)do_mc_pan_velocityInView:(UIView *)view {\n    if (CGPointEqualToPoint(CGPointZero, self.do_mc_vol_at_host)) {\n        return [self do_mc_pan_velocityInView:view];\n    }\n    return self.do_mc_vol_at_host;\n}\n\n- (CGPoint)do_mc_pan_locationInView:(UIView *)view {\n    if (CGPointEqualToPoint(CGPointZero, self.do_mc_location_at_host)) {\n        return  [self do_mc_pan_locationInView:view];\n    }\n    return self.do_mc_location_at_host;\n}\n\n- (void)do_mc_clear_all_value_at_host{\n    [super do_mc_location_at_host];\n    self.do_mc_translation_at_host = CGPointZero;\n    self.do_mc_vol_at_host = CGPointZero;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UIPinchGestureRecognizer+DoraemonMCSerializer.h",
    "content": "//\n//  UIPinchGestureRecognizer+DoraemonMCSerializer.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UIPinchGestureRecognizer (DoraemonMCSerializer)\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UIPinchGestureRecognizer+DoraemonMCSerializer.m",
    "content": "//\n//  UIPinchGestureRecognizer+DoraemonMCSerializer.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import \"UIPinchGestureRecognizer+DoraemonMCSerializer.h\"\n#import \"UIGestureRecognizer+DoraemonMCSerializer.h\"\n#import \"DoraemonMCEventCapturer.h\"\n#import \"NSObject+DoraemonMCSupport.h\"\n\nstatic NSString const *kUIPinchGestureRecognizerScaleKey = @\"pinchScale\";\n\n\n@implementation UIPinchGestureRecognizer (DoraemonMCSerializer)\n\n- (void)do_mc_serialize_setupDictionary:(NSMutableDictionary *)dictM {\n    [super do_mc_serialize_setupDictionary:dictM];\n    dictM[kUIPinchGestureRecognizerScaleKey] = @(self.scale);\n}\n\n/// 将字典中的值同步到当前手势对象的属性参数\n- (void)do_mc_serialize_syncInfoWithDictionary:(NSDictionary *)dictionary {\n    [super do_mc_serialize_syncInfoWithDictionary:dictionary];\n    \n    if (![dictionary isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    \n    NSDictionary *map = dictionary[kUIGestureRecognizerDoraemonMCSerializerWrapperKey];\n    \n    if (![map isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    \n    self.scale = [map[kUIPinchGestureRecognizerScaleKey] doubleValue];\n}\n\n#pragma mark - hook\n\n+ (void)load {\n\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(locationInView:) swizzledSel:@selector(do_mc_pinch_locationInView:)];\n    });\n}\n\n- (CGPoint)do_mc_pinch_locationInView:(UIView *)view{\n    if (CGPointEqualToPoint(CGPointZero, self.do_mc_location_at_host)) {\n        return [self do_mc_pinch_locationInView:view];\n    }\n    return self.do_mc_location_at_host;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UIResponder+DoraemonMCSerializer.h",
    "content": "//\n//  UIResponder+DoraemonMCSerializer.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\nUIKIT_EXTERN NSString const *kUIResponderDoraemonMCSerializerWrapperKey;\n\n@interface UIResponder (DoraemonMCSerializer)\n\n/// 当前对象的信息转为字典\n- (NSDictionary *)do_mc_serialize_dictionary;\n\n/// 将字典中的值同步到当前对象的属性参数\n- (void)do_mc_serialize_syncInfoWithDictionary:(NSDictionary *)dictionary;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UIResponder+DoraemonMCSerializer.m",
    "content": "//\n//  UIResponder+DoraemonMCSerializer.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import \"UIResponder+DoraemonMCSerializer.h\"\n\nNSString const *kUIResponderDoraemonMCSerializerWrapperKey = @\"resp_wrapper\";\n\nstatic NSString const *kUIResponderDoraemonMCSerializerFirstResponderKey = @\"firstResp\";\n\n\n@implementation UIResponder (DoraemonMCSerializer)\n\n/// 当前对象的信息转为字典\n- (NSDictionary *)do_mc_serialize_dictionary {\n    return @{\n        kUIResponderDoraemonMCSerializerWrapperKey : @{\n                kUIResponderDoraemonMCSerializerFirstResponderKey : @(self.isFirstResponder)\n        }\n    };\n}\n\n/// 将字典中的值同步到当前对象的属性参数\n- (void)do_mc_serialize_syncInfoWithDictionary:(NSDictionary *)dictionary {\n    if (![dictionary isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    NSDictionary *map = dictionary[kUIResponderDoraemonMCSerializerWrapperKey];\n    \n    if (![map isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    \n    NSNumber *firstResponderNumber = map[kUIResponderDoraemonMCSerializerFirstResponderKey];\n    \n    if (![firstResponderNumber isKindOfClass:[NSNumber class]]) {\n        return;\n    }\n    \n    if (self.isFirstResponder &&\n        NO == firstResponderNumber.boolValue) {\n        [self resignFirstResponder];\n    }else if (NO == self.isFirstResponder && firstResponderNumber.boolValue) {\n        [self becomeFirstResponder];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UIRotationGestureRecognizer+DoraemonMCSerializer.h",
    "content": "//\n//  UIRotationGestureRecognizer+DoraemonMCSerializer.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UIRotationGestureRecognizer (DoraemonMCSerializer)\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UIRotationGestureRecognizer+DoraemonMCSerializer.m",
    "content": "//\n//  UIRotationGestureRecognizer+DoraemonMCSerializer.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import \"UIRotationGestureRecognizer+DoraemonMCSerializer.h\"\n#import \"UIGestureRecognizer+DoraemonMCSerializer.h\"\n#import \"DoraemonMCEventCapturer.h\"\n#import \"NSObject+DoraemonMCSupport.h\"\n\nstatic NSString const *kUIRotationGestureRecognizerRotationKey = @\"rotationRotation\";\n\n@implementation UIRotationGestureRecognizer (DoraemonMCSerializer)\n\n- (void)do_mc_serialize_setupDictionary:(NSMutableDictionary *)dictM {\n    [super do_mc_serialize_setupDictionary:dictM];\n    dictM[kUIRotationGestureRecognizerRotationKey] = @(self.rotation);\n}\n\n/// 将字典中的值同步到当前手势对象的属性参数\n- (void)do_mc_serialize_syncInfoWithDictionary:(NSDictionary *)dictionary {\n    [super do_mc_serialize_syncInfoWithDictionary:dictionary];\n    \n    if (![dictionary isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    \n    NSDictionary *map = dictionary[kUIGestureRecognizerDoraemonMCSerializerWrapperKey];\n    \n    if (![map isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    \n    self.rotation = [map[kUIRotationGestureRecognizerRotationKey] doubleValue];\n}\n\n#pragma mark - hook\n\n+ (void)load {\n\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(locationInView:) swizzledSel:@selector(do_mc_rotation_locationInView:)];\n    });\n}\n\n- (CGPoint)do_mc_rotation_locationInView:(UIView *)view{\n    if (CGPointEqualToPoint(CGPointZero, self.do_mc_location_at_host)) {\n        return [self do_mc_rotation_locationInView:view];\n    }\n    return self.do_mc_location_at_host;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UISwipeGestureRecognizer+DoraemonMCSerializer.h",
    "content": "//\n//  UISwipeGestureRecognizer+DoraemonMCSerializer.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UISwipeGestureRecognizer (DoraemonMCSerializer)\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UISwipeGestureRecognizer+DoraemonMCSerializer.m",
    "content": "//\n//  UISwipeGestureRecognizer+DoraemonMCSerializer.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import \"UISwipeGestureRecognizer+DoraemonMCSerializer.h\"\n#import \"UIGestureRecognizer+DoraemonMCSerializer.h\"\n#import \"DoraemonMCEventCapturer.h\"\n#import \"NSObject+DoraemonMCSupport.h\"\n\nstatic NSString const *kUISwipeGestureRecognizerDirectionKey = @\"swipeDirection\";\n\n@implementation UISwipeGestureRecognizer (DoraemonMCSerializer)\n\n- (void)do_mc_serialize_setupDictionary:(NSMutableDictionary *)dictM {\n    [super do_mc_serialize_setupDictionary:dictM];\n    dictM[kUISwipeGestureRecognizerDirectionKey] = @(self.direction);\n}\n\n/// 将字典中的值同步到当前手势对象的属性参数\n- (void)do_mc_serialize_syncInfoWithDictionary:(NSDictionary *)dictionary {\n    [super do_mc_serialize_syncInfoWithDictionary:dictionary];\n    \n    if (![dictionary isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    \n    NSDictionary *map = dictionary[kUIGestureRecognizerDoraemonMCSerializerWrapperKey];\n    \n    if (![map isKindOfClass:[NSDictionary class]]) {\n        return;\n    }\n    \n    self.direction = [map[kUISwipeGestureRecognizerDirectionKey] intValue];\n}\n\n\n#pragma mark - hook\n\n+ (void)load {\n\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(locationInView:) swizzledSel:@selector(do_mc_swipe_locationInView:)];\n    });\n}\n\n- (CGPoint)do_mc_swipe_locationInView:(UIView *)view{\n    if (CGPointEqualToPoint(CGPointZero, self.do_mc_location_at_host)) {\n        return [self do_mc_swipe_locationInView:view];\n    }\n    return self.do_mc_location_at_host;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UITapGestureRecognizer+DoraemonMCSerializer.h",
    "content": "//\n//  UITapGestureRecognizer+DoraemonMCSerializer.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface UITapGestureRecognizer (DoraemonMCSerializer)\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/EventInfo/UITapGestureRecognizer+DoraemonMCSerializer.m",
    "content": "//\n//  UITapGestureRecognizer+DoraemonMCSerializer.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import \"UITapGestureRecognizer+DoraemonMCSerializer.h\"\n#import \"NSObject+DoraemonMCSupport.h\"\n#import \"UIGestureRecognizer+DoraemonMCSerializer.h\"\n\n@implementation UITapGestureRecognizer (DoraemonMCSerializer)\n\n#pragma mark - hook\n\n+ (void)load {\n\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [self do_mc_swizzleInstanceMethodWithOriginSel:@selector(locationInView:) swizzledSel:@selector(do_mc_tap_locationInView:)];\n    });\n}\n\n- (CGPoint)do_mc_tap_locationInView:(UIView *)view{\n    if (CGPointEqualToPoint(CGPointZero, self.do_mc_location_at_host)) {\n        return [self do_mc_tap_locationInView:view];\n    }\n    return self.do_mc_location_at_host;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/XPath/DoraemonMCXPathSerializer.h",
    "content": "//\n//  DoraemonMCXPath.h\n//  DoraemonKit-DoraemonKit\n//\n//  Created by litianhao on 2021/7/13.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonMCXPathNode : NSObject\n\n// 控件在父视图上的索引\n@property (nonatomic , assign) NSUInteger index;\n// 控件的类名\n@property (nonatomic , copy) NSString *className;\n\n+ (instancetype)nodeWithString:(NSString *)string;\n \n@end\n\n@interface DoraemonMCXPathSerializer : NSObject\n\n/// 当前控件是否在复用模式容器的单元格 例如:UITableViewCell, UICollectionViewCell\n@property (nonatomic , assign) BOOL isOneCell ;\n\n/// 如果isOneCell为true , 这个字段有值 列表容器的XPath 例如 UITableView , UICollectionView\n@property (nonatomic , strong , readonly) NSArray<DoraemonMCXPathNode *> *listContainerPathNodeList;\n\n/// 如果isOneCell为true , 这个字段有值 代表当前控件所在cell在列表容器中的索引\n@property (nonatomic , strong  ) NSIndexPath *cellIndexPath;\n\n/// 从根window到当前控件的XPath 如果isOneCell为true , 就代表从listContainer到当前控件的XPath\n@property (nonatomic , copy  , readonly) NSArray<DoraemonMCXPathNode *> *pathNodeList;\n\n/// 控件所在window在当前所有window数组的索引\n@property (nonatomic , assign) NSInteger windowIndex;\n\n/// 控件所在window的类名\n@property (nonatomic , copy  ) NSString *windowClsName;\n\n/// 控件所在window的根控制器类名\n@property (nonatomic , copy  ) NSString *windowRootVCClsName;\n\n/// 默认false\n@property (nonatomic , assign) BOOL ignore;\n\n/// 控件所在控制器类名\n@property (nonatomic , weak  ) UIViewController *vcCls;\n\n/// 根据控件获取XPath描述对象\n+ (instancetype )xPathInstanceWithView:(UIView *)view;\n\n/// 解析网络上传过来的xpath字符串信息,生成xPath对象\n+ (instancetype)xPathInstanceFromXpath:(NSString *)xpath;\n\n/// 根据控件获取XPath在网络上传输的字符串\n+ (NSString *)xPathStringWithView:(UIView *)view ;\n\n/// 根据网络上传过来的xpath字符串还原uiview\n+ (UIView *)viewFromXpath:(NSString *)xpath;\n\n/// 根据当前信息生成xPath在网络上传输的字符串\n- (NSString *)generalPathToTransfer;\n\n/// 根据当前的xPath信息查找到对应的view\n- (UIView *)fetchView;\n\n// 获取view所在控制器\n+ (UIViewController *)ownerVCWithView:(UIView *)view ;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Serialize/XPath/DoraemonMCXPathSerializer.m",
    "content": "//\n//  DoraemonMCXPath.m\n//  DoraemonKit-DoraemonKit\n//\n//  Created by litianhao on 2021/7/13.\n//\n\n#import \"DoraemonMCXPathSerializer.h\"\n#import <WebKit/WebKit.h>\n\nstatic NSUInteger const kDoraemonMCXPathUseKeyWindowIndex = 99999;\n@interface DoraemonMCXPathSerializer ()\n\n// 如果isOneCell为true , 这个字段有值 列表容器的XPath 例如 UITableView , UICollectionView\n@property (nonatomic , strong) NSArray<DoraemonMCXPathNode *> *listContainerPathNodeList;\n\n// 从根window到当前控件的XPath 如果isOneCell为true , 就代表从listContainer到当前控件的XPath\n@property (nonatomic , copy ) NSArray<DoraemonMCXPathNode *> *pathNodeList;\n\n@end\n\n@implementation DoraemonMCXPathSerializer\n\n+ (instancetype )xPathInstanceWithView:(UIView *)view {\n    return [[self alloc] initWithView:view];;\n}\n\n/// 解析网络上传过来的xpath字符串信息,生成xPath对象\n+ (instancetype)xPathInstanceFromXpath:(NSString *)xpath {\n    return [[self alloc] initWithXpathString:xpath];\n}\n\n+ (NSString *)xPathStringWithView:(UIView *)view {\n    return [[self xPathInstanceWithView:view] generalPathToTransfer];\n}\n\n+ (UIView *)viewFromXpath:(NSString *)xpath {\n    return [[self xPathInstanceFromXpath:xpath] fetchView];\n}\n\n- (instancetype)initWithView:(UIView *)view {\n    if (self = [super init]) {\n        self.windowIndex = NSNotFound;\n        [self parseView:view];\n    }\n    return self;\n}\n\n- (instancetype)initWithXpathString:(NSString *)xPathString {\n    if (self = [super init]) {\n        self.windowIndex = NSNotFound;\n        [self parseXpathString:xPathString];\n    }\n    return self;\n}\n\n- (NSArray<Class> *)ignoreContainerClasses {\n    return @[\n        [UIWebView class],\n        [WKWebView class]\n    ];\n}\n\n- (void)parseView:(UIView *)view {\n    UIWindow *currentWindow = nil;\n    if ([view isKindOfClass:[UIWindow class]] && view.window == nil) {\n        currentWindow = (UIWindow *)view;\n    }else {\n        currentWindow = view.window;\n    }\n    \n    if (currentWindow == nil) {\n        return;\n    }\n    \n    self.vcCls = [self.class ownerVCWithView:view];\n    \n    self.windowIndex = [[[UIApplication sharedApplication] windows] indexOfObject:currentWindow];\n    if (self.windowIndex == NSNotFound &&\n        [UIApplication sharedApplication].keyWindow == currentWindow) {\n        self.windowIndex = kDoraemonMCXPathUseKeyWindowIndex;\n    }\n    self.windowClsName = NSStringFromClass(currentWindow.class);\n    self.windowRootVCClsName = currentWindow.rootViewController ? NSStringFromClass(currentWindow.rootViewController.class) : @\"null\";\n    NSMutableArray<DoraemonMCXPathNode *> *currentPathNodeList = [NSMutableArray array];\n    \n    UIView *currentV = view;\n    BOOL isOnCell = NO;\n\n    while (currentV && currentV != currentWindow) {\n        \n        __block BOOL shouldIgnore = NO;\n        [[self ignoreContainerClasses] enumerateObjectsUsingBlock:^(Class  _Nonnull cls, NSUInteger idx, BOOL * _Nonnull stop) {\n            if ([currentV isKindOfClass:cls]) {\n                shouldIgnore = YES;\n                *stop = YES;\n            }\n        }];\n        if (shouldIgnore) {\n            self.ignore = YES;\n            break;\n        }\n        \n        NSDictionary *reuseViewMap = @{\n            NSStringFromClass(UICollectionViewCell.class) : UICollectionView.class,\n            NSStringFromClass(UITableViewCell.class) : UITableView.class,\n        };\n        __block Class resueViewCls =  reuseViewMap[NSStringFromClass(currentV.class)];\n        if (resueViewCls) {\n            isOnCell = YES;\n            UIView *cell = (UITableViewCell *)currentV;\n            UIView *reuseViewInstance = nil;\n            UIView *superV = cell.superview;\n            while (superV) {\n                if ([superV isKindOfClass:resueViewCls]) {\n                    reuseViewInstance = (UITableView *)superV;\n                    break;\n                }\n                superV = superV.superview;\n            }\n            if (reuseViewInstance && [reuseViewInstance respondsToSelector:@selector(indexPathForCell:)]) {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Warc-performSelector-leaks\"\n                self.cellIndexPath = [reuseViewInstance performSelector:@selector(indexPathForCell:) withObject:cell];\n#pragma clang diagnostic pop\n            }\n            currentV = reuseViewInstance;\n            self.pathNodeList = currentPathNodeList.copy;\n            [currentPathNodeList removeAllObjects];\n            \n            continue;\n        }\n        \n        DoraemonMCXPathNode *node = [[DoraemonMCXPathNode alloc] init];\n        node.className = NSStringFromClass(currentV.class);\n        node.index = [currentV.superview.subviews indexOfObject:currentV];\n        [currentPathNodeList insertObject:node atIndex:0];\n        \n        currentV = currentV.superview;\n    }\n    \n    if (self.ignore) {\n        return;\n    }\n    \n    self.isOneCell = isOnCell;\n    \n    if (isOnCell) {\n        self.listContainerPathNodeList = currentPathNodeList.copy;\n    }else {\n        self.pathNodeList = currentPathNodeList.copy;\n    }\n}\n\n\n- (void)parseXpathString:(NSString *)xPath {\n    if (![xPath isKindOfClass:[NSString class]] ||\n        xPath.length == 0) {\n        return;\n    }\n    NSArray *components = [xPath componentsSeparatedByString:@\"-\"];\n    if (components.count > 0) {\n        NSString *windowInfo = components.firstObject;\n        NSArray *windowInfoArr = [windowInfo componentsSeparatedByString:@\"/\"];\n        self.windowIndex = [windowInfoArr[0] integerValue];\n        self.windowClsName = windowInfoArr[1];\n        self.windowRootVCClsName = windowInfoArr[2];\n    }\n    switch (components.count) {\n        case 2:\n        {\n            NSArray *nodes = [components.lastObject componentsSeparatedByString:@\"/\"];\n            NSMutableArray *arrM = [NSMutableArray array];\n            [nodes enumerateObjectsUsingBlock:^(NSString *_Nonnull nodeString, NSUInteger idx, BOOL * _Nonnull stop) {\n                DoraemonMCXPathNode *node = [DoraemonMCXPathNode nodeWithString:nodeString];\n                if (node) {\n                    [arrM addObject:node];\n                }\n            }];\n            self.pathNodeList = arrM.copy;\n        }\n            break;\n        case 4:\n        {\n            NSArray *nodes = [components[1] componentsSeparatedByString:@\"/\"];\n            NSMutableArray *arrM = [NSMutableArray array];\n            [nodes enumerateObjectsUsingBlock:^(NSString *_Nonnull nodeString, NSUInteger idx, BOOL * _Nonnull stop) {\n                DoraemonMCXPathNode *node = [DoraemonMCXPathNode nodeWithString:nodeString];\n                if (node) {\n                    [arrM addObject:node];\n                }\n            }];\n            self.listContainerPathNodeList = arrM.copy;\n            \n            NSArray *indexPathComponents = [components[2] componentsSeparatedByString:@\"/\"];\n            self.isOneCell = YES;\n            if (indexPathComponents.count == 2) {\n                self.cellIndexPath = [NSIndexPath indexPathForRow:[indexPathComponents.lastObject integerValue] inSection:[indexPathComponents.firstObject integerValue]];\n            }\n            \n            nodes = [components.lastObject componentsSeparatedByString:@\"/\"];\n            arrM = [NSMutableArray array];\n            [nodes enumerateObjectsUsingBlock:^(NSString *_Nonnull nodeString, NSUInteger idx, BOOL * _Nonnull stop) {\n                DoraemonMCXPathNode *node = [DoraemonMCXPathNode nodeWithString:nodeString];\n                if (node) {\n                    [arrM addObject:node];\n                }\n            }];\n            self.pathNodeList = arrM.copy;\n            \n        }\n            break;\n        default:\n            break;\n    }\n\n}\n\n- (NSString *)pathStringWitgNode:(DoraemonMCXPathNode *)node isLastNode:(BOOL)isLastNode{\n    return [NSString stringWithFormat:@\"%zd&%@%@\",\n            node.index,\n            node.className ,\n            isLastNode?@\"\":@\"/\"];\n}\n\n/**\n 字符串拼装格式\n 1-1&UIView&2&3&0/2&UIView&2&3&0/3UIImage&2&3&0/1&UITabeView&2&3&0-5/3-3&UIView&2&3&0/2&UIButton&2&3&0/4&UILabel&2&3&0\n windowIndex-控件或list容器的索引路径与class名称路径-控件所在cell的索引section/row_控件在cell上的索引路径与class名称路径\n */\n- (NSString *)generalPathToTransfer {\n    if (self.windowIndex == NSNotFound) {\n        return @\"\";\n    }\n    NSString *resultPathString = @\"\";\n    \n    __block NSString *pathString = @\"\";\n    \n    [self.pathNodeList enumerateObjectsUsingBlock:^(DoraemonMCXPathNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) {\n        pathString = [pathString stringByAppendingString:[self pathStringWitgNode:node\n                                                                       isLastNode:(self.pathNodeList.count == (idx+1))]];\n    }];\n    \n    if (self.isOneCell) {\n        __block NSString *listContainerPath = @\"\";\n        \n        [self.listContainerPathNodeList enumerateObjectsUsingBlock:^(DoraemonMCXPathNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) {\n            listContainerPath = [listContainerPath stringByAppendingString:[self pathStringWitgNode:node\n                                                                           isLastNode:(self.listContainerPathNodeList.count == (idx+1))]];\n\n        }];\n        resultPathString = [NSString stringWithFormat:@\"%zd/%@/%@-%@-%zd/%zd-%@\",\n                            self.windowIndex,\n                            self.windowClsName,\n                            self.windowRootVCClsName,\n                            listContainerPath,\n                            self.cellIndexPath.section,\n                            self.cellIndexPath.row,\n                            pathString];\n    }else {\n        resultPathString = [NSString stringWithFormat:@\"%zd/%@/%@-%@\",self.windowIndex,self.windowClsName,self.windowRootVCClsName,pathString];\n    }\n    \n    return resultPathString;\n}\n\n- (BOOL)isWindowMatch:(UIWindow *)window {\n    if (!window) {\n        return NO;\n    }\n    \n    if (![NSStringFromClass(window.class) isEqualToString:self.windowClsName]) {\n        return NO;\n    }\n    \n    if (window.rootViewController == nil) {\n        return [self.windowClsName isEqualToString:@\"null\"];\n    }\n    \n    return [NSStringFromClass(window.rootViewController.class) isEqualToString:self.windowRootVCClsName];\n    \n}\n\n- (BOOL)isMatchWithView:(UIView *)view node:(DoraemonMCXPathNode *)node {\n    return YES;\n}\n\n- (UIView *)fetchView {\n    UIWindow *rootWidow = nil;\n    if ([UIApplication sharedApplication].windows.count > self.windowIndex) {\n        rootWidow = [[UIApplication sharedApplication].windows objectAtIndex:self.windowIndex];\n        if (![self isWindowMatch:rootWidow]) {\n            rootWidow = nil;\n            NSInteger i = 1 ;\n            while (true) {\n                if ((self.windowIndex - i) >= 0) {\n                    UIWindow *tempWindow = [UIApplication sharedApplication].windows[self.windowIndex - i];\n                    if ([self isWindowMatch:tempWindow]) {\n                        rootWidow = tempWindow ;\n                        break;\n                    }\n                }\n                \n                if ((self.windowIndex + i) < [UIApplication sharedApplication].windows.count) {\n                    UIWindow *tempWindow = [UIApplication sharedApplication].windows[self.windowIndex + i];\n                    if ([self isWindowMatch:tempWindow]) {\n                        rootWidow = tempWindow ;\n                        break;\n                    }\n                }\n                i ++ ;\n                if ((self.windowIndex - i) < 0 && [UIApplication sharedApplication].windows.count <= (i + self.windowIndex)) {\n                    break;\n                }\n            }\n        }\n    }\n    if (kDoraemonMCXPathUseKeyWindowIndex == self.windowIndex) {\n        rootWidow = [UIApplication sharedApplication].keyWindow;\n    }\n    \n    if (rootWidow == nil) {\n        return nil;\n    }\n    \n    if (self.isOneCell) {\n        __block BOOL notMatch = NO;\n        __block UIView *currentV = rootWidow;\n        [self.listContainerPathNodeList enumerateObjectsUsingBlock:^(DoraemonMCXPathNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) {\n            if (currentV != nil ) {\n                UIView *tempV = nil;\n                if (currentV.subviews.count > node.index) {\n                    tempV = currentV.subviews[node.index];\n                }\n                if (tempV && [self isMatchWithView:tempV node:node]) {\n                    currentV = tempV;\n                }else {\n                    __block UIView *resultV = nil;\n                    [currentV.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {\n                        if ([self isMatchWithView:obj node:node]) {\n                            resultV = obj;\n                            *stop = YES;\n                        }\n                    }];\n                    if (resultV) {\n                        currentV = resultV;\n                    }else {\n                        notMatch = YES;\n                        *stop = YES;\n                    }\n                }\n            }else {\n                notMatch = YES;\n                *stop = YES;\n            }\n        }];\n        if (notMatch ||\n            !([currentV isKindOfClass:[UITableView class]] ||\n              [currentV isKindOfClass:[UICollectionView class]])) {\n            return nil;\n        }\n        \n        __block BOOL notMatchOnCell = NO;\n        __block UIView *currentVOnCell = nil;\n\n        if ([currentV isKindOfClass:[UITableView class]]) {\n            UITableView *tableView = (UITableView *)currentV;\n            UITableViewCell *cell = [tableView cellForRowAtIndexPath:self.cellIndexPath];\n            currentVOnCell = cell;\n        }else if ([currentV isKindOfClass:[UICollectionView class]]) {\n            UICollectionView *collectionView = (UICollectionView *)currentV;\n            UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:self.cellIndexPath];\n            currentVOnCell = cell;\n        }\n        \n\n        [self.pathNodeList enumerateObjectsUsingBlock:^(DoraemonMCXPathNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) {\n            if (currentVOnCell != nil) {\n                UIView *tempV = nil;\n                if (currentVOnCell.subviews.count > node.index) {\n                    tempV = currentVOnCell.subviews[node.index];\n                }\n                if (tempV && [self isMatchWithView:tempV node:node]) {\n                    currentVOnCell = tempV;\n                }else {\n                    __block UIView *resultV = nil;\n                    [currentVOnCell.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {\n                        if ([self isMatchWithView:obj node:node]) {\n                            resultV = obj;\n                            *stop = YES;\n                        }\n                    }];\n                    if (resultV) {\n                        currentVOnCell = resultV;\n                    }else {\n                        notMatchOnCell = YES;\n                        *stop = YES;\n                    }\n                }\n            }else {\n                notMatchOnCell = YES;\n                *stop = YES;\n            }\n        }];\n        if (notMatchOnCell) {\n            return nil;\n        }\n        return currentVOnCell;\n        \n    }else {\n        __block BOOL notMatch = NO;\n        __block UIView *currentV = rootWidow;\n        [self.pathNodeList enumerateObjectsUsingBlock:^(DoraemonMCXPathNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) {\n             __block UIView *tempV =  nil ;\n            if (currentV != nil && currentV.subviews.count > node.index) {\n                tempV = currentV.subviews[node.index];\n                if ([self isMatchWithView:tempV node:node]) {\n                    currentV = tempV;\n                }else {\n                    tempV = nil;\n                }\n            }\n            if (!tempV) {\n                [currentV.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {\n                    if ([self isMatchWithView:obj node:node]) {\n                        tempV = obj;\n                        currentV = tempV;\n                    }\n                }];\n            }\n            \n            if (tempV == nil) {\n                notMatch = YES;\n                *stop = YES;\n            }\n        }];\n        if (notMatch) {\n            return nil;\n        }\n        return currentV;\n    }\n}\n\n+ (UIViewController *)ownerVCWithView:(UIView *)view {\n    \n    UIResponder *currentResponder = view.nextResponder;\n    while (![currentResponder isKindOfClass:[UIViewController class]] && currentResponder) {\n        currentResponder = currentResponder.nextResponder;\n    }\n    return (UIViewController *)currentResponder;\n}\n\n@end\n\n@implementation DoraemonMCXPathNode\n\n+ (instancetype)nodeWithString:(NSString *)string {\n    NSArray *nodeComponents =[string componentsSeparatedByString:@\"&\"];\n    if (nodeComponents.count == 2) {\n        DoraemonMCXPathNode *node = [[self alloc] init];\n        node.index = [nodeComponents[0] integerValue];\n        node.className = nodeComponents[1];\n        return node;\n    }\n    return nil;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Utils/NSObject+DoraemonMCSupport.h",
    "content": "//\n//  NSObject+DoraemonMCSupport.h\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface NSObject (DoraemonMCSupport)\n\n/**\n swizzle 类方法\n \n @param oriSel 原有的方法\n @param swiSel swizzle的方法\n */\n+ (void)do_mc_swizzleClassMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel;\n\n/**\n swizzle 实例方法\n \n @param oriSel 原有的方法\n @param swiSel swizzle的方法\n */\n+ (void)do_mc_swizzleInstanceMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel;\n\n+ (void)do_mc_swizzleInstanceMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel cls:(Class)cls;\n\n/// 获取key对应的关联对象的值 自动解包成CGPoint\n- (CGPoint)do_mc_point_value_forkey:(const void *)key ;\n\n/// 获取key对应的关联对象的值 自动解包成CGRect\n- (CGRect)do_mc_rect_value_forkey:(const void *)key ;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Function/EventSync/Utils/NSObject+DoraemonMCSupport.m",
    "content": "//\n//  NSObject+DoraemonMCSupport.m\n//  DoraemonKit\n//\n//  Created by litianhao on 2021/8/9.\n//\n\n#import \"NSObject+DoraemonMCSupport.h\"\n#import <objc/runtime.h>\n\n@implementation NSObject (DoraemonMCSupport)\n\n+ (void)do_mc_swizzleClassMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel {\n    Class cls = object_getClass(self);\n    \n    Method originAddObserverMethod = class_getClassMethod(cls, oriSel);\n    Method swizzledAddObserverMethod = class_getClassMethod(cls, swiSel);\n    \n    [self do_mc_swizzleMethodWithOriginSel:oriSel oriMethod:originAddObserverMethod swizzledSel:swiSel swizzledMethod:swizzledAddObserverMethod class:cls];\n}\n\n+ (void)do_mc_swizzleInstanceMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel {\n    Method originAddObserverMethod = class_getInstanceMethod(self, oriSel);\n    Method swizzledAddObserverMethod = class_getInstanceMethod(self, swiSel);\n    \n    [self do_mc_swizzleMethodWithOriginSel:oriSel oriMethod:originAddObserverMethod swizzledSel:swiSel swizzledMethod:swizzledAddObserverMethod class:self];\n}\n\n+ (void)do_mc_swizzleInstanceMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel cls:(Class)cls {\n    Method originAddObserverMethod = class_getInstanceMethod(self, oriSel);\n    Method swizzledAddObserverMethod = class_getInstanceMethod(cls, swiSel);\n    \n    \n    BOOL didAddMethod = class_addMethod(cls, oriSel, method_getImplementation(swizzledAddObserverMethod), method_getTypeEncoding(swizzledAddObserverMethod));\n    \n    if (didAddMethod) {\n        class_replaceMethod(cls, swiSel, method_getImplementation(originAddObserverMethod), method_getTypeEncoding(originAddObserverMethod));\n    } else {\n        method_exchangeImplementations(originAddObserverMethod, swizzledAddObserverMethod);\n    }\n}\n\n+ (void)do_mc_swizzleMethodWithOriginSel:(SEL)oriSel\n                         oriMethod:(Method)oriMethod\n                       swizzledSel:(SEL)swizzledSel\n                    swizzledMethod:(Method)swizzledMethod\n                             class:(Class)cls {\n    BOOL didAddMethod = class_addMethod(cls, oriSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));\n    \n    if (didAddMethod) {\n        class_replaceMethod(cls, swizzledSel, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));\n    } else {\n        method_exchangeImplementations(oriMethod, swizzledMethod);\n    }\n}\n\n- (void)setValue:(id)value forUndefinedKey:(NSString *)key{}\n\n- (CGPoint)do_mc_point_value_forkey:(const void * _Nonnull)key {\n   NSValue *value = objc_getAssociatedObject(self, key);\n    if (![value isKindOfClass:[NSValue class]]) {\n        return CGPointZero;\n    }\n    return [value CGPointValue];\n}\n\n- (CGRect)do_mc_rect_value_forkey:(const void * _Nonnull)key {\n   NSValue *value = objc_getAssociatedObject(self, key);\n    if (![value isKindOfClass:[NSValue class]]) {\n        return CGRectZero;\n    }\n    return [value CGRectValue];;\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Plugin/DoraemonMultiControlPlugin.h",
    "content": "//\n//  DoraemonWeexDevTooloPlugin.h\n//  DoraemonKit-DoraemonKit\n//\n//  Created by litianhao on 2021/7/12.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonMultiControlPlugin : NSObject\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/Plugin/DoraemonMultiControlPlugin.m",
    "content": "//\n//  DoraemonMultiControlPlugin.m\n//  DoraemonKit-DoraemonKit\n//\n//  Created by litianhao on 2021/7/12.\n//\n\n#import \"DoraemonMultiControlPlugin.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonManager.h\"\n#import \"DoraemonMCViewController.h\"\n#import \"DoraemonMCClient.h\"\n#import \"DoraemonMCServer.h\"\n\n\n@implementation DoraemonMultiControlPlugin\n\n+ (void)load {\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(UIApplicationDidFinishLaunchingNotificationDeal) name:UIApplicationDidFinishLaunchingNotification object:nil];\n    });\n}\n\n\n+ (void)UIApplicationDidFinishLaunchingNotificationDeal {\n  [[DoraemonManager shareInstance] addPluginWithTitle:DoraemonLocalizedString(@\"一机多控\")\n                                                 icon:@\"dk_icon_mc\"\n                                                 desc:@\"一机多控入口\"\n                                           pluginName:@\"一机多控\"\n                                             atModule:DoraemonLocalizedString(@\"平台工具\")\n                                               handle:^(NSDictionary * _Nonnull itemData) {\n      DoraemonMCViewController *toolVC = [DoraemonMCViewController new];\n      [DoraemonHomeWindow openPlugin:toolVC];\n      \n  }];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/UserInterface/Main/DoraemonMCViewController.h",
    "content": "//\n//  DoraemonMCViewController.h\n//  DoraemonKit-DoraemonKit\n//\n//  Created by litianhao on 2021/7/12.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\ntypedef NS_ENUM(NSInteger , DoraemonMCViewControllerWorkMode) {\n    DoraemonMCViewControllerWorkModeNone,\n    DoraemonMCViewControllerWorkModeServer,\n    DoraemonMCViewControllerWorkModeClient\n};\n\n@interface DoraemonMCViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/MultiControl/UserInterface/Main/DoraemonMCViewController.m",
    "content": "//\n//  DoraemonMCViewController.m\n//  DoraemonKit-DoraemonKit\n//\n//  Created by litianhao on 2021/7/12.\n//\n\n#import \"DoraemonMCViewController.h\"\n#import \"DoraemonQRScanView.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonMCServer.h\"\n#import \"DoraemonMCClient.h\"\n#import \"DoraemonAppInfoUtil.h\"\n#import \"DoraemonManager.h\"\n#import \"UIColor+Doraemon.h\"\n#import <DoraemonKit/DKQRCodeScanViewController.h>\n#import <DoraemonKit/DKMultiControlStreamManager.h>\n\n@interface DoraemonMCViewController () <DKMultiControlStreamManagerStateListener>\n\n@property(nonatomic, nullable, weak) UIButton *webSocketButton;\n\n@property(nonatomic, nullable, weak) UISwitch *masterSwitch;\n\n@property(nullable, nonatomic, strong) UIImageView *banner;\n\n- (void)webSocketButtonHandler:(nullable id)sender;\n\n- (void)masterSwitchHandler:(nullable id)sender;\n\n@end\n\n@implementation DoraemonMCViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    [self refreshUI];\n}\n\n- (UIImageView *)banner {\n    if (!_banner) {\n        CGFloat marginTop = 100 ;\n        UIImage *bannerImg =  [UIImage doraemon_xcassetImageNamed:@\"dk_mc_banner\"];\n        CGFloat bannerHeight = 300 ;\n        if (bannerImg.size.width) {\n            bannerHeight = self.view.bounds.size.width * bannerImg.size.height/bannerImg.size.width;\n        }\n        _banner = [[UIImageView alloc] initWithFrame:CGRectMake(0, marginTop, self.view.bounds.size.width, bannerHeight)];\n        _banner.image = bannerImg;\n        _banner.contentMode = UIViewContentModeScaleAspectFit;\n        [self.view addSubview:_banner];\n    }\n    \n    return _banner;\n}\n\n- (void)refreshUI {\n    self.title = @\"一机多控\";\n\n    [self banner];\n    \n    UIButton *webSocketButton = [[UIButton alloc] initWithFrame:CGRectMake(self.view.bounds.size.width / 2.0 - 50, CGRectGetMaxY(self.banner.frame) + 30, 100, 30)];\n    self.webSocketButton = webSocketButton;\n    webSocketButton.layer.cornerRadius = 5;\n    webSocketButton.backgroundColor = UIColor.doraemon_blue;\n    [webSocketButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];\n    webSocketButton.titleLabel.font = [UIFont systemFontOfSize:18];\n    [webSocketButton addTarget:self action:@selector(webSocketButtonHandler:) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:webSocketButton];\n    UISwitch *masterSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(self.view.bounds.size.width / 2 - 49 / 2, CGRectGetMaxY(webSocketButton.frame) + 30, 49, 31)];\n    [self.view addSubview:masterSwitch];\n    self.masterSwitch = masterSwitch;\n    [masterSwitch addTarget:self action:@selector(masterSwitchHandler:) forControlEvents:UIControlEventValueChanged];\n    [DKMultiControlStreamManager.sharedInstance registerMultiControlStreamManagerStateListener:self];\n}\n\n- (void)masterSwitchHandler:(id)sender {\n    if (((UISwitch *) sender).isOn) {\n        [DKMultiControlStreamManager.sharedInstance changeToMaster];\n    } else {\n        [DKMultiControlStreamManager.sharedInstance changeToSlave];\n    }\n}\n\n- (void)webSocketButtonHandler:(id)sender {\n    if (DKMultiControlStreamManager.sharedInstance.state == DKMultiControlStreamManagerStateClosed) {\n#if TARGET_OS_SIMULATOR\n        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@\"连接 DoKit Studio\" message:@\"请输入 ip 地址点击确定连接\" preferredStyle:UIAlertControllerStyleAlert];\n        __weak typeof(alertController) weakAlertController = alertController;\n        [alertController addAction:[UIAlertAction actionWithTitle:@\"确定\" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n            typeof(weakAlertController) alertController = weakAlertController;\n            NSString *ip = alertController.textFields.firstObject.text;\n            if (!ip) {\n                return;\n            }\n            NSURL *url = [NSURL URLWithString:ip];\n            if (!url) {\n                return;\n            }\n            [DKMultiControlStreamManager.sharedInstance enableMultiControlWithUrl:url];\n        }]];\n        [alertController addAction:[UIAlertAction actionWithTitle:@\"取消\" style:UIAlertActionStyleCancel handler:nil]];\n        [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {\n            textField.placeholder = @\"请输入 ip 地址\";\n        }];\n        [self presentViewController:alertController animated:YES completion:nil];\n#else\n        DKQRCodeScanViewController *qrCodeScanViewController = [[DKQRCodeScanViewController alloc] init];\n        qrCodeScanViewController.completionBlock = ^(NSString *decodedString) {\n            if (!decodedString) {\n                return;\n            }\n            NSURL *url = [NSURL URLWithString:decodedString];\n            if (!url) {\n                return;\n            }\n            [DKMultiControlStreamManager.sharedInstance enableMultiControlWithUrl:url];\n        };\n        [self showViewController:qrCodeScanViewController sender:sender];\n#endif\n    } else {\n        [DKMultiControlStreamManager.sharedInstance disableMultiControl];\n    }\n}\n\n- (void)changeToState:(DKMultiControlStreamManagerState)state {\n    switch (state) {\n        case DKMultiControlStreamManagerStateClosed:\n            [self.masterSwitch setOn:NO animated:YES];\n            [self.webSocketButton setTitle:@\"流式传输\" forState:UIControlStateNormal];\n            break;\n        case DKMultiControlStreamManagerStateSlave:\n            [self.masterSwitch setOn:NO animated:YES];\n            [self.webSocketButton setTitle:@\"断开连接\" forState:UIControlStateNormal];\n            break;\n        case DKMultiControlStreamManagerStateMaster:\n            [self.masterSwitch setOn:YES animated:YES];\n            [self.webSocketButton setTitle:@\"断开连接\" forState:UIControlStateNormal];\n            break;\n            \n        default:\n            break;\n    }\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/DevTool/DoraemonWeexDevToolViewController.h",
    "content": "//\n//  DoraemonWeexDevToolViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/6.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonWeexDevToolViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/DevTool/DoraemonWeexDevToolViewController.m",
    "content": "//\n//  DoraemonWeexDevToolViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/6.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonWeexDevToolViewController.h\"\n#import \"DoraemonQRScanView.h\"\n#import \"DoraemonDefine.h\"\n#import \"WXDevTool.h\"\n\n@interface DoraemonWeexDevToolViewController ()<DoraemonQRScanDelegate>\n\n@property (nonatomic, strong) DoraemonQRScanView *scanView;\n\n@end\n\n@implementation DoraemonWeexDevToolViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = @\"Weex DevTool\";\n}\n\n- (void)viewWillAppear:(BOOL)animated{\n    [super viewWillAppear:animated];\n    \n    DoraemonQRScanView *scaner = [[DoraemonQRScanView alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, self.view.doraemon_height-self.bigTitleView.doraemon_bottom)];\n    scaner.delegate = self;\n    scaner.showScanLine = YES;\n    scaner.showBorderLine = YES;\n    scaner.showCornerLine = YES;\n    scaner.scanRect = CGRectMake(scaner.doraemon_width/2-kDoraemonSizeFrom750(480)/2, kDoraemonSizeFrom750(195), kDoraemonSizeFrom750(480), kDoraemonSizeFrom750(480));\n    [self.view addSubview:scaner];\n    self.scanView = scaner;\n    [scaner startScanning];\n}\n\n- (void)viewWillDisappear:(BOOL)animated{\n    [super viewWillDisappear:animated];\n    [self removeScanView];\n}\n\n- (void)removeScanView{\n    if (self.scanView) {\n        [self.scanView stopScanning];\n        [self.scanView removeFromSuperview];\n        self.scanView = nil;\n    }\n}\n\n- (void)doUrl:(NSString *)URL{\n    NSString *transformURL = URL;\n    NSArray* elts = [URL componentsSeparatedByString:@\"?\"];\n    if (elts.count >= 2) {\n        NSArray *urls = [elts.lastObject componentsSeparatedByString:@\"=\"];\n        for (NSString *param in urls) {\n            if ([param isEqualToString:@\"_wx_tpl\"]) {\n                transformURL = [[urls lastObject]  stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];\n                break;\n            }\n        }\n    }\n    NSURL *url = [NSURL URLWithString:transformURL];\n    NSString *query = url.query;\n    for (NSString *param in [query componentsSeparatedByString:@\"&\"]){\n        NSArray *elts = [param componentsSeparatedByString:@\"=\"];\n        if([elts count] < 2) continue;\n        if ([[elts firstObject] isEqualToString:@\"_wx_devtool\"]) {\n            NSString *devToolURL = [[elts lastObject]  stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];\n            [WXDevTool launchDevToolDebugWithUrl:devToolURL];\n            [self leftNavBackClick:nil];\n            break;\n        }\n    }\n    \n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n#pragma mark -- DoraemonQRScanDelegate\n- (void)scanView:(DoraemonQRScanView *)scanView pickUpMessage:(NSString *)message{\n    if(message.length>0){\n        [self doUrl:message];\n    }\n}\n\n- (void)scanView:(DoraemonQRScanView *)scanView aroundBrightness:(NSString *)brightnessValue{\n    \n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/DevTool/DoraemonWeexDevTooloPlugin.h",
    "content": "//\n//  DoraemonWeexDevTooloPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/6.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\n@interface DoraemonWeexDevTooloPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/DevTool/DoraemonWeexDevTooloPlugin.m",
    "content": "//\n//  DoraemonWeexDevTooloPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/6.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonWeexDevTooloPlugin.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonWeexDevToolViewController.h\"\n#import \"DoraemonAppInfoUtil.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonDefine.h\"\n\n@implementation DoraemonWeexDevTooloPlugin\n\n- (void)pluginDidLoad{\n    if ([DoraemonAppInfoUtil isSimulator]) {\n        [DoraemonToastUtil showToastBlack:DoraemonLocalizedString(@\"模拟器不支持扫码功能\") inView:[DoraemonHomeWindow shareInstance]];\n        return;\n    }\n    DoraemonWeexDevToolViewController *vc = [[DoraemonWeexDevToolViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Info/DoraemonWeexInfoPlugin.h",
    "content": "//\n//  DoraemonWeexInfoPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/4.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonWeexInfoPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Info/DoraemonWeexInfoPlugin.m",
    "content": "//\n//  DoraemonWeexInfoPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/4.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonWeexInfoPlugin.h\"\n#import \"DoraemonWeexInfoViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonWeexInfoPlugin\n\n- (void)pluginDidLoad{\n    DoraemonWeexInfoViewController *vc = [[DoraemonWeexInfoViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Info/DoraemonWeexInfoViewController.h",
    "content": "//\n//  DoraemonWeexInfoViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/4.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonWeexInfoViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Info/DoraemonWeexInfoViewController.m",
    "content": "//\n//  DoraemonWeexInfoViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/4.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonWeexInfoViewController.h\"\n#import \"DoraemonWeexInfoHeaderView.h\"\n#import \"DoraemonWeexInfoDataManager.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonWeexInfoCell.h\"\n\n@interface DoraemonWeexInfoViewController ()<UITableViewDelegate,UITableViewDataSource>\n\n@property (nonatomic, strong) DoraemonWeexInfoHeaderView *headerView;\n@property (nonatomic, strong) UITableView *tableView;\n\n@end\n\n@implementation DoraemonWeexInfoViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"weex信息查看\");\n    \n    _headerView = [[DoraemonWeexInfoHeaderView alloc] init];\n    [_headerView renderUIWithWxBundleUrl:[DoraemonWeexInfoDataManager shareInstance].wxBundleUrl];\n    _headerView.frame = CGRectMake(0, self.bigTitleView.doraemon_bottom, _headerView.doraemon_width, _headerView.doraemon_height);\n    [self.view addSubview:_headerView];\n    \n    _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, _headerView.doraemon_bottom, self.view.doraemon_width, self.view.doraemon_height-_headerView.doraemon_bottom)];\n    _tableView.dataSource = self;\n    _tableView.delegate = self;\n    [self.view addSubview:_tableView];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n#pragma mark - UITableViewDelegate UITableViewDataSource\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return 8;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return [DoraemonWeexInfoCell cellHeight];\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"weex_info_cell\";\n    DoraemonWeexInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[DoraemonWeexInfoCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];\n    }\n    \n    NSUInteger row = indexPath.row;\n    NSString *title,*content;\n    if (row == 0) {\n        title = DoraemonLocalizedString(@\"weexsdk版本号\");\n        content = [DoraemonWeexInfoDataManager shareInstance].wxSDKVersion;\n    }else if(row == 1) {\n        title = DoraemonLocalizedString(@\"wxJSLib版本号\");\n        content = [DoraemonWeexInfoDataManager shareInstance].wxJSLibVersion;\n    }else if(row == 2) {\n        title = DoraemonLocalizedString(@\"wxBundleType\");\n        content = [DoraemonWeexInfoDataManager shareInstance].wxBundleType;\n    }else if(row == 3) {\n        title = DoraemonLocalizedString(@\"wxBundleSize\");\n        content = [DoraemonWeexInfoDataManager shareInstance].wxBundleSize;\n    }else if(row ==4) {\n        title = DoraemonLocalizedString(@\"请求bundle时间\");\n        content = [self timeFromStart:[DoraemonWeexInfoDataManager shareInstance].wxStartDownLoadBundle end:[DoraemonWeexInfoDataManager shareInstance].wxEndDownLoadBundle];\n    }else if(row ==5) {\n        title = DoraemonLocalizedString(@\"处理bundle时间\");\n        content = [self timeFromStart:[DoraemonWeexInfoDataManager shareInstance].wxEndDownLoadBundle end:[DoraemonWeexInfoDataManager shareInstance].wxEndLoadBundle];\n    }else if(row ==6) {\n        title = DoraemonLocalizedString(@\"第一个view出现时间\");\n        content = [self timeFromStart:[DoraemonWeexInfoDataManager shareInstance].wxEndLoadBundle end:[DoraemonWeexInfoDataManager shareInstance].wxFirstInteractionView];\n    }else if(row ==7) {\n        title = DoraemonLocalizedString(@\"可交互时间\");\n        content = [self timeFromStart:[DoraemonWeexInfoDataManager shareInstance].wxFirstInteractionView end:[DoraemonWeexInfoDataManager shareInstance].wxInteraction];\n    }else{\n        title = @\"默认\";\n        content = @\"默认\";\n    }\n    [cell renderCellWithTitle:title content:content];\n    return cell;\n}\n\n- (NSString *)timeFromStart:(NSString *)start end:(NSString *)end{\n    CGFloat duration = end.doubleValue - start.doubleValue;\n    return [NSString stringWithFormat:@\"%.1f\", duration];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Info/Function/DoraemonWeexInfoAnalyzer.h",
    "content": "//\n//  DoraemonWeexInfoAnalyzer.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/4.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import <WeexSDK/WeexSDK.h>\n\n\n@interface DoraemonWeexInfoAnalyzer : NSObject<WXAnalyzerProtocol>\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Info/Function/DoraemonWeexInfoAnalyzer.m",
    "content": "//\n//  DoraemonWeexInfoAnalyzer.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/4.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonWeexInfoAnalyzer.h\"\n#import \"DoraemonWeexInfoDataManager.h\"\n\n@implementation DoraemonWeexInfoAnalyzer\n\n- (void)transfer:(NSDictionary *) value{\n    [[DoraemonWeexInfoDataManager shareInstance] formatInfo:value];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Info/Function/DoraemonWeexInfoDataManager.h",
    "content": "//\n//  DoraemonWeexInfoDataManager.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/4.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n@interface DoraemonWeexInfoDataManager : NSObject\n\n+ (nonnull DoraemonWeexInfoDataManager *)shareInstance;\n\n- (void)formatInfo:(NSDictionary *)value;\n\n@property (nonatomic, copy) NSString *instanceId;\n@property (nonatomic, copy) NSString *wxBundleUrl;\n@property (nonatomic, copy) NSString *wxBundleSize;\n@property (nonatomic, copy) NSString *wxSDKVersion;\n@property (nonatomic, copy) NSString *wxJSLibVersion;\n@property (nonatomic, copy) NSString *wxBundleType;\n@property (nonatomic, copy) NSString *wxStartDownLoadBundle;\n@property (nonatomic, copy) NSString *wxEndDownLoadBundle;\n@property (nonatomic, copy) NSString *wxRenderTimeOrigin;\n@property (nonatomic, copy) NSString *wxEndLoadBundle;\n@property (nonatomic, copy) NSString *wxFirstInteractionView;\n@property (nonatomic, copy) NSString *wxInteraction;\n\n@end\nNS_ASSUME_NONNULL_END\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Info/Function/DoraemonWeexInfoDataManager.m",
    "content": "//\n//  DoraemonWeexInfoDataManager.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/4.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonWeexInfoDataManager.h\"\n#import \"DoraemonWeexInfoAnalyzer.h\"\n\n@implementation DoraemonWeexInfoDataManager\n\n+ (nonnull DoraemonWeexInfoDataManager *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonWeexInfoDataManager *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonWeexInfoDataManager alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    if (self = [super init]) {\n        [WXAnalyzerCenter addWxAnalyzer:[DoraemonWeexInfoAnalyzer new]];\n        [WXAnalyzerCenter setOpen:YES];\n    }\n    return self;\n}\n\n- (void)formatInfo:(NSDictionary *)value{\n    if (![value isKindOfClass:NSDictionary.class]) {\n        return;\n    }\n    \n    NSString *instanceId = value[@\"module\"];\n    if (![instanceId isKindOfClass:NSString.class]) {\n        return;\n    }\n    if (instanceId) {\n        if(!_instanceId){\n            _instanceId = instanceId;\n            NSLog(@\"doraemon instanceId = %@\",_instanceId);\n        }\n        if (![_instanceId isEqualToString:instanceId]) {\n            _instanceId = instanceId;\n            NSLog(@\"doraemon instanceId = %@\",_instanceId);\n        }\n    }\n    \n    NSDictionary *data = value[@\"data\"];\n    if (![data isKindOfClass:NSDictionary.class]) {\n        return;\n    }\n    NSString *wxBundleUrl = [data objectForKey:@\"wxBundleUrl\"];\n    if (wxBundleUrl) {\n        _wxBundleUrl = wxBundleUrl;\n        NSLog(@\"doraemon wxBundleUrl = %@\",_wxBundleUrl);\n    }\n    \n    NSNumber *wxBundleSize = [data objectForKey:@\"wxBundleSize\"];\n    if (wxBundleSize != nil) {\n        _wxBundleSize = [NSString stringWithFormat:@\"%@\",wxBundleSize];\n        NSLog(@\"doraemon wxBundleSize = %@\",_wxBundleSize);\n    }\n    \n    NSString *wxSDKVersion = [data objectForKey:@\"wxSDKVersion\"];\n    if (wxSDKVersion) {\n        _wxSDKVersion = wxSDKVersion;\n        NSLog(@\"doraemon wxSDKVersion = %@\",_wxSDKVersion);\n    }\n    \n    NSString *wxJSLibVersion = [data objectForKey:@\"wxJSLibVersion\"];\n    if (wxJSLibVersion) {\n        _wxJSLibVersion = wxJSLibVersion;\n        NSLog(@\"doraemon wxJSLibVersion = %@\",_wxJSLibVersion);\n    }\n    \n    NSString *wxBundleType = [data objectForKey:@\"wxBundleType\"];\n    if (wxBundleType) {\n        _wxBundleType = wxBundleType;\n        NSLog(@\"doraemon wxBundleType = %@\",_wxBundleType);\n    }\n\n    NSString *wxStartDownLoadBundle = [data objectForKey:@\"wxStartDownLoadBundle\"];\n    if (wxStartDownLoadBundle) {\n        _wxStartDownLoadBundle = wxStartDownLoadBundle;\n        NSLog(@\"doraemon wxStartDownLoadBundle = %@\",_wxStartDownLoadBundle);\n    }\n    NSString *wxEndDownLoadBundle = [data objectForKey:@\"wxEndDownLoadBundle\"];\n    if (wxEndDownLoadBundle) {\n        _wxEndDownLoadBundle = wxEndDownLoadBundle;\n        NSLog(@\"doraemon wxEndDownLoadBundle = %@\",_wxEndDownLoadBundle);\n    }\n    NSString *wxRenderTimeOrigin = [data objectForKey:@\"wxRenderTimeOrigin\"];\n    if (wxRenderTimeOrigin) {\n        _wxRenderTimeOrigin = wxRenderTimeOrigin;\n        NSLog(@\"doraemon wxRenderTimeOrigin = %@\",_wxRenderTimeOrigin);\n    }\n    NSString *wxEndLoadBundle = [data objectForKey:@\"wxEndLoadBundle\"];\n    if (wxEndLoadBundle) {\n        _wxEndLoadBundle = wxEndLoadBundle;\n        NSLog(@\"doraemon wxEndLoadBundle = %@\",_wxEndLoadBundle);\n    }\n    NSString *wxFirstInteractionView = [data objectForKey:@\"wxFirstInteractionView\"];\n    if (wxFirstInteractionView) {\n        _wxFirstInteractionView = wxFirstInteractionView;\n        NSLog(@\"doraemon wxFirstInteractionView = %@\",_wxFirstInteractionView);\n    }\n    NSString *wxInteraction = [data objectForKey:@\"wxInteraction\"];\n    if (wxInteraction) {\n        _wxInteraction = wxInteraction;\n        NSLog(@\"doraemon wxInteraction = %@\",_wxInteraction);\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Info/View/DoraemonWeexInfoCell.h",
    "content": "//\n//  DoraemonWeexInfoCell.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/5.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonWeexInfoCell : UITableViewCell\n\n- (void)renderCellWithTitle:(NSString *)title content:(NSString *)content;\n\n+ (CGFloat)cellHeight;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Info/View/DoraemonWeexInfoCell.m",
    "content": "//\n//  DoraemonWeexInfoCell.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/5.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonWeexInfoCell.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonWeexInfoCell()\n\n@property (nonatomic, strong) UILabel *leftLabel;\n@property (nonatomic, strong) UILabel *rightLabel;\n\n@end\n\n@implementation DoraemonWeexInfoCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        _leftLabel = [[UILabel alloc] init];\n        _leftLabel.font = [UIFont systemFontOfSize:14];\n        _leftLabel.textColor = [UIColor doraemon_black_2];\n        [self.contentView addSubview:_leftLabel];\n        \n        _rightLabel = [[UILabel alloc] init];\n        _rightLabel.font = [UIFont systemFontOfSize:14];\n        _rightLabel.textColor = [UIColor doraemon_black_2];\n        [self.contentView addSubview:_rightLabel];\n    }\n    return self;\n}\n\n- (void)renderCellWithTitle:(NSString *)title content:(NSString *)content{\n    CGFloat cellHeight = [[self class] cellHeight];\n    \n    _leftLabel.text = title;\n    [_leftLabel sizeToFit];\n    _leftLabel.frame = CGRectMake(15,cellHeight/2-_leftLabel.doraemon_height/2 , _leftLabel.doraemon_width, _leftLabel.doraemon_height);\n    \n    _rightLabel.text = content;\n    [_rightLabel sizeToFit];\n    _rightLabel.frame = CGRectMake(DoraemonScreenWidth-15-_rightLabel.doraemon_width, cellHeight/2-_rightLabel.doraemon_height/2, _rightLabel.doraemon_width, _rightLabel.doraemon_height);\n}\n\n+ (CGFloat)cellHeight{\n    return kDoraemonSizeFrom750(88);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Info/View/DoraemonWeexInfoHeaderView.h",
    "content": "//\n//  DoraemonWeexInfoHeaderView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/5.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonWeexInfoHeaderView : UIView\n\n- (void)renderUIWithWxBundleUrl:(NSString *)wxBundleUrl;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Info/View/DoraemonWeexInfoHeaderView.m",
    "content": "//\n//  DoraemonWeexInfoHeaderView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/6/5.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonWeexInfoHeaderView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonWeexInfoHeaderView()\n\n@property (nonatomic, strong) UILabel *textLabel;\n@property (nonatomic, strong) UIView *downLine;\n\n@end\n\n@implementation DoraemonWeexInfoHeaderView\n\n- (instancetype)init{\n    self = [super init];\n    if (self) {\n        _textLabel = [[UILabel alloc] init];\n        _textLabel.numberOfLines = 0;\n        _textLabel.textColor = [UIColor doraemon_black_1];\n        _textLabel.font = [UIFont systemFontOfSize:12];\n        [self addSubview:_textLabel];\n        \n        _downLine = [[UILabel alloc] init];\n        _downLine.backgroundColor = [UIColor doraemon_line];\n        [self addSubview:_downLine];\n    }\n    return self;\n}\n\n- (void)renderUIWithWxBundleUrl:(NSString *)wxBundleUrl{\n    _textLabel.text = wxBundleUrl;\n     CGSize size = [_textLabel sizeThatFits:CGSizeMake(DoraemonScreenWidth-30, CGFLOAT_MAX)];\n    _textLabel.frame = CGRectMake(15, 10, size.width, size.height);\n    self.frame = CGRectMake(0, 0, DoraemonScreenWidth, _textLabel.doraemon_height+20);\n    \n    _downLine.frame = CGRectMake(0, self.doraemon_height-0.5, self.doraemon_width, 0.5);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/DoraemonWeexLogPlugin.h",
    "content": "//\n//  DoraemonWeexLogPlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonWeexLogPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/DoraemonWeexLogPlugin.m",
    "content": "//\n//  DoraemonWeexLogPlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import \"DoraemonWeexLogPlugin.h\"\n#import \"DoraemonWeexLogViewController.h\"\n#import \"DoraemonHomeWindow.h\"\n\n@implementation DoraemonWeexLogPlugin\n\n- (void)pluginDidLoad{\n    DoraemonWeexLogViewController *vc = [[DoraemonWeexLogViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/DoraemonWeexLogViewController.h",
    "content": "//\n//  DoraemonWeexLogViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonWeexLogViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/DoraemonWeexLogViewController.m",
    "content": "//\n//  DoraemonWeexLogViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import \"DoraemonWeexLogViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonWeexLogSearchView.h\"\n#import \"DoraemonWeexLogLevelView.h\"\n#import \"DoraemonWeexLogCell.h\"\n#import \"DoraemonWeexLogDataSource.h\"\n\n@interface DoraemonWeexLogViewController ()<DoraemonWeexLogSearchViewDelegate,DoraemonWeexLogLevelViewDelegate,UITableViewDelegate,UITableViewDataSource>\n\n@property (nonatomic, strong) DoraemonWeexLogSearchView *searchView;\n@property (nonatomic, strong) DoraemonWeexLogLevelView *levelView;\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, copy) NSArray *dataArray;\n@property (nonatomic, copy) NSArray *origArray;\n\n@end\n\n@implementation DoraemonWeexLogViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"Weex日志记录\");\n    \n    self.origArray = [NSArray arrayWithArray:[DoraemonWeexLogDataSource shareInstance].logs];\n    self.dataArray = [NSArray arrayWithArray:self.origArray];\n    \n    _searchView = [[DoraemonWeexLogSearchView alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750(32), self.bigTitleView.doraemon_bottom+kDoraemonSizeFrom750(32), self.view.doraemon_width-2*kDoraemonSizeFrom750(32), kDoraemonSizeFrom750(100))];\n    _searchView.delegate = self;\n    [self.view addSubview:_searchView];\n    \n    _levelView = [[DoraemonWeexLogLevelView alloc] initWithFrame:CGRectMake(0, _searchView.doraemon_bottom+kDoraemonSizeFrom750(32), self.view.doraemon_width, kDoraemonSizeFrom750(68))];\n    _levelView.delegate = self;\n    [self.view addSubview:_levelView];\n    \n    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, _levelView.doraemon_bottom+kDoraemonSizeFrom750(32), self.view.doraemon_width, self.view.doraemon_height-_searchView.doraemon_bottom-kDoraemonSizeFrom750(32)) style:UITableViewStylePlain];\n//    self.tableView.backgroundColor = [UIColor whiteColor];\n    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;\n    self.tableView.delegate = self;\n    self.tableView.dataSource = self;\n    [self.view addSubview:self.tableView];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n#pragma mark - UITableView Delegate\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {\n    return 1;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {\n    return self.dataArray.count;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    DoraemonWeexLogModel* model = [self.dataArray objectAtIndex:indexPath.row];\n    return [DoraemonWeexLogCell cellHeightWith:model];\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {\n    return CGFLOAT_MIN;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {\n    return CGFLOAT_MIN;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {\n    static NSString *identifer = @\"nj_log_cell\";\n    DoraemonWeexLogCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (!cell) {\n        cell = [[DoraemonWeexLogCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];\n    }\n    DoraemonWeexLogModel* model = [self.dataArray objectAtIndex:indexPath.row];\n    [cell renderCellWithData:model];\n    return cell;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\n    [tableView deselectRowAtIndexPath:indexPath animated:YES];\n    DoraemonWeexLogModel* model = [self.dataArray objectAtIndex:indexPath.row];\n    model.expand = !model.expand;\n    [self.tableView reloadData];\n}\n\n#pragma mark -- DoraemonNSLogSearchViewDelegate\n- (void)searchViewInputChange:(NSString *)text{\n    if (text.length > 0) {\n        NSArray *dataArray = self.origArray;\n        NSMutableArray *resultArray = [[NSMutableArray alloc] init];\n        for(DoraemonWeexLogModel *model in dataArray){\n            NSString *content = model.content;\n            if ([content containsString:text]) {\n                [resultArray addObject:model];\n            }\n        }\n        self.dataArray = [[NSArray alloc] initWithArray:resultArray];\n    }else{\n        self.dataArray = [[NSArray alloc] initWithArray:self.origArray];\n    }\n    \n    [self.tableView reloadData];\n}\n\n#pragma mark - DoraemonNJLogLevelViewDelegate\n- (void)segmentSelected:(NSInteger)index{\n    WXLogFlag flag;\n    if (index == 0) {\n        flag = WXLogFlagDebug;\n    }else if(index == 1){\n        flag = WXLogFlagLog;\n    }else if(index == 2){\n        flag = WXLogFlagInfo;\n    }else if(index == 3){\n        flag = WXLogFlagWarning;\n    }else{\n        flag = WXLogFlagError;\n    }\n    NSArray *dataArray = self.origArray;\n    NSMutableArray *resultArray = [[NSMutableArray alloc] init];\n    for(DoraemonWeexLogModel *model in dataArray){\n        WXLogFlag modelFlag = model.flag;\n        if (modelFlag <= flag) {\n            [resultArray addObject:model];\n        }\n    }\n    self.dataArray = [[NSArray alloc] initWithArray:resultArray];\n    [self.tableView reloadData];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/Function/DoraemonWeexLogDataSource.h",
    "content": "//\n//  DoraemonWeexLogDataSource.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonWeexLogModel.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n@interface DoraemonWeexLogDataSource : NSObject\n\n+ (nonnull DoraemonWeexLogDataSource *)shareInstance;\n\n@property (nonatomic ,strong) NSMutableArray<DoraemonWeexLogModel *> *logs;\n\n- (void)addLog:(DoraemonWeexLogModel *)model;\n\n@end\nNS_ASSUME_NONNULL_END\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/Function/DoraemonWeexLogDataSource.m",
    "content": "//\n//  DoraemonWeexLogDataSource.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import \"DoraemonWeexLogDataSource.h\"\n#import \"DoraemonWeexLogger.h\"\n\n@interface DoraemonWeexLogDataSource()\n\n@property (nonatomic, strong) DoraemonWeexLogger *weexLogger;\n\n@end\n\n@implementation DoraemonWeexLogDataSource\n\n+ (nonnull DoraemonWeexLogDataSource *)shareInstance{\n    static dispatch_once_t once;\n    static DoraemonWeexLogDataSource *instance;\n    dispatch_once(&once, ^{\n        instance = [[DoraemonWeexLogDataSource alloc] init];\n    });\n    return instance;\n}\n\n- (instancetype)init{\n    if (self = [super init]) {\n        _logs = [[NSMutableArray alloc] init];\n        _weexLogger = [[DoraemonWeexLogger alloc] init];\n        [_weexLogger startLog];\n    }\n    return self;\n}\n\n- (void)addLog:(DoraemonWeexLogModel *)model{\n    if (_logs.count > 500) {\n        [_logs removeLastObject];\n    }\n    [_logs insertObject:model atIndex:0];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/Function/DoraemonWeexLogger.h",
    "content": "//\n//  DoraemonWeexLogger.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n\n\n@interface DoraemonWeexLogger : NSObject\n\n- (void)startLog;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/Function/DoraemonWeexLogger.m",
    "content": "//\n//  DoraemonWeexLogger.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import \"DoraemonWeexLogger.h\"\n#import <WeexSDK/WeexSDK.h>\n#import \"DoraemonWeexLogModel.h\"\n#import \"DoraemonWeexLogDataSource.h\"\n\n@interface DoraemonWeexLogger()<WXLogProtocol>\n\n\n@end\n\n@implementation DoraemonWeexLogger\n\n- (void)startLog{\n    [WXLog registerExternalLog:self];\n}\n\n\n- (WXLogLevel)logLevel{\n    return WXLogLevelAll;\n}\n\n- (void)log:(WXLogFlag)flag message:(NSString *)message{\n    if ([message containsString:@\"jsLog:\"]) {\n        NSRange range = [message rangeOfString:@\"jsLog:\"];\n        NSInteger jslogStart = range.location+range.length;\n        if (message.length > jslogStart) {\n            DoraemonWeexLogModel *model = [[DoraemonWeexLogModel alloc] init];\n            model.flag = flag;\n            model.timeInterval = [[NSDate date] timeIntervalSince1970];\n            model.expand = YES;\n            NSString *jsLog = [message substringFromIndex:(range.location+range.length)];\n            model.content = jsLog;\n            [[DoraemonWeexLogDataSource shareInstance] addLog:model];\n        }\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/Model/DoraemonWeexLogModel.h",
    "content": "//\n//  DoraemonWeexLogModel.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import <WeexSDK/WeexSDK.h>\n\nNS_ASSUME_NONNULL_BEGIN\n@interface DoraemonWeexLogModel : NSObject\n\n@property (nonatomic, copy) NSString *content;\n@property (nonatomic, assign) NSTimeInterval timeInterval;\n@property (nonatomic, assign) BOOL expand;\n@property (nonatomic, assign) WXLogFlag flag;\n\n@end\nNS_ASSUME_NONNULL_END\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/Model/DoraemonWeexLogModel.m",
    "content": "//\n//  DoraemonWeexLogModel.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import \"DoraemonWeexLogModel.h\"\n\n@implementation DoraemonWeexLogModel\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/View/DoraemonWeexLogCell.h",
    "content": "//\n//  DoraemonWeexLogCell.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonWeexLogModel.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n@interface DoraemonWeexLogCell : UITableViewCell\n\n- (void)renderCellWithData:(DoraemonWeexLogModel *)model;\n\n+ (CGFloat)cellHeightWith:(nullable DoraemonWeexLogModel *)model;\n\n@end\nNS_ASSUME_NONNULL_END\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/View/DoraemonWeexLogCell.m",
    "content": "//\n//  DoraemonWeexLogCell.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import \"DoraemonWeexLogCell.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonUtil.h\"\n#import <WeexSDK/WeexSDK.h>\n\n\n@interface DoraemonWeexLogCell()\n\n@property (nonatomic, strong) UIImageView *arrowImageView;\n@property (nonatomic, strong) UILabel *logLabel;\n\n@end\n\n@implementation DoraemonWeexLogCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        self.selectionStyle = UITableViewCellSelectionStyleNone;\n        \n        _arrowImageView = [[UIImageView alloc] initWithFrame:CGRectMake(kDoraemonSizeFrom750(27), [[self class] cellHeightWith:nil]/2-kDoraemonSizeFrom750(25)/2, kDoraemonSizeFrom750(25), kDoraemonSizeFrom750(25))];\n        _arrowImageView.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_expand_no\"];\n        _arrowImageView.contentMode = UIViewContentModeCenter;\n        [self.contentView addSubview:_arrowImageView];\n        \n        _logLabel = [[UILabel alloc] init];\n        _logLabel.textColor = [UIColor doraemon_black_1];\n        _logLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750(24)];\n        [self.contentView addSubview:_logLabel];\n    }\n    return self;\n}\n\n- (void)renderCellWithData:(DoraemonWeexLogModel *)model{\n    NSString *content;\n    if (model && model.expand){\n        NSString *log = model.content;\n        NSTimeInterval timeInterval = model.timeInterval;\n        NSString *time = [DoraemonUtil dateFormatTimeInterval:timeInterval];\n        content = [NSString stringWithFormat:DoraemonLocalizedString(@\"%@\\n[%@] [time]: %@\"),log,[[self class] logLevelWith:model.flag],time];\n        _logLabel.numberOfLines = 0;\n        _logLabel.text = content;\n        CGSize size = [_logLabel sizeThatFits:CGSizeMake(DoraemonScreenWidth-kDoraemonSizeFrom750(32)*2-kDoraemonSizeFrom750(25)-kDoraemonSizeFrom750(12)*2, MAXFLOAT)];\n        _logLabel.frame = CGRectMake(_arrowImageView.doraemon_right+kDoraemonSizeFrom750(12), [[self class] cellHeightWith:model]/2-size.height/2, size.width, size.height);\n        \n        _arrowImageView.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_expand\"];\n    }else{\n        _logLabel.numberOfLines = 1;\n        _logLabel.text = model.content;\n        _logLabel.frame = CGRectMake(_arrowImageView.doraemon_right+kDoraemonSizeFrom750(12), [[self class] cellHeightWith:model]/2-kDoraemonSizeFrom750(34)/2,DoraemonScreenWidth-kDoraemonSizeFrom750(32)*2-kDoraemonSizeFrom750(25)-kDoraemonSizeFrom750(12)*2 , kDoraemonSizeFrom750(34));\n        _arrowImageView.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_expand_no\"];\n    }\n}\n\n+ (CGFloat)cellHeightWith:(DoraemonWeexLogModel *)model{\n    CGFloat cellHeight = kDoraemonSizeFrom750(60);\n    if (model && model.expand) {\n        NSString *log = model.content;\n        NSTimeInterval timeInterval = model.timeInterval;\n        NSString *time = [DoraemonUtil dateFormatTimeInterval:timeInterval];\n        NSString *content = [NSString stringWithFormat:DoraemonLocalizedString(@\"%@\\n[%@] [time]: %@\"),log,[[self class] logLevelWith:model.flag],time];\n        \n        UILabel *logLabel = [[UILabel alloc] init];\n        logLabel.textColor = [UIColor doraemon_black_1];\n        logLabel.font = [UIFont systemFontOfSize:kDoraemonSizeFrom750(24)];\n        logLabel.text = content;\n        logLabel.numberOfLines = 0;\n        CGSize size = [logLabel sizeThatFits:CGSizeMake(DoraemonScreenWidth-kDoraemonSizeFrom750(32)*2-kDoraemonSizeFrom750(25)-kDoraemonSizeFrom750(12)*2, MAXFLOAT)];\n        cellHeight = kDoraemonSizeFrom750(10) + size.height + kDoraemonSizeFrom750(10);\n    }\n    return cellHeight;\n}\n\n+ (NSString *)logLevelWith:(WXLogFlag)flag{\n    NSString *text;\n    if (flag == WXLogFlagError) {\n        text = @\"error\";\n    }else if(flag == WXLogFlagWarning){\n        text = @\"warn\";\n    }else if(flag == WXLogFlagInfo){\n        text = @\"info\";\n    }else if(flag == WXLogFlagLog){\n        text = @\"log\";\n    }else{\n        text = @\"debug\";\n    }\n    return text;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/View/DoraemonWeexLogLevelView.h",
    "content": "//\n//  DoraemonWeexLogLevelView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\n@protocol DoraemonWeexLogLevelViewDelegate<NSObject>\n\n- (void)segmentSelected:(NSInteger)index;\n\n@end\n\n@interface DoraemonWeexLogLevelView : UIView\n\n@property (nonatomic, weak) id<DoraemonWeexLogLevelViewDelegate> delegate;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/View/DoraemonWeexLogLevelView.m",
    "content": "//\n//  DoraemonWeexLogLevelView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import \"DoraemonWeexLogLevelView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonWeexLogLevelView()\n\n@property (nonatomic, strong) UISegmentedControl *segment;\n\n@end\n\n@implementation DoraemonWeexLogLevelView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        NSArray *dataArray = @[@\"Debug\",@\"Log\",@\"Info\",@\"Warn\",@\"Error\"];\n        _segment = [[UISegmentedControl alloc] initWithItems:dataArray];\n        _segment.frame = CGRectMake(kDoraemonSizeFrom750(32), self.doraemon_height/2-kDoraemonSizeFrom750(68)/2, self.doraemon_width-kDoraemonSizeFrom750(32)*2, kDoraemonSizeFrom750(68));\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13, *)) {\n           _segment.selectedSegmentTintColor = [UIColor doraemon_colorWithString:@\"#337CC4\"];\n        } else {\n#endif\n            _segment.tintColor = [UIColor doraemon_colorWithString:@\"#337CC4\"];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        [_segment setSelectedSegmentIndex:0];\n        [_segment addTarget:self action:@selector(segmentChange:) forControlEvents:UIControlEventValueChanged];\n        [self addSubview:_segment];\n    }\n    return self;\n}\n\n-(void)segmentChange:(UISegmentedControl *)sender{\n    NSInteger index = sender.selectedSegmentIndex;\n    if (self.delegate && [self.delegate respondsToSelector:@selector(segmentSelected:)]) {\n        [self.delegate segmentSelected:index];\n    }\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/View/DoraemonWeexLogSearchView.h",
    "content": "//\n//  DoraemonWeexLogSearchView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\n@protocol DoraemonWeexLogSearchViewDelegate  <NSObject>\n\n- (void)searchViewInputChange:(NSString *)text;\n\n@end\n\n@interface DoraemonWeexLogSearchView : UIView\n\n@property (nonatomic, weak) id<DoraemonWeexLogSearchViewDelegate> delegate;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Log/View/DoraemonWeexLogSearchView.m",
    "content": "//\n//  DoraemonWeexLogSearchView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/23.\n//  Copyright © 2019年 Chameleon-Team. All rights reserved.\n//\n\n#import \"DoraemonWeexLogSearchView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonWeexLogSearchView()\n\n@property (nonatomic, strong) UIImageView *searchIcon;\n@property (nonatomic, strong) UITextField *textField;\n\n@end\n\n@implementation DoraemonWeexLogSearchView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        self.layer.cornerRadius = kDoraemonSizeFrom750(8);\n        self.layer.borderWidth = kDoraemonSizeFrom750(2);\n        self.layer.borderColor = [UIColor doraemon_colorWithHex:0x999999 andAlpha:0.2].CGColor;\n        \n        _searchIcon = [[UIImageView alloc] initWithImage:[UIImage doraemon_xcassetImageNamed:@\"doraemon_search\"]];\n        _searchIcon.frame = CGRectMake(kDoraemonSizeFrom750(20), self.doraemon_height/2-_searchIcon.doraemon_height/2, _searchIcon.doraemon_width, _searchIcon.doraemon_height);\n        [self addSubview:_searchIcon];\n        \n        _textField = [[UITextField alloc] initWithFrame:CGRectMake(_searchIcon.doraemon_right+kDoraemonSizeFrom750(20), self.doraemon_height/2-kDoraemonSizeFrom750(50)/2, self.doraemon_width-_searchIcon.doraemon_right-kDoraemonSizeFrom750(20), kDoraemonSizeFrom750(50))];\n        _textField.placeholder = DoraemonLocalizedString(@\"请输入您要搜索的关键字\");\n        [_textField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];\n        [self addSubview:_textField];\n    }\n    return self;\n}\n\n-(void)textFieldDidChange:(id)sender{\n    UITextField *senderTextField = (UITextField *)sender;\n    //去除首尾空格\n    NSString *textSearchStr = [senderTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];\n    if (self.delegate && [self.delegate respondsToSelector:@selector(searchViewInputChange:)]) {\n        [self.delegate searchViewInputChange:textSearchStr];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Storage/DoraemonWeexStoragePlugin.h",
    "content": "//\n//  DoraemonWeexStoragePlugin.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/30.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import \"DoraemonPluginProtocol.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonWeexStoragePlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Storage/DoraemonWeexStoragePlugin.m",
    "content": "//\n//  DoraemonWeexStoragePlugin.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/30.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonWeexStoragePlugin.h\"\n#import \"DoraemonHomeWindow.h\"\n#import \"DoraemonWeexStorageViewController.h\"\n\n@implementation DoraemonWeexStoragePlugin\n\n- (void)pluginDidLoad{\n    DoraemonWeexStorageViewController *vc = [[DoraemonWeexStorageViewController alloc] init];\n    [DoraemonHomeWindow openPlugin:vc];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Storage/DoraemonWeexStorageViewController.h",
    "content": "//\n//  DoraemonWeexStorageViewController.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/30.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonWeexStorageViewController : DoraemonBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Storage/DoraemonWeexStorageViewController.m",
    "content": "//\n//  DoraemonWeexStorageViewController.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/30.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonWeexStorageViewController.h\"\n#import \"DoraemonWeexStorageResolver.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonWeexStorageRowView.h\"\n#import \"DoraemonWeexStorageCell.h\"\n#import \"DoraemonWeexStorageShowView.h\"\n\n@interface DoraemonWeexStorageViewController ()<UITableViewDataSource,UITableViewDelegate,DoraemonWeexStorageRowViewDelegate>\n\n@property (nonatomic, copy) NSDictionary *storageInfo;\n@property (nonatomic, copy) NSArray *allKeys;\n\n@property (nonatomic, strong) DoraemonWeexStorageShowView *showView;\n\n@end\n\n@implementation DoraemonWeexStorageViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonLocalizedString(@\"Weex缓存\");\n    DoraemonWeexStorageResolver *resolver = [[DoraemonWeexStorageResolver alloc] init];\n    _storageInfo = [resolver getWeexStorageInfo];\n    _allKeys = [_storageInfo allKeys];\n    \n    UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, self.bigTitleView.doraemon_bottom, self.view.doraemon_width, self.view.doraemon_height-self.bigTitleView.doraemon_bottom)];\n    tableView.separatorStyle = UITableViewCellSeparatorStyleNone;\n//    tableView.backgroundColor = [UIColor whiteColor];\n    tableView.delegate = self;\n    tableView.dataSource = self;\n    [self.view addSubview:tableView];\n}\n\n- (BOOL)needBigTitleView{\n    return YES;\n}\n\n#pragma mark - UITableViewDelegate,UITableViewDataSource\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{\n    return 1;\n}\n\n- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{\n    DoraemonWeexStorageRowView *headerView = nil;\n    if (headerView == nil) {\n        headerView = [[DoraemonWeexStorageRowView alloc] init];\n    }\n    \n    headerView.dataArray = @[@\"Key\",@\"Value\"];\n    \n    return headerView;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{\n    return 44;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{\n    return _allKeys.count;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{\n    return 44;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{\n    static NSString *identifer = @\"DoraemonWeexStorageCellID\";\n    DoraemonWeexStorageCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];\n    if (cell == nil) {\n        cell = [[DoraemonWeexStorageCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identifer];\n        cell.selectionStyle = UITableViewCellSelectionStyleNone;\n        cell.rowView.delegate = self;\n    }\n    \n    cell.rowView.type = (indexPath.row % 2 == 0) ? DoraemonWeexStorageRowViewTypeForOne : DoraemonWeexStorageRowViewTypeForTwo;\n    NSString *key = _allKeys[indexPath.row];\n    NSString *value = _storageInfo[key];\n    [cell renderCellWithArray:@[key,value]];\n    \n    return cell;\n}\n\n#pragma mark -- DoraemonWeexStorageRowViewDelegate\n- (void)rowView:(DoraemonWeexStorageRowView *)rowView didLabelTaped:(UILabel *)label{\n    NSString *content = label.text;\n    //NSLog(@\"%@\",content);\n    [self showText:content];\n}\n\n#pragma mark -- 显示弹出文案\n- (void)showText:(NSString *)content{\n    if (self.showView) {\n        [self.showView removeFromSuperview];\n    }\n    self.showView = [[DoraemonWeexStorageShowView alloc] initWithFrame:self.view.bounds];\n    [self.view addSubview:self.showView];\n    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissView)];\n    self.showView.userInteractionEnabled = YES;\n    [self.showView addGestureRecognizer:tap];\n    \n    [self.showView showText:content];\n}\n\n- (void)dismissView{\n    if (self.showView) {\n        [self.showView removeFromSuperview];\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Storage/Function/DoraemonWeexStorageResolver.h",
    "content": "//\n//  DoraemonWeexStorageResolver.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/30.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonWeexStorageResolver : NSObject\n\n- (NSDictionary *)getWeexStorageInfo;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Storage/Function/DoraemonWeexStorageResolver.m",
    "content": "//\n//  DoraemonWeexStorageResolver.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/30.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonWeexStorageResolver.h\"\n\n@interface DoraemonWeexStorageResolver()\n\n@property (nonatomic, copy) NSString *weexStorageFile;\n\n@end\n\n@implementation DoraemonWeexStorageResolver\n\n- (instancetype)init{\n    self = [super init];\n    if (self) {\n        NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;\n        _weexStorageFile = [[docPath stringByAppendingPathComponent:@\"wxstorage\"] stringByAppendingPathComponent:@\"wxstorage.plist\"];\n    }\n    return self;\n}\n\n- (NSDictionary *)getWeexStorageInfo{\n    if (_weexStorageFile) {\n        NSDictionary *dic = [[NSDictionary alloc] initWithContentsOfFile:_weexStorageFile];\n        return dic;\n    }\n    return @{};\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Storage/View/DoraemonWeexStorageCell.h",
    "content": "//\n//  DoraemonWeexStorageCell.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/30.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoraemonWeexStorageRowView.h\"\n\n@interface DoraemonWeexStorageCell : UITableViewCell\n\n@property (nonatomic, strong) DoraemonWeexStorageRowView *rowView;\n\n- (void)renderCellWithArray:(NSArray *)array;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Storage/View/DoraemonWeexStorageCell.m",
    "content": "//\n//  DoraemonWeexStorageCell.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/30.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonWeexStorageCell.h\"\n\n@implementation DoraemonWeexStorageCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if (self) {\n        _rowView = [[DoraemonWeexStorageRowView alloc] init];\n        [self.contentView addSubview:_rowView];\n    }\n    return self;\n}\n\n- (void)layoutSubviews{\n    [super layoutSubviews];\n    _rowView.frame = self.contentView.bounds;\n}\n\n- (void)renderCellWithArray:(NSArray *)array{\n    _rowView.dataArray = array;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Storage/View/DoraemonWeexStorageRowView.h",
    "content": "//\n//  DoraemonWeexStorageRowView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/30.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n@class DoraemonWeexStorageRowView;\n\ntypedef NS_ENUM(NSInteger, DoraemonWeexStorageRowViewType) {\n    DoraemonWeexStorageRowViewTypeForTitle  = 0,\n    DoraemonWeexStorageRowViewTypeForOne   = 1,\n    DoraemonWeexStorageRowViewTypeForTwo   = 2\n};\n\n@protocol DoraemonWeexStorageRowViewDelegate <NSObject>\n\n- (void)rowView:(DoraemonWeexStorageRowView *)rowView didLabelTaped:(UILabel *)label;\n\n@end\n\n@interface DoraemonWeexStorageRowView : UIView\n\n@property(nonatomic, copy) NSArray *dataArray;\n\n@property(nonatomic, assign) DoraemonWeexStorageRowViewType type;\n\n@property(nonatomic, weak) id<DoraemonWeexStorageRowViewDelegate> delegate;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Storage/View/DoraemonWeexStorageRowView.m",
    "content": "//\n//  DoraemonWeexStorageRowView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/30.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonWeexStorageRowView.h\"\n#import \"DoraemonDefine.h\"\n\n@implementation DoraemonWeexStorageRowView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    if (self = [super initWithFrame:frame]) {\n        \n    }\n    return self;\n}\n\n- (void)setDataArray:(NSArray *)dataArray{\n    _dataArray = dataArray;\n    for (UIView *sub in self.subviews) {\n        [sub removeFromSuperview];\n    }\n    for (NSInteger i = 0; i < self.dataArray.count; i++) {\n        NSString *content = self.dataArray[i];\n        UILabel *label = [[UILabel alloc] init];\n        UIColor *color = [UIColor doraemon_colorWithString:@\"#dcdcdc\"];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            color = [UIColor doraemon_black_2];\n            if (self.type == DoraemonWeexStorageRowViewTypeForOne) {\n                color = [UIColor secondarySystemBackgroundColor];\n            }\n            if (self.type == DoraemonWeexStorageRowViewTypeForTwo) {\n                color = [UIColor tertiarySystemBackgroundColor];\n            }\n        } else {\n#endif\n            if (self.type == DoraemonWeexStorageRowViewTypeForOne) {\n                color = [UIColor doraemon_colorWithString:@\"#e6e6e6\"];\n            }\n            if (self.type == DoraemonWeexStorageRowViewTypeForTwo) {\n                color = [UIColor doraemon_colorWithString:@\"#ebebeb\"];\n            }\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        }\n#endif\n        label.backgroundColor = color;\n        label.text = content;\n        label.textAlignment = NSTextAlignmentCenter;\n        label.tag = i;\n        label.userInteractionEnabled = YES;\n        [label addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapLabel:)]];\n        [self addSubview:label];\n    }\n}\n\n- (void)layoutSubviews{\n    [super layoutSubviews];\n    \n    for (UIView *subView in self.subviews) {\n        if ([subView isKindOfClass:UILabel.class]) {\n            CGFloat width = (self.bounds.size.width - (self.dataArray.count - 1)) / self.dataArray.count;\n            subView.frame = CGRectMake(subView.tag * (width + 1), 0, width, self.bounds.size.height);\n        }\n    }\n}\n\n- (void)tapLabel:(UITapGestureRecognizer *)tap{\n    UILabel *label = (UILabel *)tap.view;\n    if ([self.delegate respondsToSelector:@selector(rowView:didLabelTaped:)]) {\n        [self.delegate rowView:self didLabelTaped:label];\n    }\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Storage/View/DoraemonWeexStorageShowView.h",
    "content": "//\n//  DoraemonWeexStorageShowView.h\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/30.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface DoraemonWeexStorageShowView : UIView\n\n- (void)showText:(NSString *)text;\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKit/Src/Weex/Storage/View/DoraemonWeexStorageShowView.m",
    "content": "//\n//  DoraemonWeexStorageShowView.m\n//  DoraemonKit\n//\n//  Created by yixiang on 2019/5/30.\n//  Copyright © 2019年 taobao. All rights reserved.\n//\n\n#import \"DoraemonWeexStorageShowView.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonWeexStorageShowView()\n\n@property (nonatomic, strong) UITextView *displayLabel;\n\n\n@end\n\n@implementation DoraemonWeexStorageShowView\n\n- (instancetype)initWithFrame:(CGRect)frame{\n    self = [super initWithFrame:frame];\n    if (self) {\n        _displayLabel = [[UITextView alloc] init];\n        //_displayLabel.numberOfLines = 0;\n        _displayLabel.textAlignment = NSTextAlignmentCenter; \n        _displayLabel.backgroundColor = [UIColor doraemon_black_2];\n#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)\n        if (@available(iOS 13.0, *)) {\n            _displayLabel.textColor = [UIColor labelColor];\n        }\n#endif\n        [self addSubview:_displayLabel];\n    }\n    return self;\n}\n\n- (void)showText:(NSString *)text{\n    _displayLabel.frame = CGRectMake(self.doraemon_width/2-150/2, self.doraemon_height/2-100/2, 75, 100);\n    __weak typeof(self) weakSelf = self;\n    [UIView animateWithDuration:0.25 animations:^{\n        __strong __typeof(weakSelf) strongSelf = weakSelf;\n        strongSelf.displayLabel.frame = CGRectMake(self.doraemon_width/2-300/2, self.doraemon_height/2-400/2, 300, 400);\n    } completion:^(BOOL finished) {\n        __strong __typeof(weakSelf) strongSelf = weakSelf;\n        strongSelf.displayLabel.text = text;\n    }];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"appicon_40.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"appicon_60.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"appicon_58.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"appicon_87.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"appicon_80.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"appicon_120.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"appicon_120-1.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"filename\" : \"appicon_180.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"filename\" : \"appicon_20.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"appicon_40-1.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"appicon_29.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"appicon_58-1.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"appicon_40-2.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"appicon_80-1.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"appicon_76.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"76x76\"\n    },\n    {\n      \"filename\" : \"appicon_152.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"76x76\"\n    },\n    {\n      \"filename\" : \"appicon_167.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"83.5x83.5\"\n    },\n    {\n      \"filename\" : \"appicon_1024.png\",\n      \"idiom\" : \"ios-marketing\",\n      \"scale\" : \"1x\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Assets.xcassets/emoji.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"emoji.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Assets.xcassets/zhaoliyin.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"zhaoliyin.jpg\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Base.lproj/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"13529\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" launchScreen=\"YES\" useTraitCollections=\"YES\" colorMatched=\"YES\" initialViewController=\"01J-lp-oVM\">\n    <device id=\"retina4_7\" orientation=\"portrait\">\n        <adaptation id=\"fullscreen\"/>\n    </device>\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"13527\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"EHf-IW-A2E\">\n            <objects>\n                <viewController id=\"01J-lp-oVM\" sceneMemberID=\"viewController\">\n                    <layoutGuides>\n                        <viewControllerLayoutGuide type=\"top\" id=\"JqV-FE-6aR\"/>\n                        <viewControllerLayoutGuide type=\"bottom\" id=\"LHq-iR-FbC\"/>\n                    </layoutGuides>\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"Ze5-6b-2t3\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"375\" height=\"667\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <color key=\"backgroundColor\" red=\"1\" green=\"1\" blue=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"sRGB\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"iYj-Kq-Ea1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"53\" y=\"375\"/>\n        </scene>\n    </scenes>\n</document>\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Base/DoraemonDemoBaseViewController.h",
    "content": "//\n//  DoraemonDemoBaseViewController.h\n//  DoraemonKitDemo\n//\n//  Created by didi on 2020/3/4.\n//  Copyright © 2020 yixiang. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoBaseViewController : UIViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Base/DoraemonDemoBaseViewController.m",
    "content": "//\n//  DoraemonDemoBaseViewController.m\n//  DoraemonKitDemo\n//\n//  Created by didi on 2020/3/4.\n//  Copyright © 2020 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n#import <DoraemonKit/DoraemonDefine.h>\n#import <DoraemonKit/DoraemonNavBarItemModel.h>\n\n@interface DoraemonDemoBaseViewController ()\n\n@property (nonatomic, strong) DoraemonNavBarItemModel *leftModel;\n\n@end\n\n@implementation DoraemonDemoBaseViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.view.backgroundColor = [UIColor whiteColor];\n    \n    UIImage *image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_back\"];\n    self.leftModel = [[DoraemonNavBarItemModel alloc] initWithImage:image selector:@selector(leftNavBackClick:)];\n    [self setLeftNavBarItems:@[self.leftModel]];\n    \n}\n\n- (void)setLeftNavBarItems:(NSArray *)items{\n    NSArray *barItems = [self navigationItems:items];\n    if (barItems) {\n        self.navigationItem.leftBarButtonItems = barItems;\n    }\n}\n\n- (NSArray *)navigationItems:(NSArray *)items{\n    NSMutableArray *barItems = [NSMutableArray array];\n    //距离左右的间距\n    UIBarButtonItem *spacer = [self getSpacerByWidth:-10];\n    [barItems addObject:spacer];\n    \n    for (NSInteger i=0; i<items.count; i++) {\n        \n        DoraemonNavBarItemModel *model = items[i];\n        UIBarButtonItem *barItem;\n        if (model.type == DoraemonNavBarItemTypeText) {//文字按钮\n            barItem = [[UIBarButtonItem alloc] initWithTitle:model.text style:UIBarButtonItemStylePlain target:self action:model.selector];\n            barItem.tintColor = model.textColor;\n        }else if(model.type == DoraemonNavBarItemTypeImage){//图片按钮\n            UIImage *image = [model.image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];//设置图片没有默认蓝色效果\n            //默认的间距太大\n//            barItem = [[UIBarButtonItem alloc] initWithImage:image style:UIBarButtonItemStylePlain target:self action:model.selector];\n            \n            UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];\n            [btn setImage:image forState:UIControlStateNormal];\n            [btn addTarget:self action:model.selector forControlEvents:UIControlEventTouchUpInside];\n            btn.frame = CGRectMake(0, 0, 30, 30);\n            btn.clipsToBounds = YES;\n            barItem = [[UIBarButtonItem alloc] initWithCustomView:btn];\n        }else{\n            barItem = [[UIBarButtonItem alloc] init];\n        }\n        [barItems addObject:barItem];\n    }\n    return barItems;\n}\n\n- (UIBarButtonItem *)getSpacerByWidth : (CGFloat)width{\n    UIBarButtonItem *spacer = [[UIBarButtonItem alloc]\n                               initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace\n                               target:nil action:nil];\n    /**\n     *  width为负数时，相当于btn向右移动width数值个像素，由于按钮本身和边界间距为5pix，所以width设为-5时，间距正好调整\n     *  为0；width为正数时，正好相反，相当于往左移动width数值个像素\n     */\n    spacer.width = width;\n    return spacer;\n}\n\n- (void)leftNavBackClick:(id)clickView{\n    [self.navigationController popViewControllerAnimated:YES];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Common/DoraemonDemoCommonViewController.h",
    "content": "//\n//  DoraemonDemoCommonViewController.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/12/3.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\n@interface DoraemonDemoCommonViewController : DoraemonDemoBaseViewController\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Common/DoraemonDemoCommonViewController.m",
    "content": "//\n//  DoraemonDemoCommonViewController.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/12/3.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoCommonViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonManager.h\"\n\n@interface DoraemonDemoCommonViewController ()\n\n@end\n\n@implementation DoraemonDemoCommonViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"通用测试Demo\");\n    \n    UIButton *btn0 = [[UIButton alloc] initWithFrame:CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT, self.view.doraemon_width, 60)];\n    btn0.backgroundColor = [UIColor orangeColor];\n    [btn0 setTitle:DoraemonDemoLocalizedString(@\"子线程UI操作\") forState:UIControlStateNormal];\n    [btn0 addTarget:self action:@selector(addSubViewAtOtherThread) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn0];\n    \n    UIButton *btn1 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn0.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn1.backgroundColor = [UIColor orangeColor];\n    [btn1 setTitle:DoraemonDemoLocalizedString(@\"显示入口\") forState:UIControlStateNormal];\n    [btn1 addTarget:self action:@selector(showEntry) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn1];\n    \n    UIButton *btn2 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn1.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn2.backgroundColor = [UIColor orangeColor];\n    [btn2 setTitle:DoraemonDemoLocalizedString(@\"隐藏入口\") forState:UIControlStateNormal];\n    [btn2 addTarget:self action:@selector(hiddenEntry) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn2];\n    \n    UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, btn2.doraemon_bottom+60, 100, 100)];\n    imgView.image = [[self class] jy_QRCodeFromString:@\"hello\" size:100];\n    [self.view addSubview:imgView];\n\n    // 设置允许摇一摇功能\n    [UIApplication sharedApplication].applicationSupportsShakeToEdit = YES;\n    // 并让自己成为第一响应者\n    [self becomeFirstResponder];\n}\n\n- (void)addSubViewAtOtherThread{\n    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{\n        UIView *v = [UIView new];\n        [self.view addSubview:v];\n    });\n}\n\n- (void)showEntry{\n    [[DoraemonManager shareInstance] showDoraemon];\n}\n\n- (void)hiddenEntry{\n    [[DoraemonManager shareInstance] hiddenDoraemon];\n}\n\n/**\n*  根据字符串生成二维码图片\n*\n*  @param code 二维码code\n*  @param size 生成图片大小\n*\n*  @return image\n*/\n+ (UIImage *)jy_QRCodeFromString:(NSString *)code size:(CGFloat)size{\n    //创建CIFilter 指定filter的名称为CIQRCodeGenerator\n    CIFilter *filter = [CIFilter filterWithName:@\"CIQRCodeGenerator\"];\n    //指定二维码的inputMessage,即你要生成二维码的字符串\n    [filter setValue:[code dataUsingEncoding:NSUTF8StringEncoding] forKey:@\"inputMessage\"];\n    //输出CIImage\n    CIImage *ciImage = [filter outputImage];\n    //对CIImage进行处理\n    return [self createfNonInterpolatedImageFromCIImage:ciImage withSize:size];\n}\n\n/**\n *  对CIQRCodeGenerator 生成的CIImage对象进行不插值放大或缩小处理\n *\n *  @param iamge 原CIImage对象\n *  @param size  处理后的图片大小\n *\n *  @return image\n */\n+ (UIImage *) createfNonInterpolatedImageFromCIImage:(CIImage *)iamge withSize:(CGFloat)size{\n    CGRect extent = iamge.extent;\n    CGFloat scale = MIN(size/CGRectGetWidth(extent), size/CGRectGetHeight(extent));\n    size_t with = scale * CGRectGetWidth(extent);\n    size_t height = scale * CGRectGetHeight(extent);\n    \n    UIGraphicsBeginImageContext(CGSizeMake(with, height));\n    CGContextRef bitmapContextRef = UIGraphicsGetCurrentContext();\n    \n    CIContext *context = [CIContext contextWithOptions:nil];\n    //通过CIContext 将CIImage生成CGImageRef\n    CGImageRef bitmapImage = [context createCGImage:iamge fromRect:extent];\n    //在对二维码放大或缩小处理时,禁止插值\n    CGContextSetInterpolationQuality(bitmapContextRef, kCGInterpolationNone);\n    //对二维码进行缩放\n    CGContextScaleCTM(bitmapContextRef, scale, scale);\n    //将二维码绘制到图片上下文\n    CGContextDrawImage(bitmapContextRef, extent, bitmapImage);\n    //获得上下文中二维码\n    UIImage *retVal =  UIGraphicsGetImageFromCurrentImageContext();\n    CGImageRelease(bitmapImage);\n    CGContextRelease(bitmapContextRef);\n    return retVal;\n}\n\n- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {\n    NSLog(@\"start shake\");\n    return;\n}\n\n- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event {\n    NSLog(@\"cancel shake\");\n    return;\n}\n\n- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {\n    if (event.subtype == UIEventSubtypeMotionShake) { // 判断是否是摇动结束\n        NSLog(@\"stop shake\");\n    }\n    return;\n}\n\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Crash/DoraemonDemoCrashMRCView.h",
    "content": "//\n//  DoraemonDemoCrashMRCView.h\n//  DoraemonKitDemo\n//\n//  Created by wenquan on 2018/11/22.\n//  Copyright © 2018 yixiang. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoCrashMRCView : UIView\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Crash/DoraemonDemoCrashMRCView.m",
    "content": "//\n//  DoraemonDemoCrashMRCView.m\n//  DoraemonKitDemo\n//\n//  Created by wenquan on 2018/11/22.\n//  Copyright © 2018 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoCrashMRCView.h\"\n\n@implementation DoraemonDemoCrashMRCView\n\n- (instancetype)init {\n    self = [super init];\n    if (self) {\n        UIView *tempView = [[UIView alloc]init];\n        //[tempView release];\n        \n        //对象已经被释放，内存不合法，此块内存地址又没被覆盖，所以此内存内垃圾内存，所以调用方法的时候会导致SIGSEGV的错误\n        [tempView setNeedsDisplay];\n    }\n    return self;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Crash/DoraemonDemoCrashViewController.h",
    "content": "//\n//  DoraemonDemoCrashViewController.h\n//  DoraemonKitDemo\n//\n//  Created by wenquan on 2018/11/5.\n//  Copyright © 2018 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoCrashViewController : DoraemonDemoBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Crash/DoraemonDemoCrashViewController.m",
    "content": "//\n//  DoraemonDemoCrashViewController.m\n//  DoraemonKitDemo\n//\n//  Created by wenquan on 2018/11/5.\n//  Copyright © 2018 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoCrashViewController.h\"\n\n#import <DoraemonKit/UIView+Doraemon.h>\n\n#import \"DoraemonDemoCrashMRCView.h\"\n#import \"DoraemonDefine.h\"\n\ntypedef struct Test\n{\n    int a;\n    int b;\n}Test;\n\n@interface DoraemonDemoCrashViewController ()\n\n@property (nonatomic, strong) UIButton *uncaughtExceptionBtn;\n@property (nonatomic, strong) UIButton *signalExceptionBtn;\n\n@end\n\n@implementation DoraemonDemoCrashViewController\n\n#pragma mark - View Lifecycle\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    // Do any additional setup after loading the view.\n    \n    self.title = @\"Crash\";\n    \n    [self.view addSubview:self.uncaughtExceptionBtn];\n    [self.view addSubview:self.signalExceptionBtn];\n}\n\n#pragma mark - Layout\n\n- (void)viewDidLayoutSubviews {\n    [super viewDidLayoutSubviews];\n    \n    self.uncaughtExceptionBtn.frame = CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT, self.view.doraemon_width, 60);\n    \n    self.signalExceptionBtn.frame = CGRectMake(0, self.uncaughtExceptionBtn.doraemon_bottom + 20, self.view.doraemon_width, 60);\n}\n\n#pragma mark - Actions\n\n- (void)uncaughtExceptionBtnClicked:(id)sender {\n    // ios崩溃\n    NSArray *array= @[@\"tom\",@\"xxx\",@\"ooo\"];\n    [array objectAtIndex:5];\n}\n\n- (void)signalExceptionBtnClicked:(id)sender {\n    // 导致SIGABRT的错误，因为内存中根本就没有这个空间，哪来的free，就在栈中的对象而已\n//    Test *pTest = {1,2};\n//    free(pTest);\n//    pTest->a = 5;\n    \n    // 导致SIGSEGV的错误\n//    DoraemonDemoCrashMRCView *view = [[DoraemonDemoCrashMRCView alloc] init];\n    \n    //SIGBUS，内存地址未对齐\n    //EXC_BAD_ACCESS(code=1,address=0x1000dba58)\n    char *s = \"hello world\";\n    *s = 'H';\n}\n\n#pragma mark - Getter\n\n- (UIButton *)uncaughtExceptionBtn {\n    if (!_uncaughtExceptionBtn) {\n        _uncaughtExceptionBtn = [[UIButton alloc] init];\n        _uncaughtExceptionBtn.backgroundColor = [UIColor orangeColor];\n        [_uncaughtExceptionBtn setTitle:@\"uncaughtException\" forState:UIControlStateNormal];\n        [_uncaughtExceptionBtn addTarget:self action:@selector(uncaughtExceptionBtnClicked:) forControlEvents:UIControlEventTouchUpInside];\n    }\n    return _uncaughtExceptionBtn;\n}\n\n- (UIButton *)signalExceptionBtn {\n    if (!_signalExceptionBtn) {\n        _signalExceptionBtn = [[UIButton alloc] init];\n        _signalExceptionBtn.backgroundColor = [UIColor orangeColor];\n        [_signalExceptionBtn setTitle:@\"signalException\" forState:UIControlStateNormal];\n        [_signalExceptionBtn addTarget:self action:@selector(signalExceptionBtnClicked:) forControlEvents:UIControlEventTouchUpInside];\n    }\n    return _signalExceptionBtn;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Home/DoraemonDemoHomeViewController.h",
    "content": "//\n//  DoraemonDemoHomeViewController.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/5/15.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\n@interface DoraemonDemoHomeViewController : DoraemonDemoBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Home/DoraemonDemoHomeViewController.m",
    "content": "//\n//  DoraemonDemoHomeViewController.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/5/15.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoHomeViewController.h\"\n#import \"DoraemonDemoLoggerViewController.h\"\n#import \"DoraemonDemoPerformanceViewController.h\"\n#import \"DoraemonDemoSanboxViewController.h\"\n#import \"DoraemonDemoUIViewController.h\"\n#import \"DoraemonDemoNetViewController.h\"\n#import \"DoraemonDemoMockGPSViewController.h\"\n#import \"DoraemonDemoCrashViewController.h\"\n#import \"DoraemonDemoCommonViewController.h\"\n#import <objc/runtime.h>\n#import \"UIView+Doraemon.h\"\n#import \"UIViewController+Doraemon.h\"\n#import \"DoraemonDemoMemoryLeakViewController.h\"\n#import \"DoraemonDemoMultiControlViewController.h\"\n\n@interface DoraemonDemoHomeViewController ()<UITableViewDelegate,UITableViewDataSource>\n\n@property (strong, nonatomic) UITableView *tableView;\n\n@end\n\n@implementation DoraemonDemoHomeViewController\n\n\n- (UITableView *)tableView {\n    if (!_tableView) {\n        _tableView = [[UITableView alloc] initWithFrame:self.view.bounds];\n        _tableView.delegate = self;\n        _tableView.dataSource = self;\n    }\n    \n    return _tableView;\n}\n\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"DoraemonKit\");\n    self.navigationItem.leftBarButtonItems = nil;\n    [self.view addSubview:self.tableView];\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{\n    return 11;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{\n    static NSString *cellId = @\"cellId\";\n    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];\n    if (!cell) {\n        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];\n    }\n    NSString *txt = nil;\n    NSInteger row = indexPath.row;\n    if (row==0) {\n        txt = DoraemonDemoLocalizedString(@\"沙盒测试Demo\");\n    }else if(row==1){\n        txt = DoraemonDemoLocalizedString(@\"日志测试Demo\");\n    }else if(row==2){\n        txt = DoraemonDemoLocalizedString(@\"性能测试Demo\");\n    }else if(row==3){\n        txt = DoraemonDemoLocalizedString(@\"视觉测试Demo\");\n    }else if(row==4){\n        txt = DoraemonDemoLocalizedString(@\"网络测试Demo\");\n    }else if(row==5){\n        txt = DoraemonDemoLocalizedString(@\"模拟位置Demo\");\n    }else if(row==6){\n        txt = DoraemonDemoLocalizedString(@\"crash触发Demo\");\n    }else if(row==7){\n        txt = DoraemonDemoLocalizedString(@\"通用测试Demo\");\n    }else if(row==8){\n        txt = DoraemonDemoLocalizedString(@\"内存泄漏测试\");\n    }else if(row == 9){\n        txt = DoraemonDemoLocalizedString(@\"一机多控测试Demo\");\n    }\n    cell.textLabel.text = txt;\n    return cell;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{\n    NSInteger row = indexPath.row;\n    UIViewController *vc = nil;\n    if (row == 0) {\n        vc = [[DoraemonDemoSanboxViewController alloc] init];\n    }else if(row == 1){\n        vc = [[DoraemonDemoLoggerViewController alloc] init];\n    }else if(row == 2){\n        vc = [[DoraemonDemoPerformanceViewController alloc] init];\n    }else if(row == 3){\n        vc = [[DoraemonDemoUIViewController alloc] init];\n    }else if(row == 4){\n        vc = [[DoraemonDemoNetViewController alloc] init];\n    }else if(row == 5){\n        vc = [[DoraemonDemoMockGPSViewController alloc] init];\n    }else if(row == 6){\n        vc = [[DoraemonDemoCrashViewController alloc] init];\n    }else if(row == 7){\n        vc = [[DoraemonDemoCommonViewController alloc] init];\n    }else if(row == 8){\n        vc = [[DoraemonDemoMemoryLeakViewController alloc] init];\n    }else if(row == 9){\n        vc = [[DoraemonDemoMultiControlViewController alloc] init];\n    }\n    if (vc) {\n        [self.navigationController pushViewController:vc animated:YES];\n    }\n \n    //产生crash\n//    NSArray *dataArray = @[@\"1\",@\"2\"];\n//    NSString *num = dataArray[2];\n//    NSLog(@\"num == %@\",num);\n}\n\n- (void)viewDidLayoutSubviews {\n    [super viewDidLayoutSubviews];\n\n    self.tableView.frame = [self fullscreen];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Logger/DoraemonDemoLoggerViewController.h",
    "content": "//\n//  DoraemonDemoLoggerViewController.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/5/15.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\n@interface DoraemonDemoLoggerViewController : DoraemonDemoBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Logger/DoraemonDemoLoggerViewController.m",
    "content": "//\n//  DoraemonDemoLoggerViewController.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/5/15.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoLoggerViewController.h\"\n#import <DoraemonKit/UIView+Doraemon.h>\n#import \"DoraemonDefine.h\"\n\n#if __has_include(<CocoaLumberjack/CocoaLumberjack.h>)\n#import <CocoaLumberjack/CocoaLumberjack.h>\n#endif\n\n@interface DoraemonDemoLoggerViewController ()\n\n@end\n\n@implementation DoraemonDemoLoggerViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"日志测试Demo\");\n    \n    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT, self.view.doraemon_width, 60)];\n    btn.backgroundColor = [UIColor orangeColor];\n    [btn setTitle:DoraemonDemoLocalizedString(@\"添加一条CocoaLumberjack日志\") forState:UIControlStateNormal];\n    [btn addTarget:self action:@selector(addLogger) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn];\n    \n    UIButton *btn_2 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn_2.backgroundColor = [UIColor orangeColor];\n    [btn_2 setTitle:DoraemonDemoLocalizedString(@\"添加一条NSLog日志\") forState:UIControlStateNormal];\n    [btn_2 addTarget:self action:@selector(addLogger2) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn_2];\n}\n\n- (void)addLogger{\n    #if __has_include(<CocoaLumberjack/CocoaLumberjack.h>)\n    DDLogInfo(@\"DDLogInfo。。。DDLogInfo。。。DDLogInfo。。。DDLogInfo。。。DDLogInfo。。。DDLogInfo。。。DDLogInfo。。。DDLogInfo。。。DDLogInfo。。。DDLogInfo。。。DDLogInfo。。。DDLogInfo。。。\");\n    DDLogError(@\"DDLogError。。。DDLogError。。。DDLogError。。。DDLogError。。。DDLogError。。。DDLogError。。。DDLogError。。。DDLogError。。。DDLogError。。。DDLogError。。。\");\n    #endif\n}\n\n- (void)addLogger2{\n    NSString *str = @\"jack\";\n    NSInteger age = 29;\n    NSLog(@\"NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦。。。str == %@  age == %zi\",str,age);\n    \n    NSString *specialString = @\"callnative://saveTian/%22saveTianDataCallback43%22\";\n    NSLog(@\"%@\",specialString);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MemoryLeak/DoraemonDemoMemoryLeakModel.h",
    "content": "//\n//  DoraemonDemoMemoryLeakModel.h\n//  DoraemonKitDemo\n//\n//  Created by didi on 2019/10/6.\n//  Copyright © 2019 yixiang. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\ntypedef void (^DoraemonDemoMemoryLeakModelBlock)(void);\n\n@interface DoraemonDemoMemoryLeakModel : NSObject\n\n- (void)addBlock:(DoraemonDemoMemoryLeakModelBlock)block;\n\n- (void)install;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MemoryLeak/DoraemonDemoMemoryLeakModel.m",
    "content": "//\n//  DoraemonDemoMemoryLeakModel.m\n//  DoraemonKitDemo\n//\n//  Created by didi on 2019/10/6.\n//  Copyright © 2019 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoMemoryLeakModel.h\"\n\n@interface DoraemonDemoMemoryLeakModel()\n\n@property (nonatomic, copy) DoraemonDemoMemoryLeakModelBlock block;\n\n@end\n\n@implementation DoraemonDemoMemoryLeakModel\n\n- (void)addBlock:(DoraemonDemoMemoryLeakModelBlock)block{\n    self.block = block;\n}\n\n- (void)install{\n    self.block();\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MemoryLeak/DoraemonDemoMemoryLeakView.h",
    "content": "//\n//  DoraemonDemoMemoryLeakView.h\n//  DoraemonKitDemo\n//\n//  Created by didi on 2019/10/6.\n//  Copyright © 2019 yixiang. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoMemoryLeakView : UIView\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MemoryLeak/DoraemonDemoMemoryLeakView.m",
    "content": "//\n//  DoraemonDemoMemoryLeakView.m\n//  DoraemonKitDemo\n//\n//  Created by didi on 2019/10/6.\n//  Copyright © 2019 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoMemoryLeakView.h\"\n#import \"DoraemonDemoMemoryLeakModel.h\"\n\n@interface DoraemonDemoMemoryLeakView()\n\n@property (nonatomic, strong) DoraemonDemoMemoryLeakModel *model;\n\n@end\n\n@implementation DoraemonDemoMemoryLeakView\n\n- (instancetype)initWithFrame:(CGRect)frame\n{\n    self = [super initWithFrame:frame];\n    if (self) {\n        self.backgroundColor = [UIColor redColor];\n    \n        _model = [[DoraemonDemoMemoryLeakModel alloc] init];\n        [_model addBlock:^{\n            [self printXX];\n        }];\n        [_model install];\n    }\n    return self;\n}\n\n- (void)printXX {\n    NSLog(@\"view XX\");\n}\n\n- (void)dealloc {\n    NSLog(@\"DoraemonDemoMemoryLeakView dealloc\");\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MemoryLeak/DoraemonDemoMemoryLeakViewController.h",
    "content": "//\n//  DoraemonDemoMemoryLeakViewController.h\n//  DoraemonKitDemo\n//\n//  Created by didi on 2019/10/6.\n//  Copyright © 2019 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoMemoryLeakViewController : DoraemonDemoBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MemoryLeak/DoraemonDemoMemoryLeakViewController.m",
    "content": "//\n//  DoraemonDemoMemoryLeakViewController.m\n//  DoraemonKitDemo\n//\n//  Created by didi on 2019/10/6.\n//  Copyright © 2019 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoMemoryLeakViewController.h\"\n#import \"DoraemonDemoMemoryLeakModel.h\"\n#import \"DoraemonDemoMemoryLeakView.h\"\n#import \"DoraemonKitDemoi18Util.h\"\n\n@interface DoraemonDemoMemoryLeakViewController ()\n\n@property (nonatomic, strong) DoraemonDemoMemoryLeakModel *model;\n@property (nonatomic, strong) DoraemonDemoMemoryLeakView *testview;\n\n@end\n\n@implementation DoraemonDemoMemoryLeakViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"内存泄漏测试\");\n    \n    //_model = [[DoraemonDemoMemoryLeakModel alloc] init];\n//    [_model addBlock:^{\n//        [self printXX];\n//    }];\n    //[_model install];\n\n    _testview = [[DoraemonDemoMemoryLeakView alloc] initWithFrame:CGRectMake(100, 200, 100, 100)];\n    [self.view addSubview:_testview];\n    \n    __weak id weakSelf = self;\n    //NSLog(@\"self == %@\",self);\n    //NSLog(@\"weakSelf == %@\",weakSelf);\n    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{\n        __strong id strongSelf = weakSelf;\n        NSLog(@\"strongSelf == %@\",strongSelf);\n        [strongSelf assertNotDeallocXX];\n    });\n    \n    /*\n    * weakself的缺陷，可能会导致内存提前回收\n    */\n       /*\n        这么做和直接用self有什么区别，为什么不会有循环引用：外部的weakSelf是为了打破环，从而使得没有循环引用，\n        \n        而内部的strongSelf仅仅是个局部变量，存在栈中，会在block执行结束后回收，不会再造成循环引用。\n        \n        这里的strongSelf会使 BlockLeakViewController 的对象引用计数＋1，使得BlockLeakViewController pop到 上个controller 的时候，并不会执行dealloc，因为引用计数还不为0，\n        \n        strongSelf仍持有BlockLeakViewController，而在block执行完，局部的strongSelf才会回收，此时BlockLeakViewController dealloc。\n        \n    那block中的StrongSelf又是做什么的呢？还是上面的例子，当你加了WeakSelf后，block中的self随时都会有被释放的可能，所以会出现一种情况，在调用doSomething的时候self还存在，在doMoreThing的时候self就变成nil了，所以为了避免这种情况发生，我们会重新strongify self。一般情况下，我们都建议这么做，这没什么风险，除非你不关心self在执行过程中变成nil，或者你确定它不会变成nil（比方说所以block都在main thread执行）。\n        \n        */\n    \n    //https://www.jianshu.com/p/51bb714051ea https://www.jianshu.com/p/ae4f84e289b9\n    \n}\n\n- (void)assertNotDeallocXX{\n    NSLog(@\"assertNotDealloc\");\n}\n\n- (void)viewDidDisappear:(BOOL)animated{\n    [super viewDidDisappear:animated];\n    \n    //__weak id weakSelf = self;\n//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{\n//        //__strong id strongSelf = weakSelf;\n//        [self printXX];\n//    });\n}\n\n- (void)printXX{\n    NSLog(@\"XX\");\n}\n\n- (void)dealloc{\n    NSLog(@\"DoraemonDemoMemoryLeakViewController dealloc\");\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MockGPS/DoraemonDemoMockGPSAnnotation.h",
    "content": "//\n//  DoraemonDemoMockGPSAnnotation.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/7/4.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import <MapKit/MapKit.h>\n\n@interface DoraemonDemoMockGPSAnnotation : NSObject<MKAnnotation>\n\n/* 必须创建的属性 */\n@property (nonatomic) CLLocationCoordinate2D coordinate;\n/* 可选的属性 */\n@property (nonatomic, copy) NSString *title;\n@property (nonatomic, copy) NSString *subtitle;\n/* 自定义的属性 */\n@property (nonatomic, strong) UIImage *icon;\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MockGPS/DoraemonDemoMockGPSAnnotation.m",
    "content": "//\n//  DoraemonDemoMockGPSAnnotation.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/7/4.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoMockGPSAnnotation.h\"\n\n@implementation DoraemonDemoMockGPSAnnotation\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MockGPS/DoraemonDemoMockGPSViewController.h",
    "content": "//\n//  DoraemonDemoMockGPSViewController.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/7/4.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\n@interface DoraemonDemoMockGPSViewController : DoraemonDemoBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MockGPS/DoraemonDemoMockGPSViewController.m",
    "content": "//\n//  DoraemonDemoMockGPSViewController.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/7/4.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoMockGPSViewController.h\"\n#import <MapKit/MapKit.h>\n#import \"UIView+Doraemon.h\"\n#import \"DoraemonDemoMockGPSAnnotation.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonDemoMockGPSViewController ()<MKMapViewDelegate,CLLocationManagerDelegate>\n\n@property (nonatomic, strong) MKMapView *mapView;\n@property (nonatomic, strong) DoraemonDemoMockGPSAnnotation *annotation;\n\n@property (nonatomic, strong) CLLocationManager *lcManager;\n\n@end\n\n@implementation DoraemonDemoMockGPSViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"模拟位置\");\n    \n    //初始化地图\n    MKMapView *mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT, self.view.doraemon_width, self.view.doraemon_height)];\n    mapView.mapType = MKMapTypeStandard;\n    mapView.delegate = self;\n    //mapView.showsUserLocation = YES;\n    [self.view addSubview:mapView];\n    self.mapView = mapView;\n    \n    // 2.判断是否打开了位置服务\n    if([CLLocationManager locationServicesEnabled]) {\n        // 创建位置管理者对象\n        self.lcManager = [[CLLocationManager alloc] init];\n        self.lcManager.delegate = self; // 设置代理\n        // 设置定位距离过滤参数 (当本次定位和上次定位之间的距离大于或等于这个值时，调用代理方法)\n        self.lcManager.distanceFilter = 100;\n        self.lcManager.desiredAccuracy = kCLLocationAccuracyBest; // 设置定位精度(精度越高越耗电)\n        [self.lcManager startUpdatingLocation]; // 开始更新位置\n    }\n}\n\n/** 获取到新的位置信息时调用*/\n-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{\n    CLLocation *l = locations[0];\n    NSLog(@\"location at %f %f\",l.coordinate.longitude,l.coordinate.latitude);\n    [self refreshAnnotation:l];\n}\n\n- (void)refreshAnnotation:(CLLocation *)loc {\n    if (_mapView) {\n        _mapView.centerCoordinate = loc.coordinate;\n        [_mapView setRegion:MKCoordinateRegionMake(loc.coordinate, MKCoordinateSpanMake(40., 40.)) animated:YES];\n    }\n\n\n    //设置当前位置的大头针\n    [_mapView removeAnnotation:_annotation];\n    if (!_annotation) {\n        _annotation = [[DoraemonDemoMockGPSAnnotation alloc] init];\n        _annotation.title = @\"title\";\n        _annotation.subtitle = @\"subTitle\";\n        _annotation.icon = [UIImage imageNamed:@\"AppIcon\"];\n    }\n\n    _annotation.coordinate = loc.coordinate;\n    \n    [_mapView addAnnotation:_annotation];\n    \n}\n/** 不能获取位置信息时调用*/\n-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{\n    NSLog(@\"location failure\");\n}\n\n/* 每当大头针显示在可视界面上时，就会调用该方法，用户位置的蓝色点也是个大头针，也会调用 */\n- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation\n{\n    if ([annotation isKindOfClass:[DoraemonDemoMockGPSAnnotation class]]) {\n        DoraemonDemoMockGPSAnnotation *annotationLT = (DoraemonDemoMockGPSAnnotation *)annotation;\n        //类似于UITableViewCell的重用机制，大头针视图也有重用机制\n        static NSString *key = @\"AnnotationIdentifier\";\n        MKAnnotationView *view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:key];\n        if (!view) {\n            view = [[MKAnnotationView alloc] initWithAnnotation:annotation\n                                                reuseIdentifier:key];\n        }\n        //设置大头针数据\n        view.annotation = annotation;\n        //自定义大头针默认是NO，表示不能弹出视图，这里让大头针可以点击弹出视图\n        view.canShowCallout = NO;\n        //设置大头针图标\n        view.image = annotationLT.icon;\n        return view;\n    }\n    //返回nil，表示显示默认样式\n    return nil;\n}\n\n\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiConLongPressGesture.h",
    "content": "//\n//  DoraemonDemoMultiConLongPressGesture.h\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/7/21.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoMultiConLongPressGesture : DoraemonDemoBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiConLongPressGesture.m",
    "content": "//\n//  DoraemonDemoMultiConLongPressGesture.m\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/7/21.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoMultiConLongPressGesture.h\"\n#import \"UIView+Doraemon.h\"\n#import \"UIImage+Doraemon.h\"\n@interface DoraemonDemoMultiConLongPressGesture ()\n\n@property (nonatomic, strong)UIImageView *imageview;\n\n@end\n\n@implementation DoraemonDemoMultiConLongPressGesture\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"长按事件\");\n    self.imageview.userInteractionEnabled = YES;\n    [self.view addSubview:self.imageview];\n    UILongPressGestureRecognizer * longpress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longpress:)];\n    longpress.minimumPressDuration = 0.5;\n    longpress.numberOfTapsRequired = 0;\n    longpress.cancelsTouchesInView = YES;\n    [self.imageview addGestureRecognizer:longpress];\n}\n\n- (UIImageView *)imageview {\n    if(!_imageview){\n        CGFloat size = 240;\n        _imageview = [[UIImageView alloc] initWithFrame:CGRectMake((self.view.doraemon_width - size) / 2.0, size, self.view.doraemon_width, size)];//        CGFloat size =\n        \n        _imageview.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_file_sync_banner\"];\n    }\n    return _imageview;\n}\n\n-(void)longpress:(UILongPressGestureRecognizer *)sender {\n    [self showAlertMessage];\n}\n\n\n- (void)showAlertMessage{\n    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@\"提示\" message:[NSString stringWithFormat:@\"是否要删除图片\"] preferredStyle:UIAlertControllerStyleAlert];\n    UIAlertAction *sureAction = [UIAlertAction actionWithTitle:@\"取消\" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n        \n    }];\n    UIAlertAction *sureAction2 = [UIAlertAction actionWithTitle:@\"同意\" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n        [self deleteImageAction];\n    }];\n    [alertController addAction:sureAction];\n    [alertController addAction:sureAction2];\n    \n    [self.navigationController presentViewController:alertController animated:YES completion:nil];\n}\n\n\n- (void)deleteImageAction {\n    [UIView animateWithDuration:1.0\n                          delay:0.0\n                        options:0\n                     animations:^{\n                         self.imageview.alpha = 0.0;\n                     } completion:^(BOOL finished) {\n                         [self.imageview removeFromSuperview];\n                     }];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiConPinchGesture.h",
    "content": "//\n//  DoraemonDemoMultiConPinchGesture.h\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/7/21.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoMultiConPinchGesture : DoraemonDemoBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiConPinchGesture.m",
    "content": "//\n//  DoraemonDemoMultiConPinchGesture.m\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/7/21.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoMultiConPinchGesture.h\"\n#import \"UIView+Doraemon.h\"\n#import \"UIImage+Doraemon.h\"\n@interface DoraemonDemoMultiConPinchGesture ()\n\n@property (nonatomic, strong)UIImageView *imageview;\n@property (nonatomic,strong) UIView * backgroundView;\n@property (nonatomic) BOOL isLargeView;\n@property (nonatomic) CGRect oldFrame;\n\n@end\n\n\n@implementation DoraemonDemoMultiConPinchGesture\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"长安事件\");\n    self.imageview.center = self.view.center;\n    [self.view addSubview:self.imageview];\n    [self.imageview setUserInteractionEnabled:YES];\n    UIPinchGestureRecognizer * pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)];\n    [self.imageview addGestureRecognizer:pinch];\n    self.isLargeView = NO;\n    self.oldFrame = self.imageview.frame;\n}\n\n\n- (UIImageView *)imageview {\n    if(!_imageview){\n        CGFloat size = 400;\n        _imageview = [[UIImageView alloc] initWithFrame:CGRectMake((self.view.doraemon_width - size) / 2.0, size, self.view.doraemon_width, size)];//        CGFloat size =\n        \n        _imageview.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_file_sync_banner\"];\n    }\n    return _imageview;\n}\n\n\n-(void)pinch:(UIPinchGestureRecognizer *)pinch{\n    if (pinch.state == UIGestureRecognizerStateRecognized) {\n        if (!self.isLargeView && pinch.velocity > 0) {\n            self.backgroundView = [[UIView alloc] initWithFrame:self.view.frame];\n            self.backgroundView.backgroundColor = [UIColor blackColor];\n            self.backgroundView.alpha = 0.0;\n            self.imageview.backgroundColor = [UIColor blueColor];\n            [self.view insertSubview:self.backgroundView belowSubview:self.imageview];\n            [UIView animateWithDuration:0.8\n                                  delay:0.0\n                                options:UIViewAnimationOptionCurveEaseInOut\n                             animations:^{\n                                 self.imageview.frame = CGRectMake(0,220,320,210);\n                             }\n                             completion:^(BOOL finished) {\n                                 self.isLargeView = YES;\n                                 \n                             }];\n        }\n        if (self.isLargeView &&  pinch.velocity < 0) {\n            [UIView animateWithDuration:0.8\n                             animations:^{\n                                 self.imageview.frame = self.oldFrame;\n                                 \n                             }\n                             completion:^(BOOL finished) {\n                                 [self.backgroundView removeFromSuperview];\n                                 self.backgroundView = nil;\n                                 self.isLargeView = NO;\n                             }];\n        }\n    }\n}\n\n\n\n\n\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiConRotationGesture.h",
    "content": "//\n//  DoraemonDemoMultiConRotationGesture.h\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/7/21.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoMultiConRotationGesture : DoraemonDemoBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiConRotationGesture.m",
    "content": "//\n//  DoraemonDemoMultiConRotationGesture.m\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/7/21.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoMultiConRotationGesture.h\"\n#import \"UIView+Doraemon.h\"\n#import \"UIImage+Doraemon.h\"\n@interface DoraemonDemoMultiConRotationGesture ()\n\n@property (nonatomic, strong)UIImageView *imageview;\n\n@end\n\n@implementation DoraemonDemoMultiConRotationGesture\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"旋转手势\");\n    [self.view addSubview:self.imageview];\n    self.imageview.center = self.view.center;\n    self.imageview.userInteractionEnabled = YES;\n    UIRotationGestureRecognizer * rotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotate:)];\n    [self.imageview addGestureRecognizer:rotate];\n}\n\n- (UIImageView *)imageview {\n    if(!_imageview){\n        CGFloat size = 240;\n        _imageview = [[UIImageView alloc] initWithFrame:CGRectMake((self.view.doraemon_width - size) / 2.0, size, self.view.doraemon_width, size)];//        CGFloat size =\n        \n        _imageview.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_file_sync_banner\"];\n    }\n    return _imageview;\n}\n\n-(void)rotate:(UIRotationGestureRecognizer *)sender{\n    if (sender.state == UIGestureRecognizerStateChanged) {\n        self.imageview.transform = CGAffineTransformMakeRotation(sender.rotation);\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiConScreenEdgePanGesture.h",
    "content": "//\n//  DoraemonDemoMultiConScreenEdgePanGesture.h\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/7/21.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoMultiConScreenEdgePanGesture : DoraemonDemoBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiConScreenEdgePanGesture.m",
    "content": "//\n//  DoraemonDemoMultiConScreenEdgePanGesture.m\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/7/21.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoMultiConScreenEdgePanGesture.h\"\n#import \"UIView+Doraemon.h\"\n#import \"UIImage+Doraemon.h\"\n@interface DoraemonDemoMultiConScreenEdgePanGesture ()\n@property (nonatomic,strong)UIView * edgeView;\n@property (nonatomic)CGPoint  offsetCenter;\n@end\n\n@implementation DoraemonDemoMultiConScreenEdgePanGesture\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"边缘手势\");\n    UIScreenEdgePanGestureRecognizer * edge = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(edgePan:)];\n    edge.edges = UIRectEdgeRight;\n    [self.view addGestureRecognizer:edge];\n}\n\n-(void)edgePan:(UIScreenEdgePanGestureRecognizer *)sender{\n    if (sender.state == UIGestureRecognizerStateBegan) {\n        self.edgeView = [[UIView alloc] initWithFrame:CGRectOffset(self.view.frame,CGRectGetWidth(self.view.frame),0)];\n        self.edgeView.backgroundColor = [UIColor blueColor];\n        self.offsetCenter = self.edgeView.center;\n        [self.view addSubview:self.edgeView];\n    }else if(sender.state == UIGestureRecognizerStateChanged){\n        CGPoint translation = [sender translationInView:self.view];\n        self.edgeView.center = CGPointMake(self.offsetCenter.x + translation.x,self.offsetCenter.y);\n    }else if(sender.state == UIGestureRecognizerStateEnded)\n    {\n        if ([sender velocityInView:self.view].x < 0) {\n            [UIView animateWithDuration:0.3 animations:^{\n                self.edgeView.center = self.view.center;\n            }];\n        }else{\n            [UIView animateWithDuration:0.3\n                                  delay:0.0\n                                options:UIViewAnimationOptionBeginFromCurrentState\n                             animations:^{\n                                 self.edgeView.center = self.offsetCenter;\n                             }\n                             completion:^(BOOL finished) {\n                                 [self.edgeView removeFromSuperview];\n                                 self.edgeView = nil;\n                             }];\n        }\n    }else{\n        [UIView animateWithDuration:0.3\n                              delay:0.0\n                            options:UIViewAnimationOptionBeginFromCurrentState\n                         animations:^{\n                             self.edgeView.center = self.offsetCenter;\n                         }\n                         completion:^(BOOL finished) {\n                             [self.edgeView removeFromSuperview];\n                             self.edgeView = nil;\n                         }];\n    }\n}\n\n\n//- (UIImageView *)imageview {\n//    if(!_imageview){\n//        CGFloat size = 240;\n//        _imageview = [[UIImageView alloc] initWithFrame:CGRectMake((self.view.doraemon_width - size) / 2.0, size, self.view.doraemon_width, size)];//        CGFloat size =\n//\n//        _imageview.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_file_sync_banner\"];\n//    }\n//    return _imageview;\n//}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiConSwipeGesture.h",
    "content": "//\n//  DoraemonDemoMultiConSwipeGesture.h\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/7/21.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoMultiConSwipeGesture : DoraemonDemoBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiConSwipeGesture.m",
    "content": "//\n//  DoraemonDemoMultiConSwipeGesture.m\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/7/21.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoMultiConSwipeGesture.h\"\n#import \"UIView+Doraemon.h\"\n#import \"UIImage+Doraemon.h\"\n#import \"UIColor+Doraemon.h\"\n@interface DoraemonDemoMultiConSwipeGesture ()\n@property (nonatomic, strong)UIImageView *image3;\n@property (nonatomic, strong)UIImageView *image2;\n@property (nonatomic, strong)UILabel *label;\n@end\n\n@implementation DoraemonDemoMultiConSwipeGesture\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"滑动手势\");\n    \n    UISwipeGestureRecognizer * swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];\n    swipe.numberOfTouchesRequired = 2;\n    [swipe setDirection:UISwipeGestureRecognizerDirectionLeft];\n    [self.view addGestureRecognizer:swipe];\n    [self.view addSubview:self.image2];\n    [self.view addSubview:self.image3];\n    [self.view addSubview:self.label];\n    self.label.text = @\"向左滑方向\";\n    self.label.center = self.view.center;\n    \n    self.image2.userInteractionEnabled = YES;\n    self.image2.hidden = NO;\n    self.image3.userInteractionEnabled = YES;\n    self.image3.hidden = YES;\n}\n\n\n-(void)swipe:(UISwipeGestureRecognizer *)sender{\n    \n    if (sender.state == UIGestureRecognizerStateRecognized) {\n        [UIView transitionWithView:self.view\n                          duration:0.8\n                           options:UIViewAnimationOptionTransitionCurlUp\n                        animations:^{\n                            self.image3.hidden = !self.image3.hidden;\n                            self.image2.hidden = !self.image2.hidden;\n                        } completion:^(BOOL finished) {\n                            \n                        }];\n        \n    }\n    \n}\n- (UILabel *)label {\n    if(!_label){\n        _label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 200, 40)];\n        _label.textColor = [UIColor doraemon_colorWithString:@\"#333333\"];\n        _label.font = [UIFont boldSystemFontOfSize:20];\n        _label.textAlignment = NSTextAlignmentCenter;\n    }\n    return _label;\n    \n}\n- (UIImageView *)image3 {\n    if(!_image3){\n        CGFloat size = 40;\n        _image3 = [[UIImageView alloc] initWithFrame:CGRectMake((self.view.doraemon_width - 3*size) / 2.0, size*3, size, size)];//        CGFloat size =\n        \n        _image3.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_file_sync\"];\n    }\n    return _image3;\n}\n\n- (UIImageView *)image2 {\n    if(!_image2){\n        CGFloat size = 40;\n        _image2 = [[UIImageView alloc] initWithFrame:CGRectMake((self.view.doraemon_width - 3*size) / 2.0, size*5, size, size)];//        CGFloat size =\n        \n        _image2.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_file_sync\"];\n    }\n    return _image2;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiConTapGesture.h",
    "content": "//\n//  DoraemonDemoMultiConTapGesture.h\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/7/21.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoMultiConTapGesture : DoraemonDemoBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiConTapGesture.m",
    "content": "//\n//  DoraemonDemoMultiConTapGesture.m\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/7/21.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoMultiConTapGesture.h\"\n#import \"UIView+Doraemon.h\"\n#import \"UIImage+Doraemon.h\"\n@interface DoraemonDemoMultiConTapGesture ()\n@property (nonatomic, strong)UIImageView *imageview;\n@end\n\n@implementation DoraemonDemoMultiConTapGesture\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"点击手势\");\n    [self.view addSubview:self.imageview];\n    self.imageview.userInteractionEnabled = YES;\n    \n    UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];\n    [self.imageview addGestureRecognizer:tap];\n}\n\n- (UIImageView *)imageview {\n    if(!_imageview){\n        CGFloat size = 240;\n        _imageview = [[UIImageView alloc] initWithFrame:CGRectMake((self.view.doraemon_width - size) / 2.0, size, self.view.doraemon_width, size)];//        CGFloat size =\n        \n        _imageview.image = [UIImage doraemon_xcassetImageNamed:@\"doraemon_file_sync_banner\"];\n    }\n    return _imageview;\n}\n\n\n- (void)tap:(UITapGestureRecognizer *)sender {\n    CAKeyframeAnimation * animation = [CAKeyframeAnimation animation];\n    animation.keyPath = @\"position.x\";\n    NSInteger initalPositionX = self.imageview.layer.position.x;\n    animation.values = @[@(initalPositionX),\n                         @(initalPositionX + 10),\n                         @(initalPositionX - 10),\n                         @(initalPositionX + 10),\n                         @(initalPositionX)];\n    animation.keyTimes = @[\n                           @(0),\n                           @(1/6.0),\n                           @(3/6.0),\n                           @(5/6.0),\n                           @(1)];\n    animation.removedOnCompletion = YES;\n    [self.imageview.layer addAnimation:animation forKey:@\"keyFrame\"];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiControlViewController.h",
    "content": "//\n//  DoraemonDemoMultiControlViewController.h\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/7/14.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoMultiControlViewController : DoraemonDemoBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiControlViewController.m",
    "content": "//\n//  DoraemonDemoMultiControlViewController.m\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/7/14.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoMultiControlViewController.h\"\n#import <DoraemonKit/UIView+Doraemon.h>\n#import \"DoraemonDefine.h\"\n#import \"DoraemonHealthAlertView.h\"\n#import \"DoraemonDemoMultiConLongPressGesture.h\"\n#import \"DoraemonDemoMultiConPinchGesture.h\"\n#import \"DoraemonDemoMultiConRotationGesture.h\"\n#import \"DoraemonDemoMultiConSwipeGesture.h\"\n#import \"DoraemonDemoMultiConTapGesture.h\"\n#import \"DoraemonDemoMultiConScreenEdgePanGesture.h\"\n#import \"DoraemonMCCommandExcutor.h\"\n#import \"DoraemonMCMessagePackager.h\"\n#import \"DoraemonDemoMultiSlideView.h\"\n@interface DoraemonMCEventHandler1: DoraemonMCEventHandler\n\n\n\n@end\n\n@implementation DoraemonMCEventHandler1\n\n- (BOOL)handleEvent:(DoraemonMCMessage*)eventInfo {\n    \n    self.messageInfo = eventInfo;\n    self.targetView = [self fetchTargetView];\n    NSString *message =  [NSString stringWithFormat:@\"%@,%@\",self.targetView, [self.messageInfo.eventInfo objectForKey:@\"eventInfo\"]];\n    UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@\"自定义手势\" message:message\n       delegate:nil cancelButtonTitle:@\"好的\" otherButtonTitles: nil];\n    [alert show];\n    \n    return YES;\n}\n\n@end\n\n\n\n@interface DoraemonMCEventHandler2: DoraemonMCEventHandler\n\n\n\n@end\n\n@implementation DoraemonMCEventHandler2\n\n- (BOOL)handleEvent:(DoraemonMCMessage*)eventInfo {\n    \n    self.messageInfo = eventInfo;\n    self.targetView = [self fetchTargetView];\n    NSString *message =  [NSString stringWithFormat:@\"%@,%@\",self.targetView, [self.messageInfo.eventInfo objectForKey:@\"eventInfo\"]];\n    UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@\"自定义手势\" message:message\n       delegate:nil cancelButtonTitle:@\"好的\" otherButtonTitles: nil];\n    [alert show];\n    \n    return YES;\n}\n\n@end\n\n\n@interface DoraemonDemoMultiControlViewController ()\n@property (nonatomic,strong) UIScrollView * superScrollView;\n@end\n\n@implementation DoraemonDemoMultiControlViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"一机多控Demo\");\n    \n    self.superScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];\n    self.superScrollView.contentSize = CGSizeMake(self.view.doraemon_width, self.view.doraemon_height*1.3);\n    [self.view addSubview:self.superScrollView];\n    \n    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, self.view.doraemon_width, 60)];\n    btn.backgroundColor = [UIColor orangeColor];\n    [btn setTitle:DoraemonDemoLocalizedString(@\"UIControl 点击事件 系统 AlertView\") forState:UIControlStateNormal];\n    [btn addTarget:self action:@selector(controlEvent) forControlEvents:UIControlEventTouchUpInside];\n    [self.superScrollView addSubview:btn];\n    \n    \n\n    \n    UIButton *btn_2 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn_2.backgroundColor = [UIColor orangeColor];\n    [btn_2 setTitle:DoraemonDemoLocalizedString(@\"UIControl 点击事件 系统 UIAlertController\") forState:UIControlStateNormal];\n    [btn_2 addTarget:self action:@selector(controlEvent2) forControlEvents:UIControlEventTouchUpInside];\n    [self.superScrollView addSubview:btn_2];\n    \n    \n    UIButton *btn_3 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn_2.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn_3.backgroundColor = [UIColor orangeColor];\n    [btn_3 setTitle:DoraemonDemoLocalizedString(@\"UIControl 点击事件 自定义Alertview\") forState:UIControlStateNormal];\n    [btn_3 addTarget:self action:@selector(controlEvent3) forControlEvents:UIControlEventTouchUpInside];\n    [self.superScrollView addSubview:btn_3];\n\n    // 自定义事件\n    DoraemonMCEventHandler1 *customHandler1 = [DoraemonMCEventHandler1 new];\n    [DoraemonMCCommandExcutor addCustomMessage:@\"customType1\" eventHandlerName:customHandler1];\n    \n    \n    DoraemonMCEventHandler2 *customHandler2 = [DoraemonMCEventHandler2 new];\n    [DoraemonMCCommandExcutor addCustomMessage:@\"customType2\" eventHandlerName:customHandler2];\n    \n    \n    DoraemonDemoMultiSlideView  *slideView  = [[DoraemonDemoMultiSlideView alloc]initWithFrame:CGRectMake(30, btn_3.doraemon_bottom +20, btn_3.doraemon_width-60, 60)];\n    [self.superScrollView addSubview:slideView];\n    \n\n    //输入文本\n    UITextField *field_1 = [[UITextField alloc] initWithFrame:CGRectMake(60, slideView.doraemon_bottom+20, self.view.doraemon_width-120, 60)];\n    field_1.placeholder = @\"输入文案测试\";\n    field_1.clearButtonMode = UITextFieldViewModeAlways;\n    field_1.backgroundColor = [UIColor whiteColor];\n    [self.superScrollView addSubview:field_1];\n    \n    \n    //输入 UITextView 文本\n    UITextView  *textView_1 = [[UITextView alloc]initWithFrame:CGRectMake(60, field_1.doraemon_bottom+20, self.view.doraemon_width-120, 60)];\n    textView_1.layer.borderWidth = 0.5;\n    textView_1.layer.borderColor = [[UIColor lightGrayColor] CGColor];\n    textView_1.backgroundColor = [UIColor whiteColor];\n    [self.superScrollView addSubview:textView_1];\n    \n    //输入 UITextView 文本\n    UITextView  *textView_2 = [[UITextView alloc]initWithFrame:CGRectMake(60, textView_1.doraemon_bottom+20, self.view.doraemon_width-120, 60)];\n    textView_2.layer.borderWidth = 0.5;\n    textView_2.layer.borderColor = [[UIColor lightGrayColor] CGColor];\n    textView_2.backgroundColor = [UIColor whiteColor];\n    [self.superScrollView addSubview:textView_2];\n    \n    //长按\n    UIButton *longPress = [[UIButton alloc] initWithFrame:CGRectMake(0, textView_2.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    longPress.backgroundColor = [UIColor orangeColor];\n    [longPress setTitle:DoraemonDemoLocalizedString(@\"长安点击事件\") forState:UIControlStateNormal];\n    [longPress addTarget:self action:@selector(longPressEvent) forControlEvents:UIControlEventTouchUpInside];\n    [self.superScrollView addSubview:longPress];\n    \n    //捏合 UIPinchGestureRecognizer\n    UIButton *pinch = [[UIButton alloc] initWithFrame:CGRectMake(0, longPress.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    pinch.backgroundColor = [UIColor orangeColor];\n    [pinch setTitle:DoraemonDemoLocalizedString(@\"拟合事件\") forState:UIControlStateNormal];\n    [pinch addTarget:self action:@selector(pinchEvent) forControlEvents:UIControlEventTouchUpInside];\n    [self.superScrollView addSubview:pinch];\n    \n    \n    //旋转事件\n    UIButton *rotation = [[UIButton alloc] initWithFrame:CGRectMake(0, pinch.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    rotation.backgroundColor = [UIColor orangeColor];\n    [rotation setTitle:DoraemonDemoLocalizedString(@\"旋转事件\") forState:UIControlStateNormal];\n    [rotation addTarget:self action:@selector(rotationEvent) forControlEvents:UIControlEventTouchUpInside];\n    [self.superScrollView addSubview:rotation];\n    \n    \n    // 单击  UITapGestureRecognizer\n    UIButton *tap = [[UIButton alloc] initWithFrame:CGRectMake(0, rotation.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    tap.backgroundColor = [UIColor orangeColor];\n    [tap setTitle:DoraemonDemoLocalizedString(@\"单击事件\") forState:UIControlStateNormal];\n    [tap addTarget:self action:@selector(tapEvent) forControlEvents:UIControlEventTouchUpInside];\n    [self.superScrollView addSubview:tap];\n    \n    //滑动手势 UISwipeGestureRecognizer\n    UIButton *swipe = [[UIButton alloc] initWithFrame:CGRectMake(0, tap.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    swipe.backgroundColor = [UIColor orangeColor];\n    [swipe setTitle:DoraemonDemoLocalizedString(@\"滑动手势事件\") forState:UIControlStateNormal];\n    [swipe addTarget:self action:@selector(swipeEvent) forControlEvents:UIControlEventTouchUpInside];\n    [self.superScrollView addSubview:swipe];\n    \n    //边缘手势  UIScreenEdgePanGestureRecognizer\n    UIButton *screenEdgePan = [[UIButton alloc] initWithFrame:CGRectMake(0, swipe.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    screenEdgePan.backgroundColor = [UIColor orangeColor];\n    [screenEdgePan setTitle:DoraemonDemoLocalizedString(@\"边缘手势事件\") forState:UIControlStateNormal];\n    [screenEdgePan addTarget:self action:@selector(screenEdgePanEvent) forControlEvents:UIControlEventTouchUpInside];\n    [self.superScrollView addSubview:screenEdgePan];\n    \n\n    \n    \n    \n}\n\n- (void)controlEvent{\n     UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@\"点击事件测试\" message:@\"我是普通警告框\"\n        delegate:nil cancelButtonTitle:@\"好的\" otherButtonTitles: nil];\n     [alert show];\n\n}\n\n- (void)controlEvent2{\n    [self showAlertMessage];\n}\n\n- (void)controlEvent3{\n    DoraemonHealthAlertView *alertView = [[DoraemonHealthAlertView alloc] init];\n    [alertView renderUI:DoraemonLocalizedString(@\"UIControl 点击事件 自定义Alertview\") placeholder:@[] inputTip:@[DoraemonLocalizedString(@\"测试用例名称\"),DoraemonLocalizedString(@\"测试人名称\")] ok:DoraemonLocalizedString(@\"提交\") quit:DoraemonLocalizedString(@\"丢弃\") cancle:DoraemonLocalizedString(@\"取消\") okBlock:^{\n\n\n    } quitBlock:^{\n\n    } cancleBlock:^{\n\n    }];\n\n    [self.view addSubview:alertView];\n}\n\n- (void)showAlertMessage{\n    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@\"提示\" message:[NSString stringWithFormat:@\"当前设备为模拟器，不支持扫一扫功能\"] preferredStyle:UIAlertControllerStyleAlert];\n    UIAlertAction *sureAction = [UIAlertAction actionWithTitle:@\"知道了\" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {\n    \n    }];\n    [alertController addAction:sureAction];\n    \n    [self.navigationController presentViewController:alertController animated:YES completion:nil];\n}\n\n\n- (void)longPressEvent{\n    DoraemonDemoMultiConLongPressGesture *longPress = [[DoraemonDemoMultiConLongPressGesture alloc]init];\n    [self.navigationController pushViewController:longPress animated:YES];\n}\n\n- (void)pinchEvent {\n    DoraemonDemoMultiConPinchGesture *pinch = [[DoraemonDemoMultiConPinchGesture alloc]init];\n    [self.navigationController pushViewController:pinch animated:YES];\n}\n- (void)rotationEvent {\n    DoraemonDemoMultiConRotationGesture *rotation = [[DoraemonDemoMultiConRotationGesture alloc]init];\n    [self.navigationController pushViewController:rotation animated:YES];\n}\n\n-(void)tapEvent {\n    DoraemonDemoMultiConTapGesture *tap = [[DoraemonDemoMultiConTapGesture alloc]init];\n    [self.navigationController pushViewController:tap animated:YES];\n    \n}\n\n-(void)swipeEvent {\n    DoraemonDemoMultiConSwipeGesture *swipe = [[DoraemonDemoMultiConSwipeGesture alloc]init];\n    [self.navigationController pushViewController:swipe animated:YES];\n}\n\n-(void)screenEdgePanEvent {\n    DoraemonDemoMultiConScreenEdgePanGesture *screenEdgePan = [[DoraemonDemoMultiConScreenEdgePanGesture alloc]init];\n    [self.navigationController pushViewController:screenEdgePan animated:YES];\n}\n@end\n\n\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiSlideView.h",
    "content": "//\n//  DoraemonDemoMultiSlideView.h\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/8/24.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoMultiSlideView : UIView\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/MultiControl/DoraemonDemoMultiSlideView.m",
    "content": "//\n//  DoraemonDemoMultiSlideView.m\n//  DoraemonKitDemo\n//\n//  Created by wzp on 2021/8/24.\n//  Copyright © 2021 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoMultiSlideView.h\"\n#import <Masonry/Masonry.h>\n#import \"DoraemonMCCommandGenerator.h\"\ntypedef void(^CommonActionBlock)(id);\n@interface DoraemonDemoMultiSlideView ()<UIGestureRecognizerDelegate>\n\n@property (nonatomic, strong) UIView *bgView;\n@property (nonatomic, strong) UIView *slideView;\n@property (nonatomic, strong) UIButton *slideButton;\n@property (nonatomic, assign) CGFloat fMaxSlideValue;\n@property (nonatomic, strong) UILabel *lbLockName;\n@property (nonatomic, assign) int nUnlockScale;\n@property (nonatomic, strong) CommonActionBlock unLockBlock;\n@end\n\n@implementation DoraemonDemoMultiSlideView\n\n- (instancetype)initWithFrame:(CGRect)frame\n{\n    self = [super initWithFrame:frame];\n    if(self){\n        _nUnlockScale = 70;    // 默认百分之七十\n        \n        _bgView = [UIView new];\n        [_bgView setBackgroundColor:[UIColor redColor]];\n        _bgView.layer.cornerRadius = 12;\n        _bgView.clipsToBounds = YES;\n        [self addSubview:_bgView];\n        [_bgView mas_makeConstraints:^(MASConstraintMaker *make) {\n            make.edges.equalTo(self);\n        }];\n        //\n        _slideView = [UIView new];\n        [_slideView setBackgroundColor:[UIColor grayColor]];\n        _slideView.layer.cornerRadius = 12;\n        [_bgView addSubview:_slideView];\n        [_slideView mas_makeConstraints:^(MASConstraintMaker *make) {\n            make.edges.equalTo(self.bgView);\n        }];\n        //\n        _slideButton = [UIButton buttonWithType:UIButtonTypeCustom];\n        [_slideButton setBackgroundColor:[UIColor orangeColor]];\n        [_slideView addSubview:_slideButton];\n        [_slideButton mas_makeConstraints:^(MASConstraintMaker *make) {\n            make.leading.top.bottom.equalTo(self.slideView);\n            make.width.equalTo(@(50));\n        }];\n\n        UIFont *font = [UIFont systemFontOfSize:24];\n        _lbLockName = [UILabel new];\n        [_lbLockName setBackgroundColor:[UIColor grayColor]];\n        [_lbLockName setText:@\"自定义事件\"];\n        [_lbLockName setTextColor:[UIColor whiteColor]];\n        [_lbLockName setTextAlignment:NSTextAlignmentCenter];\n        [_lbLockName setFont:font];\n        [_slideView addSubview:_lbLockName];\n        [_lbLockName mas_makeConstraints:^(MASConstraintMaker *make) {\n            make.leading.equalTo(self.slideView).offset(118);\n            make.trailing.equalTo(self.slideView).offset(-80);\n            make.height.equalTo(@(80));\n            make.center.equalTo(self.slideView);\n        }];\n        \n        UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(performPanGestureRecognizer:)];\n        panGestureRecognizer.delegate = self;\n        [_slideButton addGestureRecognizer:panGestureRecognizer];\n    }\n    \n    return self;\n}\n\n\n- (void)setNUnlockScale:(int)nUnlockScale\n{\n    if(nUnlockScale < 0 ){\n        nUnlockScale = 0;\n    }else if(nUnlockScale > 100){\n        nUnlockScale = 100;\n    }\n    _nUnlockScale = nUnlockScale;\n}\n\n\n\n- (void)performPanGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer\n{\n    _fMaxSlideValue = _bgView.frame.size.width - _slideButton.frame.size.width;\n    CGPoint translation = [panGestureRecognizer translationInView:_bgView];\n    if(panGestureRecognizer.state == UIGestureRecognizerStateBegan){\n        _lbLockName.hidden = YES;\n    }else if(panGestureRecognizer.state == UIGestureRecognizerStateChanged){\n        if (translation.x == 1) {\n            [DoraemonMCCommandGenerator sendCustomMessageWithView:self eventInfo:@{@\"eventInfo\":@\"customType1\"} messageType:@\"customType1\"];\n        }\n        if(translation.x > 0){\n            _lbLockName.hidden = YES;\n            CGFloat offset = translation.x;\n            if(offset > _fMaxSlideValue){\n                offset = _fMaxSlideValue;\n            }\n            [_slideView mas_remakeConstraints:^(MASConstraintMaker *make) {\n                make.leading.equalTo(self.bgView).offset(offset);\n                make.top.bottom.trailing.equalTo(self.bgView);\n            }];\n        }else{\n            \n        }\n    }else if(panGestureRecognizer.state == UIGestureRecognizerStateEnded || panGestureRecognizer.state == UIGestureRecognizerStateCancelled){\n        if(panGestureRecognizer.state == UIGestureRecognizerStateEnded) {\n            [DoraemonMCCommandGenerator sendCustomMessageWithView:self eventInfo:@{@\"eventInfo\":@\"customType2\"} messageType:@\"customType2\"];\n        }\n        if(translation.x > _fMaxSlideValue * self.nUnlockScale/100.0){\n            [_slideView mas_remakeConstraints:^(MASConstraintMaker *make) {\n                make.top.bottom.trailing.equalTo(self.bgView);\n                make.leading.equalTo(self.bgView).offset(self.fMaxSlideValue);\n            }];\n            _lbLockName.hidden = YES;\n            if(self.unLockBlock){\n                self.unLockBlock(@(YES));\n            }\n        }else{\n            [_slideView mas_remakeConstraints:^(MASConstraintMaker *make) {\n                make.edges.equalTo(self.bgView);\n            }];\n            _lbLockName.hidden = NO;\n        }\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/ NSURLProtocol/DoraemonDemoURLProtocol1.h",
    "content": "//\n//  DoraemonDemoURLProtocol1.h\n//  DoraemonKitDemo\n//\n//  Created by didi on 2020/3/19.\n//  Copyright © 2020 yixiang. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoURLProtocol1 : NSURLProtocol\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/ NSURLProtocol/DoraemonDemoURLProtocol1.m",
    "content": "//\n//  DoraemonDemoURLProtocol1.m\n//  DoraemonKitDemo\n//\n//  Created by didi on 2020/3/19.\n//  Copyright © 2020 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoURLProtocol1.h\"\n#import \"DoraemonUrlUtil.h\"\n#import \"DoraemonNetFlowManager.h\"\n\nstatic NSString * const kDoraemonDemoUrlProtocolKey = @\"doraemon_demo_url_protocol_1_key\";\n\n@interface DoraemonDemoURLProtocol1()\n\n@property (nonatomic, strong) NSURLConnection *connection;\n\n@end\n\n@implementation DoraemonDemoURLProtocol1\n\n+ (BOOL)canInitWithTask:(NSURLSessionTask *)task {\n    NSURLRequest *request = task.currentRequest;\n    NSLog(@\"11111 == canInitWithTask\");\n    return request == nil ? NO : [self canInitWithRequest:request];\n}\n\n+ (BOOL)canInitWithRequest:(NSURLRequest *)request{\n    if ([NSURLProtocol propertyForKey:kDoraemonDemoUrlProtocolKey inRequest:request]) {\n        return NO;\n    }\n    NSLog(@\"11111 == canInitWithRequest\");\n    return YES;\n}\n\n+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request{\n    NSMutableURLRequest *mutableReqeust = [request mutableCopy];\n    [NSURLProtocol setProperty:@YES forKey:kDoraemonDemoUrlProtocolKey inRequest:mutableReqeust];\n    NSLog(@\"11111 == canonicalRequestForRequest\");\n    return [mutableReqeust copy];\n}\n\n- (void)startLoading{\n    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];\n    NSLog(@\"11111 == startLoading\");\n    self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];\n}\n\n- (void)stopLoading{\n    NSLog(@\"11111 == stopLoading\");\n    [[DoraemonNetFlowManager shareInstance] httpBodyFromRequest:self.request bodyCallBack:^(NSData *httpBody) {\n        NSString* requestBody = [DoraemonUrlUtil convertJsonFromData:httpBody];\n        NSLog(@\"11111 == requestBody = %@\",requestBody);\n        [self.connection cancel];\n    }];\n}\n\n\n\n#pragma mark - NSURLSessionDelegate\n- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {\n    NSLog(@\"11111 == didReceiveResponse\");\n    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];\n}\n\n- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {\n    NSLog(@\"11111 == didReceiveData\");\n    [self.client URLProtocol:self didLoadData:data];\n}\n\n- (void) connectionDidFinishLoading:(NSURLConnection *)connection {\n    NSLog(@\"11111 == connectionDidFinishLoading\");\n    [self.client URLProtocolDidFinishLoading:self];\n}\n\n- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {\n    NSLog(@\"11111 == didFailWithError\");\n    [self.client URLProtocol:self didFailWithError:error];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/ NSURLProtocol/DoraemonDemoURLProtocol2.h",
    "content": "//\n//  DoraemonDemoURLProtocol2.h\n//  DoraemonKitDemo\n//\n//  Created by didi on 2020/3/19.\n//  Copyright © 2020 yixiang. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoURLProtocol2 : NSURLProtocol\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/ NSURLProtocol/DoraemonDemoURLProtocol2.m",
    "content": "//\n//  DoraemonDemoURLProtocol2.m\n//  DoraemonKitDemo\n//\n//  Created by didi on 2020/3/19.\n//  Copyright © 2020 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoURLProtocol2.h\"\n#import \"DoraemonUrlUtil.h\"\n#import \"DoraemonNetFlowManager.h\"\n\nstatic NSString * const kDoraemonDemoUrlProtocol2Key = @\"doraemon_demo_url_protocol_2_key\";\n\n@interface DoraemonDemoURLProtocol2()\n\n@property (nonatomic, strong) NSURLConnection *connection;\n\n@end\n\n@implementation DoraemonDemoURLProtocol2\n\n+ (BOOL)canInitWithTask:(NSURLSessionTask *)task {\n    NSURLRequest *request = task.currentRequest;\n    NSLog(@\"22222 == canInitWithTask\");\n    return request == nil ? NO : [self canInitWithRequest:request];\n}\n\n+ (BOOL)canInitWithRequest:(NSURLRequest *)request{\n    if ([NSURLProtocol propertyForKey:kDoraemonDemoUrlProtocol2Key inRequest:request]) {\n        return NO;\n    }\n    NSLog(@\"22222 == canInitWithRequest\");\n    return YES;\n}\n\n+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request{\n    NSMutableURLRequest *mutableReqeust = [request mutableCopy];\n    [NSURLProtocol setProperty:@YES forKey:kDoraemonDemoUrlProtocol2Key inRequest:mutableReqeust];\n    NSLog(@\"22222 == canonicalRequestForRequest\");\n    return [mutableReqeust copy];\n}\n\n- (void)startLoading{\n    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];\n    NSLog(@\"22222 == startLoading\");\n    self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];\n}\n\n- (void)stopLoading{\n    NSLog(@\"22222 == stopLoading\");\n    [[DoraemonNetFlowManager shareInstance] httpBodyFromRequest:self.request bodyCallBack:^(NSData *httpBody) {\n        NSString* requestBody = [DoraemonUrlUtil convertJsonFromData:httpBody];\n        NSLog(@\"22222 == requestBody = %@\",requestBody);\n        [self.connection cancel];\n    }];\n}\n\n\n\n#pragma mark - NSURLSessionDelegate\n- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {\n    NSLog(@\"22222 == didReceiveResponse\");\n    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];\n}\n\n- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {\n    NSLog(@\"22222 == didReceiveData\");\n    [self.client URLProtocol:self didLoadData:data];\n}\n\n- (void) connectionDidFinishLoading:(NSURLConnection *)connection {\n    NSLog(@\"22222 == connectionDidFinishLoading\");\n    [self.client URLProtocolDidFinishLoading:self];\n}\n\n- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {\n    NSLog(@\"22222 == didFailWithError\");\n    [self.client URLProtocol:self didFailWithError:error];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/Cell/DoraemonDemoNetTableViewCell.h",
    "content": "//\n//  DoraemonDemoNetTableViewCell.h\n//  DoraemonKitDemo\n//\n//  Created by didi on 2019/12/10.\n//  Copyright © 2019 yixiang. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoNetTableViewCell : UITableViewCell\n\n- (void)renderUIWithTitle:(NSString *)title;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/Cell/DoraemonDemoNetTableViewCell.m",
    "content": "//\n//  DoraemonDemoNetTableViewCell.m\n//  DoraemonKitDemo\n//\n//  Created by didi on 2019/12/10.\n//  Copyright © 2019 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoNetTableViewCell.h\"\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonDemoNetTableViewCell()\n\n@property (nonatomic, strong) UIButton *button;\n\n@end\n\n@implementation DoraemonDemoNetTableViewCell\n\n- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier{\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    if(self){\n        _button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, DoraemonScreenWidth, kDoraemonSizeFrom750_Landscape(104))];\n        _button.backgroundColor = [UIColor orangeColor];\n        _button.userInteractionEnabled = NO;\n        self.selectionStyle = UITableViewCellSelectionStyleNone;\n        [self addSubview:_button];\n    }\n    return self;\n}\n\n- (void)renderUIWithTitle:(NSString *)title{\n    if(!title)\n        title = @\"Button\";\n    [_button setTitle:title forState:UIControlStateNormal];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/DoraemonDemoNetViewController.h",
    "content": "//\n//  DoraemonDemoNetViewController.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/5/18.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\n@interface DoraemonDemoNetViewController : DoraemonDemoBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/DoraemonDemoNetViewController.m",
    "content": "//\n//  DoraemonDemoNetViewController.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/5/18.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoNetViewController.h\"\n#import \"UIView+Doraemon.h\"\n#import <AFNetworking/AFNetworking.h>\n#import <DoraemonKit/DoraemonNSURLProtocol.h>\n#import \"DoraemonDefine.h\"\n#import \"DoraemonUIWebViewViewController.h\"\n#import \"DoraemonWKWebViewViewController.h\"\n#import \"DoraemonDemoImageViewController.h\"\n#import <DoraemonKit/DoraemonNetworkUtil.h>\n#import \"DoraemonDemoNetTableViewCell.h\"\n#import \"DoraemonDemoURLProtocol1.h\"\n#import \"DoraemonDemoURLProtocol2.h\"\n\n@interface DoraemonDemoNetViewController ()<NSURLConnectionDataDelegate,NSURLSessionDelegate,UITableViewDataSource,UITableViewDelegate>\n\n\n@property (nonatomic, strong) UITableView *tableView;\n@property (nonatomic, strong) NSArray *cellTitleArray;\n@property (nonatomic, strong) NSString *DoraemonDemoNetViewCellID;\n\n/** 可变的二进制数据 */\n@property (nonatomic, strong) NSMutableData *fileData;\n\n@end\n\n@implementation DoraemonDemoNetViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"网络测试Demo\");\n    \n    [self initCellTitleArray];\n    _DoraemonDemoNetViewCellID = @\"DoraemonDemoNetViewCell\";\n    [self.view addSubview:self.tableView];\n    \n    //[NSURLProtocol registerClass:[DoraemonDemoURLProtocol1 class]];\n    //[NSURLProtocol registerClass:[DoraemonDemoURLProtocol2 class]];\n}\n\n- (void)initCellTitleArray{\n    _cellTitleArray = @[\n        @\"发送一条URLConnection请求\",\n        @\"发送一条NSURLSession请求\",\n        @\"发送一条AFNetworking请求\",\n        @\"发送一条AFNetworking请求2\",\n        @\"打开UIWebView\",\n        @\"打开WKWebView\",\n        @\"图片测试\",\n        @\"Mock测试\",\n        @\"Mock测试2\"\n    ];\n}\n\n- (UITableView *)tableView{\n    if(!_tableView){\n        _tableView = [[UITableView alloc] initWithFrame:self.view.bounds];\n        _tableView.backgroundColor = [UIColor whiteColor];\n        _tableView.delegate = self;\n        _tableView.dataSource = self;\n        [_tableView registerClass:[DoraemonDemoNetTableViewCell class] forCellReuseIdentifier:_DoraemonDemoNetViewCellID];\n        _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;\n    }\n    return _tableView;\n}\n\n- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{\n    return _cellTitleArray.count;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{\n    return 1;\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{\n    return kDoraemonSizeFrom750_Landscape(104);\n}\n\n- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{\n    return kDoraemonSizeFrom750_Landscape(24);\n}\n\n- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{\n    UIView* footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.doraemon_width,kDoraemonSizeFrom750_Landscape(24))];\n    footerView.backgroundColor = [UIColor clearColor];\n    return footerView;\n}\n\n- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{\n    \n    DoraemonDemoNetTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_DoraemonDemoNetViewCellID];\n    if (!cell) {\n        cell = [[DoraemonDemoNetTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:_DoraemonDemoNetViewCellID];\n    }\n    [cell renderUIWithTitle:DoraemonDemoLocalizedString(_cellTitleArray[indexPath.section])];\n    return cell;\n}\n\n- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{\n    [self cellSelected:indexPath.section];\n}\n\n- (void)cellSelected:(NSInteger)row{\n    switch (row) {\n        case 0:\n            [self netForURLConnection];\n            break;\n        case 1:\n            [self netForNSURLSession];\n            break;\n        case 2:\n            [self netForAFNetworking];\n            break;\n        case 3:\n            [self netForAFNetworking2];\n            break;\n        case 4:\n            [self openUIWebView];\n            break;\n        case 5:\n            [self openWKWebView];\n            break;\n        case 6:\n            [self imageTest];\n            break;\n        case 7:\n            [self mockTest];\n            break;\n        case 8:\n            [self mockTest2];\n            break;\n        \n        default:\n            break;\n    }\n}\n\n\n- (void)openUIWebView{\n    UIViewController *vc = [[DoraemonUIWebViewViewController alloc] init];\n    [self.navigationController pushViewController:vc animated:YES];\n}\n\n- (void)openWKWebView{\n    UIViewController *vc = [[DoraemonWKWebViewViewController alloc] init];\n    [self.navigationController pushViewController:vc animated:YES];\n}\n\n- (void)imageTest{\n    DoraemonDemoImageViewController *vc = [[DoraemonDemoImageViewController alloc] init];\n    [self.navigationController pushViewController:vc animated:YES];\n}\n\n- (void)netForURLConnection{\n    //发送一条异步请求，block方式\n    NSURL *url = [NSURL URLWithString:@\"https://www.taobao.com/\"];\n    NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:url];\n    [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init]completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {\n        if ((data != nil) && (connectionError == nil)) {\n            //            NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];\n            NSLog(@\"response == %@\",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);\n        }\n        \n    }];\n    \n    //发送一条异步请求，delegate方式\n    //NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];\n    //[connect cancel]; 取消\n    //[connect start];\n}\n\n- (void)netForNSURLSession{\n    //发送一条异步请求，block方式\n    NSURLSession *session = [NSURLSession sharedSession];\n    NSURL *url = [NSURL URLWithString:@\"https://www.taobao.com/\"];\n    // 通过URL初始化task,在block内部可以直接对返回的数据进行处理\n    NSURLSessionTask *task = [session dataTaskWithURL:url\n                                    completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {\n                                        if (error) {\n                                            NSLog(@\"error == %@\",error);\n                                            return ;\n                                        }\n                                        //                                        NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];\n                                        NSLog(@\"%@\", data);\n                                    }];\n    \n    // 启动任务\n    [task resume];\n    \n    //发送一条异步请求，delegate方式\n    // 使用代理方法需要设置代理,但是session的delegate属性是只读的,要想设置代理只能通过这种方式创建session\n    //NSURLSession *session1 = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]\n//                                                           delegate:self\n//                                                      delegateQueue:[[NSOperationQueue alloc] init]];\n    \n    // 创建任务(因为要使用代理方法,就不需要block方式的初始化了)\n    //NSURLSessionDataTask *task1 = [session1 dataTaskWithRequest:[NSURLRequest requestWithURL:url]];\n    \n    // 启动任务\n    //[task1 resume];\n}\n\n- (void)netForAFNetworking{\n    //NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];\n    //configuration.protocolClasses = @[[DoraemonNSURLProtocol class]];\n    //AFHTTPSessionManager *session = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];\n    \n    AFHTTPSessionManager *session = [AFHTTPSessionManager manager];\n    \n    //    NSMutableArray *protocolsArray = [NSMutableArray arrayWithArray:session.session.configuration.protocolClasses];\n    //    [protocolsArray insertObject:[DoraemonNSURLProtocol class] atIndex:0];\n    //    session.session.configuration.protocolClasses = [protocolsArray copy];\n    //    session.session.configuration.protocolClasses = @[[DoraemonNSURLProtocol class]];\n    \n    session.requestSerializer = [AFHTTPRequestSerializer serializer];// 请求\n    session.responseSerializer = [AFHTTPResponseSerializer serializer];// 响应\n    session.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@\"application/json\",@\"text/json\",@\"text/javascript\",@\"text/html\", nil];\n    \n    [session GET:@\"https://www.taobao.com/\" parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {\n        NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];\n        NSLog(@\"success %@\",string);\n    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {\n        NSLog(@\"failure\");\n    }];\n    \n    //    [session POST:@\"http://172.23.160.242:8080/YxReactServerDemo/message/getAllMessage\" parameters:@{@\"name\":@\"yixiang1\"} success:^(NSURLSessionDataTask *task, id responseObject) {\n    //        NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];\n    //        NSLog(@\"请求成功 %@\",string);\n    //    } failure:^(NSURLSessionDataTask *task, NSError *error) {\n    //        NSLog(@\"请求失败\");\n    //    }];\n}\n\n- (void)netForAFNetworking2{\n    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];\n    manager.requestSerializer = [AFHTTPRequestSerializer serializer];// 请求\n    manager.responseSerializer = [AFHTTPResponseSerializer serializer];// 响应\n    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@\"application/json\",@\"text/json\",@\"text/javascript\",@\"text/html\", nil];\n    //    [manager POST:@\"http://172.23.160.242:8080/YxReactServerDemo/message/getAllMessage\" parameters:@{@\"name\":@\"yixiang2\"} success:^(AFHTTPRequestOperation *operation, id responseObject) {\n    //        NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];\n    //        NSLog(@\"请求成功 %@\",string);\n    //    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {\n    //        NSLog(@\"请求失败\");\n    //    }];\n    \n    [manager POST:@\"https://www.taobao.com/\" parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {\n        NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];\n        NSLog(@\"success %@\",string);\n    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {\n        NSLog(@\"failure\");\n    }];\n}\n\n#pragma mark -- NSURLConnectionDataDelegate\n//1.当接受到服务器响应的时候会调用:response(响应头)\n-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{\n    NSLog(@\"receive response\");\n}\n\n//2.当接受到服务器返回数据的时候调用(会调用多次)\n-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{\n    //\n    NSLog(@\"receive data\");\n    //拼接数据\n    [self.fileData appendData:data];\n}\n\n//3.当请求失败的时候调用\n-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{\n    NSLog(@\"fail with error\");\n}\n\n//4.当请求结束(成功|失败)的时候调用\n-(void)connectionDidFinishLoading:(NSURLConnection *)connection{\n    NSLog(@\"connection did finish loading\");\n    //解析数据\n    NSString *json = [[NSString alloc]initWithData:self.fileData encoding:NSUTF8StringEncoding];\n    NSLog(@\"%@\",json);\n}\n\n\n#pragma mark -- NSURLSessionDelegate\n// 1.接收到服务器的响应\n- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {\n    // 允许处理服务器的响应，才会继续接收服务器返回的数据\n    completionHandler(NSURLSessionResponseAllow);\n}\n\n// 2.接收到服务器的数据（可能调用多次）\n- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {\n    // 处理每次接收的数据\n    [self.fileData appendData:data];\n}\n\n// 3.请求成功或者失败（如果失败，error有值）\n- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {\n    // 请求完成,成功或者失败的处理\n    NSString *json = [[NSString alloc]initWithData:self.fileData encoding:NSUTF8StringEncoding];\n    NSLog(@\"%@\",json);\n}\n\n- (void)mockTest {\n    [DoraemonNetworkUtil getWithUrlString:@\"http://172.23.162.150:8080/api/topics/hot.json?name=yi\" params:nil success:^(NSDictionary * _Nonnull result) {\n        NSLog(@\"result == %@\",result);\n    } error:^(NSError * _Nonnull error) {\n        NSLog(@\"error == %@\",error);\n    }];\n}\n\n- (void)mockTest2 {\n    [DoraemonNetworkUtil postWithUrlString:@\"http://172.23.162.150:8080/api/topics/hot.json\" params:@{@\"bodyTitle\":@\"bodyName\"} success:^(NSDictionary * _Nonnull result) {\n        NSLog(@\"result == %@\",result);\n    } error:^(NSError * _Nonnull error) {\n        NSLog(@\"error == %@\",error);\n    }];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/Image/DoraemonDemoImageShowViewController.h",
    "content": "//\n//  DoraemonDemoImageShowViewController.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2019/6/18.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoImageShowViewController : DoraemonDemoBaseViewController\n\n@property (nonatomic, strong) UIImage *image;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/Image/DoraemonDemoImageShowViewController.m",
    "content": "//\n//  DoraemonDemoImageShowViewController.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2019/6/18.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoImageShowViewController.h\"\n#import \"DoraemonDefine.h\"\n#import <SDWebImage/UIImageView+WebCache.h>\n\n@interface DoraemonDemoImageShowViewController ()\n\n@property (nonatomic, strong) UIImageView *imageView;\n\n@end\n\n@implementation DoraemonDemoImageShowViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    [self setOriginalImage:_image];\n    [self.imageView sd_setImageWithURL:[NSURL URLWithString:@\"http://05imgmini.eastday.com/mobile/20190617/2019061519_87d0d825dc3440d4ab2a3c4ca33380f1_8193_mwpm_03201609.jpg\"]];\n}\n\n- (void)setOriginalImage:(UIImage *)originalImage {\n    CGFloat viewWidth = self.view.doraemon_width;\n    CGFloat viewHeight = self.view.doraemon_height;\n    CGFloat imageWidth = originalImage.size.width;\n    CGFloat imageHeight = originalImage.size.height;\n    imageHeight = imageHeight ? : 1;\n    BOOL isPortrait = imageHeight / viewHeight > imageWidth / viewWidth;\n    CGFloat scaledImageWidth, scaledImageHeight;\n    CGFloat x,y;\n    CGFloat imageScale;\n    if (isPortrait) {//图片竖屏分量比较大\n        imageScale = imageHeight / viewHeight;\n        scaledImageHeight = viewHeight;\n        scaledImageWidth = imageWidth / imageScale;\n        x = (viewWidth - scaledImageWidth) / 2;\n        y = 0;\n    } else {//图片横屏分量比较大\n        imageScale = imageWidth / viewWidth;\n        scaledImageWidth = viewWidth;\n        scaledImageHeight = imageHeight / imageScale;\n        x = 0;\n        y = (viewHeight - scaledImageHeight) / 2;\n    }\n    _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(x, y, scaledImageWidth, scaledImageHeight)];\n    _imageView.image = originalImage;\n    _imageView.userInteractionEnabled = YES;\n    [self.view addSubview:_imageView];\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/Image/DoraemonDemoImageViewController.h",
    "content": "//\n//  DoraemonDemoImageViewController.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2019/6/18.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonDemoImageViewController : DoraemonDemoBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/Image/DoraemonDemoImageViewController.m",
    "content": "//\n//  DoraemonDemoImageViewController.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2019/6/18.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoImageViewController.h\"\n#import \"DoraemonDefine.h\"\n#import \"DoraemonDemoImageShowViewController.h\"\n#import <SDWebImage/SDWebImageManager.h>\n#import \"DoraemonUIWebViewViewController.h\"\n#import \"DoraemonWKWebViewViewController.h\"\n\n@interface DoraemonDemoImageViewController ()\n\n@property (nonatomic, copy) NSString *picUrl;\n\n@end\n\n@implementation DoraemonDemoImageViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"图片测试\");\n    \n    _picUrl = @\"http://wx2.sinaimg.cn/large/61e7f4aaly1g0qsmz73juj20iv0iv4h0.jpg\";\n    \n    UIButton *btn0 = [[UIButton alloc] initWithFrame:CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT, self.view.doraemon_width, 60)];\n    btn0.backgroundColor = [UIColor orangeColor];\n    [btn0 setTitle:DoraemonDemoLocalizedString(@\"dataWithContentsOfURL\") forState:UIControlStateNormal];\n    [btn0 addTarget:self action:@selector(dataWithContentsOfURL) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn0];\n    \n    UIButton *btn1 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn0.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn1.backgroundColor = [UIColor orangeColor];\n    [btn1 setTitle:DoraemonDemoLocalizedString(@\"SDWebImage\") forState:UIControlStateNormal];\n    [btn1 addTarget:self action:@selector(sdWebImage) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn1];\n    \n    UIButton *btn2 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn1.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn2.backgroundColor = [UIColor orangeColor];\n    [btn2 setTitle:DoraemonDemoLocalizedString(@\"uiWebView\") forState:UIControlStateNormal];\n    [btn2 addTarget:self action:@selector(uiWebView) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn2];\n    \n    UIButton *btn3 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn2.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn3.backgroundColor = [UIColor orangeColor];\n    [btn3 setTitle:DoraemonDemoLocalizedString(@\"wkWebView\") forState:UIControlStateNormal];\n    [btn3 addTarget:self action:@selector(wkWebView) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn3];\n}\n\n- (void)dataWithContentsOfURL{\n    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{\n        NSURL *imageUrl = [NSURL URLWithString:self.picUrl];\n        NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];\n        UIImage *result = [UIImage imageWithData:imageData];\n        dispatch_async(dispatch_get_main_queue(), ^{\n            [self showImage:result];\n        });\n    });\n}\n\n- (void)sdWebImage{\n//NSURL *imageUrl = [NSURL URLWithString:self.picUrl];\n    [[SDWebImageManager sharedManager] loadImageWithURL:_picUrl options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {\n        //\n    } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {\n        //\n        [self showImage:image];\n    }];\n}\n\n- (void)uiWebView{\n    UIViewController *vc = [[DoraemonUIWebViewViewController alloc] init];\n    [self.navigationController pushViewController:vc animated:YES];\n}\n\n- (void)wkWebView{\n    UIViewController *vc = [[DoraemonWKWebViewViewController alloc] init];\n    [self.navigationController pushViewController:vc animated:YES];\n}\n\n- (void)showImage:(UIImage *)image{\n    DoraemonDemoImageShowViewController *vc = [[DoraemonDemoImageShowViewController alloc] init];\n    vc.image = image;\n    [self.navigationController pushViewController:vc animated:YES];\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/WebView/DoraemonUIWebViewViewController.h",
    "content": "//\n//  DoraemonUIWebViewViewController.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/12/26.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonUIWebViewViewController : DoraemonDemoBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/WebView/DoraemonUIWebViewViewController.m",
    "content": "//\n//  DoraemonUIWebViewViewController.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/12/26.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonUIWebViewViewController.h\"\n\n\n@interface DoraemonUIWebViewViewController ()\n\n@end\n\n@implementation DoraemonUIWebViewViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    self.title = DoraemonDemoLocalizedString(@\"UIWebView\");\n    UIWebView * view = [[UIWebView alloc] initWithFrame:self.view.frame];\n    [view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@\"http://www.juyanwenjuan.com/\"]]];\n    [self.view addSubview:view];\n    \n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/WebView/DoraemonWKWebViewViewController.h",
    "content": "//\n//  DoraemonWKWebViewViewController.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/12/26.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonWKWebViewViewController : DoraemonDemoBaseViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Net/WebView/DoraemonWKWebViewViewController.m",
    "content": "//\n//  DoraemonWKWebViewViewController.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/12/26.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonWKWebViewViewController.h\"\n#import <WebKit/WebKit.h>\n\n@interface DoraemonWKWebViewViewController ()\n\n@end\n\n@implementation DoraemonWKWebViewViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    self.title = DoraemonDemoLocalizedString(@\"WKWebView\");\n    WKWebView *webView = [[WKWebView alloc]initWithFrame:self.view.frame];\n    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@\"http://www.juyanwenjuan.com/\"]]];\n    [self.view addSubview:webView];\n    \n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Performance/DoraemonDemoPerformanceViewController.h",
    "content": "//\n//  DoraemonDemoPerformanceViewController.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/5/15.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\n@interface DoraemonDemoPerformanceViewController : DoraemonDemoBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Performance/DoraemonDemoPerformanceViewController.m",
    "content": "//\n//  DoraemonDemoPerformanceViewController.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/5/15.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoPerformanceViewController.h\"\n#import \"UIView+Doraemon.h\"\n#import <AFNetworking/AFNetworking.h>\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonDemoPerformanceViewController (){\n    Byte  *_addMemory;\n}\n\n@property (nonatomic, assign) BOOL highCPU;\n@property (nonatomic, strong) NSThread *cpuThread;\n@property (nonatomic, assign) BOOL highMemory;\n@property (nonatomic, strong) NSThread *memoryThread;\n\n@property (nonatomic, strong) UIButton *btn0;\n@property (nonatomic, strong) UIButton *btn1;\n@property (nonatomic, strong) UIButton *btn2;\n@property (nonatomic, strong) UIButton *btn3;\n@property (nonatomic, strong) UIButton *btn4;\n\n@end\n\n@implementation DoraemonDemoPerformanceViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"性能测试Demo\");\n    \n    _highCPU = NO;\n    _highMemory = NO;\n    \n    UIButton *btn0 = [[UIButton alloc] initWithFrame:CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT, self.view.doraemon_width, 60)];\n    btn0.backgroundColor = [UIColor orangeColor];\n    [btn0 setTitle:DoraemonDemoLocalizedString(@\"低FPS操作打开\") forState:UIControlStateNormal];\n    [btn0 addTarget:self action:@selector(fpsClick) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn0];\n    _btn0 = btn0;\n    \n    UIButton *btn1 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn0.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn1.backgroundColor = [UIColor orangeColor];\n    [btn1 setTitle:DoraemonDemoLocalizedString(@\"高CPU操作打开\") forState:UIControlStateNormal];\n    [btn1 addTarget:self action:@selector(cpuClick) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn1];\n    _btn1 = btn1;\n    \n    UIButton *btn2 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn1.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn2.backgroundColor = [UIColor orangeColor];\n    [btn2 setTitle:DoraemonDemoLocalizedString(@\"高内存操作打开\") forState:UIControlStateNormal];\n    [btn2 addTarget:self action:@selector(memoryClick) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn2];\n    _btn2 = btn2;\n    \n    UIButton *btn3 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn2.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn3.backgroundColor = [UIColor orangeColor];\n    [btn3 setTitle:DoraemonDemoLocalizedString(@\"高流量操作打开\") forState:UIControlStateNormal];\n    [btn3 addTarget:self action:@selector(flowClick) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn3];\n    _btn3 = btn3;\n    \n    UIButton *btn4 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn3.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn4.backgroundColor = [UIColor orangeColor];\n    [btn4 setTitle:DoraemonDemoLocalizedString(@\"卡顿操作打开\") forState:UIControlStateNormal];\n    [btn4 addTarget:self action:@selector(anrClick) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn4];\n    _btn4 = btn4;\n    \n}\n\n- (void)dealloc{\n    if (_addMemory) {\n        free(_addMemory);\n        _addMemory = nil;\n    }\n}\n\n- (void)fpsClick{\n    [NSThread sleepForTimeInterval:0.5];\n}\n\n- (void)cpuClick{\n    _highCPU = !_highCPU;\n    if (_highCPU) {\n        _cpuThread = [[NSThread alloc] initWithTarget:self selector:@selector(highCPUOperate) object:nil];\n        _cpuThread.name = @\"HighCPUThread\";\n        [_cpuThread start];\n        \n        [_btn1 setTitle:DoraemonLocalizedString(@\"高CPU操作关闭\") forState:UIControlStateNormal];\n    }else{\n        [_cpuThread cancel];\n        _cpuThread = nil;\n        \n        [_btn1 setTitle:DoraemonLocalizedString(@\"高CPU操作打开\") forState:UIControlStateNormal];\n    }\n}\n\n- (void)memoryClick{\n    _highMemory = !_highMemory;\n    if (_highMemory) {\n        _memoryThread = [[NSThread alloc] initWithTarget:self selector:@selector(highMemoryOperate) object:nil];\n        _memoryThread.name = @\"HighMemoryThread\";\n        [_memoryThread start];\n        \n        [_btn2 setTitle:DoraemonLocalizedString(@\"高内存操作关闭\") forState:UIControlStateNormal];\n    }else{\n        [_memoryThread cancel];\n        _memoryThread = nil;\n        \n        [_btn2 setTitle:DoraemonLocalizedString(@\"高内存操作打开\") forState:UIControlStateNormal];\n    }\n}\n\n- (void)flowClick{\n    for(int i=0 ; i<10; i++){\n//        AFHTTPSessionManager *session = [AFHTTPSessionManager manager];\n//        session.requestSerializer = [AFHTTPRequestSerializer serializer];// 请求\n//        session.responseSerializer = [AFHTTPResponseSerializer serializer];// 响应\n//        session.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@\"application/json\",@\"text/json\",@\"text/javascript\",@\"text/html\", nil];\n//        [session GET:@\"https://www.taobao.com/\" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {\n//            NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];\n//            NSLog(@\"请求成功 %@\",string);\n//        } failure:^(NSURLSessionDataTask *task, NSError *error) {\n//            NSLog(@\"请求失败\");\n//        }];\n        \n        AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];\n        manager.requestSerializer = [AFHTTPRequestSerializer serializer];// 请求\n        manager.responseSerializer = [AFHTTPResponseSerializer serializer];// 响应\n        manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@\"application/json\",@\"text/json\",@\"text/javascript\",@\"text/html\", nil];\n        \n        [manager GET:@\"https://www.taobao.com/\" parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {\n            NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];\n            NSLog(@\"request success %@\",string);\n        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {\n            NSLog(@\"request failure\");\n        }];\n    }\n\n}\n\n\n- (void)highCPUOperate{\n    while (TRUE) {\n        if ([[NSThread currentThread] isCancelled]) {\n            [NSThread exit];\n        }\n    }\n}\n\n\n- (void)highMemoryOperate{\n    int addedMemSize = 400;\n    int interval = 2;\n    while (TRUE) {\n        \n        if ([[NSThread currentThread] isCancelled]) {\n            [NSThread exit];\n        }\n        if (!_addMemory) {\n            _addMemory = malloc(1024*1024*addedMemSize);\n            if (_addMemory) {\n                memset(_addMemory, 0, 1024*1024*addedMemSize);\n            }else{\n                NSLog(@\"add mem failed!\");\n            }\n            \n        }\n        \n        \n        [NSThread sleepForTimeInterval:interval];\n        if ([[NSThread currentThread] isCancelled]) {\n            [NSThread exit];\n        }\n        \n        if (_addMemory) {\n            free(_addMemory);\n            _addMemory = nil;\n            \n        }\n        \n        [NSThread sleepForTimeInterval:interval];\n    }\n}\n\n- (void)anrClick{\n    NSLog(@\"0.4s anr\");\n    [NSThread sleepForTimeInterval:0.4];\n    \n//    for(int i=0 ; i< 50000; i++){\n//        UIView *v = [[UIView alloc] init];\n//        v.frame = CGRectMake(0, 100, 100, 100);\n//        v.backgroundColor = [UIColor redColor];\n//        [self.view addSubview:v];\n//    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Sanbox/DoraemonDemoSanboxViewController.h",
    "content": "//\n//  DoraemonDemoSanboxViewController.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/5/15.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\n@interface DoraemonDemoSanboxViewController : DoraemonDemoBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/Sanbox/DoraemonDemoSanboxViewController.m",
    "content": "//\n//  DoraemonDemoSanboxViewController.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/5/15.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoSanboxViewController.h\"\n#import <DoraemonKit/UIView+Doraemon.h>\n#import \"DoraemonDefine.h\"\n#import <sqlite3.h>\n\n@interface DoraemonDemoSanboxViewController ()\n\n@end\n\n@implementation DoraemonDemoSanboxViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"沙盒测试Demo\");\n    \n    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT, self.view.doraemon_width, 60)];\n    btn.backgroundColor = [UIColor orangeColor];\n    [btn setTitle:DoraemonDemoLocalizedString(@\"添加一条json到沙盒中\") forState:UIControlStateNormal];\n    [btn addTarget:self action:@selector(addFile) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn];\n    \n    UIButton *btn1 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn1.backgroundColor = [UIColor orangeColor];\n    [btn1 setTitle:DoraemonDemoLocalizedString(@\"添加一张图片到沙盒中\") forState:UIControlStateNormal];\n    [btn1 addTarget:self action:@selector(addImageFile) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn1];\n    \n    UIButton *btn2 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn1.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn2.backgroundColor = [UIColor orangeColor];\n    [btn2 setTitle:DoraemonDemoLocalizedString(@\"添加一段mp4到沙盒中\") forState:UIControlStateNormal];\n    [btn2 addTarget:self action:@selector(addMP4File) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn2];\n    \n    UIButton *btn3 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn2.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn3.backgroundColor = [UIColor orangeColor];\n    [btn3 setTitle:DoraemonDemoLocalizedString(@\"添加doc、xlsx、pdf到沙盒中\") forState:UIControlStateNormal];\n    [btn3 addTarget:self action:@selector(addOtherFile) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn3];\n    \n    UIButton *btn4 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn3.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn4.backgroundColor = [UIColor orangeColor];\n    [btn4 setTitle:DoraemonDemoLocalizedString(@\"添加html到沙盒中\") forState:UIControlStateNormal];\n    [btn4 addTarget:self action:@selector(addHtmlFile) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn4];\n    \n    UIButton *btn5 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn4.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn5.backgroundColor = [UIColor orangeColor];\n    [btn5 setTitle:DoraemonDemoLocalizedString(@\"添加DB到沙盒中\") forState:UIControlStateNormal];\n    [btn5 addTarget:self action:@selector(addDBFile) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn5];\n    \n    UIButton *btn6 = [[UIButton alloc] initWithFrame:CGRectMake(0, btn5.doraemon_bottom+20, self.view.doraemon_width, 60)];\n    btn6.backgroundColor = [UIColor orangeColor];\n    [btn6 setTitle:DoraemonDemoLocalizedString(@\"添加Webp到沙盒中\") forState:UIControlStateNormal];\n    [btn6 addTarget:self action:@selector(addWebPFile) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn6];\n}\n\n- (void)addFile{\n    // 获取沙盒主目录路径\n    //NSString *homeDir = NSHomeDirectory();\n    // 获取Documents目录路径\n    //NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];\n    // 获取Library的目录路径\n    //NSString *libDir = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];\n    // 获取Caches目录路径\n    //NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];\n    // 获取tmp目录路径\n    //NSString *tmpDir =  NSTemporaryDirectory();\n    \n    //向Document目录中添加一条json数据\n    NSDictionary *dic = @{\n                          @\"name\":@\"yixiang\",\n                          @\"age\":@16\n                          };\n    NSString *json = [self dictToJsonStr:dic];\n    \n    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];\n    NSString *filePath = [docDir stringByAppendingPathComponent:@\"json.txt\"];\n    \n    [json writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];\n    \n}\n\n- (void)addImageFile{\n    UIImage *image2 = [UIImage imageNamed:@\"zhaoliyin.jpg\"];\n    NSString *path_document = NSHomeDirectory();\n    NSString *imagePath = [path_document stringByAppendingString:@\"/Documents/zhaoliyin.jpg\"];\n    [UIImagePNGRepresentation(image2) writeToFile:imagePath atomically:YES];\n}\n\n- (void)addMP4File{\n    [self copyBundleToSanboxWithName:@\"huoying\" type:@\"mp4\"];\n}\n\n- (void)addOtherFile{\n    [self copyBundleToSanboxWithName:@\"Doraemon\" type:@\"docx\"];\n    [self copyBundleToSanboxWithName:@\"Doraemon\" type:@\"pdf\"];\n    [self copyBundleToSanboxWithName:@\"Doraemon\" type:@\"xlsx\"];\n}\n\n- (void)addHtmlFile{\n    [self copyBundleToSanboxWithName:@\"doraemon\" type:@\"html\"];\n}\n\n- (void)addWebPFile{\n    [self copyBundleToSanboxWithName:@\"WebpDemo\" type:@\"webp\"];\n}\n\n- (void)addDBFile{\n    NSString *path_document = NSHomeDirectory();\n    NSString *appendingString = [NSString stringWithFormat:@\"/Documents/doraemon.db\"];\n    NSString *toPath = [path_document stringByAppendingString:appendingString];\n    const char *filename = [toPath UTF8String];\n    sqlite3 *db = nil;\n    int result = sqlite3_open(filename, &db);\n    if(result == SQLITE_OK){\n        NSLog(@\"open sqlite success\");\n    }else{\n        NSLog(@\"open sqlite failure\");\n    }\n    \n    //2.创建表\n    const char  *sql = \"CREATE TABLE IF NOT EXISTS t_students (id integer PRIMARY KEY AUTOINCREMENT,name text NOT NULL,age integer NOT NULL);\";\n    char *errmsg = NULL;\n    result = sqlite3_exec(db, sql, NULL, NULL, &errmsg);\n    if (result == SQLITE_OK) {\n        NSLog(@\"create table success\");\n    }else{\n        NSLog(@\"create table failure----%s\",errmsg);\n    }\n    \n    //插入数据\n    for (int i=0; i<20; i++) {\n        //1.拼接SQL语句\n        NSString *name=[NSString stringWithFormat:@\"TestText--%d\",arc4random_uniform(100)];\n        int age=arc4random_uniform(20)+10;\n        NSString *sql=[NSString stringWithFormat:@\"INSERT INTO t_students (name,age) VALUES ('%@',%d);\",name,age];\n        \n        //2.执行SQL语句\n        char *errmsg=NULL;\n        sqlite3_exec(db, sql.UTF8String, NULL, NULL, &errmsg);\n        if (errmsg) {//如果有错误信息\n            NSLog(@\"insert failure--%s\",errmsg);\n        }else\n        {\n            NSLog(@\"insert success\");\n        }\n    }\n}\n\n- (void)copyBundleToSanboxWithName:(NSString *)name type:(NSString *)type{\n    NSFileManager *fileManage = [NSFileManager defaultManager];\n    \n    NSBundle *bundle = [NSBundle mainBundle];\n    NSString *path = [bundle pathForResource:name ofType:type];\n    if(![fileManage fileExistsAtPath:path]){\n        NSLog(@\"file not exist\");\n        return;\n    }\n    NSString *fromPath = path;\n    NSString *path_document = NSHomeDirectory();\n    NSString *appendingString = [NSString stringWithFormat:@\"/Documents/%@.%@\",name,type];\n    NSString *toPath = [path_document stringByAppendingString:appendingString];\n    if (![fileManage fileExistsAtPath:toPath]) {\n        BOOL isSuccess = [fileManage copyItemAtPath:fromPath toPath:toPath error:nil];\n        NSLog(@\"name=%@ %@\",name,isSuccess ? @\"copy success\" : @\"copy failure\");\n    }\n}\n\n- (NSString *)dictToJsonStr:(NSDictionary *)dict{\n    NSString *jsonString = nil;\n    if ([NSJSONSerialization isValidJSONObject:dict])\n    {\n        NSError *error;\n        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error];\n        jsonString =[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n        //NSLog(@\"json data:%@\",jsonString);\n        if (error) {\n            NSLog(@\"Error:%@\" , error);\n        }\n    }\n    return jsonString;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/UI/DoraemonDemoUIViewController.h",
    "content": "//\n//  DoraemonDemoUIViewController.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/5/15.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoBaseViewController.h\"\n\n@interface DoraemonDemoUIViewController : DoraemonDemoBaseViewController\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DemoVC/UI/DoraemonDemoUIViewController.m",
    "content": "//\n//  DoraemonDemoUIViewController.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/5/15.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonDemoUIViewController.h\"\n#import <DoraemonKit/UIColor+Doraemon.h>\n#import \"DoraemonDefine.h\"\n\n@interface DoraemonDemoUIViewController ()\n\n@property (nonatomic, strong) UIView *redView;\n@property (nonatomic, strong) UILabel *titleLabelAAA;\n\n@end\n\n@implementation DoraemonDemoUIViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    self.title = DoraemonDemoLocalizedString(@\"视觉测试Demo\");\n    \n    UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(100, 200, 60, 60)];\n    redView.backgroundColor = [UIColor redColor];\n    [self.view addSubview:redView];\n    _redView = redView;\n    \n//    UIView *alphaView = [[UIView alloc] initWithFrame:CGRectMake(100, 300, 60, 60)];\n//    alphaView.backgroundColor = [UIColor doraemon_colorWithHexString:@\"#FFFF00\"];\n//    alphaView.alpha = 0.5;\n//    [self.view addSubview:alphaView];\n    \n    UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 400, 200, 60)];\n    titleLabel.text = DoraemonDemoLocalizedString(@\"我是来测试的\");\n    titleLabel.backgroundColor = [UIColor doraemon_colorWithString:@\"#00FF00\"];\n    titleLabel.textColor = [UIColor doraemon_colorWithString:@\"#FF0000\"];\n    [self.view addSubview:titleLabel];\n    _titleLabelAAA = titleLabel;\n    \n    UITextField *input = [[UITextField alloc] initWithFrame:CGRectMake(100, 300, 200, 50)];\n    input.textAlignment = NSTextAlignmentCenter;\n    input.keyboardType = UIKeyboardTypeNumberPad;\n    input.backgroundColor  = [UIColor lightGrayColor];\n    [self.view addSubview:input];\n    \n    UITextField *input2 = [[UITextField alloc] initWithFrame:CGRectMake(100, 500, 200, 50)];\n    input2.textAlignment = NSTextAlignmentCenter;\n    //input2.keyboardType = UIKeyboardTypeNumberPad;\n    input2.backgroundColor  = [UIColor lightGrayColor];\n    [self.view addSubview:input2];\n    \n    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(200, 200, 200, 50)];\n    button.backgroundColor = [UIColor lightGrayColor];\n    button.layer.cornerRadius = 8;\n    [button setTitle:@\"UIMenuController\" forState:UIControlStateNormal];\n    [button addTarget:self action:@selector(deleteBtnAction:) forControlEvents:UIControlEventTouchUpInside];\n    \n    [self.view addSubview:button];\n    \n    \n}\n\n\n- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event\n{\n     //设置为第一响应者\n}\n\n- (void)deleteBtnAction:(UIButton *)deleteBtn{\n    [self.view.window becomeFirstResponder];\n    [self becomeFirstResponder];// 用于UIMenuController显示，缺一不可\n    UIMenuController *menu = [UIMenuController sharedMenuController];\n    UIMenuItem *item1 = [[UIMenuItem alloc] initWithTitle:@\"cancel\" action:@selector(revokeAction)];\n    UIMenuItem *item2 = [[UIMenuItem alloc] initWithTitle:@\"ok\" action:@selector(sureAction)];\n    menu.menuItems = @[item1, item2];\n    menu.arrowDirection = UIMenuControllerArrowUp;\n    [menu setMenuVisible:YES animated:YES];\n    \n    if(![menu isMenuVisible]) {\n//            UIWindow *window = [[UIApplication sharedApplication].delegate window];\n       \n//        if([window isKeyWindow]){\n//                [window becomeKeyWindow];\n//                [window makeKeyAndVisible];\n            [menu setTargetRect:deleteBtn.bounds inView:deleteBtn];\n            if (@available(iOS 13.0, *)) {\n                [menu showMenuFromView:self.view rect:deleteBtn.frame];\n            } else {\n                [menu setMenuVisible:YES animated:YES];\n            }\n        //}\n    }\n}\n\n- (BOOL)canBecomeFirstResponder{\n    UIWindow *window = [[UIApplication sharedApplication].delegate window];\n    if(window.keyWindow == NO){\n            [window becomeKeyWindow];\n            [window makeKeyAndVisible];\n        }\n    return YES;\n }\n\n\n\n- (BOOL)canPerformAction:(SEL)action withSender:(id)sender\n{\n    if (action == @selector(revokeAction)) {\n        return YES;\n    }\n    if (action == @selector(sureAction)) {\n        return YES;\n    }\n    return NO;//隐藏系统默认的菜单项\n}\n\n- (void)revokeAction{\n    NSLog(@\"cancel\");\n    //UIWindow *window = [[UIApplication sharedApplication].delegate window];\n    //[window.rootViewController presentViewController:[[PresentViewController alloc] init] animated:YES completion:nil];\n}\n\n- (void)sureAction{\n    NSLog(@\"ok\");\n    \n    \n    //NSArray *array = [string componentsSeparatedByString:@\";\"]; //从字符A中分隔成2个元素的数组\n//    int index = 0;\n//    srandom((unsigned)time(0));\n//    while (index < array.count) {\n//        usleep(40000);\n//        NSLog(@\"%@\",array[index]);\n//        index ++;\n//    }\n//\n    \n}\n\n\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DoKitAppDelegate.h",
    "content": "//\n//  AppDelegate.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2017/12/11.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface DoKitAppDelegate : UIResponder <UIApplicationDelegate>\n\n@property (strong, nonatomic) UIWindow *window;\n\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DoKitAppDelegate.m",
    "content": "//\n//  AppDelegate.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2017/12/11.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#import \"DoKitAppDelegate.h\"\n#import <DoraemonKit/DoraemonKit.h>\n#import \"DoraemonDemoHomeViewController.h\"\n#import \"DoraemonTimeProfiler.h\"\n//#import <CocoaLumberjack/CocoaLumberjack.h>\n#import \"DoraemonUtil.h\"\n#import \"SDImageWebPCoder.h\"\n#import <DoraemonKit/DoraemonAppInfoViewController.h>\n#import <fmdb/FMDB.h>\n\n#if __has_include(<FBRetainCycleDetector/FBRetainCycleDetector.h>)\n#define XXX 1\n#else\n#define XXX 2\n#endif\n\n@interface DoKitAppDelegate ()\n\n@end\n\n@implementation DoKitAppDelegate\n\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {\n    \n    //[DoraemonTimeProfiler startRecord];\n    \n//    id a = [[NSObject alloc] init];\n//    [a b];\n    \n    //[[self class] handleCCrashReportWrap];\n    NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);\n\n    for (int i=0; i<10; i++) {\n        //DDLogInfo(@\"点击添加埋点11111\");\n    }\n    [[DoraemonManager shareInstance] addPluginWithTitle:DoraemonDemoLocalizedString(@\"测试插件\") icon:@\"doraemon_default\" desc:DoraemonDemoLocalizedString(@\"测试插件\") pluginName:@\"TestPlugin\" atModule:DoraemonDemoLocalizedString(@\"业务工具\")];\n\n    [[DoraemonManager shareInstance] addPluginWithTitle:DoraemonDemoLocalizedString(@\"block方式加入插件\") icon:@\"doraemon_default\" desc:@\"测试插件\" pluginName:@\"pluginName\" atModule:DoraemonDemoLocalizedString(@\"业务工具\") handle:^(NSDictionary *itemData) {\n        NSLog(@\"handle block plugin\");\n    }];\n    \n    [[DoraemonManager shareInstance] addPluginWithTitle:@\"自定图标\" image:[UIImage imageNamed:@\"zhaoliyin\"] desc:@\"自定义图标\" pluginName:@\"自定图标\" atModule:DoraemonDemoLocalizedString(@\"业务工具\") handle:nil];\n\n    //测试 a49842eeebeb1989b3f9565eb12c276b\n    //线上 749a0600b5e48dd77cf8ee680be7b1b7\n    //[DoraemonManager shareInstance].pId = @\"749a0600b5e48dd77cf8ee680be7b1b7\";\n    [[DoraemonManager shareInstance] addStartPlugin:@\"StartPlugin\"];\n    [DoraemonManager shareInstance].bigImageDetectionSize = 10 * 1024;//大图检测只检测10K以上的\n    [DoraemonManager shareInstance].startClass = @\"DoKitAppDelegate\";\n    //[DoraemonManager shareInstance].autoDock = NO;\n    [[DoraemonManager shareInstance] installWithPid:@\"749a0600b5e48dd77cf8ee680be7b1b7\"];\n    //[[DoraemonManager shareInstance] installWithStartingPosition:CGPointMake(66, 66)];\n    \n    [[DoraemonManager shareInstance] addANRBlock:^(NSDictionary *anrDic) {\n        NSLog(@\"anrDic == %@\",anrDic);\n    }];\n\n    [[DoraemonManager shareInstance] addH5DoorBlock:^(NSString *h5Url) {\n        NSLog(@\"使用自带容器打开H5链接: %@\",h5Url);\n        \n    }];\n    \n    [[DoraemonManager shareInstance] addWebpHandleBlock:^UIImage * _Nullable(NSString * _Nonnull filePath) {\n        NSData *data = [[NSData alloc] initWithContentsOfFile:filePath];\n        UIImage *image = [[SDImageWebPCoder sharedCoder] decodedImageWithData:data options:nil];\n        return image;\n    }];\n    // 例子：移除 GPS Mock\n//    [[DoraemonManager shareInstance] installWithCustomBlock:^{\n//        [[DoraemonManager shareInstance] removePluginWithPluginName:@\"DoraemonGPSPlugin\" atModule:@\"常用工具\"];\n//    }];\n\n    for (int i=0; i<10; i++) {\n       // DDLogInfo(@\"点击添加埋点22222\");\n    }\n    \n    \n    // Override point for customization after application launch.\n    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];\n    DoraemonDemoHomeViewController *homeVc = [[DoraemonDemoHomeViewController alloc] init];\n    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:homeVc];\n    self.window.rootViewController = nav;\n    [self.window makeKeyAndVisible];\n    \n    NSArray *array = @[];\n    NSLog(@\"%@\",[array description]);\n    \n    //[DoraemonTimeProfiler stopRecord];\n    \n    [self test];\n    \n    [self createTestDatabase];\n    \n    return YES;\n}\n\n- (void)test{\n    [self test2];\n    \n    DoraemonAppInfoViewController.customAppInfoBlock = ^(NSMutableArray<NSDictionary *> *appInfos) {\n        [appInfos addObject:@{@\"title\": @\"Build Time\", @\"value\": @\"2020-05-21 11:29:22\"}];\n        [appInfos addObject:@{@\"title\": @\"Commit\", @\"value\": @\"1ffa9c6\"}];\n        [appInfos addObject:@{@\"title\": @\"Branch\", @\"value\": @\"version/1.2.0\"}];\n    };\n}\n\n- (void)test2{\n    NSDictionary *dic = @{\n        @\"name\":@\"yixiang\"\n    }.mutableCopy;\n    [dic setValue:@\"caoweoweo\" forKey:@\"name\"];\n    NSLog(@\"a == %@\",dic);\n}\n\n- (void)createTestDatabase {\n    NSString *rootPath = NSHomeDirectory();\n    NSString *dBPath = [NSString stringWithFormat:@\"%@/Documents/dokit_test_database.sqlite\", rootPath];\n    FMDatabase *db;\n    if (![[NSFileManager defaultManager] fileExistsAtPath:dBPath]) {\n        db = [FMDatabase databaseWithPath:dBPath];\n        db.shouldCacheStatements = YES;\n        \n        if ([db open]) {\n            NSString *sql_1 = @\"CREATE TABLE IF NOT EXISTS dokit_records_table (id INTEGER PRIMARY KEY, record TEXT, record_2 INTEGER);\";\n            [db executeUpdate:sql_1];\n            \n//            NSString *sql_2 = @\"CREATE TABLE IF NOT EXISTS dokit_records_table_2 (id_2 INTEGER PRIMARY KEY AUTOINCREMENT, record_2 TEXT);\";\n//            [db executeUpdate:sql_2];\n            \n//            NSString *sql_3 = @\"SELECT * FROM sqlite_master WHERE type='table' ORDER BY name;\";\n//            FMResultSet *set = [db executeQuery:sql_3];\n//            while ([set next]) {\n//                NSString *name = [set stringForColumnIndex:1];\n//                NSLog(@\"-=-=-=- name = %@\", name);\n//            }\n\n            [db close];\n        }\n    }\n}\n\nvoid uncaughtExceptionHandler(NSException*exception){\n    NSLog(@\"CRASH: %@\", exception);\n    NSLog(@\"Stack Trace: %@\",[exception callStackSymbols]);\n    // Internal error reporting\n}\n\n+(void) handleCCrashReportWrap{    \n//    PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType: PLCrashReporterSignalHandlerTypeMach symbolicationStrategy: PLCrashReporterSymbolicationStrategyAll];\n//    PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config];\n//\n//    NSError *error;\n//\n//    // Check if we previously crashed\n//    if ([crashReporter hasPendingCrashReport])\n//        [self handleCCrashReport:crashReporter ];\n//\n//    // Enable the Crash Reporter\n//    if (![crashReporter enableCrashReporterAndReturnError: &error])\n//        NSLog(@\"Warning: Could not enable crash reporter: %@\", error);\n}\n\n//+ (void) handleCCrashReport:(PLCrashReporter * ) crashReporter{\n//    NSData *crashData;\n//    NSError *error;\n//    \n//    // Try loading the crash report\n//    crashData = [crashReporter loadPendingCrashReportDataAndReturnError: &error];\n//    if (crashData == nil) {\n//        NSLog(@\"Could not load crash report: %@\", error);\n//        [crashReporter purgePendingCrashReport];\n//        return;\n//    }\n//    \n//    // We could send the report from here, but we'll just print out\n//    // some debugging info instead\n//    PLCrashReport *report = [[PLCrashReport alloc] initWithData: crashData error: &error] ;\n//    \n//    if (report == nil) {\n//        NSLog(@\"Could not parse crash report\");\n//        [crashReporter purgePendingCrashReport];\n//        return;\n//    }\n//    \n//    //    NSLog(@\"Crashed on %@\", report.systemInfo.timestamp);\n//    //    NSLog(@\"Crashed with signal %@ (code %@, address=0x%\" PRIx64 \")\", report.signalInfo.name,\n//    //          report.signalInfo.code, report.signalInfo.address);\n//    \n//    // Purge the report\n//    [crashReporter purgePendingCrashReport];\n//    \n////    *     //上传--这后面的代码是我加的.\n//       NSString *humanText = [PLCrashReportTextFormatter stringValueForCrashReport:report withTextFormat:PLCrashReportTextFormatiOS];\n//    NSLog(@\"yixiang crash == %@\",humanText);\n////    *\n////    *     [self uploadCrashLog:@\"c crash\" crashContent:humanText withSuccessBlock:^{\n////        *     }];//uploadCrashLog这个函数是把log文件上传服务器的,请自行补充哈;还可以写入沙盒,供自己就地查看,下面是写入沙盒的代码\n////    *\n////    *     NSString * documentDic = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES).firstObject;\n////    *     NSString * fileName = [documentDic stringByAppendingPathComponent:@\"1.crash\"];\n////    *     NSError * err = nil;\n////    *     [humanText writeToFile:fileName atomically:YES encoding:NSUTF8StringEncoding error:&err];\n////    return;\n//\n//}\n\n\n- (void)applicationWillResignActive:(UIApplication *)application {\n    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.\n    // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.\n}\n\n\n- (void)applicationDidEnterBackground:(UIApplication *)application {\n    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.\n    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.\n}\n\n\n- (void)applicationWillEnterForeground:(UIApplication *)application {\n    // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.\n}\n\n\n- (void)applicationDidBecomeActive:(UIApplication *)application {\n    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.\n}\n\n\n- (void)applicationWillTerminate:(UIApplication *)application {\n    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/DoraemonKitDemo-PrefixHeader.pch",
    "content": "//\n//  DoraemonKitDemo-PrefixHeader.pch\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2017/12/13.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#ifdef __OBJC__\n#ifndef DoraemonKitDemo_PrefixHeader_pch\n#define DoraemonKitDemo_PrefixHeader_pch\n\n#import \"DoraemonKitDemoi18Util.h\"\n\n\n#if __has_include(<CocoaLumberjack/CocoaLumberjack.h>)\n#import <CocoaLumberjack/CocoaLumberjack.h>\n\nstatic const DDLogLevel ddLogLevel = DDLogLevelDebug;\n#endif\n\n\n#endif /* DoraemonKitDemo_PrefixHeader_pch */\n#endif\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleDisplayName</key>\n\t<string>DoraemonKit</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>LSApplicationQueriesSchemes</key>\n\t<array>\n\t\t<string>meituanqcsr</string>\n\t</array>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>NSAppTransportSecurity</key>\n\t<dict>\n\t\t<key>NSAllowsArbitraryLoads</key>\n\t\t<true/>\n\t</dict>\n\t<key>NSCameraUsageDescription</key>\n\t<string>是否允许访问您的摄像头，用于扫描二维码</string>\n\t<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>\n\t<string>DoraemonKit申请定位</string>\n\t<key>NSLocationAlwaysUsageDescription</key>\n\t<string>DoraemonKit申请定位</string>\n\t<key>NSLocationUsageDescription</key>\n\t<string>DoraemonKit申请定位</string>\n\t<key>NSLocationWhenInUseUsageDescription</key>\n\t<string>DoraemonKit申请定位</string>\n\t<key>NSMicrophoneUsageDescription</key>\n\t<string>是否允许访问您的麦克风</string>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIRequiredDeviceCapabilities</key>\n\t<array>\n\t\t<string>armv7</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Plugin/StartPlugin.h",
    "content": "//\n//  StartPlugin.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2017/12/17.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import <DoraemonKit/DoraemonStartPluginProtocol.h>\n\n@interface StartPlugin : NSObject<DoraemonStartPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Plugin/StartPlugin.m",
    "content": "//\n//  StartPlugin.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2017/12/17.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#import \"StartPlugin.h\"\n\n@implementation StartPlugin\n\n- (void)startPluginDidLoad{\n    NSLog(@\"start pluginDidLoad\");\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Plugin/TestPlugin.h",
    "content": "//\n//  TestPlugin.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2017/12/11.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import <DoraemonKit/DoraemonPluginProtocol.h>\n\n@interface TestPlugin : NSObject<DoraemonPluginProtocol>\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Plugin/TestPlugin.m",
    "content": "//\n//  TestPlugin.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2017/12/11.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#import \"TestPlugin.h\"\n\n\n@implementation TestPlugin\n\n- (void)pluginDidLoad{\n    NSLog(@\"TestPlugin pluginDidLoad\");\n}\n\n- (void)pluginDidLoad:(NSDictionary *)itemData{\n    NSLog(@\"TestPlugin pluginDidLoad:itemData = %@\",itemData);\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Resource/doraemon.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Doraemon</title>\n</head>\n<body>\n<h1>DoraemonKit标题</h1>\n<h2>DoraemonKit真好用</h2>\n<h3>DoraemonKit真好用真好用</h3>\n<h4>DoraemonKit真好用真好用真好用真好用</h4>\n<img src=\"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1553864579528&di=17a85f3f5eb6ca62c2d58ebd66bb65ea&imgtype=0&src=http%3A%2F%2Fwww.yulefm.com%2Fd%2Ffile%2Fnews%2F2019-02-06%2F1549443987832838.jpeg\"/>\n</body>\n</html>"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Util/DoraemonKitDemoi18Util.h",
    "content": "//\n//  DoraemonKitDemoi18Util.h\n//  DoraemonKitDemo\n//\n//  Created by didi on 2020/4/4.\n//  Copyright © 2020 yixiang. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n\n#define DoraemonDemoLocalizedString(key)   [DoraemonKitDemoi18Util localizedString:key]\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface DoraemonKitDemoi18Util : NSObject\n\n+ (NSString *)localizedString:(NSString *)key;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Util/DoraemonKitDemoi18Util.m",
    "content": "//\n//  DoraemonKitDemoi18Util.m\n//  DoraemonKitDemo\n//\n//  Created by didi on 2020/4/4.\n//  Copyright © 2020 yixiang. All rights reserved.\n//\n\n#import \"DoraemonKitDemoi18Util.h\"\n\n@implementation DoraemonKitDemoi18Util\n\n+ (NSString *)localizedString:(NSString *)key {\n    \n    NSString *language = [[NSLocale preferredLanguages] firstObject];\n    if (language.length == 0) {\n        return key;\n    }\n    NSString *fileNamePrefix = @\"zh-Hans\";\n    if([language hasPrefix:@\"en\"]) {\n        fileNamePrefix = @\"en\";\n    }\n    \n    NSString *path = [[NSBundle mainBundle] pathForResource:fileNamePrefix ofType:@\"lproj\"];\n    NSBundle *bundle = [NSBundle bundleWithPath:path];\n    NSString *localizedString = [bundle localizedStringForKey:key value:nil table:@\"DoraemonKitDemo\"];\n    if (!localizedString) {\n        localizedString = key;\n    }\n    return localizedString;\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Util/NSObject+Runtime.h",
    "content": "//\n//  NSObject+Runtime.h\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/1/30.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface NSObject (Runtime)\n\n/**\n swizzle 类方法\n \n @param oriSel 原有的方法\n @param swiSel swizzle的方法\n */\n+ (void)swizzleClassMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel;\n\n/**\n swizzle 实例方法\n \n @param oriSel 原有的方法\n @param swiSel swizzle的方法\n */\n+ (void)swizzleInstanceMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel;\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/Util/NSObject+Runtime.m",
    "content": "//\n//  NSObject+Runtime.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2018/1/30.\n//  Copyright © 2018年 yixiang. All rights reserved.\n//\n\n#import \"NSObject+Runtime.h\"\n#import <objc/runtime.h>\n\n@implementation NSObject (Runtime)\n\n+ (void)swizzleClassMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel {\n    Class cls = object_getClass(self);\n    \n    Method originAddObserverMethod = class_getClassMethod(cls, oriSel);\n    Method swizzledAddObserverMethod = class_getClassMethod(cls, swiSel);\n    \n    [self swizzleMethodWithOriginSel:oriSel oriMethod:originAddObserverMethod swizzledSel:swiSel swizzledMethod:swizzledAddObserverMethod class:cls];\n}\n\n+ (void)swizzleInstanceMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel {\n    Method originAddObserverMethod = class_getInstanceMethod(self, oriSel);\n    Method swizzledAddObserverMethod = class_getInstanceMethod(self, swiSel);\n    \n    [self swizzleMethodWithOriginSel:oriSel oriMethod:originAddObserverMethod swizzledSel:swiSel swizzledMethod:swizzledAddObserverMethod class:self];\n}\n\n+ (void)swizzleMethodWithOriginSel:(SEL)oriSel\n                         oriMethod:(Method)oriMethod\n                       swizzledSel:(SEL)swizzledSel\n                    swizzledMethod:(Method)swizzledMethod\n                             class:(Class)cls {\n    BOOL didAddMethod = class_addMethod(cls, oriSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));\n    \n    if (didAddMethod) {\n        class_replaceMethod(cls, swizzledSel, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));\n    } else {\n        method_exchangeImplementations(oriMethod, swizzledMethod);\n    }\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/en.lproj/DoraemonKitDemo.strings",
    "content": "/* \n  DoraemonKitDemo.strings\n  DoraemonKitDemo\n\n  Created by didi on 2020/4/4.\n  Copyright © 2020 yixiang. All rights reserved.\n*/\n\n{\n    //DoraemonKitDemo\n    \n    \n    //沙盒测试Demo\n    \"沙盒测试Demo\"                                 = \"Sandbox test Demo\";\n    \"添加一条json到沙盒中\"                     = \"Add json into sandbox\";\n    \"添加一张图片到沙盒中\"                   = \"Add image into sandbox\";\n    \"添加一段mp4到沙盒中\"                      = \"add mp4 into sandbox\";\n    \"添加doc、xlsx、pdf到沙盒中\"               = \"Add doc、xlsx、pdf into sandbox\";\n    \"添加html到沙盒中\"                           = \"Add html into sandbox\";\n    \"添加DB到沙盒中\"                             = \"Add DB into sandbox\";\n    \n    //日志测试Demo\n    \"日志测试Demo\"                                 = \"Log test Demo\";\n    \"添加一条NSLog日志\"                          = \"Add NSLog log\";\n    \"添加一条CocoaLumberjack日志\"                = \"Add CocoaLumberjack log\";\n    \n    //性能测试Demo\n    \"性能测试Demo\"                                 = \"Performance Test Demo\";\n    \"低FPS操作打开\"                               = \"Low FPS operation\";\n    \"高CPU操作打开\"                               = \"High CPU operation\";\n    \"高CPU操作关闭\"                               = \"High CPU operation off\";\n    \"高内存操作打开\"                            = \"High memory operation\";\n    \"高内存操作关闭\"                            = \"High memory operation off\";\n    \"高流量操作打开\"                            = \"High network traffic operation\";\n    \"卡顿操作打开\"                               = \"ANR operation\";\n    \n    //视觉测试Demo\n    \"视觉测试Demo\"                                 = \"UI test Demo\";\n    \"我是来测试的\"                               = \"Test\";\n    \n    //网络测试Demo\n    \"网络测试Demo\"                                 = \"Networking test Demo\";\n    \"发送一条URLConnection请求\"                  = \"Send URLConnection request\";\n    \"发送一条NSURLSession请求\"                   = \"Send NSURLSession request\";\n    \"发送一条AFNetworking请求\"                   = \"Send AFNetworking request\";\n    \"发送一条AFNetworking请求2\"                  = \"Send AFNetworking request2\";\n    \"打开UIWebView\"                                  = \"Open UIWebView\";\n    \"打开WKWebView\"                                  = \"Open WKWebView\";\n    \"图片测试\"                              = \"Image Test\";\n    \"Mock测试\"                              = \"Mock Test\";\n    \"Mock测试2\"                             = \"Mock Test 2\";\n    \n    //模拟位置Demo\n    \"模拟位置Demo\"                                 = \"Mock GPS Demo\";\n    \"模拟位置\"                                     = \"Mock GPS\";\n    \n    //crash\n    \"crash触发Demo\"                                  = \"Crash Demo\";\n    \"uncaughtException\"                                = \"uncaughtException\";\n    \"signalException\"                                  = \"signalException\";\n    \n    //通用测试Demo\n    \"通用测试Demo\"                                 = \"Common test Demo\";\n    \"子线程UI操作\"                                = \"Sub Thread UI\";\n    \"显示入口\"                                   = \"Show Doraemon\";\n    \"隐藏入口\"                                   = \"Hidden Doraemon\";\n    \n    //测试泄漏测试\n    \"内存泄漏测试\"                            = \"Memory Leak Test\";\n    \n    //Other\n    \"block方式加入插件\"                       = \"Add plugins from block\";\n    \"测试插件\"                                     = \"Test plugin\";\n    \n    // 一机多控测试\n    \"一机多控测试Demo\"                        = \"Multi Control Demo\" ;\n}\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/main.m",
    "content": "//\n//  main.m\n//  DoraemonKitDemo\n//\n//  Created by yixiang on 2017/12/11.\n//  Copyright © 2017年 yixiang. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"DoKitAppDelegate.h\"\n\nint main(int argc, char * argv[]) {\n    @autoreleasepool {\n        return UIApplicationMain(argc, argv, nil, NSStringFromClass([DoKitAppDelegate class]));\n    }\n}\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/zh-Hans.lproj/DoraemonKitDemo.strings",
    "content": "/* \n  DoraemonKitDemo.strings\n  DoraemonKitDemo\n\n  Created by didi on 2020/4/4.\n  Copyright © 2020 yixiang. All rights reserved.\n*/\n\n{\n    \n    //DoraemonKitDemo\n    \n    \n    //沙盒测试Demo\n    \"沙盒测试Demo\"                                 = \"沙盒测试Demo\";\n    \"添加一条json到沙盒中\"                     = \"添加一条json到沙盒中\";\n    \"添加一张图片到沙盒中\"                   = \"添加一张图片到沙盒中\";\n    \"添加一段mp4到沙盒中\"                      = \"添加一段mp4到沙盒中\";\n    \"添加doc、xlsx、pdf到沙盒中\"               = \"添加doc、xlsx、pdf到沙盒中\";\n    \"添加html到沙盒中\"                           = \"添加html到沙盒中\";\n    \"添加DB到沙盒中\"                             = \"添加DB到沙盒中\";\n    \n    //日志测试Demo\n    \"日志测试Demo\"                                 = \"日志测试Demo\";\n    \"添加一条NSLog日志\"                          = \"添加一条NSLog日志\";\n    \"添加一条CocoaLumberjack日志\"                = \"添加一条CocoaLumberjack日志\";\n    \n    //性能测试Demo\n    \"性能测试Demo\"                                 = \"性能测试Demo\";\n    \"低FPS操作打开\"                               = \"低FPS操作打开\";\n    \"高CPU操作打开\"                               = \"高CPU操作打开\";\n    \"高CPU操作关闭\"                               = \"高CPU操作关闭\";\n    \"高内存操作打开\"                            = \"高内存操作打开\";\n    \"高内存操作关闭\"                            = \"高内存操作关闭\";\n    \"高流量操作打开\"                            = \"高流量操作打开\";\n    \"卡顿操作打开\"                               = \"卡顿操作打开\";\n    \n    //视觉测试Demo\n    \"视觉测试Demo\"                                 = \"视觉测试Demo\";\n    \"我是来测试的\"                               = \"我是来测试的\";\n    \n    //网络测试Demo\n    \"网络测试Demo\"                                 = \"网络测试Demo\";\n    \"发送一条URLConnection请求\"                  = \"发送一条URLConnection请求\";\n    \"发送一条NSURLSession请求\"                   = \"发送一条NSURLSession请求\";\n    \"发送一条AFNetworking请求\"                   = \"发送一条AFNetworking请求\";\n    \"发送一条AFNetworking请求2\"                  = \"发送一条AFNetworking请求2\";\n    \"打开UIWebView\"                                  = \"打开UIWebView\";\n    \"打开WKWebView\"                                  = \"打开WKWebView\";\n    \"图片测试\"                              = \"图片测试\";\n    \"Mock测试\"                              = \"Mock测试\";\n    \"Mock测试2\"                             = \"Mock测试2\";\n    \n    //模拟位置Demo\n    \"模拟位置Demo\"                                 = \"模拟位置Demo\";\n    \"模拟位置\"                                     = \"模拟位置\";\n    \n    //crash\n    \"crash触发Demo\"                                  = \"crash触发Demo\";\n    \"uncaughtException\"                                = \"uncaughtException\";\n    \"signalException\"                                  = \"signalException\";\n    \n    //通用测试Demo\n    \"通用测试Demo\"                                 = \"通用测试Demo\";\n    \"子线程UI操作\"                                = \"子线程UI操作\";\n    \"显示入口\"                                   = \"显示入口\";\n    \"隐藏入口\"                                   = \"隐藏入口\";\n    \n    //测试泄漏测试\n    \"内存泄漏测试\"                            = \"内存泄漏测试\";\n    \n    //Other\n    \"block方式加入插件\"                       = \"block方式加入插件\";\n    \"测试插件\"                                     = \"测试插件\";\n    \n    // 一机多控测试\n    \"一机多控测试Demo\"                        = \"一机多控测试Demo\";\n}\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo/zh-Hans.lproj/LaunchScreen.strings",
    "content": "\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/DoraemonKitDemo.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 48;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t0977CCE0269F394100A6463E /* DoraemonDemoMultiControlViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0977CCDF269F394000A6463E /* DoraemonDemoMultiControlViewController.m */; };\n\t\t097F6DA326D53E5D00841403 /* DoraemonDemoMultiSlideView.m in Sources */ = {isa = PBXBuildFile; fileRef = 097F6DA226D53E5D00841403 /* DoraemonDemoMultiSlideView.m */; };\n\t\t09B042C526A8585500045D2A /* DoraemonDemoMultiConLongPressGesture.m in Sources */ = {isa = PBXBuildFile; fileRef = 09B042C426A8585500045D2A /* DoraemonDemoMultiConLongPressGesture.m */; };\n\t\t09B042C926A860B400045D2A /* DoraemonDemoMultiConPinchGesture.m in Sources */ = {isa = PBXBuildFile; fileRef = 09B042C826A860B400045D2A /* DoraemonDemoMultiConPinchGesture.m */; };\n\t\t09B042CD26A8760E00045D2A /* DoraemonDemoMultiConRotationGesture.m in Sources */ = {isa = PBXBuildFile; fileRef = 09B042CC26A8760E00045D2A /* DoraemonDemoMultiConRotationGesture.m */; };\n\t\t09B042D126A8777900045D2A /* DoraemonDemoMultiConSwipeGesture.m in Sources */ = {isa = PBXBuildFile; fileRef = 09B042D026A8777900045D2A /* DoraemonDemoMultiConSwipeGesture.m */; };\n\t\t09B042D526A877ED00045D2A /* DoraemonDemoMultiConScreenEdgePanGesture.m in Sources */ = {isa = PBXBuildFile; fileRef = 09B042D426A877ED00045D2A /* DoraemonDemoMultiConScreenEdgePanGesture.m */; };\n\t\t09B042D926A8781A00045D2A /* DoraemonDemoMultiConTapGesture.m in Sources */ = {isa = PBXBuildFile; fileRef = 09B042D826A8781A00045D2A /* DoraemonDemoMultiConTapGesture.m */; };\n\t\t09D36E8A2733D2C9005174D4 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 09D36E892733D2C9005174D4 /* Info.plist */; };\n\t\t0A5399852349ED5B00C47CB3 /* DoraemonDemoMemoryLeakViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A5399842349ED5B00C47CB3 /* DoraemonDemoMemoryLeakViewController.m */; };\n\t\t0A5399882349EE7E00C47CB3 /* DoraemonDemoMemoryLeakModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A5399872349EE7E00C47CB3 /* DoraemonDemoMemoryLeakModel.m */; };\n\t\t0A53998B2349EE9500C47CB3 /* DoraemonDemoMemoryLeakView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A53998A2349EE9500C47CB3 /* DoraemonDemoMemoryLeakView.m */; };\n\t\t0A6C9CEA2423214600BAEF5E /* DoraemonDemoURLProtocol1.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A6C9CE92423214600BAEF5E /* DoraemonDemoURLProtocol1.m */; };\n\t\t0A6C9CED2423219400BAEF5E /* DoraemonDemoURLProtocol2.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A6C9CEC2423219400BAEF5E /* DoraemonDemoURLProtocol2.m */; };\n\t\t0A6CF2762438BD3B00A2CBF4 /* DoraemonKitDemo.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0A6CF2782438BD3B00A2CBF4 /* DoraemonKitDemo.strings */; };\n\t\t0A6CF27C2438BDDD00A2CBF4 /* DoraemonKitDemoi18Util.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A6CF27B2438BDDD00A2CBF4 /* DoraemonKitDemoi18Util.m */; };\n\t\t0AA262E1240F8AC400BF144F /* DoraemonDemoBaseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0AA262E0240F8AC400BF144F /* DoraemonDemoBaseViewController.m */; };\n\t\t0AC7509C246A8EE100F87363 /* WebpDemo.webp in Resources */ = {isa = PBXBuildFile; fileRef = 0AC7509B246A8B4900F87363 /* WebpDemo.webp */; };\n\t\t0AFBC55223BAE6F80099A8BD /* DoraemonDemoNetTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0AFBC55023BAE6F80099A8BD /* DoraemonDemoNetTableViewCell.m */; };\n\t\tB65C2263AF96F94C50A222DF /* libPods-DoraemonKitDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29E964A2D92411C9DC5482EF /* libPods-DoraemonKitDemo.a */; };\n\t\tDA0C6F331FDEBC2E00F43588 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DA0C6F311FDEBC2E00F43588 /* LaunchScreen.storyboard */; };\n\t\tDA0C6F3A1FDEBE3800F43588 /* TestPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0C6F391FDEBE3800F43588 /* TestPlugin.m */; };\n\t\tDA4AD81A224E0EC8006F767C /* Doraemon.docx in Resources */ = {isa = PBXBuildFile; fileRef = DA4AD817224E0EC8006F767C /* Doraemon.docx */; };\n\t\tDA4AD81B224E0EC8006F767C /* Doraemon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = DA4AD818224E0EC8006F767C /* Doraemon.pdf */; };\n\t\tDA4AD81C224E0EC8006F767C /* Doraemon.xlsx in Resources */ = {isa = PBXBuildFile; fileRef = DA4AD819224E0EC8006F767C /* Doraemon.xlsx */; };\n\t\tDA4AD81E224E27DA006F767C /* doraemon.html in Resources */ = {isa = PBXBuildFile; fileRef = DA4AD81D224E27DA006F767C /* doraemon.html */; };\n\t\tDA63F1D321C8CF9500E0D9F5 /* huoying.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = DA63F1D221C8CCAB00E0D9F5 /* huoying.mp4 */; };\n\t\tDABBE3B221D3AA260070518E /* DoraemonUIWebViewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DABBE3B121D3AA260070518E /* DoraemonUIWebViewViewController.m */; };\n\t\tDABBE3B521D3AA4D0070518E /* DoraemonWKWebViewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DABBE3B421D3AA4D0070518E /* DoraemonWKWebViewViewController.m */; };\n\t\tDAC3FA7622B8DD7400871E5C /* DoraemonDemoImageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC3FA7522B8DD7400871E5C /* DoraemonDemoImageViewController.m */; };\n\t\tDAC3FA7922B8E0EE00871E5C /* DoraemonDemoImageShowViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC3FA7822B8E0EE00871E5C /* DoraemonDemoImageShowViewController.m */; };\n\t\tDAC8A87C1FDE2C3B00F03E6F /* DoKitAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC8A87B1FDE2C3B00F03E6F /* DoKitAppDelegate.m */; };\n\t\tDAC8A8841FDE2C3B00F03E6F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DAC8A8831FDE2C3B00F03E6F /* Assets.xcassets */; };\n\t\tDAC8A88A1FDE2C3B00F03E6F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC8A8891FDE2C3B00F03E6F /* main.m */; };\n\t\tDAD46BDA1FE6B7AF00E3E7BC /* StartPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD46BD91FE6B7AF00E3E7BC /* StartPlugin.m */; };\n\t\tDAE7BFAD20205C20008D49D8 /* NSObject+Runtime.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE7BFAC20205C20008D49D8 /* NSObject+Runtime.m */; };\n\t\tDAFE052521BD4A4D00F97A59 /* DoraemonDemoUIViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE050721BD4A4C00F97A59 /* DoraemonDemoUIViewController.m */; };\n\t\tDAFE052621BD4A4D00F97A59 /* DoraemonDemoHomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE050A21BD4A4C00F97A59 /* DoraemonDemoHomeViewController.m */; };\n\t\tDAFE052721BD4A4D00F97A59 /* DoraemonDemoNetViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE050E21BD4A4C00F97A59 /* DoraemonDemoNetViewController.m */; };\n\t\tDAFE052821BD4A4D00F97A59 /* DoraemonDemoLoggerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE051121BD4A4C00F97A59 /* DoraemonDemoLoggerViewController.m */; };\n\t\tDAFE052921BD4A4D00F97A59 /* DoraemonDemoCrashViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE051521BD4A4C00F97A59 /* DoraemonDemoCrashViewController.m */; };\n\t\tDAFE052A21BD4A4D00F97A59 /* DoraemonDemoCrashMRCView.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE051621BD4A4C00F97A59 /* DoraemonDemoCrashMRCView.m */; };\n\t\tDAFE052B21BD4A4D00F97A59 /* DoraemonDemoSanboxViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE051921BD4A4C00F97A59 /* DoraemonDemoSanboxViewController.m */; };\n\t\tDAFE052C21BD4A4D00F97A59 /* DoraemonDemoCommonViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE051C21BD4A4C00F97A59 /* DoraemonDemoCommonViewController.m */; };\n\t\tDAFE052D21BD4A4D00F97A59 /* DoraemonDemoMockGPSAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE051E21BD4A4C00F97A59 /* DoraemonDemoMockGPSAnnotation.m */; };\n\t\tDAFE052E21BD4A4D00F97A59 /* DoraemonDemoMockGPSViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE052121BD4A4C00F97A59 /* DoraemonDemoMockGPSViewController.m */; };\n\t\tDAFE052F21BD4A4D00F97A59 /* DoraemonDemoPerformanceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE052321BD4A4C00F97A59 /* DoraemonDemoPerformanceViewController.m */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t0977CCDE269F394000A6463E /* DoraemonDemoMultiControlViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoMultiControlViewController.h; sourceTree = \"<group>\"; };\n\t\t0977CCDF269F394000A6463E /* DoraemonDemoMultiControlViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoMultiControlViewController.m; sourceTree = \"<group>\"; };\n\t\t097F6DA126D53E5D00841403 /* DoraemonDemoMultiSlideView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoMultiSlideView.h; sourceTree = \"<group>\"; };\n\t\t097F6DA226D53E5D00841403 /* DoraemonDemoMultiSlideView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoMultiSlideView.m; sourceTree = \"<group>\"; };\n\t\t09B042C326A8585500045D2A /* DoraemonDemoMultiConLongPressGesture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoMultiConLongPressGesture.h; sourceTree = \"<group>\"; };\n\t\t09B042C426A8585500045D2A /* DoraemonDemoMultiConLongPressGesture.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoMultiConLongPressGesture.m; sourceTree = \"<group>\"; };\n\t\t09B042C726A860B400045D2A /* DoraemonDemoMultiConPinchGesture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoMultiConPinchGesture.h; sourceTree = \"<group>\"; };\n\t\t09B042C826A860B400045D2A /* DoraemonDemoMultiConPinchGesture.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoMultiConPinchGesture.m; sourceTree = \"<group>\"; };\n\t\t09B042CB26A8760E00045D2A /* DoraemonDemoMultiConRotationGesture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoMultiConRotationGesture.h; sourceTree = \"<group>\"; };\n\t\t09B042CC26A8760E00045D2A /* DoraemonDemoMultiConRotationGesture.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoMultiConRotationGesture.m; sourceTree = \"<group>\"; };\n\t\t09B042CF26A8777900045D2A /* DoraemonDemoMultiConSwipeGesture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoMultiConSwipeGesture.h; sourceTree = \"<group>\"; };\n\t\t09B042D026A8777900045D2A /* DoraemonDemoMultiConSwipeGesture.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoMultiConSwipeGesture.m; sourceTree = \"<group>\"; };\n\t\t09B042D326A877ED00045D2A /* DoraemonDemoMultiConScreenEdgePanGesture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoMultiConScreenEdgePanGesture.h; sourceTree = \"<group>\"; };\n\t\t09B042D426A877ED00045D2A /* DoraemonDemoMultiConScreenEdgePanGesture.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoMultiConScreenEdgePanGesture.m; sourceTree = \"<group>\"; };\n\t\t09B042D726A8781A00045D2A /* DoraemonDemoMultiConTapGesture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoMultiConTapGesture.h; sourceTree = \"<group>\"; };\n\t\t09B042D826A8781A00045D2A /* DoraemonDemoMultiConTapGesture.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoMultiConTapGesture.m; sourceTree = \"<group>\"; };\n\t\t09D36E892733D2C9005174D4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t0A5399832349ED5B00C47CB3 /* DoraemonDemoMemoryLeakViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoMemoryLeakViewController.h; sourceTree = \"<group>\"; };\n\t\t0A5399842349ED5B00C47CB3 /* DoraemonDemoMemoryLeakViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoMemoryLeakViewController.m; sourceTree = \"<group>\"; };\n\t\t0A5399862349EE7E00C47CB3 /* DoraemonDemoMemoryLeakModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoMemoryLeakModel.h; sourceTree = \"<group>\"; };\n\t\t0A5399872349EE7E00C47CB3 /* DoraemonDemoMemoryLeakModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoMemoryLeakModel.m; sourceTree = \"<group>\"; };\n\t\t0A5399892349EE9500C47CB3 /* DoraemonDemoMemoryLeakView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoMemoryLeakView.h; sourceTree = \"<group>\"; };\n\t\t0A53998A2349EE9500C47CB3 /* DoraemonDemoMemoryLeakView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoMemoryLeakView.m; sourceTree = \"<group>\"; };\n\t\t0A6C9CE82423214600BAEF5E /* DoraemonDemoURLProtocol1.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoURLProtocol1.h; sourceTree = \"<group>\"; };\n\t\t0A6C9CE92423214600BAEF5E /* DoraemonDemoURLProtocol1.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoURLProtocol1.m; sourceTree = \"<group>\"; };\n\t\t0A6C9CEB2423219400BAEF5E /* DoraemonDemoURLProtocol2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoURLProtocol2.h; sourceTree = \"<group>\"; };\n\t\t0A6C9CEC2423219400BAEF5E /* DoraemonDemoURLProtocol2.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoURLProtocol2.m; sourceTree = \"<group>\"; };\n\t\t0A6CF2752438BD0100A2CBF4 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = \"zh-Hans\"; path = \"zh-Hans.lproj/LaunchScreen.strings\"; sourceTree = \"<group>\"; };\n\t\t0A6CF2772438BD3B00A2CBF4 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = \"zh-Hans\"; path = \"zh-Hans.lproj/DoraemonKitDemo.strings\"; sourceTree = \"<group>\"; };\n\t\t0A6CF2792438BD4600A2CBF4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/DoraemonKitDemo.strings; sourceTree = \"<group>\"; };\n\t\t0A6CF27A2438BDDD00A2CBF4 /* DoraemonKitDemoi18Util.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonKitDemoi18Util.h; sourceTree = \"<group>\"; };\n\t\t0A6CF27B2438BDDD00A2CBF4 /* DoraemonKitDemoi18Util.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonKitDemoi18Util.m; sourceTree = \"<group>\"; };\n\t\t0AA262DF240F8AC400BF144F /* DoraemonDemoBaseViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoBaseViewController.h; sourceTree = \"<group>\"; };\n\t\t0AA262E0240F8AC400BF144F /* DoraemonDemoBaseViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoBaseViewController.m; sourceTree = \"<group>\"; };\n\t\t0AC7509B246A8B4900F87363 /* WebpDemo.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = WebpDemo.webp; sourceTree = \"<group>\"; };\n\t\t0AFBC55023BAE6F80099A8BD /* DoraemonDemoNetTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoNetTableViewCell.m; sourceTree = \"<group>\"; };\n\t\t0AFBC55123BAE6F80099A8BD /* DoraemonDemoNetTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoNetTableViewCell.h; sourceTree = \"<group>\"; };\n\t\t29E964A2D92411C9DC5482EF /* libPods-DoraemonKitDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = \"libPods-DoraemonKitDemo.a\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tDA0C6F321FDEBC2E00F43588 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\tDA0C6F381FDEBE3800F43588 /* TestPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestPlugin.h; sourceTree = \"<group>\"; };\n\t\tDA0C6F391FDEBE3800F43588 /* TestPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestPlugin.m; sourceTree = \"<group>\"; };\n\t\tDA4AD817224E0EC8006F767C /* Doraemon.docx */ = {isa = PBXFileReference; lastKnownFileType = file; path = Doraemon.docx; sourceTree = \"<group>\"; };\n\t\tDA4AD818224E0EC8006F767C /* Doraemon.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = Doraemon.pdf; sourceTree = \"<group>\"; };\n\t\tDA4AD819224E0EC8006F767C /* Doraemon.xlsx */ = {isa = PBXFileReference; lastKnownFileType = file; path = Doraemon.xlsx; sourceTree = \"<group>\"; };\n\t\tDA4AD81D224E27DA006F767C /* doraemon.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = doraemon.html; sourceTree = \"<group>\"; };\n\t\tDA63F1D221C8CCAB00E0D9F5 /* huoying.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = huoying.mp4; sourceTree = \"<group>\"; };\n\t\tDABBE3B021D3AA260070518E /* DoraemonUIWebViewViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonUIWebViewViewController.h; sourceTree = \"<group>\"; };\n\t\tDABBE3B121D3AA260070518E /* DoraemonUIWebViewViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonUIWebViewViewController.m; sourceTree = \"<group>\"; };\n\t\tDABBE3B321D3AA4D0070518E /* DoraemonWKWebViewViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonWKWebViewViewController.h; sourceTree = \"<group>\"; };\n\t\tDABBE3B421D3AA4D0070518E /* DoraemonWKWebViewViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonWKWebViewViewController.m; sourceTree = \"<group>\"; };\n\t\tDAC0D76F1FE103A50048E322 /* DoraemonKitDemo-PrefixHeader.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = \"DoraemonKitDemo-PrefixHeader.pch\"; sourceTree = \"<group>\"; };\n\t\tDAC3FA7422B8DD7400871E5C /* DoraemonDemoImageViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoImageViewController.h; sourceTree = \"<group>\"; };\n\t\tDAC3FA7522B8DD7400871E5C /* DoraemonDemoImageViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoImageViewController.m; sourceTree = \"<group>\"; };\n\t\tDAC3FA7722B8E0EE00871E5C /* DoraemonDemoImageShowViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoImageShowViewController.h; sourceTree = \"<group>\"; };\n\t\tDAC3FA7822B8E0EE00871E5C /* DoraemonDemoImageShowViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoImageShowViewController.m; sourceTree = \"<group>\"; };\n\t\tDAC8A8771FDE2C3B00F03E6F /* DoraemonKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DoraemonKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tDAC8A87A1FDE2C3B00F03E6F /* DoKitAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoKitAppDelegate.h; sourceTree = \"<group>\"; };\n\t\tDAC8A87B1FDE2C3B00F03E6F /* DoKitAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoKitAppDelegate.m; sourceTree = \"<group>\"; };\n\t\tDAC8A8831FDE2C3B00F03E6F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\tDAC8A8891FDE2C3B00F03E6F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = \"<group>\"; };\n\t\tDAD46BD81FE6B7AF00E3E7BC /* StartPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StartPlugin.h; sourceTree = \"<group>\"; };\n\t\tDAD46BD91FE6B7AF00E3E7BC /* StartPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StartPlugin.m; sourceTree = \"<group>\"; };\n\t\tDAE7BFAB20205C20008D49D8 /* NSObject+Runtime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = \"NSObject+Runtime.h\"; sourceTree = \"<group>\"; };\n\t\tDAE7BFAC20205C20008D49D8 /* NSObject+Runtime.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = \"NSObject+Runtime.m\"; sourceTree = \"<group>\"; };\n\t\tDAFE050721BD4A4C00F97A59 /* DoraemonDemoUIViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoUIViewController.m; sourceTree = \"<group>\"; };\n\t\tDAFE050821BD4A4C00F97A59 /* DoraemonDemoUIViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoUIViewController.h; sourceTree = \"<group>\"; };\n\t\tDAFE050A21BD4A4C00F97A59 /* DoraemonDemoHomeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoHomeViewController.m; sourceTree = \"<group>\"; };\n\t\tDAFE050B21BD4A4C00F97A59 /* DoraemonDemoHomeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoHomeViewController.h; sourceTree = \"<group>\"; };\n\t\tDAFE050D21BD4A4C00F97A59 /* DoraemonDemoNetViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoNetViewController.h; sourceTree = \"<group>\"; };\n\t\tDAFE050E21BD4A4C00F97A59 /* DoraemonDemoNetViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoNetViewController.m; sourceTree = \"<group>\"; };\n\t\tDAFE051021BD4A4C00F97A59 /* DoraemonDemoLoggerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoLoggerViewController.h; sourceTree = \"<group>\"; };\n\t\tDAFE051121BD4A4C00F97A59 /* DoraemonDemoLoggerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoLoggerViewController.m; sourceTree = \"<group>\"; };\n\t\tDAFE051321BD4A4C00F97A59 /* DoraemonDemoCrashViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoCrashViewController.h; sourceTree = \"<group>\"; };\n\t\tDAFE051421BD4A4C00F97A59 /* DoraemonDemoCrashMRCView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoCrashMRCView.h; sourceTree = \"<group>\"; };\n\t\tDAFE051521BD4A4C00F97A59 /* DoraemonDemoCrashViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoCrashViewController.m; sourceTree = \"<group>\"; };\n\t\tDAFE051621BD4A4C00F97A59 /* DoraemonDemoCrashMRCView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoCrashMRCView.m; sourceTree = \"<group>\"; };\n\t\tDAFE051821BD4A4C00F97A59 /* DoraemonDemoSanboxViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoSanboxViewController.h; sourceTree = \"<group>\"; };\n\t\tDAFE051921BD4A4C00F97A59 /* DoraemonDemoSanboxViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoSanboxViewController.m; sourceTree = \"<group>\"; };\n\t\tDAFE051B21BD4A4C00F97A59 /* DoraemonDemoCommonViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoCommonViewController.h; sourceTree = \"<group>\"; };\n\t\tDAFE051C21BD4A4C00F97A59 /* DoraemonDemoCommonViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoCommonViewController.m; sourceTree = \"<group>\"; };\n\t\tDAFE051E21BD4A4C00F97A59 /* DoraemonDemoMockGPSAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoMockGPSAnnotation.m; sourceTree = \"<group>\"; };\n\t\tDAFE051F21BD4A4C00F97A59 /* DoraemonDemoMockGPSViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoMockGPSViewController.h; sourceTree = \"<group>\"; };\n\t\tDAFE052021BD4A4C00F97A59 /* DoraemonDemoMockGPSAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoMockGPSAnnotation.h; sourceTree = \"<group>\"; };\n\t\tDAFE052121BD4A4C00F97A59 /* DoraemonDemoMockGPSViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoMockGPSViewController.m; sourceTree = \"<group>\"; };\n\t\tDAFE052321BD4A4C00F97A59 /* DoraemonDemoPerformanceViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoPerformanceViewController.m; sourceTree = \"<group>\"; };\n\t\tDAFE052421BD4A4C00F97A59 /* DoraemonDemoPerformanceViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoPerformanceViewController.h; sourceTree = \"<group>\"; };\n\t\tDB18BA61EBA817FD46B5C41B /* Pods-DoraemonKitDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-DoraemonKitDemo.debug.xcconfig\"; path = \"Pods/Target Support Files/Pods-DoraemonKitDemo/Pods-DoraemonKitDemo.debug.xcconfig\"; sourceTree = \"<group>\"; };\n\t\tDBA563858BAABEC9BC885911 /* Pods-DoraemonKitDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-DoraemonKitDemo.release.xcconfig\"; path = \"Pods/Target Support Files/Pods-DoraemonKitDemo/Pods-DoraemonKitDemo.release.xcconfig\"; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\tDAC8A8741FDE2C3B00F03E6F /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB65C2263AF96F94C50A222DF /* libPods-DoraemonKitDemo.a in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t0977CCDD269F38F500A6463E /* MultiControl */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0977CCDE269F394000A6463E /* DoraemonDemoMultiControlViewController.h */,\n\t\t\t\t0977CCDF269F394000A6463E /* DoraemonDemoMultiControlViewController.m */,\n\t\t\t\t09B042C326A8585500045D2A /* DoraemonDemoMultiConLongPressGesture.h */,\n\t\t\t\t09B042C426A8585500045D2A /* DoraemonDemoMultiConLongPressGesture.m */,\n\t\t\t\t09B042C726A860B400045D2A /* DoraemonDemoMultiConPinchGesture.h */,\n\t\t\t\t09B042C826A860B400045D2A /* DoraemonDemoMultiConPinchGesture.m */,\n\t\t\t\t09B042CB26A8760E00045D2A /* DoraemonDemoMultiConRotationGesture.h */,\n\t\t\t\t09B042CC26A8760E00045D2A /* DoraemonDemoMultiConRotationGesture.m */,\n\t\t\t\t09B042CF26A8777900045D2A /* DoraemonDemoMultiConSwipeGesture.h */,\n\t\t\t\t09B042D026A8777900045D2A /* DoraemonDemoMultiConSwipeGesture.m */,\n\t\t\t\t09B042D326A877ED00045D2A /* DoraemonDemoMultiConScreenEdgePanGesture.h */,\n\t\t\t\t09B042D426A877ED00045D2A /* DoraemonDemoMultiConScreenEdgePanGesture.m */,\n\t\t\t\t09B042D726A8781A00045D2A /* DoraemonDemoMultiConTapGesture.h */,\n\t\t\t\t09B042D826A8781A00045D2A /* DoraemonDemoMultiConTapGesture.m */,\n\t\t\t\t097F6DA126D53E5D00841403 /* DoraemonDemoMultiSlideView.h */,\n\t\t\t\t097F6DA226D53E5D00841403 /* DoraemonDemoMultiSlideView.m */,\n\t\t\t);\n\t\t\tpath = MultiControl;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0A5399822349ECF000C47CB3 /* MemoryLeak */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0A5399832349ED5B00C47CB3 /* DoraemonDemoMemoryLeakViewController.h */,\n\t\t\t\t0A5399842349ED5B00C47CB3 /* DoraemonDemoMemoryLeakViewController.m */,\n\t\t\t\t0A5399862349EE7E00C47CB3 /* DoraemonDemoMemoryLeakModel.h */,\n\t\t\t\t0A5399872349EE7E00C47CB3 /* DoraemonDemoMemoryLeakModel.m */,\n\t\t\t\t0A5399892349EE9500C47CB3 /* DoraemonDemoMemoryLeakView.h */,\n\t\t\t\t0A53998A2349EE9500C47CB3 /* DoraemonDemoMemoryLeakView.m */,\n\t\t\t);\n\t\t\tpath = MemoryLeak;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0A6C9CE72423212000BAEF5E /*  NSURLProtocol */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0A6C9CE82423214600BAEF5E /* DoraemonDemoURLProtocol1.h */,\n\t\t\t\t0A6C9CE92423214600BAEF5E /* DoraemonDemoURLProtocol1.m */,\n\t\t\t\t0A6C9CEB2423219400BAEF5E /* DoraemonDemoURLProtocol2.h */,\n\t\t\t\t0A6C9CEC2423219400BAEF5E /* DoraemonDemoURLProtocol2.m */,\n\t\t\t);\n\t\t\tpath = \" NSURLProtocol\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AA262DE240F8A7700BF144F /* Base */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0AA262DF240F8AC400BF144F /* DoraemonDemoBaseViewController.h */,\n\t\t\t\t0AA262E0240F8AC400BF144F /* DoraemonDemoBaseViewController.m */,\n\t\t\t);\n\t\t\tpath = Base;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AFBC54F23BAE6F80099A8BD /* Cell */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0AFBC55123BAE6F80099A8BD /* DoraemonDemoNetTableViewCell.h */,\n\t\t\t\t0AFBC55023BAE6F80099A8BD /* DoraemonDemoNetTableViewCell.m */,\n\t\t\t);\n\t\t\tpath = Cell;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6BF226158409D1338AF520EC /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDB18BA61EBA817FD46B5C41B /* Pods-DoraemonKitDemo.debug.xcconfig */,\n\t\t\t\tDBA563858BAABEC9BC885911 /* Pods-DoraemonKitDemo.release.xcconfig */,\n\t\t\t);\n\t\t\tname = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t9B3A6EF2639894DCF5DB2FD4 /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t29E964A2D92411C9DC5482EF /* libPods-DoraemonKitDemo.a */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDA0C6F371FDEBE2500F43588 /* Plugin */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDA0C6F381FDEBE3800F43588 /* TestPlugin.h */,\n\t\t\t\tDA0C6F391FDEBE3800F43588 /* TestPlugin.m */,\n\t\t\t\tDAD46BD81FE6B7AF00E3E7BC /* StartPlugin.h */,\n\t\t\t\tDAD46BD91FE6B7AF00E3E7BC /* StartPlugin.m */,\n\t\t\t);\n\t\t\tpath = Plugin;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDA63F1D121C8CC9700E0D9F5 /* Resource */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0AC7509B246A8B4900F87363 /* WebpDemo.webp */,\n\t\t\t\tDA63F1D221C8CCAB00E0D9F5 /* huoying.mp4 */,\n\t\t\t\tDA4AD817224E0EC8006F767C /* Doraemon.docx */,\n\t\t\t\tDA4AD818224E0EC8006F767C /* Doraemon.pdf */,\n\t\t\t\tDA4AD819224E0EC8006F767C /* Doraemon.xlsx */,\n\t\t\t\tDA4AD81D224E27DA006F767C /* doraemon.html */,\n\t\t\t);\n\t\t\tpath = Resource;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDABBE3AF21D3A9F40070518E /* WebView */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDABBE3B021D3AA260070518E /* DoraemonUIWebViewViewController.h */,\n\t\t\t\tDABBE3B121D3AA260070518E /* DoraemonUIWebViewViewController.m */,\n\t\t\t\tDABBE3B321D3AA4D0070518E /* DoraemonWKWebViewViewController.h */,\n\t\t\t\tDABBE3B421D3AA4D0070518E /* DoraemonWKWebViewViewController.m */,\n\t\t\t);\n\t\t\tpath = WebView;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAC3FA7322B8DD5C00871E5C /* Image */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDAC3FA7422B8DD7400871E5C /* DoraemonDemoImageViewController.h */,\n\t\t\t\tDAC3FA7522B8DD7400871E5C /* DoraemonDemoImageViewController.m */,\n\t\t\t\tDAC3FA7722B8E0EE00871E5C /* DoraemonDemoImageShowViewController.h */,\n\t\t\t\tDAC3FA7822B8E0EE00871E5C /* DoraemonDemoImageShowViewController.m */,\n\t\t\t);\n\t\t\tpath = Image;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAC8A86E1FDE2C3B00F03E6F = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDAC8A8791FDE2C3B00F03E6F /* DoraemonKitDemo */,\n\t\t\t\tDAC8A8781FDE2C3B00F03E6F /* Products */,\n\t\t\t\t6BF226158409D1338AF520EC /* Pods */,\n\t\t\t\t9B3A6EF2639894DCF5DB2FD4 /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAC8A8781FDE2C3B00F03E6F /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDAC8A8771FDE2C3B00F03E6F /* DoraemonKitDemo.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAC8A8791FDE2C3B00F03E6F /* DoraemonKitDemo */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDA63F1D121C8CC9700E0D9F5 /* Resource */,\n\t\t\t\tDAFE050521BD4A4C00F97A59 /* DemoVC */,\n\t\t\t\tDAE7BFAA20205BCF008D49D8 /* Util */,\n\t\t\t\tDA0C6F371FDEBE2500F43588 /* Plugin */,\n\t\t\t\tDA0C6F311FDEBC2E00F43588 /* LaunchScreen.storyboard */,\n\t\t\t\tDAC8A87A1FDE2C3B00F03E6F /* DoKitAppDelegate.h */,\n\t\t\t\tDAC8A87B1FDE2C3B00F03E6F /* DoKitAppDelegate.m */,\n\t\t\t\tDAC8A8831FDE2C3B00F03E6F /* Assets.xcassets */,\n\t\t\t\tDAC8A8881FDE2C3B00F03E6F /* Info.plist */,\n\t\t\t\tDAC8A8891FDE2C3B00F03E6F /* main.m */,\n\t\t\t\tDAC0D76F1FE103A50048E322 /* DoraemonKitDemo-PrefixHeader.pch */,\n\t\t\t\t0A6CF2782438BD3B00A2CBF4 /* DoraemonKitDemo.strings */,\n\t\t\t);\n\t\t\tpath = DoraemonKitDemo;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAE7BFAA20205BCF008D49D8 /* Util */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDAE7BFAB20205C20008D49D8 /* NSObject+Runtime.h */,\n\t\t\t\tDAE7BFAC20205C20008D49D8 /* NSObject+Runtime.m */,\n\t\t\t\t0A6CF27A2438BDDD00A2CBF4 /* DoraemonKitDemoi18Util.h */,\n\t\t\t\t0A6CF27B2438BDDD00A2CBF4 /* DoraemonKitDemoi18Util.m */,\n\t\t\t);\n\t\t\tpath = Util;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAFE050521BD4A4C00F97A59 /* DemoVC */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0977CCDD269F38F500A6463E /* MultiControl */,\n\t\t\t\t0AA262DE240F8A7700BF144F /* Base */,\n\t\t\t\t0A5399822349ECF000C47CB3 /* MemoryLeak */,\n\t\t\t\tDAFE050621BD4A4C00F97A59 /* UI */,\n\t\t\t\tDAFE050921BD4A4C00F97A59 /* Home */,\n\t\t\t\tDAFE050C21BD4A4C00F97A59 /* Net */,\n\t\t\t\tDAFE050F21BD4A4C00F97A59 /* Logger */,\n\t\t\t\tDAFE051221BD4A4C00F97A59 /* Crash */,\n\t\t\t\tDAFE051721BD4A4C00F97A59 /* Sanbox */,\n\t\t\t\tDAFE051A21BD4A4C00F97A59 /* Common */,\n\t\t\t\tDAFE051D21BD4A4C00F97A59 /* MockGPS */,\n\t\t\t\tDAFE052221BD4A4C00F97A59 /* Performance */,\n\t\t\t);\n\t\t\tpath = DemoVC;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAFE050621BD4A4C00F97A59 /* UI */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDAFE050821BD4A4C00F97A59 /* DoraemonDemoUIViewController.h */,\n\t\t\t\tDAFE050721BD4A4C00F97A59 /* DoraemonDemoUIViewController.m */,\n\t\t\t);\n\t\t\tpath = UI;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAFE050921BD4A4C00F97A59 /* Home */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDAFE050B21BD4A4C00F97A59 /* DoraemonDemoHomeViewController.h */,\n\t\t\t\tDAFE050A21BD4A4C00F97A59 /* DoraemonDemoHomeViewController.m */,\n\t\t\t);\n\t\t\tpath = Home;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAFE050C21BD4A4C00F97A59 /* Net */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0A6C9CE72423212000BAEF5E /*  NSURLProtocol */,\n\t\t\t\t0AFBC54F23BAE6F80099A8BD /* Cell */,\n\t\t\t\tDAC3FA7322B8DD5C00871E5C /* Image */,\n\t\t\t\tDABBE3AF21D3A9F40070518E /* WebView */,\n\t\t\t\tDAFE050D21BD4A4C00F97A59 /* DoraemonDemoNetViewController.h */,\n\t\t\t\tDAFE050E21BD4A4C00F97A59 /* DoraemonDemoNetViewController.m */,\n\t\t\t);\n\t\t\tpath = Net;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAFE050F21BD4A4C00F97A59 /* Logger */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDAFE051021BD4A4C00F97A59 /* DoraemonDemoLoggerViewController.h */,\n\t\t\t\tDAFE051121BD4A4C00F97A59 /* DoraemonDemoLoggerViewController.m */,\n\t\t\t);\n\t\t\tpath = Logger;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAFE051221BD4A4C00F97A59 /* Crash */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDAFE051321BD4A4C00F97A59 /* DoraemonDemoCrashViewController.h */,\n\t\t\t\tDAFE051521BD4A4C00F97A59 /* DoraemonDemoCrashViewController.m */,\n\t\t\t\tDAFE051421BD4A4C00F97A59 /* DoraemonDemoCrashMRCView.h */,\n\t\t\t\tDAFE051621BD4A4C00F97A59 /* DoraemonDemoCrashMRCView.m */,\n\t\t\t);\n\t\t\tpath = Crash;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAFE051721BD4A4C00F97A59 /* Sanbox */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDAFE051821BD4A4C00F97A59 /* DoraemonDemoSanboxViewController.h */,\n\t\t\t\tDAFE051921BD4A4C00F97A59 /* DoraemonDemoSanboxViewController.m */,\n\t\t\t);\n\t\t\tpath = Sanbox;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAFE051A21BD4A4C00F97A59 /* Common */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDAFE051B21BD4A4C00F97A59 /* DoraemonDemoCommonViewController.h */,\n\t\t\t\tDAFE051C21BD4A4C00F97A59 /* DoraemonDemoCommonViewController.m */,\n\t\t\t);\n\t\t\tpath = Common;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAFE051D21BD4A4C00F97A59 /* MockGPS */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDAFE052021BD4A4C00F97A59 /* DoraemonDemoMockGPSAnnotation.h */,\n\t\t\t\tDAFE051E21BD4A4C00F97A59 /* DoraemonDemoMockGPSAnnotation.m */,\n\t\t\t\tDAFE051F21BD4A4C00F97A59 /* DoraemonDemoMockGPSViewController.h */,\n\t\t\t\tDAFE052121BD4A4C00F97A59 /* DoraemonDemoMockGPSViewController.m */,\n\t\t\t);\n\t\t\tpath = MockGPS;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDAFE052221BD4A4C00F97A59 /* Performance */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDAFE052421BD4A4C00F97A59 /* DoraemonDemoPerformanceViewController.h */,\n\t\t\t\tDAFE052321BD4A4C00F97A59 /* DoraemonDemoPerformanceViewController.m */,\n\t\t\t);\n\t\t\tpath = Performance;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\tDAC8A8761FDE2C3B00F03E6F /* DoraemonKitDemo */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = DAC8A88D1FDE2C3B00F03E6F /* Build configuration list for PBXNativeTarget \"DoraemonKitDemo\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t4F099CCEC2895A53433BFD37 /* [CP] Check Pods Manifest.lock */,\n\t\t\t\tDAC8A8731FDE2C3B00F03E6F /* Sources */,\n\t\t\t\tDAC8A8741FDE2C3B00F03E6F /* Frameworks */,\n\t\t\t\tDAC8A8751FDE2C3B00F03E6F /* Resources */,\n\t\t\t\t4338E62332869AF074F6B38C /* [CP] Copy Pods Resources */,\n\t\t\t\t2B04C1F39AB80A38611A9343 /* [CP] Embed Pods Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = DoraemonKitDemo;\n\t\t\tproductName = DoraemonKitDemo;\n\t\t\tproductReference = DAC8A8771FDE2C3B00F03E6F /* DoraemonKitDemo.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\tDAC8A86F1FDE2C3B00F03E6F /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 1010;\n\t\t\t\tORGANIZATIONNAME = yixiang;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\tDAC8A8761FDE2C3B00F03E6F = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 9.1;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = DAC8A8721FDE2C3B00F03E6F /* Build configuration list for PBXProject \"DoraemonKitDemo\" */;\n\t\t\tcompatibilityVersion = \"Xcode 8.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t\t\"zh-Hans\",\n\t\t\t);\n\t\t\tmainGroup = DAC8A86E1FDE2C3B00F03E6F;\n\t\t\tproductRefGroup = DAC8A8781FDE2C3B00F03E6F /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\tDAC8A8761FDE2C3B00F03E6F /* DoraemonKitDemo */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\tDAC8A8751FDE2C3B00F03E6F /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t0AC7509C246A8EE100F87363 /* WebpDemo.webp in Resources */,\n\t\t\t\tDA63F1D321C8CF9500E0D9F5 /* huoying.mp4 in Resources */,\n\t\t\t\t0A6CF2762438BD3B00A2CBF4 /* DoraemonKitDemo.strings in Resources */,\n\t\t\t\tDA0C6F331FDEBC2E00F43588 /* LaunchScreen.storyboard in Resources */,\n\t\t\t\tDA4AD81E224E27DA006F767C /* doraemon.html in Resources */,\n\t\t\t\tDA4AD81A224E0EC8006F767C /* Doraemon.docx in Resources */,\n\t\t\t\tDA4AD81B224E0EC8006F767C /* Doraemon.pdf in Resources */,\n\t\t\t\tDAC8A8841FDE2C3B00F03E6F /* Assets.xcassets in Resources */,\n\t\t\t\tDA4AD81C224E0EC8006F767C /* Doraemon.xlsx in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t2B04C1F39AB80A38611A9343 /* [CP] Embed Pods Frameworks */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-DoraemonKitDemo/Pods-DoraemonKitDemo-frameworks.sh\",\n\t\t\t\t\"${PODS_ROOT}/../../DoraemonKit/Framework/DoraemonLoadAnalyze.framework\",\n\t\t\t);\n\t\t\tname = \"[CP] Embed Pods Frameworks\";\n\t\t\toutputPaths = (\n\t\t\t\t\"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DoraemonLoadAnalyze.framework\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-DoraemonKitDemo/Pods-DoraemonKitDemo-frameworks.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t4338E62332869AF074F6B38C /* [CP] Copy Pods Resources */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-DoraemonKitDemo/Pods-DoraemonKitDemo-resources.sh\",\n\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/DoraemonKit/DoraemonKit.bundle\",\n\t\t\t\t\"${PODS_ROOT}/GCDWebServer/GCDWebUploader/GCDWebUploader.bundle\",\n\t\t\t\t\"${PODS_ROOT}/WeexSDK/pre-build/native-bundle-main.js\",\n\t\t\t\t\"${PODS_ROOT}/WeexSDK/pre-build/weex-main-jsfm.js\",\n\t\t\t\t\"${PODS_ROOT}/WeexSDK/pre-build/weex-polyfill.js\",\n\t\t\t\t\"${PODS_ROOT}/WeexSDK/pre-build/weex-rax-api.js\",\n\t\t\t\t\"${PODS_ROOT}/WeexSDK/ios/sdk/WeexSDK/Resources/wx_load_error@3x.png\",\n\t\t\t\t\"${PODS_ROOT}/YYDebugDatabase/DebugDatabase/Web.bundle\",\n\t\t\t);\n\t\t\tname = \"[CP] Copy Pods Resources\";\n\t\t\toutputPaths = (\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/DoraemonKit.bundle\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GCDWebUploader.bundle\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/native-bundle-main.js\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/weex-main-jsfm.js\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/weex-polyfill.js\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/weex-rax-api.js\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/wx_load_error@3x.png\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Web.bundle\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-DoraemonKitDemo/Pods-DoraemonKitDemo-resources.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t4F099CCEC2895A53433BFD37 /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-DoraemonKitDemo-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"diff \\\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\\\" \\\"${PODS_ROOT}/Manifest.lock\\\" > /dev/null\\nif [ $? != 0 ] ; then\\n    # print error to STDERR\\n    echo \\\"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\\\" >&2\\n    exit 1\\nfi\\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\\necho \\\"SUCCESS\\\" > \\\"${SCRIPT_OUTPUT_FILE_0}\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\tDAC8A8731FDE2C3B00F03E6F /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tDAC8A88A1FDE2C3B00F03E6F /* main.m in Sources */,\n\t\t\t\t0A6C9CEA2423214600BAEF5E /* DoraemonDemoURLProtocol1.m in Sources */,\n\t\t\t\tDAFE052B21BD4A4D00F97A59 /* DoraemonDemoSanboxViewController.m in Sources */,\n\t\t\t\tDAFE052621BD4A4D00F97A59 /* DoraemonDemoHomeViewController.m in Sources */,\n\t\t\t\tDAFE052721BD4A4D00F97A59 /* DoraemonDemoNetViewController.m in Sources */,\n\t\t\t\tDAE7BFAD20205C20008D49D8 /* NSObject+Runtime.m in Sources */,\n\t\t\t\tDABBE3B521D3AA4D0070518E /* DoraemonWKWebViewViewController.m in Sources */,\n\t\t\t\t0977CCE0269F394100A6463E /* DoraemonDemoMultiControlViewController.m in Sources */,\n\t\t\t\t097F6DA326D53E5D00841403 /* DoraemonDemoMultiSlideView.m in Sources */,\n\t\t\t\tDAFE052D21BD4A4D00F97A59 /* DoraemonDemoMockGPSAnnotation.m in Sources */,\n\t\t\t\tDAFE052921BD4A4D00F97A59 /* DoraemonDemoCrashViewController.m in Sources */,\n\t\t\t\tDA0C6F3A1FDEBE3800F43588 /* TestPlugin.m in Sources */,\n\t\t\t\tDABBE3B221D3AA260070518E /* DoraemonUIWebViewViewController.m in Sources */,\n\t\t\t\t0A5399852349ED5B00C47CB3 /* DoraemonDemoMemoryLeakViewController.m in Sources */,\n\t\t\t\t0A5399882349EE7E00C47CB3 /* DoraemonDemoMemoryLeakModel.m in Sources */,\n\t\t\t\tDAFE052C21BD4A4D00F97A59 /* DoraemonDemoCommonViewController.m in Sources */,\n\t\t\t\t0AFBC55223BAE6F80099A8BD /* DoraemonDemoNetTableViewCell.m in Sources */,\n\t\t\t\t0AA262E1240F8AC400BF144F /* DoraemonDemoBaseViewController.m in Sources */,\n\t\t\t\t09B042D526A877ED00045D2A /* DoraemonDemoMultiConScreenEdgePanGesture.m in Sources */,\n\t\t\t\tDAFE052A21BD4A4D00F97A59 /* DoraemonDemoCrashMRCView.m in Sources */,\n\t\t\t\t0A6CF27C2438BDDD00A2CBF4 /* DoraemonKitDemoi18Util.m in Sources */,\n\t\t\t\t09B042C926A860B400045D2A /* DoraemonDemoMultiConPinchGesture.m in Sources */,\n\t\t\t\t09B042CD26A8760E00045D2A /* DoraemonDemoMultiConRotationGesture.m in Sources */,\n\t\t\t\tDAC3FA7622B8DD7400871E5C /* DoraemonDemoImageViewController.m in Sources */,\n\t\t\t\t09B042C526A8585500045D2A /* DoraemonDemoMultiConLongPressGesture.m in Sources */,\n\t\t\t\t09B042D926A8781A00045D2A /* DoraemonDemoMultiConTapGesture.m in Sources */,\n\t\t\t\tDAC3FA7922B8E0EE00871E5C /* DoraemonDemoImageShowViewController.m in Sources */,\n\t\t\t\tDAC8A87C1FDE2C3B00F03E6F /* DoKitAppDelegate.m in Sources */,\n\t\t\t\tDAFE052821BD4A4D00F97A59 /* DoraemonDemoLoggerViewController.m in Sources */,\n\t\t\t\tDAFE052E21BD4A4D00F97A59 /* DoraemonDemoMockGPSViewController.m in Sources */,\n\t\t\t\tDAFE052F21BD4A4D00F97A59 /* DoraemonDemoPerformanceViewController.m in Sources */,\n\t\t\t\t09B042D126A8777900045D2A /* DoraemonDemoMultiConSwipeGesture.m in Sources */,\n\t\t\t\t0A6C9CED2423219400BAEF5E /* DoraemonDemoURLProtocol2.m in Sources */,\n\t\t\t\t0A53998B2349EE9500C47CB3 /* DoraemonDemoMemoryLeakView.m in Sources */,\n\t\t\t\tDAFE052521BD4A4D00F97A59 /* DoraemonDemoUIViewController.m in Sources */,\n\t\t\t\tDAD46BDA1FE6B7AF00E3E7BC /* StartPlugin.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXVariantGroup section */\n\t\t0A6CF2782438BD3B00A2CBF4 /* DoraemonKitDemo.strings */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t0A6CF2772438BD3B00A2CBF4 /* zh-Hans */,\n\t\t\t\t0A6CF2792438BD4600A2CBF4 /* en */,\n\t\t\t);\n\t\t\tname = DoraemonKitDemo.strings;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDA0C6F311FDEBC2E00F43588 /* LaunchScreen.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\tDA0C6F321FDEBC2E00F43588 /* Base */,\n\t\t\t\t0A6CF2752438BD0100A2CBF4 /* zh-Hans */,\n\t\t\t);\n\t\t\tname = LaunchScreen.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXVariantGroup section */\n\n/* Begin XCBuildConfiguration section */\n\t\tDAC8A88B1FDE2C3B00F03E6F /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"iPhone Developer\";\n\t\t\t\tCOMPILER_INDEX_STORE_ENABLE = NO;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 9.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tDAC8A88C1FDE2C3B00F03E6F /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"iPhone Developer\";\n\t\t\t\tCOMPILER_INDEX_STORE_ENABLE = NO;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 9.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tDAC8A88E1FDE2C3B00F03E6F /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = DB18BA61EBA817FD46B5C41B /* Pods-DoraemonKitDemo.debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tDEVELOPMENT_TEAM = 7M2BQXS6D5;\n\t\t\t\tENABLE_BITCODE = YES;\n\t\t\t\tGCC_PRECOMPILE_PREFIX_HEADER = YES;\n\t\t\t\tGCC_PREFIX_HEADER = \"DoraemonKitDemo/DoraemonKitDemo-PrefixHeader.pch\";\n\t\t\t\tINFOPLIST_FILE = DoraemonKitDemo/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 9.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.didi.dokit.demo.test.a123;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tDAC8A88F1FDE2C3B00F03E6F /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = DBA563858BAABEC9BC885911 /* Pods-DoraemonKitDemo.release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEVELOPMENT_TEAM = 7M2BQXS6D5;\n\t\t\t\tENABLE_BITCODE = YES;\n\t\t\t\tGCC_PRECOMPILE_PREFIX_HEADER = YES;\n\t\t\t\tGCC_PREFIX_HEADER = \"DoraemonKitDemo/DoraemonKitDemo-PrefixHeader.pch\";\n\t\t\t\tINFOPLIST_FILE = DoraemonKitDemo/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 9.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.didi.dokit.demo;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\tDAC8A8721FDE2C3B00F03E6F /* Build configuration list for PBXProject \"DoraemonKitDemo\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tDAC8A88B1FDE2C3B00F03E6F /* Debug */,\n\t\t\t\tDAC8A88C1FDE2C3B00F03E6F /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tDAC8A88D1FDE2C3B00F03E6F /* Build configuration list for PBXNativeTarget \"DoraemonKitDemo\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tDAC8A88E1FDE2C3B00F03E6F /* Debug */,\n\t\t\t\tDAC8A88F1FDE2C3B00F03E6F /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = DAC8A86F1FDE2C3B00F03E6F /* Project object */;\n}\n"
  },
  {
    "path": "iOS/DoraemonKitDemo/Podfile",
    "content": "platform :ios, '9.0'\n\n# use_frameworks!\n# install! 'cocoapods', generate_multiple_pod_projects: true\n\ntarget 'DoraemonKitDemo' do\n  #pod 'WeexSDK', :git => 'git@github.com:apache/incubator-weex.git', :tag => '0.26.0'\n  #pod 'DoraemonKit', :subspecs => ['Core','WithLogger','WithGPS','WithLoad','WithWeex', 'WithDatabase', 'WithMLeaksFinder'],  :path => '../../'\n  #pod 'DoraemonKit', :subspecs => ['Core'],  :path => '../../'\n  pod 'DoraemonKit', :subspecs => ['Core', 'WithLogger', 'WithGPS', 'WithLoad', 'WithDatabase', 'WithMLeaksFinder', 'WithWeex' , 'WithMultiControl'], :path => '../../'\n  pod 'AFNetworking', '~> 4.0'\n  pod 'SDWebImage', '~> 5.11'\n  pod 'SocketRocket', '~> 0.5'\n  pod 'SDWebImageWebPCoder', '~> 0.8'\n  # pod 'FBRetainCycleDetector', '~> 0.1'\n  # FBRetainCycleDetector ~> 0.2 版本无法在 Xcode 12 编译通过\n  pod 'FBRetainCycleDetector', :git => 'git@github.com:facebook/FBRetainCycleDetector.git', :branch => 'master'\n  # pod 'fishhook', '~> 0.2'\n  pod 'YYDebugDatabase', '~> 2.1'\n  pod 'Masonry', '0.6.3'\n  pod 'JSONModel','~>1.4'\nend\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyze/DoraemonLoadAnalyze.h",
    "content": "//\n//  DoraemonLoadAnalyze.h\n//  DoraemonLoadAnalyze\n//\n//  Created by yixiang on 2019/1/2.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\n//在控制台打印load耗时\nFOUNDATION_EXPORT void printLoadAnalyzeInfo(void);\n\nextern NSMutableArray<NSDictionary *> *dlaLoadModels;\n\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyze/DoraemonLoadAnalyze.mm",
    "content": "//\n//  DoraemonLoadAnalyze.m\n//  DoraemonLoadAnalyze\n//\n//  Created by yixiang on 2019/1/2.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import \"DoraemonLoadAnalyze.h\"\n#import <Foundation/Foundation.h>\n#import <objc/runtime.h>\n#import <mach/mach.h>\n#import <QuartzCore/QuartzCore.h>\n#import <UIKit/UIKit.h>\n#include <mach-o/dyld.h>\n#include <mach-o/nlist.h>\n#include <mach-o/getsect.h>\n#include <string>\n\n#pragma mark -- C++ method list template\ntemplate <typename Element, typename List, uint32_t FlagMask>\nstruct entsize_list_tt {\n    uint32_t entsizeAndFlags;\n    uint32_t count;\n    Element first;\n    \n    uint32_t entsize() const {\n        return entsizeAndFlags & ~FlagMask;\n    }\n    uint32_t flags() const {\n        return entsizeAndFlags & FlagMask;\n    }\n    \n    Element& getOrEnd(uint32_t i) const {\n        assert(i <= count);\n        return *(Element *)((uint8_t *)&first + i*entsize());\n    }\n    Element& get(uint32_t i) const {\n        assert(i < count);\n        return getOrEnd(i);\n    }\n    \n    size_t byteSize() const {\n        return sizeof(*this) + (count-1)*entsize();\n    }\n    \n    List *duplicate() const {\n        return (List *)memdup(this, this->byteSize());\n    }\n    \n    struct iterator;\n    const iterator begin() const {\n        return iterator(*static_cast<const List*>(this), 0);\n    }\n    iterator begin() {\n        return iterator(*static_cast<const List*>(this), 0);\n    }\n    const iterator end() const {\n        return iterator(*static_cast<const List*>(this), count);\n    }\n    iterator end() {\n        return iterator(*static_cast<const List*>(this), count);\n    }\n    \n    struct iterator {\n        uint32_t entsize;\n        uint32_t index;  // keeping track of this saves a divide in operator-\n        Element* element;\n        \n        typedef std::random_access_iterator_tag iterator_category;\n        typedef Element value_type;\n        typedef ptrdiff_t difference_type;\n        typedef Element* pointer;\n        typedef Element& reference;\n        \n        iterator() { }\n        \n        iterator(const List& list, uint32_t start = 0)\n        : entsize(list.entsize())\n        , index(start)\n        , element(&list.getOrEnd(start))\n        { }\n        \n        const iterator& operator += (ptrdiff_t delta) {\n            element = (Element*)((uint8_t *)element + delta*entsize);\n            index += (int32_t)delta;\n            return *this;\n        }\n        const iterator& operator -= (ptrdiff_t delta) {\n            element = (Element*)((uint8_t *)element - delta*entsize);\n            index -= (int32_t)delta;\n            return *this;\n        }\n        const iterator operator + (ptrdiff_t delta) const {\n            return iterator(*this) += delta;\n        }\n        const iterator operator - (ptrdiff_t delta) const {\n            return iterator(*this) -= delta;\n        }\n        \n        iterator& operator ++ () { *this += 1; return *this; }\n        iterator& operator -- () { *this -= 1; return *this; }\n        iterator operator ++ (int) {\n            iterator result(*this); *this += 1; return result;\n        }\n        iterator operator -- (int) {\n            iterator result(*this); *this -= 1; return result;\n        }\n        \n        ptrdiff_t operator - (const iterator& rhs) const {\n            return (ptrdiff_t)this->index - (ptrdiff_t)rhs.index;\n        }\n        \n        Element& operator * () const { return *element; }\n        Element* operator -> () const { return element; }\n        \n        operator Element& () const { return *element; }\n        \n        bool operator == (const iterator& rhs) const {\n            return this->element == rhs.element;\n        }\n        bool operator != (const iterator& rhs) const {\n            return this->element != rhs.element;\n        }\n        \n        bool operator < (const iterator& rhs) const {\n            return this->element < rhs.element;\n        }\n        bool operator > (const iterator& rhs) const {\n            return this->element > rhs.element;\n        }\n    };\n};\n\nstruct method_t {\n    SEL name;\n    const char *types;\n    IMP imp;\n    \n    struct SortBySELAddress :\n    public std::binary_function<const method_t&,\n    const method_t&, bool>\n    {\n        bool operator() (const method_t& lhs,\n                         const method_t& rhs)\n        { return lhs.name < rhs.name; }\n    };\n};\n\nstruct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {\n};\n\ntypedef struct classref * classref_t;\n#ifndef __LP64__\ntypedef struct mach_header headerType;\n#else\ntypedef struct mach_header_64 headerType;\n#endif\n\nstruct category_t {\n    const char *name;\n    classref_t cls;\n    void *instanceMethods;\n    struct method_list_t *classMethods;\n    void *protocols;\n    void *instanceProperties;\n    void *_classProperties;\n    void *methodsForMeta(bool isMeta) {\n        if (isMeta) return classMethods;\n        else return instanceMethods;\n    }\n    void *propertiesForMeta(bool isMeta, struct header_info *hi);\n};\n\ntemplate <typename T>\nT* getDataSection(const headerType *mhdr, const char *sectname, size_t *outBytes, size_t *outCount) {\n    unsigned long byteCount = 0;\n    \n    \n    T* data = (T*)getsectiondata(mhdr, \"__DATA\", sectname, &byteCount);\n    if (!data) {\n        data = (T*)getsectiondata(mhdr, \"__DATA_CONST\", sectname, &byteCount);\n    }\n    if (!data) {\n        data = (T*)getsectiondata(mhdr, \"__DATA_DIRTY\", sectname, &byteCount);\n    }\n    if (outBytes) *outBytes = byteCount;\n    if (outCount) *outCount = byteCount / sizeof(T);\n    return data;\n}\n\n#define GETSECT(name, type, sectname)                                   \\\ntype *name(const headerType *mhdr, size_t *outCount) {              \\\nreturn getDataSection<type>(mhdr, sectname, nil, outCount);     \\\n}\n\nGETSECT(_getObjc2NonlazyClassList,    classref_t,      \"__objc_nlclslist\");\nGETSECT(_getObjc2NonlazyCategoryList, category_t *,    \"__objc_nlcatlist\");\n\n\nstatic NSMutableDictionary *loadMS;//To record the name of category\nstatic NSMutableDictionary *loadCS;//To void repeat analysis same class\n\nNSMutableArray<NSDictionary *> *dlaLoadModels;\n\nextern \"C\"{\n    category_t **nlcategarylist;\n    size_t categaryCount;\n}\n\n//  a IMP that returns a value\ntypedef id (* _IMP) (id, SEL, ...);\n// no return value\ntypedef void (* _VIMP) (id, SEL, ...);\n\n#define D_NEED_LOG\n\n#ifdef D_NEED_LOG\n#define DLALog(...) NSLog(__VA_ARGS__)\n#else\n#define DLALog(...)\n#endif\n\n#pragma mark -- static func define\nconst struct mach_header *get_target_image_header() {\n    \n    const uint32_t imageCount = _dyld_image_count();\n    const struct mach_header* target_image_header = 0;\n    \n    for(uint32_t iImg = 0; iImg < imageCount; iImg++) {\n        const char *image_name = _dyld_get_image_name(iImg);\n        NSBundle *mainBundle = [NSBundle mainBundle];\n        NSString *executableFile = [mainBundle objectForInfoDictionaryKey:(NSString*)kCFBundleExecutableKey];\n        const char *target_image_name = (executableFile).UTF8String;\n        if (strstr(image_name, target_image_name) != NULL) {\n            target_image_header = _dyld_get_image_header(iImg);\n            //printf(\"image_name = %s\\n\" , image_name);\n            break;\n        }\n    }\n    \n    return target_image_header;\n}\n\n#pragma mark -- lazy list\ncategory_t **get_non_lazy_categary_list(size_t *count) {\n    category_t **nlcatlist = NULL;\n    nlcatlist = _getObjc2NonlazyCategoryList((headerType *)get_target_image_header(), count);\n    return nlcatlist;\n}\n\nclassref_t *get_non_lazy_class_list(size_t *count) {\n    classref_t *nlclslist = NULL;\n    nlclslist = _getObjc2NonlazyClassList((headerType *)get_target_image_header(), count);\n    return nlclslist;\n}\n\nvoid swizzeLoadMethodInClasss(Class cls, BOOL isCategary){\n    unsigned int methodCount = 0;\n    Method * methods = class_copyMethodList(cls, &methodCount);\n    NSUInteger currentLoadIndex = 0;\n    for(unsigned int methodIndex = 0; methodIndex < methodCount; ++methodIndex){\n        Method method = methods[methodIndex];\n        //objc_method_description *des = method_getDescription(method);\n        std::string methodName(sel_getName(method_getName(method)));\n        if(methodName == \"load\"){\n            ++currentLoadIndex;\n            _VIMP load_IMP = (_VIMP)method_getImplementation(method);\n            method_setImplementation(method, imp_implementationWithBlock(^(id target, SEL action) {\n                //DLALog(@\"DLA >>>> before\");\n                CFTimeInterval begin = CACurrentMediaTime();\n                \n                load_IMP(target,action);\n                \n                CFTimeInterval end = CACurrentMediaTime();\n                if (!dlaLoadModels) {\n                    dlaLoadModels = [[NSMutableArray alloc] initWithCapacity:10];\n                }\n                \n                NSString *name = [loadMS valueForKey:[NSString stringWithFormat:@\"%p\",load_IMP]];\n                if (name && name.length > 0) {\n                }else{\n                    name = NSStringFromClass(cls);\n                }\n                [dlaLoadModels addObject:@{\n                                           @\"name\":name,\n                                           @\"cost\":@(1000 * (end - begin))\n                                           }];\n            }));\n        }\n    }\n    //DLALog(@\"%@\",@(currentLoadIndex));//多个category都实现了load的话会有多个\n}\n\nIMP _category_getLoadMethod(category_t *cat)\n{\n    const method_list_t *mlist;\n    mlist = cat->classMethods;\n    if (mlist) {\n        for (const auto& meth : *mlist) {\n            const char *name = (const char *)(void *)(meth.name);\n            if (0 == strcmp(name, \"load\")) {\n                return meth.imp;\n            }\n        }\n    }\n    return nil;\n}\n\nvoid printLoadAnalyzeInfo(void){\n    NSLog(@\"DLA >>>> all load cost info below :\");\n    NSLog(@\"\\n\");\n    \n    NSArray *testArr = dlaLoadModels;\n    \n    for(NSDictionary *costInfo in testArr){\n        NSString *name = costInfo[@\"name\"];\n        NSString *cost = costInfo[@\"cost\"];\n        NSLog(@\"%@ - %@ms\",name,cost);\n    }\n    NSLog(@\"\\n\");\n}\n\n@interface DoraemonLoadAnalyze : NSObject\n@end\n@implementation DoraemonLoadAnalyze\n+(void)load{\n    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];\n    BOOL needMethodUseTime = [defaults boolForKey:@\"doraemon_method_use_time_key\"];\n    if (!needMethodUseTime) {\n        return;\n    }\n    \n    CFTimeInterval begin = CACurrentMediaTime();\n    DLALog(@\"DLA >>>> DoraemonLoadAnalyze load start\");\n    if(!loadMS){\n        loadMS = [[NSMutableDictionary alloc]init];\n    }else{\n        [loadMS removeAllObjects];\n    }\n\n    if(!loadCS){\n        loadCS = [[NSMutableDictionary alloc]init];\n    }else{\n        [loadCS removeAllObjects];\n    }\n\n    nlcategarylist = get_non_lazy_categary_list(&categaryCount);\n    for (int i = 0; i < categaryCount; i++) {\n        Class cls = (Class)CFBridgingRelease(nlcategarylist[i]->cls);\n        cls = object_getClass(cls);\n        NSString *name = [NSString stringWithCString:nlcategarylist[i]->name encoding:NSUTF8StringEncoding];\n        category_t *cat = nlcategarylist[i];\n        _VIMP load_IMP = (_VIMP)_category_getLoadMethod(cat);\n        [loadMS addEntriesFromDictionary:@{[NSString stringWithFormat:@\"%p\",load_IMP]:[NSString stringWithFormat:@\"%@(%@)\",cls,name]}];\n        [loadCS addEntriesFromDictionary:@{[NSString stringWithFormat:@\"%@\",cls]:cls}];\n\n        //DLALog(@\"DLA >>>> category_t:%@ (%@)\",cls,name);\n        //swizzeLoadMethodInClasss(cls, YES);\n    }\n    \n    for (NSString *key in loadCS) {\n        Class cls = loadCS[key];\n        swizzeLoadMethodInClasss(cls, YES);\n    }\n\n    size_t count = 0;\n    classref_t *nlclslist = get_non_lazy_class_list(&count);\n\n    for (int i = 0; i < count; i++) {\n        //DLALog(@\"DLA >>>> %p\",nlclslist[i]);\n        classref_t nlcls = nlclslist[i];\n        Class cls = (__bridge Class)nlcls;\n        // ios deployment target 8.0有一个问题 '__ARCLite__'这个Class有点特殊，这个类也实现了load\n        if ([@\"__ARCLite__\" isEqualToString:NSStringFromClass(cls)]) {\n            continue;\n        }\n        cls = (Class)CFBridgingRelease(nlclslist[i]);\n        cls = object_getClass(cls);\n        //DLALog(@\"DLA >>>> classref_t:%@\",cls);\n\n        if(![[loadCS allKeys] containsObject:[NSString stringWithFormat:@\"%@\",cls]])\n        {\n            swizzeLoadMethodInClasss(cls, NO);\n        }\n    }\n\n    CFTimeInterval end = CACurrentMediaTime();\n\n    DLALog(@\"DLA >>>> DoraemonLoadAnalyze load end , costs : %@\",[NSString stringWithFormat:@\"%@ - %@ms\",NSStringFromClass([self class]), @(1000 * (end - begin))]);\n}\n@end\n\n\n\n\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyze/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>FMWK</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo/AppDelegate.h",
    "content": "//\n//  AppDelegate.h\n//  DoraemonLoadAnalyzeDemo\n//\n//  Created by yixiang on 2019/1/22.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface AppDelegate : UIResponder <UIApplicationDelegate>\n\n@property (strong, nonatomic) UIWindow *window;\n\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo/AppDelegate.m",
    "content": "//\n//  AppDelegate.m\n//  DoraemonLoadAnalyzeDemo\n//\n//  Created by yixiang on 2019/1/22.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import \"AppDelegate.h\"\n#import \"DoraemonLoadAnalyze.h\"\n\n@interface AppDelegate ()\n\n@end\n\n@implementation AppDelegate\n\n\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {\n    // Override point for customization after application launch.\n    printLoadAnalyzeInfo();\n    return YES;\n}\n\n\n- (void)applicationWillResignActive:(UIApplication *)application {\n    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.\n    // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.\n}\n\n\n- (void)applicationDidEnterBackground:(UIApplication *)application {\n    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.\n    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.\n}\n\n\n- (void)applicationWillEnterForeground:(UIApplication *)application {\n    // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.\n}\n\n\n- (void)applicationDidBecomeActive:(UIApplication *)application {\n    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.\n}\n\n\n- (void)applicationWillTerminate:(UIApplication *)application {\n    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"20x20\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"20x20\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"40x40\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"40x40\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"60x60\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"60x60\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"20x20\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"20x20\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"40x40\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"40x40\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"76x76\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"76x76\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"83.5x83.5\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"ios-marketing\",\n      \"size\" : \"1024x1024\",\n      \"scale\" : \"1x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo/Base.lproj/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"14460.31\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" launchScreen=\"YES\" useTraitCollections=\"YES\" colorMatched=\"YES\" initialViewController=\"01J-lp-oVM\">\n    <device id=\"retina4_7\" orientation=\"portrait\">\n        <adaptation id=\"fullscreen\"/>\n    </device>\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"14460.20\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"EHf-IW-A2E\">\n            <objects>\n                <viewController id=\"01J-lp-oVM\" sceneMemberID=\"viewController\">\n                    <layoutGuides>\n                        <viewControllerLayoutGuide type=\"top\" id=\"lZH-Ou-brt\"/>\n                        <viewControllerLayoutGuide type=\"bottom\" id=\"zuT-41-HbV\"/>\n                    </layoutGuides>\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"Ze5-6b-2t3\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"375\" height=\"667\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <color key=\"backgroundColor\" red=\"1\" green=\"1\" blue=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"sRGB\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"iYj-Kq-Ea1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"53\" y=\"375\"/>\n        </scene>\n    </scenes>\n</document>\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo/Base.lproj/Main.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"14460.31\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" useTraitCollections=\"YES\" colorMatched=\"YES\" initialViewController=\"BYZ-38-t0r\">\n    <device id=\"retina4_7\" orientation=\"portrait\">\n        <adaptation id=\"fullscreen\"/>\n    </device>\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"14460.20\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"tne-QT-ifu\">\n            <objects>\n                <viewController id=\"BYZ-38-t0r\" customClass=\"ViewController\" sceneMemberID=\"viewController\">\n                    <layoutGuides>\n                        <viewControllerLayoutGuide type=\"top\" id=\"0WT-pM-i5t\"/>\n                        <viewControllerLayoutGuide type=\"bottom\" id=\"ix4-xB-zTx\"/>\n                    </layoutGuides>\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"8bC-Xf-vdC\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"375\" height=\"667\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <color key=\"backgroundColor\" red=\"1\" green=\"1\" blue=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"sRGB\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"dkx-z0-nzr\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n        </scene>\n    </scenes>\n</document>\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIMainStoryboardFile</key>\n\t<string>Main</string>\n\t<key>UIRequiredDeviceCapabilities</key>\n\t<array>\n\t\t<string>armv7</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo/ViewController+A.h",
    "content": "//\n//  ViewController+A.h\n//  DoraemonLoadAnalyzeDemo\n//\n//  Created by yixiang on 2019/1/22.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import \"ViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface ViewController (A)\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo/ViewController+A.m",
    "content": "//\n//  ViewController+A.m\n//  DoraemonLoadAnalyzeDemo\n//\n//  Created by yixiang on 2019/1/22.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import \"ViewController+A.h\"\n\n@implementation ViewController (A)\n\n+ (void)load{\n    NSLog(@\"ViewController(A) call load\");\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo/ViewController+B.h",
    "content": "//\n//  ViewController+B.h\n//  DoraemonLoadAnalyzeDemo\n//\n//  Created by yixiang on 2019/1/22.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import \"ViewController.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface ViewController (B)\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo/ViewController+B.m",
    "content": "//\n//  ViewController+B.m\n//  DoraemonLoadAnalyzeDemo\n//\n//  Created by yixiang on 2019/1/22.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import \"ViewController+B.h\"\n\n@implementation ViewController (B)\n\n+ (void)load{\n    NSLog(@\"ViewController(B) call load\");\n}\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo/ViewController.h",
    "content": "//\n//  ViewController.h\n//  DoraemonLoadAnalyzeDemo\n//\n//  Created by yixiang on 2019/1/22.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface ViewController : UIViewController\n\n\n@end\n\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo/ViewController.m",
    "content": "//\n//  ViewController.m\n//  DoraemonLoadAnalyzeDemo\n//\n//  Created by yixiang on 2019/1/22.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import \"ViewController.h\"\n\n@interface ViewController ()\n\n@end\n\n@implementation ViewController\n\n+ (void)load{\n    NSLog(@\"ViewController call load\");\n}\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    // Do any additional setup after loading the view, typically from a nib.\n}\n\n\n@end\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo/main.m",
    "content": "//\n//  main.m\n//  DoraemonLoadAnalyzeDemo\n//\n//  Created by yixiang on 2019/1/22.\n//  Copyright © 2019年 yixiang. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"AppDelegate.h\"\n\nint main(int argc, char * argv[]) {\n    @autoreleasepool {\n        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));\n    }\n}\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 50;\n\tobjects = {\n\n/* Begin PBXAggregateTarget section */\n\t\t38EA46FA221A586400EBA62F /* UniversalFramework */ = {\n\t\t\tisa = PBXAggregateTarget;\n\t\t\tbuildConfigurationList = 38EA46FD221A586400EBA62F /* Build configuration list for PBXAggregateTarget \"UniversalFramework\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t38EA46FE221A587200EBA62F /* ShellScript */,\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = UniversalFramework;\n\t\t\tproductName = UniversalFramework;\n\t\t};\n/* End PBXAggregateTarget section */\n\n/* Begin PBXBuildFile section */\n\t\tDA1E1D9421F73061003E215F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1E1D9321F73061003E215F /* AppDelegate.m */; };\n\t\tDA1E1D9721F73061003E215F /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1E1D9621F73061003E215F /* ViewController.m */; };\n\t\tDA1E1D9A21F73061003E215F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DA1E1D9821F73061003E215F /* Main.storyboard */; };\n\t\tDA1E1D9C21F73064003E215F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA1E1D9B21F73064003E215F /* Assets.xcassets */; };\n\t\tDA1E1D9F21F73064003E215F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DA1E1D9D21F73064003E215F /* LaunchScreen.storyboard */; };\n\t\tDA1E1DA221F73064003E215F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1E1DA121F73064003E215F /* main.m */; };\n\t\tDA1E1DB421F7310C003E215F /* DoraemonLoadAnalyze.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA1E1DAD21F7310C003E215F /* DoraemonLoadAnalyze.framework */; };\n\t\tDA1E1DB521F7310C003E215F /* DoraemonLoadAnalyze.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DA1E1DAD21F7310C003E215F /* DoraemonLoadAnalyze.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\tDA1E1DBC21F73139003E215F /* DoraemonLoadAnalyze.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA1E1DBA21F73139003E215F /* DoraemonLoadAnalyze.mm */; };\n\t\tDA1E1DBD21F73139003E215F /* DoraemonLoadAnalyze.h in Headers */ = {isa = PBXBuildFile; fileRef = DA1E1DBB21F73139003E215F /* DoraemonLoadAnalyze.h */; settings = {ATTRIBUTES = (Public, ); }; };\n\t\tDA1E1DC021F732CA003E215F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA1E1DBF21F732CA003E215F /* Foundation.framework */; };\n\t\tDA1E1DC221F732D2003E215F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA1E1DC121F732D2003E215F /* UIKit.framework */; };\n\t\tDA63F7FB21F735B200574365 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA63F7FA21F735B200574365 /* QuartzCore.framework */; };\n\t\tDA63F7FE21F73A3500574365 /* ViewController+A.m in Sources */ = {isa = PBXBuildFile; fileRef = DA63F7FD21F73A3500574365 /* ViewController+A.m */; };\n\t\tDA63F80121F73A7300574365 /* ViewController+B.m in Sources */ = {isa = PBXBuildFile; fileRef = DA63F80021F73A7300574365 /* ViewController+B.m */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\tDA1E1DB221F7310C003E215F /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = DA1E1D8721F73061003E215F /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = DA1E1DAC21F7310C003E215F;\n\t\t\tremoteInfo = DoraemonLoadAnalyze;\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXCopyFilesBuildPhase section */\n\t\tDA1E1DB921F7310C003E215F /* Embed Frameworks */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 10;\n\t\t\tfiles = (\n\t\t\t\tDA1E1DB521F7310C003E215F /* DoraemonLoadAnalyze.framework in Embed Frameworks */,\n\t\t\t);\n\t\t\tname = \"Embed Frameworks\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXCopyFilesBuildPhase section */\n\n/* Begin PBXFileReference section */\n\t\tDA1E1D8F21F73061003E215F /* DoraemonLoadAnalyzeDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DoraemonLoadAnalyzeDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tDA1E1D9221F73061003E215F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = \"<group>\"; };\n\t\tDA1E1D9321F73061003E215F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = \"<group>\"; };\n\t\tDA1E1D9521F73061003E215F /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = \"<group>\"; };\n\t\tDA1E1D9621F73061003E215F /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = \"<group>\"; };\n\t\tDA1E1D9921F73061003E215F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = \"<group>\"; };\n\t\tDA1E1D9B21F73064003E215F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\tDA1E1D9E21F73064003E215F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\tDA1E1DA021F73064003E215F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\tDA1E1DA121F73064003E215F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = \"<group>\"; };\n\t\tDA1E1DAD21F7310C003E215F /* DoraemonLoadAnalyze.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DoraemonLoadAnalyze.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tDA1E1DB021F7310C003E215F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\tDA1E1DBA21F73139003E215F /* DoraemonLoadAnalyze.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DoraemonLoadAnalyze.mm; sourceTree = \"<group>\"; };\n\t\tDA1E1DBB21F73139003E215F /* DoraemonLoadAnalyze.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoraemonLoadAnalyze.h; sourceTree = \"<group>\"; };\n\t\tDA1E1DBF21F732CA003E215F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };\n\t\tDA1E1DC121F732D2003E215F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };\n\t\tDA1E1DC321F732EF003E215F /* libc++.1.tbd */ = {isa = PBXFileReference; lastKnownFileType = \"sourcecode.text-based-dylib-definition\"; name = \"libc++.1.tbd\"; path = \"usr/lib/libc++.1.tbd\"; sourceTree = SDKROOT; };\n\t\tDA63F7FA21F735B200574365 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };\n\t\tDA63F7FC21F73A3500574365 /* ViewController+A.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = \"ViewController+A.h\"; sourceTree = \"<group>\"; };\n\t\tDA63F7FD21F73A3500574365 /* ViewController+A.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = \"ViewController+A.m\"; sourceTree = \"<group>\"; };\n\t\tDA63F7FF21F73A7300574365 /* ViewController+B.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = \"ViewController+B.h\"; sourceTree = \"<group>\"; };\n\t\tDA63F80021F73A7300574365 /* ViewController+B.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = \"ViewController+B.m\"; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\tDA1E1D8C21F73061003E215F /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tDA1E1DB421F7310C003E215F /* DoraemonLoadAnalyze.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tDA1E1DAA21F7310C003E215F /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tDA63F7FB21F735B200574365 /* QuartzCore.framework in Frameworks */,\n\t\t\t\tDA1E1DC221F732D2003E215F /* UIKit.framework in Frameworks */,\n\t\t\t\tDA1E1DC021F732CA003E215F /* Foundation.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\tDA1E1D8621F73061003E215F = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDA1E1D9121F73061003E215F /* DoraemonLoadAnalyzeDemo */,\n\t\t\t\tDA1E1DAE21F7310C003E215F /* DoraemonLoadAnalyze */,\n\t\t\t\tDA1E1D9021F73061003E215F /* Products */,\n\t\t\t\tDA1E1DBE21F732CA003E215F /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDA1E1D9021F73061003E215F /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDA1E1D8F21F73061003E215F /* DoraemonLoadAnalyzeDemo.app */,\n\t\t\t\tDA1E1DAD21F7310C003E215F /* DoraemonLoadAnalyze.framework */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDA1E1D9121F73061003E215F /* DoraemonLoadAnalyzeDemo */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDA1E1D9221F73061003E215F /* AppDelegate.h */,\n\t\t\t\tDA1E1D9321F73061003E215F /* AppDelegate.m */,\n\t\t\t\tDA1E1D9521F73061003E215F /* ViewController.h */,\n\t\t\t\tDA1E1D9621F73061003E215F /* ViewController.m */,\n\t\t\t\tDA1E1D9821F73061003E215F /* Main.storyboard */,\n\t\t\t\tDA1E1D9B21F73064003E215F /* Assets.xcassets */,\n\t\t\t\tDA1E1D9D21F73064003E215F /* LaunchScreen.storyboard */,\n\t\t\t\tDA1E1DA021F73064003E215F /* Info.plist */,\n\t\t\t\tDA1E1DA121F73064003E215F /* main.m */,\n\t\t\t\tDA63F7FC21F73A3500574365 /* ViewController+A.h */,\n\t\t\t\tDA63F7FD21F73A3500574365 /* ViewController+A.m */,\n\t\t\t\tDA63F7FF21F73A7300574365 /* ViewController+B.h */,\n\t\t\t\tDA63F80021F73A7300574365 /* ViewController+B.m */,\n\t\t\t);\n\t\t\tpath = DoraemonLoadAnalyzeDemo;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDA1E1DAE21F7310C003E215F /* DoraemonLoadAnalyze */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDA1E1DBB21F73139003E215F /* DoraemonLoadAnalyze.h */,\n\t\t\t\tDA1E1DBA21F73139003E215F /* DoraemonLoadAnalyze.mm */,\n\t\t\t\tDA1E1DB021F7310C003E215F /* Info.plist */,\n\t\t\t);\n\t\t\tpath = DoraemonLoadAnalyze;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDA1E1DBE21F732CA003E215F /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDA63F7FA21F735B200574365 /* QuartzCore.framework */,\n\t\t\t\tDA1E1DC321F732EF003E215F /* libc++.1.tbd */,\n\t\t\t\tDA1E1DC121F732D2003E215F /* UIKit.framework */,\n\t\t\t\tDA1E1DBF21F732CA003E215F /* Foundation.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXHeadersBuildPhase section */\n\t\tDA1E1DA821F7310C003E215F /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tDA1E1DBD21F73139003E215F /* DoraemonLoadAnalyze.h in Headers */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXHeadersBuildPhase section */\n\n/* Begin PBXNativeTarget section */\n\t\tDA1E1D8E21F73061003E215F /* DoraemonLoadAnalyzeDemo */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = DA1E1DA521F73064003E215F /* Build configuration list for PBXNativeTarget \"DoraemonLoadAnalyzeDemo\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tDA1E1D8B21F73061003E215F /* Sources */,\n\t\t\t\tDA1E1D8C21F73061003E215F /* Frameworks */,\n\t\t\t\tDA1E1D8D21F73061003E215F /* Resources */,\n\t\t\t\tDA1E1DB921F7310C003E215F /* Embed Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\tDA1E1DB321F7310C003E215F /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = DoraemonLoadAnalyzeDemo;\n\t\t\tproductName = DoraemonLoadAnalyzeDemo;\n\t\t\tproductReference = DA1E1D8F21F73061003E215F /* DoraemonLoadAnalyzeDemo.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n\t\tDA1E1DAC21F7310C003E215F /* DoraemonLoadAnalyze */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = DA1E1DB621F7310C003E215F /* Build configuration list for PBXNativeTarget \"DoraemonLoadAnalyze\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tDA1E1DA821F7310C003E215F /* Headers */,\n\t\t\t\tDA1E1DA921F7310C003E215F /* Sources */,\n\t\t\t\tDA1E1DAA21F7310C003E215F /* Frameworks */,\n\t\t\t\tDA1E1DAB21F7310C003E215F /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = DoraemonLoadAnalyze;\n\t\t\tproductName = DoraemonLoadAnalyze;\n\t\t\tproductReference = DA1E1DAD21F7310C003E215F /* DoraemonLoadAnalyze.framework */;\n\t\t\tproductType = \"com.apple.product-type.framework\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\tDA1E1D8721F73061003E215F /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 1010;\n\t\t\t\tORGANIZATIONNAME = yixiang;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t38EA46FA221A586400EBA62F = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 10.1;\n\t\t\t\t\t};\n\t\t\t\t\tDA1E1D8E21F73061003E215F = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 10.1;\n\t\t\t\t\t};\n\t\t\t\t\tDA1E1DAC21F7310C003E215F = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 10.1;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = DA1E1D8A21F73061003E215F /* Build configuration list for PBXProject \"DoraemonLoadAnalyzeDemo\" */;\n\t\t\tcompatibilityVersion = \"Xcode 9.3\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = DA1E1D8621F73061003E215F;\n\t\t\tproductRefGroup = DA1E1D9021F73061003E215F /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\tDA1E1D8E21F73061003E215F /* DoraemonLoadAnalyzeDemo */,\n\t\t\t\tDA1E1DAC21F7310C003E215F /* DoraemonLoadAnalyze */,\n\t\t\t\t38EA46FA221A586400EBA62F /* UniversalFramework */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\tDA1E1D8D21F73061003E215F /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tDA1E1D9F21F73064003E215F /* LaunchScreen.storyboard in Resources */,\n\t\t\t\tDA1E1D9C21F73064003E215F /* Assets.xcassets in Resources */,\n\t\t\t\tDA1E1D9A21F73061003E215F /* Main.storyboard in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tDA1E1DAB21F7310C003E215F /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t38EA46FE221A587200EBA62F /* ShellScript */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"######################\\n# Options\\n######################\\n\\n#verbose\\nset -x\\n\\nREVEAL_ARCHIVE_IN_FINDER=true\\n\\nFRAMEWORK_NAME=\\\"DoraemonLoadAnalyze\\\"\\n\\nSIMULATOR_LIBRARY_PATH=\\\"${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${FRAMEWORK_NAME}.framework\\\"\\n\\nDEVICE_LIBRARY_PATH=\\\"${BUILD_DIR}/${CONFIGURATION}-iphoneos/${FRAMEWORK_NAME}.framework\\\"\\n\\nUNIVERSAL_LIBRARY_DIR=\\\"${BUILD_DIR}/${CONFIGURATION}-iphoneuniversal\\\"\\n\\nFRAMEWORK=\\\"${UNIVERSAL_LIBRARY_DIR}/${FRAMEWORK_NAME}.framework\\\"\\n\\n\\n######################\\n# Build Frameworks\\n######################\\n# Build for simulator\\nxcrun xcodebuild BITCODE_GENERATION_MODE=bitcode OTHER_CFLAGS=\\\"-fembed-bitcode\\\" -target ${FRAMEWORK_NAME} -sdk iphonesimulator -configuration ${CONFIGURATION} ARCHS=\\\"i386 x86_64\\\" ONLY_ACTIVE_ARCH=NO clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphonesimulator\\n\\n# Build for device\\nxcrun xcodebuild BITCODE_GENERATION_MODE=bitcode OTHER_CFLAGS=\\\"-fembed-bitcode\\\" -target ${FRAMEWORK_NAME} -sdk iphoneos -configuration ${CONFIGURATION} ARCHS=\\\"armv7 armv7s arm64\\\" ONLY_ACTIVE_ARCH=NO clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphoneos\\n\\n######################\\n# Create directory for universal\\n######################\\n\\nrm -rf \\\"${UNIVERSAL_LIBRARY_DIR}\\\"\\n\\nmkdir \\\"${UNIVERSAL_LIBRARY_DIR}\\\"\\n\\nmkdir \\\"${FRAMEWORK}\\\"\\n\\n\\n######################\\n# Copy files Framework\\n######################\\n\\ncp -r \\\"${DEVICE_LIBRARY_PATH}/.\\\" \\\"${FRAMEWORK}\\\"\\n\\n\\n######################\\n# Make an universal binary\\n######################\\n\\nlipo \\\"${SIMULATOR_LIBRARY_PATH}/${FRAMEWORK_NAME}\\\" \\\"${DEVICE_LIBRARY_PATH}/${FRAMEWORK_NAME}\\\" -create -output \\\"${FRAMEWORK}/${FRAMEWORK_NAME}\\\" | echo\\n\\n# For Swift framework, Swiftmodule needs to be copied in the universal framework\\nif [ -d \\\"${SIMULATOR_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/\\\" ]; then\\ncp -f ${SIMULATOR_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/* \\\"${FRAMEWORK}/Modules/${FRAMEWORK_NAME}.swiftmodule/\\\" | echo\\nfi\\n\\nif [ -d \\\"${DEVICE_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/\\\" ]; then\\ncp -f ${DEVICE_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/* \\\"${FRAMEWORK}/Modules/${FRAMEWORK_NAME}.swiftmodule/\\\" | echo\\nfi\\n\\n######################\\n# On Release, copy the result to release directory\\n######################\\nOUTPUT_DIR=\\\"${PROJECT_DIR}/build/${FRAMEWORK_NAME}-${CONFIGURATION}-iphoneuniversal/\\\"\\n\\nrm -rf \\\"$OUTPUT_DIR\\\"\\nmkdir -p \\\"$OUTPUT_DIR\\\"\\n\\ncp -r \\\"${FRAMEWORK}\\\" \\\"$OUTPUT_DIR\\\"\\n\\nif [ ${REVEAL_ARCHIVE_IN_FINDER} = true ]; then\\nopen \\\"${OUTPUT_DIR}/\\\"\\nfi\\n\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\tDA1E1D8B21F73061003E215F /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tDA63F80121F73A7300574365 /* ViewController+B.m in Sources */,\n\t\t\t\tDA63F7FE21F73A3500574365 /* ViewController+A.m in Sources */,\n\t\t\t\tDA1E1D9721F73061003E215F /* ViewController.m in Sources */,\n\t\t\t\tDA1E1DA221F73064003E215F /* main.m in Sources */,\n\t\t\t\tDA1E1D9421F73061003E215F /* AppDelegate.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tDA1E1DA921F7310C003E215F /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tDA1E1DBC21F73139003E215F /* DoraemonLoadAnalyze.mm in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\tDA1E1DB321F7310C003E215F /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = DA1E1DAC21F7310C003E215F /* DoraemonLoadAnalyze */;\n\t\t\ttargetProxy = DA1E1DB221F7310C003E215F /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin PBXVariantGroup section */\n\t\tDA1E1D9821F73061003E215F /* Main.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\tDA1E1D9921F73061003E215F /* Base */,\n\t\t\t);\n\t\t\tname = Main.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDA1E1D9D21F73064003E215F /* LaunchScreen.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\tDA1E1D9E21F73064003E215F /* Base */,\n\t\t\t);\n\t\t\tname = LaunchScreen.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXVariantGroup section */\n\n/* Begin XCBuildConfiguration section */\n\t\t38EA46FB221A586400EBA62F /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t38EA46FC221A586400EBA62F /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tDA1E1DA321F73064003E215F /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tDA1E1DA421F73064003E215F /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tDA1E1DA621F73064003E215F /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEVELOPMENT_TEAM = RSV7DQ57L7;\n\t\t\t\tINFOPLIST_FILE = DoraemonLoadAnalyzeDemo/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = \"-ObjC\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.javer.yi.DoraemonLoadAnalyzeDemoXXXX;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tDA1E1DA721F73064003E215F /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEVELOPMENT_TEAM = RSV7DQ57L7;\n\t\t\t\tINFOPLIST_FILE = DoraemonLoadAnalyzeDemo/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = \"-ObjC\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.javer.yi.DoraemonLoadAnalyzeDemoXXXX;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tDA1E1DB721F7310C003E215F /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_IDENTITY = \"\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDEVELOPMENT_TEAM = 2M632FEG3H;\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tEXCLUDED_ARCHS = arm64;\n\t\t\t\tINFOPLIST_FILE = DoraemonLoadAnalyze/Info.plist;\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = \"\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.javer.yi.DoraemonLoadAnalyze;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME:c99extidentifier)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t\tVERSION_INFO_PREFIX = \"\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tDA1E1DB821F7310C003E215F /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_IDENTITY = \"\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDEVELOPMENT_TEAM = 2M632FEG3H;\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tEXCLUDED_ARCHS = arm64;\n\t\t\t\tINFOPLIST_FILE = DoraemonLoadAnalyze/Info.plist;\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = \"\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.javer.yi.DoraemonLoadAnalyze;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME:c99extidentifier)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t\tVERSION_INFO_PREFIX = \"\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t38EA46FD221A586400EBA62F /* Build configuration list for PBXAggregateTarget \"UniversalFramework\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t38EA46FB221A586400EBA62F /* Debug */,\n\t\t\t\t38EA46FC221A586400EBA62F /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tDA1E1D8A21F73061003E215F /* Build configuration list for PBXProject \"DoraemonLoadAnalyzeDemo\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tDA1E1DA321F73064003E215F /* Debug */,\n\t\t\t\tDA1E1DA421F73064003E215F /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tDA1E1DA521F73064003E215F /* Build configuration list for PBXNativeTarget \"DoraemonLoadAnalyzeDemo\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tDA1E1DA621F73064003E215F /* Debug */,\n\t\t\t\tDA1E1DA721F73064003E215F /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tDA1E1DB621F7310C003E215F /* Build configuration list for PBXNativeTarget \"DoraemonLoadAnalyze\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tDA1E1DB721F7310C003E215F /* Debug */,\n\t\t\t\tDA1E1DB821F7310C003E215F /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = DA1E1D8721F73061003E215F /* Project object */;\n}\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo.xcodeproj/xcshareddata/xcschemes/DoraemonLoadAnalyze.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1210\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"DA1E1DAC21F7310C003E215F\"\n               BuildableName = \"DoraemonLoadAnalyze.framework\"\n               BlueprintName = \"DoraemonLoadAnalyze\"\n               ReferencedContainer = \"container:DoraemonLoadAnalyzeDemo.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Release\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"DA1E1DAC21F7310C003E215F\"\n            BuildableName = \"DoraemonLoadAnalyze.framework\"\n            BlueprintName = \"DoraemonLoadAnalyze\"\n            ReferencedContainer = \"container:DoraemonLoadAnalyzeDemo.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/DoraemonLoadAnalyzeDemo.xcodeproj/xcshareddata/xcschemes/UniversalFramework.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1010\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"38EA46FA221A586400EBA62F\"\n               BuildableName = \"UniversalFramework\"\n               BlueprintName = \"UniversalFramework\"\n               ReferencedContainer = \"container:DoraemonLoadAnalyzeDemo.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n      </Testables>\n      <AdditionalOptions>\n      </AdditionalOptions>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Release\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"38EA46FA221A586400EBA62F\"\n            BuildableName = \"UniversalFramework\"\n            BlueprintName = \"UniversalFramework\"\n            ReferencedContainer = \"container:DoraemonLoadAnalyzeDemo.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n      <AdditionalOptions>\n      </AdditionalOptions>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"38EA46FA221A586400EBA62F\"\n            BuildableName = \"UniversalFramework\"\n            BlueprintName = \"UniversalFramework\"\n            ReferencedContainer = \"container:DoraemonLoadAnalyzeDemo.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "iOS/DoraemonLoadAnalyzeDemo/README.md",
    "content": "#### 感谢QQ群@花裤衩 同学提供该功能的技术实现，他的github地址为https://github.com/huakucha/TTAnalyzeLoadTime 。\n\n#### 注意点1：调式Demo的时候记得把DoraemonLoadAnalyze中load方法的判断needMethodUseTime的代码去掉。\n\n\n\n\n"
  },
  {
    "path": "iOS/LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "iOS/Podfile",
    "content": "# Uncomment the next line to define a global platform for your project\nplatform :ios, '9.0'\n\ntarget 'Demo' do\n  # Comment the next line if you don't want to use dynamic frameworks\n  # use_frameworks!\n\n  # Pods for Demo\n  pod 'DoraemonKit', :path => '..', :subspecs => ['Foundation', 'Core', 'WithMultiControl']\n\nend\n"
  },
  {
    "path": "iOS/README.md",
    "content": "## DoraemonKit如何使用\n\n### 一、集成方式\n\n#### 1.1: cocoapods依赖\n\n```\n    pod 'DoraemonKit/Core', '~> 3.0.4', :configurations => ['Debug'] //必选\n    pod 'DoraemonKit/WithLogger', '~> 3.0.4', :configurations => ['Debug'] //可选\n    pod 'DoraemonKit/WithGPS', '~> 3.0.4', :configurations => ['Debug'] //可选\n    pod 'DoraemonKit/WithLoad', '~> 3.0.4', :configurations => ['Debug'] //可选\n    pod 'DoraemonKit/WithWeex', '~> 3.0.4', :configurations => ['Debug'] //可选\n    pod 'DoraemonKit/WithDatabase', '~> 3.0.4', :configurations => ['Debug'] //可选\n    pod 'DoraemonKit/WithMLeaksFinder', '3.0.4', :configurations => ['Debug'] //可选\n```\nCore subspec作为核心，必须引入。\n\n如果你的日志是基于CocoaLumberjack，那你也可以引入WithLogger subspec。\n\n如果你想使用模拟定位的功能，请单独接入WithGPS subspec。\n\n如果你要集成Load耗时检测的话，那就请接入WithLoad subspec。\n\n如果你要集成Weex的相关专项工具的话，那就请接入WithWeex subspec。\n\n如果你要使用[YYDebugDatabase](https://github.com/y500/iOSDebugDatabase)在网页端调式数据库的话，那就请接入WithDatabase subspec。\n\n如果你要使用[MLeaksFinder](https://github.com/Tencent/MLeaksFinder)查找内存泄漏的问题的话，那就请接入WithMLeaksFinder subspec。\n\n#### 1.2: Carthage依赖\n\n```\ngit \"https://github.com/didi/DoraemonKit.git\"  \"c3.0.4\"\n    或者\n    github \"didi/DoraemonKit\"\n```\n**tip**：只在Debug环境中进行集成，不要带到线上。有一些hook操作会污染线上代码。\n\n\n### 二、使用DoraemonKit内置工具集的接入方式\n在App启动的时候添加一下代码\n\n```objective-c\n#ifdef DEBUG\n#import <DoraemonKit/DoraemonManager.h>\n#endif\n\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {\n    #ifdef DEBUG\n        //默认\n        [[DoraemonManager shareInstance] install];\n        // 或者使用传入位置,解决遮挡关键区域,减少频繁移动\n        //[[DoraemonManager shareInstance] installWithStartingPosition:CGPointMake(66, 66)];\n    #endif\n}\n```\n\n 通过以上步骤你就可以使用DorameonKit所有的内置工具集合。如果你想把自己与业务相关的一些工具代码加入到DoraemonKit中做统一管理的话，你可以按照3的步骤来做。\n\n### 三、添加自定义测试模块到Doraemon面板中（非必要）\n比如我们要在Doraemon面板中添加一个环境切换的功能。\n\n第一步：新建一个类，实现DoraemonPluginProtocol协议中的pluginDidLoad方法，该方法就是以后点击Doraemon工具面板中\"环境切换\"按钮触发的事件。\n\n比如以代驾司机端为例，点击按钮之后会进入环境切换页面。\n\n```\n@implementation KDDoraemonEnvPlugin\n- (void)pluginDidLoad{\n    [APP_INTERACOTR.rootNav openURL:@\"KDSJ://KDDoraemonSFViewController\"];\n    [[DoraemonManager shareInstance] hiddenHomeWindow];\n}\n @end\n```\n\n\n第二步：在Doraemon初始化的地方添加第一步中添加的\"环境切换\"插件\n\n调用DoraemonManager的以下方法：\n\n```\n[[DoraemonManager shareInstance] addPluginWithTitle:@\"环境切换\" icon:@\"doraemon_default\" desc:@\"用于app内部环境切换功能\" pluginName:@\"KDDoraemonEnvPlugin\" atModule:@\"业务专区\"];\n```\n\n依次代表 集成到DoraemonKit面板中的标题，图标，描述，插件名称，和所属于的模块。\n\n比如以代驾司机端为例：\n\n```\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {\n    #ifdef DEBUG\n       [self configDoraemonKit];\n    #endif\n}\n//配置Doraemon工具集\n- (void)configDoraemonKit{\n    [[DoraemonManager shareInstance] addPluginWithTitle:@\"环境切换\" icon:@\"doraemon_default\" desc:@\"用于app内部环境切换功能\" pluginName:@\"KDDoraemonEnvPlugin\" atModule:@\"业务专区\"];\n    [[DoraemonManager shareInstance] addH5DoorBlock:^(NSString *h5Url) {\n        [APP_INTERACOTR.rootNav openURL:@\"KDSJ://KDWebViewController\" withQuery:@{@\"urlString\":h5Url}];\n    }];\n    [[DoraemonManager shareInstance] install];\n}\n```\n\n**tips**:目前也支持使用block方式接入自定义测试模块，使用方式如下：\n\n```\n\n    [[DoraemonManager shareInstance] addPluginWithTitle:@\"标题\" icon:@\"doraemon_default\" desc:@\"测试插件\" pluginName:@\"TestPlugin(可以为空)\" atModule:DoraemonLocalizedString(@\"业务工具\") handle:^(NSDictionary *itemData) {\n        NSLog(@\"handle block plugin\");\n    }];\n    \n```\n\n### 四、swift 接入方式\npod 同 OC 一样\n\n#### swift 4.0 4.2 5.0 接入方式都一样\n\n```\nimport UIKit\n\n#if DEBUG\n    import DoraemonKit\n#endif\n\n@UIApplicationMain\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\n    var window: UIWindow?\n\n\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n        // Override point for customization after application launch.\n        \n    #if DEBUG\n        DoraemonManager.shareInstance().install()\n    #endif\n        return true\n    }\n    \n}\n```\n\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/AppDelegate.swift",
    "content": "//\n//  AppDelegate.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/11.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\n\n@UIApplicationMain\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\n\n\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n        // Override point for customization after application launch.\n        return true\n    }\n\n    // MARK: UISceneSession Lifecycle\n\n    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {\n        // Called when a new scene session is being created.\n        // Use this method to select a configuration to create the new scene with.\n        return UISceneConfiguration(name: \"Default Configuration\", sessionRole: connectingSceneSession.role)\n    }\n\n    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {\n        // Called when the user discards a scene session.\n        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.\n        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.\n    }\n\n\n}\n\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"appicon_40.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"appicon_60.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"appicon_58.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"appicon_87.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"appicon_80.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"appicon_120.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"appicon_120-1.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"filename\" : \"appicon_180.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"filename\" : \"appicon_20.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"appicon_40-1.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"appicon_29.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"appicon_58-1.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"appicon_40-2.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"appicon_80-1.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"appicon_76.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"76x76\"\n    },\n    {\n      \"filename\" : \"appicon_152.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"76x76\"\n    },\n    {\n      \"filename\" : \"appicon_167.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"83.5x83.5\"\n    },\n    {\n      \"filename\" : \"appicon_1024.png\",\n      \"idiom\" : \"ios-marketing\",\n      \"scale\" : \"1x\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Assets.xcassets/emoji.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"emoji.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Assets.xcassets/zhaoliyin.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"zhaoliyin.jpg\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Base/DoraemonDemoBaseViewController.swift",
    "content": "//\n//  DoraemonDemoBaseViewController.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/13.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\n\nclass DoraemonDemoBaseViewController: UIViewController {\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        self.view.backgroundColor = UIColor.white\n\n        // Do any additional setup after loading the view.\n    }\n    \n\n    /*\n    // MARK: - Navigation\n\n    // In a storyboard-based application, you will often want to do a little preparation before navigation\n    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {\n        // Get the new view controller using segue.destination.\n        // Pass the selected object to the new view controller.\n    }\n    */\n\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Base.lproj/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"13122.16\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" launchScreen=\"YES\" useTraitCollections=\"YES\" useSafeAreas=\"YES\" colorMatched=\"YES\" initialViewController=\"01J-lp-oVM\">\n    <dependencies>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"13104.12\"/>\n        <capability name=\"Safe area layout guides\" minToolsVersion=\"9.0\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"EHf-IW-A2E\">\n            <objects>\n                <viewController id=\"01J-lp-oVM\" sceneMemberID=\"viewController\">\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"Ze5-6b-2t3\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"375\" height=\"667\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <color key=\"backgroundColor\" xcode11CocoaTouchSystemColor=\"systemBackgroundColor\" cocoaTouchSystemColor=\"whiteColor\"/>\n                        <viewLayoutGuide key=\"safeArea\" id=\"6Tk-OE-BBY\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"iYj-Kq-Ea1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"53\" y=\"375\"/>\n        </scene>\n    </scenes>\n</document>\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Base.lproj/Main.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"13122.16\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" useTraitCollections=\"YES\" useSafeAreas=\"YES\" colorMatched=\"YES\" initialViewController=\"BYZ-38-t0r\">\n    <dependencies>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"13104.12\"/>\n        <capability name=\"Safe area layout guides\" minToolsVersion=\"9.0\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"tne-QT-ifu\">\n            <objects>\n                <viewController id=\"BYZ-38-t0r\" customClass=\"ViewController\" customModuleProvider=\"target\" sceneMemberID=\"viewController\">\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"8bC-Xf-vdC\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"375\" height=\"667\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <color key=\"backgroundColor\" xcode11CocoaTouchSystemColor=\"systemBackgroundColor\" cocoaTouchSystemColor=\"whiteColor\"/>\n                        <viewLayoutGuide key=\"safeArea\" id=\"6Tk-OE-BBY\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"dkx-z0-nzr\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n        </scene>\n    </scenes>\n</document>\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Common/DoraemonDemoCommonViewController.swift",
    "content": "//\n//  DoraemonDemoCommonViewController.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/19.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\nimport DoraemonKit\n\nclass DoraemonDemoCommonViewController: DoraemonDemoBaseViewController {\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        self.title = DoraemonDemoLocalizedString(\"通用测试Demo\")\n        \n        let btn0 = UIButton(frame: CGRect(x: 0, y: kIphoneNavBarHeight, width: view.width, height: 60))\n        btn0.backgroundColor = UIColor.orange\n        btn0.setTitle(\"子线程UI操作\", for: .normal)\n        btn0.addTarget(self, action: #selector(addSubViewAtOtherThread), for: .touchUpInside)\n        view.addSubview(btn0)\n        \n        let btn1 = UIButton(frame: CGRect(x: 0, y: btn0.bottom+20, width: view.width, height: 60))\n        btn1.backgroundColor = UIColor.orange\n        btn1.setTitle(\"显示入口\", for: .normal)\n        btn1.addTarget(self, action: #selector(showEntry), for: .touchUpInside)\n        view.addSubview(btn1)\n        \n        let btn2 = UIButton(frame: CGRect(x: 0, y: btn1.bottom+20, width: view.width, height: 60))\n        btn2.backgroundColor = UIColor.orange\n        btn2.setTitle(\"隐藏入口\", for: .normal)\n        btn2.addTarget(self, action: #selector(hiddenEntry), for: .touchUpInside)\n        view.addSubview(btn2)\n        \n    }\n    \n    @objc func addSubViewAtOtherThread() {\n        let globalQueue = DispatchQueue.global()\n        globalQueue.async {\n            var v  =  UIView()\n            self.view.addSubview(v)\n        }\n    }\n    \n    @objc func showEntry() {\n        DoraemonManager.shareInstance().showDoraemon()\n    }\n    \n    @objc func hiddenEntry() {\n        DoraemonManager.shareInstance().hiddenDoraemon()\n    }\n\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Crash/DoraemonDemoCrashViewController.swift",
    "content": "//\n//  DoraemonDemoCrashViewController.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/19.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\n\nclass DoraemonDemoCrashViewController: DoraemonDemoBaseViewController {\n    \n    var uncaughtExceptionBtn: UIButton!\n    var signalExceptionBtn: UIButton!\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n         self.title = \"Crash\";\n        \n        uncaughtExceptionBtn = UIButton(frame: CGRect(x: 0, y: kIphoneNavBarHeight, width: view.width, height: 60))\n        uncaughtExceptionBtn.backgroundColor = UIColor.orange\n        uncaughtExceptionBtn.setTitle(\"uncaughtException\", for: .normal)\n        uncaughtExceptionBtn.addTarget(self, action: #selector(uncaughtExceptionBtnClicked(button:)), for: .touchUpInside)\n        view.addSubview(uncaughtExceptionBtn)\n        \n        signalExceptionBtn = UIButton(frame: CGRect(x: 0, y: uncaughtExceptionBtn.bottom+20, width: view.width, height: 60))\n        signalExceptionBtn.backgroundColor = UIColor.orange\n        signalExceptionBtn.setTitle(\"signalException\", for: .normal)\n        signalExceptionBtn.addTarget(self, action: #selector(signalExceptionBtnClicked(_:)), for: .touchUpInside)\n        view.addSubview(signalExceptionBtn)\n    }\n    \n    @objc func uncaughtExceptionBtnClicked(button: UIButton) {\n        let array = [\"A\",\"B\",\"C\"]\n        var v = array[5]\n    }\n    \n    @objc func signalExceptionBtnClicked(_ button: UIButton) {\n\n    }\n\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/DoKitSwiftDemo-Bridging-Header.h",
    "content": "//\n//  Use this file to import your target's public headers that you would like to expose to Swift.\n//\n\n#import \"DoraemonDemoOCViewController.h\"\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Home/DoraemonDemoHomeViewController.swift",
    "content": "//\n//  DoraemonDemoHomeViewController.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/13.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\n\nclass DoraemonDemoHomeViewController: DoraemonDemoBaseViewController, UITableViewDelegate, UITableViewDataSource {\n    var tableView: UITableView!\n    var items: [String]! = [] {\n        didSet {\n            self.tableView.reloadData()\n        }\n    }\n    \n    override func viewDidLoad() {\n        super.viewDidLoad()\n        self.title = DoraemonDemoLocalizedString(\"DoraemonKit\")\n        \n        tableView = UITableView(frame: view.bounds)\n        tableView.delegate = self\n        tableView.dataSource = self\n        view.addSubview(tableView)\n        \n        items  = [\"沙盒测试Demo\",\n                 \"日志测试Demo\",\n                 \"性能测试Demo\",\n                 \"视觉测试Demo\",\n                 \"网络测试Demo\",\n                 \"模拟位置Demo\",\n                 \"crash触发Demo\",\n                 \"通用测试Demo\",\n                 \"内存泄漏测试\",\n                 \"Call OC\"];\n    }\n    \n    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {\n        return items.count\n    }\n    \n    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {\n        var cell = tableView.dequeueReusableCell(withIdentifier: \"HomeCellId\")\n        if cell == nil {\n            cell = UITableViewCell(style: .default, reuseIdentifier: \"HomeCellId\")\n        }\n        cell?.textLabel?.text = DoraemonDemoLocalizedString(items[indexPath.row])\n        \n        return cell!;\n    }\n    \n    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {\n        let row = indexPath.row\n        var vc : UIViewController\n        switch row {\n        case 0:\n            vc = DoraemonDemoSanboxViewController()\n        case 1:\n            vc = DoraemonDemoLoggerViewController()\n        case 2:\n            vc = DoraemonDemoPerformanceViewController()\n        case 3:\n            vc = DoraemonDemoUIViewController()\n        case 4:\n            vc = DoraemonDemoNetViewController()\n        case 5:\n            vc = DoraemonDemoGPSViewController()\n        case 6:\n            vc = DoraemonDemoCrashViewController()\n        case 7:\n            vc = DoraemonDemoCommonViewController()\n        case 8:\n            vc = DoraemonDemoMemoryLeakViewController()\n        default:\n            vc = DoraemonDemoOCViewController()\n        }\n        \n        navigationController?.pushViewController(vc, animated: true)\n    }\n\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>UIApplicationSceneManifest</key>\n\t<dict>\n\t\t<key>UIApplicationSupportsMultipleScenes</key>\n\t\t<false/>\n\t\t<key>UISceneConfigurations</key>\n\t\t<dict>\n\t\t\t<key>UIWindowSceneSessionRoleApplication</key>\n\t\t\t<array>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>UISceneConfigurationName</key>\n\t\t\t\t\t<string>Default Configuration</string>\n\t\t\t\t\t<key>UISceneDelegateClassName</key>\n\t\t\t\t\t<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>\n\t\t\t\t\t<key>UISceneStoryboardFile</key>\n\t\t\t\t\t<string>Main</string>\n\t\t\t\t</dict>\n\t\t\t</array>\n\t\t</dict>\n\t</dict>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIMainStoryboardFile</key>\n\t<string>Main</string>\n\t<key>UIRequiredDeviceCapabilities</key>\n\t<array>\n\t\t<string>armv7</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Logger/DoraemonDemoLoggerViewController.swift",
    "content": "//\n//  DoraemonDemoLoggerViewController.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/15.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\n\nclass DoraemonDemoLoggerViewController: DoraemonDemoBaseViewController {\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        self.title = DoraemonDemoLocalizedString(\"日志测试Demo\")\n        \n        let btn = UIButton(frame: CGRect(x: 0, y: kIphoneNavBarHeight, width: view.width, height: 60.0))\n        btn.backgroundColor = UIColor.orange\n        btn.setTitle(DoraemonDemoLocalizedString(\"添加一条NSLog日志\"), for: .normal)\n        btn.addTarget(self, action: #selector(addNSLog), for: .touchUpInside);\n        view.addSubview(btn)\n    }\n    \n    @objc func addNSLog() {\n        let str = \"jack\"\n        let age = 29\n        NSLog(\"NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦NSLog日记来啦。。。str == %@  age == %zi\", str,age)\n        let specialString = \"callnative://saveTian/%22saveTianDataCallback43%22\"\n        NSLog(\"%@\", specialString)\n    }\n\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/MemoryLeak/DoraemonDemoMemoryLeakModel.swift",
    "content": "//\n//  DoraemonDemoMemoryLeakModel.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/19.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\n\nclass DoraemonDemoMemoryLeakModel: NSObject {\n    var closure: (()->Void)?\n    \n    func callClosure() {\n        closure?()\n    }\n    \n    deinit {\n        print(\"model deinit\")\n    }\n\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/MemoryLeak/DoraemonDemoMemoryLeakView.swift",
    "content": "//\n//  DoraemonDemoMemoryLeakView.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/19.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\n\nclass DoraemonDemoMemoryLeakView: UIView {\n    var model : DoraemonDemoMemoryLeakModel\n    override init(frame: CGRect) {\n        model = DoraemonDemoMemoryLeakModel()\n        super.init(frame: frame)\n        self.backgroundColor = UIColor.orange\n        model.closure = { ()->Void in\n            self.doSomeThing()\n        }\n        model.callClosure()\n    }\n    \n    func doSomeThing() {\n        print(\"view doSomeThing\");\n    }\n    \n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n    \n    deinit {\n        print(\"view deinit\")\n    }\n    \n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/MemoryLeak/DoraemonDemoMemoryLeakViewController.swift",
    "content": "//\n//  DoraemonDemoMemoryLeakViewController.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/19.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\n\nclass DoraemonDemoMemoryLeakViewController: DoraemonDemoBaseViewController {\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        \n        let leakView = DoraemonDemoMemoryLeakView(frame: CGRect(x: 100, y: 200, width: 100, height: 200))\n        view.addSubview(leakView)\n    }\n    \n    deinit {\n        print(\"vc deinit\")\n    }\n\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/MockGPS/DoraemonDemoGPSAnnotation.swift",
    "content": "//\n//  DoraemonDemoGPSAnnotation.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/18.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\nimport MapKit\n\nclass DoraemonDemoGPSAnnotation: NSObject,MKAnnotation {\n    var coordinate: CLLocationCoordinate2D\n    var title: String?\n    var subtitle: String?\n    var icon: UIImage?\n    \n    init(coordinate: CLLocationCoordinate2D, title: String?, subtitle: String?, icon: UIImage?) {\n        self.coordinate = coordinate\n        self.title = title\n        self.subtitle = title\n        self.icon = icon\n    }\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/MockGPS/DoraemonDemoGPSViewController.swift",
    "content": "//\n//  DoraemonDemoGPSViewController.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/18.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\nimport MapKit\n\nclass DoraemonDemoGPSViewController: DoraemonDemoBaseViewController, MKMapViewDelegate, CLLocationManagerDelegate {\n    var mapView: MKMapView!\n    var lcManager: CLLocationManager?\n    var annotation: DoraemonDemoGPSAnnotation?\n    \n    override func viewDidLoad() {\n        super.viewDidLoad()\n        self.title = DoraemonDemoLocalizedString(\"模拟位置\")\n        \n        mapView = MKMapView(frame: CGRect(x: 0, y: kIphoneNavBarHeight, width: view.width, height: view.height))\n        mapView.mapType = .standard\n        mapView.delegate = self\n        view.addSubview(mapView)\n        \n        if CLLocationManager.locationServicesEnabled() {\n            lcManager = CLLocationManager()\n            lcManager?.delegate = self\n            lcManager?.distanceFilter = 100\n            lcManager?.desiredAccuracy = kCLLocationAccuracyBest\n            lcManager?.startUpdatingLocation()\n        }\n    }\n    \n    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {\n        let l = locations[0]\n        print(\"location at \\(l.coordinate.longitude) \\(l.coordinate.latitude)\")\n        self.refreshAnnotation(location: l)\n    }\n    \n    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {\n        print(\"location failure\")\n    }\n    \n    func refreshAnnotation(location loc:CLLocation) {\n        mapView.centerCoordinate = loc.coordinate\n        mapView.setRegion(MKCoordinateRegion(center: loc.coordinate, span: MKCoordinateSpan(latitudeDelta: 40, longitudeDelta: 40)), animated: true)\n        \n        if annotation == nil {\n            annotation = DoraemonDemoGPSAnnotation(coordinate: loc.coordinate, title: \"title\", subtitle: \"subtitle\", icon: UIImage(named: \"AppIcon\"))\n        }else{\n            mapView.removeAnnotation(annotation!)\n        }\n        annotation?.coordinate = loc.coordinate\n        mapView.addAnnotation(annotation!)\n    }\n    \n    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {\n        if annotation is DoraemonDemoGPSAnnotation {\n            let annotation = annotation as! DoraemonDemoGPSAnnotation\n            let key = \"AnnotationIdentifier\"\n            var view = mapView .dequeueReusableAnnotationView(withIdentifier: key)\n            if view==nil {\n                view = MKAnnotationView(annotation: annotation, reuseIdentifier: key)\n            }\n            view?.annotation = annotation\n            view?.canShowCallout = false\n            view?.image = annotation.icon\n            return view\n        }\n        \n        return nil\n    }\n\n\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Net/DoraemonDemoNetViewController.swift",
    "content": "//\n//  DoraemonDemoNetViewController.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/18.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\nimport AFNetworking\n\nclass DoraemonDemoNetViewController: DoraemonDemoBaseViewController, UITableViewDelegate, UITableViewDataSource {\n    var tableView: UITableView!\n    var items: [String]! = [] {\n        didSet {\n            self.tableView.reloadData()\n        }\n    }\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        self.title = DoraemonDemoLocalizedString(\"网络测试Dem\")\n        \n        tableView = UITableView(frame: view.bounds)\n        tableView.delegate = self\n        tableView.dataSource = self\n        view.addSubview(tableView)\n        \n        items  = [\"发送一条URLConnection请求\",\n                 \"发送一条NSURLSession请求\",\n                 \"发送一条AFNetworking请求\"]\n    }\n    \n    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {\n        return items.count\n    }\n    \n    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {\n        var cell = tableView.dequeueReusableCell(withIdentifier: \"NetCellId\")\n        if cell == nil {\n            cell = UITableViewCell(style: .default, reuseIdentifier: \"NetCellId\")\n        }\n        cell?.textLabel?.text = DoraemonDemoLocalizedString(items[indexPath.row])\n        \n        return cell!;\n    }\n    \n    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {\n        let row = indexPath.row\n        switch row {\n        case 0:\n            self.netForURLConnection()\n        case 1:\n            self.netForNSURLSession()\n        case 2:\n            self.netForAFNetworking()\n        default:\n            return\n        }\n    }\n    \n    func netForURLConnection() {\n        let url: URL = URL(string: \"https://www.taobao.com/\")!\n        let request: URLRequest = URLRequest(url: url)\n        NSURLConnection.sendAsynchronousRequest(request, queue: OperationQueue()) { (response:URLResponse?, data:Data?, error:Error?) in\n            if error == nil {\n                let result: String = String(data: data!, encoding: String.Encoding.utf8)!\n                print(\"请求成功 = \\(result)\")\n            }else{\n                print(\"请求失败 error = \\(error.debugDescription)\")\n            }\n        }\n    }\n    \n    func netForNSURLSession() {\n        let url: URL = URL(string: \"https://www.taobao.com/\")!\n        let request: URLRequest = URLRequest(url: url)\n        let configuration: URLSessionConfiguration = URLSessionConfiguration.default\n        let session:URLSession = URLSession(configuration: configuration)\n        \n        let task:URLSessionDataTask = session.dataTask(with: request) { (data:Data?, respanse:URLResponse?, error:Error?) in\n            if error == nil{\n                let result: String = String(data: data!, encoding: String.Encoding.utf8)!\n                print(\"请求成功 = \\(result)\")\n            }else{\n                print(\"请求失败 error = \\(error.debugDescription)\")\n            }\n        }\n        task.resume()\n    }\n    \n    func netForAFNetworking() {\n        let manager = AFHTTPSessionManager()\n        manager.requestSerializer = AFHTTPRequestSerializer()\n        manager.responseSerializer = AFHTTPResponseSerializer()\n        manager.get(\"https://www.taobao.com/\", parameters: nil, progress: nil, success: { (task, response) in\n            let result: String = String(data: response! as! Data, encoding: String.Encoding.utf8)!\n            print(\"请求成功 = \\(result)\")\n        }) { (task, error) in\n            print(\"error == \\(error.localizedDescription)\")\n        }\n    }\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/OC/DoraemonDemoOCViewController.h",
    "content": "//\n//  DoraemonDemoOCViewController.h\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/18.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n//You cannot subclass a Swift class in Objective-C\n@interface DoraemonDemoOCViewController : UIViewController\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/OC/DoraemonDemoOCViewController.m",
    "content": "//\n//  DoraemonDemoOCViewController.m\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/18.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\n#import \"DoraemonDemoOCViewController.h\"\n#import \"DoKitSwiftDemo-Swift.h\"\n\n\n@interface DoraemonDemoOCViewController ()\n\n@end\n\n@implementation DoraemonDemoOCViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    // Do any additional setup after loading the view.\n    self.title = @\"Call OC\";\n    self.view.backgroundColor = [UIColor whiteColor];\n    \n    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, IPHONE_NAVIGATIONBAR_HEIGHT, self.view.doraemon_width, 60)];\n    btn.backgroundColor = [UIColor orangeColor];\n    [btn setTitle:@\"Call Swift\" forState:UIControlStateNormal];\n    [btn addTarget:self action:@selector(callSwift) forControlEvents:UIControlEventTouchUpInside];\n    [self.view addSubview:btn];\n}\n\n- (void)callSwift{\n    DoraemonDemoSwiftViewController *vc = [[DoraemonDemoSwiftViewController alloc] init];\n    [self.navigationController pushViewController:vc animated:YES];\n}\n\n@end\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/OC/DoraemonDemoSwiftViewController.swift",
    "content": "//\n//  DoraemonDemoSwiftViewController.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/19.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\n\nclass DoraemonDemoSwiftViewController: DoraemonDemoBaseViewController {\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        self.title = \"Call Swift\";\n        \n    }\n\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Performance/DoraemonDemoPerformanceViewController.swift",
    "content": "//\n//  DoraemonDemoPerformanceViewController.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/15.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\n\nclass DoraemonDemoPerformanceViewController: DoraemonDemoBaseViewController {\n    var highCpu = false\n    var cpuThread: Thread?\n    var highMemory = false\n    var memoryThread: Thread?\n    var addMemory: UnsafeMutableRawPointer?\n    var btn1: UIButton!\n    var btn2: UIButton!\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        self.title = DoraemonDemoLocalizedString(\"性能测试Demo\")\n        \n        let btn0 = UIButton(frame: CGRect(x: 0, y: kIphoneNavBarHeight, width: view.width, height: 60.0))\n        btn0.backgroundColor = UIColor.orange\n        btn0.setTitle(DoraemonDemoLocalizedString(\"低FPS操作打开\"), for: .normal)\n        btn0.addTarget(self, action: #selector(fpsClick), for: .touchUpInside);\n        view.addSubview(btn0)\n        \n        btn1 = UIButton(frame: CGRect(x: 0, y: btn0.bottom+20, width: view.width, height: 60.0))\n        btn1.backgroundColor = UIColor.orange\n        btn1.setTitle(DoraemonDemoLocalizedString(\"高CPU操作打开\"), for: .normal)\n        btn1.addTarget(self, action: #selector(cpuClick), for: .touchUpInside);\n        view.addSubview(btn1)\n        \n        btn2 = UIButton(frame: CGRect(x: 0, y: btn1.bottom+20, width: view.width, height: 60.0))\n        btn2.backgroundColor = UIColor.orange\n        btn2.setTitle(DoraemonDemoLocalizedString(\"高内存操作打开\"), for: .normal)\n        btn2.addTarget(self, action: #selector(memoryClick), for: .touchUpInside);\n        view.addSubview(btn2)\n        \n        let btn3 = UIButton(frame: CGRect(x: 0, y: btn2.bottom+20, width: view.width, height: 60.0))\n        btn3.backgroundColor = UIColor.orange\n        btn3.setTitle(DoraemonDemoLocalizedString(\"高流量操作打开\"), for: .normal)\n        btn3.addTarget(self, action: #selector(flowClick), for: .touchUpInside);\n        view.addSubview(btn3)\n        \n        let btn4 = UIButton(frame: CGRect(x: 0, y: btn3.bottom+20, width: view.width, height: 60.0))\n        btn4.backgroundColor = UIColor.orange\n        btn4.setTitle(DoraemonDemoLocalizedString(\"卡顿操作打开\"), for: .normal)\n        btn4.addTarget(self, action: #selector(anrClick), for: .touchUpInside);\n        view.addSubview(btn4)\n    }\n    \n    @objc func fpsClick() {\n        Thread.sleep(forTimeInterval: 0.5)\n    }\n    \n    @objc func cpuClick() {\n        highCpu = !highCpu\n        if highCpu {\n            cpuThread = Thread(target: self, selector: #selector(highCPUOperate), object: nil)\n            cpuThread?.name = \"HighCPUThread\"\n            cpuThread?.start()\n            \n            btn1.setTitle(DoraemonDemoLocalizedString(\"高CPU操作关闭\"), for: .normal)\n        }else{\n            cpuThread?.cancel()\n            cpuThread = nil\n            \n            btn1.setTitle(DoraemonDemoLocalizedString(\"高CPU操作打开\"), for: .normal)\n        }\n    }\n    \n    @objc func memoryClick() {\n        highMemory = !highMemory\n        if highMemory {\n            memoryThread = Thread(target: self, selector: #selector(highMemoryOperate), object: nil)\n            memoryThread?.name = \"HighMemoryThread\"\n            memoryThread?.start()\n            \n            btn2.setTitle(\"高内存操作关闭\", for: .normal)\n        }else{\n            memoryThread?.cancel()\n            memoryThread = nil\n            \n            btn2.setTitle(\"高内存操作打开\", for: .normal)\n        }\n    }\n    \n    @objc func flowClick() {\n        for _ in 0...10 {\n            let url: URL = URL(string: \"https://www.taobao.com/\")!\n            let request: URLRequest = URLRequest(url: url)\n            let configuration: URLSessionConfiguration = URLSessionConfiguration.default\n            let session:URLSession = URLSession(configuration: configuration)\n            \n            let task:URLSessionDataTask = session.dataTask(with: request) { (data:Data?, respanse:URLResponse?, error:Error?) in\n                if error == nil{\n                    let result: String = String(data: data!, encoding: String.Encoding.utf8)!\n                    print(\"请求成功 = \\(result)\")\n                }else{\n                    print(\"请求失败 error = \\(error.debugDescription)\")\n                }\n            }\n            task.resume()\n            \n        }\n    }\n    \n    @objc func anrClick() {\n        print(\"0.4s anr\")\n        Thread.sleep(forTimeInterval: 0.4)\n    }\n    \n    @objc func highCPUOperate() {\n        while true {\n            if Thread.current.isCancelled {\n                Thread.exit()\n            }\n        }\n    }\n    \n    @objc func highMemoryOperate() {\n        let addMemSize = 400\n        let interval: TimeInterval = 2\n        while true {\n            if Thread.current.isCancelled {\n                Thread.exit()\n            }\n            if addMemory == nil {\n                addMemory = malloc(1024*1024*addMemSize)\n                memset(addMemory, 0, 1024*1024*addMemSize)\n            }\n            \n            Thread.sleep(forTimeInterval: interval)\n            if Thread.current.isCancelled {\n                Thread.exit()\n            }\n            \n            if addMemory != nil {\n                free(addMemory)\n                addMemory = nil\n            }\n            \n            Thread.sleep(forTimeInterval: interval)\n        }\n    }\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Plugin/TestPlugin.swift",
    "content": "//\n//  TestPlugin.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/13.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\nimport DoraemonKit\n\n@objc(TestPlugin)\nclass TestPlugin: NSObject,DoraemonPluginProtocol {\n    @objc func pluginDidLoad(){\n        print(\"pluginDidLoad\")\n    }\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Resource/doraemon.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Doraemon</title>\n</head>\n<body>\n<h1>DoraemonKit标题</h1>\n<h2>DoraemonKit真好用</h2>\n<h3>DoraemonKit真好用真好用</h3>\n<h4>DoraemonKit真好用真好用真好用真好用</h4>\n<img src=\"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1553864579528&di=17a85f3f5eb6ca62c2d58ebd66bb65ea&imgtype=0&src=http%3A%2F%2Fwww.yulefm.com%2Fd%2Ffile%2Fnews%2F2019-02-06%2F1549443987832838.jpeg\"/>\n</body>\n</html>"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Sanbox/DoraemonDemoSanboxViewController.swift",
    "content": "//\n//  DoraemonDemoSanboxViewController.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/13.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\n\nclass DoraemonDemoSanboxViewController: DoraemonDemoBaseViewController {\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        self.title = DoraemonDemoLocalizedString(\"沙盒测试Demo\");\n        \n        let btn = UIButton(frame: CGRect(x: 0, y: kIphoneNavBarHeight, width: view.width, height: 60.0))\n        btn.backgroundColor = UIColor.orange\n        btn.setTitle(DoraemonDemoLocalizedString(\"添加一条json到沙盒中\"), for: .normal)\n        btn.addTarget(self, action: #selector(addFile), for: .touchUpInside);\n        view.addSubview(btn)\n        \n        let btn1 = UIButton(frame: CGRect(x: 0, y: btn.bottom+20, width: btn.width, height: 60))\n        btn1.backgroundColor = UIColor.orange\n        btn1.setTitle(DoraemonDemoLocalizedString(\"添加一张图片到沙盒中\"), for: .normal)\n        btn1.addTarget(self, action: #selector(addImageFile), for: .touchUpInside);\n        view.addSubview(btn1)\n        \n        let btn2 = UIButton(frame: CGRect(x: 0, y: btn1.bottom+20, width: btn.width, height: 60))\n        btn2.backgroundColor = UIColor.orange\n        btn2.setTitle(DoraemonDemoLocalizedString(\"添加一段mp4到沙盒中\"), for: .normal)\n        btn2.addTarget(self, action: #selector(addMP4File), for: .touchUpInside);\n        view.addSubview(btn2)\n        \n        let btn3 = UIButton(frame: CGRect(x: 0, y: btn2.bottom+20, width: btn.width, height: 60))\n        btn3.backgroundColor = UIColor.orange\n        btn3.setTitle(DoraemonDemoLocalizedString(\"添加doc、xlsx、pdf到沙盒中\"), for: .normal)\n        btn3.addTarget(self, action: #selector(addOtherFile), for: .touchUpInside);\n        view.addSubview(btn3)\n        \n        let btn4 = UIButton(frame: CGRect(x: 0, y: btn3.bottom+20, width: btn.width, height: 60))\n        btn4.backgroundColor = UIColor.orange\n        btn4.setTitle(DoraemonDemoLocalizedString(\"添加html到沙盒中\"), for: .normal)\n        btn4.addTarget(self, action: #selector(addHtmlFile), for: .touchUpInside);\n        view.addSubview(btn4)\n    }\n    \n    @objc func addFile() {\n        let dic = [\"name\":\"yixiang\",\"age\":\"16\"]\n        let json = dic.formatJson()\n        if let json = json {\n            let docDir = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory,  FileManager.SearchPathDomainMask.userDomainMask, true)[0]\n            let filePath = docDir+\"/json.text\"\n            try? json.write(toFile: filePath, atomically: true, encoding: String.Encoding.utf8)\n        }\n    }\n    \n    @objc func addImageFile() {\n        let homeDir = NSHomeDirectory()\n        let docDic = homeDir + \"/Documents\"\n        let imagePath = docDic + \"/zhaoliyin.jpg\"\n        let image = UIImage(named: \"zhaoliyin.jpg\")\n        let imageData = image!.pngData()\n        try? imageData?.write(to: URL(fileURLWithPath: imagePath))\n    }\n    \n    @objc func addMP4File() {\n        copyBundleToSanbox(\"huoying\", \"mp4\")\n    }\n    \n    @objc func addOtherFile() {\n        copyBundleToSanbox(\"Doraemon\", \"docx\")\n        copyBundleToSanbox(\"Doraemon\", \"pdf\")\n        copyBundleToSanbox(\"Doraemon\", \"xlsx\")\n    }\n    \n    @objc func addHtmlFile() {\n        copyBundleToSanbox(\"doraemon\", \"html\")\n    }\n\n    func copyBundleToSanbox(_ name: String, _ type: String) {\n        let path = Bundle.main.path(forResource: name, ofType: type)\n        let fileManager = FileManager.default\n        let fileExists = fileManager.fileExists(atPath: path!)\n        if fileExists {\n            let homeDir = NSHomeDirectory()\n            let docDic = homeDir + \"/Documents\"\n            let toPath = docDic + \"/\\(name).\\(type)\"\n            if !fileManager.fileExists(atPath: toPath) {\n                do {\n                    try fileManager.copyItem(atPath: path!, toPath: toPath)\n                }catch{\n                    print(\"catch error\")\n                }\n            }\n        }else{\n            print(\"file not exist\")\n        }\n    }\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/SceneDelegate.swift",
    "content": "//\n//  SceneDelegate.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/11.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\nimport DoraemonKit\n\nclass SceneDelegate: UIResponder, UIWindowSceneDelegate {\n\n    var window: UIWindow?\n\n\n    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {\n        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.\n        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.\n        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).\n        guard let _ = (scene as? UIWindowScene) else { return }\n        \n        DoraemonManager.shareInstance().addPlugin(withTitle: DoraemonDemoLocalizedString(\"测试插件\"), icon: \"doraemon_default\", desc: DoraemonDemoLocalizedString(\"测试插件\"), pluginName: \"TestPlugin\", atModule: DoraemonDemoLocalizedString(\"业务工具\"))\n        \n        DoraemonManager.shareInstance().addPlugin(withTitle: DoraemonDemoLocalizedString(\"block方式加入插件\"), icon: \"doraemon_default\", desc: DoraemonDemoLocalizedString(\"测试插件\"), pluginName: \"\", atModule: DoraemonDemoLocalizedString(\"业务工具\")) { (itemData: [AnyHashable : Any]) in\n            print(itemData)\n        }\n        DoraemonManager.shareInstance().install()\n\n        self.window = UIWindow(windowScene: scene as! UIWindowScene)\n        self.window?.frame = UIScreen.main.bounds;\n        let homeVc = DoraemonDemoHomeViewController()\n        let nav = UINavigationController(rootViewController: homeVc)\n        self.window?.rootViewController = nav\n        self.window?.makeKeyAndVisible()\n        \n    }\n\n    func sceneDidDisconnect(_ scene: UIScene) {\n        // Called as the scene is being released by the system.\n        // This occurs shortly after the scene enters the background, or when its session is discarded.\n        // Release any resources associated with this scene that can be re-created the next time the scene connects.\n        // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).\n    }\n\n    func sceneDidBecomeActive(_ scene: UIScene) {\n        // Called when the scene has moved from an inactive state to an active state.\n        // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.\n    }\n\n    func sceneWillResignActive(_ scene: UIScene) {\n        // Called when the scene will move from an active state to an inactive state.\n        // This may occur due to temporary interruptions (ex. an incoming phone call).\n    }\n\n    func sceneWillEnterForeground(_ scene: UIScene) {\n        // Called as the scene transitions from the background to the foreground.\n        // Use this method to undo the changes made on entering the background.\n    }\n\n    func sceneDidEnterBackground(_ scene: UIScene) {\n        // Called as the scene transitions from the foreground to the background.\n        // Use this method to save data, release shared resources, and store enough scene-specific state information\n        // to restore the scene back to its current state.\n    }\n\n\n}\n\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/UI/DoraemonDemoUIViewController.swift",
    "content": "//\n//  DoraemonDemoUIViewController.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/18.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport UIKit\n\nclass DoraemonDemoUIViewController: DoraemonDemoBaseViewController {\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        \n        let redView: UIView! = UIView(frame: CGRect(x: 100, y: 200, width: 60, height: 60))\n        redView.backgroundColor = UIColor.red\n        view.addSubview(redView)\n        \n        let titleLabel: UILabel! = UILabel(frame: CGRect(x: 100, y: 400, width: 200, height: 60))\n        titleLabel.text = DoraemonDemoLocalizedString(\"我是来测试的\")\n        titleLabel.backgroundColor = UIColor.hexColor(0x00FF00)\n        titleLabel.textColor = UIColor.hexColor(0xFF0000)\n        view.addSubview(titleLabel)\n        \n        let input: UITextField! = UITextField(frame: CGRect(x: 100, y: 300, width: 200, height: 50))\n        input.textAlignment = .center\n        input.keyboardType = .numberPad\n        input.backgroundColor = UIColor.lightGray\n        view.addSubview(input)\n        \n        let input2: UITextField! = UITextField(frame: CGRect(x: 100, y: 500, width: 200, height: 50))\n        input2.textAlignment = .center\n        input2.backgroundColor = UIColor.lightGray\n        view.addSubview(input2)\n        \n        let btn: UIButton! = UIButton(frame: CGRect(x: 200, y: 200, width: 200, height: 50))\n        btn.backgroundColor = UIColor.lightGray\n        btn.layer.cornerRadius = 8\n        btn.setTitle(\"UIMenuController\", for: .normal)\n        btn.addTarget(self, action: #selector(deleteBtnAction), for: .touchUpInside)\n        view.addSubview(btn)\n    }\n    \n    @objc func deleteBtnAction() {\n        print(\"deleteBtnAction\")\n    }\n    \n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Util/Dictionary+DoKit.swift",
    "content": "//\n//  Dictionary+DoKit.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/14.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport Foundation\n\nextension Dictionary {\n    public func formatJson() -> String? {\n        let jsonData = try? JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions())\n        if let jsonData = jsonData {\n            let jsonStr = String(data: jsonData, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue))\n            return jsonStr ?? \"\"\n        }\n        return nil\n    }\n    \n    public static func constructFromJson (json: String) -> Dictionary? {\n        let data = json.data(using: String.Encoding.utf8, allowLossyConversion: true)\n        let obj = try? JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers)\n        if let obj = obj {\n            return obj as? Dictionary\n        }\n        return nil\n    }\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Util/DoraemonDemoDefine.swift",
    "content": "//\n//  DoraemonDefine.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/13.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport Foundation\nimport UIKit\n\nlet kDoKitScreenWidth:CGFloat = UIScreen.main.bounds.size.width\nlet kDoKitScreenHeight:CGFloat = UIScreen.main.bounds.size.height\nlet kDoKitOrientationPortrait:Bool = UIApplication.shared.statusBarOrientation.isPortrait\nlet kIphoneNavBarHeight:CGFloat = kDoKitIsIphoneXSeries() ? 88 : 64\nlet kIphoneStatusBarHeight:CGFloat = kDoKitIsIphoneXSeries() ? 44 : 20\nlet kIphoneSafeBottomAreaHeight:CGFloat = kDoKitIsIphoneXSeries() ? 34 : 0\nlet kIphoneTopSensorHeight:CGFloat = kDoKitIsIphoneXSeries() ? 32 : 0\n\n\nfunc kDoKitSizeFrom750(_ x: CGFloat) -> CGFloat {\n    return x*kDoKitScreenWidth/750\n}\n\nfunc kDoKitkSizeFrom750_Landscape(_ x: CGFloat) -> CGFloat {\n    if kDoKitOrientationPortrait {\n        return kDoKitSizeFrom750(x)\n    }else{\n        return x*kDoKitScreenHeight/750\n    }\n}\n\n\nfunc kDoKitIsIphoneXSeries() -> Bool{\n    var iPhoneXSeries = false\n    if UIDevice.current.userInterfaceIdiom != .phone {\n        return iPhoneXSeries\n    }\n    \n    if #available(iOS 11.0, *) {\n        let mainWindow = getKeyWindow()\n        if let mainWindow = mainWindow {\n            if mainWindow.safeAreaInsets.bottom > 0 {\n                iPhoneXSeries = true\n            }\n        }\n    }\n    \n    return iPhoneXSeries\n}\n\nfunc getKeyWindow() -> UIWindow? {\n    let keyWindow: UIWindow? = UIApplication.shared.delegate?.window ?? UIApplication.shared.windows.first\n    return keyWindow\n    \n}\n\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Util/DoraemonDemoi18Util.swift",
    "content": "//\n//  DoraemonKitDemoi18Util.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/13.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport Foundation\nimport UIKit\n\nfunc DoraemonDemoLocalizedString(_ key: String) -> String{\n    return  DoraemonDemoi18Util.localizedString(key)\n}\n\nclass DoraemonDemoi18Util: NSObject {\n    class func localizedString(_ key : String) -> String {\n        let language = Locale.preferredLanguages.first\n        if let language = language {\n            var fileNamePrefix = \"zh-Hans\"\n            if language.hasPrefix(\"en\") {\n                fileNamePrefix = \"en\"\n            }\n            \n            let path: String? = Bundle.main.path(forResource: fileNamePrefix, ofType: \"lproj\")\n            if let path = path {\n                let bundle = Bundle.init(path: path)\n                var localizedString = bundle?.localizedString(forKey: key, value: nil, table: \"DoraemonKitDemo\")\n                if localizedString == nil {\n                    localizedString = key\n                }\n                return localizedString!\n            }else{\n                return key\n            }\n            \n        }else{\n            return key\n        }\n    }\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Util/UIColor+DoKit.swift",
    "content": "//\n//  UIColorExtension.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/13.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport Foundation\nimport UIKit\n\nextension UIColor {\n    static func hexColor(_ hexValue: Int, alphaValue: Float) -> UIColor {\n        return UIColor(red: CGFloat((hexValue & 0xFF0000) >> 16) / 255, green: CGFloat((hexValue & 0x00FF00) >> 8) / 255, blue: CGFloat(hexValue & 0x0000FF) / 255, alpha: CGFloat(alphaValue))\n    }\n    \n    static func hexColor(_ hexValue: Int) -> UIColor {\n        return hexColor(hexValue, alphaValue: 1)\n    }\n    \n    convenience init(_ hexValue: Int, alphaValue: Float) {\n        self.init(red: CGFloat((hexValue & 0xFF0000) >> 16) / 255, green: CGFloat((hexValue & 0x00FF00) >> 8) / 255, blue: CGFloat(hexValue & 0x0000FF) / 255, alpha: CGFloat(alphaValue))\n    }\n    \n    convenience init(_ hexValue: Int) {\n        self.init(hexValue, alphaValue: 1)\n    }\n    \n    func toImage() -> UIImage {\n        let rect = CGRect(x: 0, y: 0, width: 1, height: 1)\n        UIGraphicsBeginImageContext(rect.size)\n        let context = UIGraphicsGetCurrentContext()\n        context?.setFillColor(self.cgColor)\n        context?.fill(rect)\n        let image = UIGraphicsGetImageFromCurrentImageContext()\n        UIGraphicsEndImageContext()\n        return image!\n    }\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/Util/UIView+DoKit.swift",
    "content": "//\n//  DoraemonDemoUIViewExtension.swift\n//  DoKitSwiftDemo\n//\n//  Created by didi on 2020/5/13.\n//  Copyright © 2020 didi. All rights reserved.\n//\n\nimport Foundation\nimport UIKit\n\nextension UIView {\n    public var left: CGFloat {\n        get{\n            return self.frame.origin.x\n        }\n        set(value){\n            var rect = self.frame\n            rect.origin.x = value\n            self.frame = rect\n        }\n    }\n    public var top: CGFloat {\n        get{\n            return self.frame.origin.y\n        }\n        set(value){\n            var rect = self.frame\n            rect.origin.y = value\n            self.frame = rect\n        }\n    }\n    public var width: CGFloat {\n        get{\n            return self.frame.size.width\n        }\n        set(value){\n            var rect = self.frame\n            rect.size.width = value\n            self.frame = rect\n        }\n    }\n    public var height: CGFloat {\n        get{\n            return self.frame.size.height\n        }\n        set(value){\n            var rect = self.frame\n            rect.size.height = value\n            self.frame = rect\n        }\n    }\n    public var bottom: CGFloat {\n        get{\n            return self.top + self.height\n        }\n        set(value){\n            self.top = value - self.height\n        }\n    }\n    public var right: CGFloat {\n        get{\n            return self.left + self.width\n        }\n        set(value){\n            self.left = value - self.width\n        }\n    }\n}\n\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/en.lproj/DoraemonKitDemo.strings",
    "content": "/* \n  DoraemonKitDemo.strings\n  DoKitSwiftDemo\n\n  Created by didi on 2020/5/13.\n  Copyright © 2020 didi. All rights reserved.\n*/\n{\n    //DoraemonKitDemo\n    \n    \n    //沙盒测试Demo\n    \"沙盒测试Demo\"                                 = \"Sandbox test Demo\";\n    \"添加一条json到沙盒中\"                     = \"Add json into sandbox\";\n    \"添加一张图片到沙盒中\"                   = \"Add image into sandbox\";\n    \"添加一段mp4到沙盒中\"                      = \"add mp4 into sandbox\";\n    \"添加doc、xlsx、pdf到沙盒中\"               = \"Add doc、xlsx、pdf into sandbox\";\n    \"添加html到沙盒中\"                           = \"Add html into sandbox\";\n    \"添加DB到沙盒中\"                             = \"Add DB into sandbox\";\n    \n    //日志测试Demo\n    \"日志测试Demo\"                                 = \"Log test Demo\";\n    \"添加一条NSLog日志\"                          = \"Add NSLog log\";\n    \"添加一条CocoaLumberjack日志\"                = \"Add CocoaLumberjack log\";\n    \n    //性能测试Demo\n    \"性能测试Demo\"                                 = \"Performance Test Demo\";\n    \"低FPS操作打开\"                               = \"Low FPS operation\";\n    \"高CPU操作打开\"                               = \"High CPU operation\";\n    \"高CPU操作关闭\"                               = \"High CPU operation off\";\n    \"高内存操作打开\"                            = \"High memory operation\";\n    \"高内存操作关闭\"                            = \"High memory operation off\";\n    \"高流量操作打开\"                            = \"High network traffic operation\";\n    \"卡顿操作打开\"                               = \"ANR operation\";\n    \n    //视觉测试Demo\n    \"视觉测试Demo\"                                 = \"UI test Demo\";\n    \"我是来测试的\"                               = \"Test\";\n    \n    //网络测试Demo\n    \"网络测试Demo\"                                 = \"Networking test Demo\";\n    \"发送一条URLConnection请求\"                  = \"Send URLConnection request\";\n    \"发送一条NSURLSession请求\"                   = \"Send NSURLSession request\";\n    \"发送一条AFNetworking请求\"                   = \"Send AFNetworking request\";\n    \"发送一条AFNetworking请求2\"                  = \"Send AFNetworking request2\";\n    \"打开UIWebView\"                                  = \"Open UIWebView\";\n    \"打开WKWebView\"                                  = \"Open WKWebView\";\n    \"图片测试\"                              = \"Image Test\";\n    \"Mock测试\"                              = \"Mock Test\";\n    \"Mock测试2\"                             = \"Mock Test 2\";\n    \n    //模拟位置Demo\n    \"模拟位置Demo\"                                 = \"Mock GPS Demo\";\n    \"模拟位置\"                                     = \"Mock GPS\";\n    \n    //crash\n    \"crash触发Demo\"                                  = \"Crash Demo\";\n    \"uncaughtException\"                                = \"uncaughtException\";\n    \"signalException\"                                  = \"signalException\";\n    \n    //通用测试Demo\n    \"通用测试Demo\"                                 = \"Common test Demo\";\n    \"子线程UI操作\"                                = \"Sub Thread UI\";\n    \"显示入口\"                                   = \"Show Doraemon\";\n    \"隐藏入口\"                                   = \"Hidden Doraemon\";\n    \n    //测试泄漏测试\n    \"内存泄漏测试\"                            = \"Memory Leak Test\";\n    \n    //Other\n    \"block方式加入插件\"                       = \"Add plugins from block\";\n    \"测试插件\"                                     = \"Test plugin\";\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/zh-Hans.lproj/DoraemonKitDemo.strings",
    "content": "/* \n  DoraemonKitDemo.strings\n  DoKitSwiftDemo\n\n  Created by didi on 2020/5/13.\n  Copyright © 2020 didi. All rights reserved.\n*/\n{\n    \n    //DoraemonKitDemo\n    \n    \n    //沙盒测试Demo\n    \"沙盒测试Demo\"                                 = \"沙盒测试Demo\";\n    \"添加一条json到沙盒中\"                     = \"添加一条json到沙盒中\";\n    \"添加一张图片到沙盒中\"                   = \"添加一张图片到沙盒中\";\n    \"添加一段mp4到沙盒中\"                      = \"添加一段mp4到沙盒中\";\n    \"添加doc、xlsx、pdf到沙盒中\"               = \"添加doc、xlsx、pdf到沙盒中\";\n    \"添加html到沙盒中\"                           = \"添加html到沙盒中\";\n    \"添加DB到沙盒中\"                             = \"添加DB到沙盒中\";\n    \n    //日志测试Demo\n    \"日志测试Demo\"                                 = \"日志测试Demo\";\n    \"添加一条NSLog日志\"                          = \"添加一条NSLog日志\";\n    \"添加一条CocoaLumberjack日志\"                = \"添加一条CocoaLumberjack日志\";\n    \n    //性能测试Demo\n    \"性能测试Demo\"                                 = \"性能测试Demo\";\n    \"低FPS操作打开\"                               = \"低FPS操作打开\";\n    \"高CPU操作打开\"                               = \"高CPU操作打开\";\n    \"高CPU操作关闭\"                               = \"高CPU操作关闭\";\n    \"高内存操作打开\"                            = \"高内存操作打开\";\n    \"高内存操作关闭\"                            = \"高内存操作关闭\";\n    \"高流量操作打开\"                            = \"高流量操作打开\";\n    \"卡顿操作打开\"                               = \"卡顿操作打开\";\n    \n    //视觉测试Demo\n    \"视觉测试Demo\"                                 = \"视觉测试Demo\";\n    \"我是来测试的\"                               = \"我是来测试的\";\n    \n    //网络测试Demo\n    \"网络测试Demo\"                                 = \"网络测试Demo\";\n    \"发送一条URLConnection请求\"                  = \"发送一条URLConnection请求\";\n    \"发送一条NSURLSession请求\"                   = \"发送一条NSURLSession请求\";\n    \"发送一条AFNetworking请求\"                   = \"发送一条AFNetworking请求\";\n    \"发送一条AFNetworking请求2\"                  = \"发送一条AFNetworking请求2\";\n    \"打开UIWebView\"                                  = \"打开UIWebView\";\n    \"打开WKWebView\"                                  = \"打开WKWebView\";\n    \"图片测试\"                              = \"图片测试\";\n    \"Mock测试\"                              = \"Mock测试\";\n    \"Mock测试2\"                             = \"Mock测试2\";\n    \n    //模拟位置Demo\n    \"模拟位置Demo\"                                 = \"模拟位置Demo\";\n    \"模拟位置\"                                     = \"模拟位置\";\n    \n    //crash\n    \"crash触发Demo\"                                  = \"crash触发Demo\";\n    \"uncaughtException\"                                = \"uncaughtException\";\n    \"signalException\"                                  = \"signalException\";\n    \n    //通用测试Demo\n    \"通用测试Demo\"                                 = \"通用测试Demo\";\n    \"子线程UI操作\"                                = \"子线程UI操作\";\n    \"显示入口\"                                   = \"显示入口\";\n    \"隐藏入口\"                                   = \"隐藏入口\";\n    \n    //测试泄漏测试\n    \"内存泄漏测试\"                            = \"内存泄漏测试\";\n    \n    //Other\n    \"block方式加入插件\"                       = \"block方式加入插件\";\n    \"测试插件\"                                     = \"测试插件\";\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/zh-Hans.lproj/LaunchScreen.strings",
    "content": "\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo/zh-Hans.lproj/Main.strings",
    "content": "\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/DoKitSwiftDemo.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 51;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t0A652E0A24726E0E00BC12A8 /* DoraemonDemoUIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A652E0924726E0E00BC12A8 /* DoraemonDemoUIViewController.swift */; };\n\t\t0A652E0D24727B5700BC12A8 /* DoraemonDemoNetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A652E0C24727B5700BC12A8 /* DoraemonDemoNetViewController.swift */; };\n\t\t0A652E122472905700BC12A8 /* DoraemonDemoOCViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A652E112472905700BC12A8 /* DoraemonDemoOCViewController.m */; };\n\t\t0A652E1C2472B37900BC12A8 /* DoraemonDemoGPSViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A652E1B2472B37900BC12A8 /* DoraemonDemoGPSViewController.swift */; };\n\t\t0A652E1E2472B78700BC12A8 /* DoraemonDemoGPSAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A652E1D2472B78700BC12A8 /* DoraemonDemoGPSAnnotation.swift */; };\n\t\t0A652E2124737A6100BC12A8 /* DoraemonDemoCrashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A652E2024737A6100BC12A8 /* DoraemonDemoCrashViewController.swift */; };\n\t\t0A652E2424738A4600BC12A8 /* DoraemonDemoCommonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A652E2324738A4600BC12A8 /* DoraemonDemoCommonViewController.swift */; };\n\t\t0A652E272473ADE000BC12A8 /* DoraemonDemoMemoryLeakViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A652E262473ADE000BC12A8 /* DoraemonDemoMemoryLeakViewController.swift */; };\n\t\t0A652E292473B2DA00BC12A8 /* DoraemonDemoSwiftViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A652E282473B2DA00BC12A8 /* DoraemonDemoSwiftViewController.swift */; };\n\t\t0A652E2B2473BE5800BC12A8 /* DoraemonDemoMemoryLeakView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A652E2A2473BE5800BC12A8 /* DoraemonDemoMemoryLeakView.swift */; };\n\t\t0A652E2D2473C44D00BC12A8 /* DoraemonDemoMemoryLeakModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A652E2C2473C44D00BC12A8 /* DoraemonDemoMemoryLeakModel.swift */; };\n\t\t0AC750A0246BD56400F87363 /* DoraemonDemoBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC7509F246BD56400F87363 /* DoraemonDemoBaseViewController.swift */; };\n\t\t0AC750A2246BD5C000F87363 /* DoraemonDemoHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC750A1246BD5C000F87363 /* DoraemonDemoHomeViewController.swift */; };\n\t\t0AC750A5246BD90900F87363 /* TestPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC750A4246BD90900F87363 /* TestPlugin.swift */; };\n\t\t0AC750A8246BDA2700F87363 /* DoraemonDemoi18Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC750A7246BDA2700F87363 /* DoraemonDemoi18Util.swift */; };\n\t\t0AC750AB246BDC6300F87363 /* DoraemonKitDemo.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0AC750AD246BDC6300F87363 /* DoraemonKitDemo.strings */; };\n\t\t0AC750B2246BEA6D00F87363 /* DoraemonDemoDefine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC750B1246BEA6D00F87363 /* DoraemonDemoDefine.swift */; };\n\t\t0AC750B5246BF5C500F87363 /* DoraemonDemoSanboxViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC750B4246BF5C500F87363 /* DoraemonDemoSanboxViewController.swift */; };\n\t\t0AC750B7246BF9D700F87363 /* UIColor+DoKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC750B6246BF9D700F87363 /* UIColor+DoKit.swift */; };\n\t\t0AC750B9246BFA8500F87363 /* UIView+DoKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC750B8246BFA8500F87363 /* UIView+DoKit.swift */; };\n\t\t0AC750BB246CF4C500F87363 /* Dictionary+DoKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC750BA246CF4C500F87363 /* Dictionary+DoKit.swift */; };\n\t\t0AC750CF246D411400F87363 /* Doraemon.xlsx in Resources */ = {isa = PBXBuildFile; fileRef = 0AC750C9246D411300F87363 /* Doraemon.xlsx */; };\n\t\t0AC750D0246D411400F87363 /* huoying.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 0AC750CA246D411300F87363 /* huoying.mp4 */; };\n\t\t0AC750D1246D411400F87363 /* WebpDemo.webp in Resources */ = {isa = PBXBuildFile; fileRef = 0AC750CB246D411300F87363 /* WebpDemo.webp */; };\n\t\t0AC750D2246D411400F87363 /* Doraemon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 0AC750CC246D411400F87363 /* Doraemon.pdf */; };\n\t\t0AC750D3246D411400F87363 /* doraemon.html in Resources */ = {isa = PBXBuildFile; fileRef = 0AC750CD246D411400F87363 /* doraemon.html */; };\n\t\t0AC750D4246D411400F87363 /* Doraemon.docx in Resources */ = {isa = PBXBuildFile; fileRef = 0AC750CE246D411400F87363 /* Doraemon.docx */; };\n\t\t0AC750D7246E3DCF00F87363 /* DoraemonDemoLoggerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC750D6246E3DCF00F87363 /* DoraemonDemoLoggerViewController.swift */; };\n\t\t0AC750DA246E7FE200F87363 /* DoraemonDemoPerformanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC750D9246E7FE200F87363 /* DoraemonDemoPerformanceViewController.swift */; };\n\t\t0AE1029E24693DAB006CA490 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE1029D24693DAB006CA490 /* AppDelegate.swift */; };\n\t\t0AE102A024693DAB006CA490 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE1029F24693DAB006CA490 /* SceneDelegate.swift */; };\n\t\t0AE102A524693DAB006CA490 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0AE102A324693DAB006CA490 /* Main.storyboard */; };\n\t\t0AE102A724693DAD006CA490 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0AE102A624693DAD006CA490 /* Assets.xcassets */; };\n\t\t0AE102AA24693DAD006CA490 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0AE102A824693DAD006CA490 /* LaunchScreen.storyboard */; };\n\t\tDB2B059C3CCF6E7DB1873738 /* Pods_DoKitSwiftDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B7739F27405C3590224A6DCE /* Pods_DoKitSwiftDemo.framework */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t0A652E0924726E0E00BC12A8 /* DoraemonDemoUIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoUIViewController.swift; sourceTree = \"<group>\"; };\n\t\t0A652E0C24727B5700BC12A8 /* DoraemonDemoNetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoNetViewController.swift; sourceTree = \"<group>\"; };\n\t\t0A652E0F2472905700BC12A8 /* DoKitSwiftDemo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = \"DoKitSwiftDemo-Bridging-Header.h\"; sourceTree = \"<group>\"; };\n\t\t0A652E102472905700BC12A8 /* DoraemonDemoOCViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoraemonDemoOCViewController.h; sourceTree = \"<group>\"; };\n\t\t0A652E112472905700BC12A8 /* DoraemonDemoOCViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoraemonDemoOCViewController.m; sourceTree = \"<group>\"; };\n\t\t0A652E1B2472B37900BC12A8 /* DoraemonDemoGPSViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoGPSViewController.swift; sourceTree = \"<group>\"; };\n\t\t0A652E1D2472B78700BC12A8 /* DoraemonDemoGPSAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoGPSAnnotation.swift; sourceTree = \"<group>\"; };\n\t\t0A652E2024737A6100BC12A8 /* DoraemonDemoCrashViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoCrashViewController.swift; sourceTree = \"<group>\"; };\n\t\t0A652E2324738A4600BC12A8 /* DoraemonDemoCommonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoCommonViewController.swift; sourceTree = \"<group>\"; };\n\t\t0A652E262473ADE000BC12A8 /* DoraemonDemoMemoryLeakViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoMemoryLeakViewController.swift; sourceTree = \"<group>\"; };\n\t\t0A652E282473B2DA00BC12A8 /* DoraemonDemoSwiftViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoSwiftViewController.swift; sourceTree = \"<group>\"; };\n\t\t0A652E2A2473BE5800BC12A8 /* DoraemonDemoMemoryLeakView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoMemoryLeakView.swift; sourceTree = \"<group>\"; };\n\t\t0A652E2C2473C44D00BC12A8 /* DoraemonDemoMemoryLeakModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoMemoryLeakModel.swift; sourceTree = \"<group>\"; };\n\t\t0AC7509F246BD56400F87363 /* DoraemonDemoBaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoBaseViewController.swift; sourceTree = \"<group>\"; };\n\t\t0AC750A1246BD5C000F87363 /* DoraemonDemoHomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoHomeViewController.swift; sourceTree = \"<group>\"; };\n\t\t0AC750A4246BD90900F87363 /* TestPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestPlugin.swift; sourceTree = \"<group>\"; };\n\t\t0AC750A7246BDA2700F87363 /* DoraemonDemoi18Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoi18Util.swift; sourceTree = \"<group>\"; };\n\t\t0AC750AC246BDC6300F87363 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/DoraemonKitDemo.strings; sourceTree = \"<group>\"; };\n\t\t0AC750AE246BDCA600F87363 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = \"zh-Hans\"; path = \"zh-Hans.lproj/Main.strings\"; sourceTree = \"<group>\"; };\n\t\t0AC750AF246BDCA600F87363 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = \"zh-Hans\"; path = \"zh-Hans.lproj/LaunchScreen.strings\"; sourceTree = \"<group>\"; };\n\t\t0AC750B0246BDCA700F87363 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = \"zh-Hans\"; path = \"zh-Hans.lproj/DoraemonKitDemo.strings\"; sourceTree = \"<group>\"; };\n\t\t0AC750B1246BEA6D00F87363 /* DoraemonDemoDefine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoDefine.swift; sourceTree = \"<group>\"; };\n\t\t0AC750B4246BF5C500F87363 /* DoraemonDemoSanboxViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoSanboxViewController.swift; sourceTree = \"<group>\"; };\n\t\t0AC750B6246BF9D700F87363 /* UIColor+DoKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"UIColor+DoKit.swift\"; sourceTree = \"<group>\"; };\n\t\t0AC750B8246BFA8500F87363 /* UIView+DoKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"UIView+DoKit.swift\"; sourceTree = \"<group>\"; };\n\t\t0AC750BA246CF4C500F87363 /* Dictionary+DoKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"Dictionary+DoKit.swift\"; sourceTree = \"<group>\"; };\n\t\t0AC750C9246D411300F87363 /* Doraemon.xlsx */ = {isa = PBXFileReference; lastKnownFileType = file; path = Doraemon.xlsx; sourceTree = \"<group>\"; };\n\t\t0AC750CA246D411300F87363 /* huoying.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = huoying.mp4; sourceTree = \"<group>\"; };\n\t\t0AC750CB246D411300F87363 /* WebpDemo.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = WebpDemo.webp; sourceTree = \"<group>\"; };\n\t\t0AC750CC246D411400F87363 /* Doraemon.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = Doraemon.pdf; sourceTree = \"<group>\"; };\n\t\t0AC750CD246D411400F87363 /* doraemon.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = doraemon.html; sourceTree = \"<group>\"; };\n\t\t0AC750CE246D411400F87363 /* Doraemon.docx */ = {isa = PBXFileReference; lastKnownFileType = file; path = Doraemon.docx; sourceTree = \"<group>\"; };\n\t\t0AC750D6246E3DCF00F87363 /* DoraemonDemoLoggerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoLoggerViewController.swift; sourceTree = \"<group>\"; };\n\t\t0AC750D9246E7FE200F87363 /* DoraemonDemoPerformanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoraemonDemoPerformanceViewController.swift; sourceTree = \"<group>\"; };\n\t\t0AE1029A24693DAB006CA490 /* DoKitSwiftDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DoKitSwiftDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t0AE1029D24693DAB006CA490 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = \"<group>\"; };\n\t\t0AE1029F24693DAB006CA490 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = \"<group>\"; };\n\t\t0AE102A424693DAB006CA490 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = \"<group>\"; };\n\t\t0AE102A624693DAD006CA490 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t0AE102A924693DAD006CA490 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\t0AE102AB24693DAD006CA490 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t1AFC6EE9B77134AC5569E638 /* Pods-DoKitSwiftDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-DoKitSwiftDemo.debug.xcconfig\"; path = \"Target Support Files/Pods-DoKitSwiftDemo/Pods-DoKitSwiftDemo.debug.xcconfig\"; sourceTree = \"<group>\"; };\n\t\tB7739F27405C3590224A6DCE /* Pods_DoKitSwiftDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DoKitSwiftDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tCA80ADA0C49F110A66409112 /* Pods-DoKitSwiftDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-DoKitSwiftDemo.release.xcconfig\"; path = \"Target Support Files/Pods-DoKitSwiftDemo/Pods-DoKitSwiftDemo.release.xcconfig\"; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t0AE1029724693DAB006CA490 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tDB2B059C3CCF6E7DB1873738 /* Pods_DoKitSwiftDemo.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t0A652E0824726DF000BC12A8 /* UI */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0A652E0924726E0E00BC12A8 /* DoraemonDemoUIViewController.swift */,\n\t\t\t);\n\t\t\tpath = UI;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0A652E0B2472714F00BC12A8 /* Net */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0A652E0C24727B5700BC12A8 /* DoraemonDemoNetViewController.swift */,\n\t\t\t);\n\t\t\tpath = Net;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0A652E0E2472901800BC12A8 /* OC */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0A652E102472905700BC12A8 /* DoraemonDemoOCViewController.h */,\n\t\t\t\t0A652E112472905700BC12A8 /* DoraemonDemoOCViewController.m */,\n\t\t\t\t0A652E282473B2DA00BC12A8 /* DoraemonDemoSwiftViewController.swift */,\n\t\t\t);\n\t\t\tpath = OC;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0A652E172472B34500BC12A8 /* MockGPS */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0A652E1B2472B37900BC12A8 /* DoraemonDemoGPSViewController.swift */,\n\t\t\t\t0A652E1D2472B78700BC12A8 /* DoraemonDemoGPSAnnotation.swift */,\n\t\t\t);\n\t\t\tpath = MockGPS;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0A652E1F24737A2D00BC12A8 /* Crash */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0A652E2024737A6100BC12A8 /* DoraemonDemoCrashViewController.swift */,\n\t\t\t);\n\t\t\tpath = Crash;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0A652E2224738A3100BC12A8 /* Common */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0A652E2324738A4600BC12A8 /* DoraemonDemoCommonViewController.swift */,\n\t\t\t);\n\t\t\tpath = Common;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0A652E252473ADAE00BC12A8 /* MemoryLeak */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0A652E262473ADE000BC12A8 /* DoraemonDemoMemoryLeakViewController.swift */,\n\t\t\t\t0A652E2A2473BE5800BC12A8 /* DoraemonDemoMemoryLeakView.swift */,\n\t\t\t\t0A652E2C2473C44D00BC12A8 /* DoraemonDemoMemoryLeakModel.swift */,\n\t\t\t);\n\t\t\tpath = MemoryLeak;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AC7509D246BD51500F87363 /* Home */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0AC750A1246BD5C000F87363 /* DoraemonDemoHomeViewController.swift */,\n\t\t\t);\n\t\t\tpath = Home;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AC7509E246BD53800F87363 /* Base */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0AC7509F246BD56400F87363 /* DoraemonDemoBaseViewController.swift */,\n\t\t\t);\n\t\t\tpath = Base;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AC750A3246BD8E200F87363 /* Plugin */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0AC750A4246BD90900F87363 /* TestPlugin.swift */,\n\t\t\t);\n\t\t\tpath = Plugin;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AC750A6246BDA0D00F87363 /* Util */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0AC750A7246BDA2700F87363 /* DoraemonDemoi18Util.swift */,\n\t\t\t\t0AC750B1246BEA6D00F87363 /* DoraemonDemoDefine.swift */,\n\t\t\t\t0AC750B6246BF9D700F87363 /* UIColor+DoKit.swift */,\n\t\t\t\t0AC750B8246BFA8500F87363 /* UIView+DoKit.swift */,\n\t\t\t\t0AC750BA246CF4C500F87363 /* Dictionary+DoKit.swift */,\n\t\t\t);\n\t\t\tpath = Util;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AC750B3246BF57F00F87363 /* Sanbox */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0AC750B4246BF5C500F87363 /* DoraemonDemoSanboxViewController.swift */,\n\t\t\t);\n\t\t\tpath = Sanbox;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AC750BC246D40B400F87363 /* Resource */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0AC750CE246D411400F87363 /* Doraemon.docx */,\n\t\t\t\t0AC750CD246D411400F87363 /* doraemon.html */,\n\t\t\t\t0AC750CC246D411400F87363 /* Doraemon.pdf */,\n\t\t\t\t0AC750C9246D411300F87363 /* Doraemon.xlsx */,\n\t\t\t\t0AC750CA246D411300F87363 /* huoying.mp4 */,\n\t\t\t\t0AC750CB246D411300F87363 /* WebpDemo.webp */,\n\t\t\t);\n\t\t\tpath = Resource;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AC750D5246E3DA400F87363 /* Logger */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0AC750D6246E3DCF00F87363 /* DoraemonDemoLoggerViewController.swift */,\n\t\t\t);\n\t\t\tpath = Logger;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AC750D8246E7FA700F87363 /* Performance */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0AC750D9246E7FE200F87363 /* DoraemonDemoPerformanceViewController.swift */,\n\t\t\t);\n\t\t\tpath = Performance;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AE1029124693DAB006CA490 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0AE1029C24693DAB006CA490 /* DoKitSwiftDemo */,\n\t\t\t\t0AE1029B24693DAB006CA490 /* Products */,\n\t\t\t\t9F558445B4C1CC5511BB8827 /* Pods */,\n\t\t\t\t9084201A2BFB765675DE6B6F /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AE1029B24693DAB006CA490 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0AE1029A24693DAB006CA490 /* DoKitSwiftDemo.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AE1029C24693DAB006CA490 /* DoKitSwiftDemo */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t0A652E252473ADAE00BC12A8 /* MemoryLeak */,\n\t\t\t\t0A652E2224738A3100BC12A8 /* Common */,\n\t\t\t\t0A652E1F24737A2D00BC12A8 /* Crash */,\n\t\t\t\t0A652E172472B34500BC12A8 /* MockGPS */,\n\t\t\t\t0A652E0E2472901800BC12A8 /* OC */,\n\t\t\t\t0A652E0B2472714F00BC12A8 /* Net */,\n\t\t\t\t0A652E0824726DF000BC12A8 /* UI */,\n\t\t\t\t0AC750D8246E7FA700F87363 /* Performance */,\n\t\t\t\t0AC750D5246E3DA400F87363 /* Logger */,\n\t\t\t\t0AC750BC246D40B400F87363 /* Resource */,\n\t\t\t\t0AC750B3246BF57F00F87363 /* Sanbox */,\n\t\t\t\t0AC750A6246BDA0D00F87363 /* Util */,\n\t\t\t\t0AC750A3246BD8E200F87363 /* Plugin */,\n\t\t\t\t0AC7509E246BD53800F87363 /* Base */,\n\t\t\t\t0AC7509D246BD51500F87363 /* Home */,\n\t\t\t\t0AE1029D24693DAB006CA490 /* AppDelegate.swift */,\n\t\t\t\t0AE1029F24693DAB006CA490 /* SceneDelegate.swift */,\n\t\t\t\t0A652E0F2472905700BC12A8 /* DoKitSwiftDemo-Bridging-Header.h */,\n\t\t\t\t0AE102A324693DAB006CA490 /* Main.storyboard */,\n\t\t\t\t0AE102A624693DAD006CA490 /* Assets.xcassets */,\n\t\t\t\t0AE102A824693DAD006CA490 /* LaunchScreen.storyboard */,\n\t\t\t\t0AE102AB24693DAD006CA490 /* Info.plist */,\n\t\t\t\t0AC750AD246BDC6300F87363 /* DoraemonKitDemo.strings */,\n\t\t\t);\n\t\t\tpath = DoKitSwiftDemo;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t9084201A2BFB765675DE6B6F /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB7739F27405C3590224A6DCE /* Pods_DoKitSwiftDemo.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t9F558445B4C1CC5511BB8827 /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1AFC6EE9B77134AC5569E638 /* Pods-DoKitSwiftDemo.debug.xcconfig */,\n\t\t\t\tCA80ADA0C49F110A66409112 /* Pods-DoKitSwiftDemo.release.xcconfig */,\n\t\t\t);\n\t\t\tpath = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t0AE1029924693DAB006CA490 /* DoKitSwiftDemo */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 0AE102AE24693DAD006CA490 /* Build configuration list for PBXNativeTarget \"DoKitSwiftDemo\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tDC320A002ED71A2200D0A799 /* [CP] Check Pods Manifest.lock */,\n\t\t\t\t0AE1029624693DAB006CA490 /* Sources */,\n\t\t\t\t0AE1029724693DAB006CA490 /* Frameworks */,\n\t\t\t\t0AE1029824693DAB006CA490 /* Resources */,\n\t\t\t\t69356E1B92816D4FCAB695A6 /* [CP] Embed Pods Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = DoKitSwiftDemo;\n\t\t\tproductName = DoKitSwiftDemo;\n\t\t\tproductReference = 0AE1029A24693DAB006CA490 /* DoKitSwiftDemo.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t0AE1029224693DAB006CA490 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastSwiftUpdateCheck = 1140;\n\t\t\t\tLastUpgradeCheck = 1140;\n\t\t\t\tORGANIZATIONNAME = didi;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t0AE1029924693DAB006CA490 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 11.4.1;\n\t\t\t\t\t\tLastSwiftMigration = 1140;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 0AE1029524693DAB006CA490 /* Build configuration list for PBXProject \"DoKitSwiftDemo\" */;\n\t\t\tcompatibilityVersion = \"Xcode 9.3\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t\t\"zh-Hans\",\n\t\t\t);\n\t\t\tmainGroup = 0AE1029124693DAB006CA490;\n\t\t\tproductRefGroup = 0AE1029B24693DAB006CA490 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t0AE1029924693DAB006CA490 /* DoKitSwiftDemo */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t0AE1029824693DAB006CA490 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t0AC750D3246D411400F87363 /* doraemon.html in Resources */,\n\t\t\t\t0AE102AA24693DAD006CA490 /* LaunchScreen.storyboard in Resources */,\n\t\t\t\t0AC750D4246D411400F87363 /* Doraemon.docx in Resources */,\n\t\t\t\t0AC750D2246D411400F87363 /* Doraemon.pdf in Resources */,\n\t\t\t\t0AC750AB246BDC6300F87363 /* DoraemonKitDemo.strings in Resources */,\n\t\t\t\t0AC750CF246D411400F87363 /* Doraemon.xlsx in Resources */,\n\t\t\t\t0AC750D1246D411400F87363 /* WebpDemo.webp in Resources */,\n\t\t\t\t0AC750D0246D411400F87363 /* huoying.mp4 in Resources */,\n\t\t\t\t0AE102A724693DAD006CA490 /* Assets.xcassets in Resources */,\n\t\t\t\t0AE102A524693DAB006CA490 /* Main.storyboard in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t69356E1B92816D4FCAB695A6 /* [CP] Embed Pods Frameworks */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-DoKitSwiftDemo/Pods-DoKitSwiftDemo-frameworks-${CONFIGURATION}-input-files.xcfilelist\",\n\t\t\t);\n\t\t\tname = \"[CP] Embed Pods Frameworks\";\n\t\t\toutputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-DoKitSwiftDemo/Pods-DoKitSwiftDemo-frameworks-${CONFIGURATION}-output-files.xcfilelist\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-DoKitSwiftDemo/Pods-DoKitSwiftDemo-frameworks.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\tDC320A002ED71A2200D0A799 /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-DoKitSwiftDemo-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"diff \\\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\\\" \\\"${PODS_ROOT}/Manifest.lock\\\" > /dev/null\\nif [ $? != 0 ] ; then\\n    # print error to STDERR\\n    echo \\\"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\\\" >&2\\n    exit 1\\nfi\\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\\necho \\\"SUCCESS\\\" > \\\"${SCRIPT_OUTPUT_FILE_0}\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t0AE1029624693DAB006CA490 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t0AC750B9246BFA8500F87363 /* UIView+DoKit.swift in Sources */,\n\t\t\t\t0AC750BB246CF4C500F87363 /* Dictionary+DoKit.swift in Sources */,\n\t\t\t\t0A652E2124737A6100BC12A8 /* DoraemonDemoCrashViewController.swift in Sources */,\n\t\t\t\t0A652E2424738A4600BC12A8 /* DoraemonDemoCommonViewController.swift in Sources */,\n\t\t\t\t0A652E292473B2DA00BC12A8 /* DoraemonDemoSwiftViewController.swift in Sources */,\n\t\t\t\t0AC750A8246BDA2700F87363 /* DoraemonDemoi18Util.swift in Sources */,\n\t\t\t\t0AC750A2246BD5C000F87363 /* DoraemonDemoHomeViewController.swift in Sources */,\n\t\t\t\t0A652E1E2472B78700BC12A8 /* DoraemonDemoGPSAnnotation.swift in Sources */,\n\t\t\t\t0A652E272473ADE000BC12A8 /* DoraemonDemoMemoryLeakViewController.swift in Sources */,\n\t\t\t\t0AE1029E24693DAB006CA490 /* AppDelegate.swift in Sources */,\n\t\t\t\t0A652E0A24726E0E00BC12A8 /* DoraemonDemoUIViewController.swift in Sources */,\n\t\t\t\t0AE102A024693DAB006CA490 /* SceneDelegate.swift in Sources */,\n\t\t\t\t0AC750A5246BD90900F87363 /* TestPlugin.swift in Sources */,\n\t\t\t\t0AC750A0246BD56400F87363 /* DoraemonDemoBaseViewController.swift in Sources */,\n\t\t\t\t0AC750B7246BF9D700F87363 /* UIColor+DoKit.swift in Sources */,\n\t\t\t\t0AC750D7246E3DCF00F87363 /* DoraemonDemoLoggerViewController.swift in Sources */,\n\t\t\t\t0A652E122472905700BC12A8 /* DoraemonDemoOCViewController.m in Sources */,\n\t\t\t\t0AC750DA246E7FE200F87363 /* DoraemonDemoPerformanceViewController.swift in Sources */,\n\t\t\t\t0AC750B5246BF5C500F87363 /* DoraemonDemoSanboxViewController.swift in Sources */,\n\t\t\t\t0A652E2B2473BE5800BC12A8 /* DoraemonDemoMemoryLeakView.swift in Sources */,\n\t\t\t\t0A652E1C2472B37900BC12A8 /* DoraemonDemoGPSViewController.swift in Sources */,\n\t\t\t\t0A652E2D2473C44D00BC12A8 /* DoraemonDemoMemoryLeakModel.swift in Sources */,\n\t\t\t\t0A652E0D24727B5700BC12A8 /* DoraemonDemoNetViewController.swift in Sources */,\n\t\t\t\t0AC750B2246BEA6D00F87363 /* DoraemonDemoDefine.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXVariantGroup section */\n\t\t0AC750AD246BDC6300F87363 /* DoraemonKitDemo.strings */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t0AC750AC246BDC6300F87363 /* en */,\n\t\t\t\t0AC750B0246BDCA700F87363 /* zh-Hans */,\n\t\t\t);\n\t\t\tname = DoraemonKitDemo.strings;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AE102A324693DAB006CA490 /* Main.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t0AE102A424693DAB006CA490 /* Base */,\n\t\t\t\t0AC750AE246BDCA600F87363 /* zh-Hans */,\n\t\t\t);\n\t\t\tname = Main.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t0AE102A824693DAD006CA490 /* LaunchScreen.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t0AE102A924693DAD006CA490 /* Base */,\n\t\t\t\t0AC750AF246BDCA600F87363 /* zh-Hans */,\n\t\t\t);\n\t\t\tname = LaunchScreen.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXVariantGroup section */\n\n/* Begin XCBuildConfiguration section */\n\t\t0AE102AC24693DAD006CA490 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 13.4;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t0AE102AD24693DAD006CA490 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 13.4;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t0AE102AF24693DAD006CA490 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 1AFC6EE9B77134AC5569E638 /* Pods-DoKitSwiftDemo.debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEVELOPMENT_TEAM = 2M632FEG3H;\n\t\t\t\tINFOPLIST_FILE = DoKitSwiftDemo/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"javer-yi.DoKitSwiftDemo\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"DoKitSwiftDemo/DoKitSwiftDemo-Bridging-Header.h\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t0AE102B024693DAD006CA490 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = CA80ADA0C49F110A66409112 /* Pods-DoKitSwiftDemo.release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEVELOPMENT_TEAM = 2M632FEG3H;\n\t\t\t\tINFOPLIST_FILE = DoKitSwiftDemo/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"javer-yi.DoKitSwiftDemo\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"DoKitSwiftDemo/DoKitSwiftDemo-Bridging-Header.h\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t0AE1029524693DAB006CA490 /* Build configuration list for PBXProject \"DoKitSwiftDemo\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t0AE102AC24693DAD006CA490 /* Debug */,\n\t\t\t\t0AE102AD24693DAD006CA490 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t0AE102AE24693DAD006CA490 /* Build configuration list for PBXNativeTarget \"DoKitSwiftDemo\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t0AE102AF24693DAD006CA490 /* Debug */,\n\t\t\t\t0AE102B024693DAD006CA490 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 0AE1029224693DAB006CA490 /* Project object */;\n}\n"
  },
  {
    "path": "iOS/Swift/DoKitSwiftDemo/Podfile",
    "content": "source 'git@github.com:CocoaPods/Specs.git'\nplatform :ios, '8.0'\nuse_frameworks!\n\ntarget 'DoKitSwiftDemo' do\n  pod 'DoraemonKit', :subspecs => ['Core'],  :path => '../../../', :configurations => ['Debug']\n  pod 'DoraemonKit', :subspecs => ['WithGPS'],  :path => '../../../', :configurations => ['Debug']\n  pod 'DoraemonKit', :subspecs => ['WithMLeaksFinder'],  :path => '../../../', :configurations => ['Debug']\n  pod 'FBRetainCycleDetector', :git => 'https://github.com/facebook/FBRetainCycleDetector.git', :configurations => ['Debug']\n  pod 'AFNetworking'\nend\n"
  },
  {
    "path": "miniapp/.babelrc",
    "content": "{\n    \"presets\": [\"@babel/preset-env\"]\n}"
  },
  {
    "path": "miniapp/.gitignore",
    "content": "node_modules/\ndist/\nexample/dist/\n"
  },
  {
    "path": "miniapp/README.md",
    "content": "# DoKit微信小程序研发助手SDK升级\n\n## Doraemon mini program debugger\n\n一个支持小程序端的调试工具\n\n# 背景\n\n对于小程序开发者和测试同学来说，很多临时性的调试功能需要单独开发去支持，比如查看小程序信息，手机信息以及用户信息，扫码打开页面等。这些功能对于每个小程序都是相似的，而且遇到类似的需求时都需要进行单独开发。DoKit在移动端发展，获得了众多开发者的好评，其中不乏很多一线大厂(阿里，字节，腾讯，百度...)的身影，同时给我们带来了很多宝贵的经验。在广大开发者的要求下，我们重新启动了小程序端sdk的升级.\n\n此次版本升级主要提供了数据模拟功能，优化接入流程，降低用户接入成本，更好配合使用原生小程序以及第三方框架开发，提升开发同学的幸福感。\n\n# 简单总结\n\nDoKit小程序端调试工具，内置很多常用的工具，避免重复实现，一次接入，你将会拥有强大的工具集合。\n\n# 新增功能演示\n\n哆啦A梦小程序端apimock功能演示\n\n<div align=center>\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/4yXktFAckF1589275220286.png\">\n</div>\n\n> 在我们的[平台端](https://www.dokit.cn/)注册，即可使用该功能，在sdk接入部分会有详细介绍。\n\n<div align=center>\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/YfLpeuu2MH1590650509070.png\">\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/HDibtAjCqq1590651365296.png\">\n</div>\n\n点击工具中的数据模拟即可进入详情页，其中详情页分为mock数据和上传模板两块功能。\n\n> \u0001**mock数据:** 你可以通过打开指定接口的开关并选择相应的场景,此时你无需改变你的网络请求代码即可对你的指定接口进行拦截并返回你在平台创建的接口数据。\n\n> **上传模板:** 上传模板功能的适用场景是当你已经有了一个真实的接口，需要针对不同的用户场景进行测试但是同时接口返回的数据量比较庞大，所以我们为你提供了上传模板的功能。当你打开上传模板中指定接口的开关时，我们会拦截并保存你真实接口返回的数据并提供json预览功能。点击上传即可上传你的模板数据到Dokit平台端。\n\n> **实现原理:** 使用Object.defineProperty()劫持小程序的wx.request(),然后重写次方法，添加上url匹配拦截逻辑和上传模板逻辑。在平台端更新接口时，会和小程序本地数据合并，即还原原先在本地操作的记录。\n\n# 其他功能\n- app信息\n\n<div align=center>\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/Eu9DFjS6cf1590657590624.png\">\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/hHlPfUAPgu1590657697205.png\">\n</div>\n\n> 用于快速查看手机系统信息，小程序基本信息，用户信息，授权信息等基础信息，避免反复打开手机设置或者调用小程序原生api进行查看。\n\n- 位置模拟\n\n<div align=center>\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/zFDfYgfTFJ1590659245437.png\">\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/jYGvr0fsyI1590659269350.png\">\n</div>\n\n> 用于小程序端位置模拟，包括位置授权，位置查看，位置模拟，恢复位置设置等几大功能，可以通过简单的点击操作实现任意位置模拟和位置还原，该功能的实现原理是通过对wx.getLocation进行方法重写，进而进行位置模拟，位置模拟后，在小程序内所有调用位置查询的方法内都将返回你设定的位置，还原后将恢复原生方法\n\n- 缓存管理\n\n\n<div align=center>\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/MUMXoRhSFl1590667273847.png\">\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/GflYwbHHXO1590667273704.png\">\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/MM53oF1Wtj1590667274519.png\">\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/21JTvLDLp71590667274523.png\">\n</div>\n\n> 一个强大的缓存管理面板，集成了对缓存的所有操作功能，包括设置缓存，清除缓存，更新缓存值等，可以在小程序非常便利的进行缓存管理\n\n- H5任意门\n<div align=center>\n  <img width=\"200\" height=\"350\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/ZWGSKwm2ZJ1590667512289.png\">\n</div>\n\n> 可以通过扫码和粘贴链接的方式在小程序中打开h5页面，操作简单方便\n\n- 更新版本\n\n> 当你的小程序进行代码更新时，为了获取最新的线上包需要重启小程序，该功能可以在你的小程序上通过点击更新操作，直接获取到最新的远程代码资源\n\n\n# 快速上手\n\n#### 如何接入\n\n如果您是使用原生小程序的开发方式，请安如下方式接入\n\n```\n1. 通过npm安装 npm install dokit-miniapp, 然后从node_modules中将dokit-miniapp文件夹拷贝到自己的项目中，然后按如下方式进行使用\n```\n\n```\n2. 在需要引用工具的页面 page.json 中引入组件(注意引用的路径)\n```\n\n```json\n\"usingComponents\": {\n  \"dokit\": \"../../components/dokit-miniapp/dist/index/index\"\n}\n```\n\n```html\n3. 在需要引用工具的页面 page.wxml 中引入使用组件\n<dokit projectId=\"your projectId\"></dokit>\n```\n\n如果您是使用小程序第三方框架的开发方式，可以做如下优化，\n\n```js\n在所需引入页面的js中，添加变量声明，例如\nconst isProd = process.env.NODE_ENV === '\"production\"'\n```\n\n```html\n在<template></template>或者是render函数中，可以使用\n\nisProd ? '' : <dokit projectId=\"your projectId\"></dokit>\n```\n\n```js\n如果框架暴露了webpack的相关打包配置，可以按照这样的配置，优化资源打包\ncompile: {\n      exclude: [\n        path.resolve(__dirname, '..', 'src/components/dokit-miniapp')\n      ]\n    }\n```\n\n> Tip: 1.由于微信小程序暂不支持开发环境和生产环境判断，请在生产环境手动删掉引用\n\n> Tip: 2.第三方框架开始，要注意框架是否将process.env.NODE_ENV注入到的全局变量中，此外有的框架的兼容性并不友好，有些打包配置并没有支持，开发者要视情况而定\n\n\n# 后续规划\n1. 取色器\n2. 接口请求性能分析\n3. 授权开启管理工具\n\n\n# 贡献\n\n有任何意见或建议都欢迎提 issue\n\n# github地址\n\nhttps://github.com/didi/DoraemonKit/tree/master/miniapp 欢迎star\n\n# 欢迎加入DoKit交流QQ群\n\n<div align=center>\n  <img width=\"190\" height=\"260\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/MDnNgukM511590736490933.jpg\">\n</div>\n\n# 欢迎关注我们的公众号\n\n<div align=center>\n  <img width=\"190\" height=\"200\" src=\"https://pt-starimg.didistatic.com/static/starimg/img/pYXiYtMEOl1591177821127.jpeg\">\n</div>"
  },
  {
    "path": "miniapp/example/app.js",
    "content": "//app.js\nApp({\n  onLaunch: function () {\n    // 展示本地存储能力\n    var logs = wx.getStorageSync('logs') || []\n    logs.unshift(Date.now())\n    wx.setStorageSync('logs', logs)\n\n    // 登录\n    wx.login({\n      success: res => {\n        // 发送 res.code 到后台换取 openId, sessionKey, unionId\n      }\n    })\n    // 获取用户信息\n    wx.getSetting({\n      success: res => {\n        if (res.authSetting['scope.userInfo']) {\n          // 已经授权，可以直接调用 getUserInfo 获取头像昵称，不会弹框\n          wx.getUserInfo({\n            success: res => {\n              // 可以将 res 发送给后台解码出 unionId\n              this.globalData.userInfo = res.userInfo\n\n              // 由于 getUserInfo 是网络请求，可能会在 Page.onLoad 之后才返回\n              // 所以此处加入 callback 以防止这种情况\n              if (this.userInfoReadyCallback) {\n                this.userInfoReadyCallback(res)\n              }\n            }\n          })\n        }\n      }\n    })\n  },\n  globalData: {\n    userInfo: null,\n    projectId: '749a0600b5e48dd77cf8ee680be7b1b7'\n  }\n})"
  },
  {
    "path": "miniapp/example/app.json",
    "content": "{\n  \"pages\": [\n    \"pages/index/index\",\n    \"pages/logs/logs\"\n  ],\n  \"window\": {\n    \"backgroundTextStyle\": \"light\",\n    \"navigationBarBackgroundColor\": \"#fff\",\n    \"navigationBarTitleText\": \"WeChat\",\n    \"navigationBarTextStyle\": \"black\"\n  },\n  \"sitemapLocation\": \"sitemap.json\",\n  \"permission\": {\n    \"scope.userLocation\": {\n      \"desc\": \"你的位置信息将用于小程序位置接口的效果展示\"\n    }\n  }\n}\n"
  },
  {
    "path": "miniapp/example/app.wxss",
    "content": "/**app.wxss**/\n.container {\n  height: 100%;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: space-between;\n  padding: 200rpx 0;\n  box-sizing: border-box;\n} \n"
  },
  {
    "path": "miniapp/example/pages/index/index.js",
    "content": "//index.js\n//获取应用实例\nconst app = getApp()\n\nPage({\n  data: {\n    motto: 'Hello World',\n    userInfo: {},\n    hasUserInfo: false,\n    canIUse: wx.canIUse('button.open-type.getUserInfo')\n  },\n  //事件处理函数\n  bindViewTap: function() {\n    wx.navigateTo({\n      url: '../logs/logs'\n    })\n  },\n  sendRequest: function() {\n    wx.request({\n      url: 'http://jsonplaceholder.typicode.com/users/2?app=name',\n      success: (res) => {\n        this.setData({\n          motto: JSON.stringify(res.data)\n        })\n      }\n    })\n  },\n  sendDokitRequest: function () {\n    // Tip: 小程序测试\n    wx.request({\n      url: 'https://mock.dokit.cn/api/app/interface?name=zzy',\n      method: 'GET',\n      success: (res) => {\n        console.log('用户自定义',res.data)\n      }\n    })\n    // Tip: 小程序测试4-post加参数\n    // wx.request({\n    //   url: 'https://mock.dokit.cn/users/jtsky_copy_copy_copy',\n    //   data: '{name: aa}',\n    //   method: 'POST',\n    //   success: (res) => {\n    //     console.log('用户自定义',res.data)\n    //   }\n    // })\n  },\n  onLoad: function () {\n    if (app.globalData.userInfo) {\n      this.setData({\n        userInfo: app.globalData.userInfo,\n        hasUserInfo: true\n      })\n    } else if (this.data.canIUse){\n      // 由于 getUserInfo 是网络请求，可能会在 Page.onLoad 之后才返回\n      // 所以此处加入 callback 以防止这种情况\n      app.userInfoReadyCallback = res => {\n        this.setData({\n          userInfo: res.userInfo,\n          hasUserInfo: true\n        })\n      }\n    } else {\n      // 在没有 open-type=getUserInfo 版本的兼容处理\n      wx.getUserInfo({\n        success: res => {\n          app.globalData.userInfo = res.userInfo\n          this.setData({\n            userInfo: res.userInfo,\n            hasUserInfo: true\n          })\n        }\n      })\n    }\n  },\n  getUserInfo: function(e) {\n    console.log(e)\n    app.globalData.userInfo = e.detail.userInfo\n    this.setData({\n      userInfo: e.detail.userInfo,\n      hasUserInfo: true\n    })\n  }\n})\n"
  },
  {
    "path": "miniapp/example/pages/index/index.json",
    "content": "{\n  \"permission\": {\n    \"scope.userLocation\": {\n      \"desc\": \"你的位置信息将用于小程序位置接口的效果展示\"\n    },\n    \"scope.userInfo\": {\n      \"desc\": \"你的信息将用于展示\"\n    }\n  },\n  \"usingComponents\": {\n    \"dokit\": \"../../dist/index/index\"\n  }\n}\n"
  },
  {
    "path": "miniapp/example/pages/index/index.wxml",
    "content": "<!--index.wxml-->\n<view class=\"container\">\n  <view class=\"userinfo\">\n    <button wx:if=\"{{!hasUserInfo && canIUse}}\" open-type=\"getUserInfo\" bindgetuserinfo=\"getUserInfo\"> 获取头像昵称 </button>\n    <block wx:else>\n      <image bindtap=\"bindViewTap\" class=\"userinfo-avatar\" src=\"{{userInfo.avatarUrl}}\" mode=\"cover\"></image>\n      <text class=\"userinfo-nickname\">{{userInfo.nickName}}</text>\n    </block>\n  </view>\n  <view class=\"usermotto\">\n    <text class=\"user-motto\">{{motto}}</text>\n  </view>\n  <button class=\"operate-btn\" bindtap=\"sendRequest\">发个请求</button>\n  <button class=\"operate-btn\" bindtap=\"sendDokitRequest\">发个dokit.cn请求</button>\n  <dokit projectId=\"749a0600b5e48dd77cf8ee680be7b1b7\"></dokit>\n</view>\n"
  },
  {
    "path": "miniapp/example/pages/index/index.wxss",
    "content": "/**index.wxss**/\n.userinfo {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n}\n\n.userinfo-avatar {\n  width: 128rpx;\n  height: 128rpx;\n  margin: 20rpx;\n  border-radius: 50%;\n}\n\n.userinfo-nickname {\n  color: #aaa;\n}\n\n.usermotto {\n  margin-top: 100px;\n}"
  },
  {
    "path": "miniapp/example/pages/logs/logs.js",
    "content": "//logs.js\nconst util = require('../../utils/util.js')\n\nPage({\n  data: {\n    logs: []\n  },\n  onLoad: function () {\n    this.setData({\n      logs: (wx.getStorageSync('logs') || []).map(log => {\n        return util.formatTime(new Date(log))\n      })\n    })\n  }\n})\n"
  },
  {
    "path": "miniapp/example/pages/logs/logs.json",
    "content": "{\n  \"navigationBarTitleText\": \"查看启动日志\",\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "miniapp/example/pages/logs/logs.wxml",
    "content": "<!--logs.wxml-->\n<view class=\"container log-list\">\n  <block wx:for=\"{{logs}}\" wx:for-item=\"log\">\n    <text class=\"log-item\">{{index + 1}}. {{log}}</text>\n  </block>\n</view>\n"
  },
  {
    "path": "miniapp/example/pages/logs/logs.wxss",
    "content": ".log-list {\n  display: flex;\n  flex-direction: column;\n  padding: 40rpx;\n}\n.log-item {\n  margin: 10rpx;\n}\n"
  },
  {
    "path": "miniapp/example/project.config.json",
    "content": "{\n  \"description\": \"项目配置文件\",\n  \"packOptions\": {\n    \"ignore\": []\n  },\n  \"setting\": {\n    \"urlCheck\": false,\n    \"es6\": true,\n    \"enhance\": false,\n    \"postcss\": true,\n    \"preloadBackgroundData\": false,\n    \"minified\": true,\n    \"newFeature\": true,\n    \"coverView\": true,\n    \"nodeModules\": true,\n    \"autoAudits\": false,\n    \"showShadowRootInWxmlPanel\": true,\n    \"scopeDataCheck\": false,\n    \"uglifyFileName\": false,\n    \"checkInvalidKey\": true,\n    \"checkSiteMap\": true,\n    \"uploadWithSourceMap\": true,\n    \"compileHotReLoad\": false,\n    \"useMultiFrameRuntime\": true,\n    \"useApiHook\": true,\n    \"useApiHostProcess\": true,\n    \"babelSetting\": {\n      \"ignore\": [],\n      \"disablePlugins\": [],\n      \"outputPath\": \"\"\n    },\n    \"enableEngineNative\": false,\n    \"bundle\": false,\n    \"useIsolateContext\": true,\n    \"useCompilerModule\": true,\n    \"userConfirmedUseCompilerModuleSwitch\": false,\n    \"userConfirmedBundleSwitch\": false,\n    \"packNpmManually\": false,\n    \"packNpmRelationList\": [],\n    \"minifyWXSS\": true\n  },\n  \"compileType\": \"miniprogram\",\n  \"libVersion\": \"2.11.3\",\n  \"appid\": \"wxdae0660715fa1bf8\",\n  \"projectname\": \"dokit-sdk-example\",\n  \"debugOptions\": {\n    \"hidedInDevtools\": []\n  },\n  \"isGameTourist\": false,\n  \"simulatorType\": \"wechat\",\n  \"simulatorPluginLibVersion\": {},\n  \"condition\": {\n    \"search\": {\n      \"list\": []\n    },\n    \"conversation\": {\n      \"list\": []\n    },\n    \"plugin\": {\n      \"list\": []\n    },\n    \"game\": {\n      \"currentL\": -1,\n      \"list\": []\n    },\n    \"gamePlugin\": {\n      \"list\": []\n    },\n    \"miniprogram\": {\n      \"list\": [\n        {\n          \"id\": -1,\n          \"name\": \"dist/debug/debug\",\n          \"pathName\": \"dist/debug/debug\",\n          \"scene\": null\n        }\n      ]\n    }\n  }\n}"
  },
  {
    "path": "miniapp/example/sitemap.json",
    "content": "{\n  \"desc\": \"关于本文件的更多信息，请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html\",\n  \"rules\": [{\n  \"action\": \"allow\",\n  \"page\": \"*\"\n  }]\n}"
  },
  {
    "path": "miniapp/example/utils/util.js",
    "content": "const formatTime = date => {\n  const year = date.getFullYear()\n  const month = date.getMonth() + 1\n  const day = date.getDate()\n  const hour = date.getHours()\n  const minute = date.getMinutes()\n  const second = date.getSeconds()\n\n  return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')\n}\n\nconst formatNumber = n => {\n  n = n.toString()\n  return n[1] ? n : '0' + n\n}\n\nmodule.exports = {\n  formatTime: formatTime\n}\n"
  },
  {
    "path": "miniapp/gulpfile.babel.js",
    "content": "import path from 'path'\nimport gulp from 'gulp'\nimport babel from 'gulp-babel'\nimport uglify from 'gulp-uglify'\nimport less from 'gulp-less'\nimport cleanCSS from 'gulp-clean-css'\nimport rename from 'gulp-rename'\nimport postcss from 'gulp-postcss'\nimport cssnano from 'gulp-cssnano'\nimport util from 'gulp-util'\nimport through2 from 'through2'\nimport autoprefixer from 'autoprefixer'\nimport del from 'del'\n\n// 配置环境\nconst ENV = process.env.NODE_ENV\nconst isDev = ENV === 'development' || ENV === 'dev'\nconst isProd = ENV === 'production' || ENV === 'prod'\nconst buildPath = path.join(__dirname, isProd ? 'dist/' : 'example/dist/')\nconst format = isProd ? false : 'beautify'\n\nconst paths = {\n    styles: {\n        src: ['src/**/*.wxss'],\n        dest: buildPath,\n    },\n    scripts: {\n        src: 'src/**/*.js',\n        dest: buildPath,\n    },\n    copy: {\n        src: ['src/**', '!src/**/*.wxss', '!src/icon/fonts/**'],\n        dest: buildPath,\n    },\n}\n\n/**\n * 自定义插件 - px to rpx\n */\nconst px2Rpx = () => {\n\n    // 正则匹配\n    const pxReplace = (value = '') => {\n        const pxRegExp = /(\\d*\\.?\\d+)px/ig\n        const pxReplace = (strArg) => {\n            const str = parseFloat(strArg)\n            return str === 0 ? 0 : `${2 * str}rpx`\n        }\n        return value.replace(pxRegExp, pxReplace)\n    }\n\n    return through2.obj(function(file, encoding, cb) {\n\n        // 如果文件为空，不做任何操作，转入下一个操作，即下一个pipe\n        if (file.isNull()) {\n            this.push(file)\n            return cb()\n        }\n\n        // 插件不支持对stream直接操作，抛出异常\n        if (file.isStream()) {\n            this.emit('error', new util.PluginError('px2Rpx', 'Streaming not supported'))\n            return cb()\n        }\n\n        // 内容转换，处理好后，再转成 Buffer 形式\n        const content = pxReplace(file.contents.toString())\n\n        file.contents = typeof Buffer.from === 'function' ? Buffer.from(content) : new Buffer(content)\n\n        // 下面这两句基本是标配，可参考 through2 的 API\n        this.push(file)\n        cb()\n    })\n}\n\nexport const clean = () => del([buildPath])\n\nexport const styles = () => (\n    gulp\n        .src(paths.styles.src, { base: 'src' })\n        .pipe(less())\n        .pipe(px2Rpx())\n        .pipe(postcss())\n        .pipe(cleanCSS({ format }))\n        // .pipe(\n        //     cssnano({\n        //         zindex: false,\n        //         autoprefixer: false,\n        //         discardComments: { removeAll: true },\n        //     })\n        // )\n        .pipe(\n            rename((path) => (path.extname = '.wxss'))\n        )\n        .pipe(gulp.dest(paths.styles.dest))\n)\n\nexport const scripts = () => (\n    gulp.src(paths.scripts.src, { base: 'src' })\n        .pipe(babel())\n        .pipe(uglify({\n            compress: {\n                drop_console: true  // 过滤 console\n            }\n        }))\n        .pipe(gulp.dest(paths.scripts.dest))\n)\n\nexport const copy = () => (\n    gulp\n        .src(paths.copy.src, { base: 'src' })\n        .pipe(gulp.dest(paths.copy.dest))\n)\n\nconst watchFiles = () => {\n    gulp.watch(paths.styles.src, styles)\n    gulp.watch(paths.copy.src, copy)\n}\n\nexport { watchFiles as watch }\n\nexport default gulp.series(gulp.parallel(styles, copy), watchFiles)\n\nexport const build = gulp.series(clean, gulp.parallel(styles, copy), scripts)"
  },
  {
    "path": "miniapp/package.json",
    "content": "{\n  \"name\": \"dokit-miniapp\",\n  \"version\": \"0.0.4\",\n  \"description\": \"\",\n  \"main\": \"dist/index/index.js\",\n  \"miniprogram\": \"dokit\",\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.5.0\",\n    \"@babel/preset-env\": \"^7.5.0\",\n    \"@babel/register\": \"^7.4.4\",\n    \"cross-env\": \"1.0.8\",\n    \"del\": \"^5.0.0\",\n    \"gulp\": \"^4.0.2\",\n    \"gulp-babel\": \"^8.0.0\",\n    \"gulp-clean-css\": \"^3.10.0\",\n    \"gulp-cssnano\": \"^2.1.2\",\n    \"gulp-less\": \"^3.3.2\",\n    \"gulp-postcss\": \"^7.0.0\",\n    \"gulp-rename\": \"^1.2.2\",\n    \"gulp-uglify\": \"^3.0.2\",\n    \"gulp-util\": \"^3.0.8\",\n    \"postcss-font-base64\": \"^1.0.5\",\n    \"through2\": \"^2.0.3\"\n  },\n  \"files\": [\n    \"dist/*\",\n    \"src/*\",\n    \"docs/*\"\n  ],\n  \"scripts\": {\n    \"start\": \"cross-env NODE_ENV=development gulp\",\n    \"build\": \"cross-env NODE_ENV=production gulp build\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\"\n}\n"
  },
  {
    "path": "miniapp/postcss.config.js",
    "content": "module.exports = {\n\tplugins: [\n\t\trequire('postcss-font-base64')({\n\t\t\tmatch: { 'Ionicons': ['ionicons'] }\n\t\t}),\n\t\trequire('autoprefixer')\n\t]\n}"
  },
  {
    "path": "miniapp/src/components/apimock/apimock.js",
    "content": "const util = require('../../utils/util.js');\nconst img = require('../../utils/imgbase64')\nconst app = getApp()\nif (!Object.prototype.hasOwnProperty.call(app, 'originRequest') && Object.prototype.toString.apply(getApp().originRequest) !== \"[object Function]\") {\n    app.originRequest = wx.request\n}\nconst mockBaseUrl = 'https://mock.dokit.cn'\nComponent({\n    properties: {\n        projectId: {\n          type: String,\n          value: '',\n        }\n      },\n    data: {\n        mockList: [],\n        tplList: [],\n        curScene: '',\n        curNav: 'mock',\n        templateData: '',\n        urlId: '',\n        isShow: false,\n        img\n    },\n    lifetimes: {\n      created () {\n        console.log('app', app)\n      },\n      attached () {\n        this.pageInit()\n      },\n      detached () {\n        wx.setStorageSync('dokit-mocklist', this.data.mockList)\n        wx.setStorageSync('dokit-tpllist', this.data.tplList)\n      }\n    },\n    methods: {\n        onGoBack () {\n            this.triggerEvent('toggle', { componentType: 'dokit'})\n        },\n        request (options) {\n            return new Promise((resolve, reject) => {\n                app.originRequest({\n                    ...options,\n                    success: res => resolve(res),\n                    fail: err => reject(err)\n                })\n            })\n        },\n        pageInit () {\n            this.initList()\n            this.addRequestHooks()\n        },\n        getProjectId () {\n            if (!this.data.projectId) {\n                console.warn(\"您还没有设置 projectId，去快平台端体验吧：https://www.dokit.cn\")\n                return\n            } else {\n                return this.data.projectId\n            }\n        },\n        // 初始化mock列表\n        initList () {\n            const that = this\n            const opt = {\n                url: `${mockBaseUrl}/api/app/interface`,\n                method: 'GET',\n                data: { projectId: this.getProjectId(), isfull: 1 }\n            }\n            that.request(opt).then(res => {\n                const { data } = res.data\n                if (data && data.datalist && data.datalist.length) {\n                    that.updateMockList(data.datalist)\n                    that.updateTplList(data.datalist)\n                }\n            }).catch()\n        },\n        updateMockList (datalist) {\n            let list = [], that = this\n            const localMockList = wx.getStorageSync('dokit-mocklist')\n            if (localMockList && localMockList.length) {\n                list = that.mergeMockList(datalist, localMockList)\n            } else {\n                list = datalist.map(item => that.processMockItem(item))\n            }\n            that.setData({\n                mockList: list\n            }, () => {\n                console.log('dokit-mocklist', that.data.mockList)\n            })\n        },\n        updateTplList (datalist) {\n            let list = [], that = this\n            const localTplList = wx.getStorageSync('dokit-tpllist')\n            if (localTplList && localTplList.length) {\n                list = that.mergeTplList(datalist, localTplList)\n            } else {\n                list = datalist.map(item => that.processTplItem(item))\n            }\n            that.setData({\n                tplList: list\n            }, () => {\n                console.log('dokit-tpllist', that.data.tplList)\n            })\n        },\n        mergeMockList (datalist, localMockList) {\n            let list = [], that = this\n            for (let i = 0,iLen = datalist.length; i < iLen; i++) {\n                let dataItem = datalist[i]\n                let isHas = false\n                for (let j = 0, jLen = localMockList.length ; j < jLen; j++) {\n                    const mockItem = localMockList[j];\n                    if (dataItem._id == mockItem._id) {\n                        dataItem.hidden = mockItem.hidden\n                        dataItem.checked = mockItem.checked\n                        dataItem.query = dataItem.query ? JSON.stringify(dataItem.query) : '{}'\n                        dataItem.body = dataItem.body ? JSON.stringify(dataItem.body) : '{}'\n                        if (util.isArray(dataItem.sceneList) && dataItem.sceneList.length) {\n                            let isScene = false\n                            for (let k = 0,kLen = dataItem.sceneList.length; k < kLen; k++) {\n                                const nowSceneItem = dataItem.sceneList[k]\n                                for (let h = 0, hLen = mockItem.sceneList.length; h < hLen; h++) {\n                                   const preSceneItem = mockItem.sceneList[h]\n                                   if (nowSceneItem._id == preSceneItem._id && preSceneItem.checked) {\n                                        nowSceneItem.checked = preSceneItem.checked\n                                        isScene = true\n                                        break;\n                                   }\n                                }\n                                if (isScene) {\n                                    break;\n                                }\n                            }\n                            if (!isScene && dataItem.sceneList[0]) {\n                                dataItem.sceneList[0].checked = true\n                            }\n                        } else {\n                            dataItem.sceneList = []\n                        }\n                        isHas = true\n                        break;\n                    }\n                }\n                if (!isHas) {\n                    dataItem = that.processMockItem(dataItem)\n                }\n                list.push(dataItem)\n            }\n            return list\n        },\n        mergeTplList (datalist, localTplList) {\n            let list = [],that = this\n            for (let i = 0,iLen=datalist.length; i < iLen; i++) {\n                let dataItem = datalist[i]\n                let isHas = false\n                for (let j = 0, jLen = localTplList.length ; j < jLen; j++) {\n                    const tplItem = localTplList[j];\n                    if (dataItem._id == tplItem._id) {\n                        dataItem.hidden = tplItem.hidden\n                        dataItem.checked = tplItem.checked\n                        dataItem.templateData = tplItem.templateData ? tplItem.templateData : ''\n                        isHas = true\n                        break;\n                    }\n                }\n                if (!isHas) {\n                    dataItem = that.processTplItem(dataItem)\n                }\n                list.push(dataItem)\n            }\n            return list\n        },\n        processMockItem (item) {\n            return {\n                ...item,\n                hidden: true,\n                checked: false,\n                query: item.query ? JSON.stringify(item.query) : '{}',\n                body: item.body ? JSON.stringify(item.body) : '{}',\n                sceneList: util.isArray(item.sceneList) ? item.sceneList.map((sceneItem, sceneIndex) => ({\n                    ...sceneItem,\n                    checked: sceneIndex === 0 ? true:false\n                })) : []\n            }\n        },\n        processTplItem (item) {\n            return {\n                ...item,\n                hidden: true,\n                checked: false,\n                query: item.query ? JSON.stringify(item.query) : '{}',\n                body: item.body ? JSON.stringify(item.body) : '{}'\n            }\n        },\n        addRequestHooks () {\n            Object.defineProperty(wx,  \"request\" , { writable:  true });\n            console.group('addRequestHooks success')\n            const matchUrlRequest = this.matchUrlRequest.bind(this)\n            const matchUrlTpl = this.matchUrlTpl.bind(this)\n            wx.request = function (options) {\n                const opt = util.deepClone(options)\n                const originSuccessFn = options.success\n                const sceneId = matchUrlRequest(options)\n                if (sceneId) {\n                    options.url = `${mockBaseUrl}/api/app/scene/${sceneId}`\n                    console.group('request options', options)\n                    console.warn('被拦截了~')\n                }\n                options.success = function (res) {\n                    originSuccessFn(matchUrlTpl(opt, res))\n                }\n                app.originRequest(options)\n            }\n        },\n        onTabbar (event) {\n            const type = event.currentTarget.dataset.type\n        },\n        onNavChange (event) {\n            const type = event.currentTarget.dataset.type\n            this.setData({ curNav: type })\n        },\n        onExpand (evnet) {\n            const { index,type } = evnet.currentTarget.dataset\n            const curHidden = `${type}List[${index}].hidden`\n            this.setData({\n                [curHidden]: !this.data[`${type}List`][index].hidden\n            })\n        },\n        onToggleChecked (evnet) {\n            const { index,type } = evnet.currentTarget.dataset\n            const curChecked = `${type}List[${index}].checked`\n            this.setData({\n                [curChecked]: !this.data[`${type}List`][index].checked\n            })\n        },\n        onRadioChange (event) {\n            const { index, idx } = event.currentTarget.dataset\n            this.data.mockList[index].sceneList.map((sceneItem, sceneIndex) => {\n                if (sceneIndex == idx) {\n                    this.data.mockList[index].sceneList[sceneIndex].checked = true\n                } else {\n                    this.data.mockList[index].sceneList[sceneIndex].checked = false\n                }\n            })\n        },\n        matchUrlTpl (options, res) {\n            let curTplItem,that = this\n            if (!that.data.tplList.length) { return res }\n            for (let i=0,len=that.data.tplList.length;i<len;i++) {\n                curTplItem = that.data.tplList[i]\n                if (that.requestIsmatch(options, curTplItem) && curTplItem.checked && res.statusCode == 200) {\n                    that.data.tplList[i].templateData = res.data\n                }\n            }\n            wx.setStorageSync('dokit-tpllist', that.data.tplList)\n            return res\n        },\n        uploadTplData () {\n    \n        },\n        matchUrlRequest (options) {\n            let flag = false, curMockItem, sceneId;\n            if (!this.data.mockList.length) { return false }\n            for (let i = 0,len = this.data.mockList.length; i < len; i++) {\n                curMockItem = this.data.mockList[i]\n                if (this.requestIsmatch(options, curMockItem)) {\n                    flag = true\n                    break;\n                }\n            }\n            if (curMockItem.sceneList && curMockItem.sceneList.length) {\n                for (let j=0,jLen=curMockItem.sceneList.length; j<jLen; j++) {\n                    const curSceneItem = curMockItem.sceneList[j]\n                    if (curSceneItem.checked) {\n                        sceneId = curSceneItem._id\n                        break;\n                    }\n                }\n            } else {\n                sceneId = false\n            }\n            return flag && curMockItem.checked && sceneId\n        },\n        // judge url is match\n        requestIsmatch (options, mockItem) {\n            const path = util.getPartUrlByParam(options.url, 'path')\n            const query = util.getPartUrlByParam(options.url, 'query')\n            return this.urlMethodIsEqual(path, options.method, mockItem.path, mockItem.method) && this.requestParamsIsEqual(query, options.data, mockItem.query, mockItem.body)\n        },\n        // path && menthod is equal\n        urlMethodIsEqual (reqPath, reqMethod, mockPath, mockMethod) {\n            reqPath = reqPath ? `/${reqPath}` : ''\n            reqMethod = reqMethod || 'GET'\n            return (reqPath == mockPath) && (reqMethod.toUpperCase() == mockMethod.toUpperCase())\n        },\n        // 判断请求参数是否相同 \n        requestParamsIsEqual (reqQuery, reqBody, mockQuery, mockBody) {\n            reqQuery = util.search2Json(reqQuery)\n            reqBody = reqBody || {}\n            try {\n                return (JSON.stringify(reqQuery) == mockQuery) && (JSON.stringify(reqBody) == mockBody)\n            } catch (e) {\n                return false\n            }\n        },\n        onPreview (event) {\n            const that = this\n            const index = event.currentTarget.dataset.index\n            const { templateData, _id } = that.data.tplList[index]\n            if (templateData) {\n                let tplData\n                try {\n                    tplData = JSON.stringify(templateData, null, 4)\n                } catch (error) {}\n                that.setData({\n                    urlId:  _id,\n                    templateData: tplData,\n                    isShow: !that.data.isShow\n                })\n            } else {\n                wx.showToast({\n                    title: '没有模板数据哦~',\n                    image: '../assets/img/error.png',\n                    duration: 1000\n                })\n            }\n        },\n        onCancel () {\n            this.setData({\n                isShow: false\n            })\n        },\n        onUpload (event) {\n            const that = this\n            const index = event.currentTarget.dataset.index\n            let data = {}\n            if (index != undefined) {\n                const { _id, templateData } = that.data.tplList[index]\n                data = { \n                    id: _id, \n                    tempData: templateData,\n                    projectId: that.getProjectId()\n                }\n            } else {\n                data = { \n                    id: that.data.urlId, \n                    tempData: that.data.templateData,\n                    projectId: that.getProjectId()\n                }\n            }\n            const opt = {\n                url: `${mockBaseUrl}/api/app/interface`,\n                method: 'POST',\n                data\n            }\n            that.request(opt).then(res => {\n                wx.showToast({\n                    title: '上传成功!',\n                    icon: 'success',\n                    duration: 1000\n                })\n                that.data.isShow && that.onCancel()\n            }).catch()\n        }\n    }\n  });\n  "
  },
  {
    "path": "miniapp/src/components/apimock/apimock.json",
    "content": "{\n  \"navigationBarTitleText\": \"数据Mock\",\n  \"component\": true,\n  \"usingComponents\": {\n    \"back\": \"../../components/back/back\"\n  }\n}"
  },
  {
    "path": "miniapp/src/components/apimock/apimock.wxml",
    "content": "<view class=\"page-box\">\n  <view class=\"tab-bar\">\n    <view class=\"tab-bar-item\" data-type=\"api\" bindtap=\"onTabbar\">接口分组</view>\n    <view class=\"tab-bar-item\" data-type=\"status\" bindtap=\"onTabbar\">开关状态</view>\n  </view>\n  <view class=\"list-wrap\">\n    <scroll-view class='list-scroll' scroll-y style=\"height: 100%\">\n      <view hidden=\"{{curNav !== 'mock'}}\" class=\"mock-list\">\n        <view wx:for=\"{{ mockList }}\" wx:key=\"index\">\n          <view class=\"item-wrap\">\n            <view class=\"list-item\" data-index=\"{{ index }}\" data-type=\"mock\" catchtap=\"onExpand\">\n              <view class=\"item-name\">{{ item.name }}</view>\n              <switch class=\"switch\" color=\"#337CC4\" checked=\"{{ item.checked }}\" data-index=\"{{ index }}\" data-type=\"mock\" catchtap=\"onToggleChecked\" />\n            </view>\n          </view>\n          <view class=\"item-detail {{ mockList[index].hidden ? 'hidden' : '' }}\" >\n            <view class=\"item-req-desc\">path: {{ item.path }} </view>\n            <view class=\"item-req-desc\">query: {{ item.query }} </view>\n            <view class=\"item-req-desc\">body: {{ item.body }} </view>\n            <view class=\"item-req-desc\">group: {{ item.categoryName }}</view>\n            <view class=\"item-req-desc\">create person: {{ item.owner.name }} </view>\n            <view class=\"item-req-desc\">modify person: {{ item.curStatus && item.curStatus.operator && item.curStatus.operator.name }} </view>\n            <block wx:if=\"{{ item.sceneList && item.sceneList.length }}\">\n                <radio-group class=\"radio-group\">\n                  <radio class=\"radio\" \n                    wx:for=\"{{ item.sceneList }}\"\n                    catchtap=\"onRadioChange\"\n                    wx:for-item=\"sceneItem\" \n                    wx:for-index=\"idx\"\n                    data-index=\"{{ index }}\" \n                    data-idx=\"{{ idx }}\" \n                    color=\"#337CC4\" \n                    wx:key=\"idx\" \n                    value=\"{{ sceneItem.name }}\" \n                    checked=\"{{ sceneItem.checked }}\">\n                    <text>{{ sceneItem.name }}</text>\n                  </radio>\n                </radio-group>\n            </block>\n          </view>\n        </view>\n      </view>\n      <view hidden=\"{{curNav !== 'tpl'}}\" class=\"tpl-list\">\n        <view wx:for=\"{{ tplList }}\" wx:key=\"index\">\n          <view class=\"item-wrap\">\n            <view class=\"list-item\" data-index=\"{{ index }}\" data-type=\"tpl\" catchtap=\"onExpand\">\n              <view>{{ item.name }}</view>\n              <switch class=\"switch\" color=\"#337CC4\" checked=\"{{ item.checked }}\" data-index=\"{{ index }}\" data-type=\"tpl\" catchtap=\"onToggleChecked\" />\n            </view>\n          </view>\n          <view class=\"item-detail {{ tplList[index].hidden ? 'hidden' : '' }}\" >\n            <view class=\"item-req-desc\">path: {{ item.path }} </view>\n            <view class=\"item-req-desc\">query: {{ item.query }} </view>\n            <view class=\"item-req-desc\">body: {{ item.body }} </view>\n            <view class=\"item-req-desc\">group: {{ item.categoryName }}</view>\n            <view class=\"item-req-desc\">create person: {{ item.owner.name }} </view>\n            <view class=\"item-req-desc\">modify person: {{ item.curStatus && item.curStatus.operator && item.curStatus.operator.name }} </view>\n            <view class=\"item-req-desc\">本地是否存在mock模板数据: {{ item.templateData ? 'Y': 'N' }} </view>\n            <view class=\"item-btn-wrap\">\n                <view class=\"btn-item {{ item.templateData ? 'active': '' }}\" data-index=\"{{index}}\" bindtap=\"onPreview\">Preview</view>\n                <view class=\"btn-item {{ item.templateData ? 'active': '' }}\" data-index=\"{{index}}\" bindtap=\"onUpload\">Upload</view>\n            </view>\n          </view>\n        </view>\n      </view>\n    </scroll-view>\n  </view>\n  <view class=\"nav-bar\">\n    <view class=\"nav-bar-item {{curNav == 'mock' ? 'active':''}}\" data-type=\"mock\" bindtap=\"onNavChange\">\n      <image class=\"nav-bar-icon\" src=\"{{curNav == 'mock' ? img.mockactive:img.mock}}\" lazy-load=\"false\" />\n      <view>Mock数据</view>\n    </view>\n    <view class=\"nav-bar-item {{curNav == 'tpl' ? 'active':''}}\" data-type=\"tpl\" bindtap=\"onNavChange\">\n      <image class=\"nav-bar-icon\" src=\"{{curNav == 'tpl' ? img.tpluploadactive:img.tplupload}}\" lazy-load=\"false\" />\n      <view>上传模板</view>\n    </view>\n  </view>\n  <view wx:if=\"{{ isShow }}\" class=\"modal\">\n    <view class=\"modal-inner\">\n      <view class=\"content-area\">\n        {{ templateData }}\n      </view>\n      <view class=\"operate-area\">\n        <view class=\"operate-item\" bindtap=\"onUpload\">上传数据</view>\n        <view class=\"operate-item\" bindtap=\"onCancel\">取消</view>\n      </view>\n    </view>\n  </view>\n  <back bindreturn=\"onGoBack\"></back>\n</view>\n"
  },
  {
    "path": "miniapp/src/components/apimock/apimock.wxss",
    "content": ".page-box {\n    position: absolute;\n    top: 0;\n    right: 0;\n    left: 0;\n    width: 100%;\n    height:100vh;\n    display: flex;\n    flex-direction: column;\n    background: #F5F6F7;\n    z-index: 9998;\n}\n.switch {\n    zoom: 0.8;\n}\n.tab-bar {\n    width: 100%;\n    line-height: 132rpx;\n    height: 132rpx;\n    display: flex;\n    background-color: #FFFFFF;\n    flex: 0 0 132rpx;\n}\n.tab-bar-item {\n    width: 50%;\n    height: 100%;\n    line-height: 132rpx;\n    text-align: center;\n}\n.list-wrap {\n    margin-top: 12rpx;\n    padding-top: 12rpx;\n    flex: 1;\n    position: relative;\n}\n.list-scroll {\n    position: absolute;\n    left: 0;\n    right: 0;\n    top: 0;\n    bottom: 0;\n}\n.item-wrap {\n    box-sizing: border-box;\n    width: 100%;\n    padding: 0 32rpx;\n    background-color: #FFFFFF;\n}\n.list-item {\n    position: relative;\n    display: flex;\n    box-sizing: border-box;\n    justify-content: space-between;\n    width: 100%;\n    height: 104rpx;\n    align-items: center;\n    border-bottom: 1rpx solid #E5E5E5;\n}\n.list-item::after {\n    position: absolute;\n    content: '＞';\n    right: 10rpx;\n    font-size: 34rpx;\n    color: #CCC;\n}\n.item-name {\n    overflow: hidden;\n    text-overflow:ellipsis;\n    white-space: nowrap;\n}\n.switch {\n    margin-right: 72rpx;\n}\n.item-detail {\n    box-sizing: border-box;\n    padding: 0 32rpx;\n    transition: height 0.2s;\n    height: 400rpx;\n}\n.item-detail.hidden {\n    height: 0;\n    overflow: hidden;\n}\n.item-req-desc {\n    line-height: 46rpx;\n}\n.radio-group {\n    width: 100%;\n    margin-top: 12rpx;\n}\n.radio {\n    width: 50%;\n}\n.active-text {\n    color: #337CC4;\n}\n.nav-bar {\n    width: 100%;\n    display: flex;\n    flex: 0 0 100rpx;\n    border-top: 1rpx solid #e5e5e5;\n    background-color: white;\n}\n.nav-bar-item {\n    width: 50%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    color: #333333;\n    font-size: 20rpx;\n}\n.nav-bar-item.active {\n    color: #337CC4;\n}\n.nav-bar-icon {\n    display: block;\n    width: 40rpx;\n    height: 40rpx;\n}\n.item-btn-wrap {\n    display: flex;\n    align-items: flex-start;\n}\n.btn-item {\n    width: 25%;\n}\n.btn-item.active {\n    color: #337CC4;\n}\n.modal {\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    width: 100%;\n    background-color: rgba(0, 0, 0, .5);\n}\n.modal-inner {\n    margin: 50% auto 0;\n    transform: translateY(-25%);\n    width: 80%;\n    height: 80%;\n    background-color: white;\n}\n.operate-area {\n    display: flex;\n    width: 100%;\n    height: 100rpx;\n    background-color: #337CC4;\n    position: absolute;\n    bottom: 0;\n}\n.operate-item {\n    width: 50%;\n    text-align: center;\n    line-height: 100rpx;\n    color: white;\n}\n\n"
  },
  {
    "path": "miniapp/src/components/appinformation/appinformation.js",
    "content": "const {\n    setSystemInfo,\n    setUserInfo,\n    setAuthInfo\n} = require('./formatInfo.js');\n\nComponent({\n    data: {\n        systemInfo: [],\n        accountInfo: [],\n        userInfo: [],\n        authInfo: []\n    },\n    lifetimes: {\n        created () {\n          console.log('debug created')\n        },\n        attached () {\n            this.onGetSystemInfo();\n            this.onGetAccountInfo();\n            this.onGetUserInfo();\n            this.onGetAuthInfo();\n        },\n        detached () {\n          console.log('debug detached')\n        }\n    },\n    methods: {\n        onGetSystemInfo () {\n            const that = this;\n            wx.getSystemInfo({\n                success (res) {\n                    const systemInfo = setSystemInfo(res);\n                    that.setData({systemInfo});\n                }\n            })\n        },\n        onGetAccountInfo () {\n            const accountInfo = [\n                {\n                    name: \"小程序appid\",\n                    value: wx.getAccountInfoSync().miniProgram.appId\n                }\n            ];\n            this.setData({\n                accountInfo\n            })\n        },\n        onGetUserInfo () {\n            const that = this;\n            wx.getUserInfo({\n                success: function(res) {\n                    const userInfo = setUserInfo(res.userInfo);\n                    that.setData({\n                        userInfo\n                    });\n                }\n            });\n        },\n        onGetAuthInfo () {\n            const that = this;\n            wx.getSetting({\n                success (res) {\n                    const authInfo = setAuthInfo(res.authSetting);\n                    that.setData({\n                        authInfo\n                    });\n                }\n            })\n        },\n        onGoBack () {\n            this.triggerEvent('toggle', { componentType: 'dokit'})\n        }\n    }\n})"
  },
  {
    "path": "miniapp/src/components/appinformation/appinformation.json",
    "content": "{\n  \"component\": true,\n  \"usingComponents\": {\n    \"back\": \"../../components/back/back\"\n  }\n}"
  },
  {
    "path": "miniapp/src/components/appinformation/appinformation.wxml",
    "content": "<view class=\"page-box\">\n    <view class=\"appinfo-header\">系统信息</view>\n    <view class=\"appinfo-item\" wx:for=\"{{systemInfo}}\" wx:key=\"index\">\n        <text class=\"appinfo-title\">{{item.name}}</text>\n        <text class=\"appinfo-val\">{{item.value}}</text>\n    </view>\n    <view class=\"appinfo-header\">账号信息</view>\n    <view class=\"appinfo-item\" wx:for=\"{{accountInfo}}\" wx:key=\"index\">\n        <text class=\"appinfo-title\">{{item.name}}</text>\n        <text class=\"appinfo-val\">{{item.value}}</text>\n    </view>\n    <view class=\"appinfo-header\">用户信息</view>\n    <view class=\"appinfo-item\" wx:for=\"{{userInfo}}\" wx:key=\"index\">\n        <text class=\"appinfo-title\">{{item.name}}</text>\n        <image class=\"appinfo-val img\" wx:if=\"{{item.type === 'img'}}\" src=\"{{item.value}}\"></image>\n        <text wx:else class=\"appinfo-val\">{{item.value}}</text>\n    </view>\n    <view class=\"appinfo-header\">授权信息</view>\n    <view class=\"appinfo-item\" wx:for=\"{{authInfo}}\" wx:key=\"index\">\n        <text class=\"appinfo-title\">{{item.name}}</text>\n        <image class=\"appinfo-val img\" wx:if=\"{{item.type === 'img'}}\" src=\"{{item.value}}\"></image>\n        <text wx:else class=\"appinfo-val\">{{item.value}}</text>\n    </view>\n</view>\n<back bindreturn=\"onGoBack\"></back>\n"
  },
  {
    "path": "miniapp/src/components/appinformation/appinformation.wxss",
    "content": ".page-box {\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0;\n  background: #F5F6F7;\n  width: 100%;\n  min-height: 100%;\n  z-index: 9998;\n}\n.appinfo-header {\n  height:40rpx;\n  padding:0 32rpx;\n  line-height:40rpx;\n  font-size: 28rpx;\n  vertical-align:middle;\n  color:#337CC4;\n  margin-top: 50rpx;\n  margin-bottom: 24rpx;\n}\n.appinfo-item {\n  display:flex;\n  align-items:center;\n  font-size: 32rpx;\n  color: #333;\n  justify-content:space-between;\n  padding:0 32rpx;\n  height:104rpx;\n  border-bottom:1rpx solid #efefef;\n}\n\n.appinfo-val.img {\n  width: 80rpx;\n  height:80rpx;\n  display:inline-block;\n  overflow:hidden;\n  border-radius:50%;\n\n}\n"
  },
  {
    "path": "miniapp/src/components/appinformation/formatInfo.js",
    "content": "function bol2chn(bol) {\n    return bol ? \"是\" : \"否\";\n}\n\nfunction getGender(gender) {\n    return gender === 1 ? \"男\" : \"女\";\n}\n\nconst setSystemInfo = function (info) {\n    const getBenchMarkLevel = function (lev) {\n        if(lev === -2 || lev === 0) return \"该设备无法运行小游戏\";\n        if(lev === -1) return \"性能未知\";\n        if(lev>1 && lev<10) return \"低\";\n        if(lev >11 && lev < 25) return \"中\";\n        if(lev > 26 && lev < 50) return \"优\";\n    };\n    return [\n        {\n            name: \"设备品牌\",\n            value: info.brand,\n        },\n        {\n            name: \"设备型号\",\n            value: info.model,\n        },\n        {\n            name: \"设备像素比\",\n            value: info.pixelRatio,\n        },\n        {\n            name: \"屏幕宽高\",\n            value: info.screenWidth + '/' + info.screenHeight,\n        },\n        {\n            name: \"可视区域宽高\",\n            value: info.windowWidth + '/' + info.windowHeight,\n        },\n        {\n            name: \"微信版本号\",\n            value: info.version\n        },\n        {\n            name: \"操作系统及版本号\",\n            value: info.system\n        },\n        {\n            name: \"客户端平台\",\n            value: info.platform\n        },\n        {\n            name: '设备性能值',\n            value: getBenchMarkLevel(info.benchmarkLevel)\n        },\n        {\n            name: \"允许微信使用摄像头的开关\",\n            value: bol2chn(info.cameraAuthorized)\n        },\n        {\n            name: \"允许微信使用定位的开关\",\n            value: bol2chn(info.locationAuthorized)\n        },\n        {\n            name: \"允许微信使用麦克风的开关\",\n            value: bol2chn(info.microphoneAuthorized)\n        },\n        {\n            name: \"允许微信通知的开关\",\n            value: bol2chn(info.notificationAuthorized)\n        },\n        {\n            name: \"地理位置的系统开关\",\n            value: bol2chn(info.locationEnabled)\n        },\n        {\n            name: \"Wi-Fi 的系统开关\",\n            value: bol2chn(info.wifiEnabled)\n        }\n    ]\n};\nconst setUserInfo = function (info) {\n    return [\n        {\n            name: \"昵称\",\n            value: info.nickName\n        },\n        {\n            name: \"性别\",\n            value: getGender(info.gender)\n        },\n        {\n            name: \"地区\",\n            value: info.city + \" \" + info.province + \" \" + info.country\n        },\n        {\n            name: \"头像\",\n            type: \"img\",\n            value: info.avatarUrl\n        }\n    ]\n};\n\nconst setAuthInfo = function (info) {\n    return [\n        {\n            name: \"是否授权用户信息\",\n            value: bol2chn(info['scope.userInfo'])\n        },\n        {\n            name: \"是否授权地理位置\",\n            value: bol2chn(info['scope.userLocation'])\n        },\n        {\n            name: \"是否授权通讯地址\",\n            value: bol2chn(info['scope.address'])\n        },\n        {\n            name: \"是否授权发票抬头\",\n            value: bol2chn(info['scope.invoiceTitle'])\n        },\n        {\n            name: \"是否授权获取发票\",\n            value: bol2chn(info['scope.invoice'])\n        },\n        {\n            name: \"是否授权微信运动步数\",\n            value: bol2chn(info['scope.werun'])\n        },\n        {\n            name: \"是否授权录音功能\",\n            value: bol2chn(info['scope.record'])\n        },\n        {\n            name: \"是否授权保存到相册\",\n            value: bol2chn(info['scope.writePhotosAlbum'])\n        },\n        {\n            name: \"是否授权摄像头\",\n            value: bol2chn(info['scope.camera'])\n        }\n    ]\n}\n\nmodule.exports = {\n    setSystemInfo,\n    setUserInfo,\n    setAuthInfo\n};\n"
  },
  {
    "path": "miniapp/src/components/back/back.js",
    "content": "Component({\n  properties: {\n    top: {\n      type: String,\n      value: '0',\n    }\n  },\n  methods: {\n    onbackDokitEntry () {\n      this.triggerEvent('return')\n    }\n  }\n})\n"
  },
  {
    "path": "miniapp/src/components/back/back.json",
    "content": "{\n  \"component\": true,\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "miniapp/src/components/back/back.wxml",
    "content": "<cover-image\n    bindtap=\"onbackDokitEntry\"\n    data-type=\"debug\"\n    class=\"dokit-back\"\n    src=\"//pt-starimg.didistatic.com/static/starimg/img/W8OeOO6Pue1561556055823.png\"\n    style=\"top: {{ top }}\"\n></cover-image>"
  },
  {
    "path": "miniapp/src/components/back/back.wxss",
    "content": ".dokit-back {\n  position: absolute;\n  left: 50%;\n  transform: translateX(-50%);\n  width: 100rpx;\n  height: 100rpx;\n  z-index: 9999;\n}"
  },
  {
    "path": "miniapp/src/components/debug/debug.js",
    "content": "const img = require('../../utils/imgbase64')\nComponent({\n    data: {\n        logs: [],\n        tools: []\n    },\n    lifetimes: {\n        attached () {\n            console.log('debug attached')\n            this.setData({\n                tools: this.getTools()\n            });\n        }\n    },\n    methods: {\n        getTools() {\n            return [\n                {\n                    \"type\": \"common\",\n                    \"title\": \"常用工具\",\n                    \"tools\": [\n                        {\n                            \"title\": \"App信息\",\n                            \"image\": img.appinfoicon,\n                            \"type\": \"appinformation\"\n                        },\n                        {\n                            \"title\": \"位置模拟\",\n                            \"image\": img.gpsicon,\n                            \"type\": \"positionsimulation\"\n                        },\n                        {\n                            \"title\": \"缓存管理\",\n                            \"image\": img.saveicon,\n                            \"type\": \"storage\"\n                        },\n                        {\n                            \"title\": \"H5任意门\",\n                            \"image\": img.h5dooricon,\n                            \"type\": \"h5door\"\n                        },\n                        {\n                            \"title\": \"请求注射\",\n                            \"image\": img.injectoricon,\n                            \"type\": \"httpinjector\"\n                        },\n                        {\n                            \"title\": \"更新版本\",\n                            \"image\": img.updateversionicon,\n                            \"type\": \"onUpdate\",\n                        },\n                        {\n                            \"title\": \"数据\b模拟\",\n                            \"image\": img.apimockicon,\n                            \"type\": \"apimock\"\n                        },\n                        {\n\n                            \"title\": \"查看日志\",\n                            \"image\": img.apimockicon,\n                            \"type\": \"looklogs\"\n                        },\n                        {\n                            \"title\": \"page任意门\",\n                            \"image\": img.h5dooricon,\n                            \"type\": \"pagedoor\"\n                        }\n                    ]\n                }\n            ]\n        },\n        onUpdate () {\n            const updateManager = wx.getUpdateManager();\n            updateManager.onCheckForUpdate(function (res) {\n                if(!res.hasUpdate) {\n                    // 请求完新版本信息的回调\n                    wx.showModal({\n                        title: '更新提示',\n                        content: '当前已经是最新版本'\n                    })\n                }\n            });\n            updateManager.onUpdateReady(function () {\n                wx.showModal({\n                    title: '更新提示',\n                    content: '新版本已经准备好，是否重启应用？',\n                    success(res) {\n                        if (res.confirm) {\n                            // 新的版本已经下载好，调用 applyUpdate 应用新版本并重启\n                            updateManager.applyUpdate()\n                        }\n                    }\n                })\n            });\n            updateManager.onUpdateFailed(function () {\n                // 新版本下载失败\n            })\n        },\n        onToggle (event) {\n            const type = event.currentTarget.dataset.type;\n            if(type === 'onUpdate') {\n                this[type]();\n            } else {\n                this.triggerEvent('toggle', { componentType: type })\n            }\n        },\n        openSetting() {\n            wx.openSetting({\n                success: function(res) {\n                    console.log(res);\n                }\n            })\n        },\n        onGoBack () {\n            this.triggerEvent('toggle', { componentType: 'dokit'})\n        }\n    }\n})\n"
  },
  {
    "path": "miniapp/src/components/debug/debug.json",
    "content": "{\n  \"component\": true,\n  \"usingComponents\": {\n    \"back\": \"../../components/back/back\"\n  }\n}"
  },
  {
    "path": "miniapp/src/components/debug/debug.wxml",
    "content": "<view class=\"page-box\">\n  <view class=\"debug-container\">\n    <view wx:for=\"{{tools}}\" wx:key=\"index\" class=\"debug-collections card\">\n      <view class=\"debug-collections-title\">{{item.title}}</view>\n      <view class=\"debug-collections-main\">\n        <view wx:for=\"{{item.tools}}\"\n              wx:for-index=\"idx\"\n              wx:for-item=\"tool\"\n              wx:key=\"idx\"\n              data-type=\"{{tool.type}}\"\n              bindtap=\"onToggle\"\n              class=\"card-item\">\n          <!-- <cover-image\n              class=\"debug-item-image\"\n              src=\"{{tool.image}}\"\n          ></cover-image> -->\n          <image\n              class=\"debug-item-image\"\n              src=\"{{tool.image}}\"\n          ></image>\n          <text class=\"debug-text\">{{tool.title}}</text>\n        </view>\n      </view>\n    </view>\n  </view>\n  <back bindreturn=\"onGoBack\"></back>\n</view>\n\n"
  },
  {
    "path": "miniapp/src/components/debug/debug.wxss",
    "content": ".page-box{\n    position: absolute;\n    left: 0;\n    right: 0;\n    top: 0;\n    background: #F5F6F7;\n    width: 100%;\n    height:100vh;\n    z-index: 9998;\n}\n.debug-container{\n    margin-top: 20rpx;\n    width: 100%;\n}\n.debug-collections-main{\n    margin-top: 32rpx;\n    display: flex;\n    flex-flow: row wrap;\n}\n.card {\n    width:100%;\n    background:#FFFFFF;\n    box-sizing:border-box;\n    padding: 32rpx 55rpx 42rpx;\n}\n.card-item{\n    width:105rpx;\n    height:145rpx;\n    text-align: center;\n    margin-right:75rpx;\n}\n.card-item:nth-child(4n+4){\n    margin-right:0;\n}\n.debug-item-image {\n    display: block;\n    width: 68rpx;\n    height: 68rpx;\n    margin: 0 auto;\n}\n.debug-text{\n    font-size: 24rpx;\n}\n.log-list {\n    display: flex;\n    flex-direction: column;\n    padding: 40rpx;\n}\n.log-item {\n    margin: 10rpx;\n}\n"
  },
  {
    "path": "miniapp/src/components/h5door/h5door.js",
    "content": "const img = require('../../utils/imgbase64')\nComponent({\n    data: {\n        qrCodeUrl:'',\n        historyUrlList:[],\n        isShowWebView:false,\n        img\n    },\n    lifetimes: {\n      created () {\n      },\n      attached () {\n        this.getHistoryUrlList()\n      },\n      detached () {\n        console.log('detached')\n      }\n    },\n    methods: {\n        getHistoryUrlList (){\n            let urlArr = []\n            let result = wx.getStorageSync('h5door-url')\n            if(result){\n                urlArr = urlArr.concat(result.split(\",\"))\n            }else{\n                urlArr = []\n            }\n            this.setData({\n                historyUrlList:urlArr\n            })\n        },\n        setQrCode(event){\n            this.setData({\n                qrCodeUrl:event.target.dataset.qrCode\n            })\n        },\n        qrCodeArouse(){\n            let qrCodeObj = {\n                scanType:['qrCode'],\n                success:res=>{\n                    this.setData({qrCodeUrl:res.result})\n                    this.goWebview()\n                }\n            }\n            wx.scanCode(qrCodeObj)\n        },\n        textareaChange(event){\n            this.setData({\n                qrCodeUrl:event.detail.value\n            })\n        },\n        clearAll (){\n            // Todo: 清除 dokit\n            wx.clearStorageSync()\n            this.getHistoryUrlList()\n        },\n        addUrlToStorage(){\n            let urlArr = this.data.historyUrlList\n            urlArr.push(this.data.qrCodeUrl)\n            this.setData({\n                historyUrlList:urlArr\n            })\n            if(this.data.historyUrlList.length>0){\n                let newArr = new Set(this.data.historyUrlList)\n                this.setData({\n                    historyUrlList:[...newArr]\n                })\n                wx.setStorageSync('h5door-url', this.data.historyUrlList.join(\",\"))\n            }\n        },\n        goWebview(event){\n            if(!this.data.qrCodeUrl){\n                wx.showToast({title:'请输入跳转链接'})\n                return\n            }\n            this.addUrlToStorage()\n            this.setData({isShowWebView:true})\n    \n        },\n        onGoBack () {\n            this.triggerEvent('toggle', { componentType: 'dokit'})\n        }\n    }\n  });\n  "
  },
  {
    "path": "miniapp/src/components/h5door/h5door.json",
    "content": "{\n    \"component\": true,\n    \"usingComponents\": {\n      \"back\": \"../../components/back/back\"\n    }\n  }"
  },
  {
    "path": "miniapp/src/components/h5door/h5door.wxml",
    "content": "<view class=\"h5door-page\">\n    <view class=\"area-box\">\n        <textarea  class=\"url-box\" focus=\"true\" value=\"{{qrCodeUrl}}\" placeholder=\"请输入跳转链接\" maxlength=\"140\" placeholder-class=\"url-paceholder-text\"\n        bindinput=\"textareaChange\">\n        </textarea>\n    </view>\n    <image class=\"scaning-img\" src=\"{{ img.scanningicon }}\" bindtap=\"qrCodeArouse\"></image>\n    <view class=\"history-list\" wx:if=\"{{historyUrlList.length>0}}\">\n        <view class=\"history-content\">\n            <view class=\"history-item\" data-qr-code=\"{{item}}\" bindtap=\"setQrCode\" wx:for=\"{{historyUrlList}}\" wx:key=\"key\">\n                <image class=\"search-icon\" src=\"{{ img.searchicon }}\"></image>\n                {{item}}\n            </view>\n        </view>\n        <view class=\"clear-all\"  bindtap=\"clearAll\">清空搜索历史</view>\n    </view>\n    <button class=\"go-webview-button\" bindtap=\"goWebview\" size=\"default\" type=\"\" plain=\"default\" hover-stop-propagation=\"false\">\n        点击跳转\n    </button>\n</view>\n<web-view wx:if=\"{{isShowWebView}}\" src=\"{{qrCodeUrl}}\"> </web-view>\n<back top=\"50%\" bindreturn=\"onGoBack\"></back>\n"
  },
  {
    "path": "miniapp/src/components/h5door/h5door.wxss",
    "content": ".h5door-page {\n    position: absolute;\n    left: 0;\n    right: 0;\n    top: 0;\n    width: 100%;\n    height:100vh;\n    background-color: white;\n    z-index: 9998;\n}\n.h5door-page .url-box{\n    height: 358rpx;\n    width: 100%;\n    position: absolute;\n    left: 0;\n    top: 0;\n    z-index: 1;\n}\n.h5door-page .area-box{\n    width: 98%;\n    margin: 0 auto;\n    box-sizing: border-box;\n    border: 1px solid #efefef;\n    height: 358rpx;\n    position: relative;\n}\n.scaning-img {\n    position: absolute;\n    display: block;\n    right: 32rpx;\n    top: 375rpx;\n    height: 56rpx;\n    width: 56rpx;\n}\n.url-box .url-paceholder-text{\n    font-size: 32rpx;\n    color: #BEBEBE;\n}\n.h5door-page .history-list{\n    margin-top: 70rpx;\n    padding:0 20rpx;\n}\n.h5door-page .history-content{\n    max-height: 600rpx;\n    overflow: auto;\n}\n.h5door-page .history-item{\n    height: 90rpx;\n    line-height: 90rpx;\n    padding-left:57rpx;\n    position: relative;\n    font-size: 24rpx;\n    color: #333333;\n    overflow:hidden;\n    text-overflow:ellipsis;\n    white-space:nowrap;\n    border-bottom: 1px solid #efefef;\n}\n.h5door-page .clear-all{\n    width: 100%;\n    text-align: center;\n    font-size: 24rpx;\n    margin-top: 40rpx;\n    height: 32rpx;\n    line-height: 32rpx;\n    color: #999;\n}\n.h5door-page .search-icon{\n    height: 40rpx;\n    width: 40rpx;\n    position: absolute;\n    left: 0;\n    bottom: 23rpx;\n}\n.h5door-page .go-webview-button{\n    position: absolute;\n    bottom: 30rpx;\n    left: 30rpx;\n    width: 690rpx;\n    height: 100rpx;\n    background: #337CC4;\n    border: 0;\n    color:#ffffff;\n}\n"
  },
  {
    "path": "miniapp/src/components/httpinjector/httpinjector.js",
    "content": "const img = require('../../utils/imgbase64')\nconst app = getApp()\nif (!Object.prototype.hasOwnProperty.call(app, 'originRequest')) {\n    app.originRequest = wx.request\n}\nComponent({\n    data: {\n        interceptors:[],\n        isInjected: false,\n        isShowManage:false,\n        addPopupClass:'',\n        isShowMask:false,\n        addInfo:{\n            key:'',\n            value:'',\n            title:'添加',\n            isRegex: false,\n            disabled:false\n        },\n        img\n    },\n    lifetimes: {\n      created () {\n\n      },\n      attached () {\n        this.setData({\n            addInfo:{\n                key:'',\n                value:'',\n                title:'添加',\n                isRegex: false,\n                disabled:false\n            }\n        })\n        this.setData(getApp().globalData['__HTTP_INJECTOR'])\n      },\n      detached () {\n        const { interceptors, isInjected } = this.data\n        getApp().globalData['__HTTP_INJECTOR'] = { interceptors,isInjected }\n      }\n    },\n    methods: {\n        hooksRequestSuccessCallback(res) {\n            let { data } = res\n            this.data.interceptors.forEach(interceptor => {\n                const { isRegex, key, value } = interceptor\n                const replaceKey = isRegex ? new RegExp(key, 'g') : key\n                const isNotStringData = typeof data !== 'string'\n                if (isNotStringData) {\n                    data = JSON.stringify(data)\n                }\n                data = data.replace(replaceKey,value)\n                if (isNotStringData) {\n                    data = JSON.parse(data)\n                }\n            })\n            res.data = data\n            return res\n        },\n        hooksRequest() {\n            Object.defineProperty(wx,  \"request\" , { writable:  true });\n            const hooksRequestSuccessCallback = this.hooksRequestSuccessCallback\n            wx.request = function(options){\n                const originSuccessCallback = options.success\n                options.success = res => {\n                    originSuccessCallback(hooksRequestSuccessCallback(res))\n                }\n                app.originRequest(options)\n            }\n        },\n        showAddPopup(){\n            this.setData({\n                isShowManage:false,\n                addPopupClass:'add-dialog-active',\n                addInfo:{\n                    key: '',\n                    value:'',\n                    title:'添加',\n                    disabled:false\n                }\n            })\n        },\n        closeAddPopup(){\n            this.closeAll()\n        },\n        closeAll(){\n            this.setData({\n                isShowManage:false,\n                isShowMask:false,\n                addPopupClass:''\n            })\n        },\n        openManageMenu(){\n            if (this.data.isInjected)return;\n            this.setData({\n                isShowManage:true,\n                isShowMask:true\n            })\n        },\n        clearAll (){\n            this.setData({ isShowManage:false })\n            wx.showModal({\n                title: '提示',\n                content: '确定要清除所有吗？',\n                success:res => {\n                    if (res.confirm) {\n                        // Todo 清楚所欲自定的key\n                        wx.clearStorageSync()\n                    }\n                    this.closeAll()\n                }\n            })\n        },\n        checkboxChange(event){\n            this.setData({checkedStorage:event.detail.value})\n        },\n        modifyItemValue(event) {\n            this.setData({\n                isShowMask:true,\n                addPopupClass:'add-dialog-active',\n                addInfo:{\n                    key:event.currentTarget.dataset.key,\n                    value:event.currentTarget.dataset.value,\n                    title:'修改',\n                    disabled:true\n                }\n            })\n        },\n        bingAddInfoKey(event){\n            this.setData({'addInfo.key': event.detail.value})\n        },\n        bingAddInfoValue(event){\n            this.setData({'addInfo.value': event.detail.value})\n        },\n        bingAddInfoIsRegex(event){\n            this.setData({'addInfo.isRegex': event.detail.value})\n        },\n        addStorage(){\n            const { key,value, isRegex } = this.data.addInfo\n            if(key && value){\n                this.setData({\n                    interceptors: [...this.data.interceptors,{ key, value, isRegex }]\n                })\n            }\n            this.closeAll()\n        },\n        toggleInjectionState() {\n            this.setData({\n                isInjected: !this.data.isInjected\n            }, () => {\n                this.data.isInjected && this.hooksRequest()\n            })\n        },\n        onGoBack () {\n            this.triggerEvent('toggle', { componentType: 'dokit'})\n        }\n    }\n  });\n  "
  },
  {
    "path": "miniapp/src/components/httpinjector/httpinjector.json",
    "content": "{\n    \"component\": true,\n    \"usingComponents\": {\n      \"back\": \"../../components/back/back\"\n    }\n  }"
  },
  {
    "path": "miniapp/src/components/httpinjector/httpinjector.wxml",
    "content": "<view class=\"injector-container\">\n    <view class=\"mask\" wx:if=\"{{isShowMask}}\" bindtap=\"closeAll\"></view>\n    <view class=\"header\">\n        <view class=\"injection-toggle\">\n            <text class=\"injection-toggle-text\">注射器💉开关</text>\n            <switch color=\"#337CC4\" checked=\"{{isInjected}}\" bindchange=\"toggleInjectionState\" />\n        </view>\n        <view class=\"injector-manage {{ isInjected ? 'disabled':'' }}\" bindtap=\"openManageMenu\">\n            <image class=\"injector-manage-img\" src=\"{{ img.moreicon }}\" />\n        </view>\n        <view wx:if=\"{{isShowManage}}\" class=\"injector-manage-list\">\n            <view class=\"arrow-top\"></view>\n            <view class=\"manage-item\" bindtap=\"showAddPopup\"><image class=\"manage-item-img\" src=\"{{ img.addicon }}\" />新增</view>\n            <view class=\"manage-item\" bindtap=\"clearAll\"><image class=\"manage-item-img\" src=\"{{ img.clearallicon }}\" />清除全部</view>\n        </view>\n    </view>\n    <view class=\"injector-main {{ isInjected ? 'disabled':'' }}\">\n        <view class=\"table-title\">\n            <view class=\"key-title\">Origin value</view>\n            <view class=\"type-title\">type</view>\n            <view class=\"value-title\">New value</view>\n        </view>\n        <checkbox-group bindchange=\"checkboxChange\" class=\"injector-content-box\">\n            <view class=\"injector-item\" wx:for=\"{{interceptors}}\" wx:key=\"key\">\n                <text class=\"injector-key\">{{item.key}}</text>\n                <text class=\"injector-type\">{{item.isRegex?'Regex':'PlainText'}}</text>\n                <text class=\"injector-value\" title=\"{{item.value}}\">{{item.value}}</text>\n            </view>\n        </checkbox-group>\n    </view>\n    <view class=\"add-dialog {{addPopupClass}}\">\n        <view class=\"add-popup-header\">\n            <text class=\"cancel\" bindtap=\"closeAddPopup\">取消</text>\n            <text class=\"main-title\">{{addInfo.title}}</text>\n            <text class=\"add\" bindtap=\"addStorage\">确定</text>\n        </view>\n        <view class=\"add-key-input\">\n            <view class=\"name\">\n                原始值：Key\n                (\n                    正则：<switch class=\"add-key-switch\" color=\"#337CC4\" type=\"checkbox\" checked=\"{{addInfo.isRegex}}\" bindchange=\"bingAddInfoIsRegex\"/>\n                )\n            </view>\n            <input class=\"add-name-input\" value=\"{{addInfo.key}}\" bindinput=\"bingAddInfoKey\" maxlength=\"-1\"></input>\n        </view>\n        <view class=\"add-value-input\">\n            <view class=\"name\">注射值：Value</view>\n            <input class=\"add-name-input\" value=\"{{addInfo.value}}\" bindinput=\"bingAddInfoValue\"  maxlength=\"-1\"></input>\n        </view>\n    </view>\n</view>\n<back bindreturn=\"onGoBack\"></back>"
  },
  {
    "path": "miniapp/src/components/httpinjector/httpinjector.wxss",
    "content": ".injector-container {\n    position: absolute;\n    width: 100%;\n    height:100vh;\n    box-sizing: border-box;\n    left: 0;\n    right: 0;\n    top: 0;\n    padding: 32rpx;\n    background-color: white;\n    z-index: 9998;\n}\n.injector-container .mask{\n    position: fixed;\n    left: 0;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    background: rgba(0, 0, 0, 0.8);\n    z-index:10;\n}\n.injector-container .injector-content-box{\n    overflow: auto;\n    height: 800rpx;\n}\n.injector-container .add-dialog{\n    display: none;\n    position: absolute;\n    bottom: -504rpx;\n    left: 0;\n    width: 100%;\n    height: 504rpx;\n    background: #fff;\n    padding: 0 20rpx;\n    box-sizing: border-box;\n    z-index: 99;\n    transition: all .5s;\n}\n.injector-container .add-dialog-active{\n    bottom: 0;\n    display: block;\n}\n.injector-container  .title {\n    margin-right:4.2rem;\n}\n.injector-container .table-title{\n    overflow: hidden;\n    height: 68rpx;\n    display: flex;\n    text-align: center;\n    background: #337CC4;\n    line-height: 68rpx;\n    color: #FFF;\n    border-radius: 8rpx;\n}\n.injector-container .header{\n    position: relative;\n    height: 88rpx;\n}\n.injector-container .injector-manage{\n    position: absolute;\n    right:0;\n    top: 20rpx;\n}\n.injector-container .disabled {\n    opacity: 0.3;\n}\n.injector-manage-list .arrow-top{\n    width:0;\n    height:0;\n    border:16rpx solid;\n    position:absolute;\n    right:10rpx;\n    border-color:transparent transparent #fff;\n    top:-31rpx;\n}\n.injector-manage-img {\n    width: 40rpx;\n    height: 40rpx;\n}\n.injector-main .key-title,\n.injector-main .value-title{\n    position: relative;\n    flex: 5;\n}\n.injector-main .type-title{\n    position: relative;\n    flex: 3;\n}\n.injector-main .key-title:after,\n.injector-main .type-title:after{\n    content: ' ';\n    position: absolute;\n    right: 0;\n    top: 0;\n    width: 2rpx;\n    height: 100%;\n    background: #FFF;\n}\n.injector-container .head-title{\n    font-size: 28rpx;\n}\n.injector-container  .injector-item {\n    display:flex;\n    flex-flow:row nowrap;\n    font-size: 24rpx;\n    height:80rpx;\n    line-height: 80rpx;\n}\n.injector-item .injector-key,\n.injector-item .injector-value{\n    flex: 5;\n    padding-left: 10rpx;\n    overflow: scroll;\n}\n.injector-item .injector-type{\n    flex: 3;\n    padding-left: 10rpx;\n}\n.injector-item .add-some-flex{\n    flex: 3.2\n}\n.injector-manage-list{\n    width: 243rpx;\n    font-size: 28rpx;\n    background: #fff;\n    padding-left: 32rpx;\n    padding-right: 24rpx;\n    box-sizing: border-box;\n    position:absolute;\n    right:0px;\n    top:46px;\n    z-index: 99;\n}\n.injector-manage-list .manage-item{\n    width: 100%;\n    height: 72rpx;\n    line-height: 72rpx;\n    border-bottom:1rpx solid #efefef;\n}\n.manage-item-img {\n    height: 40rpx;\n    width: 40rpx;\n    vertical-align:-8rpx;\n    margin-right: 21rpx;\n}\n.injector-container .operate-btn{\n    background: #fff;\n    color: #337CC4;\n    border: 1rpx solid #337CC4;\n    height: 100rpx;\n    width: 224rpx;\n    margin-right: 16rpx;\n    display: inline-block;\n}\n.injector-container .operate-btn-delete{\n    background: #D8D8D8;\n    color: #fff;\n    height: 100rpx;\n    width: 450rpx;\n    box-sizing: border-box;\n    display: inline-block;\n}\n.injector-container .delete-active{\n    background: #337CC4;\n}\n.injector-main {\n    margin-top: 30rpx;\n}\n.injector-footer {\n    position: absolute;\n    bottom: 30rpx;\n    left:32rpx;\n}\n.add-injectorkey-input{\n    width: 20%;\n    margin-right:2rem;\n}\n.add-injectorvalue-input{\n    width: 65%\n}\n.add-popup-header{\n    font-size: 28rpx;\n    height: 100rpx;\n    line-height: 100rpx;\n}\n.add-popup-header .main-title{\n    padding: 0 250rpx;\n    text-align: center;\n    font-size: 36rpx;\n}\n.add-popup-header .cancel{\n    color: #999999;\n}\n.add-popup-header .add{\n    color: #337CC4;\n}\n.add-dialog .name{\n     font-size: 24rpx;\n     color: #666666;\n     width: 100%;\n     margin-top: 40rpx;\n     margin-bottom: 24rpx;\n}\n.add-key-switch {\n    transform: scale(0.8, 0.8);\n}\n.add-name-input {\n    width: 100%;\n    border: 0;\n    border-bottom: 1rpx solid #EEEEEE;\n    padding-bottom: 16rpx;\n}\n.injection-toggle-text {\n    margin-right: 20px;\n}"
  },
  {
    "path": "miniapp/src/components/looklogs/looklogs.js",
    "content": "// Contributor： PKU - Li Bin\nconst img = require('../../utils/imgbase64')\nconst util = require('../../utils/util')\nconst app = getApp();\nconst types = ['debug','log','info','warn','error'];\nif(!Object.prototype.hasOwnProperty.call(app,'originlog')){\n  for(let type of types){\n    app[`origin${type}`] = console[type];\n  }\n}\nComponent({\n  data: {\n    logs:{\"debug\":[],\"log\":[],\"info\":[],\"warn\":[],\"error\":[],\"search\":[]},\n    lookLog : false,\n    isShowMask:false,\n    isShowManage:false,\n    logKinds : [],\n    currentKind : 'log',\n    img\n  },\n  lifetimes:{\n    attached:function(){\n      //加载种类栏和全局存储的日志表\n      const globalLogs = getApp().globalData['__DOKIT_LOOKLOG'];\n      if(globalLogs !== undefined){\n        this.data.logs = globalLogs.logs;\n        this.data.lookLog = globalLogs.lookLog;\n        this.data.currentKind = globalLogs.currentKind;\n      }\n      this.data.logKinds = this.getKinds();\n      this.setData(this.data);\n    },\n    detached(){\n      const { logs , lookLog , currentKind} = this.data;\n      getApp().globalData['__DOKIT_LOOKLOG'] = { logs , lookLog ,currentKind};\n    }\n  },\n\n  /**\n   * 组件的方法列表\n   */\n  methods: {\n    hookConsole(){\n      for(let type of types){\n        this.hookKindLog(type);\n      }\n      console.log(\"日志查看已开启\");\n    },\n    resetConsole(){\n      for(let type of types){\n        console[types] = app[`origin${type}`];\n      }\n    },\n    chooseList(event){\n      const type = event.currentTarget.dataset.type;\n      this.setData({currentKind:type});\n    },\n    searchLog(event){\n      var text = event.detail.value;\n      if(text !== \"\"){\n        var searchList = [];\n        for(let type of types){\n          searchList = searchList.concat(this.traversal(type,text));\n        }\n        this.setData({\n          'logs.search' : searchList,\n          currentKind : 'search'\n        })\n      }\n    },\n    traversal(kind,text){\n      var tempList = [];\n      var logs = this.data.logs[kind];\n      for(var i = 0; i < logs.length; i++){\n        if((logs[i].para).search(text) !== -1){\n          tempList.push(logs[i]);\n        }\n      }\n      return tempList;\n    },\n    closeAll(){\n      this.setData({\n        isShowMask:false,\n        isShowManage:false\n      })\n    },\n    openManageMenu(){\n      if(this.data.lookLog) return;\n      this.setData({\n        isShowMask:true,\n        isShowManage:true\n      })\n    },\n    clearAll (){\n      this.setData({ isShowManage:false })\n      wx.showModal({\n          title: '提示',\n          content: '确定要清除所有吗？',\n          success:res => {\n              if (res.confirm) {\n                  // Todo 清除之前记录的日志信息\n                  getApp().globalData['__DOKIT_LOOKLOG'].logs = {\n                    \"debug\":[],\"log\":[],\"info\":[],\"warn\":[],\"error\":[],\"search\":[]};\n                  getApp().globalData['__DOKIT_LOOKLOG'].lookLog = false;\n                  this.setData(getApp().globalData['__DOKIT_LOOKLOG']);\n              }\n              this.closeAll()\n          }\n      })\n    },    \n    toggleLookLog(){\n      this.setData({\n        lookLog : !this.data.lookLog\n      },()=>{\n        if(this.data.lookLog)\n          this.hookConsole();\n        else this.resetConsole();\n      })\n    },\n    onExpand(event){\n      const index = event.currentTarget.dataset.index;\n      const curState = this.data.logs[this.data.currentKind][index].hidden;\n      const path = `logs.${this.data.currentKind}`;\n      this.setData({\n        [`${path}[${index}]hidden`]: !curState\n      })\n    },\n    hookKindLog(kind){\n      const that = this;\n      Object.defineProperty(console,kind,{writable:true});\n      console[kind] = (...vars)=>{\n        app[`origin${kind}`](...vars);\n        var param = \"\";\n        for(var i = 0; i < vars.length; i++){\n          if(typeof vars[i] === 'object') param += util.obj2str(vars[i]);\n          else if(typeof vars[i] === 'symbol') param += String(vars[i])\n          else param += vars[i];\n        }\n        const logTime = util.formatTime(new Date()) ;\n        that.data.logs[kind].push({ time:logTime , para:param ,hidden:true});\n        that.setData({\n          logs:that.data.logs\n        });\n      }\n    },\n    onGoBack () {\n      this.triggerEvent('toggle', { componentType: 'dokit'})\n    },\n    getKinds(){\n      return [\n        {\n          \"kind\" : \"Debug\",\n          \"type\" : \"debug\"\n        },\n        {\n          \"kind\" : \"Log\",\n          \"type\" : \"log\"\n        },\n        {\n          \"kind\" : \"Info\",\n          \"type\" : \"info\"\n        },\n        {\n          \"kind\" : \"Warn\",\n          \"type\" : \"warn\"\n        },\n        {\n          \"kind\" : \"Error\",\n          \"type\" : \"error\"\n        }\n      ]\n    }\n  }\n})\n"
  },
  {
    "path": "miniapp/src/components/looklogs/looklogs.json",
    "content": "{\n  \"component\": true,\n  \"usingComponents\": {\n    \"back\": \"../../components/back/back\"\n  }\n}"
  },
  {
    "path": "miniapp/src/components/looklogs/looklogs.wxml",
    "content": "<!--dist/components/looklogs/looklogs.wxml-->\n<view class=\"log-container\">\n    <view class=\"mask\" wx:if=\"{{isShowMask}}\" bindtap=\"closeAll\"></view>\n    <view class=\"header\">\n        <view class=\"log-toggle\">\n            <text class=\"log-toggle-text\">日志开关</text>\n            <switch color=\"#337CC4\" checked=\"{{lookLog}}\" bindchange=\"toggleLookLog\" />\n        </view>\n        <view class=\"log-manage {{ lookLog ? 'disabled':'' }}\" bindtap=\"openManageMenu\">\n            <image class=\"log-manage-img\" src=\"{{ img.moreicon }}\" />\n        </view>\n        <view wx:if=\"{{isShowManage}}\" class=\"log-manage-list\">\n            <view class=\"arrow-top\"></view>\n            <view class=\"manage-item\" bindtap=\"clearAll\"><image class=\"manage-item-img\" src=\"{{ img.clearallicon }}\" />清除全部</view>\n        </view>\n    </view>\n    <input class=\"search-input\" confirm-type=\"search\" bindinput=\"searchLog\" placeholder=\"请输入要搜索的关键词或正则表达式\"/>\n    <view class=\"list-wrap\">\n      <view class=\"table-title\">\n        <block wx:for=\"{{logKinds}}\" wx:key=\"index\">\n          <view class=\"title-item {{currentKind === item.type ? 'touched': ''}}\" data-type=\"{{item.type}}\" bindtap=\"chooseList\">{{ item.kind }}</view>\n        </block>\n        </view>\n    <scroll-view class='list-scroll' scroll-y style=\"height: 100%\">\n      <view>\n        <view wx:for=\"{{logs[currentKind]}}\" wx:key= \"index\">\n          <view class=\"item-wrap\">\n            <view class=\"list-item\" data-index=\"{{index}}\" catchtap=\"onExpand\">\n              <view class=\"item-name\">{{item.time+' '+item.para}}</view>\n            </view>\n          </view>\n          <view class=\"item-detail {{ logs[currentKind][index].hidden ? 'hidden' : '' }}\" >\n            <text>{{item.para}}</text>\n          </view>\n        </view>\n      </view>\n    </scroll-view>\n  </view>\n</view>\n<back bindreturn=\"onGoBack\"></back>\n"
  },
  {
    "path": "miniapp/src/components/looklogs/looklogs.wxss",
    "content": "/* dist/components/looklogs/looklogs.wxss */\n.log-container {\n  position: absolute;\n  width: 100%;\n  height: 100vh;\n  box-sizing: border-box;\n  left: 0;\n  right: 0;\n  top: 0;\n  padding: 32rpx;\n  background-color: #fff;\n  z-index: 9998\n}\n.log-container .search-input {\n  border: solid 2rpx #ccc;\n  padding: 10rpx;\n}\n.log-container .mask {\n  position: fixed;\n  left: 0;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  background: rgba(0,0,0,.8);\n  z-index: 10\n}\n.log-container .header {\n  position: relative;\n  height: 88rpx\n}\n.log-container .disabled {\n  opacity: .3\n}\n.log-toggle-text {\n  margin-right: 40rpx\n}\n.log-container .log-manage {\n  position: absolute;\n  right: 0;\n  top: 20rpx\n}\n.log-container .disabled {\n  opacity: .3\n}\n.log-container .table-title {\n  overflow: hidden;\n  height: 68rpx;\n  display: -ms-flexbox;\n  display: flex;\n  text-align: center;\n  background: #337cc4;\n  line-height: 68rpx;\n  color: #fff;\n  border-radius: 8rpx\n}\n.title-item {\n  position: relative;\n  -ms-flex: 1;\n  flex: 1;\n  background: #f5f5f6;\n  color: #337cc4;\n  border-right: solid white 2rpx;\n}\n.title-item.touched {\n  position: relative;\n  -ms-flex: 1;\n  flex: 1;\n  background: #337cc4;\n  color: #fff;\n  border-right: solid white 2rpx;\n}\n.log-manage-img {\n  width: 40rpx;\n  height: 40rpx\n}\n.log-manage-list {\n  width: 243rpx;\n  font-size: 28rpx;\n  background: #fff;\n  padding-left: 32rpx;\n  padding-right: 24rpx;\n  box-sizing: border-box;\n  position: absolute;\n  right: 0;\n  top: 92rpx;\n  z-index: 99\n}\n.log-manage-list .arrow-top {\n  width: 0;\n  height: 0;\n  border: 16rpx solid;\n  position: absolute;\n  right: 10rpx;\n  border-color: transparent transparent #fff;\n  top: -31rpx\n}\n.log-manage-list .manage-item {\n  width: 100%;\n  height: 72rpx;\n  line-height: 72rpx;\n  border-bottom: 1rpx solid #efefef\n}\n.manage-item-img {\n  height: 40rpx;\n  width: 40rpx;\n  vertical-align: -8rpx;\n  margin-right: 21rpx\n}\n.log-item-img {\n  height: 40rpx;\n  width: 40rpx;\n  vertical-align: -8rpx;\n  margin-right: 21rpx\n}\n.log-container .logs{\n  background-color: rgba(75, 241, 241, 0.1);\n  padding: 15rpx;\n  border-radius: 5rpx;\n  width: 750rpx;\n  height: 300rpx;\n}\n.log-container .log-item{\n  word-break: break-all;\n}\n.list-wrap {\n  margin-top: 12rpx;\n  padding-top: 12rpx;\n  -ms-flex: 1;\n  flex: 1;\n  position: relative\n}\n.item-wrap {\n  box-sizing: border-box;\n  width: 100%;\n  /*padding: 0 32rpx;*/\n  background-color: #fff\n}\n.list-item {\n  position: relative;\n  display: -ms-flexbox;\n  display: flex;\n  box-sizing: border-box;\n  -ms-flex-pack: justify;\n  justify-content: space-between;\n  width: 100%;\n  height: 104rpx;\n  -ms-flex-align: center;\n  align-items: center;\n  border-bottom: 1rpx solid #e5e5e5\n}\n.list-item::after {\n  position: absolute;\n  content: '＞';\n  right: 10rpx;\n  font-size: 34rpx;\n  color: #ccc;\n}\n.item-name {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap\n}\n.item-detail {\n  box-sizing: border-box;\n  padding: 0 32rpx;\n  transition: height .2s;\n  height: fit-content;\n  word-break: break-all;\n}\n.item-detail.hidden {\n  height: 0;\n  overflow: hidden\n}"
  },
  {
    "path": "miniapp/src/components/pagedoor/pagedoor.js",
    "content": "// author：zhou-xingxing\nComponent({\n  /**\n   * 组件的属性列表\n   */\n  properties: {\n  },\n  /**\n   * 组件的初始数据\n   */\n  data: {\n    parmList: [\n      {\n        key:'',\n        value:''\n      }\n    ],\n    typeList: [\n      {value: 'navigateTo', name: '打开新页面',checked:true},\n      {value: 'redirectTo', name: '页面重定向'},\n      {value: 'reLaunch', name: '重启动'},\n      {value: 'switchTab', name: 'Tab切换'},\n    ],\n    pageList:[],\n    pageUrl:'',\n    goType:'navigateTo',\n  },\n  lifetimes: {\n    attached () {\n        this.setData({\n          pageList:__wxConfig.pages\n        })\n        // console.log(this.data.pageList)\n    }\n},\n  /**\n   * 组件的方法列表\n   */\n  methods: {\n    onGoBack () {\n      this.triggerEvent('toggle', { componentType: 'dokit'})\n    },\n    // page输入框\n    textareaChange(e){\n      this.setData({\n        pageUrl:e.detail.value\n      })\n    },\n    // page选择器\n    bindPickerChange(e) {\n      let url='/'+this.data.pageList[e.detail.value]\n      this.setData({\n        pageUrl:url\n      })\n    },\n    // 修改跳转方式\n    radioChange(e) {\n      this.setData({\n        goType:e.detail.value\n      })\n    },\n    // 增加一组参数\n    addParm(e){\n      let list=this.data.parmList\n      list.push({\n        key:'',\n        value:''\n      })\n      this.setData({\n        parmList:list\n      })\n    },\n    // 输入参数key\n    changeParmkey(e){\n      let idx=e.currentTarget.dataset.index\n      let key = e.detail.value\n      let list=this.data.parmList\n      list[idx].key=key\n      this.setData({\n        parmList:list\n      })\n    },\n    // 输入参数value\n    changeParmValue(e){\n      let idx=e.currentTarget.dataset.index\n      let val = e.detail.value\n      let list=this.data.parmList\n      list[idx].value=val\n      this.setData({\n        parmList:list\n      })\n    },\n    // 拼接路径和参数\n    makeUrl(){\n      let url=this.data.pageUrl\n      let list=this.data.parmList\n      if(!url){\n        wx.showToast({\n          title:'请输入页面路径',\n          icon:\"error\"\n        })\n        return \"\"\n      }\n      url=this.data.pageUrl+'?'\n      // 检验参数是否完整\n      for(let i=0;i<list.length;i++){\n        if(list[i].key!=\"\"&&list[i].value!=\"\"){\n          url=url+list[i].key+'='+list[i].value+'&'\n        }\n        else if(list[i].key==\"\"&&list[i].value==\"\"){\n          continue\n        }\n        else{\n          wx.showToast({\n            title:'参数不完整',\n            icon:\"error\"\n          })\n          return \"\"\n        }\n      }\n      return url\n    },\n    // 页面跳转\n    goPage(){\n      let url=this.makeUrl()\n      let type=this.data.goType\n      if(url==\"\"){\n        return\n      }\n      // switchTab不支持queryString\n      if(type=='switchTab'){\n          let pos=0\n          for(;pos<url.length;pos++){\n            if(url[pos]=='?'){\n              break;\n            }\n          }\n          url=url.substring(0,pos)\n      }\n      console.log(\"页面路径：\",url)\n      // 跳转实现\n      wx[type]({\n        url: url,\n        fail:(e)=>{\n          wx.showModal({\n            title:'跳转失败',\n            content: '错误信息：'+e.errMsg,\n          })\n        }\n      })\n    },\n  }\n})\n"
  },
  {
    "path": "miniapp/src/components/pagedoor/pagedoor.json",
    "content": "{\n  \"component\": true,\n  \"usingComponents\": {\n    \"back\": \"../../components/back/back\"\n  }\n}"
  },
  {
    "path": "miniapp/src/components/pagedoor/pagedoor.wxml",
    "content": "<view class=\"pagedoor-page\">\n\n  <view class=\"panel-box\">\n    <view class=\"big-label\">页面路径</view>   \n    <view class=\"area-box\">\n        <textarea  class=\"url-box\"  placeholder=\"请输入page绝对路径，以/开头\" maxlength=\"140\" placeholder-class=\"url-paceholder-text\"\n        bindinput=\"textareaChange\" value=\"{{pageUrl}}\">\n        </textarea>\n    </view>\n    <picker bindchange=\"bindPickerChange\" value=\"{{index}}\" range=\"{{pageList}}\">\n      <view class=\"pagedoor-button\">\n        自动填写\n      </view>\n    </picker>    \n  </view>\n\n  <view class=\"panel-box\">\n    <view class=\"big-label\">参数列表</view>\n    <block wx:for=\"{{parmList}}\">\n      <view class=\"parm-list\">\n        <view class=\"parm-box\">\n          <label>key：</label>\n          <input class=\"input-box\" value=\"{{item.key}}\" bindinput='changeParmkey' data-index='{{index}}'></input>\n        </view>\n        <view class=\"parm-box\">\n          <label>value：</label>\n          <input class=\"input-box\" value=\"{{item.value}}\" bindinput='changeParmValue' data-index='{{index}}'></input>\n        </view>\n      </view>\n    </block>\n    <view class=\"pagedoor-button\" bindtap='addParm'>\n      添加参数\n    </view>\n  </view>\n\n  <view class=\"panel-box\">\n    <view class=\"big-label\">跳转方式</view>\n    <view>\n      <radio-group bindchange=\"radioChange\">\n        <view wx:for=\"{{typeList}}\" class=\"radio-group\">         \n            <radio value=\"{{item.value}}\" checked=\"{{item.checked}}\"></radio> \n            <text>{{item.name}}</text>\n        </view>\n      </radio-group>\n    </view>\n  </view>\n  \n  <button class=\"go-page-button\" bindtap=\"goPage\" size=\"default\" type=\"\" plain=\"default\" hover-stop-propagation=\"false\">\n      点击跳转\n  </button>\n</view>\n\n<back top=\"80%\" bindreturn=\"onGoBack\"></back>\n"
  },
  {
    "path": "miniapp/src/components/pagedoor/pagedoor.wxss",
    "content": ".pagedoor-page {\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0;\n  width: 100%;\n  height:100vh;\n  background-color: white;\n  z-index: 9998;\n}\n.pagedoor-page .url-box{\n  height: 358rpx;\n  width: 100%;\n  position: absolute;\n  left: 0;\n  top: 0;\n  z-index: 1;\n}\n.pagedoor-page .area-box{\n  width: 98%;\n  margin: 0 auto;\n  box-sizing: border-box;\n  border: 1px solid #efefef;\n  height: 358rpx;\n  position: relative;\n}\n.url-box .url-paceholder-text{\n  font-size: 32rpx;\n  color: #BEBEBE;\n}\n.pagedoor-page .go-page-button{\n  margin-top: 200rpx;\n  margin-bottom: 100rpx;\n  width: 600rpx;\n  height: 100rpx;\n  background: #337CC4;\n  border: 0;\n  color:#ffffff;\n}\n.pagedoor-page .radio-group{\n  margin: 20rpx 10rpx;\n}\n.panel-box{\n  margin-bottom: 40rpx ;\n}\n.big-label{\n  font-size: 40rpx;\n  color:#337CC4;\n  margin: 20rpx 0;\n}\n.parm-list{\n  display: flex;\n  justify-content: space-around;\n}\n.parm-box{\n  display: flex;\n  justify-content: center;\n  margin: 10rpx 0;\n}\n.input-box{\n  background-color:whitesmoke;\n  width: 250rpx;\n  border: 1rpx solid black;\n}\n.pagedoor-button{\n  display: flex;\n  justify-content: center;\n  align-items:center;\n  margin:40rpx auto;\n  width: 200rpx;\n  height: 70rpx;\n  color:whitesmoke;\n  background-color:#337CC4;\n}\n"
  },
  {
    "path": "miniapp/src/components/positionsimulation/positionsimulation.js",
    "content": "const img = require('../../utils/imgbase64')\nconst app = getApp()\napp.originGetLocation = wx.getLocation\nComponent({\n    lifetimes: {\n      created (){\n      },\n      attached () {\n        this.getMyPosition()\n      },\n      detached (){\n      }\n    },\n    data: {\n        currentLatitude:0,\n        currentLongitude:0,\n        img\n    },\n    methods: {\n        choosePosition (){\n            wx.chooseLocation({\n                success: res => {\n                    this.setData({ currentLatitude: res.latitude });\n                    this.setData({ currentLongitude: res.longitude })\n                    Object.defineProperty(wx, 'getLocation', {\n                        get(val) {\n                            return function (obj) {\n                                obj.success({latitude: res.latitude, longitude: res.longitude})\n                            }\n                        }\n                    })\n                }\n            })\n        },\n        getMyPosition (){\n            wx.getLocation({\n                type: 'gcj02',\n                success:res=> {\n                    this.setData({currentLatitude:res.latitude}),\n                    this.setData({currentLongitude:res.longitude})\n                }\n            })\n        },\n        openMyPosition (){\n            wx.getLocation({\n                type: 'gcj02',\n                success (res) {\n                  wx.openLocation({\n                    latitude:res.latitude,\n                    longitude:res.longitude,\n                    scale: 18\n                  })\n                }\n            })\n        },\n        resetPosition (){\n            Object.defineProperty(wx, 'getLocation',\n            {\n                get(val) {\n                    return app.originGetLocation\n                }\n            });\n            wx.showToast({title:'还原成功！'})\n            this.getMyPosition()\n        },\n        onGoBack () {\n            this.triggerEvent('toggle', { componentType: 'dokit'})\n        }\n    }\n  });\n  "
  },
  {
    "path": "miniapp/src/components/positionsimulation/positionsimulation.json",
    "content": "{\n    \"component\": true,\n    \"usingComponents\": {\n      \"back\": \"../../components/back/back\"\n    }\n  }"
  },
  {
    "path": "miniapp/src/components/positionsimulation/positionsimulation.wxml",
    "content": "<view class=\"position-tools\">\n    <view class=\"tools-item\">\n        <button class=\"fast-authorization\" open-type=\"openSetting\">快速授权</button>\n        <image class=\"tools-img\" src=\"{{ img.rightarrowicon }}\" lazy-load=\"false\" binderror=\"\" bindload=\"\" />\n    </view>\n    <view bindtap=\"openMyPosition\" class=\"tools-item\">查看我的位置\n        <image class=\"tools-img\" src=\"{{ img.rightarrowicon }}\" lazy-load=\"false\" binderror=\"\" bindload=\"\" />\n    </view>\n    <view bindtap=\"choosePosition\" class=\"tools-item\">选择我的位置\n        <image class=\"tools-img\" src=\"{{ img.rightarrowicon }}\" lazy-load=\"false\" binderror=\"\" bindload=\"\" />\n    </view>\n    <view bindtap=\"resetPosition\" class=\"tools-item\">还原\n        <image class=\"tools-img\" src=\"{{ img.rightarrowicon }}\" lazy-load=\"false\" binderror=\"\" bindload=\"\" />\n    </view>\n</view>\n<back top=\"50%\" bindreturn=\"onGoBack\"></back>\n"
  },
  {
    "path": "miniapp/src/components/positionsimulation/positionsimulation.wxss",
    "content": ".myPosition{\n    font-size: 0.8rem;\n}\n.position-tools {\n    width: 100%;\n    height:100vh;\n    box-sizing: border-box;\n    position: absolute;\n    left: 0;\n    top: 0;\n    right: 0;\n    color: #333;\n    border-bottom: 1rpx solid #EEE;\n    font-size: 32rpx;\n    padding-left: 32rpx;\n    text-align: left;\n    background-color:#fff;\n    z-index: 9998;\n}\n.tools-item{\n    height: 104rpx;\n    line-height: 104rpx;\n    color: #333;\n    border-bottom: 1rpx solid #EEE;\n    font-size:32rpx;\n    padding-left: 32rpx;\n    position: relative;\n    text-align: left;\n    background-color:#fff;\n}\n.tools-item .tools-img{\n    height: 27rpx;\n    width: 16rpx;\n    position: absolute;\n    right: 32rpx;\n    top: 40rpx;\n}\n.tools-item .fast-authorization{\n    background: #fff;\n    color: #333;\n    font-size: 32rpx;\n    text-align: left;\n    padding: 0;\n    height: 104rpx;\n    line-height: 104rpx;\n}\n.tools-item .fast-authorization:after{\n    border:0\n}"
  },
  {
    "path": "miniapp/src/components/storage/storage.js",
    "content": "const img = require('../../utils/imgbase64')\nComponent({\n    data: {\n        storage:[],\n        limitSize:0,\n        currentSize:0,\n        isShowManage:false,\n        addPopupClass:'',\n        isShowMask:false,\n        isDeleteMode:false, //是否是删除模式\n        checkedStorage:[],\n        addInfo:{\n            key:'',\n            value:'',\n            title:'添加',\n            disabled:false\n        },\n        img\n    },\n    lifetimes: {\n      created () { },\n      attached () {\n        this.componentInit()\n      },\n      detached () {\n        console.log('detached')\n      }\n    },\n    methods: {\n        componentInit () {\n            this.setData({\n                addInfo:{\n                    key:'',\n                    value:'',\n                    title:'添加',\n                    disabled:false\n                }\n            })\n            this.getStorageInfo()\n        },\n        openDeleteMode(){\n            this.setData({\n                isDeleteMode:true\n            })\n            this.closeAll()\n        },\n        cancelDelete() {\n            this.setData({\n                isDeleteMode:false\n            })\n        },\n        showAddPopup(){\n            this.setData({\n                isShowManage:false,\n                addPopupClass:'add-dialog-active',\n                addInfo:{\n                    key: '',\n                    value:'',\n                    title:'添加',\n                    disabled:false\n                }\n            })\n        },\n        getStorageInfo (){\n            let storageArr = []\n            let storageInfo = wx.getStorageInfoSync()\n            this.setData({ limitSize: storageInfo.limitSize})\n            this.setData({ currentSize: storageInfo.currentSize})\n            storageInfo.keys.forEach(key => {\n                let result = wx.getStorageSync(key)\n                let info = {\n                    key,\n                    value:result,\n                    isModify:false,\n                    ischecked:false\n                }\n                storageArr.push(info)\n            });\n            storageArr = storageArr.filter(item => [\"dokit-mocklist\", \"dokit-tpllist\"].indexOf(item.key) == -1)\n            this.setData({storage:storageArr})\n        },\n        closeAddPopup(){\n            this.closeAll()\n        },\n        closeAll(){\n            this.setData({\n                isShowManage:false,\n                isShowMask:false,\n                addPopupClass:''\n            })\n        },\n        openManageMeau(){\n            this.setData({\n                isShowManage:true,\n                isShowMask:true\n            })\n        },\n        clearStorage(event){\n            if(!this.data.checkedStorage.length){\n                return\n            }\n            wx.showModal({\n                title: '提示',\n                content: '确定删除选中内容？',\n                success:res => {\n                    if (res.confirm) {\n                        this.data.checkedStorage.forEach((item)=>{\n                            wx.removeStorageSync(item)\n                        })\n                        this.componentInit()\n                    }\n                }\n            })\n        },\n        clearAll(){\n            this.setData({\n                isShowManage:false\n            })\n            wx.showModal({\n                title: '提示',\n                content: '确定要清除所有吗？',\n                success:res => {\n                    if (res.confirm) {\n                        let storageInfo = wx.getStorageInfoSync()\n                        storageInfo.keys.forEach(key => {\n                            if ([\"dokit-mocklist\", \"dokit-tpllist\"].indexOf(key) == -1) {\n                                wx.removeStorageSync(key)\n                            }\n                        });\n                        this.componentInit()\n                    }\n                    this.closeAll()\n                }\n            })\n        },\n        checkboxChange(event){\n            this.setData({checkedStorage:event.detail.value})\n        },\n        modifyItemValue(event) {\n            this.setData({\n                isShowMask:true,\n                addPopupClass:'add-dialog-active',\n                addInfo:{\n                    key:event.currentTarget.dataset.key,\n                    value:event.currentTarget.dataset.value,\n                    title:'修改',\n                    disabled:true\n                }\n            })\n        },\n        bingAddInfoKey(event){\n            this.setData({'addInfo.key':event.detail.value})\n        },\n        bingAddInfoValue(event){\n            this.setData({'addInfo.value':event.detail.value})\n        },\n        addStorage(){\n            if(this.data.addInfo.key&&this.data.addInfo.value){\n                wx.setStorageSync(this.data.addInfo.key, this.data.addInfo.value)\n                this.componentInit()\n            }\n            this.closeAll()\n        },\n        onGoBack () {\n            this.triggerEvent('toggle', { componentType: 'dokit'})\n        }\n    }\n});\n  "
  },
  {
    "path": "miniapp/src/components/storage/storage.json",
    "content": "{\n    \"component\": true,\n    \"usingComponents\": {\n      \"back\": \"../../components/back/back\"\n    }\n  }"
  },
  {
    "path": "miniapp/src/components/storage/storage.wxml",
    "content": "<view class=\"storage-container\">\n    <view class=\"mask\" wx:if=\"{{isShowMask}}\" bindtap=\"closeAll\"></view>\n    <view class=\"header\">\n        <view class=\"head-title\">大小限制：{{limitSize}}</view>\n        <view class=\"head-title\">当前总体积：{{currentSize}}</view>\n        <view class=\"storage-manage\" bindtap=\"openManageMeau\">\n            <image class=\"storage-manage-img\" src=\"{{ img.moreicon }}\" />\n        </view>\n        <view wx:if=\"{{isShowManage}}\" class=\"storage-manage-list\">\n            <view class=\"arrow-top\"></view>\n            <view class=\"manage-item\" bindtap=\"showAddPopup\"><image class=\"manage-item-img\" src=\"{{ img.addicon }}\" />新增</view>\n            <view class=\"manage-item\" bindtap=\"openDeleteMode\"><image class=\"manage-item-img\" src=\"{{ img.deleteicon }}\" />删除</view>\n            <view class=\"manage-item\" bindtap=\"clearAll\"><image class=\"manage-item-img\" src=\"{{ img.clearallicon }}\" />清除全部</view>\n        </view>\n    </view>\n    <view class=\"storage-main\">\n        <view class=\"table-title\"><view class=\"key-title\">Key</view><view class=\"value-title\">Value</view></view>\n        <checkbox-group bindchange=\"checkboxChange\" class=\"storage-content-box\">\n            <view class=\"storage-item\" wx:for=\"{{storage}}\" wx:key=\"key\">\n                <checkbox wx:if=\"{{isDeleteMode}}\" class=\"storage-check\" value=\"{{item.key}}\" checked=\"{{item.checked}}\" color=\"#337cc4\"/>\n                <text class=\"storage-key {{isDeleteMode?'':'add-some-flex'}}\">{{item.key}}</text>\n                <text class=\"storage-value\" title=\"{{item.value}}\">{{item.value}}</text>\n                <view class=\"clear-single-btn\" type=\"primary\" data-key=\"{{item.key}}\" data-value=\"{{item.value}}\" bindtap=\"modifyItemValue\">\n                    <image class=\"clear-single-img\" src=\"{{ img.modifyicon }}\" />\n                </view>\n            </view>\n        </checkbox-group>\n    </view>\n    <view class=\"add-dialog {{addPopupClass}}\">\n        <view class=\"add-popup-header\">\n            <text class=\"cancel\" bindtap=\"closeAddPopup\">取消</text>\n            <text class=\"main-title\">{{addInfo.title}}</text>\n            <text class=\"add\" bindtap=\"addStorage\">确定</text>\n        </view>\n        <view class=\"add-key-input\">\n            <view class=\"name\">键名：Key{{addInfo.disabled?'（不可修改）':''}}</view>\n            <input class=\"add-dialog-input\" value=\"{{addInfo.key}}\" bindinput=\"bingAddInfoKey\" maxlength=\"-1\" disabled=\"{{addInfo.disabled}}\"></input>\n        </view>\n        <view class=\"add-value-input\">\n            <view class=\"name\">键值：Value</view>\n            <input class=\"add-dialog-input\" value=\"{{addInfo.value}}\" bindinput=\"bingAddInfoValue\"  maxlength=\"-1\"></input>\n        </view>\n    </view>\n    <view class=\"storage-footer\" wx:if=\"{{isDeleteMode}}\">\n        <button class=\"operate-btn\" bindtap=\"cancelDelete\">取消</button>\n        <button class=\"operate-btn-delete {{checkedStorage.length>0?'delete-active':''}}\" bindtap=\"clearStorage\">删除</button>\n    </view>\n</view>\n<back bindreturn=\"onGoBack\"></back>"
  },
  {
    "path": "miniapp/src/components/storage/storage.wxss",
    "content": ".storage-container {\n    background-color: white;\n    position: absolute;\n    width: 100%;\n    height:100vh;\n    box-sizing: border-box;\n    padding: 32rpx;\n    left: 0;\n    right: 0;\n    top: 0;\n    z-index: 9998;\n}\n.storage-container .mask{\n    position: fixed;\n    left: 0;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    background: rgba(0, 0, 0, 0.8);\n    z-index:10;\n}\n.storage-container .storage-content-box{\n    overflow: auto;\n    height: 800rpx;\n}\n.storage-container .add-dialog{\n    width: 100%;\n    height: 504rpx;\n    background: #fff;\n    padding: 0 20rpx;\n    box-sizing: border-box;\n    z-index: 99;\n    transform: translateY(504rpx);\n    transition: all .5s;\n    position: absolute;\n    bottom:0;\n    left: 0;\n}\n.storage-container .add-dialog-active{\n    transform: translateY(0);\n}\n.storage-container  .title {\n    margin-right:4.2rem;\n}\n.storage-container .table-title{\n    overflow: hidden;\n    height: 68rpx;\n}\n.storage-container .header{\n    position: relative;\n    height: 88rpx;\n}\n.storage-container .storage-manage{\n   position: absolute;\n   right:0;\n   top: 20rpx;\n}\n.storage-manage-list .arrow-top{\n    width:0;\n    height:0;\n    border:16rpx solid;\n    position:absolute;\n    right:10rpx;\n    border-color:transparent transparent #fff;\n    top:-31rpx;    \n}\n.storage-manage-img {\n    width: 40rpx;\n    height: 40rpx;\n}\n.storage-main .key-title{\n    width: 228rpx;\n    height: 68rpx;\n    float: left;\n    margin-right: 1px;\n    font-size: 28rpx;\n    line-height: 68rpx;\n    color: #fff;\n    border-radius: 8px 0 0 8px;\n    background: #337CC4;\n    text-align: center;\n}\n.storage-main .value-title{\n    width: 457rpx;\n    float: left;\n    height: 68rpx;\n    font-size: 28rpx;\n    line-height: 68rpx;\n    color: #fff;\n    border-radius: 0 8px 8px 0;\n    background: #337CC4;\n    text-align: center;\n}\n.storage-container .head-title{\n    font-size: 28rpx;\n}\n.storage-container  .storage-item {\n    display:flex;\n    flex-flow:row nowrap;\n    font-size: 24rpx;\n    height:80rpx;\n    line-height: 80rpx;\n}\n.storage-item .storage-check{\n    flex: 0.3;\n}\n.storage-item .storage-check .wx-checkbox-input {\n    width: 32rpx;\n    height: 32rpx;\n}\n.storage-item .storage-check .wx-checkbox-input.wx-checkbox-input-checked::before {\n    font-size: 24rpx;\n  }\n  /*checkbox选中后样式  */\n.storage-item .storage-check .wx-checkbox-input.wx-checkbox-input-checked {\n    background: #337CC4;\n}\n.storage-item .storage-key{\n    flex: 2.6;\n    padding-left: 10rpx;\n}\n.storage-item .storage-value{\n    flex: 6;\n    padding-left: 10rpx;\n    overflow: scroll;\n}\n.storage-item .add-some-flex{\n    flex: 3.2\n}\n.storage-manage-list{\n    width: 243rpx;\n    font-size: 28rpx;\n    background: #fff;\n    padding-left: 32rpx;\n    padding-right: 24rpx;\n    box-sizing: border-box;\n    position:absolute;\n    right:0px;\n    top:46px;\n    z-index: 99;\n}\n.storage-manage-list .manage-item{\n    width: 100%;\n    height: 72rpx;\n    line-height: 72rpx;\n    border-bottom:1rpx solid #efefef;\n}\n.manage-item-img {\n    height: 40rpx;\n    width: 40rpx;\n    vertical-align:-8rpx;\n    margin-right: 21rpx;\n}\n.storage-container .clear-single-btn{\n    flex:0.5;\n    line-height:80rpx;\n    text-align: center;\n}\n.clear-single-img {\n    height:24rpx;\n    width: 24rpx;\n}\n.storage-container .operate-btn{\n    background: #fff;\n    color: #337CC4;\n    border: 1rpx solid #337CC4;\n    height: 100rpx;\n    width: 224rpx;\n    margin-right: 16rpx;\n    display: inline-block;\n}\n.storage-container .operate-btn-delete{\n    background: #D8D8D8;\n    color: #fff;\n    height: 100rpx;\n    width: 450rpx;\n    box-sizing: border-box;\n    display: inline-block;\n}\n.storage-container .delete-active{\n    background: #337CC4;\n}\n/* .storage-container input{\n    display: inline-block;\n    border: 1px solid #555;\n} */\n.storage-main {\n    margin-top: 30rpx;\n}\n.storage-footer {\n    position: absolute;\n    bottom: 30rpx;\n    left:32rpx;\n}\n.add-storagekey-input{\n    width: 20%;\n    margin-right:2rem;\n}\n.add-storagevalue-input{\n    width: 65%\n}\n.add-popup-header{\n    font-size: 28rpx;\n    height: 100rpx;\n    line-height: 100rpx;\n}\n.add-popup-header .main-title{\n    padding: 0 250rpx;\n    text-align: center;\n    font-size: 36rpx;\n}\n.add-popup-header .cancel{\n    color: #999999;\n}\n.add-popup-header .add{\n    color: #337CC4;\n}\n.add-dialog .name{\n    font-size: 24rpx;\n    color: #666666;\n    width: 100%;\n    margin-top: 40rpx;\n    margin-bottom: 24rpx;\n}\n.add-dialog-input {\n    width: 100%;\n    border: 0;\n    border-bottom: 1rpx solid #EEEEEE;\n    padding-bottom: 16rpx;\n}"
  },
  {
    "path": "miniapp/src/index/index.js",
    "content": "Component({\n  properties: {\n    projectId: {\n      type: String,\n      value: '',\n    }\n  },\n  data: {\n    curCom: 'dokit',\n  },\n  methods: {\n      tooggleComponent(e) {\n        const componentType = e.currentTarget.dataset.type || e.detail.componentType\n          this.setData({\n            curCom: componentType\n          })\n      }\n  }\n});\n"
  },
  {
    "path": "miniapp/src/index/index.json",
    "content": "{\n  \"component\": true,\n  \"navigationBarTitleText\": \"\",\n  \"usingComponents\": {\n    \"debug\": \"../components/debug/debug\",\n    \"appinformation\": \"../components/appinformation/appinformation\",\n    \"positionsimulation\": \"../components/positionsimulation/positionsimulation\",\n    \"storage\": \"../components/storage/storage\",\n    \"h5door\": \"../components/h5door/h5door\",\n    \"httpinjector\": \"../components/httpinjector/httpinjector\",\n    \"apimock\": \"../components/apimock/apimock\",\n    \"looklogs\" : \"../components/looklogs/looklogs\",\n    \"pagedoor\":\"../components/pagedoor/pagedoor\"\n  }\n}\n"
  },
  {
    "path": "miniapp/src/index/index.wxml",
    "content": "<!-- <view hidden=\"{{ curCom == 'dokit' }}\">\n    <debug hidden=\"{{ curCom !== 'debug' }}\" bindtoggle=\"tooggleComponent\"></debug>\n    <appinformation hidden=\"{{ curCom !== 'appinformation' }}\" bindtoggle=\"tooggleComponent\"></appinformation>\n    <positionsimulation hidden=\"{{ curCom !== 'positionsimulation' }}\" bindtoggle=\"tooggleComponent\"></positionsimulation>\n    <storage hidden=\"{{ curCom !== 'storage' }}\" bindtoggle=\"tooggleComponent\"></storage>\n    <h5door hidden=\"{{ curCom !== 'h5door' }}\" bindtoggle=\"tooggleComponent\"></h5door>\n    <httpinjector hidden=\"{{ curCom !== 'httpinjector' }}\" bindtoggle=\"tooggleComponent\"></httpinjector>\n    <apimock hidden=\"{{ curCom !== 'apimock' }}\" bindtoggle=\"tooggleComponent\" projectId=\"{{ projectId }}\"></apimock>\n</view>\n<view hidden=\"{{ curCom !== 'dokit' }}\">\n    <cover-image\n        bindtap=\"tooggleComponent\"\n        data-type=\"debug\"\n        class=\"dokit-entrance\"\n        src=\"//pt-starimg.didistatic.com/static/starimg/img/W8OeOO6Pue1561556055823.png\"\n    ></cover-image>\n</view> -->\n<block wx:if=\"{{ curCom!= 'dokit' }}\">\n    <debug wx:if=\"{{ curCom === 'debug' }}\" bindtoggle=\"tooggleComponent\"></debug>\n    <appinformation wx:if=\"{{ curCom === 'appinformation' }}\" bindtoggle=\"tooggleComponent\"></appinformation>\n    <positionsimulation wx:if=\"{{ curCom === 'positionsimulation' }}\" bindtoggle=\"tooggleComponent\"></positionsimulation>\n    <storage wx:if=\"{{ curCom === 'storage' }}\" bindtoggle=\"tooggleComponent\"></storage>\n    <h5door wx:if=\"{{ curCom === 'h5door' }}\" bindtoggle=\"tooggleComponent\"></h5door>\n    <httpinjector wx:if=\"{{ curCom === 'httpinjector' }}\" bindtoggle=\"tooggleComponent\"></httpinjector>\n    <apimock wx:if=\"{{ curCom === 'apimock' }}\" bindtoggle=\"tooggleComponent\" projectId=\"{{ projectId }}\"></apimock>\n    <looklogs wx:if=\"{{ curCom === 'looklogs' }}\" bindtoggle=\"tooggleComponent\"></looklogs>\n    <pagedoor wx:if=\"{{ curCom === 'pagedoor' }}\" bindtoggle=\"tooggleComponent\"></pagedoor>\n</block>\n<block wx:else>\n    <cover-image\n        bindtap=\"tooggleComponent\"\n        data-type=\"debug\"\n        class=\"dokit-entrance\"\n        src=\"//pt-starimg.didistatic.com/static/starimg/img/W8OeOO6Pue1561556055823.png\"\n    ></cover-image>\n</block>"
  },
  {
    "path": "miniapp/src/index/index.wxss",
    "content": ".dokit-entrance {\n  position: absolute;\n  left: 50%;\n  transform: translateX(-50%);\n  top: 0;\n  width: 200rpx;\n  height: 200rpx;\n  z-index: 9999;\n}\n"
  },
  {
    "path": "miniapp/src/logs/logs.js",
    "content": "//logs.js\nconst util = require('../utils/util.js')\n\nPage({\n  data: {\n    logs: []\n  },\n  onLoad: function () {\n    this.setData({\n      logs: (wx.getStorageSync('logs') || []).map(log => {\n        return util.formatTime(new Date(log))\n      })\n    })\n  }\n})\n"
  },
  {
    "path": "miniapp/src/logs/logs.json",
    "content": "{\n  \"navigationBarTitleText\": \"查看启动日志\",\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "miniapp/src/logs/logs.wxml",
    "content": "<!--logs.wxml-->\n<view class=\"container log-list\">\n  <block wx:for=\"{{logs}}\" wx:for-item=\"log\">\n    <text class=\"log-item\">{{index + 1}}. {{log}}</text>\n  </block>\n</view>\n"
  },
  {
    "path": "miniapp/src/logs/logs.wxss",
    "content": ".log-list {\n  display: flex;\n  flex-direction: column;\n  padding: 40rpx;\n}\n.log-item {\n  margin: 10rpx;\n}\n"
  },
  {
    "path": "miniapp/src/utils/imgbase64.js",
    "content": "// debug component\nconst appinfoicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAAA+CAYAAAEqg7lAAAAAAXNSR0IArs4c6QAABAJJREFUaAXtWltIFFEY/s/umordcF1wHyLXerDclkJ8iSC1myX2YtYqBEUhIZQKBflSdEF68EEzeogCoUjd6iWRsgyNbg9KhS3lQ3ghCEtdjPKWrKfzj8447p5pZ3fGrWgGhnP2+y/nP9+cOfvPOYcAu5LdTRRL8RpsPEAsCHbU7hcxocwCoKYFiOyHjgILuq1/8F7mfbZqIUAmDu1eHy+X1D/0ArEXeZ5RSrdIAgJjpOcLXdAxFHLD1AhaeLECto53esl9ocQ6sbs9L9prCzdLcSpUsso8YKJAQyqK9twuiMLA8m9Wxp7zLoJg4FDlKZoslgziOtia8HV69AdPQcSICS4Sz6sh6kpNEjHFkjuYeNqquTUUefRJmGp6+G+f5Ge+EvQIrzW/g9ttHyBwnrNMTfshNsYsmR7ekw4zwXMJkGS3p5dNig5JU6FC0ks9S0d89HvN8WzYuNamoBYanpzyw8mrT8HbP9LHWg+eo0O7UNYQJh1lceQS1c8ynCYMp+GwpU73P+dUMfFQxx5fS3DKkha+NAIUEx7BaSjbfWeaYfjbhKAWOMfxbFkW1VTldNgq68qyefKwMJwvc8rvgPC/bS9uusB+VwCFhLC8yJQJIc9tlhW53Td3jQkTrzPF6qgu3QpxsfMztUxfVfXtxyEor2sHayJZxgY/dVypyNHkEFvFCf5yWQ74fNCq6xuFiQomhbo6FXkynIpM6FcanOrHpejJ4FRkQr/S4FQ/LkVP/w6ni5L2LEr3/1wuJS5LFG9fByX5G8SHrFiG1f1Hnf2KjuQCVU7d29LAujweGs7myW2D6pM//Sw5I+MCp/hxS4RULUhPAI7tdQHeoS78NmWeaoi92JNBZ2hXKANVckJ6Bxv2r5lLJZljCh6gNFWVcbSUWJAms7nw862C10IizYKsdDqSWCaYDabfcRutAFk7mJSfqG0Hb98we/amKlyPHGcZZXxrdcGCFY8oxqTYFA7Q3FP3cJBO4FqosOorX5ZRtIyyIG7J7AcIxqjqFY1yfNzmjEC5tGgADUY1kMc1NRjl0qIBNBjVQB7X1GCUS4sG0GBUA3lcU4NRLi0aQINRDeRxTQ1GubRoAIX9YQ32UTOVNrPUbFJFLSpZQ+LijxSoTBZRddo/AzdavPC4c0Cw35G5Go7kOSHGrM9roFugRedapC1EjLTxSQ+0dQ3A3fP5EXU80Eif7jKvOzNTAn1zsSAllQAePXuJe2/dvcOg5sCQkl9cPFSzgKhkz8MxJrwwxrmjJtCNu5nOlCQ4yhpMW5WoeVeT17AaDI+s9HzywXV22sbbj4GSPmsiuKTVRjyrNeQfPQ2UZDGGN2nZJFYTkKIOnhkE8gYI7bCZV17CjWbU/QVaHyppVJXtMgAAAABJRU5ErkJggg=='\nconst gpsicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEQAAABECAYAAAFPFKMkAAAAAXNSR0IArs4c6QAADd9JREFUeAHtWwl0VcUZ/uZlIQEEDIhSFhE4xx1oFZOwlKCidaFWLLsWtbIoVUEFxEAKJCCgFUsVDaDFpYpRpAWrRwWJrAkYCiIoCrK6hRRk04Qkb/rNfe++d+97995370uI9RzmnOTOzL/MP/+d+ef//7kP8FrkjtVS/ndf4G9a10t0eqFXFFCv60/RtI0G9+kd+lMBdKCcmq4RRiHpyJZPuXhiWJ6gXDpiSCbVYZIrrbVPCBElp0aozS4oi86plp8yN2NnSIkv3G2WQz6a2SEE1DX9+ZowkixZEpiynKzpSn63U2srIQPK27Y8IPCs9xtolcSkQNv4P2qIyCnLqRlLQ0ilO8Pjm7jM7OVXSMa+mHXTe4nEpm76QmI+BA4jLaWzGFV4PBLHsq3WT0heXW+Rz6npt1oSq84oBhtek+xbxb98OW+omXluxhZLRvLA1jDijlVRipELbg/DDSpPNHF7flg5RhWkaH3N2kJ+sU5i0ZgTgNiNjjdegu5/CKOfkXpmuBFRk3Ouq+eok8jFF0FvakbpRyk3N/0pE5Lbhty+Qspp3fxu8ePGi7HY0t/lYsuCEPliUtF9nkax1IfSieG1GhlGSSL/s1SiTWcjjrn+zEApJq4zGURTQ+Zl+h0ZKHZ3LxKUqMLI2SRJ1PZ9uh/Xme9PkP6nMOr1MN3+LRCd+5hoNSB3bIlpkUXMP1JPYY66vVI9Eq2MAG7/zaa2L6GnqW1oGHQixhj6FVOzdv3VH5rghoZpXnL3RolGZ4fBmk7EK5BysEknc/tJMak4JICZibIR9xR0DHOxqa14MFH0f73aBspZxLJsUzM22BIbAfKZQWHjYzSNEW/MSGNZl19tNzOafWOUpbMkNHbKvIxxoXWjFB5vCeknL3NQvDx+fnSmtRZLfPlEZhqOy6+4mFLQ4kLg0muBs9oBDXh4HPwS+GYHsGGRhN+v+D4ucorHxuKpw2MKIqUUyM3w48JewJX36HTuntWVwLODiSumi5yibCciR0FopUp5dp5lOjuduDnBlDlISkoTE9YctkKzFUStVNwxH6jfxIou3PclN/SBj4HGLYBON4T7rWrzbuNrq+wostd9Egk2exVBKM/sB3E+ramdEPs58LJck2HTGXMC69C8fSb6zdC7ws/hLwk83W8rO6IUELKQYWytNgWZNg6Zeu9L86qM1tVIywXaFWW7JmDVAmN3zLq1IBKbcJibw6r4EjgfyX8OxS/ORFKqA0I0yPLVcFa/lpgsTWeMTiso+5C/CSnvlWidmiruKCzXQerJV/MNzu9xDjKHGLsD9d0bOQnxaTTA4l3pSHJajxaoPvm1pTA6knqe4Cb4ahvQhIu1eXsjxFzfuwl469GjnGRjMyDQilo0kUja7hnwGNCsbSTIfTt/sERV1Z20JQvtiGIKogjltPTe8It3ecK7wg8Nto9+zbJpfmrBeU2RwBNjGrgfkDUiFRddFRrLtqIMWAI6iexi7vXYxZMgip2cmdUKFT/ut107xYuAj94s5WsweFmxBYkbg2tnhywYZ/ZalF8157pG8TD1rBHjIDJ/eBK+23JS6xNYRSNn69wa6U7Xf1YaiGuNyKezGuJQ+XTO9HqGG61pBZj/kBtpvucxRH0zHg14EoQ7hSYWD8Hnk7hioECLC+gqnhcw88pV3Pou3UV1lIhyNBQtxQPrD7kVypUgUqUVq6u34txfATdOiM27bC/w2kOUR+yihjrEJnBhWZn9mka1P4KRr9BSWuTPnEb5YC7w6UpgUpF9mjFI76gRmdf1NuK9oGJ4p/EcYYHzBjxvHHlYO0bkrIUO/uoXaySEklBlOjr1geaIO0hsKwhO+L/FoCccSD2AVAZNiGYq2WpHZSmItjgTUxKRxp3pVCoreMAtBta9BJTtccIEhuYLVPq/sEOydBUZqb2CG8Y6vlPMu1WisoKRtZjB2X6Pzcvup01piTvpNKdaOGF2EUFQMkuNkOGlaN3RTnjgmQGSbuRo5fCoCE5MWv8Yt2krJCY3x/N3AcrTtyoX9ORaYahiUawFsUAMdW17nytZbhMTi+aE+oIV8cjqg9RObywcRk1ZFIY7zCQNsoAE8+1WELu+Vc9JND6zmx2YmlmO8hPWr1U51wI0x9HFu0b81ULc987RaFYuegTlk+KEFaZ3QZJTJBOZLa2Yxew7dIAakZ9Z4XkXpPdowTVSYsVM9dFwDdNiHCuEzcsoiO9ZK5CNIOIz7PnICh9oexlQr35zrn4ePuYip2ekU8h5GBK1jgOIe0ogJq5/1UwVaFkuKs28n5BljnHMqw8Ah/YrLiu5AI9wN/xOcw9Gvio46+ixjnwL/OO+H7mY60cD1Rq2KfQ9jqBPdqOYSe0DTHWc/JGa+iVzyNb2URtCxTkpqS3EuEJKFF0sRA8i9UhtyigtmiKypxVvQNt1cRZiZb6a8mY7IRRLW0FEr8IqEt+J/CHWxilSILv2wd3A9uVgqEGV2RdbQRQJif9OU17CKN6eQyxIwThmCtJSYqE5CqKIKUwX7N3kh3JwvJa5/SXj32toAHlMO5eYgmjkk4oStfVSHQjqnFkGoQXjVWUZg3AeTrGLK0G0a21G9nh2SGyOCoNrggngH7hVb3JH4LBYIxkE0gtiLl6MkfQtZ4jDXUIXoUEkD6e2K43oDOh7jMKxslKo1INdee4OWt7UGK5dNLEnQRS5lvdQ7qGylJFlwVAJnxgvxhfydPNWPAuisW+S1hgv32seacVTQMUPX9BhmmUGnOIWj4CBdLK1rwrk9g9sb1PdimF71rhhwCN/NU/b7hru2Z2SxYh5lW7oTuOc1sBpDdRcAzXavG6Gl7OyzkFF+c3E/S3/enGz13NDF8IRQjkQK/m3FPVSljj5eCGaGlRqXSHysWsaoOLoGE78Ibr1jelrSpzXRaBdBrQEXEpDb+Kqo1xd9OwqAvbwUl+SowoZhHgc9RrNFmPfswwbvQ0Sxq41hcjcrr9nLmIhBW6g3Yz3uB3aTXl4rNqrqfTp6oWBm3eBE1TQUDGxmO5AzUuNFcLMZy6D3YnwJUh0v13g0t/UXCovHD5+B1j7Au+0qzkXkUdXaZIX8kjcuBUi89K7w48VZJiMrOHAxb0jeddtW+WwCudRJ7Q5Ql7NFbMmHgHiUgjdstkcbDRUwHtTDqtxsYlH3hg0DIv/NZVfS3yi8J5kCDEmBkEU2PNM6BYupsHsi4uu5pkxIorh/0WHyjqo4A5iMbcQbZv74kkhVMZkKuPPuPgaIGuY+1EiMY8fAja+Bny+mjf0ldYyqG/yLr8FuDCL1NYokWxN7cL5wLb31Baawoh3sgnm0HA9knafoFL5KQ0ls+w8+hIc2NqA1AXb6gX60fkGEpPG80sj3ryZS/CzqgHUwyyeWq3R6CyJvnkCDdLMiE4tWQ0m4SXKjytZO4hJ63Y5oeswh5SbjhJ8VsnJWi1jsHdl+KuYRhwNHP0u8MZyiqZEcDc1g5/0q/B+kZbWPFa2AQtHtEc3XvZ07mPCtW2oF6ZkVYYWfjXerba4BoD7wFvKqzS65h0M5C6rKiOplAHfEC5fR2VEclTXtKTpwKW/AWtfBNQnQW6LLqsuuws69wqBaKbxa9rGBVsDyrHSoNUXe0TO+qhMuQEzVjWQ51U3tm5L09ZBzKDsLug8KER+rvH7/hsXbA0o4UR4sqE3jqovQJ/gwXZ9r+exgrK7GNW9QoT4UOO3t8QFWwOKMoQdGMdA/oKnlGe/QHGSBf0SIKtnalw7Xq89XP3TZdVld0HkQSHJgQR8yT8pod8FawPKtbxRVV8AS/mE8mO0U8QAdqrKqd0uxo79h4nTDjofJwIdpmRUsqqSnDI9UIn9X8RGCWNob5iTcv3ZSJg0UPt6O/DvGZL3ShxX7GQKNgct6y2J+s4yr+u5VDq1KEfy2E1Cm078VIeXGgkedp0y5Orewucbw9u6JyNFsWt7UohiQqUs4Jv+I9pdAVw31o6vc796e59yB26n46R+CKZCemNJTpVoe7nAZX2BtFZGiLv628wwBz5Qnc8Targ7ogCWWRCXlKGV0vgcYOBfwC8RXFKeYrSqk4C6atX8HYzmDddfvY4Yl0LUIMy5d0a1fz1rKdpH+upj/Z+yqONYfUilPjNDYobIWbMlHnHiVog+GCNfRlIYrv0eqP8M3g95zIjpjOJ9VhwH1FXhUfo7AvydWvHIeFkpuhorRDGRM7q3QWXlJtqCprhiANDFU4CpWMRXNrzOILFA0ZYhOeky8fCaffExClPVikJ0drQt2TS4eVoA2H+mwBnNdVDtPpX3WzA+ELj5RDbv6Vwfq7EEqVWFqMHk7KwmOF7+ERXTHhddyZzJ3bFk8AZfSTuxXbnvPLaTG14uHl5+xBsDZ+xaV4g+nPaBp/q2MjFR4maG7k4/n9CJnJ6ljN6XTFS/cFAy38Zs2MtO6PHCTplClEB0uZOxY9+HtC0ZmnPVJ5u9XodkWlBzsnhoCBTh/DY9+RtLnq+npniVLi4p+HvpXsyKMwssfbj+YaF9J+aGE78Jw9v0bCH8zOr3Ftlr1V45paVOFKLPgFcWb1IpN6PZucAttIN2Dp1ysBY/wrNjL0nFEuZF6bLWTalThagpacGaqCrmNmqAnncBl1xrnqnKg6p8qLqAkonpImftNjPCqW3VuUL06cjczCcZwN2PM9Iko1ih/XLvrTyJY4cok5jDVXG/jluXz59MIWqSMr9PfZQenMHVwvS6llN4g78ynBAZ/dalQk6PdVoDzhr4HxNMxoecsbI0AAAAAElFTkSuQmCC'\nconst saveicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEQAAABECAYAAAFPFKMkAAAAAXNSR0IArs4c6QAABSVJREFUeAHtWz1MFEEUnr0gGJDCn4ZETAw22lgZFQqRoKU/jcFogaA2avwpVBByGA6MFtpoo3JaGEQb1MQGDGIhJloZE63UGI02AgVCBMyt+y73LrNvZ2d27naPE2eTy86b9+bNN9++nZ19s8dY0KM2meiftv/Y/K/uTsJ2td/a1+0yQBkNS3jrxuQlXmRDzedZxaEO5jJ61tLmMkIhhgXZOTwjWS+aOp5AKANH6MIDGDkCQzByGaCysmwpOmAWlNA6W5spVFgllssDNQBZaIBd+RqIPBWoDoOMEgvyqaH+9A9tMBA90NBA5ATrqA06ETKGSnrOK6CpMyr7ImkffkhtGX9NeaWvk94d+3g7aTkdh2BRf+/y6tnZua9Sa6J81dqZbU9UCy2edALKLx6wHs61fd1dvlh5QxpUVKaOfK+Ob2+OYrilLc47CuREFKnOJBTHjlzzNFYONB0XBpbIGbQROllVXsn8GmBH/DnQcPgGonKRO+l98diD+ufMlKcOK4TDGfn4HvXZM5Dtdwid+Bn71YfiJBsndNaisgDFhKBuEVQJp0hYptm2vT+M8cWs2JmXLReuafuic5hsvuN1tMz7gSc3P4WJQGWDRKTUqeODik5EsEx1jvjOmBW3U/bFsdbOLuo7NCDUsUhGQI0OKPrk1AKysnwZgylFNCNQFkRAZHVaQB40nZD5cul0gYUyG7kQ5CgYIJQ4rRiBxvBMFD3SeMcQ1Dd2NQuDmrfjy1pAYK6AIGzftpv3ISyjrVApqDQxQknxMGIx64dsHUQdGNkw8N8x4Fkqwlv53Oz8a5vZVfmyYVnW5FhLx4qc/MAS78v0ZPadml8C8ss/vl5UBtunn965smkyQMIpXrTwkTnx09WvXZ9WJfqYTVdktI0QCDUKIuOalS6IgoIJDYgMbBAwBQECIFVgCgaEB9OTtCbo3RQaEBobfpcKmEk8f7Sc6j1PX2qQiwxPb1il6RxajEB+HO8OVSeQv9I5tIAMHjit41vLNpJLo4UgY2yAUNYMI4YRygCVteYRmDGPPbnLxmd+UT8uuaFmQ6DXUr6RFpCmgesMZkzVwgm2n+BlPcg7MoLRvmtUIMAxbGGpMgYIAM/aQLBh2GcDhDJqGFlwRmAughwMBeJ55XTe0MYdo9xeE6l3H7msrLR69OC5bz5qU20YMAwYBv5hBjzTKh1L5nubt059pFMt7VdHdgYh3DbV8YG2SkJq+xLfIc0ZZJ2KTuGMGYOgORO+raxM/Y5+/pBeG6dSNny9lzcxyjUR5nyDrJdlA4lKB8kn2NPu2L6HWc4mO6R2VTv/MixabzQyR2HpMAJ4f0GiDIiBXyZigJh4LhGjjBAe2L9QzjdiFh0heNFExNQle5RZyKIjZHN1DY4pfd5Svc4l6wpIDLRL2amrqvZFN4f0aHyLqxpcLvrIIgRyVnBAyiiMfX3wgV8/o+9cBqxqE1mEQM7s6KYGdvPNSKCknwoobLVsrFoTKG+n8iXTR0YIdAprF51kogxooXSR3TKFGkDY/RhCCKOGEEMIYYCIJkIMIYQBIpoIIYREtg6BleWRwdtsavY36TI/ET5XubX3sHJTNddeIiMEtqSBDN1Mm2wgQDLsMoNvnb8FyHxSXWS3DO7Ph5lpQ1/omw4mDDkyQsIAtxA+DCGEdUOIIYQwQEQTIbqEON/R34c2YWW+SP+Ri3ymDcci61S5cweN039UmJu/wmxWjxtXMqfFpEt/FmOx0dLSJWfNVynFdGUMlkXCwF9RVs+z7mt0CgAAAABJRU5ErkJggg=='\nconst h5dooricon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEQAAABECAYAAAFPFKMkAAAAAXNSR0IArs4c6QAADUVJREFUeAHtHAuQFcWxZ3ffvlMOOBEQ4ydQlEFFjJalUYv7gOYOsYhKpJSSBDEpwaTUCsaoEUQ+SrRKkpiUH4hBQ0pDqSgqv4M7uMMoRiNB/IGJ4olyqAfHneK9z86ke9+bffuZfW/f3Ts0Kabq3c70dPf09PbM9PTMHkCExCSO+KJTyLz7ycr7MsMNSEwc7y5CfMVqu+xB0r93Hhi3zPYgegrUnPvXVVsp+JaXbBE0N6a/OWvTBnc1gJuLzCfG1Xg7g4ArZSU9kzfO8CJ4eXa3ZG1q8PRKNkn8bP2InTsgcfedHv4eBVL39LPPBeO2OzxIVMhw+PwzMBY/FqgkgEdxIDI9TM3PvQYvAsu8/Njs+UFu9D6k9PTsqqvcHMRSQByDojoxoWZgImm9psALgJDwsfi6zXOowsPEWr9GaOeNDhCEAchiqc7TWb6xwcZPTrkcrBdWhtEG4B6rtl57xX4JxuSpkHrkQUj/+eEAAQHsN9rZ6dR5mEioVjMGAJnI1y/h9JRGbzU1OmAlE+hTDub8exwCic2OGuAw5psyXac6DxN8/28j7FSqYCNHOQRU9if+/r/9IHWZDMfasNY2HtuI6uoGqDFLAPXYSVdtVQMwMbwwXybK1jUPC+Al6iqfd9t8oTx1TzLJGZuALgks9pl7OxrbhsSXEwNpCypmbOAgMH2TgcNEMJuJQ6cyNKfSl3G6EzdiJEm3kiMJe35Di6MpZBXWpfhDSwEGHwNomPtliw4TCaBnWFdsxsiAEjboSO50x67J/gmTwo2jQU6HSiaEnF70GzcN9s9nAQLekAih3UlOnhjQi3nfHyQdPnWnO0omhGk+sSJDkEigkuKZvOtv7HyxDeozAM/Y4e+8JdgJJ7pQw7NyfiUMj060U0YyUipvXK+mTqczXZx2TVA0FUWytvraRG0lp4GYmnObwAG6Q4X3/wfzvBzZPYFTaUIcbDPnLgR2zBAJLskzvfgBsLa+1ooT47FuhkpTS8DBleaCe4GdepobtyR5Y9ZcsCaOD/ROKQiuW9vErg9GuwVJTpsM4sCBbglDy6b5yF/z0ioFoXmC7/rAMx6kELFbZ4N2znl5mXan0jP2cgw4auT9XBFz2tChdlm8td0DL1VBqZGYVr4t6Rdk5OnAd+0Cq2E9iN27o7ePy5xx3fUF8ZWCsDVrErhSe4j1n8wAkUoDfPWlB56vYG1usquNKVcD9O1r58XuFvRV2Tt+OqUgfiRZFrs/Av2Sy0BDtz9KkoJIIYhGoFZREmeZkXxCbERWe5/87e2QWjjPCyyyZNsew1XGl4rSiKSVKzTNhsYNN4FWc4FdZS1dDOnnn5VoyidOC+gC5FZviVSUIH4/w1q2FFL33wdAP0xM0yB2482gVY+R/ANPjoMgbhjRNYLezz7o7BgAffsFmEmA/qNpQL9iktjXBqx+88d+mlAbQfU9kZh6JaRm/hxHyld+uqLLvH6Nvf7j63R7gw4fes2Rkri6piz5Cd8oQJyrnToKYnfehQaifrP81S2QXjiPNsNMA226Wd+0uFAjkQVRMRKX1lQkv7L+ji1mdlYM7sB9rWKPraI+DPsf0UCojSRqq+8WTExGIw/FKbqPgn0Y71M2iT1T/6mfVtlIV13VHv2ss4cYP73Oj9+jstizB5Jzfw1xvbw/LqwdbmZqQXCP7J9F3UQ9yfN/vAzpexY8HV/XbO+UJa/QCU0ilPrJMAojhBjo53vIBRH79+Pmme31C6KeGv1YOMUnrvqhHxq5HP8brsimaeOLA+0YioFWP3EkjYg9gTXKzyd/2W2J7ftxHHZTI2xwbhvSUyMWKAhjIvBqImkEysvz97iY2vZ24EJ079UU004hXLF/H2i63k2NFOJeRD0ZayzNA4JEGzWuhtLzg0F7V3Uga0z/Gca7cjYGaCMw9GR8Nc0eXKUguO9ohy+/qKAQsD/hTt4Pylu2ZlzjiakJywK2eHHKT6QUBAf6Xpx4KmgWlMlceB9Yq5+TxYJP0dYG/O03C+JJBKUg6HG1woH2EXD8CRIP2IhTwMBf1MQb6m1ByLOPkpRYNAXTVOxO4j/vQermG92gvHkySjv1r8iLJyvVgqBGHEZZzCQKwVEY/vqrkjb/MysI698/P162VimIPQX7NMKy+xvxUUskxqIjG9Sp6JFGBGrE+2pk4NN6/C8gtr2eVxjetBF4NhLAIr4apbFyHDW6TyN63cW28YlUCr2sWbYg2mm4v7l9nh3OFXgIk1owB2gtcSe9eqy7GJpXCqIJo9XPUKushjj+0vcvAnngyt/cDonJlymZGxMuBX3atco6FVApSEwXe5O+nkli44aZGAGYCeLj3ZC641agtUMm7cyzIPYr1JYi4C1xwp5KQeCE7+yFlp1hNDacHXd8LlJIhtkv2ugIY6ocNTQFC26F0QThUYXo7KAzlpwKXZyUgrjqS5oV6ItgCvgiBAwVBL07YR8zrFtFeD1LeFSTuul6wPsAFC3Knba6uLq9SRc4kxWTJpmJA610plKtnTQCYngYK53gALICwJs3Qep399o1uGxchXuZxxVomfqwCj88Oa5qKufiUYLHfnEL0HBWJs4hddcc4Fv/SduGrebgskq2rL5gTDSvRlQNiUk15ckO3oybpDO1M86E2Kx5+IJ1ENu3QWruLIFGzkDTbipb27RIRR8GK1oQN6OucdU3AOe/t2GM7Y6DOIet27zHjXM4f1gDhzXQOxooavCmxtXUWNxajqIM7h1xeokrY6060ybF1m56sVALkRWC5+CzcbMzz/Z8VLe3CrX0Ndan5t0O/F9bMRjPZsfXNi/IJ0qoCxAgEmwiweyzuUDlNxtgTP5xRkABEwpJGlkhGIDOOFUZB6sQ329UvbPPE/B5IcHU/ruCignckqOLJ9r34TlpeKLNqPXcCsA4QjhSqWsqBoD+g8uAueIWnibkS2RqT9mNG1khqIVWOobLuuBuHk5e7HwXkrfOdMqHMoN3CMH87QPAvj000KyzXcWXGqj0ASIrxA5R0DFhyBaU+Kp2/Nqo04FVHOVrtrRF4q9ffIl9OU7JOSuz0EpoIThMWlEdgWiDR4BBg+3hRHgy6WMuBK3mQln8Wp7SQhgPRtz9AkWeVLVs+F4y9zOyyxQ3xOsq7iT2FrRSN3qv5OUw1zStoDCRFcIhFmmVcZ+jUO/Ep4FTzF7pdF6m2Qket8DK8IObNrJCzDKwtZvXQpAzG+x1YsVnBWVwy9MreRmwNmN9ClpI5EmVPdPQlqitSovOTgMokoWbflVi2Uu7ss4ONvo+NJF1pXqyIceC+cclKJPi/eJVVfHlFzS3pdiqVd5wq0IABQcFVhaEk2VGw77wr5tCqxoDLBZzg3o9L1ox9oGXhJRJrjC4n1HW+4CRLYToGGBQHuA4mqTY0YFjdJs1+9ZxYC5f6WumtEX+YjOk/Je5Q4L8cojjKXfB4UJSFmUhqJLsPKKM0Za21/m4+e7RsiOPxIii2irlCoPHgaW3EOmtgnSFQ4ROXjsVBH4hRYnhjTBzyTKAiAdXISw9YP9hGoRYh02EWw1KIoKXSnjFWUiWqTRDYhBIuLZBW24PJXBS41sjnvoFmKkBfoWoPGRJKS1EdQ1C4rifRSnEcX3zuO8009PBjTuJlhZ3sef5Du/GMd+xqTyIZEIr/ZAh17eg+47dZScOBcAryDKln30K6EeJvi/S0ZXXx6I7j8tl3oTWJt7YCvTNGt/yMp7RdKnRBxythhN0f0Z5GL6INKkWtcrgXZNWy8J1vcAcYvzyNtAazoL008txXfIeB9Dckn7qCfsne8GOOAK00dXABg0C3twEHC+FR0kafm+jXzEFtPNHh6JLWTUteE9GRVSUQriV1XK+IZNtRbugFkz8OQkvi/GXmu2r/Pxd+owwlwTWWevX5gC+HCtDheEHwnrNWGCjzlA7YD4ap5g9u7cUl8scHFemKIWYR0Jr4mD+mIiLtzdLVnBBnf1zKnBIcLyKwxs3AN1DpglYG34SXhcfCzpaDES8yeDwU2Sk2x4vizZk8gW/FOzxY7a6ygSGRcw4OV8ha7+S8OsA4sWJxBWX0GFmAk9Sy6KIUNQqQwzta0aUOZQhQmqvO0m67SKadVATRSuErjsRYXrpEqCQ4Tc1ifd2QPrRP9niSZmjyFr0kCGmYkLVsGQSpuOR9zQs2vt9nPgEbuyYXncRsGHDo7RdMhzR8iFYeGOCNzUKcfBgpk8MPsPMUtOIP8xWbXg/amPdUoiKeaK25mTG+HSMy0/FYWUHUfEaocCVgWm140HezFLRFgXb2woWfn1iNdYL/CzPlh8vwHTglY9lzNAeiq/aFP3uoaLhkilEwRuSdWO+y8Gagbvkq1BJfQmH9esv9LHfZ3rtRYUdM7wrRdF0a/263N4IGHlny5mhP2iu3viKqt2ewHpVISrBknWV56C3OwPrrkAl4TYVlYRfc+oXjgPo0wd4/Wrgn2TulePN2TRuKFdqTH/QWNPYiHMBOcq9mg65QlS9SY2vqkKnj/71yVEo0ENmvyEvsCefLOKCmYrrYdhhDfSGBv4LLWYc0VdrRhcAAAAASUVORK5CYII='\nconst injectoricon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALiQAAC4kBN8nLrQAAIABJREFUeJzt3Xmc1fV97/HP95yBAZQBUdkUZBOGZUYkLnULIpAY26ooGiKk5ibRVGPSLTFNcluud2vTR3tv2zRtmqWJUbAJKiSP5taGxWDVNkYRGBxGJYyALEoUQZFtzvneP+A7C7OdOef3+26/1/MvAzO/7y955JH3K78zc44SAABKMGfBkotySi/RomeLVpNF9GCl5LCIvCoiTymVX7bmsYeed32fKI1yfQMAAL/Nvumj46pyVX8rSn6796/Wq6WYv2/tqodeSf/OUAkCAADQrTm3Lv6QKuoVSqmaUr9Ha31YVG7JuscfXpXmvaEyedc3AADw05wFS65TIv+ilDqjL9+nlOovWm6bMK3u5eatDS+ldX+oDE8AAACdzF+wZHhR6ZdE5Jxyr6G1FJTSi9c+vvyHCd4aEpJzfQMAAP8URf9vqWD8RUSUkrzWatncW+74aEK3hQTxBAAA0MF1v71ohKrK7RKl+iVxPZ4E+IknAACADlQ/dUtS4y/CkwBfEQAAgI60ujLpSxIB/iEAAAAdaFHj07guEeAXAgAA0IFSMiDFaxMBniAAAAAdaC0H0rw+EeAHAgAA0JHSW1M/gghwjgAAAHSgtVpj4xwiwC0CAADQwTlVx58QrffZOIsIcIfPAgAAdNDY2FgYP+2iY0rkBhvnKSU5rdUCPjvALp4AAAA6uaZu4t+LlqdsnceTAPt4K2AAQJeuX7jw3OOF6qeUklpbZ/K2wfbwBAAA0KUnHn10f6Gqao7W0mTrTJ4E2MMTAABAj2bfdufIfEvLkzwJiAsBAADoFREQHwIAAFASIiAuBAAAoGREQDwIAABAnxABcSAAAERh9p13Duh/uDCq2KL6HTtR2P/0T5en+oE2WUcEhI8AABCs+QuWDC+q4t1aq1tEpF6pdu9uqvVuEfVEIae+/fPHHv6Fu7uMFxEQNgIAQHCWLl2ae2rzq3+oRP6bUuqMXr9B61UtVf3uWb/iQSvvb58lREC4CAAAQZl9550D8u+2/LMSuakv36dF780X5IbVP16+Ma17yyoiIEy8EyCAYCxdujRXzviLiChRo4p5te66BYtnpXFvWbZ+xYP7eMfA8BAAAIJx6rF/n8e/nbOUkjVEQPKIgPDwEgCAIMxfsGR4QYrbS3rNv3cHtJZ561Yu25DAtdAOLweEgycAAIJQVMW7Exp/EZ4EpIYnAeEgAAAE4dSv+iWJCEgJERAGXgIA4L3Zd945IH+o5b0Ov+efHF4OSAkvB/iNJwAAvNf/cGFUSuMvwpOA1PAkwG8EAADvFVtUv5SPIAJSQgT4iwAA4L1jJwr7LRxDBKSECPATAQDAe0//dPmBk+/tnzoiICVEgH8IAACBUE9YOogISElrBIh+xdaZRED3CAAAQSjk1LctHkcEpCTf0jJaiTrX5plEQNcIAABB+PljD/9CtF5l8UgiIGHXLVg8SylZIyJn2T6bCOiMAAAQjJaqfvdo0XstHkkEJGTurYsudjX+BhHQEQEAIBjrVzy4L1+QG0TkgMVjiYAKzb110cW6mHc6/gYR0IYAABCU1T9evlFrmSdEQBDM+Cslw1zfi0EEnMRbAQMIkqPXk3nb4D7wcfzby/rbBhMAAIJFBPhr/k13zCzk1Fpfx9/IcgQQAACCRgT4J5TxN7IaAQQAgOARAf4IbfyNLEYAPwQIIHjrVi7bwA8Gujf/pjtmFvLi7Wv+PcniDwbyBABANHgS4M6cBUsuUqq4Vok62/W9VCJLTwIIAABRIQLsi2X8jaxEAAEAIDpEgD2xjb+RhQggAABEiQhIX6zjb8QeAfwQIIAo8YOB6bru1kX1tsf/jEEDZeTwc2wdF/0PBhIAAKJFBKTjulsX1YvOrbM9/l+67275yu/dI6NGDLd1bNQRkHd9AwCQpuamhr3ja+tXKyW3i8hAS8cOVEpuH19bv7q5qcHmpxemzuX4jxtzngyorpZLZ9bJxpea5L3Dh62cr5TktFYLJkyre7l5a8NLVg61gAAAED0iIBnX3bqoXun8WiXK2nP49uNvEAHJIAAAZAIRUBkz/iLidPwNIqByBACAzCACyjP3pjvqlMqtE8vjf/9n75LxY8/v9muIgMoQAAAyhQjom7k33VEneeXd+BtEQPkIAACZQwSUxvfxN4iA8hAAADKJCOjZ3JvuqNMnP9XvXFtnDho4QL503919Gn+DCOg7AgBAZhEBXQtt/A0ioG8IAACZRgR0NH/BkhlFJetCG3+DCCgdAQAg84iAk+YvWDKjIDrY8TeIgNIQAAAgRICr8b//s3fJhAvGJH5tIqB3fBYAAJxSqKrao0Xvt3ys888OcDH+AwekN/7G0JrB8uXPf4bPDugGTwAAQERm33bnyHxLy5NKqSkOjnf2JGDOrYuma62etD3+X7ov3fE3eBLQPQIAQOa1jb/UOrwN6xEw59ZF01UxH+34G0RA1wgAAJnmyfgb1iJgzq2LpiudW6eUsvZ83MX4G0RAZwQAgMzybPyN1COgdfwlG+NvEAEdEQAAMsnT8TdSiwBX43//fXfJRIfjbxABbQgAAJnj+fgbiUfAdQsXT1NaPZnV8TeIgJMIAACZEsj4G4lFwHULF0+Tos78+BtEAAEAIEMCG3+j4ghg/LuW9QggAABkQqDjb5QdAdctXDxNFWWdEjUirZs7XQjjb2Q5AggAANELfPyNPkfAvJs/PlVEPykidsf/s5+WiePG2jqyYlmNAAIAQNQiGX+j5AiYd/PHp+pckfEvURYjgAAAEK3Ixt/oNQJcjf8X7/2UTBp/ga0jE5e1CCAAAEQp0vE3uo0AN+NfLV+899NBj7+RpQggAABEx8X45/N5GVBdLS0tLbaO7BQB827++FStCutEqZHWbiKi8TeyEgHKxiEAYIur8b/3E3fIuWcPk6/93bfk8PtHbB0tInJAa5mX07kjjH+y3jn0rvzZ3/6j7H3jTWtnai0FpfTitY8v/2HaZ/EEAEA0XI7/pTPrZOiQGplRO1mee3GznDhh90mASHGJ7fH/wr2flgsjHX+R+J8EEAAAouB6/A1XESBKnWntsAyMvxFzBBAAAILny/gbjiLAioEDquUL93xKLpwwzvWtWBNrBBAAAILm2/gbMUZAFsffGFBdLfmcko0vNVk7M+0IIAAABMvX8TdiioAB1dXyxXuzOf4iIr/c2CDfXf6oaK2tnptmBBAAAILk+/gbMUQA498gf//95VIoFJycn1YEEAAAghPK+BshRwDj73b8jTQigAAAEJTQxt8IMQIGVFfLF+75pEyeON71rTjhy/gbSUcAAQAgGKGOvxFSBDD+fo2/kWQEEAAAghD6+BshRMCA6mr5o3s+KVMYf9e30qWkIoAAAOC9WMbf8DkCGH+/x99IIgIIAABei238DR8joLp/f/nCvZ9i/D0ff6PSCCAAAHgr1vE3fIoAxj+s8TcqiQACAICXYh9/w4cIqO7fX/7onk9K7aQJTs53LdTxN8qNAAIAgHeyMv6Gywhg/MMef6OcCMilfVMA0BdZG39j3Jjz5Ev33S1nDBpo9dx8PifV/ftbPdMXz0cy/oZSktdaLZt7yx0fLeXreQIAwBtZHX/DxZOAEy0t8tyLm2T6lAvlrCE1Vs70wfMbG+QbEY2/0ZcnAQQAAC9kffwNIiB9sY6/UWoEEAAAnGP8OyIC0uNi/LWWJpUrfEjr3Gyl5BwbZ5YSAQQAAKcY/64RAclzNf6Fqqo5Tz66bNvYulmPqWLxt2xHwMTp9U3bu4gAAgCAM4x/z4iA5Dy/sUG+8b1lUigWrZ1pxn/9igf3iYjsaNz0nosIEK1vnlg746ntTVt2tP87AgCAE4x/aYiAyvkw/oaLCBCl8qJk3tgZtd/e0dh43PwxAQDAOsa/b4YOqZG6qURAOXwaf8NJBIiqyRXzR5ubGp4yf0IAALCK8S/P0Bo3TwJ+uXFzsBHg4/gbLiJAK137O7ff8n/Xr1+vRQgAABYx/pVx8nLAiTAjwMX4i9YvF6r69Tr+hu0IUKIG73zj7Z82NzXsESEAAFjC+CeDCOjd85u2OBn/lqp+15Y6/obtCNAiLzQ3NbwgwlsBA7CA8U+Wi7cNPvz+EfmLb3xbmne+bu3Mcjy/aYt8458eDmL8jfUrHtxXqKqao7U0JX1rp1Mio8w/EwAAUsX4p4MI6CzE8TdsRoBBAABIDeOfLiKgTcjjb1iJAKX3mH8kAACkgvG3gwhwN/5Vokr+gb9SpR0BSuU3mH8mAAAkjvG3y2UEvLZrt7Uzu/LC5pecjf+/rVy2N43LpxUBWmTPVTMmtAYAvwUAIFGMvxuufjvguRc3y4zayTLUwW8HvLD5Jfm77z4U1fgbqfx2gJY///43v84bAQFInovxV0rJZ//L4kyPv5GlCIh5/I0kI0BrvTN/Ru53tm/efML8GQEAIBEuxt84b+QImXrhRNvHeikLEZCF8TeSiACt5XheyY2rf7jsV+3/nAAAUDGX4y8i0rRtu4gIEXBKzBGQpfE3WiNAF65Xoob36Zu1PiqiPrZ25fKfnf5XBACAirgef4MI6CjGCHAx/lr0K/0cjr+xo3HTe6MuvPThKtUyWik1s6Rv0vJSUcuNT65avq6rvyYAAJTNl/E3iICOYoqADa7GX6trXY+/sevlF483NzWsmlBb//9E9Jkiarwo6X/al2kt8p8i+k8Lb+++9+f/9i/d/pqGSvl+AUTKt/Fvb8FH5suCG+a7vg1vvLZrt3zt774lh98/Yu3MMwYNlC/dd7eMG3NexdfasPkl+XrGx78rH7j77n41+9+fltPFMVqpfjld3H9C99+y/scPvlPK9xMAAPrM5/E3iICOQo0Axj89vAQAoE9CGH8RXg44XYgvBzD+6SIAAJQslPE3iICOQooAV+OfaynOWf3jR6IffxECAECJQht/gwjoKIQIcDn+a37yz3t6/+o4EAAAehXq+BtEQEc+R8CGhkbG3xICAECPQh9/gwjoyMcI2NDQKF//zg8Yf0sIAADdimX8DSKgI58igPG3jwAA0KXYxt8gAjryIQIYfzcIAACdxDr+BhHQkcsIyOVy8r1HHrM7/lpezRWyPf4iBACA07gY/3w+L+PGnCfvHDxk60gi4DSuImBL06uitbZynogZ/8K1WR9/EQIAQDuuxv/eT9wht990gzTvfF3e/PVbto4mAk7jIgJsYvw7IgAAiIjb8b90Zp3kczm57OJ6IsCxWCOA8e+MAADgfPxb/4wI8EJsEcD4d40AADLOl/Fv/TsiwAuxRIDW8qrk83PWrlzG+J+GAAAyzLfxb/0aIsALoUeAGf91j/5gt+t78REBAGSUr+Pf+rVEgBdCjQDGv3cEAJBBvo9/6/cQAV4ILQIY/9IQAEDGhDL+rd9LBHghmAjQepvkqxj/EhAAQIaENv6t13AYAVprmTqZCBAJIAK03qbzVdcy/qUhAICMCHX8W69FBHhh6JAaqZs6WZ57sUFOnDjh+nbaMP59RgAAGRD6+LdekwjwwtCaGqmbeqE/EcD4l4UAACIXy/i3XpsI8II3EcD4l40AACIW2/i3nkEEeMF5BDD+FSEAgEjFOv6tZxEBXnAZAVrpT6x7bNnzVg+NCAEARCj28W89kwjwgqsIUKKuH19bv7q5qWGvtUMjQgAAkcnK+LeeTQR4wVEEDFRKbicCykMAABHJ2vi33gMR4AUiICwEABCJrI5/670QAV4gAsJBAAARyPr4G0SAH4iAMBAAQOAY/46IAD8QAf4jAICAMf5dcxkBRV2UaZMnWTvTZ0SA3wgAIFCMf89cRcDL25qJgHaG1tRI/dTJ8osXNxMBniEAgAAx/qUhAvwwpGYwEeAhAgAIDOPfN0SAH4gA/xAAQEAY//IQAX4gAvxCAACBYPwrQwT4gQjwBwEABIDxTwYR4AciwA8EAOA5xj9ZRIAfiAD3CADAY4x/OogAPxABbhEAgKcY/3QRAX4gAtwhAAAPMf52EAF+IALcIAAAzzD+drmMgDPPGCQTx421dqbPiAD7CADAI4y/Gy4i4LyRI2TRzb8p1dX9rZwXAhMBz23cLMeJgNQRAIAnXIy/iMgH6qfLLTd8yOaRXrIZAeeNHCFf/vxnpGbwmameE6IhNYOlrpYIsIEAADzgavxFRPa88aa0FAoyfQqvR9uIAMa/d0SAHQQA4JjL8Tde+VUzEXBKmhHA+JeOCEgfAQA45MP4G0RAmzQi4LyRI+SPP3e3DKkZnMj1soAISBcBADji0/gbRECbJCOA8S8fEZAeAgBwwMfxN4iANklEAONfOSIgHQQAYJnP428QAW0qiYDRI4fLlz/3GcY/AURA8ggAwKIQxt8gAtqUEwGMf/KIgGQRAIAlIY2/QQS06UsEMP7pIQKSQwAAFoQ4/gYR0KaUCGD800cEJIMAAFIW8vgbRECbniJg9Mjh8sef+4wMZfxTRwRUjgAAUhTD+BtEQJuuIoDxt29IzWB5973D8mrzDpvHRhMBBACQkpjG3yAC2rSPgKqqPOPvwOqnnpXH/uXfXBwdRQQo1zcAxCjG8W/vxg9fJwt/63rXt+GFEy0tcuTIUd7e17LVTz0rD61Y5fo2Dmgt89atXLbB9Y2UgycAQMJiH38RkZd/1SyFYkGmTeZJQD6X4yN9LfNk/EUCfxJAAAAJcjH++Xxefvd3FkmhUJR9b+63dSwRACc8Gn8j2AggAICEuBr/ez9xh1w+6yK57OI62fn6XiIA0fJw/I0gI4AAABLgcvwvnVknIiK5XI4IQLQ8Hn8juAggAIAK+TD+BhGAGAUw/kZQEUAAABXwafwNIgAxCWj8jWAigAAAyuTj+BtEAGIQ4PgbQUQAAQCUwefxN4gAhCzg8Te8jwACAOijEMbfIAIQogjG3/A6AggAoA9CGn+DCEBIIhp/w9sIIACAEoU4/obTCCgUZBqfHYASRDj+hpcRQAAAJQh5/A0iAD6LePwN7yKAAAB6EcP4GyYCdry+R/a9+etEr90TIgA9cTL+Wj+rc2qxiL5RiRpk6VSvIoAAAHoQ0/gbuVMfY0sEwAeuxv9IYcD1T636wauTJtf9rJiT27IYAQQA0I0Yx98gAuADl+P/7E/+6V0Rke0vN+zLagQQAEAXYh5/gwiASz6Mv5HVCCAAgNNkYfwNIgAu+DT+RhYjgAAA2snS+BtEAGzycfyN1ghQ6nalZKClu3MWAQQAcEoWx98gAmCDz+NvbH+5Yd+kKdmIAAIAkGyPv0EEIE0hjL+RlQggAJB5jH8bIgBpCGn8DZcRMGHa9J81b92yL+3DCABkGuPfGRGAJIU4/oarCNCiFk6YMnNVc9Pmt9I8iABAZjH+3XMaAcUiHyAUiZDH33ARAUrUIK30tZfMqP1uY2NjIa1zCABkEuPfOyIAlYhh/A03ESAjjhRzB5qbGv4zrTMIAGQO4186IgDliGn8je0vN+ybMH36aq1zt1l8OWDGJXW1f9PY2KjTuDgBgExh/PvORMBru3bLG/uJAPQsxvE3mrdusRsBStW8L/k1zVsbdqZxeQIAmcH4ly+Xy8nlRAB6EfP4G9YjQHRT89aGZ9K4MgGATGD8K0cEoCdZGH/DagRota25qeGnaVyaAED0GP/kEAHoSpbG37AVAUrpLc1bG36cxrUJAESN8U8eEYD2sjj+ho0I0FrWNDc1rE7j2gQAosX4p4cIgEi2x99o3rpl3/ip9WuUkttFko8AlVPfbt7asCnp64oQAIgU458+IiDbGP82zU0Ne8fX1q9OIQK0zuU/19y4KZV/vwQAosP420MEZBPj31lKEfDEusce/mZC1+okl9aFARcYf/vy+bz8/l13ykXTrf1HLiIiL2zaIkePHbN6Jhj/nqxbuWyD1jJPRA5Uei2tdVGp3J8mcFvd4gkAosH4u2P7ScB5I0fIlz//GTlj0KDUz0Ibxr93ST0JUEr+bO3jyx5O8NY64QkAouBi/EVEbr5+XubH37D1JMCMf83gM1M9Bx0x/qVbt3LZBlGFuVrL2+V8v9byw6vrLvyTpO/rdDwBQPBcjb+IyGu7dsu0KZNk2NAhto/2UtpPAhh/Nxj/vmveumXfhNqZq7TS1yqREaV8j9a6KKL+/Jr6SZ994IEHimnfIwGAoLkcfxGRlpYWeW7DZiKgnVwuJ5fNrJPXXk82Ahh/Nxj/8jU3bX7rkhm13z1SzB0QJdNFVE03X6pFy7/mcvnFax9/+KH169en8uE/p1M2DgHS4Hr82xs4YIDcf99dMvGCMa5vxRstLS3yN9/5gWx6qaniazH+bjD+yVm6dGnu2YZXryiKulKLHqe0qtai31Y59ZJW+TXrHv3Bbtv3RAAgSD6Nv0EEdJZEBDD+bjD+8eMlAATHx/EX4eWArlT6cgDj7wbjnw0EAILi6/gbREBn5UYA4+8G458dBACC4fv4G0RAZ32NAMbfDcY/WwgABCGU8TeIgM5KjQDG3w3GP3sIAHgvtPE3iIDOeosAxt8Nxj+bCAB4LdTxN4iAzrqLAMbfDcY/uwgAeCv08TeIgM5OjwDG3w3GP9sIAHgplvE3iIDOTAQcOXZMPn3HbYy/ZYw/eCMgeCe28W+PNwuCDxh/iPAEAJ6JefxFeBIA9xh/GAQAvOFi/PM5LZ+q3yi/emeYHCtUWTmTCIArjD/aIwDgBVfj/9Urn5bfnLRNLhu1R57adQERgGgx/jgdAQDnXI7/B8fsFBGRswYcJQIQLcYfXSEA4JQP428QAYgR44/uEABwxqfxN4gAxITxR08IADjh4/gbRABiwPijNwQArPN5/A0iACFj/FEKAgBWhTD+BhGAEDH+KBUBAGtCGn+DCEBIGH/0BQEAK0Icf4MIQAgYf/QVAYDUhTz+BhEAnzH+KAcBgFTFMP4GEQAfMf4oFwGA1MQ0/gYRAJ8w/qgEAYBUxDj+BhEAHzD+qBQBgMTFPP4GEQCXGH8kgQBAorIw/gYRABcYfySFAEBisjT+BhEAmxh/JIkAQCKyOP4GEQAbGH8kjQBAxbI8/gYRgDQx/kgDAYCKMP5tiACkgfFHWggAlI3x74wIQJIYf6SJAEBZGP/uEQFIAuOPtBEA6DPGv3dEACrB+MMGAgB9wviXjghAORh/2EIAoGSMf98RAegLxh82EQAoCeNfPiIApWD8YRsBgF4x/pUjAtATxh8uEADoEeOfHCIAXWH84QoBgG4x/skjAtAe4w+XCAB0ifFPDxEAETfjr7U8c7Qw4COMP0QIAHSB8U8fEZBt7sa/mvFHKwIAHTD+9hAB2cT4wxcEAFox/vY5jYDJE4kAyxh/+IQAgIi4GX8RkZnD35BPX7RRlLJ5ql+cRcCLRIBNjD98QwDA2fiLiOw7fKbsf/8MueK814kAIiBajD98RABknMvxN7YdGEYECBEQK8YfviIAMsyH8TeIgJOIgLgw/vAZAZBRPo2/QQScRATEgfGH7wiADPJx/A0i4CQiIGyMP0JAAGSMz+NvEAEnEQFhYvwRCgIgQ0IYf4MIOIkICAvjj5AQABkR0vgbRMBJREAYGH+EhgDIgBDH3yACTiIC/Mb4I0QEQORCHn+DCDiJCPAT449QEQARi2H8DSLgJCLAL4w/QkYARCqm8TeIgJOIAD8w/ggdARChGMff2HZgmPyaCCACHGP8EQMCIDIxj79BBJxEBLjB+CMWBEBEXIx/Pqflv175jAzqd0K2HRhm61gi4BQiwC7GHzEhACLhavy/euXTMnvsTrni/N2y//1BRIADRIAdjD9iQwBEwOX4f3DMThERUSJEgENEQLoYf8SIAAicD+NvEAFuEQHpYPwRKwIgYD6Nv0EEuEUEJIvxR8wIgED5OP6GiYA3D58hv3rnLDs3J0SAQQQkg/FH7AiAAPk8/oYSkSuJAGeIgMow/sgCAiAwIYy/QQS45TICpl44QYadNdTKmUlj/JEVBEBAQhp/gwhwy10ENAQZAYw/soQACISr8f/KFSd/z78SLiPgrSOD5DdG7yYCiIBeMf7IGgIgACGPv+EqAl4lAkSECOgN448sIgA8F8P4GyYC3iACnCACusb4I6sIAI/FNP4GEeAWEdAR448sIwA8FeP4G0SAW0TASYw/so4A8FDM428QAW5lPQIYf4AA8E4Wxt8gAtzKagQw/sBJBIBHsjT+BhHgVtYigPEH2hAAnnA3/s84G3/DRMC+w2fIdiLAuqxEAOMPdEQAeMDt+O+wdWSPlIhcRQQ4E3sEMP5AZwSAY4x/GyLArVgjgPEHukYAOMT4d0YEuBVbBDD+QPcIAEcY/+45jYCjREAsEcD4Az0jABxg/HvnLALeJgJEwo8Axh/oHQFgGeNfOiLArVAjgPEHSkMAWMT4911rBLx3JhHgQGgRwPgDpSMALGH8y6dE5KoxrxMBjoQSAYw/0DcEgAUuxj+ntHz1yvDH3yAC3PI9Ahh/oO8IgJQx/skhAtzyNQIYf6A8BECKGP/kEQFu+RYBjD9QPgIgJYx/eogAt3yJAMYfqAwBkALGP30mAvYSAU64joAXGhoZf6BCBEDCGH97iAC3XEXAfzy/UV5saLRynsH4I0YEQIIYf/tcRcDxlrzMHfea9M8XrJ3pIxcRUCgWrZxjMP6IFQGQEBfjLyLyx1c8K3MuyOb4G7Yj4IKag/JXc9dITfWx1M8KgYsIsIXxR8wIgAS4Gn8RkaqclqvH7Mr0o2gRexFgxn/ogKOpnRGiGCOA8UfsCIAKuRx/EZHXDg6VXYdqiABJPwIY/57FFAGMP7KAAKiA6/E3iIA2JgL2vDdYmt9J7rPlL6g5KH85d42cxfj3KIYIYPyRFQRAmXwZf4MIaKNE5OoEI4Dx75uQI4DxR5YQAGXwbfyN1w4OlZ2HauQaIiCxCGD8yxNiBDD+yBoCoI98HX9jBxHQqtIIGHvqNX/GvzwhRQDjjywiAPrA9/E3iIA25UYA45+MECKA8UdWEQAlCmX8DRMBV5+/S3JprkTpAAARjElEQVREgFx1fukRwPgny+cIYPyRZQRACUIbf2PHwaGy610iQEREqdIigPFPh48RwPgj6wiAXoQ6/gYR0Ka3CGD80+VTBDD+AAHQo9DH3yAC2nQXAWNrDspfzl0rwxj/VPkQAYw/cBIB0I1Yxt84GQFDiADpHAFt43/E9a1lgomAtTvGy4mi3f8JYvyBNgRAF2Ibf2PHwSFEwCkmAlqKOfm9S3/J+Fu27rVx8h+7z7d6JuMPdEQAnCbW8TeIgDZKicwauU8GVrW4vpVMeaypVv7hxQ9YPZPxBzojANpxMf75nJZLRu6VPe8NtnUkEQBnGH/AHwTAKa7G/6tXPi2frN8k7xwdKK+8fbato2XHwSGy8xARAHtcjX+h6vj1T69c9p7Vg4EAEADidvw/OGanKCVy+ejd9iPgEBEAO1yO//oVKxh/oAuZDwDX428QAYgV4w/4KdMB4Mv4GyYCDh4bIC8TAYgA4w/4K7MB4Nv4G0qJXDZqDxGA4DH+gN8yGQC+jr/hMgJ2HBoi1xABqBDjD/gvcwHg+/gbriJgJxGACjH+QBgyFQCuxv8rVzwts8eWPv7GyZ8J2CPvEAEIBOMPhCMzARDa+LdHBCAEjD8QlkwEQMjjb7iKgNcODZVrzt9JBKBHjD8QnugDIIbxN4gA+IjxB8IUdQDENP7G5aP3yMHjA+Tlt4gAuMf4A+GKNgBiHH/DZQRcPWaX5JS2di78xfgDYYsyAK5fuPBcXVQ/V0pNtXWmrfE3nEXAQSIAjD8Qg+gCYPZtt52pC9XrlFJ1ts48Of7PWBt/gwiAC4w/EIeoAmDp0qW5XfveeUwp9UFbZ7aN/w5bR3Zw+eg9cuhYtTS9fY61M4mA7GL8gXhEFQBVw85fqpS6y9Z5rsffuMxVBLwzVK4eSwRkBeMPxCWaAJh388eu1kq+p5Sy8nPqvoy/QQQgTYw/EJ8oAuA3Fi4c2E/6/UwpZeXFcN/G37hs9B45dLxamt4iApAcxh+IUxQBMHnKxUuVkpttnOXr+BuuIqD5naFyDREQHcYfiFfwAfChmz46Riv1iChVlfZZvo+/4SICdhEB0WH8gbgFHwAXTJ/5l0qpy9M+J6e0fPVK/8ffIAJQCcYfiF/QATDvxkWjRanvi1Kp/vsIbfyNy0bvkXdPEAHoG8YfyIagA2DctIvuV0rNSfOMUMffuGwUEYDSMf5AdgQbALfddlv+iM49pEQNTuuM0MffcBUB2985iwgICOMPZEuwATByyqx5Ssm9aV0/lvE33ERADREQCMYfyJ5gA2DCtBn3i6hL0rh2bONvXDZqj7x3vL9sJQLQDuMPZFOwATC+tu4bSqkhSV83p7R85cpn5NrIxt+4dPReIgCtHn15qnyT8QcyKcgAmL9wyXgR+dM0rv2Hl/9C5o9rTuPS3nAWAQeIAJ88+vJU+eaGWVbPZPwBfwQZAOOmTL9eKbUw6evOH9csn6jfnPRlveQkAt4lAnzB+APIub6B8qiLkr7iwKoW+d1ZLyR9Wa/d+4EX5NYpTVbPfHb3+fLf//0aadGB/lcvAow/AJFAA0ApmZL0NT8yYZsMqT6W9GW9d88sNxHwrQ0XWz0TJzH+AIwgA0BELkj6gvPGv5b0JYNhOwLG1BySRdMbrZ2Hkxh/AO2FGQBaRiR5uTP6nZALz3oryUsGx1YEjKk5JH81d40MG3Ak9bPQhvEHcLowA0CS/fW/sUMOilJJXjFM98x6QW6tTS8CGH83GH8AXQkyALRI/ySvNzSDr/13556L04kAxt8Nxh9Ad4IMgKQV+Y20DpKOgDGDGX8XGH8APQk0APTRJK924OjAJC8XhaQiYMzgQ/JX8xh/2xh/AL0JMgCUqANJXm/HwSH8XnoX7rn4BVk4ZWvZ38/4u8H4AyhFmKun5I0kL3eskJeGN4cneclo/O6sDWVFAI/93WD8AZQqyADQWhJ/s/6fbZ+Q9CWj0dcIaB3/gYy/TYw/gL4IMgBE6fKfS3fjyR0XyO73Bid92WiUGgGMvxuMP4C+CvPDgGrrhyglH0vymkVRsuPgEJk/rpn3BOjGJaP2ypGWftL463O7/HvG3w3GH0A5ggyAC6dedEAr+WLS1913+ExpKeZl1sh9SV86Gt1FwPmDD8n/YfytY/wBlCvIANjetPnwhNr620RJ4j+5t2X/cOlfVZQZ5+5P+tLROD0CGH83GH8AlQgyAEREJkytv0CUXJXGtTfsG0kE9MJEwKFj1Yy/A4w/gEoFGwDja2ccUkrdldb1iYDeXTJqr8wf35zJj1F2ifEHkIRgA6C5acvuCbV1HxOlzknrDCKgd/3zRde3kCmMP4CkBBsAIiITptb1E6U+nOYZRAB8wfgDSFLQATCxrnarLubuVUpVp3kOEQDXGH8ASQs6ALY3Nh4bP7W+Rim5Ju2ziAC4wvgDSEPQASAict7Eug35vLpLKUn9I/2IANjG+ANIS/ABsPPVhqPjp9YdVkrdYOM8IgC2MP4A0hTmZwGc5pr6C/9BRJ6zdd53Ns6URxqn2zoOGcT4A0hb8E8ARETWr1+vx02v/3dVlE+Jkn42znzxjZHSL1+UOp4EIGGMPwAboggAEZHXGhveGj+tbp8SdaOtM4kAJI3xB2BLNAEgItK8teHFCVPrRomoS2ydSQQgKYw/AJuiCgARkbHDhz2hBg2+TImaZOtMIgCVYvwB2BZdAOzYsaM4feIVK1tyxz8oSl1g61wiAOVi/AG4EF0AiIi88soLJ8bW1a5QOv9BJTLW1rlEAPpqRdNU+ccXGX8A9kUZACIiOxobj4+dQQTAX4w/AJeiDQARIgD+YvwBuBZ1AIi0RUCumLuGnwmADxh/AD6IPgBETkVAnZsIqMppqRv+pq0j4TnGH4AvMhEAIkQA3GP8AfgkMwEgQgTAHcYfgG8yFQAiRADsY/wB+ChzASByMgImXXLRj/RxTQQgVYw/AF9lMgBERLZv3nyCCECanIy/yNOFquMfYfwB9CazASBCBCA9zsY/z/gDKE2mA0CkfQTI1aKECEDFGH8AIch8AIi0RsAKIgCVYvwBhIIAOIUIQKUYfwAhIQDacRkB+byW+nOJgFAx/gBCQwCcxkRA8YRcpUTG2Tp3IxEQLMYfQIgIgC5s37z5xKQPEAHoHeMPIFQEQDeIAPSG8QcQMgKgB0QAusP4AwgdAdALIgCnY/wBxIAAKMH2zZtPTJ94xY9O5E9cTQRkG+MPIBYEQIleeeUFIiDjGH8AMSEA+oAIyC7GH0BsCIA+ao2A3PGrlFLjbJ278Y2Rks9pqecdA61j/AHEiAAowyuvvHBi+qQrVhAB8WP8AcSKACgTERA/xh9AzAiAChAB8WL8AcSOAKiQiYCW3IkrRcl4W+cSAelh/AFkAQGQAJcRkFNCBCSI8QeQFQRAQpxFwJsjiICEMP4AsoQASBAREC7GH0DWEAAJGzXqTK0GDv5NpdQ0m+cSAeX70dap8q2NjD+AbCEAEjR79uyqqrPPe0QptdDF+URA3zH+ALKKAEiIGX8RN+NvbHxzhKicyEVEQK8YfwBZRgAkwJfxNza9QQT0hvEHkHUEQIV8G3+DCOge4w8ABEBFfB1/gwjojPEHgJMIgDL5Pv4GEdCG8QeANgRAGUIZf2PTGyNEVLYjgPEHgI4IgD4KbfyNTW9mNwIYfwDojADog1DH38hiBDD+ANA1AqBEoY+/kaUIYPwBoHsEQAliGX8jCxHA+ANAzwiAXsQ2/samN0eIiJKLRrzh+lYSx/gDQO8IgB7EOv5GjBHA+ANAaQiAbrgaf631TqXUEFvnxRQBjD8AlI4A6IK7/+ev/7pQ0+9mdax4qVIyydapMUQA4w8AfUMAnMbl+K99fPkf7Ni0qWXs5bMeJQJKx/gDQN8RAO24Hn/zr4iA0jH+AFAeAuAUX8bfIAJ6x/gDQPkIAPFv/A0ioHuMPwBUJvMB4Ov4G0RAZ4w/AFQu0wHg+/gbLiNAayUzPYoAxh8AkpHZAAhl/A1XEbB5vz8RwPgDQHIyGQChjb+R5Qhg/AEgWZkLgFDH38hiBDD+AJC8TAVA6ONvZCkCGH8ASEdmAiCW8TdcRkBR7EQA4w8A6clEAMQ2/oazCHgz/Qhg/AEgXdEHQKzjb8QYAYw/AKQv6gCIffyNmCKA8QcAO6INgKyMvxFDBDD+AGBPlAGQtfE3XEbAiWJeZo3cV/Y1HmmcLt/ZdHGCd9U7xh9AlkUXAFkdf8NVBGzZP1x2HhoiHxi1V/rniyV/39FClfz1Ly+TFU3TUry7zhh/AFmnXN9AkrI+/u3NvvPOAflDLauUkg/bPPesAUfl4zMa5MPjt0t1VUu3X9dSzMma18bLgw31sv/9QRbvkPEHAJGIAoDx78xVBIiIDKo6IZeO3ivTzt4vowa/J4OqTsjRlirZd/hMafz1OfLLvaPl3eP9bd8W4w8Ap0QRAM7GX+u/Wbty+e9bPbOPXEaAbxh/AGgTfAAw/r0jAhh/ADhd0AHA+JcuyxHA+ANAZ8EGAOPfd1mMAMYfALoWZAAw/uXLUgQw/gDQveACgPGvXBYigPEHgJ4FFQCMf3JijgDGHwB6F0wAMP7JizECGH8AKE0QAcD4pyemCGD8AaB03gcA45++GCKA8QeAvvE6ABh/e0KOAMYfAPrO208DZPzt2rFpU8uUceetKPQbNFOJTHZ9PyXT8mSh6vhvMf4A0DdeBgDj78a2bdsKY4cPW6EGDj5fKXWx6/vpjdb6R/2PvL1w7U9+csT1vQBAaLx7CYDx98PcBXf8gSj1FyJS5fpeTqe1LopSD6x7fNn/EBHt+n4AIEReBQDj75c5ty65UhWLDyulxru+F0OL7FGi71z7+PI1ru8FAELmzUsAjL9/Xtu6edfYutrvKp0/U7RcopTkHN6O1lp/7/hxuXn9T5Y3OrwPAIiCF08AGH//XXfronrRua8pUddbP1zLU1oXv7hu1SPPWT8bACLlPAAY/7DMvfljV4hS92uRG5VS6T4R0PpnRcl97cmVD69L9RwAyCCnAcD4h2vOLXdcoETdKVo+ppTUJnVdrWW7iPxQ6dz316566JWkrgsA6MhZADD+8Zh788cna1WYLyIfFJFLlVLjpMT/bmmRXUr086Ll33OSW7165cNbUrxVAMApTgLA1fhrkb9d9/iy37N5ZhbNX7LkjOK7xQlayfk6J+fktBosoqtEVIvk5LAU9Fs5La/nz1Tbn1i27JDr+wWALLIeAIw/AADuWQ0Axh8AAD9YCwDGHwAAf1gJAMYfAAC/pB4AjD8AAP5JNQAYfwAA/JRaADD+AAD4K5UAYPwBAPBb4gHA+AMA4L9EA4DxBwAgDIkFAOMPAEA4EgkAxh8AgLBUHACMPwAA4akoABh/AADCVHYAMP4AAISrrABg/AEACFufA4DxBwAgfH0KAMYfAIA4lBwAjD8AAPEoKQDcjb/++rrHl3/e5pkAAGRBrwHA+AMAEJ8eA4DxBwAgTt0GAOMPAEC8ugwAxh8AgLjlT/+DpUuX5l5/t+UREXWbzRth/AEAsKdTAOSHnf+/lJLP2LwJxh8AALs6vARw3YLFs0T0L5VSOVs3wPgDAGBfVft/oUT/iTD+AABEr/UJwNybP362VsU3lOr8skAaGH8AANxpewKQ09eqLn4mIA2MPwAAbrU+7teia20cyPgDAOBeawAokaFpH8b4AwDgh/ZPAI6keRDjDwCAP9oCQOea0zqE8QcAwC+tAZDT6pk0DmD8AQDwT2sArF310Ctay4tJXpzxBwDATx3e9Ecp/bWkLsz4AwDgrw6/99+8teGlCVPrrhJREyu5KOMPAIDfOr3tb7/c8cUi8qtyL8j4AwDgv04B8MSjj+5v0XKtaNncx2tpreV/Mv4AAPivy7f+3dHUcGjs5bO+r44VqpTIJaJUVVdf10rrRlGyeN3K5d9N5S4BAECiVG9fMH/BkuFFpZeI1h/WSupEq7OVyHERvVOU/EK0euzq+kn/+sADDxRt3DAAAKjc/wdqDKw/Z4148wAAAABJRU5ErkJggg=='\nconst updateversionicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEQAAABECAYAAAFPFKMkAAAAAXNSR0IArs4c6QAABpJJREFUeAHtXGtsFUUUPtt7b28fgLRgTXmkJhCqqUb4QRvaaggtqYhBQ7S2FmsfxgcYDIIUtU1o2kpA/aWSJqYlAUoLxlThRwNVW0NslYgRVDT4aFAKFkpBWpr29rHOWTObufu+u+vuDdlNLnPmzJlzvvl2ZnZmZwuAgYtDm5UHdy8YHw/9hXJvZY2gQ5lefhTQ4LOK1wXdwFMb+ZLDewWZ47jWnorqp4XMiqY6/hY/qfhDgxjBivknv3kXk/tPlBnJLIhCZkSxscYyI7bQXlkgDZtP3UqJFAyQG2rANh2NZQbUENNEzs/JWtN6tpe1CefkufYPoenb7jCDyEOEVXc+o9afspvrh/TQyOiSVugs35Gk1VnRXteJ1KlSXtcJdoXaL9qV6oo6XSdoOSsuXqygJAgjWamA6pRGES2jqegEe20+gNijqYFailOFWpl7emFQYHh29FI4BPL3ZHZbRvO6qVKH+rL/vCGORCTsNMFGZKcMVi+da4QyJSRaOrb5hjobi0BJdsaJGicsov8fCUVBUzY6K2siOV5eJdjqDUJNJz7ynNBzgFE0nbCQtWRbnIjdnszqh3ieL9aKyJYpdnvWwJOtMiDeG+oou6n+UmvRptS5CTOpSjXFwTA79Y64jkc2j6NRdlPdMJkIZyhVIIFGeipr9J3SympLA7UpbkVzHZ/dUp9G69uWRgpEDaCa/pUTh3gcGVLAtow9qVOt/FurC0FpeDoORA2kuDBRM9DT09l7S+4aWLtkqZ65arltjFwbHVENYqTAFCMnL/wCtZ+Hr0b3f3cS8LdkbirsXVdmJHaYjSlGHky7R/FR1lb0sikQiMgUI7Qp+Fz96cpFqO/+FFoLN1G1qdQUI2ykjJQFlkGgP8tAWFBWZBkQ3C+80XnEik/Nuug7OvckmrC9Qo+BKGRAtlQkC6NrBGeyEawFFdW+nRw3bcRWz0Zpik82siXq/P1H2LOvvo8EEJeJ7K5YGjji/UckS0W0zW3ZlSQNaiYvm1kjcdL4eAVMj01djqSOmq3SrVGzlekXJ98Ffl9MUOuWsJU44C7HBgOZ3RuqLrJ6lC0BQQcdz27HxNA1ODqcWtT2Pr5flw0SS7fGUHTGSGvT5igQBpNM9IBIKYkaRiyNmq6+n6Gh6xOhcUZmYykLbN4SI78O2jKXCXgsAQn6A2yjLMmWgAyM/GMpOFvZVB+hG2/WEdWZ7SumGDlWuo3FIMovZuWJcqSCKSDxpG/U5q0PizUzGAdPZGSG6SLJmAKCAXLS0uHelPlirPaSLaJsRjANBIO992gpxAcCgG8BrF6mOisb9Ngzyv2FtTEiW2LESACjNh4QKVPRzQhZW0oB25LX8isbNcFg7MLitg9O8cCn2hKdcYKreOLf/KzH+PJEjwGPAY+B6GRA9m5ACSZ+qxcKTewhX92stGu65XxQ0FNWc0Ipnps6XULoBwOZCxfBqzlrQOv9hpGGhKamoOzjRrgychO4GK60p7z6gFI9cgjewHP8NnITYpXKFXUchDiee6ensvpNxXIDSt2lCD2exaNaq2QgnlifD1rIQdeiOSnAT/P7c/Y1vGYAp2Mmuj2Evis0u4nUaknV8TY43d9HjpDg3Z6KGns2AloBDZTp9hADPkyb7C4ogvzFGeQjAdhKhshB045srChbq9ro25CrHQ+tg+T4GXDkh29KSG8sMVQpcqMhskZ+QOlNt9SV64QgoOeXrxJ+UnB25HHnQt62J4fGJ04Rf/P0fLo6ZPTA2VFOHwRGlwu3PSGRkuoRImHMI0RCiKuTKvmkErr+OAd/j9yAyelpiCOvjtemL4XEQFAC07msq4Q0n+6G1jO9stYW3pcl0zmliLohE5qadKrtinFcJSQQ45OBGpsIyXROKhwbMr8NDZDV6NcwPjkJ2AsGbw1D3/WrsrYeJjbnrl6CBH8sfiYHsX4fbMxaDXPIataJyzFC8MOScbL1/+rCec12kX0NnL38p2ATIDvjxscqHCMDgzpGCAarXbUe+m9eh5eONsNoSHtoLJt3N7z9sOG/20D3tlyOzyHzZyXB0Q1b4cn71c8jdpJTWzfIQEYdJ4TexheW5wkHsknxiVQF6XemQkfZdsglp8duXY4OGWkjceP1UfFmuDE2SiZPvzCRSm2czrtKCG3s7LgEKrqeujZkXG+5CgCPEAkxHiGREoJn9lhH6yMCic+oylLctB164HQnVfwMmfwHGmfwvaSesyguH8J2RDE+D5rHgMfAbcLAv7ht3x5+L8FmAAAAAElFTkSuQmCC'\nconst apimockicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFQAAABgCAYAAACDgFV6AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAADZZJREFUeNrtnXtcVNUWx3/7zAsRHEXwiW8TQ0UEechDEbEyb3m79TF7iGJ9TO3e7GFlaG8gLXvYvaL5KUex7ObHrj2u2k1UFFHkIZiKYIRggCYgCAjMDHPW/QPBM3OG9wxnSL5/Metw9t7r95kz+5y911kL6AYsTftMsTTtM4XU42gLTOoBNEfg9nUTyFC/AsBcMLgCAAiFAPYxmTw2afHq81KP0Rw2J2jg1+uH8LX6GADhIDI/PsYIQJxcwV5LXLjmitRjNhqa1ANoJESjsdNT8QtEfCQBDsJjMo4DABh43nTw1YxxMX0GOX504P7ntFL7cGtM0hOoifkbz/MbABplZB/phkcn+WG88xAAQHZpMb45ewpJ+TmmblziOG5VUkTkf6T2RVJBg3dEexjq6RMCzRTaRzsNwAr/MHgOGmH2vMyrBYhNjkfe9WsmzrAjMjl7PnHRml+k8kkSQUN2bXDW1WnfAWgpEcka7Wq7XljsNR1z3aaAYy0PjSfCvpwMbD99DDfqam87xJgBYFuVdqo3Eh5fVdrVvnWpoCFHjsh1l5JWAPQ2Efo22mUcw4N3e2PRlOlwUKra1Wa1TosdGcfww4V0GHi67RhDBcDeVI4KjE2YObO+q3zsMkGnbYu+F8R/DOBuod3HdTSW+4VhuLp/p9q/fKMMm0/FI7Uwz/TQBTDuhZNL1vyvK/y0uqBBO2LG8QbDh0T4i9DuqnbCM76zMG3YWIv2d/L3XHyWcgiFN64bO8rwX04me+n4osiL1vTXaoKG7V6nrqnWv07EngOo6Smnt1KFJzwD8fAEH8gYZ5W+DcTj2/Op+CozCTd1wrsppmeMPrV3ULwbP3/1DWv0bXFB3yLiDmpilhD4aCIMaOqIAXPGTUaEdwj62dlbwxcR5XU10KQn4MDFM6DbP69gDNcYuDWzIyK3vcUY3/EexFhU0IDtUdNhoE8ImCK0Txo0DCv8Z+Mup4HW1K9Zfr3+B2KTD+Ls1d9Nnc+AjD1/YvHaY5bqyyKCBnwVNYK09D4I84X2gQ5qLPUJxYxR47tEuNY4eikbW1MP449qk6udYTdTsVdOPLG2oLN9dErQB378zL60pORVAC+DqFejXSWXY4HHNDw6yR9KmVwi+cyjM9Tjm7PJ+PcvJ6GtF9xNMVYL4ANnF5f1Pz7wTE1H2++woEGa6McNPL8euLUSdIvQMe5Y6hMKZ3tHqbVrkdKaKmxNPYzDv2WZHiqUcdyrxyPW7OpIu+0WNDAuairVYyMRBQjt45wH41n/MEwY4NreJiXl/LVCbEqOx8VS40UrxtgJJsfKpPC1ae1pr82ChmjeH6Tjde8RwyLhspqTfW8s8Q7BfXd5SK1Np/jp11+wLT0B12tuCtRhxAg7lJzytYSIV662pZ1WBZ2z/1PVjT+qngdPawjUdB0rZDI8PMEHj3sGwl6ulFoPi1BTr8OuzCR8ez4VeoNBIBKrAsei1QMdP2ltmbBFQadpYv7KiN9ARGOE9oAR47DMNxRDHPtJrYFVKK4qx5aUwzhRYPxQxRj7jRi36mRE5HfNnWtW0IC46InQ0ycEmiW0j+rnguX+YfAaPFJqn7uE01fysTk5HpfKS4zsDOwQFOz5E+FrzpmeYyTonC8/7VOpr4ohomXCZbU+dr2waEowHhjv1eqy2p8Nngg/Zp/G9tPHUKWta7IzxgyMsS19FI6RB558rrLJ3vjHdM26SXrSfwvCXY02GcfwwHhvLPIKhqPSTmrfJKVKV4cdpxPxY7bxMiEYflUwxcPHIlafbfgIIOTL9a46nf40Ebk0/p/30FFY4ReGEX2dpfbFpiioKEXsqXikF11qsjHGSpRKhVfCk68WygFAp9XHERrEdFCp8FLQ/QgeYRuPi7bGiL7OWH/vAiQWZOPD4/tRrdWCiFx0Wn0cgFAWvC3aq574dACQyzhsnLsQbrc2xXpomZzSYqzctxP1hoYFKznjvDkD+KYFjdljJ/WI2Q7cnIdg9thJTZ8N4OdzhNuTkK/raKnH2O0w0ozYWA6EpkVKp14OHWnzjkaoGYEGcYzdYTeWVoQxZqVNnTuYHkEtTI+gFqZHUAtj9Q2f7y+k40ad8RbNaKcBCBrhZtV+jxfkiILJ1Hb2mHe3t1X7tbqg32Wl4XeTKI7QMe5WF/RYfrZov2h43/5WF1SSSz6/vBTU+WaahW71IQVWF1THG0S2vOvXcODiGav1eeDiGdHlDgBag/WD8KwuqKqZfXlNegJq9DqL91ej10GTnmD2mJKTta+xDiDZLF9eW4OdmYkWb3dnZiLKazscp9BpJL1t2puVhqLKcou1V1RZjr1Z7dpGtziSClpv4LElJd5i7W1JiW9am5QKyW/sT17ORVpxXqfbSSvOw8nLuVK7I72gABCbHA8DdfxGykCE2GTLfdM7g00IermiDD9cSO/w+T9cSMflijKp3QBgI4ICQFxGIiq1te0+r1Jbi7gMy98tdBSbEbRKWwfN6fYHEmtMAhCkRhJBPQYNh51C/Lb2vpwMUdhLS1wqL8G+nAyR3U6hgMeg4VK4Jo2gzr0dsMBjmsjO84TYU22fXGJPxYPnxZPZAo9pcO4tzf6YZJf8/In+GOigFtkzivORVJDT6vlJBTnIKM4X2Qc6qDF/or9UbkknqFImwzO+oWaPbUk9DL2ZRZVG9LwBW1IPmz32jG8olDLrP7M3h6ST0vSR4+ExWPxbd6WyAnvOpTR73p5zKbhSWSGyewwejukjpQ0hknyWX+EXBnMb2bvOJKGstlpkL6utxq4zSSI7Yw1tSY3kgo51Goi5bp4ie61ejy/SEkT2L9ISUKvXi+xz3TwxVqIXy4RILigALPaagd5mXuv+OfcscgRvZ+SUXsHPuWdF/9dbqcJirxlSuwHARgTta2ePhVOCxAcI2JR8sOnjpuSDMLd3snBKEPp20fujrWETggLAQ+5TMUztJLJnXSvCobzzOJR3HlnXikTHh6md8JD7VKmH34TNCCpjHJY1M6lsTT2CralHzB5b5hdmtdfEO4LtjASAn+sY+A4bI7KX3axC2c0qkd132Bj4uY5pS9Ndhk0JCgDLfGc15WlqCRnHYZnvrDa02LXYnKDD1f0xz731YIR57t6dzlNiDaweOaKSK9DLZGVJ1cor3+GewUjMz0F1M+ujDqpeCPcMbrlfmVzUr53c+vkIrS7o5gcj2n2Og1KFr+c/26l+Xwqai5eC5lrbPRE2d8l3d3oEtTA9glqYHkEtDEfUiQ3xHowgIuIYWFPqh+tm1h97aBmhZgzsKgdGTfErKYWdD4m50zDSjFEuJwO3u/HzwdyzyCktlnqM3Yac0mIcFKzPysDtll3+/vCV4fNmzQAwiifCsfxsDFX363lPvhUSC7LxzpG9qNM3REUzsCNJT61dJwcApUoR3piAoFqrxduH9mLq0NFY7jerR1gTCipKsfnUIaQV3b7UbyUgCAcEKTKCd0R71Bv4PaYpMh682xvhU3pSZFTp6hCXkSjKpGs2RUYjc778tM8NXdU609zIjio7LPGe0abcyH82GnM9b0s/KkriArCtaqXjarNJXIT0pBlqoNNphkwJ+iLqIZ5hAxEZZSYIHOmGZT6hGOzYV2qfrcKVqgpsST0sypfPGMvjCKuOP7V2b3PntilVW+XVqhdNKyAoZDI8MtEXj08ORK8uWGfsCmrr9dh1Jgl7zqWYpGpre0WHNv8gBu+MHlyvp/dgUqOjv70DnpoagnsEuTe6Iz/nnsUXaQkoqxE8LXag5ki7Z5gAzXs+4Os3EmAUj+jmMhjP+s+Gu8tQqbVpF1klRdiUfBA5JSbpLoGT4OQrT0S8ltqe9jo0ZRMRC9TEPEGgdSAaKmxt1ugJWOozE/1tPCFrWU0VtqYewaG888bBE4wVMbDVSRGRX7GGb2i76HTK4LKSktcAWkWEphtVlVyOxyYHYP5Ef0lDC82hMxiw+1wyvj5zwihlMGOoA9iG/i4u70mSMlhIwFdRI6ClDUR4RGgf6KDGUt9QzJA4xLCRo/nZ2JoiTmrNGPZAxVZJntTalIDtUdPBYyMReQrtHoOGY4V/mGTRcbnX/0Bscjx+uXrZ2HnGMsFhpc2lXRfyFhH3sybmaYCihMkJGWsIOVzsNaPLArsq6mqw/fRR7MvJNCkMwEoAtvaeiMjPbbowgJCw3evUNVX1bxDwD9PSFQunBOEh96lWLV2xNysNOzOOi0tXAP+0d5S/021KV5hyq7jKx0S4X2h3VTthuV+YxWOTThX+hs2n4s0VV9nPyWQvdNviKqYEad69jyf2MREZzVA+rmOw3G+Whcr/HEJq4W/GDjKWzTF64XjE6z91hZ9dXqBKn5f0d2L0pmmBqnnuUxHuGdyhAlVxmYn4PitNVKCKEXtbMTrwX3/KAlVCQnZtcNbWaaMY6GnTEmoR3jNw/zjPNpVQ238xE5r0o6ISagT2ucpOtfZPX0LNlIZFbdoIohChfbTTADzrH4bJzRT5O3O1AJvMFPkDYwlyGVt5xxX5MyVge9TDZMAHnSlDyWR4+cTitd9K7YtNCArcKpSKohd5QiSIeguPNVcoFYzd5BhiFBj6UUJEhE28kmwzgjYS+PX6IVSjX0cMT7ZUypcRvmT2itVJj71qU/veNidoI9212HS3oDuVQ/8/nPNQSu73howAAAAASUVORK5CYII='\n// apimock component\nconst mock = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAABCCAYAAAGUUzB+AAAAAXNSR0IArs4c6QAAC5pJREFUeAHtm2uMVsUZx2GhuyuXAoHdGEOCbCJg0yjltlxKu5SEsCj9IEG/EL+01tbaLqAIpHKR0pTKPZXaSvxgS9JGgl8ILBJbQAJ0ubSVpCnyASRpqbpS2bIii3vp7384z8mcc+a8+77vXiSVSWZn5rn+55k5c2bmPdunTx6pryszceLEtr59+67u6OhYf+bMmRgvkEOgI6HQPHny5LqIZgKTJk16S0QsBVaMXhJJ3mLOVhvhiy69v9tQXdqnT58uAVvECoQgbET7NZgBRwIzZsy458aNG5FgVDEMVkaM7q/IhdeNj+ijRZCI7vesEYsLWi39+/f/amtr6xgTiEWXASxra2u7z5hR6fNntCi+EL6Exk3yZ+SVWNscWehSRa6qq6tH5jISwTAhBnkrc2kx7atAGebQF0PfysC3MgkEOZ3kkVkxOM3xUywY4sZCeuzYsWum4gqJpjZ5k/HdMmbEZahuhlRqkif5qbYppBgZBFc+FViHWUFgP3JtGK+qqqr/7t2724yXMmIMlSitolhHvo7BgaL1fgLFNvJ/cnn2dmPmzJkV169f/zBUbKSsYHK9wsg8mTSWMmABQ7CUvuvBCZLRBw8ePOzw4cNXjR4ZYHp/yjQux9PzePqZCbhlTU1N+bVr1z4VDeORbiBjHlyFrDqyZ8gfix/MNp7GL7vCoAkW7ywaKHfBGyq+d7rSldnyYgbWrl1bIpq13dJrIBSYYIJ79+6NZp/RrIytikZUqSCFcanTEIIgWkpduVwITG67b/yNGSAYMmRI25UrV4wWDZE7VG4dNKWRsFUEN4RsJG+JTLsrF5sMMH6A1q/ocxuwY/FhaF/G8/fhfwSaCrMeM2BEDGmqDsHQFpTWUQ+mrtsNk/UaMKZBLS0tHXDixIlgChvv/6vMGYasrhKehfBe9/AvEet7PfScpLxBMBveZWCj/QMDvZ8Z85BZnzJlyhi2EO9aWyUyDyGz36X56pkgcFqD00OuEhuZBxsaGs66tKw6+ofQr3H4Z4nSg047qqZA2EwMJRpQnBpJF1nBZjWqfzb1fv36zT558uSfrB0DobWWLVuTmL7nyJSKLYnOEqKzJWk/n0Uu8qkoJSIV8ayCoz9IhjJwZvRcZWwpyiXo8uSE+TGE+fHfBP0DelopGmW7y8tVLygSZohZf0TDBpg5RgsjVDl//vx+Rsu3LAoEj10NQJbi5E2c7wgBBPNozZo1sZNZPkAKAlFZWVmuLMMA2apNIe2lotlEBlyH2vCW5wNAMrGnQwTrFdXLOBopo6J3NXGSWtTe3v472cHmXmx/22ymQIjB6jea1e+CCaksKSlZcOrUqTdcWmd1npALTNDRJofzN3C+wNpWekEY00qM1WNsrrUpzxH++512UKW3c+ltfYJ+P7LnErRYMy8QrgaOxuPory7NrdPbt+ntN11aj9cBdXePO/nCOCh4TlhkdMxn1dRrXSf27Uy+xcYrtCwKBGvJezga5XH2KGB2e+g5SQWB4FH9DY+qe+Zo5GnYDm2946V9wIABdx89elRHzbxSXiBwPg9H+1yLbEzGsjE5bzRk9iEzz9qAO8+jOtbaucqcIDiqDm1ubm7EePTKx/gSjG/zGWWYdHOjQ/dQ4yO/Dfkl1vaVmSAw+A4KD5gSxg5jbJa1c5VM2geYtNKPEvqz0D8cEZxKCgSLTx0rotvTTzj5j+Dk77mVdCx5qgzRT9z5ApAbALkrKRoDwYvrW7y4/ugITWW2NzjtoqpEVZtcbXaDhM2Y39h+AgB7Qrm/SLA7AMgedqZqOxjaThUxEHCDCUXYdqUku0hw96PJG5QkiExXjO+T5LcY41gokwqSUU7Sc7XzBoGRsQCYzcT9fS6DklHOJZPk5Q0Cw8EVD+Vjui9KGlKbCRjdKfn4WTSvsSxho3Pn9G+rWxmO8wRrF1IWA+ISDipxep/riMUp73eFq6d6wSA43FRJEafRe4Nh0CGolKfqiHiFpoJB6HCDs1fkCOc/Dh2+qZLVsCZsF1QUDELWcWa3wNsBskM0gOlEVlQqCoQ84dRuaZ5SG2BbVRaT8gbBS2yljnc4D05kON2vdnjki171RisETKTsKrEWlLpt1XmLtlIoR6m+vr4laoQVHy0poztblxaLBL3cGzI3sDIucgW7UqdTupb+l9k4ePDgJ1ZXmXoPIOwegDvKy8tH8kvUZVepkDrvkdcA8bjp0NGNDOVz1laZAiEiintQfER1JRQvolh1q5XfXyL5CJsj2xoESuxLq9iXXkxa8IKQ0MKFC/tduHDhfaojTAkwvwaMbr4zE5Ecgdz7dMK9sXmKPcXLWUqZIEwBo+Oo/8PaKrkmqOWa4IBLUx1ZyUk+SIA5AOhaa2eVnYIwRRw8Q939FVK/QumnvSaGbzM9dxerppAX/VJldnxl3iBMGYdHcPgNaydLovQ1ovS3JD1Xu2AQMjZt2rS7bt68qR83BzjGVxOVnzrt3qvyFNy5n+i9cH8RPGlK3Q7TqqgHtNgBCn8Tfxb9FeSBGXb0ft3A7mVTMUfwDJudkns8EIz2eJZX3SdlLrG5UPLyeZtcV+gSnMumj9ftgeAlp+uiH5FXk7OuAj6jcy8RnBf08hMw9PRb9hpoT9OUDV+S7DryL9HL68XoM+KjdUsg6MQ4OqGvoeb6nIS0c7xcl/i2ID4dZpJ+LNAJItqaJOXweQCfSwjKuSSv0HZRgQg3fU/gTC/taOPnOgekNvQ7AboKoHrhF50ItDaL8vUE9twNo2tTPlbxg9dO9wspVyBXPe9A6LcoRmgjQFK/G5kDwGoLvYwNZWx7bfzuKtnWCYOwjM6yCZY9zMBlvm29T6fTQOD0RRwu8ylD0/XArrKyshVdORhl2M6LrK9OW1paNoBRJ0Vvf8CYOmgljXsVTYgpqSPpw9YOy8sYXsGzvovSPSkmxHq/STD6srYsotyA93tcBGCN/QTr8lTPDMScOXMG8qFVc0JhBc/7LxK027LJIC4HmAISpeHDhw9K3gMYM3YpYUSVTU1NqUWJqN50ZW7nug+rr0/WB+8VkTGLLRmNTejq0Bwk1pB7jx8/fsnahZTTp08fxRrwnqOziVmZtWY5YoVVM2dEYWZS0rG1Qx0hOF9PSXVCkE4iCNLIfJw7MZeT3VOB8Dk9Sse+62P4aKHsUR+vJ2i9GQjh30kHN3bWkVBmZ2dy3cnvlUCwsXnMAf0sHdVr2ZtCnk6oQUroGrnby14JBLu73Vzejgd9e9iDh+nw3zmWR4u16qLBt31Lu3Sk2+299hjslUDILx16R59yUA1Om5Rf4Xv9D9m6D1dWXTSyUpNkpXOr2fN/ey0Q6kr4PYv+dcK+Hh7G+eUDZdjDJBPyKgr59kV6XU3R1OyqoXz12QPoHmEcZ5j9bIVrydHGjSDUc2Cbl6+t7pTrkUCEG55o00MHU5jz7TC62pOkDaQsdo2Q+WiE33hcdc0zeouS33u4/NulLozCmsBz1f1uJcHrk3NGsGovSHxWNIHPAvRNtOw0kOsYfZWfewJTNSC2k6vBmMKjvqSIDiGvKcfRVh966dRZ5ui61Wam8M8rKio25/NztKtYbL22trassbHxGUZ+JTYGZdhpYR+ynCsDBShnyisQrgUiP4v2NnL0+Z3LD+uH+IZrMVPxrIdXNIkpr0/35FsYspJ8LmamHsoS8NELDoRrRB9E8v5fy2z4ISOT9Zhdhf/CoEGDXgo/cnBN5Kxrk8UHl09jew2CQ33C2G6Fv4PfQdZiP7am+eSzaF0KRNIos+UhgOnfy8YkeU57H8/rUjZL0SdIDk/f6eu/lLZAs29iXHZQx8d5fCxl1PelmEUSujUQLgbuESo5Qq+H9h1y1tsp+JhYenSsjqJCdU/S1vxV7jWe515DO9BuTz0WiCRSFtxHWXBfhD4qyctoX2Khe46F7vUMfreSey0QLmoWvZEsejphPk4OttaUH5N/yyK7iUX2n9TvpDsR+Bwj8D8dPmo31q46ygAAAABJRU5ErkJggg=='\nconst mockactive = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAABCCAYAAAGUUzB+AAAAAXNSR0IArs4c6QAACbJJREFUeAHtW3tsFVUa/2bulNKWWgFxEUWwu0GMEWHvLdDeqrhuWHcxxkUhPlaziVGiu4aiBYm5rWPb4APCY9U1uo8/1s1mF4KbLOsLX6i97dL2LorGB2YFE1eJqFCBQuXeGb9v2jP3zOvMzO29E6ucUM53vtf5ne+ex5wzZwACJInXibekc6DrMvEyHY2mzCTiqU6dNyBaBrmpt6Nhk2HFFH7/6/MtehpoG4eUOfbcH55qlLbdFee4AIqlhIW+9iQkWtIWtqEkyfJaXdNW2oVM0wRODIaNaL51VC5yoqr46si9gcXOZPUSHiOAjMHyXyZ+wEiMNJe6762HqRPHwrSJFSbXolB/XzeczQlJyxHZ9J6DQH8sGR6M4EwdO4YxEXtz8QJGIZin/uesvHcnZfndSJxo6dyg69DkVB3iSBJk+9oby3i56cQr6LyynWbttYSUV6I+yScqN/1sOs8yaU8npMEcuXVy0wMSRWmO6YR5DhKb2llJZctSKcdsHE6YgPJ4qqsFQGsDSRrItCereFl0dLw1vRGb9qWoRtdmXLTmv5MGBgY+4w0lSXoCf51lPI9ohwO/YFYrVeN3qHMOMUemA+zex7B7j2UCv9zRO8MYk/N4S6cx6I2eiaPxFL5G++RNMgdPB2Pydu3aNLH/9bYLTZ84OoFN9iZzmHB1QLKZU8aZur1t1sFmCpDwdECLAg2qa+efAU/17udtLLSnA6bVvKgW1vzrf6zoyI2fceHaN6u+6D98xCH1YdBPafYDvw5k9+XoBwZDlm63KzrKEnzOjElmIuAV46k0dlW9hucRzRsymasDJmTNGjNBruy+s+EY43/3cmEYvJobb+1aApq22SmXPsp0JKc7+WJOYBA47byPM8cMsTurFJ0v6utofMbKdZY8QSTU7gV6NveK06Rgzm7skfnZhnPjAMF6IqdTdDKmSJf1qMmXmWPL/GKfa5lSsfNcVn+J92kBwQvcaJo16U+U1iydYeisuHy6SM0iCwWCWRKQqvIYK5r59rvrYOEFk4wyduLAqSAQmb398GpqPswf3rJQbQRswrgxUNdq3ZkEQeJ4EA9itOzPb8P1DVPgEdxcbdn5KSyZd4Zh5rX78fNpGR3UMbPZbL+XUVlsSP1EbijWMhZj9B8mxiOa9HKaDvjPM/ELiOXn2KnO/8rTCgVUEV8ZVWLnkT3xRABwxd3G12OJBBPMbd95Tm7wxIesXKwcK38K9ypX2/25grArJVKdz2KjL7fzA5UV+byM2vCeSDcQCN5BnZqerWX1XTyPpyWQXuvrSF7C80pO16k9k0teyfemgtB9gkWGtvk4p+zG8niQpU2ZtqTnDp7ZeOUFgcAn2H34BDvN4VSWl2baGrY4+D6MUCBwWn5c1/VbhT4lSausqJj8+j0/PiDU44SBQOA88QucJ57m7HxJnJj24MR0rq8iKghBLFB3nXokd/QALssFLXQEAMFsRDArRGA8QeBj3ptoOEtkHEYmKbFL+9T6HW42DhB1qa7l7LzTzWAkPIzKcYxK/shv2JkFxFw1/RP7899IKvWy5Zdx0rEs5bmcvtXLsJR8CwgYPqUoZYXk2/5UbwUhqH0xHu86jlZc9EkniB5vGhjEtNMqjOMZeqQXJTrC8TrG8bILDII9rtEjPfZy18SfKbkqeDADg+Dtn19VxxcNmvYh/LmUQ0HACA3i00PHjf0FnfLz6cXVc/liKDo0iCvXZ4wK/tmUf0dDm6AyRQbaFBWSQoOg7R07waPTPEq0CaJEm6JCUkELE50CLq6bDHQiSKOG0vpn9xZSv2ETOhKspuVPvmOQbAv4t65PmCh0bhlsogOSGMKVcWzyOzC2LeS3fIzH69lRKYpSw+/2Av8cOQ0AX1Ja/LlV5MazGGGhpqrCfKlBMsvPgQ217BHtxsUqb1954VHelwUErvVX8sJS0PQS1e7X0ieYEI8Lt+JQXMzKxcpj5WW1PS3zHMPIFQRVumSzHvvwrfR+7AanjRgEnpbjvuQxLz+eIJhBXO2aCVntXVYOk6Pz5/Aw9ed+Nr4gmAPc8NyFG551rCzOpX6YWj4psyxxQqw3JA0MgjlLpNKv6qBfzMr2XFakOb1q8g07X1QODYKc1a/vqvj6oP453m2ozDuXWzMdDe35coTUyfOJCIP9/aiKutS3oVsVNEAL/YkWqHvHHs590owDezX6qPLwcxR3FA9Ux6as26Gec9xDp+jskgeCTl71LGwSTbGiVtHJrKTA8rBTsMinm6zogYg/3lcGHw/egct/q9v7aTcQwXm4KErQBmeVPxx0YQzquyiBoMcNKattwCfgwl4CBEVr00Pwz+mKvMLvBYLNzLVYUCCGH/puQY/tRXnwc4UWkonXKNCipfaC5B/4G1JBvQQOBL2L0r7OrsXzZMd7o6CVRamHV6a2ymOUlW6P9W44fAORaO16iG56uhmPFh5ttPraGlaJ8AoDgfdzt+FSd4XIwWiR0fZatLv1DEShF62+zYGZWFM9zn4OwPBazgMYk/L+o8ectyJ4hVFIi9oU+IgoTLvpyu6vGs80Ta5Y1wf7+wfNchhick05/Ls5YZo82fl/2PT8PrNcLMKzR4ykAuuJGhgNmX12dWiXZMMHgRx4juXQ3q0GJQmEtYqh0h9vmQVXxfM38d10eB7pkk1UKbJAUINSV/0IluOw8UukQ7pRpkgCsfof+Ss0N+LcseGG8zzbSDLSYYm3ZbxS5JEE4sW3v4DrHt2Fd1eHZo+LZk6AzXfMwXth+SYRTTySUSJdsiHbKBIHpbTVfbB/ABY+2ANHjmeNimpPr4QXVs+DmgrF+COaeJRIh3TJJqpUkuXTC/yhgSz89P4e+PtvZ8P0SZVwCgZh+/B7S3ZrcN+BAbj2kTfw7Ny+9nh5LQ4/sh7B4FIDr/ndLvNDKwoACwJ9fEWyqINA2DyXZb+7oKxhoym3v3DksXsGgpSMT2UiukfCgyoJLcGhTHvjeC/fwqERi0mj4uzBq3E8368twh7BHA1f9HoQy+WMN0ryQfxq9276atcPb6BA8E7irZ2XggYbkRfd8y8PwJ/ejS/6mzJtja/4q+Y1QgcibwpAFyIPawOqpOu/GcmlSN5nWBoPXLK6JD1aLVeq/EeIof2ENRDp43eaizAo6zEoM0R6I5Vh4/dg4+/EN/2hrsSK6h1RjxA5blDTpw9moQMX6JvZ5/gifaEMLxjjafmfyhVIdanJz4S6BQpLFgg7nrqW7qWarj3keqXbrmyUpY9kSV7V216/2VVcZGZkgeBxG5fstVwzfvByE/LZ2n4Qr2r9RZFj6/CW1ce8/kn6ZASij8A3wVf2LURzdowAAAAASUVORK5CYII='\nconst tplupload = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAABCCAYAAAGUUzB+AAAAAXNSR0IArs4c6QAABkNJREFUeAHtW01vG0UYtp0vESVCJSCoe6CHCCIQolI+3Fg5mNAjglMu0FtLuXNDQiVF4oTEHQI3+gMK1xKMiBLsJAUOCBVVAQ4NCLWASgHl0zzPMrOdHc/s7ti7jg1rKZqdmXfeed5n3vl6d5PLxfjlKTM5OdmwyW5ubnoyViHZuKBrkBVqeUBICiDdtgrJ/pEWrUKskIKqUL/MyK5kPvk0lEgTtnAIU1NTV2KBhmA9XJVLbcJWuHRtlI1FgdJSlQ/4miLjP4K6O37G8uB7kF4/MzPz3MHBwceNRoNT42Y+n394Y2PDKu+1V+GpCuOUR5qjKrQ9Rypx91RbV11f7vk6hi8PPzh0RLsMnp712nDooGDRRYEc7sjRiFIaqQBe+nSYklAFhAkX/2phYaHPpsSqgI0LhcIbbLi1tbXvpICN+/r6Th0eHn7OhvRKSZquyIiADer1+teYlf7OZnNtowK9l7B82wpCFw0sKlX0/u8mbIGRDAIM1wUw/h6Y9obN0llWbGegaZTE+rKGJiV7s5Zr7mLMXl1fX19SNQRAYDC/EJ03MBveVAWTeIaB49DzEnWpM0z3ac96AoA/L1I46R8M9UCoetueF6qyVp91Jpz0wKpv0WCCjQYHB8fW1tZ+dVIghFtiYnp6+iIAcAX1AFDX7u7ubVEmVMdPnJjA1vUYdp/rWJFy8Bv6zzCc7VN2J/aDmwSCutvwqQfjwnBiAp2/PTAw8AQ7RCdNJ22Un2AdgO1WKpXYBsYWpFXo+IU41gFI4IoU1caJiShlrdY7MaF3gjPCNfjIJZTv6XUu+bZA1Go1XiMWXTo0yerDcZdCYnk1yadSFtg72AOm2CdI5lPp7Z7SIpz3p3vZ7CljIMhAk2Nic3oZy/M7EBsJiiaSq2HVncXe4l8fqDUAokMzg8t/QQWiL1Zyal6G0I1EbFeUYP25SMNxxeUZ9rSs0kF45ZjDZ6VAkik650LIS0ngEK2vmEn2GVtXBkJS1RYTmM6nMM6RATnZmS1tGcTs7OwDWE++hKONYmrz1N3yryUQDLPwdK30OgFGrih5p8eWQJhCNWDkeV4FnHoXws4gYLGMFXkHIKHnNaYYnkuMu4qy2IkTCAC4BYsZ8fodC9qo7AU37e9w3nyceQZ+eT+RdXHS2CAAoA4AY1B6CwCO6coR11KBXNfrw/KxQYyMjJSxn/wMAA/ZFBIIZCbw95FNxlRu3DtMgtVqlb5w3FSnlmGHJAuxLkmyXWwmZIM00gyEZLUrmIjtmBK1mmIWvI78ANLP1HLX57ZAYCa85dqhSb4rhuP/BwLL/rgYDnXzyxl9gsEv09glVcbQsqpLHw6nWJOqyOF5WY9tO7TNRDMGMgYyBo6IgUCsSMeAtZun2g/xN6/X9Vh+GXjP4ppgjd7q+4ZvHyN4yPDbr14ngTbRhm1hE/NNP+MuTikRxlQbXAajqcTS1E6SfIZH05v9V7DCpiVTH1aPgHAglouz9A2Tgm4uM2AO2KRiDyNClfvPP2dEiCE+ciIwj0uIQM0ctctZF8u0gZXL5Ud3dnb4tuM47mN8Abo9NDRUXl1d/THtvk36O04EAuH37e3tXQUJZQ1QEWU/wDtW8U3EGXyK8rdWn2q2o1MDRr6LYPxf8ACdBN9I1lGGsn5hBx46QgTc/jz+YGPjQoRN/sezlGUbto1ok0h1qkSUSqXTMORPIPUPMdjb7+AdACPjz+gWIFyyIN4PqP/NsEQd1KXLJ5lPhQgcZR8B+O/39/e5GA4TMAzn52eTiDPej/A/y40/vhrACfaEIETeDYapizqp29iwzcJUiIBbM6h6UmD7BSQ8BQLGYOA1iRcyoRc+QUgRbfkpnSTkpNAt1SSWprJrwOhzlUrlFYzi6MrKym/toIUuvrMpzs3NHevv7/9DvOtpR6WxbSpEsCcB2EoCRhqDGz9C2y6hRuuVwlSmhqK/Zx4zIsRQZURkRARnbWqLZbCb5hx2gypKQ7fQ5lbplWRTQ3CbEZEREZxmYWtEDaL+F5o4/PC/R8ZxEOqJIC7wjuPvxaC5Odpk/FkXKyjh97JX0Wre2LL3CpexQJ/hidYE3UqEFCYh+FzuPN4JnEPZk/izhsRlmy5J+RnGN7jaf4AL3Ps2AroEawYjY6BbGfgHJbwbYjJN+KEAAAAASUVORK5CYII='\nconst tpluploadactive = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAABCCAYAAAGUUzB+AAAAAXNSR0IArs4c6QAABilJREFUeAHtW3loHUUY//bltTmaREssxqox4G20VV4SbRM8moL6j5QqiFIotbWVFlQ8sSYpTWIVRalXqak9BCkUShERFGmfBPK0OV5qU4IRKyZRbKCNtS/3+ZxvktnsbHZ2Z9/b3SR154+3c3zHb34zOzvXA5AICsqEyuvjItloTakSEBVq82cINVeXaMtpnBNiAt+9VsQJckKFFRFa+Mi7TWIhLGGCWinOkrbA+bgpkVIkcmDff/o2YPwwsJzAy4fbaZW/2LKMlQMnwHLXf9bKosYCaimJGFrQCngTp2SiK7OeKYKCZGOZZV3qyu8V2VDzhUZKb11MG2hRahC+fbUQGnauVJX0kaA+g6Xrf71IGxFb+tH3mlm24VOIxFBakGlpxOgtEtia79m0k8TjcYXUecJeZZRwtKakjJJpXxldxVfhr2VroJBZsDRwc26GmT4IeyZqsfGnuDICE4IPhBABKu890UW9N1bN/BIwWIYGUPmpT0/Bz50xKoe9kqFhiuxpaAAVfuseANK8TM7wA4GFhgZULYmIuwaiHTEhdAbOGQSBgLKZWfSfCTGgfriYNh1fKiM/QRysPzZMSfapQF9AUV5qqirZp1XhQIQq6k+64lzrcSrOvryY5LukG7U3AKDP4kHoSz1Km46xVhiOPn8P5C+ZHMRX7WqA2OCYlYpheUIgNj14PTxXlscZDG+f7Mc4wtoNtpojLyeNjvkIYP3e07Bl/xnVHzo/Hxum5cffKFbzZSK2mHjh4Xx44qMW6Dg/SG2H8rM5H2zShkuYFFK9ccmJhC0QONWXCfolkpWOreawMpZouS0m9E7az/VDbbgLxkTTE72CIJ0UiP7hcaj94U+BaflsvjnI2C6v6pwkByJaXZoFoISdMy+wlJ62VFDiZ/sMUAa4SQ3mFFVGnp2Ixz8gk5tMxzlSoKG5qmSFoijT6wfihAMRKo+cYCs5xwFoDJKFD5nlTQPhXlEvACCWQpzDaoIOhKbEzahuGjk7IHQV9EEwQpJi4pbcRSCzIceciZ4Jg8hOD8LhbXcDbujhrDuZkBCIABld2OwaneO0H7eBEw0JgTDaqnng9hzApUAiwTYIto86QGZVLHzyfQeN4lIA913tBlsgcD2RQtqil6y07q85qfrq7BmEtbujNL173R2A6xM7QRoE7uhfmbEA/u0fhYfIkk8funqGVCDHXgzpi03T0iCe2dcKF3pHYPU7jUKDCOTxD1ug7pceoYxRgTQIXE3JLGo6LwyC7CKJAZIGwRTcePogGKtzgomkloF7jndCkIwbLX9M7lmzmtl9JgXiQN1fdv0Zys+J5vgfg9Ct/meFCdxa1nYOHoQnS3YlrN/b1gLy4z4DPgM+A3OTAW6vSA8x9FbzNTA4/KVX2zd6/86lye5weuq66JuF50Q2hUTQHbyJeK1IcT7m4xm66JvFf0A1taPbmJr05RA1q5OQCFf2cmebTZP9aTERsw3aY/8+EVOEzzoRd16XCQXXOn+iYrdDJbUMtutMK597RSocIltwV2UtpNl4z2BD7RnovjSsFfMs7jkRqcEA7NlQAMvz+LsOS7JT4ZtXCuF0Vwy2HmyD4THJyw4OUeUpEdsfuxHWFuWaQkeCIjtWwLGmbtj19e+msk4WejJGrAldTa/XWJEwobl6gbJ4TRN1vQiuEoEDYX3FfVC+5ia1Ln1DY2QsaOXuIrHC14+00215HC9YQF20gbbcDK68GjmZC+Dg5mWwdPH0QcelgVHYdqgN8OINBv19KFZJPBrAu1F4SFK78S46mKYtTCED63L4++IQJbGnb5SJO/Z0pUdsXX2DSsI/fSPw5MenoOztRpUERE8O6E0rgYTgUQael+C5CgYkFm27EVzpEdVfnSUD3VnIIC3ZOzR9PpdIBfDMBgnJSkuBgZFx6Ztzdn25QgSCwMMpMxK0V89lQJvZktG3knHl1bByOhfLfSKmWsUnwieCf0FdGyx5NzNTMn9ymanlXo7/akxx6xPhE8G/ZuIeQS7N8qKXQcqkTkIi8OawJ9fmPeNXCU/Wydih+cqH6OCf4Ip3/LiJnAlsJMmCebPNP3kNo43chNjfuHPl59pb2MZU+Lk+Az4DBgz8ByWXsgA2IsY/AAAAAElFTkSuQmCC'\n// h5door component\nconst scanningicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4CAYAAAHfgQuIAAAAAXNSR0IArs4c6QAACNhJREFUaAXdW39sVdUdP+e+FpRRqV3GYgtxbSAKSIfpq9JXZP7B/oAYnFOMRhN/DOOyuGzBJYusdF1bNRoh+5Vlm7poYqbimGAM/mFMFHmvENpowNYfIzCjZZMxaAOBIbx39/2c1+/x3HvPue++9r3KdpPXc8735/ne8+v7/Z5bIYynbVN2V1tX9o8MkqgQ8DPh+3MZiLK2JtXsKUAIAdi58/nDHkQpAssfcH5ogStQUWfXbt9GoHRCeRg51L9CMSr4uq1+ivSfNYmcYsHptZG+Nx66xmRQdcAl/kQwE4DiS3BgFfK3dy2xoOVobIe+sIV4090Dq/1CfqdFjAKZttcAwgzEJGDVnFm1Ir0pq/kH+zrFnoNj4gFRNAwCJF7aof3Z86ACgcmgOS2VmkMHcqcZ/uo7R8VLP7yam5Fy3W/e0bDACyhHo345cQOj1UxUlI1hoFuAHB3q75zH9FojA1DSDNsgfbHRl+IkjfSt+/o695l41AOMbm2KcmSob4WeKpoxlslQx5NAzTlm+taVDWose29eqEnxpvHjh2lle1fuoYIoPMIIV/n7e68S3//TewoNrV4SJlCPnz6nZdIuMxa7sDSlpaImOcPjphtoAlOOjWXmpGXsMjeFPHbbFboppfyLN6PBm8WQ+lmBnjNYlR0LLtVtGp513sCGzBmGjJ1Wy5KbgXJl/x7VJm1q6MqbOVIeGOrrbIUEzYhGujv3Pb9QeAr18NPS2lnz0q0yz/AAIwPNMunMAo8U8t+1DXK+ab4pq0gThlCb9qvtvu/faEGVBaqrubTuzZ4lp0ymgIWVUmQqQJ1G3aNBVEeNnm+YsKZVWNG/u1tvE2EZzjamnrnqQUiGFDDOqKuJaVsdm+9YlPgogCB+MGXfGD7GTV1iUqW7s8fgIGzQUKOy892jRqu86tsfHLcy+AV/u0x3ZY/5wv+qlaIKQA8HQxXkOkV6OIVs2PDA22hcMHNDC9N4OPKwYMOIqrTnXzRDr8O2TbuHhS8Wm4pg5eChMTF+prhZYjK8+u6/TBJVf/y2KzVs+YJ6sWPoM7H5tcMahgpvcVohgNf3DM8+ef5EZcfU2IChI6AQADy0AUgs1mJrcn9xPNAb+lmY26owTEQL9ka/IJ6lrswJ47hNCrb582bePnR/+otjnpFGmUgh6Ns3ZdsLvriP3sm3qdlIO+NJWlIjVN/esjTzK/MIAr3riVXYsSV38bnj/idJNwZPeBv39WcedSkD3KnQNmvjBJk46aXWDPZ2vGbCuB5RWKmZSmO6gybNd1gRlwGFy3sGvoGQj5FTLqU8Sj7J1005+jzEUqioMmihGBaHuqlQW2g7ExHSYefgh5i5qsu+WxaK1d+cq9pwfHtfPijeCh1P5pgqhTavC9vaqkf3ijgfUGs1KvASXtzzj4jSQKBgc/Gwh5arDHp/8Myw6L5pgdGFYhXeH2oedpEIlgC8YdtwpWAIbcMPxyNeccsKo6vTRvhMs9S+P7r8kiRdcflDFHb/aNIRVBLFERop39brMIKsAoA2/MXOgKlU2BfXn+HRU6Lnr3+LkNDpUudUaMaREc7JA45M6ysln+l1p8I4V6+UgdilbI8nxZOIarbZkNWAwSX14IdUQ3hYJvu+XtHpkaNhAjOqD+Pi2gjXkKELP4iMAVOztKU1czln7JgQzuyuruVi4OAJBomfvhBNtt5w9Vyx8ooGRXMJKUs3z4mGeeRscRiuz0M66x6mQ3ijll7BCh9NEKlnqXJayUuuoB4lCnG+KVMrBFDlRCqoFCnr2KQC96QSr5femE4ksFyUegxNIOrF1G/uYzq+msK4uLb0vPWDvZmnXTROhczQ9ofBWvnp2edpQt3MsGgpx6Un7hrs7dwRxQUhJRUGye0t1anRs2tor7zT9xF72A91O7cJpY5L8Tq99+f8ppk7SwVGJqerPikDMbyH3xu4m7Igvyh3iF0dccPpZsOTP2++quOZpAGaKSuxgQgE2rtz/dXai8xOxdURV+/rzXRR6bwkMvlLGqjCqXzhFYpKlpqMX3qdtsPalLd2T0/H3+P64jRQBaX5sdwFZ1jYGjK0LlWfCe/XTBY4KBiIfVvlhi60UeMOmiX1EX1Fn00w1wMjqM6GA9n94WwbE3P54OpmcXumUZw+mxcjR04FLnuYxpWRY7xZ3rDsa+I6SnaHnzkX14jFTXVi1syUeD53JJK5C9PT7jvSsrSz1dyMtIHI/nx+3KdIIP7gQ8DfOr9OrN0yOKnQONKpBAC4gK9sSIv9n5wUDzw7XIJDjs5okAvZW1NTFOcYUluljINkZDPgMk4m7i/RMycauqDTzKQ4iWmAYAtsAo0yUHkO05jQd3euMhjkIMmmPysD0z25a+LdosoonW4pZNMtyOxKuv95ko6C9dPdgWnRJ+VTmKKrpkXZl6NkFWLQxnJ093x3oVjSNLsclorRulI+MQoap/3GMKYzFUchdYEbSlyn/F8+sA1rMJDWL2UpPpSB5zHdD3SaH+kk1L/dw0UYRfbjCRnEmmVzrW5VUv7J0sGVg+7kjxyHbR78NoT/yRn/NyhhE2xTngxyG7hZTNJ1XCbZbj2S8E6FBjrNL7jiZMEWztekmPDIrqd3N12//lM69NcyzFZ+fOyMuGflPLHs8ktE9qMT4j/npnTxbVMRgMHR3kJfubS31IuNWz8S0B/3qKxXX+bXTKOjCQbw55fcdpX4lmfdtZeJRY1fsY4obn66t0WvKWzysHnY1hdG7H0Kx7bu/afYFbp+tMkxryQZHzEQiIn8y8tUWi/1mPlCKXGVTXmam2x5GquB3PGJfMxesric7YvZq1/SdTnlZa6Ny8vEGsg9RH7mVH7suQtlRDFis1P1d7ryMNxvlIkMNBnUGvXzT5RKa5g8FamTVyJl6ieuTzlcOso20BSENMf54+LHFGA+mPRDF5M/rg4/kn6baxrELzn9EEfvwk3JQJtQlfUeGWjz8/4Kwi+iHz7NnU9p2jpc9IJn4pMifNFFaRL1Pwnvy5Tc3by4Y8hMGIF2qs9/AVuUcJepv57PAAAAAElFTkSuQmCC'\nconst searchicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAAH7+Yj7AAAAAXNSR0IArs4c6QAABQBJREFUWAntmE1oXUUUx99HQhKSusii0FA/QhdmnbyVWNCllVZBQQQX1qZdFopQpSJSagtKRQx2Yz/sQpCCgrXoVkVxlWRrXEi0SSMGkoVNiCG8xN9/nDPMvW/u+2geQqEPbs7MOf/5z5k5Z86dm1Kp6Dc9Pb0j29zc3J4kBsCvztASYVRJmqAMqNAIplKpUi6X37D+zMzMHWuXdnZ2Koz4zhRla0iC/AvAXrXFMDEx8b7a4SdA6EQNGCfFHFQpp8wYT+90AovBAHKYp2797suw6vn5+f6VlZUNm6JWqwWb6bQt53luBgWN5AKTyhzY7VFPT8/jMVuq7YDb29sHUsZYZ8BvY2Wq7YDEVAngklUgwlb1/aupQVrpRVZ/F/mkALSv8pxJgv83ZUOUcOk8a3FukbuXqtXqVG9v79Lm5uZBdvETPNsv77R+7GH9DR4rJbQhEGYi3gBEoZwXlud2yu50nuzjQkDC4EkvxiYXPlP09/e/ae0O5IkYmyHc2Nj4JTY2aytnvP1QIc4vAWxUFxJocFPCzs7OvpA3N0RZAMDa7IfzYOtXKpUXifgz9I8R6WtE/JjZdi2V+Tjw9K6J7l+CZFDYl9NE+r38sgjA8vDw8KOjo6P/5G3Wz+QhJGWlgydbJJqHBgYG9qhk6lyj36uKrfNuBHmZ8VBkAgwNDT00NjZ2Nw9WX2cd4iNMcIF0eSuFcTrlnvcu43VqgEiFVUHJ291gZpRUIh9n5u08KN/Hs+ekW11d/SNvc4TM6N7XvMWu5AFFfdvTvN2WdzhvaNVX4U1hHCGzjaSMzXSq4im7I2QPp1PGZjq9ElJ2W/LbMkJsNS6Fzej8+yWjU8cREoyf1KG+3ZZs9fP3w/1sVbjC2RjzsKQah4cj5NdHZkxJpdja2trfspE+2dscukA4Pj7+JTPeQHeSNLqTWj6TTWKri0w/MJmTJl2DQmWd/flCxoLfAlv0iE6K7HiZeT83EBoJA3QfOYfXNTxRitxi8Gn67iTJO7x17eS1zog6kZ70+07GPMC23IHCIBeNJA4VgvsE9ud5JnhGCLyrLT4ZlBAzPF+RFD9bUtC/p19bDvpU/5AZVNPv5bfAwT6ls9jp4EIHlbE49jnyJSNlN5bov8POfEo7HGizxxJclZ0+Cu4s7VC96d/A0ZeRxXfpiCjpYKI4TOHUKUhbvg0j7tDEQaWFInDSlCqV7exog4MQhSsNDi0NDg5yf0hfIGyydqWK/Pr6+pztKPyNX9s5soyDvvr96DGL7NpjkDQNZY6vZdeH/neA7tMJedDeZqnB4WXijecMRAhOdNs5cYtT3DYPMswZ6UIz4yCDa2bp6+uznTRV12TMHc+ZmiDjINsf7j1bW1vh5KUG7kYXc8dzpjgzDgK4ZaB6vR5OnOm6JWNudvDrZryZQ8JqKpSYP5Huf1oMPE4Ct303bjaR2XQpon1ZfZxbptTsQxaWr8wOCqivI8YuiIDfZcqOvkUyuP9Mnf0Vh7jE6UcuaK5mzgmX2UE/0AnIwn9YHJBQUBNf6bQm+tr3GQ4eifivE5mjUb+wWeigRkCq1927yDM5hkVWfpPnG0rGb/ZRoeTnenoA/LM8+r6yWmfDr9N41TqMv0aIJ5GFr72mDhqRpL4M+ZjTwXmdyS1HY0hDm4mXUX5AKKdG/Uc1Y7XoK8jXbEAzR9t20Mi6JfOO4uQPvLme6hb/A577Zgf+BfzFgTvq0N4iAAAAAElFTkSuQmCC'\n// httpinjector component\nconst moreicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAAH7+Yj7AAAAAXNSR0IArs4c6QAAAP1JREFUWAntVjEOwjAMdIAXVTwBxMhGZ3gYe5lgRPAE1B9BIBWqQmJVDnUDld0puV5y9tlpCoA8xmHFYmORdwSIvjxi1tdDgxFUXpSey2kibxlH7rIkLXKqND8v8gyTGCYZmdKYvxj20fddnYYt9rFhiucrcI015W8/sQDjqTJXt4xon7axuc7xZETJiwlVqyyh1FplCVXWHP/Ngfbc+YH1uU/9fVLH2N+W3smpLiqf2wH0kMxX5ZpbiLLf7VydQt4sBNz8cbdHDM+ARYahARowUSYZglMJmQ5ETelsKJbl/hd21JdqF+qihwSs3YbETHNagFMwETFTgCojz4En2+hHRf6aBAMAAAAASUVORK5CYII='\nconst addicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAAH7+Yj7AAAAAXNSR0IArs4c6QAABbVJREFUWAntmGloXFUUxzOTNJOFCK2oiUabYItoP9SSZGIyiQSXmmCxtji04hcrClVcKFoXVFw+SRE3KqiIQhCkDZVUBYtYSZvNMQmoLQqt0Ii2QoK2aUoYs/o7L3Oe9827L3lpG/ySB2/Ouef8z3LPXd69k5Mz3zMzMxOxYmpqah6JWjR/WWRziaqqqk6LHvqPg8PxNmGqq6vfcwT8SKBLteGhmB0WAXTGowjdIM4+GxiPJ0TudF+yGxgYiNXV1V01Pj7+hxog85cH8IsKMCmRzpptD4/yOo9gcRuE+7OpqalAo5DzMeVdinCv2zAYjHcYTWesPlUB43ef8kIBfyTUUyc8u8MVjUa39vX17RGZW0+sdouVPBaP+2c1/AL83m0YDEa3Gs1ZFvCkKaTd2tLSEjNlHj4ej1d6BIvV8FTHDEKKcRZhLzLfcqNyW6icdYxMHw5PedO8r/gUWQK6vMkcniz1bDMIQLYJdM/ajMSG9VOoOrc7GKXy8vLWqMKkkUikQl5TprxMFhbjmLZdhwjyqc24Ks6Xug77+/vXEel4gCNZ1SM2HV1OFxUVXW7TOTKpCQW/IxCQUYC7VrDZuMBpA/hJwK9nDI5ApRzreKPU81169GhGt0QucgUCB4WJ/jVr+fZMvAnosgx/hAFZy8D4RtiaW21t7U0yHXC40wZAl8zoH7DpPTJ1FvipN9A4PUfQuaeORA7jTP0KPnsXdpee1Iy6PG2rjRiqE5Pm5ubWDw8PnzJlLh9kJICF6NwMXc8LZOjR38lkMl/N8pSBytRwn+yszLb7wQRNzX8cHBxcC9snxqZDnWcizzGNxJnZdgCZHzK8vri4+BeVXXCXybC0o6PjnM8hkY4y0veoIgytr69fCc5TKtdO5p9ZJ1cxBwN+qrGx8TIT4nZZ5h/vg4BGTUAQD04OVK2dnZ3DJsa3OQB8HMDbfAHrUqnUdyZYePTXQE7wtjJQ20RmPj6HopTlJCuAMqwgazmU/wB/A7SMdyIWi5X39PQMwfseq0MTJc6HhoZuLCgo+Lm7uztUOUz7Jf5/r8C8YxyUoayPdDq9YXp6upmJVQGujIl1BfQMbZlwJzl6dTCb23t7e39Ffl5P6AQzN7I3iZLMRBohka/g9/Md+KmwsPAUW84ZllFRfn7+lZOTk6tIcgP6jdBysQHfB/8Yyysl7TDPvAnKdobTT3AWI8A+VstDXV1dzmU3TADFkPgyKvocFX8ZGa4iuzgsPKP6IBqYoFRsYmKin+RKMX6VXr8U5GShcm5dzfj9EjvZLZu5nRxckA+qFqfHM7ynGxoaloc1lk6FxQoO/50Sh3g7guzcvV8BMvnpndzFRkpKSsrCDqdcYOVfA4K5f2mozyDKqDRSwYPEewO7u2w4X4JjY2Nya5YT/nYmfdpmdDFlLLD7xR9JWjvmm4P05HfA5VRvuaxKMc5+wCToQIUpZ/InaD/M+y2LwflDQfVgR7hkHcCf53KvevwdJeYavnkVfPN+U7lQ84jkyAE6B7LR0VHr9RF9BIeNgCscg/9+VmdY2Q9F7z60zzIyMm2C/h1zYk1NTbmHQTX2JYhCDnd3UoWboQcUqJRqyJn4NW0rlTlIFW9Bf5jtY7vK56PcDi5hz5RD5QjHk+PZeN8cZOd3VhS9/iAbvBhtqvY+fqlH1LqSfQlKLwBvIcGr2QKO8TeYrcq+XDkJ76F692L7vE8ZIGCqtBJnK3a72Qs/tsF8i0RBcjGj9Idoy/F9M1tCu+oulLJfruIjkCK5Ffh6At/vBPkMTFAN6OUuHO2kl1PInmJ+vaW6hVL53w5feyUx/B2qrKxc39bWZl2M6nveBBWI89tYBB/SXikyAnxDoM84GHzOaeWk4pQmEokSNu4WMHfzbkYu3/JR+BfmqpjaKw2doBoIZW4WQdbzbiSofFdLRW4+kgztDt52TjpfZN82TOwSv5gV+Bc8RCfIXqkcngAAAABJRU5ErkJggg=='\nconst clearallicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAAH7+Yj7AAAAAXNSR0IArs4c6QAAA55JREFUWAntV89rE0EUbmKqkBD10L+gh+JJkaSmJ62K9qAiIlVERP0TBKGi/4CexLNgvSjUIiK1goKao7EN/sBzKb0qUmk8qGni98W8cXZ2Zmc3WwpCFh5v5r0333w77+3M7MCA7ymVSm1rjHKgseqKsDtC0QpLPCGD5piCc076HR0yBLxxO/qceluNFyP0lDKigX6QD50wqpe3BugIm9feYk41OTm5tVAoXBgZGfm8vLzcMv2dPoMqlcpudsrl8r7x8fFcKFAPEieDpZ1lg0ErKyu7ms3me3FQt9vteb3PaR6KAeu3zjb0F7EFNBwzYkDb/hJawIwTSYI2XYcS42LAFczn8x+QsLvOhGFwxgUgdgItLS39zOVye2q12ifmCmmoFYvFwWq12pQ40U5AE0gGiPYBS1ynOJCZNmQdEvjIVJDWYBYBfkQz/WsSSGpWrC5gAkFCX3HolRHEQpuo1+s7BZSawFDXIVchb+E/AR3/ITBE3zKsjEzEEEMzYHR09Gar1fKuJRh7sUzsfj/lCqB4bzNBPphYmUH5sIDHIIOQF8joWR+w008wiNpu0V6FqF3WOdDmwMA5iAKTGNiSg7rAegL1gemgSJY6kWjvnGcSQE0wqAOQIfajHmy6+7HZntNjAoBdsDFkcTuCLqFvv4fByW0OB+rH4eHhbTqganOTBEAgAehftIESjHbu6grA1mAQgNX5zhgT1AcWKGzeKtbW1n5nMpnK4uLiO5mUoGjf50Elrzk7O/tL/JGaoDamYH6Ddt9rBhjKTCZTPQE+ZlZAAgtoNpu9jB17mtn0gXGcE1BAG43GN9Rakf2IZx6ldjzC33f1V2DzViCyqpPS6N5QH2OcXuGPcNU9b7vqxsEP7NZxBthiSAy70Bzv4fCPcYvjTYxbIPoT3A7hn+HeYBsfZUu1gsaKfQWxw/xJMCfkAYC95SXsOyCJVrQngnGJbQTRRAR7JZaGaCyCBrE2ausOjtcr5sRJ+0bqD6Fu35gYkQQNYp0aw/m+FyD3IPzArgH0lgkap989kV8hdggv/HRhYeEUdOgKZSWIgs7gWv4E+iQArMWPr5KXksREdWIY/wyn/OmoU95KEJMfxODXkO94q6P6bQm2wBOXaFJiMomVIJ3d+8wDNM9AeibaKzFy4OMk+NedmugP4BQg3lTKfKb2EpQBcVc07YrJfKJjE5QBLqIbTUzmS0xQBhpExZxK44N8jg/yWCqQ/uD/bQX+ADuC+YmFyM4xAAAAAElFTkSuQmCC'\n// positionsimulation component\nconst rightarrowicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAbCAYAAAECMz80AAAAAXNSR0IArs4c6QAAAolJREFUOBGFlDuIE1EUhjOTiWvALpguSMAuQsijU7s0CqIoCBY+sBEUFAUXBVcUH7CixbKVWCiIK4harAiLzWIjFnlUsYkSMIKQGDCFxEec+J0hZ/bO7GwcmJxz/se55947u7GY+VSr1bE9AZZitVrtlcmGcsSvY/V6/aSPi90vzEQIRwBaLhM+lkolS4pbpspbGfClCYpqFn9dQEsZwFPj8fia1rHA0hsXooe9KwreB74/KkEwNxGumrw/hYKIzpIv8H4ql8vb1wlEyMgPGfmEZVnvvLMQUB6I5xCHSB/jXjNDrEzWv+Qpwz+NRmN/GPNrbuaKX0Qktuu6N6Q9j955QOYNIgJBk8nkTC6X+20qPJdOPBwOfzWbzS2mYG0roNqJdCumbyIMrKudwHsMv00EgQ4CyMO5uAxtOY6zI9BBSNmNkJKPRqMvgQ4QcdwjIVOpVDKbzf70BQyYAPe2yPfpcFF/RegtwdY2KSmDKukJ2u32Ztm/FMYupPQep9/vD3G4tI0rGIjc5N4AECospr7I9HcEp9NCsVg8T4z+cwuZPY/80GSeJrMGv8RIx2nkHYmBr0v9YxKG67nO53NVVTRYSafTBzOZzFCxcAw0UJIzv0x+W2savbdte0+hUPiumMbIBkoy0QUmuqc1jT6w1QrX8dXHNJkWOaMz8IuYdcHP8Xi8wkQtBab5PU7MTPSWuNsQv/lvAww2xkfEo4ZxNZFI7Mvn8z82bIDBwfiEeFiNnMEL6iOcwR8f00Tj5Kt/Rn1AMYz3+cBOE13FNPoTtFqtmcFgIP+szU/3JqvNqTgqWp1OJ9ntdpcZrSICVhnznmPFxShDGHN6vd5OzLsgRnwsxzA+DYum1f8AEL05UAjJXgsAAAAASUVORK5CYII='\nconst deleteicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAAH7+Yj7AAAAAXNSR0IArs4c6QAAAcZJREFUWAntWL1ShDAQDswV2ltaUPgAzgD91epz+Dr29na251NA5SvYWdLYCO4iySzLkoQDzhvMNfv/5csmIZlTyvcXYWKapk0URV+oF0VxuUMFf03TXPxqSsWoQNaHdqwlDSU9QFmWUevUDpTIGSUwegPO96j3fjqh5yRGOx9iG0ReOEiEopeuUEuKs6aO3Di/5cajyNKsxZH+MNHsU0pcJLkdpzkIWZYd4HzeHTs1PFVi7ZRmSrnODcGLuM1ZOQF5gcteHHAwoGtKtGBKLq0Lur0D8u7uaqTTM3oi7OOcUdRMec6eGr1RcaJTgKXcxY+eFRBW+RVYXOkl4rb2U2kFhA/uTRzH17qA29pPpRWQJvrqAXDQqackSd6Jl9skNKJKm3UkVTwE5i1Ci3xBT/GspryCvtEOmAvgmPlJd6KAc4B78kHwB9dJOjBYYs9lW4vc9O2AX0XfL+MU1r64s+7P/X6/g4HMk4ITdMV5vmTPIlhVVQGgn3me30rgrrhUw32zCHKwNexAcG5XN9/Bb+xQXdetFLrligslfZf4IuynjFtwx6bjUaVccVutjp39Ejs7CO9m/P/reenbpHuPP+pOBflvO/ADTr+I+vy9Kb8AAAAASUVORK5CYII='\nconst modifyicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAAGXcA1uAAAAAXNSR0IArs4c6QAAAsFJREFUSA29VL9PFFEQfrO3iDEBjDEa4j9gAla7B8JZKH+FHcZGKwELtLglGzkbE4lWFlpItLQysSBBaW4XhbsYEjWxJyqFRlELYfeNM09m3d3bw0MTt9h5+803v9+sUjuP69Xr5uhUg898sMzLhrMGLXyVq+GEcqp1dL1wkhmQprEjABUj4iGDU4jHQjCm8tGRdPzweAuRXM4zOFILjyVKZ2Z5nD+kEqMoe8E5A3rBhgHk5Vxv9Dt5UJR5mSkwrzTxLGtC6fi+bdsDL/yTb0wXi4icZLNWOQgK9wPApyiOl4b8YCzDZY9SjSg4V+m3YEbSIN6lgXbEpAbXCxbJ4BsqWLNKamHVHw3TDuQM7kw4h1pPCfBPcth77lBdT9NOCrvEBMdbHo5UvEI364lbDT6IUaEBk5XSoaKCQMESgrohRi0GQm7OVko0B4tuaZPm8B4VHuUotoRimSYLbkHXCa23XjVrp5KOGh2TqfexEFmWvZUBXpQ0lpzzinbkpAbKc12smayxIA0imLwcv3FYxT8e0oSvogaHBnmvJecdbybCvt6t7wqxDyO8CIjd7ciSwZ4k5IvdzRrs0plsb3dj53RmZqgXCO5VCvlGbJTs0hhvZZq65wC/HWMfTWKT+ny6MTv6kmZ+mfp2Mx8oGVs6atHZXBzz99UhOb5kGg8wzVeTdvUj32nGEPBBFEWvCZtmP3+sIMkYVI8C63zz2sh8PgHa8Quo8Q63inWWsiZXa6O3+dw2QCeO2YG5pGr7GbXnCIA1Re26xbg8LQGG/LobR7BIodtmzMZpx+mMxbHIzDYzqCO4S+IAr7+Q0lIc02r9yrhWyWSc5vI5E4AGBuWZYJDwtTxxr47FPtMi44SWXpR5WdTjPOe/f4PrB4MY4zhlV9jzv85I45fuHpyzsb/7rVrffmSh7njpOgmK0LUZXBn6+hPQqVODIW73AgAAAABJRU5ErkJggg=='\n\nmodule.exports = {\n  appinfoicon,\n  gpsicon,\n  saveicon,\n  h5dooricon,\n  injectoricon,\n  updateversionicon,\n  apimockicon,\n  mock,\n  mockactive,\n  tplupload,\n  tpluploadactive,\n  scanningicon,\n  searchicon,\n  moreicon,\n  addicon,\n  clearallicon,\n  rightarrowicon,\n  deleteicon,\n  modifyicon\n  \n}"
  },
  {
    "path": "miniapp/src/utils/util.js",
    "content": "const formatTime = date => {\n  const year = date.getFullYear()\n  const month = date.getMonth() + 1\n  const day = date.getDate()\n  const hour = date.getHours()\n  const minute = date.getMinutes()\n  const second = date.getSeconds()\n\n  return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')\n}\n\nconst formatNumber = n => {\n  n = n.toString()\n  return n[1] ? n : '0' + n\n}\nconst goToLink = url => {\n  wx.navigateTo({\n    url\n  })\n}\n\nconst getPartUrlByParam = (url, param) => {\n  const reg = /^(?:([A-Za-z]+):)?(\\/{0,3})([0-9.\\-A-Za-z]+)(?::(\\d+))?(?:\\/([^?#]*))?(?:\\?([^#]*))?(?:#(.*))?$/;\n  const res = reg.exec(url)\n  const fields = ['url', 'scheme', 'slash', 'host', 'port', 'path', 'query', 'hash'];\n  return res[fields.indexOf(param)]\n}\n\nconst search2Json = search => {\n  if (search != undefined) {\n    let o = {}\n    const arr = search.split('&')\n    arr.forEach(item => {\n        const a = item.split('=')\n        o = {\n            [a[0]]: a[1]\n        }\n    })\n    return o\n  } else {\n    return {}\n  }\n}\n\nconst isArray = list => {\n  return Object.prototype.toString.call(list).slice(8, -1) == 'Array'\n}\n\nconst deepClone = obj => {\n  if(typeof obj !=\"object\"){\n      return obj\n  }\n  var newObj = {};\n  for (var key in obj) {\n    newObj[key] = deepClone(obj[key])\n  }\n  return newObj;\n}\nconst obj2str = function(obj){\n  if(obj === null) return 'null';\n  let res = '';\n  if(typeof obj !=\"object\"){\n      return res + String(obj);\n  }\n  res += '{\\n ';\n  for (var key in obj) {\n    res += key + ':' + obj2str(obj[key]) +'\\n';\n  }\n  var objSymbols = Object.getOwnPropertySymbols(obj);\n  for(let i = 0; i < objSymbols.length; i++){\n    res += String(objSymbols[i]) + ':' + obj2str(obj[objSymbols[i]]) + '\\n';\n  }\n  res += '}';\n  return res; \n}\n\n\nmodule.exports = {\n  formatTime: formatTime,\n  goToLink,\n  search2Json,\n  getPartUrlByParam,\n  isArray,\n  deepClone,\n  obj2str\n}\n"
  }
]